##// END OF EJS Templates
rust-vfs: add docstrings to all VFS methods on the trait
Raphaël Gomès -
r53079:85bff84f default
parent child Browse files
Show More
@@ -1,1101 +1,1131
1 use crate::errors::{HgError, HgResultExt, IoErrorContext, IoResultExt};
1 use crate::errors::{HgError, HgResultExt, IoErrorContext, IoResultExt};
2 use crate::exit_codes;
2 use crate::exit_codes;
3 use crate::fncache::FnCache;
3 use crate::fncache::FnCache;
4 use crate::revlog::path_encode::path_encode;
4 use crate::revlog::path_encode::path_encode;
5 use crate::utils::files::{get_bytes_from_path, get_path_from_bytes};
5 use crate::utils::files::{get_bytes_from_path, get_path_from_bytes};
6 use dyn_clone::DynClone;
6 use dyn_clone::DynClone;
7 use format_bytes::format_bytes;
7 use format_bytes::format_bytes;
8 use memmap2::{Mmap, MmapOptions};
8 use memmap2::{Mmap, MmapOptions};
9 use rand::distributions::{Alphanumeric, DistString};
9 use rand::distributions::{Alphanumeric, DistString};
10 use std::fs::{File, Metadata, OpenOptions};
10 use std::fs::{File, Metadata, OpenOptions};
11 use std::io::{ErrorKind, Read, Seek, Write};
11 use std::io::{ErrorKind, Read, Seek, Write};
12 use std::os::fd::AsRawFd;
12 use std::os::fd::AsRawFd;
13 use std::os::unix::fs::{MetadataExt, PermissionsExt};
13 use std::os::unix::fs::{MetadataExt, PermissionsExt};
14 use std::path::{Path, PathBuf};
14 use std::path::{Path, PathBuf};
15 #[cfg(test)]
15 #[cfg(test)]
16 use std::sync::atomic::AtomicUsize;
16 use std::sync::atomic::AtomicUsize;
17 #[cfg(test)]
17 #[cfg(test)]
18 use std::sync::atomic::Ordering;
18 use std::sync::atomic::Ordering;
19 use std::sync::OnceLock;
19 use std::sync::OnceLock;
20
20
21 /// Filesystem access abstraction for the contents of a given "base" diretory
21 /// Filesystem access abstraction for the contents of a given "base" diretory
22 #[derive(Clone)]
22 #[derive(Clone)]
23 pub struct VfsImpl {
23 pub struct VfsImpl {
24 pub(crate) base: PathBuf,
24 pub(crate) base: PathBuf,
25 pub readonly: bool,
25 pub readonly: bool,
26 pub mode: Option<u32>,
26 pub mode: Option<u32>,
27 }
27 }
28
28
29 struct FileNotFound(std::io::Error, PathBuf);
29 struct FileNotFound(std::io::Error, PathBuf);
30
30
31 /// Store the umask for the whole process since it's expensive to get.
31 /// Store the umask for the whole process since it's expensive to get.
32 static UMASK: OnceLock<u32> = OnceLock::new();
32 static UMASK: OnceLock<u32> = OnceLock::new();
33
33
34 fn get_umask() -> u32 {
34 fn get_umask() -> u32 {
35 *UMASK.get_or_init(|| unsafe {
35 *UMASK.get_or_init(|| unsafe {
36 // TODO is there any way of getting the umask without temporarily
36 // TODO is there any way of getting the umask without temporarily
37 // setting it? Doesn't this affect all threads in this tiny window?
37 // setting it? Doesn't this affect all threads in this tiny window?
38 let mask = libc::umask(0);
38 let mask = libc::umask(0);
39 libc::umask(mask);
39 libc::umask(mask);
40 mask & 0o777
40 mask & 0o777
41 })
41 })
42 }
42 }
43
43
44 /// Return the (unix) mode with which we will create/fix files
44 /// Return the (unix) mode with which we will create/fix files
45 fn get_mode(base: impl AsRef<Path>) -> Option<u32> {
45 fn get_mode(base: impl AsRef<Path>) -> Option<u32> {
46 match base.as_ref().metadata() {
46 match base.as_ref().metadata() {
47 Ok(meta) => {
47 Ok(meta) => {
48 // files in .hg/ will be created using this mode
48 // files in .hg/ will be created using this mode
49 let mode = meta.mode();
49 let mode = meta.mode();
50 // avoid some useless chmods
50 // avoid some useless chmods
51 if (0o777 & !get_umask()) == (0o777 & mode) {
51 if (0o777 & !get_umask()) == (0o777 & mode) {
52 None
52 None
53 } else {
53 } else {
54 Some(mode)
54 Some(mode)
55 }
55 }
56 }
56 }
57 Err(_) => None,
57 Err(_) => None,
58 }
58 }
59 }
59 }
60
60
61 impl VfsImpl {
61 impl VfsImpl {
62 pub fn new(base: PathBuf, readonly: bool) -> Self {
62 pub fn new(base: PathBuf, readonly: bool) -> Self {
63 let mode = get_mode(&base);
63 let mode = get_mode(&base);
64 Self {
64 Self {
65 base,
65 base,
66 readonly,
66 readonly,
67 mode,
67 mode,
68 }
68 }
69 }
69 }
70
70
71 // XXX these methods are probably redundant with VFS trait?
71 // XXX these methods are probably redundant with VFS trait?
72
72
73 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
73 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
74 self.base.join(relative_path)
74 self.base.join(relative_path)
75 }
75 }
76
76
77 pub fn symlink_metadata(
77 pub fn symlink_metadata(
78 &self,
78 &self,
79 relative_path: impl AsRef<Path>,
79 relative_path: impl AsRef<Path>,
80 ) -> Result<std::fs::Metadata, HgError> {
80 ) -> Result<std::fs::Metadata, HgError> {
81 let path = self.join(relative_path);
81 let path = self.join(relative_path);
82 std::fs::symlink_metadata(&path).when_reading_file(&path)
82 std::fs::symlink_metadata(&path).when_reading_file(&path)
83 }
83 }
84
84
85 pub fn read_link(
85 pub fn read_link(
86 &self,
86 &self,
87 relative_path: impl AsRef<Path>,
87 relative_path: impl AsRef<Path>,
88 ) -> Result<PathBuf, HgError> {
88 ) -> Result<PathBuf, HgError> {
89 let path = self.join(relative_path);
89 let path = self.join(relative_path);
90 std::fs::read_link(&path).when_reading_file(&path)
90 std::fs::read_link(&path).when_reading_file(&path)
91 }
91 }
92
92
93 pub fn read(
93 pub fn read(
94 &self,
94 &self,
95 relative_path: impl AsRef<Path>,
95 relative_path: impl AsRef<Path>,
96 ) -> Result<Vec<u8>, HgError> {
96 ) -> Result<Vec<u8>, HgError> {
97 let path = self.join(relative_path);
97 let path = self.join(relative_path);
98 std::fs::read(&path).when_reading_file(&path)
98 std::fs::read(&path).when_reading_file(&path)
99 }
99 }
100
100
101 /// Returns `Ok(None)` if the file does not exist.
101 /// Returns `Ok(None)` if the file does not exist.
102 pub fn try_read(
102 pub fn try_read(
103 &self,
103 &self,
104 relative_path: impl AsRef<Path>,
104 relative_path: impl AsRef<Path>,
105 ) -> Result<Option<Vec<u8>>, HgError> {
105 ) -> Result<Option<Vec<u8>>, HgError> {
106 match self.read(relative_path) {
106 match self.read(relative_path) {
107 Err(e) => match &e {
107 Err(e) => match &e {
108 HgError::IoError { error, .. } => match error.kind() {
108 HgError::IoError { error, .. } => match error.kind() {
109 ErrorKind::NotFound => Ok(None),
109 ErrorKind::NotFound => Ok(None),
110 _ => Err(e),
110 _ => Err(e),
111 },
111 },
112 _ => Err(e),
112 _ => Err(e),
113 },
113 },
114 Ok(v) => Ok(Some(v)),
114 Ok(v) => Ok(Some(v)),
115 }
115 }
116 }
116 }
117
117
118 fn mmap_open_gen(
118 fn mmap_open_gen(
119 &self,
119 &self,
120 relative_path: impl AsRef<Path>,
120 relative_path: impl AsRef<Path>,
121 ) -> Result<Result<Mmap, FileNotFound>, HgError> {
121 ) -> Result<Result<Mmap, FileNotFound>, HgError> {
122 let path = self.join(relative_path);
122 let path = self.join(relative_path);
123 let file = match std::fs::File::open(&path) {
123 let file = match std::fs::File::open(&path) {
124 Err(err) => {
124 Err(err) => {
125 if let ErrorKind::NotFound = err.kind() {
125 if let ErrorKind::NotFound = err.kind() {
126 return Ok(Err(FileNotFound(err, path)));
126 return Ok(Err(FileNotFound(err, path)));
127 };
127 };
128 return (Err(err)).when_reading_file(&path);
128 return (Err(err)).when_reading_file(&path);
129 }
129 }
130 Ok(file) => file,
130 Ok(file) => file,
131 };
131 };
132 // Safety is "enforced" by locks and assuming other processes are
132 // Safety is "enforced" by locks and assuming other processes are
133 // well-behaved. If any misbehaving or malicious process does touch
133 // well-behaved. If any misbehaving or malicious process does touch
134 // the index, it could lead to corruption. This is inherent
134 // the index, it could lead to corruption. This is inherent
135 // to file-based `mmap`, though some platforms have some ways of
135 // to file-based `mmap`, though some platforms have some ways of
136 // mitigating.
136 // mitigating.
137 // TODO linux: set the immutable flag with `chattr(1)`?
137 // TODO linux: set the immutable flag with `chattr(1)`?
138 let mmap = unsafe { MmapOptions::new().map(&file) }
138 let mmap = unsafe { MmapOptions::new().map(&file) }
139 .when_reading_file(&path)?;
139 .when_reading_file(&path)?;
140 Ok(Ok(mmap))
140 Ok(Ok(mmap))
141 }
141 }
142
142
143 pub fn mmap_open_opt(
143 pub fn mmap_open_opt(
144 &self,
144 &self,
145 relative_path: impl AsRef<Path>,
145 relative_path: impl AsRef<Path>,
146 ) -> Result<Option<Mmap>, HgError> {
146 ) -> Result<Option<Mmap>, HgError> {
147 self.mmap_open_gen(relative_path).map(|res| res.ok())
147 self.mmap_open_gen(relative_path).map(|res| res.ok())
148 }
148 }
149
149
150 pub fn mmap_open(
150 pub fn mmap_open(
151 &self,
151 &self,
152 relative_path: impl AsRef<Path>,
152 relative_path: impl AsRef<Path>,
153 ) -> Result<Mmap, HgError> {
153 ) -> Result<Mmap, HgError> {
154 match self.mmap_open_gen(relative_path)? {
154 match self.mmap_open_gen(relative_path)? {
155 Err(FileNotFound(err, path)) => Err(err).when_reading_file(&path),
155 Err(FileNotFound(err, path)) => Err(err).when_reading_file(&path),
156 Ok(res) => Ok(res),
156 Ok(res) => Ok(res),
157 }
157 }
158 }
158 }
159
159
160 #[cfg(unix)]
160 #[cfg(unix)]
161 pub fn create_symlink(
161 pub fn create_symlink(
162 &self,
162 &self,
163 relative_link_path: impl AsRef<Path>,
163 relative_link_path: impl AsRef<Path>,
164 target_path: impl AsRef<Path>,
164 target_path: impl AsRef<Path>,
165 ) -> Result<(), HgError> {
165 ) -> Result<(), HgError> {
166 let link_path = self.join(relative_link_path);
166 let link_path = self.join(relative_link_path);
167 std::os::unix::fs::symlink(target_path, &link_path)
167 std::os::unix::fs::symlink(target_path, &link_path)
168 .when_writing_file(&link_path)
168 .when_writing_file(&link_path)
169 }
169 }
170
170
171 /// Write `contents` into a temporary file, then rename to `relative_path`.
171 /// Write `contents` into a temporary file, then rename to `relative_path`.
172 /// This makes writing to a file "atomic": a reader opening that path will
172 /// This makes writing to a file "atomic": a reader opening that path will
173 /// see either the previous contents of the file or the complete new
173 /// see either the previous contents of the file or the complete new
174 /// content, never a partial write.
174 /// content, never a partial write.
175 pub fn atomic_write(
175 pub fn atomic_write(
176 &self,
176 &self,
177 relative_path: impl AsRef<Path>,
177 relative_path: impl AsRef<Path>,
178 contents: &[u8],
178 contents: &[u8],
179 ) -> Result<(), HgError> {
179 ) -> Result<(), HgError> {
180 let mut tmp = tempfile::NamedTempFile::new_in(&self.base)
180 let mut tmp = tempfile::NamedTempFile::new_in(&self.base)
181 .when_writing_file(&self.base)?;
181 .when_writing_file(&self.base)?;
182 tmp.write_all(contents)
182 tmp.write_all(contents)
183 .and_then(|()| tmp.flush())
183 .and_then(|()| tmp.flush())
184 .when_writing_file(tmp.path())?;
184 .when_writing_file(tmp.path())?;
185 let path = self.join(relative_path);
185 let path = self.join(relative_path);
186 tmp.persist(&path)
186 tmp.persist(&path)
187 .map_err(|e| e.error)
187 .map_err(|e| e.error)
188 .when_writing_file(&path)?;
188 .when_writing_file(&path)?;
189 Ok(())
189 Ok(())
190 }
190 }
191 }
191 }
192
192
193 fn fs_metadata(
193 fn fs_metadata(
194 path: impl AsRef<Path>,
194 path: impl AsRef<Path>,
195 ) -> Result<Option<std::fs::Metadata>, HgError> {
195 ) -> Result<Option<std::fs::Metadata>, HgError> {
196 let path = path.as_ref();
196 let path = path.as_ref();
197 match path.metadata() {
197 match path.metadata() {
198 Ok(meta) => Ok(Some(meta)),
198 Ok(meta) => Ok(Some(meta)),
199 Err(error) => match error.kind() {
199 Err(error) => match error.kind() {
200 // TODO: when we require a Rust version where `NotADirectory` is
200 // TODO: when we require a Rust version where `NotADirectory` is
201 // stable, invert this logic and return None for it and `NotFound`
201 // stable, invert this logic and return None for it and `NotFound`
202 // and propagate any other error.
202 // and propagate any other error.
203 ErrorKind::PermissionDenied => Err(error).with_context(|| {
203 ErrorKind::PermissionDenied => Err(error).with_context(|| {
204 IoErrorContext::ReadingMetadata(path.to_owned())
204 IoErrorContext::ReadingMetadata(path.to_owned())
205 }),
205 }),
206 _ => Ok(None),
206 _ => Ok(None),
207 },
207 },
208 }
208 }
209 }
209 }
210
210
211 /// Abstraction over the files handled by a [`Vfs`].
211 /// Abstraction over the files handled by a [`Vfs`].
212 #[derive(Debug)]
212 #[derive(Debug)]
213 pub enum VfsFile {
213 pub enum VfsFile {
214 Atomic(AtomicFile),
214 Atomic(AtomicFile),
215
215
216 Normal {
216 Normal {
217 file: File,
217 file: File,
218 path: PathBuf,
218 path: PathBuf,
219 /// If `Some`, check (and maybe fix) this file's timestamp ambiguity.
219 /// If `Some`, check (and maybe fix) this file's timestamp ambiguity.
220 /// See [`is_filetime_ambiguous`].
220 /// See [`is_filetime_ambiguous`].
221 check_ambig: Option<Metadata>,
221 check_ambig: Option<Metadata>,
222 },
222 },
223 }
223 }
224
224
225 impl VfsFile {
225 impl VfsFile {
226 pub fn normal(file: File, path: PathBuf) -> Self {
226 pub fn normal(file: File, path: PathBuf) -> Self {
227 Self::Normal {
227 Self::Normal {
228 file,
228 file,
229 check_ambig: None,
229 check_ambig: None,
230 path,
230 path,
231 }
231 }
232 }
232 }
233 pub fn normal_check_ambig(
233 pub fn normal_check_ambig(
234 file: File,
234 file: File,
235 path: PathBuf,
235 path: PathBuf,
236 ) -> Result<Self, HgError> {
236 ) -> Result<Self, HgError> {
237 Ok(Self::Normal {
237 Ok(Self::Normal {
238 file,
238 file,
239 check_ambig: Some(path.metadata().when_reading_file(&path)?),
239 check_ambig: Some(path.metadata().when_reading_file(&path)?),
240 path,
240 path,
241 })
241 })
242 }
242 }
243 pub fn try_clone(&self) -> Result<VfsFile, HgError> {
243 pub fn try_clone(&self) -> Result<VfsFile, HgError> {
244 Ok(match self {
244 Ok(match self {
245 VfsFile::Atomic(AtomicFile {
245 VfsFile::Atomic(AtomicFile {
246 fp,
246 fp,
247 temp_path,
247 temp_path,
248 check_ambig,
248 check_ambig,
249 target_name,
249 target_name,
250 is_open,
250 is_open,
251 }) => Self::Atomic(AtomicFile {
251 }) => Self::Atomic(AtomicFile {
252 fp: fp.try_clone().when_reading_file(temp_path)?,
252 fp: fp.try_clone().when_reading_file(temp_path)?,
253 temp_path: temp_path.clone(),
253 temp_path: temp_path.clone(),
254 check_ambig: *check_ambig,
254 check_ambig: *check_ambig,
255 target_name: target_name.clone(),
255 target_name: target_name.clone(),
256 is_open: *is_open,
256 is_open: *is_open,
257 }),
257 }),
258 VfsFile::Normal {
258 VfsFile::Normal {
259 file,
259 file,
260 check_ambig,
260 check_ambig,
261 path,
261 path,
262 } => Self::Normal {
262 } => Self::Normal {
263 file: file.try_clone().when_reading_file(path)?,
263 file: file.try_clone().when_reading_file(path)?,
264 check_ambig: check_ambig.clone(),
264 check_ambig: check_ambig.clone(),
265 path: path.to_owned(),
265 path: path.to_owned(),
266 },
266 },
267 })
267 })
268 }
268 }
269 pub fn set_len(&self, len: u64) -> Result<(), std::io::Error> {
269 pub fn set_len(&self, len: u64) -> Result<(), std::io::Error> {
270 match self {
270 match self {
271 VfsFile::Atomic(atomic_file) => atomic_file.fp.set_len(len),
271 VfsFile::Atomic(atomic_file) => atomic_file.fp.set_len(len),
272 VfsFile::Normal { file, .. } => file.set_len(len),
272 VfsFile::Normal { file, .. } => file.set_len(len),
273 }
273 }
274 }
274 }
275
275
276 pub fn metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
276 pub fn metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
277 match self {
277 match self {
278 VfsFile::Atomic(atomic_file) => atomic_file.fp.metadata(),
278 VfsFile::Atomic(atomic_file) => atomic_file.fp.metadata(),
279 VfsFile::Normal { file, .. } => file.metadata(),
279 VfsFile::Normal { file, .. } => file.metadata(),
280 }
280 }
281 }
281 }
282 }
282 }
283
283
284 impl AsRawFd for VfsFile {
284 impl AsRawFd for VfsFile {
285 fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd {
285 fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd {
286 match self {
286 match self {
287 VfsFile::Atomic(atomic_file) => atomic_file.fp.as_raw_fd(),
287 VfsFile::Atomic(atomic_file) => atomic_file.fp.as_raw_fd(),
288 VfsFile::Normal { file, .. } => file.as_raw_fd(),
288 VfsFile::Normal { file, .. } => file.as_raw_fd(),
289 }
289 }
290 }
290 }
291 }
291 }
292
292
293 impl Seek for VfsFile {
293 impl Seek for VfsFile {
294 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
294 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
295 match self {
295 match self {
296 VfsFile::Atomic(atomic_file) => atomic_file.seek(pos),
296 VfsFile::Atomic(atomic_file) => atomic_file.seek(pos),
297 VfsFile::Normal { file, .. } => file.seek(pos),
297 VfsFile::Normal { file, .. } => file.seek(pos),
298 }
298 }
299 }
299 }
300 }
300 }
301
301
302 impl Read for VfsFile {
302 impl Read for VfsFile {
303 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
303 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
304 match self {
304 match self {
305 VfsFile::Atomic(atomic_file) => atomic_file.fp.read(buf),
305 VfsFile::Atomic(atomic_file) => atomic_file.fp.read(buf),
306 VfsFile::Normal { file, .. } => file.read(buf),
306 VfsFile::Normal { file, .. } => file.read(buf),
307 }
307 }
308 }
308 }
309 }
309 }
310
310
311 impl Write for VfsFile {
311 impl Write for VfsFile {
312 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
312 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
313 match self {
313 match self {
314 VfsFile::Atomic(atomic_file) => atomic_file.fp.write(buf),
314 VfsFile::Atomic(atomic_file) => atomic_file.fp.write(buf),
315 VfsFile::Normal { file, .. } => file.write(buf),
315 VfsFile::Normal { file, .. } => file.write(buf),
316 }
316 }
317 }
317 }
318
318
319 fn flush(&mut self) -> std::io::Result<()> {
319 fn flush(&mut self) -> std::io::Result<()> {
320 match self {
320 match self {
321 VfsFile::Atomic(atomic_file) => atomic_file.fp.flush(),
321 VfsFile::Atomic(atomic_file) => atomic_file.fp.flush(),
322 VfsFile::Normal { file, .. } => file.flush(),
322 VfsFile::Normal { file, .. } => file.flush(),
323 }
323 }
324 }
324 }
325 }
325 }
326
326
327 impl Drop for VfsFile {
327 impl Drop for VfsFile {
328 fn drop(&mut self) {
328 fn drop(&mut self) {
329 if let VfsFile::Normal {
329 if let VfsFile::Normal {
330 path,
330 path,
331 check_ambig: Some(old),
331 check_ambig: Some(old),
332 ..
332 ..
333 } = self
333 } = self
334 {
334 {
335 avoid_timestamp_ambiguity(path, old)
335 avoid_timestamp_ambiguity(path, old)
336 }
336 }
337 }
337 }
338 }
338 }
339
339
340 /// Records the number of times we've fixed a timestamp ambiguity, only
340 /// Records the number of times we've fixed a timestamp ambiguity, only
341 /// applicable for tests.
341 /// applicable for tests.
342 #[cfg(test)]
342 #[cfg(test)]
343 static TIMESTAMP_FIXES_CALLS: AtomicUsize = AtomicUsize::new(0);
343 static TIMESTAMP_FIXES_CALLS: AtomicUsize = AtomicUsize::new(0);
344
344
345 fn avoid_timestamp_ambiguity(path: &Path, old: &Metadata) {
345 fn avoid_timestamp_ambiguity(path: &Path, old: &Metadata) {
346 if let Ok(new) = path.metadata() {
346 if let Ok(new) = path.metadata() {
347 let is_ambiguous = is_filetime_ambiguous(&new, old);
347 let is_ambiguous = is_filetime_ambiguous(&new, old);
348 if is_ambiguous {
348 if is_ambiguous {
349 let advanced =
349 let advanced =
350 filetime::FileTime::from_unix_time(old.mtime() + 1, 0);
350 filetime::FileTime::from_unix_time(old.mtime() + 1, 0);
351 if filetime::set_file_times(path, advanced, advanced).is_ok() {
351 if filetime::set_file_times(path, advanced, advanced).is_ok() {
352 #[cfg(test)]
352 #[cfg(test)]
353 {
353 {
354 TIMESTAMP_FIXES_CALLS.fetch_add(1, Ordering::Relaxed);
354 TIMESTAMP_FIXES_CALLS.fetch_add(1, Ordering::Relaxed);
355 }
355 }
356 }
356 }
357 }
357 }
358 }
358 }
359 }
359 }
360
360
361 /// Examine whether new stat is ambiguous against old one
361 /// Examine whether new stat is ambiguous against old one
362 ///
362 ///
363 /// "S[N]" below means stat of a file at N-th change:
363 /// "S[N]" below means stat of a file at N-th change:
364 ///
364 ///
365 /// - S[n-1].ctime < S[n].ctime: can detect change of a file
365 /// - S[n-1].ctime < S[n].ctime: can detect change of a file
366 /// - S[n-1].ctime == S[n].ctime
366 /// - S[n-1].ctime == S[n].ctime
367 /// - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
367 /// - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
368 /// - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
368 /// - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
369 /// - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
369 /// - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
370 /// - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
370 /// - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
371 ///
371 ///
372 /// Case (*2) above means that a file was changed twice or more at
372 /// Case (*2) above means that a file was changed twice or more at
373 /// same time in sec (= S[n-1].ctime), and comparison of timestamp
373 /// same time in sec (= S[n-1].ctime), and comparison of timestamp
374 /// is ambiguous.
374 /// is ambiguous.
375 ///
375 ///
376 /// Base idea to avoid such ambiguity is "advance mtime 1 sec, if
376 /// Base idea to avoid such ambiguity is "advance mtime 1 sec, if
377 /// timestamp is ambiguous".
377 /// timestamp is ambiguous".
378 ///
378 ///
379 /// But advancing mtime only in case (*2) doesn't work as
379 /// But advancing mtime only in case (*2) doesn't work as
380 /// expected, because naturally advanced S[n].mtime in case (*1)
380 /// expected, because naturally advanced S[n].mtime in case (*1)
381 /// might be equal to manually advanced S[n-1 or earlier].mtime.
381 /// might be equal to manually advanced S[n-1 or earlier].mtime.
382 ///
382 ///
383 /// Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
383 /// Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
384 /// treated as ambiguous regardless of mtime, to avoid overlooking
384 /// treated as ambiguous regardless of mtime, to avoid overlooking
385 /// by confliction between such mtime.
385 /// by confliction between such mtime.
386 ///
386 ///
387 /// Advancing mtime "if isambig(new, old)" ensures "S[n-1].mtime !=
387 /// Advancing mtime "if isambig(new, old)" ensures "S[n-1].mtime !=
388 /// S[n].mtime", even if size of a file isn't changed.
388 /// S[n].mtime", even if size of a file isn't changed.
389 fn is_filetime_ambiguous(new: &Metadata, old: &Metadata) -> bool {
389 fn is_filetime_ambiguous(new: &Metadata, old: &Metadata) -> bool {
390 new.ctime() == old.ctime()
390 new.ctime() == old.ctime()
391 }
391 }
392
392
393 /// Writable file object that atomically updates a file
393 /// Writable file object that atomically updates a file
394 ///
394 ///
395 /// All writes will go to a temporary copy of the original file. Call
395 /// All writes will go to a temporary copy of the original file. Call
396 /// [`Self::close`] when you are done writing, and [`Self`] will rename
396 /// [`Self::close`] when you are done writing, and [`Self`] will rename
397 /// the temporary copy to the original name, making the changes
397 /// the temporary copy to the original name, making the changes
398 /// visible. If the object is destroyed without being closed, all your
398 /// visible. If the object is destroyed without being closed, all your
399 /// writes are discarded.
399 /// writes are discarded.
400 #[derive(Debug)]
400 #[derive(Debug)]
401 pub struct AtomicFile {
401 pub struct AtomicFile {
402 /// The temporary file to write to
402 /// The temporary file to write to
403 fp: std::fs::File,
403 fp: std::fs::File,
404 /// Path of the temp file
404 /// Path of the temp file
405 temp_path: PathBuf,
405 temp_path: PathBuf,
406 /// Used when stat'ing the file, is useful only if the target file is
406 /// Used when stat'ing the file, is useful only if the target file is
407 /// guarded by any lock (e.g. repo.lock or repo.wlock).
407 /// guarded by any lock (e.g. repo.lock or repo.wlock).
408 check_ambig: bool,
408 check_ambig: bool,
409 /// Path of the target file
409 /// Path of the target file
410 target_name: PathBuf,
410 target_name: PathBuf,
411 /// Whether the file is open or not
411 /// Whether the file is open or not
412 is_open: bool,
412 is_open: bool,
413 }
413 }
414
414
415 impl AtomicFile {
415 impl AtomicFile {
416 pub fn new(
416 pub fn new(
417 target_path: impl AsRef<Path>,
417 target_path: impl AsRef<Path>,
418 empty: bool,
418 empty: bool,
419 check_ambig: bool,
419 check_ambig: bool,
420 ) -> Result<Self, HgError> {
420 ) -> Result<Self, HgError> {
421 let target_path = target_path.as_ref().to_owned();
421 let target_path = target_path.as_ref().to_owned();
422
422
423 let random_id =
423 let random_id =
424 Alphanumeric.sample_string(&mut rand::thread_rng(), 12);
424 Alphanumeric.sample_string(&mut rand::thread_rng(), 12);
425 let filename =
425 let filename =
426 target_path.file_name().expect("target has no filename");
426 target_path.file_name().expect("target has no filename");
427 let filename = get_bytes_from_path(filename);
427 let filename = get_bytes_from_path(filename);
428 let temp_filename =
428 let temp_filename =
429 format_bytes!(b".{}-{}~", filename, random_id.as_bytes());
429 format_bytes!(b".{}-{}~", filename, random_id.as_bytes());
430 let temp_path =
430 let temp_path =
431 target_path.with_file_name(get_path_from_bytes(&temp_filename));
431 target_path.with_file_name(get_path_from_bytes(&temp_filename));
432
432
433 if !empty {
433 if !empty {
434 std::fs::copy(&target_path, &temp_path)
434 std::fs::copy(&target_path, &temp_path)
435 .with_context(|| IoErrorContext::CopyingFile {
435 .with_context(|| IoErrorContext::CopyingFile {
436 from: target_path.to_owned(),
436 from: target_path.to_owned(),
437 to: temp_path.to_owned(),
437 to: temp_path.to_owned(),
438 })
438 })
439 // If it doesn't exist, create it on open
439 // If it doesn't exist, create it on open
440 .io_not_found_as_none()?;
440 .io_not_found_as_none()?;
441 }
441 }
442 let fp = std::fs::OpenOptions::new()
442 let fp = std::fs::OpenOptions::new()
443 .write(true)
443 .write(true)
444 .create(true)
444 .create(true)
445 .truncate(empty)
445 .truncate(empty)
446 .open(&temp_path)
446 .open(&temp_path)
447 .when_writing_file(&temp_path)?;
447 .when_writing_file(&temp_path)?;
448
448
449 Ok(Self {
449 Ok(Self {
450 fp,
450 fp,
451 temp_path,
451 temp_path,
452 check_ambig,
452 check_ambig,
453 target_name: target_path,
453 target_name: target_path,
454 is_open: true,
454 is_open: true,
455 })
455 })
456 }
456 }
457
457
458 pub fn from_file(
458 pub fn from_file(
459 fp: std::fs::File,
459 fp: std::fs::File,
460 check_ambig: bool,
460 check_ambig: bool,
461 temp_name: PathBuf,
461 temp_name: PathBuf,
462 target_name: PathBuf,
462 target_name: PathBuf,
463 ) -> Self {
463 ) -> Self {
464 Self {
464 Self {
465 fp,
465 fp,
466 check_ambig,
466 check_ambig,
467 temp_path: temp_name,
467 temp_path: temp_name,
468 target_name,
468 target_name,
469 is_open: true,
469 is_open: true,
470 }
470 }
471 }
471 }
472
472
473 /// Write `buf` to the temporary file
473 /// Write `buf` to the temporary file
474 pub fn write_all(&mut self, buf: &[u8]) -> Result<(), std::io::Error> {
474 pub fn write_all(&mut self, buf: &[u8]) -> Result<(), std::io::Error> {
475 self.fp.write_all(buf)
475 self.fp.write_all(buf)
476 }
476 }
477
477
478 fn target(&self) -> PathBuf {
478 fn target(&self) -> PathBuf {
479 self.temp_path
479 self.temp_path
480 .parent()
480 .parent()
481 .expect("should not be at the filesystem root")
481 .expect("should not be at the filesystem root")
482 .join(&self.target_name)
482 .join(&self.target_name)
483 }
483 }
484
484
485 /// Close the temporary file and rename to the target
485 /// Close the temporary file and rename to the target
486 pub fn close(mut self) -> Result<(), std::io::Error> {
486 pub fn close(mut self) -> Result<(), std::io::Error> {
487 self.fp.flush()?;
487 self.fp.flush()?;
488 let target = self.target();
488 let target = self.target();
489 if self.check_ambig {
489 if self.check_ambig {
490 if let Ok(stat) = target.metadata() {
490 if let Ok(stat) = target.metadata() {
491 std::fs::rename(&self.temp_path, &target)?;
491 std::fs::rename(&self.temp_path, &target)?;
492 avoid_timestamp_ambiguity(&target, &stat);
492 avoid_timestamp_ambiguity(&target, &stat);
493 } else {
493 } else {
494 std::fs::rename(&self.temp_path, target)?;
494 std::fs::rename(&self.temp_path, target)?;
495 }
495 }
496 } else {
496 } else {
497 std::fs::rename(&self.temp_path, target)?;
497 std::fs::rename(&self.temp_path, target)?;
498 }
498 }
499 self.is_open = false;
499 self.is_open = false;
500 Ok(())
500 Ok(())
501 }
501 }
502 }
502 }
503
503
504 impl Seek for AtomicFile {
504 impl Seek for AtomicFile {
505 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
505 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
506 self.fp.seek(pos)
506 self.fp.seek(pos)
507 }
507 }
508 }
508 }
509
509
510 impl Drop for AtomicFile {
510 impl Drop for AtomicFile {
511 fn drop(&mut self) {
511 fn drop(&mut self) {
512 if self.is_open {
512 if self.is_open {
513 std::fs::remove_file(&self.temp_path).ok();
513 std::fs::remove_file(&self.temp_path).ok();
514 }
514 }
515 }
515 }
516 }
516 }
517
517
518 /// Abstracts over the VFS to allow for different implementations of the
518 /// Abstracts over the VFS to allow for different implementations of the
519 /// filesystem layer (like passing one from Python).
519 /// filesystem layer (like passing one from Python).
520 pub trait Vfs: Sync + Send + DynClone {
520 pub trait Vfs: Sync + Send + DynClone {
521 // TODO make `open` readonly and make `open_read` an `open_write`
521 // TODO make `open` readonly and make `open_read` an `open_write`
522 /// Open a [`VfsFile::Normal`] for writing and reading the file at
523 /// `filename`, relative to this VFS's root.
522 fn open(&self, filename: &Path) -> Result<VfsFile, HgError>;
524 fn open(&self, filename: &Path) -> Result<VfsFile, HgError>;
525 /// Open a [`VfsFile::Normal`] for reading the file at `filename`,
526 /// relative to this VFS's root.
523 fn open_read(&self, filename: &Path) -> Result<VfsFile, HgError>;
527 fn open_read(&self, filename: &Path) -> Result<VfsFile, HgError>;
528 /// Open a [`VfsFile::Normal`] for reading and writing the file at
529 /// `filename`, relative to this VFS's root. This file will be checked
530 /// for an ambiguous mtime on [`drop`]. See [`is_filetime_ambiguous`].
524 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError>;
531 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError>;
532 /// Create a [`VfsFile::Normal`] for reading and writing the file at
533 /// `filename`, relative to this VFS's root. If the file already exists,
534 /// it will be truncated to 0 bytes.
525 fn create(
535 fn create(
526 &self,
536 &self,
527 filename: &Path,
537 filename: &Path,
528 check_ambig: bool,
538 check_ambig: bool,
529 ) -> Result<VfsFile, HgError>;
539 ) -> Result<VfsFile, HgError>;
530 /// Must truncate the new file if exist
540 /// Create a [`VfsFile::Atomic`] for reading and writing the file at
541 /// `filename`, relative to this VFS's root. If the file already exists,
542 /// it will be truncated to 0 bytes.
531 fn create_atomic(
543 fn create_atomic(
532 &self,
544 &self,
533 filename: &Path,
545 filename: &Path,
534 check_ambig: bool,
546 check_ambig: bool,
535 ) -> Result<VfsFile, HgError>;
547 ) -> Result<VfsFile, HgError>;
548 /// Return the total file size in bytes of the open `file`. Errors are
549 /// usual IO errors (invalid file handle, permissions, etc.)
536 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError>;
550 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError>;
551 /// Return `true` if `filename` exists relative to this VFS's root. Errors
552 /// will coerce to `false`, to this also returns `false` if there are
553 /// IO problems. This is fine because any operation that actually tries
554 /// to do anything with this path will get the same error.
537 fn exists(&self, filename: &Path) -> bool;
555 fn exists(&self, filename: &Path) -> bool;
556 /// Remove the file at `filename` relative to this VFS's root. Errors
557 /// are the usual IO errors (lacking permission, file does not exist, etc.)
538 fn unlink(&self, filename: &Path) -> Result<(), HgError>;
558 fn unlink(&self, filename: &Path) -> Result<(), HgError>;
559 /// Rename the file `from` to `to`, both relative to this VFS's root.
560 /// Errors are the usual IO errors (lacking permission, file does not
561 /// exist, etc.). If `check_ambig` is `true`, the VFS will check for an
562 /// ambiguous mtime on rename. See [`is_filetime_ambiguous`].
539 fn rename(
563 fn rename(
540 &self,
564 &self,
541 from: &Path,
565 from: &Path,
542 to: &Path,
566 to: &Path,
543 check_ambig: bool,
567 check_ambig: bool,
544 ) -> Result<(), HgError>;
568 ) -> Result<(), HgError>;
569 /// Rename the file `from` to `to`, both relative to this VFS's root.
570 /// Errors are the usual IO errors (lacking permission, file does not
571 /// exist, etc.). If `check_ambig` is passed, the VFS will check for an
572 /// ambiguous mtime on rename. See [`is_filetime_ambiguous`].
545 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError>;
573 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError>;
574 /// Returns the absolute root path of this VFS, relative to which all
575 /// operations are done.
546 fn base(&self) -> &Path;
576 fn base(&self) -> &Path;
547 }
577 }
548
578
549 /// These methods will need to be implemented once `rhg` (and other) non-Python
579 /// These methods will need to be implemented once `rhg` (and other) non-Python
550 /// users of `hg-core` start doing more on their own, like writing to files.
580 /// users of `hg-core` start doing more on their own, like writing to files.
551 impl Vfs for VfsImpl {
581 impl Vfs for VfsImpl {
552 fn open(&self, filename: &Path) -> Result<VfsFile, HgError> {
582 fn open(&self, filename: &Path) -> Result<VfsFile, HgError> {
553 if self.readonly {
583 if self.readonly {
554 return Err(HgError::abort(
584 return Err(HgError::abort(
555 "write access in a readonly vfs",
585 "write access in a readonly vfs",
556 exit_codes::ABORT,
586 exit_codes::ABORT,
557 None,
587 None,
558 ));
588 ));
559 }
589 }
560 // TODO auditpath
590 // TODO auditpath
561 let path = self.base.join(filename);
591 let path = self.base.join(filename);
562 copy_in_place_if_hardlink(&path)?;
592 copy_in_place_if_hardlink(&path)?;
563
593
564 Ok(VfsFile::normal(
594 Ok(VfsFile::normal(
565 OpenOptions::new()
595 OpenOptions::new()
566 .create(false)
596 .create(false)
567 .create_new(false)
597 .create_new(false)
568 .write(true)
598 .write(true)
569 .read(true)
599 .read(true)
570 .open(&path)
600 .open(&path)
571 .when_writing_file(&path)?,
601 .when_writing_file(&path)?,
572 path.to_owned(),
602 path.to_owned(),
573 ))
603 ))
574 }
604 }
575
605
576 fn open_read(&self, filename: &Path) -> Result<VfsFile, HgError> {
606 fn open_read(&self, filename: &Path) -> Result<VfsFile, HgError> {
577 // TODO auditpath
607 // TODO auditpath
578 let path = self.base.join(filename);
608 let path = self.base.join(filename);
579 Ok(VfsFile::normal(
609 Ok(VfsFile::normal(
580 std::fs::File::open(&path).when_reading_file(&path)?,
610 std::fs::File::open(&path).when_reading_file(&path)?,
581 filename.to_owned(),
611 filename.to_owned(),
582 ))
612 ))
583 }
613 }
584
614
585 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError> {
615 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError> {
586 if self.readonly {
616 if self.readonly {
587 return Err(HgError::abort(
617 return Err(HgError::abort(
588 "write access in a readonly vfs",
618 "write access in a readonly vfs",
589 exit_codes::ABORT,
619 exit_codes::ABORT,
590 None,
620 None,
591 ));
621 ));
592 }
622 }
593
623
594 let path = self.base.join(filename);
624 let path = self.base.join(filename);
595 copy_in_place_if_hardlink(&path)?;
625 copy_in_place_if_hardlink(&path)?;
596
626
597 // TODO auditpath
627 // TODO auditpath
598 VfsFile::normal_check_ambig(
628 VfsFile::normal_check_ambig(
599 OpenOptions::new()
629 OpenOptions::new()
600 .write(true)
630 .write(true)
601 .read(true) // Can be used for reading to save on `open` calls
631 .read(true) // Can be used for reading to save on `open` calls
602 .create(false)
632 .create(false)
603 .open(&path)
633 .open(&path)
604 .when_reading_file(&path)?,
634 .when_reading_file(&path)?,
605 path.to_owned(),
635 path.to_owned(),
606 )
636 )
607 }
637 }
608
638
609 fn create(
639 fn create(
610 &self,
640 &self,
611 filename: &Path,
641 filename: &Path,
612 check_ambig: bool,
642 check_ambig: bool,
613 ) -> Result<VfsFile, HgError> {
643 ) -> Result<VfsFile, HgError> {
614 if self.readonly {
644 if self.readonly {
615 return Err(HgError::abort(
645 return Err(HgError::abort(
616 "write access in a readonly vfs",
646 "write access in a readonly vfs",
617 exit_codes::ABORT,
647 exit_codes::ABORT,
618 None,
648 None,
619 ));
649 ));
620 }
650 }
621 // TODO auditpath
651 // TODO auditpath
622 let path = self.base.join(filename);
652 let path = self.base.join(filename);
623 let parent = path.parent().expect("file at root");
653 let parent = path.parent().expect("file at root");
624 std::fs::create_dir_all(parent).when_writing_file(parent)?;
654 std::fs::create_dir_all(parent).when_writing_file(parent)?;
625
655
626 let file = OpenOptions::new()
656 let file = OpenOptions::new()
627 .create(true)
657 .create(true)
628 .truncate(true)
658 .truncate(true)
629 .write(true)
659 .write(true)
630 .read(true)
660 .read(true)
631 .open(&path)
661 .open(&path)
632 .when_writing_file(&path)?;
662 .when_writing_file(&path)?;
633
663
634 if let Some(mode) = self.mode {
664 if let Some(mode) = self.mode {
635 // Creating the file with the right permission (with `.mode()`)
665 // Creating the file with the right permission (with `.mode()`)
636 // may not work since umask takes effect for file creation.
666 // may not work since umask takes effect for file creation.
637 // So we need to fix the permission after creating the file.
667 // So we need to fix the permission after creating the file.
638 fix_directory_permissions(&self.base, &path, mode)?;
668 fix_directory_permissions(&self.base, &path, mode)?;
639 let perm = std::fs::Permissions::from_mode(mode & 0o666);
669 let perm = std::fs::Permissions::from_mode(mode & 0o666);
640 std::fs::set_permissions(&path, perm).when_writing_file(&path)?;
670 std::fs::set_permissions(&path, perm).when_writing_file(&path)?;
641 }
671 }
642
672
643 Ok(VfsFile::Normal {
673 Ok(VfsFile::Normal {
644 file,
674 file,
645 check_ambig: if check_ambig {
675 check_ambig: if check_ambig {
646 Some(path.metadata().when_reading_file(&path)?)
676 Some(path.metadata().when_reading_file(&path)?)
647 } else {
677 } else {
648 None
678 None
649 },
679 },
650 path: path.to_owned(),
680 path: path.to_owned(),
651 })
681 })
652 }
682 }
653
683
654 fn create_atomic(
684 fn create_atomic(
655 &self,
685 &self,
656 _filename: &Path,
686 _filename: &Path,
657 _check_ambig: bool,
687 _check_ambig: bool,
658 ) -> Result<VfsFile, HgError> {
688 ) -> Result<VfsFile, HgError> {
659 todo!()
689 todo!()
660 }
690 }
661
691
662 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError> {
692 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError> {
663 Ok(file
693 Ok(file
664 .metadata()
694 .metadata()
665 .map_err(|e| {
695 .map_err(|e| {
666 HgError::abort(
696 HgError::abort(
667 format!("Could not get file metadata: {}", e),
697 format!("Could not get file metadata: {}", e),
668 exit_codes::ABORT,
698 exit_codes::ABORT,
669 None,
699 None,
670 )
700 )
671 })?
701 })?
672 .size())
702 .size())
673 }
703 }
674
704
675 fn exists(&self, filename: &Path) -> bool {
705 fn exists(&self, filename: &Path) -> bool {
676 self.base.join(filename).exists()
706 self.base.join(filename).exists()
677 }
707 }
678
708
679 fn unlink(&self, filename: &Path) -> Result<(), HgError> {
709 fn unlink(&self, filename: &Path) -> Result<(), HgError> {
680 if self.readonly {
710 if self.readonly {
681 return Err(HgError::abort(
711 return Err(HgError::abort(
682 "write access in a readonly vfs",
712 "write access in a readonly vfs",
683 exit_codes::ABORT,
713 exit_codes::ABORT,
684 None,
714 None,
685 ));
715 ));
686 }
716 }
687 let path = self.base.join(filename);
717 let path = self.base.join(filename);
688 std::fs::remove_file(&path)
718 std::fs::remove_file(&path)
689 .with_context(|| IoErrorContext::RemovingFile(path))
719 .with_context(|| IoErrorContext::RemovingFile(path))
690 }
720 }
691
721
692 fn rename(
722 fn rename(
693 &self,
723 &self,
694 from: &Path,
724 from: &Path,
695 to: &Path,
725 to: &Path,
696 check_ambig: bool,
726 check_ambig: bool,
697 ) -> Result<(), HgError> {
727 ) -> Result<(), HgError> {
698 if self.readonly {
728 if self.readonly {
699 return Err(HgError::abort(
729 return Err(HgError::abort(
700 "write access in a readonly vfs",
730 "write access in a readonly vfs",
701 exit_codes::ABORT,
731 exit_codes::ABORT,
702 None,
732 None,
703 ));
733 ));
704 }
734 }
705 let old_stat = if check_ambig {
735 let old_stat = if check_ambig {
706 Some(
736 Some(
707 from.metadata()
737 from.metadata()
708 .when_reading_file(from)
738 .when_reading_file(from)
709 .io_not_found_as_none()?,
739 .io_not_found_as_none()?,
710 )
740 )
711 } else {
741 } else {
712 None
742 None
713 };
743 };
714 let from = self.base.join(from);
744 let from = self.base.join(from);
715 let to = self.base.join(to);
745 let to = self.base.join(to);
716 std::fs::rename(&from, &to).with_context(|| {
746 std::fs::rename(&from, &to).with_context(|| {
717 IoErrorContext::RenamingFile {
747 IoErrorContext::RenamingFile {
718 from,
748 from,
719 to: to.to_owned(),
749 to: to.to_owned(),
720 }
750 }
721 })?;
751 })?;
722 if let Some(Some(old)) = old_stat {
752 if let Some(Some(old)) = old_stat {
723 avoid_timestamp_ambiguity(&to, &old);
753 avoid_timestamp_ambiguity(&to, &old);
724 }
754 }
725 Ok(())
755 Ok(())
726 }
756 }
727
757
728 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError> {
758 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError> {
729 let from = self.base.join(from);
759 let from = self.base.join(from);
730 let to = self.base.join(to);
760 let to = self.base.join(to);
731 std::fs::copy(&from, &to)
761 std::fs::copy(&from, &to)
732 .with_context(|| IoErrorContext::CopyingFile { from, to })
762 .with_context(|| IoErrorContext::CopyingFile { from, to })
733 .map(|_| ())
763 .map(|_| ())
734 }
764 }
735
765
736 fn base(&self) -> &Path {
766 fn base(&self) -> &Path {
737 &self.base
767 &self.base
738 }
768 }
739 }
769 }
740
770
741 fn fix_directory_permissions(
771 fn fix_directory_permissions(
742 base: &Path,
772 base: &Path,
743 path: &Path,
773 path: &Path,
744 mode: u32,
774 mode: u32,
745 ) -> Result<(), HgError> {
775 ) -> Result<(), HgError> {
746 let mut ancestors = path.ancestors();
776 let mut ancestors = path.ancestors();
747 ancestors.next(); // yields the path itself
777 ancestors.next(); // yields the path itself
748
778
749 for ancestor in ancestors {
779 for ancestor in ancestors {
750 if ancestor == base {
780 if ancestor == base {
751 break;
781 break;
752 }
782 }
753 let perm = std::fs::Permissions::from_mode(mode);
783 let perm = std::fs::Permissions::from_mode(mode);
754 std::fs::set_permissions(ancestor, perm)
784 std::fs::set_permissions(ancestor, perm)
755 .when_writing_file(ancestor)?;
785 .when_writing_file(ancestor)?;
756 }
786 }
757 Ok(())
787 Ok(())
758 }
788 }
759
789
760 /// A VFS that understands the `fncache` store layout (file encoding), and
790 /// A VFS that understands the `fncache` store layout (file encoding), and
761 /// adds new entries to the `fncache`.
791 /// adds new entries to the `fncache`.
762 /// TODO Only works when using from Python for now.
792 /// TODO Only works when using from Python for now.
763 pub struct FnCacheVfs {
793 pub struct FnCacheVfs {
764 inner: VfsImpl,
794 inner: VfsImpl,
765 fncache: Box<dyn FnCache>,
795 fncache: Box<dyn FnCache>,
766 }
796 }
767
797
768 impl Clone for FnCacheVfs {
798 impl Clone for FnCacheVfs {
769 fn clone(&self) -> Self {
799 fn clone(&self) -> Self {
770 Self {
800 Self {
771 inner: self.inner.clone(),
801 inner: self.inner.clone(),
772 fncache: dyn_clone::clone_box(&*self.fncache),
802 fncache: dyn_clone::clone_box(&*self.fncache),
773 }
803 }
774 }
804 }
775 }
805 }
776
806
777 impl FnCacheVfs {
807 impl FnCacheVfs {
778 pub fn new(
808 pub fn new(
779 base: PathBuf,
809 base: PathBuf,
780 readonly: bool,
810 readonly: bool,
781 fncache: Box<dyn FnCache>,
811 fncache: Box<dyn FnCache>,
782 ) -> Self {
812 ) -> Self {
783 let inner = VfsImpl::new(base, readonly);
813 let inner = VfsImpl::new(base, readonly);
784 Self { inner, fncache }
814 Self { inner, fncache }
785 }
815 }
786
816
787 fn maybe_add_to_fncache(
817 fn maybe_add_to_fncache(
788 &self,
818 &self,
789 filename: &Path,
819 filename: &Path,
790 encoded_path: &Path,
820 encoded_path: &Path,
791 ) -> Result<(), HgError> {
821 ) -> Result<(), HgError> {
792 let relevant_file = (filename.starts_with("data/")
822 let relevant_file = (filename.starts_with("data/")
793 || filename.starts_with("meta/"))
823 || filename.starts_with("meta/"))
794 && is_revlog_file(filename);
824 && is_revlog_file(filename);
795 if relevant_file {
825 if relevant_file {
796 let not_load = !self.fncache.is_loaded()
826 let not_load = !self.fncache.is_loaded()
797 && (self.exists(filename)
827 && (self.exists(filename)
798 && self
828 && self
799 .inner
829 .inner
800 .join(encoded_path)
830 .join(encoded_path)
801 .metadata()
831 .metadata()
802 .when_reading_file(encoded_path)?
832 .when_reading_file(encoded_path)?
803 .size()
833 .size()
804 != 0);
834 != 0);
805 if !not_load {
835 if !not_load {
806 self.fncache.add(filename);
836 self.fncache.add(filename);
807 }
837 }
808 };
838 };
809 Ok(())
839 Ok(())
810 }
840 }
811 }
841 }
812
842
813 impl Vfs for FnCacheVfs {
843 impl Vfs for FnCacheVfs {
814 fn open(&self, filename: &Path) -> Result<VfsFile, HgError> {
844 fn open(&self, filename: &Path) -> Result<VfsFile, HgError> {
815 let encoded = path_encode(&get_bytes_from_path(filename));
845 let encoded = path_encode(&get_bytes_from_path(filename));
816 let encoded_path = get_path_from_bytes(&encoded);
846 let encoded_path = get_path_from_bytes(&encoded);
817 self.maybe_add_to_fncache(filename, encoded_path)?;
847 self.maybe_add_to_fncache(filename, encoded_path)?;
818 self.inner.open(encoded_path)
848 self.inner.open(encoded_path)
819 }
849 }
820
850
821 fn open_read(&self, filename: &Path) -> Result<VfsFile, HgError> {
851 fn open_read(&self, filename: &Path) -> Result<VfsFile, HgError> {
822 let encoded = path_encode(&get_bytes_from_path(filename));
852 let encoded = path_encode(&get_bytes_from_path(filename));
823 let filename = get_path_from_bytes(&encoded);
853 let filename = get_path_from_bytes(&encoded);
824 self.inner.open_read(filename)
854 self.inner.open_read(filename)
825 }
855 }
826
856
827 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError> {
857 fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError> {
828 let encoded = path_encode(&get_bytes_from_path(filename));
858 let encoded = path_encode(&get_bytes_from_path(filename));
829 let filename = get_path_from_bytes(&encoded);
859 let filename = get_path_from_bytes(&encoded);
830 self.inner.open_check_ambig(filename)
860 self.inner.open_check_ambig(filename)
831 }
861 }
832
862
833 fn create(
863 fn create(
834 &self,
864 &self,
835 filename: &Path,
865 filename: &Path,
836 check_ambig: bool,
866 check_ambig: bool,
837 ) -> Result<VfsFile, HgError> {
867 ) -> Result<VfsFile, HgError> {
838 let encoded = path_encode(&get_bytes_from_path(filename));
868 let encoded = path_encode(&get_bytes_from_path(filename));
839 let encoded_path = get_path_from_bytes(&encoded);
869 let encoded_path = get_path_from_bytes(&encoded);
840 self.maybe_add_to_fncache(filename, encoded_path)?;
870 self.maybe_add_to_fncache(filename, encoded_path)?;
841 self.inner.create(encoded_path, check_ambig)
871 self.inner.create(encoded_path, check_ambig)
842 }
872 }
843
873
844 fn create_atomic(
874 fn create_atomic(
845 &self,
875 &self,
846 filename: &Path,
876 filename: &Path,
847 check_ambig: bool,
877 check_ambig: bool,
848 ) -> Result<VfsFile, HgError> {
878 ) -> Result<VfsFile, HgError> {
849 let encoded = path_encode(&get_bytes_from_path(filename));
879 let encoded = path_encode(&get_bytes_from_path(filename));
850 let filename = get_path_from_bytes(&encoded);
880 let filename = get_path_from_bytes(&encoded);
851 self.inner.create_atomic(filename, check_ambig)
881 self.inner.create_atomic(filename, check_ambig)
852 }
882 }
853
883
854 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError> {
884 fn file_size(&self, file: &VfsFile) -> Result<u64, HgError> {
855 self.inner.file_size(file)
885 self.inner.file_size(file)
856 }
886 }
857
887
858 fn exists(&self, filename: &Path) -> bool {
888 fn exists(&self, filename: &Path) -> bool {
859 let encoded = path_encode(&get_bytes_from_path(filename));
889 let encoded = path_encode(&get_bytes_from_path(filename));
860 let filename = get_path_from_bytes(&encoded);
890 let filename = get_path_from_bytes(&encoded);
861 self.inner.exists(filename)
891 self.inner.exists(filename)
862 }
892 }
863
893
864 fn unlink(&self, filename: &Path) -> Result<(), HgError> {
894 fn unlink(&self, filename: &Path) -> Result<(), HgError> {
865 let encoded = path_encode(&get_bytes_from_path(filename));
895 let encoded = path_encode(&get_bytes_from_path(filename));
866 let filename = get_path_from_bytes(&encoded);
896 let filename = get_path_from_bytes(&encoded);
867 self.inner.unlink(filename)
897 self.inner.unlink(filename)
868 }
898 }
869
899
870 fn rename(
900 fn rename(
871 &self,
901 &self,
872 from: &Path,
902 from: &Path,
873 to: &Path,
903 to: &Path,
874 check_ambig: bool,
904 check_ambig: bool,
875 ) -> Result<(), HgError> {
905 ) -> Result<(), HgError> {
876 let encoded = path_encode(&get_bytes_from_path(from));
906 let encoded = path_encode(&get_bytes_from_path(from));
877 let from = get_path_from_bytes(&encoded);
907 let from = get_path_from_bytes(&encoded);
878 let encoded = path_encode(&get_bytes_from_path(to));
908 let encoded = path_encode(&get_bytes_from_path(to));
879 let to = get_path_from_bytes(&encoded);
909 let to = get_path_from_bytes(&encoded);
880 self.inner.rename(from, to, check_ambig)
910 self.inner.rename(from, to, check_ambig)
881 }
911 }
882
912
883 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError> {
913 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError> {
884 let encoded = path_encode(&get_bytes_from_path(from));
914 let encoded = path_encode(&get_bytes_from_path(from));
885 let from = get_path_from_bytes(&encoded);
915 let from = get_path_from_bytes(&encoded);
886 let encoded = path_encode(&get_bytes_from_path(to));
916 let encoded = path_encode(&get_bytes_from_path(to));
887 let to = get_path_from_bytes(&encoded);
917 let to = get_path_from_bytes(&encoded);
888 self.inner.copy(from, to)
918 self.inner.copy(from, to)
889 }
919 }
890 fn base(&self) -> &Path {
920 fn base(&self) -> &Path {
891 self.inner.base()
921 self.inner.base()
892 }
922 }
893 }
923 }
894
924
895 /// Detects whether `path` is a hardlink and does a tmp copy + rename erase
925 /// Detects whether `path` is a hardlink and does a tmp copy + rename erase
896 /// to turn it into its own file. Revlogs are usually hardlinked when doing
926 /// to turn it into its own file. Revlogs are usually hardlinked when doing
897 /// a local clone, and we don't want to modify the original repo.
927 /// a local clone, and we don't want to modify the original repo.
898 fn copy_in_place_if_hardlink(path: &Path) -> Result<(), HgError> {
928 fn copy_in_place_if_hardlink(path: &Path) -> Result<(), HgError> {
899 let metadata = path.metadata().when_writing_file(path)?;
929 let metadata = path.metadata().when_writing_file(path)?;
900 if metadata.nlink() > 0 {
930 if metadata.nlink() > 0 {
901 // If it's hardlinked, copy it and rename it back before changing it.
931 // If it's hardlinked, copy it and rename it back before changing it.
902 let tmpdir = path.parent().expect("file at root");
932 let tmpdir = path.parent().expect("file at root");
903 let name = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
933 let name = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
904 let tmpfile = tmpdir.join(name);
934 let tmpfile = tmpdir.join(name);
905 std::fs::create_dir_all(tmpfile.parent().expect("file at root"))
935 std::fs::create_dir_all(tmpfile.parent().expect("file at root"))
906 .with_context(|| IoErrorContext::CopyingFile {
936 .with_context(|| IoErrorContext::CopyingFile {
907 from: path.to_owned(),
937 from: path.to_owned(),
908 to: tmpfile.to_owned(),
938 to: tmpfile.to_owned(),
909 })?;
939 })?;
910 std::fs::copy(path, &tmpfile).with_context(|| {
940 std::fs::copy(path, &tmpfile).with_context(|| {
911 IoErrorContext::CopyingFile {
941 IoErrorContext::CopyingFile {
912 from: path.to_owned(),
942 from: path.to_owned(),
913 to: tmpfile.to_owned(),
943 to: tmpfile.to_owned(),
914 }
944 }
915 })?;
945 })?;
916 std::fs::rename(&tmpfile, path).with_context(|| {
946 std::fs::rename(&tmpfile, path).with_context(|| {
917 IoErrorContext::RenamingFile {
947 IoErrorContext::RenamingFile {
918 from: tmpfile,
948 from: tmpfile,
919 to: path.to_owned(),
949 to: path.to_owned(),
920 }
950 }
921 })?;
951 })?;
922 }
952 }
923 Ok(())
953 Ok(())
924 }
954 }
925
955
926 pub fn is_revlog_file(path: impl AsRef<Path>) -> bool {
956 pub fn is_revlog_file(path: impl AsRef<Path>) -> bool {
927 path.as_ref()
957 path.as_ref()
928 .extension()
958 .extension()
929 .map(|ext| {
959 .map(|ext| {
930 ["i", "idx", "d", "dat", "n", "nd", "sda"]
960 ["i", "idx", "d", "dat", "n", "nd", "sda"]
931 .contains(&ext.to_string_lossy().as_ref())
961 .contains(&ext.to_string_lossy().as_ref())
932 })
962 })
933 .unwrap_or(false)
963 .unwrap_or(false)
934 }
964 }
935
965
936 pub(crate) fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> {
966 pub(crate) fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> {
937 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir()))
967 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir()))
938 }
968 }
939
969
940 pub(crate) fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> {
970 pub(crate) fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> {
941 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file()))
971 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file()))
942 }
972 }
943
973
944 /// Returns whether the given `path` is on a network file system.
974 /// Returns whether the given `path` is on a network file system.
945 /// Taken from `cargo`'s codebase.
975 /// Taken from `cargo`'s codebase.
946 #[cfg(target_os = "linux")]
976 #[cfg(target_os = "linux")]
947 pub(crate) fn is_on_nfs_mount(path: impl AsRef<Path>) -> bool {
977 pub(crate) fn is_on_nfs_mount(path: impl AsRef<Path>) -> bool {
948 use std::ffi::CString;
978 use std::ffi::CString;
949 use std::mem;
979 use std::mem;
950 use std::os::unix::prelude::*;
980 use std::os::unix::prelude::*;
951
981
952 let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
982 let path = match CString::new(path.as_ref().as_os_str().as_bytes()) {
953 Ok(path) => path,
983 Ok(path) => path,
954 Err(_) => return false,
984 Err(_) => return false,
955 };
985 };
956
986
957 unsafe {
987 unsafe {
958 let mut buf: libc::statfs = mem::zeroed();
988 let mut buf: libc::statfs = mem::zeroed();
959 let r = libc::statfs(path.as_ptr(), &mut buf);
989 let r = libc::statfs(path.as_ptr(), &mut buf);
960
990
961 r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32
991 r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32
962 }
992 }
963 }
993 }
964
994
965 /// Similar to what Cargo does; although detecting NFS (or non-local
995 /// Similar to what Cargo does; although detecting NFS (or non-local
966 /// file systems) _should_ be possible on other operating systems,
996 /// file systems) _should_ be possible on other operating systems,
967 /// we'll just assume that mmap() works there, for now; after all,
997 /// we'll just assume that mmap() works there, for now; after all,
968 /// _some_ functionality is better than a compile error, i.e. none at
998 /// _some_ functionality is better than a compile error, i.e. none at
969 /// all
999 /// all
970 #[cfg(not(target_os = "linux"))]
1000 #[cfg(not(target_os = "linux"))]
971 pub(crate) fn is_on_nfs_mount(_path: impl AsRef<Path>) -> bool {
1001 pub(crate) fn is_on_nfs_mount(_path: impl AsRef<Path>) -> bool {
972 false
1002 false
973 }
1003 }
974
1004
975 #[cfg(test)]
1005 #[cfg(test)]
976 mod tests {
1006 mod tests {
977 use super::*;
1007 use super::*;
978
1008
979 #[test]
1009 #[test]
980 fn test_atomic_file() {
1010 fn test_atomic_file() {
981 let dir = tempfile::tempdir().unwrap().into_path();
1011 let dir = tempfile::tempdir().unwrap().into_path();
982 let target_path = dir.join("sometargetname");
1012 let target_path = dir.join("sometargetname");
983
1013
984 for empty in [true, false] {
1014 for empty in [true, false] {
985 let file = AtomicFile::new(&target_path, empty, false).unwrap();
1015 let file = AtomicFile::new(&target_path, empty, false).unwrap();
986 assert!(file.is_open);
1016 assert!(file.is_open);
987 let filename =
1017 let filename =
988 file.temp_path.file_name().unwrap().to_str().unwrap();
1018 file.temp_path.file_name().unwrap().to_str().unwrap();
989 // Make sure we have a coherent temp name
1019 // Make sure we have a coherent temp name
990 assert_eq!(filename.len(), 29, "{}", filename);
1020 assert_eq!(filename.len(), 29, "{}", filename);
991 assert!(filename.contains("sometargetname"));
1021 assert!(filename.contains("sometargetname"));
992
1022
993 // Make sure the temp file is created in the same folder
1023 // Make sure the temp file is created in the same folder
994 assert_eq!(target_path.parent(), file.temp_path.parent());
1024 assert_eq!(target_path.parent(), file.temp_path.parent());
995 }
1025 }
996
1026
997 assert!(!target_path.exists());
1027 assert!(!target_path.exists());
998 std::fs::write(&target_path, "version 1").unwrap();
1028 std::fs::write(&target_path, "version 1").unwrap();
999 let mut file = AtomicFile::new(&target_path, false, false).unwrap();
1029 let mut file = AtomicFile::new(&target_path, false, false).unwrap();
1000 file.write_all(b"version 2!").unwrap();
1030 file.write_all(b"version 2!").unwrap();
1001 assert_eq!(
1031 assert_eq!(
1002 std::fs::read(&target_path).unwrap(),
1032 std::fs::read(&target_path).unwrap(),
1003 b"version 1".to_vec()
1033 b"version 1".to_vec()
1004 );
1034 );
1005 let temp_path = file.temp_path.to_owned();
1035 let temp_path = file.temp_path.to_owned();
1006 // test that dropping the file should discard the temp file and not
1036 // test that dropping the file should discard the temp file and not
1007 // affect the target path.
1037 // affect the target path.
1008 drop(file);
1038 drop(file);
1009 assert_eq!(
1039 assert_eq!(
1010 std::fs::read(&target_path).unwrap(),
1040 std::fs::read(&target_path).unwrap(),
1011 b"version 1".to_vec()
1041 b"version 1".to_vec()
1012 );
1042 );
1013 assert!(!temp_path.exists());
1043 assert!(!temp_path.exists());
1014
1044
1015 let mut file = AtomicFile::new(&target_path, false, false).unwrap();
1045 let mut file = AtomicFile::new(&target_path, false, false).unwrap();
1016 file.write_all(b"version 2!").unwrap();
1046 file.write_all(b"version 2!").unwrap();
1017 assert_eq!(
1047 assert_eq!(
1018 std::fs::read(&target_path).unwrap(),
1048 std::fs::read(&target_path).unwrap(),
1019 b"version 1".to_vec()
1049 b"version 1".to_vec()
1020 );
1050 );
1021 file.close().unwrap();
1051 file.close().unwrap();
1022 assert_eq!(
1052 assert_eq!(
1023 std::fs::read(&target_path).unwrap(),
1053 std::fs::read(&target_path).unwrap(),
1024 b"version 2!".to_vec(),
1054 b"version 2!".to_vec(),
1025 "{}",
1055 "{}",
1026 std::fs::read_to_string(&target_path).unwrap()
1056 std::fs::read_to_string(&target_path).unwrap()
1027 );
1057 );
1028 assert!(target_path.exists());
1058 assert!(target_path.exists());
1029 assert!(!temp_path.exists());
1059 assert!(!temp_path.exists());
1030 }
1060 }
1031
1061
1032 #[test]
1062 #[test]
1033 fn test_vfs_file_check_ambig() {
1063 fn test_vfs_file_check_ambig() {
1034 let dir = tempfile::tempdir().unwrap().into_path();
1064 let dir = tempfile::tempdir().unwrap().into_path();
1035 let file_path = dir.join("file");
1065 let file_path = dir.join("file");
1036
1066
1037 fn vfs_file_write(file_path: &Path, check_ambig: bool) {
1067 fn vfs_file_write(file_path: &Path, check_ambig: bool) {
1038 let file = std::fs::OpenOptions::new()
1068 let file = std::fs::OpenOptions::new()
1039 .write(true)
1069 .write(true)
1040 .open(file_path)
1070 .open(file_path)
1041 .unwrap();
1071 .unwrap();
1042 let old_stat = if check_ambig {
1072 let old_stat = if check_ambig {
1043 Some(file.metadata().unwrap())
1073 Some(file.metadata().unwrap())
1044 } else {
1074 } else {
1045 None
1075 None
1046 };
1076 };
1047
1077
1048 let mut vfs_file = VfsFile::Normal {
1078 let mut vfs_file = VfsFile::Normal {
1049 file,
1079 file,
1050 path: file_path.to_owned(),
1080 path: file_path.to_owned(),
1051 check_ambig: old_stat,
1081 check_ambig: old_stat,
1052 };
1082 };
1053 vfs_file.write_all(b"contents").unwrap();
1083 vfs_file.write_all(b"contents").unwrap();
1054 }
1084 }
1055
1085
1056 std::fs::OpenOptions::new()
1086 std::fs::OpenOptions::new()
1057 .write(true)
1087 .write(true)
1058 .create(true)
1088 .create(true)
1059 .truncate(false)
1089 .truncate(false)
1060 .open(&file_path)
1090 .open(&file_path)
1061 .unwrap();
1091 .unwrap();
1062
1092
1063 let number_of_writes = 3;
1093 let number_of_writes = 3;
1064
1094
1065 // Try multiple times, because reproduction of an ambiguity depends
1095 // Try multiple times, because reproduction of an ambiguity depends
1066 // on "filesystem time"
1096 // on "filesystem time"
1067 for _ in 0..5 {
1097 for _ in 0..5 {
1068 TIMESTAMP_FIXES_CALLS.store(0, Ordering::Relaxed);
1098 TIMESTAMP_FIXES_CALLS.store(0, Ordering::Relaxed);
1069 vfs_file_write(&file_path, false);
1099 vfs_file_write(&file_path, false);
1070 let old_stat = file_path.metadata().unwrap();
1100 let old_stat = file_path.metadata().unwrap();
1071 if old_stat.ctime() != old_stat.mtime() {
1101 if old_stat.ctime() != old_stat.mtime() {
1072 // subsequent changing never causes ambiguity
1102 // subsequent changing never causes ambiguity
1073 continue;
1103 continue;
1074 }
1104 }
1075
1105
1076 // Repeat atomic write with `check_ambig == true`, to examine
1106 // Repeat atomic write with `check_ambig == true`, to examine
1077 // whether the mtime is advanced multiple times as expected
1107 // whether the mtime is advanced multiple times as expected
1078 for _ in 0..number_of_writes {
1108 for _ in 0..number_of_writes {
1079 vfs_file_write(&file_path, true);
1109 vfs_file_write(&file_path, true);
1080 }
1110 }
1081 let new_stat = file_path.metadata().unwrap();
1111 let new_stat = file_path.metadata().unwrap();
1082 if !is_filetime_ambiguous(&new_stat, &old_stat) {
1112 if !is_filetime_ambiguous(&new_stat, &old_stat) {
1083 // timestamp ambiguity was naturally avoided while repetition
1113 // timestamp ambiguity was naturally avoided while repetition
1084 continue;
1114 continue;
1085 }
1115 }
1086
1116
1087 assert_eq!(
1117 assert_eq!(
1088 TIMESTAMP_FIXES_CALLS.load(Ordering::Relaxed),
1118 TIMESTAMP_FIXES_CALLS.load(Ordering::Relaxed),
1089 number_of_writes
1119 number_of_writes
1090 );
1120 );
1091 assert_eq!(
1121 assert_eq!(
1092 old_stat.mtime() + number_of_writes as i64,
1122 old_stat.mtime() + number_of_writes as i64,
1093 file_path.metadata().unwrap().mtime()
1123 file_path.metadata().unwrap().mtime()
1094 );
1124 );
1095 break;
1125 break;
1096 }
1126 }
1097 // If we've arrived here without breaking, we might not have
1127 // If we've arrived here without breaking, we might not have
1098 // tested anything because the platform is too slow. This test will
1128 // tested anything because the platform is too slow. This test will
1099 // still work on fast platforms.
1129 // still work on fast platforms.
1100 }
1130 }
1101 }
1131 }
General Comments 0
You need to be logged in to leave comments. Login now