File Coverage

script/demerge
Criterion Covered Total %
statement 36 352 10.2
branch 0 118 0.0
condition 0 9 0.0
subroutine 12 28 42.8
pod n/a
total 48 507 9.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             # -----------------------------------------------------------------------------
4             #
5             # demerge
6             #
7             # date : 2008-12-01
8             # author : Christian Hartmann
9             # version : 0.047
10             # license : GPL-2
11             # description : Revert to previous installation states.
12             #
13             # header : $Header: /srv/cvsroot/demerge/demerge,v 1.21 2008/12/01 20:38:29 ian Exp $
14             #
15             # -----------------------------------------------------------------------------
16             #
17             # This program is free software; you can redistribute it and/or modify it under
18             # the terms of the GNU General Public License as published by the Free Software
19             # Foundation; either version 2 of the License, or (at your option) any later
20             # version.
21             #
22             # -----------------------------------------------------------------------------
23              
24             # - modules >
25 1     1   422 use warnings;
  1         1  
  1         26  
26 1     1   4 use strict;
  1         1  
  1         15  
27 1     1   633 use Getopt::Long; Getopt::Long::Configure('bundling');
  1         7335  
  1         2  
28 1     1   665 use Term::ANSIColor;
  1         4656  
  1         49  
29 1     1   475 use Term::ReadKey;
  1         2751  
  1         57  
30 1     1   421 use File::Copy;
  1         1394  
  1         42  
31 1     1   3 use File::Path;
  1         1  
  1         35  
32 1     1   716 use Path::Tiny;
  1         6929  
  1         39  
33 1     1   436 use Shell::EnvImporter;
  1         76382  
  1         26  
34 1     1   541 use PortageXS;
  1         13952  
  1         21  
35 1     1   393 use PortageXS::UI::Spinner;
  1         23868  
  1         24  
36 1     1   438 use DirHandle;
  1         349  
  1         3885  
