]> git.ipfire.org Git - thirdparty/util-linux.git/blob - text-utils/ul.c
misc: add static keyword to where needed [smatch scan]
[thirdparty/util-linux.git] / text-utils / ul.c
1 /*
2 * Copyright (c) 1980, 1993
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 * modified by Kars de Jong <jongk@cs.utwente.nl>
36 * to use terminfo instead of termcap.
37 * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
38 * added Native Language Support
39 * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
40 * modified to work correctly in multi-byte locales
41 */
42
43 #include <stdio.h>
44 #include <unistd.h> /* for getopt(), isatty() */
45 #include <string.h> /* for memset(), strcpy() */
46 #include <term.h> /* for setupterm() */
47 #include <stdlib.h> /* for getenv() */
48 #include <limits.h> /* for INT_MAX */
49 #include <signal.h> /* for signal() */
50 #include <errno.h>
51 #include <getopt.h>
52
53 #include "nls.h"
54 #include "xalloc.h"
55 #include "widechar.h"
56 #include "c.h"
57 #include "closestream.h"
58
59 #ifdef HAVE_WIDECHAR
60 /* Output an ASCII character as a wide character */
61 static int put1wc(int c)
62 {
63 if (putwchar(c) == WEOF)
64 return EOF;
65 else
66 return c;
67 }
68 #define putwp(s) tputs(s, STDOUT_FILENO, put1wc)
69 #else
70 #define putwp(s) putp(s)
71 #endif
72
73 static int handle_escape(FILE * f);
74 static void filter(FILE *f);
75 static void flushln(void);
76 static void overstrike(void);
77 static void iattr(void);
78 static void initbuf(void);
79 static void fwd(void);
80 static void reverse(void);
81 static void initinfo(void);
82 static void outc(wint_t c, int width);
83 static void xsetmode(int newmode);
84 static void setcol(int newcol);
85 static void needcol(int col);
86 static void sig_handler(int signo);
87 static void print_out(char *line);
88
89 #define IESC '\033'
90 #define SO '\016'
91 #define SI '\017'
92 #define HFWD '9'
93 #define HREV '8'
94 #define FREV '7'
95
96 #define NORMAL 000
97 #define ALTSET 001 /* Reverse */
98 #define SUPERSC 002 /* Dim */
99 #define SUBSC 004 /* Dim | Ul */
100 #define UNDERL 010 /* Ul */
101 #define BOLD 020 /* Bold */
102
103 static int must_use_uc, must_overstrike;
104 static char *CURS_UP,
105 *CURS_RIGHT,
106 *CURS_LEFT,
107 *ENTER_STANDOUT,
108 *EXIT_STANDOUT,
109 *ENTER_UNDERLINE,
110 *EXIT_UNDERLINE,
111 *ENTER_DIM,
112 *ENTER_BOLD,
113 *ENTER_REVERSE,
114 *UNDER_CHAR,
115 *EXIT_ATTRIBUTES;
116
117 struct CHAR {
118 char c_mode;
119 wchar_t c_char;
120 int c_width;
121 };
122
123 static struct CHAR *obuf;
124 static int obuflen;
125 static int col, maxcol;
126 static int mode;
127 static int halfpos;
128 static int upln;
129 static int iflag;
130
131 static void __attribute__((__noreturn__))
132 usage(FILE *out)
133 {
134 fputs(USAGE_HEADER, out);
135 fprintf(out, _(" %s [options] [<file> ...]\n"), program_invocation_short_name);
136
137 fputs(USAGE_SEPARATOR, out);
138 fputs(_("Do underlining.\n"), out);
139
140 fputs(USAGE_OPTIONS, out);
141 fputs(_(" -t, -T, --terminal TERMINAL override the TERM environment variable\n"), out);
142 fputs(_(" -i, --indicated underlining is indicated via a separate line\n"), out);
143 fputs(USAGE_SEPARATOR, out);
144 fputs(USAGE_HELP, out);
145 fputs(USAGE_VERSION, out);
146
147 fprintf(out, USAGE_MAN_TAIL("ul(1)"));
148
149 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
150 }
151
152 int main(int argc, char **argv)
153 {
154 int c, ret, tflag = 0;
155 char *termtype;
156 FILE *f;
157
158 static const struct option longopts[] = {
159 { "terminal", required_argument, NULL, 't' },
160 { "indicated", no_argument, NULL, 'i' },
161 { "version", no_argument, NULL, 'V' },
162 { "help", no_argument, NULL, 'h' },
163 { NULL, 0, NULL, 0 }
164 };
165
166 setlocale(LC_ALL, "");
167 bindtextdomain(PACKAGE, LOCALEDIR);
168 textdomain(PACKAGE);
169 atexit(close_stdout);
170
171 signal(SIGINT, sig_handler);
172 signal(SIGTERM, sig_handler);
173
174 termtype = getenv("TERM");
175
176 while ((c = getopt_long(argc, argv, "it:T:Vh", longopts, NULL)) != -1)
177 switch (c) {
178
179 case 't':
180 case 'T':
181 /* for nroff compatibility */
182 termtype = optarg;
183 tflag = 1;
184 break;
185 case 'i':
186 iflag = 1;
187 break;
188 case 'V':
189 printf(UTIL_LINUX_VERSION);
190 return EXIT_SUCCESS;
191 case 'h':
192 usage(stdout);
193 default:
194 errtryhelp(EXIT_FAILURE);
195 }
196 setupterm(termtype, STDOUT_FILENO, &ret);
197 switch (ret) {
198
199 case 1:
200 break;
201
202 default:
203 warnx(_("trouble reading terminfo"));
204 /* fall through to ... */
205
206 case 0:
207 if (tflag)
208 warnx(_("terminal `%s' is not known, defaulting to `dumb'"),
209 termtype);
210 setupterm("dumb", STDOUT_FILENO, (int *)0);
211 break;
212 }
213 initinfo();
214 if ((tigetflag("os") && ENTER_BOLD==NULL ) ||
215 (tigetflag("ul") && ENTER_UNDERLINE==NULL && UNDER_CHAR==NULL))
216 must_overstrike = 1;
217 initbuf();
218 if (optind == argc)
219 filter(stdin);
220 else
221 for (; optind < argc; optind++) {
222 f = fopen(argv[optind],"r");
223 if (!f)
224 err(EXIT_FAILURE, _("cannot open %s"),
225 argv[optind]);
226 filter(f);
227 fclose(f);
228 }
229 free(obuf);
230 return EXIT_SUCCESS;
231 }
232
233 static int handle_escape(FILE * f)
234 {
235 wint_t c;
236
237 switch (c = getwc(f)) {
238 case HREV:
239 if (halfpos == 0) {
240 mode |= SUPERSC;
241 halfpos--;
242 } else if (halfpos > 0) {
243 mode &= ~SUBSC;
244 halfpos--;
245 } else {
246 halfpos = 0;
247 reverse();
248 }
249 return 0;
250 case HFWD:
251 if (halfpos == 0) {
252 mode |= SUBSC;
253 halfpos++;
254 } else if (halfpos < 0) {
255 mode &= ~SUPERSC;
256 halfpos++;
257 } else {
258 halfpos = 0;
259 fwd();
260 }
261 return 0;
262 case FREV:
263 reverse();
264 return 0;
265 default:
266 /* unknown escape */
267 ungetwc(c, f);
268 return 1;
269 }
270 }
271
272 static void filter(FILE *f)
273 {
274 wint_t c;
275 int i, w;
276
277 while ((c = getwc(f)) != WEOF) {
278 switch (c) {
279 case '\b':
280 setcol(col - 1);
281 continue;
282 case '\t':
283 setcol((col + 8) & ~07);
284 continue;
285 case '\r':
286 setcol(0);
287 continue;
288 case SO:
289 mode |= ALTSET;
290 continue;
291 case SI:
292 mode &= ~ALTSET;
293 continue;
294 case IESC:
295 if (handle_escape(f)) {
296 c = getwc(f);
297 errx(EXIT_FAILURE,
298 _("unknown escape sequence in input: %o, %o"), IESC, c);
299 }
300 continue;
301 case '_':
302 if (obuf[col].c_char || obuf[col].c_width < 0) {
303 while (col > 0 && obuf[col].c_width < 0)
304 col--;
305 w = obuf[col].c_width;
306 for (i = 0; i < w; i++)
307 obuf[col++].c_mode |= UNDERL | mode;
308 setcol(col);
309 continue;
310 }
311 obuf[col].c_char = '_';
312 obuf[col].c_width = 1;
313 /* fall through */
314 case ' ':
315 setcol(col + 1);
316 continue;
317 case '\n':
318 flushln();
319 continue;
320 case '\f':
321 flushln();
322 putwchar('\f');
323 continue;
324 default:
325 if (!iswprint(c))
326 /* non printable */
327 continue;
328 w = wcwidth(c);
329 needcol(col + w);
330 if (obuf[col].c_char == '\0') {
331 obuf[col].c_char = c;
332 for (i = 0; i < w; i++)
333 obuf[col + i].c_mode = mode;
334 obuf[col].c_width = w;
335 for (i = 1; i < w; i++)
336 obuf[col + i].c_width = -1;
337 } else if (obuf[col].c_char == '_') {
338 obuf[col].c_char = c;
339 for (i = 0; i < w; i++)
340 obuf[col + i].c_mode |= UNDERL | mode;
341 obuf[col].c_width = w;
342 for (i = 1; i < w; i++)
343 obuf[col + i].c_width = -1;
344 } else if ((wint_t) obuf[col].c_char == c) {
345 for (i = 0; i < w; i++)
346 obuf[col + i].c_mode |= BOLD | mode;
347 } else {
348 w = obuf[col].c_width;
349 for (i = 0; i < w; i++)
350 obuf[col + i].c_mode = mode;
351 }
352 setcol(col + w);
353 continue;
354 }
355 }
356 if (maxcol)
357 flushln();
358 }
359
360 static void flushln(void)
361 {
362 int lastmode;
363 int i;
364 int hadmodes = 0;
365
366 lastmode = NORMAL;
367 for (i = 0; i < maxcol; i++) {
368 if (obuf[i].c_mode != lastmode) {
369 hadmodes++;
370 xsetmode(obuf[i].c_mode);
371 lastmode = obuf[i].c_mode;
372 }
373 if (obuf[i].c_char == '\0') {
374 if (upln) {
375 print_out(CURS_RIGHT);
376 } else
377 outc(' ', 1);
378 } else
379 outc(obuf[i].c_char, obuf[i].c_width);
380 if (obuf[i].c_width > 1)
381 i += obuf[i].c_width - 1;
382 }
383 if (lastmode != NORMAL) {
384 xsetmode(0);
385 }
386 if (must_overstrike && hadmodes)
387 overstrike();
388 putwchar('\n');
389 if (iflag && hadmodes)
390 iattr();
391 fflush(stdout);
392 if (upln)
393 upln--;
394 initbuf();
395 }
396
397 /*
398 * For terminals that can overstrike, overstrike underlines and bolds.
399 * We don't do anything with halfline ups and downs, or Greek.
400 */
401 static void overstrike(void)
402 {
403 register int i;
404 register wchar_t *lbuf = xmalloc((maxcol + 1) * sizeof(wchar_t));
405 register wchar_t *cp = lbuf;
406 int hadbold=0;
407
408 /* Set up overstrike buffer */
409 for (i = 0; i < maxcol; i++)
410 switch (obuf[i].c_mode) {
411 case NORMAL:
412 default:
413 *cp++ = ' ';
414 break;
415 case UNDERL:
416 *cp++ = '_';
417 break;
418 case BOLD:
419 *cp++ = obuf[i].c_char;
420 if (obuf[i].c_width > 1)
421 i += obuf[i].c_width - 1;
422 hadbold=1;
423 break;
424 }
425 putwchar('\r');
426 for (*cp = ' '; *cp == ' '; cp--)
427 *cp = 0;
428 fputws(lbuf, stdout);
429 if (hadbold) {
430 putwchar('\r');
431 for (cp = lbuf; *cp; cp++)
432 putwchar(*cp == '_' ? ' ' : *cp);
433 putwchar('\r');
434 for (cp = lbuf; *cp; cp++)
435 putwchar(*cp == '_' ? ' ' : *cp);
436 }
437 free(lbuf);
438 }
439
440 static void iattr(void)
441 {
442 register int i;
443 register wchar_t *lbuf = xmalloc((maxcol + 1) * sizeof(wchar_t));
444 register wchar_t *cp = lbuf;
445
446 for (i = 0; i < maxcol; i++)
447 switch (obuf[i].c_mode) {
448 case NORMAL: *cp++ = ' '; break;
449 case ALTSET: *cp++ = 'g'; break;
450 case SUPERSC: *cp++ = '^'; break;
451 case SUBSC: *cp++ = 'v'; break;
452 case UNDERL: *cp++ = '_'; break;
453 case BOLD: *cp++ = '!'; break;
454 default: *cp++ = 'X'; break;
455 }
456 for (*cp = ' '; *cp == ' '; cp--)
457 *cp = 0;
458 fputws(lbuf, stdout);
459 putwchar('\n');
460 free(lbuf);
461 }
462
463 static void initbuf(void)
464 {
465 if (obuf == NULL) {
466 /* First time. */
467 obuflen = BUFSIZ;
468 obuf = xcalloc(obuflen, sizeof(struct CHAR));
469 } else
470 /* assumes NORMAL == 0 */
471 memset(obuf, 0, sizeof(struct CHAR) * maxcol);
472
473 setcol(0);
474 maxcol = 0;
475 mode &= ALTSET;
476 }
477
478 static void fwd(void)
479 {
480 int oldcol, oldmax;
481
482 oldcol = col;
483 oldmax = maxcol;
484 flushln();
485 setcol(oldcol);
486 maxcol = oldmax;
487 }
488
489 static void reverse(void)
490 {
491 upln++;
492 fwd();
493 print_out(CURS_UP);
494 print_out(CURS_UP);
495 upln++;
496 }
497
498 static void initinfo(void)
499 {
500 CURS_UP = tigetstr("cuu1");
501 CURS_RIGHT = tigetstr("cuf1");
502 CURS_LEFT = tigetstr("cub1");
503 if (CURS_LEFT == NULL)
504 CURS_LEFT = "\b";
505
506 ENTER_STANDOUT = tigetstr("smso");
507 EXIT_STANDOUT = tigetstr("rmso");
508 ENTER_UNDERLINE = tigetstr("smul");
509 EXIT_UNDERLINE = tigetstr("rmul");
510 ENTER_DIM = tigetstr("dim");
511 ENTER_BOLD = tigetstr("bold");
512 ENTER_REVERSE = tigetstr("rev");
513 EXIT_ATTRIBUTES = tigetstr("sgr0");
514
515 if (!ENTER_BOLD && ENTER_REVERSE)
516 ENTER_BOLD = ENTER_REVERSE;
517 if (!ENTER_BOLD && ENTER_STANDOUT)
518 ENTER_BOLD = ENTER_STANDOUT;
519 if (!ENTER_UNDERLINE && ENTER_STANDOUT) {
520 ENTER_UNDERLINE = ENTER_STANDOUT;
521 EXIT_UNDERLINE = EXIT_STANDOUT;
522 }
523 if (!ENTER_DIM && ENTER_STANDOUT)
524 ENTER_DIM = ENTER_STANDOUT;
525 if (!ENTER_REVERSE && ENTER_STANDOUT)
526 ENTER_REVERSE = ENTER_STANDOUT;
527 if (!EXIT_ATTRIBUTES && EXIT_STANDOUT)
528 EXIT_ATTRIBUTES = EXIT_STANDOUT;
529
530 /*
531 * Note that we use REVERSE for the alternate character set,
532 * not the as/ae capabilities. This is because we are modeling
533 * the model 37 teletype (since that's what nroff outputs) and
534 * the typical as/ae is more of a graphics set, not the greek
535 * letters the 37 has.
536 */
537
538 UNDER_CHAR = tigetstr("uc");
539 must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE);
540 }
541
542 static int curmode = 0;
543
544 static void outc(wint_t c, int width) {
545 int i;
546
547 putwchar(c);
548 if (must_use_uc && (curmode&UNDERL)) {
549 for (i = 0; i < width; i++)
550 print_out(CURS_LEFT);
551 for (i = 0; i < width; i++)
552 print_out(UNDER_CHAR);
553 }
554 }
555
556 static void xsetmode(int newmode)
557 {
558 if (!iflag) {
559 if (curmode != NORMAL && newmode != NORMAL)
560 xsetmode(NORMAL);
561 switch (newmode) {
562 case NORMAL:
563 switch (curmode) {
564 case NORMAL:
565 break;
566 case UNDERL:
567 print_out(EXIT_UNDERLINE);
568 break;
569 default:
570 /* This includes standout */
571 print_out(EXIT_ATTRIBUTES);
572 break;
573 }
574 break;
575 case ALTSET:
576 print_out(ENTER_REVERSE);
577 break;
578 case SUPERSC:
579 /*
580 * This only works on a few terminals.
581 * It should be fixed.
582 */
583 print_out(ENTER_UNDERLINE);
584 print_out(ENTER_DIM);
585 break;
586 case SUBSC:
587 print_out(ENTER_DIM);
588 break;
589 case UNDERL:
590 print_out(ENTER_UNDERLINE);
591 break;
592 case BOLD:
593 print_out(ENTER_BOLD);
594 break;
595 default:
596 /*
597 * We should have some provision here for multiple modes
598 * on at once. This will have to come later.
599 */
600 print_out(ENTER_STANDOUT);
601 break;
602 }
603 }
604 curmode = newmode;
605 }
606
607 static void setcol(int newcol) {
608 col = newcol;
609
610 if (col < 0)
611 col = 0;
612 else if (col > maxcol)
613 needcol(col);
614 }
615
616 static void needcol(int acol) {
617 maxcol = acol;
618
619 /* If col >= obuflen, expand obuf until obuflen > col. */
620 while (acol >= obuflen) {
621 /* Paranoid check for obuflen == INT_MAX. */
622 if (obuflen == INT_MAX)
623 errx(EXIT_FAILURE, _("Input line too long."));
624
625 /* Similar paranoia: double only up to INT_MAX. */
626 if (obuflen < (INT_MAX / 2))
627 obuflen *= 2;
628 else
629 obuflen = INT_MAX;
630
631 /* Now we can try to expand obuf. */
632 obuf = xrealloc(obuf, sizeof(struct CHAR) * obuflen);
633 }
634 }
635
636 static void sig_handler(int signo __attribute__ ((__unused__)))
637 {
638 _exit(EXIT_SUCCESS);
639 }
640
641 static void print_out(char *line)
642 {
643 if (line == NULL)
644 return;
645
646 putwp(line);
647 }