* Copyright (c) 1992, 1993
* The Regents of the University of California. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
static char sccsid
[] = "@(#)log.c 8.9 (Berkeley) 12/28/93";
* The log consists of records, each containing a type byte and a variable
* length byte string, as follows:
* LOG_LINE_APPEND recno_t char *
* LOG_LINE_DELETE recno_t char *
* LOG_LINE_INSERT recno_t char *
* LOG_LINE_RESET_F recno_t char *
* LOG_LINE_RESET_B recno_t char *
* We do before image physical logging. This means that the editor layer
* MAY NOT modify records in place, even if simply deleting or overwriting
* characters. Since the smallest unit of logging is a line, we're using
* up lots of space. This may eventually have to be reduced, probably by
* doing logical logging, which is a much cooler database phrase.
* The implementation of the historic vi 'u' command, using roll-forward and
* roll-back, is simple. Each set of changes has a LOG_CURSOR_INIT record,
* followed by a number of other records, followed by a LOG_CURSOR_END record.
* LOG_LINE_RESET records come in pairs. The first is a LOG_LINE_RESET_B
* record, and is the line before the change. The second is LOG_LINE_RESET_F,
* and is the line after the change. Roll-back is done by backing up to the
* first LOG_CURSOR_INIT record before a change. Roll-forward is done in a
* The 'U' command is implemented by rolling backward to a LOG_CURSOR_END
* record for a line different from the current one. It should be noted that
* this means that a subsequent 'u' command will make a change based on the
* new position of the log's cursor. This is okay, and, in fact, historic vi
static int log_cursor1
__P((SCR
*, EXF
*, int));
static void log_trace
__P((SCR
*, char *, recno_t
, u_char
*));
/* Try and restart the log on failure, i.e. if we run out of memory. */
msgq(sp, M_ERR, "Error: %s/%d: put log error: %s.", \
tail(__FILE__), __LINE__, strerror(errno)); \
(void)ep->log->close(ep->log); \
msgq(sp, M_ERR, "Log restarted."); \
* Initialize the logging subsystem.
* Initialize the buffer. The logging subsystem has its own
* buffers because the global ones are almost by definition
* going to be in use when the log runs.
ep
->l_cursor
.lno
= 1; /* XXX Any valid recno. */
ep
->l_high
= ep
->l_cur
= 1;
ep
->log
= dbopen(NULL
, O_CREAT
| O_NONBLOCK
| O_RDWR
,
S_IRUSR
| S_IWUSR
, DB_RECNO
, NULL
);
msgq(sp
, M_ERR
, "log db: %s", strerror(errno
));
* Close the logging subsystem.
(void)(ep
->log
->close
)(ep
->log
);
ep
->l_cursor
.lno
= 1; /* XXX Any valid recno. */
ep
->l_high
= ep
->l_cur
= 1;
* Log the current cursor position, starting an event.
* If any changes were made since the last cursor init,
* put out the ending cursor record.
if (ep
->l_cursor
.lno
== OOBLNO
) {
ep
->l_cursor
.lno
= sp
->lno
;
ep
->l_cursor
.cno
= sp
->cno
;
return (log_cursor1(sp
, ep
, LOG_CURSOR_END
));
ep
->l_cursor
.lno
= sp
->lno
;
ep
->l_cursor
.cno
= sp
->cno
;
* Actually push a cursor record out.
log_cursor1(sp
, ep
, type
)
BINC_RET(sp
, ep
->l_lp
, ep
->l_len
, sizeof(u_char
) + sizeof(MARK
));
memmove(ep
->l_lp
+ sizeof(u_char
), &ep
->l_cursor
, sizeof(MARK
));
key
.size
= sizeof(recno_t
);
data
.size
= sizeof(u_char
) + sizeof(MARK
);
if (ep
->log
->put(ep
->log
, &key
, &data
, 0) == -1)
TRACE(sp
, "%lu: %s: %u/%u\n", ep
->l_cur
,
type
== LOG_CURSOR_INIT
? "log_cursor_init" : "log_cursor_end",
/* Reset high water mark. */
ep
->l_high
= ++ep
->l_cur
;
log_line(sp
, ep
, lno
, action
)
if (F_ISSET(ep
, F_NOLOG
))
* Kluge for vi. Clear the EXF undo flag so that the
* next 'u' command does a roll-back, regardless.
/* Put out one initial cursor record per set of changes. */
if (ep
->l_cursor
.lno
!= OOBLNO
) {
if (log_cursor1(sp
, ep
, LOG_CURSOR_INIT
))
ep
->l_cursor
.lno
= OOBLNO
;
* Put out the changes. If it's a LOG_LINE_RESET_B call, it's a
* special case, avoid the caches. Also, if it fails and it's
* line 1, it just means that the user started with an empty file,
* so fake an empty length line.
if (action
== LOG_LINE_RESET_B
) {
if ((lp
= file_rline(sp
, ep
, lno
, &len
)) == NULL
) {
if ((lp
= file_gline(sp
, ep
, lno
, &len
)) == NULL
) {
ep
->l_lp
, ep
->l_len
, len
+ sizeof(u_char
) + sizeof(recno_t
));
memmove(ep
->l_lp
+ sizeof(u_char
), &lno
, sizeof(recno_t
));
memmove(ep
->l_lp
+ sizeof(u_char
) + sizeof(recno_t
), lp
, len
);
key
.size
= sizeof(recno_t
);
data
.size
= len
+ sizeof(u_char
) + sizeof(recno_t
);
if (ep
->log
->put(ep
->log
, &key
, &data
, 0) == -1)
TRACE(sp
, "%u: log_line: append: %lu {%u}\n",
TRACE(sp
, "%lu: log_line: delete: %lu {%u}\n",
TRACE(sp
, "%lu: log_line: insert: %lu {%u}\n",
TRACE(sp
, "%lu: log_line: reset_f: %lu {%u}\n",
TRACE(sp
, "%lu: log_line: reset_b: %lu {%u}\n",
/* Reset high water mark. */
ep
->l_high
= ++ep
->l_cur
;
* Log a mark position. For the log to work, we assume that there
* aren't any operations that just put out a log record -- this
* would mean that undo operations would only reset marks, and not
* cause any other change.
if (F_ISSET(ep
, F_NOLOG
))
/* Put out one initial cursor record per set of changes. */
if (ep
->l_cursor
.lno
!= OOBLNO
) {
if (log_cursor1(sp
, ep
, LOG_CURSOR_INIT
))
ep
->l_cursor
.lno
= OOBLNO
;
ep
->l_len
, sizeof(u_char
) + sizeof(MARK
));
memmove(ep
->l_lp
+ sizeof(u_char
), mp
, sizeof(MARK
));
key
.size
= sizeof(recno_t
);
data
.size
= sizeof(u_char
) + sizeof(MARK
);
if (ep
->log
->put(ep
->log
, &key
, &data
, 0) == -1)
/* Reset high water mark. */
ep
->l_high
= ++ep
->l_cur
;
* Roll the log backward one operation.
if (F_ISSET(ep
, F_NOLOG
)) {
"Logging not being performed, undo not possible.");
msgq(sp
, M_BERR
, "No changes to undo.");
F_SET(ep
, F_NOLOG
); /* Turn off logging. */
key
.data
= &ep
->l_cur
; /* Initialize db request. */
key
.size
= sizeof(recno_t
);
if (ep
->log
->get(ep
->log
, &key
, &data
, 0))
log_trace(sp
, "log_backward", ep
->l_cur
, data
.data
);
switch (*(p
= (u_char
*)data
.data
)) {
memmove(rp
, p
+ sizeof(u_char
), sizeof(MARK
));
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
if (file_dline(sp
, ep
, lno
))
++sp
->rptlines
[L_DELETED
];
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
if (file_iline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
sizeof(recno_t
), data
.size
- sizeof(u_char
) -
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
if (file_sline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
sizeof(recno_t
), data
.size
- sizeof(u_char
) -
++sp
->rptlines
[L_CHANGED
];
memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
if (mark_set(sp
, ep
, m
.name
, &m
, 0))
* Reset the line to its original appearance.
* There's a bug in this code due to our not logging cursor movements
* unless a change was made. If you do a change, move off the line,
* then move back on and do a 'U', the line will be restored to the way
* it was before the original change.
if (F_ISSET(ep
, F_NOLOG
)) {
"Logging not being performed, undo not possible.");
F_SET(ep
, F_NOLOG
); /* Turn off logging. */
key
.data
= &ep
->l_cur
; /* Initialize db request. */
key
.size
= sizeof(recno_t
);
if (ep
->log
->get(ep
->log
, &key
, &data
, 0))
log_trace(sp
, "log_setline", ep
->l_cur
, data
.data
);
switch (*(p
= (u_char
*)data
.data
)) {
memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
if (m
.lno
!= sp
->lno
|| ep
->l_cur
== 1) {
memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
file_sline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
sizeof(recno_t
), data
.size
- sizeof(u_char
) -
++sp
->rptlines
[L_CHANGED
];
memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
if (mark_set(sp
, ep
, m
.name
, &m
, 0))
* Roll the log forward one operation.
if (F_ISSET(ep
, F_NOLOG
)) {
"Logging not being performed, roll-forward not possible.");
if (ep
->l_cur
== ep
->l_high
) {
msgq(sp
, M_BERR
, "No changes to re-do.");
F_SET(ep
, F_NOLOG
); /* Turn off logging. */
key
.data
= &ep
->l_cur
; /* Initialize db request. */
key
.size
= sizeof(recno_t
);
if (ep
->log
->get(ep
->log
, &key
, &data
, 0))
log_trace(sp
, "log_forward", ep
->l_cur
, data
.data
);
switch (*(p
= (u_char
*)data
.data
)) {
memmove(rp
, p
+ sizeof(u_char
), sizeof(MARK
));
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
if (file_iline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
sizeof(recno_t
), data
.size
- sizeof(u_char
) -
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
if (file_dline(sp
, ep
, lno
))
++sp
->rptlines
[L_DELETED
];
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
if (file_sline(sp
, ep
, lno
, p
+ sizeof(u_char
) +
sizeof(recno_t
), data
.size
- sizeof(u_char
) -
++sp
->rptlines
[L_CHANGED
];
memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
if (mark_set(sp
, ep
, m
.name
, &m
, 0))
log_trace(sp
, msg
, rno
, p
)
memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
TRACE(sp
, "%lu: %s: C_INIT: %u/%u\n", rno
, msg
, m
.lno
, m
.cno
);
memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
TRACE(sp
, "%lu: %s: C_END: %u/%u\n", rno
, msg
, m
.lno
, m
.cno
);
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
TRACE(sp
, "%lu: %s: APPEND: %lu\n", rno
, msg
, lno
);
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
TRACE(sp
, "%lu: %s: INSERT: %lu\n", rno
, msg
, lno
);
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
TRACE(sp
, "%lu: %s: DELETE: %lu\n", rno
, msg
, lno
);
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
TRACE(sp
, "%lu: %s: RESET_F: %lu\n", rno
, msg
, lno
);
memmove(&lno
, p
+ sizeof(u_char
), sizeof(recno_t
));
TRACE(sp
, "%lu: %s: RESET_B: %lu\n", rno
, msg
, lno
);
memmove(&m
, p
+ sizeof(u_char
), sizeof(MARK
));
TRACE(sp
, "%lu: %s: MARK: %u/%u\n", rno
, msg
, m
.lno
, m
.cno
);