]> git.ipfire.org Git - thirdparty/util-linux.git/blob - text-utils/column.c
misc: do not use plain 0 as NULL [smatch scan]
[thirdparty/util-linux.git] / text-utils / column.c
1 /*
2 * Copyright (c) 1989, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34 /*
35 * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
36 * added Native Language Support
37 * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
38 * modified to work correctly in multi-byte locales
39 */
40
41 #include <sys/types.h>
42 #include <sys/ioctl.h>
43
44 #include <ctype.h>
45 #include <stdio.h>
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <errno.h>
50 #include <getopt.h>
51
52 #include "nls.h"
53 #include "c.h"
54 #include "widechar.h"
55 #include "xalloc.h"
56 #include "strutils.h"
57 #include "closestream.h"
58 #include "ttyutils.h"
59
60 #ifdef HAVE_WIDECHAR
61 static wchar_t *mbs_to_wcs(const char *);
62 #else
63 #define mbs_to_wcs(s) xstrdup(s)
64 static char *mtsafe_strtok(char *, const char *, char **);
65 #define wcstok mtsafe_strtok
66 #endif
67
68 #define DEFCOLS 25
69 #define TAB 8
70 #define DEFNUM 1000
71 #define MAXLINELEN (LINE_MAX + 1)
72
73 static int input(FILE *fp, int *maxlength, wchar_t ***list, int *entries);
74 static void c_columnate(int maxlength, long termwidth, wchar_t **list, int entries);
75 static void r_columnate(int maxlength, long termwidth, wchar_t **list, int entries);
76 static wchar_t *local_wcstok(wchar_t *p, const wchar_t *separator, int greedy, wchar_t **wcstok_state);
77 static void maketbl(wchar_t **list, int entries, wchar_t *separator, int greedy, wchar_t *colsep);
78 static void print(wchar_t **list, int entries);
79
80 typedef struct _tbl {
81 wchar_t **list;
82 int cols, *len;
83 } TBL;
84
85
86 #ifdef HAVE_WIDECHAR
87 /* Don't use wcswidth(), we need to ignore non-printable chars. */
88 static int width(const wchar_t *str)
89 {
90 int x, width = 0;
91
92 for (; *str != '\0'; str++) {
93 x = wcwidth(*str);
94 if (x > 0)
95 width += x;
96 }
97 return width;
98 }
99 #else
100 static int width(const char *str)
101 {
102 int width = 0;
103
104 for (; *str != '\0'; str++) {
105 if (isprint(*str))
106 width++;
107 }
108 return width;
109 }
110 #endif
111
112 static void __attribute__((__noreturn__)) usage(int rc)
113 {
114 FILE *out = rc == EXIT_FAILURE ? stderr : stdout;
115
116 fputs(USAGE_HEADER, out);
117 fprintf(out, _(" %s [options] [<file>...]\n"), program_invocation_short_name);
118
119 fputs(USAGE_SEPARATOR, out);
120 fputs(_("Columnate lists.\n"), out);
121
122 fputs(USAGE_OPTIONS, out);
123 fputs(_(" -t, --table create a table\n"), out);
124 fputs(_(" -s, --separator <string> possible table delimiters\n"), out);
125 fputs(_(" -o, --output-separator <string> columns separator for table output\n"
126 " (default is two spaces)\n"), out);
127 fputs(_(" -c, --output-width <width> width of output in number of characters\n"), out);
128 fputs(_(" -x, --fillrows fill rows before columns\n"), out);
129 fputs(USAGE_SEPARATOR, out);
130 fputs(USAGE_HELP, out);
131 fputs(USAGE_VERSION, out);
132 fprintf(out, USAGE_MAN_TAIL("column(1)"));
133
134 exit(rc);
135 }
136
137 int main(int argc, char **argv)
138 {
139 int ch, tflag = 0, xflag = 0;
140 int i;
141 int termwidth = 80;
142 int entries = 0; /* number of records */
143 unsigned int eval = 0; /* exit value */
144 int maxlength = 0; /* longest record */
145 wchar_t **list = NULL; /* array of pointers to records */
146 int greedy = 1;
147 wchar_t *colsep; /* table column output separator */
148
149 /* field separator for table option */
150 wchar_t default_separator[] = { '\t', ' ', 0 };
151 wchar_t *separator = default_separator;
152
153 static const struct option longopts[] =
154 {
155 { "columns", required_argument, NULL, 'c' }, /* deprecated */
156 { "fillrows", no_argument, NULL, 'x' },
157 { "help", no_argument, NULL, 'h' },
158 { "output-separator", required_argument, NULL, 'o' },
159 { "output-width", required_argument, NULL, 'c' },
160 { "separator", required_argument, NULL, 's' },
161 { "table", no_argument, NULL, 't' },
162 { "version", no_argument, NULL, 'V' },
163 { NULL, 0, NULL, 0 },
164 };
165
166 setlocale(LC_ALL, "");
167 bindtextdomain(PACKAGE, LOCALEDIR);
168 textdomain(PACKAGE);
169 atexit(close_stdout);
170
171 termwidth = get_terminal_width(80);
172 colsep = mbs_to_wcs(" ");
173
174 while ((ch = getopt_long(argc, argv, "hVc:s:txo:", longopts, NULL)) != -1)
175 switch(ch) {
176 case 'h':
177 usage(EXIT_SUCCESS);
178 break;
179 case 'V':
180 printf(UTIL_LINUX_VERSION);
181 return EXIT_SUCCESS;
182 case 'c':
183 termwidth = strtou32_or_err(optarg, _("invalid columns argument"));
184 break;
185 case 's':
186 separator = mbs_to_wcs(optarg);
187 greedy = 0;
188 break;
189 case 'o':
190 free(colsep);
191 colsep = mbs_to_wcs(optarg);
192 break;
193 case 't':
194 tflag = 1;
195 break;
196 case 'x':
197 xflag = 1;
198 break;
199 default:
200 errtryhelp(EXIT_FAILURE);
201 }
202 argc -= optind;
203 argv += optind;
204
205 if (!*argv)
206 eval += input(stdin, &maxlength, &list, &entries);
207 else
208 for (; *argv; ++argv) {
209 FILE *fp;
210
211 if ((fp = fopen(*argv, "r")) != NULL) {
212 eval += input(fp, &maxlength, &list, &entries);
213 fclose(fp);
214 } else {
215 warn("%s", *argv);
216 eval += EXIT_FAILURE;
217 }
218 }
219
220 if (!entries)
221 exit(eval);
222
223 if (tflag)
224 maketbl(list, entries, separator, greedy, colsep);
225 else if (maxlength >= termwidth)
226 print(list, entries);
227 else if (xflag)
228 c_columnate(maxlength, termwidth, list, entries);
229 else
230 r_columnate(maxlength, termwidth, list, entries);
231
232 for (i = 0; i < entries; i++)
233 free(list[i]);
234 free(list);
235
236 if (eval == 0)
237 return EXIT_SUCCESS;
238 else
239 return EXIT_FAILURE;
240 }
241
242 static void c_columnate(int maxlength, long termwidth, wchar_t **list, int entries)
243 {
244 int chcnt, col, cnt, endcol, numcols;
245 wchar_t **lp;
246
247 maxlength = (maxlength + TAB) & ~(TAB - 1);
248 numcols = termwidth / maxlength;
249 endcol = maxlength;
250 for (chcnt = col = 0, lp = list;; ++lp) {
251 fputws(*lp, stdout);
252 chcnt += width(*lp);
253 if (!--entries)
254 break;
255 if (++col == numcols) {
256 chcnt = col = 0;
257 endcol = maxlength;
258 putwchar('\n');
259 } else {
260 while ((cnt = ((chcnt + TAB) & ~(TAB - 1))) <= endcol) {
261 putwchar('\t');
262 chcnt = cnt;
263 }
264 endcol += maxlength;
265 }
266 }
267 if (chcnt)
268 putwchar('\n');
269 }
270
271 static void r_columnate(int maxlength, long termwidth, wchar_t **list, int entries)
272 {
273 int base, chcnt, cnt, col, endcol, numcols, numrows, row;
274
275 maxlength = (maxlength + TAB) & ~(TAB - 1);
276 numcols = termwidth / maxlength;
277 if (!numcols)
278 numcols = 1;
279 numrows = entries / numcols;
280 if (entries % numcols)
281 ++numrows;
282
283 for (row = 0; row < numrows; ++row) {
284 endcol = maxlength;
285 for (base = row, chcnt = col = 0; col < numcols; ++col) {
286 fputws(list[base], stdout);
287 chcnt += width(list[base]);
288 if ((base += numrows) >= entries)
289 break;
290 while ((cnt = ((chcnt + TAB) & ~(TAB - 1))) <= endcol) {
291 putwchar('\t');
292 chcnt = cnt;
293 }
294 endcol += maxlength;
295 }
296 putwchar('\n');
297 }
298 }
299
300 static void print(wchar_t **list, int entries)
301 {
302 int cnt;
303 wchar_t **lp;
304
305 for (cnt = entries, lp = list; cnt--; ++lp) {
306 fputws(*lp, stdout);
307 putwchar('\n');
308 }
309 }
310
311 wchar_t *local_wcstok(wchar_t * p, const wchar_t * separator, int greedy,
312 wchar_t ** wcstok_state)
313 {
314 wchar_t *result;
315 if (greedy)
316 return wcstok(p, separator, wcstok_state);
317
318 if (p == NULL) {
319 if (*wcstok_state == NULL)
320 return NULL;
321 else
322 p = *wcstok_state;
323 }
324 result = p;
325 p = wcspbrk(result, separator);
326 if (p == NULL)
327 *wcstok_state = NULL;
328 else {
329 *p = '\0';
330 *wcstok_state = p + 1;
331 }
332 return result;
333 }
334
335 static void maketbl(wchar_t **list, int entries, wchar_t *separator, int greedy, wchar_t *colsep)
336 {
337 TBL *t;
338 int cnt;
339 wchar_t *p, **lp;
340 ssize_t *lens;
341 ssize_t maxcols = DEFCOLS, coloff;
342 TBL *tbl;
343 wchar_t **cols;
344 wchar_t *wcstok_state = NULL;
345
346 t = tbl = xcalloc(entries, sizeof(TBL));
347 cols = xcalloc(maxcols, sizeof(wchar_t *));
348 lens = xcalloc(maxcols, sizeof(ssize_t));
349
350 for (lp = list, cnt = 0; cnt < entries; ++cnt, ++lp, ++t) {
351 coloff = 0;
352 p = *lp;
353 while ((cols[coloff] = local_wcstok(p, separator, greedy, &wcstok_state)) != NULL) {
354 if (++coloff == maxcols) {
355 maxcols += DEFCOLS;
356 cols = xrealloc(cols, maxcols * sizeof(wchar_t *));
357 lens = xrealloc(lens, maxcols * sizeof(ssize_t));
358 /* zero fill only new memory */
359 memset(lens + (maxcols - DEFCOLS), 0,
360 DEFCOLS * sizeof(*lens));
361 }
362 p = NULL;
363 }
364 t->list = xcalloc(coloff, sizeof(wchar_t *));
365 t->len = xcalloc(coloff, sizeof(int));
366 for (t->cols = coloff; --coloff >= 0;) {
367 t->list[coloff] = cols[coloff];
368 t->len[coloff] = width(cols[coloff]);
369 if (t->len[coloff] > lens[coloff])
370 lens[coloff] = t->len[coloff];
371 }
372 }
373
374 for (t = tbl, cnt = 0; cnt < entries; ++cnt, ++t) {
375 for (coloff = 0; coloff < t->cols - 1; ++coloff) {
376 fputws(t->list[coloff], stdout);
377 #ifdef HAVE_WIDECHAR
378 wprintf(L"%*s", lens[coloff] - t->len[coloff], "");
379 #else
380 printf("%*s", (int) lens[coloff] - t->len[coloff], "");
381 #endif
382 fputws(colsep, stdout);
383 }
384 if (coloff < t->cols) {
385 fputws(t->list[coloff], stdout);
386 putwchar('\n');
387 }
388 }
389
390 for (cnt = 0; cnt < entries; ++cnt) {
391 free((tbl+cnt)->list);
392 free((tbl+cnt)->len);
393 }
394 free(cols);
395 free(lens);
396 free(tbl);
397 }
398
399 static int input(FILE *fp, int *maxlength, wchar_t ***list, int *entries)
400 {
401 static int maxentry = DEFNUM;
402 int len, lineno = 1, reportedline = 0, eval = 0;
403 wchar_t *p, buf[MAXLINELEN];
404 wchar_t **local_list = *list;
405 int local_entries = *entries;
406
407 if (!local_list)
408 local_list = xcalloc(maxentry, sizeof(wchar_t *));
409
410 while (1) {
411 if (fgetws(buf, MAXLINELEN, fp) == NULL) {
412 if (feof(fp))
413 break;
414 else
415 err(EXIT_FAILURE, _("read failed"));
416 }
417 for (p = buf; *p && iswspace(*p); ++p)
418 ;
419 if (!*p)
420 continue;
421 if (!(p = wcschr(p, '\n')) && !feof(fp)) {
422 if (reportedline < lineno) {
423 warnx(_("line %d is too long, output will be truncated"),
424 lineno);
425 reportedline = lineno;
426 }
427 eval = 1;
428 continue;
429 }
430 lineno++;
431 if (!feof(fp) && p)
432 *p = '\0';
433 len = width(buf); /* len = p - buf; */
434 if (*maxlength < len)
435 *maxlength = len;
436 if (local_entries == maxentry) {
437 maxentry += DEFNUM;
438 local_list = xrealloc(local_list,
439 (u_int)maxentry * sizeof(wchar_t *));
440 }
441 local_list[local_entries++] = wcsdup(buf);
442 }
443
444 *list = local_list;
445 *entries = local_entries;
446
447 return eval;
448 }
449
450 #ifdef HAVE_WIDECHAR
451 static wchar_t *mbs_to_wcs(const char *s)
452 {
453 ssize_t n;
454 wchar_t *wcs;
455
456 n = mbstowcs((wchar_t *)0, s, 0);
457 if (n < 0)
458 return NULL;
459 wcs = xmalloc((n + 1) * sizeof(wchar_t));
460 n = mbstowcs(wcs, s, n + 1);
461 if (n < 0) {
462 free(wcs);
463 return NULL;
464 }
465 return wcs;
466 }
467 #endif
468
469 #ifndef HAVE_WIDECHAR
470 static char *mtsafe_strtok(char *str, const char *delim, char **ptr)
471 {
472 if (str == NULL) {
473 str = *ptr;
474 if (str == NULL)
475 return NULL;
476 }
477 str += strspn(str, delim);
478 if (*str == '\0') {
479 *ptr = NULL;
480 return NULL;
481 } else {
482 char *token_end = strpbrk(str, delim);
483 if (token_end) {
484 *token_end = '\0';
485 *ptr = token_end + 1;
486 } else
487 *ptr = NULL;
488 return str;
489 }
490 }
491 #endif