YeltsinDB/src/journal.c

409 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <YeltsinDB/types.h>
#include <YeltsinDB/error_code.h>
#include <YeltsinDB/constants.h>
#include <YeltsinDB/macro.h>
#include <YeltsinDB/journal/transaction.h>
#include <YeltsinDB/journal/transaction_op.h>
#include <YeltsinDB/journal.h>
struct __YDB_Journal {
FILE *fd;
YDB_Offset first_transaction_offset;
YDB_Offset last_transaction_offset;
YDB_Offset prev_transaction_offset;
YDB_Offset curr_transaction_offset;
YDB_Offset next_transaction_offset;
YDB_Transaction* curr_transaction;
uint8_t is_consistent;
uint8_t in_use;
};
inline static YDB_Error __ydb_jrnl_truncate_incomplete(YDB_Journal* j, YDB_Offset last_complete_offset) {
THROW_IF_NULL(j, YDB_ERR_JOURNAL_NOT_INITIALIZED);
// The only transaction is incomplete
if (last_complete_offset == 0) {
j->prev_transaction_offset = 0;
j->next_transaction_offset = 0;
j->curr_transaction_offset = 0;
j->first_transaction_offset = 0;
j->last_transaction_offset = 0;
fseeko(j->fd, YDB_jrnl_first_transaction_offset, SEEK_SET);
fwrite(&j->first_transaction_offset, sizeof(YDB_Offset), 1, j->fd);
fwrite(&j->last_transaction_offset, sizeof(YDB_Offset), 1, j->fd);
ftruncate(fileno(j->fd), YDB_jrnl_data_offset);
fsync(fileno(j->fd));
return YDB_ERR_SUCCESS;
}
fseeko(j->fd, last_complete_offset + YDB_jrnl_transaction_prev_offset, SEEK_SET);
YDB_Offset prev_offset;
fread(&prev_offset, sizeof(YDB_Offset), 1, j->fd);
REASSIGN_FROM_LE(prev_offset);
YDB_Offset next_offset;
fread(&next_offset, sizeof(YDB_Offset), 1, j->fd);
REASSIGN_FROM_LE(next_offset);
fseeko(j->fd, last_complete_offset + YDB_jrnl_transaction_next_offset, SEEK_SET);
YDB_Offset new_next_offset = 0;
fwrite(&new_next_offset, sizeof(YDB_Offset), 1, j->fd);
j->last_transaction_offset = last_complete_offset;
fseeko(j->fd, YDB_jrnl_last_transaction_offset, SEEK_SET);
YDB_Offset last_complete_offset_le = TO_LE(last_complete_offset);
fwrite(&last_complete_offset_le, sizeof(YDB_Offset), 1, j->fd);
ftruncate(fileno(j->fd), next_offset);
fsync(fileno(j->fd));
return YDB_ERR_SUCCESS;
}
inline static YDB_TransactionOp* __ydb_read_op(YDB_Journal* j) {
THROW_IF_NULL(j, NULL);
YDB_TransactionOp* r = malloc(sizeof(YDB_TransactionOp));
fread(&r->opcode, sizeof(YDB_OpCode), 1, j->fd);
fread(&r->size, sizeof(YDB_OpDataSize), 1, j->fd);
r->data = malloc(r->size);
fread(r->data, r->size, 1, j->fd);
return r;
}
inline static YDB_Error __ydb_jrnl_read(YDB_Journal* j, YDB_Offset off) {
THROW_IF_NULL(j, YDB_ERR_JOURNAL_NOT_INITIALIZED);
j->curr_transaction_offset = off;
fseeko(j->fd, off, SEEK_SET);
YDB_Offset prev_offset, next_offset;
YDB_Timestamp timestamp;
YDB_Flags flags;
fread(&prev_offset, sizeof(YDB_Offset), 1, j->fd);
fread(&next_offset, sizeof(YDB_Offset), 1, j->fd);
fread(&timestamp, sizeof(YDB_Timestamp), 1, j->fd);
fread(&flags, sizeof(YDB_Flags), 1, j->fd);
j->prev_transaction_offset = prev_offset;
j->next_transaction_offset = next_offset;
YDB_Transaction* t = ydb_transaction_create();
ydb_transaction_timestamp_set(t, timestamp);
ydb_transaction_flags_set(t, flags);
YDB_TransactionOp* op;
do {
op = __ydb_read_op(j);
if (op->opcode != YDB_jrnl_op_complete) {
ydb_transaction_push_op(t, op);
}
} while (op->opcode != YDB_jrnl_op_complete);
return YDB_ERR_SUCCESS;
}
YDB_Journal *ydb_journal_init() {
YDB_Journal* result = calloc(1, sizeof(YDB_Journal));
return result;
}
void ydb_journal_destroy(YDB_Journal *jrnl) {
ydb_journal_file_close(jrnl);
free(jrnl);
}
YDB_Error ydb_journal_file_create(YDB_Journal *jrnl, char *path) {
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
THROW_IF_NULL(!jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
if (access(path, F_OK) != -1) {
// TODO: Windows does not check W_OK correctly, use other methods.
// TODO: if can't read/write, throw other error
return YDB_ERR_JOURNAL_EXIST;
}
FILE *f = fopen(path, "wb");
char tpl[] = YDB_JRNL_SIGN // File signature
"\x00\x00\x00\x00\x00\x00\x00\x00" // First transaction offset
"\x00\x00\x00\x00\x00\x00\x00\x00"; // Last transaction offset
fwrite(tpl, sizeof(tpl)-1, 1, f);
fclose(f);
return ydb_journal_file_open(jrnl, path);
}
YDB_Error ydb_journal_file_open(YDB_Journal *jrnl, char *path) {
THROW_IF_NULL(jrnl, YDB_ERR_INSTANCE_NOT_INITIALIZED);
THROW_IF_NULL(!jrnl->in_use, YDB_ERR_INSTANCE_IN_USE);
if (access(path, F_OK) == -1) {
// TODO: Windows does not check W_OK correctly, use other methods.
// TODO: if can't read/write, throw other error
return YDB_ERR_JOURNAL_NOT_EXIST;
}
jrnl->fd = fopen(path, "rb+");
THROW_IF_NULL(jrnl->fd, YDB_ERR_UNKNOWN); // TODO file open error
char signature[YDB_JRNL_SIGN_SIZE];
fread(signature, 1, YDB_JRNL_SIGN_SIZE, jrnl->fd);
int sig_match = memcmp(signature, YDB_JRNL_SIGN, YDB_JRNL_SIGN_SIZE) == 0;
if (!sig_match) {
return YDB_ERR_JOURNAL_FILE_CORRUPTED;
}
// TODO check for read error
fread(&jrnl->first_transaction_offset, sizeof(YDB_Offset), 1, jrnl->fd);
REASSIGN_FROM_LE(jrnl->first_transaction_offset);
fread(&jrnl->last_transaction_offset, sizeof(YDB_Offset), 1, jrnl->fd);
REASSIGN_FROM_LE(jrnl->last_transaction_offset);
jrnl->in_use = -1;
jrnl->curr_transaction_offset = jrnl->first_transaction_offset;
return ydb_journal_file_check_consistency(jrnl);
}
YDB_Error ydb_journal_file_close(YDB_Journal *jrnl) {
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
jrnl->first_transaction_offset = 0;
jrnl->last_transaction_offset = 0;
jrnl->prev_transaction_offset = 0;
jrnl->curr_transaction_offset = 0;
jrnl->next_transaction_offset = 0;
ydb_transaction_destroy(jrnl->curr_transaction);
jrnl->curr_transaction = NULL;
fclose(jrnl->fd);
jrnl->in_use = 0;
return YDB_ERR_SUCCESS;
}
YDB_Error ydb_journal_file_check_consistency(YDB_Journal *jrnl) {
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
// Empty journal is always consistent lol
if (jrnl->last_transaction_offset == 0) {
jrnl->is_consistent = -1;
return YDB_ERR_SUCCESS;
}
YDB_Offset last_complete_offset = 0;
YDB_Offset prev_offset = 0;
YDB_Offset next_offset = 0;
fseeko(jrnl->fd, jrnl->last_transaction_offset, SEEK_SET);
if (fread(&prev_offset, sizeof(YDB_Offset), 1, jrnl->fd) != sizeof(YDB_Offset)) {
if (feof(jrnl->fd)) {
clearerr(jrnl->fd);
perror("Mozhno nenado...\n");
abort();
// TODO try to find last transaction from begin
} else {
return YDB_ERR_UNKNOWN; // FIXME
}
}
REASSIGN_FROM_LE(prev_offset);
last_complete_offset = prev_offset;
if (fread(&next_offset, sizeof(YDB_Offset), 1, jrnl->fd) != sizeof(YDB_Offset)) {
if (feof(jrnl->fd)) {
clearerr(jrnl->fd);
__ydb_jrnl_truncate_incomplete(jrnl, last_complete_offset);
jrnl->is_consistent = -1;
return YDB_ERR_SUCCESS;
} else {
return YDB_ERR_UNKNOWN; // FIXME
}
}
REASSIGN_FROM_LE(next_offset);
if (next_offset != 0) {
perror("Kavo? Ne ponyal shyas...\n");
abort(); // Аборт -- это грех, кста
}
fseeko(jrnl->fd, jrnl->last_transaction_offset + YDB_jrnl_transaction_flags_offset, SEEK_SET);
if (feof(jrnl->fd)) {
__ydb_jrnl_truncate_incomplete(jrnl, last_complete_offset);
jrnl->is_consistent = -1;
return YDB_ERR_SUCCESS;
} else if (ferror(jrnl->fd)) {
return YDB_ERR_UNKNOWN; //FIXME
}
YDB_Flags flags = 0;
fread(&flags, sizeof(YDB_Flags), 1, jrnl->fd);
if (flags & 1) { //FIXME magic
// A transaction has been written to the storage.
return YDB_ERR_SUCCESS;
} else {
YDB_OpCode op = 0;
while (op != YDB_jrnl_op_complete) {
YDB_OpDataSize size;
fread(&op, sizeof(YDB_TransactionOp), 1, jrnl->fd);
fread(&size, sizeof(YDB_OpDataSize), 1, jrnl->fd);
REASSIGN_FROM_LE(size);
if (feof(jrnl->fd) || ferror(jrnl->fd)) break;
fseeko(jrnl->fd, size, SEEK_CUR);
}
if (op == YDB_jrnl_op_complete) {
//fseeko(jrnl->fd, jrnl->last_transaction_offset + YDB_jrnl_transaction_flags_offset, SEEK_SET);
//fwrite(flags | 1, sizeof(YDB_Flags), 1, jrnl->fd); //FIXME magic // Also flag should be written when the data in storage is consistent
//fsync(fileno(jrnl->fd));
jrnl->is_consistent = -1;
return YDB_ERR_SUCCESS;
}
__ydb_jrnl_truncate_incomplete(jrnl, last_complete_offset);
}
jrnl->is_consistent = -1;
YDB_Error e = ydb_journal_seek_to_begin(jrnl);
if (e == YDB_ERR_SUCCESS || e == YDB_ERR_JOURNAL_EMPTY) {
return YDB_ERR_SUCCESS;
}
}
YDB_Error ydb_journal_seek_to_begin(YDB_Journal *jrnl) {
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
THROW_IF_NULL(jrnl->is_consistent, YDB_ERR_JOURNAL_NOT_CONSISTENT);
THROW_IF_NULL(jrnl->first_transaction_offset, YDB_ERR_JOURNAL_EMPTY);
return __ydb_jrnl_read(jrnl, jrnl->first_transaction_offset);
}
YDB_Error ydb_journal_seek_to_end(YDB_Journal *jrnl) {
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
THROW_IF_NULL(jrnl->is_consistent, YDB_ERR_JOURNAL_NOT_CONSISTENT);
THROW_IF_NULL(jrnl->last_transaction_offset, YDB_ERR_JOURNAL_EMPTY);
return __ydb_jrnl_read(jrnl, jrnl->last_transaction_offset);
}
YDB_Error ydb_journal_prev_transaction(YDB_Journal *jrnl) {
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
THROW_IF_NULL(jrnl->is_consistent, YDB_ERR_JOURNAL_NOT_CONSISTENT);
if (jrnl->prev_transaction_offset != 0) {
__ydb_jrnl_read(jrnl, jrnl->prev_transaction_offset);
return YDB_ERR_SUCCESS;
}
return YDB_ERR_NO_MORE_TRANSACTIONS;
}
YDB_Error ydb_journal_next_transaction(YDB_Journal *jrnl) {
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
THROW_IF_NULL(jrnl->is_consistent, YDB_ERR_JOURNAL_NOT_CONSISTENT);
if (jrnl->next_transaction_offset != 0) {
__ydb_jrnl_read(jrnl, jrnl->prev_transaction_offset);
return YDB_ERR_SUCCESS;
}
return YDB_ERR_NO_MORE_TRANSACTIONS;
}
YDB_Error ydb_journal_append_transaction(YDB_Journal *jrnl, YDB_Transaction *transaction) {
THROW_IF_NULL(jrnl, YDB_ERR_JOURNAL_NOT_INITIALIZED);
THROW_IF_NULL(jrnl->in_use, YDB_ERR_JOURNAL_NOT_IN_USE);
THROW_IF_NULL(jrnl->is_consistent, YDB_ERR_JOURNAL_NOT_CONSISTENT);
YDB_Transaction* t = ydb_transaction_clone(transaction);
fseeko(jrnl->fd, 0, SEEK_END);
YDB_Offset file_end = ftello(jrnl->fd);
YDB_Offset file_end_le = TO_LE(file_end);
YDB_Offset prev = jrnl->last_transaction_offset;
YDB_Offset prev_le = TO_LE(prev);
YDB_Offset next_le = 0;
YDB_Timestamp ts_le = TO_LE(ydb_transaction_timestamp_get(t));
YDB_Flags flags = ydb_transaction_flags_get(t);
fseeko(jrnl->fd, file_end, SEEK_SET);
fwrite(&prev_le, sizeof(YDB_Offset), 1, jrnl->fd);
fwrite(&next_le, sizeof(YDB_Offset), 1, jrnl->fd);
fwrite(&ts_le, sizeof(YDB_Timestamp), 1, jrnl->fd);
fwrite(&flags, sizeof(YDB_Flags), 1, jrnl->fd);
jrnl->last_transaction_offset = file_end;
fseeko(jrnl->fd, YDB_jrnl_last_transaction_offset, SEEK_SET);
fwrite(&file_end_le, sizeof(YDB_Offset), 1, jrnl->fd);
fsync(fileno(jrnl->fd)); // Checkpoint
if (prev != 0) {
fseeko(jrnl->fd, prev + YDB_jrnl_transaction_next_offset, SEEK_SET);
} else {
fseeko(jrnl->fd, YDB_jrnl_first_transaction_offset, SEEK_SET);
}
fwrite(&file_end_le, sizeof(YDB_Offset), 1, jrnl->fd);
fsync(fileno(jrnl->fd)); // Checkpoint
fseeko(jrnl->fd, file_end, SEEK_SET);
uint32_t op_cnt = ydb_transaction_ops_size_get(t);
for (uint32_t i = 0; i < op_cnt; ++i) {
YDB_TransactionOp* op = ydb_transaction_op_at(t, i);
fwrite(&op->opcode, sizeof(YDB_OpCode), 1, jrnl->fd);
fwrite(&op->size, sizeof(YDB_OpDataSize), 1, jrnl->fd);
fwrite(&op->data, op->size, 1, jrnl->fd);
fsync(fileno(jrnl->fd));
}
// Write completion op
fputc(YDB_jrnl_op_complete, jrnl->fd);
YDB_OpDataSize sz = 0;
#ifndef NO_EASTER_EGGS
char easter[] = "Andrew is a pi-door-ass";
sz = sizeof(easter) - 1;
fwrite(&sz, sizeof(YDB_OpDataSize), 1, jrnl->fd);
fwrite(easter, sz, 1, jrnl->fd);
#else
fwrite(&sz, sizeof(YDB_OpDataSize), 1, jrnl->fd);
#endif
fsync(fileno(jrnl->fd));
return __ydb_jrnl_read(jrnl, file_end);
}
YDB_Transaction *ydb_journal_get_current_transaction(YDB_Journal *jrnl) {
THROW_IF_NULL(jrnl, NULL);
THROW_IF_NULL(jrnl->in_use, NULL);
THROW_IF_NULL(jrnl->is_consistent, NULL);
THROW_IF_NULL(jrnl->first_transaction_offset, NULL);
return jrnl->curr_transaction;
}
#ifdef __cplusplus
}
#endif