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