char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somrearrange libs into lib and extensions into extsf4a0f5b

main
10.8 KiB272 linesraw
1use super::*;
2
3pub(super) fn resolve(
4    caps: &Capabilities,
5    label: &str,
6    entry: bool,
7    write: bool,
8) -> io::Result<PathTarget> {
9    if label.is_empty() {
10        return Err(io::Error::new(
11            io::ErrorKind::NotFound,
12            "Empty filesystem path",
13        ));
14    }
15    let path = filesystem_path(caps, label)?;
16    let target = if entry {
17        PathTarget::for_entry(path)
18    } else {
19        PathTarget::for_creation(path)
20    };
21    let target = match target {
22        Ok(target) => target,
23        Err(_)
24            if !(if write {
25                caps.unrestricted_write()
26            } else {
27                caps.unrestricted_read()
28            }) =>
29        {
30            return Err(denied(label, write));
31        }
32        Err(error) => return Err(error),
33    };
34    authorize(caps, &target, label, write)?;
35    Ok(target)
36}
37
38pub(super) fn tree(
39    caps: &Capabilities,
40    target: &PathTarget,
41    label: &str,
42    write: bool,
43) -> io::Result<()> {
44    if write {
45        caps.check_write_tree(target)
46    } else {
47        caps.check_read_tree(target)
48    }
49    .map_err(|_| {
50        io::Error::new(
51            io::ErrorKind::PermissionDenied,
52            format!("{} tree {label}", if write { "write" } else { "read" }),
53        )
54    })
55}
56
57pub(super) fn follows_link(label: &str) -> bool {
58    // A trailing slash names the referent, not the link.
59    label.ends_with('/')
60        || label.ends_with("/.")
61        || (cfg!(windows) && (label.ends_with('\\') || label.ends_with("\\.")))
62}
63
64pub(super) fn mutable_entry(target: &PathTarget) -> io::Result<(Dir, OsString)> {
65    let (dir, name) = parent(target.as_path())?;
66    if name == "." {
67        return Err(io::Error::new(
68            io::ErrorKind::InvalidInput,
69            "Cannot mutate a filesystem root",
70        ));
71    }
72    Ok((dir, name))
73}
74
75fn regular(metadata: &Metadata) -> io::Result<()> {
76    if metadata.is_file() {
77        Ok(())
78    } else {
79        Err(io::Error::new(
80            io::ErrorKind::Unsupported,
81            "not yet implemented: copying non-regular files",
82        ))
83    }
84}
85
86pub(super) fn entries(
87    caps: &Capabilities,
88    path: &str,
89    whole_tree: bool,
90) -> io::Result<Vec<(String, bool, bool, bool)>> {
91    let target = resolve(caps, path, false, false)?;
92    if whole_tree {
93        tree(caps, &target, path, false)?;
94    }
95    let (parent, name) = parent(target.as_path())?;
96    let directory = parent.open_dir_nofollow(name)?;
97    let mut entries = Vec::new();
98    for entry in directory.entries()? {
99        let entry = entry?;
100        let name = entry.file_name();
101        if !whole_tree && caps.check_read(&target.child(&name)?).is_err() {
102            continue;
103        }
104        let name = name.into_string().map_err(|_| {
105            io::Error::new(
106                io::ErrorKind::Unsupported,
107                "not yet implemented: non-UTF-8 directory entries",
108            )
109        })?;
110        let kind = entry.file_type()?;
111        entries.push((name, kind.is_file(), kind.is_dir(), kind.is_symlink()));
112    }
113    Ok(entries)
114}
115
116pub(super) fn ops(files: Arc<Files>) -> Vec<Op> {
117    let ops = files.clone();
118    let read_dir = files.clone();
119    let mkdir = files.clone();
120    let remove = files.clone();
121    let rename = files.clone();
122    vec![
123        ops.io_ops("op_file_read_dir", "op_file_read_dir_sync", move |path: String| {
124            let caps = read_dir.caps.clone();
125            let pending = read_dir.pool.run(move || entries(&caps, &path, false));
126            async move { Ok::<_, Infallible>(receive(pending).await) }
127        }),
128        ops.io_ops("op_file_mkdir", "op_file_mkdir_sync",
129            move |(path, mode): (String, Option<u32>)| {
130                let caps = mkdir.caps.clone();
131                let pending = mkdir.pool.run(move || {
132                    if mode.is_some_and(|mode| mode > 0o7777) {
133                        return Err(io::Error::new(
134                            io::ErrorKind::InvalidInput,
135                            "Invalid directory mode",
136                        ));
137                    }
138                    let target = resolve(&caps, &path, true, true)?;
139                    let (directory, name) = mutable_entry(&target)?;
140                    let mut builder = cap_std::fs::DirBuilder::new();
141                    #[cfg(unix)]
142                    {
143                        use cap_std::fs::DirBuilderExt;
144                        if let Some(mode) = mode {
145                            builder.mode(mode);
146                        }
147                    }
148                    authorize(&caps, &target, &path, true)?;
149                    directory.create_dir_with(name, &builder)?;
150                    Ok(true)
151                });
152                async move { Ok::<_, Infallible>(receive(pending).await) }
153            },
154        ),
155        ops.io_ops("op_file_remove", "op_file_remove_sync",
156            move |(path, recursive): (String, bool)| {
157                let caps = remove.caps.clone();
158                let pending = remove.pool.run(move || {
159                    let target = resolve(&caps, &path, true, true)?;
160                    if recursive {
161                        tree(&caps, &target, &path, true)?;
162                    }
163                    let (directory, name) = mutable_entry(&target)?;
164                    let metadata = directory.symlink_metadata(&name)?;
165                    authorize(&caps, &target, &path, true)?;
166                    if metadata.is_dir() {
167                        if recursive {
168                            directory.remove_dir_all(name)?;
169                        } else {
170                            directory.remove_dir(name)?;
171                        }
172                    } else {
173                        directory.remove_file_or_symlink(name)?;
174                    }
175                    Ok(true)
176                });
177                async move { Ok::<_, Infallible>(receive(pending).await) }
178            },
179        ),
180        ops.io_ops("op_file_rename", "op_file_rename_sync",
181            move |(source, destination): (String, String)| {
182                let caps = rename.caps.clone();
183                let pending = rename.pool.run(move || {
184                    let _guard = super::links::MUTATIONS
185                        .lock()
186                        .unwrap_or_else(std::sync::PoisonError::into_inner);
187                    let from = resolve(&caps, &source, true, false)?;
188                    let to = resolve(&caps, &destination, true, true)?;
189                    // rename cannot atomically insist that a concurrently replaced leaf is a file.
190                    tree(&caps, &from, &source, false)?;
191                    tree(&caps, &from, &source, true)?;
192                    tree(&caps, &to, &destination, true)?;
193                    let (from_dir, from_name) = mutable_entry(&from)?;
194                    let (to_dir, to_name) = mutable_entry(&to)?;
195                    let metadata = from_dir.symlink_metadata(&from_name)?;
196                    if metadata.is_symlink() {
197                        let text = from_dir.read_link_contents(&from_name)?;
198                        super::links::authorize_target(&caps, &to, &text, &destination)?;
199                    } else if metadata.is_dir()
200                        && !(caps.unrestricted_read() && caps.unrestricted_write())
201                        && super::links::contains_links(from_dir.open_dir_nofollow(&from_name)?)?
202                    {
203                        return Err(io::Error::new(
204                            io::ErrorKind::Unsupported,
205                            "not yet implemented: rebasing directory symlinks with scoped permissions",
206                        ));
207                    }
208                    from_dir.rename(from_name, &to_dir, to_name)?;
209                    Ok(true)
210                });
211                async move { Ok::<_, Infallible>(receive(pending).await) }
212            },
213        ),
214        ops.io_ops("op_file_copy", "op_file_copy_sync",
215            move |(source, destination): (String, String)| {
216                let caps = files.caps.clone();
217                let pending = files.pool.run(move || {
218                    let from = resolve(&caps, &source, false, false)?;
219                    let to = resolve(&caps, &destination, false, true)?;
220                    let (from_dir, from_name) = parent(from.as_path())?;
221                    regular(&from_dir.symlink_metadata(&from_name)?)?;
222                    let mut read_options = OpenOptions::new();
223                    read_options
224                        .read(true)
225                        .follow(FollowSymlinks::No)
226                        .nonblock(true);
227                    let source_file = from_dir.open_with(from_name, &read_options)?;
228                    let metadata = source_file.metadata()?;
229                    regular(&metadata)?;
230                    let (to_dir, to_name) = mutable_entry(&to)?;
231                    match to_dir.symlink_metadata(&to_name) {
232                        Ok(metadata) => regular(&metadata)?,
233                        Err(error) if error.kind() == io::ErrorKind::NotFound => (),
234                        Err(error) => return Err(error),
235                    }
236                    authorize(&caps, &to, &destination, true)?;
237                    let mut write_options = OpenOptions::new();
238                    write_options
239                        .write(true)
240                        .create(true)
241                        .follow(FollowSymlinks::No)
242                        .nonblock(true);
243                    let mut permissions = metadata.permissions();
244                    #[cfg(unix)]
245                    {
246                        use cap_std::fs::{MetadataExt, OpenOptionsExt, PermissionsExt};
247                        let mode = metadata.mode() & 0o777;
248                        write_options.mode(mode);
249                        permissions.set_mode(mode);
250                    }
251                    let destination_file = to_dir.open_with(to_name, &write_options)?;
252                    regular(&destination_file.metadata()?)?;
253                    let a = same_file::Handle::from_file(source_file.try_clone()?.into_std())?;
254                    let b = same_file::Handle::from_file(destination_file.try_clone()?.into_std())?;
255                    if a == b {
256                        return Err(io::Error::new(
257                            io::ErrorKind::InvalidInput,
258                            "Source and destination are the same file",
259                        ));
260                    }
261                    destination_file.set_permissions(permissions)?;
262                    destination_file.set_len(0)?;
263                    let mut source_file = source_file.into_std();
264                    let mut destination_file = destination_file.into_std();
265                    io::copy(&mut source_file, &mut destination_file)?;
266                    Ok(true)
267                });
268                async move { Ok::<_, Infallible>(receive(pending).await) }
269            },
270        ),
271    ].into_iter().flatten().collect()
272}