This commit is contained in:
Konstantin Stepanov 2016-04-13 16:16:14 +03:00
parent 42bb127d15
commit 431464e6f6
9 changed files with 295 additions and 247 deletions

11
rustfmt.toml Normal file
View File

@ -0,0 +1,11 @@
newline_style = "Unix"
max_width = 140
fn_call_width = 120
ideal_width = 120
tab_spaces = 4
fn_args_density = "Compressed"
fn_arg_indent = "Tabbed"
single_line_if_else = true
reorder_imports = true
chain_base_indent = "Tabbed"
chain_indent = "Tabbed"

View File

@ -21,11 +21,8 @@ fn main() {
.map(|sz| {
buf.iter()
.position(|&c| c == 0x20)
.and_then(|p| if p < sz {
unsafe { transmute::<_, &str>(&buf[..p]) }.parse::<f32>().ok()
} else {
None
}).unwrap()
.and_then(|p| if p < sz { unsafe { transmute::<_, &str>(&buf[..p]) }.parse::<f32>().ok() } else { None })
.unwrap()
})
.unwrap();

View File

@ -10,14 +10,13 @@ use cronparse::{CrontabFile, CrontabFileError};
use cronparse::crontab::UserCrontabEntry;
use tempfile::NamedTempFile;
use docopt::Docopt;
use libc::{uid_t, gid_t};
use users::User;
use std::env;
use std::fs;
use std::io::{stdin, stdout, stderr, Write, Read, self, copy};
use std::io::{self, Read, Write, copy, stderr, stdin, stdout};
use std::fs::File;
use std::os::unix::fs::PermissionsExt;
use std::path::{PathBuf, Path};
use std::path::{Path, PathBuf};
use std::process::{Command, exit};
use std::ffi::CString;
@ -31,7 +30,7 @@ fn change_owner<P: AsRef<Path>>(path: P, owner: libc::uid_t, group: libc::gid_t)
match unsafe { chown(CString::new(path.as_ref().to_str().unwrap().as_bytes()).unwrap().as_ptr(), owner, group) } {
0 => Ok(()),
-1 => Err(io::Error::last_os_error()),
_ => unreachable!()
_ => unreachable!(),
}
}
@ -78,15 +77,23 @@ struct Args {
flag_remove: bool,
flag_edit: bool,
flag_show: bool,
flag_ask: bool
flag_ask: bool,
}
fn get_editor() -> Option<String> {
env::var("EDITOR").ok()
env::var("EDITOR")
.ok()
.or_else(|| env::var("VISUAL").ok())
.or_else(|| ["/usr/bin/editor", "/usr/bin/vim", "/usr/bin/nano", "/usr/bin/mcedit"].iter()
.find(|editor| fs::metadata(editor).map(|meta| meta.is_file() && meta.permissions().mode() & 0o0111 != 0).unwrap_or(false))
.map(|&s| s.to_owned()))
.or_else(|| {
["/usr/bin/editor", "/usr/bin/vim", "/usr/bin/nano", "/usr/bin/mcedit"]
.iter()
.find(|editor| {
fs::metadata(editor)
.map(|meta| meta.is_file() && meta.permissions().mode() & 0o0111 != 0)
.unwrap_or(false)
})
.map(|&s| s.to_owned())
})
}
fn confirm(msg: &str) -> bool {
@ -99,7 +106,9 @@ fn confirm(msg: &str) -> bool {
match stdin.read(&mut buf) {
Ok(n) if n > 0 && (buf[0] == 121 || buf[0] == 89) => return true,
Ok(n) if n > 0 && (buf[0] == 110 || buf[0] == 78) => return false,
_ => { stdout.write_all("Please reply \"y\" or \"n\"\n".as_bytes()).unwrap(); },
_ => {
stdout.write_all("Please reply \"y\" or \"n\"\n".as_bytes()).unwrap();
}
}
}
}
@ -123,9 +132,10 @@ fn remove(cron_file: &Path, cron_user: &User, args: &Args) -> i32 {
if let Err(e) = fs::remove_file(cron_file) {
use std::io::ErrorKind::*;
match e.kind() {
NotFound => writeln!(stderr, "no crontab for {}", cron_user.name),
_ => writeln!(stderr, "failed to remove {}: {}", cron_file.display(), e)
}.unwrap();
NotFound => writeln!(stderr, "no crontab for {}", cron_user.name),
_ => writeln!(stderr, "failed to remove {}: {}", cron_file.display(), e),
}
.unwrap();
return 1;
}
}
@ -160,8 +170,8 @@ fn edit(cron_file: &Path, cron_user: &User, _args: &Args) -> i32 {
None => {
writeln!(stderr, "no editor found").unwrap();
return 1;
},
Some(editor) => editor
}
Some(editor) => editor,
};
let mut tmpfile = match NamedTempFile::new_in(USERS_CRONTAB_DIR) {
@ -169,7 +179,7 @@ fn edit(cron_file: &Path, cron_user: &User, _args: &Args) -> i32 {
Err(err) => {
writeln!(stderr, "unable to create a temporary file in {}: {}", USERS_CRONTAB_DIR, err).unwrap();
return 1;
},
}
};
if let Err(e) = File::open(cron_file).map(|mut file| copy(&mut file, &mut tmpfile)) {
@ -215,13 +225,17 @@ fn replace(cron_file: &Path, cron_user: &User, args: &Args) -> i32 {
Err(err) => {
writeln!(stderr, "unable to create a temporary file in {}: {}", USERS_CRONTAB_DIR, err).unwrap();
return 1;
},
}
};
match args.arg_file {
Some(ref name) if &**name == "-" => { let _ = copy(&mut stdin(), &mut tmpfile); },
Some(ref name) => { let _ = copy(&mut File::open(&**name).unwrap(), &mut tmpfile); },
None => unreachable!()
Some(ref name) if &**name == "-" => {
let _ = copy(&mut stdin(), &mut tmpfile);
}
Some(ref name) => {
let _ = copy(&mut File::open(&**name).unwrap(), &mut tmpfile);
}
None => unreachable!(),
}
tmpfile.flush().unwrap();
@ -244,21 +258,23 @@ fn replace(cron_file: &Path, cron_user: &User, args: &Args) -> i32 {
fn main() {
let mut stderr = stderr();
let args: Args = Docopt::new(USAGE)
.and_then(|d| d.decode())
.unwrap_or_else(|e| e.exit());
.and_then(|d| d.decode())
.unwrap_or_else(|e| e.exit());
let cron_user = match args.flag_user {
Some(_) if users::get_current_uid() != 0 => {
writeln!(stderr, "must be privileged to use -u").unwrap();
exit(1);
},
Some(ref user) => match users::get_user_by_name(&**user) {
Some(user) => user,
None => {
writeln!(stderr, "unknown user: {}", user).unwrap();
exit(1);
}
Some(ref user) => {
match users::get_user_by_name(&**user) {
Some(user) => user,
None => {
writeln!(stderr, "unknown user: {}", user).unwrap();
exit(1);
}
}
},
}
None => users::get_user_by_uid(users::get_current_uid()).unwrap(),
};
@ -266,12 +282,14 @@ fn main() {
Ok(ref meta) if !meta.is_dir() => {
writeln!(stderr, "{} is not a directory!", USERS_CRONTAB_DIR).unwrap();
exit(1);
},
Err(_) => if let Err(_) = fs::create_dir_all(USERS_CRONTAB_DIR) {
writeln!(stderr, "{} doesn't exist!", USERS_CRONTAB_DIR).unwrap();
exit(1);
},
_ => ()
}
Err(_) => {
if let Err(_) = fs::create_dir_all(USERS_CRONTAB_DIR) {
writeln!(stderr, "{} doesn't exist!", USERS_CRONTAB_DIR).unwrap();
exit(1);
}
}
_ => (),
}
let cron_file = PathBuf::from(USERS_CRONTAB_DIR).join(cron_user.name.clone());
@ -282,13 +300,13 @@ fn main() {
Args { flag_edit: true, arg_file: None, .. } => edit(&*cron_file, &cron_user, &args),
Args { flag_edit: true, .. } => replace(&*cron_file, &cron_user, &args),
Args { flag_remove: true, .. } => remove(&*cron_file, &cron_user, &args),
_ => unreachable!()
_ => unreachable!(),
})
}
fn check_crontab_syntax<P: AsRef<Path>>(path: P) -> Result<(), CrontabFileError> {
match try!(CrontabFile::<UserCrontabEntry>::new(path)).find(Result::is_err) {
Some(Err(err)) => Err(err),
_ => Ok(())
_ => Ok(()),
}
}

