/build/source/nativelink-util/src/fs.rs
Line  | Count  | Source  | 
1  |  | // Copyright 2024 The NativeLink Authors. All rights reserved.  | 
2  |  | //  | 
3  |  | // Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License");  | 
4  |  | // you may not use this file except in compliance with the License.  | 
5  |  | // You may obtain a copy of the License at  | 
6  |  | //  | 
7  |  | //    See LICENSE file for details  | 
8  |  | //  | 
9  |  | // Unless required by applicable law or agreed to in writing, software  | 
10  |  | // distributed under the License is distributed on an "AS IS" BASIS,  | 
11  |  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  | 
12  |  | // See the License for the specific language governing permissions and  | 
13  |  | // limitations under the License.  | 
14  |  |  | 
15  |  | use core::pin::Pin;  | 
16  |  | use core::sync::atomic::{AtomicUsize, Ordering}; | 
17  |  | use core::task::{Context, Poll}; | 
18  |  | use std::fs::{Metadata, Permissions}; | 
19  |  | use std::io::{IoSlice, Seek}; | 
20  |  | use std::path::{Path, PathBuf}; | 
21  |  |  | 
22  |  | use nativelink_error::{Code, Error, ResultExt, make_err}; | 
23  |  | use rlimit::increase_nofile_limit;  | 
24  |  | /// We wrap all `tokio::fs` items in our own wrapper so we can limit the number of outstanding  | 
25  |  | /// open files at any given time. This will greatly reduce the chance we'll hit open file limit  | 
26  |  | /// issues.  | 
27  |  | pub use tokio::fs::DirEntry;  | 
28  |  | use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncWrite, ReadBuf, SeekFrom, Take}; | 
29  |  | use tokio::sync::{Semaphore, SemaphorePermit}; | 
30  |  | use tracing::{error, info, warn}; | 
31  |  |  | 
32  |  | use crate::spawn_blocking;  | 
33  |  |  | 
34  |  | /// Default read buffer size when reading to/from disk.  | 
35  |  | pub const DEFAULT_READ_BUFF_SIZE: usize = 0x4000;  | 
36  |  |  | 
37  |  | #[derive(Debug)]  | 
38  |  | pub struct FileSlot { | 
39  |  |     // We hold the permit because once it is dropped it goes back into the queue.  | 
40  |  |     _permit: SemaphorePermit<'static>,  | 
41  |  |     inner: tokio::fs::File,  | 
42  |  | }  | 
43  |  |  | 
44  |  | impl AsRef<tokio::fs::File> for FileSlot { | 
45  | 122  |     fn as_ref(&self) -> &tokio::fs::File { | 
46  | 122  |         &self.inner  | 
47  | 122  |     }  | 
48  |  | }  | 
49  |  |  | 
50  |  | impl AsMut<tokio::fs::File> for FileSlot { | 
51  | 0  |     fn as_mut(&mut self) -> &mut tokio::fs::File { | 
52  | 0  |         &mut self.inner  | 
53  | 0  |     }  | 
54  |  | }  | 
55  |  |  | 
56  |  | impl AsyncRead for FileSlot { | 
57  | 2.56k  |     fn poll_read(  | 
58  | 2.56k  |         mut self: Pin<&mut Self>,  | 
59  | 2.56k  |         cx: &mut Context<'_>,  | 
60  | 2.56k  |         buf: &mut ReadBuf<'_>,  | 
61  | 2.56k  |     ) -> Poll<Result<(), tokio::io::Error>> { | 
62  | 2.56k  |         Pin::new(&mut self.inner).poll_read(cx, buf)  | 
63  | 2.56k  |     }  | 
64  |  | }  | 
65  |  |  | 
66  |  | impl AsyncSeek for FileSlot { | 
67  | 19  |     fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> Result<(), tokio::io::Error> { | 
68  | 19  |         Pin::new(&mut self.inner).start_seek(position)  | 
69  | 19  |     }  | 
70  |  |  | 
71  | 57  |     fn poll_complete(  | 
72  | 57  |         mut self: Pin<&mut Self>,  | 
73  | 57  |         cx: &mut Context<'_>,  | 
74  | 57  |     ) -> Poll<Result<u64, tokio::io::Error>> { | 
75  | 57  |         Pin::new(&mut self.inner).poll_complete(cx)  | 
76  | 57  |     }  | 
77  |  | }  | 
78  |  |  | 
79  |  | impl AsyncWrite for FileSlot { | 
80  | 2  |     fn poll_write(  | 
81  | 2  |         mut self: Pin<&mut Self>,  | 
82  | 2  |         cx: &mut Context<'_>,  | 
83  | 2  |         buf: &[u8],  | 
84  | 2  |     ) -> Poll<Result<usize, tokio::io::Error>> { | 
85  | 2  |         Pin::new(&mut self.inner).poll_write(cx, buf)  | 
86  | 2  |     }  | 
87  |  |  | 
88  | 0  |     fn poll_flush(  | 
89  | 0  |         mut self: Pin<&mut Self>,  | 
90  | 0  |         cx: &mut Context<'_>,  | 
91  | 0  |     ) -> Poll<Result<(), tokio::io::Error>> { | 
92  | 0  |         Pin::new(&mut self.inner).poll_flush(cx)  | 
93  | 0  |     }  | 
94  |  |  | 
95  | 0  |     fn poll_shutdown(  | 
96  | 0  |         mut self: Pin<&mut Self>,  | 
97  | 0  |         cx: &mut Context<'_>,  | 
98  | 0  |     ) -> Poll<Result<(), tokio::io::Error>> { | 
99  | 0  |         Pin::new(&mut self.inner).poll_shutdown(cx)  | 
100  | 0  |     }  | 
101  |  |  | 
102  | 96  |     fn poll_write_vectored(  | 
103  | 96  |         mut self: Pin<&mut Self>,  | 
104  | 96  |         cx: &mut Context<'_>,  | 
105  | 96  |         bufs: &[IoSlice<'_>],  | 
106  | 96  |     ) -> Poll<Result<usize, tokio::io::Error>> { | 
107  | 96  |         Pin::new(&mut self.inner).poll_write_vectored(cx, bufs)  | 
108  | 96  |     }  | 
109  |  |  | 
110  | 96  |     fn is_write_vectored(&self) -> bool { | 
111  | 96  |         self.inner.is_write_vectored()  | 
112  | 96  |     }  | 
113  |  | }  | 
114  |  |  | 
115  |  | // Note: If the default changes make sure you update the documentation in  | 
116  |  | // `config/cas_server.rs`.  | 
117  |  | pub const DEFAULT_OPEN_FILE_LIMIT: usize = 24 * 1024; // 24k.  | 
118  |  | static OPEN_FILE_LIMIT: AtomicUsize = AtomicUsize::new(DEFAULT_OPEN_FILE_LIMIT);  | 
119  |  | pub static OPEN_FILE_SEMAPHORE: Semaphore = Semaphore::const_new(DEFAULT_OPEN_FILE_LIMIT);  | 
120  |  |  | 
121  |  | /// Try to acquire a permit from the open file semaphore.  | 
122  |  | #[inline]  | 
123  | 25.3k  | pub async fn get_permit() -> Result<SemaphorePermit<'static>, Error> { | 
124  | 25.3k  |     OPEN_FILE_SEMAPHORE  | 
125  | 25.3k  |         .acquire()  | 
126  | 25.3k  |         .await  | 
127  | 25.3k  |         .map_err(|e| make_err!(Code::Internal0 , "Open file semaphore closed {:?}", e))  | 
128  | 25.3k  | }  | 
129  |  | /// Acquire a permit from the open file semaphore and call a raw function.  | 
130  |  | #[inline]  | 
131  | 813  | pub async fn call_with_permit<F, T>(f: F) -> Result<T, Error>  | 
132  | 813  | where  | 
133  | 813  |     F: FnOnce(SemaphorePermit<'static>) -> Result<T, Error> + Send + 'static,  | 
134  | 813  |     T: Send + 'static,  | 
135  | 813  | { | 
136  | 813  |     let permit = get_permit().await?0 ;  | 
137  | 813  |     spawn_blocking!("fs_call_with_permit", move || f(permit)) | 
138  | 813  |         .await  | 
139  | 811  |         .unwrap_or_else(|e| Err(make_err!0 (Code::Internal0 , "background task failed: {e:?}")))  | 
140  | 811  | }  | 
141  |  |  | 
142  |  | /// Sets the soft nofile limit to `desired_open_file_limit` and adjusts  | 
143  |  | /// `OPEN_FILE_SEMAPHORE` accordingly.  | 
144  |  | ///  | 
145  |  | /// # Panics  | 
146  |  | ///  | 
147  |  | /// If any type conversion fails. This can't happen if `usize` is smaller than  | 
148  |  | /// `u64`.  | 
149  | 0  | pub fn set_open_file_limit(desired_open_file_limit: usize) { | 
150  | 0  |     let new_open_file_limit = { | 
151  | 0  |         match increase_nofile_limit(  | 
152  | 0  |             u64::try_from(desired_open_file_limit)  | 
153  | 0  |                 .expect("desired_open_file_limit is too large to convert to u64."), | 
154  | 0  |         ) { | 
155  | 0  |             Ok(open_file_limit) => { | 
156  | 0  |                 info!("set_open_file_limit() assigns new open file limit {open_file_limit}.",); | 
157  | 0  |                 usize::try_from(open_file_limit)  | 
158  | 0  |                     .expect("open_file_limit is too large to convert to usize.") | 
159  |  |             }  | 
160  | 0  |             Err(e) => { | 
161  | 0  |                 error!(  | 
162  | 0  |                     "set_open_file_limit() failed to assign open file limit. Maybe system does not have ulimits, continuing anyway. - {e:?}", | 
163  |  |                 );  | 
164  | 0  |                 DEFAULT_OPEN_FILE_LIMIT  | 
165  |  |             }  | 
166  |  |         }  | 
167  |  |     };  | 
168  |  |     // TODO(jaroeichler): Can we give a better estimate?  | 
169  | 0  |     if new_open_file_limit < DEFAULT_OPEN_FILE_LIMIT {  Branch (169:8): [True: 0, False: 0]
   Branch (169:8): [Folded - Ignored]
  | 
170  | 0  |         warn!(  | 
171  | 0  |             "The new open file limit ({new_open_file_limit}) is below the recommended value of {DEFAULT_OPEN_FILE_LIMIT}. Consider raising max_open_files.", | 
172  |  |         );  | 
173  | 0  |     }  | 
174  |  |  | 
175  |  |     // Use only 80% of the open file limit for permits from OPEN_FILE_SEMAPHORE  | 
176  |  |     // to give extra room for other file descriptors like sockets, pipes, and  | 
177  |  |     // other things.  | 
178  | 0  |     let reduced_open_file_limit = new_open_file_limit.saturating_sub(new_open_file_limit / 5);  | 
179  | 0  |     let previous_open_file_limit = OPEN_FILE_LIMIT.load(Ordering::Acquire);  | 
180  |  |     // No permit should be acquired yet, so this warning should not occur.  | 
181  | 0  |     if (OPEN_FILE_SEMAPHORE.available_permits() + reduced_open_file_limit)   Branch (181:8): [True: 0, False: 0]
   Branch (181:8): [Folded - Ignored]
  | 
182  | 0  |         < previous_open_file_limit  | 
183  |  |     { | 
184  | 0  |         warn!(  | 
185  | 0  |             "There are not enough available permits to remove {previous_open_file_limit} - {reduced_open_file_limit} permits.", | 
186  |  |         );  | 
187  | 0  |     }  | 
188  | 0  |     if previous_open_file_limit <= reduced_open_file_limit {  Branch (188:8): [True: 0, False: 0]
   Branch (188:8): [Folded - Ignored]
  | 
189  | 0  |         OPEN_FILE_LIMIT.fetch_add(  | 
190  | 0  |             reduced_open_file_limit - previous_open_file_limit,  | 
191  | 0  |             Ordering::Release,  | 
192  | 0  |         );  | 
193  | 0  |         OPEN_FILE_SEMAPHORE.add_permits(reduced_open_file_limit - previous_open_file_limit);  | 
194  | 0  |     } else { | 
195  | 0  |         OPEN_FILE_LIMIT.fetch_sub(  | 
196  | 0  |             previous_open_file_limit - reduced_open_file_limit,  | 
197  | 0  |             Ordering::Release,  | 
198  | 0  |         );  | 
199  | 0  |         OPEN_FILE_SEMAPHORE.forget_permits(previous_open_file_limit - reduced_open_file_limit);  | 
200  | 0  |     }  | 
201  | 0  | }  | 
202  |  |  | 
203  | 1  | pub fn get_open_files_for_test() -> usize { | 
204  | 1  |     OPEN_FILE_LIMIT.load(Ordering::Acquire) - OPEN_FILE_SEMAPHORE.available_permits()  | 
205  | 1  | }  | 
206  |  |  | 
207  | 64  | pub async fn open_file(  | 
208  | 64  |     path: impl AsRef<Path>,  | 
209  | 64  |     start: u64,  | 
210  | 64  |     limit: u64,  | 
211  | 64  | ) -> Result<Take<FileSlot>, Error> { | 
212  | 64  |     let path = path.as_ref().to_owned();  | 
213  | 64  |     let (permit62 , os_file62 ) = call_with_permit(move |permit| {  | 
214  | 62  |         let mut os_file =  | 
215  | 64  |             std::fs::File::open(&path).err_tip(|| format!("Could not open {}"2 , path.display()2 ))?2 ;  | 
216  | 62  |         if start > 0 {  Branch (216:12): [True: 0, False: 0]
   Branch (216:12): [Folded - Ignored]
  Branch (216:12): [True: 0, False: 1]
  Branch (216:12): [True: 0, False: 2]
  Branch (216:12): [True: 0, False: 4]
  Branch (216:12): [True: 0, False: 6]
   Branch (216:12): [True: 0, False: 0]
   Branch (216:12): [True: 0, False: 1]
   Branch (216:12): [Folded - Ignored]
   Branch (216:12): [True: 0, False: 0]
  Branch (216:12): [True: 0, False: 41]
  Branch (216:12): [True: 0, False: 3]
  Branch (216:12): [True: 0, False: 4]
  | 
217  | 0  |             os_file  | 
218  | 0  |                 .seek(SeekFrom::Start(start))  | 
219  | 0  |                 .err_tip(|| format!("Could not seek to {start} in {}", path.display()))?; | 
220  | 62  |         }  | 
221  | 62  |         Ok((permit, os_file))  | 
222  | 64  |     })  | 
223  | 64  |     .await?2 ;  | 
224  | 62  |     Ok(FileSlot { | 
225  | 62  |         _permit: permit,  | 
226  | 62  |         inner: tokio::fs::File::from_std(os_file),  | 
227  | 62  |     }  | 
228  | 62  |     .take(limit))  | 
229  | 64  | }  | 
230  |  |  | 
231  | 122  | pub async fn create_file(path: impl AsRef<Path>) -> Result<FileSlot, Error> { | 
232  | 122  |     let path = path.as_ref().to_owned();  | 
233  | 122  |     let (permit, os_file) = call_with_permit(move |permit| { | 
234  |  |         Ok((  | 
235  | 122  |             permit,  | 
236  | 122  |             std::fs::File::options()  | 
237  | 122  |                 .read(true)  | 
238  | 122  |                 .write(true)  | 
239  | 122  |                 .create(true)  | 
240  | 122  |                 .truncate(true)  | 
241  | 122  |                 .open(&path)  | 
242  | 122  |                 .err_tip(|| format!("Could not open {}"0 , path.display()0 ))?0 ,  | 
243  |  |         ))  | 
244  | 122  |     })  | 
245  | 122  |     .await?0 ;  | 
246  | 122  |     Ok(FileSlot { | 
247  | 122  |         _permit: permit,  | 
248  | 122  |         inner: tokio::fs::File::from_std(os_file),  | 
249  | 122  |     })  | 
250  | 122  | }  | 
251  |  |  | 
252  | 4  | pub async fn hard_link(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Error> { | 
253  | 4  |     let src = src.as_ref().to_owned();  | 
254  | 4  |     let dst = dst.as_ref().to_owned();  | 
255  | 4  |     call_with_permit(move |_| std::fs::hard_link(src, dst).map_err(Into::<Error>::into)).await  | 
256  | 4  | }  | 
257  |  |  | 
258  | 1  | pub async fn set_permissions(src: impl AsRef<Path>, perm: Permissions) -> Result<(), Error> { | 
259  | 1  |     let src = src.as_ref().to_owned();  | 
260  | 1  |     call_with_permit(move |_| std::fs::set_permissions(src, perm).map_err(Into::<Error>::into))  | 
261  | 1  |         .await  | 
262  | 1  | }  | 
263  |  |  | 
264  | 41  | pub async fn create_dir(path: impl AsRef<Path>) -> Result<(), Error> { | 
265  | 41  |     let path = path.as_ref().to_owned();  | 
266  | 41  |     call_with_permit(move |_| std::fs::create_dir(path).map_err(Into::<Error>::into)).await  | 
267  | 41  | }  | 
268  |  |  | 
269  | 225  | pub async fn create_dir_all(path: impl AsRef<Path>) -> Result<(), Error> { | 
270  | 225  |     let path = path.as_ref().to_owned();  | 
271  | 225  |     call_with_permit(move |_| std::fs::create_dir_all(path).map_err(Into::<Error>::into)).await  | 
272  | 225  | }  | 
273  |  |  | 
274  |  | #[cfg(target_family = "unix")]  | 
275  | 1  | pub async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Error> { | 
276  | 1  |     let src = src.as_ref().to_owned();  | 
277  | 1  |     let dst = dst.as_ref().to_owned();  | 
278  | 1  |     call_with_permit(move |_| { | 
279  | 1  |         tokio::runtime::Handle::current()  | 
280  | 1  |             .block_on(tokio::fs::symlink(src, dst))  | 
281  | 1  |             .map_err(Into::<Error>::into)  | 
282  | 1  |     })  | 
283  | 1  |     .await  | 
284  | 1  | }  | 
285  |  |  | 
286  | 2  | pub async fn read_link(path: impl AsRef<Path>) -> Result<PathBuf, Error> { | 
287  | 2  |     let path = path.as_ref().to_owned();  | 
288  | 2  |     call_with_permit(move |_| std::fs::read_link(path).map_err(Into::<Error>::into)).await  | 
289  | 2  | }  | 
290  |  |  | 
291  |  | #[derive(Debug)]  | 
292  |  | pub struct ReadDir { | 
293  |  |     // We hold the permit because once it is dropped it goes back into the queue.  | 
294  |  |     permit: SemaphorePermit<'static>,  | 
295  |  |     inner: tokio::fs::ReadDir,  | 
296  |  | }  | 
297  |  |  | 
298  |  | impl ReadDir { | 
299  | 250  |     pub fn into_inner(self) -> (SemaphorePermit<'static>, tokio::fs::ReadDir) { | 
300  | 250  |         (self.permit, self.inner)  | 
301  | 250  |     }  | 
302  |  | }  | 
303  |  |  | 
304  |  | impl AsRef<tokio::fs::ReadDir> for ReadDir { | 
305  | 0  |     fn as_ref(&self) -> &tokio::fs::ReadDir { | 
306  | 0  |         &self.inner  | 
307  | 0  |     }  | 
308  |  | }  | 
309  |  |  | 
310  |  | impl AsMut<tokio::fs::ReadDir> for ReadDir { | 
311  | 0  |     fn as_mut(&mut self) -> &mut tokio::fs::ReadDir { | 
312  | 0  |         &mut self.inner  | 
313  | 0  |     }  | 
314  |  | }  | 
315  |  |  | 
316  | 252  | pub async fn read_dir(path: impl AsRef<Path>) -> Result<ReadDir, Error> { | 
317  | 252  |     let path = path.as_ref().to_owned();  | 
318  | 252  |     let (permit, inner) = call_with_permit(move |permit| { | 
319  |  |         Ok((  | 
320  | 252  |             permit,  | 
321  | 252  |             tokio::runtime::Handle::current()  | 
322  | 252  |                 .block_on(tokio::fs::read_dir(path))  | 
323  | 252  |                 .map_err(Into::<Error>::into)?0 ,  | 
324  |  |         ))  | 
325  | 252  |     })  | 
326  | 252  |     .await?0 ;  | 
327  | 252  |     Ok(ReadDir { permit, inner }) | 
328  | 252  | }  | 
329  |  |  | 
330  | 25  | pub async fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<(), Error> { | 
331  | 25  |     let from = from.as_ref().to_owned();  | 
332  | 25  |     let to = to.as_ref().to_owned();  | 
333  | 25  |     call_with_permit(move |_| std::fs::rename(from, to).map_err(Into::<Error>::into)).await  | 
334  | 25  | }  | 
335  |  |  | 
336  | 23  | pub async fn remove_file(path: impl AsRef<Path>) -> Result<(), Error> { | 
337  | 23  |     let path = path.as_ref().to_owned();  | 
338  | 23  |     call_with_permit(move |_| std::fs::remove_file(path).map_err(Into::<Error>::into)).await  | 
339  | 21  | }  | 
340  |  |  | 
341  | 2  | pub async fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf, Error> { | 
342  | 2  |     let path = path.as_ref().to_owned();  | 
343  | 2  |     call_with_permit(move |_| std::fs::canonicalize(path).map_err(Into::<Error>::into)).await  | 
344  | 2  | }  | 
345  |  |  | 
346  | 16  | pub async fn metadata(path: impl AsRef<Path>) -> Result<Metadata, Error> { | 
347  | 16  |     let path = path.as_ref().to_owned();  | 
348  | 16  |     call_with_permit(move |_| std::fs::metadata(path).map_err(Into::<Error>::into)).await  | 
349  | 16  | }  | 
350  |  |  | 
351  | 4  | pub async fn read(path: impl AsRef<Path>) -> Result<Vec<u8>, Error> { | 
352  | 4  |     let path = path.as_ref().to_owned();  | 
353  | 4  |     call_with_permit(move |_| std::fs::read(path).map_err(Into::<Error>::into)).await  | 
354  | 4  | }  | 
355  |  |  | 
356  | 8  | pub async fn symlink_metadata(path: impl AsRef<Path>) -> Result<Metadata, Error> { | 
357  | 8  |     let path = path.as_ref().to_owned();  | 
358  | 8  |     call_with_permit(move |_| std::fs::symlink_metadata(path).map_err(Into::<Error>::into)).await  | 
359  | 8  | }  | 
360  |  |  | 
361  |  | // We can't just use the stock remove_dir_all as it falls over if someone's set readonly  | 
362  |  | // permissions. This version walks the directories and fixes the permissions where needed  | 
363  |  | // before deleting everything.  | 
364  |  | #[cfg(not(target_family = "windows"))]  | 
365  | 23  | fn internal_remove_dir_all(path: impl AsRef<Path>) -> Result<(), Error> { | 
366  |  |     // Because otherwise Windows builds complain about these things not being used  | 
367  |  |     use std::io::ErrorKind;  | 
368  |  |     use std::os::unix::fs::PermissionsExt;  | 
369  |  |  | 
370  |  |     use tracing::debug;  | 
371  |  |     use walkdir::WalkDir;  | 
372  |  |  | 
373  | 24  |     for entry in WalkDir::new23 (&path23 ) {  | 
374  | 24  |         let Ok(entry23 ) = &entry else {   Branch (374:13): [Folded - Ignored]
   Branch (374:13): [True: 1, False: 0]
   Branch (374:13): [True: 1, False: 1]
   Branch (374:13): [True: 1, False: 0]
   Branch (374:13): [True: 0, False: 0]
   Branch (374:13): [Folded - Ignored]
   Branch (374:13): [True: 20, False: 0]
  | 
375  | 1  |             debug!("Can't get into {entry:?}, assuming already deleted"); | 
376  | 1  |             continue;  | 
377  |  |         };  | 
378  | 23  |         let metadata = entry.metadata()?0 ;  | 
379  | 23  |         if metadata.is_dir() {  Branch (379:12): [Folded - Ignored]
   Branch (379:12): [True: 1, False: 0]
   Branch (379:12): [True: 1, False: 0]
   Branch (379:12): [True: 1, False: 0]
   Branch (379:12): [True: 0, False: 0]
   Branch (379:12): [Folded - Ignored]
   Branch (379:12): [True: 20, False: 0]
  | 
380  | 23  |             match std::fs::remove_dir_all(entry.path()) { | 
381  | 22  |                 Ok(()) => {} | 
382  | 1  |                 Err(e) if e.kind() == ErrorKind::PermissionDenied => {  Branch (382:27): [Folded - Ignored]
   Branch (382:27): [True: 0, False: 0]
   Branch (382:27): [True: 1, False: 0]
   Branch (382:27): [True: 0, False: 0]
   Branch (382:27): [True: 0, False: 0]
   Branch (382:27): [Folded - Ignored]
   Branch (382:27): [True: 0, False: 0]
  | 
383  | 1  |                     std::fs::set_permissions(entry.path(), Permissions::from_mode(0o700)).err_tip(  | 
384  | 0  |                         || format!("Setting permissions for {}", entry.path().display()), | 
385  | 0  |                     )?;  | 
386  |  |                 }  | 
387  | 0  |                 e @ Err(_) => e.err_tip(|| format!("Removing {}", entry.path().display()))?, | 
388  |  |             }  | 
389  | 0  |         } else if metadata.is_file() {  Branch (389:19): [Folded - Ignored]
   Branch (389:19): [True: 0, False: 0]
   Branch (389:19): [True: 0, False: 0]
   Branch (389:19): [True: 0, False: 0]
   Branch (389:19): [True: 0, False: 0]
   Branch (389:19): [Folded - Ignored]
   Branch (389:19): [True: 0, False: 0]
  | 
390  | 0  |             std::fs::set_permissions(entry.path(), Permissions::from_mode(0o600))  | 
391  | 0  |                 .err_tip(|| format!("Setting permissions for {}", entry.path().display()))?; | 
392  | 0  |         }  | 
393  |  |     }  | 
394  |  |  | 
395  |  |     // should now be safe to delete after we fixed all the permissions in the walk loop  | 
396  | 23  |     match std::fs::remove_dir_all(&path) { | 
397  | 1  |         Ok(()) => {} | 
398  | 22  |         Err(e) if e.kind() == ErrorKind::NotFound => {}  Branch (398:19): [Folded - Ignored]
   Branch (398:19): [True: 1, False: 0]
   Branch (398:19): [True: 0, False: 0]
   Branch (398:19): [True: 1, False: 0]
   Branch (398:19): [True: 0, False: 0]
   Branch (398:19): [Folded - Ignored]
   Branch (398:19): [True: 20, False: 0]
  | 
399  | 0  |         e @ Err(_) => e.err_tip(|| { | 
400  | 0  |             format!(  | 
401  | 0  |                 "Removing {} after permissions fixes", | 
402  | 0  |                 path.as_ref().display()  | 
403  |  |             )  | 
404  | 0  |         })?,  | 
405  |  |     }  | 
406  | 23  |     Ok(())  | 
407  | 23  | }  | 
408  |  |  | 
409  |  | // We can't set the permissions easily in Windows, so just fallback to  | 
410  |  | // the stock Rust remove_dir_all  | 
411  |  | #[cfg(target_family = "windows")]  | 
412  |  | fn internal_remove_dir_all(path: impl AsRef<Path>) -> Result<(), Error> { | 
413  |  |     std::fs::remove_dir_all(&path)?;  | 
414  |  |     Ok(())  | 
415  |  | }  | 
416  |  |  | 
417  | 23  | pub async fn remove_dir_all(path: impl AsRef<Path>) -> Result<(), Error> { | 
418  | 23  |     let path = path.as_ref().to_owned();  | 
419  | 23  |     call_with_permit(move |_| internal_remove_dir_all(path)).await  | 
420  | 23  | }  |