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