coreutils/src/touch/touch.rs

212 lines
7.6 KiB
Rust
Raw Normal View History

2014-07-06 08:13:36 +00:00
#![crate_name = "touch"]
/*
* This file is part of the uutils coreutils package.
*
* (c) Nick Platt <platt.nicholas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
extern crate getopts;
extern crate libc;
extern crate time;
extern crate filetime;
#[macro_use]
extern crate uucore;
use filetime::*;
use std::fs::{self, File};
2015-04-30 05:46:10 +00:00
use std::io::{Error, Write};
use std::path::Path;
use uucore::fs::UUPathExt;
2015-07-31 17:59:05 +00:00
static NAME: &'static str = "touch";
static VERSION: &'static str = "1.0.0";
// 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();
FileTime::from_seconds_since_1970(ts.sec as u64, ts.nsec as u32)
})
);
pub fn uumain(args: Vec<String>) -> i32 {
let mut opts = getopts::Options::new();
opts.optflag("a", "", "change only the access time");
opts.optflag("c", "no-create", "do not create any files");
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)");
opts.optflag("m", "", "change only the modification time");
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");
opts.optflag("h", "help", "display this help and exit");
opts.optflag("V", "version", "output version information and exit");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
2014-10-30 09:06:47 +00:00
Err(e) => panic!("Invalid options\n{}", e)
};
if matches.opt_present("version") {
2014-11-21 09:09:43 +00:00
println!("{} {}", NAME, VERSION);
return 0;
}
if matches.opt_present("help") || matches.free.is_empty() {
2014-11-21 09:09:43 +00:00
println!("{} {}", NAME, VERSION);
println!("");
2014-11-21 09:09:43 +00:00
println!("Usage: {} [OPTION]... FILE...", NAME);
println!("");
println!("{}", opts.usage("Update the access and modification times of \
each FILE to the current time."));
if matches.free.is_empty() {
return 1;
}
return 0;
}
2014-11-19 20:55:25 +00:00
if matches.opt_present("date") && matches.opts_present(&["reference".to_string(), "t".to_string()]) ||
matches.opt_present("reference") && matches.opts_present(&["date".to_string(), "t".to_string()]) ||
matches.opt_present("t") && matches.opts_present(&["date".to_string(), "reference".to_string()]) {
2014-10-30 09:06:47 +00:00
panic!("Invalid options: cannot specify reference time from more than one source");
}
let (mut atime, mut mtime) =
if matches.opt_present("reference") {
2015-04-30 05:46:10 +00:00
stat(&matches.opt_str("reference").unwrap()[..], !matches.opt_present("no-dereference"))
2014-11-19 20:55:25 +00:00
} else if matches.opts_present(&["date".to_string(), "t".to_string()]) {
let timestamp = if matches.opt_present("date") {
2015-04-30 05:46:10 +00:00
parse_date(matches.opt_str("date").unwrap().as_ref())
} else {
2015-04-30 05:46:10 +00:00
parse_timestamp(matches.opt_str("t").unwrap().as_ref())
};
(timestamp, timestamp)
} else {
let now = local_tm_to_filetime!(time::now());
(now, now)
};
for filename in matches.free.iter() {
2015-04-30 05:46:10 +00:00
let path = &filename[..];
2015-07-31 17:59:05 +00:00
if !Path::new(path).uu_exists() {
// no-dereference included here for compatibility
2014-11-19 20:55:25 +00:00
if matches.opts_present(&["no-create".to_string(), "no-dereference".to_string()]) {
continue;
}
2015-04-30 05:46:10 +00:00
match File::create(path) {
Err(e) => {
show_warning!("cannot touch '{}': {}", path, e);
continue;
},
_ => (),
};
// Minor optimization: if no reference time was specified, we're done.
2014-11-19 20:55:25 +00:00
if !matches.opts_present(&["date".to_string(), "reference".to_string(), "t".to_string()]) {
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.
2014-11-19 20:55:25 +00:00
if matches.opts_present(&["a".to_string(), "m".to_string(), "time".to_string()]) {
2015-04-30 05:46:10 +00:00
let st = stat(path, !matches.opt_present("no-dereference"));
let time = matches.opt_strs("time");
if !(matches.opt_present("a") ||
2014-05-28 06:33:39 +00:00
time.contains(&"access".to_string()) ||
time.contains(&"atime".to_string()) ||
time.contains(&"use".to_string())) {
2015-04-30 05:46:10 +00:00
atime = st.0;
}
if !(matches.opt_present("m") ||
2014-05-28 06:33:39 +00:00
time.contains(&"modify".to_string()) ||
time.contains(&"mtime".to_string())) {
2015-04-30 05:46:10 +00:00
mtime = st.1;
}
}
2015-04-30 05:46:10 +00:00
// this follows symlinks and thus does not work correctly for the -h flag
// need to use lutimes() c function on supported platforms
match filetime::set_file_times(path, atime, mtime) {
2015-04-30 05:46:10 +00:00
Err(e) => show_warning!("cannot touch '{}': {}", path, e),
_ => (),
};
}
0
}
fn stat(path: &str, follow: bool) -> (FileTime, FileTime) {
let metadata = if follow {
fs::symlink_metadata(path)
} else {
fs::metadata(path)
2015-04-30 05:46:10 +00:00
};
match metadata {
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())
}
}
fn parse_date(str: &str) -> FileTime {
// 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") {
Ok(tm) => local_tm_to_filetime!(to_local!(tm)),
2014-10-30 09:06:47 +00:00
Err(e) => panic!("Unable to parse date\n{}", e)
}
}
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() {
15 => ("%Y%m%d%H%M.%S", s.to_string()),
12 => ("%Y%m%d%H%M", s.to_string()),
13 => ("%y%m%d%H%M.%S", s.to_string()),
10 => ("%y%m%d%H%M", s.to_string()),
11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)),
8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)),
2014-10-30 09:06:47 +00:00
_ => panic!("Unknown timestamp format")
};
2015-08-12 00:50:32 +00:00
match time::strptime(&ts, format) {
Ok(tm) => local_tm_to_filetime!(to_local!(tm)),
2014-10-30 09:06:47 +00:00
Err(e) => panic!("Unable to parse timestamp\n{}", e)
}
}
#[allow(dead_code)]
fn main() {
std::process::exit(uumain(std::env::args().collect()));
}