#include "db_wrap.h"
#include "lib/tdb/include/tdb.h"
#include "lib/util/dlinklist.h"
-#include "lib/events/events.h"
+#include "lib/tevent/tevent.h"
#include "system/network.h"
#include "system/filesys.h"
#include "system/locale.h"
*/
int ctdb_call_local(struct ctdb_db_context *ctdb_db, struct ctdb_call *call,
struct ctdb_ltdb_header *header, TALLOC_CTX *mem_ctx,
- TDB_DATA *data, uint32_t caller)
+ TDB_DATA *data, bool updatetdb)
{
struct ctdb_call_info *c;
struct ctdb_registered_call *fn;
c->new_data = NULL;
c->reply_data = NULL;
c->status = 0;
+ c->header = header;
for (fn=ctdb_db->calls;fn;fn=fn->next) {
if (fn->id == call->call_id) break;
return -1;
}
- if (header->laccessor != caller) {
- header->lacount = 0;
- }
- header->laccessor = caller;
- header->lacount++;
-
- /* we need to force the record to be written out if this was a remote access,
- so that the lacount is updated */
- if (c->new_data == NULL && header->laccessor != ctdb->pnn) {
+ /* we need to force the record to be written out if this was a remote access */
+ if (c->new_data == NULL) {
c->new_data = &c->record_data;
}
- if (c->new_data) {
+ if (c->new_data && updatetdb) {
/* XXX check that we always have the lock here? */
if (ctdb_ltdb_store(ctdb_db, call->key, header, *c->new_data) != 0) {
ctdb_set_error(ctdb, "ctdb_call tdb_store failed\n");
/*
this is called in the client, when data comes in from the daemon
*/
-static void ctdb_client_read_cb(uint8_t *data, size_t cnt, void *args)
+void ctdb_client_read_cb(uint8_t *data, size_t cnt, void *args)
{
struct ctdb_context *ctdb = talloc_get_type(args, struct ctdb_context);
struct ctdb_req_header *hdr = (struct ctdb_req_header *)data;
}
/*
- connect to a unix domain socket
+ connect with exponential backoff, thanks Stevens
*/
-int ctdb_socket_connect(struct ctdb_context *ctdb)
+#define CONNECT_MAXSLEEP 64
+static int ctdb_connect_retry(struct ctdb_context *ctdb)
{
struct sockaddr_un addr;
+ int secs;
+ int ret = 0;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, ctdb->daemon.name, sizeof(addr.sun_path));
+ for (secs = 1; secs <= CONNECT_MAXSLEEP; secs *= 2) {
+ ret = connect(ctdb->daemon.sd, (struct sockaddr *)&addr,
+ sizeof(addr));
+ if ((ret == 0) || (errno != EAGAIN)) {
+ break;
+ }
+
+ if (secs <= (CONNECT_MAXSLEEP / 2)) {
+ DEBUG(DEBUG_ERR,("connect failed: %s, retry in %d second(s)\n",
+ strerror(errno), secs));
+ sleep(secs);
+ }
+ }
+
+ return ret;
+}
+
+/*
+ connect to a unix domain socket
+*/
+int ctdb_socket_connect(struct ctdb_context *ctdb)
+{
ctdb->daemon.sd = socket(AF_UNIX, SOCK_STREAM, 0);
if (ctdb->daemon.sd == -1) {
DEBUG(DEBUG_ERR,(__location__ " Failed to open client socket. Errno:%s(%d)\n", strerror(errno), errno));
set_nonblocking(ctdb->daemon.sd);
set_close_on_exec(ctdb->daemon.sd);
-
- if (connect(ctdb->daemon.sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+
+ if (ctdb_connect_retry(ctdb) == -1) {
+ DEBUG(DEBUG_ERR,(__location__ " Failed to connect client socket to daemon. Errno:%s(%d)\n", strerror(errno), errno));
close(ctdb->daemon.sd);
ctdb->daemon.sd = -1;
- DEBUG(DEBUG_ERR,(__location__ " Failed to connect client socket to daemon. Errno:%s(%d)\n", strerror(errno), errno));
return -1;
}
ctdb->daemon.queue = ctdb_queue_setup(ctdb, ctdb, ctdb->daemon.sd,
CTDB_DS_ALIGNMENT,
- ctdb_client_read_cb, ctdb);
+ ctdb_client_read_cb, ctdb, "to-ctdbd");
return 0;
}
call->status = state->call->status;
talloc_free(state);
- return 0;
+ return call->status;
}
*(state->call) = *call;
state->ctdb_db = ctdb_db;
- ret = ctdb_call_local(ctdb_db, state->call, header, state, data, ctdb->pnn);
+ ret = ctdb_call_local(ctdb_db, state->call, header, state, data, true);
return state;
}
ret = ctdb_ltdb_fetch(ctdb_db, call->key, &header, ctdb_db, &data);
+ if ((call->flags & CTDB_IMMEDIATE_MIGRATION) && (header.flags & CTDB_REC_RO_HAVE_DELEGATIONS)) {
+ ret = -1;
+ }
+
if (ret == 0 && header.dmaster == ctdb->pnn) {
state = ctdb_client_call_local_send(ctdb_db, call, &header, &data);
talloc_free(data.dptr);
tell the daemon what messaging srvid we will use, and register the message
handler function in the client
*/
-int ctdb_set_message_handler(struct ctdb_context *ctdb, uint64_t srvid,
- ctdb_message_fn_t handler,
+int ctdb_client_set_message_handler(struct ctdb_context *ctdb, uint64_t srvid,
+ ctdb_msg_fn_t handler,
void *private_data)
{
/*
tell the daemon we no longer want a srvid
*/
-int ctdb_remove_message_handler(struct ctdb_context *ctdb, uint64_t srvid, void *private_data)
+int ctdb_client_remove_message_handler(struct ctdb_context *ctdb, uint64_t srvid, void *private_data)
{
int res;
int32_t status;
/*
send a message - from client context
*/
-int ctdb_send_message(struct ctdb_context *ctdb, uint32_t pnn,
+int ctdb_client_send_message(struct ctdb_context *ctdb, uint32_t pnn,
uint64_t srvid, TDB_DATA data)
{
struct ctdb_req_message *r;
return ctdb_call(ctdb_db, &call);
}
+/*
+ try to fetch a readonly copy of a record
+ */
+static int
+ctdb_client_fetch_readonly(struct ctdb_db_context *ctdb_db, TDB_DATA key, TALLOC_CTX *mem_ctx, struct ctdb_ltdb_header **hdr, TDB_DATA *data)
+{
+ int ret;
+
+ struct ctdb_call call;
+ ZERO_STRUCT(call);
+
+ call.call_id = CTDB_FETCH_WITH_HEADER_FUNC;
+ call.call_data.dptr = NULL;
+ call.call_data.dsize = 0;
+ call.key = key;
+ call.flags = CTDB_WANT_READONLY;
+ ret = ctdb_call(ctdb_db, &call);
+
+ if (ret != 0) {
+ return -1;
+ }
+ if (call.reply_data.dsize < sizeof(struct ctdb_ltdb_header)) {
+ return -1;
+ }
+
+ *hdr = talloc_memdup(mem_ctx, &call.reply_data.dptr[0], sizeof(struct ctdb_ltdb_header));
+ if (*hdr == NULL) {
+ talloc_free(call.reply_data.dptr);
+ return -1;
+ }
+
+ data->dsize = call.reply_data.dsize - sizeof(struct ctdb_ltdb_header);
+ data->dptr = talloc_memdup(mem_ctx, &call.reply_data.dptr[sizeof(struct ctdb_ltdb_header)], data->dsize);
+ if (data->dptr == NULL) {
+ talloc_free(call.reply_data.dptr);
+ talloc_free(hdr);
+ return -1;
+ }
+
+ return 0;
+}
+
/*
get a lock on a record, and return the records data. Blocks until it gets the lock
*/
return h;
}
+/*
+ get a readonly lock on a record, and return the records data. Blocks until it gets the lock
+ */
+struct ctdb_record_handle *
+ctdb_fetch_readonly_lock(
+ struct ctdb_db_context *ctdb_db, TALLOC_CTX *mem_ctx,
+ TDB_DATA key, TDB_DATA *data,
+ int read_only)
+{
+ int ret;
+ struct ctdb_record_handle *h;
+ struct ctdb_ltdb_header *roheader = NULL;
+
+ h = talloc_zero(mem_ctx, struct ctdb_record_handle);
+ if (h == NULL) {
+ return NULL;
+ }
+
+ h->ctdb_db = ctdb_db;
+ h->key = key;
+ h->key.dptr = talloc_memdup(h, key.dptr, key.dsize);
+ if (h->key.dptr == NULL) {
+ talloc_free(h);
+ return NULL;
+ }
+ h->data = data;
+
+ data->dptr = NULL;
+ data->dsize = 0;
+
+
+again:
+ talloc_free(roheader);
+ roheader = NULL;
+
+ talloc_free(data->dptr);
+ data->dptr = NULL;
+ data->dsize = 0;
+
+ /* Lock the record/chain */
+ ret = ctdb_ltdb_lock(ctdb_db, key);
+ if (ret != 0) {
+ DEBUG(DEBUG_ERR, (__location__ " failed to lock ltdb record\n"));
+ talloc_free(h);
+ return NULL;
+ }
+
+ talloc_set_destructor(h, fetch_lock_destructor);
+
+ /* Check if record exists yet in the TDB */
+ ret = ctdb_ltdb_fetch_readonly(ctdb_db, key, &h->header, h, data);
+ if (ret != 0) {
+ ctdb_ltdb_unlock(ctdb_db, key);
+ ret = ctdb_client_force_migration(ctdb_db, key);
+ if (ret != 0) {
+ DEBUG(DEBUG_DEBUG,("ctdb_fetch_readonly_lock: force_migration failed\n"));
+ talloc_free(h);
+ return NULL;
+ }
+ goto again;
+ }
+
+ /* if this is a request for read/write and we have delegations
+ we have to revoke all delegations first
+ */
+ if ((read_only == 0)
+ && (h->header.dmaster == ctdb_db->ctdb->pnn)
+ && (h->header.flags & CTDB_REC_RO_HAVE_DELEGATIONS)) {
+ ctdb_ltdb_unlock(ctdb_db, key);
+ ret = ctdb_client_force_migration(ctdb_db, key);
+ if (ret != 0) {
+ DEBUG(DEBUG_DEBUG,("ctdb_fetch_readonly_lock: force_migration failed\n"));
+ talloc_free(h);
+ return NULL;
+ }
+ goto again;
+ }
+
+ /* if we are dmaster, just return the handle */
+ if (h->header.dmaster == ctdb_db->ctdb->pnn) {
+ return h;
+ }
+
+ if (read_only != 0) {
+ TDB_DATA rodata = {NULL, 0};
+
+ if ((h->header.flags & CTDB_REC_RO_HAVE_READONLY)
+ || (h->header.flags & CTDB_REC_RO_HAVE_DELEGATIONS)) {
+ return h;
+ }
+
+ ctdb_ltdb_unlock(ctdb_db, key);
+ ret = ctdb_client_fetch_readonly(ctdb_db, key, h, &roheader, &rodata);
+ if (ret != 0) {
+ DEBUG(DEBUG_ERR,("ctdb_fetch_readonly_lock: failed. force migration and try again\n"));
+ ret = ctdb_client_force_migration(ctdb_db, key);
+ if (ret != 0) {
+ DEBUG(DEBUG_DEBUG,("ctdb_fetch_readonly_lock: force_migration failed\n"));
+ talloc_free(h);
+ return NULL;
+ }
+
+ goto again;
+ }
+
+ if (!(roheader->flags&CTDB_REC_RO_HAVE_READONLY)) {
+ ret = ctdb_client_force_migration(ctdb_db, key);
+ if (ret != 0) {
+ DEBUG(DEBUG_DEBUG,("ctdb_fetch_readonly_lock: force_migration failed\n"));
+ talloc_free(h);
+ return NULL;
+ }
+
+ goto again;
+ }
+
+ ret = ctdb_ltdb_lock(ctdb_db, key);
+ if (ret != 0) {
+ DEBUG(DEBUG_ERR, (__location__ " failed to lock ltdb record\n"));
+ talloc_free(h);
+ return NULL;
+ }
+
+ ret = ctdb_ltdb_fetch_readonly(ctdb_db, key, &h->header, h, data);
+ if (ret != 0) {
+ ctdb_ltdb_unlock(ctdb_db, key);
+
+ ret = ctdb_client_force_migration(ctdb_db, key);
+ if (ret != 0) {
+ DEBUG(DEBUG_DEBUG,("ctdb_fetch_readonly_lock: force_migration failed\n"));
+ talloc_free(h);
+ return NULL;
+ }
+
+ goto again;
+ }
+
+ if (h->header.rsn >= roheader->rsn) {
+ DEBUG(DEBUG_ERR,("READONLY RECORD: Too small RSN, migrate and try again\n"));
+ ctdb_ltdb_unlock(ctdb_db, key);
+
+ ret = ctdb_client_force_migration(ctdb_db, key);
+ if (ret != 0) {
+ DEBUG(DEBUG_DEBUG,("ctdb_fetch_readonly_lock: force_migration failed\n"));
+ talloc_free(h);
+ return NULL;
+ }
+
+ goto again;
+ }
+
+ if (ctdb_ltdb_store(ctdb_db, key, roheader, rodata) != 0) {
+ ctdb_ltdb_unlock(ctdb_db, key);
+
+ ret = ctdb_client_force_migration(ctdb_db, key);
+ if (ret != 0) {
+ DEBUG(DEBUG_DEBUG,("ctdb_fetch_readonly_lock: force_migration failed\n"));
+ talloc_free(h);
+ return NULL;
+ }
+
+ goto again;
+ }
+ return h;
+ }
+
+ /* we are not dmaster and this was not a request for a readonly lock
+ * so unlock the record, migrate it and try again
+ */
+ ctdb_ltdb_unlock(ctdb_db, key);
+ ret = ctdb_client_force_migration(ctdb_db, key);
+ if (ret != 0) {
+ DEBUG(DEBUG_DEBUG,("ctdb_fetch_lock: force_migration failed\n"));
+ talloc_free(h);
+ return NULL;
+ }
+ goto again;
+}
+
/*
store some data to the record that was locked with ctdb_fetch_lock()
*/
call.call_id = CTDB_FETCH_FUNC;
call.call_data.dptr = NULL;
call.call_data.dsize = 0;
+ call.key = key;
ret = ctdb_call(ctdb_db, &call);
return 0;
}
+/*
+ this is a plain fetch procedure that all databases support
+ this returns the full record including the ltdb header
+*/
+static int ctdb_fetch_with_header_func(struct ctdb_call_info *call)
+{
+ call->reply_data = talloc(call, TDB_DATA);
+ if (call->reply_data == NULL) {
+ return -1;
+ }
+ call->reply_data->dsize = sizeof(struct ctdb_ltdb_header) + call->record_data.dsize;
+ call->reply_data->dptr = talloc_size(call->reply_data, call->reply_data->dsize);
+ if (call->reply_data->dptr == NULL) {
+ return -1;
+ }
+ memcpy(call->reply_data->dptr, call->header, sizeof(struct ctdb_ltdb_header));
+ memcpy(&call->reply_data->dptr[sizeof(struct ctdb_ltdb_header)], call->record_data.dptr, call->record_data.dsize);
+
+ return 0;
+}
+
/*
attach to a specific database - client call
*/
-struct ctdb_db_context *ctdb_attach(struct ctdb_context *ctdb, const char *name, bool persistent, uint32_t tdb_flags)
+struct ctdb_db_context *ctdb_attach(struct ctdb_context *ctdb,
+ struct timeval timeout,
+ const char *name,
+ bool persistent,
+ uint32_t tdb_flags)
{
struct ctdb_db_context *ctdb_db;
TDB_DATA data;
ctdb_db->db_id = *(uint32_t *)data.dptr;
talloc_free(data.dptr);
- ret = ctdb_ctrl_getdbpath(ctdb, timeval_current_ofs(2, 0), CTDB_CURRENT_NODE, ctdb_db->db_id, ctdb_db, &ctdb_db->db_path);
+ ret = ctdb_ctrl_getdbpath(ctdb, timeout, CTDB_CURRENT_NODE, ctdb_db->db_id, ctdb_db, &ctdb_db->db_path);
if (ret != 0) {
DEBUG(DEBUG_ERR,("Failed to get dbpath for database '%s'\n", name));
talloc_free(ctdb_db);
/* add well known functions */
ctdb_set_call(ctdb_db, ctdb_null_func, CTDB_NULL_FUNC);
ctdb_set_call(ctdb_db, ctdb_fetch_func, CTDB_FETCH_FUNC);
+ ctdb_set_call(ctdb_db, ctdb_fetch_with_header_func, CTDB_FETCH_WITH_HEADER_FUNC);
return ctdb_db;
}
state.private_data = private_data;
state.fn = fn;
- ret = ctdb_set_message_handler(ctdb_db->ctdb, srvid, traverse_handler, &state);
+ ret = ctdb_client_set_message_handler(ctdb_db->ctdb, srvid, traverse_handler, &state);
if (ret != 0) {
DEBUG(DEBUG_ERR,("Failed to setup traverse handler\n"));
return -1;
data, NULL, NULL, &status, NULL, NULL);
if (ret != 0 || status != 0) {
DEBUG(DEBUG_ERR,("ctdb_traverse_all failed\n"));
- ctdb_remove_message_handler(ctdb_db->ctdb, srvid, &state);
+ ctdb_client_remove_message_handler(ctdb_db->ctdb, srvid, &state);
return -1;
}
event_loop_once(ctdb_db->ctdb->ev);
}
- ret = ctdb_remove_message_handler(ctdb_db->ctdb, srvid, &state);
+ ret = ctdb_client_remove_message_handler(ctdb_db->ctdb, srvid, &state);
if (ret != 0) {
DEBUG(DEBUG_ERR,("Failed to remove ctdb_traverse handler\n"));
return -1;
return state.count;
}
-#define ISASCII(x) ((x>31)&&(x<128))
+#define ISASCII(x) (isprint(x) && !strchr("\"\\", (x)))
/*
called on each key during a catdb
*/
fprintf(f, "dmaster: %u\n", h->dmaster);
fprintf(f, "rsn: %llu\n", (unsigned long long)h->rsn);
+ fprintf(f, "flags: 0x%08x", h->flags);
+ if (h->flags & CTDB_REC_FLAG_MIGRATED_WITH_DATA) printf(" MIGRATED_WITH_DATA");
+ if (h->flags & CTDB_REC_FLAG_VACUUM_MIGRATED) printf(" VACUUM_MIGRATED");
+ if (h->flags & CTDB_REC_FLAG_AUTOMATIC) printf(" AUTOMATIC");
+ if (h->flags & CTDB_REC_RO_HAVE_DELEGATIONS) printf(" RO_HAVE_DELEGATIONS");
+ if (h->flags & CTDB_REC_RO_HAVE_READONLY) printf(" RO_HAVE_READONLY");
+ if (h->flags & CTDB_REC_RO_REVOKING_READONLY) printf(" RO_REVOKING_READONLY");
+ if (h->flags & CTDB_REC_RO_REVOKE_COMPLETE) printf(" RO_REVOKE_COMPLETE");
+ fprintf(f, "\n");
- fprintf(f, "data(%u) = \"", (unsigned)data.dsize - sizeof(*h));
+ fprintf(f, "data(%u) = \"", (unsigned)(data.dsize - sizeof(*h)));
for (i=sizeof(*h);i<data.dsize;i++) {
if (ISASCII(data.dptr[i])) {
fprintf(f, "%c", data.dptr[i]);
}
ctdb->ev = ev;
ctdb->idr = idr_init(ctdb);
+ /* Wrap early to exercise code. */
+ ctdb->lastid = INT_MAX-200;
CTDB_NO_MEMORY_NULL(ctdb, ctdb->idr);
ret = ctdb_set_socketname(ctdb, CTDB_PATH);
return NULL;
}
+ ctdb->statistics.statistics_start_time = timeval_current();
+
return ctdb;
}
return 0;
}
+const char *ctdb_get_socketname(struct ctdb_context *ctdb)
+{
+ return ctdb->daemon.name;
+}
+
/*
return the pnn of this node
*/
* to the daemon as a client process, this function can be used to change
* the ctdb context from daemon into client mode
*/
-int switch_from_server_to_client(struct ctdb_context *ctdb)
+int switch_from_server_to_client(struct ctdb_context *ctdb, const char *fmt, ...)
{
int ret;
+ va_list ap;
+
+ /* Add extra information so we can identify this in the logs */
+ va_start(ap, fmt);
+ debug_extra = talloc_strdup_append(talloc_vasprintf(NULL, fmt, ap), ":");
+ va_end(ap);
/* shutdown the transport */
if (ctdb->methods) {
/* get a new event context */
talloc_free(ctdb->ev);
ctdb->ev = event_context_init(ctdb);
+ tevent_loop_allow_nesting(ctdb->ev);
close(ctdb->daemon.sd);
ctdb->daemon.sd = -1;
+ /* the client does not need to be realtime */
+ if (ctdb->do_setsched) {
+ ctdb_restore_scheduler(ctdb);
+ }
+
/* initialise ctdb */
ret = ctdb_socket_connect(ctdb);
if (ret != 0) {
return 0;
}
+
+int ctdb_ctrl_getstathistory(struct ctdb_context *ctdb, struct timeval timeout, uint32_t destnode, TALLOC_CTX *mem_ctx, struct ctdb_statistics_wire **stats)
+{
+ int ret;
+ TDB_DATA outdata;
+ int32_t res;
+
+ ret = ctdb_control(ctdb, destnode, 0,
+ CTDB_CONTROL_GET_STAT_HISTORY, 0, tdb_null,
+ mem_ctx, &outdata, &res, &timeout, NULL);
+ if (ret != 0 || res != 0 || outdata.dsize == 0) {
+ DEBUG(DEBUG_ERR,(__location__ " ctdb_control for getstathistory failed ret:%d res:%d\n", ret, res));
+ return -1;
+ }
+
+ *stats = (struct ctdb_statistics_wire *)talloc_memdup(mem_ctx, outdata.dptr, outdata.dsize);
+ talloc_free(outdata.dptr);
+
+ return 0;
+}
+
+struct ctdb_ltdb_header *ctdb_header_from_record_handle(struct ctdb_record_handle *h)
+{
+ if (h == NULL) {
+ return NULL;
+ }
+
+ return &h->header;
+}
+
+
+struct ctdb_client_control_state *
+ctdb_ctrl_updaterecord_send(struct ctdb_context *ctdb, TALLOC_CTX *mem_ctx, struct timeval timeout, uint32_t destnode, struct ctdb_db_context *ctdb_db, TDB_DATA key, struct ctdb_ltdb_header *header, TDB_DATA data)
+{
+ struct ctdb_client_control_state *handle;
+ struct ctdb_marshall_buffer *m;
+ struct ctdb_rec_data *rec;
+ TDB_DATA outdata;
+
+ m = talloc_zero(mem_ctx, struct ctdb_marshall_buffer);
+ if (m == NULL) {
+ DEBUG(DEBUG_ERR, ("Failed to allocate marshall buffer for update record\n"));
+ return NULL;
+ }
+
+ m->db_id = ctdb_db->db_id;
+
+ rec = ctdb_marshall_record(m, 0, key, header, data);
+ if (rec == NULL) {
+ DEBUG(DEBUG_ERR,("Failed to marshall record for update record\n"));
+ talloc_free(m);
+ return NULL;
+ }
+ m = talloc_realloc_size(mem_ctx, m, rec->length + offsetof(struct ctdb_marshall_buffer, data));
+ if (m == NULL) {
+ DEBUG(DEBUG_CRIT,(__location__ " Failed to expand recdata\n"));
+ talloc_free(m);
+ return NULL;
+ }
+ m->count++;
+ memcpy((uint8_t *)m + offsetof(struct ctdb_marshall_buffer, data), rec, rec->length);
+
+
+ outdata.dptr = (uint8_t *)m;
+ outdata.dsize = talloc_get_size(m);
+
+ handle = ctdb_control_send(ctdb, destnode, 0,
+ CTDB_CONTROL_UPDATE_RECORD, 0, outdata,
+ mem_ctx, &timeout, NULL);
+ talloc_free(m);
+ return handle;
+}
+
+int ctdb_ctrl_updaterecord_recv(struct ctdb_context *ctdb, struct ctdb_client_control_state *state)
+{
+ int ret;
+ int32_t res;
+
+ ret = ctdb_control_recv(ctdb, state, state, NULL, &res, NULL);
+ if ( (ret != 0) || (res != 0) ){
+ DEBUG(DEBUG_ERR,(__location__ " ctdb_ctrl_update_record_recv failed\n"));
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+ctdb_ctrl_updaterecord(struct ctdb_context *ctdb, TALLOC_CTX *mem_ctx, struct timeval timeout, uint32_t destnode, struct ctdb_db_context *ctdb_db, TDB_DATA key, struct ctdb_ltdb_header *header, TDB_DATA data)
+{
+ struct ctdb_client_control_state *state;
+
+ state = ctdb_ctrl_updaterecord_send(ctdb, mem_ctx, timeout, destnode, ctdb_db, key, header, data);
+ return ctdb_ctrl_updaterecord_recv(ctdb, state);
+}
+
+
+
+
+
+
+/*
+ set a database to be readonly
+ */
+struct ctdb_client_control_state *
+ctdb_ctrl_set_db_readonly_send(struct ctdb_context *ctdb, uint32_t destnode, uint32_t dbid)
+{
+ TDB_DATA data;
+
+ data.dptr = (uint8_t *)&dbid;
+ data.dsize = sizeof(dbid);
+
+ return ctdb_control_send(ctdb, destnode, 0,
+ CTDB_CONTROL_SET_DB_READONLY, 0, data,
+ ctdb, NULL, NULL);
+}
+
+int ctdb_ctrl_set_db_readonly_recv(struct ctdb_context *ctdb, struct ctdb_client_control_state *state)
+{
+ int ret;
+ int32_t res;
+
+ ret = ctdb_control_recv(ctdb, state, ctdb, NULL, &res, NULL);
+ if (ret != 0 || res != 0) {
+ DEBUG(DEBUG_ERR,(__location__ " ctdb_ctrl_set_db_readonly_recv failed ret:%d res:%d\n", ret, res));
+ return -1;
+ }
+
+ return 0;
+}
+
+int ctdb_ctrl_set_db_readonly(struct ctdb_context *ctdb, uint32_t destnode, uint32_t dbid)
+{
+ struct ctdb_client_control_state *state;
+
+ state = ctdb_ctrl_set_db_readonly_send(ctdb, destnode, dbid);
+ return ctdb_ctrl_set_db_readonly_recv(ctdb, state);
+}