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
11.1 KiB255 linesraw
1use super::paths::{resolve, tree};
2use super::*;
3
4fn overlaps(directory: &Dir, path: &Path) -> io::Result<bool> {
5    let root = same_file::Handle::from_file(directory.try_clone()?.into_std_file())?;
6    for ancestor in path.ancestors() {
7        let candidate = parent(ancestor).and_then(|(parent, name)| parent.open_dir_nofollow(name));
8        match candidate {
9            Ok(candidate) => {
10                let candidate = same_file::Handle::from_file(candidate.into_std_file())?;
11                if root == candidate {
12                    return Ok(true);
13                }
14            }
15            Err(error) if error.kind() == io::ErrorKind::NotFound => (),
16            Err(error) => return Err(error),
17        }
18    }
19    Ok(false)
20}
21
22pub(super) fn ops(files: Arc<Files>) -> Vec<Op> {
23    let ops = files.clone();
24    let ensure = files.clone();
25    let prepare = files.clone();
26    let read_dir = files.clone();
27    let dir_mode = files.clone();
28    vec![
29        ops.io_ops(
30            "op_file_read_tree_dir",
31            "op_file_read_tree_dir_sync",
32            move |path: String| {
33                let caps = read_dir.caps.clone();
34                let pending = read_dir
35                    .pool
36                    .run(move || super::paths::entries(&caps, &path, true));
37                async move { Ok::<_, Infallible>(receive(pending).await) }
38            },
39        ),
40        ops.io_ops(
41            "op_file_copy_dir_mode",
42            "op_file_copy_dir_mode_sync",
43            move |(path, mode): (String, Option<u32>)| {
44                let caps = dir_mode.caps.clone();
45                let pending = dir_mode.pool.run(move || {
46                    let target = resolve(&caps, &path, false, true)?;
47                    tree(&caps, &target, &path, true)?;
48                    let (directory, name) = parent(target.as_path())?;
49                    #[cfg(unix)]
50                    {
51                        use std::os::unix::fs::PermissionsExt;
52                        if let Some(mode) = mode {
53                            if mode > 0o777 {
54                                return Err(io::Error::new(
55                                    io::ErrorKind::InvalidInput,
56                                    "Invalid copied directory mode",
57                                ));
58                            }
59                            use cap_std::fs::OpenOptionsExt;
60                            let mut flags = OpenOptions::new();
61                            flags
62                                .read(true)
63                                .follow(FollowSymlinks::No)
64                                .nonblock(true)
65                                .custom_flags(libc::O_DIRECTORY);
66                            let file = directory.open_with(name, &flags)?;
67                            if !file.metadata()?.is_dir() {
68                                return Err(io::Error::new(
69                                    io::ErrorKind::NotADirectory,
70                                    "Copy destination is not a directory",
71                                ));
72                            }
73                            file.into_std()
74                                .set_permissions(std::fs::Permissions::from_mode(mode))?;
75                        }
76                    }
77                    #[cfg(not(unix))]
78                    let _ = (directory.open_dir_nofollow(name)?, mode);
79                    Ok(true)
80                });
81                async move { Ok::<_, Infallible>(receive(pending).await) }
82            },
83        ),
84        vec![Op::new("op_file_tmp_dir", |(): ()| {
85            let dir = std::env::temp_dir()
86                .into_os_string()
87                .into_string()
88                .map_err(|_| {
89                    io::Error::new(
90                        io::ErrorKind::Unsupported,
91                        "not yet implemented: non-UTF-8 temp directory",
92                    )
93                });
94            Ok::<_, Infallible>(response(dir))
95        })],
96        vec![Op::new("op_file_path_parent", move |path: String| {
97            let parsed = filesystem_path(&files.caps, &path).and_then(|path| {
98                path.parent()
99                    .map(|parent| {
100                        parent.to_str().map(str::to_owned).ok_or_else(|| {
101                            io::Error::new(
102                                io::ErrorKind::Unsupported,
103                                "not yet implemented: non-UTF-8 paths",
104                            )
105                        })
106                    })
107                    .transpose()?
108                    .ok_or_else(|| {
109                        io::Error::new(io::ErrorKind::InvalidInput, "Filesystem root has no parent")
110                    })
111            });
112            Ok::<_, Infallible>(response(parsed))
113        })],
114        vec![Op::new(
115            "op_file_path_join",
116            |(path, name): (String, String)| {
117                let parsed = (|| {
118                    let mut components = Path::new(&name).components();
119                    if !matches!(components.next(), Some(Component::Normal(_)))
120                        || components.next().is_some()
121                    {
122                        return Err(io::Error::new(
123                            io::ErrorKind::InvalidInput,
124                            "Expected a single entry name",
125                        ));
126                    }
127                    Path::new(&path)
128                        .join(name)
129                        .into_os_string()
130                        .into_string()
131                        .map_err(|_| {
132                            io::Error::new(
133                                io::ErrorKind::Unsupported,
134                                "not yet implemented: non-UTF-8 paths",
135                            )
136                        })
137                })();
138                Ok::<_, Infallible>(response(parsed))
139            },
140        )],
141        ops.io_ops(
142            "op_file_ensure_dir",
143            "op_file_ensure_dir_sync",
144            move |(path, mode): (String, Option<u32>)| {
145                let caps = ensure.caps.clone();
146                let pending = ensure.pool.run(move || {
147                    if mode.is_some_and(|mode| mode > 0o7777) {
148                        return Err(io::Error::new(
149                            io::ErrorKind::InvalidInput,
150                            "Invalid directory mode",
151                        ));
152                    }
153                    let target = resolve(&caps, &path, false, true)?;
154                    let (directory, name) = parent(target.as_path())?;
155                    let mut builder = cap_std::fs::DirBuilder::new();
156                    #[cfg(unix)]
157                    {
158                        use cap_std::fs::DirBuilderExt;
159                        if let Some(mode) = mode {
160                            builder.mode(mode);
161                        }
162                    }
163                    match directory.create_dir_with(&name, &builder) {
164                        Ok(()) => (),
165                        Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {
166                            directory.open_dir_nofollow(name)?;
167                        }
168                        Err(error) => return Err(error),
169                    }
170                    Ok(true)
171                });
172                async move { Ok::<_, Infallible>(receive(pending).await) }
173            },
174        ),
175        ops.io_ops(
176            "op_file_prepare_copy",
177            "op_file_prepare_copy_sync",
178            move |(source, destination, moving, dereference): (String, String, bool, bool)| {
179                let caps = prepare.caps.clone();
180                let pending = prepare.pool.run(move || {
181                    let from = resolve(&caps, &source, !dereference, false)?;
182                    let (from_parent, name) = parent(from.as_path())?;
183                    let mut metadata = from_parent.symlink_metadata(&name)?;
184                    if dereference && metadata.is_symlink() {
185                        return Err(io::Error::new(
186                            io::ErrorKind::NotFound,
187                            "Symbolic link changed or has a missing target",
188                        ));
189                    }
190                    let to = resolve(&caps, &destination, metadata.is_symlink(), true)?;
191                    if !(metadata.is_symlink() || metadata.is_file() || metadata.is_dir()) {
192                        return Err(io::Error::new(
193                            io::ErrorKind::Unsupported,
194                            "not yet implemented: copying non-regular files",
195                        ));
196                    }
197                    if moving || metadata.is_dir() {
198                        tree(&caps, &from, &source, false)?;
199                        tree(&caps, &to, &destination, true)?;
200                    }
201                    if moving {
202                        tree(&caps, &from, &source, true)?;
203                    }
204                    let mut identity = None;
205                    if metadata.is_dir() {
206                        let source_dir = from_parent.open_dir_nofollow(name)?;
207                        metadata = source_dir.dir_metadata()?;
208                        {
209                            use cap_fs_ext::MetadataExt;
210                            identity = Some(format!("{:x}:{:x}", metadata.dev(), metadata.ino()));
211                        }
212                        if overlaps(&source_dir, to.as_path())? {
213                            return Err(io::Error::new(
214                                io::ErrorKind::InvalidInput,
215                                "Source and destination trees overlap",
216                            ));
217                        }
218                        let destination_dir = parent(to.as_path())
219                            .and_then(|(parent, name)| parent.open_dir_nofollow(name));
220                        match destination_dir {
221                            Ok(destination_dir) if overlaps(&destination_dir, from.as_path())? => {
222                                return Err(io::Error::new(
223                                    io::ErrorKind::InvalidInput,
224                                    "Source and destination trees overlap",
225                                ));
226                            }
227                            Ok(_) => (),
228                            Err(error) if error.kind() == io::ErrorKind::NotFound => (),
229                            Err(error) => return Err(error),
230                        }
231                    }
232                    #[cfg(unix)]
233                    let mode = {
234                        use cap_std::fs::MetadataExt;
235                        Some(metadata.mode() & 0o777)
236                    };
237                    #[cfg(not(unix))]
238                    let mode: Option<u32> = None;
239                    let kind: u8 = if metadata.is_symlink() {
240                        2
241                    } else if metadata.is_dir() {
242                        1
243                    } else {
244                        0
245                    };
246                    Ok((kind, mode, identity, super::links::text(from.into_path())?))
247                });
248                async move { Ok::<_, Infallible>(receive(pending).await) }
249            },
250        ),
251    ]
252    .into_iter()
253    .flatten()
254    .collect()
255}