/*
Copyright (c) 2010-2013 Red Hat, Inc. <http://www.redhat.com>
This file is part of GlusterFS.
This file is licensed to you under your choice of the GNU Lesser
General Public License, version 3 or any later version (LGPLv3 or
later), or the GNU General Public License, version 2 (GPLv2), in all
cases as published by the Free Software Foundation.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <pthread.h>
#include <fnmatch.h>
#include <time.h>
#include "cli.h"
#include "cli-cmd.h"
#include "cli-mem-types.h"
#include <glusterfs/dict.h>
#include <glusterfs/list.h>
#include "protocol-common.h"
#include "cli1-xdr.h"
#define MAX_SNAP_DESCRIPTION_LEN 1024
struct snap_config_opt_vals_ snap_confopt_vals[] = {
{.op_name = "snap-max-hard-limit",
.question = "Changing snapshot-max-hard-limit "
"will limit the creation of new snapshots "
"if they exceed the new limit.\n"
"Do you want to continue?"},
{.op_name = "snap-max-soft-limit",
.question = "If Auto-delete is enabled, snap-max-soft-limit will"
" trigger deletion of oldest snapshot, on the "
"creation of new snapshot, when the "
"snap-max-soft-limit is reached.\n"
"Do you want to change the snap-max-soft-limit?"},
{.op_name = "both",
.question = "Changing snapshot-max-hard-limit "
"will limit the creation of new snapshots "
"if they exceed the new snapshot-max-hard-limit.\n"
"If Auto-delete is enabled, snap-max-soft-limit will"
" trigger deletion of oldest snapshot, on the "
"creation of new snapshot, when the "
"snap-max-soft-limit is reached.\n"
"Do you want to continue?"},
{
.op_name = NULL,
}};
enum cli_snap_config_set_types {
GF_SNAP_CONFIG_SET_HARD = 0,
GF_SNAP_CONFIG_SET_SOFT = 1,
GF_SNAP_CONFIG_SET_BOTH = 2,
};
typedef enum cli_snap_config_set_types cli_snap_config_set_types;
typedef struct _cli_brick {
struct list_head list;
const char *name;
int32_t len;
} cli_brick_t;
int
cli_cmd_validate_volume(char *volname);
static const char *
id_sel(void *wcon)
{
return (const char *)wcon;
}
static char *
str_getunamb(const char *tok, char **opwords)
{
return (char *)cli_getunamb(tok, (void **)opwords, id_sel);
}
int32_t
cli_cmd_bricks_parse(const char **words, int wordcount, int brick_index,
char **bricks, int *brick_count)
{
int ret = 0;
char *delimiter = NULL;
char *host_name = NULL;
char *tmp_host = NULL;
char *bricks_str = NULL;
int len = 0;
int brick_list_len = 1; /* For initial space */
struct list_head brick_list = {
0,
};
cli_brick_t *brick = NULL;
GF_ASSERT(words);
GF_ASSERT(wordcount);
GF_ASSERT(bricks);
GF_ASSERT(brick_index > 0);
GF_ASSERT(brick_index < wordcount);
INIT_LIST_HEAD(&brick_list);
while (brick_index < wordcount) {
if (validate_brick_name((char *)words[brick_index])) {
cli_err(
"Wrong brick type: %s, use <HOSTNAME>:"
"<export-dir-abs-path>",
words[brick_index]);
ret = -1;
goto out;
} else {
delimiter = strrchr(words[brick_index], ':');
ret = gf_canonicalize_path(delimiter + 1);
if (ret)
goto out;
}
tmp_host = gf_strdup((char *)words[brick_index]);
if (!tmp_host) {
gf_log("cli", GF_LOG_ERROR, "Out of memory");
ret = -1;
goto out;
}
get_host_name(tmp_host, &host_name);
if (!host_name) {
ret = -1;
gf_log("cli", GF_LOG_ERROR,
"Unable to allocate "
"memory");
GF_FREE(tmp_host);
goto out;
}
if (!(strcmp(host_name, "localhost") &&
strcmp(host_name, "127.0.0.1") && strncmp(host_name, "0.", 2))) {
cli_err(
"Please provide a valid hostname/ip other "
"than localhost, 127.0.0.1 or loopback "
"address (0.0.0.0 to 0.255.255.255).");
ret = -1;
GF_FREE(tmp_host);
goto out;
}
if (!valid_internet_address(host_name, _gf_false, _gf_false)) {
cli_err(
"internet address '%s' does not conform to "
"standards",
host_name);
}
GF_FREE(tmp_host);
list_for_each_entry(brick, &brick_list, list)
{
if (strcmp(brick->name, words[brick_index]) == 0) {
ret = -1;
cli_err("Found duplicate exports %s", words[brick_index]);
goto out;
}
}
brick = GF_MALLOC(sizeof(cli_brick_t), gf_common_list_node);
if (brick == NULL) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Out of memory");
goto out;
}
len = strlen(words[brick_index]);
brick->name = words[brick_index];
brick->len = len;
list_add_tail(&brick->list, &brick_list);
brick_list_len += len + 1; /* Brick name + space */
++(*brick_count);
++brick_index;
}
/* If brick count is not valid exit here */
if (!*brick_count) {
cli_err("No bricks specified");
ret = -1;
goto out;
}
brick_list_len++; /* For terminating null char */
bricks_str = GF_MALLOC(brick_list_len, gf_common_mt_char);
if (bricks_str == NULL) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Out of memory");
goto out;
}
*bricks = bricks_str;
*bricks_str = ' ';
bricks_str++;
while (!list_empty(&brick_list)) {
brick = list_first_entry(&brick_list, cli_brick_t, list);
list_del_init(&brick->list);
memcpy(bricks_str, brick->name, brick->len);
bricks_str[brick->len] = ' ';
bricks_str += brick->len + 1;
GF_FREE(brick);
}
*bricks_str = 0;
out:
while (!list_empty(&brick_list)) {
brick = list_first_entry(&brick_list, cli_brick_t, list);
list_del_init(&brick->list);
GF_FREE(brick);
}
return ret;
}
int32_t
cli_cmd_create_disperse_check(struct cli_state *state, int *disperse,
int *redundancy, int *data, int count)
{
int i = 0;
int tmp = 0;
gf_answer_t answer = GF_ANSWER_NO;
char question[128];
const char *question1 =
"There isn't an optimal redundancy value "
"for this configuration. Do you want to "
"create the volume with redundancy 1 ?";
const char *question2 =
"The optimal redundancy for this "
"configuration is %d. Do you want to create "
"the volume with this value ?";
const char *question3 =
"This configuration is not optimal on most "
"workloads. Do you want to use it ?";
const char *question4 =
"Redundancy for this configuration is %d. "
"Do you want to create "
"the volume with this value ?";
if (*data > 0) {
if (*disperse > 0 && *redundancy > 0) {
if (*disperse != (*data + *redundancy)) {
cli_err(
"Disperse count(%d) should be equal "
"to sum of disperse-data count(%d) and "
"redundancy count(%d)",
*disperse, *data, *redundancy);
return -1;
}
} else if (*redundancy > 0) {
*disperse = *data + *redundancy;
} else if (*disperse > 0) {
*redundancy = *disperse - *data;
} else {
if ((count - *data) >= *data) {
cli_err(
"Please provide redundancy count "
"along with disperse-data count");
return -1;
} else {
sprintf(question, question4, count - *data);
answer = cli_cmd_get_confirmation(state, question);
if (answer == GF_ANSWER_NO)
return -1;
*redundancy = count - *data;
*disperse = count;
}
}
}
if (*disperse <= 0) {
if (count < 3) {
cli_err(
"number of bricks must be greater "
"than 2");
return -1;
}
*disperse = count;
}
if (*redundancy == -1) {
tmp = *disperse - 1;
for (i = tmp / 2; (i > 0) && ((tmp & -tmp) != tmp); i--, tmp--)
;
if (i == 0) {
answer = cli_cmd_get_confirmation(state, question1);
if (answer == GF_ANSWER_NO)
return -1;
*redundancy = 1;
} else {
*redundancy = *disperse - tmp;
if (*redundancy > 1) {
sprintf(question, question2, *redundancy);
answer = cli_cmd_get_confirmation(state, question);
if (answer == GF_ANSWER_NO)
return -1;
}
}
tmp = 0;
} else {
tmp = *disperse - *redundancy;
}
if ((*redundancy < 1) || (*redundancy > (*disperse - 1) / 2)) {
cli_err(
"redundancy must be greater than or equal to 1 and "
"less than %d for a disperse %d volume",
(*disperse + 1) / 2, *disperse);
return -1;
}
if ((tmp & -tmp) != tmp) {
answer = cli_cmd_get_confirmation(state, question3);
if (answer == GF_ANSWER_NO)
return -1;
}
return 0;
}
static int32_t
cli_validate_disperse_volume(char *word, gf1_cluster_type type,
const char **words, int32_t wordcount,
int32_t index, int32_t *disperse_count,
int32_t *redundancy_count, int32_t *data_count)
{
int ret = -1;
switch (type) {
case GF_CLUSTER_TYPE_NONE:
case GF_CLUSTER_TYPE_DISPERSE:
if (strcmp(word, "disperse") == 0) {
if (*disperse_count >= 0) {
cli_err("disperse option given twice");
goto out;
}
if (wordcount < (index + 2)) {
goto out;
}
ret = gf_string2int(words[index + 1], disperse_count);
if (ret == -1 && errno == EINVAL) {
*disperse_count = 0;
ret = 1;
} else if (ret == -1) {
goto out;
} else {
if (*disperse_count < 3) {
cli_err(
"disperse count must "
"be greater than 2");
goto out;
}
ret = 2;
}
} else if (strcmp(word, "disperse-data") == 0) {
if (*data_count >= 0) {
cli_err("disperse-data option given twice");
goto out;
}
if (wordcount < (index + 2)) {
goto out;
}
ret = gf_string2int(words[index + 1], data_count);
if (ret == -1 || *data_count < 2) {
cli_err("disperse-data must be greater than 1");
goto out;
}
ret = 2;
} else if (strcmp(word, "redundancy") == 0) {
if (*redundancy_count >= 0) {
cli_err("redundancy option given twice");
goto out;
}
if (wordcount < (index + 2)) {
goto out;
}
ret = gf_string2int(words[index + 1], redundancy_count);
if (ret == -1 || *redundancy_count < 1) {
cli_err("redundancy must be greater than 0");
goto out;
}
ret = 2;
}
break;
case GF_CLUSTER_TYPE_TIER:
cli_err(
"tier-dispersed volume is not "
"supported");
goto out;
case GF_CLUSTER_TYPE_REPLICATE:
cli_err(
"replicated-dispersed volume is not "
"supported");
goto out;
default:
cli_err("Invalid type given");
break;
}
out:
return ret;
}
int32_t
cli_validate_volname(const char *volname)
{
int32_t ret = -1;
int32_t i = -1;
int volname_len;
static const char *const invalid_volnames[] = {"volume",
"type",
"subvolumes",
"option",
"end-volume",
"all",
"volume_not_in_ring",
"description",
"force",
"snap-max-hard-limit",
"snap-max-soft-limit",
"auto-delete",
"activate-on-create",
NULL};
if (volname[0] == '-')
goto out;
for (i = 0; invalid_volnames[i]; i++) {
if (!strcmp(volname, invalid_volnames[i])) {
cli_err("\"%s\" cannot be the name of a volume.", volname);
goto out;
}
}
if (strchr(volname, '/'))
goto out;
volname_len = strlen(volname);
if (volname_len > GD_VOLUME_NAME_MAX) {
cli_err("Volume name exceeds %d characters.", GD_VOLUME_NAME_MAX);
goto out;
}
for (i = 0; i < volname_len; i++) {
if (!isalnum(volname[i]) && (volname[i] != '_') &&
(volname[i] != '-')) {
cli_err(
"Volume name should not contain \"%c\""
" character.\nVolume names can only"
"contain alphanumeric, '-' and '_' "
"characters.",
volname[i]);
goto out;
}
}
ret = 0;
out:
return ret;
}
int32_t
cli_cmd_volume_create_parse(struct cli_state *state, const char **words,
int wordcount, dict_t **options, char **brick_list)
{
dict_t *dict = NULL;
char *volname = NULL;
int ret = -1;
gf1_cluster_type type = GF_CLUSTER_TYPE_NONE;
int sub_count = 1;
int brick_index = 0;
char *trans_type = NULL;
int32_t index = 0;
char *bricks = NULL;
int32_t brick_count = 0;
char *opwords[] = {"replica", "stripe", "transport", "disperse",
"redundancy", "disperse-data", "arbiter", NULL};
char *w = NULL;
int op_count = 0;
int32_t replica_count = 1;
int32_t arbiter_count = 0;
int32_t stripe_count = 1;
int32_t disperse_count = -1;
int32_t redundancy_count = -1;
int32_t disperse_data_count = -1;
gf_boolean_t is_force = _gf_false;
int wc = wordcount;
gf_answer_t answer = GF_ANSWER_NO;
const char *question = NULL;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
if (wordcount < 3)
goto out;
volname = (char *)words[2];
GF_ASSERT(volname);
/* Validate the volume name here itself */
if (cli_validate_volname(volname) < 0)
goto out;
if (wordcount < 4) {
ret = -1;
goto out;
}
type = GF_CLUSTER_TYPE_NONE;
index = 3;
while (op_count < 3) {
ret = -1;
w = str_getunamb(words[index], opwords);
if (!w) {
break;
} else if ((strcmp(w, "replica")) == 0) {
switch (type) {
case GF_CLUSTER_TYPE_STRIPE_REPLICATE:
case GF_CLUSTER_TYPE_REPLICATE:
cli_err("replica option given twice");
goto out;
case GF_CLUSTER_TYPE_NONE:
type = GF_CLUSTER_TYPE_REPLICATE;
break;
case GF_CLUSTER_TYPE_STRIPE:
cli_err("stripe option not supported");
goto out;
case GF_CLUSTER_TYPE_TIER:
cli_err(
"replicated-tiered volume is not "
"supported");
goto out;
break;
case GF_CLUSTER_TYPE_DISPERSE:
cli_err(
"replicated-dispersed volume is not "
"supported");
goto out;
default:
cli_err("Invalid type given");
goto out;
}
if (wordcount < (index + 2)) {
ret = -1;
goto out;
}
replica_count = strtol(words[index + 1], NULL, 0);
if (replica_count < 2) {
cli_err(
"replica count should be greater"
" than 1");
ret = -1;
goto out;
}
index += 2;
if (words[index]) {
if (!strcmp(words[index], "arbiter")) {
ret = gf_string2int(words[index + 1], &arbiter_count);
if ((ret == -1) || (arbiter_count != 1)) {
cli_err(
"For arbiter "
"configuration, "
"replica count must be"
" 2 and arbiter count "
"must be 1. The 3rd "
"brick of the replica "
"will be the arbiter");
ret = -1;
goto out;
}
ret = dict_set_int32(dict, "arbiter-count", arbiter_count);
if (ret)
goto out;
index += 2;
}
}
/* Do this to keep glusterd happy with sending
"replica 3 arbiter 1" options to server */
if ((arbiter_count == 1) && (replica_count == 2))
replica_count += arbiter_count;
if (replica_count == 2) {
if (strcmp(words[wordcount - 1], "force")) {
question =
"Replica 2 volumes are prone"
" to split-brain. Use "
"Arbiter or Replica 3 to "
"avoid this. See: "
"http://docs.gluster.org/en/latest/"
"Administrator%20Guide/"
"Split%20brain%20and%20ways%20to%20deal%20with%20it/."
"\nDo you still want to "
"continue?\n";
answer = cli_cmd_get_confirmation(state, question);
if (GF_ANSWER_NO == answer) {
gf_log("cli", GF_LOG_ERROR,
"Volume create "
"cancelled, exiting");
ret = -1;
goto out;
}
}
}
ret = dict_set_int32(dict, "replica-count", replica_count);
if (ret)
goto out;
} else if ((strcmp(w, "stripe")) == 0) {
cli_err("stripe option not supported");
goto out;
} else if ((strcmp(w, "transport")) == 0) {
if (trans_type) {
cli_err(
"'transport' option given more"
" than one time");
goto out;
}
if ((strcasecmp(words[index + 1], "tcp") == 0)) {
trans_type = gf_strdup("tcp");
} else if ((strcasecmp(words[index + 1], "rdma") == 0)) {
trans_type = gf_strdup("rdma");
} else if ((strcasecmp(words[index + 1], "tcp,rdma") == 0) ||
(strcasecmp(words[index + 1], "rdma,tcp") == 0)) {
trans_type = gf_strdup("tcp,rdma");
} else {
gf_log("", GF_LOG_ERROR,
"incorrect transport"
" protocol specified");
ret = -1;
goto out;
}
index += 2;
} else if ((strcmp(w, "disperse") == 0) ||
(strcmp(w, "redundancy") == 0) ||
(strcmp(w, "disperse-data") == 0)) {
ret = cli_validate_disperse_volume(
w, type, words, wordcount, index, &disperse_count,
&redundancy_count, &disperse_data_count);
if (ret < 0)
goto out;
index += ret;
type = GF_CLUSTER_TYPE_DISPERSE;
} else if ((strcmp(w, "arbiter") == 0)) {
cli_err(
"arbiter option must be preceded by replica "
"option.");
ret = -1;
goto out;
} else {
GF_ASSERT(!"opword mismatch");
ret = -1;
goto out;
}
op_count++;
}
if (!trans_type)
trans_type = gf_strdup("tcp");
if (index >= wordcount) {
ret = -1;
goto out;
}
brick_index = index;
if (strcmp(words[wordcount - 1], "force") == 0) {
is_force = _gf_true;
wc = wordcount - 1;
}
ret = cli_cmd_bricks_parse(words, wc, brick_index, &bricks, &brick_count);
if (ret)
goto out;
if (type == GF_CLUSTER_TYPE_DISPERSE) {
ret = cli_cmd_create_disperse_check(state, &disperse_count,
&redundancy_count,
&disperse_data_count, brick_count);
if (!ret)
ret = dict_set_int32(dict, "disperse-count", disperse_count);
if (!ret)
ret = dict_set_int32(dict, "redundancy-count", redundancy_count);
if (ret)
goto out;
sub_count = disperse_count;
} else
sub_count = stripe_count * replica_count;
if (brick_count % sub_count) {
if (type == GF_CLUSTER_TYPE_STRIPE)
cli_err(
"number of bricks is not a multiple of "
"stripe count");
else if (type == GF_CLUSTER_TYPE_REPLICATE)
cli_err(
"number of bricks is not a multiple of "
"replica count");
else if (type == GF_CLUSTER_TYPE_DISPERSE)
cli_err(
"number of bricks is not a multiple of "
"disperse count");
else
cli_err(
"number of bricks given doesn't match "
"required count");
ret = -1;
goto out;
}
/* Everything is parsed fine. start setting info in dict */
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
ret = dict_set_int32(dict, "type", type);
if (ret)
goto out;
ret = dict_set_dynstr(dict, "transport", trans_type);
if (ret)
goto out;
trans_type = NULL;
ret = dict_set_dynstr(dict, "bricks", bricks);
if (ret)
goto out;
ret = dict_set_int32(dict, "count", brick_count);
if (ret)
goto out;
ret = dict_set_int32(dict, "force", is_force);
if (ret)
goto out;
*options = dict;
*brick_list = bricks;
out:
if (ret) {
GF_FREE(bricks);
gf_log("cli", GF_LOG_ERROR, "Unable to parse create volume CLI");
if (dict)
dict_unref(dict);
}
GF_FREE(trans_type);
return ret;
}
int32_t
cli_cmd_volume_reset_parse(const char **words, int wordcount, dict_t **options)
{
dict_t *dict = NULL;
char *volname = NULL;
int ret = -1;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
if (wordcount < 3)
goto out;
if (wordcount > 5)
goto out;
volname = (char *)words[2];
if (!volname) {
ret = -1;
goto out;
}
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
if (wordcount == 3) {
ret = dict_set_str(dict, "key", "all");
if (ret)
goto out;
}
if (wordcount >= 4) {
if (!strcmp("force", (char *)words[3])) {
ret = dict_set_int32(dict, "force", 1);
if (ret)
goto out;
ret = dict_set_str(dict, "key", "all");
if (ret)
goto out;
} else {
ret = dict_set_str(dict, "key", (char *)words[3]);
if (ret)
goto out;
}
}
if (wordcount == 5) {
if (strcmp("force", (char *)words[4])) {
ret = -1;
goto out;
} else {
ret = dict_set_int32(dict, "force", 1);
if (ret)
goto out;
}
}
*options = dict;
out:
if (ret && dict) {
dict_unref(dict);
}
return ret;
}
int32_t
cli_cmd_get_state_parse(struct cli_state *state, const char **words,
int wordcount, dict_t **options, char **op_errstr)
{
dict_t *dict = NULL;
int ret = -1;
char *odir = NULL;
char *filename = NULL;
char *daemon_name = NULL;
int count = 0;
uint32_t cmd = 0;
GF_VALIDATE_OR_GOTO("cli", options, out);
GF_VALIDATE_OR_GOTO("cli", words, out);
dict = dict_new();
if (!dict)
goto out;
if (wordcount < 1 || wordcount > 7) {
*op_errstr = gf_strdup(
"Problem parsing arguments."
" Check usage.");
goto out;
}
if (wordcount >= 1) {
gf_asprintf(&daemon_name, "%s", "glusterd");
for (count = 1; count < wordcount; count++) {
if (strcmp(words[count], "odir") == 0 ||
strcmp(words[count], "file") == 0) {
if (strcmp(words[count], "odir") == 0) {
if (++count < wordcount) {
odir = (char *)words[count];
continue;
} else {
ret = -1;
goto out;
}
} else if (strcmp(words[count], "file") == 0) {
if (++count < wordcount) {
filename = (char *)words[count];
continue;
} else {
ret = -1;
goto out;
}
}
} else {
if (count > 1) {
if (count == wordcount - 1) {
if (strcmp(words[count], "detail") == 0) {
cmd = GF_CLI_GET_STATE_DETAIL;
continue;
} else if (strcmp(words[count], "volumeoptions") == 0) {
cmd = GF_CLI_GET_STATE_VOLOPTS;
continue;
}
} else {
*op_errstr = gf_strdup(
"Problem"
" parsing arguments. "
"Check usage.");
ret = -1;
goto out;
}
}
if (strcmp(words[count], "glusterd") == 0) {
continue;
} else {
if (count == wordcount - 1) {
if (strcmp(words[count], "detail") == 0) {
cmd = GF_CLI_GET_STATE_DETAIL;
continue;
} else if (strcmp(words[count], "volumeoptions") == 0) {
cmd = GF_CLI_GET_STATE_VOLOPTS;
continue;
}
}
*op_errstr = gf_strdup(
"glusterd is "
"the only supported daemon.");
ret = -1;
goto out;
}
}
}
ret = dict_set_dynstr(dict, "daemon", daemon_name);
if (ret) {
*op_errstr = gf_strdup(
"Command failed. Please check "
" log file for more details.");
gf_log(THIS->name, GF_LOG_ERROR,
"Setting daemon name to dictionary failed");
goto out;
}
daemon_name = NULL;
if (odir) {
ret = dict_set_str(dict, "odir", odir);
if (ret) {
*op_errstr = gf_strdup(
"Command failed. Please"
" check log file for"
" more details.");
gf_log(THIS->name, GF_LOG_ERROR,
"Setting output directory to"
"dictionary failed");
goto out;
}
}
if (filename) {
ret = dict_set_str(dict, "filename", filename);
if (ret) {
*op_errstr = gf_strdup(
"Command failed. Please"
" check log file for"
" more details.");
gf_log(THIS->name, GF_LOG_ERROR,
"Setting filename to dictionary failed");
goto out;
}
}
if (cmd) {
ret = dict_set_uint32(dict, "getstate-cmd", cmd);
if (ret) {
*op_errstr = gf_strdup(
"Command failed. Please"
" check log file for"
" more details.");
gf_log(THIS->name, GF_LOG_ERROR,
"Setting "
"get-state command type to dictionary "
"failed");
goto out;
}
}
}
out:
if (dict)
*options = dict;
if (ret && dict)
dict_unref(dict);
GF_FREE(daemon_name);
return ret;
}
int32_t
cli_cmd_inode_quota_parse(const char **words, int wordcount, dict_t **options)
{
dict_t *dict = NULL;
char *volname = NULL;
int ret = -1;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict) {
gf_log("cli", GF_LOG_ERROR, "dict_new failed");
goto out;
}
if (wordcount != 4)
goto out;
volname = (char *)words[2];
if (!volname) {
ret = -1;
goto out;
}
/* Validate the volume name here itself */
if (cli_validate_volname(volname) < 0)
goto out;
ret = dict_set_str(dict, "volname", volname);
if (ret < 0)
goto out;
if (strcmp(words[3], "enable") != 0) {
cli_out("Invalid quota option : %s", words[3]);
ret = -1;
goto out;
}
ret = dict_set_int32(dict, "type", GF_QUOTA_OPTION_TYPE_ENABLE_OBJECTS);
if (ret < 0)
goto out;
*options = dict;
out:
if (ret < 0) {
if (dict)
dict_unref(dict);
}
return ret;
}
int32_t
cli_cmd_quota_parse(const char **words, int wordcount, dict_t **options)
{
dict_t *dict = NULL;
char *volname = NULL;
int ret = -1;
int i = -1;
char key[20] = {
0,
};
int64_t value = 0;
gf_quota_type type = GF_QUOTA_OPTION_TYPE_NONE;
char *opwords[] = {"enable",
"disable",
"limit-usage",
"remove",
"list",
"alert-time",
"soft-timeout",
"hard-timeout",
"default-soft-limit",
"limit-objects",
"list-objects",
"remove-objects",
NULL};
char *w = NULL;
uint32_t time = 0;
double percent = 0;
char *end_ptr = NULL;
int64_t limit = 0;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict) {
gf_log("cli", GF_LOG_ERROR, "dict_new failed");
goto out;
}
if (wordcount < 4) {
if ((wordcount == 3) && !(strcmp(words[2], "help"))) {
ret = 1;
}
goto out;
}
volname = (char *)words[2];
if (!volname) {
ret = -1;
goto out;
}
/* Validate the volume name here itself */
if (cli_validate_volname(volname) < 0)
goto out;
ret = dict_set_str(dict, "volname", volname);
if (ret < 0)
goto out;
w = str_getunamb(words[3], opwords);
if (!w) {
cli_out("Invalid quota option : %s", words[3]);
ret = -1;
goto out;
}
if (strcmp(w, "enable") == 0) {
if (wordcount == 4) {
type = GF_QUOTA_OPTION_TYPE_ENABLE;
ret = 0;
goto set_type;
} else {
ret = -1;
goto out;
}
}
if (strcmp(w, "disable") == 0) {
if (wordcount == 4) {
type = GF_QUOTA_OPTION_TYPE_DISABLE;
ret = 0;
goto set_type;
} else {
ret = -1;
goto out;
}
}
if (strcmp(w, "limit-usage") == 0) {
type = GF_QUOTA_OPTION_TYPE_LIMIT_USAGE;
} else if (strcmp(w, "limit-objects") == 0) {
type = GF_QUOTA_OPTION_TYPE_LIMIT_OBJECTS;
}
if (type == GF_QUOTA_OPTION_TYPE_LIMIT_USAGE ||
type == GF_QUOTA_OPTION_TYPE_LIMIT_OBJECTS) {
if (wordcount < 6 || wordcount > 7) {
ret = -1;
goto out;
}
if (words[4][0] != '/') {
cli_err("Please enter absolute path");
ret = -1;
goto out;
}
ret = dict_set_str(dict, "path", (char *)words[4]);
if (ret)
goto out;
if (!words[5]) {
cli_err("Please enter the limit value to be set");
ret = -1;
goto out;
}
if (type == GF_QUOTA_OPTION_TYPE_LIMIT_USAGE) {
ret = gf_string2bytesize_int64(words[5], &value);
if (ret != 0 || value <= 0) {
if (errno == ERANGE || value <= 0) {
ret = -1;
cli_err(
"Please enter an integer "
"value in the range of "
"(1 - %" PRId64 ")",
INT64_MAX);
} else
cli_err(
"Please enter a correct "
"value");
goto out;
}
} else {
errno = 0;
limit = strtol(words[5], &end_ptr, 10);
if (errno == ERANGE || errno == EINVAL || limit <= 0 ||
strcmp(end_ptr, "") != 0) {
ret = -1;
cli_err(
"Please enter an integer value in "
"the range 1 - %" PRId64,
INT64_MAX);
goto out;
}
}
ret = dict_set_str(dict, "hard-limit", (char *)words[5]);
if (ret < 0)
goto out;
if (wordcount == 7) {
ret = gf_string2percent(words[6], &percent);
if (ret != 0 || percent > 100) {
ret = -1;
cli_err(
"Please enter a correct value "
"in the range of 0 to 100");
goto out;
}
ret = dict_set_str(dict, "soft-limit", (char *)words[6]);
if (ret < 0)
goto out;
}
goto set_type;
}
if (strcmp(w, "remove") == 0) {
if (wordcount != 5) {
ret = -1;
goto out;
}
type = GF_QUOTA_OPTION_TYPE_REMOVE;
if (words[4][0] != '/') {
cli_err("Please enter absolute path");
ret = -1;
goto out;
}
ret = dict_set_str(dict, "path", (char *)words[4]);
if (ret < 0)
goto out;
goto set_type;
}
if (strcmp(w, "remove-objects") == 0) {
if (wordcount != 5) {
ret = -1;
goto out;
}
type = GF_QUOTA_OPTION_TYPE_REMOVE_OBJECTS;
if (words[4][0] != '/') {
cli_err("Please enter absolute path");
ret = -1;
goto out;
}
ret = dict_set_str(dict, "path", (char *)words[4]);
if (ret < 0)
goto out;
goto set_type;
}
if (strcmp(w, "list") == 0) {
type = GF_QUOTA_OPTION_TYPE_LIST;
if (words[4] && words[4][0] != '/') {
cli_err("Please enter absolute path");
ret = -1;
goto out;
}
i = 4;
while (i < wordcount) {
snprintf(key, 20, "path%d", i - 4);
ret = dict_set_str(dict, key, (char *)words[i++]);
if (ret < 0)
goto out;
}
ret = dict_set_int32(dict, "count", i - 4);
if (ret < 0)
goto out;
goto set_type;
}
if (strcmp(w, "list-objects") == 0) {
type = GF_QUOTA_OPTION_TYPE_LIST_OBJECTS;
i = 4;
while (i < wordcount) {
snprintf(key, 20, "path%d", i - 4);
ret = dict_set_str(dict, key, (char *)words[i++]);
if (ret < 0) {
gf_log("cli", GF_LOG_ERROR,
"Failed to set "
"quota patch in request dictionary");
goto out;
}
}
ret = dict_set_int32(dict, "count", i - 4);
if (ret < 0) {
gf_log("cli", GF_LOG_ERROR,
"Failed to set quota "
"limit count in request dictionary");
goto out;
}
goto set_type;
}
if (strcmp(w, "alert-time") == 0) {
if (wordcount != 5) {
ret = -1;
goto out;
}
type = GF_QUOTA_OPTION_TYPE_ALERT_TIME;
ret = gf_string2time(words[4], &time);
if (ret) {
cli_err(
"Invalid argument %s. Please enter a valid "
"string",
words[4]);
goto out;
}
ret = dict_set_str(dict, "value", (char *)words[4]);
if (ret < 0)
goto out;
goto set_type;
}
if (strcmp(w, "soft-timeout") == 0) {
if (wordcount != 5) {
ret = -1;
goto out;
}
type = GF_QUOTA_OPTION_TYPE_SOFT_TIMEOUT;
ret = gf_string2time(words[4], &time);
if (ret) {
cli_err(
"Invalid argument %s. Please enter a valid "
"string",
words[4]);
goto out;
}
ret = dict_set_str(dict, "value", (char *)words[4]);
if (ret < 0)
goto out;
goto set_type;
}
if (strcmp(w, "hard-timeout") == 0) {
if (wordcount != 5) {
ret = -1;
goto out;
}
type = GF_QUOTA_OPTION_TYPE_HARD_TIMEOUT;
ret = gf_string2time(words[4], &time);
if (ret) {
cli_err(
"Invalid argument %s. Please enter a valid "
"string",
words[4]);
goto out;
}
ret = dict_set_str(dict, "value", (char *)words[4]);
if (ret < 0)
goto out;
goto set_type;
}
if (strcmp(w, "default-soft-limit") == 0) {
if (wordcount != 5) {
ret = -1;
goto out;
}
type = GF_QUOTA_OPTION_TYPE_DEFAULT_SOFT_LIMIT;
ret = dict_set_str(dict, "value", (char *)words[4]);
if (ret < 0)
goto out;
goto set_type;
} else {
GF_ASSERT(!"opword mismatch");
}
set_type:
ret = dict_set_int32(dict, "type", type);
if (ret < 0)
goto out;
*options = dict;
out:
if (ret < 0) {
if (dict)
dict_unref(dict);
}
return ret;
}
static gf_boolean_t
cli_is_key_spl(char *key)
{
return (strcmp(key, "group") == 0);
}
static int32_t
cli_add_key_group_value(dict_t *dict, const char *name, const char *value,
int32_t id, char **op_errstr)
{
char *key = NULL;
char *data = NULL;
int32_t ret = -1;
ret = gf_asprintf(&key, "%s%d", name, id);
if (ret < 0) {
goto out;
}
data = gf_strdup(value);
if (data == NULL) {
gf_log(THIS->name, GF_LOG_ERROR, "Failed to allocate memory for data");
ret = -1;
goto out;
}
ret = dict_set_dynstr(dict, key, data);
if (ret == 0) {
data = NULL;
}
out:
GF_FREE(key);
GF_FREE(data);
if ((ret != 0) && (op_errstr != NULL)) {
*op_errstr = gf_strdup("Failed to allocate memory");
}
return ret;
}
static int
cli_add_key_group(dict_t *dict, char *key, char *value, char **op_errstr)
{
int ret = -1;
int opt_count = 0;
char *saveptr = NULL;
char *tok_key = NULL;
char *tok_val = NULL;
char *tagpath = NULL;
char line[PATH_MAX + 256] = {
0,
};
FILE *fp = NULL;
ret = gf_asprintf(&tagpath, "%s/groups/%s", GLUSTERD_DEFAULT_WORKDIR,
value);
if (ret == -1) {
tagpath = NULL;
goto out;
}
fp = fopen(tagpath, "r");
if (!fp) {
ret = -1;
if (op_errstr) {
gf_asprintf(op_errstr,
"Unable to open file '%s'. "
"Error: %s",
tagpath, strerror(errno));
}
goto out;
}
opt_count = 0;
while (fgets(line, sizeof(line), fp) != NULL) {
if (strlen(line) >= sizeof(line) - 1) {
ret = -1;
if (op_errstr != NULL) {
*op_errstr = gf_strdup("Line too long");
}
goto out;
}
opt_count++;
tok_key = strtok_r(line, "=", &saveptr);
tok_val = strtok_r(NULL, "\r\n", &saveptr);
if (!tok_key || !tok_val) {
ret = -1;
if (op_errstr) {
gf_asprintf(op_errstr,
"'%s' file format "
"not valid.",
tagpath);
}
goto out;
}
ret = cli_add_key_group_value(dict, "key", tok_key, opt_count,
op_errstr);
if (ret != 0) {
goto out;
}
ret = cli_add_key_group_value(dict, "value", tok_val, opt_count,
op_errstr);
if (ret != 0) {
goto out;
}
}
if (!opt_count) {
ret = -1;
if (op_errstr) {
gf_asprintf(op_errstr, "'%s' file format not valid.", tagpath);
}
goto out;
}
ret = dict_set_int32(dict, "count", opt_count);
out:
GF_FREE(tagpath);
if (fp)
fclose(fp);
return ret;
}
int32_t
cli_cmd_volume_set_parse(struct cli_state *state, const char **words,
int wordcount, dict_t **options, char **op_errstr)
{
dict_t *dict = NULL;
char *volname = NULL;
int ret = -1;
int count = 0;
char *key = NULL;
char *value = NULL;
int i = 0;
char str[50] = {
0,
};
const char *question = NULL;
gf_answer_t answer = GF_ANSWER_NO;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
if (wordcount < 3)
goto out;
volname = (char *)words[2];
GF_ASSERT(volname);
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
if (!strcmp(volname, "all")) {
ret = dict_set_str(dict, "globalname", "All");
if (ret) {
gf_log(THIS->name, GF_LOG_ERROR, "dict set on global key failed.");
goto out;
}
ret = dict_set_int32(dict, "hold_global_locks", _gf_true);
if (ret) {
gf_log(THIS->name, GF_LOG_ERROR, "dict set on global key failed.");
goto out;
}
}
if ((!strcmp(volname, "help") || !strcmp(volname, "help-xml")) &&
wordcount == 3) {
ret = dict_set_str(dict, volname, volname);
if (ret)
goto out;
} else if (wordcount < 5) {
ret = -1;
goto out;
} else if (wordcount == 5 && cli_is_key_spl((char *)words[3])) {
key = (char *)words[3];
value = (char *)words[4];
if (!key || !value) {
ret = -1;
goto out;
}
ret = gf_strip_whitespace(value, strlen(value));
if (ret == -1)
goto out;
if (strlen(value) == 0) {
ret = -1;
goto out;
}
ret = cli_add_key_group(dict, key, value, op_errstr);
if (ret == 0)
*options = dict;
goto out;
}
for (i = 3; i < wordcount; i += 2) {
key = (char *)words[i];
value = (char *)words[i + 1];
if (!key || !value) {
ret = -1;
goto out;
}
count++;
if (fnmatch("user.*", key, FNM_NOESCAPE) != 0) {
ret = gf_strip_whitespace(value, strlen(value));
if (ret == -1)
goto out;
}
if (strlen(value) == 0) {
ret = -1;
goto out;
}
if (cli_is_key_spl(key)) {
ret = -1;
goto out;
}
sprintf(str, "key%d", count);
ret = dict_set_str(dict, str, key);
if (ret)
goto out;
sprintf(str, "value%d", count);
ret = dict_set_str(dict, str, value);
if (ret)
goto out;
if ((!strcmp(key, "cluster.enable-shared-storage")) &&
(!strcmp(value, "disable"))) {
question =
"Disabling cluster.enable-shared-storage "
"will delete the shared storage volume"
"(gluster_shared_storage), which is used "
"by snapshot scheduler, geo-replication "
"and NFS-Ganesha. Do you still want to "
"continue?";
answer = cli_cmd_get_confirmation(state, question);
if (GF_ANSWER_NO == answer) {
gf_log("cli", GF_LOG_ERROR,
"Operation "
"cancelled, exiting");
*op_errstr = gf_strdup("Aborted by user.");
ret = -1;
goto out;
}
}
if ((!strcmp(key, "nfs.disable")) && (!strcmp(value, "off"))) {
question =
"Gluster NFS is being deprecated in favor "
"of NFS-Ganesha Enter \"yes\" to continue "
"using Gluster NFS";
answer = cli_cmd_get_confirmation(state, question);
if (GF_ANSWER_NO == answer) {
gf_log("cli", GF_LOG_ERROR,
"Operation "
"cancelled, exiting");
*op_errstr = gf_strdup("Aborted by user.");
ret = -1;
goto out;
}
}
}
ret = dict_set_int32(dict, "count", wordcount - 3);
if (ret)
goto out;
*options = dict;
out:
if (ret && dict)
dict_unref(dict);
return ret;
}
int32_t
cli_cmd_volume_add_brick_parse(struct cli_state *state, const char **words,
int wordcount, dict_t **options, int *ret_type)
{
dict_t *dict = NULL;
char *volname = NULL;
int ret = -1;
int brick_count = 0, brick_index = 0;
char *bricks = NULL;
char *opwords_cl[] = {"replica", "stripe", NULL};
gf1_cluster_type type = GF_CLUSTER_TYPE_NONE;
int count = 1;
int arbiter_count = 0;
char *w = NULL;
int index;
gf_boolean_t is_force = _gf_false;
int wc = wordcount;
gf_answer_t answer = GF_ANSWER_NO;
const char *question = NULL;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
if (wordcount < 3)
goto out;
volname = (char *)words[2];
GF_ASSERT(volname);
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
if (wordcount < 4) {
ret = -1;
goto out;
}
if (wordcount < 6) {
/* seems no options are given, go directly to the parse_brick */
brick_index = 3;
type = GF_CLUSTER_TYPE_NONE;
goto parse_bricks;
}
w = str_getunamb(words[3], opwords_cl);
if (!w) {
type = GF_CLUSTER_TYPE_NONE;
index = 3;
} else if ((strcmp(w, "replica")) == 0) {
type = GF_CLUSTER_TYPE_REPLICATE;
count = strtol(words[4], NULL, 0);
if (!count || (count < 2)) {
cli_err("replica count should be greater than 1");
ret = -1;
goto out;
}
ret = dict_set_int32(dict, "replica-count", count);
if (ret)
goto out;
index = 5;
if (words[index] && !strcmp(words[index], "arbiter")) {
arbiter_count = strtol(words[6], NULL, 0);
if (arbiter_count != 1 || count != 3) {
cli_err(
"For arbiter configuration, replica "
"count must be 3 and arbiter count "
"must be 1. The 3rd brick of the "
"replica will be the arbiter");
ret = -1;
goto out;
}
ret = dict_set_int32(dict, "arbiter-count", arbiter_count);
if (ret)
goto out;
index = 7;
}
if (count == 2) {
if (strcmp(words[wordcount - 1], "force")) {
question =
"Replica 2 volumes are prone to "
"split-brain. Use Arbiter or "
"Replica 3 to avaoid this. See: "
"http://docs.gluster.org/en/latest/Administrator%20Guide/"
"Split%20brain%20and%20ways%20to%20deal%20with%20it/."
"\nDo you still want to continue?\n";
answer = cli_cmd_get_confirmation(state, question);
if (GF_ANSWER_NO == answer) {
gf_log("cli", GF_LOG_ERROR,
"Add brick"
" cancelled, exiting");
ret = -1;
goto out;
}
}
}
} else if ((strcmp(w, "stripe")) == 0) {
cli_err("stripe option not supported");
goto out;
} else {
GF_ASSERT(!"opword mismatch");
ret = -1;
goto out;
}
brick_index = index;
parse_bricks:
if (strcmp(words[wordcount - 1], "force") == 0) {
is_force = _gf_true;
wc = wordcount - 1;
}
ret = cli_cmd_bricks_parse(words, wc, brick_index, &bricks, &brick_count);
if (ret)
goto out;
ret = dict_set_dynstr(dict, "bricks", bricks);
if (ret)
goto out;
ret = dict_set_int32(dict, "count", brick_count);
if (ret)
goto out;
ret = dict_set_int32(dict, "force", is_force);
if (ret)
goto out;
*options = dict;
out:
if (ret_type)
*ret_type = type;
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Unable to parse add-brick CLI");
if (dict)
dict_unref(dict);
}
return ret;
}
int32_t
cli_cmd_volume_tier_parse(const char **words, int wordcount, dict_t **options)
{
dict_t *dict = NULL;
char *volname = NULL;
int ret = -1;
int32_t command = GF_DEFRAG_CMD_NONE;
int32_t is_force = 0;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
if (!(wordcount == 4 || wordcount == 5)) {
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
ret = -1;
goto out;
}
volname = (char *)words[2];
GF_ASSERT(volname);
ret = cli_cmd_validate_volume(volname);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Failed to validate volume name");
goto out;
}
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
volname = (char *)words[2];
if (wordcount == 4) {
if (!strcmp(words[3], "status"))
command = GF_DEFRAG_CMD_STATUS_TIER;
else if (!strcmp(words[3], "start"))
command = GF_DEFRAG_CMD_START_TIER;
else if (!strcmp(words[3], "stop"))
command = GF_DEFRAG_CMD_STOP_TIER;
else {
ret = -1;
goto out;
}
} else if (wordcount == 5) {
if ((!strcmp(words[3], "start")) && (!strcmp(words[4], "force"))) {
command = GF_DEFRAG_CMD_START_TIER;
is_force = 1;
ret = dict_set_int32(dict, "force", is_force);
if (ret)
goto out;
} else {
ret = -1;
goto out;
}
}
ret = dict_set_int32(dict, "rebalance-command", command);
if (ret)
goto out;
*options = dict;
out:
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Unable to parse tier CLI");
if (dict)
dict_unref(dict);
}
return ret;
}
int32_t
cli_cmd_volume_detach_tier_parse(const char **words, int wordcount,
dict_t **options, int *question)
{
int ret = -1;
char *word = NULL;
dict_t *dict = NULL;
int32_t command = GF_DEFRAG_CMD_NONE;
dict = dict_new();
if (!dict)
goto out;
ret = dict_set_str(dict, "volname", (char *)words[2]);
if (ret)
goto out;
if (wordcount == 3 && !strcmp((char *)words[2], "help")) {
return -1;
}
if (wordcount != 4) {
ret = -1;
goto out;
}
word = (char *)words[3];
ret = -1;
if (!strcmp(word, "start")) {
command = GF_DEFRAG_CMD_DETACH_START;
} else if (!strcmp(word, "commit")) {
*question = 1;
command = GF_DEFRAG_CMD_DETACH_COMMIT;
} else if (!strcmp(word, "force")) {
*question = 1;
command = GF_DEFRAG_CMD_DETACH_COMMIT_FORCE;
} else if (!strcmp(word, "stop"))
command = GF_DEFRAG_CMD_DETACH_STOP;
else if (!strcmp(word, "status"))
command = GF_DEFRAG_CMD_DETACH_STATUS;
else
goto out;
ret = dict_set_int32(dict, "command", command);
if (ret)
goto out;
*options = dict;
ret = 0;
out:
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Unable to parse detach tier CLI");
if (dict)
dict_unref(dict);
}
return ret;
}
int32_t
cli_cmd_volume_remove_brick_parse(struct cli_state *state, const char **words,
int wordcount, dict_t **options,
int *question, int *brick_count,
int32_t *comm)
{
dict_t *dict = NULL;
char *volname = NULL;
char *delimiter = NULL;
int ret = -1;
char key[50];
int brick_index = 0;
int32_t tmp_index = 0;
int32_t j = 0;
char *tmp_brick = NULL;
char *tmp_brick1 = NULL;
char *type_opword[] = {"replica", NULL};
char *opwords[] = {"start", "commit", "stop", "status", "force", NULL};
char *w = NULL;
int32_t command = GF_OP_CMD_NONE;
long count = 0;
gf_answer_t answer = GF_ANSWER_NO;
const char *ques = NULL;
GF_ASSERT(words);
GF_ASSERT(options);
if (wordcount < 5)
goto out;
dict = dict_new();
if (!dict)
goto out;
volname = (char *)words[2];
GF_ASSERT(volname);
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
brick_index = 3;
w = str_getunamb(words[3], type_opword);
if (w && !strcmp("replica", w)) {
if (wordcount < 6) {
ret = -1;
goto out;
}
count = strtol(words[4], NULL, 0);
if (count < 1) {
cli_err(
"replica count should be greater than 0 in "
"case of remove-brick");
ret = -1;
goto out;
}
if (count == 2) {
if (strcmp(words[wordcount - 1], "force")) {
ques =
"Replica 2 volumes are prone to "
"split-brain. Use Arbiter or Replica 3 "
"to avaoid this. See: "
"http://docs.gluster.org/en/latest/Administrator%20Guide/"
"Split%20brain%20and%20ways%20to%20deal%20with%20it/."
"\nDo you still want to continue?\n";
answer = cli_cmd_get_confirmation(state, ques);
if (GF_ANSWER_NO == answer) {
gf_log("cli", GF_LOG_ERROR,
"Remove "
"brick cancelled, exiting");
ret = -1;
goto out;
}
}
}
ret = dict_set_int32(dict, "replica-count", count);
if (ret)
goto out;
brick_index = 5;
} else if (w) {
GF_ASSERT(!"opword mismatch");
}
w = str_getunamb(words[wordcount - 1], opwords);
if (!w) {
ret = -1;
goto out;
} else {
/* handled this option */
wordcount--;
if (!strcmp("start", w)) {
command = GF_OP_CMD_START;
if (question)
*question = 1;
} else if (!strcmp("commit", w)) {
command = GF_OP_CMD_COMMIT;
} else if (!strcmp("stop", w)) {
command = GF_OP_CMD_STOP;
} else if (!strcmp("status", w)) {
command = GF_OP_CMD_STATUS;
} else if (!strcmp("force", w)) {
command = GF_OP_CMD_COMMIT_FORCE;
if (question)
*question = 1;
} else {
GF_ASSERT(!"opword mismatch");
ret = -1;
goto out;
}
}
ret = dict_set_int32(dict, "command", command);
if (ret)
gf_log("cli", GF_LOG_INFO, "failed to set 'command' %d", command);
tmp_index = brick_index;
tmp_brick = GF_MALLOC(2048 * sizeof(*tmp_brick), gf_common_mt_char);
if (!tmp_brick) {
gf_log("", GF_LOG_ERROR,
"cli_cmd_volume_remove_brick_parse: "
"Unable to get memory");
ret = -1;
goto out;
}
tmp_brick1 = GF_MALLOC(2048 * sizeof(*tmp_brick1), gf_common_mt_char);
if (!tmp_brick1) {
gf_log("", GF_LOG_ERROR,
"cli_cmd_volume_remove_brick_parse: "
"Unable to get memory");
ret = -1;
goto out;
}
while (brick_index < wordcount) {
if (validate_brick_name((char *)words[brick_index])) {
cli_err(
"wrong brick type: %s, use <HOSTNAME>:"
"<export-dir-abs-path>",
words[brick_index]);
ret = -1;
goto out;
} else {
delimiter = strrchr(words[brick_index], ':');
ret = gf_canonicalize_path(delimiter + 1);
if (ret)
goto out;
}
j = tmp_index;
strcpy(tmp_brick, words[brick_index]);
while (j < brick_index) {
strcpy(tmp_brick1, words[j]);
if (!(strcmp(tmp_brick, tmp_brick1))) {
gf_log("", GF_LOG_ERROR,
"Duplicate bricks"
" found %s",
words[brick_index]);
cli_err("Duplicate bricks found %s", words[brick_index]);
ret = -1;
goto out;
}
j++;
}
snprintf(key, 50, "brick%d", ++(*brick_count));
ret = dict_set_str(dict, key, (char *)words[brick_index++]);
if (ret)
goto out;
}
if (command != GF_OP_CMD_STATUS && command != GF_OP_CMD_STOP) {
ret = dict_set_int32(dict, "count", *brick_count);
if (ret)
goto out;
}
*options = dict;
out:
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Unable to parse remove-brick CLI");
if (dict)
dict_unref(dict);
}
GF_FREE(tmp_brick);
GF_FREE(tmp_brick1);
*comm = command;
return ret;
}
int32_t
cli_cmd_brick_op_validate_bricks(const char **words, dict_t *dict, int src,
int dst)
{
int ret = -1;
char *delimiter = NULL;
if (validate_brick_name((char *)words[src])) {
cli_err(
"wrong brick type: %s, use "
"<HOSTNAME>:<export-dir-abs-path>",
words[3]);
ret = -1;
goto out;
} else {
delimiter = strrchr((char *)words[src], '/');
ret = gf_canonicalize_path(delimiter);
if (ret)
goto out;
}
ret = dict_set_str(dict, "src-brick", (char *)words[src]);
if (ret)
goto out;
if (dst == -1) {
ret = 0;
goto out;
}
if (validate_brick_name((char *)words[dst])) {
cli_err(
"wrong brick type: %s, use "
"<HOSTNAME>:<export-dir-abs-path>",
words[dst]);
ret = -1;
goto out;
} else {
delimiter = strrchr((char *)words[dst], '/');
ret = gf_canonicalize_path(delimiter);
if (ret)
goto out;
}
ret = dict_set_str(dict, "dst-brick", (char *)words[dst]);
if (ret)
goto out;
ret = 0;
out:
return ret;
}
int32_t
cli_cmd_volume_reset_brick_parse(const char **words, int wordcount,
dict_t **options)
{
int ret = -1;
char *volname = NULL;
dict_t *dict = NULL;
if (wordcount < 5 || wordcount > 7)
goto out;
dict = dict_new();
if (!dict)
goto out;
volname = (char *)words[2];
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
if (wordcount == 5) {
if (strcmp(words[4], "start")) {
cli_err(
"Invalid option '%s' for reset-brick. Please "
"enter valid reset-brick command",
words[4]);
ret = -1;
goto out;
}
ret = cli_cmd_brick_op_validate_bricks(words, dict, 3, -1);
if (ret)
goto out;
ret = dict_set_str(dict, "operation", "GF_RESET_OP_START");
if (ret)
goto out;
} else if (wordcount == 6) {
if (strcmp(words[5], "commit")) {
cli_err(
"Invalid option '%s' for reset-brick. Please "
"enter valid reset-brick command",
words[5]);
ret = -1;
goto out;
}
ret = cli_cmd_brick_op_validate_bricks(words, dict, 3, 4);
if (ret)
goto out;
ret = dict_set_str(dict, "operation", "GF_RESET_OP_COMMIT");
if (ret)
goto out;
} else if (wordcount == 7) {
if (strcmp(words[5], "commit") || strcmp(words[6], "force")) {
cli_err(
"Invalid option '%s %s' for reset-brick. Please "
"enter valid reset-brick command",
words[5], words[6]);
ret = -1;
goto out;
}
ret = cli_cmd_brick_op_validate_bricks(words, dict, 3, 4);
if (ret)
goto out;
ret = dict_set_str(dict, "operation", "GF_RESET_OP_COMMIT_FORCE");
if (ret)
goto out;
}
*options = dict;
out:
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Unable to parse reset-brick CLI");
if (dict)
dict_unref(dict);
}
return ret;
}
int32_t
cli_cmd_volume_replace_brick_parse(const char **words, int wordcount,
dict_t **options)
{
int ret = -1;
char *volname = NULL;
dict_t *dict = NULL;
GF_ASSERT(words);
GF_ASSERT(options);
if (wordcount != 7) {
ret = -1;
goto out;
}
dict = dict_new();
if (!dict) {
gf_log("cli", GF_LOG_ERROR, "Failed to allocate dictionary");
goto out;
}
volname = (char *)words[2];
GF_ASSERT(volname);
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
ret = cli_cmd_brick_op_validate_bricks(words, dict, 3, 4);
if (ret)
goto out;
/* commit force option */
if (strcmp("commit", words[5]) || strcmp("force", words[6])) {
cli_err(
"Invalid option '%s' '%s' for replace-brick. Please "
"enter valid replace-brick command",
words[5], words[6]);
ret = -1;
goto out;
}
ret = dict_set_str(dict, "operation", "GF_REPLACE_OP_COMMIT_FORCE");
if (ret)
goto out;
*options = dict;
out:
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Unable to parse reset-brick CLI");
if (dict)
dict_unref(dict);
}
return ret;
}
int32_t
cli_cmd_log_filename_parse(const char **words, int wordcount, dict_t **options)
{
dict_t *dict = NULL;
char *volname = NULL;
char *str = NULL;
int ret = -1;
char *delimiter = NULL;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
volname = (char *)words[3];
GF_ASSERT(volname);
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
str = (char *)words[4];
if (strchr(str, ':')) {
delimiter = strchr(words[4], ':');
if (!delimiter || delimiter == words[4] || *(delimiter + 1) != '/') {
cli_err(
"wrong brick type: %s, use <HOSTNAME>:"
"<export-dir-abs-path>",
words[4]);
ret = -1;
goto out;
} else {
ret = gf_canonicalize_path(delimiter + 1);
if (ret)
goto out;
}
ret = dict_set_str(dict, "brick", str);
if (ret)
goto out;
/* Path */
str = (char *)words[5];
ret = dict_set_str(dict, "path", str);
if (ret)
goto out;
} else {
ret = dict_set_str(dict, "path", str);
if (ret)
goto out;
}
*options = dict;
out:
if (ret && dict)
dict_unref(dict);
return ret;
}
int32_t
cli_cmd_log_level_parse(const char **words, int worcount, dict_t **options)
{
dict_t *dict = NULL;
int ret = -1;
GF_ASSERT(words);
GF_ASSERT(options);
/*
* loglevel command format:
* > volume log level <VOL> <XLATOR[*]> <LOGLEVEL>
* > volume log level colon-o posix WARNING
* > volume log level colon-o replicate* DEBUG
* > volume log level coon-o * TRACE
*/
GF_ASSERT((strncmp(words[0], "volume", 6) == 0));
GF_ASSERT((strncmp(words[1], "log", 3) == 0));
GF_ASSERT((strncmp(words[2], "level", 5) == 0));
ret = glusterd_check_log_level(words[5]);
if (ret == -1) {
cli_err("Invalid log level [%s] specified", words[5]);
cli_err(
"Valid values for loglevel: (DEBUG|WARNING|ERROR"
"|CRITICAL|NONE|TRACE)");
goto out;
}
dict = dict_new();
if (!dict)
goto out;
GF_ASSERT(words[3]);
GF_ASSERT(words[4]);
ret = dict_set_str(dict, "volname", (char *)words[3]);
if (ret)
goto out;
ret = dict_set_str(dict, "xlator", (char *)words[4]);
if (ret)
goto out;
ret = dict_set_str(dict, "loglevel", (char *)words[5]);
if (ret)
goto out;
*options = dict;
out:
if (ret && dict)
dict_unref(dict);
return ret;
}
int32_t
cli_cmd_log_locate_parse(const char **words, int wordcount, dict_t **options)
{
dict_t *dict = NULL;
char *volname = NULL;
char *str = NULL;
int ret = -1;
char *delimiter = NULL;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
volname = (char *)words[3];
GF_ASSERT(volname);
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
if (words[4]) {
delimiter = strchr(words[4], ':');
if (!delimiter || delimiter == words[4] || *(delimiter + 1) != '/') {
cli_err(
"wrong brick type: %s, use <HOSTNAME>:"
"<export-dir-abs-path>",
words[4]);
ret = -1;
goto out;
} else {
ret = gf_canonicalize_path(delimiter + 1);
if (ret)
goto out;
}
str = (char *)words[4];
ret = dict_set_str(dict, "brick", str);
if (ret)
goto out;
}
*options = dict;
out:
if (ret && dict)
dict_unref(dict);
return ret;
}
int32_t
cli_cmd_log_rotate_parse(const char **words, int wordcount, dict_t **options)
{
dict_t *dict = NULL;
char *volname = NULL;
char *str = NULL;
int ret = -1;
char *delimiter = NULL;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
if (strcmp("rotate", words[3]) == 0)
volname = (char *)words[2];
else if (strcmp("rotate", words[2]) == 0)
volname = (char *)words[3];
GF_ASSERT(volname);
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
if (words[4]) {
delimiter = strchr(words[4], ':');
if (!delimiter || delimiter == words[4] || *(delimiter + 1) != '/') {
cli_err(
"wrong brick type: %s, use <HOSTNAME>:"
"<export-dir-abs-path>",
words[4]);
ret = -1;
goto out;
} else {
ret = gf_canonicalize_path(delimiter + 1);
if (ret)
goto out;
}
str = (char *)words[4];
ret = dict_set_str(dict, "brick", str);
if (ret)
goto out;
}
*options = dict;
out:
if (ret && dict)
dict_unref(dict);
return ret;
}
static gf_boolean_t
gsyncd_url_check(const char *w)
{
return !!strpbrk(w, ":/");
}
static gf_boolean_t
gsyncd_glob_check(const char *w)
{
return !!strpbrk(w, "*?[");
}
static int
config_parse(const char **words, int wordcount, dict_t *dict, unsigned cmdi,
unsigned glob)
{
int32_t ret = -1;
int32_t i = -1;
char *append_str = NULL;
size_t append_len = 0;
char *subop = NULL;
char *ret_chkpt = NULL;
struct tm checkpoint_time;
char chkpt_buf[20] = "";
switch ((wordcount - 1) - cmdi) {
case 0:
subop = gf_strdup("get-all");
break;
case 1:
if (words[cmdi + 1][0] == '!') {
(words[cmdi + 1])++;
if (gf_asprintf(&subop, "del%s", glob ? "-glob" : "") == -1)
subop = NULL;
} else
subop = gf_strdup("get");
ret = dict_set_str(dict, "op_name", ((char *)words[cmdi + 1]));
if (ret < 0)
goto out;
break;
default:
if (gf_asprintf(&subop, "set%s", glob ? "-glob" : "") == -1)
subop = NULL;
ret = dict_set_str(dict, "op_name", ((char *)words[cmdi + 1]));
if (ret < 0)
goto out;
/* join the varargs by spaces to get the op_value */
for (i = cmdi + 2; i < wordcount; i++)
append_len += (strlen(words[i]) + 1);
/* trailing strcat will add two bytes, make space for that */
append_len++;
/* strcat is used on this allocation and hence expected to be
* initiatlized to 0. So GF_CALLOC is used.
*/
append_str = GF_CALLOC(1, append_len, cli_mt_append_str);
if (!append_str) {
ret = -1;
goto out;
}
for (i = cmdi + 2; i < wordcount; i++) {
strcat(append_str, words[i]);
strcat(append_str, " ");
}
append_str[append_len - 2] = '\0';
/* "checkpoint now" is special: we resolve that "now" */
if ((strcmp(words[cmdi + 1], "checkpoint") == 0) &&
(strcmp(append_str, "now") == 0)) {
struct timeval tv = {
0,
};
ret = gettimeofday(&tv, NULL);
if (ret == -1)
goto out;
GF_FREE(append_str);
append_str = GF_MALLOC(300, cli_mt_append_str);
if (!append_str) {
ret = -1;
goto out;
}
snprintf(append_str, 300, "%" GF_PRI_SECOND, tv.tv_sec);
} else if ((strcmp(words[cmdi + 1], "checkpoint") == 0) &&
(strcmp(append_str, "now") != 0)) {
memset(&checkpoint_time, 0, sizeof(struct tm));
ret_chkpt = strptime(append_str, "%Y-%m-%d %H:%M:%S",
&checkpoint_time);
if (ret_chkpt == NULL || *ret_chkpt != '\0') {
ret = -1;
cli_err(
"Invalid Checkpoint label. Use format "
"\"Y-m-d H:M:S\", Example: 2016-10-25 15:30:45");
goto out;
}
GF_FREE(append_str);
append_str = GF_MALLOC(300, cli_mt_append_str);
if (!append_str) {
ret = -1;
goto out;
}
strftime(chkpt_buf, sizeof(chkpt_buf), "%s", &checkpoint_time);
snprintf(append_str, 300, "%s", chkpt_buf);
}
ret = dict_set_dynstr(dict, "op_value", append_str);
if (ret != 0) {
goto out;
}
append_str = NULL;
}
ret = -1;
if (subop) {
ret = dict_set_dynstr(dict, "subop", subop);
if (!ret)
subop = NULL;
}
out:
GF_FREE(append_str);
GF_FREE(subop);
gf_log("cli", GF_LOG_DEBUG, "Returning %d", ret);
return ret;
}
/* ssh_port_parse: Parses and validates when ssh_port is given.
* ssh_index refers to index of ssh_port and
* type refers to either push-pem or no-verify
*/
static int32_t
parse_ssh_port(const char **words, int wordcount, dict_t *dict, unsigned *cmdi,
int ssh_index, char *type)
{
int ret = 0;
char *end_ptr = NULL;
int64_t limit = 0;
if (!strcmp((char *)words[ssh_index], "ssh-port")) {
if (strcmp((char *)words[ssh_index - 1], "create")) {
ret = -1;
goto out;
}
(*cmdi)++;
limit = strtol(words[ssh_index + 1], &end_ptr, 10);
if (errno == ERANGE || errno == EINVAL || limit <= 0 ||
strcmp(end_ptr, "") != 0) {
ret = -1;
cli_err("Please enter an integer value for ssh_port ");
goto out;
}
ret = dict_set_int32(dict, "ssh_port", limit);
if (ret)
goto out;
(*cmdi)++;
} else if (strcmp((char *)words[ssh_index + 1], "create")) {
ret = -1;
goto out;
}
ret = dict_set_int32(dict, type, 1);
if (ret)
goto out;
(*cmdi)++;
out:
return ret;
}
static int32_t
force_push_pem_no_verify_parse(const char **words, int wordcount, dict_t *dict,
unsigned *cmdi)
{
int32_t ret = 0;
if (!strcmp((char *)words[wordcount - 1], "force")) {
if ((strcmp((char *)words[wordcount - 2], "start")) &&
(strcmp((char *)words[wordcount - 2], "stop")) &&
(strcmp((char *)words[wordcount - 2], "create")) &&
(strcmp((char *)words[wordcount - 2], "no-verify")) &&
(strcmp((char *)words[wordcount - 2], "push-pem")) &&
(strcmp((char *)words[wordcount - 2], "pause")) &&
(strcmp((char *)words[wordcount - 2], "resume"))) {
ret = -1;
goto out;
}
ret = dict_set_int32n(dict, "force", SLEN("force"), 1);
if (ret)
goto out;
(*cmdi)++;
if (!strcmp((char *)words[wordcount - 2], "push-pem")) {
ret = parse_ssh_port(words, wordcount, dict, cmdi, wordcount - 4,
"push_pem");
if (ret)
goto out;
} else if (!strcmp((char *)words[wordcount - 2], "no-verify")) {
ret = parse_ssh_port(words, wordcount, dict, cmdi, wordcount - 4,
"no_verify");
if (ret)
goto out;
}
} else if (!strcmp((char *)words[wordcount - 1], "push-pem")) {
ret = parse_ssh_port(words, wordcount, dict, cmdi, wordcount - 3,
"push_pem");
if (ret)
goto out;
} else if (!strcmp((char *)words[wordcount - 1], "no-verify")) {
ret = parse_ssh_port(words, wordcount, dict, cmdi, wordcount - 3,
"no_verify");
if (ret)
goto out;
}
out:
gf_log("cli", GF_LOG_DEBUG, "Returning %d", ret);
return ret;
}
int32_t
cli_cmd_gsync_set_parse(const char **words, int wordcount, dict_t **options)
{
int32_t ret = -1;
dict_t *dict = NULL;
gf1_cli_gsync_set type = GF_GSYNC_OPTION_TYPE_NONE;
int i = 0;
unsigned masteri = 0;
unsigned slavei = 0;
unsigned glob = 0;
unsigned cmdi = 0;
char *opwords[] = {"create", "status", "start", "stop", "config",
"force", "delete", "ssh-port", "no-verify", "push-pem",
"detail", "pause", "resume", NULL};
char *w = NULL;
char *save_ptr = NULL;
char *slave_temp = NULL;
char *token = NULL;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
/* new syntax:
*
* volume geo-replication $m $s create [[ssh-port n] [[no-verify] |
* [push-pem]]] [force] volume geo-replication [$m [$s]] status [detail]
* volume geo-replication [$m] $s config [[!]$opt [$val]]
* volume geo-replication $m $s start|stop [force]
* volume geo-replication $m $s delete [reset-sync-time]
* volume geo-replication $m $s pause [force]
* volume geo-replication $m $s resume [force]
*/
if (wordcount < 3)
goto out;
for (i = 2; i <= 3 && i < wordcount - 1; i++) {
if (gsyncd_glob_check(words[i]))
glob = i;
if (gsyncd_url_check(words[i])) {
slavei = i;
break;
}
}
if (glob && !slavei)
/* glob is allowed only for config, thus it implies there is a
* slave argument; but that might have not been recognized on
* the first scan as it's url characteristics has been covered
* by the glob syntax.
*
* In this case, the slave is perforce the last glob-word -- the
* upcoming one is neither glob, nor url, so it's definitely not
* the slave.
*/
slavei = glob;
if (slavei) {
cmdi = slavei + 1;
if (slavei == 3)
masteri = 2;
} else if (i <= 4) {
if (strtail("detail", (char *)words[wordcount - 1])) {
cmdi = wordcount - 2;
if (i == 4)
masteri = 2;
} else {
/* no $s, can only be status cmd
* (with either a single $m before it or nothing)
* -- these conditions imply that i <= 3 after
* the iteration and that i is the successor of
* the (0 or 1 length) sequence of $m-s.
*/
cmdi = i;
if (i == 3)
masteri = 2;
}
} else
goto out;
/* now check if input really complies syntax
* (in a somewhat redundant way, in favor
* transparent soundness)
*/
if (masteri && gsyncd_url_check(words[masteri]))
goto out;
if (slavei && !glob && !gsyncd_url_check(words[slavei]))
goto out;
w = str_getunamb(words[cmdi], opwords);
if (!w)
goto out;
if (strcmp(w, "create") == 0) {
type = GF_GSYNC_OPTION_TYPE_CREATE;
if (!masteri || !slavei)
goto out;
} else if (strcmp(w, "status") == 0) {
type = GF_GSYNC_OPTION_TYPE_STATUS;
if (slavei && !masteri)
goto out;
} else if (strcmp(w, "config") == 0) {
type = GF_GSYNC_OPTION_TYPE_CONFIG;
if (!slavei)
goto out;
} else if (strcmp(w, "start") == 0) {
type = GF_GSYNC_OPTION_TYPE_START;
if (!masteri || !slavei)
goto out;
} else if (strcmp(w, "stop") == 0) {
type = GF_GSYNC_OPTION_TYPE_STOP;
if (!masteri || !slavei)
goto out;
} else if (strcmp(w, "delete") == 0) {
type = GF_GSYNC_OPTION_TYPE_DELETE;
if (!masteri || !slavei)
goto out;
} else if (strcmp(w, "pause") == 0) {
type = GF_GSYNC_OPTION_TYPE_PAUSE;
if (!masteri || !slavei)
goto out;
} else if (strcmp(w, "resume") == 0) {
type = GF_GSYNC_OPTION_TYPE_RESUME;
if (!masteri || !slavei)
goto out;
} else
GF_ASSERT(!"opword mismatch");
ret = force_push_pem_no_verify_parse(words, wordcount, dict, &cmdi);
if (ret)
goto out;
if (strtail("detail", (char *)words[wordcount - 1])) {
if (!strtail("status", (char *)words[wordcount - 2])) {
ret = -1;
goto out;
}
ret = dict_set_uint32(dict, "status-detail", _gf_true);
if (ret)
goto out;
cmdi++;
}
if (type == GF_GSYNC_OPTION_TYPE_DELETE &&
!strcmp((char *)words[wordcount - 1], "reset-sync-time")) {
if (strcmp((char *)words[wordcount - 2], "delete")) {
ret = -1;
goto out;
}
ret = dict_set_uint32(dict, "reset-sync-time", _gf_true);
if (ret)
goto out;
cmdi++;
}
if (type != GF_GSYNC_OPTION_TYPE_CONFIG && (cmdi < wordcount - 1 || glob)) {
ret = -1;
goto out;
}
/* If got so far, input is valid, assemble the message */
ret = 0;
if (masteri) {
ret = dict_set_str(dict, "master", (char *)words[masteri]);
if (!ret)
ret = dict_set_str(dict, "volname", (char *)words[masteri]);
}
if (!ret && slavei) {
/* If geo-rep is created with root user using the syntax
* gluster vol geo-rep <mastervol> root@<slavehost> ...
* pass down only <slavehost> else pass as it is.
*/
slave_temp = gf_strdup(words[slavei]);
if (slave_temp == NULL) {
ret = -1;
goto out;
}
token = strtok_r(slave_temp, "@", &save_ptr);
if (token && !strcmp(token, "root")) {
ret = dict_set_str(dict, "slave", (char *)words[slavei] + 5);
} else {
ret = dict_set_str(dict, "slave", (char *)words[slavei]);
}
}
if (!ret)
ret = dict_set_int32(dict, "type", type);
if (!ret && type == GF_GSYNC_OPTION_TYPE_CONFIG)
ret = config_parse(words, wordcount, dict, cmdi, glob);
out:
if (slave_temp)
GF_FREE(slave_temp);
if (ret) {
if (dict)
dict_unref(dict);
} else
*options = dict;
return ret;
}
int32_t
cli_cmd_volume_profile_parse(const char **words, int wordcount,
dict_t **options)
{
dict_t *dict = NULL;
char *volname = NULL;
int ret = -1;
gf1_cli_stats_op op = GF_CLI_STATS_NONE;
gf1_cli_info_op info_op = GF_CLI_INFO_NONE;
gf_boolean_t is_peek = _gf_false;
char *opwords[] = {"start", "stop", "info", NULL};
char *w = NULL;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
if (wordcount < 4)
goto out;
volname = (char *)words[2];
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
w = str_getunamb(words[3], opwords);
if (!w) {
ret = -1;
goto out;
}
if ((strcmp(w, "start") == 0 || strcmp(w, "stop") == 0) && wordcount > 5) {
ret = -1;
goto out;
}
if (strcmp(w, "info") == 0 && wordcount > 7) {
ret = -1;
goto out;
}
if (strcmp(w, "start") == 0) {
op = GF_CLI_STATS_START;
} else if (strcmp(w, "stop") == 0) {
op = GF_CLI_STATS_STOP;
} else if (strcmp(w, "info") == 0) {
op = GF_CLI_STATS_INFO;
info_op = GF_CLI_INFO_ALL;
if (wordcount > 4) {
if (strcmp(words[4], "incremental") == 0) {
info_op = GF_CLI_INFO_INCREMENTAL;
if (wordcount > 5 && strcmp(words[5], "peek") == 0) {
is_peek = _gf_true;
}
} else if (strcmp(words[4], "cumulative") == 0) {
info_op = GF_CLI_INFO_CUMULATIVE;
} else if (strcmp(words[4], "clear") == 0) {
info_op = GF_CLI_INFO_CLEAR;
} else if (strcmp(words[4], "peek") == 0) {
is_peek = _gf_true;
}
}
} else
GF_ASSERT(!"opword mismatch");
ret = dict_set_int32(dict, "op", (int32_t)op);
if (ret)
goto out;
ret = dict_set_int32(dict, "info-op", (int32_t)info_op);
if (ret)
goto out;
ret = dict_set_int32(dict, "peek", is_peek);
if (ret)
goto out;
if (!strcmp(words[wordcount - 1], "nfs")) {
ret = dict_set_int32(dict, "nfs", _gf_true);
if (ret)
goto out;
}
*options = dict;
out:
if (ret && dict)
dict_unref(dict);
return ret;
}
int32_t
cli_cmd_volume_top_parse(const char **words, int wordcount, dict_t **options)
{
dict_t *dict = NULL;
char *volname = NULL;
char *value = NULL;
char *key = NULL;
int ret = -1;
gf1_cli_stats_op op = GF_CLI_STATS_NONE;
gf1_cli_top_op top_op = GF_CLI_TOP_NONE;
int32_t list_cnt = -1;
int index = 0;
int perf = 0;
int32_t blk_size = 0;
int count = 0;
gf_boolean_t nfs = _gf_false;
char *delimiter = NULL;
char *opwords[] = {"open", "read", "write", "opendir", "readdir",
"read-perf", "write-perf", "clear", NULL};
char *w = NULL;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
if (wordcount < 4)
goto out;
volname = (char *)words[2];
ret = dict_set_str(dict, "volname", volname);
if (ret)
goto out;
op = GF_CLI_STATS_TOP;
ret = dict_set_int32(dict, "op", (int32_t)op);
if (ret)
goto out;
w = str_getunamb(words[3], opwords);
if (!w) {
ret = -1;
goto out;
}
if (strcmp(w, "open") == 0) {
top_op = GF_CLI_TOP_OPEN;
} else if (strcmp(w, "read") == 0) {
top_op = GF_CLI_TOP_READ;
} else if (strcmp(w, "write") == 0) {
top_op = GF_CLI_TOP_WRITE;
} else if (strcmp(w, "opendir") == 0) {
top_op = GF_CLI_TOP_OPENDIR;
} else if (strcmp(w, "readdir") == 0) {
top_op = GF_CLI_TOP_READDIR;
} else if (strcmp(w, "read-perf") == 0) {
top_op = GF_CLI_TOP_READ_PERF;
perf = 1;
} else if (strcmp(w, "write-perf") == 0) {
top_op = GF_CLI_TOP_WRITE_PERF;
perf = 1;
} else if (strcmp(w, "clear") == 0) {
ret = dict_set_int32(dict, "clear-stats", 1);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Could not set clear-stats in dict");
goto out;
}
} else
GF_ASSERT(!"opword mismatch");
ret = dict_set_int32(dict, "top-op", (int32_t)top_op);
if (ret)
goto out;
if ((wordcount > 4) && !strcmp(words[4], "nfs")) {
nfs = _gf_true;
ret = dict_set_int32(dict, "nfs", nfs);
if (ret)
goto out;
index = 5;
} else {
index = 4;
}
for (; index < wordcount; index += 2) {
key = (char *)words[index];
value = (char *)words[index + 1];
if (!key || !value) {
ret = -1;
goto out;
}
if (!strcmp(key, "brick")) {
delimiter = strchr(value, ':');
if (!delimiter || delimiter == value || *(delimiter + 1) != '/') {
cli_err(
"wrong brick type: %s, use <HOSTNAME>:"
"<export-dir-abs-path>",
value);
ret = -1;
goto out;
} else {
ret = gf_canonicalize_path(delimiter + 1);
if (ret)
goto out;
}
ret = dict_set_str(dict, "brick", value);
} else if (!strcmp(key, "list-cnt")) {
ret = gf_is_str_int(value);
if (!ret)
list_cnt = atoi(value);
if (ret || (list_cnt < 0) || (list_cnt > 100)) {
cli_err("list-cnt should be between 0 to 100");
ret = -1;
goto out;
}
} else if (perf && !nfs && !strcmp(key, "bs")) {
ret = gf_is_str_int(value);
if (!ret)
blk_size = atoi(value);
if (ret || (blk_size <= 0)) {
if (blk_size < 0)
cli_err(
"block size is an invalid"
" number");
else
cli_err(
"block size should be an "
"integer greater than zero");
ret = -1;
goto out;
}
ret = dict_set_uint32(dict, "blk-size", (uint32_t)blk_size);
} else if (perf && !nfs && !strcmp(key, "count")) {
ret = gf_is_str_int(value);
if (!ret)
count = atoi(value);
if (ret || (count <= 0)) {
if (count < 0)
cli_err("count is an invalid number");
else
cli_err(
"count should be an integer "
"greater than zero");
ret = -1;
goto out;
}
ret = dict_set_uint32(dict, "blk-cnt", count);
} else {
ret = -1;
goto out;
}
if (ret) {
gf_log("", GF_LOG_WARNING,
"Dict set failed for "
"key %s",
key);
goto out;
}
}
if (list_cnt == -1)
list_cnt = 100;
ret = dict_set_int32(dict, "list-cnt", list_cnt);
if (ret) {
gf_log("", GF_LOG_WARNING, "Dict set failed for list_cnt");
goto out;
}
if ((blk_size > 0) ^ (count > 0)) {
cli_err("Need to give both 'bs' and 'count'");
ret = -1;
goto out;
} else if (((uint64_t)blk_size * count) > (10 * GF_UNIT_GB)) {
cli_err("'bs * count' value %" PRIu64
" is greater than "
"maximum allowed value of 10GB",
((uint64_t)blk_size * count));
ret = -1;
goto out;
}
*options = dict;
out:
if (ret && dict)
dict_unref(dict);
return ret;
}
uint32_t
cli_cmd_get_statusop(const char *arg)
{
int i = 0;
uint32_t ret = GF_CLI_STATUS_NONE;
char *w = NULL;
char *opwords[] = {"detail", "mem", "clients", "fd", "inode",
"callpool", "tasks", "client-list", NULL};
struct {
char *opname;
uint32_t opcode;
} optable[] = {{"detail", GF_CLI_STATUS_DETAIL},
{"mem", GF_CLI_STATUS_MEM},
{"clients", GF_CLI_STATUS_CLIENTS},
{"fd", GF_CLI_STATUS_FD},
{"inode", GF_CLI_STATUS_INODE},
{"callpool", GF_CLI_STATUS_CALLPOOL},
{"tasks", GF_CLI_STATUS_TASKS},
{"client-list", GF_CLI_STATUS_CLIENT_LIST},
{NULL}};
w = str_getunamb(arg, opwords);
if (!w) {
gf_log("cli", GF_LOG_DEBUG, "Not a status op %s", arg);
goto out;
}
for (i = 0; optable[i].opname; i++) {
if (!strcmp(w, optable[i].opname)) {
ret = optable[i].opcode;
break;
}
}
out:
return ret;
}
int
cli_cmd_volume_status_parse(const char **words, int wordcount, dict_t **options)
{
dict_t *dict = NULL;
int ret = -1;
uint32_t cmd = 0;
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
switch (wordcount) {
case 2:
cmd = GF_CLI_STATUS_ALL;
ret = 0;
break;
case 3:
if (!strcmp(words[2], "all")) {
cmd = GF_CLI_STATUS_ALL;
ret = 0;
} else {
cmd = GF_CLI_STATUS_VOL;
ret = dict_set_str(dict, "volname", (char *)words[2]);
}
break;
case 4:
cmd = cli_cmd_get_statusop(words[3]);
if (!strcmp(words[2], "all")) {
if (cmd == GF_CLI_STATUS_NONE) {
cli_err("%s is not a valid status option", words[3]);
ret = -1;
goto out;
}
cmd |= GF_CLI_STATUS_ALL;
ret = 0;
} else {
ret = dict_set_str(dict, "volname", (char *)words[2]);
if (ret)
goto out;
if (cmd == GF_CLI_STATUS_NONE) {
if (!strcmp(words[3], "nfs")) {
cmd |= GF_CLI_STATUS_NFS;
} else if (!strcmp(words[3], "shd")) {
cmd |= GF_CLI_STATUS_SHD;
} else if (!strcmp(words[3], "quotad")) {
cmd |= GF_CLI_STATUS_QUOTAD;
} else if (!strcmp(words[3], "snapd")) {
cmd |= GF_CLI_STATUS_SNAPD;
} else if (!strcmp(words[3], "tierd")) {
cmd |= GF_CLI_STATUS_TIERD;
} else if (!strcmp(words[3], "bitd")) {
cmd |= GF_CLI_STATUS_BITD;
} else if (!strcmp(words[3], "scrub")) {
cmd |= GF_CLI_STATUS_SCRUB;
} else {
cmd = GF_CLI_STATUS_BRICK;
ret = dict_set_str(dict, "brick", (char *)words[3]);
}
} else {
cmd |= GF_CLI_STATUS_VOL;
ret = 0;
}
}
break;
case 5:
if (!strcmp(words[2], "all")) {
cli_err("Cannot specify brick/nfs for \"all\"");
ret = -1;
goto out;
}
cmd = cli_cmd_get_statusop(words[4]);
if (cmd == GF_CLI_STATUS_NONE) {
cli_err("%s is not a valid status option", words[4]);
ret = -1;
goto out;
}
ret = dict_set_str(dict, "volname", (char *)words[2]);
if (ret)
goto out;
if (!strcmp(words[3], "nfs")) {
if (cmd == GF_CLI_STATUS_FD || cmd == GF_CLI_STATUS_DETAIL ||
cmd == GF_CLI_STATUS_TASKS) {
cli_err(
"Detail/FD/Tasks status not available"
" for NFS Servers");
ret = -1;
goto out;
}
cmd |= GF_CLI_STATUS_NFS;
} else if (!strcmp(words[3], "shd")) {
if (cmd == GF_CLI_STATUS_FD || cmd == GF_CLI_STATUS_CLIENTS ||
cmd == GF_CLI_STATUS_DETAIL || cmd == GF_CLI_STATUS_TASKS) {
cli_err(
"Detail/FD/Clients/Tasks status not "
"available for Self-heal Daemons");
ret = -1;
goto out;
}
cmd |= GF_CLI_STATUS_SHD;
} else if (!strcmp(words[3], "quotad")) {
if (cmd == GF_CLI_STATUS_FD || cmd == GF_CLI_STATUS_CLIENTS ||
cmd == GF_CLI_STATUS_DETAIL || cmd == GF_CLI_STATUS_INODE) {
cli_err(
"Detail/FD/Clients/Inode status not "
"available for Quota Daemon");
ret = -1;
goto out;
}
cmd |= GF_CLI_STATUS_QUOTAD;
} else if (!strcmp(words[3], "snapd")) {
if (cmd == GF_CLI_STATUS_FD || cmd == GF_CLI_STATUS_CLIENTS ||
cmd == GF_CLI_STATUS_DETAIL || cmd == GF_CLI_STATUS_INODE) {
cli_err(
"Detail/FD/Clients/Inode status not "
"available for snap daemon");
ret = -1;
goto out;
}
cmd |= GF_CLI_STATUS_SNAPD;
} else if (!strcmp(words[3], "tierd")) {
if (cmd == GF_CLI_STATUS_FD || cmd == GF_CLI_STATUS_CLIENTS ||
cmd == GF_CLI_STATUS_DETAIL || cmd == GF_CLI_STATUS_INODE) {
cli_err(
"Detail/FD/Clients/Inode status not "
"available for tier daemon");
ret = -1;
goto out;
}
cmd |= GF_CLI_STATUS_TIERD;
} else {
if (cmd == GF_CLI_STATUS_TASKS) {
cli_err(
"Tasks status not available for "
"bricks");
ret = -1;
goto out;
}
cmd |= GF_CLI_STATUS_BRICK;
ret = dict_set_str(dict, "brick", (char *)words[3]);
}
break;
default:
goto out;
}
if (ret)
goto out;
ret = dict_set_int32(dict, "cmd", cmd);
if (ret)
goto out;
*options = dict;
out:
if (ret && dict)
dict_unref(dict);
return ret;
}
gf_boolean_t
cli_cmd_validate_dumpoption(const char *arg, char **option)
{
char *opwords[] = {"all", "nfs", "mem", "iobuf", "callpool",
"priv", "fd", "inode", "history", "inodectx",
"fdctx", "quotad", NULL};
char *w = NULL;
w = str_getunamb(arg, opwords);
if (!w) {
gf_log("cli", GF_LOG_DEBUG, "Unknown statedump option %s", arg);
return _gf_false;
}
*option = w;
return _gf_true;
}
int
cli_cmd_volume_statedump_options_parse(const char **words, int wordcount,
dict_t **options)
{
int ret = 0;
int i = 0;
dict_t *dict = NULL;
int option_cnt = 0;
char *option = NULL;
char *option_str = NULL;
char *tmp_str = NULL;
char *tmp = NULL;
char *ip_addr = NULL;
char *pid = NULL;
if ((wordcount >= 5) && ((strcmp(words[3], "client")) == 0)) {
tmp = gf_strdup(words[4]);
if (!tmp) {
ret = -1;
goto out;
}
ip_addr = strtok(tmp, ":");
pid = strtok(NULL, ":");
if (valid_internet_address(ip_addr, _gf_true, _gf_false) && pid &&
gf_valid_pid(pid, strlen(pid))) {
ret = gf_asprintf(&option_str, "%s %s %s", words[3], ip_addr, pid);
if (ret < 0) {
goto out;
}
option_cnt = 3;
} else {
ret = -1;
goto out;
}
} else {
for (i = 3; i < wordcount; i++, option_cnt++) {
if (!cli_cmd_validate_dumpoption(words[i], &option)) {
ret = -1;
goto out;
}
tmp_str = option_str;
option_str = NULL;
ret = gf_asprintf(&option_str, "%s%s ", tmp_str ? tmp_str : "",
option);
GF_FREE(tmp_str);
if (ret < 0) {
goto out;
}
}
if (option_str && (strstr(option_str, "nfs")) &&
strstr(option_str, "quotad")) {
ret = -1;
goto out;
}
}
dict = dict_new();
if (!dict) {
ret = -1;
goto out;
}
/* dynamic string in dict is freed up when dict is freed up, and hence
if option_str is NULL pass in an duplicate empty string to the same */
ret = dict_set_dynstr(dict, "options",
(option_str ? option_str : gf_strdup("")));
if (ret)
goto out;
option_str = NULL;
ret = dict_set_int32(dict, "option_cnt", option_cnt);
if (ret)
goto out;
*options = dict;
out:
GF_FREE(tmp);
GF_FREE(option_str);
if (ret && dict)
dict_unref(dict);
if (ret)
gf_log("cli", GF_LOG_ERROR, "Error parsing dumpoptions");
return ret;
}
int
cli_cmd_volume_clrlks_opts_parse(const char **words, int wordcount,
dict_t **options)
{
int ret = -1;
int i = 0;
dict_t *dict = NULL;
char *kind_opts[4] = {"blocked", "granted", "all", NULL};
char *types[4] = {"inode", "entry", "posix", NULL};
char *free_ptr = NULL;
dict = dict_new();
if (!dict)
goto out;
if (strcmp(words[4], "kind"))
goto out;
for (i = 0; kind_opts[i]; i++) {
if (!strcmp(words[5], kind_opts[i])) {
free_ptr = gf_strdup(words[5]);
ret = dict_set_dynstr(dict, "kind", free_ptr);
if (ret)
goto out;
free_ptr = NULL;
break;
}
}
if (i == 3)
goto out;
ret = -1;
for (i = 0; types[i]; i++) {
if (!strcmp(words[6], types[i])) {
free_ptr = gf_strdup(words[6]);
ret = dict_set_dynstr(dict, "type", free_ptr);
if (ret)
goto out;
free_ptr = NULL;
break;
}
}
if (i == 3)
goto out;
if (wordcount == 8) {
free_ptr = gf_strdup(words[7]);
ret = dict_set_dynstr(dict, "opts", free_ptr);
if (ret)
goto out;
free_ptr = NULL;
}
ret = 0;
*options = dict;
out:
if (ret) {
GF_FREE(free_ptr);
dict_unref(dict);
}
return ret;
}
static int
extract_hostname_path_from_token(const char *tmp_words, char **hostname,
char **path)
{
int ret = 0;
char *delimiter = NULL;
char *tmp_host = NULL;
char *host_name = NULL;
char *words = NULL;
int str_len = 0;
*hostname = NULL;
*path = NULL;
str_len = strlen(tmp_words) + 1;
words = GF_MALLOC(str_len, gf_common_mt_char);
if (!words) {
ret = -1;
goto out;
}
snprintf(words, str_len, "%s", tmp_words);
if (validate_brick_name(words)) {
cli_err(
"Wrong brick type: %s, use <HOSTNAME>:"
"<export-dir-abs-path>",
words);
ret = -1;
goto out;
} else {
delimiter = strrchr(words, ':');
ret = gf_canonicalize_path(delimiter + 1);
if (ret) {
goto out;
} else {
str_len = strlen(delimiter + 1) + 1;
*path = GF_MALLOC(str_len, gf_common_mt_char);
if (!*path) {
ret = -1;
goto out;
}
snprintf(*path, str_len, "%s", delimiter + 1);
}
}
tmp_host = gf_strdup(words);
if (!tmp_host) {
gf_log("cli", GF_LOG_ERROR, "Out of memory");
ret = -1;
goto out;
}
get_host_name(tmp_host, &host_name);
if (!host_name) {
ret = -1;
gf_log("cli", GF_LOG_ERROR,
"Unable to allocate "
"memory");
goto out;
}
if (!(strcmp(host_name, "localhost") && strcmp(host_name, "127.0.0.1") &&
strncmp(host_name, "0.", 2))) {
cli_err(
"Please provide a valid hostname/ip other "
"than localhost, 127.0.0.1 or loopback "
"address (0.0.0.0 to 0.255.255.255).");
ret = -1;
goto out;
}
if (!valid_internet_address(host_name, _gf_false, _gf_false)) {
cli_err(
"internet address '%s' does not conform to "
"standards",
host_name);
ret = -1;
goto out;
}
str_len = strlen(host_name) + 1;
*hostname = GF_MALLOC(str_len, gf_common_mt_char);
if (!*hostname) {
ret = -1;
goto out;
}
snprintf(*hostname, str_len, "%s", host_name);
ret = 0;
out:
GF_FREE(words);
GF_FREE(tmp_host);
return ret;
}
static int
set_hostname_path_in_dict(const char *token, dict_t *dict, int heal_op)
{
char *hostname = NULL;
char *path = NULL;
int ret = 0;
ret = extract_hostname_path_from_token(token, &hostname, &path);
if (ret)
goto out;
switch (heal_op) {
case GF_SHD_OP_SBRAIN_HEAL_FROM_BRICK:
ret = dict_set_dynstr(dict, "heal-source-hostname", hostname);
if (ret)
goto out;
hostname = NULL;
ret = dict_set_dynstr(dict, "heal-source-brickpath", path);
if (ret) {
goto out;
}
path = NULL;
break;
case GF_SHD_OP_STATISTICS_HEAL_COUNT_PER_REPLICA:
ret = dict_set_dynstr(dict, "per-replica-cmd-hostname", hostname);
if (ret)
goto out;
hostname = NULL;
ret = dict_set_dynstr(dict, "per-replica-cmd-path", path);
if (ret) {
goto out;
}
path = NULL;
break;
default:
ret = -1;
break;
}
out:
GF_FREE(hostname);
GF_FREE(path);
return ret;
}
static int
heal_command_type_get(const char *command)
{
int i = 0;
/* subcommands are set as NULL */
char *heal_cmds[GF_SHD_OP_HEAL_DISABLE + 1] = {
[GF_SHD_OP_INVALID] = NULL,
[GF_SHD_OP_HEAL_INDEX] = NULL,
[GF_SHD_OP_HEAL_FULL] = "full",
[GF_SHD_OP_INDEX_SUMMARY] = "info",
[GF_SHD_OP_HEALED_FILES] = NULL,
[GF_SHD_OP_HEAL_FAILED_FILES] = NULL,
[GF_SHD_OP_SPLIT_BRAIN_FILES] = NULL,
[GF_SHD_OP_STATISTICS] = "statistics",
[GF_SHD_OP_STATISTICS_HEAL_COUNT] = NULL,
[GF_SHD_OP_STATISTICS_HEAL_COUNT_PER_REPLICA] = NULL,
[GF_SHD_OP_SBRAIN_HEAL_FROM_BIGGER_FILE] = NULL,
[GF_SHD_OP_SBRAIN_HEAL_FROM_BRICK] = NULL,
[GF_SHD_OP_HEAL_ENABLE] = "enable",
[GF_SHD_OP_HEAL_DISABLE] = "disable",
};
for (i = 0; i <= GF_SHD_OP_HEAL_DISABLE; i++) {
if (heal_cmds[i] && (strcmp(heal_cmds[i], command) == 0))
return i;
}
return GF_SHD_OP_INVALID;
}
int
cli_cmd_volume_heal_options_parse(const char **words, int wordcount,
dict_t **options)
{
int ret = 0;
dict_t *dict = NULL;
gf_xl_afr_op_t op = GF_SHD_OP_INVALID;
dict = dict_new();
if (!dict) {
gf_log(THIS->name, GF_LOG_ERROR, "Failed to create the dict");
ret = -1;
goto out;
}
ret = dict_set_str(dict, "volname", (char *)words[2]);
if (ret) {
gf_log(THIS->name, GF_LOG_ERROR, "failed to set volname");
goto out;
}
if (wordcount == 3) {
ret = dict_set_int32(dict, "heal-op", GF_SHD_OP_HEAL_INDEX);
goto done;
}
if (wordcount == 4) {
op = heal_command_type_get(words[3]);
if (op == GF_SHD_OP_INVALID) {
ret = -1;
goto out;
}
ret = dict_set_int32(dict, "heal-op", op);
goto done;
}
if (wordcount == 5) {
if (strcmp(words[3], "info") && strcmp(words[3], "statistics") &&
strcmp(words[3], "granular-entry-heal")) {
ret = -1;
goto out;
}
if (!strcmp(words[3], "info")) {
if (!strcmp(words[4], "split-brain")) {
ret = dict_set_int32(dict, "heal-op",
GF_SHD_OP_SPLIT_BRAIN_FILES);
goto done;
}
if (!strcmp(words[4], "summary")) {
ret = dict_set_int32(dict, "heal-op", GF_SHD_OP_HEAL_SUMMARY);
goto done;
}
}
if (!strcmp(words[3], "statistics")) {
if (!strcmp(words[4], "heal-count")) {
ret = dict_set_int32(dict, "heal-op",
GF_SHD_OP_STATISTICS_HEAL_COUNT);
goto done;
}
}
if (!strcmp(words[3], "granular-entry-heal")) {
if (!strcmp(words[4], "enable")) {
ret = dict_set_int32(dict, "heal-op",
GF_SHD_OP_GRANULAR_ENTRY_HEAL_ENABLE);
goto done;
} else if (!strcmp(words[4], "disable")) {
ret = dict_set_int32(dict, "heal-op",
GF_SHD_OP_GRANULAR_ENTRY_HEAL_DISABLE);
goto done;
}
}
ret = -1;
goto out;
}
if (wordcount == 6) {
if (strcmp(words[3], "split-brain")) {
ret = -1;
goto out;
}
if (!strcmp(words[4], "bigger-file")) {
ret = dict_set_int32(dict, "heal-op",
GF_SHD_OP_SBRAIN_HEAL_FROM_BIGGER_FILE);
if (ret)
goto out;
ret = dict_set_str(dict, "file", (char *)words[5]);
if (ret)
goto out;
goto done;
}
if (!strcmp(words[4], "latest-mtime")) {
ret = dict_set_int32(dict, "heal-op",
GF_SHD_OP_SBRAIN_HEAL_FROM_LATEST_MTIME);
if (ret)
goto out;
ret = dict_set_str(dict, "file", (char *)words[5]);
if (ret)
goto out;
goto done;
}
if (!strcmp(words[4], "source-brick")) {
ret = dict_set_int32(dict, "heal-op",
GF_SHD_OP_SBRAIN_HEAL_FROM_BRICK);
if (ret)
goto out;
ret = set_hostname_path_in_dict(words[5], dict,
GF_SHD_OP_SBRAIN_HEAL_FROM_BRICK);
if (ret)
goto out;
goto done;
}
ret = -1;
goto out;
}
if (wordcount == 7) {
if (!strcmp(words[3], "statistics") &&
!strcmp(words[4], "heal-count") && !strcmp(words[5], "replica")) {
ret = dict_set_int32(dict, "heal-op",
GF_SHD_OP_STATISTICS_HEAL_COUNT_PER_REPLICA);
if (ret)
goto out;
ret = set_hostname_path_in_dict(
words[6], dict, GF_SHD_OP_STATISTICS_HEAL_COUNT_PER_REPLICA);
if (ret)
goto out;
goto done;
}
if (!strcmp(words[3], "split-brain") &&
!strcmp(words[4], "source-brick")) {
ret = dict_set_int32(dict, "heal-op",
GF_SHD_OP_SBRAIN_HEAL_FROM_BRICK);
ret = set_hostname_path_in_dict(words[5], dict,
GF_SHD_OP_SBRAIN_HEAL_FROM_BRICK);
if (ret)
goto out;
ret = dict_set_str(dict, "file", (char *)words[6]);
if (ret)
goto out;
goto done;
}
}
ret = -1;
goto out;
done:
*options = dict;
out:
if (ret && dict) {
dict_unref(dict);
*options = NULL;
}
return ret;
}
int
cli_cmd_volume_old_tier_parse(const char **words, int wordcount,
dict_t **options)
{
dict_t *dict = NULL;
int ret = -1;
char *volname = NULL;
gf_cli_defrag_type cmd = 0;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
if (wordcount != 4)
goto out;
if ((strcmp(words[1], "tier") == 0) && (strcmp(words[3], "start") == 0)) {
cmd = GF_DEFRAG_CMD_START_TIER;
} else
goto out;
volname = (char *)words[2];
ret = dict_set_str(dict, "volname", volname);
if (ret) {
gf_log(THIS->name, GF_LOG_ERROR, "failed to set dict");
goto out;
}
ret = dict_set_int32(dict, "rebalance-command", (int32_t)cmd);
if (ret) {
gf_log(THIS->name, GF_LOG_ERROR, "failed to set dict");
goto out;
}
*options = dict;
out:
if (ret && dict)
dict_unref(dict);
return ret;
}
int
cli_cmd_volume_defrag_parse(const char **words, int wordcount, dict_t **options)
{
dict_t *dict = NULL;
int ret = -1;
char *option = NULL;
char *volname = NULL;
char *command = NULL;
gf_cli_defrag_type cmd = 0;
GF_ASSERT(words);
GF_ASSERT(options);
dict = dict_new();
if (!dict)
goto out;
if (!((wordcount == 4) || (wordcount == 5)))
goto out;
if (wordcount == 4) {
if (strcmp(words[3], "start") && strcmp(words[3], "stop") &&
strcmp(words[3], "status"))
goto out;
} else {
if (strcmp(words[3], "fix-layout") && strcmp(words[3], "start"))
goto out;
}
volname = (char *)words[2];
if (wordcount == 4) {
command = (char *)words[3];
}
if (wordcount == 5) {
if ((strcmp(words[3], "fix-layout") || strcmp(words[4], "start")) &&
(strcmp(words[3], "start") || strcmp(words[4], "force"))) {
ret = -1;
goto out;
}
command = (char *)words[3];
option = (char *)words[4];
}
if (strcmp(command, "start") == 0) {
cmd = GF_DEFRAG_CMD_START;
if (option && strcmp(option, "force") == 0) {
cmd = GF_DEFRAG_CMD_START_FORCE;
}
goto done;
}
if (strcmp(command, "fix-layout") == 0) {
cmd = GF_DEFRAG_CMD_START_LAYOUT_FIX;
goto done;
}
if (strcmp(command, "stop") == 0) {
cmd = GF_DEFRAG_CMD_STOP;
goto done;
}
if (strcmp(command, "status") == 0) {
cmd = GF_DEFRAG_CMD_STATUS;
}
done:
ret = dict_set_str(dict, "volname", volname);
if (ret) {
gf_log(THIS->name, GF_LOG_ERROR, "failed to set dict");
goto out;
}
ret = dict_set_int32(dict, "rebalance-command", (int32_t)cmd);
if (ret) {
gf_log(THIS->name, GF_LOG_ERROR, "failed to set dict");
goto out;
}
*options = dict;
out:
if (ret && dict)
dict_unref(dict);
return ret;
}
int32_t
cli_snap_create_desc_parse(dict_t *dict, const char **words, size_t wordcount,
int32_t desc_opt_loc)
{
int32_t ret = -1;
char *desc = NULL;
int32_t desc_len = 0;
int len;
desc = GF_MALLOC(MAX_SNAP_DESCRIPTION_LEN + 1, gf_common_mt_char);
if (!desc) {
ret = -1;
goto out;
}
len = strlen(words[desc_opt_loc]);
if (len >= MAX_SNAP_DESCRIPTION_LEN) {
cli_out(
"snapshot create: description truncated: "
"Description provided is longer than 1024 characters");
desc_len = MAX_SNAP_DESCRIPTION_LEN;
} else {
desc_len = len;
}
snprintf(desc, desc_len + 1, "%s", words[desc_opt_loc]);
/* Calculating the size of the description as given by the user */
ret = dict_set_dynstr(dict, "description", desc);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Unable to save snap "
"description");
goto out;
}
ret = 0;
out:
if (ret && desc)
GF_FREE(desc);
return ret;
}
/* Function to check whether the Volume name is repeated */
int
cli_check_if_volname_repeated(const char **words, unsigned int start_index,
uint64_t cur_index)
{
uint64_t i = -1;
int ret = 0;
GF_ASSERT(words);
for (i = start_index; i < cur_index; i++) {
if (strcmp(words[i], words[cur_index]) == 0) {
ret = -1;
goto out;
}
}
out:
return ret;
}
/* snapshot clone <clonename> <snapname>
* @arg-0, dict : Request Dictionary to be sent to server side.
* @arg-1, words : Contains individual words of CLI command.
* @arg-2, wordcount: Contains number of words present in the CLI command.
*
* return value : -1 on failure
* 0 on success
*/
int
cli_snap_clone_parse(dict_t *dict, const char **words, int wordcount)
{
uint64_t i = 0;
int ret = -1;
char *clonename = NULL;
unsigned int cmdi = 2;
/* cmdi is command index, here cmdi is "2" (gluster snapshot clone)*/
GF_ASSERT(words);
GF_ASSERT(dict);
if (wordcount == cmdi + 1) {
cli_err("Invalid Syntax.");
gf_log("cli", GF_LOG_ERROR,
"Invalid number of words for snap clone command");
goto out;
}
if (strlen(words[cmdi]) >= GLUSTERD_MAX_SNAP_NAME) {
cli_err(
"snapshot clone: failed: clonename cannot exceed "
"255 characters.");
gf_log("cli", GF_LOG_ERROR, "Clone name too long");
goto out;
}
clonename = (char *)words[cmdi];
for (i = 0; i < strlen(clonename); i++) {
/* Following volume name convention */
if (!isalnum(clonename[i]) &&
(clonename[i] != '_' && (clonename[i] != '-'))) {
/* TODO : Is this message enough?? */
cli_err(
"Clonename can contain only alphanumeric, "
"\"-\" and \"_\" characters");
goto out;
}
}
ret = dict_set_int32(dict, "volcount", 1);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Could not save volcount");
goto out;
}
ret = dict_set_str(dict, "clonename", (char *)words[cmdi]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not save clone "
"name(%s)",
(char *)words[cmdi]);
goto out;
}
/* Filling snap name in the dictionary */
ret = dict_set_str(dict, "snapname", (char *)words[cmdi + 1]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not "
"save snap name(%s)",
(char *)words[cmdi + 1]);
goto out;
}
ret = 0;
out:
return ret;
}
/* snapshot create <snapname> <vol-name(s)> [description <description>]
* [force]
* @arg-0, dict : Request Dictionary to be sent to server side.
* @arg-1, words : Contains individual words of CLI command.
* @arg-2, wordcount: Contains number of words present in the CLI command.
*
* return value : -1 on failure
* 0 on success
*/
int
cli_snap_create_parse(dict_t *dict, const char **words, int wordcount)
{
uint64_t i = 0;
int ret = -1;
uint64_t volcount = 0;
char key[PATH_MAX] = "";
char *snapname = NULL;
unsigned int cmdi = 2;
int flags = 0;
/* cmdi is command index, here cmdi is "2" (gluster snapshot create)*/
GF_ASSERT(words);
GF_ASSERT(dict);
if (wordcount <= cmdi + 1) {
cli_err("Invalid Syntax.");
gf_log("cli", GF_LOG_ERROR, "Too less words for snap create command");
goto out;
}
if (strlen(words[cmdi]) >= GLUSTERD_MAX_SNAP_NAME) {
cli_err(
"snapshot create: failed: snapname cannot exceed "
"255 characters.");
gf_log("cli", GF_LOG_ERROR, "Snapname too long");
goto out;
}
snapname = (char *)words[cmdi];
for (i = 0; i < strlen(snapname); i++) {
/* Following volume name convention */
if (!isalnum(snapname[i]) &&
(snapname[i] != '_' && (snapname[i] != '-'))) {
/* TODO : Is this message enough?? */
cli_err(
"Snapname can contain only alphanumeric, "
"\"-\" and \"_\" characters");
goto out;
}
}
ret = dict_set_str(dict, "snapname", (char *)words[cmdi]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not save snap "
"name(%s)",
(char *)words[cmdi]);
goto out;
}
/* Filling volume name in the dictionary */
for (i = cmdi + 1;
i < wordcount && (strcmp(words[i], "description")) != 0 &&
(strcmp(words[i], "force") != 0) &&
(strcmp(words[i], "no-timestamp") != 0);
i++) {
volcount++;
/* volume index starts from 1 */
ret = snprintf(key, sizeof(key), "volname%" PRIu64, volcount);
if (ret < 0) {
goto out;
}
ret = dict_set_str(dict, key, (char *)words[i]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not "
"save volume name(%s)",
(char *)words[i]);
goto out;
}
if (i >= cmdi + 2) {
ret = -1;
cli_err(
"Creating multiple volume snapshot is not "
"supported as of now");
goto out;
}
/* TODO : remove this above condition check once
* multiple volume snapshot is supported */
}
if (volcount == 0) {
ret = -1;
cli_err("Please provide the volume name");
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = dict_set_int32(dict, "volcount", volcount);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Could not save volcount");
goto out;
}
/* Verify how we got out of "for" loop,
* if it is by reaching wordcount limit then goto "out",
* because we need not parse for "description","force" and
* "no-timestamp" after this.
*/
if (i == wordcount) {
goto out;
}
if (strcmp(words[i], "no-timestamp") == 0) {
ret = dict_set_int32n(dict, "no-timestamp", SLEN("no-timestamp"), 1);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not save "
"time-stamp option");
}
if (i == (wordcount - 1))
goto out;
i++;
}
if ((strcmp(words[i], "description")) == 0) {
++i;
if (i > (wordcount - 1)) {
ret = -1;
cli_err("Please provide a description");
gf_log("cli", GF_LOG_ERROR, "Description not provided");
goto out;
}
ret = cli_snap_create_desc_parse(dict, words, wordcount, i);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not save snap "
"description");
goto out;
}
if (i == (wordcount - 1))
goto out;
i++;
/* point the index to next word.
* As description might be follwed by force option.
* Before that, check if wordcount limit is reached
*/
}
if (strcmp(words[i], "force") == 0) {
flags = GF_CLI_FLAG_OP_FORCE;
} else {
ret = -1;
cli_err("Invalid Syntax.");
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
/* Check if the command has anything after "force" keyword */
if (++i < wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = 0;
out:
if (ret == 0) {
/*Adding force flag in either of the case i.e force set
* or unset*/
ret = dict_set_int32(dict, "flags", flags);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not save "
"snap force option");
}
}
return ret;
}
/* snapshot list [volname]
* @arg-0, dict : Request Dictionary to be sent to server side.
* @arg-1, words : Contains individual words of CLI command.
* @arg-2, wordcount: Contains number of words present in the CLI command.
*
* return value : -1 on failure
* 0 on success
*/
int
cli_snap_list_parse(dict_t *dict, const char **words, int wordcount)
{
int ret = -1;
GF_ASSERT(words);
GF_ASSERT(dict);
if (wordcount < 2 || wordcount > 3) {
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
if (wordcount == 2) {
ret = 0;
goto out;
}
ret = dict_set_str(dict, "volname", (char *)words[2]);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Failed to save volname in dictionary");
goto out;
}
out:
return ret;
}
/* snapshot info [(snapname | volume <volname>)]
* @arg-0, dict : Request Dictionary to be sent to server side.
* @arg-1, words : Contains individual words of CLI command.
* @arg-2, wordcount: Contains number of words present in the CLI command.
*
* return value : -1 on failure
* 0 on success
*/
int
cli_snap_info_parse(dict_t *dict, const char **words, int wordcount)
{
int ret = -1;
int32_t cmd = GF_SNAP_INFO_TYPE_ALL;
unsigned int cmdi = 2;
/* cmdi is command index, here cmdi is "2" (gluster snapshot info)*/
GF_ASSERT(words);
GF_ASSERT(dict);
if (wordcount > 4 || wordcount < cmdi) {
gf_log("cli", GF_LOG_ERROR, "Invalid syntax");
goto out;
}
if (wordcount == cmdi) {
ret = 0;
goto out;
}
/* If 3rd word is not "volume", then it must
* be snapname.
*/
if (strcmp(words[cmdi], "volume") != 0) {
ret = dict_set_str(dict, "snapname", (char *)words[cmdi]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Unable to save "
"snapname %s",
words[cmdi]);
goto out;
}
/* Once snap name is parsed, if we encounter any other
* word then fail it. Invalid Syntax.
* example : snapshot info <snapname> word
*/
if ((cmdi + 1) != wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
cmd = GF_SNAP_INFO_TYPE_SNAP;
ret = 0;
goto out;
/* No need to continue the parsing once we
* get the snapname
*/
}
/* If 3rd word is "volume", then check if next word
* is present. As, "snapshot info volume" is an
* invalid command.
*/
if ((cmdi + 1) == wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = dict_set_str(dict, "volname", (char *)words[wordcount - 1]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not save "
"volume name %s",
words[wordcount - 1]);
goto out;
}
cmd = GF_SNAP_INFO_TYPE_VOL;
out:
if (ret == 0) {
ret = dict_set_int32(dict, "sub-cmd", cmd);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not save "
"type of snapshot info");
}
}
return ret;
}
/* snapshot restore <snapname>
* @arg-0, dict : Request Dictionary to be sent to server side.
* @arg-1, words : Contains individual words of CLI command.
* @arg-2, wordcount: Contains number of words present in the CLI command.
*
* return value : -1 on failure
* 0 on success
*/
int
cli_snap_restore_parse(dict_t *dict, const char **words, int wordcount,
struct cli_state *state)
{
int ret = -1;
const char *question = NULL;
gf_answer_t answer = GF_ANSWER_NO;
GF_ASSERT(words);
GF_ASSERT(dict);
if (wordcount != 3) {
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = dict_set_str(dict, "snapname", (char *)words[2]);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Unable to save snap-name %s", words[2]);
goto out;
}
question =
"Restore operation will replace the "
"original volume with the snapshotted volume. "
"Do you still want to continue?";
answer = cli_cmd_get_confirmation(state, question);
if (GF_ANSWER_NO == answer) {
ret = 1;
gf_log("cli", GF_LOG_ERROR,
"User cancelled a snapshot "
"restore operation for snap %s",
(char *)words[2]);
goto out;
}
out:
return ret;
}
/* snapshot activate <snapname> [force]
* @arg-0, dict : Request Dictionary to be sent to server side.
* @arg-1, words : Contains individual words of CLI command.
* @arg-2, wordcount: Contains number of words present in the CLI command.
*
* return value : -1 on failure
* 0 on success
*/
int
cli_snap_activate_parse(dict_t *dict, const char **words, int wordcount)
{
int ret = -1;
int flags = 0;
GF_ASSERT(words);
GF_ASSERT(dict);
if ((wordcount < 3) || (wordcount > 4)) {
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = dict_set_str(dict, "snapname", (char *)words[2]);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Unable to save snap-name %s", words[2]);
goto out;
}
if (wordcount == 4) {
if (!strcmp("force", (char *)words[3])) {
flags = GF_CLI_FLAG_OP_FORCE;
} else {
gf_log("cli", GF_LOG_ERROR, "Invalid option");
ret = -1;
goto out;
}
}
ret = dict_set_int32(dict, "flags", flags);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Unable to save force option");
goto out;
}
out:
return ret;
}
/* snapshot deactivate <snapname>
* @arg-0, dict : Request Dictionary to be sent to server side.
* @arg-1, words : Contains individual words of CLI command.
* @arg-2, wordcount: Contains number of words present in the CLI command.
*
* return value : -1 on failure
* 0 on success
* 1 if user cancelled the request
*/
int
cli_snap_deactivate_parse(dict_t *dict, const char **words, int wordcount,
struct cli_state *state)
{
int ret = -1;
gf_answer_t answer = GF_ANSWER_NO;
const char *question =
"Deactivating snap will make its "
"data inaccessible. Do you want to "
"continue?";
GF_ASSERT(words);
GF_ASSERT(dict);
if ((wordcount != 3)) {
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = dict_set_str(dict, "snapname", (char *)words[2]);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Unable to save snap-name %s", words[2]);
goto out;
}
answer = cli_cmd_get_confirmation(state, question);
if (GF_ANSWER_NO == answer) {
ret = 1;
gf_log("cli", GF_LOG_DEBUG,
"User cancelled "
"snapshot deactivate operation");
goto out;
}
out:
return ret;
}
/* snapshot delete (all | snapname | volume <volname>)
* @arg-0, dict : Request Dictionary to be sent to server side.
* @arg-1, words : Contains individual words of CLI command.
* @arg-2, wordcount: Contains number of words present in the CLI command.
*
* return value : -1 on failure
* 0 on success
* 1 if user cancel the operation
*/
int
cli_snap_delete_parse(dict_t *dict, const char **words, int wordcount,
struct cli_state *state)
{
int ret = -1;
const char *question = NULL;
int32_t cmd = -1;
unsigned int cmdi = 2;
gf_answer_t answer = GF_ANSWER_NO;
GF_ASSERT(words);
GF_ASSERT(dict);
if (wordcount > 4 || wordcount <= cmdi) {
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
question =
"Deleting snap will erase all the information about "
"the snap. Do you still want to continue?";
if (strcmp(words[cmdi], "all") == 0) {
ret = 0;
cmd = GF_SNAP_DELETE_TYPE_ALL;
} else if (strcmp(words[cmdi], "volume") == 0) {
if (++cmdi == wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = dict_set_str(dict, "volname", (char *)words[cmdi]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not save "
"volume name %s",
words[wordcount - 1]);
goto out;
}
cmd = GF_SNAP_DELETE_TYPE_VOL;
} else {
ret = dict_set_str(dict, "snapname", (char *)words[cmdi]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Unable to save "
"snapname %s",
words[2]);
goto out;
}
cmd = GF_SNAP_DELETE_TYPE_SNAP;
}
if ((cmdi + 1) != wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
if (cmd == GF_SNAP_DELETE_TYPE_SNAP) {
answer = cli_cmd_get_confirmation(state, question);
if (GF_ANSWER_NO == answer) {
ret = 1;
gf_log("cli", GF_LOG_DEBUG,
"User cancelled "
"snapshot delete operation for snap %s",
(char *)words[2]);
goto out;
}
}
ret = dict_set_int32(dict, "sub-cmd", cmd);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not save "
"type of snapshot delete");
}
out:
return ret;
}
/* snapshot status [(snapname | volume <volname>)]
* @arg-0, dict : Request Dictionary to be sent to server side.
* @arg-1, words : Contains individual words of CLI command.
* @arg-2, wordcount: Contains number of words present in the CLI command.
*
* return value : -1 on failure
* 0 on success
*/
int
cli_snap_status_parse(dict_t *dict, const char **words, int wordcount)
{
int ret = -1;
int32_t cmd = GF_SNAP_STATUS_TYPE_ALL;
unsigned int cmdi = 2;
/* cmdi is command index, here cmdi is "2" (gluster snapshot status)*/
GF_ASSERT(words);
GF_ASSERT(dict);
if (wordcount > 4 || wordcount < cmdi) {
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
if (wordcount == cmdi) {
ret = 0;
goto out;
}
/* if 3rd word is not "volume", then it must be "snapname"
*/
if (strcmp(words[cmdi], "volume") != 0) {
ret = dict_set_str(dict, "snapname", (char *)words[cmdi]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Count not save "
"snap name %s",
words[cmdi]);
goto out;
}
if ((cmdi + 1) != wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = 0;
cmd = GF_SNAP_STATUS_TYPE_SNAP;
goto out;
}
/* If 3rd word is "volume", then check if next word is present.
* As, "snapshot info volume" is an invalid command
*/
if ((cmdi + 1) == wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = dict_set_str(dict, "volname", (char *)words[wordcount - 1]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Count not save "
"volume name %s",
words[wordcount - 1]);
goto out;
}
cmd = GF_SNAP_STATUS_TYPE_VOL;
out:
if (ret == 0) {
ret = dict_set_int32(dict, "sub-cmd", cmd);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not save cmd "
"of snapshot status");
}
}
return ret;
}
/* return value:
* -1 in case of failure.
* 0 in case of success.
*/
int32_t
cli_snap_config_limit_parse(const char **words, dict_t *dict,
unsigned int wordcount, unsigned int index,
char *key)
{
int ret = -1;
int limit = 0;
char *end_ptr = NULL;
GF_ASSERT(words);
GF_ASSERT(dict);
GF_ASSERT(key);
if (index >= wordcount) {
ret = -1;
cli_err("Please provide a value for %s.", key);
gf_log("cli", GF_LOG_ERROR, "Value not provided for %s", key);
goto out;
}
limit = strtol(words[index], &end_ptr, 10);
if (limit <= 0 || strcmp(end_ptr, "") != 0) {
ret = -1;
cli_err(
"Please enter an integer value "
"greater than zero for %s",
key);
goto out;
}
ret = dict_set_int32(dict, key, limit);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Could not set "
"%s in dictionary",
key);
goto out;
}
ret = dict_set_dynstr_with_alloc(dict, "globalname", "All");
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Could not set global key");
goto out;
}
ret = dict_set_int32(dict, "hold_global_locks", _gf_true);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Could not set global locks");
goto out;
}
out:
return ret;
}
/* function cli_snap_config_parse
* Config Syntax : gluster snapshot config [volname]
* [snap-max-hard-limit <count>]
* [snap-max-soft-limit <count>]
*
return value: <0 on failure
1 if user cancels the operation, or limit value is out of
range
0 on success
NOTE : snap-max-soft-limit can only be set for system.
*/
int32_t
cli_snap_config_parse(const char **words, int wordcount, dict_t *dict,
struct cli_state *state)
{
int ret = -1;
gf_answer_t answer = GF_ANSWER_NO;
gf_boolean_t vol_presence = _gf_false;
struct snap_config_opt_vals_ *conf_vals = NULL;
int8_t hard_limit = 0;
int8_t soft_limit = 0;
int8_t config_type = -1;
const char *question = NULL;
unsigned int cmdi = 2;
/* cmdi is command index, here cmdi is "2" (gluster snapshot config)*/
GF_ASSERT(words);
GF_ASSERT(dict);
GF_ASSERT(state);
if ((wordcount < 2) || (wordcount > 7)) {
gf_log("cli", GF_LOG_ERROR, "Invalid wordcount(%d)", wordcount);
goto out;
}
if (wordcount == 2) {
config_type = GF_SNAP_CONFIG_DISPLAY;
ret = 0;
goto set;
}
/* auto-delete cannot be a volume name */
/* Check whether the 3rd word is volname */
if (strcmp(words[cmdi], "snap-max-hard-limit") != 0 &&
strcmp(words[cmdi], "snap-max-soft-limit") != 0 &&
strcmp(words[cmdi], "auto-delete") != 0 &&
strcmp(words[cmdi], "activate-on-create") != 0) {
ret = dict_set_str(dict, "volname", (char *)words[cmdi]);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Failed to set volname");
goto out;
}
cmdi++;
vol_presence = _gf_true;
if (cmdi == wordcount) {
config_type = GF_SNAP_CONFIG_DISPLAY;
ret = 0;
goto set;
}
}
config_type = GF_SNAP_CONFIG_TYPE_SET;
if (strcmp(words[cmdi], "snap-max-hard-limit") == 0) {
ret = cli_snap_config_limit_parse(words, dict, wordcount, ++cmdi,
"snap-max-hard-limit");
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Failed to parse snap "
"config hard limit");
goto out;
}
hard_limit = 1;
if (++cmdi == wordcount) {
ret = 0;
goto set;
}
}
if (strcmp(words[cmdi], "snap-max-soft-limit") == 0) {
if (vol_presence == 1) {
ret = -1;
cli_err(
"Soft limit cannot be set to individual "
"volumes.");
gf_log("cli", GF_LOG_ERROR,
"Soft limit cannot be "
"set to volumes");
goto out;
}
ret = cli_snap_config_limit_parse(words, dict, wordcount, ++cmdi,
"snap-max-soft-limit");
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Failed to parse snap "
"config soft limit");
goto out;
}
if (++cmdi != wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
soft_limit = 1;
}
if (hard_limit || soft_limit)
goto set;
if (strcmp(words[cmdi], "auto-delete") == 0) {
if (vol_presence == 1) {
ret = -1;
cli_err(
"As of now, auto-delete option cannot be set "
"to volumes");
gf_log("cli", GF_LOG_ERROR,
"auto-delete option "
"cannot be set to volumes");
goto out;
}
if (++cmdi >= wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = dict_set_str(dict, "auto-delete", (char *)words[cmdi]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Failed to set "
"value of auto-delete in request "
"dictionary");
goto out;
}
if (++cmdi != wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
} else if (strcmp(words[cmdi], "activate-on-create") == 0) {
if (vol_presence == 1) {
ret = -1;
cli_err(
"As of now, activate-on-create option "
"cannot be set to volumes");
gf_log("cli", GF_LOG_ERROR,
"activate-on-create "
"option cannot be set to volumes");
goto out;
}
if (++cmdi >= wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = dict_set_str(dict, "snap-activate-on-create",
(char *)words[cmdi]);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Failed to set value "
"of activate-on-create in request dictionary");
goto out;
}
if (++cmdi != wordcount) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
} else {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = 0; /* Success */
set:
ret = dict_set_int32(dict, "config-command", config_type);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Unable to set "
"config-command");
goto out;
}
if (config_type == GF_SNAP_CONFIG_TYPE_SET && (hard_limit || soft_limit)) {
conf_vals = snap_confopt_vals;
if (hard_limit && soft_limit) {
question = conf_vals[GF_SNAP_CONFIG_SET_BOTH].question;
} else if (soft_limit) {
question = conf_vals[GF_SNAP_CONFIG_SET_SOFT].question;
} else if (hard_limit) {
question = conf_vals[GF_SNAP_CONFIG_SET_HARD].question;
}
answer = cli_cmd_get_confirmation(state, question);
if (GF_ANSWER_NO == answer) {
ret = 1;
gf_log("cli", GF_LOG_DEBUG,
"User cancelled "
"snapshot config operation");
}
}
out:
return ret;
}
int
validate_op_name(const char *op, const char *opname, char **opwords)
{
int ret = -1;
int i = 0;
GF_ASSERT(opname);
GF_ASSERT(opwords);
for (i = 0; opwords[i] != NULL; i++) {
if (strcmp(opwords[i], opname) == 0) {
cli_out("\"%s\" cannot be a %s", opname, op);
goto out;
}
}
ret = 0;
out:
return ret;
}
int32_t
cli_cmd_snapshot_parse(const char **words, int wordcount, dict_t **options,
struct cli_state *state)
{
int32_t ret = -1;
dict_t *dict = NULL;
gf1_cli_snapshot type = GF_SNAP_OPTION_TYPE_NONE;
char *w = NULL;
char *opwords[] = {"create", "delete", "restore", "activate",
"deactivate", "list", "status", "config",
"info", "clone", NULL};
char *invalid_snapnames[] = {"description", "force", "volume", "all", NULL};
char *invalid_volnames[] = {"volume",
"type",
"subvolumes",
"option",
"end-volume",
"all",
"volume_not_in_ring",
"description",
"force",
"snap-max-hard-limit",
"snap-max-soft-limit",
"auto-delete",
"activate-on-create",
NULL};
GF_ASSERT(words);
GF_ASSERT(options);
GF_ASSERT(state);
dict = dict_new();
if (!dict)
goto out;
/* Lowest wordcount possible */
if (wordcount < 2) {
gf_log("", GF_LOG_ERROR, "Invalid command: Not enough arguments");
goto out;
}
w = str_getunamb(words[1], opwords);
if (!w) {
/* Checks if the operation is a valid operation */
gf_log("", GF_LOG_ERROR, "Opword Mismatch");
goto out;
}
if (!strcmp(w, "create")) {
type = GF_SNAP_OPTION_TYPE_CREATE;
} else if (!strcmp(w, "list")) {
type = GF_SNAP_OPTION_TYPE_LIST;
} else if (!strcmp(w, "info")) {
type = GF_SNAP_OPTION_TYPE_INFO;
} else if (!strcmp(w, "delete")) {
type = GF_SNAP_OPTION_TYPE_DELETE;
} else if (!strcmp(w, "config")) {
type = GF_SNAP_OPTION_TYPE_CONFIG;
} else if (!strcmp(w, "restore")) {
type = GF_SNAP_OPTION_TYPE_RESTORE;
} else if (!strcmp(w, "status")) {
type = GF_SNAP_OPTION_TYPE_STATUS;
} else if (!strcmp(w, "activate")) {
type = GF_SNAP_OPTION_TYPE_ACTIVATE;
} else if (!strcmp(w, "deactivate")) {
type = GF_SNAP_OPTION_TYPE_DEACTIVATE;
} else if (!strcmp(w, "clone")) {
type = GF_SNAP_OPTION_TYPE_CLONE;
}
if (type != GF_SNAP_OPTION_TYPE_CONFIG &&
type != GF_SNAP_OPTION_TYPE_STATUS) {
ret = dict_set_int32(dict, "hold_snap_locks", _gf_true);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Unable to set hold-snap-locks value "
"as _gf_true");
goto out;
}
}
/* Following commands does not require volume locks */
if (type == GF_SNAP_OPTION_TYPE_STATUS ||
type == GF_SNAP_OPTION_TYPE_ACTIVATE ||
type == GF_SNAP_OPTION_TYPE_DEACTIVATE) {
ret = dict_set_int32(dict, "hold_vol_locks", _gf_false);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Setting volume lock "
"flag failed");
goto out;
}
}
/* Check which op is intended */
switch (type) {
case GF_SNAP_OPTION_TYPE_CREATE:
/* Syntax :
* gluster snapshot create <snapname> <vol-name(s)>
* [no-timestamp]
* [description <description>]
* [force]
*/
/* In cases where the snapname is not given then
* parsing fails & snapname cannot be "description",
* "force" and "volume", that check is made here
*/
if (wordcount == 2) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = validate_op_name("snapname", words[2], invalid_snapnames);
if (ret) {
goto out;
}
ret = cli_snap_create_parse(dict, words, wordcount);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "create command parsing failed.");
goto out;
}
break;
case GF_SNAP_OPTION_TYPE_CLONE:
/* Syntax :
* gluster snapshot clone <clonename> <snapname>
*/
/* In cases where the clonename is not given then
* parsing fails & snapname cannot be "description",
* "force" and "volume", that check is made here
*/
if (wordcount == 2) {
ret = -1;
gf_log("cli", GF_LOG_ERROR, "Invalid Syntax");
goto out;
}
ret = validate_op_name("clonename", words[2], invalid_volnames);
if (ret) {
goto out;
}
ret = cli_snap_clone_parse(dict, words, wordcount);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "clone command parsing failed.");
goto out;
}
break;
case GF_SNAP_OPTION_TYPE_INFO:
/* Syntax :
* gluster snapshot info [(snapname] | [vol <volname>)]
*/
ret = cli_snap_info_parse(dict, words, wordcount);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Failed to parse "
"snapshot info command");
goto out;
}
break;
case GF_SNAP_OPTION_TYPE_LIST:
/* Syntax :
* gluster snaphsot list [volname]
*/
ret = cli_snap_list_parse(dict, words, wordcount);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Failed to parse "
"snapshot list command");
goto out;
}
break;
case GF_SNAP_OPTION_TYPE_DELETE:
/* Syntax :
* snapshot delete (all | snapname | volume <volname>)
*/
ret = cli_snap_delete_parse(dict, words, wordcount, state);
if (ret) {
/* A positive ret value means user cancelled
* the command */
if (ret < 0) {
gf_log("cli", GF_LOG_ERROR,
"Failed to parse "
"snapshot delete command");
}
goto out;
}
break;
case GF_SNAP_OPTION_TYPE_CONFIG:
/* snapshot config [volname] [snap-max-hard-limit <count>]
* [snap-max-soft-limit <percent>] */
ret = cli_snap_config_parse(words, wordcount, dict, state);
if (ret) {
if (ret < 0)
gf_log("cli", GF_LOG_ERROR,
"config command parsing failed.");
goto out;
}
ret = dict_set_int32(dict, "type", GF_SNAP_OPTION_TYPE_CONFIG);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Unable to set "
"config type");
ret = -1;
goto out;
}
break;
case GF_SNAP_OPTION_TYPE_STATUS: {
/* Syntax :
* gluster snapshot status [(snapname |
* volume <volname>)]
*/
ret = cli_snap_status_parse(dict, words, wordcount);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Failed to parse "
"snapshot status command");
goto out;
}
break;
}
case GF_SNAP_OPTION_TYPE_RESTORE:
/* Syntax:
* snapshot restore <snapname>
*/
ret = cli_snap_restore_parse(dict, words, wordcount, state);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Failed to parse "
"restore command");
goto out;
}
break;
case GF_SNAP_OPTION_TYPE_ACTIVATE:
/* Syntax:
* snapshot activate <snapname> [force]
*/
ret = cli_snap_activate_parse(dict, words, wordcount);
if (ret) {
gf_log("cli", GF_LOG_ERROR,
"Failed to parse "
"start command");
goto out;
}
break;
case GF_SNAP_OPTION_TYPE_DEACTIVATE:
/* Syntax:
* snapshot deactivate <snapname>
*/
ret = cli_snap_deactivate_parse(dict, words, wordcount, state);
if (ret) {
/* A positive ret value means user cancelled
* the command */
if (ret < 0) {
gf_log("cli", GF_LOG_ERROR,
"Failed to parse deactivate "
"command");
}
goto out;
}
break;
default:
ret = -1;
gf_log("", GF_LOG_ERROR, "Opword Mismatch");
goto out;
}
ret = dict_set_int32(dict, "type", type);
if (ret) {
gf_log("", GF_LOG_ERROR, "Failed to set type.");
goto out;
}
/* If you got so far, input is valid */
ret = 0;
out:
if (ret) {
if (dict)
dict_unref(dict);
} else
*options = dict;
return ret;
}
int
cli_cmd_validate_volume(char *volname)
{
int i = 0;
int ret = -1;
int volname_len;
if (volname[0] == '-')
return ret;
if (!strcmp(volname, "all")) {
cli_err("\"all\" cannot be the name of a volume.");
return ret;
}
if (strchr(volname, '/')) {
cli_err("Volume name should not contain \"/\" character.");
return ret;
}
volname_len = strlen(volname);
if (volname_len > GD_VOLUME_NAME_MAX) {
cli_err("Volname can not exceed %d characters.", GD_VOLUME_NAME_MAX);
return ret;
}
for (i = 0; i < volname_len; i++)
if (!isalnum(volname[i]) && (volname[i] != '_') &&
(volname[i] != '-')) {
cli_err(
"Volume name should not contain \"%c\""
" character.\nVolume names can only"
"contain alphanumeric, '-' and '_' "
"characters.",
volname[i]);
return ret;
}
ret = 0;
return ret;
}
int32_t
cli_cmd_bitrot_parse(const char **words, int wordcount, dict_t **options)
{
int32_t ret = -1;
char *w = NULL;
char *volname = NULL;
char *opwords[] = {
"enable", "disable", "scrub-throttle", "scrub-frequency", "scrub",
"signing-time", NULL};
char *scrub_throt_values[] = {"lazy", "normal", "aggressive", NULL};
char *scrub_freq_values[] = {"hourly", "daily", "weekly", "biweekly",
"monthly", "minute", NULL};
char *scrub_values[] = {"pause", "resume", "status", "ondemand", NULL};
dict_t *dict = NULL;
gf_bitrot_type type = GF_BITROT_OPTION_TYPE_NONE;
int32_t expiry_time = 0;
GF_ASSERT(words);
GF_ASSERT(options);
/* Hack to print out bitrot help properly */
if ((wordcount == 3) && !(strcmp(words[2], "help"))) {
ret = 1;
return ret;
}
if (wordcount < 4 || wordcount > 5) {
gf_log("cli", GF_LOG_ERROR, "Invalid syntax");
goto out;
}
dict = dict_new();
if (!dict)
goto out;
volname = (char *)words[2];
if (!volname) {
ret = -1;
goto out;
}
ret = cli_cmd_validate_volume(volname);
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Failed to validate volume name");
goto out;
}
ret = dict_set_str(dict, "volname", volname);
if (ret) {
cli_out("Failed to set volume name in dictionary ");
goto out;
}
w = str_getunamb(words[3], opwords);
if (!w) {
cli_out("Invalid bit rot option : %s", words[3]);
ret = -1;
goto out;
}
if (strcmp(w, "enable") == 0) {
if (wordcount == 4) {
type = GF_BITROT_OPTION_TYPE_ENABLE;
ret = 0;
goto set_type;
} else {
ret = -1;
goto out;
}
}
if (strcmp(w, "disable") == 0) {
if (wordcount == 4) {
type = GF_BITROT_OPTION_TYPE_DISABLE;
ret = 0;
goto set_type;
} else {
ret = -1;
goto out;
}
}
if (!strcmp(w, "scrub-throttle")) {
if (!words[4]) {
cli_err(
"Missing scrub-throttle value for bitrot "
"option");
ret = -1;
goto out;
} else {
w = str_getunamb(words[4], scrub_throt_values);
if (!w) {
cli_err(
"Invalid scrub-throttle option for "
"bitrot");
ret = -1;
goto out;
} else {
type = GF_BITROT_OPTION_TYPE_SCRUB_THROTTLE;
ret = dict_set_str(dict, "scrub-throttle-value",
(char *)words[4]);
if (ret) {
cli_out(
"Failed to set scrub-throttle "
"value in the dict");
goto out;
}
goto set_type;
}
}
}
if (!strcmp(words[3], "scrub-frequency")) {
if (!words[4]) {
cli_err("Missing scrub-frequency value");
ret = -1;
goto out;
} else {
w = str_getunamb(words[4], scrub_freq_values);
if (!w) {
cli_err("Invalid frequency option for bitrot");
ret = -1;
goto out;
} else {
type = GF_BITROT_OPTION_TYPE_SCRUB_FREQ;
ret = dict_set_str(dict, "scrub-frequency-value",
(char *)words[4]);
if (ret) {
cli_out(
"Failed to set dict for "
"bitrot");
goto out;
}
goto set_type;
}
}
}
if (!strcmp(words[3], "scrub")) {
if (!words[4]) {
cli_err("Missing scrub value for bitrot option");
ret = -1;
goto out;
} else {
w = str_getunamb(words[4], scrub_values);
if (!w) {
cli_err("Invalid scrub option for bitrot");
ret = -1;
goto out;
} else {
if (strcmp(words[4], "status") == 0) {
type = GF_BITROT_CMD_SCRUB_STATUS;
} else if (strcmp(words[4], "ondemand") == 0) {
type = GF_BITROT_CMD_SCRUB_ONDEMAND;
} else {
type = GF_BITROT_OPTION_TYPE_SCRUB;
}
ret = dict_set_str(dict, "scrub-value", (char *)words[4]);
if (ret) {
cli_out(
"Failed to set dict for "
"bitrot");
goto out;
}
goto set_type;
}
}
}
if (!strcmp(words[3], "signing-time")) {
if (!words[4]) {
cli_err(
"Missing signing-time value for bitrot "
"option");
ret = -1;
goto out;
} else {
type = GF_BITROT_OPTION_TYPE_EXPIRY_TIME;
expiry_time = strtol(words[4], NULL, 0);
if (expiry_time < 1) {
cli_err(
"Expiry time value should not be less"
" than 1");
ret = -1;
goto out;
}
ret = dict_set_uint32(dict, "expiry-time",
(unsigned int)expiry_time);
if (ret) {
cli_out("Failed to set dict for bitrot");
goto out;
}
goto set_type;
}
} else {
cli_err(
"Invalid option %s for bitrot. Please enter valid "
"bitrot option",
words[3]);
ret = -1;
goto out;
}
set_type:
ret = dict_set_int32(dict, "type", type);
if (ret < 0)
goto out;
*options = dict;
out:
if (ret) {
gf_log("cli", GF_LOG_ERROR, "Unable to parse bitrot command");
if (dict)
dict_unref(dict);
}
return ret;
}