1/* $NetBSD: db.c,v 1.2 2008/12/05 22:51:42 christos Exp $ */ 2 3/*- 4 * Copyright (c) 1992, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * Copyright (c) 1992, 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#ifndef lint 15static const char sccsid[] = "Id: db.c,v 10.48 2002/06/08 19:32:52 skimo Exp (Berkeley) Date: 2002/06/08 19:32:52"; 16#endif /* not lint */ 17 18#include <sys/types.h> 19#include <sys/queue.h> 20#include <sys/time.h> 21 22#include <bitstring.h> 23#include <errno.h> 24#include <limits.h> 25#include <stdio.h> 26#include <string.h> 27#include <stdlib.h> 28 29#include "common.h" 30#include "../vi/vi.h" 31 32static int append __P((SCR*, db_recno_t, const CHAR_T*, size_t, lnop_t, int)); 33 34/* 35 * db_eget -- 36 * Front-end to db_get, special case handling for empty files. 37 * 38 * PUBLIC: int db_eget __P((SCR *, db_recno_t, CHAR_T **, size_t *, int *)); 39 */ 40int 41db_eget(SCR *sp, db_recno_t lno, CHAR_T **pp, size_t *lenp, int *isemptyp) 42 43 /* Line number. */ 44 /* Pointer store. */ 45 /* Length store. */ 46 47{ 48 db_recno_t l1; 49 50 if (isemptyp != NULL) 51 *isemptyp = 0; 52 53 /* If the line exists, simply return it. */ 54 if (!db_get(sp, lno, 0, pp, lenp)) 55 return (0); 56 57 /* 58 * If the user asked for line 0 or line 1, i.e. the only possible 59 * line in an empty file, find the last line of the file; db_last 60 * fails loudly. 61 */ 62 if ((lno == 0 || lno == 1) && db_last(sp, &l1)) 63 return (1); 64 65 /* If the file isn't empty, fail loudly. */ 66 if ((lno != 0 && lno != 1) || l1 != 0) { 67 db_err(sp, lno); 68 return (1); 69 } 70 71 if (isemptyp != NULL) 72 *isemptyp = 1; 73 74 return (1); 75} 76 77/* 78 * db_get -- 79 * Look in the text buffers for a line, followed by the cache, followed 80 * by the database. 81 * 82 * PUBLIC: int db_get __P((SCR *, db_recno_t, u_int32_t, CHAR_T **, size_t *)); 83 */ 84int 85db_get(SCR *sp, db_recno_t lno, u_int32_t flags, CHAR_T **pp, size_t *lenp) 86 /* Line number. */ /* Pointer store. */ /* Length store. */ 87{ 88 DBT data, key; 89 EXF *ep; 90 TEXT *tp; 91 db_recno_t l1, l2; 92 const CHAR_T *wp; 93 size_t wlen; 94 size_t nlen; 95 96 /* 97 * The underlying recno stuff handles zero by returning NULL, but 98 * have to have an OOB condition for the look-aside into the input 99 * buffer anyway. 100 */ 101 if (lno == 0) 102 goto err1; 103 104 /* Check for no underlying file. */ 105 if ((ep = sp->ep) == NULL) { 106 ex_emsg(sp, NULL, EXM_NOFILEYET); 107 goto err3; 108 } 109 110 if (LF_ISSET(DBG_NOCACHE)) 111 goto nocache; 112 113 /* 114 * Look-aside into the TEXT buffers and see if the line we want 115 * is there. 116 */ 117 if (F_ISSET(sp, SC_TINPUT)) { 118 l1 = ((TEXT *)sp->tiq.cqh_first)->lno; 119 l2 = ((TEXT *)sp->tiq.cqh_last)->lno; 120 if (l1 <= lno && l2 >= lno) { 121#if defined(DEBUG) && 0 122 vtrace(sp, 123 "retrieve TEXT buffer line %lu\n", (u_long)lno); 124#endif 125 for (tp = sp->tiq.cqh_first; 126 tp->lno != lno; tp = tp->q.cqe_next); 127 if (lenp != NULL) 128 *lenp = tp->len; 129 if (pp != NULL) 130 *pp = tp->lb; 131 return (0); 132 } 133 /* 134 * Adjust the line number for the number of lines used 135 * by the text input buffers. 136 */ 137 if (lno > l2) 138 lno -= l2 - l1; 139 } 140 141 /* Look-aside into the cache, and see if the line we want is there. */ 142 if (lno == sp->c_lno) { 143#if defined(DEBUG) && 0 144 vtrace(sp, "retrieve cached line %lu\n", (u_long)lno); 145#endif 146 if (lenp != NULL) 147 *lenp = sp->c_len; 148 if (pp != NULL) 149 *pp = sp->c_lp; 150 return (0); 151 } 152 sp->c_lno = OOBLNO; 153 154nocache: 155 nlen = 1024; 156retry: 157 /* data.size contains length in bytes */ 158 BINC_GOTO(sp, CHAR_T, sp->c_lp, sp->c_blen, nlen); 159 160 /* Get the line from the underlying database. */ 161 memset(&key, 0, sizeof(key)); 162 key.data = &lno; 163 key.size = sizeof(lno); 164 memset(&data, 0, sizeof(data)); 165 data.data = sp->c_lp; 166 data.ulen = sp->c_blen; 167 data.flags = DB_DBT_USERMEM; 168 switch (ep->db->get(ep->db, NULL, &key, &data, 0)) { 169 case DB_BUFFER_SMALL: 170 nlen = data.size; 171 goto retry; 172 default: 173 goto err2; 174 case DB_NOTFOUND: 175err1: if (LF_ISSET(DBG_FATAL)) 176err2: db_err(sp, lno); 177alloc_err: 178err3: if (lenp != NULL) 179 *lenp = 0; 180 if (pp != NULL) 181 *pp = NULL; 182 return (1); 183 case 0: 184 ; 185 } 186 187 if (FILE2INT(sp, data.data, data.size, wp, wlen)) { 188 if (!F_ISSET(sp, SC_CONV_ERROR)) { 189 F_SET(sp, SC_CONV_ERROR); 190 msgq(sp, M_ERR, "324|Conversion error on line %d", lno); 191 } 192 goto err3; 193 } 194 195 /* Reset the cache. */ 196 if (wp != data.data) { 197 BINC_GOTOW(sp, sp->c_lp, sp->c_blen, wlen); 198 MEMCPYW(sp->c_lp, wp, wlen); 199 } 200 sp->c_lno = lno; 201 sp->c_len = wlen; 202 203#if defined(DEBUG) && 0 204 vtrace(sp, "retrieve DB line %lu\n", (u_long)lno); 205#endif 206 if (lenp != NULL) 207 *lenp = wlen; 208 if (pp != NULL) 209 *pp = sp->c_lp; 210 return (0); 211} 212 213/* 214 * db_delete -- 215 * Delete a line from the file. 216 * 217 * PUBLIC: int db_delete __P((SCR *, db_recno_t)); 218 */ 219int 220db_delete(SCR *sp, db_recno_t lno) 221{ 222 DBT key; 223 EXF *ep; 224 225#if defined(DEBUG) && 0 226 vtrace(sp, "delete line %lu\n", (u_long)lno); 227#endif 228 /* Check for no underlying file. */ 229 if ((ep = sp->ep) == NULL) { 230 ex_emsg(sp, NULL, EXM_NOFILEYET); 231 return (1); 232 } 233 if (ep->l_win && ep->l_win != sp->wp) { 234 ex_emsg(sp, NULL, EXM_LOCKED); 235 return 1; 236 } 237 238 /* Update marks, @ and global commands. */ 239 if (line_insdel(sp, LINE_DELETE, lno)) 240 return 1; 241 242 /* Log before change. */ 243 log_line(sp, lno, LOG_LINE_DELETE_B); 244 245 /* Update file. */ 246 memset(&key, 0, sizeof(key)); 247 key.data = &lno; 248 key.size = sizeof(lno); 249 if ((sp->db_error = ep->db->del(ep->db, NULL, &key, 0)) != 0) { 250 msgq(sp, M_DBERR, "003|unable to delete line %lu", 251 (u_long)lno); 252 return (1); 253 } 254 255 /* Flush the cache, update line count, before screen update. */ 256 update_cache(sp, LINE_DELETE, lno); 257 258 /* File now modified. */ 259 if (F_ISSET(ep, F_FIRSTMODIFY)) 260 (void)rcv_init(sp); 261 F_SET(ep, F_MODIFIED); 262 263 /* Log after change. */ 264 log_line(sp, lno, LOG_LINE_DELETE_F); 265 266 /* Update screen. */ 267 return (scr_update(sp, lno, LINE_DELETE, 1)); 268} 269 270/* maybe this could be simpler 271 * 272 * DB3 behaves differently from DB1 273 * 274 * if lno != 0 just go to lno and put the new line after it 275 * if lno == 0 then if there are any record, put in front of the first 276 * otherwise just append to the end thus creating the first 277 * line 278 */ 279static int 280append(SCR *sp, db_recno_t lno, const CHAR_T *p, size_t len, lnop_t op, int update) 281{ 282 DBT data, key; 283 DBC *dbcp_put; 284 EXF *ep; 285 const char *fp; 286 size_t flen; 287 int rval; 288 289 /* Check for no underlying file. */ 290 if ((ep = sp->ep) == NULL) { 291 ex_emsg(sp, NULL, EXM_NOFILEYET); 292 return (1); 293 } 294 if (ep->l_win && ep->l_win != sp->wp) { 295 ex_emsg(sp, NULL, EXM_LOCKED); 296 return 1; 297 } 298 299 /* Log before change. */ 300 log_line(sp, lno + 1, LOG_LINE_APPEND_B); 301 302 /* Update file. */ 303 memset(&key, 0, sizeof(key)); 304 key.data = &lno; 305 key.size = sizeof(lno); 306 memset(&data, 0, sizeof(data)); 307 308 if ((sp->db_error = ep->db->cursor(ep->db, NULL, &dbcp_put, 0)) != 0) 309 return 1; 310 311 INT2FILE(sp, p, len, fp, flen); 312 313 if (lno != 0) { 314 if ((sp->db_error = dbcp_put->c_get(dbcp_put, &key, &data, DB_SET)) != 0) 315 goto err2; 316 317 data.data = __UNCONST(fp); 318 data.size = flen; 319 if ((sp->db_error = dbcp_put->c_put(dbcp_put, &key, &data, DB_AFTER)) != 0) { 320err2: 321 (void)dbcp_put->c_close(dbcp_put); 322 msgq(sp, M_DBERR, 323 (op == LINE_APPEND) 324 ? "004|unable to append to line %lu" 325 : "005|unable to insert at line %lu", 326 (u_long)lno); 327 return (1); 328 } 329 } else { 330 if ((sp->db_error = dbcp_put->c_get(dbcp_put, &key, &data, DB_FIRST)) != 0) { 331 if (sp->db_error != DB_NOTFOUND) 332 goto err2; 333 334 data.data = __UNCONST(fp); 335 data.size = flen; 336 if ((sp->db_error = ep->db->put(ep->db, NULL, &key, &data, DB_APPEND)) != 0) { 337 goto err2; 338 } 339 } else { 340 key.data = &lno; 341 key.size = sizeof(lno); 342 data.data = __UNCONST(fp); 343 data.size = flen; 344 if ((sp->db_error = dbcp_put->c_put(dbcp_put, &key, &data, DB_BEFORE)) != 0) { 345 goto err2; 346 } 347 } 348 } 349 350 (void)dbcp_put->c_close(dbcp_put); 351 352 /* Flush the cache, update line count, before screen update. */ 353 update_cache(sp, LINE_INSERT, lno); 354 355 /* File now dirty. */ 356 if (F_ISSET(ep, F_FIRSTMODIFY)) 357 (void)rcv_init(sp); 358 F_SET(ep, F_MODIFIED); 359 360 /* Log after change. */ 361 log_line(sp, lno + 1, LOG_LINE_APPEND_F); 362 363 /* Update marks, @ and global commands. */ 364 rval = line_insdel(sp, LINE_INSERT, lno + 1); 365 366 /* 367 * Update screen. 368 * 369 * comment copied from db_append 370 * XXX 371 * Nasty hack. If multiple lines are input by the user, they aren't 372 * committed until an <ESC> is entered. The problem is the screen was 373 * updated/scrolled as each line was entered. So, when this routine 374 * is called to copy the new lines from the cut buffer into the file, 375 * it has to know not to update the screen again. 376 */ 377 return (scr_update(sp, lno + 1, LINE_INSERT, update) || rval); 378} 379 380/* 381 * db_append -- 382 * Append a line into the file. 383 * 384 * PUBLIC: int db_append __P((SCR *, int, db_recno_t, CHAR_T *, size_t)); 385 */ 386int 387db_append(SCR *sp, int update, db_recno_t lno, const CHAR_T *p, size_t len) 388{ 389#if defined(DEBUG) && 0 390 vtrace(sp, "append to %lu: len %u {%.*s}\n", lno, len, MIN(len, 20), p); 391#endif 392 393 /* Update file. */ 394 return append(sp, lno, p, len, LINE_APPEND, update); 395} 396 397/* 398 * db_insert -- 399 * Insert a line into the file. 400 * 401 * PUBLIC: int db_insert __P((SCR *, db_recno_t, CHAR_T *, size_t)); 402 */ 403int 404db_insert(SCR *sp, db_recno_t lno, CHAR_T *p, size_t len) 405{ 406#if defined(DEBUG) && 0 407 vtrace(sp, "insert before %lu: len %lu {%.*s}\n", 408 (u_long)lno, (u_long)len, MIN(len, 20), p); 409#endif 410 return append(sp, lno - 1, p, len, LINE_INSERT, 1); 411} 412 413/* 414 * db_set -- 415 * Store a line in the file. 416 * 417 * PUBLIC: int db_set __P((SCR *, db_recno_t, CHAR_T *, size_t)); 418 */ 419int 420db_set(SCR *sp, db_recno_t lno, CHAR_T *p, size_t len) 421{ 422 DBT data, key; 423 EXF *ep; 424 const char *fp; 425 size_t flen; 426 427#if defined(DEBUG) && 0 428 vtrace(sp, "replace line %lu: len %lu {%.*s}\n", 429 (u_long)lno, (u_long)len, MIN(len, 20), p); 430#endif 431 /* Check for no underlying file. */ 432 if ((ep = sp->ep) == NULL) { 433 ex_emsg(sp, NULL, EXM_NOFILEYET); 434 return (1); 435 } 436 if (ep->l_win && ep->l_win != sp->wp) { 437 ex_emsg(sp, NULL, EXM_LOCKED); 438 return 1; 439 } 440 441 /* Log before change. */ 442 log_line(sp, lno, LOG_LINE_RESET_B); 443 444 INT2FILE(sp, p, len, fp, flen); 445 446 /* Update file. */ 447 memset(&key, 0, sizeof(key)); 448 key.data = &lno; 449 key.size = sizeof(lno); 450 memset(&data, 0, sizeof(data)); 451 data.data = __UNCONST(fp); 452 data.size = flen; 453 if ((sp->db_error = ep->db->put(ep->db, NULL, &key, &data, 0)) != 0) { 454 msgq(sp, M_DBERR, "006|unable to store line %lu", (u_long)lno); 455 return (1); 456 } 457 458 /* Flush the cache, update line count, before screen update. */ 459 update_cache(sp, LINE_RESET, lno); 460 461 /* File now dirty. */ 462 if (F_ISSET(ep, F_FIRSTMODIFY)) 463 (void)rcv_init(sp); 464 F_SET(ep, F_MODIFIED); 465 466 /* Log after change. */ 467 log_line(sp, lno, LOG_LINE_RESET_F); 468 469 /* Update screen. */ 470 return (scr_update(sp, lno, LINE_RESET, 1)); 471} 472 473/* 474 * db_exist -- 475 * Return if a line exists. 476 * 477 * PUBLIC: int db_exist __P((SCR *, db_recno_t)); 478 */ 479int 480db_exist(SCR *sp, db_recno_t lno) 481{ 482 EXF *ep; 483 484 /* Check for no underlying file. */ 485 if ((ep = sp->ep) == NULL) { 486 ex_emsg(sp, NULL, EXM_NOFILEYET); 487 return (1); 488 } 489 490 if (lno == OOBLNO) 491 return (0); 492 493 /* 494 * Check the last-line number cache. Adjust the cached line 495 * number for the lines used by the text input buffers. 496 */ 497 if (ep->c_nlines != OOBLNO) 498 return (lno <= (F_ISSET(sp, SC_TINPUT) ? 499 ep->c_nlines + (((TEXT *)sp->tiq.cqh_last)->lno - 500 ((TEXT *)sp->tiq.cqh_first)->lno) : ep->c_nlines)); 501 502 /* Go get the line. */ 503 return (!db_get(sp, lno, 0, NULL, NULL)); 504} 505 506/* 507 * db_last -- 508 * Return the number of lines in the file. 509 * 510 * PUBLIC: int db_last __P((SCR *, db_recno_t *)); 511 */ 512int 513db_last(SCR *sp, db_recno_t *lnop) 514{ 515 DBT data, key; 516 DBC *dbcp; 517 EXF *ep; 518 db_recno_t lno; 519 const CHAR_T *wp; 520 size_t wlen; 521 522 /* Check for no underlying file. */ 523 if ((ep = sp->ep) == NULL) { 524 ex_emsg(sp, NULL, EXM_NOFILEYET); 525 return (1); 526 } 527 528 /* 529 * Check the last-line number cache. Adjust the cached line 530 * number for the lines used by the text input buffers. 531 */ 532 if (ep->c_nlines != OOBLNO) { 533 *lnop = ep->c_nlines; 534 if (F_ISSET(sp, SC_TINPUT)) 535 *lnop += ((TEXT *)sp->tiq.cqh_last)->lno - 536 ((TEXT *)sp->tiq.cqh_first)->lno; 537 return (0); 538 } 539 540 memset(&key, 0, sizeof(key)); 541 key.data = &lno; 542 key.size = sizeof(lno); 543 memset(&data, 0, sizeof(data)); 544 545 if ((sp->db_error = ep->db->cursor(ep->db, NULL, &dbcp, 0)) != 0) 546 goto err1; 547 switch (sp->db_error = dbcp->c_get(dbcp, &key, &data, DB_LAST)) { 548 case DB_NOTFOUND: 549 *lnop = 0; 550 return (0); 551 default: 552 (void)dbcp->c_close(dbcp); 553alloc_err: 554err1: 555 msgq(sp, M_DBERR, "007|unable to get last line"); 556 *lnop = 0; 557 return (1); 558 case 0: 559 ; 560 } 561 562 memcpy(&lno, key.data, sizeof(lno)); 563 564 if (lno != sp->c_lno) { 565 FILE2INT(sp, data.data, data.size, wp, wlen); 566 567 /* Fill the cache. */ 568 BINC_GOTOW(sp, sp->c_lp, sp->c_blen, wlen); 569 MEMCPYW(sp->c_lp, wp, wlen); 570 sp->c_lno = lno; 571 sp->c_len = wlen; 572 } 573 ep->c_nlines = lno; 574 575 (void)dbcp->c_close(dbcp); 576 577 /* Return the value. */ 578 *lnop = (F_ISSET(sp, SC_TINPUT) && 579 ((TEXT *)sp->tiq.cqh_last)->lno > lno ? 580 ((TEXT *)sp->tiq.cqh_last)->lno : lno); 581 return (0); 582} 583 584/* 585 * db_err -- 586 * Report a line error. 587 * 588 * PUBLIC: void db_err __P((SCR *, db_recno_t)); 589 */ 590void 591db_err(SCR *sp, db_recno_t lno) 592{ 593 msgq(sp, M_ERR, 594 "008|Error: unable to retrieve line %lu", (u_long)lno); 595} 596 597/* 598 * scr_update -- 599 * Update all of the screens that are backed by the file that 600 * just changed. 601 * 602 * PUBLIC: int scr_update __P((SCR *sp, db_recno_t lno, 603 * PUBLIC: lnop_t op, int current)); 604 */ 605int 606scr_update(SCR *sp, db_recno_t lno, lnop_t op, int current) 607{ 608 EXF *ep; 609 SCR *tsp; 610 WIN *wp; 611 612 if (F_ISSET(sp, SC_EX)) 613 return (0); 614 615 /* XXXX goes outside of window */ 616 ep = sp->ep; 617 if (ep->refcnt != 1) 618 for (wp = sp->gp->dq.cqh_first; wp != (void *)&sp->gp->dq; 619 wp = wp->q.cqe_next) 620 for (tsp = wp->scrq.cqh_first; 621 tsp != (void *)&wp->scrq; tsp = tsp->q.cqe_next) 622 if (sp != tsp && tsp->ep == ep) 623 if (vs_change(tsp, lno, op)) 624 return (1); 625 return (current ? vs_change(sp, lno, op) : 0); 626} 627 628/* 629 * PUBLIC: void update_cache __P((SCR *sp, lnop_t op, db_recno_t lno)); 630 */ 631void 632update_cache(SCR *sp, lnop_t op, db_recno_t lno) 633{ 634 SCR* scrp; 635 EXF *ep; 636 637 ep = sp->ep; 638 639 /* Flush the cache, update line count, before screen update. */ 640 /* The flushing is probably not needed, since it was incorrect 641 * for db_insert. It might be better to adjust it, like 642 * marks, @ and global 643 */ 644 for (scrp = ep->scrq.cqh_first; scrp != (void *)&ep->scrq; 645 scrp = scrp->eq.cqe_next) 646 switch (op) { 647 case LINE_INSERT: 648 case LINE_DELETE: 649 if (lno <= scrp->c_lno) 650 scrp->c_lno = OOBLNO; 651 break; 652 case LINE_RESET: 653 if (lno == scrp->c_lno) 654 scrp->c_lno = OOBLNO; 655 /*FALLTHROUGH*/ 656 case LINE_APPEND: 657 break; 658 } 659 660 if (ep->c_nlines != OOBLNO) 661 switch (op) { 662 case LINE_INSERT: 663 ++ep->c_nlines; 664 break; 665 case LINE_DELETE: 666 --ep->c_nlines; 667 /*FALLTHROUGH*/ 668 case LINE_APPEND: 669 case LINE_RESET: 670 break; 671 } 672} 673 674/* 675 * PUBLIC: int line_insdel __P((SCR *sp, lnop_t op, db_recno_t lno)); 676 */ 677int 678line_insdel(SCR *sp, lnop_t op, db_recno_t lno) 679{ 680 int rval; 681 682 /* Update marks, @ and global commands. */ 683 rval = 0; 684 if (mark_insdel(sp, op, lno)) 685 rval = 1; 686 if (ex_g_insdel(sp, op, lno)) 687 rval = 1; 688 689 return rval; 690} 691