[BACK]Return to memline.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / vim

File: [local] / src / usr.bin / vim / Attic / memline.c (download)

Revision 1.1.1.1 (vendor branch), Sat Sep 7 21:40:25 1996 UTC (27 years, 9 months ago) by downsj
Branch: VIM
CVS Tags: VIM42
Changes since 1.1: +0 -0 lines

Initial import of vim 4.2.

This is meant to replace nvi in the tree.  Vim, in general, works better,
provides more features, and does not suffer from the license problems
being imposed upon nvi.

On the other hand, vim lacks a non-visual ex mode, in addition to open mode.

This includes the GUI (X11) code, but doesn't try to compile it.


/*	$OpenBSD: memline.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $	*/
/* vi:set ts=4 sw=4:
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 */

/* for debugging */
/* #define CHECK(c, s)	if (c) EMSG(s) */
#define CHECK(c, s)

/*
 * memline.c: Contains the functions for appending, deleting and changing the
 * text lines. The memfile functions are used to store the information in blocks
 * of memory, backed up by a file. The structure of the information is a tree.
 * The root of the tree is a pointer block. The leaves of the tree are data
 * blocks. In between may be several layers of pointer blocks, forming branches.
 *
 * Three types of blocks are used:
 * - Block nr 0 contains information for recovery
 * - Pointer blocks contain list of pointers to other blocks.
 * - Data blocks contain the actual text.
 *
 * Block nr 0 contains the block0 structure (see below).
 *
 * Block nr 1 is the first pointer block. It is the root of the tree.
 * Other pointer blocks are branches.
 *
 *    If a line is too big to fit in a single page, the block
 *    containing that line is made big enough to hold the line. It may span
 *	  several pages. Otherwise all blocks are one page.
 *
 *    A data block that was filled when starting to edit a file and was not
 *	  changed since then, can have a negative block number. This means that it
 *	  has not yet been assigned a place in the file. When recovering, the lines
 * 	  in this data block can be read from the original file. When the block is
 *    changed (lines appended/deleted/changed) or when it is flushed it gets
 *    a positive number. Use mf_trans_del() to get the new number, before
 *    calling mf_get().
 */

/*
 * The following functions are available to work with a memline:
 * 
 * ml_open()			open a new memline for a buffer
 * ml_setname()			change the file name for a memline
 * ml_close()			close the memline for a buffer
 * ml_close_all()		close all memlines
 * ml_recover()			read a memline for recovery
 * ml_sync_all()		flush changed blocks to file for all files
 * ml_preserve()		flush everything into the file for one file
 * ml_get()				get a pointer to a line
 * ml_get_pos()			get a pointer to a position in a line
 * ml_get_cursor()		get a pointer to the char under the cursor
 * ml_get_buf()			get a pointer to a line in a specific buffer
 * ml_line_alloced()	return TRUE if line was allocated
 * ml_append()			append a new line
 * ml_replace()			replace a line
 * ml_delete()			delete a line
 * ml_setmarked()		set mark for a line (for :global command)
 * ml_firstmarked()		get first line with a mark (for :global command)
 * ml_clearmarked()		clear all line marks (for :global command)
 */

#if defined MSDOS  ||  defined WIN32
# include <io.h>
#endif

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifndef UNIX			/* it's in unix.h for Unix */
# include <time.h>
#endif

#ifdef SASC
# include <proto/dos.h>		/* for Open() and Close() */
#endif

typedef struct block0			ZERO_BL;	/* contents of the first block */
typedef struct pointer_block	PTR_BL;		/* contents of a pointer block */
typedef struct data_block		DATA_BL;	/* contents of a data block */
typedef struct pointer_entry	PTR_EN;		/* block/line-count pair */

#define DATA_ID		(('d' << 8) + 'a')		/* data block id */
#define PTR_ID		(('p' << 8) + 't')		/* pointer block id */
#define BLOCK0_ID0	'b'						/* block 0 id 0 */
#define BLOCK0_ID1	'0'						/* block 0 id 1 */

/*
 * pointer to a block, used in a pointer block
 */
struct pointer_entry
{
	blocknr_t	pe_bnum;		/* block number */
	linenr_t	pe_line_count;	/* number of lines in this branch */
	linenr_t	pe_old_lnum;	/* lnum for this block (for recovery) */
	int			pe_page_count;	/* number of pages in block pe_bnum */
};

/*
 * A pointer block contains a list of branches in the tree.
 */
struct pointer_block
{
	short_u		pb_id;			/* ID for pointer block: PTR_ID */
	short_u		pb_count;		/* number of pointer in this block */
	short_u		pb_count_max;	/* maximum value for pb_count */
	PTR_EN		pb_pointer[1];	/* list of pointers to blocks (actually longer)
								 * followed by empty space until end of page */
};

/*
 * A data block is a leaf in the tree.
 *
 * The text of the lines is at the end of the block. The text of the first line
 * in the block is put at the end, the text of the second line in front of it,
 * etc. Thus the order of the lines is the opposite of the line number.
 */
struct data_block
{
	short_u		db_id;			/* ID for data block: DATA_ID */
	unsigned	db_free;		/* free space available */
	unsigned	db_txt_start;	/* byte where text starts */
	unsigned	db_txt_end;		/* byte just after data block */
	linenr_t	db_line_count;	/* number of lines in this block */
	unsigned	db_index[1];	/* index for start of line (actually bigger)
								 * followed by empty space upto db_txt_start
								 * followed by the text in the lines until
								 * end of page */
};

/*
 * The low bits of db_index hold the actual index. The topmost bit is
 * used for the global command to be able to mark a line. 
 * This method is not clean, but otherwise there would be at least one extra
 * byte used for each line.
 * The mark has to be in this place to keep it with the correct line when other
 * lines are inserted or deleted.
 */
#define DB_MARKED		((unsigned)1 << ((sizeof(unsigned) * 8) - 1))
#define DB_INDEX_MASK	(~DB_MARKED)

#define INDEX_SIZE	(sizeof(unsigned))		/* size of one db_index entry */
#define HEADER_SIZE	(sizeof(DATA_BL) - INDEX_SIZE)	/* size of data block header */

#define B0_FNAME_SIZE	900
#define B0_UNAME_SIZE	40
#define B0_HNAME_SIZE	40
/*
 * Restrict the numbers to 32 bits, otherwise most compilers will complain.
 * This won't detect a 64 bit machine that only swaps a byte in the top 32
 * bits, but that is crazy anyway.
 */
#define B0_MAGIC_LONG	0x30313233
#define B0_MAGIC_INT	0x20212223
#define B0_MAGIC_SHORT	0x10111213
#define B0_MAGIC_CHAR	0x55

/*
 * Block zero holds all info about the swap file.
 *
 * NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE, it makes all existing swap
 * files unusable!
 * 
 * If size of block0 changes anyway, adjust minimal block size
 * in mf_open()!!
 *
 * This block is built up of single bytes, to make it portable accros
 * different machines. b0_magic_* is used to check the byte order and size of
 * variables, because the rest of the swap is not portable.
 */
struct block0
{
	char_u		b0_id[2];		/* id for block 0: BLOCK0_ID0 and BLOCK0_ID1 */
	char_u		b0_version[10];	/* Vim version string */
	char_u		b0_page_size[4];/* number of bytes per page */
	char_u		b0_mtime[4];	/* last modification time of file */
	char_u		b0_ino[4];		/* inode of b0_fname */
	char_u		b0_pid[4];		/* process id of creator (or 0) */
	char_u		b0_uname[B0_UNAME_SIZE]; /* name of user (uid if no name) */
	char_u		b0_hname[B0_HNAME_SIZE]; /* host name (if it has a name) */
	char_u		b0_fname[B0_FNAME_SIZE]; /* name of file being edited */
	long		b0_magic_long;	/* check for byte order of long */
	int			b0_magic_int;	/* check for byte order of int */
	short		b0_magic_short;	/* check for byte order of short */
	char_u		b0_magic_char;	/* check for last char */
};

#define STACK_INCR		5		/* nr of entries added to ml_stack at a time */

/*
 * The line number where the first mark may be is remembered.
 * If it is 0 there are no marks at all.
 * (always used for the current buffer only, no buffer change possible while
 * executing a global command).
 */
static linenr_t	lowest_marked = 0;

/*
 * arguments for ml_find_line()
 */
#define ML_DELETE		0x11		/* delete line */
#define ML_INSERT		0x12		/* insert line */
#define ML_FIND			0x13		/* just find the line */
#define ML_FLUSH		0x02		/* flush locked block */
#define ML_SIMPLE(x)	(x & 0x10)	/* DEL, INS or FIND */

static void ml_open_file __ARGS((BUF *));
static void set_b0_fname __ARGS((ZERO_BL *, BUF *buf));
static void swapfile_info __ARGS((char_u *));
static int get_names __ARGS((char_u **, char_u *));
static int ml_append_int __ARGS((BUF *, linenr_t, char_u *, colnr_t, int));
static int ml_delete_int __ARGS((BUF *, linenr_t, int));
static char_u *findswapname __ARGS((BUF *, char_u **, char_u *));
static void ml_flush_line __ARGS((BUF *));
static BHDR *ml_new_data __ARGS((MEMFILE *, int, int));
static BHDR *ml_new_ptr __ARGS((MEMFILE *));
static BHDR *ml_find_line __ARGS((BUF *, linenr_t, int));
static int ml_add_stack __ARGS((BUF *));
static char_u *makeswapname __ARGS((BUF *, char_u *));
static void ml_lineadd __ARGS((BUF *, int));
static int b0_magic_wrong __ARGS((ZERO_BL *));
#ifdef CHECK_INODE
static int fnamecmp_ino __ARGS((char_u *, char_u *, long));
#endif
static void long_to_char __ARGS((long, char_u *));
static long char_to_long __ARGS((char_u *));

/*
 * open a new memline for 'curbuf'
 *
 * return FAIL for failure, OK otherwise
 */
	int
ml_open()
{
	MEMFILE		*mfp;
	char_u		*fname;
	BHDR		*hp = NULL;
	ZERO_BL		*b0p;
	PTR_BL		*pp;
	DATA_BL		*dp;
	char_u		*dirp;

/*
 * init fields in memline struct
 */
	curbuf->b_ml.ml_stack_size = 0;		/* no stack yet */
	curbuf->b_ml.ml_stack = NULL;		/* no stack yet */
	curbuf->b_ml.ml_stack_top = 0;		/* nothing in the stack */
	curbuf->b_ml.ml_locked = NULL;		/* no cached block */
	curbuf->b_ml.ml_line_lnum = 0;		/* no cached line */

/*
 * Make file name for swap file.
 * If we are unable to find a file name, mf_fname will be NULL
 * and the memfile will be in memory only (no recovery possible).
 * When 'updatecount' is 0 and for help files on Unix there is never a swap
 * file.
 */
#if defined(UNIX) || defined(__EMX__)
/* why only on Unix an exception for help files??? */
	if (p_uc == 0 || curbuf->b_help)
#else
	if (p_uc == 0)
#endif
		dirp = (char_u *)"";
	else
		dirp = p_dir;
	
/*
 * Open the memfile.
 *
 * If no swap file is wanted *dirp will be NUL, try with fname == NULL.
 * Otherwise try all directories in 'directory' option. If that fails try
 * with fname == NULL.
 */
	for (;;)
	{
		if (*dirp == NUL)
			fname = NULL;
		else
		{
			fname = findswapname(curbuf, &dirp, NULL);
			if (fname == NULL)
				continue;
		}
		mfp = mf_open(fname, TRUE);
		/* stop when memfile opened or no other fname to try */
		if (mfp != NULL || (fname == NULL && *dirp == NUL))
			break;
		vim_free(fname);
	}

	if (mfp == NULL)
		goto error;
	curbuf->b_ml.ml_mfp = mfp;
	curbuf->b_ml.ml_flags = ML_EMPTY;
	curbuf->b_ml.ml_line_count = 1;

	if (!(p_uc == 0 || curbuf->b_help) && mfp->mf_fname == NULL)
	{
		need_wait_return = TRUE;		/* call wait_return later */
		++no_wait_return;
		(void)EMSG("Unable to open swap file, recovery impossible");
		--no_wait_return;
	}
#if defined(MSDOS) || defined(WIN32)
	/*
	 * set full pathname for swap file now, because a ":!cd dir" may
	 * change directory without us knowing it.
	 */
	mf_fullname(mfp);
#endif

/*
 * fill block0 struct and write page 0
 */
	if ((hp = mf_new(mfp, FALSE, 1)) == NULL)
		goto error;
	if (hp->bh_bnum != 0)
	{
		EMSG("didn't get block nr 0?");
		goto error;
	}
	b0p = (ZERO_BL *)(hp->bh_data);

	(void)vim_memset(b0p, 0, sizeof(ZERO_BL));		/* init all to zero */

	b0p->b0_id[0] = BLOCK0_ID0;
	b0p->b0_id[1] = BLOCK0_ID1;
	b0p->b0_magic_long = (long)B0_MAGIC_LONG;
	b0p->b0_magic_int = (int)B0_MAGIC_INT;
	b0p->b0_magic_short = (short)B0_MAGIC_SHORT;
	b0p->b0_magic_char = B0_MAGIC_CHAR;

	STRNCPY(b0p->b0_version, Version, 10);
	set_b0_fname(b0p, curbuf);
	long_to_char((long)mfp->mf_page_size, b0p->b0_page_size);
	(void)mch_get_user_name(b0p->b0_uname, B0_UNAME_SIZE);
	b0p->b0_uname[B0_UNAME_SIZE - 1] = NUL;
	mch_get_host_name(b0p->b0_hname, B0_HNAME_SIZE);
	b0p->b0_hname[B0_HNAME_SIZE - 1] = NUL;
	long_to_char(mch_get_pid(), b0p->b0_pid);

	/*
	 * Always sync block number 0 to disk, so we can check the file name in
	 * the swap file in findswapname(). Don't do this for help files though.
	 */
	mf_put(mfp, hp, TRUE, FALSE);
	if (!curbuf->b_help)
		mf_sync(mfp, FALSE, FALSE, FALSE);

/*
 * fill in root pointer block and write page 1
 */
	if ((hp = ml_new_ptr(mfp)) == NULL)
		goto error;
	if (hp->bh_bnum != 1)
	{
		EMSG("didn't get block nr 1?");
		goto error;
	}
	pp = (PTR_BL *)(hp->bh_data);
	pp->pb_count = 1;
	pp->pb_pointer[0].pe_bnum = 2;
	pp->pb_pointer[0].pe_page_count = 1;
	pp->pb_pointer[0].pe_old_lnum = 1;
	pp->pb_pointer[0].pe_line_count = 1;	/* line count after insertion */
	mf_put(mfp, hp, TRUE, FALSE);

/*
 * allocate first data block and create an empty line 1.
 */
	if ((hp = ml_new_data(mfp, FALSE, 1)) == NULL)
		goto error;
	if (hp->bh_bnum != 2)
	{
		EMSG("didn't get block nr 2?");
		goto error;
	}

	dp = (DATA_BL *)(hp->bh_data);
	dp->db_index[0] = --dp->db_txt_start;		/* at end of block */
	dp->db_free -= 1 + INDEX_SIZE;
	dp->db_line_count = 1;
	*((char_u *)dp + dp->db_txt_start) = NUL;	/* emtpy line */

	return OK;

error:
	if (mfp != NULL)
	{
		if (hp)
			mf_put(mfp, hp, FALSE, FALSE);
		mf_close(mfp, TRUE);		/* will also free(mfp->mf_fname) */
	}
	else
		vim_free(fname);
	curbuf->b_ml.ml_mfp = NULL;
	return FAIL;
}

