]> granicus.if.org Git - procps-ng/commitdiff
Add flexible buffered I/O based on fopencookie(3)
authorWerner Fink <werner@suse.de>
Thu, 18 Jan 2018 10:26:37 +0000 (11:26 +0100)
committerCraig Small <csmall@enc.com.au>
Wed, 28 Feb 2018 09:46:58 +0000 (20:46 +1100)
to be able to read and write large buffers below /proc.
The buffers and file offsets are handled dynamically
on the required buffer size at read, that is lseek(2)
is used to determine this size. Large buffers at
write are split at a delimeter into pieces and also
lseek(2) is used to write each of them.

Signed-off-by: Werner Fink <werner@suse.de>
Makefile.am
proc/libprocps.sym
proc/procio.3 [new file with mode: 0644]
proc/procio.c [new file with mode: 0644]
proc/procio.h [new file with mode: 0644]

index 9d5af83fb899abf2a44748132cea174367455e35..4961deff343d3ba32e98c979b9e9a987a0b61f01 100644 (file)
@@ -233,6 +233,8 @@ proc_libprocps_la_SOURCES = \
        proc/escape.h \
        proc/numa.c \
        proc/numa.h \
+       proc/procio.c \
+       proc/procio.h \
        proc/procps-private.h \
        proc/procps.h \
        proc/pwcache.c \
@@ -258,6 +260,7 @@ proc_libprocps_la_include_HEADERS = \
        proc/devname.h \
        proc/escape.h \
        proc/numa.h \
+       proc/procio.h \
        proc/procps.h \
        proc/pwcache.h \
        proc/readproc.h \
@@ -269,6 +272,7 @@ proc_libprocps_la_include_HEADERS = \
        proc/whattime.h
 
 dist_man_MANS += \
+       proc/procio.3 \
        proc/openproc.3 \
        proc/readproc.3 \
        proc/readproctab.3
index 75f334a9440d5fa231b7cc0f2824a8aaa40e26d0..5382415c0624234c43ed6698a9c2b25c29f247e2 100644 (file)
@@ -8,6 +8,7 @@ global:
        escape_str;
        escape_strlist;
        escaped_copy;
+       fprocopen;
        free_slabinfo;
        freeproc;
        get_ns_id;
diff --git a/proc/procio.3 b/proc/procio.3
new file mode 100644 (file)
index 0000000..0b3bdb6
--- /dev/null
@@ -0,0 +1,80 @@
+'\" 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>
diff --git a/proc/procio.c b/proc/procio.c
new file mode 100644 (file)
index 0000000..479243e
--- /dev/null
@@ -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;
+}
diff --git a/proc/procio.h b/proc/procio.h
new file mode 100644 (file)
index 0000000..c965561
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef PROCPS_PROC_PROCIO_H
+#define PROCPS_PROC_PROCIO_H
+
+#include "procps.h"
+
+EXTERN_C_BEGIN
+
+extern FILE *fprocopen(const char *, const char *);
+
+EXTERN_C_END
+#endif