1 /* $OpenBSD: log.c,v 1.12 2017/04/18 01:45:35 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/stat.h>
17
18 #include <bitstring.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <libgen.h>
22 #include <limits.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "common.h"
28
29 /*
30 * The log consists of records, each containing a type byte and a variable
31 * length byte string, as follows:
32 *
33 * LOG_CURSOR_INIT MARK
34 * LOG_CURSOR_END MARK
35 * LOG_LINE_APPEND recno_t char *
36 * LOG_LINE_DELETE recno_t char *
37 * LOG_LINE_INSERT recno_t char *
38 * LOG_LINE_RESET_F recno_t char *
39 * LOG_LINE_RESET_B recno_t char *
40 * LOG_MARK LMARK
41 *
42 * We do before image physical logging. This means that the editor layer
43 * MAY NOT modify records in place, even if simply deleting or overwriting
44 * characters. Since the smallest unit of logging is a line, we're using
45 * up lots of space. This may eventually have to be reduced, probably by
46 * doing logical logging, which is a much cooler database phrase.
47 *
48 * The implementation of the historic vi 'u' command, using roll-forward and
49 * roll-back, is simple. Each set of changes has a LOG_CURSOR_INIT record,
50 * followed by a number of other records, followed by a LOG_CURSOR_END record.
51 * LOG_LINE_RESET records come in pairs. The first is a LOG_LINE_RESET_B
52 * record, and is the line before the change. The second is LOG_LINE_RESET_F,
53 * and is the line after the change. Roll-back is done by backing up to the
54 * first LOG_CURSOR_INIT record before a change. Roll-forward is done in a
55 * similar fashion.
56 *
57 * The 'U' command is implemented by rolling backward to a LOG_CURSOR_END
58 * record for a line different from the current one. It should be noted that
59 * this means that a subsequent 'u' command will make a change based on the
60 * new position of the log's cursor. This is okay, and, in fact, historic vi
61 * behaved that way.
62 */
63
64 static int log_cursor1(SCR *, int);
65 static void log_err(SCR *, char *, int);
66 #if defined(DEBUG) && 0
67 static void log_trace(SCR *, char *, recno_t, u_char *);
68 #endif
69
70 /* Try and restart the log on failure, i.e. if we run out of memory. */
71 #define LOG_ERR { \
72 log_err(sp, __FILE__, __LINE__); \
73 return (1); \
74 }
75
76 /*
77 * log_init --
78 * Initialize the logging subsystem.
79 *
80 * PUBLIC: int log_init(SCR *, EXF *);
81 */
82 int
log_init(SCR * sp,EXF * ep)83 log_init(SCR *sp, EXF *ep)
84 {
85 /*
86 * !!!
87 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
88 *
89 * Initialize the buffer. The logging subsystem has its own
90 * buffers because the global ones are almost by definition
91 * going to be in use when the log runs.
92 */
93 ep->l_lp = NULL;
94 ep->l_len = 0;
95 ep->l_cursor.lno = 1; /* XXX Any valid recno. */
96 ep->l_cursor.cno = 0;
97 ep->l_high = ep->l_cur = 1;
98
99 ep->log = dbopen(NULL, O_CREAT | O_NONBLOCK | O_RDWR,
100 S_IRUSR | S_IWUSR, DB_RECNO, NULL);
101 if (ep->log == NULL) {
102 msgq(sp, M_SYSERR, "Log file");
103 F_SET(ep, F_NOLOG);
104 return (1);
105 }
106
107 return (0);
108 }
109
110 /*
111 * log_end --
112 * Close the logging subsystem.
113 *
114 * PUBLIC: int log_end(SCR *, EXF *);
115 */
116 int
log_end(SCR * sp,EXF * ep)117 log_end(SCR *sp, EXF *ep)
118 {
119 /*
120 * !!!
121 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
122 */
123 if (ep->log != NULL) {
124 (void)(ep->log->close)(ep->log);
125 ep->log = NULL;
126 }
127 free(ep->l_lp);
128 ep->l_lp = NULL;
129 ep->l_len = 0;
130 ep->l_cursor.lno = 1; /* XXX Any valid recno. */
131 ep->l_cursor.cno = 0;
132 ep->l_high = ep->l_cur = 1;
133 return (0);
134 }
135
136 /*
137 * log_cursor --
138 * Log the current cursor position, starting an event.
139 *
140 * PUBLIC: int log_cursor(SCR *);
141 */
142 int
log_cursor(SCR * sp)143 log_cursor(SCR *sp)
144 {
145 EXF *ep;
146
147 ep = sp->ep;
148 if (F_ISSET(ep, F_NOLOG))
149 return (0);
150
151 /*
152 * If any changes were made since the last cursor init,
153 * put out the ending cursor record.
154 */
155 if (ep->l_cursor.lno == OOBLNO) {
156 ep->l_cursor.lno = sp->lno;
157 ep->l_cursor.cno = sp->cno;
158 return (log_cursor1(sp, LOG_CURSOR_END));
159 }
160 ep->l_cursor.lno = sp->lno;
161 ep->l_cursor.cno = sp->cno;
162 return (0);
163 }
164
165 /*
166 * log_cursor1 --
167 * Actually push a cursor record out.
168 */
169 static int
log_cursor1(SCR * sp,int type)170 log_cursor1(SCR *sp, int type)
171 {
172 DBT data, key;
173 EXF *ep;
174
175 ep = sp->ep;
176 BINC_RET(sp, ep->l_lp, ep->l_len, sizeof(u_char) + sizeof(MARK));
177 ep->l_lp[0] = type;
178 memmove(ep->l_lp + sizeof(u_char), &ep->l_cursor, sizeof(MARK));
179
180 key.data = &ep->l_cur;
181 key.size = sizeof(recno_t);
182 data.data = ep->l_lp;
183 data.size = sizeof(u_char) + sizeof(MARK);
184 if (ep->log->put(ep->log, &key, &data, 0) == -1)
185 LOG_ERR;
186
187 #if defined(DEBUG) && 0
188 TRACE(sp, "%lu: %s: %u/%u\n", ep->l_cur,
189 type == LOG_CURSOR_INIT ? "log_cursor_init" : "log_cursor_end",
190 sp->lno, sp->cno);
191 #endif
192 /* Reset high water mark. */
193 ep->l_high = ++ep->l_cur;
194
195 return (0);
196 }
197
198 /*
199 * log_line --
200 * Log a line change.
201 *
202 * PUBLIC: int log_line(SCR *, recno_t, u_int);
203 */
204 int
log_line(SCR * sp,recno_t lno,u_int action)205 log_line(SCR *sp, recno_t lno, u_int action)
206 {
207 DBT data, key;
208 EXF *ep;
209 size_t len;
210 char *lp;
211
212 ep = sp->ep;
213 if (F_ISSET(ep, F_NOLOG))
214 return (0);
215
216 /*
217 * XXX
218 *
219 * Kluge for vi. Clear the EXF undo flag so that the
220 * next 'u' command does a roll-back, regardless.
221 */
222 F_CLR(ep, F_UNDO);
223
224 /* Put out one initial cursor record per set of changes. */
225 if (ep->l_cursor.lno != OOBLNO) {
226 if (log_cursor1(sp, LOG_CURSOR_INIT))
227 return (1);
228 ep->l_cursor.lno = OOBLNO;
229 }
230
231 /*
232 * Put out the changes. If it's a LOG_LINE_RESET_B call, it's a
233 * special case, avoid the caches. Also, if it fails and it's
234 * line 1, it just means that the user started with an empty file,
235 * so fake an empty length line.
236 */
237 if (action == LOG_LINE_RESET_B) {
238 if (db_get(sp, lno, DBG_NOCACHE, &lp, &len)) {
239 if (lno != 1) {
240 db_err(sp, lno);
241 return (1);
242 }
243 len = 0;
244 lp = "";
245 }
246 } else
247 if (db_get(sp, lno, DBG_FATAL, &lp, &len))
248 return (1);
249 BINC_RET(sp,
250 ep->l_lp, ep->l_len, len + sizeof(u_char) + sizeof(recno_t));
251 ep->l_lp[0] = action;
252 memmove(ep->l_lp + sizeof(u_char), &lno, sizeof(recno_t));
253 memmove(ep->l_lp + sizeof(u_char) + sizeof(recno_t), lp, len);
254
255 key.data = &ep->l_cur;
256 key.size = sizeof(recno_t);
257 data.data = ep->l_lp;
258 data.size = len + sizeof(u_char) + sizeof(recno_t);
259 if (ep->log->put(ep->log, &key, &data, 0) == -1)
260 LOG_ERR;
261
262 #if defined(DEBUG) && 0
263 switch (action) {
264 case LOG_LINE_APPEND:
265 TRACE(sp, "%u: log_line: append: %lu {%u}\n",
266 ep->l_cur, lno, len);
267 break;
268 case LOG_LINE_DELETE:
269 TRACE(sp, "%lu: log_line: delete: %lu {%u}\n",
270 ep->l_cur, lno, len);
271 break;
272 case LOG_LINE_INSERT:
273 TRACE(sp, "%lu: log_line: insert: %lu {%u}\n",
274 ep->l_cur, lno, len);
275 break;
276 case LOG_LINE_RESET_F:
277 TRACE(sp, "%lu: log_line: reset_f: %lu {%u}\n",
278 ep->l_cur, lno, len);
279 break;
280 case LOG_LINE_RESET_B:
281 TRACE(sp, "%lu: log_line: reset_b: %lu {%u}\n",
282 ep->l_cur, lno, len);
283 break;
284 }
285 #endif
286 /* Reset high water mark. */
287 ep->l_high = ++ep->l_cur;
288
289 return (0);
290 }
291
292 /*
293 * log_mark --
294 * Log a mark position. For the log to work, we assume that there
295 * aren't any operations that just put out a log record -- this
296 * would mean that undo operations would only reset marks, and not
297 * cause any other change.
298 *
299 * PUBLIC: int log_mark(SCR *, LMARK *);
300 */
301 int
log_mark(SCR * sp,LMARK * lmp)302 log_mark(SCR *sp, LMARK *lmp)
303 {
304 DBT data, key;
305 EXF *ep;
306
307 ep = sp->ep;
308 if (F_ISSET(ep, F_NOLOG))
309 return (0);
310
311 /* Put out one initial cursor record per set of changes. */
312 if (ep->l_cursor.lno != OOBLNO) {
313 if (log_cursor1(sp, LOG_CURSOR_INIT))
314 return (1);
315 ep->l_cursor.lno = OOBLNO;
316 }
317
318 BINC_RET(sp, ep->l_lp,
319 ep->l_len, sizeof(u_char) + sizeof(LMARK));
320 ep->l_lp[0] = LOG_MARK;
321 memmove(ep->l_lp + sizeof(u_char), lmp, sizeof(LMARK));
322
323 key.data = &ep->l_cur;
324 key.size = sizeof(recno_t);
325 data.data = ep->l_lp;
326 data.size = sizeof(u_char) + sizeof(LMARK);
327 if (ep->log->put(ep->log, &key, &data, 0) == -1)
328 LOG_ERR;
329
330 #if defined(DEBUG) && 0
331 TRACE(sp, "%lu: mark %c: %lu/%u\n",
332 ep->l_cur, lmp->name, lmp->lno, lmp->cno);
333 #endif
334 /* Reset high water mark. */
335 ep->l_high = ++ep->l_cur;
336 return (0);
337 }
338
339 /*
340 * Log_backward --
341 * Roll the log backward one operation.
342 *
343 * PUBLIC: int log_backward(SCR *, MARK *);
344 */
345 int
log_backward(SCR * sp,MARK * rp)346 log_backward(SCR *sp, MARK *rp)
347 {
348 DBT key, data;
349 EXF *ep;
350 LMARK lm;
351 MARK m;
352 recno_t lno;
353 int didop;
354 u_char *p;
355
356 ep = sp->ep;
357 if (F_ISSET(ep, F_NOLOG)) {
358 msgq(sp, M_ERR,
359 "Logging not being performed, undo not possible");
360 return (1);
361 }
362
363 if (ep->l_cur == 1) {
364 msgq(sp, M_BERR, "No changes to undo");
365 return (1);
366 }
367
368 F_SET(ep, F_NOLOG); /* Turn off logging. */
369
370 key.data = &ep->l_cur; /* Initialize db request. */
371 key.size = sizeof(recno_t);
372 for (didop = 0;;) {
373 --ep->l_cur;
374 if (ep->log->get(ep->log, &key, &data, 0))
375 LOG_ERR;
376 #if defined(DEBUG) && 0
377 log_trace(sp, "log_backward", ep->l_cur, data.data);
378 #endif
379 switch (*(p = (u_char *)data.data)) {
380 case LOG_CURSOR_INIT:
381 if (didop) {
382 memmove(rp, p + sizeof(u_char), sizeof(MARK));
383 F_CLR(ep, F_NOLOG);
384 return (0);
385 }
386 break;
387 case LOG_CURSOR_END:
388 break;
389 case LOG_LINE_APPEND:
390 case LOG_LINE_INSERT:
391 didop = 1;
392 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
393 if (db_delete(sp, lno))
394 goto err;
395 ++sp->rptlines[L_DELETED];
396 break;
397 case LOG_LINE_DELETE:
398 didop = 1;
399 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
400 if (db_insert(sp, lno, p + sizeof(u_char) +
401 sizeof(recno_t), data.size - sizeof(u_char) -
402 sizeof(recno_t)))
403 goto err;
404 ++sp->rptlines[L_ADDED];
405 break;
406 case LOG_LINE_RESET_F:
407 break;
408 case LOG_LINE_RESET_B:
409 didop = 1;
410 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
411 if (db_set(sp, lno, p + sizeof(u_char) +
412 sizeof(recno_t), data.size - sizeof(u_char) -
413 sizeof(recno_t)))
414 goto err;
415 if (sp->rptlchange != lno) {
416 sp->rptlchange = lno;
417 ++sp->rptlines[L_CHANGED];
418 }
419 break;
420 case LOG_MARK:
421 didop = 1;
422 memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
423 m.lno = lm.lno;
424 m.cno = lm.cno;
425 if (mark_set(sp, lm.name, &m, 0))
426 goto err;
427 break;
428 default:
429 abort();
430 }
431 }
432
433 err: F_CLR(ep, F_NOLOG);
434 return (1);
435 }
436
437 /*
438 * Log_setline --
439 * Reset the line to its original appearance.
440 *
441 * XXX
442 * There's a bug in this code due to our not logging cursor movements
443 * unless a change was made. If you do a change, move off the line,
444 * then move back on and do a 'U', the line will be restored to the way
445 * it was before the original change.
446 *
447 * PUBLIC: int log_setline(SCR *);
448 */
449 int
log_setline(SCR * sp)450 log_setline(SCR *sp)
451 {
452 DBT key, data;
453 EXF *ep;
454 LMARK lm;
455 MARK m;
456 recno_t lno;
457 u_char *p;
458
459 ep = sp->ep;
460 if (F_ISSET(ep, F_NOLOG)) {
461 msgq(sp, M_ERR,
462 "Logging not being performed, undo not possible");
463 return (1);
464 }
465
466 if (ep->l_cur == 1)
467 return (1);
468
469 F_SET(ep, F_NOLOG); /* Turn off logging. */
470
471 key.data = &ep->l_cur; /* Initialize db request. */
472 key.size = sizeof(recno_t);
473
474 for (;;) {
475 --ep->l_cur;
476 if (ep->log->get(ep->log, &key, &data, 0))
477 LOG_ERR;
478 #if defined(DEBUG) && 0
479 log_trace(sp, "log_setline", ep->l_cur, data.data);
480 #endif
481 switch (*(p = (u_char *)data.data)) {
482 case LOG_CURSOR_INIT:
483 memmove(&m, p + sizeof(u_char), sizeof(MARK));
484 if (m.lno != sp->lno || ep->l_cur == 1) {
485 F_CLR(ep, F_NOLOG);
486 return (0);
487 }
488 break;
489 case LOG_CURSOR_END:
490 memmove(&m, p + sizeof(u_char), sizeof(MARK));
491 if (m.lno != sp->lno) {
492 ++ep->l_cur;
493 F_CLR(ep, F_NOLOG);
494 return (0);
495 }
496 break;
497 case LOG_LINE_APPEND:
498 case LOG_LINE_INSERT:
499 case LOG_LINE_DELETE:
500 case LOG_LINE_RESET_F:
501 break;
502 case LOG_LINE_RESET_B:
503 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
504 if (lno == sp->lno &&
505 db_set(sp, lno, p + sizeof(u_char) +
506 sizeof(recno_t), data.size - sizeof(u_char) -
507 sizeof(recno_t)))
508 goto err;
509 if (sp->rptlchange != lno) {
510 sp->rptlchange = lno;
511 ++sp->rptlines[L_CHANGED];
512 }
513 case LOG_MARK:
514 memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
515 m.lno = lm.lno;
516 m.cno = lm.cno;
517 if (mark_set(sp, lm.name, &m, 0))
518 goto err;
519 break;
520 default:
521 abort();
522 }
523 }
524
525 err: F_CLR(ep, F_NOLOG);
526 return (1);
527 }
528
529 /*
530 * Log_forward --
531 * Roll the log forward one operation.
532 *
533 * PUBLIC: int log_forward(SCR *, MARK *);
534 */
535 int
log_forward(SCR * sp,MARK * rp)536 log_forward(SCR *sp, MARK *rp)
537 {
538 DBT key, data;
539 EXF *ep;
540 LMARK lm;
541 MARK m;
542 recno_t lno;
543 int didop;
544 u_char *p;
545
546 ep = sp->ep;
547 if (F_ISSET(ep, F_NOLOG)) {
548 msgq(sp, M_ERR,
549 "Logging not being performed, roll-forward not possible");
550 return (1);
551 }
552
553 if (ep->l_cur == ep->l_high) {
554 msgq(sp, M_BERR, "No changes to re-do");
555 return (1);
556 }
557
558 F_SET(ep, F_NOLOG); /* Turn off logging. */
559
560 key.data = &ep->l_cur; /* Initialize db request. */
561 key.size = sizeof(recno_t);
562 for (didop = 0;;) {
563 ++ep->l_cur;
564 if (ep->log->get(ep->log, &key, &data, 0))
565 LOG_ERR;
566 #if defined(DEBUG) && 0
567 log_trace(sp, "log_forward", ep->l_cur, data.data);
568 #endif
569 switch (*(p = (u_char *)data.data)) {
570 case LOG_CURSOR_END:
571 if (didop) {
572 ++ep->l_cur;
573 memmove(rp, p + sizeof(u_char), sizeof(MARK));
574 F_CLR(ep, F_NOLOG);
575 return (0);
576 }
577 break;
578 case LOG_CURSOR_INIT:
579 break;
580 case LOG_LINE_APPEND:
581 case LOG_LINE_INSERT:
582 didop = 1;
583 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
584 if (db_insert(sp, lno, p + sizeof(u_char) +
585 sizeof(recno_t), data.size - sizeof(u_char) -
586 sizeof(recno_t)))
587 goto err;
588 ++sp->rptlines[L_ADDED];
589 break;
590 case LOG_LINE_DELETE:
591 didop = 1;
592 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
593 if (db_delete(sp, lno))
594 goto err;
595 ++sp->rptlines[L_DELETED];
596 break;
597 case LOG_LINE_RESET_B:
598 break;
599 case LOG_LINE_RESET_F:
600 didop = 1;
601 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
602 if (db_set(sp, lno, p + sizeof(u_char) +
603 sizeof(recno_t), data.size - sizeof(u_char) -
604 sizeof(recno_t)))
605 goto err;
606 if (sp->rptlchange != lno) {
607 sp->rptlchange = lno;
608 ++sp->rptlines[L_CHANGED];
609 }
610 break;
611 case LOG_MARK:
612 didop = 1;
613 memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
614 m.lno = lm.lno;
615 m.cno = lm.cno;
616 if (mark_set(sp, lm.name, &m, 0))
617 goto err;
618 break;
619 default:
620 abort();
621 }
622 }
623
624 err: F_CLR(ep, F_NOLOG);
625 return (1);
626 }
627
628 /*
629 * log_err --
630 * Try and restart the log on failure, i.e. if we run out of memory.
631 */
632 static void
log_err(SCR * sp,char * file,int line)633 log_err(SCR *sp, char *file, int line)
634 {
635 EXF *ep;
636
637 msgq(sp, M_SYSERR, "%s/%d: log put error", basename(file), line);
638 ep = sp->ep;
639 (void)ep->log->close(ep->log);
640 if (!log_init(sp, ep))
641 msgq(sp, M_ERR, "Log restarted");
642 }
643
644 #if defined(DEBUG) && 0
645 static void
log_trace(SCR * sp,char * msg,recno_t rno,u_char * p)646 log_trace(SCR *sp, char *msg, recno_t rno, u_char *p)
647 {
648 LMARK lm;
649 MARK m;
650 recno_t lno;
651
652 switch (*p) {
653 case LOG_CURSOR_INIT:
654 memmove(&m, p + sizeof(u_char), sizeof(MARK));
655 TRACE(sp, "%lu: %s: C_INIT: %u/%u\n", rno, msg, m.lno, m.cno);
656 break;
657 case LOG_CURSOR_END:
658 memmove(&m, p + sizeof(u_char), sizeof(MARK));
659 TRACE(sp, "%lu: %s: C_END: %u/%u\n", rno, msg, m.lno, m.cno);
660 break;
661 case LOG_LINE_APPEND:
662 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
663 TRACE(sp, "%lu: %s: APPEND: %lu\n", rno, msg, lno);
664 break;
665 case LOG_LINE_INSERT:
666 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
667 TRACE(sp, "%lu: %s: INSERT: %lu\n", rno, msg, lno);
668 break;
669 case LOG_LINE_DELETE:
670 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
671 TRACE(sp, "%lu: %s: DELETE: %lu\n", rno, msg, lno);
672 break;
673 case LOG_LINE_RESET_F:
674 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
675 TRACE(sp, "%lu: %s: RESET_F: %lu\n", rno, msg, lno);
676 break;
677 case LOG_LINE_RESET_B:
678 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
679 TRACE(sp, "%lu: %s: RESET_B: %lu\n", rno, msg, lno);
680 break;
681 case LOG_MARK:
682 memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
683 TRACE(sp,
684 "%lu: %s: MARK: %u/%u\n", rno, msg, lm.lno, lm.cno);
685 break;
686 default:
687 abort();
688 }
689 }
690 #endif
691