2020-06-08 09:55:42 +02:00
use std ::collections ::HashMap ;
2020-02-20 12:44:21 +01:00
use std ::fs ::File ;
2020-02-19 16:10:18 +01:00
use std ::io ::{ self , Read , Write } ;
2020-06-08 09:55:42 +02:00
use std ::os ::linux ::fs ::MetadataExt ;
2020-06-08 10:07:46 +02:00
use std ::os ::unix ::ffi ::OsStrExt ;
use std ::path ::{ Path , PathBuf } ;
2020-02-19 16:03:42 +01:00
use pxar ::accessor ::Accessor ;
2020-06-08 10:07:46 +02:00
use pxar ::encoder ::{ Encoder , LinkOffset , SeqWrite } ;
2020-04-24 10:41:49 +02:00
use pxar ::Metadata ;
2020-02-19 16:03:42 +01:00
2021-05-17 11:46:50 +02:00
macro_rules ! format_err {
( $( $msg :tt ) + ) = > {
std ::io ::Error ::new ( std ::io ::ErrorKind ::Other , format! ( $( $msg ) + ) )
} ;
}
macro_rules ! bail {
( $( $msg :tt ) + ) = > { {
return Err ( format_err! ( $( $msg ) + ) . into ( ) ) ;
} } ;
}
type Error = Box < dyn std ::error ::Error + Send + Sync + 'static > ;
2020-02-19 16:03:42 +01:00
fn main ( ) -> Result < ( ) , Error > {
2020-02-20 12:44:21 +01:00
let mut args = std ::env ::args_os ( ) ;
let _ = args . next ( ) ;
2020-02-19 16:03:42 +01:00
let cmd = args
. next ( )
. ok_or_else ( | | format_err! ( " expected a command (ls or cat) " ) ) ? ;
let cmd = cmd
. to_str ( )
. ok_or_else ( | | format_err! ( " expected a valid command string (utf-8) " ) ) ? ;
match cmd {
2020-02-20 12:44:21 +01:00
" create " = > return cmd_create ( args ) ,
2020-02-19 16:03:42 +01:00
" ls " | " cat " = > ( ) ,
_ = > bail! ( " valid commands are: cat, ls " ) ,
}
let file = args
. next ( )
. ok_or_else ( | | format_err! ( " expected a file name " ) ) ? ;
2020-06-08 09:55:42 +02:00
let file = std ::fs ::File ::open ( file ) ? ;
let accessor = Accessor ::from_file_ref ( & file ) ? ;
let dir = accessor . open_root ( ) ? ;
2020-02-19 16:03:42 +01:00
2020-02-19 16:10:18 +01:00
let mut buf = Vec ::new ( ) ;
2020-02-19 16:03:42 +01:00
for file in args {
let entry = dir
. lookup ( & file ) ?
. ok_or_else ( | | format_err! ( " no such file in archive: {:?} " , file ) ) ? ;
2020-02-19 16:10:18 +01:00
if cmd = = " ls " {
if file . as_bytes ( ) . ends_with ( b " / " ) {
for file in entry . enter_directory ( ) ? . read_dir ( ) {
println! ( " {:?} " , file ? . file_name ( ) ) ;
}
} else {
println! ( " {:?} " , entry . metadata ( ) ) ;
2020-02-19 16:03:42 +01:00
}
2020-02-19 16:10:18 +01:00
} else if cmd = = " cat " {
buf . clear ( ) ;
2020-06-08 09:55:42 +02:00
let entry = if entry . is_hardlink ( ) {
accessor . follow_hardlink ( & entry ) ?
} else {
entry
} ;
2020-02-19 16:10:18 +01:00
entry . contents ( ) ? . read_to_end ( & mut buf ) ? ;
io ::stdout ( ) . write_all ( & buf ) ? ;
2020-02-19 16:03:42 +01:00
} else {
2020-02-19 16:10:18 +01:00
bail! ( " unknown command: {} " , cmd ) ;
2020-02-19 16:03:42 +01:00
}
}
Ok ( ( ) )
}
2020-02-20 12:44:21 +01:00
2020-06-08 09:55:42 +02:00
#[ derive(Eq, PartialEq, Hash) ]
struct HardLinkInfo {
st_dev : u64 ,
st_ino : u64 ,
}
2020-02-20 12:44:21 +01:00
fn cmd_create ( mut args : std ::env ::ArgsOs ) -> Result < ( ) , Error > {
let file = args
. next ( )
. ok_or_else ( | | format_err! ( " expected a file name " ) ) ? ;
2020-03-04 16:39:07 +01:00
let dir_path = args
. next ( )
. ok_or_else ( | | format_err! ( " expected a directory " ) ) ? ;
if args . next ( ) . is_some ( ) {
bail! ( " too many parameters, there can only be a single root directory in a pxar archive " ) ;
2020-02-20 12:44:21 +01:00
}
2020-06-08 09:55:42 +02:00
let dir_path = Path ::new ( & dir_path ) ;
2020-03-04 16:39:07 +01:00
// we use "simple" directory traversal without `openat()`
let meta = Metadata ::from ( std ::fs ::metadata ( & dir_path ) ? ) ;
2020-06-08 09:55:42 +02:00
let dir = std ::fs ::read_dir ( & dir_path ) ? ;
2020-03-04 16:39:07 +01:00
let mut encoder = Encoder ::create ( file , & meta ) ? ;
2020-06-08 09:55:42 +02:00
add_directory ( & mut encoder , dir , & dir_path , & mut HashMap ::new ( ) ) ? ;
2020-04-24 10:41:49 +02:00
encoder . finish ( ) ? ;
2024-03-19 14:51:08 +01:00
encoder . close ( ) ? ;
2020-02-20 12:44:21 +01:00
Ok ( ( ) )
}
2020-03-04 16:39:07 +01:00
fn add_directory < ' a , T : SeqWrite + ' a > (
encoder : & mut Encoder < T > ,
dir : std ::fs ::ReadDir ,
2020-06-08 09:55:42 +02:00
root_path : & Path ,
hardlinks : & mut HashMap < HardLinkInfo , ( PathBuf , LinkOffset ) > ,
2020-03-04 16:39:07 +01:00
) -> Result < ( ) , Error > {
2020-06-08 09:55:42 +02:00
let mut file_list = Vec ::new ( ) ;
2020-03-04 16:39:07 +01:00
for file in dir {
let file = file ? ;
let file_name = file . file_name ( ) ;
if file_name = = " . " | | file_name = = " .. " {
continue ;
}
2020-06-08 09:55:42 +02:00
file_list . push ( (
file_name . to_os_string ( ) ,
file . path ( ) . to_path_buf ( ) ,
file . file_type ( ) ? ,
file . metadata ( ) ? ,
) ) ;
}
file_list . sort_unstable_by ( | a , b | ( a . 0 ) . cmp ( & b . 0 ) ) ;
2020-03-04 16:39:07 +01:00
2020-06-08 09:55:42 +02:00
for ( file_name , file_path , file_type , file_meta ) in file_list {
println! ( " {:?} " , file_path ) ;
2020-03-04 16:39:07 +01:00
let meta = Metadata ::from ( & file_meta ) ;
if file_type . is_dir ( ) {
2024-03-19 14:51:08 +01:00
encoder . create_directory ( file_name , & meta ) ? ;
2020-06-08 10:07:46 +02:00
add_directory (
2024-03-19 14:51:08 +01:00
encoder ,
2020-06-08 10:07:46 +02:00
std ::fs ::read_dir ( file_path ) ? ,
root_path ,
& mut * hardlinks ,
) ? ;
2024-03-19 14:51:08 +01:00
encoder . finish ( ) ? ;
2020-03-04 16:39:07 +01:00
} else if file_type . is_symlink ( ) {
todo! ( " symlink handling " ) ;
} else if file_type . is_file ( ) {
2020-06-08 09:55:42 +02:00
let link_info = HardLinkInfo {
st_dev : file_meta . st_dev ( ) ,
st_ino : file_meta . st_ino ( ) ,
} ;
if file_meta . st_nlink ( ) > 1 {
if let Some ( ( path , offset ) ) = hardlinks . get ( & link_info ) {
eprintln! ( " Adding hardlink {:?} => {:?} " , file_name , path ) ;
encoder . add_hardlink ( file_name , path , * offset ) ? ;
continue ;
}
}
let offset : LinkOffset = encoder . add_file (
2020-03-04 16:39:07 +01:00
& meta ,
file_name ,
file_meta . len ( ) ,
2020-06-08 09:55:42 +02:00
& mut File ::open ( & file_path ) ? ,
2020-03-04 16:39:07 +01:00
) ? ;
2020-06-08 09:55:42 +02:00
if file_meta . st_nlink ( ) > 1 {
let file_path = file_path . strip_prefix ( root_path ) ? . to_path_buf ( ) ;
hardlinks . insert ( link_info , ( file_path , offset ) ) ;
}
2020-03-04 16:39:07 +01:00
} else {
todo! ( " special file handling " ) ;
}
}
Ok ( ( ) )
}