Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
195 lines (152 sloc) 5.74 KB
#
# Copyright (c) 2010-2013 Liraz Siri <liraz@turnkeylinux.org>
#
# This file is part of TKLBAM (TurnKey GNU/Linux BAckup and Migration).
#
# TKLBAM is open source software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3 of
# the License, or (at your option) any later version.
#
import os
import sys
from os.path import *
import stat
import shutil
from datetime import datetime
from paths import Paths as _Paths
import mysql
import pgsql
from changes import Changes
from dirindex import DirIndex
from pkgman import Packages
import utils
import traceback
class Error(Exception):
pass
class Rollback:
Error = Error
PATH = "/var/backups/tklbam-rollback"
class Paths(_Paths):
files = [ 'etc', 'etc/mysql',
'fsdelta', 'dirindex', 'originals',
'newpkgs', 'myfs', 'pgfs' ]
@classmethod
def create(cls, path=PATH):
if exists(path):
shutil.rmtree(path)
os.makedirs(path)
os.chmod(path, 0700)
self = cls(path)
os.mkdir(self.paths.etc)
os.mkdir(self.paths.originals)
return self
def __init__(self, path=PATH):
"""deletes path if it exists and creates it if it doesn't"""
if not exists(path):
raise Error("No such directory " + `path`)
self.paths = self.Paths(path)
self.timestamp = datetime.fromtimestamp(os.stat(path).st_ctime)
@staticmethod
def _move(source, dest):
if not lexists(source):
raise Error("no such file or directory " + `source`)
if not exists(dirname(dest)):
os.makedirs(dirname(dest))
utils.remove_any(dest)
utils.move(source, dest)
def _move_to_originals(self, source):
"""Move source into originals"""
dest = join(self.paths.originals, source.strip('/'))
self._move(source, dest)
def _move_from_originals(self, dest):
"""Move path from originals to dest"""
source = join(self.paths.originals, dest.strip('/'))
self._move(source, dest)
def rollback_files(self):
if not exists(self.paths.fsdelta):
return
changes = Changes.fromfile(self.paths.fsdelta)
dirindex = DirIndex(self.paths.dirindex)
exceptions = 0
for change in changes:
try:
if change.path not in dirindex:
utils.remove_any(change.path)
continue
if change.OP in ('o', 'd'):
try:
self._move_from_originals(change.path)
except self.Error:
continue
dirindex_rec = dirindex[change.path]
local_rec = DirIndex.Record.frompath(change.path)
if dirindex_rec.uid != local_rec.uid or \
dirindex_rec.gid != local_rec.gid:
os.lchown(change.path, dirindex_rec.uid, dirindex_rec.gid)
if dirindex_rec.mod != local_rec.mod:
mod = stat.S_IMODE(dirindex_rec.mod)
os.chmod(change.path, mod)
except:
exceptions += 1
# fault-tolerance: warn and continue, don't die
traceback.print_exc(file=sys.stderr)
for fname in ('passwd', 'group'):
shutil.copy(join(self.paths.etc, fname), "/etc")
if exceptions:
raise Error("caught %d exceptions during rollback_files" % exceptions)
def rollback_new_packages(self):
if not exists(self.paths.newpkgs):
return
rollback_packages = Packages.fromfile(self.paths.newpkgs)
current_packages = Packages()
purge_packages = current_packages & rollback_packages
if purge_packages:
os.system("DEBIAN_FRONTEND=noninteractive dpkg --purge " + " ".join(purge_packages))
def rollback_database(self):
if exists(self.paths.myfs):
mysql.restore(self.paths.myfs, self.paths.etc.mysql,
add_drop_database=True)
if exists(self.paths.pgfs):
pgsql.restore(self.paths.pgfs)
def rollback(self):
exceptions = 0
for method in (self.rollback_database, self.rollback_files, self.rollback_new_packages):
try:
method()
except:
exceptions += 1
print >> sys.stderr, "error: %s raised an exception:" % method.__name__
traceback.print_exc(file=sys.stderr)
shutil.rmtree(self.paths)
if exceptions:
raise Error("caught %d exceptions during rollback" % exceptions)
def save_files(self, changes, overlay_path):
for fname in ("passwd", "group"):
shutil.copy(join("/etc", fname), self.paths.etc)
changes.tofile(self.paths.fsdelta)
di = DirIndex()
for change in changes:
if lexists(change.path):
di.add_path(change.path)
if change.OP in ('o', 'd'):
if change.OP == 'o' and not lexists(overlay_path + change.path):
continue
self._move_to_originals(change.path)
di.save(self.paths.dirindex)
def save_new_packages(self, packages):
packages = list(packages)
packages.sort()
fh = file(self.paths.newpkgs, "w")
for package in packages:
print >> fh, package
fh.close()
def save_database(self):
try:
mysql.backup(self.paths.myfs, self.paths.etc.mysql)
except mysql.Error:
pass
try:
pgsql.backup(self.paths.pgfs)
except pgsql.Error:
pass