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 #[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 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 pub fn list_dir<P: AsPath>(&self, path: P) -> io::Result<DirIter> {
50 open_dir(self, to_cstr(path)?.as_ref())
51 }
52
53 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 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 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 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 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 #[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 #[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 #[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 #[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 #[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 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 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 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 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 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 #[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 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 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 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 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
378pub 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
403pub 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#[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}