Change GPL-2.0+/LGPL-2.0+ to GPL-2.0-or-later/LGPL-2.0-or-later
[platform/upstream/procps-ng.git] / procio.c
1 /*
2  * procio.c -- Replace stdio for read and write on files below
3  * proc to be able to read and write large buffers as well.
4  *
5  * Copyright (C) 2017 Werner Fink
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 #ifndef _GNU_SOURCE
23 # define _GNU_SOURCE
24 #endif
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <limits.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <unistd.h>
34
35 typedef struct pcookie {
36         char    *buf;
37         size_t  count;
38         size_t  length;
39         off_t   offset;
40         int     fd;
41         int     delim;
42         int     final:1;
43 } pcookie_t;
44
45 static ssize_t proc_read(void *, char *, size_t);
46 static ssize_t proc_write(void *, const char *, size_t);
47 static int proc_close(void *);
48
49 __extension__
50 static cookie_io_functions_t procio = {
51     .read  = proc_read,
52     .write = proc_write,
53     .seek  = NULL,
54     .close = proc_close,
55 };
56
57 FILE *fprocopen(const char *path, const char *mode)
58 {
59         pcookie_t *cookie = NULL;
60         FILE *handle = NULL;
61         mode_t flags = 0;
62         size_t len = 0;
63         int c, delim;
64
65         if (!mode || !(len = strlen(mode))) {
66                 errno = EINVAL;
67                 goto out;
68         }
69
70         /* No append mode possible */
71         switch (mode[0]) {
72         case 'r':
73                 flags |= O_RDONLY;
74                 break;
75         case 'w':
76                 flags |= O_WRONLY|O_TRUNC;
77                 break;
78         default:
79                 errno = EINVAL;
80                 goto out;
81         }
82
83         delim = ',';                            /* default delimeter is the colon */
84         for (c = 1; c < len; c++) {
85                 switch (mode[c]) {
86                 case '\0':
87                         break;
88                 case '+':
89                         errno = EINVAL;
90                         goto out;
91                 case 'e':
92                         flags |= O_CLOEXEC;
93                         continue;
94                 case 'b':
95                 case 'm':
96                 case 'x':
97                         /* ignore this */
98                         continue;
99                 default:
100                         if (mode[c] == ' ' || (mode[c] >= ',' && mode[c] <= '.') || mode[c] == ':')
101                                 delim = mode[c];
102                         else {
103                                 errno = EINVAL;
104                                 goto out;
105                         }
106                         break;
107                 }
108                 break;
109         }
110
111         cookie = (pcookie_t *)malloc(sizeof(pcookie_t));
112         if (!cookie)
113                 goto out;
114         cookie->count = BUFSIZ;
115         cookie->buf = (char *)malloc(cookie->count);
116         if (!cookie->buf) {
117                 int errsv = errno;
118                 free(cookie);
119                 errno = errsv;
120                 goto out;
121         }
122         cookie->final = 0;
123         cookie->offset = 0;
124         cookie->length = 0;
125         cookie->delim = delim;
126
127         cookie->fd = openat(AT_FDCWD, path, flags);
128         if (cookie->fd < 0) {
129                 int errsv = errno;
130                 free(cookie->buf);
131                 free(cookie);
132                 errno = errsv;
133                 goto out;
134         }
135
136         handle = fopencookie(cookie, mode, procio);
137         if (!handle) {
138                 int errsv = errno;
139                 close(cookie->fd);
140                 free(cookie->buf);
141                 free(cookie);
142                 errno = errsv;
143                 goto out;
144         }
145 out:
146         return handle;
147 }
148
149 static
150 ssize_t proc_read(void *c, char *buf, size_t count)
151 {
152         pcookie_t *cookie = c;
153         ssize_t len = -1;
154         void *ptr;
155
156         if (cookie->count < count) {
157                 ptr = realloc(cookie->buf, count);
158                 if (!ptr)
159                         goto out;
160                 cookie->buf = ptr;
161                 cookie->count = count;
162         }
163
164         while (!cookie->final) {
165                 len = read(cookie->fd, cookie->buf, cookie->count);
166
167                 if (len <= 0) {
168                         if (len == 0) {
169                                 /* EOF */
170                                 cookie->final = 1;
171                                 cookie->buf[cookie->length] = '\0';
172                                 break;
173                         }
174                         goto out;               /* error or done */
175                 }
176
177                 cookie->length = len;
178
179                 if (cookie->length < cookie->count)
180                         continue;
181
182                 /* Likly to small buffer here */
183
184                 lseek(cookie->fd, 0, SEEK_SET); /* reset for a retry */
185
186                 ptr = realloc(cookie->buf, cookie->count += BUFSIZ);
187                 if (!ptr)
188                         goto out;
189                 cookie->buf = ptr;
190         }
191
192         len = count;
193         if (cookie->length - cookie->offset < len)
194                 len = cookie->length - cookie->offset;
195
196         if (len < 0)
197                 len = 0;
198
199         if (len) {
200                 (void)memcpy(buf, cookie->buf+cookie->offset, len);
201                 cookie->offset += len;
202         } else
203                 len = EOF;
204 out:
205         return len;
206 }
207
208 #define LINELEN 4096
209
210 static
211 ssize_t proc_write(void *c, const char *buf, size_t count)
212 {
213         pcookie_t *cookie = c;
214         ssize_t len = -1;
215         void *ptr;
216
217         if (!count) {
218                 len = 0;
219                 goto out;
220         }
221
222                                                     /* NL is the final input */
223         cookie->final = memrchr(buf, '\n', count) ? 1 : 0;
224
225         while (cookie->count < cookie->offset + count) {
226                 ptr = realloc(cookie->buf, cookie->count += count);
227                 if (!ptr)
228                         goto out;
229                 cookie->buf = ptr;
230         }
231
232         len = count;
233         (void)memcpy(cookie->buf+cookie->offset, buf, count);
234         cookie->offset += count;
235
236         if (cookie->final) {
237                 len = write(cookie->fd, cookie->buf, cookie->offset);
238                 if (len < 0 && errno == EINVAL) {
239                         size_t offset;
240                         off_t amount;
241                         char *token;
242                         /*
243                          * Oops buffer might be to large, split buffer into
244                          * pieces at delimeter if provided
245                          */
246                         if (!cookie->delim)
247                                 goto out;               /* Hey dude?! */
248                         offset = 0;
249                         do {
250                                 token = NULL;
251                                 if (cookie->offset > LINELEN)
252                                         token = (char*)memrchr(cookie->buf+offset, cookie->delim, LINELEN);
253                                 else
254                                         token = (char*)memrchr(cookie->buf+offset, '\n', cookie->offset);
255                                 if (token)
256                                         *token = '\n';
257                                 else {
258                                         errno = EINVAL;
259                                         len = -1;
260                                         goto out;       /* Wrong/Missing delimeter? */
261                                 }
262                                 if (offset > 0)
263                                         lseek(cookie->fd, 1, SEEK_CUR);
264
265                                 amount = token-(cookie->buf+offset)+1;
266                                 ptr = cookie->buf+offset;
267
268                                 len = write(cookie->fd, ptr, amount);
269                                 if (len < 1  || len >= cookie->offset)
270                                         break;
271
272                                 offset += len;
273                                 cookie->offset -= len;
274
275                         } while (cookie->offset > 0);
276                 }
277                 if (len > 0)
278                         len = count;
279         }
280 out:
281         return len;
282 }
283
284 static
285 int proc_close(void *c)
286 {
287         pcookie_t *cookie = c;
288         close(cookie->fd);
289         free(cookie->buf);
290         free(cookie);
291         return 0;
292 }