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=2907bd7e0f47ff438eae32a179676877be608399;hb=d5e6a47f064a3923b1e257ab84fa7ccd7c4f89f4;hpb=3af5838096872039ac201385763f1e4c0fafb034 diff --git a/source3/smbd/notify.c b/source3/smbd/notify.c index 2907bd7e0f4..a70f86df007 100644 --- a/source3/smbd/notify.c +++ b/source3/smbd/notify.c @@ -7,7 +7,7 @@ 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, @@ -16,14 +16,28 @@ 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" +#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 struct cnotify_fns *cnotify; -static struct notify_mid_map *notify_changes_by_mid; +static void notify_fsp(files_struct *fsp, uint32 action, const char *name); /* * For NTCancel, we need to find the notify_change_request indexed by @@ -33,167 +47,213 @@ static struct notify_mid_map *notify_changes_by_mid; struct notify_mid_map { struct notify_mid_map *prev, *next; struct notify_change_request *req; - uint16 mid; + uint64_t mid; }; -static BOOL notify_marshall_changes(int num_changes, - struct notify_change *changes, - prs_struct *ps) +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(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; - - u32_tmp = c->action; - if (!prs_uint32("action", 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 = namelen; - if (!prs_uint32("namelen", ps, 1, &u32_tmp)) goto fail; + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(FILE_NOTIFY_INFORMATION, &m); + } - if (!prs_unistr("name", ps, 1, &uni_name)) goto fail; + if (!data_blob_append(talloc_tos(), final_blob, + blob.data, blob.length)) { + data_blob_free(&blob); + return false; + } - /* - * Not NULL terminated, decrease by the 2 UCS2 \0 chars - */ - prs_set_offset(ps, prs_offset(ps)-2); + data_blob_free(&blob); - 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. *****************************************************************************/ -static 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]; + DATA_BLOB blob = data_blob_null; - memset(outbuf, '\0', sizeof(outbuf)); - construct_reply_common(request_buf, outbuf); - - ERROR_NT(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); - - show_msg(outbuf); - if (!send_smb(smbd_server_fd(),outbuf)) - exit_server_cleanly("change_notify_reply_packet: send_smb " - "failed."); -} - -void change_notify_reply(const char *request_buf, uint32 max_param_count, - int num_changes, struct notify_change *changes) -{ - char *outbuf = NULL; - prs_struct ps; - size_t buflen = smb_size+38+max_param_count; - - if (num_changes == -1) { - change_notify_reply_packet(request_buf, NT_STATUS_OK); + if (!NT_STATUS_IS_OK(error_code)) { + reply_fn(req, error_code, NULL, 0); return; } - 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; + data_blob_free(&blob); } - if (!(outbuf = SMB_MALLOC_ARRAY(char, buflen))) { - change_notify_reply_packet(request_buf, NT_STATUS_NO_MEMORY); - goto done; + reply_fn(req, NT_STATUS_OK, blob.data, blob.length); + + data_blob_free(&blob); + + TALLOC_FREE(notify_buf->changes); + notify_buf->num_changes = 0; +} + +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); +} + +NTSTATUS change_notify_create(struct files_struct *fsp, uint32 filter, + bool recursive) +{ + 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; } - construct_reply_common(request_buf, outbuf); + /* 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; + } - 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."); + 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; } - done: - SAFE_FREE(outbuf); - prs_mem_free(&ps); + 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; - if (!(request = SMB_MALLOC_P(struct notify_change_request)) - || !(map = SMB_MALLOC_P(struct notify_mid_map))) { - SAFE_FREE(request); + DEBUG(10, ("change_notify_add_request: Adding request for %s: " + "max_param = %d\n", fsp_str_dbg(fsp), (int)max_param)); + + 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; - request->backend_data = cnotify->notify_add(NULL, smbd_event_context(), - fsp, &request->filter); - 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; @@ -213,25 +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); - TALLOC_FREE(req->backend_data); - 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; } @@ -241,8 +300,29 @@ 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); + change_notify_reply(map->req->req, + NT_STATUS_CANCELLED, 0, NULL, map->req->reply_fn); + change_notify_remove_request(sconn, map->req); +} + +void smbd_notify_cancel_by_smbreq(const struct smb_request *smbreq) +{ + struct smbd_server_connection *sconn = smbreq->sconn; + struct notify_mid_map *map; + + for (map = sconn->smb1.notify_mid_maps; map; map = map->next) { + if (map->req->req == smbreq) { + break; + } + } + + if (map == NULL) { + return; + } + + change_notify_reply(map->req->req, + NT_STATUS_CANCELLED, 0, NULL, map->req->reply_fn); + change_notify_remove_request(sconn, map->req); } /**************************************************************************** @@ -257,168 +337,51 @@ void remove_pending_change_notify_requests_by_fid(files_struct *fsp, } while (fsp->notify->requests != NULL) { - change_notify_reply_packet( - fsp->notify->requests->request_buf, status); - change_notify_remove_request(fsp->notify->requests); - } -} - -/* 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; -} - -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; + 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; + ZERO_STRUCT(smb_fname_parent); + smb_fname_parent.base_name = parent; - for (j=0; jpid, &pids[j])) { - 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); } - - 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); -} - -void notify_fname(connection_struct *conn, uint32 action, uint32 filter, - const char *path) -{ - 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); } -void notify_fsp(files_struct *fsp, uint32 action, char *name) +static void notify_fsp(files_struct *fsp, uint32 action, const char *name) { struct notify_change *change, *changes; + char *tmp; if (fsp->notify == NULL) { /* @@ -427,56 +390,41 @@ void notify_fsp(files_struct *fsp, uint32 action, char *name) return; } - if (fsp->notify->requests != NULL) { - /* - * Someone is waiting for the change, trigger the reply - * immediately. - * - * TODO: do we have to walk the lists of requests pending? - */ - - struct notify_change_request *req = fsp->notify->requests; - struct notify_change onechange; - - if (name == NULL) { - /* - * Catch-all change, possibly from notify_hash.c - */ - change_notify_reply(req->request_buf, - req->max_param_count, - -1, NULL); - return; - } - - onechange.action = action; - onechange.name = 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? + * later. */ - if ((fsp->notify->num_changes > 30) || (name == NULL)) { + if ((fsp->notify->num_changes > 1000) || (name == NULL)) { /* - * W2k3 seems to store at most 30 changes. + * 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. */ 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; } + /* 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")); @@ -487,38 +435,45 @@ void notify_fsp(files_struct *fsp, uint32 action, char *name) change = &(fsp->notify->changes[fsp->notify->num_changes]); - if (!(change->name = talloc_strdup(changes, name))) { + if (!(tmp = talloc_strdup(changes, name))) { DEBUG(0, ("talloc_strdup failed\n")); return; } + + string_replace(tmp, '/', '\\'); + change->name = tmp; + change->action = action; fsp->notify->num_changes += 1; - return; -} - -static void notify_message_callback(int msgtype, struct process_id pid, - void *buf, size_t len, - void *private_data) -{ - struct notify_message msg; - files_struct *fsp; + if (fsp->notify->requests == NULL) { + /* + * Nobody is waiting, so don't send anything. The ot + */ + return; + } - if (!buf_to_notify_message(buf, len, &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; } - DEBUG(10, ("Received notify_message for 0x%x/%.0f: %d\n", - (unsigned)msg.dev, (double)msg.inode, msg.action)); + /* + * Someone is waiting for the change, trigger the reply immediately. + * + * TODO: do we have to walk the lists of requests pending? + */ - for(fsp = fsp_find_di_first(msg.dev, msg.inode); fsp; - fsp = fsp_find_di_next(fsp)) { - if ((fsp->notify != NULL) - && (fsp->notify->requests != NULL) - && (fsp->notify->requests->filter & msg.filter)) { - notify_fsp(fsp, msg.action, msg.name); - } - } + 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); } char *notify_filter_string(TALLOC_CTX *mem_ctx, uint32 filter) @@ -559,46 +514,19 @@ char *notify_filter_string(TALLOC_CTX *mem_ctx, uint32 filter) return result; } -/**************************************************************************** - Initialise the change notify subsystem. -****************************************************************************/ - -BOOL init_change_notify(void) -{ - cnotify = NULL; - -#if HAVE_KERNEL_CHANGE_NOTIFY - if (cnotify == NULL && lp_kernel_change_notify()) - cnotify = kernel_notify_init(smbd_event_context()); -#endif -#if HAVE_FAM_CHANGE_NOTIFY - if (cnotify == NULL && lp_fam_change_notify()) - cnotify = fam_notify_init(smbd_event_context()); -#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, NULL); - - return True; -} - -struct sys_notify_context *sys_notify_context_create(struct share_params *scfg, +struct sys_notify_context *sys_notify_context_create(connection_struct *conn, TALLOC_CTX *mem_ctx, struct event_context *ev) { struct sys_notify_context *ctx; - if (!(ctx = TALLOC_P(mem_ctx, struct sys_notify_context))) { + if (!(ctx = talloc(mem_ctx, struct sys_notify_context))) { DEBUG(0, ("talloc failed\n")); return NULL; } ctx->ev = ev; + ctx->conn = conn; ctx->private_data = NULL; return ctx; } @@ -610,6 +538,7 @@ NTSTATUS sys_notify_watch(struct sys_notify_context *ctx, struct notify_event *ev), void *private_data, void *handle) { - return inotify_watch(ctx, e, callback, private_data, handle); + return SMB_VFS_NOTIFY_WATCH(ctx->conn, ctx, e, callback, private_data, + handle); }