Beispiel #1
0
def main(args=None):

    if args is None:
        args = sys.argv[1:]

    options = parse_args(args)
    setup_logging(options)

    # Check for cached metadata
    cachepath = get_backend_cachedir(options.storage_url, options.cachedir)
    if not os.path.exists(cachepath + '.params'):
        raise QuietError("No local metadata found.")

    with open(cachepath + '.params', 'rb') as fh:
        param = pickle.load(fh)

    # Check revision
    if param['revision'] < CURRENT_FS_REV:
        raise QuietError('File system revision too old.')
    elif param['revision'] > CURRENT_FS_REV:
        raise QuietError('File system revision too new.')

    if os.path.exists(DBNAME):
        raise QuietError('%s exists, aborting.' % DBNAME)

    log.info('Copying database...')
    dst = tempfile.NamedTemporaryFile()
    with open(cachepath + '.db', 'rb') as src:
        shutil.copyfileobj(src, dst)
    dst.flush()
    db = Connection(dst.name)

    log.info('Scrambling...')
    md5 = lambda x: hashlib.md5(x).hexdigest()
    for (id_, name) in db.query('SELECT id, name FROM names'):
        db.execute('UPDATE names SET name=? WHERE id=?',
                   (md5(name), id_))

    for (id_, name) in db.query('SELECT inode, target FROM symlink_targets'):
        db.execute('UPDATE symlink_targets SET target=? WHERE inode=?',
                   (md5(name), id_))

    for (id_, name) in db.query('SELECT rowid, value FROM ext_attributes'):
        db.execute('UPDATE ext_attributes SET value=? WHERE rowid=?',
                   (md5(name), id_))

    log.info('Saving...')
    with open(DBNAME, 'wb+') as fh:
        dump_metadata(db, fh)
Beispiel #2
0
def main(args=None):

    if args is None:
        args = sys.argv[1:]

    options = parse_args(args)
    setup_logging(options)

    # Check for cached metadata
    cachepath = options.cachepath
    if not os.path.exists(cachepath + '.params'):
        raise QuietError("No local metadata found.")

    param = load_params(cachepath)

    # Check revision
    if param['revision'] < CURRENT_FS_REV:
        raise QuietError('File system revision too old.')
    elif param['revision'] > CURRENT_FS_REV:
        raise QuietError('File system revision too new.')

    if os.path.exists(DBNAME):
        raise QuietError('%s exists, aborting.' % DBNAME)

    log.info('Copying database...')
    dst = tempfile.NamedTemporaryFile()
    with open(cachepath + '.db', 'rb') as src:
        shutil.copyfileobj(src, dst)
    dst.flush()
    db = Connection(dst.name)

    log.info('Scrambling...')
    md5 = lambda x: hashlib.md5(x).hexdigest()
    for (id_, name) in db.query('SELECT id, name FROM names'):
        db.execute('UPDATE names SET name=? WHERE id=?',
                   (md5(name), id_))

    for (id_, name) in db.query('SELECT inode, target FROM symlink_targets'):
        db.execute('UPDATE symlink_targets SET target=? WHERE inode=?',
                   (md5(name), id_))

    for (id_, name) in db.query('SELECT rowid, value FROM ext_attributes'):
        db.execute('UPDATE ext_attributes SET value=? WHERE rowid=?',
                   (md5(name), id_))

    log.info('Saving...')
    with open(DBNAME, 'wb+') as fh:
        dump_metadata(db, fh)