/*
 * ml_setname() is called when the file name has been changed.
 * It may rename the swap file.
 */
	void
ml_setname()
{
	int			success = FALSE;
	MEMFILE		*mfp;
	char_u		*fname;
	char_u		*dirp;

/*
 * When 'updatecount' is 0 there is never a swap file.
 * For help files we will make a swap file now.
 */
	if (p_uc == 0)
		return;

	mfp = curbuf->b_ml.ml_mfp;
	if (mfp->mf_fd < 0)				/* there is no swap file yet */
	{
		ml_open_file(curbuf);		/* create a swap file */
		return;
	}

/*
 * Try all directories in the 'directory' option.
 */
	dirp = p_dir;
	for (;;)
	{
		if (*dirp == NUL)			/* tried all directories, fail */
			break;
		fname = findswapname(curbuf, &dirp, mfp->mf_fname);
		if (fname == NULL)			/* no file name found for this dir */
			continue;

#if defined(MSDOS) || defined(WIN32)
		/*
		 * Set full pathname for swap file now, because a ":!cd dir" may
		 * change directory without us knowing it.
		 */
		if (!did_cd)
		{
			char_u		*p;

			p = FullName_save(fname);
			vim_free(fname);
			fname = p;
			if (fname == NULL)
				continue;
		}
#endif
			/* if the file name is the same we don't have to do anything */
		if (fnamecmp(fname, mfp->mf_fname) == 0)
		{
			vim_free(fname);
			success = TRUE;
			break;
		}
			/* need to close the swap file before renaming */
		if (mfp->mf_fd >= 0)
		{
			close(mfp->mf_fd);
			mfp->mf_fd = -1;
		}

			/* try to rename the swap file */
		if (vim_rename(mfp->mf_fname, fname) == 0)
		{
			success = TRUE;
			vim_free(mfp->mf_fname);
			mfp->mf_fname = fname;
			vim_free(mfp->mf_xfname);
			mf_set_xfname(mfp);
			break;
		}
		vim_free(fname);			/* this fname didn't work, try another */
	}

	if (mfp->mf_fd == -1)			/* need to (re)open the swap file */
	{
		mfp->mf_fd = open((char *)mfp->mf_fname, O_RDWR | O_EXTRA);
		if (mfp->mf_fd < 0)
		{
			/* could not (re)open the swap file, what can we do???? */
			EMSG("Oops, lost the swap file!!!");
			return;
		}
	}
	if (!success)
		EMSG("Could not rename swap file");
}

/*
 * Open a file for the memfile for all buffers.
 * Used when 'updatecount' changes from zero to non-zero.
 */
	void
ml_open_files()
{
	BUF			*buf;

	for (buf = firstbuf; buf != NULL; buf = buf->b_next)
		ml_open_file(buf);
}

/*
 * Open a swap file for an existing memfile, if there is non swap file yet.
 */
	static void
ml_open_file(buf)
	BUF			*buf;
{
	MEMFILE		*mfp;
	char_u		*fname;
	char_u		*dirp;

	mfp = buf->b_ml.ml_mfp;
	if (mfp == NULL || mfp->mf_fd >= 0)			/* nothing to do */
		return;

	dirp = p_dir;
/*
 * Try all directories in 'directory' option.
 */
	for (;;)
	{
		if (*dirp == NUL)
			break;
		fname = findswapname(buf, &dirp, NULL);
		if (fname == NULL)
			continue;
		if (mf_open_file(mfp, fname) == OK)
			break;
		vim_free(fname);
	}

	if (mfp->mf_fname == NULL)			/* Failed! */
	{
		need_wait_return = TRUE;		/* call wait_return later */
		++no_wait_return;
		(void)EMSG2("Unable to open swap file for \"%s\", recovery impossible",
				buf->b_xfilename == NULL ? (char_u *)"No File"
										 : buf->b_xfilename);
		--no_wait_return;
	}
#if defined(MSDOS) || defined(WIN32)
	/*
	 * set full pathname for swap file now, because a ":!cd dir" may
	 * change directory without us knowing it.
	 */
	else
		mf_fullname(mfp);
#endif
}

/*
 * Close memline for buffer 'buf'.
 * If 'del_file' is TRUE, delete the swap file
 */
	void
ml_close(buf, del_file)
	BUF		*buf;
	int		del_file;
{
	if (buf->b_ml.ml_mfp == NULL)				/* not open */
		return;
	mf_close(buf->b_ml.ml_mfp, del_file);			/* close the .swp file */
	if (buf->b_ml.ml_line_lnum != 0 && (buf->b_ml.ml_flags & ML_LINE_DIRTY))
		vim_free(buf->b_ml.ml_line_ptr);
	vim_free(buf->b_ml.ml_stack);
	buf->b_ml.ml_mfp = NULL;
}

/*
 * Close all existing memlines and memfiles.
 * Used when exiting.
 * When 'del_file' is TRUE, delete the memfiles.
 */
	void
ml_close_all(del_file)
	int		del_file;
{
	BUF		*buf;

	for (buf = firstbuf; buf != NULL; buf = buf->b_next)
		ml_close(buf, del_file);
}

/*
 * Close all memfiles for not modified buffers.
 * Only use just before exiting!
 */
	void
ml_close_notmod()
{
	BUF		*buf;

	for (buf = firstbuf; buf != NULL; buf = buf->b_next)
		if (!buf->b_changed)
			ml_close(buf, TRUE);	/* close all not-modified buffers */
}

/*
 * Update the timestamp in the .swp file.
 * Used when the file has been written.
 */
	void
ml_timestamp(buf)
	BUF			*buf;
{
	MEMFILE		*mfp;
	BHDR		*hp;
	ZERO_BL		*b0p;

	mfp = buf->b_ml.ml_mfp;

	if (mfp == NULL || (hp = mf_get(mfp, (blocknr_t)0, 1)) == NULL)
		return;
	b0p = (ZERO_BL *)(hp->bh_data);
	if (b0p->b0_id[0] != BLOCK0_ID0 || b0p->b0_id[1] != BLOCK0_ID1)
		EMSG("ml_timestamp: Didn't get block 0??");
	else
		set_b0_fname(b0p, buf);
	mf_put(mfp, hp, TRUE, FALSE);
}

/*
 * Write file name and timestamp into block 0 of a swap file.
 * Also set buf->b_mtime.
 * Don't use NameBuff[]!!!
 */
	static void
set_b0_fname(b0p, buf)
	ZERO_BL		*b0p;
	BUF			*buf;
{
	struct stat	st;
	size_t		flen, ulen;
	char_u		uname[B0_UNAME_SIZE];

	if (buf->b_filename == NULL)
		b0p->b0_fname[0] = NUL;
	else
	{
		/*
		 * For a file under the home directory of the current user, we try to
		 * replace the home directory path with "~user". This helps when
		 * editing the same file on different machines over a network.
		 * First replace home dir path with "~/" with home_replace().
		 * Then insert the user name to get "~user/".
		 */
		home_replace(NULL, buf->b_filename, b0p->b0_fname, B0_FNAME_SIZE);
		if (b0p->b0_fname[0] == '~')
		{
			flen = STRLEN(b0p->b0_fname);
			/* If there is no user name or it is too long, don't use "~/" */
			if (mch_get_user_name(uname, B0_UNAME_SIZE) == FAIL ||
						 (ulen = STRLEN(uname)) + flen > B0_FNAME_SIZE - 1)
				STRNCPY(b0p->b0_fname, buf->b_filename, B0_FNAME_SIZE);
			else
			{
				vim_memmove(b0p->b0_fname + ulen + 1, b0p->b0_fname + 1, flen);
				vim_memmove(b0p->b0_fname + 1, uname, ulen);
			}
		}
		if (stat((char *)buf->b_filename, &st) >= 0)
		{
			long_to_char((long)st.st_mtime, b0p->b0_mtime);
#ifdef CHECK_INODE
			long_to_char((long)st.st_ino, b0p->b0_ino);
#endif
			buf->b_mtime = st.st_mtime;
			buf->b_mtime_read = st.st_mtime;
		}
		else
		{
			long_to_char(0L, b0p->b0_mtime);
#ifdef CHECK_INODE
			long_to_char(0L, b0p->b0_ino);
#endif
			buf->b_mtime = 0;
			buf->b_mtime_read = 0;
		}
	}
}

/*
 * try to recover curbuf from the .swp file
 */
	void
