790cb68a20ad4137f5f5db6f36cc820fbb5c0231
[symlink] / src / lib.rs
1 //! A small, cross-platform crate for creating symlinks.
2 //!
3 #![cfg_attr(not(any(target_os = "redox", unix, windows)), doc = "**This platform is not Unix, Windows or Redox; symlinks are not available.**")]
4 //!
5 //! For efficiency, you should prefer to use `symlink_file` or `symlink_dir`—whichever is
6 //! appropriate—rather than `symlink_auto`
7
8 // It’s generally nicer to produce an empty crate on unsupported platforms than to explode.
9
10 use std::fs;
11 use std::io;
12 use std::path::Path;
13
14 #[cfg(windows)]
15 mod internal {
16 pub use std::os::windows::fs::{symlink_file, symlink_dir};
17 pub use std::fs::remove_dir as remove_symlink_dir;
18 use std::fs;
19 use std::io;
20 use std::mem;
21 use std::path::Path;
22
23 #[inline]
24 pub fn symlink_auto<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
25 if fs::metadata(src.as_ref())?.is_dir() {
26 symlink_dir(src.as_ref(), dst.as_ref())
27 } else {
28 symlink_file(src.as_ref(), dst.as_ref())
29 }
30 }
31
32 // Copied from the Rust standard library, src/libstd/sys/windows/fs.rs, because it’s an
33 // implementation detail that isn’t currently exposed in the public interface; I decided to do
34 // it this way rather than depending on the Debug implementation (which likewise could change).
35 #[derive(PartialEq)]
36 enum FileType {
37 Dir, File, SymlinkFile, SymlinkDir, ReparsePoint, MountPoint,
38 }
39
40 impl FileType {
41 pub fn is_symlink_dir(&self) -> bool {
42 *self == FileType::SymlinkDir || *self == FileType::MountPoint
43 }
44 }
45
46 #[inline]
47 pub fn remove_symlink_auto<P: AsRef<Path>>(path: P) -> io::Result<()> {
48 // We need to know whether it’s wrapping a SymlinkFile or SymlinkDir, but in the interests
49 // of consistency this crucial information is concealed. This is why unsafe transmutation
50 // is necessary: to determine whether it’s a symlink file or a symlink dir.
51 let file_type = fs::metadata(path.as_ref())?.file_type();
52 let fs_imp_file_type = unsafe { mem::transmute::<fs::FileType, FileType>(file_type) };
53 if fs_imp_file_type.is_symlink_dir() {
54 fs::remove_dir(path)
55 } else if file_type.is_symlink() {
56 fs::remove_file(path)
57 } else {
58 Err(io::Error::new(io::ErrorKind::InvalidInput, "path is not a symlink"))
59 }
60 }
61 }
62
63 #[cfg(any(target_os = "redox", unix))]
64 mod internal {
65 pub use std::fs::remove_file as remove_symlink_dir;
66 pub use std::fs::remove_file as remove_symlink_auto;
67 // Note that this symlink function takes src and dst as &Path rather than as impl AsRef<Path>.
68 // I don’t know why that is, but I think we’ll go with impl AsRef<Path> in our public
69 // functions. Because of this disparity of signature, when I say that things are equivalent to
70 // calling std::os::unix::fs::symlink on Unix, you can see that I’m not being *quite* rigorous.
71 pub use std::os::unix::fs::{symlink as symlink_auto,
72 symlink as symlink_file,
73 symlink as symlink_dir};
74 }
75
76 /// Create a symlink (non-preferred way).
77 ///
78 /// On Windows, file and directory symlinks are created by distinct methods; to cope with that,
79 /// this function checks whether the destination is a file or a folder and creates the appropriate
80 /// type of symlink based on that result. Therefore, if the destination does not exist or if you do
81 /// not have permission to fetch its metadata, this will return an error on Windows.
82 ///
83 /// On Unix platforms there is no distinction, so this isn’t magic: it’s precisely equivalent to
84 /// calling `std::os::unix::fs::symlink`.
85 ///
86 /// # A note on using this function
87 ///
88 /// Because this is slightly less efficient and more hazardous on Windows, you should prefer to use
89 /// [`symlink_file`](fn.symlink_file.html) or [`symlink_dir`](fn.symlink_dir.html) instead. Only
90 /// use this if you don’t know or care whether the destination is a file or a directory (but even
91 /// then, you do need to know that it exists).
92 ///
93 /// # Errors
94 ///
95 /// An error will be returned if the symlink cannot be created, or—on Windows—if the destination
96 /// does not exist or cannot be read.
97 #[cfg(any(target_os = "redox", unix, windows))]
98 #[inline]
99 pub fn symlink_auto<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
100 internal::symlink_auto(src.as_ref(), dst.as_ref())
101 }
102
103 /// Create a symlink to a file.
104 ///
105 /// On Windows, this is equivalent to `std::os::windows::fs::symlink_file`. If you call it with a
106 /// directory as the destination, TODO CONSEQUENCES.
107 ///
108 /// On Unix, this is equivalent to `std::os::unix::fs::symlink`. If you call it with a directory as
109 /// the destination, nothing bad will happen, but you’re ruining your cross-platform technique and
110 /// ruining the point of this crate, so please don’t.
111 ///
112 /// # Errors
113 ///
114 /// An error will be returned if the symlink cannot be created.
115 #[cfg(any(target_os = "redox", unix, windows))]
116 #[inline]
117 pub fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
118 internal::symlink_file(src.as_ref(), dst.as_ref())
119 }
120
121 /// Create a symlink to a directory.
122 ///
123 /// On Windows, this is equivalent to `std::os::windows::fs::symlink_dir`. If you call it with a
124 /// directory as the destination, TODO CONSEQUENCES.
125 ///
126 /// On Unix, this is equivalent to `std::os::unix::fs::symlink`. If you call it with a directory as
127 /// the destination, nothing bad will happen, but you’re ruining your cross-platform technique and
128 /// ruining the point of this crate, so please don’t.
129 ///
130 /// # Errors
131 ///
132 /// An error will be returned if the symlink cannot be created.
133 #[cfg(any(target_os = "redox", unix, windows))]
134 #[inline]
135 pub fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {
136 internal::symlink_dir(src.as_ref(), dst.as_ref())
137 }
138
139 /// Remove a symlink (non-preferred way).
140 ///
141 /// This inspects the path metadata to remove the symlink as a file or directory, whichever is
142 /// necessary.
143 ///
144 /// # A note on using this function
145 ///
146 /// Because this is slightly less efficient on Windows, you should prefer to use
147 /// [`remove_symlink_file`](fn.remove_symlink_file.html) or
148 /// [`remove_symlink_dir`](fn.remove_symlink_dir.html) instead. Only use this if you don’t know or
149 /// care whether the destination is a file or a directory (but even then, you do need to know that
150 /// it exists).
151 ///
152 /// # Errors
153 ///
154 /// An error will be returned if the symlink cannot be removed.
155 #[cfg(any(target_os = "redox", unix, windows))]
156 #[inline]
157 pub fn remove_symlink_auto<P: AsRef<Path>>(path: P) -> io::Result<()> {
158 internal::remove_symlink_auto(path)
159 }
160
161 /// Remove a directory symlink.
162 ///
163 /// On Windows, this corresponds to `std::fs::remove_dir`.
164 ///
165 /// On Unix, this corresponds to `std::fs::remove_file`.
166 #[cfg(any(target_os = "redox", unix, windows))]
167 #[inline]
168 pub fn remove_symlink_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
169 internal::remove_symlink_dir(path)
170 }
171
172 /// Remove a file symlink.
173 ///
174 /// This just calls `std::fs::remove_file`, but the function is provided here to correspond to
175 /// `remove_symlink_dir`.
176 ///
177 /// On Unix, this corresponds to `std::fs::remove_file`.
178 #[cfg(any(target_os = "redox", unix, windows))]
179 #[inline]
180 pub fn remove_symlink_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
181 fs::remove_file(path)
182 }