d_major/
crawl.rs

1/*
2 * Description: Crawl the filesystem in parallel.
3 *
4 * Copyright (C) 2025 d@nny mc² <dmc2@hypnicjerk.ai>
5 * SPDX-License-Identifier: LGPL-3.0-or-later
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published
9 * by the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19 */
20
21//! Crawl the filesystem in parallel.
22
23
24pub mod traits {
25  use crate::vfs::traits;
26
27  pub trait RecvErr<Err> {
28    fn signal(&self, err: Err);
29  }
30
31  pub trait RecvStat<'vfs, VFS, Err>: RecvErr<Err>
32  where
33    VFS: traits::VFS,
34    Err: From<VFS::Err>,
35  {
36    fn stat<'s>(&self, ctx: VFS::Ctx<'vfs>, rel: VFS::PathRef<'s>) -> Result<VFS::Stat, VFS::Err>;
37  }
38
39  #[derive(Clone, Debug)]
40  pub struct HydratedLink<P> {
41    pub relpath: P,
42    pub target: P,
43  }
44
45  #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
46  pub enum BrokenLinkBehavior {
47    #[default]
48    IgnoreBroken,
49    ErrorOnBroken,
50  }
51
52  pub trait RecvLink<'vfs, VFS, Err>: RecvStat<'vfs, VFS, Err>
53  where
54    VFS: traits::VFS,
55    Err: From<VFS::Err>,
56  {
57    fn read_link<'s>(
58      &self,
59      ctx: VFS::Ctx<'vfs>,
60      rel: VFS::PathRef<'s>,
61    ) -> Result<VFS::OwnedPath, VFS::Err>;
62    fn send_link(
63      &self,
64      ctx: VFS::Ctx<'vfs>,
65      link: HydratedLink<VFS::OwnedPath>,
66      opts: BrokenLinkBehavior,
67    );
68  }
69
70  pub trait RecvFile<'vfs, VFS, Err>: RecvStat<'vfs, VFS, Err>
71  where
72    VFS: traits::VFS,
73    Err: From<VFS::Err>,
74  {
75    fn send_file(
76      &self,
77      ctx: VFS::Ctx<'vfs>,
78      rel: VFS::OwnedPath,
79      stat: Option<VFS::Stat>,
80      opts: VFS::FileOptions,
81    );
82  }
83
84  pub trait RecvDir<'vfs, VFS, Err>: RecvStat<'vfs, VFS, Err>
85  where
86    VFS: traits::VFS,
87    Err: From<VFS::Err>,
88  {
89    fn send_dir(&self, ctx: VFS::Ctx<'vfs>, rel: VFS::OwnedPath, stat: Option<VFS::Stat>);
90  }
91
92  pub trait RecvDirEntry<'vfs, VFS, Err>: RecvStat<'vfs, VFS, Err>
93  where
94    VFS: traits::VFS,
95    Err: From<VFS::Err>,
96  {
97    fn owned_name<'dir, 's>(
98      name: <<VFS::Dir<'vfs> as traits::DirectoryStream>::DirEntry<'dir> as traits::DirectoryEntry>::PathRef<'s>,
99    ) -> <<VFS::Dir<'vfs> as traits::DirectoryStream>::DirEntry<'dir> as traits::DirectoryEntry>::OwnedPath;
100    fn send_dir_entry(
101      &self,
102      ctx: VFS::Ctx<'vfs>,
103      file_type: traits::FileType,
104      name: <<VFS::Dir<'vfs> as traits::DirectoryStream>::DirEntry<'static> as traits::DirectoryEntry>::OwnedPath,
105      stat: Option<VFS::Stat>,
106    );
107  }
108}
109
110
111pub mod channels {
112  use std::sync::mpsc;
113
114  use super::traits;
115
116  #[derive(Debug, Clone)]
117  pub struct RecvErr<Err> {
118    sender: mpsc::SyncSender<Err>,
119  }
120
121  impl<Err> traits::RecvErr<Err> for RecvErr<Err> {
122    fn signal(&self, err: Err) { self.sender.send(err).unwrap(); }
123  }
124
125  #[derive(Debug, Clone)]
126  pub struct RecvStat<'vfs, VFS, Err> {
127    vfs: &'vfs VFS,
128    err: RecvErr<Err>,
129  }
130
131  impl<'vfs, VFS, Err> traits::RecvErr<Err> for RecvStat<'vfs, VFS, Err> {
132    fn signal(&self, err: Err) { self.err.signal(err); }
133  }
134
135  impl<'vfs, VFS, Err> traits::RecvStat<'vfs, VFS, Err> for RecvStat<'vfs, VFS, Err>
136  where
137    VFS: crate::vfs::traits::VFS,
138    Err: From<VFS::Err>,
139  {
140    fn stat<'s>(&self, ctx: VFS::Ctx<'vfs>, rel: VFS::PathRef<'s>) -> Result<VFS::Stat, VFS::Err> {
141      self.vfs.stat(ctx, rel)
142    }
143  }
144
145  #[derive(Debug, Clone)]
146  pub struct RecvLink<'vfs, VFS, Err, Link> {
147    stat: RecvStat<'vfs, VFS, Err>,
148    sender: mpsc::Sender<Link>,
149  }
150
151  impl<'vfs, VFS, Err, Link> traits::RecvErr<Err> for RecvLink<'vfs, VFS, Err, Link> {
152    fn signal(&self, err: Err) { self.stat.signal(err); }
153  }
154
155  impl<'vfs, VFS, Err, Link> traits::RecvStat<'vfs, VFS, Err> for RecvLink<'vfs, VFS, Err, Link>
156  where
157    VFS: crate::vfs::traits::VFS,
158    Err: From<VFS::Err>,
159  {
160    fn stat<'s>(&self, ctx: VFS::Ctx<'vfs>, rel: VFS::PathRef<'s>) -> Result<VFS::Stat, VFS::Err> {
161      self.stat.stat(ctx, rel)
162    }
163  }
164
165  impl<'vfs, VFS, Err> traits::RecvLink<'vfs, VFS, Err>
166    for RecvLink<
167      'vfs,
168      VFS,
169      Err,
170      (
171        VFS::Ctx<'vfs>,
172        traits::HydratedLink<VFS::OwnedPath>,
173        traits::BrokenLinkBehavior,
174      ),
175    >
176  where
177    VFS: crate::vfs::traits::VFS,
178    Err: From<VFS::Err>,
179  {
180    fn read_link<'s>(
181      &self,
182      ctx: VFS::Ctx<'vfs>,
183      rel: VFS::PathRef<'s>,
184    ) -> Result<VFS::OwnedPath, VFS::Err> {
185      self.stat.vfs.read_link(ctx, rel)
186    }
187    fn send_link(
188      &self,
189      ctx: VFS::Ctx<'vfs>,
190      link: traits::HydratedLink<VFS::OwnedPath>,
191      opts: traits::BrokenLinkBehavior,
192    ) {
193      self.sender.send((ctx, link, opts)).unwrap();
194    }
195  }
196
197  #[derive(Debug, Clone)]
198  pub struct RecvFile<'vfs, VFS, Err, File> {
199    stat: RecvStat<'vfs, VFS, Err>,
200    sender: mpsc::Sender<File>,
201  }
202
203  impl<'vfs, VFS, Err, File> traits::RecvErr<Err> for RecvFile<'vfs, VFS, Err, File> {
204    fn signal(&self, err: Err) { self.stat.signal(err); }
205  }
206
207  impl<'vfs, VFS, Err, File> traits::RecvStat<'vfs, VFS, Err> for RecvFile<'vfs, VFS, Err, File>
208  where
209    VFS: crate::vfs::traits::VFS,
210    Err: From<VFS::Err>,
211  {
212    fn stat<'s>(&self, ctx: VFS::Ctx<'vfs>, rel: VFS::PathRef<'s>) -> Result<VFS::Stat, VFS::Err> {
213      self.stat.stat(ctx, rel)
214    }
215  }
216
217  impl<'vfs, VFS, Err> traits::RecvFile<'vfs, VFS, Err>
218    for RecvFile<
219      'vfs,
220      VFS,
221      Err,
222      (
223        VFS::Ctx<'vfs>,
224        VFS::OwnedPath,
225        Option<VFS::Stat>,
226        VFS::FileOptions,
227      ),
228    >
229  where
230    VFS: crate::vfs::traits::VFS,
231    Err: From<VFS::Err>,
232  {
233    fn send_file(
234      &self,
235      ctx: VFS::Ctx<'vfs>,
236      rel: VFS::OwnedPath,
237      stat: Option<VFS::Stat>,
238      opts: VFS::FileOptions,
239    ) {
240      self.sender.send((ctx, rel, stat, opts)).unwrap();
241    }
242  }
243
244  #[derive(Debug, Clone)]
245  pub struct RecvDir<'vfs, VFS, Err, Dir> {
246    stat: RecvStat<'vfs, VFS, Err>,
247    sender: mpsc::Sender<Dir>,
248  }
249
250  impl<'vfs, VFS, Err, Dir> traits::RecvErr<Err> for RecvDir<'vfs, VFS, Err, Dir> {
251    fn signal(&self, err: Err) { self.stat.signal(err); }
252  }
253
254  impl<'vfs, VFS, Err, Dir> traits::RecvStat<'vfs, VFS, Err> for RecvDir<'vfs, VFS, Err, Dir>
255  where
256    VFS: crate::vfs::traits::VFS,
257    Err: From<VFS::Err>,
258  {
259    fn stat<'s>(&self, ctx: VFS::Ctx<'vfs>, rel: VFS::PathRef<'s>) -> Result<VFS::Stat, VFS::Err> {
260      self.stat.stat(ctx, rel)
261    }
262  }
263
264  impl<'vfs, VFS, Err> traits::RecvDir<'vfs, VFS, Err>
265    for RecvDir<'vfs, VFS, Err, (VFS::Ctx<'vfs>, VFS::OwnedPath, Option<VFS::Stat>)>
266  where
267    VFS: crate::vfs::traits::VFS,
268    Err: From<VFS::Err>,
269  {
270    fn send_dir(&self, ctx: VFS::Ctx<'vfs>, rel: VFS::OwnedPath, stat: Option<VFS::Stat>) {
271      self.sender.send((ctx, rel, stat)).unwrap();
272    }
273  }
274
275  #[derive(Debug, Clone)]
276  pub struct RecvDirEntry<'vfs, VFS, Err, DirEntry> {
277    stat: RecvStat<'vfs, VFS, Err>,
278    sender: mpsc::Sender<DirEntry>,
279  }
280
281  impl<'vfs, VFS, Err, DirEntry> traits::RecvErr<Err> for RecvDirEntry<'vfs, VFS, Err, DirEntry> {
282    fn signal(&self, err: Err) { self.stat.signal(err); }
283  }
284
285  impl<'vfs, VFS, Err, DirEntry> traits::RecvStat<'vfs, VFS, Err>
286    for RecvDirEntry<'vfs, VFS, Err, DirEntry>
287  where
288    VFS: crate::vfs::traits::VFS,
289    Err: From<VFS::Err>,
290  {
291    fn stat<'s>(&self, ctx: VFS::Ctx<'vfs>, rel: VFS::PathRef<'s>) -> Result<VFS::Stat, VFS::Err> {
292      self.stat.stat(ctx, rel)
293    }
294  }
295
296  impl<'vfs, VFS, Err> traits::RecvDirEntry<'vfs, VFS, Err>
297    for RecvDirEntry<
298      'vfs,
299      VFS,
300      Err,
301      (
302        VFS::Ctx<'vfs>,
303        crate::vfs::traits::FileType,
304        <<VFS::Dir<'vfs> as crate::vfs::traits::DirectoryStream>::DirEntry<'static> as crate::vfs::traits::DirectoryEntry>::OwnedPath,
305        Option<VFS::Stat>,
306      ),
307    >
308  where
309    VFS: crate::vfs::traits::VFS,
310    Err: From<VFS::Err>,
311  {
312    fn owned_name<'dir, 's>(
313      name: <<VFS::Dir<'vfs> as crate::vfs::traits::DirectoryStream>::DirEntry<'dir> as crate::vfs::traits::DirectoryEntry>::PathRef<'s>,
314    ) -> <<VFS::Dir<'vfs> as crate::vfs::traits::DirectoryStream>::DirEntry<'dir> as crate::vfs::traits::DirectoryEntry>::OwnedPath {
315      <<VFS::Dir<'vfs> as crate::vfs::traits::DirectoryStream>::DirEntry<'dir> as crate::vfs::traits::DirectoryEntry>::owned_name(name)
316    }
317    fn send_dir_entry(
318      &self,
319      ctx: VFS::Ctx<'vfs>,
320      file_type: crate::vfs::traits::FileType,
321      name: <<VFS::Dir<'vfs> as crate::vfs::traits::DirectoryStream>::DirEntry<'static> as crate::vfs::traits::DirectoryEntry>::OwnedPath,
322      stat: Option<VFS::Stat>,
323    ) {
324      self.sender.send((ctx, file_type, name, stat)).unwrap();
325    }
326  }
327}
328
329
330pub mod dir_handles {
331  use std::{
332    os::fd,
333    sync::atomic::{AtomicUsize, Ordering},
334  };
335
336  use indexmap::IndexMap;
337  use parking_lot::RwLock;
338
339  use crate::handles::DirFd;
340
341
342  #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
343  pub struct DirToken(fd::RawFd);
344
345  pub struct DirState<R, Ctx> {
346    dir: DirFd<R>,
347    num_entries: AtomicUsize,
348    ctx: Ctx,
349  }
350
351  impl<R, Ctx> DirState<R, Ctx> {
352    pub const fn new(dir: DirFd<R>, ctx: Ctx) -> Self {
353      Self {
354        dir,
355        num_entries: AtomicUsize::new(0),
356        ctx,
357      }
358    }
359  }
360
361  pub struct OpenDirHandles<R, Ctx> {
362    handles: RwLock<IndexMap<DirToken, DirState<R, Ctx>>>,
363  }
364
365  pub struct DirEntry<Info> {
366    token: DirToken,
367    info: Info,
368  }
369}