diff --git a/revokefs/Makefile.am.inc b/revokefs/Makefile.am.inc index d9d1b041..d1be5e96 100644 --- a/revokefs/Makefile.am.inc +++ b/revokefs/Makefile.am.inc @@ -20,7 +20,7 @@ libexec_PROGRAMS += revokefs-fuse -revokefs_fuse_SOURCES = revokefs/main.c +revokefs_fuse_SOURCES = revokefs/main.c revokefs/writer.c revokefs/writer.h revokefs_fuse_CFLAGS = $(BASE_CFLAGS) -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 $(FUSE_CFLAGS) -I$(srcdir)/libglnx revokefs_fuse_LDADD = libglnx.la $(BASE_LIBS) $(FUSE_LIBS) diff --git a/revokefs/main.c b/revokefs/main.c index 79a0d97c..401330d7 100644 --- a/revokefs/main.c +++ b/revokefs/main.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2015,2016 Colin Walters + * Copyright (C) 2018 Alexander Larsson * * SPDX-License-Identifier: LGPL-2.0+ * @@ -24,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -37,10 +39,16 @@ #include +#include "writer.h" #include "libglnx.h" +/* fh >= REMOTE_FD_OFFSET means the fd is in the writer side, otherwise it is local */ +#define REMOTE_FD_OFFSET ((guint64)G_MAXUINT32) + // Global to store our read-write path +static char *base_path = NULL; static int basefd = -1; +static int writer_socket = -1; static inline const char * ENSURE_RELPATH (const char *path) @@ -138,38 +146,34 @@ static int callback_mkdir (const char *path, mode_t mode) { path = ENSURE_RELPATH (path); - if (mkdirat (basefd, path, mode) == -1) - return -errno; - return 0; + return request_mkdir (writer_socket, path, mode); } static int callback_unlink (const char *path) { path = ENSURE_RELPATH (path); - if (unlinkat (basefd, path, 0) == -1) - return -errno; - return 0; + return request_unlink (writer_socket, path); } static int callback_rmdir (const char *path) { path = ENSURE_RELPATH (path); - if (unlinkat (basefd, path, AT_REMOVEDIR) == -1) - return -errno; - return 0; + return request_rmdir (writer_socket, path); } static int callback_symlink (const char *from, const char *to) { struct stat stbuf; + int res; to = ENSURE_RELPATH (to); - if (symlinkat (from, basefd, to) == -1) - return -errno; + res = request_symlink (writer_socket, from, to); + if (res < 0) + return res; if (fstatat (basefd, to, &stbuf, AT_SYMLINK_NOFOLLOW) == -1) { @@ -185,9 +189,8 @@ callback_rename (const char *from, const char *to) { from = ENSURE_RELPATH (from); to = ENSURE_RELPATH (to); - if (renameat (basefd, from, basefd, to) == -1) - return -errno; - return 0; + + return request_rename (writer_socket, from, to); } static int @@ -195,56 +198,37 @@ callback_link (const char *from, const char *to) { from = ENSURE_RELPATH (from); to = ENSURE_RELPATH (to); - if (linkat (basefd, from, basefd, to, 0) == -1) - return -errno; - return 0; + + return request_link (writer_socket, from, to); } static int callback_chmod (const char *path, mode_t mode) { - /* Note we can't use AT_SYMLINK_NOFOLLOW yet; - * https://marc.info/?l=linux-kernel&m=148830147803162&w=2 - * https://marc.info/?l=linux-fsdevel&m=149193779929561&w=2 - */ - if (fchmodat (basefd, path, mode, 0) != 0) - return -errno; - return 0; + path = ENSURE_RELPATH (path); + return request_chmod (writer_socket, path, mode); } static int callback_chown (const char *path, uid_t uid, gid_t gid) { - if (fchownat (basefd, path, uid, gid, AT_SYMLINK_NOFOLLOW) != 0) - return -errno; - return 0; + path = ENSURE_RELPATH (path); + return request_chown (writer_socket, path, uid, gid); } static int callback_truncate (const char *path, off_t size) { - glnx_autofd int fd = openat (basefd, path, O_NOFOLLOW|O_WRONLY); - if (fd == -1) - return -errno; - - if (ftruncate (fd, size) == -1) - return -errno; - - return 0; + path = ENSURE_RELPATH (path); + return request_truncate (writer_socket, path, size); } static int callback_utimens (const char *path, const struct timespec tv[2]) { - /* This one isn't write-verified, we support changing times - * even for hardlinked files. - */ path = ENSURE_RELPATH (path); - if (utimensat (basefd, path, tv, AT_SYMLINK_NOFOLLOW) == -1) - return -errno; - - return 0; + return request_utimens (writer_socket, path, tv); } static int @@ -260,28 +244,20 @@ do_open (const char *path, mode_t mode, struct fuse_file_info *finfo) fd = openat (basefd, path, finfo->flags, mode); if (fd == -1) return -errno; + + finfo->fh = fd; } else { /* Write */ - /* We need to specially handle O_TRUNC */ - fd = openat (basefd, path, finfo->flags & ~O_TRUNC, mode); - if (fd == -1) - return -errno; + fd = request_open (writer_socket, path, mode, finfo->flags); + if (fd < 0) + return fd; - if (finfo->flags & O_TRUNC) - { - if (ftruncate (fd, 0) == -1) - { - (void) close (fd); - return -errno; - } - } + finfo->fh = fd + REMOTE_FD_OFFSET; } - finfo->fh = fd; - return 0; } @@ -297,48 +273,22 @@ callback_create(const char *path, mode_t mode, struct fuse_file_info *finfo) return do_open (path, mode, finfo); } -static int -callback_read_buf (const char *path, struct fuse_bufvec **bufp, - size_t size, off_t offset, struct fuse_file_info *finfo) -{ - struct fuse_bufvec *src; - - src = malloc (sizeof (struct fuse_bufvec)); - if (src == NULL) - return -ENOMEM; - - *src = FUSE_BUFVEC_INIT (size); - - src->buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; - src->buf[0].fd = finfo->fh; - src->buf[0].pos = offset; - *bufp = src; - - return 0; -} - static int callback_read (const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *finfo) { int r; - r = pread (finfo->fh, buf, size, offset); - if (r == -1) - return -errno; - return r; -} - -static int -callback_write_buf (const char *path, struct fuse_bufvec *buf, off_t offset, - struct fuse_file_info *finfo) -{ - struct fuse_bufvec dst = FUSE_BUFVEC_INIT (fuse_buf_size (buf)); - - dst.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; - dst.buf[0].fd = finfo->fh; - dst.buf[0].pos = offset; - - return fuse_buf_copy (&dst, buf, FUSE_BUF_SPLICE_NONBLOCK); + if (finfo->fh >= REMOTE_FD_OFFSET) + { + return request_read (writer_socket, finfo->fh - REMOTE_FD_OFFSET, buf, size, offset); + } + else + { + r = pread (finfo->fh, buf, size, offset); + if (r == -1) + return -errno; + return r; + } } static int @@ -346,10 +296,18 @@ callback_write (const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *finfo) { int r; - r = pwrite (finfo->fh, buf, size, offset); - if (r == -1) - return -errno; - return r; + + if (finfo->fh >= REMOTE_FD_OFFSET) + { + return request_write (writer_socket, finfo->fh - REMOTE_FD_OFFSET, buf, size, offset); + } + else + { + r = pwrite (finfo->fh, buf, size, offset); + if (r == -1) + return -errno; + return r; + } } static int @@ -363,16 +321,30 @@ callback_statfs (const char *path, struct statvfs *st_buf) static int callback_release (const char *path, struct fuse_file_info *finfo) { - (void) close (finfo->fh); - return 0; + if (finfo->fh >= REMOTE_FD_OFFSET) + { + return request_close (writer_socket, finfo->fh - REMOTE_FD_OFFSET); + } + else + { + (void) close (finfo->fh); + return 0; + } } static int callback_fsync (const char *path, int crap, struct fuse_file_info *finfo) { - if (fsync (finfo->fh) == -1) - return -errno; - return 0; + if (finfo->fh >= REMOTE_FD_OFFSET) + { + return request_fsync (writer_socket, finfo->fh - REMOTE_FD_OFFSET); + } + else + { + if (fsync (finfo->fh) == -1) + return -errno; + return 0; + } } static int @@ -440,9 +412,7 @@ struct fuse_operations callback_oper = { .utimens = callback_utimens, .create = callback_create, .open = callback_open, - .read_buf = callback_read_buf, .read = callback_read, - .write_buf = callback_write_buf, .write = callback_write, .statfs = callback_statfs, .release = callback_release, @@ -458,7 +428,6 @@ struct fuse_operations callback_oper = { enum { KEY_HELP, - KEY_VERSION, }; static void @@ -467,34 +436,34 @@ usage (const char *progname) fprintf (stdout, "usage: %s basepath mountpoint [options]\n" "\n" - " Makes basepath visible at mountpoint such that files are read-only, directories are writable\n" + " Makes basepath visible at mountpoint such that files are writeable only through\n" + " fd passed in the --socket argument.\n" "\n" "general options:\n" " -o opt,[opt...] mount options\n" " -h --help print help\n" + " --socket=fd Pass in the socket fd\n" + " --backend Run the backend instead of fuse\n" "\n", progname); } static int -rofs_parse_opt (void *data, const char *arg, int key, - struct fuse_args *outargs) +revokefs_opt_proc (void *data, + const char *arg, + int key, + struct fuse_args *outargs) { (void) data; switch (key) { case FUSE_OPT_KEY_NONOPT: - if (basefd == -1) + if (base_path == NULL) { - basefd = openat (AT_FDCWD, arg, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); - if (basefd == -1) - err (1, "opening rootfs %s", arg); + base_path = g_strdup (arg); return 0; } - else - { - return 1; - } + return 1; case FUSE_OPT_KEY_OPT: return 1; case KEY_HELP: @@ -507,11 +476,19 @@ rofs_parse_opt (void *data, const char *arg, int key, return 1; } -static struct fuse_opt rofs_opts[] = { +struct revokefs_config { + int socket_fd; + int backend; +}; + +#define REVOKEFS_OPT(t, p, v) { t, offsetof(struct revokefs_config, p), v } + +static struct fuse_opt revokefs_opts[] = { + REVOKEFS_OPT ("--socket=%i", socket_fd, -1), + REVOKEFS_OPT ("--backend", backend, 1), + FUSE_OPT_KEY ("-h", KEY_HELP), FUSE_OPT_KEY ("--help", KEY_HELP), - FUSE_OPT_KEY ("-V", KEY_VERSION), - FUSE_OPT_KEY ("--version", KEY_VERSION), FUSE_OPT_END }; @@ -520,21 +497,77 @@ main (int argc, char *argv[]) { struct fuse_args args = FUSE_ARGS_INIT (argc, argv); int res; + struct revokefs_config conf = { -1 }; - res = fuse_opt_parse (&args, &basefd, rofs_opts, rofs_parse_opt); + res = fuse_opt_parse (&args, &conf, revokefs_opts, revokefs_opt_proc); if (res != 0) { fprintf (stderr, "Invalid arguments\n"); fprintf (stderr, "see `%s -h' for usage\n", argv[0]); exit (EXIT_FAILURE); } - if (basefd == -1) + + if (base_path == NULL) { fprintf (stderr, "Missing basepath\n"); fprintf (stderr, "see `%s -h' for usage\n", argv[0]); exit (EXIT_FAILURE); } + basefd = openat (AT_FDCWD, base_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); + if (basefd == -1) + { + perror ("opening basepath: "); + exit (EXIT_FAILURE); + } + + if (conf.backend) + { + if (conf.socket_fd == -1) + { + fprintf (stderr, "No --socket passed, required for --backend\n"); + exit (EXIT_FAILURE); + } + + do_writer (basefd, conf.socket_fd); + exit (0); + } + + if (conf.socket_fd != -1) + { + writer_socket = conf.socket_fd; + } + else + { + int sockets[2]; + pid_t pid; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) + { + perror ("Failed to create socket pair"); + exit (EXIT_FAILURE); + } + + pid = fork (); + if (pid == -1) + { + perror ("Failed to fork writer"); + exit (EXIT_FAILURE); + } + + if (pid == 0) + { + /* writer process */ + close (sockets[0]); + do_writer (basefd, sockets[1]); + exit (0); + } + + /* Main process */ + close (sockets[1]); + writer_socket = sockets[0]; + } + fuse_main (args.argc, args.argv, &callback_oper, NULL); return 0; diff --git a/revokefs/writer.c b/revokefs/writer.c new file mode 100644 index 00000000..fc31bae7 --- /dev/null +++ b/revokefs/writer.c @@ -0,0 +1,875 @@ +/* + * Copyright (C) 2018 Alexander Larsson + * + * SPDX-License-Identifier: LGPL-2.0+ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "writer.h" +#include "libglnx.h" + +static int basefd = -1; + +static GHashTable *outstanding_fds; + +static GMutex mutex; + +static ssize_t +do_request (int writer_socket, + RevokefsRequest *request, + const guchar *data, + size_t data_size, + const guchar *data2, + size_t data2_size, + RevokefsResponse *response, + guchar *response_data, + size_t response_data_size) +{ + size_t request_size; + size_t response_max_size; + ssize_t written_size, read_size; + struct iovec write_vecs[2] = {}; + int n_write_vecs = 0; + struct iovec read_vecs[2] = {}; + int n_read_vecs = 0; + g_autoptr(GMutexLocker) locker = NULL; + + request_size = sizeof (RevokefsRequest); + write_vecs[n_write_vecs].iov_base = (char *)request; + write_vecs[n_write_vecs++].iov_len = request_size; + + if (data) + { + write_vecs[n_write_vecs].iov_base = (char *)data; + write_vecs[n_write_vecs++].iov_len = data_size; + request_size += data_size; + } + + if (data2) + { + write_vecs[n_write_vecs].iov_base = (char *)data2; + write_vecs[n_write_vecs++].iov_len = data2_size; + request_size += data2_size; + } + + locker = g_mutex_locker_new (&mutex); + written_size = TEMP_FAILURE_RETRY (writev (writer_socket, write_vecs, n_write_vecs)); + if (written_size == -1) + { + g_printerr ("Write to socket returned error %d\n", errno); + return -1; + } + if (written_size != request_size) + { + g_printerr ("Partial Write to socket\n"); + return -1; + } + + response_max_size = sizeof (RevokefsResponse); + read_vecs[n_read_vecs].iov_base = (char *)response; + read_vecs[n_read_vecs++].iov_len = response_max_size; + + if (response_data) + { + read_vecs[n_read_vecs].iov_base = response_data; + read_vecs[n_read_vecs++].iov_len = response_data_size; + response_max_size += response_data_size; + } + + read_size = TEMP_FAILURE_RETRY (readv (writer_socket, read_vecs, n_read_vecs)); + if (read_size == -1) + { + g_printerr ("Read from socket returned error %d\n", errno); + return -1; + } + + if (read_size < sizeof (RevokefsResponse)) + { + g_printerr ("Invalid read size %zd\n", read_size); + return -1; + } + + return read_size - sizeof (RevokefsResponse); +} + +static int +request_path_i64_i64 (int writer_socket, RevokefsOps op, const char *path, guint64 arg1, guint64 arg2) +{ + RevokefsRequest request = { op }; + RevokefsResponse response; + size_t path_len = strlen (path); + ssize_t response_data_len; + + if (path_len > MAX_DATA_SIZE) + return -ENAMETOOLONG; + + request.arg1 = arg1; + request.arg2 = arg2; + + response_data_len = do_request (writer_socket, &request, path, path_len, NULL, 0, + &response, NULL, 0); + if (response_data_len != 0) + return -EIO; + + return response.result; +} + +static int +request_path_int_int (int writer_socket, RevokefsOps op, const char *path, int arg1, int arg2) +{ + return request_path_i64_i64 (writer_socket, op, path, arg1, arg2); +} + +static int +request_path_int (int writer_socket, RevokefsOps op, const char *path, int arg1) +{ + return request_path_int_int (writer_socket, op, path, arg1, 0); +} + +static int +request_path (int writer_socket, RevokefsOps op, const char *path) +{ + return request_path_int_int (writer_socket, op, path, 0, 0); +} + +static int +request_path_data (int writer_socket, RevokefsOps op, const char *path, + const char *data, size_t data_len) +{ + RevokefsRequest request = { op }; + RevokefsResponse response; + size_t path_len = strlen (path); + size_t total_len = path_len + data_len; + ssize_t response_data_len; + + if (total_len > MAX_DATA_SIZE) + return -ENAMETOOLONG; + + request.arg1 = strlen(path); + + response_data_len = do_request (writer_socket, &request, path, path_len, data, data_len, + &response, NULL, 0); + if (response_data_len != 0) + return -EIO; + + return response.result; +} + +static int +request_path_path (int writer_socket, RevokefsOps op, const char *path1, const char *path2) +{ + return request_path_data (writer_socket, op, path1, path2, strlen(path2)); +} + +static gboolean +validate_path (char *path) +{ + char *end_segment; + + /* No absolute or empty paths */ + if (*path == '/' || *path == 0) + return FALSE; + + while (*path != 0) + { + end_segment = strchr (path, '/'); + if (end_segment == NULL) + end_segment = path + strlen (path); + + if (strncmp (path, "..", 2) == 0) + return FALSE; + + path = end_segment; + while (*path == '/') + path++; + } + + return TRUE; +} + +static char * +get_valid_path (guchar *data, size_t len) +{ + char *path = g_strndup (data, len); + + if (!validate_path (path)) + { + g_printerr ("Invalid path argument %s\n", path); + exit (1); + } + + return path; +} + +static int +mask_mode (int mode) +{ + /* mask setuid, setgid and world-writable permissions bits */ + return mode & ~S_ISUID & ~S_ISGID & ~(S_IWGRP | S_IWOTH); +} + +static void +get_any_path_and_valid_path (RevokefsRequest *request, + gsize data_size, + char **any_path1, + char **valid_path2) +{ + if (request->arg1 >= data_size) + { + g_printerr ("Invalid path1 size\n"); + exit (1); + } + + *any_path1 = g_strndup (request->data, request->arg1); + *valid_path2 = get_valid_path (request->data + request->arg1, data_size - request->arg1); +} + +static void +get_valid_2path (RevokefsRequest *request, + gsize data_size, + char **path1, + char **path2) +{ + if (request->arg1 >= data_size) + { + g_printerr ("Invalid path1 size\n"); + exit (1); + } + + *path1 = get_valid_path (request->data, request->arg1); + *path2 = get_valid_path (request->data + request->arg1, data_size - request->arg1); +} + +static ssize_t +handle_mkdir (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *path = get_valid_path (request->data, data_size); + int mode = request->arg1; + + if (mkdirat (basefd, path, mask_mode (mode)) == -1) + response->result = -errno; + else + response->result = 0; + + return 0; +} + +int +request_mkdir (int writer_socket, const char *path, mode_t mode) +{ + return request_path_int (writer_socket, REVOKE_FS_MKDIR, path, mode); +} + +static ssize_t +handle_rmdir (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *path = get_valid_path (request->data, data_size); + + if (unlinkat (basefd, path, AT_REMOVEDIR) == -1) + response->result = -errno; + else + response->result = 0; + + return 0; +} + +int +request_rmdir (int writer_socket, const char *path) +{ + return request_path (writer_socket, REVOKE_FS_RMDIR, path); +} + +static ssize_t +handle_unlink (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *path = get_valid_path (request->data, data_size); + + if (unlinkat (basefd, path, 0) == -1) + response->result = -errno; + else + response->result = 0; + + return 0; +} + +int +request_unlink (int writer_socket, const char *path) +{ + return request_path (writer_socket, REVOKE_FS_UNLINK, path); +} + +static ssize_t +handle_symlink (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *from = NULL; + g_autofree char *to = NULL; + + /* from doesn't have to be a valid path, it can be absolute or whatever */ + get_any_path_and_valid_path (request, data_size, &from, &to); + + if (symlinkat (from, basefd, to) == -1) + response->result = -errno; + else + response->result = 0; + + return 0; +} + +int +request_symlink (int writer_socket, const char *from, const char *to) +{ + return request_path_path (writer_socket, REVOKE_FS_SYMLINK, from, to); +} + +static ssize_t +handle_link (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *from = NULL; + g_autofree char *to = NULL; + + get_valid_2path (request, data_size, &from, &to); + + if (linkat (basefd, from, basefd, to, 0) == -1) + response->result = -errno; + else + response->result = 0; + + return 0; +} + +int +request_link (int writer_socket, const char *from, const char *to) +{ + return request_path_path (writer_socket, REVOKE_FS_LINK, from, to); +} + +static ssize_t +handle_rename (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *from = NULL; + g_autofree char *to = NULL; + + get_valid_2path (request, data_size, &from, &to); + + if (renameat (basefd, from, basefd, to) == -1) + response->result = -errno; + else + response->result = 0; + + return 0; +} + +int +request_rename (int writer_socket, const char *from, const char *to) +{ + return request_path_path (writer_socket, REVOKE_FS_RENAME, from, to); +} + +static ssize_t +handle_chmod (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *path = get_valid_path (request->data, data_size); + int mode = request->arg1; + + /* Note we can't use AT_SYMLINK_NOFOLLOW yet; + * https://marc.info/?l=linux-kernel&m=148830147803162&w=2 + * https://marc.info/?l=linux-fsdevel&m=149193779929561&w=2 + */ + if (fchmodat (basefd, path, mask_mode (mode), 0) != 0) + response->result = -errno; + else + response->result = 0; + + return 0; +} + +int +request_chmod(int writer_socket, const char *path, mode_t mode) +{ + return request_path_int (writer_socket, REVOKE_FS_CHMOD, path, mode); +} + +static ssize_t +handle_chown (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *path = get_valid_path (request->data, data_size); + uid_t uid = request->arg1; + gid_t gid = request->arg2; + + if (fchownat (basefd, path, uid, gid, AT_SYMLINK_NOFOLLOW) != 0) + response->result = -errno; + else + response->result = 0; + + return 0; +} + +int +request_chown(int writer_socket, const char *path, uid_t uid, gid_t gid) +{ + return request_path_int_int (writer_socket, REVOKE_FS_CHOWN, path, uid, gid); +} + +static ssize_t +handle_truncate (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *path = get_valid_path (request->data, data_size); + off_t size = request->arg1; + + glnx_autofd int fd = openat (basefd, path, O_NOFOLLOW|O_WRONLY); + if (fd == -1) + response->result = -errno; + else + { + if (ftruncate (fd, size) == -1) + response->result = -errno; + else + response->result = 0; + } + + return 0; +} + +int +request_truncate (int writer_socket, const char *path, off_t size) +{ + return request_path_i64_i64 (writer_socket, REVOKE_FS_TRUNCATE, path, size, 0); +} + +static ssize_t +handle_utimens (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *path = NULL; + struct timespec *tv; + + if (request->arg1 + sizeof (struct timespec) * 2 != data_size) + { + g_printerr ("Invalid data size\n"); + exit (1); + } + + path = get_valid_path (request->data, request->arg1); + tv = (struct timespec *)(request->data + request->arg1); + + if (utimensat (basefd, path, tv, AT_SYMLINK_NOFOLLOW) == -1) + response->result = -errno; + else + response->result = 0; + + return 0; +} + +int +request_utimens (int writer_socket, const char *path, const struct timespec tv[2]) +{ + return request_path_data (writer_socket, REVOKE_FS_UTIMENS, path, + (guchar *)tv, sizeof (struct timespec) * 2); +} + +static ssize_t +handle_open (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *path = get_valid_path (request->data, data_size); + int mode = request->arg1; + int flags = request->arg2; + int fd; + + + /* We need to specially handle O_TRUNC. Also, Fuse should have already + * resolved symlinks, but use O_NOFOLLOW to be safe to avoid following + * symlinks to some other filesystem. */ + fd = openat (basefd, path, (flags & ~O_TRUNC) | O_NOFOLLOW, mask_mode (mode)); + if (fd == -1) + response->result = -errno; + else + { + response->result = 0; + if (flags & O_TRUNC) + { + if (ftruncate (fd, 0) == -1) + response->result = -errno; + } + + if (response->result == 0) + { + g_hash_table_insert (outstanding_fds, GUINT_TO_POINTER(fd), GUINT_TO_POINTER(1)); + response->result = fd; + } + else + (void) close (fd); + } + + return 0; +} + +int +request_open (int writer_socket, const char *path, mode_t mode, int flags) +{ + return request_path_int_int (writer_socket, REVOKE_FS_OPEN, path, mode, flags); +} + +static ssize_t +handle_read (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + int r; + int fd = request->arg1; + size_t size = request->arg2; + off_t offset = request->arg3; + + if (size > MAX_DATA_SIZE) + size = MAX_DATA_SIZE; + + if (g_hash_table_lookup (outstanding_fds, GUINT_TO_POINTER(fd)) == NULL) + { + response->result = -EBADFD; + return 0; + } + + r = pread (fd, response->data, size, offset); + if (r == -1) + { + response->result = -errno; + return 0; + } + else + { + response->result = r; + return r; + } +} + +int +request_read (int writer_socket, int fd, char *buf, size_t size, off_t offset) +{ + RevokefsRequest request = { REVOKE_FS_READ }; + RevokefsResponse response; + ssize_t response_data_len; + + request.arg1 = fd; + request.arg2 = size; + request.arg3 = offset; + + response_data_len = do_request (writer_socket, &request, NULL, 0, NULL, 0, + &response, buf, size); + if (response_data_len < 0) + return -EIO; + + return response.result; +} + +static ssize_t +handle_write (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + int r; + int fd = request->arg1; + off_t offset = request->arg2; + + if (g_hash_table_lookup (outstanding_fds, GUINT_TO_POINTER(fd)) == NULL) + { + response->result = -EBADFD; + return 0; + } + + r = pwrite (fd, request->data, data_size, offset); + if (r == -1) + response->result = -errno; + else + response->result = r; + + return 0; +} + +int +request_write (int writer_socket, int fd, const char *buf, size_t size, off_t offset) +{ + RevokefsRequest request = { REVOKE_FS_WRITE }; + RevokefsResponse response; + ssize_t response_data_len; + + if (size > MAX_DATA_SIZE) + size = MAX_DATA_SIZE; + + request.arg1 = fd; + request.arg2 = offset; + + response_data_len = do_request (writer_socket, &request, buf, size, NULL, 0, + &response, NULL, 0); + if (response_data_len < 0) + return -EIO; + + return response.result; +} + +static ssize_t +handle_fsync (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + int r; + int fd = request->arg1; + + if (g_hash_table_lookup (outstanding_fds, GUINT_TO_POINTER(fd)) == NULL) + { + response->result = -EBADFD; + return 0; + } + + r = fsync (fd); + if (r == -1) + response->result = -errno; + else + response->result = r; + + return 0; +} + +int +request_fsync (int writer_socket, int fd) +{ + RevokefsRequest request = { REVOKE_FS_FSYNC }; + RevokefsResponse response; + ssize_t response_data_len; + + request.arg1 = fd; + + response_data_len = do_request (writer_socket, &request, NULL, 0, NULL, 0, + &response, NULL, 0); + if (response_data_len < 0) + return -EIO; + + return response.result; +} + +static ssize_t +handle_close (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + int fd = request->arg1; + + if (!g_hash_table_remove (outstanding_fds, GUINT_TO_POINTER(fd))) + { + response->result = -EBADFD; + return 0; + } + + close (fd); + response->result = 0; + return 0; +} + +int +request_close (int writer_socket, int fd) +{ + RevokefsRequest request = { REVOKE_FS_CLOSE }; + RevokefsResponse response; + ssize_t response_data_len; + + request.arg1 = fd; + response_data_len = do_request (writer_socket, &request, NULL, 0, NULL, 0, + &response, NULL, 0); + if (response_data_len < 0) + return -EIO; + + return response.result; +} + +static ssize_t +handle_access (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) +{ + g_autofree char *path = get_valid_path (request->data, data_size); + int mode = request->arg1; + + /* Apparently at least GNU coreutils rm calls `faccessat(W_OK)` + * before trying to do an unlink. So...we'll just lie about + * writable access here. + */ + if (faccessat (basefd, path, mode, AT_SYMLINK_NOFOLLOW) == -1) + response->result = -errno; + else + response->result = 0; + + return 0; +} + +int +request_access (int writer_socket, const char *path, int mode) +{ + return request_path_int (writer_socket, REVOKE_FS_ACCESS, path, mode); +} + +void +do_writer (int basefd_arg, + int fuse_socket) +{ + guchar request_buffer[MAX_REQUEST_SIZE]; + RevokefsRequest *request = (RevokefsRequest *)&request_buffer; + guchar response_buffer[MAX_RESPONSE_SIZE]; + RevokefsResponse *response = (RevokefsResponse *)&response_buffer; + + basefd = basefd_arg; + outstanding_fds = g_hash_table_new (g_direct_hash, g_direct_equal); + + while (1) + { + ssize_t data_size, size; + ssize_t response_data_size, response_size, written_size; + + size = TEMP_FAILURE_RETRY (read (fuse_socket, request_buffer, sizeof (request_buffer))); + if (size == -1) + { + perror ("Got error reading from fuse socket: "); + exit (1); + } + + if (size == 0) + { + /* Fuse filesystem finished */ + exit (1); + } + + if (size < sizeof (RevokefsRequest)) + { + g_printerr ("Invalid request size %zd", size); + exit (1); + } + + data_size = size - sizeof (RevokefsRequest); + memset (response_buffer, 0, sizeof(RevokefsResponse)); + + switch (request->op) + { + case REVOKE_FS_MKDIR: + response_data_size = handle_mkdir (request, data_size, response); + break; + case REVOKE_FS_RMDIR: + response_data_size = handle_rmdir (request, data_size, response); + break; + case REVOKE_FS_UNLINK: + response_data_size = handle_unlink (request, data_size, response); + break; + case REVOKE_FS_SYMLINK: + response_data_size = handle_symlink (request, data_size, response); + break; + case REVOKE_FS_LINK: + response_data_size = handle_link (request, data_size, response); + break; + case REVOKE_FS_RENAME: + response_data_size = handle_rename (request, data_size, response); + break; + case REVOKE_FS_CHMOD: + response_data_size = handle_chmod (request, data_size, response); + break; + case REVOKE_FS_CHOWN: + response_data_size = handle_chown (request, data_size, response); + break; + case REVOKE_FS_TRUNCATE: + response_data_size = handle_truncate (request, data_size, response); + break; + case REVOKE_FS_UTIMENS: + response_data_size = handle_utimens (request, data_size, response); + break; + case REVOKE_FS_OPEN: + response_data_size = handle_open (request, data_size, response); + break; + case REVOKE_FS_READ: + response_data_size = handle_read (request, data_size, response); + break; + case REVOKE_FS_WRITE: + response_data_size = handle_write (request, data_size, response); + break; + case REVOKE_FS_FSYNC: + response_data_size = handle_fsync (request, data_size, response); + break; + case REVOKE_FS_CLOSE: + response_data_size = handle_close (request, data_size, response); + break; + case REVOKE_FS_ACCESS: + response_data_size = handle_access (request, data_size, response); + break; + default: + g_printerr ("Invalid request op %d", (guint) request->op); + exit (1); + } + + if (response_data_size < 0 || response_data_size > MAX_DATA_SIZE) + { + g_printerr ("Invalid response size %ld", response_size); + exit (1); + } + + response_size = RESPONSE_SIZE(response_data_size); + + written_size = TEMP_FAILURE_RETRY (write (fuse_socket, response_buffer, response_size)); + if (written_size == -1) + { + perror ("Got error writing to fuse socket: "); + exit (1); + } + + if (written_size != response_size) + { + g_printerr ("Got partial write to fuse socket"); + exit (1); + } + } +} diff --git a/revokefs/writer.h b/revokefs/writer.h new file mode 100644 index 00000000..46a0be17 --- /dev/null +++ b/revokefs/writer.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018 Alexander Larsson + * + * SPDX-License-Identifier: LGPL-2.0+ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __REVOKEFS_WRITER_H__ +#define __REVOKEFS_WRITER_H__ + +int request_mkdir(int writer_socket, const char *path, mode_t mode); +int request_rmdir (int writer_socket, const char *path); +int request_unlink (int writer_socket, const char *path); +int request_symlink (int writer_socket, const char *from, const char *to); +int request_link (int writer_socket, const char *from, const char *to); +int request_rename (int writer_socket, const char *from, const char *to); +int request_chmod(int writer_socket, const char *path, mode_t mode); +int request_chown(int writer_socket, const char *path, uid_t uid, gid_t gid); +int request_truncate (int writer_socket, const char *path, off_t size); +int request_utimens (int writer_socket, const char *path, const struct timespec tv[2]); +int request_open (int writer_socket, const char *path, mode_t mode, int flags); +int request_read (int writer_socket, int fd, char *buf, size_t size, off_t offset); +int request_write (int writer_socket, int fd, const char *buf, size_t size, off_t offset); +int request_fsync (int writer_socket, int fd); +int request_close (int writer_socket, int fd); +int request_access (int writer_socket, const char *path, int mode); + +void do_writer (int basefd, int socket); + + +typedef enum { + REVOKE_FS_MKDIR, + REVOKE_FS_RMDIR, + REVOKE_FS_UNLINK, + REVOKE_FS_SYMLINK, + REVOKE_FS_LINK, + REVOKE_FS_RENAME, + REVOKE_FS_CHMOD, + REVOKE_FS_CHOWN, + REVOKE_FS_TRUNCATE, + REVOKE_FS_UTIMENS, + REVOKE_FS_OPEN, + REVOKE_FS_READ, + REVOKE_FS_WRITE, + REVOKE_FS_FSYNC, + REVOKE_FS_CLOSE, + REVOKE_FS_ACCESS, +} RevokefsOps; + +typedef struct { + guint32 op; + guint64 arg1; + guint64 arg2; + guint64 arg3; + guchar data[]; +} RevokefsRequest; + +typedef struct { + gint32 result; + + guchar data[]; +} RevokefsResponse; + +#define REQUEST_SIZE(__data_size) (sizeof(RevokefsRequest) + (__data_size)) +#define RESPONSE_SIZE(__data_size) (sizeof(RevokefsResponse) + (__data_size)) + +#define MAX_DATA_SIZE 16384 +#define MAX_REQUEST_SIZE REQUEST_SIZE(MAX_DATA_SIZE) +#define MAX_RESPONSE_SIZE RESPONSE_SIZE(MAX_DATA_SIZE) + +#endif /* __REVOKEFS_WRITER_H__ */