/* 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 #include #include #include #include #include /** * @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); }