Initial import
[samba] / source / smbd / notify_hash.c
1 /*
2    Unix SMB/CIFS implementation.
3    change notify handling - hash based implementation
4    Copyright (C) Jeremy Allison 1994-1998
5    Copyright (C) Andrew Tridgell 2000
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "includes.h"
23
24 struct change_data {
25         time_t last_check_time; /* time we last checked this entry */
26 #ifdef HAVE_STAT_HIRES_TIMESTAMPS
27         struct timespec modify_time;
28         struct timespec status_time;
29 #else
30         time_t modify_time; /* Info from the directory we're monitoring. */ 
31         time_t status_time; /* Info from the directory we're monitoring. */
32 #endif
33         time_t total_time; /* Total time of all directory entries - don't care if it wraps. */
34         unsigned int num_entries; /* Zero or the number of files in the directory. */
35         unsigned int mode_sum;
36         unsigned char name_hash[16];
37 };
38
39
40 #ifdef HAVE_STAT_HIRES_TIMESTAMPS
41 /* Compare struct timespec. */
42 #define TIMESTAMP_NEQ(x, y) (((x).tv_sec != (y).tv_sec) || ((x).tv_nsec != (y).tv_nsec))
43 #else
44 /* Compare time_t . */
45 #define TIMESTAMP_NEQ(x, y) ((x) != (y))
46 #endif
47
48 /****************************************************************************
49  Create the hash we will use to determine if the contents changed.
50 *****************************************************************************/
51
52 static BOOL notify_hash(connection_struct *conn, char *path, uint32 flags, 
53                         struct change_data *data, struct change_data *old_data)
54 {
55         SMB_STRUCT_STAT st;
56         pstring full_name;
57         char *p;
58         const char *fname;
59         size_t remaining_len;
60         size_t fullname_len;
61         struct smb_Dir *dp;
62         long offset;
63
64         ZERO_STRUCTP(data);
65
66         if(SMB_VFS_STAT(conn,path, &st) == -1)
67                 return False;
68
69 #ifdef HAVE_STAT_HIRES_TIMESTAMPS
70         data->modify_time = st.st_mtim;
71         data->status_time = st.st_ctim;
72 #else
73         data->modify_time = st.st_mtime;
74         data->status_time = st.st_ctime;
75 #endif
76
77         if (old_data) {
78                 /*
79                  * Shortcut to avoid directory scan if the time
80                  * has changed - we always must return true then.
81                  */
82                 if (TIMESTAMP_NEQ(old_data->modify_time, data->modify_time) ||
83                     TIMESTAMP_NEQ(old_data->status_time, data->status_time) ) {
84                                 return True;
85                 }
86         }
87  
88         if (S_ISDIR(st.st_mode) && 
89             (flags & ~(FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME)) == 0)
90         {
91                 /* This is the case of a client wanting to know only when
92                  * the contents of a directory changes. Since any file
93                  * creation, rename or deletion will update the directory
94                  * timestamps, we don't need to create a hash.
95                  */
96                 return True;
97         }
98
99         /*
100          * If we are to watch for changes that are only stored
101          * in inodes of files, not in the directory inode, we must
102          * scan the directory and produce a unique identifier with
103          * which we can determine if anything changed. We use the
104          * modify and change times from all the files in the
105          * directory, added together (ignoring wrapping if it's
106          * larger than the max time_t value).
107          */
108
109         dp = OpenDir(conn, path, NULL, 0);
110         if (dp == NULL)
111                 return False;
112
113         data->num_entries = 0;
114         
115         pstrcpy(full_name, path);
116         pstrcat(full_name, "/");
117         
118         fullname_len = strlen(full_name);
119         remaining_len = sizeof(full_name) - fullname_len - 1;
120         p = &full_name[fullname_len];
121         
122         offset = 0;
123         while ((fname = ReadDirName(dp, &offset))) {
124                 SET_STAT_INVALID(st);
125                 if(strequal(fname, ".") || strequal(fname, ".."))
126                         continue;               
127
128                 if (!is_visible_file(conn, path, fname, &st, True))
129                         continue;
130
131                 data->num_entries++;
132                 safe_strcpy(p, fname, remaining_len);
133
134                 /*
135                  * Do the stat - but ignore errors.
136                  */             
137                 if (!VALID_STAT(st)) {
138                         SMB_VFS_STAT(conn,full_name, &st);
139                 }
140
141                 /*
142                  * Always sum the times.
143                  */
144
145                 data->total_time += (st.st_mtime + st.st_ctime);
146
147                 /*
148                  * If requested hash the names.
149                  */
150
151                 if (flags & (FILE_NOTIFY_CHANGE_DIR_NAME|FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_FILE)) {
152                         int i;
153                         unsigned char tmp_hash[16];
154                         mdfour(tmp_hash, (const unsigned char *)fname, strlen(fname));
155                         for (i=0;i<16;i++)
156                                 data->name_hash[i] ^= tmp_hash[i];
157                 }
158
159                 /*
160                  * If requested sum the mode_t's.
161                  */
162
163                 if (flags & (FILE_NOTIFY_CHANGE_ATTRIBUTES|FILE_NOTIFY_CHANGE_SECURITY))
164                         data->mode_sum += st.st_mode;
165         }
166         
167         CloseDir(dp);
168         
169         return True;
170 }
171
172 /****************************************************************************
173  Register a change notify request.
174 *****************************************************************************/
175
176 static void *hash_register_notify(connection_struct *conn, char *path, uint32 flags)
177 {
178         struct change_data data;
179
180         if (!notify_hash(conn, path, flags, &data, NULL))
181                 return NULL;
182
183         data.last_check_time = time(NULL);
184
185         return (void *)memdup(&data, sizeof(data));
186 }
187
188 /****************************************************************************
189  Check if a change notify should be issued.
190  A time of zero means instantaneous check - don't modify the last check time.
191 *****************************************************************************/
192
193 static BOOL hash_check_notify(connection_struct *conn, uint16 vuid, char *path, uint32 flags, void *datap, time_t t)
194 {
195         struct change_data *data = (struct change_data *)datap;
196         struct change_data data2;
197
198         if (t && t < data->last_check_time + lp_change_notify_timeout())
199                 return False;
200
201         if (!change_to_user(conn,vuid))
202                 return True;
203         if (!set_current_service(conn,FLAG_CASELESS_PATHNAMES,True)) {
204                 change_to_root_user();
205                 return True;
206         }
207
208         if (!notify_hash(conn, path, flags, &data2, data) ||
209             TIMESTAMP_NEQ(data2.modify_time, data->modify_time) ||
210             TIMESTAMP_NEQ(data2.status_time, data->status_time) ||
211             data2.total_time != data->total_time ||
212             data2.num_entries != data->num_entries ||
213                 data2.mode_sum != data->mode_sum ||
214                 memcmp(data2.name_hash, data->name_hash, sizeof(data2.name_hash))) {
215                 change_to_root_user();
216                 return True;
217         }
218
219         if (t)
220                 data->last_check_time = t;
221
222         change_to_root_user();
223
224         return False;
225 }
226
227 /****************************************************************************
228  Remove a change notify data structure.
229 *****************************************************************************/
230
231 static void hash_remove_notify(void *datap)
232 {
233         free(datap);
234 }
235
236 /****************************************************************************
237  Setup hash based change notify.
238 ****************************************************************************/
239
240 struct cnotify_fns *hash_notify_init(void) 
241 {
242         static struct cnotify_fns cnotify;
243
244         cnotify.register_notify = hash_register_notify;
245         cnotify.check_notify = hash_check_notify;
246         cnotify.remove_notify = hash_remove_notify;
247         cnotify.select_time = lp_change_notify_timeout();
248
249         return &cnotify;
250 }
251
252 /*
253   change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess);
254   change_notify_reply_packet(cnbp->request_buf,0,NT_STATUS_NOTIFY_ENUM_DIR);
255
256   chain_size = 0;
257   file_chain_reset();
258
259   uint16 vuid = (lp_security() == SEC_SHARE) ? UID_FIELD_INVALID : 
260   SVAL(cnbp->request_buf,smb_uid);
261 */