summaryrefslogtreecommitdiff
path: root/procio.c
diff options
context:
space:
mode:
Diffstat (limited to 'procio.c')
-rw-r--r--procio.c293
1 files changed, 293 insertions, 0 deletions
diff --git a/procio.c b/procio.c
new file mode 100644
index 0000000..479243e
--- /dev/null
+++ b/procio.c
@@ -0,0 +1,293 @@
+/*
+ * procio.c -- Replace stdio for read and write on files below
+ * proc to be able to read and write large buffers as well.
+ *
+ * Copyright (C) 2017 Werner Fink
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <libio.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+typedef struct pcookie {
+ char *buf;
+ size_t count;
+ size_t length;
+ off_t offset;
+ int fd;
+ int delim;
+ int final:1;
+} pcookie_t;
+
+static ssize_t proc_read(void *, char *, size_t);
+static ssize_t proc_write(void *, const char *, size_t);
+static int proc_close(void *);
+
+__extension__
+static cookie_io_functions_t procio = {
+ .read = proc_read,
+ .write = proc_write,
+ .seek = NULL,
+ .close = proc_close,
+};
+
+FILE *fprocopen(const char *path, const char *mode)
+{
+ pcookie_t *cookie = NULL;
+ FILE *handle = NULL;
+ mode_t flags = 0;
+ size_t len = 0;
+ int c, delim;
+
+ if (!mode || !(len = strlen(mode))) {
+ errno = EINVAL;
+ goto out;
+ }
+
+ /* No append mode possible */
+ switch (mode[0]) {
+ case 'r':
+ flags |= O_RDONLY;
+ break;
+ case 'w':
+ flags |= O_WRONLY|O_TRUNC;
+ break;
+ default:
+ errno = EINVAL;
+ goto out;
+ }
+
+ delim = ','; /* default delimeter is the colon */
+ for (c = 1; c < len; c++) {
+ switch (mode[c]) {
+ case '\0':
+ break;
+ case '+':
+ errno = EINVAL;
+ goto out;
+ case 'e':
+ flags |= O_CLOEXEC;
+ continue;
+ case 'b':
+ case 'm':
+ case 'x':
+ /* ignore this */
+ continue;
+ default:
+ if (mode[c] == ' ' || (mode[c] >= ',' && mode[c] <= '.') || mode[c] == ':')
+ delim = mode[c];
+ else {
+ errno = EINVAL;
+ goto out;
+ }
+ break;
+ }
+ break;
+ }
+
+ cookie = (pcookie_t *)malloc(sizeof(pcookie_t));
+ if (!cookie)
+ goto out;
+ cookie->count = BUFSIZ;
+ cookie->buf = (char *)malloc(cookie->count);
+ if (!cookie->buf) {
+ int errsv = errno;
+ free(cookie);
+ errno = errsv;
+ goto out;
+ }
+ cookie->final = 0;
+ cookie->offset = 0;
+ cookie->length = 0;
+ cookie->delim = delim;
+
+ cookie->fd = openat(AT_FDCWD, path, flags);
+ if (cookie->fd < 0) {
+ int errsv = errno;
+ free(cookie->buf);
+ free(cookie);
+ errno = errsv;
+ goto out;
+ }
+
+ handle = fopencookie(cookie, mode, procio);
+ if (!handle) {
+ int errsv = errno;
+ close(cookie->fd);
+ free(cookie->buf);
+ free(cookie);
+ errno = errsv;
+ goto out;
+ }
+out:
+ return handle;
+}
+
+static
+ssize_t proc_read(void *c, char *buf, size_t count)
+{
+ pcookie_t *cookie = c;
+ ssize_t len = -1;
+ void *ptr;
+
+ if (cookie->count < count) {
+ ptr = realloc(cookie->buf, count);
+ if (!ptr)
+ goto out;
+ cookie->buf = ptr;
+ cookie->count = count;
+ }
+
+ while (!cookie->final) {
+ len = read(cookie->fd, cookie->buf, cookie->count);
+
+ if (len <= 0) {
+ if (len == 0) {
+ /* EOF */
+ cookie->final = 1;
+ cookie->buf[cookie->length] = '\0';
+ break;
+ }
+ goto out; /* error or done */
+ }
+
+ cookie->length = len;
+
+ if (cookie->length < cookie->count)
+ continue;
+
+ /* Likly to small buffer here */
+
+ lseek(cookie->fd, 0, SEEK_SET); /* reset for a retry */
+
+ ptr = realloc(cookie->buf, cookie->count += BUFSIZ);
+ if (!ptr)
+ goto out;
+ cookie->buf = ptr;
+ }
+
+ len = count;
+ if (cookie->length - cookie->offset < len)
+ len = cookie->length - cookie->offset;
+
+ if (len < 0)
+ len = 0;
+
+ if (len) {
+ (void)memcpy(buf, cookie->buf+cookie->offset, len);
+ cookie->offset += len;
+ } else
+ len = EOF;
+out:
+ return len;
+}
+
+#define LINELEN 4096
+
+static
+ssize_t proc_write(void *c, const char *buf, size_t count)
+{
+ pcookie_t *cookie = c;
+ ssize_t len = -1;
+ void *ptr;
+
+ if (!count) {
+ len = 0;
+ goto out;
+ }
+
+ /* NL is the final input */
+ cookie->final = memrchr(buf, '\n', count) ? 1 : 0;
+
+ while (cookie->count < cookie->offset + count) {
+ ptr = realloc(cookie->buf, cookie->count += count);
+ if (!ptr)
+ goto out;
+ cookie->buf = ptr;
+ }
+
+ len = count;
+ (void)memcpy(cookie->buf+cookie->offset, buf, count);
+ cookie->offset += count;
+
+ if (cookie->final) {
+ len = write(cookie->fd, cookie->buf, cookie->offset);
+ if (len < 0 && errno == EINVAL) {
+ size_t offset;
+ off_t amount;
+ char *token;
+ /*
+ * Oops buffer might be to large, split buffer into
+ * pieces at delimeter if provided
+ */
+ if (!cookie->delim)
+ goto out; /* Hey dude?! */
+ offset = 0;
+ do {
+ token = NULL;
+ if (cookie->offset > LINELEN)
+ token = (char*)memrchr(cookie->buf+offset, ',', LINELEN);
+ else
+ token = (char*)memrchr(cookie->buf+offset, '\n', LINELEN);
+ if (token)
+ *token = '\n';
+ else {
+ errno = EINVAL;
+ len = -1;
+ goto out; /* Wrong/Missing delimeter? */
+ }
+ if (offset > 0)
+ lseek(cookie->fd, 1, SEEK_CUR);
+
+ amount = token-(cookie->buf+offset)+1;
+ ptr = cookie->buf+offset;
+
+ len = write(cookie->fd, ptr, amount);
+ if (len < 1 || len >= cookie->offset)
+ break;
+
+ offset += len;
+ cookie->offset -= len;
+
+ } while (cookie->offset > 0);
+ }
+ if (len > 0)
+ len = count;
+ }
+out:
+ return len;
+}
+
+static
+int proc_close(void *c)
+{
+ pcookie_t *cookie = c;
+ close(cookie->fd);
+ free(cookie->buf);
+ free(cookie);
+ return 0;
+}