X-Git-Url: http://git.samba.org/samba.git/?p=nivanova%2Fsamba-autobuild%2F.git;a=blobdiff_plain;f=source3%2Fsmbd%2Fnotify.c;h=a70f86df007b486f1e2e9e5fd95ef70bbab96c49;hp=3b01c2c786791885a867be33c8454996b1aa17a9;hb=d5e6a47f064a3923b1e257ab84fa7ccd7c4f89f4;hpb=90f59d441223ba9b32b0d788901c9de5cf4b3bc7 diff --git a/source3/smbd/notify.c b/source3/smbd/notify.c index 3b01c2c7867..a70f86df007 100644 --- a/source3/smbd/notify.c +++ b/source3/smbd/notify.c @@ -3,10 +3,11 @@ change notify handling Copyright (C) Andrew Tridgell 2000 Copyright (C) Jeremy Allison 1994-1998 + Copyright (C) Volker Lendecke 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 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, @@ -15,31 +16,29 @@ 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 . */ #include "includes.h" - -static struct cnotify_fns *cnotify; -static struct notify_mid_map *notify_changes_by_mid; - -/**************************************************************************** - This is the structure to queue to implement NT change - notify. It consists of smb_size bytes stored from the - transact command (to keep the mid, tid etc around). - Plus the fid to examine and notify private data. -*****************************************************************************/ - -struct change_notify { - struct change_notify *next, *prev; - files_struct *fsp; - uint32 flags; - uint32 max_param_count; - char request_buf[smb_size]; - void *change_data; +#include "smbd/smbd.h" +#include "smbd/globals.h" +#include "../librpc/gen_ndr/ndr_notify.h" + +struct notify_change_request { + struct notify_change_request *prev, *next; + struct files_struct *fsp; /* backpointer for cancel by mid */ + struct smb_request *req; + uint32 filter; + uint32 max_param; + void (*reply_fn)(struct smb_request *req, + NTSTATUS error_code, + uint8_t *buf, size_t len); + struct notify_mid_map *mid_map; + void *backend_data; }; +static void notify_fsp(files_struct *fsp, uint32 action, const char *name); + /* * For NTCancel, we need to find the notify_change_request indexed by * mid. Separate list here. @@ -48,171 +47,213 @@ struct change_notify { struct notify_mid_map { struct notify_mid_map *prev, *next; struct notify_change_request *req; - uint16 mid; + uint64_t mid; }; -static struct change_notify *change_notify_list; +static bool notify_change_record_identical(struct notify_change *c1, + struct notify_change *c2) +{ + /* Note this is deliberately case sensitive. */ + if (c1->action == c2->action && + strcmp(c1->name, c2->name) == 0) { + return True; + } + return False; +} -static BOOL notify_marshall_changes(unsigned num_changes, - struct notify_change *changes, - prs_struct *ps) +static bool notify_marshall_changes(int num_changes, + uint32 max_offset, + struct notify_change *changes, + DATA_BLOB *final_blob) { int i; - UNISTR uni_name; + + if (num_changes == -1) { + return false; + } for (i=0; iname, strlen(c->name)+1, - &uni_name.buffer, True); - if ((namelen == -1) || (uni_name.buffer == NULL)) { - goto fail; + enum ndr_err_code ndr_err; + struct notify_change *c; + struct FILE_NOTIFY_INFORMATION m; + DATA_BLOB blob; + + /* Coalesce any identical records. */ + while (i+1 < num_changes && + notify_change_record_identical(&changes[i], + &changes[i+1])) { + i++; } - namelen -= 2; /* Dump NULL termination */ + c = &changes[i]; + + m.FileName1 = c->name; + m.FileNameLength = strlen_m(c->name)*2; + m.Action = c->action; + m.NextEntryOffset = (i == num_changes-1) ? 0 : ndr_size_FILE_NOTIFY_INFORMATION(&m, 0); /* * Offset to next entry, only if there is one */ - u32_tmp = (i == num_changes-1) ? 0 : namelen + 12; - if (!prs_uint32("offset", ps, 1, &u32_tmp)) goto fail; + ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &m, + (ndr_push_flags_fn_t)ndr_push_FILE_NOTIFY_INFORMATION); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return false; + } - u32_tmp = c->action; - if (!prs_uint32("action", ps, 1, &u32_tmp)) goto fail; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(FILE_NOTIFY_INFORMATION, &m); + } - u32_tmp = namelen; - if (!prs_uint32("namelen", ps, 1, &u32_tmp)) goto fail; + if (!data_blob_append(talloc_tos(), final_blob, + blob.data, blob.length)) { + data_blob_free(&blob); + return false; + } - if (!prs_unistr("name", ps, 1, &uni_name)) goto fail; + data_blob_free(&blob); - /* - * Not NULL terminated, decrease by the 2 UCS2 \0 chars - */ - prs_set_offset(ps, prs_offset(ps)-2); - - SAFE_FREE(uni_name.buffer); + if (final_blob->length > max_offset) { + /* Too much data for client. */ + DEBUG(10, ("Client only wanted %d bytes, trying to " + "marshall %d bytes\n", (int)max_offset, + (int)final_blob->length)); + return False; + } } return True; - - fail: - SAFE_FREE(uni_name.buffer); - return False; } /**************************************************************************** Setup the common parts of the return packet and send it. *****************************************************************************/ -void change_notify_reply_packet(const char *request_buf, NTSTATUS error_code) +void change_notify_reply(struct smb_request *req, + NTSTATUS error_code, + uint32_t max_param, + struct notify_change_buf *notify_buf, + void (*reply_fn)(struct smb_request *req, + NTSTATUS error_code, + uint8_t *buf, size_t len)) { - char outbuf[smb_size+38]; - - memset(outbuf, '\0', sizeof(outbuf)); - construct_reply_common(request_buf, outbuf); - - ERROR_NT(error_code); + DATA_BLOB blob = data_blob_null; - /* - * Seems NT needs a transact command with an error code - * in it. This is a longer packet than a simple error. - */ - set_message(outbuf,18,0,False); - - show_msg(outbuf); - if (!send_smb(smbd_server_fd(),outbuf)) - exit_server_cleanly("change_notify_reply_packet: send_smb failed."); -} + if (!NT_STATUS_IS_OK(error_code)) { + reply_fn(req, error_code, NULL, 0); + return; + } -void change_notify_reply(const char *request_buf, uint32 max_param_count, - unsigned num_changes, struct notify_change *changes) -{ - char *outbuf = NULL; - prs_struct ps; - size_t buflen = smb_size+38+max_param_count; - - if (!prs_init(&ps, 0, NULL, False) - || !notify_marshall_changes(num_changes, changes, &ps)) { - change_notify_reply_packet(request_buf, NT_STATUS_NO_MEMORY); - goto done; + if (max_param == 0 || notify_buf == NULL) { + reply_fn(req, NT_STATUS_OK, NULL, 0); + return; } - if (prs_offset(&ps) > max_param_count) { + if (!notify_marshall_changes(notify_buf->num_changes, max_param, + notify_buf->changes, &blob)) { /* * We exceed what the client is willing to accept. Send * nothing. */ - change_notify_reply_packet(request_buf, NT_STATUS_OK); - goto done; - } - - if (!(outbuf = SMB_MALLOC_ARRAY(char, buflen))) { - change_notify_reply_packet(request_buf, NT_STATUS_NO_MEMORY); - goto done; + data_blob_free(&blob); } - construct_reply_common(request_buf, outbuf); + reply_fn(req, NT_STATUS_OK, blob.data, blob.length); - if (send_nt_replies(outbuf, buflen, NT_STATUS_OK, prs_data_p(&ps), - prs_offset(&ps), NULL, 0) == -1) { - exit_server("change_notify_reply_packet: send_smb failed."); - } + data_blob_free(&blob); - done: - SAFE_FREE(outbuf); - prs_mem_free(&ps); + TALLOC_FREE(notify_buf->changes); + notify_buf->num_changes = 0; } -/**************************************************************************** - Remove an entry from the list and free it, also closing any - directory handle if necessary. -*****************************************************************************/ +static void notify_callback(void *private_data, const struct notify_event *e) +{ + files_struct *fsp = (files_struct *)private_data; + DEBUG(10, ("notify_callback called for %s\n", fsp_str_dbg(fsp))); + notify_fsp(fsp, e->action, e->path); +} -static void change_notify_remove(struct change_notify *cnbp) +NTSTATUS change_notify_create(struct files_struct *fsp, uint32 filter, + bool recursive) { - cnotify->remove_notify(cnbp->change_data); - DLIST_REMOVE(change_notify_list, cnbp); - ZERO_STRUCTP(cnbp); - SAFE_FREE(cnbp); + char *fullpath; + struct notify_entry e; + NTSTATUS status; + + SMB_ASSERT(fsp->notify == NULL); + + if (!(fsp->notify = TALLOC_ZERO_P(NULL, struct notify_change_buf))) { + DEBUG(0, ("talloc failed\n")); + return NT_STATUS_NO_MEMORY; + } + + /* Do notify operations on the base_name. */ + if (asprintf(&fullpath, "%s/%s", fsp->conn->connectpath, + fsp->fsp_name->base_name) == -1) { + DEBUG(0, ("asprintf failed\n")); + TALLOC_FREE(fsp->notify); + return NT_STATUS_NO_MEMORY; + } + + ZERO_STRUCT(e); + e.path = fullpath; + e.dir_fd = fsp->fh->fd; + e.dir_id = fsp->file_id; + e.filter = filter; + e.subdir_filter = 0; + if (recursive) { + e.subdir_filter = filter; + } + + status = notify_add(fsp->conn->notify_ctx, &e, notify_callback, fsp); + SAFE_FREE(fullpath); + + return status; } -NTSTATUS change_notify_add_request(const char *inbuf, uint32 max_param_count, - uint32 filter, struct files_struct *fsp) +NTSTATUS change_notify_add_request(struct smb_request *req, + uint32 max_param, + uint32 filter, bool recursive, + struct files_struct *fsp, + void (*reply_fn)(struct smb_request *req, + NTSTATUS error_code, + uint8_t *buf, size_t len)) { struct notify_change_request *request = NULL; struct notify_mid_map *map = NULL; + struct smbd_server_connection *sconn = req->sconn; + + DEBUG(10, ("change_notify_add_request: Adding request for %s: " + "max_param = %d\n", fsp_str_dbg(fsp), (int)max_param)); - if (!(request = SMB_MALLOC_P(struct notify_change_request)) - || !(map = SMB_MALLOC_P(struct notify_mid_map))) { - SAFE_FREE(request); + if (!(request = talloc(NULL, struct notify_change_request)) + || !(map = talloc(request, struct notify_mid_map))) { + TALLOC_FREE(request); return NT_STATUS_NO_MEMORY; } request->mid_map = map; map->req = request; - memcpy(request->request_buf, inbuf, sizeof(request->request_buf)); - request->max_param_count = max_param_count; + request->req = talloc_move(request, &req); + request->max_param = max_param; request->filter = filter; request->fsp = fsp; + request->reply_fn = reply_fn; + request->backend_data = NULL; + DLIST_ADD_END(fsp->notify->requests, request, struct notify_change_request *); - map->mid = SVAL(inbuf, smb_mid); - DLIST_ADD(notify_changes_by_mid, map); - - /* Push the MID of this packet on the signing queue. */ - srv_defer_sign_response(SVAL(inbuf,smb_mid)); + map->mid = request->req->mid; + DLIST_ADD(sconn->smb1.notify_mid_maps, map); return NT_STATUS_OK; } -static void change_notify_remove_request(struct notify_change_request *remove_req) +static void change_notify_remove_request(struct smbd_server_connection *sconn, + struct notify_change_request *remove_req) { files_struct *fsp; struct notify_change_request *req; @@ -232,24 +273,24 @@ static void change_notify_remove_request(struct notify_change_request *remove_re } if (req == NULL) { - smb_panic("notify_req not found in fsp's requests\n"); + smb_panic("notify_req not found in fsp's requests"); } DLIST_REMOVE(fsp->notify->requests, req); - DLIST_REMOVE(notify_changes_by_mid, req->mid_map); - SAFE_FREE(req->mid_map); - SAFE_FREE(req); + DLIST_REMOVE(sconn->smb1.notify_mid_maps, req->mid_map); + TALLOC_FREE(req); } /**************************************************************************** Delete entries by mid from the change notify pending queue. Always send reply. *****************************************************************************/ -void remove_pending_change_notify_requests_by_mid(uint16 mid) +void remove_pending_change_notify_requests_by_mid( + struct smbd_server_connection *sconn, uint64_t mid) { struct notify_mid_map *map; - for (map = notify_changes_by_mid; map; map = map->next) { + for (map = sconn->smb1.notify_mid_maps; map; map = map->next) { if (map->mid == mid) { break; } @@ -259,319 +300,88 @@ void remove_pending_change_notify_requests_by_mid(uint16 mid) return; } - change_notify_reply_packet(map->req->request_buf, NT_STATUS_CANCELLED); - change_notify_remove_request(map->req); -} - -/**************************************************************************** - Delete entries by fnum from the change notify pending queue. -*****************************************************************************/ - -void remove_pending_change_notify_requests_by_fid(files_struct *fsp, - NTSTATUS status) -{ - if (fsp->notify == NULL) { - return; - } - - while (fsp->notify->requests != NULL) { - change_notify_reply_packet( - fsp->notify->requests->request_buf, status); - change_notify_remove_request(fsp->notify->requests); - } + change_notify_reply(map->req->req, + NT_STATUS_CANCELLED, 0, NULL, map->req->reply_fn); + change_notify_remove_request(sconn, map->req); } -/**************************************************************************** - Delete entries by filename and cnum from the change notify pending queue. - Always send reply. -*****************************************************************************/ - -void remove_pending_change_notify_requests_by_filename(files_struct *fsp, NTSTATUS status) +void smbd_notify_cancel_by_smbreq(const struct smb_request *smbreq) { - struct change_notify *cnbp, *next; + struct smbd_server_connection *sconn = smbreq->sconn; + struct notify_mid_map *map; - for (cnbp=change_notify_list; cnbp; cnbp=next) { - next=cnbp->next; - /* - * We know it refers to the same directory if the connection number and - * the filename are identical. - */ - if((cnbp->fsp->conn == fsp->conn) && strequal(cnbp->fsp->fsp_name,fsp->fsp_name)) { - change_notify_reply_packet(cnbp->request_buf, status); - change_notify_remove(cnbp); + for (map = sconn->smb1.notify_mid_maps; map; map = map->next) { + if (map->req->req == smbreq) { + break; } } -} - -/**************************************************************************** - Set the current change notify timeout to the lowest value across all service - values. -****************************************************************************/ -void set_change_notify_timeout(int val) -{ - if (val > 0) { - cnotify->select_time = MIN(cnotify->select_time, val); + if (map == NULL) { + return; } -} -/**************************************************************************** - Longest time to sleep for before doing a change notify scan. -****************************************************************************/ - -int change_notify_timeout(void) -{ - return cnotify->select_time; + change_notify_reply(map->req->req, + NT_STATUS_CANCELLED, 0, NULL, map->req->reply_fn); + change_notify_remove_request(sconn, map->req); } /**************************************************************************** - Process the change notify queue. Note that this is only called as root. - Returns True if there are still outstanding change notify requests on the - queue. + Delete entries by fnum from the change notify pending queue. *****************************************************************************/ -BOOL process_pending_change_notify_queue(time_t t) -{ - struct change_notify *cnbp, *next; - uint16 vuid; - - for (cnbp=change_notify_list; cnbp; cnbp=next) { - next=cnbp->next; - - vuid = (lp_security() == SEC_SHARE) ? UID_FIELD_INVALID : SVAL(cnbp->request_buf,smb_uid); - - if (cnbp->fsp->notify->num_changes != 0) { - DEBUG(10,("process_pending_change_notify_queue: %s " - "has %d changes!\n", cnbp->fsp->fsp_name, - cnbp->fsp->notify->num_changes)); - change_notify_reply(cnbp->request_buf, - cnbp->max_param_count, - cnbp->fsp->notify->num_changes, - cnbp->fsp->notify->changes); - change_notify_remove(cnbp); - continue; - } - - if (cnotify->check_notify(cnbp->fsp->conn, vuid, - cnbp->fsp->fsp_name, cnbp->flags, - cnbp->change_data, t)) { - DEBUG(10,("process_pending_change_notify_queue: dir " - "%s changed !\n", cnbp->fsp->fsp_name )); - change_notify_reply(cnbp->request_buf, - cnbp->max_param_count, - cnbp->fsp->notify->num_changes, - cnbp->fsp->notify->changes); - change_notify_remove(cnbp); - } - } - - return (change_notify_list != NULL); -} - -/**************************************************************************** - Now queue an entry on the notify change list. - We only need to save smb_size bytes from this incoming packet - as we will always by returning a 'read the directory yourself' - error. -****************************************************************************/ - -BOOL change_notify_set(char *inbuf, files_struct *fsp, connection_struct *conn, - uint32 flags, uint32 max_param_count) -{ - struct change_notify *cnbp; - - if((cnbp = SMB_MALLOC_P(struct change_notify)) == NULL) { - DEBUG(0,("change_notify_set: malloc fail !\n" )); - return False; - } - - ZERO_STRUCTP(cnbp); - - memcpy(cnbp->request_buf, inbuf, smb_size); - cnbp->fsp = fsp; - cnbp->flags = flags; - cnbp->max_param_count = max_param_count; - cnbp->change_data = cnotify->register_notify(conn, fsp->fsp_name, - flags); - - if (!cnbp->change_data) { - SAFE_FREE(cnbp); - return False; - } - - DLIST_ADD(change_notify_list, cnbp); - - /* Push the MID of this packet on the signing queue. */ - srv_defer_sign_response(SVAL(inbuf,smb_mid)); - - return True; -} - -int change_notify_fd(void) -{ - if (cnotify) { - return cnotify->notification_fd; - } - - return -1; -} - -/* notify message definition - -Offset Data length. -0 SMB_DEV_T dev 8 -8 SMB_INO_T inode 8 -16 uint32 filter 4 -20 uint32 action 4 -24.. name -*/ - -#define MSG_NOTIFY_MESSAGE_SIZE 25 /* Includes at least the '\0' terminator */ - -struct notify_message { - SMB_DEV_T dev; - SMB_INO_T inode; - uint32 filter; - uint32 action; - char *name; -}; - -static DATA_BLOB notify_message_to_buf(const struct notify_message *msg) +void remove_pending_change_notify_requests_by_fid(files_struct *fsp, + NTSTATUS status) { - DATA_BLOB result; - size_t len; - - len = strlen(msg->name); - - result = data_blob(NULL, MSG_NOTIFY_MESSAGE_SIZE + len); - if (!result.data) { - return result; + if (fsp->notify == NULL) { + return; } - SDEV_T_VAL(result.data, 0, msg->dev); - SINO_T_VAL(result.data, 8, msg->inode); - SIVAL(result.data, 16, msg->filter); - SIVAL(result.data, 20, msg->action); - memcpy(result.data+24, msg->name, len+1); - - return result; -} - -static BOOL buf_to_notify_message(void *buf, size_t len, - struct notify_message *msg) -{ - if (len < MSG_NOTIFY_MESSAGE_SIZE) { - DEBUG(0, ("Got invalid notify message of len %d\n", - (int)len)); - return False; + while (fsp->notify->requests != NULL) { + change_notify_reply(fsp->notify->requests->req, + status, 0, NULL, + fsp->notify->requests->reply_fn); + change_notify_remove_request(fsp->conn->sconn, + fsp->notify->requests); } - - msg->dev = DEV_T_VAL(buf, 0); - msg->inode = INO_T_VAL(buf, 8); - msg->filter = IVAL(buf, 16); - msg->action = IVAL(buf, 20); - msg->name = ((char *)buf)+24; - return True; } -void notify_action(connection_struct *conn, const char *parent, - const char *name, uint32 filter, uint32_t action) +void notify_fname(connection_struct *conn, uint32 action, uint32 filter, + const char *path) { - struct share_mode_lock *lck; - SMB_STRUCT_STAT sbuf; - int i; - struct notify_message msg; - DATA_BLOB blob; - - struct process_id *pids; - int num_pids; - - DEBUG(10, ("notify_action: parent=%s, name=%s, action=%u\n", - parent, name, (unsigned)action)); - - if (SMB_VFS_STAT(conn, parent, &sbuf) != 0) { - /* - * Not 100% critical, ignore failure - */ - return; - } - - if (!(lck = get_share_mode_lock(NULL, sbuf.st_dev, sbuf.st_ino, - NULL, NULL))) { - return; - } - - msg.dev = sbuf.st_dev; - msg.inode = sbuf.st_ino; - msg.filter = filter; - msg.action = action; - msg.name = CONST_DISCARD(char *, name); + char *fullpath; + char *parent; + const char *name; - blob = notify_message_to_buf(&msg); - if (blob.data == NULL) { - DEBUG(0, ("notify_message_to_buf failed\n")); - return; + if (path[0] == '.' && path[1] == '/') { + path += 2; } + if (parent_dirname(talloc_tos(), path, &parent, &name)) { + struct smb_filename smb_fname_parent; - pids = NULL; - num_pids = 0; - - become_root_uid_only(); - - for (i=0; inum_share_modes; i++) { - struct share_mode_entry *e = &lck->share_modes[i]; - int j; - struct process_id *tmp; - - for (j=0; jpid, &pids[j])) { - break; - } - } - - if (j < num_pids) { - /* - * Already sent to that process, skip it - */ - continue; - } - - message_send_pid(lck->share_modes[i].pid, MSG_SMB_NOTIFY, - blob.data, blob.length, True); + ZERO_STRUCT(smb_fname_parent); + smb_fname_parent.base_name = parent; - if (!(tmp = TALLOC_REALLOC_ARRAY(lck, pids, struct process_id, - num_pids+1))) { - DEBUG(0, ("realloc failed\n")); - break; + if (SMB_VFS_STAT(conn, &smb_fname_parent) != -1) { + notify_onelevel(conn->notify_ctx, action, filter, + SMB_VFS_FILE_ID_CREATE(conn, &smb_fname_parent.st), + name); } - pids = tmp; - pids[num_pids] = e->pid; - num_pids += 1; } - unbecome_root_uid_only(); - - data_blob_free(&blob); - TALLOC_FREE(lck); -} - -void notify_fname(connection_struct *conn, const char *path, - uint32 filter, uint32 action) -{ - char *parent; - const char *name; - - if (!parent_dirname_talloc(tmp_talloc_ctx(), path, &parent, &name)) { + fullpath = talloc_asprintf(talloc_tos(), "%s/%s", conn->connectpath, + path); + if (fullpath == NULL) { + DEBUG(0, ("asprintf failed\n")); return; } - - notify_action(conn, parent, name, filter, action); - TALLOC_FREE(parent); + notify_trigger(conn->notify_ctx, action, filter, fullpath); + TALLOC_FREE(fullpath); } -static void notify_fsp(files_struct *fsp, struct notify_message *msg) +static void notify_fsp(files_struct *fsp, uint32 action, const char *name) { struct notify_change *change, *changes; + char *tmp; if (fsp->notify == NULL) { /* @@ -580,34 +390,41 @@ static void notify_fsp(files_struct *fsp, struct notify_message *msg) return; } - if ((fsp->notify->requests != NULL) - && (fsp->notify->requests->filter & msg->filter)) { + /* + * Someone has triggered a notify previously, queue the change for + * later. + */ + + if ((fsp->notify->num_changes > 1000) || (name == NULL)) { /* - * Someone is waiting for the change, trigger the reply - * immediately. - * - * TODO: do we have to walk the lists of requests pending? + * The real number depends on the client buf, just provide a + * guard against a DoS here. If name == NULL the CN backend is + * alerting us to a problem. Possibly dropped events. Clear + * queued changes and send the catch-all response to the client + * if a request is pending. */ - - struct notify_change_request *req = fsp->notify->requests; - struct notify_change onechange; - - onechange.action = msg->action; - onechange.name = msg->name; - - change_notify_reply(req->request_buf, req->max_param_count, - 1, &onechange); - change_notify_remove_request(req); + TALLOC_FREE(fsp->notify->changes); + fsp->notify->num_changes = -1; + if (fsp->notify->requests != NULL) { + change_notify_reply(fsp->notify->requests->req, + NT_STATUS_OK, + fsp->notify->requests->max_param, + fsp->notify, + fsp->notify->requests->reply_fn); + change_notify_remove_request(fsp->conn->sconn, + fsp->notify->requests); + } return; } - /* - * Someone has triggered a notify previously, queue the change for - * later. TODO: Limit the number of changes queued, test how filters - * apply here. Do we have to store them? - */ + /* If we've exceeded the server side queue or received a NULL name + * from the underlying CN implementation, don't queue up any more + * requests until we can send a catch-all response to the client */ + if (fsp->notify->num_changes == -1) { + return; + } - if (!(changes = TALLOC_REALLOC_ARRAY( + if (!(changes = talloc_realloc( fsp->notify, fsp->notify->changes, struct notify_change, fsp->notify->num_changes+1))) { DEBUG(0, ("talloc_realloc failed\n")); @@ -618,59 +435,110 @@ static void notify_fsp(files_struct *fsp, struct notify_message *msg) change = &(fsp->notify->changes[fsp->notify->num_changes]); - if (!(change->name = talloc_strdup(changes, msg->name))) { + if (!(tmp = talloc_strdup(changes, name))) { DEBUG(0, ("talloc_strdup failed\n")); return; } - change->action = msg->action; - fsp->notify->num_changes += 1; - return; -} + string_replace(tmp, '/', '\\'); + change->name = tmp; -static void notify_message_callback(int msgtype, struct process_id pid, - void *buf, size_t len) -{ - struct notify_message msg; - files_struct *fsp; + change->action = action; + fsp->notify->num_changes += 1; - if (!buf_to_notify_message(buf, len, &msg)) { + if (fsp->notify->requests == NULL) { + /* + * Nobody is waiting, so don't send anything. The ot + */ return; } - DEBUG(10, ("Received notify_message for 0x%x/%.0f: %d\n", - (unsigned)msg.dev, (double)msg.inode, msg.action)); - - for(fsp = fsp_find_di_first(msg.dev, msg.inode); fsp; - fsp = fsp_find_di_next(fsp)) { - notify_fsp(fsp, &msg); + if (action == NOTIFY_ACTION_OLD_NAME) { + /* + * We have to send the two rename events in one reply. So hold + * the first part back. + */ + return; } + + /* + * Someone is waiting for the change, trigger the reply immediately. + * + * TODO: do we have to walk the lists of requests pending? + */ + + change_notify_reply(fsp->notify->requests->req, + NT_STATUS_OK, + fsp->notify->requests->max_param, + fsp->notify, + fsp->notify->requests->reply_fn); + + change_notify_remove_request(fsp->conn->sconn, fsp->notify->requests); } -/**************************************************************************** - Initialise the change notify subsystem. -****************************************************************************/ +char *notify_filter_string(TALLOC_CTX *mem_ctx, uint32 filter) +{ + char *result = NULL; + + result = talloc_strdup(mem_ctx, ""); + + if (filter & FILE_NOTIFY_CHANGE_FILE_NAME) + result = talloc_asprintf_append(result, "FILE_NAME|"); + if (filter & FILE_NOTIFY_CHANGE_DIR_NAME) + result = talloc_asprintf_append(result, "DIR_NAME|"); + if (filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) + result = talloc_asprintf_append(result, "ATTRIBUTES|"); + if (filter & FILE_NOTIFY_CHANGE_SIZE) + result = talloc_asprintf_append(result, "SIZE|"); + if (filter & FILE_NOTIFY_CHANGE_LAST_WRITE) + result = talloc_asprintf_append(result, "LAST_WRITE|"); + if (filter & FILE_NOTIFY_CHANGE_LAST_ACCESS) + result = talloc_asprintf_append(result, "LAST_ACCESS|"); + if (filter & FILE_NOTIFY_CHANGE_CREATION) + result = talloc_asprintf_append(result, "CREATION|"); + if (filter & FILE_NOTIFY_CHANGE_EA) + result = talloc_asprintf_append(result, "EA|"); + if (filter & FILE_NOTIFY_CHANGE_SECURITY) + result = talloc_asprintf_append(result, "SECURITY|"); + if (filter & FILE_NOTIFY_CHANGE_STREAM_NAME) + result = talloc_asprintf_append(result, "STREAM_NAME|"); + if (filter & FILE_NOTIFY_CHANGE_STREAM_SIZE) + result = talloc_asprintf_append(result, "STREAM_SIZE|"); + if (filter & FILE_NOTIFY_CHANGE_STREAM_WRITE) + result = talloc_asprintf_append(result, "STREAM_WRITE|"); + + if (result == NULL) return NULL; + if (*result == '\0') return result; + + result[strlen(result)-1] = '\0'; + return result; +} -BOOL init_change_notify(void) +struct sys_notify_context *sys_notify_context_create(connection_struct *conn, + TALLOC_CTX *mem_ctx, + struct event_context *ev) { - cnotify = NULL; - -#if HAVE_KERNEL_CHANGE_NOTIFY - if (cnotify == NULL && lp_kernel_change_notify()) - cnotify = kernel_notify_init(); -#endif -#if HAVE_FAM_CHANGE_NOTIFY - if (cnotify == NULL && lp_fam_change_notify()) - cnotify = fam_notify_init(); -#endif - if (!cnotify) cnotify = hash_notify_init(); - - if (!cnotify) { - DEBUG(0,("Failed to init change notify system\n")); - return False; + struct sys_notify_context *ctx; + + if (!(ctx = talloc(mem_ctx, struct sys_notify_context))) { + DEBUG(0, ("talloc failed\n")); + return NULL; } - message_register(MSG_SMB_NOTIFY, notify_message_callback); + ctx->ev = ev; + ctx->conn = conn; + ctx->private_data = NULL; + return ctx; +} - return True; +NTSTATUS sys_notify_watch(struct sys_notify_context *ctx, + struct notify_entry *e, + void (*callback)(struct sys_notify_context *ctx, + void *private_data, + struct notify_event *ev), + void *private_data, void *handle) +{ + return SMB_VFS_NOTIFY_WATCH(ctx->conn, ctx, e, callback, private_data, + handle); } +