1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 1997-2011. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: -module(tar_SUITE).
   20: 
   21: -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
   22: 	 init_per_group/2,end_per_group/2, borderline/1, atomic/1, long_names/1,
   23: 	 create_long_names/1, bad_tar/1, errors/1, extract_from_binary/1,
   24: 	 extract_from_binary_compressed/1,
   25: 	 extract_from_open_file/1, symlinks/1, open_add_close/1, cooked_compressed/1,
   26: 	 memory/1]).
   27: 
   28: -include_lib("test_server/include/test_server.hrl").
   29: -include_lib("kernel/include/file.hrl").
   30: 
   31: suite() -> [{ct_hooks,[ts_install_cth]}].
   32: 
   33: all() -> 
   34:     [borderline, atomic, long_names, create_long_names,
   35:      bad_tar, errors, extract_from_binary,
   36:      extract_from_binary_compressed, extract_from_open_file,
   37:      symlinks, open_add_close, cooked_compressed, memory].
   38: 
   39: groups() -> 
   40:     [].
   41: 
   42: init_per_suite(Config) ->
   43:     Config.
   44: 
   45: end_per_suite(_Config) ->
   46:     ok.
   47: 
   48: init_per_group(_GroupName, Config) ->
   49:     Config.
   50: 
   51: end_per_group(_GroupName, Config) ->
   52:     Config.
   53: 
   54: 
   55: borderline(doc) ->
   56:     ["Test creating, listing and extracting one file from an archive",
   57:      "multiple times with different file sizes. ",
   58:      "Also check that the file attributes of the extracted file has survived."];
   59: borderline(Config) when is_list(Config) ->
   60: 
   61:     %% Note: We cannot use absolute paths, because the pathnames will be
   62:     %% too long for the limit allowed in tar files (100 characters).
   63:     %% Therefore, strip off the current working directory from the front
   64:     %% of the private directory path.
   65: 
   66:     ?line {ok, Cwd} = file:get_cwd(),
   67:     ?line RootDir = ?config(priv_dir, Config),
   68:     ?line TempDir = remove_prefix(Cwd++"/", filename:join(RootDir, "borderline")),
   69:     ?line ok = file:make_dir(TempDir),
   70: 
   71:     ?line Record = 512,
   72:     ?line Block = 20 * Record,
   73: 
   74:     ?line lists:foreach(fun(Size) -> borderline_test(Size, TempDir) end,
   75: 			[0, 1, 10, 13, 127, 333, Record-1, Record, Record+1,
   76: 			 Block-Record-1, Block-Record, Block-Record+1,
   77: 			 Block-1, Block, Block+1,
   78: 			 Block+Record-1, Block+Record, Block+Record+1]),
   79: 
   80:     %% Clean up.
   81:     ?line delete_files([TempDir]),
   82: 
   83:     ok.
   84: 
   85: borderline_test(Size, TempDir) ->
   86:     ?line Archive = filename:join(TempDir, "ar_"++integer_to_list(Size)++".tar"),
   87:     ?line Name = filename:join(TempDir, "file_"++integer_to_list(Size)),
   88:     ?line io:format("Testing size ~p", [Size]),
   89: 
   90:     %% Create a file and archive it.
   91:     ?line {_, _, X0} = erlang:now(),
   92:     ?line file:write_file(Name, random_byte_list(X0, Size)),
   93:     ?line ok = erl_tar:create(Archive, [Name]),
   94:     ?line ok = file:delete(Name),
   95: 
   96:     %% Verify listing and extracting.
   97:     ?line {ok, [Name]} = erl_tar:table(Archive),
   98:     ?line ok = erl_tar:extract(Archive, [verbose]),
   99: 
  100:     %% Verify contents of extracted file.
  101:     ?line {ok, Bin} = file:read_file(Name),
  102:     ?line true = match_byte_list(X0, binary_to_list(Bin)),
  103: 
  104:     %% Verify that Unix tar can read it.
  105:     ?line tar_tf(Archive, Name),
  106: 
  107:     ok.
  108: 
  109: tar_tf(Archive, Name) ->
  110:     case os:type() of
  111: 	{unix, _} ->
  112: 	    tar_tf1(Archive, Name);
  113: 	_ ->
  114: 	    ok
  115:     end.
  116: 
  117: tar_tf1(Archive, Name) ->
  118:     ?line Expect = Name ++ "\n",
  119:     ?line cmd_expect("tar tf " ++ Archive, Expect).
  120: 
  121: %% We can't use os:cmd/1, because Unix 'tar tf Name' on Solaris never
  122: %% terminates when given an archive of a size it doesn't like.
  123: 
  124: cmd_expect(Cmd, Expect) ->
  125:     ?line Port = open_port({spawn, make_cmd(Cmd)}, [stream, in, eof]),
  126:     ?line get_data(Port, Expect).
  127: 
  128: get_data(Port, Expect) ->
  129:     receive
  130: 	{Port, {data, Bytes}} ->
  131: 	    ?line get_data(Port, match_output(Bytes, Expect, Port));
  132: 	{Port, eof} ->
  133: 	    Port ! {self(), close}, 
  134: 	    receive
  135: 		{Port, closed} ->
  136: 		    true
  137: 	    end, 
  138: 	    receive
  139: 		{'EXIT',  Port,  _} -> 
  140: 		    ok
  141: 	    after 1 ->				% force context switch
  142: 		    ok
  143: 	    end, 
  144: 	    ?line match_output(eof, Expect, Port)
  145:     end.
  146: 
  147: match_output([C|Output], [C|Expect], Port) ->
  148:     ?line match_output(Output, Expect, Port);
  149: match_output([_|_], [_|_], Port) ->
  150:     ?line kill_port_and_fail(Port, badmatch);
  151: match_output([X|Output], [], Port) ->
  152:     ?line kill_port_and_fail(Port, {too_much_data, [X|Output]});
  153: match_output([], Expect, _Port) ->
  154:     Expect;
  155: match_output(eof, [], _Port) ->
  156:     [];
  157: match_output(eof, _Expect, Port) ->
  158:     ?line kill_port_and_fail(Port, unexpected_end_of_input).
  159: 
  160: kill_port_and_fail(Port, Reason) ->
  161:     unlink(Port),
  162:     exit(Port, die),
  163:     test_server:fail(Reason).
  164: 
  165: make_cmd(Cmd) ->
  166:     case os:type() of
  167: 	{win32, _} -> lists:concat(["cmd /c",  Cmd]);
  168: 	{unix, _}  -> lists:concat(["sh -c '",  Cmd,  "'"])
  169:     end.
  170: 
  171: %% Verifies a random byte list.
  172: 
  173: match_byte_list(X0, [Byte|Rest]) ->
  174:     X = next_random(X0),
  175:     case (X bsr 26) band 16#ff of
  176: 	Byte -> match_byte_list(X, Rest);
  177: 	_ -> false
  178:     end;
  179: match_byte_list(_, []) ->
  180:     true.
  181: 
  182: %% Generates a random byte list.
  183: 
  184: random_byte_list(X0, Count) ->
  185:     random_byte_list(X0, Count, []).
  186: 
  187: random_byte_list(X0, Count, Result) when Count > 0->
  188:     X = next_random(X0),
  189:     random_byte_list(X, Count-1, [(X bsr 26) band 16#ff|Result]);
  190: random_byte_list(_X, 0, Result) ->
  191:     lists:reverse(Result).
  192: 
  193: %% This RNG is from line 21 on page 102 in Knuth: The Art of Computer Programming,
  194: %% Volume II, Seminumerical Algorithms.
  195: 
  196: next_random(X) ->
  197:     (X*17059465+1) band 16#fffffffff.
  198: 
  199: atomic(doc) ->
  200:     ["Test the 'atomic' operations: create/extract/table, on compressed "
  201:      "and uncompressed archives."
  202:      "Also test the 'cooked' option."];
  203: atomic(suite) -> [];
  204: atomic(Config) when is_list(Config) ->
  205:     ?line ok = file:set_cwd(?config(priv_dir, Config)),
  206:     ?line DataFiles = data_files(),
  207:     ?line Names = [Name || {Name,_,_} <- DataFiles],
  208:     io:format("Names: ~p", [Names]),
  209: 
  210:     %% Create an uncompressed archive.  The compressed flag should still be
  211:     %% allowed when listing contents or extracting.
  212: 
  213:     ?line Tar1 = "uncompressed.tar",
  214:     ?line erl_tar:create(Tar1, Names, []),
  215:     ?line {ok, Names} = erl_tar:table(Tar1, []),
  216:     ?line {ok, Names} = erl_tar:table(Tar1, [compressed]),
  217:     ?line {ok, Names} = erl_tar:table(Tar1, [cooked]),
  218:     ?line {ok, Names} = erl_tar:table(Tar1, [compressed,cooked]),
  219:     
  220:     %% Create a compressed archive.
  221: 
  222:     ?line Tar2 = "compressed.tar",
  223:     ?line erl_tar:create(Tar2, Names, [compressed]),
  224:     ?line {ok, Names} = erl_tar:table(Tar2, [compressed]),
  225:     ?line {error, Reason} = erl_tar:table(Tar2, []),
  226:     ?line {ok, Names} = erl_tar:table(Tar2, [compressed,cooked]),
  227:     ?line {error, Reason} = erl_tar:table(Tar2, [cooked]),
  228:     ?line ok = io:format("No compressed option: ~p, ~s",
  229: 			 [Reason, erl_tar:format_error(Reason)]),
  230: 
  231:     %% Same test again, but this time created with 'cooked'
  232: 
  233:     ?line Tar3 = "uncompressed_cooked.tar",
  234:     ?line erl_tar:create(Tar3, Names, [cooked]),
  235:     ?line {ok, Names} = erl_tar:table(Tar3, []),
  236:     ?line {ok, Names} = erl_tar:table(Tar3, [compressed]),
  237:     ?line {ok, Names} = erl_tar:table(Tar3, [cooked]),
  238:     ?line {ok, Names} = erl_tar:table(Tar3, [compressed,cooked]),
  239:     
  240:     ?line Tar4 = "compressed_cooked.tar",
  241:     ?line erl_tar:create(Tar4, Names, [compressed,cooked]),
  242:     ?line {ok, Names} = erl_tar:table(Tar4, [compressed]),
  243:     ?line {error, Reason} = erl_tar:table(Tar4, []),
  244:     ?line {ok, Names} = erl_tar:table(Tar4, [compressed,cooked]),
  245:     ?line {error, Reason} = erl_tar:table(Tar4, [cooked]),
  246:     ?line ok = io:format("No compressed option: ~p, ~s",
  247: 			 [Reason, erl_tar:format_error(Reason)]),
  248: 
  249:     %% Clean up.
  250:     ?line delete_files([Tar1,Tar2,Tar3,Tar4|Names]),
  251: 
  252:     ok.
  253: 
  254: %% Returns a sequence of characters.
  255: 
  256: char_seq(N, First) ->
  257:     char_seq(N, First, []).
  258: 
  259: char_seq(0, _, Result) ->
  260:     Result;
  261: char_seq(N, C, Result) when C < 127 ->
  262:     char_seq(N-1, C+1, [C|Result]);
  263: char_seq(N, _, Result) ->
  264:     char_seq(N, $!, Result).
  265: 
  266: data_files() ->
  267:     Files = [{"first_file", 1555, $a},
  268: 	     {"small_file", 7, $d},
  269: 	     {"big_file", 23875, $e},
  270: 	     {"last_file", 7500, $g}],
  271:     create_files(Files),
  272:     Files.
  273: 
  274: create_files([{Name, Size, First}|Rest]) ->
  275:     ok = file:write_file(Name, char_seq(Size, First)),
  276:     create_files(Rest);
  277: create_files([]) ->
  278:     ok.
  279: 
  280: long_names(doc) ->
  281:     ["Test to extract an Unix tar file containing filenames longer than 100 ",
  282:     "characters and empty directories."];
  283: long_names(Config) when is_list(Config) ->
  284:     ?line DataDir = ?config(data_dir, Config),
  285:     ?line Long = filename:join(DataDir, "long_names.tar"),
  286:     run_in_short_tempdir(Config,
  287: 			 fun() -> do_long_names(Long) end).
  288: 
  289: do_long_names(Long) ->
  290:     %% Try table/2 and extract/2.
  291:     ?line case erl_tar:table(Long, [verbose]) of
  292: 	      {ok,List} when is_list(List) ->
  293: 		  ?line io:format("~p\n", [List])
  294: 	  end,
  295: 
  296:     ?line {ok,Cwd} = file:get_cwd(),
  297:     ?line ok = erl_tar:extract(Long),
  298:     ?line Base = filename:join([Cwd, "original_software", "written_by",
  299: 				"a_bunch_of_hackers",
  300: 				"spending_all_their_nights",
  301: 				"still", "not_long_enough",
  302: 				"but_soon_it_will_be"]),
  303: 
  304:     %% Verify that the empty directory was created.
  305:     ?line EmptyDir = filename:join(Base, "empty_directory"),
  306:     ?line {ok, #file_info{type=directory}} = file:read_file_info(EmptyDir),
  307: 
  308:     %% Verify that the files were created.
  309:     ?line {ok,First} = file:read_file(filename:join(Base, "first_file")),
  310:     ?line {ok,Second} = file:read_file(filename:join(Base, "second_file")),
  311:     ?line "Here"++_ = binary_to_list(First),
  312:     ?line "And"++_ = binary_to_list(Second),
  313: 
  314:     ok.
  315: 
  316: create_long_names(doc) ->
  317:     ["Creates a tar file from a deep directory structure (filenames are ",
  318:      "longer than 100 characters)."];
  319: create_long_names(Config) when is_list(Config) ->
  320:     run_in_short_tempdir(Config, fun create_long_names/0).
  321:     
  322: create_long_names() ->
  323:     ?line {ok,Dir} = file:get_cwd(),
  324:     Dirs = ["aslfjkshjkhliuf",
  325: 	    "asdhjfehnbfsky",
  326: 	    "sahajfskdfhsz",
  327: 	    "asldfkdlfy4y8rchg",
  328: 	    "f7nafhjgffagkhsfkhsjk",
  329: 	    "dfjasldkfjsdkfjashbv"],
  330: 
  331:     ?line DeepDir = make_dirs(Dirs, []),
  332:     ?line AFile = filename:join(DeepDir, "a_file"),
  333:     ?line Hello = "hello, world\n",
  334:     ?line ok = file:write_file(AFile, Hello),
  335:     ?line TarName = filename:join(Dir,  "my_tar_with_long_names.tar"),
  336:     ?line ok = erl_tar:create(TarName, [AFile]),
  337: 
  338:     %% Print contents.
  339:     ?line ok = erl_tar:tt(TarName),
  340: 
  341:     %% Extract and verify.
  342:     ?line ExtractDir = "extract_dir",
  343:     ?line ok = file:make_dir(ExtractDir),
  344:     ?line ok = erl_tar:extract(TarName, [{cwd,ExtractDir}]),
  345:     ?line {ok, Bin} = file:read_file(filename:join(ExtractDir, AFile)),
  346:     ?line Hello = binary_to_list(Bin),
  347: 
  348:     ok.
  349: 
  350: make_dirs([Dir|Rest], []) ->
  351:     ?line ok = file:make_dir(Dir),
  352:     ?line make_dirs(Rest, Dir);
  353: make_dirs([Dir|Rest], Parent) ->
  354:     ?line Name = filename:join(Parent, Dir),
  355:     ?line ok = file:make_dir(Name),
  356:     ?line make_dirs(Rest, Name);
  357: make_dirs([], Dir) ->
  358:     Dir.
  359: 
  360: bad_tar(doc) ->
  361:     ["Try erl_tar:table/2 and erl_tar:extract/2 on some corrupted tar files."];
  362: bad_tar(Config) when is_list(Config) ->
  363:     ?line try_bad("bad_checksum", bad_header, Config),
  364:     ?line try_bad("bad_octal",    bad_header, Config),
  365:     ?line try_bad("bad_too_short",    eof, Config),
  366:     ?line try_bad("bad_even_shorter", eof, Config),
  367:     ok.
  368: 
  369: try_bad(Name0, Reason, Config) ->
  370:     %% Intentionally no ?line macros here.
  371: 
  372:     DataDir = ?config(data_dir, Config),
  373:     PrivDir = ?config(priv_dir, Config),
  374:     Name = Name0 ++ ".tar",
  375:     io:format("~nTrying ~s", [Name]),
  376:     Full = filename:join(DataDir, Name),
  377:     Opts = [verbose, {cwd, PrivDir}],
  378:     Expected = {error, Reason},
  379:     case {erl_tar:table(Full, Opts), erl_tar:extract(Full, Opts)} of
  380: 	{Expected, Expected} ->
  381: 	    io:format("Result: ~p", [Expected]),
  382: 	    case catch erl_tar:format_error(Reason) of
  383: 		{'EXIT', CrashReason} ->
  384: 		    test_server:fail({format_error, crashed, CrashReason});
  385: 		String when is_list(String) ->
  386: 		    io:format("format_error(~p) -> ~s", [Reason, String]);
  387: 		Other ->
  388: 		    test_server:fail({format_error, returned, Other})
  389: 	    end;
  390: 	{Other1, Other2} ->
  391: 	    io:format("table/2 returned ~p", [Other1]),
  392: 	    io:format("extract/2 returned ~p", [Other2]),
  393: 	    test_server:fail({bad_return_value, Other1, Other2})
  394:     end.
  395: 
  396: errors(doc) ->
  397:     ["Tests that some common errors return correct error codes ",
  398:      "and that format_error/1 handles them correctly."];
  399: errors(Config) when is_list(Config) ->
  400:     ?line PrivDir = ?config(priv_dir, Config),
  401: 
  402:     %% Give the tar file the same name as a directory.
  403:     ?line BadTar = filename:join(PrivDir, "bad_tarfile.tar"),
  404:     ?line ok = file:make_dir(BadTar),
  405:     ?line try_error(erl_tar, create, [BadTar, []], {BadTar, eisdir}),
  406: 
  407:     %% Try including non-existent files in the tar file.
  408:     ?line NonExistent = "non_existent_file",
  409:     ?line GoodTar = filename:join(PrivDir, "a_good_tarfile.tar"),
  410:     ?line try_error(erl_tar, create, [GoodTar, [NonExistent]],
  411: 		    {NonExistent, enoent}),
  412: 
  413:     %% Clean up.
  414:     ?line delete_files([GoodTar,BadTar]),
  415:     
  416:     ok.
  417: 
  418: try_error(M, F, A, Error) ->
  419:     io:format("Trying ~p:~p(~p)", [M, F, A]),
  420:     case catch apply(M, F, A) of
  421: 	{'EXIT', Reason} ->
  422: 	    exit(Reason);
  423: 	ok ->
  424: 	    test_server:fail(unexpected_success);
  425: 	{error, Error} ->
  426: 	    case catch erl_tar:format_error(Error) of
  427: 		{'EXIT', FReason} ->
  428: 		    test_server:fail({format_error, crashed, FReason});
  429: 		String when is_list(String) ->
  430: 		    io:format("format_error(~p) -> ~s", [Error, String]);
  431: 		Other ->
  432: 		    test_server:fail({format_error, returned, Other})
  433: 	    end;
  434: 	Other ->
  435: 	    test_server:fail({expected, {error, Error}, actual, Other})
  436:     end.
  437: 
  438: %% remove_prefix(Prefix, List) -> ListWithoutPrefix.
  439: 
  440: remove_prefix([C|Rest1], [C|Rest2]) ->
  441:     remove_prefix(Rest1, Rest2);
  442: remove_prefix(_, Result) ->
  443:     Result.
  444: 
  445: extract_from_binary(doc) ->
  446:     "Test extracting a tar archive from a binary.";
  447: extract_from_binary(Config) when is_list(Config) ->
  448:     ?line DataDir = ?config(data_dir, Config),
  449:     ?line PrivDir = ?config(priv_dir, Config),
  450:     ?line Long = filename:join(DataDir, "no_fancy_stuff.tar"),
  451:     ?line ExtractDir = filename:join(PrivDir, "extract_from_binary"),
  452:     ?line ok = file:make_dir(ExtractDir),
  453:     
  454:     %% Read a tar file into a binary and extract from the binary.
  455:     ?line {ok, Bin} = file:read_file(Long),
  456:     ?line ok = erl_tar:extract({binary, Bin}, [{cwd,ExtractDir}]),
  457: 
  458:     %% Verify.
  459:     Dir = filename:join(ExtractDir, "no_fancy_stuff"),
  460:     ?line true = filelib:is_dir(Dir),
  461:     ?line true = filelib:is_file(filename:join(Dir, "a_dir_list")),
  462:     ?line true = filelib:is_file(filename:join(Dir, "EPLICENCE")),
  463: 
  464:     %% Clean up.
  465:     ?line delete_files([ExtractDir]),
  466: 
  467:     ok.
  468: 
  469: extract_from_binary_compressed(Config) when is_list(Config) ->
  470:     %% Test extracting a compressed tar archive from a binary.
  471:     ?line DataDir = ?config(data_dir, Config),
  472:     ?line PrivDir = ?config(priv_dir, Config),
  473:     ?line Name = filename:join(DataDir, "cooked_tar_problem.tar.gz"),
  474:     ?line ExtractDir = filename:join(PrivDir, "extract_from_binary_compressed"),
  475:     ?line ok = file:make_dir(ExtractDir),
  476:     ?line {ok,Bin} = file:read_file(Name),
  477: 
  478:     %% Try taking contents.
  479:     ?line {ok,Files} = erl_tar:table({binary,Bin}, [compressed]),
  480:     ?line io:format("~p\n", [Files]),
  481:     ?line 19 = length(Files),
  482:     
  483:     %% Trying extracting from a binary.
  484:     ?line ok = erl_tar:extract({binary,Bin}, [compressed,{cwd,ExtractDir}]),
  485:     ?line {ok,List} = file:list_dir(filename:join(ExtractDir, "ddll_SUITE_data")),
  486:     ?line io:format("~p\n", [List]),
  487:     ?line 19 = length(List),
  488: 
  489:     %% Clean up while at the same time testing that all file
  490:     %% were extracted as expected.
  491:     lists:foreach(fun(N) ->
  492: 			  File = filename:join(ExtractDir, N),
  493: 			  io:format("Deleting: ~p\n", [File]),
  494: 			  ?line ok = file:delete(File)
  495: 		  end, Files),
  496: 
  497:     %% Clean up the rest.
  498:     ?line delete_files([ExtractDir]),
  499: 
  500:     ok.
  501: 
  502: extract_from_open_file(doc) ->
  503:     "Test extracting a tar archive from an open file.";
  504: extract_from_open_file(Config) when is_list(Config) ->
  505:     ?line DataDir = ?config(data_dir, Config),
  506:     ?line PrivDir = ?config(priv_dir, Config),
  507:     ?line Long = filename:join(DataDir, "no_fancy_stuff.tar"),
  508:     ?line ExtractDir = filename:join(PrivDir, "extract_from_open_file"),
  509:     ?line ok = file:make_dir(ExtractDir),
  510: 
  511:     ?line {ok, File} = file:open(Long, [read]),
  512:     ?line ok = erl_tar:extract({file, File}, [{cwd,ExtractDir}]),
  513: 
  514:     %% Verify.
  515:     Dir = filename:join(ExtractDir, "no_fancy_stuff"),
  516:     ?line true = filelib:is_dir(Dir),
  517:     ?line true = filelib:is_file(filename:join(Dir, "a_dir_list")),
  518:     ?line true = filelib:is_file(filename:join(Dir, "EPLICENCE")),
  519: 
  520:     %% Close open file.
  521:     ?line ok = file:close(File),
  522: 
  523:     %% Clean up.
  524:     ?line delete_files([ExtractDir]),
  525: 
  526:     ok.
  527: 
  528: symlinks(doc) ->
  529:     "Test that archives containing symlinks can be created and extracted.";
  530: symlinks(Config) when is_list(Config) ->
  531:     ?line PrivDir = ?config(priv_dir, Config),
  532:     ?line Dir = filename:join(PrivDir, "symlinks"),
  533:     ?line ok = file:make_dir(Dir),
  534:     ?line ABadSymlink = filename:join(Dir, "bad_symlink"),
  535:     ?line PointsTo = "/a/definitely/non_existing/path",
  536:     ?line Res = case make_symlink("/a/definitely/non_existing/path", ABadSymlink) of
  537: 		    {error, enotsup} ->
  538: 			{skip, "Symbolic links not supported on this platform"};
  539: 		    ok ->
  540: 			symlinks(Dir, "bad_symlink", PointsTo),
  541: 			long_symlink(Dir)
  542: 		end,
  543: 
  544:     %% Clean up.
  545:     ?line delete_files([Dir]),
  546:     Res.
  547: 
  548: make_symlink(Path, Link) ->
  549:     case os:type() of
  550: 	{win32,_} ->
  551: 	    %% Symlinks on Windows have two problems:
  552: 	    %%   1) file:read_link_info/1 cannot read out the target
  553: 	    %%      of the symlink if the target does not exist.
  554: 	    %%      That is possible (but not easy) to fix in the
  555: 	    %%      efile driver.
  556: 	    %%
  557: 	    %%   2) Symlinks to files and directories are different
  558: 	    %%      creatures. If the target is not existing, the
  559: 	    %%      symlink will be created to be of the file-pointing
  560: 	    %%      type. That can be partially worked around in erl_tar
  561: 	    %%      by creating all symlinks when the end of the tar
  562: 	    %%      file has been reached.
  563: 	    %%
  564: 	    %% But for now, pretend that there are no symlinks on
  565: 	    %% Windows.
  566: 	    {error, enotsup};
  567: 	_ ->
  568: 	    file:make_symlink(Path, Link)
  569:     end.
  570: 	  
  571: symlinks(Dir, BadSymlink, PointsTo) ->
  572:     ?line Tar = filename:join(Dir, "symlink.tar"),
  573:     ?line DerefTar = filename:join(Dir, "dereference.tar"),
  574: 
  575:     %% Create the archive.
  576: 
  577:     ?line ok = file:set_cwd(Dir),
  578:     ?line GoodSymlink = "good_symlink",
  579:     ?line AFile = "a_good_file",
  580:     ?line ALine = "A line of text for a file.",
  581:     ?line ok = file:write_file(AFile, ALine),
  582:     ?line ok = file:make_symlink(AFile, GoodSymlink),
  583:     ?line ok = erl_tar:create(Tar, [BadSymlink, GoodSymlink, AFile], [verbose]),
  584: 
  585:     %% List contents of tar file.
  586: 
  587:     ?line ok = erl_tar:tt(Tar),
  588: 
  589:     %% Also create another archive with the dereference flag.
  590: 
  591:     ?line ok = erl_tar:create(DerefTar, [AFile, GoodSymlink], [dereference, verbose]),
  592: 
  593:     %% Extract files to a new directory.
  594: 
  595:     ?line NewDir = filename:join(Dir, "extracted"),
  596:     ?line ok = file:make_dir(NewDir),
  597:     ?line ok = erl_tar:extract(Tar, [{cwd, NewDir}, verbose]),
  598: 
  599:     %% Verify that the files are there.
  600: 
  601:     ?line ok = file:set_cwd(NewDir),
  602:     ?line {ok, #file_info{type=symlink}} = file:read_link_info(BadSymlink),
  603:     ?line {ok, PointsTo} = file:read_link(BadSymlink),
  604:     ?line {ok, #file_info{type=symlink}} = file:read_link_info(GoodSymlink),
  605:     ?line {ok, AFile} = file:read_link(GoodSymlink),
  606:     ?line Expected = list_to_binary(ALine),
  607:     ?line {ok, Expected} = file:read_file(GoodSymlink),
  608: 
  609:     %% Extract the "dereferenced archive"  to a new directory.
  610: 
  611:     ?line NewDirDeref = filename:join(Dir, "extracted_deref"),
  612:     ?line ok = file:make_dir(NewDirDeref),
  613:     ?line ok = erl_tar:extract(DerefTar, [{cwd, NewDirDeref}, verbose]),
  614: 
  615:     %% Verify that the files are there.
  616: 
  617:     ?line ok = file:set_cwd(NewDirDeref),
  618:     ?line {ok, #file_info{type=regular}} = file:read_link_info(GoodSymlink),
  619:     ?line {ok, #file_info{type=regular}} = file:read_link_info(AFile),
  620:     ?line {ok, Expected} = file:read_file(GoodSymlink),
  621:     ?line {ok, Expected} = file:read_file(AFile),
  622: 
  623:     ok.
  624: 
  625: long_symlink(Dir) ->
  626:     ?line Tar = filename:join(Dir, "long_symlink.tar"),
  627:     ?line ok = file:set_cwd(Dir),
  628: 
  629:     ?line AFile = "long_symlink",
  630:     ?line FarTooLong = "/tmp/aarrghh/this/path/is/far/longer/than/one/hundred/characters/which/is/the/maximum/number/of/characters/allowed",
  631:     ?line ok = file:make_symlink(FarTooLong, AFile),
  632:     ?line {error,Error} = erl_tar:create(Tar, [AFile], [verbose]),
  633:     ?line io:format("Error: ~s\n", [erl_tar:format_error(Error)]),
  634:     ?line {FarTooLong,symbolic_link_too_long} = Error,
  635:     ok.
  636: 
  637: open_add_close(Config) when is_list(Config) ->
  638:     ?line PrivDir = ?config(priv_dir, Config),
  639:     ?line ok = file:set_cwd(PrivDir),
  640:     ?line Dir = filename:join(PrivDir, "open_add_close"),
  641:     ?line ok = file:make_dir(Dir),
  642: 
  643:     ?line [{FileOne,_,_},{FileTwo,_,_},{FileThree,_,_}] = oac_files(),
  644:     ?line ADir = "empty_dir",
  645:     ?line AnotherDir = "another_dir",
  646:     ?line SomeContent = filename:join(AnotherDir, "some_content"),
  647:     ?line ok = file:make_dir(ADir),
  648:     ?line ok = file:make_dir(AnotherDir),
  649:     ?line ok = file:make_dir(SomeContent),
  650: 
  651:     ?line TarOne = filename:join(Dir, "archive1.tar"),
  652:     ?line {ok,AD} = erl_tar:open(TarOne, [write]),
  653:     ?line ok = erl_tar:add(AD, FileOne, []),
  654:     ?line ok = erl_tar:add(AD, FileTwo, "second file", []),
  655:     ?line ok = erl_tar:add(AD, FileThree, [verbose]),
  656:     ?line ok = erl_tar:add(AD, ADir, [verbose]),
  657:     ?line ok = erl_tar:add(AD, AnotherDir, [verbose]),
  658:     ?line ok = erl_tar:close(AD),
  659: 
  660:     ?line ok = erl_tar:t(TarOne),
  661:     ?line ok = erl_tar:tt(TarOne),
  662: 
  663:     ?line {ok,[FileOne,"second file",FileThree,ADir,SomeContent]} = erl_tar:table(TarOne),
  664: 
  665:     ?line delete_files(["oac_file","oac_small","oac_big",Dir,AnotherDir,ADir]),
  666: 
  667:     ok.
  668: 
  669: oac_files() ->
  670:     Files = [{"oac_file", 1459, $x},
  671: 	     {"oac_small", 99, $w},
  672: 	     {"oac_big", 33896, $A}],
  673:     create_files(Files),
  674:     Files.
  675: 
  676: cooked_compressed(Config) when is_list(Config) ->
  677:     %% Test that a compressed archive can be read in cooked mode.
  678:     ?line DataDir = ?config(data_dir, Config),
  679:     ?line PrivDir = ?config(priv_dir, Config),
  680:     ?line Name = filename:join(DataDir, "cooked_tar_problem.tar.gz"),
  681: 
  682:     %% Try table/2 and extract/2.
  683:     ?line {ok,List} = erl_tar:table(Name, [cooked,compressed]),
  684:     ?line io:format("~p\n", [List]),
  685:     ?line 19 = length(List),
  686:     ?line ok = erl_tar:extract(Name, [cooked,compressed,{cwd,PrivDir}]),
  687: 
  688:     %% Clean up while at the same time testing that all file
  689:     %% were extracted as expected.
  690:     lists:foreach(fun(N) ->
  691: 			  File = filename:join(PrivDir, N),
  692: 			  io:format("Deleting: ~p\n", [File]),
  693: 			  ?line ok = file:delete(File)
  694: 		  end, List),
  695: 
  696:     %% Clean up.
  697:     ?line delete_files([filename:join(PrivDir, "ddll_SUITE_data")]),
  698:     ok.
  699: 
  700: memory(doc) ->
  701:     ["Test that an archive can be created directly from binaries and "
  702:      "that an archive can be extracted into binaries."];
  703: memory(Config) when is_list(Config) ->
  704:     ?line DataDir = ?config(data_dir, Config),
  705: 
  706:     ?line FileBins = [{"bar/fum", <<"BARFUM">>},{"foo", <<"FOO">>}],
  707:     ?line Name1 = filename:join(DataDir, "memory.tar"),
  708:     ?line ok = erl_tar:create(Name1, FileBins, [write,verbose]),
  709:     ?line {ok,Extracted1} = erl_tar:extract(Name1, [memory,verbose]),
  710:     ?line FileBins1 = lists:sort(Extracted1),
  711: 
  712:     ?line io:format("FileBins: ~p\n", [FileBins]),
  713:     ?line io:format("FileBins1: ~p\n", [FileBins1]),
  714:     ?line FileBins = FileBins1,
  715: 
  716:     ?line Name2 = filename:join(DataDir, "memory2.tar"),
  717:     ?line {ok,Fd} = erl_tar:open(Name2, [write]),
  718:     ?line [ok,ok] = [erl_tar:add(Fd, B, N, [write,verbose]) || {N,B} <- FileBins],
  719:     ?line ok = erl_tar:close(Fd),
  720:     ?line {ok,Extracted2} = erl_tar:extract(Name2, [memory,verbose]),
  721:     ?line FileBins2 = lists:sort(Extracted2),
  722:     ?line io:format("FileBins2: ~p\n", [FileBins2]),
  723:     ?line FileBins = FileBins2,
  724: 
  725:     %% Clean up.
  726:     ?line ok = delete_files([Name1,Name2]),
  727:     ok.
  728: 
  729: %% Delete the given list of files.
  730: delete_files([]) -> ok;
  731: delete_files([Item|Rest]) ->
  732:     case file:delete(Item) of
  733: 	ok ->
  734: 	    delete_files(Rest);
  735: 	{error,eperm} ->
  736: 	    file:change_mode(Item, 8#777),
  737: 	    delete_files(filelib:wildcard(filename:join(Item, "*"))),
  738: 	    file:del_dir(Item),
  739: 	    ok;
  740: 	{error,eacces} ->
  741: 	    %% We'll see about that!
  742: 	    file:change_mode(Item, 8#777),
  743: 	    case file:delete(Item) of
  744: 		ok -> ok;
  745: 		{error,_} ->
  746: 		    erlang:yield(),
  747: 		    file:change_mode(Item, 8#777),
  748: 		    file:delete(Item),
  749: 		    ok
  750: 	    end;
  751: 	{error,_} -> ok
  752:     end,
  753:     delete_files(Rest).
  754: 
  755: %% Move to a temporary directory with as short name as possible and
  756: %% execute Fun. Remove the directory and any files in it afterwards.
  757: %% This is necessary because pathnames on Windows may be limited to
  758: %% 260 characters.
  759: run_in_short_tempdir(Config, Fun) ->
  760:     {ok,Cwd} = file:get_cwd(),
  761:     PrivDir0 = ?config(priv_dir, Config),
  762: 
  763:     %% Normalize name to make sure that there is no slash at the end.
  764:     PrivDir = filename:absname(PrivDir0),
  765: 
  766:     %% We need a base directory with a much shorter pathname than
  767:     %% priv_dir. We KNOW that priv_dir is located four levels below
  768:     %% the directory that common_test puts the ct_run.* directories
  769:     %% in. That fact is not documented, but a usually reliable source
  770:     %% assured me that the directory structure is unlikely to change
  771:     %% in future versions of common_test because of backwards
  772:     %% compatibility (tools developed by users of common_test depend
  773:     %% on the current directory layout).
  774:     Base = lists:foldl(fun(_, D) ->
  775: 			       filename:dirname(D)
  776: 		       end, PrivDir, [1,2,3,4]),
  777: 
  778:     Dir = make_temp_dir(Base, 0),
  779:     ok = file:set_cwd(Dir),
  780:     io:format("Running test in ~s\n", [Dir]),
  781:     try
  782: 	Fun()
  783:     after
  784: 	file:set_cwd(Cwd),
  785: 	delete_files([Dir])
  786:     end.
  787: 
  788: make_temp_dir(Base, I) ->
  789:     Name = filename:join(Base, integer_to_list(I, 36)),
  790:     case file:make_dir(Name) of
  791: 	ok -> Name;
  792: 	{error,eexist} -> make_temp_dir(Base, I+1)
  793:     end.