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.7 1993/11/13 18:00:31 bostic Exp $ (Berkeley) $Date: 1993/11/13 18:00:31 $";
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(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
);
239 BINC(sp
, ep
->l_lp
, ep
->l_len
, len
+ sizeof(u_char
) + sizeof(recno_t
));
240 ep
->l_lp
[0] = action
;
241 memmove(ep
->l_lp
+ sizeof(u_char
), &lno
, sizeof(recno_t
));
242 memmove(ep
->l_lp
+ sizeof(u_char
) + sizeof(recno_t
), lp
, len
);
244 key
.data
= &ep
->l_cur
;
245 key
.size
= sizeof(recno_t
);
246 data
.data
= ep
->l_lp
;
247 data
.size
= len
+ sizeof(u_char
) + sizeof(recno_t
);
248 if (ep
->log
->put(ep
->log
, &key
, &data
, 0) == -1)
251 #if defined(DEBUG) && 0
253 case LOG_LINE_APPEND
:
254 TRACE(sp
, "%u: log_line: append: %lu {%u}\n",
255 ep
->l_cur
, lno
, len
);
257 case LOG_LINE_DELETE
:
258 TRACE(sp
, "%lu: log_line: delete: %lu {%u}\n",
259 ep
->l_cur
, lno
, len
);
261 case LOG_LINE_INSERT
:
262 TRACE(sp
, "%lu: log_line: insert: %lu {%u}\n",
263 ep
->l_cur
, lno
, len
);
265 case LOG_LINE_RESET_F
:
266 TRACE(sp
, "%lu: log_line: reset_f: %lu {%u}\n",
267 ep
->l_cur
, lno
, len
);
269 case LOG_LINE_RESET_B
:
270 TRACE(sp
, "%lu: log_line: reset_b: %lu {%u}\n",
271 ep
->l_cur
, lno
, len
);
275 /* Reset high water mark. */
276 ep
->l_high
= ++ep
->l_cur
;
283 * Log a mark position. For the log to work, we assume that there
284 * aren't any operations that just put out a log record -- this
285 * would mean that undo operations would only reset marks, and not
286 * cause any other change.
296 if (F_ISSET(ep
, F_NOLOG
))
299 /* Put out one initial cursor record per set of changes. */
300 if (ep
->l_cursor
.lno
!= OOBLNO
) {
301 if (log_cursor1(sp
, ep
, LOG_CURSOR_INIT
))
303 ep
->l_cursor
.lno
= OOBLNO
;
307 ep
->l_len
, sizeof(u_char
) + sizeof(MARK
));
308 ep
->l_lp
[0] = LOG_MARK
;
309 memmove(ep
->l_lp
+ sizeof(u_char
), mp
, sizeof(MARK
));
311 key
.data
= &ep
->l_cur
;
312 key
.size
= sizeof(recno_t
);
313 data
.data
= ep
->l_lp
;
314 data
.size
= sizeof(u_char
) + sizeof(MARK
);
315 if (ep
->log
->put(ep
->log
, &key
, &data
, 0) == -1)
318 /* Reset high water mark. */
319 ep
->l_high
= ++ep
->l_cur
;
325 * Roll the log backward one operation.
328 log_backward(sp
, ep
, rp
)
339 if (F_ISSET(ep
, F_NOLOG
)) {
341 "Logging not being performed, undo not possible.");
345 if (ep
->l_cur
== 1) {
346 msgq(sp
, M_BERR
, "No changes to undo.");
350 F_SET(ep
, F_NOLOG
); /* Turn off logging. */
352 key
.data
= &ep
->l_cur
; /* Initialize db request. */
353 key
.size
= sizeof(recno_t
);
356 if (ep
->log
->get(ep
->log
, &key
, &data
, 0))
358 #if defined(DEBUG) && 0
359 log_trace(sp
, "log_backward", ep
->l_cur
, data
.data
);
361 switch (*(p
= (u_char
*)data
.data
)) {
362 case LOG_CURSOR_INIT
:
364 memmove(rp
, p
+ sizeof(u_char
), sizeof(MARK
));
371 case LOG_LINE_APPEND
:
372 case LOG_LINE_INSERT
:
374 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
375 if (file_dline(sp
, ep
, lno
))
377 ++sp
->rptlines
[L_DELETED
];
379 case LOG_LINE_DELETE
:
381 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
382 if (file_iline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
383 sizeof(recno_t
), data
.size
- sizeof(u_char
) -
386 ++sp
->rptlines
[L_ADDED
];
388 case LOG_LINE_RESET_F
:
390 case LOG_LINE_RESET_B
:
392 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
393 if (file_sline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
394 sizeof(recno_t
), data
.size
- sizeof(u_char
) -
397 ++sp
->rptlines
[L_CHANGED
];
401 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
402 if (mark_set(sp
, ep
, m
.name
, &m
, 0))
410 err
: F_CLR(ep
, F_NOLOG
);
416 * Reset the line to its original appearance.
419 * There's a bug in this code due to our not logging cursor movements
420 * unless a change was made. If you do a change, move off the line,
421 * then move back on and do a 'U', the line will be restored to the way
422 * it was before the original change.
425 log_setline(sp
, ep
, rp
)
435 if (F_ISSET(ep
, F_NOLOG
)) {
437 "Logging not being performed, undo not possible.");
444 F_SET(ep
, F_NOLOG
); /* Turn off logging. */
446 key
.data
= &ep
->l_cur
; /* Initialize db request. */
447 key
.size
= sizeof(recno_t
);
450 * Historically, U always reset the cursor to the first column in
451 * the line. This seems a bit non-intuitive, but, considering that
452 * we may have undone multiple changes, anything else is going to
453 * look like random positioning.
460 if (ep
->log
->get(ep
->log
, &key
, &data
, 0))
462 #if defined(DEBUG) && 0
463 log_trace(sp
, "log_setline", ep
->l_cur
, data
.data
);
465 switch (*(p
= (u_char
*)data
.data
)) {
466 case LOG_CURSOR_INIT
:
467 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
468 if (m
.lno
!= sp
->lno
|| ep
->l_cur
== 1) {
474 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
475 if (m
.lno
!= sp
->lno
) {
481 case LOG_LINE_APPEND
:
482 case LOG_LINE_INSERT
:
483 case LOG_LINE_DELETE
:
484 case LOG_LINE_RESET_F
:
486 case LOG_LINE_RESET_B
:
487 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
488 if (lno
== sp
->lno
&&
489 file_sline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
490 sizeof(recno_t
), data
.size
- sizeof(u_char
) -
493 ++sp
->rptlines
[L_CHANGED
];
495 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
496 if (mark_set(sp
, ep
, m
.name
, &m
, 0))
504 err
: F_CLR(ep
, F_NOLOG
);
510 * Roll the log forward one operation.
513 log_forward(sp
, ep
, rp
)
524 if (F_ISSET(ep
, F_NOLOG
)) {
526 "Logging not being performed, roll-forward not possible.");
530 if (ep
->l_cur
== ep
->l_high
) {
531 msgq(sp
, M_BERR
, "No changes to re-do.");
535 F_SET(ep
, F_NOLOG
); /* Turn off logging. */
537 key
.data
= &ep
->l_cur
; /* Initialize db request. */
538 key
.size
= sizeof(recno_t
);
541 if (ep
->log
->get(ep
->log
, &key
, &data
, 0))
543 #if defined(DEBUG) && 0
544 log_trace(sp
, "log_forward", ep
->l_cur
, data
.data
);
546 switch (*(p
= (u_char
*)data
.data
)) {
550 memmove(rp
, p
+ sizeof(u_char
), sizeof(MARK
));
555 case LOG_CURSOR_INIT
:
557 case LOG_LINE_APPEND
:
558 case LOG_LINE_INSERT
:
560 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
561 if (file_iline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
562 sizeof(recno_t
), data
.size
- sizeof(u_char
) -
565 ++sp
->rptlines
[L_ADDED
];
567 case LOG_LINE_DELETE
:
569 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
570 if (file_dline(sp
, ep
, lno
))
572 ++sp
->rptlines
[L_DELETED
];
574 case LOG_LINE_RESET_B
:
576 case LOG_LINE_RESET_F
:
578 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
579 if (file_sline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
580 sizeof(recno_t
), data
.size
- sizeof(u_char
) -
583 ++sp
->rptlines
[L_CHANGED
];
587 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
588 if (mark_set(sp
, ep
, m
.name
, &m
, 0))
596 err
: F_CLR(ep
, F_NOLOG
);
600 #if defined(DEBUG) && 0
602 log_trace(sp
, msg
, rno
, p
)
612 case LOG_CURSOR_INIT
:
613 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
614 TRACE(sp
, "%lu: %s: C_INIT: %u/%u\n", rno
, msg
, m
.lno
, m
.cno
);
617 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
618 TRACE(sp
, "%lu: %s: C_END: %u/%u\n", rno
, msg
, m
.lno
, m
.cno
);
620 case LOG_LINE_APPEND
:
621 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
622 TRACE(sp
, "%lu: %s: APPEND: %lu\n", rno
, msg
, lno
);
624 case LOG_LINE_INSERT
:
625 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
626 TRACE(sp
, "%lu: %s: INSERT: %lu\n", rno
, msg
, lno
);
628 case LOG_LINE_DELETE
:
629 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
630 TRACE(sp
, "%lu: %s: DELETE: %lu\n", rno
, msg
, lno
);
632 case LOG_LINE_RESET_F
:
633 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
634 TRACE(sp
, "%lu: %s: RESET_F: %lu\n", rno
, msg
, lno
);
636 case LOG_LINE_RESET_B
:
637 memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
638 TRACE(sp
, "%lu: %s: RESET_B: %lu\n", rno
, msg
, lno
);
641 memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
642 TRACE(sp
, "%lu: %s: MARK: %u/%u\n", rno
, msg
, m
.lno
, m
.cno
);