summaryrefslogtreecommitdiffstats
path: root/perl-install/common.pm
blob: 3eb223cd5b295abd5a5ee0816d057efeb9ce28bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
package common; # $Id$

use MDK::Common;
use diagnostics;
use strict;
BEGIN { eval { require Locale::gettext } } #- allow common.pm to be used in drakxtools-backend without perl-Locale-gettext

use log;
use run_program;

use Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw($SECTORSIZE N P N_ check_for_xserver files_exist formatTime MB formatXiB get_parent_uid is_running makedev mandrake_release mandrake_release_info removeXiBSuffix require_root_capability setVirtual set_alternative set_l10n_sort set_permissions to_utf8  translate unmakedev);

# perl_checker: RE-EXPORT-ALL
push @EXPORT, @MDK::Common::EXPORT;


$::prefix ||= ""; # no warning

#-#####################################################################################
#- Globals
#-#####################################################################################
our $SECTORSIZE  = 512;

#-#####################################################################################
#- Functions
#-#####################################################################################

sub P {
    my ($s_singular, $s_plural, $nb, @para) = @_; 
    sprintf(translate($s_singular, $s_plural, $nb), @para);
}

sub N {
    my ($s, @para) = @_; 
    sprintf(translate($s), @para);
}
sub N_ { $_[0] }


sub makedev { ($_[0] << 8) | $_[1] }
sub unmakedev { $_[0] >> 8, $_[0] & 0xff }

sub translate_real {
    my ($s, $o_plural, $o_nb) = @_;
    $s or return '';
    my $s2;
    foreach (@::textdomains, 'libDrakX') {
     if ($o_plural) {
         $s2 = Locale::gettext::dngettext($_, $s, $o_plural, $o_nb);
     } else {
         $s2 = Locale::gettext::dgettext($_, $s);
     }
	# when utf8 pragma is in use, Locale::gettext() returns an utf8 string not tagged as such:
	c::set_tagged_utf8($s2) if !utf8::is_utf8($s2) && utf8::is_utf8($s);
	return $s2 if $s ne $s2 && $s2 ne $o_plural;
    }
    # didn't lookup anything or locale is "C":
    $s2;
}

sub remove_translate_context {
    my ($s) = @_;
    #- translation with context, kde-like 
    $s =~ s/^_:.*\n//;
    $s;
}

sub translate {
    my $s = translate_real(@_);
    $::one_message_has_been_translated ||= join(':', (caller(1))[1,2]); #- see mygtk2.pm
    remove_translate_context($s);
}

sub from_utf8 {
    my ($s) = @_;
    Locale::gettext::iconv($s, "utf-8", undef); #- undef = locale charmap = nl_langinfo(CODESET)
}
sub to_utf8 { 
    my ($s) = @_;
    my $str = Locale::gettext::iconv($s, undef, "utf-8"); #- undef = locale charmap = nl_langinfo(CODESET)
    c::set_tagged_utf8($str);
    $str;
}

#- This is needed because text printed by Gtk2 will always be encoded
#- in UTF-8;
#- we first check if LC_ALL is defined, because if it is, changing
#- only LC_COLLATE will have no effect.
sub set_l10n_sort() {
    my $collation_locale = $ENV{LC_ALL};
    if (!$collation_locale) {
        $collation_locale = c::setlocale(c::LC_COLLATE());
        $collation_locale =~ /UTF-8/ or c::setlocale(c::LC_COLLATE(), "$collation_locale.UTF-8");
    }
}


sub setVirtual {
    my ($vt_number) = @_;
    my $vt = '';
    sysopen(my $C, "/dev/console", 2) or die "failed to open /dev/console: $!";
    ioctl($C, c::VT_GETSTATE(), $vt) &&
      ioctl($C, c::VT_ACTIVATE(), $vt_number) &&
	ioctl($C, c::VT_WAITACTIVE(), $vt_number) or die "setVirtual failed";
    unpack "S", $vt;
}

sub nonblock {
    my ($F) = @_;
    fcntl($F, c::F_SETFL(), fcntl($F, c::F_GETFL(), 0) | c::O_NONBLOCK()) or die "can not fcntl F_SETFL: $!";
}

#- return a size in sector
#- ie MB(1) is 2048 sectors, which is 1MB
sub MB {
    my ($nb_MB) = @_;
    $nb_MB * 2048;
}

sub removeXiBSuffix {
    local $_ = shift;

    /(\d+)\s*kB?$/i and return $1 * 1024;
    /(\d+)\s*MB?$/i and return $1 * 1024 * 1024;
    /(\d+)\s*GB?$/i and return $1 * 1024 * 1024 * 1024;
    /(\d+)\s*TB?$/i and return $1 * 1024 * 1024 * 1024 * 1024;
    $_;
}
sub formatXiB {
    my ($newnb, $o_newbase) = @_;
    my $newbase = $o_newbase || 1;
    my $sign = $newnb < 0 ? -1 : 1;
    $newnb = abs(int($newnb));
    my ($nb, $base);
    my $decr = sub { 
	($nb, $base) = ($newnb, $newbase);
	$base >= 1024 ? ($newbase = $base / 1024) : ($newnb = $nb / 1024);
    };
    my $suffix;
    foreach (N("B"), N("KB"), N("MB"), N("GB"), N("TB")) {
	$decr->(); 
	if ($newnb < 1 && $newnb * $newbase < 1) {
	    $suffix = $_;
	    last;
	}
    }
    my $v = $nb * $base;
    my $s = $v < 10 && int(10 * $v - 10 * int($v));
    int($v * $sign) . ($s ? "." . abs($s) : '') . ($suffix || N("TB"));
}

sub formatTime {
    my ($s, $m, $h) = gmtime($_[0]);
    if ($h) {
	sprintf "%02d:%02d", $h, $m;
    } elsif ($m > 1) {
	N("%d minutes", $m);
    } elsif ($m == 1) {
	N("1 minute");
    } else {
	N("%d seconds", $s);
    }
}

