package Mkcd::Tools; our $VERSION = '0.5.5'; use strict; use File::NCopy qw(copy); use Image::Size qw(:all); use Mkcd::Commandline qw(parseCommandLine usage); use Digest::MD5; use MDK::Common qw(all); require Exporter; use URPM; our @ISA = qw(Exporter); our @EXPORT = qw(printTable getTracks du cpal checkcds checkDiscs cleanrpmsrate imageSize printDiscsFile readBatchFile printBatchFile config compute_md5 log_ include_md5 convert_size); our ($GB, $MB, $KB, $INFO_OFFSET, $SIZE_OFFSET, $SKIP); $INFO_OFFSET = 883; $SIZE_OFFSET = 84; $SKIP = 15; $KB = 1024; $MB = 1024 * 1024; $GB = $MB * 1024; =head1 NAME tools - mkcd tools =head1 SYNOPSYS require mkcd::tools; =head1 DESCRIPTION includes mkcd tools. =head1 SEE ALSO mkcd =head1 COPYRIGHT Copyright (C) 2000,2001 MandrakeSoft This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. =head1 CREDITS md5 code highly inspired from Redhat anaconda md5 in ISO code =cut sub printTable { my ($a, $log) = @_; my $LOG; if ($log) { $LOG = $log } else { open $LOG, ">&STDERR" } # # iterative version of a recursive scanning of a table. # ex: @config = [[[1,3],3,[1,3,[1,3]]],3,4,[4,[4,4]]] # my @A; my @i; my @tab; my $i = 0; while ($a) { my $u = ref $a; if ($u eq 'ARRAY') { while ($i < @$a) { my $b = $a->[$i]; my $t = ref $b; if ($t eq 'ARRAY') { push @tab, "\t"; push @i, $i+1; push @A, $a; $i = 0; $a = $b; next } elsif ($t eq 'HASH') { $i++; print $LOG "@tab", join ' ', keys %$b, "\n" } else { $i++; print $LOG "@tab$b\n" } } } else { print $LOG "$a\n" } pop @tab; $i = pop @i; $a = pop @A; } } sub getTracks { my ($tracks, $log) = @_; my $LOG; if ($log) { my $LOG = $log } else { open $LOG, ">&STDERR" } my @tracks = split ',',$tracks; my @t; foreach (@tracks) { /(\d+)/ and push @t, $1; /(\d+)-(\d+)/ and push @t, $1..$2 } my @ntracks; my %done; for (my $i = $#t; $i >= 0; $i--) { push @ntracks, $t[$i] if !$done{$t[$i]}; $done{$t[$i]}=1 } \@ntracks; } sub du { my ($path, $inode) = @_; my $size; $inode ||= {}; if (-d $path) { opendir O, $path; foreach (readdir O) { /^\.{1,2}$/ and next; -l "$path/$_" or $size += du("$path/$_",$inode) } } else { if (! -l $path) { my @stat = stat $path; if (!$inode->{$stat[0]}{$stat[1]}) { $size = $stat[7] + 2048; $inode->{$stat[0]}{$stat[1]} = 1 } } } $size } sub cpal { my ($source, $dest, $exclude, $verbose, $log) = @_; my $LOG; if ($log) { my $LOG = $log } else { open $LOG, ">&STDERR" } if ($exclude && "$source/$_" =~ /$exclude/) { return 0 } if (!-l $source && -d $source) { mkdir $dest; opendir O, $source; foreach (readdir O) { /^\.{1,2}$/ and next; cpal("$source/$_", "$dest/$_",$exclude,$verbose) } } else { my $ok; if (-d $dest) { my ($filename) = $source =~ /([^\/]*)$/; $dest .= "/$filename" } $ok = link $source, $dest; $verbose and print $LOG "cpal: link $source -> $dest\n"; if (!$ok) { print $LOG "Linking failed $source -> $dest: $!, trying to copy\n"; $ok = copy $source, $dest; if (!$ok) { print $LOG "Copying failed $source -> $dest: $!,\n"; return 0 } } } 1 } sub checkDiscs { my ($hdlists, $depslist, $discsFiles, $check, $log) = @_; my $LOG; if ($log) { $LOG = $log } else { open $LOG, ">&STDOUT" } print $LOG "checkDiscs: depslist $depslist\n"; # # depslist hdlist consistency -> error ok (not the same as instal one, but duplicate will break anyway) # # in hdlist, not in depslist -> error ok # # in hdlist, not in dir -> error ok # # in hdlist with packdrake, no with parsehdlist -> error # # in depslist, not in hdlist -> error ok # # in depslist, not in dir -> error ok # # in dir, not in hdlist -> warning ok # # in dir, not in depslist -> warning ok # # multiple version in depslist -> error ok # # multiple version in hdlist -> error ok # # multiple in dir -> warning ok # my $ok = 1; my $OK = 1; my %depslist; my %depslistname; if ($depslist) { my $i = 1; open my $A, $depslist or print $LOG "ERROR: unable to open $depslist" and return 0; print $LOG "checkDiscs: duplicate version in $depslist:"; while (<$A>) { my ($pkg, $name) = ((split)[0]) =~ /((.*)-[^-]+-[^-]+\.[^:]+)/; $depslist{$pkg} and do { print $LOG "\n$pkg"; $ok = 0 }; $depslistname{$name} and do { print $LOG "\n$name"; $ok = 0 }; $depslist{$pkg} = $i; $depslistname{$name} = $i++; } close $A; } $ok or $OK = 0; $ok ? print $LOG " OK\n" : print $LOG "\nFAILED\n"; my %hdlist; print $LOG "\ncheckDiscs: duplicate version in hdlists:"; my $maxidx; my %rpm; my (@rnh, @hnd, @duprep, @rnd, @hnr, %rpmKeys, %parsehdlist, @pnh, @hnp); $ok = 1; my $parsehdlist; my $path = $0; $path =~ s/[^\/]*$//; if (-x "$path/parsehdlist") { $parsehdlist = "$path/parsehdlist" } elsif (-x "/usr/bin/parsehdlist") { $parsehdlist = "/usr/bin/parsehdlist" } else { my $err = system('parsehdlist'); if ($err) { $parsehdlist = "parsehdlist" } else { print $LOG, "ERROR checkDiscs: could not find parsehdlist command ($!)\n"; return 0 } } for (my $i = 1; $i < @$hdlists; $i++) { if (! -f $hdlists->[$i]) { print $LOG "\nWARNING checkDiscs: $hdlists->[$i] is empty, ignoring\n"; next } my $packer = new packdrake($hdlists->[$i]); my $j; foreach my $file (@{$packer->{files}}) { my ($rpm, $key) = $file =~ /([^:]*)(?::(.*))?/; $rpmKeys{key}{$rpm} = $key || $rpm; $rpmKeys{rpm}{$rpmKeys{key}{$rpm}} = $rpm; my $sok; foreach my $c (@{$check->[$i]}) { my ($cd,$rep, $list) = @$c; $discsFiles->[$cd]{$rep}{$list}{$rpmKeys{key}{$rpm}} and $sok = 1; } $sok or push @hnr, [ $i, $rpm ]; $hdlist{all}{$rpm} and do { print $LOG "\n$rpm"; $ok = 0 }; $hdlist{all}{$rpm} = 1; $hdlist{cd}{$i}{$rpm} = 1; if ($depslist) { $depslist{$rpm} or push @hnd, $rpm; $depslist{$rpm} > $j and $j = $depslist{$rpm}; $depslist{$rpm} < $maxidx and print $LOG "ERROR checkDiscs: inconsistency in position between hdlist $i rpm $rpm and depslist.ordered\n" } } foreach my $c (@{$check->[$i]}) { my ($cd, $rep, $list) = @$c; foreach my $rpm (keys %{$discsFiles->[$cd]{$rep}{$list}}) { $rpm{$rpmKeys{rpm}{$rpm}} and push @duprep, $rpm; $rpm{$rpmKeys{rpm}{$rpm}} = 1; $depslist && $depslist{$rpmKeys{rpm}{$rpm}} or push @rnd, [ $i, $cd, $rep, $rpm ]; $hdlist{cd}{$i}{$rpmKeys{rpm}{$rpm}} or push @rnh, [ $i, $rpm ] } } open my $PAR, "$parsehdlist $hdlists->[$i] |"; while (<$PAR>) { chomp; s/\.rpm$//; $parsehdlist{$i}{$_} = 1; $hdlist{cd}{$i}{$_} and next; push @pnh, $_ } foreach my $p (keys %{$hdlist{cd}{$i}}) { $parsehdlist{$i}{$p} or push @hnp, $p } $maxidx = $j; } $ok or $OK = 0; $ok ? print $LOG " OK\n" : print $LOG "\nFAILED\n"; my @dnh; $ok = 1; if ($depslist) { print $LOG "\ncheckDiscs: in depslist, not on discs:"; foreach my $rpm (keys %depslist) { $hdlist{all}{$rpm} or do { push @dnh, $rpm }; $rpm{$rpm} or do { $ok = 0; print $LOG "\n$rpm" }; } $ok or $OK = 0; $ok ? print $LOG " OK\n" : print $LOG "\nFAILED\n"; print $LOG "\ncheckDiscs: in depslist, not in hdlists:"; @dnh ? do { print $LOG " FAILED\n" and $OK = 0 } : print $LOG " OK\n"; foreach (@dnh) { print $LOG "$_\n" } } print $LOG "\ncheckDiscs: in hdlists, not on discs:"; @hnr ? do { print $LOG " FAILED\n" and $OK = 0 } : print $LOG " OK\n"; foreach (@hnr) { print $LOG "hdlist $_->[0] rpm $_->[3]\n" } print $LOG "\ncheckDiscs: in hdlists, not in depslist:"; @hnd ? do { print $LOG " FAILED\n" and $OK = 0 } : print $LOG " OK\n"; foreach (@hnd) { print $LOG "$_\n" } print $LOG "\ncheckDiscs: in hdlists, not see with parsehdlist:"; @hnp ? do { print $LOG " FAILED\n" and $OK = 0 } : print $LOG " OK\n"; foreach (@hnp) { print $LOG "$_\n" } print $LOG "\ncheckDiscs: see with parsehdlist, not with packdrake:"; @pnh ? do { print $LOG " FAILED\n" and $OK = 0 } : print $LOG " OK\n"; foreach (@pnh) { print $LOG "$_\n" } print $LOG "\ncheckDiscs: on discs, not in hdlist:"; @rnh ? print $LOG " WARNING\n" : print $LOG " OK\n"; foreach (@rnh) { print $LOG "hdlist $_->[0] rpm $_->[1]\n" } print $LOG "\ncheckDiscs: on discs, not in depslist:"; @rnd ? print $LOG " WARNING\n" : print $LOG " OK\n"; foreach (@rnd) { print $LOG "hdlist $_->[0] cd $_->[1] rep $_->[2] missing rpm $_->[3]\n" } print $LOG "\ncheckDiscs: duplicate version on discs:"; @duprep ? print $LOG " WARNING\n" : print $LOG " OK\n"; foreach (@duprep) { print $LOG "$_\n" } return $OK } # # check depslist, depslists.ordered and hdlists # sub checkcds { my (@tops) = @_; my $top = "$tops[0]/"; my $depslist = "$tops[0]/Mandrake/base/depslist.ordered"; -f $depslist or print "ERROR: could not find depslist $depslist file\n" and return 0; my $hdlists = "$top/Mandrake/base/hdlists"; open my $A, $hdlists or die "unable to open $hdlists"; my @hdlist = 0; my @discsFiles; my @check = 0; while (<$A>) { my ($hdlist, $dir, undef) = split; my ($hdid) = $hdlist =~ /(\d*).cz/; my $hdfile = "$tops[0]/Mandrake/base/$hdlist"; push @hdlist, $hdfile; push @check, [[ $hdid, $dir, 1 ]]; -f $hdfile or print "ERROR: could not find $hdfile file\n" and return 0; local *C; if (! opendir C, "$top/$dir") { foreach (@tops) { opendir C, "$_/$dir" or next; last } } foreach (readdir C) { /(.*)\.rpm/ or next; $discsFiles[$hdid]{$dir}{1}{$1} = 1 } } checkDiscs(\@hdlist, $depslist, \@discsFiles, \@check) } # # regexp version # sub cleanrpmsrate2 { my ($rpmsrate, @rpms) = @_; my $LOG; open $LOG, ">&STDERR"; my @rpm; foreach (@rpms) { -d $_ or print $LOG "ERROR: $_ is not a directory\n" and next; opendir my $A, $_; push @rpm, grep { s/-[^-]+-[^-]+\.[^.]+\.rpm// } all $A; } my %done; my (@flags, @c); my ($mod, $text, $prev, $rate, $current); my (%rate, %section); open my $A, $rpmsrate or print $LOG "ERROR: cannot open $rpmsrate\n"; while (<$A>) { s/#.*//; /^\s*$/ and $text .= "\n" and next; if (/^(\S+)/) { $text .= "$1\n"; $current = $1; @flags = $current; next } my ($indent, $r, $flags, $data) = /^(\s*)([1-5]?)((?:\s+(?:(?:!\s*)?[0-9A-Z_]+(?:"[^"]*")?(?:\s+(?:\|\|\s+)?)*)+\s+)|\s+)(.*)$/; if ($r) { $rate = $r } elsif ($prev) { chop $indent; $r = $prev } push @flags, split ' ', $flags; $data or $text .= "$indent$r$flags" and next; my ($postfix) = $data =~ /(\s*)$/; my @k; foreach my $n (split ' ', $data) { @c = grep { /^$n$/ } @rpm; map { if ((!$done{$_}[1] || $current eq "INSTALL") && $done{$_}[0] ne $current) { push @k, $_; @{$done{$_}} = @flags } } @c } if (@k) { $text .= "$indent$r$flags@k$postfix\n"; $prev = '' } else { $prev = $r }; @rate{@k} = ($rate) x @k; push @{$section{$current}}, @k } close A; if (@rpms) { if (open A, ">$rpmsrate") { print A $text; close A } else { @rpms and print $LOG "ERROR: cannot open $rpmsrate for writing\n"; print $text } } [\%rate, \%section, \%done]; } sub cleanrpmsrate { my ($rpmsrate, $output, $norpmsrate, $reprpms) = @_; $norpmsrate ||= []; my $LOG; open $LOG, ">&STDERR"; open my $A, $rpmsrate or print $LOG "ERROR: cannot open $rpmsrate\n"; my (@rpmsrate, %potloc); # must preread to get locale guessed packages # postfix is just used not to break the diff when checking if the result is correct while (<$A>) { chomp; s/#.*//; #s/\s*$//; /^(\s*)$/ and push @rpmsrate, [ '', 0, '', [] ] and next; if (/^(\S+)(.*)$/) { push @rpmsrate, [ 0, 0, $1, [], $2 ]; next } if (/^(\s*)([1-5])?(\s?[0-9A-Z_]+)$/) { push @rpmsrate, [ $1, $2, $3, [] ]; next } my ($indent,$r,$flags, $data) = /^(\s*)([1-5])?(\s*(?:(?:(?:!\s*)?[0-9A-Z_]+(?:"[^"]*")?(?:\s+(?:\|\|\s+)?)*)+\s+)|\s*)(.*)$/; my ($postfix) = $data =~ /(\s*)$/; my @data; my $i; foreach ([$data =~ /(?:^|\s)(\S+)-(?:\S+)\s+\1-(?:\S+)(?:\s|$)/g], [split ' ', $data]) { $data[$i++] = [ @$norpmsrate ? grep { my $r = $_; $r if !grep { $r =~ /$_/ } @$norpmsrate } @$_ : @$_ ] } map $potloc{$_} = [], @{$data[0]}; push @rpmsrate, [ $indent,$r, $flags, $data[1], $postfix ]; } my (%rpms, $text); my (%rate, %section); my (%locale, %localized_pkg); foreach my $dir (keys %$reprpms) { foreach (@{$reprpms->{$dir}}) { my $rpm = "$_.rpm"; s/-[^-]+-[^-]+\.[^.]+$// or next; grep { $rpm =~ /$_/ } @$norpmsrate and next; if (/(.*?)([_-]*[\d._]*)-devel$/ || /((?:(?:NVIDIA_)?kernel.*)|NVIDIA_nforce.*)(-[^.]+(?:\.[^.]+){3,5}mdk)$/) { if (!$rpms{$1}) { $rpms{$1} = $2 } elsif (URPM::ranges_overlap("== $2", "> $rpms{$1}")) { $rpms{$1} = $2 } } elsif (my ($pg, $loc) = /^(.*)-([^-+]+)$/) { if ($potloc{$pg}) { my %header; tie %header, "RPM::Header", "$dir/$rpm" or print "ERROR: $RPM::err\n" and next; # some i18n packages does not require the same locale, e.g. kde-i18n-nb and nn requires locales-no # if (grep { s/locales-// && $loc =~ /^$_(_|$)/ } @{$header{REQUIRENAME}}) { if (grep { /^locales-..$/ } @{$header{REQUIRENAME}}) { push @{$locale{$pg}}, $loc; $localized_pkg{"$pg-$loc"} = 1 } } } } } my (%done, @flags, $prev, @tree_rate, $prev_level); foreach (@rpmsrate) { if (!$_->[0]) { $text .= "$_->[2]$_->[4]\n"; if ($_->[2]) { @flags = $_->[2] } next } my ($indent, $r, $flags, $data, $postfix) = @$_; my $level = (length $indent)/2 - 1; my $rate; if ($r) { #print "tree_rate[$level] = $r\n"; $rate = $r; $tree_rate[$level] = $r } else { if (@$data) { if ($level > $prev_level) { $level-- } else { # fix a syntax error in rpmsrate such as # A # 1 toto # B tata <--- # 4 titi @$data = () } } $rate = $tree_rate[$level]; } $prev_level = $level; @flags = @flags[0 .. $level]; push @flags, grep { s/\s//; !/(\|\||[A-Z_]+\"[^"]+\")/ } split(' ', $flags); my $flat_path = join ' ', @flags; if (!@$data) { $text .= "$indent$r$flags$postfix\n"; next } my @k; foreach (@$data) { my $c = $_; if ($done{$_} eq $flat_path) { next } my ($d) = /(.*)-[^-]+/; my ($a, $b); if (($flags[0] ne "INSTALL" && s/(-devel)// ? ($b = "-devel") : /^((NVIDIA_)?kernel|NVIDIA_nforce)/) && ($rpms{$_} || ($rpms{"lib$_"} and $a = "lib"))) { my $d = "$a$_" . $rpms{"$a$_"} . $b; if (!$done{$d} || $flags[0] eq "INSTALL") { $done{$d} = $flat_path; push @k, $d } } if ($locale{$d} && $localized_pkg{$c}) { foreach (sort @{$locale{$d}}) { next if $done{"$d-$_"} eq $flat_path; $done{"$d-$_"} = $flat_path; push @k , "$d-$_" } next } push @k, $c; $done{$c} = $flat_path } if (@k) { $text .= "$indent$r$flags@k$postfix\n" } @rate{@k} = ($rate) x @k; my $path; foreach (@flags) { $path .= $path ? "/$_" : $_; push @{$section{$path}}, @k } } if (%rpms || $output) { if (%$reprpms || $output) { $output ||= $rpmsrate; if (open A, ">$output") { print A $text; close A } else { print $LOG "ERROR cleanrpmsrate: cannot open $rpmsrate for writing\n"; print $text } } } [\%rate, \%section]; } sub imageSize { my ($file) = @_; my ($width, $height, $err) = imgsize $file; return (defined $width ? [ $width, $height ] : "error: $err") } sub printDiscsFile { my ($config, $discsFiles, $PRINT, $metagroups) = @_; my (%done, $a); my $log = $config->{LOG}; if ($PRINT) { open $a, ">$PRINT" } else { $a = $config->{LOG} } my $print_rejected = sub { my ($groups, $i, $rpm) = @_; # FIXME ugly hack to display more rejected in multigroups buildings because discFiles is per disc and not per group. # $done{$groups->[$i]{urpm}{rpmkey}{rpm}{$rpm}} && ! ref $groups->[$i]{rejected}{$rpm} and return 1; $done{$groups->[$i]{urpm}{rpmkey}{rpm}{$rpm}} and return 1; $groups->[$i]{done}{$rpm} and return 1; if ($groups->[$i]{brokendeps}{$rpm} == 2) { ref $groups->[$i]{rejected}{$rpm} or print $a "ERROR printDiscsFile: this should not happen, rejected is not a table for $rpm (group $i)\n" and next; } print $a "REJECTED master disc $groups->[$i]{installDisc} $rpm ("; if (ref $groups->[$i]{rejected}{$rpm}) { print $a join(',', map { "$config->{rejected_options}{$_->[0]}: $_->[1]" } @{$groups->[$i]{rejected}{$rpm}}) } else { print $a "not selected" } print $a ")\n"; 0 }; for (my $cd; $cd < @$discsFiles; $cd++) { $discsFiles->[$cd] or next; print $log "discsFiles: $cd\n"; my $cdname = $config->{disc}[$cd]{label}; foreach my $rep (keys %{$discsFiles->[$cd]}) { foreach my $list (keys %{$discsFiles->[$cd]{$rep}}) { foreach my $rpm (sort keys %{$discsFiles->[$cd]{$rep}{$list}}) { #$done{$rpm} = 1; #$rpm =~ /src$/ and next; print $a "$cdname $rpm\n"; } } } } if (!$metagroups) { $a = $config->{LOG} } foreach (@$metagroups) { my $groups = $_->[0]; for (my $i; $i < @$groups; $i++) { if (ref $groups->[$i]{buildlist}) { foreach (sort @{$groups->[$i]{buildlist}}) { $print_rejected->($groups,$i,$_) and next; $done{$groups->[$i]{urpm}{rpmkey}{rpm}{$_}} = 1 } } foreach (sort keys %{$groups->[$i]{urpm}{rpm}}) { $print_rejected->($groups,$i,$_) } } } } sub printBatchFile { my ($config, $discsFiles, $PRINTSCRIPT) = @_; # FIXME to please perl_checker my $log = $config->{LOG}; if (-f $PRINTSCRIPT) { my $err = unlink $PRINTSCRIPT; if (!$err) { print $log "Unlinking failed $PRINTSCRIPT: $!\n"; return }; } my $err = copy $config->{configfile}, $PRINTSCRIPT; if (!$err) { print $log "Linking failed $PRINTSCRIPT: $!\n"; return }; open my $A, ">>$PRINTSCRIPT"; print $A "END\n"; for (my $cd; $cd < @$discsFiles; $cd++) { $discsFiles->[$cd] or next; print $log "discsFiles: $cd\n"; print $A "CD $cd\n"; foreach my $rep (keys %{$discsFiles->[$cd]}) { print $A " REP $rep\n"; foreach my $list (keys %{$discsFiles->[$cd]{$rep}}) { print $A " LIST $list\n"; foreach my $rpm (keys %{$discsFiles->[$cd]{$rep}{$list}}) { $rpm and print $A " $rpm $discsFiles->[$cd]{$rep}{$list}{$rpm}\n"; } } } } } sub readBatchFile { my ($file) = @_; local *A; open A, $file or print "ERROR readBatchFile: could not open $file for reading\n" and return 0; my @discsFiles; my @cd; while () { /^END/ and last } my ($cd, $rep, $list); while () { if (/^CD (\d+)/) { $cd = $1; next } if (/^ REP (\S+)/) { $rep = $1; next } if (/^ LIST (\d+)/) { $list = $1; next } if (/^ (\S+) (\S+)/) { $discsFiles[$cd]{$rep}{$list}{$1} = $2; push @{$cd[$cd]{$rep}{$list}{$2}}, [ 1, "$1.rpm" ]; next } } return (\@discsFiles, \@cd) } sub config { my ($file, $config, $functions) = @_; my $log = $config->{LOG}; open F,$file or die "ERROR config: cannot open $file\n"; while () { chomp; /^#/ or !$_ or last } chomp; $config->{name} = (split)[0]; my $match_val = q((?:([^"\s]+)|"([^\"]+)")); my $match_val2 = q(([^"\s]+|"[^\"]+")); my ($cd, $fn, $nk, $type, @todo, $discMax); while () { /^#/ and next; chomp; $_ or next; s/#.*//; if (/^list (.*)/) { my $line = $1; my @args; while ($line =~ s/$match_val2//) { my $a = $1; $a =~ s/\"//g; push @args, $a } #print "config: args (" . ( join ' | ', @args) . ")\n"; my $todo = parseCommandLine("list", \@args, $functions->{list}); $cd = $todo->[0][1][0]; #print "config: list $cd (@{$todo->[0][1]})\n"; if (!$config->{list}[$cd]) { @args and usage('list', $functions->{list}, "list $cd, list definition (@args) too many arguments"); foreach (@$todo) { log_("$_->[2]\n", $config->{verbose}, $log, 3); &{$_->[0]}($cd, @{$_->[1]}) or log_("ERROR: $_->[2]\n", $config->{verbose}, $log) and $nk = 1; } $type = 1; $fn = 0 } else { $type = 0; log_("ERROR config: list $cd already defined, ignoring\n", $config->{verbose}, $log); } # FIXME keep for compatibility } elsif (/^LIST /) { if (/^LIST (\d+)(?:\s+(\S.*))*/) { $cd = $1; push @{$config->{list}[$cd]{filelist}}, (split ' ',$2) if $2; $type = 1; log_("LIST $1 $2\n", $config->{verbose}, $log, 3) } else { $nk = 1; log_("WARNING: LIST syntax error ($_)\n", $config->{verbose}, $log); log_(" LIST ... \n", $config->{verbose}, $log) } } elsif (/^disc (.*)/) { my $line = $1; my @args; while ($line =~ s/$match_val2//) { my $a = $1; $a =~ s/\"//g; push @args, $a } #print "config: args (" . ( join ' | ', @args) . ")\n"; my $todo = parseCommandLine("disc", \@args, $functions->{disc}); $cd = $todo->[0][1][0]; #print "config: disc $cd (@{$todo->[0][1]})\n"; if (!$config->{disc}[$cd]) { @args and usage('disc', $functions->{disc}, "disc $cd, disc definition (@args) too many arguments"); foreach (@$todo) { log_("$_->[2]\n", $config->{verbose}, $log, 3); &{$_->[0]}($cd, @{$_->[1]}) or log_("ERROR: $_->[2]\n", $config->{verbose}, $log) and $nk = 1; } $type = 2; $fn = 0 } else { $type = 0; log_("ERROR config: disc $cd already defined, ignoring\n", $config->{verbose}, $log); } # FIXME keep for compatibility } elsif (/^DISC (.*)/) { if (/^DISC (\d+)\s+(\d+)\s+$match_val(?:\s+DISC\s+(\d+))?\s+$match_val(?:\s+$match_val)?/) { #print "1($1) 2($2) 3($3) 4($4) 5($5) 6($6) 7($7) 8($8) 8($9)\n"; $config->{disc}[$1]{size} = $2; my $disc = $config->{disc}[$1]; $disc->{serial} = substr "$3$4", 0, 128; $disc->{name} = $5; $disc->{longname} = "$6$7"; $disc->{appname} = substr("$6$7", 0, 128); $disc->{label} = substr(("$6$7" ? "$8$9" : "$6$7"), 0, 32); $cd = $1; $type = 2; $fn = 0; $4 > $discMax and $discMax = $4; log_("DISC $1 $2 $3$4 $5 $6$7 $8$9\n", $config->{verbose}, $log) } else { $nk = 1; $type = 0; log_("WARNING: DISC syntax error ($_)\n", $config->{verbose}, $log); log_(" DISC DISC \n", $config->{verbose}, $log) } } elsif (/^END/) { last } else { $type == 1 and do { # FIXME keep for compatibility my ($prog, @args) = split; if ($prog !~ /^rpmlist$/) { push @{$config->{list}[$cd]{packages}}, { rpm => [ $prog ] , srpm => \@args } } else { push @todo, [ $prog, \@args, $cd, $fn ]; $fn++; } next }; $type == 2 and do { my ($prog, @args) = split; log_("CALLING $prog -- @args\n", $config->{verbose}, $log,4); push @todo, [$prog, \@args, $cd, $fn]; $fn++; next } } } $config->{configfile} = $file; $config->{discMax} = $discMax; foreach (@todo) { my ($prog, $args, $cd, $fn) = @$_; if ($functions->{$prog}) { log_("FUNCTION $prog (@$args)\n", $config->{verbose}, $log,5); my $todo = parseCommandLine($prog, $args, $functions->{$prog}); @$args and usage($prog, $functions->{$prog}, "disc $cd, function $fn, @$args, too many arguments"); foreach (@$todo) { log_("config: todo $_->[2]\n", $config->{verbose}, $log, 4); &{$_->[0]}($cd, $fn, @{$_->[1]}) or log_("ERROR: $_->[2]\n", $config->{verbose}, $log) and $nk = 1; } } } $nk and return 0; #printTable($config); 1 } sub compute_md5 { my ($to_check, $ignore) = @_; my @files; md5_add_tree($to_check, \@files, $ignore); my $md5 = new Digest::MD5; foreach (sort { $a->[0] cmp $b->[0] } @files) { my $f = $_->[1]; local *A, open A, $f; $md5->addfile(*A); #my $tmpmd5 = new Digest::MD5; #local *A, open A, $f; #$tmpmd5->addfile(*A); #print "MD5: $_->[0] (", $tmpmd5->hexdigest() ,")\n"; } my $digest = $md5->hexdigest(); # print "IGNORE " , join " ",keys %$ignore ,"\n"; return $digest } sub md5_add_tree { my ($to_check, $files, $ignore) = @_; foreach (@$to_check) { my ($dest, $f) = @$_; $f =~ /\/?\.{1,2}$/ and next; $f =~ /~$/ and next; $f =~ s|//+|/|g; $dest =~ s|//+|/|g; $ignore->{$dest} and next; if (-d $f) { md5_add_tree([ map { [ "$dest/$_", "$f/$_" ] } all $f ], $files, $ignore) } else { push @$files, [ $dest, $f ] } } } sub log_ { my ($msg, $verbose, $log, $level) = @_; return if $level > $verbose; my $LOG; if (!$log) { open $LOG, ">&STDERR" } else { $LOG = $log } print $LOG $msg; } # TODO must add some check of maximum authorized size sub include_md5 { my ($iso, $write, $verbose) = @_; my $ISO; if ($write) { open $ISO, "+<$iso" or return "ERROR include_md5: unable to open $iso ($!)\n"; } else { open $ISO, $iso or return "ERROR include_md5: unable to open $iso ($!)\n"; } binmode $ISO; my $offset = 16*2048; # blank header seek $ISO, $offset, 0; my ($buf, $msg); while (1) { read $ISO,$buf,2048; my $c = ord $buf; last if $c == 1; return "ERROR include_md5: could not find primary volume descriptor\n" if $c == 255; $offset += 2048 } my $size = ((ord substr $buf, $SIZE_OFFSET, 1) * 0x1000000 + (ord substr $buf, $SIZE_OFFSET + 1, 1) * 0x10000 + (ord substr $buf, $SIZE_OFFSET + 2, 1) * 0x100 + (ord substr $buf, $SIZE_OFFSET + 3, 1)) * 2048; my $volume = substr $buf, 30, 40; $volume =~ s/^\s*(\S.*\S)\s*$/$1/; my $id = substr $buf, 180, 20; $msg = "include_md5: volume name $volume volume id: $id iso size $size\n"; seek $ISO, $offset + $INFO_OFFSET, 0; read $ISO, $buf,512; my ($md5sum) = $buf =~ /.md5 = (\S+)/; $msg .= "include_md5: previous data $buf\n"; seek $ISO, 0, 0; my $md5 = new Digest::MD5; my $read = read $ISO, $buf, $offset + $INFO_OFFSET; $md5->add($buf); seek $ISO, 512, 1; $read += 512; $|=1; my $val = int $size/2048/100; $verbose and print "\rReading: 0 %"; my ($i, $j); # skip last $SKIP bytes that sometimes are not correctly burned by some drives my $n = 1; while ($n && $read < $size - $SKIP * 2048) { $n = read $ISO, $buf,2048; print "\rReading: ", $j++, " %" if ($verbose && !($i++ % $val)); $md5->add($buf); $read += $n; } print "\n"; my $digest = $md5->hexdigest(); $msg .= "include_md5: computed md5 $digest\n"; my $res = $md5sum eq $digest; if ($md5sum) { $msg .= "include_md5: previous md5 $md5sum\ninclude_md5: md5sum check "; $msg .= $res ? "OK\n" : "FAILED\n" } print $msg if $verbose; $write or return $res; seek $ISO, $offset + $INFO_OFFSET, 0; my $str = substr "$volume.md5 = $digest", 0, 512; my $l = length $str; print $ISO ($l > 512 ? substr $str, -1, 512 : $str . ' ' x (512 - $l)); close $ISO } sub convert_size { my ($size, $default, $LOG) = @_; if ($size =~ /[\d.]+g$/i) { $size = $size * $GB; } elsif ($size =~ /[\d+.]+m$/i) { $size = $size * $MB; } elsif ($size =~ /[\d+.]+k$/i) { $size = $size * $KB; } elsif ($size !~ /[\d+.]+$/i) { log_("ERROR disc: $size is invalid, using default ($default)\n",1,$LOG); $size = $default; } $size } 1 # # Changelog # # 2002 02 27 # make the locale constraint free on the right for cleanrpmsrate locale addition (kde-i18n-zh_BG and such) # # 2002 03 03 # fix typo in checkdiscs # # 2002 03 04 # fix checkcds pb with check[0] used. # # 2002 03 07 # add possibility to remove package from rpmsrate # # 2002 03 12 # add all .*kernel- in rpmsrate # # 2002 03 17 # add serial name instead of cdnumber when name is not know # # 2002 05 07 # add check_discs, compute_md5, write_graft, md5_add_tree # # 2002 05 22 # fix a pb in md5 # # 2002 05 25 # add log function # # 2002 06 05 # fix md5 for isolinux # # 2002 08 12 # fix/change cleanrpmsrate # # 2002 09 04 # do not open for writing iso file in include_md5 if not in write mode # # 2002 09 25 # add completion feedback to include_md5