%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2005-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

-module(filelib_SUITE).

-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
	 init_per_group/2,end_per_group/2,
	 init_per_testcase/2,end_per_testcase/2,
	 wildcard_one/1,wildcard_two/1,wildcard_errors/1,
	 fold_files/1,otp_5960/1,ensure_dir_eexist/1,ensure_dir_symlink/1,
         ensure_path_single_dir/1, ensure_path_nested_dirs/1,
         ensure_path_binary_args/1, ensure_path_symlink/1,
         ensure_path_relative_path/1, ensure_path_relative_path_dot_dot/1,
         ensure_path_invalid_path/1,
	 wildcard_symlink/1, is_file_symlink/1, file_props_symlink/1,
         find_source/1, find_source_subdir/1, find_source_otp/1,
         safe_relative_path/1, safe_relative_path_links/1]).

-import(lists, [foreach/2]).

-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").

-define(PRIM_FILE, prim_file).

init_per_testcase(_Case, Config) ->
    Config.

end_per_testcase(_Case, _Config) ->
    ok.

suite() ->
    [{ct_hooks,[ts_install_cth]},
     {timetrap,{minutes,5}}].

all() -> 
    [wildcard_one, wildcard_two, wildcard_errors,
     fold_files, otp_5960, ensure_dir_eexist, ensure_dir_symlink,
     ensure_path_single_dir, ensure_path_nested_dirs, ensure_path_binary_args,
     ensure_path_symlink, ensure_path_relative_path,
     ensure_path_relative_path_dot_dot,
     ensure_path_invalid_path,
     wildcard_symlink, is_file_symlink, file_props_symlink,
     find_source, find_source_subdir, find_source_otp,
     safe_relative_path, safe_relative_path_links].

groups() -> 
    [].

init_per_suite(Config) ->
    Config.

end_per_suite(_Config) ->
    ok.

init_per_group(_GroupName, Config) ->
    Config.

end_per_group(_GroupName, Config) ->
    Config.


wildcard_one(Config) when is_list(Config) ->
    {ok,OldCwd} = file:get_cwd(),
    Dir = filename:join(proplists:get_value(priv_dir, Config), "wildcard_one"),
    ok = file:make_dir(Dir),
    do_wildcard_1(Dir,
		  fun(Wc) ->
			  filelib:wildcard(Wc, Dir, erl_prim_loader)
		  end),
    file:set_cwd(Dir),
    do_wildcard_1(Dir,
		  fun(Wc) ->
			  L = filelib:wildcard(Wc),
			  L = filelib:wildcard(disable_prefix_opt(Wc)),
			  L = filelib:wildcard(Wc, erl_prim_loader),
			  L = filelib:wildcard(Wc, "."),
			  L = filelib:wildcard(Wc, Dir),
			  L = filelib:wildcard(disable_prefix_opt(Wc), Dir),
			  L = filelib:wildcard(Wc, Dir++"/.")
		  end),
    file:set_cwd(OldCwd),
    ok = file:del_dir(Dir),
    ok.

wildcard_two(Config) when is_list(Config) ->
    Dir = filename:join(proplists:get_value(priv_dir, Config), "wildcard_two"),
    ok = file:make_dir(Dir),
    do_wildcard_1(Dir, fun(Wc) -> io:format("~p~n",[{Wc,Dir, X = filelib:wildcard(Wc, Dir)}]),X  end),
    do_wildcard_1(Dir, fun(Wc) -> filelib:wildcard(Wc, Dir++"/") end),
    do_wildcard_1(Dir, fun(Wc) -> filelib:wildcard(Wc, Dir++"/.") end),
    case os:type() of
	{win32,_} ->
	    ok;
	_ ->
	    do_wildcard_1(Dir, fun(Wc) -> filelib:wildcard(Wc, "//"++Dir) end)
    end,
    ok = file:del_dir(Dir),
    ok.

wildcard_errors(Config) when is_list(Config) ->
    wcc("{", missing_delimiter),
    wcc("{a", missing_delimiter),
    wcc("{a,", missing_delimiter),
    wcc("{a,b", missing_delimiter),
    ok.

wcc(Wc, Error) ->
    {'EXIT',{{badpattern,Error},
	     [{filelib,compile_wildcard,1,_}|_]}} =
	(catch filelib:compile_wildcard(Wc)),
    {'EXIT',{{badpattern,Error},
	     [{filelib,wildcard,1,_}|_]}} = (catch filelib:wildcard(Wc)),
    {'EXIT',{{badpattern,Error},
	     [{filelib,wildcard,2,_}|_]}} = (catch filelib:wildcard(Wc, ".")).

disable_prefix_opt([_,$:|_]=Wc) ->
    Wc;
disable_prefix_opt([C|Wc]) when $a =< C, C =< $z; C =:= $@ ->
    %% There is an optimization for patterns that have a literal prefix
    %% (such as "lib/compiler/ebin/*"). Test that we'll get the same result
    %% if we disable that optimization.
    [$[, C, $] | Wc];
