xref: /freebsd/contrib/nvi/common/line.c (revision ef0cb5db0af0d5d5b75b74f8e534fe601b7176d7)
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.27 2015/04/03 14:17:21 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(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(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(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 
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 *)TAILQ_FIRST(sp->tiq))->lno;
119 		l2 = ((TEXT *)TAILQ_LAST(sp->tiq, _texth))->lno;
120 		if (l1 <= lno && l2 >= lno) {
121 #if defined(DEBUG) && 0
122 	TRACE(sp, "retrieve TEXT buffer line %lu\n", (u_long)lno);
123 #endif
124 			for (tp = TAILQ_FIRST(sp->tiq);
125 			    tp->lno != lno; tp = TAILQ_NEXT(tp, q));
126 			if (lenp != NULL)
127 				*lenp = tp->len;
128 			if (pp != NULL)
129 				*pp = tp->lb;
130 			return (0);
131 		}
132 		/*
133 		 * Adjust the line number for the number of lines used
134 		 * by the text input buffers.
135 		 */
136 		if (lno > l2)
137 			lno -= l2 - l1;
138 	}
139 
140 	/* Look-aside into the cache, and see if the line we want is there. */
141 	if (lno == ep->c_lno) {
142 #if defined(DEBUG) && 0
143 	TRACE(sp, "retrieve cached line %lu\n", (u_long)lno);
144 #endif
145 		if (lenp != NULL)
146 			*lenp = ep->c_len;
147 		if (pp != NULL)
148 			*pp = ep->c_lp;
149 		return (0);
150 	}
151 	ep->c_lno = OOBLNO;
152 
153 nocache:
154 	/* Get the line from the underlying database. */
155 	key.data = &lno;
156 	key.size = sizeof(lno);
157 	switch (ep->db->get(ep->db, &key, &data, 0)) {
158 	case -1:
159 		goto err2;
160 	case 1:
161 err1:		if (LF_ISSET(DBG_FATAL))
162 err2:			db_err(sp, lno);
163 alloc_err:
164 err3:		if (lenp != NULL)
165 			*lenp = 0;
166 		if (pp != NULL)
167 			*pp = NULL;
168 		return (1);
169 	}
170 
171 	if (FILE2INT(sp, data.data, data.size, wp, wlen)) {
172 		if (!F_ISSET(sp, SC_CONV_ERROR)) {
173 			F_SET(sp, SC_CONV_ERROR);
174 			msgq(sp, M_ERR, "324|Conversion error on line %d", lno);
175 		}
176 		goto err3;
177 	}
178 
179 	/* Reset the cache. */
180 	if (wp != data.data) {
181 		BINC_GOTOW(sp, ep->c_lp, ep->c_blen, wlen);
182 		MEMCPY(ep->c_lp, wp, wlen);
183 	} else
184 		ep->c_lp = data.data;
185 	ep->c_lno = lno;
186 	ep->c_len = wlen;
187 
188 #if defined(DEBUG) && 0
189 	TRACE(sp, "retrieve DB line %lu\n", (u_long)lno);
190 #endif
191 	if (lenp != NULL)
192 		*lenp = wlen;
193 	if (pp != NULL)
194 		*pp = ep->c_lp;
195 	return (0);
196 }
197 
198 /*
199  * db_delete --
200  *	Delete a line from the file.
201  *
202  * PUBLIC: int db_delete(SCR *, recno_t);
203  */
204 int
205 db_delete(
206 	SCR *sp,
207 	recno_t lno)
208 {
209 	DBT key;
210 	EXF *ep;
211 
212 #if defined(DEBUG) && 0
213 	TRACE(sp, "delete line %lu\n", (u_long)lno);
214 #endif
215 	/* Check for no underlying file. */
216 	if ((ep = sp->ep) == NULL) {
217 		ex_emsg(sp, NULL, EXM_NOFILEYET);
218 		return (1);
219 	}
220 
221 	/* Update marks, @ and global commands. */
222 	if (mark_insdel(sp, LINE_DELETE, lno))
223 		return (1);
224 	if (ex_g_insdel(sp, LINE_DELETE, lno))
225 		return (1);
226 
227 	/* Log change. */
228 	log_line(sp, lno, LOG_LINE_DELETE);
229 
230 	/* Update file. */
231 	key.data = &lno;
232 	key.size = sizeof(lno);
233 	if (ep->db->del(ep->db, &key, 0) == 1) {
234 		msgq(sp, M_SYSERR,
235 		    "003|unable to delete line %lu", (u_long)lno);
236 		return (1);
237 	}
238 
239 	/* Flush the cache, update line count, before screen update. */
240 	if (lno <= ep->c_lno)
241 		ep->c_lno = OOBLNO;
242 	if (ep->c_nlines != OOBLNO)
243 		--ep->c_nlines;
244 
245 	/* File now modified. */
246 	if (F_ISSET(ep, F_FIRSTMODIFY))
247 		(void)rcv_init(sp);
248 	F_SET(ep, F_MODIFIED);
249 
250 	/* Update screen. */
251 	return (scr_update(sp, lno, LINE_DELETE, 1));
252 }
253 
254 /*
255  * db_append --
256  *	Append a line into the file.
257  *
258  * PUBLIC: int db_append(SCR *, int, recno_t, CHAR_T *, size_t);
259  */
260 int
261 db_append(
262 	SCR *sp,
263 	int update,
264 	recno_t lno,
265 	CHAR_T *p,
266 	size_t len)
267 {
268 	DBT data, key;
269 	EXF *ep;
270 	char *fp;
271 	size_t flen;
272 	int rval;
273 
274 #if defined(DEBUG) && 0
275 	TRACE(sp, "append to %lu: len %u {%.*s}\n", lno, len, MIN(len, 20), p);
276 #endif
277 	/* Check for no underlying file. */
278 	if ((ep = sp->ep) == NULL) {
279 		ex_emsg(sp, NULL, EXM_NOFILEYET);
280 		return (1);
281 	}
282 
283 	INT2FILE(sp, p, len, fp, flen);
284 
285 	/* Update file. */
286 	key.data = &lno;
287 	key.size = sizeof(lno);
288 	data.data = fp;
289 	data.size = flen;
290 	if (ep->db->put(ep->db, &key, &data, R_IAFTER) == -1) {
291 		msgq(sp, M_SYSERR,
292 		    "004|unable to append to line %lu", (u_long)lno);
293 		return (1);
294 	}
295 
296 	/* Flush the cache, update line count, before screen update. */
297 	if (lno < ep->c_lno)
298 		ep->c_lno = OOBLNO;
299 	if (ep->c_nlines != OOBLNO)
300 		++ep->c_nlines;
301 
302 	/* File now dirty. */
303 	if (F_ISSET(ep, F_FIRSTMODIFY))
304 		(void)rcv_init(sp);
305 	F_SET(ep, F_MODIFIED);
306 
307 	/* Log change. */
308 	log_line(sp, lno + 1, LOG_LINE_APPEND);
309 
310 	/* Update marks, @ and global commands. */
311 	rval = 0;
312 	if (mark_insdel(sp, LINE_INSERT, lno + 1))
313 		rval = 1;
314 	if (ex_g_insdel(sp, LINE_INSERT, lno + 1))
315 		rval = 1;
316 
317 	/*
318 	 * Update screen.
319 	 *
320 	 * XXX
321 	 * Nasty hack.  If multiple lines are input by the user, they aren't
322 	 * committed until an <ESC> is entered.  The problem is the screen was
323 	 * updated/scrolled as each line was entered.  So, when this routine
324 	 * is called to copy the new lines from the cut buffer into the file,
325 	 * it has to know not to update the screen again.
326 	 */
327 	return (scr_update(sp, lno, LINE_APPEND, update) || rval);
328 }
329 
330 /*
331  * db_insert --
332  *	Insert a line into the file.
333  *
334  * PUBLIC: int db_insert(SCR *, recno_t, CHAR_T *, size_t);
335  */
336 int
337 db_insert(
338 	SCR *sp,
339 	recno_t lno,
340 	CHAR_T *p,
341 	size_t len)
342 {
343 	DBT data, key;
344 	EXF *ep;
345 	char *fp;
346 	size_t flen;
347 	int rval;
348 
349 #if defined(DEBUG) && 0
350 	TRACE(sp, "insert before %lu: len %lu {%.*s}\n",
351 	    (u_long)lno, (u_long)len, MIN(len, 20), p);
352 #endif
353 	/* Check for no underlying file. */
354 	if ((ep = sp->ep) == NULL) {
355 		ex_emsg(sp, NULL, EXM_NOFILEYET);
356 		return (1);
357 	}
358 
359 	INT2FILE(sp, p, len, fp, flen);
360 
361 	/* Update file. */
362 	key.data = &lno;
363 	key.size = sizeof(lno);
364 	data.data = fp;
365 	data.size = flen;
366 	if (ep->db->put(ep->db, &key, &data, R_IBEFORE) == -1) {
367 		msgq(sp, M_SYSERR,
368 		    "005|unable to insert at line %lu", (u_long)lno);
369 		return (1);
370 	}
371 
372 	/* Flush the cache, update line count, before screen update. */
373 	if (lno >= ep->c_lno)
374 		ep->c_lno = OOBLNO;
375 	if (ep->c_nlines != OOBLNO)
376 		++ep->c_nlines;
377 
378 	/* File now dirty. */
379 	if (F_ISSET(ep, F_FIRSTMODIFY))
380 		(void)rcv_init(sp);
381 	F_SET(ep, F_MODIFIED);
382 
383 	/* Log change. */
384 	log_line(sp, lno, LOG_LINE_INSERT);
385 
386 	/* Update marks, @ and global commands. */
387 	rval = 0;
388 	if (mark_insdel(sp, LINE_INSERT, lno))
389 		rval = 1;
390 	if (ex_g_insdel(sp, LINE_INSERT, lno))
391 		rval = 1;
392 
393 	/* Update screen. */
394 	return (scr_update(sp, lno, LINE_INSERT, 1) || rval);
395 }
396 
397 /*
398  * db_set --
399  *	Store a line in the file.
400  *
401  * PUBLIC: int db_set(SCR *, recno_t, CHAR_T *, size_t);
402  */
403 int
404 db_set(
405 	SCR *sp,
406 	recno_t lno,
407 	CHAR_T *p,
408 	size_t len)
409 {
410 	DBT data, key;
411 	EXF *ep;
412 	char *fp;
413 	size_t flen;
414 
415 #if defined(DEBUG) && 0
416 	TRACE(sp, "replace line %lu: len %lu {%.*s}\n",
417 	    (u_long)lno, (u_long)len, MIN(len, 20), p);
418 #endif
419 	/* Check for no underlying file. */
420 	if ((ep = sp->ep) == NULL) {
421 		ex_emsg(sp, NULL, EXM_NOFILEYET);
422 		return (1);
423 	}
424 
425 	/* Log before change. */
426 	log_line(sp, lno, LOG_LINE_RESET_B);
427 
428 	INT2FILE(sp, p, len, fp, flen);
429 
430 	/* Update file. */
431 	key.data = &lno;
432 	key.size = sizeof(lno);
433 	data.data = fp;
434 	data.size = flen;
435 	if (ep->db->put(ep->db, &key, &data, 0) == -1) {
436 		msgq(sp, M_SYSERR,
437 		    "006|unable to store line %lu", (u_long)lno);
438 		return (1);
439 	}
440 
441 	/* Flush the cache, before logging or screen update. */
442 	if (lno == ep->c_lno)
443 		ep->c_lno = OOBLNO;
444 
445 	/* File now dirty. */
446 	if (F_ISSET(ep, F_FIRSTMODIFY))
447 		(void)rcv_init(sp);
448 	F_SET(ep, F_MODIFIED);
449 
450 	/* Log after change. */
451 	log_line(sp, lno, LOG_LINE_RESET_F);
452 
453 	/* Update screen. */
454 	return (scr_update(sp, lno, LINE_RESET, 1));
455 }
456 
457 /*
458  * db_exist --
459  *	Return if a line exists.
460  *
461  * PUBLIC: int db_exist(SCR *, recno_t);
462  */
463 int
464 db_exist(
465 	SCR *sp,
466 	recno_t lno)
467 {
468 	EXF *ep;
469 
470 	/* Check for no underlying file. */
471 	if ((ep = sp->ep) == NULL) {
472 		ex_emsg(sp, NULL, EXM_NOFILEYET);
473 		return (1);
474 	}
475 
476 	if (lno == OOBLNO)
477 		return (0);
478 
479 	/*
480 	 * Check the last-line number cache.  Adjust the cached line
481 	 * number for the lines used by the text input buffers.
482 	 */
483 	if (ep->c_nlines != OOBLNO)
484 		return (lno <= (F_ISSET(sp, SC_TINPUT) ?
485 		    ep->c_nlines + (((TEXT *)TAILQ_LAST(sp->tiq, _texth))->lno -
486 		    ((TEXT *)TAILQ_FIRST(sp->tiq))->lno) : ep->c_nlines));
487 
488 	/* Go get the line. */
489 	return (!db_get(sp, lno, 0, NULL, NULL));
490 }
491 
492 /*
493  * db_last --
494  *	Return the number of lines in the file.
495  *
496  * PUBLIC: int db_last(SCR *, recno_t *);
497  */
498 int
499 db_last(
500 	SCR *sp,
501 	recno_t *lnop)
502 {
503 	DBT data, key;
504 	EXF *ep;
505 	recno_t lno;
506 	CHAR_T *wp;
507 	size_t wlen;
508 
509 	/* Check for no underlying file. */
510 	if ((ep = sp->ep) == NULL) {
511 		ex_emsg(sp, NULL, EXM_NOFILEYET);
512 		return (1);
513 	}
514 
515 	/*
516 	 * Check the last-line number cache.  Adjust the cached line
517 	 * number for the lines used by the text input buffers.
518 	 */
519 	if (ep->c_nlines != OOBLNO) {
520 		*lnop = ep->c_nlines;
521 		if (F_ISSET(sp, SC_TINPUT))
522 			*lnop += ((TEXT *)TAILQ_LAST(sp->tiq, _texth))->lno -
523 			    ((TEXT *)TAILQ_FIRST(sp->tiq))->lno;
524 		return (0);
525 	}
526 
527 	key.data = &lno;
528 	key.size = sizeof(lno);
529 
530 	switch (ep->db->seq(ep->db, &key, &data, R_LAST)) {
531 	case -1:
532 alloc_err:
533 		msgq(sp, M_SYSERR, "007|unable to get last line");
534 		*lnop = 0;
535 		return (1);
536 	case 1:
537 		*lnop = 0;
538 		return (0);
539 	}
540 
541 	memcpy(&lno, key.data, sizeof(lno));
542 
543 	if (lno != ep->c_lno) {
544 		FILE2INT(sp, data.data, data.size, wp, wlen);
545 
546 		/* Fill the cache. */
547 		if (wp != data.data) {
548 			BINC_GOTOW(sp, ep->c_lp, ep->c_blen, wlen);
549 			MEMCPY(ep->c_lp, wp, wlen);
550 		} else
551 			ep->c_lp = data.data;
552 		ep->c_lno = lno;
553 		ep->c_len = wlen;
554 	}
555 	ep->c_nlines = lno;
556 
557 	/* Return the value. */
558 	*lnop = (F_ISSET(sp, SC_TINPUT) &&
559 	    ((TEXT *)TAILQ_LAST(sp->tiq, _texth))->lno > lno ?
560 	    ((TEXT *)TAILQ_LAST(sp->tiq, _texth))->lno : lno);
561 	return (0);
562 }
563 
564 /*
565  * db_rget --
566  *	Retrieve a raw line from the database.
567  *
568  * PUBLIC: int db_rget(SCR *, recno_t, char **, size_t *);
569  */
570 int
571 db_rget(
572 	SCR *sp,
573 	recno_t lno,				/* Line number. */
574 	char **pp,				/* Pointer store. */
575 	size_t *lenp)				/* Length store. */
576 {
577 	DBT data, key;
578 	EXF *ep = sp->ep;
579 	int rval;
580 
581 	/* Get the line from the underlying database. */
582 	key.data = &lno;
583 	key.size = sizeof(lno);
584 	if ((rval = ep->db->get(ep->db, &key, &data, 0)) == 0)
585 	{
586 		*lenp = data.size;
587 		*pp = data.data;
588 	}
589 
590 	return (rval);
591 }
592 
593 /*
594  * db_rset --
595  *	Store a raw line into the database.
596  *
597  * PUBLIC: int db_rset(SCR *, recno_t, char *, size_t);
598  */
599 int
600 db_rset(
601 	SCR *sp,
602 	recno_t lno,
603 	char *p,
604 	size_t len)
605 {
606 	DBT data, key;
607 	EXF *ep = sp->ep;
608 
609 	/* Update file. */
610 	key.data = &lno;
611 	key.size = sizeof(lno);
612 	data.data = p;
613 	data.size = len;
614 	return ep->db->put(ep->db, &key, &data, 0);
615 }
616 
617 /*
618  * db_err --
619  *	Report a line error.
620  *
621  * PUBLIC: void db_err(SCR *, recno_t);
622  */
623 void
624 db_err(
625 	SCR *sp,
626 	recno_t lno)
627 {
628 	msgq(sp, M_ERR,
629 	    "008|Error: unable to retrieve line %lu", (u_long)lno);
630 }
631 
632 /*
633  * scr_update --
634  *	Update all of the screens that are backed by the file that
635  *	just changed.
636  */
637 static int
638 scr_update(
639 	SCR *sp,
640 	recno_t lno,
641 	lnop_t op,
642 	int current)
643 {
644 	EXF *ep;
645 	SCR *tsp;
646 
647 	if (F_ISSET(sp, SC_EX))
648 		return (0);
649 
650 	ep = sp->ep;
651 	if (ep->refcnt != 1)
652 		TAILQ_FOREACH(tsp, sp->gp->dq, q)
653 			if (sp != tsp && tsp->ep == ep)
654 				if (vs_change(tsp, lno, op))
655 					return (1);
656 	return (current ? vs_change(sp, lno, op) : 0);
657 }
658