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