Loop mounting device nodes as regular files.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Copyright 2015-2022 Rivoreo | |
Permission is hereby granted, free of charge, to any person obtaining | |
a copy of this software and associated documentation files (the | |
"Software"), to deal in the Software without restriction, including | |
without limitation the rights to use, copy, modify, merge, publish, | |
distribute, sublicense, and/or sell copies of the Software, and to | |
permit persons to whom the Software is furnished to do so, subject to | |
the following conditions: | |
The above copyright notice and this permission notice shall be | |
included in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE | |
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
#define FUSE_USE_VERSION 26 | |
#include <fuse.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <errno.h> | |
static int use_root_directory; | |
#ifdef O_DIRECT | |
#define DIRECT_IO_ALLOW 0 | |
#define DIRECT_IO_DENY 1 | |
#define DIRECT_IO_IGNORE 2 | |
#define DIRECT_IO_ALWAYS 3 | |
static int direct_io_mode; | |
#endif | |
static unsigned int source_file_count; | |
static struct source_file_info { | |
char *path; | |
const char *new_name; | |
} *source_files = NULL; | |
static const char *get_real_path(const char *path) { | |
if(use_root_directory) { | |
unsigned int i; | |
while(*path == '/') path++; | |
for(i=0; i<source_file_count; i++) { | |
if(strcmp(source_files[i].new_name, path)) continue; | |
return source_files[i].path; | |
} | |
return NULL; | |
} | |
return strcmp(path, "/") == 0 ? source_files->path : NULL; | |
} | |
static off_t get_seek_size(int fd) { | |
off_t pos = lseek(fd, 0, SEEK_CUR); | |
if(pos == -1) return 0; | |
off_t size = lseek(fd, 0, SEEK_END); | |
lseek(fd, pos, SEEK_SET); | |
return size == -1 ? 0 : size; | |
} | |
static void make_regular(int fd, struct stat *st) { | |
if(!st->st_size && (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode) || S_ISDIR(st->st_mode))) { | |
st->st_size = get_seek_size(fd); | |
} | |
st->st_mode = (st->st_mode & ~S_IFMT) | S_IFREG; | |
} | |
static int regloop_fgetattr(const char *path, struct stat *st, struct fuse_file_info *fi) { | |
if(fstat(fi->fh, st) < 0) return -errno; | |
if(!S_ISREG(st->st_mode)) make_regular(fi->fh, st); | |
return 0; | |
} | |
static int regloop_getattr(const char *path, struct stat *st) { | |
if(use_root_directory && strcmp(path, "/") == 0) { | |
memset(st, 0, sizeof(struct stat)); | |
st->st_mode = S_IFDIR | 0755; | |
st->st_nlink = 1; | |
st->st_uid = getuid(); | |
st->st_gid = getgid(); | |
st->st_size = 0; | |
st->st_blocks = 0; | |
st->st_ctime = st->st_atime = st->st_mtime = time(NULL); | |
return 0; | |
} | |
const char *real_path = get_real_path(path); | |
if(!real_path) return -ENOENT; | |
#if 0 | |
if(stat(real_path, st) < 0) return -errno; | |
if(!S_ISREG(st->st_mode)) make_regular(-1, st); | |
return 0; | |
#else | |
int fd = open(real_path, O_RDONLY); | |
if(fd == -1) return -errno; | |
struct fuse_file_info fi = { .fh = fd }; | |
int r = regloop_fgetattr(NULL, st, &fi); | |
close(fd); | |
return r; | |
#endif | |
} | |
static int regloop_open(const char *path, struct fuse_file_info *fi) { | |
if(use_root_directory && strcmp(path, "/") == 0) { | |
return (fi->flags & (O_WRONLY | O_RDWR)) ? -EISDIR : 0; | |
} | |
const char *real_path = get_real_path(path); | |
if(!real_path) return -ENOENT; | |
#ifdef O_DIRECT | |
switch(direct_io_mode) { | |
case DIRECT_IO_DENY: | |
if((fi->flags & O_DIRECT) == O_DIRECT) return -EINVAL; | |
break; | |
case DIRECT_IO_IGNORE: | |
fi->flags &= ~O_DIRECT; | |
break; | |
case DIRECT_IO_ALWAYS: | |
fi->flags |= O_DIRECT; | |
break; | |
} | |
#endif | |
fi->fh = open(real_path, fi->flags, 0666); | |
if((int)fi->fh == -1) return -errno; | |
return 0; | |
} | |
static int regloop_release(const char *path, struct fuse_file_info *fi) { | |
close(fi->fh); | |
return 0; | |
} | |
static int regloop_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { | |
int s = pread(fi->fh, buf, size, offset); | |
return s == -1 ? -errno : s; | |
} | |
static int regloop_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { | |
int s = pwrite(fi->fh, buf, size, offset); | |
return s == -1 ? -errno : s; | |
} | |
static int regloop_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { | |
if(!use_root_directory) return -ENOTDIR; | |
if(strcmp(path, "/")) return -ENOENT; | |
//filler(buf, ".", NULL, 0); | |
unsigned int i; | |
for(i=0; i<source_file_count; i++) { | |
filler(buf, source_files[i].new_name, NULL, 0); | |
} | |
return 0; | |
} | |
static struct fuse_operations operations = { | |
.fgetattr = regloop_fgetattr, | |
.getattr = regloop_getattr, | |
.open = regloop_open, | |
.release = regloop_release, | |
.read = regloop_read, | |
.write = regloop_write, | |
.readdir = regloop_readdir, | |
}; | |
static void print_usage(const char *name) { | |
fprintf(stderr, "Usage: %s [-o <fs-options>] <source-file> <mount-point>\n", name); | |
} | |
static char *parse_extended_options(char *o) { | |
char *fuse_opt = NULL; | |
size_t fuse_opt_len = 0; | |
char *comma; | |
do { | |
comma = strchr(o, ','); | |
if(comma) *comma++ = 0; | |
if(strcmp(o, "root_directory") == 0) use_root_directory = 1; | |
else if(strcmp(o, "directory") == 0) use_root_directory = 1; | |
#ifdef O_DIRECT | |
else if(strncmp(o, "direct=", 7) == 0) { | |
char *v = o + 7; | |
if(strcmp(v, "allow") == 0) direct_io_mode = DIRECT_IO_ALLOW; | |
else if(strcmp(v, "deny") == 0) direct_io_mode = DIRECT_IO_DENY; | |
else if(strcmp(v, "ignore") == 0) direct_io_mode = DIRECT_IO_IGNORE; | |
else if(strcmp(v, "always") == 0) direct_io_mode = DIRECT_IO_ALWAYS; | |
else { | |
fprintf(stderr, "Invalid mount option '%s'\n", o); | |
exit(-1); | |
} | |
} | |
#endif | |
else { | |
if(fuse_opt_len) { | |
fuse_opt[fuse_opt_len++] = ','; | |
} | |
size_t i = fuse_opt_len; | |
size_t len = strlen(o); | |
fuse_opt_len += len; | |
fuse_opt = realloc(fuse_opt, fuse_opt_len + 1); | |
if(!fuse_opt) { | |
perror("parse_extended_options: realloc"); | |
exit(1); | |
} | |
memcpy(fuse_opt + i, o, len + 1); | |
} | |
} while(comma && *(o = comma)); | |
return fuse_opt; | |
} | |
int main(int argc, char **argv) { | |
char *fuse_extended_options; | |
int end_of_options = 0; | |
if(argc < 2) { | |
print_usage(argv[0]); | |
return -1; | |
} | |
int fuse_argc = 2; | |
char **fuse_argv = malloc((fuse_argc + 1) * sizeof(char *)); | |
if(!fuse_argv) { | |
perror("malloc"); | |
return 1; | |
} | |
source_files = malloc((argc - 2) * sizeof(struct source_file_info)); | |
if(!source_files) { | |
perror("malloc"); | |
return 1; | |
} | |
int i = 1; | |
while(i < argc) { | |
if(!end_of_options && argv[i][0] == '-' && argv[i][1]) { | |
const char *o = argv[i] + 1; | |
while(*o) switch(*o++) { | |
case 'f': | |
fuse_argc++; | |
fuse_argv = realloc(fuse_argv, (fuse_argc + 1) * sizeof(char *)); | |
if(!fuse_argv) { | |
perror("realloc"); | |
return 1; | |
} | |
fuse_argv[fuse_argc - 2] = "-f"; | |
break; | |
case 'n': | |
break; | |
case 'o': | |
if(++i >= argc) { | |
fprintf(stderr, "%s: Option '-o' requires an argument\n", argv[0]); | |
return -1; | |
} | |
fuse_extended_options = parse_extended_options(argv[i]); | |
if(fuse_extended_options) { | |
fuse_argc += 2; | |
fuse_argv = realloc(fuse_argv, (fuse_argc + 1) * sizeof(char *)); | |
if(!fuse_argv) { | |
perror("realloc"); | |
return 1; | |
} | |
fuse_argv[fuse_argc - 3] = "-o"; | |
fuse_argv[fuse_argc - 2] = fuse_extended_options; | |
} | |
break; | |
case 'v': | |
break; | |
case 'h': | |
print_usage(argv[0]); | |
return 0; | |
case '-': | |
if(*o) { | |
fprintf(stderr, "%s: Invalid option '%s'\n", argv[0], argv[i]); | |
return -1; | |
} else end_of_options = 1; | |
break; | |
default: | |
fprintf(stderr, "%s: Invalid option '-%c'\n", argv[0], o[-1]); | |
print_usage(argv[0]); | |
return -1; | |
} | |
} else { | |
source_files[source_file_count].path = argv[i]; | |
source_file_count++; | |
} | |
i++; | |
} | |
if(source_file_count < 2) { | |
print_usage(argv[0]); | |
return -1; | |
} | |
if(source_file_count > 2 && !use_root_directory) { | |
fprintf(stderr, "%s: Root directory must be enabled with '-o root_directory' to use multiple source files\n", argv[0]); | |
return -1; | |
} | |
if(use_root_directory) { | |
unsigned int i; | |
for(i=0; i<source_file_count; i++) { | |
struct source_file_info *info = source_files + i; | |
char *equal_mark = strrchr(info->path, '='); | |
if(equal_mark) { | |
info->new_name = equal_mark + 1; | |
*equal_mark = 0; | |
} else { | |
char *slash = strrchr(info->path, '/'); | |
info->new_name = slash ? slash + 1 : info->path; | |
} | |
} | |
} | |
fuse_argv[0] = source_files->path; | |
fuse_argv[fuse_argc - 1] = source_files[--source_file_count].path; | |
fuse_argv[fuse_argc] = NULL; | |
if(source_file_count < argc - 2) source_files = realloc(source_files, source_file_count * sizeof(struct source_file_info)); | |
return fuse_main(fuse_argc, fuse_argv, &operations, NULL); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment