]> arthur.barton.de Git - netdata.git/blob - src/procfile.c
Merge pull request #1615 from lowfive/discord_notify
[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 #define PF_CHAR_IS_SEPARATOR    ' '
131 #define PF_CHAR_IS_NEWLINE      'N'
132 #define PF_CHAR_IS_WORD         'W'
133 #define PF_CHAR_IS_QUOTE        'Q'
134 #define PF_CHAR_IS_OPEN         'O'
135 #define PF_CHAR_IS_CLOSE        'C'
136
137 void procfile_close(procfile *ff) {
138     debug(D_PROCFILE, PF_PREFIX ": Closing file '%s'", procfile_filename(ff));
139
140     if(likely(ff->lines)) pflines_free(ff->lines);
141     if(likely(ff->words)) pfwords_free(ff->words);
142
143     if(likely(ff->fd != -1)) close(ff->fd);
144     freez(ff);
145 }
146
147 static inline void procfile_parser(procfile *ff) {
148     // debug(D_PROCFILE, PF_PREFIX ": Parsing file '%s'", ff->filename);
149
150     register char *s = ff->data, *e = &ff->data[ff->len], *t = ff->data;
151     register char *separators = ff->separators;
152     char quote = 0;
153     size_t l = 0, w = 0, opened = 0;
154
155     ff->lines = pflines_add(ff->lines, w);
156
157     while(likely(s < e)) {
158         // we are not at the end
159
160         switch(separators[(unsigned char)(*s)]) {
161             case PF_CHAR_IS_OPEN:
162                 if(s == t) {
163                     opened++;
164                     t = ++s;
165                 }
166                 else if(opened) {
167                     opened++;
168                     s++;
169                 }
170                 else
171                     s++;
172                 break;
173
174             case PF_CHAR_IS_CLOSE:
175                 if(opened) {
176                     opened--;
177
178                     if(!opened) {
179                         *s = '\0';
180                         ff->words = pfwords_add(ff->words, t);
181                         ff->lines->lines[l].words++;
182                         w++;
183
184                         t = ++s;
185                     }
186                     else
187                         s++;
188                 }
189                 else
190                     s++;
191                 break;
192
193             case PF_CHAR_IS_QUOTE:
194                 if(unlikely(!quote && s == t)) {
195                     // quote opened at the beginning
196                     quote = *s;
197                     t = ++s;
198                 }
199                 else if(unlikely(quote && quote == *s)) {
200                     // quote closed
201                     quote = 0;
202
203                     *s = '\0';
204                     ff->words = pfwords_add(ff->words, t);
205                     ff->lines->lines[l].words++;
206                     w++;
207
208                     t = ++s;
209                 }
210                 else
211                     s++;
212                 break;
213
214             case PF_CHAR_IS_SEPARATOR:
215                 if(unlikely(quote || opened)) {
216                     // we are inside a quote
217                     s++;
218                     break;
219                 }
220
221                 if(unlikely(s == t)) {
222                     // skip all leading white spaces
223                     t = ++s;
224                     break;
225                 }
226
227                 // end of word
228                 *s = '\0';
229
230                 ff->words = pfwords_add(ff->words, t);
231                 ff->lines->lines[l].words++;
232                 w++;
233
234                 t = ++s;
235                 break;
236
237             case PF_CHAR_IS_NEWLINE:
238                 // end of line
239                 *s = '\0';
240
241                 ff->words = pfwords_add(ff->words, t);
242                 ff->lines->lines[l].words++;
243                 w++;
244
245                 // debug(D_PROCFILE, PF_PREFIX ":   ended line %d with %d words", l, ff->lines->lines[l].words);
246
247                 ff->lines = pflines_add(ff->lines, w);
248                 l++;
249
250                 t = ++s;
251                 break;
252
253             default:
254                 s++;
255                 break;
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         ff->lines->lines[l].words++;
270     }
271 }
272
273 procfile *procfile_readall(procfile *ff) {
274     // debug(D_PROCFILE, PF_PREFIX ": Reading file '%s'.", ff->filename);
275
276     ff->len = 0;    // zero the used size
277     ssize_t r = 1;  // read at least once
278     while(r > 0) {
279         ssize_t s = ff->len;
280         ssize_t x = ff->size - s;
281
282         if(unlikely(!x)) {
283             debug(D_PROCFILE, PF_PREFIX ": Expanding data buffer for file '%s'.", procfile_filename(ff));
284             ff = reallocz(ff, sizeof(procfile) + ff->size + PROCFILE_INCREMENT_BUFFER);
285             ff->size += PROCFILE_INCREMENT_BUFFER;
286         }
287
288         debug(D_PROCFILE, "Reading file '%s', from position %ld with length %lu", procfile_filename(ff), s, ff->size - s);
289         r = read(ff->fd, &ff->data[s], ff->size - s);
290         if(unlikely(r == -1)) {
291             if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot read from file '%s'", procfile_filename(ff));
292             procfile_close(ff);
293             return NULL;
294         }
295
296         ff->len += r;
297     }
298
299     // debug(D_PROCFILE, "Rewinding file '%s'", ff->filename);
300     if(unlikely(lseek(ff->fd, 0, SEEK_SET) == -1)) {
301         if(unlikely(!(ff->flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot rewind on file '%s'.", procfile_filename(ff));
302         procfile_close(ff);
303         return NULL;
304     }
305
306     pflines_reset(ff->lines);
307     pfwords_reset(ff->words);
308     procfile_parser(ff);
309
310     if(unlikely(procfile_adaptive_initial_allocation)) {
311         if(unlikely(ff->len > procfile_max_allocation)) procfile_max_allocation = ff->len;
312         if(unlikely(ff->lines->len > procfile_max_lines)) procfile_max_lines = ff->lines->len;
313         if(unlikely(ff->words->len > procfile_max_words)) procfile_max_words = ff->words->len;
314     }
315
316     // debug(D_PROCFILE, "File '%s' updated.", ff->filename);
317     return ff;
318 }
319
320 static void procfile_set_separators(procfile *ff, const char *separators) {
321     static char def[256] = { [0 ... 255] = 0 };
322
323     if(unlikely(!def[255])) {
324         // this is thread safe
325         // we check that the last byte is non-zero
326         // if it is zero, multiple threads may be executing this at the same time
327         // setting in def[] the exact same values
328         int i;
329         for(i = 0; likely(i < 256) ;i++) {
330             if(unlikely(i == '\n' || i == '\r')) def[i] = PF_CHAR_IS_NEWLINE;
331             else if(unlikely(isspace(i) || !isprint(i))) def[i] = PF_CHAR_IS_SEPARATOR;
332             else def[i] = PF_CHAR_IS_WORD;
333         }
334     }
335
336     // copy the default
337     char *ffs = ff->separators, *ffd = def, *ffe = &def[256];
338     while(likely(ffd != ffe)) *ffs++ = *ffd++;
339
340     // set the separators
341     if(unlikely(!separators))
342         separators = " \t=|";
343
344     ffs = ff->separators;
345     const char *s = separators;
346     while(likely(*s))
347         ffs[(int)*s++] = PF_CHAR_IS_SEPARATOR;
348 }
349
350 void procfile_set_quotes(procfile *ff, const char *quotes) {
351     // remove all quotes
352     int i;
353     for(i = 0; i < 256 ; i++)
354         if(unlikely(ff->separators[i] == PF_CHAR_IS_QUOTE))
355             ff->separators[i] = PF_CHAR_IS_WORD;
356
357     // if nothing given, return
358     if(unlikely(!quotes || !*quotes))
359         return;
360
361     // set the quotes
362     char *ffs = ff->separators;
363     const char *s = quotes;
364     while(likely(*s))
365         ffs[(int)*s++] = PF_CHAR_IS_QUOTE;
366 }
367
368 void procfile_set_open_close(procfile *ff, const char *open, const char *close) {
369     // remove all open/close
370     int i;
371     for(i = 0; i < 256 ; i++)
372         if(unlikely(ff->separators[i] == PF_CHAR_IS_OPEN || ff->separators[i] == PF_CHAR_IS_CLOSE))
373             ff->separators[i] = PF_CHAR_IS_WORD;
374
375     // if nothing given, return
376     if(unlikely(!open || !*open || !close || !*close))
377         return;
378
379     // set the openings
380     char *ffs = ff->separators;
381     const char *s = open;
382     while(likely(*s))
383         ffs[(int)*s++] = PF_CHAR_IS_OPEN;
384
385     s = close;
386     while(likely(*s))
387         ffs[(int)*s++] = PF_CHAR_IS_CLOSE;
388 }
389
390 procfile *procfile_open(const char *filename, const char *separators, uint32_t flags) {
391     debug(D_PROCFILE, PF_PREFIX ": Opening file '%s'", filename);
392
393     int fd = open(filename, O_RDONLY, 0666);
394     if(unlikely(fd == -1)) {
395         if(unlikely(!(flags & PROCFILE_FLAG_NO_ERROR_ON_FILE_IO))) error(PF_PREFIX ": Cannot open file '%s'", filename);
396         return NULL;
397     }
398
399     size_t size = (unlikely(procfile_adaptive_initial_allocation)) ? procfile_max_allocation : PROCFILE_INCREMENT_BUFFER;
400     procfile *ff = mallocz(sizeof(procfile) + size);
401
402     //strncpyz(ff->filename, filename, FILENAME_MAX);
403     ff->filename[0] = '\0';
404
405     ff->fd = fd;
406     ff->size = size;
407     ff->len = 0;
408     ff->flags = flags;
409
410     ff->lines = pflines_new();
411     ff->words = pfwords_new();
412
413     procfile_set_separators(ff, separators);
414
415     debug(D_PROCFILE, "File '%s' opened.", filename);
416     return ff;
417 }
418
419 procfile *procfile_reopen(procfile *ff, const char *filename, const char *separators, uint32_t flags) {
420     if(unlikely(!ff)) return procfile_open(filename, separators, flags);
421
422     if(likely(ff->fd != -1)) close(ff->fd);
423
424     ff->fd = open(filename, O_RDONLY, 0666);
425     if(unlikely(ff->fd == -1)) {
426         procfile_close(ff);
427         return NULL;
428     }
429
430     //strncpyz(ff->filename, filename, FILENAME_MAX);
431     ff->filename[0] = '\0';
432
433     ff->flags = flags;
434
435     // do not do the separators again if NULL is given
436     if(likely(separators)) procfile_set_separators(ff, separators);
437
438     return ff;
439 }
440
441 // ----------------------------------------------------------------------------
442 // example parsing of procfile data
443
444 void procfile_print(procfile *ff) {
445     size_t lines = procfile_lines(ff), l;
446     char *s;
447
448     debug(D_PROCFILE, "File '%s' with %zu lines and %zu words", procfile_filename(ff), ff->lines->len, ff->words->len);
449
450     for(l = 0; likely(l < lines) ;l++) {
451         size_t words = procfile_linewords(ff, l);
452
453         debug(D_PROCFILE, " line %zu starts at word %zu and has %zu words", l, ff->lines->lines[l].first, ff->lines->lines[l].words);
454
455         size_t w;
456         for(w = 0; likely(w < words) ;w++) {
457             s = procfile_lineword(ff, l, w);
458             debug(D_PROCFILE, "     [%zu.%zu] '%s'", l, w, s);
459         }
460     }
461 }