Skip to content

Instantly share code, notes, and snippets.

@Low-power
Created Mar 24, 2022
Embed
What would you like to do?
Loop mounting device nodes as regular files.
/* 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