vim_findfile_init(
    char_u	*path,
    char_u	*filename,
    char_u	*stopdirs UNUSED,
    int		level,
    int		free_visited,
    int		find_what,
    void	*search_ctx_arg,
    int		tagfile,	// expanding names of tags files
    char_u	*rel_fname)	// file name to use for "."
{
#ifdef FEAT_PATH_EXTRA
    char_u		*wc_part;
#endif
    ff_stack_T		*sptr;
    ff_search_ctx_T	*search_ctx;

    // If a search context is given by the caller, reuse it, else allocate a
    // new one.
    if (search_ctx_arg != NULL)
	search_ctx = search_ctx_arg;
    else
    {
	search_ctx = ALLOC_CLEAR_ONE(ff_search_ctx_T);
	if (search_ctx == NULL)
	    goto error_return;
    }
    search_ctx->ffsc_find_what = find_what;
    search_ctx->ffsc_tagfile = tagfile;

    // clear the search context, but NOT the visited lists
    ff_clear(search_ctx);

    // clear visited list if wanted
    if (free_visited == TRUE)
	vim_findfile_free_visited(search_ctx);
    else
    {
	// Reuse old visited lists. Get the visited list for the given
	// filename. If no list for the current filename exists, creates a new
	// one.
	search_ctx->ffsc_visited_list = ff_get_visited_list(filename,
					&search_ctx->ffsc_visited_lists_list);
	if (search_ctx->ffsc_visited_list == NULL)
	    goto error_return;
	search_ctx->ffsc_dir_visited_list = ff_get_visited_list(filename,
				    &search_ctx->ffsc_dir_visited_lists_list);
	if (search_ctx->ffsc_dir_visited_list == NULL)
	    goto error_return;
    }

    if (ff_expand_buffer == NULL)
    {
	ff_expand_buffer = alloc(MAXPATHL);
	if (ff_expand_buffer == NULL)
	    goto error_return;
    }

    // Store information on starting dir now if path is relative.
    // If path is absolute, we do that later.
    if (path[0] == '.'
	    && (vim_ispathsep(path[1]) || path[1] == NUL)
	    && (!tagfile || vim_strchr(p_cpo, CPO_DOTTAG) == NULL)
	    && rel_fname != NULL)
    {
	int	len = (int)(gettail(rel_fname) - rel_fname);

	if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL)
	{
	    // Make the start dir an absolute path name.
	    vim_strncpy(ff_expand_buffer, rel_fname, len);
	    search_ctx->ffsc_start_dir = FullName_save(ff_expand_buffer, FALSE);
	}
	else
	    search_ctx->ffsc_start_dir = vim_strnsave(rel_fname, len);
	if (search_ctx->ffsc_start_dir == NULL)
	    goto error_return;
	if (*++path != NUL)
	    ++path;
    }
    else if (*path == NUL || !vim_isAbsName(path))
    {
#ifdef BACKSLASH_IN_FILENAME
	// "c:dir" needs "c:" to be expanded, otherwise use current dir
	if (*path != NUL && path[1] == ':')
	{
	    char_u  drive[3];

	    drive[0] = path[0];
	    drive[1] = ':';
	    drive[2] = NUL;
	    if (vim_FullName(drive, ff_expand_buffer, MAXPATHL, TRUE) == FAIL)
		goto error_return;
	    path += 2;
	}
	else
#endif
	if (mch_dirname(ff_expand_buffer, MAXPATHL) == FAIL)
	    goto error_return;

	search_ctx->ffsc_start_dir = vim_strsave(ff_expand_buffer);
	if (search_ctx->ffsc_start_dir == NULL)
	    goto error_return;

#ifdef BACKSLASH_IN_FILENAME
	// A path that starts with "/dir" is relative to the drive, not to the
	// directory (but not for "//machine/dir").  Only use the drive name.
	if ((*path == '/' || *path == '\\')
		&& path[1] != path[0]
		&& search_ctx->ffsc_start_dir[1] == ':')
	    search_ctx->ffsc_start_dir[2] = NUL;
#endif
    }

