Changeset View
Changeset View
Standalone View
Standalone View
lib/libdpv/dialog_util.c
- This file was added.
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
/*- | |||||
* Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org> | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <ctype.h> | |||||
#include <err.h> | |||||
#include <fcntl.h> | |||||
#include <limits.h> | |||||
#include <spawn.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <sys/ioctl.h> | |||||
#include <termios.h> | |||||
#include <unistd.h> | |||||
#include "dialog_util.h" | |||||
#include "dpv.h" | |||||
#include "dpv_private.h" | |||||
extern char **environ; | |||||
#define TTY_DEFAULT_ROWS 24 | |||||
#define TTY_DEFAULT_COLS 80 | |||||
/* [X]dialog(1) characteristics */ | |||||
uint8_t dialog_test = 0; | |||||
uint8_t use_dialog = 0; | |||||
uint8_t use_libdialog = 1; | |||||
uint8_t use_xdialog = 0; | |||||
uint8_t use_color = 1; | |||||
char dialog[PATH_MAX] = DIALOG; | |||||
/* [X]dialog(1) functionality */ | |||||
int dheight = 0; | |||||
int dwidth = 0; | |||||
char *title = NULL; | |||||
char *backtitle = NULL; | |||||
/* TTY/Screen characteristics */ | |||||
static struct winsize *maxsize = NULL; | |||||
/* Function prototypes for private functions (see style(9)) */ | |||||
void tty_maxsize_update(void); | |||||
void x11_maxsize_update(void); | |||||
/* | |||||
* Update row/column fields of `maxsize' global (used by dialog_maxrows() and | |||||
* dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized. | |||||
* The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current | |||||
* maximum height and width (respectively) for a dialog(1) widget based on the | |||||
* active TTY size. | |||||
* | |||||
* This function is called automatically by dialog_maxrows/cols() to reflect | |||||
* changes in terminal size in-between calls. | |||||
*/ | |||||
void | |||||
tty_maxsize_update(void) | |||||
{ | |||||
int fd = STDIN_FILENO; | |||||
struct termios t; | |||||
if (maxsize == NULL) | |||||
{ | |||||
if ((maxsize = malloc(sizeof(struct winsize))) == NULL) | |||||
errx(EXIT_FAILURE, "Out of memory?!"); | |||||
memset((void *)maxsize, '\0', sizeof(struct winsize)); | |||||
} | |||||
if (!isatty(fd)) fd = open("/dev/tty", O_RDONLY); | |||||
if ((tcgetattr(fd, &t) < 0) || (ioctl(fd, TIOCGWINSZ, maxsize) < 0)) | |||||
{ | |||||
maxsize->ws_row = TTY_DEFAULT_ROWS; | |||||
maxsize->ws_col = TTY_DEFAULT_COLS; | |||||
} | |||||
} | |||||
/* | |||||
* Update row/column fields of `maxsize' global (used by dialog_maxrows() and | |||||
* dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized. | |||||
* The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current | |||||
* maximum height and width (respectively) for an Xdialog(1) widget based on | |||||
* the active video resolution of the X11 environment. | |||||
* | |||||
* This function is called automatically by dialog_maxrows/cols() to initialize | |||||
* `maxsize'. Since video resolution changes are less common and more obtrusive | |||||
* than changes to terminal size, the dialog_maxrows/cols() functions only call | |||||
* this function when `maxsize' is set to NULL. | |||||
*/ | |||||
void | |||||
x11_maxsize_update(void) | |||||
{ | |||||
FILE *f = NULL; | |||||
char *cp, *rows, *cols; | |||||
char cmdbuf[LINE_MAX], rbuf[LINE_MAX]; | |||||
if (maxsize == NULL) | |||||
{ | |||||
if ((maxsize = malloc(sizeof(struct winsize))) == NULL) | |||||
errx(EXIT_FAILURE, "Out of memory?!"); | |||||
memset((void *)maxsize, '\0', sizeof(struct winsize)); | |||||
} | |||||
/* Assemble the command necessary to get X11 sizes */ | |||||
snprintf(cmdbuf, LINE_MAX, "%s --print-maxsize 2>&1", dialog); | |||||
fflush(STDIN_FILENO); /* prevent popen(3) from seeking on stdin */ | |||||
if ((f = popen(cmdbuf, "r")) == NULL) | |||||
{ | |||||
if (debug) warnx("WARNING! Command `%s' failed", cmdbuf); | |||||
return; | |||||
} | |||||
/* Read in the line returned from Xdialog(1) */ | |||||
if ((fgets(rbuf, LINE_MAX, f) == NULL) || (pclose(f) < 0)) return; | |||||
/* Check for X11-related errors */ | |||||
if (strncmp(rbuf, "Xdialog: Error", 14) == 0) return; | |||||
/* Parse expected output: MaxSize: YY, XXX */ | |||||
if ((rows = strchr(rbuf, ' ')) == NULL) return; | |||||
if ((cols = strchr(rows, ',')) != NULL) | |||||
{ | |||||
/* strtonum(3) doesn't like trailing junk */ | |||||
*(cols++) = '\0'; | |||||
if ((cp = strchr(cols, '\n')) != NULL) *cp = '\0'; | |||||
} | |||||
/* Convert to unsigned short */ | |||||
maxsize->ws_row = (unsigned short)strtonum( | |||||
rows, 0, USHRT_MAX, (const char **)NULL); | |||||
maxsize->ws_col = (unsigned short)strtonum( | |||||
cols, 0, USHRT_MAX, (const char **)NULL); | |||||
} | |||||
/* | |||||
* Return the current maximum height (rows) for an [X]dialog(1) widget. | |||||
*/ | |||||
int | |||||
dialog_maxrows(void) | |||||
{ | |||||
if (use_xdialog && maxsize == NULL) | |||||
x11_maxsize_update(); /* initialize maxsize for GUI */ | |||||
else if (!use_xdialog) | |||||
tty_maxsize_update(); /* update maxsize for TTY */ | |||||
return maxsize->ws_row; | |||||
} | |||||
/* | |||||
* Return the current maximum width (cols) for an [X]dialog(1) widget. | |||||
*/ | |||||
int | |||||
dialog_maxcols(void) | |||||
{ | |||||
if (use_xdialog && maxsize == NULL) | |||||
x11_maxsize_update(); /* initialize maxsize for GUI */ | |||||
else if (!use_xdialog) | |||||
tty_maxsize_update(); /* update maxsize for TTY */ | |||||
if (use_dialog || use_libdialog) { | |||||
if (use_shadow) | |||||
return (maxsize->ws_col - 2); | |||||
else | |||||
return (maxsize->ws_col); | |||||
} else | |||||
return (maxsize->ws_col); | |||||
} | |||||
/* | |||||
* Return the current maximum width (cols) for the terminal. | |||||
*/ | |||||
int | |||||
tty_maxcols(void) | |||||
{ | |||||
if (use_xdialog && maxsize == NULL) | |||||
x11_maxsize_update(); /* initialize maxsize for GUI */ | |||||
else if (!use_xdialog) | |||||
tty_maxsize_update(); /* update maxsize for TTY */ | |||||
return (maxsize->ws_col); | |||||
} | |||||
/* | |||||
* Spawn an [X]dialog(1) `--gauge' box with a `--prompt' value of init_prompt. | |||||
* Writes the resulting process ID to the pid_t pointed at by `pid'. Returns a | |||||
* file descriptor (int) suitable for writing data to the [X]dialog(1) instance | |||||
* (data written to the file descriptor is seen as standard-in by the spawned | |||||
* [X]dialog(1) process). | |||||
*/ | |||||
int | |||||
dialog_spawn_gauge(char *init_prompt, pid_t *pid) | |||||
{ | |||||
unsigned int n = 0; | |||||
int height, width, error, stdin_pipe[2] = { -1, -1 }; | |||||
char *cp, *dargv[13], dummy_init[2] = ""; | |||||
posix_spawn_file_actions_t action; | |||||
/* Override `dialog' with a path from ENV_DIALOG if provided */ | |||||
if ((cp = getenv(ENV_DIALOG)) != NULL) | |||||
snprintf(dialog, PATH_MAX, "%s", cp); | |||||
/* For Xdialog(1), set ENV_XDIALOG_HIGH_DIALOG_COMPAT */ | |||||
setenv(ENV_XDIALOG_HIGH_DIALOG_COMPAT, "1", 1); | |||||
/* Constrain the height/width */ | |||||
height = dialog_maxrows(); | |||||
if (backtitle != NULL) height -= (use_shadow ? 5 : 4); | |||||
if (dheight < height) height = dheight; | |||||
width = dialog_maxcols(); | |||||
if (dwidth < width) width = dwidth; | |||||
/* Populate argument array */ | |||||
dargv[n++] = dialog; | |||||
if (title != NULL) | |||||
{ | |||||
if ((dargv[n] = malloc(8)) == NULL) | |||||
errx(EXIT_FAILURE, "Out of memory?!"); | |||||
sprintf(dargv[n++], "--title"); | |||||
dargv[n++] = title; | |||||
} | |||||
if (backtitle != NULL) | |||||
{ | |||||
if ((dargv[n] = malloc(12)) == NULL) | |||||
errx(EXIT_FAILURE, "Out of memory?!"); | |||||
sprintf(dargv[n++], "--backtitle"); | |||||
dargv[n++] = backtitle; | |||||
} | |||||
if (use_color) | |||||
{ | |||||
if ((dargv[n] = malloc(11)) == NULL) | |||||
errx(EXIT_FAILURE, "Out of memory?!"); | |||||
sprintf(dargv[n++], "--colors"); | |||||
} | |||||
if (use_xdialog) | |||||
{ | |||||
if ((dargv[n] = malloc(7)) == NULL) | |||||
errx(EXIT_FAILURE, "Out of memory?!"); | |||||
sprintf(dargv[n++], "--left"); | |||||
/* | |||||
* NOTE: Xdialog(1)'s `--wrap' appears to be broken for the | |||||
* `--gauge' widget prompt-updates. Add it anyway (in-case it | |||||
* gets fixed in some later release). | |||||
*/ | |||||
if ((dargv[n] = malloc(7)) == NULL) | |||||
errx(EXIT_FAILURE, "Out of memory?!"); | |||||
sprintf(dargv[n++], "--wrap"); | |||||
} | |||||
if ((dargv[n] = malloc(8)) == NULL) | |||||
errx(EXIT_FAILURE, "Out of memory?!"); | |||||
sprintf(dargv[n++], "--gauge"); | |||||
dargv[n++] = (use_xdialog ? dummy_init : init_prompt); | |||||
if ((dargv[n] = malloc(40)) == NULL) | |||||
errx(EXIT_FAILURE, "Out of memory?!"); | |||||
snprintf(dargv[n++], 40, "%u", height); | |||||
if ((dargv[n] = malloc(40)) == NULL) | |||||
errx(EXIT_FAILURE, "Out of memory?!"); | |||||
snprintf(dargv[n++], 40, "%u", width); | |||||
dargv[n] = NULL; | |||||
/* Open a pipe(2) to communicate with [X]dialog(1) */ | |||||
if (pipe(stdin_pipe) < 0) err(EXIT_FAILURE, "%s: pipe(2)", __func__); | |||||
/* Fork [X]dialog(1) process */ | |||||
#if DIALOG_SPAWN_DEBUG | |||||
{ | |||||
unsigned int i; | |||||
fprintf(stderr, "%s: spawning `", __func__); | |||||
for (i = 0; i < n; i++) | |||||
{ | |||||
if (i == 0) | |||||
fprintf(stderr, "%s", dargv[i]); | |||||
else if (*dargv[i] == '-' && *(dargv[i] + 1) == '-') | |||||
fprintf(stderr, " %s", dargv[i]); | |||||
else | |||||
fprintf(stderr, " \"%s\"", dargv[i]); | |||||
} | |||||
fprintf(stderr, "'\n"); | |||||
} | |||||
#endif | |||||
posix_spawn_file_actions_init(&action); | |||||
posix_spawn_file_actions_adddup2(&action, stdin_pipe[0], STDIN_FILENO); | |||||
posix_spawn_file_actions_addclose(&action, stdin_pipe[1]); | |||||
error = posix_spawnp(pid, dialog, &action, | |||||
(const posix_spawnattr_t *)NULL, dargv, environ); | |||||
if (error != 0) err(EXIT_FAILURE, "%s: posix_spawnp(3)", __func__); | |||||
/* NB: Do not free(3) *dargv[], else SIGSEGV */ | |||||
return stdin_pipe[1]; | |||||
} | |||||
/* | |||||
* Returns the number of lines in buffer pointed to by `prompt'. Takes both | |||||
* newlines and escaped-newlines into account. | |||||
*/ | |||||
unsigned int | |||||
dialog_prompt_numlines(const char *prompt, uint8_t nlstate) | |||||
{ | |||||
uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */ | |||||
const char *cp = prompt; | |||||
unsigned int nlines = 1; | |||||
if (prompt == NULL || *prompt == '\0') return 0; | |||||
while (*cp != '\0') | |||||
{ | |||||
if (use_dialog) { | |||||
if (strncmp(cp, "\\n", 2) == 0) { | |||||
cp++; | |||||
nlines++; | |||||
nls = 1; /* See declaration comment */ | |||||
} else if (*cp == '\n') { | |||||
if (!nls) nlines++; | |||||
nls = 0; /* See declaration comment */ | |||||
} | |||||
} else if (use_libdialog) { | |||||
if (*cp == '\n') nlines++; | |||||
} else if (strncmp(cp, "\\n", 2) == 0) { | |||||
cp++; | |||||
nlines++; | |||||
} | |||||
cp++; | |||||
} | |||||
return nlines; | |||||
} | |||||
/* | |||||
* Returns the length in bytes of the longest line in buffer pointed to by | |||||
* `prompt'. Takes newlines and escaped newlines into account. Also discounts | |||||
* dialog(1) color escape codes if enabled (via `use_color' global). | |||||
*/ | |||||
unsigned int | |||||
dialog_prompt_longestline(const char *prompt, uint8_t nlstate) | |||||
{ | |||||
uint8_t backslash = 0; | |||||
uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */ | |||||
int n = 0, longest = 0; | |||||
const char *p = prompt; | |||||
/* `prompt' parameter is required */ | |||||
if (prompt == NULL) return 0; | |||||
if (*prompt == '\0') return 0; /* shortcut */ | |||||
/* Loop until the end of the string */ | |||||
while (*p != '\0') | |||||
{ | |||||
/* dialog(1) and dialog(3) will render literal newlines */ | |||||
if (use_dialog || use_libdialog) { | |||||
if (*p == '\n') { | |||||
if (!use_libdialog && nls) n++; | |||||
else { | |||||
if (n > longest) longest = n; | |||||
n = 0; | |||||
} | |||||
nls = 0; /* See declaration comment */ | |||||
p++; | |||||
continue; | |||||
} | |||||
} | |||||
/* Check for backslash character */ | |||||
if (*p == '\\') { | |||||
/* If second backslash, count as a single-char */ | |||||
if ((backslash ^= 1) == 0) n++; | |||||
} else if (backslash) { | |||||
if (*p == 'n' && !use_libdialog) { /* new line */ | |||||
/* NB: dialog(3) ignores escaped newlines */ | |||||
nls = 1; /* See declaration comment */ | |||||
if (n > longest) longest = n; | |||||
n = 0; | |||||
} else if (use_color && *p == 'Z') { | |||||
if (*++p != '\0') p++; | |||||
backslash = 0; | |||||
continue; | |||||
} else /* [X]dialog(1)/dialog(3) only expand those */ | |||||
n += 2; | |||||
backslash = 0; | |||||
} else | |||||
n++; | |||||
p++; | |||||
} | |||||
if (n > longest) longest = n; | |||||
return longest; | |||||
} | |||||
/* | |||||
* Returns a pointer to the last line in buffer pointed to by `prompt'. Takes | |||||
* both newlines (if using dialog(1) versus Xdialog(1)) and escaped newlines | |||||
* into account. If no newlines (escaped or otherwise) appear in the buffer, | |||||
* `prompt' is returned. If passed a NULL pointer, returns NULL. | |||||
*/ | |||||
char * | |||||
dialog_prompt_lastline(char *prompt, uint8_t nlstate) | |||||
{ | |||||
char *lastline, *p; | |||||
uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */ | |||||
if (prompt == NULL) return NULL; | |||||
if (*prompt == '\0') return prompt; /* shortcut */ | |||||
lastline = p = prompt; | |||||
while (*p != '\0') | |||||
{ | |||||
/* dialog(1) and dialog(3) will render literal newlines */ | |||||
if (use_dialog || use_libdialog) { | |||||
if (*p == '\n') { | |||||
if (use_libdialog || !nls) lastline = p + 1; | |||||
nls = 0; /* See declaration comment */ | |||||
} | |||||
} | |||||
/* dialog(3) does not expand escaped newlines */ | |||||
if (use_libdialog) { | |||||
p++; | |||||
continue; | |||||
} | |||||
if (*p == '\\' && *(p + 1) != '\0' && *(++p) == 'n') { | |||||
nls = 1; /* See declaration comment */ | |||||
lastline = p + 1; | |||||
} | |||||
p++; | |||||
} | |||||
return lastline; | |||||
} | |||||
/* | |||||
* Returns the number of extra lines generated by wrapping the text in buffer | |||||
* pointed to by `prompt' within `ncols' columns (for prompts, this should be | |||||
* dwidth - 4). Also discounts dialog(1) color escape codes if enabled (via | |||||
* `use_color' global). | |||||
*/ | |||||
int | |||||
dialog_prompt_wrappedlines(char *prompt, int ncols, uint8_t nlstate) | |||||
{ | |||||
uint8_t backslash = 0; | |||||
uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */ | |||||
int wlines = 0, n = 0; | |||||
char *p = prompt; | |||||
/* `prompt' parameter is required */ | |||||
if (p == NULL) return 0; | |||||
if (*p == '\0') return 0; /* shortcut */ | |||||
/* Loop until the end of the string */ | |||||
while (*p != '\0') | |||||
{ | |||||
/* dialog(1) and dialog(3) will render literal newlines */ | |||||
if (use_dialog || use_libdialog) { | |||||
if (*p == '\n') { | |||||
if (use_dialog || !nls) n = 0; | |||||
nls = 0; /* See declaration comment */ | |||||
} | |||||
} | |||||
/* Check for backslash character */ | |||||
if (*p == '\\') { | |||||
/* If second backslash, count as a single-char */ | |||||
if ((backslash ^= 1) == 0) n++; | |||||
} else if (backslash) { | |||||
if (*p == 'n' && !use_libdialog) { /* new line */ | |||||
/* NB: dialog(3) ignores escaped newlines */ | |||||
nls = 1; /* See declaration comment */ | |||||
n = 0; | |||||
} else if (use_color && *p == 'Z') { | |||||
if (*++p != '\0') p++; | |||||
backslash = 0; | |||||
continue; | |||||
} else /* [X]dialog(1)/dialog(3) only expand those */ | |||||
n += 2; | |||||
backslash = 0; | |||||
} else | |||||
n++; | |||||
/* Did we pass the width barrier? */ | |||||
if (n > ncols) | |||||
{ | |||||
/* | |||||
* Work backward to find the first whitespace on-which | |||||
* dialog(1) will wrap the line (but don't go before | |||||
* the start of this line). | |||||
*/ | |||||
char *cp = p; | |||||
while (n > 1 && !isspace(*cp)) { | |||||
cp--; | |||||
n--; | |||||
} | |||||
if (n > 0 && isspace(*cp)) p = cp; | |||||
wlines++; | |||||
n = 1; | |||||
} | |||||
p++; | |||||
} | |||||
return wlines; | |||||
} | |||||
/* | |||||
* Returns zero if the buffer pointed to by `prompt' contains an escaped | |||||
* newline but only if appearing after any/all literal newlines. This is | |||||
* specific to dialog(1) and does not apply to Xdialog(1). | |||||
* | |||||
* As an attempt to make shell scripts easier to read, dialog(1) will "eat" | |||||
* the first literal newline after an escaped newline. This however has a bug | |||||
* in its implementation in that rather than allowing `\\n\n' to be treated | |||||
* similar to `\\n' or `\n', dialog(1) expands the `\\n' and then translates | |||||
* the following literal newline (with or without characters between [!]) into | |||||
* a single space. | |||||
* | |||||
* If you want to be compatible with Xdialog(1), it is suggested that you not | |||||
* use literal newlines (they aren't supported); but if you have to use them, | |||||
* go right ahead. But be forewarned... if you set $DIALOG in your environment | |||||
* to something other than `cdialog' (our current dialog(1)), then it should | |||||
* do the same thing w/respect to how to handle a literal newline after an | |||||
* escaped newline (you could do no wrong by translating every literal newline | |||||
* into a space but only when you've previously encountered an escaped one; | |||||
* this is what dialog(1) is doing). | |||||
* | |||||
* The ``newline state'' (or nlstate for short; as I'm calling it) is helpful | |||||
* if you plan to combine multiple strings into a single prompt text. In lead- | |||||
* up to this procedure, a common task is to calculate and utilize the widths | |||||
* and heights of each piece of prompt text to later be combined. However, if | |||||
* (for example) the first string ends in a positive newline state (has an | |||||
* escaped newline without trailing literal), the first literal newline in the | |||||
* second string will be mangled. | |||||
* | |||||
* The return value of this function should be used as the `nlstate' argument | |||||
* to dialog_*() functions that require it to allow accurate calculations in | |||||
* the event such information is needed. | |||||
*/ | |||||
uint8_t | |||||
dialog_prompt_nlstate(const char *prompt) | |||||
{ | |||||
const char *cp; | |||||
if (prompt == NULL) return 0; | |||||
/* | |||||
* Work our way backward from the end of the string for efficiency. | |||||
*/ | |||||
cp = prompt + strlen(prompt); | |||||
while (--cp >= prompt) | |||||
{ | |||||
/* | |||||
* If we get to a literal newline first, this prompt ends in a | |||||
* clean state for rendering with dialog(1). Otherwise, if we | |||||
* get to an escaped newline first, this prompt ends in an un- | |||||
* clean state (following literal will be mangled; see above). | |||||
*/ | |||||
if (*cp == '\n') | |||||
return 0; | |||||
else if (*cp == 'n' && --cp > prompt && *cp == '\\') | |||||
return 1; | |||||
} | |||||
return 0; /* no newlines (escaped or otherwise) */ | |||||
} |