Coverage Report

Created: 2025-11-11 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdbm/tools/wordwrap.c
Line
Count
Source
1
/* This file is part of GDBM, the GNU data base manager.
2
   Copyright (C) 2011-2025 Free Software Foundation, Inc.
3
4
   GDBM is free software; you can redistribute it and/or modify
5
   it under the terms of the GNU General Public License as published by
6
   the Free Software Foundation; either version 3, or (at your option)
7
   any later version.
8
9
   GDBM is distributed in the hope that it will be useful,
10
   but WITHOUT ANY WARRANTY; without even the implied warranty of
11
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
   GNU General Public License for more details.
13
14
   You should have received a copy of the GNU General Public License
15
   along with GDBM. If not, see <http://www.gnu.org/licenses/>.   */
16
17
#include "autoconf.h"
18
#include "gdbmapp.h"
19
#include <unistd.h>
20
#include <stdio.h>
21
#include <string.h>
22
#include <wctype.h>
23
#include <wchar.h>
24
#include <errno.h>
25
#include <limits.h>
26
#include <termios.h>
27
#include <sys/ioctl.h>
28
29
0
#define UNSET ((unsigned)-1)
30
0
#define ISSET(c) (c != UNSET)
31
0
#define DEFAULT_RIGHT_MARGIN 80
32
33
struct position
34
{
35
  unsigned off;
36
  unsigned col;
37
};
38
39
0
#define POSITION_INITIALIZER { 0, 0 }
40
41
static inline void
42
position_init (struct position *pos, unsigned n)
43
0
{
44
0
  pos->off = pos->col = n;
45
0
}
46
47
static inline void
48
position_incr (struct position *pos, int nbytes)
49
0
{
50
0
  pos->off += nbytes;
51
0
  pos->col++;
52
0
}
53
54
static inline void
55
position_add (struct position *a, struct position *b)
56
0
{
57
0
  a->off += b->off;
58
0
  a->col += b->col;
59
0
}
60
61
static inline int
62
position_eq (struct position *a, struct position *b)
63
0
{
64
0
  return a->col == b->col;
65
0
}
66
67
struct wordwrap_file
68
{
69
  int fd;                /* Output file descriptor. */
70
  ssize_t (*writer) (void *, char const *, size_t);
71
  void *stream;
72
  unsigned left_margin;  /* Left margin. */
73
  unsigned right_margin; /* Right margin. */
74
  char *buffer;          /* Output buffer. */
75
  size_t bufsize;        /* Size of buffer in bytes. */
76
  struct position cur;   /* Current position in buffer. */
77
  struct position last_ws; /* Position of the beginning of the last whitespace
78
            sequence written to the buffer. */
79
  struct position ws_run; /* Number of bytes/columns in the last
80
           whitespace sequence. */
81
82
  unsigned word_start;   /* Start of a sequence that should be treated as a
83
          single word. */
84
  unsigned next_left_margin; /* Left margin to be set after next flush. */
85
86
  int indent;            /* If 1, reindent next line. */
87
  int unibyte;           /* 0: Normal operation.
88
          1: multibyte functions disabled for this line. */
89
  int err;               /* Last errno value associated with this file. */
90
};
91
92
/*
93
 * Reset the file for the next input line.
94
 */
95
static void
96
wordwrap_line_init (WORDWRAP_FILE wf, int clrws)
97
0
{
98
0
  position_init (&wf->cur, wf->left_margin);
99
0
  wf->unibyte = 0;
100
0
  if (clrws)
101
0
    {
102
0
      position_init (&wf->ws_run, 0);
103
0
    }
104
0
}
105
106
/*
107
 * Detect the value of the right margin.  Use TIOCGWINSZ ioctl, the COLUMNS
108
 * environment variable, or the default value, in that order.
109
 */
