lib: modules: Change XXX_init interface from XXX_init(void) to XXX_init(TALLOC_CTX *)
[nivanova/samba-autobuild/.git] / source3 / modules / vfs_btrfs.c
index 36d6120cdcc71ebb8b7f715f878d0d87c484211b..ab4fd99ba2c3e64bd46b8e303e43ba4a52062835 100644 (file)
 #include <linux/ioctl.h>
 #include <linux/fs.h>
 #include <sys/ioctl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <dirent.h>
+#include <libgen.h>
+#include "system/filesys.h"
 #include "includes.h"
 #include "smbd/smbd.h"
 #include "librpc/gen_ndr/smbXsrv.h"
 #include "librpc/gen_ndr/ioctl.h"
 #include "lib/util/tevent_ntstatus.h"
 
+static uint32_t btrfs_fs_capabilities(struct vfs_handle_struct *handle,
+                                     enum timestamp_set_resolution *_ts_res)
+{
+       uint32_t fs_capabilities;
+       enum timestamp_set_resolution ts_res;
+
+       /* inherit default capabilities, expose compression support */
+       fs_capabilities = SMB_VFS_NEXT_FS_CAPABILITIES(handle, &ts_res);
+       fs_capabilities |= FILE_FILE_COMPRESSION;
+       *_ts_res = ts_res;
+
+       return fs_capabilities;
+}
+
+#define SHADOW_COPY_PREFIX "@GMT-"     /* vfs_shadow_copy format */
+#define SHADOW_COPY_PATH_FORMAT "@GMT-%Y.%m.%d-%H.%M.%S"
+
+#define BTRFS_SUBVOL_RDONLY            (1ULL << 1)
+#define BTRFS_SUBVOL_NAME_MAX          4039
+#define BTRFS_PATH_NAME_MAX            4087
+struct btrfs_ioctl_vol_args_v2 {
+       int64_t fd;
+       uint64_t transid;
+       uint64_t flags;
+       uint64_t unused[4];
+       char name[BTRFS_SUBVOL_NAME_MAX + 1];
+};
+struct btrfs_ioctl_vol_args {
+       int64_t fd;
+       char name[BTRFS_PATH_NAME_MAX + 1];
+};
+
 struct btrfs_ioctl_clone_range_args {
        int64_t src_fd;
        uint64_t src_offset;
@@ -40,6 +73,10 @@ struct btrfs_ioctl_clone_range_args {
 #define BTRFS_IOCTL_MAGIC 0x94
 #define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \
                                   struct btrfs_ioctl_clone_range_args)
+#define BTRFS_IOC_SNAP_DESTROY _IOW(BTRFS_IOCTL_MAGIC, 15, \
+                                   struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_SNAP_CREATE_V2 _IOW(BTRFS_IOCTL_MAGIC, 23, \
+                                     struct btrfs_ioctl_vol_args_v2)
 
 struct btrfs_cc_state {
        struct vfs_handle_struct *handle;
@@ -71,6 +108,27 @@ static struct tevent_req *btrfs_copy_chunk_send(struct vfs_handle_struct *handle
        }
        cc_state->handle = handle;
 
+       if (num == 0) {
+               /*
+                * With a @src_length of zero, BTRFS_IOC_CLONE_RANGE clones
+                * all data from @src_offset->EOF! This is certainly not what
+                * the caller expects, and not what vfs_default does.
+                */
+               cc_state->subreq = SMB_VFS_NEXT_COPY_CHUNK_SEND(handle,
+                                                               cc_state, ev,
+                                                               src_fsp,
+                                                               src_off,
+                                                               dest_fsp,
+                                                               dest_off, num);
+               if (tevent_req_nomem(cc_state->subreq, req)) {
+                       return tevent_req_post(req, ev);
+               }
+               tevent_req_set_callback(cc_state->subreq,
+                                       btrfs_copy_chunk_done,
+                                       req);
+               return req;
+       }
+
        status = vfs_stat_fsp(src_fsp);
        if (tevent_req_nterror(req, status)) {
                return tevent_req_post(req, ev);
@@ -82,6 +140,11 @@ static struct tevent_req *btrfs_copy_chunk_send(struct vfs_handle_struct *handle
                return tevent_req_post(req, ev);
        }
 
+       if (src_fsp->op == NULL || dest_fsp->op == NULL) {
+               tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+               return tevent_req_post(req, ev);
+       }
+
        init_strict_lock_struct(src_fsp,
                                src_fsp->op->global->open_persistent_id,
                                src_off,
@@ -112,7 +175,7 @@ static struct tevent_req *btrfs_copy_chunk_send(struct vfs_handle_struct *handle
        cr_args.src_length = (uint64_t)num;
 
        ret = ioctl(dest_fsp->fh->fd, BTRFS_IOC_CLONE_RANGE, &cr_args);
-       SMB_VFS_STRICT_UNLOCK(src_fsp->conn, src_fsp, &dest_lck);
+       SMB_VFS_STRICT_UNLOCK(dest_fsp->conn, dest_fsp, &dest_lck);
        SMB_VFS_STRICT_UNLOCK(src_fsp->conn, src_fsp, &src_lck);
        if (ret < 0) {
                /*
@@ -142,7 +205,6 @@ static struct tevent_req *btrfs_copy_chunk_send(struct vfs_handle_struct *handle
                                        btrfs_copy_chunk_done,
                                        req);
                return req;
-
        }
 
        DEBUG(5, ("BTRFS_IOC_CLONE_RANGE returned %d\n", ret));
@@ -207,23 +269,29 @@ static NTSTATUS btrfs_get_compression(struct vfs_handle_struct *handle,
        int fd;
        bool opened = false;
        NTSTATUS status;
+       DIR *dir = NULL;
 
        if ((fsp != NULL) && (fsp->fh->fd != -1)) {
                fd = fsp->fh->fd;
        } else if (smb_fname != NULL) {
                if (S_ISDIR(smb_fname->st.st_ex_mode)) {
-                       DIR *dir = opendir(smb_fname->base_name);
+                       dir = opendir(smb_fname->base_name);
                        if (dir == NULL) {
                                return NT_STATUS_UNSUCCESSFUL;
                        }
+                       opened = true;
                        fd = dirfd(dir);
+                       if (fd < 0) {
+                               status = NT_STATUS_UNSUCCESSFUL;
+                               goto err_close;
+                       }
                } else {
                        fd = open(smb_fname->base_name, O_RDONLY);
+                       if (fd < 0) {
+                               return NT_STATUS_UNSUCCESSFUL;
+                       }
+                       opened = true;
                }
-               if (fd < 0) {
-                       return NT_STATUS_UNSUCCESSFUL;
-               }
-               opened = true;
        } else {
                return NT_STATUS_INVALID_PARAMETER;
        }
@@ -243,7 +311,11 @@ static NTSTATUS btrfs_get_compression(struct vfs_handle_struct *handle,
        status = NT_STATUS_OK;
 err_close:
        if (opened) {
-               close(fd);
+               if (dir != NULL) {
+                       closedir(dir);
+               } else {
+                       close(fd);
+               }
        }
 
        return status;
@@ -299,16 +371,313 @@ err_out:
        return status;
 }
 
+/*
+ * Check whether a path can be shadow copied. Return the base volume, allowing
+ * the caller to determine if multiple paths lie on the same base volume.
+ */
+#define BTRFS_INODE_SUBVOL 256
+static NTSTATUS btrfs_snap_check_path(struct vfs_handle_struct *handle,
+                                     TALLOC_CTX *mem_ctx,
+                                     const char *service_path,
+                                     char **base_volume)
+{
+       struct stat st;
+       char *base;
+
+       if (!lp_parm_bool(SNUM(handle->conn),
+                        "btrfs", "manipulate snapshots", false)) {
+               DEBUG(2, ("Btrfs snapshot manipulation disabled, passing\n"));
+               return SMB_VFS_NEXT_SNAP_CHECK_PATH(handle, mem_ctx,
+                                                   service_path, base_volume);
+       }
+
+       /* btrfs userspace uses this logic to confirm subvolume */
+       if (stat(service_path, &st) < 0) {
+               return NT_STATUS_NOT_SUPPORTED;
+       }
+       if ((st.st_ino != BTRFS_INODE_SUBVOL) || !S_ISDIR(st.st_mode)) {
+               DEBUG(0, ("%s not a btrfs subvolume, snapshots not available\n",
+                         service_path));
+               return NT_STATUS_NOT_SUPPORTED;
+       }
+
+       /* we "snapshot" the service path itself */
+       base = talloc_strdup(mem_ctx, service_path);
+       if (base == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       *base_volume = base;
+
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS btrfs_gen_snap_dest_path(TALLOC_CTX *mem_ctx,
+                                        const char *src_path,
+                                        time_t *tstamp,
+                                        char **dest_path, char **subvolume)
+{
+       struct tm t_gmt;
+       char time_str[50];
+       size_t tlen;
+
+       gmtime_r(tstamp, &t_gmt);
+
+       tlen = strftime(time_str, ARRAY_SIZE(time_str),
+                       SHADOW_COPY_PATH_FORMAT, &t_gmt);
+       if (tlen <= 0) {
+               return NT_STATUS_UNSUCCESSFUL;
+       }
+
+       *dest_path = talloc_strdup(mem_ctx, src_path);
+       *subvolume = talloc_strdup(mem_ctx, time_str);
+       if ((*dest_path == NULL) || (*subvolume == NULL)) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS btrfs_snap_create(struct vfs_handle_struct *handle,
+                                 TALLOC_CTX *mem_ctx,
+                                 const char *base_volume,
+                                 time_t *tstamp,
+                                 bool rw,
+                                 char **_base_path,
+                                 char **_snap_path)
+{
+       struct btrfs_ioctl_vol_args_v2 ioctl_arg;
+       DIR *src_dir;
+       DIR *dest_dir;
+       int src_fd;
+       int dest_fd;
+       char *dest_path = NULL;
+       char *dest_subvolume = NULL;
+       int ret;
+       NTSTATUS status;
+       char *base_path;
+       char *snap_path;
+       TALLOC_CTX *tmp_ctx;
+       int saved_errno;
+       size_t len;
+
+       if (!lp_parm_bool(SNUM(handle->conn),
+                         "btrfs", "manipulate snapshots", false)) {
+               DEBUG(2, ("Btrfs snapshot manipulation disabled, passing\n"));
+               return SMB_VFS_NEXT_SNAP_CREATE(handle, mem_ctx, base_volume,
+                                               tstamp, rw, _base_path,
+                                               _snap_path);
+       }
+
+       tmp_ctx = talloc_new(mem_ctx);
+       if (tmp_ctx == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       base_path = talloc_strdup(tmp_ctx, base_volume);
+       if (base_path == NULL) {
+               talloc_free(tmp_ctx);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       status = btrfs_gen_snap_dest_path(tmp_ctx, base_volume, tstamp,
+                                         &dest_path, &dest_subvolume);
+       if (!NT_STATUS_IS_OK(status)) {
+               talloc_free(tmp_ctx);
+               return status;
+       }
+
+       snap_path = talloc_asprintf(tmp_ctx, "%s/%s", dest_path,
+                                   dest_subvolume);
+       if (snap_path == NULL) {
+               talloc_free(tmp_ctx);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       src_dir = opendir(base_volume);
+       if (src_dir == NULL) {
+               DEBUG(0, ("snap src %s open failed: %s\n",
+                         base_volume, strerror(errno)));
+               status = map_nt_error_from_unix(errno);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+       src_fd = dirfd(src_dir);
+       if (src_fd < 0) {
+               status = map_nt_error_from_unix(errno);
+               closedir(src_dir);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+
+       dest_dir = opendir(dest_path);
+       if (dest_dir == NULL) {
+               DEBUG(0, ("snap dest %s open failed: %s\n",
+                         dest_path, strerror(errno)));
+               status = map_nt_error_from_unix(errno);
+               closedir(src_dir);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+       dest_fd = dirfd(dest_dir);
+       if (dest_fd < 0) {
+               status = map_nt_error_from_unix(errno);
+               closedir(src_dir);
+               closedir(dest_dir);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+
+       /* avoid zeroing the entire struct here, name is 4k */
+       ioctl_arg.fd = src_fd;
+       ioctl_arg.transid = 0;
+       ioctl_arg.flags = (rw == false) ? BTRFS_SUBVOL_RDONLY : 0;
+       memset(ioctl_arg.unused, 0, sizeof(ioctl_arg.unused));
+       len = strlcpy(ioctl_arg.name, dest_subvolume,
+                     ARRAY_SIZE(ioctl_arg.name));
+       if (len >= ARRAY_SIZE(ioctl_arg.name)) {
+               DEBUG(1, ("subvolume name too long for SNAP_CREATE ioctl\n"));
+               closedir(src_dir);
+               closedir(dest_dir);
+               talloc_free(tmp_ctx);
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       become_root();
+       ret = ioctl(dest_fd, BTRFS_IOC_SNAP_CREATE_V2, &ioctl_arg);
+       saved_errno = errno;
+       unbecome_root();
+       if (ret < 0) {
+               DEBUG(0, ("%s -> %s(%s) BTRFS_IOC_SNAP_CREATE_V2 failed: %s\n",
+                         base_volume, dest_path, dest_subvolume,
+                         strerror(saved_errno)));
+               status = map_nt_error_from_unix(saved_errno);
+               closedir(src_dir);
+               closedir(dest_dir);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+       DEBUG(5, ("%s -> %s(%s) BTRFS_IOC_SNAP_CREATE_V2 done\n",
+                 base_volume, dest_path, dest_subvolume));
+
+       *_base_path = talloc_steal(mem_ctx, base_path);
+       *_snap_path = talloc_steal(mem_ctx, snap_path);
+       closedir(src_dir);
+       closedir(dest_dir);
+       talloc_free(tmp_ctx);
+
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS btrfs_snap_delete(struct vfs_handle_struct *handle,
+                                 TALLOC_CTX *mem_ctx,
+                                 char *base_path,
+                                 char *snap_path)
+{
+       char *tstr;
+       struct tm t_gmt;
+       DIR *dest_dir;
+       int dest_fd;
+       struct btrfs_ioctl_vol_args ioctl_arg;
+       int ret;
+       NTSTATUS status;
+       char *dest_path;
+       char *subvolume;
+       TALLOC_CTX *tmp_ctx;
+       int saved_errno;
+       size_t len;
+
+       if (!lp_parm_bool(SNUM(handle->conn),
+                         "btrfs", "manipulate snapshots", false)) {
+               DEBUG(2, ("Btrfs snapshot manipulation disabled, passing\n"));
+               return SMB_VFS_NEXT_SNAP_DELETE(handle, mem_ctx,
+                                               base_path, snap_path);
+       }
+
+       tmp_ctx = talloc_new(mem_ctx);
+       if (tmp_ctx == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       dest_path = talloc_strdup(tmp_ctx, snap_path);
+       if (dest_path == NULL) {
+               talloc_free(tmp_ctx);
+               return NT_STATUS_NO_MEMORY;
+       }
+       subvolume = talloc_strdup(tmp_ctx, snap_path);
+       if (subvolume == NULL) {
+               talloc_free(tmp_ctx);
+               return NT_STATUS_NO_MEMORY;
+       }
+       dest_path = dirname(dest_path);
+       subvolume = basename(subvolume);
+
+       /* confirm snap_path matches creation format */
+       tstr = strptime(subvolume, SHADOW_COPY_PATH_FORMAT, &t_gmt);
+       if ((tstr == NULL) || (*tstr != '\0')) {
+               DEBUG(0, ("snapshot path %s does not match creation format\n",
+                         snap_path));
+               talloc_free(tmp_ctx);
+               return NT_STATUS_UNSUCCESSFUL;
+       }
+
+       dest_dir = opendir(dest_path);
+       if (dest_dir == NULL) {
+               DEBUG(0, ("snap destroy dest %s open failed: %s\n",
+                         dest_path, strerror(errno)));
+               status = map_nt_error_from_unix(errno);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+       dest_fd = dirfd(dest_dir);
+       if (dest_fd < 0) {
+               status = map_nt_error_from_unix(errno);
+               closedir(dest_dir);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+
+       ioctl_arg.fd = -1;      /* not needed */
+       len = strlcpy(ioctl_arg.name, subvolume, ARRAY_SIZE(ioctl_arg.name));
+       if (len >= ARRAY_SIZE(ioctl_arg.name)) {
+               DEBUG(1, ("subvolume name too long for SNAP_DESTROY ioctl\n"));
+               closedir(dest_dir);
+               talloc_free(tmp_ctx);
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       become_root();
+       ret = ioctl(dest_fd, BTRFS_IOC_SNAP_DESTROY, &ioctl_arg);
+       saved_errno = errno;
+       unbecome_root();
+       if (ret < 0) {
+               DEBUG(0, ("%s(%s) BTRFS_IOC_SNAP_DESTROY failed: %s\n",
+                         dest_path, subvolume, strerror(saved_errno)));
+               status = map_nt_error_from_unix(saved_errno);
+               closedir(dest_dir);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+       DEBUG(5, ("%s(%s) BTRFS_IOC_SNAP_DESTROY done\n",
+                 dest_path, subvolume));
+
+       closedir(dest_dir);
+       talloc_free(tmp_ctx);
+       return NT_STATUS_OK;
+}
 
 static struct vfs_fn_pointers btrfs_fns = {
+       .fs_capabilities_fn = btrfs_fs_capabilities,
        .copy_chunk_send_fn = btrfs_copy_chunk_send,
        .copy_chunk_recv_fn = btrfs_copy_chunk_recv,
        .get_compression_fn = btrfs_get_compression,
        .set_compression_fn = btrfs_set_compression,
+       .snap_check_path_fn = btrfs_snap_check_path,
+       .snap_create_fn = btrfs_snap_create,
+       .snap_delete_fn = btrfs_snap_delete,
 };
 
-NTSTATUS vfs_btrfs_init(void);
-NTSTATUS vfs_btrfs_init(void)
+NTSTATUS vfs_btrfs_init(TALLOC_CTX *);
+NTSTATUS vfs_btrfs_init(TALLOC_CTX *ctx)
 {
        return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
                                "btrfs", &btrfs_fns);