diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /support/pkgcheck/src/utils.rs |
Initial commit
Diffstat (limited to 'support/pkgcheck/src/utils.rs')
-rw-r--r-- | support/pkgcheck/src/utils.rs | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/support/pkgcheck/src/utils.rs b/support/pkgcheck/src/utils.rs new file mode 100644 index 0000000000..6b2d0a8753 --- /dev/null +++ b/support/pkgcheck/src/utils.rs @@ -0,0 +1,396 @@ +use std::fs::read_dir; +use std::fs::read_link; +use std::os::unix::fs::PermissionsExt; +use std::path::Path; +use std::path::PathBuf; +use walkdir::DirEntry; + +use regex::Regex; +use std::borrow::Cow; +use std::io; +use std::process::Command; + + +use std::fs; + +pub fn temp_file_endings() -> Vec<(&'static str, &'static str)> { + // https://github.com/github/gitignore/blob/master/TeX.gitignore + // http://hopf.math.purdue.edu/doc/html/suffixes.html + vec![ + ("-blx.aux", "bibliography auxiliary file"), + ("-blx.bib", "bibliography auxiliary file"), + (".4ct", "htlatex related"), + (".4tc", "htlatex related"), + (".DS_Store", "Mac OS custom attribute file"), + (".acn", "glossaries related"), + (".acr", "glossaries related"), + (".alg", ""), + (".aux", "core latex auxiliary file"), + (".backup", "backup file"), + (".bak", "backup file"), + (".bbl", "bibliography auxiliary file"), + (".bcf", "bibliography auxiliary file"), + (".blg", "bibliography log file"), + (".brf", "hyperref related"), + (".cb", "core latex auxiliary file"), + (".cb2", "core latex auxiliary file"), + (".cpt", "cprotect related"), + (".dvi", "intermediate document"), + (".ent", ""), + (".fdb_latexmk", "latexmk related"), + (".fff", "endfloat related"), + (".fls", ""), + (".fmt", "core latex auxiliary file"), + (".fot", "core latex auxiliary file"), + (".gaux", "generated by gregoriotex"), + (".glg", "glossary related"), + (".glo", "glossary related"), + (".glog", "generated by gregoriotex"), + (".gls", "glossary related"), + (".glsdefs", "glossaries related"), + (".gtex", "generated by gregoriotex"), + (".idv", "htlatex related"), + (".idx", "makeidx related"), + (".ilg", "makeidx related"), + (".ind", "makeidx related"), + (".lg", "htlatex related"), + (".loa", "core latex auxiliary file (list of algorithms)"), + (".lod", "generated by easy-todo"), + (".lof", "core latex auxiliary file (list of figures)"), + (".log", "a log file for any flavor of TeX"), + (".lol", "core latex auxiliary file (list of listings)"), + (".los", "list of slides"), + (".lot", "core latex auxiliary file"), + (".lox", ""), + (".lyx#", "LyX related autosave file"), + (".maf", "generated by minitoc"), + (".mlc", "generated by minitoc"), + (".mlf", "generated by minitoc"), + (".mlt", "generated by minitoc"), + (".nav", "beamer related"), + (".nlg", ""), + (".nlo", ""), + (".nls", ""), + (".o", "C object file"), + (".out", "Core latex auxiliary file"), + (".pdfsync", "pdfsync related"), + (".pre", "beamer related"), + (".pyg", ""), + (".run.xml", ""), + (".sav", "used for saved data"), + (".snm", "beamer related"), + (".soc", ""), + (".spl", "elsarticle related"), + (".sta", "generated by standalone package"), + (".swp", "vim swap file"), + (".synctex", "synctex related"), + (".synctex(busy)", "synctex related"), + (".synctex.gz", "synctex related"), + (".synctex.gz(busy)", "synctex related"), + (".tdo", "generated by todonotes (list of todos)"), + (".thm", "amsthm related"), + (".tmb", "generated by thumbs package"), + (".tmp", "indicates a temporary file"), + (".toc", "core latex auxiliary file (table of contents)"), + (".trc", "htlatex related"), + (".ttt", "endfloat related"), + (".tuc", ""), + (".upa", "generated by the soulpos package"), + (".upb", "generated by the soulpos package"), + (".url", "generated by jurabib"), + (".vrb", "beamer related"), + (".w18", "temporary file for the ifplatform package"), + (".xdv", "intermediate document"), + (".xref", "htlatex related"), + ("~", "a file name ending with ~ (tilde) is temporary anyway"), + // ( ".lyx~", "LyX related backup file" ), + ] +} + +pub fn regex_temporary_file_endings() -> Regex { + let mut rv = String::new(); + let mut first_time = true; + for (p, _) in temp_file_endings() { + if first_time { + rv.push('('); + first_time = false; + } else { + rv.push('|'); + } + let px = str::replace(p, ".", "\\."); + + rv.push_str(&px); + } + rv.push_str(")$"); + Regex::new(&rv).unwrap() +} + +pub fn is_temporary_file(entry: &str) -> bool { + lazy_static! { + static ref RE: Regex = regex_temporary_file_endings(); + } + + RE.is_match(entry) +} + +pub fn is_empty_directory(entry: &DirEntry) -> Result<bool, io::Error> { + let s = entry.path().to_str().unwrap(); + match read_dir(s) { + // try to read the directory specified + Ok(contents) => Ok(contents.count() == 0), + Err(e) => Err(e), + } +} + +pub fn is_unwanted_directory(entry: &str) -> bool { + let names = ["__MACOSX"]; + for n in &names { + if *n == entry { + return true; + }; + } + + false +} + +// is_hidden checks for files or directories starting with a dot +pub fn is_hidden(entry: &str) -> bool { + entry.starts_with('.') +} + +// is_hidden checks for files or directories starting with a dot +pub fn is_hidden_directory(entry: &DirEntry) -> bool { + let fname = entry.file_name(); + + if fname == "." { + return false; + }; + if fname == "./" { + return false; + }; + + fname.to_str().map(|s| s.starts_with('.')).unwrap_or(false) +} + +pub fn get_symlink(entry: &DirEntry) -> Result<Option<PathBuf>, io::Error> { + let r = entry.path().to_str().unwrap(); + + match read_link(r) { + Ok(o) => { + let full_path = if o.is_absolute() { + o + } else { + // make the relative path absolute + let p = entry.path().parent().unwrap(); + p.join(&o) + }; + if full_path.exists() { + Ok(Some(full_path)) + } else { + Ok(None) + } + } + Err(e) => Err(e), + } +} + +pub fn _is_symlink_broken(entry: &DirEntry) -> Result<bool, io::Error> { + let r = entry.path().to_str().unwrap(); + + match read_link(r) { + Ok(o) => { + let full_path = if o.is_absolute() { + o + } else { + // make the relative path absolute + let p = entry.path().parent().unwrap(); + p.join(&o) + }; + Ok(!full_path.exists()) + } + Err(e) => Err(e), + } +} + +// Runs `pdfinfo` to check a PDF document. If `pdfinfo` +// returns non zero we assume that the PDF document is +// corrupted. +pub fn is_pdf_ok(fname: &str) -> CmdReturn { + run_cmd("/usr/bin/pdfinfo", &[fname]) +} + +pub fn get_perms(path: &Path) -> u32 { + match path.metadata() { + Ok(p) => p.permissions().mode(), + Err(_e) => 0, + } +} + +pub fn others_match(p: u32, m: u32) -> bool { + p == m +} + +#[test] +fn test_others_have() { + assert_eq!(others_have(0o600, 4), false); + assert_eq!(others_have(0o601, 4), false); + assert_eq!(others_have(0o602, 4), false); + assert_eq!(others_have(0o603, 4), false); + assert_eq!(others_have(0o604, 4), true); + assert_eq!(others_have(0o605, 4), true); + assert_eq!(others_have(0o606, 4), true); + assert_eq!(others_have(0o607, 4), true); +} + +// It checks if a permission `p` has the bits +// given in `m` set for others +pub fn others_have(p: u32, m: u32) -> bool { + let p1 = p & 0o0007; + p1 & m == m +} + +#[test] +fn test_owner_has() { + assert_eq!(owner_has(0o000, 4), false); + assert_eq!(owner_has(0o100, 4), false); + assert_eq!(owner_has(0o200, 4), false); + assert_eq!(owner_has(0o300, 4), false); + assert_eq!(owner_has(0o400, 4), true); + assert_eq!(owner_has(0o505, 4), true); + assert_eq!(owner_has(0o605, 4), true); + assert_eq!(owner_has(0o705, 4), true); +} + +// It checks if a permission `p` has the bits +// given in `m` set for the owner. +pub fn owner_has(p: u32, m: u32) -> bool { + let p1 = p & 0o7777; + let m1 = m << 6; + p1 & m1 == m1 +} + +#[allow(dead_code)] +fn owner_match(p: u32, m: u32) -> bool { + let p1 = p & 0o0700; + + let m1 = m << 6; + p1 == m1 +} + +// Formats a permission value to octal for output +pub fn perms_to_string(p: u32) -> String { + format!("{:04o}", p & 0o7777) +} + +pub struct CmdReturn { + pub status: bool, + pub output: Option<String>, +} + +// Runs a command in a shell +// If the command returns 0 stdout gets captured. Otherwise +// stderr gets captured and returned. +pub fn run_cmd(cmd: &str, argument: &[&str]) -> CmdReturn { + let output = Command::new(cmd) + .args(argument.iter()) + .output() + .expect("Failed to execute process"); + + if output.status.success() { + CmdReturn { + status: true, + output: Some(String::from_utf8_lossy(&output.stdout).to_string()), + } + } else { + CmdReturn { + status: false, + output: Some(String::from_utf8_lossy(&output.stderr).to_string()), + } + } +} + +// Sets permissions for a file or directory +// Sample invocation: set_perms("somfile", 0o644); +pub fn set_perms(entry: &str, p: u32) -> bool { + let ps = &format!("{:o}", p); + let rc = run_cmd("chmod", &["-v", ps, entry]); + + if rc.status { + if let Some(op) = rc.output { + print!("{}", op); + } + true + } else { + false + } +} + +// returns true if file is a directory and does exist +// returns false otherwise +pub fn exists_dir(file: &str) -> bool { + match fs::metadata(file) { + Ok(attr) => attr.is_dir(), + Err(_) => false, + } +} + +// returns true if file is a regular file and does exist +// returns false otherwise +pub fn exists_file(file: &str) -> bool { + match fs::metadata(file) { + Ok(attr) => attr.is_file(), + Err(_) => false, + } +} + +#[test] +fn test_dirname() { + assert!(dirname("/etc/fstab") == Some("/etc")); + assert!(dirname("/etc/") == Some("/etc")); + assert!(dirname("/") == Some("/")); +} + +#[allow(dead_code)] +pub fn dirname(entry: &str) -> Option<&str> { + if entry.ends_with('/') { + if entry.len() == 1 { + return Some(entry); + } + return Some(&entry[..entry.len() - 1]); + } + let pos = entry.rfind('/'); + match pos { + None => None, + Some(pos) => Some(&entry[..pos]), + } +} + +#[test] +fn test_filename() { + assert!(filename("/etc/fstab") == Some("fstab")); + assert!(filename("fstab") == Some("fstab")); + assert!(filename("../pkgcheck.rs/testdirs/fira.tds.zip") == Some("fira.tds.zip")); + assert!(filename("/etc/") == None); + assert!(filename("/") == None); +} + +pub fn filename(entry: &str) -> Option<&str> { + if entry.ends_with('/') { + return None; + } + + let pos = entry.rfind('/'); + match pos { + None => Some(entry), + Some(pos) => Some(&entry[pos + 1..]), + } +} + +// Found here: https://codereview.stackexchange.com/questions/98536/extracting-the-last-component-basename-of-a-filesystem-path +pub fn basename(path: &str) -> Cow<str> { + let mut pieces = path.rsplitn(2, |c| c == '/' || c == '\\'); + match pieces.next() { + Some(p) => p.into(), + None => path.into(), + } +} |