37              
38             $|=1;
39              
40             # - init vars & constants >
41             my $VERSION = '0.048';
42             my $NEEDVERSION = '0.043';
43             my $DEBUG = 0;
44             my $comment = '';
45             my $do = 0;
46             my $recordSystemState = 0;
47             my $restoreSystemState = 0;
48             my $restorePrevious = 0;
49             my $wipe = 0;
50             my $wipeOlder = 0;
51             my $noColor = 0;
52             my $postsync = 0;
53             my $pxs = PortageXS->new();
54             my $homedir = $pxs->getHomedir().'/.demerge';
55             my $cmd_homedir = '';
56             my $configfile = '/etc/demerge.conf';
57             my @emergePackages = ();
58             my @unmergePackages = ();
59             my @crossgradePackages = ();
60             my @quickpkgPackages = ();
61             my $time = 0;
62             my %emergeOpts = ();
63             my $terminalWidth = 80;
64             my $missingEbuilds = 0;
65             my %CACHE_getUseSettingsOfInstalledPackage = ();
66             my %CACHE_getUseSettingsOfRecordedPackage = ();
67             my %CACHE_getUseSettingsOfInstalledPackageF = ();
68             my %CACHE_getUseSettingsOfRecordedPackageF = ();
69             my @CACHE_searchInstalledPackage = ();
70             my @CACHE_repos = ();
71             push(@CACHE_repos,$pxs->getPortageMakeParam('PORTDIR'));
72             push(@CACHE_repos,split(/ /,$pxs->getPortageMakeParam('PORTDIR_OVERLAY')));
73              
74             # - init colors >
75             my $yellow = color('bold yellow');
76             my $green = color('green');
77             my $lightgreen = color('bold green');
78             my $white = color('bold white');
79             my $cyan = color('bold cyan');
80             my $red = color('bold red');
81             my $blue = color('bold blue');
82             my $reset = color('reset');
83              
84             # - Check if colors are allowed (first make.conf then .demergerc then command-line option) >
85             if (lc($pxs->getParamFromFile(path('/etc/portage/make.conf')->slurp,'NOCOLOR','lastseen')) eq 'true') {
86             $noColor=1;
87             }
88             else {
89             $noColor=0;
90             }
91             if (-e $configfile) {
92             if (lc($pxs->getParamFromFile(path($configfile)->slurp,'nocolor','lastseen')) eq 'true') {
93             $noColor=1;
94             }
95             elsif (lc($pxs->getParamFromFile(path($configfile)->slurp,'nocolor','lastseen')) eq 'false') {
96             $noColor=0;
97             }
98             }
99              
100             # - get options >
101             GetOptions(
102             'comment=s' => \$comment,
103             'dir=s' => \$cmd_homedir,
104             'do' => \$do,
105             'record-system-state' => \$recordSystemState,
106             'record' => \$recordSystemState,
107             'restore-system-state=s' => \$restoreSystemState,
108             'restore=s' => \$restoreSystemState,
109             'restore-previous' => \$restorePrevious,
110             'usepkg|k' => \$emergeOpts{'usepkg'},
111             'wipe:1' => \$wipe,
112             'wipe-older=s' => \$wipeOlder,
113             'nocolor|C' => \$noColor,
114             'postsync' => \$postsync,
115             'help|h' => sub { printHeader(); printUsage(); }
116             ) || printUsage();
117              
118             if ($noColor) {
119             $yellow = '';
120             $green = '';
121             $lightgreen = '';
122             $white = '';
123             $cyan = '';
124             $red = '';
125             $blue = '';
126             $reset = '';
127             $pxs->{'COLORS'}{'YELLOW'} = '';
128             $pxs->{'COLORS'}{'GREEN'} = '';
129             $pxs->{'COLORS'}{'LIGHTGREEN'} = '';
130             $pxs->{'COLORS'}{'WHITE'} = '';
131             $pxs->{'COLORS'}{'CYAN'} = '';
132             $pxs->{'COLORS'}{'RED'} = '';
133             $pxs->{'COLORS'}{'BLUE'} = '';
134             $pxs->{'COLORS'}{'RESET'} = '';
135             }
136              
137             # - check if user is root >
138             if ($< > 0) {
139             printHeader();
140             print $red.' * '.$reset."To use demerge you must have root access. Aborting.\n\n";
141             exit(0);
142             }
143             else {
144             if ($postsync) {
145             if (-e $configfile) {
146             $homedir=$pxs->getParamFromFile(path($configfile)->slurp,'datadir','lastseen');
147             }
148             postsyncHook();
149             exit(0);
150             }
151             else {
152             # - normal startup >
153             # - Actually do not call GetTerminalSize() when --postsync is called (error reported on efika/ppc by amne)
154             # - Also do not call GetTerminalSize() if TERM is not set (when demerge is called by cron e.g.; reported by pille)
155             if (exists $ENV{'TERM'}) {
156             $terminalWidth = (GetTerminalSize())[0];
157             }
158             printHeader();
159             print $red.' Use this program carefully - otherwise you might run into problems.'.$reset."\n";
160             print $red.' You are root. You are responsible for your actions.'.$reset."\n";
161             print " Bugs and requests go to ian .\n\n";
162            
163             print " Following repositories will be used:\n";
164             my $number=0;
165             foreach (@CACHE_repos) {
166             $number++;
167             print ' ['.$number.'] '.$_."\n";
168             }
169             print "\n";
170             }
171             }
172              
173             # - read configfile (if available) >
174             if (-e $configfile) {
175             demergeReadConfig();
176             }
177              
178             # - User defined homedir overrides config settings >
179             if ($cmd_homedir) {
180             $homedir=$cmd_homedir;
181             }
182              
183             # - Do some basic checks >
184             if (! -d $homedir) {
185             if(mkdir($homedir)) {
186             print $lightgreen.' * '.$reset.'Created '.$homedir."\n";
187             }
188             else {
189             print $red.' * '.$reset.'Could not create '.$homedir.". Aborting.\n\n";
190             exit(0);
191             }
192             }
193              
194             print $lightgreen.' * '.$reset.'Using datadir: '.$homedir."\n\n";
195              
196             # - dispatcher >
197             if (!$recordSystemState && !$restoreSystemState && !$restorePrevious && !$wipe && !$wipeOlder) {
198             my @availableStates=getAvailableStates();
199            
200             if ($#availableStates>-1) {
201             setupCache();
202            
203             # - Process states >
204             print $lightgreen.' * '.$reset."Found previous states:\n\n";
205             my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
206             foreach (@availableStates) {
207             # - print timestamp && date && comment (if any) >
208             ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($_);
209             print ' ',$_,' (',($year+1900),'-',sprintf('%02s',($mon+1)),'-',sprintf('%02s',$mday),' ',sprintf('%02s',$hour),':',sprintf('%02s',$min),':',sprintf('%02s',$sec),')';
210             print ' - ',path($homedir, $_.'.comment')->slurp if (-e path($homedir, $_.'.comment'));
211             print "\n";
212            
213             if (!printStateDiff($_,1)) {
214             print " Skipping - system state has been recorded with an older/incompatible version of demerge.\n";
215             }
216             print "\n";
217             }
218             print $lightgreen.' * '.$reset."To revert to one of the previous system-states run 'demerge --restore timestamp'.\n\n";
219             }
220             else {
221             printUsage();
222             }
223             exit(0);
224             }
225              
226             if ($recordSystemState) {
227             print $lightgreen.' * '.$reset.'Recording current system state..';
228             $time=recordSystemState();
229             print " done\n";
230             print $lightgreen.' * '.$reset."To restore the system-state run 'demerge --restore ".$time."'.\n\n";
231             exit(0);
232             }
233              
234             if ($restoreSystemState || $restorePrevious) {
235             setupCache();
236            
237             if ($restorePrevious) {
238             # - Get latest state >
239             my @availableStates=getAvailableStates();
240             $restoreSystemState=$availableStates[$#availableStates];
241             if (!$restoreSystemState) { $restoreSystemState=0; }
242             }
243            
244             # - Check if given file exists >
245             if (!-e $homedir.'/'.$restoreSystemState.'.systemstate') {
246             $pxs->printColored('RED',"No systemstate file found or unable to open! Aborting.\n\n");
247             exit(0);
248             }
249            
250             if (!printStateDiff($restoreSystemState)) {
251             print $red.' * '.$reset."System state has been recorded with an older/incompatible version of demerge.\n\n";
252             print $yellow.' * '.$reset."Quitting.\n\n";
253             exit(1);
254             }
255            
256             if (!$do) {
257             if ($missingEbuilds) {
258             if ($missingEbuilds==1) {
259             print $red.' * '.$reset.$missingEbuilds." ebuild missing. Due to this demerge will not be able to revert to the given state!\n\n";
260             }
261             else {
262             print $red.' * '.$reset.$missingEbuilds." ebuilds are missing. Due to this demerge will not be able to revert to the given state!\n\n";
263             }
264             exit(1);
265             }
266             if ($pxs->cmdAskUser('Proceed?','y,n') eq 'n') {
267             print "\n";
268             print $yellow.' * '.$reset."Quitting.\n\n";
269             exit(1);
270             }
271             }
272            
273             print "\n";
274             print $lightgreen.' * '.$reset.'Recording current system state..';
275             $comment='State recorded before restoring to '.$restoreSystemState;
276             $time=recordSystemState();
277             print " done\n";
278            
279             if ($#unmergePackages>=0) {
280             if ($emergeOpts{'usepkg'}) {
281             demergeCmdExec('quickpkg ='.join(' =',@unmergePackages));
282             }
283             demergeCmdExec('emerge -C ='.join(' =',@unmergePackages));
284             }
285             if ($#emergePackages>=0) {
286             if ($emergeOpts{'usepkg'}) {
287             $emergeOpts{'usepkg'}='-k';
288             }
289             else {
290             $emergeOpts{'usepkg'}='';
291             }
292            
293             foreach (@emergePackages) {
294             $emergeOpts{'P_USEFLAGS'}=$CACHE_getUseSettingsOfRecordedPackage{$restoreSystemState.'/'.$_};
295             demergeCmdExec("USE='-* ".$emergeOpts{'P_USEFLAGS'}."' emerge ".$emergeOpts{'usepkg'}." =".$_);
296             }
297             }
298             if ($#crossgradePackages>=0) {
299             if ($emergeOpts{'usepkg'}) {
300             $emergeOpts{'usepkg'}='-k';
301             }
302             else {
303             $emergeOpts{'usepkg'}='';
304             }
305            
306             if ($emergeOpts{'usepkg'}) {
307             foreach (@quickpkgPackages) {
308             demergeCmdExec('quickpkg ='.$_);
309             }
310             }
311            
312             foreach (@crossgradePackages) {
313             $emergeOpts{'P_USEFLAGS'}=$CACHE_getUseSettingsOfRecordedPackage{$restoreSystemState.'/'.$_};
314             demergeCmdExec("USE='-* ".$emergeOpts{'P_USEFLAGS'}."' emerge ".$emergeOpts{'usepkg'}." =".$_);
315             }
316             }
317            
318             if (-e $homedir.'/'.$restoreSystemState.'.world') {
319             print $lightgreen.' * '.$reset.'Restoring world file... ';
320             copy($homedir.'/'.$restoreSystemState.'.world','/var/lib/portage/world');
321             print "done\n\n";
322             }
323            
324             print $lightgreen.' * '.$reset."To revert to the previous system-state run 'demerge --restore ".$time."'.\n\n";
325            
326             exit(0);
327             }
328              
329             if ($wipe==1) {
330             # - Cleanup .demerge dir >
331             if (!$do) {
332             if ($pxs->cmdAskUser('Do you really want to remove all recorded system-states?','y,n') eq 'n') {
333             print "\n";
334             print $yellow.' * '.$reset."Quitting.\n\n";
335             exit(1);
336             }
337             }
338             demergeCmdExec('rm -r '.$homedir);
339             mkdir($homedir);
340             print " Done.\n";
341             }
342             elsif ($wipe>1) {
343             # -Just delete the state given >
344             if (!$do) {
345             if ($pxs->cmdAskUser('Do you really want to remove this state?','y,n') eq 'n') {
346             print "\n";
347             print $yellow.' * '.$reset."Quitting.\n\n";
348             exit(1);
349             }
350             }
351             wipe($wipe);
352             print " Done.\n";
353             }
354              
355             if ($wipeOlder) {
356             my $dh = new DirHandle($homedir);
357             my @availableStates=();
358             if (defined $dh) {
359             while (defined(my $this_file = $dh->read)) {
360             if ($this_file=~m/^([0-9]+)\.systemstate$/i) {
361             my $this_timestamp=$1;
362             if ($wipeOlder>$this_timestamp) {
363             push(@availableStates,$this_timestamp);
364             }
365             }
366             }
367             }
368            
369             if ($#availableStates>-1) {
370             print $lightgreen.' * '.$reset."Previous states selected for removal:\n\n";
371             my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
372             foreach (sort @availableStates) {
373             ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($_);
374             print ' ',$_,' (',($year+1900),'-',sprintf('%02s',($mon+1)),'-',sprintf('%02s',$mday),' ',sprintf('%02s',$hour),':',sprintf('%02s',$min),':',sprintf('%02s',$sec),")\n";
375             }
376             }
377             else {
378             $pxs->printColored('YELLOW',"Nothing to wipe. Aborting.\n\n");
379             exit(1);
380             }
381            
382             print "\n";
383             if (!$do) {
384             if($pxs->cmdAskUser('Do you really want to remove these system-states?','y,n') eq 'n') {
385             print "\n";
386             print $yellow.' * '.$reset."Quitting.\n\n";
387             exit(1);
388             }
389             }
390            
391             foreach (@availableStates) {
392             wipe($_);
393             }
394             print " Done.\n";
395             }
396              
397             print "\n";
398             exit(0);
399              
400             # -----------------------------------------------------------------------------
401             # subs >
402             # -----------------------------------------------------------------------------
403              
404             sub wipe {
405 0     0     my $wipe = shift;
406 0 0         if (-e $homedir.'/'.$wipe.'.systemstate') { demergeCmdExec('rm '.$homedir.'/'.$wipe.'.systemstate'); }
  0            
407 0 0         if (-e $homedir.'/'.$wipe.'.version') { demergeCmdExec('rm '.$homedir.'/'.$wipe.'.version'); }
  0            
408 0 0         if (-e $homedir.'/'.$wipe.'.comment') { demergeCmdExec('rm '.$homedir.'/'.$wipe.'.comment'); }
  0            
409 0 0         if (-e $homedir.'/'.$wipe.'.world') { demergeCmdExec('rm '.$homedir.'/'.$wipe.'.world'); }
  0            
410 0 0         if (-e $homedir.'/'.$wipe.'.dvdb') { demergeCmdExec('rm '.$homedir.'/'.$wipe.'.dvdb'); }
  0            
411 0 0         if (-d $homedir.'/'.$wipe) { demergeCmdExec('rm -r '.$homedir.'/'.$wipe); }
  0            
412 0           return 1;
413             }
414              
415             sub demergeReadConfig {
416 0     0     print $lightgreen.' * '.$reset.'Using configuration: '.$configfile."\n";
417 0           $homedir=$pxs->getParamFromFile(path($configfile)->slurp,'datadir','lastseen');
418 0 0         if (lc($pxs->getParamFromFile(path($configfile)->slurp,'usepkg','lastseen')) eq 'yes') {
419 0           $emergeOpts{'usepkg'}='-k';
420 0           print $lightgreen.' * '.$reset."--usepkg enabled in configuration.\n";
421             }
422             }
423              
424             sub postsyncHook {
425 0 0   0     if(! -d $homedir) {
426 0 0         if(mkdir($homedir)) {
427 0           print $red.'demerge'.$reset.': Created '.$homedir."\n";
428             }
429             else {
430 0           print $red.'demerge'.$reset.': Could not create '.$homedir.". Aborting.\n";
431 0           exit(0);
432             }
433             }
434            
435             # - Get latest state >
436 0           my @availableStates=getAvailableStates();
437 0           my $lastSystemState=$availableStates[$#availableStates];
438            
439 0           $comment='Postsync';
440 0           print $red.'demerge'.$reset.': Recording system state...';
441 0           $time=recordSystemState();
442 0           print "\n";
443 0           print $red.'demerge'.$reset.': Timestamp: '.$time."\n";
444            
445             # - Compare states >
446 0 0         if ($lastSystemState) {
447 0 0         if (path($homedir,$lastSystemState.'.systemstate')->slurp eq path($homedir, $time.'.systemstate')->slurp) {
448             # - States are identical - wipe newest >
449 0           print $red.'demerge'.$reset.': State is identical to '.$lastSystemState.'. Wiping '.$time.".\n";
450 0           wipe($time);
451             }
452             }
453            
454 0           return 1;
455             }
456              
457             sub recordSystemState {
458             # - record system state and exit >
459 0     0     $time = time();
460 0           my @rpackages=$pxs->searchInstalledPackage('*');
461            
462 0 0         open(FH,'>'.$homedir.'/'.$time.'.systemstate') or die('Cannot create systemstate file!');
463 0           print FH join("\n",@rpackages)."\n";
464 0           close(FH);
465 0           print '.';
466            
467 0 0         open(FH,'>'.$homedir.'/'.$time.'.version') or die('Cannot create version file!');
468 0           print FH $VERSION;
469 0           close(FH);
470 0           print '.';
471            
472 0 0         if ($comment) {
473 0 0         open(FH,'>'.$homedir.'/'.$time.'.comment') or die('Cannot create comment file!');
474 0           print FH $comment;
475 0           close(FH);
476 0           print '.';
477             }
478            
479 0 0         if (-e '/var/lib/portage/world') { copy('/var/lib/portage/world',$homedir.'/'.$time.'.world'); }
  0            
480 0           print '.';
481            
482 0           my $thisIUSE='';
483 0           my $thisUSE='';
484 0           my @package_IUSE=();
485 0           my @package_USE=();
486 0           my $uses='';
487 0           my $iuses='';
488 0           my $hasuse='';
489 0           my @USEs=();
490            
491 0 0         open(DVDB,'>'.$homedir.'/'.$time.'.dvdb') or die('Cannot create dvdb file!');
492 0           foreach (@rpackages) {
493 0           @package_USE=();
494 0           @package_IUSE=();
495 0           @USEs=();
496 0 0         if (-e '/var/db/pkg/'.$_.'/USE') {
497 0           $uses=path('/var/db/pkg', $_, 'USE')->slurp;
498 0           $uses=~s/\n//g;
499 0           @package_USE=split(/ /,$uses);
500             }
501 0 0         if (-e '/var/db/pkg/'.$_.'/IUSE') {
502 0           $iuses=path('/var/db/pkg',$_,'IUSE')->slurp;
503 0           $iuses=~s/\n//g;
504 0           @package_IUSE=split(/ /,$iuses);
505             }
506            
507 0           foreach $thisIUSE (@package_IUSE) {
508 0 0         next if ($thisIUSE eq '');
509 0           $hasuse = '-';
510 0           foreach $thisUSE (@package_USE) {
511 0 0         if ($thisIUSE eq $thisUSE) {
512 0           $hasuse='';
513 0           last;
514             }
515             }
516 0           push(@USEs,$hasuse.$thisIUSE);
517             }
518 0           print DVDB $_,':USE:',join(' ',uniqifyArray(@USEs)),"\n";
519             }
520 0           close(DVDB);
521 0           print '.';
522              
523 0           return $time;
524             }
525              
526             sub printHeader {
527 0     0     print "\n",$lightgreen,' demerge',$reset,' version ',$VERSION,' ',$blue,'(using PortageXS-',$pxs->{'VERSION'},')',$reset,"\n\n";
528 0           return 1;
529             }
530              
531             sub printUsage {
532 0     0     print " --comment [ comment ] : Add comment to state for your convenience.\n";
533 0           print " --do : Do not ask user to confirm actions - just do it.\n";
534 0           print " --dir [ directory ] : Select directory to store/get demerge data.\n";
535 0           print " -h, --help : Show this help.\n";
536 0           print " -k, --usepkg : Pass -k to emerge so that binary packages\n";
537 0           print " : will be used when available. When enabling this\n";
538 0           print " : option demerge will also create binpkgs of\n";
539 0           print " : packages before removing them.\n";
540 0           print " : (Note: Currently --usepkg is not useflag aware. So\n";
541 0           print " : no matter what useflags were set in the system-state\n";
542 0           print " : portage will install the binpkg as is.)\n";
543 0           print " -C, --nocolor : Turn off colors.\n";
544 0           print " --record : Records which packages are installed\n";
545 0           print " : on this system.\n";
546 0           print " --restore [ timestamp ] : Restores previous recorded system-state of the given\n";
547 0           print " : timestamp.\n";
548 0           print " --restore-previous : Restores previous recorded system-state.\n";
549 0           print " --wipe [ timestamp ] : Remove all/given system-states.\n";
550 0           print " --wipe-older [ timestamp ] : Remove all recorded system-states that are\n";
551 0           print " : older than the given timestamp.\n";
552 0           print "\n";
553            
554 0           exit(0);
555             }
556              
557             sub compareCachedUseSettings {
558 0     0     my $t=shift;
559 0           my $t2=shift;
560 0 0         if (!$CACHE_getUseSettingsOfInstalledPackageF{$t2}) {
561 0           $CACHE_getUseSettingsOfInstalledPackage{$t2}=join(' ',uniqifyArray($pxs->getUseSettingsOfInstalledPackage($t2)));
562 0           $CACHE_getUseSettingsOfInstalledPackageF{$t2}=1;
563             }
564 0 0         if (!$CACHE_getUseSettingsOfRecordedPackageF{$t}) {
565 0           $CACHE_getUseSettingsOfRecordedPackage{$t}=join(' ',uniqifyArray(getUseSettingsOfRecordedPackage($t)));
566 0           $CACHE_getUseSettingsOfRecordedPackageF{$t}=1;
567             }
568 0 0         if ($CACHE_getUseSettingsOfInstalledPackage{$t2} eq $CACHE_getUseSettingsOfRecordedPackage{$t}) {
569 0           return 1;
570             }
571             else {
572 0           return 0;
573             }
574             }
575              
576             sub setupCache {
577             # - Set up cache >
578 0     0     print $lightgreen,' * ',$reset,"Analyzing current state.. ";
579 0           @CACHE_searchInstalledPackage=$pxs->searchInstalledPackage('*');
580 0           my $spinner=PortageXS::UI::Spinner->new();
581 0           foreach (@CACHE_searchInstalledPackage) {
582 0           $CACHE_getUseSettingsOfInstalledPackage{$_}=join(' ',uniqifyArray($pxs->getUseSettingsOfInstalledPackage($_)));
583 0           $CACHE_getUseSettingsOfInstalledPackageF{$_}=1;
584 0           $spinner->spin();
585             }
586 0           $spinner->reset();
587 0           print "done\n\n";
588 0           return 1;
589             }
590              
591             sub printStateDiff {
592 0     0     my $restoreSystemState = shift;
593 0           my $indent = shift;
594            
595 0 0         if (getSystemStateVersion($restoreSystemState) >= $NEEDVERSION) {
596 0           @emergePackages = ();
597 0           @unmergePackages = ();
598 0           @crossgradePackages = ();
599 0           my $this_src='';
600 0           my $this_dest='';
601 0           my $has=0;
602 0           $missingEbuilds=0;
603            
604             # - Check for differences in the two states >
605 0           my @destState = split(/\n/,path($homedir,$restoreSystemState.'.systemstate')->slurp);
606 0           foreach $this_src (@CACHE_searchInstalledPackage) {
607 0           $has=0;
608 0           foreach $this_dest (@destState) {
609 0 0         if ($this_src eq $this_dest) {
610             # - Check for useflag differences >
611 0 0         if (compareCachedUseSettings($restoreSystemState.'/'.$this_src,$this_src)) {
612 0           $has++;
613 0           last;
614             }
615             }
616             }
617 0 0         if (!$has) {
618 0           push(@unmergePackages,$this_src);
619             }
620             }
621            
622 0           foreach $this_dest (@destState) {
623 0           $has=0;
624 0           foreach $this_src (@CACHE_searchInstalledPackage) {
625 0 0         if ($this_src eq $this_dest) {
626             # - Check for useflag differences >
627 0 0         if (compareCachedUseSettings($restoreSystemState.'/'.$this_src,$this_src)) {
628 0           $has++;
629 0           last;
630             }
631             }
632             }
633 0 0         if (!$has) {
634 0           push(@emergePackages,$this_dest);
635             }
636             }
637            
638 0 0         if ($indent) {
639 0 0 0       if ($#emergePackages<0 && $#unmergePackages<0) {
640 0           print " No differences found.\n";
641             }
642             else {
643 0 0         if ($#unmergePackages>-1) {
644 0           foreach (@unmergePackages) {
645 0           print ' -'.$green.$_.$reset;
646 0           demergeOutputUseflags(9,$_,$pxs->formatUseflags(split(/ /,$CACHE_getUseSettingsOfInstalledPackage{$_})));
647             }
648             }
649 0 0         if ($#emergePackages>-1) {
650 0           foreach (@emergePackages) {
651 0           print ' +'.$green.$_.$reset;
652 0           my $ebuildDa=ebuildDa($_);
653 0 0         if ($ebuildDa == 0) { $missingEbuilds++; $ebuildDa='MISSING'; }
  0            
  0            
654 0           print ' [',$ebuildDa,']';
655 0           demergeOutputUseflags(9,$_,$pxs->formatUseflags(split(/ /,getUseSettingsOfRecordedPackage($restoreSystemState.'/'.$_))));
656             }
657             }
658             }
659             }
660             else {
661 0 0 0       if ($#emergePackages<0 && $#unmergePackages<0) {
662 0           $pxs->printColored('YELLOW',"No differences found. Aborting.\n\n");
663 0           exit(1);
664             }
665             else {
666 0 0         if ($#unmergePackages>-1) {
667 0           print $lightgreen.' * '.$reset."Packages that will be uninstalled:\n";
668 0           foreach (@unmergePackages) {
669 0           print ' '.$green.$_.$reset;
670 0           demergeOutputUseflags(3,$_,$pxs->formatUseflags(uniqifyArray($pxs->getUseSettingsOfInstalledPackage($_))));
671             }
672 0           print "\n";
673             }
674 0 0         if ($#emergePackages>-1) {
675 0           print $lightgreen.' * '.$reset."Packages that will be installed:\n";
676 0           foreach (@emergePackages) {
677 0           print ' '.$green.$_.$reset;
678 0           my $ebuildDa=ebuildDa($_);
679 0 0         if ($ebuildDa == 0) { $missingEbuilds++; $ebuildDa='MISSING'; }
  0            
  0            
680 0           print ' [',$ebuildDa,']';
681 0           demergeOutputUseflags(3,$_,$pxs->formatUseflags(split(/ /,getUseSettingsOfRecordedPackage($restoreSystemState.'/'.$_))));
682             }
683 0           print "\n";
684             }
685             }
686            
687             # - check for crossgrades >
688 0           my %tmp_unmergePackages=();
689 0           my %tmp_emergePackages=();
690 0           my %tmp_crossgradePackages=();
691 0           my %tmp_crossgradePackagesEN=();
692 0           my $e='';
693 0           my $u='';
694            
695 0           foreach $u (@unmergePackages) {
696 0           $tmp_unmergePackages{$u}=1;
697 0           foreach $e (@emergePackages) {
698 0           $tmp_emergePackages{$e}=1;
699 0 0         if ($pxs->getEbuildName($u) eq $pxs->getEbuildName($e)) {
700 0           $tmp_unmergePackages{$u}=0;
701 0           $tmp_emergePackages{$e}=0;
702 0           $tmp_crossgradePackages{$e}=1;
703 0           $tmp_crossgradePackagesEN{$pxs->getEbuildName($e)}=1;
704 0           last;
705             }
706             }
707             }
708            
709 0           foreach $e (@emergePackages) {
710 0           $tmp_emergePackages{$e}=1;
711 0           foreach $u (@unmergePackages) {
712 0           $tmp_unmergePackages{$u}=1;
713 0 0         if ($pxs->getEbuildName($u) eq $pxs->getEbuildName($e)) {
714 0           $tmp_unmergePackages{$u}=0;
715 0           $tmp_emergePackages{$e}=0;
716 0           $tmp_crossgradePackages{$e}=1;
717 0           $tmp_crossgradePackagesEN{$pxs->getEbuildName($e)}=1;
718 0           last;
719             }
720             }
721             }
722            
723 0           @unmergePackages=();
724 0           @emergePackages=();
725 0           @crossgradePackages=();
726 0           foreach (keys %tmp_unmergePackages) {
727 0 0         if ($tmp_unmergePackages{$_}) {
728 0 0         if (!$tmp_crossgradePackagesEN{$pxs->getEbuildName($_)}) {
729 0           push(@unmergePackages,$_);
730             }
731             }
732             else {
733 0           push(@quickpkgPackages,$_);
734             }
735             }
736 0           foreach (keys %tmp_emergePackages) {
737 0 0 0       if ($tmp_emergePackages{$_} && !$tmp_crossgradePackages{$_}) {
738 0 0         if (!$tmp_crossgradePackagesEN{$pxs->getEbuildName($_)}) {
739 0           push(@emergePackages,$_);
740             }
741             }
742             }
743 0           foreach (keys %tmp_crossgradePackages) {
744 0 0         if ($tmp_crossgradePackages{$_}) {
745 0           push(@crossgradePackages,$_);
746             }
747             }
748             }
749            
750 0           return 1;
751             }
752             else {
753 0           return 0;
754             }
755             }
756              
757             # Description:
758             # Returns useflag settings of the given recorded package.
759             # @useflags = $pxs->getUseSettingsOfRecordedPackage("timestamp/dev-perl/perl-5.8.8-r3");
760             sub getUseSettingsOfRecordedPackage {
761 0     0     my $package = shift;
762 0           my $category = '';
763 0           my $time = 0;
764 0           my $tmp_filecontents = '';
765            
766 0           ($time,$category,$package) = split(/\//,$package);
767 0           $package=$category.'/'.$package;
768            
769 0 0         if (!$CACHE_getUseSettingsOfRecordedPackageF{$time.'/'.$package}) {
770 0           my @dvdb=split(/\n/,path($homedir,$time.'.dvdb')->slurp);
771 0           foreach my $this_line (@dvdb) {
772 0           my @elements=split(/:/,$this_line);
773 0           $CACHE_getUseSettingsOfRecordedPackageF{$time.'/'.$elements[0]}=1;
774 0 0         if (!$elements[2]) {
775 0           $CACHE_getUseSettingsOfRecordedPackage{$time.'/'.$elements[0]}='';
776             }
777             else {
778 0           $elements[2]=~s/\n//g;
779 0           $CACHE_getUseSettingsOfRecordedPackage{$time.'/'.$elements[0]}=$elements[2];
780             }
781             }
782             }
783            
784 0           return $CACHE_getUseSettingsOfRecordedPackage{$time.'/'.$package};
785             }
786              
787             sub getSystemStateVersion {
788 0     0     my $timestamp = shift;
789 0           my $version = 0;
790            
791 0 0         if (-e $homedir.'/'.$timestamp.'.version') {
792 0 0         open(FH,'<'.$homedir.'/'.$timestamp.'.version') or die('Cannot open version file!');
793 0           while() {
794 0           $version .= $_;
795             }
796 0           close(FH);
797             }
798            
799 0           return $version;
800             }
801              
802             # Description:
803             # Returns the given array without duplicates.
804             # @array = uniqifyArray(@array);
805             sub uniqifyArray {
806 0     0     my %seen = ();
807 0           return grep { ! $seen{$_} ++ } @_;
  0            
808             }
809              
810             # Description:
811             # Reformat useflags so that it matches the terminal width.
812             sub demergeOutputUseflags {
813 0     0     my $offset = shift;
814 0           my $thisPackage = shift;
815 0           my @useflags = @_;
816 0           my $cx = 0;
817 0           my $chars = 0;
818 0           my $thisUse = '';
819 0           my $thisUseClean= '';
820            
821 0           $offset+=5;
822            
823 0 0         if (@useflags) {
824 0           print ' USE="';
825 0           $chars=$offset+length($thisPackage);
826 0           foreach $thisUse (@useflags) {
827 0           $thisUse.=' ';
828 0           $thisUseClean=$thisUse;
829 0           $thisUseClean=~s/[\000-\037]\[(\d|;)+m//g;
830 0 0         if ($chars+length($thisUseClean)>=$terminalWidth) {
831 0           $chars=$offset+length($thisPackage);
832 0           printf("\n %".$chars.'s',' ');
833             }
834 0           print $thisUse;
835 0           $chars+=length($thisUseClean);
836             }
837 0           print "\b \b\"\n";
838             }
839             else {
840 0           print " USE=\"\"\n";
841             }
842 0           return 1;
843             }
844              
845             # Description:
846             # Execute given command and quit on error/user termination.
847             # demergeCmdExec(@command);
848             sub demergeCmdExec {
849 0     0     my $cmd = shift;
850 0           my $rc = 0;
851            
852 0 0         if (!$DEBUG) {
853 0           $rc=system($cmd);
854            
855 0 0         if ($rc == 0) {
856 0           return 1;
857             }
858             else {
859 0           print $yellow.' * '.$reset."Quitting.\n\n";
860 0           exit(1);
861             }
862             }
863             else {
864 0           print 'DEBUG: '.$cmd."\n";
865 0           return 1;
866             }
867             }
868              
869             # Description:
870             # Returns an array containing all recorded states.
871             # @availableStates=getAvailableStates();
872             sub getAvailableStates {
873 0     0     my $dh = new DirHandle($homedir);
874 0           my @availableStates=();
875 0 0         if (defined $dh) {
876 0           while (defined(my $this_file = $dh->read)) {
877 0 0         if ($this_file=~m/^([0-9]+)\.systemstate$/i) {
878 0           push(@availableStates,$1)
879             }
880             }
881             }
882 0           return sort(@availableStates);
883             }
884              
885             # Description:
886             # Check if ebuild is available and return number of repo
887             sub ebuildDa {
888 0     0     my $ebuild = shift;
889 0           my $number = ($#CACHE_repos)+1;
890            
891 0           foreach (reverse(@CACHE_repos)) {
892 0           my $pn=(split(/\//,$pxs->getEbuildName($ebuild)))[1];
893 0 0         if ($pn) {
894 0 0         if (-e $_.'/'.$pxs->getEbuildName($ebuild).'/'.$pn.'-'.$pxs->getEbuildVersion($ebuild).'.ebuild') {
895 0           last;
896             }
897 0           $number--;
898             }
899             }
900            
901 0           return $number;
902             }
903              
904             # - Here comes the POD >
905              
906             =head1 NAME
907              
908             demerge - Revert to previous installation states.
909              
910             =head1 VERSION
911              
912             This document refers to version 0.048 of demerge
913              
914             =head1 SYNOPSIS
915              
916             demerge [option]...
917              
918             =head1 DESCRIPTION
919              
920             Using demerge you can easily record and restore your system state.
921              
922             =head1 ARGUMENTS
923              
924             --comment [ comment ] Add comment to state for your convenience.
925              
926             --do Do not ask user to confirm actions - just do it.
927              
928             --dir [ directory ] Select directory to store/get demerge data.
929              
930             -h, --help Show options.
931              
932             -k, --usepkg Pass -k to emerge so that binary packages
933             will be used when available. When enabling this option
934             demerge will also create binpkgs of packages before
935             removing them.
936             (Note: Currently --usepkg is not useflag aware. So no matter what
937             useflags were set in the system-state portage will install
938             the binpkg as is.)
939              
940             -C, --nocolor Turn off colors.
941              
942             --record Records which packages are installed on this system.
943              
944             --restore [ timestamp ] Restores previous recorded system-state of the given timestamp.
945              
946             --restore-previous Restores previous recorded system-state.
947              
948             --wipe [ timestamp ] Remove all/given system-states.
949              
950             --wipe-older [ timestamp ] Remove all recorded system-states that are
951             older than the given timestamp.
952              
953             =head1 CONFIGURATION FILE
954              
955             Rather than calling demerge with the --dir, --usepkg and --nocolor parameter you can
956             also use the /etc/demerge.conf configuration file to change the location where demerge stores
957             and expects its data.
958              
959             To make demerge use the directory /var/lib/demerge instead of /root/.demerge:
960              
961             # echo 'datadir=/var/lib/demerge' >> /etc/demerge.conf
962              
963             To make demerge use the --usepgk option by default:
964              
965             # echo 'usepkg=yes' >> /etc/demerge.conf
966              
967             To disable colors:
968              
969             # echo 'nocolor=true' >> /etc/demerge.conf
970              
971             =head1 AUTHOR
972              
973             Christian Hartmann
974              
975             =head1 CONTRIBUTORS
976              
977             Many thanks go out to all the people listed below:
978              
979             Wernfried Haas
980              
981             Tobias Scherbaum
982              
983             Kalin Kozhuharov
984              
985             Michael Cummings
986              
987             Raul Porcel
988              
989             pille
990              
991             =head1 LICENSE
992              
993             demerge - Revert to previous installation states.
994             Copyright (C) 2007 Christian Hartmann
995              
996             This program is free software; you can redistribute it and/or modify
997             it under the terms of the GNU General Public License as published by
998             the Free Software Foundation; either version 2 of the License, or
999             (at your option) any later version.
1000              
1001             This program is distributed in the hope that it will be useful,
1002             but WITHOUT ANY WARRANTY; without even the implied warranty of
1003             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1004             GNU General Public License for more details.
1005              
1006             You should have received a copy of the GNU General Public License
1007             along with this program; if not, write to the Free Software
1008             Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
1009              
1010             =cut