vfs: add snapshot create/delete calls to vfs_btrfs
authorDavid Disseldorp <ddiss@samba.org>
Tue, 4 Sep 2012 13:29:58 +0000 (15:29 +0200)
committerDavid Disseldorp <ddiss@samba.org>
Wed, 19 Sep 2012 03:59:04 +0000 (05:59 +0200)
Issue BTRFS_IOC_SNAP_CREATE_V2 and BTRFS_IOC_SNAP_DESTROY ioctls
accordingly. Base share paths must exist as btrfs subvolumes in order to
be supported for snapshot operations.

The ioctl definitions are taken from the GPLv2 licensed btrfs_ioctl.h.
Such interface definitions do not equate to copyrightable information;
therefore, they may remain part of the GPLv3 licensed Samba source.

source3/configure.in
source3/modules/vfs_btrfs.c
source3/wscript

index 5439416f1d0e5dacba9801184ea1e06897c503f8..edec9e12a5aaf351aef066c349189c1fd733eac4 100644 (file)
@@ -609,6 +609,7 @@ AC_CHECK_HEADERS(xfs/libxfs.h)
 AC_CHECK_HEADERS(netgroup.h)
 AC_CHECK_HEADERS(linux/falloc.h)
 AC_CHECK_HEADERS(CommonCrypto/CommonDigest.h)
