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 ;
2020-05-21 17:49:51 +02:00
use std ::fs ::File as OsFile ;
2020-05-17 01:17:19 +02:00
use std ::io ::BufRead ;
use std ::io ::BufReader ;
2020-05-18 16:57:21 +02:00
use failure ::Error ;
2020-05-25 16:13:22 +02:00
use crate ::payload ::Payload ;
use std ::clone ::Clone ;
trait FacetedAction {
// Add a facet to the action if the facet is already present the function returns false.
fn add_facet ( & mut self , facet : Facet ) -> bool ;
// Remove a facet from the action.
fn remove_facet ( & mut self , facet : Facet ) -> bool ;
}
2020-06-04 16:09:09 +02:00
#[ derive(Debug, Default) ]
2020-05-25 16:13:22 +02:00
pub struct Action {
kind : ActionKind ,
2020-06-04 16:09:09 +02:00
payload : Payload ,
2020-05-25 16:13:22 +02:00
properties : Vec < Property > ,
facets : HashSet < Facet > ,
}
2020-06-04 16:09:09 +02:00
impl Action {
fn new ( kind : ActionKind ) -> Action {
Action {
kind ,
payload : Payload ::default ( ) ,
properties : Vec ::new ( ) ,
facets : HashSet ::new ( ) ,
}
}
}
2020-05-25 16:13:22 +02:00
impl FacetedAction for Action {
fn add_facet ( & mut self , facet : Facet ) -> bool {
return self . facets . insert ( facet )
}
fn remove_facet ( & mut self , facet : Facet ) -> bool {
return self . facets . remove ( & facet )
}
}
2020-05-17 21:52:39 +02:00
2020-05-18 14:57:55 +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 > ,
}
2020-05-25 16:13:22 +02:00
impl FacetedAction for Dir {
fn add_facet ( & mut self , facet : Facet ) -> bool {
return self . facets . insert ( facet )
}
fn remove_facet ( & mut self , facet : Facet ) -> bool {
return self . facets . remove ( & facet )
}
}
2020-06-04 16:09:09 +02:00
#[ derive(Debug) ]
2020-05-21 17:49:51 +02:00
pub struct File {
2020-05-25 16:13:22 +02:00
pub payload : Payload ,
2020-05-21 17:49:51 +02:00
pub path : String ,
pub group : String ,
pub owner : String ,
pub mode : String , //TODO implement as bitmask
pub preserve : bool ,
pub overlay : bool ,
pub original_name : String ,
pub revert_tag : String ,
pub sys_attr : String ,
pub properties : Vec < Property > ,
pub facets : HashSet < Facet > ,
}
2020-05-25 16:13:22 +02:00
impl FacetedAction for File {
fn add_facet ( & mut self , facet : Facet ) -> bool {
return self . facets . insert ( facet )
}
fn remove_facet ( & mut self , facet : Facet ) -> bool {
return self . facets . remove ( & facet )
}
}
2020-05-19 22:14:28 +02:00
#[ 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
2020-05-18 14:57:55 +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
}
2020-05-18 14:57:55 +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-21 17:49:51 +02:00
pub files : Vec < File > ,
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-21 17:49:51 +02:00
files : Vec ::new ( ) ,
2020-05-17 01:17:19 +02:00
} ;
}
}
2020-05-25 16:13:22 +02:00
#[ derive(Debug) ]
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 ( ) ;
2020-05-21 17:49:51 +02:00
let f = OsFile ::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-21 17:49:51 +02:00
manifest . files . push ( parse_file_action ( String ::from ( line ) , line_nr ) ? ) ;
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-25 16:13:22 +02:00
fn add_facet_to_action < T : FacetedAction > ( action : & mut T , facet_string : String , line : String , line_nr : usize ) -> Result < ( ) , ManifestError > {
let facet_key = match facet_string . find ( " . " ) {
Some ( idx ) = > {
facet_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 facet_string . find ( " = " ) {
Some ( idx ) = > {
facet_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 ! action . add_facet ( Facet { name : facet_key , value } ) {
return Err ( ManifestError ::InvalidAction { action : line , line : line_nr , message : String ::from ( " double declaration of facet " ) } ) ?
}
Ok ( ( ) )
}
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-21 17:49:51 +02:00
fn clean_string_value ( orig : & str ) -> String {
return String ::from ( orig ) . trim_end ( ) . replace ( & [ '"' , '\\' ] [ .. ] , " " )
}
fn string_to_bool ( orig : & str ) -> Result < bool , String > {
match & String ::from ( orig ) . trim ( ) . to_lowercase ( ) [ .. ] {
" true " = > Ok ( true ) ,
" false " = > Ok ( false ) ,
" t " = > Ok ( true ) ,
" f " = > Ok ( false ) ,
_ = > Err ( String ::from ( " not a boolean like value " ) )
}
}
fn parse_file_action ( line : String , line_nr : usize ) -> Result < File , Error > {
let mut act = File ::default ( ) ;
let regex_set = RegexSet ::new ( & [
r "file ([a-zA-Z0-9]+) " ,
r # "([^ ]+)=([^"][^ ]+[^"])"# ,
r # "([^ ]+)="(.+)"#
] ) ? ;
for ( pat , idx ) in regex_set . matches ( line . trim_start ( ) ) . into_iter ( ) . map ( | match_idx | ( & regex_set . patterns ( ) [ match_idx ] , match_idx ) ) {
let regex = Regex ::new ( & pat ) ? ;
for cap in regex . captures_iter ( line . trim_start ( ) ) {
if idx = = 0 {
act . payload = String ::from ( & cap [ 1 ] ) ;
continue ;
}
let full_cap_idx = 0 ;
let key_cap_idx = 1 ;
let val_cap_idx = 2 ;
match & cap [ key_cap_idx ] {
" path " = > act . path = clean_string_value ( & cap [ val_cap_idx ] ) ,
" owner " = > act . owner = clean_string_value ( & cap [ val_cap_idx ] ) ,
" group " = > act . group = clean_string_value ( & cap [ val_cap_idx ] ) ,
" mode " = > act . mode = clean_string_value ( & cap [ val_cap_idx ] ) ,
" revert-tag " = > act . revert_tag = clean_string_value ( & cap [ val_cap_idx ] ) ,
" original_name " = > act . original_name = clean_string_value ( & cap [ val_cap_idx ] ) ,
" sysattr " = > act . sys_attr = clean_string_value ( & cap [ val_cap_idx ] ) ,
" overlay " = > act . overlay = match string_to_bool ( & cap [ val_cap_idx ] ) {
Ok ( b ) = > b ,
Err ( e ) = > return Err ( ManifestError ::InvalidAction { action : line , line : line_nr , message : e } ) ?
} ,
" preserve " = > act . preserve = match string_to_bool ( & cap [ val_cap_idx ] ) {
Ok ( b ) = > b ,
Err ( e ) = > return Err ( ManifestError ::InvalidAction { action : line , line : line_nr , message : e } ) ?
} ,
_ = > {
let key_val_string = clean_string_value ( & cap [ full_cap_idx ] ) ;
if key_val_string . contains ( " facet. " ) {
2020-05-25 16:13:22 +02:00
match add_facet_to_action ( & mut act , key_val_string , line , line_nr ) {
Ok ( _ ) = > continue ,
Err ( e ) = > return Err ( e ) ? ,
2020-05-21 17:49:51 +02:00
}
}
2020-05-25 16:13:22 +02:00
let mut key = key_val_string . clone ( ) ;
let value = match key . find ( " = " ) {
Some ( idx ) = > {
key . split_off ( idx + 1 )
} ,
None = > return Err ( ManifestError ::InvalidAction { action : line , line : line_nr , message : String ::from ( " no value present for facet " ) } ) ?
} ;
key = key . replace ( " = " , " " ) ;
act . properties . push ( Property { key , value } ) ;
2020-05-21 17:49:51 +02:00
}
}
}
}
Ok ( act )
}
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 ( ) ;
2020-05-21 12:04:47 +02:00
let regex_set = RegexSet ::new ( & [
r # "([^ ]+)=([^"][^ ]+[^"])"# ,
r # "([^ ]+)="(.+)"#
] ) ? ;
for pat in regex_set . matches ( line . trim_start ( ) ) . into_iter ( ) . map ( | match_idx | & regex_set . patterns ( ) [ match_idx ] ) {
let regex = Regex ::new ( & pat ) ? ;
for cap in regex . captures_iter ( line . trim_start ( ) ) {
let full_cap_idx = 0 ;
let key_cap_idx = 1 ;
let val_cap_idx = 2 ;
match & cap [ key_cap_idx ] {
2020-05-25 16:13:22 +02:00
" path " = > act . path = clean_string_value ( & cap [ val_cap_idx ] ) ,
" owner " = > act . owner = clean_string_value ( & cap [ val_cap_idx ] ) ,
" group " = > act . group = clean_string_value ( & cap [ val_cap_idx ] ) ,
" mode " = > act . mode = clean_string_value ( & cap [ val_cap_idx ] ) ,
" revert-tag " = > act . revert_tag = clean_string_value ( & cap [ val_cap_idx ] ) ,
" salvage-from " = > act . salvage_from = clean_string_value ( & cap [ val_cap_idx ] ) ,
2020-05-21 12:04:47 +02:00
_ = > {
2020-05-25 16:13:22 +02:00
let key_val_string = clean_string_value ( & cap [ full_cap_idx ] ) ;
2020-05-21 12:04:47 +02:00
if key_val_string . contains ( " facet. " ) {
2020-05-25 16:13:22 +02:00
match add_facet_to_action ( & mut act , key_val_string , line , line_nr ) {
Ok ( _ ) = > continue ,
Err ( e ) = > return Err ( e ) ? ,
2020-05-21 12:04:47 +02:00
}
2020-05-19 22:14:28 +02:00
}
}
}
}
}
2020-05-21 12:04:47 +02:00
2020-05-19 22:14:28 +02:00
Ok ( act )
}
fn parse_attr_action ( line : String ) -> Result < Attr , Error > {
2020-05-18 14:57:55 +02:00
// 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=(.+)$" ) ? ;
2020-05-18 14:57:55 +02:00
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-21 12:04:47 +02:00
//TODO knock out single quotes somehow without
2020-05-18 14:57:55 +02:00
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
}