Initial import
[samba] / source / smbd / notify_hash.c
diff --git a/source/smbd/notify_hash.c b/source/smbd/notify_hash.c
new file mode 100644 (file)
index 0000000..ee7d431
--- /dev/null
@@ -0,0 +1,261 @@
+/*
+   Unix SMB/CIFS implementation.
+   change notify handling - hash based implementation
+   Copyright (C) Jeremy Allison 1994-1998
+   Copyright (C) Andrew Tridgell 2000
+
+   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
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   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.
+*/
+
+#include "includes.h"
+
+struct change_data {
+       time_t last_check_time; /* time we last checked this entry */
+#ifdef HAVE_STAT_HIRES_TIMESTAMPS
+       struct timespec modify_time;
+       struct timespec status_time;
+#else
+       time_t modify_time; /* Info from the directory we're monitoring. */ 
+       time_t status_time; /* Info from the directory we're monitoring. */
+#endif
+       time_t total_time; /* Total time of all directory entries - don't care if it wraps. */
+       unsigned int num_entries; /* Zero or the number of files in the directory. */
+       unsigned int mode_sum;
+       unsigned char name_hash[16];
+};
+
+
+#ifdef HAVE_STAT_HIRES_TIMESTAMPS
+/* Compare struct timespec. */
+#define TIMESTAMP_NEQ(x, y) (((x).tv_sec != (y).tv_sec) || ((x).tv_nsec != (y).tv_nsec))
+#else
+/* Compare time_t . */
+#define TIMESTAMP_NEQ(x, y) ((x) != (y))
+#endif
+
+/****************************************************************************
+ Create the hash we will use to determine if the contents changed.
+*****************************************************************************/
+
+static BOOL notify_hash(connection_struct *conn, char *path, uint32 flags, 
+                       struct change_data *data, struct change_data *old_data)
+{
+       SMB_STRUCT_STAT st;
+       pstring full_name;
+       char *p;
+       const char *fname;
+       size_t remaining_len;
+       size_t fullname_len;
+       struct smb_Dir *dp;
+       long offset;
+
+       ZERO_STRUCTP(data);
+
+       if(SMB_VFS_STAT(conn,path, &st) == -1)
+               return False;
+
+#ifdef HAVE_STAT_HIRES_TIMESTAMPS
+       data->modify_time = st.st_mtim;
+       data->status_time = st.st_ctim;
+#else
+       data->modify_time = st.st_mtime;
+       data->status_time = st.st_ctime;
+#endif
+
+       if (old_data) {
+               /*
+                * Shortcut to avoid directory scan if the time
+                * has changed - we always must return true then.
+                */
+               if (TIMESTAMP_NEQ(old_data->modify_time, data->modify_time) ||
+                   TIMESTAMP_NEQ(old_data->status_time, data->status_time) ) {
+                               return True;
+               }
+       }
+        if (S_ISDIR(st.st_mode) && 
+            (flags & ~(FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME)) == 0)
+        {
+               /* This is the case of a client wanting to know only when
+                * the contents of a directory changes. Since any file
+                * creation, rename or deletion will update the directory
+                * timestamps, we don't need to create a hash.
+                */
+                return True;
+        }
+
+       /*
+        * If we are to watch for changes that are only stored
+        * in inodes of files, not in the directory inode, we must
+        * scan the directory and produce a unique identifier with
+        * which we can determine if anything changed. We use the
+        * modify and change times from all the files in the
+        * directory, added together (ignoring wrapping if it's
+        * larger than the max time_t value).
+        */
+
+       dp = OpenDir(conn, path, NULL, 0);
+       if (dp == NULL)
+               return False;
+
+       data->num_entries = 0;
+       
+       pstrcpy(full_name, path);
+       pstrcat(full_name, "/");
+       
+       fullname_len = strlen(full_name);
+       remaining_len = sizeof(full_name) - fullname_len - 1;
+       p = &full_name[fullname_len];
+       
+       offset = 0;
+       while ((fname = ReadDirName(dp, &offset))) {
+               SET_STAT_INVALID(st);
+               if(strequal(fname, ".") || strequal(fname, ".."))
+                       continue;               
+
+               if (!is_visible_file(conn, path, fname, &st, True))
+                       continue;
+
+               data->num_entries++;
+               safe_strcpy(p, fname, remaining_len);
+
+               /*
+                * Do the stat - but ignore errors.
+                */             
+               if (!VALID_STAT(st)) {
+                       SMB_VFS_STAT(conn,full_name, &st);
+               }
+
+               /*
+                * Always sum the times.
+                */
+
+               data->total_time += (st.st_mtime + st.st_ctime);
+
+               /*
+                * If requested hash the names.
+                */
+
+               if (flags & (FILE_NOTIFY_CHANGE_DIR_NAME|FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_FILE)) {
+                       int i;
+                       unsigned char tmp_hash[16];
+                       mdfour(tmp_hash, (const unsigned char *)fname, strlen(fname));
+                       for (i=0;i<16;i++)
+                               data->name_hash[i] ^= tmp_hash[i];
+               }
+
+               /*
+                * If requested sum the mode_t's.
+                */
+
+               if (flags & (FILE_NOTIFY_CHANGE_ATTRIBUTES|FILE_NOTIFY_CHANGE_SECURITY))
+                       data->mode_sum += st.st_mode;
+       }
+       
+       CloseDir(dp);
+       
+       return True;
+}
+
+/****************************************************************************
+ Register a change notify request.
+*****************************************************************************/
+
+static void *hash_register_notify(connection_struct *conn, char *path, uint32 flags)
+{
+       struct change_data data;
+
+       if (!notify_hash(conn, path, flags, &data, NULL))
+               return NULL;
+
+       data.last_check_time = time(NULL);
+
+       return (void *)memdup(&data, sizeof(data));
+}
+
+/****************************************************************************
+ Check if a change notify should be issued.
+ A time of zero means instantaneous check - don't modify the last check time.
+*****************************************************************************/
+
+static BOOL hash_check_notify(connection_struct *conn, uint16 vuid, char *path, uint32 flags, void *datap, time_t t)
+{
+       struct change_data *data = (struct change_data *)datap;
+       struct change_data data2;
+
+       if (t && t < data->last_check_time + lp_change_notify_timeout())
+               return False;
+
+       if (!change_to_user(conn,vuid))
+               return True;
+       if (!set_current_service(conn,FLAG_CASELESS_PATHNAMES,True)) {
+               change_to_root_user();
+               return True;
+       }
+
+       if (!notify_hash(conn, path, flags, &data2, data) ||
+           TIMESTAMP_NEQ(data2.modify_time, data->modify_time) ||
+           TIMESTAMP_NEQ(data2.status_time, data->status_time) ||
+           data2.total_time != data->total_time ||
+           data2.num_entries != data->num_entries ||
+               data2.mode_sum != data->mode_sum ||
+               memcmp(data2.name_hash, data->name_hash, sizeof(data2.name_hash))) {
+               change_to_root_user();
+               return True;
+       }
+
+       if (t)
+               data->last_check_time = t;
+
+       change_to_root_user();
+
+       return False;
+}
+
+/****************************************************************************
+ Remove a change notify data structure.
+*****************************************************************************/
+
+static void hash_remove_notify(void *datap)
+{
+       free(datap);
+}
+
+/****************************************************************************
+ Setup hash based change notify.
+****************************************************************************/
+
+struct cnotify_fns *hash_notify_init(void) 
+{
+       static struct cnotify_fns cnotify;
+
+       cnotify.register_notify = hash_register_notify;
+       cnotify.check_notify = hash_check_notify;
+       cnotify.remove_notify = hash_remove_notify;
+       cnotify.select_time = lp_change_notify_timeout();
+
+       return &cnotify;
+}
+
+/*
+  change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess);
+  change_notify_reply_packet(cnbp->request_buf,0,NT_STATUS_NOTIFY_ENUM_DIR);
+
+  chain_size = 0;
+  file_chain_reset();
+
+  uint16 vuid = (lp_security() == SEC_SHARE) ? UID_FIELD_INVALID : 
+  SVAL(cnbp->request_buf,smb_uid);
+*/