s3: Implement global locks in a g_lock tdb
authorVolker Lendecke <vl@samba.org>
Sun, 25 Oct 2009 15:12:12 +0000 (16:12 +0100)
committerKarolin Seeger <kseeger@samba.org>
Thu, 1 Apr 2010 07:39:13 +0000 (09:39 +0200)
This is the basis to implement global locks in ctdb without depending on a
shared file system. The initial goal is to make ctdb persistent transactions
deterministic without too many timeouts.
(cherry picked from commit 4c1c3f2549f32fd069e0e7bf3aec299213f1e85b)
(cherry picked from commit 98873e0d6a38abdcaed48af6504a9a39a23ae027)

source3/Makefile.in
source3/include/ctdbd_conn.h
source3/include/g_lock.h [new file with mode: 0644]
source3/lib/ctdbd_conn.c
source3/lib/g_lock.c [new file with mode: 0644]
source3/librpc/gen_ndr/messaging.h
source3/librpc/gen_ndr/ndr_messaging.c
source3/librpc/idl/messaging.idl
source3/utils/net.c
source3/utils/net_g_lock.c [new file with mode: 0644]
source3/utils/net_proto.h

index f84ed20f917df2e708f6c53d3ab4a4fd1c333d50..f58bb7078a23dbe160ff26c11edb1f746d88438b 100644 (file)
@@ -265,6 +265,7 @@ EXTRA_ALL_TARGETS = @EXTRA_ALL_TARGETS@
 TDB_LIB_OBJ = lib/util_tdb.o ../lib/util/util_tdb.o \
          lib/dbwrap.o lib/dbwrap_tdb.o \
          lib/dbwrap_ctdb.o \
+         lib/g_lock.o \
          lib/dbwrap_rbt.o
 
 TDB_VALIDATE_OBJ = lib/tdb_validate.o
@@ -1011,6 +1012,7 @@ NET_OBJ1 = utils/net.o utils/net_ads.o utils/net_help.o \
           utils/net_conf.o utils/net_join.o utils/net_user.o \
           utils/net_group.o utils/net_file.o utils/net_registry.o \
           auth/token_util.o utils/net_dom.o utils/net_share.o \
+          utils/net_g_lock.o \
           utils/net_eventlog.o
 
 # these are not processed by make proto
index d72123533622c3f5f16e85cc5ac33b8f9232806b..96e7aa08a603e31baa2ee5c48ce097330dda452b 100644 (file)
@@ -73,5 +73,7 @@ NTSTATUS ctdbd_control_local(struct ctdbd_connection *conn, uint32 opcode,
                             uint64_t srvid, uint32_t flags, TDB_DATA data, 
                             TALLOC_CTX *mem_ctx, TDB_DATA *outdata,
                             int *cstatus);
+NTSTATUS ctdb_watch_us(struct ctdbd_connection *conn);
+NTSTATUS ctdb_unwatch(struct ctdbd_connection *conn);
 
 #endif /* _CTDBD_CONN_H */
diff --git a/source3/include/g_lock.h b/source3/include/g_lock.h
new file mode 100644 (file)
index 0000000..c0eed38
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+   Unix SMB/CIFS implementation.
+   global locks based on ctdb
+   Copyright (C) 2009 by Volker Lendecke
+
+   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/>.
+*/
+
+#ifndef _G_LOCK_H_
+#define _G_LOCK_H_
+
+#include "dbwrap.h"
+
+struct g_lock_ctx;
+
+enum g_lock_type {
+       G_LOCK_READ = 0,
+       G_LOCK_WRITE = 1,
+};
+
+/*
+ * Or'ed with g_lock_type
+ */
+#define G_LOCK_PENDING (2)
+
+struct g_lock_ctx *g_lock_ctx_init(TALLOC_CTX *mem_ctx,
+                                  struct messaging_context *msg);
+
+NTSTATUS g_lock_lock(struct g_lock_ctx *ctx, const char *name,
+                    enum g_lock_type lock_type, struct timeval timeout);
+NTSTATUS g_lock_unlock(struct g_lock_ctx *ctx, const char *name);
+NTSTATUS g_lock_get(struct g_lock_ctx *ctx, const char *name,
+               struct server_id *pid);
+
+int g_lock_locks(struct g_lock_ctx *ctx,
+                int (*fn)(const char *name, void *private_data),
+                void *private_data);
+NTSTATUS g_lock_dump(struct g_lock_ctx *ctx, const char *name,
+                    int (*fn)(struct server_id pid,
+                              enum g_lock_type lock_type,
+                              void *private_data),
+                    void *private_data);
+
+#endif
index 8ddb12a765bd296fdc42752b2df439552d0d71fd..900fa345a1449f45609519ee04bd0d63ccc51be6 100644 (file)
@@ -361,10 +361,18 @@ static NTSTATUS ctdb_read_req(struct ctdbd_connection *conn, uint32 reqid,
                        goto next_pkt;
                }
 
