]> git.ipfire.org Git - thirdparty/util-linux.git/blame - text-utils/column.c
misc: add static keyword to where needed [smatch scan]
[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 *
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 */
7eda085c 33
eb63b9b8 34/*
b50945d4 35 * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
eb63b9b8
KZ
36 * added Native Language Support
37 * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
38 * modified to work correctly in multi-byte locales
7eda085c 39 */
6dbe3af9 40
6dbe3af9
KZ
41#include <sys/types.h>
42#include <sys/ioctl.h>
43
44#include <ctype.h>
6dbe3af9 45#include <stdio.h>
fd6b7a7f 46#include <unistd.h>
6dbe3af9
KZ
47#include <stdlib.h>
48#include <string.h>
a4cc8dfe
SK
49#include <errno.h>
50#include <getopt.h>
6dbe3af9 51
3b56eea7 52#include "nls.h"
eb76ca98 53#include "c.h"
9ea8ded3 54#include "widechar.h"
a29e40ca 55#include "xalloc.h"
02b77f7b 56#include "strutils.h"
b87cbe84 57#include "closestream.h"
3b56eea7 58#include "ttyutils.h"
eb63b9b8 59
06b04b23 60#ifdef HAVE_WIDECHAR
eb63b9b8
KZ
61static wchar_t *mbs_to_wcs(const char *);
62#else
a29e40ca 63#define mbs_to_wcs(s) xstrdup(s)
eb63b9b8
KZ
64static char *mtsafe_strtok(char *, const char *, char **);
65#define wcstok mtsafe_strtok
66#endif
67
1ae90932
SK
68#define DEFCOLS 25
69#define TAB 8
70#define DEFNUM 1000
71#define MAXLINELEN (LINE_MAX + 1)
72
daf093d2
SK
73static int input(FILE *fp, int *maxlength, wchar_t ***list, int *entries);
74static void c_columnate(int maxlength, long termwidth, wchar_t **list, int entries);
75static void r_columnate(int maxlength, long termwidth, wchar_t **list, int entries);
732e3dec 76static wchar_t *local_wcstok(wchar_t *p, const wchar_t *separator, int greedy, wchar_t **wcstok_state);
47bd8ddc 77static void maketbl(wchar_t **list, int entries, wchar_t *separator, int greedy, wchar_t *colsep);
daf093d2 78static void print(wchar_t **list, int entries);
6dbe3af9 79
1ae90932
SK
80typedef struct _tbl {
81 wchar_t **list;
82 int cols, *len;
83} TBL;
84
683ddbd5
KZ
85
86#ifdef HAVE_WIDECHAR
87/* Don't use wcswidth(), we need to ignore non-printable chars. */
88static 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
100static 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
a4cc8dfe
SK
112static void __attribute__((__noreturn__)) usage(int rc)
113{
114 FILE *out = rc == EXIT_FAILURE ? stderr : stdout;
115
3eed42f3 116 fputs(USAGE_HEADER, out);
4ce393f4 117 fprintf(out, _(" %s [options] [<file>...]\n"), program_invocation_short_name);
451dbcfa
BS
118
119 fputs(USAGE_SEPARATOR, out);
120 fputs(_("Columnate lists.\n"), out);
121
3eed42f3 122 fputs(USAGE_OPTIONS, out);
d7a3bf94
KZ
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);
3eed42f3
SK
129 fputs(USAGE_SEPARATOR, out);
130 fputs(USAGE_HELP, out);
131 fputs(USAGE_VERSION, out);
132 fprintf(out, USAGE_MAN_TAIL("column(1)"));
a4cc8dfe 133
a4cc8dfe
SK
134 exit(rc);
135}
1df29104
SK
136
137int main(int argc, char **argv)
6dbe3af9 138{
a46e644e 139 int ch, tflag = 0, xflag = 0;
6a41edfa 140 int i;
3b56eea7 141 int termwidth = 80;
daf093d2
SK
142 int entries = 0; /* number of records */
143 unsigned int eval = 0; /* exit value */
a46e644e 144 int maxlength = 0; /* longest record */
daf093d2 145 wchar_t **list = NULL; /* array of pointers to records */
732e3dec 146 int greedy = 1;
47bd8ddc 147 wchar_t *colsep; /* table column output separator */
a46e644e 148
daf093d2
SK
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 {
87918040
SK
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 },
daf093d2
SK
164 };
165
7eda085c
KZ
166 setlocale(LC_ALL, "");
167 bindtextdomain(PACKAGE, LOCALEDIR);
168 textdomain(PACKAGE);
b87cbe84 169 atexit(close_stdout);
6dbe3af9 170
43b4f7ea 171 termwidth = get_terminal_width(80);
47bd8ddc 172 colsep = mbs_to_wcs(" ");
6dbe3af9 173
47bd8ddc 174 while ((ch = getopt_long(argc, argv, "hVc:s:txo:", longopts, NULL)) != -1)
6dbe3af9 175 switch(ch) {
a4cc8dfe 176 case 'h':
1ae90932
SK
177 usage(EXIT_SUCCESS);
178 break;
4ef21375 179 case 'V':
f6277500
SK
180 printf(UTIL_LINUX_VERSION);
181 return EXIT_SUCCESS;
6dbe3af9 182 case 'c':
3b56eea7 183 termwidth = strtou32_or_err(optarg, _("invalid columns argument"));
6dbe3af9
KZ
184 break;
185 case 's':
eb63b9b8 186 separator = mbs_to_wcs(optarg);
732e3dec 187 greedy = 0;
6dbe3af9 188 break;
47bd8ddc
SK
189 case 'o':
190 free(colsep);
191 colsep = mbs_to_wcs(optarg);
192 break;
6dbe3af9
KZ
193 case 't':
194 tflag = 1;
195 break;
196 case 'x':
197 xflag = 1;
198 break;
6dbe3af9 199 default:
677ec86c 200 errtryhelp(EXIT_FAILURE);
1df29104 201 }
6dbe3af9
KZ
202 argc -= optind;
203 argv += optind;
204
205 if (!*argv)
daf093d2 206 eval += input(stdin, &maxlength, &list, &entries);
1df29104 207 else
1ae90932 208 for (; *argv; ++argv) {
a46e644e
KZ
209 FILE *fp;
210
1ae90932 211 if ((fp = fopen(*argv, "r")) != NULL) {
daf093d2
SK
212 eval += input(fp, &maxlength, &list, &entries);
213 fclose(fp);
1ae90932
SK
214 } else {
215 warn("%s", *argv);
daf093d2 216 eval += EXIT_FAILURE;
1ae90932 217 }
6dbe3af9
KZ
218 }
219
220 if (!entries)
221 exit(eval);
222
223 if (tflag)
47bd8ddc 224 maketbl(list, entries, separator, greedy, colsep);
6dbe3af9 225 else if (maxlength >= termwidth)
daf093d2 226 print(list, entries);
6dbe3af9 227 else if (xflag)
daf093d2 228 c_columnate(maxlength, termwidth, list, entries);
6dbe3af9 229 else
daf093d2 230 r_columnate(maxlength, termwidth, list, entries);
dcbca568 231
6a41edfa 232 for (i = 0; i < entries; i++)
dcbca568
SK
233 free(list[i]);
234 free(list);
235
daf093d2
SK
236 if (eval == 0)
237 return EXIT_SUCCESS;
238 else
239 return EXIT_FAILURE;
6dbe3af9
KZ
240}
241
daf093d2 242static void c_columnate(int maxlength, long termwidth, wchar_t **list, int entries)
6dbe3af9
KZ
243{
244 int chcnt, col, cnt, endcol, numcols;
eb63b9b8 245 wchar_t **lp;
6dbe3af9
KZ
246
247 maxlength = (maxlength + TAB) & ~(TAB - 1);
248 numcols = termwidth / maxlength;
249 endcol = maxlength;
250 for (chcnt = col = 0, lp = list;; ++lp) {
eb63b9b8 251 fputws(*lp, stdout);
683ddbd5 252 chcnt += width(*lp);
6dbe3af9
KZ
253 if (!--entries)
254 break;
255 if (++col == numcols) {
256 chcnt = col = 0;
257 endcol = maxlength;
eb63b9b8 258 putwchar('\n');
6dbe3af9 259 } else {
fd6b7a7f 260 while ((cnt = ((chcnt + TAB) & ~(TAB - 1))) <= endcol) {
eb63b9b8 261 putwchar('\t');
6dbe3af9
KZ
262 chcnt = cnt;
263 }
264 endcol += maxlength;
265 }
266 }
267 if (chcnt)
eb63b9b8 268 putwchar('\n');
6dbe3af9
KZ
269}
270
daf093d2 271static void r_columnate(int maxlength, long termwidth, wchar_t **list, int entries)
6dbe3af9
KZ
272{
273 int base, chcnt, cnt, col, endcol, numcols, numrows, row;
274
275 maxlength = (maxlength + TAB) & ~(TAB - 1);
276 numcols = termwidth / maxlength;
683ddbd5 277 if (!numcols)
1ae90932 278 numcols = 1;
6dbe3af9
KZ
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) {
eb63b9b8 286 fputws(list[base], stdout);
683ddbd5 287 chcnt += width(list[base]);
6dbe3af9
KZ
288 if ((base += numrows) >= entries)
289 break;
fd6b7a7f 290 while ((cnt = ((chcnt + TAB) & ~(TAB - 1))) <= endcol) {
eb63b9b8 291 putwchar('\t');
6dbe3af9
KZ
292 chcnt = cnt;
293 }
294 endcol += maxlength;
295 }
eb63b9b8 296 putwchar('\n');
6dbe3af9
KZ
297 }
298}
299
daf093d2 300static void print(wchar_t **list, int entries)
6dbe3af9
KZ
301{
302 int cnt;
eb63b9b8 303 wchar_t **lp;
6dbe3af9 304
eb63b9b8
KZ
305 for (cnt = entries, lp = list; cnt--; ++lp) {
306 fputws(*lp, stdout);
307 putwchar('\n');
308 }
6dbe3af9
KZ
309}
310
2ba641e5
SK
311static wchar_t *local_wcstok(wchar_t *p, const wchar_t *separator, int greedy,
312 wchar_t **wcstok_state)
732e3dec
SK
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
47bd8ddc 335static void maketbl(wchar_t **list, int entries, wchar_t *separator, int greedy, wchar_t *colsep)
6dbe3af9
KZ
336{
337 TBL *t;
d2b7bc74 338 int cnt;
eb63b9b8 339 wchar_t *p, **lp;
4eaeb0ef
SK
340 ssize_t *lens;
341 ssize_t maxcols = DEFCOLS, coloff;
6dbe3af9 342 TBL *tbl;
eb63b9b8 343 wchar_t **cols;
732e3dec 344 wchar_t *wcstok_state = NULL;
6dbe3af9 345
12bef812
SK
346 t = tbl = xcalloc(entries, sizeof(TBL));
347 cols = xcalloc(maxcols, sizeof(wchar_t *));
4eaeb0ef
SK
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;
732e3dec 353 while ((cols[coloff] = local_wcstok(p, separator, greedy, &wcstok_state)) != NULL) {
1ae90932 354 if (++coloff == maxcols) {
1ae90932 355 maxcols += DEFCOLS;
4eaeb0ef
SK
356 cols = xrealloc(cols, maxcols * sizeof(wchar_t *));
357 lens = xrealloc(lens, maxcols * sizeof(ssize_t));
358 /* zero fill only new memory */
26ae00a7
JM
359 memset(lens + (maxcols - DEFCOLS), 0,
360 DEFCOLS * sizeof(*lens));
1ae90932 361 }
4eaeb0ef 362 p = NULL;
1ae90932 363 }
12bef812
SK
364 t->list = xcalloc(coloff, sizeof(wchar_t *));
365 t->len = xcalloc(coloff, sizeof(int));
1ae90932
SK
366 for (t->cols = coloff; --coloff >= 0;) {
367 t->list[coloff] = cols[coloff];
683ddbd5 368 t->len[coloff] = width(cols[coloff]);
1ae90932
SK
369 if (t->len[coloff] > lens[coloff])
370 lens[coloff] = t->len[coloff];
6dbe3af9
KZ
371 }
372 }
4eaeb0ef
SK
373
374 for (t = tbl, cnt = 0; cnt < entries; ++cnt, ++t) {
1ae90932
SK
375 for (coloff = 0; coloff < t->cols - 1; ++coloff) {
376 fputws(t->list[coloff], stdout);
683ddbd5 377#ifdef HAVE_WIDECHAR
d2b7bc74 378 wprintf(L"%*s", lens[coloff] - t->len[coloff], "");
683ddbd5
KZ
379#else
380 printf("%*s", (int) lens[coloff] - t->len[coloff], "");
381#endif
47bd8ddc 382 fputws(colsep, stdout);
1ae90932 383 }
3f7fc4d4
KZ
384 if (coloff < t->cols) {
385 fputws(t->list[coloff], stdout);
386 putwchar('\n');
387 }
6dbe3af9 388 }
dcbca568
SK
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);
6dbe3af9
KZ
397}
398
daf093d2 399static int input(FILE *fp, int *maxlength, wchar_t ***list, int *entries)
6dbe3af9 400{
12bef812 401 static int maxentry = DEFNUM;
daf093d2 402 int len, lineno = 1, reportedline = 0, eval = 0;
eb63b9b8 403 wchar_t *p, buf[MAXLINELEN];
a46e644e 404 wchar_t **local_list = *list;
daf093d2
SK
405 int local_entries = *entries;
406
407 if (!local_list)
408 local_list = xcalloc(maxentry, sizeof(wchar_t *));
6dbe3af9 409
acb5f9b5
SK
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 }
1df29104
SK
417 for (p = buf; *p && iswspace(*p); ++p)
418 ;
6dbe3af9
KZ
419 if (!*p)
420 continue;
80fa094c 421 if (!(p = wcschr(p, '\n')) && !feof(fp)) {
bf90b8a6 422 if (reportedline < lineno) {
1ae90932
SK
423 warnx(_("line %d is too long, output will be truncated"),
424 lineno);
bf90b8a6
SK
425 reportedline = lineno;
426 }
daf093d2 427 eval = 1;
6dbe3af9
KZ
428 continue;
429 }
bf90b8a6 430 lineno++;
c630db89 431 if (!feof(fp) && p)
80fa094c 432 *p = '\0';
683ddbd5 433 len = width(buf); /* len = p - buf; */
daf093d2
SK
434 if (*maxlength < len)
435 *maxlength = len;
436 if (local_entries == maxentry) {
6dbe3af9 437 maxentry += DEFNUM;
daf093d2
SK
438 local_list = xrealloc(local_list,
439 (u_int)maxentry * sizeof(wchar_t *));
6dbe3af9 440 }
daf093d2 441 local_list[local_entries++] = wcsdup(buf);
6dbe3af9 442 }
daf093d2
SK
443
444 *list = local_list;
445 *entries = local_entries;
446
447 return eval;
6dbe3af9
KZ
448}
449
06b04b23 450#ifdef HAVE_WIDECHAR
eb63b9b8
KZ
451static wchar_t *mbs_to_wcs(const char *s)
452{
395801be 453 ssize_t n;
eb63b9b8
KZ
454 wchar_t *wcs;
455
456 n = mbstowcs((wchar_t *)0, s, 0);
457 if (n < 0)
458 return NULL;
cce4d25a 459 wcs = xmalloc((n + 1) * sizeof(wchar_t));
395801be 460 n = mbstowcs(wcs, s, n + 1);
cd70a460
KZ
461 if (n < 0) {
462 free(wcs);
eb63b9b8 463 return NULL;
cd70a460 464 }
eb63b9b8
KZ
465 return wcs;
466}
467#endif
468
06b04b23 469#ifndef HAVE_WIDECHAR
eb63b9b8
KZ
470static 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