sub expand_symlinks_with_absolute_symlinks_in_prefix {
    my ($prefix, $link) = @_;

    my ($first, @l) = split '/', $link;
    $first eq '' or die "expand_symlinks: $link is relative\n";
    my ($f, $l);
    foreach (@l) {
	$f .= "/$_";
	while ($l = readlink "$prefix$f") {
	    $f = $l =~ m!^/! ? $l : MDK::Common::File::concat_symlink($f, "../$l");
	}
    }
    "$prefix$f";
}

sub expand_symlinks_but_simple {
    my ($f) = @_;
    my $link = readlink($f);
    my $f2 = expand_symlinks($f);
    if ($link && $link !~ m|/|) {
	# put back the last simple symlink
	$f2 =~ s|\Q$link\E$|basename($f)|e;
    }
    $f2;
}

sub sync { &MDK::Common::System::sync }

BEGIN { undef *formatError }
sub formatError {
    my ($err) = @_;
    ref($err) eq 'SCALAR' and $err = $$err;
    log::l("error: $err");
    &MDK::Common::String::formatError($err);
}

sub group_by(&@) {
    my $f = shift;
    @_ or return;
    my $e = shift;
    my @l = my $last_l = [$e];
    foreach (@_) {
	if ($f->($e, $_)) {
	    push @$last_l, $_;
	} else {
	    push @l, $last_l = [$_];
	    $e = $_;
	}
    }
    @l;
}

# Group the list by n. Returns a reference of lists of length n
sub group_n_lm {
    my $n = shift;
    my @l;
    push @l, [ splice(@_, 0, $n) ] while @_;
    @l;
}

sub join_lines {
    my @l;
    my $s;
    foreach (@_) {
	if (/^\s/) {
	    $s .= $_;
	} else {
	    push @l, $s if $s;
	    $s = $_;
	}
    }
    @l, if_($s, $s);
}


sub read_alternative {
    my ($name) = @_;
    my $alt = readlink("$::prefix/etc/alternatives/$name");
    $alt && $::prefix . $alt;
}

sub set_alternative {
    my ($command, $executable) = @_;

    #- check the existance of $executable as an alternative for $command
    #- (is this needed???)
    run_program::rooted_get_stdout($::prefix, 'update-alternatives', '--display', $command) =~ /^\Q$executable /m or return;

    #- this does not handle relative symlink, but neither does update-alternatives ;p
    symlinkf $executable, "$::prefix/etc/alternatives/$command";
}

sub files_exist { and_(map { -f "$::prefix$_" } @_) }

sub open_file {
    my ($file) = @_;
    my $F;
    open($F, $file) ? $F : do { log::l("Can not open $file: $!"); undef };
}


sub secured_file {
    my ($f) = @_;
    c::is_secure_file($f) or die "can not ensure a safe $f";
    $f;
}

sub unwind_protect {
    my ($to_do, $cleanup) = @_;
    my @l = eval { $to_do->() };
    my $err = $@;
    $cleanup->();
    $err and die $err;
    wantarray() ? @l : $l[0];
}

sub with_private_tmp_file {
    my ($file, $content, $f) = @_;

    my $prev_umask = umask 077;

    unwind_protect(sub { 
	MDK::Common::File::secured_output($file, $content);
	$f->($file);
    }, sub {
	umask $prev_umask;
	unlink $file;
    });
}

sub chown_ {
    my ($b_recursive, $name, $group, @files) = @_;

    my ($uid, $gid) = (getpwnam($name) || $name, getgrnam($group) || $group);
      
    require POSIX;
    my $chown; $chown = sub {
	foreach (@_) {
	    POSIX::lchown($uid, $gid, $_) or die "chown of file $_ failed: $!\n";
	    ! -l $_ && -d $_ && $b_recursive and &$chown(glob_($_));
	}
    };
    $chown->(@files);
}


sub set_permissions {
    my ($file, $perms, $o_owner, $o_group) = @_;
    # We only need to set the permissions during installation to be able to
    # print test pages. After installation udev does the business automatically.
    return 1 unless $::isInstall;
    if ($o_owner || $o_group) {
	$o_owner ||= (lstat($file))[4];
	$o_group ||= (lstat($file))[5];
	chown_(0, $o_owner, $o_group, $file);
    }
    chmod(oct($perms), $file) or die "chmod of file $file failed: $!\n";
}

sub is_running {
    my ($name, $o_user) = @_;
    my $user = $o_user || $ENV{USER};
    foreach (`ps -o '%P %p %c' -u $user`) {
	my ($ppid, $pid, $n) = /^\s*(\d+)\s+(\d+)\s+(.*)/;
	return $pid if $ppid != 1 && $pid != $$ && $n eq $name;
    }
}

sub parse_release_file {
    my ($prefix, $f, $part) = @_;
    chomp(my $s = cat_("$prefix$f"));
    my $version = $s =~ s/\s+release\s+(\S+)// && $1;
    my $arch = $s =~ s/\s+for\s+(\S+)// && $1;
    log::l("find_root_parts found $part->{device}: $s for $arch" . ($f !~ m!/etc/! ? " in special release file $f" : ''));
    { release => $s, version => $version, 
      release_file => $f, part => $part, arch => $arch };
}

sub release_file {
    my ($o_dir) = @_;
    my @names = ('mandrakelinux-release', 'mandrake-release', 'conectiva-release', 'release', 'redhat-release', 'fedora-release', 'SuSE-release');
    find { -r "$o_dir$_" } (
	(map { "/root/drakx/$_.upgrading" } @names), 
	(map { "/etc/$_" } @names),
    );
}

sub mandrake_release_info() {
    parse_LDAP_namespace_structure(cat_('/etc/product.id'));
}

sub parse_LDAP_namespace_structure {
    my ($s) = @_;
    my %h = map { if_(/(.*?)=(.*)/, $1 => $2) } split(',', $s);
    \%h;
}

sub mandrake_release {
    my ($o_dir) = @_;
    my $f = release_file($o_dir);
    $f && chomp_(cat_("$o_dir$f"));
}

sub get_parent_uid() {
    cat_('/proc/' . getppid() . '/status') =~ /Uid:\s*(\d+)/ ? $1 : undef;
}

