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