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