xref: /relibc/src/platform/redox/path.rs (revision 041d1604b526a0b908d4222e39baa0030a94fd8f)
1 use syscall::{data::Stat, error::*, flag::*};
2 
3 use alloc::{
4     borrow::{Cow, ToOwned},
5     boxed::Box,
6     string::String,
7     vec::Vec,
8 };
9 
10 use super::FdGuard;
11 use crate::sync::Mutex;
12 
13 // TODO: Define in syscall
14 const PATH_MAX: usize = 4096;
15 
16 /// Make a relative path absolute.
17 ///
18 /// Given a cwd of "scheme:/path", this his function will turn "foo" into "scheme:/path/foo".
19 /// "/foo" will turn into "scheme:/foo". "bar:/foo" will be used directly, as it is already
20 /// absolute
21 pub fn canonicalize_using_cwd<'a>(cwd_opt: Option<&str>, path: &'a str) -> Option<String> {
22     let mut canon = if path.find(':').is_none() {
23         let cwd = cwd_opt?;
24         let path_start = cwd.find(':')? + 1;
25 
26         let mut canon = if !path.starts_with('/') {
27             let mut c = cwd.to_owned();
28             if !c.ends_with('/') {
29                 c.push('/');
30             }
31             c
32         } else {
33             cwd[..path_start].to_owned()
34         };
35 
36         canon.push_str(&path);
37         canon
38     } else {
39         path.to_owned()
40     };
41 
42     // NOTE: assumes the scheme does not include anything like "../" or "./"
43     let mut result = {
44         let parts = canon
45             .split('/')
46             .rev()
47             .scan(0, |nskip, part| {
48                 if part == "." {
49                     Some(None)
50                 } else if part == ".." {
51                     *nskip += 1;
52                     Some(None)
53                 } else if *nskip > 0 {
54                     *nskip -= 1;
55                     Some(None)
56                 } else {
57                     Some(Some(part))
58                 }
59             })
60             .filter_map(|x| x)
61             .filter(|x| !x.is_empty())
62             .collect::<Vec<_>>();
63         parts.iter().rev().fold(String::new(), |mut string, &part| {
64             string.push_str(part);
65             string.push('/');
66             string
67         })
68     };
69     result.pop(); // remove extra '/'
70 
71     // replace with the root of the scheme if it's empty
72     Some(if result.is_empty() {
73         let pos = canon.find(':').map_or(canon.len(), |p| p + 1);
74         canon.truncate(pos);
75         canon
76     } else {
77         result
78     })
79 }
80 
81 // XXX: chdir is not marked thread-safe (MT-safe) by POSIX. But on Linux it is simply run as a
82 // syscall and is therefore atomic, which is presumably why Rust's libstd doesn't synchronize
83 // access to this.
84 //
85 // https://internals.rust-lang.org/t/synchronized-ffi-access-to-posix-environment-variable-functions/15475
86 //
87 // chdir is however signal-safe, forbidding the use of locks. We therefore call sigprocmask before
88 // and after acquiring the locks. (TODO: ArcSwap? That will need to be ported to no_std first,
89 // though).
90 pub fn chdir(path: &str) -> Result<()> {
91     let _siglock = SignalMask::lock();
92     let mut cwd_guard = CWD.lock();
93 
94     let canonicalized =
95         canonicalize_using_cwd(cwd_guard.as_deref(), path).ok_or(Error::new(ENOENT))?;
96 
97     let fd = syscall::open(&canonicalized, O_STAT | O_CLOEXEC)?;
98     let mut stat = Stat::default();
99     if syscall::fstat(fd, &mut stat).is_err() || (stat.st_mode & MODE_TYPE) != MODE_DIR {
100         return Err(Error::new(ENOTDIR));
101     }
102     let _ = syscall::close(fd);
103 
104     *cwd_guard = Some(canonicalized.into_boxed_str());
105 
106     // TODO: Check that the dir exists and is a directory.
107 
108     Ok(())
109 }
110 
111 pub fn clone_cwd() -> Option<Box<str>> {
112     let _siglock = SignalMask::lock();
113     CWD.lock().clone()
114 }
115 
116 // TODO: MaybeUninit
117 pub fn getcwd(buf: &mut [u8]) -> Option<usize> {
118     let _siglock = SignalMask::lock();
119     let cwd_guard = CWD.lock();
120     let cwd = cwd_guard.as_deref().unwrap_or("").as_bytes();
121 
122     // But is already checked not to be empty.
123     if buf.len() - 1 < cwd.len() {
124         return None;
125     }
126 
127     buf[..cwd.len()].copy_from_slice(&cwd);
128     buf[cwd.len()..].fill(0_u8);
129 
130     Some(cwd.len())
131 }
132 
133 // TODO: Move cwd from kernel to libc. It is passed via auxiliary vectors.
134 pub fn canonicalize(path: &str) -> Result<String> {
135     let _siglock = SignalMask::lock();
136     let cwd = CWD.lock();
137     canonicalize_using_cwd(cwd.as_deref(), path).ok_or(Error::new(ENOENT))
138 }
139 
140 // TODO: arraystring?
141 static CWD: Mutex<Option<Box<str>>> = Mutex::new(None);
142 
143 pub fn setcwd_manual(cwd: Box<str>) {
144     let _siglock = SignalMask::lock();
145     *CWD.lock() = Some(cwd);
146 }
147 
148 /// RAII guard able to magically fix signal unsafety, by disabling signals during a critical
149 /// section.
150 pub struct SignalMask {
151     oldset: [u64; 2],
152 }
153 impl SignalMask {
154     pub fn lock() -> Self {
155         let mut oldset = [0; 2];
156         syscall::sigprocmask(syscall::SIG_SETMASK, Some(&[!0, !0]), Some(&mut oldset))
157             .expect("failed to run sigprocmask");
158         Self { oldset }
159     }
160 }
161 impl Drop for SignalMask {
162     fn drop(&mut self) {
163         let _ = syscall::sigprocmask(syscall::SIG_SETMASK, Some(&self.oldset), None);
164     }
165 }
166 
167 pub fn open(path: &str, flags: usize) -> Result<usize> {
168     // TODO: SYMLOOP_MAX
169     const MAX_LEVEL: usize = 64;
170 
171     let mut resolve_buf = [0_u8; 4096];
172     let mut path = path;
173 
174     for _ in 0..MAX_LEVEL {
175         let canon = canonicalize(path)?;
176         match syscall::open(&*canon, flags) {
177             Ok(fd) => return Ok(fd),
178             Err(error) if error == Error::new(EXDEV) => {
179                 let resolve_flags = O_CLOEXEC | O_SYMLINK | O_RDONLY;
180                 let resolve_fd = FdGuard::new(syscall::open(&*canon, resolve_flags)?);
181 
182                 let bytes_read = syscall::read(*resolve_fd, &mut resolve_buf)?;
183                 // TODO: make resolve_buf PATH_MAX + 1 bytes?
184                 if bytes_read == resolve_buf.len() {
185                     return Err(Error::new(ENAMETOOLONG));
186                 }
187 
188                 // If the symbolic link path is non-UTF8, it cannot be opened, and is thus
189                 // considered a "dangling symbolic link".
190                 path = core::str::from_utf8(&resolve_buf[..bytes_read])
191                     .map_err(|_| Error::new(ENOENT))?;
192             }
193             Err(other_error) => return Err(other_error),
194         }
195     }
196     Err(Error::new(ELOOP))
197 }
198