diff options
Diffstat (limited to 'src/undo.c')
| -rw-r--r-- | src/undo.c | 2880 |
1 files changed, 0 insertions, 2880 deletions
diff --git a/src/undo.c b/src/undo.c deleted file mode 100644 index 47ce8ee8da..0000000000 --- a/src/undo.c +++ /dev/null @@ -1,2880 +0,0 @@ -/* - * VIM - Vi IMproved by Bram Moolenaar - * - * Do ":help uganda" in Vim to read copying and usage conditions. - * Do ":help credits" in Vim to see a list of people who contributed. - * See README.txt for an overview of the Vim source code. - */ - -/* - * undo.c: multi level undo facility - * - * The saved lines are stored in a list of lists (one for each buffer): - * - * b_u_oldhead------------------------------------------------+ - * | - * V - * +--------------+ +--------------+ +--------------+ - * b_u_newhead--->| u_header | | u_header | | u_header | - * | uh_next------>| uh_next------>| uh_next---->NULL - * NULL<--------uh_prev |<---------uh_prev |<---------uh_prev | - * | uh_entry | | uh_entry | | uh_entry | - * +--------|-----+ +--------|-----+ +--------|-----+ - * | | | - * V V V - * +--------------+ +--------------+ +--------------+ - * | u_entry | | u_entry | | u_entry | - * | ue_next | | ue_next | | ue_next | - * +--------|-----+ +--------|-----+ +--------|-----+ - * | | | - * V V V - * +--------------+ NULL NULL - * | u_entry | - * | ue_next | - * +--------|-----+ - * | - * V - * etc. - * - * Each u_entry list contains the information for one undo or redo. - * curbuf->b_u_curhead points to the header of the last undo (the next redo), - * or is NULL if nothing has been undone (end of the branch). - * - * For keeping alternate undo/redo branches the uh_alt field is used. Thus at - * each point in the list a branch may appear for an alternate to redo. The - * uh_seq field is numbered sequentially to be able to find a newer or older - * branch. - * - * +---------------+ +---------------+ - * b_u_oldhead --->| u_header | | u_header | - * | uh_alt_next ---->| uh_alt_next ----> NULL - * NULL <----- uh_alt_prev |<------ uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * V V - * +---------------+ +---------------+ - * | u_header | | u_header | - * | uh_alt_next | | uh_alt_next | - * b_u_newhead --->| uh_alt_prev | | uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * V V - * NULL +---------------+ +---------------+ - * | u_header | | u_header | - * | uh_alt_next ---->| uh_alt_next | - * | uh_alt_prev |<------ uh_alt_prev | - * | uh_prev | | uh_prev | - * +-----|---------+ +-----|---------+ - * | | - * etc. etc. - * - * - * All data is allocated and will all be freed when the buffer is unloaded. - */ - -/* Uncomment the next line for including the u_check() function. This warns - * for errors in the debug information. */ -/* #define U_DEBUG 1 */ -#define UH_MAGIC 0x18dade /* value for uh_magic when in use */ -#define UE_MAGIC 0xabc123 /* value for ue_magic when in use */ - -#include <string.h> - -#include "vim.h" -#include "undo.h" -#include "edit.h" -#include "eval.h" -#include "fileio.h" -#include "fold.h" -#include "mark.h" -#include "memline.h" -#include "message.h" -#include "misc1.h" -#include "misc2.h" -#include "memory.h" -#include "crypt.h" -#include "garray.h" -#include "option.h" -#include "os_unix.h" -#include "path.h" -#include "quickfix.h" -#include "screen.h" -#include "sha256.h" -#include "os/os.h" -#include "os/time.h" - -static long get_undolevel(void); -static void u_unch_branch(u_header_T *uhp); -static u_entry_T *u_get_headentry(void); -static void u_getbot(void); -static void u_doit(int count); -static void u_undoredo(int undo); -static void u_undo_end(int did_undo, int absolute); -static void u_add_time(char_u *buf, size_t buflen, time_t tt); -static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp); -static void u_freebranch(buf_T *buf, u_header_T *uhp, u_header_T **uhpp); -static void u_freeentries(buf_T *buf, u_header_T *uhp, - u_header_T **uhpp); -static void u_freeentry(u_entry_T *, long); -static void corruption_error(char *mesg, char_u *file_name); -static void u_free_uhp(u_header_T *uhp); -static size_t fwrite_crypt(buf_T *buf, char_u *ptr, size_t len, - FILE *fp); -static char_u *read_string_decrypt(buf_T *buf, FILE *fd, int len); -static int serialize_header(FILE *fp, buf_T *buf, char_u *hash); -static int serialize_uhp(FILE *fp, buf_T *buf, u_header_T *uhp); -static u_header_T *unserialize_uhp(FILE *fp, char_u *file_name); -static int serialize_uep(FILE *fp, buf_T *buf, u_entry_T *uep); -static u_entry_T *unserialize_uep(FILE *fp, int *error, - char_u *file_name); -static void serialize_pos(pos_T pos, FILE *fp); -static void unserialize_pos(pos_T *pos, FILE *fp); -static void serialize_visualinfo(visualinfo_T *info, FILE *fp); -static void unserialize_visualinfo(visualinfo_T *info, FILE *fp); -static void put_header_ptr(FILE *fp, u_header_T *uhp); - -static char_u *u_save_line(linenr_T); - -/* used in undo_end() to report number of added and deleted lines */ -static long u_newcount, u_oldcount; - -/* - * When 'u' flag included in 'cpoptions', we behave like vi. Need to remember - * the action that "u" should do. - */ -static int undo_undoes = FALSE; - -static int lastmark = 0; - -#if defined(U_DEBUG) || defined(PROTO) -/* - * Check the undo structures for being valid. Print a warning when something - * looks wrong. - */ -static int seen_b_u_curhead; -static int seen_b_u_newhead; -static int header_count; - -static void u_check_tree(u_header_T *uhp, - u_header_T *exp_uh_next, - u_header_T *exp_uh_alt_prev) { - u_entry_T *uep; - - if (uhp == NULL) - return; - ++header_count; - if (uhp == curbuf->b_u_curhead && ++seen_b_u_curhead > 1) { - EMSG("b_u_curhead found twice (looping?)"); - return; - } - if (uhp == curbuf->b_u_newhead && ++seen_b_u_newhead > 1) { - EMSG("b_u_newhead found twice (looping?)"); - return; - } - - if (uhp->uh_magic != UH_MAGIC) - EMSG("uh_magic wrong (may be using freed memory)"); - else { - /* Check pointers back are correct. */ - if (uhp->uh_next.ptr != exp_uh_next) { - EMSG("uh_next wrong"); - smsg((char_u *)"expected: 0x%x, actual: 0x%x", - exp_uh_next, uhp->uh_next.ptr); - } - if (uhp->uh_alt_prev.ptr != exp_uh_alt_prev) { - EMSG("uh_alt_prev wrong"); - smsg((char_u *)"expected: 0x%x, actual: 0x%x", - exp_uh_alt_prev, uhp->uh_alt_prev.ptr); - } - - /* Check the undo tree at this header. */ - for (uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next) { - if (uep->ue_magic != UE_MAGIC) { - EMSG("ue_magic wrong (may be using freed memory)"); - break; - } - } - - /* Check the next alt tree. */ - u_check_tree(uhp->uh_alt_next.ptr, uhp->uh_next.ptr, uhp); - - /* Check the next header in this branch. */ - u_check_tree(uhp->uh_prev.ptr, uhp, NULL); - } -} - -static void u_check(int newhead_may_be_NULL) { - seen_b_u_newhead = 0; - seen_b_u_curhead = 0; - header_count = 0; - - u_check_tree(curbuf->b_u_oldhead, NULL, NULL); - - if (seen_b_u_newhead == 0 && curbuf->b_u_oldhead != NULL - && !(newhead_may_be_NULL && curbuf->b_u_newhead == NULL)) - EMSGN("b_u_newhead invalid: 0x%x", curbuf->b_u_newhead); - if (curbuf->b_u_curhead != NULL && seen_b_u_curhead == 0) - EMSGN("b_u_curhead invalid: 0x%x", curbuf->b_u_curhead); - if (header_count != curbuf->b_u_numhead) { - EMSG("b_u_numhead invalid"); - smsg((char_u *)"expected: %" PRId64 ", actual: %" PRId64, - (int64_t)header_count, (int64_t)curbuf->b_u_numhead); - } -} - -#endif - -/* - * Save the current line for both the "u" and "U" command. - * Careful: may trigger autocommands that reload the buffer. - * Returns OK or FAIL. - */ -int u_save_cursor(void) -{ - return u_save((linenr_T)(curwin->w_cursor.lnum - 1), - (linenr_T)(curwin->w_cursor.lnum + 1)); -} - -/* - * Save the lines between "top" and "bot" for both the "u" and "U" command. - * "top" may be 0 and bot may be curbuf->b_ml.ml_line_count + 1. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ -int u_save(linenr_T top, linenr_T bot) -{ - if (undo_off) - return OK; - - if (top > curbuf->b_ml.ml_line_count - || top >= bot - || bot > curbuf->b_ml.ml_line_count + 1) - return FALSE; /* rely on caller to do error messages */ - - if (top + 2 == bot) - u_saveline((linenr_T)(top + 1)); - - return u_savecommon(top, bot, (linenr_T)0, FALSE); -} - -/* - * Save the line "lnum" (used by ":s" and "~" command). - * The line is replaced, so the new bottom line is lnum + 1. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ -int u_savesub(linenr_T lnum) -{ - if (undo_off) - return OK; - - return u_savecommon(lnum - 1, lnum + 1, lnum + 1, FALSE); -} - -/* - * A new line is inserted before line "lnum" (used by :s command). - * The line is inserted, so the new bottom line is lnum + 1. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ -int u_inssub(linenr_T lnum) -{ - if (undo_off) - return OK; - - return u_savecommon(lnum - 1, lnum, lnum + 1, FALSE); -} - -/* - * Save the lines "lnum" - "lnum" + nlines (used by delete command). - * The lines are deleted, so the new bottom line is lnum, unless the buffer - * becomes empty. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ -int u_savedel(linenr_T lnum, long nlines) -{ - if (undo_off) - return OK; - - return u_savecommon(lnum - 1, lnum + nlines, - nlines == curbuf->b_ml.ml_line_count ? 2 : lnum, FALSE); -} - -/* - * Return TRUE when undo is allowed. Otherwise give an error message and - * return FALSE. - */ -int undo_allowed(void) -{ - /* Don't allow changes when 'modifiable' is off. */ - if (!curbuf->b_p_ma) { - EMSG(_(e_modifiable)); - return FALSE; - } - -#ifdef HAVE_SANDBOX - /* In the sandbox it's not allowed to change the text. */ - if (sandbox != 0) { - EMSG(_(e_sandbox)); - return FALSE; - } -#endif - - /* Don't allow changes in the buffer while editing the cmdline. The - * caller of getcmdline() may get confused. */ - if (textlock != 0) { - EMSG(_(e_secure)); - return FALSE; - } - - return TRUE; -} - -/* - * Get the undolevle value for the current buffer. - */ -static long get_undolevel(void) -{ - if (curbuf->b_p_ul == NO_LOCAL_UNDOLEVEL) - return p_ul; - return curbuf->b_p_ul; -} - -/* - * Common code for various ways to save text before a change. - * "top" is the line above the first changed line. - * "bot" is the line below the last changed line. - * "newbot" is the new bottom line. Use zero when not known. - * "reload" is TRUE when saving for a buffer reload. - * Careful: may trigger autocommands that reload the buffer. - * Returns FAIL when lines could not be saved, OK otherwise. - */ -int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload) -{ - linenr_T lnum; - long i; - u_header_T *uhp; - u_header_T *old_curhead; - u_entry_T *uep; - u_entry_T *prev_uep; - long size; - - if (!reload) { - /* When making changes is not allowed return FAIL. It's a crude way - * to make all change commands fail. */ - if (!undo_allowed()) - return FAIL; - - - /* - * Saving text for undo means we are going to make a change. Give a - * warning for a read-only file before making the change, so that the - * FileChangedRO event can replace the buffer with a read-write version - * (e.g., obtained from a source control system). - */ - change_warning(0); - if (bot > curbuf->b_ml.ml_line_count + 1) { - /* This happens when the FileChangedRO autocommand changes the - * file in a way it becomes shorter. */ - EMSG(_("E881: Line count changed unexpectedly")); - return FAIL; - } - } - -#ifdef U_DEBUG - u_check(FALSE); -#endif - - size = bot - top - 1; - - /* - * If curbuf->b_u_synced == TRUE make a new header. - */ - if (curbuf->b_u_synced) { - /* Need to create new entry in b_changelist. */ - curbuf->b_new_change = TRUE; - - if (get_undolevel() >= 0) { - /* - * Make a new header entry. Do this first so that we don't mess - * up the undo info when out of memory. - */ - uhp = xmalloc(sizeof(u_header_T)); -#ifdef U_DEBUG - uhp->uh_magic = UH_MAGIC; -#endif - } else - uhp = NULL; - - /* - * If we undid more than we redid, move the entry lists before and - * including curbuf->b_u_curhead to an alternate branch. - */ - old_curhead = curbuf->b_u_curhead; - if (old_curhead != NULL) { - curbuf->b_u_newhead = old_curhead->uh_next.ptr; - curbuf->b_u_curhead = NULL; - } - - /* - * free headers to keep the size right - */ - while (curbuf->b_u_numhead > get_undolevel() - && curbuf->b_u_oldhead != NULL) { - u_header_T *uhfree = curbuf->b_u_oldhead; - - if (uhfree == old_curhead) - /* Can't reconnect the branch, delete all of it. */ - u_freebranch(curbuf, uhfree, &old_curhead); - else if (uhfree->uh_alt_next.ptr == NULL) - /* There is no branch, only free one header. */ - u_freeheader(curbuf, uhfree, &old_curhead); - else { - /* Free the oldest alternate branch as a whole. */ - while (uhfree->uh_alt_next.ptr != NULL) - uhfree = uhfree->uh_alt_next.ptr; - u_freebranch(curbuf, uhfree, &old_curhead); - } -#ifdef U_DEBUG - u_check(TRUE); -#endif - } - - if (uhp == NULL) { /* no undo at all */ - if (old_curhead != NULL) - u_freebranch(curbuf, old_curhead, NULL); - curbuf->b_u_synced = FALSE; - return OK; - } - - uhp->uh_prev.ptr = NULL; - uhp->uh_next.ptr = curbuf->b_u_newhead; - uhp->uh_alt_next.ptr = old_curhead; - if (old_curhead != NULL) { - uhp->uh_alt_prev.ptr = old_curhead->uh_alt_prev.ptr; - if (uhp->uh_alt_prev.ptr != NULL) - uhp->uh_alt_prev.ptr->uh_alt_next.ptr = uhp; - old_curhead->uh_alt_prev.ptr = uhp; - if (curbuf->b_u_oldhead == old_curhead) - curbuf->b_u_oldhead = uhp; - } else - uhp->uh_alt_prev.ptr = NULL; - if (curbuf->b_u_newhead != NULL) - curbuf->b_u_newhead->uh_prev.ptr = uhp; - - uhp->uh_seq = ++curbuf->b_u_seq_last; - curbuf->b_u_seq_cur = uhp->uh_seq; - uhp->uh_time = time(NULL); - uhp->uh_save_nr = 0; - curbuf->b_u_time_cur = uhp->uh_time + 1; - - uhp->uh_walk = 0; - uhp->uh_entry = NULL; - uhp->uh_getbot_entry = NULL; - uhp->uh_cursor = curwin->w_cursor; /* save cursor pos. for undo */ - if (virtual_active() && curwin->w_cursor.coladd > 0) - uhp->uh_cursor_vcol = getviscol(); - else - uhp->uh_cursor_vcol = -1; - - /* save changed and buffer empty flag for undo */ - uhp->uh_flags = (curbuf->b_changed ? UH_CHANGED : 0) + - ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0); - - /* save named marks and Visual marks for undo */ - memmove(uhp->uh_namedm, curbuf->b_namedm, sizeof(pos_T) * NMARKS); - uhp->uh_visual = curbuf->b_visual; - - curbuf->b_u_newhead = uhp; - if (curbuf->b_u_oldhead == NULL) - curbuf->b_u_oldhead = uhp; - ++curbuf->b_u_numhead; - } else { - if (get_undolevel() < 0) /* no undo at all */ - return OK; - - /* - * When saving a single line, and it has been saved just before, it - * doesn't make sense saving it again. Saves a lot of memory when - * making lots of changes inside the same line. - * This is only possible if the previous change didn't increase or - * decrease the number of lines. - * Check the ten last changes. More doesn't make sense and takes too - * long. - */ - if (size == 1) { - uep = u_get_headentry(); - prev_uep = NULL; - for (i = 0; i < 10; ++i) { - if (uep == NULL) - break; - - /* If lines have been inserted/deleted we give up. - * Also when the line was included in a multi-line save. */ - if ((curbuf->b_u_newhead->uh_getbot_entry != uep - ? (uep->ue_top + uep->ue_size + 1 - != (uep->ue_bot == 0 - ? curbuf->b_ml.ml_line_count + 1 - : uep->ue_bot)) - : uep->ue_lcount != curbuf->b_ml.ml_line_count) - || (uep->ue_size > 1 - && top >= uep->ue_top - && top + 2 <= uep->ue_top + uep->ue_size + 1)) - break; - - /* If it's the same line we can skip saving it again. */ - if (uep->ue_size == 1 && uep->ue_top == top) { - if (i > 0) { - /* It's not the last entry: get ue_bot for the last - * entry now. Following deleted/inserted lines go to - * the re-used entry. */ - u_getbot(); - curbuf->b_u_synced = FALSE; - - /* Move the found entry to become the last entry. The - * order of undo/redo doesn't matter for the entries - * we move it over, since they don't change the line - * count and don't include this line. It does matter - * for the found entry if the line count is changed by - * the executed command. */ - prev_uep->ue_next = uep->ue_next; - uep->ue_next = curbuf->b_u_newhead->uh_entry; - curbuf->b_u_newhead->uh_entry = uep; - } - - /* The executed command may change the line count. */ - if (newbot != 0) - uep->ue_bot = newbot; - else if (bot > curbuf->b_ml.ml_line_count) - uep->ue_bot = 0; - else { - uep->ue_lcount = curbuf->b_ml.ml_line_count; - curbuf->b_u_newhead->uh_getbot_entry = uep; - } - return OK; - } - prev_uep = uep; - uep = uep->ue_next; - } - } - - /* find line number for ue_bot for previous u_save() */ - u_getbot(); - } - - /* - * add lines in front of entry list - */ - uep = xmalloc(sizeof(u_entry_T)); - memset(uep, 0, sizeof(u_entry_T)); -#ifdef U_DEBUG - uep->ue_magic = UE_MAGIC; -#endif - - uep->ue_size = size; - uep->ue_top = top; - if (newbot != 0) - uep->ue_bot = newbot; - /* - * Use 0 for ue_bot if bot is below last line. - * Otherwise we have to compute ue_bot later. - */ - else if (bot > curbuf->b_ml.ml_line_count) - uep->ue_bot = 0; - else { - uep->ue_lcount = curbuf->b_ml.ml_line_count; - curbuf->b_u_newhead->uh_getbot_entry = uep; - } - - if (size > 0) { - uep->ue_array = xmalloc(sizeof(char_u *) * size); - for (i = 0, lnum = top + 1; i < size; ++i) { - fast_breakcheck(); - if (got_int) { - u_freeentry(uep, i); - return FAIL; - } - if ((uep->ue_array[i] = u_save_line(lnum++)) == NULL) { - u_freeentry(uep, i); - goto nomem; - } - } - } else - uep->ue_array = NULL; - uep->ue_next = curbuf->b_u_newhead->uh_entry; - curbuf->b_u_newhead->uh_entry = uep; - curbuf->b_u_synced = FALSE; - undo_undoes = FALSE; - -#ifdef U_DEBUG - u_check(FALSE); -#endif - return OK; - -nomem: - msg_silent = 0; /* must display the prompt */ - if (ask_yesno((char_u *)_("No undo possible; continue anyway"), TRUE) - == 'y') { - undo_off = TRUE; /* will be reset when character typed */ - return OK; - } - do_outofmem_msg((long_u)0); - return FAIL; -} - - -# define UF_START_MAGIC "Vim\237UnDo\345" /* magic at start of undofile */ -# define UF_START_MAGIC_LEN 9 -# define UF_HEADER_MAGIC 0x5fd0 /* magic at start of header */ -# define UF_HEADER_END_MAGIC 0xe7aa /* magic after last header */ -# define UF_ENTRY_MAGIC 0xf518 /* magic at start of entry */ -# define UF_ENTRY_END_MAGIC 0x3581 /* magic after last entry */ -# define UF_VERSION 2 /* 2-byte undofile version number */ -# define UF_VERSION_CRYPT 0x8002 /* idem, encrypted */ - -/* extra fields for header */ -# define UF_LAST_SAVE_NR 1 - -/* extra fields for uhp */ -# define UHP_SAVE_NR 1 - -static char_u e_not_open[] = N_("E828: Cannot open undo file for writing: %s"); - -/* - * Compute the hash for the current buffer text into hash[UNDO_HASH_SIZE]. - */ -void u_compute_hash(char_u *hash) -{ - context_sha256_T ctx; - linenr_T lnum; - char_u *p; - - sha256_start(&ctx); - for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) { - p = ml_get(lnum); - sha256_update(&ctx, p, (uint32_t)(STRLEN(p) + 1)); - } - sha256_finish(&ctx, hash); -} - -/* - * Return an allocated string of the full path of the target undofile. - * When "reading" is TRUE find the file to read, go over all directories in - * 'undodir'. - * When "reading" is FALSE use the first name where the directory exists. - * Returns NULL when there is no place to write or no file to read. - */ -char_u *u_get_undo_file_name(char_u *buf_ffname, int reading) -{ - char_u *dirp; - char_u dir_name[IOSIZE + 1]; - char_u *munged_name = NULL; - char_u *undo_file_name = NULL; - int dir_len; - char_u *p; - char_u *ffname = buf_ffname; -#ifdef HAVE_READLINK - char_u fname_buf[MAXPATHL]; -#endif - - if (ffname == NULL) - return NULL; - -#ifdef HAVE_READLINK - /* Expand symlink in the file name, so that we put the undo file with the - * actual file instead of with the symlink. */ - if (resolve_symlink(ffname, fname_buf) == OK) - ffname = fname_buf; -#endif - - /* Loop over 'undodir'. When reading find the first file that exists. - * When not reading use the first directory that exists or ".". */ - dirp = p_udir; - while (*dirp != NUL) { - dir_len = copy_option_part(&dirp, dir_name, IOSIZE, ","); - if (dir_len == 1 && dir_name[0] == '.') { - /* Use same directory as the ffname, - * "dir/name" -> "dir/.name.un~" */ - undo_file_name = vim_strnsave(ffname, (int)(STRLEN(ffname) + 5)); - if (undo_file_name == NULL) - break; - p = path_tail(undo_file_name); - memmove(p + 1, p, STRLEN(p) + 1); - *p = '.'; - STRCAT(p, ".un~"); - } else { - dir_name[dir_len] = NUL; - if (os_isdir(dir_name)) { - if (munged_name == NULL) { - munged_name = vim_strsave(ffname); - if (munged_name == NULL) - return NULL; - for (p = munged_name; *p != NUL; mb_ptr_adv(p)) - if (vim_ispathsep(*p)) - *p = '%'; - } - undo_file_name = concat_fnames(dir_name, munged_name, TRUE); - } - } - - // When reading check if the file exists. - if (undo_file_name != NULL && - (!reading || os_file_exists(undo_file_name))) { - break; - } - free(undo_file_name); - undo_file_name = NULL; - } - - free(munged_name); - return undo_file_name; -} - -static void corruption_error(char *mesg, char_u *file_name) -{ - EMSG3(_("E825: Corrupted undo file (%s): %s"), mesg, file_name); -} - -static void u_free_uhp(u_header_T *uhp) -{ - u_entry_T *nuep; - u_entry_T *uep; - - uep = uhp->uh_entry; - while (uep != NULL) { - nuep = uep->ue_next; - u_freeentry(uep, uep->ue_size); - uep = nuep; - } - free(uhp); -} - -/* - * Like fwrite() but crypt the bytes when 'key' is set. - * Returns 1 if successful. - */ -static size_t fwrite_crypt(buf_T *buf, char_u *ptr, size_t len, FILE *fp) -{ - char_u *copy; - char_u small_buf[100]; - size_t i; - - if (*buf->b_p_key == NUL) - return fwrite(ptr, len, (size_t)1, fp); - if (len < 100) - copy = small_buf; /* no malloc()/free() for short strings */ - else { - copy = xmalloc(len); - } - crypt_encode(ptr, len, copy); - i = fwrite(copy, len, (size_t)1, fp); - if (copy != small_buf) - free(copy); - return i; -} - -/* - * Read a string of length "len" from "fd". - * When 'key' is set decrypt the bytes. - */ -static char_u *read_string_decrypt(buf_T *buf, FILE *fd, int len) -{ - char_u *ptr; - - ptr = read_string(fd, len); - if (ptr != NULL && *buf->b_p_key != NUL) - crypt_decode(ptr, len); - return ptr; -} - -static int serialize_header(FILE *fp, buf_T *buf, char_u *hash) -{ - int len; - - /* Start writing, first the magic marker and undo info version. */ - if (fwrite(UF_START_MAGIC, (size_t)UF_START_MAGIC_LEN, (size_t)1, fp) != 1) - return FAIL; - - /* If the buffer is encrypted then all text bytes following will be - * encrypted. Numbers and other info is not crypted. */ - if (*buf->b_p_key != NUL) { - char_u *header; - int header_len; - - put_bytes(fp, (long_u)UF_VERSION_CRYPT, 2); - header = prepare_crypt_write(buf, &header_len); - if (header == NULL) - return FAIL; - len = (int)fwrite(header, (size_t)header_len, (size_t)1, fp); - free(header); - if (len != 1) { - crypt_pop_state(); - return FAIL; - } - } else - put_bytes(fp, (long_u)UF_VERSION, 2); - - - /* Write a hash of the buffer text, so that we can verify it is still the - * same when reading the buffer text. */ - if (fwrite(hash, (size_t)UNDO_HASH_SIZE, (size_t)1, fp) != 1) - return FAIL; - - /* buffer-specific data */ - put_bytes(fp, (long_u)buf->b_ml.ml_line_count, 4); - len = buf->b_u_line_ptr != NULL ? (int)STRLEN(buf->b_u_line_ptr) : 0; - put_bytes(fp, (long_u)len, 4); - if (len > 0 && fwrite_crypt(buf, buf->b_u_line_ptr, (size_t)len, fp) != 1) - return FAIL; - put_bytes(fp, (long_u)buf->b_u_line_lnum, 4); - put_bytes(fp, (long_u)buf->b_u_line_colnr, 4); - - /* Undo structures header data */ - put_header_ptr(fp, buf->b_u_oldhead); - put_header_ptr(fp, buf->b_u_newhead); - put_header_ptr(fp, buf->b_u_curhead); - - put_bytes(fp, (long_u)buf->b_u_numhead, 4); - put_bytes(fp, (long_u)buf->b_u_seq_last, 4); - put_bytes(fp, (long_u)buf->b_u_seq_cur, 4); - put_time(fp, buf->b_u_time_cur); - - /* Optional fields. */ - putc(4, fp); - putc(UF_LAST_SAVE_NR, fp); - put_bytes(fp, (long_u)buf->b_u_save_nr_last, 4); - - putc(0, fp); /* end marker */ - - return OK; -} - -static int serialize_uhp(FILE *fp, buf_T *buf, u_header_T *uhp) -{ - int i; - u_entry_T *uep; - - if (put_bytes(fp, (long_u)UF_HEADER_MAGIC, 2) == FAIL) - return FAIL; - - put_header_ptr(fp, uhp->uh_next.ptr); - put_header_ptr(fp, uhp->uh_prev.ptr); - put_header_ptr(fp, uhp->uh_alt_next.ptr); - put_header_ptr(fp, uhp->uh_alt_prev.ptr); - put_bytes(fp, uhp->uh_seq, 4); - serialize_pos(uhp->uh_cursor, fp); - put_bytes(fp, (long_u)uhp->uh_cursor_vcol, 4); - put_bytes(fp, (long_u)uhp->uh_flags, 2); - /* Assume NMARKS will stay the same. */ - for (i = 0; i < NMARKS; ++i) - serialize_pos(uhp->uh_namedm[i], fp); - serialize_visualinfo(&uhp->uh_visual, fp); - put_time(fp, uhp->uh_time); - - /* Optional fields. */ - putc(4, fp); - putc(UHP_SAVE_NR, fp); - put_bytes(fp, (long_u)uhp->uh_save_nr, 4); - - putc(0, fp); /* end marker */ - - /* Write all the entries. */ - for (uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next) { - put_bytes(fp, (long_u)UF_ENTRY_MAGIC, 2); - if (serialize_uep(fp, buf, uep) == FAIL) - return FAIL; - } - put_bytes(fp, (long_u)UF_ENTRY_END_MAGIC, 2); - return OK; -} - -static u_header_T *unserialize_uhp(FILE *fp, char_u *file_name) -{ - u_header_T *uhp; - int i; - u_entry_T *uep, *last_uep; - int c; - int error; - - uhp = xmalloc(sizeof(u_header_T)); - memset(uhp, 0, sizeof(u_header_T)); -#ifdef U_DEBUG - uhp->uh_magic = UH_MAGIC; -#endif - uhp->uh_next.seq = get4c(fp); - uhp->uh_prev.seq = get4c(fp); - uhp->uh_alt_next.seq = get4c(fp); - uhp->uh_alt_prev.seq = get4c(fp); - uhp->uh_seq = get4c(fp); - if (uhp->uh_seq <= 0) { - corruption_error("uh_seq", file_name); - free(uhp); - return NULL; - } - unserialize_pos(&uhp->uh_cursor, fp); - uhp->uh_cursor_vcol = get4c(fp); - uhp->uh_flags = get2c(fp); - for (i = 0; i < NMARKS; ++i) - unserialize_pos(&uhp->uh_namedm[i], fp); - unserialize_visualinfo(&uhp->uh_visual, fp); - uhp->uh_time = get8ctime(fp); - - /* Optional fields. */ - for (;; ) { - int len = getc(fp); - int what; - - if (len == 0) - break; - what = getc(fp); - switch (what) { - case UHP_SAVE_NR: - uhp->uh_save_nr = get4c(fp); - break; - default: - /* field not supported, skip */ - while (--len >= 0) - (void)getc(fp); - } - } - - /* Unserialize the uep list. */ - last_uep = NULL; - while ((c = get2c(fp)) == UF_ENTRY_MAGIC) { - error = FALSE; - uep = unserialize_uep(fp, &error, file_name); - if (last_uep == NULL) - uhp->uh_entry = uep; - else - last_uep->ue_next = uep; - last_uep = uep; - if (uep == NULL || error) { - u_free_uhp(uhp); - return NULL; - } - } - if (c != UF_ENTRY_END_MAGIC) { - corruption_error("entry end", file_name); - u_free_uhp(uhp); - return NULL; - } - - return uhp; -} - -/* - * Serialize "uep" to "fp". - */ -static int serialize_uep(FILE *fp, buf_T *buf, u_entry_T *uep) -{ - int i; - size_t len; - - put_bytes(fp, (long_u)uep->ue_top, 4); - put_bytes(fp, (long_u)uep->ue_bot, 4); - put_bytes(fp, (long_u)uep->ue_lcount, 4); - put_bytes(fp, (long_u)uep->ue_size, 4); - for (i = 0; i < uep->ue_size; ++i) { - len = STRLEN(uep->ue_array[i]); - if (put_bytes(fp, (long_u)len, 4) == FAIL) - return FAIL; - if (len > 0 && fwrite_crypt(buf, uep->ue_array[i], len, fp) != 1) - return FAIL; - } - return OK; -} - -static u_entry_T *unserialize_uep(FILE *fp, int *error, char_u *file_name) -{ - int i; - u_entry_T *uep; - char_u **array; - char_u *line; - int line_len; - - uep = xmalloc(sizeof(u_entry_T)); - memset(uep, 0, sizeof(u_entry_T)); -#ifdef U_DEBUG - uep->ue_magic = UE_MAGIC; -#endif - uep->ue_top = get4c(fp); - uep->ue_bot = get4c(fp); - uep->ue_lcount = get4c(fp); - uep->ue_size = get4c(fp); - if (uep->ue_size > 0) { - array = xmalloc(sizeof(char_u *) * uep->ue_size); - memset(array, 0, sizeof(char_u *) * uep->ue_size); - } else - array = NULL; - uep->ue_array = array; - - for (i = 0; i < uep->ue_size; ++i) { - line_len = get4c(fp); - if (line_len >= 0) - line = read_string_decrypt(curbuf, fp, line_len); - else { - line = NULL; - corruption_error("line length", file_name); - } - if (line == NULL) { - *error = TRUE; - return uep; - } - array[i] = line; - } - return uep; -} - -/* - * Serialize "pos" to "fp". - */ -static void serialize_pos(pos_T pos, FILE *fp) -{ - put_bytes(fp, (long_u)pos.lnum, 4); - put_bytes(fp, (long_u)pos.col, 4); - put_bytes(fp, (long_u)pos.coladd, 4); -} - -/* - * Unserialize the pos_T at the current position in fp. - */ -static void unserialize_pos(pos_T *pos, FILE *fp) -{ - pos->lnum = get4c(fp); - if (pos->lnum < 0) - pos->lnum = 0; - pos->col = get4c(fp); - if (pos->col < 0) - pos->col = 0; - pos->coladd = get4c(fp); - if (pos->coladd < 0) - pos->coladd = 0; -} - -/* - * Serialize "info" to "fp". - */ -static void serialize_visualinfo(visualinfo_T *info, FILE *fp) -{ - serialize_pos(info->vi_start, fp); - serialize_pos(info->vi_end, fp); - put_bytes(fp, (long_u)info->vi_mode, 4); - put_bytes(fp, (long_u)info->vi_curswant, 4); -} - -/* - * Unserialize the visualinfo_T at the current position in fp. - */ -static void unserialize_visualinfo(visualinfo_T *info, FILE *fp) -{ - unserialize_pos(&info->vi_start, fp); - unserialize_pos(&info->vi_end, fp); - info->vi_mode = get4c(fp); - info->vi_curswant = get4c(fp); -} - -/* - * Write the pointer to an undo header. Instead of writing the pointer itself - * we use the sequence number of the header. This is converted back to - * pointers when reading. */ -static void put_header_ptr(FILE *fp, u_header_T *uhp) -{ - put_bytes(fp, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4); -} - -/* - * Write the undo tree in an undo file. - * When "name" is not NULL, use it as the name of the undo file. - * Otherwise use buf->b_ffname to generate the undo file name. - * "buf" must never be null, buf->b_ffname is used to obtain the original file - * permissions. - * "forceit" is TRUE for ":wundo!", FALSE otherwise. - * "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. - */ -void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) -{ - u_header_T *uhp; - char_u *file_name; - int mark; -#ifdef U_DEBUG - int headers_written = 0; -#endif - int fd; - FILE *fp = NULL; - int perm; - int write_ok = FALSE; - int do_crypt = FALSE; - - if (name == NULL) { - file_name = u_get_undo_file_name(buf->b_ffname, FALSE); - if (file_name == NULL) { - if (p_verbose > 0) { - verbose_enter(); - smsg((char_u *) - _("Cannot write undo file in any directory in 'undodir'")); - verbose_leave(); - } - return; - } - } else - file_name = name; - - /* - * Decide about the permission to use for the undo file. If the buffer < |