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 #include <sys/cdefs.h>
13 #if 0
14 #ifndef lint
15 static const char sccsid[] = "Id: db1.c,v 10.1 2002/03/09 12:53:57 skimo Exp  (Berkeley) Date: 2002/03/09 12:53:57 ";
16 #endif /* not lint */
17 #else
18 __RCSID("$NetBSD: vi_db1.c,v 1.10 2018/08/07 11:25:45 rin Exp $");
19 #endif
20 
21 #include <sys/types.h>
22 #include <sys/queue.h>
23 #include <sys/time.h>
24 #include <sys/stat.h>
25 
26 #include <bitstring.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <limits.h>
30 #include <stdio.h>
31 #include <string.h>
32 
33 #include "common.h"
34 #include "../vi/vi.h"
35 #include "dbinternal.h"
36 
37 /*
38  * db_eget --
39  *        Front-end to db_get, special case handling for empty files.
40  *
41  * PUBLIC: int db_eget __P((SCR *, db_recno_t, CHAR_T **, size_t *, int *));
42  */
43 int
db_eget(SCR * sp,db_recno_t lno,CHAR_T ** pp,size_t * lenp,int * isemptyp)44 db_eget(SCR *sp, db_recno_t lno, CHAR_T **pp, size_t *lenp, int *isemptyp)
45 
46                                                             /* Line number. */
47                                                             /* Pointer store. */
48                                                             /* Length store. */
49 
50 {
51           db_recno_t l1;
52 
53           if (isemptyp != NULL)
54                     *isemptyp = 0;
55 
56           /* If the line exists, simply return it. */
57           if (!db_get(sp, lno, 0, pp, lenp))
58                     return (0);
59 
60           /*
61            * If the user asked for line 0 or line 1, i.e. the only possible
62            * line in an empty file, find the last line of the file; db_last
63            * fails loudly.
64            */
65           if ((lno == 0 || lno == 1) && db_last(sp, &l1))
66                     return (1);
67 
68           /* If the file isn't empty, fail loudly. */
69           if ((lno != 0 && lno != 1) || l1 != 0) {
70                     db_err(sp, lno);
71                     return (1);
72           }
73 
74           if (isemptyp != NULL)
75                     *isemptyp = 1;
76 
77           return (1);
78 }
79 
80 /*
81  * db_get --
82  *        Look in the text buffers for a line, followed by the cache, followed
83  *        by the database.
84  *
85  * PUBLIC: int db_get __P((SCR *, db_recno_t, u_int32_t, CHAR_T **, size_t *));
86  */
87 int
db_get(SCR * sp,db_recno_t lno,u_int32_t flags,CHAR_T ** pp,size_t * lenp)88 db_get(SCR *sp, db_recno_t lno, u_int32_t flags, CHAR_T **pp, size_t *lenp)
89 
90                                                             /* Line number. */
91 
92                                                             /* Pointer store. */
93                                                             /* Length store. */
94 {
95           DBT data, key;
96           EXF *ep;
97           TEXT *tp;
98           db_recno_t l1, l2;
99           const CHAR_T *wp;
100           size_t wlen;
101 
102           /*
103            * The underlying recno stuff handles zero by returning NULL, but
104            * have to have an OOB condition for the look-aside into the input
105            * buffer anyway.
106            */
107           if (lno == 0)
108                     goto err1;
109 
110           /* Check for no underlying file. */
111           if ((ep = sp->ep) == NULL) {
112                     ex_emsg(sp, NULL, EXM_NOFILEYET);
113                     goto err3;
114           }
115 
116           if (LF_ISSET(DBG_NOCACHE))
117                     goto nocache;
118 
119           /*
120            * Look-aside into the TEXT buffers and see if the line we want
121            * is there.
122            */
123           if (F_ISSET(sp, SC_TINPUT)) {
124                     l1 = TAILQ_FIRST(&sp->tiq)->lno;
125                     l2 = TAILQ_LAST(&sp->tiq, _texth)->lno;
126                     if (l1 <= lno && l2 >= lno) {
127 #if defined(DBDEBUG) && defined(TRACE)
128                               vtrace(
129                                   "retrieve TEXT buffer line %lu\n", (u_long)lno);
130 #endif
131                               for (tp = TAILQ_FIRST(&sp->tiq);
132                                   tp->lno != lno; tp = TAILQ_NEXT(tp, q));
133                               if (lenp != NULL)
134                                         *lenp = tp->len;
135                               if (pp != NULL)
136                                         *pp = tp->lb;
137                               return (0);
138                     }
139                     /*
140                      * Adjust the line number for the number of lines used
141                      * by the text input buffers.
142                      */
143                     if (lno > l2)
144                               lno -= l2 - l1;
145           }
146 
147           /* Look-aside into the cache, and see if the line we want is there. */
148           if (lno == sp->c_lno) {
149 #if defined(DBDEBUG) && defined(TRACE)
150                     vtrace("retrieve cached line %lu\n", (u_long)lno);
151 #endif
152                     if (lenp != NULL)
153                               *lenp = sp->c_len;
154                     if (pp != NULL)
155                               *pp = sp->c_lp;
156                     return (0);
157           }
158           sp->c_lno = OOBLNO;
159 
160 nocache:
161           /* Get the line from the underlying database. */
162           key.data = &lno;
163           key.size = sizeof(lno);
164           switch (ep->db->get(ep->db, &key, &data, 0)) {
165         case -1:
166                     goto err2;
167           case 1:
168 err1:               if (LF_ISSET(DBG_FATAL))
169 err2:                         db_err(sp, lno);
170 alloc_err:
171 err3:               if (lenp != NULL)
172                               *lenp = 0;
173                     if (pp != NULL)
174                               *pp = NULL;
175                     return (1);
176           default:
177                     break;
178           }
179 
180           if (FILE2INT(sp, data.data, data.size, wp, wlen)) {
181               if (!F_ISSET(sp, SC_CONV_ERROR)) {
182                     F_SET(sp, SC_CONV_ERROR);
183                     msgq(sp, M_ERR, "324|Conversion error on line %d", lno);
184               }
185               goto err3;
186           }
187 
188           /* Reset the cache. */
189           if (wp != data.data) {
190               BINC_GOTOW(sp, sp->c_lp, sp->c_blen, wlen);
191               MEMCPYW(sp->c_lp, wp, wlen);
192           } else
193               sp->c_lp = data.data;
194           sp->c_lno = lno;
195           sp->c_len = wlen;
196 
197 #if defined(DBDEBUG) && defined(TRACE)
198           vtrace("retrieve DB line %lu\n", (u_long)lno);
199 #endif
200           if (lenp != NULL)
201                     *lenp = wlen;
202           if (pp != NULL)
203                     *pp = sp->c_lp;
204           return (0);
205 }
206 
207 /*
208  * db_delete --
209  *        Delete a line from the file.
210  *
211  * PUBLIC: int db_delete __P((SCR *, db_recno_t));
212  */
213 int
db_delete(SCR * sp,db_recno_t lno)214 db_delete(SCR *sp, db_recno_t lno)
215 {
216           DBT key;
217           EXF *ep;
218 
219 #if defined(DBDEBUG) && defined(TRACE)
220           vtrace("delete line %lu\n", (u_long)lno);
221 #endif
222           /* Check for no underlying file. */
223           if ((ep = sp->ep) == NULL) {
224                     ex_emsg(sp, NULL, EXM_NOFILEYET);
225                     return (1);
226           }
227           if (ep->l_win && ep->l_win != sp->wp) {
228                     ex_emsg(sp, NULL, EXM_LOCKED);
229                     return 1;
230           }
231 
232           /* Update marks, @ and global commands. */
233           if (mark_insdel(sp, LINE_DELETE, lno))
234                     return (1);
235           if (ex_g_insdel(sp, LINE_DELETE, lno))
236                     return (1);
237 
238           /* Log change. */
239           log_line(sp, lno, LOG_LINE_DELETE_B);
240 
241           /* Update file. */
242           key.data = &lno;
243           key.size = sizeof(lno);
244           sp->db_error = ep->db->del(ep->db, &key, 0);
245           if (sp->db_error != 0) {
246                     if (sp->db_error == -1)
247                               sp->db_error = errno;
248 
249                     msgq(sp, M_DBERR, "003|unable to delete line %lu",
250                         (u_long)lno);
251                     return (1);
252           }
253 
254           /* Flush the cache, update line count, before screen update. */
255           update_cache(sp, LINE_DELETE, lno);
256 
257           /* File now modified. */
258           if (F_ISSET(ep, F_FIRSTMODIFY))
259                     (void)rcv_init(sp);
260           F_SET(ep, F_MODIFIED);
261 
262           /* Log after change. */
263           log_line(sp, lno, LOG_LINE_DELETE_F);
264 
265           /* Update screen. */
266           return (scr_update(sp, lno, LINE_DELETE, 1));
267 }
268 
269 /*
270  * db_append --
271  *        Append a line into the file.
272  *
273  * PUBLIC: int db_append __P((SCR *, int, db_recno_t, const CHAR_T *, size_t));
274  */
275 int
db_append(SCR * sp,int update,db_recno_t lno,const CHAR_T * p,size_t len)276 db_append(SCR *sp, int update, db_recno_t lno, const CHAR_T *p, size_t len)
277 {
278           DBT data, key;
279           EXF *ep;
280           const char *fp;
281           size_t flen;
282           int rval;
283 
284 #if defined(DBDEBUG) && defined(TRACE)
285           vtrace("append to %lu: len %u {%.*s}\n", lno, len, MIN(len, 20), p);
286 #endif
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           INT2FILE(sp, p, len, fp, flen);
301 
302           /* Update file. */
303           key.data = &lno;
304           key.size = sizeof(lno);
305           data.data = __UNCONST(fp);
306           data.size = flen;
307           if (ep->db->put(ep->db, &key, &data, R_IAFTER)) {
308                     msgq(sp, M_DBERR, "004|unable to append to line %lu",
309                               (u_long)lno);
310                     return (1);
311           }
312 
313           /* Flush the cache, update line count, before screen update. */
314           update_cache(sp, LINE_INSERT, lno);
315 
316           /* File now dirty. */
317           if (F_ISSET(ep, F_FIRSTMODIFY))
318                     (void)rcv_init(sp);
319           F_SET(ep, F_MODIFIED);
320 
321           /* Log after change. */
322           log_line(sp, lno + 1, LOG_LINE_APPEND_F);
323 
324           /* Update marks, @ and global commands. */
325           rval = 0;
326           if (mark_insdel(sp, LINE_INSERT, lno + 1))
327                     rval = 1;
328           if (ex_g_insdel(sp, LINE_INSERT, lno + 1))
329                     rval = 1;
330 
331           /*
332            * Update screen.
333            *
334            * XXX
335            * Nasty hack.  If multiple lines are input by the user, they aren't
336            * committed until an <ESC> is entered.  The problem is the screen was
337            * updated/scrolled as each line was entered.  So, when this routine
338            * is called to copy the new lines from the cut buffer into the file,
339            * it has to know not to update the screen again.
340            */
341           return (scr_update(sp, lno, LINE_APPEND, update) || rval);
342 }
343 
344 /*
345  * db_insert --
346  *        Insert a line into the file.
347  *
348  * PUBLIC: int db_insert __P((SCR *, db_recno_t, CHAR_T *, size_t));
349  */
350 int
db_insert(SCR * sp,db_recno_t lno,CHAR_T * p,size_t len)351 db_insert(SCR *sp, db_recno_t lno, CHAR_T *p, size_t len)
352 {
353           DBT data, key;
354           EXF *ep;
355           const char *fp;
356           size_t flen;
357           int rval;
358 
359 #if defined(DBDEBUG) && defined(TRACE)
360           vtrace("insert before %lu: len %lu {%.*s}\n",
361               (u_long)lno, (u_long)len, MIN(len, 20), p);
362 #endif
363           /* Check for no underlying file. */
364           if ((ep = sp->ep) == NULL) {
365                     ex_emsg(sp, NULL, EXM_NOFILEYET);
366                     return (1);
367           }
368           if (ep->l_win && ep->l_win != sp->wp) {
369                     ex_emsg(sp, NULL, EXM_LOCKED);
370                     return 1;
371           }
372 
373           /* Log before change. */
374           log_line(sp, lno, LOG_LINE_APPEND_B);
375 
376           INT2FILE(sp, p, len, fp, flen);
377 
378           /* Update file. */
379           key.data = &lno;
380           key.size = sizeof(lno);
381           data.data = __UNCONST(fp);
382           data.size = flen;
383           if (ep->db->put(ep->db, &key, &data, R_IBEFORE)) {
384                     msgq(sp, M_SYSERR,
385                         "005|unable to insert at line %lu", (u_long)lno);
386                     return (1);
387           }
388 
389           /* Flush the cache, update line count, before screen update. */
390           update_cache(sp, LINE_INSERT, lno);
391 
392           /* File now dirty. */
393           if (F_ISSET(ep, F_FIRSTMODIFY))
394                     (void)rcv_init(sp);
395           F_SET(ep, F_MODIFIED);
396 
397           /* Log after change. */
398           log_line(sp, lno, LOG_LINE_APPEND_F);
399 
400           /* Update marks, @ and global commands. */
401           rval = 0;
402           if (mark_insdel(sp, LINE_INSERT, lno))
403                     rval = 1;
404           if (ex_g_insdel(sp, LINE_INSERT, lno))
405                     rval = 1;
406 
407           /* Update screen. */
408           return (scr_update(sp, lno, LINE_INSERT, 1) || rval);
409 }
410 
411 /*
412  * db_set --
413  *        Store a line in the file.
414  *
415  * PUBLIC: int db_set __P((SCR *, db_recno_t, const CHAR_T *, size_t));
416  */
417 int
db_set(SCR * sp,db_recno_t lno,const CHAR_T * p,size_t len)418 db_set(SCR *sp, db_recno_t lno, const CHAR_T *p, size_t len)
419 {
420           DBT data, key;
421           EXF *ep;
422           const char *fp;
423           size_t flen;
424 
425 #if defined(DBDEBUG) && defined(TRACE)
426           vtrace("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           key.data = &lno;
446           key.size = sizeof(lno);
447           data.data = __UNCONST(fp);
448           data.size = flen;
449           sp->db_error =
450                     ep->db->put(ep->db, &key, &data, 0);
451           if (sp->db_error != 0) {
452                     if (sp->db_error == -1)
453                               sp->db_error = errno;
454 
455                     msgq(sp, M_DBERR, "006|unable to store line %lu", (u_long)lno);
456                     return (1);
457           }
458 
459           /* Flush the cache, before logging or screen update. */
460           update_cache(sp, LINE_RESET, lno);
461 
462           /* File now dirty. */
463           if (F_ISSET(ep, F_FIRSTMODIFY))
464                     (void)rcv_init(sp);
465           F_SET(ep, F_MODIFIED);
466 
467           /* Log after change. */
468           log_line(sp, lno, LOG_LINE_RESET_F);
469 
470           /* Update screen. */
471           return (scr_update(sp, lno, LINE_RESET, 1));
472 }
473 
474 /*
475  * db_exist --
476  *        Return if a line exists.
477  *
478  * PUBLIC: int db_exist __P((SCR *, db_recno_t));
479  */
480 int
db_exist(SCR * sp,db_recno_t lno)481 db_exist(SCR *sp, db_recno_t lno)
482 {
483           EXF *ep;
484 
485           /* Check for no underlying file. */
486           if ((ep = sp->ep) == NULL) {
487                     ex_emsg(sp, NULL, EXM_NOFILEYET);
488                     return (1);
489           }
490 
491           if (lno == OOBLNO)
492                     return (0);
493 
494           /*
495            * Check the last-line number cache.  Adjust the cached line
496            * number for the lines used by the text input buffers.
497            */
498           if (ep->c_nlines != OOBLNO)
499                     return (lno <= (F_ISSET(sp, SC_TINPUT) ?
500                         ep->c_nlines + (TAILQ_LAST(&sp->tiq, _texth)->lno -
501                         TAILQ_FIRST(&sp->tiq)->lno) : ep->c_nlines));
502 
503           /* Go get the line. */
504           return (!db_get(sp, lno, 0, NULL, NULL));
505 }
506 
507 /*
508  * db_last --
509  *        Return the number of lines in the file.
510  *
511  * PUBLIC: int db_last __P((SCR *, db_recno_t *));
512  */
513 int
db_last(SCR * sp,db_recno_t * lnop)514 db_last(SCR *sp, db_recno_t *lnop)
515 {
516           DBT data, key;
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 += TAILQ_LAST(&sp->tiq, _texth)->lno -
536                                   TAILQ_FIRST(&sp->tiq)->lno;
537                     return (0);
538           }
539 
540           key.data = &lno;
541           key.size = sizeof(lno);
542 
543           sp->db_error = ep->db->seq(ep->db, &key, &data, R_LAST);
544           switch (sp->db_error) {
545           case 1:
546                     *lnop = 0;
547                     return (0);
548           case -1:
549                     sp->db_error = errno;
550 alloc_err:
551                     msgq(sp, M_DBERR, "007|unable to get last line");
552                     *lnop = 0;
553                     return (1);
554           default:
555                     break;
556           }
557 
558           memcpy(&lno, key.data, sizeof(lno));
559 
560           if (lno != sp->c_lno) {
561               FILE2INT(sp, data.data, data.size, wp, wlen);
562 
563               /* Fill the cache. */
564               if (wp != data.data) {
565                     BINC_GOTOW(sp, sp->c_lp, sp->c_blen, wlen);
566                     MEMCPYW(sp->c_lp, wp, wlen);
567               } else
568                     sp->c_lp = data.data;
569               sp->c_lno = lno;
570               sp->c_len = wlen;
571           }
572           ep->c_nlines = lno;
573 
574           /* Return the value. */
575           *lnop = (F_ISSET(sp, SC_TINPUT) &&
576               TAILQ_LAST(&sp->tiq, _texth)->lno > lno ?
577               TAILQ_LAST(&sp->tiq, _texth)->lno : lno);
578           return (0);
579 }
580 
581 /*
582  * db_err --
583  *        Report a line error.
584  *
585  * PUBLIC: void db_err __P((SCR *, db_recno_t));
586  */
587 void
db_err(SCR * sp,db_recno_t lno)588 db_err(SCR *sp, db_recno_t lno)
589 {
590           msgq(sp, M_ERR,
591               "008|Error: unable to retrieve line %lu", (u_long)lno);
592 }
593 
594 /*
595  * scr_update --
596  *        Update all of the screens that are backed by the file that
597  *        just changed.
598  * PUBLIC: int scr_update __P((SCR *sp, db_recno_t lno,
599  * PUBLIC:                      lnop_t op, int current));
600  */
601 int
scr_update(SCR * sp,db_recno_t lno,lnop_t op,int current)602 scr_update(SCR *sp, db_recno_t lno, lnop_t op, int current)
603 {
604           EXF *ep;
605           SCR *tsp;
606           WIN *wp;
607 
608           if (F_ISSET(sp, SC_EX))
609                     return (0);
610 
611           /* XXXX goes outside of window */
612           ep = sp->ep;
613           if (ep->refcnt != 1)
614                     TAILQ_FOREACH(wp, &sp->gp->dq, q)
615                               TAILQ_FOREACH(tsp, &wp->scrq, q)
616                                         if (sp != tsp && tsp->ep == ep)
617                                                   if (vs_change(tsp, lno, op))
618                                                             return (1);
619           return (current ? vs_change(sp, lno, op) : 0);
620 }
621 
622 /*
623  * PUBLIC: void update_cache __P((SCR *sp, lnop_t op, db_recno_t lno));
624  */
625 void
update_cache(SCR * sp,lnop_t op,db_recno_t lno)626 update_cache(SCR *sp, lnop_t op, db_recno_t lno)
627 {
628           SCR* scrp;
629           EXF *ep;
630 
631           ep = sp->ep;
632 
633           /* Flush the cache, update line count, before screen update. */
634           /* The flushing is probably not needed, since it was incorrect
635            * for db_insert.  It might be better to adjust it, like
636            * marks, @ and global
637            */
638           TAILQ_FOREACH(scrp, &ep->scrq, eq)
639                     switch (op) {
640                     case LINE_INSERT:
641                     case LINE_DELETE:
642                               if (lno <= scrp->c_lno)
643                                         scrp->c_lno = OOBLNO;
644                               break;
645                     case LINE_RESET:
646                               if (lno == scrp->c_lno)
647                                         scrp->c_lno = OOBLNO;
648                               break;
649                     case LINE_APPEND:
650                               break;
651                     }
652 
653           if (ep->c_nlines != OOBLNO)
654                     switch (op) {
655                     case LINE_INSERT:
656                               ++ep->c_nlines;
657                               break;
658                     case LINE_DELETE:
659                               --ep->c_nlines;
660                               break;
661                     case LINE_APPEND:
662                     case LINE_RESET:
663                               break;
664                     }
665 }
666 
667 /*
668  * PUBLIC: int db_msg_open __P((SCR *, const char *, DB **));
669  */
db_msg_open(SCR * sp,const char * file,DB ** dbp)670 int db_msg_open(SCR *sp, const char *file, DB **dbp)
671 {
672           *dbp = dbopen(file, O_NONBLOCK | O_RDONLY, 0, DB_RECNO, NULL);
673 
674           return *dbp == NULL;
675 }
676 
677 /*
678  * PUBLIC: int db_init __P((SCR *, EXF *, char *, char *, size_t, int *));
679  */
680 int
db_init(SCR * sp,EXF * ep,char * rcv_name,char * oname,size_t psize,int * open_err)681 db_init(SCR *sp, EXF *ep, char *rcv_name, char *oname, size_t psize, int *open_err)
682 {
683           RECNOINFO oinfo;
684 
685           memset(&oinfo, 0, sizeof(RECNOINFO));
686           oinfo.bval = '\n';                      /* Always set. */
687           /*
688            * If we are not recovering, set the pagesize and arrange to
689            * first get a snapshot of the file.
690            */
691           if (rcv_name == NULL) {
692                     oinfo.psize = psize;
693                     oinfo.flags = R_SNAPSHOT;
694           }
695           /*
696            * Always set the btree name, otherwise we are going to be using
697            * an in-memory database for the btree.
698            */
699           oinfo.bfname = ep->rcv_path;
700 
701 #define _DB_OPEN_MODE         S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH
702 
703           ep->db = dbopen(rcv_name == NULL ? oname : NULL,
704               O_NONBLOCK | O_RDONLY, _DB_OPEN_MODE, DB_RECNO, &oinfo);
705 
706           if (!ep->db) {
707                     msgq_str(sp,
708                         M_DBERR, rcv_name == NULL ? oname : rcv_name, "%s");
709                     /*
710                      * !!!
711                      * Historically, vi permitted users to edit files that couldn't
712                      * be read.  This isn't useful for single files from a command
713                      * line, but it's quite useful for "vi *.c", since you can skip
714                      * past files that you can't read.
715                      */
716                     ep->db = NULL; /* Don't close it; it wasn't opened */
717 
718                     *open_err = 1;
719                     return 1;
720           } else {
721                     /*
722                      * We always sync the underlying btree so that the header
723                      * is written first
724                      */
725                     ep->db->sync(ep->db, R_RECNOSYNC);
726           }
727 
728           return 0;
729 }
730 
731 const char *
db_strerror(int error)732 db_strerror(int error)
733 {
734           return error > 0 ? strerror(error) : "record not found";
735 }
736