diff -uNr a/v/v.pl b/v/v.pl --- a/v/v.pl false +++ b/v/v.pl 5fc781391183934125154ce25c932b651b9b49f1a6bed879e124f6f9a2ad7c16543b0763731147634a224832206e94d1fcd3119c074b4d4fc225c4252257032d @@ -0,0 +1,969 @@ +#!/usr/bin/perl + +# (C) 2014 - 2017 The Bitcoin Foundation. You do not have, nor can you ever +# acquire the right to use, copy or distribute this software ; Should you use +# this software for any purpose, or copy and distribute it to anyone or in any +# manner, you are breaking the laws of whatever soi-disant jurisdiction, and +# you promise to continue doing so for the indefinite future. In any case, +# please always : read and understand any software ; verify any PGP signatures +# that you use - for any purpose. + +use strict; + +my $version = "99994 K "; + +my $tdir = get_homedir() . "/.gnupgtmp"; + +my $graph; +my %wot = (); +my %map = (); +my %tmp_map = (); +my %banners = (); +my %desc_map = (); +my %vp_map = (); + +my ($pdir, $sdir, $wdir) = ""; +my (@pfiles, @sfiles, @wfiles) = (); + +sub get_homedir { + my $home = `echo \$HOME`; chomp($home); + return $home; +} + +sub get_pwd { + my $pwd = `pwd`; chomp($pwd); + return $pwd; +} + +sub set_files { + 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); +} + +sub wash { my (@a)=@_; my @b; foreach(@a) {chomp($_); push @b, $_;} return @b; } + +sub init { + my ($URL, $pd, $sd) = @_; + + if($URL && $pd && $sd) { + if(!-d $pd) { `mkdir -p $pd`; sync_all_vpatches($URL, $pd); } + else { print "$pd dir exists! Skipping initial Vpatch sync\n"; } + if(!-d $sd) { `mkdir -p $sd`; sync_seals($URL, $sd); } + else { print "$sd dir exists! Skipping initial Seal sync\n"; } + } +} + +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); + my $res_fp = `gpg --homedir $tdir --logger-fd 1 --fingerprint $keyid`; + $fp = $1 if $res_fp =~ /Key fingerprint = (.*)/; $fp =~ s/\s+//g; + $wot{$uid} = { fp => $fp, banner => $banner }; + } +} + +sub validate_seals { + 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$/) { + if(exists $wot{$seal_signatory}) { + if(not exists $banners{$patch} && $patch ne "") { + $banners{$patch} = $patch; + %sig = (); + } + my $verify = "gpg --homedir $tdir --logger-fd 1 --verify $sdir/$seal " . + "$pdir/$patch"; + my @res = `$verify`; + foreach my $r (@res) { + $fp = $1 if $r =~ /Primary key fingerprint: (.*)/; $fp =~ s/\s+//g; + foreach my $uidkey (sort keys %wot) { + if($wot{$uidkey}{fp} eq $fp) { + $uid = $uidkey; + last; + } + } + } + my $verified = ""; + foreach my $r (@res) { + if($r =~ /Good signature/ && $uid ne "") { + $sig{$wot{$uid}{fp}} = $uid; + $banners{$patch} = {%sig}; + $verified = "true"; + last; + } + } + if($verified ne "true") { + my $border = "-----------------------------------------" . + "-----------------------------------------"; + print "$border\n"; + print "WARNING: $seal is an INVALID seal for $patch!\n"; + my $msg = "Check that this user is in your WoT.\n" . + "Otherwise remove the invalid seal from your SEALS directory."; + print "$msg\n"; + print "$border\n"; + death(); + } + $verified = ""; + } + } + } + } +} + +sub build_map { + my %vpdata; + @pfiles = (); + foreach my $vpatch (keys %banners) { + push @pfiles, $vpatch; + } + foreach my $pfile (@pfiles) { + $map{$pfile} = $pfile; + my @patch = `cat $pdir/$pfile`; + my $src_file = "", my $ante_hash = "", my $desc_hash = ""; + foreach my $p (@patch) { + $src_file = $1, $ante_hash = $2 if $p =~ /^--- (.*) (.*)/; + $desc_hash = $1 if $p =~ /^\+\+\+ .* (.*)/; + if($src_file && $ante_hash && $desc_hash) { + death("$pfile is an invalid vpatch!\n") if $ante_hash eq $desc_hash; + $vpdata{$src_file} = { a => $ante_hash, b => $desc_hash }; + $map{$pfile} = {%vpdata}; + $src_file = "", $ante_hash = "", $desc_hash = ""; + } + } + death("Error! $pfile is an invalid vpatch file.\n") if !%vpdata; + %vpdata = (); + } + return %map; +} + +sub roots { + my @roots = (); + my $is_root = "false"; + foreach my $vpatch (keys %map) { + my %ante = antecedents($vpatch); + if(!%ante) { + foreach my $src_file_name (keys %{$map{$vpatch}}) { + if($map{$vpatch}{$src_file_name}->{a} eq "false") { + $is_root = "true"; + next; + } else { + $is_root = "false"; + last; + } + } + push @roots, $vpatch if $is_root eq "true"; + } + } + return @roots; +} + +sub leafs { + my @leafs; + foreach my $vpatch (keys %map) { + my %desc = descendants($vpatch); + push @leafs, $vpatch if !%desc; + } + return @leafs; +} + +sub traverse_desc { + my (%st) = @_; + my %desc; + foreach my $k (keys %map) { + my @tmp = (); + foreach my $src_file_name (keys %{$map{$k}}) { + my $src_file = $map{$k}{$src_file_name}; + foreach my $sf_name (keys %st) { + my $sf = $st{$sf_name}; + if($src_file_name eq $sf_name && + $src_file->{a} eq $sf->{b} && + $src_file->{a} ne "false") { + push @tmp, $sf_name; + $desc{$k} = [@tmp]; + } + } + } + } + return %desc; +} + +sub traverse_ante { + my (%st) = @_; + my %ante; + foreach my $k (keys %map) { + my @tmp = (); + foreach my $src_file_name (keys %{$map{$k}}) { + my $src_file = $map{$k}{$src_file_name}; + foreach my $sf_name (keys %st) { + my $sf = $st{$sf_name}; + if($src_file_name eq $sf_name && + $src_file->{b} eq $sf->{a} && + $src_file->{b} ne "false") { + push @tmp, $sf_name; + $ante{$k} = [@tmp]; + } + } + } + } + return %ante; +} + +sub search_map { + my ($search_key) = @_; + if(exists $map{$search_key}) { + return %{$map{$search_key}}; + } else { + death("Error! Could not find vpatch \"$search_key\" in $pdir\n"); + } +} + +sub antecedents { + my ($vpatch) = @_; + return traverse_ante(search_map($vpatch)); +} + +sub descendants { + my ($vpatch) = @_; + return traverse_desc(search_map($vpatch)); +} + +sub get_signatories { + my ($vpatch) = @_; + my @sigs; + foreach my $k (keys %banners) { + foreach my $fp (keys %{$banners{$k}}) { + my $uid = $banners{$k}{$fp}; + push @sigs, $uid if $vpatch eq $k; + } + } + push @sigs, "WILD" if !@sigs; + return "(" . join(', ', sort @sigs) . ")"; +} + +sub build_flow { + my @flow = (); + my @roots = roots(); + %tmp_map = %map; + foreach my $root (@roots) { + my %desc = descendants($root); + if(%desc) { + $desc_map{$root} = [keys %desc]; + get_all_descendant_nodes(sort keys %desc); + verify_ante($root); + } else { + $desc_map{$root} = []; + } + } + @flow = toposort(%desc_map); + %map = scrub_map(@flow); + return @flow; +} + +sub scrub_map { + my (@flow) = @_; + foreach my $k (keys %tmp_map) { + if(!grep {/$k/} @flow) { delete $tmp_map{$k} if exists $tmp_map{$k}; } + } + return %tmp_map; +} + +sub verify_ante { + my (@vpatch) = @_; + my %desc = (); + foreach my $vp (@vpatch) { + %desc = descendants($vp); + if(%desc) { + foreach my $a (sort keys %desc) { + check_ante($a); + } + } + } + if(%desc) { + verify_ante(sort keys %desc); + } +} + +sub check_ante { + my ($vp) = @_; + my @curr_node_edge_hashes = (); + my @verified_hashes = (); + my %ante = antecedents($vp); + foreach my $curr_node_edge (keys %{$tmp_map{$vp}}) { + if($tmp_map{$vp}{$curr_node_edge}->{a} ne "false") { + push @curr_node_edge_hashes, $tmp_map{$vp}{$curr_node_edge}->{a}; + } + foreach my $ante_node (keys %ante) { + foreach my $ante_node_edge (keys %{$tmp_map{$ante_node}}) { + next if $curr_node_edge ne $ante_node_edge; + if($tmp_map{$vp}{$curr_node_edge}->{a} eq + $tmp_map{$ante_node}{$ante_node_edge}->{b}) { + push @verified_hashes, $tmp_map{$vp}{$curr_node_edge}->{a}; + } + } + } + } + if(@curr_node_edge_hashes != @verified_hashes) { + remove_desc($vp); + } +} + +sub remove_desc { + my (@vp) = @_; + my %desc = (); + foreach my $v (@vp) { + delete $desc_map{$v} if exists $desc_map{$v}; + delete $tmp_map{$v} if exists $tmp_map{$v}; + my %desc = descendants($v); + next if !%desc; + foreach my $d (keys %desc) { + foreach my $dkeys (keys %desc_map) { + my @tmp = @{$desc_map{$dkeys}}; + if(@tmp) { + my $offset = 0; + foreach my $t (@tmp) { + if($t eq $d) { + splice @tmp, $offset, 1; + } + $offset++; + } + $desc_map{$dkeys} = [@tmp]; + } + } + } + remove_desc(sort keys %desc) if %desc; + } +} + +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} = []; + } + } + return %desc_map; +} + +sub toposort { + my (%unsorted) = @_; + my $acyclic = "", my $flag = "f", my @flow = (); + while(%unsorted) { + $acyclic = "false"; + foreach my $node (sort keys %unsorted) { + my @edges = @{$unsorted{$node}}; + foreach my $edge (@edges) { + $flag = "t" and last if exists $unsorted{$edge}; + } + if($flag ne "t") { + $acyclic = "true"; + delete $unsorted{$node}; + push @flow, $node; + } + $flag = ""; + } + if(!$acyclic eq "true") { + death("Cyclic Graph!\n"); + } + } + return reverse @flow; +} + +sub press_vpatches { + 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; + `rm -rf $press[0]` if -d $press[0]; + `mkdir -p $press[0]`; + foreach my $vp (@flow) { + if($v) { + my @out = `patch -F 0 -E --dir $press[0] -p1 < $pdir/$vp 2>&1`; + print "$vp\n"; + foreach my $o (@out) { print " $o"; } + } else { + `patch -F 0 -E --dir $press[0] -p1 < $pdir/$vp`; + } + %vp_map = (); + verify_pressed($press[0], add_pressed($vp)); + last if $vp eq $press[1]; + } +} + +sub add_pressed { + my ($vpatch) = @_; + $vp_map{$vpatch} = $map{$vpatch}; + return %vp_map; +} + +sub get_filepath { + my ($fp) = @_; + $fp =~ /^[a|b]\/(.*)$/; + return $1; +} + +sub verify_pressed { + my ($press_dir, %vp_map) = @_; + foreach my $vp (keys %vp_map) { + foreach my $src_file_name (keys %{$vp_map{$vp}}) { + my $file_hash = $vp_map{$vp}{$src_file_name}{b}; + if($file_hash ne "false") { + my $fp = $press_dir . "/" . get_filepath($src_file_name); + my $hashed = `sha512sum $fp`; + $hashed =~ /^(.*) .*$/; + my $pressed_hash = $1; + if($file_hash ne $pressed_hash) { + print " File: $fp\n" . + "Expected: $file_hash\n" . + " Actual: $pressed_hash\n"; + death("Pressed file hash did not match expected!\n"); + } + } + } + } +} + +sub sync_seals { + my ($URL, $out) = @_; + if(!-d $out) { `mkdir -p $out`; } + my $wget = "wget -q -r -nd -N --no-parent " . + "--reject \"index.html*\" $URL/v/seals/ -P $out"; + `$wget`; + print "Seal sync complete to \"$out\"\n"; +} + +sub sync_vpatches { + my ($URL, $out, @sync) = @_; + my $wget = ""; + if(!-d $out) { `mkdir -p $out`; } + foreach my $vpatch (@sync) { + $wget = "wget -q -r -nd -N --no-parent " . + "--reject \"index.html*\" $URL/v/patches/$vpatch -P $out"; + `$wget`; + print "$vpatch sync complete to \"$out\"\n"; + } +} + +sub sync_all_vpatches { + my ($URL, $out) = @_; + if(!-d $out) { `mkdir -p $out`; } + my $wget = "wget -q -r -nd -N --no-parent " . + "--reject \"index.html*\" $URL/v/patches/ -P $out"; + `$wget`; + print "Full vpatch sync complete to \"$out\"\n"; +} + +sub sync_everything { + my ($URL, $pd, $sd) = @_; + sync_all_vpatches($URL, $pd); + sync_seals($URL, $sd); +} + +sub build_desc_full_graph { + $graph->set_attributes("graph", + { + font => "monospace", + label => "..::[ The Bitcoin Foundation: Vpatch Graph ]::.." + }); + $graph->set_attributes("node", + { + linkbase => "http://thebitcoin.foundation/v/patches/", + autolink => "name", + color => "blue" + }); + my @roots = roots(); + foreach my $root (@roots) { + my $node = $graph->add_node($root); + $node->set_attribute("title", "Signed By: " . get_signatories($root)); + my %desc = descendants($root); + my @dkeys = keys %desc; + add_desc_edges($root, @dkeys); + my @sn = $graph->source_nodes(); + add_desc_src_files($sn[0]); + } +} + +sub add_desc_edges { + my ($origin, @vpatch) = @_; + my %desc = (); + foreach my $vp (@vpatch) { + %desc = descendants($vp); + my $node = $graph->add_node($vp); + my $sigs = get_signatories($vp); + $node->set_attribute("title", "Signed By: $sigs"); + $graph->add_edge_once($origin, $vp); + if(keys %desc) { + my @dkeys = sort keys %desc; + add_desc_edges($vp, @dkeys); + } + } +} + +sub add_desc_src_files { + my ($node) = @_; + if($node != "") { + my %desc = descendants($node->name()); + my @suc = $node->successors(); + foreach my $s (@suc) { + my $name = $s->name(); + my @edges = $node->edges_to($s); + foreach my $e (@edges) { + $e->set_attribute("title", "[ " . + join('; ', sort @{$desc{$name}}) . " ]"); + add_desc_src_files($s); + } + } + } +} + +sub rank_leafs_gviz { + build_desc_full_graph(); + my $gviz = $graph->as_graphviz(); + my @leafs = leafs(); + $gviz =~ s/GRAPH_0/VPATCH_GRAPH/; + $gviz =~ s/rankdir=LR/rankdir=BT,ranksep=1.00,nodesep=.50/; + $gviz =~ s/}$//; + $gviz .= " { rank=same; "; + foreach my $l (@leafs) { + $gviz .= "\"$l\" "; + } + $gviz .= "}\n}"; + return $gviz; +} + +sub make_tmpdir { + my ($dir) = @_; + `mkdir -p $dir && chmod 0700 $dir` if !-d $dir or die "$dir exists! $!"; +} + +sub death { + my ($msg) = @_; + remove_tmpdir($tdir); + die "$msg"; +} + +sub remove_tmpdir { + my ($dir) = @_; + `rm -rf $dir` if -d $dir; +} + +sub print_graph { + my ($graph, @gv) = @_; + if(!@gv) { + print "$graph\n"; + } elsif($#gv eq 1) { + 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"; + } + print "Executed `dot` and built svg html output file: $gv[1]\n"; + } else { + open FH, ">$gv[0]"; print FH "$graph\n"; + close FH; + print "Printed Graphviz dot file to $gv[0]\n"; + } +} + +sub get_mirrors { + my ($out) = @_; + my @mirror_sigs = (); + if(!-d $out) { `mkdir -p $out`; } + my $wget = "wget -q -r -nd -N --no-parent " . + "--reject \"index.html*\" -A 'mirrors.*' " . + "http://thebitcoin.foundation/v/ -P $out"; + `$wget`; + + my @sigs = `ls $out | sort`; + @sigs = wash(@sigs); + foreach my $sig (@sigs) { + my $who = $1 if $sig =~ /.*\..*\.(.*)\..*/; + my $verify = "gpg --homedir $tdir --logger-fd 1 --verify $out/$sig " . + "$out/mirrors.txt"; + my @res = `$verify`; + foreach my $r (@res) { + if($r =~ /Good signature/) { + push @mirror_sigs, $who; + next; + } + } + } + return @mirror_sigs; +} + +sub print_mirrors { + my ($out) = @_; + 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"; } + } +} + +sub print_roots { + my @r = roots(); + foreach(@r) { + print "Root: $_ " . get_signatories($_) . "\n"; + } +} + +sub print_leafs { + my @l = leafs(); + foreach(@l) { + print "Leaf: $_ " . get_signatories($_) . "\n"; + } +} + +sub print_wot { + my ($finger) = @_; + if(%wot) { + foreach my $uid (sort keys %wot) { + if(!$finger) { + print "$uid:$wot{$uid}{fp}:$wot{$uid}{banner}\n"; + } else { + print "$uid-" . substr($wot{$uid}{fp}, -16) . + ":$wot{$uid}{fp}:$wot{$uid}{banner}\n"; + } + } + } +} + +sub print_antecedents { + my ($vpatch) = @_; + my %ante = antecedents($vpatch); + my $sigs; + foreach my $a (sort keys %ante) { + $sigs = get_signatories($a); + print "Antecedent: $a $sigs [ " . join('; ', sort @{$ante{$a}}) . " ]\n"; + } +} + +sub print_descendants { + my ($vpatch) = @_; + my %desc = descendants($vpatch); + my $sigs; + foreach my $d (sort keys %desc) { + $sigs = get_signatories($d); + print "Descendant: $d $sigs [ " . join('; ', sort @{$desc{$d}}) . " ]\n"; + } +} + +sub print_origin { + my ($hash) = @_; + my $found = "f"; + foreach my $k (keys %map) { + foreach my $sf (keys %{$map{$k}}) { + if($map{$k}{$sf}{b} eq $hash) { + $found = "t"; + print "Origin: $k " . get_signatories($k) . "\n"; + } + } + } + print "No Origin Found by Hash: $hash\n" if $found ne "t"; +} + +sub print_flow { + my (@flow) = @_; + foreach(@flow) { print "$_ " . get_signatories($_) . "\n"; } +} + +sub get_version { + my $version_text = << "END_VERSION_TEXT"; +################################################################################ +# ..::[ The Bitcoin Foundation: V ]::.. # +# # +# Version: $version # +# Author: mod6 # +# Fingerprint: 0x027A8D7C0FB8A16643720F40721705A8B71EADAF # +# # +################################################################################ +END_VERSION_TEXT + return $version_text; +} + +sub short_help { + my ($flag) = @_; + my $short_help = << "END_SHORT_HELP"; +################################################################################ +# ..::[ The Bitcoin Foundation: V ]::.. # +# # +# Version: $version # +# Author: mod6 # +# Fingerprint: 0x027A8D7C0FB8A16643720F40721705A8B71EADAF # +# # +# Usage: v.pl # +# (m | mirrors) () # +# (i | init) (mirror_url) [( )] # +# (wd | wotdir) () # +# (pd | patchdir) () # +# (sd | sealdir) () # +# (w | wot) [ finger ] # +# (r | roots) # +# (l | leafs) # +# (f | flow) # +# (p | press) ( ) # +# (ss | sync-seals) ( ) # +# (sv | sync-vpatches) ( ... ) # +# (sa | sync-all-vpatches) ( ) # +# (se | sync-everything) ( ) # +# (a | ante | antecedents) () # +# (d | desc | descendants) () # +# (o | origin) () # +# (g | graph) ( []) # +# (v | version) # +# (h | ? | help) # +# # +END_SHORT_HELP + my $l = "########################################" . + "########################################\n"; + if($flag) { $short_help .= $l; } + return $short_help; +} + +sub long_help { + print short_help(); + my $long_help = << "END_LONG_HELP"; +# Commands: # +# m, mirrors () # +# Will attempt to retrieve, cryptographically verify and print entries # +# in this list for usage in other commands. Mirrors command my only be # +# invoked by itself. [See: sync-seals, sync-vpatches, sync-everything] # +# # +# i, init () [( )] # +# init should be run as the first command executed with V. init only # +# requires one option: . The and options are # +# optional. Use these if you want to override the default Vpatches and # +# Seals directories in that exact order. # +# # +# Defaults: "~/.wot", "patches" (in present working directory) and # +# "~/.seals" will be used as defaults. WoTs pubkeys can not be sync'd # +# these need to be placed in the WoT directory manually. # +# # +# 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 # +# # +# r, roots # +# Finds the root vpatches (which have no antecedents) and prints them # +# to stdout. # +# # +# l, leafs # +# Finds the leaf vpatches (which have no descendants) and prints them # +# to stdout. # +# # +# f, flow # +# Prints the topological flow of vpatches based on precedence. # +# # +# p, press ( ) # +# Given required options output directory and # +# press will apply vpatches in topologicial order up through the # +# supplied (head) vpatch. Will print patching output if 'verbose' flag # +# is supplied immediately after ( p | press ) option. # +# See: ( f | flow ) to view the topological ordering. # +# # +# ss, sync-seals ( ) # +# Given required options of and output directory # +# will pull all of the available seal files from the given mirror into # +# output directory. # +# # +# sv, sync-vpatches ( ... ) # +# Given required options of and output directory # +# will pull the requested vpatch(s) from the given mirror into output # +# directory. # +# # +# sa, sync-all-vpatches ( ) # +# Given required options of and output directory # +# will pull all available vpatches from the given mirror into output # +# directory. # +# # +# se, sync-everything ( ) # +# Given required options of , , and ; # +# sync-everything will pull all of the available seals and vpatches # +# available at the given mirror. # +# # +# a, ante, antecedents () # +# Finds the antecedents of a given vpatch and prints the results to # +# stdout # +# # +# d, desc, descendants () # +# Finds the descendants of a given vpatch and prints the results to # +# stdout # +# # +# o, origin () # +# Returns the vpatch and signatories where the given hash originated in # +# the source tree. # +# # +# g, graph ( []) # +# Builds a complete directed GraphViz graph of all vpatches from a # +# topological flow and prints the Dot language output to file. If the # +# output_svg_html_file argument is supplied the V will attempt to parse # +# the output_dotfile into an html file; Requires having separately # +# installed 'graphviz' ahead of time. # +# # +# v, version # +# Prints the version message. # +# # +# h, ?, help # +# Prints this full help message. # +# # +################################################################################ +END_LONG_HELP + return $long_help; +} + +sub main { + my $cmd; + if(@ARGV > 0) { $cmd = shift @ARGV; } + else { print "Unknown or missing option!\n"; print short_help("t"); return; } + + my $home = get_homedir(); + 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 || + $cmd =~ /^sa$|^sync-all-vpatches$/i || + $cmd =~ /^sa$|^sync-all-vpatches$/i || + $cmd =~ /^se$|^sync-everything$/i || + $cmd =~ /^a$|^ante$|^antecedents$/i || + $cmd =~ /^d$|^desc$|^descendants$/i || + $cmd =~ /^o$|^origin$/i || + $cmd =~ /^g$|^graph$/i) && !@ARGV) { + print "Option \"$cmd\" requires arguments!\n"; + print short_help("t"); return; + } + + my @tmp = (); + while(@ARGV > 0) { + if($ARGV[0] =~ /^wd$|^wotdir$/) { + shift @ARGV; $wdir = shift @ARGV; next; + } elsif($ARGV[0] =~ /^pd$|^patchdir$/) { + shift @ARGV; $pdir = shift @ARGV; next; + } elsif($ARGV[0] =~ /^sd$|^sealdir$/) { + shift @ARGV; $sdir = shift @ARGV; next; + } else { + push @tmp, shift @ARGV; + } + } + @ARGV = @tmp; + + @wfiles = set_files($wdir); + build_wot(); + + if($cmd =~ /^h$|^help$|^\?$/) { print long_help(); return; } + if($cmd =~ /^i$|^init$/) { + if(@ARGV == 1) { + init(@ARGV, $pdir, $sdir); return; + } elsif(@ARGV == 3) { + $sdir = pop @ARGV; $pdir = pop @ARGV; + init(@ARGV, $pdir, $sdir); return; + } else { + print "Incorrect number of arguments passed to init!\n"; + print short_help("t"); return; + } + } + + 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); + + validate_seals(); + build_map(); + my @flow = build_flow(); + + if ($cmd =~ /^r$|^roots$/) { print_roots(); } + elsif($cmd =~ /^l$|^leafs$/) { print_leafs(); } + elsif($cmd =~ /^f$|^flow$/) { print_flow(@flow); } + elsif($cmd =~ /^p$|^press$/) { + if(@ARGV < 2) { + print "$cmd requires two arguments: ( )\n\n"; + print short_help("t"); } + else { press_vpatches(\@ARGV, @flow); } } + elsif($cmd =~ /^ss$|^sync-seals$/) { + if(@ARGV < 2) { + print "$cmd requires two arguments: ( )\n\n"; + print short_help("t"); } + else { sync_seals(@ARGV); } } + elsif($cmd =~ /^sv$|^sync-vpatches$/) { + if(@ARGV < 3) { + print "$cmd requires three arguments: " . + "( ... )\n\n"; print short_help("t"); } + else { sync_vpatches(@ARGV); } } + elsif($cmd =~ /^sa$|^sync-all-vpatches$/) { + if(@ARGV < 2) { + print "$cmd requires two arguments: " . + "( )\n\n"; print short_help("t"); } + else { sync_all_vpatches(@ARGV); } } + elsif($cmd =~ /^se$|^sync-everything$/) { + if(@ARGV < 3) { + 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 =~ /^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"); } +} + +make_tmpdir($tdir); +main(); +remove_tmpdir($tdir); diff -uNr a/v/v_quick_start.txt b/v/v_quick_start.txt --- a/v/v_quick_start.txt false +++ b/v/v_quick_start.txt 48d9a73b6bc7aebc3a70bfff5bd8568db21a641bd28d0042ceb0e0bcbc397aedf7291670c4b4987ed952471d62f450ffbebd1c9134e70eb040cabfc0637c3b3e @@ -0,0 +1,72 @@ + ..::[ The Bitcoin Foundation: V Quick Start ]::.. + + + Doc Version: 99994 K + Author: mod6 + Fingerprint: 0x027A8D7C0FB8A16643720F40721705A8B71EADAF + +0x00]: Introduction + + Welcome to V! + + This document is intended for the advanced user. This will get you up and + running quickly with V. + +0x01]: What's required? + + 0x010: perl 5.005 or greater + 0x011: cpan | cpanm [CPAN | cpanminus] + 0x012: Graph::Easy [Nice to have, not required] + 0x013: graphviz [Nice to have, not required] + 0x014: gpg [GnuPG] + 0x015: wget + +0x02]: Where do I get V ? + + curl -s http://thebitcoin.foundation/v/V-20170317.tar.gz \ + -o V-20170317.tar.gz + curl -s http://thebitcoin.foundation/v/V-20170317.tar.gz.mod6.sig \ + -o V-20170317.tar.gz.mod6.sig + + gpg --keyserver pool.sks-keyservers.net \ + --recv-keys 0x027A8D7C0FB8A16643720F40721705A8B71EADAF + + gpg --verify V-20170317.tar.gz.mod6.sig V-20170317.tar.gz + + If the signature is good, now extract the archive. + + tar -xf V-20170317.tar.gz + +0x03]: How do I setup V? + + First, set the executable bit on v.pl: + + chmod +x v.pl + + Next, create a WoT directory (V will look for '.wot' in the current working + directory by default): + + mkdir -p .wot + + Then, place WoT pubkeys into the '.wot' directory. Name the pubkey files + as such: .asc + + For example: mod6.asc + +0x04]: It's my first time running V now what? + + First, check the available signed mirrors: + + ./v.pl m mirrors + + Then run the intial sync of the vpatches and seals from a given mirror: + + ./v.pl i http://thebitcoin.foundation + +0x05]: Now that I've sync'd from a mirror, what's next? + + The rest is up to you, either refer to the Users Guide for a detailed + description of each option in V or just execute V and read the long + help message: + + ./v.pl help diff -uNr a/v/v_users_manual.txt b/v/v_users_manual.txt --- a/v/v_users_manual.txt false +++ b/v/v_users_manual.txt 12cca3d90b11ad346b6224e009e263be37361b8eb80e523c3b3f694586ab4e057b07b191ecdabaf4f73db778f1c23d7fc78241f48d1cb5e1de566a7f1beecbb0 @@ -0,0 +1,536 @@ + ..::[ The Bitcoin Foundation: V Users Manual ]::.. + + + Doc Version: 99994 K + Author: mod6 + Fingerprint: 0x027A8D7C0FB8A16643720F40721705A8B71EADAF + +0x00]: Introduction + + Welcome to V! + + This document is intended to be a users manual for mod6's perl implementation + of V. Contained herein the user will find usage explanations and examples of + the many options available. + +0x01]: Getting Started + + [ Check if perl is installed & version ]: + + First, in order to execute the perl version of V, the user must have perl + version 5.005 or greater installed on the local UNIX environment. Perl + typically comes installed on many UNIX environments ahead of time, but just + to be sure, do the following from the command prompt: + + perl --version + + The user should see a lengthy version message that starts with something + like this: + + `This is perl 5, version 18, subversion 2 (v5.18.2) ...' + + If not, please install perl 5.005 or greater before going any further: + + Ubuntu: + apt-get install -y perl + + Gentoo: + emerge =perl-5.20.2.ebuild + + [ Install CPAN ]: + + If it is desired to at all use the graphing feature of V the user will need + to install CPAN [ `cpan` or `cpanm` or on gentoo, `g-cpan` ] so that the + Graph::Easy perl library can be installed. + + Ubuntu: + + apt-get install cpanminus + cpanm -i Graph::Easy + + Gentoo [x86-64]: + + emerge g-cpan + + [ NOTE: Copy the next nine lines exacty as they are presented here and ] + [ then paste them into the shell. ] + +cat < /etc/portage/package.accept_keywords +# required by perl-gcpan/JSON-MaybeXS-1.003005::x-g-cpan +# required by JSON-MaybeXS (argument) +=dev-perl/Test-Without-Module-0.180.0 ~amd64 +# required by perl-gcpan/Cpanel-JSON-XS-3.0115::x-g-cpan +# required by Cpanel-JSON-XS (argument) +=dev-perl/common-sense-3.730.0 ~amd64 +EOF + + g-cpan -i Graph::Easy + + [ Install GnuPG ]: + + The user will need GnuPG installed locally to run V, if you already have + this installed, skip this step. + + Ubuntu: + sudo apt-get install -y gnupg + + [NOTE: 1.4.19 seems to be the oldest portage a recent gentoo ships with)]: + Gentoo: + emerge =gnupg-1.4.19 + + [ Install wget ]: + + Many UNIX systems come with `wget` installed already, if yours does not, + install it: + + Ubuntu: + sudo apt-get install -y wget + + Gentoo: + emerge wget + + [ Install GraphViz ]: + + In addition to Graph::Easy, if you wish to have V automatically build the + SVG graph in an HTML output file for you from the Dot language output file, + also output by V, you need to install GraphViz. + + Ubuntu: + sudo apt-get install -y graphviz + + Gentoo: + emerge -a graphviz + etc-update [ enter -5 when prompted ] + emerge -a graphviz [ Answer Yes at prompt ] + + [ Download V Archive ]: + + Now you must obtain the V archive & signature from: + + http://thebitcoin.foundation/v/V-20170317.tar.gz + http://thebitcoin.foundation/v/V-20170317.tar.gz.mod6.sig + + Then check the PGP signatures supplied: + + wget http://mod6.net/mod6.asc -O mod6.asc + gpg --import mod6.asc + + [ Key Fingerprint: 0x027A8D7C0FB8A16643720F40721705A8B71EADAF ] + + gpg --verify V-20170317.tar.gz.mod6.sig V-20170317.tar.gz + + Then unpack the archive: + + tar -xf V-20170317.tar.gz + + [ Start V for the first time ]: + + First, set the executable bit on v.pl: + + chmod 0755 v.pl + + Before you get any further, create a '.wot' directory (V looks for '.wot' + in the current working directory by default) and populate it with PGP public + keys manually. Name the pubkey files as such: .asc + + For example: mod6.asc + + Now have V sync the required Seals and Patches directories from a signed + mirror. To view the list of mirrors, run the following command, the output + you'll see below. + + ./v.pl m v_mirrors + Mirrors signed by (mod6): + http://thebitcoin.foundation + + As stated from mirrors output we can use URL from The Bitcoin Foundation to + sync V up-to-date with all the latest WoT entries (PGP Public Keys), VPatch + Signatures (Seals) and Vpatches. + + To begin this initial sync do the following: + + ./v.pl i http://thebitcoin.foundation + + +0x01]: V Usage + + At this point the user has installed all of V's requirements, performed a + lookup of available signed mirrors, and sync'd the WoT, Patches and Seals. + + What follows are usages and definitions of V's options with examples. + + A note about option syntax. Any option enclosed with parenthesis ( ) is + required. The | separates any aliases of commands. Square brackets [ ] + denote optional options. Angle brackets < > denote required arguments to + a given command. + + [ m | mirrors ]: + Arguments: + () + + The m or mirrors command is available to connect to the main V site: + http://thebitcoin.foundation Here, V can connect and pull the list of + available mirrors, signed by mirror administrators with L1 trust with + assbot and are in good standing in the WoT. Ask mod6 about this if you + want to run an authorized, signed mirror of V. + + The mirrors command takes one required argument, an output directory for the + list of mirrors and the signatures so V can verify the list. + + Example: + ./v.pl m v_mirrors + Mirrors signed by (mod6): + http://thebitcoin.foundation + + [ i | init ]: + + Arguments & Options: + (mirror_url) [( )] + + The i or init command is intended to be utilized the first time running V + on an environment. It takes one required option, and two optional + options. The required argument is a V mirror URL to sync from. Next, the + user can choose to use the default Patches ('patches' in the current working + directory) and Seals (.seals in the current working directory) directories + by not setting the two or options -- only passing a mirror URL + argument to the init command. If the user wishes to use their own named + directories, simply pass in those arguements in the exact order specified. + + Example: + ./v.pl i http://thebitcoin.foundation + Full vpatch sync complete to "/home/mod6/patches" + Seal sync complete to "/home/mod6/.seals" + + Example: + ./v.pl i http://thebitcoin.foundation wot vpatches seals + Full vpatch sync complete to "vpatches" + Seal sync complete to "seals" + + [ wd | wotdir ]: + + Arguments: + () + + The wd or wotdir command is available to point V at a specific WoT + directory rather than the default: '.wot' (in the current working directory) + There is one required argument: a WoT directory. + + Example case, only mod6.asc exists in wot2: + ./v.pl f wd wot2 + genesis.vpatch (mod6) + bitcoin-asciilifeform.1.vpatch (mod6) + rm_rf_upnp.vpatch (mod6) + ... + + [ pd | patchdir ]: + + Arguments: + () + + The pd or patchdir command is available to point V at a specific patch + directory rather than the default: 'patches', in V's present working + directory. There is one required argument: a vpatches directory. + + Example case, only genesis.vpatch exists in patches2: + ./v.pl f pd patches2 + genesis.vpatch (asciilifeform, mircea_popescu, mod6, trinque) + + [ sd | sealdir ]: + + Arguments: + () + + The sd or sealdir command is available to point V at a specific seal + directory rather than the default: '.seals' (in the current working + directory) + There is one required argument: a seals directory. + + Example case, only seals created by mod6 exist in seal2: + ./v.pl f sd seals2/ + genesis.vpatch (mod6) + bitcoin-asciilifeform.1.vpatch (mod6) + rm_rf_upnp.vpatch (mod6) + ... + + [ w | wot ]: + + Options: + [ finger ] + + The w or wot command will build a list of WoT entities eligable for + creating seals for approved vpatches. If a specific vpatch has not + been signed by a WoT entity, it is considered a 'WILD' vpatch, and will be + ignored. A vpatch must be signed by both Co-Chairs of The Bitcoin Foundation + to be considered approved for release. + + Example: + ./v.pl w + asciilifeform:17215D118B7239507FAFED98B98228A001ABFFC7:Stanislav Datskovskiy + ben_vulpes:4F7907942CA8B89B01E25A762AFA1A9FD2D031DA:Ben Vulpes + mircea_popescu:6160E1CAC8A3C52966FD76998A736F0E2FB7B452:Mircea Popescu (Acest articol are apriori avantajul aliteralitatii alaturi.) + mod6:027A8D7C0FB8A16643720F40721705A8B71EADAF:mod6 (mod6) + punkman:F28E0095843B91CB22E7D65533588BE08B232B13:punkman + trinque:FC66C0C5D98C42A1D4A98B6B42F9985AFAB953C4:Michael Trinque + + ./v.pl w finger + asciilifeform-B98228A001ABFFC7:17215D118B7239507FAFED98B98228A001ABFFC7:Stanislav Datskovskiy + ben_vulpes-2AFA1A9FD2D031DA:4F7907942CA8B89B01E25A762AFA1A9FD2D031DA:Ben Vulpes + mircea_popescu-8A736F0E2FB7B452:6160E1CAC8A3C52966FD76998A736F0E2FB7B452:Mircea Popescu (Acest articol are apriori avantajul aliteralitatii alaturi.) + mod6-721705A8B71EADAF:027A8D7C0FB8A16643720F40721705A8B71EADAF:mod6 (mod6) + punkman-33588BE08B232B13:F28E0095843B91CB22E7D65533588BE08B232B13:punkman + trinque-42F9985AFAB953C4:FC66C0C5D98C42A1D4A98B6B42F9985AFAB953C4:Michael Trinque + + [ r | roots ]: + + The r or roots command takes no options or arguments, unless a different + pd or patchdir is specified. It simply traverses the vpatch directories + contained vpatches and returns any root vpatches found in the topological + order. + + Examples: + ./v.pl r + Root: genesis.vpatch (asciilifeform, mircea_popescu, mod6, trinque) + + Examples: + ./v.pl r pd patches2 + Root: genesis.vpatch (asciilifeform, mircea_popescu, mod6, trinque) + + [ l | leafs ]: + + The l or leafs command takes no options or arguments, unless a different + pd or patchdir is specified. It simply traverses the vpatch directories + contained vpatches and returns any leaf vpatches found in the topological + order. + + Examples: + ./v.pl l + Leaf: asciilifeform_add_verifyall_option.vpatch (asciilifeform, mod6) + Leaf: asciilifeform_maxint_locks_corrected.vpatch (asciilifeform, mod6) + + Example case, only genesis.vpatch exists in patches2: + ./v.pl l pd patches2 + Leaf: genesis.vpatch (asciilifeform, mircea_popescu, mod6, trinque) + + [ f | flow ]: + + The f or flow command prints a topoligical flow of vpatches based on + precedence. By default will look for the 'patches' directory in the present + working directory of V and print the flow to stdout. + + Example: + ./v.pl f + genesis.vpatch (asciilifeform, mircea_popescu, mod6, trinque) + bitcoin-asciilifeform.1.vpatch (asciilifeform, mod6) + rm_rf_upnp.vpatch (asciilifeform, mod6) + bitcoin-asciilifeform.3-turdmeister-alert-snip.vpatch (asciilifeform, mod6) + bitcoin-asciilifeform.2-https_snipsnip.vpatch (asciilifeform, mod6) + bitcoin-asciilifeform.4-goodbye-win32.vpatch (asciilifeform, mod6) + asciilifeform_dnsseed_snipsnip.vpatch (asciilifeform, mod6) + asciilifeform_zap_hardcoded_seeds.vpatch (asciilifeform, mod6) + asciilifeform-kills-integer-retardation.vpatch (asciilifeform, mod6) + asciilifeform_zap_showmyip_crud.vpatch (asciilifeform, mod6) + asciilifeform_dns_thermonyukyoolar_kleansing.vpatch (asciilifeform, mod6) + asciilifeform_and_now_we_have_block_dumper_corrected.vpatch (asciilifeform, mod6) + mod6_fix_dumpblock_params.vpatch (asciilifeform, mod6) + bitcoin-v0_5_3_1-static_makefile_v002.8.vpatch (asciilifeform, mod6) + bitcoin-v0_5_3_1-rev_bump.7.vpatch (asciilifeform, mod6) + asciilifeform_ver_now_5_4_and_irc_is_gone_and_now_must_give_ip.vpatch (asciilifeform, mod6) + asciilifeform_orphanage_thermonuke.vpatch (asciilifeform, mod6) + asciilifeform_and_now_we_have_eatblock.vpatch (asciilifeform, mod6) + bitcoin-v0_5_3-db_config.6.vpatch (asciilifeform, mod6) + asciilifeform_tx-orphanage_amputation.vpatch (asciilifeform, mod6) + asciilifeform_maxint_locks_corrected.vpatch (asciilifeform, mod6) + asciilifeform_lets_lose_testnet.vpatch (asciilifeform, mod6) + asciilifeform_add_verifyall_option.vpatch (asciilifeform, mod6) + + Example case, only genesis.vpatch exists in patches2: + ./v.pl f pd patches2 + genesis.vpatch (asciilifeform, mircea_popescu, mod6, trinque) + + [ p | press ]: + + Arguments: + ( ) + + The p or press command is available to apply vpatches up through in + topological order (see flow command) to an output directory . + There are two required arguments; an output directory for applied patches + and a vpatch as the tip of the tree to press up through. + By default, no output is shown; however if the user supplies `v' or + `verbose' directly after the p or press command then the output from the + patching applcation process will be directed to stdout. + + Example: + ./v.pl p press_output genesis.vpatch + + Example: + ./v.pl p verbose press_verbose genesis.vpatch + genesis.vpatch + patching file bitcoin/.gitignore + patching file bitcoin/COPYING + patching file bitcoin/src/base58.h + patching file bitcoin/src/bignum.h + patching file bitcoin/src/bitcoinrpc.cpp + patching file bitcoin/src/bitcoinrpc.h + patching file bitcoin/src/checkpoints.cpp + patching file bitcoin/src/checkpoints.h + patching file bitcoin/src/crypter.cpp + patching file bitcoin/src/crypter.h + patching file bitcoin/src/db.cpp + patching file bitcoin/src/db.h + patching file bitcoin/src/headers.h + patching file bitcoin/src/init.cpp + ... + + [ ss | sync-seals ]: + + Arguments: + ( ) + + The ss or sync-seals command is avilable to allow the user to sync seal to + an alternative directory. Two arguments are required; the + from which to sync and the alternative or existing seals directory . + + Example: + ./v.pl ss http://thebitcoin.foundation seals2 + Seal sync complete to "seals2" + + [ sv | sync-vpatches ]: + + Arguments: + ( ... ) + + The sv or sync-vpatches command is availabe to allow the user to sync one or + more individual vpatches to a alternative output directory. Three arguments + are required; the from which to sync, the alternative or + existing directory and one or more . + + Example case, sync one vpatch to an existing patches directory: + ./v.pl sv http://thebitcoin.foundation patches genesis.vpatch + genesis.vpatch sync complete to "patches" + + Example case, sync more than one vpatch to an alternative patches directory: + ./v.pl sv http://thebitcoin.foundation patches2 genesis.vpatch rm_rf_upnp.vpatch + genesis.vpatch sync complete to "patches2" + rm_rf_upnp.vpatch sync complete to "patches2" + + [ sa | sync-all-vpatches ]: + + Arguments: + ( ) + + The sa or sync-all-vpatches command is avilable to allow the user to sync + all vpatches from a given mirror to an alternative output directory. Two + arguments are required; the from which to sync and the + alternative or existing patches directory . + + Example case, sync all vpatches to the pre-existing patches directory: + ./v.pl sa http://thebitcoin.foundation patches + Full vpatch sync complete to "patches" + + Example case, sync all vpatches to an alternative patches directory: + ./v.pl sa http://thebitcoin.foundation patches2 + Full vpatch sync complete to "patches2" + + [ se | sync-everything ]: + + Arguments: + ( ) + + The se or sync-everything command is available to allow the user to sync + all patches and seals from a given mirror to either existing or alternative + directories. Three arguments are required; the from which to + sync, the existing or alternative output patches directory , and the + existing or alternative seals directory . + + ./v.pl se http://thebitcoin.foundation patches2 seals2 + Full vpatch sync complete to "patches2" + Seal sync complete to "seals2" + + [ a | ante | antecedents ]: + + Arguments: + () + + The a or ante or antecedents command is available to allow the user to view + the antecedents of a given vpatch. One argument is required, the name of a + . + + Example: + ./v.pl a mod6_fix_dumpblock_params.vpatch + Antecedent: asciilifeform_and_now_we_have_block_dumper_corrected.vpatch \ + (asciilifeform, mod6) [ a/bitcoin/src/bitcoinrpc.cpp ] + + Example case, examine the antecedents of a given vpatch from an alternative + directory: + ./v.pl a asciilifeform_zap_showmyip_crud.vpatch pd patches2 + Antecedent: asciilifeform_zap_hardcoded_seeds.vpatch (asciilifeform, mod6) \ + [ a/bitcoin/src/net.cpp ] + + [ d | desc | descendants ]: + + Arguments: + () + + The d or desc or descendants command is available to allow the user to view + the descendants of a given . One argument is required, the name of + a . + + Example: + ./v.pl d mod6_fix_dumpblock_params.vpatch + Descendant: asciilifeform_and_now_we_have_eatblock.vpatch \ + (asciilifeform, mod6) [ a/bitcoin/src/bitcoinrpc.cpp ] + + Example case, examine the descendants of a given vpatch from an alternative + directory: + ./v.pl d asciilifeform_zap_showmyip_crud.vpatch pd patches2 + Descendant: asciilifeform_dns_thermonyukyoolar_kleansing.vpatch \ + (asciilifeform, mod6) [ a/bitcoin/src/net.cpp ] + + [ o | origin ]: + + Arguments: + () + + The o or origin command is avaliable to allow the user to pass a sha512 + hash and view where the given hash originated in the source tree. + + [ g | graph ]: + + Arguments & Options: + ( []) + + The g or graph command is available to produce a topoligical flow graph in + SVG format. The command has a required argument, the . + The output Dot language file can be used to generate the SVG HTML output + file with the `dot` binary. To produce the output Dot language file, the + perl library Graph::Easy must be installed on the local environment. + The optional command is given as a name for the . + If 'graphviz' is installed on the local environment, then the `dot` binary + should be available for V to invoke and generate the SVG HTML output for + the user automatically. If it is not possible for the user to install + 'graphviz' on the local environment for whatever reason, the Dot language + output file can be copied to a seperate environment where 'graphviz' can be + installed. A command like this is used to generate the SVG HTML output + with the `dot` binary: `dot -Tsvg v-graph.dot > v-graph.html`. + + Example: + ./v.pl g vpatch.dot vpatch-graph.html + Printed Graphviz dot file to vpatch.dot + Executed `dot` and built svg html output file: vpatch-graph.html + + Example case, build a graph from an alternative WoT, Patches and Seals + directory: + ./v.pl g vpatch-graph2.dot vpatch-graph2.html wd wot2 pd patches2 sd seals2 + Printed Graphviz dot file to vpatch-graph2.dot + Executed `dot` and built svg html output file: vpatch-graph2.html + + [ v | version ]: + + The v or version command simply prints the current version of V to stdout. + This command takes no options or arguments. + + [ h | ? | help ]: + + The h or ? or help command will print a longer, more verbose help message + to stdout. This command takes no options or arguments.