ml_recover()
{
	BUF			*buf = NULL;
	MEMFILE		*mfp = NULL;
	char_u		*fname;
	BHDR		*hp = NULL;
	ZERO_BL		*b0p;
	PTR_BL		*pp;
	DATA_BL		*dp;
	IPTR		*ip;
	blocknr_t	bnum;
	int			page_count;
	struct stat	org_stat, swp_stat;
	int			len;
	int			directly;
	linenr_t	lnum;
	char_u		*p;
	int			i;
	long		error;
	int			cannot_open;
	linenr_t	line_count;
	int			has_error;
	int			idx;
	int			top;
	int			txt_start;
	long		size;
	int			called_from_main;
	int			serious_error = TRUE;
	long		mtime;

	called_from_main = (curbuf->b_ml.ml_mfp == NULL);
/*
 * If the file name ends in ".sw?" we use it directly.
 * Otherwise a search is done to find the swap file(s).
 */
	fname = curbuf->b_xfilename;
	if (fname == NULL)				/* When there is no file name */
		fname = (char_u *)"";
	len = STRLEN(fname);
	if (len >= 4 && vim_strnicmp(fname + len - 4,
									(char_u *)".sw", (size_t)3) == 0)
	{
		directly = TRUE;
		fname = strsave(fname);		/* make a copy for mf_open */
	}
	else
	{
		directly = FALSE;

			/* count the number of matching swap files */
		len = recover_names(&fname, FALSE, 0);
		if (len == 0)				/* no swap files found */
		{
			EMSG2("No swap file found for %s", fname);
			fname = NULL;
			goto theend;
		}
		if (len == 1)				/* one swap file found, use it */
			i = 1;
		else						/* several swap files found, choose */
		{
				/* list the names of the swap files */
			(void)recover_names(&fname, TRUE, 0);
			msg_outchar('\n');
			MSG_OUTSTR("Enter number of swap file to use (0 to quit): ");
			i = get_number();
			if (i < 1 || i > len)
			{
				fname = NULL;
				goto theend;
			}
		}
				/* get the swap file name that will be used */
		(void)recover_names(&fname, FALSE, i);
	}
	if (fname == NULL)
		goto theend;					/* out of memory */

	/* When called from main() still need to initialize storage structure */
	if (called_from_main && ml_open() == FAIL)
		getout(1);

/*
 * allocate a buffer structure (only the memline in it is really used)
 */
	buf = (BUF *)alloc((unsigned)sizeof(BUF));
	if (buf == NULL)
		goto theend;

/*
 * init fields in memline struct
 */
	buf->b_ml.ml_stack_size = 0;		/* no stack yet */
	buf->b_ml.ml_stack = NULL;			/* no stack yet */
	buf->b_ml.ml_stack_top = 0;			/* nothing in the stack */
	buf->b_ml.ml_line_lnum = 0;			/* no cached line */
	buf->b_ml.ml_locked = NULL;			/* no locked block */
	buf->b_ml.ml_flags = 0;

/*
 * open the memfile from the old swap file
 */
	mfp = mf_open(fname, FALSE);
	if (mfp == NULL || mfp->mf_fd < 0)
	{
		EMSG2("Cannot open %s", fname);
		goto theend;
	}
	buf->b_ml.ml_mfp = mfp;
#if defined(MSDOS) || defined(WIN32)
	/*
	 * set full pathname for swap file now, because a ":!cd dir" may
	 * change directory without us knowing it.
	 * Careful: May free fname!
	 */
	mf_fullname(mfp);
#endif

/*
 * try to read block 0
 */
	if ((hp = mf_get(mfp, (blocknr_t)0, 1)) == NULL)
	{
		msg_start();
		MSG_OUTSTR("Unable to read block 0 from ");
		msg_outtrans(mfp->mf_fname);
		MSG_OUTSTR("\nMaybe no changes were made or Vim did not update the swap file");
		msg_end();
		goto theend;
	}
	b0p = (ZERO_BL *)(hp->bh_data);
	if (b0p->b0_id[0] != BLOCK0_ID0 || b0p->b0_id[1] != BLOCK0_ID1)
	{
		EMSG2("%s is not a swap file", mfp->mf_fname);
		goto theend;
	}
	if (b0_magic_wrong(b0p))
	{
		msg_start();
		MSG_OUTSTR("The file ");
		msg_outtrans(mfp->mf_fname);
#if defined(MSDOS) || defined(WIN32)
		if (STRNCMP(b0p->b0_hname, "PC ", 3) == 0)
			MSG_OUTSTR(" cannot be used with this version of Vim.\n");
		else
#endif
			MSG_OUTSTR(" cannot be used on this computer.\n");
		MSG_OUTSTR("The file was created on ");
				/* avoid going past the end of currupted hostname */
		b0p->b0_fname[0] = NUL;
		MSG_OUTSTR(b0p->b0_hname);
		MSG_OUTSTR(",\nor the file has been damaged.");
		msg_end();
		goto theend;
	}
	/*
	 * If we guessed the wrong page size, we have to recalculate the
	 * highest block number in the file.
	 */
	if (mfp->mf_page_size != (unsigned)char_to_long(b0p->b0_page_size))
	{
		mfp->mf_page_size = (unsigned)char_to_long(b0p->b0_page_size);
		if ((size = lseek(mfp->mf_fd, 0L, SEEK_END)) <= 0)
			mfp->mf_blocknr_max = 0;		/* no file or empty file */
		else
			mfp->mf_blocknr_max = size / mfp->mf_page_size;
		mfp->mf_infile_count = mfp->mf_blocknr_max;
	}

/*
 * If .swp file name given directly, use name from swap file for buffer.
 */
	if (directly)
	{
		expand_env(b0p->b0_fname, NameBuff, MAXPATHL);
		if (setfname(NameBuff, NULL, TRUE) == FAIL)
			goto theend;
	}

	home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL);
	smsg((char_u *)"Using swap file \"%s\"", NameBuff);

	if (curbuf->b_filename == NULL)
		STRCPY(NameBuff, "No File");
	else
		home_replace(NULL, curbuf->b_filename, NameBuff, MAXPATHL);
	smsg((char_u *)"Original file \"%s\"", NameBuff);
	msg_outchar((char_u)'\n');

/*
 * check date of swap file and original file
 */
	mtime = char_to_long(b0p->b0_mtime);
	if (curbuf->b_filename != NULL &&
			stat((char *)curbuf->b_filename, &org_stat) != -1 &&
			((stat((char *)mfp->mf_fname, &swp_stat) != -1 &&
			org_stat.st_mtime > swp_stat.st_mtime) ||
			org_stat.st_mtime != mtime))
	{
		EMSG("Warning: Original file may have been changed");
	}
	flushbuf();
	mf_put(mfp, hp, FALSE, FALSE);		/* release block 0 */
	hp = NULL;

	/*
	 * Now that we are sure that the file is going to be recovered, clear the
	 * contents of the current buffer.
	 */
	while (!(curbuf->b_ml.ml_flags & ML_EMPTY))
		ml_delete((linenr_t)1, FALSE);

	bnum = 1;			/* start with block 1 */
	page_count = 1;		/* which is 1 page */
	lnum = 0;			/* append after line 0 in curbuf */
	line_count = 0;
	idx = 0;			/* start with first index in block 1 */
	error = 0;
	buf->b_ml.ml_stack_top = 0;
	buf->b_ml.ml_stack = NULL;
	buf->b_ml.ml_stack_size = 0;		/* no stack yet */

	if (curbuf->b_filename == NULL)
		cannot_open = TRUE;
	else
		cannot_open = FALSE;

	serious_error = FALSE;
	for ( ; !got_int; line_breakcheck())
	{
		if (hp != NULL)
			mf_put(mfp, hp, FALSE, FALSE);		/* release previous block */

		/*
		 * get block
		 */
		if ((hp = mf_get(mfp, (blocknr_t)bnum, page_count)) == NULL)
		{
			if (bnum == 1)
			{
				EMSG2("Unable to read block 1 from %s", mfp->mf_fname);
				goto theend;
			}
			++error;
			ml_append(lnum++, (char_u *)"???MANY LINES MISSING", (colnr_t)0, TRUE);
		}
		else			/* there is a block */
		{
			pp = (PTR_BL *)(hp->bh_data);
			if (pp->pb_id == PTR_ID)			/* it is a pointer block */
			{
					/* check line count when using pointer block first time */
				if (idx == 0 && line_count != 0)
				{
					for (i = 0; i < (int)pp->pb_count; ++i)
						line_count -= pp->pb_pointer[i].pe_line_count;
					if (line_count != 0)
					{
						++error;
						ml_append(lnum++, (char_u *)"???LINE COUNT WRONG", (colnr_t)0, TRUE);
					}
				}

				if (pp->pb_count == 0)
				{
					ml_append(lnum++, (char_u *)"???EMPTY BLOCK", (colnr_t)0, TRUE);
					++error;
				}
				else if (idx < (int)pp->pb_count)		/* go a block deeper */
				{
					if (pp->pb_pointer[idx].pe_bnum < 0)
					{
						/*
						 * Data block with negative block number.
						 * Try to read lines from the original file.
						 * This is slow, but it works.
						 */
						if (!cannot_open)
						{
							line_count = pp->pb_pointer[idx].pe_line_count;
							if (readfile(curbuf->b_filename, NULL, lnum, FALSE,
										pp->pb_pointer[idx].pe_old_lnum - 1,
										line_count, FALSE) == FAIL)
								cannot_open = TRUE;
							else
								lnum += line_count;
						}
						if (cannot_open)
						{
							++error;
							ml_append(lnum++, (char_u *)"???LINES MISSING", (colnr_t)0, TRUE);
						}
						++idx;		/* get same block again for next index */
						continue;
					}

					/*
					 * going one block deeper in the tree
					 */
					if ((top = ml_add_stack(buf)) < 0)		/* new entry in stack */
					{
						++error;
						break;				/* out of memory */
					}
					ip = &(buf->b_ml.ml_stack[top]);
					ip->ip_bnum = bnum;
					ip->ip_index = idx;

					bnum = pp->pb_pointer[idx].pe_bnum;
					line_count = pp->pb_pointer[idx].pe_line_count;
					page_count = pp->pb_pointer[idx].pe_page_count;
					continue;
				}
			}
			else			/* not a pointer block */
			{
				dp = (DATA_BL *)(hp->bh_data);
				if (dp->db_id != DATA_ID)		/* block id wrong */
				{
					if (bnum == 1)
					{
						EMSG2("Block 1 ID wrong (%s not a .swp file?)",
															   mfp->mf_fname);
						goto theend;
					}
					++error;
					ml_append(lnum++, (char_u *)"???BLOCK MISSING", (colnr_t)0, TRUE);
				}
				else
				{
					/*
					 * it is a data block
					 * Append all the lines in this block
					 */
					has_error = FALSE;
						/*
						 * check length of block
						 * if wrong, use length in pointer block
						 */
					if (page_count * mfp->mf_page_size != dp->db_txt_end)
					{
						ml_append(lnum++, (char_u *)"??? from here until ???END lines may be messed up", (colnr_t)0, TRUE);
						++error;
						has_error = TRUE;
						dp->db_txt_end = page_count * mfp->mf_page_size;
					}
						
						/* make sure there is a NUL at the end of the block */
					*((char_u *)dp + dp->db_txt_end - 1) = NUL;

						/*
						 * check number of lines in block
						 * if wrong, use count in data block
						 */
					if (line_count != dp->db_line_count)
					{
						ml_append(lnum++, (char_u *)"??? from here until ???END lines may have been inserted/deleted", (colnr_t)0, TRUE);
						++error;
						has_error = TRUE;
					}

					for (i = 0; i < dp->db_line_count; ++i)
					{
						txt_start = (dp->db_index[i] & DB_INDEX_MASK);
						if (txt_start <= HEADER_SIZE || txt_start >= (int)dp->db_txt_end)
						{
							p = (char_u *)"???";
							++error;
						}
						else
							p = (char_u *)dp + txt_start;
						ml_append(lnum++, p, (colnr_t)0, TRUE);
					}
					if (has_error)
						ml_append(lnum++, (char_u *)"???END", (colnr_t)0, TRUE);
				}
			}
		}

		if (buf->b_ml.ml_stack_top == 0)		/* finished */
			break;

		/*
		 * go one block up in the tree
		 */
		ip = &(buf->b_ml.ml_stack[--(buf->b_ml.ml_stack_top)]);
		bnum = ip->ip_bnum;
		idx = ip->ip_index + 1;		/* go to next index */
		page_count = 1;
	}

	/*
	 * The dummy line from the empty buffer will now be after the last line in
	 * the buffer. Delete it.
	 */
	ml_delete(curbuf->b_ml.ml_line_count, FALSE);

	recoverymode = FALSE;
	if (got_int)
		EMSG("Recovery Interrupted");
	else if (error)
		EMSG("Errors detected while recovering; look for lines starting with ???");
	else
	{
		MSG("Recovery completed. You should check if everything is OK.");
		MSG_OUTSTR("\n(You might want to write out this file under another name\n");
		MSG_OUTSTR("and run diff with the original file to check for changes)\n");
		MSG_OUTSTR("Delete the .swp file afterwards.\n\n");
		cmdline_row = msg_row;
	}

theend:
	if (mfp != NULL)
	{
		if (hp != NULL)
			mf_put(mfp, hp, FALSE, FALSE);
		mf_close(mfp, FALSE);		/* will also vim_free(mfp->mf_fname) */
	}
	else
		vim_free(fname);
	vim_free(buf);
	if (serious_error && called_from_main)
		ml_close(curbuf, TRUE);
	return;
}

/*
 * Find the names of swap files in current directory and the directory given
 * with the 'directory' option.
 *
 * Used to:
 * - list the swap files for "vim -r"
 * - count the number of swap files when recovering
 * - list the swap files when recovering
 * - find the name of the n'th swap file when recovering
 */
	int
