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