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