recover_names(fname, list, nr)
	char_u		**fname;	/* base for swap file name */
	int			list;		/* when TRUE, list the swap file names */
	int			nr;			/* when non-zero, return nr'th swap file name */
{
	int			num_names;
	char_u		*(names[6]);
	char_u		*tail;
	char_u		*p;
	int			num_files;
	int			file_count = 0;
	char_u		**files;
	int			i;
	char_u		*dirp;
	char_u		*dir_name;

	if (list)
	{
			/* use msg() to start the scrolling properly */
		msg((char_u *)"Swap files found:");
		msg_outchar('\n');
	}
	expand_interactively = TRUE;
	/*
	 * Do the loop for every directory in 'directory'.
	 */
	dirp = p_dir;
	while (*dirp)
	{
		/* find character after directory name */
		dir_name = dirp;
		while (*dirp && *dirp != ',')
			++dirp;
		dir_name = strnsave(dir_name, (int)(dirp - dir_name));
		if (dir_name == NULL)		/* out of memory */
			break;
		if (*dir_name == '.')		/* check current dir */
		{
			if (fname == NULL || *fname == NULL)
			{
				names[0] = strsave((char_u *)"*.sw?");
#ifdef UNIX
					/* for Unix names starting with a dot are special */
				names[1] = strsave((char_u *)".*.sw?");
				names[2] = strsave((char_u *)".sw?");
				num_names = 3;
#else
				num_names = 1;
#endif
			}
			else
				num_names = get_names(names, *fname);
		}
		else						/* check directory dir_name */
		{
			if (fname == NULL || *fname == NULL)
			{
				names[0] = concat_fnames(dir_name, (char_u *)"*.sw?", TRUE);
#ifdef UNIX
					/* for Unix names starting with a dot are special */
				names[1] = concat_fnames(dir_name, (char_u *)".*.sw?", TRUE);
				names[2] = concat_fnames(dir_name, (char_u *)".sw?", TRUE);
				num_names = 3;
#else
				num_names = 1;
#endif
			}
			else
			{
				tail = gettail(*fname);
				tail = concat_fnames(dir_name, tail, TRUE);
				if (tail == NULL)
					num_names = 0;
				else
				{
					num_names = get_names(names, tail);
					vim_free(tail);
				}
			}
		}

			/* check for out-of-memory */
		for (i = 0; i < num_names; ++i)
		{
			if (names[i] == NULL)
			{
				for (i = 0; i < num_names; ++i)
					vim_free(names[i]);
				num_names = 0;
			}
		}
		if (num_names == 0)
			num_files = 0;
		else if (ExpandWildCards(num_names, names,
									 &num_files, &files, TRUE, FALSE) == FAIL)
		{
			MSG_OUTSTR(files);		/* print error message */
			num_files = 0;
		}
		/*
		 * remove swapfile name of the current buffer, it must be ignored
		 */
		if (curbuf->b_ml.ml_mfp != NULL &&
								(p = curbuf->b_ml.ml_mfp->mf_fname) != NULL)
		{
			for (i = 0; i < num_files; ++i)
				if (fullpathcmp(p, files[i]) == FPC_SAME)
				{
					vim_free(files[i]);
					--num_files;
					for ( ; i < num_files; ++i)
						files[i] = files[i + 1];
					break;
				}
		}
		if (nr)
		{
			file_count += num_files;
			if (nr <= file_count)
			{
				*fname = strsave(files[nr - 1 + num_files - file_count]);
				dirp = (char_u *)"";				/* stop searching */
			}
		}
		else if (list)
		{
			if (*dir_name == '.')
			{
				if (fname == NULL || *fname == NULL)
					MSG_OUTSTR("   In current directory:\n");
				else
					MSG_OUTSTR("   Using specified name:\n");
			}
			else
			{
				MSG_OUTSTR("   In directory ");
				msg_home_replace(dir_name);
				MSG_OUTSTR(":\n");
			}

			if (num_files)
			{
				for (i = 0; i < num_files; ++i)
				{
					/* print the swap file name */
					msg_outnum((long)++file_count);
					MSG_OUTSTR(".    ");
					msg_outstr(gettail(files[i]));
					msg_outchar('\n');
					swapfile_info(files[i]);
				}
			}
			else
				MSG_OUTSTR("      -- none --\n");
			flushbuf();
		}
		else
			file_count += num_files;

		for (i = 0; i < num_names; ++i)
			vim_free(names[i]);
		FreeWild(num_files, files);

		/* advance dirp to next directory name */
		vim_free(dir_name);
		if (*dirp == ',')
			++dirp;
		dirp = skipwhite(dirp);
	}
	expand_interactively = FALSE;
	return file_count;
}

/*
 * Give information about an existing swap file
 */
	static void
swapfile_info(fname)
	char_u		*fname;
{
	struct stat		st;
	int				fd;
	struct block0	b0;
	time_t			x;

	/* print the swap file date */
	if (stat((char *)fname, &st) != -1)
	{
		MSG_OUTSTR("             dated: ");
		x = st.st_mtime;					/* Manx C can't do &st.st_mtime */
		MSG_OUTSTR(ctime(&x));
	}

	/*
	 * print the original file name
	 */
	fd = open((char *)fname, O_RDONLY | O_EXTRA);
	if (fd >= 0)
	{
		if (read(fd, (char *)&b0, sizeof(b0)) == sizeof(b0))
		{
			if (b0.b0_id[0] != BLOCK0_ID0 ||
								b0.b0_id[1] != BLOCK0_ID1)
				MSG_OUTSTR("         [is not a swap file]");
			else
			{
				MSG_OUTSTR("         file name: ");
				msg_outstr(b0.b0_fname);

				if (*(b0.b0_hname) != NUL)
				{
					MSG_OUTSTR("\n         host name: ");
					msg_outstr(b0.b0_hname);
				}

				if (*(b0.b0_uname) != NUL)
				{
					MSG_OUTSTR("\n         user name: ");
					msg_outstr(b0.b0_uname);
				}

				if (char_to_long(b0.b0_pid) != 0L)
				{
					MSG_OUTSTR("\n        process ID: ");
					msg_outnum(char_to_long(b0.b0_pid));
#if defined(UNIX) || defined(__EMX__)
					/* EMX kill() not working correctly, it seems */
					if (kill(char_to_long(b0.b0_pid), 0) == 0)
						MSG_OUTSTR(" (still running)");
#endif
				}

				if (b0_magic_wrong(&b0))
				{
#if defined(MSDOS) || defined(WIN32)
					if (STRNCMP(b0.b0_hname, "PC ", 3) == 0)
						MSG_OUTSTR("\n         [not usable with this version of Vim]");
					else
#endif
						MSG_OUTSTR("\n         [not usable on this computer]");
				}
					
			}
		}
		else
			MSG_OUTSTR("         [cannot be read]");
		close(fd);
	}
	else
		MSG_OUTSTR("         [cannot be opened]");
	msg_outchar('\n');
}

	static int
get_names(names, fname)
	char_u **names;
	char_u	*fname;
{
	int		num_names;

#ifdef SHORT_FNAME
	/*
	 * (MS-DOS) always short names
	 */
	names[0] = modname(fname, (char_u *)".sw?");
	num_names = 1;
#else /* !SHORT_FNAME */
# ifdef WIN32
	/*
	 * (WIN32) never short names
	 */
	num_names = 1;
	names[0] = concat_fnames(fname, (char_u *)".sw?", FALSE);
# else /* !WIN32 */
	/*
	 * (Not MS-DOS or WIN32) maybe short name, maybe not: try both.
	 * Only use the short name if it is different.
	 */

	int		i;

	names[0] = concat_fnames(fname, (char_u *)".sw?", FALSE);
	i = curbuf->b_shortname;
	curbuf->b_shortname = TRUE;
	names[1] = modname(fname, (char_u *)".sw?");
	curbuf->b_shortname = i;
	if (STRCMP(names[0], names[1]) == 0)
	{
		vim_free(names[1]);
		num_names = 1;
	}
	else
		num_names = 2;
# endif /* !WIN32 */
#endif /* !SHORT_FNAME */
	return num_names;
}

/*
 * sync all memlines
 *
 * If 'check_file' is TRUE, check if original file exists and was not changed.
 * If 'check_char' is TRUE, stop syncing when character becomes available, but
 * always sync at least one block.
 */
	void
ml_sync_all(check_file, check_char)
	int		check_file;
	int		check_char;
{
	BUF				*buf;
	struct stat		st;

	for (buf = firstbuf; buf != NULL; buf = buf->b_next)
	{
		if (buf->b_ml.ml_mfp == NULL || buf->b_ml.ml_mfp->mf_fname == NULL)
			continue;						/* no file */

		ml_flush_line(buf);					/* flush buffered line */
											/* flush locked block */
		(void)ml_find_line(buf, (linenr_t)0, ML_FLUSH);
		if (buf->b_changed && check_file && mf_need_trans(buf->b_ml.ml_mfp) &&
									buf->b_filename != NULL)
		{
			/*
			 * if original file does not exist anymore or has been changed
			 * call ml_preserve to get rid of all negative numbered blocks
			 */
			if (stat((char *)buf->b_filename, &st) == -1 ||
								st.st_mtime != buf->b_mtime_read)
			{
				ml_preserve(buf, FALSE);
				need_check_timestamps = TRUE;	/* give message later */
			}
		}
		if (buf->b_ml.ml_mfp->mf_dirty)
		{
			mf_sync(buf->b_ml.ml_mfp, FALSE, check_char, buf->b_changed);
			if (check_char && mch_char_avail())	/* character available now */
				break;
		}
	}
}

/*
 * sync one buffer, including negative blocks
 *
 * after this all the blocks are in the swap file
 *
 * Used for the :preserve command and when the original file has been
 * changed or deleted.
 *
 * when message is TRUE the success of preserving is reported
 */
	void
ml_preserve(buf, message)
	BUF		*buf;
	int		message;
{
	BHDR		*hp;
	linenr_t	lnum;
	MEMFILE		*mfp = buf->b_ml.ml_mfp;
	int			status;

	if (mfp == NULL || mfp->mf_fname == NULL)
	{
		if (message)
			EMSG("Cannot preserve, there is no swap file");
		return;
	}

	ml_flush_line(buf);								/* flush buffered line */
	(void)ml_find_line(buf, (linenr_t)0, ML_FLUSH);	/* flush locked block */
	status = mf_sync(mfp, TRUE, FALSE, TRUE);
		
			/* stack is invalid after mf_sync(.., TRUE, ..) */
	buf->b_ml.ml_stack_top = 0;

	/*
	 * Some of the data blocks may have been changed from negative to
	 * positive block number. In that case the pointer blocks need to be
	 * updated.
	 *
	 * We don't know in which pointer block the references are, so we visit
	 * all data blocks until there are no more translations to be done (or
	 * we hit the end of the file, which can only happen in case a write fails,
	 * e.g. when file system if full).
	 * ml_find_line() does the work by translating the negative block numbers
	 * when getting the first line of each data block.
	 */
	if (mf_need_trans(mfp))
	{
		lnum = 1;
		while (mf_need_trans(mfp) && lnum <= buf->b_ml.ml_line_count)
		{
			hp = ml_find_line(buf, lnum, ML_FIND);
			if (hp == NULL)
			{
				status = FAIL;
				goto theend;
			}
			CHECK(buf->b_ml.ml_locked_low != lnum, "low != lnum");
			lnum = buf->b_ml.ml_locked_high + 1;
		}
		(void)ml_find_line(buf, (linenr_t)0, ML_FLUSH);	/* flush locked block */
		if (mf_sync(mfp, TRUE, FALSE, TRUE) == FAIL)	/* sync the updated pointer blocks */
			status = FAIL;
		buf->b_ml.ml_stack_top = 0;			/* stack is invalid now */
	}
theend:
	if (message)
	{
		if (status == OK)
			MSG("File preserved");
		else
			EMSG("Preserve failed");
	}
}

/*
 * get a pointer to a (read-only copy of a) line 
 * 
 * On failure an error message is given and IObuff is returned (to avoid
 * having to check for error everywhere).
 */
	char_u	*
ml_get(lnum)
	linenr_t	lnum;
{
	return ml_get_buf(curbuf, lnum, FALSE);
}

/*
 * ml_get_pos: get pointer to position 'pos'
 */
    char_u *
ml_get_pos(pos)
    FPOS	*pos;
{
	return (ml_get_buf(curbuf, pos->lnum, FALSE) + pos->col);
}

/*
 * ml_get_pos: get pointer to cursor line.
 */
	char_u *
ml_get_curline()
{
	return ml_get_buf(curbuf, curwin->w_cursor.lnum, FALSE);
}

/*
 * ml_get_pos: get pointer to cursor position
 */
	char_u *
ml_get_cursor()
{
	return (ml_get_buf(curbuf, curwin->w_cursor.lnum, FALSE) +
														curwin->w_cursor.col);
}

/*
 * get a pointer to a line in a specific buffer
 *
 *  will_change: if TRUE mark the buffer dirty (chars in the line will be
 *  changed)
 */
	char_u	*
ml_get_buf(buf, lnum, will_change)
	BUF			*buf;
	linenr_t	lnum;
	int			will_change;			/* line will be changed */
{
	BHDR	*hp;
	DATA_BL	*dp;
	char_u	*ptr;

	if (lnum > buf->b_ml.ml_line_count)	/* invalid line number */
	{
		EMSGN("ml_get: invalid lnum: %ld", lnum);
errorret:
		STRCPY(IObuff, "???");
		return IObuff;
	}
	if (lnum <= 0)						/* pretend line 0 is line 1 */
		lnum = 1;
	
	if (buf->b_ml.ml_mfp == NULL)		/* there are no lines */
		return (char_u *)"";

/*
 * See if it is the same line as requested last time.
 * Otherwise may need to flush last used line.
 */
	if (buf->b_ml.ml_line_lnum != lnum)
	{
		ml_flush_line(buf);

		/*
		 * find the data block containing the line
		 * This also fills the stack with the blocks from the root to the data block
		 * This also releases any locked block.
		 */
		if ((hp = ml_find_line(buf, lnum, ML_FIND)) == NULL)
		{
			EMSGN("ml_get: cannot find line %ld", lnum);
			goto errorret;
		}

		dp = (DATA_BL *)(hp->bh_data);

		ptr = (char_u *)dp + ((dp->db_index[lnum - buf->b_ml.ml_locked_low]) & DB_INDEX_MASK);
		buf->b_ml.ml_line_ptr = ptr;
		buf->b_ml.ml_line_lnum = lnum;
		buf->b_ml.ml_flags &= ~ML_LINE_DIRTY;
	}
	if (will_change)
		buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS);

	return buf->b_ml.ml_line_ptr;
}

/*
 * Check if a line that was just obtained by a call to ml_get
 * is in allocated memory.
 */
	int
ml_line_alloced()
{
	return (curbuf->b_ml.ml_flags & ML_LINE_DIRTY);
}

/*
 * append a line after lnum (may be 0 to insert a line in front of the file)
 *
 *   newfile: TRUE when starting to edit a new file, meaning that pe_old_lnum
 *				will be set for recovery
 *
 * return FAIL for failure, OK otherwise
 */
	int
ml_append(lnum, line, len, newfile)
	linenr_t	lnum;			/* append after this line (can be 0) */
	char_u		*line;			/* text of the new line */
	colnr_t		len;			/* length of new line, including NUL, or 0 */
	int			newfile;		/* flag, see above */
{
 	if (curbuf->b_ml.ml_line_lnum != 0)
		ml_flush_line(curbuf);
	return ml_append_int(curbuf, lnum, line, len, newfile);
}

	static int
