format_items(): Check for NULL pointers.
[adu.git] / format.c
1 /*
2  * Copyright (C) 2008 Andre Noll <maan@systemlinux.org>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file format.c Functions for pretty-printing numbers and strings. */
8
9 #include <dirent.h> /* readdir() */
10 #include "adu.h"
11 #include "gcc-compat.h"
12 #include "fd.h"
13 #include "string.h"
14 #include "error.h"
15 #include "format.h"
16 enum alignment {ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER};
17
18 struct num_format {
19         enum alignment align;
20         char unit;
21         int supress_unit;
22 };
23
24 struct string_format {
25         enum alignment align;
26 };
27
28 struct const_string {
29         char *string;
30 };
31
32 union atom_format {
33         struct string_format sf;
34         struct num_format nf;
35         struct const_string cs;
36 };
37
38 struct format_item {
39         struct atom *atom_ptr;
40         unsigned int width;
41         union atom_format af;
42 };
43
44 struct format_info {
45         struct atom *atoms;
46         struct format_item **items;
47 };
48
49 #include <stdio.h>
50 #include <string.h>
51 #include <stdlib.h>
52 #include <inttypes.h>
53
54
55 static const char units[] = "bkmgth KMGTH";
56 /*
57  * In a format string, find the next occurrence of %(atom).
58  */
59 static char *find_next(char *cp)
60 {
61         while (*cp) {
62                 if (*cp == '%') {
63                         /* %( is the start of an atom;
64                          * %% is a quoted per-cent.
65                          */
66                         if (cp[1] == '(')
67                                 return cp;
68                         else if (cp[1] == '%')
69                                 cp++; /* skip over two % */
70                         /* otherwise this is a singleton, literal % */
71                 }
72                 cp++;
73         }
74         return NULL;
75 }
76
77 static struct format_item *make_const_string(char *cp, char *ep)
78 {
79         struct format_item *fi = malloc(sizeof *fi);
80         char *op;
81
82         fi->atom_ptr = NULL;
83         fi->width = ep - cp;
84         op = fi->af.cs.string = malloc(fi->width + 1);
85         while (*cp && (!ep || cp < ep)) {
86                 if (*cp == '%' && cp[1] == '%') {
87                         cp++;
88                         fi->width--;
89                 }
90                 *op = *cp;
91                 cp++;
92                 op++;
93         }
94         *op = '\0';
95         return fi;
96 }
97
98 static int parse_atom(char *ap, struct atom *atoms, struct format_item **result)
99 {
100         struct format_item *fi = NULL;
101         int i, n, len;
102         char *col, *ep = strchr(ap, ')');
103         int ret = -E_MALFORMED_FORMAT;
104
105         if (!ep)
106                 goto err;
107         col = memchr(ap, ':', ep - ap);
108         if (col)
109                 len = col - ap;
110         else
111                 len = ep - ap;
112         for (i = 0; atoms[i].name; i++) {
113                 int j;
114                 struct atom *a = atoms + i;
115                 if (strlen(a->name) != len)
116                         continue;
117                 if (strncmp(a->name, ap, len))
118                         continue;
119                 fi = malloc(sizeof(*fi));
120                 fi->atom_ptr = a;
121                 /* set defaults */
122                 fi->width = 0;
123                 switch (a->type) {
124                 case AT_STRING:
125                         fi->af.sf.align = ALIGN_LEFT;
126                         break;
127                 case AT_ID:
128                         fi->af.nf.align = ALIGN_RIGHT;
129                         fi->af.nf.unit = ' ';
130                         fi->af.nf.supress_unit = 0;
131                         break;
132                 case AT_COUNT:
133                         fi->af.nf.align = ALIGN_RIGHT;
134                         fi->af.nf.unit = 'H';
135                         fi->af.nf.supress_unit = 0;
136                         break;
137                 case AT_SIZE:
138                         fi->af.nf.align = ALIGN_RIGHT;
139                         fi->af.nf.unit = 'h';
140                         fi->af.nf.supress_unit = 0;
141                         break;
142                 }
143                 if (!col)
144                         goto success;
145                 /* read alignment */
146                 switch (col[1]) {
147                 case 'l':
148                         fi->af.sf.align = ALIGN_LEFT;
149                         break;
150                 case 'r':
151                         fi->af.nf.align = ALIGN_RIGHT;
152                         break;
153                 case 'c':
154                         fi->af.nf.align = ALIGN_CENTER;
155                         break;
156                 case ':': /* no alignment spec is OK */
157                         col--;
158                         break;
159                 default:
160                         ret = -E_BAD_ALIGN_SPEC;
161                         goto err;
162                 }
163                 switch (col[2]) {
164                 case ')':
165                         goto success;
166                 case ':':
167                         col += 2;
168                         break;
169                 default:
170                         ret = -E_TRAILING_GARBAGE;
171                         goto err;
172                 }
173                 /* read width */
174                 n = 0;
175                 sscanf(col + 1, "%u%n", &fi->width, &n);
176                 /* read unit */
177                 switch (col[1 + n]) {
178                 case ')':
179                         goto success;
180                 case ':':
181                         col += 1 + n;
182                         break;
183                 default:
184                         ret = -E_TRAILING_GARBAGE;
185                         goto err;
186                 }
187                 if (a->type != AT_SIZE && a->type != AT_COUNT) {
188                         ret = -E_UNIT;
189                         goto err;
190                 }
191                 if (col[1] == '*') {
192                         fi->af.nf.supress_unit = 1;
193                         col++;
194                 }
195                 for (j = 0; units[j]; j++) {
196                         if (units[j] != col[1])
197                                 continue;
198                         fi->af.nf.unit = units[j];
199                         break;
200                 }
201                 if (!units[j]) {
202                         ret = -E_BAD_UNIT;
203                         goto err;
204                 }
205                 if (col + 2 != ep) {
206                         ret = -E_TRAILING_GARBAGE;
207                         goto err;
208                 }
209                 goto success;
210         }
211         ret = -E_BAD_ATOM;
212 err:
213         free(fi);
214         return ret;
215 success:
216         *result = fi;
217         return 1;
218 }
219
220 int parse_format_string(char *fmt, struct atom *atoms, struct format_info **result)
221 {
222         char *cp, *ap, *ep;
223         int num_items, ret;
224         struct format_info *info = malloc(sizeof(*info));
225
226         info->atoms = atoms;
227         info->items = NULL;
228         for (cp = fmt, num_items = 0; *cp && (ap = find_next(cp));
229                         cp = ep + 1, num_items++) {
230                 if (cp < ap) {
231                         info->items = realloc(info->items,
232                                 (num_items + 1) * sizeof(struct format_info *));
233                         info->items[num_items] = make_const_string(cp, ap);
234                         num_items++;
235                 }
236                 info->items = realloc(info->items, (num_items + 1) * sizeof(struct format_info *));
237                 ret = parse_atom(ap + 2, atoms, info->items + num_items);
238                 if (ret < 0) {
239                         num_items--;
240                         goto err;
241                 }
242                 ep = strchr(ap, ')');
243         }
244         if (*cp) {
245                 ep = cp + strlen(cp);
246                 info->items = realloc(info->items, (num_items + 1) * sizeof(struct format_info *));
247                 info->items[num_items] = make_const_string(cp, ep);
248                 num_items++;
249         }
250         info->items = realloc(info->items, (num_items + 1) * sizeof(struct format_info *));
251         info->items[num_items] = NULL;
252         *result = info;
253         return 1;
254 err:
255         for (; num_items >= 0; num_items--) {
256                 if (!info->items[num_items]->atom_ptr)
257                         free(info->items[num_items]->af.cs.string);
258                 free(info->items[num_items]);
259         }
260         free(info->items);
261         free(info);
262         return ret;
263 }
264
265 /**
266  * It's OK to pass a \p NULL pointer to this function.
267  */
268 void free_format_info(struct format_info *info)
269 {
270         int i;
271         struct format_item *item;
272
273         if (!info)
274                 return;
275
276         for (i = 0; (item = info->items[i]); i++) {
277                 if (!item->atom_ptr)
278                         free(item->af.cs.string);
279                 free(info->items[i]);
280         }
281         free(info->items);
282         free(info);
283 }
284
285 static char *align_string(const char *src, unsigned int width, enum alignment align)
286 {
287         int len;
288
289         if (!width)
290                 return adu_strdup(src);
291         if (align == ALIGN_LEFT)
292                 return make_message("%-*s", width, src);
293         if (align == ALIGN_RIGHT)
294                 return make_message("%*s", width, src);
295         len = strlen(src);
296         return make_message("%*s%*s", (width + len) / 2, src,
297                 width - (width + len) / 2, "");
298 }
299
300 /** Compute the number of (decimal) digits of a number. */
301 #define GET_NUM_DIGITS(x, num) { \
302         typeof((x)) _tmp = x; \
303         *num = 1; \
304         if ((x)) \
305                 while ((_tmp) > 9) { \
306                         (_tmp) /= 10; \
307                         (*num)++; \
308                 } \
309 } \
310
311 static long long unsigned normalize_number(long long unsigned num, char unit,
312         char *effective_unit)
313 {
314         long long unsigned normalized_num;
315
316         if (unit == 'h') {
317                 if (num < 1024ULL)
318                         *effective_unit = ' ';
319                 else if (num < 1024ULL * 1024ULL)
320                         *effective_unit = 'k';
321                 else if (num < 1024ULL * 1024ULL * 1024ULL)
322                         *effective_unit = 'm';
323                 else if (num < 1024ULL * 1024ULL * 1024ULL * 1024ULL)
324                         *effective_unit = 'g';
325                 else
326                         *effective_unit = 't';
327         } else if (unit == 'H') {
328                 if (num < 1000ULL)
329                         *effective_unit = ' ';
330                 else if (num < 1000ULL * 1000ULL)
331                         *effective_unit = 'K';
332                 else if (num < 1000ULL * 1000ULL * 1000ULL)
333                         *effective_unit = 'M';
334                 else if (num < 1000ULL * 1000ULL * 1000ULL * 1000ULL)
335                         *effective_unit = 'G';
336                 else
337                         *effective_unit = 'T';
338         } else
339                 *effective_unit = unit;
340         switch (*effective_unit) {
341         case ' ':
342         case 'b': normalized_num = num; break;
343         case 'k': normalized_num = num / 1024; break;
344         case 'm': normalized_num = num / 1024 / 1024; break;
345         case 'g': normalized_num = num / 1024 / 1024 / 1024; break;
346         case 't': normalized_num = num / 1024 / 1024 / 1024 / 1024; break;
347         case 'K': normalized_num = num / 1000; break;
348         case 'M': normalized_num = num / 1000 / 1000; break;
349         case 'G': normalized_num = num / 1000 / 1000 / 1000; break;
350         case 'T': normalized_num = num / 1000 / 1000 / 1000 / 1000; break;
351         default:
352                 EMERG_LOG("BUG: invalid unit %c\n", *effective_unit);
353                 exit(1);
354         }
355         return normalized_num;
356 }
357
358 static void get_unit_postfix(struct num_format *nf, char eu, enum atom_type type,
359                 char postfix[2])
360 {
361         if (nf->supress_unit) {
362                 *postfix = '\0';
363                 return;
364         }
365         postfix[0] = eu;
366         postfix[1] = '\0';
367         if (eu != ' ')
368                 return;
369         if (nf->unit != 'h' && nf->unit != 'H')
370                 return;
371         if (type != AT_SIZE)
372                 return;
373         postfix[0] = 'b'; /* bytes */
374 }
375
376 static char *align_unsigned_int(long long unsigned num, unsigned int width,
377                 struct num_format *nf, enum atom_type type)
378 {
379         char eu; /* effective unit */
380         long long unsigned nnum = normalize_number(num, nf->unit, &eu);
381         char postfix[2] = "\0\0";
382         int len;
383
384         get_unit_postfix(nf, eu, type, postfix);
385         if (!width)
386                 return make_message("%llu%s", nnum, postfix);
387         GET_NUM_DIGITS(nnum, &len);
388         len += strlen(postfix);
389         if (len > width)
390                 width = len;
391         if (nf->align == ALIGN_LEFT)
392                 return make_message("%llu%s%*s", nnum, postfix, width - len, "");
393         if (nf->align == ALIGN_RIGHT)
394                 return make_message("%*s%llu%s", width - len, "", nnum, postfix);
395         return make_message("%*llu%s%*s", (width + len) / 2,
396                 nnum, postfix, width - (width + len) / 2, "");
397 }
398
399 char *format_items(struct format_info *info, union atom_value *values)
400 {
401         int i;
402         char *buf = NULL;
403
404         if (!info)
405                 return NULL;
406         for (i = 0; info->items[i]; i++) {
407                 struct atom *a;
408                 struct format_item *fi = info->items[i];
409                 union atom_format *af = &fi->af;
410                 enum alignment align;
411                 enum atom_type type;
412                 char *val;
413                 int idx;
414
415                 if (!fi->atom_ptr) { /* const string */
416                         buf = adu_strcat(buf, af->cs.string);
417                         continue;
418                 }
419                 a = fi->atom_ptr;
420                 type = a->type;
421                 idx = a - info->atoms;
422
423                 if (type == AT_STRING) {
424                         align = af->sf.align;
425                         val = align_string(values[idx].string_value, fi->width, align);
426                 } else {
427                         char unit;
428                         align = af->nf.align;
429                         unit = af->nf.unit;
430                         val = align_unsigned_int(values[idx].num_value,
431                                 fi->width, &af->nf, type);
432                 }
433                 buf = adu_strcat(buf, val);
434                 free(val);
435         }
436         return buf;
437 }