changeset 2231:aa6412cab544 vim73

Various improvements to undo file code to make it more robust.
author Bram Moolenaar <bram@vim.org>
date Sat, 29 May 2010 20:33:07 +0200
parents 290ee42cae85
children 2e6906bbc5f4
files runtime/doc/tags runtime/doc/todo.txt runtime/doc/undo.txt runtime/syntax/sed.vim src/misc2.c src/proto/undo.pro src/undo.c
diffstat 7 files changed, 563 insertions(+), 483 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -4170,13 +4170,10 @@ E822	undo.txt	/*E822*
 E823	undo.txt	/*E823*
 E824	undo.txt	/*E824*
 E825	undo.txt	/*E825*
-E826	undo.txt	/*E826*
-E827	undo.txt	/*E827*
 E828	undo.txt	/*E828*
 E829	undo.txt	/*E829*
 E83	message.txt	/*E83*
 E830	undo.txt	/*E830*
-E831	undo.txt	/*E831*
 E84	windows.txt	/*E84*
 E85	options.txt	/*E85*
 E86	windows.txt	/*E86*
--- a/runtime/doc/todo.txt
+++ b/runtime/doc/todo.txt
@@ -1097,6 +1097,7 @@ Vim 7.3:
     - When there is no undo info (undolevels negative), delete the undo file.
     - Need to check all values for evil manipulation.
     - Add undofile(name): get undo file name for buffer "name".
+- patch for unused functions. (Dominique Pelle, 2010 May 29)
 - Also crypt the undo file.
 - Also crypt the swap file, each block separately.  Change mf_write() and
     mf_read().  How to get b_p_key to these functions?
--- a/runtime/doc/undo.txt
+++ b/runtime/doc/undo.txt
@@ -267,10 +267,7 @@ Reading an existing undo file may fail f
 	The file text differs from when the undo file was written.  This means
 	the undo file cannot be used, it would corrupt the text.  This also
 	happens when 'encoding' differs from when the undo file was written.
-*E825* *E826* *E831*
-	The undo file does not contain valid contents and cannot be used.
-*E827*	The magic number at the end of the file was not found.  This usually
-	means the file was truncated.
+*E825*  The undo file does not contain valid contents and cannot be used.
 
 Writing an undo file may fail for these reasons:
 *E828*	The file to be written cannot be created.  Perhaps you do not have
--- a/runtime/syntax/sed.vim
+++ b/runtime/syntax/sed.vim
@@ -2,7 +2,7 @@
 " Language:	sed
 " Maintainer:	Haakon Riiser <hakonrk@fys.uio.no>
 " URL:		http://folk.uio.no/hakonrk/vim/syntax/sed.vim
-" Last Change:	2005 Dec 15
+" Last Change:	2010 May 29
 
 " For version 5.x: Clear all syntax items
 " For version 6.x: Quit when a syntax file was already loaded
@@ -49,7 +49,7 @@ syn match sedReplaceMeta    "&\|\\\($\|.
 " Metacharacters: $ * . \ ^ [ ~
 " @ is used as delimiter and treated on its own below
 let __at = char2nr("@")
-let __sed_i = char2nr(" ")
+let __sed_i = char2nr(" ") " ASCII: 32, EBCDIC: 64
 if has("ebcdic")
     let __sed_last = 255
 else
@@ -105,8 +105,8 @@ if version >= 508 || !exists("did_sed_sy
     if exists("highlight_sedtabs")
 	HiLink sedTab		Todo
     endif
-    let __sed_i = 32
-    while __sed_i <= 126
+    let __sed_i = char2nr(" ") " ASCII: 32, EBCDIC: 64
+    while __sed_i <= __sed_last
 	exe "HiLink sedRegexp".__sed_i		"Macro"
 	exe "HiLink sedReplacement".__sed_i	"NONE"
 	let __sed_i = __sed_i + 1
@@ -115,7 +115,7 @@ if version >= 508 || !exists("did_sed_sy
     delcommand HiLink
 endif
 
-unlet __sed_i __sed_delimiter __sed_metacharacters
+unlet __sed_i __sed_last __sed_delimiter __sed_metacharacters
 
 let b:current_syntax = "sed"
 
--- 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);
--- 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));
--- a/src/undo.c
+++ b/src/undo.c
@@ -100,13 +100,16 @@ static void u_freebranch __ARGS((buf_T *
 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,6 +773,467 @@ u_get_undo_file_name(buf_ffname, reading
     return undo_file_name;
 }
 
+    static void
+corruption_error(msg, file_name)
+    char *msg;
+    char_u *file_name;
+{
+    EMSG3(_("E825: Corrupted undo file (%s): %s"), msg, file_name);
+}
+
+    static void
+u_free_uhp(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;
+    }
+    vim_free(uhp);
+}
+
+/*
+ * Serialize "uep" to "fp".
+ */
+    static int
+serialize_uep(uep, fp)
+    u_entry_T	*uep;
+    FILE	*fp;
+{
+    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(uep->ue_array[i], len, (size_t)1, fp) != 1)
+	    return FAIL;
+    }
+    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".
+ */
+    static void
+serialize_pos(pos, fp)
+    pos_T pos;
+    FILE  *fp;
+{
+    put_bytes(fp, (long_u)pos.lnum, 4);
+    put_bytes(fp, (long_u)pos.col, 4);
+#ifdef FEAT_VIRTUALEDIT
+    put_bytes(fp, (long_u)pos.coladd, 4);
+#else
+    put_bytes(fp, (long_u)0, 4);
+#endif
+}
+
+/*
+ * 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
+serialize_visualinfo(info, fp)
+    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(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.
+ * "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(name, forceit, buf, hash)
+    char_u	*name;
+    int		forceit;
+    buf_T	*buf;
+    char_u	*hash;
+{
+    u_header_T	*uhp;
+    u_entry_T	*uep;
+    char_u	*file_name;
+    int		str_len, i, mark;
+#ifdef U_DEBUG
+    int		headers_written = 0;
+#endif
+    int		fd;
+    FILE	*fp = NULL;
+    int		perm;
+    int		write_ok = FALSE;
+#ifdef UNIX
+    int		st_old_valid = FALSE;
+    struct stat	st_old;
+    struct stat	st_new;
+#endif
+
+    if (name == NULL)
+    {
+        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;
+
+    /*
+     * 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)
+	{
+	    perm = st_old.st_mode;
+	    st_old_valid = TRUE;
+	}
+#else
+	perm = mch_getperm(buf->b_ffname);
+	if (perm < 0)
+	    perm = 0600;
+#endif
+    }
+
+    /* strip any s-bit */
+    perm = perm & 0777;
+
+    /* 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)
+	{
+	    /* Check we can read it and it's an undo file. */
+	    fd = mch_open((char *)file_name, O_RDONLY|O_EXTRA, 0);
+	    if (fd < 0)
+	    {
+		if (name != NULL || p_verbose > 0)
+		    smsg((char_u *)_("Will not overwrite with undo file, cannot read: %s"),
+								   file_name);
+		goto theend;
+	    }
+	    else
+	    {
+		char_u	buf[UF_START_MAGIC_LEN];
+		int	len;
+
+		len = vim_read(fd, buf, UF_START_MAGIC_LEN);
+		close(fd);
+		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"),
+								   file_name);
+		    goto theend;
+		}
+	    }
+	}
+	mch_remove(file_name);
+    }
+
+    fd = mch_open((char *)file_name,
+			    O_CREAT|O_EXTRA|O_WRONLY|O_EXCL|O_NOFOLLOW, 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);
+
+#ifdef UNIX
+    /*
+     * Try to set the group of the undo file same as the original file. If
+     * this fails, set the protection bits for the group same as the
+     * protection bits for others.
+     */
+    if (st_old_valid && (mch_stat((char *)file_name, &st_new) >= 0
+		&& st_new.st_gid != st_old.st_gid
+# ifdef HAVE_FCHOWN  /* sequent-ptx lacks fchown() */
+		&& fchown(fd, (uid_t)-1, st_old.st_gid) != 0)
+# endif
+       )
+	mch_setperm(file_name, (perm & 0707) | ((perm & 07) << 3));
+# ifdef HAVE_SELINUX
+    if (buf->b_ffname != NULL)
+	mch_copy_sec(buf->b_ffname, file_name);
+# endif
+#endif
+
+    fp = fdopen(fd, "w");
+    if (fp == NULL)
+    {
+        EMSG2(_(e_not_open), file_name);
+	close(fd);
+	mch_remove(file_name);
+	goto theend;
+    }
+
+    /* 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
+     * same when reading the buffer text. */
+    if (fwrite(hash, (size_t)UNDO_HASH_SIZE, (size_t)1, fp) != 1)
+	goto write_error;
+    put_bytes(fp, (long_u)buf->b_ml.ml_line_count, 4);
+
+    /* Begin undo data for U */
+    str_len = buf->b_u_line_ptr != NULL ? (int)STRLEN(buf->b_u_line_ptr) : 0;
+    put_bytes(fp, (long_u)str_len, 4);
+    if (str_len > 0 && fwrite(buf->b_u_line_ptr, (size_t)str_len,
+							  (size_t)1, fp) != 1)
+	goto write_error;
+
+    put_bytes(fp, (long_u)buf->b_u_line_lnum, 4);
+    put_bytes(fp, (long_u)buf->b_u_line_colnr, 4);
+
+    /* Begin general undo 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_seq_time);
+
+    /*
+     * Iteratively serialize UHPs and their UEPs from the top down.
+     */
+    mark = ++lastmark;
+    uhp = buf->b_u_oldhead;
+    while (uhp != NULL)
+    {
+        /* 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_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
+            put_bytes(fp, (long_u)uhp->uh_cursor_vcol, 4);
+#else
+            put_bytes(fp, (long_u)0, 4);
+#endif
+            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);
+#ifdef FEAT_VISUAL
+            serialize_visualinfo(&uhp->uh_visual, fp);
+#else
+	    {
+		visualinfo_T info;
+
+		memset(&info, 0, sizeof(visualinfo_T));
+		serialize_visualinfo(&info, fp);
+	    }
+#endif
+            put_time(fp, uhp->uh_time);
+
+	    /* 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;
+	    }
+	    put_bytes(fp, (long_u)UF_ENTRY_END_MAGIC, 2);
+        }
+
+        /* Now walk through the tree - algorithm from undo_time */
+        if (uhp->uh_prev != NULL && uhp->uh_prev->uh_walk != mark)
+            uhp = uhp->uh_prev;
+        else if (uhp->uh_alt_next != NULL && uhp->uh_alt_next->uh_walk != mark)
+            uhp = uhp->uh_alt_next;
+        else if (uhp->uh_next != NULL && uhp->uh_alt_prev == NULL
+					     && uhp->uh_next->uh_walk != mark)
+            uhp = uhp->uh_next;
+        else if (uhp->uh_alt_prev != NULL)
+            uhp = uhp->uh_alt_prev;
+        else
+            uhp = uhp->uh_next;
+    }
+
+    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);
+    if (!write_ok)
+        EMSG2(_("E829: write error in undo file: %s"), file_name);
+
+#if defined(MACOS_CLASSIC) || defined(WIN3264)
+    if (buf->b_ffname != NULL)
+	(void)mch_copy_file_attribute(buf->b_ffname, file_name);
+#endif
+#ifdef HAVE_ACL
+    if (buf->b_ffname != NULL)
+    {
+	vim_acl_T	    acl;
+
+	/* For systems that support ACL: get the ACL from the original file. */
+	acl = mch_get_acl(buf->b_ffname);
+	mch_set_acl(file_name, acl);
+    }
+#endif
+
+theend:
+    if (file_name != name)
+	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
@@ -812,13 +1248,11 @@ u_read_undo(name, hash)
 {
     char_u	*file_name;
     FILE	*fp;
-    long	magic, version, str_len;
+    long	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;
@@ -827,12 +1261,14 @@ u_read_undo(name, hash)
     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];