ml_append_int(buf, lnum, line, len, newfile)
	BUF			*buf;
	linenr_t	lnum;			/* append after this line (can be 0) */
	char_u		*line;			/* text of the new line */
	colnr_t		len;			/* length of line, including NUL, or 0 */
	int			newfile;		/* flag, see above */
{
	int			i;
	int			line_count;		/* number of indexes in current block */
	int			offset;
	int			from, to;
	int			space_needed;	/* space needed for new line */
	int			page_size;
	int			page_count;
	int			db_idx;			/* index for lnum in data block */
	BHDR		*hp;
	MEMFILE		*mfp;
	DATA_BL		*dp;
	PTR_BL		*pp;
	IPTR		*ip;

										/* lnum out of range */
	if (lnum > buf->b_ml.ml_line_count || buf->b_ml.ml_mfp == NULL)
		return FAIL;
	
	if (lowest_marked && lowest_marked > lnum)
		lowest_marked = lnum + 1;

	if (len == 0)
		len = STRLEN(line) + 1;			/* space needed for the text */
	space_needed = len + INDEX_SIZE;	/* space needed for text + index */

	mfp = buf->b_ml.ml_mfp;
	page_size = mfp->mf_page_size;

/*
 * find the data block containing the previous line
 * This also fills the stack with the blocks from the root to the data block
 * This also releases any locked block.
 */
	if ((hp = ml_find_line(buf, lnum == 0 ? (linenr_t)1 : lnum,
													ML_INSERT)) == NULL)
		return FAIL;

	buf->b_ml.ml_flags &= ~ML_EMPTY;

	if (lnum == 0)				/* got line one instead, correct db_idx */
		db_idx = -1;			/* careful, it is negative! */
	else
		db_idx = lnum - buf->b_ml.ml_locked_low;
				/* get line count before the insertion */
	line_count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low;

	dp = (DATA_BL *)(hp->bh_data);

/*
 * If
 * - there is not enough room in the current block
 * - appending to the last line in the block
 * - not appending to the last line in the file
 * insert in front of the next block.
 */
	if ((int)dp->db_free < space_needed && db_idx == line_count - 1 &&
											lnum < buf->b_ml.ml_line_count)
	{
		/*
		 * Now that the line is not going to be inserted in the block that we
		 * expected, the line count has to be adjusted in the pointer blocks
		 * by using ml_locked_lineadd.
		 */
		--(buf->b_ml.ml_locked_lineadd);
		--(buf->b_ml.ml_locked_high);
		if ((hp = ml_find_line(buf, lnum + 1, ML_INSERT)) == NULL)
			return FAIL;

		db_idx = -1;				/* careful, it is negative! */
					/* get line count before the insertion */
		line_count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low;
		CHECK(buf->b_ml.ml_locked_low != lnum + 1, "locked_low != lnum + 1");

		dp = (DATA_BL *)(hp->bh_data);
	}

	++buf->b_ml.ml_line_count;

	if ((int)dp->db_free >= space_needed)		/* enough room in data block */
	{
/*
 * Insert new line in existing data block, or in data block allocated above.
 */
		dp->db_txt_start -= len;
		dp->db_free -= space_needed;
		++(dp->db_line_count);

		/*
		 * move the text of the lines that follow to the front
		 * adjust the indexes of the lines that follow
		 */
		if (line_count > db_idx + 1)		/* if there are following lines */
		{
			/*
			 * Offset is the start of the previous line.
			 * This will become the character just after the new line.
			 */
			if (db_idx < 0)
				offset = dp->db_txt_end;
			else
				offset = ((dp->db_index[db_idx]) & DB_INDEX_MASK);
			vim_memmove((char *)dp + dp->db_txt_start,
										  (char *)dp + dp->db_txt_start + len,
								 (size_t)(offset - (dp->db_txt_start + len)));
			for (i = line_count - 1; i > db_idx; --i)
				dp->db_index[i + 1] = dp->db_index[i] - len;
			dp->db_index[db_idx + 1] = offset - len;
		}
		else								/* add line at the end */
			dp->db_index[db_idx + 1] = dp->db_txt_start;

		/*
		 * copy the text into the block
		 */
		vim_memmove((char *)dp + dp->db_index[db_idx + 1], line, (size_t)len);

		/*
		 * Mark the block dirty.
		 */
		buf->b_ml.ml_flags |= ML_LOCKED_DIRTY;
		if (!newfile)
			buf->b_ml.ml_flags |= ML_LOCKED_POS;
	}
	else			/* not enough space in data block */
	{
/*
 * If there is not enough room we have to create a new data block and copy some
 * lines into it.
 * Then we have to insert an entry in the pointer block.
 * If this pointer block also is full, we go up another block, and so on, up
 * to the root if necessary.
 * The line counts in the pointer blocks have already been adjusted by
 * ml_find_line().
 */
		long		line_count_left, line_count_right;
		int			page_count_left, page_count_right;
		BHDR		*hp_left;
		BHDR		*hp_right;
		BHDR		*hp_new;
		int			lines_moved;
		int			data_moved = 0;			/* init to shut up gcc */
		int			total_moved = 0;		/* init to shut up gcc */
		DATA_BL		*dp_right, *dp_left;
		int			stack_idx;
		int			in_left;
		int			lineadd;
		blocknr_t	bnum_left, bnum_right;
		linenr_t	lnum_left, lnum_right;
		int			pb_idx;
		PTR_BL		*pp_new;

		/*
		 * We are going to allocate a new data block. Depending on the
		 * situation it will be put to the left or right of the existing
		 * block.  If possible we put the new line in the left block and move
		 * the lines after it to the right block. Otherwise the new line is
		 * also put in the right block. This method is more efficient when
		 * inserting a lot of lines at one place.
		 */
		if (db_idx < 0)			/* left block is new, right block is existing */
		{
			lines_moved = 0;
			in_left = TRUE;
			/* space_needed does not change */
		}
		else					/* left block is existing, right block is new */
		{
			lines_moved = line_count - db_idx - 1;
			if (lines_moved == 0)
				in_left = FALSE;		/* put new line in right block */
										/* space_needed does not change */
			else
			{
				data_moved = ((dp->db_index[db_idx]) & DB_INDEX_MASK) -
															dp->db_txt_start;
				total_moved = data_moved + lines_moved * INDEX_SIZE;
				if ((int)dp->db_free + total_moved >= space_needed)
				{
					in_left = TRUE;		/* put new line in left block */
					space_needed = total_moved;
				}
				else
				{
					in_left = FALSE;		/* put new line in right block */
					space_needed += total_moved;
				}
			}
		}

		page_count = ((space_needed + HEADER_SIZE) + page_size - 1) / page_size;
		if ((hp_new = ml_new_data(mfp, newfile, page_count)) == NULL)
		{
						/* correct line counts in pointer blocks */
			--(buf->b_ml.ml_locked_lineadd);
			--(buf->b_ml.ml_locked_high);
			return FAIL;
		}
		if (db_idx < 0)			/* left block is new */
		{
			hp_left = hp_new;
			hp_right = hp;
			line_count_left = 0;
			line_count_right = line_count;
		}
		else					/* right block is new */
		{
			hp_left = hp;
			hp_right = hp_new;
			line_count_left = line_count;
			line_count_right = 0;
		}
		dp_right = (DATA_BL *)(hp_right->bh_data);
		dp_left = (DATA_BL *)(hp_left->bh_data);
		bnum_left = hp_left->bh_bnum;
		bnum_right = hp_right->bh_bnum;
		page_count_left = hp_left->bh_page_count;
		page_count_right = hp_right->bh_page_count;

		/*
		 * May move the new line into the right/new block.
		 */
		if (!in_left)
		{
			dp_right->db_txt_start -= len;
			dp_right->db_free -= len + INDEX_SIZE;
			dp_right->db_index[0] = dp_right->db_txt_start;
			vim_memmove((char *)dp_right + dp_right->db_txt_start,
														   line, (size_t)len);
			++line_count_right;
		}
		/*
		 * may move lines from the left/old block to the right/new one.
		 */
		if (lines_moved)
		{
			/*
			 */
			dp_right->db_txt_start -= data_moved;
			dp_right->db_free -= total_moved;
			vim_memmove((char *)dp_right + dp_right->db_txt_start,
				 		(char *)dp_left + dp_left->db_txt_start,
						(size_t)data_moved);
			offset = dp_right->db_txt_start - dp_left->db_txt_start;
			dp_left->db_txt_start += data_moved;
			dp_left->db_free += total_moved;

			/*
			 * update indexes in the new block
			 */
			for (to = line_count_right, from = db_idx + 1;
										 from < line_count_left; ++from, ++to)
				dp_right->db_index[to] = dp->db_index[from] + offset;
			line_count_right += lines_moved;
			line_count_left -= lines_moved;
		}

		/*
		 * May move the new line into the left (old or new) block.
		 */
		if (in_left)
		{
			dp_left->db_txt_start -= len;
			dp_left->db_free -= len + INDEX_SIZE;
			dp_left->db_index[line_count_left] = dp_left->db_txt_start;
			vim_memmove((char *)dp_left + dp_left->db_txt_start,
														   line, (size_t)len);
			++line_count_left;
		}

		if (db_idx < 0)			/* left block is new */
		{
			lnum_left = lnum + 1;
			lnum_right = 0;
		}
		else					/* right block is new */
		{
			lnum_left = 0;
			if (in_left)
				lnum_right = lnum + 2;
			else
				lnum_right = lnum + 1;
		}
		dp_left->db_line_count = line_count_left;
		dp_right->db_line_count = line_count_right;

		/*
		 * release the two data blocks
		 * The new one (hp_new) already has a correct blocknumber.
		 * The old one (hp, in ml_locked) gets a positive blocknumber if
		 * we changed it and we are not editing a new file.
		 */
		if (lines_moved || in_left)
			buf->b_ml.ml_flags |= ML_LOCKED_DIRTY;
		if (!newfile && db_idx >= 0 && in_left)
			buf->b_ml.ml_flags |= ML_LOCKED_POS;
		mf_put(mfp, hp_new, TRUE, FALSE);

		/*
		 * flush the old data block
		 * set ml_locked_lineadd to 0, because the updating of the
		 * pointer blocks is done below
		 */
		lineadd = buf->b_ml.ml_locked_lineadd;
		buf->b_ml.ml_locked_lineadd = 0;
		ml_find_line(buf, (linenr_t)0, ML_FLUSH);	/* flush data block */

		/*
		 * update pointer blocks for the new data block
		 */
		for (stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; --stack_idx)
		{
			ip = &(buf->b_ml.ml_stack[stack_idx]);
			pb_idx = ip->ip_index;
			if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL)
				return FAIL;
			pp = (PTR_BL *)(hp->bh_data);	/* must be pointer block */
			if (pp->pb_id != PTR_ID)
			{
				EMSG("pointer block id wrong 3");
				mf_put(mfp, hp, FALSE, FALSE);
				return FAIL;
			}
			/*
			 * TODO: If the pointer block is full and we are adding at the end
			 * try to insert in front of the next block
			 */
			if (pp->pb_count < pp->pb_count_max)	/* block not full, add one entry */
			{
				if (pb_idx + 1 < (int)pp->pb_count)
					vim_memmove(&pp->pb_pointer[pb_idx + 2],
								&pp->pb_pointer[pb_idx + 1],
						(size_t)(pp->pb_count - pb_idx - 1) * sizeof(PTR_EN));
				++pp->pb_count;
				pp->pb_pointer[pb_idx].pe_line_count = line_count_left;
				pp->pb_pointer[pb_idx].pe_bnum = bnum_left;
				pp->pb_pointer[pb_idx].pe_page_count = page_count_left;
				pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right;
				pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right;
				pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right;

				if (lnum_left != 0)
					pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left;
				if (lnum_right != 0)
					pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right;

				mf_put(mfp, hp, TRUE, FALSE);
				buf->b_ml.ml_stack_top = stack_idx + 1;		/* truncate stack */

				if (lineadd)
				{
					--(buf->b_ml.ml_stack_top);
						/* fix line count for rest of blocks in the stack */
					ml_lineadd(buf, lineadd);
														/* fix stack itself */
					buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high +=
																	  lineadd;
					++(buf->b_ml.ml_stack_top);
				}

				return OK;
			}
			else						/* pointer block full */
			{
				/*
				 * split the pointer block
				 * allocate a new pointer block
				 * move some of the pointer into the new block
				 * prepare for updating the parent block
				 */
				for (;;)		/* do this twice when splitting block 1 */
				{
					hp_new = ml_new_ptr(mfp);
					if (hp_new == NULL)		/* TODO: try to fix tree */
						return FAIL;
					pp_new = (PTR_BL *)(hp_new->bh_data);

					if (hp->bh_bnum != 1)
						break;

					/*
					 * if block 1 becomes full the tree is given an extra level
					 * The pointers from block 1 are moved into the new block.
					 * block 1 is updated to point to the new block
					 * then continue to split the new block
					 */
					vim_memmove(pp_new, pp, (size_t)page_size);
					pp->pb_count = 1;
					pp->pb_pointer[0].pe_bnum = hp_new->bh_bnum;
					pp->pb_pointer[0].pe_line_count = buf->b_ml.ml_line_count;
					pp->pb_pointer[0].pe_old_lnum = 1;
					pp->pb_pointer[0].pe_page_count = 1;
					mf_put(mfp, hp, TRUE, FALSE);	/* release block 1 */
					hp = hp_new;					/* new block is to be split */
					pp = pp_new;
					CHECK(stack_idx != 0, "stack_idx should be 0");
					ip->ip_index = 0;
					++stack_idx;		/* do block 1 again later */
				}
				/*
				 * move the pointers after the current one to the new block
				 * If there are none, the new entry will be in the new block.
				 */
				total_moved = pp->pb_count - pb_idx - 1;
				if (total_moved)
				{
					vim_memmove(&pp_new->pb_pointer[0],
								&pp->pb_pointer[pb_idx + 1],
								(size_t)(total_moved) * sizeof(PTR_EN));
					pp_new->pb_count = total_moved;
					pp->pb_count -= total_moved - 1;
					pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right;
					pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right;
					pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right;
					if (lnum_right)
						pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right;
				}
				else
				{
					pp_new->pb_count = 1;
					pp_new->pb_pointer[0].pe_bnum = bnum_right;
					pp_new->pb_pointer[0].pe_line_count = line_count_right;
					pp_new->pb_pointer[0].pe_page_count = page_count_right;
					pp_new->pb_pointer[0].pe_old_lnum = lnum_right;
				}
				pp->pb_pointer[pb_idx].pe_bnum = bnum_left;
				pp->pb_pointer[pb_idx].pe_line_count = line_count_left;
				pp->pb_pointer[pb_idx].pe_page_count = page_count_left;
				if (lnum_left)
					pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left;
				lnum_left = 0;
				lnum_right = 0;

				/*
				 * recompute line counts
				 */
				line_count_right = 0;
				for (i = 0; i < (int)pp_new->pb_count; ++i)
					line_count_right += pp_new->pb_pointer[i].pe_line_count;
				line_count_left = 0;
				for (i = 0; i < (int)pp->pb_count; ++i)
					line_count_left += pp->pb_pointer[i].pe_line_count;

				bnum_left = hp->bh_bnum;
				bnum_right = hp_new->bh_bnum;
				page_count_left = 1;
				page_count_right = 1;
				mf_put(mfp, hp, TRUE, FALSE);
				mf_put(mfp, hp_new, TRUE, FALSE);
			}
		}
		EMSG("Updated too many blocks?");
		buf->b_ml.ml_stack_top = 0;		/* invalidate stack */
	}
	return OK;
}

