csv: fix use of uninitialized memory - ploot - simple plotting tools HTML git clone git://bitreich.org/ploot git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ploot DIR Log DIR Files DIR Refs DIR Tags DIR README DIR LICENSE --- DIR commit f7f88c2ee5573abff4c44c36bf7b2e705081b2ed DIR parent c3fcef87d156b02a9ad8ca7cd47fee4a826534f4 HTML Author: Josuah Demangeon <me@josuah.net> Date: Sun, 27 Jun 2021 01:04:39 +0200 csv: fix use of uninitialized memory Diffstat: M Makefile | 6 +++--- M csv.c | 33 ++++++++++++++----------------- M csv.h | 1 - M ploot-braille.c | 157 ++++++++++++++++++++++--------- M ploot-farbfeld.c | 14 +++++++------- D scale.c | 94 ------------------------------- D scale.h | 14 -------------- 7 files changed, 140 insertions(+), 179 deletions(-) --- DIR diff --git a/Makefile b/Makefile @@ -7,9 +7,9 @@ LFLAGS = -static -lm PREFIX = /usr/local MANOREFIX = $(PREFIX)/share/man -SRC = csv.c drawille.c font.c font13.c font8.c scale.c util.c -INC = csv.h drawille.h font.h scale.h util.h -BIN = ploot-farbfeld ploot-feed ploot-braille ploot-text +SRC = csv.c drawille.c font.c font13.c font8.c util.c +INC = csv.h drawille.h font.h util.h +BIN = ploot-feed ploot-braille ploot-text # ploot-farbfeld OBJ = ${SRC:.c=.o} all: ${BIN} DIR diff --git a/csv.c b/csv.c @@ -13,11 +13,10 @@ */ static void -csv_addtime(struct csv *vl, time_t epoch) +csv_add_time(struct csv *vl, time_t epoch) { void *mem; - debug("csv_addtime %p", vl->t); if ((mem = realloc(vl->t, (vl->n + 1) * sizeof *vl->t)) == NULL) err(1, "realloc: %s", strerror(errno)); vl->t = mem; @@ -25,11 +24,10 @@ csv_addtime(struct csv *vl, time_t epoch) } static void -csv_addval(struct csv *vl, double field) +csv_add_val(struct csv *vl, double field) { void *mem; - debug("csv_addval %p", vl->t); if ((mem = realloc(vl->v, (vl->n + 1) * sizeof *vl->v)) == NULL) err(1, "", strerror(errno)); vl->v = mem; @@ -40,8 +38,8 @@ csv_addval(struct csv *vl, double field) * Add to each column the value on the current row. The time_t * buffer is shared among all fields. */ -void -csv_addrow(struct csv *vl, size_t ncol, char *line) +static void +csv_add_row(struct csv *vl, size_t ncol, char *line) { char *field; time_t *tbuf; @@ -55,7 +53,7 @@ csv_addrow(struct csv *vl, size_t ncol, char *line) if (errno) err(100, "parsing number '%s'", field); - csv_addtime(vl, l); + csv_add_time(vl, l); tbuf = vl[0].t; for (; (field = strsep(&line, ",")); ncol--, vl->n++, vl++) { if (ncol == 0) @@ -63,7 +61,7 @@ csv_addrow(struct csv *vl, size_t ncol, char *line) d = strtod(field, NULL); if (errno) err(100, "parsing double '%s'", field); - csv_addval(vl, d); + csv_add_val(vl, d); vl->t = tbuf; } if (ncol > 0) @@ -75,10 +73,10 @@ csv_addrow(struct csv *vl, size_t ncol, char *line) * label1,label2,label3 */ void -csv_labels(FILE *fp, struct csv **vl, size_t *ncol) +csv_labels(FILE *fp, struct csv **vlp, size_t *ncol) { char *field, *line, *cp; - struct csv *col; + struct csv *vl, *col; size_t sz; ssize_t r; @@ -94,16 +92,16 @@ csv_labels(FILE *fp, struct csv **vl, size_t *ncol) if (strcmp(strsep(&cp, ","), "epoch") != 0) err(1, "first label must be 'epoch'"); - *vl = NULL; - *ncol = 0; + sz = 0, vl = NULL, *ncol = 0; while ((field = strsep(&cp, ","))) { - if ((*vl = realloc(*vl, sz += sizeof **vl)) == NULL) + if ((vl = realloc(vl, sz += sizeof *vl)) == NULL) err(1, "realloc: %s", strerror(errno)); - col = (*vl) + (*ncol)++; - strlcpy(col->label, field, sizeof(col->label)); + memset(vl, 0, sizeof *vl); + col = vl + (*ncol)++; + strlcpy(col->label, field, sizeof col->label); } - free(line); + *vlp = vl; } /* @@ -122,11 +120,10 @@ csv_values(FILE *fp, struct csv *vl, size_t ncol) sz = 0, line = NULL; while (getline(&line, &sz, fp) > -1) - csv_addrow(vl, ncol, line); + csv_add_row(vl, ncol, line); if (vl->n == 0) err(1, "no value could be read"); if (vl->n == 1) err(1, "only one value could be read"); - free(line); } DIR diff --git a/csv.h b/csv.h @@ -15,7 +15,6 @@ struct csv { char label[64]; /* for the legend */ }; -void csv_addrow(struct csv *, size_t, char *); void csv_labels(FILE *, struct csv **, size_t *); void csv_values(FILE *, struct csv *, size_t); DIR diff --git a/ploot-braille.c b/ploot-braille.c @@ -8,13 +8,90 @@ #include <math.h> #include <unistd.h> #include "drawille.h" -#include "scale.h" #include "util.h" +#include "csv.h" #ifndef __OpenBSD__ #define pledge(...) 0 #endif +static int +get_min_max(struct csv *vl, int ncol, + time_t *tmin, time_t *tmax, + double *vmin, double *vmax) +{ + double *v; + time_t *t; + size_t n; + + *vmin = *vmax = 0; /* always show 0 on the scale */ + *tmin = *tmax = *vl->t; + + for (; ncol > 0; ncol--, vl++) { + for (t = vl->t, v = vl->v, n = vl->n; n > 0; t++, v++, n--) { + if (*v < *vmin) *vmin = *v; + if (*v > *vmax) *vmax = *v; + if (*t < *tmin) *tmin = *t; + if (*t > *tmax) *tmax = *t; + } + } + if (*tmin == *tmax) + return -1; + return 0; +} + +static time_t +time_mark_step(time_t min, time_t max, int dots) +{ + time_t dt, scale[] = { + 1, 5, 2, 10, 20, 30, 60, 60*2, 60*5, 60*10, 60*20, 60*30, 3600, + 3600*2, 3600*6, 3600*12, 3600*24, 3600*24*2, + 3600*24*7, 3600*24*14, 3600*24*20, 3600*24*21, 3600*24*28, 3600*24*50, + 3600*24*100, 3600*24*365, 0 + }; + + dt = max - min; + for (time_t *sc = scale; *sc > 0; sc++) + if (dt < *sc * dots) + return *sc; + return dt / dots; +} + +/* + * Make the value scale aligned with round values by changing the + * minimal and maximal values. + */ +static void +adjust_scale(double *min, double *max, int rows) +{ + double dv, step, scale[] = { 1, 2, 2.5, 5, }; + + dv = *max - *min; + + if (dv > 1) { + for (double mant = 1;; mant *= 10) { + double *sc = scale; + for (; sc < scale + LEN(scale); sc++) { + step = mant * *sc; + if (dv < (rows - 2) * step) + goto end; + } + } + } else { + for (double mant = 1;; mant /= 10) { + double *sc = scale + LEN(scale) - 1; + for (; sc >= scale; sc--) { + step = mant * *sc; + if (dv > (rows - 2) * step) + goto end; + } + } + } +end: + *min = (int)(*min / step) * step; + *max = *min + step * rows; +} + /* * Plot the body as an histogram interpolating the gaps and include * a vertical and horizontal axis. @@ -28,25 +105,31 @@ braille_histogram(struct csv *vl, struct drawille *drw, time_t *t; size_t n; - zero = scale_ypos(0, vmin, vmax, drw->row*4); +#define SHIFT (4 / 2) +#define POSITION(val, min, max, sz) ((sz) * ((val) - (min)) / ((max) - (min)) + SHIFT) + + zero = POSITION(0, vmin, vmax, drw->row*4); v = vl->v; t = vl->t; n = vl->n; for (; n > 0; n--, t++, v++) { - if (isnan(*v)) /* XXX: better handling? */ + if (isnan(*v)) /* XXX: better handling? */ continue; - y = scale_ypos(*v, vmin, vmax, drw->row * 4); - x = scale_xpos(*t, tmin, tmax, drw->col * 2); - if (n < vl->n) /* only plot when xprev, yprev are set */ + y = POSITION(*v, vmin, vmax, drw->row * 4); + x = POSITION(*t, tmin, tmax, drw->col * 2); + if (n < vl->n) /* only plot when xprev, yprev are set */ drawille_histogram_line(drw, xprev, yprev, x, y, zero); xprev = x; yprev = y; } + +#undef POSITION + return 0; } static int -braille_axis_x(FILE *fp, time_t tmin, time_t tmax, time_t csvep, int col) +braille_axis_x(FILE *fp, time_t tmin, time_t tmax, time_t tstep, int col) { int x, o, prec; char tmp[sizeof("MM/DD HH:MM")], *fmt; @@ -54,13 +137,13 @@ braille_axis_x(FILE *fp, time_t tmin, time_t tmax, time_t csvep, int col) time_t t; fmt = - (csvep < 3600 * 12) ? "^%H:%M:%S" : - (csvep < 3600 * 24) ? "^%m/%d %H:%M" : - "^%Y/%m/%d"; + (tstep < 3600 * 12) ? "^%H:%M:%S" : + (tstep < 3600 * 24) ? "^%m/%d %H:%M" : + "^%Y/%m/%d"; n = x = 0; - t = tmin + csvep - tmin % csvep; - for (; t < tmax; t += csvep) { + t = tmin + tstep - tmin % tstep; + for (; t < tmax; t += tstep) { x = (t - tmin) * col / (tmax - tmin); strftime(tmp, sizeof tmp, fmt, localtime(&t)); prec = x - n + strlen(tmp); @@ -76,51 +159,41 @@ braille_axis_x(FILE *fp, time_t tmin, time_t tmax, time_t csvep, int col) * Plot a single line out of the y axis, at row <r> out of <rows>. */ static void -braille_axis_y(FILE *fp, double vmin, double vmax, int r, int rows) +braille_axis_y(FILE *fp, double min, double max, int r, int rows) { - char tmp[10] = "", *s; - double val; - - val = (rows - r) * (vmax - vmin) / rows; - humanize(tmp, val); - s = - (r == 0) ? "┌" : - (r == rows - 1) ? "└" : - "├"; - fprintf(fp, "%s%-6s ", s, tmp); + char buf[10] = ""; + + humanize(buf, (rows - 1 - r) * (max - min) / rows); + fprintf(fp, "├%s ", buf); } static int -braille_render(struct drawille *drw, FILE *fp, double vmin, double vmax) +braille_render(struct drawille *drw, FILE *fp, double min, double max) { int row; for (row = 0; row < drw->row; row++) { drawille_put_row(fp, drw, row); - braille_axis_y(fp, vmin, vmax, row, drw->row); + braille_axis_y(fp, min, max, row, drw->row); fprintf(fp, "\n"); } return 0; } static void -plot(struct csv *vl, FILE *fp, size_t ncol, int rows, int cols) +plot(struct csv *vl, size_t ncol, int rows, int cols, FILE *fp) { - double vmin, vmax, vstep; - time_t tmin, tmax, csvep; + double vmin, vmax; + time_t tmin, tmax, tstep; struct drawille *drw; - cols -= 9; /* scale printed at the right */ - - scale_minmax(vl, ncol, &tmin, &tmax, &vmin, &vmax); - csvep = scale_csvep(tmin, tmax, cols / 10); - vstep = scale_vstep(vmin, vmax, rows / 10); + rows = MAX(rows, 2); /* readable */ - rows -= ncol - 1; /* room for the labels and the scale */ - rows /= ncol; /* plot <ncol> times */ - rows = MAX(rows, 3); /* readable */ - - debug("vstep=%lf vstep=%ld ncol=%zu rows=%zu", vstep, csvep, ncol, rows); + if (get_min_max(vl, ncol, &tmin, &tmax, &vmin, &vmax) < 0) + err(1, "invalid scale: tmin=%lld tmax=%lld vmin=%fd vmax=%fd", + (long long)tmin, (long long)tmax, vmin, vmax); + adjust_scale(&vmin, &vmax, rows); + tstep = time_mark_step(tmin, tmax, cols); for (; ncol > 0; vl++, ncol--) { if ((drw = drawille_new(rows, cols)) == NULL) @@ -132,7 +205,7 @@ plot(struct csv *vl, FILE *fp, size_t ncol, int rows, int cols) err(1, "rendering braille canvas"); free(drw); } - if (braille_axis_x(fp, tmin, tmax, csvep, cols) == -1) + if (braille_axis_x(fp, tmin, tmax, tstep * 10, cols) == -1) err(1, "printing x axis");; } @@ -153,7 +226,7 @@ main(int argc, char **argv) if (pledge("stdio", "") < 0) err(1, "pledge: %s", strerror(errno)); - rows = 20, cols = 80; + rows = 4, cols = 60; arg0 = *argv; while ((c = getopt(argc, argv, "r:c:")) > -1) { switch (c) { @@ -185,8 +258,8 @@ main(int argc, char **argv) csv_labels(stdin, &vl, &ncol); csv_values(stdin, vl, ncol); - plot(vl, stdout, ncol, rows, cols); + plot(vl, ncol, rows, cols, stdout); free(vl); - return 1; + return 0; } DIR diff --git a/ploot-farbfeld.c b/ploot-farbfeld.c @@ -236,20 +236,20 @@ ffplot_v2y(double v, double vmin, double vmax) static void ffplot_xaxis(struct ffplot *plot, struct ffcolor *label, struct ffcolor *grid, - time_t tmin, time_t tmax, time_t csvep) + time_t tmin, time_t tmax, time_t tstep) { time_t t; int x; char str[sizeof("MM/DD HH/MM")], *fmt; - if (csvep < 3600 * 12) + if (tstep < 3600 * 12) fmt = "%H:%M:%S"; - else if (csvep < 3600 * 24) + else if (tstep < 3600 * 24) fmt = "%m/%d %H:%M"; else fmt = "%X/%m/%d"; - for (t = tmax - tmax % csvep; t >= tmin; t -= csvep) { + for (t = tmax - tmax % tstep; t >= tmin; t -= tstep) { x = ffplot_t2x(t, tmin, tmax); ffplot_line(plot, grid, @@ -358,10 +358,10 @@ plot(struct csv *vl, struct ffcolor **cl, size_t ncol, char *name, char *units) struct ffcolor label_fg = { 0x8888, 0x8888, 0x8888, 0xffff }; struct ffcolor title_fg = { 0xdddd, 0xdddd, 0xdddd, 0xffff }; double vmin, vmax, vstep; - time_t tmin, tmax, csvep; + time_t tmin, tmax, tstep; scale_minmax(vl, ncol, &tmin, &tmax, &vmin, &vmax); - csvep = scale_csvep(tmin, tmax, 7); + tstep = scale_tstep(tmin, tmax, 7); vstep = scale_vstep(vmin, vmax, 7); if ((plot.buf = calloc(IMAGE_H * IMAGE_W, sizeof *plot.buf)) == NULL) @@ -377,7 +377,7 @@ plot(struct csv *vl, struct ffcolor **cl, size_t ncol, char *name, char *units) plot.x = XLABEL_X; plot.y = XLABEL_Y; - ffplot_xaxis(&plot, &label_fg, &grid_fg, tmin, tmax, csvep); + ffplot_xaxis(&plot, &label_fg, &grid_fg, tmin, tmax, tstep); plot.x = YLABEL_X; plot.y = YLABEL_Y; DIR diff --git a/scale.c b/scale.c @@ -1,94 +0,0 @@ -#include "scale.h" - -#include <stddef.h> -#include <time.h> - -#include "util.h" - -/* - * - <max ^ - * - | Translate the coordinates between double values - * - <val szy and height in the plot of <row> rows. - * - | - * - <min v - */ -int -scale_ypos(double val, double min, double max, int szy) -{ - return szy * (val - min) / (max - min); -} - -/* - * <---- szx ----> Translate the coordinates between the time - * range and position in the plot of <col> cols. - * t1 t t2 - * | . . | . . | - */ -int -scale_xpos(time_t t, time_t t1, time_t t2, int szx) -{ - return szx * (t - t1) / (t2 - t1); -} - -void -scale_minmax(struct csv *vl, int ncol, - time_t *tmin, time_t *tmax, - double *vmin, double *vmax) -{ - double *v; - time_t *t; - size_t n; - - *vmin = *vmax = 0; - *tmin = *tmax = *vl->t; - - for (; ncol > 0; ncol--, vl++) { - for (t = vl->t, v = vl->v, n = vl->n; n > 0; t++, v++, n--) { - if (*v < *vmin) *vmin = *v; - if (*v > *vmax) *vmax = *v; - if (*t < *tmin) *tmin = *t; - if (*t > *tmax) *tmax = *t; - } - } - - if (*tmin == *tmax) - err(1, "invalid time scale: min=%lld max=%lld", *tmin, *tmax); -} - -time_t -scale_csvep(time_t min, time_t max, int nval) -{ - time_t dt, *sc, scale[] = { - 1, 5, 2, 10, 20, 30, 60, 60*2, 60*5, 60*10, 60*20, 60*30, 3600, - 3600*2, 3600*5, 3600*10, 3600*18, 3600*24, 3600*24*2, - 3600*24*5, 3600*24*10, 3600*24*20, 3600*24*30, 3600*24*50, - 3600*24*100, 3600*24*365, 0 - }; - - dt = max - min; - - for (sc = scale; *sc > 0; sc++) - if (dt < *sc * nval) - return *sc; - return dt / nval; -} - -double -scale_vstep(double min, double max, int nval) -{ - double dv, d, *sc, scale[] = { 1, 2, 3, 5 }; - - dv = max - min; - - if (dv > 1) - for (d = 1; d != 0; d *= 10) - for (sc = scale; sc < scale + LEN(scale); sc++) - if (dv < *sc * d * nval) - return *sc * d; - if (dv < 1) - for (d = 1; d != 0; d *= 10) - for (sc = scale + LEN(scale) - 1; sc >= scale; sc--) - if (dv > *sc / d * nval / 2) - return *sc / d; - return 0; -} DIR diff --git a/scale.h b/scale.h @@ -1,14 +0,0 @@ -#ifndef SCALE_H -#define SCALE_H - -#include <stddef.h> -#include <time.h> -#include "csv.h" - -int scale_ypos(double, double, double, int); -int scale_xpos(time_t, time_t, time_t, int); -void scale_minmax(struct csv *, int, time_t *, time_t *, double *, double *); -time_t scale_csvep(time_t, time_t, int); -double scale_vstep(double, double, int); - -#endif