1
0
mirror of https://gitlab.com/80486DX2-66/gists synced 2025-05-13 18:59:13 +05:30
gists/c-programming/experiments/reverse-ramdisk.c

547 lines
12 KiB
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).
*
* 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 <errno.h>
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* macros: definitions */
#ifdef __unix__
# include <unistd.h>
#endif
#if _POSIX_VERSION >= 200112L
# include <fcntl.h>
# 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