mirror of
https://github.com/samba-team/samba.git
synced 2024-12-22 13:34:15 +03:00
7163846a49
RFC5952 says the existing style is not recommended and the [] style should be employed. There are more optimised ways of adding the square brackets but they tend to be uglier. Parsing IPv6 sockets without [] is now tested indirectly by parsing examples in both styles and comparing the results. Signed-off-by: Martin Schwenke <martin@meltin.net> Signed-off-by: Volker Lendecke <vl@samba.org> Autobuild-User(master): Volker Lendecke <vl@samba.org> Autobuild-Date(master): Thu Jan 13 17:02:21 UTC 2022 on sn-devel-184
750 lines
14 KiB
C
750 lines
14 KiB
C
/*
|
|
CTDB protocol marshalling
|
|
|
|
Copyright (C) Amitay Isaacs 2015
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "replace.h"
|
|
#include "system/network.h"
|
|
|
|
#include <talloc.h>
|
|
|
|
#include "common/line.h"
|
|
|
|
#include "protocol.h"
|
|
#include "protocol_util.h"
|
|
#include "lib/util/util.h"
|
|
#include "lib/util/smb_strtox.h"
|
|
|
|
static struct {
|
|
enum ctdb_runstate runstate;
|
|
const char * label;
|
|
} runstate_map[] = {
|
|
{ CTDB_RUNSTATE_UNKNOWN, "UNKNOWN" },
|
|
{ CTDB_RUNSTATE_INIT, "INIT" },
|
|
{ CTDB_RUNSTATE_SETUP, "SETUP" },
|
|
{ CTDB_RUNSTATE_FIRST_RECOVERY, "FIRST_RECOVERY" },
|
|
{ CTDB_RUNSTATE_STARTUP, "STARTUP" },
|
|
{ CTDB_RUNSTATE_RUNNING, "RUNNING" },
|
|
{ CTDB_RUNSTATE_SHUTDOWN, "SHUTDOWN" },
|
|
{ -1, NULL },
|
|
};
|
|
|
|
const char *ctdb_runstate_to_string(enum ctdb_runstate runstate)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; runstate_map[i].label != NULL; i++) {
|
|
if (runstate_map[i].runstate == runstate) {
|
|
return runstate_map[i].label;
|
|
}
|
|
}
|
|
|
|
return runstate_map[0].label;
|
|
}
|
|
|
|
enum ctdb_runstate ctdb_runstate_from_string(const char *runstate_str)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; runstate_map[i].label != NULL; i++) {
|
|
if (strcasecmp(runstate_map[i].label,
|
|
runstate_str) == 0) {
|
|
return runstate_map[i].runstate;
|
|
}
|
|
}
|
|
|
|
return CTDB_RUNSTATE_UNKNOWN;
|
|
}
|
|
|
|
static struct {
|
|
enum ctdb_event event;
|
|
const char *label;
|
|
} event_map[] = {
|
|
{ CTDB_EVENT_INIT, "init" },
|
|
{ CTDB_EVENT_SETUP, "setup" },
|
|
{ CTDB_EVENT_STARTUP, "startup" },
|
|
{ CTDB_EVENT_START_RECOVERY, "startrecovery" },
|
|
{ CTDB_EVENT_RECOVERED, "recovered" },
|
|
{ CTDB_EVENT_TAKE_IP, "takeip" },
|
|
{ CTDB_EVENT_RELEASE_IP, "releaseip" },
|
|
{ CTDB_EVENT_MONITOR, "monitor" },
|
|
{ CTDB_EVENT_SHUTDOWN, "shutdown" },
|
|
{ CTDB_EVENT_UPDATE_IP, "updateip" },
|
|
{ CTDB_EVENT_IPREALLOCATED, "ipreallocated" },
|
|
{ CTDB_EVENT_MAX, "all" },
|
|
{ -1, NULL },
|
|
};
|
|
|
|
const char *ctdb_event_to_string(enum ctdb_event event)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; event_map[i].label != NULL; i++) {
|
|
if (event_map[i].event == event) {
|
|
return event_map[i].label;
|
|
}
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
enum ctdb_event ctdb_event_from_string(const char *event_str)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; event_map[i].label != NULL; i++) {
|
|
if (strcmp(event_map[i].label, event_str) == 0) {
|
|
return event_map[i].event;
|
|
}
|
|
}
|
|
|
|
return CTDB_EVENT_MAX;
|
|
}
|
|
|
|
int ctdb_sock_addr_to_buf(char *buf, socklen_t buflen,
|
|
ctdb_sock_addr *addr, bool with_port)
|
|
{
|
|
const char *t;
|
|
size_t len = 0;
|
|
|
|
switch (addr->sa.sa_family) {
|
|
case AF_INET:
|
|
t = inet_ntop(addr->ip.sin_family, &addr->ip.sin_addr,
|
|
buf, buflen);
|
|
if (t == NULL) {
|
|
return errno;
|
|
}
|
|
if (with_port) {
|
|
len = strlen(buf);
|
|
}
|
|
break;
|
|
|
|
case AF_INET6: {
|
|
char tmp[INET6_ADDRSTRLEN];
|
|
|
|
t = inet_ntop(addr->ip6.sin6_family,
|
|
&addr->ip6.sin6_addr,
|
|
tmp,
|
|
sizeof(tmp));
|
|
if (t == NULL) {
|
|
return errno;
|
|
}
|
|
|
|
if (with_port) {
|
|
int ret = snprintf(buf, buflen, "[%s]", tmp);
|
|
if (ret < 0) {
|
|
return ENOSPC;
|
|
}
|
|
len = (size_t)ret;
|
|
} else {
|
|
len = strlcpy(buf, tmp, buflen);
|
|
}
|
|
if (len >= buflen){
|
|
return ENOSPC;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return EAFNOSUPPORT;
|
|
break;
|
|
}
|
|
|
|
if (with_port) {
|
|
int ret;
|
|
|
|
ret = snprintf(buf+len, buflen-len,
|
|
":%u", ctdb_sock_addr_port(addr));
|
|
if (ret < 0 || (size_t)ret >= buflen-len) {
|
|
return ENOSPC;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *ctdb_sock_addr_to_string(TALLOC_CTX *mem_ctx,
|
|
ctdb_sock_addr *addr,
|
|
bool with_port)
|
|
{
|
|
size_t len = 64;
|
|
char *cip;
|
|
int ret;
|
|
|
|
cip = talloc_size(mem_ctx, len);
|
|
|
|
if (cip == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
ret = ctdb_sock_addr_to_buf(cip, len, addr, with_port);
|
|
if (ret != 0) {
|
|
talloc_free(cip);
|
|
return NULL;
|
|
}
|
|
|
|
return cip;
|
|
}
|
|
|
|
static int ipv4_from_string(const char *str, struct sockaddr_in *ip)
|
|
{
|
|
int ret;
|
|
|
|
*ip = (struct sockaddr_in) {
|
|
.sin_family = AF_INET,
|
|
};
|
|
|
|
ret = inet_pton(AF_INET, str, &ip->sin_addr);
|
|
if (ret != 1) {
|
|
return EINVAL;
|
|
}
|
|
|
|
#ifdef HAVE_SOCK_SIN_LEN
|
|
ip->sin_len = sizeof(*ip);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int ipv6_from_string(const char *str, struct sockaddr_in6 *ip6)
|
|
{
|
|
int ret;
|
|
|
|
*ip6 = (struct sockaddr_in6) {
|
|
.sin6_family = AF_INET6,
|
|
};
|
|
|
|
ret = inet_pton(AF_INET6, str, &ip6->sin6_addr);
|
|
if (ret != 1) {
|
|
return EINVAL;
|
|
}
|
|
|
|
#ifdef HAVE_SOCK_SIN6_LEN
|
|
ip6->sin6_len = sizeof(*ip6);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int ip_from_string(const char *str, ctdb_sock_addr *addr)
|
|
{
|
|
char *p;
|
|
int ret;
|
|
|
|
if (addr == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
ZERO_STRUCTP(addr); /* valgrind :-) */
|
|
|
|
/* IPv4 or IPv6 address?
|
|
*
|
|
* Use strrchr() because we need the right-most ':' below for
|
|
* IPv4-mapped IPv6 addresses anyway...
|
|
*/
|
|
p = strrchr(str, ':');
|
|
if (p == NULL) {
|
|
ret = ipv4_from_string(str, &addr->ip);
|
|
} else {
|
|
static const uint8_t ipv4_mapped_prefix[12] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff
|
|
};
|
|
size_t len = strlen(str);
|
|
char s[64];
|
|
|
|
len = strlcpy(s, str, sizeof(s));
|
|
if (len >= sizeof(s)) {
|
|
return EINVAL;
|
|
}
|
|
|
|
if ((len >= 2) && (s[0] == '[') && (s[len-1] == ']')) {
|
|
s[len-1] = '\0';
|
|
str = s+1;
|
|
p = strrchr(str, ':');
|
|
}
|
|
|
|
ret = ipv6_from_string(str, &addr->ip6);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Check for IPv4-mapped IPv6 address
|
|
* (e.g. ::ffff:192.0.2.128) - reparse as IPv4 if
|
|
* necessary
|
|
*/
|
|
if (memcmp(&addr->ip6.sin6_addr.s6_addr[0],
|
|
ipv4_mapped_prefix,
|
|
sizeof(ipv4_mapped_prefix)) == 0) {
|
|
/* Initialize addr struct to zero before reparsing as IPV4 */
|
|
ZERO_STRUCTP(addr);
|
|
|
|
/* Reparse as IPv4 */
|
|
ret = ipv4_from_string(p+1, &addr->ip);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ctdb_sock_addr_from_string(const char *str,
|
|
ctdb_sock_addr *addr, bool with_port)
|
|
{
|
|
char *p;
|
|
char s[64]; /* Much longer than INET6_ADDRSTRLEN */
|
|
unsigned port;
|
|
size_t len;
|
|
int ret;
|
|
|
|
if (! with_port) {
|
|
ret = ip_from_string(str, addr);
|
|
return ret;
|
|
}
|
|
|
|
/* Parse out port number and then IP address */
|
|
|
|
len = strlcpy(s, str, sizeof(s));
|
|
if (len >= sizeof(s)) {
|
|
return EINVAL;
|
|
}
|
|
|
|
p = strrchr(s, ':');
|
|
if (p == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
port = smb_strtoul(p+1, NULL, 10, &ret, SMB_STR_FULL_STR_CONV);
|
|
if (ret != 0) {
|
|
/* Empty string or trailing garbage */
|
|
return EINVAL;
|
|
}
|
|
|
|
*p = '\0';
|
|
ret = ip_from_string(s, addr);
|
|
|
|
ctdb_sock_addr_set_port(addr, port);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ctdb_sock_addr_mask_from_string(const char *str,
|
|
ctdb_sock_addr *addr,
|
|
unsigned int *mask)
|
|
{
|
|
char *p;
|
|
char s[64]; /* Much longer than INET6_ADDRSTRLEN */
|
|
unsigned int m;
|
|
size_t len;
|
|
int ret = 0;
|
|
|
|
if (addr == NULL || mask == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
len = strlcpy(s, str, sizeof(s));
|
|
if (len >= sizeof(s)) {
|
|
return EINVAL;
|
|
}
|
|
|
|
p = strrchr(s, '/');
|
|
if (p == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
m = smb_strtoul(p+1, NULL, 10, &ret, SMB_STR_FULL_STR_CONV);
|
|
if (ret != 0) {
|
|
/* Empty string or trailing garbage */
|
|
return EINVAL;
|
|
}
|
|
|
|
*p = '\0';
|
|
ret = ip_from_string(s, addr);
|
|
|
|
if (ret == 0) {
|
|
*mask = m;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
unsigned int ctdb_sock_addr_port(ctdb_sock_addr *addr)
|
|
{
|
|
switch (addr->sa.sa_family) {
|
|
case AF_INET:
|
|
return ntohs(addr->ip.sin_port);
|
|
break;
|
|
case AF_INET6:
|
|
return ntohs(addr->ip6.sin6_port);
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void ctdb_sock_addr_set_port(ctdb_sock_addr *addr, unsigned int port)
|
|
{
|
|
switch (addr->sa.sa_family) {
|
|
case AF_INET:
|
|
addr->ip.sin_port = htons(port);
|
|
break;
|
|
case AF_INET6:
|
|
addr->ip6.sin6_port = htons(port);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int ctdb_sock_addr_cmp_family(const ctdb_sock_addr *addr1,
|
|
const ctdb_sock_addr *addr2)
|
|
{
|
|
/* This is somewhat arbitrary. However, when used for sorting
|
|
* it just needs to be consistent.
|
|
*/
|
|
if (addr1->sa.sa_family < addr2->sa.sa_family) {
|
|
return -1;
|
|
}
|
|
if (addr1->sa.sa_family > addr2->sa.sa_family) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ctdb_sock_addr_cmp_ip(const ctdb_sock_addr *addr1,
|
|
const ctdb_sock_addr *addr2)
|
|
{
|
|
int ret;
|
|
|
|
ret = ctdb_sock_addr_cmp_family(addr1, addr2);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
switch (addr1->sa.sa_family) {
|
|
case AF_INET:
|
|
ret = memcmp(&addr1->ip.sin_addr.s_addr,
|
|
&addr2->ip.sin_addr.s_addr, 4);
|
|
break;
|
|
|
|
case AF_INET6:
|
|
ret = memcmp(addr1->ip6.sin6_addr.s6_addr,
|
|
addr2->ip6.sin6_addr.s6_addr, 16);
|
|
break;
|
|
|
|
default:
|
|
ret = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ctdb_sock_addr_cmp(const ctdb_sock_addr *addr1,
|
|
const ctdb_sock_addr *addr2)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = ctdb_sock_addr_cmp_ip(addr1, addr2);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
switch (addr1->sa.sa_family) {
|
|
case AF_INET:
|
|
if (addr1->ip.sin_port < addr2->ip.sin_port) {
|
|
ret = -1;
|
|
} else if (addr1->ip.sin_port > addr2->ip.sin_port) {
|
|
ret = 1;
|
|
}
|
|
break;
|
|
|
|
case AF_INET6:
|
|
if (addr1->ip6.sin6_port < addr2->ip6.sin6_port) {
|
|
ret = -1;
|
|
} else if (addr1->ip6.sin6_port > addr2->ip6.sin6_port) {
|
|
ret = 1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ret = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool ctdb_sock_addr_same_ip(const ctdb_sock_addr *addr1,
|
|
const ctdb_sock_addr *addr2)
|
|
{
|
|
return (ctdb_sock_addr_cmp_ip(addr1, addr2) == 0);
|
|
}
|
|
|
|
bool ctdb_sock_addr_same(const ctdb_sock_addr *addr1,
|
|
const ctdb_sock_addr *addr2)
|
|
{
|
|
return (ctdb_sock_addr_cmp(addr1, addr2) == 0);
|
|
}
|
|
|
|
int ctdb_connection_to_buf(char *buf, size_t buflen,
|
|
struct ctdb_connection *conn, bool client_first)
|
|
{
|
|
char server[64], client[64];
|
|
int ret;
|
|
|
|
ret = ctdb_sock_addr_to_buf(server, sizeof(server),
|
|
&conn->server, true);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ctdb_sock_addr_to_buf(client, sizeof(client),
|
|
&conn->client, true);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (! client_first) {
|
|
ret = snprintf(buf, buflen, "%s %s", server, client);
|
|
} else {
|
|
ret = snprintf(buf, buflen, "%s %s", client, server);
|
|
}
|
|
if (ret < 0 || (size_t)ret >= buflen) {
|
|
return ENOSPC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *ctdb_connection_to_string(TALLOC_CTX *mem_ctx,
|
|
struct ctdb_connection *conn,
|
|
bool client_first)
|
|
{
|
|
const size_t len = 128;
|
|
char *out;
|
|
int ret;
|
|
|
|
out = talloc_size(mem_ctx, len);
|
|
if (out == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
ret = ctdb_connection_to_buf(out, len, conn, client_first);
|
|
if (ret != 0) {
|
|
talloc_free(out);
|
|
return NULL;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
int ctdb_connection_from_string(const char *str, bool client_first,
|
|
struct ctdb_connection *conn)
|
|
{
|
|
char s[128];
|
|
char *t1 = NULL, *t2 = NULL;
|
|
size_t len;
|
|
ctdb_sock_addr *first = (client_first ? &conn->client : &conn->server);
|
|
ctdb_sock_addr *second = (client_first ? &conn->server : &conn->client);
|
|
int ret;
|
|
|
|
len = strlcpy(s, str, sizeof(s));
|
|
if (len >= sizeof(s)) {
|
|
return EINVAL;
|
|
}
|
|
|
|
t1 = strtok(s, " \t\n");
|
|
if (t1 == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
t2 = strtok(NULL, " \t\n\0");
|
|
if (t2 == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
ret = ctdb_sock_addr_from_string(t1, first, true);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ctdb_sock_addr_from_string(t2, second, true);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ctdb_sock_addr_cmp_family(first, second);
|
|
if (ret != 0) {
|
|
return EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ctdb_connection_list_add(struct ctdb_connection_list *conn_list,
|
|
struct ctdb_connection *conn)
|
|
{
|
|
uint32_t len;
|
|
|
|
if (conn_list == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
/* Ensure array is big enough */
|
|
len = talloc_array_length(conn_list->conn);
|
|
if (conn_list->num == len) {
|
|
conn_list->conn = talloc_realloc(conn_list, conn_list->conn,
|
|
struct ctdb_connection,
|
|
len+128);
|
|
if (conn_list->conn == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
}
|
|
|
|
conn_list->conn[conn_list->num] = *conn;
|
|
conn_list->num++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int connection_cmp(const void *a, const void *b)
|
|
{
|
|
const struct ctdb_connection *conn_a = a;
|
|
const struct ctdb_connection *conn_b = b;
|
|
int ret;
|
|
|
|
ret = ctdb_sock_addr_cmp(&conn_a->server, &conn_b->server);
|
|
if (ret == 0) {
|
|
ret = ctdb_sock_addr_cmp(&conn_a->client, &conn_b->client);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int ctdb_connection_list_sort(struct ctdb_connection_list *conn_list)
|
|
{
|
|
if (conn_list == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
if (conn_list->num > 0) {
|
|
qsort(conn_list->conn, conn_list->num,
|
|
sizeof(struct ctdb_connection), connection_cmp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *ctdb_connection_list_to_string(
|
|
TALLOC_CTX *mem_ctx,
|
|
struct ctdb_connection_list *conn_list, bool client_first)
|
|
{
|
|
uint32_t i;
|
|
char *out;
|
|
|
|
out = talloc_strdup(mem_ctx, "");
|
|
if (out == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (conn_list == NULL || conn_list->num == 0) {
|
|
return out;
|
|
}
|
|
|
|
for (i = 0; i < conn_list->num; i++) {
|
|
char buf[128];
|
|
int ret;
|
|
|
|
ret = ctdb_connection_to_buf(buf, sizeof(buf),
|
|
&conn_list->conn[i], client_first);
|
|
if (ret != 0) {
|
|
talloc_free(out);
|
|
return NULL;
|
|
}
|
|
|
|
out = talloc_asprintf_append(out, "%s\n", buf);
|
|
if (out == NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
struct ctdb_connection_list_read_state {
|
|
struct ctdb_connection_list *list;
|
|
bool client_first;
|
|
};
|
|
|
|
static int ctdb_connection_list_read_line(char *line, void *private_data)
|
|
{
|
|
struct ctdb_connection_list_read_state *state =
|
|
(struct ctdb_connection_list_read_state *)private_data;
|
|
struct ctdb_connection conn;
|
|
int ret;
|
|
|
|
/* Skip empty lines */
|
|
if (line[0] == '\0') {
|
|
return 0;
|
|
}
|
|
|
|
/* Comment */
|
|
if (line[0] == '#') {
|
|
return 0;
|
|
}
|
|
|
|
ret = ctdb_connection_from_string(line, state->client_first, &conn);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ctdb_connection_list_add(state->list, &conn);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ctdb_connection_list_read(TALLOC_CTX *mem_ctx,
|
|
int fd,
|
|
bool client_first,
|
|
struct ctdb_connection_list **conn_list)
|
|
{
|
|
struct ctdb_connection_list_read_state state;
|
|
int ret;
|
|
|
|
if (conn_list == NULL) {
|
|
return EINVAL;
|
|
}
|
|
|
|
state.list = talloc_zero(mem_ctx, struct ctdb_connection_list);
|
|
if (state.list == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
state.client_first = client_first;
|
|
|
|
ret = line_read(fd,
|
|
128,
|
|
mem_ctx,
|
|
ctdb_connection_list_read_line,
|
|
&state,
|
|
NULL);
|
|
|
|
*conn_list = state.list;
|
|
|
|
return ret;
|
|
}
|