add svi_column so svi can return sc_col to vi/v_ntext.c
[nvi.git] / common / log.c
blob52346a852efd1f1b6acc19e243a605c5e8843455
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.7 1993/11/13 18:00:31 bostic Exp $ (Berkeley) $Date: 1993/11/13 18:00:31 $";
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(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(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)
249 LOG_ERR;
251 #if defined(DEBUG) && 0
252 switch (action) {
253 case LOG_LINE_APPEND:
254 TRACE(sp, "%u: log_line: append: %lu {%u}\n",
255 ep->l_cur, lno, len);
256 break;
257 case LOG_LINE_DELETE:
258 TRACE(sp, "%lu: log_line: delete: %lu {%u}\n",
259 ep->l_cur, lno, len);
260 break;
261 case LOG_LINE_INSERT:
262 TRACE(sp, "%lu: log_line: insert: %lu {%u}\n",
263 ep->l_cur, lno, len);
264 break;
265 case LOG_LINE_RESET_F:
266 TRACE(sp, "%lu: log_line: reset_f: %lu {%u}\n",
267 ep->l_cur, lno, len);
268 break;
269 case LOG_LINE_RESET_B:
270 TRACE(sp, "%lu: log_line: reset_b: %lu {%u}\n",
271 ep->l_cur, lno, len);
272 break;
274 #endif
275 /* Reset high water mark. */
276 ep->l_high = ++ep->l_cur;
278 return (0);
282 * log_mark --
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.
289 log_mark(sp, ep, mp)
290 SCR *sp;
291 EXF *ep;
292 MARK *mp;
294 DBT data, key;
296 if (F_ISSET(ep, F_NOLOG))
297 return (0);
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))
302 return (1);
303 ep->l_cursor.lno = OOBLNO;
306 BINC(sp, ep->l_lp,
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)
316 LOG_ERR;
318 /* Reset high water mark. */
319 ep->l_high = ++ep->l_cur;
320 return (0);
324 * Log_backward --
325 * Roll the log backward one operation.
328 log_backward(sp, ep, rp)
329 SCR *sp;
330 EXF *ep;
331 MARK *rp;
333 DBT key, data;
334 MARK m;
335 recno_t lno;
336 int didop;
337 u_char *p;
339 if (F_ISSET(ep, F_NOLOG)) {
340 msgq(sp, M_ERR,
341 "Logging not being performed, undo not possible.");
342 return (1);
345 if (ep->l_cur == 1) {
346 msgq(sp, M_BERR, "No changes to undo.");
347 return (1);
350 F_SET(ep, F_NOLOG); /* Turn off logging. */
352 key.data = &ep->l_cur; /* Initialize db request. */
353 key.size = sizeof(recno_t);
354 for (didop = 0;;) {
355 --ep->l_cur;
356 if (ep->log->get(ep->log, &key, &data, 0))
357 LOG_ERR;
358 #if defined(DEBUG) && 0
359 log_trace(sp, "log_backward", ep->l_cur, data.data);
360 #endif
361 switch (*(p = (u_char *)data.data)) {
362 case LOG_CURSOR_INIT:
363 if (didop) {
364 memmove(rp, p + sizeof(u_char), sizeof(MARK));
365 F_CLR(ep, F_NOLOG);
366 return (0);
368 break;
369 case LOG_CURSOR_END:
370 break;
371 case LOG_LINE_APPEND:
372 case LOG_LINE_INSERT:
373 didop = 1;
374 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
375 if (file_dline(sp, ep, lno))
376 goto err;
377 ++sp->rptlines[L_DELETED];
378 break;
379 case LOG_LINE_DELETE:
380 didop = 1;
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) -
384 sizeof(recno_t)))
385 goto err;
386 ++sp->rptlines[L_ADDED];
387 break;
388 case LOG_LINE_RESET_F:
389 break;
390 case LOG_LINE_RESET_B:
391 didop = 1;
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) -
395 sizeof(recno_t)))
396 goto err;
397 ++sp->rptlines[L_CHANGED];
398 break;
399 case LOG_MARK:
400 didop = 1;
401 memmove(&m, p + sizeof(u_char), sizeof(MARK));
402 if (mark_set(sp, ep, m.name, &m, 0))
403 goto err;
404 break;
405 default:
406 abort();
410 err: F_CLR(ep, F_NOLOG);
411 return (1);
415 * Log_setline --
416 * Reset the line to its original appearance.
418 * XXX
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)
426 SCR *sp;
427 EXF *ep;
428 MARK *rp;
430 DBT key, data;
431 MARK m;
432 recno_t lno;
433 u_char *p;
435 if (F_ISSET(ep, F_NOLOG)) {
436 msgq(sp, M_ERR,
437 "Logging not being performed, undo not possible.");
438 return (1);
441 if (ep->l_cur == 1)
442 return (1);
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.
455 rp->lno = sp->lno;
456 rp->cno = 0;
458 for (;;) {
459 --ep->l_cur;
460 if (ep->log->get(ep->log, &key, &data, 0))
461 LOG_ERR;
462 #if defined(DEBUG) && 0
463 log_trace(sp, "log_setline", ep->l_cur, data.data);
464 #endif
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) {
469 F_CLR(ep, F_NOLOG);
470 return (0);
472 break;
473 case LOG_CURSOR_END:
474 memmove(&m, p + sizeof(u_char), sizeof(MARK));
475 if (m.lno != sp->lno) {
476 ++ep->l_cur;
477 F_CLR(ep, F_NOLOG);
478 return (0);
480 break;
481 case LOG_LINE_APPEND:
482 case LOG_LINE_INSERT:
483 case LOG_LINE_DELETE:
484 case LOG_LINE_RESET_F:
485 break;
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) -
491 sizeof(recno_t)))
492 goto err;
493 ++sp->rptlines[L_CHANGED];
494 case LOG_MARK:
495 memmove(&m, p + sizeof(u_char), sizeof(MARK));
496 if (mark_set(sp, ep, m.name, &m, 0))
497 goto err;
498 break;
499 default:
500 abort();
504 err: F_CLR(ep, F_NOLOG);
505 return (1);
509 * Log_forward --
510 * Roll the log forward one operation.
513 log_forward(sp, ep, rp)
514 SCR *sp;
515 EXF *ep;
516 MARK *rp;
518 DBT key, data;
519 MARK m;
520 recno_t lno;
521 int didop;
522 u_char *p;
524 if (F_ISSET(ep, F_NOLOG)) {
525 msgq(sp, M_ERR,
526 "Logging not being performed, roll-forward not possible.");
527 return (1);
530 if (ep->l_cur == ep->l_high) {
531 msgq(sp, M_BERR, "No changes to re-do.");
532 return (1);
535 F_SET(ep, F_NOLOG); /* Turn off logging. */
537 key.data = &ep->l_cur; /* Initialize db request. */
538 key.size = sizeof(recno_t);
539 for (didop = 0;;) {
540 ++ep->l_cur;
541 if (ep->log->get(ep->log, &key, &data, 0))
542 LOG_ERR;
543 #if defined(DEBUG) && 0
544 log_trace(sp, "log_forward", ep->l_cur, data.data);
545 #endif
546 switch (*(p = (u_char *)data.data)) {
547 case LOG_CURSOR_END:
548 if (didop) {
549 ++ep->l_cur;
550 memmove(rp, p + sizeof(u_char), sizeof(MARK));
551 F_CLR(ep, F_NOLOG);
552 return (0);
554 break;
555 case LOG_CURSOR_INIT:
556 break;
557 case LOG_LINE_APPEND:
558 case LOG_LINE_INSERT:
559 didop = 1;
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) -
563 sizeof(recno_t)))
564 goto err;
565 ++sp->rptlines[L_ADDED];
566 break;
567 case LOG_LINE_DELETE:
568 didop = 1;
569 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
570 if (file_dline(sp, ep, lno))
571 goto err;
572 ++sp->rptlines[L_DELETED];
573 break;
574 case LOG_LINE_RESET_B:
575 break;
576 case LOG_LINE_RESET_F:
577 didop = 1;
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) -
581 sizeof(recno_t)))
582 goto err;
583 ++sp->rptlines[L_CHANGED];
584 break;
585 case LOG_MARK:
586 didop = 1;
587 memmove(&m, p + sizeof(u_char), sizeof(MARK));
588 if (mark_set(sp, ep, m.name, &m, 0))
589 goto err;
590 break;
591 default:
592 abort();
596 err: F_CLR(ep, F_NOLOG);
597 return (1);
600 #if defined(DEBUG) && 0
601 static void
602 log_trace(sp, msg, rno, p)
603 SCR *sp;
604 char *msg;
605 recno_t rno;
606 u_char *p;
608 MARK m;
609 recno_t lno;
611 switch (*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);
615 break;
616 case LOG_CURSOR_END:
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);
619 break;
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);
623 break;
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);
627 break;
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);
631 break;
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);
635 break;
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);
639 break;
640 case LOG_MARK:
641 memmove(&m, p + sizeof(u_char), sizeof(MARK));
642 TRACE(sp, "%lu: %s: MARK: %u/%u\n", rno, msg, m.lno, m.cno);
643 break;
644 default:
645 abort();
648 #endif