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 crate::metadata::{self, Metadata};
11use crate::list::{DirIter, open_dir, open_dirfd};
12
13use crate::{Dir, AsPath};
14
15#[cfg(target_os="linux")]
16const BASE_OPEN_FLAGS: libc::c_int = libc::O_PATH|libc::O_CLOEXEC;
17#[cfg(target_os="freebsd")]
18const BASE_OPEN_FLAGS: libc::c_int = libc::O_DIRECTORY|libc::O_CLOEXEC;
19#[cfg(not(any(target_os="linux", target_os="freebsd")))]
20const BASE_OPEN_FLAGS: libc::c_int = libc::O_CLOEXEC;
21
22impl Dir {
23 #[deprecated(since="0.1.15", note="\
26 Use `Dir::open(\".\")` instead. \
27 Dir::cwd() doesn't open actual file descriptor and uses magic value \
28 instead which resolves to current dir on any syscall invocation. \
29 This is usually counter-intuitive and yields a broken \
30 file descriptor when using `Dir::as_raw_fd`. \
31 Will be removed in version v0.2 of the library.")]
32 pub fn cwd() -> Dir {
33 Dir(libc::AT_FDCWD)
34 }
35
36 pub fn open<P: AsPath>(path: P) -> io::Result<Dir> {
39 Dir::_open(to_cstr(path)?.as_ref())
40 }
41
42 fn _open(path: &CStr) -> io::Result<Dir> {
43 let fd = unsafe {
44 libc::open(path.as_ptr(), BASE_OPEN_FLAGS)
45 };
46 if fd < 0 {
47 Err(io::Error::last_os_error())
48 } else {
49 Ok(Dir(fd))
50 }
51 }
52
53 pub fn list_dir<P: AsPath>(&self, path: P) -> io::Result<DirIter> {
57 open_dir(self, to_cstr(path)?.as_ref())
58 }
59
60 pub fn list_self(&self) -> io::Result<DirIter> {
62 unsafe {
63 open_dirfd(libc::dup(self.0))
64 }
65 }
66
67 pub fn sub_dir<P: AsPath>(&self, path: P) -> io::Result<Dir> {
74 self._sub_dir(to_cstr(path)?.as_ref())
75 }
76
77 fn _sub_dir(&self, path: &CStr) -> io::Result<Dir> {
78 let fd = unsafe {
79 libc::openat(self.0,
80 path.as_ptr(),
81 BASE_OPEN_FLAGS|libc::O_NOFOLLOW)
82 };
83 if fd < 0 {
84 Err(io::Error::last_os_error())
85 } else {
86 Ok(Dir(fd))
87 }
88 }
89
90 pub fn read_link<P: AsPath>(&self, path: P) -> io::Result<PathBuf> {
92 self._read_link(to_cstr(path)?.as_ref())
93 }
94
95 fn _read_link(&self, path: &CStr) -> io::Result<PathBuf> {
96 let mut buf = vec![0u8; 4096];
97 let res = unsafe {
98 libc::readlinkat(self.0,
99 path.as_ptr(),
100 buf.as_mut_ptr() as *mut libc::c_char, buf.len())
101 };
102 if res < 0 {
103 Err(io::Error::last_os_error())
104 } else {
105 buf.truncate(res as usize);
106 Ok(OsString::from_vec(buf).into())
107 }
108 }
109
110 pub fn open_file<P: AsPath>(&self, path: P) -> io::Result<File> {
117 self._open_file(to_cstr(path)?.as_ref(),
118 libc::O_RDONLY, 0)
119 }
120
121 pub fn write_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
130 -> io::Result<File>
131 {
132 self._open_file(to_cstr(path)?.as_ref(),
133 libc::O_CREAT|libc::O_WRONLY|libc::O_TRUNC,
134 mode)
135 }
136
137 pub fn append_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
144 -> io::Result<File>
145 {
146 self._open_file(to_cstr(path)?.as_ref(),
147 libc::O_CREAT|libc::O_WRONLY|libc::O_APPEND,
148 mode)
149 }
150
151 #[deprecated(since="0.1.7", note="please use `write_file` instead")]
162 pub fn create_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
163 -> io::Result<File>
164 {
165 self._open_file(to_cstr(path)?.as_ref(),
166 libc::O_CREAT|libc::O_WRONLY|libc::O_TRUNC,
167 mode)
168 }
169
170 #[cfg(target_os="linux")]
194 pub fn new_unnamed_file(&self, mode: libc::mode_t)
195 -> io::Result<File>
196 {
197 self._open_file(unsafe { CStr::from_bytes_with_nul_unchecked(b".\0") },
198 libc::O_TMPFILE|libc::O_WRONLY,
199 mode)
200 }
201
202 #[cfg(not(target_os="linux"))]
223 pub fn new_unnamed_file<P: AsPath>(&self, _mode: libc::mode_t)
224 -> io::Result<File>
225 {
226 Err(io::Error::new(io::ErrorKind::Other,
227 "creating unnamed tmpfiles is only supported on linux"))
228 }
229
230 #[cfg(target_os="linux")]
242 pub fn link_file_at<F: AsRawFd, P: AsPath>(&self, file: &F, path: P)
243 -> io::Result<()>
244 {
245 let fd_path = format!("/proc/self/fd/{}", file.as_raw_fd());
246 _hardlink(&Dir(libc::AT_FDCWD), to_cstr(fd_path)?.as_ref(),
247 &self, to_cstr(path)?.as_ref(),
248 libc::AT_SYMLINK_FOLLOW)
249 }
250
251 #[cfg(not(target_os="linux"))]
263 pub fn link_file_at<F: AsRawFd, P: AsPath>(&self, _file: F, _path: P)
264 -> io::Result<()>
265 {
266 Err(io::Error::new(io::ErrorKind::Other,
267 "linking unnamed fd to directories is only supported on linux"))
268 }
269
270 pub fn new_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
277 -> io::Result<File>
278 {
279 self._open_file(to_cstr(path)?.as_ref(),
280 libc::O_CREAT|libc::O_EXCL|libc::O_WRONLY,
281 mode)
282 }
283
284 pub fn update_file<P: AsPath>(&self, path: P, mode: libc::mode_t)
291 -> io::Result<File>
292 {
293 self._open_file(to_cstr(path)?.as_ref(),
294 libc::O_CREAT|libc::O_RDWR,
295 mode)
296 }
297
298 fn _open_file(&self, path: &CStr, flags: libc::c_int, mode: libc::mode_t)
299 -> io::Result<File>
300 {
301 unsafe {
302 let res = libc::openat(self.0, path.as_ptr(),
308 flags|libc::O_CLOEXEC|libc::O_NOFOLLOW,
309 mode as libc::c_uint);
310 if res < 0 {
311 Err(io::Error::last_os_error())
312 } else {
313 Ok(File::from_raw_fd(res))
314 }
315 }
316 }
317
318 pub fn symlink<P: AsPath, R: AsPath>(&self, path: P, value: R)
322 -> io::Result<()>
323 {
324 self._symlink(to_cstr(path)?.as_ref(), to_cstr(value)?.as_ref())
325 }
326 fn _symlink(&self, path: &CStr, link: &CStr) -> io::Result<()> {
327 unsafe {
328 let res = libc::symlinkat(link.as_ptr(),
329 self.0, path.as_ptr());
330 if res < 0 {
331 Err(io::Error::last_os_error())
332 } else {
333 Ok(())
334 }
335 }
336 }
337
338 pub fn create_dir<P: AsPath>(&self, path: P, mode: libc::mode_t)
340 -> io::Result<()>
341 {
342 self._create_dir(to_cstr(path)?.as_ref(), mode)
343 }
344 fn _create_dir(&self, path: &CStr, mode: libc::mode_t) -> io::Result<()> {
345 unsafe {
346 let res = libc::mkdirat(self.0, path.as_ptr(), mode);
347 if res < 0 {
348 Err(io::Error::last_os_error())
349 } else {
350 Ok(())
351 }
352 }
353 }
354
355 pub fn local_rename<P: AsPath, R: AsPath>(&self, old: P, new: R)
357 -> io::Result<()>
358 {
359 rename(self, to_cstr(old)?.as_ref(), self, to_cstr(new)?.as_ref())
360 }
361
362 #[cfg(target_os="linux")]
366 pub fn local_exchange<P: AsPath, R: AsPath>(&self, old: P, new: R)
367 -> io::Result<()>
368 {
369 let flags = libc::RENAME_EXCHANGE as libc::c_int;
374 rename_flags(self, to_cstr(old)?.as_ref(),
375 self, to_cstr(new)?.as_ref(),
376 flags)
377 }
378
379 pub fn remove_dir<P: AsPath>(&self, path: P)
383 -> io::Result<()>
384 {
385 self._unlink(to_cstr(path)?.as_ref(), libc::AT_REMOVEDIR)
386 }
387 pub fn remove_file<P: AsPath>(&self, path: P)
389 -> io::Result<()>
390 {
391 self._unlink(to_cstr(path)?.as_ref(), 0)
392 }
393 fn _unlink(&self, path: &CStr, flags: libc::c_int) -> io::Result<()> {
394 unsafe {
395 let res = libc::unlinkat(self.0, path.as_ptr(), flags);
396 if res < 0 {
397 Err(io::Error::last_os_error())
398 } else {
399 Ok(())
400 }
401 }
402 }
403
404 pub fn recover_path(&self) -> io::Result<PathBuf> {
409 let fd = self.0;
410 if fd != libc::AT_FDCWD {
411 read_link(format!("/proc/self/fd/{}", fd))
412 } else {
413 read_link("/proc/self/cwd")
414 }
415 }
416
417 pub fn metadata<P: AsPath>(&self, path: P) -> io::Result<Metadata> {
425 self._stat(to_cstr(path)?.as_ref(), libc::AT_SYMLINK_NOFOLLOW)
426 }
427 fn _stat(&self, path: &CStr, flags: libc::c_int) -> io::Result<Metadata> {
428 unsafe {
429 let mut stat = mem::zeroed();
430 let res = libc::fstatat(self.0, path.as_ptr(),
431 &mut stat, flags);
432 if res < 0 {
433 Err(io::Error::last_os_error())
434 } else {
435 Ok(metadata::new(stat))
436 }
437 }
438 }
439
440 pub fn self_metadata(&self) -> io::Result<Metadata> {
442 unsafe {
443 let mut stat = mem::zeroed();
444 let res = libc::fstat(self.0, &mut stat);
445 if res < 0 {
446 Err(io::Error::last_os_error())
447 } else {
448 Ok(metadata::new(stat))
449 }
450 }
451 }
452
453 pub unsafe fn from_raw_fd_checked(fd: RawFd) -> io::Result<Self> {
460 let mut stat = mem::zeroed();
461 let res = libc::fstat(fd, &mut stat);
462 if res < 0 {
463 Err(io::Error::last_os_error())
464 } else {
465 match stat.st_mode & libc::S_IFMT {
466 libc::S_IFDIR => Ok(Dir(fd)),
467 _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR))
468 }
469 }
470 }
471
472 pub fn try_clone(&self) -> io::Result<Self> {
474 let fd = unsafe { libc::dup(self.0) };
475 if fd == -1 {
476 Err(io::Error::last_os_error())
477 } else {
478 unsafe { Self::from_raw_fd_checked(fd) }
479 }
480 }
481}
482
483pub fn rename<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R)
488 -> io::Result<()>
489 where P: AsPath, R: AsPath,
490{
491 _rename(old_dir, to_cstr(old)?.as_ref(), new_dir, to_cstr(new)?.as_ref())
492}
493
494fn _rename(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr)
495 -> io::Result<()>
496{
497 unsafe {
498 let res = libc::renameat(old_dir.0, old.as_ptr(),
499 new_dir.0, new.as_ptr());
500 if res < 0 {
501 Err(io::Error::last_os_error())
502 } else {
503 Ok(())
504 }
505 }
506}
507
508pub fn hardlink<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R)
517 -> io::Result<()>
518 where P: AsPath, R: AsPath,
519{
520 _hardlink(old_dir, to_cstr(old)?.as_ref(),
521 new_dir, to_cstr(new)?.as_ref(),
522 0)
523}
524
525fn _hardlink(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr,
526 flags: libc::c_int)
527 -> io::Result<()>
528{
529 unsafe {
530 let res = libc::linkat(old_dir.0, old.as_ptr(),
531 new_dir.0, new.as_ptr(), flags);
532 if res < 0 {
533 Err(io::Error::last_os_error())
534 } else {
535 Ok(())
536 }
537 }
538}
539
540#[cfg(target_os="linux")]
547pub fn rename_flags<P, R>(old_dir: &Dir, old: P, new_dir: &Dir, new: R,
548 flags: libc::c_int)
549 -> io::Result<()>
550 where P: AsPath, R: AsPath,
551{
552 _rename_flags(old_dir, to_cstr(old)?.as_ref(),
553 new_dir, to_cstr(new)?.as_ref(),
554 flags)
555}
556
557#[cfg(target_os="linux")]
558fn _rename_flags(old_dir: &Dir, old: &CStr, new_dir: &Dir, new: &CStr,
559 flags: libc::c_int)
560 -> io::Result<()>
561{
562 unsafe {
563 let res = libc::syscall(
564 libc::SYS_renameat2,
565 old_dir.0, old.as_ptr(),
566 new_dir.0, new.as_ptr(), flags);
567 if res < 0 {
568 Err(io::Error::last_os_error())
569 } else {
570 Ok(())
571 }
572 }
573}
574
575impl AsRawFd for Dir {
576 #[inline]
577 fn as_raw_fd(&self) -> RawFd {
578 self.0
579 }
580}
581
582impl FromRawFd for Dir {
583 #[inline]
586 unsafe fn from_raw_fd(fd: RawFd) -> Dir {
587 Dir(fd)
588 }
589}
590
591impl IntoRawFd for Dir {
592 #[inline]
593 fn into_raw_fd(self) -> RawFd {
594 let result = self.0;
595 mem::forget(self);
596 return result;
597 }
598}
599
600impl Drop for Dir {
601 fn drop(&mut self) {
602 let fd = self.0;
603 if fd != libc::AT_FDCWD {
604 unsafe {
605 libc::close(fd);
606 }
607 }
608 }
609}
610
611fn to_cstr<P: AsPath>(path: P) -> io::Result<P::Buffer> {
612 path.to_path()
613 .ok_or_else(|| {
614 io::Error::new(io::ErrorKind::InvalidInput,
615 "nul byte in file name")
616 })
617}
618
619#[cfg(test)]
620mod test {
621 use std::io::{Read};
622 use std::path::Path;
623 use std::os::unix::io::{FromRawFd, IntoRawFd};
624 use crate::{Dir};
625
626 #[test]
627 fn test_open_ok() {
628 assert!(Dir::open("src").is_ok());
629 }
630
631 #[test]
632 #[cfg_attr(target_os="freebsd", should_panic(expected="Not a directory"))]
633 fn test_open_file() {
634 Dir::open("src/lib.rs").unwrap();
635 }
636
637 #[test]
638 fn test_read_file() {
639 let dir = Dir::open("src").unwrap();
640 let mut buf = String::new();
641 dir.open_file("lib.rs").unwrap()
642 .read_to_string(&mut buf).unwrap();
643 assert!(buf.find("extern crate libc;").is_some());
644 }
645
646 #[test]
647 fn test_from_into() {
648 let dir = Dir::open("src").unwrap();
649 let dir = unsafe { Dir::from_raw_fd(dir.into_raw_fd()) };
650 let mut buf = String::new();
651 dir.open_file("lib.rs").unwrap()
652 .read_to_string(&mut buf).unwrap();
653 assert!(buf.find("extern crate libc;").is_some());
654 }
655
656 #[test]
657 #[should_panic(expected="No such file or directory")]
658 fn test_open_no_dir() {
659 Dir::open("src/some-non-existent-file").unwrap();
660 }
661
662 #[test]
663 fn test_list() {
664 let dir = Dir::open("src").unwrap();
665 let me = dir.list_dir(".").unwrap();
666 assert!(me.collect::<Result<Vec<_>, _>>().unwrap()
667 .iter().find(|x| {
668 x.file_name() == Path::new("lib.rs").as_os_str()
669 })
670 .is_some());
671 }
672
673 #[test]
674 fn test_from_raw_fd_checked() {
675 let fd = Dir::open(".").unwrap().into_raw_fd();
676 let dir = unsafe { Dir::from_raw_fd_checked(fd) }.unwrap();
677 let filefd = dir.open_file("src/lib.rs").unwrap().into_raw_fd();
678 match unsafe { Dir::from_raw_fd_checked(filefd) } {
679 Ok(_) => assert!(false, "from_raw_fd_checked succeeded on a non-directory fd!"),
680 Err(e) => assert_eq!(e.raw_os_error().unwrap(), libc::ENOTDIR)
681 }
682 }
683
684 #[test]
685 fn test_try_clone() {
686 let d = Dir::open(".").unwrap();
687 let d2 = d.try_clone().unwrap();
688 drop(d);
689 let _file = d2.open_file("src/lib.rs").unwrap();
690 }
691}