+    char_u	magic_buf[UF_START_MAGIC_LEN];
+#ifdef U_DEBUG
+    int		*uhp_table_used;
+#endif
 
     if (name == NULL)
     {
@@ -853,11 +1289,13 @@ u_read_undo(name, hash)
 	goto error;
     }
 
-    /* Begin overall file information */
-    magic = get2c(fp);
-    if (magic != UF_START_MAGIC)
+    /*
+     * 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(_(e_corrupted), file_name);
+	EMSG2(_("E823: Not an undo file: %s"), file_name);
         goto error;
     }
     version = get2c(fp);
@@ -869,7 +1307,7 @@ u_read_undo(name, hash)
 
     if (fread(read_hash, UNDO_HASH_SIZE, 1, fp) != 1)
     {
-        EMSG2(_(e_corrupted), file_name);
+	corruption_error("hash", file_name);
         goto error;
     }
     line_count = (linenr_T)get4c(fp);
@@ -922,12 +1360,11 @@ u_read_undo(name, hash)
 	vim_memset(uhp_table, 0, num_head * sizeof(u_header_T *));
     }
 
-    c = get2c(fp);
-    while (c == UF_HEADER_MAGIC)
+    while ((c = get2c(fp)) == UF_HEADER_MAGIC)
     {
 	if (num_read_uhps >= num_head)
 	{
-	    EMSG2(_("E831 Undo file corruption: num_head: %s"), file_name);
+	    corruption_error("num_head", file_name);
 	    u_free_uhp(uhp);
 	    goto error;
 	}
@@ -936,6 +1373,9 @@ u_read_undo(name, hash)
         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. */
