Coverage Report

Created: 2026-01-01 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/frr/lib/csv.c
Line
Count
Source
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
0
#define DEBUG_E 1
23
#define DEBUG_V 1
24
25
#define log_error(fmt, ...)                                                    \
26
0
  do {                                                                   \
27
0
    if (DEBUG_E)                                                   \
28
0
      fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__,          \
29
0
        __LINE__, __func__, ##__VA_ARGS__);            \
30
0
  } 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
int csvlen(csv_t *csv)
63
0
{
64
0
  return (csv->csv_len);
65
0
}
66
67
csv_t *csv_init(csv_t *csv, char *buf, int buflen)
68
0
{
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
0
    }
75
0
  }
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
0
}
83
84
void csv_clean(csv_t *csv)
85
0
{
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
0
  }
95
0
}
96
97
void csv_free(csv_t *csv)
98
0
{
99
0
  if (csv != NULL) {
100
0
    free(csv);
101
0
  }
102
0
}
103
104
static void csv_init_record(csv_record_t *record)
105
0
{
106
0
  TAILQ_INIT(&(record->fields));
107
0
  record->rec_len = 0;
108
0
}
109
110
csv_record_t *csv_record_iter(csv_t *csv)
111
0
{
112
0
  return (TAILQ_FIRST(&(csv->records)));
113
0
}
114
115
csv_record_t *csv_record_iter_next(csv_record_t *rec)
116
0
{
117
0
  if (!rec)
118
0
    return NULL;
119
0
  return (TAILQ_NEXT(rec, next_record));
120
0
}
121
122
char *csv_field_iter(csv_record_t *rec, csv_field_t **fld)
123
0
{
124
0
  if (!rec)
125
0
    return NULL;
126
0
  *fld = TAILQ_FIRST(&(rec->fields));
127
0
  return ((*fld)->field);
128
0
}
129
130
char *csv_field_iter_next(csv_field_t **fld)
131
0
{
132
0
  *fld = TAILQ_NEXT(*fld, next_field);
133
0
  if ((*fld) == NULL) {
134
0
    return NULL;
135
0
  }
136
0
  return ((*fld)->field);
137
0
}
138
139
int csv_field_len(csv_field_t *fld)
140
0
{
141
0
  if (fld) {
142
0
    return fld->field_len;
143
0
  }
144
0
  return 0;
145
0
}
146
147
static void csv_decode_record(csv_record_t *rec)
148
0
{
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
0
    }
161
0
    curr = field + 1;
162
0
    field = strpbrk(curr, ",");
163
0
  }
164
0
  field = strstr(curr, "\n");
165
0
  if (!field)
166
0
    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
0
  }
174
0
}
175
176
static csv_field_t *csv_add_field_to_record(csv_t *csv, csv_record_t *rec,
177
              char *col)
178
0
{
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
0
  }
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
0
}
197
198
csv_record_t *csv_encode(csv_t *csv, int count, ...)
199
0
{
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
0
  } 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
0
    }
219
0
  }
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
0
  }
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
0
    }
247
0
    if (tempc < (count - 1)) {
248
0
      rec->rec_len += snprintf((str + rec->rec_len),
249
0
             (len - rec->rec_len), ",");
250
0
    }
251
0
  }
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
0
}
259
260
int csv_num_records(csv_t *csv)
261
0
{
262
0
  if (csv) {
263
0
    return csv->num_recs;
264
0
  }
265
0
  return 0;
266
0
}
267
268
csv_record_t *csv_encode_record(csv_t *csv, csv_record_t *rec, int count, ...)
269
0
{
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
0
  }
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
0
    }
289
0
    str = csv_field_iter_next(&fld);
290
0
  }
291
0
  va_end(list);
292
0
  return (rec);
293
0
}
294
295
csv_record_t *csv_append_record(csv_t *csv, csv_record_t *rec, int count, ...)
296
0
{
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
0
    return NULL;
307
0
  }
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
0
    }
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
0
    }
323
0
    csv_insert_record(csv, rec);
324
0
  }
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
0
    }
344
0
    if (tempc < (count - 1)) {
345
0
      rec->rec_len += snprintf((str + rec->rec_len),
346
0
             (len - rec->rec_len), ",");
347
0
    }
348
0
  }
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
0
}
356
357
int csv_serialize(csv_t *csv, char *msgbuf, int msglen)
358
0
{
359
0
  csv_record_t *rec;
360
0
  int offset = 0;
361
362
0
  if (!csv || !msgbuf)
363
0
    return -1;
364
365
0
  rec = csv_record_iter(csv);
366
0
  while (rec != NULL) {
367
0
    if ((offset + rec->rec_len) >= msglen)
368
0
      return -1;
369
0
    offset += sprintf(&msgbuf[offset], "%s", rec->record);
370
0
    rec = csv_record_iter_next(rec);
371
0
  }
372
373
0
  return 0;
374
0
}
375
376
void csv_clone_record(csv_t *csv, csv_record_t *in_rec, csv_record_t **out_rec)
377
0
{
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
0
  }
386
387
  /* only works with csv with discrete bufs */
388
0
  if (csv->buf) {
389
0
    log_error(
390
0
      "un-supported for this csv type - single buf detected\n");
391
0
    return;
392
0
  }
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
0
  }
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
0
  }
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
0
}
416
417
void csv_remove_record(csv_t *csv, csv_record_t *rec)
418
0
{
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
0
  }
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
0
  }
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
0
}
445
446
void csv_insert_record(csv_t *csv, csv_record_t *rec)
447
0
{
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
0
  }
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
0
      "un-supported for this csv type - single buf detected\n");
458
0
    return;
459
0
  }
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
0
  }
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
0
}
472
473
csv_record_t *csv_concat_record(csv_t *csv, csv_record_t *rec1,
474
        csv_record_t *rec2)
475
0
{
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
0
  }
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
0
      "un-supported for this csv type - single buf detected\n");
491
0
    return NULL;
492
0
  }
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
0
  }
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
0
  }
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
0
  }
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
0
  }
524
525
0
  snprintf((curr + strlen(curr)), (int)(ret - rec2->record + 1), "%s",
526
0
     rec2->record);
527
0
  strcat(curr, "\n");
528
0
  rec->rec_len = strlen(curr);
529
530
  /* paranoia */
531
0
  assert(csv->buflen
532
0
         > (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
0
}
550
551
void csv_decode(csv_t *csv, char *inbuf)
552
0
{
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
0
      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
0
    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
0
      }
576
0
      strncpy(rec->record, buf, pos - buf + 1);
577
0
    }
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
0
  }
584
0
}
585
586
int csv_is_record_valid(csv_t *csv, csv_record_t *in_rec)
587
0
{
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
0
      valid = 1;
595
0
      break;
596
0
    }
597
0
    rec = csv_record_iter_next(rec);
598
0
  }
599
600
0
  return valid;
601
0
}
602
603
void csv_dump(csv_t *csv)
604
0
{
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
      fprintf(stderr, "%s\n", str);
614
0
      str = csv_field_iter_next(&fld);
615
0
    }
616
0
    rec = csv_record_iter_next(rec);
617
0
  }
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