#ifdef FEAT_PATH_EXTRA
    /*
     * If stopdirs are given, split them into an array of pointers.
     * If this fails (mem allocation), there is no upward search at all or a
     * stop directory is not recognized -> continue silently.
     * If stopdirs just contains a ";" or is empty,
     * search_ctx->ffsc_stopdirs_v will only contain a  NULL pointer. This
     * is handled as unlimited upward search.  See function
     * ff_path_in_stoplist() for details.
     */
    if (stopdirs != NULL)
    {
	char_u	*walker = stopdirs;
	int	dircount;

	while (*walker == ';')
	    walker++;

	dircount = 1;
	search_ctx->ffsc_stopdirs_v = ALLOC_ONE(char_u *);

	if (search_ctx->ffsc_stopdirs_v != NULL)
	{
	    do
	    {
		char_u	*helper;
		void	*ptr;

		helper = walker;
		ptr = vim_realloc(search_ctx->ffsc_stopdirs_v,
					   (dircount + 1) * sizeof(char_u *));
		if (ptr)
		    search_ctx->ffsc_stopdirs_v = ptr;
		else
		    // ignore, keep what we have and continue
		    break;
		walker = vim_strchr(walker, ';');
		if (walker)
		{
		    search_ctx->ffsc_stopdirs_v[dircount-1] =
					 vim_strnsave(helper, walker - helper);
		    walker++;
		}
		else
		    // this might be "", which means ascent till top
		    // of directory tree.
		    search_ctx->ffsc_stopdirs_v[dircount-1] =
							  vim_strsave(helper);

		dircount++;

	    } while (walker != NULL);
	    search_ctx->ffsc_stopdirs_v[dircount-1] = NULL;
	}
    }
#endif

#ifdef FEAT_PATH_EXTRA
    search_ctx->ffsc_level = level;

    /*
     * split into:
     *  -fix path
     *  -wildcard_stuff (might be NULL)
     */
    wc_part = vim_strchr(path, '*');
    if (wc_part != NULL)
    {
	int	llevel;
	int	len;
	char	*errpt;

	// save the fix part of the path
	search_ctx->ffsc_fix_path = vim_strnsave(path, wc_part - path);

	/*
	 * copy wc_path and add restricts to the '**' wildcard.
	 * The octet after a '**' is used as a (binary) counter.
	 * So '**3' is transposed to '**^C' ('^C' is ASCII value 3)
	 * or '**76' is transposed to '**N'( 'N' is ASCII value 76).
	 * For EBCDIC you get different character values.
	 * If no restrict is given after '**' the default is used.
	 * Due to this technique the path looks awful if you print it as a
	 * string.
	 */
	len = 0;
	while (*wc_part != NUL)
	{
	    if (len + 5 >= MAXPATHL)
	    {
		emsg(_(e_pathtoolong));
		break;
	    }
	    if (STRNCMP(wc_part, "**", 2) == 0)
	    {
		ff_expand_buffer[len++] = *wc_part++;
		ff_expand_buffer[len++] = *wc_part++;

		llevel = strtol((char *)wc_part, &errpt, 10);
		if ((char_u *)errpt != wc_part && llevel > 0 && llevel < 255)
		    ff_expand_buffer[len++] = llevel;
		else if ((char_u *)errpt != wc_part && llevel == 0)
		    // restrict is 0 -> remove already added '**'
		    len -= 2;
		else
		    ff_expand_buffer[len++] = FF_MAX_STAR_STAR_EXPAND;
		wc_part = (char_u *)errpt;
		if (*wc_part != NUL && !vim_ispathsep(*wc_part))
		{
		    semsg(_("E343: Invalid path: '**[number]' must be at the end of the path or be followed by '%s'."), PATHSEPSTR);
		    goto error_return;
		}
	    }
	    else
		ff_expand_buffer[len++] = *wc_part++;
	}
	ff_expand_buffer[len] = NUL;
	search_ctx->ffsc_wc_path = vim_strsave(ff_expand_buffer);

	if (search_ctx->ffsc_wc_path == NULL)
	    goto error_return;
    }
    else
