↑ 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