(root)/
tar-1.35/
gnu/
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     This file is free software: you can redistribute it and/or modify
       7     it under the terms of the GNU Lesser General Public License as
       8     published by the Free Software Foundation, either version 3 of the
       9     License, or (at your option) any later version.
      10  
      11     This file 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
      14     GNU Lesser General Public License for more details.
      15  
      16     You should have received a copy of the GNU Lesser General Public License
      17     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      18  
      19  /* This package emulates glibc 'line_wrap_stream' semantics for systems that
      20     don't have that.  */
      21  
      22  #ifdef HAVE_CONFIG_H
      23  # include <config.h>
      24  #endif
      25  
      26  #include <stdlib.h>
      27  #include <string.h>
      28  #include <errno.h>
      29  #include <stdarg.h>
      30  #include <ctype.h>
      31  
      32  #include "argp-fmtstream.h"
      33  #include "argp-namefrob.h"
      34  
      35  #ifndef ARGP_FMTSTREAM_USE_LINEWRAP
      36  
      37  #ifndef isblank
      38  #define isblank(ch) ((ch)==' ' || (ch)=='\t')
      39  #endif
      40  
      41  #ifdef _LIBC
      42  # include <wchar.h>
      43  # include <libio/libioP.h>
      44  # define __vsnprintf(s, l, f, a) _IO_vsnprintf (s, l, f, a)
      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 ((unsigned char) *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 ((unsigned char) *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                if (p < nl)
     249                  do
     250                    ++p;
     251                  while (p < nl && !isblank ((unsigned char) *p));
     252                if (p == nl)
     253                  {
     254                    /* It already ends a line.  No fussing required.  */
     255                    fs->point_col = 0;
     256                    buf = nl + 1;
     257                    continue;
     258                  }
     259                /* We will move the newline to replace the first blank.  */
     260                nl = p;
     261                /* Swallow separating blanks.  */
     262                do
     263                  ++p;
     264                while (isblank ((unsigned char) *p));
     265                /* The next line will start here.  */
     266                nextline = p;
     267              }
     268  
     269            /* Note: There are a bunch of tests below for
     270               NEXTLINE == BUF + LEN + 1; this case is where NL happens to fall
     271               at the end of the buffer, and NEXTLINE is in fact empty (and so
     272               we need not be careful to maintain its contents).  */
     273  
     274            if ((nextline == buf + len + 1
     275                 ? fs->end - nl < fs->wmargin + 1
     276                 : nextline - (nl + 1) < fs->wmargin)
     277                && fs->p > nextline)
     278              {
     279                /* The margin needs more blanks than we removed.  */
     280                if (fs->end - fs->p > fs->wmargin + 1)
     281                  /* Make some space for them.  */
     282                  {
     283                    size_t mv = fs->p - nextline;
     284                    memmove (nl + 1 + fs->wmargin, nextline, mv);
     285                    nextline = nl + 1 + fs->wmargin;
     286                    len = nextline + mv - buf;
     287                    *nl++ = '\n';
     288                  }
     289                else
     290                  /* Output the first line so we can use the space.  */
     291                  {
     292  #ifdef _LIBC
     293                    __fxprintf (fs->stream, "%.*s\n",
     294                                (int) (nl - fs->buf), fs->buf);
     295  #else
     296                    if (nl > fs->buf)
     297                      fwrite_unlocked (fs->buf, 1, nl - fs->buf, fs->stream);
     298                    putc_unlocked ('\n', fs->stream);
     299  #endif
     300  
     301                    len += buf - fs->buf;
     302                    nl = buf = fs->buf;
     303                  }
     304              }
     305            else
     306              /* We can fit the newline and blanks in before
     307                 the next word.  */
     308              *nl++ = '\n';
     309  
     310            if (nextline - nl >= fs->wmargin
     311                || (nextline == buf + len + 1 && fs->end - nextline >= fs->wmargin))
     312              /* Add blanks up to the wrap margin column.  */
     313              for (i = 0; i < fs->wmargin; ++i)
     314                *nl++ = ' ';
     315            else
     316              for (i = 0; i < fs->wmargin; ++i)
     317  #ifdef _LIBC
     318                if (_IO_fwide (fs->stream, 0) > 0)
     319                  putwc_unlocked (L' ', fs->stream);
     320                else
     321  #endif
     322                  putc_unlocked (' ', fs->stream);
     323  
     324            /* Copy the tail of the original buffer into the current buffer
     325               position.  */
     326            if (nl < nextline)
     327              memmove (nl, nextline, buf + len - nextline);
     328            len -= nextline - buf;
     329  
     330            /* Continue the scan on the remaining lines in the buffer.  */
     331            buf = nl;
     332  
     333            /* Restore bufp to include all the remaining text.  */
     334            fs->p = nl + len;
     335  
     336            /* Reset the counter of what has been output this line.  If wmargin
     337               is 0, we want to avoid the lmargin getting added, so we set
     338               point_col to a magic value of -1 in that case.  */
     339            fs->point_col = fs->wmargin ? fs->wmargin : -1;
     340          }
     341      }
     342  
     343    /* Remember that we've scanned as far as the end of the buffer.  */
     344    fs->point_offs = fs->p - fs->buf;
     345  }
     346  
     347  /* Ensure that FS has space for AMOUNT more bytes in its buffer, either by
     348     growing the buffer, or by flushing it.  True is returned iff we succeed. */
     349  int
     350  __argp_fmtstream_ensure (struct argp_fmtstream *fs, size_t amount)
     351  {
     352    if ((size_t) (fs->end - fs->p) < amount)
     353      {
     354        ssize_t wrote;
     355  
     356        /* Flush FS's buffer.  */
     357        __argp_fmtstream_update (fs);
     358  
     359  #ifdef _LIBC
     360        __fxprintf (fs->stream, "%.*s", (int) (fs->p - fs->buf), fs->buf);
     361        wrote = fs->p - fs->buf;
     362  #else
     363        wrote = fwrite_unlocked (fs->buf, 1, fs->p - fs->buf, fs->stream);
     364  #endif
     365        if (wrote == fs->p - fs->buf)
     366          {
     367            fs->p = fs->buf;
     368            fs->point_offs = 0;
     369          }
     370        else
     371          {
     372            fs->p -= wrote;
     373            fs->point_offs -= wrote;
     374            memmove (fs->buf, fs->buf + wrote, fs->p - fs->buf);
     375            return 0;
     376          }
     377  
     378        if ((size_t) (fs->end - fs->buf) < amount)
     379          /* Gotta grow the buffer.  */
     380          {
     381            size_t old_size = fs->end - fs->buf;
     382            size_t new_size = old_size + amount;
     383            char *new_buf;
     384  
     385            if (new_size < old_size || ! (new_buf = realloc (fs->buf, new_size)))
     386              {
     387                __set_errno (ENOMEM);
     388                return 0;
     389              }
     390  
     391            fs->buf = new_buf;
     392            fs->end = new_buf + new_size;
     393            fs->p = fs->buf;
     394          }
     395      }
     396  
     397    return 1;
     398  }
     399  
     400  ssize_t
     401  __argp_fmtstream_printf (struct argp_fmtstream *fs, const char *fmt, ...)
     402  {
     403    int out;
     404    size_t avail;
     405    size_t size_guess = PRINTF_SIZE_GUESS; /* How much space to reserve. */
     406  
     407    do
     408      {
     409        va_list args;
     410  
     411        if (! __argp_fmtstream_ensure (fs, size_guess))
     412          return -1;
     413  
     414        va_start (args, fmt);
     415        avail = fs->end - fs->p;
     416        out = __vsnprintf (fs->p, avail, fmt, args);
     417        va_end (args);
     418        if ((size_t) out >= avail)
     419          size_guess = out + 1;
     420      }
     421    while ((size_t) out >= avail);
     422  
     423    fs->p += out;
     424  
     425    return out;
     426  }
     427  #if 0
     428  /* Not exported.  */
     429  #ifdef weak_alias
     430  weak_alias (__argp_fmtstream_printf, argp_fmtstream_printf)
     431  #endif
     432  #endif
     433  
     434  #endif /* !ARGP_FMTSTREAM_USE_LINEWRAP */