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