directory change notification patch

Mark Weaver mark-clist at npsl.co.uk
Fri Mar 25 23:37:30 GMT 2005


>>Sorry, I'm a bit confused now.  Does this mean that there is already a 
>>patch that does the same thing?  I couldn't find anything, other than 
>>the original that I based my changes off -- from Feb 2003 -- and 
> 
> 
> Yes, it's that one. It's been outstanding a long time :-(.
> 
I'm sure that there have been plenty of other things to take care of!

Here's another version of the patch with the following changes:

* Hopefully resolve security issue opening directories in 
kernel_notify.c by opening them and then becoming root before issuing 
the DNOTIFY call.  I believe that this covers the security issue (checks 
that user can access directory), and also covers the lost signals issue 
(signals are always delivered if they were registered for as root).
* Fix a missing free in change_notify_set in an error path
* Fix missing check for chdir() failure when processing notifications
* Fix potential restart failure, then cnotify->remove_notify usage 
(which would have caused a segmentation fault)
* Add parameter to configuration file: 'change notify tracking' which 
can be set to none|disk|memory.  none implements the old behaviour of 
always returning STATUS_NOTIFY_ENUM_DIR.  This is the default option, 
and is sufficient to make explorer work.  This could be changed, but as 
it is the patch has minimal impact on existing installation.  memory 
uses a memory backed tdb for storing directory contents, disk uses a 
disk backed tdb.  The memory and disk options provide a performance for 
space trade-off.

I've had it running for a few days without problems, and put it through 
a few hoops.  Let me know if there are any other changes required (or bugs!)

Thanks,

Mark
-------------- next part --------------
diff -ur samba-3.0.10/source/include/smb.h samba-notify-3.0.10/source/include/smb.h
--- samba-3.0.10/source/include/smb.h	Wed Dec 15 14:33:11 2004
+++ samba-notify-3.0.10/source/include/smb.h	Wed Mar 23 02:24:27 2005
@@ -1199,6 +1199,16 @@
 #define FILE_NOTIFY_CHANGE_SECURITY    0x100
 #define FILE_NOTIFY_CHANGE_FILE_NAME   0x200
 
+/* file notification actions */
+#define FILE_ACTION_ADDED              0x00000001
+#define FILE_ACTION_REMOVED            0x00000002
+#define FILE_ACTION_MODIFIED		   0x00000003
+#define FILE_ACTION_RENAMED_OLD_NAME   0x00000004
+#define FILE_ACTION_RENAMED_NEW_NAME   0x00000005
+#define FILE_ACTION_ADDED_STREAM	   0x00000006
+#define FILE_ACTION_REMOVED_STREAM	   0x00000007
+#define FILE_ACTION_MODIFIED_STREAM    0x00000008
+
 /* where to find the base of the SMB packet proper */
 #define smb_base(buf) (((char *)(buf))+4)
 
@@ -1532,7 +1542,11 @@
 	int select_time;
 };
 
-
+/* these define the various types of file change monitoring required:
+   none - no individual tracking; memory - using in memory tdb;
+   disk - using on disk tdb
+ */
+enum {CHANGE_NOTIFY_NONE, CHANGE_NOTIFY_MEMORY, CHANGE_NOTIFY_DISK};
 
 #include "smb_macros.h"
 
diff -ur samba-3.0.10/source/lib/select.c samba-notify-3.0.10/source/lib/select.c
--- samba-3.0.10/source/lib/select.c	Mon Oct 25 22:04:59 2004
+++ samba-notify-3.0.10/source/lib/select.c	Mon Mar 21 21:53:32 2005
@@ -50,7 +50,7 @@
 
 /*******************************************************************
  Like select() but avoids the signal race using a pipe
- it also guuarantees that fds on return only ever contains bits set
+ it also guarantees that fds on return only ever contains bits set
  for file descriptors that were readable.
 ********************************************************************/
 
@@ -99,20 +99,17 @@
 			FD_ZERO(writefds);
 		if (errorfds)
 			FD_ZERO(errorfds);
-	}
-
-	if (FD_ISSET(select_pipe[0], readfds2)) {
+	} else if (FD_ISSET(select_pipe[0], readfds2)) {
 		char c;
 		saved_errno = errno;
 		if (read(select_pipe[0], &c, 1) == 1) {
 			pipe_read++;
-		}
-		errno = saved_errno;
-		FD_CLR(select_pipe[0], readfds2);
-		ret--;
-		if (ret == 0) {
-			ret = -1;
 			errno = EINTR;
+			ret = -1;
+		} else {
+			errno = saved_errno;
+			FD_CLR(select_pipe[0], readfds2);
+			ret--;
 		}
 	}
 
@@ -145,7 +142,18 @@
 		if (tval)
 			tval2 = *tval;
 
