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