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