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)