blob: 1827f2a444033fdc72d99b2d9e0a99b0bb404af9 [file] [log] [blame]
#define _GNU_SOURCE
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/netlink.h>
#include <linux/genetlink.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.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 <linux/genl_magic_func.h>
#include "drbdtool_common.h"
#include "config_flags.h"
#ifndef AF_INET_SDP
#define AF_INET_SDP 27
#define PF_INET_SDP AF_INET_SDP
#endif
#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)
/* ============================================================================================== */
static int enum_string_to_int(const char **map, int size, const char *value,
int (*strcmp)(const char *, const char *))
{
int n;
if (!value)
return -1;
for (n = 0; n < size; n++) {
if (map[n] && !strcmp(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 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 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 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);
}
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;
}
/* ---------------------------------------------------------------------------------------------- */
static bool address_is_default(struct field_def *field, const char *value)
{
return true;
}
static bool address_is_equal(struct field_def *field, const char *a, const char *b)
{
return !strcmp(a, b);
}
/* It will only print the WARNING if the warn flag is set
with the _first_ call! */
#define PROC_NET_AF_SCI_FAMILY "/proc/net/af_sci/family"
#define PROC_NET_AF_SSOCKS_FAMILY "/proc/net/af_ssocks/family"
int get_af_ssocks(int warn_and_use_default)
{
char buf[16];
int c, fd;
static int af = -1;
if (af > 0)
return af;
fd = open(PROC_NET_AF_SSOCKS_FAMILY, O_RDONLY);
if (fd < 0)
fd = open(PROC_NET_AF_SCI_FAMILY, O_RDONLY);
if (fd < 0) {
if (warn_and_use_default) {
fprintf(stderr, "open(" PROC_NET_AF_SSOCKS_FAMILY ") "
"failed: %m\n WARNING: assuming AF_SSOCKS = 27. "
"Socket creation may fail.\n");
af = 27;
}
return af;
}
c = read(fd, buf, sizeof(buf)-1);
if (c > 0) {
buf[c] = 0;
if (buf[c-1] == '\n')
buf[c-1] = 0;
af = m_strtoll(buf,1);
} else {
if (warn_and_use_default) {
fprintf(stderr, "read(" PROC_NET_AF_SSOCKS_FAMILY ") "
"failed: %m\n WARNING: assuming AF_SSOCKS = 27. "
"Socket creation may fail.\n");
af = 27;
}
}
close(fd);
return af;
}
static char *af_to_str(int af)
{
if (af == AF_INET)
return "ipv4";
else if (af == AF_INET6)
return "ipv6";
/* AF_SSOCKS typically is 27, the same as AF_INET_SDP.
* But with warn_and_use_default = 0, it will stay at -1 if not available.
* Just keep the test on ssocks before the one on SDP (which is hard-coded),
* and all should be fine. */
else if (af == get_af_ssocks(0))
return "ssocks";
else if (af == AF_INET_SDP)
return "sdp";
else return "unknown";
}
void sprint_address(char *buffer, void *address, int addr_len)
{
union {
struct sockaddr addr;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
} a;
memset(&a, 0, sizeof(a));
memcpy(&a.addr, address, addr_len);
if (a.addr.sa_family == AF_INET
|| a.addr.sa_family == get_af_ssocks(0)
|| a.addr.sa_family == AF_INET_SDP) {
sprintf(buffer, "%s %s:%d",
af_to_str(a.addr4.sin_family),
inet_ntoa(a.addr4.sin_addr),
ntohs(a.addr4.sin_port));
} else if (a.addr.sa_family == AF_INET6) {
char buf2[ADDRESS_STR_MAX];
int n;
buf2[0] = 0;
getnameinfo(&a.addr, addr_len, buf2, sizeof(buf2),
NULL, 0, NI_NUMERICHOST|NI_NUMERICSERV);
n = snprintf(buffer, ADDRESS_STR_MAX, "%s [%s]:%d",
af_to_str(a.addr6.sin6_family), buf2,
ntohs(a.addr6.sin6_port));
assert(n > 0);
assert(n < ADDRESS_STR_MAX); /* there should be no need to truncate */
} else {
sprintf(buffer, "[unknown af=%d, len=%d]", a.addr.sa_family, addr_len);
}
}
static const char *get_address(struct context_def *ctx, struct field_def *field, struct nlattr *nla)
{
static char buffer[ADDRESS_STR_MAX];
sprint_address(buffer, nla_data(nla), nla_len(nla));
return buffer;
}
static void resolv6(const char *name, struct sockaddr_in6 *addr)
{
struct addrinfo hints, *res, *tmp;
int err;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
err = getaddrinfo(name, 0, &hints, &res);
if (err) {
fprintf(stderr, "getaddrinfo %s: %s\n", name, gai_strerror(err));
exit(20);
}
/* Yes, it is a list. We use only the first result. The loop is only
* there to document that we know it is a list */
for (tmp = res; tmp; tmp = tmp->ai_next) {
memcpy(addr, tmp->ai_addr, sizeof(*addr));
break;
}
freeaddrinfo(res);
if (0) { /* debug output */
char ip[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip));
fprintf(stderr, "%s -> %02x %04x %08x %s %08x\n",
name,
addr->sin6_family,
addr->sin6_port,
addr->sin6_flowinfo,
ip,
addr->sin6_scope_id);
}
}
static unsigned long resolv(const char* name)
{
unsigned long retval;
if((retval = inet_addr(name)) == INADDR_NONE ) {
struct hostent *he;
he = gethostbyname(name);
if (!he) {
fprintf(stderr, "can not resolve the hostname: gethostbyname(%s): %s\n",
name, hstrerror(h_errno));
exit(20);
}
retval = ((struct in_addr *)(he->h_addr_list[0]))->s_addr;
}
return retval;
}
static void split_ipv6_addr(char **address, int *port)
{
/* ipv6:[fe80::0234:5678:9abc:def1]:8000; */
char *b = strrchr(*address,']');
if (address[0][0] != '[' || b == NULL ||
(b[1] != ':' && b[1] != '\0')) {
fprintf(stderr, "unexpected ipv6 format: %s\n",
*address);
exit(20);
}
*b = 0;
*address += 1; /* skip '[' */
if (b[1] == ':')
*port = m_strtoll(b+2,1); /* b+2: "]:" */
else
*port = 7788; /* will we ever get rid of that default port? */
}
static void split_address(int *af, char** address, int* port)
{
static struct { char* text; int af; } afs[] = {
{ "ipv4:", AF_INET },
{ "ipv6:", AF_INET6 },
{ "sdp:", AF_INET_SDP },
{ "ssocks:", -1 },
};
unsigned int i;
char *b;
*af=AF_INET;
for (i=0; i<ARRAY_SIZE(afs); i++) {
if (!strncmp(*address, afs[i].text, strlen(afs[i].text))) {
*af = afs[i].af;
*address += strlen(afs[i].text);
break;
}
}
if (*af == AF_INET6 && address[0][0] == '[')
return split_ipv6_addr(address, port);
if (*af == -1)
*af = get_af_ssocks(1);
b=strrchr(*address,':');
if (b) {
*b = 0;
if (*af == AF_INET6) {
/* compatibility handling of ipv6 addresses,
* in the style expected before drbd 8.3.9.
* may go wrong without explicit port */
fprintf(stderr, "interpreting ipv6:%s:%s as ipv6:[%s]:%s\n",
*address, b+1, *address, b+1);
}
*port = m_strtoll(b+1,1);
} else
*port = 7788;
}
int nla_put_address(struct msg_buff *msg, int attrtype, const char *arg)
{
int af, port;
char *address = strdupa(arg);
split_address(&af, &address, &port);
if (af == AF_INET6) {
struct sockaddr_in6 addr6;
memset(&addr6, 0, sizeof(addr6));
resolv6(address, &addr6);
addr6.sin6_port = htons(port);
/* addr6.sin6_len = sizeof(addr6); */
nla_put(msg, attrtype, sizeof(addr6), &addr6);
} else {
/* AF_INET, AF_SDP, AF_SSOCKS,
* all use the IPv4 addressing scheme */
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_port = htons(port);
addr.sin_family = af;
addr.sin_addr.s_addr = resolv(address);
nla_put(msg, attrtype, sizeof(addr), &addr);
}
return NO_ERROR;
}
static bool put_address(struct context_def *ctx, struct field_def *field,
struct msg_buff *msg, const char *value)
{
assert(type_of_field(ctx, field) == NLA_BINARY);
nla_put_address(msg, field->nla_type, value);
return true;
}
static int address_usage(struct field_def *field, char *str, int size)
{
return snprintf(str, size,"[--%s=[{af}:]{local_addr}[:{port}]]",
field->name);
}
static void address_describe_xml(struct field_def *field)
{
printf("\t<option name=\"%s\" type=\"address\">\n"
"\t</option>\n",
field->name);
}
/* ============================================================================================== */
#define ENUM(f, d) \
.nla_type = T_ ## f, \
.is_default = enum_is_default, \
.is_equal = enum_is_equal, \
.get = get_enum, \
.put = put_enum, \
.usage = enum_usage, \
.describe_xml = enum_describe_xml, \
.u = { .e = { \
.map = f ## _map, \
.size = ARRAY_SIZE(f ## _map), \
.def = DRBD_ ## d ## _DEF } }
#define ENUM_NOCASE(f, d) \
.nla_type = T_ ## f, \
.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, \
.u = { .e = { \
.map = f ## _map, \
.size = ARRAY_SIZE(f ## _map), \
.def = DRBD_ ## d ## _DEF } }
#define NUMERIC(f, d) \
.nla_type = T_ ## f, \
.is_default = numeric_is_default, \
.is_equal = numeric_is_equal, \
.get = get_numeric, \
.put = put_numeric, \
.usage = numeric_usage, \
.describe_xml = numeric_describe_xml, \
.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, \
.is_default = boolean_is_default, \
.is_equal = boolean_is_equal, \
.get = get_boolean, \
.put = put_boolean, \
.usage = boolean_usage, \
.describe_xml = boolean_describe_xml, \
.u = { .b = { \
.def = DRBD_ ## d ## _DEF } }, \
.argument_is_optional = true
#define FLAG(f) \
.nla_type = T_ ## f, \
.is_default = boolean_is_default, \
.is_equal = boolean_is_equal, \
.get = get_boolean, \
.put = put_flag, \
.usage = boolean_usage, \
.describe_xml = boolean_describe_xml, \
.u = { .b = { \
.def = false } }, \
.argument_is_optional = true
#define STRING(f) \
.nla_type = T_ ## f, \
.is_default = string_is_default, \
.is_equal = string_is_equal, \
.get = get_string, \
.put = put_string, \
.usage = string_usage, \
.describe_xml = string_describe_xml, \
.needs_double_quoting = true
#define ADDRESS(f) \
.nla_type = T_ ## f, \
.is_default = address_is_default, \
.is_equal = address_is_equal, \
.get = get_address, \
.put = put_address, \
.usage = address_usage, \
.describe_xml = address_describe_xml, \
.needs_double_quoting = false
/* ============================================================================================== */
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_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",
};
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"
};
#define CHANGEABLE_DISK_OPTIONS \
{ "on-io-error", ENUM(on_io_error, ON_IO_ERROR) }, \
{ "fencing", ENUM(fencing, 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-rate", NUMERIC(resync_rate, RESYNC_RATE), \
.unit = "bytes/second" }, \
{ "resync-after", NUMERIC(resync_after, MINOR_NUMBER) }, \
{ "al-extents", NUMERIC(al_extents, AL_EXTENTS) }, \
{ "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) }, \
{ "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" }, \
{ "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) } \
#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) }, \
{ "max-buffers", NUMERIC(max_buffers, MAX_BUFFERS) }, \
{ "unplug-watermark", NUMERIC(unplug_watermark, UNPLUG_WATERMARK) }, \
{ "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(shared_secret) }, \
{ "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) }
struct context_def disk_options_ctx = {
NLA_POLICY(disk_conf),
.fields = {
CHANGEABLE_DISK_OPTIONS,
{ } },
};
struct context_def net_options_ctx = {
NLA_POLICY(net_conf),
.fields = {
CHANGEABLE_NET_OPTIONS,
{ } },
};
struct context_def primary_cmd_ctx = {
NLA_POLICY(set_role_parms),
.fields = {
{ "force", FLAG(assume_uptodate) },
{ } },
};
struct context_def attach_cmd_ctx = {
NLA_POLICY(disk_conf),
.fields = {
{ "size", NUMERIC(disk_size, DISK_SIZE),
.unit = "bytes" },
{ "max-bio-bvecs", NUMERIC(max_bio_bvecs, MAX_BIO_BVECS) },
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),
.fields = {
{ "force", FLAG(force_detach) },
{ }
},
};
struct context_def connect_cmd_ctx = {
NLA_POLICY(net_conf),
.fields = {
{ "tentative", FLAG(tentative) },
{ "discard-my-data", FLAG(discard_my_data) },
{ "alternate-address", ADDRESS(my_addr2) },
{ "alternate-peer-address", ADDRESS(peer_addr2) },
CHANGEABLE_NET_OPTIONS,
{ } },
};
struct context_def disconnect_cmd_ctx = {
NLA_POLICY(disconnect_parms),
.fields = {
{ "force", FLAG(force_disconnect) },
{ } },
};
struct context_def resize_cmd_ctx = {
NLA_POLICY(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_cmd_ctx = {
NLA_POLICY(res_opts),
.fields = {
{ "cpu-mask", STRING(cpu_mask) },
{ "on-no-data-accessible", ENUM(on_no_data, ON_NO_DATA) },
{ } },
};
struct context_def new_current_uuid_cmd_ctx = {
NLA_POLICY(new_c_uuid_parms),
.fields = {
{ "clear-bitmap", FLAG(clear_bm) },
{ } },
};
struct context_def verify_cmd_ctx = {
NLA_POLICY(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 new_minor_cmd_ctx = {
NLA_POLICY(drbd_cfg_context),
.fields = {
/* { "*", STRING(ctx_resource_name) }, */
/* { "*", NUMERIC(ctx_volume, >= 0) }, */
/* { "*", BINARY(ctx_my_addr) }, */
/* { "*", BINARY(ctx_peer_addr) }, */
{ } },
};