285 lines
8.4 KiB
C
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);
|
|
}
|