X-Git-Url: http://git.samba.org/samba.git/?p=tprouty%2Fsamba.git;a=blobdiff_plain;f=source%2Fsmbd%2Fnotify.c;h=6ab4266c19f04410cdc49b9bea876a10ec19bdf1;hp=6bedb17261f8b7b60d1a48b076406417548cd18a;hb=b0132e94fc5fef936aa766fb99a306b3628e9f07;hpb=33b5950bec407e295b2c6139e21d062e376df330 diff --git a/source/smbd/notify.c b/source/smbd/notify.c index 6bedb17261..6ab4266c19 100644 --- a/source/smbd/notify.c +++ b/source/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,34 +16,51 @@ 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; +/* Max size we can send to client in a notify response. */ +extern int max_send; + +struct notify_change_request { + struct notify_change_request *prev, *next; + struct files_struct *fsp; /* backpointer for cancel by mid */ + char request_buf[smb_size]; + uint32 filter; + uint32 current_bufsize; + struct notify_mid_map *mid_map; + void *backend_data; +}; + +static void notify_fsp(files_struct *fsp, uint32 action, const char *name); + 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. -*****************************************************************************/ +/* + * For NTCancel, we need to find the notify_change_request indexed by + * mid. Separate list here. + */ -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; +struct notify_mid_map { + struct notify_mid_map *prev, *next; + struct notify_change_request *req; + uint16 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, +static BOOL notify_marshall_changes(int num_changes, struct notify_change *changes, prs_struct *ps) { @@ -50,11 +68,20 @@ static BOOL notify_marshall_changes(unsigned num_changes, UNISTR uni_name; for (i=0; iname, strlen(c->name)+1, &uni_name.buffer, True); @@ -98,8 +125,10 @@ static BOOL notify_marshall_changes(unsigned num_changes, Setup the common parts of the return packet and send it. *****************************************************************************/ -void change_notify_reply_packet(const char *request_buf, NTSTATUS error_code) +static void change_notify_reply_packet(const char *request_buf, + NTSTATUS error_code) { + const char *inbuf = request_buf; char outbuf[smb_size+38]; memset(outbuf, '\0', sizeof(outbuf)); @@ -111,27 +140,36 @@ void change_notify_reply_packet(const char *request_buf, NTSTATUS error_code) * 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); + set_message(inbuf,outbuf,18,0,False); show_msg(outbuf); if (!send_smb(smbd_server_fd(),outbuf)) - exit_server_cleanly("change_notify_reply_packet: send_smb failed."); + exit_server_cleanly("change_notify_reply_packet: send_smb " + "failed."); } -void change_notify_reply(const char *request_buf, uint32 max_param_count, - unsigned num_changes, struct notify_change *changes) +void change_notify_reply(const char *request_buf, + struct notify_change_buf *notify_buf) { char *outbuf = NULL; prs_struct ps; - size_t buflen = smb_size+38+max_param_count; + size_t buflen; + + if (notify_buf->num_changes == -1) { + change_notify_reply_packet(request_buf, NT_STATUS_OK); + return; + } if (!prs_init(&ps, 0, NULL, False) - || !notify_marshall_changes(num_changes, changes, &ps)) { + || !notify_marshall_changes(notify_buf->num_changes, + notify_buf->changes, &ps)) { change_notify_reply_packet(request_buf, NT_STATUS_NO_MEMORY); goto done; } - if (prs_offset(&ps) > max_param_count) { + buflen = smb_size+38+prs_offset(&ps) + 4 /* padding */; + + if (buflen > max_send) { /* * We exceed what the client is willing to accept. Send * nothing. @@ -147,7 +185,7 @@ void change_notify_reply(const char *request_buf, uint32 max_param_count, construct_reply_common(request_buf, outbuf); - if (send_nt_replies(outbuf, buflen, NT_STATUS_OK, prs_data_p(&ps), + if (send_nt_replies(request_buf, outbuf, buflen, NT_STATUS_OK, prs_data_p(&ps), prs_offset(&ps), NULL, 0) == -1) { exit_server("change_notify_reply_packet: send_smb failed."); } @@ -155,23 +193,54 @@ void change_notify_reply(const char *request_buf, uint32 max_param_count, 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->fsp_name)); + 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; + } + + if (asprintf(&fullpath, "%s/%s", fsp->conn->connectpath, + fsp->fsp_name) == -1) { + DEBUG(0, ("asprintf failed\n")); + return NT_STATUS_NO_MEMORY; + } + + e.path = fullpath; + 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(const char *inbuf, + uint32 filter, BOOL recursive, + struct files_struct *fsp) { struct notify_change_request *request = NULL; struct notify_mid_map *map = NULL; @@ -186,9 +255,11 @@ NTSTATUS change_notify_add_request(const char *inbuf, uint32 max_param_count, map->req = request; memcpy(request->request_buf, inbuf, sizeof(request->request_buf)); - request->max_param_count = max_param_count; + request->current_bufsize = 0; request->filter = filter; request->fsp = fsp; + request->backend_data = NULL; + DLIST_ADD_END(fsp->notify->requests, request, struct notify_change_request *); @@ -221,12 +292,13 @@ 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); + TALLOC_FREE(req->backend_data); SAFE_FREE(req); } @@ -270,396 +342,163 @@ void remove_pending_change_notify_requests_by_fid(files_struct *fsp, } } -/**************************************************************************** - 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) -{ - struct change_notify *cnbp, *next; - - 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); - } - } -} - -/**************************************************************************** - 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); - } -} - -/**************************************************************************** - Longest time to sleep for before doing a change notify scan. -****************************************************************************/ - -int change_notify_timeout(void) -{ - return cnotify->select_time; -} - -/**************************************************************************** - 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. -*****************************************************************************/ - -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) +void notify_fname(connection_struct *conn, uint32 action, uint32 filter, + const char *path) { - struct change_notify *cnbp; + char *fullpath; - 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; + if (asprintf(&fullpath, "%s/%s", conn->connectpath, path) == -1) { + DEBUG(0, ("asprintf failed\n")); + return; } - 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; + notify_trigger(conn->notify_ctx, action, filter, fullpath); + SAFE_FREE(fullpath); } -int change_notify_fd(void) +static void notify_fsp(files_struct *fsp, uint32 action, const char *name) { - 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) -{ - DATA_BLOB result; - size_t len; - - len = strlen(msg->name); - - result = data_blob(NULL, MSG_NOTIFY_MESSAGE_SIZE + len); - if (!result.data) { - return result; - } - - 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; -} + struct notify_change *change, *changes; + char *tmp; -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; + if (fsp->notify == NULL) { + /* + * Nobody is waiting, don't queue + */ + return; } - 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) -{ - 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)); + /* + * Someone has triggered a notify previously, queue the change for + * later. + */ - if (SMB_VFS_STAT(conn, parent, &sbuf) != 0) { + if ((fsp->notify->num_changes > 1000) || (name == NULL)) { /* - * Not 100% critical, ignore failure + * The real number depends on the client buf, just provide a + * guard against a DoS here. */ + TALLOC_FREE(fsp->notify->changes); + fsp->notify->num_changes = -1; return; } - if (!(lck = get_share_mode_lock(NULL, sbuf.st_dev, sbuf.st_ino, - NULL, NULL))) { + if (fsp->notify->num_changes == -1) { return; } - msg.dev = sbuf.st_dev; - msg.inode = sbuf.st_ino; - msg.filter = filter; - msg.action = action; - msg.name = CONST_DISCARD(char *, name); - - blob = notify_message_to_buf(&msg); - if (blob.data == NULL) { - DEBUG(0, ("notify_message_to_buf failed\n")); + if (!(changes = TALLOC_REALLOC_ARRAY( + fsp->notify, fsp->notify->changes, + struct notify_change, fsp->notify->num_changes+1))) { + DEBUG(0, ("talloc_realloc failed\n")); return; } - 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); - - if (!(tmp = TALLOC_REALLOC_ARRAY(lck, pids, struct process_id, - num_pids+1))) { - DEBUG(0, ("realloc failed\n")); - break; - } - pids = tmp; - pids[num_pids] = e->pid; - num_pids += 1; - } - - unbecome_root_uid_only(); - - data_blob_free(&blob); - TALLOC_FREE(lck); -} + fsp->notify->changes = changes; -void notify_fname(connection_struct *conn, const char *path, - uint32 filter, uint32 action) -{ - char *parent; - const char *name; + change = &(fsp->notify->changes[fsp->notify->num_changes]); - if (!parent_dirname_talloc(tmp_talloc_ctx(), path, &parent, &name)) { + if (!(tmp = talloc_strdup(changes, name))) { + DEBUG(0, ("talloc_strdup failed\n")); return; } - notify_action(conn, parent, name, filter, action); - TALLOC_FREE(parent); -} + string_replace(tmp, '/', '\\'); + change->name = tmp; -static void notify_fsp(files_struct *fsp, struct notify_message *msg) -{ - struct notify_change *change, *changes; + change->action = action; + fsp->notify->num_changes += 1; - if (fsp->notify == NULL) { + if (fsp->notify->requests == NULL) { /* - * Nobody is waiting, don't queue + * Nobody is waiting, so don't send anything. The ot */ return; } - if ((fsp->notify->requests != NULL) - && (fsp->notify->requests->filter & msg->filter)) { + if (action == NOTIFY_ACTION_OLD_NAME) { /* - * Someone is waiting for the change, trigger the reply - * immediately. - * - * TODO: do we have to walk the lists of requests pending? + * We have to send the two rename events in one reply. So hold + * the first part back. */ - - 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); 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? + * Someone is waiting for the change, trigger the reply immediately. + * + * TODO: do we have to walk the lists of requests pending? */ - if (!(changes = TALLOC_REALLOC_ARRAY( - fsp->notify, fsp->notify->changes, - struct notify_change, fsp->notify->num_changes+1))) { - DEBUG(0, ("talloc_realloc failed\n")); - return; - } + change_notify_reply(fsp->notify->requests->request_buf, + fsp->notify); - fsp->notify->changes = changes; - - change = &(fsp->notify->changes[fsp->notify->num_changes]); - - if (!(change->name = talloc_strdup(changes, msg->name))) { - DEBUG(0, ("talloc_strdup failed\n")); - return; - } - change->action = msg->action; - fsp->notify->num_changes += 1; + change_notify_remove_request(fsp->notify->requests); +} - return; +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; } -static void notify_message_callback(int msgtype, struct process_id pid, - void *buf, size_t len) +struct sys_notify_context *sys_notify_context_create(connection_struct *conn, + TALLOC_CTX *mem_ctx, + struct event_context *ev) { - struct notify_message msg; - files_struct *fsp; + struct sys_notify_context *ctx; - if (!buf_to_notify_message(buf, len, &msg)) { - return; + if (!(ctx = TALLOC_P(mem_ctx, struct sys_notify_context))) { + DEBUG(0, ("talloc failed\n")); + return NULL; } - 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); - } + ctx->ev = ev; + ctx->conn = conn; + ctx->private_data = NULL; + return ctx; } -/**************************************************************************** - Initialise the change notify subsystem. -****************************************************************************/ - -BOOL init_change_notify(void) +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) { - 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; - } - - message_register(MSG_SMB_NOTIFY, notify_message_callback); - - return True; + return SMB_VFS_NOTIFY_WATCH(ctx->conn, ctx, e, callback, private_data, + handle); } +