1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <errno.h>
  4 #include <fcntl.h>
  5 #include <assert.h>
  6 #include "rhttpd.h"
  7 #include "sys/inotify.h"
  8 #include "rh_string.h"
  9 #include "rh_stat.h"
 10 #include "rh_event.h"
 11         
 12 #ifndef RH_STAT_WD_MAX
 13 # define RH_STAT_WD_MAX (128)
 14 #endif
 15 
 16 /*
 17  * since inotify() always increment the watch descriptors (wd)
 18  * its needed to resize the wd vector to find which rh_stat()
 19  * was inotified.
 20  *
 21  * resize up to a infinite size makes no sense ;-)
 22  *
 23  * this structure holds at least RH_STAT_WD_MAX wd's.
 24  * 
 25  * if it becomes full a new struct, with a new inotify fd
 26  * (wds started at 0), is allocated.
 27  *
 28  * if it becomes unused and if its not the current cache's inotify
 29  * its destroyed.
 30  *
 31  */
 32 struct rh_stat_inotify {
 33         rh_stat_cache_t *cache;                 /* the parent */
 34 
 35         int             fd;                     /* inotify_init () */
 36         rh_event_t      ev;                     /* event for fd */
 37         
 38         size_t          reference;              /* reference count.
 39                                                    if goes to zero,
 40                                                    structure is free'd */
 41 
 42         size_t          wd_used;                /* used entries in wd[] */
 43         rh_stat_t       *wd[RH_STAT_WD_MAX];
 44 };
 45 
 46 static struct rh_stat_inotify * rh_stat_inotify_init (rh_event_base_t *evbase);
 47 static void   rh_stat_inotify_add (rh_stat_t *st);
 48 static void   rh_stat_inotify_del (rh_stat_t *st);
 49 static void   rh_stat_inotify_read (int fd, short rh_events, void *arg);
 50 
 51 
 52 static int rh_stat_cache_grow (rh_stat_cache_t *cache);
 53 static void rh_stat_entry_free (rh_stat_t *st, struct rh_stat_cache_list *list);
 54 
 55 static void rh_stat_force (rh_stat_t *st);
 56 
 57 #define IS_INOTIFIED(_st)       ( (_st)->wd > -1 )
 58 
 59 #define RH_STAT_CACHE_INOTIFY_EVENTS    \
 60         ( IN_ALL_EVENTS & ~(IN_ACCESS | IN_CLOSE | IN_OPEN) )
 61 
 62 int rh_stat_cache_init (rh_stat_cache_t *cache, rh_event_base_t *ev_base,
 63                         mimetype_base_t *mimebase)
 64 {
 65         TYPE_ZERO(cache);
 66 
 67         TAILQ_INIT (&cache->timeout);
 68         
 69         cache->mimebase = mimebase;
 70         cache->ev_base = ev_base;
 71 
 72         if (-1 == rh_stat_cache_grow (cache))
 73                 return -1;
 74                 
 75         return 0;
 76 
 77 }
 78 
 79 void rh_stat_cache_destroy (rh_stat_cache_t *cache)
 80 {
 81         if (cache->buckets) {
 82                 size_t  i;
 83                 
 84                 for (i=0; i<cache->nbuckets; ++i) {
 85                         struct rh_stat_cache_list       *list_src;
 86                         rh_stat_t                       *st;
 87                 
 88                         list_src = &(cache->buckets[i]);
 89 
 90                         while ( (st = TAILQ_FIRST(list_src)) ) {
 91                                 rh_stat_entry_free (st, list_src);
 92                         }
 93                 }
 94 
 95                 free (cache->buckets);
 96         }
 97         
 98         if (cache->inotify && ! cache->inotify->reference) {
 99                 close (cache->inotify->fd);
100                 free (cache->inotify);
101         }
102 
103 
104         TYPE_ZERO(cache);
105 }
106 
107 /*
108  *
109  * removes all timeout'd entries
110  *
111  *
112  */
113 static void rh_stat_cache_cleanup (rh_stat_cache_t *cache)
114 {
115         time_t  atime;
116 
117         atime = cache->ev_base->time;
118 
119 #ifdef RHTTPD_STAT_CLEANUP_INTERVAL
120         if (atime - cache->last_cleanup < RHTTPD_STAT_CLEANUP_INTERVAL)
121                 return;
122         
123         cache->last_cleanup = atime;
124 #endif
125 
126 #if 0
127         printf ("%s(%p): time(%lu)\n", __FUNCTION__, (void*)cache, atime);
128 #endif
129 
130         for (;;) {
131                 struct rh_stat_cache_list       *list;
132                 rh_stat_t                       *st;
133                 
134                 st = TAILQ_FIRST(&cache->timeout);
135 
136                 if (NULL == st)
137                         break;
138 
139                 if (atime - st->atime < RHTTPD_STAT_CACHE_FREE_INTERVAL)
140                         break;
141 
142                 list = &cache->buckets[st->path.hash % cache->nbuckets];
143 #if 0
144                 printf ("%s(): timeout: st(%p) list(%p) filename(%.*s)\n",
145                                 __FUNCTION__,
146                                 (void*)st,
147                                 (void*)list,
148                                 st->path.used, st->path.data );
149 #endif
150 
151                 rh_stat_entry_free (st, list);
152         }
153 }
154 
155 
156 static int rh_stat_cache_grow (rh_stat_cache_t *cache)
157 {
158         struct rh_stat_cache_list       *buckets;
159         size_t                          nbuckets;
160         size_t                          i;
161 
162         if (0 == cache->nbuckets) {
163                 nbuckets = 15;
164         } else {
165                 /* ~ 5 elements per bucket */
166                 if ( ( (1+cache->elements) / cache->nbuckets) < 5)
167                         return 0;
168 
169                 nbuckets = cache->nbuckets * 2;
170         }
171 
172         buckets = malloc (nbuckets * sizeof(*buckets));
173         if (NULL == buckets)
174                 return -1;
175 
176         /* init new buckets */
177         for (i=0; i<nbuckets; ++i)
178                 TAILQ_INIT(&(buckets[i]));
179         
180         /* rehash old buckets */
181         for (i=0; i<cache->nbuckets; ++i) {
182                 struct rh_stat_cache_list       *list_src, *list_dst;
183                 rh_stat_t                       *st;
184                 
185                 list_src = &(cache->buckets[i]);
186 
187                 while ( (st = TAILQ_FIRST(list_src)) ) {
188                         TAILQ_REMOVE(list_src, st, hash);
189 
190                         list_dst = &(buckets[st->path.hash % nbuckets]);
191 
192                         TAILQ_INSERT_TAIL(list_dst, st, hash);
193                 }
194         }
195 
196         /* set new buckets */
197         if (cache->buckets)
198                 free (cache->buckets);
199         
200         cache->buckets = buckets;
201         cache->nbuckets = nbuckets;
202 
203         return 0;
204 }
205 
206 
207 static void rh_stat_entry_free (rh_stat_t *st, struct rh_stat_cache_list *list)
208 {
209         if (NULL == st)
210                 return;
211 
212         if (!st->removed) {     
213                 if (list)
214                         TAILQ_REMOVE(list, st, hash);
215 
216                 if (0 == st->used)
217                         TAILQ_REMOVE(&st->cache->timeout, st, timeout);
218         }
219         
220         st->removed = 1;
221 
222         if (st->used)
223                 --st->used;
224         
225         if (st->used)
226                 return;
227         
228         rh_stat_inotify_del (st);
229         
230         mimetype_push (&st->mime);
231         
232         rh_buffer_destroy(&st->path);
233         
234         TYPE_ZERO(st);
235         
236         free (st);
237 }
238 
239 /*
240  *
241  * stat() the entry and add/del to/from fd_inotify
242  *
243  * if stat failed
244  *      if inotified
245  *              inotify_remove
246  * else
247  *      if not inotified
248  *              inotify_add
249  * 
250  */
251 static void rh_stat_force (rh_stat_t *st)
252 {
253 #if 0
254         printf ("%s(%p): file(%.*s)\n",
255                         __FUNCTION__, (void*)st,
256                         st->path.used, st->path.data );
257 #endif
258         if (!st->mime.initialized && st->cache->mimebase) {
259                 mimetype_pop (  st->cache->mimebase,
260                                 &st->mime,
261                                 st->path.data, st->path.used );
262         }
263         
264         st->mtime = st->cache->ev_base->time;
265         
266         if (-1 == stat (st->path.data, &st->stbuf)) {
267                 st->error = errno;
268 
269                 rh_stat_inotify_del (st);
270         } else {
271                 st->error = 0;
272                 
273                 rh_stat_inotify_add (st);
274         
275                 rh_gmtime_update (&st->gmtime, st->stbuf.st_mtime);
276         }
277 }
278 
279 rh_stat_t * rh_stat_pop (rh_stat_cache_t *cache, rh_buffer_t *filename)
280 {
281         rh_stat_t                       *st;
282         size_t                          hash;
283         struct rh_stat_cache_list       *list;
284         time_t                          atime;  /* current time from ev_base */
285 
286         if (NULL == filename || 0 == filename->used)
287                 return NULL;
288 
289         if (0 != rh_stat_cache_grow (cache))
290                 return NULL;
291                         
292         atime = cache->ev_base->time;
293 
294 #if 0
295         printf ("%s() filename[%.*s]\n", __FUNCTION__,
296                         filename->used, filename->data );
297 #endif
298 
299         /* DONT use filename->hash here, it may not correct */
300         hash = rh_buffer_hashcase(filename);
301 
302         /* list from hash bucket */
303         list = &(cache->buckets[hash % cache->nbuckets]);
304 
305         /* find entry in list */
306         TAILQ_FOREACH(st, list, hash) {
307                 if (st->path.used != filename->used)
308                         continue;
309 
310                 if (rh_memcmp (st->path.data, filename->data, filename->used))
311                         continue;
312         
313                 st->atime = atime;
314 
315                 /* not inotify'd ?
316                  * stat() if timeout is elapsed */
317                 if (!IS_INOTIFIED(st) && (st->atime - st->mtime) > 
318                                                 RHTTPD_STAT_CACHE_TIMEOUT)
319                 {
320                         rh_stat_force (st);
321                 }
322 
323                 if (1 == ++st->used)
324                         TAILQ_REMOVE(&cache->timeout, st, timeout);
325                 
326                 return st;
327         }
328 
329         st = calloc (1, sizeof(*st));
330         if (NULL == st)
331                 return NULL;
332         
333         st->wd = -1;
334         st->cache = cache;
335         
336         rh_gmtime_init (&st->gmtime, atime);
337         
338         TAILQ_INSERT_HEAD(list, st, hash);
339         
340         if (0 != rh_buffer_reserve(&st->path, filename->used + 1))
341                 goto error_out;
342 
343         if (0 != rh_buffer_append (&st->path, filename->data, filename->used))
344                 goto error_out;
345         
346         st->path.data[st->path.used] = 0;
347         
348         st->path.hash = hash;
349         
350         st->atime = st->cache->ev_base->time;
351         ++cache->elements;
352         st->used = 1;
353         
354         rh_stat_force (st);
355         
356         return st;
357 
358 error_out:
359         rh_stat_entry_free (st, list);
360         
361         return NULL;
362 }
363 
364 void rh_stat_push (rh_stat_t *st)
365 {
366         rh_stat_cache_t *cache;
367 
368         if (st->removed) {
369                 rh_stat_entry_free (st, NULL);
370                 return;
371         }
372         
373         cache = st->cache;
374         
375         st->atime = cache->ev_base->time;
376 
377         rh_stat_cache_cleanup (st->cache);
378         
379         if (st->used)
380                 --st->used;
381         
382         if (0 == st->used)
383                 TAILQ_INSERT_TAIL(&cache->timeout, st, timeout);
384 
385         return;
386 }
387 
388 
389 
390 /*
391  *
392  *
393  * inotify support
394  *
395  *
396  *
397  *
398  */
399 
400 /*
401  *
402  * allocates a inotify structure
403  * sets up event handling
404  *
405  * returns pointer to initialized struct
406  */
407 static struct rh_stat_inotify * rh_stat_inotify_init (rh_event_base_t *ev_base)
408 {
409         struct rh_stat_inotify  *st_inotify;
410         int                     fd_flags;
411         
412         st_inotify = calloc (1, sizeof(*st_inotify));
413         if (NULL == st_inotify)
414                 return NULL;
415 
416         st_inotify->fd = inotify_init ();
417         if (-1 == st_inotify->fd)
418                 goto error_out;
419         
420         fd_flags = fcntl (st_inotify->fd, F_GETFL);
421         if (-1 == fd_flags)
422                 goto error_out;
423                 
424         if (-1 == fcntl (st_inotify->fd, F_SETFL, fd_flags | O_NONBLOCK))
425                 goto error_out;
426 
427         rh_event_set (  &(st_inotify->ev),
428                         ev_base,
429                         st_inotify->fd,
430                         RH_EVENT_READ,
431                         rh_stat_inotify_read,
432                         st_inotify );
433 
434         if (-1 == rh_event_add (&(st_inotify->ev)))
435                 goto error_out;
436 
437 #if 0
438         printf ("%s(): inotify(%p) fd(%d)\n", __FUNCTION__,
439                                 (void*)st_inotify,
440                                 st_inotify->fd);
441 #endif
442         
443         return st_inotify;
444 
445 error_out:
446         if (st_inotify) {
447                 if (st_inotify->fd > -1) {
448                         rh_event_del (&(st_inotify->ev));
449                         close (st_inotify->fd);
450                 }
451                 free (st_inotify);
452         }
453         return NULL;
454 }
455 
456 /*
457  *
458  * delete a watch descriptor from st if not allready deleted.
459  * 
460  * if st->inotify is no more referenced (st->reference < 1) its free'd
461  *
462  */
463 static void rh_stat_inotify_del (rh_stat_t *st)
464 {
465         struct rh_stat_inotify  *st_inotify;
466         
467         if (NULL == st || st->used || NULL == st->inotify || st->wd < 0)
468                 return;
469 
470 #if 0
471         printf ("%s(): st(%p:%d) inotify(%p:%d/%d)\n", __FUNCTION__, (void*)st,
472                         st->wd, (void*)st->inotify,
473                         st->inotify->wd_used,
474                         RH_STAT_WD_MAX);
475 #endif
476 
477         st_inotify = st->inotify;
478         
479         st->inotify->wd[st->wd] = NULL;
480         inotify_rm_watch (st_inotify->fd, st->wd);
481 
482         st->inotify = NULL;
483         st->wd = -1;
484 
485         st = NULL;
486         
487         if (st_inotify->reference > 1) {
488                 /* its needed by some other stat()'s */
489                 --st_inotify->reference;
490                 return;
491         }
492         
493         if (st_inotify->cache->inotify == st_inotify &&
494                         st_inotify->wd_used < RH_STAT_WD_MAX)
495         {
496                 /* there are wd's left and its the inotify of the cache */
497                 st_inotify->reference = 0;
498                 return;
499         }
500                 
501 #if 1
502         printf ("%s(): free(%p) fd(%d) ref(%d) wd_used(%d)\n", __FUNCTION__,
503                         (void*)st_inotify,
504                         st_inotify->fd,
505                         st_inotify->reference,
506                         st_inotify->wd_used);
507 #endif
508 
509         if (st_inotify->cache->inotify == st_inotify)
510                 st_inotify->cache->inotify = NULL;
511 
512         close (st_inotify->fd);
513         free (st_inotify);
514 }
515 
516 /*
517  *
518  * add a watch descriptor to a st if it istn allready watched
519  *
520  * if st->cache has no (or no usable) inotify ptr its allocated.
521  *
522  * 
523  */
524 static void rh_stat_inotify_add (rh_stat_t *st)
525 {
526         if (NULL == st || st->wd > -1 || NULL == st->cache)
527                 return;
528         
529         if (    st->cache->inotify &&
530                 st->cache->inotify->wd_used > RH_STAT_WD_MAX - 2)
531         {
532                 st->cache->inotify = NULL;
533         }
534 
535         if (NULL == st->cache->inotify) {
536                 st->cache->inotify = rh_stat_inotify_init (st->cache->ev_base);
537                 
538                 if (NULL == st->cache->inotify)
539                         return;
540                 
541                 st->cache->inotify->cache = st->cache;
542         }
543         
544         st->inotify = st->cache->inotify;
545 
546         /*
547          * this seams to be true: new_wd == old_wd + 1
548          *
549          */
550         
551         st->wd = inotify_add_watch (    st->inotify->fd,
552                                         st->path.data,
553                                         RH_STAT_CACHE_INOTIFY_EVENTS);
554 
555         if (-1 == st->wd)
556                 return;
557         
558         /*
559          *
560          * this may happen:
561          *      /directory
562          *      /directory/
563          *
564          * the strings are not equal but the wd
565          *
566          */
567         if (st->inotify->wd[st->wd]) {
568                 assert (st->wd == st->inotify->wd[st->wd]->wd);
569                 ++st->used;
570         } else {
571                 st->inotify->wd_used++;
572                 st->inotify->wd[st->wd] = st;
573         }
574         
575         ++st->inotify->reference;
576 }
577 
578 
579 /*
580  *
581  * poll'd if something happen on the inotify fd.
582  *
583  * calls stat_force()
584  *
585  */
586 
587 static void rh_stat_inotify_read (int fd, short rh_events, void *arg)
588 {
589         struct rh_stat_inotify  *st_inotify = arg;
590         struct inotify_event    *ino_event;
591         ssize_t                 bytes;
592         char                    *ptr;
593         
594         bytes = read (  fd,
595                         st_inotify->cache->buffer.data,
596                         sizeof(st_inotify->cache->buffer.data));
597 
598 #if 1
599         printf ("%s(%3d, %hd, %p): read %zd bytes\n",
600                         __FUNCTION__, fd, rh_events, arg,
601                         bytes);
602 #endif
603         
604         for (ptr = st_inotify->cache->buffer.data; bytes > 0; ) {
605                 rh_stat_t       *st;
606                 size_t          len;
607                 
608                 ino_event = (void*)ptr;
609                 
610                 len = sizeof(*ino_event) + ino_event->len;
611                 bytes -= len;
612                 ptr   += len;
613 #if 0
614                 printf ("%s(): wd(%3d) mask(%08x) cookie(%08x) len(%3d)\n",
615                                 __FUNCTION__,
616                                 ino_event->wd, ino_event->mask,
617                                 ino_event->cookie, ino_event->len );
618 #endif
619 
620                 /* 
621                  * inotify() reports "deleted self".
622                  * 
623                  * this is nice but the removal was done some time before;
624                  * so just ignore if there are NULL wd's in wd[]
625                  *
626                  */
627                 st = st_inotify->wd[ino_event->wd];
628                 if (NULL == st)
629                         continue;
630 
631                 printf ("%s(): st(%p) wd(%d==%d) stfile(%.*s)\n",
632                         __FUNCTION__,
633                         (void*)st,
634                         st->wd, ino_event->wd,
635                         st->path.used, st->path.data );
636 
637                 assert (st->wd == ino_event->wd);
638 
639                 rh_stat_force (st);
640         }
641 }
642 
643 
644 
645 
646 
647 
648 
649 
650 
651 
652 
653 
654 
655 
656 
657 
658 
659 
660 
661 
662 /*
663  *
664  * inotify syscalls if glibc < 2.4
665  * 
666  */
667 
668 #ifndef __NR_inotify_init
669 # define __NR_inotify_init      291
670   int inotify_init (void)
671   {
672           return syscall (__NR_inotify_init);
673   }
674 #endif
675 
676 #ifndef __NR_inotify_add_watch
677 # define __NR_inotify_add_watch 292
678   int inotify_add_watch (int __fd, const char *__name, uint32_t __mask)
679   {
680           return syscall (__NR_inotify_add_watch, __fd, __name, __mask);
681   }
682 #endif
683 
684 
685 #ifndef __NR_inotify_rm_watch
686 # define __NR_inotify_rm_watch  293
687   int inotify_rm_watch (int __fd, uint32_t __wd)
688   {
689           return syscall (__NR_inotify_rm_watch, __fd, __wd);
690   }
691 #endif


syntax highlighted by Code2HTML, v. 0.9.1