@@ -946,8 +1386,7 @@ u_read_undo(name, hash)
         uhp->uh_seq = get4c(fp);
         if (uhp->uh_seq <= 0)
         {
-            EMSG2(_("E825: Undo file corruption: invalid uh_seq.: %s"),
-								   file_name);
+	    corruption_error("uh_seq", file_name);
             vim_free(uhp);
             goto error;
         }
@@ -971,60 +1410,29 @@ u_read_undo(name, hash)
 #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.*/
+        /* Unserialize the uep list. */
         last_uep = NULL;
-        while ((uep_len = get4c(fp)) != -1)
+        while ((c = get2c(fp)) == UF_ENTRY_MAGIC)
         {
-            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));
+	    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;
-
-            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)
+	    if (uep == NULL || error)
 	    {
-		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);
+		u_free_uhp(uhp);
+                goto error;
 	    }
-            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;
-            }
+        }
+	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
@@ -1052,25 +1460,31 @@ u_read_undo(name, hash)
             }
             else if (uhp->uh_seq == uhp_table[i]->uh_seq)
             {
-                EMSG2(_("E826 Undo file corruption: duplicate uh_seq: %s"),
-								   file_name);
+		corruption_error("duplicate uh_seq", file_name);
 		u_free_uhp(uhp);
                 goto error;
             }
         }
         num_read_uhps++;
