From b1e9fab5de8ab45987b2eed9bfd12a1eb188707b Mon Sep 17 00:00:00 2001 From: shishir gowda Date: Tue, 22 Oct 2013 16:55:55 +0530 Subject: mgmt/glusterd:store for CG Change-Id: I6ec888a5553ad29ded032c02c80dd940b2aae007 Signed-off-by: shishir gowda --- xlators/mgmt/glusterd/src/glusterd-snapshot.c | 211 +++++++------- xlators/mgmt/glusterd/src/glusterd-store.c | 386 +++++++++++++++++++++++++- xlators/mgmt/glusterd/src/glusterd-store.h | 1 + xlators/mgmt/glusterd/src/glusterd.h | 5 + 4 files changed, 493 insertions(+), 110 deletions(-) diff --git a/xlators/mgmt/glusterd/src/glusterd-snapshot.c b/xlators/mgmt/glusterd/src/glusterd-snapshot.c index f3428ff03..39392c655 100644 --- a/xlators/mgmt/glusterd/src/glusterd-snapshot.c +++ b/xlators/mgmt/glusterd/src/glusterd-snapshot.c @@ -43,107 +43,6 @@ #include #endif -int -glusterd_handle_snapshot_fn (rpcsvc_request_t *req) -{ - int32_t ret = 0; - dict_t *dict = NULL; - gf_cli_req cli_req = {{0},}; - glusterd_op_t cli_op = GD_OP_SNAP; - int type = 0; - glusterd_conf_t *conf = NULL; - char *host_uuid = NULL; - char err_str[2048] = {0,}; - xlator_t *this = NULL; - - GF_ASSERT (req); - - this = THIS; - GF_ASSERT (this); - conf = this->private; - GF_ASSERT (conf); - - ret = xdr_to_generic (req->msg[0], &cli_req, - (xdrproc_t)xdr_gf_cli_req); - if (ret < 0) { - req->rpc_err = GARBAGE_ARGS; - goto out; - } - - if (cli_req.dict.dict_len > 0) { - dict = dict_new (); - if (!dict) - goto out; - - ret = dict_unserialize (cli_req.dict.dict_val, - cli_req.dict.dict_len, - &dict); - if (ret < 0) { - gf_log (this->name, GF_LOG_ERROR, "failed to " - "unserialize req-buffer to dictionary"); - snprintf (err_str, sizeof (err_str), "Unable to decode " - "the command"); - goto out; - } - - dict->extra_stdfree = cli_req.dict.dict_val; - - host_uuid = gf_strdup (uuid_utoa(MY_UUID)); - if (host_uuid == NULL) { - snprintf (err_str, sizeof (err_str), "Failed to get " - "the uuid of local glusterd"); - ret = -1; - goto out; - } - ret = dict_set_dynstr (dict, "host-uuid", host_uuid); - if (ret) - goto out; - - } else { - gf_log (this->name, GF_LOG_ERROR, "request dict length is %d", - cli_req.dict.dict_len); - goto out; - } - - ret = dict_get_int32 (dict, "type", &type); - if (ret < 0) { - snprintf (err_str, sizeof (err_str), "Command type not found"); - gf_log (this->name, GF_LOG_ERROR, "%s", err_str); - goto out; - } - - switch (type) { - case GF_SNAP_OPTION_TYPE_CREATE: - ret = glusterd_mgmt_v3_initiate_all_phases (req, cli_op, dict); - break; - case GF_SNAP_OPTION_TYPE_LIST: - ret = glusterd_handle_snapshot_list (req, cli_op, dict); - break; - default: - gf_log (this->name, GF_LOG_ERROR, "Unkown snapshot request " - "type (%d)", type); - ret = -1; /* Failure */ - } - -out: - if (ret) { - GF_FREE (host_uuid); - if (err_str[0] == '\0') - snprintf (err_str, sizeof (err_str), - "Operation failed"); - ret = glusterd_op_send_cli_response (cli_op, ret, 0, req, - dict, err_str); - } - - return ret; -} - -int -glusterd_handle_snapshot (rpcsvc_request_t *req) -{ - return glusterd_big_locked_handler (req, glusterd_handle_snapshot_fn); -} - int glusterd_snapshot_prevalidate (dict_t *dict, char **op_errstr, dict_t *rsp_dict) @@ -1733,7 +1632,7 @@ glusterd_build_snap_device_path (char *device, char *snapname) snap_device = gf_strdup (snap); if (!snap_device) { gf_log (this->name, GF_LOG_WARNING, "cannot copy the " - "snapshot device name (volname: %s, " + "snapshot device name " "snapname: %s)", snapname); goto out; } @@ -1825,7 +1724,6 @@ glusterd_snap_brick_create (char *device, glusterd_volinfo_t *snap_volinfo, char *mnt_pt = NULL; struct mntent *entry = NULL; FILE *mtab = NULL; - char msg[PATH_MAX] = {0, }; this = THIS; priv = this->private; @@ -1928,12 +1826,6 @@ glusterd_do_snap (glusterd_volinfo_t *volinfo, char *name, dict_t *dict, int32_t ret = -1; xlator_t *this = NULL; glusterd_conf_t *priv = NULL; - char volfile[PATH_MAX] = {0,}; - char exp_path[PATH_MAX] = {0,}; - char logfile[PATH_MAX] = {0,}; - int port = 0; - int rdma_port = 0; - char socketpath[PATH_MAX] = {0}; glusterd_brickinfo_t *brickinfo = NULL; char *device = NULL; char snapname[PATH_MAX] = {0, }; @@ -2325,3 +2217,104 @@ glusterd_snapshot (dict_t *dict, char **op_errstr, dict_t *rsp_dict) out: return ret; } + +int +glusterd_handle_snapshot_fn (rpcsvc_request_t *req) +{ + int32_t ret = 0; + dict_t *dict = NULL; + gf_cli_req cli_req = {{0},}; + glusterd_op_t cli_op = GD_OP_SNAP; + int type = 0; + glusterd_conf_t *conf = NULL; + char *host_uuid = NULL; + char err_str[2048] = {0,}; + xlator_t *this = NULL; + + GF_ASSERT (req); + + this = THIS; + GF_ASSERT (this); + conf = this->private; + GF_ASSERT (conf); + + ret = xdr_to_generic (req->msg[0], &cli_req, + (xdrproc_t)xdr_gf_cli_req); + if (ret < 0) { + req->rpc_err = GARBAGE_ARGS; + goto out; + } + + if (cli_req.dict.dict_len > 0) { + dict = dict_new (); + if (!dict) + goto out; + + ret = dict_unserialize (cli_req.dict.dict_val, + cli_req.dict.dict_len, + &dict); + if (ret < 0) { + gf_log (this->name, GF_LOG_ERROR, "failed to " + "unserialize req-buffer to dictionary"); + snprintf (err_str, sizeof (err_str), "Unable to decode " + "the command"); + goto out; + } + + dict->extra_stdfree = cli_req.dict.dict_val; + + host_uuid = gf_strdup (uuid_utoa(MY_UUID)); + if (host_uuid == NULL) { + snprintf (err_str, sizeof (err_str), "Failed to get " + "the uuid of local glusterd"); + ret = -1; + goto out; + } + ret = dict_set_dynstr (dict, "host-uuid", host_uuid); + if (ret) + goto out; + + } else { + gf_log (this->name, GF_LOG_ERROR, "request dict length is %d", + cli_req.dict.dict_len); + goto out; + } + + ret = dict_get_int32 (dict, "type", &type); + if (ret < 0) { + snprintf (err_str, sizeof (err_str), "Command type not found"); + gf_log (this->name, GF_LOG_ERROR, "%s", err_str); + goto out; + } + + switch (type) { + case GF_SNAP_OPTION_TYPE_CREATE: + ret = glusterd_mgmt_v3_initiate_all_phases (req, cli_op, dict); + break; + case GF_SNAP_OPTION_TYPE_LIST: + ret = glusterd_handle_snapshot_list (req, cli_op, dict); + break; + default: + gf_log (this->name, GF_LOG_ERROR, "Unkown snapshot request " + "type (%d)", type); + ret = -1; /* Failure */ + } + +out: + if (ret) { + GF_FREE (host_uuid); + if (err_str[0] == '\0') + snprintf (err_str, sizeof (err_str), + "Operation failed"); + ret = glusterd_op_send_cli_response (cli_op, ret, 0, req, + dict, err_str); + } + + return ret; +} + +int +glusterd_handle_snapshot (rpcsvc_request_t *req) +{ + return glusterd_big_locked_handler (req, glusterd_handle_snapshot_fn); +} diff --git a/xlators/mgmt/glusterd/src/glusterd-store.c b/xlators/mgmt/glusterd/src/glusterd-store.c index 50e680064..b1ce059c3 100644 --- a/xlators/mgmt/glusterd/src/glusterd-store.c +++ b/xlators/mgmt/glusterd/src/glusterd-store.c @@ -731,6 +731,17 @@ glusterd_store_create_volume_dir (glusterd_volinfo_t *volinfo) return ret; } +static void +glusterd_store_vol_snaps_cg_dirpath_set (char *cgdirpath, size_t len) +{ + glusterd_conf_t *priv = NULL; + + priv = THIS->private; + GF_ASSERT (priv); + + snprintf (cgdirpath, len, "%s/%s", priv->workdir, + GLUSTERD_VOL_SNAP_CG_DIR_PREFIX); +} static void glusterd_store_vol_snaps_dirpath_set (glusterd_volinfo_t *volinfo, char *snapdirpath, size_t len) @@ -779,7 +790,19 @@ glusterd_store_create_snaps_dir (glusterd_volinfo_t *volinfo) return ret; } +/* creates GLUSTERD_VOLUME_DIR_PREFIX/cg directory */ +static int32_t +glusterd_store_create_snaps_cg_dir () +{ + int32_t ret = -1; + char cgdirpath[PATH_MAX] = {0,}; + glusterd_store_vol_snaps_cg_dirpath_set (cgdirpath, + sizeof (cgdirpath)); + ret = gf_store_mkdir (cgdirpath); + gf_log (THIS->name, GF_LOG_DEBUG, "Returning with %d", ret); + return ret; +} /* creates GLUSTERD_VOLUME_DIR_PREFIX//snaps/ directory */ static int32_t glusterd_store_create_snap_vol_dir (glusterd_volinfo_t *volinfo, char *snap_name) @@ -912,6 +935,34 @@ glusterd_store_create_snap_vol_shandle_on_absence (glusterd_volinfo_t *volinfo, ret = gf_store_handle_create_on_absence (&snapinfo->shandle, volfpath); return ret; } + +static void +glusterd_store_snap_cgfpath_set (char *cg_name, + char *cgfpath, size_t len) +{ + char cgdirpath[PATH_MAX] = {0,}; + GF_ASSERT (cg_name); + GF_ASSERT (cgfpath); + GF_ASSERT (len <= PATH_MAX); + + glusterd_store_vol_snaps_cg_dirpath_set (cgdirpath, + sizeof (cgdirpath)); + snprintf (cgfpath, len, "%s/%s.info", cgdirpath, cg_name); +} + +int32_t +glusterd_store_create_snap_cg_shandle_on_absence (glusterd_snap_cg_t *cg) +{ + char cgfpath[PATH_MAX] = {0}; + int32_t ret = 0; + + GF_ASSERT (cg); + + glusterd_store_snap_cgfpath_set (cg->cg_name, cgfpath, + sizeof (cgfpath)); + ret = gf_store_handle_create_on_absence (&cg->shandle, cgfpath); + return ret; +} int32_t glusterd_store_create_rbstate_shandle_on_absence (glusterd_volinfo_t *volinfo) { @@ -1177,6 +1228,78 @@ out: return ret; } +int32_t +glusterd_store_snap_cg_write (int fd, glusterd_snap_cg_t *cg) +{ + int ret = -1; + char buf[PATH_MAX] = {0, }; + uint64_t count = 0; + + GF_ASSERT (fd > 0); + GF_ASSERT (cg); + + ret = gf_store_save_value (fd, GLUSTERD_STORE_KEY_SNAP_NAME, + cg->cg_name); + if (ret) + goto out; + + snprintf (buf, sizeof (buf), "%"PRIu64, cg->volume_count); + ret = gf_store_save_value (fd, GLUSTERD_STORE_KEY_CG_VOL_COUNT, buf); + if (ret) + goto out; + + ret = gf_store_save_value (fd, GLUSTERD_STORE_KEY_SNAP_CG_ID, + uuid_utoa(cg->cg_id)); + if (ret) + goto out; + + snprintf (buf, sizeof (buf), "%d", cg->cg_status); + ret = gf_store_save_value (fd, GLUSTERD_STORE_KEY_SNAP_STATUS, buf); + if (ret) + goto out; + while (count < cg->volume_count) { + ret = gf_store_save_value (fd, GLUSTERD_STORE_KEY_VOL_ID, + uuid_utoa (cg->volumes[count].volume_id)); + if (ret) + goto out; + + count++; + } +out: + gf_log (THIS->name, GF_LOG_DEBUG, "Returning %d", ret); + return ret; +} +int32_t +glusterd_store_perform_snap_cg_store (glusterd_snap_cg_t *cg) +{ + int fd = -1; + int32_t ret = -1; + GF_ASSERT (cg); + + ret = glusterd_store_create_snap_cg_shandle_on_absence (cg); + if (ret) { + gf_log (THIS->name, GF_LOG_ERROR, "Failed to create " + " shandle for cg %s ", cg->cg_name); + goto out; + } + fd = gf_store_mkstemp (cg->shandle); + if (fd <= 0) { + ret = -1; + goto out; + } + + ret = glusterd_store_snap_cg_write (fd, cg); + if (ret) + goto out; +out: + if (ret && (fd > 0)) + gf_store_unlink_tmppath (cg->shandle); + if (fd > 0) + close (fd); + gf_log (THIS->name, GF_LOG_DEBUG, "Returning %d", ret); + return ret; +} + int32_t glusterd_store_perform_snap_volume_store (glusterd_volinfo_t *volinfo, glusterd_volinfo_t *snap_volinfo) @@ -1219,7 +1342,6 @@ out: gf_log (THIS->name, GF_LOG_DEBUG, "Returning %d", ret); return ret; } - int32_t glusterd_store_snap_volume (glusterd_volinfo_t *volinfo, glusterd_snap_t *snap) { @@ -1318,8 +1440,59 @@ out: return ret; } +int32_t +glusterd_store_snap_cg (glusterd_snap_cg_t *cg) +{ + int32_t ret = -1; + + GF_ASSERT (cg); + + + LOCK (&cg->lock); + { + ret = glusterd_store_perform_snap_cg_store (cg); + if (ret) { + gf_log (THIS->name, GF_LOG_ERROR, + "Failed store snap cg (%s)", cg->cg_name); + goto unlock; + } + } +unlock: + UNLOCK (&cg->lock); + + return 0; + +} +int32_t +glusterd_store_perform_snap_cgs_store (glusterd_conf_t *priv) +{ + int32_t ret = -1; + glusterd_snap_cg_t *entry = NULL; + glusterd_snap_cg_t *tmp = NULL; + + GF_ASSERT (priv); + ret = glusterd_store_create_snaps_cg_dir (priv); + if (ret) + goto out; + +// LOCK (&priv->lock); + { + list_for_each_entry_safe (entry, tmp, &priv->snap_cg, + cg_list) { + ret = glusterd_store_snap_cg (entry); + if (ret) + goto unlock; + } + } +unlock: +// UNLOCK (&priv->lock); + +out: + gf_log (THIS->name, GF_LOG_DEBUG, "Returning %d", ret); + return ret; +} int32_t glusterd_store_perform_node_state_store (glusterd_volinfo_t *volinfo) @@ -1541,7 +1714,73 @@ out: return ret; } +int32_t +glusterd_store_delete_snap_cg (glusterd_snap_cg_t *cg) +{ + char pathname[PATH_MAX] = {0,}; + int32_t ret = 0; + glusterd_conf_t *priv = NULL; + char path[PATH_MAX] = {0,}; + char delete_path[PATH_MAX] = {0,}; + char trashdir[PATH_MAX] = {0,}; + xlator_t *this = NULL; + gf_boolean_t rename_fail = _gf_false; + + this = THIS; + GF_ASSERT (this); + + GF_ASSERT (cg); + priv = this->private; + + GF_ASSERT (priv); + + glusterd_store_snap_cgfpath_set (cg->cg_name, pathname, sizeof (path)); + snprintf (delete_path, sizeof (delete_path), + "%s/"GLUSTERD_TRASH"/%s/%s.deleted", priv->workdir, + GLUSTERD_VOL_SNAP_CG_DIR_PREFIX, cg->cg_name); + + snprintf (trashdir, sizeof (trashdir), "%s/"GLUSTERD_TRASH"/%s", + priv->workdir, GLUSTERD_VOL_SNAP_CG_DIR_PREFIX); + + ret = mkdir (trashdir, 0777); + if (ret && errno != EEXIST) { + gf_log (this->name, GF_LOG_ERROR, "Failed to create trash " + "directory, reason : %s", strerror (errno)); + ret = -1; + goto out; + } + + ret = rename (pathname, delete_path); + if (ret) { + gf_log (this->name, GF_LOG_ERROR, "Failed to rename %s cg " + "info file", cg->cg_name); + rename_fail = _gf_true; + goto out; + } + + ret = unlink (delete_path); + if (ret) { + gf_log (this->name, GF_LOG_DEBUG, " Failed to remove " + "%s. Reason : %s", delete_path, strerror (errno)); + } + + ret = rmdir (trashdir); + if (ret) { + gf_log (this->name, GF_LOG_DEBUG, "Failed to rmdir: %s, Reason:" + " %s", trashdir, strerror (errno)); + } + +out: + if (!ret && cg->shandle) { + gf_store_handle_destroy (cg->shandle); + cg->shandle = NULL; + } + ret = (rename_fail == _gf_true) ? -1: 0; + + gf_log (this->name, GF_LOG_DEBUG, "Returning %d", ret); + return ret; +} int32_t glusterd_store_delete_volume (glusterd_volinfo_t *volinfo, glusterd_volinfo_t *snapinfo) @@ -2548,6 +2787,109 @@ out: return ret; } +int32_t +glusterd_store_retrieve_snap_cg (char *cg_name, glusterd_conf_t *priv) +{ + int32_t ret = -1; + gf_store_iter_t *iter = NULL; + char *key = NULL; + char *value = NULL; + char cg_path[PATH_MAX] = {0,}; + char path[PATH_MAX] = {0,}; + gf_store_op_errno_t op_errno = GD_STORE_SUCCESS; + glusterd_snap_cg_t *cg = NULL; + uint64_t vol_count = 0; + gf_store_handle_t *tmp_shandle = NULL; + uint64_t count = 0; + glusterd_volinfo_t *volinfo = NULL; + glusterd_volinfo_t *tmpinfo = NULL; + uuid_t vol_id = {0, }; + + GF_ASSERT (cg_name); + GF_ASSERT (priv); + + glusterd_store_snap_cgfpath_set (cg_name, path, sizeof (path)); + ret = gf_store_handle_retrieve (path, &tmp_shandle); + if (ret) + goto out; + + ret = gf_store_iter_new (tmp_shandle, &iter); + if (ret) + goto out; + ret = gf_store_iter_get_matching (iter, + GLUSTERD_STORE_KEY_CG_VOL_COUNT, + &value); + if (ret) + goto out; + + vol_count = atoi (value); + + GF_FREE (value); + value = NULL; + + cg = glusterd_new_snap_cg_object (vol_count); + if (!cg) { + gf_log (THIS->name, GF_LOG_ERROR, + "Failed to create snap cg object"); + goto out; + } + + cg->shandle = tmp_shandle; + + GLUSTERD_GET_SNAP_CG_DIR (cg_path, priv); + snprintf (path, sizeof (path), "%s/%s", cg_path, + cg_name); + cg->volume_count = atoi (value); + + ret = gf_store_iter_get_next (iter, &key, &value, &op_errno); + while (!ret) { + + if (!strncmp(key, GLUSTERD_STORE_KEY_SNAP_NAME, + sizeof (*key))) { + strncpy (cg->cg_name, value, sizeof (*value)); + } else if (!strncmp(key, GLUSTERD_STORE_KEY_SNAP_STATUS, + sizeof (*key))) { + cg->cg_status = atoi (value); + } else if (!strncmp (key, GLUSTERD_STORE_KEY_SNAP_CG_ID, + sizeof (*key))) { + uuid_parse (value, cg->cg_id); + } else if (!strncmp (key, GLUSTERD_STORE_KEY_VOL_ID, + sizeof (*key))) { + uuid_parse (key, vol_id); + ret = glusterd_volinfo_find_by_volume_id (vol_id, &volinfo); + if (ret) + break; + if (count < vol_count) { + tmpinfo = &cg->volumes[count]; + tmpinfo = volinfo; + count++; + } + } + + GF_FREE (value); + value = NULL; + } + + ret = glusterd_add_snap_cg (priv, cg); + if (ret) { + gf_log (THIS->name, GF_LOG_ERROR, "Failed to add %s to" + " cg_list", cg_name); + goto out; + } + + if (op_errno != GD_STORE_EOF) + goto out; + + ret = gf_store_iter_destroy (iter); + + if (ret) + goto out; + +out: + gf_log ("", GF_LOG_DEBUG, "Returning with %d", ret); + + return ret; +} int32_t glusterd_store_retrieve_snap_list (char *volname) { @@ -2670,6 +3012,44 @@ out: return ret; } + +int32_t +glusterd_store_retrieve_snap_cgs (xlator_t *this) +{ + int32_t ret = 0; + char path[PATH_MAX] = {0,}; + glusterd_conf_t *priv = NULL; + DIR *dir = NULL; + struct dirent *entry = NULL; + + GF_ASSERT (this); + priv = this->private; + + GF_ASSERT (priv); + + snprintf (path, PATH_MAX, "%s/%s", priv->workdir, + GLUSTERD_VOL_SNAP_CG_DIR_PREFIX); + + dir = opendir (path); + + if (!dir) { + gf_log ("", GF_LOG_ERROR, "Unable to open dir %s", path); + ret = -1; + goto out; + } + + glusterd_for_each_entry (entry, dir); + + while (entry) { + ret = glusterd_store_retrieve_snap_cg (entry->d_name, priv); + if (ret) + goto out; + glusterd_for_each_entry (entry, dir); + } +out: + return 0; +} + int32_t glusterd_store_retrieve_volumes (xlator_t *this) { @@ -3155,6 +3535,10 @@ glusterd_restore () if (ret) goto out; + ret = glusterd_store_retrieve_snap_cgs (this); + if (ret) + goto out; + ret = glusterd_store_retrieve_peers (this); if (ret) goto out; diff --git a/xlators/mgmt/glusterd/src/glusterd-store.h b/xlators/mgmt/glusterd/src/glusterd-store.h index ae767398a..ece2003d8 100644 --- a/xlators/mgmt/glusterd/src/glusterd-store.h +++ b/xlators/mgmt/glusterd/src/glusterd-store.h @@ -66,6 +66,7 @@ typedef enum glusterd_store_ver_ac_{ #define GLUSTERD_STORE_KEY_SNAP_TIMESTAMP "time-stamp" #define GLUSTERD_STORE_KEY_SNAP_STATUS "status" #define GLUSTERD_STORE_KEY_SNAP_COUNT "count" +#define GLUSTERD_STORE_KEY_CG_VOL_COUNT "count" #define GLUSTERD_STORE_KEY_BRICK_HOSTNAME "hostname" #define GLUSTERD_STORE_KEY_BRICK_PATH "path" diff --git a/xlators/mgmt/glusterd/src/glusterd.h b/xlators/mgmt/glusterd/src/glusterd.h index 48da712f8..10b338dda 100644 --- a/xlators/mgmt/glusterd/src/glusterd.h +++ b/xlators/mgmt/glusterd/src/glusterd.h @@ -406,6 +406,7 @@ enum glusterd_vol_comp_status_ { #define GLUSTERD_NODE_STATE_FILE "node_state.info" #define GLUSTERD_VOL_SNAP_FILE "snap_list.info" #define GLUSTERD_VOL_SNAP_DIR_PREFIX "snaps" +#define GLUSTERD_VOL_SNAP_CG_DIR_PREFIX "cgs" #define GLUSTERD_DEFAULT_SNAPS_BRICK_DIR "/var/run/gluster/snaps" @@ -424,6 +425,10 @@ typedef ssize_t (*gd_serialize_t) (struct iovec outmsg, void *args); snprintf (path, PATH_MAX, "%s/vols/%s", priv->workdir,\ volinfo->volname); +#define GLUSTERD_GET_SNAP_CG_DIR(path, priv) \ + snprintf (path, PATH_MAX, "%s/%s", priv->workdir,\ + GLUSTERD_VOL_SNAP_CG_DIR_PREFIX); + #define GLUSTERD_GET_SNAP_DIR(path, volinfo, snap_name, priv) \ snprintf (path, PATH_MAX, "%s/vols/%s/snaps/%s", priv->workdir,\ volinfo->volname, snap_name); -- cgit