-		ret = sys_select(maxfd, readfds2, writefds2, errorfds2, ptval);
+		/* Changed to call select directly to avoid eating signals unnecessarily */
+		errno = 0;
+		ret = select(maxfd, readfds2, writefds2, errorfds2, ptval);
+
+		if (ret <= 0) {
+			if (readfds)
+				FD_ZERO(readfds);
+			if (writefds)
+				FD_ZERO(writefds);
+			if (errorfds)
+				FD_ZERO(errorfds);
+		}
 	} while (ret == -1 && errno == EINTR);
 
 	if (readfds)
diff -ur samba-3.0.10/source/param/loadparm.c samba-notify-3.0.10/source/param/loadparm.c
--- samba-3.0.10/source/param/loadparm.c	Tue Mar 22 00:13:13 2005
+++ samba-notify-3.0.10/source/param/loadparm.c	Fri Mar 25 12:55:39 2005
@@ -291,6 +291,7 @@
 	BOOL bUnixExtensions;
 	BOOL bDisableNetbios;
 	BOOL bKernelChangeNotify;
+	int change_notify_tracking;
 	BOOL bUseKerberosKeytab;
 	BOOL bDeferSharingViolations;
 	int restrict_anonymous;
@@ -749,6 +750,16 @@
 	{-1, NULL}
 };
 
+/* Change notify individual file tracking: none (default), memory (file cache
+ * store in memory tdb), disk (file cache stored in disk tdb)
+ */
+static const struct enum_list enum_change_notify_tracking[] = {
+	{CHANGE_NOTIFY_NONE, "none"},
+	{CHANGE_NOTIFY_MEMORY, "memory"},
+	{CHANGE_NOTIFY_DISK, "disk"},
+	{-1, NULL}
+};
+
 /* Note: We do not initialise the defaults union - it is not allowed in ANSI C
  *
  * The FLAG_HIDE is explicit. Paramters set this way do NOT appear in any edit
@@ -936,6 +947,7 @@
 	{"getwd cache", P_BOOL, P_GLOBAL, &use_getwd_cache, NULL, NULL, FLAG_ADVANCED}, 
 	{"keepalive", P_INTEGER, P_GLOBAL, &keepalive, NULL, NULL, FLAG_ADVANCED}, 
 	{"kernel change notify", P_BOOL, P_GLOBAL, &Globals.bKernelChangeNotify, NULL, NULL, FLAG_ADVANCED}, 
+	{"change notify tracking", P_ENUM, P_GLOBAL, &Globals.change_notify_tracking, NULL, enum_change_notify_tracking, FLAG_ADVANCED},
 
 	{"lpq cache time", P_INTEGER, P_GLOBAL, &Globals.lpqcachetime, NULL, NULL, FLAG_ADVANCED}, 
 	{"max smbd processes", P_INTEGER, P_GLOBAL, &Globals.iMaxSmbdProcesses, NULL, NULL, FLAG_ADVANCED}, 
@@ -1418,6 +1430,7 @@
 	Globals.machine_password_timeout = 60 * 60 * 24 * 7;	/* 7 days default. */
 	Globals.change_notify_timeout = 60;	/* 1 minute default. */
 	Globals.bKernelChangeNotify = True;	/* On if we have it. */
+	Globals.change_notify_tracking = CHANGE_NOTIFY_NONE; /* Don't track individual changes by default */
 	Globals.lm_announce = 2;	/* = Auto: send only if LM clients found */
 	Globals.lm_interval = 60;
 	Globals.announce_as = ANNOUNCE_AS_NT_SERVER;
@@ -1777,6 +1790,7 @@
 FN_GLOBAL_BOOL(lp_client_use_spnego, &Globals.bClientUseSpnego)
 FN_GLOBAL_BOOL(lp_hostname_lookups, &Globals.bHostnameLookups)
 FN_GLOBAL_BOOL(lp_kernel_change_notify, &Globals.bKernelChangeNotify)
+FN_GLOBAL_INTEGER(lp_change_notify_tracking, &Globals.change_notify_tracking)
 FN_GLOBAL_BOOL(lp_use_kerberos_keytab, &Globals.bUseKerberosKeytab)
 FN_GLOBAL_BOOL(lp_defer_sharing_violations, &Globals.bDeferSharingViolations)
 FN_GLOBAL_INTEGER(lp_os_level, &Globals.os_level)
