aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/test/tar_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib/test/tar_SUITE.erl')
-rw-r--r--lib/stdlib/test/tar_SUITE.erl327
1 files changed, 286 insertions, 41 deletions
diff --git a/lib/stdlib/test/tar_SUITE.erl b/lib/stdlib/test/tar_SUITE.erl
index 64dd41e75a..4061008812 100644
--- a/lib/stdlib/test/tar_SUITE.erl
+++ b/lib/stdlib/test/tar_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1997-2017. 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.
@@ -20,11 +20,15 @@
-module(tar_SUITE).
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
- init_per_group/2,end_per_group/2, borderline/1, atomic/1, long_names/1,
+ init_per_group/2, end_per_group/2,
+ init_per_testcase/2,
+ borderline/1, atomic/1, long_names/1,
create_long_names/1, bad_tar/1, errors/1, extract_from_binary/1,
- extract_from_binary_compressed/1,
+ extract_from_binary_compressed/1, extract_filtered/1,
extract_from_open_file/1, symlinks/1, open_add_close/1, cooked_compressed/1,
- memory/1,unicode/1]).
+ memory/1,unicode/1,read_other_implementations/1,
+ sparse/1, init/1, leading_slash/1, dotdot/1,
+ roundtrip_metadata/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
@@ -35,7 +39,10 @@ all() ->
[borderline, atomic, long_names, create_long_names,
bad_tar, errors, extract_from_binary,
extract_from_binary_compressed, extract_from_open_file,
- symlinks, open_add_close, cooked_compressed, memory, unicode].
+ extract_filtered,
+ symlinks, open_add_close, cooked_compressed, memory, unicode,
+ read_other_implementations,
+ sparse,init,leading_slash,dotdot,roundtrip_metadata].
groups() ->
[].
@@ -52,6 +59,9 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
+init_per_testcase(_Case, Config) ->
+ Ports = ordsets:from_list(erlang:ports()),
+ [{ports,Ports}|Config].
%% Test creating, listing and extracting one file from an archive,
%% multiple times with different file sizes. Also check that the file
@@ -81,20 +91,33 @@ borderline(Config) when is_list(Config) ->
%% Clean up.
delete_files([TempDir]),
- ok.
+ verify_ports(Config).
borderline_test(Size, TempDir) ->
- Archive = filename:join(TempDir, "ar_"++integer_to_list(Size)++".tar"),
- Name = filename:join(TempDir, "file_"++integer_to_list(Size)),
io:format("Testing size ~p", [Size]),
+ borderline_test(Size, TempDir, true),
+ borderline_test(Size, TempDir, false),
+ ok.
+
+borderline_test(Size, TempDir, IsUstar) ->
+ Prefix = case IsUstar of
+ true ->
+ "file_";
+ false ->
+ lists:duplicate(100, $f) ++ "ile_"
+ end,
+ SizeList = integer_to_list(Size),
+ Archive = filename:join(TempDir, "ar_"++ SizeList ++".tar"),
+ Name = filename:join(TempDir, Prefix++SizeList),
%% Create a file and archive it.
X0 = erlang:monotonic_time(),
- file:write_file(Name, random_byte_list(X0, Size)),
+ ok = file:write_file(Name, random_byte_list(X0, Size)),
ok = erl_tar:create(Archive, [Name]),
ok = file:delete(Name),
%% Verify listing and extracting.
+ IsUstar = is_ustar(Archive),
{ok, [Name]} = erl_tar:table(Archive),
ok = erl_tar:extract(Archive, [verbose]),
@@ -103,7 +126,12 @@ borderline_test(Size, TempDir) ->
true = match_byte_list(X0, binary_to_list(Bin)),
%% Verify that Unix tar can read it.
- tar_tf(Archive, Name),
+ case IsUstar of
+ true ->
+ tar_tf(Archive, Name);
+ false ->
+ ok
+ end,
ok.
@@ -248,7 +276,7 @@ atomic(Config) when is_list(Config) ->
%% Clean up.
delete_files([Tar1,Tar2,Tar3,Tar4|Names]),
- ok.
+ verify_ports(Config).
%% Returns a sequence of characters.
@@ -282,7 +310,9 @@ long_names(Config) when is_list(Config) ->
DataDir = proplists:get_value(data_dir, Config),
Long = filename:join(DataDir, "long_names.tar"),
run_in_short_tempdir(Config,
- fun() -> do_long_names(Long) end).
+ fun() -> do_long_names(Long) end),
+ verify_ports(Config).
+
do_long_names(Long) ->
%% Try table/2 and extract/2.
@@ -314,7 +344,8 @@ do_long_names(Long) ->
%% Creates a tar file from a deep directory structure (filenames are
%% longer than 100 characters).
create_long_names(Config) when is_list(Config) ->
- run_in_short_tempdir(Config, fun create_long_names/0).
+ run_in_short_tempdir(Config, fun create_long_names/0),
+ verify_ports(Config).
create_long_names() ->
{ok,Dir} = file:get_cwd(),
@@ -336,6 +367,7 @@ create_long_names() ->
ok = erl_tar:tt(TarName),
%% Extract and verify.
+ true = is_ustar(TarName),
ExtractDir = "extract_dir",
ok = file:make_dir(ExtractDir),
ok = erl_tar:extract(TarName, [{cwd,ExtractDir}]),
@@ -357,10 +389,10 @@ make_dirs([], Dir) ->
%% Try erl_tar:table/2 and erl_tar:extract/2 on some corrupted tar files.
bad_tar(Config) when is_list(Config) ->
try_bad("bad_checksum", bad_header, Config),
- try_bad("bad_octal", bad_header, Config),
+ try_bad("bad_octal", invalid_tar_checksum, Config),
try_bad("bad_too_short", eof, Config),
try_bad("bad_even_shorter", eof, Config),
- ok.
+ verify_ports(Config).
try_bad(Name0, Reason, Config) ->
%% Intentionally no macros here.
@@ -370,8 +402,10 @@ try_bad(Name0, Reason, Config) ->
Name = Name0 ++ ".tar",
io:format("~nTrying ~s", [Name]),
Full = filename:join(DataDir, Name),
- Opts = [verbose, {cwd, PrivDir}],
+ Dest = filename:join(PrivDir, Name0),
+ Opts = [verbose, {cwd, Dest}],
Expected = {error, Reason},
+ io:fwrite("Expected: ~p\n", [Expected]),
case {erl_tar:table(Full, Opts), erl_tar:extract(Full, Opts)} of
{Expected, Expected} ->
io:format("Result: ~p", [Expected]),
@@ -408,7 +442,7 @@ errors(Config) when is_list(Config) ->
%% Clean up.
delete_files([GoodTar,BadTar]),
- ok.
+ verify_ports(Config).
try_error(M, F, A, Error) ->
io:format("Trying ~p:~p(~p)", [M, F, A]),
@@ -458,7 +492,7 @@ extract_from_binary(Config) when is_list(Config) ->
%% Clean up.
delete_files([ExtractDir]),
- ok.
+ verify_ports(Config).
extract_from_binary_compressed(Config) when is_list(Config) ->
%% Test extracting a compressed tar archive from a binary.
@@ -491,7 +525,28 @@ extract_from_binary_compressed(Config) when is_list(Config) ->
%% Clean up the rest.
delete_files([ExtractDir]),
- ok.
+ verify_ports(Config).
+
+%% Test extracting a tar archive from a binary.
+extract_filtered(Config) when is_list(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Long = filename:join(DataDir, "no_fancy_stuff.tar"),
+ ExtractDir = filename:join(PrivDir, "extract_from_binary"),
+ ok = file:make_dir(ExtractDir),
+
+ ok = erl_tar:extract(Long, [{cwd,ExtractDir},{files,["no_fancy_stuff/EPLICENCE"]}]),
+
+ %% Verify.
+ Dir = filename:join(ExtractDir, "no_fancy_stuff"),
+ true = filelib:is_dir(Dir),
+ false = filelib:is_file(filename:join(Dir, "a_dir_list")),
+ true = filelib:is_file(filename:join(Dir, "EPLICENCE")),
+
+ %% Clean up.
+ delete_files([ExtractDir]),
+
+ verify_ports(Config).
%% Test extracting a tar archive from an open file.
extract_from_open_file(Config) when is_list(Config) ->
@@ -516,7 +571,7 @@ extract_from_open_file(Config) when is_list(Config) ->
%% Clean up.
delete_files([ExtractDir]),
- ok.
+ verify_ports(Config).
%% Test that archives containing symlinks can be created and extracted.
symlinks(Config) when is_list(Config) ->
@@ -535,6 +590,7 @@ symlinks(Config) when is_list(Config) ->
%% Clean up.
delete_files([Dir]),
+ verify_ports(Config),
Res.
make_symlink(Path, Link) ->
@@ -573,6 +629,7 @@ symlinks(Dir, BadSymlink, PointsTo) ->
ok = file:write_file(AFile, ALine),
ok = file:make_symlink(AFile, GoodSymlink),
ok = erl_tar:create(Tar, [BadSymlink, GoodSymlink, AFile], [verbose]),
+ true = is_ustar(Tar),
%% List contents of tar file.
@@ -581,6 +638,7 @@ symlinks(Dir, BadSymlink, PointsTo) ->
%% Also create another archive with the dereference flag.
ok = erl_tar:create(DerefTar, [AFile, GoodSymlink], [dereference, verbose]),
+ true = is_ustar(DerefTar),
%% Extract files to a new directory.
@@ -619,13 +677,51 @@ long_symlink(Dir) ->
ok = file:set_cwd(Dir),
AFile = "long_symlink",
- FarTooLong = "/tmp/aarrghh/this/path/is/far/longer/than/one/hundred/characters/which/is/the/maximum/number/of/characters/allowed",
- ok = file:make_symlink(FarTooLong, AFile),
- {error,Error} = erl_tar:create(Tar, [AFile], [verbose]),
- io:format("Error: ~s\n", [erl_tar:format_error(Error)]),
- {FarTooLong,symbolic_link_too_long} = Error,
+ RequiresPAX = "/tmp/aarrghh/this/path/is/far/longer/than/one/hundred/characters/which/is/the/maximum/number/of/characters/allowed",
+ ok = file:make_symlink(RequiresPAX, AFile),
+ ok = erl_tar:create(Tar, [AFile], [verbose]),
+ false = is_ustar(Tar),
+ NewDir = filename:join(Dir, "extracted"),
+ _ = file:make_dir(NewDir),
+ ok = erl_tar:extract(Tar, [{cwd, NewDir}, verbose]),
+ ok = file:set_cwd(NewDir),
+ {ok, #file_info{type=symlink}} = file:read_link_info(AFile),
+ {ok, RequiresPAX} = file:read_link(AFile),
ok.
+init(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ ok = file:set_cwd(PrivDir),
+ Dir = filename:join(PrivDir, "init"),
+ ok = file:make_dir(Dir),
+
+ [{FileOne,_,_}|_] = oac_files(),
+ TarOne = filename:join(Dir, "archive1.tar"),
+ {ok,Fd} = file:open(TarOne, [write]),
+
+ %% If the arity of the fun is wrong, badarg should be returned
+ {error, badarg} = erl_tar:init(Fd, write, fun file_op_bad/1),
+
+ %% Otherwise we should be good to go
+ {ok, Tar} = erl_tar:init(Fd, write, fun file_op/2),
+ ok = erl_tar:add(Tar, FileOne, []),
+ ok = erl_tar:close(Tar),
+ {ok, [FileOne]} = erl_tar:table(TarOne),
+
+ verify_ports(Config).
+
+file_op_bad(_) ->
+ throw({error, should_never_be_called}).
+
+file_op(write, {Fd, Data}) ->
+ file:write(Fd, Data);
+file_op(position, {Fd, Pos}) ->
+ file:position(Fd, Pos);
+file_op(read2, {Fd, Size}) ->
+ file:read(Fd, Size);
+file_op(close, Fd) ->
+ file:close(Fd).
+
open_add_close(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
ok = file:set_cwd(PrivDir),
@@ -643,21 +739,30 @@ open_add_close(Config) when is_list(Config) ->
TarOne = filename:join(Dir, "archive1.tar"),
{ok,AD} = erl_tar:open(TarOne, [write]),
ok = erl_tar:add(AD, FileOne, []),
- ok = erl_tar:add(AD, FileTwo, "second file", []),
- ok = erl_tar:add(AD, FileThree, [verbose]),
+
+ %% Add with {NameInArchive,Name}
+ ok = erl_tar:add(AD, {"second file", FileTwo}, []),
+
+ %% Add with {binary, Bin}
+ {ok,FileThreeBin} = file:read_file(FileThree),
+ ok = erl_tar:add(AD, {FileThree, FileThreeBin}, [verbose]),
+
+ %% Add with Name
ok = erl_tar:add(AD, FileThree, "chunked", [{chunks,11411},verbose]),
ok = erl_tar:add(AD, ADir, [verbose]),
ok = erl_tar:add(AD, AnotherDir, [verbose]),
ok = erl_tar:close(AD),
+ true = is_ustar(TarOne),
ok = erl_tar:t(TarOne),
ok = erl_tar:tt(TarOne),
- {ok,[FileOne,"second file",FileThree,"chunked",ADir,SomeContent]} = erl_tar:table(TarOne),
+ Expected = {ok,[FileOne,"second file",FileThree,"chunked",ADir,SomeContent]},
+ Expected = erl_tar:table(TarOne),
delete_files(["oac_file","oac_small","oac_big",Dir,AnotherDir,ADir]),
- ok.
+ verify_ports(Config).
oac_files() ->
Files = [{"oac_file", 1459, $x},
@@ -688,7 +793,8 @@ cooked_compressed(Config) when is_list(Config) ->
%% Clean up.
delete_files([filename:join(PrivDir, "ddll_SUITE_data")]),
- ok.
+
+ verify_ports(Config).
%% Test that an archive can be created directly from binaries and
%% that an archive can be extracted into binaries.
@@ -716,24 +822,67 @@ memory(Config) when is_list(Config) ->
%% Clean up.
ok = delete_files([Name1,Name2]),
- ok.
+
+ verify_ports(Config).
+
+read_other_implementations(Config) when is_list(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ Files = ["v7.tar", "gnu.tar", "bsd.tar",
+ "star.tar", "pax_mtime.tar"],
+ do_read_other_implementations(Files, DataDir),
+ verify_ports(Config).
+
+do_read_other_implementations([], _DataDir) ->
+ ok;
+do_read_other_implementations([File|Rest], DataDir) ->
+ io:format("~nTrying ~s", [File]),
+ Full = filename:join(DataDir, File),
+ {ok, _} = erl_tar:table(Full),
+ {ok, _} = erl_tar:extract(Full, [memory]),
+ do_read_other_implementations(Rest, DataDir).
+
+
+%% Test handling of sparse files
+sparse(Config) when is_list(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Sparse01Empty = "sparse01_empty.tar",
+ Sparse01 = "sparse01.tar",
+ Sparse10Empty = "sparse10_empty.tar",
+ Sparse10 = "sparse10.tar",
+ do_sparse([Sparse01Empty, Sparse01, Sparse10Empty, Sparse10], DataDir, PrivDir),
+ verify_ports(Config).
+
+do_sparse([], _DataDir, _PrivDir) ->
+ ok;
+do_sparse([Name|Rest], DataDir, PrivDir) ->
+ io:format("~nTrying sparse file ~s", [Name]),
+ Full = filename:join(DataDir, Name),
+ {ok, [_]} = erl_tar:table(Full),
+ {ok, _} = erl_tar:extract(Full, [memory]),
+ do_sparse(Rest, DataDir, PrivDir).
%% Test filenames with characters outside the US ASCII range.
unicode(Config) when is_list(Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- do_unicode(PrivDir),
+ run_unicode_node(Config, "+fnu"),
case has_transparent_naming() of
true ->
- Pa = filename:dirname(code:which(?MODULE)),
- Node = start_node(unicode, "+fnl -pa "++Pa),
- ok = rpc:call(Node, erlang, apply,
- [fun() -> do_unicode(PrivDir) end,[]]),
- true = test_server:stop_node(Node),
- ok;
+ run_unicode_node(Config, "+fnl");
false ->
ok
end.
+run_unicode_node(Config, Option) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Pa = filename:dirname(code:which(?MODULE)),
+ Args = Option ++ " -pa "++Pa,
+ io:format("~s\n", [Args]),
+ Node = start_node(unicode, Args),
+ ok = rpc:call(Node, erlang, apply,
+ [fun() -> do_unicode(PrivDir) end,[]]),
+ true = test_server:stop_node(Node),
+ ok.
+
has_transparent_naming() ->
case os:type() of
{unix,darwin} -> false;
@@ -745,10 +894,14 @@ do_unicode(PrivDir) ->
ok = file:set_cwd(PrivDir),
ok = file:make_dir("unicöde"),
- Names = unicode_create_files(),
+ Names = lists:sort(unicode_create_files()),
Tar = "unicöde.tar",
ok = erl_tar:create(Tar, ["unicöde"], []),
- {ok,Names} = erl_tar:table(Tar, []),
+
+ %% Unicode filenames require PAX format.
+ false = is_ustar(Tar),
+ {ok,Names0} = erl_tar:table(Tar, []),
+ Names = lists:sort(Names0),
_ = [ok = file:delete(Name) || Name <- Names],
ok = erl_tar:extract(Tar),
_ = [{ok,_} = file:read_file(Name) || Name <- Names],
@@ -768,6 +921,75 @@ unicode_create_files() ->
[]
end].
+leading_slash(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Dir = filename:join(PrivDir, ?FUNCTION_NAME),
+ TarFile = filename:join(Dir, "leading_slash.tar"),
+ ok = filelib:ensure_dir(TarFile),
+ {ok,Fd} = erl_tar:open(TarFile, [write]),
+ TarMemberName = "e/d/c/b/a_member",
+ TarMemberNameAbs = "/" ++ TarMemberName,
+ Contents = <<"contents\n">>,
+ ok = erl_tar:add(Fd, Contents, TarMemberNameAbs, [verbose]),
+ ok = erl_tar:close(Fd),
+
+ ok = erl_tar:extract(TarFile, [{cwd,Dir}]),
+
+ {ok,Contents} = file:read_file(filename:join(Dir, TarMemberName)),
+ ok.
+
+dotdot(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Dir = filename:join(PrivDir, ?FUNCTION_NAME),
+ ok = file:make_dir(Dir),
+ Tar = filename:join(Dir, "dotdot.tar"),
+ {ok,Fd} = erl_tar:open(Tar, [write]),
+ BeamFile = code:which(?MODULE),
+ ok = erl_tar:add(Fd, BeamFile, "a/./../../some_file", []),
+ ok = erl_tar:close(Fd),
+
+ {error,{_,unsafe_path=Error}} = erl_tar:extract(Tar, [{cwd,Dir}]),
+ false = filelib:is_regular(filename:join(PrivDir, "some_file")),
+ io:format("~s\n", [erl_tar:format_error(Error)]),
+
+ ok.
+
+roundtrip_metadata(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Dir = filename:join(PrivDir, ?FUNCTION_NAME),
+ ok = file:make_dir(Dir),
+
+ do_roundtrip_metadata(Dir, "name-does-not-matter"),
+ ok.
+
+do_roundtrip_metadata(Dir, File) ->
+ Tar = filename:join(Dir, atom_to_list(?FUNCTION_NAME)++".tar"),
+ BeamFile = code:which(compile),
+ {ok,Fd} = erl_tar:open(Tar, [write]),
+ ok = erl_tar:add(Fd, BeamFile, File, []),
+ ok = erl_tar:close(Fd),
+
+ ok = erl_tar:extract(Tar, [{cwd,Dir}]),
+
+ %% Make sure that size and modification times are the same
+ %% on all platforms.
+ {ok,OrigInfo} = file:read_file_info(BeamFile),
+ ExtractedFile = filename:join(Dir, File),
+ {ok,ExtractedInfo} = file:read_file_info(ExtractedFile),
+ #file_info{size=Size,mtime=Mtime,type=regular} = OrigInfo,
+ #file_info{size=Size,mtime=Mtime,type=regular} = ExtractedInfo,
+
+ %% On Unix platforms more fields are expected to be the same.
+ case os:type() of
+ {unix,_} ->
+ #file_info{access=Access,mode=Mode} = OrigInfo,
+ #file_info{access=Access,mode=Mode} = ExtractedInfo,
+ ok;
+ _ ->
+ ok
+ end.
+
+
%% Delete the given list of files.
delete_files([]) -> ok;
delete_files([Item|Rest]) ->
@@ -844,3 +1066,26 @@ start_node(Name, Args) ->
ct:log("Node ~p started~n", [Node]),
Node
end.
+
+%% Test that the given tar file is a plain USTAR archive,
+%% without any PAX extensions.
+is_ustar(File) ->
+ {ok,Bin} = file:read_file(File),
+ <<_:257/binary,"ustar",0,_/binary>> = Bin,
+ <<_:156/binary,Type:8,_/binary>> = Bin,
+ case Type of
+ $x -> false;
+ $g -> false;
+ _ -> true
+ end.
+
+
+verify_ports(Config) ->
+ PortsBefore = proplists:get_value(ports, Config),
+ PortsAfter = ordsets:from_list(erlang:ports()),
+ case ordsets:subtract(PortsAfter, PortsBefore) of
+ [] ->
+ ok;
+ [_|_]=Rem ->
+ error({leaked_ports,Rem})
+ end.