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