Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/lib/libdpv/dialog_util.c
39475 views
1
/*-
2
* Copyright (c) 2013-2018 Devin Teske <[email protected]>
3
* 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
*
14
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
* SUCH DAMAGE.
25
*/
26
27
#include <sys/cdefs.h>
28
#include <sys/ioctl.h>
29
30
#include <ctype.h>
31
#include <err.h>
32
#include <fcntl.h>
33
#include <limits.h>
34
#include <spawn.h>
35
#include <stdio.h>
36
#include <stdlib.h>
37
#include <string.h>
38
#include <termios.h>
39
#include <unistd.h>
40
41
#include "dialog_util.h"
42
#include "dpv.h"
43
#include "dpv_private.h"
44
45
extern char **environ;
46
47
#define TTY_DEFAULT_ROWS 24
48
#define TTY_DEFAULT_COLS 80
49
50
/* [X]dialog(1) characteristics */
51
uint8_t dialog_test = 0;
52
uint8_t use_dialog = 0;
53
uint8_t use_libdialog = 1;
54
uint8_t use_xdialog = 0;
55
uint8_t use_color = 1;
56
char dialog[PATH_MAX] = DIALOG;
57
58
/* [X]dialog(1) functionality */
59
char *title = NULL;
60
char *backtitle = NULL;
61
int dheight = 0;
62
int dwidth = 0;
63
static char *dargv[64] = { NULL };
64
65
/* TTY/Screen characteristics */
66
static struct winsize *maxsize = NULL;
67
68
/* Function prototypes */
69
static void tty_maxsize_update(void);
70
static void x11_maxsize_update(void);
71
72
/*
73
* Update row/column fields of `maxsize' global (used by dialog_maxrows() and
74
* dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
75
* The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
76
* maximum height and width (respectively) for a dialog(1) widget based on the
77
* active TTY size.
78
*
79
* This function is called automatically by dialog_maxrows/cols() to reflect
80
* changes in terminal size in-between calls.
81
*/
82
static void
83
tty_maxsize_update(void)
84
{
85
int fd = STDIN_FILENO;
86
struct termios t;
87
88
if (maxsize == NULL) {
89
if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
90
errx(EXIT_FAILURE, "Out of memory?!");
91
memset((void *)maxsize, '\0', sizeof(struct winsize));
92
}
93
94
if (!isatty(fd))
95
fd = open("/dev/tty", O_RDONLY);
96
if ((tcgetattr(fd, &t) < 0) || (ioctl(fd, TIOCGWINSZ, maxsize) < 0)) {
97
maxsize->ws_row = TTY_DEFAULT_ROWS;
98
maxsize->ws_col = TTY_DEFAULT_COLS;
99
}
100
}
101
102
/*
103
* Update row/column fields of `maxsize' global (used by dialog_maxrows() and
104
* dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
105
* The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
106
* maximum height and width (respectively) for an Xdialog(1) widget based on
107
* the active video resolution of the X11 environment.
108
*
109
* This function is called automatically by dialog_maxrows/cols() to initialize
110
* `maxsize'. Since video resolution changes are less common and more obtrusive
111
* than changes to terminal size, the dialog_maxrows/cols() functions only call
112
* this function when `maxsize' is set to NULL.
113
*/
114
static void
115
x11_maxsize_update(void)
116
{
117
FILE *f = NULL;
118
char *cols;
119
char *cp;
120
char *rows;
121
char cmdbuf[LINE_MAX];
122
char rbuf[LINE_MAX];
123
124
if (maxsize == NULL) {
125
if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
126
errx(EXIT_FAILURE, "Out of memory?!");
127
memset((void *)maxsize, '\0', sizeof(struct winsize));
128
}
129
130
/* Assemble the command necessary to get X11 sizes */
131
snprintf(cmdbuf, LINE_MAX, "%s --print-maxsize 2>&1", dialog);
132
133
fflush(STDIN_FILENO); /* prevent popen(3) from seeking on stdin */
134
135
if ((f = popen(cmdbuf, "r")) == NULL) {
136
if (debug)
137
warnx("WARNING! Command `%s' failed", cmdbuf);
138
return;
139
}
140
141
/* Read in the line returned from Xdialog(1) */
142
if ((fgets(rbuf, LINE_MAX, f) == NULL) || (pclose(f) < 0))
143
return;
144
145
/* Check for X11-related errors */
146
if (strncmp(rbuf, "Xdialog: Error", 14) == 0)
147
return;
148
149
/* Parse expected output: MaxSize: YY, XXX */
150
if ((rows = strchr(rbuf, ' ')) == NULL)
151
return;
152
if ((cols = strchr(rows, ',')) != NULL) {
153
/* strtonum(3) doesn't like trailing junk */
154
*(cols++) = '\0';
155
if ((cp = strchr(cols, '\n')) != NULL)
156
*cp = '\0';
157
}
158
159
/* Convert to unsigned short */
160
maxsize->ws_row = (unsigned short)strtonum(
161
rows, 0, USHRT_MAX, (const char **)NULL);
162
maxsize->ws_col = (unsigned short)strtonum(
163
cols, 0, USHRT_MAX, (const char **)NULL);
164
}
165
166
/*
167
* Return the current maximum height (rows) for an [X]dialog(1) widget.
168
*/
169
int
170
dialog_maxrows(void)
171
{
172
173
if (use_xdialog && maxsize == NULL)
174
x11_maxsize_update(); /* initialize maxsize for GUI */
175
else if (!use_xdialog)
176
tty_maxsize_update(); /* update maxsize for TTY */
177
return (maxsize->ws_row);
178
}
179
180
/*
181
* Return the current maximum width (cols) for an [X]dialog(1) widget.
182
*/
183
int
184
dialog_maxcols(void)
185
{
186
187
if (use_xdialog && maxsize == NULL)
188
x11_maxsize_update(); /* initialize maxsize for GUI */
189
else if (!use_xdialog)
190
tty_maxsize_update(); /* update maxsize for TTY */
191
192
if (use_dialog || use_libdialog) {
193
if (use_shadow)
194
return (maxsize->ws_col - 2);
195
else
196
return (maxsize->ws_col);
197
} else
198
return (maxsize->ws_col);
199
}
200
201
/*
202
* Return the current maximum width (cols) for the terminal.
203
*/
204
int
205
tty_maxcols(void)
206
{
207
208
if (use_xdialog && maxsize == NULL)
209
x11_maxsize_update(); /* initialize maxsize for GUI */
210
else if (!use_xdialog)
211
tty_maxsize_update(); /* update maxsize for TTY */
212
213
return (maxsize->ws_col);
214
}
215
216
/*
217
* Spawn an [X]dialog(1) `--gauge' box with a `--prompt' value of init_prompt.
218
* Writes the resulting process ID to the pid_t pointed at by `pid'. Returns a
219
* file descriptor (int) suitable for writing data to the [X]dialog(1) instance
220
* (data written to the file descriptor is seen as standard-in by the spawned
221
* [X]dialog(1) process).
222
*/
223
int
224
dialog_spawn_gauge(char *init_prompt, pid_t *pid)
225
{
226
char dummy_init[2] = "";
227
char *cp;
228
int height;
229
int width;
230
int error;
231
posix_spawn_file_actions_t action;
232
#if DIALOG_SPAWN_DEBUG
233
unsigned int i;
234
#endif
235
unsigned int n = 0;
236
int stdin_pipe[2] = { -1, -1 };
237
238
/* Override `dialog' with a path from ENV_DIALOG if provided */
239
if ((cp = getenv(ENV_DIALOG)) != NULL)
240
snprintf(dialog, PATH_MAX, "%s", cp);
241
242
/* For Xdialog(1), set ENV_XDIALOG_HIGH_DIALOG_COMPAT */
243
setenv(ENV_XDIALOG_HIGH_DIALOG_COMPAT, "1", 1);
244
245
/* Constrain the height/width */
246
height = dialog_maxrows();
247
if (backtitle != NULL)
248
height -= use_shadow ? 5 : 4;
249
if (dheight < height)
250
height = dheight;
251
width = dialog_maxcols();
252
if (dwidth < width)
253
width = dwidth;
254
255
/* Populate argument array */
256
dargv[n++] = dialog;
257
if (title != NULL) {
258
if ((dargv[n] = malloc(8)) == NULL)
259
errx(EXIT_FAILURE, "Out of memory?!");
260
sprintf(dargv[n++], "--title");
261
dargv[n++] = title;
262
} else {
263
if ((dargv[n] = malloc(8)) == NULL)
264
errx(EXIT_FAILURE, "Out of memory?!");
265
sprintf(dargv[n++], "--title");
266
if ((dargv[n] = malloc(1)) == NULL)
267
errx(EXIT_FAILURE, "Out of memory?!");
268
*dargv[n++] = '\0';
269
}
270
if (backtitle != NULL) {
271
if ((dargv[n] = malloc(12)) == NULL)
272
errx(EXIT_FAILURE, "Out of memory?!");
273
sprintf(dargv[n++], "--backtitle");
274
dargv[n++] = backtitle;
275
}
276
if (use_color) {
277
if ((dargv[n] = malloc(11)) == NULL)
278
errx(EXIT_FAILURE, "Out of memory?!");
279
sprintf(dargv[n++], "--colors");
280
}
281
if (use_xdialog) {
282
if ((dargv[n] = malloc(7)) == NULL)
283
errx(EXIT_FAILURE, "Out of memory?!");
284
sprintf(dargv[n++], "--left");
285
286
/*
287
* NOTE: Xdialog(1)'s `--wrap' appears to be broken for the
288
* `--gauge' widget prompt-updates. Add it anyway (in-case it
289
* gets fixed in some later release).
290
*/
291
if ((dargv[n] = malloc(7)) == NULL)
292
errx(EXIT_FAILURE, "Out of memory?!");
293
sprintf(dargv[n++], "--wrap");
294
}
295
if ((dargv[n] = malloc(8)) == NULL)
296
errx(EXIT_FAILURE, "Out of memory?!");
297
sprintf(dargv[n++], "--gauge");
298
dargv[n++] = use_xdialog ? dummy_init : init_prompt;
299
if ((dargv[n] = malloc(40)) == NULL)
300
errx(EXIT_FAILURE, "Out of memory?!");
301
snprintf(dargv[n++], 40, "%u", height);
302
if ((dargv[n] = malloc(40)) == NULL)
303
errx(EXIT_FAILURE, "Out of memory?!");
304
snprintf(dargv[n++], 40, "%u", width);
305
dargv[n] = NULL;
306
307
/* Open a pipe(2) to communicate with [X]dialog(1) */
308
if (pipe(stdin_pipe) < 0)
309
err(EXIT_FAILURE, "%s: pipe(2)", __func__);
310
311
/* Fork [X]dialog(1) process */
312
#if DIALOG_SPAWN_DEBUG
313
fprintf(stderr, "%s: spawning `", __func__);
314
for (i = 0; i < n; i++) {
315
if (i == 0)
316
fprintf(stderr, "%s", dargv[i]);
317
else if (*dargv[i] == '-' && *(dargv[i] + 1) == '-')
318
fprintf(stderr, " %s", dargv[i]);
319
else
320
fprintf(stderr, " \"%s\"", dargv[i]);
321
}
322
fprintf(stderr, "'\n");
323
#endif
324
posix_spawn_file_actions_init(&action);
325
posix_spawn_file_actions_adddup2(&action, stdin_pipe[0], STDIN_FILENO);
326
posix_spawn_file_actions_addclose(&action, stdin_pipe[1]);
327
error = posix_spawnp(pid, dialog, &action,
328
(const posix_spawnattr_t *)NULL, dargv, environ);
329
if (error != 0) err(EXIT_FAILURE, "%s", dialog);
330
331
/* NB: Do not free(3) *dargv[], else SIGSEGV */
332
333
return (stdin_pipe[1]);
334
}
335
336
/*
337
* Returns the number of lines in buffer pointed to by `prompt'. Takes both
338
* newlines and escaped-newlines into account.
339
*/
340
unsigned int
341
dialog_prompt_numlines(const char *prompt, uint8_t nlstate)
342
{
343
uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
344
const char *cp = prompt;
345
unsigned int nlines = 1;
346
347
if (prompt == NULL || *prompt == '\0')
348
return (0);
349
350
while (*cp != '\0') {
351
if (use_dialog) {
352
if (strncmp(cp, "\\n", 2) == 0) {
353
cp++;
354
nlines++;
355
nls = TRUE; /* See declaration comment */
356
} else if (*cp == '\n') {
357
if (!nls)
358
nlines++;
359
nls = FALSE; /* See declaration comment */
360
}
361
} else if (use_libdialog) {
362
if (*cp == '\n')
363
nlines++;
364
} else if (strncmp(cp, "\\n", 2) == 0) {
365
cp++;
366
nlines++;
367
}
368
cp++;
369
}
370
371
return (nlines);
372
}
373
374
/*
375
* Returns the length in bytes of the longest line in buffer pointed to by
376
* `prompt'. Takes newlines and escaped newlines into account. Also discounts
377
* dialog(1) color escape codes if enabled (via `use_color' global).
378
*/
379
unsigned int
380
dialog_prompt_longestline(const char *prompt, uint8_t nlstate)
381
{
382
uint8_t backslash = 0;
383
uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
384
const char *p = prompt;
385
int longest = 0;
386
int n = 0;
387
388
/* `prompt' parameter is required */
389
if (prompt == NULL)
390
return (0);
391
if (*prompt == '\0')
392
return (0); /* shortcut */
393
394
/* Loop until the end of the string */
395
while (*p != '\0') {
396
/* dialog(1) and dialog(3) will render literal newlines */
397
if (use_dialog || use_libdialog) {
398
if (*p == '\n') {
399
if (!use_libdialog && nls)
400
n++;
401
else {
402
if (n > longest)
403
longest = n;
404
n = 0;
405
}
406
nls = FALSE; /* See declaration comment */
407
p++;
408
continue;
409
}
410
}
411
412
/* Check for backslash character */
413
if (*p == '\\') {
414
/* If second backslash, count as a single-char */
415
if ((backslash ^= 1) == 0)
416
n++;
417
} else if (backslash) {
418
if (*p == 'n' && !use_libdialog) { /* new line */
419
/* NB: dialog(3) ignores escaped newlines */
420
nls = TRUE; /* See declaration comment */
421
if (n > longest)
422
longest = n;
423
n = 0;
424
} else if (use_color && *p == 'Z') {
425
if (*++p != '\0')
426
p++;
427
backslash = 0;
428
continue;
429
} else /* [X]dialog(1)/dialog(3) only expand those */
430
n += 2;
431
432
backslash = 0;
433
} else
434
n++;
435
p++;
436
}
437
if (n > longest)
438
longest = n;
439
440
return (longest);
441
}
442
443
/*
444
* Returns a pointer to the last line in buffer pointed to by `prompt'. Takes
445
* both newlines (if using dialog(1) versus Xdialog(1)) and escaped newlines
446
* into account. If no newlines (escaped or otherwise) appear in the buffer,
447
* `prompt' is returned. If passed a NULL pointer, returns NULL.
448
*/
449
char *
450
dialog_prompt_lastline(char *prompt, uint8_t nlstate)
451
{
452
uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
453
char *lastline;
454
char *p;
455
456
if (prompt == NULL)
457
return (NULL);
458
if (*prompt == '\0')
459
return (prompt); /* shortcut */
460
461
lastline = p = prompt;
462
while (*p != '\0') {
463
/* dialog(1) and dialog(3) will render literal newlines */
464
if (use_dialog || use_libdialog) {
465
if (*p == '\n') {
466
if (use_libdialog || !nls)
467
lastline = p + 1;
468
nls = FALSE; /* See declaration comment */
469
}
470
}
471
/* dialog(3) does not expand escaped newlines */
472
if (use_libdialog) {
473
p++;
474
continue;
475
}
476
if (*p == '\\' && *(p + 1) != '\0' && *(++p) == 'n') {
477
nls = TRUE; /* See declaration comment */
478
lastline = p + 1;
479
}
480
p++;
481
}
482
483
return (lastline);
484
}
485
486
/*
487
* Returns the number of extra lines generated by wrapping the text in buffer
488
* pointed to by `prompt' within `ncols' columns (for prompts, this should be
489
* dwidth - 4). Also discounts dialog(1) color escape codes if enabled (via
490
* `use_color' global).
491
*/
492
int
493
dialog_prompt_wrappedlines(char *prompt, int ncols, uint8_t nlstate)
494
{
495
uint8_t backslash = 0;
496
uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
497
char *cp;
498
char *p = prompt;
499
int n = 0;
500
int wlines = 0;
501
502
/* `prompt' parameter is required */
503
if (p == NULL)
504
return (0);
505
if (*p == '\0')
506
return (0); /* shortcut */
507
508
/* Loop until the end of the string */
509
while (*p != '\0') {
510
/* dialog(1) and dialog(3) will render literal newlines */
511
if (use_dialog || use_libdialog) {
512
if (*p == '\n') {
513
if (use_dialog || !nls)
514
n = 0;
515
nls = FALSE; /* See declaration comment */
516
}
517
}
518
519
/* Check for backslash character */
520
if (*p == '\\') {
521
/* If second backslash, count as a single-char */
522
if ((backslash ^= 1) == 0)
523
n++;
524
} else if (backslash) {
525
if (*p == 'n' && !use_libdialog) { /* new line */
526
/* NB: dialog(3) ignores escaped newlines */
527
nls = TRUE; /* See declaration comment */
528
n = 0;
529
} else if (use_color && *p == 'Z') {
530
if (*++p != '\0')
531
p++;
532
backslash = 0;
533
continue;
534
} else /* [X]dialog(1)/dialog(3) only expand those */
535
n += 2;
536
537
backslash = 0;
538
} else
539
n++;
540
541
/* Did we pass the width barrier? */
542
if (n > ncols) {
543
/*
544
* Work backward to find the first whitespace on-which
545
* dialog(1) will wrap the line (but don't go before
546
* the start of this line).
547
*/
548
cp = p;
549
while (n > 1 && !isspace(*cp)) {
550
cp--;
551
n--;
552
}
553
if (n > 0 && isspace(*cp))
554
p = cp;
555
wlines++;
556
n = 1;
557
}
558
559
p++;
560
}
561
562
return (wlines);
563
}
564
565
/*
566
* Returns zero if the buffer pointed to by `prompt' contains an escaped
567
* newline but only if appearing after any/all literal newlines. This is
568
* specific to dialog(1) and does not apply to Xdialog(1).
569
*
570
* As an attempt to make shell scripts easier to read, dialog(1) will "eat"
571
* the first literal newline after an escaped newline. This however has a bug
572
* in its implementation in that rather than allowing `\\n\n' to be treated
573
* similar to `\\n' or `\n', dialog(1) expands the `\\n' and then translates
574
* the following literal newline (with or without characters between [!]) into
575
* a single space.
576
*
577
* If you want to be compatible with Xdialog(1), it is suggested that you not
578
* use literal newlines (they aren't supported); but if you have to use them,
579
* go right ahead. But be forewarned... if you set $DIALOG in your environment
580
* to something other than `cdialog' (our current dialog(1)), then it should
581
* do the same thing w/respect to how to handle a literal newline after an
582
* escaped newline (you could do no wrong by translating every literal newline
583
* into a space but only when you've previously encountered an escaped one;
584
* this is what dialog(1) is doing).
585
*
586
* The ``newline state'' (or nlstate for short; as I'm calling it) is helpful
587
* if you plan to combine multiple strings into a single prompt text. In lead-
588
* up to this procedure, a common task is to calculate and utilize the widths
589
* and heights of each piece of prompt text to later be combined. However, if
590
* (for example) the first string ends in a positive newline state (has an
591
* escaped newline without trailing literal), the first literal newline in the
592
* second string will be mangled.
593
*
594
* The return value of this function should be used as the `nlstate' argument
595
* to dialog_*() functions that require it to allow accurate calculations in
596
* the event such information is needed.
597
*/
598
uint8_t
599
dialog_prompt_nlstate(const char *prompt)
600
{
601
const char *cp;
602
603
if (prompt == NULL)
604
return 0;
605
606
/*
607
* Work our way backward from the end of the string for efficiency.
608
*/
609
cp = prompt + strlen(prompt);
610
while (--cp >= prompt) {
611
/*
612
* If we get to a literal newline first, this prompt ends in a
613
* clean state for rendering with dialog(1). Otherwise, if we
614
* get to an escaped newline first, this prompt ends in an un-
615
* clean state (following literal will be mangled; see above).
616
*/
617
if (*cp == '\n')
618
return (0);
619
else if (*cp == 'n' && --cp > prompt && *cp == '\\')
620
return (1);
621
}
622
623
return (0); /* no newlines (escaped or otherwise) */
624
}
625
626
/*
627
* Free allocated items initialized by tty_maxsize_update() and
628
* x11_maxsize_update()
629
*/
630
void
631
dialog_maxsize_free(void)
632
{
633
if (maxsize != NULL) {
634
free(maxsize);
635
maxsize = NULL;
636
}
637
}
638
639