split all the change notify code out into a separate module
[nivanova/samba-autobuild/.git] / source3 / smbd / notify.c
1 #define OLD_NTDOMAIN 1
2 /*
3    Unix SMB/Netbios implementation.
4    Version 1.9.
5    SMB NT transaction handling
6    Copyright (C) Jeremy Allison 1994-1998
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 extern int DEBUGLEVEL;
26
27 /****************************************************************************
28  This is the structure to keep the information needed to
29  determine if a directory has changed.
30 *****************************************************************************/
31
32 typedef struct {
33   time_t modify_time; /* Info from the directory we're monitoring. */ 
34   time_t status_time; /* Info from the directory we're monitoring. */
35   time_t total_time; /* Total time of all directory entries - don't care if it wraps. */
36   unsigned int num_entries; /* Zero or the number of files in the directory. */
37 } change_hash_data;
38
39 /****************************************************************************
40  This is the structure to queue to implement NT change
41  notify. It consists of smb_size bytes stored from the
42  transact command (to keep the mid, tid etc around).
43  Plus the fid to examine and the time to check next.
44 *****************************************************************************/
45
46 typedef struct {
47   ubi_slNode msg_next;
48   files_struct *fsp;
49   connection_struct *conn;
50   uint32 flags;
51   time_t next_check_time;
52   change_hash_data change_data;
53   char request_buf[smb_size];
54 } change_notify_buf;
55
56 static ubi_slList change_notify_queue = { NULL, (ubi_slNodePtr)&change_notify_queue, 0};
57
58 /****************************************************************************
59  Setup the common parts of the return packet and send it.
60 *****************************************************************************/
61
62 static void change_notify_reply_packet(char *inbuf, int error_class, uint32 error_code)
63 {
64   char outbuf[smb_size+38];
65
66   memset(outbuf, '\0', sizeof(outbuf));
67   construct_reply_common(inbuf, outbuf);
68
69   /*
70    * If we're returning a 'too much in the directory changed' we need to
71    * set this is an NT error status flags. If we don't then the (probably
72    * untested) code in the NT redirector has a bug in that it doesn't re-issue
73    * the change notify.... Ah - I *love* it when I get so deeply into this I
74    * can even determine how MS failed to test stuff and why.... :-). JRA.
75    */
76
77   if(error_class == 0) /* NT Error. */
78     SSVAL(outbuf,smb_flg2, SVAL(outbuf,smb_flg2) | FLAGS2_32_BIT_ERROR_CODES);
79
80   ERROR(error_class,error_code);
81
82   /*
83    * Seems NT needs a transact command with an error code
84    * in it. This is a longer packet than a simple error.
85    */
86   set_message(outbuf,18,0,False);
87
88   send_smb(smbd_server_fd(),outbuf);
89 }
90
91 /****************************************************************************
92  Create the hash we will use to determine if the contents changed.
93 *****************************************************************************/
94
95 static BOOL create_directory_notify_hash( change_notify_buf *cnbp, change_hash_data *change_data)
96 {
97   SMB_STRUCT_STAT st;
98   files_struct *fsp = cnbp->fsp;
99
100   memset((char *)change_data, '\0', sizeof(change_data));
101
102   /* 
103    * Store the current timestamp on the directory we are monitoring.
104    */
105
106   if(dos_stat(fsp->fsp_name, &st) < 0) {
107     DEBUG(0,("create_directory_notify_hash: Unable to stat name = %s. \
108 Error was %s\n", fsp->fsp_name, strerror(errno) ));
109     return False;
110   }
111  
112   change_data->modify_time = st.st_mtime;
113   change_data->status_time = st.st_ctime;
114
115   /*
116    * If we are to watch for changes that are only stored
117    * in inodes of files, not in the directory inode, we must
118    * scan the directory and produce a unique identifier with
119    * which we can determine if anything changed. We use the
120    * modify and change times from all the files in the
121    * directory, added together (ignoring wrapping if it's
122    * larger than the max time_t value).
123    */
124
125   if(cnbp->flags & (FILE_NOTIFY_CHANGE_SIZE|FILE_NOTIFY_CHANGE_LAST_WRITE)) {
126     pstring full_name;
127     char *p;
128     char *fname;
129     size_t remaining_len;
130     size_t fullname_len;
131     void *dp = OpenDir(cnbp->conn, fsp->fsp_name, True);
132
133     if(dp == NULL) {
134       DEBUG(0,("create_directory_notify_hash: Unable to open directory = %s. \
135 Error was %s\n", fsp->fsp_name, strerror(errno) ));
136       return False;
137     }
138
139     change_data->num_entries = 0;
140
141     pstrcpy(full_name, fsp->fsp_name);
142     pstrcat(full_name, "/");
143
144     fullname_len = strlen(full_name);
145     remaining_len = sizeof(full_name) - fullname_len - 1;
146     p = &full_name[fullname_len];
147
148     while ((fname = ReadDirName(dp))) {
149       if(strequal(fname, ".") || strequal(fname, ".."))
150         continue;
151
152       change_data->num_entries++;
153       safe_strcpy( p, fname, remaining_len);
154
155       memset(&st, '\0', sizeof(st));
156
157       /*
158        * Do the stat - but ignore errors.
159        */
160
161       if(dos_stat(full_name, &st) < 0) {
162         DEBUG(5,("create_directory_notify_hash: Unable to stat content file = %s. \
163 Error was %s\n", fsp->fsp_name, strerror(errno) ));
164       }
165       change_data->total_time += (st.st_mtime + st.st_ctime);
166     }
167
168     CloseDir(dp);
169   }
170
171   return True;
172 }
173
174 /****************************************************************************
175  Delete entries by fnum from the change notify pending queue.
176 *****************************************************************************/
177
178 void remove_pending_change_notify_requests_by_fid(files_struct *fsp)
179 {
180   change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue );
181   change_notify_buf *prev = NULL;
182
183   while(cnbp != NULL) {
184     if(cnbp->fsp->fnum == fsp->fnum) {
185       free((char *)ubi_slRemNext( &change_notify_queue, prev));
186       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
187       continue;
188     }
189
190     prev = cnbp;
191     cnbp = (change_notify_buf *)ubi_slNext(cnbp);
192   }
193 }
194
195 /****************************************************************************
196  Delete entries by mid from the change notify pending queue. Always send reply.
197 *****************************************************************************/
198
199 void remove_pending_change_notify_requests_by_mid(int mid)
200 {
201   change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue );
202   change_notify_buf *prev = NULL;
203
204   while(cnbp != NULL) {
205     if(SVAL(cnbp->request_buf,smb_mid) == mid) {
206       change_notify_reply_packet(cnbp->request_buf,0,0xC0000000 |NT_STATUS_CANCELLED);
207       free((char *)ubi_slRemNext( &change_notify_queue, prev));
208       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
209       continue;
210     }
211
212     prev = cnbp;
213     cnbp = (change_notify_buf *)ubi_slNext(cnbp);
214   }
215 }
216
217 /****************************************************************************
218  Delete entries by filename and cnum from the change notify pending queue.
219  Always send reply.
220 *****************************************************************************/
221
222 void remove_pending_change_notify_requests_by_filename(files_struct *fsp)
223 {
224   change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue );
225   change_notify_buf *prev = NULL;
226
227   while(cnbp != NULL) {
228     /*
229      * We know it refers to the same directory if the connection number and
230      * the filename are identical.
231      */
232     if((cnbp->fsp->conn == fsp->conn) && strequal(cnbp->fsp->fsp_name,fsp->fsp_name)) {
233       change_notify_reply_packet(cnbp->request_buf,0,0xC0000000 |NT_STATUS_CANCELLED);
234       free((char *)ubi_slRemNext( &change_notify_queue, prev));
235       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
236       continue;
237     }
238
239     prev = cnbp;
240     cnbp = (change_notify_buf *)ubi_slNext(cnbp);
241   }
242 }
243
244 /****************************************************************************
245  Process the change notify queue. Note that this is only called as root.
246  Returns True if there are still outstanding change notify requests on the
247  queue.
248 *****************************************************************************/
249
250 BOOL process_pending_change_notify_queue(time_t t)
251 {
252   change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue );
253   change_notify_buf *prev = NULL;
254
255   if(cnbp == NULL)
256     return False;
257
258   if(cnbp->next_check_time >= t)
259     return True;
260
261   /*
262    * It's time to check. Go through the queue and see if
263    * the timestamps changed.
264    */
265
266   while((cnbp != NULL) && (cnbp->next_check_time <= t)) {
267     change_hash_data change_data;
268     connection_struct *conn = cnbp->conn;
269     uint16 vuid = (lp_security() == SEC_SHARE) ? UID_FIELD_INVALID : 
270                   SVAL(cnbp->request_buf,smb_uid);
271
272     ZERO_STRUCT(change_data);
273
274     /*
275      * Ensure we don't have any old chain_fsp values
276      * sitting around....
277      */
278     chain_size = 0;
279     file_chain_reset();
280
281     if(!become_user(conn,vuid)) {
282       DEBUG(0,("process_pending_change_notify_queue: Unable to become user vuid=%d.\n",
283             vuid ));
284       /*
285        * Remove the entry and return an error to the client.
286        */
287       change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess);
288       free((char *)ubi_slRemNext( &change_notify_queue, prev));
289       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
290       continue;
291     }
292
293     if(!become_service(conn,True)) {
294             DEBUG(0,("process_pending_change_notify_queue: Unable to become service Error was %s.\n", strerror(errno) ));
295       /*
296        * Remove the entry and return an error to the client.
297        */
298       change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess);
299       free((char *)ubi_slRemNext( &change_notify_queue, prev));
300       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
301       unbecome_user();
302       continue;
303     }
304
305     if(!create_directory_notify_hash( cnbp, &change_data)) {
306       DEBUG(0,("process_pending_change_notify_queue: Unable to create change data for \
307 directory %s\n", cnbp->fsp->fsp_name ));
308       /*
309        * Remove the entry and return an error to the client.
310        */
311       change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess);
312       free((char *)ubi_slRemNext( &change_notify_queue, prev));
313       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
314       unbecome_user();
315       continue;
316     }
317
318     if(memcmp( (char *)&cnbp->change_data, (char *)&change_data, sizeof(change_data))) {
319       /*
320        * Remove the entry and return a change notify to the client.
321        */
322       DEBUG(5,("process_pending_change_notify_queue: directory name = %s changed.\n",
323             cnbp->fsp->fsp_name ));
324       change_notify_reply_packet(cnbp->request_buf,0,NT_STATUS_NOTIFY_ENUM_DIR);
325       free((char *)ubi_slRemNext( &change_notify_queue, prev));
326       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
327       unbecome_user();
328       continue;
329     }
330
331     unbecome_user();
332
333     /*
334      * Move to the next in the list.
335      */
336     prev = cnbp;
337     cnbp = (change_notify_buf *)ubi_slNext(cnbp);
338   }
339
340   return (cnbp != NULL);
341 }
342
343 /****************************************************************************
344  Return true if there are pending change notifies.
345 ****************************************************************************/
346 BOOL change_notifies_pending(void)
347 {
348   change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue );
349   return (cnbp != NULL);
350 }
351
352 /****************************************************************************
353    * Now queue an entry on the notify change stack. We timestamp
354    * the entry we are adding so that we know when to scan next.
355    * We only need to save smb_size bytes from this incoming packet
356    * as we will always by returning a 'read the directory yourself'
357    * error.
358 ****************************************************************************/
359 BOOL change_notify_set(char *inbuf, files_struct *fsp, connection_struct *conn, uint32 flags)
360 {
361         change_notify_buf *cnbp;
362
363         if((cnbp = (change_notify_buf *)malloc(sizeof(change_notify_buf))) == NULL) {
364                 DEBUG(0,("call_nt_transact_notify_change: malloc fail !\n" ));
365                 return -1;
366         }
367
368         ZERO_STRUCTP(cnbp);
369
370         memcpy(cnbp->request_buf, inbuf, smb_size);
371         cnbp->fsp = fsp;
372         cnbp->conn = conn;
373         cnbp->next_check_time = time(NULL) + lp_change_notify_timeout();
374         cnbp->flags = flags;
375         
376         if (!create_directory_notify_hash(cnbp, &cnbp->change_data)) {
377                 free((char *)cnbp);
378                 return False;
379         }
380         
381         /*
382          * Adding to the tail enables us to check only
383          * the head when scanning for change, as this entry
384          * is forced to have the first timeout expiration.
385          */
386         
387         ubi_slAddTail(&change_notify_queue, cnbp);
388
389         return True;
390 }
391
392 #undef OLD_NTDOMAIN