/*
 * replace line lnum, with buffering, in current buffer
 *
 * If copy is TRUE, make a copy of the line, otherwise the line has been
 * copied to allocated memory already.
 *
 * return FAIL for failure, OK otherwise
 */
	int
ml_replace(lnum, line, copy)
	linenr_t	lnum;
	char_u		*line;
	int			copy;
{
	if (line == NULL)			/* just checking... */
		return FAIL;

	if (curbuf->b_ml.ml_line_lnum != lnum)			/* other line buffered */
		ml_flush_line(curbuf);						/* flush it */
	else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY)	/* same line allocated */
		vim_free(curbuf->b_ml.ml_line_ptr);			/* free it */
	if (copy && (line = strsave(line)) == NULL)		/* allocate memory */
		return FAIL;
	curbuf->b_ml.ml_line_ptr = line;
	curbuf->b_ml.ml_line_lnum = lnum;
	curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY;

	return OK;
}

/*
 * delete line 'lnum'
 *
 * return FAIL for failure, OK otherwise
 */
	int
ml_delete(lnum, message)
	linenr_t	lnum;
	int			message;
{
	ml_flush_line(curbuf);
	return ml_delete_int(curbuf, lnum, message);
}

	static int
ml_delete_int(buf, lnum, message)
	BUF			*buf;
	linenr_t	lnum;
	int			message;
{
	BHDR	*hp;
	MEMFILE	*mfp;
	DATA_BL	*dp;
	PTR_BL	*pp;
	IPTR	*ip;
	int		count;			/* number of entries in block */
	int		idx;
	int		stack_idx;
	int		text_start;
	int		line_start;
	int		line_size;
	int		i;

	if (lnum < 1 || lnum > buf->b_ml.ml_line_count)
		return FAIL;

	if (lowest_marked && lowest_marked > lnum)
		lowest_marked--;

/*
 * If the file becomes empty the last line is replaced by an empty line.
 */
	if (buf->b_ml.ml_line_count == 1)		/* file becomes empty */
	{
		if (message)
			keep_msg = no_lines_msg;
		i = ml_replace((linenr_t)1, (char_u *)"", TRUE);
		buf->b_ml.ml_flags |= ML_EMPTY;
		return i;
	}

/*
 * find the data block containing the line
 * This also fills the stack with the blocks from the root to the data block
 * This also releases any locked block.
 */
	mfp = buf->b_ml.ml_mfp;
	if (mfp == NULL)
		return FAIL;

	if ((hp = ml_find_line(buf, lnum, ML_DELETE)) == NULL)
		return FAIL;

	dp = (DATA_BL *)(hp->bh_data);
			/* compute line count before the delete */
	count = (long)(buf->b_ml.ml_locked_high) - (long)(buf->b_ml.ml_locked_low) + 2;
	idx = lnum - buf->b_ml.ml_locked_low;

	--buf->b_ml.ml_line_count;

/*
 * special case: If there is only one line in the data block it becomes empty.
 * Then we have to remove the entry, pointing to this data block, from the
 * pointer block. If this pointer block also becomes empty, we go up another
 * block, and so on, up to the root if necessary.
 * The line counts in the pointer blocks have already been adjusted by
 * ml_find_line().
 */
	if (count == 1)
	{
		mf_free(mfp, hp);		/* free the data block */
		buf->b_ml.ml_locked = NULL;

		for (stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; --stack_idx)
		{
			buf->b_ml.ml_stack_top = 0;		/* stack is invalid when failing */
			ip = &(buf->b_ml.ml_stack[stack_idx]);
			idx = ip->ip_index;
			if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL)
				return FAIL;
			pp = (PTR_BL *)(hp->bh_data);	/* must be pointer block */
			if (pp->pb_id != PTR_ID)
			{
				EMSG("pointer block id wrong 4");
				mf_put(mfp, hp, FALSE, FALSE);
				return FAIL;
			}
			count = --(pp->pb_count);
			if (count == 0)			/* the pointer block becomes empty! */
				mf_free(mfp, hp);
			else
			{
				if (count != idx)		/* move entries after the deleted one */
					vim_memmove(&pp->pb_pointer[idx], &pp->pb_pointer[idx + 1],
									  (size_t)(count - idx) * sizeof(PTR_EN));
				mf_put(mfp, hp, TRUE, FALSE);

				buf->b_ml.ml_stack_top = stack_idx;		/* truncate stack */
					/* fix line count for rest of blocks in the stack */
				if (buf->b_ml.ml_locked_lineadd)
				{
					ml_lineadd(buf, buf->b_ml.ml_locked_lineadd);
					buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high +=
												buf->b_ml.ml_locked_lineadd;
				}
				++(buf->b_ml.ml_stack_top);

				return OK;
			}
		}
		CHECK(1, "deleted block 1?");

		return OK;
	}

	/*
	 * delete the text by moving the next lines forwards
	 */
	text_start = dp->db_txt_start;
	line_start = ((dp->db_index[idx]) & DB_INDEX_MASK);
	if (idx == 0)				/* first line in block, text at the end */
		line_size = dp->db_txt_end - line_start;
	else
		line_size = ((dp->db_index[idx - 1]) & DB_INDEX_MASK) - line_start;
	vim_memmove((char *)dp + text_start + line_size, (char *)dp + text_start,
										   (size_t)(line_start - text_start));

	/*
	 * delete the index by moving the next indexes backwards
	 * Adjust the indexes for the text movement.
	 */
	for (i = idx; i < count - 1; ++i)
		dp->db_index[i] = dp->db_index[i + 1] + line_size;

	dp->db_free += line_size + INDEX_SIZE;
	dp->db_txt_start += line_size;
	--(dp->db_line_count);

	/*
	 * mark the block dirty and make sure it is in the file (for recovery)
	 */
	buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS);

	return OK;
}

/*
 * set the B_MARKED flag for line 'lnum'
 */
	void
ml_setmarked(lnum)
	linenr_t lnum;
{
	BHDR	*hp;
	DATA_BL	*dp;
									/* invalid line number */
	if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count ||
												curbuf->b_ml.ml_mfp == NULL)
		return;						/* give error message? */

	if (lowest_marked == 0 || lowest_marked > lnum)
		lowest_marked = lnum;

	/*
	 * find the data block containing the line
	 * This also fills the stack with the blocks from the root to the data block
	 * This also releases any locked block.
	 */
	if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL)
		return;				/* give error message? */

	dp = (DATA_BL *)(hp->bh_data);
	dp->db_index[lnum - curbuf->b_ml.ml_locked_low] |= DB_MARKED;
	curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY;
}

/*
 * find the first line with its B_MARKED flag set
 */
	linenr_t
ml_firstmarked()
{
	BHDR		*hp;
	DATA_BL		*dp;
	linenr_t	lnum;
	int			i;

	if (curbuf->b_ml.ml_mfp == NULL)
		return (linenr_t) 0;

	/*
	 * The search starts with lowest_marked line. This is the last line where
	 * a mark was found, adjusted by inserting/deleting lines.
	 */
	for (lnum = lowest_marked; lnum <= curbuf->b_ml.ml_line_count; )
	{
		/*
		 * Find the data block containing the line.
		 * This also fills the stack with the blocks from the root to the data
		 * block This also releases any locked block.
		 */
		if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL)
			return (linenr_t)0;				/* give error message? */

		dp = (DATA_BL *)(hp->bh_data);

		for (i = lnum - curbuf->b_ml.ml_locked_low;
							lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum)
			if ((dp->db_index[i]) & DB_MARKED)
			{
				(dp->db_index[i]) &= DB_INDEX_MASK;
				curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY;
				lowest_marked = lnum + 1;
				return lnum;
			}
	}

	return (linenr_t) 0;
}

/*
 * return TRUE if line 'lnum' has a mark
 */
	int
ml_has_mark(lnum)
	linenr_t	lnum;
{
	BHDR		*hp;
	DATA_BL		*dp;

	if (curbuf->b_ml.ml_mfp == NULL ||
						(hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL)
		return FALSE;

	dp = (DATA_BL *)(hp->bh_data);
	return (int)((dp->db_index[lnum - curbuf->b_ml.ml_locked_low]) & DB_MARKED);
}

/*
 * clear all DB_MARKED flags
 */
	void
ml_clearmarked()
{
	BHDR		*hp;
	DATA_BL		*dp;
	linenr_t	lnum;
	int			i;

	if (curbuf->b_ml.ml_mfp == NULL)		/* nothing to do */
		return;

	/*
	 * The search starts with line lowest_marked.
	 */
	for (lnum = lowest_marked; lnum <= curbuf->b_ml.ml_line_count; )
	{
		/*
		 * Find the data block containing the line.
		 * This also fills the stack with the blocks from the root to the data block
		 * This also releases any locked block.
		 */
		if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL)
			return;				/* give error message? */

		dp = (DATA_BL *)(hp->bh_data);

		for (i = lnum - curbuf->b_ml.ml_locked_low;
							lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum)
			if ((dp->db_index[i]) & DB_MARKED)
			{
				(dp->db_index[i]) &= DB_INDEX_MASK;
				curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY;
			}
	}

	lowest_marked = 0;
	return;
}

/*
 * flush ml_line if necessary
 */
	static void
ml_flush_line(buf)
	BUF		*buf;
{
	BHDR		*hp;
	DATA_BL		*dp;
	linenr_t	lnum;
	char_u		*new_line;
	char_u		*old_line;
	colnr_t		new_len;
	int			old_len;
	int			extra;
	int			idx;
	int			start;
	int			count;
	int			i;

 	if (buf->b_ml.ml_line_lnum == 0 ||
						buf->b_ml.ml_mfp == NULL)		/* nothing to do */
		return;

	if (buf->b_ml.ml_flags & ML_LINE_DIRTY)
	{
		lnum = buf->b_ml.ml_line_lnum;
		new_line = buf->b_ml.ml_line_ptr;

		hp = ml_find_line(buf, lnum, ML_FIND);
		if (hp == NULL)
			EMSGN("Cannot find line %ld", lnum);
		else
		{
			dp = (DATA_BL *)(hp->bh_data);
			idx = lnum - buf->b_ml.ml_locked_low;
			start = ((dp->db_index[idx]) & DB_INDEX_MASK);
			old_line = (char_u *)dp + start;
			if (idx == 0)		/* line is last in block */
				old_len = dp->db_txt_end - start;
			else				/* text of previous line follows */
				old_len = (dp->db_index[idx - 1] & DB_INDEX_MASK) - start;
			new_len = STRLEN(new_line) + 1;
			extra = new_len - old_len;		/* negative if lines gets smaller */

			/*
			 * if new line fits in data block, replace directly
			 */
			if ((int)dp->db_free >= extra)
			{
					/* if the length changes and there are following lines */
				count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1;
				if (extra != 0 && idx < count - 1)
				{
						/* move text of following lines */
					vim_memmove((char *)dp + dp->db_txt_start - extra,
								(char *)dp + dp->db_txt_start,
								(size_t)(start - dp->db_txt_start));

						/* adjust pointers of this and following lines */
					for (i = idx + 1; i < count; ++i)
						dp->db_index[i] -= extra;
				}
				dp->db_index[idx] -= extra;

					/* adjust free space */
				dp->db_free -= extra;
				dp->db_txt_start -= extra;

					/* copy new line into the data block */
				vim_memmove(old_line - extra, new_line, (size_t)new_len);
				buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS);
			}
			else
			{
				/*
				 * Cannot do it in one data block: delete and append.
				 */
					/* How about handling errors??? */
				(void)ml_delete_int(buf, lnum, FALSE);
				(void)ml_append_int(buf, lnum - 1, new_line, new_len, FALSE);
			}
		}
		vim_free(new_line);
	}

	buf->b_ml.ml_line_lnum = 0;
}

