path_abs/
abs.rs

1/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com
2 *
3 * Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4 * http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5 * http://opensource.org/licenses/MIT>, at your option. This file may not be
6 * copied, modified, or distributed except according to those terms.
7 */
8//! The absolute path type, the root type for all `Path*` types in this module.
9use std::env;
10use std::ffi;
11use std::fmt;
12use std::io;
13use std::path::{Component, PrefixComponent};
14use std_prelude::*;
15
16use super::{Error, PathMut, PathOps, Result};
17
18/// Converts any PrefixComponent into verbatim ("extended-length") form.
19fn make_verbatim_prefix(prefix: &PrefixComponent<'_>) -> Result<PathBuf> {
20    let path_prefix = Path::new(prefix.as_os_str());
21
22    if prefix.kind().is_verbatim() {
23        // This prefix already uses the extended-length
24        // syntax, so we can use it as-is.
25        Ok(path_prefix.to_path_buf())
26    } else {
27        // This prefix needs canonicalization.
28        let res = path_prefix
29            .canonicalize()
30            .map_err(|e| Error::new(e, "canonicalizing", path_prefix.to_path_buf().into()))?;
31        Ok(res)
32    }
33}
34
35/// Pops the last component from path, returning an error for a root path.
36fn pop_or_error(path: &mut PathBuf) -> ::std::result::Result<(), io::Error> {
37    if path.pop() {
38        Ok(())
39    } else {
40        Err(io::Error::new(io::ErrorKind::NotFound, ".. consumed root"))
41    }
42}
43
44#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
45/// An absolute (not _necessarily_ [canonicalized][1]) path that may or may not exist.
46///
47/// [1]: https://doc.rust-lang.org/std/path/struct.Path.html?search=#method.canonicalize
48pub struct PathAbs(pub(crate) Arc<PathBuf>);
49
50impl PathAbs {
51    /// Construct an absolute path from an arbitrary (absolute or relative) one.
52    ///
53    /// This is different from [`canonicalize`] in that it _preserves_ symlinks
54    /// and the destination may or may not exist.
55    ///
56    /// This function will:
57    /// - Resolve relative paths against the current working directory.
58    /// - Strip any `.` components (`/a/./c` -> `/a/c`)
59    /// - Resolve `..` _semantically_ (not using the file system). So, `a/b/c/../d => a/b/d` will
60    ///   _always_ be true regardless of symlinks. If you want symlinks correctly resolved, use
61    ///   `canonicalize()` instead.
62    ///
63    /// > On windows, this will sometimes call `canonicalize()` on the first component to guarantee
64    /// > it is the correct canonicalized prefix. For paths starting with root it also has to get
65    /// > the [`current_dir`]
66    ///
67    /// > On linux, the only syscall this will make is to get the [`current_dir`] for relative
68    /// > paths.
69    ///
70    /// [`canonicalize`]: struct.PathAbs.html#method.canonicalize
71    /// [`current_dir`]: fn.current_dir.html
72    ///
73    /// # Examples
74    ///
75    /// ```rust
76    /// use path_abs::{PathAbs, PathInfo};
77    ///
78    /// # fn try_main() -> ::std::io::Result<()> {
79    /// let lib = PathAbs::new("src/lib.rs")?;
80    ///
81    /// assert_eq!(lib.is_absolute(), true);
82    /// # Ok(()) } fn main() { try_main().unwrap() }
83    /// ```
84    pub fn new<P: AsRef<Path>>(path: P) -> Result<PathAbs> {
85        let path = Arc::new(path.as_ref().to_path_buf());
86        let mut res = PathBuf::new();
87
88        fn maybe_init_res(res: &mut PathBuf, resolvee: Arc<PathBuf>) -> Result<()> {
89            if !res.as_os_str().is_empty() {
90                // res has already been initialized, let's leave it alone.
91                return Ok(());
92            }
93
94            // res has not been initialized, let's initialize it to the
95            // canonicalized current directory.
96            let cwd = env::current_dir().map_err(|e| {
97                Error::new(e, "getting current_dir while resolving absolute", resolvee)
98            })?;
99            *res = cwd
100                .canonicalize()
101                .map_err(|e| Error::new(e, "canonicalizing", cwd.into()))?;
102
103            Ok(())
104        };
105
106        for each in path.components() {
107            match each {
108                Component::Prefix(p) => {
109                    // We don't care what's already in res, we can entirely
110                    // replace it..
111                    res = make_verbatim_prefix(&p)?;
112                }
113
114                Component::RootDir => {
115                    if cfg!(windows) {
116                        // In an ideal world, we would say
117                        //
118                        //  res = std::fs::canonicalize(each)?;
119                        //
120                        // ...to get a properly canonicalized path.
121                        // Unfortunately, Windows cannot canonicalize `\` if
122                        // the current directory happens to use extended-length
123                        // syntax (like `\\?\C:\Windows`), so we'll have to do
124                        // it manually: initialize `res` with the current
125                        // working directory (whatever it is), and truncate it
126                        // to its prefix by pushing `\`.
127                        maybe_init_res(&mut res, path.clone())?;
128                        res.push(each);
129                    } else {
130                        // On other platforms, a root path component is always
131                        // absolute so we can replace whatever's in res.
132                        res = Path::new(&each).to_path_buf();
133                    }
134                }
135
136                // This does nothing and can be ignored.
137                Component::CurDir => (),
138
139                Component::ParentDir => {
140                    // A parent component is always relative to some existing
141                    // path.
142                    maybe_init_res(&mut res, path.clone())?;
143                    pop_or_error(&mut res)
144                        .map_err(|e| Error::new(e, "resolving absolute", path.clone()))?;
145                }
146
147                Component::Normal(c) => {
148                    // A normal component is always relative to some existing
149                    // path.
150                    maybe_init_res(&mut res, path.clone())?;
151                    res.push(c);
152                }
153            }
154        }
155
156        Ok(PathAbs(Arc::new(res)))
157    }
158
159    /// Create a PathAbs unchecked.
160    ///
161    /// This is mostly used for constructing during tests, or if the path was previously validated.
162    /// This is effectively the same as a `Arc<PathBuf>`.
163    ///
164    /// > Note: This is memory safe, so is not marked `unsafe`. However, it could cause
165    /// > panics in some methods if the path was not properly validated.
166    pub fn new_unchecked<P: Into<Arc<PathBuf>>>(path: P) -> PathAbs {
167        PathAbs(path.into())
168    }
169
170    /// Return a reference to a basic `std::path::Path`
171    pub fn as_path(&self) -> &Path {
172        self.as_ref()
173    }
174}
175
176impl fmt::Debug for PathAbs {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        self.0.fmt(f)
179    }
180}
181
182impl PathMut for PathAbs {
183    fn append<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
184        self.0.append(path)
185    }
186    fn pop_up(&mut self) -> Result<()> {
187        self.0.pop_up()
188    }
189    fn truncate_to_root(&mut self) {
190        self.0.truncate_to_root()
191    }
192    fn set_file_name<S: AsRef<ffi::OsStr>>(&mut self, file_name: S) {
193        self.0.set_file_name(file_name)
194    }
195    fn set_extension<S: AsRef<ffi::OsStr>>(&mut self, extension: S) -> bool {
196        self.0.set_extension(extension)
197    }
198}
199
200impl PathOps for PathAbs {
201    type Output = PathAbs;
202
203    fn concat<P: AsRef<Path>>(&self, path: P) -> Result<Self::Output> {
204        Ok(PathAbs(self.0.concat(path)?))
205    }
206
207    fn join<P: AsRef<Path>>(&self, path: P) -> Self::Output {
208        let buf = Path::join(self.as_path(), path);
209        Self::Output::new_unchecked(buf)
210    }
211
212    fn with_file_name<S: AsRef<ffi::OsStr>>(&self, file_name: S) -> Self::Output {
213        PathAbs(self.0.with_file_name(file_name))
214    }
215
216    fn with_extension<S: AsRef<ffi::OsStr>>(&self, extension: S) -> Self::Output {
217        PathAbs(self.0.with_extension(extension))
218    }
219}
220
221impl AsRef<ffi::OsStr> for PathAbs {
222    fn as_ref(&self) -> &std::ffi::OsStr {
223        self.0.as_ref().as_ref()
224    }
225}
226
227impl AsRef<Path> for PathAbs {
228    fn as_ref(&self) -> &Path {
229        self.0.as_ref()
230    }
231}
232
233impl AsRef<PathBuf> for PathAbs {
234    fn as_ref(&self) -> &PathBuf {
235        self.0.as_ref()
236    }
237}
238
239impl Borrow<Path> for PathAbs {
240    fn borrow(&self) -> &Path {
241        self.as_ref()
242    }
243}
244
245impl Borrow<PathBuf> for PathAbs {
246    fn borrow(&self) -> &PathBuf {
247        self.as_ref()
248    }
249}
250
251impl<'a> Borrow<Path> for &'a PathAbs {
252    fn borrow(&self) -> &Path {
253        self.as_ref()
254    }
255}
256
257impl<'a> Borrow<PathBuf> for &'a PathAbs {
258    fn borrow(&self) -> &PathBuf {
259        self.as_ref()
260    }
261}
262
263impl From<PathAbs> for Arc<PathBuf> {
264    fn from(path: PathAbs) -> Arc<PathBuf> {
265        path.0
266    }
267}
268
269impl From<PathAbs> for PathBuf {
270    fn from(path: PathAbs) -> PathBuf {
271        match Arc::try_unwrap(path.0) {
272            Ok(p) => p,
273            Err(inner) => inner.as_ref().clone(),
274        }
275    }
276}