#!/bin/sh ############################################################################## # # # This entire file is toast, a program for installing and managing software. # # Copyright (C) 2003-2005 Jacques Frechet. # # Note that this file contains Version 2 of the GNU General Public License, # # which includes its own copyright notice. # # # # For more information on toast, including purpose, usage, licensing, and # # LACK OF ANY WARRANTY, visit http://www.toastball.net/toast/, run "toast" # # without arguments, or refer to the documentation at the end of this file. # # # ############################################################################## exec perl -x $0 ${1+"$@"} echo "Can't find perl in PATH; aborting." >&2 exit 1 ############################################################################## #!perl &the_correct_line_number_for_this_line_is(23); # perl thinks this is line 2... use 5; # oldest version that actually works might be 5.003; not sure.... # allow script to run even in the absence of strict.pm, etc. BEGIN { $^W = 1 } # use warnings BEGIN { $^H |= 0x602 } # use strict # warn if "use warnings" and "use strict" are not both in effect if($^V && %SIG) # test requires perl 5.6.0 with working %SIG (not microperl) { { my($ok); { local $SIG{'__WARN__'} = sub { die }; $ok = !eval('1 + "a"') } $ok || warn("use warnings"); } { eval('$foo = 1') && warn("use strict"); $@ = undef } } ############################################################################## my($rcsid) = q$Id: toast,v 1.432 2005/11/27 07:01:15 zaphod Exp $; $rcsid =~ /^Id: (.+),v (\S+) (\S+ \S+) (\S+) Exp (\S+ )?$/ || die; my($myname, $myversion, $mytimestamp, $myauthor) = ($1, $2, $3, $4); $myversion .= "+" if $5; # if this version may contain changes not in RCS my($myurl) = "http://www.toastball.net/toast/"; my($mycopyright) = "Copyright (C) 2003-2005 Jacques Frechet"; my($genby) = "generated by $myname version $myversion [$myurl]"; $myname eq "toast" && $myauthor eq "zaphod" || die; # avoid accidents w/ CVS ############################################################################## BEGIN { my($linedelta, $filedelta); sub the_correct_line_number_for_this_line_is($) { my($realline) = @_; my($package, $file, $line) = caller(0); $linedelta = $realline - $line; $filedelta = $file; } sub trace(;$) { my($i, $out, $prev) = (shift || 1, "", ""); while(my($package, $file, $line) = caller($i++)) { $line += $linedelta if defined($linedelta) && $file eq $filedelta; $out .= ($prev eq $file ? "/" : $out ? "; $file: " : "$file: ") . $line; $prev = $file; } $out .= " (from #!perl line)" unless defined($linedelta); $out .= " v$myversion"; return $out; } } sub msg($@) { my($default) = shift; my($msg) = join('', @_) || $default; $msg =~ s/\n+$//; return $msg . "\n[" . trace(2) . "]\n"; } sub error(@) { die(msg("assertion failed", @_)); } sub warning(@) { warn(msg("warning", @_)); } ############################################################################## sub true() { 1 } sub false() { "" } sub scalify(@) { return @_ if wantarray; error if scalar(@_) > 1; $_[0] } sub emptytoundef(@) { scalify(map(defined($_) && $_ eq "" ? undef : $_, @_)) } sub undeftoempty(@) { scalify(map(defined($_) ? $_ : "", @_)) } sub firstdef(@) { return $_ foreach grep(defined($_), @_); undef; } sub samelist(\@\@) { my($x, $y) = @_; my($size) = scalar(@$x); return false if $size != scalar(@$y); return true unless $size > 0; for(0..$size-1) { my($vx, $vy) = ($$x[$_], $$y[$_]); my($dx, $dy) = map { defined($_) ? 1 : 0 } ($vx, $vy); return false if $dx != $dy || $dx & $vx ne $vy; } return true; } ############################################################################## sub max(@) { my($result); for(@_) { $result = $_ if !defined($result) || $result < $_; } $result; } sub min(@) { my($result); for(@_) { $result = $_ if !defined($result) || $result > $_; } $result; } sub uniq(@) { my(@list) = @_; my(@result, %seen); for(@list) { next if $seen{$_}; $seen{$_} = true; push(@result, $_); } return @result; } ############################################################################## sub dirname($) { my($arg) = @_; $arg =~ s|/[^/]*$|| ? $arg : "."; } sub basename($) { my($arg) = @_; $arg =~ s|.*/||g; $arg; } sub stripext($) { my($a)=@_;$a=~s/(\.(t|sh)ar(\.g?z)?)?(\.[a-z]\w*)?$//i;$a } sub stripquery($) { my($arg) = @_; $arg =~ s/\?.*$//; $arg; } sub path(@) { my(@args) = @_; @args || error("empty path"); defined($_) || error("undefined path component in @args") for @args; $args[0] = "" if $args[0] eq "/"; my($ret) = join("/", @args); $ret =~ m|//| && error("double slash in path(@args): $ret"); $ret =~ m|/$| && error("final slash in path(@args): $ret"); return $ret; } sub laxpath(@) { my(@args) = @_; defined($_) || error("undefined path component in @args") for @args; length($_) || error("empty path component in @args") for @args; my($ret) = join("/", @args); $ret =~ s|/+|/|g; $ret =~ s|/$||; return $ret; } sub optpath(@) { path(grep { defined($_) && $_ ne "" } @_); } sub unpath($) { my($path) = @_; $path =~ s|^/|| || error; $path =~ m|^/| && error; $path =~ m|/$| && error; $path =~ m|//| && error; return split(/\//, $path); } ############################################################################## sub checkedeval($) { my($code) = @_; my($result) = eval($code); if($@) { local($_) = $code; s/\s+/ /g; error($@, $_); } $result; } ############################################################################## sub safeopen(*$$) { local(*HANDLE) = shift; my($mode, $file) = @_; my($escaped) = "$file\x00"; $escaped = "./$escaped" unless $escaped =~ m!^/!; open(HANDLE, "$mode$escaped") || error("open $file: $!"); binmode(HANDLE) || error("binmode $file: $!"); # perl 5.8.0 utf8 bug } sub whilefile(&$) { my($sub, $file) = @_; local(*FILE, $_); safeopen(*FILE, "<", $file); while(defined($_ = ) && &$sub($_)) { } close(FILE) || error("close $file for read: $!"); !defined($_); } sub readfile($) { my($file) = @_; my(@result); whilefile { push(@result, $_) } $file; @result; } ############################################################################## BEGIN { my($uid, $euid) = ($<, $>); sub superuser() { $uid == 0 || $euid == 0; } } ############################################################################## BEGIN { my(%optdefault) = ( "storedir" => (superuser || !$ENV{HOME}) ? "/toast" : "$ENV{HOME}/.toast", "armdir" => superuser ? "/usr/local" : "armed", "altarmdirs" => "", "username" => "toast", "fallbackuid" => 23, "nice" => 10, "postarmprog" => superuser ? "/sbin/ldconfig" : "", "editprog" => "", "defaultcmd" => "help", "stickyopts" => "reconfigure confappend makeappend", "findsites" => "all", "httpproxy" => exists($ENV{http_proxy}) ? $ENV{http_proxy} : "", "ftpproxy" => exists($ENV{ftp_proxy}) ? $ENV{ftp_proxy} : "", "confappend" => "", "makeappend" => "", "quiet" => "false", "expand" => "true", "autofind" => "true", "autochange" => "false", "autorename" => "true", "autoenv" => "true", "autoclean" => "true", "autopurge" => "false", "autoarm" => "true", "autodisarm" => "true", "autodemolish" => "true", "autoremove" => "false", "crossversion" => "false", "skipmismatched" => "true", "ccache" => "true", "preload" => "true", "strictpreload" => $^O eq "linux" ? "true" : "false", "useflock" => $^O =~ /win/i ? "false" : "true", "reconfigure" => "true", "fixliblinks" => "true", "interactive" => "false", "stoponerror" => "true", "ignorecase" => "true", "showurls" => "true", "showopts" => "true", "infodir" => "true", "xmlcatalog" => "true", "hspkg" => "false", "protect" => "false", "relative" => "false", "debugrewrite" => "false", ); sub envopt($) { $ENV{uc("${myname}_$_[0]")}; } my(%optloaded); sub istrue($) { my($val) = @_; return $val && $val =~ /^(1|true|on|yes|enabled)$/i; } sub isboolean($) { my($val) = @_; return !$val || istrue($val) || $val =~ /^(false|off|no|disabled)$/i; } sub isopt($) { my($name) = @_; return exists($optdefault{$name}); } sub isboolopt($) { my($name) = @_; return false unless isopt($name); my($def) = $optdefault{$name}; return defined($def) && ($def eq "true" || $def eq "false"); } sub checkoptname($) { my($name) = @_; isopt($name) || error("no such option: $name"); } my(%cmdlineopt); sub unloadopts(;$;$) { my($n, $v) = @_; my($key) = undeftoempty($n) . "/" . undeftoempty($v); delete $optloaded{$key}; } sub loadopts(;$;$) { my($n, $v) = @_; my($key) = undeftoempty($n) . "/" . undeftoempty($v); return $optloaded{$key} if exists($optloaded{$key}); my($opts) = $optloaded{$key} = {}; my(@list); push(@list, laxpath($ENV{HOME}, qw[.toast conf])) if exists($ENV{HOME}) && length($ENV{HOME}); push(@list, qw[/toast/conf /etc/toast.conf /usr/local/etc/toast.conf]); @list = path(pkgpath($n, $v), "conf") if defined($n); for(@list) { my($dotfile) = $_; if(-e($dotfile)) { whilefile { s/^\s+//; s/\s+$//; return true if $_ eq "" || /^\#/; /^([^\=]*?)\s*\=\s*(.*)$/ || error("$dotfile: line $.: missing \"=\""); my($name, $val) = ($1, $2); isopt($name) || error("$dotfile: line $.: unknown option name \"$name\""); !isboolopt($name) || isboolean($val) || error("$dotfile: line $.: illegal boolean value: \"$val\""); $opts->{$name} = $val; true; } $dotfile; last unless defined($n); } next unless defined($n); next if (defined($v) ? 1 : 0) eq (&crossversion ? 1 : 0); my($changed); for(map($_ ne "all" ? $_ : grep($_ ne "stickyopts", keys(%cmdlineopt)), map(lc, split(/\W+/, getopt("stickyopts"))))) { my($new) = $cmdlineopt{$_}; next unless defined($new); my($old) = $opts->{$_}; if($new eq getglobalopt($_)) { next unless defined($old); delete($opts->{$_}); $changed = true; } elsif(!defined($old) || $new ne $old) { $new =~ s/\n/ /g; $opts->{$_} = $new; $changed = true; } } if($changed) { my(@lines); for(sort(keys(%{$opts}))) { my($value) = $opts->{$_}; next unless defined($value); push(@lines, "$_=" . (!isboolopt($_) ? $value : $value ? "1" : "0") . "\n"); } writefile($dotfile, @lines) if @lines; rm($dotfile) unless @lines; } } return $opts; } sub parseopt($$) { my($name, $val) = @_; checkoptname($name); if(isboolopt($name)) { error("$name is a boolean option") unless isboolean($val); $val = istrue($val); } else { error("option $name is undefined") unless defined($val); if($name =~ /dir$/ && $val !~ m|^/|) { error("relative path not allowed for storedir: $val") if $name eq "storedir"; $val = path(&storedir, $val); } } return $val; } sub setopt($$) { my($name, $val) = @_; $cmdlineopt{$name} = parseopt($name, $val); } sub getglobalopt($) { my($name) = @_; error unless isopt($name); return firstdef(envopt($name), loadopts()->{$name}, $optdefault{$name}); } my($an, $av); sub setactivepkg(;$;$) { unloadopts($an, $av) if defined($an); ($an, $av) = @_; unloadopts($an, $av) if defined($an); loadopts($an, $av) if defined($an); } sub getopt($) { my($name) = @_; error unless isopt($name); my($nvopts, $nopts); $nvopts = loadopts($an, $av) if defined($av); $nopts = loadopts($an) if defined($an); return parseopt($name, firstdef($cmdlineopt{$name}, $nvopts->{$name}, $nopts->{$name}, getglobalopt($name))); } checkedeval("sub $_() { getopt('$_') }") foreach keys(%optdefault); } sub cmd_getopt(@) { my($name) = shift; error("option name required") unless defined($name); error("invalid option name: $name") unless isopt($name); my(@pkgs) = parse(@_); error("only one package allowed") if scalar(@pkgs) > 1; setactivepkg(); for(@pkgs) { my($n, $v) = @$_; setactivepkg($n, $v); } my($result); eval { $result = getopt($name) }; my($err) = $@; setactivepkg(); die($err) if $err; $result = ($result ? "true" : "false") if isboolopt($name); error unless defined($result); print("$result\n"); } ############################################################################## sub pkgdir() { "pkg" } sub archivedir() { "archive" } sub editdir() { "edit" } sub urlfile() { "url" } sub armdirlink() { "armdir" } sub srcdir() { "src" } sub helperdir() { "helpers" } sub rootdir() { "root" } sub buildlog() { "build.log" } sub brokenlog() { "broken.log" } sub offsuffix() { ".off" } sub tmpsuffix() { ".tmp" } sub baksuffix() { ".orig" } ############################################################################## BEGIN { my($verbosified) = false; sub verbosify() { $verbosified = true } sub say(@) { print(@_) if $verbosified || !quiet } } sub explain(@) { say("# @_\n"); } sub announce(@) { say("@_\n"); } ############################################################################## sub unbuffer(*) { local(*HANDLE) = shift; my($save) = select(HANDLE) || error; $| = 1; select($save) || error; } ############################################################################## sub whiledir(&$) { my($sub, $dir) = @_; local(*DIR); opendir(DIR, $dir) || error("opendir $dir: $!"); my(@list) = sort(grep($_ ne "." && $_ ne "..", readdir(DIR))); closedir(DIR); my($result) = true; for(@list) { last unless $result = &$sub($_); } return !!$result; } sub fordir(&@) { my($sub, $dir) = @_; my($result) = true; whiledir { &$sub(@_) || !($result = false) } $dir; $result; } sub abswhiledir(&$) { my($sub, $dir) = @_; whiledir { &$sub($_ = path($dir, $_)) } $dir; } sub ls($) { my($dir) = @_; my(@result); whiledir { push(@result, $_) } $dir; @result; } sub optls($) { my($dir) = @_; return -e($dir) ? ls($dir) : (); } sub absls($) { my($dir) = @_; map { path($dir, $_) } ls($dir); } sub dfs($&&&) { my($base, $predir, $file, $postdir, $rel) = @_; defined($rel) || -e($base) || -l($base) || error("not found: $base"); local($_) = optpath($base, $rel); (-l || !-d) ? &$file($rel) : &$predir($rel) && (whiledir {&dfs($base, $predir, $file, $postdir, optpath($rel, $_))} $_) && &$postdir($rel); } ############################################################################## sub md(@) { announce("mkdir", @_); mkdir($_, 0777) || error("mkdir $_: $!") foreach @_; true; } sub optmd(@) { foreach(@_) { next if -d; announce("mkdir", $_); if(!mkdir($_, 0777)) { my($err) = $!; -d || error("mkdir $_: $err"); } } true; } ############################################################################## sub mv($$) { my($source, $dest) = @_; announce("mv", $source, $dest); rename($source, $dest) || error("mv $source $dest: $!"); } sub ln($$) { my($source, $target) = @_; announce("ln", "-s", $source, $target); symlink($source, $target) || error("ln -s $source $target: $!"); } sub relln($$) { my($src, $target) = @_; ln($src =~ m|^/| ? findrelpath(dirname($target), $src) : $src, $target); } sub optrelln($$) { my($source, $target) = @_; relln($source, $target) unless -e($target) || -l($target); } ############################################################################## sub safestat($) { my($file) = @_; my(@result) = stat($file); @result || error("stat $file: $!"); @result; } sub getmode($) { (safestat($_[0]))[2] & 07777 } sub chmodimpl($$@) { my($mode, $announce, @files) = @_; $mode &= 01777 & ~umask; my($txtmode) = sprintf("%lo", $mode); @files = grep { getmode($_) != $mode } @files; announce("chmod", $txtmode, @files) if @files && $announce; chmod($mode, $_) || error("chmod $txtmode $_: $!") for @files; true; } sub safechmod($@) { my($mode, @files) = @_; chmodimpl($mode, true, @files); } sub silentchmod($@) { my($mode, @files) = @_; chmodimpl($mode, false, @files); } sub writefilemode($$@) { my($name, $mode, @contents) = @_; local(*FILE); explain("creating $name"); safeopen(*FILE, ">", $name) || error("open $name for write: $!"); print FILE @contents; close(FILE) || error("close $name for write: $!"); safechmod($mode, $name) if $mode; } sub writefile($@) { my($name, @contents) = @_; writefilemode($name, false, @contents); } sub writescript($@) { my($name, @contents) = @_; writefilemode($name, 0777, @contents); } sub samefile($$) { my($a, $b) = @_; my($da, $ia) = safestat($a); my($db, $ib) = safestat($b); $da eq $db && $ia eq $ib; } sub optsamefile($$) { my($a, $b) = @_; my($da, $ia) = stat($a); return false unless defined($da); my($db, $ib) = stat($b); defined($db) && $da eq $db && $ia eq $ib; } sub patch(&$) { my($sub, $file) = @_; my(@contents) = readfile($file); my($patched) = false; local($_); for(@contents) { my($old) = $_; &$sub($_); $patched ||= $old ne $_; } if($patched) { my($mode) = getmode($file); mv($file, addbak($file)); writefilemode($file, $mode, @contents); } return $patched; } sub optpatch(&$) { my($sub, $file) = @_; return -e($file) ? &patch($sub, $file) : false; } ############################################################################## sub silentrm(@) { unlink || error("rm $_: $!") for @_; true; } sub rm(@) { announce("rm", "-f", @_) if @_; silentrm(@_); } sub silentrd(@) { rmdir || error("rmdir $_: $!") for @_; true; } sub rd(@) { announce("rmdir", @_) if @_; silentrd(@_); } sub rmall(@) { announce("rm", "-rf", @_) if @_; dfs ( $_, sub { -w($_) ? true : silentchmod(0777, $_) }, sub { silentrm($_) }, sub { silentrd($_) } ) for @_; true; } sub optrmall(@) { rmall(grep(-e || -l, @_)) } ############################################################################## sub benice() { my($inc) = nice; return $inc eq "0" unless $inc =~ /^-?[1-9][0-9]*$/; eval { setpriority(0, 0, min(getpriority(0, 0) + $inc, 20)) }; } sub safeexec(@) { my(@prog) = @_; { local($^W) = false; # suppress misguided exec failure warning exec(@prog); } error("exec @prog: $!"); } sub runimpl(@) { my(@prog) = @_; announce(@prog); my($result); { local($^W) = false; # suppress misguided exec failure warning $result = system(@prog); } return "exec @prog: $!" unless defined($result) && $result != -1; my($code) = ($? >> 8) & 0xff; my($sig) = $? & 0xff; return "@prog failed (code $code, signal $sig)" if $?; return undef; } sub run(@) { my($msg) = runimpl(@_); error($msg) if defined($msg); return true; } sub optrun(@) { my($msg) = runimpl(@_); explain("warning: $msg; continuing") if defined($msg); return !defined($msg); } sub optcdrunimpl($$@) { my($asroot, $dir, @prog) = @_; announce("(cd $dir; @prog)"); my($pid); if($pid = fork) # parent { waitpid($pid, 0); return !$?; } else # child { defined($pid) || error("fork: $!"); dropprivs() unless $asroot; silentcd($dir); safeexec(@prog); } } sub optcdrun($@) { my($dir, @prog) = @_; optcdrunimpl(true, $dir, @prog); } sub cdrun($@) { my($dir, @prog) = @_; optcdrun($dir, @prog) || error("@prog returned $?"); } sub cdrunnonroot($@) { my($dir, @prog) = @_; optcdrunimpl(false, $dir, @prog) || error("@prog returned $?"); } sub shellescape(@) { my(@words) = @_; for(@words) { error unless defined($_); next unless m![^\w\+,\./:\@-]!; /'/ ? s!([^\w\+,\./:\@-])!\\$1!g : ($_ = "'$_'"); } return join(" ", @words); } sub openprog(*$@) { local(*HANDLE) = shift; my($prog, @args) = @_; announce($prog, @args); @args = shellescape(@args); { local($^W) = false; # suppress misguided exec failure warning if(!open(HANDLE, "$prog @args |")) { explain("failed to exec $prog: $!"); return false; } } binmode(HANDLE) || error("binmode: $!"); # perl 5.8.0 utf8 bug } ############################################################################## sub urlunescape($) { local($_) = @_; s/\%([0-9a-f]{2})/chr(hex($1))/gei; $_; } sub tcpconnect(*$$;$) { local(*HANDLE) = shift; my($host, $port, $ip) = @_; $ip = gethostbyname($host) || error("gethostbyname $host: $!") unless defined($ip); socket(HANDLE, 2, 1, 0) || error("$!"); binmode(HANDLE) || error("binmode: $!"); # perl 5.8.0 utf8 bug connect(HANDLE, pack("Sna4x8", 2, $port, $ip)) || error("connect $host:$port: $!"); unbuffer(HANDLE); return $ip; } sub openhttp(*$;$;$) { local(*HANDLE) = shift; my($url, $method, $proxy) = @_; $method ||= "GET"; $proxy = httpproxy unless defined($proxy); explain("fetching $url"); $url =~ m!^(\w+)://([-\w\.]+)(:(\d+))?(/[\!-\~]*)?$! || error("bad url: $url"); my($proto, $host, $port, $path) = ($1, $2, $4 || 80, $5 || '/'); my($hdrhost) = $port == 80 ? $host : "$host:$port"; if($proxy) { $path = "$proto://$host:$port$path"; $proxy =~ m!^(\w+://)?([-\w\.]+)(:(\d+))?/?$! || error("bad proxy URL: $proxy"); ($host, $port) = ($2, $4 || 8080); } my($uagent) = "$myname/$myversion ($^O; $myurl)"; my($request) = "$method $path HTTP/1.0\r\nHost: $hdrhost\r\n". "User-Agent: $uagent\r\nAccept: */*\r\n\r\n"; tcpconnect(*HANDLE, $host, $port); print HANDLE $request || error("write to $host:$port: $!"); } sub httphead($) { my($url) = @_; local(*HANDLE); openhttp(*HANDLE, $url, "HEAD"); my($result) = join('', ); close(HANDLE) || error; return $result; } sub openhttpurl(*$;$) { local(*HANDLE) = shift; my($url, $proxy) = @_; for(1..5) { openhttp(*HANDLE, $url, undef, $proxy); local($_); $_ = ; s/\r?\n?$//; m|^HTTP/[\w\.]+ [23]0[01237] | || error("$url: $_"); my($redirect); while() { $redirect = $1 if /^location:\s*(\S+)/i; last if /^\r?\n?$/; } return $url unless $redirect; ($url) = linksfromstring("", $url); # *wince* } error("too many HTTP redirects"); } sub ftpcmd(*$;$) { local(*CTRL, $_) = shift; my($cmd, $expected) = @_; print CTRL "$cmd\r\n" || error("error sending ftp command: $!") if $cmd; while() { s/[\r\n]+$//; if(/^(\d{3}) /) { my($response) = $1; error("ftp server: $_") if defined($expected) && $response != $expected; return $_; } } error("lost ftp control connection") unless $cmd =~ /QUIT/; } sub openftpurl(*$) { local(*HANDLE) = shift; my($url) = @_; return openhttpurl(*HANDLE, $url, ftpproxy) if ftpproxy; explain("fetching $url"); $url =~ m!^(\w+)://([-\w\.]+)(:(\d+))?(/[\!-\~]*)?$! || error("bad url: $url"); my($proto, $host, $port, $path) = ($1, $2, $4 || 21, $5 || '/'); error("don't know how to fetch FTP directory listings") if $path =~ m!/$!; local(*CTRL, *DATA); my($ip) = tcpconnect(*CTRL, $host, $port); ftpcmd(*CTRL, undef, 220); ftpcmd(*CTRL, "USER anonymous", 230); ftpcmd(*CTRL, "TYPE I"); my($size) = ftpcmd(*CTRL, "SIZE $path", 213); $size =~ /^\d+\s+(\d+)/ or die("bad SIZE response: $size"); $size = $1; my($pasv) = ftpcmd(*CTRL, "PASV", 227); $pasv =~ /(\d+)\s*,\s*(\d+)\s*\)/ || error("bad PASV response: $pasv"); my($dataport) = $1*256 + $2; tcpconnect(*DATA, $host, $dataport, $ip); my($retr) = ftpcmd(*CTRL, "RETR $path"); $retr =~ /^(\d+) / && $1 <= 150 || error("ftp RETR error: $retr"); my($pid) = open(HANDLE, "-|"); error("fork: $!") unless defined($pid); if($pid) { close(CTRL) || error("close ftp control: $!"); close(DATA) || error("close ftp data: $!"); return true; } binmode(STDOUT) || error("binmode stdout: $!"); # perl 5.8.0 utf8 bug my($buf) = 0; while(sysread(DATA, $buf = "", min(8192, $size), 0)) { print($buf) || error("write stdout: $!"); $size -= length($buf); } error("lost ftp data connection") if $size; close(DATA) || error("close ftp data: $!"); ftpcmd(*CTRL, undef, 226); ftpcmd(*CTRL, "QUIT"); exit(0); error; } sub openfileurl(*$) { local(*HANDLE) = shift; my($url) = @_; $url =~ m!^file://([^/]+)(/.*)$! || error("bad url: $url"); my($host, $path) = ($1, $2); $host eq "localhost" || error("bad file-url hostname: $host"); my($source) = urlunescape($path); explain("reading $source"); safeopen(*HANDLE, "<", $source); } sub opensshurl(*$) { local(*HANDLE) = shift; my($url) = @_; $url =~ m!^ssh://([^/]+)(/.*)$! || error("bad url: $url"); my($userhost, $path) = ($1, $2); $userhost = urlunecape($userhost); $path = urlunescape($path); $userhost =~ /^\-/ && error("bad url: $url"); $path =~ /^\-/ && error("bad url: $url"); openprog(*HANDLE, "ssh", $userhost, "cat", $path); } sub openurl(*$) { local(*HANDLE) = shift; my($url) = @_; $url =~ /^(\w+):/ || error("bad url: $url"); my($proto) = lc($1); local($ENV{http_proxy}) = httpproxy if httpproxy; local($ENV{ftp_proxy}) = ftpproxy if ftpproxy; return openhttpurl(*HANDLE, $url) if $proto eq "http"; $proto eq "file" && openfileurl(*HANDLE, $url) || $proto eq "ssh" && opensshurl (*HANDLE, $url) || openprog(*HANDLE, "wget", "-O-", $url) || openprog(*HANDLE, "GET", $url) || $proto eq "ftp" && openftpurl(*HANDLE, $url) || error("unable to download: $url"); return $url; } sub geturl($$) { my($url, $dest) = @_; my($newurl); eval q{ use LWP::UserAgent; $newurl = false; explain("fetching $url to $dest"); my($ua) = LWP::UserAgent->new; $ua->proxy(["http", "https"], httpproxy) if httpproxy; $ua->proxy("ftp", ftpproxy) if ftpproxy; my($r) = $ua->get($url, ":content_file" => $dest); my($msg) = $r->message; die("unable to fetch $url: $msg\n") if $r->is_error; $newurl = $r->base; } if $url !~ /^(file|ssh)/; error($@) if $@ && defined($newurl); return $newurl if $newurl; if(defined($newurl)) { error($@) unless $newurl; return $newurl; } local(*SOURCE, *DEST); explain("creating $dest"); safeopen(*DEST, ">", $dest); $newurl = openurl(*SOURCE, $url); my($buf, $result); my(@dstat) = stat(DEST); my($bufsize) = $dstat[11] || 4096; while($result = read(SOURCE, $buf, $bufsize)) { (print DEST $buf) || error("write to $dest: $!"); } defined($result) || error("read from $url: $!"); close(DEST) || error("close $dest for write: $!"); close(SOURCE) || error("close $url for read: $!"); return $newurl; } sub linksfromstring($$) { my($html, $url) = @_; $url =~ m!^((\w+:)//[^/]+)([^\?\#]*/)?! || error("bad url: $url"); my($proto, $host, $parent) = ($2, $1, $3); $parent = defined($parent) && $parent ne "" ? "$host$parent" : "$host/"; my(@links); while($html =~ m!\b(href\s*\=\s*\"?|((http|ftp)://))([^\s\>\"]+)!ig) { my($link) = join("", undeftoempty($2, $4)); next if $link =~ /^mailto:/i; $link = $proto . $link if $link =~ m!^//!; $link = $host . $link if $link =~ m!^/!; $link = $parent . $link unless $link =~ m!^(\w+)://!; $link =~ s/\&/\&/g; push(@links, cleanurl($link)); } return @links; } sub linksfromstream(*$) { local(*HANDLE, $_) = shift; my($url) = @_; my(@links); while() { push(@links, linksfromstring($_, $url)); } return uniq(@links); } sub linksfromurl($) { my($url) = @_; return map("file://localhost$_", absls($1)) if $url =~ m!^file://\w+(/.*)/$!; local(*HANDLE); my($newurl) = openurl(*HANDLE, $url); my(@links) = linksfromstream(*HANDLE, $newurl); close(HANDLE) || error("unable to fetch $url"); return @links; } ############################################################################## { my($pwd); sub pwd() { return $pwd if defined($pwd); my($env) = $ENV{PWD}; return $pwd = $env if defined($env) && -d($env) && samefile($env, "."); my($dir, $result) = (".", ""); for(1..1024) { my($parent) = path($dir, ".."); my($dd, $id) = safestat($dir); my($dp, $ip) = safestat($parent); return $pwd = "/$result" if $dd eq $dp && $id eq $ip; error("can't find working directory") if whiledir { my($d, $i) = safestat(path($parent, $_)); my($same) = $d eq $dd && $i eq $id; $result = optpath($_, $result) if $same; !$same; } $parent; $dir = $parent; } error("level>1024 in pwd"); } sub silentcd($) { my($dir) = @_; $ENV{PWD} = $pwd = abspath($dir); chdir($dir) || error("chdir $dir: $!"); return true; } } sub abspath($) { my($path) = @_; return $path if $path =~ m!^/!; $path =~ s!^./!!; $path =~ s!/./!/!g; return path(pwd, $path); } sub findrelpath($$) { my($from, $to) = @_; # allow last component of $to to be missing or not a directory my(@append); if(!-d($to)) { push(@append, basename($to)); $to = dirname($to); } # walk from $to all the way to /, leaving a trail of bread crumbs my($dir, $lastdi, @names, %ditonameidx) = ($to, ""); for(1..1024) { my($d, $i) = safestat($dir); my($di) = "$d $i"; $ditonameidx{$di} = $#names unless exists($ditonameidx{$di}); last if $dir eq "/"; $dir = path($dir, ".."); last if whiledir # figure out how to get down one level from here { my($pd, $pi) = stat(path($dir, $_)); return true unless defined($pd) && "$pd $pi" eq $di; push(@names, $_); return false; } $dir; last if $di eq $lastdi; $lastdi = $di; } # walk up from $from towards / looking for bread crumbs ($dir, $lastdi) = ($from, ""); my(@result); for(1..1024) { my($d, $i) = safestat($dir); my($di) = "$d $i"; last if $di eq $lastdi; $lastdi = $di; if(exists($ditonameidx{$di})) { my($nameidx) = $ditonameidx{$di}; push(@result, reverse(@names[0..$nameidx])); push(@result, @append); return scalar(@result) == 0 ? "." : path(@result); } $dir = path($dir, ".."); push(@result, ".."); } error("no relative path from $from to $to"); } ############################################################################## sub validname($) { my($name) = @_; defined($name) && $name =~ /^[\w\x80-\xff][\w_ -\)\+-\-\x80-\xff]*[\w\x80-\xff]/; } sub validversion($) { my($version) = @_; defined($version) && $version =~ /^[\w\x80-\xff]([\w_ -\)\+-\-\x80-\xff\.]*[\w\x80-\xff])?/; } sub validbuild($) { my($build) = @_; defined($build) && /[1-9][0-9]*/; } ############################################################################## sub pkgpath(;$$$) { my($name, $version, $build) = @_; !defined($build) || ($build =~ /^[1-9]\d*$/) || error("bad build: $build"); optpath(storedir, pkgdir, $name, defined($version) && "v$version", $build); } sub addoff($) { my($path) = @_; error if offsuffix eq ""; return $path . offsuffix; } sub addtmp($) { my($path) = @_; error if tmpsuffix eq ""; return $path . tmpsuffix; } sub addbak($) { my($path) = @_; error if baksuffix eq ""; return $path . baksuffix; } ############################################################################## sub commonlen(@) { my(@sources) = @_; my($firstsource) = $sources[0] || error; my($i) = 0; while(true) { for(@sources) { return $i if length == $i || substr($_, $i, 1) ne substr($firstsource, $i, 1); } $i++; } } sub reverseall(@) { my(@result); push(@result, scalar reverse($_)) foreach @_; @result; } sub collapse(@) { my(@sources) = @_; my($firstsource) = $sources[0]; return $firstsource if scalar(@sources) == 1; my($frontlen) = commonlen(@sources); my($backlen) = commonlen(reverseall(@sources)); my($len) = length($firstsource); return $firstsource if $frontlen + $backlen >= $len || $frontlen + $backlen == 0; substr($firstsource, 0, $frontlen) . substr($firstsource, $len - $backlen); } sub sanitize($) { my($word) = @_; $word =~ s/\:/_/g; $word =~ s/^[\W_]+//; $word =~ s/(source|src)$//i; # e.g. mozilla, libjpeg, minicom, XFree86 $word =~ s/[^a-z0-9\+]+$//i; # allow e.g. gtk+ $word eq "" ? undef : $word; } sub guessnv(@) { my(@urls) = @_; s/^(cvs.*)\#(\d+)$/$1-$2/ for @urls; my($base) = collapse(map(stripext(basename(stripquery($_))), @urls)); $base =~ /^([\w]+[\w\-]*[a-z]+)[-_]v?(\d[\w\.\+\-]+)$/i # Cryptix_src_3-1-1 || $base =~ /^([a-z][a-z_-]*[a-z])\.(\d[\d\.]+)$/i #device-mapper.1.00.07 || $base =~ /^(LVM2)\.(2\.\d[\d\.]+)$/ # LVM2.2.00.08 || $base =~ /^([^-]+)-(.*\d.+)$/ # iputils-ss020124 || $base =~ /^(\D+[^a-z])v(\d.*)$/ # TinyMAZEv2.4a || $base =~ /^(\D+)(\d.*)$/ # fceu019linux || $base =~ /^(.*)()$/; # Xmerge my($name, $version) = ($1, $2); $name =~ s/\./_/g; # helps parse() distinguish filenames from pkg names $name =~ s/::/-/g; # perl module naming convention return (sanitize($name), sanitize($version)); } ############################################################################## sub padfactor() { 40 } sub padsingle($) { my($arg) = @_; $arg = "" unless defined($arg); my($len) = length($arg); $len > padfactor ? $arg : (' ' x (padfactor - $len)) . $arg; } sub padnum($) { my($arg) = @_; my(@post) = $arg =~ s/-?([a-z]+)(\d+)$//i ? ($1, $2) : ("z" x padfactor, 0); my(@n) = split(/\./, $arg); join(' ', map(padsingle($_), @n[0..max(padfactor, $#n)], @post)); } sub cmpab() { my($na, $nb) = map { my($x) = $_; $x =~ s/\d+(\.\d+)*(-?(pre|rc|test)\d+)?/padnum($&)/gie; $x; } ($a, $b); my($lca, $lcb) = (lc($na), lc($nb)); $lca ne $lcb ? $lca cmp $lcb : $na ne $nb ? $na cmp $nb : $a cmp $b; } sub lastitem(@) { @_ ? $_[$#_] : undef; } sub mkcmdline(@) { join(' ', map { my($a) = $_; $a =~ s/[^\w_\-\.\/]/\\$&/g; $a; } @_); } ############################################################################## BEGIN { my($uid, $gid, $usertext); sub initnonroot() { error unless superuser; return if $uid; $usertext = username; my($name); ($name, undef, $uid, $gid) = getpwnam($usertext); return if defined($name); my($tryuid) = fallbackuid; error("no such user: $usertext") unless $tryuid; explain("no such user: $usertext"); for(1..65535) { if(!defined(getpwuid($tryuid))) { explain("falling back on uid $tryuid, gid $tryuid"); ($uid, $gid, $usertext) = ($tryuid, $tryuid, $tryuid); return; } ++$uid; $uid = 1 if $uid == 65536; } error("can't find unused uid"); } sub chownnonroot(@) { my(@files) = @_; return true unless superuser; initnonroot; $uid || error; announce("chown", $usertext, @files); chown($uid, $gid, @files) || error("chown $uid:$gid @files: $!"); } sub belongstononroot($) { my($path) = @_; my(@stats) = safestat($path); initnonroot; $uid || error; return $stats[4] == $uid; } sub dropprivs() { return true unless superuser; initnonroot; explain("running as user $usertext"); $uid || error("refusing to run as root"); $( = $gid; $) = "$gid $gid"; ($<, $>) = ($uid, $uid); $> == $< || error("real and effective UIDs do not match"); $> == $uid || error("uid is not set correctly"); } } ############################################################################## sub which($) { my($prog) = @_; for(split(/:/, $ENV{"PATH"})) { my($path) = path($_, $prog); $path = abspath($path) unless $path =~ m!^/!; return $path if -x($path); } error("can't find $prog in PATH"); } ############################################################################## sub newenvvar($$;$;$) { my($varname, $subdirs, $default, $delim) = @_; $delim ||= ":"; my(@dirs) = map(path(armdir, $_), split(/$delim/, $subdirs)); my($current) = exists($ENV{$varname}) ? $ENV{$varname} : $default; if(defined($current)) { my(%have); $have{$_} = 1 for split(/$delim/, $current); @dirs = grep(!$have{$_}, @dirs); push(@dirs, $current); } return($varname, join($delim, @dirs)); } sub newenv() { my($defaultman) = "/usr/man:/usr/share/man:/usr/local/man:/usr/X11R6/man"; if(!exists($ENV{MANPATH})) # don't bother unless we're going to use it... { my($out) = `man -w 2>/dev/null`; chomp($out) if defined($out); $defaultman = $out if defined($out) && $out =~ m!^/! && $out !~ /\n/; } my($x) = path(qw(etc xml toast-xml-catalog)); my($hspkgdir) = path(qw(etc toast-hs-package.conf)); my(@vars); push(@vars, newenvvar("PATH", "bin")); push(@vars, newenvvar("MANPATH", "man", $defaultman)); push(@vars, newenvvar("INFOPATH", "info", "")) if infodir; push(@vars, newenvvar("CPATH", "include")); push(@vars, newenvvar("LIBRARY_PATH", "lib")); push(@vars, newenvvar("XML_CATALOG_FILES", $x, "/$x", " ")) if xmlcatalog; push(@vars, newenvvar("GHC_PKG_PATH", $hspkgdir)) if hspkg; # there's also a LIBRARY_RUN_PATH or something that affects ld somehow... return @vars; } sub printnewenv() { my(@vars) = newenv; while(@vars) { my($key, $value) = (shift(@vars), shellescape(shift(@vars))); my($export) = exists($ENV{$key}) ? "" : " export $key;"; print("$key=$value;$export\n"); } return @vars; } sub setbuildenv() { my(%vars) = printnewenv; $ENV{$_} = $vars{$_} for keys(%vars); true; } sub env(@) { @_ && error; printnewenv; return true; } ############################################################################## sub yes() { explain("forking yes subprocess"); my($pid) = open(STDIN, "-|"); error("fork: $!") unless defined($pid); return if $pid; print("\n") || last for 1..9999; exit(0) } sub showprebuildinfo($$$) { my($name, $version, $build) = @_; explain("$myname $myversion building " . pkgname($name, $version, $build)); explain("$^X $0 $] $^O"); optrun("uname", "-a"); optrun("perl", "-V"); optrun("printenv") || optrun("env"); explain("argv: $_") for @ARGV; my(@urls) = pkgurls($name, $version); explain("no urls") unless @urls; explain("url: $_") for @urls; my(@archives) = absls(path(pkgpath($name, $version), archivedir)); explain("no archives") unless @archives; optrun("md5sum", @archives) if @archives; true; } sub gettimes() { (time, times) } sub showtimedeltas(@) { my($swall, $suser, $ssys, $scuser, $scsys) = @_; my($ewall, $euser, $esys, $ecuser, $ecsys) = gettimes; my($text) = ""; if(defined($suser)) { $text .= ($euser - $suser + $ecuser - $scuser) . "s user "; $text .= ($esys - $ssys + $ecsys - $scsys ) . "s system "; } $text .= ($ewall - $swall) . "s total"; explain($text); } ############################################################################## # patch files may have leading garbage, so it can help for this to be largish: sub magicbufsize { 1024 } sub magicstring($) { local($_) = @_; return ".Z" if /^\x1f\x9d/; return ".gz" if /^\x1f\x8b/; return ".bz2" if /^BZ/; return ".zip" if /^PK\x03\x04/; return ".rpm" if /^\xed\xab\xee\xdb/; return ".deb" if /^\!\\n/; return ".cpio" if /^07070[a-f\d]{30}/i; return ".tar" if /^[^\x00]+(..)?\x00{5,}[\x00\s\d]{30}/ || /\x00ustar[ \x00]/; return ".shar" if m:^(\#!/bin/sh\n)?\# This is a shell archive:; return ".patch" if /^(diff |\*\*\* |Only in |Common subdirectories: |--- )/m; return ""; } sub magicfile($) { my($file) = $_; local(*FILE); safeopen(*FILE, "<", $file); my($buf); defined(read(FILE, $buf, magicbufsize)) || error("read $file: $!"); my($type) = magicstring($buf); if(!$type && length($buf) >= magicbufsize) { seek(FILE, -22, 2) || error("seek $file: $!"); defined(read(FILE, $buf = "", 22)) || error("read $file: $!"); $type = ".zip" if $buf =~ /^PK\x05\x06/; # end-of-directory signature } close(FILE) || error("close $file for read: $!"); return $type; } sub readstdin($) { my($len) = @_; my($buf) = ""; while($len > 0) { my($result) = sysread(STDIN, $buf, $len, length($buf)); defined($result) || error("read stdin: $!"); last unless $result; $len -= $result; } $buf; } sub skipstdin($) { my($len) = @_; while($len > 0) { my($chunk) = min($len, 8192); error("unexpected eof") unless length(readstdin($chunk)) == $chunk; $len -= $chunk; } return true; } sub forkstdin() { my($pid) = open(STDIN, "-|"); defined($pid) || error("fork stdin: $!"); binmode(STDIN) || error("binmode stdin: $!"); # perl 5.8.0 utf8 bug return $pid; } sub dumpstdin($) { my($buf) = @_; binmode(STDOUT) || error("binmode stdout: $!"); # perl 5.8.0 utf8 bug print($buf); print($buf) while sysread(STDIN, $buf = "", 8192, 0); exit(0); } sub extractstdin($); sub autoextractstdin(;$) { my($buf) = @_; $buf = readstdin(magicbufsize) unless defined($buf); my($type) = magicstring($buf); error("unknown file type: " . unpack("H*", $buf)) unless $type; forkstdin ? extractstdin($type) : dumpstdin($buf); } sub applypatchfromstdin() { my($subdir); my($ok) = whiledir { !defined($subdir) && ($subdir = $_) } "."; safeexec(qw[patch -p1 -d], $subdir) if $ok && $subdir; safeexec(qw[patch -p0]); } sub rpmextractstdin() { my($lead) = readstdin(96); $lead =~ /^\xed\xab\xee\xdb[\x03\x04]/ || error("not rpm v3 or v4"); my($pad) = 0; my($hdr); while(magicstring($hdr = readstdin($pad + 16)) !~ /^\..z/i) { error("short rpm: " . unpack("H*", $hdr)) unless length($hdr) == $pad + 16; my($magic, $zero, $sections, $bytes) = unpack("x$pad N4", $hdr); $magic == 0x8eade801 || error(sprintf("bad rpm header: %08x", $magic)); skipstdin($bytes + 16*$sections); $pad = (8 - $bytes%8) % 8; } autoextractstdin($hdr); error; } sub debextractstdin() { my($magic) = readstdin(8); error("bad deb magic: " . unpack("H*", $magic)) unless $magic eq "!\n"; for(;;) { my($hdr) = readstdin(60); error("bad deb hdr: " . unpack("H*", $hdr)) unless $hdr =~ / (\d+) +\`$/; my($len) = $1; skipstdin(1) if $hdr =~ s/^\n//; autoextractstdin if $hdr =~ /^data\./; # autoextractstdin doesn't return skipstdin($len); } error; } BEGIN # built-in gunzip (zcat) { my($inbuf, $inlen, $outbuf, $written); my(@llens) = qw[3 4 5 6 7 8 9 10 11 13 15 17 19 23 27 31 35 43 51 59 67 83 99 115 131 163 195 227 258]; my(@lbits) = qw[0 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 5 5 5 5 0]; my(@dists) = qw[1 2 3 4 5 7 9 13 17 25 33 49 65 97 129 193 257 385 513 769 1025 1537 2049 3073 4097 6145 8193 12289 16385 24577]; my(@dbits) = qw[0 0 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 11 12 12 13 13]; my(@order) = qw[16 17 18 0 8 7 9 6 10 5 11 4 12 3 13 2 14 1 15]; sub zread($) { my($len) = @_; my($ofs, $buf) = 0; while($len > 0) { my($ret) = read(STDIN, $buf, $len, $ofs); defined($ret) or die("read: $!"); die("unexpected EOF") if $ret == 0; $ofs += $ret; $len -= $ret; } return $buf; } sub readbit() { ($inbuf, $inlen) = (unpack("C", zread(1)), 8) unless $inlen; return ($inbuf & (1 << (8 - $inlen--))) ? 1 : 0; } sub nextbyte() { readbit && die("bad pad bit") while $inlen; } sub readbits($) { my($result, $bits) = (0, @_); $result |= readbit << $_ for(0..$bits-1); return $result; } sub inithuff(@) { my(@lens) = @_; my($maxlen) = 0; $_ > $maxlen && ($maxlen = $_) for @lens; my(%result) = (maxlen => $maxlen); my($code, $len) = 0; for $len (1..$maxlen) { $code <<= 1; for(0..$#lens) { $result{sprintf("\%0${len}b", $code++)} = $_ if $lens[$_] == $len; } } return %result; } sub readhuff(%) { my(%map) = @_; my($maxlen) = $map{"maxlen"}; my($bits) = ""; while(length($bits) < $maxlen) { $bits .= readbit; return $map{$bits} if exists($map{$bits}); } die("bad huffman code: $bits"); } sub zwrite($) { my($data) = @_; $written += length($data); $outbuf .= $data; substr($outbuf, 0, length($outbuf) - 32768) = "" if length($outbuf) > 65536; print($data); } sub zcat() { my($id1, $id2, $cm, $flg) = unpack("C4", zread(10)); die("bad magic: $id1 $id2") unless $id1 == 31 && $id2 == 139; die("bad cm: $cm") unless $cm == 8; die("bad flags: $flg") if $flg & 0xe0; zread(unpack("v", zread(2))) if $flg & 4; # FEXTRA if($flg & 8) { while(zread(1) ne "\x00") { } } # FNAME if($flg & 16) { while(zread(1) ne "\x00") { } } # FCOMMENT zread(2) if $flg & 2; # FHCRC ($written, $outbuf) = (0, ""); my($bfinal); do { $bfinal = readbit; my($btype) = readbits(2); if($btype == 3) { die("bad btype"); } elsif($btype == 0) # no compression { nextbyte; my($len, $nlen) = unpack("v2", zread(4)); die("bad nlen: $len $nlen") if $nlen != (65535 - $len); zwrite(zread($len)); } else { my(%llmap, %dmap, $ll); if($btype == 1) # fixed Huffman { $llmap{sprintf('%07b', $_)} = $_ + 256 for 0..23; $llmap{sprintf('%08b', $_ + 48)} = $_ for 0..143; $llmap{sprintf('%08b', $_ + 192)} = $_ + 280 for 0..7; $llmap{sprintf('%09b', $_ + 400)} = $_ + 144 for 0..111; $llmap{"maxlen"} = 9; $dmap{sprintf('%05b', $_)} = $_ for 0..29; $dmap{"maxlen"} = 5; } else # dynamic Huffman { my($hlit, $hdist, $hclen) = map(readbits($_), 5, 5, 4); my(@rawclens, @clens, @lens); push(@rawclens, readbits(3)) for 1..4+$hclen; $clens[$order[$_]] = $rawclens[$_] || 0 for 0..$#order; my(%cmap) = inithuff(@clens); while(scalar(@lens) < $hlit + $hdist + 258) { my($code) = readhuff(%cmap); if($code == 16) { die("no last code") unless @lens; my($last) = $lens[$#lens]; push(@lens, $last) for 1..3+readbits(2); } elsif($code == 17) { push(@lens, 0) for 1..3+readbits(3); } elsif($code == 18) { push(@lens, 0) for 1..11+readbits(7); } else { push(@lens, $code); } } %llmap = inithuff(@lens[0..$hlit+256]); %dmap = inithuff(@lens[$hlit+257..$hlit+$hdist+257]); } while(256 != ($ll = readhuff(%llmap))) { if($ll < 256) { zwrite(chr($ll)); } else { my($i) = $ll - 257; my($len) = $llens[$i] + readbits($lbits[$i]); my($dist) = $dists[$i = readhuff(%dmap)] + readbits($dbits[$i]); zwrite(substr($outbuf, length($outbuf) - $dist, 1)) for(1..$len); } } } } until($bfinal); my($crc32, $isize) = unpack("V2", zread(8)); die("bad isize: $isize != $written") unless $isize == $written; } } sub zfork() { explain("falling back on built-in gunzip"); return true if forkstdin; zcat; exit(0); error; } sub extractstdin($) { my($type) = @_; safeexec("tar", "xf", "-") if $type eq ".tar"; safeexec("cpio", "-di") if $type eq ".cpio"; safeexec("/bin/sh") if $type eq ".shar"; applypatchfromstdin if $type eq ".patch"; rpmextractstdin if $type eq ".rpm"; debextractstdin if $type eq ".deb"; if($type =~ /^\.(Z|gz|bz2)$/) { my($prog) = $type eq ".bz2" ? "bunzip2" : "gunzip"; open(STDIN, "$prog |") || $prog eq "gunzip" && zfork || error("$prog: $!"); binmode(STDIN) || error("binmode stdin: $!"); # perl 5.8.0 utf8 bug autoextractstdin; error; } error("unable to handle $type data in this context") if $type; error("unknown file type"); } sub extractfile($$) { my($infile, $outdir) = @_; explain("extracting $infile"); my($type) = magicfile($infile); my($pid) = fork; error("fork: $!") unless defined($pid); if($pid) { waitpid($pid, 0); error("extract subprocess returned $?") if $? && !($? == 256 && $type eq ".zip"); } else { silentcd($outdir); safeexec("unzip", "-qo", $infile) if $type eq ".zip"; safeopen(*STDIN, "<", $infile); extractstdin($type); error; } } sub extractname($) { my($file) = @_; my($arg) = local($_) = $file; s/\.tgz$/.tar.gz/i; my($cmd, $ok) = ("", false); ($cmd, $arg) = ($cmd . "gunzip < $arg | ", "-") if s/\.gz$//i; ($cmd, $arg) = ($cmd . "bunzip2 < $arg | ", "-") if s/\.bz2$//i; ($cmd, $ok) = ($cmd . "tar tf $arg | ", true) if s/\.tar$//i; ($cmd, $ok) = ("unzip -Z1 $arg | ", true) if $arg eq $_ && s/\.zip$//i; return undef unless $ok; $cmd .= "head"; explain($cmd); local(*SAVE); open(SAVE, "<&STDIN") || error("save stdin: $!"); safeopen(*STDIN, "<", $file); my($result) = `$cmd`; open(STDIN, "<&SAVE") || error("restore stdin: $!"); $result = undeftoempty($result); $result =~ s/[\r\n].*//; $result =~ s|/+$||; emptytoundef($result); } ############################################################################## sub allnames(;$) { @_ && defined($_[0]) ? @_ : sort cmpab optls(pkgpath); } sub allversions($;$) { my($name) = shift; @_ && defined($_[0]) ? @_ : sort cmpab grep { s/^v// } ls(pkgpath($name)); } sub allbuilds($$;$) { my($name, $version) = (shift, shift); @_ && defined $_[0] ? @_ : sort { $a<=>$b } grep { /^[1-9]\d*$/ } ls(pkgpath($name, $version)); } ############################################################################## sub isname($) { my($name) = @_; defined($name) && -d(pkgpath($name)); } sub isversion($$) { my($name, $version) = @_; defined($name) && defined($version) && -d(pkgpath($name, $version)); } sub isbuild($$$) { my($name, $version, $build) = @_; defined($name) && defined($version) && defined($build) && -d(pkgpath($name, $version, $build)); } ############################################################################## sub latestversion($;$) { my($name, $version) = @_; lastitem(allversions($name, $version)); } sub latestbuilt($$;$) { my($name, $version, $build) = @_; defined($name) || error; defined($version) || error; return undef unless isversion($name, $version); for $build (reverse(allbuilds($name, $version, $build))) { return $build if isbuiltmatch($name, $version, $build); } return undef; } ############################################################################## sub whilebuild(&@) { my($sub, $name, $version, $build, @urls) = @_; for $name (allnames($name)) { for $version (allversions($name, $version)) { for $build (allbuilds($name, $version, $build)) { return false unless &$sub($name, $version, $build); } } } return true; } ############################################################################## sub isadded($$) { my($name, $version) = @_; return -d(pkgpath($name, $version)); } sub isstored($$) { my($name, $version) = @_; return -d(path(pkgpath($name, $version), archivedir)); } sub isbuilt(@) { my($name, $version, $build) = @_; return isadded($name, $version) && !whilebuild { my($name, $version, $build) = @_; return !-f(path(pkgpath($name, $version, $build), buildlog)); } @_; } sub isbroken($$$) { my($name, $version, $build) = @_; return -f(path(pkgpath($name, $version, $build), brokenlog)); } sub isclean($$$) { my($name, $version, $build) = @_; return !-d(path(pkgpath($name, $version, $build), srcdir)); } sub isbuildarmedin($$$$) { my($armdir, $name, $version, $build) = @_; $build || error; my($rootdir) = path(pkgpath($name, $version, $build), rootdir); return -d($rootdir) && !dfs ( $rootdir, sub { true }, sub { my($rel) = @_; my($armfile) = path($armdir, $rel); while(-e($armfile) || -l($armfile)) { return false if optsamefile($_, $armfile); $armfile = addoff($armfile); } return true; }, sub { true } ); } sub allarmdirs() { my(@armdirs, %seendi); for (armdir, split(/:/, altarmdirs)) { my($armdir) = m!^/! ? $_ : path(storedir, $_); my($device, $inode) = stat($armdir); next unless defined($device); next unless -d(_); my($di) = "$device $inode"; next if exists($seendi{$di}); $seendi{$di} = true; push(@armdirs, $armdir); } return @armdirs; } sub isarmed(@) { return !whilebuild { my($name, $version, $build) = @_; for (allarmdirs) { return false if isbuildarmedin($_, $name, $version, $build); } return true; } @_; } sub isarmedmatch(@) { return isarmed(@_) if !skipmismatched; return !whilebuild { my($name, $version, $build) = @_; return !isbuildarmedin(armdir, $name, $version, $build); } @_; } sub ismismatched($$$) { my($name, $version, $build) = @_; $build || error; my($armdirlink) = path(pkgpath($name, $version, $build), armdirlink); my($armdirisdir, $linkisdir) = (-d(armdir), -d($armdirlink)); return !samefile($armdirlink, armdir) if $armdirisdir && $linkisdir; return true if !$armdirisdir && $linkisdir; my($target) = readlink($armdirlink); return false unless defined($target); return $target ne armdir; } sub isbuiltmatch(@) { return isbuilt(@_) if !skipmismatched; my($name, $version, $build) = @_; return !whilebuild { my($name, $version, $build) = @_; return !isbuilt(@_) || ismismatched($name, $version, $build); } @_; } ############################################################################## sub lookslikepkgurl($;$;$) { my($url, $name, $version) = @_; return false unless $url =~ m!^(http|ftp)://.*/[^/]+\.[a-z][^/]+$!i; return false if $url =~ m/\#/; my($noquery) = stripquery($url); return false unless $noquery =~ m!\.\w+$!; return false if $noquery =~ m!\.(html?|php|txt|diff|gif|jpg|png|css|asc|sum|sig|sign|lsm|md5)(\.\w\w)?$!i; return true unless defined($name); my($basename) = basename($noquery); return false unless $basename =~ /\Q$name\E/i; return true unless defined($version); return false unless $basename =~ /\Q$version\E/i; return true; } sub findnewpkg($$) { my($name, $version) = @_; defined($name) || error; $name =~ /^[\w-]+$/ || error("invalid package name: $name"); my($lcname) = lc($name); my($perlname) = $name; $perlname =~ s/-/::/g; my(@sites) = ( "freshmeat" => "http://freshmeat.net/projects-xml/$lcname/$lcname.xml", "sourceforge" => "http://prdownloads.sourceforge.net/$lcname/", "gnu" => "http://ftp.gnu.org/gnu/$lcname/", "cpan" => "http://cpan.uwinnipeg.ca/module/$perlname", ); my(%sites) = @sites; my(@all) = grep(/^\w+$/, @sites); my(@findsites) = split(/\s+/, findsites); @findsites = map { ($_ eq "all") ? @all : $_ } @findsites; @findsites = map { exists($sites{$_}) ? $sites{$_} : $_ } @findsites; s/\*/\Q$name\E/g for @findsites; @findsites = $myurl if $lcname eq $myname; @findsites = uniq(@findsites); my($url); for $url (@findsites) { my(@ret) = eval { my($tries) = 1; if($url =~ m!^http://(freshmeat.net)/.*\.xml$!) { local(*XML, $_); my($sitename) = $1; openurl(*XML, $url); my($notfound, %fmurl); while() { $notfound = /^Error: project not found/i ? 1 : 0 if !defined($notfound); $fmurl{$1} = $2 while m!([^<]+) 1 && interactive; my(@allurls); for(@pkgs) { my($name, $version, $build, @urls) = @$_; error unless @urls; push(@allurls, @urls); } error unless @allurls; my($pl, $sl) = (commonlen(@allurls), commonlen(reverseall(@allurls))); print(substr($allurls[0], 0, $pl), " [...] ", substr($allurls[0], -$sl), "\n") if $pl || $sl; my($i, $cvi) = (0, 0); for(@pkgs) { ++$i; my($name, $version, $build, @urls) = @$_; my($margin) = " "; ($cvi, $margin) = ($i, "0) ") if !$cvi && $curver && $version && $curver eq $version; print("$margin$i) ", join(" ", map(substr($_, $pl, $sl ? -$sl : 9999), @urls)), "\n"); } my($choice) = ""; while(!($choice =~ /^\d+$/ && $choice > 0 && $choice <= $i)) { print("> [$i] "); $choice = scalar(); error("EOF") unless defined($choice); $choice =~ s/\s+//g; $choice = $i if $choice eq ""; $choice = $cvi if $choice eq "0"; } return $pkgs[$choice-1]; } ############################################################################## sub add(@) { my($name, $version, $build, @urls) = @_; $build && error; $name || @urls || error; if(!@urls && autofind) { my(@pkgs) = reverse(choose(undef, findnewpkg($name, $version))); error unless @pkgs; ($name, $version, undef, @urls) = @{$pkgs[0]}; error unless $name; error unless @urls; } @urls || error("autofind is disabled; please specify URL(s) for " . pkgname($name, $version)); $name = "unknown" unless defined($name); my($namedir) = pkgpath($name); optmd(storedir, pkgpath, $namedir); my($goodver) = defined($version); $version = "unknown" unless $goodver; my($verdir) = pkgpath($name, $version); if($goodver) { if(!-d($verdir)) { md($verdir); } else { my(@existingurls) = pkgurls($name, $version); if(samelist(@urls, @existingurls)) { explain(pkgname($name, $version) . " exists with matching urls; treating as successfully added"); return($name, $version); } else { error(pkgname($name, $version) . " exists with conflicting urls:\n " . join("\n ", @existingurls)); } } } else { my($max, $errmsg) = (-1, true); while($errmsg) { local($_); for(allversions($name)) { $max = max($max, $1 || 0) if /^unknown(\d*)$/; } if($max >= 0) { $version = "unknown" . ($max + 1); $verdir = pkgpath($name, $version); } $errmsg = mkdir($verdir, 0777) ? false : $!; error("mkdir $verdir: $errmsg") if $errmsg && !-d($verdir); } announce("mkdir", $verdir); } setpkgurls($name, $version, @urls); setactivepkg($name, $version); ($name, $version, @urls); } ############################################################################## sub smartgeturl($$) { my($url, $dir) = @_; if($url =~ m!^cvsroot(\+ssh)?:(.+)/([^/]+)\#(\d+)$!) { my($usessh, $cvsroot, $module, $time) = ($1, $2, $3, $4); announce("export CVS_RSH=ssh") if $usessh; local($ENV{CVS_RSH}) = "ssh" if $usessh; my($moddir) = path($dir, $module); optmd($moddir); chownnonroot($moddir); cdrunnonroot($dir, "cvs", (quiet ? "-Q" : "-q"), "-d", $cvsroot, "co", "-D", "\@$time", $module); cdrun($dir, qw(tar czf), "$module.tar.gz", $module); rmall($moddir); return $url; } my(%visited); for(1..5) { my($basename) = basename(stripquery($url)); $basename = "index" if $basename eq ""; my($file) = path($dir, $basename); my($newurl) = geturl($url, $file); $visited{$url} = 1; local(*FILE); safeopen(*FILE, "<", $file); my($header); read(FILE, $header, 128) || error("read $file: $!"); my($redir); if($header =~ /^\<.*\bHTML\b/i) { seek(FILE, 0, 0) || error("rewind $file: $!"); my(@links) = reverse(sort cmpab linksfromstream(*FILE, $newurl)); my(@goodlinks) = grep(/\.tar\./, @links); @goodlinks = grep(!/\.(s?html?|php)$/i, @links) unless @goodlinks; @links = @goodlinks if @goodlinks; for (@links) { $redir = $_ if !$redir && basename(stripquery($_)) eq $basename && !$visited{$_}; } my($ext); for $ext (qw[.tar.bz2 .tar.gz .tgz .zip .deb .rpm]) { for (@links) { $redir = $_ if !$redir && stripquery($_) =~ /\Q$ext\E$/i && !$visited{$_}; } } error("no more links to try") unless $redir; } close(FILE) || error("close $file: $!"); return $url unless $redir; rm($file); $url = $redir; error if $visited{$url}; } error("too many links: $url"); } sub renamepkg($$$$) { my($oldname, $oldversion, $newname, $newversion) = @_; my($oldnamedir) = pkgpath($oldname); my($newnamedir) = pkgpath($newname); my($oldverdir) = pkgpath($oldname, $oldversion); my($newverdir) = pkgpath($newname, $newversion); optmd($newnamedir); mv($oldverdir, $newverdir); rmdir($oldnamedir) && announce("rmdir", $oldnamedir); return ($newname, $newversion); } sub autorenamepkg($$@) { my($name, $version, @urls) = @_; if(@urls) { my($newname, $newversion) = guessnv(@urls); return renamepkg($name, $version, $newname, $newversion) if defined($newname) && defined($newversion); } my($verdir) = pkgpath($name, $version); my($archivedir) = path($verdir, archivedir); local($_); for(absls($archivedir)) { my($extractname) = extractname($_); next unless defined($extractname); my($newname, $newversion) = guessnv($extractname); return renamepkg($name, $version, $newname, $newversion) if defined($newname) && defined($newversion); } ($name, $version); } sub get(@) { my($name, $version, $build, @urls) = @_; my($autorename) = @urls && !defined($version) && autorename; ($name, $version, @urls) = add(@_) if @urls || !isadded($name, $version); $build && error; defined($name) || error; defined($version) || error; @urls = pkgurls($name, $version) unless @urls; return ($name, $version, @urls) if isstored($name, $version); my($verdir) = pkgpath($name, $version); my($realdir) = path($verdir, archivedir); my($tempdir) = addtmp($realdir); optmd($tempdir); my($changed) = false; for(@urls) { my($newurl) = smartgeturl($_, $tempdir); if($newurl ne $_ && autochange) { $_ = $newurl; $changed = true; } } setpkgurls($name, $version, @urls) if $changed; mv($tempdir, $realdir); ($name, $version) = autorenamepkg($name, $version, $changed ? @urls : ()) if $autorename; ($name, $version, @urls); } ############################################################################## sub exprecedence($) { local($_) = @_; /\.(patch|diff)\b/i; } sub excmp($$) { my($a, $b) = @_; exprecedence($a) - exprecedence($b) || $a cmp $b; } sub extract($$) { my($indir, $outdir) = @_; my(@infiles) = absls($indir); @infiles || error("$indir is empty"); extractfile($_, $outdir) foreach sort { excmp($a, $b) } @infiles; } sub makedir($) { my($dir) = @_; my($subdir); my($ok) = whiledir { my($abs) = path($dir, $_); return false unless -d($abs); return true if $_ eq "CVS"; return false if defined($subdir); $subdir = $abs; return true; } $dir; error("empty directory: $dir") if $ok && !defined($subdir); return $ok ? &makedir($subdir) : $dir; } sub quote($) { local($_) = undeftoempty(@_); s/\\/\\\\/g; s/'/\\'/g; "'$_'"; } sub cquote($) { local($_) = @_; s/\\/\\\\/g; s/"/\\"/g; "\"$_\""; } sub helpstub($$) { my($helperdir, $cmd) = @_; my($path) = quote($ENV{PATH}); writescript(path($helperdir, $cmd), "#!/bin/sh\nPATH=$path\nexec $cmd \"\$\@\"\n"); } sub helpnop($$) { my($helperdir, $cmd) = @_; helpstub($helperdir, $cmd); writescript(path($helperdir, "$cmd.helper"), "#!/bin/sh\ntrue\n"); } sub helprewrite($$$$$) { my($srcdir, $rootdir, $helperdir, $cmd, $force) = @_; helpstub($helperdir, $cmd); writescript(path($helperdir, "$cmd.helper"), "#!/bin/sh\n# $genby\n", q[ exec perl -x $0 ${1+"$@"} echo "Can't find perl in PATH; aborting." >&2 exit 1 #!perl $cmd = ], quote($cmd), q[; $srcdir = ], quote($srcdir), q[; $rootdir = ], quote($rootdir), q[; $armdir = ], quote(armdir), q[; $path = ], quote($ENV{PATH}), q[; $preload = ], quote($ENV{LD_PRELOAD}), q[; $myname = ], quote($myname), q[; # cleanse environment $ENV{PATH} = $path; if($preload eq "") { delete($ENV{LD_PRELOAD}); } else { $ENV{LD_PRELOAD} = $preload; } # save old arguments @oldargs = @ARGV; # compute new arguments my($dashp) = $cmd eq "mkdir"; # force mkdir -p while(@ARGV) { $_ = shift(@ARGV); $dashp &&= $_ ne "-p"; if($cmd eq "install" && /^-(o|-owner|g|-group)$/) { $rewritten = 1; shift(@ARGV); } else { if(m|^/| && !m!^(\Q$srcdir\E|\Q$rootdir\E)($|/)! && (s!^\Q$armdir\E($|/)!$1! || !m!^/(dev|proc|tmp)($|/)!)) { $rewritten = 1; my($dir, $elem) = ""; for $component (split(/\//, $_)) { $dir ne "" && mkdir("$rootdir$dir", 0777) && print("$myname: created directory $rootdir$dir\n"); $dir .= "/$component"; } -d("$armdir$_") && mkdir("$rootdir$_", 0777) && print("$myname: created directory $rootdir$_\n"); $_ = "$rootdir$_"; } push(@newargs, $_); } } if($dashp) { $rewritten = 1; unshift(@newargs, "-p"); } # print and execute real command print("$myname: rewriting $cmd @oldargs -> $cmd @newargs\n") if $rewritten; ], $force ? q[ # return success, even if command returns failure system($cmd, @newargs) == -1 && die("system $cmd: $!"); exit(0); ] : q[ # allow command to fail exec($cmd, @newargs); die("exec $cmd: $!"); ]); } sub helphspkg($$$) { my($rootdir, $helperdir, $cmd) = @_; return true unless hspkg; helpstub($helperdir, $cmd); writescript(path($helperdir, "$cmd.helper"), "#!/bin/sh\n# $genby\n", q[ rootdir=], quote($rootdir), q[ name=], time() . "-$$", q[ PATH=], quote($ENV{"PATH"}), q[ wrapper="$rootdir/bin/ghc.wrapped" cat > "$wrapper" << "EOF" #!/bin/sh exec perl -wx "$0" "$@" #!perl $0 =~ m!([^/]+)$! or die; my($me) = $1; my($wrap) = $ENV{"TOAST_HS_WRAPPER"} || ""; $wrap .= ":" if $wrap; $ENV{"TOAST_HS_WRAPPER"} = $wrap .= $0; my(@extra); push(@extra, ($me =~ /-pkg/ ? "-" : "") . "-package-conf", $_) for grep(-r, split(/:/, $ENV{"GHC_PKG_PATH"} || "")); DIR: for(split(/:/, $ENV{"PATH"})) { for("$_/$me.unwrapped", "$_/$me") { if(-x) { my(@stats) = stat or die("stat $_: $!"); my($skip); for $skip (split(/:/, $wrap)) { my(@skipstats) = stat($skip) or die("stat $skip: $!"); next DIR if "$skipstats[0] $skipstats[1]" eq "$stats[0] $stats[1]"; } if(exists($ENV{"TOAST_VERBOSE_HS_WRAPPER"})) { print("toast: not rewriting $0 @ARGV\n") unless @extra; print("toast: rewriting $0 @ARGV -> $0 @extra @ARGV\n") if @extra; } exec $_ ($0, @extra, @ARGV); die("exec $_ failed: $!"); } } } die("can't find next $me in PATH"); EOF chmod +x "$wrapper" for i in ghc ghci ghc-pkg; do path="$rootdir/bin/$i" if [ -r "$path" -a ! -r "$path.unwrapped" ] && ! diff -q "$path" "$wrapper" then mv "$path" "$path.unwrapped" fi ln -sf ghc.wrapped "$rootdir/bin/$i" done [ "$*" = "--toast-install-wrapper-only" ] && exit 0 ghc-pkg "$@" --user # let --auto-ghci-libs do its thing for arg; do case "$arg" in -*) ;; *) [ -r "$arg" ] && exec < "$arg" ;; esac done exec cat > "$rootdir/hspkg/$name" ]); } sub helpccache($$$) { my($rootdir, $helperdir, $cmd) = @_; return true unless ccache; my($path) = $ENV{"PATH"}; my($found); $found ||= -x(laxpath($_, "ccache")) for split(/:/, $path); return true unless $found; for($cmd, "$cmd.helper") { writescript(path($helperdir, $_), qq[#!/bin/sh # $genby PATH=$path exec ccache $cmd "\$\@" ]); } } sub helplib($$$) { my($srcdir, $rootdir, $helperdir) = @_; return undef unless preload; my($defdebug) = "#define DEBUG stderr"; $defdebug = "/* $defdebug */" unless debugrewrite; my($code) = qq[/* $genby */ #define _GNU_SOURCE #include #include #include #include #ifdef __CYGWIN__ #ifndef RTLD_NEXT #define RTLD_NEXT ((void *) -1L) #endif #endif #ifndef DEBUG $defdebug #endif struct timeval; /* to match the utimes() prototype that SunOS pulls in... */ #ifdef DEBUG #define debug(fmt, args...) do { fprintf(DEBUG, "$myname: " fmt "\\n", ## args); fflush(DEBUG); } while(0) #else #define debug(fmt, args...) ((void)0) #endif #define BUFSIZE 4096 static const char *srcdir = ] . cquote($srcdir) . qq[; static const char *rootdir = ] . cquote($rootdir) . qq[; static const char *helperdir = ] . cquote($helperdir) . qq[; static const char *armdir = ] . cquote(armdir) . qq[; static int startswith(const char *s, const char *prefix) { int len = strlen(prefix); int result = strncmp(s, prefix, len) == 0 && (s[len] == 0 || s[len] == '/'); /* debug("startswith(%s, %s) = %d", s, prefix, result); */ return result; } static const char *check_strip_armdir(const char *pathname) { if(!pathname) { debug("NULL pathname"); return 0; } else if(pathname[0] != '/') { debug("relative pathname: %s", pathname); return 0; } else if(startswith(pathname, srcdir)) { debug("pathname in srcdir: %s", pathname); return 0; } else if(startswith(pathname, rootdir)) { debug("pathname in rootdir: %s", pathname); return 0; } else if(startswith(pathname, helperdir)) { debug("pathname in helperdir: %s", pathname); return 0; } else { if(startswith(pathname, armdir)) { debug("pathname in armdir: %s", pathname); pathname += strlen(armdir); debug("stripping armdir: %s", pathname); } else if(startswith(pathname, "/dev")) { debug("pathname in /dev: %s", pathname); return 0; } else if(startswith(pathname, "/proc")) { debug("pathname in /proc: %s", pathname); return 0; } else if(startswith(pathname, "/tmp")) { debug("pathname in /tmp: %s", pathname); return 0; } if(BUFSIZE < strlen(rootdir) + strlen(pathname) + 1) { debug("pathname is too long: %s", pathname); return 0; } else { debug("pathname is rewritable: %s", pathname); return pathname; } } } static int real_mkdir(const char *pathname, int mode); static void mkparents(const char *pathname) { const char *stripped = check_strip_armdir(pathname); if(!stripped) { debug("not creating parents for %s", pathname); } else { int saved_errno = errno; char buf[BUFSIZE]; char *p = buf + strlen(rootdir); int done = 0; memset(buf, 0, sizeof(buf)); strcpy(buf, rootdir); while(!done) { do { *(p++) = *(stripped++); done = done || !*stripped; } while(!done && *stripped != '/'); if(!done) { int ret = real_mkdir(buf, 0755) != 0; if(ret == -1) { debug("can't create parent directory %s", buf); } else { debug("created parent directory %s", buf); } } } errno = saved_errno; } } static const char *rewrite(const char *pathname) { static char buffers[2][BUFSIZE]; static int whichbuf = 0; const char *stripped = check_strip_armdir(pathname); if(!stripped) { debug("not rewriting pathname %s", pathname); return pathname; } else { char *buffer = buffers[whichbuf]; whichbuf ^= 1; strcpy(buffer, rootdir); strcat(buffer, stripped); debug("rewriting %s -> %s", pathname, buffer); return buffer; } } ]; my(@decls) = split(/;/, q[ wrap FILE *fopen(const char *pathname, const char *mode); wrap FILE *freopen(const char *pathname, const char *mode, FILE *file); wrap void *opendir(const char *pathname); wrap void *dlopen(const char *pathname, int flag); wrap int creat(const char *pathname, int flags); wrap int open(const char *pathname, int flags, int mode); wrap int access(const char *pathname, int flags); wrap int chdir(const char *pathname); wrap int xstat(int version, const char *pathname, void *foo); wrap int lxstat(int version, const char *pathname, void *foo); wrap int readlink(const char *pathname, char *buf, int size); wrap int link(const char *pathname1, const char *pathname2); wrap int symlink(const char *pathname1, const char *pathname2); wrap0 int mkdir(const char *pathname, int mode); wrap int rmdir(const char *pathname); wrap int chmod(const char *pathname, int mode); wrap0 int rename(const char *pathname1, const char *pathname2); wrap0 int unlink(const char *pathname); wrap int utime(const char *pathname, void *foo); wrap int utimes(const char *pathname, const struct timeval *tvp); wrap int execve(const char *pathname, void *foo1, void *foo2); suppress int chown(const char *pathname, int user, int group); suppress int lchown(const char *pathname, int user, int group); suppress int fchown(int fd, int user, int group); ]); for(@decls) { s/\s+//; next unless $_; /^(wrap0?|suppress) (int |\w+ \*)(\w+)\((.*)\)$/ || error; my($iswrap, $rettype, $basename, $proto) = ($1 ne "suppress", $2, $3, $4); # wrapped dlopen() always fails with "Service not available" in FreeBSD (?) next if $basename eq "dlopen" && $^O eq "freebsd"; my($iswrap0) = $1 eq "wrap0"; my($retfailed, $retfmt) = $rettype =~ /\*/ ? (0, '%p') : (-1, '%d'); my(@alist) = map { /\w+$/ ? $& : error } split(/, /, $proto); my($args) = join(", ", @alist); my($newargs) = join(", ", map { /pathname/ ? "rewrite($_)" : $_ } @alist); my($allrewritable) = join(" && ", map("check_strip_armdir($_)", grep(/pathname/, @alist))); my($destarg) = $proto =~ /pathname2/ ? "pathname2" : "pathname"; my($nameprefix, $namesuffix); for $nameprefix ("", "_", "__") { for $namesuffix ("", "64") { my($name) = "$nameprefix$basename$namesuffix"; $code .= $iswrap ? qq[ static $rettype(*next_$name)($proto) = 0; static ${rettype}real_$name($proto) { if(!next_$name) { next_$name = dlsym(RTLD_NEXT, "$name"); } return next_$name($args); } $rettype$name($proto) { ${rettype}result; debug("rewriting $name"); mkparents($destarg); result = real_$name($newargs); debug("rewritten $name returned $retfmt", result); if(result == $retfailed) { debug("retrying $name with original args"); result = real_$name($args); debug("original $name returned $retfmt", result); } ] . ($iswrap0 ? qq[ if(result == $retfailed && $allrewritable) { debug("suppressing $name"); result = 0; errno = 0; } ] : "") . qq[ return result; } ] : qq[ $rettype$name($proto) { debug("suppressing $name"); return 0; } ]; } } } my($so, $do, $cc, $ld, $libs) = ("so", ".o", "gcc -fPIC", "gcc -shared", "-ldl"); eval q{ use Config; $so = $Config{"dlext"} or die; $do = $Config{"_o"} or die; $libs = "" unless $Config{"libs"} =~ /-ldl\b/; $cc = "$Config{cc} $Config{cccdlflags}"; $ld = "$Config{ld} $Config{lddlflags}"; $cc .= " -Wall -Werror" if $Config{ccname} eq "gcc" && strictpreload; }; writefile(path($helperdir, "dummy.c"), "void ____toast_dummy_4u38291(){}\n"); writefile(path($helperdir, "rewrite.c"), $code); return path($helperdir, "rewrite.$so") if optcdrun($helperdir, "$cc -c dummy.c -o dummy$do") && optcdrun($helperdir, "$ld dummy$do -o rewrite.$so") && optcdrun($helperdir, "$cc -c rewrite.c -o rewrite$do") && optcdrun($helperdir, "$ld rewrite$do -o rewrite.$so.helper $libs"); error("unable to compile with strictpreload enabled") if strictpreload; explain("unable to compile; strictpreload disabled, continuing"); return undef; } sub helphome($$$) { my($srcdir, $rootdir, $helperdir) = @_; my($makehome) = path($helperdir, "home"); my($installhome) = path($helperdir, "home.helper"); md($makehome, $installhome); my($makedir) = makedir($srcdir); my($scriptdir) = path($installhome, "bin"); my($script) = path($scriptdir, "installkernel"); my($kbase) = "vmlinuz"; my($mbase) = "System.map"; my($kernel) = path($rootdir, "boot", $kbase); my($map) = path($rootdir, "boot", $mbase); md($scriptdir); writescript($script, qq[#!/bin/sh # script to be run by Linux kernel Makefile install target # $genby set -e set -x cat \$2 > $kernel cp \$3 $map ln -s $kbase $kernel-\$1 ln -s $mbase $map-\$1 ]); return $makehome; } sub inithelpers($$$) { my($srcdir, $rootdir, $helperdir) = @_; my(%env) = %ENV; md($helperdir); helprewrite($srcdir, $rootdir, $helperdir, $_, false) for (qw[mkdir rmdir touch cp rm ln install chmod test ranlib gzip xmlcatalog], "["); helprewrite($srcdir, $rootdir, $helperdir, $_, true) for (qw[mv]); helpnop($helperdir, $_) for (qw[chown chgrp ldconfig install-info mknod]); helphspkg($rootdir, $helperdir, $_) for (qw[ghc-pkg]); helpccache($rootdir, $helperdir, $_) for (qw[cc gcc g++]); $env{PATH} = "$helperdir:$env{PATH}"; my($preload) = helplib($srcdir, $rootdir, $helperdir); if($preload) { if(exists($env{LD_PRELOAD}) && length($env{LD_PRELOAD})) { $env{LD_PRELOAD} .= " $preload"; } else { $env{LD_PRELOAD} = $preload; } } $env{HOME} = helphome($srcdir, $rootdir, $helperdir); %env; } sub armhelpers($) { my($helperdir) = @_; my(@files) = absls($helperdir); /\.helper$/ ? mv($_, $`) : mv($_, "$_.stub") foreach sort(@files); } sub makefile($) { my($dir) = @_; for(qw[GNUmakefile makefile Makefile]) { my($file) = path($dir, $_); return $file if -r($file); } return false; } sub splitwords($) { my($s) = @_; $s =~ s/^\s+//; my(@words); while($s =~ s/^"((\\.|[^"])*)"//s || $s =~ s/^'((\\.|[^'])*)'//s || $s =~ s/^((\\.|\S)+)//s) { push(@words, $1); $s =~ s/^\s+//; } error("unbalanced quotes") unless $s eq ""; return @words; } sub cdrunconf($@) { my($dir, @prog) = @_; cdrun($dir, @prog, splitwords(confappend)); } sub configure($$) { my($dir, $prefix) = @_; # Haskell Cabal (http://www.haskell.org/cabal/) # Must be checked for before "configure" due to the following craziness: # http://haskell.org/ghc/docs/6.4/html/Cabal/authors.html#system-dependent for("Setup.hs", "Setup.lhs") { if(-r(path($dir, $_))) { cdrun($dir, qw(ghc-pkg list)); # work around ~/.ghc bug in ghc-pkg 6.4 (?) cdrunconf($dir, "runghc", $_, "configure", "--prefix=$prefix", "--with-hc-pkg=ghc-pkg"); writefile ( path($dir, "Makefile"), "# $genby\n\n", "all:\n\trunghc $_ build\n\n", "install:\n\trunghc $_ copy --copy-prefix=\$(DESTDIR)\n", "\trunghc $_ register\n", ); return $dir; } } # find a configure script my($cfg); # autoconf Perl openssl elm libmng pmk for $cfg (qw[configure configure.gnu config Configure autogen.sh pmkcfg.sh]) { my($confprog) = path($dir, $cfg); next if -d($confprog); next unless -x($confprog) || (-r($confprog) && $confprog =~ /\.sh$/); my($self) = false; my($builddir) = false; my($bogus) = false; my($twowordprefix) = false; my($noprefix) = false; my($mozilla) = false; my(%confopts); my($opensslcfg) = path($dir, "Configure"); if(reconfigure && $cfg eq "config" && -x($opensslcfg)) { whilefile { !($confopts{"shared"} ||= /^# \[no-\]shared\s/); } $opensslcfg; } patch { $self ||= /this is toast 89a72ef9c34e1fd4927afa36c9be8b15/; $twowordprefix ||= /^\s*prefix=\$2; shift;;$/; # ocaml $twowordprefix ||= / --prefix dir /; # doxygen $builddir ||= /error: you must configure in a separate build/; # glibc $builddir||=m!\. \$srcdir/build-tools/scripts/configure-top !;#arch/tla $builddir ||= /Building in the source directory is not support/; # gcc 4 $bogus ||= /There is no .+configure.+ script .+Instead/i; # libpng $bogus ||= /configure is no longer used - just type /i; # cdrtools s/(^\s*)read\s+(acceptance)(\s*$)/$1$2=yes$3/; # qt license s/^if test ! -t 0; /if false; /; # elm $noprefix = $confopts{"-d"} = 1 if /^\s*-d\) shift; fastread='y/; # elm $noprefix ||= /elif \[ x.sysname = x \]; then/; # isc dhcp if(reconfigure) { s/^\s*AUTOTHREAD=no\s+\#+ leave off for now/\#$&/; # qt multithreaded $mozilla = $1 if /(--enable-default-toolkit)=TK/; my($opt); # glibc; libjpeg; tetex; others? for $opt ("enable-add-ons", # glibc "enable-shared", # various "with-shared", # ncurses "disable-multiplatform") # tetex { $confopts{"--$opt"} ||= / \Q--$opt\E\b($|[^-])/; } $confopts{$1} = 1 if /"configure (-\S+)"; by default a static/;#zlib } } $confprog; $noprefix ||= -x(path($dir, "installnetpbm")); # netpbm 10.19 if($mozilla) { local($^W) = false; # suppress misguided exec failure warning $confopts{"$mozilla=gtk2"} ||= system(qw(pkg-config gtk+)) == 0; } my(@confcmd) = ("./$cfg", "--prefix=$prefix"); if(!$self) { next if $bogus; @confcmd = ("./$cfg", "--prefix", $prefix) if $twowordprefix; @confcmd = ("./$cfg") if $noprefix; push(@confcmd, sort(grep($confopts{$_}, keys(%confopts)))); unshift(@confcmd, "sh") unless -x($confprog); if($builddir) { $confcmd[0] = "../$cfg"; $dir = path($dir, "$myname-build.d"); md($dir); } elsif($cfg eq "configure.gnu") { # force perl to install files it thinks are already installed # (5.8.2 and later support DESTDIR and should no longer need this) optpatch { s/^use File::Compare;$/sub compare(\$\$){1}; #$genby/ } path($dir, "installperl"); } } my($amhelper) = path($dir, "install-sh"); if(-l($amhelper) && !-r($amhelper)) # e.g. sgml-common/0.6.3 { optcdrun($dir, "aclocal"); optcdrun($dir, "automake", "-acf"); optcdrun($dir, "autoconf", "-f"); } cdrunconf($dir, @confcmd); return $dir; } for(qw[build_unix build.unix]) # e.g. Sleepycat DB and nvi, respectively { my($bubuilddir) = path($dir, $_); my($buconfprog) = path($dir, "dist", "configure"); return $bubuilddir if -d($bubuilddir) && -x($buconfprog) && cdrunconf($bubuilddir, "../dist/configure", "--prefix=$prefix"); } # configure script might be hidden in a "source" subdirectory (e.g. samba) for(qw[source src]) { my($subdir) = path($dir, $_); my($confprog) = path($subdir, "configure"); return &configure($subdir, $prefix) if -x($confprog); } # if this XFree86, we have to set ProjectRoot in xc/config/cf/site.def... optpatch { s!(\#\s*define\s+ProjectRoot\s+)(/usr/X\S+)!$1$prefix!; } path($dir, qw[config cf site.def]); # if there's a Makefile, just use that return $dir if makefile($dir); # OK, how about an Imakefile? my($imf) = path($dir, "Imakefile"); if(-r($imf)) { my($xaw3d); # cajole standalone Xaw3d build patch { $xaw3d = 0 if $xaw3d && s/^XCOMM\s+//; $xaw3d = 1 if !defined($xaw3d) && /^XCOMM When.*outside an X11 source t/; } $imf; if(defined($xaw3d)) { md(path($dir, "X11")); ln("..", path($dir, "X11", lastitem(unpath($dir)))); } cdrunconf($dir, "xmkmf"); return $dir; } # perl modules have a Makefile.PL script that spits out a Makefile my($mfpl) = path($dir, "Makefile.PL"); return $dir if -r($mfpl) && cdrunconf($dir, "perl", $mfpl); # pmk is some kind of newfangled autoconf replacement my($pmkfile) = path($dir, "pmkfile"); return $dir if -r($pmkfile) && cdrunconf($dir, "pmk"); # qmake is a similar tool that comes with Qt my(@profiles) = grep(/\.pro$/i, ls($dir)); return $dir if scalar(@profiles)==1&& cdrunconf($dir, "qmake", $profiles[0]); # Write a custom Makefile for glibc with add-ons and/or kernel headers. my($glibcdir, $hdrdir, @addons); my($glibcok) = whiledir { push(@addons, $_); my($abs) = path($dir, $_); return false if !-d($abs) || -l($abs); if(-r(path($abs, qw[include linux kernel.h]))) { $hdrdir = $abs; pop(@addons); return true; } my($cfg) = path($abs, "configure"); if(-r($cfg)) { return true if whilefile { /^\s*(\#|exit|$)/ } $cfg; return false if $glibcdir; $glibcdir = $abs; pop(@addons); } true; } $dir; if($glibcok && (@addons || $hdrdir) && $glibcdir) { mv(path($dir, $_), path($glibcdir, $_)) foreach @addons; my($makedir) = path($glibcdir, "$myname-build.d"); md($makedir); if($hdrdir) { cdrun($hdrdir, qw[make include/linux/version.h]); optcdrun($hdrdir, qw[make include/asm]) || # 2.6.x cdrun($hdrdir, qw[make symlinks]); # 2.4.x } my($emdir) = shellescape($makedir); my($ehdir) = defined($hdrdir) ? (shellescape($hdrdir)) : (); my($mftext) = "# $genby\n\nall:\n" . "\tyes '' | make -C $ehdir oldconfig dep\n" . # mtools, LVM2... "\t\$(MAKE) -C $emdir\n\n" . "install:\n\t\$(MAKE) -C $emdir install\n"; my($inclist) = "include/linux include/asm/. include/asm-generic"; $mftext .= "\t\(cd $ehdir && tar c $inclist) | (cd \$(DESTDIR) && tar x)\n" if $ehdir; writefile(path($dir, "Makefile"), $mftext); my(%confopts) = ("--prefix=$prefix" => 1); $confopts{"--with-headers=$hdrdir/include"} = 1 if $hdrdir; my($cfg) = path($glibcdir, "configure"); patch { $confopts{$1} = 1 if /(--(enable-add-ons|with-tls))\b/; } $cfg if reconfigure; cdrunconf($makedir, qw(sh ../configure), sort(keys(%confopts))); return $dir; } # Write a custom Makefile for Sun J2SDK (grumble grumble). if(-x(path($dir, qw[jre bin java]))) { my($mftext) = "# $genby" . q{ all: for i in "" `find . -name '*.pack'`; do [ -n "$$i" ] && \ lib/unpack $$i "`dirname $$i`/`basename $$i .pack`.jar"; rm -f $$i; done install: tar c */ | (cd $(DESTDIR)/usr && tar x) }; $mftext =~ s/^ /\t/gm; writefile(path($dir, "Makefile"), $mftext); return $dir; } # look for Unixish or OS-specific Makefile in a subdir and mv it here # e.g. zip -> unix/Makefile; libpng -> scripts/makefile.linux # note that this rule would incorrectly grab a spurious subdirectory # Makefile from glibc+linuxthreads or j2sdk 1.5 if the above rules didn't # come first return $dir unless dfs ( $dir, sub { true }, sub { my($rel) = @_; return true unless $rel =~ /makefile/i; return true unless $rel =~ /$^O|\bunix/i; mv($_, path($dir, "Makefile")); return false; }, sub { true } ); # Maybe there's just an install script of some kind that we need to run. # Create an appropriate Makefile. for(qw[install.sh INSTALL install.pl vmware-install.pl]) { my($script) = path($dir, $_); if(-x($script)) { my(@cmd) = "./$_"; my($isscript, $useprefix, $prefixenvvar); patch { $isscript ||= /^#!/; $useprefix ||= /--prefix=/; s/\bOutputLicense\b/true/g unless /\(/; # acroread $prefixenvvar = $1 if /\b(InstallDir)\b/; # acroread s/not is_root\(\)/0/; # vmware } $script; next unless $isscript; unshift(@cmd, "$prefixenvvar=$prefix") if defined($prefixenvvar); push(@cmd, "--prefix=$prefix") if $useprefix; writefile(path($dir, "Makefile"), "# $genby\n\nall:\n\ttrue\n\ninstall:\n\t@cmd\n"); return $dir; } } # Construct a Makefile to invoke setup.py on behalf of certain Python source # packages (notably BitTorrent): if(-r(path($dir, "setup.py"))) { writefile ( path($dir, "Makefile"), "# $genby\n\n", "all:\n\tpython setup.py build\n\n", "install:\n\tpython setup.py install " . "--prefix=\Q$prefix\E --root=\$(DESTDIR)\n", ); return $dir; } # some packages (e.g. htmltidy) expect us to locate and run yet another # shell script that generates the real configure script that generates.... for(grep(-r(path($dir, $_)), "build/gnuauto/setup.sh")) { cdrun($dir, "sh", $_); return &configure($dir, $prefix) if -x(path($dir, "configure")); } # try looking in promisingly-named subdirectories -d && return &configure($_, $prefix) foreach map(path($dir, $_), qw[src]); # construct a Makefile for various intransigent binary packages my(%file2dir); %file2dir = () unless whiledir { my($name) = @_; local($_) = path($dir, $name); return false if !-f; return $file2dir{$name} = "/usr/lib/netscape/plugins" if $name =~ /flashplayer\.(xpt|so)$/; # flash player 6 return $file2dir{$name} = "/bin" if $name =~ /^[a-z]+$/ && -x && -B; # upx return $file2dir{$name} = "/man/man1" if $name =~ /^[a-z]+\.1$/ && !-x && -T; $name =~ /readme|flashplayer-installer|^[A-Z]+$|\.(doc|html)$/; } $dir; if(%file2dir) { my($mftext) = "# $genby\nPREFIX=$prefix\nall:\n\ttrue\n\ninstall:\n"; for(sort(keys(%file2dir))) { my($d) = $file2dir{$_}; $mftext .= "\tmkdir -p $d\n\tcp $_ \$(DESTDIR)\$(PREFIX)$d\n"; } writefile(path($dir, "Makefile"), $mftext); return $dir; } # getting desperate now -- try to construct a Makefile from thin air my(@prog, @sources, @man); dfs ( $dir, sub { true }, sub { my($file) = @_; if(/\.c$/) { push(@sources, $file); push(@prog, stripext(basename($_))) if grep { /\bmain\b/ } readfile($_); } push(@man, $file) if /\.1$/ || ($file =~ s/\.man$/.1/); true; }, sub { true } ); # give up if our still-hypothetical Makefile is looking too implausible error("can't figure out how to compile in $dir") unless scalar(@prog) == 1 && @sources && scalar(@sources) < 100; my($mftext) = qq[# $genby PREFIX=$prefix BINDIR=\$(PREFIX)/bin MANDIR=\$(PREFIX)/man/man1 @prog: @sources install: @prog \tcp @prog \$(BINDIR) ]; $mftext .= "\tcp @man \$(MANDIR)\n" if @man; writefile(path($dir, "Makefile"), $mftext); return $dir; } sub make($) { my($dir) = @_; my($mf) = makefile($dir) || error("no Makefile found"); my($abort) = false; my($ok) = true; my($systype) = false; my($lk) = false; # blatant special case for older Linux kernels my($xf86) = false; # blatant special case for XFree86 my($please) = ""; # blatant special case for xdaliclock my($figlet, $ocaml); # further eponymous special case my($menuconfig); # busybox, uClibc my($silentoldconfig); # newer linux kernels explain("examining $mf..."); patch { $systype ||= /You must specify the system which you want to compil/;#mpg123 $systype ||= /If you're not sure about the characteristics of your /;#unzip $systype ||= /^\t\@echo 'Choose target:'$/; # plugger 5.0 $lk ||= /^do-it-all:.*\bvmlinux\b/; $xf86 ||= /Please use make World/i; $ocaml ||= /^\# For users who don't read the INSTALL file$/; #thanks ocaml! $please = $1 if / please make one of: "([^"]+)"/; $abort ||= /^all:[^\#]*\binstall\b/; # all implies install (e.g. man-pages) s/^DEFAULTFONTDIR = fonts$/# $&/ if $figlet; $figlet ||= m!^DEFAULTFONTDIR = /usr.*/figlet$!; $menuconfig ||= m!^all: menuconfig$!; $silentoldconfig ||= /Makefile silentoldconfig$/; s!(^PERL\s*=\s*)(/usr/.*/perl)($)!$1 . (-x($2) ? $2 : which("perl")) . $3!e if reconfigure; # psutils/p17 } $mf; return false if $abort; my(@prog) = "make"; my(@targets); @targets = $^O if $systype; @targets = qw[oldconfig dep bzImage] if $lk; @targets = qw[oldconfig all] if $menuconfig || $silentoldconfig; @targets = "World" if $xf86; @targets = qw[world opt] if $ocaml; @targets = $please if $please; if(($lk || $silentoldconfig) && reconfigure) { my($in) = path("/proc", "config.gz"); my($out) = path($dir, ".config"); optrun("gunzip < " . shellescape($in) . " > " . shellescape($out)) if -r($in) && !-e($out) } my(@args) = splitwords(makeappend); return cdrun($dir, @prog, @args) unless @targets; cdrun($dir, @prog, $_, @args) foreach @targets; return true; } sub install($$) { my($makedir, $rootdir) = @_; my($mf) = makefile($makedir) || error("no Makefile found"); my(@trace); @trace = qw[strace -s 256 -f -F -o make.install.strace] if debugrewrite; @trace = qw[ktrace -i -f make.install.ktrace] if @trace && $^O =~ /bsd/i; local($ENV{ROOT}) = $rootdir; # lilo local($ENV{DESTDIR}) = $rootdir; # not always on command line due to libtiff local($ENV{install_root}) = $rootdir; local($ENV{PREFIX}) = $rootdir; # airhook v2 announce("export", "$_=$ENV{$_}") for (qw[ROOT DESTDIR install_root PREFIX]); my(@targets) = "install"; my($man, $subdir, $netpbm, $usedestdir, $nodestdir, $useinstallprefix, $cdrtools, $e2fsprogs, $modules); whilefile { $man ||= /^install\.man:/; $subdir = $1 if / to install the X version: "cd (\w+);/; # xdaliclock $netpbm ||= /^\t\@echo " make package pkgdir=DIR"$/; $usedestdir ||= /\b(DESTDIR|Makefile\.in)\b/; # atop, glibc; not libtiff! $nodestdir ||= m!^\s*DESTDIR\s*=[^#]*/bin\s*$!; # figlet $useinstallprefix ||= m!INSTALL_PREFIX is for package builders!; # openssl $cdrtools ||= / Due to a bug in SunPRO make we need special rules /; $e2fsprogs ||= /^\t.* e2fsck .* ; then \$\(MAKE\) install-libs ; fi/; $modules ||= /^modules_install: _modinst_/; # linux 2.6.x true } $mf; my($cf) = path($makedir, ".config"); $modules &&= whilefile { !/ CONFIG_MODULES is not/ } $cf if -r($cf); $netpbm &&= -x(path($makedir, "installnetpbm")) && path($makedir, "pkg"); return &install(path($makedir, $subdir), $rootdir) if $subdir; push(@targets, "install.man") if $man && reconfigure; # e.g. XFree86 push(@targets, "install-libs") if $e2fsprogs && reconfigure; @targets = ("package", "pkgdir=$netpbm") if $netpbm; @targets = "upgrade" if -r(path($makedir, "postfix-install")); # postfix push(@targets, "DESTDIR=$rootdir") if $usedestdir && !$nodestdir; push(@targets, "install_root=$rootdir") if $usedestdir; # glibc push(@targets, "PREFIX=$rootdir") if $usedestdir; # busybox push(@targets, "INSTALL_PREFIX=$rootdir") if $useinstallprefix; # openssl push(@targets, "INS_BASE=$rootdir") if $cdrtools; push(@targets, "RUN_QUERY_LOADER_TEST=true", "RUN_QUERY_IMMODULES_TEST=true") if -d(path($makedir, "gdk-pixbuf")); # gtk+ push(@targets, "modules_install", "INSTALL_MOD_PATH=$rootdir") if $modules; cdrun($makedir, @trace, "make", @targets); run(@trace, "sh", "-c", "cp -R '$netpbm'/*/ '$rootdir'") if $netpbm; error("trace complete; aborting") if debugrewrite; } sub stddirs() { my(@result) = ( qw[bin boot etc include info lib libexec man share src var], map(path("man", "man$_"), 1..9), path(qw[var spool]), path(qw[etc rc.d]), map(path("etc", "rc.d", "rc$_.d"), 0..6), # vmware path(qw[var run]), # hotplug/2004_01_05 path(qw[share aclocal]), # librep/0.16.1 "hspkg", # holding area for Haskell package files; see sub helphspkg() ); sort(@result); } sub stdlinks() { ( "usr" => ".", "local" => ".", "X11R6" => ".", "sbin" => "bin", "games" => "bin", "share/man" => "../man", "share/info" => "../info", ); } sub rootlinks() { stdlinks } sub mkrootdir($$) { my($rootdir, $armdir) = @_; md($rootdir); md(path($rootdir, $_)) foreach stddirs; my(%link) = rootlinks; ln($link{$_}, path($rootdir, $_)) foreach keys(%link); my($p) = $rootdir; optrelln($rootdir, $p = path($p, $_)) for unpath($armdir), unpath($rootdir); } sub mkxmlcatalog($@) { my($name, @contents) = @_; writefile($name, qq[ \n], map(" $_\n", @contents), "\n"); } sub compiledata($$) { my($srcdir, $rootdir) = @_; my(@installdir); # docbook-xml, maybe other things too? my($cat) = path($srcdir, "catalog.xml"); if(-r($cat)) { my($name, $version, $dup); patch { ($name, $version) = (lc($1), $2) if m!//DTD (\w+) XML V([\d\.]+)//!i; $dup .= $_ if $dup; $dup = "$1.1.2/$2" if reconfigure && m!(/; } $cat; @installdir = (qw(share xml), $name, $version) if $version; } # docbook-xsl my(@ls) = ls($srcdir); if(!@installdir && scalar(@ls) == 1 && $ls[0] =~ /^docbook-xsl-/) { $srcdir = path($srcdir, $ls[0]); @installdir = qw(share xml docbook-xsl); my($cat) = path($srcdir, "catalog.xml"); my($rewritePrefix) = path(armdir, @installdir); mkxmlcatalog($cat, qq[]) unless -r($cat); } return false unless @installdir; my($dir) = $rootdir; for(@installdir) { md($dir); $dir = path($dir, $_); } return mv($srcdir, $dir); } sub compilebin($$) { my($srcdir, $rootdir) = @_; return false if -d(path($srcdir, "xc")); # Xaw3d my($one); my($ok); abswhiledir ( sub { $one = $one ? 0 : $_ unless defined($one); $ok ||= m!/(usr|bin|etc|lib|sbin)$!; -d }, $srcdir ) && ($ok ? mv($srcdir, $rootdir) : ($one && &compilebin($one, $rootdir))); } sub compilehelp($$$) { my($srcdir, $rootdir, $helperdir) = @_; my($makedir) = makedir($srcdir); my(%initenv) = inithelpers($srcdir, $rootdir, $helperdir); return false unless %initenv; defined($ENV{$_}) && $ENV{$_} eq $initenv{$_} || announce("export", "$_=$initenv{$_}") foreach keys(%initenv); local(%ENV) = %initenv; $makedir = configure($makedir, armdir); make($makedir); mkrootdir($rootdir, armdir); armhelpers($helperdir); install($makedir, $rootdir); !defined($ENV{$_}) ? announce("unset", $_) : ($ENV{$_} eq $initenv{$_} || announce("export", "$_=$ENV{$_}")) foreach keys(%initenv); return true; } sub fixbrokenrootlink($) { # stdlinks fool XFree86 and busybox into creating broken symlinks; fix them! my($link) = @_; my($dest) = readlink($link); defined($dest) || error("not a link: $link"); explain("found broken symlink: $link -> $dest"); while($dest =~ s!\.\./!! && !-e($link)) { rm($link); ln($dest, $link); } rm($link) unless -e($link); true; } sub fixrootfile($) { my($file) = @_; return -e($file) ? true : fixbrokenrootlink($file) if -l($file); safechmod(-x($file) ? 0555 : 0444, $file); } sub polishrootdir($$) { my($rootdir, $armdir) = @_; my(%link) = rootlinks; -l && rm($_) foreach map { path($rootdir, $_) } (keys(%link), unpath($rootdir), unpath($armdir)); optrmall(path($rootdir, "info", "dir")); my($total, %count) = 0; dfs ( $rootdir, sub { $count{$_} = $total; true }, sub { fixrootfile($_); ++$total }, sub { $count{$_} < $total ? !protect || safechmod(0555, $_) : rd($_) } ); $total || error("no files found in $rootdir"); # XFree86 4.3.0 libGL.so needs this due to some kind of bug/interaction, # as do some binary packages: my($libdir) = path($rootdir, "lib"); if(fixliblinks && -d($libdir)) { my(%liblinks); fordir { return unless /^(lib.*\.so)((\.[\d]+)+)$/; my($base, $ext) = ($1, $2); return if exists($liblinks{$base}) && length($liblinks{$base}) > length; $liblinks{$base} = $_; for $ext (split(/\./, $ext)) { next unless $ext; $base .= ".$ext"; next if $_ eq $base; $liblinks{$base} = $_; } } $libdir; my($mode) = getmode($libdir); for(sort(keys(%liblinks))) { my($from, $to) = ($liblinks{$_}, path($libdir, $_)); if(!-e($to) && !-l($to)) { safechmod(0777, $libdir); ln($from, $to); } } safechmod($mode, $libdir); } } sub wraphaskell($$$) { my($srcdir, $rootdir, $helperdir) = @_; return true unless -x(path($rootdir, qw(bin ghc))); my($script) = path($helperdir, "ghc-pkg"); if(!-x($script)) { optmd($helperdir); helphspkg($rootdir, $helperdir, "wrap-ghc"); $script = path($helperdir, "wrap-ghc"); armhelpers($helperdir); } run($script, "--toast-install-wrapper-only"); } sub compile($$$) { my($srcdir, $rootdir, $helperdir) = @_; compiledata($srcdir, $rootdir) || compilebin($srcdir, $rootdir) || compilehelp($srcdir, $rootdir, $helperdir); wraphaskell($srcdir, $rootdir, $helperdir) if hspkg; polishrootdir($rootdir, armdir); } sub clean(@) { whilebuild { my($name, $version, $build) = @_; my($builddir) = pkgpath($name, $version, $build); return rmall($builddir) if isbroken($name, $version, $build); return true unless isbuilt($name, $version, $build); my($srcdir) = path($builddir, srcdir); my($helperdir) = path($builddir, helperdir); optrmall($srcdir, $helperdir); } @_; return true; } sub purge(@) { my($name, $version, $build, @urls) = @_; $build && error; for $name (allnames($name)) { for $version (allversions($name, $version)) { my($archivedir) = path(pkgpath($name, $version), archivedir); optrmall($archivedir); } } return true; } sub rebuild(@) { my($name, $version, $build, @urls) = @_; $build && error; ($name, $version) = get(@_) if !isstored($name, $version); defined($name) || error; defined($version) || error; clean($name, $version) if autoclean; local(%ENV) = %ENV; if(ccache) { my($storecachedir) = path(storedir, "ccache"); my($cachedir); if(exists($ENV{"CCACHE_DIR"})) { $cachedir = $ENV{"CCACHE_DIR"}; } elsif(superuser) { optmd($cachedir = $storecachedir); } elsif(exists($ENV{"HOME"}) && -d($ENV{"HOME"})) { $cachedir = path($ENV{"HOME"}, ".ccache"); } chownnonroot($cachedir) if superuser && !belongstononroot($cachedir) && optsamefile($cachedir, $storecachedir); $ENV{"CCACHE_DIR"} = $cachedir; announce("export", "CCACHE_DIR=$cachedir"); } my($verdir) = pkgpath($name, $version); my(@allbuilds) = allbuilds($name, $version); $build = @allbuilds ? max(@allbuilds) : 0; my($errmsg, $builddir) = true; while($errmsg) { $build++; $builddir = path($verdir, $build); $errmsg = mkdir($builddir, 0777) ? false : $!; error("mkdir $builddir: $errmsg") if $errmsg && !-d($builddir); } announce("mkdir", $builddir); chownnonroot($builddir); local(*CHILD); my($pid); if(!($pid = open(CHILD, "-|"))) # child { defined($pid) || error("fork: $!"); my(@times) = gettimes; open(STDERR, ">&STDOUT") || error("dup stdout: $!"); verbosify; benice; dropprivs; showprebuildinfo($name, $version, $build); yes; setbuildenv if autoenv; my($archivedir) = path($verdir, archivedir); my($srcdir) = path($builddir, srcdir); my($helperdir) = path($builddir, helperdir); my($rootdir) = path($builddir, rootdir); my($armdirlink) = path($builddir, armdirlink); ln(armdir, $armdirlink); md($srcdir); extract($archivedir, $srcdir); compile($srcdir, $rootdir, $helperdir); showtimedeltas(@times); exit(0); } $SIG{INT} = "IGNORE"; my($tmplogname) = addtmp(path($builddir, buildlog)); local(*LOG); safeopen(*LOG, ">", $tmplogname); while() { print LOG $_; chomp; say(" $_\n"); } my($success) = close(CHILD); $SIG{INT} = "DEFAULT"; my($msg) = "child returned $?"; close(LOG) || error("close $tmplogname for write: $!"); if($success && superuser) { announce("chown", "-Rh", "root:root", $builddir); dfs ( $builddir, sub { chown(0, 0, $_) || ($success = !($msg = "chown $_: $!")) }, sub { if(!-l) { return chown(0, 0, $_) || ($success = !($msg = "chown $_: $!")) } else { my($source) = readlink; error("readlink $_: $!") unless defined($source); return (unlink || ($success = !($msg = "unlink $_: $!"))) && (symlink($source, $_) || ($success = !($msg = "symlink $source $_: $!"))); } }, sub { true } ); } my($logname) = path($builddir, $success ? buildlog : brokenlog); mv($tmplogname, $logname); $success || error($msg); clean($name, $version, $build) if autoclean; purge($name, $version) if autopurge; arm($name, $version, $build) if autoarm && isarmedmatch($name, $version); if(autodemolish || autoremove) { my($aversion, $abuild); for $aversion (allversions($name, crossversion ? undef : $version)) { if(autodemolish) { for $abuild (allbuilds($name, $aversion)) { next if $aversion eq $version && $abuild == $build; next if skipmismatched && ismismatched($name, $aversion, $abuild); next if !autodisarm && isarmed($name, $aversion, $abuild); demolish($name, $aversion, $abuild); } } if(autoremove) { next if $aversion eq $version; next if !autodisarm && isarmed($name, $aversion); remove($name, $aversion); } } } ($name, $version, $build); } sub build(@) { my($name, $version, $build, @urls) = @_; $build && error; $build = defined($version) ? latestbuilt($name, $version) : undef; return $build ? ($name, $version, $build) : rebuild(@_); } ############################################################################## sub displace($) { local($_) = @_; if(-e || -l) { error("refusing to displace directory: $_") if -d && !-l; my($offname) = addoff($_); &displace($offname); mv($_, $offname); } $_; } sub isempty($) { my($dir) = @_; my($result) = true; whiledir { $result = false } $dir; $result; } BEGIN { my(%locks); sub lock($) { my($path) = @_; error("$path already locked") if $locks{$path}; local(*LOCK); safeopen(*LOCK, "<", $path); $locks{$path} = *LOCK{IO}; return unless useflock; if(!flock(LOCK, 6)) # LOCK_EX | LOCK_NB { explain("waiting for lock on $path"); flock(LOCK, 2) || error("flock $path: $!"); # LOCK_EX } } sub unlock($) { my($path) = @_; my($lock) = $locks{$path}; error("$path not locked") unless $lock; flock($lock, 8) || error("unlock $path: $!") if useflock; # LOCK_UN close($lock) || error("close $path: $!"); delete($locks{$path}); } } sub rebuildinfodir($) { my($armdir) = @_; my($dir) = path($armdir, "info"); return true unless -d($dir); my($dirfile) = path($dir, "dir"); # remove old links left by previous version of toast: my($oldfile) = addoff($dirfile); while(-e($oldfile) || -l($oldfile)) { rm($oldfile); $oldfile = addoff($oldfile); } my($mode) = getmode($dir); if(!infodir) { if(-e($dirfile) || -l($dirfile)) { safechmod(0777, $dir); rm($dirfile); } } else { my($tmpfile) = addtmp($dirfile); if(-e($tmpfile) || -l($tmpfile)) { safechmod(0777, $dir); rm($tmpfile); } my($tmpsuffix) = tmpsuffix; my($offsuffix) = offsuffix; abswhiledir { return true if m!(/dir|\Q$tmpsuffix\E|\Q$offsuffix\E|-\d+(\.info)?)$!; safechmod(0777, $dir); optrun("install-info", $_, $tmpfile); true; } $dir; mv($tmpfile, $dirfile) if -e($tmpfile); } safechmod($mode, $dir); } sub rebuildxmlcatalog(@) { my($armdir) = @_; my($etcdir) = path($armdir, "etc"); my($xmldir) = path($etcdir, "xml"); my($master) = path($xmldir, "toast-xml-catalog"); my(@subcats); if(xmlcatalog) { @subcats = -d($xmldir) ? grep(m!/catalog[^/]*$!, absls($xmldir)) : (); my($dir) = path($armdir, qw[share xml]); dfs ( $dir, sub { true }, sub { push(@subcats, $_) if m!/catalog.xml$!; true }, sub { true }, ) if -d($dir); } return -e($master) || -l($master) ? rm($master) : true unless @subcats; for(grep(!-d, $etcdir, $xmldir)) { my($parent) = dirname($_); my($mode) = getmode($parent); safechmod(0777, $parent); md($_); safechmod($mode, $parent); } my($mode) = getmode($xmldir); safechmod(0777, $xmldir); safechmod(0666, $master) if -e($master); mkxmlcatalog($master, map(qq[], sort(@subcats))); safechmod($mode, $xmldir); if(protect) { safechmod(0444, $master); safechmod(0555, $_) for($xmldir, $etcdir, $armdir); } } sub rebuildhspkg($) { my($armdir) = @_; my($dir) = path($armdir, "hspkg"); my(@pkgs); @pkgs = absls($dir) if hspkg and -d($dir); my($etcdir) = path($armdir, "etc"); my($file) = path($etcdir, "toast-hs-package.conf"); return -e($file) || -l($file) ? rm($file) : true unless @pkgs; if(!-d($etcdir)) { my($mode) = getmode($armdir); safechmod(0777, $armdir); md(path($etcdir)); safechmod($mode, $armdir); } my($mode) = getmode($etcdir); safechmod(0777, $etcdir); safechmod(0666, $file) if -e($file); writefile($file, "[]\n"); optrun("ghc-pkg", "--force", "-f", $file, "update", $_) for(@pkgs); optrmall("$file.old"); safechmod($mode, $etcdir); if(protect) { safechmod(0444, $file); safechmod(0555, $_) for($etcdir, $armdir); } } sub arm(@) { my($name, $version, $build, @urls) = @_; ($name, $version, $build) = build(@_) unless $build; return ($name, $version, $build) if isarmed($name, $version, $build); optmd(armdir); lock(armdir); my($mode) = (protect ? 0555 : 0777) & ~umask; if(getmode(armdir) != $mode) { dfs ( armdir, sub { safechmod($mode, $_) }, sub { true }, sub { true } ); } my(%link) = stdlinks; for(keys(%link)) { my($abs) = path(armdir, $_); if(!-e($abs) && !-l($abs)) { my($dir) = dirname($abs); if(!-d($dir)) { my($parent) = dirname($dir); safechmod(0777, $parent); md($dir); safechmod($mode, $parent); } safechmod(0777, $dir); ln($link{$_}, $abs); safechmod($mode, $dir); } } $build || error; my($rootdir) = path(pkgpath($name, $version, $build), rootdir); dfs ( $rootdir, sub { my($dir) = optpath(armdir, $_[0]); if(!-d($dir)) { safechmod(0777, dirname($dir)) if defined($_[0]); md($dir); } return true; }, sub { my($target) = optpath(armdir, $_[0]); safechmod(0777, dirname($target)); displace($target); relative ? relln($_, $target) : ln($_, $target); }, sub { safechmod($mode, optpath(armdir, $_[0])) } ); rebuildinfodir(armdir); rebuildxmlcatalog(armdir); rebuildhspkg(armdir); run(postarmprog) if postarmprog; unlock(armdir); if(autodisarm) { my($aversion, $abuild); for $aversion (allversions($name, crossversion ? undef : $version)) { for $abuild (allbuilds($name, $aversion)) { disarm($name, $aversion, $abuild) unless $aversion eq $version && $abuild == $build; } } } ($name, $version, $build); } sub disarm(@) { my(@nvb) = @_; # see nvb comment below my(@armdirs) = allarmdirs; my($i, $armdir, $anychanged); for $armdir (@armdirs) { lock($armdir); my($changed); whilebuild { my($name, $version, $build) = @_; my($rootdir) = path(pkgpath($name, $version, $build), rootdir); my(@dirmodes); -d($rootdir) && dfs # ignore broken packages ( $rootdir, sub { my($rel) = @_; my($armsubdir) = optpath($armdir, $rel); if(-d($armsubdir) && !-l($armsubdir)) { push(@dirmodes, getmode($armsubdir)); } return true; }, sub { my($rel) = @_; my($armfile) = path($armdir, $rel); while(-e($armfile) || -l($armfile)) { if(optsamefile($armfile, $_)) { safechmod(0777, dirname($armfile)); rm($armfile); my($off); while(-e($off = addoff($armfile)) || -l($off)) { mv($off, $armfile); $armfile = $off; } return $changed = true; } $armfile = addoff($armfile); } return true; }, sub { my($rel) = @_; my($armsubdir) = optpath($armdir, $rel); if(-d($armsubdir) && !-l($armsubdir)) { my($mode) = pop(@dirmodes); return safechmod($mode, $armsubdir) unless isempty($armsubdir); safechmod(0777, dirname($armsubdir)) if @dirmodes; rd($armsubdir); } return true; } ); error if @dirmodes; return true; } @nvb; # can't replace @nvb with ($n, $v, $b) due to perl 5.6.1 bug (?) rebuildinfodir($armdir) if $changed; rebuildxmlcatalog($armdir) if $changed; rebuildhspkg($armdir) if $changed; $anychanged ||= $changed; run(postarmprog) if ++$i == scalar(@armdirs) && $anychanged && postarmprog; unlock($armdir); } return true; } ############################################################################## sub findnewerpkg($$$@) { my($name, $version, $build, @urls) = @_; error unless defined($name); error unless defined($version); error if defined($build); @urls = pkgurls($name, $version) unless @urls; error unless @urls; # Look for new version numbers that start with a digit and that don't # contain dashes (as in "zsh-4.0.6-doc.tar.gz"), # unless the original version number lacked those properties. my($verchar) = $version =~ /-/ ? '.' : '[^\-]'; my($verpat) = $version =~ /^\d/ ? "\\d$verchar*" : "$verchar+"; my(%linkmap, %candidates, @newurls); my($hasver) = false; for(@urls) { if(/^(cvs.*)\#\Q$version\E$/) { my($newurl) = cleanurl($1); my(undef, $newv) = guessnv($newurl); push(@newurls, $newurl); $candidates{$newv} = $hasver = true; next; } m!^((http|ftp|file)://[^\?]+/)([^\?/]*)(\?.*)?$!i || error("bad URL for upgrade: $_"); my($dirname, $basename, $query) = undeftoempty($1, $3, $4); if($basename !~ /^(.*)\Q$version\E(.*)$/) { push(@newurls, $_); # URL has no version number; use as-is } else { $hasver = true; my($pre, $post) = ($1, $2); $linkmap{$dirname} = [linksfromurl($dirname)] unless exists($linkmap{$dirname}); my(@links) = @{$linkmap{$dirname}}; my(%vermap); for(@links) { if(m!/\Q$pre\E($verpat)\Q$post\E(\?.*)?$!) { $vermap{$1} = $_; $candidates{$1} = 1; } } push(@newurls, \%vermap); } } return &findnewerpkg($name, $version, $build, @urls) if !$hasver && $version =~ s/-toast\d+$//; my($pkgname) = pkgname($name, $version); error("URLs for $pkgname don't seem version-specific") unless $hasver; my(@result); for(sort cmpab keys(%candidates)) { my($candidate) = $_; my($ok) = true; my(@curls) = @newurls; for(@curls) { next unless ref; my(%vermap) = %$_; if(!exists($vermap{$candidate})) { $ok = false; last; } $_ = $vermap{$candidate}; } push(@result, [$name, $candidate, undef, @curls]) if $ok; } error("can't find consistent URLs for $pkgname") unless @result; return @result; } sub find(@) { my($name, $version, $build, @urls) = @_; error unless defined($name); error if defined($build); my(@pkgs) = defined($version) && (@urls || isadded($name, $version)) ? findnewerpkg($name, $version, $build, @urls) : findnewpkg($name, $version); error unless @pkgs; $name = ${$pkgs[0]}[0]; $name eq ${$pkgs[$_]}[0] or error for 0..$#pkgs; say("\n"); print("$name\n"); for(@pkgs) { (undef, $version, undef, @urls) = @$_; $version = "unknown" unless defined($version); print(" version $version: found\n"); print(" urls:\n"); print(" $_\n") for(@urls); } return true; } sub upgrade(@) { my($name, $version, $build, @urls) = @_; error if defined($build); my(@pkgs) = reverse(choose($version, findnewerpkg($name, $version, $build, @urls))); my(@cmdargs) = @{$pkgs[0]}; my($newver) = $cmdargs[1]; my($pkgname) = pkgname($name, $version); return ($name, $version, $build, @urls) if interactive && $version eq $newver; error("$pkgname appears to be the latest available version") if $version eq $newver; my(@sortvers) = sort cmpab ($version, $newver); error("only found older versions of $pkgname") if $sortvers[1] eq $version && !interactive; if(autoarm && isarmedmatch($name, $version)) { return arm(@cmdargs); } elsif(isbuiltmatch($name, $version)) { return build(@cmdargs); } elsif(isstored($name, $version)) { return get(@cmdargs); } else { return add(@cmdargs); } } ############################################################################## sub ensuredisarmed($;$$) { my($name, $version, $build) = @_; return true unless isarmed($name, $version, $build); error(pkgname($name, $version, $build) . " is armed") unless autodisarm; disarm($name, $version, $build); } ############################################################################## sub remove(@) { my($name, $version, $build, @urls) = @_; $name || error; ensuredisarmed($name, $version, $build); rmall(pkgpath($name, $version, $build)); } sub demolish(@) { my($name, $version, $build, @urls) = @_; whilebuild { remove(@_) } @_; } ############################################################################## sub printopts($;$) { my($name, $version) = @_; return true unless showopts; my($indent) = defined($version) ? " " : ""; my($opts) = loadopts($name, $version); return unless %{$opts}; print("$indent options:\n"); for(sort(keys(%{$opts}))) { my($value) = $opts->{$_}; print("$indent --" . (!isboolopt($_) ? "$_=" . quote($value) : $value ? "$_" : "no$_") . "\n"); } } sub status(@) { my($name, $version, $build, @urls) = @_; my($result) = true; my(@armdirs) = allarmdirs; for $name (allnames($name)) { print("$name\n"); printopts($name); for $version (allversions($name, $version)) { print(" version $version", isstored($name, $version) ? ": stored\n" : "\n"); if(showurls || @urls) { my(@haveurls) = pkgurls($name, $version); my($mismatch) = !@urls || samelist(@haveurls, @urls) ? "" : " differ from those specified"; $result = false if $mismatch; if(showurls || $mismatch) { print(" urls$mismatch:\n"); print(" $_\n") foreach @haveurls; } } printopts($name, $version); for $build (allbuilds($name, $version, $build)) { my($status, @notes, @armedin); my($normalarmdir) = armdir; if(isbroken($name, $version, $build)) { $status = "broken"; } elsif(!isbuilt($name, $version, $build)) { $status = "building"; } else { push(@notes, "not clean") unless isclean($name, $version, $build); for(@armdirs) { push(@armedin, $_) if isbuildarmedin($_, $name, $version, $build); } if(ismismatched($name, $version, $build)) { $status = "mismatched"; my($armdirlink) = path(pkgpath($name, $version, $build), armdirlink); my($builtfor) = readlink($armdirlink); push(@notes, "built for $builtfor") if defined($builtfor); push(@notes, "armed") if @armedin; $normalarmdir = $armdirlink; } elsif(@armedin) { $status = "armed"; } else { $status = "built"; } } my($notetext) = @notes ? " (" . join("; ", @notes) . ")" : ""; print(" build $build: $status$notetext\n"); if(@armedin && (scalar(@armedin) > 1 || !optsamefile($armedin[0], $normalarmdir))) { print(" armed in:\n"); print(" $_\n") for @armedin; } } } } return $result; } ############################################################################## sub edit(@) { my($name, $version, $build, @urls) = @_; error unless defined($name); error if defined($build); ($name, $version) = get($name, $version, $build, @urls) unless defined($version) && isstored($name, $version); error unless defined($name); error unless defined($version); my($suffix) = "-$myname"; my($oldversion) = $version =~ /^(.*)(\Q$suffix\E\d+)$/ ? $1 : $version; error unless defined($oldversion); error(pkgname($name, $oldversion) . " is no longer stored") unless isstored($name, $oldversion); my($oldverdir) = pkgpath($name, $oldversion); my($oldarchivedir) = path($oldverdir, archivedir); my($verdir) = pkgpath($name, $version); my($archivedir) = path($verdir, archivedir); my($num) = 1; my($newversion, $newverdir); while(true) { $newversion = $oldversion . $suffix . $num; $newverdir = pkgpath($name, $newversion); last if mkdir($newverdir, 0777); error("mkdir $newverdir: $!") unless -d($newverdir); $num++; } announce("mkdir", $newverdir); my($newarchivedir) = path($newverdir, archivedir); my($editdir) = path($newverdir, editdir); my($olddir) = path($editdir, "old"); my($newdir) = path($editdir, "new"); my($patchfilename) = "edit.patch"; my($patchfile) = path($editdir, $patchfilename); md($editdir, $olddir, $newdir); chownnonroot($olddir, $newdir); local(*PATCH); safeopen(*PATCH, ">", $patchfile); my($pid) = fork; error("fork: $!") unless defined($pid); if(!$pid) # child { dropprivs; extract($oldarchivedir, $olddir); extract($archivedir, $newdir); my($prog) = length(editprog) ? editprog : exists($ENV{SHELL}) ? $ENV{SHELL} : "/bin/sh"; cdrun(makedir($newdir), $prog); local(*DIFF, $_); openprog(*DIFF, "diff", "-urN", $olddir, $newdir); while() { s!^(\+\+\+|---) (\Q$olddir\E|\Q$newdir\E)/!$1 !; print PATCH $_; } close(DIFF); # ignore error close(PATCH) or error("close $patchfile: $!"); exit(0); error; } my($errmsg); waitpid($pid, 0) or error("waitpid $pid: $!"); $errmsg ||= "subprocess returned $?" unless $? == 0; close(PATCH) or error("close $patchfile: $!"); $errmsg ||= "no changes found" unless -s($patchfile); if($errmsg) { rmall($newverdir); error($errmsg); } rmall($olddir, $newdir); my(@newurls) = pkgurls($name, $oldversion); push(@newurls, fileurl($patchfile)); setpkgurls($name, $newversion, @newurls); md($newarchivedir); fordir { ln(path($oldarchivedir, $_), path($newarchivedir, $_)) } $oldarchivedir; ln($patchfile, path($newarchivedir, $patchfilename)); return build($name, $newversion, undef, @newurls); } ############################################################################## sub rename(@) { my($sname, $sversion, $sbuild, $dname, $dversion, $dbuild) = @_; defined($sname) == defined($dname) || error; defined($sversion) == defined($dversion) || error; defined($sbuild) == defined($dbuild) || error; defined($sname) || error; defined($sbuild) && !defined($sversion) && error; my($sdir) = pkgpath($sname, $sversion, $sbuild); my($ddir) = pkgpath($dname, $dversion, $dbuild); -d($sdir) || error(pkgname($sname, $sversion, $sbuild) . " does not exist"); -d($ddir) && error(pkgname($dname, $dversion, $dbuild) . " already exists"); isarmed($sname, $sversion, $sbuild) && error(pkgname($sname, $sversion, $sbuild) . " is armed"); my($namedir) = pkgpath($dname); optmd($namedir) unless $ddir eq $namedir; my($versiondir) = pkgpath($dname, $dversion); optmd($versiondir) unless $ddir eq $versiondir; mv($sdir, $ddir); my($oldnamedir) = pkgpath($sname); rmdir($oldnamedir) && announce("rmdir", $oldnamedir); return !defined($dversion) ? ($dname) : !defined($dbuild) ? ($dname, $dversion) : ($dname, $dversion, $dbuild); } ############################################################################## sub change(@) { my($name, $version, $build, @urls) = @_; error unless defined($name); error unless defined($version); error if defined($build); error unless @urls; setpkgurls($name, $version, @urls); ($name, $version); } ############################################################################## BEGIN { my($checkresult); sub failcheck(@) { print(join(": ", @_) . "\n"); $checkresult = false; return true; } sub checkbuild($$$) { my($name, $version, $build) = @_; my($path) = pkgpath($name, $version, $build); return failcheck($path, "not a directory") unless -d($path); # @todo check various fordir { /^(src|root|helpers|build\.log\.tmp|build\.log|broken\.log)$/ || failcheck(path($path, $_)); } $path; } sub checkversion($$) { my($name, $version) = @_; my($path) = pkgpath($name, $version); failcheck($path, "invalid version number") unless validversion($version); return failcheck($path, "not a directory") unless -d($path); my($urlfile) = path($path, urlfile); # @todo check urlfile my($archivedir) = path($path, archivedir); # @todo check archivedir fordir { validbuild($_) ? checkbuild($name, $version, $_) : /^(archive|url)(\.tmp)?$/ || failcheck(path($path, $_)); } $path; } sub checkname($) { my($name) = @_; my($path) = pkgpath($name); failcheck($path, "invalid package name") unless validname($name); return failcheck($path, "not a directory") unless -d($path); fordir { s/^v// ? checkversion($name, $_) : failcheck(path($path, $_)); } $path; } sub checkall() { my($path) = pkgpath; return failcheck($path, "not found") unless -e($path); return failcheck($path, "not a directory") unless -d(_); # @todo check permissions fordir { checkname($_) } $path; } sub check(@) { my($name, $version, $build) = @_; $checkresult = true; defined($build) ? checkbuild($name, $version, $build) : defined($version) ? checkversion($name, $version) : defined($name) ? checkname($name) : checkall; my($msg) = "$myname check "; $msg .= $checkresult ? "passed" : "failed"; $msg .= ": " . pkgname($name, $version, $build) if defined($name); print("$msg\n"); return $checkresult; } } ############################################################################## sub selfopen(*) { local(*SCRIPT) = @_; my($package, $file) = caller(0); safeopen(*SCRIPT, "<", $file); } sub depodify($) { local($_) = @_; s/I\<([^\>]+)\>/\*$1\*/g; s/C\<([^\>]+)\>/\`$1\'/g; s/[A-Z]\<|\>//g; s/^=(over|back).*\r?\n?//gm; s/^=\w+\s+//gm; s/\n\n\n+/\n\n/g; $_; } sub help(@) { my(@topics) = @_; @topics = ("commands") unless @topics; print << "EOF"; $myname version $myversion -- $myurl $mycopyright $myname comes with ABSOLUTELY NO WARRANTY; for details run "$myname license". EOF local(*SCRIPT, $_); selfopen(*SCRIPT); my($usage); while(