diff -uNr a/v/manifest b/v/manifest --- a/v/manifest bf7afc66778d7ca3fed3f76c922621047a149ea92172c8739e046ff924adf6bd4e104ce7858a6f20b298a04de44fded6619746966439f27617af280b7524659a +++ b/v/manifest 19ba07fd25ca643d1b8e2cfebd77d4828e7f5b35b2bf2b6710f4553162d8eb1bc972f2690dd97ef2dc7122d118c4fec2d41501e77509ec690d12ecccb69d0873 @@ -3,3 +3,4 @@ 549967 v_keccak_vtools diana_coman Changed V to use vtools with Keccak hashes (ksum, vpatch). Version number changed to 99992. 600670 v_fixes_vpatch_sync spyked Limit recursion depth of wget to 1 for init command (sync_all_vpatches). Also, record changes in manifest rather than v.pl itself. Version number changed to 99991. 623999 v_strict_headers jfw Prevent false patch header matches by requiring a preceding "diff" line, as seen in phf's vpatch and bvt's vfilter. Also expand manifest to cover all patches and remove file extension from the titles as indicated by the manifest spec. Version 99990. +635743 v_fix_exptimes_paths_etc jfw Eliminate use of external binaries (cat ls sort pwd which) and provide more hygienic directory listing; add missing error handling in build_wot; eliminate some variable regex patterns; avoid slurping full vpatches and verbose output into memory; fix exponential recursion blowup in traverse_press_path and get_all_descendant_nodes; allow the patchdir to be a relative path; replace some numeric indexing with named variables; tweak hash program parsing to not require two spaces; document restricted positioning of global options handled distinctly from commands; make help and version commands work without a wotdir; allow patches and seals to share a directory but require standard extensions (.vpatch .sig); take basenames of patch arguments to allow tab-completeable paths; clean up the tempdir on SIGINT (^C); factor out some repetition; other minor simplifications. Version 99989. diff -uNr a/v/v.pl b/v/v.pl --- a/v/v.pl 5083784b62d8aa9d0e59cdb64b5ff66ffb09c40018253963af3dbb1cba64e468db8987ae9ca7cf77e5469bc5da5f9b8ef425a531f66a009b3e27eb7b50030c58 +++ b/v/v.pl 97b52b43a15557326e585ed3095ff47eb29a01b5bb32c3e60e41f98eb6644408b255642dd90296c51b09a3ecfb699cc9d99fb820c7d0658ee0dc32627d97d606 @@ -10,7 +10,7 @@ use strict; -my $version = "99990 K "; +my $version = "99989 K "; my $tdir = ""; @@ -23,11 +23,12 @@ my %desc_map = (); my %vp_map = (); -my ($pdir, $sdir, $wdir) = ""; -my (@pfiles, @sfiles, @wfiles) = (); +my $pdir = "patches"; +my $sdir = ".seals"; +my $wdir = ".wot"; +my (@pfiles, @sfiles) = (); -my @dep_bins = ("cat", "echo", "gpg", "ls", "mkdir", "vpatch", "pwd", - "rm", "ksum", "sort", "wget", "which", "mktemp"); +my @dep_bins = ("gpg", "mkdir", "vpatch", "rm", "ksum", "wget", "mktemp"); sub check_required_bins { my (@bins) = @_; @@ -38,23 +39,19 @@ } } -sub get_pwd { - my $pwd = `pwd`; chomp($pwd); - return $pwd; -} - -sub set_files { +sub listdir { my ($dir) = @_; - if(!-d $dir) { - my $msg = "$dir directory does not exist.\n" . - "See 'init' or 'sync' commands in 'help'.\n"; - death($msg); - } - my @a = `ls $dir | sort`; - return wash(@a); + death("opendir($dir): $!") if !opendir(my $dh, $dir); + my @entries = sort(readdir($dh)); + closedir($dh); + return @entries; } -sub wash { my (@a)=@_; my @b; foreach(@a) {chomp($_); push @b, $_;} return @b; } +sub basename { + my ($s) = @_; + $s =~ s/.*\///; + return $s; +} sub init { my ($URL, $pd, $sd) = @_; @@ -68,16 +65,21 @@ } sub build_wot { - my $uid, my $banner, my $keyid, my $fp; - foreach my $pubkey (@wfiles) { - my $import = "gpg --homedir $tdir --logger-fd 1 --keyid-format=long " . - "--import $wdir/$pubkey 2> /dev/null"; - my $res = `$import`; - $uid = $1 if $pubkey =~ /(.*)\.asc/; chomp($uid); - $banner = $1 if $res =~ /\"(.*)\"/; chomp($banner); - $keyid = $1 if $res =~ /key (.*)\:/; chomp($keyid); + foreach my $pubkey (grep(/\.asc$/, listdir($wdir))) { + $pubkey =~ /(.*)\.asc$/; + my $uid = $1; + my $res = `gpg --homedir $tdir --logger-fd 1 --keyid-format=long --import $wdir/$pubkey 2>/dev/null`; + if (!($res =~ /key (.*): public key "(.*)"/)) { + death("Failed to load GPG key: $wdir/$pubkey\n"); + } + my $keyid = $1; + my $banner = $2; my $res_fp = `gpg --homedir $tdir --logger-fd 1 --fingerprint $keyid`; - $fp = $1 if $res_fp =~ /Key fingerprint = (.*)/; $fp =~ s/\s+//g; + if (!($res_fp =~ /Key fingerprint = (.*)/)) { + death("Failed to get fingerprint for keyid: $keyid\n"); + } + my $fp = $1; + $fp =~ s/\s+//g; $wot{$uid} = { fp => $fp, banner => $banner }; } } @@ -86,9 +88,10 @@ my $seal_key, my $seal_signatory, my $uid, my $fp, my $patch, my %sig; foreach my $patch (@pfiles) { foreach my $seal (@sfiles) { - $seal_key = $1 if $seal =~ /^(.*)\..*\..*$/; - $seal_signatory = $1 if $seal =~ /^.*\.(.*)\..*$/; - if($patch =~ /^$seal_key$/) { + $seal =~ /^(.*)\.(.*)\.sig$/ or next; + $seal_key = $1; + $seal_signatory = $2; + if($patch eq $seal_key) { if(exists $wot{$seal_signatory}) { if(not exists $banners{$patch} && $patch ne "") { $banners{$patch} = $patch; @@ -141,10 +144,10 @@ } foreach my $pfile (@pfiles) { $map{$pfile} = $pfile; - my @patch = `cat $pdir/$pfile`; + death("open($pdir/$pfile): $!\n") if !open(my $fh, "<", "$pdir/$pfile"); my $in_header = 0; my $src_file = "", my $ante_hash = "", my $desc_hash = ""; - foreach my $p (@patch) { + while (my $p = <$fh>) { if($in_header) { $src_file = $1, $ante_hash = $2 if $p =~ /^--- (.*) (.*)/; $desc_hash = $1 if $p =~ /^\+\+\+ .* (.*)/; @@ -159,6 +162,7 @@ $in_header = 0; } } + close($fh); death("Error! $pfile is an invalid vpatch file.\n") if !%vpdata; %vpdata = (); } @@ -297,15 +301,17 @@ sub traverse_press_path { my ($vp) = @_; - my %ante = antecedents($vp); + if (not exists $ante_map{$vp}) { + my %ante = antecedents($vp); - if(%ante) { - $ante_map{$vp} = [keys %ante]; - foreach my $a (keys %ante) { - traverse_press_path($a); - } - } else { - $ante_map{$vp} = []; + if(%ante) { + $ante_map{$vp} = [keys %ante]; + foreach my $a (keys %ante) { + traverse_press_path($a); + } + } else { + $ante_map{$vp} = []; + } } } @@ -386,19 +392,18 @@ sub get_all_descendant_nodes { my (@vpatch) = @_; - my %desc = (); foreach my $vp (@vpatch) { - %desc = descendants($vp); - if(keys %desc) { - my @dkeys = keys %desc; - $desc_map{$vp} = [@dkeys]; - get_all_descendant_nodes(sort @dkeys); - } - if(!%desc) { - $desc_map{$vp} = []; + if (not exists $desc_map{$vp}) { + my %desc = descendants($vp); + if (%desc) { + my @dkeys = sort keys %desc; + $desc_map{$vp} = [@dkeys]; + get_all_descendant_nodes(@dkeys); + } else { + $desc_map{$vp} = []; + } } } - return %desc_map; } sub toposort { @@ -429,21 +434,25 @@ my ($p, @flow) = @_; my @press = @{$p}; my $v = 1 and shift @press if $press[0] =~ /^v$|^verbose$/i; - death("HEAD: $press[1] not found in flow\n") if !grep /^$press[1]$/, @flow; - my @pp = calc_press_path($press[1]); - death("Directory $press[0] already exists! Halting press.\n") if -d $press[0]; - `mkdir -p $press[0]`; + my $press_dir = $press[0]; + my $head = basename($press[1]); + death("HEAD: $head not found in flow\n") if !grep { $_ eq $head } @flow; + my @pp = calc_press_path($head); + death("Directory $press_dir already exists! Halting press.\n") if -d $press_dir; + `mkdir -p $press_dir`; foreach my $vp (@pp) { + my $cmd = "(cd $press_dir && exec vpatch) < $pdir/$vp"; if($v) { - my @out = `cd $press[0] && vpatch < $pdir/$vp 2>&1`; print "$vp\n"; - foreach my $o (@out) { print " $o"; } + open(my $fh, "-|", "$cmd 2>&1"); + while (my $o = <$fh>) { print " $o"; } + close($fh); } else { - `cd $press[0] && vpatch < $pdir/$vp`; + `$cmd`; } %vp_map = (); - verify_pressed($press[0], add_pressed($vp)); - last if $vp eq $press[1]; + verify_pressed($press_dir, add_pressed($vp)); + last if $vp eq $head; } } @@ -467,7 +476,7 @@ if($file_hash ne "false") { my $fp = $press_dir . "/" . get_filepath($src_file_name); my $hashed = `ksum $fp`; - $hashed =~ /^(.*) .*$/; + $hashed =~ /^([^ ]*)/; my $pressed_hash = $1; if($file_hash ne $pressed_hash) { print " File: $fp\n" . @@ -596,13 +605,12 @@ sub death { my ($msg) = @_; - remove_tmpdir($tdir); + remove_tmpdir(); die "$msg"; } sub remove_tmpdir { - my ($dir) = @_; - `rm -rf $dir` if -d $dir; + `rm -rf $tdir` if -d $tdir; } sub print_graph { @@ -610,15 +618,11 @@ if(!@gv) { print "$graph\n"; } elsif($#gv eq 1) { + check_required_bins("dot"); open FH, ">$gv[0]"; print FH "$graph\n"; close FH; print "Printed Graphviz dot file to $gv[0]\n"; - my @which = `which dot`; chomp($which[0]); - if($which[0] =~ /dot/) { - `$which[0] -Tsvg $gv[0] > $gv[1]`; - } else { - print "`dot` binary not found, check if 'graphviz' is installed\n"; - } + `dot -Tsvg $gv[0] > $gv[1]`; print "Executed `dot` and built svg html output file: $gv[1]\n"; } else { open FH, ">$gv[0]"; print FH "$graph\n"; @@ -636,8 +640,7 @@ "http://thebitcoin.foundation/v/ -P $out"; `$wget`; - my @sigs = `ls $out | sort`; - @sigs = wash(@sigs); + my @sigs = grep(/\.sig$/, listdir($out)); foreach my $sig (@sigs) { my $who = $1 if $sig =~ /.*\..*\.(.*)\..*/; my $verify = "gpg --homedir $tdir --logger-fd 1 --verify $out/$sig " . @@ -658,9 +661,11 @@ my @mirror_sigs = get_mirrors($out); if(-d $out) { - my @mirrors = `cat $out/mirrors.txt`; print "Mirrors signed by (" . join(', ', sort @mirror_sigs) . "):\n"; - foreach(@mirrors) { chomp($_); print "$_\n"; } + my $path = "$out/mirrors.txt"; + death("open($path): $!\n") if ! open(my $fh, "<", "$path"); + while (<$fh>) { chomp($_); print "$_\n"; } + close($fh); } } @@ -761,12 +766,10 @@ # Author: mod6 # # Fingerprint: 0x027A8D7C0FB8A16643720F40721705A8B71EADAF # # # -# Usage: v.pl # +# Usage: v.pl [] [] # +# Commands: # # (m | mirrors) () # # (i | init) (mirror_url) [( )] # -# (wd | wotdir) () # -# (pd | patchdir) () # -# (sd | sealdir) () # # (w | wot) [ finger ] # # (r | roots) # # (l | leafs) # @@ -783,6 +786,10 @@ # (g | graph) ( []) # # (v | version) # # (h | help) # +# Global options: # +# (wd | wotdir) () # +# (pd | patchdir) () # +# (sd | sealdir) () # # # END_SHORT_HELP my $l = "########################################" . @@ -814,19 +821,6 @@ # Set to one of the signed URLs in the PGP signed mirrors # # list at: http://thebitcoin.foundation/v/mirrors.txt # # # -# wd, wotdir () # -# Given the required option , overrides the default wotdir # -# ( .wot in the current working directory ) containing PGP public keys. # -# # -# pd, patchdir () # -# Given required option of , overrides the default # -# patchdir ( ./patches ) containing vpatch files. # -# # -# sd, sealdir () # -# Given required option of , overrides the default sealdir # -# ( .seals in the current working directory ) containing PGP detached # -# signatures of vpatch files. # -# # # w, wot [ finger ] # # Loads PGP public keys from wotdir and prints the WoT to stdout # # # @@ -897,6 +891,23 @@ # h, help # # Prints this full help message. # # # +# Global options (must go between the command name and its specific options): # +# # +# wd, wotdir () # +# Given the required option , overrides the default wotdir # +# ( .wot in the current working directory ) containing PGP public keys. # +# # +# pd, patchdir () # +# Given required option of , overrides the default # +# patchdir ( ./patches ) containing vpatch files. # +# Prior to version 99989 K this had to be an absolute path. # +# # +# sd, sealdir () # +# Given required option of , overrides the default sealdir # +# ( .seals in the current working directory ) containing PGP detached # +# signatures of vpatch files. # +# Prior to version 99989 K this had to be distinct from the patchdir. # +# # ################################################################################ END_LONG_HELP return $long_help; @@ -909,16 +920,8 @@ if(@ARGV > 0) { $cmd = shift @ARGV; } else { print "Unknown or missing option!\n"; print short_help("t"); return; } - my $pwd = get_pwd(); - $wdir = "$pwd/.wot"; - $pdir = "$pwd/patches"; - $sdir = "$pwd/.seals"; - if(($cmd =~ /^m$|^mirrors$/i || $cmd =~ /^i$|^init$/i || - $cmd =~ /^wd$|^wotdir$/i || - $cmd =~ /^pd$|^patchdir$/i || - $cmd =~ /^sd$|^sealdir$/i || $cmd =~ /^p$|^press$/i || $cmd =~ /^ss$|^sync-seals$/i || $cmd =~ /^sv$|^sync-vpatches$/i || @@ -929,7 +932,7 @@ $cmd =~ /^d$|^desc$|^descendants$/i || $cmd =~ /^o$|^origin$/i || $cmd =~ /^g$|^graph$/i) && !@ARGV) { - print "Option \"$cmd\" requires arguments!\n"; + print "Command \"$cmd\" requires arguments!\n"; print short_help("t"); return; } @@ -947,10 +950,11 @@ } @ARGV = @tmp; - @wfiles = set_files($wdir); + if($cmd =~ /^h$|^help$/) { print long_help(); return; } + if($cmd =~ /^v$|^version$/) { print get_version(); return; } + build_wot(); - if($cmd =~ /^h$|^help$/) { print long_help(); return; } if($cmd =~ /^i$|^init$/) { if(@ARGV == 1) { init(@ARGV, $pdir, $sdir); return; @@ -965,10 +969,9 @@ if($cmd =~ /^m$|^mirrors$/) { print_mirrors(@ARGV); return; } if($cmd =~ /^w$|^wot$/) { print_wot(@ARGV); return; } - if($cmd =~ /^v$|^version$/) { print get_version(); return; } - @pfiles = set_files($pdir); - @sfiles = set_files($sdir); + @pfiles = grep(/\.vpatch$/, listdir($pdir)); + @sfiles = grep(/\.sig$/, listdir($sdir)); validate_seals(); build_map(); @@ -977,7 +980,9 @@ if ($cmd =~ /^r$|^roots$/) { print_roots(); } elsif($cmd =~ /^l$|^leafs$/) { print_leafs(); } elsif($cmd =~ /^f$|^flow$/) { print_flow(@flow); } - elsif($cmd =~ /^pp$|^press-path$/) { print_press_path(@ARGV); } + elsif($cmd =~ /^pp$|^press-path$/) { + print_press_path(basename($ARGV[0])); + } elsif($cmd =~ /^p$|^press$/) { if(@ARGV < 2) { print "$cmd requires two arguments: ( )\n\n"; @@ -1003,18 +1008,25 @@ print "$cmd requires three arguments: " . "( )\n\n"; print short_help("t"); } else { sync_everything(@ARGV); } } - elsif($cmd =~ /^a$|^ante$|^antecedents$/) { print_antecedents(@ARGV); } - elsif($cmd =~ /^d$|^desc$|^descendants$/) { print_descendants(@ARGV); } - elsif($cmd =~ /^o$|^origin$/) { print_origin(@ARGV); } + elsif($cmd =~ /^a$|^ante$|^antecedents$/) { + print_antecedents(basename($ARGV[0])); + } + elsif($cmd =~ /^d$|^desc$|^descendants$/) { + print_descendants(basename($ARGV[0])); + } + elsif($cmd =~ /^o$|^origin$/) { + print_origin($ARGV[0]); + } elsif($cmd =~ /^g$|^graph$/) { my $mod = "Graph::Easy"; (my $req = $mod . ".pm") =~ s{::}{/}g; require $req; $graph = $mod->new(); print_graph(rank_leafs_gviz(), @ARGV); } - else { print "Unknown option: \"$cmd\"\n"; print short_help("t"); } + else { print "Unknown command: \"$cmd\"\n"; print short_help("t"); } } make_tmpdir(); +$SIG{INT} = sub { death("Cancelled\n"); }; main(); -remove_tmpdir($tdir); +remove_tmpdir();