diff --git a/Makefile.am b/Makefile.am index 9d5af83f..4961deff 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/proc/libprocps.sym b/proc/libprocps.sym index 75f334a9..5382415c 100644 --- a/proc/libprocps.sym +++ b/proc/libprocps.sym @@ -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 index 00000000..0b3bdb6c --- /dev/null +++ b/proc/procio.3 @@ -0,0 +1,80 @@ +'\" t -*- coding: UTF-8 -*- +.\" +.\" This file describes the readproc interface to the /proc filesystem +.\" +.\" Copyright 2018 Werner Fink +.\" +.\" 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 +.br +.B #include +.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 [ ] +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 diff --git a/proc/procio.c b/proc/procio.c new file mode 100644 index 00000000..479243e0 --- /dev/null +++ b/proc/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 index 00000000..c9655610 --- /dev/null +++ b/proc/procio.h @@ -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