/* * 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). * * 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: 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 * SPDX-License-Identifier: CC0-1.0 OR Unlicense */ #include #include #include #include #include #include #include /* macros: definitions */ #ifdef __unix__ # include #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 #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) #else # define DBG_PRINT(...) #endif #define RETREAT(s) do { \ perror(s); \ exit(EXIT_FAILURE); \ } while (0) /* 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 /* global variables */ temp_file_t* temp_files = NULL; size_t num_temp_files = 0; /* 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 /* 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 = num_temp_files == 0 ? 1 : (size_t) floor(log10((double) num_temp_files)) + 1, 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 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(-3); return TEMP_FILES_ALLOC_FAILURE; } 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 fopen(file_path, "w+b"); if (file == NULL) { #endif 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(temp_file_t)) : realloc(temp_files, (num_temp_files + 1) * sizeof(temp_file_t)); if (temp_files == NULL) { free(file_path); #ifdef IS_POSIX close #else fclose #endif (file); LINE_FAIL(-8); 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 ( #ifdef IS_POSIX lseek #else fseek #endif (file, offset, SEEK_SET) == -1) { free(src); temp_files[n].locked = false; LINE_FAIL(-3); return TEMP_FILES_FAILURE; } // read bytes #ifdef IS_POSIX ssize_t bytes_read = read(file, src, data_size); #else size_t bytes_read = fread(src, 1, data_size, file); #endif memcpy(dest, src, data_size); free(src); // Free the allocated memory if ( #ifdef IS_POSIX (size_t) #endif bytes_read != data_size) { temp_files[n].locked = false; errno = EIO; return TEMP_FILES_FAILURE; } #ifdef DEBUG 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); printf("0x%02" PRIX8, *((uint8_t*) ((uint8_t*) dest + i))); } printf("'\n"); fflush(stdout); #endif TEMP_FILES_RELEASE; return TEMP_FILES_SUCCESS; } static tf_error_t tf_wipe(tf_file_t file, size_t items, size_t size) { TEMP_FILES_SEEK_MACRO(file, 0, SEEK_SET); 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 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"); int test_data_1[ARRAY_1_LEN] = {123, 456, 789, -123}; DBG_PRINT("initialized array 1\n"); uint16_t test_data_2[ARRAY_2_LEN]; for (size_t i = 0; i < ARRAY_2_LEN; i++) test_data_2[i] = 1 << i; DBG_PRINT("initialized array 2\n"); // Automatic testing uintmax_t tests_failed = 0; // Pause fputs("Press Enter to continue: ", stdout); getchar(); 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_finish(); if (tests_failed != 0) { printf("%" PRIuMAX " tests failed\n", tests_failed); return EXIT_FAILURE; } puts("Tests passed!"); return EXIT_SUCCESS; } #endif