Home | History | Annotate | Line # | Download | only in common
vi_db.c revision 1.1
      1 /*-
      2  * Copyright (c) 1992, 1993, 1994
      3  *	The Regents of the University of California.  All rights reserved.
      4  * Copyright (c) 1992, 1993, 1994, 1995, 1996
      5  *	Keith Bostic.  All rights reserved.
      6  *
      7  * See the LICENSE file for redistribution information.
      8  */
      9 
     10 #include "config.h"
     11 
     12 #ifndef lint
     13 static 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 ";
     14 #endif /* not lint */
     15 
     16 #include <sys/types.h>
     17 #include <sys/queue.h>
     18 #include <sys/time.h>
     19 #include <sys/stat.h>
     20 
     21 #include <bitstring.h>
     22 #include <errno.h>
     23 #include <limits.h>
     24 #include <stdio.h>
     25 #include <string.h>
     26 
     27 #include "common.h"
     28 #include "../vi/vi.h"
     29 
     30 static int append __P((SCR*, db_recno_t, CHAR_T*, size_t, lnop_t, int));
     31 
     32 /*
     33  * db_eget --
     34  *	Front-end to db_get, special case handling for empty files.
     35  *
     36  * PUBLIC: int db_eget __P((SCR *, db_recno_t, CHAR_T **, size_t *, int *));
     37  */
     38 int
     39 db_eget(SCR *sp, db_recno_t lno, CHAR_T **pp, size_t *lenp, int *isemptyp)
     40 
     41 	               				/* Line number. */
     42 	            				/* Pointer store. */
     43 	             				/* Length store. */
     44 
     45 {
     46 	db_recno_t l1;
     47 
     48 	if (isemptyp != NULL)
     49 		*isemptyp = 0;
     50 
     51 	/* If the line exists, simply return it. */
     52 	if (!db_get(sp, lno, 0, pp, lenp))
     53 		return (0);
     54 
     55 	/*
     56 	 * If the user asked for line 0 or line 1, i.e. the only possible
     57 	 * line in an empty file, find the last line of the file; db_last
     58 	 * fails loudly.
     59 	 */
     60 	if ((lno == 0 || lno == 1) && db_last(sp, &l1))
     61 		return (1);
     62 
     63 	/* If the file isn't empty, fail loudly. */
     64 	if ((lno != 0 && lno != 1) || l1 != 0) {
     65 		db_err(sp, lno);
     66 		return (1);
     67 	}
     68 
     69 	if (isemptyp != NULL)
     70 		*isemptyp = 1;
     71 
     72 	return (1);
     73 }
     74 
     75 /*
     76  * db_get --
     77  *	Look in the text buffers for a line, followed by the cache, followed
     78  *	by the database.
     79  *
     80  * PUBLIC: int db_get __P((SCR *, db_recno_t, u_int32_t, CHAR_T **, size_t *));
     81  */
     82 int
     83 db_get(SCR *sp, db_recno_t lno, u_int32_t flags, CHAR_T **pp, size_t *lenp)
     84 		/* Line number. */ /* Pointer store. */ /* Length store. */
     85 {
     86 	DBT data, key;
     87 	EXF *ep;
     88 	TEXT *tp;
     89 	db_recno_t l1, l2;
     90 	CHAR_T *wp;
     91 	size_t wlen;
     92 	size_t nlen;
     93 
     94 	/*
     95 	 * The underlying recno stuff handles zero by returning NULL, but
     96 	 * have to have an OOB condition for the look-aside into the input
     97 	 * buffer anyway.
     98 	 */
     99 	if (lno == 0)
    100 		goto err1;
    101 
    102 	/* Check for no underlying file. */
    103 	if ((ep = sp->ep) == NULL) {
    104 		ex_emsg(sp, NULL, EXM_NOFILEYET);
    105 		goto err3;
    106 	}
    107 
    108 	if (LF_ISSET(DBG_NOCACHE))
    109 		goto nocache;
    110 
    111 	/*
    112 	 * Look-aside into the TEXT buffers and see if the line we want
    113 	 * is there.
    114 	 */
    115 	if (F_ISSET(sp, SC_TINPUT)) {
    116 		l1 = ((TEXT *)sp->tiq.cqh_first)->lno;
    117 		l2 = ((TEXT *)sp->tiq.cqh_last)->lno;
    118 		if (l1 <= lno && l2 >= lno) {
    119 #if defined(DEBUG) && 0
    120 			vtrace(sp,
    121 			    "retrieve TEXT buffer line %lu\n", (u_long)lno);
    122 #endif
    123 			for (tp = sp->tiq.cqh_first;
    124 			    tp->lno != lno; tp = tp->q.cqe_next);
    125 			if (lenp != NULL)
    126 				*lenp = tp->len;
    127 			if (pp != NULL)
    128 				*pp = tp->lb;
    129 			return (0);
    130 		}
    131 		/*
    132 		 * Adjust the line number for the number of lines used
    133 		 * by the text input buffers.
    134 		 */
    135 		if (lno > l2)
    136 			lno -= l2 - l1;
    137 	}
    138 
    139 	/* Look-aside into the cache, and see if the line we want is there. */
    140 	if (lno == sp->c_lno) {
    141 #if defined(DEBUG) && 0
    142 		vtrace(sp, "retrieve cached line %lu\n", (u_long)lno);
    143 #endif
    144 		if (lenp != NULL)
    145 			*lenp = sp->c_len;
    146 		if (pp != NULL)
    147 			*pp = sp->c_lp;
    148 		return (0);
    149 	}
    150 	sp->c_lno = OOBLNO;
    151 
    152 nocache:
    153 	nlen = 1024;
    154 retry:
    155 	/* data.size contains length in bytes */
    156 	BINC_GOTO(sp, CHAR_T, sp->c_lp, sp->c_blen, nlen);
    157 
    158 	/* Get the line from the underlying database. */
    159 	memset(&key, 0, sizeof(key));
    160 	key.data = &lno;
    161 	key.size = sizeof(lno);
    162 	memset(&data, 0, sizeof(data));
    163 	data.data = sp->c_lp;
    164 	data.ulen = sp->c_blen;
    165 	data.flags = DB_DBT_USERMEM;
    166 	switch (ep->db->get(ep->db, NULL, &key, &data, 0)) {
    167 	case DB_BUFFER_SMALL:
    168 		nlen = data.size;
    169 		goto retry;
    170         default:
    171 		goto err2;
    172 	case DB_NOTFOUND:
    173 err1:		if (LF_ISSET(DBG_FATAL))
    174 err2:			db_err(sp, lno);
    175 alloc_err:
    176 err3:		if (lenp != NULL)
    177 			*lenp = 0;
    178 		if (pp != NULL)
    179 			*pp = NULL;
    180 		return (1);
    181 	case 0:
    182 		;
    183 	}
    184 
    185 	if (FILE2INT(sp, data.data, data.size, wp, wlen)) {
    186 	    if (!F_ISSET(sp, SC_CONV_ERROR)) {
    187 		F_SET(sp, SC_CONV_ERROR);
    188 		msgq(sp, M_ERR, "324|Conversion error on line %d", lno);
    189 	    }
    190 	    goto err3;
    191 	}
    192 
    193 	/* Reset the cache. */
    194 	if (wp != data.data) {
    195 	    BINC_GOTOW(sp, sp->c_lp, sp->c_blen, wlen);
    196 	    MEMCPYW(sp->c_lp, wp, wlen);
    197 	}
    198 	sp->c_lno = lno;
    199 	sp->c_len = wlen;
    200 
    201 #if defined(DEBUG) && 0
    202 	vtrace(sp, "retrieve DB line %lu\n", (u_long)lno);
    203 #endif
    204 	if (lenp != NULL)
    205 		*lenp = wlen;
    206 	if (pp != NULL)
    207 		*pp = sp->c_lp;
    208 	return (0);
    209 }
    210 
    211 /*
    212  * db_delete --
    213  *	Delete a line from the file.
    214  *
    215  * PUBLIC: int db_delete __P((SCR *, db_recno_t));
    216  */
    217 int
    218 db_delete(SCR *sp, db_recno_t lno)
    219 {
    220 	DBT key;
    221 	EXF *ep;
    222 
    223 #if defined(DEBUG) && 0
    224 	vtrace(sp, "delete line %lu\n", (u_long)lno);
    225 #endif
    226 	/* Check for no underlying file. */
    227 	if ((ep = sp->ep) == NULL) {
    228 		ex_emsg(sp, NULL, EXM_NOFILEYET);
    229 		return (1);
    230 	}
    231 	if (ep->l_win && ep->l_win != sp->wp) {
    232 		ex_emsg(sp, NULL, EXM_LOCKED);
    233 		return 1;
    234 	}
    235 
    236 	/* Update marks, @ and global commands. */
    237 	if (line_insdel(sp, LINE_DELETE, lno))
    238 		return 1;
    239 
    240 	/* Log before change. */
    241 	log_line(sp, lno, LOG_LINE_DELETE_B);
    242 
    243 	/* Update file. */
    244 	memset(&key, 0, sizeof(key));
    245 	key.data = &lno;
    246 	key.size = sizeof(lno);
    247 	if ((sp->db_error = ep->db->del(ep->db, NULL, &key, 0)) != 0) {
    248 		msgq(sp, M_DBERR, "003|unable to delete line %lu",
    249 		    (u_long)lno);
    250 		return (1);
    251 	}
    252 
    253 	/* Flush the cache, update line count, before screen update. */
    254 	update_cache(sp, LINE_DELETE, lno);
    255 
    256 	/* File now modified. */
    257 	if (F_ISSET(ep, F_FIRSTMODIFY))
    258 		(void)rcv_init(sp);
    259 	F_SET(ep, F_MODIFIED);
    260 
    261 	/* Log after change. */
    262 	log_line(sp, lno, LOG_LINE_DELETE_F);
    263 
    264 	/* Update screen. */
    265 	return (scr_update(sp, lno, LINE_DELETE, 1));
    266 }
    267 
    268 /* maybe this could be simpler
    269  *
    270  * DB3 behaves differently from DB1
    271  *
    272  * if lno != 0 just go to lno and put the new line after it
    273  * if lno == 0 then if there are any record, put in front of the first
    274  *		    otherwise just append to the end thus creating the first
    275  *				line
    276  */
    277 static int
    278 append(SCR *sp, db_recno_t lno, CHAR_T *p, size_t len, lnop_t op, int update)
    279 {
    280 	DBT data, key;
    281 	DBC *dbcp_put;
    282 	EXF *ep;
    283 	char *fp;
    284 	size_t flen;
    285 	int rval;
    286 
    287 	/* Check for no underlying file. */
    288 	if ((ep = sp->ep) == NULL) {
    289 		ex_emsg(sp, NULL, EXM_NOFILEYET);
    290 		return (1);
    291 	}
    292 	if (ep->l_win && ep->l_win != sp->wp) {
    293 		ex_emsg(sp, NULL, EXM_LOCKED);
    294 		return 1;
    295 	}
    296 
    297 	/* Log before change. */
    298 	log_line(sp, lno + 1, LOG_LINE_APPEND_B);
    299 
    300 	/* Update file. */
    301 	memset(&key, 0, sizeof(key));
    302 	key.data = &lno;
    303 	key.size = sizeof(lno);
    304 	memset(&data, 0, sizeof(data));
    305 
    306 	if ((sp->db_error = ep->db->cursor(ep->db, NULL, &dbcp_put, 0)) != 0)
    307 	    return 1;
    308 
    309 	INT2FILE(sp, p, len, fp, flen);
    310 
    311 	if (lno != 0) {
    312 	    if ((sp->db_error = dbcp_put->c_get(dbcp_put, &key, &data, DB_SET)) != 0)
    313 		goto err2;
    314 
    315 	    data.data = fp;
    316 	    data.size = flen;
    317 	    if ((sp->db_error = dbcp_put->c_put(dbcp_put, &key, &data, DB_AFTER)) != 0) {
    318 err2:
    319 		(void)dbcp_put->c_close(dbcp_put);
    320 		msgq(sp, M_DBERR,
    321 			(op == LINE_APPEND)
    322 			    ? "004|unable to append to line %lu"
    323 			    : "005|unable to insert at line %lu",
    324 			(u_long)lno);
    325 		return (1);
    326 	    }
    327 	} else {
    328 	    if ((sp->db_error = dbcp_put->c_get(dbcp_put, &key, &data, DB_FIRST)) != 0) {
    329 		if (sp->db_error != DB_NOTFOUND)
    330 		    goto err2;
    331 
    332 		data.data = fp;
    333 		data.size = flen;
    334 		if ((sp->db_error = ep->db->put(ep->db, NULL, &key, &data, DB_APPEND)) != 0) {
    335 		    goto err2;
    336 		}
    337 	    } else {
    338 		key.data = &lno;
    339 		key.size = sizeof(lno);
    340 		data.data = fp;
    341 		data.size = flen;
    342 		if ((sp->db_error = dbcp_put->c_put(dbcp_put, &key, &data, DB_BEFORE)) != 0) {
    343 		    goto err2;
    344 		}
    345 	    }
    346 	}
    347 
    348 	(void)dbcp_put->c_close(dbcp_put);
    349 
    350 	/* Flush the cache, update line count, before screen update. */
    351 	update_cache(sp, LINE_INSERT, lno);
    352 
    353 	/* File now dirty. */
    354 	if (F_ISSET(ep, F_FIRSTMODIFY))
    355 		(void)rcv_init(sp);
    356 	F_SET(ep, F_MODIFIED);
    357 
    358 	/* Log after change. */
    359 	log_line(sp, lno + 1, LOG_LINE_APPEND_F);
    360 
    361 	/* Update marks, @ and global commands. */
    362 	rval = line_insdel(sp, LINE_INSERT, lno + 1);
    363 
    364 	/*
    365 	 * Update screen.
    366 	 *
    367 	 * comment copied from db_append
    368 	 * XXX
    369 	 * Nasty hack.  If multiple lines are input by the user, they aren't
    370 	 * committed until an <ESC> is entered.  The problem is the screen was
    371 	 * updated/scrolled as each line was entered.  So, when this routine
    372 	 * is called to copy the new lines from the cut buffer into the file,
    373 	 * it has to know not to update the screen again.
    374 	 */
    375 	return (scr_update(sp, lno + 1, LINE_INSERT, update) || rval);
    376 }
    377 
    378 /*
    379  * db_append --
    380  *	Append a line into the file.
    381  *
    382  * PUBLIC: int db_append __P((SCR *, int, db_recno_t, CHAR_T *, size_t));
    383  */
    384 int
    385 db_append(SCR *sp, int update, db_recno_t lno, CHAR_T *p, size_t len)
    386 {
    387 #if defined(DEBUG) && 0
    388 	vtrace(sp, "append to %lu: len %u {%.*s}\n", lno, len, MIN(len, 20), p);
    389 #endif
    390 
    391 	/* Update file. */
    392 	return append(sp, lno, p, len, LINE_APPEND, update);
    393 }
    394 
    395 /*
    396  * db_insert --
    397  *	Insert a line into the file.
    398  *
    399  * PUBLIC: int db_insert __P((SCR *, db_recno_t, CHAR_T *, size_t));
    400  */
    401 int
    402 db_insert(SCR *sp, db_recno_t lno, CHAR_T *p, size_t len)
    403 {
    404 #if defined(DEBUG) && 0
    405 	vtrace(sp, "insert before %lu: len %lu {%.*s}\n",
    406 	    (u_long)lno, (u_long)len, MIN(len, 20), p);
    407 #endif
    408 	return append(sp, lno - 1, p, len, LINE_INSERT, 1);
    409 }
    410 
    411 /*
    412  * db_set --
    413  *	Store a line in the file.
    414  *
    415  * PUBLIC: int db_set __P((SCR *, db_recno_t, CHAR_T *, size_t));
    416  */
    417 int
    418 db_set(SCR *sp, db_recno_t lno, CHAR_T *p, size_t len)
    419 {
    420 	DBT data, key;
    421 	EXF *ep;
    422 	char *fp;
    423 	size_t flen;
    424 
    425 #if defined(DEBUG) && 0
    426 	vtrace(sp, "replace line %lu: len %lu {%.*s}\n",
    427 	    (u_long)lno, (u_long)len, MIN(len, 20), p);
    428 #endif
    429 	/* Check for no underlying file. */
    430 	if ((ep = sp->ep) == NULL) {
    431 		ex_emsg(sp, NULL, EXM_NOFILEYET);
    432 		return (1);
    433 	}
    434 	if (ep->l_win && ep->l_win != sp->wp) {
    435 		ex_emsg(sp, NULL, EXM_LOCKED);
    436 		return 1;
    437 	}
    438 
    439 	/* Log before change. */
    440 	log_line(sp, lno, LOG_LINE_RESET_B);
    441 
    442 	INT2FILE(sp, p, len, fp, flen);
    443 
    444 	/* Update file. */
    445 	memset(&key, 0, sizeof(key));
    446 	key.data = &lno;
    447 	key.size = sizeof(lno);
    448 	memset(&data, 0, sizeof(data));
    449 	data.data = fp;
    450 	data.size = flen;
    451 	if ((sp->db_error = ep->db->put(ep->db, NULL, &key, &data, 0)) != 0) {
    452 		msgq(sp, M_DBERR, "006|unable to store line %lu", (u_long)lno);
    453 		return (1);
    454 	}
    455 
    456 	/* Flush the cache, update line count, before screen update. */
    457 	update_cache(sp, LINE_RESET, lno);
    458 
    459 	/* File now dirty. */
    460 	if (F_ISSET(ep, F_FIRSTMODIFY))
    461 		(void)rcv_init(sp);
    462 	F_SET(ep, F_MODIFIED);
    463 
    464 	/* Log after change. */
    465 	log_line(sp, lno, LOG_LINE_RESET_F);
    466 
    467 	/* Update screen. */
    468 	return (scr_update(sp, lno, LINE_RESET, 1));
    469 }
    470 
    471 /*
    472  * db_exist --
    473  *	Return if a line exists.
    474  *
    475  * PUBLIC: int db_exist __P((SCR *, db_recno_t));
    476  */
    477 int
    478 db_exist(SCR *sp, db_recno_t lno)
    479 {
    480 	EXF *ep;
    481 
    482 	/* Check for no underlying file. */
    483 	if ((ep = sp->ep) == NULL) {
    484 		ex_emsg(sp, NULL, EXM_NOFILEYET);
    485 		return (1);
    486 	}
    487 
    488 	if (lno == OOBLNO)
    489 		return (0);
    490 
    491 	/*
    492 	 * Check the last-line number cache.  Adjust the cached line
    493 	 * number for the lines used by the text input buffers.
    494 	 */
    495 	if (ep->c_nlines != OOBLNO)
    496 		return (lno <= (F_ISSET(sp, SC_TINPUT) ?
    497 		    ep->c_nlines + (((TEXT *)sp->tiq.cqh_last)->lno -
    498 		    ((TEXT *)sp->tiq.cqh_first)->lno) : ep->c_nlines));
    499 
    500 	/* Go get the line. */
    501 	return (!db_get(sp, lno, 0, NULL, NULL));
    502 }
    503 
    504 /*
    505  * db_last --
    506  *	Return the number of lines in the file.
    507  *
    508  * PUBLIC: int db_last __P((SCR *, db_recno_t *));
    509  */
    510 int
    511 db_last(SCR *sp, db_recno_t *lnop)
    512 {
    513 	DBT data, key;
    514 	DBC *dbcp;
    515 	EXF *ep;
    516 	db_recno_t lno;
    517 	CHAR_T *wp;
    518 	size_t wlen;
    519 
    520 	/* Check for no underlying file. */
    521 	if ((ep = sp->ep) == NULL) {
    522 		ex_emsg(sp, NULL, EXM_NOFILEYET);
    523 		return (1);
    524 	}
    525 
    526 	/*
    527 	 * Check the last-line number cache.  Adjust the cached line
    528 	 * number for the lines used by the text input buffers.
    529 	 */
    530 	if (ep->c_nlines != OOBLNO) {
    531 		*lnop = ep->c_nlines;
    532 		if (F_ISSET(sp, SC_TINPUT))
    533 			*lnop += ((TEXT *)sp->tiq.cqh_last)->lno -
    534 			    ((TEXT *)sp->tiq.cqh_first)->lno;
    535 		return (0);
    536 	}
    537 
    538 	memset(&key, 0, sizeof(key));
    539 	key.data = &lno;
    540 	key.size = sizeof(lno);
    541 	memset(&data, 0, sizeof(data));
    542 
    543 	if ((sp->db_error = ep->db->cursor(ep->db, NULL, &dbcp, 0)) != 0)
    544 	    goto err1;
    545 	switch (sp->db_error = dbcp->c_get(dbcp, &key, &data, DB_LAST)) {
    546         case DB_NOTFOUND:
    547 		*lnop = 0;
    548 		return (0);
    549 	default:
    550 		(void)dbcp->c_close(dbcp);
    551 alloc_err:
    552 err1:
    553 		msgq(sp, M_DBERR, "007|unable to get last line");
    554 		*lnop = 0;
    555 		return (1);
    556         case 0:
    557 		;
    558 	}
    559 
    560 	memcpy(&lno, key.data, sizeof(lno));
    561 
    562 	if (lno != sp->c_lno) {
    563 	    FILE2INT(sp, data.data, data.size, wp, wlen);
    564 
    565 	    /* Fill the cache. */
    566 	    BINC_GOTOW(sp, sp->c_lp, sp->c_blen, wlen);
    567 	    MEMCPYW(sp->c_lp, wp, wlen);
    568 	    sp->c_lno = lno;
    569 	    sp->c_len = wlen;
    570 	}
    571 	ep->c_nlines = lno;
    572 
    573 	(void)dbcp->c_close(dbcp);
    574 
    575 	/* Return the value. */
    576 	*lnop = (F_ISSET(sp, SC_TINPUT) &&
    577 	    ((TEXT *)sp->tiq.cqh_last)->lno > lno ?
    578 	    ((TEXT *)sp->tiq.cqh_last)->lno : lno);
    579 	return (0);
    580 }
    581 
    582 /*
    583  * db_err --
    584  *	Report a line error.
    585  *
    586  * PUBLIC: void db_err __P((SCR *, db_recno_t));
    587  */
    588 void
    589 db_err(SCR *sp, db_recno_t lno)
    590 {
    591 	msgq(sp, M_ERR,
    592 	    "008|Error: unable to retrieve line %lu", (u_long)lno);
    593 }
    594 
    595 /*
    596  * scr_update --
    597  *	Update all of the screens that are backed by the file that
    598  *	just changed.
    599  *
    600  * PUBLIC: int scr_update __P((SCR *sp, db_recno_t lno,
    601  * PUBLIC: 			lnop_t op, int current));
    602  */
    603 int
    604 scr_update(SCR *sp, db_recno_t lno, lnop_t op, int current)
    605 {
    606 	EXF *ep;
    607 	SCR *tsp;
    608 	WIN *wp;
    609 
    610 	if (F_ISSET(sp, SC_EX))
    611 		return (0);
    612 
    613 	/* XXXX goes outside of window */
    614 	ep = sp->ep;
    615 	if (ep->refcnt != 1)
    616 		for (wp = sp->gp->dq.cqh_first; wp != (void *)&sp->gp->dq;
    617 		    wp = wp->q.cqe_next)
    618 			for (tsp = wp->scrq.cqh_first;
    619 			    tsp != (void *)&wp->scrq; tsp = tsp->q.cqe_next)
    620 			if (sp != tsp && tsp->ep == ep)
    621 				if (vs_change(tsp, lno, op))
    622 					return (1);
    623 	return (current ? vs_change(sp, lno, op) : 0);
    624 }
    625 
    626 /*
    627  * PUBLIC: void update_cache __P((SCR *sp, lnop_t op, db_recno_t lno));
    628  */
    629 void
    630 update_cache(SCR *sp, lnop_t op, db_recno_t lno)
    631 {
    632 	SCR* scrp;
    633 	EXF *ep;
    634 
    635 	ep = sp->ep;
    636 
    637 	/* Flush the cache, update line count, before screen update. */
    638 	/* The flushing is probably not needed, since it was incorrect
    639 	 * for db_insert.  It might be better to adjust it, like
    640 	 * marks, @ and global
    641 	 */
    642 	for (scrp = ep->scrq.cqh_first; scrp != (void *)&ep->scrq;
    643 	    scrp = scrp->eq.cqe_next)
    644 		switch (op) {
    645 		case LINE_INSERT:
    646 		case LINE_DELETE:
    647 			if (lno <= scrp->c_lno)
    648 				scrp->c_lno = OOBLNO;
    649 			break;
    650 		case LINE_RESET:
    651 			if (lno == scrp->c_lno)
    652 				scrp->c_lno = OOBLNO;
    653 			break;
    654 		}
    655 
    656 	if (ep->c_nlines != OOBLNO)
    657 		switch (op) {
    658 		case LINE_INSERT:
    659 			++ep->c_nlines;
    660 			break;
    661 		case LINE_DELETE:
    662 			--ep->c_nlines;
    663 			break;
    664 		}
    665 }
    666 
    667 /*
    668  * PUBLIC: int line_insdel __P((SCR *sp, lnop_t op, db_recno_t lno));
    669  */
    670 int
    671 line_insdel(SCR *sp, lnop_t op, db_recno_t lno)
    672 {
    673 	int rval;
    674 
    675 	/* Update marks, @ and global commands. */
    676 	rval = 0;
    677 	if (mark_insdel(sp, op, lno))
    678 		rval = 1;
    679 	if (ex_g_insdel(sp, op, lno))
    680 		rval = 1;
    681 
    682 	return rval;
    683 }
    684 
    685 #ifdef USE_DB4_LOGGING
    686 #define VI_DB_INIT_LOG	DB_INIT_LOG
    687 #else
    688 #define VI_DB_INIT_LOG	0
    689 #endif
    690 
    691 /*
    692  * PUBLIC: int db_setup __P((SCR *, EXF *));
    693  */
    694 int
    695 db_setup(SCR *sp, EXF *ep)
    696 {
    697 	char path[MAXPATHLEN];
    698 	int fd;
    699 	DB_ENV	*env;
    700 
    701 	(void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", O_STR(sp, O_RECDIR));
    702 	if ((fd = mkstemp(path)) == -1) {
    703 		msgq(sp, M_SYSERR, "%s", path);
    704 		goto err;
    705 	}
    706 	(void)close(fd);
    707 	(void)unlink(path);
    708 	if (mkdir(path, S_IRWXU)) {
    709 		msgq(sp, M_SYSERR, "%s", path);
    710 		goto err;
    711 	}
    712 	if (db_env_create(&env, 0)) {
    713 		msgq(sp, M_ERR, "env_create");
    714 		goto err;
    715 	}
    716 #ifdef USE_DB4_LOGGING
    717 	if ((sp->db_error = vi_db_init_recover(env))) {
    718 		msgq(sp, M_DBERR, "init_recover");
    719 		goto err;
    720 	}
    721 	if ((sp->db_error = __vi_init_recover(env))) {
    722 		msgq(sp, M_DBERR, "init_recover");
    723 		goto err;
    724 	}
    725 #endif
    726 	if ((sp->db_error = db_env_open(env, path,
    727 	    DB_PRIVATE | DB_CREATE | DB_INIT_MPOOL | VI_DB_THREAD
    728 	    | VI_DB_INIT_LOG, 0)) != 0) {
    729 		msgq(sp, M_DBERR, "env->open");
    730 		goto err;
    731 	}
    732 
    733 	if ((ep->env_path = strdup(path)) == NULL) {
    734 		msgq(sp, M_SYSERR, NULL);
    735 		(void)rmdir(path);
    736 		goto err;
    737 	}
    738 	ep->env = env;
    739 	return 0;
    740 err:
    741 	return 1;
    742 }
    743 
    744 /* Round up v to the nearest power of 2 */
    745 static size_t round_up(size_t v)
    746 {
    747 	ssize_t old_v = v;
    748 
    749 	while (v) {
    750 		old_v = v;
    751 		v ^= v & -v;
    752 	}
    753 	return old_v << 1;
    754 }
    755 
    756 /*
    757  * PUBLIC: int db_msg_open __P((SCR *, char *, DB **));
    758  */
    759 int db_msg_open(SCR *sp, char *file, DB **dbp)
    760 {
    761 	return  (sp->db_error = db_create(dbp, 0, 0)) != 0 ||
    762 		(sp->db_error = (*dbp)->set_re_source(*dbp, file)) != 0 ||
    763 		(sp->db_error = db_open(*dbp, NULL, DB_RECNO, 0, 0)) != 0;
    764 }
    765 
    766 /*
    767  * PUBLIC: int db_init __P((SCR *, EXF *, char *, char *, size_t, int *));
    768  */
    769 int
    770 db_init(SCR *sp, EXF *ep, char *rcv_name, char *oname, size_t psize, int *open_err)
    771 {
    772 	if (db_setup(sp, ep))
    773 		return 1;
    774 
    775 	/* Open a db structure. */
    776 	if ((sp->db_error = db_create(&ep->db, 0, 0)) != 0) {
    777 		msgq(sp, M_DBERR, "db_create");
    778 		return 1;
    779 	}
    780 
    781 	ep->db->set_re_delim(ep->db, '\n');		/* Always set. */
    782 	ep->db->set_pagesize(ep->db, round_up(psize));
    783 	ep->db->set_flags(ep->db, DB_RENUMBER | DB_SNAPSHOT);
    784 	if (rcv_name == NULL)
    785 		ep->db->set_re_source(ep->db, oname);
    786 
    787 /*
    788  * Don't let db use mmap when using fcntl for locking
    789  */
    790 #ifdef HAVE_LOCK_FCNTL
    791 #define NOMMAPIFFCNTL DB_NOMMAP
    792 #else
    793 #define NOMMAPIFFCNTL 0
    794 #endif
    795 
    796 #define _DB_OPEN_MODE	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH
    797 
    798 	if ((sp->db_error = db_open(ep->db, ep->rcv_path, DB_RECNO,
    799 	    ((rcv_name == 0) ? DB_TRUNCATE : 0) | VI_DB_THREAD | NOMMAPIFFCNTL,
    800 	    _DB_OPEN_MODE)) != 0) {
    801 		msgq_str(sp,
    802 		    M_DBERR, rcv_name == NULL ? oname : rcv_name, "%s");
    803 		/*
    804 		 * !!!
    805 		 * Historically, vi permitted users to edit files that couldn't
    806 		 * be read.  This isn't useful for single files from a command
    807 		 * line, but it's quite useful for "vi *.c", since you can skip
    808 		 * past files that you can't read.
    809 		 */
    810 		ep->db = NULL; /* Don't close it; it wasn't opened */
    811 
    812 		*open_err = 1;
    813 		return 1;
    814 	}
    815 
    816 	/* re_source is loaded into the database.
    817 	 * Close it and reopen it in the environment.
    818 	 */
    819 	if ((sp->db_error = ep->db->close(ep->db, 0))) {
    820 		msgq(sp, M_DBERR, "close");
    821 		return 1;
    822 	}
    823 	if ((sp->db_error = db_create(&ep->db, ep->env, 0)) != 0) {
    824 		msgq(sp, M_DBERR, "db_create 2");
    825 		return 1;
    826 	}
    827 	if ((sp->db_error = db_open(ep->db, ep->rcv_path, DB_RECNO,
    828 	    VI_DB_THREAD | NOMMAPIFFCNTL, _DB_OPEN_MODE)) != 0) {
    829 		msgq_str(sp,
    830 		    M_DBERR, ep->rcv_path, "%s");
    831 		return 1;
    832 	}
    833 
    834 	return 0;
    835 }
    836