1#![deny(unused_results)]
22#![deny(missing_docs)]
23#![deny(unsafe_code)]
24
25const TEMPFILE_ATTEMPTS: u32 = 100;
26
27use rand::Rng;
28use std::ffi::OsStr;
29use std::fs::File;
30use std::io::prelude::*;
31use std::os::unix::ffi::OsStrExt;
32use std::os::unix::fs::PermissionsExt;
33use std::os::unix::io::AsRawFd;
34use std::os::unix::prelude::FileExt as UnixFileExt;
35use std::path::Path;
36use std::{fs, io};
37
38macro_rules! retry_eintr {
40 ($inner:expr) => {
41 loop {
42 let err = match $inner {
43 Err(e) => e,
44 val => break val,
45 };
46
47 if let Some(errno) = err.raw_os_error() {
48 if errno == libc::EINTR {
49 continue;
50 }
51 }
52
53 break Err(err);
54 }
55 };
56}
57
58pub trait OpenatDirExt {
60 fn open_file_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<fs::File>>;
65
66 fn read_to_string<P: openat::AsPath>(&self, p: P) -> io::Result<String>;
68
69 fn read_to_string_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<String>>;
71
72 fn remove_file_optional<P: openat::AsPath>(&self, p: P) -> io::Result<bool>;
75
76 fn remove_dir_optional<P: openat::AsPath>(&self, p: P) -> io::Result<bool>;
79
80 fn sub_dir_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<openat::Dir>>;
82
83 fn metadata_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<openat::Metadata>>;
85
86 fn get_file_type(&self, e: &openat::Entry) -> io::Result<openat::SimpleType>;
89
90 fn exists<P: openat::AsPath>(&self, p: P) -> io::Result<bool>;
93
94 fn ensure_dir<P: openat::AsPath>(&self, p: P, mode: libc::mode_t) -> io::Result<()>;
96
97 fn ensure_dir_all<P: openat::AsPath>(&self, p: P, mode: libc::mode_t) -> io::Result<()>;
99
100 fn remove_all<P: openat::AsPath>(&self, p: P) -> io::Result<bool>;
102
103 fn syncfs(&self) -> io::Result<()>;
105
106 fn local_rename_optional<P: AsRef<Path>, R: AsRef<Path>>(
110 &self,
111 oldpath: P,
112 newpath: R,
113 ) -> io::Result<bool>;
114
115 fn update_timestamps<P: openat::AsPath>(&self, path: P) -> io::Result<()>;
120
121 fn set_mode<P: openat::AsPath>(&self, path: P, mode: libc::mode_t) -> io::Result<()>;
125
126 fn copy_file<S: openat::AsPath, D: openat::AsPath>(&self, s: S, d: D) -> io::Result<()>;
132
133 fn copy_file_at<S: openat::AsPath, D: openat::AsPath>(
138 &self,
139 s: S,
140 target_dir: &openat::Dir,
141 d: D,
142 ) -> io::Result<()>;
143
144 fn new_file_writer(&self, mode: libc::mode_t) -> io::Result<FileWriter>;
147
148 fn write_file_with<P: AsRef<Path>, F, T, E>(
153 &self,
154 destname: P,
155 mode: libc::mode_t,
156 gen_content_fn: F,
157 ) -> Result<T, E>
158 where
159 F: FnOnce(&mut std::io::BufWriter<std::fs::File>) -> Result<T, E>,
160 E: From<io::Error>,
161 {
162 let mut w = self.new_file_writer(mode)?;
163 gen_content_fn(&mut w.writer).and_then(|t| {
164 w.complete(destname)?;
165 Ok(t)
166 })
167 }
168
169 fn write_file_with_sync<P: AsRef<Path>, F, T, E>(
171 &self,
172 destname: P,
173 mode: libc::mode_t,
174 gen_content_fn: F,
175 ) -> Result<T, E>
176 where
177 F: FnOnce(&mut std::io::BufWriter<std::fs::File>) -> Result<T, E>,
178 E: From<io::Error>,
179 {
180 let mut w = self.new_file_writer(mode)?;
181 gen_content_fn(&mut w.writer).and_then(|t| {
182 w.complete_with(destname, |f| f.sync_all())?;
183 Ok(t)
184 })
185 }
186
187 fn write_file_contents<P: AsRef<Path>, C: AsRef<[u8]>>(
190 &self,
191 destname: P,
192 mode: libc::mode_t,
193 contents: C,
194 ) -> io::Result<()> {
195 self.write_file_with(destname, mode, |w| w.write_all(contents.as_ref()))
196 }
197}
198
199impl OpenatDirExt for openat::Dir {
200 fn open_file_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<fs::File>> {
201 match self.open_file(p) {
202 Ok(f) => Ok(Some(f)),
203 Err(e) => {
204 if e.kind() == io::ErrorKind::NotFound {
205 Ok(None)
206 } else {
207 Err(e)
208 }
209 }
210 }
211 }
212
213 fn read_to_string<P: openat::AsPath>(&self, p: P) -> io::Result<String> {
214 impl_read_to_string(self.open_file(p)?)
215 }
216
217 fn read_to_string_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<String>> {
218 if let Some(f) = self.open_file_optional(p)? {
219 Ok(Some(impl_read_to_string(f)?))
220 } else {
221 Ok(None)
222 }
223 }
224
225 fn remove_file_optional<P: openat::AsPath>(&self, p: P) -> io::Result<bool> {
226 impl_remove_file_optional(self, p)
227 }
228
229 fn remove_dir_optional<P: openat::AsPath>(&self, p: P) -> io::Result<bool> {
230 match self.remove_dir(p) {
231 Ok(_) => Ok(true),
232 Err(e) => {
233 if e.kind() == io::ErrorKind::NotFound {
234 Ok(false)
235 } else {
236 Err(e)
237 }
238 }
239 }
240 }
241
242 fn metadata_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<openat::Metadata>> {
243 match self.metadata(p) {
244 Ok(d) => Ok(Some(d)),
245 Err(e) => {
246 if e.kind() == io::ErrorKind::NotFound {
247 Ok(None)
248 } else {
249 Err(e)
250 }
251 }
252 }
253 }
254
255 fn sub_dir_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<openat::Dir>> {
256 match self.sub_dir(p) {
257 Ok(d) => Ok(Some(d)),
258 Err(e) => {
259 if e.kind() == io::ErrorKind::NotFound {
260 Ok(None)
261 } else {
262 Err(e)
263 }
264 }
265 }
266 }
267
268 fn get_file_type(&self, e: &openat::Entry) -> io::Result<openat::SimpleType> {
269 if let Some(ftype) = e.simple_type() {
270 Ok(ftype)
271 } else {
272 Ok(self.metadata(e.file_name())?.simple_type())
273 }
274 }
275
276 fn exists<P: openat::AsPath>(&self, p: P) -> io::Result<bool> {
277 match self.metadata(p) {
278 Ok(_) => Ok(true),
279 Err(e) => {
280 if e.kind() == io::ErrorKind::NotFound {
281 Ok(false)
282 } else {
283 Err(e)
284 }
285 }
286 }
287 }
288
289 fn ensure_dir<P: openat::AsPath>(&self, p: P, mode: libc::mode_t) -> io::Result<()> {
290 match self.create_dir(p, mode) {
291 Ok(_) => Ok(()),
292 Err(e) => {
293 if e.kind() == io::ErrorKind::AlreadyExists {
294 Ok(())
295 } else {
296 Err(e)
297 }
298 }
299 }
300 }
301
302 fn ensure_dir_all<P: openat::AsPath>(&self, p: P, mode: libc::mode_t) -> io::Result<()> {
303 let p = to_cstr(p)?;
304 let p = p.as_ref();
305 let p = Path::new(OsStr::from_bytes(p.to_bytes()));
307 match self.create_dir(p, mode) {
310 Ok(_) => {}
311 Err(e) => match e.kind() {
312 io::ErrorKind::AlreadyExists => {}
313 io::ErrorKind::NotFound => impl_ensure_dir_all(self, p, mode)?,
315 _ => return Err(e),
316 },
317 }
318 Ok(())
319 }
320
321 fn copy_file<S: openat::AsPath, D: openat::AsPath>(&self, s: S, d: D) -> io::Result<()> {
322 let src = self.open_file(s)?;
323 impl_copy_regfile(&src, self, d)
324 }
325
326 fn copy_file_at<S: openat::AsPath, D: openat::AsPath>(
327 &self,
328 s: S,
329 target_dir: &openat::Dir,
330 d: D,
331 ) -> io::Result<()> {
332 let src = self.open_file(s)?;
333 impl_copy_regfile(&src, target_dir, d)
334 }
335
336 fn remove_all<P: openat::AsPath>(&self, p: P) -> io::Result<bool> {
337 impl_remove_all(self, p)
338 }
339
340 #[allow(unsafe_code)]
341 fn syncfs(&self) -> io::Result<()> {
342 let dirfd = self.open_file(".")?;
346 let ret = unsafe { libc::syncfs(dirfd.as_raw_fd()) };
347 if ret == 0 {
348 Ok(())
349 } else {
350 Err(std::io::Error::last_os_error())
351 }
352 }
353
354 fn local_rename_optional<P: AsRef<Path>, R: AsRef<Path>>(
357 &self,
358 oldpath: P,
359 newpath: R,
360 ) -> io::Result<bool> {
361 if self.exists(oldpath.as_ref())? {
362 self.local_rename(oldpath.as_ref(), newpath.as_ref())
363 .and(Ok(true))
364 } else {
365 Ok(false)
366 }
367 }
368
369 fn update_timestamps<P: openat::AsPath>(&self, p: P) -> io::Result<()> {
370 use nix::sys::stat::{utimensat, UtimensatFlags};
371 use nix::sys::time::TimeSpec;
372
373 let path = p
374 .to_path()
375 .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "null byte in path"))?;
376 let now = TimeSpec::from(libc::timespec {
377 tv_nsec: libc::UTIME_NOW,
378 tv_sec: 0,
379 });
380 retry_eintr!(utimensat(
381 Some(self.as_raw_fd()),
382 path.as_ref(),
383 &now,
384 &now,
385 UtimensatFlags::NoFollowSymlink,
386 )
387 .map_err(map_nix_error))
388 }
389
390 fn set_mode<P: openat::AsPath>(&self, p: P, mode: libc::mode_t) -> io::Result<()> {
391 use nix::sys::stat::{fchmodat, FchmodatFlags, Mode};
392 use openat::SimpleType;
393
394 let path = p
395 .to_path()
396 .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "null byte in path"))?;
397
398 {
399 let entry_meta = self.metadata(path.as_ref())?;
403 if entry_meta.simple_type() == SimpleType::Symlink {
404 return Ok(());
405 };
406 }
407
408 let perms = Mode::from_bits_truncate(mode);
409 fchmodat(
410 Some(self.as_raw_fd()),
411 path.as_ref(),
412 perms,
413 FchmodatFlags::FollowSymlink,
414 )
415 .map_err(map_nix_error)?;
416
417 Ok(())
418 }
419
420 fn new_file_writer(&self, mode: libc::mode_t) -> io::Result<FileWriter> {
421 let (tmpf, name) = if let Ok(tmpf) = self.new_unnamed_file(mode) {
422 (tmpf, None)
423 } else {
424 let (tmpf, name) = tempfile_in(self, ".tmp", ".tmp", mode)?;
426 (tmpf, Some(name))
427 };
428 Ok(FileWriter::new(self, tmpf, name))
429 }
430}
431
432fn impl_read_to_string(mut f: File) -> io::Result<String> {
433 let mut buf = String::new();
434 let _ = f.read_to_string(&mut buf)?;
435 Ok(buf)
436}
437
438fn map_nix_error(e: nix::Error) -> io::Error {
439 io::Error::from_raw_os_error(e as i32)
440}
441
442#[allow(deprecated)]
443fn copy_regfile_inner(
444 src: &File,
445 srcmeta: &std::fs::Metadata,
446 dest: &mut FileWriter,
447) -> io::Result<()> {
448 let destf = dest.writer.get_mut();
450 let _ = src.copy_to(destf)?;
451 let nixmode = nix::sys::stat::Mode::from_bits_truncate(srcmeta.permissions().mode());
452 nix::sys::stat::fchmod(destf.as_raw_fd(), nixmode).map_err(map_nix_error)?;
453 Ok(())
454}
455
456fn impl_copy_regfile<D: openat::AsPath>(
457 src: &File,
458 target_dir: &openat::Dir,
459 d: D,
460) -> io::Result<()> {
461 let d = to_cstr(d)?;
462 let d = OsStr::from_bytes(d.as_ref().to_bytes());
463 let meta = src.metadata()?;
464 let mut w = target_dir.new_file_writer(0o600)?;
466 copy_regfile_inner(src, &meta, &mut w).and_then(|t| {
467 w.complete(d)?;
468 Ok(t)
469 })
470}
471
472fn impl_remove_file_optional<P: openat::AsPath>(d: &openat::Dir, path: P) -> io::Result<bool> {
473 match d.remove_file(path) {
474 Ok(_) => Ok(true),
475 Err(e) => {
476 if e.kind() == io::ErrorKind::NotFound {
477 Ok(false)
478 } else {
479 Err(e)
480 }
481 }
482 }
483}
484
485pub(crate) fn random_name(rng: &mut rand::rngs::ThreadRng, prefix: &str, suffix: &str) -> String {
486 let mut tmpname = prefix.to_string();
487 for _ in 0..8 {
488 tmpname.push(rng.sample(rand::distributions::Alphanumeric).into());
489 }
490 tmpname.push_str(suffix);
491 tmpname
492}
493
494pub(crate) fn tempfile_in(
495 d: &openat::Dir,
496 prefix: &str,
497 suffix: &str,
498 mode: libc::mode_t,
499) -> io::Result<(fs::File, String)> {
500 for _ in 0..TEMPFILE_ATTEMPTS {
501 let tmpname = random_name(&mut rand::thread_rng(), prefix, suffix);
502 match d.new_file(tmpname.as_str(), mode) {
503 Ok(f) => return Ok((f, tmpname)),
504 Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
505 Err(e) => return Err(e),
506 }
507 }
508 Err(io::Error::new(
509 io::ErrorKind::AlreadyExists,
510 format!(
511 "Exhausted {} attempts to create temporary file",
512 TEMPFILE_ATTEMPTS
513 ),
514 ))
515}
516
517pub(crate) fn impl_ensure_dir_all(d: &openat::Dir, p: &Path, mode: libc::mode_t) -> io::Result<()> {
521 if let Some(parent) = p.parent() {
522 if !parent.as_os_str().is_empty() {
523 impl_ensure_dir_all(d, parent, mode)?;
524 }
525 }
526 d.ensure_dir(p, mode)?;
527 Ok(())
528}
529
530pub(crate) fn remove_children(d: &openat::Dir, iter: openat::DirIter) -> io::Result<()> {
531 for entry in iter {
532 let entry = entry?;
533 match d.get_file_type(&entry)? {
534 openat::SimpleType::Dir => {
535 let subd = d.sub_dir(&entry)?;
536 remove_children(&subd, subd.list_dir(".")?)?;
537 let _ = d.remove_dir_optional(&entry)?;
538 }
539 _ => {
540 let _ = d.remove_file_optional(entry.file_name())?;
541 }
542 }
543 }
544 Ok(())
545}
546
547fn impl_remove_all<P: openat::AsPath>(d: &openat::Dir, p: P) -> io::Result<bool> {
548 let cp = to_cstr(p)?;
549 let cp = cp.as_ref();
550 match impl_remove_file_optional(d, cp) {
551 Ok(b) => Ok(b),
552 Err(e) => {
553 if let Some(ecode) = e.raw_os_error() {
554 match ecode {
555 libc::ENOENT => Ok(false),
556 libc::EISDIR => {
557 let iter = d.list_dir(cp)?;
558 let subd = d.sub_dir(cp)?;
559 remove_children(&subd, iter)?;
560 d.remove_dir(cp)?;
561 Ok(true)
562 }
563 _ => Err(e),
564 }
565 } else {
566 unreachable!("Unexpected non-OS error from openat::sub_dir: {}", e)
567 }
568 }
569 }
570}
571
572pub struct FileWriter<'a> {
579 pub writer: std::io::BufWriter<std::fs::File>,
581 pub tmp_prefix: String,
583 pub tmp_suffix: String,
585
586 dir: &'a openat::Dir,
588 tempname: Option<String>,
590}
591
592impl<'a> FileWriter<'a> {
593 fn new(dir: &'a openat::Dir, f: std::fs::File, tempname: Option<String>) -> Self {
594 Self {
595 writer: std::io::BufWriter::new(f),
596 tempname,
597 tmp_prefix: ".tmp.".to_string(),
598 tmp_suffix: ".tmp".to_string(),
599 dir,
600 }
601 }
602
603 fn linkat(
604 dir: &openat::Dir,
605 fd: fs::File,
606 rng: &mut rand::rngs::ThreadRng,
607 prefix: &str,
608 suffix: &str,
609 ) -> io::Result<String> {
610 for _ in 0..TEMPFILE_ATTEMPTS {
613 let tmpname = random_name(rng, prefix, suffix);
614 match dir.link_file_at(&fd, tmpname.as_str()) {
615 Ok(()) => {
616 return Ok(tmpname);
617 }
618 Err(e) => {
619 if e.kind() == io::ErrorKind::AlreadyExists {
620 continue;
621 } else {
622 return Err(e);
623 }
624 }
625 }
626 }
627 Err(io::Error::new(
628 io::ErrorKind::AlreadyExists,
629 format!(
630 "Exhausted {} attempts to create temporary file",
631 TEMPFILE_ATTEMPTS
632 ),
633 ))
634 }
635
636 pub fn complete_with<P: AsRef<Path>, F>(self, dest: P, f: F) -> io::Result<()>
643 where
644 F: Fn(&fs::File) -> io::Result<()>,
645 {
646 let dest = dest.as_ref();
647 let dir = self.dir;
648 let prefix = self.tmp_prefix;
649 let suffix = self.tmp_suffix;
650 let fd = self.writer.into_inner()?;
651 f(&fd)?;
652 let mut rng = rand::thread_rng();
653 let tmpname = if let Some(t) = self.tempname {
656 t
657 } else {
658 Self::linkat(&dir, fd, &mut rng, prefix.as_str(), suffix.as_str())?
659 };
660 let tmpname = tmpname.as_str();
661 match self.dir.local_rename(tmpname, dest) {
663 Ok(()) => Ok(()),
664 Err(e) => {
665 let _ = self.dir.remove_file(tmpname);
667 Err(e)
668 }
669 }
670 }
671
672 pub fn complete<P: AsRef<Path>>(self, dest: P) -> io::Result<()> {
675 self.complete_with(dest, |_f| Ok(()))
676 }
677}
678
679pub(crate) fn fallback_file_copy(src: &File, dest: &File) -> io::Result<u64> {
683 let mut off: u64 = 0;
684 let mut buf = [0u8; 8192];
685 loop {
686 let n = src.read_at(&mut buf, off)?;
687 if n == 0 {
688 return Ok(off);
689 }
690 dest.write_all_at(&buf[0..n], off)?;
691 off += n as u64;
692 }
693}
694
695pub trait FileExt {
697 #[deprecated = "Use std::io::copy instead"]
701 fn copy_to(&self, to: &File) -> io::Result<u64>;
702
703 fn update_timestamps(&self) -> io::Result<()>;
705
706 fn pread_exact(&self, buf: &mut [u8], position: usize) -> io::Result<()>;
709}
710
711impl FileExt for File {
712 #[cfg(not(any(target_os = "linux", target_os = "android")))]
713 fn copy_to(&self, to: &File) -> io::Result<u64> {
714 fallback_file_copy(self, to)
715 }
716
717 #[cfg(any(target_os = "linux", target_os = "android"))]
719 fn copy_to(&self, to: &File) -> io::Result<u64> {
720 use nix::errno::Errno;
721 use nix::fcntl::copy_file_range;
722 use std::sync::atomic::{AtomicBool, Ordering};
723
724 static HAS_COPY_FILE_RANGE: AtomicBool = AtomicBool::new(true);
727
728 let len = self.metadata()?.len();
729
730 let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed);
731 let mut written = 0u64;
732 while written < len {
733 let copy_result = if has_copy_file_range {
734 let bytes_to_copy = std::cmp::min(len - written, usize::MAX as u64) as usize;
735 let copy_result =
738 copy_file_range(self.as_raw_fd(), None, to.as_raw_fd(), None, bytes_to_copy);
739 if let Err(ref copy_err) = copy_result {
740 match copy_err {
741 Errno::ENOSYS | Errno::EPERM => {
742 HAS_COPY_FILE_RANGE.store(false, Ordering::Relaxed);
743 }
744 _ => {}
745 }
746 }
747 copy_result
748 } else {
749 Err(Errno::ENOSYS)
750 };
751 match copy_result {
752 Ok(ret) => written += ret as u64,
753 Err(err) => {
754 match err {
755 os_err
756 if os_err == Errno::ENOSYS
757 || os_err == Errno::EXDEV
758 || os_err == Errno::EINVAL
759 || os_err == Errno::EPERM =>
760 {
761 assert_eq!(written, 0);
767 return fallback_file_copy(self, to);
768 }
769 os_err => return Err(map_nix_error(os_err)),
770 }
771 }
772 }
773 }
774 Ok(written)
775 }
776
777 fn update_timestamps(&self) -> io::Result<()> {
778 use nix::sys::{stat::futimens, time::TimeSpec};
779
780 let now = TimeSpec::from(libc::timespec {
781 tv_nsec: libc::UTIME_NOW,
782 tv_sec: 0,
783 });
784 retry_eintr!(futimens(self.as_raw_fd(), &now, &now,).map_err(map_nix_error))
785 }
786
787 fn pread_exact(&self, buf: &mut [u8], start_pos: usize) -> io::Result<()> {
788 use nix::sys::uio::pread;
789
790 if buf.is_empty() {
791 return Err(io::Error::new(
792 io::ErrorKind::InvalidInput,
793 "zero-sized buffer in input",
794 ));
795 }
796
797 let mut total_bytes_read = 0;
798 while total_bytes_read < buf.len() {
799 let remaining_buf = &mut buf[total_bytes_read..];
800 let cur_offset = start_pos.saturating_add(total_bytes_read);
801 let bytes_read =
802 retry_eintr!(
803 pread(self.as_raw_fd(), remaining_buf, cur_offset as libc::off_t)
804 .map_err(map_nix_error)
805 )?;
806 total_bytes_read += bytes_read;
807 if bytes_read == 0 {
808 break;
809 }
810 }
811
812 if total_bytes_read < buf.len() {
813 return Err(io::Error::new(
814 io::ErrorKind::UnexpectedEof,
815 format!("pread reached EOF after {} bytes", total_bytes_read),
816 ));
817 }
818
819 Ok(())
820 }
821}
822
823fn to_cstr<P: openat::AsPath>(path: P) -> io::Result<P::Buffer> {
824 path.to_path()
825 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "nul byte in file name"))
826}
827
828#[cfg(test)]
829mod tests {
830 use super::*;
831 use std::path::{Path, PathBuf};
832 use std::time::Duration;
833 use std::{error, result, thread};
834 use tempfile;
835
836 type Result<T> = result::Result<T, Box<dyn error::Error>>;
837
838 #[test]
839 fn open_file_optional() -> Result<()> {
840 let td = tempfile::tempdir()?;
841 let d = openat::Dir::open(td.path())?;
842 assert!(d.open_file_optional("foo")?.is_none());
843 d.write_file("foo", 0o644)?.sync_all()?;
844 assert!(d.open_file_optional("foo")?.is_some());
845 Ok(())
846 }
847
848 #[test]
849 fn read_to_string() -> Result<()> {
850 let td = tempfile::tempdir()?;
851 let d = openat::Dir::open(td.path())?;
852 assert!(d.read_to_string("foo").is_err());
853 d.write_file_contents("foo", 0o644, "bar")?;
854 assert_eq!(d.read_to_string("foo")?, "bar");
855 Ok(())
856 }
857
858 #[test]
859 fn read_to_string_optional() -> Result<()> {
860 let td = tempfile::tempdir()?;
861 let d = openat::Dir::open(td.path())?;
862 assert!(d.read_to_string_optional("foo")?.is_none());
863 d.write_file_contents("foo", 0o644, "bar")?;
864 assert!(d.read_to_string_optional("foo")?.is_some());
865 assert_eq!(d.read_to_string_optional("foo")?.unwrap(), "bar");
866 Ok(())
867 }
868
869 #[test]
870 fn remove_file_optional() -> Result<()> {
871 let td = tempfile::tempdir()?;
872 let d = openat::Dir::open(td.path())?;
873 d.write_file("foo", 0o644)?.sync_all()?;
874 assert!(d.open_file_optional("foo")?.is_some());
875 let removed = d.remove_file_optional("foo")?;
876 assert!(removed);
877 assert!(d.open_file_optional("foo")?.is_none());
878 Ok(())
879 }
880
881 #[test]
882 fn metadata_optional() -> Result<()> {
883 let td = tempfile::tempdir()?;
884 let d = openat::Dir::open(td.path())?;
885 assert!(d.metadata_optional("foo")?.is_none());
886 d.write_file("foo", 0o644)?.sync_all()?;
887 assert!(d.metadata_optional("foo")?.is_some());
888 Ok(())
889 }
890
891 #[test]
892 fn get_file_type() -> Result<()> {
893 let td = tempfile::tempdir()?;
894 let d = openat::Dir::open(td.path())?;
895 d.write_file("foo", 0o644)?.sync_all()?;
896 for x in d.list_dir(".")? {
897 let x = x?;
898 assert_eq!("foo", x.file_name());
899 let t = d.get_file_type(&x)?;
900 assert_eq!(openat::SimpleType::File, t);
901 }
902 Ok(())
903 }
904
905 #[test]
906 fn ensure_dir_all() -> Result<()> {
907 let td = tempfile::tempdir()?;
908 let d = openat::Dir::open(td.path())?;
909 let mode = 0o755;
910 let p = Path::new("foo/bar/baz");
911 d.ensure_dir_all(p, mode)?;
912 assert_eq!(d.metadata(p)?.stat().st_mode & !libc::S_IFMT, mode);
913 d.ensure_dir_all(p, mode)?;
914 d.ensure_dir_all("foo/bar", mode)?;
915 d.ensure_dir_all("foo", mode)?;
916 d.ensure_dir_all("bar", 0o700)?;
917 assert_eq!(d.metadata("bar")?.stat().st_mode & !libc::S_IFMT, 0o700);
918 Ok(())
919 }
920
921 #[test]
922 fn test_local_rename() {
923 let td = tempfile::tempdir().unwrap();
924 let d = openat::Dir::open(td.path()).unwrap();
925
926 {
927 d.ensure_dir_all("src/foo", 0o755).unwrap();
928 let renamed = d.local_rename_optional("src", "dst").unwrap();
929 assert_eq!(renamed, true);
930 assert_eq!(d.exists("src").unwrap(), false);
931 assert_eq!(d.exists("dst/foo").unwrap(), true);
932 let noent = d.local_rename_optional("src", "dst").unwrap();
933 assert_eq!(noent, false);
934 assert_eq!(d.remove_all("dst").unwrap(), true);
935 }
936 {
937 let noent = d.local_rename_optional("missing", "dst").unwrap();
938 assert_eq!(noent, false);
939 }
940 {
941 d.ensure_dir_all("src/foo", 0o755).unwrap();
942 let renamed = d.local_rename_optional("src", "dst").unwrap();
943 assert_eq!(renamed, true);
944 assert_eq!(d.exists("dst/foo").unwrap(), true);
945 d.ensure_dir_all("src", 0o755).unwrap();
946 let _ = d.local_rename_optional("src", "dst").unwrap_err();
947 assert_eq!(d.exists("dst/foo").unwrap(), true);
948 assert_eq!(d.remove_all("dst").unwrap(), true);
949 }
950 }
951
952 #[test]
953 fn test_syncfs() {
954 let td = tempfile::tempdir().unwrap();
955 let d = openat::Dir::open(td.path()).unwrap();
956 d.ensure_dir_all("foo/bar", 0o755).unwrap();
957 d.syncfs().unwrap();
958 assert_eq!(d.exists("foo/bar").unwrap(), true);
959 }
960
961 #[test]
962 fn test_update_timestamps() {
963 const TICKING_PAUSE: Duration = Duration::from_millis(100);
966
967 let td = tempfile::tempdir().unwrap();
968 let d = openat::Dir::open(td.path()).unwrap();
969
970 {
971 d.ensure_dir("foo", 0o755).unwrap();
972 let before = d.metadata("foo").unwrap();
973 thread::sleep(TICKING_PAUSE);
974 d.update_timestamps("foo").unwrap();
975 let after = d.metadata("foo").unwrap();
976 if before.stat().st_mtime == after.stat().st_mtime {
977 assert_ne!(before.stat().st_mtime_nsec, after.stat().st_mtime_nsec);
978 }
979 }
980 {
981 use nix::sys::stat::fstat;
982
983 let bar = d.update_file("bar", 0o644).unwrap();
984 let before = fstat(bar.as_raw_fd()).unwrap();
985 thread::sleep(TICKING_PAUSE);
986 bar.update_timestamps().unwrap();
987 let after = fstat(bar.as_raw_fd()).unwrap();
988 if before.st_mtime == after.st_mtime {
989 assert_ne!(before.st_mtime_nsec, after.st_mtime_nsec);
990 }
991 }
992 }
993
994 #[test]
995 fn test_fchmodat() {
996 let td = tempfile::tempdir().unwrap();
997 let d = openat::Dir::open(td.path()).unwrap();
998 d.ensure_dir("foo", 0o777).unwrap();
999 d.set_mode("foo", 0o750).unwrap();
1000 assert_eq!(
1001 d.metadata("foo").unwrap().stat().st_mode & !libc::S_IFMT,
1002 0o750
1003 );
1004 d.set_mode("foo", 0o700).unwrap();
1005 assert_eq!(
1006 d.metadata("foo").unwrap().stat().st_mode & !libc::S_IFMT,
1007 0o700
1008 );
1009
1010 d.symlink("bar", "foo").unwrap();
1011 d.set_mode("bar", 0o000).unwrap();
1012 assert_ne!(
1013 d.metadata("bar").unwrap().stat().st_mode & !libc::S_IFMT,
1014 0o000
1015 );
1016 assert_ne!(
1017 d.metadata("foo").unwrap().stat().st_mode & !libc::S_IFMT,
1018 0o000
1019 );
1020 }
1021
1022 fn find_test_file(tempdir: &Path) -> Result<PathBuf> {
1023 for p in ["/proc/self/exe", "/usr/bin/bash"].iter() {
1024 let p = Path::new(p);
1025 if p.exists() {
1026 return Ok(p.into());
1027 }
1028 }
1029 let fallback = tempdir.join("testfile-fallback");
1030 std::fs::write(&fallback, "some test data")?;
1031 Ok(fallback)
1032 }
1033
1034 #[test]
1035 fn copy_fallback() -> Result<()> {
1036 use std::io::Read;
1037 let td = tempfile::tempdir()?;
1038 let src_p = find_test_file(td.path())?;
1039 let dest_p = td.path().join("bash");
1040 {
1041 let src = File::open(&src_p)?;
1042 let dest = File::create(&dest_p)?;
1043 let _ = fallback_file_copy(&src, &dest)?;
1044 }
1045 let mut src = File::open(&src_p)?;
1046 let mut srcbuf = Vec::new();
1047 let _ = src.read_to_end(&mut srcbuf)?;
1048 let mut destbuf = Vec::new();
1049 let mut dest = File::open(&dest_p)?;
1050 let _ = dest.read_to_end(&mut destbuf)?;
1051 assert_eq!(srcbuf.len(), destbuf.len());
1052 assert_eq!(&srcbuf, &destbuf);
1053 Ok(())
1054 }
1055
1056 #[test]
1057 fn copy_file_at() {
1058 let src_td = tempfile::tempdir().unwrap();
1059 let src_dir = openat::Dir::open(src_td.path()).unwrap();
1060 src_dir
1061 .write_file_contents("foo", 0o644, "test content")
1062 .unwrap();
1063
1064 let dst_td = tempfile::tempdir().unwrap();
1065 let dst_dir = openat::Dir::open(dst_td.path()).unwrap();
1066
1067 assert_eq!(dst_dir.exists("bar").unwrap(), false);
1068 src_dir.copy_file_at("foo", &dst_dir, "bar").unwrap();
1069 assert_eq!(dst_dir.exists("bar").unwrap(), true);
1070
1071 let srcbuf = {
1072 let mut src = src_dir.open_file("foo").unwrap();
1073 let mut srcbuf = Vec::new();
1074 let _ = src.read_to_end(&mut srcbuf).unwrap();
1075 srcbuf
1076 };
1077 let destbuf = {
1078 let mut destbuf = Vec::new();
1079 let mut dest = dst_dir.open_file("bar").unwrap();
1080 let _ = dest.read_to_end(&mut destbuf).unwrap();
1081 destbuf
1082 };
1083 assert_eq!(&srcbuf, b"test content");
1084 assert_eq!(&srcbuf, &destbuf);
1085 }
1086
1087 #[test]
1088 fn test_pread_exact() {
1089 let td = tempfile::tempdir().unwrap();
1090 let d = openat::Dir::open(td.path()).unwrap();
1091 static TESTINPUT: &str = "test1 test2 test3";
1092 d.write_file_contents("foo", 0o700, TESTINPUT).unwrap();
1093 let mut testfile = d.open_file("foo").unwrap();
1094 {
1095 let mut buf = [0; 0];
1096 let _ = testfile.pread_exact(&mut buf, 0).unwrap_err();
1097 }
1098 {
1099 let mut buf = [0; 18];
1100 let _ = testfile.pread_exact(&mut buf, 0).unwrap_err();
1101 }
1102 {
1103 let mut buf = [0; 1];
1104 let _ = testfile.pread_exact(&mut buf, 2000).unwrap_err();
1105 }
1106 {
1107 let mut buf1 = [0; 5];
1108 let mut buf2 = [0; 5];
1109 let _ = testfile.pread_exact(&mut buf1, 6).unwrap();
1110 let _ = testfile.pread_exact(&mut buf2, 6).unwrap();
1111 assert_eq!(buf1, "test2".as_bytes());
1112 assert_eq!(buf1, buf2);
1113
1114 let mut str_buf = String::new();
1115 let _ = testfile.read_to_string(&mut str_buf).unwrap();
1116 assert_eq!(str_buf, TESTINPUT);
1117 }
1118 }
1119}