summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2010-05-29 20:33:07 +0200
committerBram Moolenaar <Bram@vim.org>2010-05-29 20:33:07 +0200
commit9db580634c0055674017eab535b1b9eec7d6939d (patch)
treed34ad271b13df816deeeae7622757b8dd385cf1a /src
parentf05e3b0220a6b68791b5563ddf67ad42dbf74ee2 (diff)
downloadvim-git-9db580634c0055674017eab535b1b9eec7d6939d.tar.gz
Various improvements to undo file code to make it more robust.
Diffstat (limited to 'src')
-rw-r--r--src/misc2.c6
-rw-r--r--src/proto/undo.pro1
-rw-r--r--src/undo.c968
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 */