Home | History | Annotate | Line # | Download | only in common
      1 /*	$NetBSD: msg.c,v 1.4 2017/11/06 03:03:54 rin Exp $ */
      2 /*-
      3  * Copyright (c) 1991, 1993, 1994
      4  *	The Regents of the University of California.  All rights reserved.
      5  * Copyright (c) 1991, 1993, 1994, 1995, 1996
      6  *	Keith Bostic.  All rights reserved.
      7  *
      8  * See the LICENSE file for redistribution information.
      9  */
     10 
     11 #include "config.h"
     12 
     13 #include <sys/cdefs.h>
     14 #if 0
     15 #ifndef lint
     16 static const char sccsid[] = "Id: msg.c,v 10.61 2003/07/18 23:17:30 skimo Exp  (Berkeley) Date: 2003/07/18 23:17:30 ";
     17 #endif /* not lint */
     18 #else
     19 __RCSID("$NetBSD: msg.c,v 1.4 2017/11/06 03:03:54 rin Exp $");
     20 #endif
     21 
     22 #include <sys/param.h>
     23 #include <sys/types.h>		/* XXX: param.h may not have included types.h */
     24 #include <sys/queue.h>
     25 #include <sys/stat.h>
     26 #include <sys/time.h>
     27 
     28 #include <bitstring.h>
     29 #include <ctype.h>
     30 #include <errno.h>
     31 #include <fcntl.h>
     32 #include <limits.h>
     33 #include <stdio.h>
     34 #include <stdlib.h>
     35 #include <string.h>
     36 #include <unistd.h>
     37 
     38 #ifdef __STDC__
     39 #include <stdarg.h>
     40 #else
     41 #include <varargs.h>
     42 #endif
     43 
     44 #include "common.h"
     45 #include "dbinternal.h"
     46 #include "../vi/vi.h"
     47 
     48 /*
     49  * msgq --
     50  *	Display a message.
     51  *
     52  * PUBLIC: void msgq __P((SCR *, mtype_t, const char *, ...));
     53  */
     54 void
     55 #ifdef __STDC__
     56 msgq(SCR *sp, mtype_t mt, const char *fmt, ...)
     57 #else
     58 msgq(sp, mt, fmt, va_alist)
     59 	SCR *sp;
     60 	mtype_t mt;
     61         const char *fmt;
     62         va_dcl
     63 #endif
     64 {
     65 #ifndef NL_ARGMAX
     66 #define	__NL_ARGMAX	20		/* Set to 9 by System V. */
     67 	struct {
     68 		const char *str;	/* String pointer. */
     69 		size_t	 arg;		/* Argument number. */
     70 		size_t	 prefix;	/* Prefix string length. */
     71 		size_t	 skip;		/* Skipped string length. */
     72 		size_t	 suffix;	/* Suffix string length. */
     73 	} str[__NL_ARGMAX];
     74 #endif
     75 	static int reenter;		/* STATIC: Re-entrancy check. */
     76 	GS *gp;
     77 	WIN *wp = NULL;
     78 	size_t blen, len, mlen, nlen;
     79 	const char *p;
     80 	char *bp, *mp;
     81         va_list ap;
     82 #ifndef NL_ARGMAX
     83 	int ch;
     84 	char *rbp, *s_rbp;
     85 	const char *t, *u;
     86 	size_t cnt1, cnt2, soff;
     87 #endif
     88 
     89 	/*
     90 	 * !!!
     91 	 * It's possible to enter msg when there's no screen to hold the
     92 	 * message.  If sp is NULL, ignore the special cases and put the
     93 	 * message out to stderr.
     94 	 */
     95 	if (sp == NULL) {
     96 		gp = NULL;
     97 		if (mt == M_BERR)
     98 			mt = M_ERR;
     99 		else if (mt == M_VINFO)
    100 			mt = M_INFO;
    101 	} else {
    102 		gp = sp->gp;
    103 		wp = sp->wp;
    104 		switch (mt) {
    105 		case M_BERR:
    106 			if (F_ISSET(sp, SC_VI) && !O_ISSET(sp, O_VERBOSE)) {
    107 				F_SET(gp, G_BELLSCHED);
    108 				return;
    109 			}
    110 			mt = M_ERR;
    111 			break;
    112 		case M_VINFO:
    113 			if (!O_ISSET(sp, O_VERBOSE))
    114 				return;
    115 			mt = M_INFO;
    116 			/* FALLTHROUGH */
    117 		case M_INFO:
    118 			if (F_ISSET(sp, SC_EX_SILENT))
    119 				return;
    120 			break;
    121 		case M_ERR:
    122 		case M_SYSERR:
    123 		case M_DBERR:
    124 			break;
    125 		default:
    126 			abort();
    127 		}
    128 	}
    129 
    130 	/*
    131 	 * It's possible to reenter msg when it allocates space.  We're
    132 	 * probably dead anyway, but there's no reason to drop core.
    133 	 *
    134 	 * XXX
    135 	 * Yes, there's a race, but it should only be two instructions.
    136 	 */
    137 	if (reenter++)
    138 		return;
    139 
    140 	/* Get space for the message. */
    141 	nlen = 1024;
    142 	if (0) {
    143 retry:		FREE_SPACE(sp, bp, blen);
    144 		nlen *= 2;
    145 	}
    146 	bp = NULL;
    147 	blen = 0;
    148 	GET_SPACE_GOTOC(sp, bp, blen, nlen);
    149 
    150 	/*
    151 	 * Error prefix.
    152 	 *
    153 	 * mp:	 pointer to the current next character to be written
    154 	 * mlen: length of the already written characters
    155 	 * blen: total length of the buffer
    156 	 */
    157 #define	REM	(blen - mlen)
    158 	mp = bp;
    159 	mlen = 0;
    160 	if (mt == M_SYSERR || mt == M_DBERR) {
    161 		p = msg_cat(sp, "020|Error: ", &len);
    162 		if (REM < len)
    163 			goto retry;
    164 		memcpy(mp, p, len);
    165 		mp += len;
    166 		mlen += len;
    167 	}
    168 
    169 	/*
    170 	 * If we're running an ex command that the user didn't enter, display
    171 	 * the file name and line number prefix.
    172 	 */
    173 	if ((mt == M_ERR || mt == M_SYSERR) &&
    174 	    sp != NULL && wp != NULL && wp->if_name != NULL) {
    175 		for (p = wp->if_name; *p != '\0'; ++p) {
    176 			len = snprintf(mp, REM, "%s", KEY_NAME(sp, *p));
    177 			mp += len;
    178 			if ((mlen += len) > blen)
    179 				goto retry;
    180 		}
    181 		len = snprintf(mp, REM, ", %d: ", wp->if_lno);
    182 		mp += len;
    183 		if ((mlen += len) > blen)
    184 			goto retry;
    185 	}
    186 
    187 	/* If nothing to format, we're done. */
    188 	if (fmt == NULL)
    189 		goto nofmt;
    190 	fmt = msg_cat(sp, fmt, NULL);
    191 
    192 #ifndef NL_ARGMAX
    193 	/*
    194 	 * Nvi should run on machines that don't support the numbered argument
    195 	 * specifications (%[digit]*$).  We do this by reformatting the string
    196 	 * so that we can hand it to vsprintf(3) and it will use the arguments
    197 	 * in the right order.  When vsprintf returns, we put the string back
    198 	 * into the right order.  It's undefined, according to SVID III, to mix
    199 	 * numbered argument specifications with the standard style arguments,
    200 	 * so this should be safe.
    201 	 *
    202 	 * In addition, we also need a character that is known to not occur in
    203 	 * any vi message, for separating the parts of the string.  As callers
    204 	 * of msgq are responsible for making sure that all the non-printable
    205 	 * characters are formatted for printing before calling msgq, we use a
    206 	 * random non-printable character selected at terminal initialization
    207 	 * time.  This code isn't fast by any means, but as messages should be
    208 	 * relatively short and normally have only a few arguments, it won't be
    209 	 * too bad.  Regardless, nobody has come up with any other solution.
    210 	 *
    211 	 * The result of this loop is an array of pointers into the message
    212 	 * string, with associated lengths and argument numbers.  The array
    213 	 * is in the "correct" order, and the arg field contains the argument
    214 	 * order.
    215 	 */
    216 	for (p = fmt, soff = 0; soff < __NL_ARGMAX;) {
    217 		for (t = p; *p != '\0' && *p != '%'; ++p);
    218 		if (*p == '\0')
    219 			break;
    220 		++p;
    221 		if (!isdigit((unsigned char)*p)) {
    222 			if (*p == '%')
    223 				++p;
    224 			continue;
    225 		}
    226 		for (u = p; *++p != '\0' && isdigit((unsigned char)*p););
    227 		if (*p != '$')
    228 			continue;
    229 
    230 		/* Up to, and including the % character. */
    231 		str[soff].str = t;
    232 		str[soff].prefix = u - t;
    233 
    234 		/* Up to, and including the $ character. */
    235 		str[soff].arg = atoi(u);
    236 		str[soff].skip = (p - u) + 1;
    237 		if (str[soff].arg >= __NL_ARGMAX)
    238 			goto ret;
    239 
    240 		/* Up to, and including the conversion character. */
    241 		for (u = p; (ch = (unsigned char)*++p) != '\0';)
    242 			if (isalpha(ch) &&
    243 			    strchr("diouxXfeEgGcspn", ch) != NULL)
    244 				break;
    245 		str[soff].suffix = p - u;
    246 		if (ch != '\0')
    247 			++p;
    248 		++soff;
    249 	}
    250 
    251 	/* If no magic strings, we're done. */
    252 	if (soff == 0)
    253 		goto format;
    254 
    255 	 /* Get space for the reordered strings. */
    256 	if ((rbp = malloc(nlen)) == NULL)
    257 		goto ret;
    258 	s_rbp = rbp;
    259 
    260 	/*
    261 	 * Reorder the strings into the message string based on argument
    262 	 * order.
    263 	 *
    264 	 * !!!
    265 	 * We ignore arguments that are out of order, i.e. if we don't find
    266 	 * an argument, we continue.  Assume (almost certainly incorrectly)
    267 	 * that whoever created the string knew what they were doing.
    268 	 *
    269 	 * !!!
    270 	 * Brute force "sort", but since we don't expect more than one or two
    271 	 * arguments in a string, the setup cost of a fast sort will be more
    272 	 * expensive than the loop.
    273 	 */
    274 	for (cnt1 = 1; cnt1 <= soff; ++cnt1)
    275 		for (cnt2 = 0; cnt2 < soff; ++cnt2)
    276 			if (cnt1 == str[cnt2].arg) {
    277 				memmove(s_rbp, str[cnt2].str, str[cnt2].prefix);
    278 				memmove(s_rbp + str[cnt2].prefix,
    279 				    str[cnt2].str + str[cnt2].prefix +
    280 				    str[cnt2].skip, str[cnt2].suffix);
    281 				s_rbp += str[cnt2].prefix + str[cnt2].suffix;
    282 				*s_rbp++ =
    283 				    gp == NULL ? DEFAULT_NOPRINT : gp->noprint;
    284 				break;
    285 			}
    286 	*s_rbp = '\0';
    287 	fmt = rbp;
    288 #endif
    289 
    290 #ifndef NL_ARGMAX
    291 format:	/* Format the arguments into the string. */
    292 #endif
    293 #ifdef __STDC__
    294         va_start(ap, fmt);
    295 #else
    296         va_start(ap);
    297 #endif
    298 	len = vsnprintf(mp, REM, fmt, ap);
    299 	va_end(ap);
    300 	if (len >= nlen)
    301 		goto retry;
    302 
    303 #ifndef NL_ARGMAX
    304 	if (soff == 0)
    305 		goto nofmt;
    306 
    307 	/*
    308 	 * Go through the resulting string, and, for each separator character
    309 	 * separated string, enter its new starting position and length in the
    310 	 * array.
    311 	 */
    312 	for (p = t = mp, cnt1 = 1,
    313 	    ch = gp == NULL ? DEFAULT_NOPRINT : gp->noprint; *p != '\0'; ++p)
    314 		if (*p == ch) {
    315 			for (cnt2 = 0; cnt2 < soff; ++cnt2)
    316 				if (str[cnt2].arg == cnt1)
    317 					break;
    318 			str[cnt2].str = t;
    319 			str[cnt2].prefix = p - t;
    320 			t = p + 1;
    321 			++cnt1;
    322 		}
    323 
    324 	/*
    325 	 * Reorder the strings once again, putting them back into the
    326 	 * message buffer.
    327 	 *
    328 	 * !!!
    329 	 * Note, the length of the message gets decremented once for
    330 	 * each substring, when we discard the separator character.
    331 	 */
    332 	for (s_rbp = rbp, cnt1 = 0; cnt1 < soff; ++cnt1) {
    333 		memmove(rbp, str[cnt1].str, str[cnt1].prefix);
    334 		rbp += str[cnt1].prefix;
    335 		--len;
    336 	}
    337 	memmove(mp, s_rbp, rbp - s_rbp);
    338 
    339 	/* Free the reordered string memory. */
    340 	free(s_rbp);
    341 #endif
    342 
    343 nofmt:	mp += len;
    344 	if ((mlen += len) > blen)
    345 		goto retry;
    346 	if (mt == M_SYSERR) {
    347 		len = snprintf(mp, REM, ": %s", strerror(errno));
    348 		mp += len;
    349 		if ((mlen += len) > blen)
    350 			goto retry;
    351 		mt = M_ERR;
    352 	}
    353 	if (mt == M_DBERR) {
    354 		len = snprintf(mp, REM, ": %s", db_strerror(sp->db_error));
    355 		mp += len;
    356 		if ((mlen += len) > blen)
    357 			goto retry;
    358 		mt = M_ERR;
    359 	}
    360 
    361 	/* Add trailing newline. */
    362 	if ((mlen += 1) > blen)
    363 		goto retry;
    364 	*mp = '\n';
    365 
    366 	if (sp != NULL && sp->ep != NULL)
    367 		(void)ex_fflush(sp);
    368 	if (wp != NULL)
    369 		wp->scr_msg(sp, mt, bp, mlen);
    370 	else
    371 		(void)fprintf(stderr, "%.*s", (int)mlen, bp);
    372 
    373 	/* Cleanup. */
    374 #ifndef NL_ARGMAX
    375 ret:
    376 #endif
    377 	FREE_SPACE(sp, bp, blen);
    378 alloc_err:
    379 	reenter = 0;
    380 }
    381 
    382 /*
    383  * msgq_str --
    384  *	Display a message with an embedded string.
    385  *
    386  * PUBLIC: void msgq_wstr __P((SCR *, mtype_t, const CHAR_T *, const char *));
    387  */
    388 void
    389 msgq_wstr(SCR *sp, mtype_t mtype, const CHAR_T *str, const char *fmt)
    390 {
    391 	size_t nlen;
    392 	const char *nstr;
    393 
    394 	if (str == NULL) {
    395 		msgq(sp, mtype, "%s", fmt);
    396 		return;
    397 	}
    398 	INT2CHAR(sp, str, STRLEN(str) + 1, nstr, nlen);
    399 	msgq_str(sp, mtype, nstr, fmt);
    400 }
    401 
    402 /*
    403  * msgq_str --
    404  *	Display a message with an embedded string.
    405  *
    406  * PUBLIC: void msgq_str __P((SCR *, mtype_t, const char *, const char *));
    407  */
    408 void
    409 msgq_str(SCR *sp, mtype_t mtype, const char *str, const char *fmt)
    410 {
    411 	int nf, sv_errno;
    412 	char *p;
    413 
    414 	if (str == NULL) {
    415 		msgq(sp, mtype, "%s", fmt);
    416 		return;
    417 	}
    418 
    419 	sv_errno = errno;
    420 	p = msg_print(sp, str, &nf);
    421 	errno = sv_errno;
    422 	msgq(sp, mtype, fmt, p);
    423 	if (nf)
    424 		FREE_SPACE(sp, p, 0);
    425 }
    426 
    427 /*
    428  * mod_rpt --
    429  *	Report on the lines that changed.
    430  *
    431  * !!!
    432  * Historic vi documentation (USD:15-8) claimed that "The editor will also
    433  * always tell you when a change you make affects text which you cannot see."
    434  * This wasn't true -- edit a large file and do "100d|1".  We don't implement
    435  * this semantic since it requires tracking each line that changes during a
    436  * command instead of just keeping count.
    437  *
    438  * Line counts weren't right in historic vi, either.  For example, given the
    439  * file:
    440  *	abc
    441  *	def
    442  * the command 2d}, from the 'b' would report that two lines were deleted,
    443  * not one.
    444  *
    445  * PUBLIC: void mod_rpt __P((SCR *));
    446  */
    447 void
    448 mod_rpt(SCR *sp)
    449 {
    450 	static const char * const action[] = {
    451 		"293|added",
    452 		"294|changed",
    453 		"295|deleted",
    454 		"296|joined",
    455 		"297|moved",
    456 		"298|shifted",
    457 		"299|yanked",
    458 	};
    459 	static const char * const lines[] = {
    460 		"300|line",
    461 		"301|lines",
    462 	};
    463 	db_recno_t total;
    464 	u_long rptval;
    465 	int first;
    466 	size_t cnt, blen, len, tlen;
    467 	const char *t;
    468 	const char * const *ap;
    469 	char *bp, *p;
    470 
    471 	/* Change reports are turned off in batch mode. */
    472 	if (F_ISSET(sp, SC_EX_SILENT))
    473 		return;
    474 
    475 	/* Reset changing line number. */
    476 	sp->rptlchange = OOBLNO;
    477 
    478 	/*
    479 	 * Don't build a message if not enough changed.
    480 	 *
    481 	 * !!!
    482 	 * And now, a vi clone test.  Historically, vi reported if the number
    483 	 * of changed lines was > than the value, not >=, unless it was a yank
    484 	 * command, which used >=.  No lie.  Furthermore, an action was never
    485 	 * reported for a single line action.  This is consistent for actions
    486 	 * other than yank, but yank didn't report single line actions even if
    487 	 * the report edit option was set to 1.  In addition, setting report to
    488 	 * 0 in the 4BSD historic vi was equivalent to setting it to 1, for an
    489 	 * unknown reason (this bug was fixed in System III/V at some point).
    490 	 * I got complaints, so nvi conforms to System III/V historic practice
    491 	 * except that we report a yank of 1 line if report is set to 1.
    492 	 */
    493 #define	ARSIZE(a)	sizeof(a) / sizeof (*a)
    494 #define	MAXNUM		25
    495 	rptval = O_VAL(sp, O_REPORT);
    496 	for (cnt = 0, total = 0; cnt < ARSIZE(action); ++cnt)
    497 		total += sp->rptlines[cnt];
    498 	if (total == 0)
    499 		return;
    500 	if (total <= rptval && sp->rptlines[L_YANKED] < rptval) {
    501 		for (cnt = 0; cnt < ARSIZE(action); ++cnt)
    502 			sp->rptlines[cnt] = 0;
    503 		return;
    504 	}
    505 
    506 	/* Build and display the message. */
    507 	GET_SPACE_GOTOC(sp, bp, blen, sizeof(action) * MAXNUM + 1);
    508 	for (p = bp, first = 1, tlen = 0,
    509 	    ap = action, cnt = 0; cnt < ARSIZE(action); ++ap, ++cnt)
    510 		if (sp->rptlines[cnt] != 0) {
    511 			if (first)
    512 				first = 0;
    513 			else {
    514 				*p++ = ';';
    515 				*p++ = ' ';
    516 				tlen += 2;
    517 			}
    518 			len = snprintf(p, MAXNUM, "%lu ",
    519 				(unsigned long) sp->rptlines[cnt]);
    520 			p += len;
    521 			tlen += len;
    522 			t = msg_cat(sp,
    523 			    lines[sp->rptlines[cnt] == 1 ? 0 : 1], &len);
    524 			memcpy(p, t, len);
    525 			p += len;
    526 			tlen += len;
    527 			*p++ = ' ';
    528 			++tlen;
    529 			t = msg_cat(sp, *ap, &len);
    530 			memcpy(p, t, len);
    531 			p += len;
    532 			tlen += len;
    533 			sp->rptlines[cnt] = 0;
    534 		}
    535 
    536 	/* Add trailing newline. */
    537 	*p = '\n';
    538 	++tlen;
    539 
    540 	(void)ex_fflush(sp);
    541 	sp->wp->scr_msg(sp, M_INFO, bp, tlen);
    542 
    543 	FREE_SPACE(sp, bp, blen);
    544 alloc_err:
    545 	return;
    546 
    547 #undef ARSIZE
    548 #undef MAXNUM
    549 }
    550 
    551 /*
    552  * msgq_status --
    553  *	Report on the file's status.
    554  *
    555  * PUBLIC: void msgq_status __P((SCR *, db_recno_t, u_int));
    556  */
    557 void
    558 msgq_status(SCR *sp, db_recno_t lno, u_int flags)
    559 {
    560 	db_recno_t last;
    561 	size_t blen, len;
    562 	int cnt, needsep;
    563 	const char *t;
    564 	char **ap, *bp, *np, *p, *s;
    565 
    566 	/* Get sufficient memory. */
    567 	len = strlen(sp->frp->name);
    568 	GET_SPACE_GOTOC(sp, bp, blen, len * MAX_CHARACTER_COLUMNS + 128);
    569 	p = bp;
    570 
    571 	/* Copy in the filename. */
    572 	for (p = bp, t = sp->frp->name; *t != '\0'; ++t) {
    573 		len = KEY_LEN(sp, *t);
    574 		memcpy(p, KEY_NAME(sp, *t), len);
    575 		p += len;
    576 	}
    577 	np = p;
    578 	*p++ = ':';
    579 	*p++ = ' ';
    580 
    581 	/* Copy in the argument count. */
    582 	if (F_ISSET(sp, SC_STATUS_CNT) && sp->argv != NULL) {
    583 		for (cnt = 0, ap = sp->argv; *ap != NULL; ++ap, ++cnt);
    584 		if (cnt > 1) {
    585 			(void)sprintf(p,
    586 			    msg_cat(sp, "317|%d files to edit", NULL), cnt);
    587 			p += strlen(p);
    588 			*p++ = ':';
    589 			*p++ = ' ';
    590 		}
    591 		F_CLR(sp, SC_STATUS_CNT);
    592 	}
    593 
    594 	/*
    595 	 * See nvi/exf.c:file_init() for a description of how and when the
    596 	 * read-only bit is set.
    597 	 *
    598 	 * !!!
    599 	 * The historic display for "name changed" was "[Not edited]".
    600 	 */
    601 	needsep = 0;
    602 	if (F_ISSET(sp->frp, FR_NEWFILE)) {
    603 		F_CLR(sp->frp, FR_NEWFILE);
    604 		t = msg_cat(sp, "021|new file", &len);
    605 		memcpy(p, t, len);
    606 		p += len;
    607 		needsep = 1;
    608 	} else {
    609 		if (F_ISSET(sp->frp, FR_NAMECHANGE)) {
    610 			t = msg_cat(sp, "022|name changed", &len);
    611 			memcpy(p, t, len);
    612 			p += len;
    613 			needsep = 1;
    614 		}
    615 		if (needsep) {
    616 			*p++ = ',';
    617 			*p++ = ' ';
    618 		}
    619 		if (F_ISSET(sp->ep, F_MODIFIED))
    620 			t = msg_cat(sp, "023|modified", &len);
    621 		else
    622 			t = msg_cat(sp, "024|unmodified", &len);
    623 		memcpy(p, t, len);
    624 		p += len;
    625 		needsep = 1;
    626 	}
    627 	if (F_ISSET(sp->frp, FR_UNLOCKED)) {
    628 		if (needsep) {
    629 			*p++ = ',';
    630 			*p++ = ' ';
    631 		}
    632 		t = msg_cat(sp, "025|UNLOCKED", &len);
    633 		memcpy(p, t, len);
    634 		p += len;
    635 		needsep = 1;
    636 	}
    637 	if (O_ISSET(sp, O_READONLY)) {
    638 		if (needsep) {
    639 			*p++ = ',';
    640 			*p++ = ' ';
    641 		}
    642 		t = msg_cat(sp, "026|readonly", &len);
    643 		memcpy(p, t, len);
    644 		p += len;
    645 		needsep = 1;
    646 	}
    647 	if (needsep) {
    648 		*p++ = ':';
    649 		*p++ = ' ';
    650 	}
    651 	if (LF_ISSET(MSTAT_SHOWLAST)) {
    652 		if (db_last(sp, &last))
    653 			return;
    654 		if (last == 0) {
    655 			t = msg_cat(sp, "028|empty file", &len);
    656 			memcpy(p, t, len);
    657 			p += len;
    658 		} else {
    659 			t = msg_cat(sp, "027|line %lu of %lu [%ld%%]", &len);
    660 			(void)sprintf(p, t, lno, last,
    661 			    (long)((lno * 100) / last));
    662 			p += strlen(p);
    663 		}
    664 	} else {
    665 		t = msg_cat(sp, "029|line %lu", &len);
    666 		(void)sprintf(p, t, lno);
    667 		p += strlen(p);
    668 	}
    669 #ifdef DEBUG
    670 	(void)sprintf(p, " (pid %lu)", (u_long)getpid());
    671 	p += strlen(p);
    672 #endif
    673 	*p++ = '\n';
    674 	len = p - bp;
    675 
    676 	/*
    677 	 * There's a nasty problem with long path names.  Cscope and tags files
    678 	 * can result in long paths and vi will request a continuation key from
    679 	 * the user as soon as it starts the screen.  Unfortunately, the user
    680 	 * has already typed ahead, and chaos results.  If we assume that the
    681 	 * characters in the filenames and informational messages only take a
    682 	 * single screen column each, we can trim the filename.
    683 	 *
    684 	 * XXX
    685 	 * Status lines get put up at fairly awkward times.  For example, when
    686 	 * you do a filter read (e.g., :read ! echo foo) in the top screen of a
    687 	 * split screen, we have to repaint the status lines for all the screens
    688 	 * below the top screen.  We don't want users having to enter continue
    689 	 * characters for those screens.  Make it really hard to screw this up.
    690 	 */
    691 	s = bp;
    692 	if (LF_ISSET(MSTAT_TRUNCATE) && len > sp->cols) {
    693 		for (; s < np && (*s != '/' || (size_t)(p - s) > sp->cols - 3); ++s);
    694 		if (s == np) {
    695 			s = p - (sp->cols - 5);
    696 			*--s = ' ';
    697 		}
    698 		*--s = '.';
    699 		*--s = '.';
    700 		*--s = '.';
    701 		len = p - s;
    702 	}
    703 
    704 	/* Flush any waiting ex messages. */
    705 	(void)ex_fflush(sp);
    706 
    707 	sp->wp->scr_msg(sp, M_INFO, s, len);
    708 
    709 	FREE_SPACE(sp, bp, blen);
    710 alloc_err:
    711 	return;
    712 }
    713 
    714 /*
    715  * msg_open --
    716  *	Open the message catalogs.
    717  *
    718  * PUBLIC: int msg_open __P((SCR *, const char *));
    719  */
    720 int
    721 msg_open(SCR *sp, const char *file)
    722 {
    723 	/*
    724 	 * !!!
    725 	 * Assume that the first file opened is the system default, and that
    726 	 * all subsequent ones user defined.  Only display error messages
    727 	 * if we can't open the user defined ones -- it's useful to know if
    728 	 * the system one wasn't there, but if nvi is being shipped with an
    729 	 * installed system, the file will be there, if it's not, then the
    730 	 * message will be repeated every time nvi is started up.
    731 	 */
    732 	static int first = 1;
    733 	DB *db;
    734 	DBT data, key;
    735 	db_recno_t msgno;
    736 	char buf[MAXPATHLEN];
    737 	const char *p, *t;
    738 
    739 	if ((p = strrchr(file, '/')) != NULL && p[1] == '\0' &&
    740 	    (((t = getenv("LC_MESSAGES")) != NULL && t[0] != '\0') ||
    741 	    ((t = getenv("LANG")) != NULL && t[0] != '\0'))) {
    742 		(void)snprintf(buf, sizeof(buf), "%s%s", file, t);
    743 		p = buf;
    744 	} else
    745 		p = file;
    746 	if (db_msg_open(sp, p, &db)) {
    747 		if (first) {
    748 			first = 0;
    749 			return (1);
    750 		}
    751 		msgq_str(sp, M_DBERR, p, "%s");
    752 		return (1);
    753 	}
    754 
    755 	/*
    756 	 * Test record 1 for the magic string.  The msgq call is here so
    757 	 * the message catalog build finds it.
    758 	 */
    759 #define	VMC	"VI_MESSAGE_CATALOG"
    760 	memset(&key, 0, sizeof(key));
    761 	key.data = &msgno;
    762 	key.size = sizeof(db_recno_t);
    763 	memset(&data, 0, sizeof(data));
    764 	msgno = 1;
    765 	if ((sp->db_error = db_get_low(db, &key, &data, 0)) != 0 ||
    766 	    data.size != sizeof(VMC) - 1 ||
    767 	    memcmp(data.data, VMC, sizeof(VMC) - 1)) {
    768 		(void)db_close(db);
    769 		if (first) {
    770 			first = 0;
    771 			return (1);
    772 		}
    773 		msgq_str(sp, M_DBERR, p,
    774 		    "030|The file %s is not a message catalog");
    775 		return (1);
    776 	}
    777 	first = 0;
    778 
    779 	if (sp->gp->msg != NULL)
    780 		(void)db_close(sp->gp->msg);
    781 	sp->gp->msg = db;
    782 	return (0);
    783 }
    784 
    785 /*
    786  * msg_close --
    787  *	Close the message catalogs.
    788  *
    789  * PUBLIC: void msg_close __P((GS *));
    790  */
    791 void
    792 msg_close(GS *gp)
    793 {
    794 	if (gp->msg != NULL) {
    795 		(void)db_close(gp->msg);
    796 		gp->msg = NULL;
    797 	}
    798 }
    799 
    800 /*
    801  * msg_cont --
    802  *	Return common continuation messages.
    803  *
    804  * PUBLIC: const char *msg_cmsg __P((SCR *, cmsg_t, size_t *));
    805  */
    806 const char *
    807 msg_cmsg(SCR *sp, cmsg_t which, size_t *lenp)
    808 {
    809 	switch (which) {
    810 	case CMSG_CONF:
    811 		return (msg_cat(sp, "268|confirm? [ynq]", lenp));
    812 	case CMSG_CONT:
    813 		return (msg_cat(sp, "269|Press any key to continue: ", lenp));
    814 	case CMSG_CONT_EX:
    815 		return (msg_cat(sp,
    816 	    "270|Press any key to continue [: to enter more ex commands]: ",
    817 		    lenp));
    818 	case CMSG_CONT_R:
    819 		return (msg_cat(sp, "161|Press Enter to continue: ", lenp));
    820 	case CMSG_CONT_S:
    821 		return (msg_cat(sp, "275| cont?", lenp));
    822 	case CMSG_CONT_Q:
    823 		return (msg_cat(sp,
    824 		    "271|Press any key to continue [q to quit]: ", lenp));
    825 	default:
    826 		abort();
    827 	}
    828 	/* NOTREACHED */
    829 }
    830 
    831 /*
    832  * msg_cat --
    833  *	Return a single message from the catalog, plus its length.
    834  *
    835  * !!!
    836  * Only a single catalog message can be accessed at a time, if multiple
    837  * ones are needed, they must be copied into local memory.
    838  *
    839  * PUBLIC: const char *msg_cat __P((SCR *, const char *, size_t *));
    840  */
    841 const char *
    842 msg_cat(SCR *sp, const char *str, size_t *lenp)
    843 {
    844 	GS *gp;
    845 	DBT data, key;
    846 	db_recno_t msgno;
    847 
    848 	/*
    849 	 * If it's not a catalog message, i.e. has doesn't have a leading
    850 	 * number and '|' symbol, we're done.
    851 	 */
    852 	if (isdigit((unsigned char)str[0]) &&
    853 	    isdigit((unsigned char)str[1]) && isdigit((unsigned char)str[2]) &&
    854 	    str[3] == '|') {
    855 		memset(&key, 0, sizeof(key));
    856 		key.data = &msgno;
    857 		key.size = sizeof(db_recno_t);
    858 		memset(&data, 0, sizeof(data));
    859 		msgno = atoi(str);
    860 
    861 		/*
    862 		 * XXX
    863 		 * Really sleazy hack -- we put an extra character on the
    864 		 * end of the format string, and then we change it to be
    865 		 * the nul termination of the string.  There ought to be
    866 		 * a better way.  Once we can allocate multiple temporary
    867 		 * memory buffers, maybe we can use one of them instead.
    868 		 */
    869 		gp = sp == NULL ? NULL : sp->gp;
    870 		if (gp != NULL && gp->msg != NULL &&
    871 		    db_get_low(gp->msg, &key, &data, 0) == 0 &&
    872 		    data.size != 0) {
    873 			if (lenp != NULL)
    874 				*lenp = data.size - 1;
    875 			((char *)data.data)[data.size - 1] = '\0';
    876 			return (data.data);
    877 		}
    878 		str = &str[4];
    879 	}
    880 	if (lenp != NULL)
    881 		*lenp = strlen(str);
    882 	return (str);
    883 }
    884 
    885 /*
    886  * msg_print --
    887  *	Return a printable version of a string, in allocated memory.
    888  *
    889  * PUBLIC: char *msg_print __P((SCR *, const char *, int *));
    890  */
    891 char *
    892 msg_print(SCR *sp, const char *s, int *needfree)
    893 {
    894 	size_t blen, nlen;
    895 	const char *cp;
    896 	char *bp, *ep, *p;
    897 	unsigned char *t;
    898 
    899 	*needfree = 0;
    900 
    901 	for (cp = s; *cp != '\0'; ++cp)
    902 		if (!isprint((unsigned char)*cp))
    903 			break;
    904 	if (*cp == '\0')
    905 		return ((char *)__UNCONST(s));	/* SAFE: needfree set to 0. */
    906 
    907 	nlen = 0;
    908 	if (0) {
    909 retry:		if (sp == NULL)
    910 			free(bp);
    911 		else
    912 			FREE_SPACE(sp, bp, blen);
    913 		*needfree = 0;
    914 	}
    915 	nlen += 256;
    916 	if (sp == NULL) {
    917 		if ((bp = malloc(nlen)) == NULL)
    918 			goto alloc_err;
    919 	} else
    920 		GET_SPACE_GOTOC(sp, bp, blen, nlen);
    921 	if (0) {
    922 alloc_err:	return __UNCONST("");
    923 	}
    924 	*needfree = 1;
    925 
    926 	for (p = bp, ep = (bp + blen) - 1, cp = s; *cp != '\0' && p < ep; ++cp)
    927 		for (t = KEY_NAME(sp, *cp); *t != '\0' && p < ep; *p++ = *t++);
    928 	if (p == ep)
    929 		goto retry;
    930 	*p = '\0';
    931 	return (bp);
    932 }
    933