sub wrap_command_for_root {
    my ($name, @args) = @_;
    ([ 'consolehelper', $name ], @args);
}

sub require_root_capability() {
    return if $::testing || !$>; # we're already root

    my ($command, @args) = wrap_command_for_root($0, @ARGV);
    exec { $command->[0] } $command->[1], @args or die N("command %s missing", $command->[0]);

    # still not root ?
    die "you must be root to run this program" if $>;
}

sub check_for_xserver() {
    if (!defined $::xtest) {
	$::xtest = 0;         
	eval { 
	    require xf86misc::main; 
	    $::xtest = xf86misc::main::Xtest($ENV{DISPLAY});
	} if $ENV{DISPLAY};
    }
    return $::xtest;
}

#- special unpack
#- - returning an array refs for each element like "s10"
#- - handling things like s10* at the end of the format
sub unpack_with_refs {
    my ($format, $s) = @_;
    my $initial_format = $format;
    my @r;
    while ($format =~ s/\s*(\w(\d*))(\*?)\s*//) {
	my ($sub_format, $nb, $many) = ($1, $2, $3);
	$many && $format and internal_error("bad * in the middle of format in $initial_format");

	my $done = $many && !length($s);
	while (!$done) {
	    my @l = unpack("$sub_format a*", $s);
	    $s = pop @l;
	    push @r, $nb ? \@l : @l;
	    $done = !$many || !length($s);
	}
    }
    @r;
}

#- used in userdrake and mdkonline
sub md5file {
    require Digest::MD5;
    my @md5 = map {
        my $sum;
	if (open(my $FILE, $_)) {
            binmode($FILE);
            $sum = Digest::MD5->new->addfile($FILE)->hexdigest;
            close($FILE);
        }
        $sum;
    } @_;
    return wantarray() ? @md5 : $md5[0];
}

sub load_modules_from_base {
    my ($base) = @_;
    $base =~ s|::|/|g;
    my $base_file = $base . ".pm";
    require $base_file;
    my ($inc_path) = substr($INC{$base_file}, 0, -length($base_file));
    my @files = map { substr($_, length($inc_path)) } glob_($inc_path . $base . '/*.pm');
    require $_ foreach @files;
    #- return the matching modules list
    map { local $_ = $_; s|/|::|g; s|\.pm$||g; $_ } @files;
}

sub get_alternatives {
    my ($name) = @_;

    my $dir = $::prefix . '/var/lib/rpm/alternatives';
    my ($state, $main_link, @l) = chomp_(cat_("$dir/$name")) or return;
    my @slaves;
    while (@l && $l[0] ne '') {
	my ($name, $link) = splice(@l, 0, 2);
	push @slaves, { name => $name, link => $link };
    }
    shift @l; #- empty line
    my @alternatives;
    while (@l && $l[0] ne '') {
	my ($file, $weight, @slave_files) = splice(@l, 0, 2 + @slaves);
	
	push @alternatives, { file => $file, weight => $weight, slave_files => \@slave_files };
    }
    { name => $name, link => $main_link, state => $state, slaves => \@slaves, alternatives => \@alternatives };
}

sub symlinkf_update_alternatives {
    my ($name, $wanted_file) = @_;
    run_program::rooted($::prefix, 'update-alternatives', '--set', $name, $wanted_file);
}

sub update_gnomekderc_no_create {
    my ($file, $category, %subst_) = @_;
    if (-e $file) {
	update_gnomekderc($file, $category, %subst_);
    }
}

