2014-07-06 08:13:36 +00:00
|
|
|
#![crate_name = "touch"]
|
2015-04-30 05:46:10 +00:00
|
|
|
#![feature(rustc_private, path_ext, fs_time)]
|
2014-05-24 04:47:42 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2015-04-30 05:46:10 +00:00
|
|
|
extern crate libc;
|
2014-05-24 04:47:42 +00:00
|
|
|
extern crate getopts;
|
|
|
|
extern crate time;
|
|
|
|
|
2015-04-30 05:46:10 +00:00
|
|
|
use libc::types::os::arch::c95::c_char;
|
|
|
|
use libc::types::os::arch::posix01::stat as stat_t;
|
|
|
|
use libc::funcs::posix88::stat_::stat as c_stat;
|
|
|
|
use libc::funcs::posix01::stat_::lstat as c_lstat;
|
|
|
|
|
|
|
|
use std::fs::{set_file_times, File, PathExt};
|
|
|
|
use std::io::{Error, Write};
|
|
|
|
use std::mem::uninitialized;
|
|
|
|
use std::path::Path;
|
2014-05-24 04:47:42 +00:00
|
|
|
|
|
|
|
#[path = "../common/util.rs"]
|
2015-01-08 12:54:22 +00:00
|
|
|
#[macro_use]
|
2014-05-24 04:47:42 +00:00
|
|
|
mod util;
|
|
|
|
|
|
|
|
static NAME: &'static str = "touch";
|
|
|
|
static VERSION: &'static str = "1.0.0";
|
|
|
|
|
2015-02-06 13:48:07 +00:00
|
|
|
pub fn uumain(args: Vec<String>) -> i32 {
|
2014-05-30 08:35:54 +00:00
|
|
|
let opts = [
|
2014-05-24 04:47:42 +00:00
|
|
|
getopts::optflag("a", "", "change only the access time"),
|
|
|
|
getopts::optflag("c", "no-create", "do not create any files"),
|
|
|
|
getopts::optopt( "d", "date", "parse argument and use it instead of current time", "STRING"),
|
|
|
|
getopts::optflag("h", "no-dereference", "affect each symbolic link instead of any referenced file \
|
|
|
|
(only for systems that can change the timestamps of a symlink)"),
|
|
|
|
getopts::optflag("m", "", "change only the modification time"),
|
|
|
|
getopts::optopt( "r", "reference", "use this file's times instead of the current time", "FILE"),
|
|
|
|
getopts::optopt( "t", "", "use [[CC]YY]MMDDhhmm[.ss] instead of the current time", "STAMP"),
|
|
|
|
getopts::optopt( "", "time", "change only the specified time: \"access\", \"atime\", or \
|
|
|
|
\"use\" are equivalent to -a; \"modify\" or \"mtime\" are \
|
|
|
|
equivalent to -m", "WORD"),
|
|
|
|
getopts::optflag("h", "help", "display this help and exit"),
|
|
|
|
getopts::optflag("V", "version", "output version information and exit"),
|
|
|
|
];
|
|
|
|
|
2015-04-30 05:46:10 +00:00
|
|
|
let matches = match getopts::getopts(&args[1..], &opts) {
|
2014-05-24 04:47:42 +00:00
|
|
|
Ok(m) => m,
|
2014-10-30 09:06:47 +00:00
|
|
|
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!("");
|
2014-11-21 09:09:43 +00:00
|
|
|
println!("{}", getopts::usage("Update the access and modification times of \
|
2014-11-19 20:55:25 +00:00
|
|
|
each FILE to the current time.", &opts));
|
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
|
|
|
}
|
|
|
|
|
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");
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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()]) {
|
2014-05-24 04:47:42 +00:00
|
|
|
let timestamp = if matches.opt_present("date") {
|
2015-04-30 05:46:10 +00:00
|
|
|
parse_date(matches.opt_str("date").unwrap().as_ref())
|
2014-05-24 04:47:42 +00:00
|
|
|
} else {
|
2015-04-30 05:46:10 +00:00
|
|
|
parse_timestamp(matches.opt_str("t").unwrap().as_ref())
|
2014-05-24 04:47:42 +00:00
|
|
|
};
|
|
|
|
(timestamp, timestamp)
|
|
|
|
} else {
|
|
|
|
// FIXME: Should use Timespec. https://github.com/mozilla/rust/issues/10301
|
|
|
|
let now = (time::get_time().sec * 1000) as u64;
|
|
|
|
(now, now)
|
|
|
|
};
|
|
|
|
|
|
|
|
for filename in matches.free.iter() {
|
2015-04-30 05:46:10 +00:00
|
|
|
let path = &filename[..];
|
2014-05-24 04:47:42 +00:00
|
|
|
|
2015-04-30 05:46:10 +00:00
|
|
|
if ! Path::new(path).exists() {
|
2014-05-24 04:47:42 +00:00
|
|
|
// 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()]) {
|
2014-05-24 04:47:42 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-04-30 05:46:10 +00:00
|
|
|
match File::create(path) {
|
|
|
|
Err(e) => {
|
|
|
|
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.
|
2014-11-19 20:55:25 +00:00
|
|
|
if !matches.opts_present(&["date".to_string(), "reference".to_string(), "t".to_string()]) {
|
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.
|
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"));
|
2014-05-24 04:47:42 +00:00
|
|
|
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;
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 set_file_times(path, atime, mtime) {
|
|
|
|
Err(e) => 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-04-30 05:46:10 +00:00
|
|
|
fn stat(path: &str, follow: bool) -> (u64, u64) {
|
|
|
|
let stat_fn = if follow {
|
|
|
|
c_stat
|
2014-05-24 04:47:42 +00:00
|
|
|
} else {
|
2015-04-30 05:46:10 +00:00
|
|
|
c_lstat
|
|
|
|
};
|
|
|
|
let mut st: stat_t = unsafe { uninitialized() };
|
|
|
|
let result = unsafe { stat_fn(path.as_ptr() as *const c_char, &mut st as *mut stat_t) };
|
|
|
|
|
|
|
|
if result < 0 {
|
|
|
|
crash!(1, "failed to get attributes of '{}': {}", path, Error::last_os_error());
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
2015-04-30 05:46:10 +00:00
|
|
|
|
|
|
|
// set_file_times expects milliseconds
|
|
|
|
let atime = if st.st_atime_nsec == 0 {
|
|
|
|
st.st_atime * 1000
|
|
|
|
} else {
|
|
|
|
st.st_atime_nsec / 1000
|
|
|
|
} as u64;
|
|
|
|
|
|
|
|
// set_file_times expects milliseconds
|
|
|
|
let mtime = if st.st_mtime_nsec == 0 {
|
|
|
|
st.st_mtime * 1000
|
|
|
|
} else {
|
|
|
|
st.st_mtime_nsec / 1000
|
|
|
|
} as u64;
|
|
|
|
|
|
|
|
(atime, mtime)
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_date(str: &str) -> u64 {
|
|
|
|
// 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) => (tm.to_timespec().sec * 1000) as u64,
|
2014-10-30 09:06:47 +00:00
|
|
|
Err(e) => panic!("Unable to parse date\n{}", e)
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_timestamp(str: &str) -> u64 {
|
2014-12-28 11:03:54 +00:00
|
|
|
let format = match str.chars().count() {
|
2014-05-24 04:47:42 +00:00
|
|
|
15 => "%Y%m%d%H%M.%S",
|
|
|
|
12 => "%Y%m%d%H%M",
|
|
|
|
13 => "%y%m%d%H%M.%S",
|
|
|
|
10 => "%y%m%d%H%M",
|
|
|
|
11 => "%m%d%H%M.%S",
|
|
|
|
8 => "%m%d%H%M",
|
2014-10-30 09:06:47 +00:00
|
|
|
_ => panic!("Unknown timestamp format")
|
2014-05-24 04:47:42 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
match time::strptime(str, format) {
|
|
|
|
Ok(tm) => (tm.to_timespec().sec * 1000) as u64,
|
2014-10-30 09:06:47 +00:00
|
|
|
Err(e) => panic!("Unable to parse timestamp\n{}", e)
|
2014-05-24 04:47:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|