]> git.ipfire.org Git - thirdparty/util-linux.git/blame - text-utils/col.c
wipefs: add --lock and LOCK_BLOCK_DEVICE
[thirdparty/util-linux.git] / text-utils / col.c
CommitLineData
6dbe3af9
KZ
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)
b50945d4 40 * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
eb63b9b8
KZ
41 * added Native Language Support
42 * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
43 * modified to work correctly in multi-byte locales
7eda085c 44 *
6dbe3af9
KZ
45 */
46
7d2600e2
SK
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
fd6b7a7f 56#include <stdlib.h>
6dbe3af9
KZ
57#include <errno.h>
58#include <ctype.h>
59#include <string.h>
60#include <stdio.h>
fd6b7a7f 61#include <unistd.h>
f688c298 62#include <getopt.h>
eb63b9b8 63
04faca4a
DB
64#include "nls.h"
65#include "xalloc.h"
eb63b9b8 66#include "widechar.h"
b07e72d9 67#include "strutils.h"
b87cbe84 68#include "closestream.h"
6dbe3af9
KZ
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
86typedef char CSET;
87
88typedef struct char_str {
89#define CS_NORMAL 1
90#define CS_ALTERNATE 2
004356f0 91 int c_column; /* column character is in */
6dbe3af9 92 CSET c_set; /* character set (currently only 2) */
eb63b9b8 93 wchar_t c_char; /* character in question */
66ee8158 94 int c_width; /* character width */
6dbe3af9
KZ
95} CHAR;
96
97typedef struct line_str LINE;
98struct 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
fd6b7a7f
KZ
108void free_line(LINE *l);
109void flush_line(LINE *l);
110void flush_lines(int);
111void flush_blanks(void);
fd6b7a7f 112LINE *alloc_line(void);
6dbe3af9 113
2ba641e5
SK
114static CSET last_set; /* char_set of last char printed */
115static LINE *lines;
116static int compress_spaces; /* if doing space -> tab conversion */
117static int fine; /* if `fine' resolution (half lines) */
118static unsigned max_bufd_lines; /* max # lines to keep in memory */
119static int nblank_lines; /* # blanks after last flushed line */
120static int no_backspaces; /* if not to output any backspaces */
121static int pass_unknown_seqs; /* whether to pass unknown control sequences */
6dbe3af9
KZ
122
123#define PUTC(ch) \
eb63b9b8 124 if (putwchar(ch) == WEOF) \
6dbe3af9
KZ
125 wrerr();
126
6e1eda6f 127static void __attribute__((__noreturn__)) usage(void)
2ebef870 128{
6e1eda6f 129 FILE *out = stdout;
f688c298
SK
130 fprintf(out, _(
131 "\nUsage:\n"
132 " %s [options]\n"), program_invocation_short_name);
133
451dbcfa
BS
134 fputs(USAGE_SEPARATOR, out);
135 fputs(_("Filter out reverse line feeds.\n"), out);
136
f688c298
SK
137 fprintf(out, _(
138 "\nOptions:\n"
139 " -b, --no-backspaces do not output backspaces\n"
140 " -f, --fine permit forward half line feeds\n"
141 " -p, --pass pass unknown control sequences\n"
142 " -h, --tabs convert spaces to tabs\n"
143 " -x, --spaces convert tabs to spaces\n"
144 " -l, --lines NUM buffer at least NUM lines\n"
b3054454
RM
145 ));
146 printf( " -H, --help %s\n", USAGE_OPTSTR_HELP);
147 printf( " -V, --version %s\n", USAGE_OPTSTR_VERSION);
f688c298 148
b3054454 149 fputs(USAGE_SEPARATOR, out);
f688c298
SK
150 fprintf(out, _(
151 "%s reads from standard input and writes to standard output\n\n"),
152 program_invocation_short_name);
153
f45f3ec3 154 printf(USAGE_MAN_TAIL("col(1)"));
6e1eda6f 155 exit(EXIT_SUCCESS);
2ebef870
KZ
156}
157
0825179f 158static void __attribute__((__noreturn__)) wrerr(void)
2ebef870 159{
48899b9e 160 errx(EXIT_FAILURE, _("write error"));
2ebef870
KZ
161}
162
fd6b7a7f 163int main(int argc, char **argv)
6dbe3af9 164{
eb63b9b8 165 register wint_t ch;
26c91027 166 CHAR *c = NULL;
6dbe3af9
KZ
167 CSET cur_set; /* current character set */
168 LINE *l; /* current line */
169 int extra_lines; /* # of lines above first line */
170 int cur_col; /* current column */
171 int cur_line; /* line number of current position */
172 int max_line; /* max value of cur_line */
173 int this_line; /* line l points to */
174 int nflushd_lines; /* number of lines that were flushed */
175 int adjust, opt, warned;
2ebef870 176 int ret = EXIT_SUCCESS;
6dbe3af9 177
f688c298 178 static const struct option longopts[] = {
87918040
SK
179 { "no-backspaces", no_argument, NULL, 'b' },
180 { "fine", no_argument, NULL, 'f' },
181 { "pass", no_argument, NULL, 'p' },
182 { "tabs", no_argument, NULL, 'h' },
183 { "spaces", no_argument, NULL, 'x' },
184 { "lines", required_argument, NULL, 'l' },
185 { "version", no_argument, NULL, 'V' },
186 { "help", no_argument, NULL, 'H' },
187 { NULL, 0, NULL, 0 }
f688c298
SK
188 };
189
7eda085c
KZ
190 setlocale(LC_ALL, "");
191 bindtextdomain(PACKAGE, LOCALEDIR);
192 textdomain(PACKAGE);
2c308875 193 close_stdout_atexit();
2ebef870 194
b07e72d9 195 max_bufd_lines = 128 * 2;
6dbe3af9 196 compress_spaces = 1; /* compress spaces into tabs */
22853e4a 197 pass_unknown_seqs = 0; /* remove unknown escape sequences */
f688c298
SK
198
199 while ((opt = getopt_long(argc, argv, "bfhl:pxVH", longopts, NULL)) != -1)
6dbe3af9
KZ
200 switch (opt) {
201 case 'b': /* do not output backspaces */
202 no_backspaces = 1;
203 break;
204 case 'f': /* allow half forward line feeds */
205 fine = 1;
206 break;
207 case 'h': /* compress spaces into tabs */
208 compress_spaces = 1;
209 break;
b07e72d9
SK
210 case 'l':
211 /*
212 * Buffered line count, which is a value in half
213 * lines e.g. twice the amount specified.
214 */
3b56eea7 215 max_bufd_lines = strtou32_or_err(optarg, _("bad -l argument")) * 2;
6dbe3af9 216 break;
22853e4a
KZ
217 case 'p':
218 pass_unknown_seqs = 1;
219 break;
6dbe3af9
KZ
220 case 'x': /* do not compress spaces into tabs */
221 compress_spaces = 0;
222 break;
2c308875 223
f688c298 224 case 'V':
2c308875 225 print_version(EXIT_SUCCESS);
f688c298 226 case 'H':
6e1eda6f 227 usage();
6dbe3af9 228 default:
677ec86c 229 errtryhelp(EXIT_FAILURE);
6dbe3af9
KZ
230 }
231
6e1eda6f
RM
232 if (optind != argc) {
233 warnx(_("bad usage"));
234 errtryhelp(EXIT_FAILURE);
235 }
6dbe3af9 236
6dbe3af9
KZ
237 adjust = cur_col = extra_lines = warned = 0;
238 cur_line = max_line = nflushd_lines = this_line = 0;
239 cur_set = last_set = CS_NORMAL;
240 lines = l = alloc_line();
2ebef870 241
8e0ec3b4 242 while (feof(stdin) == 0) {
e9ce5ccc
KZ
243 errno = 0;
244 if ((ch = getwchar()) == WEOF) {
8e0ec3b4 245 if (errno == EILSEQ) {
cc06250f 246 warn(_("failed on line %d"), max_line + 1);
2ebef870 247 ret = EXIT_FAILURE;
e9ce5ccc
KZ
248 }
249 break;
2ebef870 250 }
eb63b9b8 251 if (!iswgraph(ch)) {
6dbe3af9
KZ
252 switch (ch) {
253 case BS: /* can't go back further */
254 if (cur_col == 0)
255 continue;
26c91027
GM
256 if (c)
257 cur_col -= c->c_width;
258 else
259 cur_col--;
6dbe3af9
KZ
260 continue;
261 case CR:
262 cur_col = 0;
263 continue;
264 case ESC: /* just ignore EOF */
eb63b9b8 265 switch(getwchar()) {
6dbe3af9
KZ
266 case RLF:
267 cur_line -= 2;
268 break;
269 case RHLF:
270 cur_line--;
271 break;
272 case FHLF:
273 cur_line++;
274 if (cur_line > max_line)
275 max_line = cur_line;
276 }
277 continue;
278 case NL:
279 cur_line += 2;
280 if (cur_line > max_line)
281 max_line = cur_line;
282 cur_col = 0;
283 continue;
284 case SPACE:
285 ++cur_col;
286 continue;
287 case SI:
288 cur_set = CS_NORMAL;
289 continue;
290 case SO:
291 cur_set = CS_ALTERNATE;
292 continue;
293 case TAB: /* adjust column */
294 cur_col |= 7;
295 ++cur_col;
296 continue;
297 case VT:
298 cur_line -= 2;
299 continue;
300 }
66ee8158
KZ
301 if (iswspace(ch)) {
302 if (wcwidth(ch) > 0)
303 cur_col += wcwidth(ch);
304 continue;
305 }
22853e4a
KZ
306 if (!pass_unknown_seqs)
307 continue;
6dbe3af9
KZ
308 }
309
310 /* Must stuff ch in a line - are we at the right one? */
311 if (cur_line != this_line - adjust) {
312 LINE *lnew;
313 int nmove;
314
315 adjust = 0;
316 nmove = cur_line - this_line;
317 if (!fine) {
318 /* round up to next line */
319 if (cur_line & 1) {
320 adjust = 1;
321 nmove++;
322 }
323 }
324 if (nmove < 0) {
325 for (; nmove < 0 && l->l_prev; nmove++)
326 l = l->l_prev;
327 if (nmove) {
328 if (nflushd_lines == 0) {
329 /*
330 * Allow backup past first
331 * line if nothing has been
332 * flushed yet.
333 */
334 for (; nmove < 0; nmove++) {
335 lnew = alloc_line();
336 l->l_prev = lnew;
337 lnew->l_next = l;
338 l = lines = lnew;
339 extra_lines++;
340 }
341 } else {
342 if (!warned++)
2ebef870
KZ
343 warnx(
344 _("warning: can't back up %s."), cur_line < 0 ?
345 _("past first line") : _("-- line already flushed"));
6dbe3af9
KZ
346 cur_line -= nmove;
347 }
348 }
349 } else {
350 /* may need to allocate here */
351 for (; nmove > 0 && l->l_next; nmove--)
352 l = l->l_next;
353 for (; nmove > 0; nmove--) {
354 lnew = alloc_line();
355 lnew->l_prev = l;
356 l->l_next = lnew;
357 l = lnew;
358 }
359 }
360 this_line = cur_line + adjust;
361 nmove = this_line - nflushd_lines;
3b56eea7
KZ
362 if (nmove > 0
363 && (unsigned) nmove >= max_bufd_lines + BUFFER_MARGIN) {
6dbe3af9
KZ
364 nflushd_lines += nmove - max_bufd_lines;
365 flush_lines(nmove - max_bufd_lines);
366 }
367 }
368 /* grow line's buffer? */
369 if (l->l_line_len + 1 >= l->l_lsize) {
370 int need;
371
372 need = l->l_lsize ? l->l_lsize * 2 : 90;
fea1cbf7 373 l->l_line = xrealloc((void *) l->l_line,
04faca4a 374 (unsigned) need * sizeof(CHAR));
6dbe3af9
KZ
375 l->l_lsize = need;
376 }
377 c = &l->l_line[l->l_line_len++];
378 c->c_char = ch;
379 c->c_set = cur_set;
26c91027
GM
380 if (0 < cur_col)
381 c->c_column = cur_col;
382 else
383 c->c_column = 0;
66ee8158 384 c->c_width = wcwidth(ch);
6dbe3af9
KZ
385 /*
386 * If things are put in out of order, they will need sorting
387 * when it is flushed.
388 */
389 if (cur_col < l->l_max_col)
390 l->l_needs_sort = 1;
391 else
392 l->l_max_col = cur_col;
66ee8158
KZ
393 if (c->c_width > 0)
394 cur_col += c->c_width;
6dbe3af9
KZ
395 }
396 /* goto the last line that had a character on it */
397 for (; l->l_next; l = l->l_next)
398 this_line++;
b6b5272b
SK
399 if (max_line == 0)
400 return EXIT_SUCCESS; /* no lines, so just exit */
6dbe3af9
KZ
401 flush_lines(this_line - nflushd_lines + extra_lines + 1);
402
403 /* make sure we leave things in a sane state */
404 if (last_set != CS_NORMAL)
405 PUTC('\017');
406
407 /* flush out the last few blank lines */
408 nblank_lines = max_line - this_line;
409 if (max_line & 1)
410 nblank_lines++;
411 else if (!nblank_lines)
412 /* missing a \n on the last line? */
413 nblank_lines = 2;
414 flush_blanks();
e9ce5ccc 415 return ret;
6dbe3af9
KZ
416}
417
fd6b7a7f 418void flush_lines(int nflush)
6dbe3af9
KZ
419{
420 LINE *l;
421
422 while (--nflush >= 0) {
423 l = lines;
424 lines = l->l_next;
425 if (l->l_line) {
426 flush_blanks();
427 flush_line(l);
428 }
429 nblank_lines++;
9e01635e 430 free((void *)l->l_line);
6dbe3af9
KZ
431 free_line(l);
432 }
433 if (lines)
434 lines->l_prev = NULL;
435}
436
437/*
438 * Print a number of newline/half newlines. If fine flag is set, nblank_lines
439 * is the number of half line feeds, otherwise it is the number of whole line
440 * feeds.
441 */
3acc206d 442void flush_blanks(void)
6dbe3af9
KZ
443{
444 int half, i, nb;
445
446 half = 0;
447 nb = nblank_lines;
448 if (nb & 1) {
449 if (fine)
450 half = 1;
451 else
452 nb++;
453 }
454 nb /= 2;
455 for (i = nb; --i >= 0;)
456 PUTC('\n');
457 if (half) {
458 PUTC('\033');
459 PUTC('9');
460 if (!nb)
461 PUTC('\r');
462 }
463 nblank_lines = 0;
464}
465
466/*
467 * Write a line to stdout taking care of space to tab conversion (-h flag)
468 * and character set shifts.
469 */
fd6b7a7f 470void flush_line(LINE *l)
6dbe3af9
KZ
471{
472 CHAR *c, *endc;
473 int nchars, last_col, this_col;
474
475 last_col = 0;
476 nchars = l->l_line_len;
477
478 if (l->l_needs_sort) {
004356f0
KZ
479 static CHAR *sorted = NULL;
480 static int count_size = 0, *count = NULL, sorted_size = 0;
481 int i, tot;
6dbe3af9
KZ
482
483 /*
484 * Do an O(n) sort on l->l_line by column being careful to
485 * preserve the order of characters in the same column.
486 */
487 if (l->l_lsize > sorted_size) {
488 sorted_size = l->l_lsize;
fea1cbf7 489 sorted = xrealloc((void *)sorted,
04faca4a 490 (unsigned)sizeof(CHAR) * sorted_size);
6dbe3af9
KZ
491 }
492 if (l->l_max_col >= count_size) {
493 count_size = l->l_max_col + 1;
04faca4a 494 count = (int *)xrealloc((void *)count,
6dbe3af9
KZ
495 (unsigned)sizeof(int) * count_size);
496 }
22853e4a 497 memset(count, 0, sizeof(int) * l->l_max_col + 1);
004356f0 498 for (i = nchars, c = l->l_line; c && --i >= 0; c++)
6dbe3af9
KZ
499 count[c->c_column]++;
500
501 /*
502 * calculate running total (shifted down by 1) to use as
503 * indices into new line.
504 */
505 for (tot = 0, i = 0; i <= l->l_max_col; i++) {
004356f0 506 int save = count[i];
6dbe3af9
KZ
507 count[i] = tot;
508 tot += save;
509 }
510
511 for (i = nchars, c = l->l_line; --i >= 0; c++)
512 sorted[count[c->c_column]++] = *c;
513 c = sorted;
514 } else
515 c = l->l_line;
516 while (nchars > 0) {
517 this_col = c->c_column;
518 endc = c;
519 do {
520 ++endc;
521 } while (--nchars > 0 && this_col == endc->c_column);
522
523 /* if -b only print last character */
66ee8158 524 if (no_backspaces) {
6dbe3af9 525 c = endc - 1;
66ee8158
KZ
526 if (nchars > 0 &&
527 this_col + c->c_width > endc->c_column)
528 continue;
529 }
6dbe3af9
KZ
530
531 if (this_col > last_col) {
532 int nspace = this_col - last_col;
533
534 if (compress_spaces && nspace > 1) {
535 int ntabs;
536
537 ntabs = this_col / 8 - last_col / 8;
8e0ec3b4
SK
538 if (ntabs > 0) {
539 nspace = this_col & 7;
540 while (--ntabs >= 0)
541 PUTC('\t');
542 }
6dbe3af9
KZ
543 }
544 while (--nspace >= 0)
545 PUTC(' ');
546 last_col = this_col;
547 }
6dbe3af9
KZ
548
549 for (;;) {
550 if (c->c_set != last_set) {
551 switch (c->c_set) {
552 case CS_NORMAL:
553 PUTC('\017');
554 break;
555 case CS_ALTERNATE:
556 PUTC('\016');
557 }
558 last_set = c->c_set;
559 }
560 PUTC(c->c_char);
8e0ec3b4 561 if ((c + 1) < endc) {
66ee8158
KZ
562 int i;
563 for (i=0; i < c->c_width; i++)
564 PUTC('\b');
565 }
6dbe3af9
KZ
566 if (++c >= endc)
567 break;
6dbe3af9 568 }
8e0ec3b4 569 last_col += (c - 1)->c_width;
6dbe3af9
KZ
570 }
571}
572
573#define NALLOC 64
574
575static LINE *line_freelist;
576
577LINE *
3acc206d 578alloc_line(void)
6dbe3af9
KZ
579{
580 LINE *l;
581 int i;
582
583 if (!line_freelist) {
04faca4a 584 l = xmalloc(sizeof(LINE) * NALLOC);
6dbe3af9
KZ
585 line_freelist = l;
586 for (i = 1; i < NALLOC; i++, l++)
587 l->l_next = l + 1;
588 l->l_next = NULL;
589 }
590 l = line_freelist;
591 line_freelist = l->l_next;
592
22853e4a 593 memset(l, 0, sizeof(LINE));
8e0ec3b4 594 return l;
6dbe3af9
KZ
595}
596
fd6b7a7f 597void free_line(LINE *l)
6dbe3af9
KZ
598{
599 l->l_next = line_freelist;
600 line_freelist = l;
601}