2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
9 static char sccsid
[] = "$Id: log.c,v 8.8 1993/12/09 19:42:07 bostic Exp $ (Berkeley) $Date: 1993/12/09 19:42:07 $";
12 #include <sys/types.h>
23 * The log consists of records, where a record contains a type byte and
24 * the a variable length byte string, as follows:
26 * LOG_CURSOR_INIT MARK
28 * LOG_LINE_APPEND recno_t char *
29 * LOG_LINE_DELETE recno_t char *
30 * LOG_LINE_INSERT recno_t char *
31 * LOG_LINE_RESET_F recno_t char *
32 * LOG_LINE_RESET_B recno_t char *
35 * We do before image physical logging. This means that the editor layer
36 * cannot modify records in place, even if simply deleting or overwriting
37 * characters. Since the smallest unit of logging is a line, we're using
38 * up lots of space. This may eventually have to be reduced, probably by
39 * doing logical logging, which is a much cooler database phrase.
41 * The implementation of roll-forward and roll-back when O_NUNDO is set
42 * is fairly simple. Each set of changes consists of a LOG_CURSOR_INIT
43 * record, followed by some number of other records, followed by a
44 * LOG_CURSOR_END record. LOG_LINE_RESET records come in pairs; the first
45 * is a LOG_LINE_RESET_B record, and is the line before the change. The
46 * second is a LOG_LINE_RESET_F, and is the line after the change. To do
47 * roll-back, back up until the LOG_CURSOR_INIT record before a change
48 * is reached. Roll-forward is done in a similar fashion.
50 * The historic vi undo (the variable O_NUNDO is not set) is trickier.
51 * The obvious solution is to implement the log as described above, but
52 * toggle whether the 'u' command means roll-forward or roll-back. In
53 * this scheme the 'U' command results in rolling backward until reaching
54 * a LOG_CURSOR_END record for a line different from the current one. It
55 * should be noted that this means that a subsequent 'u' command will make
56 * a change based on the new position of the log's cursor. This is okay,
57 * and, in fact, historic vi behaved that way.
60 static int log_cursor1
__P((SCR
*, EXF
*, int));
61 #if defined(DEBUG) && 0
62 static void log_trace
__P((SCR
*, char *, recno_t
, u_char
*));
65 /* Try and restart the log on failure, i.e. if we run out of memory. */
67 msgq(sp, M_ERR, "Error: %s/%d: put log error: %s.", \
68 tail(__FILE__), __LINE__, strerror(errno)); \
69 (void)ep->log->close(ep->log); \
70 if (!log_init(sp, ep)) \
71 msgq(sp, M_ERR, "Log restarted."); \
77 * Initialize the logging subsystem.
85 * Initialize the buffer. The logging subsystem has its own
86 * buffers because the global ones are almost by definition
87 * going to be in use when the log runs.
91 ep
->l_cursor
.lno
= 1; /* XXX Any valid recno. */
93 ep
->l_high
= ep
->l_cur
= 1;
95 ep
->log
= dbopen(NULL
, O_CREAT
| O_NONBLOCK
| O_RDWR
,
96 S_IRUSR
| S_IWUSR
, DB_RECNO
, NULL
);
97 if (ep
->log
== NULL
) {
98 msgq(sp
, M_ERR
, "log db: %s", strerror(errno
));
108 * Close the logging subsystem.
115 if (ep
->log
!= NULL
) {
116 (void)(ep
->log
->close
)(ep
->log
);
119 if (ep
->l_lp
!= NULL
) {
124 ep
->l_cursor
.lno
= 1; /* XXX Any valid recno. */
125 ep
->l_cursor
.cno
= 0;
126 ep
->l_high
= ep
->l_cur
= 1;
132 * Log the current cursor position, starting an event.
140 * If any changes were made since the last cursor init,
141 * put out the ending cursor record.
143 if (ep
->l_cursor
.lno
== OOBLNO
) {
144 ep
->l_cursor
.lno
= sp
->lno
;
145 ep
->l_cursor
.cno
= sp
->cno
;
146 return (log_cursor1(sp
, ep
, LOG_CURSOR_END
));
148 ep
->l_cursor
.lno
= sp
->lno
;
149 ep
->l_cursor
.cno
= sp
->cno
;
155 * Actually push a cursor record out.
158 log_cursor1(sp
, ep
, type
)
165 BINC_RET(sp
, ep
->l_lp
, ep
->l_len
, sizeof(u_char
) + sizeof(MARK
));
167 memmove(ep
->l_lp
+ sizeof(u_char
), &ep
->l_cursor
, sizeof(MARK
));
169 key
.data
= &ep
->l_cur
;
170 key
.size
= sizeof(recno_t
);
171 data
.data
= ep
->l_lp
;
172 data
.size
= sizeof(u_char
) + sizeof(MARK
);
173 if (ep
->log
->put(ep
->log
, &key
, &data
, 0) == -1)
176 #if defined(DEBUG) && 0
177 TRACE(sp
, "%lu: %s: %u/%u\n", ep
->l_cur
,
178 type
== LOG_CURSOR_INIT
? "log_cursor_init" : "log_cursor_end",
181 /* Reset high water mark. */
182 ep
->l_high
= ++ep
->l_cur
;
192 log_line(sp
, ep
, lno
, action
)
202 if (F_ISSET(ep
, F_NOLOG
))
207 * Hack for vi. Clear the undo flag so that the next 'u'
208 * command does a roll-back instead of a roll-forward.
212 /* Put out one initial cursor record per set of changes. */
213 if (ep
->l_cursor
.lno
!= OOBLNO
) {
214 if (log_cursor1(sp
, ep
, LOG_CURSOR_INIT
))
216 ep
->l_cursor
.lno
= OOBLNO
;
220 * Put out the changes. If it's a LOG_LINE_RESET_B call, it's a
221 * special case, avoid the caches. Also, if it fails and it's
222 * line 1, it just means that the user started with an empty file,
223 * so fake an empty length line.
225 if (action
== LOG_LINE_RESET_B
) {
226 if ((lp
= file_rline(sp
, ep
, lno
, &len
)) == NULL
) {
228 GETLINE_ERR(sp
, lno
);
235 if ((lp
= file_gline(sp
, ep
, lno
, &len
)) == NULL
) {
236 GETLINE_ERR(sp
, lno
);
240 ep
->l_lp
, ep
->l_len
, len
+ sizeof(u_char
) + sizeof(recno_t
));
241 ep
->l_lp
[0] = action
;
242 memmove(ep
->l_lp
+ sizeof(u_char
), &lno
, sizeof(recno_t
));
243 memmove(ep
->l_lp
+ sizeof(u_char
) + sizeof(recno_t
), lp
, len
);
245 key
.data
= &ep
->l_cur
;
246 key
.size
= sizeof(recno_t
);
247 data
.data
= ep
->l_lp
;
248 data
.size
= len
+ sizeof(u_char
) + sizeof(recno_t
);
249 if (ep
->log
->put(ep
->log
, &key
, &data
, 0) == -1)
252 #if defined(DEBUG) && 0
254 case LOG_LINE_APPEND
:
255 TRACE(sp
, "%u: log_line: append: %lu {%u}\n",
256 ep
->l_cur
, lno
, len
);
258 case LOG_LINE_DELETE
:
259 TRACE(sp
, "%lu: log_line: delete: %lu {%u}\n",
260 ep
->l_cur
, lno
, len
);
262 case LOG_LINE_INSERT
:
263 TRACE(sp
, "%lu: log_line: insert: %lu {%u}\n",
264 ep
->l_cur
, lno
, len
);
266 case LOG_LINE_RESET_F
:
267 TRACE(sp
, "%lu: log_line: reset_f: %lu {%u}\n",
268 ep
->l_cur
, lno
, len
);
270 case LOG_LINE_RESET_B
:
271 TRACE(sp
, "%lu: log_line: reset_b: %lu {%u}\n",
272 ep
->l_cur
, lno
, len
);
276 /* Reset high water mark. */
277 ep
->l_high
= ++ep
->l_cur
;
284 * Log a mark position. For the log to work, we assume that there
285 * aren't any operations that just put out a log record -- this
286 * would mean that undo operations would only reset marks, and not
287 * cause any other change.
297 if (F_ISSET(ep
, F_NOLOG
))
300 /* Put out one initial cursor record per set of changes. */
301 if (ep
->l_cursor
.lno
!= OOBLNO
) {
302 if (log_cursor1(sp
, ep
, LOG_CURSOR_INIT
))
304 ep
->l_cursor
.lno
= OOBLNO
;
307 BINC_RET(sp
, ep
->l_lp
,
308 ep
->l_len
, sizeof(u_char
) + sizeof(MARK
));
309 ep
->l_lp
[0] = LOG_MARK
;
310 memmove(ep
->l_lp
+ sizeof(u_char
), mp
, sizeof(MARK
));
312 key
.data
= &ep
->l_cur
;
313 key
.size
= sizeof(recno_t
);
314 data
.data
= ep
->l_lp
;
315 data
.size
= sizeof(u_char
) + sizeof(MARK
);
316 if (ep
->log
->put(ep
->log
, &key
, &data
, 0) == -1)
319 /* Reset high water mark. */
320 ep
->l_high
= ++ep
->l_cur
;
326 * Roll the log backward one operation.
329 log_backward(sp
, ep
, rp
)
340 if (F_ISSET(ep
, F_NOLOG
)) {
342 "Logging not being performed, undo not possible.");
346 if (ep
->l_cur
== 1) {
347 msgq(sp
, M_BERR
, "No changes to undo.");
351 F_SET(ep
, F_NOLOG
); /* Turn off logging. */
353 key
.data
= &ep
->l_cur
; /* Initialize db request. */
354 key
.size
= sizeof(recno_t
);
357 if (ep
->log
->get(ep
->log
, &key
, &data
, 0))
359 #if defined(DEBUG) && 0
360 log_trace(sp
, "log_backward", ep
->l_cur
, data
.data
);
362 switch (*(p
= (u_char
*)data
.data
)) {
363 case LOG_CURSOR_INIT
:
365 memmove(rp
, p
+ sizeof(u_char
), sizeof(MARK
));
372 case LOG_LINE_APPEND
:
373 case LOG_LINE_INSERT
:
375 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
376 if (file_dline(sp
, ep
, lno
))
378 ++sp
->rptlines
[L_DELETED
];
380 case LOG_LINE_DELETE
:
382 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
383 if (file_iline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
384 sizeof(recno_t
), data
.size
- sizeof(u_char
) -
387 ++sp
->rptlines
[L_ADDED
];
389 case LOG_LINE_RESET_F
:
391 case LOG_LINE_RESET_B
:
393 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
394 if (file_sline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
395 sizeof(recno_t
), data
.size
- sizeof(u_char
) -
398 ++sp
->rptlines
[L_CHANGED
];
402 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
403 if (mark_set(sp
, ep
, m
.name
, &m
, 0))
411 err
: F_CLR(ep
, F_NOLOG
);
417 * Reset the line to its original appearance.
420 * There's a bug in this code due to our not logging cursor movements
421 * unless a change was made. If you do a change, move off the line,
422 * then move back on and do a 'U', the line will be restored to the way
423 * it was before the original change.
426 log_setline(sp
, ep
, rp
)
436 if (F_ISSET(ep
, F_NOLOG
)) {
438 "Logging not being performed, undo not possible.");
445 F_SET(ep
, F_NOLOG
); /* Turn off logging. */
447 key
.data
= &ep
->l_cur
; /* Initialize db request. */
448 key
.size
= sizeof(recno_t
);
451 * Historically, U always reset the cursor to the first column in
452 * the line. This seems a bit non-intuitive, but, considering that
453 * we may have undone multiple changes, anything else is going to
454 * look like random positioning.
461 if (ep
->log
->get(ep
->log
, &key
, &data
, 0))
463 #if defined(DEBUG) && 0
464 log_trace(sp
, "log_setline", ep
->l_cur
, data
.data
);
466 switch (*(p
= (u_char
*)data
.data
)) {
467 case LOG_CURSOR_INIT
:
468 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
469 if (m
.lno
!= sp
->lno
|| ep
->l_cur
== 1) {
475 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
476 if (m
.lno
!= sp
->lno
) {
482 case LOG_LINE_APPEND
:
483 case LOG_LINE_INSERT
:
484 case LOG_LINE_DELETE
:
485 case LOG_LINE_RESET_F
:
487 case LOG_LINE_RESET_B
:
488 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
489 if (lno
== sp
->lno
&&
490 file_sline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
491 sizeof(recno_t
), data
.size
- sizeof(u_char
) -
494 ++sp
->rptlines
[L_CHANGED
];
496 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
497 if (mark_set(sp
, ep
, m
.name
, &m
, 0))
505 err
: F_CLR(ep
, F_NOLOG
);
511 * Roll the log forward one operation.
514 log_forward(sp
, ep
, rp
)
525 if (F_ISSET(ep
, F_NOLOG
)) {
527 "Logging not being performed, roll-forward not possible.");
531 if (ep
->l_cur
== ep
->l_high
) {
532 msgq(sp
, M_BERR
, "No changes to re-do.");
536 F_SET(ep
, F_NOLOG
); /* Turn off logging. */
538 key
.data
= &ep
->l_cur
; /* Initialize db request. */
539 key
.size
= sizeof(recno_t
);
542 if (ep
->log
->get(ep
->log
, &key
, &data
, 0))
544 #if defined(DEBUG) && 0
545 log_trace(sp
, "log_forward", ep
->l_cur
, data
.data
);
547 switch (*(p
= (u_char
*)data
.data
)) {
551 memmove(rp
, p
+ sizeof(u_char
), sizeof(MARK
));
556 case LOG_CURSOR_INIT
:
558 case LOG_LINE_APPEND
:
559 case LOG_LINE_INSERT
:
561 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
562 if (file_iline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
563 sizeof(recno_t
), data
.size
- sizeof(u_char
) -
566 ++sp
->rptlines
[L_ADDED
];
568 case LOG_LINE_DELETE
:
570 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
571 if (file_dline(sp
, ep
, lno
))
573 ++sp
->rptlines
[L_DELETED
];
575 case LOG_LINE_RESET_B
:
577 case LOG_LINE_RESET_F
:
579 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
580 if (file_sline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
581 sizeof(recno_t
), data
.size
- sizeof(u_char
) -
584 ++sp
->rptlines
[L_CHANGED
];
588 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
589 if (mark_set(sp
, ep
, m
.name
, &m
, 0))
597 err
: F_CLR(ep
, F_NOLOG
);
601 #if defined(DEBUG) && 0
603 log_trace(sp
, msg
, rno
, p
)
613 case LOG_CURSOR_INIT
:
614 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
615 TRACE(sp
, "%lu: %s: C_INIT: %u/%u\n", rno
, msg
, m
.lno
, m
.cno
);
618 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
619 TRACE(sp
, "%lu: %s: C_END: %u/%u\n", rno
, msg
, m
.lno
, m
.cno
);
621 case LOG_LINE_APPEND
:
622 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
623 TRACE(sp
, "%lu: %s: APPEND: %lu\n", rno
, msg
, lno
);
625 case LOG_LINE_INSERT
:
626 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
627 TRACE(sp
, "%lu: %s: INSERT: %lu\n", rno
, msg
, lno
);
629 case LOG_LINE_DELETE
:
630 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
631 TRACE(sp
, "%lu: %s: DELETE: %lu\n", rno
, msg
, lno
);
633 case LOG_LINE_RESET_F
:
634 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
635 TRACE(sp
, "%lu: %s: RESET_F: %lu\n", rno
, msg
, lno
);
637 case LOG_LINE_RESET_B
:
638 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
639 TRACE(sp
, "%lu: %s: RESET_B: %lu\n", rno
, msg
, lno
);
642 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
643 TRACE(sp
, "%lu: %s: MARK: %u/%u\n", rno
, msg
, m
.lno
, m
.cno
);