blob: 3a5a221689566bdf3e0be9859a5a4bc39fccb3c8 [file] [log] [blame] [raw]
/* Drop client
mount.drop or dropfs
Copyright 2015-2019 Rivoreo
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.
*/
#define _FILE_OFFSET_BITS 64
#define FUSE_USE_VERSION 26
#include <fuse/fuse.h>
#include <sys/wait.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <ctype.h>
#define DROPFS_STAT_MAX_FD 256
static size_t data_written[DROPFS_STAT_MAX_FD];
static char *host;
static uint16_t port = 22;
static char *user;
static char **ssh_options;
static mode_t root_mode = 0755;
static void signal_handler(int sig) {
if(sig != SIGCHLD) return;
while(1) {
pid_t pid = waitpid(-1, NULL, WNOHANG);
if(pid == -1) {
if(errno == EINTR) continue;
if(errno == ECHILD) return;
perror("waitpid");
return;
}
if(!pid) return;
}
}
static void add_arg(char ***argv, char *a) {
unsigned int count = 1;
char **p = *argv;
if(p) while(*p++) count++;
p = realloc(*argv, sizeof(char *) * (count + 1));
if(!p) {
perror("realloc");
exit(1);
}
p[count - 1] = a;
p[count] = NULL;
*argv = p;
}
static pid_t start_ssh_process(const char *host, uint16_t port, const char *user, const char *command, char **extra_argv, int *pipe_write_fd, int *pipe_read_fd) {
int pipe_fds_0[2];
int pipe_fds_1[2];
if(pipe(pipe_fds_0) < 0) {
perror("pipe");
goto failed;
}
if(pipe(pipe_fds_1) < 0) {
perror("pipe");
close(pipe_fds_0[0]);
close(pipe_fds_0[1]);
goto failed;
}
pid_t pid = fork();
if(pid < 0) {
perror("fork");
close(pipe_fds_0[0]);
close(pipe_fds_0[1]);
close(pipe_fds_1[0]);
close(pipe_fds_1[1]);
goto failed;
}
if(pid) {
close(pipe_fds_0[0]);
close(pipe_fds_1[1]);
*pipe_write_fd = pipe_fds_0[1];
*pipe_read_fd = pipe_fds_1[0];
return pid;
} else {
unsigned int i = 0;
unsigned int count = user ? 11 : 9;
if(extra_argv) {
char **v = extra_argv;
while(*v++) count++;
}
unsigned int count_without_command = count;
if(command) count += 2;
char port_number_s[6];
snprintf(port_number_s, sizeof port_number_s, "%hu", (unsigned short int)port);
char *full_argv[count + 1];
full_argv[i++] = "ssh";
full_argv[i++] = "-o";
full_argv[i++] = "ServerAliveInterval=120";
full_argv[i++] = "-o";
full_argv[i++] = "PasswordAuthentication=no";
full_argv[i++] = (char *)host;
full_argv[i++] = "-p";
full_argv[i++] = port_number_s;
full_argv[i++] = "-T";
if(user) {
full_argv[i++] = "-l";
full_argv[i++] = (char *)user;
}
if(extra_argv && count_without_command > i) memcpy(full_argv + i, extra_argv, sizeof(char *) * (count_without_command - i));
if(command) {
full_argv[count - 2] = "--";
full_argv[count - 1] = (char *)command;
}
full_argv[count] = NULL;
close(pipe_fds_0[1]);
close(pipe_fds_1[0]);
close(0);
close(1);
if(dup2(pipe_fds_0[0], 0) == -1) {
perror("dup2");
_exit(1);
}
if(dup2(pipe_fds_1[1], 1) == -1) {
perror("dup2");
_exit(1);
}
execvp("ssh", full_argv);
perror("ssh");
_exit(1);
}
failed:
*pipe_write_fd = -1;
*pipe_read_fd = -1;
return -1;
}
#if 1
static int drop_fgetattr(const char *path, struct stat *st, struct fuse_file_info *fi) {
fprintf(stderr, "function: drop_fgetattr(%p<%s>, %p)\n", path, path, st);
fprintf(stderr, "drop_fgetattr: fi->fh = %llu\n", fi->fh);
if(fi->fh >= DROPFS_STAT_MAX_FD) return -EMFILE;
memset(st, 0, sizeof(struct stat));
st->st_mode = S_IFREG | 0644;
st->st_nlink = 1;
st->st_uid = getuid();
st->st_gid = getgid();
st->st_size = data_written[fi->fh];
st->st_blocks = 0;
st->st_atime = st->st_mtime = time(NULL);
st->st_ctime = 0;
return 0;
}
#endif
static int drop_getattr(const char *path, struct stat *st) {
fprintf(stderr, "function: drop_getattr(%p<%s>, %p)\n", path, path, st);
#if 1
if(strcmp(path, "/")) return -ENOENT;
st->st_mode = S_IFDIR | root_mode;
#else
st->st_mode = strcmp(path, "/") == 0 ? S_IFDIR | root_mode : S_IFREG | 0644;
#endif
st->st_nlink = 1;
st->st_uid = getuid();
st->st_gid = getgid();
st->st_size = 0;
st->st_blocks = 0;
st->st_atime = st->st_mtime = time(NULL);
st->st_ctime = 0;
return 0;
}
static int drop_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) {
if(strcmp(path, "/")) return -ENOENT;
filler(buf, ".", NULL, 0);
return 0;
}
static int drop_open(const char *path, struct fuse_file_info *fi) {
fprintf(stderr, "function: drop_open(%p<%s>, %p)\n", path, path, fi);
fprintf(stderr, "%s: fi->flags = 0x%x\n", __func__, fi->flags);
if(!(fi->flags & O_CREAT)) return -ENOENT;
if(fi->flags & O_APPEND) return -EACCES;
if(fi->flags & O_RDWR) return -EACCES;
if(!(fi->flags & O_WRONLY)) return -ENOENT;
if(fi->flags & O_DIRECTORY) return -ENOENT;
if(*path == '/') path++;
if(*path == '-') return -EINVAL;
if(strchr(path, '/')) return -ENOENT;
int write_fd, read_fd;
pid_t pid = start_ssh_process(host, port, user, path, ssh_options, &write_fd, &read_fd);
if(pid == -1) return -errno;
close(read_fd);
fi->nonseekable = 1;
fi->fh = write_fd;
if(write_fd < DROPFS_STAT_MAX_FD) data_written[write_fd] = 0;
return 0;
}
static int drop_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
fprintf(stderr, "function: drop_create(%p<%s>, 0%o, %p)\n", path, path, mode, fi);
fprintf(stderr, "%s: fi->flags = 0x%x\n", __func__, fi->flags);
if(!(fi->flags & O_WRONLY)) return -ENOENT;
if(*path == '/') path++;
//if(*path == '-') return -EINVAL;
if(strchr(path, '/')) return -ENOENT;
size_t len = strlen(path);
char command[8 + len + 1];
sprintf(command, "-m %ho %s", (unsigned short int)(mode & 07777), path);
int write_fd, read_fd;
pid_t pid = start_ssh_process(host, port, user, command, ssh_options, &write_fd, &read_fd);
if(pid == -1) return -errno;
close(read_fd);
fi->nonseekable = 1;
fi->fh = write_fd;
return 0;
}
static int drop_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
return -EBADF;
}
static int drop_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
int s;
do {
s = write(fi->fh, buf, size);
} while(s < 0 && errno == EINTR);
if(fi->fh < DROPFS_STAT_MAX_FD) data_written[fi->fh] += s;
int e = errno;
#ifdef ESTALE
if(e == EPIPE) e = ESTALE;
#endif
return s < 0 ? -e : s;
}
static int drop_release(const char *path, struct fuse_file_info *fi) {
//return close(fi->fh) < 0 ? -errno : 0;
close(fi->fh);
if(fi->fh < DROPFS_STAT_MAX_FD) data_written[fi->fh] = 0;
return 0;
}
static int drop_unlink(const char *path) {
return strcmp(path, "/") == 0 ? -EPERM : -ENOENT;
}
static struct fuse_operations operations = {
.fgetattr = drop_fgetattr,
.getattr = drop_getattr,
.readdir = drop_readdir,
.open = drop_open,
.create = drop_create,
.release = drop_release,
.read = drop_read,
.write = drop_write,
.unlink = drop_unlink,
};
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(strncmp(o, "port=", 5) == 0) port = atoi(o + 5);
else if(strncmp(o, "username=", 9) == 0) user = o + 9;
else if(strncmp(o, "mode=", 5) == 0) root_mode = strtol(o + 5, NULL, 8);
else if(isupper(*o) && strchr(o, '=')) {
add_arg(&ssh_options, "-o");
add_arg(&ssh_options, o);
} 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;
}
static void print_usage(const char *name) {
fprintf(stderr, "Usage: %s [-o <fs-or-ssh-options>] [-nv] <host> <mount-point>\n",
name);
}
int main(int argc, char **argv) {
char *fuse_extended_options;
char *mount_point = NULL;
int end_of_options = 0;
int fuse_argc = 2;
char **fuse_argv = malloc((fuse_argc + 1) * sizeof(char *));
if(!fuse_argv) {
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 if(!host) {
host = argv[i];
fuse_argv[0] = host;
} else if(!mount_point) {
mount_point = argv[i];
} else {
print_usage(argv[0]);
return -1;
}
i++;
}
if(!host || !mount_point) {
print_usage(argv[0]);
return -1;
}
struct sigaction act = { .sa_handler = SIG_IGN };
if(sigaction(SIGPIPE, &act, NULL) < 0) {
perror("sigaction: SIGPIPE");
return 1;
}
act.sa_handler = signal_handler;
if(sigaction(SIGCHLD, &act, NULL) < 0) {
perror("sigaction: SIGCHLD");
return 1;
}
fuse_argv[fuse_argc - 1] = mount_point;
fuse_argv[fuse_argc] = NULL;
return fuse_main(fuse_argc, fuse_argv, &operations, NULL);
}