Beispiel #3
0
class DumpTests(unittest.TestCase):
    def setUp(self):
        self.tmpfh1 = tempfile.NamedTemporaryFile()
        self.tmpfh2 = tempfile.NamedTemporaryFile()
        self.src = Connection(self.tmpfh1.name)
        self.dst = Connection(self.tmpfh2.name)
        self.fh = tempfile.TemporaryFile()

        # Disable exclusive locking for all tests
        self.src.execute('PRAGMA locking_mode = NORMAL')
        self.dst.execute('PRAGMA locking_mode = NORMAL')

        self.create_table(self.src)
        self.create_table(self.dst)

    def tearDown(self):
        self.src.close()
        self.dst.close()
        self.tmpfh1.close()
        self.tmpfh2.close()
        self.fh.close()

    def test_transactions(self):
        self.fill_vals(self.src)
        dumpspec = (('id', deltadump.INTEGER, 0),)
        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        self.fh.seek(0)
        self.dst.execute('PRAGMA journal_mode = WAL')

        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh, trx_rows=10)
        self.compare_tables(self.src, self.dst)

    def test_1_vals_1(self):
        self.fill_vals(self.src)
        dumpspec = (('id', deltadump.INTEGER, 0),)
        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_1_vals_2(self):
        self.fill_vals(self.src)
        dumpspec = (('id', deltadump.INTEGER, 1),)
        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_1_vals_3(self):
        self.fill_vals(self.src)
        dumpspec = (('id', deltadump.INTEGER, -1),)
        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_2_buf_auto(self):
        self.fill_vals(self.src)
        self.fill_buf(self.src)
        dumpspec = (('id', deltadump.INTEGER),
                    ('buf', deltadump.BLOB))
        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_2_buf_fixed(self):
        BUFLEN = 32
        self.fill_vals(self.src)
        self.fill_buf(self.src, BUFLEN)
        dumpspec = (('id', deltadump.INTEGER),
                    ('buf', deltadump.BLOB, BUFLEN))
        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_3_deltas_1(self):
        self.fill_deltas(self.src)
        dumpspec = (('id', deltadump.INTEGER, 0),)
        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_3_deltas_2(self):
        self.fill_deltas(self.src)
        dumpspec = (('id', deltadump.INTEGER, 1),)
        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_3_deltas_3(self):
        self.fill_deltas(self.src)
        dumpspec = (('id', deltadump.INTEGER, -1),)
        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_4_time(self):
        self.fill_vals(self.src)

        t1 = 0.5 * time.time()
        t2 = 2 * time.time()
        for (id_,) in self.src.query('SELECT id FROM test'):
            val = random.uniform(t1, t2)
            self.src.execute('UPDATE test SET buf=? WHERE id=?', (val, id_))

        dumpspec = (('id', deltadump.INTEGER),
                    ('buf', deltadump.TIME))

        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh)

        self.compare_tables(self.src, self.dst)


    def test_5_multi(self):
        self.fill_vals(self.src)
        dumpspec = (('id', deltadump.INTEGER, 0),)
        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        deltadump.dump_table(table='test', order='id', columns=dumpspec,
                             db=self.src, fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh)
        self.dst.execute('DELETE FROM test')
        deltadump.load_table(table='test', columns=dumpspec, db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)


    def compare_tables(self, db1, db2):
        i1 = db1.query('SELECT id, buf FROM test ORDER BY id')
        i2 = db2.query('SELECT id, buf FROM test ORDER BY id')

        for (id1, buf1) in i1:
            (id2, buf2) = next(i2)

            self.assertEqual(id1, id2)
            if isinstance(buf1, float):
                self.assertAlmostEqual(buf1, buf2, places=9)
            else:
                self.assertEqual(buf1, buf2)

        self.assertRaises(StopIteration, i2.__next__)

    def fill_buf(self, db, len_=None):
        with open('/dev/urandom', 'rb') as rfh:
            first = True
            for (id_,) in db.query('SELECT id FROM test'):
                if len_ is None and first:
                    val = b'' # We always want to check this case
                    first = False
                elif len_ is None:
                    val = rfh.read(random.randint(0, 140))
                else:
                    val = rfh.read(len_)

                db.execute('UPDATE test SET buf=? WHERE id=?', (val, id_))

    def fill_vals(self, db):
        vals = []
        for exp in [7, 8, 9, 15, 16, 17, 31, 32, 33, 62]:
            vals += list(range(2 ** exp - 5, 2 ** exp + 6))
        vals += list(range(2 ** 63 - 5, 2 ** 63))
        vals += [ -v for v  in vals ]
        vals.append(-(2 ** 63))

        for val in vals:
            db.execute('INSERT INTO test (id) VALUES(?)', (val,))

    def fill_deltas(self, db):
        deltas = []
        for exp in [7, 8, 9, 15, 16, 17, 31, 32, 33]:
            deltas += list(range(2 ** exp - 5, 2 ** exp + 6))
        deltas += [ -v for v  in deltas ]

        last = 0
        for delta in deltas:
            val = last + delta
            last = val
            db.execute('INSERT INTO test (id) VALUES(?)', (val,))

    def create_table(self, db):
        db.execute('''CREATE TABLE test (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            buf BLOB)''')
