blob: 866d2274d795a87b760765e2d271d4689af6dd4a [file] [edit]
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/genetlink.h>
#include "libgenl.h"
#include <linux/drbd.h>
#include <linux/drbd_config.h>
#include <linux/drbd_genl_api.h>
#include <linux/drbd_limits.h>
#include "drbd_nla.h"
#include "drbdtool_common.h"
#include <linux/genl_magic_func.h>
#include "config_flags.h"
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#endif
#define NLA_POLICY(p) \
.nla_policy = p ## _nl_policy, \
.nla_policy_size = ARRAY_SIZE(p ## _nl_policy)
struct en_map {
const char *name;
int value;
};
/* ============================================================================================== */
static int enum_string_to_int(const char **map, int size, const char *value,
int (*strcmp_fn)(const char *, const char *))
{
int n;
if (!value)
return -1;
for (n = 0; n < size; n++) {
if (map[n] && !strcmp_fn(value, map[n]))
return n;
}
return -1;
}
static bool enum_is_default(struct field_def *field, const char *value)
{
int n;
n = enum_string_to_int(field->u.e.map, field->u.e.size, value, strcmp);
return n == field->u.e.def;
}
static bool enum_is_equal(struct field_def *field, const char *a, const char *b)
{
return !strcmp(a, b);
}
static int type_of_field(struct context_def *ctx, struct field_def *field)
{
return ctx->nla_policy[__nla_type(field->nla_type)].type;
}
static int len_of_field(struct context_def *ctx, struct field_def *field)
{
return ctx->nla_policy[__nla_type(field->nla_type)].len;
}
static const char *get_enum(struct context_def *ctx, struct field_def *field, struct nlattr *nla)
{
int i;
assert(type_of_field(ctx, field) == NLA_U32);
i = nla_get_u32(nla);
if (i < 0 || i >= field->u.e.size)
return NULL;
return field->u.e.map[i];
}
static bool put_enum(struct context_def *ctx, struct field_def *field,
struct msg_buff *msg, const char *value)
{
int n;
n = enum_string_to_int(field->u.e.map, field->u.e.size, value, strcmp);
if (n == -1)
return false;
assert(type_of_field(ctx, field) == NLA_U32);
nla_put_u32(msg, field->nla_type, n);
return true;
}
static int enum_usage(struct field_def *field, char *str, int size)
{
const char** map = field->u.e.map;
char sep = '{';
int n, len = 0, l;
l = snprintf(str, size, "[--%s=", field->name);
len += l; size -= l;
for (n = 0; n < field->u.e.size; n++) {
if (!map[n])
continue;
l = snprintf(str + len, size, "%c%s", sep, map[n]);
len += l; size -= l;
sep = '|';
}
assert (sep != '{');
l = snprintf(str+len, size, "}]");
len += l;
/* size -= l; */
return len;
}
static bool enum_is_default_nocase(struct field_def *field, const char *value)
{
int n;
n = enum_string_to_int(field->u.e.map, field->u.e.size, value, strcasecmp);
return n == field->u.e.def;
}
static bool enum_is_equal_nocase(struct field_def *field, const char *a, const char *b)
{
return !strcasecmp(a, b);
}
static bool put_enum_nocase(struct context_def *ctx, struct field_def *field,
struct msg_buff *msg, const char *value)
{
int n;
n = enum_string_to_int(field->u.e.map, field->u.e.size, value, strcasecmp);
if (n == -1)
return false;
assert(type_of_field(ctx, field) == NLA_U32);
nla_put_u32(msg, field->nla_type, n);
return true;
}
static void enum_describe_xml(struct field_def *field)
{
const char **map = field->u.e.map;
int n;
printf("\t<option name=\"%s\" type=\"handler\">\n",
field->name);
for (n = 0; n < field->u.e.size; n++) {
if (!map[n])
continue;
printf("\t\t<handler>%s</handler>\n", map[n]);
}
printf("\t</option>\n");
}
static enum check_codes enum_check(struct field_def *field, const char *value)
{
int n = enum_string_to_int(field->u.e.map, field->u.e.size, value, strcmp);
return n == -1 ? CC_NOT_AN_ENUM : CC_OK;
}
static enum check_codes enum_check_nocase(struct field_def *field, const char *value)
{
int n = enum_string_to_int(field->u.e.map, field->u.e.size, value, strcasecmp);
return n == -1 ? CC_NOT_AN_ENUM : CC_OK;
}
struct field_class fc_enum = {
.is_default = enum_is_default,
.is_equal = enum_is_equal,
.get = get_enum,
.put = put_enum,
.usage = enum_usage,
.describe_xml = enum_describe_xml,
.check = enum_check,
};
struct field_class fc_enum_nocase = {
.is_default = enum_is_default_nocase,
.is_equal = enum_is_equal_nocase,
.get = get_enum,
.put = put_enum_nocase,
.usage = enum_usage,
.describe_xml = enum_describe_xml,
.check = enum_check_nocase,
};
/* ---------------------------------------------------------------------------------------------- */
static bool numeric_is_default(struct field_def *field, const char *value)
{
long long l;
/* FIXME: unsigned long long values are broken. */
l = m_strtoll(value, field->u.n.scale);
return l == field->u.n.def;
}
static bool numeric_is_equal(struct field_def *field, const char *a, const char *b)
{
long long la, lb;
/* FIXME: unsigned long long values are broken. */
la = m_strtoll(a, field->u.n.scale);
lb = m_strtoll(b, field->u.n.scale);
return la == lb;
}
static const char *get_numeric(struct context_def *ctx, struct field_def *field, struct nlattr *nla)
{
static char buffer[1 + 20 + 2];
char scale = field->u.n.scale;
unsigned long long l;
int n;
switch(type_of_field(ctx, field)) {
case NLA_U8:
l = nla_get_u8(nla);
break;
case NLA_U16:
l = nla_get_u16(nla);
break;
case NLA_U32:
l = nla_get_u32(nla);
break;
case NLA_U64:
l = nla_get_u64(nla);
break;
default:
return NULL;
}
if (field->u.n.is_signed) {
/* Sign extend. */
switch(type_of_field(ctx, field)) {
case NLA_U8:
l = (int8_t)l;
break;
case NLA_U16:
l = (int16_t)l;
break;
case NLA_U32:
l = (int32_t)l;
break;
case NLA_U64:
l = (int64_t)l;
break;
}
n = snprintf(buffer, sizeof(buffer), "%lld%c",
l, scale == '1' ? 0 : scale);
} else
n = snprintf(buffer, sizeof(buffer), "%llu%c",
l, scale == '1' ? 0 : scale);
assert(n < sizeof(buffer));
return buffer;
}
static bool put_numeric(struct context_def *ctx, struct field_def *field,
struct msg_buff *msg, const char *value)
{
long long l;
/* FIXME: unsigned long long values are broken. */
l = m_strtoll(value, field->u.n.scale);
switch(type_of_field(ctx, field)) {
case NLA_U8:
nla_put_u8(msg, field->nla_type, l);
break;
case NLA_U16:
nla_put_u16(msg, field->nla_type, l);
break;
case NLA_U32:
nla_put_u32(msg, field->nla_type, l);
break;
case NLA_U64:
nla_put_u64(msg, field->nla_type, l);
break;
default:
return false;
}
return true;
}
static int numeric_usage(struct field_def *field, char *str, int size)
{
return snprintf(str, size,"[--%s=(%lld ... %lld)]",
field->name,
field->u.n.min,
field->u.n.max);
}
static void numeric_describe_xml(struct field_def *field)
{
printf("\t<option name=\"%s\" type=\"numeric\">\n"
"\t\t<min>%lld</min>\n"
"\t\t<max>%lld</max>\n"
"\t\t<default>%lld</default>\n"
"\t\t<unit_prefix>%c</unit_prefix>\n",
field->name,
field->u.n.min,
field->u.n.max,
field->u.n.def,
field->u.n.scale);
if(field->unit) {
printf("\t\t<unit>%s</unit>\n",
field->unit);
}
printf("\t</option>\n");
}
static enum check_codes numeric_check(struct field_def *field, const char *value)
{
enum new_strtoll_errs e;
unsigned long long l;
e = new_strtoll(value, field->u.n.scale, &l);
if (e != MSE_OK)
return CC_NOT_A_NUMBER;
if (l < field->u.n.min) {
if (field->implicit_clamp)
l = field->u.n.min;
else
return CC_TOO_SMALL;
}
if (l > field->u.n.max) {
if (field->implicit_clamp)
l = field->u.n.max;
else
return CC_TOO_BIG;
}
return CC_OK;
}
struct field_class fc_numeric = {
.is_default = numeric_is_default,
.is_equal = numeric_is_equal,
.get = get_numeric,
.put = put_numeric,
.usage = numeric_usage,
.describe_xml = numeric_describe_xml,
.check = numeric_check,
};
/* ---------------------------------------------------------------------------------------------- */
static int enum_num_to_int(const struct en_map *map, int map_size, const char *value,
enum new_strtoll_errs *err_p)
{
enum new_strtoll_errs e;
unsigned long long l;
int i;
if (!value) {
if (err_p)
*err_p = MSE_MISSING_NUMBER;
return -1;
}
for (i = 0; i < map_size; i++) {
if (!strcmp(value, map[i].name))
return map[i].value;
}
e = new_strtoll(value, 1, &l);
if (err_p)
*err_p = e;
return e == MSE_OK ? l : -1;
}
static bool enum_num_is_default(struct field_def *field, const char *value)
{
int n;
n = enum_num_to_int(field->u.en.map, field->u.en.map_size, value, NULL);
return n == field->u.en.def;
}
static bool enum_num_is_equal(struct field_def *field, const char *a, const char *b)
{
return !strcmp(a, b);
}
static const char *enum_num_to_string(struct field_def *field, int value)
{
static char buffer[1 + 10 + 2];
int i;
for (i = 0; i < field->u.en.map_size; i++) {
if (value == field->u.en.map[i].value)
return field->u.en.map[i].name;
}
i = snprintf(buffer, sizeof(buffer), "%d", value);
assert(i < sizeof(buffer));
return buffer;
}
static const char *get_enum_num(struct context_def *ctx, struct field_def *field, struct nlattr *nla)
{
int n;
assert(type_of_field(ctx, field) == NLA_U32);
n = nla_get_u32(nla);
return enum_num_to_string(field, n);
}
static bool put_enum_num(struct context_def *ctx, struct field_def *field,
struct msg_buff *msg, const char *value)
{
int n;
n = enum_num_to_int(field->u.en.map, field->u.en.map_size, value, NULL);
if (n == -1)
return false;
assert(type_of_field(ctx, field) == NLA_U32);
nla_put_u32(msg, field->nla_type, n);
return true;
}
static int enum_num_usage(struct field_def *field, char *str, int size)
{
const struct en_map *map = field->u.en.map;
char sep = '{';
int i, len = 0, l;
l = snprintf(str, size, "[--%s=", field->name);
len += l; size -= l;
for (i = 0; i < field->u.en.map_size; i++) {
l = snprintf(str + len, size, "%c%s", sep, map[i].name);
len += l; size -= l;
sep = '|';
}
assert (sep != '{');
l = snprintf(str + len, size, "}|(%d ... %d)]",
field->u.en.min, field->u.en.max);
len += l;
/* size -= l; */
return len;
}
static void enum_num_describe_xml(struct field_def *field)
{
const struct en_map *map = field->u.en.map;
int i;
printf("\t<option name=\"%s\" type=\"numeric-or-symbol\">\n"
"\t\t<min>%d</min>\n"
"\t\t<max>%d</max>\n"
"\t\t<default>%s</default>\n",
field->name,
field->u.en.min,
field->u.en.max,
enum_num_to_string(field, field->u.en.def));
for (i = 0; i < field->u.en.map_size; i++)
printf("\t\t<symbol>%s</symbol>\n", map[i].name);
printf("\t</option>\n");
}
static enum check_codes enum_num_check(struct field_def *field, const char *value)
{
enum new_strtoll_errs e = 777;
int n;
n = enum_num_to_int(field->u.en.map, field->u.en.map_size, value, &e);
if (n == -1 && e != MSE_OK)
return CC_NOT_AN_ENUM_NUM;
/* n positive, but e not touched -> it was a symbolic value, no range check */
if (e == 777)
return CC_OK;
if (n < field->u.en.min)
return CC_TOO_SMALL;
if (n > field->u.en.max)
return CC_TOO_BIG;
return CC_OK;
}
struct field_class fc_enum_num = {
.is_default = enum_num_is_default,
.is_equal = enum_num_is_equal,
.get = get_enum_num,
.put = put_enum_num,
.usage = enum_num_usage,
.describe_xml = enum_num_describe_xml,
.check = enum_num_check,
};
/* ---------------------------------------------------------------------------------------------- */
static int boolean_string_to_int(const char *value)
{
if (!value || !strcmp(value, "yes"))
return 1;
else if (!strcmp(value, "no"))
return 0;
else
return -1;
}
static bool boolean_is_default(struct field_def *field, const char *value)
{
int yesno;
yesno = boolean_string_to_int(value);
return yesno == field->u.b.def;
}
static bool boolean_is_equal(struct field_def *field, const char *a, const char *b)
{
return boolean_string_to_int(a) == boolean_string_to_int(b);
}
static const char *get_boolean(struct context_def *ctx, struct field_def *field, struct nlattr *nla)
{
int i;
assert(type_of_field(ctx, field) == NLA_U8);
i = nla_get_u8(nla);
return i ? "yes" : "no";
}
static bool put_boolean(struct context_def *ctx, struct field_def *field,
struct msg_buff *msg, const char *value)
{
int yesno;
yesno = boolean_string_to_int(value);
if (yesno == -1)
return false;
assert(type_of_field(ctx, field) == NLA_U8);
nla_put_u8(msg, field->nla_type, yesno);
return true;
}
static bool put_flag(struct context_def *ctx, struct field_def *field,
struct msg_buff *msg, const char *value)
{
int yesno;
yesno = boolean_string_to_int(value);
if (yesno == -1)
return false;
assert(type_of_field(ctx, field) == NLA_U8);
if (yesno)
nla_put_u8(msg, field->nla_type, yesno);
return true;
}
static int boolean_usage(struct field_def *field, char *str, int size)
{
return snprintf(str, size,"[--%s={yes|no}]",
field->name);
}
static void boolean_describe_xml(struct field_def *field)
{
printf("\t<option name=\"%s\" type=\"boolean\">\n"
"\t\t<default>%s</default>\n"
"\t</option>\n",
field->name,
field->u.b.def ? "yes" : "no");
}
static enum check_codes boolean_check(struct field_def *field, const char *value)
{
int yesno = boolean_string_to_int(value);
return yesno == -1 ? CC_NOT_A_BOOL : CC_OK;
}
struct field_class fc_boolean = {
.is_default = boolean_is_default,
.is_equal = boolean_is_equal,
.get = get_boolean,
.put = put_boolean,
.usage = boolean_usage,
.describe_xml = boolean_describe_xml,
.check = boolean_check,
};
struct field_class fc_flag = {
.is_default = boolean_is_default,
.is_equal = boolean_is_equal,
.get = get_boolean,
.put = put_flag,
.usage = boolean_usage,
.describe_xml = boolean_describe_xml,
.check = boolean_check,
};
/* ---------------------------------------------------------------------------------------------- */
static bool string_is_default(struct field_def *field, const char *value)
{
return value && !strcmp(value, "");
}
static bool string_is_equal(struct field_def *field, const char *a, const char *b)
{
return !strcmp(a, b);
}
static const char *get_string(struct context_def *ctx, struct field_def *field, struct nlattr *nla)
{
char *str;
int len;
assert(type_of_field(ctx, field) == NLA_NUL_STRING);
str = (char *)nla_data(nla);
len = len_of_field(ctx, field);
assert(memchr(str, 0, len + 1) != NULL);
return str;
}
static bool put_string(struct context_def *ctx, struct field_def *field,
struct msg_buff *msg, const char *value)
{
assert(type_of_field(ctx, field) == NLA_NUL_STRING);
nla_put_string(msg, field->nla_type, value);
return true;
}
static int string_usage(struct field_def *field, char *str, int size)
{
return snprintf(str, size,"[--%s=<str>]",
field->name);
}
static void string_describe_xml(struct field_def *field)
{
printf("\t<option name=\"%s\" type=\"string\">\n"
"\t</option>\n",
field->name);
}
static enum check_codes string_check(struct field_def *field, const char *value)
{
if (field->u.s.max_len) {
if (strlen(value) >= field->u.s.max_len)
return CC_STR_TOO_LONG;
}
return CC_OK;
}
const char *double_quote_string(const char *str)
{
static char *buffer;
const char *s;
char *b;
int len = 0;
for (s = str; *s; s++) {
if (*s == '\\' || *s == '"')
len++;
len++;
}
b = realloc(buffer, len + 3);
if (!b)
return NULL;
buffer = b;
*b++ = '"';
for (s = str; *s; s++) {
if (*s == '\\' || *s == '"')
*b++ = '\\';
*b++ = *s;
}
*b++ = '"';
*b++ = 0;
return buffer;
}
struct field_class fc_string = {
.is_default = string_is_default,
.is_equal = string_is_equal,
.get = get_string,
.put = put_string,
.usage = string_usage,
.describe_xml = string_describe_xml,
.check = string_check,
};
/* ============================================================================================== */
#define ENUM(f, d) \
.nla_type = T_ ## f, \
.ops = &fc_enum, \
.u = { .e = { \
.map = f ## _map, \
.size = ARRAY_SIZE(f ## _map), \
.def = DRBD_ ## d ## _DEF } }
#define ENUM_NOCASE(f, d) \
.nla_type = T_ ## f, \
.ops = &fc_enum_nocase, \
.u = { .e = { \
.map = f ## _map, \
.size = ARRAY_SIZE(f ## _map), \
.def = DRBD_ ## d ## _DEF } }
#define NUMERIC(f, d) \
.nla_type = T_ ## f, \
.ops = &fc_numeric, \
.u = { .n = { \
.min = DRBD_ ## d ## _MIN, \
.max = DRBD_ ## d ## _MAX, \
.def = DRBD_ ## d ## _DEF, \
.is_signed = F_ ## f ## _IS_SIGNED, \
.scale = DRBD_ ## d ## _SCALE } }
#define BOOLEAN(f, d) \
.nla_type = T_ ## f, \
.ops = &fc_boolean, \
.u = { .b = { \
.def = DRBD_ ## d ## _DEF } }, \
.argument_is_optional = true
#define FLAG(f) \
.nla_type = T_ ## f, \
.ops = &fc_flag, \
.u = { .b = { \
.def = false } }, \
.argument_is_optional = true
#define STRING(f) \
.nla_type = T_ ## f, \
.ops = &fc_string, \
.needs_double_quoting = true
#define STRING_MAX_LEN(f, l) \
STRING(f), \
.u = { .s = { .max_len = l } } }
#define ENUM_NUM(f, d, num_min, num_max) \
.nla_type = T_ ## f, \
.ops = &fc_enum_num, \
.u = { .en = { \
.map = f ## _map, \
.map_size = ARRAY_SIZE(f ## _map), \
.min = num_min, \
.max = num_max, \
.def = DRBD_ ## d ## _DEF, } } \
/* ============================================================================================== */
const char *wire_protocol_map[] = {
[DRBD_PROT_A] = "A",
[DRBD_PROT_B] = "B",
[DRBD_PROT_C] = "C",
};
const char *on_io_error_map[] = {
[EP_PASS_ON] = "pass_on",
[EP_CALL_HELPER] = "call-local-io-error",
[EP_DETACH] = "detach",
};
const char *fencing_policy_map[] = {
[FP_DONT_CARE] = "dont-care",
[FP_RESOURCE] = "resource-only",
[FP_STONITH] = "resource-and-stonith",
};
const char *after_sb_0p_map[] = {
[ASB_DISCONNECT] = "disconnect",
[ASB_DISCARD_YOUNGER_PRI] = "discard-younger-primary",
[ASB_DISCARD_OLDER_PRI] = "discard-older-primary",
[ASB_DISCARD_ZERO_CHG] = "discard-zero-changes",
[ASB_DISCARD_LEAST_CHG] = "discard-least-changes",
[ASB_DISCARD_LOCAL] = "discard-local",
[ASB_DISCARD_REMOTE] = "discard-remote",
};
const char *after_sb_1p_map[] = {
[ASB_DISCONNECT] = "disconnect",
[ASB_CONSENSUS] = "consensus",
[ASB_VIOLENTLY] = "violently-as0p",
[ASB_DISCARD_SECONDARY] = "discard-secondary",
[ASB_CALL_HELPER] = "call-pri-lost-after-sb",
};
const char *after_sb_2p_map[] = {
[ASB_DISCONNECT] = "disconnect",
[ASB_VIOLENTLY] = "violently-as0p",
[ASB_CALL_HELPER] = "call-pri-lost-after-sb",
};
const char *rr_conflict_map[] = {
[ASB_DISCONNECT] = "disconnect",
[ASB_VIOLENTLY] = "violently",
[ASB_CALL_HELPER] = "call-pri-lost",
};
const char *on_no_data_map[] = {
[OND_IO_ERROR] = "io-error",
[OND_SUSPEND_IO] = "suspend-io",
};
#define on_no_quorum_map on_no_data_map
/* ONQ_XX == OND_XX */
const char *on_congestion_map[] = {
[OC_BLOCK] = "block",
[OC_PULL_AHEAD] = "pull-ahead",
[OC_DISCONNECT] = "disconnect",
};
const char *read_balancing_map[] = {
[RB_PREFER_LOCAL] = "prefer-local",
[RB_PREFER_REMOTE] = "prefer-remote",
[RB_ROUND_ROBIN] = "round-robin",
[RB_LEAST_PENDING] = "least-pending",
[RB_CONGESTED_REMOTE] = "when-congested-remote",
[RB_32K_STRIPING] = "32K-striping",
[RB_64K_STRIPING] = "64K-striping",
[RB_128K_STRIPING] = "128K-striping",
[RB_256K_STRIPING] = "256K-striping",
[RB_512K_STRIPING] = "512K-striping",
[RB_1M_STRIPING] = "1M-striping"
};
const struct en_map quorum_map[] = {
{ "off", QOU_OFF },
{ "majority", QOU_MAJORITY },
{ "all", QOU_ALL },
};
#define CHANGEABLE_DISK_OPTIONS \
{ "on-io-error", ENUM(on_io_error, ON_IO_ERROR) }, \
/*{ "fencing", ENUM(fencing_policy, FENCING) },*/ \
{ "disk-barrier", BOOLEAN(disk_barrier, DISK_BARRIER) }, \
{ "disk-flushes", BOOLEAN(disk_flushes, DISK_FLUSHES) }, \
{ "disk-drain", BOOLEAN(disk_drain, DISK_DRAIN) }, \
{ "md-flushes", BOOLEAN(md_flushes, MD_FLUSHES) }, \
{ "resync-after", NUMERIC(resync_after, MINOR_NUMBER), .checked_in_postparse = true}, \
{ "al-extents", NUMERIC(al_extents, AL_EXTENTS), .implicit_clamp = true, }, \
{ "al-updates", BOOLEAN(al_updates, AL_UPDATES) }, \
{ "discard-zeroes-if-aligned", \
BOOLEAN(discard_zeroes_if_aligned, DISCARD_ZEROES_IF_ALIGNED) }, \
{ "disable-write-same", \
BOOLEAN(disable_write_same, DISABLE_WRITE_SAME) }, \
{ "disk-timeout", NUMERIC(disk_timeout, DISK_TIMEOUT), \
.unit = "1/10 seconds" }, \
{ "read-balancing", ENUM(read_balancing, READ_BALANCING) }, \
{ "rs-discard-granularity", \
NUMERIC(rs_discard_granularity, RS_DISCARD_GRANULARITY), \
.unit = "bytes" }
#define CHANGEABLE_NET_OPTIONS \
{ "protocol", ENUM_NOCASE(wire_protocol, PROTOCOL) }, \
{ "timeout", NUMERIC(timeout, TIMEOUT), \
.unit = "1/10 seconds" }, \
{ "max-epoch-size", NUMERIC(max_epoch_size, MAX_EPOCH_SIZE) }, \
{ "connect-int", NUMERIC(connect_int, CONNECT_INT), \
.unit = "seconds" }, \
{ "ping-int", NUMERIC(ping_int, PING_INT), \
.unit = "seconds" }, \
{ "sndbuf-size", NUMERIC(sndbuf_size, SNDBUF_SIZE), \
.unit = "bytes" }, \
{ "rcvbuf-size", NUMERIC(rcvbuf_size, RCVBUF_SIZE), \
.unit = "bytes" }, \
{ "ko-count", NUMERIC(ko_count, KO_COUNT) }, \
{ "allow-two-primaries", BOOLEAN(two_primaries, ALLOW_TWO_PRIMARIES) }, \
{ "cram-hmac-alg", STRING(cram_hmac_alg) }, \
{ "shared-secret", STRING_MAX_LEN(shared_secret, SHARED_SECRET_MAX), \
{ "after-sb-0pri", ENUM(after_sb_0p, AFTER_SB_0P) }, \
{ "after-sb-1pri", ENUM(after_sb_1p, AFTER_SB_1P) }, \
{ "after-sb-2pri", ENUM(after_sb_2p, AFTER_SB_2P) }, \
{ "always-asbp", BOOLEAN(always_asbp, ALWAYS_ASBP) }, \
{ "rr-conflict", ENUM(rr_conflict, RR_CONFLICT) }, \
{ "ping-timeout", NUMERIC(ping_timeo, PING_TIMEO), \
.unit = "1/10 seconds" }, \
{ "data-integrity-alg", STRING(integrity_alg) }, \
{ "tcp-cork", BOOLEAN(tcp_cork, TCP_CORK) }, \
{ "on-congestion", ENUM(on_congestion, ON_CONGESTION) }, \
{ "congestion-fill", NUMERIC(cong_fill, CONG_FILL), \
.unit = "bytes" }, \
{ "congestion-extents", NUMERIC(cong_extents, CONG_EXTENTS) }, \
{ "csums-alg", STRING(csums_alg) }, \
{ "csums-after-crash-only", BOOLEAN(csums_after_crash_only, \
CSUMS_AFTER_CRASH_ONLY) }, \
{ "verify-alg", STRING(verify_alg) }, \
{ "use-rle", BOOLEAN(use_rle, USE_RLE) }, \
{ "socket-check-timeout", NUMERIC(sock_check_timeo, SOCKET_CHECK_TIMEO) }, \
{ "fencing", ENUM(fencing_policy, FENCING) }, \
{ "max-buffers", NUMERIC(max_buffers, MAX_BUFFERS) }, \
{ "_name", STRING(name) }
struct context_def disk_options_ctx = {
NLA_POLICY(disk_conf),
.nla_type = DRBD_NLA_DISK_CONF,
.fields = {
CHANGEABLE_DISK_OPTIONS,
{ } },
};
struct context_def net_options_ctx = {
NLA_POLICY(net_conf),
.nla_type = DRBD_NLA_NET_CONF,
.fields = {
CHANGEABLE_NET_OPTIONS,
{ } },
};
struct context_def primary_cmd_ctx = {
NLA_POLICY(set_role_parms),
.nla_type = DRBD_NLA_SET_ROLE_PARMS,
.fields = {
{ "force", FLAG(assume_uptodate) },
{ } },
};
struct context_def attach_cmd_ctx = {
NLA_POLICY(disk_conf),
.nla_type = DRBD_NLA_DISK_CONF,
.fields = {
{ "size", NUMERIC(disk_size, DISK_SIZE),
.unit = "bytes" },
CHANGEABLE_DISK_OPTIONS,
/* { "*", STRING(backing_dev) }, */
/* { "*", STRING(meta_dev) }, */
/* { "*", NUMERIC(meta_dev_idx, MINOR_NUMBER) }, */
{ } },
};
struct context_def detach_cmd_ctx = {
NLA_POLICY(detach_parms),
.nla_type = DRBD_NLA_DETACH_PARMS,
.fields = {
{ "force", FLAG(force_detach) },
{ }
},
};
struct context_def new_peer_cmd_ctx = {
NLA_POLICY(net_conf),
.nla_type = DRBD_NLA_NET_CONF,
.fields = {
{ "transport", STRING(transport_name) },
CHANGEABLE_NET_OPTIONS,
{ } },
};
struct context_def path_cmd_ctx = {
NLA_POLICY(path_parms),
.nla_type = DRBD_NLA_PATH_PARMS,
.fields = { { } },
};
#define CONNECT_CMD_OPTIONS \
{ "tentative", FLAG(tentative) }, \
{ "discard-my-data", FLAG(discard_my_data) }
struct context_def connect_cmd_ctx = {
NLA_POLICY(connect_parms),
.nla_type = DRBD_NLA_CONNECT_PARMS,
.fields = {
CONNECT_CMD_OPTIONS,
{ } },
};
struct context_def show_net_options_ctx = {
NLA_POLICY(net_conf),
.nla_type = DRBD_NLA_NET_CONF,
.fields = {
{ "transport", STRING(transport_name) },
CHANGEABLE_NET_OPTIONS,
{ } },
};
struct context_def disconnect_cmd_ctx = {
NLA_POLICY(disconnect_parms),
.nla_type = DRBD_NLA_DISCONNECT_PARMS,
.fields = {
{ "force", FLAG(force_disconnect) },
{ } },
};
struct context_def resize_cmd_ctx = {
NLA_POLICY(resize_parms),
.nla_type = DRBD_NLA_RESIZE_PARMS,
.fields = {
{ "size", NUMERIC(resize_size, DISK_SIZE),
.unit = "bytes" },
{ "assume-peer-has-space", FLAG(resize_force) },
{ "assume-clean", FLAG(no_resync) },
{ "al-stripes", NUMERIC(al_stripes, AL_STRIPES) },
{ "al-stripe-size-kB", NUMERIC(al_stripe_size, AL_STRIPE_SIZE) },
{ } },
};
struct context_def resource_options_ctx = {
NLA_POLICY(res_opts),
.nla_type = DRBD_NLA_RESOURCE_OPTS,
.fields = {
{ "cpu-mask", STRING(cpu_mask) },
{ "on-no-data-accessible", ENUM(on_no_data, ON_NO_DATA) },
{ "auto-promote", BOOLEAN(auto_promote, AUTO_PROMOTE) },
{ "peer-ack-window", NUMERIC(peer_ack_window, PEER_ACK_WINDOW), .unit = "bytes" },
{ "peer-ack-delay", NUMERIC(peer_ack_delay, PEER_ACK_DELAY),
.unit = "milliseconds" },
{ "twopc-timeout", NUMERIC(twopc_timeout, TWOPC_TIMEOUT), .unit = "1/10 seconds" },
{ "twopc-retry-timeout", NUMERIC(twopc_retry_timeout, TWOPC_RETRY_TIMEOUT),
.unit = "1/10 seconds" },
{ "auto-promote-timeout", NUMERIC(auto_promote_timeout, AUTO_PROMOTE_TIMEOUT),
.unit = "1/10 seconds"},
{ "max-io-depth", NUMERIC(nr_requests, NR_REQUESTS) },
{ "quorum", ENUM_NUM(quorum, QUORUM, 1, DRBD_PEERS_MAX) },
{ "on-no-quorum", ENUM(on_no_quorum, ON_NO_QUORUM) },
{ } },
};
struct context_def new_current_uuid_cmd_ctx = {
NLA_POLICY(new_c_uuid_parms),
.nla_type = DRBD_NLA_NEW_C_UUID_PARMS,
.fields = {
{ "clear-bitmap", FLAG(clear_bm) },
{ } },
};
struct context_def verify_cmd_ctx = {
NLA_POLICY(start_ov_parms),
.nla_type = DRBD_NLA_START_OV_PARMS,
.fields = {
{ "start", NUMERIC(ov_start_sector, DISK_SIZE),
.unit = "bytes" },
{ "stop", NUMERIC(ov_stop_sector, DISK_SIZE),
.unit = "bytes" },
{ } },
};
struct context_def device_options_ctx = {
NLA_POLICY(device_conf),
.nla_type = DRBD_NLA_DEVICE_CONF,
.fields = {
{ "max-bio-size", NUMERIC(max_bio_size, MAX_BIO_SIZE) },
{ "diskless", FLAG(intentional_diskless) },
{ } },
};
struct context_def invalidate_ctx = {
NLA_POLICY(invalidate_parms),
.nla_type = DRBD_NLA_INVALIDATE_PARMS,
.fields = {
{ "sync-from-peer-node-id", NUMERIC(sync_from_peer_node_id, SYNC_FROM_NID) },
{ } },
};
struct context_def peer_device_options_ctx = {
NLA_POLICY(peer_device_conf),
.nla_type = DRBD_NLA_PEER_DEVICE_OPTS,
.fields = {
{ "resync-rate", NUMERIC(resync_rate, RESYNC_RATE), .unit = "bytes/second" },
{ "c-plan-ahead", NUMERIC(c_plan_ahead, C_PLAN_AHEAD), .unit = "1/10 seconds" },
{ "c-delay-target", NUMERIC(c_delay_target, C_DELAY_TARGET), .unit = "1/10 seconds" },
{ "c-fill-target", NUMERIC(c_fill_target, C_FILL_TARGET), .unit = "bytes" },
{ "c-max-rate", NUMERIC(c_max_rate, C_MAX_RATE), .unit = "bytes/second" },
{ "c-min-rate", NUMERIC(c_min_rate, C_MIN_RATE), .unit = "bytes/second" },
{ "bitmap", BOOLEAN(bitmap, BITMAP) },
{ } },
};
// only used in drbdadm:
struct context_def create_md_ctx = {
.fields = {
{ .name = "max-peers", .argument_is_optional = false },
{ .name = "peer-max-bio-size", .argument_is_optional = false },
{ .name = "al-stripes", .argument_is_optional = false },
{ .name = "al-stripe-size-kB", .argument_is_optional = false },
{ .name = "force", .argument_is_optional = true },
{ } },
};
struct context_def adjust_ctx = {
.fields = {
{ "skip-disk", .argument_is_optional = true },
{ "skip-net", .argument_is_optional = true },
CONNECT_CMD_OPTIONS,
{ } },
};
// only used by drbdadm's config file parser:
struct context_def handlers_ctx = {
.fields = {
{ "pri-on-incon-degr", .ops = &fc_string, .needs_double_quoting = true},
{ "pri-lost-after-sb", .ops = &fc_string, .needs_double_quoting = true},
{ "pri-lost", .ops = &fc_string, .needs_double_quoting = true},
{ "initial-split-brain", .ops = &fc_string, .needs_double_quoting = true},
{ "split-brain", .ops = &fc_string, .needs_double_quoting = true},
{ "outdate-peer", .ops = &fc_string, .needs_double_quoting = true},
{ "fence-peer", .ops = &fc_string, .needs_double_quoting = true},
{ "unfence-peer", .ops = &fc_string, .needs_double_quoting = true},
{ "local-io-error", .ops = &fc_string, .needs_double_quoting = true},
{ "before-resync-target", .ops = &fc_string, .needs_double_quoting = true},
{ "after-resync-target", .ops = &fc_string, .needs_double_quoting = true},
{ "before-resync-source", .ops = &fc_string, .needs_double_quoting = true},
{ "out-of-sync", .ops = &fc_string, .needs_double_quoting = true},
{ "quorum-lost", .ops = &fc_string, .needs_double_quoting = true},
{ } },
};
struct context_def proxy_options_ctx = {
.fields = {
{ "memlimit", .ops = &fc_numeric, .u={.n={.min = 0, .max=-1}}},
{ "read-loops", .ops = &fc_numeric, .u={.n={.min = 0, .max=-1}}},
{ "compression", .ops = &fc_numeric, .u={.n={.min = 0, .max=-1}}},
{ "bwlimit", .ops = &fc_numeric, .u={.n={.min = 0, .max=-1}}},
{ "sndbuf-size", NUMERIC(sndbuf_size, SNDBUF_SIZE), .unit = "bytes" },
{ "rcvbuf-size", NUMERIC(rcvbuf_size, RCVBUF_SIZE), .unit = "bytes" },
{ "ping-timeout", NUMERIC(ping_timeo, PING_TIMEO), .unit = "1/10 seconds" },
{ } },
};
#define ADM_NUMERIC(d) \
.ops = &fc_numeric, \
.u = { .n = { \
.min = DRBD_ ## d ## _MIN, \
.max = DRBD_ ## d ## _MAX, \
.def = DRBD_ ## d ## _DEF, \
.is_signed = false, \
.scale = DRBD_ ## d ## _SCALE } }
struct context_def startup_options_ctx = {
.fields = {
{ "wfc-timeout", ADM_NUMERIC(WFC_TIMEOUT) },
{ "degr-wfc-timeout", ADM_NUMERIC(DEGR_WFC_TIMEOUT) },
{ "outdated-wfc-timeout", ADM_NUMERIC(OUTDATED_WFC_TIMEOUT) },
{ "wait-after-sb", .ops = &fc_boolean },
{ } },
};
struct context_def wildcard_ctx = {
};