diff -ur samba-3.0.10/source/smbd/notify.c samba-notify-3.0.10/source/smbd/notify.c
--- samba-3.0.10/source/smbd/notify.c	Wed Dec 15 14:33:17 2004
+++ samba-notify-3.0.10/source/smbd/notify.c	Fri Mar 25 22:40:08 2005
@@ -24,6 +24,24 @@
 static struct cnotify_fns *cnotify;
 
 /****************************************************************************
+This structure holds a list of files and associated notification actions.
+*****************************************************************************/
+struct file_action {
+	struct file_action *next, *prev;
+	int action;
+	char *filename;
+	size_t filename_length;
+};
+
+/****************************************************************************
+This structure contains a file actions list head and element count
+*****************************************************************************/
+struct file_actions {
+	struct file_action *head;
+	uint32_t count;
+};
+
+/****************************************************************************
  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).
@@ -35,17 +53,207 @@
 	files_struct *fsp;
 	connection_struct *conn;
 	uint32 flags;
+	uint32 max_parameter_count;
 	char request_buf[smb_size];
 	void *change_data;
+	char *working_directory;
+	TDB_CONTEXT *file_data;
+	char *file_data_path;
+};
+
+/****************************************************************************
+This is the context structure used for tdb_traverse when checking for changes
+*****************************************************************************/
+struct file_data_exists_context {
+	BOOL ok;
+	struct change_notify *cnbp;
+	struct file_actions *fact;
 };
 
 static struct change_notify *change_notify_list;
 
 /****************************************************************************
- Setup the common parts of the return packet and send it.
-*****************************************************************************/
+ Free contents of a file_actions list
+****************************************************************************/
+static void change_notify_destroy_file_actions(struct file_actions *fact)
+{
+	while (fact->head != NULL) {
+		struct file_action *fa = fact->head;
+		fact->head = fact->head->next;
+		SAFE_FREE(fa->filename);
+		free(fa);
+	}
+	fact->count = 0;
+}
+
+
+/****************************************************************************
+Return a file action struct with the given filename and fileaction
+****************************************************************************/
+static struct file_action *change_notify_get_file_action(const char *filename, int fileaction)
+{
+	struct file_action *fa;
+
+	if (!(fa = (struct file_action *)malloc(sizeof(struct file_action)))) {
+		DEBUG(0, ("change_notify_get_file_action: SMB_MALLOC failed\n"));
+		return NULL;
+	}
+	fa->action = fileaction;
+	if ( (fa->filename = SMB_STRDUP(filename)) == NULL ) {
+		DEBUG(0, ("change_notify_get_file_action: strdup failed\n"));
+		free(fa);
+		return NULL;
+	}
+	fa->filename_length = strlen(filename);
+
+	return fa;
+}
+
+/****************************************************************************
+Check to make sure that the file in the given cnbp.file_data record
+still exists.
+****************************************************************************/
+static int change_notify_file_data_exists(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA data, void *state)
+{
+
+	struct file_action *fa;
+	struct file_data_exists_context *context = (struct file_data_exists_context *)state;
+	char *filename;
+	const char *path = context->cnbp->fsp->fsp_name;
+	size_t filename_len = strlen((char *)key.dptr) + strlen((char *)path) + 2;
+	int fd;
+
+	/* Make a full path to the file */
+	if (!(filename = (char *)SMB_MALLOC(filename_len))) {
+		DEBUG(0, ("change_notify_file_data_exists: malloc failed\n"));
+		context->ok = False;
+		return 1; /* stop traversing */
+
+	}
+
+	filename[0] = '\0';
+	safe_strcat(filename, (char *)path, filename_len);
+	safe_strcat(filename, "/", filename_len);
+	safe_strcat(filename, (char *)key.dptr, filename_len);
+
+	/* Try and open it */
+	if ((fd = open(filename, O_RDONLY)) == -1) {
+		fa = change_notify_get_file_action
+			((char *)key.dptr, FILE_ACTION_REMOVED);
+		if (fa == NULL) {
+			free(filename);
+			DEBUG(0, ("change_notify_file_data_exists: getting file action failed\n"));
+			context->ok = False;
+			return 1; /* stop traversing */
+		}
+		++context->fact->count;
+		DLIST_ADD(context->fact->head, fa);
+	} else {
+		close(fd);
+	}
+	free(filename);
+
+	return 0;
+}
 
