44% % 3. -doc and -moduledoc attributes removed for OTP 24 compatibility
55% % 4. safe_link_name/2 fixed to validate symlink targets relative to symlink's
66% % parent directory instead of in isolation
7+ % % 5. When extracting to disk (cwd option), stream file entries in chunks
8+ % % instead of loading them fully into memory
9+ % % 6. Default chunk_size to 65536 in add_opts instead of 0 with special case
10+ % % 7. Use compressed instead of compressed_one for file:open for OTP 24 compat
711% %
812% % OTP commit: 013041bd68c2547848e88963739edea7f0a1a90f
913% %
@@ -147,12 +151,18 @@ extract1(eof, Reader, _, Acc) ->
147151extract1 (# tar_header {name = Name ,size = Size }= Header , Reader0 , Opts , Acc0 ) ->
148152 case check_extract (Name , Opts ) of
149153 true ->
150- case do_read (Reader0 , Size ) of
151- {ok , Bin , Reader1 } ->
152- Acc = extract2 (Header , Bin , Opts , Acc0 ),
153- {ok , Acc , Reader1 };
154- {error , _ } = Err ->
155- throw (Err )
154+ case Opts # read_opts .output of
155+ memory ->
156+ case do_read (Reader0 , Size ) of
157+ {ok , Bin , Reader1 } ->
158+ Acc = extract2 (Header , Bin , Opts , Acc0 ),
159+ {ok , Acc , Reader1 };
160+ {error , _ } = Err ->
161+ throw (Err )
162+ end ;
163+ file ->
164+ Reader1 = extract_to_file (Header , Reader0 , Opts ),
165+ {ok , Acc0 , Reader1 }
156166 end ;
157167 false ->
158168 {ok , Acc0 , skip_file (Reader0 )}
@@ -173,6 +183,79 @@ extract2(Header, Bin, Opts, Acc) ->
173183 throw (Err )
174184 end .
175185
186+ extract_to_file (# tar_header {name = Name0 }= Header , Reader0 , Opts ) ->
187+ case typeflag (Header # tar_header .typeflag ) of
188+ regular ->
189+ Name1 = make_safe_path (Name0 , Opts ),
190+ case stream_to_file (Name1 , Reader0 , Opts ) of
191+ {ok , Reader1 } ->
192+ read_verbose (Opts , " x ~ts~n " , [Name0 ]),
193+ _ = set_extracted_file_info (Name1 , Header ),
194+ Reader1 ;
195+ {error , _ } = Err ->
196+ throw (Err )
197+ end ;
198+ _ ->
199+ Reader1 = skip_file (Reader0 ),
200+ _ = write_extracted_element (Header , <<>>, Opts ),
201+ Reader1
202+ end .
203+
204+ stream_to_file (Name , Reader0 , Opts ) ->
205+ Write =
206+ case Opts # read_opts .keep_old_files of
207+ true ->
208+ case file :read_file_info (Name ) of
209+ {ok , _ } -> false ;
210+ _ -> true
211+ end ;
212+ false -> true
213+ end ,
214+ case Write of
215+ true ->
216+ ChunkSize = Opts # read_opts .chunk_size ,
217+ case open_output_file (Name ) of
218+ {ok , Fd } ->
219+ try
220+ stream_to_file_loop (Fd , Reader0 , ChunkSize )
221+ after
222+ file :close (Fd )
223+ end ;
224+ {error , _ } = Err ->
225+ Err
226+ end ;
227+ false ->
228+ {ok , skip_file (Reader0 )}
229+ end .
230+
231+ open_output_file (Name ) ->
232+ case file :open (Name , [write , raw , binary ]) of
233+ {ok , _ } = Ok ->
234+ Ok ;
235+ {error , enoent } ->
236+ ok = make_dirs (Name , file ),
237+ file :open (Name , [write , raw , binary ]);
238+ {error , _ } = Err ->
239+ Err
240+ end .
241+
242+ stream_to_file_loop (_Fd , # reg_file_reader {num_bytes = 0 }= Reader , _ChunkSize ) ->
243+ {ok , Reader };
244+ stream_to_file_loop (_Fd , # sparse_file_reader {num_bytes = 0 }= Reader , _ChunkSize ) ->
245+ {ok , Reader };
246+ stream_to_file_loop (Fd , Reader , ChunkSize ) ->
247+ case do_read (Reader , ChunkSize ) of
248+ {ok , Bin , Reader1 } ->
249+ case file :write (Fd , Bin ) of
250+ ok ->
251+ stream_to_file_loop (Fd , Reader1 , ChunkSize );
252+ {error , _ } = Err ->
253+ Err
254+ end ;
255+ {error , _ } = Err ->
256+ Err
257+ end .
258+
176259% % Checks if the file Name should be extracted.
177260check_extract (_ , # read_opts {files = all }) ->
178261 true ;
@@ -382,13 +465,7 @@ open1({file, Fd}=Handle, read, [raw], Opts) ->
382465 end ;
383466open1 ({file , _Fd }= Handle , read , [], _Opts ) ->
384467 {error , {Handle , {incompatible_option , cooked }}};
385- open1 (Name , Access , Raw , Opts0 ) when is_list (Name ); is_binary (Name ) ->
386- Opts = case lists :member (compressed , Opts0 ) andalso Access == read of
387- true ->
388- [compressed_one | (Opts0 -- [compressed ])];
389- false ->
390- Opts0
391- end ,
468+ open1 (Name , Access , Raw , Opts ) when is_list (Name ); is_binary (Name ) ->
392469 case file :open (Name , Raw ++ [binary , Access |Opts ]) of
393470 {ok , File } ->
394471 {ok , # reader {handle = File ,access = Access ,func = fun file_op /2 }};
@@ -1833,9 +1910,6 @@ do_write(#reader{handle=Handle,func=Fun}=Reader0, Data)
18331910 Err
18341911 end .
18351912
1836- do_copy (# reader {func = Fun }= Reader , Source , # add_opts {chunk_size = 0 }= Opts )
1837- when is_function (Fun , 2 ) ->
1838- do_copy (Reader , Source , Opts # add_opts {chunk_size = 65536 });
18391913do_copy (# reader {func = Fun }= Reader , Source , # add_opts {chunk_size = ChunkSize })
18401914 when is_function (Fun , 2 ) ->
18411915 case file :open (Source , [read , binary ]) of
@@ -2009,6 +2083,8 @@ extract_opts([cooked|Rest], Opts=#read_opts{open_mode=OpenMode}) ->
20092083 extract_opts (Rest , Opts # read_opts {open_mode = [cooked |OpenMode ]});
20102084extract_opts ([verbose |Rest ], Opts ) ->
20112085 extract_opts (Rest , Opts # read_opts {verbose = true });
2086+ extract_opts ([{chunks ,N }|Rest ], Opts ) ->
2087+ extract_opts (Rest , Opts # read_opts {chunk_size = N });
20122088extract_opts ([Other |Rest ], Opts ) ->
20132089 extract_opts (Rest , read_opts ([Other ], Opts ));
20142090extract_opts ([], Opts ) ->
0 commit comments