mikelieman icon

Rsync based backup ported from Perl to Rust

mikelieman | PRO | 06/16/22 01:56:28 PM UTC | 0 ⭐ | 9112 👁️ | Never ⏰ | []
Rust |

12.93 KB

|

None

|

0 👍

/

0 👎

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<String, i32>) {
 
    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<String, i32>, 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<String, i32>) {
 
    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<String, i32>, 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<String, i32> = 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()
 
 
 

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