Merge branch 'master' of git://git.openpandora.org/pandora-libraries
[pandora-libraries.git] / lib / pnd_notify.c
1
2 #include <sys/inotify.h>    // for INOTIFY duh
3 #include <stdio.h>          // for stdio, NULL
4 #include <stdlib.h>         // for malloc, etc
5 #include <unistd.h>         // for close
6 #include <time.h>           // for time()
7
8 #define _XOPEN_SOURCE 500
9 #define __USE_XOPEN_EXTENDED
10 #include <ftw.h> /* for nftw, tree walker */
11
12 #include "pnd_notify.h"
13 #include "pnd_pathiter.h"
14 #include "pnd_logger.h"
15
16 typedef struct {
17   int fd;              // notify API file descriptor
18 } pnd_notify_t;
19
20 static int notify_handle;
21
22 //static void pnd_notify_hookup ( int fd );
23
24 #if 1
25 #define PND_INOTIFY_MASK     IN_CREATE | IN_DELETE | IN_UNMOUNT \
26                              | IN_DELETE_SELF | IN_MOVE_SELF    \
27                              | IN_MOVED_FROM | IN_MOVED_TO | IN_MODIFY
28 #else
29 #define PND_INOTIFY_MASK     IN_ALL_EVENTS
30 #endif
31
32 pnd_notify_handle pnd_notify_init ( void ) {
33   int fd;
34   pnd_notify_t *p;
35
36   fd = inotify_init();
37
38   if ( fd < 0 ) {
39     return ( NULL );
40   }
41
42   p = malloc ( sizeof(pnd_notify_t) );
43
44   if ( ! p ) {
45     close ( fd );
46     return ( NULL ); // uhh..
47   }
48
49   p -> fd = fd;
50
51   // setup some default watches
52   //pnd_notify_hookup ( fd );
53
54   return ( p );
55 }
56
57 void pnd_notify_shutdown ( pnd_notify_handle h ) {
58   pnd_notify_t *p = (pnd_notify_t*) h;
59
60   close ( p -> fd );
61
62   free ( p );
63
64   return;
65 }
66
67 static int pnd_notify_callback ( const char *fpath, const struct stat *sb,
68                                  int typeflag, struct FTW *ftwbuf )
69 {
70
71   // only include directories
72   if ( ! ( typeflag & FTW_D ) ) {
73     return ( 0 ); // continue the tree walk
74   }
75
76   //printf ( "Implicitly watching dir '%s'\n", fpath );
77
78   inotify_add_watch ( notify_handle, fpath, PND_INOTIFY_MASK );
79
80   if ( pnd_log_do_buried_logging() ) {
81     pnd_log ( PND_LOG_DEFAULT, "notify callback: added watch on %s\n", fpath );
82   }
83
84   return ( 0 ); // continue the tree walk
85 }
86
87 void pnd_notify_watch_path ( pnd_notify_handle h, char *fullpath, unsigned int flags ) {
88   pnd_notify_t *p = (pnd_notify_t*) h;
89
90   inotify_add_watch ( p -> fd, fullpath, PND_INOTIFY_MASK );
91
92   if ( flags & PND_NOTIFY_RECURSE ) {
93
94     notify_handle = p -> fd;
95
96     nftw ( fullpath,             // path to descend
97            pnd_notify_callback,  // callback to do processing
98            10,                   // no more than X open fd's at once
99            FTW_PHYS );           // do not follow symlinks
100
101     notify_handle = -1;
102
103   } // recurse
104
105   return;
106 }
107
108 #if 0
109 static void pnd_notify_hookup ( int fd ) {
110
111   inotify_add_watch ( fd, "./testdata", IN_CREATE | IN_DELETE | IN_UNMOUNT );
112   inotify_add_watch ( fd, "/media", IN_CREATE | IN_DELETE | IN_UNMOUNT );
113
114   return;
115 }
116 #endif
117
118 unsigned char pnd_notify_rediscover_p ( pnd_notify_handle h ) {
119   pnd_notify_t *p = (pnd_notify_t*) h;
120
121   struct timeval t;
122   fd_set rfds;
123   int retcode;
124
125   // don't block for long..
126   //t.tv_sec = 1;
127   //t.tv_usec = 0; //5000;
128   t.tv_sec = 0;
129   t.tv_usec = 5000;
130
131   // only for our useful fd
132   FD_ZERO ( &rfds );
133   FD_SET ( (p->fd), &rfds );
134
135   // wait and test
136   retcode = select ( (p->fd) + 1, &rfds, NULL, NULL, &t );
137
138   if ( retcode < 0 ) {
139     return ( 0 ); // hmm.. need a better error code handler
140   } else if ( retcode == 0 ) {
141     return ( 0 ); // timeout
142   }
143
144   if ( ! FD_ISSET ( (p->fd), &rfds ) ) {
145     return ( 0 );
146   }
147
148   // by this point, something must have happened on our watch
149 #define BINBUFLEN ( 100 * ( sizeof(struct inotify_event) + 30 ) ) /* approx 100 events in a shot? */
150   unsigned char binbuf [ BINBUFLEN ];
151   int actuallen;
152
153   actuallen = read ( (p->fd), binbuf, BINBUFLEN );
154
155   if ( actuallen < 0 ) {
156     return ( 0 ); // error
157   } else if ( actuallen == 0 ) {
158     return ( 0 ); // nothing, or overflow, or .. whatever.
159   }
160
161   unsigned int i = 0;
162   struct inotify_event *e;
163
164   while ( i < actuallen ) {
165     e = (struct inotify_event *) &binbuf [ i ];
166
167     /* do it!
168      */
169
170     if ( pnd_log_do_buried_logging() ) {
171       pnd_log ( PND_LOG_DEFAULT, "notify: Got event against '%s' [%u %x]\n", e -> name, e -> mask, e -> mask );
172     }
173
174     /* do it!
175      */
176
177     // next
178     i += ( sizeof(struct inotify_event) + e -> len );
179   } // while
180
181   return ( 1 );
182 }
183
184 #define _INOTIFY_TEST_PATH "/tmp/.notifytest"
185 #define _INOTIFY_TEST_FILE "/tmp/.notifytest/foopanda"
186 static unsigned char _inotify_test_run ( void ) {
187
188   // set up inotify
189   pnd_notify_t fdt;
190   int wd; // watch-descriptor
191
192   // set up inotify
193   fdt.fd = inotify_init();
194
195   if ( fdt.fd < 0 ) {
196     return ( 0 ); // failed to init at all
197   }
198
199   // make a temp dir
200   mkdir ( _INOTIFY_TEST_PATH, 0777 ); // if it fails we assume it exists, which is fine
201
202   // watch the dir
203   if ( ( wd = inotify_add_watch ( fdt.fd, _INOTIFY_TEST_PATH, IN_DELETE ) ) < 0 ) {
204     return ( 0 ); // couldn't watch dir
205   }
206
207   // sleep a sec, just to be safe; seems to dislike being called immediately sometimes
208   usleep ( 1000000 / 2 );
209
210   // create a temp file
211   FILE *f;
212   if ( ! ( f = fopen ( _INOTIFY_TEST_FILE, "w" ) ) ) {
213     close ( fdt.fd );
214     return ( 0 ); // couldn't create test file?!
215   }
216
217   fclose ( f );
218
219   // delete the temp file; this should trigger an event
220   if ( unlink ( _INOTIFY_TEST_FILE ) < 0 ) {
221     return ( 0 ); // couldn't rm a file? life is harsh.
222   }
223
224   // did we get anything?
225   unsigned char s;
226   s = pnd_notify_rediscover_p ( &fdt );
227
228   // ditch inotify
229   close ( fdt.fd );
230
231   // success
232   return ( s );
233 }
234
235 /* we've run into the issue where inotify returns that it is set up, but in
236  * fact is not doing anything; restarting the process repairs it.. so here
237  * we devise a wank that continually tests inotify until it responds, then
238  * returns knowing we're good
239  */
240 unsigned char pnd_notify_wait_until_ready ( unsigned int secs_timeout ) {
241   time_t start = time ( NULL );
242
243   while ( ( secs_timeout == 0 ) ||
244           ( time ( NULL ) - start < secs_timeout ) )
245   {
246     if ( _inotify_test_run() ) {
247       return ( 1 );
248     }
249     usleep ( 1000000 / 2 );
250   }
251
252   return ( 0 ); // fail
253 }