]> git.ipfire.org Git - thirdparty/util-linux.git/blob - text-utils/col.c
libfdisk: (dos) accept start for log.partitions on template
[thirdparty/util-linux.git] / text-utils / col.c
1 /*-
2 * Copyright (c) 1990 The Regents of the University of California.
3 * All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Michael Rendell of the Memorial University of Newfoundland.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 *
36 * Wed Jun 22 22:15:41 1994, faith@cs.unc.edu: Added internationalization
37 * patches from Andries.Brouwer@cwi.nl
38 * Wed Sep 14 22:31:17 1994: patches from Carl Christofferson
39 * (cchris@connected.com)
40 * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
41 * added Native Language Support
42 * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
43 * modified to work correctly in multi-byte locales
44 *
45 */
46
47 /*
48 * This command is deprecated. The utility is in maintenance mode,
49 * meaning we keep them in source tree for backward compatibility
50 * only. Do not waste time making this command better, unless the
51 * fix is about security or other very critical issue.
52 *
53 * See Documentation/deprecated.txt for more information.
54 */
55
56 #include <stdlib.h>
57 #include <errno.h>
58 #include <ctype.h>
59 #include <string.h>
60 #include <stdio.h>
61 #include <unistd.h>
62 #include <getopt.h>
63
64 #include "nls.h"
65 #include "xalloc.h"
66 #include "widechar.h"
67 #include "strutils.h"
68 #include "closestream.h"
69
70 #define BS '\b' /* backspace */
71 #define TAB '\t' /* tab */
72 #define SPACE ' ' /* space */
73 #define NL '\n' /* newline */
74 #define CR '\r' /* carriage return */
75 #define ESC '\033' /* escape */
76 #define SI '\017' /* shift in to normal character set */
77 #define SO '\016' /* shift out to alternate character set */
78 #define VT '\013' /* vertical tab (aka reverse line feed) */
79 #define RLF '\007' /* ESC-07 reverse line feed */
80 #define RHLF '\010' /* ESC-010 reverse half-line feed */
81 #define FHLF '\011' /* ESC-011 forward half-line feed */
82
83 /* build up at least this many lines before flushing them out */
84 #define BUFFER_MARGIN 32
85
86 typedef char CSET;
87
88 typedef struct char_str {
89 #define CS_NORMAL 1
90 #define CS_ALTERNATE 2
91 short c_column; /* column character is in */
92 CSET c_set; /* character set (currently only 2) */
93 wchar_t c_char; /* character in question */
94 int c_width; /* character width */
95 } CHAR;
96
97 typedef struct line_str LINE;
98 struct line_str {
99 CHAR *l_line; /* characters on the line */
100 LINE *l_prev; /* previous line */
101 LINE *l_next; /* next line */
102 int l_lsize; /* allocated sizeof l_line */
103 int l_line_len; /* strlen(l_line) */
104 int l_needs_sort; /* set if chars went in out of order */
105 int l_max_col; /* max column in the line */
106 };
107
108 void free_line(LINE *l);
109 void flush_line(LINE *l);
110 void flush_lines(int);
111 void flush_blanks(void);
112 LINE *alloc_line(void);
113
114 static CSET last_set; /* char_set of last char printed */
115 static LINE *lines;
116 static int compress_spaces; /* if doing space -> tab conversion */
117 static int fine; /* if `fine' resolution (half lines) */
118 static unsigned max_bufd_lines; /* max # lines to keep in memory */
119 static int nblank_lines; /* # blanks after last flushed line */
120 static int no_backspaces; /* if not to output any backspaces */
121 static int pass_unknown_seqs; /* whether to pass unknown control sequences */
122
123 #define PUTC(ch) \
124 if (putwchar(ch) == WEOF) \
125 wrerr();
126
127 static void __attribute__((__noreturn__)) usage(FILE *out)
128 {
129 fprintf(out, _(
130 "\nUsage:\n"
131 " %s [options]\n"), program_invocation_short_name);
132
133 fputs(USAGE_SEPARATOR, out);
134 fputs(_("Filter out reverse line feeds.\n"), out);
135
136 fprintf(out, _(
137 "\nOptions:\n"
138 " -b, --no-backspaces do not output backspaces\n"
139 " -f, --fine permit forward half line feeds\n"
140 " -p, --pass pass unknown control sequences\n"
141 " -h, --tabs convert spaces to tabs\n"
142 " -x, --spaces convert tabs to spaces\n"
143 " -l, --lines NUM buffer at least NUM lines\n"
144 " -V, --version output version information and exit\n"
145 " -H, --help display this help and exit\n\n"));
146
147 fprintf(out, _(
148 "%s reads from standard input and writes to standard output\n\n"),
149 program_invocation_short_name);
150
151 fprintf(out, USAGE_MAN_TAIL("col(1)"));
152 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
153 }
154
155 static void __attribute__((__noreturn__)) wrerr(void)
156 {
157 errx(EXIT_FAILURE, _("write error"));
158 }
159
160 int main(int argc, char **argv)
161 {
162 register wint_t ch;
163 CHAR *c = NULL;
164 CSET cur_set; /* current character set */
165 LINE *l; /* current line */
166 int extra_lines; /* # of lines above first line */
167 int cur_col; /* current column */
168 int cur_line; /* line number of current position */
169 int max_line; /* max value of cur_line */
170 int this_line; /* line l points to */
171 int nflushd_lines; /* number of lines that were flushed */
172 int adjust, opt, warned;
173 int ret = EXIT_SUCCESS;
174
175 static const struct option longopts[] = {
176 { "no-backspaces", no_argument, NULL, 'b' },
177 { "fine", no_argument, NULL, 'f' },
178 { "pass", no_argument, NULL, 'p' },
179 { "tabs", no_argument, NULL, 'h' },
180 { "spaces", no_argument, NULL, 'x' },
181 { "lines", required_argument, NULL, 'l' },
182 { "version", no_argument, NULL, 'V' },
183 { "help", no_argument, NULL, 'H' },
184 { NULL, 0, NULL, 0 }
185 };
186
187 setlocale(LC_ALL, "");
188 bindtextdomain(PACKAGE, LOCALEDIR);
189 textdomain(PACKAGE);
190 atexit(close_stdout);
191
192 max_bufd_lines = 128 * 2;
193 compress_spaces = 1; /* compress spaces into tabs */
194 pass_unknown_seqs = 0; /* remove unknown escape sequences */
195
196 while ((opt = getopt_long(argc, argv, "bfhl:pxVH", longopts, NULL)) != -1)
197 switch (opt) {
198 case 'b': /* do not output backspaces */
199 no_backspaces = 1;
200 break;
201 case 'f': /* allow half forward line feeds */
202 fine = 1;
203 break;
204 case 'h': /* compress spaces into tabs */
205 compress_spaces = 1;
206 break;
207 case 'l':
208 /*
209 * Buffered line count, which is a value in half
210 * lines e.g. twice the amount specified.
211 */
212 max_bufd_lines = strtou32_or_err(optarg, _("bad -l argument")) * 2;
213 break;
214 case 'p':
215 pass_unknown_seqs = 1;
216 break;
217 case 'x': /* do not compress spaces into tabs */
218 compress_spaces = 0;
219 break;
220 case 'V':
221 printf(UTIL_LINUX_VERSION);
222 return EXIT_SUCCESS;
223 case 'H':
224 usage(stdout);
225 default:
226 errtryhelp(EXIT_FAILURE);
227 }
228
229 if (optind != argc)
230 usage(stderr);
231
232 adjust = cur_col = extra_lines = warned = 0;
233 cur_line = max_line = nflushd_lines = this_line = 0;
234 cur_set = last_set = CS_NORMAL;
235 lines = l = alloc_line();
236
237 while (feof(stdin) == 0) {
238 errno = 0;
239 if ((ch = getwchar()) == WEOF) {
240 if (errno == EILSEQ) {
241 warn(NULL);
242 ret = EXIT_FAILURE;
243 }
244 break;
245 }
246 if (!iswgraph(ch)) {
247 switch (ch) {
248 case BS: /* can't go back further */
249 if (cur_col == 0)
250 continue;
251 if (c)
252 cur_col -= c->c_width;
253 else
254 cur_col--;
255 continue;
256 case CR:
257 cur_col = 0;
258 continue;
259 case ESC: /* just ignore EOF */
260 switch(getwchar()) {
261 case RLF:
262 cur_line -= 2;
263 break;
264 case RHLF:
265 cur_line--;
266 break;
267 case FHLF:
268 cur_line++;
269 if (cur_line > max_line)
270 max_line = cur_line;
271 }
272 continue;
273 case NL:
274 cur_line += 2;
275 if (cur_line > max_line)
276 max_line = cur_line;
277 cur_col = 0;
278 continue;
279 case SPACE:
280 ++cur_col;
281 continue;
282 case SI:
283 cur_set = CS_NORMAL;
284 continue;
285 case SO:
286 cur_set = CS_ALTERNATE;
287 continue;
288 case TAB: /* adjust column */
289 cur_col |= 7;
290 ++cur_col;
291 continue;
292 case VT:
293 cur_line -= 2;
294 continue;
295 }
296 if (iswspace(ch)) {
297 if (wcwidth(ch) > 0)
298 cur_col += wcwidth(ch);
299 continue;
300 }
301 if (!pass_unknown_seqs)
302 continue;
303 }
304
305 /* Must stuff ch in a line - are we at the right one? */
306 if (cur_line != this_line - adjust) {
307 LINE *lnew;
308 int nmove;
309
310 adjust = 0;
311 nmove = cur_line - this_line;
312 if (!fine) {
313 /* round up to next line */
314 if (cur_line & 1) {
315 adjust = 1;
316 nmove++;
317 }
318 }
319 if (nmove < 0) {
320 for (; nmove < 0 && l->l_prev; nmove++)
321 l = l->l_prev;
322 if (nmove) {
323 if (nflushd_lines == 0) {
324 /*
325 * Allow backup past first
326 * line if nothing has been
327 * flushed yet.
328 */
329 for (; nmove < 0; nmove++) {
330 lnew = alloc_line();
331 l->l_prev = lnew;
332 lnew->l_next = l;
333 l = lines = lnew;
334 extra_lines++;
335 }
336 } else {
337 if (!warned++)
338 warnx(
339 _("warning: can't back up %s."), cur_line < 0 ?
340 _("past first line") : _("-- line already flushed"));
341 cur_line -= nmove;
342 }
343 }
344 } else {
345 /* may need to allocate here */
346 for (; nmove > 0 && l->l_next; nmove--)
347 l = l->l_next;
348 for (; nmove > 0; nmove--) {
349 lnew = alloc_line();
350 lnew->l_prev = l;
351 l->l_next = lnew;
352 l = lnew;
353 }
354 }
355 this_line = cur_line + adjust;
356 nmove = this_line - nflushd_lines;
357 if (nmove > 0
358 && (unsigned) nmove >= max_bufd_lines + BUFFER_MARGIN) {
359 nflushd_lines += nmove - max_bufd_lines;
360 flush_lines(nmove - max_bufd_lines);
361 }
362 }
363 /* grow line's buffer? */
364 if (l->l_line_len + 1 >= l->l_lsize) {
365 int need;
366
367 need = l->l_lsize ? l->l_lsize * 2 : 90;
368 l->l_line = xrealloc((void *) l->l_line,
369 (unsigned) need * sizeof(CHAR));
370 l->l_lsize = need;
371 }
372 c = &l->l_line[l->l_line_len++];
373 c->c_char = ch;
374 c->c_set = cur_set;
375 if (0 < cur_col)
376 c->c_column = cur_col;
377 else
378 c->c_column = 0;
379 c->c_width = wcwidth(ch);
380 /*
381 * If things are put in out of order, they will need sorting
382 * when it is flushed.
383 */
384 if (cur_col < l->l_max_col)
385 l->l_needs_sort = 1;
386 else
387 l->l_max_col = cur_col;
388 if (c->c_width > 0)
389 cur_col += c->c_width;
390 }
391 /* goto the last line that had a character on it */
392 for (; l->l_next; l = l->l_next)
393 this_line++;
394 if (max_line == 0)
395 return EXIT_SUCCESS; /* no lines, so just exit */
396 flush_lines(this_line - nflushd_lines + extra_lines + 1);
397
398 /* make sure we leave things in a sane state */
399 if (last_set != CS_NORMAL)
400 PUTC('\017');
401
402 /* flush out the last few blank lines */
403 nblank_lines = max_line - this_line;
404 if (max_line & 1)
405 nblank_lines++;
406 else if (!nblank_lines)
407 /* missing a \n on the last line? */
408 nblank_lines = 2;
409 flush_blanks();
410 return ret;
411 }
412
413 void flush_lines(int nflush)
414 {
415 LINE *l;
416
417 while (--nflush >= 0) {
418 l = lines;
419 lines = l->l_next;
420 if (l->l_line) {
421 flush_blanks();
422 flush_line(l);
423 }
424 nblank_lines++;
425 free((void *)l->l_line);
426 free_line(l);
427 }
428 if (lines)
429 lines->l_prev = NULL;
430 }
431
432 /*
433 * Print a number of newline/half newlines. If fine flag is set, nblank_lines
434 * is the number of half line feeds, otherwise it is the number of whole line
435 * feeds.
436 */
437 void flush_blanks(void)
438 {
439 int half, i, nb;
440
441 half = 0;
442 nb = nblank_lines;
443 if (nb & 1) {
444 if (fine)
445 half = 1;
446 else
447 nb++;
448 }
449 nb /= 2;
450 for (i = nb; --i >= 0;)
451 PUTC('\n');
452 if (half) {
453 PUTC('\033');
454 PUTC('9');
455 if (!nb)
456 PUTC('\r');
457 }
458 nblank_lines = 0;
459 }
460
461 /*
462 * Write a line to stdout taking care of space to tab conversion (-h flag)
463 * and character set shifts.
464 */
465 void flush_line(LINE *l)
466 {
467 CHAR *c, *endc;
468 int nchars, last_col, this_col;
469
470 last_col = 0;
471 nchars = l->l_line_len;
472
473 if (l->l_needs_sort) {
474 static CHAR *sorted;
475 static int count_size, *count, i, save, sorted_size, tot;
476
477 /*
478 * Do an O(n) sort on l->l_line by column being careful to
479 * preserve the order of characters in the same column.
480 */
481 if (l->l_lsize > sorted_size) {
482 sorted_size = l->l_lsize;
483 sorted = xrealloc((void *)sorted,
484 (unsigned)sizeof(CHAR) * sorted_size);
485 }
486 if (l->l_max_col >= count_size) {
487 count_size = l->l_max_col + 1;
488 count = (int *)xrealloc((void *)count,
489 (unsigned)sizeof(int) * count_size);
490 }
491 memset(count, 0, sizeof(int) * l->l_max_col + 1);
492 for (i = nchars, c = l->l_line; --i >= 0; c++)
493 count[c->c_column]++;
494
495 /*
496 * calculate running total (shifted down by 1) to use as
497 * indices into new line.
498 */
499 for (tot = 0, i = 0; i <= l->l_max_col; i++) {
500 save = count[i];
501 count[i] = tot;
502 tot += save;
503 }
504
505 for (i = nchars, c = l->l_line; --i >= 0; c++)
506 sorted[count[c->c_column]++] = *c;
507 c = sorted;
508 } else
509 c = l->l_line;
510 while (nchars > 0) {
511 this_col = c->c_column;
512 endc = c;
513 do {
514 ++endc;
515 } while (--nchars > 0 && this_col == endc->c_column);
516
517 /* if -b only print last character */
518 if (no_backspaces) {
519 c = endc - 1;
520 if (nchars > 0 &&
521 this_col + c->c_width > endc->c_column)
522 continue;
523 }
524
525 if (this_col > last_col) {
526 int nspace = this_col - last_col;
527
528 if (compress_spaces && nspace > 1) {
529 int ntabs;
530
531 ntabs = this_col / 8 - last_col / 8;
532 if (ntabs > 0) {
533 nspace = this_col & 7;
534 while (--ntabs >= 0)
535 PUTC('\t');
536 }
537 }
538 while (--nspace >= 0)
539 PUTC(' ');
540 last_col = this_col;
541 }
542
543 for (;;) {
544 if (c->c_set != last_set) {
545 switch (c->c_set) {
546 case CS_NORMAL:
547 PUTC('\017');
548 break;
549 case CS_ALTERNATE:
550 PUTC('\016');
551 }
552 last_set = c->c_set;
553 }
554 PUTC(c->c_char);
555 if ((c + 1) < endc) {
556 int i;
557 for (i=0; i < c->c_width; i++)
558 PUTC('\b');
559 }
560 if (++c >= endc)
561 break;
562 }
563 last_col += (c - 1)->c_width;
564 }
565 }
566
567 #define NALLOC 64
568
569 static LINE *line_freelist;
570
571 LINE *
572 alloc_line(void)
573 {
574 LINE *l;
575 int i;
576
577 if (!line_freelist) {
578 l = xmalloc(sizeof(LINE) * NALLOC);
579 line_freelist = l;
580 for (i = 1; i < NALLOC; i++, l++)
581 l->l_next = l + 1;
582 l->l_next = NULL;
583 }
584 l = line_freelist;
585 line_freelist = l->l_next;
586
587 memset(l, 0, sizeof(LINE));
588 return l;
589 }
590
591 void free_line(LINE *l)
592 {
593 l->l_next = line_freelist;
594 line_freelist = l;
595 }