/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2013 Nexenta Systems, Inc. All rights reserved. */ /* * File Change Notification (FCN) * Common parts shared by SMB1 & SMB2 */ /* * This command notifies the client when the specified directory * has changed, and optionally returns the names of files and * directories that changed, and how they changed. The caller * specifies a "Completion Filter" to select which kinds of * changes they want to know about. * * When a change that's in the CompletionFilter is made to the directory, * the command completes. The names of the files that have changed since * the last time the command was issued are returned to the client. * If too many files have changed since the last time the command was * issued, then zero bytes are returned and an alternate status code * is returned in the Status field of the response. * * The CompletionFilter is a mask created as the sum of any of the * following flags: * * FILE_NOTIFY_CHANGE_FILE_NAME 0x00000001 * FILE_NOTIFY_CHANGE_DIR_NAME 0x00000002 * FILE_NOTIFY_CHANGE_NAME 0x00000003 * FILE_NOTIFY_CHANGE_ATTRIBUTES 0x00000004 * FILE_NOTIFY_CHANGE_SIZE 0x00000008 * FILE_NOTIFY_CHANGE_LAST_WRITE 0x00000010 * FILE_NOTIFY_CHANGE_LAST_ACCESS 0x00000020 * FILE_NOTIFY_CHANGE_CREATION 0x00000040 * FILE_NOTIFY_CHANGE_EA 0x00000080 * FILE_NOTIFY_CHANGE_SECURITY 0x00000100 * FILE_NOTIFY_CHANGE_STREAM_NAME 0x00000200 * FILE_NOTIFY_CHANGE_STREAM_SIZE 0x00000400 * FILE_NOTIFY_CHANGE_STREAM_WRITE 0x00000800 * * * The response contains FILE_NOTIFY_INFORMATION structures, as defined * below. The NextEntryOffset field of the structure specifies the offset, * in bytes, from the start of the current entry to the next entry in the * list. If this is the last entry in the list, this field is zero. Each * entry in the list must be longword aligned, so NextEntryOffset must be a * multiple of four. * * typedef struct { * ULONG NextEntryOffset; * ULONG Action; * ULONG FileNameLength; * WCHAR FileName[1]; * } FILE_NOTIFY_INFORMATION; * * Where Action describes what happened to the file named FileName: * * FILE_ACTION_ADDED 0x00000001 * FILE_ACTION_REMOVED 0x00000002 * FILE_ACTION_MODIFIED 0x00000003 * FILE_ACTION_RENAMED_OLD_NAME 0x00000004 * FILE_ACTION_RENAMED_NEW_NAME 0x00000005 * FILE_ACTION_ADDED_STREAM 0x00000006 * FILE_ACTION_REMOVED_STREAM 0x00000007 * FILE_ACTION_MODIFIED_STREAM 0x00000008 */ #include #include static void smb_notify_sr(smb_request_t *, uint_t, const char *); static uint32_t smb_notify_encode_action(struct smb_request *, mbuf_chain_t *, uint32_t, char *); uint32_t smb_notify_common(smb_request_t *sr, mbuf_chain_t *mbc, uint32_t CompletionFilter) { smb_notify_change_req_t *nc; smb_node_t *node; uint32_t status; if (sr->fid_ofile == NULL) return (NT_STATUS_INVALID_HANDLE); node = sr->fid_ofile->f_node; if (node == NULL || !smb_node_is_dir(node)) { /* * Notify change is only valid on directories. */ return (NT_STATUS_INVALID_PARAMETER); } /* * Prepare to receive event data. */ nc = &sr->sr_ncr; nc->nc_flags = CompletionFilter; ASSERT(nc->nc_action == 0); ASSERT(nc->nc_fname == NULL); nc->nc_fname = kmem_zalloc(MAXNAMELEN, KM_SLEEP); /* * Subscribe to events on this node. */ smb_node_fcn_subscribe(node, sr); /* * Wait for subscribed events to arrive. * Expect SMB_REQ_STATE_EVENT_OCCURRED * or SMB_REQ_STATE_CANCELED when signaled. * Note it's possible (though rare) to already * have SMB_REQ_STATE_CANCELED here. */ mutex_enter(&sr->sr_mutex); if (sr->sr_state == SMB_REQ_STATE_ACTIVE) sr->sr_state = SMB_REQ_STATE_WAITING_EVENT; while (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT) { cv_wait(&nc->nc_cv, &sr->sr_mutex); } if (sr->sr_state == SMB_REQ_STATE_EVENT_OCCURRED) sr->sr_state = SMB_REQ_STATE_ACTIVE; mutex_exit(&sr->sr_mutex); /* * Unsubscribe from events on this node. */ smb_node_fcn_unsubscribe(node, sr); /* * Why did we wake up? */ switch (sr->sr_state) { case SMB_REQ_STATE_ACTIVE: break; case SMB_REQ_STATE_CANCELED: status = NT_STATUS_CANCELLED; goto out; default: status = NT_STATUS_INTERNAL_ERROR; goto out; } /* * We have SMB_REQ_STATE_ACTIVE. * * If we have event data, marshall it now, else just * say "many things changed". Note that when we get * action FILE_ACTION_SUBDIR_CHANGED, we don't have * any event details and only know that some subdir * changed, so just report "many things changed". */ switch (nc->nc_action) { case FILE_ACTION_ADDED: case FILE_ACTION_REMOVED: case FILE_ACTION_MODIFIED: case FILE_ACTION_RENAMED_OLD_NAME: case FILE_ACTION_RENAMED_NEW_NAME: case FILE_ACTION_ADDED_STREAM: case FILE_ACTION_REMOVED_STREAM: case FILE_ACTION_MODIFIED_STREAM: /* * Build the reply */ status = smb_notify_encode_action(sr, mbc, nc->nc_action, nc->nc_fname); break; case FILE_ACTION_SUBDIR_CHANGED: status = NT_STATUS_NOTIFY_ENUM_DIR; break; case FILE_ACTION_DELETE_PENDING: status = NT_STATUS_DELETE_PENDING; break; default: ASSERT(0); status = NT_STATUS_INTERNAL_ERROR; break; } out: kmem_free(nc->nc_fname, MAXNAMELEN); nc->nc_fname = NULL; return (status); } /* * Encode a FILE_NOTIFY_INFORMATION struct. * * We only ever put one of these in a response, so this * does not bother handling appending additional ones. */ static uint32_t smb_notify_encode_action(struct smb_request *sr, mbuf_chain_t *mbc, uint32_t action, char *fname) { uint32_t namelen; ASSERT(FILE_ACTION_ADDED <= action && action <= FILE_ACTION_MODIFIED_STREAM); if (fname == NULL) return (NT_STATUS_INTERNAL_ERROR); namelen = smb_wcequiv_strlen(fname); if (namelen == 0) return (NT_STATUS_INTERNAL_ERROR); if (smb_mbc_encodef(mbc, "%lllU", sr, 0, /* NextEntryOffset */ action, namelen, fname)) return (NT_STATUS_NOTIFY_ENUM_DIR); return (0); } /* * smb_notify_file_closed * * Cancel any change-notify calls on this open file. */ void smb_notify_file_closed(struct smb_ofile *of) { smb_session_t *ses; smb_request_t *sr; smb_slist_t *list; SMB_OFILE_VALID(of); ses = of->f_session; SMB_SESSION_VALID(ses); list = &ses->s_req_list; smb_slist_enter(list); sr = smb_slist_head(list); while (sr) { SMB_REQ_VALID(sr); if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT && sr->fid_ofile == of) { smb_request_cancel(sr); } sr = smb_slist_next(list, sr); } smb_slist_exit(list); } /* * smb_notify_event * * Post an event to the watchers on a given node. * * This makes one exception for RENAME, where we expect a * pair of events for the {old,new} directory element names. * This only delivers an event for the "new" name. * * The event delivery mechanism does not implement delivery of * multiple events for one "NT Notify" call. One could do that, * but modern clients don't actually use the event data. They * set a max. received data size of zero, which means we discard * the data and send the special "lots changed" error instead. * Given that, there's not really any point in implementing the * delivery of multiple events. In fact, we don't even need to * implement single event delivery, but do so for completeness, * for debug convenience, and to be nice to older clients that * may actually want some event data instead of the error. * * Given that we only deliver a single event for an "NT Notify" * caller, we want to deliver the "new" name event. (The "old" * name event is less important, even ignored by some clients.) * Since we know these are delivered in pairs, we can simply * discard the "old" name event, knowing that the "new" name * event will be delivered immediately afterwards. * * So, why do event sources post the "old name" event at all? * (1) For debugging, so we see both {old,new} names here. * (2) If in the future someone decides to implement the * delivery of both {old,new} events, the changes can be * mostly isolated to this file. */ void smb_notify_event(smb_node_t *node, uint_t action, const char *name) { smb_request_t *sr; smb_node_fcn_t *fcn; SMB_NODE_VALID(node); fcn = &node->n_fcn; if (action == FILE_ACTION_RENAMED_OLD_NAME) return; /* see above */ mutex_enter(&fcn->fcn_mutex); sr = list_head(&fcn->fcn_watchers); while (sr) { smb_notify_sr(sr, action, name); sr = list_next(&fcn->fcn_watchers, sr); } mutex_exit(&fcn->fcn_mutex); } /* * What completion filter (masks) apply to each of the * FILE_ACTION_... events. */ static const uint32_t smb_notify_action_mask[] = { 0, /* not used */ /* FILE_ACTION_ADDED */ FILE_NOTIFY_CHANGE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, /* FILE_ACTION_REMOVED */ FILE_NOTIFY_CHANGE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, /* FILE_ACTION_MODIFIED */ FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_EA | FILE_NOTIFY_CHANGE_SECURITY, /* FILE_ACTION_RENAMED_OLD_NAME */ FILE_NOTIFY_CHANGE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, /* FILE_ACTION_RENAMED_NEW_NAME */ FILE_NOTIFY_CHANGE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, /* FILE_ACTION_ADDED_STREAM */ FILE_NOTIFY_CHANGE_STREAM_NAME, /* FILE_ACTION_REMOVED_STREAM */ FILE_NOTIFY_CHANGE_STREAM_NAME, /* FILE_ACTION_MODIFIED_STREAM */ FILE_NOTIFY_CHANGE_STREAM_SIZE | FILE_NOTIFY_CHANGE_STREAM_WRITE, /* FILE_ACTION_SUBDIR_CHANGED */ NODE_FLAGS_WATCH_TREE, /* FILE_ACTION_DELETE_PENDING */ NODE_FLAGS_WATCH_TREE | FILE_NOTIFY_VALID_MASK, }; static const int smb_notify_action_nelm = sizeof (smb_notify_action_mask) / sizeof (smb_notify_action_mask[0]); /* * smb_notify_sr * * Post an event to an smb request waiting on some node. * * Note that node->fcn.mutex is held. This implies a * lock order: node->fcn.mutex, then sr_mutex */ static void smb_notify_sr(smb_request_t *sr, uint_t action, const char *name) { smb_notify_change_req_t *ncr; uint32_t mask; SMB_REQ_VALID(sr); ncr = &sr->sr_ncr; /* * Compute the completion filter mask bits for which * we will signal waiting notify requests. */ VERIFY(action < smb_notify_action_nelm); mask = smb_notify_action_mask[action]; mutex_enter(&sr->sr_mutex); if (sr->sr_state == SMB_REQ_STATE_WAITING_EVENT && (ncr->nc_flags & mask) != 0) { sr->sr_state = SMB_REQ_STATE_EVENT_OCCURRED; /* * Save event data in the sr_ncr field so the * reply handler can return it. */ ncr->nc_action = action; if (name != NULL) (void) strlcpy(ncr->nc_fname, name, MAXNAMELEN); cv_signal(&ncr->nc_cv); } mutex_exit(&sr->sr_mutex); }