use itertools::Itertools; use log::{debug, error, info, trace, warn, log_enabled}; use std::collections::HashMap; use std::ffi::OsStr; use std::fs::File; use std::io::Write; use std::path::Path; use std::process::{Command, Output}; use whoami::username; /////////////////////////////////////////////////////////////////////// // /* my $VERBOSE = q{-v}; # mount->cmd needs T_FSTYPE and T_UUID $cmds->{'mount'}->{'cmd'} = $cmds->{'mount'}->{'bin'} . qq{ $VERBOSE -t $T_FSTYPE -U $T_UUID $FS_MOUNTPOINT } . $cmds->{'mount'}->{'opt'}; # rsync-opt needs excludes filename, derived from T_UUID my $CHKSUM = q{}; if ($RSYNC_CHECKSUMS) { INFO(q{Checksums for rsync are enabled}); $CHKSUM = q{-c} }; $cmds->{'rsync'}->{'opt'} = qq{-a $VERBOSE $CHKSUM --delete --delete-excluded --delete-before } . qq{--exclude-from=$backup_vols->{$T_UUID}->{'EXCLUDES'} }; $cmds->{'rsync'}->{'cmd'} = qq{$cmds->{'ionice'}->{'bin'} $cmds->{'ionice'}->{'opt'} } . qq{$cmds->{'rsync'}->{'bin'} $cmds->{'rsync'}->{'opt'} } . qq{$backup_vols->{$T_UUID}->{'SOURCES'} $FS_MOUNTPOINT}; */ // // /* $cmds->{'rsync'}->{'cmd'} = qq{$cmds->{'ionice'}->{'bin'} $cmds->{'ionice'}->{'opt'} } . qq{$cmds->{'rsync'}->{'bin'} . qq{-a $VERBOSE $CHKSUM --delete --delete-excluded --delete-before } . qq{--exclude-from=$backup_vols->{$T_UUID}->{'EXCLUDES'} }; . qq{$backup_vols->{$T_UUID}->{'SOURCES'} $FS_MOUNTPOINT}; 'SOURCES' => '/boot /etc /home /opt /root /srv /storage /usr /var' */ /////////////////////////////////////////////////////////////////////// fn test_rsync() { info!("test_rsync() - Begin."); info!("testing rsync with rpm exclusion file..."); let mut command = Command::new("/usr/bin/ionice"); command.arg("-c"); // scheduling class command.arg("3"); // idle command.arg("/usr/bin/rsync"); command.arg("--dry-run"); command.arg("-a"); // archive mode command.arg("-v"); // verbose // command.arg(""); // checksum command.arg("--delete"); command.arg("--delete-excluded"); command.arg("--delete-before"); // command.arg("--exclude-from=rsync-excludes.txt"); // RPM EXCLUDES command.arg("--exclude-from=excludes-1tb.conf"); // VOLUME EXCLUDES command.arg("/boot"); // SOURCES command.arg("/etc"); // SOURCES command.arg("/home"); // SOURCES command.arg("/opt"); // SOURCES command.arg("/root"); // SOURCES command.arg("/srv"); // SOURCES command.arg("/storage"); // SOURCES command.arg("/usr"); // SOURCES command.arg("/var"); // SOURCES command.arg("/storage/tmp/rsync-test"); // FS_MOUNTPOINT let args: Vec<&OsStr> = command.get_args().collect(); info!("Arguments to ionice: {:?}", args); match command.output() { Ok(o) => { // stdout match String::from_utf8(o.stdout) { Ok(stdout) => { for line in stdout.lines() { info!("stdout: {}", line); } // for }, // Ok(stdout) Err(e) => { error!("Could not convert from UTF8. Bailing out! {:?}", e); panic!("Could not convert from UTF8. Bailing out! {:?}", e); }, // Err } //match stdout // stderr match String::from_utf8(o.stderr) { Ok(stderr) => { for line in stderr.lines() { info!("stderr: {}", line); } // for }, // Ok(stderr) Err(e) => { error!("Could not convert from UTF8. Bailing out! {:?}", e); panic!("Could not convert from UTF8. Bailing out! {:?}", e); }, // Err } //match stderr }, // Ok(o) Err(e) => { error!("Could not run the command. Bailing out! {:?}", e); panic!("Could not run the command. Bailing out! {:?}", e); }, // Err } // match info!("test_rsync() - End."); } // fn test_rsync /////////////////////////////////////////////////////////////////////// // // // /////////////////////////////////////////////////////////////////////// fn main() { match setup_logger() { Ok(_) => info!("Logging initialized. (log level: {})", log::max_level()), Err(e) => { println!("Could not initialize logging: {e:?}. Bailing out! "); std::process::exit(1); }, } // match setup_logger let username = username(); info!("Running as user '{}'", username); if username.ne(&String::from("root")) { error!("YOU ARE NOT RUNNING AS 'root'! YOU WILL NOT GET A PROPER BACKUP!"); } debug!("create_rsync_exclude_rpm_files()"); create_rsync_exclude_rpm_files(); info!("testing rsync w/ exclude file (--dry-run)"); test_rsync(); } // main /////////////////////////////////////////////////////////////////////// // // // /////////////////////////////////////////////////////////////////////// fn setup_logger() -> Result<(), fern::InitError> { fern::Dispatch::new() .format(|out, message, record| { out.finish(format_args!( "{} [{}] [{}] {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), record.target(), record.level(), message )) }) .level(log::LevelFilter::Debug) .chain(std::io::stdout()) .chain(fern::log_file("output.log")?) .apply()?; Ok(()) } //setup_logger /////////////////////////////////////////////////////////////////////// // // // /////////////////////////////////////////////////////////////////////// fn get_all_rpm_files(f: &mut HashMap) { info!("Collecting exclusion list of files from all installed rpm packages..."); let mut command = Command::new("/usr/bin/rpm"); command.arg("-qal"); match command.output() { Ok(output) => { process_rpm_file(f, output); }, Err(e) => { error!("Could not run the command. Bailing out! {:?}", e); panic!("Could not run the command. Bailing out! {:?}", e); }, } // match command.output() info!("HashMap has {} elements.", f.len()); } // get_all_rpm_files /////////////////////////////////////////////////////////////////////// // // // /////////////////////////////////////////////////////////////////////// fn process_rpm_file(f: &mut HashMap, o: Output) { // stdout match String::from_utf8(o.stdout) { Ok(stdout) => { for line in stdout.lines() { trace!("{}", line); if Path::new(line).is_file() { trace!("{} is a regular file", line); // insert file, with some arbitrary value f.insert( line.to_string(), 1 ) ; } // if path exists else { debug!("{} is not a regular file. Skipped.", line); } // else } // for }, // Ok(stdout) Err(e) => { error!("Could not convert from UTF8. Bailing out! {:?}", e); panic!("Could not convert from UTF8. Bailing out! {:?}", e); }, // Err } //match // stderr match String::from_utf8(o.stderr) { Ok(stderr) => { for line in stderr.lines() { info!("stderr: {}", line); } // for }, // Ok(stderr) Err(e) => { error!("Could not convert from UTF8. Bailing out! {:?}", e); panic!("Could not convert from UTF8. Bailing out! {:?}", e); }, // Err } //match stderr } // fn process_rom_file /////////////////////////////////////////////////////////////////////// // // // /////////////////////////////////////////////////////////////////////// fn get_rpm_verify_info(f: &mut HashMap) { info!("Verifying files on exclusion list..."); let mut command = Command::new("/usr/bin/ionice"); command.arg("-c"); // scheduling class command.arg("3"); // idle command.arg("/usr/bin/rpm"); command.arg("-Va"); // Verify all match command.output() { Ok(output) => { process_verify_entry(f, output); }, Err(e) => { error!("Could not run the command. Bailing out! {:?}", e); panic!("Could not run the command. Bailing out! {:?}", e); }, } // match command.output() info!("HashMap has {} elements.", f.len()); } // get_rpm_verify_info /////////////////////////////////////////////////////////////////////// // // // /////////////////////////////////////////////////////////////////////// fn process_verify_entry(f: &mut HashMap, o: Output) { // stdout match String::from_utf8(o.stdout) { Ok(stdout) => { let mut file_list: Vec<&str> = Vec::new(); for line in stdout.lines() { trace!("{}", line); if line.contains("(Permission denied)") { error!("{}", line); } // if line contains... else { let mut t: Vec<&str> = line.split_whitespace().collect(); t.reverse(); let _flags = t.pop().unwrap(); t.reverse(); let name = t.pop().unwrap(); if Path::new(name).is_file() { file_list.push(name); } else { debug!("{} is a directory",name); } } // else line contains. } // for file_list.sort(); file_list.dedup(); info!("Removing changed files from exclusion list..."); let mut removed_count = 0; for elem in file_list.iter() { f.remove(&elem.to_string()); debug!("removed {} from HashMap", elem); removed_count += 1; trace!("HashMap has {} elements.", f.len()); } // for... info!("Total removed from HashMap: {}", removed_count); }, // Ok(stdout) Err(e) => { error!("Could not convert from UTF8. Bailing out! {:?}", e); panic!("Could not convert from UTF8. Bailing out! {:?}", e); }, // Err } //match // stderr match String::from_utf8(o.stderr) { Ok(stderr) => { for line in stderr.lines() { info!("stderr: {}", line); } // for }, // Ok(stderr) Err(e) => { error!("Could not convert from UTF8. Bailing out! {:?}", e); panic!("Could not convert from UTF8. Bailing out! {:?}", e); }, // Err } //match stderr } // fn process_verify_entry /////////////////////////////////////////////////////////////////////// // // fn create_rsync_exclude_rpm_files() // // Write HashMap keys to rsync-format exclude file // /////////////////////////////////////////////////////////////////////// fn create_rsync_exclude_rpm_files() { info!("Creating rsync exclude file for unchanged rpm managed files..."); let mut files: HashMap = HashMap::new(); info!("HashMap has {} elements.", files.len()); // step 1 get_all_rpm_files(&mut files); trace!("HashMap has {} elements after get_all_rpm_files.", files.len()); if log_enabled!(log::Level::Trace) { trace!("Raw {:?}", files); trace!("Contents of HashMap"); for (file, status) in &files { trace!("[all-files-tag] {}: {}", file, status); } } // if log enabled // step 2 get_rpm_verify_info(&mut files); trace!("HashMap has {} elements after get_rpm_verify_info.", files.len()); if log_enabled!(log::Level::Trace) { trace!("Raw {:?}", files); trace!("Contents of HashMap"); for (file, status) in &files { trace!("[all-rpm-tag] {}: {}", file, status); } } // if log enabled // step 3 let mut exclude_file = File::create("rsync-excludes.txt").expect("Unable to create file"); for file in files.keys().sorted() { let line = format!("{}\n", file); trace!("{}", line); exclude_file.write_all(line.as_bytes()).expect("Unable to write data"); } // for... } // fn create_rsync_exclude_rpm_files()