Repository: vitiral/path_abs (language: rust)

Issue:
Moving/copying/removing a symbolic link
(I wish GitHub provided a better way to have discussion other than opening a bug report)

**Let's talk symlinks.**

I have an application which has historically taken the easy way out of path management by changing the current directory.  In moving away from this setup towards a more principled design, I considered using `path_abs` as a means of leveraging the type system to ensure that I don't accidentally leave behind any lingering relative paths.

Reviewing the documentation, however, it is evident to me that my mental model of symlinks differs from that of the library.  Of course, this is to be expected; I understand that PathAbs is opinionated, and that our ideas will inevitably clash *somewhere.*

But with regard to symlinks specifically, I find the model presented by this crate to be **dangerous, and quite scary!**  I'd like to hear your point of view on this, and I wonder if there is something I am missing.

---

I'll describe these clashing models by making analogy to Rust's borrow semantics.

To me:
* A directory in the filesystem is like some `Box<_>` type.  It has a unique owner (its "true" parent) and that's the only way you can move or delete it.
* A file in the filesystem is like some `Arc<_>` type.  It may have multiple owners via hard links, and dies when the last link vanishes.
* A symlink is like some `*mut _` type.  It can change data belonging to its target, but only if you explicitly dereference it (by joining another path component).  The pointer itself does not own its target, and its target might not even exist.

In PathAbs:
* by immediately resolving symlinks, PathAbs makes symlinks feel more like C++'s mutable references.  A symlink **is** the directory or file it points to.  To me, this is terrifying, because if I'm not careful, then code which was *intended* to delete a symlink could end up accidentally **recursively deleting its target!**

What do you think of this?  Do you disagree with this risk about deleting a symlink?  Is there room in PathAbs for symlinks to live as first class citizens?  Am I asking too many questions? :stuck_out_tongue: 


Discussion / hints:
I tried to put together something resembling what I think an API with support for symlinks *might* look like, without changing the semantics of anything that already existed.

https://github.com/vitiral/path_abs/commit/e14c4d9eec6b636b025a219bd34728b11e6a901a

The commit shows:
*  The addition of a type `PathEntry` which is like `PathAbs` but with a slightly weaker invariant that it doesn't resolve a symlink in the final path component.  This makes it not the canonical form of a file or directory, but rather, of a *directory entry;* hence the name.
* `PathSymlink` which holds a PathEntry to a path previously identified as a symlink.
* `PathDir::list_entries`, which is capable of losslessly listing all entries of a directory as paths *in that directory*, in contrast to `list`
Sorry I haven't replied yet. This issue is very interesting to me, but
unfortunately my 6mo old spilled half a cup of coffee on my laptop a few
days ago and it's taking me a bit to set up my new computer with NixOS.

