// bin.h - flat file, fixed block size, time series database // // Thread safety: Not thread-safe. External synchronization required. // Behavior: No input validation — out-of-order data persists as-is. // Unbuffered I/O by default (use _buffered variants for performance). // _ok variants return 1 on success, 0 on any error (invalid args, I/O fail, // overflow). Non-_ok variants remain for backward compatibility. #ifndef BIN_H #define BIN_H /* USER-DEFINED (define before including): * typedef bin_key_t; */ #include #include #include #include #include "type.h" typedef struct { u64 length; u64 block_size; } bin_meta_t; FILE *bin_init(char const *, u64); FILE *bin_init_buffered(char const *, u64); FILE *bin_open(char const *path); FILE *bin_open_buffered(char const *path); int bin_read(FILE *, i64, void *, u64); void bin_write(FILE *, i64, void *, u64); int bin_write_ok(FILE *, i64, void *, u64); void bin_insert(FILE *, i64, void *, u64); int bin_insert_ok(FILE *, i64, void *, u64); void bin_append(FILE *, void *, u64); int bin_append_ok(FILE *, void *, u64); int bin_close(FILE *); u64 bin_length(FILE *); u64 bin_block_size(FILE *); i64 bin_search(FILE *, bin_key_t); u64 bin_fuzzy_index(i64); #endif // BIN_H #ifdef BIN_IMPLEMENTATION // IMPLEMENTATION #include static void bin_write_length(FILE *, u64); static void bin_write_block_size(FILE *, u64); static inline i64 index_bytes_safe(i64 i, u64 bs, u64 *out) { if (bs == 0 || i < 0 || (u64)i > (UINT64_MAX - sizeof(bin_meta_t)) / bs) return -1; *out = (u64)i * bs + sizeof(bin_meta_t); return 0; } #define member_sizeof(type, member) (sizeof(((type *)0)->member)) static inline int fseek_safe(FILE *f, u64 offset) { #ifdef _WIN32 return _fseeki64(f, (long long)offset, SEEK_SET); #else if ((u64)(long)offset != offset) return -1; return fseek(f, (long)offset, SEEK_SET); #endif } // bin_init() // // Creates the desired binary file // // path - The path and file name for the desired file // // return - The newly created file FILE *bin_init(char const *path, u64 block_size) { if (!path) return NULL; FILE *f = fopen(path, "wb+"); if (!f) return NULL; setvbuf(f, NULL, _IONBF, 0); bin_write_length(f, 0); bin_write_block_size(f, block_size); return f; } FILE *bin_init_buffered(char const *path, u64 block_size) { if (!path) return NULL; FILE *f = fopen(path, "wb+"); if (!f) return NULL; bin_write_length(f, 0); bin_write_block_size(f, block_size); return f; } // bin_open() // // Opens the desired binary file // // path - The path and file name for the desired file // // return - The newly opened file FILE *bin_open(char const *path) { if (!path) return NULL; FILE *f = fopen(path, "rb+"); if (!f) return NULL; setvbuf(f, NULL, _IONBF, 0); return f; } FILE *bin_open_buffered(char const *path) { if (!path) return NULL; return fopen(path, "rb+"); } // bin_close() // // Closes the desired binary file // // f - The file descriptor to be closed int bin_close(FILE *f) { if (!f) return 0; int r = fclose(f); return r == 0; } // bin_read() // // Read the desired data into the buffer // // f - The file pointer // i - The initial index of the read // data - The block_t buffer to be read into // n - The number of elements to read into the buffer int bin_read(FILE *f, i64 i, void *data, u64 n) { if (!f || !data || n == 0) return 0; u64 bs = bin_block_size(f); if (bs == 0) return 0; if (i < 0) return 0; u64 len = bin_length(f); if ((u64)i + n > len) return 0; u64 offset = 0; if (index_bytes_safe(i, bs, &offset) != 0) return 0; if (bs > SIZE_MAX / n) return 0; fseek_safe(f, offset); size_t read_n = fread(data, (size_t)bs, (size_t)n, f); if (read_n != n) { if ((n - read_n) > SIZE_MAX / bs) return (int)read_n; memset((char *)data + read_n * bs, 0, (n - read_n) * bs); return (int)read_n; } return (int)n; } // bin_write() // // Write the buffer into the file // // f - The file pointer // i - The initial index of the write // data - The block_t buffer to write from // n - The number of elements to write from the buffer void bin_write(FILE *f, i64 i, void *data, u64 n) { (void)bin_write_ok(f, i, data, n); } int bin_write_ok(FILE *f, i64 i, void *data, u64 n) { if (!f || !data || n == 0) return 0; u64 l = bin_length(f); u64 bs = bin_block_size(f); if (bs == 0) return 0; if (i < 0) return 0; if ((u64)i + n > l) return 0; u64 offset = 0; if (index_bytes_safe(i, bs, &offset) != 0) return 0; if (fseek_safe(f, offset) != 0) return 0; size_t written = fwrite(data, bs, n, f); return written == (size_t)n; } // bin_append() // // Appends the given data to the end of the binary file // // f - The file to be appended to // data - The blocks to append to the file // n - The number of block to be appended void bin_append(FILE *f, void *data, u64 n) { (void)bin_append_ok(f, data, n); } int bin_append_ok(FILE *f, void *data, u64 n) { if (!f || !data || n == 0) return 0; u64 l = bin_length(f); u64 bs = bin_block_size(f); if (bs == 0) return 0; if (l > UINT64_MAX - n) return 0; u64 offset = 0; if (index_bytes_safe((i64)l, bs, &offset) != 0) return 0; if (fseek_safe(f, offset) != 0) return 0; size_t written = fwrite(data, bs, n, f); if (written != (size_t)n) return 0; bin_write_length(f, l + n); return 1; } // bin_insert() // // Inserts the given data into the binary file // // f - The file to be inserted into // i - The initial index of the insertion // data - The blocks to insert into the file // n - The number of blocks to be inserted void bin_insert(FILE *f, i64 i, void *data, u64 n) { (void)bin_insert_ok(f, i, data, n); } int bin_insert_ok(FILE *f, i64 i, void *data, u64 n) { if (!f || !data || n == 0) return 0; u64 l = bin_length(f); u64 bs = bin_block_size(f); if (bs == 0) return 0; if (i < 0 || (u64)i > l) return 0; u64 blocks_to_move = l - (u64)i; if (blocks_to_move > UINT64_MAX / bs) return 0; if (l > UINT64_MAX - n) return 0; u64 offset = 0; if (index_bytes_safe(i, bs, &offset) != 0) return 0; if (fseek_safe(f, offset) != 0) return 0; if (blocks_to_move > 0) { u64 tmp_n = blocks_to_move * bs; if (tmp_n > SIZE_MAX) return 0; u8 *tmp = malloc((size_t)tmp_n); if (tmp == NULL) return 0; size_t rn = fread(tmp, (size_t)bs, (size_t)blocks_to_move, f); if (rn != (size_t)blocks_to_move) { memset((char *)tmp + rn * (size_t)bs, 0, ((size_t)blocks_to_move - rn) * (size_t)bs); } if (fseek_safe(f, offset) != 0) { free(tmp); return 0; } size_t written = fwrite(data, bs, n, f); if (written != (size_t)n) { free(tmp); return 0; } fwrite(tmp, 1, (size_t)tmp_n, f); free(tmp); } else { size_t written = fwrite(data, bs, n, f); if (written != (size_t)n) return 0; } bin_write_length(f, l + n); return 1; } // bin_length() // // Reads the block count number from the given file // // f - The file to be read from // // return - The length / number of entries in the file u64 bin_length(FILE *f) { if (!f) return 0; u64 n = 0; fseek(f, offsetof(bin_meta_t, length), SEEK_SET); size_t rn = fread(&n, member_sizeof(bin_meta_t, length), 1, f); if (rn != 1) return 0; return n; } // bin_block_size() // // Reads the block size from the given file // // f - The file to be read from // // return - The block size for the entries in the file u64 bin_block_size(FILE *f) { if (!f) return 0; u64 n = 0; fseek(f, offsetof(bin_meta_t, block_size), SEEK_SET); size_t rn = fread(&n, member_sizeof(bin_meta_t, block_size), 1, f); if (rn != 1) return 0; return n; } // bin_search() // // Search for a given key in the file // // f - The file to be searched within // k - The key to search for // // return - The block_t index of the key. In the case that the key is not found, // The returned value is -( index + 1 ) where index is that of the // first element greater than k or bin_length(). i64 bin_search(FILE *f, bin_key_t k) { if (!f) return -1; fpos_t origin; fgetpos(f, &origin); u64 len = bin_length(f); if (len == 0) { fsetpos(f, &origin); return -1; } i64 l = 0; i64 r = (i64)len - 1; i64 m; u64 bs = bin_block_size(f); bin_key_t key = 0; int found = 0; while (l <= r) { m = l + (r - l) / 2; u64 offset = 0; if (index_bytes_safe(m, bs, &offset) != 0) { fsetpos(f, &origin); return -(l + 1); } fseek_safe(f, offset); if (fread(&key, sizeof(k), 1, f) != 1) { fsetpos(f, &origin); return -(l + 1); } if (key < k) l = m + 1; else if (key > k) r = m - 1; else { found = 1; break; } } fsetpos(f, &origin); if (!found) return -(l + 1); return m; } // bin_fuzzy_index() // // If one does a binary search on the file and is returned a negative error, // it may be useful to retrueve the index which it would be at. // // i - The number to map to the correct index // // return - The index remapped the binary file. If the index is already // positive, then no operation occurrs. u64 bin_fuzzy_index(i64 i) { if (i < 0) return (u64)(-(i + 1)); return (u64)i; } // bin_write_length() // // Writes a length to the given file // // f - The file to be written to // count - The length to be written to the file static void bin_write_length(FILE *f, u64 length) { fseek(f, offsetof(bin_meta_t, length), SEEK_SET); if (fwrite(&length, member_sizeof(bin_meta_t, length), 1, f) != 1) { clearerr(f); } } // bin_write_block_size() // // Writes a block size to the given file // // f - The file to be written to // count - The block size to be written to the file static void bin_write_block_size(FILE *f, u64 block_size) { fseek(f, offsetof(bin_meta_t, block_size), SEEK_SET); fwrite(&block_size, member_sizeof(bin_meta_t, block_size), 1, f); } #endif