disable_prefix_opt(Wc) ->
    Wc.

do_wildcard_1(Dir, Wcf0) ->
    do_wildcard_2(Dir, Wcf0),
    Wcf = fun(Wc0) ->
		  Wc = Dir ++ "/" ++ Wc0,
		  L = Wcf0(Wc),
		  [subtract_dir(N, Dir) || N <- L]
	  end,
    do_wildcard_2(Dir, Wcf).

subtract_dir([C|Cs], [C|Dir]) -> subtract_dir(Cs, Dir);
subtract_dir("/"++Cs, []) -> Cs.

do_wildcard_2(Dir, Wcf) ->
    %% Basic wildcards.
    All = ["abc","abcdef","glurf"],
    Files = mkfiles(lists:reverse(All), Dir),
    All = Wcf("*"),
    ["abc","abcdef"] = Wcf("a*"),
    ["abc","abcdef"] = Wcf("abc*"),
    ["abcdef"] = Wcf("abc???"),
    ["abcdef"] = Wcf("abcd*"),
    ["abcdef"] = Wcf("*def"),
    ["abcdef","glurf"] = Wcf("{*def,gl*}"),
    ["abc","abcdef"] = Wcf("a*{def,}"),
    ["abc","abcdef"] = Wcf("a*{,def}"),

    %% Constant wildcard.
    ["abcdef"] = Wcf("abcdef"),

    %% Negative tests.
    [] = Wcf("b*"),
    [] = Wcf("bufflig"),

    del(Files),
    do_wildcard_3(Dir, Wcf).

do_wildcard_3(Dir, Wcf) ->
    %% Some character sets.
    All = ["a01","a02","a03","b00","c02","d19"],
    Files = mkfiles(lists:reverse(All), Dir),
    All = Wcf("[a-z]*"),
    All = Wcf("[a-d]*"),
    All = Wcf("[adbc]*"),
    All = Wcf("?[0-9][0-9]"),
    All = Wcf("?[0-1][0-39]"),
    All = Wcf("[abcdefgh][10][01239]"),
    ["a01","a02","a03","b00","c02"] = Wcf("[a-z]0[0-3]"),
    [] = Wcf("?[a-z][0-39]"),
    del(Files),
    do_wildcard_4(Dir, Wcf).

do_wildcard_4(Dir, Wcf) ->
    %% More character sets: tricky characters.
    All = ["a-","aA","aB","aC","a[","a]"],
    Files = mkfiles(lists:reverse(All), Dir),
    All = Wcf("a[][A-C-]"),
    ["a-"] = Wcf("a[-]"),
    ["a["] = Wcf("a["),
    del(Files),
    do_wildcard_5(Dir, Wcf).

do_wildcard_5(Dir, Wcf) ->
    Dirs = ["xa","blurf","yyy"],
    foreach(fun(D) -> ok = file:make_dir(filename:join(Dir, D)) end, Dirs),
    All = ["blurf/nisse","xa/arne","xa/kalle","yyy/arne"],
    Files = mkfiles(lists:reverse(All), Dir),

    %% Test.
    All = Wcf("*/*"),
    ["blurf/nisse","xa/arne","xa/kalle"] = Wcf("{blurf,xa}/*"),
    ["xa/arne","yyy/arne"] = Wcf("*/arne"),
    ["blurf/nisse"] = Wcf("*/nisse"),
    [] = Wcf("mountain/*"),
    [] = Wcf("xa/gurka"),
    ["blurf/nisse"] = Wcf("blurf/nisse"),

    %% Cleanup
    del(Files),
    foreach(fun(D) -> ok = file:del_dir(filename:join(Dir, D)) end, Dirs),
    do_wildcard_6(Dir, Wcf).

do_wildcard_6(Dir, Wcf) ->
    ok = file:make_dir(filename:join(Dir, "xbin")),
    All = ["xbin/a.x","xbin/b.x","xbin/c.x"],
    Files = mkfiles(All, Dir),
    All = Wcf("xbin/*.x"),
    All = Wcf("xbin/*"),
    ["xbin"] = Wcf("*"),
    All = Wcf("*/*"),
    del(Files),
    ok = file:del_dir(filename:join(Dir, "xbin")),
    do_wildcard_7(Dir, Wcf).

