diff options
author | Bram Moolenaar <Bram@vim.org> | 2010-05-29 20:33:07 +0200 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2010-05-29 20:33:07 +0200 |
commit | 9db580634c0055674017eab535b1b9eec7d6939d (patch) | |
tree | d34ad271b13df816deeeae7622757b8dd385cf1a /src | |
parent | f05e3b0220a6b68791b5563ddf67ad42dbf74ee2 (diff) | |
download | vim-git-9db580634c0055674017eab535b1b9eec7d6939d.tar.gz |
Various improvements to undo file code to make it more robust.
Diffstat (limited to 'src')
-rw-r--r-- | src/misc2.c | 6 | ||||
-rw-r--r-- | src/proto/undo.pro | 1 | ||||
-rw-r--r-- | src/undo.c | 968 |
3 files changed, 530 insertions, 445 deletions
diff --git a/src/misc2.c b/src/misc2.c index 470bc2f4c..f5bad9c87 100644 --- a/src/misc2.c +++ b/src/misc2.c @@ -6134,7 +6134,7 @@ emsgn(s, n) get2c(fd) FILE *fd; { - long n; + int n; n = getc(fd); n = (n << 8) + getc(fd); @@ -6148,7 +6148,7 @@ get2c(fd) get3c(fd) FILE *fd; { - long n; + int n; n = getc(fd); n = (n << 8) + getc(fd); @@ -6163,7 +6163,7 @@ get3c(fd) get4c(fd) FILE *fd; { - long n; + int n; n = getc(fd); n = (n << 8) + getc(fd); diff --git a/src/proto/undo.pro b/src/proto/undo.pro index e5f28bec4..90e11d2bb 100644 --- a/src/proto/undo.pro +++ b/src/proto/undo.pro @@ -1,4 +1,5 @@ /* undo.c */ +void u_check __ARGS((int newhead_may_be_NULL)); int u_save_cursor __ARGS((void)); int u_save __ARGS((linenr_T top, linenr_T bot)); int u_savesub __ARGS((linenr_T lnum)); diff --git a/src/undo.c b/src/undo.c index 52d6ae0fc..958c6d2de 100644 --- a/src/undo.c +++ b/src/undo.c @@ -100,13 +100,16 @@ static void u_freebranch __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp) static void u_freeentries __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp)); static void u_freeentry __ARGS((u_entry_T *, long)); #ifdef FEAT_PERSISTENT_UNDO -static void unserialize_pos __ARGS((pos_T *pos, FILE *fp)); -static void unserialize_visualinfo __ARGS((visualinfo_T *info, FILE *fp)); static char_u *u_get_undo_file_name __ARGS((char_u *, int reading)); +static void corruption_error __ARGS((char *msg, char_u *file_name)); static void u_free_uhp __ARGS((u_header_T *uhp)); static int serialize_uep __ARGS((u_entry_T *uep, FILE *fp)); +static u_entry_T *unserialize_uep __ARGS((FILE *fp, int *error, char_u *file_name)); static void serialize_pos __ARGS((pos_T pos, FILE *fp)); +static void unserialize_pos __ARGS((pos_T *pos, FILE *fp)); static void serialize_visualinfo __ARGS((visualinfo_T *info, FILE *fp)); +static void unserialize_visualinfo __ARGS((visualinfo_T *info, FILE *fp)); +static void put_header_ptr __ARGS((FILE *fp, u_header_T *uhp)); #endif #define U_ALLOC_LINE(size) lalloc((long_u)(size), FALSE) @@ -122,7 +125,7 @@ static int undo_undoes = FALSE; static int lastmark = 0; -#ifdef U_DEBUG +#if defined(U_DEBUG) || defined(PROTO) /* * Check the undo structures for being valid. Print a warning when something * looks wrong. @@ -658,13 +661,15 @@ nomem: #ifdef FEAT_PERSISTENT_UNDO -# define UF_START_MAGIC 0xfeac /* magic at start of undofile */ -# define UF_HEADER_MAGIC 0x5fd0 /* magic at start of header */ -# define UF_END_MAGIC 0xe7aa /* magic after last header */ -# define UF_VERSION 1 /* 2-byte undofile version number */ +# 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 1 /* 2-byte undofile version number */ static char_u e_not_open[] = N_("E828: Cannot open undo file for writing: %s"); -static char_u e_corrupted[] = N_("E823: Corrupted undo file: %s"); /* * Compute the hash for the current buffer text into hash[UNDO_HASH_SIZE]. @@ -687,41 +692,11 @@ u_compute_hash(hash) } /* - * Unserialize the pos_T at the current position in fp. - */ - static void -unserialize_pos(pos, fp) - pos_T *pos; - FILE *fp; -{ - pos->lnum = get4c(fp); - pos->col = get4c(fp); -#ifdef FEAT_VIRTUALEDIT - pos->coladd = get4c(fp); -#else - (void)get4c(fp); -#endif -} - -/* - * Unserialize the visualinfo_T at the current position in fp. - */ - static void -unserialize_visualinfo(info, fp) - 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); -} - -/* * 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. */ static char_u * u_get_undo_file_name(buf_ffname, reading) @@ -798,341 +773,12 @@ u_get_undo_file_name(buf_ffname, reading) return undo_file_name; } -/* - * Load the undo tree from an undo file. - * If "name" is not NULL use it as the undo file name. This also means being - * a bit more verbose. - * Otherwise use curbuf->b_ffname to generate the undo file name. - * "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. - */ - void -u_read_undo(name, hash) - char_u *name; - char_u *hash; + static void +corruption_error(msg, file_name) + char *msg; + char_u *file_name; { - char_u *file_name; - FILE *fp; - long magic, version, str_len; - char_u *line_ptr = NULL; - linenr_T line_lnum; - colnr_T line_colnr; - linenr_T line_count; - int uep_len; - int line_len; - int num_head = 0; - long old_header_seq, new_header_seq, cur_header_seq; - long seq_last, seq_cur; - short old_idx = -1, new_idx = -1, cur_idx = -1; - long num_read_uhps = 0; - time_t seq_time; - int i, j; - int c; - char_u **array; - char_u *line; - u_entry_T *uep, *last_uep; - u_header_T *uhp; - u_header_T **uhp_table = NULL; - char_u read_hash[UNDO_HASH_SIZE]; - - if (name == NULL) - { - file_name = u_get_undo_file_name(curbuf->b_ffname, TRUE); - if (file_name == NULL) - return; - } - else - file_name = name; - - if (p_verbose > 0) - smsg((char_u *)_("Reading undo file: %s"), file_name); - fp = mch_fopen((char *)file_name, "r"); - if (fp == NULL) - { - if (name != NULL || p_verbose > 0) - EMSG2(_("E822: Cannot open undo file for reading: %s"), file_name); - goto error; - } - - /* Begin overall file information */ - magic = get2c(fp); - if (magic != UF_START_MAGIC) - { - EMSG2(_(e_corrupted), file_name); - goto error; - } - version = get2c(fp); - if (version != UF_VERSION) - { - EMSG2(_("E824: Incompatible undo file: %s"), file_name); - goto error; - } - - if (fread(read_hash, UNDO_HASH_SIZE, 1, fp) != 1) - { - EMSG2(_(e_corrupted), file_name); - goto error; - } - line_count = (linenr_T)get4c(fp); - if (memcmp(hash, read_hash, UNDO_HASH_SIZE) != 0 - || line_count != curbuf->b_ml.ml_line_count) - { - if (p_verbose > 0 || name != NULL) - { - verbose_enter(); - give_warning((char_u *)_("File contents changed, cannot use undo info"), TRUE); - verbose_leave(); - } - goto error; - } - - /* Begin undo data for U */ - str_len = get4c(fp); - if (str_len < 0) - goto error; - else if (str_len > 0) - { - if ((line_ptr = U_ALLOC_LINE(str_len + 1)) == NULL) - goto error; - for (i = 0; i < str_len; i++) - line_ptr[i] = (char_u)getc(fp); - line_ptr[i] = NUL; - } - line_lnum = (linenr_T)get4c(fp); - line_colnr = (colnr_T)get4c(fp); - - /* Begin general undo data */ - old_header_seq = get4c(fp); - new_header_seq = get4c(fp); - cur_header_seq = get4c(fp); - num_head = get4c(fp); - seq_last = get4c(fp); - seq_cur = get4c(fp); - seq_time = get8ctime(fp); - - /* uhp_table will store the freshly created undo headers we allocate - * until we insert them into curbuf. The table remains sorted by the - * sequence numbers of the headers. - * When there are no headers uhp_table is NULL. */ - if (num_head > 0) - { - uhp_table = (u_header_T **)U_ALLOC_LINE( - num_head * sizeof(u_header_T *)); - if (uhp_table == NULL) - goto error; - vim_memset(uhp_table, 0, num_head * sizeof(u_header_T *)); - } - - c = get2c(fp); - while (c == UF_HEADER_MAGIC) - { - if (num_read_uhps >= num_head) - { - EMSG2(_("E831 Undo file corruption: num_head: %s"), file_name); - u_free_uhp(uhp); - goto error; - } - - uhp = (u_header_T *)U_ALLOC_LINE(sizeof(u_header_T)); - if (uhp == NULL) - goto error; - vim_memset(uhp, 0, sizeof(u_header_T)); - /* We're not actually trying to store pointers here. We're just storing - * IDs so we can swizzle them into pointers later - hence the type - * cast. */ - uhp->uh_next = (u_header_T *)(long_u)get4c(fp); - uhp->uh_prev = (u_header_T *)(long_u)get4c(fp); - uhp->uh_alt_next = (u_header_T *)(long_u)get4c(fp); - uhp->uh_alt_prev = (u_header_T *)(long_u)get4c(fp); - uhp->uh_seq = get4c(fp); - if (uhp->uh_seq <= 0) - { - EMSG2(_("E825: Undo file corruption: invalid uh_seq.: %s"), - file_name); - vim_free(uhp); - goto error; - } - uhp->uh_walk = 0; - unserialize_pos(&uhp->uh_cursor, fp); -#ifdef FEAT_VIRTUALEDIT - uhp->uh_cursor_vcol = get4c(fp); -#else - (void)get4c(fp); -#endif - uhp->uh_flags = get2c(fp); - for (i = 0; i < NMARKS; ++i) - unserialize_pos(&uhp->uh_namedm[i], fp); -#ifdef FEAT_VISUAL - unserialize_visualinfo(&uhp->uh_visual, fp); -#else - { - visualinfo_T info; - unserialize_visualinfo(&info, fp); - } -#endif - uhp->uh_time = get8ctime(fp); - - /* Unserialize uep list. The first 4 bytes is the length of the - * entire uep in bytes minus the length of the strings within. - * -1 is a sentinel value meaning no more ueps.*/ - last_uep = NULL; - while ((uep_len = get4c(fp)) != -1) - { - uep = (u_entry_T *)U_ALLOC_LINE(sizeof(u_entry_T)); - if (uep == NULL) - { - u_free_uhp(uhp); - goto error; - } - vim_memset(uep, 0, sizeof(u_entry_T)); - if (last_uep == NULL) - uhp->uh_entry = uep; - else - last_uep->ue_next = uep; - last_uep = uep; - - uep->ue_top = get4c(fp); - uep->ue_bot = get4c(fp); - uep->ue_lcount = get4c(fp); - uep->ue_size = get4c(fp); - uep->ue_next = NULL; - if (uep->ue_size > 0) - { - array = (char_u **)U_ALLOC_LINE( - sizeof(char_u *) * uep->ue_size); - if (array == NULL) - { - u_free_uhp(uhp); - goto error; - } - vim_memset(array, 0, sizeof(char_u *) * uep->ue_size); - } - uep->ue_array = array; - - for (i = 0; i < uep->ue_size; i++) - { - line_len = get4c(fp); - if (line_len >= 0) - line = (char_u *)U_ALLOC_LINE(line_len + 1); - else - line = NULL; - if (line == NULL) - { - u_free_uhp(uhp); - goto error; - } - for (j = 0; j < line_len; j++) - line[j] = getc(fp); - line[j] = '\0'; - array[i] = line; - } - } - - /* Insertion sort the uhp into the table by its uh_seq. This is - * required because, while the number of uhps is limited to - * num_head, and the uh_seq order is monotonic with respect to - * creation time, the starting uh_seq can be > 0 if any undolevel - * culling was done at undofile write time, and there can be uh_seq - * gaps in the uhps. - */ - for (i = num_read_uhps - 1; i >= -1; i--) - { - /* if i == -1, we've hit the leftmost side of the table, so insert - * at uhp_table[0]. */ - if (i == -1 || uhp->uh_seq > uhp_table[i]->uh_seq) - { - /* If we've had to move from the rightmost side of the table, - * we have to shift everything to the right by one spot. */ - if (num_read_uhps - i - 1 > 0) - { - memmove(uhp_table + i + 2, uhp_table + i + 1, - (num_read_uhps - i - 1) * sizeof(u_header_T *)); - } - uhp_table[i + 1] = uhp; - break; - } - else if (uhp->uh_seq == uhp_table[i]->uh_seq) - { - EMSG2(_("E826 Undo file corruption: duplicate uh_seq: %s"), - file_name); - u_free_uhp(uhp); - goto error; - } - } - num_read_uhps++; - c = get2c(fp); - } - - if (c != UF_END_MAGIC) - { - EMSG2(_("E827: Undo file corruption; no end marker: %s"), file_name); - goto error; - } - - /* We've organized all of the uhps into a table sorted by uh_seq. Now we - * iterate through the table and swizzle each sequence number we've - * stored in uh_foo into a pointer corresponding to the header with that - * sequence number. Then free curbuf's old undo structure, give curbuf - * the updated {old,new,cur}head pointers, and then free the table. */ - for (i = 0; i < num_head; i++) - { - uhp = uhp_table[i]; - if (uhp == NULL) - continue; - for (j = 0; j < num_head; j++) - { - if (uhp_table[j] == NULL) - continue; - if (uhp_table[j]->uh_seq == (long)uhp->uh_next) - uhp->uh_next = uhp_table[j]; - if (uhp_table[j]->uh_seq == (long)uhp->uh_prev) - uhp->uh_prev = uhp_table[j]; - if (uhp_table[j]->uh_seq == (long)uhp->uh_alt_next) - uhp->uh_alt_next = uhp_table[j]; - if (uhp_table[j]->uh_seq == (long)uhp->uh_alt_prev) - uhp->uh_alt_prev = uhp_table[j]; - } - if (old_header_seq > 0 && old_idx < 0 && uhp->uh_seq == old_header_seq) - old_idx = i; - if (new_header_seq > 0 && new_idx < 0 && uhp->uh_seq == new_header_seq) - new_idx = i; - if (cur_header_seq > 0 && cur_idx < 0 && uhp->uh_seq == cur_header_seq) - cur_idx = i; - } - u_blockfree(curbuf); - curbuf->b_u_oldhead = old_idx < 0 ? 0 : uhp_table[old_idx]; - curbuf->b_u_newhead = new_idx < 0 ? 0 : uhp_table[new_idx]; - curbuf->b_u_curhead = cur_idx < 0 ? 0 : uhp_table[cur_idx]; - curbuf->b_u_line_ptr = line_ptr; - curbuf->b_u_line_lnum = line_lnum; - curbuf->b_u_line_colnr = line_colnr; - curbuf->b_u_numhead = num_head; - curbuf->b_u_seq_last = seq_last; - curbuf->b_u_seq_cur = seq_cur; - curbuf->b_u_seq_time = seq_time; - vim_free(uhp_table); -#ifdef U_DEBUG - u_check(TRUE); -#endif - if (name != NULL) - smsg((char_u *)_("Finished reading undo file %s"), file_name); - goto theend; - -error: - vim_free(line_ptr); - if (uhp_table != NULL) - { - for (i = 0; i < num_head; i++) - if (uhp_table[i] != NULL) - u_free_uhp(uhp_table[i]); - vim_free(uhp_table); - } - -theend: - if (fp != NULL) - fclose(fp); - if (file_name != name) - vim_free(file_name); - return; + EMSG3(_("E825: Corrupted undo file (%s): %s"), msg, file_name); } static void @@ -1160,44 +806,83 @@ serialize_uep(uep, fp) u_entry_T *uep; FILE *fp; { - int i; - int uep_len; - int *entry_lens; - - if (uep->ue_size > 0) - entry_lens = (int *)alloc(uep->ue_size * sizeof(int)); - else - entry_lens = NULL; - - /* Define uep_len to be the size of the entire uep minus the size of its - * component strings, in bytes. The sizes of the component strings - * are written before each individual string. - * We have 4 entries each of 4 bytes, plus ue_size * 4 bytes - * of string size information. */ + int i; + size_t len; - uep_len = uep->ue_size * 4; - /* Collect sizing information for later serialization. */ - for (i = 0; i < uep->ue_size; i++) - { - entry_lens[i] = (int)STRLEN(uep->ue_array[i]); - uep_len += entry_lens[i]; - } - put_bytes(fp, (long_u)uep_len, 4); 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++) + for (i = 0; i < uep->ue_size; ++i) { - if (put_bytes(fp, (long_u)entry_lens[i], 4) == FAIL) + len = STRLEN(uep->ue_array[i]); + if (put_bytes(fp, (long_u)len, 4) == FAIL) + return FAIL; + if (len > 0 && fwrite(uep->ue_array[i], len, (size_t)1, fp) != 1) return FAIL; - fprintf(fp, "%s", uep->ue_array[i]); } - if (uep->ue_size > 0) - vim_free(entry_lens); return OK; } + static u_entry_T * +unserialize_uep(fp, error, file_name) + FILE *fp; + int *error; + char_u *file_name; +{ + int i; + int j; + u_entry_T *uep; + char_u **array; + char_u *line; + int line_len; + + uep = (u_entry_T *)U_ALLOC_LINE(sizeof(u_entry_T)); + if (uep == NULL) + return NULL; + vim_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 = (char_u **)U_ALLOC_LINE(sizeof(char_u *) * uep->ue_size); + if (array == NULL) + { + *error = TRUE; + return uep; + } + vim_memset(array, 0, sizeof(char_u *) * uep->ue_size); + } + uep->ue_array = array; + + for (i = 0; i < uep->ue_size; ++i) + { + line_len = get4c(fp); + if (line_len >= 0) + line = (char_u *)U_ALLOC_LINE(line_len + 1); + else + { + line = NULL; + corruption_error("line length", file_name); + } + if (line == NULL) + { + *error = TRUE; + return uep; + } + for (j = 0; j < line_len; j++) + line[j] = getc(fp); + line[j] = NUL; + array[i] = line; + } + return uep; +} + /* * Serialize "pos" to "fp". */ @@ -1216,6 +901,23 @@ serialize_pos(pos, fp) } /* + * Unserialize the pos_T at the current position in fp. + */ + static void +unserialize_pos(pos, fp) + pos_T *pos; + FILE *fp; +{ + pos->lnum = get4c(fp); + pos->col = get4c(fp); +#ifdef FEAT_VIRTUALEDIT + pos->coladd = get4c(fp); +#else + (void)get4c(fp); +#endif +} + +/* * Serialize "info" to "fp". */ static void @@ -1230,6 +932,32 @@ serialize_visualinfo(info, fp) } /* + * Unserialize the visualinfo_T at the current position in fp. + */ + static void +unserialize_visualinfo(info, fp) + 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(fp, uhp) + 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. @@ -1248,7 +976,10 @@ u_write_undo(name, forceit, buf, hash) u_header_T *uhp; u_entry_T *uep; char_u *file_name; - int str_len, i, uep_len, mark; + int str_len, i, mark; +#ifdef U_DEBUG + int headers_written = 0; +#endif int fd; FILE *fp = NULL; int perm; @@ -1263,14 +994,22 @@ u_write_undo(name, forceit, buf, hash) { file_name = u_get_undo_file_name(buf->b_ffname, FALSE); if (file_name == NULL) + { + if (p_verbose > 0) + smsg((char_u *)_("Cannot write undo file in any directory in 'undodir'")); return; + } } else file_name = name; - if (buf->b_ffname == NULL) - perm = 0600; - else + /* + * Decide about the permission to use for the undo file. If the buffer + * has a name use the permission of the original file. Otherwise only + * allow the user to access the undo file. + */ + perm = 0600; + if (buf->b_ffname != NULL) { #ifdef UNIX if (mch_stat((char *)buf->b_ffname, &st_old) >= 0) @@ -1278,8 +1017,6 @@ u_write_undo(name, forceit, buf, hash) perm = st_old.st_mode; st_old_valid = TRUE; } - else - perm = 0600; #else perm = mch_getperm(buf->b_ffname); if (perm < 0) @@ -1287,11 +1024,11 @@ u_write_undo(name, forceit, buf, hash) #endif } - /* set file protection same as original file, but strip s-bit */ + /* strip any s-bit */ perm = perm & 0777; - /* If the undo file exists, verify that it actually is an undo file, and - * delete it. */ + /* If the undo file already exists, verify that it actually is an undo + * file, and delete it. */ if (mch_getperm(file_name) >= 0) { if (name == NULL || !forceit) @@ -1307,12 +1044,13 @@ u_write_undo(name, forceit, buf, hash) } else { - char_u buf[2]; + char_u buf[UF_START_MAGIC_LEN]; int len; - len = vim_read(fd, buf, 2); + len = vim_read(fd, buf, UF_START_MAGIC_LEN); close(fd); - if (len < 2 || (buf[0] << 8) + buf[1] != UF_START_MAGIC) + if (len < UF_START_MAGIC_LEN + || memcmp(buf, UF_START_MAGIC, UF_START_MAGIC_LEN) != 0) { if (name != NULL || p_verbose > 0) smsg((char_u *)_("Will not overwrite, this is not an undo file: %s"), @@ -1326,12 +1064,12 @@ u_write_undo(name, forceit, buf, hash) fd = mch_open((char *)file_name, O_CREAT|O_EXTRA|O_WRONLY|O_EXCL|O_NOFOLLOW, perm); - (void)mch_setperm(file_name, perm); if (fd < 0) { EMSG2(_(e_not_open), file_name); goto theend; } + (void)mch_setperm(file_name, perm); if (p_verbose > 0) smsg((char_u *)_("Writing undo file: %s"), file_name); @@ -1363,8 +1101,9 @@ u_write_undo(name, forceit, buf, hash) goto theend; } - /* Start writing, first overall file information */ - put_bytes(fp, (long_u)UF_START_MAGIC, 2); + /* Start writing, first the undo file header. */ + if (fwrite(UF_START_MAGIC, (size_t)UF_START_MAGIC_LEN, (size_t)1, fp) != 1) + goto write_error; put_bytes(fp, (long_u)UF_VERSION, 2); /* Write a hash of the buffer text, so that we can verify it is still the @@ -1384,21 +1123,18 @@ u_write_undo(name, forceit, buf, hash) put_bytes(fp, (long_u)buf->b_u_line_colnr, 4); /* Begin general undo data */ - uhp = buf->b_u_oldhead; - put_bytes(fp, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4); - - uhp = buf->b_u_newhead; - put_bytes(fp, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4); - - uhp = buf->b_u_curhead; - put_bytes(fp, (long_u)(uhp != NULL ? uhp->uh_seq : 0), 4); + 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_seq_time); - /* Iteratively serialize UHPs and their UEPs from the top down. */ + /* + * Iteratively serialize UHPs and their UEPs from the top down. + */ mark = ++lastmark; uhp = buf->b_u_oldhead; while (uhp != NULL) @@ -1406,17 +1142,18 @@ u_write_undo(name, forceit, buf, hash) /* Serialize current UHP if we haven't seen it */ if (uhp->uh_walk != mark) { + uhp->uh_walk = mark; +#ifdef U_DEBUG + ++headers_written; +#endif + if (put_bytes(fp, (long_u)UF_HEADER_MAGIC, 2) == FAIL) goto write_error; - put_bytes(fp, (long_u)((uhp->uh_next != NULL) - ? uhp->uh_next->uh_seq : 0), 4); - put_bytes(fp, (long_u)((uhp->uh_prev != NULL) - ? uhp->uh_prev->uh_seq : 0), 4); - put_bytes(fp, (long_u)((uhp->uh_alt_next != NULL) - ? uhp->uh_alt_next->uh_seq : 0), 4); - put_bytes(fp, (long_u)((uhp->uh_alt_prev != NULL) - ? uhp->uh_alt_prev->uh_seq : 0), 4); + put_header_ptr(fp, uhp->uh_next); + put_header_ptr(fp, uhp->uh_prev); + put_header_ptr(fp, uhp->uh_alt_next); + put_header_ptr(fp, uhp->uh_alt_prev); put_bytes(fp, uhp->uh_seq, 4); serialize_pos(uhp->uh_cursor, fp); #ifdef FEAT_VIRTUALEDIT @@ -1440,17 +1177,14 @@ u_write_undo(name, forceit, buf, hash) #endif put_time(fp, uhp->uh_time); - uep = uhp->uh_entry; - while (uep != NULL) - { + /* 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(uep, fp) == FAIL) goto write_error; - uep = uep->ue_next; - } - /* Sentinel value: no more ueps */ - uep_len = -1; - put_bytes(fp, (long_u)uep_len, 4); - uhp->uh_walk = mark; + } + put_bytes(fp, (long_u)UF_ENTRY_END_MAGIC, 2); } /* Now walk through the tree - algorithm from undo_time */ @@ -1467,8 +1201,13 @@ u_write_undo(name, forceit, buf, hash) uhp = uhp->uh_next; } - if (put_bytes(fp, (long_u)UF_END_MAGIC, 2) == OK) + if (put_bytes(fp, (long_u)UF_HEADER_END_MAGIC, 2) == OK) write_ok = TRUE; +#ifdef U_DEBUG + if (headers_written != buf->b_u_numhead) + EMSG3("Written %ld headers, but numhead is %ld", + headers_written, buf->b_u_numhead); +#endif write_error: fclose(fp); @@ -1495,6 +1234,351 @@ theend: vim_free(file_name); } +/* + * Load the undo tree from an undo file. + * If "name" is not NULL use it as the undo file name. This also means being + * a bit more verbose. + * Otherwise use curbuf->b_ffname to generate the undo file name. + * "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. + */ + void +u_read_undo(name, hash) + char_u *name; + char_u *hash; +{ + char_u *file_name; + FILE *fp; + long version, str_len; + char_u *line_ptr = NULL; + linenr_T line_lnum; + colnr_T line_colnr; + linenr_T line_count; + int num_head = 0; + long old_header_seq, new_header_seq, cur_header_seq; + long seq_last, seq_cur; + short old_idx = -1, new_idx = -1, cur_idx = -1; + long num_read_uhps = 0; + time_t seq_time; + int i, j; + int c; + u_entry_T *uep, *last_uep; + u_header_T *uhp; + u_header_T **uhp_table = NULL; + char_u read_hash[UNDO_HASH_SIZE]; + char_u magic_buf[UF_START_MAGIC_LEN]; +#ifdef U_DEBUG + int *uhp_table_used; +#endif + + if (name == NULL) + { + file_name = u_get_undo_file_name(curbuf->b_ffname, TRUE); + if (file_name == NULL) + return; + } + else + file_name = name; + + if (p_verbose > 0) + smsg((char_u *)_("Reading undo file: %s"), file_name); + fp = mch_fopen((char *)file_name, "r"); + if (fp == NULL) + { + if (name != NULL || p_verbose > 0) + EMSG2(_("E822: Cannot open undo file for reading: %s"), file_name); + goto error; + } + + /* + * Read the undo file header. + */ + if (fread(magic_buf, UF_START_MAGIC_LEN, 1, fp) != 1 + || memcmp(magic_buf, UF_START_MAGIC, UF_START_MAGIC_LEN) != 0) + { + EMSG2(_("E823: Not an undo file: %s"), file_name); + goto error; + } + version = get2c(fp); + if (version != UF_VERSION) + { + EMSG2(_("E824: Incompatible undo file: %s"), file_name); + goto error; + } + + if (fread(read_hash, UNDO_HASH_SIZE, 1, fp) != 1) + { + corruption_error("hash", file_name); + goto error; + } + line_count = (linenr_T)get4c(fp); + if (memcmp(hash, read_hash, UNDO_HASH_SIZE) != 0 + || line_count != curbuf->b_ml.ml_line_count) + { + if (p_verbose > 0 || name != NULL) + { + verbose_enter(); + give_warning((char_u *)_("File contents changed, cannot use undo info"), TRUE); + verbose_leave(); + } + goto error; + } + + /* Begin undo data for U */ + str_len = get4c(fp); + if (str_len < 0) + goto error; + else if (str_len > 0) + { + if ((line_ptr = U_ALLOC_LINE(str_len + 1)) == NULL) + goto error; + for (i = 0; i < str_len; i++) + line_ptr[i] = (char_u)getc(fp); + line_ptr[i] = NUL; + } + line_lnum = (linenr_T)get4c(fp); + line_colnr = (colnr_T)get4c(fp); + + /* Begin general undo data */ + old_header_seq = get4c(fp); + new_header_seq = get4c(fp); + cur_header_seq = get4c(fp); + num_head = get4c(fp); + seq_last = get4c(fp); + seq_cur = get4c(fp); + seq_time = get8ctime(fp); + + /* uhp_table will store the freshly created undo headers we allocate + * until we insert them into curbuf. The table remains sorted by the + * sequence numbers of the headers. + * When there are no headers uhp_table is NULL. */ + if (num_head > 0) + { + uhp_table = (u_header_T **)U_ALLOC_LINE( + num_head * sizeof(u_header_T *)); + if (uhp_table == NULL) + goto error; + vim_memset(uhp_table, 0, num_head * sizeof(u_header_T *)); + } + + while ((c = get2c(fp)) == UF_HEADER_MAGIC) + { + if (num_read_uhps >= num_head) + { + corruption_error("num_head", file_name); + u_free_uhp(uhp); + goto error; + } + + uhp = (u_header_T *)U_ALLOC_LINE(sizeof(u_header_T)); + if (uhp == NULL) + goto error; + vim_memset(uhp, 0, sizeof(u_header_T)); +#ifdef U_DEBUG + uhp->uh_magic = UH_MAGIC; +#endif + /* We're not actually trying to store pointers here. We're just storing + * IDs so we can swizzle them into pointers later - hence the type + * cast. */ + uhp->uh_next = (u_header_T *)(long_u)get4c(fp); + uhp->uh_prev = (u_header_T *)(long_u)get4c(fp); + uhp->uh_alt_next = (u_header_T *)(long_u)get4c(fp); + uhp->uh_alt_prev = (u_header_T *)(long_u)get4c(fp); + uhp->uh_seq = get4c(fp); + if (uhp->uh_seq <= 0) + { + corruption_error("uh_seq", file_name); + vim_free(uhp); + goto error; + } + uhp->uh_walk = 0; + unserialize_pos(&uhp->uh_cursor, fp); +#ifdef FEAT_VIRTUALEDIT + uhp->uh_cursor_vcol = get4c(fp); +#else + (void)get4c(fp); +#endif + uhp->uh_flags = get2c(fp); + for (i = 0; i < NMARKS; ++i) + unserialize_pos(&uhp->uh_namedm[i], fp); +#ifdef FEAT_VISUAL + unserialize_visualinfo(&uhp->uh_visual, fp); +#else + { + visualinfo_T info; + unserialize_visualinfo(&info, fp); + } +#endif + uhp->uh_time = get8ctime(fp); + + /* Unserialize the uep list. */ + last_uep = NULL; + while ((c = get2c(fp)) == UF_ENTRY_MAGIC) + { + int 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); + goto error; + } + } + if (c != UF_ENTRY_END_MAGIC) + { + corruption_error("entry end", file_name); + u_free_uhp(uhp); + goto error; + } + + /* Insertion sort the uhp into the table by its uh_seq. This is + * required because, while the number of uhps is limited to + * num_head, and the uh_seq order is monotonic with respect to + * creation time, the starting uh_seq can be > 0 if any undolevel + * culling was done at undofile write time, and there can be uh_seq + * gaps in the uhps. + */ + for (i = num_read_uhps - 1; i >= -1; i--) + { + /* if i == -1, we've hit the leftmost side of the table, so insert + * at uhp_table[0]. */ + if (i == -1 || uhp->uh_seq > uhp_table[i]->uh_seq) + { + /* If we've had to move from the rightmost side of the table, + * we have to shift everything to the right by one spot. */ + if (num_read_uhps - i - 1 > 0) + { + memmove(uhp_table + i + 2, uhp_table + i + 1, + (num_read_uhps - i - 1) * sizeof(u_header_T *)); + } + uhp_table[i + 1] = uhp; + break; + } + else if (uhp->uh_seq == uhp_table[i]->uh_seq) + { + corruption_error("duplicate uh_seq", file_name); + u_free_uhp(uhp); + goto error; + } + } + num_read_uhps++; + } + + if (c != UF_HEADER_END_MAGIC) + { + corruption_error("end marker", file_name); + goto error; + } + +#ifdef U_DEBUG + uhp_table_used = (int *)alloc_clear( + (unsigned)(sizeof(int) * num_head + 1)); +# define SET_FLAG(j) ++uhp_table_used[j] +#else +# define SET_FLAG(j) +#endif + + /* We've organized all of the uhps into a table sorted by uh_seq. Now we + * iterate through the table and swizzle each sequence number we've + * stored in uh_* into a pointer corresponding to the header with that + * sequence number. Then free curbuf's old undo structure, give curbuf + * the updated {old,new,cur}head pointers, and then free the table. */ + for (i = 0; i < num_head; i++) + { + uhp = uhp_table[i]; + if (uhp == NULL) + continue; + for (j = 0; j < num_head; j++) + { + if (uhp_table[j] == NULL) + continue; + if (uhp_table[j]->uh_seq == (long)uhp->uh_next) + { + uhp->uh_next = uhp_table[j]; + SET_FLAG(j); + } + if (uhp_table[j]->uh_seq == (long)uhp->uh_prev) + { + uhp->uh_prev = uhp_table[j]; + SET_FLAG(j); + } + if (uhp_table[j]->uh_seq == (long)uhp->uh_alt_next) + { + uhp->uh_alt_next = uhp_table[j]; + SET_FLAG(j); + } + if (uhp_table[j]->uh_seq == (long)uhp->uh_alt_prev) + { + uhp->uh_alt_prev = uhp_table[j]; + SET_FLAG(j); + } + } + if (old_header_seq > 0 && old_idx < 0 && uhp->uh_seq == old_header_seq) + { + old_idx = i; + SET_FLAG(i); + } + if (new_header_seq > 0 && new_idx < 0 && uhp->uh_seq == new_header_seq) + { + new_idx = i; + SET_FLAG(i); + } + if (cur_header_seq > 0 && cur_idx < 0 && uhp->uh_seq == cur_header_seq) + { + cur_idx = i; + SET_FLAG(i); + } + } + + /* Now that we have read the undo info successfully, free the current undo + * info and use the info from the file. */ + u_blockfree(curbuf); + curbuf->b_u_oldhead = old_idx < 0 ? NULL : uhp_table[old_idx]; + curbuf->b_u_newhead = new_idx < 0 ? NULL : uhp_table[new_idx]; + curbuf->b_u_curhead = cur_idx < 0 ? NULL : uhp_table[cur_idx]; + curbuf->b_u_line_ptr = line_ptr; + curbuf->b_u_line_lnum = line_lnum; + curbuf->b_u_line_colnr = line_colnr; + curbuf->b_u_numhead = num_head; + curbuf->b_u_seq_last = seq_last; + curbuf->b_u_seq_cur = seq_cur; + curbuf->b_u_seq_time = seq_time; + vim_free(uhp_table); + +#ifdef U_DEBUG + for (i = 0; i < num_head; ++i) + if (uhp_table_used[i] == 0) + EMSGN("uhp_table entry %ld not used, leaking memory", i); + vim_free(uhp_table_used); + u_check(TRUE); +#endif + + if (name != NULL) + smsg((char_u *)_("Finished reading undo file %s"), file_name); + goto theend; + +error: + vim_free(line_ptr); + if (uhp_table != NULL) + { + for (i = 0; i < num_head; i++) + if (uhp_table[i] != NULL) + u_free_uhp(uhp_table[i]); + vim_free(uhp_table); + } + +theend: + if (fp != NULL) + fclose(fp); + if (file_name != name) + vim_free(file_name); + return; +} + #endif /* FEAT_PERSISTENT_UNDO */ |