blob: ac794fa18f854a94f9be73ecd3e7a39e238d3806 [file] [log] [blame]
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import glob
import logging
import os
import shelve
import shutil
import factory_common # pylint: disable=W0611
from cros.factory.test import utils
from cros.factory.utils.process_utils import Spawn
BACKUP_DIRECTORY = 'backup'
class RecoveryException(Exception):
pass
def IsShelfValid(shelf):
"""Checks whether a shelf can be loaded and unshelved.
This is done in a separate process, since some databases (like gdbm)
may throw fatal errors if the shelf is not valid.
Returns:
True if valid, False if not valid.
"""
process = Spawn(['python', '-c',
'import factory_common, shelve, sys; '
'shelve.open(sys.argv[1], "r").items(); '
r'print "\nSHELF OK"',
os.path.realpath(shelf)],
cwd=os.path.dirname(__file__), call=True,
log=True, read_stdout=True, read_stderr=True)
if process.returncode == 0 and process.stdout_data.endswith('SHELF OK\n'):
return True
logging.warn('Unable to validate shelf %r: '
'returncode=%r, stdout=%r, stderr=%r',
shelf, process.returncode,
process.stdout_data, process.stderr_data)
return False
def FindShelfFiles(shelf):
"""Returns all files in shelf.
We assume this to be files that have the same name as the shelf, or
the shelf plus dot and a suffix."""
shelf_files = glob.glob(shelf + '.*')
if os.path.exists(shelf):
shelf_files.append(shelf)
return shelf_files
def BackupShelfIfValid(shelf):
"""Validates a shelf, and backs it up if it is valid.
Files that have the same name as the shelf, or the shelf plus dot and a
suffix, are backed up.
Returns:
True if the shelf was valid and is backed up.
"""
shelf_files = FindShelfFiles(shelf)
if not shelf_files:
# Nothing to back up.
logging.info('Shelf %s not present; not backing up', shelf)
return False
if not IsShelfValid(shelf):
logging.warn('Shelf %s is invalid; not backing up', shelf)
return False
backup_dir = os.path.join(os.path.dirname(shelf), BACKUP_DIRECTORY)
utils.TryMakeDirs(backup_dir)
logging.info('Backing up %s to %s', shelf_files, backup_dir)
for f in shelf_files:
shutil.copyfile(f, os.path.join(backup_dir, os.path.basename(f)))
return True
def RecoverShelf(shelf):
"""Recovers a shelf from its backup.
Raises:
RecoveryException if unable to recover and validate the shelf.
"""
backup_shelf = os.path.join(os.path.dirname(shelf),
BACKUP_DIRECTORY,
os.path.basename(shelf))
# Validate the backup
if not IsShelfValid(backup_shelf):
raise IOError('Backup shelf %s is invalid or missing' % backup_shelf)
shelf_files = FindShelfFiles(backup_shelf)
assert shelf_files
for f in shelf_files:
dest_path = os.path.join(os.path.dirname(shelf),
os.path.basename(f))
logging.info('Recovering %s to %s', f, dest_path)
shutil.copyfile(f, dest_path)
def OpenShelfOrBackup(shelf, flag='c', protocol=None, writeback=False):
"""Opens a shelf, or its backup if invalid.
If the shelf is valid, it is backed up.
Args:
shelf: Path to the shelf.
Other arguments: See shelve.open.
"""
if not FindShelfFiles(shelf) and flag in ['c', 'n']:
# No worries; just create a new shelf.
pass
elif BackupShelfIfValid(shelf):
# The shelf is valid.
pass
else:
# Attempt to recover the shelf, throwing an exception if we can't.
RecoverShelf(shelf)
# At this point the shelf is guaranteed to be valid.
return shelve.open(shelf, flag, protocol, writeback)