ICFS/src/sourcefs.c
2025-06-23 20:47:54 +02:00

285 lines
8.4 KiB
C

/*
ICFS: Interactively Controlled File System
Copyright (C) 2024-2025 Fedir Kovalov
This program can be distributed under the terms of the GNU GPLv2.
See the file LICENSE.
*/
#define _GNU_SOURCE
#include "sourcefs.h"
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/**
* @brief Global handle for the source filesystem implementation
*
* Stores the root directory information to enable relative path operations.
*/
static struct source_files_handle {
char *mountpoint; // Absolute path to the mounted filesystem root
int root_fd; // File descriptor for the root directory (O_PATH)
} handle;
/**
* @brief Translate FUSE paths to real paths in the underlying filesystem
*
* FUSE passes paths starting with '/' (e.g., "/dir/file"), but we need to
* operate relative to our root_fd. This function converts:
* - "/" -> "." (current directory)
* - "/dir/file" -> "dir/file"
*
* @param filename Path from FUSE (always starts with '/')
* @return Relative path suitable for use with root_fd
*/
const char *source_filename_translate(const char *filename) {
if (strcmp("/", filename) == 0) {
return ".";
}
return filename + 1; // Skip leading slash
}
/**
* @brief Initialize the source filesystem with a root directory
*
* Sets up the mount point and opens a protected file descriptor to the
* root directory for safe relative operations.
*
* @param root_path Absolute path to the physical root directory
* @return 0 on success, -1 on failure
*/
int source_init(const char *root_path) {
// Allocate memory for mount point path
handle.mountpoint = malloc(strlen(root_path) + 1);
if (handle.mountpoint == NULL) {
perror("[ICFS] Malloc failed");
return -1;
}
strcpy(handle.mountpoint, root_path);
// Open root directory with O_PATH to prevent accidental reads/writes
// while maintaining a valid descriptor for relative operations
int root_fd = open(root_path, O_PATH);
if (root_fd == -1) {
fprintf(stderr, "[ICFS] Could not initialize source file system at %s",
root_path);
perror("");
return -1;
}
handle.root_fd = root_fd;
return 0;
}
/**
* @brief Clean up resources used by the source filesystem
*/
void source_destroy(void) {
free(handle.mountpoint); // Free allocated mount point path
}
/**
* @brief Get the current mount point path
* @return Absolute path to the filesystem root
*/
const char *get_mountpoint(void) { return handle.mountpoint; }
/**
* @brief Build a real path to the file.
*
* @param filename Relative path within the filesystem
* @return Newly allocated absolute path (caller must free)
*/
const char *real_filename(const char *filename) {
const char *mountpoint = get_mountpoint();
size_t len1 = strlen(mountpoint);
size_t len2 = strlen(filename);
// Allocate space for combined path + null terminator
char *result = malloc(len1 + len2 + 1);
if (result == NULL) {
fprintf(stderr, "[ICFS] Memory allocation failed");
perror("");
return NULL;
}
strcpy(result, mountpoint);
strcat(result, filename);
return result;
}
/**
* @brief Create a directory in the filesystem
*
* Uses mkdirat() to safely create directories relative to root_fd,
* preventing race conditions from concurrent path modifications.
*/
int source_mkdir(const char *filename, mode_t mode) {
const char *relative_filename = source_filename_translate(filename);
return mkdirat(handle.root_fd, relative_filename, mode);
}
/**
* @brief Remove a file from the filesystem
*
* Uses unlinkat() to safely remove files relative to the root directory.
*/
int source_unlink(const char *filename) {
const char *relative_filename = source_filename_translate(filename);
return unlinkat(handle.root_fd, relative_filename, 0);
}
/**
* @brief Get file status information
*
* Uses fstatat() to retrieve metadata relative to root_fd. Follows symlinks
* by default (flags=0).
*/
int source_stat(const char *restrict filename, struct stat *restrict statbuf) {
const char *relative_filename = source_filename_translate(filename);
return fstatat(handle.root_fd, relative_filename, statbuf, 0);
}
/**
* @brief Remove an empty directory
*
* Uses unlinkat() with AT_REMOVEDIR flag to safely remove directories.
*/
int source_rmdir(const char *filename) {
const char *relative_filename = source_filename_translate(filename);
return unlinkat(handle.root_fd, relative_filename, AT_REMOVEDIR);
}
/**
* @brief Create a symbolic link
*
* Creates symlinks relative to the root_fd directory for safety.
*/
int source_symlink(const char *target, const char *linkpath) {
const char *relative_linkpath = source_filename_translate(linkpath);
return symlinkat(target, handle.root_fd, relative_linkpath);
}
/**
* @brief Check file access permissions
*
* Uses faccessat() to check access rights relative to root_fd.
*/
int source_access(const char *filename, int mode) {
const char *relative_filename = source_filename_translate(filename);
return faccessat(handle.root_fd, relative_filename, mode, 0);
}
/**
* @brief Open a directory for reading
*
* Combines openat() and fdopendir() to safely access directories relative
* to the root_fd.
*/
DIR *source_opendir(const char *filename) {
const char *relative_filename = source_filename_translate(filename);
int fd = openat(handle.root_fd, relative_filename, 0);
if (fd < 0) {
perror("[ICFS] Openat failed");
return NULL;
}
DIR *dir_pointer = fdopendir(fd);
return dir_pointer;
}
/**
* @brief Rename a file or directory
*
* Uses renameat() to safely rename within the same root_fd namespace.
*/
int source_rename(const char *oldpath, const char *newpath) {
const char *relative_oldpath = source_filename_translate(oldpath);
const char *relative_newpath = source_filename_translate(newpath);
return renameat(handle.root_fd, relative_oldpath, handle.root_fd,
relative_newpath);
}
/**
* @brief Create a hard link
*
* Uses linkat() with flags=0 (default behavior). May need AT_SYMLINK_NOFOLLOW
* if symlink handling should be modified.
*/
int source_link(const char *oldpath, const char *newpath) {
const char *relative_oldpath = source_filename_translate(oldpath);
const char *relative_newpath = source_filename_translate(newpath);
return linkat(handle.root_fd, relative_oldpath, handle.root_fd,
relative_newpath, 0);
}
/**
* @brief Change file access mode
*
* Uses fchmodat() with flags=0 (follow symlinks). Consider using
* AT_SYMLINK_NOFOLLOW if symlink metadata should be modified directly.
*/
int source_chmod(const char *filename, mode_t mode) {
const char *relative_filename = source_filename_translate(filename);
return fchmodat(handle.root_fd, relative_filename, mode, 0);
}
/**
* @brief Change file owner and group
*
* Uses fchownat() with AT_SYMLINK_NOFOLLOW to modify symlink metadata
* rather than its target.
*/
int source_chown(const char *filename, uid_t owner, gid_t group) {
const char *relative_filename = source_filename_translate(filename);
return fchownat(handle.root_fd, filename, owner, group, AT_SYMLINK_NOFOLLOW);
}
/**
* @brief Truncate a file to a specified length
*
* Opens the file with read-only access then truncates it. This may fail
* if the file wasn't opened with write permissions. Consider changing
* openat() flags to O_WRONLY for reliability.
*/
int source_truncate(const char *filename, off_t length) {
const char *relative_filename = source_filename_translate(filename);
int fd = openat(handle.root_fd, relative_filename, 0);
if (fd < 0) {
perror("[ICFS] Openat failed");
return -1;
}
return ftruncate(fd, length);
}
/**
* @brief Open a file with specified flags
*
* Uses openat() to safely access files relative to root_fd.
*/
int source_open(const char *filename, int flags) {
const char *relative_filename = source_filename_translate(filename);
return openat(handle.root_fd, relative_filename, flags);
}
/**
* @brief Create and open a new file
*
* Uses openat() with O_CREAT to create files relative to root_fd.
*/
int source_create(const char *filename, int flags, mode_t mode) {
const char *relative_filename = source_filename_translate(filename);
return openat(handle.root_fd, relative_filename, flags | O_CREAT, mode);
}
int source_utimens(const char *filename, const struct timespec ts[2],
int flags) {
const char *relative_filename = source_filename_translate(filename);
return utimensat(handle.root_fd, relative_filename, ts, AT_SYMLINK_NOFOLLOW);
}