110
static unsigned
111
detect_right_margin (WORDWRAP_FILE wf)
112
0
{
113
0
  struct winsize ws;
114
0
  unsigned r = 0;
115
116
0
  ws.ws_col = ws.ws_row = 0;
117
0
  if ((ioctl (wf->fd, TIOCGWINSZ, (char *) &ws) < 0) || ws.ws_col == 0)
118
0
    {
119
0
      char *p = getenv ("COLUMNS");
120
0
      if (p)
121
0
  {
122
0
    unsigned long n;
123
0
    char *ep;
124
0
    errno = 0;
125
0
    n = strtoul (p, &ep, 10);
126
0
    if (!(errno || *ep || n > UINT_MAX))
127
0
      r = n;
128
0
  }
129
0
      else
130
0
  r = DEFAULT_RIGHT_MARGIN;
131
0
    }
132
0
  else
133
0
    r = ws.ws_col;
134
0
  return r;
135
0
}
136
137
static ssize_t
138
_ww_fd_writer (void *data, const char *str, size_t n)
139
0
{
140
0
  WORDWRAP_FILE wf = data;
141
0
  return write (wf->fd, str, n);
142
0
}
143
144
/*
145
 * Create a wordwrap file operating on file descriptor FD.
146
 * In the contrast to the libc fdopen, the descriptor is dup'ed.
147
 * Left margin is set to 0, right margin is auto detected.
148
 */
149
WORDWRAP_FILE
150
wordwrap_open (int fd, ssize_t (*writer) (void *, const char *, size_t),
151
         void *data)
152
0
{
153
0
  struct wordwrap_file *wf;
154
0
  int ec;
155
156
0
  if ((wf = calloc (1, sizeof (*wf))) == NULL)
157
0
    return NULL;
158
0
  if ((wf->fd = dup (fd)) == -1)
159
0
    {
160
0
      ec = errno;
161
0
      free (wf);
162
0
      errno = ec;
163
0
      return NULL;
164
0
    }
165
0
  wf->writer = writer;
166
0
  wf->stream = data;
167
168
0
  wf->word_start = UNSET;
169
0
  wf->next_left_margin = UNSET;
170
171
0
  wordwrap_set_right_margin (wf, 0);
172
173
0
  return wf;
174
0
}
175
176
WORDWRAP_FILE
177
wordwrap_fdopen (int fd)
178
0
{
179
0
  WORDWRAP_FILE wf = wordwrap_open (fd, _ww_fd_writer, NULL);
180
0
  wf->stream = wf;
181
0
  return wf;
182
0
}
183
/*
184
 * Close the descriptor associated with the wordwrap file, and deallocate
185
 * the memory.
186
 */
187
int
188
wordwrap_close (WORDWRAP_FILE wf)
189
0
{
190
0
  int rc;
191
192
0
  rc = wordwrap_flush (wf);
193
0
  close (wf->fd);
194
0
  free (wf->buffer);
195
0
  free (wf);
196
197
0
  return rc;
198
0
}
199
200
/*
201
 * Return true if wordwrap file is at the beginning of line.
202
 */
203
int
204
wordwrap_at_bol (WORDWRAP_FILE wf)
205
0
{
206
0
  return wf->cur.col == wf->left_margin;
207
0
}
208
209
/*
210
 * Return true if wordwrap file is at the end of line.
211
 */
212
int
213
wordwrap_at_eol (WORDWRAP_FILE wf)
214
0
{
215
0
  return wf->cur.col == wf->right_margin;
216
0
}
217
218
/*
219
 * Write SIZE bytes from the buffer to the file.
220
 * Return the number of bytes written.
221
 * Set the file error indicator on error.
222
 */
223
static ssize_t
224
full_write (WORDWRAP_FILE wf, size_t size)
225
0
{
226
0
  ssize_t total = 0;
227
228
0
  while (total < size)
229
0
    {
230
0
      ssize_t n = wf->writer (wf->stream, wf->buffer + total, size - total);
231
0
      if (n == -1)
232
0
  {
233
0
    wf->err = errno;
234
0
    break;
235
0
  }
236
0
      if (n == 0)
237
0
  {
238
0
    wf->err = ENOSPC;
239
0
    break;
240
0
  }
241
0
      total += n;
242
0
    }
243
0
  return total;
244
0
}
245
246
/*
247
 * A fail-safe version of mbrtowc.  If the call to mbrtowc, fails,
248
 * switches the stream to the unibyte mode.
249
 */
