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