Beispiel #4
0
class DumpTests(unittest.TestCase):
    def setUp(self):
        self.tmpfh1 = tempfile.NamedTemporaryFile()
        self.tmpfh2 = tempfile.NamedTemporaryFile()
        self.src = Connection(self.tmpfh1.name)
        self.dst = Connection(self.tmpfh2.name)
        self.fh = tempfile.TemporaryFile()

        # Disable exclusive locking for all tests
        self.src.execute('PRAGMA locking_mode = NORMAL')
        self.dst.execute('PRAGMA locking_mode = NORMAL')

        self.create_table(self.src)
        self.create_table(self.dst)

    def tearDown(self):
        self.src.close()
        self.dst.close()
        self.tmpfh1.close()
        self.tmpfh2.close()
        self.fh.close()

    def test_transactions(self):
        self.fill_vals(self.src)
        dumpspec = (('id', deltadump.INTEGER, 0), )
        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        self.fh.seek(0)
        self.dst.execute('PRAGMA journal_mode = WAL')

        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh,
                             trx_rows=10)
        self.compare_tables(self.src, self.dst)

    def test_1_vals_1(self):
        self.fill_vals(self.src)
        dumpspec = (('id', deltadump.INTEGER, 0), )
        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_1_vals_2(self):
        self.fill_vals(self.src)
        dumpspec = (('id', deltadump.INTEGER, 1), )
        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_1_vals_3(self):
        self.fill_vals(self.src)
        dumpspec = (('id', deltadump.INTEGER, -1), )
        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_2_buf_auto(self):
        self.fill_vals(self.src)
        self.fill_buf(self.src)
        dumpspec = (('id', deltadump.INTEGER), ('buf', deltadump.BLOB))
        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_2_buf_fixed(self):
        BUFLEN = 32
        self.fill_vals(self.src)
        self.fill_buf(self.src, BUFLEN)
        dumpspec = (('id', deltadump.INTEGER), ('buf', deltadump.BLOB, BUFLEN))
        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_3_deltas_1(self):
        self.fill_deltas(self.src)
        dumpspec = (('id', deltadump.INTEGER, 0), )
        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_3_deltas_2(self):
        self.fill_deltas(self.src)
        dumpspec = (('id', deltadump.INTEGER, 1), )
        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_3_deltas_3(self):
        self.fill_deltas(self.src)
        dumpspec = (('id', deltadump.INTEGER, -1), )
        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def test_4_time(self):
        self.fill_vals(self.src)

        t1 = 0.5 * time.time()
        t2 = 2 * time.time()
        for (id_, ) in self.src.query('SELECT id FROM test'):
            val = random.uniform(t1, t2)
            self.src.execute('UPDATE test SET buf=? WHERE id=?', (val, id_))

        dumpspec = (('id', deltadump.INTEGER), ('buf', deltadump.TIME))

        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh)

        self.compare_tables(self.src, self.dst)

    def test_5_multi(self):
        self.fill_vals(self.src)
        dumpspec = (('id', deltadump.INTEGER, 0), )
        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        deltadump.dump_table(table='test',
                             order='id',
                             columns=dumpspec,
                             db=self.src,
                             fh=self.fh)
        self.fh.seek(0)
        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh)
        self.dst.execute('DELETE FROM test')
        deltadump.load_table(table='test',
                             columns=dumpspec,
                             db=self.dst,
                             fh=self.fh)
        self.compare_tables(self.src, self.dst)

    def compare_tables(self, db1, db2):
        i1 = db1.query('SELECT id, buf FROM test ORDER BY id')
        i2 = db2.query('SELECT id, buf FROM test ORDER BY id')

        for (id1, buf1) in i1:
            (id2, buf2) = next(i2)

            self.assertEqual(id1, id2)
            if isinstance(buf1, float):
                self.assertAlmostEqual(buf1, buf2, places=9)
            else:
                self.assertEqual(buf1, buf2)

        self.assertRaises(StopIteration, i2.__next__)

    def fill_buf(self, db, len_=None):
        with open('/dev/urandom', 'rb') as rfh:
            first = True
            for (id_, ) in db.query('SELECT id FROM test'):
                if len_ is None and first:
                    val = b''  # We always want to check this case
                    first = False
                elif len_ is None:
                    val = rfh.read(random.randint(0, 140))
                else:
                    val = rfh.read(len_)

                db.execute('UPDATE test SET buf=? WHERE id=?', (val, id_))

    def fill_vals(self, db):
        vals = []
        for exp in [7, 8, 9, 15, 16, 17, 31, 32, 33, 62]:
            vals += list(range(2**exp - 5, 2**exp + 6))
        vals += list(range(2**63 - 5, 2**63))
        vals += [-v for v in vals]
        vals.append(-(2**63))

        for val in vals:
            db.execute('INSERT INTO test (id) VALUES(?)', (val, ))

    def fill_deltas(self, db):
        deltas = []
        for exp in [7, 8, 9, 15, 16, 17, 31, 32, 33]:
            deltas += list(range(2**exp - 5, 2**exp + 6))
        deltas += [-v for v in deltas]

        last = 0
        for delta in deltas:
            val = last + delta
            last = val
            db.execute('INSERT INTO test (id) VALUES(?)', (val, ))

    def create_table(self, db):
        db.execute('''CREATE TABLE test (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            buf BLOB)''')
