1 #include <sys/types.h>
2 #include <fcntl.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #ifdef __linux__
7 #include <sys/inotify.h>
8 #endif
9 #include "mmap.h"
10 #include "dirfd.h"
11 #include <stdio.h>
12 
13 int maxlngdelta=10;
14 
15 #if __GNUC__ < 3
16 #define __expect(foo,bar) (foo)
17 #else
18 #define __expect(foo,bar) __builtin_expect((long)(foo),bar)
19 #endif
20 #define __likely(foo) __expect((foo),1)
21 #define __unlikely(foo) __expect((foo),0)
22 
cchash(const char * key)23 size_t cchash(const char* key) {
24   size_t i,h;
25   for (i=h=0; key[i]; ++i)
26     h=((h<<5)+h)^key[i];
27   return h;
28 }
29 
30 const size_t primes[] = { 257, 521, 1031, 2053, 4099, 8209, 16411, 32771, 65537, 131101, 262147, 524309, 1048583, 2097169, 4194319 };
31 const size_t numprimes = sizeof(primes)/sizeof(primes[0])-1;
32 
33 struct hashtable dc;
34 
35 #ifdef __linux__
36 int rootwd;
37 #endif
38 
39 /* initialize a hashtable as empty */
initdircache(void)40 int initdircache(void) {
41   dc.ht=calloc(dc.slots=257,sizeof(dc.ht[0]));
42   if (!dc.ht) return -1;
43   dc.members=0;
44 #ifdef __linux__
45   ifd=inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
46   if (ifd!=-1)
47     rootwd=inotify_add_watch(ifd,".",IN_DELETE|IN_CREATE|IN_MOVED_FROM|IN_MOVED_TO);
48 #endif
49   return 0;
50 }
51 
deinitdircacheentry(struct dircacheentry * d)52 void deinitdircacheentry(struct dircacheentry* d) {
53   if (d->fd!=-1) close(d->fd);
54   if (d->htaccess_global) mmap_unmap(d->htaccess_global,d->htaccess_global_len);
55 }
56 
hashtable_lookup(const char * restrict key,size_t hashval)57 struct dircacheentry** hashtable_lookup(const char* restrict key,size_t hashval) {
58   size_t slot=hashval % dc.slots;
59   struct dircacheentry** restrict cur;
60 //  printf("hashtable_lookup(\"%s\") -> hashval %x -> slot %x\n",key,hashval,slot);
61   for (cur=&dc.ht[slot]; *cur; cur=&(*cur)->next)
62     if ((*cur)->hashval == hashval && !strcmp(key,(*cur)->dirname))
63       break;
64   return cur;
65 }
66 
dc_resize()67 void dc_resize() {
68   if (dc.slots < primes[numprimes] && dc.members > dc.slots+(dc.slots/2)) {
69     size_t i,newslots;
70     struct dircacheentry** newtab;
71     for (i=0; i<numprimes && primes[i]<dc.members; ++i) ;
72     newslots=primes[i];
73     newtab=calloc(newslots,sizeof(newtab[0]));
74     if (!newtab) return;	/* do not resize if out of memory */
75     for (i=0; i<dc.slots; ++i) {
76       struct dircacheentry* cur,* next;
77       for (cur=dc.ht[i]; cur; cur=next) {
78 	size_t newslot=cur->hashval % newslots;
79 	next=cur->next;
80 	cur->next=newtab[newslot];
81 	newtab[newslot]=cur;
82       }
83     }
84     free(dc.ht);
85     dc.ht=newtab;
86     dc.slots=newslots;
87   }
88 }
89 
getdir(const char * name,time_t now)90 struct dircacheentry* getdir(const char* name,time_t now) {
91   size_t hashval=cchash(name);
92   struct dircacheentry** hnp=hashtable_lookup(name,hashval);
93   struct dircacheentry* h;
94   struct dircacheentry* next=0;
95   if (*hnp) {
96     if (now-(*hnp)->lng>maxlngdelta) {
97       /* need to expire */
98 //      printf("expire(\"%s\")\n",(*hnp)->dirname);
99       deinitdircacheentry(*hnp);
100 #if 0
101 #ifdef __linux__
102       if ((*hnp)->inwd)
103 	inotify_rm_watch(ifd,(*hnp)->inwd);
104 #endif
105 #endif
106       next=(*hnp)->next;
107       goto expired;
108     }
109     return *hnp;
110   }
111   /* if not there, make new entry */
112   *hnp=malloc(sizeof(**hnp)+strlen(name));
113   if (!*hnp) return 0;
114 expired:
115   h=*hnp;
116   memset(h,0,sizeof(*h));
117   h->next=next;
118   strcpy(h->dirname,name);
119   h->hashval=hashval;
120   h->lng=now;
121 #ifndef O_PATH
122 #define O_PATH 0
123 #endif
124 #ifndef O_DIRECTORY
125 #define O_DIRECTORY 0
126 #endif
127 #ifndef O_CLOEXEC
128 #define O_CLOEXEC 0
129 #endif
130   h->fd=open(name,O_RDONLY|O_DIRECTORY|O_PATH|O_CLOEXEC);
131 //  printf("getdir(\"%s\") -> %d\n",name,h->fd);
132 #if 0
133 #ifdef __linux__
134   if (h->fd!=-1)
135     h->inwd=inotify_add_watch(ifd,name,IN_DELETE|IN_CREATE|IN_MOVED_FROM|IN_MOVED_TO);
136 #endif
137 #endif
138   if (dc.slots < primes[numprimes] && dc.members > dc.slots+(dc.slots/2)) {
139 //    printf("  RESIZE\n");
140     dc_resize();
141   }
142   return h;
143 }
144 
145 /* return value belonging to key or -1 if not found */
getdirfd(const char * restrict key,time_t now)146 int getdirfd(const char* restrict key,time_t now) {
147   struct dircacheentry* hn=getdir(key,now);
148   return hn?hn->fd:-1;
149 }
150 
151 /* return value belonging to key or -1 if not found */
getdirfd2(const char * restrict key,time_t now,struct dircacheentry ** x)152 int getdirfd2(const char* restrict key,time_t now,struct dircacheentry** x) {
153   struct dircacheentry* hn=getdir(key,now);
154   *x=hn;
155   return hn?hn->fd:-1;
156 }
157 
158 
159 /* traverse hash table: return first element (NULL if no elements) */
hashtable_findfirst()160 struct dircacheentry* hashtable_findfirst() {
161   size_t i;
162   for (i=0; i<dc.slots; ++i)
163     if (dc.ht[i]) return dc.ht[i];
164   return 0;
165 }
166 
167 /* traverse hash table: return next element (NULL if no more elements) */
hashtable_findnext(struct dircacheentry * restrict hn)168 struct dircacheentry* hashtable_findnext(struct dircacheentry* restrict hn) {
169   size_t slot;
170   if (hn->next) return hn->next;
171   slot=hn->hashval % dc.slots;
172   for (++slot; slot<dc.slots; ++slot)
173     if (dc.ht[slot]) return dc.ht[slot];
174   return 0;
175 }
176 
177 /* delete key+value from hash table */
178 /* calls ff on key+value first, unless NULL is passed */
179 /* return -1 if key not found, 0 if key deleted OK */
hashtable_delete(const char * restrict key)180 int hashtable_delete(const char* restrict key) {
181   struct dircacheentry** cur=hashtable_lookup(key,cchash(key));
182   struct dircacheentry* tmp;
183   if (!*cur) return -1;
184 #if 0
185 #ifdef __linux__
186   if ((*cur)->inwd>=0)
187     inotify_rm_watch(ifd,(*cur)->inwd);
188 #endif
189 #endif
190   if ((*cur)->fd!=-1)
191     deinitdircacheentry(*cur);
192   tmp=(*cur);
193   *cur=tmp->next;
194   free(tmp);
195   --dc.members;
196   return 0;
197 }
198 
199 /* free whole hash table, calling ff on each node if ff is non-NULL */
hashtable_free(void)200 void hashtable_free(void) {
201   size_t i;
202   for (i=0; i<dc.slots; ++i) {
203     struct dircacheentry* cur,* next;
204     for (cur=dc.ht[i]; cur; cur=next) {
205       next=cur->next;
206 
207 #if 0
208 #ifdef __linux__
209       if (cur->inwd>=0)
210 	inotify_rm_watch(ifd,cur->inwd);
211 #endif
212 #endif
213       if (cur->fd!=-1)
214 	deinitdircacheentry(cur);
215 
216       free(cur);
217     }
218   }
219   free(dc.ht);
220 }
221 
expiredirfd(const char * dirname)222 void expiredirfd(const char* dirname) {
223   hashtable_delete(dirname);
224 }
225 
226 #ifdef __linux__
handle_inotify_events(void)227 void handle_inotify_events(void) {
228   char buf[2048];
229   struct inotify_event* ie=(struct inotify_event*)buf;
230   ssize_t n=read(ifd,buf,sizeof(buf));
231   if (n<=0) return;
232   if (ie->wd==rootwd) {
233     /* a vhost disappeared or was added */
234 //    if (ie->mask & (IN_MOVED_FROM|IN_MOVED_TO|IN_DELETE|IN_CREATE))
235     expiredirfd(ie->name);
236   }
237 }
238 #endif
239 
240 #ifdef TEST
241 
242 #include <stdio.h>
243 #include <time.h>
244 
main()245 int main() {
246   time_t now=time(0);
247   if (initdircache()) {
248     perror("dircache init failed");
249     return 1;
250   }
251   handle_inotify_events();
252   printf("localhost:80 -> %d\n",getdirfd("localhost:80",now));
253   handle_inotify_events();
254   symlink("default","localhost:80");
255   handle_inotify_events();
256   printf("localhost:80 -> %d\n",getdirfd("localhost:80",now));
257   handle_inotify_events();
258   return 0;
259 }
260 #endif
261