1;
span class="hl kwa">sub packageRequest { my ($packages, $pkg) = @_; #- check if the same or better version is installed, #- do not select in such case. $pkg && ($pkg->flag_upgrade || !$pkg->flag_installed) or return; #- check for medium selection, if the medium has not been #- selected, the package cannot be selected. foreach (values %{$packages->{mediums}}) { !$_->{selected} && $pkg->id >= $_->{start} && $pkg->id <= $_->{end} and return; } return { $pkg->id => 1 }; } sub packageCallbackChoices { my ($urpm, $_db, $state, $choices) = @_; if (my $prefer = find { $_->arch ne 'src' && exists $preferred{$_->name} } @$choices) { $prefer; } else { my @l = grep { #- or even if a package requires a specific locales which #- is already selected. find { /locales-/ && do { my $p = packageByName($urpm, $_); $p && $p->flag_available; }; } $_->requires_nosense; } @$choices; if (!@l) { push @l, $choices->[0]; log::l("packageCallbackChoices: default choice from ", join(",", map { $urpm->{depslist}[$_]->name } keys %{$state->{selected}}), " in ", join(",", map { $_->name } @$choices)); } #-log::l("packageCallbackChoices: chosen " . join(" ", map { $_->name } @l)); @l; } } #- selection, unselection of package. sub selectPackage { my ($packages, $pkg, $b_base, $o_otherOnly) = @_; #- select package and dependancies, o_otherOnly may be a reference #- to a hash to indicate package that will strictly be selected #- when value is true, may be selected when value is false (this #- is only used for unselection, not selection) my $state = $packages->{state} ||= {}; my @l = $packages->resolve_requested($packages->{rpmdb}, $state, packageRequest($packages, $pkg) || {}, callback_choices => \&packageCallbackChoices); if ($b_base || $o_otherOnly) { foreach (@l) { $b_base and $_->set_flag_base; $o_otherOnly and $o_otherOnly->{$_->id} = $_->flag_requested; } $o_otherOnly and $packages->disable_selected($packages->{rpmdb}, $state, @l); } 1; } sub unselectPackage($$;$) { my ($packages, $pkg, $o_otherOnly) = @_; #- base package are not unselectable, #- and already unselected package are no more unselectable. $pkg->flag_base and return; $pkg->flag_selected or return; my $state = $packages->{state} ||= {}; log::l("removing selection on package ".$pkg->fullname); my @l = $packages->disable_selected($packages->{rpmdb}, $state, $pkg); log::l(" removed selection on package " . $pkg->fullname . "gives " . join(',', map { scalar $_->fullname } @l)); if ($o_otherOnly) { foreach (@l) { $o_otherOnly->{$_->id} = undef; } log::l(" reselecting removed selection..."); $packages->resolve_requested($packages->{rpmdb}, $state, $o_otherOnly, callback_choices => \&packageCallbackChoices); log::l(" done"); } 1; } sub setPackageSelection($$$) { my ($packages, $pkg, $value) = @_; $value ? selectPackage($packages, $pkg) : unselectPackage($packages, $pkg); } sub unselectAllPackages($) { my ($packages) = @_; my %keep_selected; log::l("unselecting all packages..."); foreach (@{$packages->{depslist}}) { if ($_->flag_base || $_->flag_installed && $_->flag_selected) { #- keep track of package that should be kept selected. $keep_selected{$_->id} = $_; log::l("...keeping ".$_->fullname); } else { #- deselect all packages except base or packages that need to be upgraded. $_->set_flag_required(0); $_->set_flag_requested(0); } } #- clean staten, in order to start with a brand new set... $packages->{state} = {}; $packages->resolve_requested($packages->{rpmdb}, $packages->{state}, \%keep_selected, callback_choices => \&packageCallbackChoices); } sub psUpdateHdlistsDeps { my ($prefix, $_method, $packages) = @_; my $need_copy = 0; #- check if current configuration is still up-to-date and do not need to be updated. foreach (values %{$packages->{mediums}}) { $_->{selected} || $_->{ignored} or next; my $urpmidir = -w "$prefix/var/lib/urpmi" ? "$prefix/var/lib/urpmi" : "/tmp"; my $hdlistf = "$urpmidir/hdlist.$_->{fakemedium}.cz" . ($_->{hdlist} =~ /\.cz2/ && "2"); my $synthesisf = "$urpmidir/synthesis.hdlist.$_->{fakemedium}.cz" . ($_->{hdlist} =~ /\.cz2/ && "2"); if (-s $hdlistf != $_->{hdlist_size}) { install_any::getAndSaveFile("Mandrake/base/$_->{hdlist}", $hdlistf) or die "no $_->{hdlist} found"; symlinkf $hdlistf, "/tmp/$_->{hdlist}"; ++$need_copy; } if (-s $synthesisf != $_->{synthesis_hdlist_size}) { install_any::getAndSaveFile("Mandrake/base/synthesis.$_->{hdlist}", $synthesisf); -s $synthesisf > 0 or unlink $synthesisf; } } if ($need_copy) { #- this is necessary for urpmi. my $urpmidir = -w "$prefix/var/lib/urpmi" ? "$prefix/var/lib/urpmi" : "/tmp"; install_any::getAndSaveFile("Mandrake/base/$_", "$urpmidir/$_") foreach qw(rpmsrate); } } sub psUsingHdlists { my ($prefix, $method) = @_; my $listf = install_any::getFile('Mandrake/base/hdlists') or die "no hdlists found"; my $packages = new URPM; #- add additional fields used by DrakX. @$packages{qw(count mediums)} = (0, {}); #- parse hdlists file. my $medium = 1; foreach (<$listf>) { chomp; s/\s*#.*$//; /^\s*$/ and next; m/^\s*(noauto:)?(hdlist\S*\.cz2?)\s+(\S+)\s*(.*)$/ or die qq(invalid hdlist description "$_" in hdlists file); #- make sure the first medium is always selected! #- by default select all image. psUsingHdlist($prefix, $method, $packages, $2, $medium, $3, $4, !$1); ++$medium; } log::l("psUsingHdlists read " . int(@{$packages->{depslist}}) . " headers on " . int(keys %{$packages->{mediums}}) . " hdlists"); $packages; } sub psUsingHdlist { my ($prefix, $method, $packages, $hdlist, $medium, $rpmsdir, $descr, $selected, $o_fhdlist, $o_pubkey) = @_; my $fakemedium = "$descr ($method$medium)"; my $urpmidir = -w "$prefix/var/lib/urpmi" ? "$prefix/var/lib/urpmi" : "/tmp"; log::l("trying to read $hdlist for medium $medium"); #- if the medium already exist, use it. $packages->{mediums}{$medium} and return $packages->{mediums}{$medium}; my $m = { hdlist => $hdlist, method => $method, medium => $medium, rpmsdir => $rpmsdir, #- where is RPMS directory. descr => $descr, fakemedium => $fakemedium, selected => $selected, #- default value is only CD1, it is really the minimal. ignored => !$selected, #- keep track of ignored medium by DrakX. pubkey => [], #- all pubkey block here }; #- copy hdlist file directly to urpmi directory, this will be used #- for getting header of package during installation or after by urpmi. my $newf = "$urpmidir/hdlist.$fakemedium.cz" . ($hdlist =~ /\.cz2/ && "2"); -e $newf and do { unlink $newf or die "cannot remove $newf: $!" }; install_any::getAndSaveFile($o_fhdlist || "Mandrake/base/$hdlist", $newf) or do { unlink $newf; die "no $hdlist found" }; $m->{hdlist_size} = -s $newf; #- keep track of size for post-check. symlinkf $newf, "/tmp/$hdlist"; #- if $o_fhdlist is defined, this is preferable not to try to find the associated synthesis. my $newsf = "$urpmidir/synthesis.hdlist.$fakemedium.cz" . ($hdlist =~ /\.cz2/ && "2"); unless ($o_fhdlist) { #- copy existing synthesis file too. install_any::getAndSaveFile("Mandrake/base/synthesis.$hdlist", $newsf); $m->{synthesis_hdlist_size} = -s $newsf; #- keep track of size for post-check. -s $newsf > 0 or unlink $newsf; } #- get all keys corresponding in the right pubkey file, #- they will be added in rpmdb later if not found. if (!$o_fhdlist || $o_pubkey) { $m->{pubkey} = $o_pubkey; unless ($m->{pubkey}) { my $pubkey = install_any::getFile("Mandrake/base/pubkey" . ($hdlist =~ /hdlist(\S*)\.cz2?/ && $1)); $m->{pubkey} = [ $packages->parse_armored_file($pubkey) ]; } } #- integrate medium in media list, only here to avoid download error (update) to be propagated. $packages->{mediums}{$medium} = $m; #- avoid using more than one medium if Cd is not ejectable. #- but keep all medium here so that urpmi has the whole set. $m->{ignored} ||= $method eq 'cdrom' && $medium > 1 && !common::usingRamdisk(); #- parse synthesis (if available) of directly hdlist (with packing). if ($m->{ignored}) { log::l("ignoring packages in $hdlist"); } else { if (-s $newsf) { ($m->{start}, $m->{end}) = $packages->parse_synthesis($newsf); } elsif (-s $newf) { ($m->{start}, $m->{end}) = $packages->parse_hdlist($newf, 1); } else { delete $packages->{mediums}{$medium}; unlink $newf; $o_fhdlist or unlink $newsf; die "fatal: no hdlist nor synthesis to read for $fakemedium"; } $m->{start} > $m->{end} and do { delete $packages->{mediums}{$medium}; unlink $newf; $o_fhdlist or unlink $newsf; die "fatal: nothing read in hdlist or synthesis for $fakemedium" }; log::l("read " . ($m->{end} - $m->{start} + 1) . " packages in $hdlist"); } $m; } sub read_rpmsrate { my ($packages, $f) = @_; my $line_nb = 0; my $fatal_error; my (@l); local $_; while (<$f>) { $line_nb++; /\t/ and die "tabulations not allowed at line $line_nb\n"; s/#.*//; # comments my ($indent, $data) = /(\s*)(.*)/; next if !$data; # skip empty lines @l = grep { $_->[0] < length $indent } @l; my @m = @l ? @{$l[-1][1]} : (); my ($t, $flag, @l2); while ($data =~ /^(( [1-5] | (?: (?: !\s*)? [0-9A-Z_]+(?:".*?")?) (?: \s*\|\|\s* (?: !\s*)? [0-9A-Z_]+(?:".*?")?)* ) (?:\s+|$) )(.*)/x) { #@")) { ($t, $flag, $data) = ($1,$2,$3); while ($flag =~ s,^\s*(("[^"]*"|[^"\s]*)*)\s+,$1,) {} my $ok = 0; my ($inv, $p); $flag = join('||', grep { if (($inv, $p) = /^(!)?HW"(.*)"/) { ($inv xor detect_devices::matching_desc($p)) and $ok = 1; 0; } elsif (($inv, $p) = /^(!)?DRIVER"(.*)"/) { ($inv xor detect_devices::matching_driver($p)) and $ok = 1; 0; } else { 1; } } split '\|\|', $flag); push @m, $ok ? 'TRUE' : $flag || 'FALSE'; push @l2, [ length $indent, [ @m ] ]; $indent .= $t; } if ($data) { # has packages on same line my $rate = find { /^\d$/ } @m or die sprintf qq(missing rate for "%s" at line %d (flags are %s)\n), $data, $line_nb, join('&&', @m); foreach (split ' ', $data) { if ($packages) { my $p = packageByName($packages, $_) or next; my @m2 = map { if_(/locales-(.*)/, qq(LOCALES"$1")) } $p->requires_nosense; my @m3 = ((grep { !/^\d$/ } @m), @m2); if (member('INSTALL', @m3)) { member('NOCOPY', @m3) or push @{$packages->{needToCopy} ||= []}, $_; next; #- don't need to put INSTALL flag for a package. } if (member('PRINTER', @m3)) { push @{$packages->{needToCopy} ||= []}, $_; } if ($p->rate) { my @m4 = $p->rflags; if (@m3 > 1 || @m4 > 1) { log::l("can't handle complicate flags for packages appearing twice ($_)"); $fatal_error++; } log::l("package $_ appearing twice with different rates ($rate != ".$p->rate.")") if $rate != $p->rate; $p->set_rate($rate); $p->set_rflags("$m3[0]||$m4[0]"); } else { $p->set_rate($rate); $p->set_rflags(@m3); } } else { print "$_ = ", join(" && ", @m), "\n"; } } push @l, @l2; } else { push @l, [ $l2[0][0], $l2[-1][1] ]; } } $fatal_error and die "$fatal_error fatal errors in rpmsrate"; } sub readCompssUsers { my ($meta_class) = @_; my $file = 'Mandrake/base/compssUsers'; my $f = $meta_class && install_any::getFile("$file.$meta_class") || install_any::getFile($file) or die "can't find $file"; readCompssUsers_raw($f); } sub readCompssUsers_raw { my ($f) = @_; my (%compssUsers, @sorted, $l); local $_; while (<$f>) { /^\s*$/ || /^#/ and next; s/#.*//; if (/^(\S.*)/) { my $verbatim = $_; my ($icon, $descr, $path, $selected); /^(.*?)\s*\[path=(.*?)\](.*)/ and $_ = "$1$3", $path = $2; /^(.*?)\s*\[icon=(.*?)\](.*)/ and $_ = "$1$3", $icon = $2; /^(.*?)\s*\[descr=(.*?)\](.*)/ and $_ = "$1$3", $descr = $2; /^(.*?)\s*\[selected=(.*?)\](.*)/ and $_ = "$1$3", $selected = $2; $compssUsers{"$path|$_"} = { label => $_, verbatim => $verbatim, path => $path, icons => $icon, descr => $descr, if_(defined $selected, selected => [ split /[\s,]+/, $selected ]), flags => $l = [] }; push @sorted, "$path|$_"; } elsif (/^\s+(.*?)\s*$/) { push @$l, $1; } } \%compssUsers, \@sorted; } sub saveCompssUsers { my ($prefix, $packages, $compssUsers, $sorted) = @_; my $flat; foreach (@$sorted) { my @fl = @{$compssUsers->{$_}{flags}}; my %fl; $fl{$_} = 1 foreach @fl; $flat .= $compssUsers->{$_}{verbatim}; foreach my $p (@{$packages->{depslist}}) { my @flags = $p->rflags; if ($p->rate && any { any { !/^!/ && $fl{$_} } split('\|\|') } @flags) { $flat .= sprintf "\t%d %s\n", $p->rate, $p->name; } } } my $urpmidir = -w "$prefix/var/lib/urpmi" ? "$prefix/var/lib/urpmi" : "/tmp"; output "$urpmidir/compssUsers.flat", $flat; } sub setSelectedFromCompssList { my ($packages, $compssUsersChoice, $min_level, $max_size) = @_; $compssUsersChoice->{TRUE} = 1; #- ensure TRUE is set my $nb = selectedSize($packages); foreach my $p (sort { $b->rate <=> $a->rate } @{$packages->{depslist}}) { my @flags = $p->rflags; next if !$p->rate || $p->rate < $min_level || any { !any { /^!(.*)/ ? !$compssUsersChoice->{$1} : $compssUsersChoice->{$_} } split('\|\|') } @flags; #- determine the packages that will be selected when #- selecting $p. the packages are not selected. my $state = $packages->{state} ||= {}; my @l = $packages->resolve_requested($packages->{rpmdb}, $state, packageRequest($packages, $p) || {}, callback_choices => \&packageCallbackChoices); #- this enable an incremental total size. my $old_nb = $nb; foreach (@l) { $nb += $_->size; } if ($max_size && $nb > $max_size) { $nb = $old_nb; $min_level = $p->rate; $packages->disable_selected($packages->{rpmdb}, $state, @l); last; } } log::l("setSelectedFromCompssList: reached size ", formatXiB($nb), ", up to indice $min_level (less than ", formatXiB($max_size), ")"); log::l("setSelectedFromCompssList: ", join(" ", sort map { $_->name } grep { $_->flag_selected } @{$packages->{depslist}})); $min_level; } #- usefull to know the size it would take for a given min_level/max_size #- just saves the selected packages, call setSelectedFromCompssList and restores the selected packages sub saveSelected { my ($packages) = @_; my $state = delete $packages->{state}; my @l = @{$packages->{depslist}}; my @flags = map { ($_->flag_requested && 1) + ($_->flag_required && 2) + ($_->flag_upgrade && 4) } @l; [ $packages, $state, \@l, \@flags ]; } sub restoreSelected { my ($packages, $state, $l, $flags) = @{$_[0]}; $packages->{state} = $state; mapn { my ($pkg, $flag) = @_; $pkg->set_flag_requested($flag & 1); $pkg->set_flag_required($flag & 2); $pkg->set_flag_upgrade($flag & 4); } $l, $flags; } sub computeGroupSize { my ($packages, $min_level) = @_; sub inside { my ($l1, $l2) = @_; my $i = 0; return if @$l1 > @$l2; foreach (@$l1) { my $c; while ($c = $l2->[$i++] cmp $_) { return if $c == 1 || $i > @$l2; } } 1; } sub or_ify { my ($first, @other) = @_; my @l = split('\|\|', $first); foreach (@other) { @l = map { my $n = $_; map { "$_&&$n" } @l; } split('\|\|'); } #- HACK, remove LOCALES & CHARSET, too costly grep { !/LOCALES|CHARSET/ } @l; } sub or_clean { my (@l) = map { [ sort split('&&') ] } @_ or return ''; my @r; B: while (@l) { my $e = shift @l; foreach (@r, @l) { inside($e, $_) and next B; } push @r, $e; } join("\t", map { join('&&', @$_) } @r); } my (%group, %memo, $slowpart_counter); foreach my $p (@{$packages->{depslist}}) { my @flags = $p->rflags; next if !$p->rate || $p->rate < $min_level; my $flags = join("\t", @flags = or_ify(@flags)); $group{$p->name} = ($memo{$flags} ||= or_clean(@flags)); #- determine the packages that will be selected when selecting $p. #- make a fast selection (but potentially erroneous). #- installed and upgrade flags must have been computed (see compute_installed_flags). my %newSelection; unless ($p->flag_available) { my @l2 = $p->id; my $id; while (defined($id = shift @l2)) { exists $newSelection{$id} and next; $newSelection{$id} = undef; my $pkg = $packages->{depslist}[$id]; foreach ($pkg->requires_nosense) { my @choices = keys %{$packages->{provides}{$_} || {}}; if (@choices <= 1) { push @l2, @choices; } elsif (! find { exists $newSelection{$_} } @choices) { my ($candidate_id, $prefer_id); foreach (@choices) { ++$slowpart_counter; my $ppkg = $packages->{depslist}[$_] or next; $ppkg->flag_available and $prefer_id = $candidate_id = undef, last; exists $preferred{$ppkg->name} and $prefer_id = $_; $ppkg->name =~ /kernel-\d/ and $prefer_id ||= $_; foreach my $l ($ppkg->requires_nosense) { /locales-/ or next; my $pppkg = packageByName($packages, $l) or next; $pppkg->flag_available and $prefer_id ||= $_; } $candidate_id = $_; } if (defined $prefer_id || defined $candidate_id) { push @l2, defined $prefer_id ? $prefer_id : $candidate_id; } } } } } foreach (keys %newSelection) { my $p = $packages->{depslist}[$_] or next; my $s = $group{$p->name} || do { join("\t", or_ify($p->rflags)); }; next if length($s) > 120; # HACK, truncated too complicated expressions, too costly my $m = "$flags\t$s"; $group{$p->name} = ($memo{$m} ||= or_clean(@flags, split("\t", $s))); } } my (%sizes, %pkgs); while (my ($k, $v) = each %group) { my $pkg = packageByName($packages, $k) or next; push @{$pkgs{$v}}, $k; $sizes{$v} += $pkg->size - $packages->{sizes}{$pkg->name}; } log::l(sprintf "%s %dMB %s", $_, $sizes{$_} / sqr(1024), join(',', @{$pkgs{$_}})) foreach keys %sizes; \%sizes, \%pkgs; } sub openInstallLog { my ($prefix) = @_; my $f = "$prefix/root/drakx/install.log"; open(my $LOG, ">> $f") ? log::l("opened $f") : log::l("Failed to open $f. No install log will be kept."); #-# CORE::select((CORE::select($LOG), $| = 1)[0]); c::rpmErrorSetCallback(fileno $LOG); #- c::rpmSetVeryVerbose(); $LOG; } sub rpmDbOpen { my ($prefix, $o_rebuild_needed) = @_; if ($o_rebuild_needed) { if (my $pid = fork()) { waitpid $pid, 0; $? & 0xff00 and die "rebuilding of rpm database failed"; } else { log::l("rebuilding rpm database"); my $rebuilddb_dir = "$prefix/var/lib/rpmrebuilddb.$$"; -d $rebuilddb_dir and log::l("removing stale directory $rebuilddb_dir"), rm_rf($rebuilddb_dir); URPM::DB::rebuild($prefix) or log::l("rebuilding of rpm database failed: " . c::rpmErrorString()), c::_exit(2); c::_exit(0); } } my $db; if ($db = URPM::DB::open($prefix)) { log::l("opened rpm database for examining existing packages"); } else { log::l("unable to open rpm database, using empty rpm db emulation"); $db = new URPM; } $db; } sub rpmDbOpenForInstall { my ($prefix) = @_; #- there is a bug in rpm 4.2 where all operations for accessing rpmdb files are not #- always done using prefix, we need to setup a symlink in /var/lib/rpm for that ... unless (-e "/var/lib/rpm") { #- check if at some time a /var/lib directory has been made. if (-d "/var/lib") { symlinkf "$prefix/var/lib/rpm", "/var/lib/rpm"; } else { symlinkf "$prefix/var/lib", "/var/lib"; } } my $db = URPM::DB::open($prefix, 1); $db and log::l("opened rpmdb for writing in $prefix"); $db; } sub cleanOldRpmDb { my ($prefix) = @_; my $failed; foreach (qw(Basenames Conflictname Group Name Packages Providename Requirename Triggername)) { -s "$prefix/var/lib/rpm/$_" or $failed = 'failed'; } #- rebuilding has been successfull, so remove old rpm database if any. #- once we have checked the rpm4 db file are present and not null, in case #- of doubt, avoid removing them... unless ($failed) { log::l("rebuilding rpm database completed successfully"); foreach (qw(conflictsindex.rpm fileindex.rpm groupindex.rpm nameindex.rpm packages.rpm providesindex.rpm requiredby.rpm triggerindex.rpm)) { -e "$prefix/var/lib/rpm/$_" or next; log::l("removing old rpm file $_"); rm_rf("$prefix/var/lib/rpm/$_"); } } } sub selectPackagesAlreadyInstalled { my ($packages, $_prefix) = @_; log::l("computing installed flags and size of installed packages"); $packages->{sizes} = $packages->compute_installed_flags($packages->{rpmdb}); } sub selectPackagesToUpgrade { my ($packages, $_prefix, $o_medium) = @_; #- check before that if medium is given, it should be valid. $o_medium && (! defined $o_medium->{start} || ! defined $o_medium->{end}) and return; log::l("selecting packages to upgrade"); my $state = $packages->{state} ||= {}; $state->{selected} = {}; my %selection; $packages->request_packages_to_upgrade($packages->{rpmdb}, $state, \%selection, requested => undef, $o_medium ? (start => $o_medium->{start}, end => $o_medium->{end}) : (), ); log::l("resolving dependencies..."); $packages->resolve_requested($packages->{rpmdb}, $state, \%selection, callback_choices => \&packageCallbackChoices); log::l("...done"); } sub allowedToUpgrade { $_[0] !~ /^(kernel|kernel22|kernel2.2|kernel-secure|kernel-smp|kernel-linus|kernel-linus2.2|hackkernel|kernel-enterprise)$/ } sub installTransactionClosure { my ($packages, $id2pkg) = @_; my ($id, %closure, @l, $medium, $min_id, $max_id); @l = sort { $a <=> $b } keys %$id2pkg; #- search first usable medium (sorted by medium ordering). foreach (sort { $a->{start} <=> $b->{start} } values %{$packages->{mediums}}) { unless ($_->{selected}) { #- this medium is not selected, but we have to make sure no package are left #- in $id2pkg. if (defined $_->{start} && defined $_->{end}) { foreach ($_->{start} .. $_->{end}) { delete $id2pkg->{$_}; } } #- anyway, examine the next one. next; } if ($l[0] <= $_->{end}) { #- we have a candidate medium, it could be the right one containing #- the first package of @l... $l[0] >= $_->{start} and $medium = $_, last; #- ... but it could be necessary to find the first #- medium containing package of @l. foreach my $id (@l) { $id >= $_->{start} && $id <= $_->{end} and $medium = $_, last; } $medium and last; } } $medium or return (); #- no more medium usable -> end of installation by returning empty list. ($min_id, $max_id) = ($medium->{start}, $medium->{end}); #- it is sure at least one package will be installed according to medium chosen. install_any::useMedium($medium->{medium}); if ($medium->{method} eq 'cdrom') { my $pkg = $packages->{depslist}[$l[0]]; #- force changeCD callback to be called from main process. install_any::getFile($pkg->filename, $medium->{descr}); #- close opened handle above. install_any::getFile('XXX'); } while (defined($id = shift @l)) { my @l2 = $id; while (defined($id = shift @l2)) { exists $closure{$id} and next; $id >= $min_id && $id <= $max_id or next; $closure{$id} = undef; my $pkg = $packages->{depslist}[$id]; foreach ($pkg->requires_nosense) { foreach (keys %{$packages->{provides}{$_} || {}}) { if ($id2pkg->{$_}) { push @l2, $_; last; } } } } keys %closure >= $limitMinTrans and last; } map { delete $id2pkg->{$_} } grep { $id2pkg->{$_} } sort { $a <=> $b } keys %closure; } sub installCallback { # my $msg = shift; # log::l($msg .": ". join(',', @_)); } sub install($$$;$$) { my ($prefix, $isUpgrade, $toInstall, $packages) = @_; my %packages; delete $packages->{rpmdb}; #- make sure rpmdb is closed before. return if !@$toInstall; #- for root loopback'ed /boot my $loop_boot = loopback::prepare_boot(); #- first stage to extract some important informations #- about the packages selected. this is used to select #- one or many transaction. my ($total, $nb); foreach my $pkg (@$toInstall) { $packages{$pkg->id} = $pkg; $nb++; $total += to_int($pkg->size); #- do not correct for upgrade! } log::l("pkgs::install $prefix"); log::l("pkgs::install the following: ", join(" ", map { $_->name } values %packages)); eval { fs::mount("/proc", "$prefix/proc", "proc", 0) } unless -e "$prefix/proc/cpuinfo"; URPM::read_config_files(); my $LOG = openInstallLog($prefix); #- do not modify/translate the message used with installCallback since #- these are keys during progressing installation, or change in other #- place (install_steps_gtk.pm,...). installCallback($packages, 'user', undef, 'install', $nb, $total); do { my @transToInstall = installTransactionClosure($packages, \%packages); $nb = values %packages; #- added to exit typically after last media unselected. if ($nb == 0 && scalar(@transToInstall) == 0) { cleanHeaders($prefix); loopback::save_boot($loop_boot); return; } #- extract headers for parent as they are used by callback. extractHeaders($prefix, \@transToInstall, $packages->{mediums}); my ($retry_pkg, $retry_count); while ($retry_pkg || @transToInstall) { local (*INPUT, *OUTPUT); pipe INPUT, OUTPUT; if (my $pid = fork()) { close OUTPUT; my $error_msg = ''; local $_; while (<INPUT>) { if (/^die:(.*)/) { $error_msg = $1; last; } else { chomp; my @params = split ":"; if ($params[0] eq 'close') { my $pkg = $packages->{depslist}[$params[1]]; #- update flag associated to package. $pkg->set_flag_installed(1); $pkg->set_flag_upgrade(0); #- update obsoleted entry. foreach (keys %{$packages->{state}{rejected}}) { if (exists $packages->{state}{rejected}{$_}{closure}{$pkg->fullname}) { delete $packages->{state}{rejected}{$_}{closure}{$pkg->fullname}; %{$packages->{state}{rejected}{$_}{closure}} or delete $packages->{state}{rejected}{$_}; } } } else { installCallback($packages, @params); } } } $error_msg and $error_msg .= join('', <INPUT>); waitpid $pid, 0; close INPUT; $error_msg and die $error_msg; } else { #- child process will run each transaction. $SIG{SEGV} = sub { log::l("segmentation fault on transactions"); c::_exit(0) }; my @prev_pids = grep { /^\d+$/ } all("/proc"); close INPUT; select((select(OUTPUT), $| = 1)[0]); if ($::testing) { my $size_typical = $nb ? int($total/$nb) : 0; foreach (@transToInstall) { log::l("i would install ", $_->name, " now"); my $id = $_->id; print OUTPUT "inst:$id:start:0:$size_typical\ninst:$id:progress:0:$size_typical\nclose:$id\n"; } } else { eval { my $db = rpmDbOpenForInstall($prefix) or die "error opening RPM database: ", c::rpmErrorString(); my $trans = $db->create_transaction($prefix); if ($retry_pkg) { log::l("opened rpm database for retry transaction of 1 package only"); $trans->add($retry_pkg, $isUpgrade && allowedToUpgrade($retry_pkg->name)); } else { log::l("opened rpm database for transaction of " . int(@transToInstall) . " new packages, still $nb after that to do"); $trans->add($_, $isUpgrade && allowedToUpgrade($_->name)) foreach @transToInstall; } my @checks = $trans->check; @checks and log::l("check failed : ".join("\n ", @checks)); $trans->order or die "error ordering package list: " . c::rpmErrorString(); $trans->set_script_fd(fileno $LOG); log::l("rpm transactions start"); my $fd; #- since we return the "fileno", perl doesn't know we're still using it, and so closes it, and :-( my @probs = $trans->run($packages, force => 1, nosize => 1, callback_open => sub { my ($data, $_type, $id) = @_; my $pkg = defined $id && $data->{depslist}[$id]; my $medium = packageMedium($packages, $pkg); my $f = $pkg && $pkg->filename; print $LOG "$f\n"; $fd = install_any::getFile($f, $medium->{descr}); $fd ? fileno $fd : -1; }, callback_close => sub { my ($data, $_type, $id) = @_; my $pkg = defined $id && $data->{depslist}[$id] or return; my $check_installed; $db->traverse_tag('name', [ $pkg->name ], sub { my ($p) = @_; $check_installed ||= $pkg->compare_pkg($p) == 0; }); $check_installed and print OUTPUT "close:$id\n"; }, callback_inst => sub { my ($_data, $type, $id, $subtype, $amount, $total) = @_; print OUTPUT "$type:$id:$subtype:$amount:$total\n"; }); log::l("transactions done, now trying to close still opened fd"); install_any::getFile('XXX'); #- close still opened fd. @probs and die "installation of rpms failed:\n ", join("\n ", @probs); }; $@ and print OUTPUT "die:$@\n" } close OUTPUT; #- now search for child process which may be locking the cdrom, making it unable to be ejected. my @allpids = grep { /^\d+$/ } all("/proc"); my %ppids; foreach (@allpids) { push @{$ppids{$1 || 1}}, $_ if cat_("/proc/$_/status") =~ /^PPid:\s+(\d+)/m; } my @killpid = difference2(\@allpids, [ @prev_pids, difference2([ $$, hashtree2list(getppid(), \%ppids) ], [ hashtree2list($$, \%ppids) ]) ]); if (@killpid && $::isInstall) { foreach (@killpid) { my ($prog, @para) = split("\0", cat_("/proc/$_/cmdline")); log::l("ERROR: DrakX should not have to clean the packages shit. Killing $_: " . join(' ', $prog, @para) . ".") if $prog ne '/usr/lib/gconfd-2'; } kill 15, @killpid; sleep 2; kill 9, @killpid; } c::_exit(0); } #- if we are using a retry mode, this means we have to split the transaction with only #- one package for each real transaction.