erl_tar.erl
来自「OTP是开放电信平台的简称」· ERL 代码 · 共 921 行 · 第 1/2 页
ERL
921 行
{ok, {read, File}} -> Result = case catch foldl_read1(Fun, Accu, File, Opts) of {'EXIT', Reason} -> exit(Reason); {error, {Reason, Format, Args}} -> read_verbose(Opts, Format, Args), {error, Reason}; {error, Reason} -> {error, Reason}; Ok -> Ok end, file:close(File), Result; Error -> Error end.foldl_read1(Fun, Accu0, File, Opts) -> case get_header(File) of eof -> Fun(eof, File, Opts, Accu0); Header -> {ok, NewAccu} = Fun(Header, File, Opts, Accu0), foldl_read1(Fun, NewAccu, File, Opts) end.table1(eof, _, _, Result) -> {ok, lists:reverse(Result)};table1(Header, File, #read_opts{verbose=true}, Result) when is_record(Header, tar_header) -> #tar_header{name=Name, size=Size, mtime=Mtime, typeflag=Type, mode=Mode, uid=Uid, gid=Gid} = Header, skip(File, Size), {ok, [{Name, Type, Size, posix_to_erlang_time(Mtime), Mode, Uid, Gid}|Result]};table1(Header, File, _, Result) when is_record(Header, tar_header) -> Name = Header#tar_header.name, skip(File, Header#tar_header.size), {ok, [Name|Result]}.extract1(eof, _, _, []) -> ok;extract1(Header, File, Opts, Result) -> Name = Header#tar_header.name, ok = extract2(File, Header, Opts, check_extract(Name, Opts)), {ok, Result}.extract2(File, Header, Opts, true) -> {ok, Bin} = get_element(File, Header), write_extracted_element(Header, Bin, Opts);extract2(File, Header, _, false) -> skip(File, Header#tar_header.size).%% Checks if the file Name should be extracted.check_extract(_, #read_opts{files=all}) -> true;check_extract(Name, #read_opts{files=Files}) -> ordsets:is_element(Name, Files).get_header(File) -> case file:read(File, ?record_size) of eof -> throw({error, eof}); {ok, Bin} when is_binary(Bin) -> convert_header(Bin); {ok, List} -> convert_header(list_to_binary(List)); {error, Reason} -> throw({error, Reason}) end.%% Converts the tar header to a record.convert_header(Bin) when size(Bin) =:= ?record_size -> case verify_checksum(Bin) of ok -> Hd = #tar_header{name=get_name(Bin), mode=from_octal(Bin, ?th_mode, ?th_mode_len), uid=from_octal(Bin, ?th_uid, ?th_uid_len), gid=from_octal(Bin, ?th_gid, ?th_gid_len), size=from_octal(Bin, ?th_size, ?th_size_len), mtime=from_octal(Bin, ?th_mtime, ?th_mtime_len), linkname=from_string(Bin, ?th_linkname, ?th_linkname_len), typeflag=typeflag(Bin)}, convert_header1(Hd); eof -> eof end;convert_header(Bin) when size(Bin) =:= 0 -> eof;convert_header(_Bin) -> throw({error, eof}).%% Basic sanity. Better set the element size to zero here if the type%% always is of zero length.convert_header1(H) when H#tar_header.typeflag =:= symlink, H#tar_header.size =/= 0 -> convert_header1(H#tar_header{size=0});convert_header1(H) when H#tar_header.typeflag =:= directory, H#tar_header.size =/= 0 -> convert_header1(H#tar_header{size=0});convert_header1(Header) -> Header.typeflag(Bin) -> [T] = binary_to_list(Bin, ?th_typeflag+1, ?th_typeflag+1), case T of 0 -> regular; $0 -> regular; $1 -> link; $2 -> symlink; $3 -> char; $4 -> block; $5 -> directory; $6 -> fifo; $7 -> regular; _ -> unknown end.%% Get the name of the file from the prefix and name fields of the%% tar header.get_name(Bin) -> Name = from_string(Bin, ?th_name, ?th_name_len), case binary_to_list(Bin, ?th_prefix+1, ?th_prefix+1) of [0] -> Name; [_] -> Prefix = binary_to_list(Bin, ?th_prefix+1, size(Bin)), lists:reverse(remove_nulls(Prefix), [$/|Name]) end.from_string(Bin, Pos, Len) -> lists:reverse(remove_nulls(binary_to_list(Bin, Pos+1, Pos+Len))). %% Returns all characters up to (but not including) the first null%% character, in REVERSE order.remove_nulls(List) -> remove_nulls(List, []).remove_nulls([0|_], Result) -> remove_nulls([], Result);remove_nulls([C|Rest], Result) -> remove_nulls(Rest, [C|Result]);remove_nulls([], Result) -> Result.from_octal(Bin, Pos, Len) -> from_octal(binary_to_list(Bin, Pos+1, Pos+Len)).from_octal([$\s|Rest]) -> from_octal(Rest);from_octal([Digit|Rest]) when $0 =< Digit, Digit =< $7 -> from_octal(Rest, Digit-$0);from_octal(Bin) when is_binary(Bin) -> from_octal(binary_to_list(Bin));from_octal(Other) -> throw({error, {bad_header, "Bad octal number: ~p", [Other]}}).from_octal([Digit|Rest], Result) when $0 =< Digit, Digit =< $7 -> from_octal(Rest, Result*8+Digit-$0);from_octal([$\s|_], Result) -> Result;from_octal([0|_], Result) -> Result;from_octal(Other, _) -> throw({error, {bad_header, "Bad contents in octal field: ~p", [Other]}}).%% Retrieves the next element from the archive.%% Returns {ok, Bin} | eof | {error, Reason}get_element(File, #tar_header{size = 0}) -> skip_to_next(File), {ok,<<>>};get_element(File, #tar_header{size = Size}) -> case file:read(File, Size) of {ok,Bin}=Res when size(Bin) =:= Size -> skip_to_next(File), Res; {ok,List} when length(List) =:= Size -> skip_to_next(File), {ok,list_to_binary(List)}; {ok,_} -> throw({error,eof}); {error, Reason} -> throw({error, Reason}); eof -> throw({error,eof}) end.%% Verify the checksum in the header. First try an unsigned addition%% of all bytes in the header (as it should be according to Posix).verify_checksum(Bin) -> <<H1:?th_chksum/binary,CheckStr:?th_chksum_len/binary,H2/binary>> = Bin, case checksum(H1) + checksum(H2) of 0 -> eof; Checksum0 -> Csum = from_octal(CheckStr), CsumInit = ?th_chksum_len * $\s, case Checksum0 + CsumInit of Csum -> ok; Unsigned -> verify_checksum(H1, H2, CsumInit, Csum, Unsigned) end end.%% The checksums didn't match. Now try a signed addition.verify_checksum(H1, H2, Csum, ShouldBe, Unsigned) -> case signed_sum(binary_to_list(H1), signed_sum(binary_to_list(H2), Csum)) of ShouldBe -> ok; Signed -> throw({error, {bad_header, "Incorrect directory checksum ~w (~w), should be ~w", [Signed, Unsigned, ShouldBe]}}) end.signed_sum([C|Rest], Sum) when C < 128 -> signed_sum(Rest, Sum+C);signed_sum([C|Rest], Sum) -> signed_sum(Rest, Sum+C-256);signed_sum([], Sum) -> Sum.write_extracted_element(Header, Bin, Opts) -> Name = filename:absname(Header#tar_header.name, Opts#read_opts.cwd), Created = case Header#tar_header.typeflag of regular -> write_extracted_file(Name, Bin, Opts); directory -> create_extracted_dir(Name, Opts); symlink -> create_symlink(Name, Header, Opts); Other -> % Ignore. read_verbose(Opts, "x ~s - unsupported type ~p~n", [Name, Other]), not_written end, case Created of ok -> set_extracted_file_info(Name, Header); not_written -> ok end.create_extracted_dir(Name, _Opts) -> case file:make_dir(Name) of ok -> ok; {error,enotsup} -> not_written; {error,eexist} -> not_written; {error,enoent} -> make_dirs(Name, dir); {error,Reason} -> throw({error, Reason}) end.create_symlink(Name, #tar_header{linkname=Linkname}=Header, Opts) -> case file:make_symlink(Linkname, Name) of ok -> ok; {error,enoent} -> ok = make_dirs(Name, file), create_symlink(Name, Header, Opts); {error,eexist} -> not_written; {error,enotsup} -> read_verbose(Opts, "x ~s - symbolic links not supported~n", [Name]), not_written; {error,Reason} -> throw({error, Reason}) end.write_extracted_file(Name, Bin, Opts) -> Write = case Opts#read_opts.keep_old_files of true -> case file:read_file_info(Name) of {ok, _} -> false; _ -> true end; false -> true end, case Write of true -> read_verbose(Opts, "x ~s~n", [Name]), write_file(Name, Bin); false -> read_verbose(Opts, "x ~s - exists, not created~n", [Name]), not_written end.write_file(Name, Bin) -> case file:write_file(Name, Bin) of ok -> ok; {error,enoent} -> ok = make_dirs(Name, file), write_file(Name, Bin); {error,Reason} -> throw({error, Reason}) end.set_extracted_file_info(_, #tar_header{typeflag = symlink}) -> ok;set_extracted_file_info(Name, #tar_header{mode=Mode, mtime=Mtime}) -> Info = #file_info{mode=Mode, mtime=posix_to_erlang_time(Mtime)}, file:write_file_info(Name, Info).%% Makes all directories leading up to the file.make_dirs(Name, Type) -> make_dirs1(filename:split(Name), Type).make_dirs1([Dir, Next|Rest], Type) -> case file:read_file_info(Dir) of {ok, #file_info{type=directory}} -> make_dirs1([filename:join(Dir, Next)|Rest], Type); {ok, #file_info{}} -> throw({error, enotdir}); {error, _} -> case file:make_dir(Dir) of ok -> make_dirs1([filename:join(Dir, Next)|Rest], Type); {error, Reason} -> throw({error, Reason}) end end;make_dirs1([_], file) -> ok;make_dirs1([Dir], dir) -> file:make_dir(Dir);make_dirs1([], _) -> %% There must be something wrong here. The list was not supposed %% to be empty. throw({error, enoent}).%% Prints the message on if the verbose option is given (for reading).read_verbose(#read_opts{verbose=true}, Format, Args) -> io:format(Format, Args), io:nl();read_verbose(_, _, _) -> ok.%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Utility functions.%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Returns the checksum of a binary.checksum(Bin) -> checksum(Bin, 0).checksum(<<A,B,C,D,E,F,G,H,T/binary>>, Sum) -> checksum(T, Sum+A+B+C+D+E+F+G+H);checksum(<<A,T/binary>>, Sum) -> checksum(T, Sum+A);checksum(<<>>, Sum) -> Sum.%% Returns a list of zeroes to pad out to the given block size.padding(Size, BlockSize) -> zeroes(pad_size(Size, BlockSize)).pad_size(Size, BlockSize) -> case Size rem BlockSize of 0 -> 0; Rem -> BlockSize-Rem end.zeroes(0) -> [];zeroes(1) -> [0];zeroes(2) -> [0,0];zeroes(Number) -> Half = zeroes(Number div 2), case Number rem 2 of 0 -> [Half|Half]; 1 -> [Half|[0|Half]] end.%% Skips the given number of bytes rounded up to an even record.skip(File, Size) -> %% Note: There is no point in handling failure to get the current position %% in the file. If it doesn't work, something serious is wrong. Amount = ((Size + ?record_size - 1) div ?record_size) * ?record_size, {ok,_} = file:position(File, {cur, Amount}), ok.%% Skips to the next record in the file.skip_to_next(File) -> %% Note: There is no point in handling failure to get the current position %% in the file. If it doesn't work, something serious is wrong. {ok, Position} = file:position(File, {cur, 0}), NewPosition = ((Position + ?record_size - 1) div ?record_size) * ?record_size, {ok,NewPosition} = file:position(File, NewPosition), ok.%% Prints the message on if the verbose option is given.add_verbose(#add_opts{verbose=true}, Format, Args) -> io:format(Format, Args);add_verbose(_, _, _) -> ok.%% Converts a tuple containing the time to a Posix time (seconds%% since Jan 1, 1970).posix_time(Time) -> EpochStart = {{1970,1,1},{0,0,0}}, {Days,{Hour,Min,Sec}} = calendar:time_difference(EpochStart, Time), 86400*Days + 3600*Hour + 60*Min + Sec.posix_to_erlang_time(Sec) -> OneMillion = 1000000, Time = calendar:now_to_datetime({Sec div OneMillion, Sec rem OneMillion, 0}), erlang:universaltime_to_localtime(Time).read_file_and_info(Name, Opts) -> ReadInfo = Opts#add_opts.read_info, case ReadInfo(Name) of {ok,Info} when Info#file_info.type =:= regular -> case file:read_file(Name) of {ok,Bin} -> {ok,Bin,Info}; Error -> Error end; {ok,Info} when Info#file_info.type =:= symlink -> case file:read_link(Name) of {ok,PointsTo} -> {ok,PointsTo,Info}; Error -> Error end; {ok, Info} -> {ok,[],Info}; Error -> Error end.foreach_while_ok(Fun, [First|Rest]) -> case Fun(First) of ok -> foreach_while_ok(Fun, Rest); Other -> Other end;foreach_while_ok(_, []) -> ok. open_mode(Mode) -> open_mode(Mode, false, [raw], []).open_mode(read, _, Raw, _) -> {ok, read, Raw, []};open_mode(write, _, Raw, _) -> {ok, write, Raw, []};open_mode([read|Rest], false, Raw, Opts) -> open_mode(Rest, read, Raw, Opts);open_mode([write|Rest], false, Raw, Opts) -> open_mode(Rest, write, Raw, Opts);open_mode([compressed|Rest], Access, Raw, Opts) -> open_mode(Rest, Access, Raw, [compressed|Opts]);open_mode([cooked|Rest], Access, _Raw, Opts) -> open_mode(Rest, Access, [], Opts);open_mode([], Access, Raw, Opts) -> {ok, Access, Raw, Opts};open_mode(_, _, _, _) -> {error, einval}.
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?