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