Line data Source code
1 : // SPDX-License-Identifier: GPL-2.0-or-later
2 : /* CSV
3 : * Copyright (C) 2013,2020 Cumulus Networks, Inc.
4 : */
5 :
6 : #ifdef HAVE_CONFIG_H
7 : #include "config.h"
8 : #endif
9 :
10 : #include <zebra.h>
11 :
12 : #include <stdio.h>
13 : #include <stdlib.h>
14 : #include <string.h>
15 : #include <stdarg.h>
16 : #include <assert.h>
17 : #include <sys/queue.h>
18 : #include <fcntl.h>
19 : #include <unistd.h>
20 : #include "csv.h"
21 :
22 : #define DEBUG_E 1
23 : #define DEBUG_V 1
24 :
25 : #define log_error(fmt, ...) \
26 : do { \
27 : if (DEBUG_E) \
28 : fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
29 : __LINE__, __func__, ##__VA_ARGS__); \
30 : } while (0)
31 :
32 : #define log_verbose(fmt, ...) \
33 : do { \
34 : if (DEBUG_V) \
35 : fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
36 : __LINE__, __func__, __VA_ARGS__); \
37 : } while (0)
38 :
39 : struct _csv_field_t_ {
40 : TAILQ_ENTRY(_csv_field_t_) next_field;
41 : char *field;
42 : int field_len;
43 : };
44 :
45 : struct _csv_record_t_ {
46 : TAILQ_HEAD(, _csv_field_t_) fields;
47 : TAILQ_ENTRY(_csv_record_t_) next_record;
48 : char *record;
49 : int rec_len;
50 : };
51 :
52 : struct _csv_t_ {
53 : TAILQ_HEAD(, _csv_record_t_) records;
54 : char *buf;
55 : int buflen;
56 : int csv_len;
57 : int pointer;
58 : int num_recs;
59 : };
60 :
61 :
62 0 : int csvlen(csv_t *csv)
63 : {
64 0 : return (csv->csv_len);
65 : }
66 :
67 0 : csv_t *csv_init(csv_t *csv, char *buf, int buflen)
68 : {
69 0 : if (csv == NULL) {
70 0 : csv = malloc(sizeof(csv_t));
71 0 : if (csv == NULL) {
72 0 : log_error("CSV Malloc failed\n");
73 0 : return NULL;
74 : }
75 : }
76 0 : memset(csv, 0, sizeof(csv_t));
77 :
78 0 : csv->buf = buf;
79 0 : csv->buflen = buflen;
80 0 : TAILQ_INIT(&(csv->records));
81 0 : return (csv);
82 : }
83 :
84 0 : void csv_clean(csv_t *csv)
85 : {
86 0 : csv_record_t *rec;
87 0 : csv_record_t *rec_n;
88 :
89 0 : rec = TAILQ_FIRST(&(csv->records));
90 0 : while (rec != NULL) {
91 0 : rec_n = TAILQ_NEXT(rec, next_record);
92 0 : csv_remove_record(csv, rec);
93 0 : rec = rec_n;
94 : }
95 0 : }
96 :
97 0 : void csv_free(csv_t *csv)
98 : {
99 0 : if (csv != NULL) {
100 0 : free(csv);
101 : }
102 0 : }
103 :
104 0 : static void csv_init_record(csv_record_t *record)
105 : {
106 0 : TAILQ_INIT(&(record->fields));
107 0 : record->rec_len = 0;
108 : }
109 :
110 0 : csv_record_t *csv_record_iter(csv_t *csv)
111 : {
112 0 : return (TAILQ_FIRST(&(csv->records)));
113 : }
114 :
115 0 : csv_record_t *csv_record_iter_next(csv_record_t *rec)
116 : {
117 0 : if (!rec)
118 : return NULL;
119 0 : return (TAILQ_NEXT(rec, next_record));
120 : }
121 :
122 0 : char *csv_field_iter(csv_record_t *rec, csv_field_t **fld)
123 : {
124 0 : if (!rec)
125 : return NULL;
126 0 : *fld = TAILQ_FIRST(&(rec->fields));
127 0 : return ((*fld)->field);
128 : }
129 :
130 0 : char *csv_field_iter_next(csv_field_t **fld)
131 : {
132 0 : *fld = TAILQ_NEXT(*fld, next_field);
133 0 : if ((*fld) == NULL) {
134 : return NULL;
135 : }
136 0 : return ((*fld)->field);
137 : }
138 :
139 0 : int csv_field_len(csv_field_t *fld)
140 : {
141 0 : if (fld) {
142 0 : return fld->field_len;
143 : }
144 : return 0;
145 : }
146 :
147 0 : static void csv_decode_record(csv_record_t *rec)
148 : {
149 0 : char *curr = rec->record;
150 0 : char *field;
151 0 : csv_field_t *fld;
152 :
153 0 : field = strpbrk(curr, ",");
154 0 : while (field != NULL) {
155 0 : fld = malloc(sizeof(csv_field_t));
156 0 : if (fld) {
157 0 : TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field);
158 0 : fld->field = curr;
159 0 : fld->field_len = field - curr;
160 : }
161 0 : curr = field + 1;
162 0 : field = strpbrk(curr, ",");
163 : }
164 0 : field = strstr(curr, "\n");
165 0 : if (!field)
166 : return;
167 :
168 0 : fld = malloc(sizeof(csv_field_t));
169 0 : if (fld) {
170 0 : fld->field = curr;
171 0 : fld->field_len = field - curr;
172 0 : TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field);
173 : }
174 : }
175 :
176 0 : static csv_field_t *csv_add_field_to_record(csv_t *csv, csv_record_t *rec,
177 : char *col)
178 : {
179 0 : csv_field_t *fld;
180 0 : char *str = rec->record;
181 0 : int rlen = rec->rec_len;
182 0 : int blen = csv->buflen;
183 :
184 0 : fld = malloc(sizeof(csv_field_t));
185 0 : if (!fld) {
186 0 : log_error("field malloc failed\n");
187 : /* more cleanup needed */
188 0 : return NULL;
189 : }
190 0 : TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field);
191 0 : fld->field = str + rlen;
192 0 : fld->field_len = snprintf((str + rlen), (blen - rlen), "%s", col);
193 0 : rlen += fld->field_len;
194 0 : rec->rec_len = rlen;
195 0 : return fld;
196 : }
197 :
198 0 : csv_record_t *csv_encode(csv_t *csv, int count, ...)
199 : {
200 0 : int tempc;
201 0 : va_list list;
202 0 : char *buf = csv->buf;
203 0 : int len = csv->buflen;
204 0 : int pointer = csv->pointer;
205 0 : char *str = NULL;
206 0 : char *col;
207 0 : csv_record_t *rec;
208 0 : csv_field_t *fld;
209 :
210 0 : if (buf) {
211 0 : str = buf + pointer;
212 : } else {
213 : /* allocate sufficient buffer */
214 0 : str = (char *)malloc(csv->buflen);
215 0 : if (!str) {
216 0 : log_error("field str malloc failed\n");
217 0 : return NULL;
218 : }
219 : }
220 :
221 0 : va_start(list, count);
222 0 : rec = malloc(sizeof(csv_record_t));
223 0 : if (!rec) {
224 0 : log_error("record malloc failed\n");
225 0 : if (!buf)
226 0 : free(str);
227 0 : va_end(list);
228 0 : return NULL;
229 : }
230 0 : csv_init_record(rec);
231 0 : rec->record = str;
232 0 : TAILQ_INSERT_TAIL(&(csv->records), rec, next_record);
233 0 : csv->num_recs++;
234 :
235 : /**
236 : * Iterate through the fields passed as a variable list and add them
237 : */
238 0 : for (tempc = 0; tempc < count; tempc++) {
239 0 : col = va_arg(list, char *);
240 0 : fld = csv_add_field_to_record(csv, rec, col);
241 0 : if (!fld) {
242 0 : log_error("fld malloc failed\n");
243 0 : csv_remove_record(csv, rec);
244 0 : va_end(list);
245 0 : return NULL;
246 : }
247 0 : if (tempc < (count - 1)) {
248 0 : rec->rec_len += snprintf((str + rec->rec_len),
249 0 : (len - rec->rec_len), ",");
250 : }
251 : }
252 0 : rec->rec_len +=
253 0 : snprintf((str + rec->rec_len), (len - rec->rec_len), "\n");
254 0 : va_end(list);
255 0 : csv->csv_len += rec->rec_len;
256 0 : csv->pointer += rec->rec_len;
257 0 : return (rec);
258 : }
259 :
260 0 : int csv_num_records(csv_t *csv)
261 : {
262 0 : if (csv) {
263 0 : return csv->num_recs;
264 : }
265 : return 0;
266 : }
267 :
268 0 : csv_record_t *csv_encode_record(csv_t *csv, csv_record_t *rec, int count, ...)
269 : {
270 0 : int tempc;
271 0 : va_list list;
272 0 : char *str;
273 0 : char *col;
274 0 : csv_field_t *fld = NULL;
275 0 : int i;
276 :
277 0 : va_start(list, count);
278 0 : str = csv_field_iter(rec, &fld);
279 0 : if (!fld) {
280 0 : va_end(list);
281 0 : return NULL;
282 : }
283 :
284 0 : for (tempc = 0; tempc < count; tempc++) {
285 0 : col = va_arg(list, char *);
286 0 : for (i = 0; i < fld->field_len; i++) {
287 0 : str[i] = col[i];
288 : }
289 0 : str = csv_field_iter_next(&fld);
290 : }
291 0 : va_end(list);
292 0 : return (rec);
293 : }
294 :
295 0 : csv_record_t *csv_append_record(csv_t *csv, csv_record_t *rec, int count, ...)
296 : {
297 0 : int tempc;
298 0 : va_list list;
299 0 : int len = csv->buflen, tlen;
300 0 : char *str;
301 0 : csv_field_t *fld;
302 0 : char *col;
303 :
304 0 : if (csv->buf) {
305 : /* not only works with discrete bufs */
306 : return NULL;
307 : }
308 :
309 0 : if (!rec) {
310 : /* create a new rec */
311 0 : rec = calloc(1, sizeof(csv_record_t));
312 0 : if (!rec) {
313 0 : log_error("record malloc failed\n");
314 0 : return NULL;
315 : }
316 0 : csv_init_record(rec);
317 0 : rec->record = calloc(1, csv->buflen);
318 0 : if (!rec->record) {
319 0 : log_error("field str malloc failed\n");
320 0 : free(rec);
321 0 : return NULL;
322 : }
323 0 : csv_insert_record(csv, rec);
324 : }
325 :
326 0 : str = rec->record;
327 :
328 0 : va_start(list, count);
329 :
330 0 : if (rec->rec_len && (str[rec->rec_len - 1] == '\n'))
331 0 : str[rec->rec_len - 1] = ',';
332 :
333 : /**
334 : * Iterate through the fields passed as a variable list and add them
335 : */
336 0 : tlen = rec->rec_len;
337 0 : for (tempc = 0; tempc < count; tempc++) {
338 0 : col = va_arg(list, char *);
339 0 : fld = csv_add_field_to_record(csv, rec, col);
340 0 : if (!fld) {
341 0 : log_error("fld malloc failed\n");
342 0 : break;
343 : }
344 0 : if (tempc < (count - 1)) {
345 0 : rec->rec_len += snprintf((str + rec->rec_len),
346 0 : (len - rec->rec_len), ",");
347 : }
348 : }
349 0 : rec->rec_len +=
350 0 : snprintf((str + rec->rec_len), (len - rec->rec_len), "\n");
351 0 : va_end(list);
352 0 : csv->csv_len += (rec->rec_len - tlen);
353 0 : csv->pointer += (rec->rec_len - tlen);
354 0 : return (rec);
355 : }
356 :
357 0 : int csv_serialize(csv_t *csv, char *msgbuf, int msglen)
358 : {
359 0 : csv_record_t *rec;
360 0 : int offset = 0;
361 :
362 0 : if (!csv || !msgbuf)
363 : return -1;
364 :
365 0 : rec = csv_record_iter(csv);
366 0 : while (rec != NULL) {
367 0 : if ((offset + rec->rec_len) >= msglen)
368 : return -1;
369 0 : offset += sprintf(&msgbuf[offset], "%s", rec->record);
370 0 : rec = csv_record_iter_next(rec);
371 : }
372 :
373 : return 0;
374 : }
375 :
376 0 : void csv_clone_record(csv_t *csv, csv_record_t *in_rec, csv_record_t **out_rec)
377 : {
378 0 : char *curr;
379 0 : csv_record_t *rec;
380 :
381 : /* first check if rec belongs to this csv */
382 0 : if (!csv_is_record_valid(csv, in_rec)) {
383 0 : log_error("rec not in this csv\n");
384 0 : return;
385 : }
386 :
387 : /* only works with csv with discrete bufs */
388 0 : if (csv->buf) {
389 0 : log_error(
390 : "un-supported for this csv type - single buf detected\n");
391 0 : return;
392 : }
393 :
394 : /* create a new rec */
395 0 : rec = calloc(1, sizeof(csv_record_t));
396 0 : if (!rec) {
397 0 : log_error("record malloc failed\n");
398 0 : return;
399 : }
400 0 : csv_init_record(rec);
401 0 : curr = calloc(1, csv->buflen);
402 0 : if (!curr) {
403 0 : log_error("field str malloc failed\n");
404 0 : free(rec);
405 0 : return;
406 : }
407 0 : rec->record = curr;
408 0 : rec->rec_len = in_rec->rec_len;
409 0 : strlcpy(rec->record, in_rec->record, csv->buflen);
410 :
411 : /* decode record into fields */
412 0 : csv_decode_record(rec);
413 :
414 0 : *out_rec = rec;
415 : }
416 :
417 0 : void csv_remove_record(csv_t *csv, csv_record_t *rec)
418 : {
419 0 : csv_field_t *fld = NULL, *p_fld;
420 :
421 : /* first check if rec belongs to this csv */
422 0 : if (!csv_is_record_valid(csv, rec)) {
423 0 : log_error("rec not in this csv\n");
424 0 : return;
425 : }
426 :
427 : /* remove fields */
428 0 : csv_field_iter(rec, &fld);
429 0 : while (fld) {
430 0 : p_fld = fld;
431 0 : csv_field_iter_next(&fld);
432 0 : TAILQ_REMOVE(&(rec->fields), p_fld, next_field);
433 0 : free(p_fld);
434 : }
435 :
436 0 : TAILQ_REMOVE(&(csv->records), rec, next_record);
437 :
438 0 : csv->num_recs--;
439 0 : csv->csv_len -= rec->rec_len;
440 0 : csv->pointer -= rec->rec_len;
441 0 : if (!csv->buf)
442 0 : free(rec->record);
443 0 : free(rec);
444 : }
445 :
446 0 : void csv_insert_record(csv_t *csv, csv_record_t *rec)
447 : {
448 : /* first check if rec already in csv */
449 0 : if (csv_is_record_valid(csv, rec)) {
450 0 : log_error("rec already in this csv\n");
451 0 : return;
452 : }
453 :
454 : /* we can only insert records if no buf was supplied during csv init */
455 0 : if (csv->buf) {
456 0 : log_error(
457 : "un-supported for this csv type - single buf detected\n");
458 0 : return;
459 : }
460 :
461 : /* do we go beyond the max buf set for this csv ?*/
462 0 : if ((csv->csv_len + rec->rec_len) > csv->buflen) {
463 0 : log_error("cannot insert - exceeded buf size\n");
464 0 : return;
465 : }
466 :
467 0 : TAILQ_INSERT_TAIL(&(csv->records), rec, next_record);
468 0 : csv->num_recs++;
469 0 : csv->csv_len += rec->rec_len;
470 0 : csv->pointer += rec->rec_len;
471 : }
472 :
473 0 : csv_record_t *csv_concat_record(csv_t *csv, csv_record_t *rec1,
474 : csv_record_t *rec2)
475 : {
476 0 : char *curr;
477 0 : char *ret;
478 0 : csv_record_t *rec;
479 :
480 : /* first check if rec1 and rec2 belong to this csv */
481 0 : if (!csv_is_record_valid(csv, rec1)
482 0 : || !csv_is_record_valid(csv, rec2)) {
483 0 : log_error("rec1 and/or rec2 invalid\n");
484 0 : return NULL;
485 : }
486 :
487 : /* we can only concat records if no buf was supplied during csv init */
488 0 : if (csv->buf) {
489 0 : log_error(
490 : "un-supported for this csv type - single buf detected\n");
491 0 : return NULL;
492 : }
493 :
494 : /* create a new rec */
495 0 : rec = calloc(1, sizeof(csv_record_t));
496 0 : if (!rec) {
497 0 : log_error("record malloc failed\n");
498 0 : return NULL;
499 : }
500 0 : csv_init_record(rec);
501 :
502 0 : curr = (char *)calloc(1, csv->buflen);
503 0 : if (!curr) {
504 0 : log_error("field str malloc failed\n");
505 0 : goto out_rec;
506 : }
507 0 : rec->record = curr;
508 :
509 : /* concat the record string */
510 0 : ret = strstr(rec1->record, "\n");
511 0 : if (!ret) {
512 0 : log_error("rec1 str not properly formatted\n");
513 0 : goto out_curr;
514 : }
515 :
516 0 : snprintf(curr, (int)(ret - rec1->record + 1), "%s", rec1->record);
517 0 : strcat(curr, ",");
518 :
519 0 : ret = strstr(rec2->record, "\n");
520 0 : if (!ret) {
521 0 : log_error("rec2 str not properly formatted\n");
522 0 : goto out_curr;
523 : }
524 :
525 0 : snprintf((curr + strlen(curr)), (int)(ret - rec2->record + 1), "%s",
526 : rec2->record);
527 0 : strcat(curr, "\n");
528 0 : rec->rec_len = strlen(curr);
529 :
530 : /* paranoia */
531 0 : assert(csv->buflen
532 : > (csv->csv_len - rec1->rec_len - rec2->rec_len + rec->rec_len));
533 :
534 : /* decode record into fields */
535 0 : csv_decode_record(rec);
536 :
537 : /* now remove rec1 and rec2 and insert rec into this csv */
538 0 : csv_remove_record(csv, rec1);
539 0 : csv_remove_record(csv, rec2);
540 0 : csv_insert_record(csv, rec);
541 :
542 0 : return rec;
543 :
544 0 : out_curr:
545 0 : free(curr);
546 0 : out_rec:
547 0 : free(rec);
548 0 : return NULL;
549 : }
550 :
551 0 : void csv_decode(csv_t *csv, char *inbuf)
552 : {
553 0 : char *buf;
554 0 : char *pos;
555 0 : csv_record_t *rec;
556 :
557 0 : buf = (inbuf) ? inbuf : csv->buf;
558 0 : assert(buf);
559 :
560 0 : pos = strpbrk(buf, "\n");
561 0 : while (pos != NULL) {
562 0 : rec = calloc(1, sizeof(csv_record_t));
563 0 : if (!rec)
564 : return;
565 0 : csv_init_record(rec);
566 0 : TAILQ_INSERT_TAIL(&(csv->records), rec, next_record);
567 0 : csv->num_recs++;
568 0 : if (csv->buf)
569 0 : rec->record = buf;
570 : else {
571 0 : rec->record = calloc(1, csv->buflen);
572 0 : if (!rec->record) {
573 0 : log_error("field str malloc failed\n");
574 0 : return;
575 : }
576 0 : strncpy(rec->record, buf, pos - buf + 1);
577 : }
578 0 : rec->rec_len = pos - buf + 1;
579 : /* decode record into fields */
580 0 : csv_decode_record(rec);
581 0 : buf = pos + 1;
582 0 : pos = strpbrk(buf, "\n");
583 : }
584 : }
585 :
586 0 : int csv_is_record_valid(csv_t *csv, csv_record_t *in_rec)
587 : {
588 0 : csv_record_t *rec;
589 0 : int valid = 0;
590 :
591 0 : rec = csv_record_iter(csv);
592 0 : while (rec) {
593 0 : if (rec == in_rec) {
594 : valid = 1;
595 : break;
596 : }
597 0 : rec = csv_record_iter_next(rec);
598 : }
599 :
600 0 : return valid;
601 : }
602 :
603 0 : void csv_dump(csv_t *csv)
604 : {
605 0 : csv_record_t *rec;
606 0 : csv_field_t *fld;
607 0 : char *str;
608 :
609 0 : rec = csv_record_iter(csv);
610 0 : while (rec != NULL) {
611 0 : str = csv_field_iter(rec, &fld);
612 0 : while (str != NULL) {
613 0 : fprintf(stderr, "%s\n", str);
614 0 : str = csv_field_iter_next(&fld);
615 : }
616 0 : rec = csv_record_iter_next(rec);
617 : }
618 0 : }
619 :
620 : #ifdef TEST_CSV
621 :
622 : static int get_memory_usage(pid_t pid)
623 : {
624 : int fd, data, stack;
625 : char buf[4096], status_child[PATH_MAX];
626 : char *vm;
627 :
628 : snprintf(status_child, sizeof(status_child), "/proc/%d/status", pid);
629 : fd = open(status_child, O_RDONLY);
630 : if (fd < 0)
631 : return -1;
632 :
633 : read(fd, buf, 4095);
634 : buf[4095] = '\0';
635 : close(fd);
636 :
637 : data = stack = 0;
638 :
639 : vm = strstr(buf, "VmData:");
640 : if (vm) {
641 : sscanf(vm, "%*s %d", &data);
642 : }
643 : vm = strstr(buf, "VmStk:");
644 : if (vm) {
645 : sscanf(vm, "%*s %d", &stack);
646 : }
647 :
648 : return data + stack;
649 : }
650 :
651 : int main()
652 : {
653 : char buf[10000];
654 : csv_t csv;
655 : int i;
656 : csv_record_t *rec;
657 : char hdr1[32], hdr2[32];
658 :
659 : log_verbose("Mem: %d\n", get_memory_usage(getpid()));
660 : csv_init(&csv, buf, 256);
661 : snprintf(hdr1, sizeof(hdr1), "%4d", 0);
662 : snprintf(hdr2, sizeof(hdr2), "%4d", 1);
663 : log_verbose("(%zu/%zu/%d/%d)\n", strlen(hdr1), strlen(hdr2), atoi(hdr1),
664 : atoi(hdr2));
665 : rec = csv_encode(&csv, 2, hdr1, hdr2);
666 : csv_encode(&csv, 4, "name", "age", "sex", "hei");
667 : csv_encode(&csv, 3, NULL, "0", NULL);
668 : csv_encode(&csv, 2, "p", "35");
669 : for (i = 0; i < 50; i++) {
670 : csv_encode(&csv, 2, "p", "10");
671 : }
672 : csv_encode(&csv, 2, "pdfadfadfadsadsaddfdfdsfdsd", "35444554545454545");
673 : log_verbose("%s\n", buf);
674 : snprintf(hdr1, sizeof(hdr1), "%4d", csv.csv_len);
675 : snprintf(hdr2, sizeof(hdr2), "%4d", 1);
676 : log_verbose("(%zu/%zu/%d/%d)\n", strlen(hdr1), strlen(hdr2), atoi(hdr1),
677 : atoi(hdr2));
678 : rec = csv_encode_record(&csv, rec, 2, hdr1, hdr2);
679 : log_verbose("(%d/%d)\n%s\n", rec->rec_len, csv.csv_len, buf);
680 :
681 : log_verbose("Mem: %d\n", get_memory_usage(getpid()));
682 : csv_clean(&csv);
683 : log_verbose("Mem: %d\n", get_memory_usage(getpid()));
684 : csv_init(&csv, buf, 256);
685 : csv_decode(&csv, NULL);
686 : log_verbose("%s", "AFTER DECODE\n");
687 : csv_dump(&csv);
688 : csv_clean(&csv);
689 : log_verbose("Mem: %d\n", get_memory_usage(getpid()));
690 : }
691 : #endif
|