do_wildcard_7(Dir, Wcf) ->
    Dirs = ["blurf","xa","yyy"],
    SubDirs = ["blurf/nisse"],
    foreach(fun(D) ->
		    ok = file:make_dir(filename:join(Dir, D))
	    end, Dirs ++ SubDirs),
    All = ["blurf/nisse/baz","xa/arne","xa/kalle","yyy/arne"],
    Files = mkfiles(lists:reverse(All), Dir),

    %% Test.
    Listing = Wcf("**"),
    ["blurf","blurf/nisse","blurf/nisse/baz",
     "xa","xa/arne","xa/kalle","yyy","yyy/arne"] = Listing,
    Listing = Wcf("**/*"),
    ["xa/arne","yyy/arne"] = Wcf("**/arne"),
    ["blurf/nisse"] = Wcf("**/nisse"),
    [] = Wcf("mountain/**"),

    %% Cleanup
    del(Files),
    foreach(fun(D) ->
		    ok = file:del_dir(filename:join(Dir, D))
	    end, SubDirs ++ Dirs),
    do_wildcard_8(Dir, Wcf).

do_wildcard_8(Dir, Wcf) ->
    Dirs0 = ["blurf"],
    Dirs1 = ["blurf/nisse"],
    Dirs2 = ["blurf/nisse/a", "blurf/nisse/b"],
    foreach(fun(D) ->
		    ok = file:make_dir(filename:join(Dir, D))
	    end, Dirs0 ++ Dirs1 ++ Dirs2),
    All = ["blurf/nisse/a/1.txt", "blurf/nisse/b/2.txt", "blurf/nisse/b/3.txt"],
    Files = mkfiles(lists:reverse(All), Dir),

    %% Test.
    All = Wcf("**/blurf/**/*.txt"),

    %% Cleanup
    del(Files),
    foreach(fun(D) ->
		    ok = file:del_dir(filename:join(Dir, D))
	    end, Dirs2 ++ Dirs1 ++ Dirs0),
    do_wildcard_9(Dir, Wcf).

do_wildcard_9(Dir, Wcf) ->
    Dirs0 = ["lib","lib/app","lib/app/ebin"],
    Dirs = [filename:join(Dir, D) || D <- Dirs0],
    [ok = file:make_dir(D) || D <- Dirs],
    Files0 = [filename:join("lib/app/ebin", F++".bar") ||
		 F <- ["abc","foo","foobar"]],
    Files = [filename:join(Dir, F) || F <- Files0],
    [ok = file:write_file(F, <<"some content\n">>) || F <- Files],
    Files0 = Wcf("lib/app/ebin/*.bar"),

    %% Cleanup.
    del(Files),
    [ok = file:del_dir(D) || D <- lists:reverse(Dirs)],
    do_wildcard_10(Dir, Wcf).

%% ERL-451/OTP-14577: Escape characters using \\.
do_wildcard_10(Dir, Wcf) ->
    All0 = ["{abc}","abc","def","---","z--","@a,b","@c"],
    All = case os:type() of
              {unix,_} ->
                  %% '?' is allowed in file names on Unix, but
                  %% not on Windows.
                  ["?q"|All0];
              _ ->
                  All0
          end,
    Files = mkfiles(lists:reverse(All), Dir),

    ["{abc}"] = Wcf("\\{a*"),
    ["{abc}"] = Wcf("\\{abc}"),
    ["abc","def","z--"] = Wcf("[a-z]*"),
    ["---","abc","z--"] = Wcf("[a\\-z]*"),
    ["@a,b","@c"] = Wcf("@{a\\,b,c}"),
    ["@c"] = Wcf("@{a,b,c}"),

    case os:type() of
        {unix,_} ->
            ["?q"] = Wcf("\\?q");
        _ ->
            [] = Wcf("\\?q")
    end,

    del(Files),
    wildcard_11(Dir, Wcf).

%% ERL-ERL-1029/OTP-15987: Fix problems with "@/.." and ".." in general.
wildcard_11(Dir, Wcf) ->
    Dirs0 = ["@","@dir","dir@"],
    Dirs = [filename:join(Dir, D) || D <- Dirs0],
    _ = [ok = file:make_dir(D) || D <- Dirs],
    Files0 = ["@a","b@","x","y","z"],
    Files = mkfiles(Files0, Dir),

    ["@","@a","@dir","b@","dir@","x","y","z"] = Wcf("*"),
    ["@"] = Wcf("@"),
    ["@","@a","@dir"] = Wcf("@*"),
    ["@/..","@dir/.."] = Wcf("@*/.."),
    ["@/../@","@/../@a","@/../@dir",
     "@dir/../@","@dir/../@a","@dir/../@dir"] = Wcf("@*/../@*"),

    %% Non-directories followed by "/.." should not match any files.
    [] = Wcf("@a/.."),
    [] = Wcf("x/.."),

    %% Cleanup.
    del(Files),
    [ok = file:del_dir(D) || D <- Dirs],
    ok.