-static void change_notify_reply_packet(char *inbuf, NTSTATUS error_code)
+/****************************************************************************
+Get a list of all the file notification actions
+****************************************************************************/
+static BOOL change_notify_find_file_actions(struct change_notify *cnbp, struct file_actions *fact)
+{
+	void *dp;
+	const char *fname;
+	TDB_DATA tdb_key, tdb_data;
+	SMB_STRUCT_STAT old_st, new_st;
+	pstring full_name;
+	size_t fullname_len, remaining_len;
+	char *p;
+	struct file_action *fa;
+	struct file_data_exists_context exists_context;
+
+	/* Initialise the output list */
+	fact->head = NULL;
+	fact->count = 0;
+
+	if (!(dp = OpenDir(cnbp->conn, cnbp->fsp->fsp_name, True))) {
+		DEBUG(0, ("change_notify_find_file_actions: failed to open directory '%s' with %d, uid = %d, euid=%d\n",
+			  cnbp->fsp->fsp_name, errno, getuid(), geteuid()));
+		goto err;
+	}
+
+	pstrcpy(full_name, cnbp->fsp->fsp_name);
+	pstrcat(full_name, "/");
+
+	fullname_len = strlen(full_name);
+	remaining_len = sizeof(full_name) - fullname_len - 1;
+	p = &full_name[fullname_len];
+
+	while ((fname = ReadDirName(dp))) {
+		if (strequal(fname, ".") || strequal(fname, "..")) {
+			continue;
+		}
+
+		safe_strcpy(p, fname, remaining_len);
+		if (SMB_VFS_STAT(cnbp->conn, full_name, &old_st) != 0) {
+			DEBUG(10,("change_notify_find_file_actions: stat failed for '%s', ignoring the file\n",
+				  full_name));
+		} else {
+			tdb_key.dptr = (void *)fname;
+			tdb_key.dsize = strlen(fname) + 1;
+			tdb_data = tdb_fetch(cnbp->file_data, tdb_key);
+			if (!tdb_data.dptr) {
+				fa = change_notify_get_file_action
+					(fname, FILE_ACTION_ADDED);
+				if (fa == NULL)
+					goto err;
+				++fact->count;
+				DLIST_ADD(fact->head, fa);
+				continue;
+			}
+			new_st = *((SMB_STRUCT_STAT *)tdb_data.dptr);
+			free(tdb_data.dptr);
+			if ((old_st.st_atime != new_st.st_atime) ||
+		    	(old_st.st_mtime != new_st.st_mtime) ||
+			    (old_st.st_ctime != new_st.st_ctime)) {
+				fa = change_notify_get_file_action
+					(fname, FILE_ACTION_MODIFIED);
+				if (fa == NULL)
+					goto err;
+				++fact->count;
+				DLIST_ADD(fact->head, fa);
+			}
+		}
+	}
+	CloseDir(dp);
+
+	/* check for deleted files */
+	exists_context.fact = fact;
+	exists_context.cnbp = cnbp;
+	exists_context.ok = True;
+	if (tdb_traverse(cnbp->file_data, &change_notify_file_data_exists, &exists_context) == -1) {
+		DEBUG(0, ("change_notify_find_file_actions: traverse failed: %s\n", tdb_errorstr(cnbp->file_data)));
+		goto err;
+	}
+	if (!exists_context.ok) {
+		DEBUG(0, ("change_notify_find_file_actions: traverse encountered a problem\n"));
+		goto err;
+	}
+
+	return True;
+
+ err:
+	change_notify_destroy_file_actions(fact);
+	return False;
+}
+
+/****************************************************************************
+ * Send an error code back
+ *
+ * @param inbuf
+ * @param error_code NTSTATUS value
+ *
+ *****************************************************************************/
+static void change_notify_reply_error(char *inbuf, NTSTATUS error_code)
 {
 	char outbuf[smb_size+38];
 
@@ -61,9 +269,121 @@
 	set_message(outbuf,18,0,False);
 
 	if (!send_smb(smbd_server_fd(),outbuf))
+		exit_server("change_notify_reply_error: send_smb failed.");
+}
+
+/****************************************************************************
+ * Setup a reply packet with the names of individual files changed and send it
+ *
+ * Returns True if a reply was sent, False if not.  In the False case, this
+ * means that their was no information to send (packets are always sent if
+ * an error has occurred; currently these contain STATUS_NOTIFY_ENUM_DIR as
+ * I'm not sure which error code(s) would be appropriate to send).  This at
+ * least will inform the application monitoring that something has happened,
+ * and presumably it will find out later what went wrong (if it posts another
+ * change notify, for example).
+ *****************************************************************************/
+static BOOL change_notify_reply_packet(struct change_notify *cnbp)
+{
+	struct file_action *fa;
+	char *outbuf;
+	char *p;
+	int offset;
+	uint32_t total;
+	BOOL rc;
+	int alignment = 4- smb_size%4;
+	struct file_actions fact;
+
+	/* See if change tracking is off; if so reply STATUS_NOTIFY_ENUM_DIR */
+	if (cnbp->file_data == NULL) {
+		change_notify_reply_error(cnbp->request_buf, STATUS_NOTIFY_ENUM_DIR);
+		return True;
+	}
+
+	/* Get changes */
+	if (!change_notify_find_file_actions(cnbp, &fact)) {
+		DEBUG(10, ("change_notify_find_file_actions failed: returning STATUS_NOTIFY_ENUM_DIR\n"));
+		goto err_enum_dir;
+	}
+
+	/* Check if any changes were noted.  If not, return False which
+       will cause the caller to monitor the directory again
+	 */
+	if (fact.count == 0) {
+		change_notify_destroy_file_actions(&fact);
+		return False;
+	}
+
+
+	/* return STATUS_NOTIFY_ENUM_DIR if we have too many files or none at all  */
+	if (fact.count >= cnbp->max_parameter_count) {
+		DEBUG(10,("change_notify_reply_packet: returning STATUS_NOTIFY_ENUM_DIR with max parameter count %u, count %u\n", cnbp->max_parameter_count, fact.count));
+		goto err_enum_dir;
+	}
+
+
+	/* calculate parameter size to get max packet length */
+	for (total = 0, fa = fact.head; fa; fa = fa->next) {
+		size_t length = strlen(fa->filename) * 2 + 12;
+		/* align FILE_NOTIFY_INFORMATION to 4 byte boundary */
+		length += (length%4 ? 4-(length%4) : 0);
+		total += length;
+	}
+	total += smb_size+ 2*18+alignment;
+
+	if (total > 65535) {
+		DEBUG(10,("change_notify_reply_packet: returning STATUS_NOTIFY_ENUM_DIR with max parameter count %u, count %u, total %u\n", cnbp->max_parameter_count, fact.count, total));
+		goto err_enum_dir;
+	}
+	outbuf = SMB_MALLOC(total);
+	if (outbuf == NULL) {
+		DEBUG(0,("change_notify_reply_packet: SMB_MALLOC failed for %u bytes, returning STATUS_NOTIFY_ENUM_DIR\n", total));
+		goto err_enum_dir;
+	}
+
+	/* setup reply packet */
+	memset(outbuf, '\0', total);
+	construct_reply_common(cnbp->request_buf, outbuf);
+	set_message(outbuf,18,0,False);
+
+	/* fill NT NOTIFY parameters */
+	p = smb_buf(outbuf) + alignment;
+	for (total = 0, fa = fact.head; fa; fa = fa->next) {
+		/* Set the filename, byte length and action fields */
+		size_t length = push_ucs2(NULL, p+12, fa->filename, strlen(fa->filename)*2, 0);
+		DEBUG(10,("change_notify_reply_packet: file %s, action = %d\n", fa->filename, fa->action));
+		SIVAL(p,8,length);
+		SIVAL(p,4,fa->action);
+		offset = length +12;
+		/* align FNI structure to 4 byte boundary */
+		offset += (length%4 ? 4-(length%4) : 0);
+		/* Set offset to next structure (0 for last structure */
+		SIVAL(p,0, (fa->next ? offset : 0));
+		p += offset;
+		total += offset;
+	}
+	SIVAL(outbuf,smb_ntr_TotalParameterCount,total);
+	SIVAL(outbuf,smb_ntr_ParameterCount,total);
+	set_message(outbuf,18,total + alignment,False);
+	SIVAL(outbuf,smb_ntr_ParameterOffset,smb_buf(outbuf) + alignment - smb_base(outbuf));
+
+	/* done with the notifications list */
+	change_notify_destroy_file_actions(&fact);
+
+	/* send the response */
+	rc = send_smb(smbd_server_fd(),outbuf);
+	free(outbuf);
+	if (!rc)
 		exit_server("change_notify_reply_packet: send_smb failed.");
+	return True;
+
+ err_enum_dir:
+	change_notify_destroy_file_actions(&fact);
+	change_notify_reply_error(cnbp->request_buf, STATUS_NOTIFY_ENUM_DIR);
+	return True;
 }
 
