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