add NEXINIT as alternative to EXINIT
[nvi.git] / common / log.c
blobd7a8653297f8699335910575f1d69db4c3b5c5fc
1 /*-
2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
6 */
8 #ifndef lint
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 $";
10 #endif /* not lint */
12 #include <sys/types.h>
13 #include <sys/stat.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <stdlib.h>
18 #include <string.h>
20 #include "vi.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
27 * LOG_CURSOR_END 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 *
33 * LOG_MARK MARK
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 *));
63 #endif
65 /* Try and restart the log on failure, i.e. if we run out of memory. */
66 #define LOG_ERR { \
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."); \
72 return (1); \
76 * log_init --
77 * Initialize the logging subsystem.
79 int
80 log_init(sp, ep)
81 SCR *sp;
82 EXF *ep;
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.
89 ep->l_lp = NULL;
90 ep->l_len = 0;
91 ep->l_cursor.lno = 1; /* XXX Any valid recno. */
92 ep->l_cursor.cno = 0;
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));
99 F_SET(ep, F_NOLOG);
100 return (1);
103 return (0);
107 * log_end --
108 * Close the logging subsystem.
111 log_end(sp, ep)
112 SCR *sp;
113 EXF *ep;
115 if (ep->log != NULL) {
116 (void)(ep->log->close)(ep->log);
117 ep->log = NULL;
119 if (ep->l_lp != NULL) {
120 free(ep->l_lp);
121 ep->l_lp = NULL;
123 ep->l_len = 0;
124 ep->l_cursor.lno = 1; /* XXX Any valid recno. */
125 ep->l_cursor.cno = 0;
126 ep->l_high = ep->l_cur = 1;
127 return (0);
131 * log_cursor --
132 * Log the current cursor position, starting an event.
135 log_cursor(sp, ep)
136 SCR *sp;
137 EXF *ep;
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;
150 return (0);
154 * log_cursor1 --
155 * Actually push a cursor record out.
157 static int
158 log_cursor1(sp, ep, type)
159 SCR *sp;
160 EXF *ep;
161 int type;
163 DBT data, key;
165 BINC_RET(sp, ep->l_lp, ep->l_len, sizeof(u_char) + sizeof(MARK));
166 ep->l_lp[0] = type;
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)
174 LOG_ERR;
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",
179 sp->lno, sp->cno);
180 #endif
181 /* Reset high water mark. */
182 ep->l_high = ++ep->l_cur;
184 return (0);
188 * log_line --
189 * Log a line change.
192 log_line(sp, ep, lno, action)
193 SCR *sp;
194 EXF *ep;
195 recno_t lno;
196 u_int action;
198 DBT data, key;
199 size_t len;
200 char *lp;
202 if (F_ISSET(ep, F_NOLOG))
203 return (0);
206 * XXX
207 * Hack for vi. Clear the undo flag so that the next 'u'
208 * command does a roll-back instead of a roll-forward.
210 F_CLR(ep, F_UNDO);
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))
215 return (1);
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) {
227 if (lno != 1) {
228 GETLINE_ERR(sp, lno);
229 return (1);
231 len = 0;
232 lp = "";
234 } else
235 if ((lp = file_gline(sp, ep, lno, &len)) == NULL) {
236 GETLINE_ERR(sp, lno);
237 return (1);
239 BINC_RET(sp,
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)
250 LOG_ERR;
252 #if defined(DEBUG) && 0
253 switch (action) {
254 case LOG_LINE_APPEND:
255 TRACE(sp, "%u: log_line: append: %lu {%u}\n",
256 ep->l_cur, lno, len);
257 break;
258 case LOG_LINE_DELETE:
259 TRACE(sp, "%lu: log_line: delete: %lu {%u}\n",
260 ep->l_cur, lno, len);
261 break;
262 case LOG_LINE_INSERT:
263 TRACE(sp, "%lu: log_line: insert: %lu {%u}\n",
264 ep->l_cur, lno, len);
265 break;
266 case LOG_LINE_RESET_F:
267 TRACE(sp, "%lu: log_line: reset_f: %lu {%u}\n",
268 ep->l_cur, lno, len);
269 break;
270 case LOG_LINE_RESET_B:
271 TRACE(sp, "%lu: log_line: reset_b: %lu {%u}\n",
272 ep->l_cur, lno, len);
273 break;
275 #endif
276 /* Reset high water mark. */
277 ep->l_high = ++ep->l_cur;
279 return (0);
283 * log_mark --
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.
290 log_mark(sp, ep, mp)
291 SCR *sp;
292 EXF *ep;
293 MARK *mp;
295 DBT data, key;
297 if (F_ISSET(ep, F_NOLOG))
298 return (0);
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))
303 return (1);
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)
317 LOG_ERR;
319 /* Reset high water mark. */
320 ep->l_high = ++ep->l_cur;
321 return (0);
325 * Log_backward --
326 * Roll the log backward one operation.
329 log_backward(sp, ep, rp)
330 SCR *sp;
331 EXF *ep;
332 MARK *rp;
334 DBT key, data;
335 MARK m;
336 recno_t lno;
337 int didop;
338 u_char *p;
340 if (F_ISSET(ep, F_NOLOG)) {
341 msgq(sp, M_ERR,
342 "Logging not being performed, undo not possible.");
343 return (1);
346 if (ep->l_cur == 1) {
347 msgq(sp, M_BERR, "No changes to undo.");
348 return (1);
351 F_SET(ep, F_NOLOG); /* Turn off logging. */
353 key.data = &ep->l_cur; /* Initialize db request. */
354 key.size = sizeof(recno_t);
355 for (didop = 0;;) {
356 --ep->l_cur;
357 if (ep->log->get(ep->log, &key, &data, 0))
358 LOG_ERR;
359 #if defined(DEBUG) && 0
360 log_trace(sp, "log_backward", ep->l_cur, data.data);
361 #endif
362 switch (*(p = (u_char *)data.data)) {
363 case LOG_CURSOR_INIT:
364 if (didop) {
365 memmove(rp, p + sizeof(u_char), sizeof(MARK));
366 F_CLR(ep, F_NOLOG);
367 return (0);
369 break;
370 case LOG_CURSOR_END:
371 break;
372 case LOG_LINE_APPEND:
373 case LOG_LINE_INSERT:
374 didop = 1;
375 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
376 if (file_dline(sp, ep, lno))
377 goto err;
378 ++sp->rptlines[L_DELETED];
379 break;
380 case LOG_LINE_DELETE:
381 didop = 1;
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) -
385 sizeof(recno_t)))
386 goto err;
387 ++sp->rptlines[L_ADDED];
388 break;
389 case LOG_LINE_RESET_F:
390 break;
391 case LOG_LINE_RESET_B:
392 didop = 1;
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) -
396 sizeof(recno_t)))
397 goto err;
398 ++sp->rptlines[L_CHANGED];
399 break;
400 case LOG_MARK:
401 didop = 1;
402 memmove(&m, p + sizeof(u_char), sizeof(MARK));
403 if (mark_set(sp, ep, m.name, &m, 0))
404 goto err;
405 break;
406 default:
407 abort();
411 err: F_CLR(ep, F_NOLOG);
412 return (1);
416 * Log_setline --
417 * Reset the line to its original appearance.
419 * XXX
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)
427 SCR *sp;
428 EXF *ep;
429 MARK *rp;
431 DBT key, data;
432 MARK m;
433 recno_t lno;
434 u_char *p;
436 if (F_ISSET(ep, F_NOLOG)) {
437 msgq(sp, M_ERR,
438 "Logging not being performed, undo not possible.");
439 return (1);
442 if (ep->l_cur == 1)
443 return (1);
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.
456 rp->lno = sp->lno;
457 rp->cno = 0;
459 for (;;) {
460 --ep->l_cur;
461 if (ep->log->get(ep->log, &key, &data, 0))
462 LOG_ERR;
463 #if defined(DEBUG) && 0
464 log_trace(sp, "log_setline", ep->l_cur, data.data);
465 #endif
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) {
470 F_CLR(ep, F_NOLOG);
471 return (0);
473 break;
474 case LOG_CURSOR_END:
475 memmove(&m, p + sizeof(u_char), sizeof(MARK));
476 if (m.lno != sp->lno) {
477 ++ep->l_cur;
478 F_CLR(ep, F_NOLOG);
479 return (0);
481 break;
482 case LOG_LINE_APPEND:
483 case LOG_LINE_INSERT:
484 case LOG_LINE_DELETE:
485 case LOG_LINE_RESET_F:
486 break;
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) -
492 sizeof(recno_t)))
493 goto err;
494 ++sp->rptlines[L_CHANGED];
495 case LOG_MARK:
496 memmove(&m, p + sizeof(u_char), sizeof(MARK));
497 if (mark_set(sp, ep, m.name, &m, 0))
498 goto err;
499 break;
500 default:
501 abort();
505 err: F_CLR(ep, F_NOLOG);
506 return (1);
510 * Log_forward --
511 * Roll the log forward one operation.
514 log_forward(sp, ep, rp)
515 SCR *sp;
516 EXF *ep;
517 MARK *rp;
519 DBT key, data;
520 MARK m;
521 recno_t lno;
522 int didop;
523 u_char *p;
525 if (F_ISSET(ep, F_NOLOG)) {
526 msgq(sp, M_ERR,
527 "Logging not being performed, roll-forward not possible.");
528 return (1);
531 if (ep->l_cur == ep->l_high) {
532 msgq(sp, M_BERR, "No changes to re-do.");
533 return (1);
536 F_SET(ep, F_NOLOG); /* Turn off logging. */
538 key.data = &ep->l_cur; /* Initialize db request. */
539 key.size = sizeof(recno_t);
540 for (didop = 0;;) {
541 ++ep->l_cur;
542 if (ep->log->get(ep->log, &key, &data, 0))
543 LOG_ERR;
544 #if defined(DEBUG) && 0
545 log_trace(sp, "log_forward", ep->l_cur, data.data);
546 #endif
547 switch (*(p = (u_char *)data.data)) {
548 case LOG_CURSOR_END:
549 if (didop) {
550 ++ep->l_cur;
551 memmove(rp, p + sizeof(u_char), sizeof(MARK));
552 F_CLR(ep, F_NOLOG);
553 return (0);
555 break;
556 case LOG_CURSOR_INIT:
557 break;
558 case LOG_LINE_APPEND:
559 case LOG_LINE_INSERT:
560 didop = 1;
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) -
564 sizeof(recno_t)))
565 goto err;
566 ++sp->rptlines[L_ADDED];
567 break;
568 case LOG_LINE_DELETE:
569 didop = 1;
570 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
571 if (file_dline(sp, ep, lno))
572 goto err;
573 ++sp->rptlines[L_DELETED];
574 break;
575 case LOG_LINE_RESET_B:
576 break;
577 case LOG_LINE_RESET_F:
578 didop = 1;
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) -
582 sizeof(recno_t)))
583 goto err;
584 ++sp->rptlines[L_CHANGED];
585 break;
586 case LOG_MARK:
587 didop = 1;
588 memmove(&m, p + sizeof(u_char), sizeof(MARK));
589 if (mark_set(sp, ep, m.name, &m, 0))
590 goto err;
591 break;
592 default:
593 abort();
597 err: F_CLR(ep, F_NOLOG);
598 return (1);
601 #if defined(DEBUG) && 0
602 static void
603 log_trace(sp, msg, rno, p)
604 SCR *sp;
605 char *msg;
606 recno_t rno;
607 u_char *p;
609 MARK m;
610 recno_t lno;
612 switch (*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);
616 break;
617 case LOG_CURSOR_END:
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);
620 break;
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);
624 break;
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);
628 break;
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);
632 break;
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);
636 break;
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);
640 break;
641 case LOG_MARK:
642 memmove(&m, p + sizeof(u_char), sizeof(MARK));
643 TRACE(sp, "%lu: %s: MARK: %u/%u\n", rno, msg, m.lno, m.cno);
644 break;
645 default:
646 abort();
649 #endif