/*
 * create a new, empty, data block
 */
	static BHDR *
ml_new_data(mfp, negative, page_count)
	MEMFILE		*mfp;
	int			negative;
	int			page_count;
{
	BHDR		*hp;
	DATA_BL		*dp;

	if ((hp = mf_new(mfp, negative, page_count)) == NULL)
		return NULL;
	
	dp = (DATA_BL *)(hp->bh_data);
	dp->db_id = DATA_ID;
	dp->db_txt_start = dp->db_txt_end = page_count * mfp->mf_page_size;
	dp->db_free = dp->db_txt_start - HEADER_SIZE;
	dp->db_line_count = 0;

	return hp;
}

/*
 * create a new, empty, pointer block
 */
	static BHDR *
ml_new_ptr(mfp)
	MEMFILE		*mfp;
{
	BHDR		*hp;
	PTR_BL		*pp;

	if ((hp = mf_new(mfp, FALSE, 1)) == NULL)
		return NULL;
	
	pp = (PTR_BL *)(hp->bh_data);
	pp->pb_id = PTR_ID;
	pp->pb_count = 0;
	pp->pb_count_max = (mfp->mf_page_size - sizeof(PTR_BL)) / sizeof(PTR_EN) + 1;

	return hp;
}

/*
 * lookup line 'lnum' in a memline
 *
 *   action: if ML_DELETE or ML_INSERT the line count is updated while searching
 *			 if ML_FLUSH only flush a locked block
 *			 if ML_FIND just find the line
 *
 * If the block was found it is locked and put in ml_locked.
 * The stack is updated to lead to the locked block. The ip_high field in
 * the stack is updated to reflect the last line in the block AFTER the
 * insert or delete, also if the pointer block has not been updated yet. But
 * if if ml_locked != NULL ml_locked_lineadd must be added to ip_high.
 *
 * return: NULL for failure, pointer to block header otherwise
 */
	static BHDR *
ml_find_line(buf, lnum, action)
	BUF			*buf;
	linenr_t	lnum;
	int			action;
{
	DATA_BL		*dp;
	PTR_BL		*pp;
	IPTR		*ip;
	BHDR		*hp;
	MEMFILE		*mfp;
	linenr_t	t;
	blocknr_t	bnum, bnum2;
	int			dirty;
	linenr_t	low, high;
	int			top;
	int			page_count;
	int			idx;

	mfp = buf->b_ml.ml_mfp;

	/*
	 * If there is a locked block check if the wanted line is in it.
	 * If not, flush and release the locked block.
	 * Don't do this for ML_INSERT_SAME, because the stack need to be updated.
	 * Don't do this for ML_FLUSH, because we want to flush the locked block.
	 */
	if (buf->b_ml.ml_locked)
	{
		if (ML_SIMPLE(action) && buf->b_ml.ml_locked_low <= lnum &&
									buf->b_ml.ml_locked_high >= lnum)
		{
				/* remember to update pointer blocks and stack later */
			if (action == ML_INSERT)
			{
				++(buf->b_ml.ml_locked_lineadd);
				++(buf->b_ml.ml_locked_high);
			}
			else if (action == ML_DELETE)
			{
				--(buf->b_ml.ml_locked_lineadd);
				--(buf->b_ml.ml_locked_high);
			}
			return (buf->b_ml.ml_locked);
		}

		mf_put(mfp, buf->b_ml.ml_locked, buf->b_ml.ml_flags & ML_LOCKED_DIRTY,
											buf->b_ml.ml_flags & ML_LOCKED_POS);
		buf->b_ml.ml_locked = NULL;

			/*
			 * if lines have been added or deleted in the locked block, need to
			 * update the line count in pointer blocks
			 */
		if (buf->b_ml.ml_locked_lineadd)
			ml_lineadd(buf, buf->b_ml.ml_locked_lineadd);
	}

	if (action == ML_FLUSH)			/* nothing else to do */
		return NULL;

	bnum = 1;						/* start at the root of the tree */
	page_count = 1;
	low = 1;
	high = buf->b_ml.ml_line_count;

	if (action == ML_FIND)		/* first try stack entries */
	{
		for (top = buf->b_ml.ml_stack_top - 1; top >= 0; --top)
		{
			ip = &(buf->b_ml.ml_stack[top]);
			if (ip->ip_low <= lnum && ip->ip_high >= lnum)
			{
				bnum = ip->ip_bnum;
				low = ip->ip_low;
				high = ip->ip_high;
				buf->b_ml.ml_stack_top = top;	/* truncate stack at prev entry */
				break;
			}
		}
		if (top < 0)
			buf->b_ml.ml_stack_top = 0;			/* not found, start at the root */
	}
	else		/* ML_DELETE or ML_INSERT */
		buf->b_ml.ml_stack_top = 0;		/* start at the root */

/*
 * search downwards in the tree until a data block is found
 */
	for (;;)
	{
		if ((hp = mf_get(mfp, bnum, page_count)) == NULL)
			goto error_noblock;

		/*
		 * update high for insert/delete
		 */
		if (action == ML_INSERT)
			++high;
		else if (action == ML_DELETE)
			--high;

		dp = (DATA_BL *)(hp->bh_data);
		if (dp->db_id == DATA_ID)		/* data block */
		{
			buf->b_ml.ml_locked = hp;
			buf->b_ml.ml_locked_low = low;
			buf->b_ml.ml_locked_high = high;
			buf->b_ml.ml_locked_lineadd = 0;
			buf->b_ml.ml_flags &= ~(ML_LOCKED_DIRTY | ML_LOCKED_POS);
			return hp;
		}

		pp = (PTR_BL *)(dp);			/* must be pointer block */
		if (pp->pb_id != PTR_ID)
		{
			EMSG("pointer block id wrong");
			goto error_block;
		}

		if ((top = ml_add_stack(buf)) < 0)		/* add new entry to stack */
			goto error_block;
		ip = &(buf->b_ml.ml_stack[top]);
		ip->ip_bnum = bnum;
		ip->ip_low = low;
		ip->ip_high = high;
		ip->ip_index = -1;				/* index not known yet */

		dirty = FALSE;
		for (idx = 0; idx < (int)pp->pb_count; ++idx)
		{
			t = pp->pb_pointer[idx].pe_line_count;
			CHECK(t == 0, "pe_line_count is zero");
			if ((low += t) > lnum)
			{
				ip->ip_index = idx;
				bnum = pp->pb_pointer[idx].pe_bnum;
				page_count = pp->pb_pointer[idx].pe_page_count;
				high = low - 1;
				low -= t;

				/*
				 * a negative block number may have been changed
				 */
				if (bnum < 0)
				{
					bnum2 = mf_trans_del(mfp, bnum);
					if (bnum != bnum2)
					{
						bnum = bnum2;
						pp->pb_pointer[idx].pe_bnum = bnum;
						dirty = TRUE;
					}
				}

				break;
			}
		}
		if (idx >= (int)pp->pb_count)		/* past the end: something wrong! */
		{
			if (lnum > buf->b_ml.ml_line_count)
				EMSGN("line number out of range: %ld past the end",
											  lnum - buf->b_ml.ml_line_count);

			else
				EMSGN("line count wrong in block %ld", bnum);
			goto error_block;
		}
		if (action == ML_DELETE)
		{
			pp->pb_pointer[idx].pe_line_count--;
			dirty = TRUE;
		}
		else if (action == ML_INSERT)
		{
			pp->pb_pointer[idx].pe_line_count++;
			dirty = TRUE;
		}
		mf_put(mfp, hp, dirty, FALSE);
	}

error_block:
	mf_put(mfp, hp, FALSE, FALSE);
error_noblock:
/*
 * If action is ML_DELETE or ML_INSERT we have to correct the tree for
 * the incremented/decremented line counts, because there won't be a line
 * inserted/deleted after all.
 */
	if (action == ML_DELETE)
		ml_lineadd(buf, 1);
	else if (action == ML_INSERT)
		ml_lineadd(buf, -1);
	buf->b_ml.ml_stack_top = 0;
	return NULL;
}

/*
 * add an entry to the info pointer stack
 *
 * return -1 for failure, number of the new entry otherwise
 */
	static int
ml_add_stack(buf)
	BUF		*buf;
{
	int		top;
	IPTR	*newstack;

	top = buf->b_ml.ml_stack_top;

		/* may have to increase the stack size */
	if (top == buf->b_ml.ml_stack_size)
	{
		CHECK(top > 0, "Stack size increases");	/* more than 5 levels??? */

		newstack = (IPTR *)alloc((unsigned)sizeof(IPTR) *
										(buf->b_ml.ml_stack_size + STACK_INCR));
		if (newstack == NULL)
			return -1;
		vim_memmove(newstack, buf->b_ml.ml_stack, (size_t)top * sizeof(IPTR));
		vim_free(buf->b_ml.ml_stack);
		buf->b_ml.ml_stack = newstack;
		buf->b_ml.ml_stack_size += STACK_INCR;
	}

	buf->b_ml.ml_stack_top++;
	return top;
}

/*
 * Update the pointer blocks on the stack for inserted/deleted lines.
 * The stack itself is also updated.
 *
 * When a insert/delete line action fails, the line is not inserted/deleted,
 * but the pointer blocks have already been updated. That is fixed here by
 * walking through the stack.
 *
 * Count is the number of lines added, negative if lines have been deleted.
 */
	static void
ml_lineadd(buf, count)
	BUF			*buf;
	int			count;
{
	int			idx;
	IPTR		*ip;
	PTR_BL		*pp;
	MEMFILE		*mfp = buf->b_ml.ml_mfp;
	BHDR		*hp;

	for (idx = buf->b_ml.ml_stack_top - 1; idx >= 0; --idx)
	{
		ip = &(buf->b_ml.ml_stack[idx]);
		if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL)
			break;
		pp = (PTR_BL *)(hp->bh_data);	/* must be pointer block */
		if (pp->pb_id != PTR_ID)
		{
			mf_put(mfp, hp, FALSE, FALSE);
			EMSG("pointer block id wrong 2");
			break;
		}
		pp->pb_pointer[ip->ip_index].pe_line_count += count;
		ip->ip_high += count;
		mf_put(mfp, hp, TRUE, FALSE);
	}
}

/*
 * make swap file name out of the filename and a directory name
 */
	static char_u *
makeswapname(buf, dir_name)
	BUF		*buf;
	char_u	*dir_name;
{
	char_u		*r, *s, *fname;

#ifdef VMS
	r = modname(buf->b_xfilename, (char_u *)"_swp");
#else
	r = modname(buf->b_xfilename, (char_u *)".swp");
#endif
	/*
	 * do not use dir_name
	 * - if dir_name starts with '.' (use current directory)
	 * - if out of memory
	 */
	if (*dir_name == '.' || r == NULL)
		return r;

	fname = gettail(r);
	s = concat_fnames(dir_name, fname, TRUE);
	vim_free(r);
	return s;
}

/*
 * Find out what name to use for the swap file for buffer 'buf'.
 *
 * Several names are tried to find one that does not exist
 *
 * Note: if MAXNAMLEN is not correct, you will get error messages for
 *       not being able to open the swapfile
 */
	static char_u *
