/* vi:set sts=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 os_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);	/* allocates fname */
	    if (fname == NULL)
		continue;
	}
	mfp = mf_open(fname, TRUE);			/* consumes fname! */

	/* stop when memfile opened or no other fname to try */
	if (mfp != NULL || fname == NULL)
	    break;
    }

    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);

    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, "VIM ", 4);
    STRNCPY(b0p->b0_version + 4, Version, 6);
    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) */
    }
    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;
#if defined(MSDOS) || defined(WIN32)
    char_u	*p;
#endif

/*
 * 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); /* alloc's 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.
	 */
	p = FullName_save(fname, FALSE);
	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_ffname);
#if defined(MSDOS) || defined(WIN32)
	    mfp->mf_ffname = NULL;  /* mf_fname is full pathname already */
#else
	    mf_set_ffname(mfp);
#endif
	    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);	/* allocates fname */
	if (fname == NULL)
	    continue;
	if (mf_open_file(mfp, fname) == OK)	/* consumes fname! */
	    break;
    }

    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_fname == NULL ? (char_u *)"No File" : buf->b_fname);
	--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_ffname == 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_ffname, 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_ffname, 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_ffname, &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;
    off_t	size;
    int		called_from_main;
    int		serious_error = TRUE;
    long	mtime;
    int		attr;

    called_from_main = (curbuf->b_ml.ml_mfp == NULL);
    attr = highlight_attr[HLF_E];
/*
 * 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_fname;
    if (fname == NULL)		    /* When there is no file name */
	fname = (char_u *)"";
    len = STRLEN(fname);
    if (len >= 4 && STRNICMP(fname + len - 4, ".sw", 3) == 0)
    {
	directly = TRUE;
	fname = vim_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);
	    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_putchar('\n');
	    MSG_PUTS("Enter number of swap file to use (0 to quit): ");
	    i = get_number();
	    if (i < 1 || i > len)
		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)
    {
	vim_free(fname);
	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
 */
    p = vim_strsave(fname);		/* save fname for the message */
    mfp = mf_open(fname, FALSE);	/* consumes fname! */
    if (mfp == NULL || mfp->mf_fd < 0)
    {
	if (p != NULL)
	{
	    EMSG2("Cannot open %s", p);
	    vim_free(p);
	}
	goto theend;
    }
    vim_free(p);
    buf->b_ml.ml_mfp = mfp;

/*
 * try to read block 0
 */
    if ((hp = mf_get(mfp, (blocknr_t)0, 1)) == NULL)
    {
	msg_start();
	MSG_PUTS_ATTR("Unable to read block 0 from ", attr);
	msg_outtrans_attr(mfp->mf_fname, attr);
	MSG_PUTS_ATTR(
		"\nMaybe no changes were made or Vim did not update the swap file",
		attr);
	msg_end();
	goto theend;
    }
    b0p = (ZERO_BL *)(hp->bh_data);
    if (STRNCMP(b0p->b0_version, "VIM 3.0", 7) == 0)
    {
	msg_start();
	MSG_PUTS("The file ");
	msg_outtrans(mfp->mf_fname);
	MSG_PUTS(" cannot be used with this version of Vim.\n");
	MSG_PUTS("Use Vim version 3.0.\n");
	msg_end();
	goto theend;
    }
    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_PUTS_ATTR("The file ", attr);
	msg_outtrans_attr(mfp->mf_fname, attr);
#if defined(MSDOS) || defined(WIN32)
	if (STRNCMP(b0p->b0_hname, "PC ", 3) == 0)
	    MSG_PUTS_ATTR(" cannot be used with this version of Vim.\n", attr);
	else
#endif
	    MSG_PUTS_ATTR(" cannot be used on this computer.\n", attr);
	MSG_PUTS_ATTR("The file was created on ", attr);
		/* avoid going past the end of currupted hostname */
	b0p->b0_fname[0] = NUL;
	MSG_PUTS_ATTR(b0p->b0_hname, attr);
	MSG_PUTS_ATTR(",\nor the file has been damaged.", attr);
	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, (off_t)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_ffname == NULL)
	STRCPY(NameBuff, "No File");
    else
	home_replace(NULL, curbuf->b_ffname, NameBuff, MAXPATHL);
    smsg((char_u *)"Original file \"%s\"", NameBuff);
    msg_putchar('\n');

