split is "sp", not "s"
[nvi.git] / common / log.c
blob8b4fdcc2575e9ea6b602ebca26aa69c9ff1b1f06
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.9 1993/12/28 16:39:21 bostic Exp $ (Berkeley) $Date: 1993/12/28 16:39:21 $";
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, each containing a type byte and a variable
24 * 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 * MAY NOT 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 the historic vi 'u' command, using roll-forward and
42 * roll-back, is simple. Each set of changes has a LOG_CURSOR_INIT record,
43 * followed by a number of other records, followed by a LOG_CURSOR_END record.
44 * LOG_LINE_RESET records come in pairs. The first is a LOG_LINE_RESET_B
45 * record, and is the line before the change. The second is LOG_LINE_RESET_F,
46 * and is the line after the change. Roll-back is done by backing up to the
47 * first LOG_CURSOR_INIT record before a change. Roll-forward is done in a
48 * similar fashion.
50 * The 'U' command is implemented by rolling backward to a LOG_CURSOR_END
51 * record for a line different from the current one. It should be noted that
52 * this means that a subsequent 'u' command will make a change based on the
53 * new position of the log's cursor. This is okay, and, in fact, historic vi
54 * behaved that way.
57 static int log_cursor1 __P((SCR *, EXF *, int));
58 #if defined(DEBUG) && 0
59 static void log_trace __P((SCR *, char *, recno_t, u_char *));
60 #endif
62 /* Try and restart the log on failure, i.e. if we run out of memory. */
63 #define LOG_ERR { \
64 msgq(sp, M_ERR, "Error: %s/%d: put log error: %s.", \
65 tail(__FILE__), __LINE__, strerror(errno)); \
66 (void)ep->log->close(ep->log); \
67 if (!log_init(sp, ep)) \
68 msgq(sp, M_ERR, "Log restarted."); \
69 return (1); \
73 * log_init --
74 * Initialize the logging subsystem.
76 int
77 log_init(sp, ep)
78 SCR *sp;
79 EXF *ep;
82 * Initialize the buffer. The logging subsystem has its own
83 * buffers because the global ones are almost by definition
84 * going to be in use when the log runs.
86 ep->l_lp = NULL;
87 ep->l_len = 0;
88 ep->l_cursor.lno = 1; /* XXX Any valid recno. */
89 ep->l_cursor.cno = 0;
90 ep->l_high = ep->l_cur = 1;
92 ep->log = dbopen(NULL, O_CREAT | O_NONBLOCK | O_RDWR,
93 S_IRUSR | S_IWUSR, DB_RECNO, NULL);
94 if (ep->log == NULL) {
95 msgq(sp, M_ERR, "log db: %s", strerror(errno));
96 F_SET(ep, F_NOLOG);
97 return (1);
100 return (0);
104 * log_end --
105 * Close the logging subsystem.
108 log_end(sp, ep)
109 SCR *sp;
110 EXF *ep;
112 if (ep->log != NULL) {
113 (void)(ep->log->close)(ep->log);
114 ep->log = NULL;
116 if (ep->l_lp != NULL) {
117 free(ep->l_lp);
118 ep->l_lp = NULL;
120 ep->l_len = 0;
121 ep->l_cursor.lno = 1; /* XXX Any valid recno. */
122 ep->l_cursor.cno = 0;
123 ep->l_high = ep->l_cur = 1;
124 return (0);
128 * log_cursor --
129 * Log the current cursor position, starting an event.
132 log_cursor(sp, ep)
133 SCR *sp;
134 EXF *ep;
137 * If any changes were made since the last cursor init,
138 * put out the ending cursor record.
140 if (ep->l_cursor.lno == OOBLNO) {
141 ep->l_cursor.lno = sp->lno;
142 ep->l_cursor.cno = sp->cno;
143 return (log_cursor1(sp, ep, LOG_CURSOR_END));
145 ep->l_cursor.lno = sp->lno;
146 ep->l_cursor.cno = sp->cno;
147 return (0);
151 * log_cursor1 --
152 * Actually push a cursor record out.
154 static int
155 log_cursor1(sp, ep, type)
156 SCR *sp;
157 EXF *ep;
158 int type;
160 DBT data, key;
162 BINC_RET(sp, ep->l_lp, ep->l_len, sizeof(u_char) + sizeof(MARK));
163 ep->l_lp[0] = type;
164 memmove(ep->l_lp + sizeof(u_char), &ep->l_cursor, sizeof(MARK));
166 key.data = &ep->l_cur;
167 key.size = sizeof(recno_t);
168 data.data = ep->l_lp;
169 data.size = sizeof(u_char) + sizeof(MARK);
170 if (ep->log->put(ep->log, &key, &data, 0) == -1)
171 LOG_ERR;
173 #if defined(DEBUG) && 0
174 TRACE(sp, "%lu: %s: %u/%u\n", ep->l_cur,
175 type == LOG_CURSOR_INIT ? "log_cursor_init" : "log_cursor_end",
176 sp->lno, sp->cno);
177 #endif
178 /* Reset high water mark. */
179 ep->l_high = ++ep->l_cur;
181 return (0);
185 * log_line --
186 * Log a line change.
189 log_line(sp, ep, lno, action)
190 SCR *sp;
191 EXF *ep;
192 recno_t lno;
193 u_int action;
195 DBT data, key;
196 size_t len;
197 char *lp;
199 if (F_ISSET(ep, F_NOLOG))
200 return (0);
203 * XXX
205 * Kluge for vi. Clear the EXF undo flag so that the
206 * next 'u' command does a roll-back, regardless.
208 F_CLR(ep, F_UNDO);
210 /* Put out one initial cursor record per set of changes. */
211 if (ep->l_cursor.lno != OOBLNO) {
212 if (log_cursor1(sp, ep, LOG_CURSOR_INIT))
213 return (1);
214 ep->l_cursor.lno = OOBLNO;
218 * Put out the changes. If it's a LOG_LINE_RESET_B call, it's a
219 * special case, avoid the caches. Also, if it fails and it's
220 * line 1, it just means that the user started with an empty file,
221 * so fake an empty length line.
223 if (action == LOG_LINE_RESET_B) {
224 if ((lp = file_rline(sp, ep, lno, &len)) == NULL) {
225 if (lno != 1) {
226 GETLINE_ERR(sp, lno);
227 return (1);
229 len = 0;
230 lp = "";
232 } else
233 if ((lp = file_gline(sp, ep, lno, &len)) == NULL) {
234 GETLINE_ERR(sp, lno);
235 return (1);
237 BINC_RET(sp,
238 ep->l_lp, ep->l_len, len + sizeof(u_char) + sizeof(recno_t));
239 ep->l_lp[0] = action;
240 memmove(ep->l_lp + sizeof(u_char), &lno, sizeof(recno_t));
241 memmove(ep->l_lp + sizeof(u_char) + sizeof(recno_t), lp, len);
243 key.data = &ep->l_cur;
244 key.size = sizeof(recno_t);
245 data.data = ep->l_lp;
246 data.size = len + sizeof(u_char) + sizeof(recno_t);
247 if (ep->log->put(ep->log, &key, &data, 0) == -1)
248 LOG_ERR;
250 #if defined(DEBUG) && 0
251 switch (action) {
252 case LOG_LINE_APPEND:
253 TRACE(sp, "%u: log_line: append: %lu {%u}\n",
254 ep->l_cur, lno, len);
255 break;
256 case LOG_LINE_DELETE:
257 TRACE(sp, "%lu: log_line: delete: %lu {%u}\n",
258 ep->l_cur, lno, len);
259 break;
260 case LOG_LINE_INSERT:
261 TRACE(sp, "%lu: log_line: insert: %lu {%u}\n",
262 ep->l_cur, lno, len);
263 break;
264 case LOG_LINE_RESET_F:
265 TRACE(sp, "%lu: log_line: reset_f: %lu {%u}\n",
266 ep->l_cur, lno, len);
267 break;
268 case LOG_LINE_RESET_B:
269 TRACE(sp, "%lu: log_line: reset_b: %lu {%u}\n",
270 ep->l_cur, lno, len);
271 break;
273 #endif
274 /* Reset high water mark. */
275 ep->l_high = ++ep->l_cur;
277 return (0);
281 * log_mark --
282 * Log a mark position. For the log to work, we assume that there
283 * aren't any operations that just put out a log record -- this
284 * would mean that undo operations would only reset marks, and not
285 * cause any other change.
288 log_mark(sp, ep, mp)
289 SCR *sp;
290 EXF *ep;
291 MARK *mp;
293 DBT data, key;
295 if (F_ISSET(ep, F_NOLOG))
296 return (0);
298 /* Put out one initial cursor record per set of changes. */
299 if (ep->l_cursor.lno != OOBLNO) {
300 if (log_cursor1(sp, ep, LOG_CURSOR_INIT))
301 return (1);
302 ep->l_cursor.lno = OOBLNO;
305 BINC_RET(sp, ep->l_lp,
306 ep->l_len, sizeof(u_char) + sizeof(MARK));
307 ep->l_lp[0] = LOG_MARK;
308 memmove(ep->l_lp + sizeof(u_char), mp, sizeof(MARK));
310 key.data = &ep->l_cur;
311 key.size = sizeof(recno_t);
312 data.data = ep->l_lp;
313 data.size = sizeof(u_char) + sizeof(MARK);
314 if (ep->log->put(ep->log, &key, &data, 0) == -1)
315 LOG_ERR;
317 /* Reset high water mark. */
318 ep->l_high = ++ep->l_cur;
319 return (0);
323 * Log_backward --
324 * Roll the log backward one operation.
327 log_backward(sp, ep, rp)
328 SCR *sp;
329 EXF *ep;
330 MARK *rp;
332 DBT key, data;
333 MARK m;
334 recno_t lno;
335 int didop;
336 u_char *p;
338 if (F_ISSET(ep, F_NOLOG)) {
339 msgq(sp, M_ERR,
340 "Logging not being performed, undo not possible.");
341 return (1);
344 if (ep->l_cur == 1) {
345 msgq(sp, M_BERR, "No changes to undo.");
346 return (1);
349 F_SET(ep, F_NOLOG); /* Turn off logging. */
351 key.data = &ep->l_cur; /* Initialize db request. */
352 key.size = sizeof(recno_t);
353 for (didop = 0;;) {
354 --ep->l_cur;
355 if (ep->log->get(ep->log, &key, &data, 0))
356 LOG_ERR;
357 #if defined(DEBUG) && 0
358 log_trace(sp, "log_backward", ep->l_cur, data.data);
359 #endif
360 switch (*(p = (u_char *)data.data)) {
361 case LOG_CURSOR_INIT:
362 if (didop) {
363 memmove(rp, p + sizeof(u_char), sizeof(MARK));
364 F_CLR(ep, F_NOLOG);
365 return (0);
367 break;
368 case LOG_CURSOR_END:
369 break;
370 case LOG_LINE_APPEND:
371 case LOG_LINE_INSERT:
372 didop = 1;
373 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
374 if (file_dline(sp, ep, lno))
375 goto err;
376 ++sp->rptlines[L_DELETED];
377 break;
378 case LOG_LINE_DELETE:
379 didop = 1;
380 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
381 if (file_iline(sp, ep, lno, p + sizeof(u_char) +
382 sizeof(recno_t), data.size - sizeof(u_char) -
383 sizeof(recno_t)))
384 goto err;
385 ++sp->rptlines[L_ADDED];
386 break;
387 case LOG_LINE_RESET_F:
388 break;
389 case LOG_LINE_RESET_B:
390 didop = 1;
391 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
392 if (file_sline(sp, ep, lno, p + sizeof(u_char) +
393 sizeof(recno_t), data.size - sizeof(u_char) -
394 sizeof(recno_t)))
395 goto err;
396 ++sp->rptlines[L_CHANGED];
397 break;
398 case LOG_MARK:
399 didop = 1;
400 memmove(&m, p + sizeof(u_char), sizeof(MARK));
401 if (mark_set(sp, ep, m.name, &m, 0))
402 goto err;
403 break;
404 default:
405 abort();
409 err: F_CLR(ep, F_NOLOG);
410 return (1);
414 * Log_setline --
415 * Reset the line to its original appearance.
417 * XXX
418 * There's a bug in this code due to our not logging cursor movements
419 * unless a change was made. If you do a change, move off the line,
420 * then move back on and do a 'U', the line will be restored to the way
421 * it was before the original change.
424 log_setline(sp, ep)
425 SCR *sp;
426 EXF *ep;
428 DBT key, data;
429 MARK m;
430 recno_t lno;
431 u_char *p;
433 if (F_ISSET(ep, F_NOLOG)) {
434 msgq(sp, M_ERR,
435 "Logging not being performed, undo not possible.");
436 return (1);
439 if (ep->l_cur == 1)
440 return (1);
442 F_SET(ep, F_NOLOG); /* Turn off logging. */
444 key.data = &ep->l_cur; /* Initialize db request. */
445 key.size = sizeof(recno_t);
447 for (;;) {
448 --ep->l_cur;
449 if (ep->log->get(ep->log, &key, &data, 0))
450 LOG_ERR;
451 #if defined(DEBUG) && 0
452 log_trace(sp, "log_setline", ep->l_cur, data.data);
453 #endif
454 switch (*(p = (u_char *)data.data)) {
455 case LOG_CURSOR_INIT:
456 memmove(&m, p + sizeof(u_char), sizeof(MARK));
457 if (m.lno != sp->lno || ep->l_cur == 1) {
458 F_CLR(ep, F_NOLOG);
459 return (0);
461 break;
462 case LOG_CURSOR_END:
463 memmove(&m, p + sizeof(u_char), sizeof(MARK));
464 if (m.lno != sp->lno) {
465 ++ep->l_cur;
466 F_CLR(ep, F_NOLOG);
467 return (0);
469 break;
470 case LOG_LINE_APPEND:
471 case LOG_LINE_INSERT:
472 case LOG_LINE_DELETE:
473 case LOG_LINE_RESET_F:
474 break;
475 case LOG_LINE_RESET_B:
476 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
477 if (lno == sp->lno &&
478 file_sline(sp, ep, lno, p + sizeof(u_char) +
479 sizeof(recno_t), data.size - sizeof(u_char) -
480 sizeof(recno_t)))
481 goto err;
482 ++sp->rptlines[L_CHANGED];
483 case LOG_MARK:
484 memmove(&m, p + sizeof(u_char), sizeof(MARK));
485 if (mark_set(sp, ep, m.name, &m, 0))
486 goto err;
487 break;
488 default:
489 abort();
493 err: F_CLR(ep, F_NOLOG);
494 return (1);
498 * Log_forward --
499 * Roll the log forward one operation.
502 log_forward(sp, ep, rp)
503 SCR *sp;
504 EXF *ep;
505 MARK *rp;
507 DBT key, data;
508 MARK m;
509 recno_t lno;
510 int didop;
511 u_char *p;
513 if (F_ISSET(ep, F_NOLOG)) {
514 msgq(sp, M_ERR,
515 "Logging not being performed, roll-forward not possible.");
516 return (1);
519 if (ep->l_cur == ep->l_high) {
520 msgq(sp, M_BERR, "No changes to re-do.");
521 return (1);
524 F_SET(ep, F_NOLOG); /* Turn off logging. */
526 key.data = &ep->l_cur; /* Initialize db request. */
527 key.size = sizeof(recno_t);
528 for (didop = 0;;) {
529 ++ep->l_cur;
530 if (ep->log->get(ep->log, &key, &data, 0))
531 LOG_ERR;
532 #if defined(DEBUG) && 0
533 log_trace(sp, "log_forward", ep->l_cur, data.data);
534 #endif
535 switch (*(p = (u_char *)data.data)) {
536 case LOG_CURSOR_END:
537 if (didop) {
538 ++ep->l_cur;
539 memmove(rp, p + sizeof(u_char), sizeof(MARK));
540 F_CLR(ep, F_NOLOG);
541 return (0);
543 break;
544 case LOG_CURSOR_INIT:
545 break;
546 case LOG_LINE_APPEND:
547 case LOG_LINE_INSERT:
548 didop = 1;
549 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
550 if (file_iline(sp, ep, lno, p + sizeof(u_char) +
551 sizeof(recno_t), data.size - sizeof(u_char) -
552 sizeof(recno_t)))
553 goto err;
554 ++sp->rptlines[L_ADDED];
555 break;
556 case LOG_LINE_DELETE:
557 didop = 1;
558 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
559 if (file_dline(sp, ep, lno))
560 goto err;
561 ++sp->rptlines[L_DELETED];
562 break;
563 case LOG_LINE_RESET_B:
564 break;
565 case LOG_LINE_RESET_F:
566 didop = 1;
567 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
568 if (file_sline(sp, ep, lno, p + sizeof(u_char) +
569 sizeof(recno_t), data.size - sizeof(u_char) -
570 sizeof(recno_t)))
571 goto err;
572 ++sp->rptlines[L_CHANGED];
573 break;
574 case LOG_MARK:
575 didop = 1;
576 memmove(&m, p + sizeof(u_char), sizeof(MARK));
577 if (mark_set(sp, ep, m.name, &m, 0))
578 goto err;
579 break;
580 default:
581 abort();
585 err: F_CLR(ep, F_NOLOG);
586 return (1);
589 #if defined(DEBUG) && 0
590 static void
591 log_trace(sp, msg, rno, p)
592 SCR *sp;
593 char *msg;
594 recno_t rno;
595 u_char *p;
597 MARK m;
598 recno_t lno;
600 switch (*p) {
601 case LOG_CURSOR_INIT:
602 memmove(&m, p + sizeof(u_char), sizeof(MARK));
603 TRACE(sp, "%lu: %s: C_INIT: %u/%u\n", rno, msg, m.lno, m.cno);
604 break;
605 case LOG_CURSOR_END:
606 memmove(&m, p + sizeof(u_char), sizeof(MARK));
607 TRACE(sp, "%lu: %s: C_END: %u/%u\n", rno, msg, m.lno, m.cno);
608 break;
609 case LOG_LINE_APPEND:
610 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
611 TRACE(sp, "%lu: %s: APPEND: %lu\n", rno, msg, lno);
612 break;
613 case LOG_LINE_INSERT:
614 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
615 TRACE(sp, "%lu: %s: INSERT: %lu\n", rno, msg, lno);
616 break;
617 case LOG_LINE_DELETE:
618 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
619 TRACE(sp, "%lu: %s: DELETE: %lu\n", rno, msg, lno);
620 break;
621 case LOG_LINE_RESET_F:
622 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
623 TRACE(sp, "%lu: %s: RESET_F: %lu\n", rno, msg, lno);
624 break;
625 case LOG_LINE_RESET_B:
626 memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
627 TRACE(sp, "%lu: %s: RESET_B: %lu\n", rno, msg, lno);
628 break;
629 case LOG_MARK:
630 memmove(&m, p + sizeof(u_char), sizeof(MARK));
631 TRACE(sp, "%lu: %s: MARK: %u/%u\n", rno, msg, m.lno, m.cno);
632 break;
633 default:
634 abort();
637 #endif