Amusingly enough, I think I may have destroyed my own computer, too, while I was in the middle of performing the migration to `path_abs`. (no 5mo, just a novice's DIY follies).  I'm now trying to rebuild my workflow on my desktop PC, and to hunt down one of the planet's only five drive enclosures for mSATA (a.k.a. *why does this standard even exist?*).
Okay, I'm back and ready to look at a design which incorporates symlinks in some way.

## Usecase / Problem Statement

First, some definitions:
- _absolute path_: an absolute path to is the absolute path to a fs object _that exists_ (file, directory, symlink). Some or even all of the directories in that path may be symlinks, but the beginning directory must always be root (or [`\\?\` on windows](https://stackoverflow.com/a/41233992/1036670)).
- _canonicalized path_: is an absolute path with no symlinks, including the destination.

The goal of this issue should be to use **absolute paths** as opposed to **cannonicalized paths** as the _base_ type (but `cannonicalize()` still returns a PathAbs` as well). I think this would solve all of your issues.

## Technical Solution
- If the path does not start with `/` then prefix it with `std::env::current_dir().canonicalize()`.
- If on windows and the path does not start with `\\?\` then replace the first component with it's  `canonicalized()` form.
- If the path has `.` or `..` in it then handle as follows:
  - Remove `.` components
  - Remove `..` components that follow components by stripping out the component. I.e. `/foo/bar/../baz -> /foo/baz`.
  - If left with leading `..` then the path is invalid and return an error (we consumed root).

## Part 2
I think the above should theoretically work for what we want -- now what to do with the rest of the API. If the following can be zero cost they would be nice:
- add `is_symlink()` to all `Path*` types that are not `Arc` (definitely zero cost)
- add `is_cannonical()` to all `Path*` types that are not `Arc` (maybe zero-ish cost). My thought is that we are at _least_ going to call `exists()` on the path, so we might as well instead call `canonicalize()`. If the canonicalized version is identical to the path then `is_cannonical() == true`

Other thoughts are welcome!
Sounds like part of what you're describing is Path::clean ([stdlib PR](https://github.com/rust-lang/rust/pull/47363)).

Absolute paths would solve this problem and some others. What you've got drafted there at the time of me writing this sounds like a bit of a wild mix of canonicalization and cleaning; I'm not sure about Windows, but on Unix I think `current_dir().join(path).clean()` is usually all I am looking for.

(**note:** well, okay, that's only true for CLI args.  For paths in config files, some form canonicalization is needed so that the current directory does not impact resolution)
[this](https://github.com/rust-lang/rust/pull/47363#issuecomment-359606479) and [this](https://github.com/rust-lang/rust/pull/47363#issuecomment-372019693) are particularly enlightening. Fully supporting windows is _not easy_ and I think I would want to wait until at least `clean()` has landed.

Edit: the first link was only interesting because they said that `..` was a "symbolic link, this would have to call realpath in order to not get a different result than accessing the original path, and would have to hit the filesystem", but I thought your response was very good. Who _cares_ if it's a symbolic link... let's just clean it.
@kennytm I followed your comments on the rust issue and I'm wondering what your thoughts are for this issue.

The basic use case: we want "absolute paths for files that exist" -- but they are allowed to have symlinks inside.

In particular, do you think the technical solution I gave will work for windows?
agh, I [hate my life](https://github.com/rust-lang-nursery/cli-wg/issues/10#issuecomment-374947099)

> This is not generally possible in POSIX because of symlinks: if a/b/c is a symlink to a/d, then a/b/c/../foo is actually a/foo, not a/b/foo. Now that modern versions of Windows support symlinks too, I suspect that's also an issue there.

that actually makes sense and makes me very sad.
So, thinking a little more about this, I don't think there is really _any_ value in allowing `..` anywhere but the beginning. When does someone have a path like `/a/b/../c/d/../e/f`? That is a pretty absurd use-case IMO, and resolving it either correctly _or_ incorrectly could lead to a lot of confusion. Better to just error in my opinion.

So my suggestion: only allow (multiple) `..` at the beginning of the the path (so `../../a/b/c/d/e/f` would be valid).

The issue of resolving the wrong path doesn't exist when `..` is at the beginning since the `current_dir` is already canonicalized so we can safely walk down the path (we know it is "real").
I construct paths with interior `..` all the time when joining relative paths from config files...
good point. I think treating it semantically is probably the best course of action, agreed?

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index 9fd1456..037ae36 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,5 +24,5 @@ notifications:
     on_success: never
 
 script:
-  - RUST_BACKTRACE=1 cargo test --verbose --all -- --nocapture
-  - RUST_BACKTRACE=1 cargo test --verbose --all --no-default-features -- --nocapture
+  - RUST_BACKTRACE=1 cargo test --verbose --all --no-fail-fast -- --nocapture
+  - RUST_BACKTRACE=1 cargo test --verbose --all --no-fail-fast --no-default-features -- --nocapture
diff --git a/Cargo.toml b/Cargo.toml
index c1a8c78..51b5f93 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@ license = "MIT OR Apache-2.0"
 name = "path_abs"
 readme = "README.md"
 repository = "https://github.com/vitiral/path_abs"
-version = "0.3.17"
+version = "0.4.0-beta1"
 
 [dependencies]
 std_prelude = "0.2.12"
diff --git a/appveyor.yml b/appveyor.yml
index 37e6831..29a6072 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -42,6 +42,7 @@ branches:
 # channels to enable unstable features when building for nightly. Or you could add additional
 # matrix entries to test different combinations of features.
 environment:
+  RUST_BACKTRACE: 1
   matrix:
 
 ### MSVC Toolchains ###
@@ -127,4 +128,4 @@ build: false
 #directly or perform other testing commands. Rust will automatically be placed in the PATH
 # environment variable.
 test_script:
-- cargo test --verbose --all %cargoflags% -- --nocapture
+- cargo test --verbose --all --no-fail-fast %cargoflags% -- --nocapture
diff --git a/src/abs.rs b/src/abs.rs
index bddbda5..4cc9670 100644
--- a/src/abs.rs
+++ b/src/abs.rs
@@ -13,7 +13,7 @@ use std_prelude::*;
 use super::{PathArc, PathDir, PathFile, Result};
 
 #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
-/// An absolute ([canonicalized][1]) path that is guaranteed (when created) to exist.
+/// An absolute (not _necessarily_ [canonicalized][1]) path that may or may not exist.
 ///
 /// [1]: https://doc.rust-lang.org/std/path/struct.Path.html?search=#method.canonicalize
 pub struct PathAbs(pub(crate) PathArc);
@@ -31,7 +31,7 @@ impl PathAbs {
     /// ```
     pub fn new<P: AsRef<Path>>(path: P) -> Result<PathAbs> {
         let arc = PathArc::new(path);
-        arc.canonicalize()
+        arc.absolute()
     }
 
     /// Resolve the `PathAbs` as a `PathFile`. Return an error if it is not a file.
diff --git a/src/arc.rs b/src/arc.rs
index c62de4e..6885cdf 100644
--- a/src/arc.rs
+++ b/src/arc.rs
@@ -9,13 +9,32 @@
 
 use std::fmt;
 use std::fs;
+use std::io;
+use std::env;
 use std::ffi::OsStr;
+use std::path::{Component, Components, Prefix};
 use std_prelude::*;
 
 use super::{Error, Result};
 use abs::PathAbs;
 use dir::{ListDir, PathDir};
 
+/// Same as [`std::env::current_dir`] except it uses a more descriptive error message
+/// and returns `PathArc`.
+///
+/// [`std::env::current_dir`]: https://doc.rust-lang.org/beta/std/env/fn.set_current_dir.html
+pub fn current_dir(resolving: &PathArc) -> Result<PathArc> {
+    let cwd = env::current_dir().map_err(|e| {
+        Error::new(
+            e,
+            "getting current_dir while resolving absolute",
+            resolving.clone(),
+        )
+    })?;
+    Ok(PathArc::from(cwd))
+}
+
+
 #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
 /// A `PathBuf` that is atomically reference counted and reimplements the `PathBuf`
 /// methods to display the action and path when there is an error.
@@ -101,8 +120,6 @@ impl PathArc {
     /// Returns the canonical form of the path with all intermediate components normalized and
     /// symbolic links resolved.
     ///
-    /// > This is identical to `PathAbs::new(path)`.
-    ///
     /// This function is identical to [std::path::Path::canonicalize][0] except:
     /// - It returns a `PathAbs` object
     /// - It has error messages which include the action and the path
@@ -146,6 +163,69 @@ impl PathArc {
     pub fn as_path(&self) -> &Path {
         self.as_ref()
     }
+
+    /// Convert the path to an absolute one, this is different from [`canonicalize`] in that it
+    /// _preserves_ symlinks and the destination may or may not exist.
+    ///
+    /// This function will:
+    /// - Use [`current_dir`] to resolve relative paths.
+    /// - Strip any `.` components (`/a/./c` -> `/a/c`)
+    /// - Resolve `..` _semantically_ (not using the file system). So, `a/b/c/../d => a/b/d` will
+    ///   _always_ be true regardless of symlinks. If you want symlinks correctly resolved, use
+    ///   `canonicalize()` instead.
+    ///
+    /// > On windows, this will sometimes call `canonicalize()` on the first component to guarantee
+    /// > it is the correct canonicalized prefix. For paths starting with root it also has to get
+    /// > the [`current_dir`]
+    ///
+    /// > On linux, the only syscall this will make is to get the [`current_dir`] for relative
+    /// > paths.
+    ///
+    /// [`canonicalize`]: struct.PathAbs.html#method.canonicalize
+    /// [`current_dir`]: fn.current_dir.html
+    pub fn absolute(&self) -> Result<PathAbs> {
+        let mut components = self.components();
+        let mut stack: Vec<OsString> = Vec::new();
+
+        macro_rules! pop_stack { [] => {{
+            if let None = stack.pop() {
+                return Err(Error::new(
+                    io::Error::new(io::ErrorKind::NotFound, ".. consumed root"),
+                    "resolving absolute",
+                    self.clone(),
+                ));
+            }
+        }}}
+
+        handle_prefix(self, &mut stack, &mut components, false)?;
+
+        for component in components {
+            match component {
+                Component::CurDir => { /* ignore, probably impossible */ }
+                Component::Prefix(_) => unreachable!(),
+                Component::RootDir => {
+                    if cfg!(unix) {
+                        unreachable!("root is already handled on unix");
+                    }
+                    // This is actually possible on windows because root is distinct
+                    // from prefix (?)
+                    stack.push(to_os(component));
+                }
+                Component::ParentDir => pop_stack!(),
+                Component::Normal(_) => stack.push(to_os(component)),
+            }
+        }
+
+        if stack.is_empty() {
+            return Err(Error::new(
+                io::Error::new(io::ErrorKind::NotFound, "resolving resulted in empty path"),
+                "resolving absolute",
+                self.clone(),
+            ));
+        }
+
+        Ok(PathAbs(PathArc(Arc::new(PathBuf::from_iter(stack)))))
+    }
 }
 
 impl fmt::Debug for PathArc {
@@ -230,3 +310,141 @@ impl Into<PathBuf> for PathArc {
         }
     }
 }
+
+fn to_os(c: Component) -> OsString {
+    c.as_os_str().to_os_string()
+}
+
+/// Handle the prefix in the components.
+///
+/// Pretty much 100% of this logic is because windows is evil. You can't call `canonicalize` on `\`
+/// since it depends on the current directory. You also can't call it when it would be a noop, i.e.
+/// for `\\?\C:`.
+fn handle_prefix(
+    resolving: &PathArc,
+    stack: &mut Vec<OsString>,
+    components: &mut Components,
+    recursing: bool,
+) -> Result<()> {
+    macro_rules! pop_stack { [] => {{
+        if let None = stack.pop() {
+            return Err(Error::new(
+                io::Error::new(io::ErrorKind::NotFound, ".. consumed root"),
+                "resolving absolute",
+                resolving.clone(),
+            ));
+        }
+    }}}
+    loop {
+        // The whole reason we're here is because we haven't added anything to the stack yet.
+        assert_eq!(stack.len(), 0, "{:?}", stack);
+
+        let component = match components.next() {
+            None => break,
+            Some(c) => c,
+        };
+
+        match component {
+            Component::CurDir => {
+                assert_eq!(recursing, false);
+
+                // ignore
+                continue;
+            }
+            Component::Prefix(prefix) => {
+                assert!(!cfg!(unix), "Component::Prefix in unix");
+                match prefix.kind() {
+                    Prefix::Disk(_) | Prefix::UNC(_, _) => {
+                        // Make the prefix a more "standard" form
+                        let c = PathArc::new(component.as_os_str()).canonicalize()?;
+                        stack.extend(c.components().map(to_os));
+                    }
+                    _ => {
+                        // Already in the "most standardized" form
+                        // TODO: some more testing to make sure that canoninicalize()
+                        // cannot be called on these forms would be good
+                        stack.push(to_os(component));
+                    }
+                }
+            }
+            Component::RootDir => {
+
+                if cfg!(windows) {
+                    // we were called by something that got cwd... so it better not start with `\`.
+                    assert!(!recursing);
+
+                    // https://stackoverflow.com/questions/151860
+                    // > In Windows [root is] relative to what drive your current working
+                    // > directory is at the time.
+                    //
+                    // So, we need to push the "drive" first.
+
+                    let cwd = current_dir(resolving)?;
+                    handle_prefix(resolving, stack, &mut cwd.components(), true)?;
+                    {
+                        // Double check that we aren't being dumb. `current_dir`
+                        // should have always started with some kind of prefix.
+
+                        // TODO: not sure why, but this assertion actually can fail and
+                        // does in the tests.
+                        // assert_eq!(1, stack.len(), "{:?}", stack);
+
+                        let first = Path::new(&stack[0]).components().next().unwrap();
+                        if let Component::Prefix(prefix) = first {
+                            if let Prefix::DeviceNS(_) = prefix.kind() {
+                            } else if !prefix.kind().is_verbatim() {
+                                panic!(
+                                    "First item kind is neither verbatim nor DeviceNs: {:?}",
+                                    stack
+                                )
+                            }
+                        } else {
+                            panic!("First item is not a Prefix on windows: {:?}", stack)
+                        }
+                    }
+                }
+                // Always push the "root" component.
+                stack.push(to_os(component));
+            }
+            Component::ParentDir | Component::Normal(_) => {
+                assert!(!recursing);
+
+                // First item is either a ParentDir or Normal, in either
+                // case we need to get current_dir
+                let cwd = current_dir(resolving)?;
+                let mut cwd_components = cwd.components();
+                handle_prefix(resolving, stack, &mut cwd_components, true)?;
+                stack.extend(cwd_components.map(to_os));
+
+                match component {
+                    Component::ParentDir => pop_stack!(),
+                    Component::Normal(_) => stack.push(to_os(component)),
+                    _ => unreachable!(),
+                }
+            }
+        }
+        break;
+    }
+    Ok(())
+}
+
+#[test]
+fn test_prefix_windows() {
+    fn f<P: AsRef<Path>>(p: P) -> Result<PathAbs> {
+        PathArc::new(p).absolute()
+    }
+    assert!(f(r"\\?\C:\blah\blah").is_ok());
+    assert!(f(r"\blah\blah").is_ok());
+    assert!(f(r"C:\blah\blah").is_ok());
+
+    // TODO: this is how to get the hostname, but getting the "share name"
+    // seems to be more difficult.
+    // let hostname = ::std::process::Command::new("hostname")
+    //     .output()
+    //     .expect("could not get hostname")
+    //     .stdout;
+    // let hostname = ::std::str::from_utf8(&hostname).unwrap().trim();
+
+    // assert!(f(format!(r"\\{}\share", hostname)).is_ok());
+    // assert!(f(format!(r"\\?\UNC\{}\share", hostname)).is_ok());
+}
diff --git a/src/dir.rs b/src/dir.rs
index 56242c8..4eb3d01 100644
--- a/src/dir.rs
+++ b/src/dir.rs
@@ -61,11 +61,6 @@ impl PathDir {
     ///
     /// If the path is actually a file returns `io::ErrorKind::InvalidInput`.
     ///
-    /// > This does not call [`Path::cannonicalize()`][1], instead trusting that the input is
-    /// > already a fully qualified path.
-    ///
-    /// [1]: https://doc.rust-lang.org/std/path/struct.Path.html?search=#method.canonicalize
-    ///
     /// # Examples
     /// ```rust
     /// # extern crate path_abs;
@@ -172,10 +167,6 @@ impl PathDir {
 
     /// List the contents of the directory, returning an iterator of `PathType`s.
     ///
-    /// > **Warning**: because `PathAbs` is the canonicalized path, symlinks are always resolved.
-    /// > This means that if the directory contains a symlink you may get a path from a completely
-    /// > _different directory_.
-    ///
     /// # Examples
     /// ```rust
     /// # extern crate path_abs;
@@ -287,21 +278,26 @@ impl PathDir {
     /// let dir = PathDir::create(example)?;
     /// let file = PathFile::create(dir.join("example.txt"))?;
     ///
-    /// dir.symlink(example_sym)?;
-    /// let dir_sym = PathDir::new(example_sym)?;
+    /// let dir_sym = dir.symlink(example_sym)?;
+    ///
+    /// // They have a different "absolute path"
+    /// assert_ne!(dir, dir_sym);
+    ///
+    /// // But they can be canonicalized to the same file.
+    /// let dir_can = dir_sym.canonicalize()?;
+    /// assert_eq!(dir, dir_can);
     ///
-    /// // They are canonicalized to the same file.
-    /// assert_eq!(dir, dir_sym);
     /// # Ok(()) } fn main() { try_main().unwrap() }
     /// ```
-    pub fn symlink<P: AsRef<Path>>(&self, dst: P) -> Result<()> {
+    pub fn symlink<P: AsRef<Path>>(&self, dst: P) -> Result<PathDir> {
         symlink_dir(&self, &dst).map_err(|err| {
             Error::new(
                 err,
-                &format!("linking to {} from", dst.as_ref().display()),
+                &format!("linking from {} to", dst.as_ref().display()),
                 self.clone().into(),
             )
-        })
+        })?;
+        PathDir::new(dst)
     }
 
     /// Return a reference to a basic `std::path::Path`
@@ -309,6 +305,16 @@ impl PathDir {
         self.as_ref()
     }
 
+    /// Returns the canonical form of the path with all intermediate components normalized and
+    /// symbolic links resolved.
+    ///
+    /// See [`PathAbs::canonicalize`]
+    ///
+    /// [`PathAbs::canonicalize`]: struct.PathAbs.html#method.canonicalize
+    pub fn canonicalize(&self) -> Result<PathDir> {
+        Ok(PathDir(self.0.canonicalize()?))
+    }
+
     /// Create a mock dir type. *For use in tests only*.
     ///
     /// See the docs for [`PathAbs::mock`](struct.PathAbs.html#method.mock)
diff --git a/src/edit.rs b/src/edit.rs
index 9d58d22..2e00d5d 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -52,7 +52,7 @@ impl FileEdit {
         Ok(FileEdit(FileOpen::open(path, options)?))
     }
 
-    /// Shortcut to open the file if the path is already canonicalized.
+    /// Shortcut to open the file if the path is already absolute.
     pub(crate) fn open_path(path: PathFile, mut options: fs::OpenOptions) -> Result<FileEdit> {
         options.write(true);
         options.read(true);
diff --git a/src/file.rs b/src/file.rs
index dc274f4..2abdd9d 100644
--- a/src/file.rs
+++ b/src/file.rs
@@ -41,11 +41,6 @@ impl PathFile {
     ///
     /// If the path is actually a dir returns `io::ErrorKind::InvalidInput`.
     ///
-    /// > This does not call [`Path::cannonicalize()`][1], instead trusting that the input is
-    /// > already a fully qualified path.
-    ///
-    /// [1]: https://doc.rust-lang.org/std/path/struct.Path.html?search=#method.canonicalize
-    ///
     /// # Examples
     /// ```rust
     /// # extern crate path_abs;
@@ -375,21 +370,25 @@ impl PathFile {
     ///
     /// let contents = "This is some contents";
     /// file.write_str(contents);
-    /// file.symlink(example_sym)?;
-    /// let file_sym = PathFile::new(example_sym)?;
+    /// let file_sym = file.symlink(example_sym)?;
     ///
-    /// // They are canonicalized to the same file.
-    /// assert_eq!(file, file_sym);
+    /// // They have a different "absolute path"
+    /// assert_ne!(file, file_sym);
+    ///
+    /// // But they can be canonicalized to the same file.
+    /// let file_can = file_sym.canonicalize()?;
+    /// assert_eq!(file, file_can);
     /// # Ok(()) } fn main() { try_main().unwrap() }
     /// ```
-    pub fn symlink<P: AsRef<Path>>(&self, dst: P) -> Result<()> {
+    pub fn symlink<P: AsRef<Path>>(&self, dst: P) -> Result<PathFile> {
         symlink_file(&self, &dst).map_err(|err| {
             Error::new(
                 err,
-                &format!("linking to {} from", dst.as_ref().display()),
+                &format!("linking from {} to", dst.as_ref().display()),
                 self.clone().into(),
             )
-        })
+        })?;
+        PathFile::new(dst)
     }
 
     /// Remove (delete) the file from the filesystem, consuming self.
@@ -424,6 +423,16 @@ impl PathFile {
         self.as_ref()
     }
 
+    /// Returns the canonical form of the path with all intermediate components normalized and
+    /// symbolic links resolved.
+    ///
+    /// See [`PathAbs::canonicalize`]
+    ///
+    /// [`PathAbs::canonicalize`]: struct.PathAbs.html#method.canonicalize
+    pub fn canonicalize(&self) -> Result<PathFile> {
+        Ok(PathFile(self.0.canonicalize()?))
+    }
+
     /// Create a mock file type. *For use in tests only*.
     ///
     /// See the docs for [`PathAbs::mock`](struct.PathAbs.html#method.mock)
diff --git a/src/lib.rs b/src/lib.rs
index 92a0263..9cc6f97 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,9 +12,10 @@
 //!
 //! This includes:
 //!
+//! - Cleaner _absolute_ paths (which is distinct from canonicalized paths).
 //! - Improved error messages, see the **Better Errors** section.
-//! - Improved type safety. The types specify that a file _once_ existed and was _once_ a certain
-//!   type. Obviously a file/directory can be deleted/changed by another process.
+//! - Improved type safety. The types specify that a file/dir _once_ existed and was _once_ a certain
+//!   type. Obviously a file/dir can be deleted/changed by another process.
 //! - More stringent mutability requirements. See the **Differing Method Signatures** section.
 //! - Cheap cloning: all path types are `Arc`, which a cheap operation compared to filesystem
 //!   operations and allows more flexibility and ergonomics in the library for relatively low cost.
@@ -22,8 +23,8 @@
 //! ## Better Errors
 //!
 //! All errors include the **path** and **action** which caused the error, as well as the unaltered
-//! `std::io::Error` message. Errors are `std::io::Error`, giving almost complete compatibility
-//! with existing code.
+//! `std::io::Error` message. Errors are convertable into `std::io::Error`, giving almost complete
+//! compatibility with existing code.
 //!
 //! ### `set_len` (i.e. truncate a file):
 //!
@@ -57,12 +58,12 @@
 //! - [`PathArc`](struct.PathArc.html): a reference counted `PathBuf` with methods reimplemented
 //!   with better error messages. Use this for a generic serializable path that may or may
 //!   not exist.
-//! - [`PathAbs`](struct.PathAbs.html): a reference counted absolute (canonicalized) path that is
-//!   guaranteed (on initialization) to exist.
-//! - [`PathFile`](struct.PathFile.html): a `PathAbs` that is guaranteed to be a file, with
-//!   associated methods.
-//! - [`PathDir`](struct.PathDir.html): a `PathAbs` that is guaranteed to be a directory, with
-//!   associated methods.
+//! - [`PathAbs`](struct.PathAbs.html): a reference counted absolute (_not necessarily_
+//!   canonicalized) path that is not necessarily guaranteed to exist.
+//! - [`PathFile`](struct.PathFile.html): a `PathAbs` that is guaranteed (at instantiation) to
+//!   exist and be a file, with associated methods.
+//! - [`PathDir`](struct.PathDir.html): a `PathAbs` that is guaranteed (at instantiation) to exist
+//!   and be a directory, with associated methods.
 //! - [`PathType`](struct.PathType.html): an enum containing either a PathFile or a PathDir.
 //!   Returned by [`PathDir::list`][dir_list]
 //!
@@ -71,7 +72,7 @@
 //! that crate for more details on how the resulting encoding can be edited (by hand) even in the
 //! case of what *would be* ill-formed UTF-16.
 //!
-//! [dir_list]: struct.PathDir.html#method.list)
+//! [dir_list]: struct.PathDir.html#method.list
 //!
 //!
 //! ## Exported File Types
@@ -117,7 +118,7 @@
 //! use std::path::Path;
 //! use std::collections::HashSet;
 //! use path_abs::{
-//!     PathAbs,   // absolute path that exists
+//!     PathAbs,   // absolute path
 //!     PathDir,   // absolute path to a directory
 //!     PathFile,  // absolute path to a file
 //!     PathType,  // enum of Dir or File
@@ -239,7 +240,7 @@ mod write;
 mod read;
 
 pub use abs::PathAbs;
-pub use arc::PathArc;
+pub use arc::{PathArc, current_dir};
 pub use dir::{ListDir, PathDir};
 pub use file::PathFile;
 pub use ty::PathType;
@@ -372,7 +373,6 @@ mod tests {
     }
 
     #[test]
-
     /// Tests to make sure the error messages look like we expect.
     fn sanity_errors() {
         let tmp_dir = TempDir::new("example").expect("create temp dir");
diff --git a/src/open.rs b/src/open.rs
index 7706284..ce94510 100644
--- a/src/open.rs
+++ b/src/open.rs
@@ -36,7 +36,7 @@ impl FileOpen {
         })
     }
 
-    /// Shortcut to open the file if the path is already canonicalized.
+    /// Shortcut to open the file if the path is already absolute.
     ///
     /// Typically you should use `PathFile::open` instead (i.e. `file.open(options)` or
     /// `file.read()`).
diff --git a/src/read.rs b/src/read.rs
index 3f40e9e..51f1d46 100644
--- a/src/read.rs
+++ b/src/read.rs
@@ -51,7 +51,7 @@ impl FileRead {
         Ok(FileRead(FileOpen::open(path, options)?))
     }
 
-    /// Shortcut to open the file if the path is already canonicalized.
+    /// Shortcut to open the file if the path is already absolute.
     pub(crate) fn read_path(path: PathFile) -> Result<FileRead> {
         let mut options = fs::OpenOptions::new();
         options.read(true);
diff --git a/src/ty.rs b/src/ty.rs
index 4de79e5..90e6c4c 100644
--- a/src/ty.rs
+++ b/src/ty.rs
@@ -5,10 +5,9 @@
  * http://opensource.org/licenses/MIT>, at your option. This file may not be
  * copied, modified, or distributed except according to those terms.
  */
-use std::io;
 use std_prelude::*;
 
-use super::{Error, Result};
+use super::Result;
 use super::{PathAbs, PathArc, PathDir, PathFile};
 
 #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
@@ -20,8 +19,7 @@ use super::{PathAbs, PathArc, PathDir, PathFile};
 /// - The items returned from `PathDir::list`
 /// - Serializing paths of different types.
 ///
-/// > Note: symlinks are not supported because they are
-/// > *impossible* for canonicalized paths.
+/// Note that for symlinks, this returns the underlying file type.
 pub enum PathType {
     File(PathFile),
     Dir(PathDir),
@@ -54,11 +52,7 @@ impl PathType {
         } else if ty.is_dir() {
             Ok(PathType::Dir(PathDir(abs)))
         } else {
-            Err(Error::new(
-                io::Error::new(io::ErrorKind::InvalidInput, "path is not a dir or a file"),
-                "resolving",
-                abs.into(),
-            ))
+            unreachable!("rust docs: The fs::metadata function follows symbolic links")
         }
     }
 
diff --git a/src/write.rs b/src/write.rs
index 39509f7..0ef5e92 100644
--- a/src/write.rs
+++ b/src/write.rs
@@ -49,7 +49,7 @@ impl FileWrite {
         Ok(FileWrite(FileOpen::open(path, options)?))
     }
 
-    /// Shortcut to open the file if the path is already canonicalized.
+    /// Shortcut to open the file if the path is already absolute.
     pub(crate) fn open_path(
         path_file: PathFile,
         mut options: fs::OpenOptions,


Test patch:
diff --git a/tests/test_absolute.rs b/tests/test_absolute.rs
new file mode 100644
index 0000000..ee8f743
--- /dev/null
+++ b/tests/test_absolute.rs
@@ -0,0 +1,75 @@
+//! The absolute paths have some gotchas that need to be tested.
+//!
+//! - Using the current working directory
+//! - `..` paths that consume the "root"
+
+extern crate path_abs;
+extern crate tempdir;
+#[macro_use]
+extern crate pretty_assertions;
+
+use path_abs::*;
+use std::path::Path;
+use std::env;
+
+#[test]
+fn test_absolute() {
+    let tmp = tempdir::TempDir::new("ex").unwrap();
+    let tmp = tmp.path();
+    let tmp_abs = PathArc::new(&tmp).canonicalize().unwrap();
+    env::set_current_dir(&tmp_abs).unwrap();
+
+    // Create directory like:
+    // a/
+    // + e/ -> b/c/d
+    // + b/
+    //   + c/
+    //     + d/
+
+    let a = PathDir::create(&tmp.join("a")).unwrap();
+    let b = PathDir::create(&a.join("b")).unwrap();
+    let c = PathDir::create(&b.join("c")).unwrap();
+    let d = PathDir::create(&c.join("d")).unwrap();
+
+    // create symbolic link from a/e -> a/b/c/d
+    let e_sym = d.symlink(&a.join("e")).unwrap();
+    let ty = e_sym.symlink_metadata().unwrap().file_type();
+    assert!(ty.is_symlink(), "{}", e_sym.display());
+
+    assert_ne!(d, e_sym);
+    assert_eq!(d, e_sym.canonicalize().unwrap());
+
+    let a_cwd = Path::new("a");
+    let b_cwd = a.join("b");
+    let c_cwd = b.join("c");
+    let d_cwd = c.join("d");
+    let e_cwd = a.join("e");
+
+    assert_eq!(a, PathDir::new(&a_cwd).unwrap());
+    assert_eq!(b, PathDir::new(&b_cwd).unwrap());
+    assert_eq!(c, PathDir::new(&c_cwd).unwrap());
+    assert_eq!(d, PathDir::new(&d_cwd).unwrap());
+    assert_eq!(e_sym, PathDir::new(&e_cwd).unwrap());
+
+    assert_eq!(b, PathDir::new(c.join("..")).unwrap());
+    assert_eq!(a, PathDir::new(c.join("..").join("..")).unwrap());
+    // just create a PathType
+    let _ = PathType::new(&e_sym).unwrap();
+
+    let mut root_dots = tmp_abs.to_path_buf();
+    let mut dots = tmp_abs.components().count() - 1;
+    if cfg!(windows) {
+        // windows has _two_ "roots", prefix _and_ "root".
+        dots -= 1;
+    }
+    for _ in 0..dots {
+        root_dots.push("..");
+    }
+    let root = PathDir::new(root_dots).unwrap();
+    if cfg!(windows) {
+        assert_eq!(PathDir::new("\\").unwrap(), root);
+    } else {
+        assert_eq!(PathDir::new("/").unwrap(), root);
+    }
+    assert!(PathDir::new(root.join("..")).is_err());
+}