erl_tar.erl
来自「OTP是开放电信平台的简称」· ERL 代码 · 共 921 行 · 第 1/2 页
ERL
921 行
%% ``The contents of this file are subject to the Erlang Public License,%% Version 1.1, (the "License"); you may not use this file except in%% compliance with the License. You should have received a copy of the%% Erlang Public License along with this software. If not, it can be%% retrieved via the world wide web at http://www.erlang.org/.%% %% Software distributed under the License is distributed on an "AS IS"%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See%% the License for the specific language governing rights and limitations%% under the License.%% %% The Initial Developer of the Original Code is Ericsson Utvecklings AB.%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings%% AB. All Rights Reserved.''%% %% $Id$%%-module(erl_tar).%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Purpose: Unix tar (tape archive) utility.%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%-export([create/2, create/3, extract/1, extract/2, table/1, table/2, open/2, close/1, add/3, add/4, t/1, tt/1, format_error/1]).-include_lib("kernel/include/file.hrl").-record(add_opts, {read_info, % Fun to use for read file/link info. verbose = false}). % Verbose on/off.%% Opens a tar archive.open(Name, Mode) -> case open_mode(Mode) of {ok, Access, Raw, Opts} -> open1(Name, Access, Raw, Opts); {error, Reason} -> {error, {Name, Reason}} end.open1({binary,Bin}, read, _Raw, Opts) -> case file:open(Bin, [ram,binary,read]) of {ok,File} -> case Opts of [compressed] -> ram_file:uncompress(File); [] -> ok end, {ok,{read,File}}; Error -> Error end;open1({file, Fd}, read, _Raw, _Opts) -> {ok, {read, Fd}};open1(Name, Access, Raw, Opts) -> case file:open(Name, Raw ++ [binary, Access|Opts]) of {ok, File} -> {ok, {Access, File}}; {error, Reason} -> {error, {Name, Reason}} end.%% Closes a tar archive.close({read, File}) -> file:close(File), ok;close({write, File}) -> PadResult = pad_file(File), file:close(File), PadResult;close(_) -> {error, einval}.%% Adds a file to a tape archive.add(File, Name, Options) -> add(File, Name, Name, Options).add({write, File}, Name, NameInArchive, Options) -> Opts = #add_opts{read_info=fun(F) -> file:read_link_info(F) end}, add1(File, Name, NameInArchive, add_opts(Options, Opts));add({read, _File}, _, _, _) -> {error, eacces};add(_, _, _, _) -> {error, einval}.add_opts([dereference|T], Opts) -> add_opts(T, Opts#add_opts{read_info=fun(F) -> file:read_file_info(F) end});add_opts([verbose|T], Opts) -> add_opts(T, Opts#add_opts{verbose=true});add_opts([_|T], Opts) -> add_opts(T, Opts);add_opts([], Opts) -> Opts.%% Creates a tar file Name containing the given files.create(Name, Filenames) -> create(Name, Filenames, []).%% Creates a tar archive Name containing the given files.%% Accepted options: verbose, compressed, cookedcreate(Name, FileList, Options) -> Mode = lists:filter(fun(X) -> (X=:=compressed) or (X=:=cooked) end, Options), case open(Name, [write|Mode]) of {ok, TarFile} -> Add = fun(Nm) -> add(TarFile, Nm, Nm, Options) end, Result = foreach_while_ok(Add, FileList), case {Result, close(TarFile)} of {ok, Res} -> Res; {Res, _} -> Res end; Reason -> Reason end.%% Extracts all files from the tar file Name.extract(Name) -> extract(Name, []).%% Extracts (all) files from the tar file Name.%% Options accepted: keep_old_files, {files, ListOfFilesToExtract}, verbose,%% {cwd, AbsoluteDirectory}extract(Name, Opts) -> case foldl_read(Name, fun extract1/4, [], extract_opts(Opts)) of [] -> ok; Other -> Other end.%% Returns a list of names of the files in the tar file Name.%% Options accepted: verbosetable(Name) -> table(Name, []).%% Returns a list of names of the files in the tar file Name.%% Options accepted: compressed, verbose, cooked.table(Name, Opts) -> foldl_read(Name, fun table1/4, [], table_opts(Opts)).%% Comments for printing the contents of a tape archive,%% meant to be invoked from the shell.t(Name) -> case table(Name) of {ok, List} -> lists:foreach(fun(N) -> ok = io:format("~s\n", [N]) end, List); Error -> Error end.tt(Name) -> case table(Name, [verbose]) of {ok, List} -> lists:foreach(fun print_header/1, List); Error -> Error end.print_header({Name, Type, Size, Mtime, Mode, Uid, Gid}) -> io:format("~s~s ~4w/~-4w ~7w ~s ~s\n", [type_to_string(Type), mode_to_string(Mode), Uid, Gid, Size, time_to_string(Mtime), Name]).type_to_string(regular) -> "-";type_to_string(directory) -> "d";type_to_string(link) -> "l";type_to_string(symlink) -> "s";type_to_string(char) -> "c";type_to_string(block) -> "b";type_to_string(fifo) -> "f";type_to_string(_) -> "?".mode_to_string(Mode) -> mode_to_string(Mode, "xwrxwrxwr", []).mode_to_string(Mode, [C|T], Acc) when Mode band 1 =:= 1 -> mode_to_string(Mode bsr 1, T, [C|Acc]);mode_to_string(Mode, [_|T], Acc) -> mode_to_string(Mode bsr 1, T, [$-|Acc]);mode_to_string(_, [], Acc) -> Acc.time_to_string({{Y, Mon, Day}, {H, Min, _}}) -> io_lib:format("~s ~2w ~s:~s ~w", [month(Mon), Day, two_d(H), two_d(Min), Y]).two_d(N) -> tl(integer_to_list(N + 100)).month(1) -> "Jan";month(2) -> "Feb";month(3) -> "Mar";month(4) -> "Apr";month(5) -> "May";month(6) -> "Jun";month(7) -> "Jul";month(8) -> "Aug";month(9) -> "Sep";month(10) -> "Oct";month(11) -> "Nov";month(12) -> "Dec".%% Converts the short error reason to a descriptive string.format_error(bad_header) -> "Bad directory header";format_error(eof) -> "Unexpected end of file";format_error(symbolic_link_too_long) -> "Symbolic link too long";format_error({Name,Reason}) -> lists:flatten(io_lib:format("~s: ~s", [Name,format_error(Reason)]));format_error(Atom) when is_atom(Atom) -> file:format_error(Atom);format_error(Term) -> lists:flatten(io_lib:format("~p", [Term])).%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Useful definitions (also start of implementation).%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Offset for fields in the tar header.%% Note that these offsets are ZERO-based as in the POSIX standard%% document, while binaries use ONE-base offset. Caveat Programmer.-define(th_name, 0).-define(th_mode, 100).-define(th_uid, 108).-define(th_gid, 116).-define(th_size, 124).-define(th_mtime, 136).-define(th_chksum, 148).-define(th_typeflag, 156).-define(th_linkname, 157).-define(th_magic, 257).-define(th_version, 263).-define(th_prefix, 345).%% Length of these fields.-define(th_name_len, 100).-define(th_mode_len, 8).-define(th_uid_len, 8).-define(th_gid_len, 8).-define(th_size_len, 12).-define(th_mtime_len, 12).-define(th_chksum_len, 8).-define(th_linkname_len, 100).-define(th_magic_len, 6).-define(th_version_len, 2).-define(th_prefix_len, 167).-record(tar_header, {name, % Name of file. mode, % Mode bits. uid, % User id. gid, % Group id. size, % Size of file mtime, % Last modified (seconds since % Jan 1, 1970). chksum, % Checksum of header. typeflag = [], % Type of file. linkname = [], % Name of link. filler = [], prefix}). % Filename prefix.-define(record_size, 512).-define(block_size, (512*20)).%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Adding members to a tar archive.%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%add1(TarFile, Name, NameInArchive, Opts) -> case read_file_and_info(Name, Opts) of {ok, Bin, Info} when Info#file_info.type =:= regular -> Header = create_header(NameInArchive, Info), add1(TarFile, Name, Header, Bin, Opts); {ok, PointsTo, Info} when Info#file_info.type =:= symlink -> if length(PointsTo) > 100 -> {error,{PointsTo,symbolic_link_too_long}}; true -> Info2 = Info#file_info{size=0}, Header = create_header(NameInArchive, Info2, PointsTo), add1(TarFile, Name, Header, list_to_binary([]), Opts) end; {ok, _, Info} when Info#file_info.type =:= directory -> add_directory(TarFile, Name, NameInArchive, Info, Opts); {ok, _, #file_info{type=Type}} -> {error, {bad_file_type, Name, Type}}; {error, Reason} -> {error, {Name, Reason}} end.add1(Tar, Name, Header, Bin, Options) -> add_verbose(Options, "a ~s~n", [Name]), file:write(Tar, [Header, Bin, padding(size(Bin), ?record_size)]).add_directory(TarFile, DirName, NameInArchive, Info, Options) -> case file:list_dir(DirName) of {ok, []} -> add_verbose(Options, "a ~s~n", [DirName]), Header = create_header(NameInArchive, Info), file:write(TarFile, Header); {ok, Files} -> Add = fun (File) -> add1(TarFile, filename:join(DirName, File), filename:join(NameInArchive, File), Options) end, foreach_while_ok(Add, Files); {error, Reason} -> {error, {DirName, Reason}} end. %% Creates a header for file in a tar file.create_header(Name, Info) -> create_header(Name, Info, []).create_header(Name, #file_info {mode=Mode, uid=Uid, gid=Gid, size=Size, mtime=Mtime0, type=Type}, Linkname) -> Mtime = posix_time(erlang:localtime_to_universaltime(Mtime0)), {Prefix,Suffix} = split_filename(Name), H0 = [to_string(Suffix, 100), to_octal(Mode, 8), to_octal(Uid, 8), to_octal(Gid, 8), to_octal(Size, ?th_size_len), to_octal(Mtime, ?th_mtime_len), <<" ">>, file_type(Type), to_string(Linkname, ?th_linkname_len), "ustar",0, "00", zeroes(?th_prefix-?th_version-?th_version_len), to_string(Prefix, ?th_prefix_len)], H = list_to_binary(H0), 512 = size(H), %Assertion. ChksumString = to_octal(checksum(H), 6, [0,$\s]), <<Before:?th_chksum/binary,_:?th_chksum_len/binary,After/binary>> = H, [Before,ChksumString|After].file_type(regular) -> $0;file_type(symlink) -> $2;file_type(directory) -> $5. to_octal(Int, Count) when Count > 1 -> to_octal(Int, Count-1, [0]).to_octal(_, 0, Result) -> Result;to_octal(Int, Count, Result) -> to_octal(Int div 8, Count-1, [Int rem 8 + $0|Result]).to_string(Str0, Count) -> Str = list_to_binary(Str0), case size(Str) of Size when Size < Count -> [Str|zeroes(Count-Size)]; _ -> Str end.%% Pads out end of file.pad_file(File) -> {ok,Position} = file:position(File, {cur,0}), %% There must be at least one empty record at the end of the file. Zeros = zeroes(?block_size - (Position rem ?block_size)), file:write(File, Zeros).split_filename(Name) when length(Name) =< ?th_name_len -> {"", Name};split_filename(Name0) -> split_filename(lists:reverse(filename:split(Name0)), [], [], 0).split_filename([Comp|Rest], Prefix, Suffix, Len) when Len+length(Comp) < ?th_name_len -> split_filename(Rest, Prefix, [Comp|Suffix], Len+length(Comp)+1);split_filename([Comp|Rest], Prefix, Suffix, Len) -> split_filename(Rest, [Comp|Prefix], Suffix, Len+length(Comp)+1);split_filename([], Prefix, Suffix, _) -> {filename:join(Prefix),filename:join(Suffix)}.%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Retrieving files from a tape archive.%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Options used when reading a tar archive.-record(read_opts, {cwd, % Current working directory. keep_old_files = false, % Owerwrite or not. files = all, % Set of files to extract % (or all). open_mode = [], % Open mode options. verbose = false}). % Verbose on/off.extract_opts(List) -> extract_opts(List, default_options()).table_opts(List) -> read_opts(List, default_options()).default_options() -> {ok, Cwd} = file:get_cwd(), #read_opts{cwd=Cwd}.%% Parse options for extract.extract_opts([keep_old_files|Rest], Opts) -> extract_opts(Rest, Opts#read_opts{keep_old_files=true});extract_opts([{cwd, Cwd}|Rest], Opts) -> extract_opts(Rest, Opts#read_opts{cwd=Cwd});extract_opts([{files, Files}|Rest], Opts) -> Set = ordsets:from_list(Files), extract_opts(Rest, Opts#read_opts{files=Set});extract_opts([compressed|Rest], Opts=#read_opts{open_mode=OpenMode}) -> extract_opts(Rest, Opts#read_opts{open_mode=[compressed|OpenMode]});extract_opts([cooked|Rest], Opts=#read_opts{open_mode=OpenMode}) -> extract_opts(Rest, Opts#read_opts{open_mode=[cooked|OpenMode]});extract_opts([verbose|Rest], Opts) -> extract_opts(Rest, Opts#read_opts{verbose=true});extract_opts([Other|Rest], Opts) -> extract_opts(Rest, read_opts([Other], Opts));extract_opts([], Opts) -> Opts.%% Common options for all read operations.read_opts([compressed|Rest], Opts=#read_opts{open_mode=OpenMode}) -> read_opts(Rest, Opts#read_opts{open_mode=[compressed|OpenMode]});read_opts([cooked|Rest], Opts=#read_opts{open_mode=OpenMode}) -> read_opts(Rest, Opts#read_opts{open_mode=[cooked|OpenMode]});read_opts([verbose|Rest], Opts) -> read_opts(Rest, Opts#read_opts{verbose=true});read_opts([_|Rest], Opts) -> read_opts(Rest, Opts);read_opts([], Opts) -> Opts.foldl_read(TarName, Fun, Accu, Opts) -> case open(TarName, [read|Opts#read_opts.open_mode]) of
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?