d2fdbfc49aea576095f925a0c80f2c97d2b4d919
[ira/wip.git] / source4 / ntvfs / posix / pvfs_notify.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    POSIX NTVFS backend - notify
5
6    Copyright (C) Andrew Tridgell 2006
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 #include "vfs_posix.h"
25 #include "lib/messaging/irpc.h"
26 #include "messaging/messaging.h"
27 #include "dlinklist.h"
28
29 /* pending notifies buffer, hung off struct pvfs_file for open directories
30    that have used change notify */
31 struct pvfs_notify_buffer {
32         struct pvfs_file *f;
33         uint32_t num_changes;
34         struct notify_changes *changes;
35         uint32_t max_buffer_size;
36         uint32_t current_buffer_size;
37
38         /* a list of requests waiting for events on this handle */
39         struct notify_pending {
40                 struct notify_pending *next, *prev;
41                 struct ntvfs_request *req;
42                 struct smb_notify *info;
43         } *pending;
44 };
45
46 /*
47   send a reply to a pending notify request
48 */
49 static void pvfs_notify_send(struct pvfs_notify_buffer *notify_buffer, NTSTATUS status)
50 {
51         struct notify_pending *pending = notify_buffer->pending;
52         struct ntvfs_request *req;
53         struct smb_notify *info;
54
55         if (notify_buffer->current_buffer_size > notify_buffer->max_buffer_size && 
56             notify_buffer->num_changes != 0) {
57                 /* on buffer overflow return no changes and destroys the notify buffer */
58                 notify_buffer->num_changes = 0;
59                 while (notify_buffer->pending) {
60                         pvfs_notify_send(notify_buffer, NT_STATUS_OK);
61                 }
62                 talloc_free(notify_buffer);
63                 return;
64         }
65
66         /* see if there is anyone waiting */
67         if (notify_buffer->pending == NULL) {
68                 return;
69         }
70
71         DLIST_REMOVE(notify_buffer->pending, pending);
72
73         req = pending->req;
74         info = pending->info;
75
76         info->out.num_changes = notify_buffer->num_changes;
77         info->out.changes = talloc_steal(req, notify_buffer->changes);
78         notify_buffer->num_changes = 0;
79         notify_buffer->changes = NULL;
80         notify_buffer->current_buffer_size = 0;
81
82         talloc_free(pending);
83
84         if (info->out.num_changes != 0) {
85                 status = NT_STATUS_OK;
86         }
87
88         req->async_states->status = status;
89         req->async_states->send_fn(req);
90 }
91
92 /*
93   destroy a notify buffer. Called when the handle is closed
94  */
95 static int pvfs_notify_destructor(void *ptr)
96 {
97         struct pvfs_notify_buffer *n = talloc_get_type(ptr, struct pvfs_notify_buffer);
98         notify_remove(n->f->pvfs->notify_context, n);
99         n->f->notify_buffer = NULL;
100         pvfs_notify_send(n, NT_STATUS_OK);
101         return 0;
102 }
103
104
105 /*
106   called when a async notify event comes in
107 */
108 static void pvfs_notify_callback(void *private, const struct notify_event *ev)
109 {
110         struct pvfs_notify_buffer *n = talloc_get_type(private, struct pvfs_notify_buffer);
111         size_t len;
112
113         n->changes = talloc_realloc(n, n->changes, struct notify_changes, n->num_changes+1);
114         n->changes[n->num_changes].action = ev->action;
115         n->changes[n->num_changes].name.s = talloc_strdup(n->changes, ev->path);
116         n->num_changes++;
117
118         /*
119           work out how much room this will take in the buffer
120         */
121         len = 12 + strlen_m(ev->path)*2;
122         if (len & 3) {
123                 len += 4 - (len & 3);
124         }
125         n->current_buffer_size += len;
126
127         /* send what we have */
128         pvfs_notify_send(n, NT_STATUS_OK);
129 }
130
131 /*
132   setup a notify buffer on a directory handle
133 */
134 static NTSTATUS pvfs_notify_setup(struct pvfs_state *pvfs, struct pvfs_file *f, 
135                                   uint32_t buffer_size, uint32_t filter, BOOL recursive)
136 {
137         NTSTATUS status;
138         struct notify_entry e;
139
140         f->notify_buffer = talloc_zero(f, struct pvfs_notify_buffer);
141         NT_STATUS_HAVE_NO_MEMORY(f->notify_buffer);
142
143         f->notify_buffer->max_buffer_size = buffer_size;
144         f->notify_buffer->f = f;
145
146         e.filter    = filter;
147         e.path      = f->handle->name->full_name;
148         if (recursive) {
149                 e.subdir_filter = filter;
150         } else {
151                 e.subdir_filter = 0;
152         }
153
154         status = notify_add(pvfs->notify_context, &e, 
155                             pvfs_notify_callback, f->notify_buffer);
156         NT_STATUS_NOT_OK_RETURN(status);
157
158         talloc_set_destructor(f->notify_buffer, pvfs_notify_destructor);
159
160         return NT_STATUS_OK;
161 }
162
163 /*
164   called from the pvfs_wait code when either an event has come in, or
165   the notify request has been cancelled
166 */
167 static void pvfs_notify_end(void *private, enum pvfs_wait_notice reason)
168 {
169         struct pvfs_notify_buffer *notify_buffer = talloc_get_type(private, 
170                                                                    struct pvfs_notify_buffer);
171         if (reason == PVFS_WAIT_CANCEL) {
172                 pvfs_notify_send(notify_buffer, NT_STATUS_CANCELLED);
173         } else {
174                 pvfs_notify_send(notify_buffer, NT_STATUS_OK);
175         }
176 }
177
178 /* change notify request - always async. This request blocks until the
179    event buffer is non-empty */
180 NTSTATUS pvfs_notify(struct ntvfs_module_context *ntvfs, 
181                      struct ntvfs_request *req,
182                      struct smb_notify *info)
183 {
184         struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data, 
185                                                   struct pvfs_state);
186         struct pvfs_file *f;
187         NTSTATUS status;
188         struct notify_pending *pending;
189
190         f = pvfs_find_fd(pvfs, req, info->in.file.fnum);
191         if (!f) {
192                 return NT_STATUS_INVALID_HANDLE;
193         }
194
195         /* this request doesn't make sense unless its async */
196         if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
197                 return NT_STATUS_INVALID_PARAMETER;
198         }
199
200         /* its only valid for directories */
201         if (f->handle->fd != -1) {
202                 return NT_STATUS_INVALID_PARAMETER;
203         }
204
205         /* if the handle doesn't currently have a notify buffer then
206            create one */
207         if (f->notify_buffer == NULL) {
208                 status = pvfs_notify_setup(pvfs, f, 
209                                            info->in.buffer_size, 
210                                            info->in.completion_filter,
211                                            info->in.recursive);
212                 NT_STATUS_NOT_OK_RETURN(status);
213         }
214
215         f->notify_buffer->max_buffer_size = info->in.buffer_size;
216
217         pending = talloc(f->notify_buffer, struct notify_pending);
218         NT_STATUS_HAVE_NO_MEMORY(pending);
219
220         pending->req = talloc_reference(pending, req);
221         pending->info = info;
222
223         DLIST_ADD_END(f->notify_buffer->pending, pending, struct notify_pending *);
224
225         /* if the buffer is empty then start waiting */
226         if (f->notify_buffer->num_changes == 0) {
227                 void *wait_handle =
228                         pvfs_wait_message(pvfs, req, -1, timeval_zero(), 
229                                           pvfs_notify_end, f->notify_buffer);
230                 NT_STATUS_HAVE_NO_MEMORY(wait_handle);
231                 talloc_steal(req, wait_handle);
232                 return NT_STATUS_OK;
233         }
234
235         pvfs_notify_send(f->notify_buffer, NT_STATUS_OK);
236
237         return NT_STATUS_OK;
238 }