250
static inline size_t
251
safe_mbrtowc (WORDWRAP_FILE wf, wchar_t *wc, const char *s, mbstate_t *ps)
252
0
{
253
0
  if (!wf->unibyte)
254
0
    {
255
0
      size_t n = mbrtowc (wc, s, MB_CUR_MAX, ps);
256
0
      if (n == (size_t) -1 || n == (size_t) -2)
257
0
  wf->unibyte = 1;
258
0
      else
259
0
  return n;
260
0
    }
261
0
  *wc = *(unsigned char *)s;
262
0
  return 1;
263
0
}
264
265
/*
266
 * Return length of the whitespace prefix in STR.
267
 */
268
static size_t
269
wsprefix (WORDWRAP_FILE wf, char const *str, size_t size)
270
0
{
271
0
  size_t i;
272
0
  mbstate_t mbs;
273
0
  wchar_t wc;
274
275
0
  memset (&mbs, 0, sizeof (mbs));
276
0
  for (i = 0; i < size; )
277
0
    {
278
0
      size_t n = safe_mbrtowc (wf, &wc, &str[i], &mbs);
279
280
0
      if (!iswblank (wc))
281
0
  break;
282
283
0
      i += n;
284
0
    }
285
286
0
  return i;
287
0
}
288
289
/*
290
 * Rescan SIZE bytes from the current buffer from the current offset.
291
 * Update offset, column, and whitespace segment counters.
292
 */
293
static void
294
wordwrap_rescan (WORDWRAP_FILE wf, size_t size)
295
0
{
296
0
  mbstate_t mbs;
297
0
  wchar_t wc;
298
299
0
  wordwrap_line_init (wf, 0);
300
301
0
  memset (&mbs, 0, sizeof (mbs));
302
0
  while (wf->cur.off < size)
303
0
    {
304
0
      size_t n = safe_mbrtowc (wf, &wc, &wf->buffer[wf->cur.off], &mbs);
305
306
0
      if (iswblank (wc))
307
0
  {
308
0
    if (!(wf->ws_run.col > 0 &&
309
0
    wf->last_ws.col + wf->ws_run.col == wf->cur.col))
310
0
      {
311
0
        wf->last_ws = wf->cur;
312
0
        position_init (&wf->ws_run, 0);
313
0
      }
314
0
    position_incr (&wf->ws_run, n);
315
0
  }
316
317
0
      position_incr(&wf->cur, n);
318
0
    }
319
0
}
320
321
static struct position
322
wordwrap_last_ws (WORDWRAP_FILE wf, size_t size, struct position *last_ws)
323
0
{
324
0
  mbstate_t mbs;
325
0
  wchar_t wc;
326
0
  struct position cur = POSITION_INITIALIZER;
327
0
  struct position ws_run = POSITION_INITIALIZER;
328
329
0
  memset (&mbs, 0, sizeof (mbs));
330
0
  last_ws->off = last_ws->col = UNSET;
331
0
  while (cur.off < size)
332
0
    {
333
0
      size_t n = safe_mbrtowc (wf, &wc, &wf->buffer[cur.off], &mbs);
334
0
      if (iswblank (wc))
335
0
  {
336
0
    if (!(ws_run.col > 0 && last_ws->col + ws_run.col == cur.col))
337
0
      {
338
0
        *last_ws = cur;
339
0
        position_init (&ws_run, 0);
340
0
      }
341
0
    position_incr (&ws_run, n);
342
0
  }
343
0
      else
344
0
  {
345
0
    position_init (last_ws, UNSET);
346
0
    position_init (&ws_run, 0);
347
0
  }
348
0
      position_incr (&cur, n);
349
0
    }
350
0
  return cur;
351
0
}
352
353
/*
354
 * Flush SIZE bytes from the current buffer to the FD.
355
 * Reinitialize WF for the next line.
356
 */
