]> arthur.barton.de Git - netdata.git/blob - src/procfile.c
all required system headers in common.h; some progress on health variables
[netdata.git] / src / procfile.c
1 #include "common.h"
2
3 #define PF_PREFIX "PROCFILE"
4
5 #define PFWORDS_INCREASE_STEP 200
6 #define PFLINES_INCREASE_STEP 10
7 #define PROCFILE_INCREMENT_BUFFER 512
8
9 int procfile_adaptive_initial_allocation = 0;
10
11 // if adaptive allocation is set, these store the
12 // max values we have seen so far
13 uint32_t procfile_max_lines = PFLINES_INCREASE_STEP;
14 uint32_t procfile_max_words = PFWORDS_INCREASE_STEP;
15 size_t procfile_max_allocation = PROCFILE_INCREMENT_BUFFER;
16
17 // ----------------------------------------------------------------------------
18 // An array of words
19
20
21 pfwords *pfwords_add(pfwords *fw, char *str) {
22         // debug(D_PROCFILE, PF_PREFIX ":       adding word No %d: '%s'", fw->len, str);
23
24         if(unlikely(fw->len == fw->size)) {
25                 // debug(D_PROCFILE, PF_PREFIX ":       expanding words");
26
27                 pfwords *new = realloc(fw, sizeof(pfwords) + (fw->size + PFWORDS_INCREASE_STEP) * sizeof(char *));
28                 if(unlikely(!new)) {
29                         error(PF_PREFIX ":      failed to expand words");
30                         free(fw);
31                         return NULL;
32                 }
33                 fw = new;
34                 fw->size += PFWORDS_INCREASE_STEP;
35         }
36
37         fw->words[fw->len++] = str;
38
39         return fw;
40 }
41
42 pfwords *pfwords_new(void) {
43         // debug(D_PROCFILE, PF_PREFIX ":       initializing words");
44
45         uint32_t size = (procfile_adaptive_initial_allocation) ? procfile_max_words : PFWORDS_INCREASE_STEP;
46
47         pfwords *new = malloc(sizeof(pfwords) + size * sizeof(char *));
48         if(unlikely(!new)) return NULL;
49
50         new->len = 0;
51         new->size = size;
52         return new;
53 }
54
55 void pfwords_reset(pfwords *fw) {
56         // debug(D_PROCFILE, PF_PREFIX ":       reseting words");
57         fw->len = 0;
58 }
59
60 void pfwords_free(pfwords *fw) {
61         // debug(D_PROCFILE, PF_PREFIX ":       freeing words");
62
63         free(fw);
64 }
65
66
67 // ----------------------------------------------------------------------------
68 // An array of lines
69
70 pflines *pflines_add(pflines *fl, uint32_t first_word) {
71         // debug(D_PROCFILE, PF_PREFIX ":       adding line %d at word %d", fl->len, first_word);
72
73         if(unlikely(fl->len == fl->size)) {
74                 // debug(D_PROCFILE, PF_PREFIX ":       expanding lines");
75
76                 pflines *new = realloc(fl, sizeof(pflines) + (fl->size + PFLINES_INCREASE_STEP) * sizeof(ffline));
77                 if(unlikely(!new)) {
78                         error(PF_PREFIX ":      failed to expand lines");
79                         free(fl);
80                         return NULL;
81                 }
82                 fl = new;
83                 fl->size += PFLINES_INCREASE_STEP;
84         }
85
86         fl->lines[fl->len].words = 0;
87         fl->lines[fl->len++].first = first_word;
88
89         return fl;
90 }
91
92 pflines *pflines_new(void) {
93         // debug(D_PROCFILE, PF_PREFIX ":       initializing lines");
94
95         uint32_t size = (unlikely(procfile_adaptive_initial_allocation)) ? procfile_max_words : PFLINES_INCREASE_STEP;
96
97         pflines *new = malloc(sizeof(pflines) + size * sizeof(ffline));
98         if(unlikely(!new)) return NULL;
99
100         new->len = 0;
101         new->size = size;
102         return new;
103 }
104
105 void pflines_reset(pflines *fl) {
106         // debug(D_PROCFILE, PF_PREFIX ":       reseting lines");
107
108         fl->len = 0;
109 }
110
111 void pflines_free(pflines *fl) {
112         // debug(D_PROCFILE, PF_PREFIX ":       freeing lines");
113
114         free(fl);
115 }
116
117
118 // ----------------------------------------------------------------------------
119 // The procfile
120
121 #define PF_CHAR_IS_SEPARATOR    ' '
122 #define PF_CHAR_IS_NEWLINE              'N'
123 #define PF_CHAR_IS_WORD                 'W'
124 #define PF_CHAR_IS_QUOTE        'Q'
125 #define PF_CHAR_IS_OPEN         'O'
126 #define PF_CHAR_IS_CLOSE        'C'
127
128 void procfile_close(procfile *ff) {
129         debug(D_PROCFILE, PF_PREFIX ": Closing file '%s'", ff->filename);
130
131         if(likely(ff->lines)) pflines_free(ff->lines);
132         if(likely(ff->words)) pfwords_free(ff->words);
133
134         if(likely(ff->fd != -1)) close(ff->fd);
135         free(ff);
136 }
137
138 procfile *procfile_parser(procfile *ff) {
139         debug(D_PROCFILE, PF_PREFIX ": Parsing file '%s'", ff->filename);
140
141         char *s = ff->data, *e = &ff->data[ff->len], *t = ff->data, quote = 0;
142         uint32_t l = 0, w = 0;
143         int opened = 0;
144
145         ff->lines = pflines_add(ff->lines, w);
146         if(unlikely(!ff->lines)) goto cleanup;
147
148         while(likely(s < e)) {
149                 // we are not at the end
150
151                 switch(ff->separators[(uint8_t)(*s)]) {
152                         case PF_CHAR_IS_OPEN:
153                                 if(s == t) {
154                                         opened++;
155                                         t = ++s;
156                                 }
157                                 else if(opened) {
158                                         opened++;
159                                         s++;
160                                 }
161                                 else
162                                         s++;
163                                 continue;
164
165                         case PF_CHAR_IS_CLOSE:
166                                 if(opened) {
167                                         opened--;
168
169                                         if(!opened) {
170                                                 *s = '\0';
171                                                 ff->words = pfwords_add(ff->words, t);
172                                                 if(unlikely(!ff->words)) goto cleanup;
173
174                                                 ff->lines->lines[l].words++;
175                                                 w++;
176
177                                                 t = ++s;
178                                         }
179                                         else
180                                                 s++;
181                                 }
182                                 else
183                                         s++;
184                                 continue;
185
186                         case PF_CHAR_IS_QUOTE:
187                                 if(unlikely(!quote && s == t)) {
188                                         // quote opened at the beginning
189                                         quote = *s;
190                                         t = ++s;
191                                 }
192                                 else if(unlikely(quote && quote == *s)) {
193                                         // quote closed
194                                         quote = 0;
195
196                                         *s = '\0';
197                                         ff->words = pfwords_add(ff->words, t);
198                                         if(unlikely(!ff->words)) goto cleanup;
199
200                                         ff->lines->lines[l].words++;
201                                         w++;
202
203                                         t = ++s;
204                                 }
205                                 else
206                                         s++;
207                                 continue;
208
209                         case PF_CHAR_IS_SEPARATOR:
210                                 if(unlikely(quote || opened)) {
211                                         // we are inside a quote
212                                         s++;
213                                         continue;
214                                 }
215
216                                 if(unlikely(s == t)) {
217                                         // skip all leading white spaces
218                                         t = ++s;
219                                         continue;
220                                 }
221
222                                 // end of word
223                                 *s = '\0';
224
225                                 ff->words = pfwords_add(ff->words, t);
226                                 if(unlikely(!ff->words)) goto cleanup;
227
228                                 ff->lines->lines[l].words++;
229                                 w++;
230
231                                 t = ++s;
232                                 continue;
233
234                         case PF_CHAR_IS_NEWLINE:
235                                 // end of line
236                                 *s = '\0';
237
238                                 ff->words = pfwords_add(ff->words, t);
239                                 if(unlikely(!ff->words)) goto cleanup;
240
241                                 ff->lines->lines[l].words++;
242                                 w++;
243
244                                 // debug(D_PROCFILE, PF_PREFIX ":       ended line %d with %d words", l, ff->lines->lines[l].words);
245
246                                 ff->lines = pflines_add(ff->lines, w);
247                                 if(unlikely(!ff->lines)) goto cleanup;
248                                 l++;
249
250                                 t = ++s;
251                                 continue;
252
253                         default:
254                                 s++;
255                                 continue;
256                 }
257         }
258
259         if(likely(s > t && t < e)) {
260                 // the last word
261                 if(likely(ff->len < ff->size))
262                         *s = '\0';
263                 else {
264                         // we are going to loose the last byte
265                         ff->data[ff->size - 1] = '\0';
266                 }
267
268                 ff->words = pfwords_add(ff->words, t);
269                 if(unlikely(!ff->words)) goto cleanup;
270
271                 ff->lines->lines[l].words++;
272                 w++;
273         }
274
275         return ff;
276
277 cleanup:
278         error(PF_PREFIX ": Failed to parse file '%s'", ff->filename);
279         procfile_close(ff);
280         return NULL;
281 }
282
283 procfile *procfile_readall(procfile *ff) {
284         debug(D_PROCFILE, PF_PREFIX ": Reading file '%s'.", ff->filename);
285
286         ssize_t s, r = 1, x;
287         ff->len = 0;
288
289         while(likely(r > 0)) {
290                 s = ff->len;
291                 x = ff->size - s;
292
293                 if(!x) {
294                         debug(D_PROCFILE, PF_PREFIX ": Expanding data buffer for file '%s'.", ff->filename);
295
296                         procfile *new = realloc(ff, sizeof(procfile) + ff->size + PROCFILE_INCREMENT_BUFFER);
297                         if(unlikely(!new)) {
298                                 error(PF_PREFIX ": Cannot allocate memory for file '%s'", ff->filename);
299                                 procfile_close(ff);
300                                 return NULL;
301                         }
302                         ff = new;
303                         ff->size += PROCFILE_INCREMENT_BUFFER;
304                 }
305
306                 debug(D_PROCFILE, "Reading file '%s', from position %ld with length %lu", ff->filename, s, ff->size - s);
307                 r = read(ff->fd, &ff->data[s], ff->size - s);
308                 if(unlikely(r == -1)) {
309                         if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot read from file '%s'", ff->filename);
310                         procfile_close(ff);
311                         return NULL;
312                 }
313
314                 ff->len += r;
315         }
316
317         debug(D_PROCFILE, "Rewinding file '%s'", ff->filename);
318         if(unlikely(lseek(ff->fd, 0, SEEK_SET) == -1)) {
319                 if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot rewind on file '%s'.", ff->filename);
320                 procfile_close(ff);
321                 return NULL;
322         }
323
324         pflines_reset(ff->lines);
325         pfwords_reset(ff->words);
326
327         ff = procfile_parser(ff);
328
329         if(unlikely(procfile_adaptive_initial_allocation)) {
330                 if(unlikely(ff->len > procfile_max_allocation)) procfile_max_allocation = ff->len;
331                 if(unlikely(ff->lines->len > procfile_max_lines)) procfile_max_lines = ff->lines->len;
332                 if(unlikely(ff->words->len > procfile_max_words)) procfile_max_words = ff->words->len;
333         }
334
335         debug(D_PROCFILE, "File '%s' updated.", ff->filename);
336         return ff;
337 }
338
339 static void procfile_set_separators(procfile *ff, const char *separators) {
340         static char def[256] = { [0 ... 255] = 0 };
341         int i;
342
343         if(unlikely(!def[255])) {
344                 // this is thread safe
345                 // we check that the last byte is non-zero
346                 // if it is zero, multiple threads may be executing this at the same time
347                 // setting in def[] the exact same values
348                 for(i = 0; likely(i < 256) ;i++) {
349                         if(unlikely(i == '\n' || i == '\r')) def[i] = PF_CHAR_IS_NEWLINE;
350                         else if(unlikely(isspace(i) || !isprint(i))) def[i] = PF_CHAR_IS_SEPARATOR;
351                         else def[i] = PF_CHAR_IS_WORD;
352                 }
353         }
354
355         // copy the default
356         char *ffs = ff->separators, *ffd = def, *ffe = &def[256];
357         while(likely(ffd != ffe)) *ffs++ = *ffd++;
358
359         // set the separators
360         if(unlikely(!separators))
361                 separators = " \t=|";
362
363         ffs = ff->separators;
364         const char *s = separators;
365         while(likely(*s))
366                 ffs[(int)*s++] = PF_CHAR_IS_SEPARATOR;
367 }
368
369 void procfile_set_quotes(procfile *ff, const char *quotes) {
370         // remove all quotes
371         int i;
372         for(i = 0; i < 256 ; i++)
373                 if(ff->separators[i] == PF_CHAR_IS_QUOTE)
374                         ff->separators[i] = PF_CHAR_IS_WORD;
375
376         // if nothing given, return
377         if(unlikely(!quotes || !*quotes))
378                 return;
379
380         // set the quotes
381         char *ffs = ff->separators;
382         const char *s = quotes;
383         while(likely(*s))
384                 ffs[(int)*s++] = PF_CHAR_IS_QUOTE;
385 }
386
387 void procfile_set_open_close(procfile *ff, const char *open, const char *close) {
388         // remove all open/close
389         int i;
390         for(i = 0; i < 256 ; i++)
391                 if(ff->separators[i] == PF_CHAR_IS_OPEN || ff->separators[i] == PF_CHAR_IS_CLOSE)
392                         ff->separators[i] = PF_CHAR_IS_WORD;
393
394         // if nothing given, return
395         if(unlikely(!open || !*open || !close || !*close))
396                 return;
397
398         // set the openings
399         char *ffs = ff->separators;
400         const char *s = open;
401         while(likely(*s))
402                 ffs[(int)*s++] = PF_CHAR_IS_OPEN;
403
404         s = close;
405         while(likely(*s))
406                 ffs[(int)*s++] = PF_CHAR_IS_CLOSE;
407 }
408
409 procfile *procfile_open(const char *filename, const char *separators, uint32_t flags) {
410         debug(D_PROCFILE, PF_PREFIX ": Opening file '%s'", filename);
411
412         int fd = open(filename, O_RDONLY, 0666);
413         if(unlikely(fd == -1)) {
414                 if(unlikely(!(flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot open file '%s'", filename);
415                 return NULL;
416         }
417
418         size_t size = (unlikely(procfile_adaptive_initial_allocation)) ? procfile_max_allocation : PROCFILE_INCREMENT_BUFFER;
419         procfile *ff = malloc(sizeof(procfile) + size);
420         if(unlikely(!ff)) {
421                 error(PF_PREFIX ": Cannot allocate memory for file '%s'", filename);
422                 close(fd);
423                 return NULL;
424         }
425
426         strncpyz(ff->filename, filename, FILENAME_MAX);
427
428         ff->fd = fd;
429         ff->size = size;
430         ff->len = 0;
431         ff->flags = flags;
432
433         ff->lines = pflines_new();
434         ff->words = pfwords_new();
435
436         if(unlikely(!ff->lines || !ff->words)) {
437                 error(PF_PREFIX ": Cannot initialize parser for file '%s'", filename);
438                 procfile_close(ff);
439                 return NULL;
440         }
441
442         procfile_set_separators(ff, separators);
443
444         debug(D_PROCFILE, "File '%s' opened.", filename);
445         return ff;
446 }
447
448 procfile *procfile_reopen(procfile *ff, const char *filename, const char *separators, uint32_t flags) {
449         if(unlikely(!ff)) return procfile_open(filename, separators, flags);
450
451         if(likely(ff->fd != -1)) close(ff->fd);
452
453         ff->fd = open(filename, O_RDONLY, 0666);
454         if(unlikely(ff->fd == -1)) {
455                 procfile_close(ff);
456                 return NULL;
457         }
458
459         strncpyz(ff->filename, filename, FILENAME_MAX);
460
461         ff->flags = flags;
462
463         // do not do the separators again if NULL is given
464         if(likely(separators)) procfile_set_separators(ff, separators);
465
466         return ff;
467 }
468
469 // ----------------------------------------------------------------------------
470 // example parsing of procfile data
471
472 void procfile_print(procfile *ff) {
473         uint32_t lines = procfile_lines(ff), l;
474         uint32_t words, w;
475         char *s;
476
477         debug(D_PROCFILE, "File '%s' with %u lines and %u words", ff->filename, ff->lines->len, ff->words->len);
478
479         for(l = 0; likely(l < lines) ;l++) {
480                 words = procfile_linewords(ff, l);
481
482                 debug(D_PROCFILE, "     line %u starts at word %u and has %u words", l, ff->lines->lines[l].first, ff->lines->lines[l].words);
483
484                 for(w = 0; likely(w < words) ;w++) {
485                         s = procfile_lineword(ff, l, w);
486                         debug(D_PROCFILE, "             [%u.%u] '%s'", l, w, s);
487                 }
488         }
489 }