Skip to main content

lux_lib/fs/
mod.rs

1use std::path::Path;
2
3#[cfg(unix)]
4use std::os::unix::fs::PermissionsExt;
5
6use miette::Diagnostic;
7use thiserror::Error;
8
9pub mod sync;
10pub mod tempfile;
11pub mod tokio;
12#[cfg(unix)]
13pub mod unix;
14#[cfg(windows)]
15pub mod windows;
16
17pub type Result<T> = core::result::Result<T, Error>;
18
19#[derive(Debug, Error, Diagnostic)]
20#[non_exhaustive]
21pub enum Error {
22    #[error("failed to read file '{}'", path.display())]
23    #[diagnostic(
24        code(lux_lib::fs::read),
25        help("ensure '{}' exists and is readable", path.display())
26    )]
27    Read {
28        path: std::path::PathBuf,
29        source: std::io::Error,
30    },
31    #[error("failed to read '{}' as UTF-8 string", path.display())]
32    #[diagnostic(
33        code(lux_lib::fs::read_to_string),
34        help("ensure '{}' exists, is readable, and contains valid UTF-8", path.display())
35    )]
36    ReadToString {
37        path: std::path::PathBuf,
38        source: std::io::Error,
39    },
40    #[error("failed to write to '{}'", path.display())]
41    #[diagnostic(
42        code(lux_lib::fs::write),
43        help("ensure the parent directory of '{}' exists and is writable", path.display())
44    )]
45    Write {
46        path: std::path::PathBuf,
47        source: std::io::Error,
48    },
49    #[error("failed to copy '{}' to '{}'", from.display(), to.display())]
50    #[diagnostic(
51        code(lux_lib::fs::copy),
52        help("ensure '{}' exists and the parent directory of '{}' is writable", from.display(), to.display())
53    )]
54    Copy {
55        from: std::path::PathBuf,
56        to: std::path::PathBuf,
57        source: std::io::Error,
58    },
59    #[error("failed to create directory '{}'", path.display())]
60    #[diagnostic(
61        code(lux_lib::fs::create_dir),
62        help("ensure the parent directory of '{}' exists and is writable", path.display())
63    )]
64    CreateDir {
65        path: std::path::PathBuf,
66        source: std::io::Error,
67    },
68    #[error("failed to create a temporaty directory")]
69    #[diagnostic(
70        code(lux_lib::fs::create_tempdir),
71        help("ensure the '{}' exists and is writable", std::env::temp_dir().display())
72    )]
73    CreateTempDir { source: std::io::Error },
74    #[error("failed to create directory tree '{}'", path.display())]
75    #[diagnostic(
76        code(lux_lib::fs::create_dir_all),
77        help("ensure the path to '{}' is accessible", path.display())
78    )]
79    CreateDirAll {
80        path: std::path::PathBuf,
81        source: std::io::Error,
82    },
83    #[error("failed to remove file '{}'", path.display())]
84    #[diagnostic(
85        code(lux_lib::fs::remove_file),
86        help("ensure '{}' exists and is writable", path.display())
87    )]
88    RemoveFile {
89        path: std::path::PathBuf,
90        source: std::io::Error,
91    },
92    #[error("failed to remove directory '{}'", path.display())]
93    #[diagnostic(
94        code(lux_lib::fs::remove_dir_all),
95        help("ensure '{}' exists and is writable", path.display())
96    )]
97    RemoveDirAll {
98        path: std::path::PathBuf,
99        source: std::io::Error,
100    },
101    #[error("failed to read directory '{}'", path.display())]
102    #[diagnostic(
103        code(lux_lib::fs::read_dir),
104        help("ensure '{}' exists and is a directory", path.display())
105    )]
106    ReadDir {
107        path: std::path::PathBuf,
108        source: std::io::Error,
109    },
110    #[error("failed to get metadata for '{}'", path.display())]
111    #[diagnostic(
112        code(lux_lib::fs::metadata),
113        help("ensure '{}' exists", path.display())
114    )]
115    Metadata {
116        path: std::path::PathBuf,
117        source: std::io::Error,
118    },
119    #[error("failed to rename '{}' to '{}'", from.display(), to.display())]
120    #[diagnostic(
121        code(lux_lib::fs::rename),
122        help("ensure '{}' exists and the parent directory of '{}' is writable", from.display(), to.display())
123    )]
124    Rename {
125        from: std::path::PathBuf,
126        to: std::path::PathBuf,
127        source: std::io::Error,
128    },
129    #[error("failed to set permissions on '{}'", path.display())]
130    #[diagnostic(
131        code(lux_lib::fs::set_permissions),
132        help("ensure '{}' exists and is writable", path.display())
133    )]
134    SetPermissions {
135        path: std::path::PathBuf,
136        source: std::io::Error,
137    },
138    #[error("failed to open file '{}'", path.display())]
139    #[diagnostic(
140        code(lux_lib::fs::file_open),
141        help("ensure '{}' exists and is readable", path.display())
142    )]
143    FileOpen {
144        path: std::path::PathBuf,
145        source: std::io::Error,
146    },
147    #[error("failed to create file '{}'", path.display())]
148    #[diagnostic(
149        code(lux_lib::fs::file_create),
150        help("ensure the parent directory of '{}' exists and is writable", path.display())
151    )]
152    FileCreate {
153        path: std::path::PathBuf,
154        source: std::io::Error,
155    },
156    #[error("failed to get the absolute path for '{}'", path.display())]
157    #[diagnostic(code(lux_lib::fs::get_absolute_path))]
158    GetAbsolutePath {
159        path: std::path::PathBuf,
160        source: std::io::Error,
161    },
162    #[error("failed to create symlink '{}' pointing to '{}'", link.display(), target.display())]
163    #[diagnostic(
164        code(lux_lib::fs::symlink),
165        help("ensure the parent directory of '{}' exists and is writable", link.display())
166    )]
167    Symlink {
168        target: std::path::PathBuf,
169        link: std::path::PathBuf,
170        source: std::io::Error,
171    },
172    #[error("failed to create hard link '{}' pointing to '{}'", to.display(), from.display())]
173    #[diagnostic(
174        code(lux_lib::fs::hard_link),
175        help("ensure '{}' is on a filesystem that supports hard links", to.display())
176    )]
177    HardLink {
178        from: std::path::PathBuf,
179        to: std::path::PathBuf,
180        source: std::io::Error,
181    },
182    #[error("{0}")]
183    Other(String),
184}
185
186/// Ensures the file at `path` is writable by its owner, so that it can be
187/// overwritten on a later install, even when the source is read-only
188/// (e.g. a vendored rock in a read-only Nix store).
189#[cfg(unix)]
190pub(crate) async fn make_writable(path: &Path) -> Result<()> {
191    let mut perms = self::tokio::metadata(path).await?.permissions();
192    perms.set_mode(perms.mode() | 0o200);
193    self::tokio::set_permissions(path, perms).await?;
194    Ok(())
195}
196
197#[cfg(not(unix))]
198pub(crate) async fn make_writable(_path: &Path) -> Result<()> {
199    Ok(())
200}
201
202/// See [`make_writable`].
203#[cfg(unix)]
204pub(crate) fn make_writable_sync(path: &Path) -> Result<()> {
205    let mut perms = std::fs::metadata(path)
206        .map_err(|source| Error::Metadata {
207            path: path.to_path_buf(),
208            source,
209        })?
210        .permissions();
211    perms.set_mode(perms.mode() | 0o200);
212    std::fs::set_permissions(path, perms).map_err(|source| Error::SetPermissions {
213        path: path.to_path_buf(),
214        source,
215    })?;
216    Ok(())
217}
218
219#[cfg(not(unix))]
220pub(crate) fn make_writable_sync(_path: &Path) -> Result<()> {
221    Ok(())
222}
223
224#[cfg(unix)]
225#[tracing::instrument(level = "trace")]
226pub(crate) async fn set_executable_permissions(script: &Path) -> Result<()> {
227    let mut perms = self::tokio::metadata(script).await?.permissions();
228    perms.set_mode(perms.mode() | 0o111);
229    self::tokio::set_permissions(script, perms).await?;
230    Ok(())
231}