fold_files(Config) when is_list(Config) ->
    Dir = filename:join(proplists:get_value(priv_dir, Config), "fold_files"),
    ok = file:make_dir(Dir),
    Dirs = [filename:join(Dir, D) || D <- ["blurf","blurf/blarf"]],
    foreach(fun(D) -> ok = file:make_dir(D) end, Dirs),
    All = ["fb.txt","ko.txt",
	   "blurf/nisse.text","blurf/blarf/aaa.txt","blurf/blarf/urfa.txt"],
    Files = mkfiles(lists:reverse(All), Dir),

    %% Test.
    Files0 = filelib:fold_files(Dir, "^", false,
				fun(H, T) -> [H|T] end, []),
    same_lists(["fb.txt","ko.txt"], Files0, Dir),

    Files1 = filelib:fold_files(Dir, "^", true,
				fun(H, T) -> [H|T] end, []),
    same_lists(All, Files1, Dir),

    Files2 = filelib:fold_files(Dir, "[.]text$", true,
				fun(H, T) -> [H|T] end, []),
    same_lists(["blurf/nisse.text"], Files2, Dir),


    Files3 = filelib:fold_files(Dir, "^..[.]", true,
				fun(H, T) -> [H|T] end, []),
    same_lists(["fb.txt","ko.txt"], Files3, Dir),

    Files4 = filelib:fold_files(Dir, "^ko[.]txt$", true,
				fun(H, T) -> [H|T] end, []),
    same_lists(["ko.txt"], Files4, Dir),
    Files4 = filelib:fold_files(Dir, "^ko[.]txt$", false,
				fun(H, T) -> [H|T] end, []),

    [] = filelib:fold_files(Dir, "^$", true,
			    fun(H, T) -> [H|T] end, []),

    %% Cleanup
    del(Files),
    foreach(fun(D) -> ok = file:del_dir(D) end, lists:reverse(Dirs)),
    ok = file:del_dir(Dir).

same_lists(Expected0, Actual0, BaseDir) ->
    Expected = [filename:absname(N, BaseDir) || N <- lists:sort(Expected0)],
    Actual = lists:sort(Actual0),
    Expected = Actual.

mkfiles([H|T], Dir) ->
    Name = filename:join(Dir, H),
    Garbage = [31+rand:uniform(95) || _ <- lists:seq(1, rand:uniform(1024))],
    file:write_file(Name, Garbage),
    [Name|mkfiles(T, Dir)];
mkfiles([], _) -> [].

del([H|T]) ->
    ok = file:delete(H),
    del(T);
del([]) -> ok.

