From f8227e4ffbdb202f0b8beed18e8c8ec8ded04a47 Mon Sep 17 00:00:00 2001 From: Intel A80486DX2-66 Date: Wed, 31 Jul 2024 01:30:03 +0300 Subject: [PATCH] reverse-ramdisk.c: revision 2 --- c-programming/experiments/reverse-ramdisk.c | 689 +++++++++++--------- 1 file changed, 389 insertions(+), 300 deletions(-) diff --git a/c-programming/experiments/reverse-ramdisk.c b/c-programming/experiments/reverse-ramdisk.c index 36f2146..fbbc686 100644 --- a/c-programming/experiments/reverse-ramdisk.c +++ b/c-programming/experiments/reverse-ramdisk.c @@ -1,24 +1,15 @@ /* - * reverse-ramdisk.c + * reverse-ramdisk.c: revision 2 * * C programming idea: Handling temporary files like memory allocations * (allocating -> creating empty file, using -> locking for R/W, * freeing -> deleting). * - * 20% AI, 80% human (the code is tested and reviewed) - * - * XXX: The current result is quick and dirty. Not for educational or - * production purposes. - * * Compile with set macro TEST (using -DTEST) to set macro TEST as defined, * and/or with set macro DEBUG (using -DDEBUG) to enable debug mode * - * TODO:XXX: `tf_free`: Do not shift the array, only empty entry - * TODO:XXX: `tf_free`, `tf_write`, `tf_read`: Do not use `ID` as `index`, but - * look for it in TempFile structs - * TODO:XXX: Set freed memory pointers to NULL - * TODO:XXX: Add more error handling - * TODO: Test: Automate the test verification + * TODO: Make it possible to reuse non-last elements + * FIXME: First file not being deleted from file system * * Author: Intel A80486DX2-66 * License: Creative Commons Zero 1.0 Universal or Unlicense @@ -33,22 +24,64 @@ #include #include -#if _POSIX_VERSION >= 200112L -# include +/* macros: definitions */ +#ifdef __unix__ # include -# define IS_POSIX 1 -#else -# define IS_POSIX 0 #endif +#if _POSIX_VERSION >= 200112L +# include +# define IS_POSIX +#endif + +/* typedefs */ +typedef bool tf_error_t; +#ifdef IS_POSIX +typedef int tf_file_t; +#else +typedef FILE* tf_file_t; +#endif +typedef unsigned char uintmin_t; + +/* structures */ +struct _Temp_File { + bool locked; + char* file_path; + tf_file_t file; + size_t size; +}; + +/* typedefs: structures */ +typedef struct _Temp_File temp_file_t; + +/* macros: definitions */ +#define TEMP_FILES_SUCCESS true +#define TEMP_FILES_FAILURE false +#define TEMP_FILES_ALLOC_FAILURE SIZE_MAX + +#define TEMP_FILES_POSIX_PERMISSIONS (S_IRUSR | S_IWUSR) + +#ifdef IS_POSIX +# define FCNTL_SUPPORTED +#endif + +/* macros: lambdas */ #ifdef DEBUG # define LINE_FAIL(x) printf("failed on line %d\n", __LINE__ + x) #else # define LINE_FAIL(x) #endif -#if defined(DEBUG) || defined(TEST) +#ifdef IS_POSIX +# define TEMP_FILES_SEEK_MACRO lseek +#else +# define TEMP_FILES_SEEK_MACRO fseek +#endif + +/* macros: procedures */ +#if defined(DEBUG) # define DBG_PRINT(...) do { \ + fputs("[debug] ", stdout); \ printf(__VA_ARGS__); \ fflush(stdout); \ } while (0) @@ -61,29 +94,44 @@ exit(EXIT_FAILURE); \ } while (0) -typedef struct { - bool locked; - int ID; - char* file_path; -#if IS_POSIX - int -#else - FILE* -#endif - file; -} TempFile; +/* macros: procedures: internal */ +#define TEMP_FILES_CHECK_AND_LOCK \ + if (temp_files[n].locked) { \ + errno = EBUSY; \ + return TEMP_FILES_FAILURE; \ + } \ + temp_files[n].locked = true +#define TEMP_FILES_RELEASE \ + temp_files[n].locked = false -TempFile* temp_files = NULL; +/* global variables */ +temp_file_t* temp_files = NULL; size_t num_temp_files = 0; -int tf_alloc(size_t n, size_t type_size); -int tf_free(int ID); -int tf_write(int ID, size_t offset, void* src, size_t data_size); -int tf_read(int ID, size_t offset, void* dest, size_t data_size); -void tf_finish(void); +/* function definitions */ +static size_t tf_alloc(size_t items, size_t size); +static tf_error_t tf_free(size_t n); +static tf_error_t tf_read(size_t n, size_t offset, void* dest, + size_t data_size); +static tf_error_t tf_wipe(tf_file_t file, size_t items, size_t size); +static tf_error_t tf_write(size_t n, size_t offset, void* src, + size_t data_size); +static void tf_finish(void); +/* function definitions: internal */ +#ifdef FCNTL_SUPPORTED +static void tf_fs_lock(int file); +static void tf_fs_unlock(int file); +#else +# define tf_fs_lock(...) +# define tf_fs_unlock(...) +#endif -int tf_alloc(size_t n, size_t type_size) { - DBG_PRINT("tf_alloc(%zu, %zu)\n", n, type_size); +/* function implementations */ +static size_t tf_alloc(size_t items, size_t size) { + if ((num_temp_files + 1) == TEMP_FILES_ALLOC_FAILURE) { + errno = ENOSPC; + return TEMP_FILES_ALLOC_FAILURE; + } // Create an empty file size_t len_digit = @@ -91,254 +139,157 @@ int tf_alloc(size_t n, size_t type_size) { 1 : (size_t) floor(log10((double) num_temp_files)) + 1, - file_path_len = len_digit + strlen("tf_.tmp"); + file_path_len = len_digit + strlen("tf_.tmp"); char* file_path = malloc((file_path_len + 1) * sizeof(char)); if (file_path == NULL) { LINE_FAIL(-2); - return -1; + return TEMP_FILES_ALLOC_FAILURE; } + int res = snprintf(file_path, file_path_len + 1, "tf_%" PRIuMAX ".tmp", (uintmax_t) num_temp_files); if ((size_t) res != file_path_len) { - LINE_FAIL(-2); - return -1; + LINE_FAIL(-3); + return TEMP_FILES_ALLOC_FAILURE; } -#if IS_POSIX - int file = open(file_path, O_RDWR | O_CREAT, 0666); - if (file == -1) { + tf_file_t file; + + for (uintmin_t i = 0; i < 2; i++) { + file = +#ifdef IS_POSIX + open(file_path, O_RDWR | O_CREAT | O_EXCL, + TEMP_FILES_POSIX_PERMISSIONS); + if (file == -1) { #else - FILE* file = fopen(file_path, "w+b"); - if (file == NULL) { + fopen(file_path, "w+b"); + if (file == NULL) { #endif - free(file_path); - LINE_FAIL(-2); - return -1; + if (i < 1) { + unlink(file_path); + continue; + } + + free(file_path); + LINE_FAIL(-16); + return TEMP_FILES_ALLOC_FAILURE; + } + + break; } // Allocate/reallocate memory for all TempFiles temp_files = temp_files == NULL ? - malloc(sizeof(TempFile)) + malloc(sizeof(temp_file_t)) : - realloc(temp_files, (num_temp_files + 1) * sizeof(TempFile)); + realloc(temp_files, (num_temp_files + 1) * sizeof(temp_file_t)); if (temp_files == NULL) { free(file_path); -#if IS_POSIX - close(file); -#else - fclose(file); -#endif - LINE_FAIL(-2); - return -1; - } - - // Assign the ID, file path, file handler - temp_files[num_temp_files].locked = false; - temp_files[num_temp_files].ID = num_temp_files; - temp_files[num_temp_files].file_path = file_path; - temp_files[num_temp_files].file = file; - - // Increment the number of temporary files - num_temp_files++; - - return temp_files[num_temp_files - 1].ID; -} - -int tf_free(int ID) { - DBG_PRINT("tf_free(%d)\n", ID); - - size_t index = (size_t) ID; - if (index >= num_temp_files) { - errno = EINVAL; - return -1; - } - - if (temp_files[index].locked) { - errno = EBUSY; - return -1; - } - temp_files[index].locked = true; - -#if IS_POSIX - close -#else - fclose -#endif - (temp_files[index].file); - - // Delete the file - if (remove(temp_files[index].file_path) != 0) { - temp_files[index].locked = false; - LINE_FAIL(-2); - return -1; - } - - free(temp_files[index].file_path); - temp_files[index].file_path = NULL; - - // Shift the remaining temporary files in the array - for (size_t i = index; i < num_temp_files - 1; i++) - temp_files[i] = temp_files[i + 1]; - - // Reallocate memory for the temp_files array - if (--num_temp_files > 0) { - DBG_PRINT("num_temp_files = %zu\n", num_temp_files); - TempFile* new_temp_files_ptr = realloc(temp_files, num_temp_files * - sizeof(TempFile)); - if (new_temp_files_ptr == NULL) { - LINE_FAIL(-3); - return -1; - } - temp_files = new_temp_files_ptr; - } else { - free(temp_files); - temp_files = NULL; - } - - temp_files[index].locked = false; - - return 0; -} - -int tf_write(int ID, size_t offset, void* src, size_t data_size) { - DBG_PRINT("tf_write(%d, %zu, %p, %zu)\n", ID, offset, src, data_size); - - size_t index = (size_t) ID; - if (index >= num_temp_files) { - errno = EINVAL; - return -1; - } - - if (temp_files[index].locked) { - errno = EBUSY; - return -1; - } - temp_files[index].locked = true; - -#if IS_POSIX - // Check file handler for -1 - int file = temp_files[index].file; - if (file == -1) -#else - // Check file handler for NULL - FILE* file = temp_files[index].file; - if (file == NULL) -#endif - return -1; - - // Set the position - if ( -#if IS_POSIX - lseek -#else - fseek -#endif - (file, offset, SEEK_SET) == -1) { - LINE_FAIL(-1); - return -1; - } - - // Write the data to the file -#if IS_POSIX - ssize_t -#else - size_t -#endif - bytes_written = -#if IS_POSIX - write(file, src, data_size); -#else - fwrite(src, 1, data_size, file); -#endif - - if ( -#if IS_POSIX - (size_t) -#endif - bytes_written != data_size) { - temp_files[index].locked = false; - errno = EIO; - return -1; - } - -#if IS_POSIX - if (fsync(file) == -1) { - temp_files[index].locked = false; - LINE_FAIL(-2); - return -1; - } -#else - fflush(file); -#endif - - temp_files[index].locked = false; - - return 0; -} - -int tf_read(int ID, size_t offset, void* dest, size_t data_size) { - DBG_PRINT("tf_read(%d, %zu, %p, %zu)\n", ID, offset, dest, data_size); - - size_t index = (size_t) ID; - if (index >= num_temp_files) { - errno = EINVAL; - return -1; - } - - if (temp_files[index].locked) { - errno = EBUSY; - return -1; - } - temp_files[index].locked = true; - -#if IS_POSIX - int -#else - FILE* -#endif - file = temp_files[index].file; - if (file == -#if IS_POSIX - -1 -#else - NULL -#endif - ) { - temp_files[index].locked = false; - return -1; - } - - // Read the data from the file - void* src = malloc(data_size); - if (src == NULL) { - temp_files[index].locked = false; -#if IS_POSIX +#ifdef IS_POSIX close #else fclose #endif (file); LINE_FAIL(-8); - return -1; + return TEMP_FILES_ALLOC_FAILURE; + } + + tf_fs_lock(file); + + tf_wipe(file, items, size); + + temp_files[num_temp_files].locked = false; + temp_files[num_temp_files].file_path = file_path; + temp_files[num_temp_files].file = file; + temp_files[num_temp_files].size = items * size; + + num_temp_files++; + + return num_temp_files - 1; +} + +static tf_error_t tf_free(size_t n) { + TEMP_FILES_CHECK_AND_LOCK; + + if ((n + 1) < num_temp_files) { + fputs("tf_free: [WARNING] Deallocating a non-last element; no action " + "taken.\n", stderr); + return TEMP_FILES_FAILURE; + } + + tf_wipe(temp_files[n].file, temp_files[n].size, 1); + tf_fs_unlock(temp_files[n].file); + +#ifdef IS_POSIX + close(temp_files[n].file); + temp_files[n].file = -1; +#else + fclose(temp_files[n].file); + temp_files[n].file = NULL; +#endif + + // Delete the file + if (unlink(temp_files[n].file_path) == -1) { + temp_files[n].locked = false; + LINE_FAIL(-2); + return TEMP_FILES_FAILURE; + } + + free(temp_files[n].file_path); + temp_files[n].file_path = NULL; + temp_files[n].locked = false; + + return TEMP_FILES_SUCCESS; +} + +static tf_error_t tf_read(size_t n, size_t offset, void* dest, + size_t data_size) { + TEMP_FILES_CHECK_AND_LOCK; + + tf_file_t file = temp_files[n].file; + if (file == +#ifdef IS_POSIX + -1 +#else + NULL +#endif + ) { + temp_files[n].locked = false; + return TEMP_FILES_FAILURE; + } + + // Read the data from the file + void* src = malloc(data_size); + if (src == NULL) { + temp_files[n].locked = false; +#ifdef IS_POSIX + close +#else + fclose +#endif + (file); + LINE_FAIL(-9); + return TEMP_FILES_FAILURE; } memset(src, 0, data_size); // clear destination // Set the position if ( -#if IS_POSIX +#ifdef IS_POSIX lseek #else fseek #endif (file, offset, SEEK_SET) == -1) { free(src); - temp_files[index].locked = false; + temp_files[n].locked = false; LINE_FAIL(-3); - return -1; + return TEMP_FILES_FAILURE; } // read bytes -#if IS_POSIX +#ifdef IS_POSIX ssize_t bytes_read = read(file, src, data_size); #else size_t bytes_read = fread(src, 1, data_size, file); @@ -348,18 +299,18 @@ int tf_read(int ID, size_t offset, void* dest, size_t data_size) { free(src); // Free the allocated memory if ( -#if IS_POSIX +#ifdef IS_POSIX (size_t) #endif bytes_read != data_size) { - temp_files[index].locked = false; + temp_files[n].locked = false; errno = EIO; - return -1; + return TEMP_FILES_FAILURE; } #ifdef DEBUG - printf("Read: ID = %d, src = %p, size = %zu -> '", - ID, dest, data_size); + DBG_PRINT("Read: n = %" PRIuMAX ", src = %p, size = %" PRIuMAX " -> '", + (uintmax_t) n, (void*) dest, (uintmax_t) data_size); for (size_t i = 0; i < data_size; i++) { if (i > 0) putc(' ', stdout); @@ -369,32 +320,195 @@ int tf_read(int ID, size_t offset, void* dest, size_t data_size) { fflush(stdout); #endif - temp_files[index].locked = false; + TEMP_FILES_RELEASE; - return 0; + return TEMP_FILES_SUCCESS; } -void tf_finish(void) { - DBG_PRINT("tf_finish()\n"); +static tf_error_t tf_wipe(tf_file_t file, size_t items, size_t size) { + TEMP_FILES_SEEK_MACRO(file, 0, SEEK_SET); - for (size_t i = 0; i < num_temp_files; i++) - if (temp_files[i].file_path != NULL) - tf_free(temp_files[i].ID); + size_t n = items * size; + + void* zeros = calloc(items, size); + if (zeros == NULL) + return TEMP_FILES_FAILURE; + + for (size_t i = 0; i < n; i++) { + if ( +#ifdef IS_POSIX + write(file, zeros, size) +#else + fwrite(zeros, size, 1, file) +#endif + != 1) { + return TEMP_FILES_FAILURE; + } + } + + free(zeros); + + TEMP_FILES_SEEK_MACRO(file, 0, SEEK_SET); + + return TEMP_FILES_SUCCESS; +} + +static tf_error_t tf_write(size_t n, size_t offset, void* src, + size_t data_size) { + TEMP_FILES_CHECK_AND_LOCK; + +#ifdef IS_POSIX + // Check file handler for -1 + int file = temp_files[n].file; + if (file == -1) +#else + // Check file handler for NULL + FILE* file = temp_files[n].file; + if (file == NULL) +#endif + return TEMP_FILES_FAILURE; + + // Set the position + if (TEMP_FILES_SEEK_MACRO(file, offset, SEEK_SET) == -1) { + LINE_FAIL(-1); + return TEMP_FILES_FAILURE; + } + + // Write the data to the file +#ifdef IS_POSIX + ssize_t +#else + size_t +#endif + bytes_written = +#ifdef IS_POSIX + write(file, src, data_size); +#else + fwrite(src, 1, data_size, file); +#endif + + if ( +#ifdef IS_POSIX + (size_t) +#endif + bytes_written != data_size) { + temp_files[n].locked = false; + errno = EIO; + return TEMP_FILES_FAILURE; + } + +#ifdef IS_POSIX + if (fsync(file) == -1) { + temp_files[n].locked = false; + LINE_FAIL(-2); + return TEMP_FILES_FAILURE; + } +#else + fflush(file); +#endif + + TEMP_FILES_RELEASE; + + return TEMP_FILES_SUCCESS; +} + +static void tf_finish(void) { + for (size_t i = num_temp_files; i > 0; i--) { + size_t index = i - 1; + + if (temp_files[index].file_path != NULL) + tf_free(index); + } free(temp_files); temp_files = NULL; num_temp_files = 0; } +/* function implementations: internal */ +#ifdef FCNTL_SUPPORTED +static void tf_fs_lock(int file) { + DBG_PRINT("Locking file...\n"); + + struct flock lock; + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + lock.l_pid = getpid(); + + if (fcntl(file, F_SETLK, &lock) == -1) + RETREAT("fcntl"); +} + +static void tf_fs_unlock(int file) { + struct flock lock; + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + lock.l_pid = getpid(); + + fcntl(file, F_SETLK, &lock); +} +#endif + #ifdef TEST +/* macros: definitions */ +# define ROUND_TRIP_DATA_FAILURE 0 +# define ROUND_TRIP_DATA_SUCCESS 1 + +/* macros: lambdas */ +# define ARRAY_SIZE(length, size) \ + ((size_t) length * (size_t) size) + +/* macros: procedures */ +# define ROUND_TRIP_DATA_MACRO(id, array, length, size) do { \ + if (round_trip_data(id, array, ARRAY_SIZE(length, size)) == \ + ROUND_TRIP_DATA_FAILURE) \ + tests_failed += 1; \ +} while (0) + +/* function definitions */ +static int round_trip_data(size_t n, void* expected_data, size_t data_size); + +/* function implementations */ +static int round_trip_data(size_t n, void* expected_data, size_t data_size) { + if (tf_write(n, 0, expected_data, data_size) == TEMP_FILES_FAILURE) + RETREAT("tf_write"); + + void* actual_data = malloc(data_size); + if (actual_data == NULL) + RETREAT("malloc"); + + if (tf_read(n, 0, actual_data, data_size) == TEMP_FILES_FAILURE) + RETREAT("tf_read"); + + bool data_is_equal = memcmp(expected_data, actual_data, data_size) == 0; + + free(actual_data); + + if (data_is_equal) + return ROUND_TRIP_DATA_SUCCESS; + + printf("Data verification failed for ID %" PRIuMAX "\n", (uintmax_t) n); + return ROUND_TRIP_DATA_FAILURE; +} + int main(void) { DBG_PRINT("started\n"); +#ifdef IS_POSIX + DBG_PRINT("POSIX features are supported\n"); +#endif + #define ARRAY_1_LEN 4 #define ARRAY_2_LEN 16 - int ID_1 = tf_alloc(ARRAY_1_LEN, sizeof(int)); - int ID_2 = tf_alloc(ARRAY_2_LEN, sizeof(uint16_t)); - if (ID_1 == -1 || ID_2 == -1) + size_t ID_1 = tf_alloc(ARRAY_1_LEN, sizeof(int)); + size_t ID_2 = tf_alloc(ARRAY_2_LEN, sizeof(uint16_t)); + if (ID_1 == TEMP_FILES_ALLOC_FAILURE || ID_2 == TEMP_FILES_ALLOC_FAILURE) RETREAT("tf_alloc"); DBG_PRINT("allocated memory\n"); @@ -406,52 +520,27 @@ int main(void) { test_data_2[i] = 1 << i; DBG_PRINT("initialized array 2\n"); - for (size_t i = 0; i < ARRAY_1_LEN; i++) - if (tf_write(ID_1, i * sizeof(int), &test_data_1[i], sizeof(int)) == -1) - RETREAT("tf_write"); - DBG_PRINT("wrote array 1\n"); + // Automatic testing + uintmax_t tests_failed = 0; - for (size_t i = 0; i < ARRAY_2_LEN; i++) - if (tf_write(ID_2, i * sizeof(uint16_t), &test_data_2[i], - sizeof(uint16_t)) == -1) - RETREAT("tf_write"); - DBG_PRINT("wrote array 2\n"); + // Pause + fputs("Press Enter to continue: ", stdout); + getchar(); - // round-trip - test_data_1[0] = 111; - test_data_1[1] = 222; - test_data_1[2] = 333; - test_data_1[3] = 444; - DBG_PRINT("filled array 1 with garbage\n"); - - for (size_t i = 0; i < 16; i++) - test_data_2[i] ^= 1; - DBG_PRINT("filled array 2 with garbage\n"); - - for (size_t i = 0; i < ARRAY_1_LEN; i++) - if (tf_read(ID_1, i * sizeof(int), &test_data_1[i], sizeof(int)) == -1) - RETREAT("tf_read"); - DBG_PRINT("restored array 1\n"); - - for (size_t i = 0; i < ARRAY_2_LEN; i++) - if (tf_read(ID_2, i * sizeof(uint16_t), &test_data_2[i], - sizeof(uint16_t)) == -1) - RETREAT("tf_read"); - DBG_PRINT("restored array 2\n"); - - DBG_PRINT("Values (1): "); - for (size_t i = 0; i < ARRAY_1_LEN; i++) - DBG_PRINT("%d%c", test_data_1[i], i == (ARRAY_1_LEN - 1) ? '\n' : ' '); - DBG_PRINT("Values (2): "); - for (size_t i = 0; i < ARRAY_2_LEN; i++) - DBG_PRINT("%d%c", test_data_2[i], i == (ARRAY_2_LEN - 1) ? '\n' : ' '); + ROUND_TRIP_DATA_MACRO(ID_1, test_data_1, ARRAY_1_LEN, sizeof(int)); + ROUND_TRIP_DATA_MACRO(ID_2, test_data_2, ARRAY_2_LEN, sizeof(uint16_t)); tf_free(ID_1); - tf_free(ID_2); - DBG_PRINT("freed both files\n"); tf_finish(); + if (tests_failed != 0) { + printf("%" PRIuMAX " tests failed\n", tests_failed); + return EXIT_FAILURE; + } + + puts("Tests passed!"); + return EXIT_SUCCESS; } #endif