1/* $NetBSD: msg.c,v 1.4 2017/11/06 03:03:54 rin 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 16static 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.4 2017/11/06 03:03:54 rin 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 */ 54void 55#ifdef __STDC__ 56msgq(SCR *sp, mtype_t mt, const char *fmt, ...) 57#else 58msgq(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) { 143retry: 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 291format: /* 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 343nofmt: 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 375ret: 376#endif 377 FREE_SPACE(sp, bp, blen); 378alloc_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 */ 388void 389msgq_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 */ 408void 409msgq_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 */ 447void 448mod_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); 544alloc_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 */ 557void 558msgq_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, 661 (long)((lno * 100) / last)); 662 p += strlen(p); 663 } 664 } else { 665 t = msg_cat(sp, "029|line %lu", &len); 666 (void)sprintf(p, t, lno); 667 p += strlen(p); 668 } 669#ifdef DEBUG 670 (void)sprintf(p, " (pid %lu)", (u_long)getpid()); 671 p += strlen(p); 672#endif 673 *p++ = '\n'; 674 len = p - bp; 675 676 /* 677 * There's a nasty problem with long path names. Cscope and tags files 678 * can result in long paths and vi will request a continuation key from 679 * the user as soon as it starts the screen. Unfortunately, the user 680 * has already typed ahead, and chaos results. If we assume that the 681 * characters in the filenames and informational messages only take a 682 * single screen column each, we can trim the filename. 683 * 684 * XXX 685 * Status lines get put up at fairly awkward times. For example, when 686 * you do a filter read (e.g., :read ! echo foo) in the top screen of a 687 * split screen, we have to repaint the status lines for all the screens 688 * below the top screen. We don't want users having to enter continue 689 * characters for those screens. Make it really hard to screw this up. 690 */ 691 s = bp; 692 if (LF_ISSET(MSTAT_TRUNCATE) && len > sp->cols) { 693 for (; s < np && (*s != '/' || (size_t)(p - s) > sp->cols - 3); ++s); 694 if (s == np) { 695 s = p - (sp->cols - 5); 696 *--s = ' '; 697 } 698 *--s = '.'; 699 *--s = '.'; 700 *--s = '.'; 701 len = p - s; 702 } 703 704 /* Flush any waiting ex messages. */ 705 (void)ex_fflush(sp); 706 707 sp->wp->scr_msg(sp, M_INFO, s, len); 708 709 FREE_SPACE(sp, bp, blen); 710alloc_err: 711 return; 712} 713 714/* 715 * msg_open -- 716 * Open the message catalogs. 717 * 718 * PUBLIC: int msg_open __P((SCR *, const char *)); 719 */ 720int 721msg_open(SCR *sp, const char *file) 722{ 723 /* 724 * !!! 725 * Assume that the first file opened is the system default, and that 726 * all subsequent ones user defined. Only display error messages 727 * if we can't open the user defined ones -- it's useful to know if 728 * the system one wasn't there, but if nvi is being shipped with an 729 * installed system, the file will be there, if it's not, then the 730 * message will be repeated every time nvi is started up. 731 */ 732 static int first = 1; 733 DB *db; 734 DBT data, key; 735 db_recno_t msgno; 736 char buf[MAXPATHLEN]; 737 const char *p, *t; 738 739 if ((p = strrchr(file, '/')) != NULL && p[1] == '\0' && 740 (((t = getenv("LC_MESSAGES")) != NULL && t[0] != '\0') || 741 ((t = getenv("LANG")) != NULL && t[0] != '\0'))) { 742 (void)snprintf(buf, sizeof(buf), "%s%s", file, t); 743 p = buf; 744 } else 745 p = file; 746 if (db_msg_open(sp, p, &db)) { 747 if (first) { 748 first = 0; 749 return (1); 750 } 751 msgq_str(sp, M_DBERR, p, "%s"); 752 return (1); 753 } 754 755 /* 756 * Test record 1 for the magic string. The msgq call is here so 757 * the message catalog build finds it. 758 */ 759#define VMC "VI_MESSAGE_CATALOG" 760 memset(&key, 0, sizeof(key)); 761 key.data = &msgno; 762 key.size = sizeof(db_recno_t); 763 memset(&data, 0, sizeof(data)); 764 msgno = 1; 765 if ((sp->db_error = db_get_low(db, &key, &data, 0)) != 0 || 766 data.size != sizeof(VMC) - 1 || 767 memcmp(data.data, VMC, sizeof(VMC) - 1)) { 768 (void)db_close(db); 769 if (first) { 770 first = 0; 771 return (1); 772 } 773 msgq_str(sp, M_DBERR, p, 774 "030|The file %s is not a message catalog"); 775 return (1); 776 } 777 first = 0; 778 779 if (sp->gp->msg != NULL) 780 (void)db_close(sp->gp->msg); 781 sp->gp->msg = db; 782 return (0); 783} 784 785/* 786 * msg_close -- 787 * Close the message catalogs. 788 * 789 * PUBLIC: void msg_close __P((GS *)); 790 */ 791void 792msg_close(GS *gp) 793{ 794 if (gp->msg != NULL) { 795 (void)db_close(gp->msg); 796 gp->msg = NULL; 797 } 798} 799 800/* 801 * msg_cont -- 802 * Return common continuation messages. 803 * 804 * PUBLIC: const char *msg_cmsg __P((SCR *, cmsg_t, size_t *)); 805 */ 806const char * 807msg_cmsg(SCR *sp, cmsg_t which, size_t *lenp) 808{ 809 switch (which) { 810 case CMSG_CONF: 811 return (msg_cat(sp, "268|confirm? [ynq]", lenp)); 812 case CMSG_CONT: 813 return (msg_cat(sp, "269|Press any key to continue: ", lenp)); 814 case CMSG_CONT_EX: 815 return (msg_cat(sp, 816 "270|Press any key to continue [: to enter more ex commands]: ", 817 lenp)); 818 case CMSG_CONT_R: 819 return (msg_cat(sp, "161|Press Enter to continue: ", lenp)); 820 case CMSG_CONT_S: 821 return (msg_cat(sp, "275| cont?", lenp)); 822 case CMSG_CONT_Q: 823 return (msg_cat(sp, 824 "271|Press any key to continue [q to quit]: ", lenp)); 825 default: 826 abort(); 827 } 828 /* NOTREACHED */ 829} 830 831/* 832 * msg_cat -- 833 * Return a single message from the catalog, plus its length. 834 * 835 * !!! 836 * Only a single catalog message can be accessed at a time, if multiple 837 * ones are needed, they must be copied into local memory. 838 * 839 * PUBLIC: const char *msg_cat __P((SCR *, const char *, size_t *)); 840 */ 841const char * 842msg_cat(SCR *sp, const char *str, size_t *lenp) 843{ 844 GS *gp; 845 DBT data, key; 846 db_recno_t msgno; 847 848 /* 849 * If it's not a catalog message, i.e. has doesn't have a leading 850 * number and '|' symbol, we're done. 851 */ 852 if (isdigit((unsigned char)str[0]) && 853 isdigit((unsigned char)str[1]) && isdigit((unsigned char)str[2]) && 854 str[3] == '|') { 855 memset(&key, 0, sizeof(key)); 856 key.data = &msgno; 857 key.size = sizeof(db_recno_t); 858 memset(&data, 0, sizeof(data)); 859 msgno = atoi(str); 860 861 /* 862 * XXX 863 * Really sleazy hack -- we put an extra character on the 864 * end of the format string, and then we change it to be 865 * the nul termination of the string. There ought to be 866 * a better way. Once we can allocate multiple temporary 867 * memory buffers, maybe we can use one of them instead. 868 */ 869 gp = sp == NULL ? NULL : sp->gp; 870 if (gp != NULL && gp->msg != NULL && 871 db_get_low(gp->msg, &key, &data, 0) == 0 && 872 data.size != 0) { 873 if (lenp != NULL) 874 *lenp = data.size - 1; 875 ((char *)data.data)[data.size - 1] = '\0'; 876 return (data.data); 877 } 878 str = &str[4]; 879 } 880 if (lenp != NULL) 881 *lenp = strlen(str); 882 return (str); 883} 884 885/* 886 * msg_print -- 887 * Return a printable version of a string, in allocated memory. 888 * 889 * PUBLIC: char *msg_print __P((SCR *, const char *, int *)); 890 */ 891char * 892msg_print(SCR *sp, const char *s, int *needfree) 893{ 894 size_t blen, nlen; 895 const char *cp; 896 char *bp, *ep, *p; 897 unsigned char *t; 898 899 *needfree = 0; 900 901 for (cp = s; *cp != '\0'; ++cp) 902 if (!isprint((unsigned char)*cp)) 903 break; 904 if (*cp == '\0') 905 return ((char *)__UNCONST(s)); /* SAFE: needfree set to 0. */ 906 907 nlen = 0; 908 if (0) { 909retry: if (sp == NULL) 910 free(bp); 911 else 912 FREE_SPACE(sp, bp, blen); 913 *needfree = 0; 914 } 915 nlen += 256; 916 if (sp == NULL) { 917 if ((bp = malloc(nlen)) == NULL) 918 goto alloc_err; 919 } else 920 GET_SPACE_GOTOC(sp, bp, blen, nlen); 921 if (0) { 922alloc_err: return __UNCONST(""); 923 } 924 *needfree = 1; 925 926 for (p = bp, ep = (bp + blen) - 1, cp = s; *cp != '\0' && p < ep; ++cp) 927 for (t = KEY_NAME(sp, *cp); *t != '\0' && p < ep; *p++ = *t++); 928 if (p == ep) 929 goto retry; 930 *p = '\0'; 931 return (bp); 932} 933