You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2016 line
75 KiB

  1. %% Vendored from hex_core v0.7.0, do not edit manually
  2. %% @private
  3. %% Copied from https://github.com/erlang/otp/blob/OTP-20.0.1/lib/stdlib/src/erl_tar.erl
  4. %% with modifications:
  5. %% - Change module name to `r3_hex_erl_tar`
  6. %% - Set tar mtimes to 0 and remove dependency on :os.system_time/1
  7. %% - Preserve modes when building tarball
  8. %% - Do not crash if failing to write tar
  9. %% - Allow setting file_info opts on :r3_hex_erl_tar.add
  10. %% - Add safe_relative_path_links/2 to check directory traversal vulnerability when extracting files,
  11. %% it differs from OTP's current fix (2020-02-04) in that it checks regular files instead of
  12. %% symlink targets. This allows creating symlinks with relative path targets such as `../tmp/log`
  13. %% - Remove ram_file usage (backported from OTP master)
  14. %%
  15. %% %CopyrightBegin%
  16. %%
  17. %% Copyright Ericsson AB 1997-2017. All Rights Reserved.
  18. %%
  19. %% Licensed under the Apache License, Version 2.0 (the "License");
  20. %% you may not use this file except in compliance with the License.
  21. %% You may obtain a copy of the License at
  22. %%
  23. %% http://www.apache.org/licenses/LICENSE-2.0
  24. %%
  25. %% Unless required by applicable law or agreed to in writing, software
  26. %% distributed under the License is distributed on an "AS IS" BASIS,
  27. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  28. %% See the License for the specific language governing permissions and
  29. %% limitations under the License.
  30. %%
  31. %% %CopyrightEnd%
  32. %%
  33. %% This module implements extraction/creation of tar archives.
  34. %% It supports reading most common tar formats, namely V7, STAR,
  35. %% USTAR, GNU, BSD/libarchive, and PAX. It produces archives in USTAR
  36. %% format, unless it must use PAX headers, in which case it produces PAX
  37. %% format.
  38. %%
  39. %% The following references where used:
  40. %% http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5
  41. %% http://www.gnu.org/software/tar/manual/html_node/Standard.html
  42. %% http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
  43. -module(r3_hex_erl_tar).
  44. -export([init/3,
  45. create/2, create/3,
  46. extract/1, extract/2,
  47. table/1, table/2, t/1, tt/1,
  48. open/2, close/1,
  49. add/3, add/4, add/5,
  50. format_error/1]).
  51. -include_lib("kernel/include/file.hrl").
  52. -include_lib("r3_hex_erl_tar.hrl").
  53. %% Converts the short error reason to a descriptive string.
  54. -spec format_error(term()) -> string().
  55. format_error(invalid_tar_checksum) ->
  56. "Checksum failed";
  57. format_error(bad_header) ->
  58. "Unrecognized tar header format";
  59. format_error({bad_header, Reason}) ->
  60. lists:flatten(io_lib:format("Unrecognized tar header format: ~p", [Reason]));
  61. format_error({invalid_header, negative_size}) ->
  62. "Invalid header: negative size";
  63. format_error(invalid_sparse_header_size) ->
  64. "Invalid sparse header: negative size";
  65. format_error(invalid_sparse_map_entry) ->
  66. "Invalid sparse map entry";
  67. format_error({invalid_sparse_map_entry, Reason}) ->
  68. lists:flatten(io_lib:format("Invalid sparse map entry: ~p", [Reason]));
  69. format_error(invalid_end_of_archive) ->
  70. "Invalid end of archive";
  71. format_error(eof) ->
  72. "Unexpected end of file";
  73. format_error(integer_overflow) ->
  74. "Failed to parse numeric: integer overflow";
  75. format_error({misaligned_read, Pos}) ->
  76. lists:flatten(io_lib:format("Read a block which was misaligned: block_size=~p pos=~p",
  77. [?BLOCK_SIZE, Pos]));
  78. format_error(invalid_gnu_1_0_sparsemap) ->
  79. "Invalid GNU sparse map (version 1.0)";
  80. format_error({invalid_gnu_0_1_sparsemap, Format}) ->
  81. lists:flatten(io_lib:format("Invalid GNU sparse map (version ~s)", [Format]));
  82. format_error(unsafe_path) ->
  83. "The path points above the current working directory";
  84. format_error({Name,Reason}) ->
  85. lists:flatten(io_lib:format("~ts: ~ts", [Name,format_error(Reason)]));
  86. format_error(Atom) when is_atom(Atom) ->
  87. file:format_error(Atom);
  88. format_error(Term) ->
  89. lists:flatten(io_lib:format("~tp", [Term])).
  90. %% Initializes a new reader given a custom file handle and I/O wrappers
  91. -spec init(handle(), write | read, file_op()) -> {ok, reader()} | {error, badarg}.
  92. init(Handle, AccessMode, Fun) when is_function(Fun, 2) ->
  93. Reader = #reader{handle=Handle,access=AccessMode,func=Fun},
  94. {ok, Pos, Reader2} = do_position(Reader, {cur, 0}),
  95. {ok, Reader2#reader{pos=Pos}};
  96. init(_Handle, _AccessMode, _Fun) ->
  97. {error, badarg}.
  98. %%%================================================================
  99. %% Extracts all files from the tar file Name.
  100. -spec extract(open_handle()) -> ok | {error, term()}.
  101. extract(Name) ->
  102. extract(Name, []).
  103. %% Extracts (all) files from the tar file Name.
  104. %% Options accepted:
  105. %% - cooked: Opens the tar file without mode `raw`
  106. %% - compressed: Uncompresses the tar file when reading
  107. %% - memory: Returns the tar contents as a list of tuples {Name, Bin}
  108. %% - keep_old_files: Extracted files will not overwrite the destination
  109. %% - {files, ListOfFilesToExtract}: Only extract ListOfFilesToExtract
  110. %% - verbose: Prints verbose information about the extraction,
  111. %% - {cwd, AbsoluteDir}: Sets the current working directory for the extraction
  112. -spec extract(open_handle(), [extract_opt()]) ->
  113. ok
  114. | {ok, [{string(), binary()}]}
  115. | {error, term()}.
  116. extract({binary, Bin}, Opts) when is_list(Opts) ->
  117. do_extract({binary, Bin}, Opts);
  118. extract({file, Fd}, Opts) when is_list(Opts) ->
  119. do_extract({file, Fd}, Opts);
  120. extract(#reader{}=Reader, Opts) when is_list(Opts) ->
  121. do_extract(Reader, Opts);
  122. extract(Name, Opts) when is_list(Name); is_binary(Name), is_list(Opts) ->
  123. do_extract(Name, Opts).
  124. do_extract(Handle, Opts) when is_list(Opts) ->
  125. Opts2 = extract_opts(Opts),
  126. Acc = if Opts2#read_opts.output =:= memory -> []; true -> ok end,
  127. foldl_read(Handle, fun extract1/4, Acc, Opts2).
  128. extract1(eof, Reader, _, Acc) when is_list(Acc) ->
  129. {ok, {ok, lists:reverse(Acc)}, Reader};
  130. extract1(eof, Reader, _, leading_slash) ->
  131. error_logger:info_msg("erl_tar: removed leading '/' from member names\n"),
  132. {ok, ok, Reader};
  133. extract1(eof, Reader, _, Acc) ->
  134. {ok, Acc, Reader};
  135. extract1(#tar_header{name=Name,size=Size}=Header, Reader0, Opts, Acc0) ->
  136. case check_extract(Name, Opts) of
  137. true ->
  138. case do_read(Reader0, Size) of
  139. {ok, Bin, Reader1} ->
  140. Acc = extract2(Header, Bin, Opts, Acc0),
  141. {ok, Acc, Reader1};
  142. {error, _} = Err ->
  143. throw(Err)
  144. end;
  145. false ->
  146. {ok, Acc0, skip_file(Reader0)}
  147. end.
  148. extract2(Header, Bin, Opts, Acc) ->
  149. case write_extracted_element(Header, Bin, Opts) of
  150. ok ->
  151. case Header of
  152. #tar_header{name="/"++_} ->
  153. leading_slash;
  154. #tar_header{} ->
  155. Acc
  156. end;
  157. {ok, NameBin} when is_list(Acc) ->
  158. [NameBin | Acc];
  159. {error, _} = Err ->
  160. throw(Err)
  161. end.
  162. %% Checks if the file Name should be extracted.
  163. check_extract(_, #read_opts{files=all}) ->
  164. true;
  165. check_extract(Name, #read_opts{files=Files}) ->
  166. ordsets:is_element(Name, Files).
  167. %%%================================================================
  168. %% The following table functions produce a list of information about
  169. %% the files contained in the archive.
  170. -type filename() :: string().
  171. -type typeflag() :: regular | link | symlink |
  172. char | block | directory |
  173. fifo | reserved | unknown.
  174. -type mode() :: non_neg_integer().
  175. -type uid() :: non_neg_integer().
  176. -type gid() :: non_neg_integer().
  177. -type tar_entry() :: {filename(),
  178. typeflag(),
  179. non_neg_integer(),
  180. tar_time(),
  181. mode(),
  182. uid(),
  183. gid()}.
  184. %% Returns a list of names of the files in the tar file Name.
  185. -spec table(open_handle()) -> {ok, [string()]} | {error, term()}.
  186. table(Name) ->
  187. table(Name, []).
  188. %% Returns a list of names of the files in the tar file Name.
  189. %% Options accepted: compressed, verbose, cooked.
  190. -spec table(open_handle(), [compressed | verbose | cooked]) ->
  191. {ok, [tar_entry()]} | {error, term()}.
  192. table(Name, Opts) when is_list(Opts) ->
  193. foldl_read(Name, fun table1/4, [], table_opts(Opts)).
  194. table1(eof, Reader, _, Result) ->
  195. {ok, {ok, lists:reverse(Result)}, Reader};
  196. table1(#tar_header{}=Header, Reader, #read_opts{verbose=Verbose}, Result) ->
  197. Attrs = table1_attrs(Header, Verbose),
  198. Reader2 = skip_file(Reader),
  199. {ok, [Attrs|Result], Reader2}.
  200. %% Extracts attributes relevant to table1's output
  201. table1_attrs(#tar_header{typeflag=Typeflag,mode=Mode}=Header, true) ->
  202. Type = typeflag(Typeflag),
  203. Name = Header#tar_header.name,
  204. Mtime = Header#tar_header.mtime,
  205. Uid = Header#tar_header.uid,
  206. Gid = Header#tar_header.gid,
  207. Size = Header#tar_header.size,
  208. {Name, Type, Size, Mtime, Mode, Uid, Gid};
  209. table1_attrs(#tar_header{name=Name}, _Verbose) ->
  210. Name.
  211. typeflag(?TYPE_REGULAR) -> regular;
  212. typeflag(?TYPE_REGULAR_A) -> regular;
  213. typeflag(?TYPE_GNU_SPARSE) -> regular;
  214. typeflag(?TYPE_CONT) -> regular;
  215. typeflag(?TYPE_LINK) -> link;
  216. typeflag(?TYPE_SYMLINK) -> symlink;
  217. typeflag(?TYPE_CHAR) -> char;
  218. typeflag(?TYPE_BLOCK) -> block;
  219. typeflag(?TYPE_DIR) -> directory;
  220. typeflag(?TYPE_FIFO) -> fifo;
  221. typeflag(_) -> unknown.
  222. %%%================================================================
  223. %% Comments for printing the contents of a tape archive,
  224. %% meant to be invoked from the shell.
  225. %% Prints each filename in the archive
  226. -spec t(file:filename()) -> ok | {error, term()}.
  227. t(Name) when is_list(Name); is_binary(Name) ->
  228. case table(Name) of
  229. {ok, List} ->
  230. lists:foreach(fun(N) -> ok = io:format("~ts\n", [N]) end, List);
  231. Error ->
  232. Error
  233. end.
  234. %% Prints verbose information about each file in the archive
  235. -spec tt(open_handle()) -> ok | {error, term()}.
  236. tt(Name) ->
  237. case table(Name, [verbose]) of
  238. {ok, List} ->
  239. lists:foreach(fun print_header/1, List);
  240. Error ->
  241. Error
  242. end.
  243. %% Used by tt/1 to print a tar_entry tuple
  244. -spec print_header(tar_entry()) -> ok.
  245. print_header({Name, Type, Size, Mtime, Mode, Uid, Gid}) ->
  246. io:format("~s~s ~4w/~-4w ~7w ~s ~s\n",
  247. [type_to_string(Type), mode_to_string(Mode),
  248. Uid, Gid, Size, time_to_string(Mtime), Name]).
  249. type_to_string(regular) -> "-";
  250. type_to_string(directory) -> "d";
  251. type_to_string(link) -> "l";
  252. type_to_string(symlink) -> "s";
  253. type_to_string(char) -> "c";
  254. type_to_string(block) -> "b";
  255. type_to_string(fifo) -> "f";
  256. type_to_string(unknown) -> "?".
  257. %% Converts a numeric mode to its human-readable representation
  258. mode_to_string(Mode) ->
  259. mode_to_string(Mode, "xwrxwrxwr", []).
  260. mode_to_string(Mode, [C|T], Acc) when Mode band 1 =:= 1 ->
  261. mode_to_string(Mode bsr 1, T, [C|Acc]);
  262. mode_to_string(Mode, [_|T], Acc) ->
  263. mode_to_string(Mode bsr 1, T, [$-|Acc]);
  264. mode_to_string(_, [], Acc) ->
  265. Acc.
  266. %% Converts a tar_time() (POSIX time) to a readable string
  267. time_to_string(Secs0) ->
  268. Epoch = calendar:datetime_to_gregorian_seconds(?EPOCH),
  269. Secs = Epoch + Secs0,
  270. DateTime0 = calendar:gregorian_seconds_to_datetime(Secs),
  271. DateTime = calendar:universal_time_to_local_time(DateTime0),
  272. {{Y, Mon, Day}, {H, Min, _}} = DateTime,
  273. io_lib:format("~s ~2w ~s:~s ~w", [month(Mon), Day, two_d(H), two_d(Min), Y]).
  274. two_d(N) ->
  275. tl(integer_to_list(N + 100)).
  276. month(1) -> "Jan";
  277. month(2) -> "Feb";
  278. month(3) -> "Mar";
  279. month(4) -> "Apr";
  280. month(5) -> "May";
  281. month(6) -> "Jun";
  282. month(7) -> "Jul";
  283. month(8) -> "Aug";
  284. month(9) -> "Sep";
  285. month(10) -> "Oct";
  286. month(11) -> "Nov";
  287. month(12) -> "Dec".
  288. %%%================================================================
  289. %% The open function with friends is to keep the file and binary api of this module
  290. -type open_handle() :: file:filename()
  291. | {binary, binary()}
  292. | {file, term()}.
  293. -spec open(open_handle(), [write | compressed | cooked]) ->
  294. {ok, reader()} | {error, term()}.
  295. open({binary, Bin}, Mode) when is_binary(Bin) ->
  296. do_open({binary, Bin}, Mode);
  297. open({file, Fd}, Mode) ->
  298. do_open({file, Fd}, Mode);
  299. open(Name, Mode) when is_list(Name); is_binary(Name) ->
  300. do_open(Name, Mode).
  301. do_open(Name, Mode) when is_list(Mode) ->
  302. case open_mode(Mode) of
  303. {ok, Access, Raw, Opts} ->
  304. open1(Name, Access, Raw, Opts);
  305. {error, Reason} ->
  306. {error, {Name, Reason}}
  307. end.
  308. open1({binary,Bin0}, read, _Raw, Opts) when is_binary(Bin0) ->
  309. Bin = case lists:member(compressed, Opts) of
  310. true ->
  311. try
  312. zlib:gunzip(Bin0)
  313. catch
  314. _:_ -> Bin0
  315. end;
  316. false ->
  317. Bin0
  318. end,
  319. case file:open(Bin, [ram,binary,read]) of
  320. {ok,File} ->
  321. {ok, #reader{handle=File,access=read,func=fun file_op/2}};
  322. Error ->
  323. Error
  324. end;
  325. open1({file, Fd}, read, _Raw, _Opts) ->
  326. Reader = #reader{handle=Fd,access=read,func=fun file_op/2},
  327. case do_position(Reader, {cur, 0}) of
  328. {ok, Pos, Reader2} ->
  329. {ok, Reader2#reader{pos=Pos}};
  330. {error, _} = Err ->
  331. Err
  332. end;
  333. open1(Name, Access, Raw, Opts) when is_list(Name) or is_binary(Name) ->
  334. case file:open(Name, Raw ++ [binary, Access|Opts]) of
  335. {ok, File} ->
  336. {ok, #reader{handle=File,access=Access,func=fun file_op/2}};
  337. {error, Reason} ->
  338. {error, {Name, Reason}}
  339. end.
  340. open_mode(Mode) ->
  341. open_mode(Mode, false, [raw], []).
  342. open_mode(read, _, Raw, _) ->
  343. {ok, read, Raw, []};
  344. open_mode(write, _, Raw, _) ->
  345. {ok, write, Raw, []};
  346. open_mode([read|Rest], false, Raw, Opts) ->
  347. open_mode(Rest, read, Raw, Opts);
  348. open_mode([write|Rest], false, Raw, Opts) ->
  349. open_mode(Rest, write, Raw, Opts);
  350. open_mode([compressed|Rest], Access, Raw, Opts) ->
  351. open_mode(Rest, Access, Raw, [compressed|Opts]);
  352. open_mode([cooked|Rest], Access, _Raw, Opts) ->
  353. open_mode(Rest, Access, [], Opts);
  354. open_mode([], Access, Raw, Opts) ->
  355. {ok, Access, Raw, Opts};
  356. open_mode(_, _, _, _) ->
  357. {error, einval}.
  358. file_op(write, {Fd, Data}) ->
  359. file:write(Fd, Data);
  360. file_op(position, {Fd, Pos}) ->
  361. file:position(Fd, Pos);
  362. file_op(read2, {Fd, Size}) ->
  363. file:read(Fd, Size);
  364. file_op(close, Fd) ->
  365. file:close(Fd).
  366. %% Closes a tar archive.
  367. -spec close(reader()) -> ok | {error, term()}.
  368. close(#reader{access=read}=Reader) ->
  369. ok = do_close(Reader);
  370. close(#reader{access=write}=Reader) ->
  371. {ok, Reader2} = pad_file(Reader),
  372. ok = do_close(Reader2),
  373. ok;
  374. close(_) ->
  375. {error, einval}.
  376. pad_file(#reader{pos=Pos}=Reader) ->
  377. %% There must be at least two zero blocks at the end.
  378. PadCurrent = skip_padding(Pos+?BLOCK_SIZE),
  379. Padding = <<0:PadCurrent/unit:8>>,
  380. do_write(Reader, [Padding, ?ZERO_BLOCK, ?ZERO_BLOCK]).
  381. %%%================================================================
  382. %% Creation/modification of tar archives
  383. %% Creates a tar file Name containing the given files.
  384. -spec create(file:filename(), filelist()) -> ok | {error, {string(), term()}}.
  385. create(Name, FileList) when is_list(Name); is_binary(Name) ->
  386. create(Name, FileList, []).
  387. %% Creates a tar archive Name containing the given files.
  388. %% Accepted options: verbose, compressed, cooked
  389. -spec create(file:filename(), filelist(), [create_opt()]) ->
  390. ok | {error, term()} | {error, {string(), term()}}.
  391. create(Name, FileList, Options) when is_list(Name); is_binary(Name) ->
  392. Mode = lists:filter(fun(X) -> (X=:=compressed) or (X=:=cooked)
  393. end, Options),
  394. case open(Name, [write|Mode]) of
  395. {ok, TarFile} ->
  396. do_create(TarFile, FileList, Options);
  397. {error, _} = Err ->
  398. Err
  399. end.
  400. do_create(TarFile, [], _Opts) ->
  401. close(TarFile);
  402. do_create(TarFile, [{NameInArchive, NameOrBin}|Rest], Opts) ->
  403. case add(TarFile, NameOrBin, NameInArchive, Opts) of
  404. ok ->
  405. do_create(TarFile, Rest, Opts);
  406. {error, _} = Err ->
  407. _ = close(TarFile),
  408. Err
  409. end;
  410. do_create(TarFile, [Name|Rest], Opts) ->
  411. case add(TarFile, Name, Name, Opts) of
  412. ok ->
  413. do_create(TarFile, Rest, Opts);
  414. {error, _} = Err ->
  415. _ = close(TarFile),
  416. Err
  417. end.
  418. %% Adds a file to a tape archive.
  419. -type add_type() :: string()
  420. | {string(), string()}
  421. | {string(), binary()}.
  422. -spec add(reader(), add_type(), [add_opt()]) -> ok | {error, term()}.
  423. add(Reader, {NameInArchive, Name}, Opts)
  424. when is_list(NameInArchive), is_list(Name) ->
  425. do_add(Reader, Name, NameInArchive, undefined, Opts);
  426. add(Reader, {NameInArchive, Bin}, Opts)
  427. when is_list(NameInArchive), is_binary(Bin) ->
  428. do_add(Reader, Bin, NameInArchive, undefined, Opts);
  429. add(Reader, {NameInArchive, Bin, Mode}, Opts)
  430. when is_list(NameInArchive), is_binary(Bin), is_integer(Mode) ->
  431. do_add(Reader, Bin, NameInArchive, Mode, Opts);
  432. add(Reader, Name, Opts) when is_list(Name) ->
  433. do_add(Reader, Name, Name, undefined, Opts).
  434. -spec add(reader(), string() | binary(), string(), [add_opt()]) ->
  435. ok | {error, term()}.
  436. add(Reader, NameOrBin, NameInArchive, Options)
  437. when is_list(NameOrBin); is_binary(NameOrBin),
  438. is_list(NameInArchive), is_list(Options) ->
  439. do_add(Reader, NameOrBin, NameInArchive, undefined, Options).
  440. -spec add(reader(), string() | binary(), string(), integer(), [add_opt()]) ->
  441. ok | {error, term()}.
  442. add(Reader, NameOrBin, NameInArchive, Mode, Options)
  443. when is_list(NameOrBin); is_binary(NameOrBin),
  444. is_list(NameInArchive), is_integer(Mode), is_list(Options) ->
  445. do_add(Reader, NameOrBin, NameInArchive, Mode, Options).
  446. do_add(#reader{access=write}=Reader, Name, NameInArchive, Mode, Options)
  447. when is_list(NameInArchive), is_list(Options) ->
  448. RF = fun(F) -> apply_file_info_opts(Options, file:read_link_info(F, [{time, posix}])) end,
  449. Opts = #add_opts{read_info=RF},
  450. add1(Reader, Name, NameInArchive, Mode, add_opts(Options, Options, Opts));
  451. do_add(#reader{access=read},_,_,_,_) ->
  452. {error, eacces};
  453. do_add(Reader,_,_,_,_) ->
  454. {error, {badarg, Reader}}.
  455. add_opts([dereference|T], AllOptions, Opts) ->
  456. RF = fun(F) -> apply_file_info_opts(AllOptions, file:read_file_info(F, [{time, posix}])) end,
  457. add_opts(T, AllOptions, Opts#add_opts{read_info=RF});
  458. add_opts([verbose|T], AllOptions, Opts) ->
  459. add_opts(T, AllOptions, Opts#add_opts{verbose=true});
  460. add_opts([{chunks,N}|T], AllOptions, Opts) ->
  461. add_opts(T, AllOptions, Opts#add_opts{chunk_size=N});
  462. add_opts([{atime,Value}|T], AllOptions, Opts) ->
  463. add_opts(T, AllOptions, Opts#add_opts{atime=Value});
  464. add_opts([{mtime,Value}|T], AllOptions, Opts) ->
  465. add_opts(T, AllOptions, Opts#add_opts{mtime=Value});
  466. add_opts([{ctime,Value}|T], AllOptions, Opts) ->
  467. add_opts(T, AllOptions, Opts#add_opts{ctime=Value});
  468. add_opts([{uid,Value}|T], AllOptions, Opts) ->
  469. add_opts(T, AllOptions, Opts#add_opts{uid=Value});
  470. add_opts([{gid,Value}|T], AllOptions, Opts) ->
  471. add_opts(T, AllOptions, Opts#add_opts{gid=Value});
  472. add_opts([_|T], AllOptions, Opts) ->
  473. add_opts(T, AllOptions, Opts);
  474. add_opts([], _AllOptions, Opts) ->
  475. Opts.
  476. apply_file_info_opts(Opts, {ok, FileInfo}) ->
  477. {ok, do_apply_file_info_opts(Opts, FileInfo)};
  478. apply_file_info_opts(_Opts, Other) ->
  479. Other.
  480. do_apply_file_info_opts([{atime,Value}|T], FileInfo) ->
  481. do_apply_file_info_opts(T, FileInfo#file_info{atime=Value});
  482. do_apply_file_info_opts([{mtime,Value}|T], FileInfo) ->
  483. do_apply_file_info_opts(T, FileInfo#file_info{mtime=Value});
  484. do_apply_file_info_opts([{ctime,Value}|T], FileInfo) ->
  485. do_apply_file_info_opts(T, FileInfo#file_info{ctime=Value});
  486. do_apply_file_info_opts([{uid,Value}|T], FileInfo) ->
  487. do_apply_file_info_opts(T, FileInfo#file_info{uid=Value});
  488. do_apply_file_info_opts([{gid,Value}|T], FileInfo) ->
  489. do_apply_file_info_opts(T, FileInfo#file_info{gid=Value});
  490. do_apply_file_info_opts([_|T], FileInfo) ->
  491. do_apply_file_info_opts(T, FileInfo);
  492. do_apply_file_info_opts([], FileInfo) ->
  493. FileInfo.
  494. add1(#reader{}=Reader, Name, NameInArchive, undefined, #add_opts{read_info=ReadInfo}=Opts)
  495. when is_list(Name) ->
  496. Res = case ReadInfo(Name) of
  497. {error, Reason0} ->
  498. {error, {Name, Reason0}};
  499. {ok, #file_info{type=symlink}=Fi} ->
  500. add_verbose(Opts, "a ~ts~n", [NameInArchive]),
  501. {ok, Linkname} = file:read_link(Name),
  502. Header = fileinfo_to_header(NameInArchive, Fi, Linkname),
  503. add_header(Reader, Header, Opts);
  504. {ok, #file_info{type=regular}=Fi} ->
  505. add_verbose(Opts, "a ~ts~n", [NameInArchive]),
  506. Header = fileinfo_to_header(NameInArchive, Fi, false),
  507. {ok, Reader2} = add_header(Reader, Header, Opts),
  508. FileSize = Header#tar_header.size,
  509. {ok, FileSize, Reader3} = do_copy(Reader2, Name, Opts),
  510. Padding = skip_padding(FileSize),
  511. Pad = <<0:Padding/unit:8>>,
  512. do_write(Reader3, Pad);
  513. {ok, #file_info{type=directory}=Fi} ->
  514. add_directory(Reader, Name, NameInArchive, Fi, Opts);
  515. {ok, #file_info{}=Fi} ->
  516. add_verbose(Opts, "a ~ts~n", [NameInArchive]),
  517. Header = fileinfo_to_header(NameInArchive, Fi, false),
  518. add_header(Reader, Header, Opts)
  519. end,
  520. case Res of
  521. ok -> ok;
  522. {ok, _Reader} -> ok;
  523. {error, _Reason} = Err -> Err
  524. end;
  525. add1(Reader, Bin, NameInArchive, Mode, Opts) when is_binary(Bin) ->
  526. add_verbose(Opts, "a ~ts~n", [NameInArchive]),
  527. Now = 0,
  528. Header = #tar_header{
  529. name = NameInArchive,
  530. size = byte_size(Bin),
  531. typeflag = ?TYPE_REGULAR,
  532. atime = add_opts_time(Opts#add_opts.atime, Now),
  533. mtime = add_opts_time(Opts#add_opts.mtime, Now),
  534. ctime = add_opts_time(Opts#add_opts.ctime, Now),
  535. uid = Opts#add_opts.uid,
  536. gid = Opts#add_opts.gid,
  537. mode = default_mode(Mode, 8#100644)},
  538. {ok, Reader2} = add_header(Reader, Header, Opts),
  539. Padding = skip_padding(byte_size(Bin)),
  540. Data = [Bin, <<0:Padding/unit:8>>],
  541. case do_write(Reader2, Data) of
  542. {ok, _Reader3} -> ok;
  543. {error, Reason} -> {error, {NameInArchive, Reason}}
  544. end.
  545. add_opts_time(undefined, _Now) -> 0;
  546. add_opts_time(Time, _Now) -> Time.
  547. default_mode(undefined, Mode) -> Mode;
  548. default_mode(Mode, _) -> Mode.
  549. add_directory(Reader, DirName, NameInArchive, Info, Opts) ->
  550. case file:list_dir(DirName) of
  551. {ok, []} ->
  552. add_verbose(Opts, "a ~ts~n", [NameInArchive]),
  553. Header = fileinfo_to_header(NameInArchive, Info, false),
  554. add_header(Reader, Header, Opts);
  555. {ok, Files} ->
  556. add_verbose(Opts, "a ~ts~n", [NameInArchive]),
  557. try add_files(Reader, Files, DirName, NameInArchive, Opts) of
  558. ok -> ok;
  559. {error, _} = Err -> Err
  560. catch
  561. throw:{error, {_Name, _Reason}} = Err -> Err;
  562. throw:{error, Reason} -> {error, {DirName, Reason}}
  563. end;
  564. {error, Reason} ->
  565. {error, {DirName, Reason}}
  566. end.
  567. add_files(_Reader, [], _Dir, _DirInArchive, _Opts) ->
  568. ok;
  569. add_files(Reader, [Name|Rest], Dir, DirInArchive, #add_opts{read_info=Info}=Opts) ->
  570. FullName = filename:join(Dir, Name),
  571. NameInArchive = filename:join(DirInArchive, Name),
  572. Res = case Info(FullName) of
  573. {error, Reason} ->
  574. {error, {FullName, Reason}};
  575. {ok, #file_info{type=directory}=Fi} ->
  576. add_directory(Reader, FullName, NameInArchive, Fi, Opts);
  577. {ok, #file_info{type=symlink}=Fi} ->
  578. add_verbose(Opts, "a ~ts~n", [NameInArchive]),
  579. {ok, Linkname} = file:read_link(FullName),
  580. Header = fileinfo_to_header(NameInArchive, Fi, Linkname),
  581. add_header(Reader, Header, Opts);
  582. {ok, #file_info{type=regular}=Fi} ->
  583. add_verbose(Opts, "a ~ts~n", [NameInArchive]),
  584. Header = fileinfo_to_header(NameInArchive, Fi, false),
  585. {ok, Reader2} = add_header(Reader, Header, Opts),
  586. FileSize = Header#tar_header.size,
  587. {ok, FileSize, Reader3} = do_copy(Reader2, FullName, Opts),
  588. Padding = skip_padding(FileSize),
  589. Pad = <<0:Padding/unit:8>>,
  590. do_write(Reader3, Pad);
  591. {ok, #file_info{}=Fi} ->
  592. add_verbose(Opts, "a ~ts~n", [NameInArchive]),
  593. Header = fileinfo_to_header(NameInArchive, Fi, false),
  594. add_header(Reader, Header, Opts)
  595. end,
  596. case Res of
  597. ok -> add_files(Reader, Rest, Dir, DirInArchive, Opts);
  598. {ok, ReaderNext} -> add_files(ReaderNext, Rest, Dir, DirInArchive, Opts);
  599. {error, _} = Err -> Err
  600. end.
  601. format_string(String, Size) when length(String) > Size ->
  602. throw({error, {write_string, field_too_long}});
  603. format_string(String, Size) ->
  604. Ascii = to_ascii(String),
  605. if byte_size(Ascii) < Size ->
  606. [Ascii, 0];
  607. true ->
  608. Ascii
  609. end.
  610. format_octal(Octal) ->
  611. iolist_to_binary(io_lib:fwrite("~.8B", [Octal])).
  612. add_header(#reader{}=Reader, #tar_header{}=Header, Opts) ->
  613. {ok, Iodata} = build_header(Header, Opts),
  614. do_write(Reader, Iodata).
  615. write_to_block(Block, IoData, Start) when is_list(IoData) ->
  616. write_to_block(Block, iolist_to_binary(IoData), Start);
  617. write_to_block(Block, Bin, Start) when is_binary(Bin) ->
  618. Size = byte_size(Bin),
  619. <<Head:Start/unit:8, _:Size/unit:8, Rest/binary>> = Block,
  620. <<Head:Start/unit:8, Bin/binary, Rest/binary>>.
  621. build_header(#tar_header{}=Header, Opts) ->
  622. #tar_header{
  623. name=Name,
  624. mode=Mode,
  625. uid=Uid,
  626. gid=Gid,
  627. size=Size,
  628. typeflag=Type,
  629. linkname=Linkname,
  630. uname=Uname,
  631. gname=Gname,
  632. devmajor=Devmaj,
  633. devminor=Devmin
  634. } = Header,
  635. Mtime = Header#tar_header.mtime,
  636. Block0 = ?ZERO_BLOCK,
  637. {Block1, Pax0} = write_string(Block0, ?V7_NAME, ?V7_NAME_LEN, Name, ?PAX_PATH, #{}),
  638. Block2 = write_octal(Block1, ?V7_MODE, ?V7_MODE_LEN, Mode),
  639. {Block3, Pax1} = write_numeric(Block2, ?V7_UID, ?V7_UID_LEN, Uid, ?PAX_UID, Pax0),
  640. {Block4, Pax2} = write_numeric(Block3, ?V7_GID, ?V7_GID_LEN, Gid, ?PAX_GID, Pax1),
  641. {Block5, Pax3} = write_numeric(Block4, ?V7_SIZE, ?V7_SIZE_LEN, Size, ?PAX_SIZE, Pax2),
  642. {Block6, Pax4} = write_numeric(Block5, ?V7_MTIME, ?V7_MTIME_LEN, Mtime, ?PAX_NONE, Pax3),
  643. {Block7, Pax5} = write_string(Block6, ?V7_TYPE, ?V7_TYPE_LEN, <<Type>>, ?PAX_NONE, Pax4),
  644. {Block8, Pax6} = write_string(Block7, ?V7_LINKNAME, ?V7_LINKNAME_LEN,
  645. Linkname, ?PAX_LINKPATH, Pax5),
  646. {Block9, Pax7} = write_string(Block8, ?USTAR_UNAME, ?USTAR_UNAME_LEN,
  647. Uname, ?PAX_UNAME, Pax6),
  648. {Block10, Pax8} = write_string(Block9, ?USTAR_GNAME, ?USTAR_GNAME_LEN,
  649. Gname, ?PAX_GNAME, Pax7),
  650. {Block11, Pax9} = write_numeric(Block10, ?USTAR_DEVMAJ, ?USTAR_DEVMAJ_LEN,
  651. Devmaj, ?PAX_NONE, Pax8),
  652. {Block12, Pax10} = write_numeric(Block11, ?USTAR_DEVMIN, ?USTAR_DEVMIN_LEN,
  653. Devmin, ?PAX_NONE, Pax9),
  654. {Block13, Pax11} = set_path(Block12, Pax10),
  655. PaxEntry = case maps:size(Pax11) of
  656. 0 -> [];
  657. _ -> build_pax_entry(Header, Pax11, Opts)
  658. end,
  659. Block14 = set_format(Block13, ?FORMAT_USTAR),
  660. Block15 = set_checksum(Block14),
  661. {ok, [PaxEntry, Block15]}.
  662. set_path(Block0, Pax) ->
  663. %% only use ustar header when name is too long
  664. case maps:get(?PAX_PATH, Pax, nil) of
  665. nil ->
  666. {Block0, Pax};
  667. PaxPath ->
  668. case split_ustar_path(PaxPath) of
  669. {ok, UstarName, UstarPrefix} ->
  670. {Block1, _} = write_string(Block0, ?V7_NAME, ?V7_NAME_LEN,
  671. UstarName, ?PAX_NONE, #{}),
  672. {Block2, _} = write_string(Block1, ?USTAR_PREFIX, ?USTAR_PREFIX_LEN,
  673. UstarPrefix, ?PAX_NONE, #{}),
  674. {Block2, maps:remove(?PAX_PATH, Pax)};
  675. false ->
  676. {Block0, Pax}
  677. end
  678. end.
  679. set_format(Block0, Format)
  680. when Format =:= ?FORMAT_USTAR; Format =:= ?FORMAT_PAX ->
  681. Block1 = write_to_block(Block0, ?MAGIC_USTAR, ?USTAR_MAGIC),
  682. write_to_block(Block1, ?VERSION_USTAR, ?USTAR_VERSION);
  683. set_format(_Block, Format) ->
  684. throw({error, {invalid_format, Format}}).
  685. set_checksum(Block) ->
  686. Checksum = compute_checksum(Block),
  687. write_octal(Block, ?V7_CHKSUM, ?V7_CHKSUM_LEN, Checksum).
  688. build_pax_entry(Header, PaxAttrs, Opts) ->
  689. Path = Header#tar_header.name,
  690. Filename = filename:basename(Path),
  691. Dir = filename:dirname(Path),
  692. Path2 = filename:join([Dir, "PaxHeaders.0", Filename]),
  693. AsciiPath = to_ascii(Path2),
  694. Path3 = if byte_size(AsciiPath) > ?V7_NAME_LEN ->
  695. binary_part(AsciiPath, 0, ?V7_NAME_LEN - 1);
  696. true ->
  697. AsciiPath
  698. end,
  699. Keys = maps:keys(PaxAttrs),
  700. SortedKeys = lists:sort(Keys),
  701. PaxFile = build_pax_file(SortedKeys, PaxAttrs),
  702. Size = byte_size(PaxFile),
  703. Padding = (?BLOCK_SIZE -
  704. (byte_size(PaxFile) rem ?BLOCK_SIZE)) rem ?BLOCK_SIZE,
  705. Pad = <<0:Padding/unit:8>>,
  706. PaxHeader = #tar_header{
  707. name=unicode:characters_to_list(Path3),
  708. size=Size,
  709. mtime=Header#tar_header.mtime,
  710. atime=Header#tar_header.atime,
  711. ctime=Header#tar_header.ctime,
  712. typeflag=?TYPE_X_HEADER
  713. },
  714. {ok, PaxHeaderData} = build_header(PaxHeader, Opts),
  715. [PaxHeaderData, PaxFile, Pad].
  716. build_pax_file(Keys, PaxAttrs) ->
  717. build_pax_file(Keys, PaxAttrs, []).
  718. build_pax_file([], _, Acc) ->
  719. unicode:characters_to_binary(Acc);
  720. build_pax_file([K|Rest], Attrs, Acc) ->
  721. V = maps:get(K, Attrs),
  722. Size = sizeof(K) + sizeof(V) + 3,
  723. Size2 = sizeof(Size) + Size,
  724. Key = to_string(K),
  725. Value = to_string(V),
  726. Record = unicode:characters_to_binary(io_lib:format("~B ~ts=~ts\n", [Size2, Key, Value])),
  727. if byte_size(Record) =/= Size2 ->
  728. Size3 = byte_size(Record),
  729. Record2 = io_lib:format("~B ~ts=~ts\n", [Size3, Key, Value]),
  730. build_pax_file(Rest, Attrs, [Acc, Record2]);
  731. true ->
  732. build_pax_file(Rest, Attrs, [Acc, Record])
  733. end.
  734. sizeof(Bin) when is_binary(Bin) ->
  735. byte_size(Bin);
  736. sizeof(List) when is_list(List) ->
  737. length(List);
  738. sizeof(N) when is_integer(N) ->
  739. byte_size(integer_to_binary(N));
  740. sizeof(N) when is_float(N) ->
  741. byte_size(float_to_binary(N)).
  742. to_string(Bin) when is_binary(Bin) ->
  743. unicode:characters_to_list(Bin);
  744. to_string(List) when is_list(List) ->
  745. List;
  746. to_string(N) when is_integer(N) ->
  747. integer_to_list(N);
  748. to_string(N) when is_float(N) ->
  749. float_to_list(N).
  750. split_ustar_path(Path) ->
  751. Len = length(Path),
  752. NotAscii = not is_ascii(Path),
  753. if Len =< ?V7_NAME_LEN; NotAscii ->
  754. false;
  755. true ->
  756. PathBin = binary:list_to_bin(Path),
  757. case binary:split(PathBin, [<<$/>>], [global, trim_all]) of
  758. [Part] when byte_size(Part) >= ?V7_NAME_LEN ->
  759. false;
  760. Parts ->
  761. case lists:last(Parts) of
  762. Name when byte_size(Name) >= ?V7_NAME_LEN ->
  763. false;
  764. Name ->
  765. Parts2 = lists:sublist(Parts, length(Parts) - 1),
  766. join_split_ustar_path(Parts2, {ok, Name, nil})
  767. end
  768. end
  769. end.
  770. join_split_ustar_path([], Acc) ->
  771. Acc;
  772. join_split_ustar_path([Part|_], {ok, _, nil})
  773. when byte_size(Part) > ?USTAR_PREFIX_LEN ->
  774. false;
  775. join_split_ustar_path([Part|_], {ok, _Name, Acc})
  776. when (byte_size(Part)+byte_size(Acc)) > ?USTAR_PREFIX_LEN ->
  777. false;
  778. join_split_ustar_path([Part|Rest], {ok, Name, nil}) ->
  779. join_split_ustar_path(Rest, {ok, Name, Part});
  780. join_split_ustar_path([Part|Rest], {ok, Name, Acc}) ->
  781. join_split_ustar_path(Rest, {ok, Name, <<Acc/binary,$/,Part/binary>>}).
  782. write_octal(Block, Pos, Size, X) ->
  783. Octal = zero_pad(format_octal(X), Size-1),
  784. if byte_size(Octal) < Size ->
  785. write_to_block(Block, Octal, Pos);
  786. true ->
  787. throw({error, {write_failed, octal_field_too_long}})
  788. end.
  789. write_string(Block, Pos, Size, Str, PaxAttr, Pax0) ->
  790. NotAscii = not is_ascii(Str),
  791. if PaxAttr =/= ?PAX_NONE andalso (length(Str) > Size orelse NotAscii) ->
  792. Pax1 = maps:put(PaxAttr, Str, Pax0),
  793. {Block, Pax1};
  794. true ->
  795. Formatted = format_string(Str, Size),
  796. {write_to_block(Block, Formatted, Pos), Pax0}
  797. end.
  798. write_numeric(Block, Pos, Size, X, PaxAttr, Pax0) ->
  799. %% attempt octal
  800. Octal = zero_pad(format_octal(X), Size-1),
  801. if byte_size(Octal) < Size ->
  802. {write_to_block(Block, [Octal, 0], Pos), Pax0};
  803. PaxAttr =/= ?PAX_NONE ->
  804. Pax1 = maps:put(PaxAttr, X, Pax0),
  805. {Block, Pax1};
  806. true ->
  807. throw({error, {write_failed, numeric_field_too_long}})
  808. end.
  809. zero_pad(Str, Size) when byte_size(Str) >= Size ->
  810. Str;
  811. zero_pad(Str, Size) ->
  812. Padding = Size - byte_size(Str),
  813. Pad = binary:copy(<<$0>>, Padding),
  814. <<Pad/binary, Str/binary>>.
  815. %%%================================================================
  816. %% Functions for creating or modifying tar archives
  817. read_block(Reader) ->
  818. case do_read(Reader, ?BLOCK_SIZE) of
  819. eof ->
  820. throw({error, eof});
  821. %% Two zero blocks mark the end of the archive
  822. {ok, ?ZERO_BLOCK, Reader1} ->
  823. case do_read(Reader1, ?BLOCK_SIZE) of
  824. eof ->
  825. % This is technically a malformed end-of-archive marker,
  826. % as two ZERO_BLOCKs are expected as the marker,
  827. % but if we've already made it this far, we should just ignore it
  828. eof;
  829. {ok, ?ZERO_BLOCK, _Reader2} ->
  830. eof;
  831. {ok, _Block, _Reader2} ->
  832. throw({error, invalid_end_of_archive});
  833. {error,_} = Err ->
  834. throw(Err)
  835. end;
  836. {ok, Block, Reader1} when is_binary(Block) ->
  837. {ok, Block, Reader1};
  838. {error, _} = Err ->
  839. throw(Err)
  840. end.
  841. get_header(#reader{}=Reader) ->
  842. case read_block(Reader) of
  843. eof ->
  844. eof;
  845. {ok, Block, Reader1} ->
  846. convert_header(Block, Reader1)
  847. end.
  848. %% Converts the tar header to a record.
  849. to_v7(Bin) when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE ->
  850. #header_v7{
  851. name=binary_part(Bin, ?V7_NAME, ?V7_NAME_LEN),
  852. mode=binary_part(Bin, ?V7_MODE, ?V7_MODE_LEN),
  853. uid=binary_part(Bin, ?V7_UID, ?V7_UID_LEN),
  854. gid=binary_part(Bin, ?V7_GID, ?V7_GID_LEN),
  855. size=binary_part(Bin, ?V7_SIZE, ?V7_SIZE_LEN),
  856. mtime=binary_part(Bin, ?V7_MTIME, ?V7_MTIME_LEN),
  857. checksum=binary_part(Bin, ?V7_CHKSUM, ?V7_CHKSUM_LEN),
  858. typeflag=binary:at(Bin, ?V7_TYPE),
  859. linkname=binary_part(Bin, ?V7_LINKNAME, ?V7_LINKNAME_LEN)
  860. };
  861. to_v7(_) ->
  862. {error, header_block_too_small}.
  863. to_gnu(#header_v7{}=V7, Bin)
  864. when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE ->
  865. #header_gnu{
  866. header_v7=V7,
  867. magic=binary_part(Bin, ?GNU_MAGIC, ?GNU_MAGIC_LEN),
  868. version=binary_part(Bin, ?GNU_VERSION, ?GNU_VERSION_LEN),
  869. uname=binary_part(Bin, 265, 32),
  870. gname=binary_part(Bin, 297, 32),
  871. devmajor=binary_part(Bin, 329, 8),
  872. devminor=binary_part(Bin, 337, 8),
  873. atime=binary_part(Bin, 345, 12),
  874. ctime=binary_part(Bin, 357, 12),
  875. sparse=to_sparse_array(binary_part(Bin, 386, 24*4+1)),
  876. real_size=binary_part(Bin, 483, 12)
  877. }.
  878. to_star(#header_v7{}=V7, Bin)
  879. when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE ->
  880. #header_star{
  881. header_v7=V7,
  882. magic=binary_part(Bin, ?USTAR_MAGIC, ?USTAR_MAGIC_LEN),
  883. version=binary_part(Bin, ?USTAR_VERSION, ?USTAR_VERSION_LEN),
  884. uname=binary_part(Bin, ?USTAR_UNAME, ?USTAR_UNAME_LEN),
  885. gname=binary_part(Bin, ?USTAR_GNAME, ?USTAR_GNAME_LEN),
  886. devmajor=binary_part(Bin, ?USTAR_DEVMAJ, ?USTAR_DEVMAJ_LEN),
  887. devminor=binary_part(Bin, ?USTAR_DEVMIN, ?USTAR_DEVMIN_LEN),
  888. prefix=binary_part(Bin, 345, 131),
  889. atime=binary_part(Bin, 476, 12),
  890. ctime=binary_part(Bin, 488, 12),
  891. trailer=binary_part(Bin, ?STAR_TRAILER, ?STAR_TRAILER_LEN)
  892. }.
  893. to_ustar(#header_v7{}=V7, Bin)
  894. when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE ->
  895. #header_ustar{
  896. header_v7=V7,
  897. magic=binary_part(Bin, ?USTAR_MAGIC, ?USTAR_MAGIC_LEN),
  898. version=binary_part(Bin, ?USTAR_VERSION, ?USTAR_VERSION_LEN),
  899. uname=binary_part(Bin, ?USTAR_UNAME, ?USTAR_UNAME_LEN),
  900. gname=binary_part(Bin, ?USTAR_GNAME, ?USTAR_GNAME_LEN),
  901. devmajor=binary_part(Bin, ?USTAR_DEVMAJ, ?USTAR_DEVMAJ_LEN),
  902. devminor=binary_part(Bin, ?USTAR_DEVMIN, ?USTAR_DEVMIN_LEN),
  903. prefix=binary_part(Bin, 345, 155)
  904. }.
  905. to_sparse_array(Bin) when is_binary(Bin) ->
  906. MaxEntries = byte_size(Bin) div 24,
  907. IsExtended = 1 =:= binary:at(Bin, 24*MaxEntries),
  908. Entries = parse_sparse_entries(Bin, MaxEntries-1, []),
  909. #sparse_array{
  910. entries=Entries,
  911. max_entries=MaxEntries,
  912. is_extended=IsExtended
  913. }.
  914. parse_sparse_entries(<<>>, _, Acc) ->
  915. Acc;
  916. parse_sparse_entries(_, -1, Acc) ->
  917. Acc;
  918. parse_sparse_entries(Bin, N, Acc) ->
  919. case to_sparse_entry(binary_part(Bin, N*24, 24)) of
  920. nil ->
  921. parse_sparse_entries(Bin, N-1, Acc);
  922. Entry = #sparse_entry{} ->
  923. parse_sparse_entries(Bin, N-1, [Entry|Acc])
  924. end.
  925. -define(EMPTY_ENTRY, <<0,0,0,0,0,0,0,0,0,0,0,0>>).
  926. to_sparse_entry(Bin) when is_binary(Bin), byte_size(Bin) =:= 24 ->
  927. OffsetBin = binary_part(Bin, 0, 12),
  928. NumBytesBin = binary_part(Bin, 12, 12),
  929. case {OffsetBin, NumBytesBin} of
  930. {?EMPTY_ENTRY, ?EMPTY_ENTRY} ->
  931. nil;
  932. _ ->
  933. #sparse_entry{
  934. offset=parse_numeric(OffsetBin),
  935. num_bytes=parse_numeric(NumBytesBin)}
  936. end.
  937. -spec get_format(binary()) -> {ok, pos_integer(), header_v7()}
  938. | ?FORMAT_UNKNOWN
  939. | {error, term()}.
  940. get_format(Bin) when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE ->
  941. do_get_format(to_v7(Bin), Bin).
  942. do_get_format({error, _} = Err, _Bin) ->
  943. Err;
  944. do_get_format(#header_v7{}=V7, Bin)
  945. when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE ->
  946. Checksum = parse_octal(V7#header_v7.checksum),
  947. Chk1 = compute_checksum(Bin),
  948. Chk2 = compute_signed_checksum(Bin),
  949. if Checksum =/= Chk1 andalso Checksum =/= Chk2 ->
  950. ?FORMAT_UNKNOWN;
  951. true ->
  952. %% guess magic
  953. Ustar = to_ustar(V7, Bin),
  954. Star = to_star(V7, Bin),
  955. Magic = Ustar#header_ustar.magic,
  956. Version = Ustar#header_ustar.version,
  957. Trailer = Star#header_star.trailer,
  958. Format = if
  959. Magic =:= ?MAGIC_USTAR, Trailer =:= ?TRAILER_STAR ->
  960. ?FORMAT_STAR;
  961. Magic =:= ?MAGIC_USTAR ->
  962. ?FORMAT_USTAR;
  963. Magic =:= ?MAGIC_GNU, Version =:= ?VERSION_GNU ->
  964. ?FORMAT_GNU;
  965. true ->
  966. ?FORMAT_V7
  967. end,
  968. {ok, Format, V7}
  969. end.
  970. unpack_format(Format, #header_v7{}=V7, Bin, Reader)
  971. when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE ->
  972. Mtime = parse_numeric(V7#header_v7.mtime),
  973. Header0 = #tar_header{
  974. name=parse_string(V7#header_v7.name),
  975. mode=parse_numeric(V7#header_v7.mode),
  976. uid=parse_numeric(V7#header_v7.uid),
  977. gid=parse_numeric(V7#header_v7.gid),
  978. size=parse_numeric(V7#header_v7.size),
  979. mtime=Mtime,
  980. atime=Mtime,
  981. ctime=Mtime,
  982. typeflag=V7#header_v7.typeflag,
  983. linkname=parse_string(V7#header_v7.linkname)
  984. },
  985. Typeflag = Header0#tar_header.typeflag,
  986. Header1 = if Format > ?FORMAT_V7 ->
  987. unpack_modern(Format, V7, Bin, Header0);
  988. true ->
  989. Name = Header0#tar_header.name,
  990. Header0#tar_header{name=safe_join_path("", Name)}
  991. end,
  992. HeaderOnly = is_header_only_type(Typeflag),
  993. Header2 = if HeaderOnly ->
  994. Header1#tar_header{size=0};
  995. true ->
  996. Header1
  997. end,
  998. if Typeflag =:= ?TYPE_GNU_SPARSE ->
  999. Gnu = to_gnu(V7, Bin),
  1000. RealSize = parse_numeric(Gnu#header_gnu.real_size),
  1001. {Sparsemap, Reader2} = parse_sparse_map(Gnu, Reader),
  1002. Header3 = Header2#tar_header{size=RealSize},
  1003. {Header3, new_sparse_file_reader(Reader2, Sparsemap, RealSize)};
  1004. true ->
  1005. FileReader = #reg_file_reader{
  1006. handle=Reader,
  1007. num_bytes=Header2#tar_header.size,
  1008. size=Header2#tar_header.size,
  1009. pos = 0
  1010. },
  1011. {Header2, FileReader}
  1012. end.
  1013. unpack_modern(Format, #header_v7{}=V7, Bin, #tar_header{}=Header0)
  1014. when is_binary(Bin) ->
  1015. Typeflag = Header0#tar_header.typeflag,
  1016. Ustar = to_ustar(V7, Bin),
  1017. H0 = Header0#tar_header{
  1018. uname=parse_string(Ustar#header_ustar.uname),
  1019. gname=parse_string(Ustar#header_ustar.gname)},
  1020. H1 = if Typeflag =:= ?TYPE_CHAR
  1021. orelse Typeflag =:= ?TYPE_BLOCK ->
  1022. Ma = parse_numeric(Ustar#header_ustar.devmajor),
  1023. Mi = parse_numeric(Ustar#header_ustar.devminor),
  1024. H0#tar_header{
  1025. devmajor=Ma,
  1026. devminor=Mi
  1027. };
  1028. true ->
  1029. H0
  1030. end,
  1031. {Prefix, H2} = case Format of
  1032. ?FORMAT_USTAR ->
  1033. {parse_string(Ustar#header_ustar.prefix), H1};
  1034. ?FORMAT_STAR ->
  1035. Star = to_star(V7, Bin),
  1036. Prefix0 = parse_string(Star#header_star.prefix),
  1037. Atime0 = Star#header_star.atime,
  1038. Atime = parse_numeric(Atime0),
  1039. Ctime0 = Star#header_star.ctime,
  1040. Ctime = parse_numeric(Ctime0),
  1041. {Prefix0, H1#tar_header{
  1042. atime=Atime,
  1043. ctime=Ctime
  1044. }};
  1045. _ ->
  1046. {"", H1}
  1047. end,
  1048. Name = H2#tar_header.name,
  1049. H2#tar_header{name=safe_join_path(Prefix, Name)}.
  1050. safe_join_path([], Name) ->
  1051. filename:join([Name]);
  1052. safe_join_path(Prefix, []) ->
  1053. filename:join([Prefix]);
  1054. safe_join_path(Prefix, Name) ->
  1055. filename:join(Prefix, Name).
  1056. new_sparse_file_reader(Reader, Sparsemap, RealSize) ->
  1057. true = validate_sparse_entries(Sparsemap, RealSize),
  1058. #sparse_file_reader{
  1059. handle = Reader,
  1060. num_bytes = RealSize,
  1061. pos = 0,
  1062. size = RealSize,
  1063. sparse_map = Sparsemap}.
  1064. validate_sparse_entries(Entries, RealSize) ->
  1065. validate_sparse_entries(Entries, RealSize, 0, 0).
  1066. validate_sparse_entries([], _RealSize, _I, _LastOffset) ->
  1067. true;
  1068. validate_sparse_entries([#sparse_entry{}=Entry|Rest], RealSize, I, LastOffset) ->
  1069. Offset = Entry#sparse_entry.offset,
  1070. NumBytes = Entry#sparse_entry.num_bytes,
  1071. if
  1072. Offset > ?MAX_INT64-NumBytes ->
  1073. throw({error, {invalid_sparse_map_entry, offset_too_large}});
  1074. Offset+NumBytes > RealSize ->
  1075. throw({error, {invalid_sparse_map_entry, offset_too_large}});
  1076. I > 0 andalso LastOffset > Offset ->
  1077. throw({error, {invalid_sparse_map_entry, overlapping_offsets}});
  1078. true ->
  1079. ok
  1080. end,
  1081. validate_sparse_entries(Rest, RealSize, I+1, Offset+NumBytes).
  1082. -spec parse_sparse_map(header_gnu(), reader_type()) ->
  1083. {[sparse_entry()], reader_type()}.
  1084. parse_sparse_map(#header_gnu{sparse=Sparse}, Reader)
  1085. when Sparse#sparse_array.is_extended ->
  1086. parse_sparse_map(Sparse, Reader, []);
  1087. parse_sparse_map(#header_gnu{sparse=Sparse}, Reader) ->
  1088. {Sparse#sparse_array.entries, Reader}.
  1089. parse_sparse_map(#sparse_array{is_extended=true,entries=Entries}, Reader, Acc) ->
  1090. case read_block(Reader) of
  1091. eof ->
  1092. throw({error, eof});
  1093. {ok, Block, Reader2} ->
  1094. Sparse2 = to_sparse_array(Block),
  1095. parse_sparse_map(Sparse2, Reader2, Entries++Acc)
  1096. end;
  1097. parse_sparse_map(#sparse_array{entries=Entries}, Reader, Acc) ->
  1098. Sorted = lists:sort(fun (#sparse_entry{offset=A},#sparse_entry{offset=B}) ->
  1099. A =< B
  1100. end, Entries++Acc),
  1101. {Sorted, Reader}.
  1102. %% Defined by taking the sum of the unsigned byte values of the
  1103. %% entire header record, treating the checksum bytes to as ASCII spaces
  1104. compute_checksum(<<H1:?V7_CHKSUM/binary,
  1105. H2:?V7_CHKSUM_LEN/binary,
  1106. Rest:(?BLOCK_SIZE - ?V7_CHKSUM - ?V7_CHKSUM_LEN)/binary,
  1107. _/binary>>) ->
  1108. C0 = checksum(H1) + (byte_size(H2) * $\s),
  1109. C1 = checksum(Rest),
  1110. C0 + C1.
  1111. compute_signed_checksum(<<H1:?V7_CHKSUM/binary,
  1112. H2:?V7_CHKSUM_LEN/binary,
  1113. Rest:(?BLOCK_SIZE - ?V7_CHKSUM - ?V7_CHKSUM_LEN)/binary,
  1114. _/binary>>) ->
  1115. C0 = signed_checksum(H1) + (byte_size(H2) * $\s),
  1116. C1 = signed_checksum(Rest),
  1117. C0 + C1.
  1118. %% Returns the checksum of a binary.
  1119. checksum(Bin) -> checksum(Bin, 0).
  1120. checksum(<<A/unsigned,Rest/binary>>, Sum) ->
  1121. checksum(Rest, Sum+A);
  1122. checksum(<<>>, Sum) -> Sum.
  1123. signed_checksum(Bin) -> signed_checksum(Bin, 0).
  1124. signed_checksum(<<A/signed,Rest/binary>>, Sum) ->
  1125. signed_checksum(Rest, Sum+A);
  1126. signed_checksum(<<>>, Sum) -> Sum.
  1127. -spec parse_numeric(binary()) -> non_neg_integer().
  1128. parse_numeric(<<>>) ->
  1129. 0;
  1130. parse_numeric(<<First, _/binary>> = Bin) ->
  1131. %% check for base-256 format first
  1132. %% if the bit is set, then all following bits constitute a two's
  1133. %% complement encoded number in big-endian byte order
  1134. if
  1135. First band 16#80 =/= 0 ->
  1136. %% Handling negative numbers relies on the following identity:
  1137. %% -a-1 == ^a
  1138. %% If the number is negative, we use an inversion mask to invert
  1139. %% the data bytes and treat the value as an unsigned number
  1140. Inv = if First band 16#40 =/= 0 -> 16#00; true -> 16#FF end,
  1141. Bytes = binary:bin_to_list(Bin),
  1142. Reducer = fun (C, {I, X}) ->
  1143. C1 = C bxor Inv,
  1144. C2 = if I =:= 0 -> C1 band 16#7F; true -> C1 end,
  1145. if (X bsr 56) > 0 ->
  1146. throw({error,integer_overflow});
  1147. true ->
  1148. {I+1, (X bsl 8) bor C2}
  1149. end
  1150. end,
  1151. {_, N} = lists:foldl(Reducer, {0,0}, Bytes),
  1152. if (N bsr 63) > 0 ->
  1153. throw({error, integer_overflow});
  1154. true ->
  1155. if Inv =:= 16#FF ->
  1156. -1 bxor N;
  1157. true ->
  1158. N
  1159. end
  1160. end;
  1161. true ->
  1162. %% normal case is an octal number
  1163. parse_octal(Bin)
  1164. end.
  1165. parse_octal(Bin) when is_binary(Bin) ->
  1166. %% skip leading/trailing zero bytes and spaces
  1167. do_parse_octal(Bin, <<>>).
  1168. do_parse_octal(<<>>, <<>>) ->
  1169. 0;
  1170. do_parse_octal(<<>>, Acc) ->
  1171. case io_lib:fread("~8u", binary:bin_to_list(Acc)) of
  1172. {error, _} -> throw({error, invalid_tar_checksum});
  1173. {ok, [Octal], []} -> Octal;
  1174. {ok, _, _} -> throw({error, invalid_tar_checksum})
  1175. end;
  1176. do_parse_octal(<<$\s,Rest/binary>>, Acc) ->
  1177. do_parse_octal(Rest, Acc);
  1178. do_parse_octal(<<0, Rest/binary>>, Acc) ->
  1179. do_parse_octal(Rest, Acc);
  1180. do_parse_octal(<<C, Rest/binary>>, Acc) ->
  1181. do_parse_octal(Rest, <<Acc/binary, C>>).
  1182. parse_string(Bin) when is_binary(Bin) ->
  1183. do_parse_string(Bin, <<>>).
  1184. do_parse_string(<<>>, Acc) ->
  1185. case unicode:characters_to_list(Acc) of
  1186. Str when is_list(Str) ->
  1187. Str;
  1188. {incomplete, _Str, _Rest} ->
  1189. binary:bin_to_list(Acc);
  1190. {error, _Str, _Rest} ->
  1191. throw({error, {bad_header, invalid_string}})
  1192. end;
  1193. do_parse_string(<<0, _/binary>>, Acc) ->
  1194. do_parse_string(<<>>, Acc);
  1195. do_parse_string(<<C, Rest/binary>>, Acc) ->
  1196. do_parse_string(Rest, <<Acc/binary, C>>).
  1197. convert_header(Bin, #reader{pos=Pos}=Reader)
  1198. when byte_size(Bin) =:= ?BLOCK_SIZE, (Pos rem ?BLOCK_SIZE) =:= 0 ->
  1199. case get_format(Bin) of
  1200. ?FORMAT_UNKNOWN ->
  1201. throw({error, bad_header});
  1202. {ok, Format, V7} ->
  1203. unpack_format(Format, V7, Bin, Reader);
  1204. {error, Reason} ->
  1205. throw({error, {bad_header, Reason}})
  1206. end;
  1207. convert_header(Bin, #reader{pos=Pos}) when byte_size(Bin) =:= ?BLOCK_SIZE ->
  1208. throw({error, misaligned_read, Pos});
  1209. convert_header(Bin, _Reader) when byte_size(Bin) =:= 0 ->
  1210. eof;
  1211. convert_header(_Bin, _Reader) ->
  1212. throw({error, eof}).
  1213. %% Creates a partially-populated header record based
  1214. %% on the provided file_info record. If the file is
  1215. %% a symlink, then `link` is used as the link target.
  1216. %% If the file is a directory, a slash is appended to the name.
  1217. fileinfo_to_header(Name, #file_info{}=Fi, Link) when is_list(Name) ->
  1218. BaseHeader = #tar_header{name=Name,
  1219. mtime=0,
  1220. atime=0,
  1221. ctime=0,
  1222. mode=Fi#file_info.mode,
  1223. typeflag=?TYPE_REGULAR},
  1224. do_fileinfo_to_header(BaseHeader, Fi, Link).
  1225. do_fileinfo_to_header(Header, #file_info{size=Size,type=regular}, _Link) ->
  1226. Header#tar_header{size=Size,typeflag=?TYPE_REGULAR};
  1227. do_fileinfo_to_header(#tar_header{name=Name}=Header,
  1228. #file_info{type=directory}, _Link) ->
  1229. Header#tar_header{name=Name++"/",typeflag=?TYPE_DIR};
  1230. do_fileinfo_to_header(Header, #file_info{type=symlink}, Link) ->
  1231. Header#tar_header{typeflag=?TYPE_SYMLINK,linkname=Link};
  1232. do_fileinfo_to_header(Header, #file_info{type=device,mode=Mode}=Fi, _Link)
  1233. when (Mode band ?S_IFMT) =:= ?S_IFCHR ->
  1234. Header#tar_header{typeflag=?TYPE_CHAR,
  1235. devmajor=Fi#file_info.major_device,
  1236. devminor=Fi#file_info.minor_device};
  1237. do_fileinfo_to_header(Header, #file_info{type=device,mode=Mode}=Fi, _Link)
  1238. when (Mode band ?S_IFMT) =:= ?S_IFBLK ->
  1239. Header#tar_header{typeflag=?TYPE_BLOCK,
  1240. devmajor=Fi#file_info.major_device,
  1241. devminor=Fi#file_info.minor_device};
  1242. do_fileinfo_to_header(Header, #file_info{type=other,mode=Mode}, _Link)
  1243. when (Mode band ?S_IFMT) =:= ?S_FIFO ->
  1244. Header#tar_header{typeflag=?TYPE_FIFO};
  1245. do_fileinfo_to_header(Header, Fi, _Link) ->
  1246. {error, {invalid_file_type, Header#tar_header.name, Fi}}.
  1247. is_ascii(Str) when is_list(Str) ->
  1248. not lists:any(fun (Char) -> Char >= 16#80 end, Str);
  1249. is_ascii(Bin) when is_binary(Bin) ->
  1250. is_ascii1(Bin).
  1251. is_ascii1(<<>>) ->
  1252. true;
  1253. is_ascii1(<<C,_Rest/binary>>) when C >= 16#80 ->
  1254. false;
  1255. is_ascii1(<<_, Rest/binary>>) ->
  1256. is_ascii1(Rest).
  1257. to_ascii(Str) when is_list(Str) ->
  1258. case is_ascii(Str) of
  1259. true ->
  1260. unicode:characters_to_binary(Str);
  1261. false ->
  1262. Chars = lists:filter(fun (Char) -> Char < 16#80 end, Str),
  1263. unicode:characters_to_binary(Chars)
  1264. end;
  1265. to_ascii(Bin) when is_binary(Bin) ->
  1266. to_ascii(Bin, <<>>).
  1267. to_ascii(<<>>, Acc) ->
  1268. Acc;
  1269. to_ascii(<<C, Rest/binary>>, Acc) when C < 16#80 ->
  1270. to_ascii(Rest, <<Acc/binary,C>>);
  1271. to_ascii(<<_, Rest/binary>>, Acc) ->
  1272. to_ascii(Rest, Acc).
  1273. is_header_only_type(?TYPE_SYMLINK) -> true;
  1274. is_header_only_type(?TYPE_LINK) -> true;
  1275. is_header_only_type(?TYPE_DIR) -> true;
  1276. is_header_only_type(_) -> false.
  1277. foldl_read(#reader{access=read}=Reader, Fun, Accu, #read_opts{}=Opts)
  1278. when is_function(Fun,4) ->
  1279. case foldl_read0(Reader, Fun, Accu, Opts) of
  1280. {ok, Result, _Reader2} ->
  1281. Result;
  1282. {error, _} = Err ->
  1283. Err
  1284. end;
  1285. foldl_read(#reader{access=Access}, _Fun, _Accu, _Opts) ->
  1286. {error, {read_mode_expected, Access}};
  1287. foldl_read(TarName, Fun, Accu, #read_opts{}=Opts)
  1288. when is_function(Fun,4) ->
  1289. try open(TarName, [read|Opts#read_opts.open_mode]) of
  1290. {ok, #reader{access=read}=Reader} ->
  1291. try
  1292. foldl_read(Reader, Fun, Accu, Opts)
  1293. after
  1294. _ = close(Reader)
  1295. end;
  1296. {error, _} = Err ->
  1297. Err
  1298. catch
  1299. throw:Err ->
  1300. Err
  1301. end.
  1302. foldl_read0(Reader, Fun, Accu, Opts) ->
  1303. try foldl_read1(Fun, Accu, Reader, Opts, #{}) of
  1304. {ok,_,_} = Ok ->
  1305. Ok
  1306. catch
  1307. throw:{error, {Reason, Format, Args}} ->
  1308. read_verbose(Opts, Format, Args),
  1309. {error, Reason};
  1310. throw:Err ->
  1311. Err
  1312. end.
  1313. foldl_read1(Fun, Accu0, Reader0, Opts, ExtraHeaders) ->
  1314. {ok, Reader1} = skip_unread(Reader0),
  1315. case get_header(Reader1) of
  1316. eof ->
  1317. Fun(eof, Reader1, Opts, Accu0);
  1318. {Header, Reader2} ->
  1319. case Header#tar_header.typeflag of
  1320. ?TYPE_X_HEADER ->
  1321. {ExtraHeaders2, Reader3} = parse_pax(Reader2),
  1322. ExtraHeaders3 = maps:merge(ExtraHeaders, ExtraHeaders2),
  1323. foldl_read1(Fun, Accu0, Reader3, Opts, ExtraHeaders3);
  1324. ?TYPE_GNU_LONGNAME ->
  1325. {RealName, Reader3} = get_real_name(Reader2),
  1326. ExtraHeaders2 = maps:put(?PAX_PATH,
  1327. parse_string(RealName), ExtraHeaders),
  1328. foldl_read1(Fun, Accu0, Reader3, Opts, ExtraHeaders2);
  1329. ?TYPE_GNU_LONGLINK ->
  1330. {RealName, Reader3} = get_real_name(Reader2),
  1331. ExtraHeaders2 = maps:put(?PAX_LINKPATH,
  1332. parse_string(RealName), ExtraHeaders),
  1333. foldl_read1(Fun, Accu0, Reader3, Opts, ExtraHeaders2);
  1334. _ ->
  1335. Header1 = merge_pax(Header, ExtraHeaders),
  1336. {ok, NewAccu, Reader3} = Fun(Header1, Reader2, Opts, Accu0),
  1337. foldl_read1(Fun, NewAccu, Reader3, Opts, #{})
  1338. end
  1339. end.
  1340. %% Applies all known PAX attributes to the current tar header
  1341. -spec merge_pax(tar_header(), #{binary() => binary()}) -> tar_header().
  1342. merge_pax(Header, ExtraHeaders) when is_map(ExtraHeaders) ->
  1343. do_merge_pax(Header, maps:to_list(ExtraHeaders)).
  1344. do_merge_pax(Header, []) ->
  1345. Header;
  1346. do_merge_pax(Header, [{?PAX_PATH, Path}|Rest]) ->
  1347. do_merge_pax(Header#tar_header{name=unicode:characters_to_list(Path)}, Rest);
  1348. do_merge_pax(Header, [{?PAX_LINKPATH, LinkPath}|Rest]) ->
  1349. do_merge_pax(Header#tar_header{linkname=unicode:characters_to_list(LinkPath)}, Rest);
  1350. do_merge_pax(Header, [{?PAX_GNAME, Gname}|Rest]) ->
  1351. do_merge_pax(Header#tar_header{gname=unicode:characters_to_list(Gname)}, Rest);
  1352. do_merge_pax(Header, [{?PAX_UNAME, Uname}|Rest]) ->
  1353. do_merge_pax(Header#tar_header{uname=unicode:characters_to_list(Uname)}, Rest);
  1354. do_merge_pax(Header, [{?PAX_UID, Uid}|Rest]) ->
  1355. Uid2 = binary_to_integer(Uid),
  1356. do_merge_pax(Header#tar_header{uid=Uid2}, Rest);
  1357. do_merge_pax(Header, [{?PAX_GID, Gid}|Rest]) ->
  1358. Gid2 = binary_to_integer(Gid),
  1359. do_merge_pax(Header#tar_header{gid=Gid2}, Rest);
  1360. do_merge_pax(Header, [{?PAX_ATIME, Atime}|Rest]) ->
  1361. Atime2 = parse_pax_time(Atime),
  1362. do_merge_pax(Header#tar_header{atime=Atime2}, Rest);
  1363. do_merge_pax(Header, [{?PAX_MTIME, Mtime}|Rest]) ->
  1364. Mtime2 = parse_pax_time(Mtime),
  1365. do_merge_pax(Header#tar_header{mtime=Mtime2}, Rest);
  1366. do_merge_pax(Header, [{?PAX_CTIME, Ctime}|Rest]) ->
  1367. Ctime2 = parse_pax_time(Ctime),
  1368. do_merge_pax(Header#tar_header{ctime=Ctime2}, Rest);
  1369. do_merge_pax(Header, [{?PAX_SIZE, Size}|Rest]) ->
  1370. Size2 = binary_to_integer(Size),
  1371. do_merge_pax(Header#tar_header{size=Size2}, Rest);
  1372. do_merge_pax(Header, [{<<?PAX_XATTR_STR, _Key/binary>>, _Value}|Rest]) ->
  1373. do_merge_pax(Header, Rest);
  1374. do_merge_pax(Header, [_Ignore|Rest]) ->
  1375. do_merge_pax(Header, Rest).
  1376. %% Returns the time since UNIX epoch as a datetime
  1377. -spec parse_pax_time(binary()) -> tar_time().
  1378. parse_pax_time(Bin) when is_binary(Bin) ->
  1379. TotalNano = case binary:split(Bin, [<<$.>>]) of
  1380. [SecondsStr, NanoStr0] ->
  1381. Seconds = binary_to_integer(SecondsStr),
  1382. if byte_size(NanoStr0) < ?MAX_NANO_INT_SIZE ->
  1383. %% right pad
  1384. PaddingN = ?MAX_NANO_INT_SIZE-byte_size(NanoStr0),
  1385. Padding = binary:copy(<<$0>>, PaddingN),
  1386. NanoStr1 = <<NanoStr0/binary,Padding/binary>>,
  1387. Nano = binary_to_integer(NanoStr1),
  1388. (Seconds*?BILLION)+Nano;
  1389. byte_size(NanoStr0) > ?MAX_NANO_INT_SIZE ->
  1390. %% right truncate
  1391. NanoStr1 = binary_part(NanoStr0, 0, ?MAX_NANO_INT_SIZE),
  1392. Nano = binary_to_integer(NanoStr1),
  1393. (Seconds*?BILLION)+Nano;
  1394. true ->
  1395. (Seconds*?BILLION)+binary_to_integer(NanoStr0)
  1396. end;
  1397. [SecondsStr] ->
  1398. binary_to_integer(SecondsStr)*?BILLION
  1399. end,
  1400. %% truncate to microseconds
  1401. Micro = TotalNano div 1000,
  1402. Mega = Micro div 1000000000000,
  1403. Secs = Micro div 1000000 - (Mega*1000000),
  1404. Secs.
  1405. %% Given a regular file reader, reads the whole file and
  1406. %% parses all extended attributes it contains.
  1407. parse_pax(#reg_file_reader{handle=Handle,num_bytes=0}) ->
  1408. {#{}, Handle};
  1409. parse_pax(#reg_file_reader{handle=Handle0,num_bytes=NumBytes}) ->
  1410. case do_read(Handle0, NumBytes) of
  1411. {ok, Bytes, Handle1} ->
  1412. do_parse_pax(Handle1, Bytes, #{});
  1413. {error, _} = Err ->
  1414. throw(Err)
  1415. end.
  1416. do_parse_pax(Reader, <<>>, Headers) ->
  1417. {Headers, Reader};
  1418. do_parse_pax(Reader, Bin, Headers) ->
  1419. {Key, Value, Residual} = parse_pax_record(Bin),
  1420. NewHeaders = maps:put(Key, Value, Headers),
  1421. do_parse_pax(Reader, Residual, NewHeaders).
  1422. %% Parse an extended attribute
  1423. parse_pax_record(Bin) when is_binary(Bin) ->
  1424. case binary:split(Bin, [<<$\n>>]) of
  1425. [Record, Residual] ->
  1426. case [X || X <- binary:split(Record, [<<$\s>>], [global]), X =/= <<>>] of
  1427. [_Len, Record1] ->
  1428. case [X || X <- binary:split(Record1, [<<$=>>], [global]), X =/= <<>>] of
  1429. [AttrName, AttrValue] ->
  1430. {AttrName, AttrValue, Residual};
  1431. _Other ->
  1432. throw({error, malformed_pax_record})
  1433. end;
  1434. _Other ->
  1435. throw({error, malformed_pax_record})
  1436. end;
  1437. _Other ->
  1438. throw({error, malformed_pax_record})
  1439. end.
  1440. get_real_name(#reg_file_reader{handle=Handle,num_bytes=0}) ->
  1441. {"", Handle};
  1442. get_real_name(#reg_file_reader{handle=Handle0,num_bytes=NumBytes}) ->
  1443. case do_read(Handle0, NumBytes) of
  1444. {ok, RealName, Handle1} ->
  1445. {RealName, Handle1};
  1446. {error, _} = Err ->
  1447. throw(Err)
  1448. end;
  1449. get_real_name(#sparse_file_reader{num_bytes=NumBytes}=Reader0) ->
  1450. case do_read(Reader0, NumBytes) of
  1451. {ok, RealName, Reader1} ->
  1452. {RealName, Reader1};
  1453. {error, _} = Err ->
  1454. throw(Err)
  1455. end.
  1456. %% Skip the remaining bytes for the current file entry
  1457. skip_file(#reg_file_reader{handle=Handle0,pos=Pos,size=Size}=Reader) ->
  1458. Padding = skip_padding(Size),
  1459. AbsPos = Handle0#reader.pos + (Size-Pos) + Padding,
  1460. case do_position(Handle0, AbsPos) of
  1461. {ok, _, Handle1} ->
  1462. Reader#reg_file_reader{handle=Handle1,num_bytes=0,pos=Size};
  1463. Err ->
  1464. throw(Err)
  1465. end;
  1466. skip_file(#sparse_file_reader{pos=Pos,size=Size}=Reader) ->
  1467. case do_read(Reader, Size-Pos) of
  1468. {ok, _, Reader2} ->
  1469. Reader2;
  1470. Err ->
  1471. throw(Err)
  1472. end.
  1473. skip_padding(0) ->
  1474. 0;
  1475. skip_padding(Size) when (Size rem ?BLOCK_SIZE) =:= 0 ->
  1476. 0;
  1477. skip_padding(Size) when Size =< ?BLOCK_SIZE ->
  1478. ?BLOCK_SIZE - Size;
  1479. skip_padding(Size) ->
  1480. ?BLOCK_SIZE - (Size rem ?BLOCK_SIZE).
  1481. skip_unread(#reader{pos=Pos}=Reader0) when (Pos rem ?BLOCK_SIZE) > 0 ->
  1482. Padding = skip_padding(Pos + ?BLOCK_SIZE),
  1483. AbsPos = Pos + Padding,
  1484. case do_position(Reader0, AbsPos) of
  1485. {ok, _, Reader1} ->
  1486. {ok, Reader1};
  1487. Err ->
  1488. throw(Err)
  1489. end;
  1490. skip_unread(#reader{}=Reader) ->
  1491. {ok, Reader};
  1492. skip_unread(#reg_file_reader{handle=Handle,num_bytes=0}) ->
  1493. skip_unread(Handle);
  1494. skip_unread(#reg_file_reader{}=Reader) ->
  1495. #reg_file_reader{handle=Handle} = skip_file(Reader),
  1496. {ok, Handle};
  1497. skip_unread(#sparse_file_reader{handle=Handle,num_bytes=0}) ->
  1498. skip_unread(Handle);
  1499. skip_unread(#sparse_file_reader{}=Reader) ->
  1500. #sparse_file_reader{handle=Handle} = skip_file(Reader),
  1501. {ok, Handle}.
  1502. write_extracted_element(#tar_header{name=Name,typeflag=Type},
  1503. Bin,
  1504. #read_opts{output=memory}=Opts) ->
  1505. case typeflag(Type) of
  1506. regular ->
  1507. read_verbose(Opts, "x ~ts~n", [Name]),
  1508. {ok, {Name, Bin}};
  1509. _ ->
  1510. ok
  1511. end;
  1512. write_extracted_element(#tar_header{name=Name0}=Header, Bin, Opts) ->
  1513. Name1 = make_safe_path(Name0, Opts),
  1514. Created =
  1515. case typeflag(Header#tar_header.typeflag) of
  1516. regular ->
  1517. create_regular(Name1, Name0, Bin, Opts);
  1518. directory ->
  1519. read_verbose(Opts, "x ~ts~n", [Name0]),
  1520. create_extracted_dir(Name1, Opts);
  1521. symlink ->
  1522. read_verbose(Opts, "x ~ts~n", [Name0]),
  1523. create_symlink(Name1, Header#tar_header.linkname, Opts);
  1524. Device when Device =:= char orelse Device =:= block ->
  1525. %% char/block devices will be created as empty files
  1526. %% and then have their major/minor device set later
  1527. create_regular(Name1, Name0, <<>>, Opts);
  1528. fifo ->
  1529. %% fifo devices will be created as empty files
  1530. create_regular(Name1, Name0, <<>>, Opts);
  1531. Other -> % Ignore.
  1532. read_verbose(Opts, "x ~ts - unsupported type ~p~n",
  1533. [Name0, Other]),
  1534. not_written
  1535. end,
  1536. case Created of
  1537. ok -> set_extracted_file_info(Name1, Header);
  1538. not_written -> ok
  1539. end.
  1540. make_safe_path([$/|Path], Opts) ->
  1541. make_safe_path(Path, Opts);
  1542. make_safe_path(Path, #read_opts{cwd=Cwd}) ->
  1543. case safe_relative_path_links(Path, Cwd) of
  1544. unsafe ->
  1545. throw({error,{Path,unsafe_path}});
  1546. SafePath ->
  1547. filename:absname(SafePath, Cwd)
  1548. end.
  1549. safe_relative_path_links(Path, Cwd) ->
  1550. case filename:pathtype(Path) of
  1551. relative -> safe_relative_path_links(filename:split(Path), Cwd, [], "");
  1552. _ -> unsafe
  1553. end.
  1554. safe_relative_path_links([], _Cwd, _PrevLinks, Acc) ->
  1555. Acc;
  1556. safe_relative_path_links([Segment | Segments], Cwd, PrevLinks, Acc) ->
  1557. AccSegment = join(Acc, Segment),
  1558. case r3_hex_filename:safe_relative_path(AccSegment) of
  1559. unsafe ->
  1560. unsafe;
  1561. SafeAccSegment ->
  1562. case file:read_link(join(Cwd, SafeAccSegment)) of
  1563. {ok, LinkPath} ->
  1564. case lists:member(LinkPath, PrevLinks) of
  1565. true ->
  1566. unsafe;
  1567. false ->
  1568. case safe_relative_path_links(filename:split(LinkPath), Cwd, [LinkPath | PrevLinks], Acc) of
  1569. unsafe -> unsafe;
  1570. NewAcc -> safe_relative_path_links(Segments, Cwd, [], NewAcc)
  1571. end
  1572. end;
  1573. {error, _} ->
  1574. safe_relative_path_links(Segments, Cwd, PrevLinks, SafeAccSegment)
  1575. end
  1576. end.
  1577. join([], Path) -> Path;
  1578. join(Left, Right) -> filename:join(Left, Right).
  1579. create_regular(Name, NameInArchive, Bin, Opts) ->
  1580. case write_extracted_file(Name, Bin, Opts) of
  1581. not_written ->
  1582. read_verbose(Opts, "x ~ts - exists, not created~n", [NameInArchive]),
  1583. not_written;
  1584. Ok ->
  1585. read_verbose(Opts, "x ~ts~n", [NameInArchive]),
  1586. Ok
  1587. end.
  1588. create_extracted_dir(Name, _Opts) ->
  1589. case file:make_dir(Name) of
  1590. ok -> ok;
  1591. {error,enotsup} -> not_written;
  1592. {error,eexist} -> not_written;
  1593. {error,enoent} -> make_dirs(Name, dir);
  1594. {error,Reason} -> throw({error, Reason})
  1595. end.
  1596. create_symlink(Name, Linkname, Opts) ->
  1597. case file:make_symlink(Linkname, Name) of
  1598. ok -> ok;
  1599. {error,enoent} ->
  1600. ok = make_dirs(Name, file),
  1601. create_symlink(Name, Linkname, Opts);
  1602. {error,eexist} -> not_written;
  1603. {error,enotsup} ->
  1604. read_verbose(Opts, "x ~ts - symbolic links not supported~n", [Name]),
  1605. not_written;
  1606. {error,Reason} -> throw({error, Reason})
  1607. end.
  1608. write_extracted_file(Name, Bin, Opts) ->
  1609. Write =
  1610. case Opts#read_opts.keep_old_files of
  1611. true ->
  1612. case file:read_file_info(Name) of
  1613. {ok, _} -> false;
  1614. _ -> true
  1615. end;
  1616. false -> true
  1617. end,
  1618. case Write of
  1619. true -> write_file(Name, Bin);
  1620. false -> not_written
  1621. end.
  1622. write_file(Name, Bin) ->
  1623. case file:write_file(Name, Bin) of
  1624. ok -> ok;
  1625. {error,enoent} ->
  1626. case make_dirs(Name, file) of
  1627. ok ->
  1628. write_file(Name, Bin);
  1629. {error,Reason} ->
  1630. throw({error, Reason})
  1631. end;
  1632. {error,Reason} ->
  1633. throw({error, Reason})
  1634. end.
  1635. set_extracted_file_info(_, #tar_header{typeflag = ?TYPE_SYMLINK}) -> ok;
  1636. set_extracted_file_info(_, #tar_header{typeflag = ?TYPE_LINK}) -> ok;
  1637. set_extracted_file_info(Name, #tar_header{typeflag = ?TYPE_CHAR}=Header) ->
  1638. set_device_info(Name, Header);
  1639. set_extracted_file_info(Name, #tar_header{typeflag = ?TYPE_BLOCK}=Header) ->
  1640. set_device_info(Name, Header);
  1641. set_extracted_file_info(Name, #tar_header{mtime=Mtime,mode=Mode}) ->
  1642. Info = #file_info{mode=Mode, mtime=Mtime},
  1643. file:write_file_info(Name, Info, [{time, posix}]).
  1644. set_device_info(Name, #tar_header{}=Header) ->
  1645. Mtime = Header#tar_header.mtime,
  1646. Mode = Header#tar_header.mode,
  1647. Devmajor = Header#tar_header.devmajor,
  1648. Devminor = Header#tar_header.devminor,
  1649. Info = #file_info{
  1650. mode=Mode,
  1651. mtime=Mtime,
  1652. major_device=Devmajor,
  1653. minor_device=Devminor
  1654. },
  1655. file:write_file_info(Name, Info).
  1656. %% Makes all directories leading up to the file.
  1657. make_dirs(Name, file) ->
  1658. filelib:ensure_dir(Name);
  1659. make_dirs(Name, dir) ->
  1660. filelib:ensure_dir(filename:join(Name,"*")).
  1661. %% Prints the message on if the verbose option is given (for reading).
  1662. read_verbose(#read_opts{verbose=true}, Format, Args) ->
  1663. io:format(Format, Args);
  1664. read_verbose(_, _, _) ->
  1665. ok.
  1666. %% Prints the message on if the verbose option is given.
  1667. add_verbose(#add_opts{verbose=true}, Format, Args) ->
  1668. io:format(Format, Args);
  1669. add_verbose(_, _, _) ->
  1670. ok.
  1671. %%%%%%%%%%%%%%%%%%
  1672. %% I/O primitives
  1673. %%%%%%%%%%%%%%%%%%
  1674. do_write(#reader{handle=Handle,func=Fun}=Reader0, Data)
  1675. when is_function(Fun,2) ->
  1676. case Fun(write,{Handle,Data}) of
  1677. ok ->
  1678. {ok, Pos, Reader1} = do_position(Reader0, {cur,0}),
  1679. {ok, Reader1#reader{pos=Pos}};
  1680. {error, _} = Err ->
  1681. Err
  1682. end.
  1683. do_copy(#reader{func=Fun}=Reader, Source, #add_opts{chunk_size=0}=Opts)
  1684. when is_function(Fun, 2) ->
  1685. do_copy(Reader, Source, Opts#add_opts{chunk_size=65536});
  1686. do_copy(#reader{func=Fun}=Reader, Source, #add_opts{chunk_size=ChunkSize})
  1687. when is_function(Fun, 2) ->
  1688. case file:open(Source, [read, binary]) of
  1689. {ok, SourceFd} ->
  1690. case copy_chunked(Reader, SourceFd, ChunkSize, 0) of
  1691. {ok, _Copied, _Reader2} = Ok->
  1692. _ = file:close(SourceFd),
  1693. Ok;
  1694. Err ->
  1695. _ = file:close(SourceFd),
  1696. throw(Err)
  1697. end;
  1698. Err ->
  1699. throw(Err)
  1700. end.
  1701. copy_chunked(#reader{}=Reader, Source, ChunkSize, Copied) ->
  1702. case file:read(Source, ChunkSize) of
  1703. {ok, Bin} ->
  1704. {ok, Reader2} = do_write(Reader, Bin),
  1705. copy_chunked(Reader2, Source, ChunkSize, Copied+byte_size(Bin));
  1706. eof ->
  1707. {ok, Copied, Reader};
  1708. Other ->
  1709. Other
  1710. end.
  1711. do_position(#reader{handle=Handle,func=Fun}=Reader, Pos)
  1712. when is_function(Fun,2)->
  1713. case Fun(position, {Handle,Pos}) of
  1714. {ok, NewPos} ->
  1715. %% since Pos may not always be an absolute seek,
  1716. %% make sure we update the reader with the new absolute position
  1717. {ok, AbsPos} = Fun(position, {Handle, {cur, 0}}),
  1718. {ok, NewPos, Reader#reader{pos=AbsPos}};
  1719. Other ->
  1720. Other
  1721. end.
  1722. do_read(#reg_file_reader{handle=Handle,pos=Pos,size=Size}=Reader, Len) ->
  1723. NumBytes = Size - Pos,
  1724. ActualLen = if NumBytes - Len < 0 -> NumBytes; true -> Len end,
  1725. case do_read(Handle, ActualLen) of
  1726. {ok, Bin, Handle2} ->
  1727. NewPos = Pos + ActualLen,
  1728. NumBytes2 = Size - NewPos,
  1729. Reader1 = Reader#reg_file_reader{
  1730. handle=Handle2,
  1731. pos=NewPos,
  1732. num_bytes=NumBytes2},
  1733. {ok, Bin, Reader1};
  1734. Other ->
  1735. Other
  1736. end;
  1737. do_read(#sparse_file_reader{}=Reader, Len) ->
  1738. do_sparse_read(Reader, Len);
  1739. do_read(#reader{pos=Pos,handle=Handle,func=Fun}=Reader, Len)
  1740. when is_function(Fun,2)->
  1741. %% Always convert to binary internally
  1742. case Fun(read2,{Handle,Len}) of
  1743. {ok, List} when is_list(List) ->
  1744. Bin = list_to_binary(List),
  1745. NewPos = Pos+byte_size(Bin),
  1746. {ok, Bin, Reader#reader{pos=NewPos}};
  1747. {ok, Bin} when is_binary(Bin) ->
  1748. NewPos = Pos+byte_size(Bin),
  1749. {ok, Bin, Reader#reader{pos=NewPos}};
  1750. Other ->
  1751. Other
  1752. end.
  1753. do_sparse_read(Reader, Len) ->
  1754. do_sparse_read(Reader, Len, <<>>).
  1755. do_sparse_read(#sparse_file_reader{sparse_map=[#sparse_entry{num_bytes=0}|Entries]
  1756. }=Reader0, Len, Acc) ->
  1757. %% skip all empty fragments
  1758. Reader1 = Reader0#sparse_file_reader{sparse_map=Entries},
  1759. do_sparse_read(Reader1, Len, Acc);
  1760. do_sparse_read(#sparse_file_reader{sparse_map=[],
  1761. pos=Pos,size=Size}=Reader0, Len, Acc)
  1762. when Pos < Size ->
  1763. %% if there are no more fragments, it is possible that there is one last sparse hole
  1764. %% this behaviour matches the BSD tar utility
  1765. %% however, GNU tar stops returning data even if we haven't reached the end
  1766. {ok, Bin, Reader1} = read_sparse_hole(Reader0, Size, Len),
  1767. do_sparse_read(Reader1, Len-byte_size(Bin), <<Acc/binary,Bin/binary>>);
  1768. do_sparse_read(#sparse_file_reader{sparse_map=[]}=Reader, _Len, Acc) ->
  1769. {ok, Acc, Reader};
  1770. do_sparse_read(#sparse_file_reader{}=Reader, 0, Acc) ->
  1771. {ok, Acc, Reader};
  1772. do_sparse_read(#sparse_file_reader{sparse_map=[#sparse_entry{offset=Offset}|_],
  1773. pos=Pos}=Reader0, Len, Acc)
  1774. when Pos < Offset ->
  1775. {ok, Bin, Reader1} = read_sparse_hole(Reader0, Offset, Offset-Pos),
  1776. do_sparse_read(Reader1, Len-byte_size(Bin), <<Acc/binary,Bin/binary>>);
  1777. do_sparse_read(#sparse_file_reader{sparse_map=[Entry|Entries],
  1778. pos=Pos}=Reader0, Len, Acc) ->
  1779. %% we're in a data fragment, so read from it
  1780. %% end offset of fragment
  1781. EndPos = Entry#sparse_entry.offset + Entry#sparse_entry.num_bytes,
  1782. %% bytes left in fragment
  1783. NumBytes = EndPos - Pos,
  1784. ActualLen = if Len > NumBytes -> NumBytes; true -> Len end,
  1785. case do_read(Reader0#sparse_file_reader.handle, ActualLen) of
  1786. {ok, Bin, Handle} ->
  1787. BytesRead = byte_size(Bin),
  1788. ActualEndPos = Pos+BytesRead,
  1789. Reader1 = if ActualEndPos =:= EndPos ->
  1790. Reader0#sparse_file_reader{sparse_map=Entries};
  1791. true ->
  1792. Reader0
  1793. end,
  1794. Size = Reader1#sparse_file_reader.size,
  1795. NumBytes2 = Size - ActualEndPos,
  1796. Reader2 = Reader1#sparse_file_reader{
  1797. handle=Handle,
  1798. pos=ActualEndPos,
  1799. num_bytes=NumBytes2},
  1800. do_sparse_read(Reader2, Len-byte_size(Bin), <<Acc/binary,Bin/binary>>);
  1801. Other ->
  1802. Other
  1803. end.
  1804. %% Reads a sparse hole ending at Offset
  1805. read_sparse_hole(#sparse_file_reader{pos=Pos}=Reader, Offset, Len) ->
  1806. N = Offset - Pos,
  1807. N2 = if N > Len ->
  1808. Len;
  1809. true ->
  1810. N
  1811. end,
  1812. Bin = <<0:N2/unit:8>>,
  1813. NumBytes = Reader#sparse_file_reader.size - (Pos+N2),
  1814. {ok, Bin, Reader#sparse_file_reader{
  1815. num_bytes=NumBytes,
  1816. pos=Pos+N2}}.
  1817. -spec do_close(reader()) -> ok | {error, term()}.
  1818. do_close(#reader{handle=Handle,func=Fun}) when is_function(Fun,2) ->
  1819. Fun(close,Handle).
  1820. %%%%%%%%%%%%%%%%%%
  1821. %% Option parsing
  1822. %%%%%%%%%%%%%%%%%%
  1823. extract_opts(List) ->
  1824. extract_opts(List, default_options()).
  1825. table_opts(List) ->
  1826. read_opts(List, default_options()).
  1827. default_options() ->
  1828. {ok, Cwd} = file:get_cwd(),
  1829. #read_opts{cwd=Cwd}.
  1830. extract_opts([keep_old_files|Rest], Opts) ->
  1831. extract_opts(Rest, Opts#read_opts{keep_old_files=true});
  1832. extract_opts([{cwd, Cwd}|Rest], Opts) ->
  1833. extract_opts(Rest, Opts#read_opts{cwd=Cwd});
  1834. extract_opts([{files, Files}|Rest], Opts) ->
  1835. Set = ordsets:from_list(Files),
  1836. extract_opts(Rest, Opts#read_opts{files=Set});
  1837. extract_opts([memory|Rest], Opts) ->
  1838. extract_opts(Rest, Opts#read_opts{output=memory});
  1839. extract_opts([compressed|Rest], Opts=#read_opts{open_mode=OpenMode}) ->
  1840. extract_opts(Rest, Opts#read_opts{open_mode=[compressed|OpenMode]});
  1841. extract_opts([cooked|Rest], Opts=#read_opts{open_mode=OpenMode}) ->
  1842. extract_opts(Rest, Opts#read_opts{open_mode=[cooked|OpenMode]});
  1843. extract_opts([verbose|Rest], Opts) ->
  1844. extract_opts(Rest, Opts#read_opts{verbose=true});
  1845. extract_opts([Other|Rest], Opts) ->
  1846. extract_opts(Rest, read_opts([Other], Opts));
  1847. extract_opts([], Opts) ->
  1848. Opts.
  1849. read_opts([compressed|Rest], Opts=#read_opts{open_mode=OpenMode}) ->
  1850. read_opts(Rest, Opts#read_opts{open_mode=[compressed|OpenMode]});
  1851. read_opts([cooked|Rest], Opts=#read_opts{open_mode=OpenMode}) ->
  1852. read_opts(Rest, Opts#read_opts{open_mode=[cooked|OpenMode]});
  1853. read_opts([verbose|Rest], Opts) ->
  1854. read_opts(Rest, Opts#read_opts{verbose=true});
  1855. read_opts([_|Rest], Opts) ->
  1856. read_opts(Rest, Opts);
  1857. read_opts([], Opts) ->
  1858. Opts.