Add notify_onelevel.tdb
authorVolker Lendecke <vl@samba.org>
Tue, 14 Apr 2009 18:39:14 +0000 (20:39 +0200)
committerMichael Adam <obnox@samba.org>
Tue, 28 Jul 2009 13:12:44 +0000 (15:12 +0200)
This optimizes non-recursive notifys. For non-recursive notifies we can use a
per-directory file-id indexed notify record. This matters for the Windows
Explorer and IIS cases which do not use recursive notifies. In these cases, we
do not have to shuffle around the whole notify record on every change.

For the cluster case, this improves correctness of the notifies, ctdb only
distributes the tdb seqnum once a second, so we can lose notifies.

source/librpc/gen_ndr/ndr_notify.c
source/librpc/gen_ndr/ndr_notify.h
source/librpc/gen_ndr/notify.h
source/librpc/idl/notify.idl
source/smbd/files.c
source/smbd/notify.c
source/smbd/notify_internal.c

index d4ac42e96146ee7c1ffc4ca56e7bb78afa995a56..844c278cd238743c653ed670b785b5198da09b18 100644 (file)
@@ -68,6 +68,69 @@ _PUBLIC_ void ndr_print_notify_entry(struct ndr_print *ndr, const char *name, co
        ndr->depth--;
 }
 
