(root)/
glibc-2.38/
argp/
argp-fmtstream.c
       1  /* Word-wrapping and line-truncating streams
       2     Copyright (C) 1997-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4     Written by Miles Bader <miles@gnu.ai.mit.edu>.
       5  
       6     The GNU C Library is free software; you can redistribute it and/or
       7     modify it under the terms of the GNU Lesser General Public
       8     License as published by the Free Software Foundation; either
       9     version 2.1 of the License, or (at your option) any later version.
      10  
      11     The GNU C Library is distributed in the hope that it will be useful,
      12     but WITHOUT ANY WARRANTY; without even the implied warranty of
      13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      14     Lesser General Public License for more details.
      15  
      16     You should have received a copy of the GNU Lesser General Public
      17     License along with the GNU C Library; if not, see
      18     <https://www.gnu.org/licenses/>.  */
      19  
      20  /* This package emulates glibc `line_wrap_stream' semantics for systems that
      21     don't have that.  */
      22  
      23  #ifdef HAVE_CONFIG_H
      24  # include <config.h>
      25  #endif
      26  
      27  #include <stdlib.h>
      28  #include <string.h>
      29  #include <errno.h>
      30  #include <stdarg.h>
      31  #include <ctype.h>
      32  
      33  #include <argp-fmtstream.h>
      34  #include "argp-namefrob.h"
      35  
      36  #ifndef ARGP_FMTSTREAM_USE_LINEWRAP
      37  
      38  #ifndef isblank
      39  #define isblank(ch) ((ch)==' ' || (ch)=='\t')
      40  #endif
      41  
      42  #ifdef _LIBC
      43  # include <wchar.h>
      44  # include <libio/libioP.h>
      45  #endif
      46  
      47  #define INIT_BUF_SIZE 200
      48  #define PRINTF_SIZE_GUESS 150
      49  
      50  /* Return an argp_fmtstream that outputs to STREAM, and which prefixes lines
      51     written on it with LMARGIN spaces and limits them to RMARGIN columns
      52     total.  If WMARGIN >= 0, words that extend past RMARGIN are wrapped by
      53     replacing the whitespace before them with a newline and WMARGIN spaces.
      54     Otherwise, chars beyond RMARGIN are simply dropped until a newline.
      55     Returns NULL if there was an error.  */
      56  argp_fmtstream_t
      57  __argp_make_fmtstream (FILE *stream,
      58  		       size_t lmargin, size_t rmargin, ssize_t wmargin)
      59  {
      60    argp_fmtstream_t fs;
      61  
      62    fs = (struct argp_fmtstream *) malloc (sizeof (struct argp_fmtstream));
      63    if (fs != NULL)
      64      {
      65        fs->stream = stream;
      66  
      67        fs->lmargin = lmargin;
      68        fs->rmargin = rmargin;
      69        fs->wmargin = wmargin;
      70        fs->point_col = 0;
      71        fs->point_offs = 0;
      72  
      73        fs->buf = (char *) malloc (INIT_BUF_SIZE);
      74        if (! fs->buf)
      75  	{
      76  	  free (fs);
      77  	  fs = 0;
      78  	}
      79        else
      80  	{
      81  	  fs->p = fs->buf;
      82  	  fs->end = fs->buf + INIT_BUF_SIZE;
      83  	}
      84      }
      85  
      86    return fs;
      87  }
      88  #if 0
      89  /* Not exported.  */
      90  #ifdef weak_alias
      91  weak_alias (__argp_make_fmtstream, argp_make_fmtstream)
      92  #endif
      93  #endif
      94  
      95  /* Flush FS to its stream, and free it (but don't close the stream).  */
      96  void
      97  __argp_fmtstream_free (argp_fmtstream_t fs)
      98  {
      99    __argp_fmtstream_update (fs);
     100    if (fs->p > fs->buf)
     101      {
     102  #ifdef _LIBC
     103        __fxprintf (fs->stream, "%.*s", (int) (fs->p - fs->buf), fs->buf);
     104  #else
     105        fwrite_unlocked (fs->buf, 1, fs->p - fs->buf, fs->stream);
     106  #endif
     107      }
     108    free (fs->buf);
     109    free (fs);
     110  }
     111  #if 0
     112  /* Not exported.  */
     113  #ifdef weak_alias
     114  weak_alias (__argp_fmtstream_free, argp_fmtstream_free)
     115  #endif
     116  #endif
     117  
     118  /* Process FS's buffer so that line wrapping is done from POINT_OFFS to the
     119     end of its buffer.  This code is mostly from glibc stdio/linewrap.c.  */
     120  void
     121  __argp_fmtstream_update (argp_fmtstream_t fs)
     122  {
     123    char *buf, *nl;
     124    size_t len;
     125  
     126    /* Scan the buffer for newlines.  */
     127    buf = fs->buf + fs->point_offs;
     128    while (buf < fs->p)
     129      {
     130        size_t r;
     131  
     132        if (fs->point_col == 0 && fs->lmargin != 0)
     133  	{
     134  	  /* We are starting a new line.  Print spaces to the left margin.  */
     135  	  const size_t pad = fs->lmargin;
     136  	  if (fs->p + pad < fs->end)
     137  	    {
     138  	      /* We can fit in them in the buffer by moving the
     139  		 buffer text up and filling in the beginning.  */
     140  	      memmove (buf + pad, buf, fs->p - buf);
     141  	      fs->p += pad; /* Compensate for bigger buffer. */
     142  	      memset (buf, ' ', pad); /* Fill in the spaces.  */
     143  	      buf += pad; /* Don't bother searching them.  */
     144  	    }
     145  	  else
     146  	    {
     147  	      /* No buffer space for spaces.  Must flush.  */
     148  	      size_t i;
     149  	      for (i = 0; i < pad; i++)
     150  		{
     151  #ifdef _LIBC
     152  		  if (_IO_fwide (fs->stream, 0) > 0)
     153  		    putwc_unlocked (L' ', fs->stream);
     154  		  else
     155  #endif
     156  		    putc_unlocked (' ', fs->stream);
     157  		}
     158  	    }
     159  	  fs->point_col = pad;
     160  	}
     161  
     162        len = fs->p - buf;
     163        nl = memchr (buf, '\n', len);
     164  
     165        if (fs->point_col < 0)
     166  	fs->point_col = 0;
     167  
     168        if (!nl)
     169  	{
     170  	  /* The buffer ends in a partial line.  */
     171  
     172  	  if (fs->point_col + len < fs->rmargin)
     173  	    {
     174  	      /* The remaining buffer text is a partial line and fits
     175  		 within the maximum line width.  Advance point for the
     176  		 characters to be written and stop scanning.  */
     177  	      fs->point_col += len;
     178  	      break;
     179  	    }
     180  	  else
     181  	    /* Set the end-of-line pointer for the code below to
     182  	       the end of the buffer.  */
     183  	    nl = fs->p;
     184  	}
     185        else if (fs->point_col + (nl - buf) < (ssize_t) fs->rmargin)
     186  	{
     187  	  /* The buffer contains a full line that fits within the maximum
     188  	     line width.  Reset point and scan the next line.  */
     189  	  fs->point_col = 0;
     190  	  buf = nl + 1;
     191  	  continue;
     192  	}
     193  
     194        /* This line is too long.  */
     195        r = fs->rmargin - 1;
     196  
     197        if (fs->wmargin < 0)
     198  	{
     199  	  /* Truncate the line by overwriting the excess with the
     200  	     newline and anything after it in the buffer.  */
     201  	  if (nl < fs->p)
     202  	    {
     203  	      memmove (buf + (r - fs->point_col), nl, fs->p - nl);
     204  	      fs->p -= buf + (r - fs->point_col) - nl;
     205  	      /* Reset point for the next line and start scanning it.  */
     206  	      fs->point_col = 0;
     207  	      buf += r + 1; /* Skip full line plus \n. */
     208  	    }
     209  	  else
     210  	    {
     211  	      /* The buffer ends with a partial line that is beyond the
     212  		 maximum line width.  Advance point for the characters
     213  		 written, and discard those past the max from the buffer.  */
     214  	      fs->point_col += len;
     215  	      fs->p -= fs->point_col - r;
     216  	      break;
     217  	    }
     218  	}
     219        else
     220  	{
     221  	  /* Do word wrap.  Go to the column just past the maximum line
     222  	     width and scan back for the beginning of the word there.
     223  	     Then insert a line break.  */
     224  
     225  	  char *p, *nextline;
     226  	  int i;
     227  
     228  	  p = buf + (r + 1 - fs->point_col);
     229  	  while (p >= buf && !isblank (*p))
     230  	    --p;
     231  	  nextline = p + 1;	/* This will begin the next line.  */
     232  
     233  	  if (nextline > buf)
     234  	    {
     235  	      /* Swallow separating blanks.  */
     236  	      if (p >= buf)
     237  		do
     238  		  --p;
     239  		while (p >= buf && isblank (*p));
     240  	      nl = p + 1;	/* The newline will replace the first blank. */
     241  	    }
     242  	  else
     243  	    {
     244  	      /* A single word that is greater than the maximum line width.
     245  		 Oh well.  Put it on an overlong line by itself.  */
     246  	      p = buf + (r + 1 - fs->point_col);
     247  	      /* Find the end of the long word.  */
     248  	      do
     249  		++p;
     250  	      while (p < nl && !isblank (*p));
     251  	      if (p == nl)
     252  		{
     253  		  /* It already ends a line.  No fussing required.  */
     254  		  fs->point_col = 0;
     255  		  buf = nl + 1;
     256  		  continue;
     257  		}
     258  	      /* We will move the newline to replace the first blank.  */
     259  	      nl = p;
     260  	      /* Swallow separating blanks.  */
     261  	      do
     262  		++p;
     263  	      while (isblank (*p));
     264  	      /* The next line will start here.  */
     265  	      nextline = p;
     266  	    }
     267  
     268  	  /* Note: There are a bunch of tests below for
     269  	     NEXTLINE == BUF + LEN + 1; this case is where NL happens to fall
     270  	     at the end of the buffer, and NEXTLINE is in fact empty (and so
     271  	     we need not be careful to maintain its contents).  */
     272  
     273  	  if ((nextline == buf + len + 1
     274  	       ? fs->end - nl < fs->wmargin + 1
     275  	       : nextline - (nl + 1) < fs->wmargin)
     276  	      && fs->p > nextline)
     277  	    {
     278  	      /* The margin needs more blanks than we removed.  */
     279  	      if (fs->end - fs->p > fs->wmargin + 1)
     280  		/* Make some space for them.  */
     281  		{
     282  		  size_t mv = fs->p - nextline;
     283  		  memmove (nl + 1 + fs->wmargin, nextline, mv);
     284  		  nextline = nl + 1 + fs->wmargin;
     285  		  len = nextline + mv - buf;
     286  		  *nl++ = '\n';
     287  		}
     288  	      else
     289  		/* Output the first line so we can use the space.  */
     290  		{
     291  #ifdef _LIBC
     292  		  __fxprintf (fs->stream, "%.*s\n",
     293  			      (int) (nl - fs->buf), fs->buf);
     294  #else
     295  		  if (nl > fs->buf)
     296  		    fwrite_unlocked (fs->buf, 1, nl - fs->buf, fs->stream);
     297  		  putc_unlocked ('\n', fs->stream);
     298  #endif
     299  
     300  		  len += buf - fs->buf;
     301  		  nl = buf = fs->buf;
     302  		}
     303  	    }
     304  	  else
     305  	    /* We can fit the newline and blanks in before
     306  	       the next word.  */
     307  	    *nl++ = '\n';
     308  
     309  	  if (nextline - nl >= fs->wmargin
     310  	      || (nextline == buf + len + 1 && fs->end - nextline >= fs->wmargin))
     311  	    /* Add blanks up to the wrap margin column.  */
     312  	    for (i = 0; i < fs->wmargin; ++i)
     313  	      *nl++ = ' ';
     314  	  else
     315  	    for (i = 0; i < fs->wmargin; ++i)
     316  #ifdef _LIBC
     317  	      if (_IO_fwide (fs->stream, 0) > 0)
     318  		putwc_unlocked (L' ', fs->stream);
     319  	      else
     320  #endif
     321  		putc_unlocked (' ', fs->stream);
     322  
     323  	  /* Copy the tail of the original buffer into the current buffer
     324  	     position.  */
     325  	  if (nl < nextline)
     326  	    memmove (nl, nextline, buf + len - nextline);
     327  	  len -= nextline - buf;
     328  
     329  	  /* Continue the scan on the remaining lines in the buffer.  */
     330  	  buf = nl;
     331  
     332  	  /* Restore bufp to include all the remaining text.  */
     333  	  fs->p = nl + len;
     334  
     335  	  /* Reset the counter of what has been output this line.  If wmargin
     336  	     is 0, we want to avoid the lmargin getting added, so we set
     337  	     point_col to a magic value of -1 in that case.  */
     338  	  fs->point_col = fs->wmargin ? fs->wmargin : -1;
     339  	}
     340      }
     341  
     342    /* Remember that we've scanned as far as the end of the buffer.  */
     343    fs->point_offs = fs->p - fs->buf;
     344  }
     345  
     346  /* Ensure that FS has space for AMOUNT more bytes in its buffer, either by
     347     growing the buffer, or by flushing it.  True is returned iff we succeed. */
     348  int
     349  __argp_fmtstream_ensure (struct argp_fmtstream *fs, size_t amount)
     350  {
     351    if ((size_t) (fs->end - fs->p) < amount)
     352      {
     353        ssize_t wrote;
     354  
     355        /* Flush FS's buffer.  */
     356        __argp_fmtstream_update (fs);
     357  
     358  #ifdef _LIBC
     359        __fxprintf (fs->stream, "%.*s", (int) (fs->p - fs->buf), fs->buf);
     360        wrote = fs->p - fs->buf;
     361  #else
     362        wrote = fwrite_unlocked (fs->buf, 1, fs->p - fs->buf, fs->stream);
     363  #endif
     364        if (wrote == fs->p - fs->buf)
     365  	{
     366  	  fs->p = fs->buf;
     367  	  fs->point_offs = 0;
     368  	}
     369        else
     370  	{
     371  	  fs->p -= wrote;
     372  	  fs->point_offs -= wrote;
     373  	  memmove (fs->buf, fs->buf + wrote, fs->p - fs->buf);
     374  	  return 0;
     375  	}
     376  
     377        if ((size_t) (fs->end - fs->buf) < amount)
     378  	/* Gotta grow the buffer.  */
     379  	{
     380  	  size_t old_size = fs->end - fs->buf;
     381  	  size_t new_size = old_size + amount;
     382  	  char *new_buf;
     383  
     384  	  if (new_size < old_size || ! (new_buf = realloc (fs->buf, new_size)))
     385  	    {
     386  	      __set_errno (ENOMEM);
     387  	      return 0;
     388  	    }
     389  
     390  	  fs->buf = new_buf;
     391  	  fs->end = new_buf + new_size;
     392  	  fs->p = fs->buf;
     393  	}
     394      }
     395  
     396    return 1;
     397  }
     398  
     399  ssize_t
     400  __argp_fmtstream_printf (struct argp_fmtstream *fs, const char *fmt, ...)
     401  {
     402    int out;
     403    size_t avail;
     404    size_t size_guess = PRINTF_SIZE_GUESS; /* How much space to reserve. */
     405  
     406    do
     407      {
     408        va_list args;
     409  
     410        if (! __argp_fmtstream_ensure (fs, size_guess))
     411  	return -1;
     412  
     413        va_start (args, fmt);
     414        avail = fs->end - fs->p;
     415        out = __vsnprintf_internal (fs->p, avail, fmt, args, 0);
     416        va_end (args);
     417        if ((size_t) out >= avail)
     418  	size_guess = out + 1;
     419      }
     420    while ((size_t) out >= avail);
     421  
     422    fs->p += out;
     423  
     424    return out;
     425  }
     426  #if 0
     427  /* Not exported.  */
     428  #ifdef weak_alias
     429  weak_alias (__argp_fmtstream_printf, argp_fmtstream_printf)
     430  #endif
     431  #endif
     432  
     433  #endif /* !ARGP_FMTSTREAM_USE_LINEWRAP */