diff options
Diffstat (limited to 'lib/csv.c')
-rw-r--r-- | lib/csv.c | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/lib/csv.c b/lib/csv.c new file mode 100644 index 0000000..fdd89a0 --- /dev/null +++ b/lib/csv.c @@ -0,0 +1,691 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* CSV + * Copyright (C) 2013,2020 Cumulus Networks, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <zebra.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <assert.h> +#include <sys/queue.h> +#include <fcntl.h> +#include <unistd.h> +#include "csv.h" + +#define DEBUG_E 1 +#define DEBUG_V 1 + +#define log_error(fmt, ...) \ + do { \ + if (DEBUG_E) \ + fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ + __LINE__, __func__, ##__VA_ARGS__); \ + } while (0) + +#define log_verbose(fmt, ...) \ + do { \ + if (DEBUG_V) \ + fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ + __LINE__, __func__, __VA_ARGS__); \ + } while (0) + +struct _csv_field_t_ { + TAILQ_ENTRY(_csv_field_t_) next_field; + char *field; + int field_len; +}; + +struct _csv_record_t_ { + TAILQ_HEAD(, _csv_field_t_) fields; + TAILQ_ENTRY(_csv_record_t_) next_record; + char *record; + int rec_len; +}; + +struct _csv_t_ { + TAILQ_HEAD(, _csv_record_t_) records; + char *buf; + int buflen; + int csv_len; + int pointer; + int num_recs; +}; + + +int csvlen(csv_t *csv) +{ + return (csv->csv_len); +} + +csv_t *csv_init(csv_t *csv, char *buf, int buflen) +{ + if (csv == NULL) { + csv = malloc(sizeof(csv_t)); + if (csv == NULL) { + log_error("CSV Malloc failed\n"); + return NULL; + } + } + memset(csv, 0, sizeof(csv_t)); + + csv->buf = buf; + csv->buflen = buflen; + TAILQ_INIT(&(csv->records)); + return (csv); +} + +void csv_clean(csv_t *csv) +{ + csv_record_t *rec; + csv_record_t *rec_n; + + rec = TAILQ_FIRST(&(csv->records)); + while (rec != NULL) { + rec_n = TAILQ_NEXT(rec, next_record); + csv_remove_record(csv, rec); + rec = rec_n; + } +} + +void csv_free(csv_t *csv) +{ + if (csv != NULL) { + free(csv); + } +} + +static void csv_init_record(csv_record_t *record) +{ + TAILQ_INIT(&(record->fields)); + record->rec_len = 0; +} + +csv_record_t *csv_record_iter(csv_t *csv) +{ + return (TAILQ_FIRST(&(csv->records))); +} + +csv_record_t *csv_record_iter_next(csv_record_t *rec) +{ + if (!rec) + return NULL; + return (TAILQ_NEXT(rec, next_record)); +} + +char *csv_field_iter(csv_record_t *rec, csv_field_t **fld) +{ + if (!rec) + return NULL; + *fld = TAILQ_FIRST(&(rec->fields)); + return ((*fld)->field); +} + +char *csv_field_iter_next(csv_field_t **fld) +{ + *fld = TAILQ_NEXT(*fld, next_field); + if ((*fld) == NULL) { + return NULL; + } + return ((*fld)->field); +} + +int csv_field_len(csv_field_t *fld) +{ + if (fld) { + return fld->field_len; + } + return 0; +} + +static void csv_decode_record(csv_record_t *rec) +{ + char *curr = rec->record; + char *field; + csv_field_t *fld; + + field = strpbrk(curr, ","); + while (field != NULL) { + fld = malloc(sizeof(csv_field_t)); + if (fld) { + TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field); + fld->field = curr; + fld->field_len = field - curr; + } + curr = field + 1; + field = strpbrk(curr, ","); + } + field = strstr(curr, "\n"); + if (!field) + return; + + fld = malloc(sizeof(csv_field_t)); + if (fld) { + fld->field = curr; + fld->field_len = field - curr; + TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field); + } +} + +static csv_field_t *csv_add_field_to_record(csv_t *csv, csv_record_t *rec, + char *col) +{ + csv_field_t *fld; + char *str = rec->record; + int rlen = rec->rec_len; + int blen = csv->buflen; + + fld = malloc(sizeof(csv_field_t)); + if (!fld) { + log_error("field malloc failed\n"); + /* more cleanup needed */ + return NULL; + } + TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field); + fld->field = str + rlen; + fld->field_len = snprintf((str + rlen), (blen - rlen), "%s", col); + rlen += fld->field_len; + rec->rec_len = rlen; + return fld; +} + +csv_record_t *csv_encode(csv_t *csv, int count, ...) +{ + int tempc; + va_list list; + char *buf = csv->buf; + int len = csv->buflen; + int pointer = csv->pointer; + char *str = NULL; + char *col; + csv_record_t *rec; + csv_field_t *fld; + + if (buf) { + str = buf + pointer; + } else { + /* allocate sufficient buffer */ + str = (char *)malloc(csv->buflen); + if (!str) { + log_error("field str malloc failed\n"); + return NULL; + } + } + + va_start(list, count); + rec = malloc(sizeof(csv_record_t)); + if (!rec) { + log_error("record malloc failed\n"); + if (!buf) + free(str); + va_end(list); + return NULL; + } + csv_init_record(rec); + rec->record = str; + TAILQ_INSERT_TAIL(&(csv->records), rec, next_record); + csv->num_recs++; + + /** + * Iterate through the fields passed as a variable list and add them + */ + for (tempc = 0; tempc < count; tempc++) { + col = va_arg(list, char *); + fld = csv_add_field_to_record(csv, rec, col); + if (!fld) { + log_error("fld malloc failed\n"); + csv_remove_record(csv, rec); + va_end(list); + return NULL; + } + if (tempc < (count - 1)) { + rec->rec_len += snprintf((str + rec->rec_len), + (len - rec->rec_len), ","); + } + } + rec->rec_len += + snprintf((str + rec->rec_len), (len - rec->rec_len), "\n"); + va_end(list); + csv->csv_len += rec->rec_len; + csv->pointer += rec->rec_len; + return (rec); +} + +int csv_num_records(csv_t *csv) +{ + if (csv) { + return csv->num_recs; + } + return 0; +} + +csv_record_t *csv_encode_record(csv_t *csv, csv_record_t *rec, int count, ...) +{ + int tempc; + va_list list; + char *str; + char *col; + csv_field_t *fld = NULL; + int i; + + va_start(list, count); + str = csv_field_iter(rec, &fld); + if (!fld) { + va_end(list); + return NULL; + } + + for (tempc = 0; tempc < count; tempc++) { + col = va_arg(list, char *); + for (i = 0; i < fld->field_len; i++) { + str[i] = col[i]; + } + str = csv_field_iter_next(&fld); + } + va_end(list); + return (rec); +} + +csv_record_t *csv_append_record(csv_t *csv, csv_record_t *rec, int count, ...) +{ + int tempc; + va_list list; + int len = csv->buflen, tlen; + char *str; + csv_field_t *fld; + char *col; + + if (csv->buf) { + /* not only works with discrete bufs */ + return NULL; + } + + if (!rec) { + /* create a new rec */ + rec = calloc(1, sizeof(csv_record_t)); + if (!rec) { + log_error("record malloc failed\n"); + return NULL; + } + csv_init_record(rec); + rec->record = calloc(1, csv->buflen); + if (!rec->record) { + log_error("field str malloc failed\n"); + free(rec); + return NULL; + } + csv_insert_record(csv, rec); + } + + str = rec->record; + + va_start(list, count); + + if (rec->rec_len && (str[rec->rec_len - 1] == '\n')) + str[rec->rec_len - 1] = ','; + + /** + * Iterate through the fields passed as a variable list and add them + */ + tlen = rec->rec_len; + for (tempc = 0; tempc < count; tempc++) { + col = va_arg(list, char *); + fld = csv_add_field_to_record(csv, rec, col); + if (!fld) { + log_error("fld malloc failed\n"); + break; + } + if (tempc < (count - 1)) { + rec->rec_len += snprintf((str + rec->rec_len), + (len - rec->rec_len), ","); + } + } + rec->rec_len += + snprintf((str + rec->rec_len), (len - rec->rec_len), "\n"); + va_end(list); + csv->csv_len += (rec->rec_len - tlen); + csv->pointer += (rec->rec_len - tlen); + return (rec); +} + +int csv_serialize(csv_t *csv, char *msgbuf, int msglen) +{ + csv_record_t *rec; + int offset = 0; + + if (!csv || !msgbuf) + return -1; + + rec = csv_record_iter(csv); + while (rec != NULL) { + if ((offset + rec->rec_len) >= msglen) + return -1; + offset += sprintf(&msgbuf[offset], "%s", rec->record); + rec = csv_record_iter_next(rec); + } + + return 0; +} + +void csv_clone_record(csv_t *csv, csv_record_t *in_rec, csv_record_t **out_rec) +{ + char *curr; + csv_record_t *rec; + + /* first check if rec belongs to this csv */ + if (!csv_is_record_valid(csv, in_rec)) { + log_error("rec not in this csv\n"); + return; + } + + /* only works with csv with discrete bufs */ + if (csv->buf) { + log_error( + "un-supported for this csv type - single buf detected\n"); + return; + } + + /* create a new rec */ + rec = calloc(1, sizeof(csv_record_t)); + if (!rec) { + log_error("record malloc failed\n"); + return; + } + csv_init_record(rec); + curr = calloc(1, csv->buflen); + if (!curr) { + log_error("field str malloc failed\n"); + free(rec); + return; + } + rec->record = curr; + rec->rec_len = in_rec->rec_len; + strlcpy(rec->record, in_rec->record, csv->buflen); + + /* decode record into fields */ + csv_decode_record(rec); + + *out_rec = rec; +} + +void csv_remove_record(csv_t *csv, csv_record_t *rec) +{ + csv_field_t *fld = NULL, *p_fld; + + /* first check if rec belongs to this csv */ + if (!csv_is_record_valid(csv, rec)) { + log_error("rec not in this csv\n"); + return; + } + + /* remove fields */ + csv_field_iter(rec, &fld); + while (fld) { + p_fld = fld; + csv_field_iter_next(&fld); + TAILQ_REMOVE(&(rec->fields), p_fld, next_field); + free(p_fld); + } + + TAILQ_REMOVE(&(csv->records), rec, next_record); + + csv->num_recs--; + csv->csv_len -= rec->rec_len; + csv->pointer -= rec->rec_len; + if (!csv->buf) + free(rec->record); + free(rec); +} + +void csv_insert_record(csv_t *csv, csv_record_t *rec) +{ + /* first check if rec already in csv */ + if (csv_is_record_valid(csv, rec)) { + log_error("rec already in this csv\n"); + return; + } + + /* we can only insert records if no buf was supplied during csv init */ + if (csv->buf) { + log_error( + "un-supported for this csv type - single buf detected\n"); + return; + } + + /* do we go beyond the max buf set for this csv ?*/ + if ((csv->csv_len + rec->rec_len) > csv->buflen) { + log_error("cannot insert - exceeded buf size\n"); + return; + } + + TAILQ_INSERT_TAIL(&(csv->records), rec, next_record); + csv->num_recs++; + csv->csv_len += rec->rec_len; + csv->pointer += rec->rec_len; +} + +csv_record_t *csv_concat_record(csv_t *csv, csv_record_t *rec1, + csv_record_t *rec2) +{ + char *curr; + char *ret; + csv_record_t *rec; + + /* first check if rec1 and rec2 belong to this csv */ + if (!csv_is_record_valid(csv, rec1) + || !csv_is_record_valid(csv, rec2)) { + log_error("rec1 and/or rec2 invalid\n"); + return NULL; + } + + /* we can only concat records if no buf was supplied during csv init */ + if (csv->buf) { + log_error( + "un-supported for this csv type - single buf detected\n"); + return NULL; + } + + /* create a new rec */ + rec = calloc(1, sizeof(csv_record_t)); + if (!rec) { + log_error("record malloc failed\n"); + return NULL; + } + csv_init_record(rec); + + curr = (char *)calloc(1, csv->buflen); + if (!curr) { + log_error("field str malloc failed\n"); + goto out_rec; + } + rec->record = curr; + + /* concat the record string */ + ret = strstr(rec1->record, "\n"); + if (!ret) { + log_error("rec1 str not properly formatted\n"); + goto out_curr; + } + + snprintf(curr, (int)(ret - rec1->record + 1), "%s", rec1->record); + strcat(curr, ","); + + ret = strstr(rec2->record, "\n"); + if (!ret) { + log_error("rec2 str not properly formatted\n"); + goto out_curr; + } + + snprintf((curr + strlen(curr)), (int)(ret - rec2->record + 1), "%s", + rec2->record); + strcat(curr, "\n"); + rec->rec_len = strlen(curr); + + /* paranoia */ + assert(csv->buflen + > (csv->csv_len - rec1->rec_len - rec2->rec_len + rec->rec_len)); + + /* decode record into fields */ + csv_decode_record(rec); + + /* now remove rec1 and rec2 and insert rec into this csv */ + csv_remove_record(csv, rec1); + csv_remove_record(csv, rec2); + csv_insert_record(csv, rec); + + return rec; + +out_curr: + free(curr); +out_rec: + free(rec); + return NULL; +} + +void csv_decode(csv_t *csv, char *inbuf) +{ + char *buf; + char *pos; + csv_record_t *rec; + + buf = (inbuf) ? inbuf : csv->buf; + assert(buf); + + pos = strpbrk(buf, "\n"); + while (pos != NULL) { + rec = calloc(1, sizeof(csv_record_t)); + if (!rec) + return; + csv_init_record(rec); + TAILQ_INSERT_TAIL(&(csv->records), rec, next_record); + csv->num_recs++; + if (csv->buf) + rec->record = buf; + else { + rec->record = calloc(1, csv->buflen); + if (!rec->record) { + log_error("field str malloc failed\n"); + return; + } + strncpy(rec->record, buf, pos - buf + 1); + } + rec->rec_len = pos - buf + 1; + /* decode record into fields */ + csv_decode_record(rec); + buf = pos + 1; + pos = strpbrk(buf, "\n"); + } +} + +int csv_is_record_valid(csv_t *csv, csv_record_t *in_rec) +{ + csv_record_t *rec; + int valid = 0; + + rec = csv_record_iter(csv); + while (rec) { + if (rec == in_rec) { + valid = 1; + break; + } + rec = csv_record_iter_next(rec); + } + + return valid; +} + +void csv_dump(csv_t *csv) +{ + csv_record_t *rec; + csv_field_t *fld; + char *str; + + rec = csv_record_iter(csv); + while (rec != NULL) { + str = csv_field_iter(rec, &fld); + while (str != NULL) { + fprintf(stderr, "%s\n", str); + str = csv_field_iter_next(&fld); + } + rec = csv_record_iter_next(rec); + } +} + +#ifdef TEST_CSV + +static int get_memory_usage(pid_t pid) +{ + int fd, data, stack; + char buf[4096], status_child[PATH_MAX]; + char *vm; + + snprintf(status_child, sizeof(status_child), "/proc/%d/status", pid); + fd = open(status_child, O_RDONLY); + if (fd < 0) + return -1; + + read(fd, buf, 4095); + buf[4095] = '\0'; + close(fd); + + data = stack = 0; + + vm = strstr(buf, "VmData:"); + if (vm) { + sscanf(vm, "%*s %d", &data); + } + vm = strstr(buf, "VmStk:"); + if (vm) { + sscanf(vm, "%*s %d", &stack); + } + + return data + stack; +} + +int main() +{ + char buf[10000]; + csv_t csv; + int i; + csv_record_t *rec; + char hdr1[32], hdr2[32]; + + log_verbose("Mem: %d\n", get_memory_usage(getpid())); + csv_init(&csv, buf, 256); + snprintf(hdr1, sizeof(hdr1), "%4d", 0); + snprintf(hdr2, sizeof(hdr2), "%4d", 1); + log_verbose("(%zu/%zu/%d/%d)\n", strlen(hdr1), strlen(hdr2), atoi(hdr1), + atoi(hdr2)); + rec = csv_encode(&csv, 2, hdr1, hdr2); + csv_encode(&csv, 4, "name", "age", "sex", "hei"); + csv_encode(&csv, 3, NULL, "0", NULL); + csv_encode(&csv, 2, "p", "35"); + for (i = 0; i < 50; i++) { + csv_encode(&csv, 2, "p", "10"); + } + csv_encode(&csv, 2, "pdfadfadfadsadsaddfdfdsfdsd", "35444554545454545"); + log_verbose("%s\n", buf); + snprintf(hdr1, sizeof(hdr1), "%4d", csv.csv_len); + snprintf(hdr2, sizeof(hdr2), "%4d", 1); + log_verbose("(%zu/%zu/%d/%d)\n", strlen(hdr1), strlen(hdr2), atoi(hdr1), + atoi(hdr2)); + rec = csv_encode_record(&csv, rec, 2, hdr1, hdr2); + log_verbose("(%d/%d)\n%s\n", rec->rec_len, csv.csv_len, buf); + + log_verbose("Mem: %d\n", get_memory_usage(getpid())); + csv_clean(&csv); + log_verbose("Mem: %d\n", get_memory_usage(getpid())); + csv_init(&csv, buf, 256); + csv_decode(&csv, NULL); + log_verbose("%s", "AFTER DECODE\n"); + csv_dump(&csv); + csv_clean(&csv); + log_verbose("Mem: %d\n", get_memory_usage(getpid())); +} +#endif |