/*
 * check date of swap file and original file
 */
    mtime = char_to_long(b0p->b0_mtime);
    if (curbuf->b_ffname != NULL &&
	    stat((char *)curbuf->b_ffname, &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_ffname == 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_ffname, NULL, lnum,
					pp->pb_pointer[idx].pe_old_lnum - 1,
					line_count, 0) == 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_PUTS("\n(You might want to write out this file under another name\n");
	MSG_PUTS("and run diff with the original file to check for changes)\n");
	MSG_PUTS("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) */
    }
    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_putchar('\n');
    }
    expand_interactively = TRUE;

    /*
     * Do the loop for every directory in 'directory'.
     * First allocate some memory to put the directory name in.
     */
    dir_name = alloc((unsigned)STRLEN(p_dir) + 1);
    dirp = p_dir;
    while (dir_name != NULL && *dirp)
    {
	/*
	 * Isolate a directory name from *dirp and put it in dir_name.
	 * Advance dirp to next directory name.
	 */
	(void)copy_option_part(&dirp, dir_name, 31000, ",");

	if (dir_name[0] == '.' && dir_name[1] == NUL)	/* check current dir */
	{
	    if (fname == NULL || *fname == NULL)
	    {
		names[0] = vim_strsave((char_u *)"*.sw?");
#ifdef UNIX
		    /* for Unix names starting with a dot are special */
		names[1] = vim_strsave((char_u *)".*.sw?");
		names[2] = vim_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 (expand_wildcards(num_names, names, &num_files, &files,
							     EW_FILE) == FAIL)
	{
	    /* print error message */
	    MSG_PUTS_ATTR(files, highlight_attr[HLF_E]);
	    num_files = 0;
	}

	/*
	 * When no swap file found, wildcard expansion might have failed (e.g.
	 * not able to execute the shell).
	 * Try finding a swap file by simply adding ".swp" to the file name.
	 */
	if (file_count == 0 && fname != NULL && *fname != NULL)
	{
	    struct stat	    st;
	    char_u	    *swapname;

	    swapname = modname(*fname, (char_u *)".swp",
#ifdef UNIX
							TRUE
#else
							FALSE
#endif
								);
	    if (swapname != NULL)
	    {
		if (stat((char *)swapname, &st) != -1)	    /* It exists! */
		{
		    files = (char_u **)alloc((unsigned)sizeof(char_u *));
		    if (files != NULL)
		    {
			files[0] = swapname;
			swapname = NULL;
			num_files = 1;
		    }
		}
		vim_free(swapname);
	    }
	}

	/*
	 * 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 = vim_strsave(files[nr - 1 + num_files - file_count]);
		dirp = (char_u *)"";		    /* stop searching */
	    }
	}
	else if (list)
	{
	    if (dir_name[0] == '.' && dir_name[1] == NUL)
	    {
		if (fname == NULL || *fname == NULL)
		    MSG_PUTS("   In current directory:\n");
		else
		    MSG_PUTS("   Using specified name:\n");
	    }
	    else
	    {
		MSG_PUTS("   In directory ");
		msg_home_replace(dir_name);
		MSG_PUTS(":\n");
	    }

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

	for (i = 0; i < num_names; ++i)
	    vim_free(names[i]);
	FreeWild(num_files, files);
    }
    vim_free(dir_name);
    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_PUTS("             dated: ");
	x = st.st_mtime;		    /* Manx C can't do &st.st_mtime */
	MSG_PUTS(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 (STRNCMP(b0.b0_version, "VIM 3.0", 7) == 0)
	    {
		MSG_PUTS("         [from Vim version 3.0]");
	    }
	    else if (b0.b0_id[0] != BLOCK0_ID0 ||
				b0.b0_id[1] != BLOCK0_ID1)
	    {
		MSG_PUTS("         [is not a swap file]");
	    }
	    else
	    {
		MSG_PUTS("         file name: ");
		msg_outtrans(b0.b0_fname);

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

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

		if (char_to_long(b0.b0_pid) != 0L)
		{
		    MSG_PUTS("\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_PUTS(" (still running)");
#endif
		}

		if (b0_magic_wrong(&b0))
		{
#if defined(MSDOS) || defined(WIN32)
		    if (STRNCMP(b0.b0_hname, "PC ", 3) == 0)
			MSG_PUTS("\n         [not usable with this version of Vim]");
		    else
#endif
			MSG_PUTS("\n         [not usable on this computer]");
		}
	    }
	}
	else
	    MSG_PUTS("         [cannot be read]");
	close(fd);
    }
    else
	MSG_PUTS("         [cannot be opened]");
    msg_putchar('\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?", FALSE);
    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?", FALSE);
    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_ffname != 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_ffname, &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 && ui_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 and 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);
#ifdef SYNTAX_HL
	if (buf->b_syn_change_lnum > lnum)
	    buf->b_syn_change_lnum = lnum;
#endif
    }

    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 */
{
#ifdef SYNTAX_HL
    if (curbuf->b_syn_change_lnum > lnum + 1)
	curbuf->b_syn_change_lnum = lnum + 1;
#endif

    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 = vim_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;
{
#ifdef SYNTAX_HL
    if (curbuf->b_syn_change_lnum > lnum)
	curbuf->b_syn_change_lnum = lnum;
#endif

    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;
	    keep_msg_attr = 0;
	}
	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.
		 * Append first, because ml_delete_int() cannot delete the
		 * last line in a buffer, which causes trouble for a buffer
		 * that has only one line.
		 */
		/* How about handling errors??? */
		(void)ml_append_int(buf, lnum, new_line, new_len, FALSE);
		(void)ml_delete_int(buf, lnum, 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 file name and a directory name
 */
    static char_u *
makeswapname(buf, dir_name)
    BUF	    *buf;
    char_u  *dir_name;
{
    char_u	*r, *s;

    r = modname(buf->b_fname, (char_u *)
#ifdef VMS
	    "_swp",
#else
	    ".swp",
#endif
#ifdef UNIX
	    dir_name[0] == '.' && dir_name[1] == NUL
#else
	    FALSE
#endif
	       );
    if (r == NULL)	    /* out of memory */
	return NULL;

    s = get_file_in_dir(r, dir_name);
    vim_free(r);
    return s;
}

/*
 * Get file name to use for swap file or backup file.
 * Use the name of the edited file "fname" and an entry in the 'dir' or 'bdir'
 * option "dname".
 * - If "dname" is ".", return "fname".
 * - If "dname" starts with "./", insert "dname" in "fname".
 * - Otherwise, prepend "dname" to the tail of "fname".
 *
 * The return value is an allocated string and can be NULL.
 */
    char_u *
get_file_in_dir(fname, dname)
    char_u  *fname;
    char_u  *dname;	/* don't use "dirname", it is a global for Alpha */
{
    char_u	*t;
    char_u	*tail;
    char_u	*retval;
    int		save_char;

    tail = gettail(fname);

    if (dname[0] == '.' && dname[1] == NUL)
	retval = vim_strsave(fname);
    else if (dname[0] == '.' && ispathsep(dname[1]))
    {
	if (tail == fname)	    /* no path before file name */
	    retval = concat_fnames(dname + 2, tail, TRUE);
	else
	{
	    save_char = *tail;
	    *tail = NUL;
	    t = concat_fnames(fname, dname + 2, TRUE);
	    *tail = save_char;
	    if (t == NULL)	    /* out of memory */
		retval = NULL;
	    else
	    {
		retval = concat_fnames(t, tail, TRUE);
		vim_free(t);
	    }
	}
    }
    else
	retval = concat_fnames(dname, tail, TRUE);

    return retval;
}

/*
 * 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 BASENAMELEN 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 file name */
{
    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".
 * Don't do this for a symbolic link to a file that doesn't exist yet,
 * because the link would be deleted further on!
 */
    if (!(buf->b_p_sn || buf->b_shortname) && buf->b_fname &&
						mch_getperm(buf->b_fname) < 0)
    {
# if defined(HAVE_LSTAT) && ((defined(S_IFMT) && defined(S_IFLNK)) || defined(S_ISLNK))
	struct stat st;

	if (lstat((char *)buf->b_fname, &st) == -1 ||
#  if defined(S_IFMT) && defined(S_IFLNK)
		(st.st_mode & S_IFMT) != S_IFLNK
#  else
		!S_ISLNK(st.st_mode)
#  endif
						    )
# endif
	    dummyfd = fopen((char *)buf->b_fname, "w");
    }
#endif

/*
 * Isolate a directory name from *dirp and put it in dir_name.
 * First allocate some memory 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 swapfile name does not fit in 8.3:
	     * It either contains two dots or it is longer than 8 chars.
	     */
	    tail = gettail(buf->b_fname);
	    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 (mch_getperm(fname) < 0)	/* it does not exist */
	{
#ifdef AMIGA
	    fh = Open((UBYTE *)fname, (long)MODE_NEWFILE);
	    /*
	     * on the Amiga mch_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 = mch_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_fname != 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_ffname, NameBuff,
						     char_to_long(b0.b0_ino)))
			    differ = TRUE;
#else
			if (fnamecmp(NameBuff, buf->b_ffname) != 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_PUTS("\nFound a swap file by the name \"");
		    msg_home_replace(fname);
		    MSG_PUTS("\"\n");
		    swapfile_info(fname);
		    MSG_PUTS("While opening file \"");
		    msg_outtrans(buf->b_fname);
		    MSG_PUTS("\"\n");
		    if (stat((char *)buf->b_fname, &st) != -1)
		    {
			MSG_PUTS("             dated: ");
			x = st.st_mtime;    /* Manx C can't do &st.st_mtime */
			MSG_PUTS(ctime(&x));
		    }
		    MSG_PUTS("\n(1) Another program may be editing the same file.\n");
		    MSG_PUTS("    If this is the case, quit this edit session to avoid having\n");
		    MSG_PUTS("    two different instances of the same file when making changes.\n");
		    MSG_PUTS("\n(2) An edit session for this file crashed.\n");
		    MSG_PUTS("    If this is the case, use \":recover\" or \"vim -r ");
		    msg_outtrans(buf->b_fname);
		    MSG_PUTS("\"\n    to recover the changes (see \":help recovery)\".\n");
		    MSG_PUTS("    If you did this already, delete the swap file \"");
		    msg_outtrans(fname);
		    MSG_PUTS("\"\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_fname);
    }
#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 mch_FullName() and
     * compare the file names.
     */
    retval_c = mch_FullName(fname_c, buf_c, MAXPATHL, TRUE);
    retval_s = mch_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 file names, 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 = (unsigned)n >> 8;
    s[1] = (n & 0xff);
    n = (unsigned)n >> 8;
    s[2] = (n & 0xff);
    n = (unsigned)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;
}
