File Coverage

blib/lib/Linux/APT.pm
Criterion Covered Total %
statement 6 163 3.6
branch 0 86 0.0
condition 0 12 0.0
subroutine 2 9 22.2
pod 7 7 100.0
total 15 277 5.4


line stmt bran cond sub pod time code
1             package Linux::APT;
2              
3 1     1   33332 use strict;
  1         2  
  1         37  
4 1     1   5 use warnings;
  1         2  
  1         2953  
5              
6             our $VERSION = '0.02';
7              
8             =head1 NAME
9              
10             Linux::APT - Interface with APT for Debian distributions
11              
12             =head1 DESCRIPTION
13              
14             Perl interface to C and C.
15             If Debian's C modules were on CPAN, this module (probably) wouldn't be necessary.
16             This is just a wrapper around the C tools along with some regular expression magic
17             to capture interesting pieces of information/warnings/errors in the process.
18             It doesn't do I that is possible, but it should fill the most typical needs.
19             Features will be added on request or my own need.
20             Please file a wishlist bug report on the CPAN bug tracker with your feature requests.
21              
22             All (or almost all?) features require root privileges.
23             If you can use C to provide this functionality, see C to see how to do this.
24              
25             It's not ready for production use, but you're welcome to give it a try.
26             I file bug reports if you come across any problems/bugs/etc.
27             Patches are always welcomed, of course.
28              
29             =head1 EXAMPLE
30              
31             my $apt = Linux::APT->new;
32             my $update = $apt->update;
33             my $toupgrade = $apt->toupgrade;
34             my $upgraded = $apt->install(keys(%{$toupgrade->{packages}}));
35              
36             =head1 METHODS
37              
38             =head2 new
39              
40             my $apt = Linux::APT->new;
41              
42             # only if you _really_ want to see what's going on...
43             my $apt = Linux::APT->new(debug => 1);
44              
45             # if you want to use an alternate apt-get/apt-cache binary
46             my $apt = Linux::APT->new(
47             aptget => '/some/path/to/apt-get',
48             aptcache => '/some/path/to/apt-cache',
49             );
50              
51             # if you have special needs (like sudo, etc)
52             my $apt = Linux::APT->new(
53             aptget => '/usr/bin/sudo /some/path/to/apt-get -s', # sudo and no-act
54             aptcache => '/usr/bin/sudo /some/path/to/apt-cache', # sudo
55             );
56              
57             Creates an instance of Linux::APT, just like you would expect.
58              
59             If you have special needs for only one function (install maybe?), make a separate instance
60             with your special needs (flags, sudo, etc) and use that instance for your special need.
61              
62             If your special need can't be accommodated via the C option above, let me know and
63             I'll attempt to implement whatever it is that you need within the module or make your special
64             need a bit more "accessible" to you.
65             File a bug report on the CPAN bug tracker.
66             Patches welcome, of course.
67              
68             Arguments available:
69              
70             =over
71              
72             =item debug
73              
74             Set to C<1> to enable, defaults to C<0>.
75              
76             =item aptget
77              
78             Specify the C binary to use along with any special flags or command line tricks (sudo, chroot, fakeroot, etc).
79             Defaults to C<`which apt-get`>.
80              
81             =item aptcache
82              
83             Specify the C binary to use along with any special flags or command line tricks (sudo, chroot, fakeroot, etc).
84             Defaults to C<`which apt-cache`>.
85              
86             =back
87              
88             =cut
89              
90             sub new
91             {
92 0     0 1   my $class = shift;
93 0           my $self = {};
94 0           my %args = @_;
95              
96 0           $self->{debug} = $args{debug};
97              
98 0   0       $self->{aptget} = $args{aptget} || `which apt-get`;
99 0           chomp($self->{aptget});
100 0 0         die qq(apt-get doesn't appear to be available.\n) unless $self->{aptget};
101              
102 0   0       $self->{aptcache} = $args{aptcache} || `which apt-cache`;
103 0           chomp($self->{aptcache});
104 0 0         die qq(apt-cache doesn't appear to be available.\n) unless $self->{aptcache};
105              
106 0           return bless($self, $class);
107             }
108              
109             =head2 update
110              
111             my $update = $apt->update;
112              
113             warn "There were errors...\n" if $update->{error};
114             warn "There were warnings...\n" if $update->{warning};
115              
116             Update apt cache.
117             Basically equivalent to C.
118              
119             Returns hashref containing these items:
120              
121             =over
122              
123             =item error
124              
125             Arrayref of errors.
126              
127             =item warning
128              
129             Arrayref of warnings.
130              
131             =item speed
132              
133             Network transfer speed of update.
134              
135             =item time
136              
137             Wallclock time it took to update.
138              
139             =item size
140              
141             Amount of received transferred during update.
142              
143             =back
144              
145             =cut
146              
147             sub update
148             {
149 0     0 1   my $self = shift;
150 0           my $update = {};
151              
152 0 0         if (open(APT, "$self->{aptget} -q update 2>&1 |"))
153             {
154 0           while (my $line = )
155             {
156 0           chomp($line);
157 0 0         print qq($line\n) if $self->{debug};
158 0 0         if ($line =~ m#Fetched (\d+\S+) in (.*?) \((\d+\S+?)\)#i)
    0          
    0          
159             {
160 0           $update->{size} = $1;
161 0           $update->{time} = $2;
162 0           $update->{speed} = $3;
163             }
164             elsif ($line =~ s#^W: ##) # warning
165             {
166 0           my $warning = {};
167 0           $warning->{message} = $line;
168 0           push(@{$update->{warning}}, $warning);
  0            
169             }
170             elsif ($line =~ s#^E: ##) # error
171             {
172 0           my $error = {};
173 0           $error->{message} = $line;
174 0           push(@{$update->{error}}, $error);
  0            
175             }
176             }
177 0           close(APT);
178             }
179             else
180             {
181 0           die "Couldn't use APT: $!\n";
182             }
183              
184 0           return $update;
185             }
186              
187             =head2 toupgrade
188              
189             my $toupgrade = $apt->toupgrade;
190              
191             Returns hashref of packages, errors, and warnings:
192              
193             =over
194              
195             =item warning
196              
197             Warnings, if any.
198              
199             =item error
200              
201             Errors, if any.
202              
203             =item packages
204              
205             Contains a hashref of updateable packages.
206             Keys are package names.
207             Each update is a hashref containing these items:
208              
209             =over
210              
211             =item current
212              
213             Currently installed version.
214              
215             =item new
216              
217             Version to be installed.
218              
219             =back
220              
221             =back
222              
223             =cut
224              
225             sub toupgrade
226             {
227 0     0 1   my $self = shift;
228 0           my $updates = {};
229              
230 0 0         if (open(APT, "echo n | $self->{aptget} -q -V upgrade 2>&1 |"))
231             {
232 0           while (my $line = )
233             {
234 0           chomp($line);
235 0 0         print qq($line\n) if $self->{debug};
236 0 0         if ($line =~ m#^\s+(\S+)\s+\((\S+)\s+=>\s+(\S+)\)#)
    0          
    0          
237             {
238 0           my $update = {};
239 0           my $package = $1;
240 0           $update->{current} = $2;
241 0           $update->{new} = $3;
242 0           $updates->{packages}->{$package} = $update;
243             }
244             elsif ($line =~ s#^W: ##) # warning
245             {
246 0           my $warning = {};
247 0           $warning->{message} = $line;
248 0           push(@{$updates->{warning}}, $warning);
  0            
249             }
250             elsif ($line =~ s#^E: ##) # error
251             {
252 0           my $error = {};
253 0           $error->{message} = $line;
254 0           push(@{$updates->{error}}, $error);
  0            
255             }
256             }
257 0           close(APT);
258             }
259              
260 0           return $updates;
261             }
262              
263             =head2 search
264            
265             my $search = $apt->search('^t\w+d$', 'perl');
266              
267             my $search = $apt->search({in => ['all']}, '^t\w+d$', 'perl'); # 'all' is default
268              
269             my $search = $apt->search({in=>['name', 'description']},
270             'linux[\s-]image', 'linux[\s-]source', 'linux kernel image');
271              
272             my $search = $apt->search({in => ['description']}, 'linux kernel source');
273            
274             Requires one or more search arguments in regex format. Optional options as first
275             argument in hashref format.
276              
277             Return a hashref of packages that match the regex search.
278              
279             =over
280            
281             =item packages
282            
283             Multiple searches can be specified. Each search is a hash key then broken
284             down by each matching package name and it's summary.
285              
286             =back
287            
288             =cut
289            
290             sub search
291             {
292 0     0 1   my $self = shift;
293 0           my $search = {};
294 0           my @args = @_;
295 0           my $opts = {
296             in => ['all'],
297             };
298              
299 0 0         if (ref($args[0]) eq 'HASH')
300             {
301 0           my $optarg = shift;
302 0           foreach my $arg (keys(%{$optarg}))
  0            
303             {
304 0           $opts->{$arg} = $optarg->{$arg};
305             }
306             }
307            
308 0           foreach my $pkg (@args)
309             {
310 0 0         if (open(APT, "$self->{'aptcache'} search '$pkg' 2>&1 |"))
311             {
312 0           while (my $line = )
313             {
314 0           my $okay = 0;
315 0 0         $okay = 1 if (grep(m/all/, @{$opts->{in}}));
  0            
316 0           chomp($line);
317 0 0         print qq($line\n) if $self->{'debug'};
318 0 0         if ($line =~ m/^(\S+)\s+-\s+(.*)$/)
319             {
320 0           my ($name, $desc) = ($1, $2);
321 0           chomp($desc);
322 0 0 0       $okay = 1 if (grep(m/name/, @{$opts->{in}}) && $name =~ m/$pkg/i);
  0            
323 0 0 0       $okay = 1 if (grep(m/description/, @{$opts->{in}}) && $desc =~ m/$pkg/i);
  0            
324 0 0         next unless $okay;
325 0           $search->{$pkg}->{$name} = $desc;
326             }
327             }
328             }
329 0           close(APT);
330             }
331            
332 0           return $search;
333             }
334              
335             =head2 install
336              
337             # install or upgrade the specified packages (and all deps)
338             my $install = $apt->install('nautilus', 'libcups2', 'rhythmbox');
339              
340             # just a dry run
341             my $install = $apt->install('-test', 'nautilus', 'libcups2', 'rhythmbox');
342              
343             # upgrade all upgradable packages with a name containing "pulseaudio" (and all deps)
344             my $toupgrade = $apt->toupgrade;
345             my $install = $apt->install(grep(m/pulseaudio/i, keys(%{$toupgrade->{packages}})));
346              
347             Install a list of packages.
348             If the packages are already installed, they will be upgraded if an upgrade is available.
349              
350             Pass in these optional options:
351              
352             =over
353              
354             =item -force
355              
356             If you wish to force an update (eg: C),
357             pass C<-force> as one of your arguments (same effect as C).
358              
359             =item -test
360              
361             If you just want to know what packages would be installed/upgraded/removed, pass C<-test>
362             as one of your arguments.
363             No actions will actually take place, only the actions that would have been performed will be captured.
364             This is useful when you want to ensure some bad thing doesn't happen on accident (like removing
365             C when you install C) or to allow you to present the proposed changes to the
366             user via a user interface (GUI, webapp, etc).
367              
368             =back
369              
370             Returns hashref of packages, errors, and warnings:
371              
372             =over
373              
374             =item warning
375              
376             Warnings, if any.
377              
378             =item error
379              
380             Errors, if any.
381              
382             =item packages
383              
384             Contains a hashref of installed/upgraded packages.
385             Keys are package names.
386             Each item is a hashref containing these items:
387              
388             =over
389              
390             =item current
391              
392             Currently installed version (after install/upgrade attempt).
393             This version is found via an experimental technique and might fail (though it has yet to fail for me).
394             Let me know if you find a bug or have a problem with this value.
395              
396             =item new
397              
398             Version to be installed.
399             If C, the action seems to have succeeded.
400              
401             =item old
402              
403             Version that was installed before the upgrade was performed.
404             If C, the action seems to have failed.
405              
406             =back
407              
408             =back
409              
410             =cut
411              
412             sub install
413             {
414 0     0 1   my $self = shift;
415 0           my @install = @_;
416              
417 0           my $action = 'install';
418 0           my $force = '';
419 0           my $noop = 0;
420 0           my $packages = '';
421 0           my $installed = {};
422              
423 0           foreach my $install (@install)
424             {
425 0 0         if ($install eq '-force')
    0          
    0          
    0          
426             {
427 0           $force = '--force-yes';
428 0           next;
429             }
430             elsif ($install eq '-test')
431             {
432 0           $noop = 1;
433 0           next;
434             }
435             elsif ($install eq '-remove')
436             {
437 0           $action = 'remove';
438 0           next;
439             }
440             elsif ($install eq '-purge')
441             {
442 0           $action = 'purge';
443 0           next;
444             }
445              
446 0           (my $package = $install) =~ s/[^a-z0-9\+\-_\.]//ig;
447 0           $packages .= $package.' ';
448             }
449              
450 0           my $state = '';
451 0 0         my $notreally = ($noop ? 'echo n |' : '');
452 0 0         my $justsayyes = ($noop ? '-s' : "-y $force");
453              
454 0 0         if (open(APT, "$notreally $self->{aptget} $justsayyes -q -V $action $packages 2>&1 |"))
455             {
456 0           while (my $line = )
457             {
458 0           chomp($line);
459 0 0         print qq($line\n) if $self->{debug};
460 0 0         if ($line =~ m/The following packages will be REMOVED:/i)
    0          
    0          
    0          
    0          
    0          
    0          
    0          
461             {
462 0           $state = 'removed';
463             }
464             elsif ($line =~ m/The following NEW packages will be installed:/i)
465             {
466 0           $state = 'installed';
467             }
468             elsif ($line =~ m/The following packages will be upgraded:/i)
469             {
470 0           $state = 'upgraded';
471             }
472             elsif ($line =~ m#^\s+(\S+)\s+\((\S+)\s+=>\s+(\S+)\)#) # upgrading
473             {
474 0           my $update = {};
475 0           my $package = $1;
476 0           $update->{old} = $2;
477 0           $update->{new} = $3;
478 0           $package =~ s/\*$//;
479 0           $installed->{packages}->{$package} = $update;
480 0           $installed->{$state}->{$package} = $installed->{packages}->{$package};
481             }
482             elsif ($line =~ m#^\s+(\S+)\s+\((\S+)\)#) # installing
483             {
484 0           my $update = {};
485 0           my $package = $1;
486 0           my $version = $2;
487 0           $package =~ s/\*$//;
488 0 0         if ($state eq 'removed')
489             {
490 0           $installed->{$state}->{$package} = $version
491             }
492             else
493             {
494 0           $update->{new} = $version;
495 0 0         $installed->{packages}->{$package} = $update if $state;
496 0 0         $installed->{$state}->{$package} = $installed->{packages}->{$package} if $state;
497             }
498             }
499             elsif ($line =~ m/^(\d+)\s+upgraded,\s+(\d+)\s+newly\s+installed,\s+(\d+)\s+to\s+remove\s+and\s+(\d+)\s+not\s+upgraded./i)
500             {
501 0           $state = '';
502 0           $installed->{intended}->{upgraded} = $1;
503 0           $installed->{intended}->{installed} = $2;
504 0           $installed->{intended}->{removed} = $3;
505 0           $installed->{intended}->{upgradable} = $4;
506             }
507             elsif ($line =~ s#^W: ##) # warning
508             {
509 0           my $warning = {};
510 0           $warning->{message} = $line;
511 0           push(@{$installed->{warning}}, $warning);
  0            
512             }
513             elsif ($line =~ s#^E: ##) # error
514             {
515 0           my $error = {};
516 0           $error->{message} = $line;
517 0           push(@{$installed->{error}}, $error);
  0            
518             }
519             }
520 0           close(APT);
521             }
522              
523 0 0         unless ($noop)
524             {
525 0           foreach my $package (keys(%{$installed->{packages}}))
  0            
526             {
527 0 0         if (open(APT, "$self->{aptcache} showpkg $package |"))
528             {
529 0           while (my $line = )
530             {
531 0           chomp($line);
532 0 0         print qq($line\n) if $self->{debug};
533 0 0         if ($line =~ m#^(\S+)\s+.*?\(/var/lib/dpkg/status\)#)
534             {
535 0           $installed->{packages}->{$package}->{current} = $1;
536             }
537             }
538 0           close(APT);
539             }
540             }
541             }
542              
543 0           return $installed;
544             }
545              
546             =head2 remove
547              
548             my $removed = $apt->remove('php5', 'php5-common');
549              
550             # just a dry run
551             my $removed = $apt->remove('-test', 'php5', 'php5-common');
552              
553             Remove a list of packages.
554             Arguments are the exact same as C.
555             Returns the exact same as C.
556              
557             =cut
558              
559             sub remove
560             {
561 0     0 1   my $self = shift;
562 0           return $self->install('-remove', @_);
563             }
564              
565             =head2 purge
566              
567             my $removed = $apt->purge('php5', 'php5-common');
568              
569             # just a dry run
570             my $removed = $apt->purge('-test', 'php5', 'php5-common');
571              
572             Purge a list of packages.
573             Arguments are the exact same as C.
574             Returns the exact same as C.
575              
576             =cut
577              
578             sub purge
579             {
580 0     0 1   my $self = shift;
581 0           return $self->install('-purge', @_);
582             }
583              
584             =head1 TODO
585              
586             =over
587              
588             =item (update this todo list...)
589              
590             =item Add functions to modify the C.
591              
592             =item Add C functionality.
593              
594             =item Add function to show version(s) of currently installed specified package(s).
595              
596             =item Determine other necessary features. (please use the CPAN bug tracker to request features)
597              
598             =back
599              
600             =head1 BUGS/WISHLIST
601              
602             B
603             Report any bugs to the CPAN bug tracker. Bug reports are adored.
604              
605             To wishlist something, use the CPAN bug tracker (set as wishlist).
606             I'd be happy to implement useful functionality in this module on request.
607              
608             =head1 PARTICIPATION
609              
610             I'd be very, very happy to accept patches in diff format. Or...
611              
612             If you wish to hack on this code, please fork the git repository found at:
613             L
614              
615             If you have some goodness to push back to that repository, just use the
616             "pull request" button on the github site or let me know where to pull from.
617              
618             =head1 THANKS
619              
620             =over
621              
622             =item Nicholas DeClario
623              
624             =over
625              
626             =item Patch to provide initial search function for version 0.02.
627              
628             =back
629              
630             =back
631              
632             =head1 COPYRIGHT/LICENSE
633              
634             Copyright 2009 Megagram. You can use any one of these licenses: Perl Artistic, GPL (version >= 2), BSD.
635              
636             =head2 Perl Artistic License
637              
638             Read it at L.
639             This is the license we prefer.
640              
641             =head2 GNU General Public License (GPL) Version 2
642              
643             This program is free software; you can redistribute it and/or
644             modify it under the terms of the GNU General Public License
645             as published by the Free Software Foundation; either version 2
646             of the License, or (at your option) any later version.
647              
648             This program is distributed in the hope that it will be useful,
649             but WITHOUT ANY WARRANTY; without even the implied warranty of
650             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
651             GNU General Public License for more details.
652              
653             You should have received a copy of the GNU General Public License
654             along with this program. If not, see http://www.gnu.org/licenses/
655              
656             See the full license at L.
657              
658             =head2 GNU General Public License (GPL) Version 3
659              
660             This program is free software: you can redistribute it and/or modify
661             it under the terms of the GNU General Public License as published by
662             the Free Software Foundation, either version 3 of the License, or
663             (at your option) any later version.
664              
665             This program is distributed in the hope that it will be useful,
666             but WITHOUT ANY WARRANTY; without even the implied warranty of
667             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
668             GNU General Public License for more details.
669              
670             You should have received a copy of the GNU General Public License
671             along with this program. If not, see http://www.gnu.org/licenses/
672              
673             See the full license at L.
674              
675             =head2 BSD License
676              
677             Copyright (c) 2009 Megagram.
678             All rights reserved.
679              
680             Redistribution and use in source and binary forms, with or without modification, are permitted
681             provided that the following conditions are met:
682              
683             * Redistributions of source code must retain the above copyright notice, this list of conditions
684             and the following disclaimer.
685             * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
686             and the following disclaimer in the documentation and/or other materials provided with the
687             distribution.
688             * Neither the name of Megagram nor the names of its contributors may be used to endorse
689             or promote products derived from this software without specific prior written permission.
690              
691             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
692             WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
693             PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
694             ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
695             LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
696             INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
697             OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
698             IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
699              
700             =cut
701              
702             1;