openat/
dir.rs

1use std::io;
2use std::mem;
3use std::ffi::{OsString, CStr};
4use std::fs::{File, read_link};
5use std::os::unix::io::{AsRawFd, RawFd, FromRawFd, IntoRawFd};
6use std::os::unix::ffi::{OsStringExt};
7use std::path::{PathBuf};
8
9use libc;
10use metadata::{self, Metadata};
11use list::{DirIter, open_dir};
12
13use {Dir, AsPath};
14
15impl Dir {
16    /// Creates a directory descriptor that resolves paths relative to current
17    /// working directory (AT_FDCWD)
18    #[deprecated(since="0.1.15", note="\
19        Use `Dir::open(\".\")` instead. \
20        Dir::cwd() doesn't open actual file descriptor and uses magic value \
21        instead which resolves to current dir on any syscall invocation. \
22        This is usually counter-intuitive and yields a broken \
23        file descriptor when using `Dir::as_raw_fd`. \
24        Will be removed in version v0.2 of the library.")]
25    pub fn cwd() -> Dir {
26        Dir(libc::AT_FDCWD)
27    }
28
29    /// Open a directory descriptor at specified path
30    // TODO(tailhook) maybe accept only absolute paths?
31    pub fn open<P: AsPath>(path: P) -> io::Result<Dir> {
32        Dir::_open(to_cstr(path)?.as_ref())
33    }
34
35    fn _open(path: &CStr) -> io::Result<Dir> {
36        let fd = unsafe {
37            libc::open(path.as_ptr(), libc::O_PATH|libc::O_CLOEXEC)
38        };
39        if fd < 0 {
40            Err(io::Error::last_os_error())
41        } else {
42            Ok(Dir(fd))
43        }
44    }
45
46    /// List subdirectory of this dir
47    ///
48    /// You can list directory itself if `"."` is specified as path.
49    pub fn list_dir<P: AsPath>(&self, path: P) -> io::Result<DirIter> {
50        open_dir(self, to_cstr(path)?.as_ref())
51    }
52
53    /// Open subdirectory
54    pub fn sub_dir<P: AsPath>(&self, path: P) -> io::Result<Dir> {
55        self._sub_dir(to_cstr(path)?.as_ref())
56    }
57
58    fn _sub_dir(&self, path: &CStr) -> io::Result<Dir> {
59        let fd = unsafe {
60            libc::openat(self.0,
61                        path.as_ptr(),
62                        libc::O_PATH|libc::O_CLOEXEC|libc::O_NOFOLLOW)
63        };
64        if fd < 0 {
65            Err(io::Error::last_os_error())
66        } else {
67            Ok(Dir(fd))
68        }
69    }
70
71    /// Read link in this directory
72    pub fn read_link<P: AsPath>(&self, path: P) -> io::Result<PathBuf> {
73        self._read_link(to_cstr(path)?.as_ref())
74    }
75
76    fn _read_link(&self, path: &CStr) -> io::Result<PathBuf> {
77        let mut buf = vec![0u8; 4096];
78        let res = unsafe {
79            libc::readlinkat(self.0,
80                        path.as_ptr(),
81                        buf.as_mut_ptr() as *mut libc::c_char, buf.len())
82        };
83        if res < 0 {
84            Err(io::Error::last_os_error())
85        } else {
86            buf.truncate(res as usize);
87            Ok(OsString::from_vec(buf).into())
88        }
89    }
90
91    /// Open file for reading in this directory
92    pub fn open_file<P: AsPath>(&self, path: P) -> io::Result<File> {
93        self._open_file(to_cstr(path)?.as_ref(),
94            libc::O_RDONLY, 0)
95    }
96
97    /// Open file for writing, create if necessary, truncate on open
98    pub fn write_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
99        -> io::Result<File>
100    {
101        self._open_file(to_cstr(path)?.as_ref(),
102            libc::O_CREAT|libc::O_WRONLY|libc::O_TRUNC,
103            mode)
104    }
105
106    /// Open file for append, create if necessary
107    pub fn append_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
108        -> io::Result<File>
109    {
110        self._open_file(to_cstr(path)?.as_ref(),
111            libc::O_CREAT|libc::O_WRONLY|libc::O_APPEND,
112            mode)
113    }
114
115    /// Create file for writing (and truncate) in this directory
116    ///
117    /// Deprecated alias for `write_file`
118    #[deprecated(since="0.1.7", note="please use `write_file` instead")]
119    pub fn create_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
120        -> io::Result<File>
121    {
122        self._open_file(to_cstr(path)?.as_ref(),
123            libc::O_CREAT|libc::O_WRONLY|libc::O_TRUNC,
124            mode)
125    }
126
127    /// Create a tmpfile in this directory which isn't linked to any filename
128    ///
129    /// This works by passing `O_TMPFILE` into the openat call. The flag is
130    /// supported only on linux. So this function always returns error on
131    /// such systems.
132    ///
133    /// **WARNING!** On glibc < 2.22 file permissions of the newly created file
134    /// may be arbitrary. Consider chowning after creating a file.
135    ///
136    /// Note: It may be unclear why creating unnamed file requires a dir. There
137    /// are two reasons:
138    ///
139    /// 1. It's created (and occupies space) on a real filesystem, so the
140    ///    directory is a way to find out which filesystem to attach file to
141    /// 2. This method is mostly needed to initialize the file then link it
142    ///    using ``link_file_at`` to the real directory entry. When linking
143    ///    it must be linked into the same filesystem. But because for most
144    ///    programs finding out filesystem layout is an overkill the rule of
145    ///    thumb is to create a file in the the target directory.
146    ///
147    /// Currently, we recommend to fallback on any error if this operation
148    /// can't be accomplished rather than relying on specific error codes,
149    /// because semantics of errors are very ugly.
150    #[cfg(target_os="linux")]
151    pub fn new_unnamed_file(&self, mode: libc::mode_t)
152        -> io::Result<File>
153    {
154        self._open_file(unsafe { CStr::from_bytes_with_nul_unchecked(b".\0") },
155            libc::O_TMPFILE|libc::O_WRONLY,
156            mode)
157    }
158
159    /// Create a tmpfile in this directory which isn't linked to any filename
160    ///
161    /// This works by passing `O_TMPFILE` into the openat call. The flag is
162    /// supported only on linux. So this function always returns error on
163    /// such systems.
164    ///
165    /// Note: It may be unclear why creating unnamed file requires a dir. There
166    /// are two reasons:
167    ///
168    /// 1. It's created (and occupies space) on a real filesystem, so the
169    ///    directory is a way to find out which filesystem to attach file to
170    /// 2. This method is mostly needed to initialize the file then link it
171    ///    using ``link_file_at`` to the real directory entry. When linking
172    ///    it must be linked into the same filesystem. But because for most
173    ///    programs finding out filesystem layout is an overkill the rule of
174    ///    thumb is to create a file in the the target directory.
175    ///
176    /// Currently, we recommend to fallback on any error if this operation
177    /// can't be accomplished rather than relying on specific error codes,
178    /// because semantics of errors are very ugly.
179    #[cfg(not(target_os="linux"))]
180    pub fn new_unnamed_file<P: AsPath>(&self, _mode: libc::mode_t)
181        -> io::Result<File>
182    {
183        Err(io::Error::new(io::ErrorKind::Other,
184            "creating unnamed tmpfiles is only supported on linux"))
185    }
186
187    /// Link open file to a specified path
188    ///
189    /// This is used with ``new_unnamed_file()`` to create and initialize the
190    /// file before linking it into a filesystem. This requires `/proc` to be
191    /// mounted and works **only on linux**.
192    ///
193    /// On systems other than linux this always returns error. It's expected
194    /// that in most cases this methos is not called if ``new_unnamed_file``
195    /// fails. But in obscure scenarios where `/proc` is not mounted this
196    /// method may fail even on linux. So your code should be able to fallback
197    /// to a named file if this method fails too.
198    #[cfg(target_os="linux")]
199    pub fn link_file_at<F: AsRawFd, P: AsPath>(&self, file: &F, path: P)
200        -> io::Result<()>
201    {
202        let fd_path = format!("/proc/self/fd/{}", file.as_raw_fd());
203        _hardlink(&Dir(libc::AT_FDCWD), to_cstr(fd_path)?.as_ref(),
204            &self, to_cstr(path)?.as_ref(),
205            libc::AT_SYMLINK_FOLLOW)
206    }
207
208    /// Link open file to a specified path
209    ///
210    /// This is used with ``new_unnamed_file()`` to create and initialize the
211    /// file before linking it into a filesystem. This requires `/proc` to be
212    /// mounted and works **only on linux**.
213    ///
214    /// On systems other than linux this always returns error. It's expected
215    /// that in most cases this methos is not called if ``new_unnamed_file``
216    /// fails. But in obscure scenarios where `/proc` is not mounted this
217    /// method may fail even on linux. So your code should be able to fallback
218    /// to a named file if this method fails too.
219    #[cfg(not(target_os="linux"))]
220    pub fn link_file_at<F: AsRawFd, P: AsPath>(&self, _file: F, _path: P)
221        -> io::Result<()>
222    {
223        Err(io::Error::new(io::ErrorKind::Other,
224            "linking unnamed fd to directories is only supported on linux"))
225    }
226
227    /// Create file if not exists, fail if exists
228    ///
229    /// This function checks existence and creates file atomically with
230    /// respect to other threads and processes.
231    ///
232    /// Technically it means passing `O_EXCL` flag to open.
233    pub fn new_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
234        -> io::Result<File>
235    {
236        self._open_file(to_cstr(path)?.as_ref(),
237            libc::O_CREAT|libc::O_EXCL|libc::O_WRONLY,
238            mode)
239    }
240
241    /// Open file for reading and writing without truncation, create if needed
242    pub fn update_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
243        -> io::Result<File>
244    {
245        self._open_file(to_cstr(path)?.as_ref(),
246            libc::O_CREAT|libc::O_RDWR,
247            mode)
248    }
249
250    fn _open_file(&self, path: &CStr, flags: libc::c_int, mode: libc::mode_t)
251        -> io::Result<File>
252    {
253        unsafe {
254            let res = libc::openat(self.0, path.as_ptr(),
255                            flags|libc::O_CLOEXEC|libc::O_NOFOLLOW,
256                            mode as libc::c_uint);
257            if res < 0 {
258                Err(io::Error::last_os_error())
259            } else {
260                Ok(File::from_raw_fd(res))
261            }
262        }
263    }
264
265    /// Make a symlink in this directory
266    ///
267    /// Note: the order of arguments differ from `symlinkat`
268    pub fn symlink<P: AsPath, R: AsPath>(&self, path: P, value: R)
269        -> io::Result<()>
270    {
271        self._symlink(to_cstr(path)?.as_ref(), to_cstr(value)?.as_ref())
272    }
273    fn _symlink(&self, path: &CStr, link: &CStr) -> io::Result<()> {
274        unsafe {
275            let res = libc::symlinkat(link.as_ptr(),
276                self.0, path.as_ptr());
277            if res < 0 {
278                Err(io::Error::last_os_error())
279            } else {
280                Ok(())
281            }
282        }
283    }
284
285    /// Create a subdirectory in this directory
286    pub fn create_dir<P: AsPath>(&self, path: P, mode: libc::mode_t)
287        -> io::Result<()>
288    {
289        self._create_dir(to_cstr(path)?.as_ref(), mode)
290    }
291    fn _create_dir(&self, path: &CStr, mode: libc::mode_t) -> io::Result<()> {
292        unsafe {
293            let res = libc::mkdirat(self.0, path.as_ptr(), mode);
294            if res < 0 {
295                Err(io::Error::last_os_error())
296            } else {
297                Ok(())
298            }
299        }
300    }
301
302    /// Rename a file in this directory to another name (keeping same dir)
303    pub fn local_rename<P: AsPath, R: AsPath>(&self, old: P, new: R)
304        -> io::Result<()>
305    {
306        rename(self, to_cstr(old)?.as_ref(), self, to_cstr(new)?.as_ref())
307    }
308
309    /// Similar to `local_rename` but atomically swaps both paths
310    ///
311    /// Only supported on Linux.
312    #[cfg(target_os="linux")]
313    pub fn local_exchange<P: AsPath, R: AsPath>(&self, old: P, new: R)
314        -> io::Result<()>
315    {
316        rename_flags(self, to_cstr(old)?.as_ref(),
317            self, to_cstr(new)?.as_ref(),
318            libc::RENAME_EXCHANGE)
319    }
320
321    /// Remove a subdirectory in this directory
322    ///
323    /// Note only empty directory may be removed
324    pub fn remove_dir<P: AsPath>(&self, path: P)
325        -> io::Result<()>
326    {
327        self._unlink(to_cstr(path)?.as_ref(), libc::AT_REMOVEDIR)
328    }
329    /// Remove a file in this directory
330    pub fn remove_file<P: AsPath>(&self, path: P)
331        -> io::Result<()>
332    {
333        self._unlink(to_cstr(path)?.as_ref(), 0)
334    }
335    fn _unlink(&self, path: &CStr, flags: libc::c_int) -> io::Result<()> {
336        unsafe {
337            let res = libc::unlinkat(self.0, path.as_ptr(), flags);
338            if res < 0 {
339                Err(io::Error::last_os_error())
340            } else {
341                Ok(())
342            }
343        }
344    }
345
346    /// Get the path of this directory (if possible)
347    ///
348    /// This uses symlinks in `/proc/self`, they sometimes may not be
349    /// available so use with care.
350    pub fn recover_path(&self) -> io::Result<PathBuf> {
351        let fd = self.0;
352        if fd != libc::AT_FDCWD {
353            read_link(format!("/proc/self/fd/{}", fd))
354        } else {
355            read_link("/proc/self/cwd")
356        }
357    }
358
359    /// Returns metadata of an entry in this directory
360    pub fn metadata<P: AsPath>(&self, path: P) -> io::Result<Metadata> {
361        self._stat(to_cstr(path)?.as_ref(), libc::AT_SYMLINK_NOFOLLOW)
362    }
363    fn _stat(&self, path: &CStr, flags: libc::c_int) -> io::Result<Metadata> {
364        unsafe {
365            let mut stat = mem::zeroed();
366            let res = libc::fstatat(self.0, path.as_ptr(),
367                &mut stat, flags);
368            if res < 0 {
369                Err(io::Error::last_os_error())
370            } else {
371                Ok(metadata::new(stat))
372            }
373        }
374    }
375
376}
377
378/// Rename (move) a file between directories
379///
380/// Files must be on a single filesystem anyway. This funtion does **not**
381/// fallback to copying if needed.
382pub fn rename<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R)
383    -> io::Result<()>
384    where P: AsPath, R: AsPath,
385{
386    _rename(old_dir, to_cstr(old)?.as_ref(), new_dir, to_cstr(new)?.as_ref())
387}
388
389fn _rename(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr)
390    -> io::Result<()>
391{
392    unsafe {
393        let res = libc::renameat(old_dir.0, old.as_ptr(),
394            new_dir.0, new.as_ptr());
395        if res < 0 {
396            Err(io::Error::last_os_error())
397        } else {
398            Ok(())
399        }
400    }
401}
402
403/// Create a hardlink to a file
404///
405/// Files must be on a single filesystem even if they are in different
406/// directories.
407///
408/// Note: by default ``linkat`` syscall doesn't resolve symbolic links, and
409/// it's also behavior of this function. It's recommended to resolve symlinks
410/// manually if needed.
411pub fn hardlink<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R)
412    -> io::Result<()>
413    where P: AsPath, R: AsPath,
414{
415    _hardlink(old_dir, to_cstr(old)?.as_ref(),
416              new_dir, to_cstr(new)?.as_ref(),
417              0)
418}
419
420fn _hardlink(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr,
421             flags: libc::c_int)
422    -> io::Result<()>
423{
424    unsafe {
425        let res = libc::linkat(old_dir.0, old.as_ptr(),
426            new_dir.0, new.as_ptr(), flags);
427        if res < 0 {
428            Err(io::Error::last_os_error())
429        } else {
430            Ok(())
431        }
432    }
433}
434
435/// Rename (move) a file between directories with flags
436///
437/// Files must be on a single filesystem anyway. This funtion does **not**
438/// fallback to copying if needed.
439///
440/// Only supported on Linux.
441#[cfg(target_os="linux")]
442pub fn rename_flags<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R,
443    flags: libc::c_int)
444    -> io::Result<()>
445    where P: AsPath, R: AsPath,
446{
447    _rename_flags(old_dir, to_cstr(old)?.as_ref(),
448        new_dir, to_cstr(new)?.as_ref(),
449        flags)
450}
451
452#[cfg(target_os="linux")]
453fn _rename_flags(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr,
454    flags: libc::c_int)
455    -> io::Result<()>
456{
457    unsafe {
458        let res = libc::syscall(
459            libc::SYS_renameat2,
460            old_dir.0, old.as_ptr(),
461            new_dir.0, new.as_ptr(), flags);
462        if res < 0 {
463            Err(io::Error::last_os_error())
464        } else {
465            Ok(())
466        }
467    }
468}
469
470impl AsRawFd for Dir {
471    #[inline]
472    fn as_raw_fd(&self) -> RawFd {
473        self.0
474    }
475}
476
477impl FromRawFd for Dir {
478    #[inline]
479    unsafe fn from_raw_fd(fd: RawFd) -> Dir {
480        Dir(fd)
481    }
482}
483
484impl IntoRawFd for Dir {
485    #[inline]
486    fn into_raw_fd(self) -> RawFd {
487        let result = self.0;
488        mem::forget(self);
489        return result;
490    }
491}
492
493impl Drop for Dir {
494    fn drop(&mut self) {
495        let fd = self.0;
496        if fd != libc::AT_FDCWD {
497            unsafe {
498                libc::close(fd);
499            }
500        }
501    }
502}
503
504fn to_cstr<P: AsPath>(path: P) -> io::Result<P::Buffer> {
505    path.to_path()
506    .ok_or_else(|| {
507        io::Error::new(io::ErrorKind::InvalidInput,
508                       "nul byte in file name")
509    })
510}
511
512#[cfg(test)]
513mod test {
514    use std::io::{Read};
515    use std::path::Path;
516    use std::os::unix::io::{FromRawFd, IntoRawFd};
517    use {Dir};
518
519    #[test]
520    fn test_open_ok() {
521        assert!(Dir::open("src").is_ok());
522    }
523
524    #[test]
525    fn test_open_file() {
526        Dir::open("src/lib.rs").unwrap();
527    }
528
529    #[test]
530    fn test_read_file() {
531        let dir = Dir::open("src").unwrap();
532        let mut buf = String::new();
533        dir.open_file("lib.rs").unwrap()
534            .read_to_string(&mut buf).unwrap();
535        assert!(buf.find("extern crate libc;").is_some());
536    }
537
538    #[test]
539    fn test_from_into() {
540        let dir = Dir::open("src").unwrap();
541        let dir = unsafe { Dir::from_raw_fd(dir.into_raw_fd()) };
542        let mut buf = String::new();
543        dir.open_file("lib.rs").unwrap()
544            .read_to_string(&mut buf).unwrap();
545        assert!(buf.find("extern crate libc;").is_some());
546    }
547
548    #[test]
549    #[should_panic(expected="No such file or directory")]
550    fn test_open_no_dir() {
551        Dir::open("src/some-non-existent-file").unwrap();
552    }
553
554    #[test]
555    fn test_list() {
556        let dir = Dir::open("src").unwrap();
557        let me = dir.list_dir(".").unwrap();
558        assert!(me.collect::<Result<Vec<_>, _>>().unwrap()
559                .iter().find(|x| {
560                    x.file_name() == Path::new("lib.rs").as_os_str()
561                })
562                .is_some());
563    }
564}