source: trunk/third/nvi/common/log.c @ 14302

Revision 14302, 16.0 KB checked in by ghudson, 25 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r14301, which included commits to RCS files with non-trunk default branches.
Line 
1/*-
2 * Copyright (c) 1992, 1993, 1994
3 *      The Regents of the University of California.  All rights reserved.
4 * Copyright (c) 1992, 1993, 1994, 1995, 1996
5 *      Keith Bostic.  All rights reserved.
6 *
7 * See the LICENSE file for redistribution information.
8 */
9
10#include "config.h"
11
12#ifndef lint
13static const char sccsid[] = "@(#)log.c 10.8 (Berkeley) 3/6/96";
14#endif /* not lint */
15
16#include <sys/types.h>
17#include <sys/queue.h>
18#include <sys/stat.h>
19
20#include <bitstring.h>
21#include <errno.h>
22#include <fcntl.h>
23#include <limits.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27
28#include "common.h"
29
30/*
31 * The log consists of records, each containing a type byte and a variable
32 * length byte string, as follows:
33 *
34 *      LOG_CURSOR_INIT         MARK
35 *      LOG_CURSOR_END          MARK
36 *      LOG_LINE_APPEND         recno_t         char *
37 *      LOG_LINE_DELETE         recno_t         char *
38 *      LOG_LINE_INSERT         recno_t         char *
39 *      LOG_LINE_RESET_F        recno_t         char *
40 *      LOG_LINE_RESET_B        recno_t         char *
41 *      LOG_MARK                LMARK
42 *
43 * We do before image physical logging.  This means that the editor layer
44 * MAY NOT modify records in place, even if simply deleting or overwriting
45 * characters.  Since the smallest unit of logging is a line, we're using
46 * up lots of space.  This may eventually have to be reduced, probably by
47 * doing logical logging, which is a much cooler database phrase.
48 *
49 * The implementation of the historic vi 'u' command, using roll-forward and
50 * roll-back, is simple.  Each set of changes has a LOG_CURSOR_INIT record,
51 * followed by a number of other records, followed by a LOG_CURSOR_END record.
52 * LOG_LINE_RESET records come in pairs.  The first is a LOG_LINE_RESET_B
53 * record, and is the line before the change.  The second is LOG_LINE_RESET_F,
54 * and is the line after the change.  Roll-back is done by backing up to the
55 * first LOG_CURSOR_INIT record before a change.  Roll-forward is done in a
56 * similar fashion.
57 *
58 * The 'U' command is implemented by rolling backward to a LOG_CURSOR_END
59 * record for a line different from the current one.  It should be noted that
60 * this means that a subsequent 'u' command will make a change based on the
61 * new position of the log's cursor.  This is okay, and, in fact, historic vi
62 * behaved that way.
63 */
64
65static int      log_cursor1 __P((SCR *, int));
66static void     log_err __P((SCR *, char *, int));
67#if defined(DEBUG) && 0
68static void     log_trace __P((SCR *, char *, recno_t, u_char *));
69#endif
70
71/* Try and restart the log on failure, i.e. if we run out of memory. */
72#define LOG_ERR {                                                       \
73        log_err(sp, __FILE__, __LINE__);                                \
74        return (1);                                                     \
75}
76
77/*
78 * log_init --
79 *      Initialize the logging subsystem.
80 *
81 * PUBLIC: int log_init __P((SCR *, EXF *));
82 */
83int
84log_init(sp, ep)
85        SCR *sp;
86        EXF *ep;
87{
88        /*
89         * !!!
90         * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
91         *
92         * Initialize the buffer.  The logging subsystem has its own
93         * buffers because the global ones are almost by definition
94         * going to be in use when the log runs.
95         */
96        ep->l_lp = NULL;
97        ep->l_len = 0;
98        ep->l_cursor.lno = 1;           /* XXX Any valid recno. */
99        ep->l_cursor.cno = 0;
100        ep->l_high = ep->l_cur = 1;
101
102        ep->log = dbopen(NULL, O_CREAT | O_NONBLOCK | O_RDWR,
103            S_IRUSR | S_IWUSR, DB_RECNO, NULL);
104        if (ep->log == NULL) {
105                msgq(sp, M_SYSERR, "009|Log file");
106                F_SET(ep, F_NOLOG);
107                return (1);
108        }
109
110        return (0);
111}
112
113/*
114 * log_end --
115 *      Close the logging subsystem.
116 *
117 * PUBLIC: int log_end __P((SCR *, EXF *));
118 */
119int
120log_end(sp, ep)
121        SCR *sp;
122        EXF *ep;
123{
124        /*
125         * !!!
126         * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
127         */
128        if (ep->log != NULL) {
129                (void)(ep->log->close)(ep->log);
130                ep->log = NULL;
131        }
132        if (ep->l_lp != NULL) {
133                free(ep->l_lp);
134                ep->l_lp = NULL;
135        }
136        ep->l_len = 0;
137        ep->l_cursor.lno = 1;           /* XXX Any valid recno. */
138        ep->l_cursor.cno = 0;
139        ep->l_high = ep->l_cur = 1;
140        return (0);
141}
142
143/*
144 * log_cursor --
145 *      Log the current cursor position, starting an event.
146 *
147 * PUBLIC: int log_cursor __P((SCR *));
148 */
149int
150log_cursor(sp)
151        SCR *sp;
152{
153        EXF *ep;
154
155        ep = sp->ep;
156        if (F_ISSET(ep, F_NOLOG))
157                return (0);
158
159        /*
160         * If any changes were made since the last cursor init,
161         * put out the ending cursor record.
162         */
163        if (ep->l_cursor.lno == OOBLNO) {
164                ep->l_cursor.lno = sp->lno;
165                ep->l_cursor.cno = sp->cno;
166                return (log_cursor1(sp, LOG_CURSOR_END));
167        }
168        ep->l_cursor.lno = sp->lno;
169        ep->l_cursor.cno = sp->cno;
170        return (0);
171}
172
173/*
174 * log_cursor1 --
175 *      Actually push a cursor record out.
176 */
177static int
178log_cursor1(sp, type)
179        SCR *sp;
180        int type;
181{
182        DBT data, key;
183        EXF *ep;
184
185        ep = sp->ep;
186        BINC_RET(sp, ep->l_lp, ep->l_len, sizeof(u_char) + sizeof(MARK));
187        ep->l_lp[0] = type;
188        memmove(ep->l_lp + sizeof(u_char), &ep->l_cursor, sizeof(MARK));
189
190        key.data = &ep->l_cur;
191        key.size = sizeof(recno_t);
192        data.data = ep->l_lp;
193        data.size = sizeof(u_char) + sizeof(MARK);
194        if (ep->log->put(ep->log, &key, &data, 0) == -1)
195                LOG_ERR;
196
197#if defined(DEBUG) && 0
198        TRACE(sp, "%lu: %s: %u/%u\n", ep->l_cur,
199            type == LOG_CURSOR_INIT ? "log_cursor_init" : "log_cursor_end",
200            sp->lno, sp->cno);
201#endif
202        /* Reset high water mark. */
203        ep->l_high = ++ep->l_cur;
204
205        return (0);
206}
207
208/*
209 * log_line --
210 *      Log a line change.
211 *
212 * PUBLIC: int log_line __P((SCR *, recno_t, u_int));
213 */
214int
215log_line(sp, lno, action)
216        SCR *sp;
217        recno_t lno;
218        u_int action;
219{
220        DBT data, key;
221        EXF *ep;
222        size_t len;
223        char *lp;
224
225        ep = sp->ep;
226        if (F_ISSET(ep, F_NOLOG))
227                return (0);
228
229        /*
230         * XXX
231         *
232         * Kluge for vi.  Clear the EXF undo flag so that the
233         * next 'u' command does a roll-back, regardless.
234         */
235        F_CLR(ep, F_UNDO);
236
237        /* Put out one initial cursor record per set of changes. */
238        if (ep->l_cursor.lno != OOBLNO) {
239                if (log_cursor1(sp, LOG_CURSOR_INIT))
240                        return (1);
241                ep->l_cursor.lno = OOBLNO;
242        }
243
244        /*
245         * Put out the changes.  If it's a LOG_LINE_RESET_B call, it's a
246         * special case, avoid the caches.  Also, if it fails and it's
247         * line 1, it just means that the user started with an empty file,
248         * so fake an empty length line.
249         */
250        if (action == LOG_LINE_RESET_B) {
251                if (db_get(sp, lno, DBG_NOCACHE, &lp, &len)) {
252                        if (lno != 1) {
253                                db_err(sp, lno);
254                                return (1);
255                        }
256                        len = 0;
257                        lp = "";
258                }
259        } else
260                if (db_get(sp, lno, DBG_FATAL, &lp, &len))
261                        return (1);
262        BINC_RET(sp,
263            ep->l_lp, ep->l_len, len + sizeof(u_char) + sizeof(recno_t));
264        ep->l_lp[0] = action;
265        memmove(ep->l_lp + sizeof(u_char), &lno, sizeof(recno_t));
266        memmove(ep->l_lp + sizeof(u_char) + sizeof(recno_t), lp, len);
267
268        key.data = &ep->l_cur;
269        key.size = sizeof(recno_t);
270        data.data = ep->l_lp;
271        data.size = len + sizeof(u_char) + sizeof(recno_t);
272        if (ep->log->put(ep->log, &key, &data, 0) == -1)
273                LOG_ERR;
274
275#if defined(DEBUG) && 0
276        switch (action) {
277        case LOG_LINE_APPEND:
278                TRACE(sp, "%u: log_line: append: %lu {%u}\n",
279                    ep->l_cur, lno, len);
280                break;
281        case LOG_LINE_DELETE:
282                TRACE(sp, "%lu: log_line: delete: %lu {%u}\n",
283                    ep->l_cur, lno, len);
284                break;
285        case LOG_LINE_INSERT:
286                TRACE(sp, "%lu: log_line: insert: %lu {%u}\n",
287                    ep->l_cur, lno, len);
288                break;
289        case LOG_LINE_RESET_F:
290                TRACE(sp, "%lu: log_line: reset_f: %lu {%u}\n",
291                    ep->l_cur, lno, len);
292                break;
293        case LOG_LINE_RESET_B:
294                TRACE(sp, "%lu: log_line: reset_b: %lu {%u}\n",
295                    ep->l_cur, lno, len);
296                break;
297        }
298#endif
299        /* Reset high water mark. */
300        ep->l_high = ++ep->l_cur;
301
302        return (0);
303}
304
305/*
306 * log_mark --
307 *      Log a mark position.  For the log to work, we assume that there
308 *      aren't any operations that just put out a log record -- this
309 *      would mean that undo operations would only reset marks, and not
310 *      cause any other change.
311 *
312 * PUBLIC: int log_mark __P((SCR *, LMARK *));
313 */
314int
315log_mark(sp, lmp)
316        SCR *sp;
317        LMARK *lmp;
318{
319        DBT data, key;
320        EXF *ep;
321
322        ep = sp->ep;
323        if (F_ISSET(ep, F_NOLOG))
324                return (0);
325
326        /* Put out one initial cursor record per set of changes. */
327        if (ep->l_cursor.lno != OOBLNO) {
328                if (log_cursor1(sp, LOG_CURSOR_INIT))
329                        return (1);
330                ep->l_cursor.lno = OOBLNO;
331        }
332
333        BINC_RET(sp, ep->l_lp,
334            ep->l_len, sizeof(u_char) + sizeof(LMARK));
335        ep->l_lp[0] = LOG_MARK;
336        memmove(ep->l_lp + sizeof(u_char), lmp, sizeof(LMARK));
337
338        key.data = &ep->l_cur;
339        key.size = sizeof(recno_t);
340        data.data = ep->l_lp;
341        data.size = sizeof(u_char) + sizeof(LMARK);
342        if (ep->log->put(ep->log, &key, &data, 0) == -1)
343                LOG_ERR;
344
345#if defined(DEBUG) && 0
346        TRACE(sp, "%lu: mark %c: %lu/%u\n",
347            ep->l_cur, lmp->name, lmp->lno, lmp->cno);
348#endif
349        /* Reset high water mark. */
350        ep->l_high = ++ep->l_cur;
351        return (0);
352}
353
354/*
355 * Log_backward --
356 *      Roll the log backward one operation.
357 *
358 * PUBLIC: int log_backward __P((SCR *, MARK *));
359 */
360int
361log_backward(sp, rp)
362        SCR *sp;
363        MARK *rp;
364{
365        DBT key, data;
366        EXF *ep;
367        LMARK lm;
368        MARK m;
369        recno_t lno;
370        int didop;
371        u_char *p;
372
373        ep = sp->ep;
374        if (F_ISSET(ep, F_NOLOG)) {
375                msgq(sp, M_ERR,
376                    "010|Logging not being performed, undo not possible");
377                return (1);
378        }
379
380        if (ep->l_cur == 1) {
381                msgq(sp, M_BERR, "011|No changes to undo");
382                return (1);
383        }
384
385        F_SET(ep, F_NOLOG);             /* Turn off logging. */
386
387        key.data = &ep->l_cur;          /* Initialize db request. */
388        key.size = sizeof(recno_t);
389        for (didop = 0;;) {
390                --ep->l_cur;
391                if (ep->log->get(ep->log, &key, &data, 0))
392                        LOG_ERR;
393#if defined(DEBUG) && 0
394                log_trace(sp, "log_backward", ep->l_cur, data.data);
395#endif
396                switch (*(p = (u_char *)data.data)) {
397                case LOG_CURSOR_INIT:
398                        if (didop) {
399                                memmove(rp, p + sizeof(u_char), sizeof(MARK));
400                                F_CLR(ep, F_NOLOG);
401                                return (0);
402                        }
403                        break;
404                case LOG_CURSOR_END:
405                        break;
406                case LOG_LINE_APPEND:
407                case LOG_LINE_INSERT:
408                        didop = 1;
409                        memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
410                        if (db_delete(sp, lno))
411                                goto err;
412                        ++sp->rptlines[L_DELETED];
413                        break;
414                case LOG_LINE_DELETE:
415                        didop = 1;
416                        memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
417                        if (db_insert(sp, lno, p + sizeof(u_char) +
418                            sizeof(recno_t), data.size - sizeof(u_char) -
419                            sizeof(recno_t)))
420                                goto err;
421                        ++sp->rptlines[L_ADDED];
422                        break;
423                case LOG_LINE_RESET_F:
424                        break;
425                case LOG_LINE_RESET_B:
426                        didop = 1;
427                        memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
428                        if (db_set(sp, lno, p + sizeof(u_char) +
429                            sizeof(recno_t), data.size - sizeof(u_char) -
430                            sizeof(recno_t)))
431                                goto err;
432                        if (sp->rptlchange != lno) {
433                                sp->rptlchange = lno;
434                                ++sp->rptlines[L_CHANGED];
435                        }
436                        break;
437                case LOG_MARK:
438                        didop = 1;
439                        memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
440                        m.lno = lm.lno;
441                        m.cno = lm.cno;
442                        if (mark_set(sp, lm.name, &m, 0))
443                                goto err;
444                        break;
445                default:
446                        abort();
447                }
448        }
449
450err:    F_CLR(ep, F_NOLOG);
451        return (1);
452}
453
454/*
455 * Log_setline --
456 *      Reset the line to its original appearance.
457 *
458 * XXX
459 * There's a bug in this code due to our not logging cursor movements
460 * unless a change was made.  If you do a change, move off the line,
461 * then move back on and do a 'U', the line will be restored to the way
462 * it was before the original change.
463 *
464 * PUBLIC: int log_setline __P((SCR *));
465 */
466int
467log_setline(sp)
468        SCR *sp;
469{
470        DBT key, data;
471        EXF *ep;
472        LMARK lm;
473        MARK m;
474        recno_t lno;
475        u_char *p;
476
477        ep = sp->ep;
478        if (F_ISSET(ep, F_NOLOG)) {
479                msgq(sp, M_ERR,
480                    "012|Logging not being performed, undo not possible");
481                return (1);
482        }
483
484        if (ep->l_cur == 1)
485                return (1);
486
487        F_SET(ep, F_NOLOG);             /* Turn off logging. */
488
489        key.data = &ep->l_cur;          /* Initialize db request. */
490        key.size = sizeof(recno_t);
491
492        for (;;) {
493                --ep->l_cur;
494                if (ep->log->get(ep->log, &key, &data, 0))
495                        LOG_ERR;
496#if defined(DEBUG) && 0
497                log_trace(sp, "log_setline", ep->l_cur, data.data);
498#endif
499                switch (*(p = (u_char *)data.data)) {
500                case LOG_CURSOR_INIT:
501                        memmove(&m, p + sizeof(u_char), sizeof(MARK));
502                        if (m.lno != sp->lno || ep->l_cur == 1) {
503                                F_CLR(ep, F_NOLOG);
504                                return (0);
505                        }
506                        break;
507                case LOG_CURSOR_END:
508                        memmove(&m, p + sizeof(u_char), sizeof(MARK));
509                        if (m.lno != sp->lno) {
510                                ++ep->l_cur;
511                                F_CLR(ep, F_NOLOG);
512                                return (0);
513                        }
514                        break;
515                case LOG_LINE_APPEND:
516                case LOG_LINE_INSERT:
517                case LOG_LINE_DELETE:
518                case LOG_LINE_RESET_F:
519                        break;
520                case LOG_LINE_RESET_B:
521                        memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
522                        if (lno == sp->lno &&
523                            db_set(sp, lno, p + sizeof(u_char) +
524                            sizeof(recno_t), data.size - sizeof(u_char) -
525                            sizeof(recno_t)))
526                                goto err;
527                        if (sp->rptlchange != lno) {
528                                sp->rptlchange = lno;
529                                ++sp->rptlines[L_CHANGED];
530                        }
531                case LOG_MARK:
532                        memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
533                        m.lno = lm.lno;
534                        m.cno = lm.cno;
535                        if (mark_set(sp, lm.name, &m, 0))
536                                goto err;
537                        break;
538                default:
539                        abort();
540                }
541        }
542
543err:    F_CLR(ep, F_NOLOG);
544        return (1);
545}
546
547/*
548 * Log_forward --
549 *      Roll the log forward one operation.
550 *
551 * PUBLIC: int log_forward __P((SCR *, MARK *));
552 */
553int
554log_forward(sp, rp)
555        SCR *sp;
556        MARK *rp;
557{
558        DBT key, data;
559        EXF *ep;
560        LMARK lm;
561        MARK m;
562        recno_t lno;
563        int didop;
564        u_char *p;
565
566        ep = sp->ep;
567        if (F_ISSET(ep, F_NOLOG)) {
568                msgq(sp, M_ERR,
569            "013|Logging not being performed, roll-forward not possible");
570                return (1);
571        }
572
573        if (ep->l_cur == ep->l_high) {
574                msgq(sp, M_BERR, "014|No changes to re-do");
575                return (1);
576        }
577
578        F_SET(ep, F_NOLOG);             /* Turn off logging. */
579
580        key.data = &ep->l_cur;          /* Initialize db request. */
581        key.size = sizeof(recno_t);
582        for (didop = 0;;) {
583                ++ep->l_cur;
584                if (ep->log->get(ep->log, &key, &data, 0))
585                        LOG_ERR;
586#if defined(DEBUG) && 0
587                log_trace(sp, "log_forward", ep->l_cur, data.data);
588#endif
589                switch (*(p = (u_char *)data.data)) {
590                case LOG_CURSOR_END:
591                        if (didop) {
592                                ++ep->l_cur;
593                                memmove(rp, p + sizeof(u_char), sizeof(MARK));
594                                F_CLR(ep, F_NOLOG);
595                                return (0);
596                        }
597                        break;
598                case LOG_CURSOR_INIT:
599                        break;
600                case LOG_LINE_APPEND:
601                case LOG_LINE_INSERT:
602                        didop = 1;
603                        memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
604                        if (db_insert(sp, lno, p + sizeof(u_char) +
605                            sizeof(recno_t), data.size - sizeof(u_char) -
606                            sizeof(recno_t)))
607                                goto err;
608                        ++sp->rptlines[L_ADDED];
609                        break;
610                case LOG_LINE_DELETE:
611                        didop = 1;
612                        memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
613                        if (db_delete(sp, lno))
614                                goto err;
615                        ++sp->rptlines[L_DELETED];
616                        break;
617                case LOG_LINE_RESET_B:
618                        break;
619                case LOG_LINE_RESET_F:
620                        didop = 1;
621                        memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
622                        if (db_set(sp, lno, p + sizeof(u_char) +
623                            sizeof(recno_t), data.size - sizeof(u_char) -
624                            sizeof(recno_t)))
625                                goto err;
626                        if (sp->rptlchange != lno) {
627                                sp->rptlchange = lno;
628                                ++sp->rptlines[L_CHANGED];
629                        }
630                        break;
631                case LOG_MARK:
632                        didop = 1;
633                        memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
634                        m.lno = lm.lno;
635                        m.cno = lm.cno;
636                        if (mark_set(sp, lm.name, &m, 0))
637                                goto err;
638                        break;
639                default:
640                        abort();
641                }
642        }
643
644err:    F_CLR(ep, F_NOLOG);
645        return (1);
646}
647
648/*
649 * log_err --
650 *      Try and restart the log on failure, i.e. if we run out of memory.
651 */
652static void
653log_err(sp, file, line)
654        SCR *sp;
655        char *file;
656        int line;
657{
658        EXF *ep;
659
660        msgq(sp, M_SYSERR, "015|%s/%d: log put error", tail(file), line);
661        ep = sp->ep;
662        (void)ep->log->close(ep->log);
663        if (!log_init(sp, ep))
664                msgq(sp, M_ERR, "267|Log restarted");
665}
666
667#if defined(DEBUG) && 0
668static void
669log_trace(sp, msg, rno, p)
670        SCR *sp;
671        char *msg;
672        recno_t rno;
673        u_char *p;
674{
675        LMARK lm;
676        MARK m;
677        recno_t lno;
678
679        switch (*p) {
680        case LOG_CURSOR_INIT:
681                memmove(&m, p + sizeof(u_char), sizeof(MARK));
682                TRACE(sp, "%lu: %s:  C_INIT: %u/%u\n", rno, msg, m.lno, m.cno);
683                break;
684        case LOG_CURSOR_END:
685                memmove(&m, p + sizeof(u_char), sizeof(MARK));
686                TRACE(sp, "%lu: %s:   C_END: %u/%u\n", rno, msg, m.lno, m.cno);
687                break;
688        case LOG_LINE_APPEND:
689                memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
690                TRACE(sp, "%lu: %s:  APPEND: %lu\n", rno, msg, lno);
691                break;
692        case LOG_LINE_INSERT:
693                memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
694                TRACE(sp, "%lu: %s:  INSERT: %lu\n", rno, msg, lno);
695                break;
696        case LOG_LINE_DELETE:
697                memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
698                TRACE(sp, "%lu: %s:  DELETE: %lu\n", rno, msg, lno);
699                break;
700        case LOG_LINE_RESET_F:
701                memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
702                TRACE(sp, "%lu: %s: RESET_F: %lu\n", rno, msg, lno);
703                break;
704        case LOG_LINE_RESET_B:
705                memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
706                TRACE(sp, "%lu: %s: RESET_B: %lu\n", rno, msg, lno);
707                break;
708        case LOG_MARK:
709                memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
710                TRACE(sp,
711                    "%lu: %s:    MARK: %u/%u\n", rno, msg, lm.lno, lm.cno);
712                break;
713        default:
714                abort();
715        }
716}
717#endif
Note: See TracBrowser for help on using the repository browser.