View File

@ -1,7 +1,7 @@
use std::env;
use std::process::{Command, Stdio};
use std::io::{Write, Result};
use std::io::{Result, Write};
macro_rules! try_log {
($exp:expr) => {
@ -19,10 +19,11 @@ fn get_systemd_unit_property(unit: &str, prop: &str) -> Result<String> {
.arg("--property")
.arg(prop)
.output()
.map(|out|
String::from_utf8_lossy(&out.stdout[prop.len() + 1..])
.trim_right_matches('\n')
.to_owned())
.map(|out| {
String::from_utf8_lossy(&out.stdout[prop.len() + 1..])
.trim_right_matches('\n')
.to_owned()
})
}
fn main() {
@ -56,9 +57,9 @@ fn main() {
}
let mut hostname = String::from_utf8_lossy(&try_log!(Command::new("uname")
.arg("-n")
.output())
.stdout[..])
.arg("-n")
.output())
.stdout[..])
.trim_right_matches('\n')
.to_owned();
@ -95,8 +96,7 @@ Auto-Submitted: auto-generated
.spawn());
if let Some(ref mut stdin) = mailer.stdin {
try_log!(stdin.write_all(head.as_bytes()).and_then(|_|
stdin.write_all(&*status.stdout)));
try_log!(stdin.write_all(head.as_bytes()).and_then(|_| stdin.write_all(&*status.stdout)));
}
mailer.wait().unwrap();

