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