Implemented file change notifications, based on a patch by Mike
[wine] / server / change.c
1 /*
2  * Server-side change notification management
3  *
4  * Copyright (C) 1998 Alexandre Julliard
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include <assert.h>
25 #include <fcntl.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <signal.h>
29
30 #include "windef.h"
31
32 #include "file.h"
33 #include "handle.h"
34 #include "thread.h"
35 #include "request.h"
36 #include "list.h"
37
38 struct change
39 {
40     struct object  obj;      /* object header */
41     struct fd     *fd;       /* file descriptor to the directory */
42     struct list    entry;    /* entry in global change notifications list */
43     int            subtree;  /* watch all the subtree */
44     unsigned int   filter;   /* notification filter */
45     long           notified; /* SIGIO counter */
46     long           signaled; /* the file changed */
47 };
48
49 static void change_dump( struct object *obj, int verbose );
50 static int change_signaled( struct object *obj, struct thread *thread );
51 static void change_destroy( struct object *obj );
52
53 static const struct object_ops change_ops =
54 {
55     sizeof(struct change),    /* size */
56     change_dump,              /* dump */
57     add_queue,                /* add_queue */
58     remove_queue,             /* remove_queue */
59     change_signaled,          /* signaled */
60     no_satisfied,             /* satisfied */
61     no_get_fd,                /* get_fd */
62     change_destroy            /* destroy */
63 };
64
65 static struct list change_list = LIST_INIT(change_list);
66
67 static void adjust_changes( int fd, unsigned int filter )
68 {
69 #ifdef F_NOTIFY
70     unsigned int val;
71     if ( 0 > fcntl( fd, F_SETSIG, SIGIO) )
72         return;
73
74     val = DN_MULTISHOT;
75     if( filter & FILE_NOTIFY_CHANGE_FILE_NAME )
76         val |= DN_RENAME | DN_DELETE | DN_CREATE;
77     if( filter & FILE_NOTIFY_CHANGE_DIR_NAME )
78         val |= DN_RENAME | DN_DELETE | DN_CREATE;
79     if( filter & FILE_NOTIFY_CHANGE_ATTRIBUTES )
80         val |= DN_ATTRIB;
81     if( filter & FILE_NOTIFY_CHANGE_SIZE )
82         val |= DN_MODIFY;
83     if( filter & FILE_NOTIFY_CHANGE_LAST_WRITE )
84         val |= DN_MODIFY;
85     if( filter & FILE_NOTIFY_CHANGE_SECURITY )
86         val |= DN_ATTRIB;
87     fcntl( fd, F_NOTIFY, val );
88 #endif
89 }
90
91 /* insert change in the global list */
92 static inline void insert_change( struct change *change )
93 {
94     sigset_t sigset;
95
96     sigemptyset( &sigset );
97     sigaddset( &sigset, SIGIO );
98     sigprocmask( SIG_BLOCK, &sigset, NULL );
99     list_add_head( &change_list, &change->entry );
100     sigprocmask( SIG_UNBLOCK, &sigset, NULL );
101 }
102
103 /* remove change from the global list */
104 static inline void remove_change( struct change *change )
105 {
106     sigset_t sigset;
107
108     sigemptyset( &sigset );
109     sigaddset( &sigset, SIGIO );
110     sigprocmask( SIG_BLOCK, &sigset, NULL );
111     list_remove( &change->entry );
112     sigprocmask( SIG_UNBLOCK, &sigset, NULL );
113 }
114
115 static struct change *create_change_notification( struct fd *fd, int subtree, unsigned int filter )
116 {
117     struct change *change;
118
119     if ((change = alloc_object( &change_ops )))
120     {
121         change->fd       = (struct fd *)grab_object( fd );
122         change->subtree  = subtree;
123         change->filter   = filter;
124         change->notified = 0;
125         change->signaled = 0;
126         insert_change( change );
127         adjust_changes( get_unix_fd(fd), filter );
128     }
129     return change;
130 }
131
132 static void change_dump( struct object *obj, int verbose )
133 {
134     struct change *change = (struct change *)obj;
135     assert( obj->ops == &change_ops );
136     fprintf( stderr, "Change notification fd=%p sub=%d filter=%08x\n",
137              change->fd, change->subtree, change->filter );
138 }
139
140 static int change_signaled( struct object *obj, struct thread *thread )
141 {
142     struct change *change = (struct change *)obj;
143
144     return change->signaled != 0;
145 }
146
147 static void change_destroy( struct object *obj )
148 {
149     struct change *change = (struct change *)obj;
150
151     release_object( change->fd );
152     remove_change( change );
153 }
154
155 /* enter here directly from SIGIO signal handler */
156 void do_change_notify( int unix_fd )
157 {
158     struct list *ptr;
159
160     /* FIXME: this is O(n) ... probably can be improved */
161     LIST_FOR_EACH( ptr, &change_list )
162     {
163         struct change *change = LIST_ENTRY( ptr, struct change, entry );
164         if (get_unix_fd( change->fd ) != unix_fd) continue;
165         interlocked_xchg_add( &change->notified, 1 );
166         break;
167     }
168 }
169
170 /* SIGIO callback, called synchronously with the poll loop */
171 void sigio_callback(void)
172 {
173     struct list *ptr;
174
175     LIST_FOR_EACH( ptr, &change_list )
176     {
177         struct change *change = LIST_ENTRY( ptr, struct change, entry );
178         long count = interlocked_xchg( &change->notified, 0 );
179         if (count)
180         {
181             change->signaled += count;
182             if (change->signaled == count)  /* was it 0? */
183                 wake_up( &change->obj, 0 );
184         }
185     }
186 }
187
188 /* create a change notification */
189 DECL_HANDLER(create_change_notification)
190 {
191     struct change *change;
192     struct file *file;
193     struct fd *fd;
194
195     if (!(file = get_file_obj( current->process, req->handle, 0 ))) return;
196     fd = get_obj_fd( (struct object *)file );
197     release_object( file );
198     if (!fd) return;
199
200     if ((change = create_change_notification( fd, req->subtree, req->filter )))
201     {
202         reply->handle = alloc_handle( current->process, change,
203                                       STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE, 0 );
204         release_object( change );
205     }
206     release_object( fd );
207 }
208
209 /* move to the next change notification */
210 DECL_HANDLER(next_change_notification)
211 {
212     struct change *change;
213
214     if ((change = (struct change *)get_handle_obj( current->process, req->handle,
215                                                    0, &change_ops )))
216     {
217         if (change->signaled > 0) change->signaled--;
218         release_object( change );
219     }
220 }