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 "widechar.h"
54 #include "c.h"
55 #include "xalloc.h"
56 #include "strutils.h"
57 #include "closestream.h"
58 #include "ttyutils.h"
59 
60 #ifdef HAVE_WIDECHAR
61 #define wcs_width(s) wcswidth(s,wcslen(s))
62 static wchar_t *mbs_to_wcs(const char *);
63 #else
64 #define wcs_width(s) strlen(s)
65 #define mbs_to_wcs(s) xstrdup(s)
66 static char *mtsafe_strtok(char *, const char *, char **);
67 #define wcstok mtsafe_strtok
68 #endif
69 
70 #define DEFCOLS     25
71 #define TAB         8
72 #define DEFNUM      1000
73 #define MAXLINELEN  (LINE_MAX + 1)
74 
75 static int input(FILE *fp, int *maxlength, wchar_t ***list, int *entries);
76 static void c_columnate(int maxlength, long termwidth, wchar_t **list, int entries);
77 static void r_columnate(int maxlength, long termwidth, wchar_t **list, int entries);
78 static wchar_t *local_wcstok(wchar_t *p, const wchar_t *separator, int greedy, wchar_t **wcstok_state);
79 static void maketbl(wchar_t **list, int entries, wchar_t *separator, int greedy, wchar_t *colsep);
80 static void print(wchar_t **list, int entries);
81 
82 typedef struct _tbl {
83 	wchar_t **list;
84 	int cols, *len;
85 } TBL;
86 
usage(int rc)87 static void __attribute__((__noreturn__)) usage(int rc)
88 {
89 	FILE *out = rc == EXIT_FAILURE ? stderr : stdout;
90 
91 	fputs(USAGE_HEADER, out);
92 	fprintf(out, _(" %s [options] [<file>...]\n"), program_invocation_short_name);
93 	fputs(USAGE_OPTIONS, out);
94 	fputs(_(" -c, --columns <width>    width of output in number of characters\n"), out);
95 	fputs(_(" -t, --table              create a table\n"), out);
96 	fputs(_(" -s, --separator <string> possible table delimiters\n"), out);
97 	fputs(_(" -o, --output-separator <string>\n"
98 	        "                          columns separator for table output; default is two spaces\n"), out);
99 	fputs(_(" -x, --fillrows           fill rows before columns\n"), out);
100 	fputs(USAGE_SEPARATOR, out);
101 	fputs(USAGE_HELP, out);
102 	fputs(USAGE_VERSION, out);
103 	fprintf(out, USAGE_MAN_TAIL("column(1)"));
104 
105 	exit(rc);
106 }
107 
main(int argc,char ** argv)108 int main(int argc, char **argv)
109 {
110 	int ch, tflag = 0, xflag = 0;
111 	int i;
112 	int termwidth = 80;
113 	int entries = 0;		/* number of records */
114 	unsigned int eval = 0;		/* exit value */
115 	int maxlength = 0;		/* longest record */
116 	wchar_t **list = NULL;		/* array of pointers to records */
117 	int greedy = 1;
118 	wchar_t *colsep;		/* table column output separator */
119 
120 	/* field separator for table option */
121 	wchar_t default_separator[] = { '\t', ' ', 0 };
122 	wchar_t *separator = default_separator;
123 
124 	static const struct option longopts[] =
125 	{
126 		{ "help",	0, 0, 'h' },
127 		{ "version",    0, 0, 'V' },
128 		{ "columns",	1, 0, 'c' },
129 		{ "table",	0, 0, 't' },
130 		{ "separator",	1, 0, 's' },
131 		{ "output-separator", 1, 0, 'o' },
132 		{ "fillrows",	0, 0, 'x' },
133 		{ NULL,		0, 0, 0 },
134 	};
135 
136 	setlocale(LC_ALL, "");
137 	bindtextdomain(PACKAGE, LOCALEDIR);
138 	textdomain(PACKAGE);
139 	atexit(close_stdout);
140 
141 	termwidth = get_terminal_width();
142 	if (termwidth <= 0)
143 		termwidth = 80;
144 	colsep = mbs_to_wcs("  ");
145 
146 	while ((ch = getopt_long(argc, argv, "hVc:s:txo:", longopts, NULL)) != -1)
147 		switch(ch) {
148 		case 'h':
149 			usage(EXIT_SUCCESS);
150 			break;
151 		case 'V':
152 			printf(_("%s from %s\n"), program_invocation_short_name,
153 				 PACKAGE_STRING);
154 				 return EXIT_SUCCESS;
155 		case 'c':
156 			termwidth = strtou32_or_err(optarg, _("invalid columns argument"));
157 			break;
158 		case 's':
159 			separator = mbs_to_wcs(optarg);
160 			greedy = 0;
161 			break;
162 		case 'o':
163 			free(colsep);
164 			colsep = mbs_to_wcs(optarg);
165 			break;
166 		case 't':
167 			tflag = 1;
168 			break;
169 		case 'x':
170 			xflag = 1;
171 			break;
172 		default:
173 			usage(EXIT_FAILURE);
174 	}
175 	argc -= optind;
176 	argv += optind;
177 
178 	if (!*argv)
179 		eval += input(stdin, &maxlength, &list, &entries);
180 	else
181 		for (; *argv; ++argv) {
182 			FILE *fp;
183 
184 			if ((fp = fopen(*argv, "r")) != NULL) {
185 				eval += input(fp, &maxlength, &list, &entries);
186 				fclose(fp);
187 			} else {
188 				warn("%s", *argv);
189 				eval += EXIT_FAILURE;
190 			}
191 		}
192 
193 	if (!entries)
194 		exit(eval);
195 
196 	if (tflag)
197 		maketbl(list, entries, separator, greedy, colsep);
198 	else if (maxlength >= termwidth)
199 		print(list, entries);
200 	else if (xflag)
201 		c_columnate(maxlength, termwidth, list, entries);
202 	else
203 		r_columnate(maxlength, termwidth, list, entries);
204 
205 	for (i = 0; i < entries; i++)
206 		free(list[i]);
207 	free(list);
208 
209 	if (eval == 0)
210 		return EXIT_SUCCESS;
211 	else
212 		return EXIT_FAILURE;
213 }
214 
c_columnate(int maxlength,long termwidth,wchar_t ** list,int entries)215 static void c_columnate(int maxlength, long termwidth, wchar_t **list, int entries)
216 {
217 	int chcnt, col, cnt, endcol, numcols;
218 	wchar_t **lp;
219 
220 	maxlength = (maxlength + TAB) & ~(TAB - 1);
221 	numcols = termwidth / maxlength;
222 	endcol = maxlength;
223 	for (chcnt = col = 0, lp = list;; ++lp) {
224 		fputws(*lp, stdout);
225 		chcnt += wcs_width(*lp);
226 		if (!--entries)
227 			break;
228 		if (++col == numcols) {
229 			chcnt = col = 0;
230 			endcol = maxlength;
231 			putwchar('\n');
232 		} else {
233 			while ((cnt = ((chcnt + TAB) & ~(TAB - 1))) <= endcol) {
234 				putwchar('\t');
235 				chcnt = cnt;
236 			}
237 			endcol += maxlength;
238 		}
239 	}
240 	if (chcnt)
241 		putwchar('\n');
242 }
243 
r_columnate(int maxlength,long termwidth,wchar_t ** list,int entries)244 static void r_columnate(int maxlength, long termwidth, wchar_t **list, int entries)
245 {
246 	int base, chcnt, cnt, col, endcol, numcols, numrows, row;
247 
248 	maxlength = (maxlength + TAB) & ~(TAB - 1);
249 	numcols = termwidth / maxlength;
250 	if (!numcols)
251 		numcols = 1;
252 	numrows = entries / numcols;
253 	if (entries % numcols)
254 		++numrows;
255 
256 	for (row = 0; row < numrows; ++row) {
257 		endcol = maxlength;
258 		for (base = row, chcnt = col = 0; col < numcols; ++col) {
259 			fputws(list[base], stdout);
260 			chcnt += wcs_width(list[base]);
261 			if ((base += numrows) >= entries)
262 				break;
263 			while ((cnt = ((chcnt + TAB) & ~(TAB - 1))) <= endcol) {
264 				putwchar('\t');
265 				chcnt = cnt;
266 			}
267 			endcol += maxlength;
268 		}
269 		putwchar('\n');
270 	}
271 }
272 
print(wchar_t ** list,int entries)273 static void print(wchar_t **list, int entries)
274 {
275 	int cnt;
276 	wchar_t **lp;
277 
278 	for (cnt = entries, lp = list; cnt--; ++lp) {
279 		fputws(*lp, stdout);
280 		putwchar('\n');
281 	}
282 }
283 
local_wcstok(wchar_t * p,const wchar_t * separator,int greedy,wchar_t ** wcstok_state)284 wchar_t *local_wcstok(wchar_t * p, const wchar_t * separator, int greedy,
285 		      wchar_t ** wcstok_state)
286 {
287 	wchar_t *result;
288 	if (greedy)
289 		return wcstok(p, separator, wcstok_state);
290 
291 	if (p == NULL) {
292 		if (*wcstok_state == NULL)
293 			return NULL;
294 		else
295 			p = *wcstok_state;
296 	}
297 	result = p;
298 	p = wcspbrk(result, separator);
299 	if (p == NULL)
300 		*wcstok_state = NULL;
301 	else {
302 		*p = '\0';
303 		*wcstok_state = p + 1;
304 	}
305 	return result;
306 }
307 
maketbl(wchar_t ** list,int entries,wchar_t * separator,int greedy,wchar_t * colsep)308 static void maketbl(wchar_t **list, int entries, wchar_t *separator, int greedy, wchar_t *colsep)
309 {
310 	TBL *t;
311 	int cnt;
312 	wchar_t *p, **lp;
313 	ssize_t *lens;
314 	ssize_t maxcols = DEFCOLS, coloff;
315 	TBL *tbl;
316 	wchar_t **cols;
317 	wchar_t *wcstok_state = NULL;
318 
319 	t = tbl = xcalloc(entries, sizeof(TBL));
320 	cols = xcalloc(maxcols, sizeof(wchar_t *));
321 	lens = xcalloc(maxcols, sizeof(ssize_t));
322 
323 	for (lp = list, cnt = 0; cnt < entries; ++cnt, ++lp, ++t) {
324 		coloff = 0;
325 		p = *lp;
326 		while ((cols[coloff] = local_wcstok(p, separator, greedy, &wcstok_state)) != NULL) {
327 			if (++coloff == maxcols) {
328 				maxcols += DEFCOLS;
329 				cols = xrealloc(cols, maxcols * sizeof(wchar_t *));
330 				lens = xrealloc(lens, maxcols * sizeof(ssize_t));
331 				/* zero fill only new memory */
332 				memset(lens + (maxcols - DEFCOLS), 0,
333 				       DEFCOLS * sizeof(*lens));
334 			}
335 			p = NULL;
336 		}
337 		t->list = xcalloc(coloff, sizeof(wchar_t *));
338 		t->len = xcalloc(coloff, sizeof(int));
339 		for (t->cols = coloff; --coloff >= 0;) {
340 			t->list[coloff] = cols[coloff];
341 			t->len[coloff] = wcs_width(cols[coloff]);
342 			if (t->len[coloff] > lens[coloff])
343 				lens[coloff] = t->len[coloff];
344 		}
345 	}
346 
347 	for (t = tbl, cnt = 0; cnt < entries; ++cnt, ++t) {
348 		for (coloff = 0; coloff < t->cols - 1; ++coloff) {
349 			fputws(t->list[coloff], stdout);
350 			wprintf(L"%*s", lens[coloff] - t->len[coloff], "");
351 			fputws(colsep, stdout);
352 		}
353 		if (coloff < t->cols) {
354 			fputws(t->list[coloff], stdout);
355 			putwchar('\n');
356 		}
357 	}
358 
359 	for (cnt = 0; cnt < entries; ++cnt) {
360 		free((tbl+cnt)->list);
361 		free((tbl+cnt)->len);
362 	}
363 	free(cols);
364 	free(lens);
365 	free(tbl);
366 }
367 
input(FILE * fp,int * maxlength,wchar_t *** list,int * entries)368 static int input(FILE *fp, int *maxlength, wchar_t ***list, int *entries)
369 {
370 	static int maxentry = DEFNUM;
371 	int len, lineno = 1, reportedline = 0, eval = 0;
372 	wchar_t *p, buf[MAXLINELEN];
373 	wchar_t **local_list = *list;
374 	int local_entries = *entries;
375 
376 	if (!local_list)
377 		local_list = xcalloc(maxentry, sizeof(wchar_t *));
378 
379 	while (1) {
380 		if (fgetws(buf, MAXLINELEN, fp) == NULL) {
381 			if (feof(fp))
382 				break;
383 			else
384 				err(EXIT_FAILURE, _("read failed"));
385 		}
386 		for (p = buf; *p && iswspace(*p); ++p)
387 			;
388 		if (!*p)
389 			continue;
390 		if (!(p = wcschr(p, '\n')) && !feof(fp)) {
391 			if (reportedline < lineno) {
392 				warnx(_("line %d is too long, output will be truncated"),
393 					lineno);
394 				reportedline = lineno;
395 			}
396 			eval = 1;
397 			continue;
398 		}
399 		lineno++;
400 		if (!feof(fp) && p)
401 			*p = '\0';
402 		len = wcs_width(buf);	/* len = p - buf; */
403 		if (*maxlength < len)
404 			*maxlength = len;
405 		if (local_entries == maxentry) {
406 			maxentry += DEFNUM;
407 			local_list = xrealloc(local_list,
408 				(u_int)maxentry * sizeof(wchar_t *));
409 		}
410 		local_list[local_entries++] = wcsdup(buf);
411 	}
412 
413 	*list = local_list;
414 	*entries = local_entries;
415 
416 	return eval;
417 }
418 
419 #ifdef HAVE_WIDECHAR
mbs_to_wcs(const char * s)420 static wchar_t *mbs_to_wcs(const char *s)
421 {
422 	ssize_t n;
423 	wchar_t *wcs;
424 
425 	n = mbstowcs((wchar_t *)0, s, 0);
426 	if (n < 0)
427 		return NULL;
428 	wcs = xmalloc((n + 1) * sizeof(wchar_t));
429 	n = mbstowcs(wcs, s, n + 1);
430 	if (n < 0) {
431 		free(wcs);
432 		return NULL;
433 	}
434 	return wcs;
435 }
436 #endif
437 
438 #ifndef HAVE_WIDECHAR
mtsafe_strtok(char * str,const char * delim,char ** ptr)439 static char *mtsafe_strtok(char *str, const char *delim, char **ptr)
440 {
441 	if (str == NULL) {
442 		str = *ptr;
443 		if (str == NULL)
444 			return NULL;
445 	}
446 	str += strspn(str, delim);
447 	if (*str == '\0') {
448 		*ptr = NULL;
449 		return NULL;
450 	} else {
451 		char *token_end = strpbrk(str, delim);
452 		if (token_end) {
453 			*token_end = '\0';
454 			*ptr = token_end + 1;
455 		} else
456 			*ptr = NULL;
457 		return str;
458 	}
459 }
460 #endif
461