--- /dev/null
+'\" t -*- coding: UTF-8 -*-
+.\"
+.\" This file describes the readproc interface to the /proc filesystem
+.\"
+.\" Copyright 2018 Werner Fink <werner@suse.de>
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of this
+.\" manual under the conditions for verbatim copying, provided that the
+.\" entire resulting derived work is distributed under the terms of a
+.\" permission notice identical to this one
+.\"
+.\" Formatted or processed versions of this manual, if unaccompanied by
+.\" the source, must acknowledge the copyright and authors of this work.
+.\"
+.TH PROCIO 3 "16 January 2018" "Linux Manpage" "Linux Programmer's Manual"
+.SH NAME
+fprocopen \- stream open functions on files below /proc/##
+.SH SYNOPSIS
+.B #define _GNU_SOURCE
+.br
+.B #include <stdio.h>
+.br
+.B #include <proc/procio.h>
+.sp
+.BI "FILE *fprocopen(const char *path, const char *mode);
+
+.SH DESCRIPTION
+
+The
+.B fprocopen
+function opens files below
+.I /proc/##
+whose name is the string to by path and associates a stream with it.
+The argument
+.I mode
+points to a string beginning with one of the following sequences
+.TP
+.B r
+Open a file below
+.I /proc/##
+for reading even large buffers. The stream is positioned at
+the beginning of the file.
+.TP
+.BR w [ <del> ]
+Open a file below
+.I /proc/##
+for writing even large buffers. The optional delimeter character
+can be one of the follwoing
+.BR '\ ' ,\ ',' ,\ '.' ,\ and\ ':'
+where the default is the colon
+.BR ',' .
+This allows to split very large input lines into pieces at this
+delimeter and write each of them to the opened file below
+.IR /proc/## .
+.TP
+.B e
+The underlying file descriptor will be closed if you use any
+of the ‘exec...’ functions within your code.
+.PP
+The internal API allows to use stdio functions to read and write
+large buffers below
+.IR /proc/## .
+.PP
+.SH SEE ALSO
+.BR fopen (3),
+.br
+.BR fopencookie (3)
+.br
+.BR setvbuf (3)
+.br
+.BR lseek (3)
+.PP
+.SH COPYRIGHT
+2018 Werner Fink,
+.SH AUTHOR
+Werner Fink <werner@suse.de>
--- /dev/null
+/*
+ * 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;
+}