+AC_CHECK_HEADERS(linux/ioctl.h)
 
 AC_CHECK_HEADERS(rpcsvc/yp_prot.h,,,[[
 #if HAVE_RPC_RPC_H
@@ -6306,15 +6307,9 @@ fi
 # End
 # Checks for the vfs_fileid module
 
-# Check for btrfs support
-AC_CACHE_CHECK([for btrfs],samba_cv_have_btrfs, [
-    AC_TRY_COMPILE([#include <asm-generic/int-ll64.h>
-                    #include <btrfs/btrfs_ioctl.h>],
-                   [struct btrfs_ioctl_clone_range_args cr;],
-                   samba_cv_have_btrfs=yes,
-                  samba_cv_have_btrfs=no)])
-if test x"$samba_cv_have_btrfs" = x"yes"; then
-    AC_DEFINE(HAVE_BTRFS, 1, [If btrfs ioctls are available])
+# btrfs features are dependent on Linux ioctl headers
+if test x"$ac_cv_header_sys_ioctl_h" = xyes -a \
+        x"$ac_cv_header_linux_ioctl_h" = xyes; then
     default_shared_modules="$default_shared_modules vfs_btrfs"
 fi
 # End btrfs
index 74898131c791bec2b7ea538fc3d7908da9b9ebef..aeaf583abd724de5d96d8c1921ba852430a8ca72 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Module to make use of awesome btrfs features
  *
- * Copyright (C) David Disseldorp 2011
+ * Copyright (C) David Disseldorp 2011-2012
  *
  * 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
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <asm-generic/int-ll64.h>
-#include <btrfs/btrfs_ioctl.h>
+#include <linux/ioctl.h>
+#include <sys/ioctl.h>
+#include <dirent.h>
+#include <libgen.h>
 #include "includes.h"
 #include "system/filesys.h"
 #include "smbd/smbd.h"
 #include "lib/util/tevent_ntstatus.h"
 
+#define SHADOW_COPY_PREFIX "@GMT-"     /* vfs_shadow_copy format */
+#define SHADOW_COPY_PATH_FORMAT "@GMT-%Y.%m.%d-%H.%M.%S"
+
+/*
+ * The following ioctl definitions are taken from the GPLv2 licensed
+ * btrfs_ioctl.h. Such interface definitions do not equate to copyrightable
+ * information; therefore, they may remain part of the GPLv3 licensed Samba
+ * source.
+ */
+#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;
+       uint64_t src_length;
+       uint64_t dest_offset;
+};
+
+#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)
+/* end btrfs_ioctl.h based interface definitions */
+
 struct btrfs_cc_state {
        struct vfs_handle_struct *handle;
-       SMB_OFF_T copied;
+       off_t copied;
        NTSTATUS status;
        struct tevent_req *subreq;      /* non-null if passed to next VFS fn */
 };
@@ -36,10 +78,10 @@ static struct tevent_req *btrfs_copy_chunk_send(struct vfs_handle_struct *handle
                                                TALLOC_CTX *mem_ctx,
                                                struct tevent_context *ev,
                                                struct files_struct *src_fsp,
-                                               SMB_OFF_T src_off,
+                                               off_t src_off,
                                                struct files_struct *dest_fsp,
-                                               SMB_OFF_T dest_off,
-                                               SMB_OFF_T num)
+                                               off_t dest_off,
+                                               off_t num)
 {
        struct tevent_req *req;
        struct btrfs_cc_state *cc_state;
@@ -66,8 +108,8 @@ static struct tevent_req *btrfs_copy_chunk_send(struct vfs_handle_struct *handle
                 * cloning. Which is 4096 by default, therefore fall back to
                 * manual read/write on failure.
                 */
-               DEBUG(5, ("BTRFS_IOC_CLONE_RANGE failed: %s, length %llu, "
-                         "src fd: %lld off: %llu, dest fd: %d off: %llu\n",
+               DEBUG(5, ("BTRFS_IOC_CLONE_RANGE failed: %s, length %lu, "
+                         "src fd: %ld off: %lu, dest fd: %d off: %lu\n",
                          strerror(errno), cr_args.src_length,
                          cr_args.src_fd, cr_args.src_offset,
                          dest_fsp->fh->fd, cr_args.dest_offset));
@@ -113,7 +155,7 @@ static void btrfs_copy_chunk_done(struct tevent_req *subreq)
 
 static NTSTATUS btrfs_copy_chunk_recv(struct vfs_handle_struct *handle,
                                      struct tevent_req *req,
-                                     SMB_OFF_T *copied)
+                                     off_t *copied)
 {
        struct btrfs_cc_state *cc_state = tevent_req_data(req,
                                                        struct btrfs_cc_state);
@@ -124,9 +166,299 @@ static NTSTATUS btrfs_copy_chunk_recv(struct vfs_handle_struct *handle,
        return cc_state->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;
+
+       /* 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;
+}
+
+struct btrfs_snap_create_state {
+       NTSTATUS status;
+       char *base_path;
+       char *snap_path;
+};
+
+static struct tevent_req *btrfs_snap_create_send(struct vfs_handle_struct *handle,
+                                                TALLOC_CTX *mem_ctx,
+                                                struct tevent_context *ev,
+                                                const char *base_volume,
+                                                time_t *tstamp,
+                                                bool rw)
+{
+       struct tevent_req *req;
+       struct btrfs_snap_create_state *create_state;
+       struct btrfs_ioctl_vol_args_v2 ioctl_arg;
+       DIR *src_dir;
+       DIR *dest_dir;
+       int src_fd;
+       int dest_fd;
+       char *dest_path;
+       char *dest_subvolume;
+       int ret;
+       NTSTATUS status;
+
+       req = tevent_req_create(mem_ctx, &create_state,
+                               struct btrfs_snap_create_state);
+       if (req == NULL) {
+               return NULL;
+       }
+
+       create_state->base_path = talloc_strdup(mem_ctx, base_volume);
+       if (create_state->base_path == NULL) {
+               create_state->status = NT_STATUS_NO_MEMORY;
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+               /* TODO use tevent_req_nomem */
+       }
+
+       status = btrfs_gen_snap_dest_path(mem_ctx, base_volume, tstamp,
+                                         &dest_path, &dest_subvolume);
+       if (!NT_STATUS_IS_OK(status)) {
+               create_state->status = status;
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+
+       create_state->snap_path = talloc_asprintf(mem_ctx, "%s/%s",
+                                                 dest_path, dest_subvolume);
+       if (create_state->snap_path == NULL) {
+               create_state->status = NT_STATUS_NO_MEMORY;
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+
+       src_dir = opendir(base_volume);
+       if (src_dir == NULL) {
+               DEBUG(0, ("clone src %s open failed: %s\n",
+                         base_volume, strerror(errno)));
+               create_state->status = map_nt_error_from_unix(errno);
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+       src_fd = dirfd(src_dir);
+       if (src_fd < 0) {
+               create_state->status = map_nt_error_from_unix(errno);
+               closedir(src_dir);
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+
+       dest_dir = opendir(dest_path);
+       if (dest_dir == NULL) {
+               DEBUG(0, ("clone dest %s open failed: %s\n",
+                         dest_path, strerror(errno)));
+               create_state->status = map_nt_error_from_unix(errno);
+               closedir(src_dir);
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+       dest_fd = dirfd(dest_dir);
+       if (dest_fd < 0) {
+               create_state->status = map_nt_error_from_unix(errno);
+               closedir(src_dir);
+               closedir(dest_dir);
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+
+       /* 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, ARRAY_SIZE(ioctl_arg.unused));
+       strncpy(ioctl_arg.name, dest_subvolume, ARRAY_SIZE(ioctl_arg.name));
+
+       become_root();
+       ret = ioctl(dest_fd, BTRFS_IOC_SNAP_CREATE_V2, &ioctl_arg);
+       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(errno)));
+               create_state->status = map_nt_error_from_unix(errno);
+               closedir(src_dir);
+               closedir(dest_dir);
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+       DEBUG(5, ("%s -> %s(%s) BTRFS_IOC_SNAP_CREATE_V2 done\n",
+                 base_volume, dest_path, dest_subvolume));
+
+       create_state->status = NT_STATUS_OK;
+       closedir(src_dir);
+       closedir(dest_dir);
+       tevent_req_done(req);
+       return tevent_req_post(req, ev);
+}
+
+static NTSTATUS btrfs_snap_create_recv(struct vfs_handle_struct *handle,
+                                      struct tevent_req *req,
+                                     TALLOC_CTX *mem_ctx,
+                                      char **base_path, char **snap_path)
+{
+       struct btrfs_snap_create_state *create_state = tevent_req_data(req,
+                                               struct btrfs_snap_create_state);
+
+       *base_path = talloc_strdup(mem_ctx, create_state->base_path);
+       *snap_path = talloc_strdup(mem_ctx, create_state->snap_path);
+       return create_state->status;
+}
+
+struct btrfs_snap_del_state {
+       struct vfs_handle_struct *handle;
+       NTSTATUS status;
+};
+
+static struct tevent_req *btrfs_snap_delete_send(struct vfs_handle_struct *handle,
+                                                TALLOC_CTX *mem_ctx,
+                                                struct tevent_context *ev,
+                                                char *snap_path)
+{
+       struct tevent_req *req;
+       struct btrfs_snap_del_state *del_state;
+       char *tstr;
+       struct tm t_gmt;
+       DIR *dest_dir;
+       int dest_fd;
+       char *dest_path;
+       char *subvolume;
+       struct btrfs_ioctl_vol_args ioctl_arg;
+       int ret;
+
+       req = tevent_req_create(mem_ctx, &del_state,
+                               struct btrfs_snap_del_state);
+       if (req == NULL) {
+               return NULL;
+       }
+
+       dest_path = talloc_strdup(mem_ctx, snap_path);
+       subvolume = talloc_strdup(mem_ctx, snap_path);
+       if ((dest_path == NULL) || (subvolume == NULL)) {
+               del_state->status = NT_STATUS_NO_MEMORY;
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+       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));
+               del_state->status = NT_STATUS_UNSUCCESSFUL;
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+
+       dest_dir = opendir(dest_path);
+       if (dest_dir == NULL) {
+               DEBUG(0, ("snap destroy dest %s open failed: %s\n",
+                         dest_path, strerror(errno)));
+               del_state->status = map_nt_error_from_unix(errno);
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+       dest_fd = dirfd(dest_dir);
+       if (dest_fd < 0) {
+               del_state->status = map_nt_error_from_unix(errno);
+               closedir(dest_dir);
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+
+       ioctl_arg.fd = -1;      /* not needed */
+       strncpy(ioctl_arg.name, subvolume, ARRAY_SIZE(ioctl_arg.name));
+
+       become_root();
+       ret = ioctl(dest_fd, BTRFS_IOC_SNAP_DESTROY, &ioctl_arg);
+       unbecome_root();
+       if (ret < 0) {
+               DEBUG(0, ("%s(%s) BTRFS_IOC_SNAP_DESTROY failed: %s\n",
+                         dest_path, subvolume, strerror(errno)));
+               del_state->status = map_nt_error_from_unix(errno);
+               closedir(dest_dir);
+               tevent_req_done(req);
+               return tevent_req_post(req, ev);
+       }
+       DEBUG(5, ("%s(%s) BTRFS_IOC_SNAP_DESTROY done\n",
+                 dest_path, subvolume));
+
+       del_state->status = NT_STATUS_OK;
+       closedir(dest_dir);
+       tevent_req_done(req);
+       return tevent_req_post(req, ev);
+}
+
+static NTSTATUS btrfs_snap_delete_recv(struct vfs_handle_struct *handle,
+                                        struct tevent_req *req)
+{
+       struct btrfs_snap_del_state *del_state = tevent_req_data(req,
+                                               struct btrfs_snap_del_state);
+       return del_state->status;
+}
+
 static struct vfs_fn_pointers btrfs_fns = {
        .copy_chunk_send_fn = btrfs_copy_chunk_send,
        .copy_chunk_recv_fn = btrfs_copy_chunk_recv,
+       .snap_check_path_fn = btrfs_snap_check_path,
+       .snap_create_send_fn = btrfs_snap_create_send,
+       .snap_create_recv_fn = btrfs_snap_create_recv,
+       .snap_delete_send_fn = btrfs_snap_delete_send,
+       .snap_delete_recv_fn = btrfs_snap_delete_recv,
 };
 
 NTSTATUS vfs_btrfs_init(void);
index 54270704b022817547a6a134d99c77bcd6128243..22ad19de532a3c2b8a8198455a5817c862896515 100644 (file)
@@ -1501,13 +1501,8 @@ main() {
     if conf.CHECK_HEADERS('gpfs_gpl.h'):
         conf.DEFINE('HAVE_GPFS', '1')
 
-    conf.CHECK_CODE('''
-                   #include <asm-generic/int-ll64.h>
-                   #include <btrfs/btrfs_ioctl.h>
-                   struct btrfs_ioctl_clone_range_args cr;
-                   ''',
-                   'HAVE_BTRFS',
-                   msg='Checking whether btrfs ioctls are available')
+    conf.CHECK_HEADERS('linux/ioctl.h sys/ioctl.h'):
+           conf.DEFINE('HAVE_LINUX_IOCTL', '1')
 
     default_static_modules.extend(TO_LIST('''pdb_smbpasswd pdb_tdbsam pdb_wbc_sam
                                       auth_sam auth_unix auth_winbind auth_wbc
@@ -1569,7 +1564,7 @@ main() {
     if conf.CONFIG_SET('HAVE_GPFS'):
        default_shared_modules.extend(TO_LIST('vfs_gpfs'))
 
-    if conf.CONFIG_SET('HAVE_BTRFS'):
+    if conf.CONFIG_SET('HAVE_LINUX_IOCTL'):
        default_shared_modules.extend(TO_LIST('vfs_btrfs'))
 
     explicit_shared_modules = TO_LIST(Options.options.shared_modules, delimiter=',')