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