ips/src/actions/mod.rs

310 lines
9.1 KiB
Rust
Raw Normal View History

2020-05-17 21:52:39 +02:00
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of the
// MPL was not distributed with this file, You can
// obtain one at https://mozilla.org/MPL/2.0/.
2020-05-19 22:14:28 +02:00
use regex::{RegexSet, Regex};
2020-05-18 10:32:16 +02:00
use std::collections::HashSet;
use std::error;
use std::fmt;
2020-05-17 01:17:19 +02:00
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
2020-05-18 16:57:21 +02:00
use failure::Error;
2020-05-17 21:52:39 +02:00
#[derive(Debug, Default)]
2020-05-17 21:52:39 +02:00
pub struct Dir {
pub path: String,
pub group: String,
pub owner: String,
pub mode: String, //TODO implement as bitmask
2020-05-19 22:14:28 +02:00
pub revert_tag: String,
pub salvage_from: String,
pub facets: HashSet<Facet>,
}
#[derive(Hash, Eq, PartialEq, Debug, Default)]
pub struct Facet {
pub name: String,
pub value: String,
2020-05-17 21:52:39 +02:00
}
2020-05-17 01:17:19 +02:00
#[derive(Debug, Default)]
2020-05-17 01:17:19 +02:00
pub struct Attr {
2020-05-17 21:52:39 +02:00
pub key: String,
pub values: Vec<String>,
pub properties: HashSet<Property>,
}
2020-05-19 22:14:28 +02:00
#[derive(Hash, Eq, PartialEq, Debug, Default)]
2020-05-17 21:52:39 +02:00
pub struct Property {
pub key: String,
pub value: String,
2020-05-17 01:17:19 +02:00
}
#[derive(Debug, Default)]
2020-05-17 01:17:19 +02:00
pub struct Manifest {
2020-05-17 21:52:39 +02:00
pub attributes: Vec<Attr>,
2020-05-19 22:14:28 +02:00
pub directories: Vec<Dir>,
2020-05-17 01:17:19 +02:00
}
impl Manifest {
pub fn new() -> Manifest {
2020-05-18 10:32:16 +02:00
return Manifest {
2020-05-17 21:52:39 +02:00
attributes: Vec::new(),
2020-05-19 22:14:28 +02:00
directories: Vec::new(),
2020-05-17 01:17:19 +02:00
};
}
}
2020-05-17 21:52:39 +02:00
enum ActionKind {
Attr,
Dir,
File,
Dependency,
User,
Group,
Driver,
License,
Link,
2020-05-18 16:57:21 +02:00
Legacy,
Unknown{action: String},
2020-05-17 21:52:39 +02:00
}
2020-05-18 16:57:21 +02:00
//TODO Multierror and no failure for these cases
#[derive(Debug, Fail)]
2020-05-17 01:17:19 +02:00
pub enum ManifestError {
2020-05-18 16:57:21 +02:00
#[fail(display = "unknown action {} at line {}", action, line)]
UnknownAction {
line: usize,
action: String,
},
2020-05-19 22:14:28 +02:00
#[fail(display = "action string \"{}\" at line {} is invalid: {}", action, line, message)]
InvalidAction {
line: usize,
action: String,
message: String,
},
2020-05-17 01:17:19 +02:00
}
2020-05-18 16:57:21 +02:00
pub fn parse_manifest_file(filename: String) -> Result<Manifest, Error> {
let mut m = Manifest::new();
let f = File::open(filename)?;
2020-05-17 01:17:19 +02:00
let file = BufReader::new(&f);
2020-05-18 16:57:21 +02:00
for (line_nr, line_read) in file.lines().enumerate() {
2020-05-19 10:39:06 +02:00
handle_manifest_line(&mut m, line_read?.trim_start(), line_nr)?;
2020-05-18 10:32:16 +02:00
}
2020-05-18 16:57:21 +02:00
return Ok(m);
2020-05-17 01:17:19 +02:00
}
2020-05-18 16:57:21 +02:00
pub fn parse_manifest_string(manifest: String) -> Result<Manifest, Error> {
2020-05-17 01:17:19 +02:00
let mut m = Manifest::new();
2020-05-18 16:57:21 +02:00
for (line_nr, line) in manifest.lines().enumerate() {
2020-05-19 10:39:06 +02:00
handle_manifest_line(&mut m, line.trim_start(), line_nr)?;
}
return Ok(m);
}
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
fn handle_manifest_line(manifest: &mut Manifest, line: &str, line_nr: usize) -> Result<(), Error> {
match determine_action_kind(&line) {
ActionKind::Attr => {
manifest.attributes.push(parse_attr_action(String::from(line))?);
}
ActionKind::Dir => {
2020-05-19 22:14:28 +02:00
manifest.directories.push(parse_dir_action(String::from(line), line_nr)?);
2020-05-19 10:39:06 +02:00
}
ActionKind::File => {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind::Dependency => {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind::User => {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind::Group => {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind::Driver => {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind::License => {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind::Link => {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind::Legacy => {
2020-05-18 16:57:21 +02:00
2020-05-19 10:39:06 +02:00
}
ActionKind::Unknown{action} => {
Err(ManifestError::UnknownAction {action, line: line_nr})?;
2020-05-17 01:17:19 +02:00
}
}
2020-05-19 10:39:06 +02:00
Ok(())
2020-05-17 01:17:19 +02:00
}
2020-05-18 16:57:21 +02:00
fn determine_action_kind(line: &str) -> ActionKind {
let mut act = String::new();
for c in line.trim_start().chars() {
if c == ' ' {
break
}
act.push(c)
}
return match act.as_str() {
"set" => ActionKind::Attr,
"depend" => ActionKind::Dependency,
"dir" => ActionKind::Dir,
"file" => ActionKind::File,
"license" => ActionKind::License,
"hardlink" => ActionKind::Link,
"link" => ActionKind::Link,
"driver" => ActionKind::Driver,
"group" => ActionKind::Group,
"user" => ActionKind::User,
"legacy" => ActionKind::Legacy,
_ => ActionKind::Unknown{action: act},
2020-05-17 01:17:19 +02:00
}
}
2020-05-19 22:14:28 +02:00
fn parse_dir_action(line: String, line_nr: usize) -> Result<Dir, Error> {
let mut act = Dir::default();
let regex = Regex::new(r#"(([^ ]+)=([^"][^ ]+[^"])|([^ ]+)=([^"][^ ]+[^"]))"#)?;
for cap in regex.captures_iter(line.trim_start()) {
match &cap[1] {
"path" => act.path = String::from(&cap[2]).replace(&['"', '\\'][..], ""),
"owner" => act.owner = String::from(&cap[2]).replace(&['"', '\\'][..], ""),
"group" => act.group = String::from(&cap[2]).replace(&['"', '\\'][..], ""),
"mode" => act.mode = String::from(&cap[2]).replace(&['"', '\\'][..], ""),
"revert-tag" => act.revert_tag = String::from(&cap[2]).replace(&['"', '\\'][..], ""),
"salvage-from" => act.salvage_from = String::from(&cap[2]).replace(&['"', '\\'][..], ""),
_ => {
let key_val_string = String::from(&cap[1]).replace(&['"', '\\'][..], "");
if key_val_string.contains("facet.") {
let key = match key_val_string.find(".") {
Some(idx) => {
key_val_string.clone().split_off(idx+1)
},
None => return Err(ManifestError::InvalidAction{action: line, line: line_nr, message: String::from("separation dot not found but string contains facet.")})?
};
let value = match key_val_string.find("=") {
Some(idx) => {
key_val_string.clone().split_off(idx+1)
},
None => return Err(ManifestError::InvalidAction{action: line, line: line_nr, message: String::from("no value present for facet")})?
};
if !act.facets.insert(Facet{name: key, value: value}) {
return Err(ManifestError::InvalidAction{action: line, line: line_nr, message: String::from("double declaration of facet")})?
}
}
}
}
}
Ok(act)
}
fn parse_attr_action(line: String) -> Result<Attr, Error> {
// Do a full line match to see if we can fast path this.
// This also catches values with spaces, that have not been properly escaped.
// Note: values with spaces must be properly escaped or the rest here will fail. Strings with
// unescaped spaces are never valid but sadly present in the wild.
// Fast path will fail if a value has multiple values or a '=' sign in the values
2020-05-18 16:57:21 +02:00
let full_line_regex = Regex::new(r"^set name=([^ ]+) value=(.+)$")?;
if full_line_regex.is_match(line.trim_start()) {
match full_line_regex.captures(line.trim_start()) {
Some(captures) => {
let mut fast_path_fail = false;
let mut val = String::from(&captures[2]);
if val.contains("=") {
fast_path_fail = true;
}
if val.contains("value=") {
fast_path_fail = true;
}
if val.contains("name=") {
fast_path_fail = true;
}
val = val.replace(&['"', '\\'][..], "");
2020-05-18 16:57:21 +02:00
//TODO knock out single quotes somehow
if !fast_path_fail{
return Ok(Attr{
key: String::from(&captures[1]),
values: vec![val],
..Attr::default()
});
}
}
None => (),
};
}
2020-05-17 21:52:39 +02:00
//Todo move regex initialisation out of for loop into static area
2020-05-18 16:57:21 +02:00
let name_regex = Regex::new(r"name=([^ ]+) value=")?;
2020-05-17 21:52:39 +02:00
let mut key = String::new();
2020-05-17 01:17:19 +02:00
for cap in name_regex.captures_iter(line.trim_start()) {
2020-05-17 21:52:39 +02:00
key = String::from(&cap[1]);
2020-05-17 01:17:19 +02:00
}
let mut values = Vec::new();
2020-05-18 16:57:21 +02:00
let value_no_space_regex = Regex::new(r#"value="(.+)""#)?;
2020-05-17 01:17:19 +02:00
2020-05-18 16:57:21 +02:00
let value_space_regex = Regex::new(r#"value=([^"][^ ]+[^"])"#)?;
2020-05-17 01:17:19 +02:00
2020-05-17 21:52:39 +02:00
let mut properties = HashSet::new();
2020-05-18 16:57:21 +02:00
let optionals_regex_no_quotes = Regex::new(r#"([^ ]+)=([^"][^ ]+[^"])"#)?;
let optionals_regex_quotes = Regex::new(r#"([^ ]+)=([^"][^ ]+[^"])"#)?;
2020-05-17 21:52:39 +02:00
2020-05-17 01:17:19 +02:00
for cap in value_no_space_regex.captures_iter(line.trim_start()) {
2020-05-17 21:52:39 +02:00
values.push(String::from(cap[1].trim()));
2020-05-17 01:17:19 +02:00
}
for cap in value_space_regex.captures_iter(line.trim_start()) {
2020-05-17 21:52:39 +02:00
values.push(String::from(cap[1].trim()));
}
for cap in optionals_regex_quotes.captures_iter(line.trim_start()) {
if cap[1].trim().starts_with("name") || cap[1].trim().starts_with("value") {
continue;
}
2020-05-18 10:32:16 +02:00
properties.insert(Property {
2020-05-17 22:02:35 +02:00
key: String::from(cap[1].trim()),
value: String::from(cap[2].trim()),
2020-05-17 21:52:39 +02:00
});
}
for cap in optionals_regex_no_quotes.captures_iter(line.trim_start()) {
if cap[1].trim().starts_with("name") || cap[1].trim().starts_with("value") {
continue;
}
2020-05-18 10:32:16 +02:00
properties.insert(Property {
2020-05-17 22:02:35 +02:00
key: String::from(cap[1].trim()),
value: String::from(cap[2].trim()),
2020-05-17 21:52:39 +02:00
});
2020-05-17 01:17:19 +02:00
}
2020-05-18 10:32:16 +02:00
Ok(Attr {
2020-05-17 21:52:39 +02:00
key,
values,
properties,
2020-05-17 01:17:19 +02:00
})
2020-05-18 10:32:16 +02:00
}