-	c = get2c(fp);
     }
 
-    if (c != UF_END_MAGIC)
+    if (c != UF_HEADER_END_MAGIC)
     {
-	EMSG2(_("E827: Undo file corruption; no end marker: %s"), file_name);
+	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_foo into a pointer corresponding to the header with that
+     * 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++)
@@ -1083,25 +1497,49 @@ u_read_undo(name, hash)
             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 ? 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_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;
@@ -1110,9 +1548,15 @@ u_read_undo(name, hash)
     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;
@@ -1135,366 +1579,6 @@ theend:
     return;
 }
 
-    static void
-u_free_uhp(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;
-    }
-    vim_free(uhp);
-}
-
-/*
- * Serialize "uep" to "fp".
- */
-    static int
-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. */
-
-    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++)
-    {
-        if (put_bytes(fp, (long_u)entry_lens[i], 4) == FAIL)
-	    return FAIL;
-        fprintf(fp, "%s", uep->ue_array[i]);
-    }
-    if (uep->ue_size > 0)
-        vim_free(entry_lens);
-    return OK;
-}
-
-/*
- * Serialize "pos" to "fp".
- */
-    static void
-serialize_pos(pos, fp)
-    pos_T pos;
-    FILE  *fp;
-{
-    put_bytes(fp, (long_u)pos.lnum, 4);
-    put_bytes(fp, (long_u)pos.col, 4);
-#ifdef FEAT_VIRTUALEDIT
-    put_bytes(fp, (long_u)pos.coladd, 4);
-#else
-    put_bytes(fp, (long_u)0, 4);
-#endif
-}
-
-/*
- * Serialize "info" to "fp".
- */
-    static void
-serialize_visualinfo(info, fp)
-    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);
-}
-
-/*
- * 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(name, forceit, buf, hash)
-    char_u	*name;
-    int		forceit;
-    buf_T	*buf;
-    char_u	*hash;
-{
-    u_header_T	*uhp;
-    u_entry_T	*uep;
-    char_u	*file_name;
-    int		str_len, i, uep_len, mark;
-    int		fd;
-    FILE	*fp = NULL;
-    int		perm;
-    int		write_ok = FALSE;
-#ifdef UNIX
-    int		st_old_valid = FALSE;
-    struct stat	st_old;
-    struct stat	st_new;
-#endif
-
-    if (name == NULL)
-    {
-        file_name = u_get_undo_file_name(buf->b_ffname, FALSE);
-	if (file_name == NULL)
-	    return;
-    }
-    else
-        file_name = name;
-
-    if (buf->b_ffname == NULL)
-	perm = 0600;
-    else
-    {
-#ifdef UNIX
-	if (mch_stat((char *)buf->b_ffname, &st_old) >= 0)
-	{
-	    perm = st_old.st_mode;
-	    st_old_valid = TRUE;
-	}
-	else
-	    perm = 0600;
-#else
-	perm = mch_getperm(buf->b_ffname);
-	if (perm < 0)
-	    perm = 0600;
-#endif
-    }
-
-    /* set file protection same as original file, but strip s-bit */
-    perm = perm & 0777;
-
-    /* If the undo file exists, verify that it actually is an undo file, and
-     * delete it. */
-    if (mch_getperm(file_name) >= 0)
-    {
-	if (name == NULL || !forceit)
-	{
-	    /* Check we can read it and it's an undo file. */
-	    fd = mch_open((char *)file_name, O_RDONLY|O_EXTRA, 0);
-	    if (fd < 0)
-	    {
-		if (name != NULL || p_verbose > 0)
-		    smsg((char_u *)_("Will not overwrite with undo file, cannot read: %s"),
-								   file_name);
-		goto theend;
-	    }
-	    else
-	    {
-		char_u	buf[2];
-		int	len;
-
-		len = vim_read(fd, buf, 2);
-		close(fd);
-		if (len < 2 || (buf[0] << 8) + buf[1] != UF_START_MAGIC)
-		{
-		    if (name != NULL || p_verbose > 0)
-			smsg((char_u *)_("Will not overwrite, this is not an undo file: %s"),
-								   file_name);
-		    goto theend;
-		}
-	    }
-	}
-	mch_remove(file_name);
-    }
-
-    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;
-    }
-    if (p_verbose > 0)
-	smsg((char_u *)_("Writing undo file: %s"), file_name);
-
-#ifdef UNIX
-    /*
-     * Try to set the group of the undo file same as the original file. If
-     * this fails, set the protection bits for the group same as the
-     * protection bits for others.
-     */
-    if (st_old_valid && (mch_stat((char *)file_name, &st_new) >= 0
-		&& st_new.st_gid != st_old.st_gid
-# ifdef HAVE_FCHOWN  /* sequent-ptx lacks fchown() */
-		&& fchown(fd, (uid_t)-1, st_old.st_gid) != 0)
-# endif
-       )
-	mch_setperm(file_name, (perm & 0707) | ((perm & 07) << 3));
-# ifdef HAVE_SELINUX
-    if (buf->b_ffname != NULL)
-	mch_copy_sec(buf->b_ffname, file_name);
-# endif
-#endif
-
-    fp = fdopen(fd, "w");
-    if (fp == NULL)
-    {
-        EMSG2(_(e_not_open), file_name);
-	close(fd);
-	mch_remove(file_name);
-	goto theend;
-    }
-
-    /* Start writing, first overall file information */
-    put_bytes(fp, (long_u)UF_START_MAGIC, 2);
-    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)
-	goto write_error;
-    put_bytes(fp, (long_u)buf->b_ml.ml_line_count, 4);
-
-    /* Begin undo data for U */
-    str_len = buf->b_u_line_ptr != NULL ? (int)STRLEN(buf->b_u_line_ptr) : 0;
-    put_bytes(fp, (long_u)str_len, 4);
-    if (str_len > 0 && fwrite(buf->b_u_line_ptr, (size_t)str_len,
-							  (size_t)1, fp) != 1)
-	goto write_error;
-
-    put_bytes(fp, (long_u)buf->b_u_line_lnum, 4);
-    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_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.  */
-    mark = ++lastmark;
-    uhp = buf->b_u_oldhead;
-    while (uhp != NULL)
-    {
-        /* Serialize current UHP if we haven't seen it */
-        if (uhp->uh_walk != mark)
-        {
-	    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_bytes(fp, uhp->uh_seq, 4);
-            serialize_pos(uhp->uh_cursor, fp);
-#ifdef FEAT_VIRTUALEDIT
-            put_bytes(fp, (long_u)uhp->uh_cursor_vcol, 4);
-#else
-            put_bytes(fp, (long_u)0, 4);
-#endif
-            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);
-#ifdef FEAT_VISUAL
-            serialize_visualinfo(&uhp->uh_visual, fp);
-#else
-	    {
-		visualinfo_T info;
-
-		memset(&info, 0, sizeof(visualinfo_T));
-		serialize_visualinfo(&info, fp);
-	    }
-#endif
-            put_time(fp, uhp->uh_time);
-
-            uep = uhp->uh_entry;
-            while (uep != NULL)
-            {
-                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;
-        }
-
-        /* Now walk through the tree - algorithm from undo_time */
-        if (uhp->uh_prev != NULL && uhp->uh_prev->uh_walk != mark)
-            uhp = uhp->uh_prev;
-        else if (uhp->uh_alt_next != NULL && uhp->uh_alt_next->uh_walk != mark)
-            uhp = uhp->uh_alt_next;
-        else if (uhp->uh_next != NULL && uhp->uh_alt_prev == NULL
-					     && uhp->uh_next->uh_walk != mark)
-            uhp = uhp->uh_next;
-        else if (uhp->uh_alt_prev != NULL)
-            uhp = uhp->uh_alt_prev;
-        else
-            uhp = uhp->uh_next;
-    }
-
-    if (put_bytes(fp, (long_u)UF_END_MAGIC, 2) == OK)
-	write_ok = TRUE;
-
-write_error:
-    fclose(fp);
-    if (!write_ok)
-        EMSG2(_("E829: write error in undo file: %s"), file_name);
-
-#if defined(MACOS_CLASSIC) || defined(WIN3264)
-    if (buf->b_ffname != NULL)
-	(void)mch_copy_file_attribute(buf->b_ffname, file_name);
-#endif
-#ifdef HAVE_ACL
-    if (buf->b_ffname != NULL)
-    {
-	vim_acl_T	    acl;
-
-	/* For systems that support ACL: get the ACL from the original file. */
-	acl = mch_get_acl(buf->b_ffname);
-	mch_set_acl(file_name, acl);
-    }
-#endif
-
-theend:
-    if (file_name != name)
-	vim_free(file_name);
-}
-
 #endif /* FEAT_PERSISTENT_UNDO */