#endif
	search_ctx->ffsc_fix_path = vim_strsave(path);

    if (search_ctx->ffsc_start_dir == NULL)
    {
	// store the fix part as startdir.
	// This is needed if the parameter path is fully qualified.
	search_ctx->ffsc_start_dir = vim_strsave(search_ctx->ffsc_fix_path);
	if (search_ctx->ffsc_start_dir == NULL)
	    goto error_return;
	search_ctx->ffsc_fix_path[0] = NUL;
    }

    // create an absolute path
    if (STRLEN(search_ctx->ffsc_start_dir)
			  + STRLEN(search_ctx->ffsc_fix_path) + 3 >= MAXPATHL)
    {
	emsg(_(e_pathtoolong));
	goto error_return;
    }
    STRCPY(ff_expand_buffer, search_ctx->ffsc_start_dir);
    add_pathsep(ff_expand_buffer);
    {
	int    eb_len = (int)STRLEN(ff_expand_buffer);
	char_u *buf = alloc(eb_len
				+ (int)STRLEN(search_ctx->ffsc_fix_path) + 1);

	STRCPY(buf, ff_expand_buffer);
	STRCPY(buf + eb_len, search_ctx->ffsc_fix_path);
	if (mch_isdir(buf))
	{
	    STRCAT(ff_expand_buffer, search_ctx->ffsc_fix_path);
	    add_pathsep(ff_expand_buffer);
	}
#ifdef FEAT_PATH_EXTRA
	else
	{
	    char_u *p =  gettail(search_ctx->ffsc_fix_path);
	    char_u *wc_path = NULL;
	    char_u *temp = NULL;
	    int    len = 0;

	    if (p > search_ctx->ffsc_fix_path)
	    {
		// do not add '..' to the path and start upwards searching
		len = (int)(p - search_ctx->ffsc_fix_path) - 1;
		if ((len >= 2
			&& STRNCMP(search_ctx->ffsc_fix_path, "..", 2) == 0)
			&& (len == 2
				   || search_ctx->ffsc_fix_path[2] == PATHSEP))
		{
		    vim_free(buf);
		    goto error_return;
		}
		STRNCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, len);
		add_pathsep(ff_expand_buffer);
	    }
	    else
		len = (int)STRLEN(search_ctx->ffsc_fix_path);

	    if (search_ctx->ffsc_wc_path != NULL)
	    {
		wc_path = vim_strsave(search_ctx->ffsc_wc_path);
		temp = alloc(STRLEN(search_ctx->ffsc_wc_path)
				 + STRLEN(search_ctx->ffsc_fix_path + len)
				 + 1);
		if (temp == NULL || wc_path == NULL)
		{
		    vim_free(buf);
		    vim_free(temp);
		    vim_free(wc_path);
		    goto error_return;
		}

		STRCPY(temp, search_ctx->ffsc_fix_path + len);
		STRCAT(temp, search_ctx->ffsc_wc_path);
		vim_free(search_ctx->ffsc_wc_path);
		vim_free(wc_path);
		search_ctx->ffsc_wc_path = temp;
	    }
	}
#endif
	vim_free(buf);
    }

    sptr = ff_create_stack_element(ff_expand_buffer,
#ifdef FEAT_PATH_EXTRA
	    search_ctx->ffsc_wc_path,
#endif
	    level, 0);

    if (sptr == NULL)
	goto error_return;

    ff_push(search_ctx, sptr);

    search_ctx->ffsc_file_to_search = vim_strsave(filename);
    if (search_ctx->ffsc_file_to_search == NULL)
	goto error_return;

    return search_ctx;

error_return:
    /*
     * We clear the search context now!
     * Even when the caller gave us a (perhaps valid) context we free it here,
     * as we might have already destroyed it.
     */
    vim_findfile_cleanup(search_ctx);
    return NULL;
}