357
static int
358
flush_line (WORDWRAP_FILE wf, size_t size)
359
0
{
360
0
  ssize_t n;
361
0
  struct position pos, last_ws;
362
363
0
  if (wf->ws_run.off > 0 && size == wf->last_ws.off + wf->ws_run.off)
364
0
    {
365
0
      pos = last_ws = wf->last_ws;
366
0
    }
367
0
  else
368
0
    {
369
0
      pos = wordwrap_last_ws (wf, size, &last_ws);
370
0
    }
371
372
0
  if ((pos.col >= wf->left_margin && wf->cur.col > wf->left_margin) ||
373
0
      size == wf->cur.off)
374
0
    {
375
0
      if (last_ws.off != UNSET)
376
0
  pos = last_ws;
377
378
0
      n = full_write (wf, pos.off);
379
0
      if (n == -1)
380
0
  return -1;
381
382
0
      if (n < pos.off)
383
0
  {
384
    //FIXME
385
0
    abort ();
386
0
  }
387
0
    }
388
389
0
  wf->writer (wf->stream, "\n", 1);
390
391
0
  if (ISSET (wf->next_left_margin))
392
0
    {
393
0
      wf->left_margin = wf->next_left_margin;
394
0
      wf->next_left_margin = UNSET;
395
0
    }
396
397
0
  n = wf->cur.off - size;
398
0
  if (n > 0)
399
0
    {
400
0
      size_t wsn;
401
402
0
      wsn = wsprefix (wf, wf->buffer + size, n);
403
404
0
      size += wsn;
405
0
      n -= wsn;
406
407
0
      if (n)
408
0
  {
409
0
    memmove (wf->buffer + wf->left_margin, wf->buffer + size, n);
410
0
    wf->cur.off = wf->left_margin + n;
411
0
    position_init (&wf->ws_run, 0);
412
0
  }
413
0
    }
414
415
0
  if (wf->indent)
416
0
    {
417
0
      memset (wf->buffer, ' ', wf->left_margin);
418
0
      wf->indent = 0;
419
0
      position_init (&wf->last_ws, 0);
420
0
      position_init (&wf->ws_run, wf->left_margin);
421
0
    }
422
423
0
  wordwrap_rescan (wf, wf->left_margin + n);
424
425
0
  return 0;
426
0
}
427
428
/*
429
 * Flush the wordwrap file buffer.
430
 */
431
int
432
wordwrap_flush (WORDWRAP_FILE wf)
433
0
{
434
0
  if (wf->cur.col > wf->left_margin)
435
0
    return flush_line (wf, wf->cur.off);
436
0
  return 0;
437
0
}
438
439
/*
440
 * Return error indicator (last errno value).
441
 */
442
int
443
wordwrap_error (WORDWRAP_FILE wf)
444
0
{
445
0
  return wf->err;
446
0
}
447
448
/*
449
 * Set left margin value.
450
 */
451
int
452
wordwrap_set_left_margin (WORDWRAP_FILE wf, unsigned left)
453
0
{
454
0
  int bol;
455
456
0
  if (left == wf->left_margin)
457
0
    return 0;
458
0
  else if (left >= wf->right_margin)
459
0
    {
460
0
      wf->err = errno = EINVAL;
461
0
      return -1;
462
0
    }
463
464
0
  bol = wordwrap_at_bol (wf);
465
0
  wf->left_margin = left;
466
0
  wf->indent = 1;
467
0
  if (left < wf->cur.col ||
468
0
      (left == wf->cur.col && (wf->ws_run.col == 0 ||
469
0
            wf->cur.col > wf->last_ws.col + wf->ws_run.col)))
470
0
    {
471
0
      if (!bol)
472
0
  flush_line (wf, wf->cur.off);//FIXME: remove trailing ws
473
0
      else
474
0
  wordwrap_line_init (wf, 1);
475
0
    }
476
0
  else if (left > wf->cur.col)
477
0
    {
478
0
      size_t n = wf->left_margin - wf->cur.col;
479
0
      if (n > 0)
480
0
  {
481
0
    memset (wf->buffer + wf->cur.off, ' ', n);
482
0
    wf->last_ws = wf->cur;
483
0
    position_init (&wf->ws_run, n);
484
0
    position_add (&wf->cur, &wf->ws_run);
485
0
    wf->unibyte = 0;
486
0
  }
487
0
      else
488
0
  wordwrap_line_init (wf, 1);
489
0
    }
490
0
  return 0;
491
0
}
492
493
/*
494
 * Set delayed left margin value.  The new value will take effect after the
495
 * current line is flushed.
496
 */