-               if (msg->srvid == CTDB_SRVID_RECONFIGURE) {
-                       DEBUG(0,("Got cluster reconfigure message in ctdb_read_req\n"));
+               if ((msg->srvid == CTDB_SRVID_RECONFIGURE)
+                   || (msg->srvid == CTDB_SRVID_SAMBA_NOTIFY)) {
+
+                       DEBUG(1, ("ctdb_read_req: Got %s message\n",
+                                 (msg->srvid == CTDB_SRVID_RECONFIGURE)
+                                 ? "cluster reconfigure" : "SAMBA_NOTIFY"));
+
                        messaging_send(conn->msg_ctx, procid_self(),
                                       MSG_SMB_BRL_VALIDATE, &data_blob_null);
+                       messaging_send(conn->msg_ctx, procid_self(),
+                                      MSG_DBWRAP_G_LOCK_RETRY,
+                                      &data_blob_null);
                        TALLOC_FREE(hdr);
                        goto next_pkt;
                }
@@ -493,6 +501,11 @@ NTSTATUS ctdbd_messaging_connection(TALLOC_CTX *mem_ctx,
                goto fail;
        }
 
+       status = register_with_ctdbd(conn, CTDB_SRVID_SAMBA_NOTIFY);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto fail;
+       }
+
        *pconn = conn;
        return NT_STATUS_OK;
 
