2cd8fbc6bc06651c61dfa54bf07597e56006b2d1
[samba.git] / source3 / 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    Copyright (C) Volker Lendecke 2007
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22
23 #include "includes.h"
24
25 struct hash_change_data {
26         time_t last_check_time; /* time we last checked this entry */
27         struct timespec modify_time; /* Info from the directory we're
28                                       * monitoring. */
29         struct timespec status_time; /* Info from the directory we're
30                                       * monitoring. */
31         time_t total_time; /* Total time of all directory entries - don't care
32                             * if it wraps. */
33         unsigned int num_entries; /* Zero or the number of files in the
34                                    * directory. */
35         unsigned int mode_sum;
36         unsigned char name_hash[16];
37 };
38
39 struct hash_notify_ctx {
40         struct hash_change_data *data;
41         files_struct *fsp;
42         char *path;
43         uint32 filter;
44 };
45
46 /* Compare struct timespec. */
47 #define TIMESTAMP_NEQ(x, y) (((x).tv_sec != (y).tv_sec) || ((x).tv_nsec != (y).tv_nsec))
48
49 /****************************************************************************
50  Create the hash we will use to determine if the contents changed.
51 *****************************************************************************/
52
53 static BOOL notify_hash(connection_struct *conn, char *path, uint32 flags, 
54                         struct hash_change_data *data,
55                         struct hash_change_data *old_data)
56 {
57         SMB_STRUCT_STAT st;
58         pstring full_name;
59         char *p;
60         const char *fname;
61         size_t remaining_len;
62         size_t fullname_len;
63         struct smb_Dir *dp;
64         long offset;
65
66         ZERO_STRUCTP(data);
67
68         if(SMB_VFS_STAT(conn,path, &st) == -1)
69                 return False;
70
71         data->modify_time = get_mtimespec(&st);
72         data->status_time = get_ctimespec(&st);
73
74         if (old_data) {
75                 /*
76                  * Shortcut to avoid directory scan if the time
77                  * has changed - we always must return true then.
78                  */
79                 if (TIMESTAMP_NEQ(old_data->modify_time, data->modify_time) ||
80                     TIMESTAMP_NEQ(old_data->status_time, data->status_time) ) {
81                                 return True;
82                 }
83         }
84  
85         if (S_ISDIR(st.st_mode) && 
86             (flags & ~(FILE_NOTIFY_CHANGE_FILE_NAME
87                        | FILE_NOTIFY_CHANGE_DIR_NAME)) == 0)
88         {
89                 /* This is the case of a client wanting to know only when
90                  * the contents of a directory changes. Since any file
91                  * creation, rename or deletion will update the directory
92                  * timestamps, we don't need to create a hash.
93                  */
94                 return True;
95         }
96
97         /*
98          * If we are to watch for changes that are only stored
99          * in inodes of files, not in the directory inode, we must
100          * scan the directory and produce a unique identifier with
101          * which we can determine if anything changed. We use the
102          * modify and change times from all the files in the
103          * directory, added together (ignoring wrapping if it's
104          * larger than the max time_t value).
105          */
106
107         dp = OpenDir(conn, path, NULL, 0);
108         if (dp == NULL)
109                 return False;
110
111         data->num_entries = 0;
112         
113         pstrcpy(full_name, path);
114         pstrcat(full_name, "/");
115         
116         fullname_len = strlen(full_name);
117         remaining_len = sizeof(full_name) - fullname_len - 1;
118         p = &full_name[fullname_len];
119         
120         offset = 0;
121         while ((fname = ReadDirName(dp, &offset))) {
122                 SET_STAT_INVALID(st);
123                 if(strequal(fname, ".") || strequal(fname, ".."))
124                         continue;               
125
126                 if (!is_visible_file(conn, path, fname, &st, True))
127                         continue;
128
129                 data->num_entries++;
130                 safe_strcpy(p, fname, remaining_len);
131
132                 /*
133                  * Do the stat - but ignore errors.
134                  */             
135                 if (!VALID_STAT(st)) {
136                         SMB_VFS_STAT(conn,full_name, &st);
137                 }
138
139                 /*
140                  * Always sum the times.
141                  */
142
143                 data->total_time += (st.st_mtime + st.st_ctime);
144
145                 /*
146                  * If requested hash the names.
147                  */
148
149                 if (flags & (FILE_NOTIFY_CHANGE_DIR_NAME
150                              |FILE_NOTIFY_CHANGE_FILE_NAME)) {
151                         int i;
152                         unsigned char tmp_hash[16];
153                         mdfour(tmp_hash, (const unsigned char *)fname,
154                                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
164                              |FILE_NOTIFY_CHANGE_SECURITY))
165                         data->mode_sum += st.st_mode;
166         }
167         
168         CloseDir(dp);
169         
170         return True;
171 }
172
173 static void hash_change_notify_handler(struct event_context *event_ctx,
174                                        struct timed_event *te,
175                                        const struct timeval *now,
176                                        void *private_data)
177 {
178         struct hash_change_data *new_data;
179         struct hash_notify_ctx *ctx =
180                 talloc_get_type_abort(private_data,
181                                       struct hash_notify_ctx);
182
183         TALLOC_FREE(te);
184
185         if (!(new_data = TALLOC_P(ctx, struct hash_change_data))) {
186                 DEBUG(0, ("talloc failed\n"));
187                 /*
188                  * No new timed event;
189                  */
190                 return;
191         }
192
193         if (!notify_hash(ctx->fsp->conn, ctx->fsp->fsp_name,
194                          ctx->filter, new_data, ctx->data)
195             || TIMESTAMP_NEQ(new_data->modify_time, ctx->data->modify_time)
196             || TIMESTAMP_NEQ(new_data->status_time, ctx->data->status_time)
197             || new_data->total_time != ctx->data->total_time
198             || new_data->num_entries != ctx->data->num_entries
199             || new_data->mode_sum != ctx->data->mode_sum
200             || (memcmp(new_data->name_hash, ctx->data->name_hash,
201                        sizeof(new_data->name_hash)))) {
202                 notify_fsp(ctx->fsp, 0, NULL);
203         }
204
205         TALLOC_FREE(ctx->data);
206         ctx->data = new_data;
207
208         event_add_timed(
209                 event_ctx, ctx,
210                 timeval_current_ofs(
211                         lp_change_notify_timeout(SNUM(ctx->fsp->conn)), 0),
212                 "hash_change_notify_handler",
213                 hash_change_notify_handler, ctx);
214 }
215
216
217
218 static void *hash_notify_add(TALLOC_CTX *mem_ctx,
219                              struct event_context *event_ctx,
220                              files_struct *fsp,
221                              uint32 *filter)
222 {
223         struct hash_notify_ctx *ctx;
224         int timeout = lp_change_notify_timeout(SNUM(fsp->conn));
225
226         if (timeout <= 0) {
227                 /* It change notify timeout has been disabled, never scan the
228                  * directory. */
229                 return NULL;
230         }
231
232         if (!(ctx = TALLOC_P(mem_ctx, struct hash_notify_ctx))) {
233                 DEBUG(0, ("talloc failed\n"));
234                 return NULL;
235         }
236
237         if (!(ctx->path = talloc_asprintf(ctx, "%s/%s", fsp->conn->connectpath,
238                                           fsp->fsp_name))) {
239                 DEBUG(0, ("talloc_asprintf failed\n"));
240                 TALLOC_FREE(ctx);
241                 return NULL;
242         }
243
244         if (!(ctx->data = TALLOC_P(ctx, struct hash_change_data))) {
245                 DEBUG(0, ("talloc failed\n"));
246                 TALLOC_FREE(ctx);
247                 return NULL;
248         }
249
250         ctx->fsp = fsp;
251
252         /*
253          * Don't change the Samba filter, hash can only be a very bad attempt
254          * anyway.
255          */
256         ctx->filter = *filter;
257
258         notify_hash(fsp->conn, ctx->path, ctx->filter, ctx->data, NULL);
259
260         event_add_timed(event_ctx, ctx, timeval_current_ofs(timeout, 0),
261                         "hash_change_notify_handler",
262                         hash_change_notify_handler, ctx);
263
264         return (void *)ctx;
265 }
266
267 /****************************************************************************
268  Setup hash based change notify.
269 ****************************************************************************/
270
271 struct cnotify_fns *hash_notify_init(void) 
272 {
273         static struct cnotify_fns cnotify;
274
275         cnotify.notify_add = hash_notify_add;
276
277         return &cnotify;
278 }