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