diff --git a/script.d/common.sh b/script.d/common.sh index 461277f..cf3e4bb 100644 --- a/script.d/common.sh +++ b/script.d/common.sh @@ -4,7 +4,11 @@ TMD_OPTS="--tt-id=K" TMP_FILES=(Another nsmb.d XmasNewer NewerFiles "Newer*Summer*Sun" \ ZPW_1.1.ips Epic_Super_Bowser_World_v1.00 Riivolution Koopa \ Cannon_Super_Mario_Bros._Wii_v1.1 riivolution "Readme*" "*.txt" "*.rtf" \ -"*.dol" "*.elf" nsmb) +"*.dol" "*.elf" nsmb "Retro Remix") + +if [[ -e $HOME/.patchimage.rc ]]; then + source $HOME/.patchimage.rc +fi setup_tools () { @@ -134,20 +138,18 @@ check_riivolution_patch () { if [[ ${DOWNLOAD_LINK} ]]; then if [[ ! -f "${RIIVOLUTION_ZIP}" ]]; then wget --no-check-certificate ${DOWNLOAD_LINK} -O "${RIIVOLUTION_ZIP}" - if [[ "${RIIVOLUTION_ZIP}" == *.zip ]]; then - unzip "${RIIVOLUTION_ZIP}" >/dev/null - elif [[ "${RIIVOLUTION_ZIP}" == *.rar ]]; then - unrar x "${RIIVOLUTION_ZIP}" >/dev/null - fi + tools/unp "${RIIVOLUTION_ZIP}" >/dev/null fi else echo "no download link for ${GAMENAME} available." exit 1 fi elif [[ -f "${RIIVOLUTION_ZIP}" && ! -d "${RIIVOLUTION_DIR}" ]]; then - unzip "${RIIVOLUTION_ZIP}" >/dev/null + tools/unp "${RIIVOLUTION_ZIP}" >/dev/null + elif [[ -f "${PATCHIMAGE_RIIVOLUTION_DIR}"/"${RIIVOLUTION_ZIP}" && ! -d "${RIIVOLUTION_DIR}" ]]; then + tools/unp "${PATCHIMAGE_RIIVOLUTION_DIR}"/"${RIIVOLUTION_ZIP}" >/dev/null elif [[ ! -d "${RIIVOLUTION_DIR}" ]]; then - echo -e "please specify zip to use with --riivolution=" + echo -e "please specify zip/rar to use with --riivolution=" exit 1 fi @@ -190,11 +192,7 @@ while [[ $xcount -lt $pcount ]]; do --riivolution* ) RIIVOLUTION=${1/*=} if [[ -e "${RIIVOLUTION}" ]]; then - if [[ "${RIIVOLUTION}" == *.zip ]]; then - unzip "${RIIVOLUTION}" >/dev/null - elif [[ "${RIIVOLUTION}" == *.rar ]]; then - unrar x "${RIIVOLUTION}" >/dev/null - fi + tools/unp "${RIIVOLUTION}" >/dev/null else echo -e "Riivolution patch ${RIIVOLUTION} not found." exit 1 @@ -204,11 +202,7 @@ while [[ $xcount -lt $pcount ]]; do --patch* ) PATCH=${1/*=} if [[ -e "${PATCH}" ]]; then - if [[ "${PATCH}" == *.zip ]]; then - unzip "${PATCH}" >/dev/null - elif [[ "${PATCH}" == *.rar ]]; then - unrar x "${PATCH}" >/dev/null - fi + tools/unp "${PATCH}" >/dev/null else echo -e "PATCH patch ${PATCH} not found." exit 1 diff --git a/tools/ucat b/tools/ucat new file mode 100755 index 0000000..cca2735 --- /dev/null +++ b/tools/ucat @@ -0,0 +1,628 @@ +#! /usr/bin/perl +# +# "unp" runs the correct unpack program depending on the file extension +# of the given parameter. +# +# Author: Eduard Bloch , 2010 +# +# UI modelled after unp versions 1.x by Eduard Bloch (2000-2009) and original +# unp by Andre Karwath (1997). +# +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# ----------------------------------------------------------------------- +# If you make changes to this script, please feel free to forward the new +# version to the author. +# ----------------------------------------------------------------------- + +require 5.002; + +use Getopt::Long qw(:config no_ignore_case bundling pass_through); +use File::Basename; +use Cwd; + +use strict; +use warnings; +#use diagnostics; + +my @ifiles; +my @xargs; +my @tools; +my $retcode=0; + +my $opt_quiet; +my $opt_debug; +my $opt_help; +my $opt_force; +my $opt_formats; +my $opt_umode; +my $opt_umode_smart; +my $opt_verbose; + +BEGIN { + eval 'use Locale::gettext'; + if ($@) { + eval q{ sub gettext { return shift; } }; + } else { + textdomain('unp'); + } +} + +my $catmode = $0=~/(^|\/ucat)$/; + +&init_formats; +&parse_cli; +&process_files; +print STDERR "WARNING: There were errors while processing files!\n" if $retcode; +print STDERR "retcode: $retcode\n" if $opt_debug; +# return something meaning, i.e. it may be single unpacker's only return code +# if the status is bad but the value is zero, return at least 1 +exit ( $retcode ? ( $retcode < 256 ? $retcode : 1 ) : 0 ); + +sub show_help +{ +printf(gettext(" +USAGE: + %s [ options ] file [ files... ] + file: compressed file(s) to expand/extract + + Use -- [ ARGUMENTS ] to pass arguments to external programs, eg. some tar options: + unp fastgl.tgz xmnt.tgz -- -C /tmp + + Options: + -f Continue even if program availability checks fail or directory collision occurs + -u Special helper mode. + For most archive types: + - create directory / + - extract contents there + For Debian/Ubuntu packages: + - extract data.tar.gz after each operation in local directory + - extract control.tar.gz into control// + -U Smart mode, acts like -u (see above) if archive contains multiple + elements but if there is only one file/directory element then it's stored + in the current directory. + -s Show the list of supported formats + -v More verbosity + -h Show this help +"), $0) if !$catmode; +printf(gettext(" +USAGE: + %s [ options ] file [ files... ] + Uncompress multiple files to STDOUT + + Options: + -s Show the list of supported formats + -h Show this help + -v More verbosity, to STDERR +"), $0) if $catmode; +exit 1; +} + + +sub init_formats +{ + my $sh="/bin/sh"; + +# Database format: +# - providing packages (human readable) +# - filename suffix, +# - libmagic (file tool) pattern, +# - process flags (bitfield), +# - array of command sets (=arrays of command and arguments). If the first arg +# of the command set is an array then it's interpreted as a list of +# requirements which need to be checked. +# +# Flag values: +# 2: retry other tools calls if the first candidate failed +# 4: pass source and extra args in different order (src args...), good for +# shell commands using $0 and $@ +# 8: stream filters, contents may be extracted if tar format is detected. Only +# simple syntax allowed. +# 16: append suggested target name to calling arguments +use constant +{ + TRY_OTHER_TOOLS => 2, + SHELL_STYLE_ARGS => 4, + IS_STREAM_FILTER => 8, + APPEND_GEN_NAME => 16 +}; + + @tools = ( + [ "tar", "tar", "tar.archive", 0, + [ "tar", "-x", "-v", "-f"] + ], + + # shortcuts, no extra scripting needed + [ gettext("tar with gzip"), "tgz|tar.gz", undef, 0, + [[ "gzip" ], "tar", "-z", "-x", "-v", "-f"] + ], + + [ gettext("tar with bzip2"), "tar.bz2|tbz2", undef, 0, + [ [ "bzip2" ], "tar", "--bzip2", "-x", "-v", "-f"] + ], + + [ gettext("tar with xz-utils"), "tar.xz|txz", undef, 0, + + [ [ "xz" ], "tar", "--xz", "-x", "-v", "-f"] + ], + + [gettext( "tar with lzip"), "tar.lzip", undef, 0, + + [[ "lzip" ], "tar", "--lzip", "-x", "-v", "-f"] + ], + + [gettext( "tar with lzop"), "tar.lzop|tzo", undef, 0, + + [[ "lzop" ], "tar", "--lzop", "-x", "-v", "-f"] + ], + + [gettext( "tar with compress"), "tar.z", undef, 0, + + [ [ "compress" ], "tar", "-Z", "-x", "-v", "-f"] + ], + + # XXX: that's ok for now but if support for other unpackers is needed + # (like multithreaded implementations) than ucat code needs to be + # extended to check them + [ "gzip", "gz", "gzip.compressed.data", IS_STREAM_FILTER, + [ "gzip", "-cd" ] + ], + + [ "bzip2", "bz2", "bzip2.compressed", IS_STREAM_FILTER, + [ "bzip2", "-cd" ] + ], + + [ "lzop", "lzo", "lzop.compressed", IS_STREAM_FILTER, + [ "lzop", "-cd" ] + ], + + [ "xz-utils", "xz", "xz.compressed", IS_STREAM_FILTER, + [ "xzcat" ] + ], + + [ "lzip", "lz", "lzip.compressed", IS_STREAM_FILTER, + [ "lzip", "-cd" ] + ], + + [ gettext("xz-utils or lzma"), "lzma", "lzma.compressed", IS_STREAM_FILTER|TRY_OTHER_TOOLS, + [ "xzcat" ], + [ "lzcat" ] + ], + + [ gettext("cpio or afio"), "cpio|afio", "cpio", SHELL_STYLE_ARGS, + [ "afio", "-Z", "-v", "-i" ], + [ ["cpio"], $sh, "-c", 'cpio -i -d --verbose "$@" < "$0"' ] + ], + + [ gettext("rpm2cpio and cpio"), "rpm", 'PPM\ v', SHELL_STYLE_ARGS, + [ ["rpm2cpio", "cpio"], $sh, "-c", 'rpm2cpio < "$0" | cpio -i -d --verbose "$@"' ] + ], + + [ gettext("formail and mpack"), "mbox", "(mail.text)|news", SHELL_STYLE_ARGS, + [ ["formail","munpack"], $sh, "-c", 'formail -s munpack "$@" < "$0"' ] + ], + + [ gettext("libchm-bin or archmage"), "chm", "Windows HtmlHelp Data", APPEND_GEN_NAME, + [ 'extract_chmLib' ], + [ 'archmage'] + ], + + [ gettext("rar or unrar or unrar-free"), "rar", "RAR.*archive", 0, + [ "rar", "x" ], + [ "unrar", "x" ] + ] + , + [ "binutils", "ar|deb", "(Debian binary package|\ ar.*archive)", 0, + [ "ar", "-x", "-v" ] + ] + , + [ "unzip", "zip|cbz|cbr|jar|war|ear|xpi|adf", "Zip.*archive", 0, + [ "unzip" ] + ] + , + [ "lha", "lha|lzh", "LHa.*archive", 0, + [ "lha", "x" ] + ] + , + [ "arj", "arj", "ARJ.*archive", 0, + [ "arj", "x" ], + [ "unarj", "x" ], + ] + , + [ "ppmd", "pmd", "PPmd.*archive", 0, + [ "PPMd", "x" ] + ] + , + [ "zoo", "zoo", "Zoo.*archive", 0, + [ "unzoo", "-x" ] + ] + , + [ "sharutils", "shar", "shell.*archive", 0, + [ "unshar" ] + ] + , + [ "sharutils", "uu", "uuencoded", 0, + [ "undecode" ] + ] + , + [ "tnef", "dat", "Transport Neutral Encapsulation Format", 0, + [ "tnef", "-v" ] + ] + , + [ gettext("p7zip or p7zip-full"), "7z", "7-zip.*archive", 0, + [ "7z", "x" ] + ] + , + [ "cabextract", "cab", "CAB file", 0, + [ "cabextract" ] + ] + , + [ "unace", "ace", "ACE.*archive", 0, + [ "unace", "e" ] + ] + , + [ "xdms", "dms", "DMS.*archive", 0, + [ "xdms", "x" ] + ] + , + [ "unlzx", "lzx", "LZX.*archive", 0, + [ "unace", "e" ] + ] + , + [ "macutils", "sea|sea\.bin", "SEA.*archive", 0, + [ "macutils", "-v" ] + ] + , + [ "macutils", "hqx", "BinHex.binary", 0, + [ "hexbin", "-v" ] + ] + , + [ "maybe orange or unzip or unrar or unarj or lha ", "exe", "executable", 3, + [ "orange" ], + [ "unzip" ], + [ "unrar", "x" ], + [ "rar", "x" ], + [ "arj", "x" ], + [ "lha", "x" ] + ] + +); +} + +sub show_formats +{ + print "Known archive formats and tools:\n"; + my %t; + my $len=0; + foreach my $line (@tools) + { + my @dset = @$line; + my $sux = $dset[1]; + $sux=~s/\|/,/g; + $t{$sux}=$dset[0]; + $len=length($sux) if(length($sux)>$len && length($sux)<20); + #print $sux . ":\t\t$dset[0]\n"; + } + foreach (sort (keys %t)) + { + print "$_:"; + my $diff=$len-length($_); + print " "x$diff." $t{$_}\n"; + } + exit 1; +} + +sub parse_cli +{ + my %options = ( + "q|quiet" => \$opt_quiet, + "d|debug" => \$opt_debug, + "h|help" => \$opt_help, + "f|force" => \$opt_force, + "s|show-formats" => \$opt_formats, + "u|to-subdir" => \$opt_umode, + "U|smart-subdir" => \$opt_umode_smart, + "v|verbose" => \$opt_verbose + ); + &show_help unless ( GetOptions(%options)); + &show_help if ($opt_help); + &show_formats if ($opt_formats); + while(@ARGV) + { + if("--" eq $ARGV[0]) + { + shift(@ARGV); + @xargs = @ARGV; + last; + } + push(@ifiles, shift(@ARGV)); + } + print STDERR join(" ; ", "ifiles", @ifiles, "xargs", @xargs, "argv", @ARGV, "\n") if $opt_debug; +} + +sub try_unarch +{ + my $ifile=shift; + my $magicdata=shift; + print STDERR "magic string: $magicdata\n" if $opt_debug && $magicdata; + UNPIFILE: foreach my $line (@tools) + { + my ($name, $suxARG, $patARG, $cmdflags) = @$line; + + print STDERR "hm, $magicdata vs. $patARG\n" if ( $opt_debug && $magicdata); + + # needs magic data to test against + next if(defined($magicdata) && !defined($patARG)); + + next if($catmode && ! ($cmdflags & IS_STREAM_FILTER)); + + if( + (defined($magicdata) && $magicdata=~/$patARG/i) + || ( !defined($magicdata) && $ifile =~ /.*\.($suxARG)$/i) ) + { + print STDERR "got unpacker description for $ifile\n" if $opt_debug; + my $misscount=0; + my @dset = @$line; + + TOOL: foreach my $pArgs (@dset[4..$#dset]) + { + my @args=@$pArgs; + my @prqs = ($args[0]); + + # there is a list of prqs prepended, use that one and weed out the ref + @prqs=@{shift(@args)} if( ref($args[0]) eq "ARRAY"); + + my $misscountCur=0; + foreach(@prqs) + { + if(!which($_)) + { + $misscountCur++; + $misscount++; + } + } + + # if all tools are here? Let's start... + if(! $misscountCur) + { + my $rcodeprev=$retcode; + $retcode += ($catmode ? &cat_one($ifile, $line, @args) : &unpack_one($ifile, $line, @args)); + + return 1 if($rcodeprev == $retcode); + + # try other tools if hinted but never in cat mode in order to prevent data corruption + return 0 if($catmode); + next TOOL if($cmdflags & TRY_OTHER_TOOLS); + } + } + if($misscount) + { + print STDERR gettext("Error, following packages must be installed in order to proceed:\n").$name."\n"; + exit 1; + } + } + } + return 0; +} + +sub getmagic +{ + my $path=shift; + print STDERR "getting magic value from $path\n" if $opt_debug; + if(open(my $fd, "-|", "file", "-L", $path)) + { + my $fileret=scalar <$fd>; + #print STDERR "got: $fileret\n" if $opt_debug; + close $fd; + chomp $fileret; + return $fileret; + } + return "G.N.D.N."; +} + +sub cat_file +{ + my $file=shift; + if(open(my $fd, $file) ) + { + my $buf; + while(my $len=sysread($fd, $buf, 1<<16, 0)) + { + my $off=0; + my $res; + while($res=syswrite(STDOUT, $buf, $len, $off)) + { + die "Failed to print: $!\n" if !defined($res); + last if 0==$res; + $off+=$res; + last if $off>=$len; + } + } + close $fd || $retcode++; + } + else { $retcode++; } +} + +sub process_files +{ + IFILE: + foreach my $ifile (@ifiles) + { + if(!-r $ifile) + { + printf STDERR gettext("Cannot read %s, skipping...\n"), $ifile; + $retcode++; + next IFILE; + } + + next if (&try_unarch($ifile) or &try_unarch($ifile, getmagic($ifile))); + + printf STDERR gettext("Failed to detect file type of %s.\n"), $ifile; + + # print the file as is in cat mode, otherwise remember that problem + $retcode += ($catmode ? system("cat", $ifile) : 1); + } +} + +sub which +{ + my $prog=shift; + for(split(/:/,$ENV{"PATH"})) { + if(-x "$_/$prog") { + return 1; + } + } + return undef; +} + +sub cat_one +{ + print STDERR "\ncat_one: @_\n" if $opt_debug; + my $file=shift; + my $toolRef=shift; + my $flags=$toolRef->[3]; + my @cmd = (SHELL_STYLE_ARGS & $flags) ? (@_, $file, @xargs) : (@_, @xargs, $file); + print STDERR join(" ", "test cmd line: ", @cmd, "\n") if $opt_debug; + return (system(@cmd) >> 8); +} + +sub unpack_one +{ + print STDERR "unpack_one: @_\n" if $opt_debug; + + my $file=shift; + my $toolRef=shift; + my $cwd=getcwd; + + my $ret=1; + my $flags=@{$toolRef}[3]; + + my $sufpat=@{$toolRef}[1]; + my $tgtname=basename($file); + $tgtname=~s/(.*)\.($sufpat)$/$1/i; + + return special_debmode($file) if($opt_umode && $file=~/\.deb$/i); + + # make sure that target file/directory for certain types is not occupied + if( ( $opt_umode_smart || $opt_umode || ( $flags & (IS_STREAM_FILTER|APPEND_GEN_NAME)) ) && -e $tgtname) + { + printf STDERR (gettext( + "Cannot create target %s: file already exists. Trying alternative targets...\n"), + $tgtname); + $tgtname.=".unp"; + if(-e $tgtname) + { + print STDERR sprintf(gettext( + "Cannot create target %s: file already exists\n"), $tgtname); + $tgtname.=".".rand; + } + if(-e $tgtname) + { + print STDERR sprintf(gettext( + "Cannot create target %s: file already exists\n"), $tgtname); + exit 1 if $opt_force; + } + print STDERR "Suggested target name: $tgtname\n"; + } + print STDERR "tgtname: $tgtname\n" if $opt_debug; + + my $tmpdir; + + if($opt_umode || $opt_umode_smart) + { + $file=Cwd::abs_path($file); + print STDERR "set abs.path to $file\n" if $opt_debug; + $tmpdir="unp.".rand; + mkdir $tmpdir; + chdir($tmpdir) || return 23; + } + + my @cmd = (SHELL_STYLE_ARGS & $flags) ? (@_, $file, @xargs) : (@_, @xargs, $file); + + + print STDERR "temp.cmd: ".join("\t", @cmd, "\n") if $opt_debug; + + # filter commands... use shell to keep our code simple. Tar stream may be + # inside, detect it and unpack it. + if(IS_STREAM_FILTER & $flags) + { + my $magic=""; + @cmd=("sh", "-c", join(" ", @_).' "$0" | file -', $file); + if(open(my $fh, "-|", @cmd)) + { + $magic = join('', <$fh>); + close($fh); + } + print STDERR "Internal magic: $magic\n" if $opt_debug; + if($magic=~/tar.archive/) + { + @cmd=("sh", "-c", join(" ", @_).' "$0" | tar -v -x -f - "$@" ', $file, @xargs); + } + else + { + @cmd=("sh", "-c", join(" ", @_).' "$0" > "$1"', $file, $tgtname); + } + } + + push(@cmd, $tgtname) if(APPEND_GEN_NAME & $flags); + + print STDERR join(" ", "test cmd line: ", @cmd, "\n") if $opt_debug; + + $ret = (system(@cmd) >> 8); + + if( $opt_umode_smart) + { + chdir ".."; + my @cont=(<$tmpdir/*>, <$tmpdir/.*>); + # . and .. and one element? + if(3==@cont) + { + # use same name as target, fall back to checked tgtname if that is already occupied + my $cand=basename($cont[0]); + if (-e $cand) + { + print STDERR gettext("Cannot create target directory (already exists), using alternative name\n"); + $cand=$tgtname ; + } + return 44 if ! (rename($cont[0], $cand) && rmdir $tmpdir); + } + else + { + rename $tmpdir, $tgtname || return 43; + } + + } + elsif ($opt_umode) + { + chdir ".."; + rename($tmpdir, $tgtname) || return 42; + } + + chdir $cwd; + + return $ret; +} + +sub special_debmode +{ + my $file=shift; + basename($file)=~/^(.*)\.deb$/; + my $bname=$1; + die "cannot recognice package name\n" if !$bname; + + mkdir "control"; + my $contgt="control/$bname"; + mkdir $contgt; + + return (system("ar", "x", $file) >> 8 ) + + (system("tar", "-z", "-x", "-v", "-f", "control.tar.gz", "-C", + $contgt) >> 8 ) + + ( system("tar", "-z", "-x", "-v", "-f", "data.tar.gz") >> 8 ); +} diff --git a/tools/unp b/tools/unp new file mode 100755 index 0000000..cca2735 --- /dev/null +++ b/tools/unp @@ -0,0 +1,628 @@ +#! /usr/bin/perl +# +# "unp" runs the correct unpack program depending on the file extension +# of the given parameter. +# +# Author: Eduard Bloch , 2010 +# +# UI modelled after unp versions 1.x by Eduard Bloch (2000-2009) and original +# unp by Andre Karwath (1997). +# +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# ----------------------------------------------------------------------- +# If you make changes to this script, please feel free to forward the new +# version to the author. +# ----------------------------------------------------------------------- + +require 5.002; + +use Getopt::Long qw(:config no_ignore_case bundling pass_through); +use File::Basename; +use Cwd; + +use strict; +use warnings; +#use diagnostics; + +my @ifiles; +my @xargs; +my @tools; +my $retcode=0; + +my $opt_quiet; +my $opt_debug; +my $opt_help; +my $opt_force; +my $opt_formats; +my $opt_umode; +my $opt_umode_smart; +my $opt_verbose; + +BEGIN { + eval 'use Locale::gettext'; + if ($@) { + eval q{ sub gettext { return shift; } }; + } else { + textdomain('unp'); + } +} + +my $catmode = $0=~/(^|\/ucat)$/; + +&init_formats; +&parse_cli; +&process_files; +print STDERR "WARNING: There were errors while processing files!\n" if $retcode; +print STDERR "retcode: $retcode\n" if $opt_debug; +# return something meaning, i.e. it may be single unpacker's only return code +# if the status is bad but the value is zero, return at least 1 +exit ( $retcode ? ( $retcode < 256 ? $retcode : 1 ) : 0 ); + +sub show_help +{ +printf(gettext(" +USAGE: + %s [ options ] file [ files... ] + file: compressed file(s) to expand/extract + + Use -- [ ARGUMENTS ] to pass arguments to external programs, eg. some tar options: + unp fastgl.tgz xmnt.tgz -- -C /tmp + + Options: + -f Continue even if program availability checks fail or directory collision occurs + -u Special helper mode. + For most archive types: + - create directory / + - extract contents there + For Debian/Ubuntu packages: + - extract data.tar.gz after each operation in local directory + - extract control.tar.gz into control// + -U Smart mode, acts like -u (see above) if archive contains multiple + elements but if there is only one file/directory element then it's stored + in the current directory. + -s Show the list of supported formats + -v More verbosity + -h Show this help +"), $0) if !$catmode; +printf(gettext(" +USAGE: + %s [ options ] file [ files... ] + Uncompress multiple files to STDOUT + + Options: + -s Show the list of supported formats + -h Show this help + -v More verbosity, to STDERR +"), $0) if $catmode; +exit 1; +} + + +sub init_formats +{ + my $sh="/bin/sh"; + +# Database format: +# - providing packages (human readable) +# - filename suffix, +# - libmagic (file tool) pattern, +# - process flags (bitfield), +# - array of command sets (=arrays of command and arguments). If the first arg +# of the command set is an array then it's interpreted as a list of +# requirements which need to be checked. +# +# Flag values: +# 2: retry other tools calls if the first candidate failed +# 4: pass source and extra args in different order (src args...), good for +# shell commands using $0 and $@ +# 8: stream filters, contents may be extracted if tar format is detected. Only +# simple syntax allowed. +# 16: append suggested target name to calling arguments +use constant +{ + TRY_OTHER_TOOLS => 2, + SHELL_STYLE_ARGS => 4, + IS_STREAM_FILTER => 8, + APPEND_GEN_NAME => 16 +}; + + @tools = ( + [ "tar", "tar", "tar.archive", 0, + [ "tar", "-x", "-v", "-f"] + ], + + # shortcuts, no extra scripting needed + [ gettext("tar with gzip"), "tgz|tar.gz", undef, 0, + [[ "gzip" ], "tar", "-z", "-x", "-v", "-f"] + ], + + [ gettext("tar with bzip2"), "tar.bz2|tbz2", undef, 0, + [ [ "bzip2" ], "tar", "--bzip2", "-x", "-v", "-f"] + ], + + [ gettext("tar with xz-utils"), "tar.xz|txz", undef, 0, + + [ [ "xz" ], "tar", "--xz", "-x", "-v", "-f"] + ], + + [gettext( "tar with lzip"), "tar.lzip", undef, 0, + + [[ "lzip" ], "tar", "--lzip", "-x", "-v", "-f"] + ], + + [gettext( "tar with lzop"), "tar.lzop|tzo", undef, 0, + + [[ "lzop" ], "tar", "--lzop", "-x", "-v", "-f"] + ], + + [gettext( "tar with compress"), "tar.z", undef, 0, + + [ [ "compress" ], "tar", "-Z", "-x", "-v", "-f"] + ], + + # XXX: that's ok for now but if support for other unpackers is needed + # (like multithreaded implementations) than ucat code needs to be + # extended to check them + [ "gzip", "gz", "gzip.compressed.data", IS_STREAM_FILTER, + [ "gzip", "-cd" ] + ], + + [ "bzip2", "bz2", "bzip2.compressed", IS_STREAM_FILTER, + [ "bzip2", "-cd" ] + ], + + [ "lzop", "lzo", "lzop.compressed", IS_STREAM_FILTER, + [ "lzop", "-cd" ] + ], + + [ "xz-utils", "xz", "xz.compressed", IS_STREAM_FILTER, + [ "xzcat" ] + ], + + [ "lzip", "lz", "lzip.compressed", IS_STREAM_FILTER, + [ "lzip", "-cd" ] + ], + + [ gettext("xz-utils or lzma"), "lzma", "lzma.compressed", IS_STREAM_FILTER|TRY_OTHER_TOOLS, + [ "xzcat" ], + [ "lzcat" ] + ], + + [ gettext("cpio or afio"), "cpio|afio", "cpio", SHELL_STYLE_ARGS, + [ "afio", "-Z", "-v", "-i" ], + [ ["cpio"], $sh, "-c", 'cpio -i -d --verbose "$@" < "$0"' ] + ], + + [ gettext("rpm2cpio and cpio"), "rpm", 'PPM\ v', SHELL_STYLE_ARGS, + [ ["rpm2cpio", "cpio"], $sh, "-c", 'rpm2cpio < "$0" | cpio -i -d --verbose "$@"' ] + ], + + [ gettext("formail and mpack"), "mbox", "(mail.text)|news", SHELL_STYLE_ARGS, + [ ["formail","munpack"], $sh, "-c", 'formail -s munpack "$@" < "$0"' ] + ], + + [ gettext("libchm-bin or archmage"), "chm", "Windows HtmlHelp Data", APPEND_GEN_NAME, + [ 'extract_chmLib' ], + [ 'archmage'] + ], + + [ gettext("rar or unrar or unrar-free"), "rar", "RAR.*archive", 0, + [ "rar", "x" ], + [ "unrar", "x" ] + ] + , + [ "binutils", "ar|deb", "(Debian binary package|\ ar.*archive)", 0, + [ "ar", "-x", "-v" ] + ] + , + [ "unzip", "zip|cbz|cbr|jar|war|ear|xpi|adf", "Zip.*archive", 0, + [ "unzip" ] + ] + , + [ "lha", "lha|lzh", "LHa.*archive", 0, + [ "lha", "x" ] + ] + , + [ "arj", "arj", "ARJ.*archive", 0, + [ "arj", "x" ], + [ "unarj", "x" ], + ] + , + [ "ppmd", "pmd", "PPmd.*archive", 0, + [ "PPMd", "x" ] + ] + , + [ "zoo", "zoo", "Zoo.*archive", 0, + [ "unzoo", "-x" ] + ] + , + [ "sharutils", "shar", "shell.*archive", 0, + [ "unshar" ] + ] + , + [ "sharutils", "uu", "uuencoded", 0, + [ "undecode" ] + ] + , + [ "tnef", "dat", "Transport Neutral Encapsulation Format", 0, + [ "tnef", "-v" ] + ] + , + [ gettext("p7zip or p7zip-full"), "7z", "7-zip.*archive", 0, + [ "7z", "x" ] + ] + , + [ "cabextract", "cab", "CAB file", 0, + [ "cabextract" ] + ] + , + [ "unace", "ace", "ACE.*archive", 0, + [ "unace", "e" ] + ] + , + [ "xdms", "dms", "DMS.*archive", 0, + [ "xdms", "x" ] + ] + , + [ "unlzx", "lzx", "LZX.*archive", 0, + [ "unace", "e" ] + ] + , + [ "macutils", "sea|sea\.bin", "SEA.*archive", 0, + [ "macutils", "-v" ] + ] + , + [ "macutils", "hqx", "BinHex.binary", 0, + [ "hexbin", "-v" ] + ] + , + [ "maybe orange or unzip or unrar or unarj or lha ", "exe", "executable", 3, + [ "orange" ], + [ "unzip" ], + [ "unrar", "x" ], + [ "rar", "x" ], + [ "arj", "x" ], + [ "lha", "x" ] + ] + +); +} + +sub show_formats +{ + print "Known archive formats and tools:\n"; + my %t; + my $len=0; + foreach my $line (@tools) + { + my @dset = @$line; + my $sux = $dset[1]; + $sux=~s/\|/,/g; + $t{$sux}=$dset[0]; + $len=length($sux) if(length($sux)>$len && length($sux)<20); + #print $sux . ":\t\t$dset[0]\n"; + } + foreach (sort (keys %t)) + { + print "$_:"; + my $diff=$len-length($_); + print " "x$diff." $t{$_}\n"; + } + exit 1; +} + +sub parse_cli +{ + my %options = ( + "q|quiet" => \$opt_quiet, + "d|debug" => \$opt_debug, + "h|help" => \$opt_help, + "f|force" => \$opt_force, + "s|show-formats" => \$opt_formats, + "u|to-subdir" => \$opt_umode, + "U|smart-subdir" => \$opt_umode_smart, + "v|verbose" => \$opt_verbose + ); + &show_help unless ( GetOptions(%options)); + &show_help if ($opt_help); + &show_formats if ($opt_formats); + while(@ARGV) + { + if("--" eq $ARGV[0]) + { + shift(@ARGV); + @xargs = @ARGV; + last; + } + push(@ifiles, shift(@ARGV)); + } + print STDERR join(" ; ", "ifiles", @ifiles, "xargs", @xargs, "argv", @ARGV, "\n") if $opt_debug; +} + +sub try_unarch +{ + my $ifile=shift; + my $magicdata=shift; + print STDERR "magic string: $magicdata\n" if $opt_debug && $magicdata; + UNPIFILE: foreach my $line (@tools) + { + my ($name, $suxARG, $patARG, $cmdflags) = @$line; + + print STDERR "hm, $magicdata vs. $patARG\n" if ( $opt_debug && $magicdata); + + # needs magic data to test against + next if(defined($magicdata) && !defined($patARG)); + + next if($catmode && ! ($cmdflags & IS_STREAM_FILTER)); + + if( + (defined($magicdata) && $magicdata=~/$patARG/i) + || ( !defined($magicdata) && $ifile =~ /.*\.($suxARG)$/i) ) + { + print STDERR "got unpacker description for $ifile\n" if $opt_debug; + my $misscount=0; + my @dset = @$line; + + TOOL: foreach my $pArgs (@dset[4..$#dset]) + { + my @args=@$pArgs; + my @prqs = ($args[0]); + + # there is a list of prqs prepended, use that one and weed out the ref + @prqs=@{shift(@args)} if( ref($args[0]) eq "ARRAY"); + + my $misscountCur=0; + foreach(@prqs) + { + if(!which($_)) + { + $misscountCur++; + $misscount++; + } + } + + # if all tools are here? Let's start... + if(! $misscountCur) + { + my $rcodeprev=$retcode; + $retcode += ($catmode ? &cat_one($ifile, $line, @args) : &unpack_one($ifile, $line, @args)); + + return 1 if($rcodeprev == $retcode); + + # try other tools if hinted but never in cat mode in order to prevent data corruption + return 0 if($catmode); + next TOOL if($cmdflags & TRY_OTHER_TOOLS); + } + } + if($misscount) + { + print STDERR gettext("Error, following packages must be installed in order to proceed:\n").$name."\n"; + exit 1; + } + } + } + return 0; +} + +sub getmagic +{ + my $path=shift; + print STDERR "getting magic value from $path\n" if $opt_debug; + if(open(my $fd, "-|", "file", "-L", $path)) + { + my $fileret=scalar <$fd>; + #print STDERR "got: $fileret\n" if $opt_debug; + close $fd; + chomp $fileret; + return $fileret; + } + return "G.N.D.N."; +} + +sub cat_file +{ + my $file=shift; + if(open(my $fd, $file) ) + { + my $buf; + while(my $len=sysread($fd, $buf, 1<<16, 0)) + { + my $off=0; + my $res; + while($res=syswrite(STDOUT, $buf, $len, $off)) + { + die "Failed to print: $!\n" if !defined($res); + last if 0==$res; + $off+=$res; + last if $off>=$len; + } + } + close $fd || $retcode++; + } + else { $retcode++; } +} + +sub process_files +{ + IFILE: + foreach my $ifile (@ifiles) + { + if(!-r $ifile) + { + printf STDERR gettext("Cannot read %s, skipping...\n"), $ifile; + $retcode++; + next IFILE; + } + + next if (&try_unarch($ifile) or &try_unarch($ifile, getmagic($ifile))); + + printf STDERR gettext("Failed to detect file type of %s.\n"), $ifile; + + # print the file as is in cat mode, otherwise remember that problem + $retcode += ($catmode ? system("cat", $ifile) : 1); + } +} + +sub which +{ + my $prog=shift; + for(split(/:/,$ENV{"PATH"})) { + if(-x "$_/$prog") { + return 1; + } + } + return undef; +} + +sub cat_one +{ + print STDERR "\ncat_one: @_\n" if $opt_debug; + my $file=shift; + my $toolRef=shift; + my $flags=$toolRef->[3]; + my @cmd = (SHELL_STYLE_ARGS & $flags) ? (@_, $file, @xargs) : (@_, @xargs, $file); + print STDERR join(" ", "test cmd line: ", @cmd, "\n") if $opt_debug; + return (system(@cmd) >> 8); +} + +sub unpack_one +{ + print STDERR "unpack_one: @_\n" if $opt_debug; + + my $file=shift; + my $toolRef=shift; + my $cwd=getcwd; + + my $ret=1; + my $flags=@{$toolRef}[3]; + + my $sufpat=@{$toolRef}[1]; + my $tgtname=basename($file); + $tgtname=~s/(.*)\.($sufpat)$/$1/i; + + return special_debmode($file) if($opt_umode && $file=~/\.deb$/i); + + # make sure that target file/directory for certain types is not occupied + if( ( $opt_umode_smart || $opt_umode || ( $flags & (IS_STREAM_FILTER|APPEND_GEN_NAME)) ) && -e $tgtname) + { + printf STDERR (gettext( + "Cannot create target %s: file already exists. Trying alternative targets...\n"), + $tgtname); + $tgtname.=".unp"; + if(-e $tgtname) + { + print STDERR sprintf(gettext( + "Cannot create target %s: file already exists\n"), $tgtname); + $tgtname.=".".rand; + } + if(-e $tgtname) + { + print STDERR sprintf(gettext( + "Cannot create target %s: file already exists\n"), $tgtname); + exit 1 if $opt_force; + } + print STDERR "Suggested target name: $tgtname\n"; + } + print STDERR "tgtname: $tgtname\n" if $opt_debug; + + my $tmpdir; + + if($opt_umode || $opt_umode_smart) + { + $file=Cwd::abs_path($file); + print STDERR "set abs.path to $file\n" if $opt_debug; + $tmpdir="unp.".rand; + mkdir $tmpdir; + chdir($tmpdir) || return 23; + } + + my @cmd = (SHELL_STYLE_ARGS & $flags) ? (@_, $file, @xargs) : (@_, @xargs, $file); + + + print STDERR "temp.cmd: ".join("\t", @cmd, "\n") if $opt_debug; + + # filter commands... use shell to keep our code simple. Tar stream may be + # inside, detect it and unpack it. + if(IS_STREAM_FILTER & $flags) + { + my $magic=""; + @cmd=("sh", "-c", join(" ", @_).' "$0" | file -', $file); + if(open(my $fh, "-|", @cmd)) + { + $magic = join('', <$fh>); + close($fh); + } + print STDERR "Internal magic: $magic\n" if $opt_debug; + if($magic=~/tar.archive/) + { + @cmd=("sh", "-c", join(" ", @_).' "$0" | tar -v -x -f - "$@" ', $file, @xargs); + } + else + { + @cmd=("sh", "-c", join(" ", @_).' "$0" > "$1"', $file, $tgtname); + } + } + + push(@cmd, $tgtname) if(APPEND_GEN_NAME & $flags); + + print STDERR join(" ", "test cmd line: ", @cmd, "\n") if $opt_debug; + + $ret = (system(@cmd) >> 8); + + if( $opt_umode_smart) + { + chdir ".."; + my @cont=(<$tmpdir/*>, <$tmpdir/.*>); + # . and .. and one element? + if(3==@cont) + { + # use same name as target, fall back to checked tgtname if that is already occupied + my $cand=basename($cont[0]); + if (-e $cand) + { + print STDERR gettext("Cannot create target directory (already exists), using alternative name\n"); + $cand=$tgtname ; + } + return 44 if ! (rename($cont[0], $cand) && rmdir $tmpdir); + } + else + { + rename $tmpdir, $tgtname || return 43; + } + + } + elsif ($opt_umode) + { + chdir ".."; + rename($tmpdir, $tgtname) || return 42; + } + + chdir $cwd; + + return $ret; +} + +sub special_debmode +{ + my $file=shift; + basename($file)=~/^(.*)\.deb$/; + my $bname=$1; + die "cannot recognice package name\n" if !$bname; + + mkdir "control"; + my $contgt="control/$bname"; + mkdir $contgt; + + return (system("ar", "x", $file) >> 8 ) + + (system("tar", "-z", "-x", "-v", "-f", "control.tar.gz", "-C", + $contgt) >> 8 ) + + ( system("tar", "-z", "-x", "-v", "-f", "data.tar.gz") >> 8 ); +}