(root)/
util-linux-2.39/
text-utils/
col.c
       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)
      40   * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
      41   * 	added Native Language Support
      42   * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
      43   * 	modified to work correctly in multi-byte locales
      44   *
      45   */
      46  
      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  
      56  #include <ctype.h>
      57  #include <errno.h>
      58  #include <getopt.h>
      59  #include <stdio.h>
      60  #include <stdlib.h>
      61  #include <string.h>
      62  #include <unistd.h>
      63  
      64  #include "c.h"
      65  #include "closestream.h"
      66  #include "nls.h"
      67  #include "optutils.h"
      68  #include "strutils.h"
      69  #include "widechar.h"
      70  #include "xalloc.h"
      71  
      72  #define	SPACE	' '		/* space */
      73  #define	BS	'\b'		/* backspace */
      74  #define	NL	'\n'		/* newline */
      75  #define	CR	'\r'		/* carriage return */
      76  #define	TAB	'\t'		/* tab */
      77  #define	VT	'\v'		/* vertical tab (aka reverse line feed) */
      78  
      79  #define	ESC	'\033'		/* escape */
      80  #define	RLF	'\a'		/* ESC-007 reverse line feed */
      81  #define	RHLF	BS		/* ESC-010 reverse half-line feed */
      82  #define	FHLF	TAB		/* ESC-011 forward half-line feed */
      83  
      84  #define	SO	'\016'		/* activate the G1 character set */
      85  #define	SI	'\017'		/* activate the G0 character set */
      86  
      87  /* build up at least this many lines before flushing them out */
      88  #define	BUFFER_MARGIN		32
      89  
      90  /* number of lines to allocate */
      91  #define	NALLOC			64
      92  
      93  #if HAS_FEATURE_ADDRESS_SANITIZER || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
      94  # define COL_DEALLOCATE_ON_EXIT
      95  #endif
      96  
      97  /* SI & SO charset mode */
      98  enum {
      99  	CS_NORMAL,
     100  	CS_ALTERNATE
     101  };
     102  
     103  struct col_char {
     104  	size_t		c_column;	/* column character is in */
     105  	wchar_t		c_char;		/* character in question */
     106  	int		c_width;	/* character width */
     107  
     108  	uint8_t		c_set:1;	/* character set (currently only 2) */
     109  };
     110  
     111  struct col_line {
     112  	struct col_char	*l_line;	/* characters on the line */
     113  	struct col_line	*l_prev;	/* previous line */
     114  	struct col_line	*l_next;	/* next line */
     115  	size_t		l_lsize;	/* allocated sizeof l_line */
     116  	size_t		l_line_len;	/* strlen(l_line) */
     117  	size_t		l_max_col;	/* max column in the line */
     118  
     119  	uint8_t		l_needs_sort:1;	/* set if chars went in out of order */
     120  };
     121  
     122  #ifdef COL_DEALLOCATE_ON_EXIT
     123  /*
     124   * Free memory before exit when compiling LeakSanitizer.
     125   */
     126  struct col_alloc {
     127  	struct col_line	 *l;
     128  	struct col_alloc *next;
     129  };
     130  #endif
     131  
     132  struct col_ctl {
     133  	struct col_line *lines;
     134  	struct col_line *l;		/* current line */
     135  	size_t max_bufd_lines;		/* max # lines to keep in memory */
     136  	struct col_line *line_freelist;
     137  	size_t nblank_lines;		/* # blanks after last flushed line */
     138  #ifdef COL_DEALLOCATE_ON_EXIT
     139  	struct col_alloc *alloc_root;	/* first of line allocations */
     140  	struct col_alloc *alloc_head;	/* latest line allocation */
     141  #endif
     142  	unsigned int
     143  		last_set:1,		/* char_set of last char printed */
     144  		compress_spaces:1,	/* if doing space -> tab conversion */
     145  		fine:1,			/* if `fine' resolution (half lines) */
     146  		no_backspaces:1,	/* if not to output any backspaces */
     147  		pass_unknown_seqs:1;	/* whether to pass unknown control sequences */
     148  };
     149  
     150  struct col_lines {
     151  	struct col_char *c;
     152  	wint_t ch;
     153  	size_t adjust;
     154  	size_t cur_col;
     155  	ssize_t cur_line;
     156  	size_t extra_lines;
     157  	size_t max_line;
     158  	size_t nflushd_lines;
     159  	size_t this_line;
     160  
     161  	unsigned int
     162  		cur_set:1,
     163  		warned:1;
     164  };
     165  
     166  static void __attribute__((__noreturn__)) usage(void)
     167  {
     168  	FILE *out = stdout;
     169  	fprintf(out, _(
     170  		"\nUsage:\n"
     171  		" %s [options]\n"), program_invocation_short_name);
     172  
     173  	fputs(USAGE_SEPARATOR, out);
     174  	fputs(_("Filter out reverse line feeds from standard input.\n"), out);
     175  
     176  	fprintf(out, _(
     177  		"\nOptions:\n"
     178  		" -b, --no-backspaces    do not output backspaces\n"
     179  		" -f, --fine             permit forward half line feeds\n"
     180  		" -p, --pass             pass unknown control sequences\n"
     181  		" -h, --tabs             convert spaces to tabs\n"
     182  		" -x, --spaces           convert tabs to spaces\n"
     183  		" -l, --lines NUM        buffer at least NUM lines\n"
     184  		));
     185  	printf( " -H, --help             %s\n", USAGE_OPTSTR_HELP);
     186  	printf( " -v, --version          %s\n", USAGE_OPTSTR_VERSION);
     187  
     188  	printf(USAGE_MAN_TAIL("col(1)"));
     189  	exit(EXIT_SUCCESS);
     190  }
     191  
     192  static inline void col_putchar(wchar_t ch)
     193  {
     194  	if (putwchar(ch) == WEOF)
     195  		err(EXIT_FAILURE, _("write failed"));
     196  }
     197  
     198  /*
     199   * Print a number of newline/half newlines.  If fine flag is set, nblank_lines
     200   * is the number of half line feeds, otherwise it is the number of whole line
     201   * feeds.
     202   */
     203  static void flush_blanks(struct col_ctl *ctl)
     204  {
     205  	int half = 0;
     206  	ssize_t i, nb = ctl->nblank_lines;
     207  
     208  	if (nb & 1) {
     209  		if (ctl->fine)
     210  			half = 1;
     211  		else
     212  			nb++;
     213  	}
     214  	nb /= 2;
     215  	for (i = nb; --i >= 0;)
     216  		col_putchar(NL);
     217  
     218  	if (half) {
     219  		col_putchar(ESC);
     220  		col_putchar('9');
     221  		if (!nb)
     222  			col_putchar(CR);
     223  	}
     224  	ctl->nblank_lines = 0;
     225  }
     226  
     227  /*
     228   * Write a line to stdout taking care of space to tab conversion (-h flag)
     229   * and character set shifts.
     230   */
     231  static void flush_line(struct col_ctl *ctl, struct col_line *l)
     232  {
     233  	struct col_char *c, *endc;
     234  	size_t nchars = l->l_line_len, last_col = 0, this_col;
     235  
     236  	if (l->l_needs_sort) {
     237  		static struct col_char *sorted = NULL;
     238  		static size_t count_size = 0, *count = NULL, sorted_size = 0;
     239  		size_t i, tot;
     240  
     241  		/*
     242  		 * Do an O(n) sort on l->l_line by column being careful to
     243  		 * preserve the order of characters in the same column.
     244  		 */
     245  		if (sorted_size < l->l_lsize) {
     246  			sorted_size = l->l_lsize;
     247  			sorted = xrealloc(sorted, sizeof(struct col_char) * sorted_size);
     248  		}
     249  		if (count_size <= l->l_max_col) {
     250  			count_size = l->l_max_col + 1;
     251  			count = xrealloc(count, sizeof(size_t) * count_size);
     252  		}
     253  		memset(count, 0, sizeof(size_t) * l->l_max_col + 1);
     254  		for (i = nchars, c = l->l_line; c && 0 < i; i--, c++)
     255  			count[c->c_column]++;
     256  
     257  		/*
     258  		 * calculate running total (shifted down by 1) to use as
     259  		 * indices into new line.
     260  		 */
     261  		for (tot = 0, i = 0; i <= l->l_max_col; i++) {
     262  			size_t save = count[i];
     263  			count[i] = tot;
     264  			tot += save;
     265  		}
     266  
     267  		for (i = nchars, c = l->l_line; 0 < i; i--, c++)
     268  			sorted[count[c->c_column]++] = *c;
     269  		c = sorted;
     270  	} else
     271  		c = l->l_line;
     272  
     273  	while (0 < nchars) {
     274  		this_col = c->c_column;
     275  		endc = c;
     276  
     277  		/* find last character */
     278  		do {
     279  			++endc;
     280  		} while (0 < --nchars && this_col == endc->c_column);
     281  
     282  		if (ctl->no_backspaces) {
     283  			/* print only the last character */
     284  			c = endc - 1;
     285  			if (0 < nchars && endc->c_column < this_col + c->c_width)
     286  				continue;
     287  		}
     288  
     289  		if (last_col < this_col) {
     290  			/* tabs and spaces handling */
     291  			ssize_t nspace = this_col - last_col;
     292  
     293  			if (ctl->compress_spaces && 1 < nspace) {
     294  				ssize_t ntabs;
     295  
     296  				ntabs = this_col / 8 - last_col / 8;
     297  				if (0 < ntabs) {
     298  					nspace = this_col & 7;
     299  					while (0 <= --ntabs)
     300  						col_putchar(TAB);
     301  				}
     302  			}
     303  			while (0 <= --nspace)
     304  				col_putchar(SPACE);
     305  			last_col = this_col;
     306  		}
     307  
     308  		for (;;) {
     309  			/* SO / SI character set changing */
     310  			if (c->c_set != ctl->last_set) {
     311  				switch (c->c_set) {
     312  				case CS_NORMAL:
     313  					col_putchar(SI);
     314  					break;
     315  				case CS_ALTERNATE:
     316  					col_putchar(SO);
     317  					break;
     318  				default:
     319  					abort();
     320  				}
     321  				ctl->last_set = c->c_set;
     322  			}
     323  
     324  			/* output a character */
     325  			col_putchar(c->c_char);
     326  
     327  			/* rubout control chars from output */
     328  			if (c + 1 < endc) {
     329  				int i;
     330  
     331  				for (i = 0; i < c->c_width; i++)
     332  					col_putchar(BS);
     333  			}
     334  
     335  			if (endc <= ++c)
     336  				break;
     337  		}
     338  		last_col += (c - 1)->c_width;
     339  	}
     340  }
     341  
     342  static struct col_line *alloc_line(struct col_ctl *ctl)
     343  {
     344  	struct col_line *l;
     345  	size_t i;
     346  
     347  	if (!ctl->line_freelist) {
     348  		l = xmalloc(sizeof(struct col_line) * NALLOC);
     349  #ifdef COL_DEALLOCATE_ON_EXIT
     350  		if (ctl->alloc_root == NULL) {
     351  			ctl->alloc_root = xcalloc(1, sizeof(struct col_alloc));
     352  			ctl->alloc_root->l = l;
     353  			ctl->alloc_head = ctl->alloc_root;
     354  		} else {
     355  			ctl->alloc_head->next = xcalloc(1, sizeof(struct col_alloc));
     356  			ctl->alloc_head = ctl->alloc_head->next;
     357  			ctl->alloc_head->l = l;
     358  		}
     359  #endif
     360  		ctl->line_freelist = l;
     361  		for (i = 1; i < NALLOC; i++, l++)
     362  			l->l_next = l + 1;
     363  		l->l_next = NULL;
     364  	}
     365  	l = ctl->line_freelist;
     366  	ctl->line_freelist = l->l_next;
     367  
     368  	memset(l, 0, sizeof(struct col_line));
     369  	return l;
     370  }
     371  
     372  static void free_line(struct col_ctl *ctl, struct col_line *l)
     373  {
     374  	l->l_next = ctl->line_freelist;
     375  	ctl->line_freelist = l;
     376  }
     377  
     378  static void flush_lines(struct col_ctl *ctl, ssize_t nflush)
     379  {
     380  	struct col_line *l;
     381  
     382  	while (0 <= --nflush) {
     383  		l = ctl->lines;
     384  		ctl->lines = l->l_next;
     385  		if (l->l_line) {
     386  			flush_blanks(ctl);
     387  			flush_line(ctl, l);
     388  		}
     389  		ctl->nblank_lines++;
     390  		free(l->l_line);
     391  		free_line(ctl, l);
     392  	}
     393  	if (ctl->lines)
     394  		ctl->lines->l_prev = NULL;
     395  }
     396  
     397  static int handle_not_graphic(struct col_ctl *ctl, struct col_lines *lns)
     398  {
     399  	switch (lns->ch) {
     400  	case BS:
     401  		if (lns->cur_col == 0)
     402  			return 1;	/* can't go back further */
     403  		if (lns->c)
     404  			lns->cur_col -= lns->c->c_width;
     405  		else
     406  			lns->cur_col -= 1;
     407  		return 1;
     408  	case CR:
     409  		lns->cur_col = 0;
     410  		return 1;
     411  	case ESC:
     412  		switch (getwchar()) {	/* just ignore EOF */
     413  		case RLF:
     414  			lns->cur_line -= 2;
     415  			break;
     416  		case RHLF:
     417  			lns->cur_line -= 1;
     418  			break;
     419  		case FHLF:
     420  			lns->cur_line += 1;
     421  			if (0 < lns->cur_line && lns->max_line < (size_t)lns->cur_line)
     422  				lns->max_line = lns->cur_line;
     423  			break;
     424  		default:
     425  			break;
     426  		}
     427  		return 1;
     428  	case NL:
     429  		lns->cur_line += 2;
     430  		if (0 < lns->cur_line && lns->max_line < (size_t)lns->cur_line)
     431  			lns->max_line = lns->cur_line;
     432  		lns->cur_col = 0;
     433  		return 1;
     434  	case SPACE:
     435  		lns->cur_col += 1;
     436  		return 1;
     437  	case SI:
     438  		lns->cur_set = CS_NORMAL;
     439  		return 1;
     440  	case SO:
     441  		lns->cur_set = CS_ALTERNATE;
     442  		return 1;
     443  	case TAB:		/* adjust column */
     444  		lns->cur_col |= 7;
     445  		lns->cur_col += 1;
     446  		return 1;
     447  	case VT:
     448  		lns->cur_line -= 2;
     449  		return 1;
     450  	default:
     451  		break;
     452  	}
     453  	if (iswspace(lns->ch)) {
     454  		if (0 < wcwidth(lns->ch))
     455  			lns->cur_col += wcwidth(lns->ch);
     456  		return 1;
     457  	}
     458  
     459  	if (!ctl->pass_unknown_seqs)
     460  		return 1;
     461  	return 0;
     462  }
     463  
     464  static void update_cur_line(struct col_ctl *ctl, struct col_lines *lns)
     465  {
     466  	ssize_t nmove;
     467  
     468  	lns->adjust = 0;
     469  	nmove = lns->cur_line - lns->this_line;
     470  	if (!ctl->fine) {
     471  		/* round up to next line */
     472  		if (lns->cur_line & 1) {
     473  			lns->adjust = 1;
     474  			nmove++;
     475  		}
     476  	}
     477  	if (nmove < 0) {
     478  		for (; nmove < 0 && ctl->l->l_prev; nmove++)
     479  			ctl->l = ctl->l->l_prev;
     480  
     481  		if (nmove) {
     482  			if (lns->nflushd_lines == 0) {
     483  				/*
     484  				 * Allow backup past first line if nothing
     485  				 * has been flushed yet.
     486  				 */
     487  				for (; nmove < 0; nmove++) {
     488  					struct col_line *lnew = alloc_line(ctl);
     489  					ctl->l->l_prev = lnew;
     490  					lnew->l_next = ctl->l;
     491  					ctl->l = ctl->lines = lnew;
     492  					lns->extra_lines += 1;
     493  				}
     494  			} else {
     495  				if (!lns->warned) {
     496  					warnx(_("warning: can't back up %s."),
     497  						  lns->cur_line < 0 ?
     498  						    _("past first line") :
     499  					            _("-- line already flushed"));
     500  					lns->warned = 1;
     501  				}
     502  				lns->cur_line -= nmove;
     503  			}
     504  		}
     505  	} else {
     506  		/* may need to allocate here */
     507  		for (; 0 < nmove && ctl->l->l_next; nmove--)
     508  			ctl->l = ctl->l->l_next;
     509  
     510  		for (; 0 < nmove; nmove--) {
     511  			struct col_line *lnew = alloc_line(ctl);
     512  			lnew->l_prev = ctl->l;
     513  			ctl->l->l_next = lnew;
     514  			ctl->l = lnew;
     515  		}
     516  	}
     517  
     518  	lns->this_line = lns->cur_line + lns->adjust;
     519  	nmove = lns->this_line - lns->nflushd_lines;
     520  
     521  	if (0 < nmove && ctl->max_bufd_lines + BUFFER_MARGIN <= (size_t)nmove) {
     522  		lns->nflushd_lines += nmove - ctl->max_bufd_lines;
     523  		flush_lines(ctl, nmove - ctl->max_bufd_lines);
     524  	}
     525  }
     526  
     527  static void parse_options(struct col_ctl *ctl, int argc, char **argv)
     528  {
     529  	static const struct option longopts[] = {
     530  		{ "no-backspaces", no_argument,		NULL, 'b' },
     531  		{ "fine",	   no_argument,		NULL, 'f' },
     532  		{ "pass",	   no_argument,		NULL, 'p' },
     533  		{ "tabs",	   no_argument,		NULL, 'h' },
     534  		{ "spaces",	   no_argument,		NULL, 'x' },
     535  		{ "lines",	   required_argument,	NULL, 'l' },
     536  		{ "version",	   no_argument,		NULL, 'V' },
     537  		{ "help",	   no_argument,		NULL, 'H' },
     538  		{ NULL, 0, NULL, 0 }
     539  	};
     540  	static const ul_excl_t excl[] = {
     541  		{ 'h', 'x' },
     542  		{ 0 }
     543  	};
     544  	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
     545  	int opt;
     546  
     547  	while ((opt = getopt_long(argc, argv, "bfhl:pxVH", longopts, NULL)) != -1) {
     548  		err_exclusive_options(opt, longopts, excl, excl_st);
     549  
     550  		switch (opt) {
     551  		case 'b':		/* do not output backspaces */
     552  			ctl->no_backspaces = 1;
     553  			break;
     554  		case 'f':		/* allow half forward line feeds */
     555  			ctl->fine = 1;
     556  			break;
     557  		case 'h':		/* compress spaces into tabs */
     558  			ctl->compress_spaces = 1;
     559  			break;
     560  		case 'l':
     561  			/*
     562  			 * Buffered line count, which is a value in half
     563  			 * lines e.g. twice the amount specified.
     564  			 */
     565  			ctl->max_bufd_lines = strtou32_or_err(optarg, _("bad -l argument")) * 2;
     566  			break;
     567  		case 'p':
     568  			ctl->pass_unknown_seqs = 1;
     569  			break;
     570  		case 'x':		/* do not compress spaces into tabs */
     571  			ctl->compress_spaces = 0;
     572  			break;
     573  
     574  		case 'V':
     575  			print_version(EXIT_SUCCESS);
     576  		case 'H':
     577  			usage();
     578  		default:
     579  			errtryhelp(EXIT_FAILURE);
     580  		}
     581  	}
     582  
     583  	if (optind != argc) {
     584  		warnx(_("bad usage"));
     585  		errtryhelp(EXIT_FAILURE);
     586  	}
     587  }
     588  
     589  #ifdef COL_DEALLOCATE_ON_EXIT
     590  static void free_line_allocations(struct col_alloc *root)
     591  {
     592  	struct col_alloc *next;
     593  
     594  	while (root) {
     595  		next = root->next;
     596  		free(root->l);
     597  		free(root);
     598  		root = next;
     599  	}
     600  }
     601  #endif
     602  
     603  static void process_char(struct col_ctl *ctl, struct col_lines *lns)
     604  {
     605                  /* Deal printable characters */
     606                  if (!iswgraph(lns->ch) && handle_not_graphic(ctl, lns))
     607                          return;
     608  
     609                  /* Must stuff ch in a line - are we at the right one? */
     610                  if ((size_t)lns->cur_line != lns->this_line - lns->adjust)
     611                          update_cur_line(ctl, lns);
     612  
     613                  /* Does line buffer need to grow? */
     614                  if (ctl->l->l_lsize <= ctl->l->l_line_len + 1) {
     615                          size_t need;
     616  
     617                          need = ctl->l->l_lsize ? ctl->l->l_lsize * 2 : NALLOC;
     618                          ctl->l->l_line = xrealloc(ctl->l->l_line, need * sizeof(struct col_char));
     619                          ctl->l->l_lsize = need;
     620                  }
     621  
     622                  /* Store character */
     623                  lns->c = &ctl->l->l_line[ctl->l->l_line_len++];
     624                  lns->c->c_char = lns->ch;
     625                  lns->c->c_set = lns->cur_set;
     626  
     627                  if (0 < lns->cur_col)
     628                          lns->c->c_column = lns->cur_col;
     629                  else
     630                          lns->c->c_column = 0;
     631                  lns->c->c_width = wcwidth(lns->ch);
     632  
     633                  /*
     634                   * If things are put in out of order, they will need sorting
     635                   * when it is flushed.
     636                   */
     637                  if (lns->cur_col < ctl->l->l_max_col)
     638                          ctl->l->l_needs_sort = 1;
     639                  else
     640                          ctl->l->l_max_col = lns->cur_col;
     641                  if (0 < lns->c->c_width)
     642                          lns->cur_col += lns->c->c_width;
     643  
     644  }
     645  
     646  int main(int argc, char **argv)
     647  {
     648  	struct col_ctl ctl = {
     649  		.compress_spaces = 1,
     650  		.last_set = CS_NORMAL,
     651  		.max_bufd_lines = BUFFER_MARGIN * 2,
     652  	};
     653  	struct col_lines lns = {
     654  		.cur_set = CS_NORMAL,
     655  	};
     656  	int ret = EXIT_SUCCESS;
     657  
     658  	setlocale(LC_ALL, "");
     659  	bindtextdomain(PACKAGE, LOCALEDIR);
     660  	textdomain(PACKAGE);
     661  	close_stdout_atexit();
     662  
     663  	ctl.lines = ctl.l = alloc_line(&ctl);
     664  
     665  	parse_options(&ctl, argc, argv);
     666  
     667  	while (feof(stdin) == 0) {
     668  		errno = 0;
     669  		/* Get character */
     670  		lns.ch = getwchar();
     671  
     672  		if (lns.ch == WEOF) {
     673  			if (errno == EILSEQ) {
     674  				/* Illegal multibyte sequence */
     675  				int c;
     676  				char buf[5];
     677  				size_t len, i;
     678  
     679  				c = getchar();
     680  				if (c == EOF)
     681  					break;
     682  				sprintf(buf, "\\x%02x", (unsigned char) c);
     683  				len = strlen(buf);
     684  				for (i = 0; i < len; i++) {
     685  					lns.ch = buf[i];
     686  					process_char(&ctl, &lns);
     687  				}
     688  			} else
     689  				/* end of file */
     690  				break;
     691  		} else
     692  			/* the common case */
     693  			process_char(&ctl, &lns);
     694  	}
     695  
     696  	/* goto the last line that had a character on it */
     697  	for (; ctl.l->l_next; ctl.l = ctl.l->l_next)
     698  		lns.this_line++;
     699  	if (lns.max_line == 0 && lns.cur_col == 0) {
     700  #ifdef COL_DEALLOCATE_ON_EXIT
     701  		free_line_allocations(ctl.alloc_root);
     702  #endif
     703  		return EXIT_SUCCESS;	/* no lines, so just exit */
     704  	}
     705  	flush_lines(&ctl, lns.this_line - lns.nflushd_lines + lns.extra_lines + 1);
     706  
     707  	/* make sure we leave things in a sane state */
     708  	if (ctl.last_set != CS_NORMAL)
     709  		col_putchar(SI);
     710  
     711  	/* flush out the last few blank lines */
     712  	ctl.nblank_lines = lns.max_line - lns.this_line;
     713  	if (lns.max_line & 1)
     714  		ctl.nblank_lines++;
     715  	else if (!ctl.nblank_lines)
     716  		/* missing a \n on the last line? */
     717  		ctl.nblank_lines = 2;
     718  	flush_blanks(&ctl);
     719  #ifdef COL_DEALLOCATE_ON_EXIT
     720  	free_line_allocations(ctl.alloc_root);
     721  #endif
     722  	return ret;
     723  }