summaryrefslogtreecommitdiffstats
path: root/source4/ntvfs/common
diff options
context:
space:
mode:
Diffstat (limited to 'source4/ntvfs/common')
-rw-r--r--source4/ntvfs/common/brlock.c136
-rw-r--r--source4/ntvfs/common/brlock.h55
-rw-r--r--source4/ntvfs/common/brlock_tdb.c775
-rw-r--r--source4/ntvfs/common/init.c34
-rw-r--r--source4/ntvfs/common/notify.c687
-rw-r--r--source4/ntvfs/common/ntvfs_common.h32
-rw-r--r--source4/ntvfs/common/opendb.c200
-rw-r--r--source4/ntvfs/common/opendb.h59
-rw-r--r--source4/ntvfs/common/opendb_tdb.c886
-rw-r--r--source4/ntvfs/common/wscript_build9
10 files changed, 2873 insertions, 0 deletions
diff --git a/source4/ntvfs/common/brlock.c b/source4/ntvfs/common/brlock.c
new file mode 100644
index 0000000..d99cc4e
--- /dev/null
+++ b/source4/ntvfs/common/brlock.c
@@ -0,0 +1,136 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ generic byte range locking code
+
+ Copyright (C) Andrew Tridgell 1992-2004
+ Copyright (C) Jeremy Allison 1992-2000
+
+ 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/>.
+*/
+
+/* This module implements a tdb based byte range locking service,
+ replacing the fcntl() based byte range locking previously
+ used. This allows us to provide the same semantics as NT */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "messaging/messaging.h"
+#include "lib/messaging/irpc.h"
+#include "libcli/libcli.h"
+#include "cluster/cluster.h"
+#include "ntvfs/common/ntvfs_common.h"
+
+static const struct brlock_ops *ops;
+
+/*
+ set the brl backend ops
+*/
+void brlock_set_ops(const struct brlock_ops *new_ops)
+{
+ ops = new_ops;
+}
+
+/*
+ Open up the brlock database. Close it down using talloc_free(). We
+ need the imessaging_ctx to allow for pending lock notifications.
+*/
+struct brl_context *brlock_init(TALLOC_CTX *mem_ctx, struct server_id server,
+ struct loadparm_context *lp_ctx,
+ struct imessaging_context *imessaging_ctx)
+{
+ if (ops == NULL) {
+ brl_tdb_init_ops();
+ }
+ return ops->brl_init(mem_ctx, server, lp_ctx, imessaging_ctx);
+}
+
+struct brl_handle *brlock_create_handle(TALLOC_CTX *mem_ctx, struct ntvfs_handle *ntvfs, DATA_BLOB *file_key)
+{
+ return ops->brl_create_handle(mem_ctx, ntvfs, file_key);
+}
+
+/*
+ Lock a range of bytes. The lock_type can be a PENDING_*_LOCK, in
+ which case a real lock is first tried, and if that fails then a
+ pending lock is created. When the pending lock is triggered (by
+ someone else closing an overlapping lock range) a messaging
+ notification is sent, identified by the notify_ptr
+*/
+NTSTATUS brlock_lock(struct brl_context *brl,
+ struct brl_handle *brlh,
+ uint32_t smbpid,
+ uint64_t start, uint64_t size,
+ enum brl_type lock_type,
+ void *notify_ptr)
+{
+ return ops->brl_lock(brl, brlh, smbpid, start, size, lock_type, notify_ptr);
+}
+
+
+/*
+ Unlock a range of bytes.
+*/
+NTSTATUS brlock_unlock(struct brl_context *brl,
+ struct brl_handle *brlh,
+ uint32_t smbpid,
+ uint64_t start, uint64_t size)
+{
+ return ops->brl_unlock(brl, brlh, smbpid, start, size);
+}
+
+/*
+ remove a pending lock. This is called when the caller has either
+ given up trying to establish a lock or when they have succeeded in
+ getting it. In either case they no longer need to be notified.
+*/
+NTSTATUS brlock_remove_pending(struct brl_context *brl,
+ struct brl_handle *brlh,
+ void *notify_ptr)
+{
+ return ops->brl_remove_pending(brl, brlh, notify_ptr);
+}
+
+
+/*
+ Test if we are allowed to perform IO on a region of an open file
+*/
+NTSTATUS brlock_locktest(struct brl_context *brl,
+ struct brl_handle *brlh,
+ uint32_t smbpid,
+ uint64_t start, uint64_t size,
+ enum brl_type lock_type)
+{
+ return ops->brl_locktest(brl, brlh, smbpid, start, size, lock_type);
+}
+
+
+/*
+ Remove any locks associated with a open file.
+*/
+NTSTATUS brlock_close(struct brl_context *brl,
+ struct brl_handle *brlh)
+{
+ return ops->brl_close(brl, brlh);
+}
+
+/*
+ Get a number of locks associated with a open file.
+*/
+NTSTATUS brlock_count(struct brl_context *brl,
+ struct brl_handle *brlh,
+ int *count)
+{
+ return ops->brl_count(brl, brlh, count);
+}
diff --git a/source4/ntvfs/common/brlock.h b/source4/ntvfs/common/brlock.h
new file mode 100644
index 0000000..650136b
--- /dev/null
+++ b/source4/ntvfs/common/brlock.h
@@ -0,0 +1,55 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ generic byte range locking code - common include
+
+ Copyright (C) Andrew Tridgell 2006
+
+ 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 "libcli/libcli.h"
+
+struct brlock_ops {
+ struct brl_context *(*brl_init)(TALLOC_CTX *, struct server_id ,
+ struct loadparm_context *lp_ctx,
+ struct imessaging_context *);
+ struct brl_handle *(*brl_create_handle)(TALLOC_CTX *, struct ntvfs_handle *, DATA_BLOB *);
+ NTSTATUS (*brl_lock)(struct brl_context *,
+ struct brl_handle *,
+ uint32_t ,
+ uint64_t , uint64_t ,
+ enum brl_type ,
+ void *);
+ NTSTATUS (*brl_unlock)(struct brl_context *,
+ struct brl_handle *,
+ uint32_t ,
+ uint64_t , uint64_t );
+ NTSTATUS (*brl_remove_pending)(struct brl_context *,
+ struct brl_handle *,
+ void *);
+ NTSTATUS (*brl_locktest)(struct brl_context *,
+ struct brl_handle *,
+ uint32_t ,
+ uint64_t , uint64_t ,
+ enum brl_type );
+ NTSTATUS (*brl_close)(struct brl_context *,
+ struct brl_handle *);
+ NTSTATUS (*brl_count)(struct brl_context *,
+ struct brl_handle *,
+ int *count);
+};
+
+void brlock_set_ops(const struct brlock_ops *new_ops);
+void brl_tdb_init_ops(void);
diff --git a/source4/ntvfs/common/brlock_tdb.c b/source4/ntvfs/common/brlock_tdb.c
new file mode 100644
index 0000000..c4cd76b
--- /dev/null
+++ b/source4/ntvfs/common/brlock_tdb.c
@@ -0,0 +1,775 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ generic byte range locking code - tdb backend
+
+ Copyright (C) Andrew Tridgell 1992-2006
+ Copyright (C) Jeremy Allison 1992-2000
+
+ 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/>.
+*/
+
+/* This module implements a tdb based byte range locking service,
+ replacing the fcntl() based byte range locking previously
+ used. This allows us to provide the same semantics as NT */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "messaging/messaging.h"
+#include "lib/messaging/irpc.h"
+#include "libcli/libcli.h"
+#include "cluster/cluster.h"
+#include "ntvfs/common/brlock.h"
+#include "ntvfs/ntvfs.h"
+#include "param/param.h"
+#include "dbwrap/dbwrap.h"
+
+/*
+ in this module a "DATA_BLOB *file_key" is a blob that uniquely identifies
+ a file. For a local posix filesystem this will usually be a combination
+ of the device and inode numbers of the file, but it can be anything
+ that uniquely identifies a file for locking purposes, as long
+ as it is applied consistently.
+*/
+
+/* this struct is typically attached to tcon */
+struct brl_context {
+ struct db_context *db;
+ struct server_id server;
+ struct imessaging_context *imessaging_ctx;
+};
+
+/*
+ the lock context contains the elements that define whether one
+ lock is the same as another lock
+*/
+struct lock_context {
+ struct server_id server;
+ uint32_t smbpid;
+ struct brl_context *ctx;
+};
+
+/* The data in brlock records is an unsorted linear array of these
+ records. It is unnecessary to store the count as tdb provides the
+ size of the record */
+struct lock_struct {
+ struct lock_context context;
+ struct ntvfs_handle *ntvfs;
+ uint64_t start;
+ uint64_t size;
+ enum brl_type lock_type;
+ void *notify_ptr;
+};
+
+/* this struct is attached to on oprn file handle */
+struct brl_handle {
+ DATA_BLOB key;
+ struct ntvfs_handle *ntvfs;
+ struct lock_struct last_lock;
+};
+
+/* see if we have wrapped locks, which are no longer allowed (windows
+ * changed this in win7 */
+static bool brl_invalid_lock_range(uint64_t start, uint64_t size)
+{
+ return (size > 1 && (start + size < start));
+}
+
+/*
+ Open up the brlock.tdb database. Close it down using
+ talloc_free(). We need the imessaging_ctx to allow for
+ pending lock notifications.
+*/
+static struct brl_context *brl_tdb_init(TALLOC_CTX *mem_ctx, struct server_id server,
+ struct loadparm_context *lp_ctx,
+ struct imessaging_context *imessaging_ctx)
+{
+ struct brl_context *brl;
+
+ brl = talloc(mem_ctx, struct brl_context);
+ if (brl == NULL) {
+ return NULL;
+ }
+
+ brl->db = cluster_db_tmp_open(brl, lp_ctx, "brlock", TDB_DEFAULT);
+ if (brl->db == NULL) {
+ talloc_free(brl);
+ return NULL;
+ }
+
+ brl->server = server;
+ brl->imessaging_ctx = imessaging_ctx;
+
+ return brl;
+}
+
+static struct brl_handle *brl_tdb_create_handle(TALLOC_CTX *mem_ctx, struct ntvfs_handle *ntvfs,
+ DATA_BLOB *file_key)
+{
+ struct brl_handle *brlh;
+
+ brlh = talloc(mem_ctx, struct brl_handle);
+ if (brlh == NULL) {
+ return NULL;
+ }
+
+ brlh->key = *file_key;
+ brlh->ntvfs = ntvfs;
+ ZERO_STRUCT(brlh->last_lock);
+
+ return brlh;
+}
+
+/*
+ see if two locking contexts are equal
+*/
+static bool brl_tdb_same_context(struct lock_context *ctx1, struct lock_context *ctx2)
+{
+ return (cluster_id_equal(&ctx1->server, &ctx2->server) &&
+ ctx1->smbpid == ctx2->smbpid &&
+ ctx1->ctx == ctx2->ctx);
+}
+
+/*
+ see if lck1 and lck2 overlap
+
+ lck1 is the existing lock. lck2 is the new lock we are
+ looking at adding
+*/
+static bool brl_tdb_overlap(struct lock_struct *lck1,
+ struct lock_struct *lck2)
+{
+ /* this extra check is not redundant - it copes with locks
+ that go beyond the end of 64 bit file space */
+ if (lck1->size != 0 &&
+ lck1->start == lck2->start &&
+ lck1->size == lck2->size) {
+ return true;
+ }
+
+ if (lck1->start >= (lck2->start+lck2->size) ||
+ lck2->start >= (lck1->start+lck1->size)) {
+ return false;
+ }
+
+ /* we have a conflict. Now check to see if lck1 really still
+ * exists, which involves checking if the process still
+ * exists. We leave this test to last as its the most
+ * expensive test, especially when we are clustered */
+ /* TODO: need to do this via a server_id_exists() call, which
+ * hasn't been written yet. When clustered this will need to
+ * call into ctdb */
+
+ return true;
+}
+
+/*
+ See if lock2 can be added when lock1 is in place.
+*/
+static bool brl_tdb_conflict(struct lock_struct *lck1,
+ struct lock_struct *lck2)
+{
+ /* pending locks don't conflict with anything */
+ if (lck1->lock_type >= PENDING_READ_LOCK ||
+ lck2->lock_type >= PENDING_READ_LOCK) {
+ return false;
+ }
+
+ if (lck1->lock_type == READ_LOCK && lck2->lock_type == READ_LOCK) {
+ return false;
+ }
+
+ if (brl_tdb_same_context(&lck1->context, &lck2->context) &&
+ lck2->lock_type == READ_LOCK && lck1->ntvfs == lck2->ntvfs) {
+ return false;
+ }
+
+ return brl_tdb_overlap(lck1, lck2);
+}
+
+
+/*
+ Check to see if this lock conflicts, but ignore our own locks on the
+ same fnum only.
+*/
+static bool brl_tdb_conflict_other(struct lock_struct *lck1, struct lock_struct *lck2)
+{
+ /* pending locks don't conflict with anything */
+ if (lck1->lock_type >= PENDING_READ_LOCK ||
+ lck2->lock_type >= PENDING_READ_LOCK) {
+ return false;
+ }
+
+ if (lck1->lock_type == READ_LOCK && lck2->lock_type == READ_LOCK)
+ return false;
+
+ /*
+ * note that incoming write calls conflict with existing READ
+ * locks even if the context is the same. JRA. See LOCKTEST7
+ * in smbtorture.
+ */
+ if (brl_tdb_same_context(&lck1->context, &lck2->context) &&
+ lck1->ntvfs == lck2->ntvfs &&
+ (lck2->lock_type == READ_LOCK || lck1->lock_type == WRITE_LOCK)) {
+ return false;
+ }
+
+ return brl_tdb_overlap(lck1, lck2);
+}
+
+
+/*
+ amazingly enough, w2k3 "remembers" whether the last lock failure
+ is the same as this one and changes its error code. I wonder if any
+ app depends on this?
+*/
+static NTSTATUS brl_tdb_lock_failed(struct brl_handle *brlh, struct lock_struct *lock)
+{
+ /*
+ * this function is only called for non pending lock!
+ */
+
+ /* in SMB2 mode always return NT_STATUS_LOCK_NOT_GRANTED! */
+ if (lock->ntvfs->ctx->protocol >= PROTOCOL_SMB2_02) {
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+
+ /*
+ * if the notify_ptr is non NULL,
+ * it means that we're at the end of a pending lock
+ * and the real lock is requested after the timeout went by
+ * In this case we need to remember the last_lock and always
+ * give FILE_LOCK_CONFLICT
+ */
+ if (lock->notify_ptr) {
+ brlh->last_lock = *lock;
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ /*
+ * amazing the little things you learn with a test
+ * suite. Locks beyond this offset (as a 64 bit
+ * number!) always generate the conflict error code,
+ * unless the top bit is set
+ */
+ if (lock->start >= 0xEF000000 && (lock->start >> 63) == 0) {
+ brlh->last_lock = *lock;
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ /*
+ * if the current lock matches the last failed lock on the file handle
+ * and starts at the same offset, then FILE_LOCK_CONFLICT should be returned
+ */
+ if (cluster_id_equal(&lock->context.server, &brlh->last_lock.context.server) &&
+ lock->context.ctx == brlh->last_lock.context.ctx &&
+ lock->ntvfs == brlh->last_lock.ntvfs &&
+ lock->start == brlh->last_lock.start) {
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ brlh->last_lock = *lock;
+ return NT_STATUS_LOCK_NOT_GRANTED;
+}
+
+/*
+ Lock a range of bytes. The lock_type can be a PENDING_*_LOCK, in
+ which case a real lock is first tried, and if that fails then a
+ pending lock is created. When the pending lock is triggered (by
+ someone else closing an overlapping lock range) a messaging
+ notification is sent, identified by the notify_ptr
+*/
+static NTSTATUS brl_tdb_lock(struct brl_context *brl,
+ struct brl_handle *brlh,
+ uint32_t smbpid,
+ uint64_t start, uint64_t size,
+ enum brl_type lock_type,
+ void *notify_ptr)
+{
+ TDB_DATA kbuf, dbuf;
+ int count=0, i;
+ struct lock_struct lock, *locks=NULL;
+ NTSTATUS status;
+ struct db_record *locked;
+
+ kbuf.dptr = brlh->key.data;
+ kbuf.dsize = brlh->key.length;
+
+ if (brl_invalid_lock_range(start, size)) {
+ return NT_STATUS_INVALID_LOCK_RANGE;
+ }
+
+ locked = dbwrap_fetch_locked(brl->db, brl, kbuf);
+ if (!locked) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ /* if this is a pending lock, then with the chainlock held we
+ try to get the real lock. If we succeed then we don't need
+ to make it pending. This prevents a possible race condition
+ where the pending lock gets created after the lock that is
+ preventing the real lock gets removed */
+ if (lock_type >= PENDING_READ_LOCK) {
+ enum brl_type rw = (lock_type==PENDING_READ_LOCK? READ_LOCK : WRITE_LOCK);
+
+ /* here we need to force that the last_lock isn't overwritten */
+ lock = brlh->last_lock;
+ status = brl_tdb_lock(brl, brlh, smbpid, start, size, rw, NULL);
+ brlh->last_lock = lock;
+
+ if (NT_STATUS_IS_OK(status)) {
+ talloc_free(locked);
+ return NT_STATUS_OK;
+ }
+ }
+
+ dbuf = dbwrap_record_get_value(locked);
+
+ lock.context.smbpid = smbpid;
+ lock.context.server = brl->server;
+ lock.context.ctx = brl;
+ lock.ntvfs = brlh->ntvfs;
+ lock.context.ctx = brl;
+ lock.start = start;
+ lock.size = size;
+ lock.lock_type = lock_type;
+ lock.notify_ptr = notify_ptr;
+
+ if (dbuf.dptr) {
+ /* there are existing locks - make sure they don't conflict */
+ locks = (struct lock_struct *)dbuf.dptr;
+ count = dbuf.dsize / sizeof(*locks);
+ for (i=0; i<count; i++) {
+ if (brl_tdb_conflict(&locks[i], &lock)) {
+ status = brl_tdb_lock_failed(brlh, &lock);
+ goto fail;
+ }
+ }
+ }
+
+ /* no conflicts - add it to the list of locks */
+ /* FIXME: a dbwrap_record_append() would help here! */
+ locks = talloc_array(locked, struct lock_struct, count+1);
+ if (!locks) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ if (dbuf.dsize > 0) {
+ memcpy(locks, dbuf.dptr, dbuf.dsize);
+ }
+ locks[count] = lock;
+
+ dbuf.dptr = (unsigned char *)locks;
+ dbuf.dsize += sizeof(lock);
+
+ status = dbwrap_record_store(locked, dbuf, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ talloc_free(locked);
+
+ /* the caller needs to know if the real lock was granted. If
+ we have reached here then it must be a pending lock that
+ was granted, so tell them the lock failed */
+ if (lock_type >= PENDING_READ_LOCK) {
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+
+ return NT_STATUS_OK;
+
+ fail:
+ talloc_free(locked);
+ return status;
+}
+
+
+/*
+ we are removing a lock that might be holding up a pending lock. Scan for pending
+ locks that cover this range and if we find any then notify the server that it should
+ retry the lock
+*/
+static void brl_tdb_notify_unlock(struct brl_context *brl,
+ struct lock_struct *locks, int count,
+ struct lock_struct *removed_lock)
+{
+ int i, last_notice;
+
+ /* the last_notice logic is to prevent stampeding on a lock
+ range. It prevents us sending hundreds of notifies on the
+ same range of bytes. It doesn't prevent all possible
+ stampedes, but it does prevent the most common problem */
+ last_notice = -1;
+
+ for (i=0;i<count;i++) {
+ if (locks[i].lock_type >= PENDING_READ_LOCK &&
+ brl_tdb_overlap(&locks[i], removed_lock)) {
+ if (last_notice != -1 && brl_tdb_overlap(&locks[i], &locks[last_notice])) {
+ continue;
+ }
+ if (locks[i].lock_type == PENDING_WRITE_LOCK) {
+ last_notice = i;
+ }
+ imessaging_send_ptr(brl->imessaging_ctx, locks[i].context.server,
+ MSG_BRL_RETRY, locks[i].notify_ptr);
+ }
+ }
+}
+
+
+/*
+ send notifications for all pending locks - the file is being closed by this
+ user
+*/
+static void brl_tdb_notify_all(struct brl_context *brl,
+ struct lock_struct *locks, int count)
+{
+ int i;
+ for (i=0;i<count;i++) {
+ if (locks->lock_type >= PENDING_READ_LOCK) {
+ brl_tdb_notify_unlock(brl, locks, count, &locks[i]);
+ }
+ }
+}
+
+
+
+/*
+ Unlock a range of bytes.
+*/
+static NTSTATUS brl_tdb_unlock(struct brl_context *brl,
+ struct brl_handle *brlh,
+ uint32_t smbpid,
+ uint64_t start, uint64_t size)
+{
+ TDB_DATA kbuf, dbuf;
+ int count, i;
+ struct lock_struct *locks, *lock = NULL;
+ struct lock_context context;
+ struct db_record *locked;
+ NTSTATUS status;
+
+ kbuf.dptr = brlh->key.data;
+ kbuf.dsize = brlh->key.length;
+
+ if (brl_invalid_lock_range(start, size)) {
+ return NT_STATUS_INVALID_LOCK_RANGE;
+ }
+
+ locked = dbwrap_fetch_locked(brl->db, brl, kbuf);
+ if (!locked) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ dbuf = dbwrap_record_get_value(locked);
+
+ context.smbpid = smbpid;
+ context.server = brl->server;
+ context.ctx = brl;
+
+ /* there are existing locks - find a match */
+ locks = (struct lock_struct *)dbuf.dptr;
+ count = dbuf.dsize / sizeof(*locks);
+
+ for (i=0; i<count; i++) {
+ lock = &locks[i];
+ if (brl_tdb_same_context(&lock->context, &context) &&
+ lock->ntvfs == brlh->ntvfs &&
+ lock->start == start &&
+ lock->size == size &&
+ lock->lock_type == WRITE_LOCK) {
+ break;
+ }
+ }
+ if (i < count) goto found;
+
+ for (i=0; i<count; i++) {
+ lock = &locks[i];
+ if (brl_tdb_same_context(&lock->context, &context) &&
+ lock->ntvfs == brlh->ntvfs &&
+ lock->start == start &&
+ lock->size == size &&
+ lock->lock_type < PENDING_READ_LOCK) {
+ break;
+ }
+ }
+
+found:
+ if (i < count) {
+ /* found it - delete it */
+ if (count == 1) {
+ status = dbwrap_record_delete(locked);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ } else {
+ struct lock_struct removed_lock = *lock;
+ if (i < count-1) {
+ memmove(&locks[i], &locks[i+1],
+ sizeof(*locks)*((count-1) - i));
+ }
+ count--;
+
+ /* send notifications for any relevant pending locks */
+ brl_tdb_notify_unlock(brl, locks, count, &removed_lock);
+
+ dbuf.dsize = count * sizeof(*locks);
+
+ status = dbwrap_record_store(locked, dbuf, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ talloc_free(locked);
+ return NT_STATUS_OK;
+ }
+
+ /* we didn't find it */
+ status = NT_STATUS_RANGE_NOT_LOCKED;
+
+ fail:
+ talloc_free(locked);
+ return status;
+}
+
+
+/*
+ remove a pending lock. This is called when the caller has either
+ given up trying to establish a lock or when they have succeeded in
+ getting it. In either case they no longer need to be notified.
+*/
+static NTSTATUS brl_tdb_remove_pending(struct brl_context *brl,
+ struct brl_handle *brlh,
+ void *notify_ptr)
+{
+ TDB_DATA kbuf, dbuf;
+ int count, i;
+ struct lock_struct *locks;
+ NTSTATUS status;
+ struct db_record *locked;
+
+ kbuf.dptr = brlh->key.data;
+ kbuf.dsize = brlh->key.length;
+
+ locked = dbwrap_fetch_locked(brl->db, brl, kbuf);
+ if (!locked) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ dbuf = dbwrap_record_get_value(locked);
+ if (!dbuf.dptr) {
+ talloc_free(locked);
+ return NT_STATUS_RANGE_NOT_LOCKED;
+ }
+
+ /* there are existing locks - find a match */
+ locks = (struct lock_struct *)dbuf.dptr;
+ count = dbuf.dsize / sizeof(*locks);
+
+ for (i=0; i<count; i++) {
+ struct lock_struct *lock = &locks[i];
+
+ if (lock->lock_type >= PENDING_READ_LOCK &&
+ lock->notify_ptr == notify_ptr &&
+ cluster_id_equal(&lock->context.server, &brl->server)) {
+ /* found it - delete it */
+ if (count == 1) {
+ status = dbwrap_record_delete(locked);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ } else {
+ if (i < count-1) {
+ memmove(&locks[i], &locks[i+1],
+ sizeof(*locks)*((count-1) - i));
+ }
+ count--;
+ dbuf.dsize = count * sizeof(*locks);
+ status = dbwrap_record_store(locked, dbuf,
+ TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ talloc_free(locked);
+ return NT_STATUS_OK;
+ }
+ }
+
+ /* we didn't find it */
+ status = NT_STATUS_RANGE_NOT_LOCKED;
+
+ fail:
+ talloc_free(locked);
+ return status;
+}
+
+
+/*
+ Test if we are allowed to perform IO on a region of an open file
+*/
+static NTSTATUS brl_tdb_locktest(struct brl_context *brl,
+ struct brl_handle *brlh,
+ uint32_t smbpid,
+ uint64_t start, uint64_t size,
+ enum brl_type lock_type)
+{
+ TDB_DATA kbuf, dbuf;
+ int count, i;
+ struct lock_struct lock, *locks;
+ NTSTATUS status;
+
+ kbuf.dptr = brlh->key.data;
+ kbuf.dsize = brlh->key.length;
+
+ if (brl_invalid_lock_range(start, size)) {
+ return NT_STATUS_INVALID_LOCK_RANGE;
+ }
+
+ status = dbwrap_fetch(brl->db, brl, kbuf, &dbuf);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return NT_STATUS_OK;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ lock.context.smbpid = smbpid;
+ lock.context.server = brl->server;
+ lock.context.ctx = brl;
+ lock.ntvfs = brlh->ntvfs;
+ lock.start = start;
+ lock.size = size;
+ lock.lock_type = lock_type;
+
+ /* there are existing locks - make sure they don't conflict */
+ locks = (struct lock_struct *)dbuf.dptr;
+ count = dbuf.dsize / sizeof(*locks);
+
+ for (i=0; i<count; i++) {
+ if (brl_tdb_conflict_other(&locks[i], &lock)) {
+ talloc_free(dbuf.dptr);
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+ }
+
+ talloc_free(dbuf.dptr);
+ return NT_STATUS_OK;
+}
+
+
+/*
+ Remove any locks associated with a open file.
+*/
+static NTSTATUS brl_tdb_close(struct brl_context *brl,
+ struct brl_handle *brlh)
+{
+ TDB_DATA kbuf, dbuf;
+ int count, i, dcount=0;
+ struct lock_struct *locks;
+ struct db_record *locked;
+ NTSTATUS status;
+
+ kbuf.dptr = brlh->key.data;
+ kbuf.dsize = brlh->key.length;
+
+ locked = dbwrap_fetch_locked(brl->db, brl, kbuf);
+ if (!locked) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ dbuf = dbwrap_record_get_value(locked);
+ if (!dbuf.dptr) {
+ talloc_free(locked);
+ return NT_STATUS_OK;
+ }
+
+ /* there are existing locks - remove any for this fnum */
+ locks = (struct lock_struct *)dbuf.dptr;
+ count = dbuf.dsize / sizeof(*locks);
+
+ for (i=0; i<count; i++) {
+ struct lock_struct *lock = &locks[i];
+
+ if (lock->context.ctx == brl &&
+ cluster_id_equal(&lock->context.server, &brl->server) &&
+ lock->ntvfs == brlh->ntvfs) {
+ /* found it - delete it */
+ if (count > 1 && i < count-1) {
+ memmove(&locks[i], &locks[i+1],
+ sizeof(*locks)*((count-1) - i));
+ }
+ count--;
+ i--;
+ dcount++;
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+ if (count == 0) {
+ status = dbwrap_record_delete(locked);
+ } else if (dcount != 0) {
+ /* tell all pending lock holders for this file that
+ they have a chance now. This is a bit indiscriminant,
+ but works OK */
+ brl_tdb_notify_all(brl, locks, count);
+
+ dbuf.dsize = count * sizeof(*locks);
+
+ status = dbwrap_record_store(locked, dbuf, TDB_REPLACE);
+ }
+ talloc_free(locked);
+
+ return status;
+}
+
+static NTSTATUS brl_tdb_count(struct brl_context *brl, struct brl_handle *brlh,
+ int *count)
+{
+ TDB_DATA kbuf, dbuf;
+ NTSTATUS status;
+
+ kbuf.dptr = brlh->key.data;
+ kbuf.dsize = brlh->key.length;
+ *count = 0;
+
+ status = dbwrap_fetch(brl->db, brl, kbuf, &dbuf);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return NT_STATUS_OK;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *count = dbuf.dsize / sizeof(struct lock_struct);
+
+ talloc_free(dbuf.dptr);
+
+ return NT_STATUS_OK;
+}
+
+static const struct brlock_ops brlock_tdb_ops = {
+ .brl_init = brl_tdb_init,
+ .brl_create_handle = brl_tdb_create_handle,
+ .brl_lock = brl_tdb_lock,
+ .brl_unlock = brl_tdb_unlock,
+ .brl_remove_pending = brl_tdb_remove_pending,
+ .brl_locktest = brl_tdb_locktest,
+ .brl_close = brl_tdb_close,
+ .brl_count = brl_tdb_count
+};
+
+
+void brl_tdb_init_ops(void)
+{
+ brlock_set_ops(&brlock_tdb_ops);
+}
diff --git a/source4/ntvfs/common/init.c b/source4/ntvfs/common/init.c
new file mode 100644
index 0000000..f8f8e27
--- /dev/null
+++ b/source4/ntvfs/common/init.c
@@ -0,0 +1,34 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2006
+
+ 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/>.
+*/
+
+/*
+ this is the change notify database. It implements mechanisms for
+ storing current change notify waiters in a tdb, and checking if a
+ given event matches any of the stored notify waiiters.
+*/
+
+#include "includes.h"
+#include "ntvfs/sysdep/sys_notify.h"
+
+NTSTATUS ntvfs_common_init(void);
+
+NTSTATUS ntvfs_common_init(void)
+{
+ return sys_notify_init();
+}
diff --git a/source4/ntvfs/common/notify.c b/source4/ntvfs/common/notify.c
new file mode 100644
index 0000000..a434fab
--- /dev/null
+++ b/source4/ntvfs/common/notify.c
@@ -0,0 +1,687 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Tridgell 2006
+
+ 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/>.
+*/
+
+/*
+ this is the change notify database. It implements mechanisms for
+ storing current change notify waiters in a tdb, and checking if a
+ given event matches any of the stored notify waiiters.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "messaging/messaging.h"
+#include "lib/messaging/irpc.h"
+#include "librpc/gen_ndr/ndr_notify.h"
+#include "../lib/util/dlinklist.h"
+#include "ntvfs/common/ntvfs_common.h"
+#include "ntvfs/sysdep/sys_notify.h"
+#include "cluster/cluster.h"
+#include "param/param.h"
+#include "lib/util/tsort.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "../lib/util/util_tdb.h"
+
+struct notify_context {
+ struct db_context *db;
+ struct server_id server;
+ struct imessaging_context *imessaging_ctx;
+ struct notify_list *list;
+ struct notify_array *array;
+ int64_t seqnum;
+ struct sys_notify_context *sys_notify_ctx;
+};
+
+
+struct notify_list {
+ struct notify_list *next, *prev;
+ void *private_data;
+ void (*callback)(void *, const struct notify_event *);
+ void *sys_notify_handle;
+ int depth;
+};
+
+#define NOTIFY_KEY "notify array"
+
+#define NOTIFY_ENABLE "notify:enable"
+#define NOTIFY_ENABLE_DEFAULT true
+
+static NTSTATUS notify_remove_all(struct notify_context *notify);
+static void notify_handler(struct imessaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ size_t num_fds,
+ int *fds,
+ DATA_BLOB *data);
+
+/*
+ destroy the notify context
+*/
+static int notify_destructor(struct notify_context *notify)
+{
+ imessaging_deregister(notify->imessaging_ctx, MSG_PVFS_NOTIFY, notify);
+ notify_remove_all(notify);
+ return 0;
+}
+
+/*
+ Open up the notify.tdb database. You should close it down using
+ talloc_free(). We need the imessaging_ctx to allow for notifications
+ via internal messages
+*/
+struct notify_context *notify_init(TALLOC_CTX *mem_ctx, struct server_id server,
+ struct imessaging_context *imessaging_ctx,
+ struct loadparm_context *lp_ctx,
+ struct tevent_context *ev,
+ struct share_config *scfg)
+{
+ struct notify_context *notify;
+
+ if (share_bool_option(scfg, NOTIFY_ENABLE, NOTIFY_ENABLE_DEFAULT) != true) {
+ return NULL;
+ }
+
+ if (ev == NULL) {
+ return NULL;
+ }
+
+ notify = talloc(mem_ctx, struct notify_context);
+ if (notify == NULL) {
+ return NULL;
+ }
+
+ notify->db = cluster_db_tmp_open(notify, lp_ctx, "notify", TDB_SEQNUM);
+ if (notify->db == NULL) {
+ talloc_free(notify);
+ return NULL;
+ }
+
+ notify->server = server;
+ notify->imessaging_ctx = imessaging_ctx;
+ notify->list = NULL;
+ notify->array = NULL;
+ notify->seqnum = dbwrap_get_seqnum(notify->db);
+
+ talloc_set_destructor(notify, notify_destructor);
+
+ /* register with the messaging subsystem for the notify
+ message type */
+ imessaging_register(notify->imessaging_ctx, notify,
+ MSG_PVFS_NOTIFY, notify_handler);
+
+ notify->sys_notify_ctx = sys_notify_context_create(scfg, notify, ev);
+
+ return notify;
+}
+
+
+/*
+ lock the notify db
+*/
+static struct db_record *notify_lock(struct notify_context *notify)
+{
+ TDB_DATA key = string_term_tdb_data(NOTIFY_KEY);
+
+ return dbwrap_fetch_locked(notify->db, notify, key);
+}
+
+static void notify_unlock(struct db_record *lock)
+{
+ talloc_free(lock);
+}
+
+/*
+ load the notify array
+*/
+static NTSTATUS notify_load(struct notify_context *notify)
+{
+ TDB_DATA dbuf;
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ int seqnum;
+ NTSTATUS status;
+
+ seqnum = dbwrap_get_seqnum(notify->db);
+
+ if (seqnum == notify->seqnum && notify->array != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ notify->seqnum = seqnum;
+
+ talloc_free(notify->array);
+ notify->array = talloc_zero(notify, struct notify_array);
+ NT_STATUS_HAVE_NO_MEMORY(notify->array);
+
+ status = dbwrap_fetch_bystring(notify->db, notify, NOTIFY_KEY, &dbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OK;
+ }
+
+ blob.data = dbuf.dptr;
+ blob.length = dbuf.dsize;
+
+ ndr_err = ndr_pull_struct_blob(&blob, notify->array, notify->array,
+ (ndr_pull_flags_fn_t)ndr_pull_notify_array);
+ talloc_free(dbuf.dptr);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ compare notify entries for sorting
+*/
+static int notify_compare(const void *p1, const void *p2)
+{
+ const struct notify_entry *e1 = p1, *e2 = p2;
+ return strcmp(e1->path, e2->path);
+}
+
+/*
+ save the notify array
+*/
+static NTSTATUS notify_save(struct notify_context *notify)
+{
+ TDB_DATA dbuf;
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+
+ /* if possible, remove some depth arrays */
+ while (notify->array->num_depths > 0 &&
+ notify->array->depth[notify->array->num_depths-1].num_entries == 0) {
+ notify->array->num_depths--;
+ }
+
+ /* we might just be able to delete the record */
+ if (notify->array->num_depths == 0) {
+ return dbwrap_delete_bystring(notify->db, NOTIFY_KEY);
+ }
+
+ tmp_ctx = talloc_new(notify);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ ndr_err = ndr_push_struct_blob(&blob, tmp_ctx, notify->array,
+ (ndr_push_flags_fn_t)ndr_push_notify_array);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(tmp_ctx);
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ dbuf.dptr = blob.data;
+ dbuf.dsize = blob.length;
+
+ status = dbwrap_store_bystring(notify->db, NOTIFY_KEY, dbuf,
+ TDB_REPLACE);
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+
+/*
+ handle incoming notify messages
+*/
+static void notify_handler(struct imessaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ size_t num_fds,
+ int *fds,
+ DATA_BLOB *data)
+{
+ struct notify_context *notify = talloc_get_type(private_data, struct notify_context);
+ enum ndr_err_code ndr_err;
+ struct notify_event ev;
+ TALLOC_CTX *tmp_ctx = talloc_new(notify);
+ struct notify_list *listel;
+
+ if (num_fds != 0) {
+ DBG_WARNING("Received %zu fds, ignoring message\n", num_fds);
+ return;
+ }
+
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob(data, tmp_ctx, &ev,
+ (ndr_pull_flags_fn_t)ndr_pull_notify_event);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ for (listel=notify->list;listel;listel=listel->next) {
+ if (listel->private_data == ev.private_data) {
+ listel->callback(listel->private_data, &ev);
+ break;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ callback from sys_notify telling us about changes from the OS
+*/
+static void sys_notify_callback(struct sys_notify_context *ctx,
+ void *ptr, struct notify_event *ev)
+{
+ struct notify_list *listel = talloc_get_type(ptr, struct notify_list);
+ ev->private_data = listel;
+ listel->callback(listel->private_data, ev);
+}
+
+/*
+ add an entry to the notify array
+*/
+static NTSTATUS notify_add_array(struct notify_context *notify, struct notify_entry *e,
+ void *private_data, int depth)
+{
+ int i;
+ struct notify_depth *d;
+ struct notify_entry *ee;
+
+ /* possibly expand the depths array */
+ if (depth >= notify->array->num_depths) {
+ d = talloc_realloc(notify->array, notify->array->depth,
+ struct notify_depth, depth+1);
+ NT_STATUS_HAVE_NO_MEMORY(d);
+ for (i=notify->array->num_depths;i<=depth;i++) {
+ ZERO_STRUCT(d[i]);
+ }
+ notify->array->depth = d;
+ notify->array->num_depths = depth+1;
+ }
+ d = &notify->array->depth[depth];
+
+ /* expand the entries array */
+ ee = talloc_realloc(notify->array->depth, d->entries, struct notify_entry,
+ d->num_entries+1);
+ NT_STATUS_HAVE_NO_MEMORY(ee);
+ d->entries = ee;
+
+ d->entries[d->num_entries] = *e;
+ d->entries[d->num_entries].private_data = private_data;
+ d->entries[d->num_entries].server = notify->server;
+ d->entries[d->num_entries].path_len = strlen(e->path);
+ d->num_entries++;
+
+ d->max_mask |= e->filter;
+ d->max_mask_subdir |= e->subdir_filter;
+
+ TYPESAFE_QSORT(d->entries, d->num_entries, notify_compare);
+
+ /* recalculate the maximum masks */
+ d->max_mask = 0;
+ d->max_mask_subdir = 0;
+
+ for (i=0;i<d->num_entries;i++) {
+ d->max_mask |= d->entries[i].filter;
+ d->max_mask_subdir |= d->entries[i].subdir_filter;
+ }
+
+ return notify_save(notify);
+}
+
+/*
+ add a notify watch. This is called when a notify is first setup on a open
+ directory handle.
+*/
+NTSTATUS notify_add(struct notify_context *notify, struct notify_entry *e0,
+ void (*callback)(void *, const struct notify_event *),
+ void *private_data)
+{
+ struct notify_entry e = *e0;
+ NTSTATUS status;
+ char *tmp_path = NULL;
+ struct notify_list *listel;
+ size_t len;
+ int depth;
+ struct db_record *locked;
+
+ /* see if change notify is enabled at all */
+ if (notify == NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ locked = notify_lock(notify);
+ if (!locked) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ status = notify_load(notify);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* cope with /. on the end of the path */
+ len = strlen(e.path);
+ if (len > 1 && e.path[len-1] == '.' && e.path[len-2] == '/') {
+ tmp_path = talloc_strndup(notify, e.path, len-2);
+ if (tmp_path == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ e.path = tmp_path;
+ }
+
+ depth = count_chars(e.path, '/');
+
+ listel = talloc_zero(notify, struct notify_list);
+ if (listel == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ listel->private_data = private_data;
+ listel->callback = callback;
+ listel->depth = depth;
+ DLIST_ADD(notify->list, listel);
+
+ /* ignore failures from sys_notify */
+ if (notify->sys_notify_ctx != NULL) {
+ /*
+ this call will modify e.filter and e.subdir_filter
+ to remove bits handled by the backend
+ */
+ status = sys_notify_watch(notify->sys_notify_ctx, &e,
+ sys_notify_callback, listel,
+ &listel->sys_notify_handle);
+ if (NT_STATUS_IS_OK(status)) {
+ talloc_steal(listel, listel->sys_notify_handle);
+ }
+ }
+
+ /* if the system notify handler couldn't handle some of the
+ filter bits, or couldn't handle a request for recursion
+ then we need to install it in the array used for the
+ intra-samba notify handling */
+ if (e.filter != 0 || e.subdir_filter != 0) {
+ status = notify_add_array(notify, &e, private_data, depth);
+ }
+
+done:
+ notify_unlock(locked);
+ talloc_free(tmp_path);
+
+ return status;
+}
+
+/*
+ remove a notify watch. Called when the directory handle is closed
+*/
+NTSTATUS notify_remove(struct notify_context *notify, void *private_data)
+{
+ NTSTATUS status;
+ struct notify_list *listel;
+ int i, depth;
+ struct notify_depth *d;
+ struct db_record *locked;
+
+ /* see if change notify is enabled at all */
+ if (notify == NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ for (listel=notify->list;listel;listel=listel->next) {
+ if (listel->private_data == private_data) {
+ DLIST_REMOVE(notify->list, listel);
+ break;
+ }
+ }
+ if (listel == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ depth = listel->depth;
+
+ talloc_free(listel);
+
+ locked = notify_lock(notify);
+ if (!locked) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ status = notify_load(notify);
+ if (!NT_STATUS_IS_OK(status)) {
+ notify_unlock(locked);
+ return status;
+ }
+
+ if (depth >= notify->array->num_depths) {
+ notify_unlock(locked);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* we only have to search at the depth of this element */
+ d = &notify->array->depth[depth];
+
+ for (i=0;i<d->num_entries;i++) {
+ if (private_data == d->entries[i].private_data &&
+ cluster_id_equal(&notify->server, &d->entries[i].server)) {
+ break;
+ }
+ }
+ if (i == d->num_entries) {
+ notify_unlock(locked);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (i < d->num_entries-1) {
+ memmove(&d->entries[i], &d->entries[i+1],
+ sizeof(d->entries[i])*(d->num_entries-(i+1)));
+ }
+ d->num_entries--;
+
+ status = notify_save(notify);
+
+ notify_unlock(locked);
+
+ return status;
+}
+
+/*
+ remove all notify watches for this messaging server
+*/
+static NTSTATUS notify_remove_all(struct notify_context *notify)
+{
+ NTSTATUS status;
+ int i, depth, del_count=0;
+ struct db_record *locked;
+
+ if (notify->list == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ locked = notify_lock(notify);
+ if (!locked) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ status = notify_load(notify);
+ if (!NT_STATUS_IS_OK(status)) {
+ notify_unlock(locked);
+ return status;
+ }
+
+ /* we have to search for all entries across all depths, looking for matches
+ for our server id */
+ for (depth=0;depth<notify->array->num_depths;depth++) {
+ struct notify_depth *d = &notify->array->depth[depth];
+ for (i=0;i<d->num_entries;i++) {
+ if (cluster_id_equal(&notify->server, &d->entries[i].server)) {
+ if (i < d->num_entries-1) {
+ memmove(&d->entries[i], &d->entries[i+1],
+ sizeof(d->entries[i])*(d->num_entries-(i+1)));
+ }
+ i--;
+ d->num_entries--;
+ del_count++;
+ }
+ }
+ }
+
+ if (del_count > 0) {
+ status = notify_save(notify);
+ }
+
+ notify_unlock(locked);
+
+ return status;
+}
+
+
+/*
+ send a notify message to another messaging server
+*/
+static void notify_send(struct notify_context *notify, struct notify_entry *e,
+ const char *path, uint32_t action)
+{
+ struct notify_event ev;
+ DATA_BLOB data;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *tmp_ctx;
+
+ ev.action = action;
+ ev.dir = discard_const_p(char, "");
+ ev.path = path;
+ ev.private_data = e->private_data;
+
+ tmp_ctx = talloc_new(notify);
+
+ ndr_err = ndr_push_struct_blob(&data, tmp_ctx, &ev, (ndr_push_flags_fn_t)ndr_push_notify_event);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ status = imessaging_send(notify->imessaging_ctx, e->server,
+ MSG_PVFS_NOTIFY, &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+
+ talloc_free(tmp_ctx);
+}
+
+
+/*
+ trigger a notify message for anyone waiting on a matching event
+
+ This function is called a lot, and needs to be very fast. The unusual data structure
+ and traversal is designed to be fast in the average case, even for large numbers of
+ notifies
+*/
+void notify_trigger(struct notify_context *notify,
+ uint32_t action, uint32_t filter, const char *path)
+{
+ NTSTATUS status;
+ int depth;
+ const char *p, *next_p;
+
+ /* see if change notify is enabled at all */
+ if (notify == NULL) {
+ return;
+ }
+
+ status = notify_load(notify);
+ if (!NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+ /* loop along the given path, working with each directory depth separately */
+ for (depth=0,p=path;
+ p && depth < notify->array->num_depths;
+ p=next_p,depth++) {
+ int p_len = p - path;
+ int min_i, max_i, i;
+ struct notify_depth *d = &notify->array->depth[depth];
+ next_p = strchr(p+1, '/');
+
+ /* see if there are any entries at this depth */
+ if (d->num_entries == 0) continue;
+
+ /* try to skip based on the maximum mask. If next_p is
+ NULL then we know it will be a 'this directory'
+ match, otherwise it must be a subdir match */
+ if (next_p != NULL) {
+ if (0 == (filter & d->max_mask_subdir)) {
+ continue;
+ }
+ } else {
+ if (0 == (filter & d->max_mask)) {
+ continue;
+ }
+ }
+
+ /* we know there is an entry here worth looking
+ for. Use a bisection search to find the first entry
+ with a matching path */
+ min_i = 0;
+ max_i = d->num_entries-1;
+
+ while (min_i < max_i) {
+ struct notify_entry *e;
+ int cmp;
+ i = (min_i+max_i)/2;
+ e = &d->entries[i];
+ cmp = strncmp(path, e->path, p_len);
+ if (cmp == 0) {
+ if (p_len == e->path_len) {
+ max_i = i;
+ } else {
+ max_i = i-1;
+ }
+ } else if (cmp < 0) {
+ max_i = i-1;
+ } else {
+ min_i = i+1;
+ }
+ }
+
+ if (min_i != max_i) {
+ /* none match */
+ continue;
+ }
+
+ /* we now know that the entries start at min_i */
+ for (i=min_i;i<d->num_entries;i++) {
+ struct notify_entry *e = &d->entries[i];
+ if (p_len != e->path_len ||
+ strncmp(path, e->path, p_len) != 0) break;
+ if (next_p != NULL) {
+ if (0 == (filter & e->subdir_filter)) {
+ continue;
+ }
+ } else {
+ if (0 == (filter & e->filter)) {
+ continue;
+ }
+ }
+ notify_send(notify, e, path + e->path_len + 1, action);
+ }
+ }
+}
diff --git a/source4/ntvfs/common/ntvfs_common.h b/source4/ntvfs/common/ntvfs_common.h
new file mode 100644
index 0000000..37dd553
--- /dev/null
+++ b/source4/ntvfs/common/ntvfs_common.h
@@ -0,0 +1,32 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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 _NTVFS_COMMON_H_
+#define _NTVFS_COMMON_H_
+
+#include "ntvfs/ntvfs.h"
+
+struct notify_event;
+struct notify_entry;
+
+#include "ntvfs/common/brlock.h"
+#include "ntvfs/common/opendb.h"
+#include "ntvfs/common/proto.h"
+
+#endif /* _NTVFS_COMMON_H_ */
diff --git a/source4/ntvfs/common/opendb.c b/source4/ntvfs/common/opendb.c
new file mode 100644
index 0000000..29081ef
--- /dev/null
+++ b/source4/ntvfs/common/opendb.c
@@ -0,0 +1,200 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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/>.
+*/
+
+/*
+ this is the open files database. It implements shared storage of
+ what files are open between server instances, and implements the rules
+ of shared access to files.
+
+ The caller needs to provide a file_key, which specifies what file
+ they are talking about. This needs to be a unique key across all
+ filesystems, and is usually implemented in terms of a device/inode
+ pair.
+
+ Before any operations can be performed the caller needs to establish
+ a lock on the record associated with file_key. That is done by
+ calling odb_lock(). The caller releases this lock by calling
+ talloc_free() on the returned handle.
+
+ All other operations on a record are done by passing the odb_lock()
+ handle back to this module. The handle contains internal
+ information about what file_key is being operated on.
+*/
+
+#include "includes.h"
+#include "ntvfs/ntvfs.h"
+#include "ntvfs/common/ntvfs_common.h"
+#include "cluster/cluster.h"
+#include "param/param.h"
+
+static const struct opendb_ops *ops;
+
+/*
+ set the odb backend ops
+*/
+void odb_set_ops(const struct opendb_ops *new_ops)
+{
+ ops = new_ops;
+}
+
+/*
+ Open up the openfiles.tdb database. Close it down using
+ talloc_free(). We need the imessaging_ctx to allow for pending open
+ notifications.
+*/
+struct odb_context *odb_init(TALLOC_CTX *mem_ctx,
+ struct ntvfs_context *ntvfs_ctx)
+{
+ if (ops == NULL) {
+ odb_tdb_init_ops();
+ }
+ return ops->odb_init(mem_ctx, ntvfs_ctx);
+}
+
+/*
+ get a lock on a entry in the odb. This call returns a lock handle,
+ which the caller should unlock using talloc_free().
+*/
+struct odb_lock *odb_lock(TALLOC_CTX *mem_ctx,
+ struct odb_context *odb, DATA_BLOB *file_key)
+{
+ return ops->odb_lock(mem_ctx, odb, file_key);
+}
+
+DATA_BLOB odb_get_key(TALLOC_CTX *mem_ctx, struct odb_lock *lck)
+{
+ return ops->odb_get_key(mem_ctx, lck);
+}
+
+/*
+ register an open file in the open files database.
+ The share_access rules are implemented by odb_can_open()
+ and it's needed to call odb_can_open() before
+ odb_open_file() otherwise NT_STATUS_INTERNAL_ERROR is returned
+
+ Note that the path is only used by the delete on close logic, not
+ for comparing with other filenames
+*/
+NTSTATUS odb_open_file(struct odb_lock *lck,
+ void *file_handle, const char *path,
+ int *fd, NTTIME open_write_time,
+ bool allow_level_II_oplock,
+ uint32_t oplock_level, uint32_t *oplock_granted)
+{
+ return ops->odb_open_file(lck, file_handle, path,
+ fd, open_write_time,
+ allow_level_II_oplock,
+ oplock_level, oplock_granted);
+}
+
+
+/*
+ register a pending open file in the open files database
+*/
+NTSTATUS odb_open_file_pending(struct odb_lock *lck, void *private_data)
+{
+ return ops->odb_open_file_pending(lck, private_data);
+}
+
+
+/*
+ remove a opendb entry
+*/
+NTSTATUS odb_close_file(struct odb_lock *lck, void *file_handle,
+ const char **delete_path)
+{
+ return ops->odb_close_file(lck, file_handle, delete_path);
+}
+
+
+/*
+ remove a pending opendb entry
+*/
+NTSTATUS odb_remove_pending(struct odb_lock *lck, void *private_data)
+{
+ return ops->odb_remove_pending(lck, private_data);
+}
+
+
+/*
+ rename the path in a open file
+*/
+NTSTATUS odb_rename(struct odb_lock *lck, const char *path)
+{
+ return ops->odb_rename(lck, path);
+}
+
+/*
+ get back the path of an open file
+*/
+NTSTATUS odb_get_path(struct odb_lock *lck, const char **path)
+{
+ return ops->odb_get_path(lck, path);
+}
+
+/*
+ update delete on close flag on an open file
+*/
+NTSTATUS odb_set_delete_on_close(struct odb_lock *lck, bool del_on_close)
+{
+ return ops->odb_set_delete_on_close(lck, del_on_close);
+}
+
+/*
+ update the write time on an open file
+*/
+NTSTATUS odb_set_write_time(struct odb_lock *lck,
+ NTTIME write_time, bool force)
+{
+ return ops->odb_set_write_time(lck, write_time, force);
+}
+
+/*
+ return the current value of the delete_on_close bit,
+ and the current write time.
+*/
+NTSTATUS odb_get_file_infos(struct odb_context *odb, DATA_BLOB *key,
+ bool *del_on_close, NTTIME *write_time)
+{
+ return ops->odb_get_file_infos(odb, key, del_on_close, write_time);
+}
+
+/*
+ determine if a file can be opened with the given share_access,
+ create_options and access_mask
+*/
+NTSTATUS odb_can_open(struct odb_lock *lck,
+ uint32_t stream_id, uint32_t share_access,
+ uint32_t access_mask, bool delete_on_close,
+ uint32_t open_disposition, bool break_to_none)
+{
+ return ops->odb_can_open(lck, stream_id, share_access, access_mask,
+ delete_on_close, open_disposition, break_to_none);
+}
+
+NTSTATUS odb_update_oplock(struct odb_lock *lck, void *file_handle,
+ uint32_t oplock_level)
+{
+ return ops->odb_update_oplock(lck, file_handle, oplock_level);
+}
+
+NTSTATUS odb_break_oplocks(struct odb_lock *lck)
+{
+ return ops->odb_break_oplocks(lck);
+}
diff --git a/source4/ntvfs/common/opendb.h b/source4/ntvfs/common/opendb.h
new file mode 100644
index 0000000..1bfc6aa
--- /dev/null
+++ b/source4/ntvfs/common/opendb.h
@@ -0,0 +1,59 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ open database code - common include
+
+ Copyright (C) Andrew Tridgell 2007
+
+ 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/>.
+*/
+
+struct opendb_ops {
+ struct odb_context *(*odb_init)(TALLOC_CTX *mem_ctx,
+ struct ntvfs_context *ntvfs_ctx);
+ struct odb_lock *(*odb_lock)(TALLOC_CTX *mem_ctx,
+ struct odb_context *odb, DATA_BLOB *file_key);
+ DATA_BLOB (*odb_get_key)(TALLOC_CTX *mem_ctx, struct odb_lock *lck);
+ NTSTATUS (*odb_open_file)(struct odb_lock *lck,
+ void *file_handle, const char *path,
+ int *fd, NTTIME open_write_time,
+ bool allow_level_II_oplock,
+ uint32_t oplock_level, uint32_t *oplock_granted);
+ NTSTATUS (*odb_open_file_pending)(struct odb_lock *lck, void *private_data);
+ NTSTATUS (*odb_close_file)(struct odb_lock *lck, void *file_handle,
+ const char **delete_path);
+ NTSTATUS (*odb_remove_pending)(struct odb_lock *lck, void *private_data);
+ NTSTATUS (*odb_rename)(struct odb_lock *lck, const char *path);
+ NTSTATUS (*odb_get_path)(struct odb_lock *lck, const char **path);
+ NTSTATUS (*odb_set_delete_on_close)(struct odb_lock *lck, bool del_on_close);
+ NTSTATUS (*odb_set_write_time)(struct odb_lock *lck,
+ NTTIME write_time, bool force);
+ NTSTATUS (*odb_get_file_infos)(struct odb_context *odb, DATA_BLOB *key,
+ bool *del_on_close, NTTIME *write_time);
+ NTSTATUS (*odb_can_open)(struct odb_lock *lck,
+ uint32_t stream_id, uint32_t share_access,
+ uint32_t access_mask, bool delete_on_close,
+ uint32_t open_disposition, bool break_to_none);
+ NTSTATUS (*odb_update_oplock)(struct odb_lock *lck, void *file_handle,
+ uint32_t oplock_level);
+ NTSTATUS (*odb_break_oplocks)(struct odb_lock *lck);
+};
+
+struct opendb_oplock_break {
+ void *file_handle;
+ uint8_t level;
+};
+
+void odb_set_ops(const struct opendb_ops *new_ops);
+void odb_tdb_init_ops(void);
diff --git a/source4/ntvfs/common/opendb_tdb.c b/source4/ntvfs/common/opendb_tdb.c
new file mode 100644
index 0000000..9fe0b26
--- /dev/null
+++ b/source4/ntvfs/common/opendb_tdb.c
@@ -0,0 +1,886 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Tridgell 2004
+ Copyright (C) Stefan Metzmacher 2008
+
+ 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/>.
+*/
+
+/*
+ this is the open files database, tdb backend. It implements shared
+ storage of what files are open between server instances, and
+ implements the rules of shared access to files.
+
+ The caller needs to provide a file_key, which specifies what file
+ they are talking about. This needs to be a unique key across all
+ filesystems, and is usually implemented in terms of a device/inode
+ pair.
+
+ Before any operations can be performed the caller needs to establish
+ a lock on the record associated with file_key. That is done by
+ calling odb_lock(). The caller releases this lock by calling
+ talloc_free() on the returned handle.
+
+ All other operations on a record are done by passing the odb_lock()
+ handle back to this module. The handle contains internal
+ information about what file_key is being operated on.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "messaging/messaging.h"
+#include "lib/messaging/irpc.h"
+#include "librpc/gen_ndr/ndr_opendb.h"
+#include "ntvfs/ntvfs.h"
+#include "ntvfs/common/ntvfs_common.h"
+#include "cluster/cluster.h"
+#include "param/param.h"
+#include "ntvfs/sysdep/sys_lease.h"
+
+struct odb_context {
+ struct db_context *db;
+ struct ntvfs_context *ntvfs_ctx;
+ bool oplocks;
+ struct sys_lease_context *lease_ctx;
+};
+
+/*
+ an odb lock handle. You must obtain one of these using odb_lock() before doing
+ any other operations.
+*/
+struct odb_lock {
+ struct odb_context *odb;
+ struct db_record *locked;
+
+ struct opendb_file file;
+
+ struct {
+ struct opendb_entry *e;
+ bool attrs_only;
+ } can_open;
+};
+
+static NTSTATUS odb_oplock_break_send(struct imessaging_context *msg_ctx,
+ struct opendb_entry *e,
+ uint8_t level);
+
+/*
+ Open up the openfiles.tdb database. Close it down using
+ talloc_free(). We need the imessaging_ctx to allow for pending open
+ notifications.
+*/
+static struct odb_context *odb_tdb_init(TALLOC_CTX *mem_ctx,
+ struct ntvfs_context *ntvfs_ctx)
+{
+ struct odb_context *odb;
+
+ odb = talloc(mem_ctx, struct odb_context);
+ if (odb == NULL) {
+ return NULL;
+ }
+
+ odb->db = cluster_db_tmp_open(odb, ntvfs_ctx->lp_ctx,
+ "openfiles", TDB_DEFAULT);
+ if (odb->db == NULL) {
+ talloc_free(odb);
+ return NULL;
+ }
+
+ odb->ntvfs_ctx = ntvfs_ctx;
+
+ odb->oplocks = share_bool_option(ntvfs_ctx->config, SHARE_OPLOCKS, SHARE_OPLOCKS_DEFAULT);
+
+ odb->lease_ctx = sys_lease_context_create(ntvfs_ctx->config, odb,
+ ntvfs_ctx->event_ctx,
+ ntvfs_ctx->msg_ctx,
+ odb_oplock_break_send);
+
+ return odb;
+}
+
+static NTSTATUS odb_pull_record(struct odb_lock *lck, struct opendb_file *file);
+
+/*
+ get a lock on a entry in the odb. This call returns a lock handle,
+ which the caller should unlock using talloc_free().
+*/
+static struct odb_lock *odb_tdb_lock(TALLOC_CTX *mem_ctx,
+ struct odb_context *odb, DATA_BLOB *file_key)
+{
+ struct odb_lock *lck;
+ NTSTATUS status;
+ TDB_DATA key;
+
+ lck = talloc(mem_ctx, struct odb_lock);
+ if (lck == NULL) {
+ return NULL;
+ }
+
+ lck->odb = talloc_reference(lck, odb);
+ key.dptr = talloc_memdup(lck, file_key->data, file_key->length);
+ key.dsize = file_key->length;
+ if (key.dptr == NULL) {
+ talloc_free(lck);
+ return NULL;
+ }
+
+ lck->locked = dbwrap_fetch_locked(odb->db, lck, key);
+ if (!lck->locked) {
+ talloc_free(lck);
+ return NULL;
+ }
+
+ ZERO_STRUCT(lck->can_open);
+
+ status = odb_pull_record(lck, &lck->file);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /* initialise a blank structure */
+ ZERO_STRUCT(lck->file);
+ } else if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ return NULL;
+ }
+
+ return lck;
+}
+
+static DATA_BLOB odb_tdb_get_key(TALLOC_CTX *mem_ctx, struct odb_lock *lck)
+{
+ TDB_DATA key = dbwrap_record_get_key(lck->locked);
+ return data_blob_talloc(mem_ctx, key.dptr, key.dsize);
+}
+
+
+/*
+ determine if two odb_entry structures conflict
+
+ return NT_STATUS_OK on no conflict
+*/
+static NTSTATUS share_conflict(struct opendb_entry *e1,
+ uint32_t stream_id,
+ uint32_t share_access,
+ uint32_t access_mask)
+{
+ /* if either open involves no read.write or delete access then
+ it can't conflict */
+ if (!(e1->access_mask & (SEC_FILE_WRITE_DATA |
+ SEC_FILE_APPEND_DATA |
+ SEC_FILE_READ_DATA |
+ SEC_FILE_EXECUTE |
+ SEC_STD_DELETE))) {
+ return NT_STATUS_OK;
+ }
+ if (!(access_mask & (SEC_FILE_WRITE_DATA |
+ SEC_FILE_APPEND_DATA |
+ SEC_FILE_READ_DATA |
+ SEC_FILE_EXECUTE |
+ SEC_STD_DELETE))) {
+ return NT_STATUS_OK;
+ }
+
+ /* data IO access masks. This is skipped if the two open handles
+ are on different streams (as in that case the masks don't
+ interact) */
+ if (e1->stream_id != stream_id) {
+ return NT_STATUS_OK;
+ }
+
+#define CHECK_MASK(am, right, sa, share) \
+ if (((am) & (right)) && !((sa) & (share))) return NT_STATUS_SHARING_VIOLATION
+
+ CHECK_MASK(e1->access_mask, SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA,
+ share_access, NTCREATEX_SHARE_ACCESS_WRITE);
+ CHECK_MASK(access_mask, SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA,
+ e1->share_access, NTCREATEX_SHARE_ACCESS_WRITE);
+
+ CHECK_MASK(e1->access_mask, SEC_FILE_READ_DATA | SEC_FILE_EXECUTE,
+ share_access, NTCREATEX_SHARE_ACCESS_READ);
+ CHECK_MASK(access_mask, SEC_FILE_READ_DATA | SEC_FILE_EXECUTE,
+ e1->share_access, NTCREATEX_SHARE_ACCESS_READ);
+
+ CHECK_MASK(e1->access_mask, SEC_STD_DELETE,
+ share_access, NTCREATEX_SHARE_ACCESS_DELETE);
+ CHECK_MASK(access_mask, SEC_STD_DELETE,
+ e1->share_access, NTCREATEX_SHARE_ACCESS_DELETE);
+#undef CHECK_MASK
+ return NT_STATUS_OK;
+}
+
+/*
+ pull a record, translating from the db format to the opendb_file structure defined
+ in opendb.idl
+*/
+static NTSTATUS odb_pull_record(struct odb_lock *lck, struct opendb_file *file)
+{
+ TDB_DATA dbuf;
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+
+ dbuf = dbwrap_record_get_value(lck->locked);
+ if (!dbuf.dptr) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ blob.data = dbuf.dptr;
+ blob.length = dbuf.dsize;
+
+ ndr_err = ndr_pull_struct_blob(&blob, lck, file, (ndr_pull_flags_fn_t)ndr_pull_opendb_file);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ push a record, translating from the opendb_file structure defined in opendb.idl
+*/
+static NTSTATUS odb_push_record(struct odb_lock *lck, struct opendb_file *file)
+{
+ TDB_DATA dbuf;
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+
+ if (file->num_entries == 0) {
+ return dbwrap_record_delete(lck->locked);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, lck, file, (ndr_push_flags_fn_t)ndr_push_opendb_file);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ dbuf.dptr = blob.data;
+ dbuf.dsize = blob.length;
+
+ status = dbwrap_record_store(lck->locked, dbuf, TDB_REPLACE);
+ data_blob_free(&blob);
+ return status;
+}
+
+/*
+ send an oplock break to a client
+*/
+static NTSTATUS odb_oplock_break_send(struct imessaging_context *msg_ctx,
+ struct opendb_entry *e,
+ uint8_t level)
+{
+ NTSTATUS status;
+ struct opendb_oplock_break op_break;
+ DATA_BLOB blob;
+
+ ZERO_STRUCT(op_break);
+
+ /* tell the server handling this open file about the need to send the client
+ a break */
+ op_break.file_handle = e->file_handle;
+ op_break.level = level;
+
+ blob = data_blob_const(&op_break, sizeof(op_break));
+
+ status = imessaging_send(msg_ctx, e->server,
+ MSG_NTVFS_OPLOCK_BREAK, &blob);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ return NT_STATUS_OK;
+}
+
+static bool access_attributes_only(uint32_t access_mask,
+ uint32_t open_disposition,
+ bool break_to_none)
+{
+ switch (open_disposition) {
+ case NTCREATEX_DISP_SUPERSEDE:
+ case NTCREATEX_DISP_OVERWRITE_IF:
+ case NTCREATEX_DISP_OVERWRITE:
+ return false;
+ default:
+ break;
+ }
+
+ if (break_to_none) {
+ return false;
+ }
+
+#define CHECK_MASK(m,g) ((m) && (((m) & ~(g))==0) && (((m) & (g)) != 0))
+ return CHECK_MASK(access_mask,
+ SEC_STD_SYNCHRONIZE |
+ SEC_FILE_READ_ATTRIBUTE |
+ SEC_FILE_WRITE_ATTRIBUTE);
+#undef CHECK_MASK
+}
+
+static NTSTATUS odb_tdb_open_can_internal(struct odb_context *odb,
+ const struct opendb_file *file,
+ uint32_t stream_id, uint32_t share_access,
+ uint32_t access_mask, bool delete_on_close,
+ uint32_t open_disposition, bool break_to_none,
+ bool *_attrs_only)
+{
+ NTSTATUS status;
+ uint32_t i;
+ bool attrs_only = false;
+
+ /* see if anyone has an oplock, which we need to break */
+ for (i=0;i<file->num_entries;i++) {
+ if (file->entries[i].oplock_level == OPLOCK_BATCH) {
+ bool oplock_return = OPLOCK_BREAK_TO_LEVEL_II;
+ /* if this is an attribute only access
+ * it doesn't conflict with a BACTCH oplock
+ * but we'll not grant the oplock below
+ */
+ attrs_only = access_attributes_only(access_mask,
+ open_disposition,
+ break_to_none);
+ if (attrs_only) {
+ break;
+ }
+ /* a batch oplock caches close calls, which
+ means the client application might have
+ already closed the file. We have to allow
+ this close to propagate by sending a oplock
+ break request and suspending this call
+ until the break is acknowledged or the file
+ is closed */
+ if (break_to_none ||
+ !file->entries[i].allow_level_II_oplock) {
+ oplock_return = OPLOCK_BREAK_TO_NONE;
+ }
+ odb_oplock_break_send(odb->ntvfs_ctx->msg_ctx,
+ &file->entries[i],
+ oplock_return);
+ return NT_STATUS_OPLOCK_NOT_GRANTED;
+ }
+ }
+
+ if (file->delete_on_close) {
+ /* while delete on close is set, no new opens are allowed */
+ return NT_STATUS_DELETE_PENDING;
+ }
+
+ if (file->num_entries != 0 && delete_on_close) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ /* check for sharing violations */
+ for (i=0;i<file->num_entries;i++) {
+ status = share_conflict(&file->entries[i], stream_id,
+ share_access, access_mask);
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+
+ /* we now know the open could succeed, but we need to check
+ for any exclusive oplocks. We can't grant a second open
+ till these are broken. Note that we check for batch oplocks
+ before checking for sharing violations, and check for
+ exclusive oplocks afterwards. */
+ for (i=0;i<file->num_entries;i++) {
+ if (file->entries[i].oplock_level == OPLOCK_EXCLUSIVE) {
+ bool oplock_return = OPLOCK_BREAK_TO_LEVEL_II;
+ /* if this is an attribute only access
+ * it doesn't conflict with an EXCLUSIVE oplock
+ * but we'll not grant the oplock below
+ */
+ attrs_only = access_attributes_only(access_mask,
+ open_disposition,
+ break_to_none);
+ if (attrs_only) {
+ break;
+ }
+ /*
+ * send an oplock break to the holder of the
+ * oplock and tell caller to retry later
+ */
+ if (break_to_none ||
+ !file->entries[i].allow_level_II_oplock) {
+ oplock_return = OPLOCK_BREAK_TO_NONE;
+ }
+ odb_oplock_break_send(odb->ntvfs_ctx->msg_ctx,
+ &file->entries[i],
+ oplock_return);
+ return NT_STATUS_OPLOCK_NOT_GRANTED;
+ }
+ }
+
+ if (_attrs_only) {
+ *_attrs_only = attrs_only;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ register an open file in the open files database.
+ The share_access rules are implemented by odb_can_open()
+ and it's needed to call odb_can_open() before
+ odb_open_file() otherwise NT_STATUS_INTERNAL_ERROR is returned
+
+ Note that the path is only used by the delete on close logic, not
+ for comparing with other filenames
+*/
+static NTSTATUS odb_tdb_open_file(struct odb_lock *lck,
+ void *file_handle, const char *path,
+ int *fd, NTTIME open_write_time,
+ bool allow_level_II_oplock,
+ uint32_t oplock_level, uint32_t *oplock_granted)
+{
+ struct odb_context *odb = lck->odb;
+
+ if (!lck->can_open.e) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (odb->oplocks == false) {
+ oplock_level = OPLOCK_NONE;
+ }
+
+ if (!oplock_granted) {
+ oplock_level = OPLOCK_NONE;
+ }
+
+ if (lck->file.path == NULL) {
+ lck->file.path = talloc_strdup(lck, path);
+ NT_STATUS_HAVE_NO_MEMORY(lck->file.path);
+ }
+
+ if (lck->file.open_write_time == 0) {
+ lck->file.open_write_time = open_write_time;
+ }
+
+ /*
+ possibly grant an exclusive, batch or level2 oplock
+ */
+ if (lck->can_open.attrs_only) {
+ oplock_level = OPLOCK_NONE;
+ } else if (oplock_level == OPLOCK_EXCLUSIVE) {
+ if (lck->file.num_entries == 0) {
+ oplock_level = OPLOCK_EXCLUSIVE;
+ } else if (allow_level_II_oplock) {
+ oplock_level = OPLOCK_LEVEL_II;
+ } else {
+ oplock_level = OPLOCK_NONE;
+ }
+ } else if (oplock_level == OPLOCK_BATCH) {
+ if (lck->file.num_entries == 0) {
+ oplock_level = OPLOCK_BATCH;
+ } else if (allow_level_II_oplock) {
+ oplock_level = OPLOCK_LEVEL_II;
+ } else {
+ oplock_level = OPLOCK_NONE;
+ }
+ } else if (oplock_level == OPLOCK_LEVEL_II) {
+ oplock_level = OPLOCK_LEVEL_II;
+ } else {
+ oplock_level = OPLOCK_NONE;
+ }
+
+ lck->can_open.e->file_handle = file_handle;
+ lck->can_open.e->fd = fd;
+ lck->can_open.e->allow_level_II_oplock = allow_level_II_oplock;
+ lck->can_open.e->oplock_level = oplock_level;
+
+ if (odb->lease_ctx && fd) {
+ NTSTATUS status;
+ status = sys_lease_setup(odb->lease_ctx, lck->can_open.e);
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+
+ if (oplock_granted) {
+ if (lck->can_open.e->oplock_level == OPLOCK_EXCLUSIVE) {
+ *oplock_granted = EXCLUSIVE_OPLOCK_RETURN;
+ } else if (lck->can_open.e->oplock_level == OPLOCK_BATCH) {
+ *oplock_granted = BATCH_OPLOCK_RETURN;
+ } else if (lck->can_open.e->oplock_level == OPLOCK_LEVEL_II) {
+ *oplock_granted = LEVEL_II_OPLOCK_RETURN;
+ } else {
+ *oplock_granted = NO_OPLOCK_RETURN;
+ }
+ }
+
+ /* it doesn't conflict, so add it to the end */
+ lck->file.entries = talloc_realloc(lck, lck->file.entries,
+ struct opendb_entry,
+ lck->file.num_entries+1);
+ NT_STATUS_HAVE_NO_MEMORY(lck->file.entries);
+
+ lck->file.entries[lck->file.num_entries] = *lck->can_open.e;
+ lck->file.num_entries++;
+
+ talloc_free(lck->can_open.e);
+ lck->can_open.e = NULL;
+
+ return odb_push_record(lck, &lck->file);
+}
+
+
+/*
+ register a pending open file in the open files database
+*/
+static NTSTATUS odb_tdb_open_file_pending(struct odb_lock *lck, void *private_data)
+{
+ struct odb_context *odb = lck->odb;
+
+ if (lck->file.path == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ lck->file.pending = talloc_realloc(lck, lck->file.pending,
+ struct opendb_pending,
+ lck->file.num_pending+1);
+ NT_STATUS_HAVE_NO_MEMORY(lck->file.pending);
+
+ lck->file.pending[lck->file.num_pending].server = odb->ntvfs_ctx->server_id;
+ lck->file.pending[lck->file.num_pending].notify_ptr = private_data;
+
+ lck->file.num_pending++;
+
+ return odb_push_record(lck, &lck->file);
+}
+
+
+/*
+ remove a opendb entry
+*/
+static NTSTATUS odb_tdb_close_file(struct odb_lock *lck, void *file_handle,
+ const char **_delete_path)
+{
+ struct odb_context *odb = lck->odb;
+ const char *delete_path = NULL;
+ int i;
+
+ if (lck->file.path == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* find the entry, and delete it */
+ for (i=0;i<lck->file.num_entries;i++) {
+ if (file_handle == lck->file.entries[i].file_handle &&
+ cluster_id_equal(&odb->ntvfs_ctx->server_id, &lck->file.entries[i].server)) {
+ if (lck->file.entries[i].delete_on_close) {
+ lck->file.delete_on_close = true;
+ }
+ if (odb->lease_ctx && lck->file.entries[i].fd) {
+ NTSTATUS status;
+ status = sys_lease_remove(odb->lease_ctx, &lck->file.entries[i]);
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+ if (i < lck->file.num_entries-1) {
+ memmove(lck->file.entries+i, lck->file.entries+i+1,
+ (lck->file.num_entries - (i+1)) *
+ sizeof(struct opendb_entry));
+ }
+ break;
+ }
+ }
+
+ if (i == lck->file.num_entries) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* send any pending notifications, removing them once sent */
+ for (i=0;i<lck->file.num_pending;i++) {
+ imessaging_send_ptr(odb->ntvfs_ctx->msg_ctx,
+ lck->file.pending[i].server,
+ MSG_PVFS_RETRY_OPEN,
+ lck->file.pending[i].notify_ptr);
+ }
+ lck->file.num_pending = 0;
+
+ lck->file.num_entries--;
+
+ if (lck->file.num_entries == 0 && lck->file.delete_on_close) {
+ delete_path = lck->file.path;
+ }
+
+ if (_delete_path) {
+ *_delete_path = delete_path;
+ }
+
+ return odb_push_record(lck, &lck->file);
+}
+
+/*
+ update the oplock level of the client
+*/
+static NTSTATUS odb_tdb_update_oplock(struct odb_lock *lck, void *file_handle,
+ uint32_t oplock_level)
+{
+ struct odb_context *odb = lck->odb;
+ int i;
+
+ if (lck->file.path == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* find the entry, and update it */
+ for (i=0;i<lck->file.num_entries;i++) {
+ if (file_handle == lck->file.entries[i].file_handle &&
+ cluster_id_equal(&odb->ntvfs_ctx->server_id, &lck->file.entries[i].server)) {
+ lck->file.entries[i].oplock_level = oplock_level;
+
+ if (odb->lease_ctx && lck->file.entries[i].fd) {
+ NTSTATUS status;
+ status = sys_lease_update(odb->lease_ctx, &lck->file.entries[i]);
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+
+ break;
+ }
+ }
+
+ if (i == lck->file.num_entries) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* send any pending notifications, removing them once sent */
+ for (i=0;i<lck->file.num_pending;i++) {
+ imessaging_send_ptr(odb->ntvfs_ctx->msg_ctx,
+ lck->file.pending[i].server,
+ MSG_PVFS_RETRY_OPEN,
+ lck->file.pending[i].notify_ptr);
+ }
+ lck->file.num_pending = 0;
+
+ return odb_push_record(lck, &lck->file);
+}
+
+/*
+ send oplocks breaks to none to all level2 holders
+*/
+static NTSTATUS odb_tdb_break_oplocks(struct odb_lock *lck)
+{
+ struct odb_context *odb = lck->odb;
+ int i;
+ bool modified = false;
+
+ /* see if anyone has an oplock, which we need to break */
+ for (i=0;i<lck->file.num_entries;i++) {
+ if (lck->file.entries[i].oplock_level == OPLOCK_LEVEL_II) {
+ /*
+ * there could be multiple level2 oplocks
+ * and we just send a break to none to all of them
+ * without waiting for a release
+ */
+ odb_oplock_break_send(odb->ntvfs_ctx->msg_ctx,
+ &lck->file.entries[i],
+ OPLOCK_BREAK_TO_NONE);
+ lck->file.entries[i].oplock_level = OPLOCK_NONE;
+ modified = true;
+ }
+ }
+
+ if (modified) {
+ return odb_push_record(lck, &lck->file);
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ remove a pending opendb entry
+*/
+static NTSTATUS odb_tdb_remove_pending(struct odb_lock *lck, void *private_data)
+{
+ struct odb_context *odb = lck->odb;
+ int i;
+
+ if (lck->file.path == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* find the entry, and delete it */
+ for (i=0;i<lck->file.num_pending;i++) {
+ if (private_data == lck->file.pending[i].notify_ptr &&
+ cluster_id_equal(&odb->ntvfs_ctx->server_id, &lck->file.pending[i].server)) {
+ if (i < lck->file.num_pending-1) {
+ memmove(lck->file.pending+i, lck->file.pending+i+1,
+ (lck->file.num_pending - (i+1)) *
+ sizeof(struct opendb_pending));
+ }
+ break;
+ }
+ }
+
+ if (i == lck->file.num_pending) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ lck->file.num_pending--;
+
+ return odb_push_record(lck, &lck->file);
+}
+
+
+/*
+ rename the path in a open file
+*/
+static NTSTATUS odb_tdb_rename(struct odb_lock *lck, const char *path)
+{
+ if (lck->file.path == NULL) {
+ /* not having the record at all is OK */
+ return NT_STATUS_OK;
+ }
+
+ lck->file.path = talloc_strdup(lck, path);
+ NT_STATUS_HAVE_NO_MEMORY(lck->file.path);
+
+ return odb_push_record(lck, &lck->file);
+}
+
+/*
+ get the path of an open file
+*/
+static NTSTATUS odb_tdb_get_path(struct odb_lock *lck, const char **path)
+{
+ *path = NULL;
+
+ /* we don't ignore NT_STATUS_OBJECT_NAME_NOT_FOUND here */
+ if (lck->file.path == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ *path = lck->file.path;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ update delete on close flag on an open file
+*/
+static NTSTATUS odb_tdb_set_delete_on_close(struct odb_lock *lck, bool del_on_close)
+{
+ if (lck->file.path == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ lck->file.delete_on_close = del_on_close;
+
+ return odb_push_record(lck, &lck->file);
+}
+
+/*
+ update the write time on an open file
+*/
+static NTSTATUS odb_tdb_set_write_time(struct odb_lock *lck,
+ NTTIME write_time, bool force)
+{
+ if (lck->file.path == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (lck->file.changed_write_time != 0 && !force) {
+ return NT_STATUS_OK;
+ }
+
+ lck->file.changed_write_time = write_time;
+
+ return odb_push_record(lck, &lck->file);
+}
+
+/*
+ return the current value of the delete_on_close bit, and how many
+ people still have the file open
+*/
+static NTSTATUS odb_tdb_get_file_infos(struct odb_context *odb, DATA_BLOB *key,
+ bool *del_on_close, NTTIME *write_time)
+{
+ struct odb_lock *lck;
+
+ if (del_on_close) {
+ *del_on_close = false;
+ }
+ if (write_time) {
+ *write_time = 0;
+ }
+
+ lck = odb_lock(odb, odb, key);
+ NT_STATUS_HAVE_NO_MEMORY(lck);
+
+ if (del_on_close) {
+ *del_on_close = lck->file.delete_on_close;
+ }
+ if (write_time) {
+ if (lck->file.changed_write_time == 0) {
+ *write_time = lck->file.open_write_time;
+ } else {
+ *write_time = lck->file.changed_write_time;
+ }
+ }
+
+ talloc_free(lck);
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ determine if a file can be opened with the given share_access,
+ create_options and access_mask
+*/
+static NTSTATUS odb_tdb_can_open(struct odb_lock *lck,
+ uint32_t stream_id, uint32_t share_access,
+ uint32_t access_mask, bool delete_on_close,
+ uint32_t open_disposition, bool break_to_none)
+{
+ struct odb_context *odb = lck->odb;
+ NTSTATUS status;
+
+ status = odb_tdb_open_can_internal(odb, &lck->file, stream_id,
+ share_access, access_mask,
+ delete_on_close, open_disposition,
+ break_to_none, &lck->can_open.attrs_only);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ lck->can_open.e = talloc(lck, struct opendb_entry);
+ NT_STATUS_HAVE_NO_MEMORY(lck->can_open.e);
+
+ lck->can_open.e->server = odb->ntvfs_ctx->server_id;
+ lck->can_open.e->file_handle = NULL;
+ lck->can_open.e->fd = NULL;
+ lck->can_open.e->stream_id = stream_id;
+ lck->can_open.e->share_access = share_access;
+ lck->can_open.e->access_mask = access_mask;
+ lck->can_open.e->delete_on_close = delete_on_close;
+ lck->can_open.e->allow_level_II_oplock = false;
+ lck->can_open.e->oplock_level = OPLOCK_NONE;
+
+ return NT_STATUS_OK;
+}
+
+
+static const struct opendb_ops opendb_tdb_ops = {
+ .odb_init = odb_tdb_init,
+ .odb_lock = odb_tdb_lock,
+ .odb_get_key = odb_tdb_get_key,
+ .odb_open_file = odb_tdb_open_file,
+ .odb_open_file_pending = odb_tdb_open_file_pending,
+ .odb_close_file = odb_tdb_close_file,
+ .odb_remove_pending = odb_tdb_remove_pending,
+ .odb_rename = odb_tdb_rename,
+ .odb_get_path = odb_tdb_get_path,
+ .odb_set_delete_on_close = odb_tdb_set_delete_on_close,
+ .odb_set_write_time = odb_tdb_set_write_time,
+ .odb_get_file_infos = odb_tdb_get_file_infos,
+ .odb_can_open = odb_tdb_can_open,
+ .odb_update_oplock = odb_tdb_update_oplock,
+ .odb_break_oplocks = odb_tdb_break_oplocks
+};
+
+
+void odb_tdb_init_ops(void)
+{
+ sys_lease_init();
+ odb_set_ops(&opendb_tdb_ops);
+}
diff --git a/source4/ntvfs/common/wscript_build b/source4/ntvfs/common/wscript_build
new file mode 100644
index 0000000..b144472
--- /dev/null
+++ b/source4/ntvfs/common/wscript_build
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+
+bld.SAMBA_SUBSYSTEM('ntvfs_common',
+ source='init.c brlock.c brlock_tdb.c opendb.c opendb_tdb.c notify.c',
+ autoproto='proto.h',
+ deps='util_tdb tdb-wrap',
+ public_deps='NDR_OPENDB NDR_NOTIFY sys_notify sys_lease share'
+ )
+