Beispiel #5
0
def upgrade(bucket):
    '''Upgrade file system to newest revision'''

    # Access to protected member
    #pylint: disable=W0212
    log.info('Getting file system parameters..')
    seq_nos = [ int(x[len('s3ql_seq_no_'):]) for x in bucket.list('s3ql_seq_no_') ]
    seq_no = max(seq_nos)
    if not seq_nos:
        raise QuietError(textwrap.dedent(''' 
            File system revision too old to upgrade!
            
            You need to use an older S3QL version to upgrade to a more recent
            revision before you can use this version to upgrade to the newest
            revision.
            '''))                     
    param = bucket.lookup('s3ql_metadata')

    # Check for unclean shutdown
    if param['seq_no'] < seq_no:
        if bucket.is_get_consistent():
            raise QuietError(textwrap.fill(textwrap.dedent('''\
                It appears that the file system is still mounted somewhere else. If this is not
                the case, the file system may have not been unmounted cleanly and you should try
                to run fsck on the computer where the file system has been mounted most recently.
                ''')))
        else:                
            raise QuietError(textwrap.fill(textwrap.dedent('''\
                It appears that the file system is still mounted somewhere else. If this is not the
                case, the file system may have not been unmounted cleanly or the data from the 
                most-recent mount may have not yet propagated through the backend. In the later case,
                waiting for a while should fix the problem, in the former case you should try to run
                fsck on the computer where the file system has been mounted most recently.
                ''')))    

    # Check that the fs itself is clean
    if param['needs_fsck']:
        raise QuietError("File system damaged, run fsck!")
    
    # Check revision
    if param['revision'] < CURRENT_FS_REV - 1:
        raise QuietError(textwrap.dedent(''' 
            File system revision too old to upgrade!
            
            You need to use an older S3QL version to upgrade to a more recent
            revision before you can use this version to upgrade to the newest
            revision.
            '''))

    elif param['revision'] >= CURRENT_FS_REV:
        print('File system already at most-recent revision')
        return
    
    print(textwrap.dedent('''
        I am about to update the file system to the newest revision. 
        You will not be able to access the file system with any older version
        of S3QL after this operation. 
        
        You should make very sure that this command is not interrupted and
        that no one else tries to mount, fsck or upgrade the file system at
        the same time.
        '''))

    print('Please enter "yes" to continue.', '> ', sep='\n', end='')

    if sys.stdin.readline().strip().lower() != 'yes':
        raise QuietError()

    log.info('Upgrading from revision %d to %d...', CURRENT_FS_REV - 1,
             CURRENT_FS_REV)
    
    
    if 's3ql_hash_check_status' not in bucket:        
        if (isinstance(bucket, LegacyLocalBucket) or
            (isinstance(bucket, BetterBucket) and
             isinstance(bucket.bucket, LegacyLocalBucket))):
            if isinstance(bucket, LegacyLocalBucket):
                bucketpath = bucket.name
            else:
                bucketpath = bucket.bucket.name
            for (path, _, filenames) in os.walk(bucketpath, topdown=True):
                for name in filenames:
                    if not name.endswith('.meta'):
                        continue
                    
                    basename = os.path.splitext(name)[0]
                    if '=00' in basename:
                        raise RuntimeError("No, seriously, you tried to break things, didn't you?")
                    
                    with open(os.path.join(path, name), 'r+b') as dst:
                        dst.seek(0, os.SEEK_END)
                        with open(os.path.join(path, basename + '.dat'), 'rb') as src:
                            shutil.copyfileobj(src, dst)
                    
                    basename = basename.replace('#', '=23')
                    os.rename(os.path.join(path, name),
                              os.path.join(path, basename))
                    os.unlink(os.path.join(path, basename + '.dat'))
                    
            if isinstance(bucket, LegacyLocalBucket):
                bucket = LocalBucket(bucket.name, None, None)
            else:
                bucket.bucket = LocalBucket(bucket.bucket.name, None, None)
                         
        # Download metadata
        log.info("Downloading & uncompressing metadata...")
        dbfile = tempfile.NamedTemporaryFile()
        with tempfile.TemporaryFile() as tmp:    
            with bucket.open_read("s3ql_metadata") as fh:
                shutil.copyfileobj(fh, tmp)
        
            db = Connection(dbfile.name, fast_mode=True)
            tmp.seek(0)
            restore_legacy_metadata(tmp, db)
    
        # Increase metadata sequence no
        param['seq_no'] += 1
        bucket['s3ql_seq_no_%d' % param['seq_no']] = 'Empty'
        for i in seq_nos:
            if i < param['seq_no'] - 5:
                del bucket['s3ql_seq_no_%d' % i ]
    
        log.info("Uploading database..")
        cycle_metadata(bucket)
        param['last-modified'] = time.time() - time.timezone
        with bucket.open_write("s3ql_metadata", param) as fh:
            dump_metadata(fh, db)
            
    else:
        log.info("Downloading & uncompressing metadata...")
        dbfile = tempfile.NamedTemporaryFile()
        with tempfile.TemporaryFile() as tmp:    
            with bucket.open_read("s3ql_metadata") as fh:
                shutil.copyfileobj(fh, tmp)
        
            db = Connection(dbfile.name, fast_mode=True)
            tmp.seek(0)
            restore_metadata(tmp, db)        

    print(textwrap.dedent('''
        The following process may take a long time, but can be interrupted
        with Ctrl-C and resumed from this point by calling `s3qladm upgrade`
        again. Please see Changes.txt for why this is necessary.
        '''))

    if 's3ql_hash_check_status' not in bucket:
        log.info("Starting hash verification..")
        start_obj = 0
    else:
        start_obj = int(bucket['s3ql_hash_check_status'])
        log.info("Resuming hash verification with object %d..", start_obj)
    
    try:
        total = db.get_val('SELECT COUNT(id) FROM objects')
        i = 0
        for (obj_id, hash_) in db.query('SELECT obj_id, hash FROM blocks JOIN objects '
                                        'ON obj_id == objects.id WHERE obj_id > ? '
                                        'ORDER BY obj_id ASC', (start_obj,)):
            if i % 100 == 0:
                log.info(' ..checked %d/%d objects..', i, total)
              
            sha = hashlib.sha256()
            with bucket.open_read("s3ql_data_%d" % obj_id) as fh:
                while True:
                    buf = fh.read(128*1024)
                    if not buf:
                        break
                    sha.update(buf)
                    
            if sha.digest() != hash_:
                log.warn('Object %d corrupted! Deleting..', obj_id)
                bucket.delete('s3ql_data_%d' % obj_id)
            i += 1
            
    except KeyboardInterrupt:
        log.info("Storing verification status...")
        bucket['s3ql_hash_check_status'] = '%d' % obj_id
        raise QuietError('Aborting..')
    
    log.info('Running fsck...')
    fsck = Fsck(tempfile.mkdtemp(), bucket, param, db)
    fsck.check()    
    
    if fsck.uncorrectable_errors:
        raise QuietError("Uncorrectable errors found, aborting.")
            
    param['revision'] = CURRENT_FS_REV
    param['seq_no'] += 1
    bucket['s3ql_seq_no_%d' % param['seq_no']] = 'Empty'

    log.info("Uploading database..")
    cycle_metadata(bucket)
    param['last-modified'] = time.time() - time.timezone
    with bucket.open_write("s3ql_metadata", param) as fh:
        dump_metadata(fh, db)