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