Hi,

attached is a modified version of Hal's file change notification patch.
It's against Samba HEAD and works for me.

Changes:
- use push_ucs2() to send unicode file names
- make some functions static (make proto works now)
- limit maximum number of directory entries stored in the TDB to 2000, so  
  large directories won't create monster TDBs.
- the maximum reply packet size is limited to 64K. I guess this should never
  be a problem :-)
  What ist the maximum allowed size of a NT_TRANS packet anyway ?

...Juergen


--- smbd/notify.orig	2002-12-12 03:01:53.000000000 +0100
+++ smbd/notify.c	2003-03-03 21:56:56.000000000 +0100
@@ -21,9 +21,21 @@
 
 #include "includes.h"
 
+#define MAX_DIRENTRIES 1000
+
 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;
+	int filename_length;
+};
+
+/****************************************************************************
  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,16 +47,145 @@
 	files_struct *fsp;
 	connection_struct *conn;
 	uint32 flags;
+	uint32 max_parameter_count;
 	char request_buf[smb_size];
 	void *change_data;
+	TDB_CONTEXT *file_data;
+	struct file_action *file_actions;
 };
 
 static struct change_notify *change_notify_list;
 
-/****************************************************************************
- Setup the common parts of the return packet and send it.
-*****************************************************************************/
-static void change_notify_reply_packet(char *inbuf, NTSTATUS error_code)
+/**
+ * Return a file action struct with the given filename and fileaction
+ *
+ **/
+static struct file_action *change_notify_get_file_action(char *filename, int fileaction)
+{
+	struct file_action *fa;
+
+	if (!(fa = (struct file_action *)malloc(sizeof(struct file_action)))) {
+		DEBUG(0, ("malloc failed!"));
+	}
+	fa->action = fileaction;
+	fa->filename = strdup(filename);
+	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 *status)
+{
+
+	struct file_action *fa;
+	struct change_notify *cnbp = (struct change_notify *)status;
+	char *filename;
+	char *path = ((struct change_notify *)cnbp)->fsp->fsp_name;
+	int filename_len = strlen((char *)key.dptr) + strlen((char *)path) + 2;
+	int fd;
+
+	if (!(filename = (char *)malloc(filename_len))) {
+		DEBUG(0, ("malloc failed"));
+	}
+
+	filename[0] = '\0';
+	safe_strcat(filename, (char *)path, filename_len);
+	safe_strcat(filename, "/", filename_len);
+	safe_strcat(filename, (char *)key.dptr, filename_len);
+
+	if ((fd = open(filename, O_RDONLY)) == -1) {
+		fa = change_notify_get_file_action
+			((char *)key.dptr, FILE_ACTION_REMOVED);
+		DLIST_ADD(cnbp->file_actions, fa);
+	} else {
+		close(fd);
+	}
+
+	return 0;
+}
+
+/**
+ * Get a list of all the file notification actions
+ *
+ **/
+static int change_notify_find_file_actions(struct change_notify *cnbp)
+{
+	void *dp;
+	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;
+	int count;
+	struct file_action *fa;
+
+	cnbp->file_actions = NULL;
+
+	if (!(dp = OpenDir(cnbp->conn, cnbp->fsp->fsp_name, True))) {
+		DEBUG(0, ("Failed to open directory '%s'",
+			  cnbp->fsp->fsp_name));
+	}
+
+	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];
+	count = 0;
+
+	while ((fname = ReadDirName(dp)) && count < MAX_DIRENTRIES) {
+		count ++;
+		if (strequal(fname, ".") || strequal(fname, "..")) {
+			continue;
+		}
+
+		DEBUG(10, ("check file: %s\n", fname));
+		safe_strcpy(p, fname, remaining_len);
+		vfs_stat(cnbp->conn, full_name, &old_st);
+		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);
+			DLIST_ADD(cnbp->file_actions, 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);
+			DLIST_ADD(cnbp->file_actions, fa);
+		}
+	}
+
+	/* check for deleted files */
+	if (tdb_traverse(cnbp->file_data, &change_notify_file_data_exists, cnbp) == -1) {
+		DEBUG(0, ("traverse failed: %s", tdb_errorstr(cnbp->file_data)));
+	}
+	if (count == 0 || count == MAX_DIRENTRIES)
+		return 0;
+	return count;
+}
+
+/**
+ * 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];
 
@@ -63,6 +204,83 @@
 		exit_server("change_notify_reply_packet: send_smb failed.");
 }
 
+/**
+ * Setup a reply packet with the names of individual files changed and send it
+ *
+ **/
+static void change_notify_reply_packet(struct change_notify *cnbp)
+{
+	struct file_action *fa;
+	char *outbuf;
+	char *p;
+	int count;
+	int offset;
+	int total;
+	int length;
+	BOOL rc;
+	int alignment = 4- smb_size%4;
+
+	count = change_notify_find_file_actions(cnbp);
+	
+	/* return STATUS_NOTIFY_ENUM_DIR if we have too many files or none at all  */
+	if ((cnbp->max_parameter_count == 0) || (count == 0) || (count == cnbp->max_parameter_count)) {
+		change_notify_reply_error(cnbp->request_buf,STATUS_NOTIFY_ENUM_DIR);
+		return;
+	}
+
+
+	/* calculate parameter size to get max packet length */
+	count = 0;
+	total = 0;
+	fa = cnbp->file_actions;
+	while (fa && (count++ < cnbp->max_parameter_count) ) {
+		total += strlen(fa->filename) * 2 + 12;
+		fa = fa->next;
+	}
+	total += smb_size+ 2*18+alignment;
+	DEBUG(10,("length=%d, count=%d, max_count=%d\n",total, count,cnbp->max_parameter_count));
+
+	if ( total > 65535) {
+		change_notify_reply_error(cnbp->request_buf,STATUS_NOTIFY_ENUM_DIR);
+		return;
+	}
+	outbuf = malloc(total);
+	if (outbuf == NULL)
+		exit_server("change_notify_reply_notify: malloc failed.");
+
+	/* setup reply packet */
+	memset(outbuf, '\0', total);
+	construct_reply_common(cnbp->request_buf, outbuf);
+	set_message(outbuf,18,0,False);
+
+	/* fill NT NOTIFY parameters */
+	fa = cnbp->file_actions;
+	p = smb_buf(outbuf) + alignment;
+	count = 0;
+	total = 0;
+	while (fa && (count++ < cnbp->max_parameter_count) ) {
+		length=push_ucs2(NULL, p+12, fa->filename, sizeof(fstring),0);
+		SIVAL(p,8,length);
+		SIVAL(p,4,fa->action);
+		offset = length +12;
+		fa = fa->next;
+		SIVAL(p,0, (fa ? offset : 0));
+		p += offset;
+		total +=offset;
+	}
+	SIVAL(outbuf,smb_ntr_TotalParameterCount,total);
+	SIVAL(outbuf,smb_ntr_ParameterCount,total);
+	total += (total%4 ? 4-(total%4) : 0); // make sure we are aligned
+	set_message(outbuf,18,total,False);
+	SIVAL(outbuf,smb_ntr_ParameterOffset,smb_buf(outbuf) + alignment - smb_base(outbuf));
+
+	rc = send_smb(smbd_server_fd(),outbuf);
+	free(outbuf);
+	if (!rc)
+		exit_server("change_notify_reply_notify: send_smb failed.");
+}
+
+
 /****************************************************************************
  Remove an entry from the list and free it, also closing any
  directory handle if necessary.
@@ -103,7 +321,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);
 		}
 	}
@@ -125,7 +343,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);
 		}
 	}
@@ -158,7 +376,7 @@
 
 		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_reply_packet(cnbp);
 			change_notify_remove(cnbp);
 		}
 	}
@@ -167,13 +385,67 @@
 }
 
 /****************************************************************************
+ Setup the file_data field in the change_notify struct to contain
+ a tdb table of file stat data keyed by file name
+****************************************************************************/
+static void change_notify_setup_file_data(struct change_notify *cnbp) {
+
+	void *dp;
+	char *fname;
+	TDB_DATA tdb_key, tdb_data;
+	SMB_STRUCT_STAT st;
+	pstring full_name;
+	size_t fullname_len, remaining_len;
+	char *p;
+
+	if (!(cnbp->file_data = tdb_open("/dev/null", 0, TDB_INTERNAL,
+					 O_RDWR | O_CREAT | O_TRUNC, 0600))) {
+		DEBUG(0, ("Failed to open file data db"));
+
+	}
+
+	if (!(dp = OpenDir(cnbp->conn, cnbp->fsp->fsp_name, True))) {
+		DEBUG(0, ("Failed to open directory '%s'", cnbp->fsp->fsp_name));
+	}
+
+
+	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);
+
+		/*stat(full_name, (struct stat *)&st);*/
+		vfs_stat(cnbp->conn, full_name, &st);
+
+		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, ("Unable to store data for '%s': %s", full_name, tdb_errorstr(cnbp->file_data)));
+		}
+	}
+}
+
+/****************************************************************************
  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;
 
@@ -188,6 +460,7 @@
 	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) {
@@ -195,6 +468,7 @@
 		return False;
 	}
 
+	change_notify_setup_file_data(cnbp);
 	DLIST_ADD(change_notify_list, cnbp);
 
 	return True;
--- smbd/nttrans.orig	2003-03-03 21:23:16.000000000 +0100
+++ smbd/nttrans.c	2003-03-01 22:40:15.000000000 +0100
@@ -1436,6 +1436,7 @@
 	char *setup = *ppsetup;
 	files_struct *fsp;
 	uint32 flags;
+	uint32 max_parameter_count = IVAL(inbuf, smb_nt_MaxParameterCount);
 
 	fsp = file_fsp(setup,4);
 	flags = IVAL(setup, 0);
@@ -1448,7 +1449,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 \
--- include/smb.orig	2003-03-03 21:16:57.000000000 +0100
+++ include/smb.h	2003-02-27 21:13:33.000000000 +0100
@@ -1257,6 +1257,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)
 

Reply via email to