xref: /openbsd/usr.bin/vi/common/msg.c (revision cecf84d4)
1 /*	$OpenBSD: msg.c,v 1.22 2015/01/16 06:40:14 deraadt Exp $	*/
2 
3 /*-
4  * Copyright (c) 1991, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  * Copyright (c) 1991, 1993, 1994, 1995, 1996
7  *	Keith Bostic.  All rights reserved.
8  *
9  * See the LICENSE file for redistribution information.
10  */
11 
12 #include "config.h"
13 
14 #include <sys/queue.h>
15 #include <sys/stat.h>
16 #include <sys/time.h>
17 
18 #include <bitstring.h>
19 #include <ctype.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <limits.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include "common.h"
30 #include "../vi/vi.h"
31 
32 /*
33  * msgq --
34  *	Display a message.
35  *
36  * PUBLIC: void msgq(SCR *, mtype_t, const char *, ...);
37  */
38 void
39 msgq(SCR *sp, mtype_t mt, const char *fmt, ...)
40 {
41 	static int reenter;		/* STATIC: Re-entrancy check. */
42 	GS *gp;
43 	size_t blen, len, mlen, nlen;
44 	const char *p;
45 	char *bp, *mp;
46         va_list ap;
47 
48 	/*
49 	 * !!!
50 	 * It's possible to enter msg when there's no screen to hold the
51 	 * message.  If sp is NULL, ignore the special cases and put the
52 	 * message out to stderr.
53 	 */
54 	if (sp == NULL) {
55 		gp = NULL;
56 		if (mt == M_BERR)
57 			mt = M_ERR;
58 		else if (mt == M_VINFO)
59 			mt = M_INFO;
60 	} else {
61 		gp = sp->gp;
62 		switch (mt) {
63 		case M_BERR:
64 			if (F_ISSET(sp, SC_VI) && !O_ISSET(sp, O_VERBOSE)) {
65 				F_SET(gp, G_BELLSCHED);
66 				return;
67 			}
68 			mt = M_ERR;
69 			break;
70 		case M_VINFO:
71 			if (!O_ISSET(sp, O_VERBOSE))
72 				return;
73 			mt = M_INFO;
74 			/* FALLTHROUGH */
75 		case M_INFO:
76 			if (F_ISSET(sp, SC_EX_SILENT))
77 				return;
78 			break;
79 		case M_ERR:
80 		case M_SYSERR:
81 			break;
82 		default:
83 			abort();
84 		}
85 	}
86 
87 	/*
88 	 * It's possible to reenter msg when it allocates space.  We're
89 	 * probably dead anyway, but there's no reason to drop core.
90 	 *
91 	 * XXX
92 	 * Yes, there's a race, but it should only be two instructions.
93 	 */
94 	if (reenter++)
95 		return;
96 
97 	/* Get space for the message. */
98 	nlen = 1024;
99 	if (0) {
100 retry:		FREE_SPACE(sp, bp, blen);
101 		nlen *= 2;
102 	}
103 	bp = NULL;
104 	blen = 0;
105 	GET_SPACE_GOTO(sp, bp, blen, nlen);
106 
107 	/*
108 	 * Error prefix.
109 	 *
110 	 * mp:	 pointer to the current next character to be written
111 	 * mlen: length of the already written characters
112 	 * blen: total length of the buffer
113 	 */
114 #define	REM	(blen - mlen)
115 	mp = bp;
116 	mlen = 0;
117 	if (mt == M_SYSERR) {
118 		p = msg_cat(sp, "020|Error: ", &len);
119 		if (REM < len)
120 			goto retry;
121 		memcpy(mp, p, len);
122 		mp += len;
123 		mlen += len;
124 	}
125 
126 	/*
127 	 * If we're running an ex command that the user didn't enter, display
128 	 * the file name and line number prefix.
129 	 */
130 	if ((mt == M_ERR || mt == M_SYSERR) &&
131 	    sp != NULL && gp != NULL && gp->if_name != NULL) {
132 		for (p = gp->if_name; *p != '\0'; ++p) {
133 			len = snprintf(mp, REM, "%s", KEY_NAME(sp, *p));
134 			mp += len;
135 			if ((mlen += len) > blen)
136 				goto retry;
137 		}
138 		len = snprintf(mp, REM, ", %d: ", gp->if_lno);
139 		mp += len;
140 		if ((mlen += len) > blen)
141 			goto retry;
142 	}
143 
144 	/* If nothing to format, we're done. */
145 	if (fmt == NULL) {
146 		len = 0;
147 		goto nofmt;
148 	}
149 	fmt = msg_cat(sp, fmt, NULL);
150 
151 	/* Format the arguments into the string. */
152         va_start(ap, fmt);
153 	len = vsnprintf(mp, REM, fmt, ap);
154 	va_end(ap);
155 	if (len >= nlen)
156 		goto retry;
157 
158 nofmt:	mp += len;
159 	if ((mlen += len) > blen)
160 		goto retry;
161 	if (mt == M_SYSERR) {
162 		len = snprintf(mp, REM, ": %s", strerror(errno));
163 		mp += len;
164 		if ((mlen += len) > blen)
165 			goto retry;
166 		mt = M_ERR;
167 	}
168 
169 	/* Add trailing newline. */
170 	if ((mlen += 1) > blen)
171 		goto retry;
172 	*mp = '\n';
173 
174 	if (sp != NULL)
175 		(void)ex_fflush(sp);
176 	if (gp != NULL)
177 		gp->scr_msg(sp, mt, bp, mlen);
178 	else
179 		(void)fprintf(stderr, "%.*s", (int)mlen, bp);
180 
181 	/* Cleanup. */
182 	FREE_SPACE(sp, bp, blen);
183 alloc_err:
184 	reenter = 0;
185 }
186 
187 /*
188  * msgq_str --
189  *	Display a message with an embedded string.
190  *
191  * PUBLIC: void msgq_str(SCR *, mtype_t, char *, char *);
192  */
193 void
194 msgq_str(SCR *sp, mtype_t mtype, char *str, char *fmt)
195 {
196 	int nf, sv_errno;
197 	char *p;
198 
199 	if (str == NULL) {
200 		msgq(sp, mtype, fmt);
201 		return;
202 	}
203 
204 	sv_errno = errno;
205 	p = msg_print(sp, str, &nf);
206 	errno = sv_errno;
207 	msgq(sp, mtype, fmt, p);
208 	if (nf)
209 		FREE_SPACE(sp, p, 0);
210 }
211 
212 /*
213  * mod_rpt --
214  *	Report on the lines that changed.
215  *
216  * !!!
217  * Historic vi documentation (USD:15-8) claimed that "The editor will also
218  * always tell you when a change you make affects text which you cannot see."
219  * This wasn't true -- edit a large file and do "100d|1".  We don't implement
220  * this semantic since it requires tracking each line that changes during a
221  * command instead of just keeping count.
222  *
223  * Line counts weren't right in historic vi, either.  For example, given the
224  * file:
225  *	abc
226  *	def
227  * the command 2d}, from the 'b' would report that two lines were deleted,
228  * not one.
229  *
230  * PUBLIC: void mod_rpt(SCR *);
231  */
232 void
233 mod_rpt(SCR *sp)
234 {
235 	static char * const action[] = {
236 		"293|added",
237 		"294|changed",
238 		"295|deleted",
239 		"296|joined",
240 		"297|moved",
241 		"298|shifted",
242 		"299|yanked",
243 	};
244 	static char * const lines[] = {
245 		"300|line",
246 		"301|lines",
247 	};
248 	recno_t total;
249 	u_long rptval;
250 	int first, cnt;
251 	size_t blen, len, tlen;
252 	const char *t;
253 	char * const *ap;
254 	char *bp, *p;
255 
256 	/* Change reports are turned off in batch mode. */
257 	if (F_ISSET(sp, SC_EX_SILENT))
258 		return;
259 
260 	/* Reset changing line number. */
261 	sp->rptlchange = OOBLNO;
262 
263 	/*
264 	 * Don't build a message if not enough changed.
265 	 *
266 	 * !!!
267 	 * And now, a vi clone test.  Historically, vi reported if the number
268 	 * of changed lines was > than the value, not >=, unless it was a yank
269 	 * command, which used >=.  No lie.  Furthermore, an action was never
270 	 * reported for a single line action.  This is consistent for actions
271 	 * other than yank, but yank didn't report single line actions even if
272 	 * the report edit option was set to 1.  In addition, setting report to
273 	 * 0 in the 4BSD historic vi was equivalent to setting it to 1, for an
274 	 * unknown reason (this bug was fixed in System III/V at some point).
275 	 * I got complaints, so nvi conforms to System III/V historic practice
276 	 * except that we report a yank of 1 line if report is set to 1.
277 	 */
278 #define	ARSIZE(a)	sizeof(a) / sizeof (*a)
279 #define	MAXNUM		25
280 	rptval = O_VAL(sp, O_REPORT);
281 	for (cnt = 0, total = 0; cnt < ARSIZE(action); ++cnt)
282 		total += sp->rptlines[cnt];
283 	if (total == 0)
284 		return;
285 	if (total <= rptval && sp->rptlines[L_YANKED] < rptval) {
286 		for (cnt = 0; cnt < ARSIZE(action); ++cnt)
287 			sp->rptlines[cnt] = 0;
288 		return;
289 	}
290 
291 	/* Build and display the message. */
292 	GET_SPACE_GOTO(sp, bp, blen, sizeof(action) * MAXNUM + 1);
293 	for (p = bp, first = 1, tlen = 0,
294 	    ap = action, cnt = 0; cnt < ARSIZE(action); ++ap, ++cnt)
295 		if (sp->rptlines[cnt] != 0) {
296 			if (first)
297 				first = 0;
298 			else {
299 				*p++ = ';';
300 				*p++ = ' ';
301 				tlen += 2;
302 			}
303 			len = snprintf(p, MAXNUM, "%u ", sp->rptlines[cnt]);
304 			p += len;
305 			tlen += len;
306 			t = msg_cat(sp,
307 			    lines[sp->rptlines[cnt] == 1 ? 0 : 1], &len);
308 			memcpy(p, t, len);
309 			p += len;
310 			tlen += len;
311 			*p++ = ' ';
312 			++tlen;
313 			t = msg_cat(sp, *ap, &len);
314 			memcpy(p, t, len);
315 			p += len;
316 			tlen += len;
317 			sp->rptlines[cnt] = 0;
318 		}
319 
320 	/* Add trailing newline. */
321 	*p = '\n';
322 	++tlen;
323 
324 	(void)ex_fflush(sp);
325 	sp->gp->scr_msg(sp, M_INFO, bp, tlen);
326 
327 	FREE_SPACE(sp, bp, blen);
328 alloc_err:
329 	return;
330 
331 #undef ARSIZE
332 #undef MAXNUM
333 }
334 
335 /*
336  * msgq_status --
337  *	Report on the file's status.
338  *
339  * PUBLIC: void msgq_status(SCR *, recno_t, u_int);
340  */
341 void
342 msgq_status(SCR *sp, recno_t lno, u_int flags)
343 {
344 	recno_t last;
345 	size_t blen, len;
346 	int cnt, needsep;
347 	const char *t;
348 	char **ap, *bp, *np, *p, *s, *ep;
349 
350 	/* Get sufficient memory. */
351 	len = strlen(sp->frp->name);
352 	GET_SPACE_GOTO(sp, bp, blen, len * MAX_CHARACTER_COLUMNS + 128);
353 	p = bp;
354 	ep = bp + blen;
355 
356 	/* Copy in the filename. */
357 	for (t = sp->frp->name; *t != '\0'; ++t) {
358 		len = KEY_LEN(sp, *t);
359 		memcpy(p, KEY_NAME(sp, *t), len);
360 		p += len;
361 	}
362 	np = p;
363 	*p++ = ':';
364 	*p++ = ' ';
365 
366 	/* Copy in the argument count. */
367 	if (F_ISSET(sp, SC_STATUS_CNT) && sp->argv != NULL) {
368 		for (cnt = 0, ap = sp->argv; *ap != NULL; ++ap, ++cnt);
369 		if (cnt > 1) {
370 			(void)snprintf(p, ep - p,
371 			    msg_cat(sp, "317|%d files to edit", NULL), cnt);
372 			p += strlen(p);
373 			*p++ = ':';
374 			*p++ = ' ';
375 		}
376 		F_CLR(sp, SC_STATUS_CNT);
377 	}
378 
379 	/*
380 	 * See nvi/exf.c:file_init() for a description of how and when the
381 	 * read-only bit is set.
382 	 *
383 	 * !!!
384 	 * The historic display for "name changed" was "[Not edited]".
385 	 */
386 	needsep = 0;
387 	if (F_ISSET(sp->frp, FR_NEWFILE)) {
388 		F_CLR(sp->frp, FR_NEWFILE);
389 		t = msg_cat(sp, "021|new file", &len);
390 		memcpy(p, t, len);
391 		p += len;
392 		needsep = 1;
393 	} else {
394 		if (F_ISSET(sp->frp, FR_NAMECHANGE)) {
395 			t = msg_cat(sp, "022|name changed", &len);
396 			memcpy(p, t, len);
397 			p += len;
398 			needsep = 1;
399 		}
400 		if (needsep) {
401 			*p++ = ',';
402 			*p++ = ' ';
403 		}
404 		if (F_ISSET(sp->ep, F_MODIFIED))
405 			t = msg_cat(sp, "023|modified", &len);
406 		else
407 			t = msg_cat(sp, "024|unmodified", &len);
408 		memcpy(p, t, len);
409 		p += len;
410 		needsep = 1;
411 	}
412 	if (F_ISSET(sp->frp, FR_UNLOCKED)) {
413 		if (needsep) {
414 			*p++ = ',';
415 			*p++ = ' ';
416 		}
417 		t = msg_cat(sp, "025|UNLOCKED", &len);
418 		memcpy(p, t, len);
419 		p += len;
420 		needsep = 1;
421 	}
422 	if (O_ISSET(sp, O_READONLY)) {
423 		if (needsep) {
424 			*p++ = ',';
425 			*p++ = ' ';
426 		}
427 		t = msg_cat(sp, "026|readonly", &len);
428 		memcpy(p, t, len);
429 		p += len;
430 		needsep = 1;
431 	}
432 	if (needsep) {
433 		*p++ = ':';
434 		*p++ = ' ';
435 	}
436 	if (LF_ISSET(MSTAT_SHOWLAST)) {
437 		if (db_last(sp, &last))
438 			return;
439 		if (last == 0) {
440 			t = msg_cat(sp, "028|empty file", &len);
441 			memcpy(p, t, len);
442 			p += len;
443 		} else {
444 			t = msg_cat(sp, "027|line %lu of %lu [%ld%%]", &len);
445 			(void)snprintf(p, ep - p, t, lno, last,
446 			    (lno * 100) / last);
447 			p += strlen(p);
448 		}
449 	} else {
450 		t = msg_cat(sp, "029|line %lu", &len);
451 		(void)snprintf(p, ep - p, t, lno);
452 		p += strlen(p);
453 	}
454 #ifdef DEBUG
455 	(void)snprintf(p, ep - p, " (pid %ld)", (long)getpid());
456 	p += strlen(p);
457 #endif
458 	*p++ = '\n';
459 	len = p - bp;
460 
461 	/*
462 	 * There's a nasty problem with long path names.  Cscope and tags files
463 	 * can result in long paths and vi will request a continuation key from
464 	 * the user as soon as it starts the screen.  Unfortunately, the user
465 	 * has already typed ahead, and chaos results.  If we assume that the
466 	 * characters in the filenames and informational messages only take a
467 	 * single screen column each, we can trim the filename.
468 	 *
469 	 * XXX
470 	 * Status lines get put up at fairly awkward times.  For example, when
471 	 * you do a filter read (e.g., :read ! echo foo) in the top screen of a
472 	 * split screen, we have to repaint the status lines for all the screens
473 	 * below the top screen.  We don't want users having to enter continue
474 	 * characters for those screens.  Make it really hard to screw this up.
475 	 */
476 	s = bp;
477 	if (LF_ISSET(MSTAT_TRUNCATE) && len > sp->cols) {
478 		for (; s < np && (*s != '/' || (p - s) > sp->cols - 3); ++s);
479 		if (s == np) {
480 			s = p - (sp->cols - 5);
481 			*--s = ' ';
482 		}
483 		*--s = '.';
484 		*--s = '.';
485 		*--s = '.';
486 		len = p - s;
487 	}
488 
489 	/* Flush any waiting ex messages. */
490 	(void)ex_fflush(sp);
491 
492 	sp->gp->scr_msg(sp, M_INFO, s, len);
493 
494 	FREE_SPACE(sp, bp, blen);
495 alloc_err:
496 	return;
497 }
498 
499 /*
500  * msg_open --
501  *	Open the message catalogs.
502  *
503  * PUBLIC: int msg_open(SCR *, char *);
504  */
505 int
506 msg_open(SCR *sp, char *file)
507 {
508 	/*
509 	 * !!!
510 	 * Assume that the first file opened is the system default, and that
511 	 * all subsequent ones user defined.  Only display error messages
512 	 * if we can't open the user defined ones -- it's useful to know if
513 	 * the system one wasn't there, but if nvi is being shipped with an
514 	 * installed system, the file will be there, if it's not, then the
515 	 * message will be repeated every time nvi is started up.
516 	 */
517 	static int first = 1;
518 	DB *db;
519 	DBT data, key;
520 	recno_t msgno;
521 	char *p, *t, buf[PATH_MAX];
522 
523 	if ((p = strrchr(file, '/')) != NULL && p[1] == '\0' &&
524 	    (((t = getenv("LC_MESSAGES")) != NULL && t[0] != '\0') ||
525 	    ((t = getenv("LANG")) != NULL && t[0] != '\0'))) {
526 		(void)snprintf(buf, sizeof(buf), "%s%s", file, t);
527 		p = buf;
528 	} else
529 		p = file;
530 	if ((db = dbopen(p,
531 	    O_NONBLOCK | O_RDONLY, 0, DB_RECNO, NULL)) == NULL) {
532 		if (first) {
533 			first = 0;
534 			return (1);
535 		}
536 		msgq_str(sp, M_SYSERR, p, "%s");
537 		return (1);
538 	}
539 
540 	/*
541 	 * Test record 1 for the magic string.  The msgq call is here so
542 	 * the message catalog build finds it.
543 	 */
544 #define	VMC	"VI_MESSAGE_CATALOG"
545 	key.data = &msgno;
546 	key.size = sizeof(recno_t);
547 	msgno = 1;
548 	if (db->get(db, &key, &data, 0) != 0 ||
549 	    data.size != sizeof(VMC) - 1 ||
550 	    memcmp(data.data, VMC, sizeof(VMC) - 1)) {
551 		(void)db->close(db);
552 		if (first) {
553 			first = 0;
554 			return (1);
555 		}
556 		msgq_str(sp, M_ERR, p,
557 		    "030|The file %s is not a message catalog");
558 		return (1);
559 	}
560 	first = 0;
561 
562 	if (sp->gp->msg != NULL)
563 		(void)sp->gp->msg->close(sp->gp->msg);
564 	sp->gp->msg = db;
565 	return (0);
566 }
567 
568 /*
569  * msg_close --
570  *	Close the message catalogs.
571  *
572  * PUBLIC: void msg_close(GS *);
573  */
574 void
575 msg_close(GS *gp)
576 {
577 	if (gp->msg != NULL)
578 		(void)gp->msg->close(gp->msg);
579 }
580 
581 /*
582  * msg_cont --
583  *	Return common continuation messages.
584  *
585  * PUBLIC: const char *msg_cmsg(SCR *, cmsg_t, size_t *);
586  */
587 const char *
588 msg_cmsg(SCR *sp, cmsg_t which, size_t *lenp)
589 {
590 	switch (which) {
591 	case CMSG_CONF:
592 		return (msg_cat(sp, "268|confirm? [ynq]", lenp));
593 	case CMSG_CONT:
594 		return (msg_cat(sp, "269|Press any key to continue: ", lenp));
595 	case CMSG_CONT_EX:
596 		return (msg_cat(sp,
597 	    "270|Press any key to continue [: to enter more ex commands]: ",
598 		    lenp));
599 	case CMSG_CONT_R:
600 		return (msg_cat(sp, "161|Press Enter to continue: ", lenp));
601 	case CMSG_CONT_S:
602 		return (msg_cat(sp, "275| cont?", lenp));
603 	case CMSG_CONT_Q:
604 		return (msg_cat(sp,
605 		    "271|Press any key to continue [q to quit]: ", lenp));
606 	default:
607 		abort();
608 	}
609 	/* NOTREACHED */
610 }
611 
612 /*
613  * msg_cat --
614  *	Return a single message from the catalog, plus its length.
615  *
616  * !!!
617  * Only a single catalog message can be accessed at a time, if multiple
618  * ones are needed, they must be copied into local memory.
619  *
620  * PUBLIC: const char *msg_cat(SCR *, const char *, size_t *);
621  */
622 const char *
623 msg_cat(SCR *sp, const char *str, size_t *lenp)
624 {
625 	GS *gp;
626 	DBT data, key;
627 	recno_t msgno;
628 
629 	/*
630 	 * If it's not a catalog message, i.e. has doesn't have a leading
631 	 * number and '|' symbol, we're done.
632 	 */
633 	if (isdigit(str[0]) &&
634 	    isdigit(str[1]) && isdigit(str[2]) && str[3] == '|') {
635 		key.data = &msgno;
636 		key.size = sizeof(recno_t);
637 		msgno = atoi(str);
638 
639 		/*
640 		 * XXX
641 		 * Really sleazy hack -- we put an extra character on the
642 		 * end of the format string, and then we change it to be
643 		 * the nul termination of the string.  There ought to be
644 		 * a better way.  Once we can allocate multiple temporary
645 		 * memory buffers, maybe we can use one of them instead.
646 		 */
647 		gp = sp == NULL ? NULL : sp->gp;
648 		if (gp != NULL && gp->msg != NULL &&
649 		    gp->msg->get(gp->msg, &key, &data, 0) == 0 &&
650 		    data.size != 0) {
651 			if (lenp != NULL)
652 				*lenp = data.size - 1;
653 			((char *)data.data)[data.size - 1] = '\0';
654 			return (data.data);
655 		}
656 		str = &str[4];
657 	}
658 	if (lenp != NULL)
659 		*lenp = strlen(str);
660 	return (str);
661 }
662 
663 /*
664  * msg_print --
665  *	Return a printable version of a string, in allocated memory.
666  *
667  * PUBLIC: char *msg_print(SCR *, const char *, int *);
668  */
669 char *
670 msg_print(SCR *sp, const char *s, int *needfree)
671 {
672 	size_t blen, nlen;
673 	const char *cp;
674 	char *bp, *ep, *p, *t;
675 
676 	*needfree = 0;
677 
678 	for (cp = s; *cp != '\0'; ++cp)
679 		if (!isprint(*cp))
680 			break;
681 	if (*cp == '\0')
682 		return ((char *)s);	/* SAFE: needfree set to 0. */
683 
684 	nlen = 0;
685 	if (0) {
686 retry:		if (sp == NULL)
687 			free(bp);
688 		else
689 			FREE_SPACE(sp, bp, blen);
690 		*needfree = 0;
691 	}
692 	nlen += 256;
693 	if (sp == NULL) {
694 		if ((bp = malloc(nlen)) == NULL)
695 			goto alloc_err;
696 		blen = 0;
697 	} else
698 		GET_SPACE_GOTO(sp, bp, blen, nlen);
699 	if (0) {
700 alloc_err:	return ("");
701 	}
702 	*needfree = 1;
703 
704 	for (p = bp, ep = (bp + blen) - 1, cp = s; *cp != '\0' && p < ep; ++cp)
705 		for (t = KEY_NAME(sp, *cp); *t != '\0' && p < ep; *p++ = *t++);
706 	if (p == ep)
707 		goto retry;
708 	*p = '\0';
709 	return (bp);
710 }
711