1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <plumb.h>
5 #include <regexp.h>
6 #include <bio.h>
7 #include <9pclient.h>
8 #include "faces.h"
9 
10 enum	/* number of deleted faces to cache */
11 {
12 	Nsave	= 20
13 };
14 
15 static Facefile	*facefiles;
16 static int		nsaved;
17 static char	*facedom;
18 static char	*libface;
19 static char	*homeface;
20 
21 /*
22  * Loading the files is slow enough on a dial-up line to be worth this trouble
23  */
24 typedef struct Readcache	Readcache;
25 struct Readcache {
26 	char *file;
27 	char *data;
28 	long mtime;
29 	long rdtime;
30 	Readcache *next;
31 };
32 
33 static Readcache *rcache;
34 
35 ulong
dirlen(char * s)36 dirlen(char *s)
37 {
38 	Dir *d;
39 	ulong len;
40 
41 	d = dirstat(s);
42 	if(d == nil)
43 		return 0;
44 	len = d->length;
45 	free(d);
46 	return len;
47 }
48 
49 ulong
fsdirlen(CFsys * fs,char * s)50 fsdirlen(CFsys *fs,char *s)
51 {
52 	Dir *d;
53 	ulong len;
54 
55 	d = fsdirstat(fs,s);
56 	if(d == nil)
57 		return 0;
58 	len = d->length;
59 	free(d);
60 	return len;
61 }
62 
63 ulong
dirmtime(char * s)64 dirmtime(char *s)
65 {
66 	Dir *d;
67 	ulong t;
68 
69 	d = dirstat(s);
70 	if(d == nil)
71 		return 0;
72 	t = d->mtime;
73 	free(d);
74 	return t;
75 }
76 
77 static char*
doreadfile(char * s)78 doreadfile(char *s)
79 {
80 	char *p;
81 	int fd, n;
82 	ulong len;
83 
84 	len = dirlen(s);
85 	if(len == 0)
86 		return nil;
87 
88 	p = malloc(len+1);
89 	if(p == nil)
90 		return nil;
91 
92 	if((fd = open(s, OREAD)) < 0
93 	|| (n = readn(fd, p, len)) < 0) {
94 		close(fd);
95 		free(p);
96 		return nil;
97 	}
98 
99 	p[n] = '\0';
100 	return p;
101 }
102 
103 static char*
readfile(char * s)104 readfile(char *s)
105 {
106 	Readcache *r, **l;
107 	char *p;
108 	ulong mtime;
109 
110 	for(l=&rcache, r=*l; r; l=&r->next, r=*l) {
111 		if(strcmp(r->file, s) != 0)
112 			continue;
113 
114 		/*
115 		 * if it's less than 30 seconds since we read it, or it
116 		 * hasn't changed, send back our copy
117 		 */
118 		if(time(0) - r->rdtime < 30)
119 			return strdup(r->data);
120 		if(dirmtime(s) == r->mtime) {
121 			r->rdtime = time(0);
122 			return strdup(r->data);
123 		}
124 
125 		/* out of date, remove this and fall out of loop */
126 		*l = r->next;
127 		free(r->file);
128 		free(r->data);
129 		free(r);
130 		break;
131 	}
132 
133 	/* add to cache */
134 	mtime = dirmtime(s);
135 	if(mtime == 0)
136 		return nil;
137 
138 	if((p = doreadfile(s)) == nil)
139 		return nil;
140 
141 	r = malloc(sizeof(*r));
142 	if(r == nil)
143 		return nil;
144 	r->mtime = mtime;
145 	r->file = estrdup(s);
146 	r->data = p;
147 	r->rdtime = time(0);
148 	r->next = rcache;
149 	rcache = r;
150 	return strdup(r->data);
151 }
152 
153 static char*
translatedomain(char * dom,char * list)154 translatedomain(char *dom, char *list)
155 {
156 	static char buf[200];
157 	char *p, *ep, *q, *nextp, *file;
158 	char *bbuf, *ebuf;
159 	Reprog *exp;
160 
161 	if(dom == nil || *dom == 0)
162 		return nil;
163 
164 	if(list == nil || (file = readfile(list)) == nil)
165 		return dom;
166 
167 	for(p=file; p; p=nextp) {
168 		if(nextp = strchr(p, '\n'))
169 			*nextp++ = '\0';
170 
171 		if(*p == '#' || (q = strpbrk(p, " \t")) == nil || q-p > sizeof(buf)-2)
172 			continue;
173 
174 		bbuf = buf+1;
175 		ebuf = buf+(1+(q-p));
176 		strncpy(bbuf, p, ebuf-bbuf);
177 		*ebuf = 0;
178 		if(*bbuf != '^')
179 			*--bbuf = '^';
180 		if(ebuf[-1] != '$') {
181 			*ebuf++ = '$';
182 			*ebuf = 0;
183 		}
184 
185 		if((exp = regcomp(bbuf)) == nil){
186 			fprint(2, "bad regexp in machinelist: %s\n", bbuf);
187 			killall("regexp");
188 		}
189 
190 		if(regexec(exp, dom, 0, 0)){
191 			free(exp);
192 			ep = p+strlen(p);
193 			q += strspn(q, " \t");
194 			if(ep-q+2 > sizeof buf) {
195 				fprint(2, "huge replacement in machinelist: %.*s\n", utfnlen(q, ep-q), q);
196 				exits("bad big replacement");
197 			}
198 			strncpy(buf, q, ep-q);
199 			ebuf = buf+(ep-q);
200 			*ebuf = 0;
201 			while(ebuf > buf && (ebuf[-1] == ' ' || ebuf[-1] == '\t'))
202 				*--ebuf = 0;
203 			free(file);
204 			return buf;
205 		}
206 		free(exp);
207 	}
208 	free(file);
209 
210 	return dom;
211 }
212 
213 static char*
tryfindpicture(char * dom,char * user,char * dir,char * dict)214 tryfindpicture(char *dom, char *user, char *dir, char *dict)
215 {
216 	static char buf[1024];
217 	char *file, *p, *nextp, *q;
218 
219 	if((file = readfile(dict)) == nil)
220 		return nil;
221 
222 	snprint(buf, sizeof buf, "%s/%s", dom, user);
223 
224 	for(p=file; p; p=nextp){
225 		if(nextp = strchr(p, '\n'))
226 			*nextp++ = '\0';
227 
228 		if(*p == '#' || (q = strpbrk(p, " \t")) == nil)
229 			continue;
230 		*q++ = 0;
231 
232 		if(strcmp(buf, p) == 0){
233 			q += strspn(q, " \t");
234 			snprint(buf, sizeof buf, "%s/%s", dir, q);
235 			q = buf+strlen(buf);
236 			while(q > buf && (q[-1] == ' ' || q[-1] == '\t'))
237 				*--q = 0;
238 			free(file);
239 			return estrdup(buf);
240 		}
241 	}
242 	free(file);
243 	return nil;
244 }
245 
246 static char*
estrstrdup(char * a,char * b)247 estrstrdup(char *a, char *b)
248 {
249 	char *t;
250 
251 	t = emalloc(strlen(a)+strlen(b)+1);
252 	strcpy(t, a);
253 	strcat(t, b);
254 	return t;
255 }
256 
257 static char*
tryfindfiledir(char * dom,char * user,char * dir)258 tryfindfiledir(char *dom, char *user, char *dir)
259 {
260 	char *dict, *ndir, *x, *odom;
261 	int fd;
262 	int i, n;
263 	Dir *d;
264 
265 	/*
266 	 * If this directory has a .machinelist, use it.
267 	 */
268 	x = estrstrdup(dir, "/.machinelist");
269 	dom = estrdup(translatedomain(dom, x));
270 	free(x);
271 	/*
272 	 * If this directory has a .dict, use it.
273 	 */
274 	dict = estrstrdup(dir, "/.dict");
275 	if(access(dict, AEXIST) >= 0){
276 		x = tryfindpicture(dom, user, dir, dict);
277 		free(dict);
278 		free(dom);
279 		return x;
280 	}
281 	free(dict);
282 
283 	/*
284 	 * If not, recurse into subdirectories.
285 	 * Ignore 48x48xN directories for now.
286 	 */
287 	if((fd = open(dir, OREAD)) < 0)
288 		return nil;
289 	while((n = dirread(fd, &d)) > 0){
290 		for(i=0; i<n; i++){
291 			if((d[i].mode&DMDIR)&& strncmp(d[i].name, "48x48x", 6) != 0){
292 				ndir = emalloc(strlen(dir)+1+strlen(d[i].name)+1);
293 				strcpy(ndir, dir);
294 				strcat(ndir, "/");
295 				strcat(ndir, d[i].name);
296 				if((x = tryfindfiledir(dom, user, ndir)) != nil){
297 					free(ndir);
298 					free(d);
299 					close(fd);
300 					free(dom);
301 					return x;
302 				}
303 			}
304 		}
305 		free(d);
306 	}
307 	close(fd);
308 
309 	/*
310 	 * Handle 48x48xN directories in the right order.
311 	 */
312 	ndir = estrstrdup(dir, "/48x48x8");
313 	for(i=8; i>0; i>>=1){
314 		ndir[strlen(ndir)-1] = i+'0';
315 		if(access(ndir, AEXIST) >= 0 && (x = tryfindfiledir(dom, user, ndir)) != nil){
316 			free(ndir);
317 			free(dom);
318 			return x;
319 		}
320 	}
321 	free(ndir);
322 	free(dom);
323 	return nil;
324 }
325 
326 static char*
tryfindfile(char * dom,char * user)327 tryfindfile(char *dom, char *user)
328 {
329 	char *p;
330 
331 	while(dom && *dom){
332 		if(homeface && (p = tryfindfiledir(dom, user, homeface)) != nil)
333 			return p;
334 		if((p = tryfindfiledir(dom, user, libface)) != nil)
335 			return p;
336 		if((dom = strchr(dom, '.')) == nil)
337 			break;
338 		dom++;
339 	}
340 	return nil;
341 }
342 
343 char*
findfile(Face * f,char * dom,char * user)344 findfile(Face *f, char *dom, char *user)
345 {
346 	char *p;
347 
348 	if(facedom == nil){
349 		facedom = getenv("facedom");
350 		if(facedom == nil)
351 			facedom = DEFAULT;
352 	}
353 	if(libface == nil)
354 		libface = unsharp("#9/face");
355 	if(homeface == nil)
356 		homeface = smprint("%s/lib/face", getenv("HOME"));
357 
358 	if(dom == nil)
359 		dom = facedom;
360 
361 	f->unknown = 0;
362 	if((p = tryfindfile(dom, user)) != nil)
363 		return p;
364 	f->unknown = 1;
365 	p = tryfindfile(dom, "unknown");
366 	if(p != nil || strcmp(dom, facedom) == 0)
367 		return p;
368 	return tryfindfile("unknown", "unknown");
369 }
370 
371 static
372 void
clearsaved(void)373 clearsaved(void)
374 {
375 	Facefile *f, *next, **lf;
376 
377 	lf = &facefiles;
378 	for(f=facefiles; f!=nil; f=next){
379 		next = f->next;
380 		if(f->ref > 0){
381 			*lf = f;
382 			lf = &(f->next);
383 			continue;
384 		}
385 		if(f->image != display->black && f->image != display->white)
386 			freeimage(f->image);
387 		free(f->file);
388 		free(f);
389 	}
390 	*lf = nil;
391 	nsaved = 0;
392 }
393 
394 void
freefacefile(Facefile * f)395 freefacefile(Facefile *f)
396 {
397 	if(f==nil || f->ref-->1)
398 		return;
399 	if(++nsaved > Nsave)
400 		clearsaved();
401 }
402 
403 static Image*
myallocimage(ulong chan)404 myallocimage(ulong chan)
405 {
406 	Image *img;
407 	img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
408 	if(img == nil){
409 		clearsaved();
410 		img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
411 		if(img == nil)
412 			return nil;
413 	}
414 	return img;
415 }
416 
417 
418 static Image*
readbit(int fd,ulong chan)419 readbit(int fd, ulong chan)
420 {
421 	char buf[4096], hx[4], *p;
422 	uchar data[Facesize*Facesize];	/* more than enough */
423 	int nhx, i, n, ndata, nbit;
424 	Image *img;
425 
426 	n = readn(fd, buf, sizeof buf);
427 	if(n <= 0)
428 		return nil;
429 	if(n >= sizeof buf)
430 		n = sizeof(buf)-1;
431 	buf[n] = '\0';
432 
433 	n = 0;
434 	nhx = 0;
435 	nbit = chantodepth(chan);
436 	ndata = (Facesize*Facesize*nbit)/8;
437 	p = buf;
438 	while(n < ndata) {
439 		p = strpbrk(p+1, "0123456789abcdefABCDEF");
440 		if(p == nil)
441 			break;
442 		if(p[0] == '0' && p[1] == 'x')
443 			continue;
444 
445 		hx[nhx] = *p;
446 		if(++nhx == 2) {
447 			hx[nhx] = 0;
448 			i = strtoul(hx, 0, 16);
449 			data[n++] = i;
450 			nhx = 0;
451 		}
452 	}
453 	if(n < ndata)
454 		return allocimage(display, Rect(0,0,Facesize,Facesize), CMAP8, 0, 0x88888888);
455 
456 	img = myallocimage(chan);
457 	if(img == nil)
458 		return nil;
459 	loadimage(img, img->r, data, ndata);
460 	return img;
461 }
462 
463 static Facefile*
readface(char * fn)464 readface(char *fn)
465 {
466 	int x, y, fd;
467 	uchar bits;
468 	uchar *p;
469 	Image *mask;
470 	Image *face;
471 	char buf[16];
472 	uchar data[Facesize*Facesize];
473 	uchar mdata[(Facesize*Facesize)/8];
474 	Facefile *f;
475 	Dir *d;
476 
477 	for(f=facefiles; f!=nil; f=f->next){
478 		if(strcmp(fn, f->file) == 0){
479 			if(f->image == nil)
480 				break;
481 			if(time(0) - f->rdtime >= 30) {
482 				if(dirmtime(fn) != f->mtime){
483 					f = nil;
484 					break;
485 				}
486 				f->rdtime = time(0);
487 			}
488 			f->ref++;
489 			return f;
490 		}
491 	}
492 
493 	if((fd = open(fn, OREAD)) < 0)
494 		return nil;
495 
496 	if(readn(fd, buf, sizeof buf) != sizeof buf){
497 		close(fd);
498 		return nil;
499 	}
500 
501 	seek(fd, 0, 0);
502 
503 	mask = nil;
504 	if(buf[0] == '0' && buf[1] == 'x'){
505 		/* greyscale faces are just masks that we draw black through! */
506 		if(buf[2+8] == ',')	/* ldepth 1 */
507 			mask = readbit(fd, GREY2);
508 		else
509 			mask = readbit(fd, GREY1);
510 		face = display->black;
511 	}else{
512 		face = readimage(display, fd, 0);
513 		if(face == nil)
514 			goto Done;
515 		else if(face->chan == GREY4 || face->chan == GREY8){	/* greyscale: use inversion as mask */
516 			mask = myallocimage(face->chan);
517 			/* okay if mask is nil: that will copy the image white background and all */
518 			if(mask == nil)
519 				goto Done;
520 
521 			/* invert greyscale image */
522 			draw(mask, mask->r, display->white, nil, ZP);
523 			gendraw(mask, mask->r, display->black, ZP, face, face->r.min);
524 			freeimage(face);
525 			face = display->black;
526 		}else if(face->depth == 8){	/* snarf the bytes back and do a fill. */
527 			mask = myallocimage(GREY1);
528 			if(mask == nil)
529 				goto Done;
530 			if(unloadimage(face, face->r, data, Facesize*Facesize) != Facesize*Facesize){
531 				freeimage(mask);
532 				goto Done;
533 			}
534 			bits = 0;
535 			p = mdata;
536 			for(y=0; y<Facesize; y++){
537 				for(x=0; x<Facesize; x++){
538 					bits <<= 1;
539 					if(data[Facesize*y+x] != 0xFF)
540 						bits |= 1;
541 					if((x&7) == 7)
542 						*p++ = bits&0xFF;
543 				}
544 			}
545 			if(loadimage(mask, mask->r, mdata, sizeof mdata) != sizeof mdata){
546 				freeimage(mask);
547 				goto Done;
548 			}
549 		}
550 	}
551 
552 Done:
553 	/* always add at beginning of list, so updated files don't collide in cache */
554 	if(f == nil){
555 		f = emalloc(sizeof(Facefile));
556 		f->file = estrdup(fn);
557 		d = dirfstat(fd);
558 		if(d != nil){
559 			f->mtime = d->mtime;
560 			free(d);
561 		}
562 		f->next = facefiles;
563 		facefiles = f;
564 	}
565 	f->ref++;
566 	f->image = face;
567 	f->mask = mask;
568 	f->rdtime = time(0);
569 	close(fd);
570 	return f;
571 }
572 
573 void
findbit(Face * f)574 findbit(Face *f)
575 {
576 	char *fn;
577 
578 	fn = findfile(f, f->str[Sdomain], f->str[Suser]);
579 	if(fn) {
580 		if(strstr(fn, "unknown"))
581 			f->unknown = 1;
582 		f->file = readface(fn);
583 	}
584 	if(f->file){
585 		f->bit = f->file->image;
586 		f->mask = f->file->mask;
587 	}else{
588 		/* if returns nil, this is still ok: draw(nil) works */
589 		f->bit = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DYellow);
590 		replclipr(f->bit, 1, Rect(0, 0, Facesize, Facesize));
591 		f->mask = nil;
592 	}
593 }
594