View File

@ -9,19 +9,17 @@ use std::collections::BTreeSet;
use time::{Duration, get_time};
use glob::glob;
static KNOWN_STAMPS: [&'static str; 6] = [
"/var/lib/systemd/timers/stamp-cron-daily.timer",
"/var/lib/systemd/timers/stamp-cron-weekly.timer",
"/var/lib/systemd/timers/stamp-cron-monthly.timer",
"/var/lib/systemd/timers/stamp-cron-quarterly.timer",
"/var/lib/systemd/timers/stamp-cron-semi-annually.timer",
"/var/lib/systemd/timers/stamp-cron-yearly.timer"
];
static KNOWN_STAMPS: [&'static str; 6] = ["/var/lib/systemd/timers/stamp-cron-daily.timer",
"/var/lib/systemd/timers/stamp-cron-weekly.timer",
"/var/lib/systemd/timers/stamp-cron-monthly.timer",
"/var/lib/systemd/timers/stamp-cron-quarterly.timer",
"/var/lib/systemd/timers/stamp-cron-semi-annually.timer",
"/var/lib/systemd/timers/stamp-cron-yearly.timer"];
static ACTUAL_STAMPS_GLOB: &'static str = "/var/lib/systemd/timers/stamp-cron-*.timer";
static TIMER_STAMPS_GLOB: &'static str = "/run/systemd/generator/cron-*.timer";
fn cleanup<P: AsRef<Path>, I: IntoIterator<Item=P>>(iter: I) {
fn cleanup<P: AsRef<Path>, I: IntoIterator<Item = P>>(iter: I) {
let ten_days_ago = get_time() - Duration::days(10);
for stamp in iter {
if let Ok(meta) = metadata(&stamp) {
@ -33,17 +31,21 @@ fn cleanup<P: AsRef<Path>, I: IntoIterator<Item=P>>(iter: I) {
}
fn main() {
let stale_stamps =
&(&glob(ACTUAL_STAMPS_GLOB).unwrap()
.flat_map(Result::into_iter)
.collect::<BTreeSet<_>>() -
&glob(TIMER_STAMPS_GLOB).unwrap()
.flat_map(Result::into_iter)
.map(|s| PathBuf::from(s.to_string_lossy().replace("/run/systemd/generator/cron-", "/var/lib/systemd/timers/stamp-cron-")))
.collect::<BTreeSet<_>>()) -
&KNOWN_STAMPS.iter()
.map(PathBuf::from)
.collect::<BTreeSet<_>>();
let stale_stamps = &(&glob(ACTUAL_STAMPS_GLOB)
.unwrap()
.flat_map(Result::into_iter)
.collect::<BTreeSet<_>>() -
&glob(TIMER_STAMPS_GLOB)
.unwrap()
.flat_map(Result::into_iter)
.map(|s| {
PathBuf::from(s.to_string_lossy()
.replace("/run/systemd/generator/cron-", "/var/lib/systemd/timers/stamp-cron-"))
})
.collect::<BTreeSet<_>>()) -
&KNOWN_STAMPS.iter()
.map(PathBuf::from)
.collect::<BTreeSet<_>>();
cleanup(&stale_stamps);
}

View File

@ -1,19 +1,19 @@
use std::io::{self, Write};
use std::fs::{File, create_dir_all, set_permissions, metadata};
use std::fs::{File, create_dir_all, metadata, set_permissions};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::{symlink, MetadataExt, PermissionsExt};
use std::os::unix::fs::{MetadataExt, PermissionsExt, symlink};
use std::path::Path;
use std::collections::{BTreeMap, BTreeSet};
use std::ffi::CStr;
use cronparse::Limited;
use cronparse::crontab::{CrontabEntry, SystemCrontabEntry, UserCrontabEntry};
use cronparse::schedule::{Schedule, Period, Calendar};
use cronparse::schedule::{Calendar, Period, Schedule};
use cronparse::interval::Interval;
use getpwent::{PwEntIter, User};
use super::{REBOOT_FILE, PACKAGE, LIB_DIR};
use super::{LIB_DIR, PACKAGE, REBOOT_FILE};
pub fn generate_systemd_units(entry: CrontabEntry, env: &BTreeMap<String, String>, path: &Path, dstdir: &Path) -> io::Result<()> {
use cronparse::crontab::CrontabEntry::*;
@ -22,118 +22,122 @@ pub fn generate_systemd_units(entry: CrontabEntry, env: &BTreeMap<String, String
let owner = try!(metadata(path)).uid();
let mut persistent = env.get("PERSISTENT").and_then(|v| match &**v {
"yes" | "true" | "1" => Some(true),
"auto" | "" => None,
_ => Some(false)
}).unwrap_or_else(|| match entry {
Anacron(_) | User(UserCrontabEntry { sched: Schedule::Period(_), .. }) | System(SystemCrontabEntry { sched: Schedule::Period(_), .. }) => true,
_ => false
});
let mut persistent = env.get("PERSISTENT")
.and_then(|v| {
match &**v {
"yes" | "true" | "1" => Some(true),
"auto" | "" => None,
_ => Some(false),
}
})
.unwrap_or_else(|| {
match entry {
Anacron(_) |
User(UserCrontabEntry { sched: Schedule::Period(_), .. }) |
System(SystemCrontabEntry { sched: Schedule::Period(_), .. }) => true,
_ => false,
}
});
let batch = env.get("BATCH").map(|v| match &**v {
"yes" | "true" | "1" => true,
_ => false
}).unwrap_or(false);
let batch = env.get("BATCH")
.map(|v| {
match &**v {
"yes" | "true" | "1" => true,
_ => false,
}
})
.unwrap_or(false);
let random_delay = env.get("RANDOM_DELAY").and_then(|v| v.parse::<u64>().ok()).unwrap_or(1);
let mut delay = env.get("DELAY").and_then(|v| v.parse::<u64>().ok()).unwrap_or(0);
let hour = env.get("START_HOURS_RANGE").and_then(|v| v.splitn(1, '-').next().and_then(|v| v.parse::<u64>().ok())).unwrap_or(0);
let hour = env.get("START_HOURS_RANGE")
.and_then(|v| v.splitn(1, '-').next().and_then(|v| v.parse::<u64>().ok()))
.unwrap_or(0);
let shell = env.get("SHELL").map(|v| &**v).unwrap_or("/bin/sh");
let daemon_reload = metadata(REBOOT_FILE).map(|m| m.is_file()).unwrap_or(false);
let schedule = entry.period().and_then(|period| match *period {
Period::Reboot => {
persistent = false;
if delay == 0 {
delay = 1;
}
None
},
Period::Minutely => {
persistent = false;
Some("minutely".to_owned())
},
Period::Hourly => {
if delay == 0 {
Some("hourly".to_owned())
} else {
Some(format!("*-*-* *:{}:0", delay))
}
},
Period::Midnight => {
if delay == 0 {
Some("daily".to_owned())
} else {
Some(format!("*-*-* 0:{}:0", delay))
}
},
Period::Daily => {
if delay == 0 && hour == 0 {
Some("daily".to_owned())
} else {
Some(format!("*-*-* {}:{}:0", hour, delay))
}
},
Period::Weekly => {
if delay == 0 && hour == 0 {
Some("weekly".to_owned())
} else {
Some(format!("Mon *-*-* {}:{}:0", hour, delay))
}
},
Period::Monthly => {
if delay == 0 && hour == 0 {
Some("monthly".to_owned())
} else {
Some(format!("*-*-1 {}:{}:0", hour, delay))
}
},
Period::Quaterly => {
if delay == 0 && hour == 0 {
Some("quaterly".to_owned())
} else {
Some(format!("*-1,4,7,10-1 {}:{}:0", hour, delay))
}
},
Period::Biannually => {
if delay == 0 && hour == 0 {
Some("semiannually".to_owned())
} else {
Some(format!("*-1,7-1 {}:{}:0", hour, delay))
}
},
Period::Yearly => {
if delay == 0 && hour == 0 {
Some("yearly".to_owned())
} else {
Some(format!("*-1-1 {}:{}:0", hour, delay))
}
},
Period::Days(days) => {
// workaround for anacrontab
if days > 31 {
Some(format!("*-1/{}-1 {}:{}:0", days / 30, hour, delay))
} else {
Some(format!("*-*-1/{} {}:{}:0", days, hour, delay))
}
},
}).or_else(|| entry.calendar().and_then(|cal| {
let Calendar {
ref dows,
ref days,
ref mons,
ref hrs,
ref mins
} = *cal;
let schedule = entry.period()
.and_then(|period| {
match *period {
Period::Reboot => {
persistent = false;
if delay == 0 {
delay = 1;
}
None
}
Period::Minutely => {
persistent = false;
Some("minutely".to_owned())
}
Period::Hourly => if delay == 0 { Some("hourly".to_owned()) } else { Some(format!("*-*-* *:{}:0", delay)) },
Period::Midnight => {
if delay == 0 { Some("daily".to_owned()) } else { Some(format!("*-*-* 0:{}:0", delay)) }
}
Period::Daily => {
if delay == 0 && hour == 0 {
Some("daily".to_owned())
} else {
Some(format!("*-*-* {}:{}:0", hour, delay))
}
}
Period::Weekly => {
if delay == 0 && hour == 0 {
Some("weekly".to_owned())
} else {
Some(format!("Mon *-*-* {}:{}:0", hour, delay))
}
}
Period::Monthly => {
if delay == 0 && hour == 0 {
Some("monthly".to_owned())
} else {
Some(format!("*-*-1 {}:{}:0", hour, delay))
}
}
Period::Quaterly => {
if delay == 0 && hour == 0 {
Some("quaterly".to_owned())
} else {
Some(format!("*-1,4,7,10-1 {}:{}:0", hour, delay))
}
}
Period::Biannually => {
if delay == 0 && hour == 0 {
Some("semiannually".to_owned())
} else {
Some(format!("*-1,7-1 {}:{}:0", hour, delay))
}
}
Period::Yearly => {
if delay == 0 && hour == 0 {
Some("yearly".to_owned())
} else {
Some(format!("*-1-1 {}:{}:0", hour, delay))
}
}
Period::Days(days) => {
// workaround for anacrontab
if days > 31 {
Some(format!("*-1/{}-1 {}:{}:0", days / 30, hour, delay))
} else {
Some(format!("*-*-1/{} {}:{}:0", days, hour, delay))
}
}
}
})
.or_else(|| {
entry.calendar().and_then(|cal| {
let Calendar { ref dows, ref days, ref mons, ref hrs, ref mins } = *cal;
Some(format!("{} *-{}-{} {}:{}:00",
linearize(&**dows, "", ToString::to_string),
linearize(&**mons, "*", |&mon| (mon as u8).to_string()),
linearize(&**days, "*", ToString::to_string),
linearize(&**hrs, "*", ToString::to_string),
linearize(&**mins, "*", ToString::to_string)))
}));
Some(format!("{} *-{}-{} {}:{}:00",
linearize(&**dows, "", ToString::to_string),
linearize(&**mons, "*", |&mon| (mon as u8).to_string()),
linearize(&**days, "*", ToString::to_string),
linearize(&**hrs, "*", ToString::to_string),
linearize(&**mins, "*", ToString::to_string)))
})
});
if daemon_reload && schedule.is_none() {
warn!("skipping job from {}: \"{}\"", path.display(), entry);
@ -143,12 +147,17 @@ pub fn generate_systemd_units(entry: CrontabEntry, env: &BTreeMap<String, String
if let Some(cmd) = entry.command() {
// make sure we know the user
let user = try!(entry.user().and_then(
|user| PwEntIter::new()
.and_then(|mut iter| iter.find(|&pw| unsafe {
(*pw).pw_uid == owner || CStr::from_ptr((*pw).pw_name).to_bytes() == user.as_bytes()
})).map(|pw| unsafe { User::from_ptr(pw) }))
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "unknown user")));
let user = try!(entry.user()
.and_then(|user| {
PwEntIter::new()
.and_then(|mut iter| {
iter.find(|&pw| unsafe {
(*pw).pw_uid == owner || CStr::from_ptr((*pw).pw_name).to_bytes() == user.as_bytes()
})
})
.map(|pw| unsafe { User::from_ptr(pw) })
})
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "unknown user")));
// generate unique cron job id
let mut md5ctx = ::md5::Context::new();
@ -265,7 +274,7 @@ Unit={service_unit_name}"###,
service_unit_name = service_unit_name,
));
if cfg![feature="persistent"] {
if cfg![feature = "persistent"] {
try!(writeln!(timer_unit_file, "Persistent={}", persistent));
}
@ -285,7 +294,10 @@ Unit={service_unit_name}"###,
Ok(())
}
fn linearize<T, C>(input: &[Interval<T>], star: &str, conv: C) -> String where T: Limited, C: Fn(&T) -> String {
fn linearize<T, C>(input: &[Interval<T>], star: &str, conv: C) -> String
where T: Limited,
C: Fn(&T) -> String
{
if input.len() == 1 && input[0] == Interval::Full(1) {
star.to_owned()
} else {
@ -305,7 +317,7 @@ fn tohex(input: &[u8]) -> String {
match d {
0...9 => (d + 0x30) as char,
10...15 => (d + 0x57) as char,
_ => unreachable!("unexpected value: {}", d)
_ => unreachable!("unexpected value: {}", d),
}
}
@ -316,4 +328,3 @@ fn tohex(input: &[u8]) -> String {
}
buf
}

View File

@ -3,7 +3,7 @@ extern crate libc;
use std::ptr;
use std::path::Path;
use std::mem::uninitialized;
use libc::{uid_t, gid_t, c_int, FILE, c_char, size_t, fopen, fclose};
use libc::{FILE, c_char, c_int, fclose, fopen, gid_t, size_t, uid_t};
use std::ffi::CStr;
#[repr(C)]
@ -18,12 +18,7 @@ pub struct PwEnt {
}
extern "C" {
fn fgetpwent_r(stream: *mut FILE,
pwbuf: *mut PwEnt,
buf: *mut c_char,
buflen: size_t,
pwbufp: *mut *mut PwEnt)
-> c_int;
fn fgetpwent_r(stream: *mut FILE, pwbuf: *mut PwEnt, buf: *mut c_char, buflen: size_t, pwbufp: *mut *mut PwEnt) -> c_int;
}
#[derive(Debug, Clone, Eq, PartialEq)]
@ -89,24 +84,15 @@ impl PwEntIter {
}
pub fn from_path<P: AsRef<Path>>(path: P) -> Option<PwEntIter> {
unsafe {
path.as_ref().to_str().and_then(|p| {
PwEntIter::from_ptr(CStr::from_ptr(p.as_ptr() as *const c_char).as_ptr())
})
}
unsafe { path.as_ref().to_str().and_then(|p| PwEntIter::from_ptr(CStr::from_ptr(p.as_ptr() as *const c_char).as_ptr())) }
}
}
impl Iterator for PwEntIter {
type Item = *const PwEnt;
fn next(&mut self) -> Option<*const PwEnt> {
if unsafe {
fgetpwent_r(self.stream,
&mut self.pwbuf,
&mut self.buf as *mut _ as *mut c_char,
BUFLEN,
&mut self.pwbufp)
} != 0 || self.pwbufp.is_null() {
if unsafe { fgetpwent_r(self.stream, &mut self.pwbuf, &mut self.buf as *mut _ as *mut c_char, BUFLEN, &mut self.pwbufp) } != 0 ||
self.pwbufp.is_null() {
None
} else {
Some(self.pwbufp)
@ -117,12 +103,8 @@ impl Iterator for PwEntIter {
#[test]
fn find_root() {
let root = PwEntIter::new()
.and_then(|mut iter| {
iter.find(|&pw| unsafe {
(*pw).pw_uid == 0 || CStr::from_ptr((*pw).pw_name).to_bytes() == b"root"
})
})
.map(|pw| unsafe { User::from_ptr(pw) });
.and_then(|mut iter| iter.find(|&pw| unsafe { (*pw).pw_uid == 0 || CStr::from_ptr((*pw).pw_name).to_bytes() == b"root" }))
.map(|pw| unsafe { User::from_ptr(pw) });
assert_eq!(root,
Some(User {
name: String::from("root"),

View File

@ -13,7 +13,7 @@ use std::os::unix::fs::symlink;
use std::io::Write;
use std::path::Path;
use cronparse::crontab::{UserCrontabEntry, SystemCrontabEntry, AnacrontabEntry};
use cronparse::crontab::{AnacrontabEntry, SystemCrontabEntry, UserCrontabEntry};
mod getpwent;
mod generate;
@ -41,8 +41,8 @@ fn main() {
None => {
println!("Usage: systemd-crontab-generator <destination-directory>");
return;
},
Some(path) => path
}
Some(path) => path,
};
let s = dest_dir.clone();
@ -74,7 +74,8 @@ fn main() {
fn generate_after_var_unit(dest_dir: &str) {
let cron_after_var_unit_path = Path::new(dest_dir).join("cron-after-var.service");
let mut cron_after_var_unit_file = try_!(File::create(&cron_after_var_unit_path));
try_!(writeln!(cron_after_var_unit_file, r###"[Unit]
try_!(writeln!(cron_after_var_unit_file,
r###"[Unit]
Description=Rerun systemd-crontab-generator because /var is a separate mount
Documentation=man:systemd.cron(7)
After=cron.target
@ -83,8 +84,8 @@ ConditionDirectoryNotEmpty={statedir}
[Service]
Type=oneshot
ExecStart=/bin/sh -c "{bindir}/systemctl daemon-reload ; {bindir}/systemctl try-restart cron.target""###,
statedir = USERS_CRONTAB_DIR,
bindir = BIN_DIR));
statedir = USERS_CRONTAB_DIR,
bindir = BIN_DIR));
let multiuser_wants_path = Path::new(dest_dir).join("multi-user.target.wants");
try_!(create_dir_all(&multiuser_wants_path));

View File

@ -1,43 +1,69 @@
use std::convert::AsRef;
use std::fs::{read_dir, metadata};
use std::fs::{metadata, read_dir};
use std::path::{Path, PathBuf};
use std::collections::BTreeMap;
use std::str::FromStr;
use cronparse::{CrontabFile, CrontabFileError, CrontabFileErrorKind};
use cronparse::crontab::{EnvVarEntry, CrontabEntry};
use cronparse::crontab::{CrontabEntry, EnvVarEntry};
use generate::generate_systemd_units;
pub fn process_crontab_dir<T: FromStr, D: AsRef<Path>>(srcdir: &str, dstdir: D) where CrontabEntry: From<T>, CrontabFileError: From<<T as FromStr>::Err> {
let files = read_dir(srcdir).and_then(|fs| fs.map(|r| r.map(|p| p.path()))
.filter(|r| r.as_ref().map(|p| !p.file_name().and_then(|n| n.to_str().map(|n| n.starts_with("."))).unwrap_or(true)
&& metadata(p).map(|m| m.is_file()).unwrap_or(true)).unwrap_or(true))
.collect::<Result<Vec<PathBuf>, _>>());
pub fn process_crontab_dir<T: FromStr, D: AsRef<Path>>(srcdir: &str, dstdir: D)
where CrontabEntry: From<T>,
CrontabFileError: From<<T as FromStr>::Err>
{
let files = read_dir(srcdir).and_then(|fs| {
fs.map(|r| r.map(|p| p.path()))
.filter(|r| {
r.as_ref()
.map(|p| {
!p.file_name()
.and_then(|n| n.to_str().map(|n| n.starts_with(".")))
.unwrap_or(true) && metadata(p).map(|m| m.is_file()).unwrap_or(true)
})
.unwrap_or(true)
})
.collect::<Result<Vec<PathBuf>, _>>()
});
match files {
Err(err) => warn!("error processing directory {}: {}", srcdir, err),
Ok(files) => for file in files {
process_crontab_file::<T, _, _>(file, dstdir.as_ref());
Ok(files) => {
for file in files {
process_crontab_file::<T, _, _>(file, dstdir.as_ref());
}
}
}
}
pub fn process_crontab_file<T: FromStr, P: AsRef<Path>, D: AsRef<Path>>(path: P, dstdir: D) where CrontabEntry: From<T>, CrontabFileError: From<<T as FromStr>::Err> {
CrontabFile::<T>::new(path.as_ref()).map(|crontab| {
let mut env = BTreeMap::new();
for entry in crontab {
match entry {
Ok(CrontabEntry::EnvVar(EnvVarEntry(name, value))) => { env.insert(name, value); },
Ok(data) => match generate_systemd_units(data, &env, path.as_ref(), dstdir.as_ref()) {
Ok(_) => (),
Err(err) => warn!("error generating unit from {}: {}", path.as_ref().display(), err)
},
Err(err @ CrontabFileError { kind: CrontabFileErrorKind::Io(_), .. }) => warn!("error accessing file {}: {}", path.as_ref().display(), err),
Err(err @ CrontabFileError { kind: CrontabFileErrorKind::Parse(_), .. }) => warn!("skipping file {} due to parsing error: {}", path.as_ref().display(), err),
pub fn process_crontab_file<T: FromStr, P: AsRef<Path>, D: AsRef<Path>>(path: P, dstdir: D)
where CrontabEntry: From<T>,
CrontabFileError: From<<T as FromStr>::Err>
{
CrontabFile::<T>::new(path.as_ref())
.map(|crontab| {
let mut env = BTreeMap::new();
for entry in crontab {
match entry {
Ok(CrontabEntry::EnvVar(EnvVarEntry(name, value))) => {
env.insert(name, value);
}
Ok(data) => {
match generate_systemd_units(data, &env, path.as_ref(), dstdir.as_ref()) {
Ok(_) => (),
Err(err) => warn!("error generating unit from {}: {}", path.as_ref().display(), err),
}
}
Err(err @ CrontabFileError { kind: CrontabFileErrorKind::Io(_), .. }) => {
warn!("error accessing file {}: {}", path.as_ref().display(), err)
}
Err(err @ CrontabFileError { kind: CrontabFileErrorKind::Parse(_), .. }) => {
warn!("skipping file {} due to parsing error: {}", path.as_ref().display(), err)
}
}
}
}
}).unwrap_or_else(|err| {
warn!("error parsing file {}: {}", path.as_ref().display(), err);
});
})
.unwrap_or_else(|err| {
warn!("error parsing file {}: {}", path.as_ref().display(), err);
});
}