+
 /****************************************************************************
  Remove an entry from the list and free it, also closing any
  directory handle if necessary.
@@ -71,12 +391,37 @@
 
 static void change_notify_remove(struct change_notify *cnbp)
 {
-	cnotify->remove_notify(cnbp->change_data);
+	if (cnbp->change_data)
+		cnotify->remove_notify(cnbp->change_data);
+	if (cnbp->file_data)
+		tdb_close(cnbp->file_data);
+	if (cnbp->file_data_path) {
+		DEBUG(10,("change_notify_remove: unlink temporary database %s\n", cnbp->file_data_path));
+		unlink(cnbp->file_data_path);
+		free(cnbp->file_data_path);
+	}
+	SAFE_FREE(cnbp->working_directory);
 	DLIST_REMOVE(change_notify_list, cnbp);
 	ZERO_STRUCTP(cnbp);
 	SAFE_FREE(cnbp);
 }
 
+				
+static BOOL change_notify_restart(struct change_notify *cnbp)
+{
+	/* Remove the old notification if required */
+	if (cnbp->change_data)
+		cnotify->remove_notify(cnbp->change_data);
+	/* Register another one */
+	cnbp->change_data = cnotify->register_notify(cnbp->conn, cnbp->fsp->fsp_name, cnbp->flags);
+	if (!cnbp->change_data) {
+		DEBUG(0,("change_notify_restart: register_notify failed for '%s'\n", cnbp->fsp->fsp_name));
+		return False;
+	}
+	return True;
+}
+
+
 /****************************************************************************
  Delete entries by fnum from the change notify pending queue.
 *****************************************************************************/
@@ -104,7 +449,7 @@
 	for (cnbp=change_notify_list; cnbp; cnbp=next) {
 		next=cnbp->next;
 		if(SVAL(cnbp->request_buf,smb_mid) == mid) {
-			change_notify_reply_packet(cnbp->request_buf,NT_STATUS_CANCELLED);
+			change_notify_reply_error(cnbp->request_buf,NT_STATUS_CANCELLED);
 			change_notify_remove(cnbp);
 		}
 	}
