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 + -
显示快捷键?