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