@@ -126,7 +471,7 @@
 		 * 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,NT_STATUS_CANCELLED);
+			change_notify_reply_error(cnbp->request_buf,NT_STATUS_CANCELLED);
 			change_notify_remove(cnbp);
 		}
 	}
@@ -151,47 +496,217 @@
 {
 	struct change_notify *cnbp, *next;
 	uint16 vuid;
+	pstring wd;
 
+	/* Store the current working directory, in case needed */
+	if (sys_getwd(wd) == NULL) {
+		DEBUG(0,("process_pending_change_notify_queue: sys_getwd failed\n"));
+		return False;
+	}
+
+	/* TODO: better to switch to vuid? Most of the time, this runs as root
+	   anyway
+     */
+	become_root();
 	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);
 
+		DEBUG(10,("process_pending_change_notify_queue: checking notify for %s/%s\n", cnbp->working_directory, cnbp->fsp->fsp_name));
 		if (cnotify->check_notify(cnbp->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_packet(cnbp->request_buf,STATUS_NOTIFY_ENUM_DIR);
-			change_notify_remove(cnbp);
+			/* change to the appropriate directory first */
+			if (chdir(cnbp->working_directory) != 0) {
+				DEBUG(2,("process_pending_change_notify_queue: could not change to working directory '%s' (deleted?)\n", cnbp->working_directory));
+				/* can't chdir, send `stuff changed` */
+				change_notify_reply_error(cnbp->request_buf, STATUS_NOTIFY_ENUM_DIR);
+				change_notify_remove(cnbp);
+			} else {
+				DEBUG(10,("process_pending_change_notify_queue: directory '%s' changed\n", cnbp->fsp->fsp_name ));
+				/* See if anything really changed */
+				if (!change_notify_reply_packet(cnbp)) {
+					DEBUG(10,("process_pending_change_notify_queue: directory '%s' didn't really change, queuing another notification\n", cnbp->fsp->fsp_name));
+					/* If not, restart the notification, but don't send a reply packet */
+					if (!change_notify_restart(cnbp)) {
+						/* Restart failed, so give a `stuff changed` error */
+						change_notify_reply_error(cnbp->request_buf, STATUS_NOTIFY_ENUM_DIR);
+						change_notify_remove(cnbp);
+					}
+				} else {
+					/* Sent a packet so remove the old notification */
+				    change_notify_remove(cnbp);
+				}
+			}
 		}
 	}
