def testBadTransaction(self): # Find transaction headers and blast them. L = self.storage.undoLog() r = L[3] tid = decodebytes(r["id"] + b"\n") pos1 = self.storage._txn_find(tid, 0) r = L[8] tid = decodebytes(r["id"] + b"\n") pos2 = self.storage._txn_find(tid, 0) self.storage.close() # Overwrite the entire header. with open(self.path, "a+b") as f: f.seek(pos1 - 50) f.write(b"\0" * 100) output = self.recover() self.assertTrue('error' in output, output) self.recovered = FileStorage(self.dest) self.recovered.close() os.remove(self.path) os.rename(self.dest, self.path) # Overwrite part of the header. with open(self.path, "a+b") as f: f.seek(pos2 + 10) f.write(b"\0" * 100) output = self.recover() self.assertTrue('error' in output, output) self.recovered = FileStorage(self.dest) self.recovered.close()
def testUncommittedAtEnd(self): # Find a transaction near the end. L = self.storage.undoLog() r = L[1] tid = decodebytes(r["id"] + b"\n") pos = self.storage._txn_find(tid, 0) # Overwrite its status with 'c'. with open(self.path, "r+b") as f: f.seek(pos + 16) current_status = f.read(1) self.assertEqual(current_status, b' ') f.seek(pos + 16) f.write(b'c') # Try to recover. The original bug was that this never completed -- # infinite loop in fsrecover.py. Also, in the ZODB 3.2 line, # reference to an undefined global masked the infinite loop. self.recover() # Verify the destination got truncated. self.assertEqual(os.path.getsize(self.dest), pos) # Get rid of the temp file holding the truncated bytes. os.remove(ZODB.fsrecover._trname)
def undo(self, serial_id, transaction): undo_serial, keys = self.__storage.undo(serial_id, transaction) # serial_id is the transaction id of the txn that we wish to undo. # "undo_serial" is the transaction id of txn in which the undo is # performed. "keys" is the list of oids that are involved in the # undo transaction. # The serial_id is assumed to be given to us base-64 encoded # (belying the web UI legacy of the ZODB code :-() serial_id = decodebytes(serial_id + b'\n') self._lock_acquire() try: # we get all the blob oids on the filesystem related to the # transaction we want to undo. for oid in self.fshelper.getOIDsForSerial(serial_id): # we want to find the serial id of the previous revision # of this blob object. load_result = self.loadBefore(oid, serial_id) if load_result is None: # There was no previous revision of this blob # object. The blob was created in the transaction # represented by serial_id. We copy the blob data # to a new file that references the undo # transaction in case a user wishes to undo this # undo. It would be nice if we had some way to # link to old blobs. orig_fn = self.fshelper.getBlobFilename(oid, serial_id) new_fn = self.fshelper.getBlobFilename(oid, undo_serial) else: # A previous revision of this blob existed before the # transaction implied by "serial_id". We copy the blob # data to a new file that references the undo transaction # in case a user wishes to undo this undo. data, serial_before, serial_after = load_result orig_fn = self.fshelper.getBlobFilename(oid, serial_before) new_fn = self.fshelper.getBlobFilename(oid, undo_serial) with open(orig_fn, "rb") as orig: with open(new_fn, "wb") as new: utils.cp(orig, new) self.dirty_oids.append((oid, undo_serial)) finally: self._lock_release() return undo_serial, keys