+_PUBLIC_ enum ndr_err_code ndr_push_notify_entry_array(struct ndr_push *ndr, int ndr_flags, const struct notify_entry_array *r)
+{
+       uint32_t cntr_entries_0;
+       if (ndr_flags & NDR_SCALARS) {
+               NDR_CHECK(ndr_push_align(ndr, 8));
+               NDR_CHECK(ndr_push_uint32(ndr, NDR_SCALARS, r->num_entries));
+               for (cntr_entries_0 = 0; cntr_entries_0 < r->num_entries; cntr_entries_0++) {
+                       NDR_CHECK(ndr_push_notify_entry(ndr, NDR_SCALARS, &r->entries[cntr_entries_0]));
+               }
+       }
+       if (ndr_flags & NDR_BUFFERS) {
+               for (cntr_entries_0 = 0; cntr_entries_0 < r->num_entries; cntr_entries_0++) {
+                       NDR_CHECK(ndr_push_notify_entry(ndr, NDR_BUFFERS, &r->entries[cntr_entries_0]));
+               }
+       }
+       return NDR_ERR_SUCCESS;
+}
+
+_PUBLIC_ enum ndr_err_code ndr_pull_notify_entry_array(struct ndr_pull *ndr, int ndr_flags, struct notify_entry_array *r)
+{
+       uint32_t cntr_entries_0;
+       TALLOC_CTX *_mem_save_entries_0;
+       if (ndr_flags & NDR_SCALARS) {
+               NDR_CHECK(ndr_pull_align(ndr, 8));
+               NDR_CHECK(ndr_pull_uint32(ndr, NDR_SCALARS, &r->num_entries));
+               NDR_PULL_ALLOC_N(ndr, r->entries, r->num_entries);
+               _mem_save_entries_0 = NDR_PULL_GET_MEM_CTX(ndr);
+               NDR_PULL_SET_MEM_CTX(ndr, r->entries, 0);
+               for (cntr_entries_0 = 0; cntr_entries_0 < r->num_entries; cntr_entries_0++) {
+                       NDR_CHECK(ndr_pull_notify_entry(ndr, NDR_SCALARS, &r->entries[cntr_entries_0]));
+               }
+               NDR_PULL_SET_MEM_CTX(ndr, _mem_save_entries_0, 0);
+       }
+       if (ndr_flags & NDR_BUFFERS) {
+               _mem_save_entries_0 = NDR_PULL_GET_MEM_CTX(ndr);
+               NDR_PULL_SET_MEM_CTX(ndr, r->entries, 0);
+               for (cntr_entries_0 = 0; cntr_entries_0 < r->num_entries; cntr_entries_0++) {
+                       NDR_CHECK(ndr_pull_notify_entry(ndr, NDR_BUFFERS, &r->entries[cntr_entries_0]));
+               }
+               NDR_PULL_SET_MEM_CTX(ndr, _mem_save_entries_0, 0);
+       }
+       return NDR_ERR_SUCCESS;
+}
+
+_PUBLIC_ void ndr_print_notify_entry_array(struct ndr_print *ndr, const char *name, const struct notify_entry_array *r)
+{
+       uint32_t cntr_entries_0;
+       ndr_print_struct(ndr, name, "notify_entry_array");
+       ndr->depth++;
+       ndr_print_uint32(ndr, "num_entries", r->num_entries);
+       ndr->print(ndr, "%s: ARRAY(%d)", "entries", (int)r->num_entries);
+       ndr->depth++;
+       for (cntr_entries_0=0;cntr_entries_0<r->num_entries;cntr_entries_0++) {
+               char *idx_0=NULL;
+               if (asprintf(&idx_0, "[%d]", cntr_entries_0) != -1) {
+                       ndr_print_notify_entry(ndr, "entries", &r->entries[cntr_entries_0]);
+                       free(idx_0);
+               }
+       }
+       ndr->depth--;
+       ndr->depth--;
+}
+
 static enum ndr_err_code ndr_push_notify_depth(struct ndr_push *ndr, int ndr_flags, const struct notify_depth *r)
 {
        uint32_t cntr_entries_0;
index 23d3d3fc0aa9b3d19e0c7344bbd8a280674e344b..fa2972dbc618fa83ee56898b2a210f250daa3fdb 100644 (file)
@@ -10,6 +10,9 @@
 enum ndr_err_code ndr_push_notify_entry(struct ndr_push *ndr, int ndr_flags, const struct notify_entry *r);
 enum ndr_err_code ndr_pull_notify_entry(struct ndr_pull *ndr, int ndr_flags, struct notify_entry *r);
 void ndr_print_notify_entry(struct ndr_print *ndr, const char *name, const struct notify_entry *r);
+enum ndr_err_code ndr_push_notify_entry_array(struct ndr_push *ndr, int ndr_flags, const struct notify_entry_array *r);
+enum ndr_err_code ndr_pull_notify_entry_array(struct ndr_pull *ndr, int ndr_flags, struct notify_entry_array *r);
+void ndr_print_notify_entry_array(struct ndr_print *ndr, const char *name, const struct notify_entry_array *r);
 void ndr_print_notify_depth(struct ndr_print *ndr, const char *name, const struct notify_depth *r);
 enum ndr_err_code ndr_push_notify_array(struct ndr_push *ndr, int ndr_flags, const struct notify_array *r);
 enum ndr_err_code ndr_pull_notify_array(struct ndr_pull *ndr, int ndr_flags, struct notify_array *r);
index a5ec4a46e6e7a335bc999249e8d8f6773c1ebcda..945280556c328ac1322712791ff493bcb74aa5f7 100644 (file)
@@ -16,6 +16,11 @@ struct notify_entry {
        void* private_data;
 }/* [public] */;
 
+struct notify_entry_array {
+       uint32_t num_entries;
+       struct notify_entry *entries;
+}/* [public] */;
+
 struct notify_depth {
        uint32_t max_mask;
        uint32_t max_mask_subdir;
index 550783b5cd3a99b46b8cf6787a8533afb8bab346..0e806790744cc88f5e9b04d6f306a2a250df9c31 100644 (file)
@@ -25,6 +25,11 @@ interface notify
                pointer private_data;
        } notify_entry;
 
+       typedef [public] struct {
+               uint32 num_entries;
+               notify_entry entries[num_entries];
+       } notify_entry_array;
+
        /*
          to allow for efficient search for matching entries, we
          divide them by the directory depth, with a separate array
index f643892835d9c84bf3fc8bf78e601d9b70ad6dfe..6c4a02230804f41be16cbcdb8a97953b3258fada 100644 (file)
@@ -435,6 +435,10 @@ void file_free(files_struct *fsp)
        }
 
        if (fsp->notify) {
+               if (fsp->is_directory) {
+                       notify_remove_onelevel(fsp->conn->notify_ctx,
+                                              &fsp->file_id, fsp);
+               }
                notify_remove(fsp->conn->notify_ctx, fsp);
                TALLOC_FREE(fsp->notify);
        }
index 7be103b5b834435ca13dc5536bae61530486a405..180e08655bfc34f2e48195d26a4ab120d6ac857a 100644 (file)
@@ -361,6 +361,9 @@ void notify_fname(connection_struct *conn, uint32 action, uint32 filter,
                  const char *path)
 {
        char *fullpath;
+       char *parent;
+       const char *name;
+       SMB_STRUCT_STAT sbuf;
 
        if (path[0] == '.' && path[1] == '/') {
                path += 2;
@@ -370,6 +373,14 @@ void notify_fname(connection_struct *conn, uint32 action, uint32 filter,
                return;
        }
 
+       if (parent_dirname_talloc(talloc_tos(), path, &parent, &name)
+           && (SMB_VFS_STAT(conn, parent, &sbuf) != -1)) {
+               notify_onelevel(conn->notify_ctx, action, filter,
+                               SMB_VFS_FILE_ID_CREATE(conn, sbuf.st_dev,
+                                                      sbuf.st_ino),
+                               name);
+       }
+
        notify_trigger(conn->notify_ctx, action, filter, fullpath);
        SAFE_FREE(fullpath);
 }
index 4dd7d7e28acc8551970e3358a6860bbbbc5f36dd..b28e345671de26efa6cb42940fdc48293b5a32cd 100644 (file)
@@ -28,6 +28,7 @@
 
 struct notify_context {
        struct db_context *db_recursive;
+       struct db_context *db_onelevel;
        struct server_id server;
        struct messaging_context *messaging_ctx;
        struct notify_list *list;
@@ -99,6 +100,14 @@ struct notify_context *notify_init(TALLOC_CTX *mem_ctx, struct server_id server,
                return NULL;
        }
 
+       notify->db_onelevel = db_open(notify, lock_path("notify_onelevel.tdb"),
+                                     0, TDB_SEQNUM|TDB_CLEAR_IF_FIRST,
+                                     O_RDWR|O_CREAT, 0644);
+       if (notify->db_onelevel == NULL) {
+               talloc_free(notify);
+               return NULL;
+       }
+
        notify->server = server;
        notify->messaging_ctx = messaging_ctx;
        notify->list = NULL;
@@ -346,6 +355,96 @@ static NTSTATUS notify_add_array(struct notify_context *notify, struct db_record
        return notify_save(notify, rec);
 }
 
+/*
+  Add a non-recursive watch
+*/
+
+static void notify_add_onelevel(struct notify_context *notify,
+                               struct notify_entry *e, void *private_data)
+{
+       struct notify_entry_array *array;
+       struct db_record *rec;
+       DATA_BLOB blob;
+       TDB_DATA dbuf;
+       enum ndr_err_code ndr_err;
+       NTSTATUS status;
+
+       array = talloc_zero(talloc_tos(), struct notify_entry_array);
+       if (array == NULL) {
+               return;
+       }
+
+       rec = notify->db_onelevel->fetch_locked(
+               notify->db_onelevel, talloc_tos(),
+               make_tdb_data((uint8_t *)&e->dir_id, sizeof(e->dir_id)));
+       if (rec == NULL) {
+               DEBUG(10, ("notify_add_onelevel: fetch_locked for %s failed"
+                          "\n", file_id_string_tos(&e->dir_id)));
+               TALLOC_FREE(array);
+               return;
+       }
+
+       blob.data = (uint8_t *)rec->value.dptr;
+       blob.length = rec->value.dsize;
+
+       if (blob.length > 0) {
+               ndr_err = ndr_pull_struct_blob(
+                       &blob, array, array,
+                       (ndr_pull_flags_fn_t)ndr_pull_notify_entry_array);
+               if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+                       DEBUG(10, ("ndr_pull_notify_entry_array failed: %s\n",
+                                  ndr_errstr(ndr_err)));
+                       TALLOC_FREE(array);
+                       return;
+               }
+               if (DEBUGLEVEL >= 10) {
+                       DEBUG(10, ("notify_add_onelevel:\n"));
+                       NDR_PRINT_DEBUG(notify_entry_array, array);
+               }
+       }
+
+       array->entries = talloc_realloc(array, array->entries,
+                                       struct notify_entry,
+                                       array->num_entries+1);
+       if (array->entries == NULL) {
+               TALLOC_FREE(array);
+               return;
+       }
+       array->entries[array->num_entries] = *e;
+       array->entries[array->num_entries].private_data = private_data;
+       array->entries[array->num_entries].server = notify->server;
+       array->num_entries += 1;
+
+       ndr_err = ndr_push_struct_blob(
+               &blob, rec, array,
+               (ndr_push_flags_fn_t)ndr_push_notify_entry_array);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               DEBUG(10, ("ndr_push_notify_entry_array failed: %s\n",
+                          ndr_errstr(ndr_err)));
+               TALLOC_FREE(array);
+               return;
+       }
+
+       if (DEBUGLEVEL >= 10) {
+               DEBUG(10, ("notify_add_onelevel:\n"));
+               NDR_PRINT_DEBUG(notify_entry_array, array);
+       }
+
+       dbuf.dptr = blob.data;
+       dbuf.dsize = blob.length;
+
+       status = rec->store(rec, dbuf, TDB_REPLACE);
+       TALLOC_FREE(array);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(10, ("notify_add_onelevel: store failed: %s\n",
+                          nt_errstr(status)));
+               return;
+       }
+       e->filter = 0;
+       return;
+}
+
+
 /*
   add a notify watch. This is called when a notify is first setup on a open
   directory handle.
@@ -414,6 +513,11 @@ NTSTATUS notify_add(struct notify_context *notify, struct notify_entry *e0,
                }
        }
 
+       if (e.filter != 0) {
+               notify_add_onelevel(notify, &e, private_data);
+               status = NT_STATUS_OK;
+       }
+
        /* 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
@@ -429,6 +533,102 @@ done:
        return status;
 }
 
+NTSTATUS notify_remove_onelevel(struct notify_context *notify,
+                               const struct file_id *fid,
+                               void *private_data)
+{
+       struct notify_entry_array *array;
+       struct db_record *rec;
+       DATA_BLOB blob;
+       TDB_DATA dbuf;
+       enum ndr_err_code ndr_err;
+       NTSTATUS status;
+       int i;
+
+       array = talloc_zero(talloc_tos(), struct notify_entry_array);
+       if (array == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       rec = notify->db_onelevel->fetch_locked(
+               notify->db_onelevel, talloc_tos(),
+               make_tdb_data((uint8_t *)fid, sizeof(*fid)));
+       if (rec == NULL) {
+               DEBUG(10, ("notify_remove_onelevel: fetch_locked for %s failed"
+                          "\n", file_id_string_tos(fid)));
+               TALLOC_FREE(array);
+               return NT_STATUS_INTERNAL_DB_CORRUPTION;
+       }
+
+       blob.data = (uint8_t *)rec->value.dptr;
+       blob.length = rec->value.dsize;
+
+       if (blob.length > 0) {
+               ndr_err = ndr_pull_struct_blob(
+                       &blob, array, array,
+                       (ndr_pull_flags_fn_t)ndr_pull_notify_entry_array);
+               if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+                       DEBUG(10, ("ndr_pull_notify_entry_array failed: %s\n",
+                                  ndr_errstr(ndr_err)));
+                       TALLOC_FREE(array);
+                       return ndr_map_error2ntstatus(ndr_err);
+               }
+               if (DEBUGLEVEL >= 10) {
+                       DEBUG(10, ("notify_remove_onelevel:\n"));
+                       NDR_PRINT_DEBUG(notify_entry_array, array);
+               }
+       }
+
+       for (i=0; i<array->num_entries; i++) {
+               if ((private_data == array->entries[i].private_data) &&
+                   cluster_id_equal(&notify->server,
+                                    &array->entries[i].server)) {
+                       break;
+               }
+       }
+
+       if (i == array->num_entries) {
+               TALLOC_FREE(array);
+               return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+       }
+
+       array->entries[i] = array->entries[array->num_entries-1];
+       array->num_entries -= 1;
+
+       if (array->num_entries == 0) {
+               rec->delete_rec(rec);
+               TALLOC_FREE(array);
+               return NT_STATUS_OK;
+       }
+
+       ndr_err = ndr_push_struct_blob(
+               &blob, rec, array,
+               (ndr_push_flags_fn_t)ndr_push_notify_entry_array);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               DEBUG(10, ("ndr_push_notify_entry_array failed: %s\n",
+                          ndr_errstr(ndr_err)));
+               TALLOC_FREE(array);
+               return ndr_map_error2ntstatus(ndr_err);
+       }
+
+       if (DEBUGLEVEL >= 10) {
+               DEBUG(10, ("notify_add_onelevel:\n"));
+               NDR_PRINT_DEBUG(notify_entry_array, array);
+       }
+
+       dbuf.dptr = blob.data;
+       dbuf.dsize = blob.length;
+
+       status = rec->store(rec, dbuf, TDB_REPLACE);
+       TALLOC_FREE(array);
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(10, ("notify_add_onelevel: store failed: %s\n",
+                          nt_errstr(status)));
+               return status;
+       }
+       return NT_STATUS_OK;
+}
+
 /*
   remove a notify watch. Called when the directory handle is closed
 */
