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