@@ -533,15 +546,21 @@ static NTSTATUS ctdb_handle_message(uint8_t *buf, size_t length,
 
        SMB_ASSERT(conn->msg_ctx != NULL);
 
-       if (msg->srvid == CTDB_SRVID_RECONFIGURE) {
+       if ((msg->srvid == CTDB_SRVID_RECONFIGURE)
+           || (msg->srvid == CTDB_SRVID_SAMBA_NOTIFY)){
                DEBUG(0,("Got cluster reconfigure message\n"));
                /*
-                * when the cluster is reconfigured, we need to clean the brl
-                * database
+                * when the cluster is reconfigured or someone of the
+                * family has passed away (SAMBA_NOTIFY), we need to
+                * clean the brl database
                 */
                messaging_send(conn->msg_ctx, procid_self(),
                               MSG_SMB_BRL_VALIDATE, &data_blob_null);
 
+               messaging_send(conn->msg_ctx, procid_self(),
+                              MSG_DBWRAP_G_LOCK_RETRY,
+                              &data_blob_null);
+
                TALLOC_FREE(buf);
                return NT_STATUS_OK;
        }
@@ -1302,6 +1321,50 @@ NTSTATUS ctdbd_control_local(struct ctdbd_connection *conn, uint32 opcode,
        return ctdbd_control(conn, CTDB_CURRENT_NODE, opcode, srvid, flags, data, mem_ctx, outdata, cstatus);
 }
 
+NTSTATUS ctdb_watch_us(struct ctdbd_connection *conn)
+{
+       struct ctdb_client_notify_register reg_data;
+       size_t struct_len;
+       NTSTATUS status;
+       int cstatus;
+
+       reg_data.srvid = CTDB_SRVID_SAMBA_NOTIFY;
+       reg_data.len = 1;
+       reg_data.notify_data[0] = 0;
+
+       struct_len = offsetof(struct ctdb_client_notify_register,
+                             notify_data) + reg_data.len;
+
+       status = ctdbd_control_local(
+               conn, CTDB_CONTROL_REGISTER_NOTIFY, conn->rand_srvid, 0,
+               make_tdb_data((uint8_t *)&reg_data, struct_len),
+               NULL, NULL, &cstatus);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("ctdbd_control_local failed: %s\n",
+                         nt_errstr(status)));
+       }
+       return status;
+}
+
+NTSTATUS ctdb_unwatch(struct ctdbd_connection *conn)
+{
+       struct ctdb_client_notify_deregister dereg_data;
+       NTSTATUS status;
+       int cstatus;
+
+       dereg_data.srvid = CTDB_SRVID_SAMBA_NOTIFY;
+
+       status = ctdbd_control_local(
+               conn, CTDB_CONTROL_DEREGISTER_NOTIFY, conn->rand_srvid, 0,
+               make_tdb_data((uint8_t *)&dereg_data, sizeof(dereg_data)),
+               NULL, NULL, &cstatus);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("ctdbd_control_local failed: %s\n",
+                         nt_errstr(status)));
+       }
+       return status;
+}
+
 #else
 
 NTSTATUS ctdbd_init_connection(TALLOC_CTX *mem_ctx,
diff --git a/source3/lib/g_lock.c b/source3/lib/g_lock.c
new file mode 100644 (file)
index 0000000..6508b39
--- /dev/null
@@ -0,0 +1,594 @@
+/*
+   Unix SMB/CIFS implementation.
+   global locks based on dbwrap and messaging
+   Copyright (C) 2009 by Volker Lendecke
+
+   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 "includes.h"
+#include "g_lock.h"
+
+static NTSTATUS g_lock_force_unlock(struct g_lock_ctx *ctx, const char *name,
+                                   struct server_id pid);
+
+struct g_lock_ctx {
+       struct db_context *db;
+       struct messaging_context *msg;
+};
+
+/*
+ * The "g_lock.tdb" file contains records, indexed by the 0-terminated
+ * lockname. The record contains an array of "struct g_lock_rec"
+ * structures. Waiters have the lock_type with G_LOCK_PENDING or'ed.
+ */
+
+struct g_lock_rec {
+       enum g_lock_type lock_type;
+       struct server_id pid;
+};
+
+struct g_lock_ctx *g_lock_ctx_init(TALLOC_CTX *mem_ctx,
+                                  struct messaging_context *msg)
+{
+       struct g_lock_ctx *result;
+
+       result = talloc(mem_ctx, struct g_lock_ctx);
+       if (result == NULL) {
+               return NULL;
+       }
+       result->msg = msg;
+
+       result->db = db_open(result, lock_path("g_lock.tdb"), 0,
+                            TDB_CLEAR_IF_FIRST, O_RDWR|O_CREAT, 0700);
+       if (result->db == NULL) {
+               DEBUG(1, ("g_lock_init: Could not open g_lock.tdb"));
+               TALLOC_FREE(result);
+               return NULL;
+       }
+       return result;
+}
+
+static bool g_lock_conflicts(enum g_lock_type lock_type,
+                            const struct g_lock_rec *rec)
+{
+       enum g_lock_type rec_lock = rec->lock_type;
+
+       if ((rec_lock & G_LOCK_PENDING) != 0) {
+               return false;
+       }
+
+       /*
+        * Only tested write locks so far. Very likely this routine
+        * needs to be fixed for read locks....
+        */
+       if ((lock_type == G_LOCK_READ) && (rec_lock == G_LOCK_READ)) {
+               return false;
+       }
+       return true;
+}
+
+static bool g_lock_parse(TALLOC_CTX *mem_ctx, TDB_DATA data,
+                        int *pnum_locks, struct g_lock_rec **plocks)
+{
+       int i, num_locks;
+       struct g_lock_rec *locks;
+
+       if ((data.dsize % sizeof(struct g_lock_rec)) != 0) {
+               DEBUG(1, ("invalid lock record length %d\n", (int)data.dsize));
+               return false;
+       }
+
+       num_locks = data.dsize / sizeof(struct g_lock_rec);
+       locks = talloc_array(mem_ctx, struct g_lock_rec, num_locks);
+       if (locks == NULL) {
+               DEBUG(1, ("talloc failed\n"));
+               return false;
+       }
+
+       memcpy(locks, data.dptr, data.dsize);
+
+       DEBUG(10, ("locks:\n"));
+       for (i=0; i<num_locks; i++) {
+               DEBUGADD(10, ("%s: %s %s\n",
+                             procid_str(talloc_tos(), &locks[i].pid),
+                             ((locks[i].lock_type & 1) == G_LOCK_READ) ?
+                             "read" : "write",
+                             (locks[i].lock_type & G_LOCK_PENDING) ?
+                             "(pending)" : "(owner)"));
+
+               if (process_exists(locks[i].pid)) {
+                       continue;
+               }
+               DEBUGADD(10, ("%s does not exist -- discarding\n",
+                             procid_str(talloc_tos(), &locks[i].pid)));
+
+               if (i < (num_locks-1)) {
+                       locks[i] = locks[num_locks-1];
+               }
+               num_locks -= 1;
+       }
+
+       *plocks = locks;
+       *pnum_locks = num_locks;
+       return true;
+}
+
+static struct g_lock_rec *g_lock_addrec(TALLOC_CTX *mem_ctx,
+                                       struct g_lock_rec *locks,
+                                       int num_locks,
+                                       const struct server_id pid,
+                                       enum g_lock_type lock_type)
+{
+       struct g_lock_rec *result;
+
+       result = talloc_realloc(mem_ctx, locks, struct g_lock_rec,
+                               num_locks+1);
+       if (result == NULL) {
+               return NULL;
+       }
+
+       result[num_locks].pid = pid;
+       result[num_locks].lock_type = lock_type;
+       return result;
+}
+
+static void g_lock_got_retry(struct messaging_context *msg,
+                            void *private_data,
+                            uint32_t msg_type,
+                            struct server_id server_id,
+                            DATA_BLOB *data);
+static void g_lock_timedout(struct tevent_context *ev,
+                           struct tevent_timer *te,
+                           struct timeval current_time,
+                           void *private_data);
+
+static NTSTATUS g_lock_trylock(struct g_lock_ctx *ctx, const char *name,
+                              enum g_lock_type lock_type)
+{
+       struct db_record *rec = NULL;
+       struct g_lock_rec *locks = NULL;
+       int i, num_locks;
+       struct server_id self;
+       int our_index;
+       TDB_DATA data;
+       NTSTATUS status = NT_STATUS_OK;
+       NTSTATUS store_status;
+
+again:
+       rec = ctx->db->fetch_locked(ctx->db, talloc_tos(),
+                                   string_term_tdb_data(name));
+       if (rec == NULL) {
+               DEBUG(10, ("fetch_locked(\"%s\") failed\n", name));
+               status = NT_STATUS_LOCK_NOT_GRANTED;
+               goto done;
+       }
+
+       if (!g_lock_parse(talloc_tos(), rec->value, &num_locks, &locks)) {
+               DEBUG(10, ("g_lock_parse for %s failed\n", name));
+               status = NT_STATUS_INTERNAL_ERROR;
+               goto done;
+       }
+
+       self = procid_self();
+       our_index = -1;
+
+       for (i=0; i<num_locks; i++) {
+               if (procid_equal(&self, &locks[i].pid)) {
+                       if (our_index != -1) {
+                               DEBUG(1, ("g_lock_trylock: Added ourself "
+                                         "twice!\n"));
+                               status = NT_STATUS_INTERNAL_ERROR;
+                               goto done;
+                       }
+                       if ((locks[i].lock_type & G_LOCK_PENDING) == 0) {
+                               DEBUG(1, ("g_lock_trylock: Found ourself not "
+                                         "pending!\n"));
+                               status = NT_STATUS_INTERNAL_ERROR;
+                               goto done;
+                       }
+
+                       our_index = i;
+
+                       /* never conflict with ourself */
+                       continue;
+               }
+               if (g_lock_conflicts(lock_type, &locks[i])) {
+                       struct server_id pid = locks[i].pid;
+
+                       if (!process_exists(pid)) {
+                               TALLOC_FREE(locks);
+                               TALLOC_FREE(rec);
+                               status = g_lock_force_unlock(ctx, name, pid);
+                               if (!NT_STATUS_IS_OK(status)) {
+                                       DEBUG(1, ("Could not unlock dead lock "
+                                                 "holder!\n"));
+                                       goto done;
+                               }
+                               goto again;
+                       }
+                       lock_type |= G_LOCK_PENDING;
+               }
+       }
+
+       if (our_index == -1) {
+               /* First round, add ourself */
+
+               locks = g_lock_addrec(talloc_tos(), locks, num_locks,
+                                     self, lock_type);
+               if (locks == NULL) {
+                       DEBUG(10, ("g_lock_addrec failed\n"));
+                       status = NT_STATUS_NO_MEMORY;
+                       goto done;
+               }
+       } else {
+               /*
+                * Retry. We were pending last time. Overwrite the
+                * stored lock_type with what we calculated, we might
+                * have acquired the lock this time.
+                */
+               locks[our_index].lock_type = lock_type;
+       }
+
+       data = make_tdb_data((uint8_t *)locks, talloc_get_size(locks));
+       store_status = rec->store(rec, data, 0);
+       if (!NT_STATUS_IS_OK(store_status)) {
+               DEBUG(1, ("rec->store failed: %s\n",
+                         nt_errstr(store_status)));
+               status = store_status;
+       }
+
+done:
+       TALLOC_FREE(locks);
+       TALLOC_FREE(rec);
+
+       if (NT_STATUS_IS_OK(status) && (lock_type & G_LOCK_PENDING) != 0) {
+               return STATUS_PENDING;
+       }
+
+       return NT_STATUS_OK;
+}
+
+NTSTATUS g_lock_lock(struct g_lock_ctx *ctx, const char *name,
+                    enum g_lock_type lock_type, struct timeval timeout)
+{
+       struct tevent_timer *te = NULL;
+       NTSTATUS status;
+       bool retry = false;
+       bool timedout = false;
+
+       DEBUG(10, ("Trying to acquire lock %d for %s\n", (int)lock_type,
+                  name));
+
+       if (lock_type & ~1) {
+               DEBUG(1, ("Got invalid lock type %d for %s\n",
+                         (int)lock_type, name));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+#ifdef CLUSTER_SUPPORT
+       if (lp_clustering()) {
+               status = ctdb_watch_us(messaging_ctdbd_connection());
+               if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(10, ("could not register retry with ctdb: %s\n",
+                                  nt_errstr(status)));
+                       goto done;
+               }
+       }
+#endif
+
+       status = messaging_register(ctx->msg, &retry, MSG_DBWRAP_G_LOCK_RETRY,
+                                   g_lock_got_retry);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(10, ("messaging_register failed: %s\n",
+                          nt_errstr(status)));
+               return status;
+       }
+again:
+       retry = false;
+
+       status = g_lock_trylock(ctx, name, lock_type);
+       if (NT_STATUS_IS_OK(status)) {
+               DEBUG(10, ("Got lock %s\n", name));
+               goto done;
+       }
+       if (!NT_STATUS_EQUAL(status, STATUS_PENDING)) {
+               DEBUG(10, ("g_lock_trylock failed: %s\n",
+                          nt_errstr(status)));
+               goto done;
+       }
+
+       if (retry) {
+               goto again;
+       }
+
+       DEBUG(10, ("g_lock_trylock: Did not get lock, waiting...\n"));
+
+       if (te == NULL) {
+               te = tevent_add_timer(
+                       ctx->msg->event_ctx, talloc_tos(),
+                       timeval_current_ofs(timeout.tv_sec, timeout.tv_usec),
+                       g_lock_timedout, &timedout);
+               if (te == NULL) {
+                       DEBUG(10, ("tevent_add_timer failed\n"));
+                       status = NT_STATUS_NO_MEMORY;
+                       goto done;
+               }
+       }
+
+       while (true) {
+               if (tevent_loop_once(ctx->msg->event_ctx) == -1) {
+                       DEBUG(1, ("tevent_loop_once failed\n"));
+                       status = NT_STATUS_INTERNAL_ERROR;
+                       goto done;
+               }
+               if (retry) {
+                       goto again;
+               }
+               if (timedout) {
+                       DEBUG(10, ("g_lock_lock timed out\n"));
+
+                       te = NULL;
+
+                       status = NT_STATUS_LOCK_NOT_GRANTED;
+                       goto done;
+               }
+       }
+done:
+
+       if (!NT_STATUS_IS_OK(status)) {
+               NTSTATUS unlock_status;
+
+               unlock_status = g_lock_unlock(ctx, name);
+
+               if (!NT_STATUS_IS_OK(unlock_status)) {
+                       DEBUG(1, ("Could not remove ourself from the locking "
+                                 "db: %s\n", nt_errstr(status)));
+               }
+       }
+
+       messaging_deregister(ctx->msg, MSG_DBWRAP_G_LOCK_RETRY, &retry);
+       TALLOC_FREE(te);
+
+       return status;
+}
+
+static void g_lock_got_retry(struct messaging_context *msg,
+                            void *private_data,
+                            uint32_t msg_type,
+                            struct server_id server_id,
+                            DATA_BLOB *data)
+{
+       bool *pretry = (bool *)private_data;
+
+       DEBUG(10, ("Got retry message from pid %s\n",
+                  procid_str(talloc_tos(), &server_id)));
+
+       *pretry = true;
+}
+
+static void g_lock_timedout(struct tevent_context *ev,
+                           struct tevent_timer *te,
+                           struct timeval current_time,
+                           void *private_data)
+{
+       bool *ptimedout = (bool *)private_data;
+       *ptimedout = true;
+       TALLOC_FREE(te);
+}
+
+static NTSTATUS g_lock_force_unlock(struct g_lock_ctx *ctx, const char *name,
+                                   struct server_id pid)
+{
+       struct db_record *rec = NULL;
+       struct g_lock_rec *locks = NULL;
+       int i, num_locks;
+       enum g_lock_type lock_type;
+       NTSTATUS status;
+
+       rec = ctx->db->fetch_locked(ctx->db, talloc_tos(),
+                                   string_term_tdb_data(name));
+       if (rec == NULL) {
+               DEBUG(10, ("fetch_locked(\"%s\") failed\n", name));
+               status = NT_STATUS_INTERNAL_ERROR;
+               goto done;
+       }
+
+       if (!g_lock_parse(talloc_tos(), rec->value, &num_locks, &locks)) {
+               DEBUG(10, ("g_lock_parse for %s failed\n", name));
+               status = NT_STATUS_INTERNAL_ERROR;
+               goto done;
+       }
+
+       for (i=0; i<num_locks; i++) {
+               if (procid_equal(&pid, &locks[i].pid)) {
+                       break;
+               }
+       }
+
+       if (i == num_locks) {
+               DEBUG(10, ("g_lock_force_unlock: Lock not found\n"));
+               status = NT_STATUS_INTERNAL_ERROR;
+               goto done;
+       }
+
+       lock_type = locks[i].lock_type;
+
+       if (i < (num_locks-1)) {
+               locks[i] = locks[num_locks-1];
+       }
+       num_locks -= 1;
+
+       if (num_locks == 0) {
+               status = rec->delete_rec(rec);
+       } else {
+               TDB_DATA data;
+               data = make_tdb_data((uint8_t *)locks,
+                                    sizeof(struct g_lock_rec) * num_locks);
+               status = rec->store(rec, data, 0);
+       }
+
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(1, ("g_lock_force_unlock: Could not store record: %s\n",
+                         nt_errstr(status)));
+               goto done;
+       }
+
+       if ((lock_type & G_LOCK_PENDING) == 0) {
+               /*
+                * We've been the lock holder. Tell all others to retry.
+                */
+               for (i=0; i<num_locks; i++) {
+                       if ((locks[i].lock_type & G_LOCK_PENDING) == 0) {
+                               continue;
+                       }
+
+                       /*
+                        * Ping all waiters to retry
+                        */
+                       status = messaging_send(ctx->msg, locks[i].pid,
+                                               MSG_DBWRAP_G_LOCK_RETRY,
+                                               &data_blob_null);
+                       if (!NT_STATUS_IS_OK(status)) {
+                               DEBUG(1, ("sending retry to %s failed: %s\n",
+                                         procid_str(talloc_tos(),
+                                                    &locks[i].pid),
+                                         nt_errstr(status)));
+                       }
+               }
+       }
+done:
+
+       TALLOC_FREE(locks);
+       TALLOC_FREE(rec);
+       return status;
+}
+
+NTSTATUS g_lock_unlock(struct g_lock_ctx *ctx, const char *name)
+{
+       NTSTATUS status;
+
+       status = g_lock_force_unlock(ctx, name, procid_self());
+
+#ifdef CLUSTER_SUPPORT
+       if (lp_clustering()) {
+               ctdb_unwatch(messaging_ctdbd_connection());
+       }
+#endif
+       return status;
+}
+
+struct g_lock_locks_state {
+       int (*fn)(const char *name, void *private_data);
+       void *private_data;
+};
+
+static int g_lock_locks_fn(struct db_record *rec, void *priv)
+{
+       struct g_lock_locks_state *state = (struct g_lock_locks_state *)priv;
+
+       if ((rec->key.dsize == 0) || (rec->key.dptr[rec->key.dsize-1] != 0)) {
+               DEBUG(1, ("invalid key in g_lock.tdb, ignoring\n"));
+               return 0;
+       }
+       return state->fn((char *)rec->key.dptr, state->private_data);
+}
+
+int g_lock_locks(struct g_lock_ctx *ctx,
+                int (*fn)(const char *name, void *private_data),
+                void *private_data)
+{
+       struct g_lock_locks_state state;
+
+       state.fn = fn;
+       state.private_data = private_data;
+
+       return ctx->db->traverse_read(ctx->db, g_lock_locks_fn, &state);
+}
+
+NTSTATUS g_lock_dump(struct g_lock_ctx *ctx, const char *name,
+                    int (*fn)(struct server_id pid,
+                              enum g_lock_type lock_type,
+                              void *private_data),
+                    void *private_data)
+{
+       TDB_DATA data;
+       int i, num_locks;
+       struct g_lock_rec *locks = NULL;
+       bool ret;
+
+       if (ctx->db->fetch(ctx->db, talloc_tos(), string_term_tdb_data(name),
+                          &data) != 0) {
+               return NT_STATUS_NOT_FOUND;
+       }
+
+       if ((data.dsize == 0) || (data.dptr == NULL)) {
+               return NT_STATUS_OK;
+       }
+
+       ret = g_lock_parse(talloc_tos(), data, &num_locks, &locks);
+
+       TALLOC_FREE(data.dptr);
+
+       if (!ret) {
+               DEBUG(10, ("g_lock_parse for %s failed\n", name));
+               return NT_STATUS_INTERNAL_ERROR;
+       }
+
+       for (i=0; i<num_locks; i++) {
+               if (fn(locks[i].pid, locks[i].lock_type, private_data) != 0) {
+                       break;
+               }
+       }
+       TALLOC_FREE(locks);
+       return NT_STATUS_OK;
+}
+
+struct g_lock_get_state {
+       bool found;
+       struct server_id *pid;
+};
+
+static int g_lock_get_fn(struct server_id pid, enum g_lock_type lock_type,
+                        void *priv)
+{
+       struct g_lock_get_state *state = (struct g_lock_get_state *)priv;
+
+       if ((lock_type & G_LOCK_PENDING) != 0) {
+               return 0;
+       }
+
+       state->found = true;
+       *state->pid = pid;
+       return 1;
+}
+
+NTSTATUS g_lock_get(struct g_lock_ctx *ctx, const char *name,
+                   struct server_id *pid)
+{
+       struct g_lock_get_state state;
+       NTSTATUS status;
+
+       state.found = false;
+       state.pid = pid;
+
+       status = g_lock_dump(ctx, name, g_lock_get_fn, &state);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+       if (!state.found) {
+               return NT_STATUS_NOT_FOUND;
+       }
+       return NT_STATUS_OK;
+}
index 225440a8fd69c6c0d09a2c40dbae55e25c33373e..1312c845d4b84548031d4ebf84899cec0c9602e1 100644 (file)
@@ -62,7 +62,8 @@ enum messaging_type
        MSG_WINBIND_VALIDATE_CACHE=(int)(0x0408),
        MSG_WINBIND_DUMP_DOMAIN_LIST=(int)(0x0409),
        MSG_DUMP_EVENT_LIST=(int)(0x0500),
