1use std::path::{Path, PathBuf};
6use std::time::SystemTime;
7
8use crate::error::Error;
9
10#[derive(Debug, Clone, PartialEq)]
11pub struct Options {
12 pub update_access: bool,
14
15 pub update_modification: bool,
17
18 pub no_create: bool,
20
21 pub no_dereference: bool,
23
24 pub date: Option<SystemTime>,
26
27 pub reference_file: Option<PathBuf>,
29}
30
31impl Default for Options {
32 fn default() -> Self {
33 Self {
34 update_access: true,
35 update_modification: true,
36 no_create: false,
37 no_dereference: false,
38 date: None,
39 reference_file: None,
40 }
41 }
42}
43
44pub fn touch<P: AsRef<Path>>(file: P, options: &Options) -> Result<(), Error> {
46 let mut new_times = [nc::timespec_t::default(), nc::timespec_t::default()];
47 if let Some(ref ref_file) = options.reference_file {
48 let fd = unsafe { nc::openat(nc::AT_FDCWD, ref_file, nc::O_RDONLY, 0)? };
49 let mut statbuf = nc::stat_t::default();
50 unsafe { nc::fstat(fd, &mut statbuf)? };
51 unsafe { nc::close(fd)? };
52 new_times[0].tv_sec = statbuf.st_atime as isize;
53 new_times[0].tv_nsec = statbuf.st_atime_nsec as isize;
54 new_times[1].tv_sec = statbuf.st_mtime as isize;
55 new_times[1].tv_nsec = statbuf.st_mtime_nsec as isize;
56 } else {
57 let new_time = if let Some(date) = options.date {
58 date
59 } else {
60 SystemTime::now()
61 };
62 let duration = new_time.duration_since(SystemTime::UNIX_EPOCH).unwrap();
63 new_times[0].tv_sec = duration.as_secs() as isize;
64 new_times[0].tv_nsec = (duration.as_nanos() % 1000) as isize;
65 new_times[1] = new_times[0];
66 }
67
68 let access = unsafe { nc::faccessat(nc::AT_FDCWD, file.as_ref(), nc::R_OK | nc::W_OK) };
69 if access.is_err() && options.no_create {
70 return access.map_err(Into::into);
71 }
72
73 let fd = unsafe {
74 nc::openat(
75 nc::AT_FDCWD,
76 file.as_ref(),
77 nc::O_WRONLY | nc::O_CREAT,
78 0o644,
79 )?
80 };
81
82 let mut statbuf = nc::stat_t::default();
83 unsafe { nc::fstat(fd, &mut statbuf)? };
84 unsafe { nc::close(fd)? };
85
86 if !options.update_access {
87 new_times[0].tv_sec = statbuf.st_atime as isize;
88 new_times[0].tv_nsec = statbuf.st_atime_nsec as isize;
89 }
90 if !options.update_modification {
91 new_times[1].tv_sec = statbuf.st_mtime as isize;
92 new_times[1].tv_nsec = statbuf.st_mtime_nsec as isize;
93 }
94
95 let flags = if options.no_dereference {
96 nc::AT_SYMLINK_NOFOLLOW
97 } else {
98 0
99 };
100 unsafe { nc::utimensat(nc::AT_FDCWD, file.as_ref(), &new_times, flags).map_err(Into::into) }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_touch() {
109 let file = "/tmp/touch.shell-rs";
110 assert!(touch(file, &Options::default()).is_ok());
111 assert!(touch(
112 file,
113 &Options {
114 no_create: true,
115 ..Options::default()
116 }
117 )
118 .is_ok());
119
120 assert!(touch(
121 file,
122 &Options {
123 reference_file: Some(PathBuf::from("/etc/passwd")),
124 ..Options::default()
125 }
126 )
127 .is_ok());
128 }
129}