+	/* Restore old user/directory */
+	unbecome_root();
+	if (chdir(wd) != 0) {
+		DEBUG(0,("process_pending_change_notify_queue: failed to change back to directory '%s'\n",
+				 wd));
+	}
 
 	return (change_notify_list != NULL);
 }
 
 /****************************************************************************
+ Setup the file_data field in the change_notify struct to contain
+ a tdb table of file stat data keyed by file name
+****************************************************************************/
+static BOOL change_notify_setup_file_data(struct change_notify *cnbp) {
+
+	void *dp = 0;
+	const char *fname;
+	TDB_DATA tdb_key, tdb_data;
+	SMB_STRUCT_STAT st;
+	pstring full_name;
+	size_t fullname_len, remaining_len;
+	char *p;
+	pstring tdb_path;
+	int tdb_flags = 0;
+
+	/* Check if tracking is on */
+	switch (lp_change_notify_tracking()) {
+		/* Memory tracking */
+		case CHANGE_NOTIFY_MEMORY:
+			pstrcpy(tdb_path, "/dev/null");
+			tdb_flags = TDB_INTERNAL;
+			break;
+
+		/* Disk tracking.  Use a temporary file. */
+		case CHANGE_NOTIFY_DISK:
+		{
+			int fd;
+			
+			slprintf(tdb_path, sizeof(tdb_path)-1, "%s/cn.XXXXXX", tmpdir());
+			fd = smb_mkstemp(tdb_path);
+			if (fd == -1) {
+				DEBUG(0,("change_notify_setup_file_data: failed to create temporary database file %s\n", tdb_path));
+				return False;
+			}
+			close(fd);
+			cnbp->file_data_path = SMB_STRDUP(tdb_path);
+			if (cnbp->file_data_path == NULL) {
+				DEBUG(0,("change_notify_setup_file_data: malloc failed\n"));
+				unlink(cnbp->file_data_path);
+				return False;
+			}
+			tdb_flags = TDB_NOLOCK|TDB_NOMMAP;
+			DEBUG(10,("change_notify_setup_file_data: tracking in file %s\n", tdb_path));
+			break;
+		}
+		
+		/* No tracking required */
+		case CHANGE_NOTIFY_NONE:
+		default:
+			return True;
+	}
+
+	if (!(cnbp->file_data = tdb_open(tdb_path, 0, tdb_flags,
+					 O_RDWR | O_CREAT | O_TRUNC, 0600))) {
+		DEBUG(0, ("change_notify_setup_file_data: failed to open file data db %s\n", tdb_path));
+		goto err;
+	}
+
+	if (!(dp = OpenDir(cnbp->conn, cnbp->fsp->fsp_name, True))) {
+		DEBUG(0, ("change_notify_setup_file_data: failed to open directory '%s'\n", cnbp->fsp->fsp_name));
+		goto err;
+	}
+
+
+	pstrcpy(full_name, cnbp->fsp->fsp_name);
+	pstrcat(full_name, "/");
+
+	fullname_len = strlen(full_name);
+	remaining_len = sizeof(full_name) - fullname_len - 1;
+	p = &full_name[fullname_len];
+
+	while ((fname = ReadDirName(dp))) {
+		if (strequal(fname, ".") || strequal(fname, "..")) {
+			continue;
+		}
+
+		safe_strcpy(p, fname, remaining_len);
+
+		if (SMB_VFS_STAT(cnbp->conn, full_name, &st) != 0) {
+			DEBUG(10, ("change_notify_setup_file_data: stat failed for '%s', ignoring\n", full_name));
+			continue;
+		}
+
+		tdb_key.dptr = (void *)fname;
+		tdb_key.dsize = strlen(fname) + 1;
+
+		tdb_data.dptr = (void *)&st;
+		tdb_data.dsize = (size_t)sizeof(st);
+
+		if (tdb_store(cnbp->file_data, tdb_key, tdb_data, 0)) {
+			DEBUG(0, ("change_notify_setup_file_data: unable to store data for '%s': %s\n", full_name, tdb_errorstr(cnbp->file_data)));
+			goto err;
+		}
+	}
+	CloseDir(dp);
+	return True;
+
+err:
+	if (dp) CloseDir(dp);
+	if (cnbp->file_data)
+		tdb_close(cnbp->file_data);
+	if (cnbp->file_data_path) {
+		unlink(cnbp->file_data_path);
+		free(cnbp->file_data_path);
+	}
+	return False;
+}
+
+/****************************************************************************
  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)
+BOOL change_notify_set(char *inbuf, files_struct *fsp, connection_struct *conn, uint32 flags, uint32 max_parameter_count)
 {
 	struct change_notify *cnbp;
+	pstring wd;
 
+	DEBUG(10,("change_notify_set called\n"));
 	if((cnbp = SMB_MALLOC_P(struct change_notify)) == NULL) {
 		DEBUG(0,("change_notify_set: malloc fail !\n" ));
-		return -1;
+		return False;
 	}
 
 	ZERO_STRUCTP(cnbp);
 
+	/* Store the working directory for operating on the fsp sensibly */
+	if (!sys_getwd(wd)) {
+		DEBUG(0,("change_notify_set: sys_getwd failed\n"));
+		SAFE_FREE(cnbp);
+		return False;
+	}
+	if ( (cnbp->working_directory = SMB_STRDUP(wd)) == NULL ) {
+		DEBUG(0,("change_notify_set: strdup failed\n"));
+		SAFE_FREE(cnbp);
+		return False;
+	}
+
 	memcpy(cnbp->request_buf, inbuf, smb_size);
 	cnbp->fsp = fsp;
 	cnbp->conn = conn;
 	cnbp->flags = flags;
+	cnbp->max_parameter_count = max_parameter_count;
 	cnbp->change_data = cnotify->register_notify(conn, fsp->fsp_name, flags);
 	
 	if (!cnbp->change_data) {
+		DEBUG(10,("change_notify_set: register_notify failed for '%s'\n", fsp->fsp_name));
+		SAFE_FREE(cnbp->working_directory);
+		SAFE_FREE(cnbp);
+		return False;
+	}
+
+	if (!change_notify_setup_file_data(cnbp)) {
+		cnotify->remove_notify(cnbp->change_data);
+		SAFE_FREE(cnbp->working_directory);
 		SAFE_FREE(cnbp);
 		return False;
 	}
diff -ur samba-3.0.10/source/smbd/notify_kernel.c samba-notify-3.0.10/source/smbd/notify_kernel.c
--- samba-3.0.10/source/smbd/notify_kernel.c	Mon Oct 25 22:04:54 2004
+++ samba-notify-3.0.10/source/smbd/notify_kernel.c	Mon Mar 21 21:59:38 2005
@@ -23,7 +23,7 @@
 
 #if HAVE_KERNEL_CHANGE_NOTIFY
 
-#define FD_PENDING_SIZE 20
+#define FD_PENDING_SIZE 10000
 static SIG_ATOMIC_T fd_pending_array[FD_PENDING_SIZE];
 static SIG_ATOMIC_T signals_received;
 
@@ -89,8 +89,17 @@
 	int i;
 	BOOL ret = False;
 