-       MSG_DBWRAP_TDB2_CHANGES=(int)(4001)
+       MSG_DBWRAP_TDB2_CHANGES=(int)(4001),
+       MSG_DBWRAP_G_LOCK_RETRY=(int)(4002)
 }
 #else
  { __donnot_use_enum_messaging_type=0x7FFFFFFF}
@@ -118,6 +119,7 @@ enum messaging_type
 #define MSG_WINBIND_DUMP_DOMAIN_LIST ( 0x0409 )
 #define MSG_DUMP_EVENT_LIST ( 0x0500 )
 #define MSG_DBWRAP_TDB2_CHANGES ( 4001 )
+#define MSG_DBWRAP_G_LOCK_RETRY ( 4002 )
 #endif
 ;
 
index 3e2aa1f3b0ff191f06eff3b99703ef8cb69791b8..14526305391ddf86b923bab4ffa23dcb5d89e2b1 100644 (file)
@@ -74,6 +74,7 @@ _PUBLIC_ void ndr_print_messaging_type(struct ndr_print *ndr, const char *name,
                case MSG_WINBIND_DUMP_DOMAIN_LIST: val = "MSG_WINBIND_DUMP_DOMAIN_LIST"; break;
                case MSG_DUMP_EVENT_LIST: val = "MSG_DUMP_EVENT_LIST"; break;
                case MSG_DBWRAP_TDB2_CHANGES: val = "MSG_DBWRAP_TDB2_CHANGES"; break;
+               case MSG_DBWRAP_G_LOCK_RETRY: val = "MSG_DBWRAP_G_LOCK_RETRY"; break;
        }
        ndr_print_enum(ndr, name, "ENUM", val, r);
 }