%% Test that filelib:ensure_dir/1 returns ok or {error,Reason}.
otp_5960(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    Dir = filename:join(PrivDir, "otp_5960_dir"),
    Name1 = filename:join(Dir, name1),
    Name2 = filename:join(Dir, name2),
    ok = filelib:ensure_dir(Name1), % parent is created
    ok = filelib:ensure_dir(Name1), % repeating it should be OK
    ok = filelib:ensure_dir(Name2), % parent already exists
    ok = filelib:ensure_dir(Name2), % repeating it should be OK
    Name3 = filename:join(Name1, name3),
    {ok, FileInfo} = file:read_file_info(Dir),
    case os:type() of
	{win32,_} ->
	    %% Not possibly to write protect directories on Windows
	    %% (at least not using file:write_file_info/2).
	    ok;
	_ ->
	    Mode = FileInfo#file_info.mode,
	    NoWriteMode = Mode - 8#00200 - 8#00020 - 8#00002,
	    ok = file:write_file_info(Dir, #file_info{mode=NoWriteMode}),
	    {error, _} = filelib:ensure_dir(Name3),
	    ok = file:write_file_info(Dir, #file_info{mode=Mode}),
	    ok
    end.

ensure_dir_eexist(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    Dir = filename:join(PrivDir, "ensure_dir_eexist"),
    Name = filename:join(Dir, "same_name_as_file_and_dir"),
    ok = filelib:ensure_dir(Name),
    ok = file:write_file(Name, <<"some string\n">>),

    %% There already is a file with the name of the directory
    %% we want to create.
    NeedFile = filename:join(Name, "file"),
    NeedFileB = filename:join(Name, <<"file">>),
    {error, eexist} = filelib:ensure_dir(NeedFile),
    {error, eexist} = filelib:ensure_dir(NeedFileB),
    ok.

ensure_dir_symlink(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    Dir = filename:join(PrivDir, "ensure_dir_symlink"),
    Name = filename:join(Dir, "same_name_as_file_and_dir"),
    ok = filelib:ensure_dir(Name),
    ok = file:write_file(Name, <<"some string\n">>),
    %% With a symlink to the directory.
    Symlink = filename:join(PrivDir, "ensure_dir_symlink_link"),
    case file:make_symlink(Dir, Symlink) of
        {error,enotsup} ->
            {skip,"Symlinks not supported on this platform"};
        {error,eperm} ->
            {win32,_} = os:type(),
            {skip,"Windows user not privileged to create symlinks"};
        ok ->
            SymlinkedName = filename:join(Symlink, "same_name_as_file_and_dir"),
            ok = filelib:ensure_dir(SymlinkedName)
    end.

ensure_path_single_dir(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    Dir = filename:join(PrivDir, "ensure_path_single_dir"),
    ok = filelib:ensure_path(Dir),
    true = filelib:is_dir(Dir).

ensure_path_nested_dirs(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    BaseDir = filename:join(PrivDir, "ensure_path_nested_dirs"),
    Path = filename:join(BaseDir, "foo/bar/baz"),
    ok = filelib:ensure_path(Path),
    true = filelib:is_dir(Path).

ensure_path_binary_args(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    BaseDir = filename:join(PrivDir, "ensure_path_binary_args"),
    Path = filename:join(BaseDir, "foo/bar/baz"),
    ok = filelib:ensure_path(list_to_binary(Path)),
    true = filelib:is_dir(Path).

ensure_path_invalid_path(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    BaseDir = filename:join(PrivDir, "ensure_path_invalid_path"),
    ok = filelib:ensure_path(BaseDir),
    FileName =  filename:join(BaseDir, "foo"),
    ok = file:write_file(FileName, <<"eh?\n">>),
    Path = filename:join(FileName, "foo/bar/baz"),
    {error,enotdir} = filelib:ensure_path(Path),
    false = filelib:is_dir(Path).

ensure_path_relative_path(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    BaseDir = filename:join(PrivDir, "ensure_path_relative_path"),
    ok = filelib:ensure_path(BaseDir),
    ok = file:set_cwd(BaseDir),
    Path = filename:join(BaseDir, "foo/bar/baz"),
    ok = filelib:ensure_path("foo/bar/baz"),
    true = filelib:is_dir(Path).

ensure_path_relative_path_dot_dot(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    BaseDir = filename:join(PrivDir, "ensure_path_relative_path"),
    SubDir = filename:join(BaseDir, "dot_dot"),
    ok = filelib:ensure_path(SubDir),
    ok = file:set_cwd(SubDir),
    Path = filename:join(BaseDir, "foo/bar/baz"),
    ok = filelib:ensure_path("../foo/bar/baz"),
    true = filelib:is_dir(Path).

ensure_path_symlink(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    Dir = filename:join(PrivDir, "ensure_path_symlink"),
    Name = filename:join(Dir, "same_name_as_file_and_dir"),
    ok = filelib:ensure_path(Dir),
    ok = file:write_file(Name, <<"some string\n">>),
    %% With a symlink to the directory.
    Symlink = filename:join(PrivDir, "ensure_path_symlink_link"),
    case file:make_symlink(Dir, Symlink) of
        {error,enotsup} ->
            {skip,"Symlinks not supported on this platform"};
        {error,eperm} ->
            {win32,_} = os:type(),
            {skip,"Windows user not privileged to create symlinks"};
        ok ->
            SymlinkedName = filename:join(Symlink, "same_name_as_file_and_dir"),
            ok = filelib:ensure_dir(SymlinkedName)
    end.

wildcard_symlink(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    Dir = filename:join(PrivDir, ?MODULE_STRING++"_wildcard_symlink"),
    SubDir = filename:join(Dir, "sub"),
    AFile = filename:join(SubDir, "a_file"),
    Alias = filename:join(Dir, "symlink"),
    ok = file:make_dir(Dir),
    ok = file:make_dir(SubDir),
    ok = file:write_file(AFile, "not that big\n"),
    case file:make_symlink(AFile, Alias) of
	{error, enotsup} ->
	    {skip, "Links not supported on this platform"};
	{error, eperm} ->
	    {win32,_} = os:type(),
	    {skip, "Windows user not privileged to create symlinks"};
	ok ->
	    ["sub","symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "*"))),
	    ["symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "symlink"))),
	    ["sub","symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "*"),
						erl_prim_loader)),
	    ["symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "symlink"),
						erl_prim_loader)),
	    ["sub","symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "*"),
						?PRIM_FILE)),
	    ["symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "symlink"),
						?PRIM_FILE)),
	    ok = file:delete(AFile),
	    %% The symlink should still be visible even when its target
	    %% has been deleted.
	    ["sub","symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "*"))),
	    ["symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "symlink"))),
	    ["sub","symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "*"),
						erl_prim_loader)),
	    ["symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "symlink"),
						erl_prim_loader)),
	    ["sub","symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "*"),
						?PRIM_FILE)),
	    ["symlink"] =
		basenames(Dir, filelib:wildcard(filename:join(Dir, "symlink"),
						?PRIM_FILE)),
	    ok
    end.

basenames(Dir, Files) ->
    [begin
	 Dir = filename:dirname(F),
	 filename:basename(F)
     end || F <- Files].

is_file_symlink(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    Dir = filename:join(PrivDir, ?MODULE_STRING++"_is_file_symlink"),
    SubDir = filename:join(Dir, "sub"),
    AFile = filename:join(SubDir, "a_file"),
    DirAlias = filename:join(Dir, "dir_symlink"),
    FileAlias = filename:join(Dir, "file_symlink"),
    ok = file:make_dir(Dir),
    ok = file:make_dir(SubDir),
    ok = file:write_file(AFile, "not that big\n"),
    case file:make_symlink(SubDir, DirAlias) of
	{error, enotsup} ->
	    {skip, "Links not supported on this platform"};
	{error, eperm} ->
	    {win32,_} = os:type(),
	    {skip, "Windows user not privileged to create symlinks"};
	ok ->
	    true = filelib:is_dir(DirAlias),
	    true = filelib:is_dir(DirAlias, erl_prim_loader),
	    true = filelib:is_dir(DirAlias, ?PRIM_FILE),
	    true = filelib:is_file(DirAlias),
	    true = filelib:is_file(DirAlias, erl_prim_loader),
	    true = filelib:is_file(DirAlias, ?PRIM_FILE),
	    ok = file:make_symlink(AFile,FileAlias),
	    true = filelib:is_file(FileAlias),
	    true = filelib:is_file(FileAlias, erl_prim_loader),
	    true = filelib:is_file(FileAlias, ?PRIM_FILE),
	    true = filelib:is_regular(FileAlias),
	    true = filelib:is_regular(FileAlias, erl_prim_loader),
	    true = filelib:is_regular(FileAlias, ?PRIM_FILE),
	    ok
    end.

file_props_symlink(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    Dir = filename:join(PrivDir, ?MODULE_STRING++"_file_props_symlink"),
    AFile = filename:join(Dir, "a_file"),
    Alias = filename:join(Dir, "symlink"),
    ok = file:make_dir(Dir),
    ok = file:write_file(AFile, "not that big\n"),
    case file:make_symlink(AFile, Alias) of
	{error, enotsup} ->
	    {skip, "Links not supported on this platform"};
	{error, eperm} ->
	    {win32,_} = os:type(),
	    {skip, "Windows user not privileged to create symlinks"};
	ok ->
	    {_,_} = LastMod = filelib:last_modified(AFile),
	    LastMod = filelib:last_modified(Alias),
	    LastMod = filelib:last_modified(Alias, erl_prim_loader),
	    LastMod = filelib:last_modified(Alias, ?PRIM_FILE),
	    FileSize = filelib:file_size(AFile),
	    FileSize = filelib:file_size(Alias),
	    FileSize = filelib:file_size(Alias, erl_prim_loader),
	    FileSize = filelib:file_size(Alias, ?PRIM_FILE)
    end.

find_source(Config) when is_list(Config) ->
    %% filelib:find_{file,source}() does not work if the files are
    %% cover-compiled. To make sure that the test does not fail
    %% when the STDLIB is cover-compiled, search for modules in
    %% the compiler application.

    BeamFile = code:which(compile),
    BeamName = filename:basename(BeamFile),
    BeamDir = filename:dirname(BeamFile),
    SrcName = filename:basename(BeamFile, ".beam") ++ ".erl",

    {ok, BeamFile} = filelib:find_file(BeamName, BeamDir),
    {ok, BeamFile} = filelib:find_file(BeamName, BeamDir, []),
    {ok, BeamFile} = filelib:find_file(BeamName, BeamDir, [{"",""},{"ebin","src"}]),
    {error, not_found} = filelib:find_file(BeamName, BeamDir, [{"ebin","src"}]),

    {ok, SrcFile} = filelib:find_file(SrcName, BeamDir),
    {ok, SrcFile} = filelib:find_file(SrcName, BeamDir, []),
    {ok, SrcFile} = filelib:find_file(SrcName, BeamDir, [{"foo","bar"},{"ebin","src"}]),
    {error, not_found} = filelib:find_file(SrcName, BeamDir, [{"",""}]),

    {ok, SrcFile} = filelib:find_source(BeamFile),
    {ok, SrcFile} = filelib:find_source(BeamName, BeamDir),
    {ok, SrcFile} = filelib:find_source(BeamName, BeamDir,
                                         [{".erl",".yrl",[{"",""}]},
                                          {".beam",".erl",[{"ebin","src"}]}]),
    {error, not_found} = filelib:find_source(BeamName, BeamDir,
                                              [{".erl",".yrl",[{"",""}]}]),

    {ok, ParserErl} = filelib:find_source(code:which(core_parse)),
    ParserErlName = filename:basename(ParserErl),
    ParserErlDir = filename:dirname(ParserErl),
    {ok, ParserYrl} = filelib:find_source(ParserErl),
    "lry." ++ _ = lists:reverse(ParserYrl),
    {ok, ParserYrl} = filelib:find_source(ParserErlName, ParserErlDir,
                                           [{".beam",".erl",[{"ebin","src"}]},
                                            {".erl",".yrl",[{"",""}]}]),

    %% find_source automatically checks the local directory regardless of rules
    {ok, ParserYrl} = filelib:find_source(ParserErl),
    {ok, ParserYrl} = filelib:find_source(ParserErlName, ParserErlDir,
                                          [{".erl",".yrl",[{"ebin","src"}]}]),

    %% find_file does not check the local directory unless in the rules
    ParserYrlName = filename:basename(ParserYrl),
    ParserYrlDir = filename:dirname(ParserYrl),
    {ok, ParserYrl} = filelib:find_file(ParserYrlName, ParserYrlDir,
                                        [{"",""}]),
    {error, not_found} = filelib:find_file(ParserYrlName, ParserYrlDir,
                                           [{"ebin","src"}]),

    %% local directory is in the default list for find_file
    {ok, ParserYrl} = filelib:find_file(ParserYrlName, ParserYrlDir),
    {ok, ParserYrl} = filelib:find_file(ParserYrlName, ParserYrlDir, []),
    ok.

find_source_subdir(Config) when is_list(Config) ->
    BeamFile = code:which(inets), % Located in lib/inets/src/inets_app/
    BeamName = filename:basename(BeamFile),
    BeamDir = filename:dirname(BeamFile),
    SrcName = filename:basename(BeamFile, ".beam") ++ ".erl",

    {ok, SrcFile} = filelib:find_source(BeamName, BeamDir),
    SrcName = filename:basename(SrcFile),

    {error, not_found} =
        filelib:find_source(BeamName, BeamDir,
                            [{".beam",".erl",[{"ebin","src"}]}]),
    {ok, SrcFile} =
        filelib:find_source(BeamName, BeamDir,
                            [{".beam",".erl",
                              [{"ebin",filename:join("src", "*")}]}]),

    {ok, SrcFile} = filelib:find_file(SrcName, BeamDir),

    ok.

%% Test that all available beam files in Erlang/OTP can be
%% tracked to their source files.
find_source_otp(Config) when is_list(Config) ->
    %% We do this in a peer as testcases before this may have
    %% edited the code path and thus more modules show up as
    %% available than should.
    {ok, Peer, Node} = ?CT_PEER(#{ env => [{"ERL_LIBS", false}] }),
    erpc:call(
      Node,
      fun() ->
              lists:map(
                fun F({Module, preloaded, Loaded}) ->
                        F({Module, code:where_is_file(Module ++ ".beam"), Loaded});
		    F({Module, cover_compiled, Loaded}) ->
		        ok;
                    F({Module, Filename, _Loaded}) ->
                        case filelib:find_source(Filename) of
                            {ok, _} -> ok;
                            {error,_} = E ->
                                ct:fail({failed_to_find, Module, Filename, E})
                        end
                end, code:all_available())
      end),
    peer:stop(Peer),
    ok.

safe_relative_path(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    Root = filename:join(PrivDir, "filelib_SUITE_safe_relative_path"),
    ok = file:make_dir(Root),
    ok = file:set_cwd(Root),

    ok = file:make_dir("a"),
    ok = file:set_cwd("a"),
    ok = file:make_dir("b"),
    ok = file:set_cwd("b"),
    ok = file:make_dir("c"),

    ok = file:set_cwd(Root),

    "a" = test_srp("a"),
    "a/b" = test_srp("a/b"),
    "a/b" = test_srp("a/./b"),
    "a/b" = test_srp("a/./b/."),

    "" = test_srp("a/.."),
    "" = test_srp("a/./.."),
    "" = test_srp("a/../."),
    "a" = test_srp("a/b/.."),
    "a" = test_srp("a/../a"),
    "a" = test_srp("a/../a/../a"),
    "a/b/c" = test_srp("a/../a/b/c"),

    unsafe = test_srp("a/../.."),
    unsafe = test_srp("a/../../.."),
    unsafe = test_srp("a/./../.."),
    unsafe = test_srp("a/././../../.."),
    unsafe = test_srp("a/b/././../../.."),

    unsafe = test_srp(PrivDir),                 %Absolute path.

    ok.

test_srp(RelPath) ->
    Res = do_test_srp(RelPath),
    Res = case do_test_srp(list_to_binary(RelPath)) of
              Bin when is_binary(Bin) ->
                  binary_to_list(Bin);
              Other ->
                  Other
          end.

do_test_srp(RelPath) ->
    {ok,Root} = file:get_cwd(),
    ok = file:set_cwd(RelPath),
    {ok,Cwd} = file:get_cwd(),
    ok = file:set_cwd(Root),
    case filelib:safe_relative_path(RelPath, Cwd) of
        unsafe ->
            true = length(Cwd) < length(Root),
            unsafe;
        "" ->
            "";
        SafeRelPath ->
            ok = file:set_cwd(SafeRelPath),
            {ok,Cwd} = file:get_cwd(),
            true = length(Cwd) >= length(Root),
            ok = file:set_cwd(Root),
            SafeRelPath
    end.

safe_relative_path_links(Config) ->
    PrivDir = ?config(priv_dir, Config),
    BaseDir = filename:join(PrivDir, "filelib_SUITE_safe_relative_path_links"),
    ok = file:make_dir(BaseDir),
    try
        case check_symlink_support(BaseDir) of
            true ->
                simple_test(BaseDir),
                inside_directory_test(BaseDir),
                nested_links_test(BaseDir),
                loop_test(BaseDir),
                loop_with_parent_test(BaseDir),
                revist_links_test(BaseDir);
            false ->
                {skipped, "This platform/user can't create symlinks."}
        end
    after
        %% This test leaves some rather nasty links that may screw with
        %% z_SUITE's core file search, so we must make sure everything's
        %% removed regardless of what happens.
        rm_rf(BaseDir)
    end.

check_symlink_support(BaseDir) ->
    Canary = filename:join(BaseDir, "symlink_canary"),
    Link = filename:join(BaseDir, "symlink_canary_link"),
    ok = file:write_file(Canary, <<"chirp">>),
    ok =:= file:make_symlink(Canary, Link).

simple_test(BaseDir) ->
    file:make_dir(filename:join(BaseDir, "simple_test")),
    file:make_symlink("..", filename:join(BaseDir, "simple_test/link")),

    unsafe = filelib:safe_relative_path("link/file", filename:join(BaseDir, "simple_test")),
    "file" = filelib:safe_relative_path("file", filename:join(BaseDir, "simple_test/link")).

inside_directory_test(BaseDir) ->
    file:make_dir(filename:join(BaseDir, "inside_directory_test")),
    file:make_symlink("..", filename:join(BaseDir, "inside_directory_test/link")),

    unsafe = filelib:safe_relative_path("link/file", filename:join(BaseDir, "inside_directory_test")),
    "file" = filelib:safe_relative_path("file", filename:join(BaseDir, "inside_directory_test/link")).

nested_links_test(BaseDir) ->
    file:make_dir(filename:join(BaseDir, "nested_links_test")),
    file:make_dir(filename:join(BaseDir, "nested_links_test/a")),
    file:make_symlink("a/b/c", filename:join(BaseDir, "nested_links_test/link")),
    file:make_symlink("..", filename:join(BaseDir, "nested_links_test/a/b")),

    "c/file" = filelib:safe_relative_path("link/file", filename:join(BaseDir, "nested_links_test")),

    file:delete(filename:join(BaseDir, "nested_links_test/a/b")),
    file:make_symlink("../..", filename:join(BaseDir, "nested_links_test/a/b")),
    unsafe = filelib:safe_relative_path("link/file", filename:join(BaseDir, "nested_links_test")).

loop_test(BaseDir) ->
    file:make_dir(filename:join(BaseDir, "loop_test")),

    file:make_symlink("b", filename:join(BaseDir, "loop_test/c")),
    file:make_symlink("c", filename:join(BaseDir, "loop_test/b")),

    unsafe = filelib:safe_relative_path("c", filename:join(BaseDir, "loop_test")).

loop_with_parent_test(BaseDir) ->
    file:make_dir(filename:join(BaseDir, "loop_with_parent_test")),
    file:make_dir(filename:join(BaseDir, "loop_with_parent_test/bar")),

    file:make_symlink("../bar/foo", filename:join(BaseDir, "loop_with_parent_test/bar/foo")),

    unsafe = filelib:safe_relative_path("bar/foo", filename:join(BaseDir, "loop_with_parent_test")).

revist_links_test(BaseDir) ->
    file:make_dir(filename:join(BaseDir, "revist_links_test")),

    file:make_symlink(".", filename:join(BaseDir, "revist_links_test/x")),
    file:make_symlink("x", filename:join(BaseDir, "revist_links_test/y")),
    file:make_symlink("y", filename:join(BaseDir, "revist_links_test/z")),

    "file" = filelib:safe_relative_path("x/file", filename:join(BaseDir, "revist_links_test")),
    "file" = filelib:safe_relative_path("y/x/file", filename:join(BaseDir, "revist_links_test")),
    "file" = filelib:safe_relative_path("x/x/file", filename:join(BaseDir, "revist_links_test")),
    "file" = filelib:safe_relative_path("x/y/x/y/file", filename:join(BaseDir, "revist_links_test")),
    "file" = filelib:safe_relative_path("x/y/z/x/y/z/file", filename:join(BaseDir, "revist_links_test")),
    "file" = filelib:safe_relative_path("x/x/y/y/file", filename:join(BaseDir, "revist_links_test")),
    "file" = filelib:safe_relative_path("x/z/y/x/./z/foo/../x/./y/file", filename:join(BaseDir, "revist_links_test")).

rm_rf(Dir) ->
    case file:read_link_info(Dir) of
        {ok, #file_info{type = directory}} ->
            {ok, Content} = file:list_dir_all(Dir),
            [ rm_rf(filename:join(Dir,C)) || C <- Content ],
            file:del_dir(Dir),
            ok;
        {ok, #file_info{}} ->
            file:delete(Dir);
        _ ->
            ok
    end.
