Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | magnus | 1 | package DebPool::Packages; |
2 | |||
3 | ### |
||
4 | # |
||
5 | # DebPool::Packages - Module for handling package metadata |
||
6 | # |
||
7 | # Copyright 2003-2004 Joel Aelwyn. All rights reserved. |
||
8 | # |
||
9 | # Redistribution and use in source and binary forms, with or without |
||
10 | # modification, are permitted provided that the following conditions |
||
11 | # are met: |
||
12 | # 1. Redistributions of source code must retain the above copyright |
||
13 | # notice, this list of conditions and the following disclaimer. |
||
14 | # 2. Redistributions in binary form must reproduce the above copyright |
||
15 | # notice, this list of conditions and the following disclaimer in the |
||
16 | # documentation and/or other materials provided with the distribution. |
||
17 | # 3. Neither the name of the Author nor the names of any contributors |
||
18 | # may be used to endorse or promote products derived from this software |
||
19 | # without specific prior written permission. |
||
20 | # |
||
21 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
||
22 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||
23 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||
24 | # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
||
25 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
||
26 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
||
27 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
||
28 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||
29 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
||
30 | # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
||
31 | # SUCH DAMAGE. |
||
32 | # |
||
33 | # $Id: Packages.pm 70 2006-06-26 20:44:57Z joel $ |
||
34 | # |
||
35 | ### |
||
36 | |||
37 | # We use 'our', so we must have at least Perl 5.6 |
||
38 | |||
39 | require 5.006_000; |
||
40 | |||
41 | # Always good ideas. |
||
42 | |||
43 | use strict; |
||
44 | use warnings; |
||
45 | |||
46 | use POSIX; # WEXITSTATUS |
||
47 | use File::Temp qw(tempfile); |
||
48 | |||
49 | ### Module setup |
||
50 | |||
51 | BEGIN { |
||
52 | use Exporter (); |
||
53 | our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); |
||
54 | |||
55 | # Version checking |
||
56 | $VERSION = '0.1.5'; |
||
57 | |||
58 | @ISA = qw(Exporter); |
||
59 | |||
60 | @EXPORT = qw( |
||
61 | ); |
||
62 | |||
63 | @EXPORT_OK = qw( |
||
64 | &Allow_Version |
||
65 | &Audit_Package |
||
66 | &Generate_List |
||
67 | &Generate_Package |
||
68 | &Generate_Source |
||
69 | &Guess_Section |
||
70 | &Install_List |
||
71 | &Install_Package |
||
72 | &Parse_Changes |
||
73 | &Parse_DSC |
||
74 | &Reject_Package |
||
75 | &Verify_MD5 |
||
76 | ); |
||
77 | |||
78 | %EXPORT_TAGS = ( |
||
79 | 'functions' => [qw(&Allow_Version &Audit_Package &Generate_List |
||
80 | &Generate_Package &Generate_Source &Guess_Section |
||
81 | &Install_List &Install_Package &Parse_Changes |
||
82 | &Parse_DSC &Reject_Package &Verify_MD5)], |
||
83 | 'vars' => [qw()], |
||
84 | ); |
||
85 | } |
||
86 | |||
87 | ### Exported package globals |
||
88 | |||
89 | # None |
||
90 | |||
91 | ### Non-exported package globals |
||
92 | |||
93 | # Thread-safe? What's that? Package global error value. We don't export |
||
94 | # this directly, because it would conflict with other modules. |
||
95 | |||
96 | our($Error); |
||
97 | |||
98 | # Fields (other than package relationships) from dpkg --info that we |
||
99 | # actually care about in some fashion. |
||
100 | |||
101 | my(@Info_Fields) = ( |
||
102 | # 'Package', |
||
103 | 'Priority', |
||
104 | 'Section', |
||
105 | 'Installed-Size', |
||
106 | # 'Maintainer', |
||
107 | 'Architecture', |
||
108 | # 'Version', |
||
109 | 'Essential', |
||
110 | ); |
||
111 | |||
112 | # Package relationship fieldnames. |
||
113 | |||
114 | my(@Relationship_Fields) = ( |
||
115 | 'Pre-Depends', |
||
116 | 'Depends', |
||
117 | 'Provides', |
||
118 | 'Conflicts', |
||
119 | 'Recommends', |
||
120 | 'Suggests', |
||
121 | 'Enhances', |
||
122 | 'Replaces', |
||
123 | ); |
||
124 | |||
125 | # Normal fields potentially found in .changes files |
||
126 | |||
127 | my(%Changes_Fields) = ( |
||
128 | 'Format' => 'string', |
||
129 | 'Date' => 'string', |
||
130 | 'Source' => 'string', |
||
131 | 'Binary' => 'space_array', |
||
132 | 'Architecture' => 'space_array', |
||
133 | 'Version' => 'string', |
||
134 | 'Distribution' => 'space_array', |
||
135 | 'Urgency' => 'string', |
||
136 | 'Maintainer' => 'string', |
||
137 | 'Changed-By' => 'string', |
||
138 | 'Closes' => 'space_array', |
||
139 | ); |
||
140 | |||
141 | # Normal fields potentially found in .dsc files |
||
142 | |||
143 | my(%DSC_Fields) = ( |
||
144 | 'Format' => 'string', |
||
145 | 'Source' => 'string', |
||
146 | 'Version' => 'string', |
||
147 | 'Binary' => 'comma_array', |
||
148 | 'Maintainer' => 'string', |
||
149 | 'Architecture' => 'space_array', |
||
150 | 'Standards-Version' => 'string', |
||
151 | 'Build-Depends' => 'comma_array', |
||
152 | 'Build-Depends-Indep' => 'comma_array', |
||
153 | ); |
||
154 | |||
155 | ### File lexicals |
||
156 | |||
157 | # None |
||
158 | |||
159 | ### Constant functions |
||
160 | |||
161 | # None |
||
162 | |||
163 | ### Meaningful functions |
||
164 | |||
165 | # Allow_Version($package, $version, $distribution) |
||
166 | # |
||
167 | # Decide, based on version comparison and config options, whether $version |
||
168 | # is an acceptable version for $package in $distribution. Returns 1 if the |
||
169 | # version is acceptable, 0 if it is not, and undef (and sets $Error) in the |
||
170 | # case of an error. |
||
171 | |||
172 | sub Allow_Version { |
||
173 | use DebPool::Config qw(:vars); |
||
174 | use DebPool::DB qw(:functions); |
||
175 | use DebPool::Logging qw(:functions :facility :level); |
||
176 | |||
177 | my($package, $version, $distribution) = @_; |
||
178 | my($old_version) = Get_Version($distribution, $package, 'meta'); |
||
179 | |||
180 | # If we permit rollback, any version is valid. |
||
181 | |||
182 | if ($Options{'rollback'}) { |
||
183 | return 1; |
||
184 | } |
||
185 | |||
186 | # If we don't have an old version, anything is acceptable. |
||
187 | |||
188 | if (!defined($old_version)) { |
||
189 | return 1; |
||
190 | } |
||
191 | |||
192 | my($dpkg_bin) = '/usr/bin/dpkg'; |
||
193 | my(@args) = ('--compare-versions', $version, 'gt', $old_version); |
||
194 | |||
195 | my($sysret) = WEXITSTATUS(system($dpkg_bin, @args)); |
||
196 | |||
197 | if (0 != $sysret) { # DPKG says no go. |
||
198 | my($msg) = "Version comparison for '$package': proposed version for "; |
||
199 | $msg .= "$distribution ($version) is not greater than current "; |
||
200 | $msg .= "version ($old_version)"; |
||
201 | Log_Message($msg, LOG_GENERAL, LOG_DEBUG); |
||
202 | |||
203 | return 0; |
||
204 | } |
||
205 | |||
206 | return 1; |
||
207 | } |
||
208 | |||
209 | # Parse_Changes($changes_filename) |
||
210 | # |
||
211 | # Parses the changes file found at $changes_filename (which should be a |
||
212 | # fully qualified path and filename), and returns a hashref pointing to a |
||
213 | # Changes hash. Returns undef in the case of a failure (and sets $Error). |
||
214 | |||
215 | # Changes Hash format: |
||
216 | # { |
||
217 | # 'Architecture' => \@Architectures |
||
218 | # 'Binary' => \@Binaries |
||
219 | # 'Changed-By' => Changed-By |
||
220 | # 'Changes' => \@Changes lines |
||
221 | # 'Closes' => \@Bugs |
||
222 | # 'Description' => Description |
||
223 | # 'Files' => \@\%File Hashes |
||
224 | # 'Date' => RFC 822 timestamp |
||
225 | # 'Distribution' => \@Distributions |
||
226 | # 'Maintainer' => Maintainer |
||
227 | # 'Source' => Source |
||
228 | # 'Urgency' => Urgency |
||
229 | # 'Version' => Version |
||
230 | # } |
||
231 | |||
232 | # File Hash format: |
||
233 | # { |
||
234 | # 'Filename' => Filename (leaf node only) |
||
235 | # 'MD5Sum' => File MD5Sum |
||
236 | # 'Priority' => Requested archive priority |
||
237 | # 'Section' => Requested archive section |
||
238 | # 'Size' => File size (in bytes) |
||
239 | # } |
||
240 | |||
241 | sub Parse_Changes { |
||
242 | use DebPool::GnuPG qw(:functions); |
||
243 | use DebPool::Logging qw(:functions :facility :level); |
||
244 | |||
245 | my($file) = @_; |
||
246 | my(%result); |
||
247 | |||
248 | # Read in the entire Changes file, stripping GPG encoding if we find |
||
249 | # it. It should be small, this is fine. |
||
250 | |||
251 | if (!open(CHANGES, '<', $file)) { |
||
252 | $Error = "Couldn't open changes file '$file': $!"; |
||
253 | return undef; |
||
254 | } |
||
255 | |||
256 | my(@changes) = <CHANGES>; |
||
257 | chomp(@changes); |
||
258 | @changes = Strip_GPG(@changes); |
||
259 | close(CHANGES); |
||
260 | |||
261 | # Go through each of the primary fields, stuffing it into the result |
||
262 | # hash if we find it. |
||
263 | |||
264 | my($field); |
||
265 | foreach $field (keys(%Changes_Fields)) { |
||
266 | my(@lines) = grep(/^${field}:\s+/, @changes); |
||
267 | if (-1 == $#lines) { # No match |
||
268 | next; |
||
269 | } elsif (0 < $#lines) { # Multiple matches |
||
270 | Log_Message("Duplicate entries for field '$field'", |
||
271 | LOG_PARSE, LOG_WARNING); |
||
272 | } |
||
273 | |||
274 | $lines[0] =~ s/^${field}:\s+//; |
||
275 | |||
276 | if ('string' eq $Changes_Fields{$field}) { |
||
277 | $result{$field} = $lines[0]; |
||
278 | } elsif ('space_array' eq $Changes_Fields{$field}) { |
||
279 | my(@array) = split(/\s+/, $lines[0]); |
||
280 | $result{$field} = \@array; |
||
281 | } elsif ('comma_array' eq $Changes_Fields{$field}) { |
||
282 | my(@array) = split(/\s+,\s+/, $lines[0]); |
||
283 | $result{$field} = \@array; |
||
284 | } |
||
285 | } |
||
286 | |||
287 | # Now that we should have it, check to make sure we have a Format |
||
288 | # header, and that it's format 1.7 (the only thing we grok). |
||
289 | |||
290 | if (!defined($result{'Format'})) { |
||
291 | Log_Message("No Format header found in changes file '$file'", |
||
292 | LOG_PARSE, LOG_ERROR); |
||
293 | $Error = 'No Format header found'; |
||
294 | return undef; |
||
295 | } elsif ('1.7' ne $result{'Format'}) { |
||
296 | Log_Message("Unrecognized Format version '$result{'Format'}'", |
||
297 | LOG_PARSE, LOG_ERROR); |
||
298 | $Error = 'Unrecognized Format version'; |
||
299 | return undef; |
||
300 | } |
||
301 | |||
302 | # Special case: Description. One-line entry, immediately after a line |
||
303 | # with '^Description:'. |
||
304 | |||
305 | my($count); |
||
306 | |||
307 | for $count (0..$#changes) { |
||
308 | if ($changes[$count] =~ m/^Description:/) { |
||
309 | $result{'Description'} = $changes[$count+1]; |
||
310 | } |
||
311 | } |
||
312 | |||
313 | # Special case: Changes. Multi-line entry, starts one line after |
||
314 | # '^Changes:', goes until we hit the Files header. |
||
315 | |||
316 | my($found) = 0; |
||
317 | my(@changelines); |
||
318 | |||
319 | for $count (0..$#changes) { |
||
320 | if ($found) { |
||
321 | if ($changes[$count] =~ m/^Files:/) { |
||
322 | $found = 0; |
||
323 | } else { |
||
324 | push(@changelines, $changes[$count]); |
||
325 | } |
||
326 | } else { |
||
327 | if ($changes[$count] =~ m/^Changes:/) { |
||
328 | $found = 1; |
||
329 | } |
||
330 | } |
||
331 | } |
||
332 | |||
333 | $result{'Changes'} = \@changelines; |
||
334 | |||
335 | # The Files section is a special case. It starts on the line after the |
||
336 | # 'Files:' header, and goes until we hit a blank line, or the end of |
||
337 | # the data. |
||
338 | |||
339 | my(@files); |
||
340 | |||
341 | for $count (0..$#changes) { |
||
342 | if ($found) { |
||
343 | if ($changes[$count] =~ m/^\s*$/) { # Blank line |
||
344 | $found = 0; # No longer in Files |
||
345 | } elsif ($changes[$count] =~ m/\s*([[:xdigit:]]+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)/) { |
||
346 | my($md5, $size, $sec, $pri, $file) = ($1, $2, $3, $4, $5); |
||
347 | push(@files, { |
||
348 | 'Filename' => $file, |
||
349 | 'MD5Sum' => $md5, |
||
350 | 'Priority' => $pri, |
||
351 | 'Section' => $sec, |
||
352 | 'Size' => $size, |
||
353 | }); |
||
354 | } else { # What's this doing here? |
||
355 | my($msg) = 'Unrecognized data in Files section of changes file'; |
||
356 | $msg .= " '$file'"; |
||
357 | Log_Message($msg, LOG_PARSE, LOG_WARNING); |
||
358 | } |
||
359 | } else { |
||
360 | if ($changes[$count] =~ m/^Files:/) { |
||
361 | $found = 1; |
||
362 | } |
||
363 | } |
||
364 | } |
||
365 | |||
366 | $result{'Files'} = \@files; |
||
367 | |||
368 | return \%result; |
||
369 | } |
||
370 | |||
371 | # Parse_DSC($dsc_filename) |
||
372 | # |
||
373 | # Parses the dsc file found at $dsc_filename (which should be a fully |
||
374 | # qualified path and filename), and returns a hashref pointing to a DSC |
||
375 | # hash. Returns undef in the case of a failure (and sets $Error). |
||
376 | |||
377 | # DSC Hash format: |
||
378 | # { |
||
379 | # 'Format' => Format |
||
380 | # 'Source' => Source |
||
381 | # 'Binary' => \@Binaries |
||
382 | # 'Maintainer' => Maintainer |
||
383 | # 'Architecture' => \@Architectures |
||
384 | # 'Standards-Version' => Standards-Version |
||
385 | # 'Build-Depends' => Build-Depends |
||
386 | # 'Build-Depends-Indep' => Build-Depends-Indep |
||
387 | # 'Files' => \@\%Filehash |
||
388 | # } |
||
389 | |||
390 | # File Hash format: |
||
391 | # { |
||
392 | # 'Filename' => Filename (leaf node only) |
||
393 | # 'MD5Sum' => File MD5Sum |
||
394 | # 'Size' => File size (in bytes) |
||
395 | # } |
||
396 | |||
397 | sub Parse_DSC { |
||
398 | use DebPool::GnuPG qw(:functions); |
||
399 | use DebPool::Logging qw(:functions :facility :level); |
||
400 | |||
401 | my($file) = @_; |
||
402 | my(%result); |
||
403 | |||
404 | # Read in the entire DSC file, stripping GPG encoding if we find it. It |
||
405 | # should be small, this is fine. |
||
406 | |||
407 | if (!open(DSC, '<', $file)) { |
||
408 | $Error = "Couldn't open dsc file '$file': $!"; |
||
409 | return undef; |
||
410 | } |
||
411 | |||
412 | my(@dsc) = <DSC>; |
||
413 | chomp(@dsc); |
||
414 | @dsc = Strip_GPG(@dsc); |
||
415 | close(DSC); |
||
416 | |||
417 | # Go through each of the primary fields, stuffing it into the result |
||
418 | # hash if we find it. |
||
419 | |||
420 | my($field); |
||
421 | foreach $field (keys(%DSC_Fields)) { |
||
422 | my(@lines) = grep(/^${field}:\s+/, @dsc); |
||
423 | if (-1 == $#lines) { # No match |
||
424 | next; |
||
425 | } elsif (0 < $#lines) { # Multiple matches |
||
426 | Log_Message("Duplicate entries for field '$field'", |
||
427 | LOG_PARSE, LOG_WARNING); |
||
428 | } |
||
429 | |||
430 | $lines[0] =~ s/^${field}:\s+//; |
||
431 | |||
432 | if ('string' eq $DSC_Fields{$field}) { |
||
433 | $result{$field} = $lines[0]; |
||
434 | } elsif ('space_array' eq $DSC_Fields{$field}) { |
||
435 | my(@array) = split(/\s+/, $lines[0]); |
||
436 | $result{$field} = \@array; |
||
437 | } elsif ('comma_array' eq $DSC_Fields{$field}) { |
||
438 | my(@array) = split(/\s+,\s+/, $lines[0]); |
||
439 | $result{$field} = \@array; |
||
440 | } |
||
441 | } |
||
442 | |||
443 | # Now that we should have it, check to make sure we have a Format |
||
444 | # header, and that it's format 1.0 (the only thing we grok). |
||
445 | |||
446 | if (!defined($result{'Format'})) { |
||
447 | Log_Message("No Format header found in dsc file '$file'", |
||
448 | LOG_PARSE, LOG_ERROR); |
||
449 | $Error = 'No Format header found'; |
||
450 | return undef; |
||
451 | } elsif ('1.0' ne $result{'Format'}) { |
||
452 | Log_Message("Unrecognized Format version '$result{'Format'}'", |
||
453 | LOG_PARSE, LOG_ERROR); |
||
454 | $Error = 'Unrecognized Format version'; |
||
455 | return undef; |
||
456 | } |
||
457 | |||
458 | # The Files section is a special case. It starts on the line after the |
||
459 | # 'Files:' header, and goes until we hit a blank line, or the end of |
||
460 | # the data. |
||
461 | |||
462 | # In fact, it's even more special than that; it includes, first, an entry |
||
463 | # for the DSC file itself... |
||
464 | |||
465 | my($count); |
||
466 | my($found) = 0; |
||
467 | my(@files); |
||
468 | |||
469 | my(@temp) = split(/\//, $file); |
||
470 | my($dsc_leaf) = pop(@temp); |
||
471 | |||
472 | my($cmd_result) = `/usr/bin/md5sum $file`; |
||
473 | $cmd_result =~ m/^([[:xdigit:]]+)\s+/; |
||
474 | my($dsc_md5) = $1; |
||
475 | |||
476 | my(@stat) = stat($file); |
||
477 | if (!@stat) { |
||
478 | $Error = "Couldn't stat DSC file '$file'"; |
||
479 | return undef; |
||
480 | } |
||
481 | my($dsc_size) = $stat[7]; |
||
482 | |||
483 | push(@files, { |
||
484 | 'Filename' => $dsc_leaf, |
||
485 | 'MD5Sum' => $dsc_md5, |
||
486 | 'Size' => $dsc_size, |
||
487 | }); |
||
488 | |||
489 | for $count (0..$#dsc) { |
||
490 | if ($found) { |
||
491 | if ($dsc[$count] =~ m/^\s*$/) { # Blank line |
||
492 | $found = 0; # No longer in Files |
||
493 | } elsif ($dsc[$count] =~ m/\s*([[:xdigit:]]+)\s+(\d+)\s+(\S+)/) { |
||
494 | my($md5, $size, $file) = ($1, $2, $3); |
||
495 | push(@files, { |
||
496 | 'Filename' => $file, |
||
497 | 'MD5Sum' => $md5, |
||
498 | 'Size' => $size, |
||
499 | }); |
||
500 | } else { # What's this doing here? |
||
501 | my($msg) = 'Unrecognized data in Files section of dsc file'; |
||
502 | $msg .= " '$file'"; |
||
503 | Log_Message($msg, LOG_PARSE, LOG_WARNING); |
||
504 | } |
||
505 | } else { |
||
506 | if ($dsc[$count] =~ m/^Files:/) { |
||
507 | $found = 1; |
||
508 | } |
||
509 | } |
||
510 | } |
||
511 | |||
512 | $result{'Files'} = \@files; |
||
513 | |||
514 | return \%result; |
||
515 | } |
||
516 | |||
517 | # Generate_List($distribution, $section, $arch) |
||
518 | # |
||
519 | # Generates a Packages (or Sources) file for the given distribution, |
||
520 | # section, and architecture (with 'source' being a special value for |
||
521 | # Sources). Returns the filename of the generated file on success, or undef |
||
522 | # (and sets $Error) on failure. Note that requests for an 'all' list are |
||
523 | # ignored - however, every non-source arch gets 'all' files. |
||
524 | |||
525 | sub Generate_List { |
||
526 | use DebPool::Config qw(:vars); |
||
527 | use DebPool::DB qw(:functions :vars); |
||
528 | use DebPool::Dirs qw(:functions); |
||
529 | |||
530 | my($distribution, $section, $arch) = @_; |
||
531 | |||
532 | my(%packages); |
||
533 | |||
534 | if ('all' eq $arch) { |
||
535 | $Error = "No point in generating Packages file for binary-all"; |
||
536 | return undef; |
||
537 | } |
||
538 | |||
539 | my(@sources) = grep($ComponentDB{$distribution}->{$_} eq $section, |
||
540 | keys(%{$ComponentDB{$distribution}})); |
||
541 | |||
542 | my($tmpfile_handle, $tmpfile_name) = tempfile(); |
||
543 | |||
544 | my($source); |
||
545 | |||
546 | # Dump the data from pool/*/*/pkg_ver.{package,source} into the list. |
||
547 | |||
548 | # FIXME: This needs to be refactored. Needs it pretty badly, in fact. |
||
549 | |||
550 | if ('source' eq $arch) { |
||
551 | foreach $source (@sources) { |
||
552 | my($pool) = join('/', |
||
553 | ($Options{'pool_dir'}, PoolDir($source, $section), $source)); |
||
554 | my($version) = Get_Version($distribution, $source, 'meta'); |
||
555 | my($target) = "$pool/${source}_" . Strip_Epoch($version); |
||
556 | $target .= '.source'; |
||
557 | |||
558 | # Source files aren't always present. |
||
559 | next if (!open(SRC, '<', "$target")); |
||
560 | |||
561 | print $tmpfile_handle <SRC>; |
||
562 | close(SRC); |
||
563 | } |
||
564 | } else { |
||
565 | foreach $source (@sources) { |
||
566 | my($pool) = join('/', |
||
567 | ($Options{'pool_dir'}, PoolDir($source, $section), $source)); |
||
568 | my($version) = Get_Version($distribution, $source, 'meta'); |
||
569 | my($target) = "$pool/${source}_" . Strip_Epoch($version); |
||
570 | $target .= '.package'; |
||
571 | |||
572 | if (!open(PKG, '<', "$target")) { |
||
573 | my($msg) = "Skipping package entry for all packages from "; |
||
574 | $msg .= "${source}: couldn't open '$target' for reading: $!"; |
||
575 | |||
576 | Log_Message($msg, LOG_GENERAL, LOG_ERROR); |
||
577 | next; |
||
578 | } |
||
579 | |||
580 | # Playing around with the record separator ($/) to make this |
||
581 | # easier. |
||
582 | |||
583 | my($backup_RS) = $/; |
||
584 | $/ = ""; |
||
585 | |||
586 | my(@entries) = <PKG>; |
||
587 | close(PKG); |
||
588 | |||
589 | $/ = $backup_RS; |
||
590 | |||
591 | # Pare it down to the relevant entries, and print those out. |
||
592 | |||
593 | @entries = grep(/\nArchitecture: ($arch|all)\n/, @entries); |
||
594 | print $tmpfile_handle @entries; |
||
595 | } |
||
596 | } |
||
597 | |||
598 | close($tmpfile_handle); |
||
599 | |||
600 | return $tmpfile_name; |
||
601 | } |
||
602 | |||
603 | # Install_Package($changes, $Changes_hashref, $DSC, $DSC_hashref, \@distributions) |
||
604 | # |
||
605 | # Install all of the package files for $Changes_hashref (which should |
||
606 | # be a Parse_Changes result hash) into the pool directory, and install |
||
607 | # the file in $changes to the installed directory. Also generates (and |
||
608 | # installes) .package and .source meta-data files. It also updates the |
||
609 | # Version database for the listed distributions. Returns 1 if successful, 0 |
||
610 | # if not (and sets $Error). |
||
611 | |||
612 | sub Install_Package { |
||
613 | use DebPool::Config qw(:vars); |
||
614 | use DebPool::Dirs qw(:functions); |
||
615 | use DebPool::DB qw(:functions :vars); |
||
616 | use DebPool::Util qw(:functions); |
||
617 | |||
618 | my($changes, $chg_hashref, $dsc, $dsc_hashref, $distributions) = @_; |
||
619 | |||
620 | my($incoming_dir) = $Options{'incoming_dir'}; |
||
621 | my($installed_dir) = $Options{'installed_dir'}; |
||
622 | my($pool_dir) = $Options{'pool_dir'}; |
||
623 | |||
624 | my($pkg_name) = $chg_hashref->{'Source'}; |
||
625 | my($pkg_ver) = $chg_hashref->{'Version'}; |
||
626 | |||
627 | my($guess_section) = Guess_Section($chg_hashref); |
||
628 | my($pkg_dir) = join('/', |
||
629 | ($pool_dir, PoolDir($pkg_name, $guess_section), $pkg_name)); |
||
630 | |||
631 | # Make sure the package directory exists (and is a directory!) |
||
632 | |||
633 | if (! -e $pkg_dir) { |
||
634 | if (!mkdir($pkg_dir)) { |
||
635 | $Error = "Failed to mkdir '$pkg_dir': $!"; |
||
636 | return 0; |
||
637 | } |
||
638 | if (!chmod($Options{'pool_dir_mode'}, $pkg_dir)) { |
||
639 | $Error = "Failed to chmod '$pkg_dir': $!"; |
||
640 | return 0; |
||
641 | } |
||
642 | } elsif (! -d $pkg_dir) { |
||
643 | $Error = "Target '$pkg_dir' is not a directory."; |
||
644 | return 0; |
||
645 | } |
||
646 | |||
647 | # Walk the File Hash, trying to install each listed file into the |
||
648 | # pool directory. |
||
649 | |||
650 | my($filehash); |
||
651 | |||
652 | foreach $filehash (@{$chg_hashref->{'Files'}}) { |
||
653 | my($file) = $filehash->{'Filename'}; |
||
654 | if (!Move_File("${incoming_dir}/${file}", "${pkg_dir}/${file}", |
||
655 | $Options{'pool_file_mode'})) { |
||
656 | $Error = "Failed to move '${incoming_dir}/${file}' "; |
||
657 | $Error .= "to '${pkg_dir}/${file}': ${DebPool::Util::Error}"; |
||
658 | return 0; |
||
659 | } |
||
660 | } |
||
661 | |||
662 | # Generate and install .package and .source metadata files. |
||
663 | |||
664 | my($pkg_file) = Generate_Package($chg_hashref); |
||
665 | |||
666 | if (!defined($pkg_file)) { |
||
667 | $Error = "Failed to generate .package file: $Error"; |
||
668 | return undef; |
||
669 | } |
||
670 | |||
671 | my($target) = "$pkg_dir/${pkg_name}_" . Strip_Epoch($pkg_ver) . '.package'; |
||
672 | |||
673 | if (!Move_File($pkg_file, $target, $Options{'pool_file_mode'})) { |
||
674 | $Error = "Failed to move '$pkg_file' to '$target': "; |
||
675 | $Error .= $DebPool::Util::Error; |
||
676 | return 0; |
||
677 | } |
||
678 | |||
679 | if (defined($dsc) && defined($dsc_hashref)) { |
||
680 | my($src_file) = Generate_Source($dsc, $dsc_hashref, $chg_hashref); |
||
681 | |||
682 | if (!defined($src_file)) { |
||
683 | $Error = "Failed to generate .source file: $Error"; |
||
684 | return undef; |
||
685 | } |
||
686 | |||
687 | $target = "$pkg_dir/${pkg_name}_" . Strip_Epoch($pkg_ver) . '.source'; |
||
688 | |||
689 | if (!Move_File($src_file, $target, $Options{'pool_file_mode'})) { |
||
690 | $Error = "Failed to move '$src_file' to '$target': "; |
||
691 | $Error .= $DebPool::Util::Error; |
||
692 | return 0; |
||
693 | } |
||
694 | } |
||
695 | |||
696 | # Finally, try to install the changes file to the installed directory. |
||
697 | |||
698 | if (!Move_File("$incoming_dir/$changes", "$installed_dir/$changes", |
||
699 | $Options{'installed_file_mode'})) { |
||
700 | $Error = "Failed to move '$incoming_dir/$changes' to "; |
||
701 | $Error .= "'$installed_dir/$changes': ${DebPool::Util::Error}"; |
||
702 | return 0; |
||
703 | } |
||
704 | |||
705 | # Update the various databases. |
||
706 | |||
707 | my($distribution); |
||
708 | |||
709 | # This whole block is just to calculate the component. What a stupid |
||
710 | # setup - it should be in the changes file. Oh well. |
||
711 | |||
712 | my(@filearray) = @{$chg_hashref->{'Files'}}; |
||
713 | my($fileref) = $filearray[0]; |
||
714 | my($section) = $fileref->{'Section'}; |
||
715 | my($component) = Strip_Subsection($section); |
||
716 | |||
717 | foreach $distribution (@{$distributions}) { |
||
718 | Set_Versions($distribution, $pkg_name, $pkg_ver, |
||
719 | $chg_hashref->{'Files'}); |
||
720 | $ComponentDB{$distribution}->{$pkg_name} = $component; |
||
721 | } |
||
722 | |||
723 | return 1; |
||
724 | } |
||
725 | |||
726 | # Reject_Package($changes, $chg_hashref) |
||
727 | # |
||
728 | # Move all of the package files for $chg_hashref (which should be a |
||
729 | # Parse_Changes result hash) into the rejected directory, as well as the |
||
730 | # file in $changes. Returns 1 if successful, 0 if not (and sets $Error). |
||
731 | |||
732 | sub Reject_Package { |
||
733 | use DebPool::Config qw(:vars); |
||
734 | use DebPool::DB qw(:functions); |
||
735 | use DebPool::Util qw(:functions); |
||
736 | |||
737 | my($changes, $chg_hashref) = @_; |
||
738 | |||
739 | my($incoming_dir) = $Options{'incoming_dir'}; |
||
740 | my($reject_dir) = $Options{'reject_dir'}; |
||
741 | my($reject_file_mode) = $Options{'reject_file_mode'}; |
||
742 | |||
743 | # Walk the File Hash, moving each file to the rejected directory. |
||
744 | |||
745 | my($filehash); |
||
746 | |||
747 | foreach $filehash (@{$chg_hashref->{'Files'}}) { |
||
748 | my($file) = $filehash->{'Filename'}; |
||
749 | if (!Move_File("$incoming_dir/$file", "$reject_dir/$file", |
||
750 | $reject_file_mode)) { |
||
751 | $Error = "Failed to move '$incoming_dir/$file' "; |
||
752 | $Error .= "to '$reject_dir/$file': ${DebPool::Util::Error}"; |
||
753 | return 0; |
||
754 | } |
||
755 | } |
||
756 | |||
757 | # Now move the changes file to the rejected directory, as well. |
||
758 | |||
759 | if (!Move_File("$incoming_dir/$changes", "$reject_dir/$changes", |
||
760 | $reject_file_mode)) { |
||
761 | $Error = "Failed to move '$incoming_dir/$changes' to "; |
||
762 | $Error .= "'$reject_dir/$changes': ${DebPool::Util::Error}"; |
||
763 | return 0; |
||
764 | } |
||
765 | |||
766 | return 1; |
||
767 | } |
||
768 | |||
769 | # Verify_MD5($file, $md5) |
||
770 | # |
||
771 | # Verifies the MD5 checksum of $file against $md5. Returns 1 if it matches, |
||
772 | # 0 if it doesn't, and undef (also setting $Error) if an error occurs. This |
||
773 | # routine uses the dpkg md5sum utility, to avoid pulling in a dependancy on |
||
774 | # Digest::MD5. |
||
775 | |||
776 | sub Verify_MD5 { |
||
777 | use DebPool::Logging qw(:functions :facility :level); |
||
778 | |||
779 | my($file, $md5) = @_; |
||
780 | |||
781 | # Read in and mangle the md5 output. |
||
782 | |||
783 | if (! -r $file) { # The file doesn't exist! Will be hard to checksum it... |
||
784 | my($msg) = "MD5 checksum unavailable: file '$file' does not exist!"; |
||
785 | Log_Message($msg, LOG_GENERAL, LOG_ERROR); |
||
786 | return 0; |
||
787 | } |
||
788 | |||
789 | my($cmd_result) = `/usr/bin/md5sum $file`; |
||
790 | if (!$cmd_result) { # Failed to run md5sum for some reason |
||
791 | my($msg) = "MD5 checksum unavailable: file '$file'"; |
||
792 | Log_Message($msg, LOG_GENERAL, LOG_ERROR); |
||
793 | return 0; |
||
794 | } |
||
795 | |||
796 | $cmd_result =~ m/^([[:xdigit:]]+)\s+/; |
||
797 | my($check_md5) = $1; |
||
798 | |||
799 | if ($md5 ne $check_md5) { |
||
800 | my($msg) = "MD5 checksum failure: file '$file', "; |
||
801 | $msg .= "expected '$md5', got '$check_md5'"; |
||
802 | Log_Message($msg, LOG_GENERAL, LOG_ERROR); |
||
803 | return 0; |
||
804 | } |
||
805 | |||
806 | return 1; |
||
807 | } |
||
808 | |||
809 | # Audit_Package($package, $chg_hashref) |
||
810 | # |
||
811 | # Delete a package and changes files for the named (source) package which |
||
812 | # are not referenced by any version currently found in the various release |
||
813 | # databases. Returns the number of files unlinked (which may be 0), or |
||
814 | # undef (and sets $Error) on an error. |
||
815 | |||
816 | sub Audit_Package { |
||
817 | use DebPool::Config qw(:vars); |
||
818 | use DebPool::Dirs qw(:functions); |
||
819 | use DebPool::Logging qw(:functions :facility :level); |
||
820 | |||
821 | my($package, $changes_hashref) = @_; |
||
822 | |||
823 | my($installed_dir) = $Options{'installed_dir'}; |
||
824 | my($pool_dir) = $Options{'pool_dir'}; |
||
825 | |||
826 | my($section) = Guess_Section($changes_hashref); |
||
827 | my($package_dir) = join('/', |
||
828 | ($pool_dir, PoolDir($package, $section), $package)); |
||
829 | |||
830 | my(@changes) = grep(/${package}_/, Scan_Changes($installed_dir)); |
||
831 | |||
832 | my($pool_scan) = Scan_All($package_dir); |
||
833 | if (!defined($pool_scan)) { |
||
834 | $Error = $DebPool::Dirs::Error; |
||
835 | return undef; |
||
836 | } |
||
837 | my(@pool_files) = @{$pool_scan}; |
||
838 | |||
839 | # Go through each file found in the pool directory, and determine its |
||
840 | # version. If it isn't in the current version tables, unlink it. |
||
841 | |||
842 | my($file); |
||
843 | my($unlinked) = 0; |
||
844 | foreach $file (@pool_files) { |
||
845 | my($orig) = 0; |
||
846 | my($deb) = 0; |
||
847 | my($src) = 0; |
||
848 | my($bin_package, $version); |
||
849 | |||
850 | if ($file =~ m/^([^_]+)_([^_]+)\.orig\.tar\.gz$/) { # orig.tar.gz |
||
851 | $bin_package = $1; |
||
852 | $version = $2; |
||
853 | $src = 1; |
||
854 | $orig = 1; |
||
855 | } elsif ($file =~ m/^([^_]+)_([^_]+)\.tar\.gz$/) { # tar.gz |
||
856 | $bin_package = $1; |
||
857 | $version = $2; |
||
858 | $src = 1; |
||
859 | } elsif ($file =~ m/^([^_]+)_([^_]+)\.diff\.gz$/) { # diff.gz |
||
860 | $bin_package = $1; |
||
861 | $version = $2; |
||
862 | $src = 1; |
||
863 | } elsif ($file =~ m/^([^_]+)_([^_]+)\.dsc$/) { # dsc |
||
864 | $bin_package = $1; |
||
865 | $version = $2; |
||
866 | $src = 1; |
||
867 | } elsif ($file =~ m/^([^_]+)_([^_]+)_.+\.deb$/) { # deb |
||
868 | $bin_package = $1; |
||
869 | $version = $2; |
||
870 | $deb = 1; |
||
871 | } elsif ($file =~ m/^([^_]+)_([^_]+)_.+\.udeb$/) { # udeb |
||
872 | $bin_package = $1; |
||
873 | $version = $2; |
||
874 | $deb = 1; |
||
875 | } elsif ($file =~ m/^([^_]+)_([^_]+)\.package$/) { # package metadata |
||
876 | $bin_package = $1; |
||
877 | $version = $2; |
||
878 | } elsif ($file =~ m/^([^_]+)_([^_]+)\.source$/) { # source metadata |
||
879 | $bin_package = $1; |
||
880 | $version = $2; |
||
881 | } else { |
||
882 | Log_Message("Couldn't figure out filetype for '$package_dir/$file'", |
||
883 | LOG_AUDIT, LOG_ERROR); |
||
884 | next; |
||
885 | } |
||
886 | |||
887 | # Skip it if we recognize it as a valid version. |
||
888 | |||
889 | my($matched) = 0; |
||
890 | my($dist); |
||
891 | foreach $dist (@{$Options{'realdists'}}) { |
||
892 | my($ver_pkg); |
||
893 | if ($src) { |
||
894 | $ver_pkg = 'source'; |
||
895 | } elsif ($deb) { |
||
896 | $ver_pkg = $bin_package; |
||
897 | } else { |
||
898 | $ver_pkg = 'meta'; |
||
899 | } |
||
900 | |||
901 | my($dist_ver) = Get_Version($dist, $package, $ver_pkg); |
||
902 | next if (!defined($dist_ver)); # No version in specified dist |
||
903 | $dist_ver = Strip_Epoch($dist_ver); |
||
904 | if ($orig) { $dist_ver =~ s/-.+$//; } |
||
905 | if ($version eq $dist_ver) { $matched = 1; } |
||
906 | } |
||
907 | next if $matched; |
||
908 | |||
909 | # Otherwise, unlink it. |
||
910 | |||
911 | if (unlink("$package_dir/$file")) { |
||
912 | $unlinked += 1; |
||
913 | Log_Message("Unlinked obsolete pool file '$package_dir/$file'", |
||
914 | LOG_AUDIT, LOG_DEBUG); |
||
915 | } else { |
||
916 | Log_Message("Couldn't obsolete pool file '$package_dir/$file'", |
||
917 | LOG_AUDIT, LOG_ERROR); |
||
918 | } |
||
919 | } |
||
920 | |||
921 | foreach $file (@changes) { |
||
922 | $file =~ m/^[^_]+_([^_]+)_.+$/; # changes |
||
923 | my($version) = $1; |
||
924 | |||
925 | my($matched) = 0; |
||
926 | my($dist); |
||
927 | foreach $dist (@{$Options{'realdists'}}) { |
||
928 | my($dist_ver) = Get_Version($dist, $package, 'meta'); |
||
929 | next if (!defined($dist_ver)); # No version in specified dist |
||
930 | $dist_ver = Strip_Epoch($dist_ver); |
||
931 | if ($version eq $dist_ver) { $matched = 1; } |
||
932 | } |
||
933 | next if $matched; |
||
934 | |||
935 | if (unlink("$installed_dir/$file")) { |
||
936 | $unlinked += 1; |
||
937 | Log_Message("Unlinked obsolete changes file " . |
||
938 | "'$installed_dir/$file'", LOG_AUDIT, LOG_DEBUG); |
||
939 | } else { |
||
940 | Log_Message("Couldn't obsolete changes file " . |
||
941 | "'$installed_dir/$file'", LOG_AUDIT, LOG_ERROR); |
||
942 | } |
||
943 | } |
||
944 | |||
945 | return $unlinked; |
||
946 | } |
||
947 | |||
948 | # Generate_Package($chg_hashref) |
||
949 | # |
||
950 | # Generates a .package metadata file (Packages entries for each binary |
||
951 | # package) in the tempfile area, and returns the filename. Returns undef |
||
952 | # (and sets $Error) on failure. |
||
953 | |||
954 | sub Generate_Package { |
||
955 | use DebPool::Config qw(:vars); |
||
956 | use DebPool::Dirs qw(:functions); |
||
957 | use DebPool::Logging qw(:functions :facility :level); |
||
958 | |||
959 | my($changes_data) = @_; |
||
960 | my($source) = $changes_data->{'Source'}; |
||
961 | my(@files) = @{$changes_data->{'Files'}}; |
||
962 | my($pool_base) = PoolBasePath(); |
||
963 | |||
964 | # Grab a temporary file. |
||
965 | |||
966 | my($tmpfile_handle, $tmpfile_name) = tempfile(); |
||
967 | |||
968 | my(@packages) = @{$changes_data->{'Binary'}}; |
||
969 | my(@architectures) = @{$changes_data->{'Architecture'}}; |
||
970 | @architectures = grep(!/source/, @architectures); # Source is on it's own. |
||
971 | |||
972 | my($package, $arch); |
||
973 | |||
974 | foreach $package (@packages) { |
||
975 | foreach $arch (@architectures) { |
||
976 | # Construct a pattern to match the filename and nothing else. |
||
977 | # This used to be an exact match using the source version, but |
||
978 | # Debian's standards are sort of insane, and the version number |
||
979 | # on binary files is not always the same as that on the source |
||
980 | # file (nor is it even something simple like "source version |
||
981 | # without the epoch" -- it is more or less arbitrary, as long |
||
982 | # as it is a well-formed version number). |
||
983 | |||
984 | my($filepat) = "${package}_.*_${arch}\\.deb"; |
||
985 | $filepat =~ s/\+/\\\+/; |
||
986 | |||
987 | my($section) = Guess_Section($changes_data); |
||
988 | my($pool) = join('/', (PoolDir($source, $section), $source)); |
||
989 | |||
990 | my($marker) = -1; |
||
991 | my($count) = 0; |
||
992 | |||
993 | # Step through each file, match against filename. Save matches |
||
994 | # for later use. |
||
995 | |||
996 | for $count (0..$#files) { |
||
997 | if ($files[$count]->{'Filename'} =~ m/$filepat/) { |
||
998 | $marker = $count; |
||
999 | } |
||
1000 | } |
||
1001 | |||
1002 | # The changes file has a stupid quirk; it puts all binaries from |
||
1003 | # a package in the Binary: line, even if they weren't built (for |
||
1004 | # example, an Arch: all doc package when doing an arch-only build |
||
1005 | # for a port). So if we didn't find a .deb file for it, assume |
||
1006 | # that it's one of those, and skip, rather than choking on it. |
||
1007 | |||
1008 | next if (-1 == $marker); |
||
1009 | |||
1010 | # Run Dpkg_Info to grab the dpkg --info data on the package. |
||
1011 | |||
1012 | my($file) = $files[$marker]->{'Filename'}; |
||
1013 | my($info) = Dpkg_Info("$Options{'pool_dir'}/$pool/$file"); |
||
1014 | |||
1015 | # Dump all of our data into the metadata tempfile. |
||
1016 | |||
1017 | print $tmpfile_handle "Package: $package\n"; |
||
1018 | |||
1019 | if (defined($info->{'Priority'})) { |
||
1020 | print $tmpfile_handle "Priority: $info->{'Priority'}\n"; |
||
1021 | } |
||
1022 | |||
1023 | if (defined($info->{'Section'})) { |
||
1024 | print $tmpfile_handle "Section: $info->{'Section'}\n"; |
||
1025 | } |
||
1026 | |||
1027 | if (defined($info->{'Essential'})) { |
||
1028 | print $tmpfile_handle "Essential: $info->{'Essential'}\n"; |
||
1029 | } |
||
1030 | |||
1031 | print $tmpfile_handle "Installed-Size: $info->{'Installed-Size'}\n"; |
||
1032 | |||
1033 | print $tmpfile_handle "Maintainer: $changes_data->{'Maintainer'}\n"; |
||
1034 | print $tmpfile_handle "Architecture: $arch\n"; |
||
1035 | print $tmpfile_handle "Source: $source\n"; |
||
1036 | print $tmpfile_handle "Version: $changes_data->{'Version'}\n"; |
||
1037 | |||
1038 | # All of the inter-package relationships go together, and any |
||
1039 | # one of them can potentially be empty (and omitted). |
||
1040 | |||
1041 | my($field); |
||
1042 | foreach $field (@Relationship_Fields) { |
||
1043 | if (defined($info->{$field})) { |
||
1044 | print $tmpfile_handle "${field}: $info->{$field}\n"; |
||
1045 | } |
||
1046 | } |
||
1047 | |||
1048 | # And now, some stuff we can grab out of the parsed changes |
||
1049 | # data far more easily than anywhere else. |
||
1050 | |||
1051 | print $tmpfile_handle "Filename: $pool_base/$pool/$file\n"; |
||
1052 | |||
1053 | print $tmpfile_handle "Size: $files[$marker]->{'Size'}\n"; |
||
1054 | print $tmpfile_handle "MD5sum: $files[$marker]->{'MD5Sum'}\n"; |
||
1055 | |||
1056 | print $tmpfile_handle "Description: $info->{'Description'}"; |
||
1057 | } |
||
1058 | |||
1059 | print $tmpfile_handle "\n"; |
||
1060 | } |
||
1061 | |||
1062 | # All done |
||
1063 | |||
1064 | close($tmpfile_handle); |
||
1065 | return $tmpfile_name; |
||
1066 | } |
||
1067 | |||
1068 | # Generate_Source($dsc, $dsc_hashref, $changes_hashref) |
||
1069 | # |
||
1070 | # Generates a .source metadata file (Sources entries for the source |
||
1071 | # package) in the tempfile area, and returns the filename. Returns undef |
||
1072 | # (and sets $Error) on failure. |
||
1073 | |||
1074 | sub Generate_Source { |
||
1075 | use DebPool::Dirs qw(:functions); |
||
1076 | use DebPool::Logging qw(:functions :facility :level); |
||
1077 | |||
1078 | my($dsc, $dsc_data, $changes_data) = @_; |
||
1079 | my($source) = $dsc_data->{'Source'}; |
||
1080 | my(@files) = @{$dsc_data->{'Files'}}; |
||
1081 | |||
1082 | # Figure out the priority and section, using the DSC filename and |
||
1083 | # the Changes file data. |
||
1084 | |||
1085 | my($section, $priority); |
||
1086 | my($filehr); |
||
1087 | foreach $filehr (@{$changes_data->{'Files'}}) { |
||
1088 | if ($filehr->{'Filename'} eq $dsc) { |
||
1089 | $section = $filehr->{'Section'}; |
||
1090 | $priority = $filehr->{'Priority'}; |
||
1091 | } |
||
1092 | } |
||
1093 | |||
1094 | # Grab a temporary file. |
||
1095 | |||
1096 | my($tmpfile_handle, $tmpfile_name) = tempfile(); |
||
1097 | |||
1098 | # Dump out various metadata. |
||
1099 | |||
1100 | print $tmpfile_handle "Package: $source\n"; |
||
1101 | print $tmpfile_handle "Binary: " . join(', ', @{$dsc_data->{'Binary'}}) . "\n"; |
||
1102 | print $tmpfile_handle "Version: $dsc_data->{'Version'}\n"; |
||
1103 | print $tmpfile_handle "Priority: $priority\n"; |
||
1104 | print $tmpfile_handle "Section: $section\n"; |
||
1105 | print $tmpfile_handle "Maintainer: $dsc_data->{'Maintainer'}\n"; |
||
1106 | |||
1107 | if (defined($dsc_data->{'Build-Depends'})) { |
||
1108 | print $tmpfile_handle 'Build-Depends: '; |
||
1109 | print $tmpfile_handle join(', ', @{$dsc_data->{'Build-Depends'}}) . "\n"; |
||
1110 | } |
||
1111 | |||
1112 | if (defined($dsc_data->{'Build-Depends-Indep'})) { |
||
1113 | print $tmpfile_handle 'Build-Depends-Indep: '; |
||
1114 | print $tmpfile_handle join(', ', @{$dsc_data->{'Build-Depends-Indep'}}) . "\n"; |
||
1115 | } |
||
1116 | |||
1117 | print $tmpfile_handle 'Architecture: '; |
||
1118 | print $tmpfile_handle join(' ', @{$dsc_data->{'Architecture'}}) . "\n"; |
||
1119 | |||
1120 | print $tmpfile_handle "Standards-Version: $dsc_data->{'Standards-Version'}\n"; |
||
1121 | print $tmpfile_handle "Format: $dsc_data->{'Format'}\n"; |
||
1122 | print $tmpfile_handle "Directory: " . join('/', |
||
1123 | (PoolBasePath(), PoolDir($source, $section), $source)) . "\n"; |
||
1124 | |||
1125 | print $tmpfile_handle "Files:\n"; |
||
1126 | |||
1127 | my($fileref); |
||
1128 | foreach $fileref (@files) { |
||
1129 | print $tmpfile_handle " $fileref->{'MD5Sum'}"; |
||
1130 | print $tmpfile_handle " $fileref->{'Size'}"; |
||
1131 | print $tmpfile_handle " $fileref->{'Filename'}\n"; |
||
1132 | } |
||
1133 | |||
1134 | print $tmpfile_handle "\n"; |
||
1135 | |||
1136 | # All done |
||
1137 | |||
1138 | close($tmpfile_handle); |
||
1139 | return $tmpfile_name; |
||
1140 | } |
||
1141 | |||
1142 | # Dpkg_Info($file) |
||
1143 | # |
||
1144 | # Runs dpkg --info on $file, and returns a hash of relevant information. |
||
1145 | # |
||
1146 | # Internal support function for Generate_Package. |
||
1147 | |||
1148 | sub Dpkg_Info { |
||
1149 | my($file) = @_; |
||
1150 | my(%result); |
||
1151 | |||
1152 | # Grab the info from dpkg --info. |
||
1153 | |||
1154 | my(@info) = `/usr/bin/dpkg --info $file`; |
||
1155 | my($smashed) = join('', @info); |
||
1156 | |||
1157 | # Look for each of these fields in the info. All are single line values, |
||
1158 | # so the matching is fairly easy. |
||
1159 | |||
1160 | my($field); |
||
1161 | |||
1162 | foreach $field (@Info_Fields, @Relationship_Fields) { |
||
1163 | if ($smashed =~ m/\n ${field}:\s+(\S.*)\n/) { |
||
1164 | $result{$field} = $1; |
||
1165 | } |
||
1166 | } |
||
1167 | |||
1168 | # And, finally, grab the description. |
||
1169 | |||
1170 | my($line); |
||
1171 | my($found) = 0; |
||
1172 | foreach $line (@info) { |
||
1173 | if ($found) { |
||
1174 | $line =~ s/^ //; |
||
1175 | $result{'Description'} .= $line; |
||
1176 | } elsif ($line =~ m/^ Description: (.+)/) { |
||
1177 | $result{'Description'} = "$1\n"; |
||
1178 | $found = 1; |
||
1179 | } |
||
1180 | } |
||
1181 | |||
1182 | return \%result; |
||
1183 | } |
||
1184 | |||
1185 | # Install_List($archive, $component, $architecture, $listfile, $gzfile) |
||
1186 | # |
||
1187 | # Installs a distribution list file (from Generate_List), along with an |
||
1188 | # optional gzipped version of the same file (if $gzfile is defined). |
||
1189 | # Returns 1 on success, or 0 (and sets $Error) on failure. |
||
1190 | |||
1191 | sub Install_List { |
||
1192 | use DebPool::Config qw(:vars); |
||
1193 | use DebPool::Dirs qw(:functions); |
||
1194 | |||
1195 | my($archive, $component, $architecture, $listfile, $gzfile) = @_; |
||
1196 | |||
1197 | my($dists_file_mode) = $Options{'dists_file_mode'}; |
||
1198 | my($inst_file) = "$Options{'dists_dir'}/"; |
||
1199 | $inst_file .= Archfile($archive, $component, $architecture, 0); |
||
1200 | |||
1201 | # Now install the file(s) into the appropriate place(s). |
||
1202 | |||
1203 | if (!Move_File($listfile, $inst_file, $dists_file_mode)) { |
||
1204 | $Error = "Couldn't install distribution file '$listfile' "; |
||
1205 | $Error .= "to '${inst_file}': ${DebPool::Util::Error}"; |
||
1206 | return 0; |
||
1207 | } |
||
1208 | |||
1209 | if (defined($gzfile) && !Move_File($gzfile, "${inst_file}.gz", |
||
1210 | $dists_file_mode)) { |
||
1211 | $Error = "Couldn't install gzipped distribution file '$gzfile' "; |
||
1212 | $Error .= "to '${inst_file}.gz': ${DebPool::Util::Error}"; |
||
1213 | return 0; |
||
1214 | } |
||
1215 | |||
1216 | return 1; |
||
1217 | } |
||
1218 | |||
1219 | # Guess_Section($changes_hashref) |
||
1220 | # |
||
1221 | # Attempt to guess the freeness section of a package based on the data |
||
1222 | # for the first file listed in the changes. |
||
1223 | |||
1224 | sub Guess_Section { |
||
1225 | # Pull out the primary section from the changes data. Note that this is |
||
1226 | # a cheap hack, but it is mostly used when needing the pool directory |
||
1227 | # section, which is based solely on freeness-sections (main, contrib, |
||
1228 | # non-free). |
||
1229 | |||
1230 | my($changes_hashref) = @_; |
||
1231 | |||
1232 | my(@changes_files) = @{$changes_hashref->{'Files'}}; |
||
1233 | return $changes_files[0]->{'Section'}; |
||
1234 | } |
||
1235 | |||
1236 | # Strip_Epoch($version) |
||
1237 | # |
||
1238 | # Strips any epoch data off of the version. |
||
1239 | |||
1240 | sub Strip_Epoch { |
||
1241 | my($version) = @_; |
||
1242 | |||
1243 | $version =~ s/^[^:]://; |
||
1244 | return $version; |
||
1245 | } |
||
1246 | |||
1247 | END {} |
||
1248 | |||
1249 | 1; |
||
1250 | |||
1251 | __END__ |
||
1252 | |||
1253 | # vim:set tabstop=4 expandtab: |