@@ -577,6 +777,92 @@ static NTSTATUS notify_send(struct notify_context *notify, struct notify_entry *
        return status;
 }
 
+void notify_onelevel(struct notify_context *notify, uint32_t action,
+                    uint32_t filter, struct file_id fid, const char *name)
+{
+       struct notify_entry_array *array;
+       TDB_DATA dbuf;
+       DATA_BLOB blob;
+       bool have_dead_entries = false;
+       int i;
+
+       array = talloc_zero(talloc_tos(), struct notify_entry_array);
+       if (array == NULL) {
+               return;
+       }
+
+       if (notify->db_onelevel->fetch(
+                   notify->db_onelevel, array,
+                   make_tdb_data((uint8_t *)&fid, sizeof(fid)),
+                   &dbuf) == -1) {
+               TALLOC_FREE(array);
+               return;
+       }
+
+       blob.data = (uint8 *)dbuf.dptr;
+       blob.length = dbuf.dsize;
+
+       if (blob.length > 0) {
+               enum ndr_err_code ndr_err;
+               ndr_err = ndr_pull_struct_blob(
+                       &blob, array, array,
+                       (ndr_pull_flags_fn_t)ndr_pull_notify_entry_array);
+               if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+                       DEBUG(10, ("ndr_pull_notify_entry_array failed: %s\n",
+                                  ndr_errstr(ndr_err)));
+                       TALLOC_FREE(array);
+                       return;
+               }
+               if (DEBUGLEVEL >= 10) {
+                       DEBUG(10, ("notify_onelevel:\n"));
+                       NDR_PRINT_DEBUG(notify_entry_array, array);
+               }
+       }
+
+       for (i=0; i<array->num_entries; i++) {
+               struct notify_entry *e = &array->entries[i];
+
+               if ((e->filter & filter) != 0) {
+                       NTSTATUS status;
+
+                       status = notify_send(notify, e, name, action);
+                       if (NT_STATUS_EQUAL(
+                                   status, NT_STATUS_INVALID_HANDLE)) {
+                               /*
+                                * Mark the entry as dead. All entries have a
+                                * path set. The marker used here is setting
+                                * that to NULL.
+                                */
+                               e->path = NULL;
+                               have_dead_entries = true;
+                       }
+               }
+       }
+
+       if (!have_dead_entries) {
+               TALLOC_FREE(array);
+               return;
+       }
+
+       for (i=0; i<array->num_entries; i++) {
+               struct notify_entry *e = &array->entries[i];
+               if (e->path != NULL) {
+                       continue;
+               }
+               DEBUG(10, ("Deleting notify entries for process %s because "
+                          "it's gone\n", procid_str_static(&e->server)));
+               /*
+                * Potential TODO: This might need optimizing,
+                * notify_remove_onelevel() does a fetch_locked() operation at
+                * every call. But this would only matter if a process with
+                * MANY notifies has died without shutting down properly.
+                */
+               notify_remove_onelevel(notify, &e->dir_id, e->private_data);
+       }
+
+       TALLOC_FREE(array);
+       return;
+}
 
 /*
   trigger a notify message for anyone waiting on a matching event