📄 transaction.c
字号:
CONVERT(rec); /* build the recovery data into a single blob to allow us to do a single large write, which should be more efficient */ p = data + sizeof(*rec); for (el=tdb->transaction->elements;el;el=el->next) { if (el->offset >= old_map_size) { continue; } if (el->offset + el->length > tdb->transaction->old_map_size) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_setup_recovery: transaction data over new region boundary\n")); free(data); tdb->ecode = TDB_ERR_CORRUPT; return -1; } memcpy(p, &el->offset, 4); memcpy(p+4, &el->length, 4); if (DOCONV()) { tdb_convert(p, 8); } /* the recovery area contains the old data, not the new data, so we have to call the original tdb_read method to get it */ if (methods->tdb_read(tdb, el->offset, p + 8, el->length, 0) != 0) { free(data); tdb->ecode = TDB_ERR_IO; return -1; } p += 8 + el->length; } /* and the tailer */ tailer = sizeof(*rec) + recovery_max_size; memcpy(p, &tailer, 4); CONVERT(p); /* write the recovery data to the recovery area */ if (methods->tdb_write(tdb, recovery_offset, data, sizeof(*rec) + recovery_size) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_setup_recovery: failed to write recovery data\n")); free(data); tdb->ecode = TDB_ERR_IO; return -1; } /* as we don't have ordered writes, we have to sync the recovery data before we update the magic to indicate that the recovery data is present */ if (transaction_sync(tdb, recovery_offset, sizeof(*rec) + recovery_size) == -1) { free(data); return -1; } free(data); magic = TDB_RECOVERY_MAGIC; CONVERT(magic); *magic_offset = recovery_offset + offsetof(struct list_struct, magic); if (methods->tdb_write(tdb, *magic_offset, &magic, sizeof(magic)) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_setup_recovery: failed to write recovery magic\n")); tdb->ecode = TDB_ERR_IO; return -1; } /* ensure the recovery magic marker is on disk */ if (transaction_sync(tdb, *magic_offset, sizeof(magic)) == -1) { return -1; } return 0;}/* commit the current transaction*/int tdb_transaction_commit(struct tdb_context *tdb){ const struct tdb_methods *methods; tdb_off_t magic_offset = 0; u32 zero = 0; if (tdb->transaction == NULL) { TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_commit: no transaction\n")); return -1; } if (tdb->transaction->transaction_error) { tdb->ecode = TDB_ERR_IO; tdb_transaction_cancel(tdb); TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_commit: transaction error pending\n")); return -1; } if (tdb->transaction->nesting != 0) { tdb->transaction->nesting--; return 0; } /* check for a null transaction */ if (tdb->transaction->elements == NULL) { tdb_transaction_cancel(tdb); return 0; } methods = tdb->transaction->io_methods; /* if there are any locks pending then the caller has not nested their locks properly, so fail the transaction */ if (tdb->num_locks || tdb->global_lock.count) { tdb->ecode = TDB_ERR_LOCK; TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_commit: locks pending on commit\n")); tdb_transaction_cancel(tdb); return -1; } /* upgrade the main transaction lock region to a write lock */ if (tdb_brlock_upgrade(tdb, FREELIST_TOP, 0) == -1) { TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_start: failed to upgrade hash locks\n")); tdb->ecode = TDB_ERR_LOCK; tdb_transaction_cancel(tdb); return -1; } /* get the global lock - this prevents new users attaching to the database during the commit */ if (tdb_brlock(tdb, GLOBAL_LOCK, F_WRLCK, F_SETLKW, 0, 1) == -1) { TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_commit: failed to get global lock\n")); tdb->ecode = TDB_ERR_LOCK; tdb_transaction_cancel(tdb); return -1; } if (!(tdb->flags & TDB_NOSYNC)) { /* write the recovery data to the end of the file */ if (transaction_setup_recovery(tdb, &magic_offset) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_commit: failed to setup recovery data\n")); tdb_brlock(tdb, GLOBAL_LOCK, F_UNLCK, F_SETLKW, 0, 1); tdb_transaction_cancel(tdb); return -1; } } /* expand the file to the new size if needed */ if (tdb->map_size != tdb->transaction->old_map_size) { if (methods->tdb_expand_file(tdb, tdb->transaction->old_map_size, tdb->map_size - tdb->transaction->old_map_size) == -1) { tdb->ecode = TDB_ERR_IO; TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_commit: expansion failed\n")); tdb_brlock(tdb, GLOBAL_LOCK, F_UNLCK, F_SETLKW, 0, 1); tdb_transaction_cancel(tdb); return -1; } tdb->map_size = tdb->transaction->old_map_size; methods->tdb_oob(tdb, tdb->map_size + 1, 1); } /* perform all the writes */ while (tdb->transaction->elements) { struct tdb_transaction_el *el = tdb->transaction->elements; if (methods->tdb_write(tdb, el->offset, el->data, el->length) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_commit: write failed during commit\n")); /* we've overwritten part of the data and possibly expanded the file, so we need to run the crash recovery code */ tdb->methods = methods; tdb_transaction_recover(tdb); tdb_transaction_cancel(tdb); tdb_brlock(tdb, GLOBAL_LOCK, F_UNLCK, F_SETLKW, 0, 1); TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_commit: write failed\n")); return -1; } tdb->transaction->elements = el->next; free(el->data); free(el); } if (!(tdb->flags & TDB_NOSYNC)) { /* ensure the new data is on disk */ if (transaction_sync(tdb, 0, tdb->map_size) == -1) { return -1; } /* remove the recovery marker */ if (methods->tdb_write(tdb, magic_offset, &zero, 4) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_commit: failed to remove recovery magic\n")); return -1; } /* ensure the recovery marker has been removed on disk */ if (transaction_sync(tdb, magic_offset, 4) == -1) { return -1; } } tdb_brlock(tdb, GLOBAL_LOCK, F_UNLCK, F_SETLKW, 0, 1); /* TODO: maybe write to some dummy hdr field, or write to magic offset without mmap, before the last sync, instead of the utime() call */ /* on some systems (like Linux 2.6.x) changes via mmap/msync don't change the mtime of the file, this means the file may not be backed up (as tdb rounding to block sizes means that file size changes are quite rare too). The following forces mtime changes when a transaction completes */#ifdef HAVE_UTIME utime(tdb->name, NULL);#endif /* use a transaction cancel to free memory and remove the transaction locks */ tdb_transaction_cancel(tdb); return 0;}/* recover from an aborted transaction. Must be called with exclusive database write access already established (including the global lock to prevent new processes attaching)*/int tdb_transaction_recover(struct tdb_context *tdb){ tdb_off_t recovery_head, recovery_eof; unsigned char *data, *p; u32 zero = 0; struct list_struct rec; /* find the recovery area */ if (tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, &recovery_head) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to read recovery head\n")); tdb->ecode = TDB_ERR_IO; return -1; } if (recovery_head == 0) { /* we have never allocated a recovery record */ return 0; } /* read the recovery record */ if (tdb->methods->tdb_read(tdb, recovery_head, &rec, sizeof(rec), DOCONV()) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to read recovery record\n")); tdb->ecode = TDB_ERR_IO; return -1; } if (rec.magic != TDB_RECOVERY_MAGIC) { /* there is no valid recovery data */ return 0; } if (tdb->read_only) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: attempt to recover read only database\n")); tdb->ecode = TDB_ERR_CORRUPT; return -1; } recovery_eof = rec.key_len; data = (unsigned char *)malloc(rec.data_len); if (data == NULL) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to allocate recovery data\n")); tdb->ecode = TDB_ERR_OOM; return -1; } /* read the full recovery data */ if (tdb->methods->tdb_read(tdb, recovery_head + sizeof(rec), data, rec.data_len, 0) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to read recovery data\n")); tdb->ecode = TDB_ERR_IO; return -1; } /* recover the file data */ p = data; while (p+8 < data + rec.data_len) { u32 ofs, len; if (DOCONV()) { tdb_convert(p, 8); } memcpy(&ofs, p, 4); memcpy(&len, p+4, 4); if (tdb->methods->tdb_write(tdb, ofs, p+8, len) == -1) { free(data); TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to recover %d bytes at offset %d\n", len, ofs)); tdb->ecode = TDB_ERR_IO; return -1; } p += 8 + len; } free(data); if (transaction_sync(tdb, 0, tdb->map_size) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to sync recovery\n")); tdb->ecode = TDB_ERR_IO; return -1; } /* if the recovery area is after the recovered eof then remove it */ if (recovery_eof <= recovery_head) { if (tdb_ofs_write(tdb, TDB_RECOVERY_HEAD, &zero) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to remove recovery head\n")); tdb->ecode = TDB_ERR_IO; return -1; } } /* remove the recovery magic */ if (tdb_ofs_write(tdb, recovery_head + offsetof(struct list_struct, magic), &zero) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to remove recovery magic\n")); tdb->ecode = TDB_ERR_IO; return -1; } /* reduce the file size to the old size */ tdb_munmap(tdb); if (ftruncate(tdb->fd, recovery_eof) != 0) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to reduce to recovery size\n")); tdb->ecode = TDB_ERR_IO; return -1; } tdb->map_size = recovery_eof; tdb_mmap(tdb); if (transaction_sync(tdb, 0, recovery_eof) == -1) { TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_transaction_recover: failed to sync2 recovery\n")); tdb->ecode = TDB_ERR_IO; return -1; } TDB_LOG((tdb, TDB_DEBUG_TRACE, "tdb_transaction_recover: recovered %d byte database\n", recovery_eof)); /* all done */ return 0;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -