Mercurial > hg
annotate rust/hg-core/src/vfs.rs @ 51950:93a533fa04ec
tests: stabilize `test-sparse.t` on Windows
One of the reserved characters for path values is '*', so it can't be used.
Fortunately, missing this seems to not get in the way of any other tests, and it
is removed shortly after with `rm -r foo*bar`, and the extant 'foo-bar' matches
the pattern.
author | Matt Harbison <matt_harbison@yahoo.com> |
---|---|
date | Fri, 04 Oct 2024 01:23:31 -0400 |
parents | db7dbe6f7bb2 |
children | 46c68c0fe137 |
rev | line source |
---|---|
47952
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
1 use crate::errors::{HgError, IoErrorContext, IoResultExt}; |
51868 | 2 use crate::exit_codes; |
3 use dyn_clone::DynClone; | |
47955
e834b79def74
rust: Switch to the memmap2-rs crate
Simon Sapin <simon.sapin@octobus.net>
parents:
47952
diff
changeset
|
4 use memmap2::{Mmap, MmapOptions}; |
51868 | 5 use std::fs::File; |
48418
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
6 use std::io::{ErrorKind, Write}; |
51868 | 7 use std::os::unix::fs::MetadataExt; |
47952
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
8 use std::path::{Path, PathBuf}; |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
9 |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
10 /// Filesystem access abstraction for the contents of a given "base" diretory |
51868 | 11 #[derive(Clone)] |
12 pub struct VfsImpl { | |
13 pub(crate) base: PathBuf, | |
47952
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
14 } |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
15 |
48199
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
16 struct FileNotFound(std::io::Error, PathBuf); |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
17 |
51868 | 18 impl VfsImpl { |
47952
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
19 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf { |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
20 self.base.join(relative_path) |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
21 } |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
22 |
48345
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
23 pub fn symlink_metadata( |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
24 &self, |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
25 relative_path: impl AsRef<Path>, |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
26 ) -> Result<std::fs::Metadata, HgError> { |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
27 let path = self.join(relative_path); |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
28 std::fs::symlink_metadata(&path).when_reading_file(&path) |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
29 } |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
30 |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
31 pub fn read_link( |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
32 &self, |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
33 relative_path: impl AsRef<Path>, |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
34 ) -> Result<PathBuf, HgError> { |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
35 let path = self.join(relative_path); |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
36 std::fs::read_link(&path).when_reading_file(&path) |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
37 } |
d5a91701f7dc
rhg: Fix status desambiguation of symlinks and executable files
Simon Sapin <simon.sapin@octobus.net>
parents:
48199
diff
changeset
|
38 |
47952
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
39 pub fn read( |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
40 &self, |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
41 relative_path: impl AsRef<Path>, |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
42 ) -> Result<Vec<u8>, HgError> { |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
43 let path = self.join(relative_path); |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
44 std::fs::read(&path).when_reading_file(&path) |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
45 } |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
46 |
49485
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
47 /// Returns `Ok(None)` if the file does not exist. |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
48 pub fn try_read( |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
49 &self, |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
50 relative_path: impl AsRef<Path>, |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
51 ) -> Result<Option<Vec<u8>>, HgError> { |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
52 match self.read(relative_path) { |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
53 Err(e) => match &e { |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
54 HgError::IoError { error, .. } => match error.kind() { |
49914
58074252db3c
rust: run `cargo clippy`
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
55 ErrorKind::NotFound => Ok(None), |
49485
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
56 _ => Err(e), |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
57 }, |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
58 _ => Err(e), |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
59 }, |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
60 Ok(v) => Ok(Some(v)), |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
61 } |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
62 } |
ffd4b1f1c9cb
rhg: add sparse support
Raphaël Gomès <rgomes@octobus.net>
parents:
48418
diff
changeset
|
63 |
48199
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
64 fn mmap_open_gen( |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
65 &self, |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
66 relative_path: impl AsRef<Path>, |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
67 ) -> Result<Result<Mmap, FileNotFound>, HgError> { |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
68 let path = self.join(relative_path); |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
69 let file = match std::fs::File::open(&path) { |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
70 Err(err) => { |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
71 if let ErrorKind::NotFound = err.kind() { |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
72 return Ok(Err(FileNotFound(err, path))); |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
73 }; |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
74 return (Err(err)).when_reading_file(&path); |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
75 } |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
76 Ok(file) => file, |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
77 }; |
51868 | 78 // Safety is "enforced" by locks and assuming other processes are |
79 // well-behaved. If any misbehaving or malicious process does touch | |
80 // the index, it could lead to corruption. This is inherent | |
81 // to file-based `mmap`, though some platforms have some ways of | |
82 // mitigating. | |
83 // TODO linux: set the immutable flag with `chattr(1)`? | |
48199
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
84 let mmap = unsafe { MmapOptions::new().map(&file) } |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
85 .when_reading_file(&path)?; |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
86 Ok(Ok(mmap)) |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
87 } |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
88 |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
89 pub fn mmap_open_opt( |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
90 &self, |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
91 relative_path: impl AsRef<Path>, |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
92 ) -> Result<Option<Mmap>, HgError> { |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
93 self.mmap_open_gen(relative_path).map(|res| res.ok()) |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
94 } |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
95 |
47952
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
96 pub fn mmap_open( |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
97 &self, |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
98 relative_path: impl AsRef<Path>, |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
99 ) -> Result<Mmap, HgError> { |
48199
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
100 match self.mmap_open_gen(relative_path)? { |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
101 Err(FileNotFound(err, path)) => Err(err).when_reading_file(&path), |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
102 Ok(res) => Ok(res), |
9d0e5629cfbf
rhg: do not fail when the repo is empty
Arseniy Alekseyev <aalekseyev@janestreet.com>
parents:
47955
diff
changeset
|
103 } |
47952
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
104 } |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
105 |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
106 pub fn rename( |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
107 &self, |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
108 relative_from: impl AsRef<Path>, |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
109 relative_to: impl AsRef<Path>, |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
110 ) -> Result<(), HgError> { |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
111 let from = self.join(relative_from); |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
112 let to = self.join(relative_to); |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
113 std::fs::rename(&from, &to) |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
114 .with_context(|| IoErrorContext::RenamingFile { from, to }) |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
115 } |
48417
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
116 |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
117 pub fn remove_file( |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
118 &self, |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
119 relative_path: impl AsRef<Path>, |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
120 ) -> Result<(), HgError> { |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
121 let path = self.join(relative_path); |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
122 std::fs::remove_file(&path) |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
123 .with_context(|| IoErrorContext::RemovingFile(path)) |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
124 } |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
125 |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
126 #[cfg(unix)] |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
127 pub fn create_symlink( |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
128 &self, |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
129 relative_link_path: impl AsRef<Path>, |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
130 target_path: impl AsRef<Path>, |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
131 ) -> Result<(), HgError> { |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
132 let link_path = self.join(relative_link_path); |
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
133 std::os::unix::fs::symlink(target_path, &link_path) |
48418
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
134 .when_writing_file(&link_path) |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
135 } |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
136 |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
137 /// Write `contents` into a temporary file, then rename to `relative_path`. |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
138 /// This makes writing to a file "atomic": a reader opening that path will |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
139 /// see either the previous contents of the file or the complete new |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
140 /// content, never a partial write. |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
141 pub fn atomic_write( |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
142 &self, |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
143 relative_path: impl AsRef<Path>, |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
144 contents: &[u8], |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
145 ) -> Result<(), HgError> { |
51868 | 146 let mut tmp = tempfile::NamedTempFile::new_in(&self.base) |
147 .when_writing_file(&self.base)?; | |
48418
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
148 tmp.write_all(contents) |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
149 .and_then(|()| tmp.flush()) |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
150 .when_writing_file(tmp.path())?; |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
151 let path = self.join(relative_path); |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
152 tmp.persist(&path) |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
153 .map_err(|e| e.error) |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
154 .when_writing_file(&path)?; |
abeae090ce67
rust: Add Vfs::write_atomic
Simon Sapin <simon.sapin@octobus.net>
parents:
48417
diff
changeset
|
155 Ok(()) |
48417
5734b03ecf3e
rhg: Initial repository locking
Simon Sapin <simon.sapin@octobus.net>
parents:
48345
diff
changeset
|
156 } |
47952
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
157 } |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
158 |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
159 fn fs_metadata( |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
160 path: impl AsRef<Path>, |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
161 ) -> Result<Option<std::fs::Metadata>, HgError> { |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
162 let path = path.as_ref(); |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
163 match std::fs::metadata(path) { |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
164 Ok(meta) => Ok(Some(meta)), |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
165 Err(error) => match error.kind() { |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
166 // TODO: when we require a Rust version where `NotADirectory` is |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
167 // stable, invert this logic and return None for it and `NotFound` |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
168 // and propagate any other error. |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
169 ErrorKind::PermissionDenied => Err(error).with_context(|| { |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
170 IoErrorContext::ReadingMetadata(path.to_owned()) |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
171 }), |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
172 _ => Ok(None), |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
173 }, |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
174 } |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
175 } |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
176 |
51868 | 177 /// Writable file object that atomically updates a file |
178 /// | |
179 /// All writes will go to a temporary copy of the original file. Call | |
180 /// [`Self::close`] when you are done writing, and [`Self`] will rename | |
181 /// the temporary copy to the original name, making the changes | |
182 /// visible. If the object is destroyed without being closed, all your | |
183 /// writes are discarded. | |
184 pub struct AtomicFile { | |
185 /// The temporary file to write to | |
186 fp: std::fs::File, | |
187 /// Path of the temp file | |
188 temp_path: PathBuf, | |
189 /// Used when stat'ing the file, is useful only if the target file is | |
190 /// guarded by any lock (e.g. repo.lock or repo.wlock). | |
191 check_ambig: bool, | |
192 /// Path of the target file | |
193 target_name: PathBuf, | |
194 /// Whether the file is open or not | |
195 is_open: bool, | |
196 } | |
197 | |
198 impl AtomicFile { | |
199 pub fn new( | |
200 fp: std::fs::File, | |
201 check_ambig: bool, | |
202 temp_name: PathBuf, | |
203 target_name: PathBuf, | |
204 ) -> Self { | |
205 Self { | |
206 fp, | |
207 check_ambig, | |
208 temp_path: temp_name, | |
209 target_name, | |
210 is_open: true, | |
211 } | |
212 } | |
213 | |
214 /// Write `buf` to the temporary file | |
215 pub fn write_all(&mut self, buf: &[u8]) -> Result<(), std::io::Error> { | |
216 self.fp.write_all(buf) | |
217 } | |
218 | |
219 fn target(&self) -> PathBuf { | |
220 self.temp_path | |
221 .parent() | |
222 .expect("should not be at the filesystem root") | |
223 .join(&self.target_name) | |
224 } | |
225 | |
226 /// Close the temporary file and rename to the target | |
227 pub fn close(mut self) -> Result<(), std::io::Error> { | |
228 self.fp.flush()?; | |
229 let target = self.target(); | |
230 if self.check_ambig { | |
231 if let Ok(stat) = std::fs::metadata(&target) { | |
232 std::fs::rename(&self.temp_path, &target)?; | |
233 let new_stat = std::fs::metadata(&target)?; | |
234 let ctime = new_stat.ctime(); | |
235 let is_ambiguous = ctime == stat.ctime(); | |
236 if is_ambiguous { | |
237 let advanced = | |
238 filetime::FileTime::from_unix_time(ctime + 1, 0); | |
239 filetime::set_file_times(target, advanced, advanced)?; | |
240 } | |
241 } else { | |
242 std::fs::rename(&self.temp_path, target)?; | |
243 } | |
244 } else { | |
245 std::fs::rename(&self.temp_path, target).unwrap(); | |
246 } | |
247 self.is_open = false; | |
248 Ok(()) | |
249 } | |
250 } | |
251 | |
252 impl Drop for AtomicFile { | |
253 fn drop(&mut self) { | |
254 if self.is_open { | |
255 std::fs::remove_file(self.target()).ok(); | |
256 } | |
257 } | |
258 } | |
259 | |
260 /// Abstracts over the VFS to allow for different implementations of the | |
261 /// filesystem layer (like passing one from Python). | |
262 pub trait Vfs: Sync + Send + DynClone { | |
263 fn open(&self, filename: &Path) -> Result<std::fs::File, HgError>; | |
264 fn open_read(&self, filename: &Path) -> Result<std::fs::File, HgError>; | |
265 fn open_check_ambig( | |
266 &self, | |
267 filename: &Path, | |
268 ) -> Result<std::fs::File, HgError>; | |
269 fn create(&self, filename: &Path) -> Result<std::fs::File, HgError>; | |
270 /// Must truncate the new file if exist | |
271 fn create_atomic( | |
272 &self, | |
273 filename: &Path, | |
274 check_ambig: bool, | |
275 ) -> Result<AtomicFile, HgError>; | |
276 fn file_size(&self, file: &File) -> Result<u64, HgError>; | |
277 fn exists(&self, filename: &Path) -> bool; | |
278 fn unlink(&self, filename: &Path) -> Result<(), HgError>; | |
279 fn rename( | |
280 &self, | |
281 from: &Path, | |
282 to: &Path, | |
283 check_ambig: bool, | |
284 ) -> Result<(), HgError>; | |
285 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError>; | |
286 } | |
287 | |
288 /// These methods will need to be implemented once `rhg` (and other) non-Python | |
289 /// users of `hg-core` start doing more on their own, like writing to files. | |
290 impl Vfs for VfsImpl { | |
291 fn open(&self, _filename: &Path) -> Result<std::fs::File, HgError> { | |
292 todo!() | |
293 } | |
294 fn open_read(&self, filename: &Path) -> Result<std::fs::File, HgError> { | |
295 let path = self.base.join(filename); | |
296 std::fs::File::open(&path).when_reading_file(&path) | |
297 } | |
298 fn open_check_ambig( | |
299 &self, | |
300 _filename: &Path, | |
301 ) -> Result<std::fs::File, HgError> { | |
302 todo!() | |
303 } | |
304 fn create(&self, _filename: &Path) -> Result<std::fs::File, HgError> { | |
305 todo!() | |
306 } | |
307 fn create_atomic( | |
308 &self, | |
309 _filename: &Path, | |
310 _check_ambig: bool, | |
311 ) -> Result<AtomicFile, HgError> { | |
312 todo!() | |
313 } | |
314 fn file_size(&self, file: &File) -> Result<u64, HgError> { | |
315 Ok(file | |
316 .metadata() | |
317 .map_err(|e| { | |
318 HgError::abort( | |
319 format!("Could not get file metadata: {}", e), | |
320 exit_codes::ABORT, | |
321 None, | |
322 ) | |
323 })? | |
324 .size()) | |
325 } | |
326 fn exists(&self, _filename: &Path) -> bool { | |
327 todo!() | |
328 } | |
329 fn unlink(&self, _filename: &Path) -> Result<(), HgError> { | |
330 todo!() | |
331 } | |
332 fn rename( | |
333 &self, | |
334 _from: &Path, | |
335 _to: &Path, | |
336 _check_ambig: bool, | |
337 ) -> Result<(), HgError> { | |
338 todo!() | |
339 } | |
340 fn copy(&self, _from: &Path, _to: &Path) -> Result<(), HgError> { | |
341 todo!() | |
342 } | |
343 } | |
344 | |
47952
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
345 pub(crate) fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> { |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
346 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir())) |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
347 } |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
348 |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
349 pub(crate) fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> { |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
350 Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file())) |
9cd35c8c6044
rust: Move VFS code to its own module
Simon Sapin <simon.sapin@octobus.net>
parents:
diff
changeset
|
351 } |
50180
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
352 |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
353 /// Returns whether the given `path` is on a network file system. |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
354 /// Taken from `cargo`'s codebase. |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
355 #[cfg(target_os = "linux")] |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
356 pub(crate) fn is_on_nfs_mount(path: impl AsRef<Path>) -> bool { |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
357 use std::ffi::CString; |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
358 use std::mem; |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
359 use std::os::unix::prelude::*; |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
360 |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
361 let path = match CString::new(path.as_ref().as_os_str().as_bytes()) { |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
362 Ok(path) => path, |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
363 Err(_) => return false, |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
364 }; |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
365 |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
366 unsafe { |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
367 let mut buf: libc::statfs = mem::zeroed(); |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
368 let r = libc::statfs(path.as_ptr(), &mut buf); |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
369 |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
370 r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32 |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
371 } |
be019ac8c1e4
dirstate-v2: don't mmap the data file when on NFS
Raphaël Gomès <rgomes@octobus.net>
parents:
49485
diff
changeset
|
372 } |
50274
0cc19a53cef4
rust: fix building on macOS (issue6801)
Dan Villiom Podlaski Christiansen <danchr@gmail.com>
parents:
50252
diff
changeset
|
373 |
0cc19a53cef4
rust: fix building on macOS (issue6801)
Dan Villiom Podlaski Christiansen <danchr@gmail.com>
parents:
50252
diff
changeset
|
374 /// Similar to what Cargo does; although detecting NFS (or non-local |
0cc19a53cef4
rust: fix building on macOS (issue6801)
Dan Villiom Podlaski Christiansen <danchr@gmail.com>
parents:
50252
diff
changeset
|
375 /// file systems) _should_ be possible on other operating systems, |
0cc19a53cef4
rust: fix building on macOS (issue6801)
Dan Villiom Podlaski Christiansen <danchr@gmail.com>
parents:
50252
diff
changeset
|
376 /// we'll just assume that mmap() works there, for now; after all, |
0cc19a53cef4
rust: fix building on macOS (issue6801)
Dan Villiom Podlaski Christiansen <danchr@gmail.com>
parents:
50252
diff
changeset
|
377 /// _some_ functionality is better than a compile error, i.e. none at |
0cc19a53cef4
rust: fix building on macOS (issue6801)
Dan Villiom Podlaski Christiansen <danchr@gmail.com>
parents:
50252
diff
changeset
|
378 /// all |
0cc19a53cef4
rust: fix building on macOS (issue6801)
Dan Villiom Podlaski Christiansen <danchr@gmail.com>
parents:
50252
diff
changeset
|
379 #[cfg(not(target_os = "linux"))] |
0cc19a53cef4
rust: fix building on macOS (issue6801)
Dan Villiom Podlaski Christiansen <danchr@gmail.com>
parents:
50252
diff
changeset
|
380 pub(crate) fn is_on_nfs_mount(_path: impl AsRef<Path>) -> bool { |
0cc19a53cef4
rust: fix building on macOS (issue6801)
Dan Villiom Podlaski Christiansen <danchr@gmail.com>
parents:
50252
diff
changeset
|
381 false |
0cc19a53cef4
rust: fix building on macOS (issue6801)
Dan Villiom Podlaski Christiansen <danchr@gmail.com>
parents:
50252
diff
changeset
|
382 } |