cachedir-0.3.0/.cargo_vcs_info.json0000644000000001120000000000000125670ustar { "git": { "sha1": "ece593d0362b117ce52c01dee5c158f5dfa71bb6" } } cachedir-0.3.0/.github/workflows/ci.yml000064400000000000000000000034400000000000000160570ustar 00000000000000name: CI on: [push, pull_request] jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] version: [nightly] fail-fast: false steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.version }} override: true components: rustfmt, llvm-tools-preview # We need this cache, otherwise cargo install grcov takes ages (read: minutes) # which makes the CI take orders of magnituted longer than without it. Caching helps. # There are currently no nicer solutions to this, but using the cache action # to cache ~/.cargo/bin/ should be good enough. - name: Cache things installed with cargo id: cache-cargo-bin uses: actions/cache@v1 with: path: ~/.cargo/bin/ key: ${{ runner.os }}-cargo-bin-grcov - name: Install grcov # If we do "cargo install grcov" while ~/.cargo/bin/grcov exists it'll cause an # error so we need to protect against it. if: steps.cache-cargo-bin.outputs.cache-hit != 'true' run: cargo install grcov - name: Clean run: cargo clean - name: Build run: cargo build --verbose env: RUSTFLAGS: -Zinstrument-coverage - name: Test run: cargo test env: # cargo test builds some things too so we need to add this flag here as well RUSTFLAGS: -Zinstrument-coverage LLVM_PROFILE_FILE: cachedir-%p-%m.profraw - name: Enforce formatting run: cargo fmt -- --check - name: Generate code coverage run: grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "/*" -o lcov.info - uses: codecov/codecov-action@v1 with: verbose: true cachedir-0.3.0/.gitignore000064400000000000000000000000500000000000000133260ustar 00000000000000/target **/*.rs.bk Cargo.lock *.profraw cachedir-0.3.0/Cargo.lock0000644000000067640000000000000105650ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "cachedir" version = "0.3.0" dependencies = [ "tempfile", ] [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "getrandom" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "libc" version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" [[package]] name = "ppv-lite86" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", "rand_chacha", "rand_core", "rand_hc", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ "rand_core", ] [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] [[package]] name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ "cfg-if", "libc", "rand", "redox_syscall", "remove_dir_all", "winapi", ] [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" cachedir-0.3.0/Cargo.toml0000644000000015020000000000000105710ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "cachedir" version = "0.3.0" authors = ["Jakub Stasiak "] description = "A library to help interacting with cache directories and CACHEDIR.TAG files." license = "MIT" repository = "https://github.com/jstasiak/cachedir" [dependencies.tempfile] version = "3" cachedir-0.3.0/Cargo.toml.orig000064400000000000000000000004560000000000000142370ustar 00000000000000[package] name = "cachedir" version = "0.3.0" authors = ["Jakub Stasiak "] edition = "2018" license = "MIT" description = "A library to help interacting with cache directories and CACHEDIR.TAG files." repository = "https://github.com/jstasiak/cachedir" [dependencies] tempfile = "3" cachedir-0.3.0/README.md000064400000000000000000000043200000000000000126210ustar 00000000000000# cachedir [![crates.io](https://img.shields.io/crates/v/cachedir)](https://crates.io/crates/cachedir) [![CI](https://github.com/jstasiak/cdir/workflows/CI/badge.svg)](https://github.com/jstasiak/cdir/actions?query=workflow%3ACI+branch%3Amaster) [![codecov](https://codecov.io/gh/jstasiak/cachedir/branch/master/graph/badge.svg?token=JUWV54A0WG)](https://codecov.io/gh/jstasiak/cachedir) A Rust library and a CLI tool to help interacting with cache directories and CACHEDIR.TAG files as defined in [Cache Directory Tagging Specification](https://bford.info/cachedir/). You can find the [library documentation on docs.rs](https://docs.rs/cachedir/). To install the CLI tool run `cargo install cachedir`. To see what options are available run `cachedir --help`. Only one subcommand, `is-tagged`, is implemented right now. It allows checking if a directory is tagged with `CACHEDIR.TAG` (prints the relevant information to stderr *and* sets an appropriate exit-code: 0 for true, 1 for false, 2 for an error): ``` ~/projects/cachedir% ls -lah target total 16 drwxr-xr-x@ 6 user staff 192B Dec 10 17:02 ./ drwxr-xr-x 10 user staff 320B Dec 10 17:16 ../ -rw-r--r-- 1 user staff 1.4K Dec 12 21:52 .rustc_info.json -rw-r--r-- 1 user staff 177B Dec 10 15:52 CACHEDIR.TAG drwxr-xr-x 13 user staff 416B Dec 12 21:47 debug/ drwxr-xr-x@ 5 user staff 160B Dec 10 17:02 rls/ ~/projects/cachedir% cat target/CACHEDIR.TAG Signature: 8a477f597d28d172789f06886806bc55 # This file is a cache directory tag created by cargo. # For information about cache directory tags see https://bford.info/cachedir/ ~/projects/cachedir% cachedir is-tagged does-not-exist No such file or directory (os error 2) % echo $? 2 ~/projects/cachedir% cachedir is-tagged . . is not tagged with CACHEDIR.TAG ~/projects/cachedir% echo $? 1 ~/projects/cachedir% cachedir is-tagged target target is tagged with CACHEDIR.TAG ~/projects/cachedir% echo $? 0 ``` Versions 0.1.0 and 0.1.1 of this crate on [crates.io](https://crates.io) are actually distributions of [a different, abandonded project by Lilian Anatolie Moraru](https://github.com/lilianmoraru/cachedir). Credits to Lilian for transferring the name to me. cachedir-0.3.0/src/bin/cachedir.rs000064400000000000000000000053730000000000000150220ustar 00000000000000use cachedir; use std::env::args; use std::process::exit; fn main() { let (exit_code, message) = app(args()); if let Some(message) = message { eprintln!("{}", message); } exit(exit_code); } fn app(args: I) -> (i32, Option) where I: IntoIterator, T: AsRef, { let mut args = args.into_iter(); let app = args.next().unwrap(); // We need this split into two lines, otherwise we bump into the "creates a temporary which is // freed while still in use" error. let args: Vec<_> = args.collect(); let args: Vec<_> = args.iter().map(|item| item.as_ref()).collect(); match args.as_slice() { [] => (1, Some(help_text(&app))), ["--help"] => (0, Some(help_text(&app))), ["--version"] => (0, Some(help_text(&app))), [command, directory] => match *command { "is-tagged" => match cachedir::is_tagged(directory) { Err(e) => (2, Some(e.to_string())), Ok(is_tagged) => match is_tagged { true => ( 0, Some(format!("{} is tagged with CACHEDIR.TAG", directory)), ), false => ( 1, Some(format!("{} is not tagged with CACHEDIR.TAG", directory)), ), }, }, _ => (1, Some(help_text(&app))), }, _ => (1, Some(help_text(&app))), } } fn help_text>(binary: T) -> String { let binary = binary.as_ref(); format!( "Usage: {} --help Print this help message {} is-tagged DIRECTORY Check if the directory is tagged or not Application version: 0.3.0 ", binary, binary, ) } #[test] fn help_works() { assert!(app(vec!["binary", "--help"]) == (0, Some(help_text("binary")))); assert!(app(vec!["binary"]) == (1, Some(help_text("binary")))); } #[test] fn is_tagged_works() { let directory = tempfile::tempdir().unwrap(); let directory_str = directory.path().to_str().unwrap().to_string(); assert!( app(vec!["binary", "is-tagged", &directory_str]) == ( 1, Some(format!("{} is not tagged with CACHEDIR.TAG", directory_str)) ) ); cachedir::add_tag(&directory).unwrap(); assert!( app(vec!["binary", "is-tagged", &directory_str]) == ( 0, Some(format!("{} is tagged with CACHEDIR.TAG", directory_str)) ) ); directory.close().unwrap(); // The directory doesn't exist anymore – we should be getting an error. let (exit_code, _output) = app(vec!["binary", "is-tagged", &directory_str]); assert!(exit_code != 0); } cachedir-0.3.0/src/lib.rs000064400000000000000000000220610000000000000132470ustar 00000000000000//! A Rust library to help interacting with cache directories and `CACHEDIR.TAG` files as defined //! in [Cache Directory Tagging Specification](https://bford.info/cachedir/). //! //! The abstract of the spefication should be more than enough to illustrate what we're doing here: //! //! > Many applications create and manage directories containing cached information about content //! > stored elsewhere, such as cached Web content or thumbnail-size versions of images or movies. //! > For speed and storage efficiency we would often like to avoid backing up, archiving, or //! > otherwise unnecessarily copying such directories around, but it is a pain to identify and //! > individually exclude each such directory during data transfer operations. I propose an //! > extremely simple convention by which applications can reliably "tag" any cache directories //! > they create, for easy identification by backup systems and other data management utilities. //! > Data management utilities can then heed or ignore these tags as the user sees fit. use std::io::prelude::*; use std::{env, fs, io, path}; use tempfile; /// The `CACHEDIR.TAG` file header as defined by the specification. pub const HEADER: &'static [u8; 43] = b"Signature: 8a477f597d28d172789f06886806bc55"; /// Returns `true` if the tag is present at `directory`, `false` otherwise. /// This is basically a shortcut for /// /// ```ignore /// get_tag_state(directory).map(|state| match state { /// TagState::Present => true, /// _ => false, /// }) /// ``` /// /// See [get_tag_state](fn.get_tag_state.html) for error conditions documentation. pub fn is_tagged>(directory: P) -> io::Result { get_tag_state(directory).map(|state| match state { TagState::Present => true, _ => false, }) } /// Gets the state of the tag in the specified directory. /// /// Will return an error if: /// /// * The directory can't be accessed for any reason (it doesn't exist, permission error etc.) /// * The `CACHEDIR.TAG` in the directory exists but can't be accessed or read from pub fn get_tag_state>(directory: P) -> io::Result { let directory = directory.as_ref(); match fs::File::open(directory.join("CACHEDIR.TAG")) { Ok(mut cachedir_tag) => { let mut buffer = vec![0; HEADER.len()]; let read = cachedir_tag.read(&mut buffer)?; let header_ok = read == HEADER.len() && buffer == &HEADER[..]; Ok(if header_ok { TagState::Present } else { TagState::WrongHeader }) } Err(e) => match e.kind() { io::ErrorKind::NotFound => { if directory.is_dir() { Ok(TagState::Absent) } else { Err(e) } } _ => Err(e), }, } } /// The state of a `CACHEDIR.TAG` file. pub enum TagState { /// The file doesn't exist. Absent, /// The file exists, but doesn't contain the header required by the /// specification. WrongHeader, /// The file exists and contains the correct header. Present, } /// Adds a tag to the specified `directory`. /// /// Will return an error if: /// /// * The `directory` exists and contains a `CACHEDIR.TAG` file, regardless of its content. /// * The file can't be created for any reason (the `directory` doesn't exist, permission error, /// can't write to the file etc.) pub fn add_tag>(directory: P) -> io::Result<()> { let directory = directory.as_ref(); match fs::OpenOptions::new() .write(true) .create_new(true) .open(directory.join("CACHEDIR.TAG")) { Ok(mut cachedir_tag) => cachedir_tag.write_all(HEADER), Err(e) => Err(e), } } /// Ensures the tag exists in `directory`. /// /// This function considers the `CACHEDIR.TAG` file in `directory` existing, regardless of its /// content, as a success. /// /// Will return an error if The tag file doesn't exist and can't be created for any reason /// (the `directory` doesn't exist, permission error, can't write to the file etc.). pub fn ensure_tag>(directory: P) -> io::Result<()> { match add_tag(directory) { Err(e) => match e.kind() { io::ErrorKind::AlreadyExists => Ok(()), _ => Err(e), }, other => other, } } /// Tries to create `directory` with a `CACHEDIR.TAG` file atomically and returns `true` if it /// created it or `false` if the directory already exists, regardless of if the `CACHEDIR.TAG` /// file exists in it or if it has the correct header. /// /// This function first creates a temporary directory in the same directory where `directory` is /// supposed to exist. The temporary directory has a semi-random name based on the `directory` base /// name. Then the `CACHEDIR.TAG` file is created in the temporary directory and the temporary /// directory is attempted to be renamed to `directory`. This (as opposed to creating the directory /// with the final name and creating `CACHEDIR.TAG` file in it) is a way to ensure that the /// `directory` is always created with the `CACHEDIR.TAG` file. If we simply created the directory /// with the final name the program could be interrupted before `CACHEDIR.TAG` creation and the /// `directory` would remain not excluded from backups as this function does not attempt to verify /// or change the `CACHEDIR.TAG` file in `directory` if it already exists. pub fn mkdir_atomic>(directory: P) -> io::Result { let mut directory = directory.as_ref().to_path_buf(); if directory.exists() { return Ok(false); } if directory.is_relative() { directory = env::current_dir()?.join(directory); } let tempdir = tempfile::Builder::new() .prefix(directory.file_name().unwrap()) .tempdir_in(directory.parent().unwrap())?; add_tag(tempdir.path())?; match fs::rename(tempdir.path(), &directory) { Ok(()) => Ok(true), Err(e) => { if directory.is_dir() { Ok(false) } else { Err(e) } } } } #[test] fn is_tagged_on_nonexistent_directory_is_an_error() { let directory = path::Path::new("this directory does not exist"); assert!(!directory.exists()); assert!(is_tagged(directory).is_err()); } #[test] fn empty_directory_is_not_tagged() { assert!(!is_tagged(tempfile::tempdir().unwrap()).unwrap()); } #[test] fn directory_with_a_tag_with_wrong_content_is_not_tagged() { let directory = tempfile::tempdir().unwrap(); let cachedir_tag = directory.path().join("CACHEDIR.TAG"); fs::write(&cachedir_tag, "").unwrap(); assert!(!is_tagged(&directory).unwrap()); fs::write(&cachedir_tag, &HEADER[..(HEADER.len() - 2)]).unwrap(); assert!(!is_tagged(&directory).unwrap()); } #[test] fn add_tag_is_detected_by_is_tagged() { let directory = tempfile::tempdir().unwrap(); add_tag(directory.path()).unwrap(); assert!(is_tagged(directory.path()).unwrap()); } #[test] fn add_tag_errors_when_called_with_nonexistent_directory() { let directory = path::Path::new("this directory does not exist"); assert!(!directory.exists()); assert!(add_tag(directory).is_err()); } #[test] fn add_tag_errors_when_tag_already_exists() { let directory = tempfile::tempdir().unwrap(); assert!(add_tag(directory.path()).is_ok()); assert!(add_tag(directory.path()).is_err()); } #[test] fn ensure_tag_is_detected_by_is_tagged() { let directory = tempfile::tempdir().unwrap(); ensure_tag(directory.path()).unwrap(); assert!(is_tagged(directory.path()).unwrap()); } #[test] fn ensure_tag_errors_when_called_with_nonexistent_directory() { let directory = path::Path::new("this directory does not exist"); assert!(!directory.exists()); assert!(ensure_tag(directory).is_err()); assert!(is_tagged(directory).is_err()); } #[test] fn ensure_tag_is_idempotent() { let directory = tempfile::tempdir().unwrap(); assert!(ensure_tag(directory.path()).is_ok()); assert!(is_tagged(directory.path()).unwrap()); assert!(ensure_tag(directory.path()).is_ok()); assert!(is_tagged(directory.path()).unwrap()); } #[test] fn mkdir_atomic_works() { let directory = tempfile::tempdir().unwrap(); let cache = directory.path().join("cache"); let threads = (0..10).map(|_| { let cache = cache.clone(); thread::spawn(move || mkdir_atomic(cache)) }); let results = threads.map(|t| t.join().unwrap().unwrap()); let creations: usize = results.map(|created| if created { 1 } else { 0 }).sum(); // One and only one actually creates the desired directory... assert_eq!(creations, 1); // ...which is tagged correctly. assert!(is_tagged(cache).unwrap()); // The mkdir_atomic() calls which didn't actually create the final directory shouldn't leave // behind any garbage. assert_eq!( fs::read_dir(directory.path()) .unwrap() .map(|entry| entry.unwrap().file_name()) .collect::>(), ["cache"], ); }