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
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
|
package Youri::Repository::Mageia;
=head1 NAME
Youri::Repository::Mageia - Mageia repository implementation
=head1 DESCRIPTION
This module implements Mageia repository
=cut
use warnings;
use strict;
use Carp;
use Memoize;
use File::Find 'find';
use base qw/Youri::Repository/;
use MDV::Distribconf::Build;
use SVN::Client;
use Sys::Hostname;
use constant {
PACKAGE_CLASS => 'Youri::Package::RPM::URPM',
PACKAGE_CHARSET => 'utf8'
};
memoize('_get_media_config');
my %translate_arch = (
i386 => 'i586',
sparc64 => 'sparcv9',
);
sub _init {
my $self = shift;
my %options = (
noarch => 'i586', # noarch packages policy
src => 'i586',
install_root => '',
test => 0, # test mode
verbose => 0, # verbose mode
queue => '',
rejected => '',
@_
);
foreach my $var ('upload_state') {
$self->{"_$var"} = [];
foreach my $value (split ' ', $options{$var}) {
push @{$self->{"_$var"}}, $value
}
}
print "Initializing repository\n";
foreach my $v ('rejected', 'svn', 'queue', 'noarch', 'install_root', 'upload_root', 'verbose') {
$self->{"_$v"} = $options{$v}
}
foreach my $target (@{$options{targets}}) {
$self->{$target} = [];
print "Adding $target ($options{$target}{arch})\n" if $self->{_verbose};
foreach my $value (split ' ', $options{$target}{arch}) {
push @{$self->{_arch}{$target}}, $value;
push @{$self->{_extra_arches}}, $value
}
}
$self
}
sub get_group_id {
my ($user) = @_;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time);
$year+=1900;
$mon++;
my ($host) = hostname =~ /([^.]*)/;
sprintf "$year%02d%02d%02d%02d%02d.$user.$host.${$}_", $mon, $mday, $hour, $min, $sec;
}
sub get_target_arch {
my ($self, $target) = $_;
return $self->{_arch}{$target}
}
sub set_arch_changed {
my ($self, $target, $arch) = @_;
if ($arch eq 'noarch') {
$self->{_arch_changed}{$_} = 1 foreach @{$self->{_arch}{$target}}
} elsif ($arch eq 'src') {
$self->{_arch_changed} = $self->{_src}
} else {
$self->{_arch_changed}{$arch} = 1
}
}
sub get_arch_changed {
my ($self, $target) = @_;
return [ keys %{$self->{_arch_changed}} ]
}
sub set_install_dir_changed {
my ($self, $install_dir) = @_;
$self->{_install_dir_changed}{$install_dir} = 1;
}
sub get_install_dir_changed {
my ($self) = @_;
return [ keys %{$self->{_install_dir_changed}} ];
}
sub _get_media_config {
my ($self, $target) = @_;
my %media;
my $real_target = $target;
$real_target =~ s/_force//;
foreach my $arch (@{$self->{_arch}{$target}}) {
my $root = "$self->{_install_root}/$real_target/$arch";
my $distrib = MDV::Distribconf::Build->new($root);
print "Getting media config from $root\n" if $self->{_verbose};
$self->{distrib}{$arch} = $distrib;
$distrib->loadtree or die "$root does not seem to be a distribution tree\n";
$distrib->parse_mediacfg;
foreach my $media ($distrib->listmedia) {
my $rpms = $distrib->getvalue($media, 'rpms');
my $debug_for = $distrib->getvalue($media, 'debug_for');
my $srpms = $distrib->getvalue($media, 'srpms');
my $path = $distrib->getfullpath($media, 'path');
if (!$rpms) {
if (-d $path) {
print "MEDIA defining $media in $path\n" if $self->{_verbose} > 1;
$media{$arch}{$media} = $path
} else {
print "ERROR $path does not exist for media $media on $arch\n"
}
} else {
my ($media) = split ' ', $rpms;
if (-d $path) {
print "MEDIA defining SOURCE media for $media in $path\n" if $self->{_verbose} > 1;
$media{src}{$media} = $path
} else {
print "ERROR $path does not exist for source media $media on $arch\n"
}
}
}
}
\%media
}
sub get_package_class {
return PACKAGE_CLASS;
}
sub get_package_charset {
return PACKAGE_CHARSET;
}
sub get_upload_dir {
my ($self, $package, $target, $user_context, $app_context) = @_;
croak "Not a class method" unless ref $self;
my $arch = $package->get_arch();
return
$self->{_upload_root} .
"/$self->{_queue}/$target/" .
_get_section($self, $package, $target, $user_context, $app_context) .
'/' .
($user_context->{prefix} ? '' : get_group_id($user_context->{user}))
}
sub get_install_path {
my ($self, $package, $target, $user_context, $app_context) = @_;
return $self->_get_path($package, $target, $user_context, $app_context);
}
sub get_distribution_paths {
my ($self, $package, $target) = @_;
return $self->_get_distribution_paths($package, $target);
}
=head2 get_distribution_roots()
Returns distribution roots (ie install_root + target + arch)
(it returns a list in case of noarch)
=cut
sub get_distribution_roots {
my ($self, $package, $target) = @_;
croak "Not a class method" unless ref $self;
map {
$self->_get_dir($self->{_install_root}, $_);
} $self->_get_distribution_paths($package, $target);
}
sub get_archive_path {
my ($self, $package, $target, $user_context, $app_context) = @_;
return $self->_get_path($package, $target, $user_context, $app_context);
}
sub get_reject_path {
my ($self, $package, $target, $user_context, $app_context) = @_;
return $self->{_rejected};
}
sub _get_path {
my ($self, $package, $target, $user_context, $app_context) = @_;
my $section = $self->_get_section($package, $target, $user_context, $app_context);
my $arch = $app_context->{arch} || $package->get_arch();
$arch = $translate_arch{$arch} || $arch;
if ($arch eq 'noarch') {
$arch = $self->{_noarch}
} elsif ($arch eq 'src') {
return "$target/SRPMS/$section"
}
"$target/$arch/media/$section"
}
sub _get_distribution_paths {
my ($self, $package, $target) = @_;
my $arch = $package->get_arch();
$arch = $translate_arch{$arch} || $arch;
if ($arch eq 'noarch') {
map { "$target/$_" } $self->get_target_arches($target);
} elsif ($arch eq 'src') {
die "no way to get distribution path using a $arch package";
} else {
"$target/$arch";
}
}
sub get_arch {
my ($self, $package, $target, $user_context, $app_context) = @_;
my $arch = $package->get_arch();
$arch = $translate_arch{$arch} || $arch;
if ($arch eq 'noarch') {
$arch = $self->{_noarch}
}
$arch
}
sub get_version_path {
my ($self, $package, $target, $user_context, $app_context) = @_;
my $section = $self->_get_section($package, $target, $user_context, $app_context);
return "$self->{_module}/$section";
}
=head2 get_replaced_packages($package, $target, $user_context, $app_context)
Overrides parent method to add libified packages.
=cut
sub get_replaced_packages {
my ($self, $package, $target, $user_context, $app_context) = @_;
croak "Not a class method" unless ref $self;
my @replaced_packages =
$self->SUPER::get_replaced_packages($package, $target, $user_context, $app_context);
# mandriva lib policy:
# library package names change with revision, making mandatory to
# duplicate older revisions search with a custom pattern
my $name = $package->get_name();
if ($name =~ /^(lib\w+[a-zA-Z_])[\d_\.]+([-\w]*)$/) {
push(@replaced_packages,
grep { $package->compare($_) > 0 }
map { PACKAGE_CLASS->new(file => $_) }
$self->get_files(
$self->{_install_root},
$self->get_install_path($package, $target, $user_context, $app_context),
PACKAGE_CLASS->get_pattern(
$1 . '[\d_\.]+' . $2, # custom name pattern
undef,
undef,
$package->get_arch()
),
)
);
}
# kernel packages have the version in the name
# binary dkms built for old kernels have to be removed too
if ($name =~ /^kernel-([^\d]*-)?([\d.]*)-(.*)$/) { # "desktop", "2.6.28", "2mnb"
push(@replaced_packages,
map { PACKAGE_CLASS->new(file => $_) }
$self->get_files(
$self->{_install_root},
$self->get_install_path($package, $target, $user_context, $app_context),
PACKAGE_CLASS->get_pattern(
'(kernel-' . $1 . '\d.*|.*-kernel-[\d.]*-' . $1 . '\d.*)',
undef,
undef,
$package->get_arch()
),
)
);
}
return @replaced_packages;
}
sub _get_main_section {
my ($self, $package, $target, $user_context, $app_context) = @_;
my $section = $self->_get_section($package, $target, $user_context, $app_context);
my ($main_section) = $section =~ m,^([^/]+),;
$main_section
}
sub _get_section {
my ($self, $package, $target, $user_context, $app_context) = @_;
my $name = $package->get_name();
my $cname = $package->get_canonical_name();
my $version = $package->get_version();
my $release = $package->get_release();
my $section = $user_context->{section};
my $media = $self->_get_media_config($target);
my $arch = $package->get_arch();
my $file = $package->as_file();
$file =~ s,/+,/,g; # unneeded?
# FIXME: use $self->get_arch()
$arch = $self->{_noarch} if $arch eq 'noarch';
$arch = $translate_arch{$arch} || $arch;
if (!$section) {
$section = $self->{packages}{$file}{section};
print "Section undefined, repository says it is '$section' for '$file'\n" if $self->{_verbose};
}
# FIXME: use debug_for info
if ($section && $section !~ m|debug/| && $package->is_debug()) {
$section = "debug/$section"
}
# if have section already, check if it exists, and may return immediately
if ($section) {
print "Using requested section $section\n" if $self->{_verbose};
if ($media->{$arch}{$section}) {
return $section
} else {
die "FATAL youri: unknown section $section for target $target for arch $arch\n"
}
}
# else, try to find section automatically
# pattern for search of src package with specific version-release,
# should be searched first, because we prefer to find the precise
# section a package is already in
my $specific_source_pattern = PACKAGE_CLASS->get_pattern(
$cname,
$version,
$release,
'src'
);
my $source_pattern = PACKAGE_CLASS->get_pattern(
$cname,
undef,
undef,
'src'
);
# if a media has no source media configured, or if it is a debug
# package, we search in binary media
# pattern for search when a binary media has no src media configured
my $specific_binary_pattern = PACKAGE_CLASS->get_pattern(
$name,
$version,
$release,
$arch
);
# last resort pattern: previous existing binary packages
my $binary_pattern = PACKAGE_CLASS->get_pattern(
$name,
undef,
undef,
$arch
);
# first try to find section for the specific version, as it is possibly already there;
# this is the case for when called in Youri::Submit::Action::Archive, to find the
# section the package got installed
print "Looking for package $name with version $version-$release\n" if $self->{_verbose};
foreach my $m (keys %{$media->{$arch}}) {
print " .. section '$m' path '".$media->{$arch}{$m}."'\n" if $self->{_verbose};
# - prefer source for non-debug packages, use binary if there is no source media configured
# - debug packages must be searched in binary medias, due to their
# src section != binary section; NOTE: should/need we search in
# src medias and add the 'debug_' prefix?
if (!$package->is_debug() && $media->{src}{$m}) {
next unless $self->get_files('', $media->{src}{$m}, $specific_source_pattern);
} else {
next unless $self->get_files('', $media->{$arch}{$m}, $specific_binary_pattern);
}
$section = $m;
last;
}
# if still not found, try finding any version of the package in a
# /release subsection (safe default: /release is default for cooker,
# should be locked for released distros, and we don't risk wrongly
# choosing /backports, /testing, or /updates);
# this is the case for when called at submit, to find the section where
# the package already resides
if (!$section) {
# debug packages should be found by previous specific version search
# NOTE: as above, should/need we search here and add the 'debug_' prefix?
# ... probably... as at least mga-youri-submit-force will process debug packages
if ($package->is_debug() && $self->{_verbose}) {
print "Warning: debug package $name with version $version-$release not found.\n";
}
print "Warning: Looking for any section with a package $name of any version\n";
foreach my $m (keys %{$media->{$arch}}) {
print " .. section '$m' path '".$media->{$arch}{$m}."'\n" if $self->{_verbose};
# NOTE: !$package->is_debug() test is here to prevent when above FATAL error is removed
next if $m !~ /release/ || ($m =~ /debug/ && !$package->is_debug());
# - prefer source
if ($media->{src}{$m}) {
next unless $self->get_files('', $media->{src}{$m}, $source_pattern);
} else {
next unless $self->get_files('', $media->{$arch}{$m}, $binary_pattern);
}
$section = $m;
last;
}
}
# FIXME: doing this here is wrong; this way the caller can never know if
# a section was actually found or not; should return undef and let the
# caller set a default (Note: IIRC PLF|Zarb has this right, see there) -spuk
print STDERR "Warning: Can't guess destination: section missing, defaulting to core/release\n" unless $section;
$section ||= 'core/release';
# next time we don't need to search everything again
$self->{packages}{$file}{section} = $section;
print "Section is '$section'.\n" if $self->{_verbose};
return $section;
}
sub get_upload_newer_revisions {
my ($self, $package, $target, $user_context, $app_context) = @_;
croak "Not a class method" unless ref $self;
my $arch = $package->get_arch();
my $name = $package->as_string();
$name =~ s/^\@\d+://;
my $pattern = $self->get_package_class()->get_pattern($package->get_name(), undef, undef, $arch);
my $media = $self->_get_media_config($target);
my @packages;
foreach my $state (@{$self->{_upload_state}}) {
foreach my $m (keys %{$media->{$arch}}) {
my $path = "$self->{_upload_root}/$state/$target/$m";
print "Looking for package $package revisions for $target in $path (pattern $pattern)\n" if $self->{_verbose};
find(
sub {
s/\d{14}\.[^.]*\.[^.]*\.\d+_//;
s/^\@\d+://;
return if ! /^$pattern/;
return if /\.info$/;
print "Find $_\n" if $self->{_verbose} > 1;
push @packages, $File::Find::name if $package->check_ranges_compatibility("== $name", "< $_")
}, $path);
}
}
return
@packages;
}
sub package_in_svn {
my ($self, $srpm_name) = @_;
my $ctx = new SVN::Client(
auth => [SVN::Client::get_simple_provider(),
SVN::Client::get_simple_prompt_provider(\&simple_prompt,2),
SVN::Client::get_username_provider()]
);
my $svn_entry = $ctx->ls("$self->{_svn}/$srpm_name", 'HEAD', 0);
if ($svn_entry) {
print "Package $srpm_name is in the SVN\n" if $self->{_verbose};
return 1
}
}
sub get_svn_url {
my ($self) = @_;
$self->{_svn}
}
sub reject {
my ($self, $package, $target, $user_context, $app_context) = @_;
croak "Not a class method" unless ref $self;
}
sub get_archive_dir {
my ($self, $package, $target, $user_context, $app_context) = @_;
croak "Not a class method" unless ref $self;
return
$self->{_archive_root}
}
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2002-2006, YOURI project
Copyright (C) 2006,2007,2009 Mandriva
Copyright (C) 2011 Nicolas Vigier, Michael Scherer, Pascal Terjan
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
=cut
1;
|