497
int
498
wordwrap_next_left_margin (WORDWRAP_FILE wf, unsigned left)
499
0
{
500
0
  if (left == wf->left_margin)
501
0
    return 0;
502
0
  else if (left >= wf->right_margin)
503
0
    {
504
0
      wf->err = errno = EINVAL;
505
0
      return -1;
506
0
    }
507
0
  wf->next_left_margin = left;
508
0
  wf->indent = 1;
509
0
  return 0;
510
0
}
511
512
/*
513
 * Set right margin for the file.
514
 */
515
int
516
wordwrap_set_right_margin (WORDWRAP_FILE wf, unsigned right)
517
0
{
518
0
  if (right == 0)
519
0
    right = detect_right_margin (wf);
520
521
0
  if (right == wf->right_margin)
522
0
    return 0;
523
0
  else if (right <= wf->left_margin)
524
0
    {
525
0
      wf->err = errno = EINVAL;
526
0
      return -1;
527
0
    }
528
0
  else
529
0
    {
530
0
      char *p;
531
0
      size_t size;
532
533
0
      if (right < wf->cur.off)
534
0
  {
535
0
    if (wordwrap_flush (wf))
536
0
      return -1;
537
0
  }
538
539
0
      size = MB_CUR_MAX * (right + 1);
540
0
      p = realloc (wf->buffer, size);
541
0
      if (!p)
542
0
  {
543
0
    wf->err = errno;
544
0
    return -1;
545
0
  }
546
547
0
      wf->buffer = p;
548
0
      wf->bufsize = size;
549
0
      wf->right_margin = right;
550
0
    }
551
552
0
  return 0;
553
0
}
554
555
/*
556
 * Mark current output position as the word start.  The normal whitespace
557
 * splitting is disabled, until wordwrap_word_end is called or the current
558
 * buffer is flushed, whichever happens first.
559
 * The functions wordwrap_word_start () / wordwrap_word_end () mark the
560
 * sequence of characters that should not be split on whitespace, such as,
561
 * e.g. option name with argument in help output ("-f FILE").
562
 */
563
void
564
wordwrap_word_start (WORDWRAP_FILE wf)
565
0
{
566
0
  wf->word_start = wf->cur.off;
567
0
}
568
569
/*
570
 * Disable word marker.
571
 */
572
void
573
wordwrap_word_end (WORDWRAP_FILE wf)
574
0
{
575
0
  wf->word_start = UNSET;
576
0
}
577
578
/*
579
 * Write LEN bytes from the string STR to the wordwrap file.
580
 */
