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 2 of the License, or
+ 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,
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, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
#include "includes.h"
#include "system/filesys.h"
-#include "lib/tdb/include/tdb.h"
-#include "lib/tdb/include/tdbutil.h"
+#include <tdb.h>
+#include "../lib/util/util_tdb.h"
#include "messaging/messaging.h"
-#include "db_wrap.h"
+#include "tdb_wrap.h"
#include "lib/messaging/irpc.h"
-#include "librpc/gen_ndr/ndr_notify.h"
-#include "dlinklist.h"
+#include "librpc/gen_ndr/ndr_s4_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"
struct notify_context {
struct tdb_wrap *w;
- uint32_t server;
+ struct server_id server;
struct messaging_context *messaging_ctx;
struct notify_list *list;
struct notify_array *array;
int seqnum;
+ struct sys_notify_context *sys_notify_ctx;
};
struct notify_list {
struct notify_list *next, *prev;
- void *private;
+ 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 messaging_context *msg_ctx, void *private,
- uint32_t msg_type, uint32_t server_id, DATA_BLOB *data);
+static void notify_handler(struct messaging_context *msg_ctx, void *private_data,
+ uint32_t msg_type, struct server_id server_id, DATA_BLOB *data);
/*
destroy the notify context
*/
-static int notify_destructor(void *p)
+static int notify_destructor(struct notify_context *notify)
{
- struct notify_context *notify = talloc_get_type(p, struct notify_context);
messaging_deregister(notify->messaging_ctx, MSG_PVFS_NOTIFY, notify);
notify_remove_all(notify);
return 0;
talloc_free(). We need the messaging_ctx to allow for notifications
via internal messages
*/
-struct notify_context *notify_init(TALLOC_CTX *mem_ctx, uint32_t server,
- struct messaging_context *messaging_ctx)
+struct notify_context *notify_init(TALLOC_CTX *mem_ctx, struct server_id server,
+ struct messaging_context *messaging_ctx,
+ struct loadparm_context *lp_ctx,
+ struct tevent_context *ev,
+ struct share_config *scfg)
{
- char *path;
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;
}
- path = smbd_tmp_path(notify, "notify.tdb");
- notify->w = tdb_wrap_open(notify, path, 0,
- TDB_SEQNUM,
- O_RDWR|O_CREAT, 0600);
- talloc_free(path);
+ notify->w = cluster_tdb_tmp_open(notify, lp_ctx, "notify.tdb", TDB_SEQNUM);
if (notify->w == NULL) {
talloc_free(notify);
return NULL;
messaging_register(notify->messaging_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 NTSTATUS notify_lock(struct notify_context *notify)
+{
+ if (tdb_lock_bystring(notify->w->tdb, NOTIFY_KEY) != 0) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ unlock the notify db
+*/
+static void notify_unlock(struct notify_context *notify)
+{
+ tdb_unlock_bystring(notify->w->tdb, NOTIFY_KEY);
+}
+
/*
load the notify array
*/
{
TDB_DATA dbuf;
DATA_BLOB blob;
- NTSTATUS status;
+ enum ndr_err_code ndr_err;
int seqnum;
seqnum = tdb_get_seqnum(notify->w->tdb);
blob.data = dbuf.dptr;
blob.length = dbuf.dsize;
- status = ndr_pull_struct_blob(&blob, notify->array, notify->array,
- (ndr_pull_flags_fn_t)ndr_pull_notify_array);
+ ndr_err = ndr_pull_struct_blob(&blob, notify->array, notify->array,
+ (ndr_pull_flags_fn_t)ndr_pull_notify_array);
free(dbuf.dptr);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
- return status;
+ 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
{
TDB_DATA dbuf;
DATA_BLOB blob;
- NTSTATUS status;
+ enum ndr_err_code ndr_err;
int ret;
TALLOC_CTX *tmp_ctx;
- if (notify->array->num_entries == 0) {
+ /* 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) {
ret = tdb_delete_bystring(notify->w->tdb, NOTIFY_KEY);
if (ret != 0) {
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
tmp_ctx = talloc_new(notify);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
- status = ndr_push_struct_blob(&blob, tmp_ctx, notify->array,
- (ndr_push_flags_fn_t)ndr_push_notify_array);
- if (!NT_STATUS_IS_OK(status)) {
+ 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 status;
+ return ndr_map_error2ntstatus(ndr_err);
}
dbuf.dptr = blob.data;
/*
handle incoming notify messages
*/
-static void notify_handler(struct messaging_context *msg_ctx, void *private,
- uint32_t msg_type, uint32_t server_id, DATA_BLOB *data)
+static void notify_handler(struct messaging_context *msg_ctx, void *private_data,
+ uint32_t msg_type, struct server_id server_id, DATA_BLOB *data)
{
- struct notify_context *notify = talloc_get_type(private, struct notify_context);
- NTSTATUS status;
+ 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;
- status = ndr_pull_struct_blob(data, tmp_ctx, &ev,
+ 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 (!NT_STATUS_IS_OK(status)) {
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
talloc_free(tmp_ctx);
return;
}
for (listel=notify->list;listel;listel=listel->next) {
- if (listel->private == ev.private) {
- listel->callback(listel->private, &ev);
+ 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 = ¬ify->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 *e,
+NTSTATUS notify_add(struct notify_context *notify, struct notify_entry *e0,
void (*callback)(void *, const struct notify_event *),
- void *private)
+ void *private_data)
{
+ struct notify_entry e = *e0;
NTSTATUS status;
+ char *tmp_path = NULL;
struct notify_list *listel;
+ size_t len;
+ int depth;
- status = notify_load(notify);
- NT_STATUS_NOT_OK_RETURN(status);
+ /* see if change notify is enabled at all */
+ if (notify == NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
- notify->array->entries = talloc_realloc(notify->array, notify->array->entries,
- struct notify_entry,
- notify->array->num_entries+1);
+ status = notify_lock(notify);
+ NT_STATUS_NOT_OK_RETURN(status);
- if (notify->array->entries == NULL) {
- return NT_STATUS_NO_MEMORY;
+ status = notify_load(notify);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
}
- notify->array->entries[notify->array->num_entries] = *e;
- notify->array->entries[notify->array->num_entries].private = private;
- notify->array->entries[notify->array->num_entries].server = notify->server;
- notify->array->num_entries++;
+ /* 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;
+ }
- status = notify_save(notify);
- NT_STATUS_NOT_OK_RETURN(status);
+ depth = count_chars(e.path, '/');
- listel = talloc(notify, struct notify_list);
- NT_STATUS_HAVE_NO_MEMORY(listel);
+ listel = talloc_zero(notify, struct notify_list);
+ if (listel == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
- listel->private = private;
+ 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(notify);
+ 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)
+NTSTATUS notify_remove(struct notify_context *notify, void *private_data)
{
NTSTATUS status;
struct notify_list *listel;
- int i;
+ int i, depth;
+ struct notify_depth *d;
+
+ /* 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 == private) {
+ if (listel->private_data == private_data) {
DLIST_REMOVE(notify->list, listel);
break;
}
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
- status = notify_load(notify);
+ depth = listel->depth;
+
+ talloc_free(listel);
+
+ status = notify_lock(notify);
NT_STATUS_NOT_OK_RETURN(status);
- for (i=0;i<notify->array->num_entries;i++) {
- if (notify->server == notify->array->entries[i].server &&
- private == notify->array->entries[i].private) {
+ status = notify_load(notify);
+ if (!NT_STATUS_IS_OK(status)) {
+ notify_unlock(notify);
+ return status;
+ }
+
+ if (depth >= notify->array->num_depths) {
+ notify_unlock(notify);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* we only have to search at the depth of this element */
+ d = ¬ify->array->depth[depth];
+
+ for (i=0;i<d->num_entries;i++) {
+ if (private_data == d->entries[i].private_data &&
+ cluster_id_equal(¬ify->server, &d->entries[i].server)) {
break;
}
}
- if (i == notify->array->num_entries) {
+ if (i == d->num_entries) {
+ notify_unlock(notify);
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
- if (i < notify->array->num_entries-1) {
- memmove(¬ify->array->entries[i], ¬ify->array->entries[i+1],
- sizeof(notify->array->entries[i])*(notify->array->num_entries-(i+1)));
+ if (i < d->num_entries-1) {
+ memmove(&d->entries[i], &d->entries[i+1],
+ sizeof(d->entries[i])*(d->num_entries-(i+1)));
}
- notify->array->num_entries--;
+ d->num_entries--;
- return notify_save(notify);
+ status = notify_save(notify);
+
+ notify_unlock(notify);
+
+ return status;
}
/*
static NTSTATUS notify_remove_all(struct notify_context *notify)
{
NTSTATUS status;
- int i;
+ int i, depth, del_count=0;
if (notify->list == NULL) {
return NT_STATUS_OK;
}
- status = notify_load(notify);
+ status = notify_lock(notify);
NT_STATUS_NOT_OK_RETURN(status);
- for (i=0;i<notify->array->num_entries;i++) {
- if (notify->server == notify->array->entries[i].server) {
- if (i < notify->array->num_entries-1) {
- memmove(¬ify->array->entries[i], ¬ify->array->entries[i+1],
- sizeof(notify->array->entries[i])*(notify->array->num_entries-(i+1)));
- }
- i--;
- notify->array->num_entries--;
- }
- }
-
-
- return notify_save(notify);
-}
-
-
-/*
- see if a notify event matches
-*/
-static BOOL notify_match(struct notify_context *notify, struct notify_entry *e,
- const char *path, uint32_t filter)
-{
- size_t len;
-
- if (!(filter & e->filter)) {
- return False;
+ status = notify_load(notify);
+ if (!NT_STATUS_IS_OK(status)) {
+ notify_unlock(notify);
+ return status;
}
- len = strlen(e->path);
-
- if (strncmp(path, e->path, len) != 0) {
- return False;
+ /* 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 = ¬ify->array->depth[depth];
+ for (i=0;i<d->num_entries;i++) {
+ if (cluster_id_equal(¬ify->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 (path[len] != '/') {
- return False;
+ if (del_count > 0) {
+ status = notify_save(notify);
}
- if (!e->recursive) {
- if (strchr(&path[len+1], '/') != NULL) {
- return False;
- }
- }
+ notify_unlock(notify);
- return True;
+ return status;
}
struct notify_event ev;
DATA_BLOB data;
NTSTATUS status;
+ enum ndr_err_code ndr_err;
TALLOC_CTX *tmp_ctx;
ev.action = action;
ev.path = path;
- ev.private = e->private;
+ ev.private_data = e->private_data;
tmp_ctx = talloc_new(notify);
- status = ndr_push_struct_blob(&data, tmp_ctx, &ev,
- (ndr_push_flags_fn_t)ndr_push_notify_event);
- if (!NT_STATUS_IS_OK(status)) {
+ 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;
}
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 i;
+ 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;
}
- /* this needs to be changed to a log(n) search */
- for (i=0;i<notify->array->num_entries;i++) {
- if (notify_match(notify, ¬ify->array->entries[i], path, filter)) {
- notify_send(notify, ¬ify->array->entries[i],
- path + strlen(notify->array->entries[i].path) + 1,
- action);
+ /* 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 = ¬ify->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);
}
}
}