findswapname(buf, dirp, old_fname)
	BUF		*buf;
	char_u	**dirp;			/* pointer to list of directories */
	char_u	*old_fname;		/* don't give warning for this filename */
{
	char_u		*fname;
	int			n;
	time_t		x;
	char_u		*dir_name;

#ifdef AMIGA
	BPTR		fh;
#endif

#ifndef SHORT_FNAME
	int			r;
	FILE		*dummyfd = NULL;

/*
 * If we start editing a new file, e.g. "test.doc", which resides on an MSDOS
 * compatible filesystem, it is possible that the file "test.doc.swp" which we
 * create will be exactly the same file. To avoid this problem we temporarily
 * create "test.doc".
 */
	if (!(buf->b_p_sn || buf->b_shortname) && buf->b_xfilename &&
												getperm(buf->b_xfilename) < 0)
		dummyfd = fopen((char *)buf->b_xfilename, "w");
#endif

/*
 * Isolate a directory name from *dirp and put it in dir_name.
 * First allocate some memore to put the directory name in.
 */
	dir_name = alloc((unsigned)STRLEN(*dirp) + 1);
	if (dir_name != NULL)
		(void)copy_option_part(dirp, dir_name, 31000, ",");

/*
 * we try different names until we find one that does not exist yet
 */
	if (dir_name == NULL)			/* out of memory */
		fname = NULL;
	else
		fname = makeswapname(buf, dir_name);

	for (;;)
	{
		if (fname == NULL)		/* must be out of memory */
			break;
		if ((n = STRLEN(fname)) == 0)	/* safety check */
		{
			vim_free(fname);
			fname = NULL;
			break;
		}
#if (defined(UNIX) || defined(OS2)) && !defined(ARCHIE) && !defined(SHORT_FNAME)
/*
 * Some systems have a MS-DOS compatible filesystem that use 8.3 character
 * file names. If this is the first try and the swap file name does not fit in
 * 8.3, detect if this is the case, set shortname and try again.
 */
		if (fname[n - 1] == 'p' && !(buf->b_p_sn || buf->b_shortname))
		{
			char_u			*tail;
			char_u			*fname2;
			struct stat		s1, s2;
			int				f1, f2;
			int				created1 = FALSE, created2 = FALSE;
			int				same = FALSE;

			/*
			 * Check if swapfilename does not fit in 8.3:
			 * It either contains two dots or it is longer than 8 chars.
			 */
			tail = gettail(buf->b_xfilename);
			if (vim_strchr(tail, '.') != NULL || STRLEN(tail) > (size_t)8)
			{
				fname2 = alloc(n + 1);
				if (fname2 != NULL)
				{
					STRCPY(fname2, fname);
					if (vim_strchr(tail, '.') != NULL)
						fname2[n - 1] = 'x';	/* change ".swp" to ".swx" */
					else
						fname2[n - 5] += 1;		/* change "x.swp" to "y.swp" */
					/*
					 * may need to create the files to be able to use stat()
					 */
					f1 = open((char *)fname, O_RDONLY | O_EXTRA);
					if (f1 < 0)
					{
						f1 = open((char *)fname, O_RDWR|O_CREAT|O_EXCL|O_EXTRA
#ifdef AMIGA				/* Amiga has no mode argument */
																			);
#endif
#ifdef UNIX					/* open in rw------- mode */
															  , (mode_t)0600);
#endif
#if defined(MSDOS) || defined(WIN32) || defined(OS2)  /* open read/write */
														, S_IREAD | S_IWRITE);
#endif
#if defined(OS2)
						if (f1 < 0 && errno == ENOENT)
							same = TRUE;
#endif
						created1 = TRUE;
					}
					if (f1 >= 0)
					{
						f2 = open((char *)fname2, O_RDONLY | O_EXTRA);
						if (f2 < 0)
						{
							f2 = open((char *)fname2,
												 O_RDWR|O_CREAT|O_EXCL|O_EXTRA
#ifdef AMIGA				/* Amiga has no mode argument */
																			);
#endif
#ifdef UNIX					/* open in rw------- mode */
															  , (mode_t)0600);
#endif
#if defined(MSDOS) || defined(WIN32) || defined(OS2)  /* open read/write */
														, S_IREAD | S_IWRITE);
#endif
							created2 = TRUE;
						}
						if (f2 >= 0)
						{
							/*
							 * Both files exist now. If stat() returns the
							 * same device and inode they are the same file.
							 */
							if (fstat(f1, &s1) != -1 &&
										fstat(f2, &s2) != -1 &&
										s1.st_dev == s2.st_dev &&
										s1.st_ino == s2.st_ino)
								same = TRUE;
							close(f2);
							if (created2)
								vim_remove(fname2);
						}
						close(f1);
						if (created1)
							vim_remove(fname);
					}
					vim_free(fname2);
					if (same)
					{
						buf->b_shortname = TRUE;
						vim_free(fname);
						fname = makeswapname(buf, dir_name);
						continue;		/* try again with b_shortname set */
					}
				}
			}
		}
#endif
		/*
		 * check if the swapfile already exists
		 */
		if (getperm(fname) < 0)		/* it does not exist */
		{
#ifdef AMIGA
			fh = Open((UBYTE *)fname, (long)MODE_NEWFILE);
			/*
			 * on the Amiga getperm() will return -1 when the file exists but
			 * is being used by another program. This happens if you edit
			 * a file twice.
			 */
			if (fh != (BPTR)NULL)		/* can open file, OK */
			{
				Close(fh);
				break;
			}
			if (IoErr() != ERROR_OBJECT_IN_USE &&
											   IoErr() != ERROR_OBJECT_EXISTS)
#endif
				break;
		}
		/*
		 * A file name equal to old_fname is OK to use.
		 */
		if (old_fname != NULL && fnamecmp(fname, old_fname) == 0)
			break;

		/*
		 * get here when file already exists
		 */
		if (fname[n - 1] == 'p')		/* first try */
		{
#ifndef SHORT_FNAME
			/*
			 * on MS-DOS compatible filesystems (e.g. messydos) file.doc.swp
			 * and file.doc are the same file. To guess if this problem is
			 * present try if file.doc.swx exists. If it does, we set
			 * buf->b_shortname and try file_doc.swp (dots replaced by
			 * underscores for this file), and try again. If it doesn't we
			 * assume that "file.doc.swp" already exists.
			 */
			if (!(buf->b_p_sn || buf->b_shortname))		/* not tried yet */
			{
				fname[n - 1] = 'x';
				r = getperm(fname);			/* try "file.swx" */
				fname[n - 1] = 'p';
				if (r >= 0)					/* "file.swx" seems to exist */
				{
					buf->b_shortname = TRUE;
					vim_free(fname);
					fname = makeswapname(buf, dir_name);
					continue;		/* try again with '.' replaced by '_' */
				}
			}
#endif
			/*
			 * If we get here the ".swp" file really exists.
			 * Give an error message, unless recovering, no file name, we are
			 * viewing a help file or when the path of the file is different
			 * (happens when all .swp files are in one directory).
			 */
			if (!recoverymode && buf->b_xfilename != NULL && !buf->b_help)
			{
				int				fd;
				struct block0	b0;
				int				differ = FALSE;

				/*
				 * Try to read block 0 from the swap file to get the original
				 * file name (and inode number).
				 */
				fd = open((char *)fname, O_RDONLY | O_EXTRA);
				if (fd >= 0)
				{
					if (read(fd, (char *)&b0, sizeof(b0)) == sizeof(b0))
					{
						/*
						 * The name in the swap file may be "~user/path/file".
						 * Expand it first.
						 */
						expand_env(b0.b0_fname, NameBuff, MAXPATHL);
#ifdef CHECK_INODE
						if (fnamecmp_ino(buf->b_filename, NameBuff,
													 char_to_long(b0.b0_ino)))
							differ = TRUE;
#else
						if (fnamecmp(NameBuff, buf->b_filename) != 0)
							differ = TRUE;
#endif
					}
					close(fd);
				}
				if (differ == FALSE)
				{
					struct stat st;

					++no_wait_return;
#ifdef SLEEP_IN_EMSG
					++dont_sleep;
#endif
					(void)EMSG("ATTENTION");
#ifdef SLEEP_IN_EMSG
					--dont_sleep;
#endif
					MSG_OUTSTR("\nFound a swap file by the name \"");
					msg_home_replace(fname);
					MSG_OUTSTR("\"\n");
					swapfile_info(fname);
					MSG_OUTSTR("While opening file \"");
					msg_outtrans(buf->b_xfilename);
					MSG_OUTSTR("\"\n");
					if (stat((char *)buf->b_xfilename, &st) != -1)
					{
						MSG_OUTSTR("             dated: ");
						x = st.st_mtime;	/* Manx C can't do &st.st_mtime */
						MSG_OUTSTR(ctime(&x));
					}
					MSG_OUTSTR("\n(1) Another program may be editing the same file.\n");
					MSG_OUTSTR("    If this is the case, quit this edit session to avoid having\n");
					MSG_OUTSTR("    two different instances of the same file when making changes.\n");
					MSG_OUTSTR("\n(2) An edit session for this file crashed.\n");
					MSG_OUTSTR("    If this is the case, use \":recover\" or \"vim -r ");
					msg_outtrans(buf->b_xfilename);
					MSG_OUTSTR("\"\n    to recover the changes (see \":help recovery)\".\n");
					MSG_OUTSTR("    If you did this already, delete the swap file \"");
					msg_outtrans(fname);
					MSG_OUTSTR("\"\n    to avoid this message.\n\n");
					cmdline_row = msg_row;
					--no_wait_return;
					need_wait_return = TRUE;		/* call wait_return later */
				}
			}
		}

		if (fname[n - 1] == 'a')	/* tried enough names, give up */
		{
			vim_free(fname);
			fname = NULL;
			break;
		}
		--fname[n - 1];				/* change last char of the name */
	}

	vim_free(dir_name);
#ifndef SHORT_FNAME
	if (dummyfd)		/* file has been created temporarily */
	{
		fclose(dummyfd);
		vim_remove(buf->b_xfilename);
	}
#endif
	return fname;
}

	static int
b0_magic_wrong(b0p)
	ZERO_BL *b0p;
{
	return (b0p->b0_magic_long != (long)B0_MAGIC_LONG ||
			b0p->b0_magic_int != (int)B0_MAGIC_INT ||
			b0p->b0_magic_short != (short)B0_MAGIC_SHORT ||
			b0p->b0_magic_char != B0_MAGIC_CHAR);
}

#ifdef CHECK_INODE
/*
 * Compare current file name with file name from swap file.
 * Try to use inode numbers when possible.
 * Return non-zero when files are different.
 *
 * When comparing file names a few things have to be taken into consideration:
 * - When working over a network the full path of a file depends on the host.
 *   We check the inode number if possible.  It is not 100% reliable though,
 *   because the device number cannot be used over a network.
 * - When a file does not exist yet (editing a new file) there is no inode
 *   number.
 * - The file name in a swap file may not be valid on the current host.  The
 *	 "~user" form is used whenever possible to avoid this.
 *
 * This is getting complicated, let's make a table:
 *
 *				ino_c  ino_s  fname_c  fname_s  differ =
 *
 * both files exist -> compare inode numbers:
 *				!= 0   != 0     X        X      ino_c != ino_s
 *
 * inode number(s) unknown, file names available -> compare file names
 *				== 0    X       OK       OK     fname_c != fname_s
 *				 X     == 0     OK       OK     fname_c != fname_s
 *
 * current file doesn't exist, file for swap file exist, file name(s) not
 * available -> probably different
 *				== 0   != 0    FAIL      X      TRUE
 *				== 0   != 0     X       FAIL    TRUE
 *
 * current file exists, inode for swap unknown, file name(s) not
 * available -> probably different
 *				!= 0   == 0    FAIL      X      TRUE
 *				!= 0   == 0     X       FAIL    TRUE
 *
 * current file doesn't exist, inode for swap unknown, one file name not
 * available -> probably different
 *				== 0   == 0    FAIL      OK     TRUE
 *				== 0   == 0     OK      FAIL    TRUE
 *
 * current file doesn't exist, inode for swap unknown, both file names not
 * available -> probably same file
 *				== 0   == 0    FAIL     FAIL    FALSE
 */

	static int
fnamecmp_ino(fname_c, fname_s, ino_block0)
	char_u		*fname_c;			/* current file name */
	char_u		*fname_s;			/* file name from swap file */
	long		ino_block0;
{
	struct stat	st;
	long		ino_c = 0;			/* ino of current file */
	long		ino_s;				/* ino of file from swap file */
	char_u		buf_c[MAXPATHL];	/* full path of fname_c */
	char_u		buf_s[MAXPATHL];	/* full path of fname_s */
	int			retval_c;			/* flag: buf_c valid */
	int			retval_s;			/* flag: buf_s valid */


	if (stat((char *)fname_c, &st) == 0)
		ino_c = st.st_ino;

	/*
	 * First we try to get the inode from the file name, because the inode in
	 * the swap file may be outdated.  If that fails (e.g. this path is not
	 * valid on this machine), use the inode from block 0.
	 */
	if (stat((char *)fname_s, &st) == 0)
		ino_s = st.st_ino;
	else
		ino_s = ino_block0;

	if (ino_c && ino_s)
		return (ino_c != ino_s);

	/*
	 * One of the inode numbers is unknown, try a forced FullName() and
	 * compare the file names.
	 */
	retval_c = FullName(fname_c, buf_c, MAXPATHL, TRUE);
	retval_s = FullName(fname_s, buf_s, MAXPATHL, TRUE);
	if (retval_c == OK && retval_s == OK)
		return (STRCMP(buf_c, buf_s) != 0);

	/*
	 * Can't compare inodes or filenames, guess that the files are different,
	 * unless both appear not to exist at all.
	 */
	if (ino_s == 0 && ino_c == 0 && retval_c == FAIL && retval_s == FAIL)
		return FALSE;
	return TRUE;
}
#endif /* CHECK_INODE */

/*
 * Move a long integer into a four byte character array.
 * Used for machine independency in block zero.
 */
	static void
long_to_char(n, s)
	long	n;
	char_u	*s;
{
	s[0] = (n & 0xff);
	n >>= 8;
	s[1] = (n & 0xff);
	n >>= 8;
	s[2] = (n & 0xff);
	n >>= 8;
	s[3] = (n & 0xff);
}

	static long
char_to_long(s)
	char_u	*s;
{
	long	retval;

	retval = s[3];
	retval <<= 8;
	retval += s[2];
	retval <<= 8;
	retval += s[1];
	retval <<= 8;
	retval += s[0];

	return retval;
}