2015-12-08 02:42:08 +00:00
|
|
|
#![crate_name = "uu_touch"]
|
2014-05-24 04:47:42 +00:00
|
|
|
|
2016-08-27 22:48:59 +00:00
|
|
|
// This file is part of the uutils coreutils package.
|
|
|
|
//
|
|
|
|
// (c) Nick Platt <platt.nicholas@gmail.com>
|
|
|
|
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
|
|
|
|
//
|
|
|
|
// For the full copyright and license information, please view the LICENSE file
|
|
|
|
// that was distributed with this source code.
|
|
|
|
//
|
2014-05-24 04:47:42 +00:00
|
|
|
|
2018-03-12 08:20:58 +00:00
|
|
|
pub extern crate filetime;
|
2014-05-24 04:47:42 +00:00
|
|
|
extern crate getopts;
|
|
|
|
extern crate time;
|
|
|
|
|
2015-11-24 01:00:51 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate uucore;
|
|
|
|
|
2015-08-11 23:13:22 +00:00
|
|
|
use filetime::*;
|
|
|
|
use std::fs::{self, File};
|
2018-11-25 08:18:52 +00:00
|
|
|
use std::io::Error;
|
2015-04-30 05:46:10 +00:00
|
|
|
use std::path::Path;
|
2015-07-31 17:59:05 +00:00
|
|
|
|
2018-09-04 12:33:36 +00:00
|
|
|
static NAME: &str = "touch";
|
|
|
|
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
2014-05-24 04:47:42 +00:00
|
|
|
|
2015-08-11 23:13:22 +00:00
|
|
|
// Since touch's date/timestamp parsing doesn't account for timezone, the
|
|
|
|
// returned value from time::strptime() is UTC. We get system's timezone to
|
|
|
|
// localize the time.
|
|
|
|
macro_rules! to_local(
|
|
|
|
($exp:expr) => ({
|
|
|
|
let mut tm = $exp;
|
|
|
|
tm.tm_utcoff = time::now().tm_utcoff;
|
|
|
|
tm
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
macro_rules! local_tm_to_filetime(
|
|
|
|
($exp:expr) => ({
|
|
|
|
let ts = $exp.to_timespec();
|
2018-06-13 14:05:56 +00:00
|
|
|
FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32)
|
2015-08-11 23:13:22 +00:00
|
|
|
})
|
|
|
|
);
|
|
|
|
|
2015-02-06 13:48:07 +00:00
|
|
|
pub fn uumain(args: Vec<String>) -> i32 {
|
2015-05-22 01:31:21 +00:00
|
|
|
let mut opts = getopts::Options::new();
|
|
|
|
|
2016-08-27 22:48:59 +00:00
|
|
|
opts.optflag("a", "", "change only the access time");
|
|
|
|
opts.optflag("c", "no-create", "do not create any files");
|
2018-03-12 08:20:58 +00:00
|
|
|
opts.optopt(
|
|
|
|
"d",
|
|
|
|
"date",
|
|
|
|
"parse argument and use it instead of current time",
|
|
|
|
"STRING",
|
|
|
|
);
|
|
|
|
opts.optflag(
|
|
|
|
"h",
|
|
|
|
"no-dereference",
|
|
|
|
"affect each symbolic link instead of any referenced file \
|
|
|
|
(only for systems that can change the timestamps of a symlink)",
|
|
|
|
);
|
2016-08-27 22:48:59 +00:00
|
|
|
opts.optflag("m", "", "change only the modification time");
|
2018-03-12 08:20:58 +00:00
|
|
|
opts.optopt(
|
|
|
|
"r",
|
|
|
|
"reference",
|
|
|
|
"use this file's times instead of the current time",
|
|
|
|
"FILE",
|
|
|
|
);
|
|
|
|
opts.optopt(
|
|
|
|
"t",
|
|
|
|
"",
|
|
|
|
"use [[CC]YY]MMDDhhmm[.ss] instead of the current time",
|
|
|
|
"STAMP",
|
|
|
|
);
|
|
|
|
opts.optopt(
|
|
|
|
"",
|
|
|
|
"time",
|
|
|
|
"change only the specified time: \"access\", \"atime\", or \
|
|
|
|
\"use\" are equivalent to -a; \"modify\" or \"mtime\" are \
|
|
|
|
equivalent to -m",
|
|
|
|
"WORD",
|
|
|
|
);
|
2016-08-27 22:48:59 +00:00
|
|
|
opts.optflag("h", "help", "display this help and exit");
|
|
|
|
opts.optflag("V", "version", "output version information and exit");
|
2014-05-24 04:47:42 +00:00
|
|
|
|
2015-05-22 01:31:21 +00:00
|
|
|
let matches = match opts.parse(&args[1..]) {
|
2016-08-27 22:48:59 +00:00
|
|
|
Ok(m) => m,
|
|
|
|
Err(e) => panic!("Invalid options\n{}", e),
|
2014-05-24 04:47:42 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if matches.opt_present("version") {
|
2014-11-21 09:09:43 +00:00
|
|
|
println!("{} {}", NAME, VERSION);
|
2014-06-08 07:56:37 +00:00
|
|
|
return 0;
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if matches.opt_present("help") || matches.free.is_empty() {
|
2014-11-21 09:09:43 +00:00
|
|
|
println!("{} {}", NAME, VERSION);
|
2014-05-24 04:47:42 +00:00
|
|
|
println!("");
|
2014-11-21 09:09:43 +00:00
|
|
|
println!("Usage: {} [OPTION]... FILE...", NAME);
|
2014-05-24 04:47:42 +00:00
|
|
|
println!("");
|
2018-03-12 08:20:58 +00:00
|
|
|
println!(
|
|
|
|
"{}",
|
|
|
|
opts.usage(
|
|
|
|
"Update the access and modification times of \
|
|
|
|
each FILE to the current time."
|
|
|
|
)
|
|
|
|
);
|
2014-06-22 14:44:31 +00:00
|
|
|
if matches.free.is_empty() {
|
|
|
|
return 1;
|
|
|
|
}
|
2014-06-08 07:56:37 +00:00
|
|
|
return 0;
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
|
2018-03-12 08:20:58 +00:00
|
|
|
if matches.opt_present("date")
|
|
|
|
&& matches.opts_present(&["reference".to_owned(), "t".to_owned()])
|
|
|
|
|| matches.opt_present("reference")
|
|
|
|
&& matches.opts_present(&["date".to_owned(), "t".to_owned()])
|
|
|
|
|| matches.opt_present("t")
|
|
|
|
&& matches.opts_present(&["date".to_owned(), "reference".to_owned()])
|
|
|
|
{
|
2014-10-30 09:06:47 +00:00
|
|
|
panic!("Invalid options: cannot specify reference time from more than one source");
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
|
2016-08-27 22:48:59 +00:00
|
|
|
let (mut atime, mut mtime) = if matches.opt_present("reference") {
|
2018-03-12 08:20:58 +00:00
|
|
|
stat(
|
|
|
|
&matches.opt_str("reference").unwrap()[..],
|
|
|
|
!matches.opt_present("no-dereference"),
|
|
|
|
)
|
2016-08-27 22:48:59 +00:00
|
|
|
} else if matches.opts_present(&["date".to_owned(), "t".to_owned()]) {
|
|
|
|
let timestamp = if matches.opt_present("date") {
|
|
|
|
parse_date(matches.opt_str("date").unwrap().as_ref())
|
2014-05-24 04:47:42 +00:00
|
|
|
} else {
|
2016-08-27 22:48:59 +00:00
|
|
|
parse_timestamp(matches.opt_str("t").unwrap().as_ref())
|
2014-05-24 04:47:42 +00:00
|
|
|
};
|
2016-08-27 22:48:59 +00:00
|
|
|
(timestamp, timestamp)
|
|
|
|
} else {
|
|
|
|
let now = local_tm_to_filetime!(time::now());
|
|
|
|
(now, now)
|
|
|
|
};
|
2014-05-24 04:47:42 +00:00
|
|
|
|
2016-01-05 19:42:52 +00:00
|
|
|
for filename in &matches.free {
|
2015-04-30 05:46:10 +00:00
|
|
|
let path = &filename[..];
|
2014-05-24 04:47:42 +00:00
|
|
|
|
2015-12-10 19:20:01 +00:00
|
|
|
if !Path::new(path).exists() {
|
2014-05-24 04:47:42 +00:00
|
|
|
// no-dereference included here for compatibility
|
2016-01-05 19:42:52 +00:00
|
|
|
if matches.opts_present(&["no-create".to_owned(), "no-dereference".to_owned()]) {
|
2014-05-24 04:47:42 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-01-05 19:42:52 +00:00
|
|
|
if let Err(e) = File::create(path) {
|
|
|
|
show_warning!("cannot touch '{}': {}", path, e);
|
|
|
|
continue;
|
2014-05-24 04:47:42 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Minor optimization: if no reference time was specified, we're done.
|
2016-01-05 19:42:52 +00:00
|
|
|
if !matches.opts_present(&["date".to_owned(), "reference".to_owned(), "t".to_owned()]) {
|
2014-05-24 04:47:42 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If changing "only" atime or mtime, grab the existing value of the other.
|
|
|
|
// Note that "-a" and "-m" may be passed together; this is not an xor.
|
2016-01-05 19:42:52 +00:00
|
|
|
if matches.opts_present(&["a".to_owned(), "m".to_owned(), "time".to_owned()]) {
|
2015-04-30 05:46:10 +00:00
|
|
|
let st = stat(path, !matches.opt_present("no-dereference"));
|
2014-05-24 04:47:42 +00:00
|
|
|
let time = matches.opt_strs("time");
|
|
|
|
|
2018-03-12 08:20:58 +00:00
|
|
|
if !(matches.opt_present("a") || time.contains(&"access".to_owned())
|
|
|
|
|| time.contains(&"atime".to_owned())
|
|
|
|
|| time.contains(&"use".to_owned()))
|
|
|
|
{
|
2015-04-30 05:46:10 +00:00
|
|
|
atime = st.0;
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
|
2018-03-12 08:20:58 +00:00
|
|
|
if !(matches.opt_present("m") || time.contains(&"modify".to_owned())
|
|
|
|
|| time.contains(&"mtime".to_owned()))
|
|
|
|
{
|
2015-04-30 05:46:10 +00:00
|
|
|
mtime = st.1;
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-27 22:48:59 +00:00
|
|
|
if matches.opt_present("h") {
|
2018-11-25 08:18:52 +00:00
|
|
|
if let Err(e) = set_symlink_file_times(path, atime, mtime) {
|
2016-08-27 22:48:59 +00:00
|
|
|
show_warning!("cannot touch '{}': {}", path, e);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if let Err(e) = filetime::set_file_times(path, atime, mtime) {
|
|
|
|
show_warning!("cannot touch '{}': {}", path, e);
|
|
|
|
}
|
|
|
|
}
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
2014-06-08 07:56:37 +00:00
|
|
|
|
2014-06-12 04:41:53 +00:00
|
|
|
0
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
|
2015-08-11 23:13:22 +00:00
|
|
|
fn stat(path: &str, follow: bool) -> (FileTime, FileTime) {
|
|
|
|
let metadata = if follow {
|
|
|
|
fs::symlink_metadata(path)
|
2014-05-24 04:47:42 +00:00
|
|
|
} else {
|
2015-08-11 23:13:22 +00:00
|
|
|
fs::metadata(path)
|
2015-04-30 05:46:10 +00:00
|
|
|
};
|
|
|
|
|
2015-08-11 23:13:22 +00:00
|
|
|
match metadata {
|
2018-03-12 08:20:58 +00:00
|
|
|
Ok(m) => (
|
|
|
|
FileTime::from_last_access_time(&m),
|
|
|
|
FileTime::from_last_modification_time(&m),
|
|
|
|
),
|
|
|
|
Err(_) => crash!(
|
|
|
|
1,
|
|
|
|
"failed to get attributes of '{}': {}",
|
|
|
|
path,
|
|
|
|
Error::last_os_error()
|
|
|
|
),
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-11 23:13:22 +00:00
|
|
|
fn parse_date(str: &str) -> FileTime {
|
2014-05-24 04:47:42 +00:00
|
|
|
// This isn't actually compatible with GNU touch, but there doesn't seem to
|
|
|
|
// be any simple specification for what format this parameter allows and I'm
|
|
|
|
// not about to implement GNU parse_datetime.
|
|
|
|
// http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y
|
|
|
|
match time::strptime(str, "%c") {
|
2015-08-11 23:13:22 +00:00
|
|
|
Ok(tm) => local_tm_to_filetime!(to_local!(tm)),
|
2016-08-27 22:48:59 +00:00
|
|
|
Err(e) => panic!("Unable to parse date\n{}", e),
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-12 00:50:32 +00:00
|
|
|
fn parse_timestamp(s: &str) -> FileTime {
|
|
|
|
let now = time::now();
|
|
|
|
let (format, ts) = match s.chars().count() {
|
2016-01-05 19:42:52 +00:00
|
|
|
15 => ("%Y%m%d%H%M.%S", s.to_owned()),
|
|
|
|
12 => ("%Y%m%d%H%M", s.to_owned()),
|
|
|
|
13 => ("%y%m%d%H%M.%S", s.to_owned()),
|
|
|
|
10 => ("%y%m%d%H%M", s.to_owned()),
|
2015-08-12 00:50:32 +00:00
|
|
|
11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)),
|
2016-08-27 22:48:59 +00:00
|
|
|
8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)),
|
|
|
|
_ => panic!("Unknown timestamp format"),
|
2014-05-24 04:47:42 +00:00
|
|
|
};
|
|
|
|
|
2015-08-12 00:50:32 +00:00
|
|
|
match time::strptime(&ts, format) {
|
2015-08-11 23:13:22 +00:00
|
|
|
Ok(tm) => local_tm_to_filetime!(to_local!(tm)),
|
2016-08-27 22:48:59 +00:00
|
|
|
Err(e) => panic!("Unable to parse timestamp\n{}", e),
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
}
|