581
int
582
wordwrap_write (WORDWRAP_FILE wf, char const *str, size_t len)
583
0
{
584
0
  size_t i;
585
0
  wchar_t wc;
586
0
  mbstate_t mbs;
587
588
0
  memset (&mbs, 0, sizeof (mbs));
589
0
  for (i = 0; i < len; )
590
0
    {
591
0
      size_t n = safe_mbrtowc (wf, &wc, &str[i], &mbs);
592
593
0
      if (wf->cur.col + 1 == wf->right_margin || wc == '\n')
594
0
  {
595
0
    size_t len;
596
597
0
    if (ISSET (wf->word_start))
598
0
      {
599
0
        len = wf->word_start;
600
0
        wf->word_start = UNSET;
601
0
      }
602
0
    else if (!iswspace (wc) && wf->ws_run.off > 0 && wf->last_ws.off > 0)
603
0
      len = wf->last_ws.off;
604
0
    else
605
0
      len = wf->cur.off;
606
607
0
    flush_line (wf, len);
608
0
    if (wc == '\n')
609
0
      {
610
0
        i += n;
611
0
        continue;
612
0
      }
613
0
  }
614
615
0
      if (iswblank (wc))
616
0
  {
617
0
    if (wf->cur.col == wf->left_margin)
618
0
      {
619
        /* Skip leading whitespace */
620
0
        i += n;
621
0
        continue;
622
0
      }
623
0
    else if (!(wf->ws_run.col > 0 &&
624
0
         wf->last_ws.col + wf->ws_run.col == wf->cur.col))
625
0
      {
626
0
        wf->last_ws = wf->cur;
627
0
        position_init (&wf->ws_run, 0);
628
0
      }
629
0
    position_incr (&wf->ws_run, n);
630
0
  }
631
632
0
      memcpy (wf->buffer + wf->cur.off, str + i, n);
633
634
0
      position_incr (&wf->cur, n);
635
0
      i += n;
636
0
    }
637
0
  return 0;
638
0
}
639
640
/*
641
 * Write a nul-terminated string STR to the file (terminating \0 not
642
 * included).
643
 */
644
int
645
wordwrap_puts (WORDWRAP_FILE wf, char const *str)
646
0
{
647
0
  return wordwrap_write (wf, str, strlen (str));
648
0
}
649
650
/*
651
 * Write a single character to the file.
652
 */
653
int
654
wordwrap_putc (WORDWRAP_FILE wf, int c)
655
0
{
656
0
  char ch = c;
657
0
  return wordwrap_write (wf, &ch, 1);
658
0
}
659
660
/*
661
 * Insert a paragraph (empty line).
662
 */
663
int
664
wordwrap_para (WORDWRAP_FILE wf)
665
0
{
666
0
  if (wordwrap_at_bol (wf))
667
0
    return wordwrap_write (wf, "\n", 1);
668
0
  else
669
0
    return wordwrap_write (wf, "\n\n", 2);
670
0
}
671
672
/*
673
 * Format AP according to FMT and write the formatted output to file.
674
 */
675
int
676
wordwrap_vprintf (WORDWRAP_FILE wf, char const *fmt, va_list ap)
677
0
{
678
0
  size_t buflen = 64;
679
0
  char *buf;
680
0
  ssize_t n;
681
0
  int rc;
682
683
0
  buf = malloc (buflen);
684
0
  if (!buf)
685
0
    {
686
0
      wf->err = errno;
687
0
      return -1;
688
0
    }
689
690
0
  for (;;)
691
0
    {
692
0
      va_list aq;
693
694
0
      va_copy (aq, ap);
695
0
      n = vsnprintf (buf, buflen, fmt, aq);
696
0
      va_end (aq);
697
698
0
      if (n < 0 || n >= buflen || !memchr(buf, '\0', n + 1))
699
0
  {
700
0
    char *p;
701
702
0
    if ((size_t) -1 / 3 * 2  <= buflen)
703
0
      {
704
0
        wf->err = ENOMEM;
705
0
        free (buf);
706
0
        return -1;
707
0
      }
708
709
0
    buflen += (buflen + 1) / 2;
710
0
    p = realloc (buf, buflen);
711
0
    if (!p)
712
0
      {
713
0
        wf->err = errno;
714
0
        free (buf);
715
0
        return -1;
716
0
      }
717
0
    buf = p;
718
0
  }
719
0
      else
720
0
  break;
721
0
    }
722
723
0
  rc = wordwrap_write (wf, buf, n);
724
0
  free (buf);
725
0
  return rc;
726
0
}
727
728
/*
729
 * Format argument list according to FMT and write the formatted output
730
 * to file.
731
 */
732
int
733
wordwrap_printf (WORDWRAP_FILE wf, char const *fmt, ...)
734
0
{
735
0
  va_list ap;
736
0
  int rc;
737
738
0
  va_start (ap, fmt);
739
0
  rc = wordwrap_vprintf (wf, fmt, ap);
740
  va_end (ap);
741
0
  return rc;
742
0
}