-	if (t)
-		return False;
+	/* TODO: took this out as signals were not always caught,
+		due to e.g. calling sys_select_intr.  Although that ought
+		to be fixed, the polling method is infrequent, and most of
+		the time there are no waiting signals.
+		However, performing the polling provides an opportunity
+		to recover in the case of lost signals, which is better
+		than breaking.
+	*/
+	DEBUG(10,("kernel_check_notify at %u, fd=%d, signals=%d\n", t, data->directory_handle, signals_received));
+	/*	if (t)
+		return False;*/
 
 	BlockSignals(True, RT_SIGNAL_NOTIFY);
 	for (i = 0; i < signals_received; i++) {
@@ -150,20 +159,31 @@
 
 static void *kernel_register_notify(connection_struct *conn, char *path, uint32 flags)
 {
-	struct change_data data;
-	int fd;
+	struct change_data *data = NULL;
+	int uid_switched = 0;
 	unsigned long kernel_flags;
 	
-	fd = sys_open(path,O_RDONLY, 0);
+	data = SMB_MALLOC(sizeof(struct change_data));
+	if (data == NULL) {
+		DEBUG(3,("Failed to allocate memory for a change notification\n"));
+		goto err;
+	}
 
-	if (fd == -1) {
+	data->directory_handle = sys_open(path,O_RDONLY, 0);
+	if (data->directory_handle == -1) {
 		DEBUG(3,("Failed to open directory %s for change notify\n", path));
-		return NULL;
+		goto err;
 	}
 
-	if (sys_fcntl_long(fd, F_SETSIG, RT_SIGNAL_NOTIFY) == -1) {
+	/* Become root so that signals are always delivered for this fd.
+	   Security should have been checked with the sys_open above
+     */
+	become_root();
+	uid_switched = 1;
+
+	if (sys_fcntl_long(data->directory_handle, F_SETSIG, RT_SIGNAL_NOTIFY) == -1) {
 		DEBUG(3,("Failed to set signal handler for change notify\n"));
-		return NULL;
+		goto err;
 	}
 
 	kernel_flags = DN_CREATE|DN_DELETE|DN_RENAME; /* creation/deletion changes everything! */
@@ -177,18 +197,28 @@
 	if (flags & FILE_NOTIFY_CHANGE_SECURITY)    kernel_flags |= DN_ATTRIB;
 	if (flags & FILE_NOTIFY_CHANGE_EA)          kernel_flags |= DN_ATTRIB;
 	if (flags & FILE_NOTIFY_CHANGE_FILE_NAME)   kernel_flags |= DN_RENAME|DN_DELETE;
-
-	if (sys_fcntl_long(fd, F_NOTIFY, kernel_flags) == -1) {
+	if (sys_fcntl_long(data->directory_handle, F_NOTIFY, kernel_flags) == -1) {
 		DEBUG(3,("Failed to set async flag for change notify\n"));
-		return NULL;
+		goto err;
 	}
-
-	data.directory_handle = fd;
+	unbecome_root();
 
 	DEBUG(3,("kernel change notify on %s (ntflags=0x%x flags=0x%x) fd=%d\n", 
-		 path, (int)flags, (int)kernel_flags, fd));
+		 path, (int)flags, (int)kernel_flags, data->directory_handle));
+	return data;
 
-	return (void *)memdup(&data, sizeof(data));
+err:
+	/* Change user back */
+	if (uid_switched) {
+		unbecome_root();
+	}
+	/* Free data/fds */
+	if (data) {
+		if (data->directory_handle != -1)
+			close(data->directory_handle);
+		free(data);
+	}
+	return NULL;
 }
 
 /****************************************************************************
diff -ur samba-3.0.10/source/smbd/nttrans.c samba-notify-3.0.10/source/smbd/nttrans.c
--- samba-3.0.10/source/smbd/nttrans.c	Wed Dec 15 14:33:17 2004
+++ samba-notify-3.0.10/source/smbd/nttrans.c	Wed Mar 16 17:30:54 2005
@@ -1788,6 +1788,7 @@
 	char *setup = *ppsetup;
 	files_struct *fsp;
 	uint32 flags;
+	uint32 max_parameter_count = IVAL(inbuf, smb_nt_MaxParameterCount);
 
         if(setup_count < 6)
 		return ERROR_DOS(ERRDOS,ERRbadfunc);
@@ -1803,7 +1804,7 @@
 	if((!fsp->is_directory) || (conn != fsp->conn))
 		return ERROR_DOS(ERRDOS,ERRbadfid);
 
-	if (!change_notify_set(inbuf, fsp, conn, flags))
+	if (!change_notify_set(inbuf, fsp, conn, flags, max_parameter_count))
 		return(UNIXERROR(ERRDOS,ERRbadfid));
 
 	DEBUG(3,("call_nt_transact_notify_change: notify change called on directory \


More information about the samba-technical mailing list