/* * reverse-ramdisk.c * * 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) * * Warning: The current result is quick and dirty. Not for educational or * production purposes. * * GCC/Clang/TCC: Compile with -DTEST to set macro TEST as defined, with * -DDEBUG to enable debug mode * * To-Do: Test: Fix the bug with invalid next size to reallocate * To-Do: Test: Automate the test verification * * Author: Intel A80486DX2-66 * License: Creative Commons Zero 1.0 Universal */ #include #include #include #include #include #include #include #if _POSIX_VERSION >= 200112L # include # include # define IS_POSIX 1 #else # define IS_POSIX 0 #endif #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) # define DBG_PRINT(...) do { \ printf(__VA_ARGS__); \ fflush(stdout); \ } while (0) #else # define DBG_PRINT(...) #endif #define RETREAT(s) do { \ perror(s); \ exit(EXIT_FAILURE); \ } while (0) typedef struct { bool locked; int ID; char* file_path; #if IS_POSIX int file; #else FILE* file; #endif } TempFile; TempFile* 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); int tf_alloc(size_t n, size_t type_size) { DBG_PRINT("tf_alloc(%zu, %zu)\n", n, type_size); // Create an empty file size_t len_digit; if (num_temp_files == 0) len_digit = 1; else len_digit = (size_t) floor(log10((double) num_temp_files)) + 1; size_t 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; } 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; } #if IS_POSIX int file = open(file_path, O_RDWR | O_CREAT); if (file == -1) { #else FILE* file = fopen(file_path, "w+b"); if (file == NULL) { #endif LINE_FAIL(-2); return -1; } // Allocate memory for the TempFile struct TempFile* temp_file = malloc(sizeof(TempFile)); if (temp_file == NULL) { LINE_FAIL(-2); return -1; } // Assign the ID, file path, file handler temp_file->locked = false; temp_file->ID = num_temp_files; temp_file->file_path = strdup(file_path); temp_file->file = file; // Allocate/reallocate memory for the temp_files structure if (temp_files == NULL) temp_files = malloc(sizeof(TempFile)); else temp_files = realloc(temp_files, num_temp_files * sizeof(TempFile)); if (temp_files == NULL) { LINE_FAIL(-2); return -1; } // Add the temp file to the array temp_files[num_temp_files++] = *temp_file; return temp_file->ID; } int tf_free(int ID) { DBG_PRINT("tf_free(%d)\n", ID); size_t index = (size_t) ID; if (temp_files[index].locked) { errno = EBUSY; return -1; } temp_files[index].locked = true; #if IS_POSIX close(temp_files[index].file); #else fclose(temp_files[index].file); #endif // Delete the file if (remove(temp_files[index].file_path) != 0) { LINE_FAIL(-1); return -1; } free(temp_files[index].file_path); // Shift the remaining temp 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); if ((temp_files = realloc(temp_files, num_temp_files * sizeof(TempFile))) == NULL) { LINE_FAIL(-2); return -1; } } 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 (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 IS_POSIX if (lseek(file, offset, SEEK_SET) == -1) { #else if (fseek(file, offset, SEEK_SET) == -1) { #endif 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) { errno = EIO; return -1; } #if IS_POSIX if (fsync(file) == -1) { LINE_FAIL(-1); 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 (temp_files[index].locked) { errno = EBUSY; return -1; } temp_files[index].locked = true; #if IS_POSIX int file = temp_files[index].file; if (file == -1) #else FILE* file = temp_files[index].file; if (file == NULL) #endif return -1; // Read the data from the file void* src = malloc(data_size); if (src == NULL) { #if IS_POSIX close(file); #else fclose(file); #endif LINE_FAIL(-7); return -1; } memset(src, 0, data_size); // clear destination // Set the position #if IS_POSIX if (lseek(file, offset, SEEK_SET) == -1) { #else if (fseek(file, offset, SEEK_SET) == -1) { #endif LINE_FAIL(-1); return -1; } // read bytes #if 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 ( #if IS_POSIX (size_t) #endif bytes_read != data_size) { errno = EIO; return -1; } #ifdef DEBUG printf("Read: ID = %d, src = %p, size = %zu -> '", ID, dest, data_size); for (size_t i = 0; i < data_size; i++) printf("0x%02" PRIX8 "%c", *((uint8_t*)((uint8_t*)dest + i)), i == (data_size - 1) ? '\'' : ' '); printf("\n"); fflush(stdout); #endif temp_files[index].locked = false; return 0; } #ifdef TEST int main(void) { DBG_PRINT("started\n"); #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) 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"); 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"); 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"); // 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' : ' '); tf_free(ID_1); tf_free(ID_2); DBG_PRINT("freed both files\n"); return 0; } #endif