index 068658587f76bac33a114b5d095faf4e3a5479fb..08caa5950888b2d99510708432edb431212274b7 100644 (file)
@@ -88,7 +88,8 @@ interface messaging
                MSG_DUMP_EVENT_LIST             = 0x0500,
 
                /* dbwrap messages 4001-4999 */
-               MSG_DBWRAP_TDB2_CHANGES         = 4001
+               MSG_DBWRAP_TDB2_CHANGES         = 4001,
+               MSG_DBWRAP_G_LOCK_RETRY         = 4002
        } messaging_type;
 
        /* messaging struct sent across the sockets and stored in the tdb */
index 85c3c7dcedd8ab8fe4daaf6b13ed6242eaf27ca3..0c5f0807fffb0e60ad62d20dfe317457815b6daf 100644 (file)
@@ -612,6 +612,13 @@ static struct functable net_func[] = {
                N_("  Use 'net help lookup' to get more information about 'net "
                   "lookup' commands.")
        },
+       {       "g_lock",
+               net_g_lock,
+               NET_TRANSPORT_LOCAL,
+               N_("Manipulate the global lock table"),
+               N_("  Use 'net help g_lock' to get more information about "
+                  "'net g_lock' commands.")
+       },
        {       "join",
                net_join,
                NET_TRANSPORT_ADS | NET_TRANSPORT_RPC,
diff --git a/source3/utils/net_g_lock.c b/source3/utils/net_g_lock.c
new file mode 100644 (file)
index 0000000..f30ed33
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Interface to the g_lock facility
+ * Copyright (C) Volker Lendecke 2009
+ *
+ * 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 "includes.h"
+#include "net.h"
+#include "g_lock.h"
+
+static bool net_g_lock_init(TALLOC_CTX *mem_ctx,
+                           struct tevent_context **pev,
+                           struct messaging_context **pmsg,
+                           struct g_lock_ctx **pg_ctx)
+{
+       struct tevent_context *ev = NULL;
+       struct messaging_context *msg = NULL;
+       struct g_lock_ctx *g_ctx = NULL;
+
+       ev = tevent_context_init(talloc_tos());
+       if (ev == NULL) {
+               d_fprintf(stderr, "ERROR: could not init event context\n");
+               goto fail;
+       }
+       msg = messaging_init(talloc_tos(), server_id_self(), ev);
+       if (msg == NULL) {
+               d_fprintf(stderr, "ERROR: could not init messaging context\n");
+               goto fail;
+       }
+       g_ctx = g_lock_ctx_init(talloc_tos(), msg);
+       if (g_ctx == NULL) {
+               d_fprintf(stderr, "ERROR: could not init g_lock context\n");
+               goto fail;
+       }
+
+       *pev = ev;
+       *pmsg = msg;
+       *pg_ctx = g_ctx;
+       return true;
+fail:
+       TALLOC_FREE(g_ctx);
+       TALLOC_FREE(msg);
+       TALLOC_FREE(ev);
+       return false;
+}
+
+
+static int net_g_lock_do(struct net_context *c, int argc, const char **argv)
+{
+       struct tevent_context *ev = NULL;
+       struct messaging_context *msg = NULL;
+       struct g_lock_ctx *g_ctx = NULL;
+       const char *name, *cmd;
+       int timeout, res;
+       bool locked = false;
+       NTSTATUS status;
+       int ret = -1;
+
+       if (argc != 3) {
+               d_printf("Usage: net g_lock do <lockname> <timeout> "
+                        "<command>\n");
+               return -1;
+       }
+       name = argv[0];
+       timeout = atoi(argv[1]);
+       cmd = argv[2];
+
+       if (!net_g_lock_init(talloc_tos(), &ev, &msg, &g_ctx)) {
+               goto done;
+       }
+
+       status = g_lock_lock(g_ctx, name, G_LOCK_WRITE,
+                            timeval_set(timeout / 1000, timeout % 1000));
+       if (!NT_STATUS_IS_OK(status)) {
+               d_fprintf(stderr, "ERROR: Could not get lock: %s\n",
+                         nt_errstr(status));
+               goto done;
+       }
+       locked = true;
+
+       res = system(cmd);
+
+       if (res == -1) {
+               d_fprintf(stderr, "ERROR: system() returned %s\n",
+                         strerror(errno));
+               goto done;
+       }
+       d_fprintf(stderr, "command returned %d\n", res);
+
+       ret = 0;
+
+done:
+       if (locked) {
+               g_lock_unlock(g_ctx, name);
+       }
+       TALLOC_FREE(g_ctx);
+       TALLOC_FREE(msg);
+       TALLOC_FREE(ev);
+       return ret;
+}
+
+static int net_g_lock_dump_fn(struct server_id pid, enum g_lock_type lock_type,
+                             void *private_data)
+{
+       char *pidstr;
+
+       pidstr = procid_str(talloc_tos(), &pid);
+       d_printf("%s: %s (%s)\n", pidstr,
+                (lock_type & 1) ? "WRITE" : "READ",
+                (lock_type & G_LOCK_PENDING) ? "pending" : "holder");
+       TALLOC_FREE(pidstr);
+       return 0;
+}
+
+static int net_g_lock_dump(struct net_context *c, int argc, const char **argv)
+{
+       struct tevent_context *ev = NULL;
+       struct messaging_context *msg = NULL;
+       struct g_lock_ctx *g_ctx = NULL;
+       NTSTATUS status;
+       int ret = -1;
+
+       if (argc != 1) {
+               d_printf("Usage: net g_lock dump <lockname>\n");
+               return -1;
+       }
+
+       if (!net_g_lock_init(talloc_tos(), &ev, &msg, &g_ctx)) {
+               goto done;
+       }
+
+       status = g_lock_dump(g_ctx, argv[0], net_g_lock_dump_fn, NULL);
+
+       ret = 0;
+done:
+       TALLOC_FREE(g_ctx);
+       TALLOC_FREE(msg);
+       TALLOC_FREE(ev);
+       return ret;
+}
+
+static int net_g_lock_locks_fn(const char *name, void *private_data)
+{
+       d_printf("%s\n", name);
+       return 0;
+}
+
+static int net_g_lock_locks(struct net_context *c, int argc, const char **argv)
+{
+       struct tevent_context *ev = NULL;
+       struct messaging_context *msg = NULL;
+       struct g_lock_ctx *g_ctx = NULL;
+       int ret = -1;
+
+       if (argc != 0) {
+               d_printf("Usage: net g_lock locks\n");
+               return -1;
+       }
+
+       if (!net_g_lock_init(talloc_tos(), &ev, &msg, &g_ctx)) {
+               goto done;
+       }
+
+       ret = g_lock_locks(g_ctx, net_g_lock_locks_fn, NULL);
+done:
+       TALLOC_FREE(g_ctx);
+       TALLOC_FREE(msg);
+       TALLOC_FREE(ev);
+       return ret;
+}
+
+int net_g_lock(struct net_context *c, int argc, const char **argv)
+{
+       struct functable func[] = {
+               {
+                       "do",
+                       net_g_lock_do,
+                       NET_TRANSPORT_LOCAL,
+                       N_("Execute a shell command under a lock"),
+                       N_("net g_lock do <lock name> <timeout> <command>\n")
+               },
+               {
+                       "locks",
+                       net_g_lock_locks,
+                       NET_TRANSPORT_LOCAL,
+                       N_("List all locknames"),
+                       N_("net g_lock locks\n")
+               },
+               {
+                       "dump",
+                       net_g_lock_dump,
+                       NET_TRANSPORT_LOCAL,
+                       N_("Dump a g_lock locking table"),
+                       N_("net g_lock dump <lock name>\n")
+               },
+               {NULL, NULL, 0, NULL, NULL}
+       };
+
+       return net_run_function(c, argc, argv, "net g_lock", func);
+}
index 098e2a22be6b486db68ed0f891258dc900664b71..540d31e6b17420a2386303ae243353ecf41a68f3 100644 (file)
@@ -497,4 +497,7 @@ NTSTATUS net_lookup_sid_from_name(struct net_context *c, TALLOC_CTX *ctx,
 char *stdin_new_passwd( void);
 char *get_pass( const char *prompt, bool stdin_get);
 
+/* The following definitions come from utils/net_g_lock.c  */
+int net_g_lock(struct net_context *c, int argc, const char **argv);
+
 #endif /*  _NET_PROTO_H_  */