use std::borrow::Cow;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::path::Path;
use crate::util::Either;
pub fn create(
subvol: &Path,
) -> impl IntoIterator<Item = &OsStr, IntoIter = impl Iterator<Item = &OsStr> + Clone> + Copy {
["subvolume".as_ref(), "create".as_ref(), subvol.as_os_str()]
}
pub fn delete(
subvol: &Path,
) -> impl IntoIterator<Item = &OsStr, IntoIter = impl Iterator<Item = &OsStr> + Clone> + Copy {
["subvolume".as_ref(), "delete".as_ref(), subvol.as_os_str()]
}
pub fn snapshot<'input>(
source: &'input Path,
destination: &'input Path,
readonly: bool,
) -> impl IntoIterator<Item = &'input OsStr, IntoIter = impl Iterator<Item = &'input OsStr> + Clone>
+ Clone {
let command = [
"subvolume".as_ref(),
"snapshot".as_ref(),
source.as_os_str(),
destination.as_os_str(),
];
if readonly {
Either::Left(command.into_iter().chain(["-r".as_ref()]))
} else {
Either::Right(command.into_iter())
}
}
pub fn sync(
filesystem: &Path,
) -> impl IntoIterator<Item = &OsStr, IntoIter = impl Iterator<Item = &OsStr> + Clone> + Copy {
[
"filesystem".as_ref(),
"sync".as_ref(),
filesystem.as_os_str(),
]
}
pub fn serialize<'input, I>(
subvol: &'input Path,
parents: I,
) -> impl IntoIterator<Item = &'input OsStr, IntoIter = impl Iterator<Item = &'input OsStr> + Clone>
+ Clone
where
I: IntoIterator<Item = &'input OsStr>,
I::IntoIter: Clone,
{
let options = parents
.into_iter()
.flat_map(|parent| ["-c".as_ref(), parent]);
["send".as_ref()]
.into_iter()
.chain(options)
.chain([subvol.as_os_str()])
}
pub fn deserialize(
destination: &Path,
) -> impl IntoIterator<Item = &OsStr, IntoIter = impl Iterator<Item = &OsStr> + Clone> + Copy {
["receive".as_ref(), destination.as_os_str()]
}
pub fn subvolumes(
directory: &Path,
readonly: bool,
) -> impl IntoIterator<Item = &OsStr, IntoIter = impl Iterator<Item = &OsStr> + Clone> + Clone {
let command = [
"subvolume".as_ref(),
"list".as_ref(),
"-o".as_ref(),
directory.as_os_str(),
];
if readonly {
Either::Left(command.into_iter().chain(["-r".as_ref()]))
} else {
Either::Right(command.into_iter())
}
}
pub fn diff(
subvol: &Path,
generation: usize,
) -> impl IntoIterator<Item = Cow<'_, OsStr>, IntoIter = impl Iterator<Item = Cow<'_, OsStr>> + Clone>
+ Clone {
let generation = generation.to_string();
[
"subvolume".as_ref(),
"find-new".as_ref(),
subvol.as_os_str(),
]
.into_iter()
.map(Cow::from)
.chain([Cow::from(OsString::from(generation))])
}
pub fn show_filesystem(
filesystem: &Path,
) -> impl IntoIterator<Item = &OsStr, IntoIter = impl Iterator<Item = &OsStr> + Clone> + Copy {
[
"filesystem".as_ref(),
"show".as_ref(),
filesystem.as_os_str(),
]
}
pub fn root_id(
path: &Path,
) -> impl IntoIterator<Item = &OsStr, IntoIter = impl Iterator<Item = &OsStr> + Clone> + Copy {
[
"inspect-internal".as_ref(),
"rootid".as_ref(),
path.as_os_str(),
]
}
pub fn resolve_id(
id: usize,
path: &Path,
) -> impl IntoIterator<Item = Cow<'_, OsStr>, IntoIter = impl Iterator<Item = Cow<'_, OsStr>> + Clone>
+ Clone {
let id = id.to_string();
["inspect-internal".as_ref(), "subvolid-resolve".as_ref()]
.into_iter()
.map(Cow::Borrowed)
.chain([Cow::from(OsString::from(id))])
.chain([Cow::from(path.as_os_str())])
}
pub fn set_readonly(
path: &Path,
) -> impl IntoIterator<Item = Cow<'_, OsStr>, IntoIter = impl Iterator<Item = Cow<'_, OsStr>> + Clone>
+ Clone {
["property".as_ref(), "set".as_ref()]
.into_iter()
.map(Cow::Borrowed)
.chain([
Cow::from(path.as_os_str()),
Cow::from(OsStr::new("ro")),
Cow::from(OsStr::new("true")),
])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::join;
#[test]
fn command_construction() {
fn stringify<'iter, I>(iter: I) -> String
where
I: IntoIterator<Item = &'iter OsStr>,
{
let iter = iter.into_iter().map(|part| part.to_str().unwrap());
join(' ', iter).unwrap()
}
fn stringify_cow<'iter, I>(iter: I) -> String
where
I: IntoIterator<Item = Cow<'iter, OsStr>>,
{
let iter = iter
.into_iter()
.map(|part| part.to_str().unwrap().to_string());
join(' ', iter).unwrap()
}
let command = stringify(create(Path::new("/tmp/foobar")));
assert_eq!(command, "subvolume create /tmp/foobar");
let command = stringify(delete(Path::new("blahblubber")));
assert_eq!(command, "subvolume delete blahblubber");
let src = Path::new("source");
let dst = Path::new("destination");
let command = stringify(snapshot(src, dst, true));
assert_eq!(command, "subvolume snapshot source destination -r");
let command = stringify(snapshot(src, dst, false));
assert_eq!(command, "subvolume snapshot source destination");
let fs = Path::new("some-filesystem");
let command = stringify(sync(fs));
assert_eq!(command, "filesystem sync some-filesystem");
let subvol = Path::new("a-sub-volume");
let parents = [];
let command = stringify(serialize(subvol, parents));
assert_eq!(command, "send a-sub-volume");
let dst = Path::new("destination-volume");
let command = stringify(deserialize(dst));
assert_eq!(command, "receive destination-volume");
let dir = Path::new("/usr/bin/baz");
let readonly = true;
let command = stringify(subvolumes(dir, readonly));
assert_eq!(command, "subvolume list -o /usr/bin/baz -r");
let subvol = Path::new("/var/my-subvol");
let gen = 1337;
let command = stringify_cow(diff(subvol, gen));
assert_eq!(command, "subvolume find-new /var/my-subvol 1337");
let fs = Path::new("another-fs");
let command = stringify(show_filesystem(fs));
assert_eq!(command, "filesystem show another-fs");
let path = Path::new("/var/some-path");
let command = stringify(root_id(path));
assert_eq!(command, "inspect-internal rootid /var/some-path");
let id = 42;
let path = Path::new("hihi-i-am-relative");
let command = stringify_cow(resolve_id(id, path));
assert_eq!(
command,
"inspect-internal subvolid-resolve 42 hihi-i-am-relative"
);
let path = Path::new("/var/some-subvol");
let command = stringify_cow(set_readonly(path));
assert_eq!(command, "property set /var/some-subvol ro true");
}
}