only clean out gs when last window goes
[nvi.git] / common / log.c
blob356e65f461bad3653ffa8982eb510559e470d327
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.
7 * See the LICENSE file for redistribution information.
8 */
10 #include "config.h"
12 #ifndef lint
13 static const char sccsid[] = "$Id: log.c,v 10.12 2000/04/21 21:26:19 skimo Exp $ (Berkeley) $Date: 2000/04/21 21:26:19 $";
14 #endif /* not lint */
16 #include <sys/types.h>
17 #include <sys/queue.h>
18 #include <sys/stat.h>
20 #include <bitstring.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
28 #include "common.h"
31 * The log consists of records, each containing a type byte and a variable
32 * length byte string, as follows:
34 * LOG_CURSOR_INIT MARK
35 * LOG_CURSOR_END MARK
36 * LOG_LINE_APPEND db_recno_t char *
37 * LOG_LINE_DELETE db_recno_t char *
38 * LOG_LINE_INSERT db_recno_t char *
39 * LOG_LINE_RESET_F db_recno_t char *
40 * LOG_LINE_RESET_B db_recno_t char *
41 * LOG_MARK LMARK
43 * We do before image physical logging. This means that the editor layer
44 * MAY NOT modify records in place, even if simply deleting or overwriting
45 * characters. Since the smallest unit of logging is a line, we're using
46 * up lots of space. This may eventually have to be reduced, probably by
47 * doing logical logging, which is a much cooler database phrase.
49 * The implementation of the historic vi 'u' command, using roll-forward and
50 * roll-back, is simple. Each set of changes has a LOG_CURSOR_INIT record,
51 * followed by a number of other records, followed by a LOG_CURSOR_END record.
52 * LOG_LINE_RESET records come in pairs. The first is a LOG_LINE_RESET_B
53 * record, and is the line before the change. The second is LOG_LINE_RESET_F,
54 * and is the line after the change. Roll-back is done by backing up to the
55 * first LOG_CURSOR_INIT record before a change. Roll-forward is done in a
56 * similar fashion.
58 * The 'U' command is implemented by rolling backward to a LOG_CURSOR_END
59 * record for a line different from the current one. It should be noted that
60 * this means that a subsequent 'u' command will make a change based on the
61 * new position of the log's cursor. This is okay, and, in fact, historic vi
62 * behaved that way.
65 static int log_cursor1 __P((SCR *, int));
66 static void log_err __P((SCR *, char *, int));
67 #if defined(DEBUG) && 0
68 static void log_trace __P((SCR *, char *, db_recno_t, u_char *));
69 #endif
71 /* Try and restart the log on failure, i.e. if we run out of memory. */
72 #define LOG_ERR { \
73 log_err(sp, __FILE__, __LINE__); \
74 return (1); \
78 * log_init --
79 * Initialize the logging subsystem.
81 * PUBLIC: int log_init __P((SCR *, EXF *));
83 int
84 log_init(sp, ep)
85 SCR *sp;
86 EXF *ep;
89 * !!!
90 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
92 * Initialize the buffer. The logging subsystem has its own
93 * buffers because the global ones are almost by definition
94 * going to be in use when the log runs.
96 ep->l_lp = NULL;
97 ep->l_len = 0;
98 ep->l_cursor.lno = 1; /* XXX Any valid recno. */
99 ep->l_cursor.cno = 0;
100 ep->l_high = ep->l_cur = 1;
102 if (db_create(&ep->log, sp->gp->env, 0) != 0 ||
103 ep->log->open(ep->log, NULL, NULL, DB_RECNO, DB_CREATE,
104 S_IRUSR | S_IWUSR) != 0) {
105 msgq(sp, M_SYSERR, "009|Log file");
106 F_SET(ep, F_NOLOG);
107 return (1);
110 return (0);
114 * log_end --
115 * Close the logging subsystem.
117 * PUBLIC: int log_end __P((SCR *, EXF *));
120 log_end(sp, ep)
121 SCR *sp;
122 EXF *ep;
125 * !!!
126 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
128 if (ep->log != NULL) {
129 (void)(ep->log->close)(ep->log,DB_NOSYNC);
130 ep->log = NULL;
132 if (ep->l_lp != NULL) {
133 free(ep->l_lp);
134 ep->l_lp = NULL;
136 ep->l_len = 0;
137 ep->l_cursor.lno = 1; /* XXX Any valid recno. */
138 ep->l_cursor.cno = 0;
139 ep->l_high = ep->l_cur = 1;
140 return (0);
144 * log_cursor --
145 * Log the current cursor position, starting an event.
147 * PUBLIC: int log_cursor __P((SCR *));
150 log_cursor(sp)
151 SCR *sp;
153 EXF *ep;
155 ep = sp->ep;
156 if (F_ISSET(ep, F_NOLOG))
157 return (0);
160 * If any changes were made since the last cursor init,
161 * put out the ending cursor record.
163 if (ep->l_cursor.lno == OOBLNO) {
164 ep->l_cursor.lno = sp->lno;
165 ep->l_cursor.cno = sp->cno;
166 return (log_cursor1(sp, LOG_CURSOR_END));
168 ep->l_cursor.lno = sp->lno;
169 ep->l_cursor.cno = sp->cno;
170 return (0);
174 * log_cursor1 --
175 * Actually push a cursor record out.
177 static int
178 log_cursor1(sp, type)
179 SCR *sp;
180 int type;
182 DBT data, key;
183 EXF *ep;
185 ep = sp->ep;
186 BINC_RET(sp, ep->l_lp, ep->l_len, sizeof(u_char) + sizeof(MARK));
187 ep->l_lp[0] = type;
188 memmove(ep->l_lp + sizeof(u_char), &ep->l_cursor, sizeof(MARK));
190 memset(&key, 0, sizeof(key));
191 key.data = &ep->l_cur;
192 key.size = sizeof(db_recno_t);
193 memset(&data, 0, sizeof(data));
194 data.data = ep->l_lp;
195 data.size = sizeof(u_char) + sizeof(MARK);
196 if (ep->log->put(ep->log, NULL, &key, &data, 0) == -1)
197 LOG_ERR;
199 #if defined(DEBUG) && 0
200 vtrace(sp, "%lu: %s: %u/%u\n", ep->l_cur,
201 type == LOG_CURSOR_INIT ? "log_cursor_init" : "log_cursor_end",
202 sp->lno, sp->cno);
203 #endif
204 /* Reset high water mark. */
205 ep->l_high = ++ep->l_cur;
207 return (0);
211 * log_line --
212 * Log a line change.
214 * PUBLIC: int log_line __P((SCR *, db_recno_t, u_int));
217 log_line(sp, lno, action)
218 SCR *sp;
219 db_recno_t lno;
220 u_int action;
222 DBT data, key;
223 EXF *ep;
224 size_t len;
225 CHAR_T *lp;
227 ep = sp->ep;
228 if (F_ISSET(ep, F_NOLOG))
229 return (0);
232 * XXX
234 * Kluge for vi. Clear the EXF undo flag so that the
235 * next 'u' command does a roll-back, regardless.
237 F_CLR(ep, F_UNDO);
239 /* Put out one initial cursor record per set of changes. */
240 if (ep->l_cursor.lno != OOBLNO) {
241 if (log_cursor1(sp, LOG_CURSOR_INIT))
242 return (1);
243 ep->l_cursor.lno = OOBLNO;
247 * Put out the changes. If it's a LOG_LINE_RESET_B call, it's a
248 * special case, avoid the caches. Also, if it fails and it's
249 * line 1, it just means that the user started with an empty file,
250 * so fake an empty length line.
252 if (action == LOG_LINE_RESET_B) {
253 if (db_get(sp, lno, DBG_NOCACHE, &lp, &len)) {
254 if (lno != 1) {
255 db_err(sp, lno);
256 return (1);
258 len = 0;
259 lp = "";
261 } else
262 if (db_get(sp, lno, DBG_FATAL, &lp, &len))
263 return (1);
264 BINC_RET(sp,
265 ep->l_lp, ep->l_len, len + sizeof(u_char) + sizeof(db_recno_t));
266 ep->l_lp[0] = action;
267 memmove(ep->l_lp + sizeof(u_char), &lno, sizeof(db_recno_t));
268 memmove(ep->l_lp + sizeof(u_char) + sizeof(db_recno_t), lp, len);
270 memset(&key, 0, sizeof(key));
271 key.data = &ep->l_cur;
272 key.size = sizeof(db_recno_t);
273 memset(&data, 0, sizeof(data));
274 data.data = ep->l_lp;
275 data.size = len + sizeof(u_char) + sizeof(db_recno_t);
276 if (ep->log->put(ep->log, NULL, &key, &data, 0) == -1)
277 LOG_ERR;
279 #if defined(DEBUG) && 0
280 switch (action) {
281 case LOG_LINE_APPEND:
282 vtrace(sp, "%u: log_line: append: %lu {%u}\n",
283 ep->l_cur, lno, len);
284 break;
285 case LOG_LINE_DELETE:
286 vtrace(sp, "%lu: log_line: delete: %lu {%u}\n",
287 ep->l_cur, lno, len);
288 break;
289 case LOG_LINE_INSERT:
290 vtrace(sp, "%lu: log_line: insert: %lu {%u}\n",
291 ep->l_cur, lno, len);
292 break;
293 case LOG_LINE_RESET_F:
294 vtrace(sp, "%lu: log_line: reset_f: %lu {%u}\n",
295 ep->l_cur, lno, len);
296 break;
297 case LOG_LINE_RESET_B:
298 vtrace(sp, "%lu: log_line: reset_b: %lu {%u}\n",
299 ep->l_cur, lno, len);
300 break;
302 #endif
303 /* Reset high water mark. */
304 ep->l_high = ++ep->l_cur;
306 return (0);
310 * log_mark --
311 * Log a mark position. For the log to work, we assume that there
312 * aren't any operations that just put out a log record -- this
313 * would mean that undo operations would only reset marks, and not
314 * cause any other change.
316 * PUBLIC: int log_mark __P((SCR *, LMARK *));
319 log_mark(sp, lmp)
320 SCR *sp;
321 LMARK *lmp;
323 DBT data, key;
324 EXF *ep;
326 ep = sp->ep;
327 if (F_ISSET(ep, F_NOLOG))
328 return (0);
330 /* Put out one initial cursor record per set of changes. */
331 if (ep->l_cursor.lno != OOBLNO) {
332 if (log_cursor1(sp, LOG_CURSOR_INIT))
333 return (1);
334 ep->l_cursor.lno = OOBLNO;
337 BINC_RET(sp, ep->l_lp,
338 ep->l_len, sizeof(u_char) + sizeof(LMARK));
339 ep->l_lp[0] = LOG_MARK;
340 memmove(ep->l_lp + sizeof(u_char), lmp, sizeof(LMARK));
342 memset(&key, 0, sizeof(key));
343 key.data = &ep->l_cur;
344 key.size = sizeof(db_recno_t);
345 memset(&data, 0, sizeof(data));
346 data.data = ep->l_lp;
347 data.size = sizeof(u_char) + sizeof(LMARK);
348 if (ep->log->put(ep->log, NULL, &key, &data, 0) == -1)
349 LOG_ERR;
351 #if defined(DEBUG) && 0
352 vtrace(sp, "%lu: mark %c: %lu/%u\n",
353 ep->l_cur, lmp->name, lmp->lno, lmp->cno);
354 #endif
355 /* Reset high water mark. */
356 ep->l_high = ++ep->l_cur;
357 return (0);
361 * Log_backward --
362 * Roll the log backward one operation.
364 * PUBLIC: int log_backward __P((SCR *, MARK *));
367 log_backward(sp, rp)
368 SCR *sp;
369 MARK *rp;
371 DBT key, data;
372 EXF *ep;
373 LMARK lm;
374 MARK m;
375 db_recno_t lno;
376 int didop;
377 u_char *p;
379 ep = sp->ep;
380 if (F_ISSET(ep, F_NOLOG)) {
381 msgq(sp, M_ERR,
382 "010|Logging not being performed, undo not possible");
383 return (1);
386 if (ep->l_cur == 1) {
387 msgq(sp, M_BERR, "011|No changes to undo");
388 return (1);
391 F_SET(ep, F_NOLOG); /* Turn off logging. */
393 memset(&key, 0, sizeof(key));
394 key.data = &ep->l_cur; /* Initialize db request. */
395 key.size = sizeof(db_recno_t);
396 memset(&data, 0, sizeof(data));
397 for (didop = 0;;) {
398 --ep->l_cur;
399 if (ep->log->get(ep->log, NULL, &key, &data, 0))
400 LOG_ERR;
401 #if defined(DEBUG) && 0
402 log_trace(sp, "log_backward", ep->l_cur, data.data);
403 #endif
404 switch (*(p = (u_char *)data.data)) {
405 case LOG_CURSOR_INIT:
406 if (didop) {
407 memmove(rp, p + sizeof(u_char), sizeof(MARK));
408 F_CLR(ep, F_NOLOG);
409 return (0);
411 break;
412 case LOG_CURSOR_END:
413 break;
414 case LOG_LINE_APPEND:
415 case LOG_LINE_INSERT:
416 didop = 1;
417 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
418 if (db_delete(sp, lno))
419 goto err;
420 ++sp->rptlines[L_DELETED];
421 break;
422 case LOG_LINE_DELETE:
423 didop = 1;
424 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
425 if (db_insert(sp, lno, p + sizeof(u_char) +
426 sizeof(db_recno_t), data.size - sizeof(u_char) -
427 sizeof(db_recno_t)))
428 goto err;
429 ++sp->rptlines[L_ADDED];
430 break;
431 case LOG_LINE_RESET_F:
432 break;
433 case LOG_LINE_RESET_B:
434 didop = 1;
435 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
436 if (db_set(sp, lno, p + sizeof(u_char) +
437 sizeof(db_recno_t), data.size - sizeof(u_char) -
438 sizeof(db_recno_t)))
439 goto err;
440 if (sp->rptlchange != lno) {
441 sp->rptlchange = lno;
442 ++sp->rptlines[L_CHANGED];
444 break;
445 case LOG_MARK:
446 didop = 1;
447 memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
448 m.lno = lm.lno;
449 m.cno = lm.cno;
450 if (mark_set(sp, lm.name, &m, 0))
451 goto err;
452 break;
453 default:
454 abort();
458 err: F_CLR(ep, F_NOLOG);
459 return (1);
463 * Log_setline --
464 * Reset the line to its original appearance.
466 * XXX
467 * There's a bug in this code due to our not logging cursor movements
468 * unless a change was made. If you do a change, move off the line,
469 * then move back on and do a 'U', the line will be restored to the way
470 * it was before the original change.
472 * PUBLIC: int log_setline __P((SCR *));
475 log_setline(sp)
476 SCR *sp;
478 DBT key, data;
479 EXF *ep;
480 LMARK lm;
481 MARK m;
482 db_recno_t lno;
483 u_char *p;
485 ep = sp->ep;
486 if (F_ISSET(ep, F_NOLOG)) {
487 msgq(sp, M_ERR,
488 "012|Logging not being performed, undo not possible");
489 return (1);
492 if (ep->l_cur == 1)
493 return (1);
495 F_SET(ep, F_NOLOG); /* Turn off logging. */
497 memset(&key, 0, sizeof(key));
498 key.data = &ep->l_cur; /* Initialize db request. */
499 key.size = sizeof(db_recno_t);
500 memset(&data, 0, sizeof(data));
502 for (;;) {
503 --ep->l_cur;
504 if (ep->log->get(ep->log, NULL, &key, &data, 0))
505 LOG_ERR;
506 #if defined(DEBUG) && 0
507 log_trace(sp, "log_setline", ep->l_cur, data.data);
508 #endif
509 switch (*(p = (u_char *)data.data)) {
510 case LOG_CURSOR_INIT:
511 memmove(&m, p + sizeof(u_char), sizeof(MARK));
512 if (m.lno != sp->lno || ep->l_cur == 1) {
513 F_CLR(ep, F_NOLOG);
514 return (0);
516 break;
517 case LOG_CURSOR_END:
518 memmove(&m, p + sizeof(u_char), sizeof(MARK));
519 if (m.lno != sp->lno) {
520 ++ep->l_cur;
521 F_CLR(ep, F_NOLOG);
522 return (0);
524 break;
525 case LOG_LINE_APPEND:
526 case LOG_LINE_INSERT:
527 case LOG_LINE_DELETE:
528 case LOG_LINE_RESET_F:
529 break;
530 case LOG_LINE_RESET_B:
531 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
532 if (lno == sp->lno &&
533 db_set(sp, lno, p + sizeof(u_char) +
534 sizeof(db_recno_t), data.size - sizeof(u_char) -
535 sizeof(db_recno_t)))
536 goto err;
537 if (sp->rptlchange != lno) {
538 sp->rptlchange = lno;
539 ++sp->rptlines[L_CHANGED];
541 case LOG_MARK:
542 memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
543 m.lno = lm.lno;
544 m.cno = lm.cno;
545 if (mark_set(sp, lm.name, &m, 0))
546 goto err;
547 break;
548 default:
549 abort();
553 err: F_CLR(ep, F_NOLOG);
554 return (1);
558 * Log_forward --
559 * Roll the log forward one operation.
561 * PUBLIC: int log_forward __P((SCR *, MARK *));
564 log_forward(sp, rp)
565 SCR *sp;
566 MARK *rp;
568 DBT key, data;
569 EXF *ep;
570 LMARK lm;
571 MARK m;
572 db_recno_t lno;
573 int didop;
574 u_char *p;
576 ep = sp->ep;
577 if (F_ISSET(ep, F_NOLOG)) {
578 msgq(sp, M_ERR,
579 "013|Logging not being performed, roll-forward not possible");
580 return (1);
583 if (ep->l_cur == ep->l_high) {
584 msgq(sp, M_BERR, "014|No changes to re-do");
585 return (1);
588 F_SET(ep, F_NOLOG); /* Turn off logging. */
590 memset(&key, 0, sizeof(key));
591 key.data = &ep->l_cur; /* Initialize db request. */
592 key.size = sizeof(db_recno_t);
593 memset(&data, 0, sizeof(data));
594 for (didop = 0;;) {
595 ++ep->l_cur;
596 if (ep->log->get(ep->log, NULL, &key, &data, 0))
597 LOG_ERR;
598 #if defined(DEBUG) && 0
599 log_trace(sp, "log_forward", ep->l_cur, data.data);
600 #endif
601 switch (*(p = (u_char *)data.data)) {
602 case LOG_CURSOR_END:
603 if (didop) {
604 ++ep->l_cur;
605 memmove(rp, p + sizeof(u_char), sizeof(MARK));
606 F_CLR(ep, F_NOLOG);
607 return (0);
609 break;
610 case LOG_CURSOR_INIT:
611 break;
612 /* XXXX LOG_LINE_APPEND and LOG_LINE_INSERT split
613 for now, because db_insert won't work for adding
614 last line
616 case LOG_LINE_APPEND:
617 didop = 1;
618 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
619 --lno;
620 if (db_append(sp, 1, lno, p + sizeof(u_char) +
621 sizeof(db_recno_t), data.size - sizeof(u_char) -
622 sizeof(db_recno_t)))
623 goto err;
624 ++sp->rptlines[L_ADDED];
625 break;
626 case LOG_LINE_INSERT:
627 didop = 1;
628 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
629 if (db_insert(sp, lno, p + sizeof(u_char) +
630 sizeof(db_recno_t), data.size - sizeof(u_char) -
631 sizeof(db_recno_t)))
632 goto err;
633 ++sp->rptlines[L_ADDED];
634 break;
635 case LOG_LINE_DELETE:
636 didop = 1;
637 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
638 if (db_delete(sp, lno))
639 goto err;
640 ++sp->rptlines[L_DELETED];
641 break;
642 case LOG_LINE_RESET_B:
643 break;
644 case LOG_LINE_RESET_F:
645 didop = 1;
646 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
647 if (db_set(sp, lno, p + sizeof(u_char) +
648 sizeof(db_recno_t), data.size - sizeof(u_char) -
649 sizeof(db_recno_t)))
650 goto err;
651 if (sp->rptlchange != lno) {
652 sp->rptlchange = lno;
653 ++sp->rptlines[L_CHANGED];
655 break;
656 case LOG_MARK:
657 didop = 1;
658 memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
659 m.lno = lm.lno;
660 m.cno = lm.cno;
661 if (mark_set(sp, lm.name, &m, 0))
662 goto err;
663 break;
664 default:
665 abort();
669 err: F_CLR(ep, F_NOLOG);
670 return (1);
674 * log_err --
675 * Try and restart the log on failure, i.e. if we run out of memory.
677 static void
678 log_err(sp, file, line)
679 SCR *sp;
680 char *file;
681 int line;
683 EXF *ep;
685 msgq(sp, M_SYSERR, "015|%s/%d: log put error", tail(file), line);
686 ep = sp->ep;
687 (void)ep->log->close(ep->log, DB_NOSYNC);
688 if (!log_init(sp, ep))
689 msgq(sp, M_ERR, "267|Log restarted");
692 #if defined(DEBUG) && 0
693 static void
694 log_trace(sp, msg, rno, p)
695 SCR *sp;
696 char *msg;
697 db_recno_t rno;
698 u_char *p;
700 LMARK lm;
701 MARK m;
702 db_recno_t lno;
704 switch (*p) {
705 case LOG_CURSOR_INIT:
706 memmove(&m, p + sizeof(u_char), sizeof(MARK));
707 vtrace(sp, "%lu: %s: C_INIT: %u/%u\n", rno, msg, m.lno, m.cno);
708 break;
709 case LOG_CURSOR_END:
710 memmove(&m, p + sizeof(u_char), sizeof(MARK));
711 vtrace(sp, "%lu: %s: C_END: %u/%u\n", rno, msg, m.lno, m.cno);
712 break;
713 case LOG_LINE_APPEND:
714 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
715 vtrace(sp, "%lu: %s: APPEND: %lu\n", rno, msg, lno);
716 break;
717 case LOG_LINE_INSERT:
718 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
719 vtrace(sp, "%lu: %s: INSERT: %lu\n", rno, msg, lno);
720 break;
721 case LOG_LINE_DELETE:
722 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
723 vtrace(sp, "%lu: %s: DELETE: %lu\n", rno, msg, lno);
724 break;
725 case LOG_LINE_RESET_F:
726 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
727 vtrace(sp, "%lu: %s: RESET_F: %lu\n", rno, msg, lno);
728 break;
729 case LOG_LINE_RESET_B:
730 memmove(&lno, p + sizeof(u_char), sizeof(db_recno_t));
731 vtrace(sp, "%lu: %s: RESET_B: %lu\n", rno, msg, lno);
732 break;
733 case LOG_MARK:
734 memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
735 vtrace(sp,
736 "%lu: %s: MARK: %u/%u\n", rno, msg, lm.lno, lm.cno);
737 break;
738 default:
739 abort();
742 #endif