@@ -8,6 +8,7 @@ use std::io::{BufWriter, Error, Result};
88use std:: io:: { ErrorKind , Write } ;
99use std:: path:: Path ;
1010use std:: process:: { Child , Command , Stdio } ;
11+ use uucore:: display:: Quotable ;
1112use uucore:: error:: USimpleError ;
1213use uucore:: fs;
1314use uucore:: fs:: FileInformation ;
@@ -127,36 +128,32 @@ impl Drop for FilterWriter {
127128/// Instantiate either a file writer or a "write to shell process's stdin" writer
128129pub fn instantiate_current_writer (
129130 filter : Option < & str > ,
131+ input : & OsStr ,
130132 filename : & str ,
131133 is_new : bool ,
132134) -> Result < BufWriter < Box < dyn Write > > > {
133135 match filter {
134136 None => {
135137 let file = if is_new {
136- // create new file
137- std:: fs:: OpenOptions :: new ( )
138- . write ( true )
139- . create ( true )
140- . truncate ( true )
141- . open ( Path :: new ( & filename) )
142- . map_err ( |e| match e. kind ( ) {
143- ErrorKind :: IsADirectory => Error :: other (
144- translate ! ( "split-error-is-a-directory" , "dir" => filename) ,
145- ) ,
146- _ => Error :: other (
147- translate ! ( "split-error-unable-to-open-file" , "file" => filename) ,
148- ) ,
149- } ) ?
138+ create_or_truncate_output_file ( input, filename) ?
150139 } else {
151140 // re-open file that we previously created to append to it
152- std:: fs:: OpenOptions :: new ( )
141+ let file = std:: fs:: OpenOptions :: new ( )
153142 . append ( true )
154143 . open ( Path :: new ( & filename) )
155144 . map_err ( |_| {
156145 Error :: other (
157146 translate ! ( "split-error-unable-to-reopen-file" , "file" => filename) ,
158147 )
159- } ) ?
148+ } ) ?;
149+
150+ if input_and_output_refer_to_same_file ( input, & file) {
151+ return Err ( Error :: other (
152+ translate ! ( "split-error-would-overwrite-input" , "file" => filename. quote( ) ) ,
153+ ) ) ;
154+ }
155+
156+ file
160157 } ;
161158 Ok ( BufWriter :: new ( Box :: new ( file) as Box < dyn Write > ) )
162159 }
@@ -167,6 +164,52 @@ pub fn instantiate_current_writer(
167164 }
168165}
169166
167+ fn create_or_truncate_output_file ( input : & OsStr , filename : & str ) -> Result < std:: fs:: File > {
168+ match std:: fs:: OpenOptions :: new ( )
169+ . write ( true )
170+ . create_new ( true )
171+ . open ( Path :: new ( filename) )
172+ {
173+ Ok ( file) => Ok ( file) ,
174+ Err ( e) if e. kind ( ) == ErrorKind :: AlreadyExists => {
175+ let file = std:: fs:: OpenOptions :: new ( )
176+ . write ( true )
177+ . open ( Path :: new ( filename) )
178+ . map_err ( |err| open_file_error ( filename, err. kind ( ) ) ) ?;
179+
180+ if input_and_output_refer_to_same_file ( input, & file) {
181+ return Err ( Error :: other (
182+ translate ! ( "split-error-would-overwrite-input" , "file" => filename. quote( ) ) ,
183+ ) ) ;
184+ }
185+
186+ file. set_len ( 0 )
187+ . map_err ( |err| open_file_error ( filename, err. kind ( ) ) ) ?;
188+ Ok ( file)
189+ }
190+ Err ( e) => Err ( open_file_error ( filename, e. kind ( ) ) ) ,
191+ }
192+ }
193+
194+ fn open_file_error ( filename : & str , kind : ErrorKind ) -> Error {
195+ match kind {
196+ ErrorKind :: IsADirectory => {
197+ Error :: other ( translate ! ( "split-error-is-a-directory" , "dir" => filename) )
198+ }
199+ _ => Error :: other ( translate ! ( "split-error-unable-to-open-file" , "file" => filename) ) ,
200+ }
201+ }
202+
203+ fn input_and_output_refer_to_same_file ( input : & OsStr , output : & std:: fs:: File ) -> bool {
204+ let input_info = if input == "-" {
205+ FileInformation :: from_file ( & std:: io:: stdin ( ) )
206+ } else {
207+ FileInformation :: from_path ( Path :: new ( input) , true )
208+ } ;
209+
210+ fs:: infos_refer_to_same_file ( input_info, FileInformation :: from_file ( output) )
211+ }
212+
170213pub fn paths_refer_to_same_file ( p1 : & OsStr , p2 : & OsStr ) -> bool {
171214 // We have to take symlinks and relative paths into account.
172215 let p1 = if p1 == "-" {
@@ -176,3 +219,45 @@ pub fn paths_refer_to_same_file(p1: &OsStr, p2: &OsStr) -> bool {
176219 } ;
177220 fs:: infos_refer_to_same_file ( p1, FileInformation :: from_path ( Path :: new ( p2) , true ) )
178221}
222+
223+ #[ cfg( test) ]
224+ mod tests {
225+ use super :: instantiate_current_writer;
226+ use std:: fs;
227+ use std:: os:: unix:: fs:: symlink;
228+ use std:: time:: { SystemTime , UNIX_EPOCH } ;
229+
230+ #[ test]
231+ fn reopened_writer_rejects_symlink_swapped_to_input ( ) {
232+ let tmp = std:: env:: temp_dir ( ) . join ( format ! (
233+ "uutils-split-{}-{}" ,
234+ std:: process:: id( ) ,
235+ SystemTime :: now( )
236+ . duration_since( UNIX_EPOCH )
237+ . expect( "system time before unix epoch" )
238+ . as_nanos( )
239+ ) ) ;
240+ fs:: create_dir_all ( & tmp) . expect ( "failed to create temp dir" ) ;
241+
242+ let input = tmp. join ( "input.txt" ) ;
243+ let output = tmp. join ( "xaa" ) ;
244+ fs:: write ( & input, b"input\n " ) . expect ( "failed to write input" ) ;
245+ symlink ( & input, & output) . expect ( "failed to create output symlink" ) ;
246+
247+ let Err ( err) = instantiate_current_writer (
248+ None ,
249+ input. as_os_str ( ) ,
250+ output. to_str ( ) . expect ( "non-utf8 path" ) ,
251+ false ,
252+ ) else {
253+ panic ! ( "re-opened writer should reject swapped symlink" ) ;
254+ } ;
255+ assert ! (
256+ err. to_string( )
257+ . contains( "split-error-would-overwrite-input" ) ,
258+ "unexpected error: {err}"
259+ ) ;
260+
261+ fs:: remove_dir_all ( & tmp) . expect ( "failed to cleanup temp dir" ) ;
262+ }
263+ }
0 commit comments