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