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