vfs_snapper: create/delete snapshot support
authorDavid Disseldorp <ddiss@samba.org>
Sun, 14 Oct 2012 17:54:24 +0000 (19:54 +0200)
committerJeremy Allison <jra@samba.org>
Tue, 31 Mar 2015 16:40:25 +0000 (18:40 +0200)
Extend vfs_snapper to support the new remote snapshot creation and
deletion hooks added for FSRVP.

Signed-off-by: David Disseldorp <ddiss@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
source3/modules/vfs_snapper.c

index 415514aa1fd0a4613b02aa943c007b6c21b3e1ca..e2e2587fa8c747348ed498dbedb7478bc0b58804 100644 (file)
@@ -40,6 +40,8 @@
 
 #define SNAPPER_SIG_LIST_SNAPS_RSP "a(uquxussa{ss})"
 #define SNAPPER_SIG_LIST_CONFS_RSP "a(ssa{ss})"
+#define SNAPPER_SIG_CREATE_SNAP_RSP "u"
+#define SNAPPER_SIG_DEL_SNAPS_RSP ""
 #define SNAPPER_SIG_STRING_DICT "{ss}"
 
 struct snapper_dict {
@@ -860,6 +862,296 @@ static NTSTATUS snapper_list_snaps_unpack(TALLOC_CTX *mem_ctx,
        return NT_STATUS_OK;
 }
 
+static NTSTATUS snapper_create_snap_pack(TALLOC_CTX *mem_ctx,
+                                        const char *snapper_conf,
+                                        const char *desc,
+                                        uint32_t num_user_data,
+                                        struct snapper_dict *user_data,
+                                        DBusMessage **req_msg_out)
+{
+       DBusMessage *msg;
+       DBusMessageIter args;
+       DBusMessageIter array_iter;
+       DBusMessageIter struct_iter;
+       const char *empty = "";
+       char *str_encoded;
+       uint32_t i;
+       bool ok;
+       TALLOC_CTX *enc_ctx;
+       NTSTATUS status;
+
+       DEBUG(10, ("CreateSingleSnapshot: %s, %s, %s, num user %u\n",
+                 snapper_conf, desc, empty, num_user_data));
+
+       enc_ctx = talloc_new(mem_ctx);
+       if (enc_ctx == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       msg = dbus_message_new_method_call("org.opensuse.Snapper",
+                                          "/org/opensuse/Snapper",
+                                          "org.opensuse.Snapper",
+                                          "CreateSingleSnapshot");
+       if (msg == NULL) {
+               DEBUG(0, ("failed to create req msg\n"));
+               talloc_free(enc_ctx);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       status = snapper_dbus_str_encode(enc_ctx, snapper_conf, &str_encoded);
+       if (!NT_STATUS_IS_OK(status)) {
+               dbus_message_unref(msg);
+               talloc_free(enc_ctx);
+               return status;
+       }
+
+       /* append arguments */
+       dbus_message_iter_init_append(msg, &args);
+       ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
+                                           &str_encoded);
+       if (!ok) {
+               dbus_message_unref(msg);
+               talloc_free(enc_ctx);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       status = snapper_dbus_str_encode(enc_ctx, desc, &str_encoded);
+       if (!NT_STATUS_IS_OK(status)) {
+               dbus_message_unref(msg);
+               talloc_free(enc_ctx);
+               return status;
+       }
+
+       ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
+                                           &str_encoded);
+       if (!ok) {
+               dbus_message_unref(msg);
+               talloc_free(enc_ctx);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       /* cleanup - no need to encode empty string */
+       ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
+                                           &empty);
+       if (!ok) {
+               dbus_message_unref(msg);
+               talloc_free(enc_ctx);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       ok = dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
+                                             SNAPPER_SIG_STRING_DICT,
+                                             &array_iter);
+       if (!ok) {
+               dbus_message_unref(msg);
+               talloc_free(enc_ctx);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       for (i = 0; i < num_user_data; i++) {
+               ok = dbus_message_iter_open_container(&array_iter,
+                                                     DBUS_TYPE_DICT_ENTRY,
+                                                     NULL, &struct_iter);
+               if (!ok) {
+                       dbus_message_unref(msg);
+                       talloc_free(enc_ctx);
+                       return NT_STATUS_NO_MEMORY;
+               }
+
+               status = snapper_dbus_str_encode(enc_ctx, user_data[i].key,
+                                                &str_encoded);
+               if (!NT_STATUS_IS_OK(status)) {
+                       dbus_message_unref(msg);
+                       talloc_free(enc_ctx);
+                       return status;
+               }
+
+               ok = dbus_message_iter_append_basic(&struct_iter,
+                                                   DBUS_TYPE_STRING,
+                                                   &str_encoded);
+               if (!ok) {
+                       dbus_message_unref(msg);
+                       talloc_free(enc_ctx);
+                       return NT_STATUS_NO_MEMORY;
+               }
+
+               status = snapper_dbus_str_encode(enc_ctx, user_data[i].val,
+                                                &str_encoded);
+               if (!NT_STATUS_IS_OK(status)) {
+                       dbus_message_unref(msg);
+                       talloc_free(enc_ctx);
+                       return status;
+               }
+
+               ok = dbus_message_iter_append_basic(&struct_iter,
+                                                   DBUS_TYPE_STRING,
+                                                   &str_encoded);
+               if (!ok) {
+                       dbus_message_unref(msg);
+                       talloc_free(enc_ctx);
+                       return NT_STATUS_NO_MEMORY;
+               }
+
+               ok = dbus_message_iter_close_container(&array_iter, &struct_iter);
+               if (!ok) {
+                       dbus_message_unref(msg);
+                       talloc_free(enc_ctx);
+                       return NT_STATUS_NO_MEMORY;
+               }
+       }
+
+       ok = dbus_message_iter_close_container(&args, &array_iter);
+       if (!ok) {
+               dbus_message_unref(msg);
+               talloc_free(enc_ctx);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       *req_msg_out = msg;
+
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_create_snap_unpack(DBusConnection *conn,
+                                          DBusMessage *rsp_msg,
+                                          uint32_t *snap_id_out)
+{
+       NTSTATUS status;
+       DBusMessageIter iter;
+       int msg_type;
+       const char *sig;
+       uint32_t snap_id;
+
+       msg_type = dbus_message_get_type(rsp_msg);
+       if (msg_type == DBUS_MESSAGE_TYPE_ERROR) {
+               const char *err_str = dbus_message_get_error_name(rsp_msg);
+               DEBUG(0, ("create snap error response: %s, euid %d egid %d\n",
+                         err_str, geteuid(), getegid()));
+               return snapper_err_ntstatus_map(err_str);
+       }
+
+       if (msg_type != DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+               DEBUG(0, ("unexpected create snap ret type: %d\n",
+                         msg_type));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       sig = dbus_message_get_signature(rsp_msg);
+       if ((sig == NULL)
+        || (strcmp(sig, SNAPPER_SIG_CREATE_SNAP_RSP) != 0)) {
+               DEBUG(0, ("bad create snap response sig: %s, expected: %s\n",
+                         (sig ? sig : "NULL"), SNAPPER_SIG_CREATE_SNAP_RSP));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       /* read the parameters */
+       if (!dbus_message_iter_init(rsp_msg, &iter)) {
+               DEBUG(0, ("response has no arguments!\n"));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       status = snapper_type_check_get(&iter, DBUS_TYPE_UINT32, &snap_id);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+       *snap_id_out = snap_id;
+
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_del_snap_pack(TALLOC_CTX *mem_ctx,
+                                     const char *snapper_conf,
+                                     uint32_t snap_id,
+                                     DBusMessage **req_msg_out)
+{
+       DBusMessage *msg;
+       DBusMessageIter args;
+       DBusMessageIter array_iter;
+       char *conf_encoded;
+       bool ok;
+       NTSTATUS status;
+
+       msg = dbus_message_new_method_call("org.opensuse.Snapper",
+                                          "/org/opensuse/Snapper",
+                                          "org.opensuse.Snapper",
+                                          "DeleteSnapshots");
+       if (msg == NULL) {
+               DEBUG(0, ("failed to create req msg\n"));
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       status = snapper_dbus_str_encode(mem_ctx, snapper_conf, &conf_encoded);
+       if (!NT_STATUS_IS_OK(status)) {
+               dbus_message_unref(msg);
+               return status;
+       }
+
+       /* append arguments */
+       dbus_message_iter_init_append(msg, &args);
+       ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
+                                           &conf_encoded);
+       if (!ok) {
+               talloc_free(conf_encoded);
+               dbus_message_unref(msg);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       ok = dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
+                                              DBUS_TYPE_UINT32_AS_STRING,
+                                              &array_iter);
+       if (!ok) {
+               talloc_free(conf_encoded);
+               dbus_message_unref(msg);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       ok = dbus_message_iter_append_basic(&array_iter,
+                                           DBUS_TYPE_UINT32,
+                                           &snap_id);
+       if (!ok) {
+               talloc_free(conf_encoded);
+               dbus_message_unref(msg);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       dbus_message_iter_close_container(&args, &array_iter);
+       *req_msg_out = msg;
+
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_del_snap_unpack(DBusConnection *conn,
+                                       DBusMessage *rsp_msg)
+{
+       int msg_type;
+       const char *sig;
+
+       msg_type = dbus_message_get_type(rsp_msg);
+       if (msg_type == DBUS_MESSAGE_TYPE_ERROR) {
+               const char *err_str = dbus_message_get_error_name(rsp_msg);
+               DEBUG(0, ("del snap error response: %s\n", err_str));
+               return snapper_err_ntstatus_map(err_str);
+       }
+
+       if (msg_type != DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+               DEBUG(0, ("unexpected del snap ret type: %d\n",
+                         msg_type));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       sig = dbus_message_get_signature(rsp_msg);
+       if ((sig == NULL)
+        || (strcmp(sig, SNAPPER_SIG_DEL_SNAPS_RSP) != 0)) {
+               DEBUG(0, ("bad create snap response sig: %s, expected: %s\n",
+                         (sig ? sig : "NULL"), SNAPPER_SIG_DEL_SNAPS_RSP));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       /* no parameters in response */
+
+       return NT_STATUS_OK;
+}
+
 static NTSTATUS snapper_list_snaps_at_time_pack(TALLOC_CTX *mem_ctx,
                                                const char *snapper_conf,
                                                time_t time_lower,
@@ -914,6 +1206,61 @@ static NTSTATUS snapper_list_snaps_at_time_pack(TALLOC_CTX *mem_ctx,
 }
 /* no snapper_list_snaps_at_time_unpack, use snapper_list_snaps_unpack */
 
+/*
+ * Determine the snapper snapshot id given a path.
+ * Ideally this should be determined via a lookup.
+ */
+static NTSTATUS snapper_snap_path_to_id(TALLOC_CTX *mem_ctx,
+                                       const char *snap_path,
+                                       uint32_t *snap_id_out)
+{
+       char *path_dup;
+       char *str_idx;
+       char *str_end;
+       uint32_t snap_id;
+
+       path_dup = talloc_strdup(mem_ctx, snap_path);
+       if (path_dup == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       /* trim trailing '/' */
+       str_idx = path_dup + strlen(path_dup) - 1;
+       while (*str_idx == '/') {
+               *str_idx = '\0';
+               str_idx--;
+       }
+
+       str_idx = strrchr(path_dup, '/');
+       if ((str_idx == NULL)
+        || (strcmp(str_idx + 1, "snapshot") != 0)) {
+               talloc_free(path_dup);
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       while (*str_idx == '/') {
+               *str_idx = '\0';
+               str_idx--;
+       }
+
+       str_idx = strrchr(path_dup, '/');
+       if (str_idx == NULL) {
+               talloc_free(path_dup);
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       str_idx++;
+       snap_id = strtoul(str_idx, &str_end, 10);
+       if (str_idx == str_end) {
+               talloc_free(path_dup);
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       talloc_free(path_dup);
+       *snap_id_out = snap_id;
+       return NT_STATUS_OK;
+}
+
 /*
  * Determine the snapper snapshot path given an id and base.
  * Ideally this should be determined via a lookup.
@@ -1009,6 +1356,242 @@ 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.
+ */
+static NTSTATUS snapper_snap_check_path(struct vfs_handle_struct *handle,
+                                       TALLOC_CTX *mem_ctx,
+                                       const char *service_path,
+                                       char **base_volume)
+{
+       NTSTATUS status;
+       DBusConnection *dconn;
+       char *conf_name;
+       char *base_path;
+
+       dconn = snapper_dbus_conn_create();
+       if (dconn == NULL) {
+               return NT_STATUS_UNSUCCESSFUL;
+       }
+
+       status = snapper_get_conf_call(mem_ctx, dconn, service_path,
+                                      &conf_name, &base_path);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto err_conn_close;
+       }
+
+       talloc_free(conf_name);
+       *base_volume = base_path;
+       snapper_dbus_conn_destroy(dconn);
+
+       return NT_STATUS_OK;
+
+err_conn_close:
+       snapper_dbus_conn_destroy(dconn);
+       return status;
+}
+
+static NTSTATUS snapper_create_snap_call(TALLOC_CTX *mem_ctx,
+                                        DBusConnection *dconn,
+                                        const char *conf_name,
+                                        const char *base_path,
+                                        const char *snap_desc,
+                                        uint32_t num_user_data,
+                                        struct snapper_dict *user_data,
+                                        char **snap_path_out)
+{
+       NTSTATUS status;
+       DBusMessage *req_msg;
+       DBusMessage *rsp_msg;
+       uint32_t snap_id;
+       char *snap_path;
+
+       status = snapper_create_snap_pack(mem_ctx,
+                                         conf_name,
+                                         snap_desc,
+                                         num_user_data,
+                                         user_data,
+                                         &req_msg);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto err_out;
+       }
+
+       status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto err_req_free;
+       }
+
+       status = snapper_create_snap_unpack(dconn, rsp_msg, &snap_id);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto err_rsp_free;
+       }
+
+       status = snapper_snap_id_to_path(mem_ctx, base_path, snap_id,
+                                        &snap_path);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto err_rsp_free;
+       }
+
+       dbus_message_unref(rsp_msg);
+       dbus_message_unref(req_msg);
+
+       DEBUG(6, ("created new snapshot %u at %s\n", snap_id, snap_path));
+       *snap_path_out = snap_path;
+
+       return NT_STATUS_OK;
+
+err_rsp_free:
+       dbus_message_unref(rsp_msg);
+err_req_free:
+       dbus_message_unref(req_msg);
+err_out:
+       return status;
+}
+
+static NTSTATUS snapper_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)
+{
+       DBusConnection *dconn;
+       NTSTATUS status;
+       char *conf_name;
+       char *base_path;
+       char *snap_path;
+       TALLOC_CTX *tmp_ctx;
+
+       tmp_ctx = talloc_new(mem_ctx);
+       if (tmp_ctx == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       dconn = snapper_dbus_conn_create();
+       if (dconn == NULL) {
+               talloc_free(tmp_ctx);
+               return NT_STATUS_UNSUCCESSFUL;
+       }
+
+       status = snapper_get_conf_call(tmp_ctx, dconn, base_volume,
+                                      &conf_name, &base_path);
+       if (!NT_STATUS_IS_OK(status)) {
+               snapper_dbus_conn_destroy(dconn);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+
+       status = snapper_create_snap_call(tmp_ctx, dconn,
+                                         conf_name, base_path,
+                                         "Snapshot created by Samba",
+                                         0, NULL,
+                                         &snap_path);
+       if (!NT_STATUS_IS_OK(status)) {
+               snapper_dbus_conn_destroy(dconn);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+
+       snapper_dbus_conn_destroy(dconn);
+       *_base_path = talloc_steal(mem_ctx, base_path);
+       *_snap_path = talloc_steal(mem_ctx, snap_path);
+       talloc_free(tmp_ctx);
+
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_delete_snap_call(TALLOC_CTX *mem_ctx,
+                                        DBusConnection *dconn,
+                                        const char *conf_name,
+                                        uint32_t snap_id)
+{
+       NTSTATUS status;
+       DBusMessage *req_msg;
+       DBusMessage *rsp_msg;
+
+       status = snapper_del_snap_pack(mem_ctx, conf_name, snap_id, &req_msg);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto err_out;
+       }
+
+       status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto err_req_free;
+       }
+
+       status = snapper_del_snap_unpack(dconn, rsp_msg);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto err_rsp_free;
+       }
+
+       dbus_message_unref(rsp_msg);
+       dbus_message_unref(req_msg);
+
+       DEBUG(6, ("deleted snapshot %u\n", snap_id));
+
+       return NT_STATUS_OK;
+
+err_rsp_free:
+       dbus_message_unref(rsp_msg);
+err_req_free:
+       dbus_message_unref(req_msg);
+err_out:
+       return status;
+}
+
+static NTSTATUS snapper_snap_delete(struct vfs_handle_struct *handle,
+                                   TALLOC_CTX *mem_ctx,
+                                   char *base_path,
+                                   char *snap_path)
+{
+       DBusConnection *dconn;
+       NTSTATUS status;
+       char *conf_name;
+       char *snap_base_path;
+       uint32_t snap_id;
+       TALLOC_CTX *tmp_ctx;
+
+       tmp_ctx = talloc_new(mem_ctx);
+       if (tmp_ctx == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       dconn = snapper_dbus_conn_create();
+       if (dconn == NULL) {
+               talloc_free(tmp_ctx);
+               return NT_STATUS_UNSUCCESSFUL;
+       }
+
+       status = snapper_get_conf_call(tmp_ctx, dconn, base_path,
+                                      &conf_name, &snap_base_path);
+       if (!NT_STATUS_IS_OK(status)) {
+               snapper_dbus_conn_destroy(dconn);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+
+       status = snapper_snap_path_to_id(tmp_ctx, snap_path, &snap_id);
+       if (!NT_STATUS_IS_OK(status)) {
+               snapper_dbus_conn_destroy(dconn);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+
+       status = snapper_delete_snap_call(tmp_ctx, dconn, conf_name, snap_id);
+       if (!NT_STATUS_IS_OK(status)) {
+               snapper_dbus_conn_destroy(dconn);
+               talloc_free(tmp_ctx);
+               return status;
+       }
+
+       snapper_dbus_conn_destroy(dconn);
+       talloc_free(tmp_ctx);
+
+       return NT_STATUS_OK;
+}
+
 /* sc_data used as parent talloc context for all labels */
 static int snapper_get_shadow_copy_data(struct vfs_handle_struct *handle,
                                        struct files_struct *fsp,
@@ -2186,6 +2769,9 @@ static uint64_t snapper_gmt_disk_free(vfs_handle_struct *handle,
 
 
 static struct vfs_fn_pointers snapper_fns = {
+       .snap_check_path_fn = snapper_snap_check_path,
+       .snap_create_fn = snapper_snap_create,
+       .snap_delete_fn = snapper_snap_delete,
        .get_shadow_copy_data_fn = snapper_get_shadow_copy_data,
        .opendir_fn = snapper_gmt_opendir,
        .disk_free_fn = snapper_gmt_disk_free,