/* vi:ai:tabstop=8:shiftwidth=4:softtabstop=4:expandtab * * Author: Gabriel Burca (gburca dash fuse at ebixio dot com) * Version: 1.2 * Latest version: http://ebixio.com/rofs-filtered/old/rofs-filtered.c * * This FUSE file system allows the user to mount a directory read-only and filter * the files shown in the read-only directory based on regular expressions found in * the optional /etc/rofs-filtered.rc configuration file. * * What's the use of such a file system? Say you have a ton of *.flac music * files, along with the transcoded *.mp3 files in the same directory * structure. You'll want to show only one of the formats to music players that * can play both flac and mp3 so that the songs don't show up twice. You might * also want to show only mp3 files to players that don't understand the flac * format. * * Based on: * ROFS - The read-only filesystem for FUSE. * * On Ubuntu/Debian install: libfuse2, libfuse-dev, fuse-utils * Version 2.5 or later of FUSE is required. If needed, it can be obtained from * debuntu.org by adding the following line to /etc/apt/sources.list: * deb http://repository.debuntu.org/ dapper multiverse * * Compile using: gcc -o rofs-filtered -Wall -ansi -W -std=c99 -g -ggdb -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -lfuse rofs-filtered.c * * Mount by adding the following line to /etc/fstab: * /full/path/to/rofs-filtered#/the/read/write/device /the/read/only/mount/point fuse defaults,allow_other 0 0 * * Unmount: fusermount -u /the/read/only/mount/point * OR * Unmount: umount /the/read/only/mount/point * * The user might need to be in the "fuse" UNIX group. * ********************************************************************************* * Copyright (C) 2006-2007 Gabriel Burca (gburca dash fuse at ebixio dot com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ********************************************************************************* */ // We depend on version 2.5 of FUSE because it provides an "access" callback. // Some applications would call "access" and figure out a file is writable // (which was the default behavior of "access" prior to 2.5), then attempt to // open the file "rw", fail, and bomb out because of the conflicting info. #define FUSE_USE_VERSION 25 #include <sys/types.h> #include <sys/stat.h> #include <sys/statvfs.h> #include <stdio.h> #include <strings.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <errno.h> #include <fcntl.h> #include <sys/xattr.h> #include <dirent.h> #include <unistd.h> #include <regex.h> #include <syslog.h> #include <stdarg.h> #include <fuse.h> // Some hard-coded values: static const char *EXEC_NAME = "rofs-filtered"; static const char config_file[] = "/etc/rofs-filtered.rc"; static const int log_facility = LOG_DAEMON; // Global to store our read-write path char *rw_path; regex_t **patterns = NULL; int pattern_count = 0; /** Translate an rofs path into it's underlying filesystem path */ static char* translate_path(const char* path) { char *rPath= malloc(sizeof(char)*(strlen(path)+strlen(rw_path)+1)); if (!rPath) return NULL; strcpy(rPath,rw_path); if (rPath[strlen(rPath)-1]=='/') { rPath[strlen(rPath)-1]='\0'; } strcat(rPath,path); return rPath; } /** Log a message to syslog */ static void log_msg(const int level, const char *format, ... /*args*/) { va_list ap; va_start(ap, format); vsyslog(log_facility | level, format, ap); va_end(ap); } /** Report user-friendly regex errors */ static void log_regex_error(int error, regex_t *regex, const char* pattern) { size_t msg_len; char *err_msg; msg_len = regerror(error, regex, NULL, 0); err_msg = (char *)malloc(msg_len); if (err_msg) { regerror(error, regex, err_msg, msg_len); log_msg(LOG_ERR, "RegEx error: \"%s\" while parsing pattern: \"%s\"", err_msg, pattern); //printf("Error: %s %s\n", err_msg, pattern); free(err_msg); } regfree(regex); } /** Read the RegEx configuration file */ static int read_config(const char *conf_file) { #define MAX_LINE 1024 regex_t *regex, *ignore_pattern; char line[MAX_LINE]; int regcomp_res, pcount = 0; char *eol = NULL; FILE *fh = fopen(conf_file, "r"); if (fh == NULL) { log_msg(LOG_ERR, "Failed to open config file: %s", conf_file); return -1; } ignore_pattern = (regex_t *)malloc(sizeof(regex_t)); if (! ignore_pattern) { log_msg(LOG_ERR, "Out of memory!"); return -1; } regcomp_res = regcomp(ignore_pattern, "^#|^\\s*$", REG_EXTENDED | REG_NOSUB); if (regcomp_res) { log_msg(LOG_ERR, "Failed compiling config parser regex."); return -1; } while ( fgets(line, MAX_LINE, fh) ) { if (! regexec(ignore_pattern, line, 0, NULL, 0)) continue; regex = (regex_t *)malloc(sizeof(regex_t)); if (! regex) { log_msg(LOG_ERR, "Out of memory!"); return -1; } // Remove the \n EOL eol = index(line, '\n'); if (eol) *eol = '\0'; regcomp_res = regcomp(regex, line, REG_EXTENDED | REG_NOSUB); if ( regcomp_res ) { log_regex_error(regcomp_res, regex, line); } else { // Add regex to the stash pcount++; patterns = realloc(patterns, sizeof(regex_t *) * pcount); patterns[pcount - 1] = regex; } } pattern_count = pcount; regfree(ignore_pattern); fclose(fh); return 0; } /** If the file name matches one of the RegEx patterns, hide it. */ static int should_hide(const char *name) { int res; for (int i = 0; i < pattern_count; i++) { res = regexec(patterns[i], name, 0, NULL, 0); if (res == 0) { // We have a match. return 1; } } return 0; } /****************************** * * Callbacks for FUSE * ******************************/ static int callback_getattr(const char *path, struct stat *st_data) { if (should_hide(path)) return -ENOENT; int res; char *trpath=translate_path(path); if (!trpath) { errno = ENOMEM; return -errno; } res = lstat(trpath, st_data); free(trpath); if(res == -1) { return -errno; } // Remove write permissions = chmod a-w st_data->st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); return 0; } static int callback_readlink(const char *path, char *buf, size_t size) { if (should_hide(path)) return -ENOENT; int res; char *trpath=translate_path(path); if (!trpath) { errno = ENOMEM; return -errno; } res = readlink(trpath, buf, size - 1); free(trpath); if(res == -1) { return -errno; } buf[res] = '\0'; return 0; } static int callback_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { if (should_hide(path)) return -ENOENT; DIR *dp; struct dirent *de; (void) offset; (void) fi; char *trpath=translate_path(path); if (!trpath) { errno = ENOMEM; return -errno; } dp = opendir(trpath); free(trpath); if(dp == NULL) { return -errno; } while((de = readdir(dp)) != NULL) { if (should_hide(de->d_name)) { // hide some files and directories } else { struct stat st; memset(&st, 0, sizeof(st)); st.st_ino = de->d_ino; st.st_mode = de->d_type << 12; if (filler(buf, de->d_name, &st, 0)) break; } } closedir(dp); return 0; } static int callback_mknod(const char *path, mode_t mode, dev_t rdev) { (void)path; (void)mode; (void)rdev; return -EPERM; } static int callback_mkdir(const char *path, mode_t mode) { (void)path; (void)mode; return -EPERM; } static int callback_unlink(const char *path) { (void)path; return -EPERM; } static int callback_rmdir(const char *path) { (void)path; return -EPERM; } static int callback_symlink(const char *from, const char *to) { (void)from; (void)to; return -EPERM; } static int callback_rename(const char *from, const char *to) { if (should_hide(from)) return -ENOENT; (void)from; (void)to; return -EPERM; } static int callback_link(const char *from, const char *to) { if (should_hide(from)) return -ENOENT; (void)from; (void)to; return -EPERM; } static int callback_chmod(const char *path, mode_t mode) { if (should_hide(path)) return -ENOENT; (void)path; (void)mode; return -EPERM; } static int callback_chown(const char *path, uid_t uid, gid_t gid) { if (should_hide(path)) return -ENOENT; (void)path; (void)uid; (void)gid; return -EPERM; } static int callback_truncate(const char *path, off_t size) { if (should_hide(path)) return -ENOENT; (void)path; (void)size; return -EPERM; } static int callback_utime(const char *path, struct utimbuf *buf) { (void)path; (void)buf; return -EPERM; } /** This function should just check if the operation is permitted for the given * flags. FUSE will provide it's own file descriptor to the calling * application. */ static int callback_open(const char *path, struct fuse_file_info *finfo) { if (should_hide(path)) return -ENOENT; int res; /* We allow opens, unless they're tring to write, sneaky * people. */ int flags = finfo->flags; if ((flags & O_WRONLY) || (flags & O_RDWR) || (flags & O_CREAT) || (flags & O_EXCL) || (flags & O_TRUNC)) { return -EPERM; } char *trpath=translate_path(path); if (!trpath) { errno = ENOMEM; return -errno; } res = open(trpath, flags); free(trpath); if(res == -1) { return -errno; } close(res); return 0; } static int callback_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *finfo) { if (should_hide(path)) return -ENOENT; int fd; int res; (void)finfo; char *trpath=translate_path(path); if (!trpath) { errno = ENOMEM; return -errno; } errno = 0; fd = open(trpath, O_RDONLY); free(trpath); if (fd == -1) return -errno; errno = 0; res = pread(fd, buf, size, offset); if (res == -1) { res = -errno; } close(fd); return res; } static int callback_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *finfo) { if (should_hide(path)) return -ENOENT; (void)path; (void)buf; (void)size; (void)offset; (void)finfo; return -EPERM; } static int callback_statfs(const char *path, struct statvfs *st_buf) { if (should_hide(path)) return -ENOENT; int res; char *trpath=translate_path(path); if (!trpath) { errno = ENOMEM; return -errno; } res = statvfs(trpath, st_buf); free(trpath); if (res == -1) { return -errno; } return 0; } static int callback_release(const char *path, struct fuse_file_info *finfo) { (void) path; (void) finfo; return 0; } static int callback_fsync(const char *path, int crap, struct fuse_file_info *finfo) { (void) path; (void) crap; (void) finfo; return 0; } static int callback_access(const char *path, int mode) { if (should_hide(path)) return -ENOENT; if (mode & W_OK) return -1; // We are ReadOnly int res; char *trpath=translate_path(path); if (!trpath) { errno = ENOMEM; return -errno; } errno = 0; res = access(trpath, mode); free(trpath); if (res == -1 && errno != 0) { return -errno; } return res; } /** * Set the value of an extended attribute */ static int callback_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { if (should_hide(path)) return -ENOENT; (void)path; (void)name; (void)value; (void)size; (void)flags; return -EPERM; } /** * Get the value of an extended attribute. */ static int callback_getxattr(const char *path, const char *name, char *value, size_t size) { if (should_hide(path)) return -ENOENT; int res; char *trpath=translate_path(path); if (!trpath) { errno = ENOMEM; return -errno; } res = lgetxattr(trpath, name, value, size); free(trpath); if(res == -1) { return -errno; } return res; } /** * List the supported extended attributes. */ static int callback_listxattr(const char *path, char *list, size_t size) { if (should_hide(path)) return -ENOENT; int res; char *trpath=translate_path(path); if (!trpath) { errno = ENOMEM; return -errno; } res = llistxattr(trpath, list, size); free(trpath); if(res == -1) { return -errno; } return res; } /** * Remove an extended attribute. */ static int callback_removexattr(const char *path, const char *name) { if (should_hide(path)) return -ENOENT; (void)path; (void)name; return -EPERM; } struct fuse_operations callback_oper = { .getattr = callback_getattr, .readlink = callback_readlink, .readdir = callback_readdir, .mknod = callback_mknod, .mkdir = callback_mkdir, .symlink = callback_symlink, .unlink = callback_unlink, .rmdir = callback_rmdir, .rename = callback_rename, .link = callback_link, .chmod = callback_chmod, .chown = callback_chown, .truncate = callback_truncate, .utime = callback_utime, .open = callback_open, .read = callback_read, .write = callback_write, .statfs = callback_statfs, .release = callback_release, .fsync = callback_fsync, .access = callback_access, /* Extended attributes support for userland interaction */ .setxattr = callback_setxattr, .getxattr = callback_getxattr, .listxattr = callback_listxattr, .removexattr= callback_removexattr }; int main(int argc, char *argv[]) { openlog(EXEC_NAME, LOG_PID, log_facility); log_msg(LOG_INFO, "Starting up..."); for (int i = 0; i < argc; i++) log_msg(LOG_DEBUG, " arg %i = %s", i, argv[i]); if (argc < 3) { fprintf(stderr, "Usage: rofs-filtered <RW-Path> <Filtered-Path> [FUSE options]\n"); log_msg(LOG_ERR, "Not enough arguments. argc = %i", argc); exit(1); } if (access(argv[1], F_OK)) { log_msg(LOG_ERR, "The following directory does not exist: %s", argv[1]); exit(2); } if (access(argv[2], F_OK)) { log_msg(LOG_ERR, "The following directory does not exist: %s", argv[2]); exit(3); } // We save away the first argument (the RW-Path) int len = strlen(argv[1]) + 1; rw_path = malloc(len); if (rw_path) { strncpy(rw_path, argv[1], len); } else { exit(4); } // Shift all arguments up by one (overwriting the first argument) because // fuse_main doesn't accept the mount source, only the mountpoint. for (int i = 2; i < argc; i++) { argv[i - 1] = argv[i]; } argc--; if (read_config(config_file)) { log_msg(LOG_ERR, "Error parsing config file: %s", config_file); } // Hand off control to FUSE fuse_main(argc, argv, &callback_oper); return 0; }