#!/usr/bin/perl # $Id$ #- Copyright (C) 2006 Mandriva SA use strict; BEGIN { #- clean environment $ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin"; delete @ENV{qw(ENV BASH_ENV IFS CDPATH)}; } use urpm::args; use urpm::msg; use URPM; use urpm; our $MACROS = '/etc/rpm/macros.d/urpmi.recover.macros'; our $listdate; our $do_checkpoint; our $noclean; our $rollback; our $disable; sub usage () { print N("urpmi.recover version %s Copyright (C) 2006 Mandriva. This is free software and may be redistributed under the terms of the GNU GPL. usage: ", $urpm::VERSION) . N(" --help - print this help message. ") . N(" --checkpoint - set repackaging start now ") . N(" --noclean - don't clean repackage directory on checkpoint ") . N(" --urpmi-root - use another root for urpmi db & rpm installation. ") . N(" --list - list transactions since provided date/duration argument ") . N(" --list-all - list all transactions in rpmdb (long) ") . N(" --list-safe - list transactions since checkpoint ") . N(" --rollback - rollback until specified date, or rollback the specified number of transactions ") . N(" --disable - turn off repackaging "); exit(1); } sub fmt_tid { my ($tid) = @_; require POSIX; POSIX->import('strftime'); strftime("%F %T", localtime($tid)); } sub date_to_tid { my ($date) = @_; require Date::Manip; Date::Manip->import; my $d = ParseDate($date) or die N("Invalid date or duration [%s]\n", $date); UnixDate($d, '%s'); } #- clean up repackage directory sub clean_repackage_dir { my ($repackage_dir) = @_; if (!$repackage_dir || $repackage_dir eq '/') { die N("Repackage directory not defined\n"); } -d $repackage_dir or die N("Can't write to repackage directory [%s]\n", $repackage_dir); unless ($noclean) { print N("Cleaning up repackage directory [%s]...\n", $repackage_dir); my $nb = unlink grep { ! -d $_ } glob("$repackage_dir/*"); print P("%d file removed\n", "%d files removed\n", $nb, $nb); } } #- option parsing @ARGV or usage(); my $command_line = "@ARGV"; #- for logging urpm::args::parse_cmdline() or exit(1); @ARGV and die N("Spurious command-line arguments [%s]\n", "@ARGV"); $do_checkpoint && $rollback and die N("You can't specify --checkpoint and --rollback at the same time\n"); $do_checkpoint && $listdate and die N("You can't specify --checkpoint and --list at the same time\n"); $rollback && $listdate and die N("You can't specify --rollback and --list at the same time\n"); $disable && ($listdate || $rollback || $do_checkpoint) and die N("You can't specify --disable along with another option"); #- --list <date> and --list-all if ($listdate) { my $listtime = -1; if ($listdate eq 'checkpoint') { URPM::read_config_files(); $listtime = URPM::expand("%_unsafe_rollbacks"); } elsif ($listdate ne -1) { #- convert to timestamp $listtime = date_to_tid($listdate); } my %tids; my $db = URPM::DB::open() or die "Can't open RPM DB\n"; $db->traverse(sub { my ($p) = @_; my $tid = $p->installtid; return if $tid < $listtime; exists $tids{$tid} or $tids{$tid} = []; push @{ $tids{$tid} }, scalar($p->fullname); }); unless (scalar keys %tids) { die N("No transaction found since %s\n", $listdate); } print "Date rpms\n"; print "------------------- -------------------\n"; foreach my $tid (sort { $a <=> $b } keys %tids) { my @p = @{$tids{$tid}}; print fmt_tid($tid), " ", shift(@p), "\n"; while (@p) { print " " x 20, shift(@p), "\n"; } } exit(0); } #- check we're running as root $< == 0 or die N("You must be superuser to do this"); #- --checkpoint if ($do_checkpoint) { URPM::read_config_files(); my $repackage_dir = URPM::expand("%_repackage_dir"); my $unsafe_rollbacks = time(); clean_repackage_dir($repackage_dir); #- write rpm config file print N("Writing rpm macros file [%s]...\n", $MACROS); open my $fh, '>', $MACROS or die "Can't open $MACROS for writing: $!\n"; print $fh <<MACROS; # Generated by urpmi.recover # Turn repackaging on %_repackage_all_erasures 1 # Put repackaged rpms here (lots of space necessary) %_repackage_dir $repackage_dir # Don't erase on rollback before this date (seconds since epoch) %_unsafe_rollbacks $unsafe_rollbacks # Automate transaction rollbacks on upgrade failure %_rollback_transaction_on_failure 0 MACROS close $fh; sys_log("checkpoint defined"); exit(0); } #- --rollback if ($rollback) { sys_log("called with: $command_line"); my $tid; if ($rollback !~ /\D/) { #- $rollback contains a number of transactions to roll back #- get a date from there my %tids; my $db = URPM::DB::open() or die "Can't open RPM DB\n"; $db->traverse(sub { ++$tids{ $_[0]->installtid } }); my @tids = sort { $b <=> $a } keys %tids; $tid = $tids[$rollback - 1]; } else { #- $rollback contains a date, convert it to tid $tid = date_to_tid($rollback); } $tid or die N("No rollback date found\n"); my $rollbackdate = fmt_tid($tid); print N("Rollback until %s...\n", $rollbackdate), "\n"; exec '/bin/rpm', '-Uvh', '--rollback', $rollbackdate; } #- --disable if ($disable) { print N("Disabling repackaging\n"); unless ($noclean) { URPM::read_config_files(); my $repackage_dir = URPM::expand("%_repackage_dir"); clean_repackage_dir($repackage_dir); } open my $fh, '<', $MACROS or die "Can't open $MACROS for reading: $!\n"; my $macrosfile = join '', <$fh>; close $fh; #- turn off repackaging macro $macrosfile =~ s/_repackage_all_erasures\s+\w+/_repackage_all_erasures 0/g; print N("Writing rpm macros file [%s]...\n", $MACROS); open $fh, '>', $MACROS or die "Can't open $MACROS for writing: $!\n"; print $fh $macrosfile; close $fh; sys_log("repackaging disabled"); exit(0); }