@@ -106,50 +106,114 @@ fn find_resource_dirs_from_program(
106106
107107pub fn add_named_blob (
108108 resource_dir : & Path ,
109- mut contents : impl std:: io:: Seek + std :: io :: Read ,
109+ mut contents : impl std:: io:: BufRead ,
110110 executable : bool ,
111111 name : & Path ,
112112) -> Result < PathBuf , AddBlobError > {
113- let mut hasher = blake3:: Hasher :: new ( ) ;
114- std:: io:: copy ( & mut contents, & mut hasher) ?;
115- let hash = hasher. finalize ( ) ;
116-
117- let blob_suffix = if executable { ".x" } else { "" } ;
118- let blob_name = format ! ( "{hash}{blob_suffix}" ) ;
119-
120- contents. seek ( std:: io:: SeekFrom :: Start ( 0 ) ) ?;
121-
113+ // Create the 'blobs' directory
122114 let blob_dir = resource_dir. join ( "blobs" ) ;
123- let blob_path = blob_dir. join ( & blob_name) ;
124- let blob_temp_id = ulid:: Ulid :: new ( ) ;
125- let blob_temp_path = blob_dir. join ( format ! ( "{blob_name}-{blob_temp_id}" ) ) ;
126115 std:: fs:: create_dir_all ( & blob_dir) ?;
127116
117+ // Open a temporary file to copy the contents to
118+ let blob_temp_id = ulid:: Ulid :: new ( ) ;
119+ let blob_temp_path = blob_dir. join ( blob_temp_id. to_string ( ) ) ;
128120 let mut blob_file_options = std:: fs:: OpenOptions :: new ( ) ;
129121 blob_file_options. create_new ( true ) . write ( true ) ;
130122 if executable {
131123 blob_file_options. mode ( 0o777 ) ;
132124 }
133125 let mut blob_file = blob_file_options. open ( & blob_temp_path) ?;
134- std:: io:: copy ( & mut contents, & mut blob_file) ?;
126+
127+ // Read the contents, both copying it to the temporary file and hashing
128+ // as we go
129+ let mut hasher = blake3:: Hasher :: new ( ) ;
130+ loop {
131+ let buf = contents. fill_buf ( ) ?;
132+ if buf. is_empty ( ) {
133+ break ;
134+ }
135+
136+ hasher. update ( buf) ;
137+ blob_file. write_all ( buf) ?;
138+
139+ let consumed = buf. len ( ) ;
140+ contents. consume ( consumed) ;
141+ }
142+
143+ // Get the hash of the contents, which we'll use as the blob's name
144+ let hash = hasher. finalize ( ) ;
145+
146+ // Get the final blob's filename. We use a suffix to distinguish identical
147+ // blobs with different permissions
148+ let blob_suffix = if executable { ".x" } else { "" } ;
149+ let blob_name = format ! ( "{hash}{blob_suffix}" ) ;
150+ let blob_path = blob_dir. join ( & blob_name) ;
151+
152+ // Rename the blob to its final path
135153 drop ( blob_file) ;
136154 std:: fs:: rename ( & blob_temp_path, & blob_path) ?;
137155
138- let alias_dir = resource_dir. join ( "aliases" ) . join ( name) . join ( & blob_name) ;
139- std:: fs:: create_dir_all ( & alias_dir) ?;
156+ // Create a temporary directory for the alias dir
157+ let alias_temp_dir = resource_dir. join ( format ! ( "{blob_name}-{blob_temp_id}-alias" ) ) ;
158+ let alias_temp_path = alias_temp_dir. join ( name) ;
159+ std:: fs:: create_dir ( & alias_temp_dir) ?;
140160
141- let temp_alias_path = alias_dir. join ( format ! ( "{}-{blob_temp_id}" , name. display( ) ) ) ;
142- let alias_path = alias_dir. join ( name) ;
161+ // Create the symlink within the temporary dir
162+ let alias_parent_dir = resource_dir. join ( "aliases" ) . join ( name) ;
163+ let alias_dir = alias_parent_dir. join ( & blob_name) ;
143164 let blob_pack_relative_path = pathdiff:: diff_paths ( & blob_path, & alias_dir)
144165 . expect ( "blob path is not a prefix of alias path" ) ;
145- std:: os:: unix:: fs:: symlink ( blob_pack_relative_path, & temp_alias_path) ?;
146- std:: fs:: rename ( & temp_alias_path, & alias_path) ?;
166+ std:: os:: unix:: fs:: symlink ( & blob_pack_relative_path, & alias_temp_path) ?;
167+
168+ // Create directory for the alias dir
169+ std:: fs:: create_dir_all ( & alias_parent_dir) ?;
147170
171+ // Rename the temp dir to the final alias path. This ensures that the alias
172+ // dir itself is atomic, and never appears empty
173+ let alias_path = alias_dir. join ( name) ;
174+ let result = std:: fs:: rename ( & alias_temp_dir, alias_dir) ;
175+ match result {
176+ Ok ( ( ) ) => {
177+ // Alias dir created successfully
178+ }
179+ Err ( err)
180+ if err. kind ( ) == std:: io:: ErrorKind :: AlreadyExists
181+ || err. kind ( ) == std:: io:: ErrorKind :: DirectoryNotEmpty =>
182+ {
183+ // Could not rename temp alias dir to final path. On Unix, this
184+ // means that the alias dir already exists and is non-empty
185+
186+ // Clean up the temporary dir first
187+ std:: fs:: remove_dir_all ( & alias_temp_dir) ?;
188+
189+ // Try to create the symlink again-- this time in its final path
190+ let result = std:: os:: unix:: fs:: symlink ( & blob_pack_relative_path, & alias_path) ;
191+ match result {
192+ Ok ( ( ) ) => {
193+ // Symlink created successfully. This means the alias
194+ // dir already existed and was not empty, but contained
195+ // something else? This probably shouldn't happen...
196+ }
197+ Err ( err) if err. kind ( ) == std:: io:: ErrorKind :: AlreadyExists => {
198+ // Path already exists, nothing to do
199+ }
200+ Err ( err) => {
201+ return Err ( err. into ( ) ) ;
202+ }
203+ }
204+ }
205+ Err ( err) => {
206+ return Err ( err. into ( ) ) ;
207+ }
208+ }
209+
210+ // Return the symlink alias path relative to the resource dir
148211 let alias_path = alias_path
149212 . strip_prefix ( resource_dir)
150213 . expect ( "alias path is not in resource dir" ) ;
151214 Ok ( alias_path. to_owned ( ) )
152215}
216+
153217pub fn add_named_resource_directory (
154218 resource_dir : & Path ,
155219 source : & Path ,
0 commit comments