]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/aux/searchfs.c
audiohda: fix syntax error
[plan9front.git] / sys / src / cmd / aux / searchfs.c
1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4 #include <fcall.h>
5
6 /*
7  * caveat:
8  * this stuff is only meant to work for ascii databases
9  */
10
11 typedef struct Fid Fid;
12 typedef struct Fs Fs;
13 typedef struct Quick Quick;
14 typedef struct Match Match;
15 typedef struct Search Search;
16
17 enum
18 {
19         OPERM   = 0x3,          /* mask of all permission types in open mode */
20         Nfidhash        = 32,
21
22         /*
23          * qids
24          */
25         Qroot   = 1,
26         Qsearch = 2,
27         Qstats  = 3,
28 };
29
30 /*
31  * boyer-moore quick string matching
32  */
33 struct Quick
34 {
35         char    *pat;
36         char    *up;            /* match string for upper case of pat */
37         int     len;            /* of pat (and up) -1; used for fast search */
38         uchar   jump[256];      /* jump index table */
39         int     miss;           /* amount to jump if we falsely match the last char */
40 };
41 extern void     quickmk(Quick*, char*, int);
42 extern void     quickfree(Quick*);
43 extern char*    quicksearch(Quick*, char*, char*);
44
45 /*
46  * exact matching of a search string
47  */
48 struct Match
49 {
50         Match   *next;
51         char    *pat;                           /* null-terminated search string */
52         char    *up;                            /* upper case of pat */
53         int     len;                            /* length of both pat and up */
54         int     (*op)(Match*, char*, char*);            /* method for this partiticular search */
55 };
56
57 struct Search
58 {
59         Quick           quick;          /* quick match */
60         Match           *match;         /* exact matches */
61         int             skip;           /* number of matches to skip */
62 };
63
64 extern char*    searchsearch(Search*, char*, char*, int*);
65 extern Search*  searchparse(char*, char*);
66 extern void     searchfree(Search*);
67
68 struct Fid
69 {
70         Lock;
71         Fid     *next;
72         Fid     **last;
73         uint    fid;
74         int     ref;            /* number of fcalls using the fid */
75         int     attached;               /* fid has beed attached or cloned and not clunked */
76
77         int     open;
78         Qid     qid;
79         Search  *search;                /* search patterns */
80         char    *where;         /* current location in the database */
81         int     n;              /* number of bytes left in found item */
82 };
83
84 int                     dostat(int, uchar*, int);
85 void*                   emalloc(uint);
86 void                    fatal(char*, ...);
87 Match*                  mkmatch(Match*, int(*)(Match*, char*, char*), char*);
88 Match*                  mkstrmatch(Match*, char*);
89 char*           nextsearch(char*, char*, char**, char**);
90 int                     strlook(Match*, char*, char*);
91 char*                   strndup(char*, int);
92 int                     tolower(int);
93 int                     toupper(int);
94 char*                   urlunesc(char*, char*);
95 void                    usage(void);
96
97 struct Fs
98 {
99         Lock;                   /* for fids */
100
101         Fid     *hash[Nfidhash];
102         uchar   statbuf[1024];  /* plenty big enough */
103 };
104 extern  void    fsrun(Fs*, int);
105 extern  Fid*    getfid(Fs*, uint);
106 extern  Fid*    mkfid(Fs*, uint);
107 extern  void    putfid(Fs*, Fid*);
108 extern  char*   fsversion(Fs*, Fcall*);
109 extern  char*   fsauth(Fs*, Fcall*);
110 extern  char*   fsattach(Fs*, Fcall*);
111 extern  char*   fswalk(Fs*, Fcall*);
112 extern  char*   fsopen(Fs*, Fcall*);
113 extern  char*   fscreate(Fs*, Fcall*);
114 extern  char*   fsread(Fs*, Fcall*);
115 extern  char*   fswrite(Fs*, Fcall*);
116 extern  char*   fsclunk(Fs*, Fcall*);
117 extern  char*   fsremove(Fs*, Fcall*);
118 extern  char*   fsstat(Fs*, Fcall*);
119 extern  char*   fswstat(Fs*, Fcall*);
120
121 char    *(*fcalls[])(Fs*, Fcall*) =
122 {
123         [Tversion]              fsversion,
124         [Tattach]       fsattach,
125         [Tauth] fsauth,
126         [Twalk]         fswalk,
127         [Topen]         fsopen,
128         [Tcreate]       fscreate,
129         [Tread]         fsread,
130         [Twrite]        fswrite,
131         [Tclunk]        fsclunk,
132         [Tremove]       fsremove,
133         [Tstat]         fsstat,
134         [Twstat]        fswstat
135 };
136
137 char    Eperm[] =       "permission denied";
138 char    Enotdir[] =     "not a directory";
139 char    Enotexist[] =   "file does not exist";
140 char    Eisopen[] =     "file already open for I/O";
141 char    Einuse[] =      "fid is already in use";
142 char    Enofid[] =      "no such fid";
143 char    Enotopen[] =    "file is not open";
144 char    Ebadsearch[] =  "bad search string";
145
146 Fs      fs;
147 char    *database;
148 char    *edatabase;
149 int     messagesize = 8192+IOHDRSZ;
150 void
151 main(int argc, char **argv)
152 {
153         Dir *d;
154         char buf[12], *mnt, *srv;
155         int fd, p[2], n;
156
157         mnt = "/tmp";
158         srv = nil;
159         ARGBEGIN{
160                 case 's':
161                         srv = ARGF();
162                         mnt = nil;
163                         break;
164                 case 'm':
165                         mnt = ARGF();
166                         break;
167         }ARGEND
168
169         fmtinstall('F', fcallfmt);
170
171         if(argc != 1)
172                 usage();
173         d = nil;
174         fd = open(argv[0], OREAD);
175         if(fd < 0 || (d=dirfstat(fd)) == nil)
176                 fatal("can't open %s: %r", argv[0]);
177         n = d->length;
178         free(d);
179         if(n == 0)
180                 fatal("zero length database %s", argv[0]);
181         database = emalloc(n);
182         if(read(fd, database, n) != n)
183                 fatal("can't read %s: %r", argv[0]);
184         close(fd);
185         edatabase = database + n;
186
187         if(pipe(p) < 0)
188                 fatal("pipe failed");
189
190         switch(rfork(RFPROC|RFMEM|RFNOTEG|RFNAMEG)){
191         case 0:
192                 fsrun(&fs, p[0]);
193                 exits(nil);
194         case -1:        
195                 fatal("fork failed");
196         }
197
198         if(mnt == nil){
199                 if(srv == nil)
200                         usage();
201                 fd = create(srv, OWRITE, 0666);
202                 if(fd < 0){
203                         remove(srv);
204                         fd = create(srv, OWRITE, 0666);
205                         if(fd < 0){
206                                 close(p[1]);
207                                 fatal("create of %s failed", srv);
208                         }
209                 }
210                 sprint(buf, "%d", p[1]);
211                 if(write(fd, buf, strlen(buf)) < 0){
212                         close(p[1]);
213                         fatal("writing %s", srv);
214                 }
215                 close(p[1]);
216                 exits(nil);
217         }
218
219         if(mount(p[1], -1, mnt, MREPL, "") == -1){
220                 close(p[1]);
221                 fatal("mount failed");
222         }
223         close(p[1]);
224         exits(nil);
225 }
226
227 /*
228  * execute the search
229  * do a quick match,
230  * isolate the line in which the occured,
231  * and try all of the exact matches
232  */
233 char*
234 searchsearch(Search *search, char *where, char *end, int *np)
235 {
236         Match *m;
237         char *s, *e;
238
239         *np = 0;
240         if(search == nil || where == nil)
241                 return nil;
242         for(;;){
243                 s = quicksearch(&search->quick, where, end);
244                 if(s == nil)
245                         return nil;
246                 e = memchr(s, '\n', end - s);
247                 if(e == nil)
248                         e = end;
249                 else
250                         e++;
251                 while(s > where && s[-1] != '\n')
252                         s--;
253                 for(m = search->match; m != nil; m = m->next){
254                         if((*m->op)(m, s, e) == 0)
255                                 break;
256                 }
257
258                 if(m == nil){
259                         if(search->skip > 0)
260                                 search->skip--;
261                         else{
262                                 *np = e - s;
263                                 return s;
264                         }
265                 }
266
267                 where = e;
268         }
269 }
270
271 /*
272  * parse a search string of the form
273  * tag=val&tag1=val1...
274  */
275 Search*
276 searchparse(char *search, char *esearch)
277 {
278         Search *s;
279         Match *m, *next, **last;
280         char *tag, *val, *p;
281         int ok;
282
283         s = emalloc(sizeof *s);
284         s->match = nil;
285
286         /*
287          * acording to the http spec,
288          * repeated search queries are ingored.
289          * the last search given is performed on the original object
290          */
291         while((p = memchr(s, '?', esearch - search)) != nil){
292                 search = p + 1;
293         }
294         while(search < esearch){
295                 search = nextsearch(search, esearch, &tag, &val);
296                 if(tag == nil)
297                         continue;
298
299                 ok = 0;
300                 if(strcmp(tag, "skip") == 0){
301                         s->skip = strtoul(val, &p, 10);
302                         if(*p == 0)
303                                 ok = 1;
304                 }else if(strcmp(tag, "search") == 0){
305                         s->match = mkstrmatch(s->match, val);
306                         ok = 1;
307                 }
308                 free(tag);
309                 free(val);
310                 if(!ok){
311                         searchfree(s);
312                         return nil;
313                 }
314         }
315
316         if(s->match == nil){
317                 free(s);
318                 return nil;
319         }
320
321         /*
322          * order the matches by probability of occurance
323          * first cut is just by length
324          */
325         for(ok = 0; !ok; ){
326                 ok = 1;
327                 last = &s->match;
328                 for(m = *last; m && m->next; m = *last){
329                         if(m->next->len > m->len){
330                                 next = m->next;
331                                 m->next = next->next;
332                                 next->next = m;
333                                 *last = next;
334                                 ok = 0;
335                         }
336                         last = &m->next;
337                 }
338         }
339
340         /*
341          * convert the best search into a fast lookup
342          */
343         m = s->match;
344         s->match = m->next;
345         quickmk(&s->quick, m->pat, 1);
346         free(m->pat);
347         free(m->up);
348         free(m);
349         return s;
350 }
351
352 void
353 searchfree(Search *s)
354 {
355         Match *m, *next;
356
357         if(s == nil)
358                 return;
359         for(m = s->match; m; m = next){
360                 next = m->next;
361                 free(m->pat);
362                 free(m->up);
363                 free(m);
364         }
365         quickfree(&s->quick);
366         free(s);
367 }
368
369 char*
370 nextsearch(char *search, char *esearch, char **tagp, char **valp)
371 {
372         char *tag, *val;
373
374         *tagp = nil;
375         *valp = nil;
376         for(tag = search; search < esearch && *search != '='; search++)
377                 ;
378         if(search == esearch)
379                 return search;
380         tag = urlunesc(tag, search);
381         search++;
382         for(val = search; search < esearch && *search != '&'; search++)
383                 ;
384         val = urlunesc(val, search);
385         if(search != esearch)
386                 search++;
387         *tagp = tag;
388         *valp = val;
389         return search;
390 }
391
392 Match*
393 mkstrmatch(Match *m, char *pat)
394 {
395         char *s;
396
397         for(s = pat; *s; s++){
398                 if(*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'){
399                         *s = 0;
400                         m = mkmatch(m, strlook, pat);
401                         pat = s + 1;
402                 }else
403                         *s = tolower(*s);
404         }
405         return mkmatch(m, strlook, pat);
406 }
407
408 Match*
409 mkmatch(Match *next, int (*op)(Match*, char*, char*), char *pat)
410 {
411         Match *m;
412         char *p;
413         int n;
414
415         n = strlen(pat);
416         if(n == 0)
417                 return next;
418         m = emalloc(sizeof *m);
419         m->op = op;
420         m->len = n;
421         m->pat = strdup(pat);
422         m->up = strdup(pat);
423         for(p = m->up; *p; p++)
424                 *p = toupper(*p);
425         for(p = m->pat; *p; p++)
426                 *p = tolower(*p);
427         m->next = next;
428         return m;
429 }
430
431 int
432 strlook(Match *m, char *str, char *e)
433 {
434         char *pat, *up, *s;
435         int c, pc, fc, fuc, n;
436
437         n = m->len;
438         fc = m->pat[0];
439         fuc = m->up[0];
440         for(; str + n <= e; str++){
441                 c = *str;
442                 if(c != fc && c != fuc)
443                         continue;
444                 s = str + 1;
445                 up = m->up + 1;
446                 for(pat = m->pat + 1; pc = *pat; pat++){
447                         c = *s;
448                         if(c != pc && c != *up)
449                                 break;
450                         up++;
451                         s++;
452                 }
453                 if(pc == 0)
454                         return 1;
455         }
456         return 0;
457 }
458
459 /*
460  * boyer-moore style pattern matching
461  * implements an exact match for ascii
462  * however, if mulitbyte upper-case and lower-case
463  * characters differ in length or in more than one byte,
464  * it only implements an approximate match
465  */
466 void
467 quickmk(Quick *q, char *spat, int ignorecase)
468 {
469         char *pat, *up;
470         uchar *j;       
471         int ep, ea, cp, ca, i, c, n;
472
473         /*
474          * allocate the machine
475          */
476         n = strlen(spat);
477         if(n == 0){
478                 q->pat = nil;
479                 q->up = nil;
480                 q->len = -1;
481                 return;
482         }
483         pat = emalloc(2* n + 2);
484         q->pat = pat;
485         up = pat;
486         if(ignorecase)
487                 up = pat + n + 1;
488         q->up = up;
489         while(c = *spat++){
490                 if(ignorecase){
491                         *up++ = toupper(c);
492                         c = tolower(c);
493                 }
494                 *pat++ = c;
495         }
496         pat = q->pat;
497         up = q->up;
498         pat[n] = up[n] = '\0';
499
500         /*
501          * make the skipping table
502          */
503         if(n > 255)
504                 n = 255;
505         j = q->jump;
506         memset(j, n, 256);
507         n--;
508         q->len = n;
509         for(i = 0; i <= n; i++){
510                 j[(uchar)pat[i]] = n - i;
511                 j[(uchar)up[i]] = n - i;
512         }
513         
514         /*
515          * find the minimum safe amount to skip
516          * if we match the last char but not the whole pat
517          */
518         ep = pat[n];
519         ea = up[n];
520         for(i = n - 1; i >= 0; i--){
521                 cp = pat[i];
522                 ca = up[i];
523                 if(cp == ep || cp == ea || ca == ep || ca == ea)
524                         break;
525         }
526         q->miss = n - i;
527 }
528
529 void
530 quickfree(Quick *q)
531 {
532         if(q->pat != nil)
533                 free(q->pat);
534         q->pat = nil;
535 }
536
537 char *
538 quicksearch(Quick *q, char *s, char *e)
539 {
540         char *pat, *up, *m, *ee;
541         uchar *j;
542         int len, n, c, mc;
543
544         len = q->len;
545         if(len < 0)
546                 return s;
547         j = q->jump;
548         pat = q->pat;
549         up = q->up;
550         s += len;
551         ee = e - (len * 4 + 4);
552         while(s < e){
553                 /*
554                  * look for a match on the last char
555                  */
556                 while(s < ee && (n = j[(uchar)*s])){
557                         s += n;
558                         s += j[(uchar)*s];
559                         s += j[(uchar)*s];
560                         s += j[(uchar)*s];
561                 }
562                 if(s >= e)
563                         return nil;
564                 while(n = j[(uchar)*s]){
565                         s += n;
566                         if(s >= e)
567                                 return nil;
568                 }
569
570                 /*
571                  * does the string match?
572                  */
573                 m = s - len;
574                 for(n = 0; c = pat[n]; n++){
575                         mc = *m++;
576                         if(c != mc && mc != up[n])
577                                 break;
578                 }
579                 if(!c)
580                         return s - len;
581                 s += q->miss;
582         }
583         return nil;
584 }
585
586 void
587 fsrun(Fs *fs, int fd)
588 {
589         Fcall rpc;
590         char *err;
591         uchar *buf;
592         int n;
593
594         buf = emalloc(messagesize);
595         while((n = read9pmsg(fd, buf, messagesize)) != 0){
596                 if(n < 0)
597                         fatal("mount read: %r");
598                 rpc.data = (char*)buf + IOHDRSZ;
599                 if(convM2S(buf, n, &rpc) != n)
600                         fatal("convM2S format error: %r");
601
602                 /*
603                  * flushes are way too hard.
604                  * a reply to the original message seems to work
605                  */
606                 if(rpc.type == Tflush)
607                         continue;
608                 else if(rpc.type >= Tmax || !fcalls[rpc.type])
609                         err = "bad fcall type";
610                 else
611                         err = (*fcalls[rpc.type])(fs, &rpc);
612                 if(err){
613                         rpc.type = Rerror;
614                         rpc.ename = err;
615                 }else
616                         rpc.type++;
617                 n = convS2M(&rpc, buf, messagesize);
618                 // fprint(2, "send: %F\n", &rpc);
619                 if(write(fd, buf, n) != n)
620                         fatal("mount write");
621         }
622 }
623
624 Fid*
625 mkfid(Fs *fs, uint fid)
626 {
627         Fid *f;
628         int h;
629
630         h = fid % Nfidhash;
631         for(f = fs->hash[h]; f; f = f->next){
632                 if(f->fid == fid)
633                         return nil;
634         }
635
636         f = emalloc(sizeof *f);
637         f->next = fs->hash[h];
638         if(f->next != nil)
639                 f->next->last = &f->next;
640         f->last = &fs->hash[h];
641         fs->hash[h] = f;
642
643         f->fid = fid;
644         f->ref = 1;
645         f->attached = 1;
646         f->open = 0;
647         return f;
648 }
649
650 Fid*
651 getfid(Fs *fs, uint fid)
652 {
653         Fid *f;
654         int h;
655
656         h = fid % Nfidhash;
657         for(f = fs->hash[h]; f; f = f->next){
658                 if(f->fid == fid){
659                         if(f->attached == 0)
660                                 break;
661                         f->ref++;
662                         return f;
663                 }
664         }
665         return nil;
666 }
667
668 void
669 putfid(Fs *, Fid *f)
670 {
671         f->ref--;
672         if(f->ref == 0 && f->attached == 0){
673                 *f->last = f->next;
674                 if(f->next != nil)
675                         f->next->last = f->last;
676                 if(f->search != nil)
677                         searchfree(f->search);
678                 free(f);
679         }
680 }
681
682 char*
683 fsversion(Fs *, Fcall *rpc)
684 {
685         if(rpc->msize < 256)
686                 return "version: message size too small";
687         if(rpc->msize > messagesize)
688                 rpc->msize = messagesize;
689         messagesize = rpc->msize;
690         if(strncmp(rpc->version, "9P", 2) != 0)
691                 rpc->version = "unknown";
692         else
693                 rpc->version = "9P2000";
694         return nil;
695 }
696
697 char*
698 fsauth(Fs *, Fcall *)
699 {
700         return "searchfs: authentication not required";
701 }
702
703 char*
704 fsattach(Fs *fs, Fcall *rpc)
705 {
706         Fid *f;
707
708         f = mkfid(fs, rpc->fid);
709         if(f == nil)
710                 return Einuse;
711         f->open = 0;
712         f->qid.type = QTDIR;
713         f->qid.path = Qroot;
714         f->qid.vers = 0;
715         rpc->qid = f->qid;
716         putfid(fs, f);
717         return nil;
718 }
719
720 char*
721 fswalk(Fs *fs, Fcall *rpc)
722 {
723         Fid *f, *nf;
724         int nqid, nwname, type;
725         char *err, *name;
726         ulong path;
727
728         f = getfid(fs, rpc->fid);
729         if(f == nil)
730                 return Enofid;
731         nf = nil;
732         if(rpc->fid != rpc->newfid){
733                 nf = mkfid(fs, rpc->newfid);
734                 if(nf == nil){
735                         putfid(fs, f);
736                         return Einuse;
737                 }
738                 nf->qid = f->qid;
739                 putfid(fs, f);
740                 f = nf; /* walk f */
741         }
742
743         err = nil;
744         path = f->qid.path;
745         nwname = rpc->nwname;
746         for(nqid=0; nqid<nwname; nqid++){
747                 if(path != Qroot){
748                         err = Enotdir;
749                         break;
750                 }
751                 name = rpc->wname[nqid];
752                 if(strcmp(name, "search") == 0){
753                         type = QTFILE;
754                         path = Qsearch;
755                 }else if(strcmp(name, "stats") == 0){
756                         type = QTFILE;
757                         path = Qstats;
758                 }else if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0){
759                         type = QTDIR;
760                         path = path;
761                 }else{
762                         err = Enotexist;
763                         break;
764                 }
765                 rpc->wqid[nqid] = (Qid){path, 0, type};
766         }
767
768         if(nwname > 0){
769                 if(nf != nil && nqid < nwname)
770                         nf->attached = 0;
771                 if(nqid == nwname)
772                         f->qid = rpc->wqid[nqid-1];
773         }
774
775         putfid(fs, f);
776         rpc->nwqid = nqid;
777         f->open = 0;
778         return err;
779 }
780
781 char *
782 fsopen(Fs *fs, Fcall *rpc)
783 {
784         Fid *f;
785         int mode;
786
787         f = getfid(fs, rpc->fid);
788         if(f == nil)
789                 return Enofid;
790         if(f->open){
791                 putfid(fs, f);
792                 return Eisopen;
793         }
794         mode = rpc->mode & OPERM;
795         if(mode == OEXEC
796         || f->qid.path == Qroot && (mode == OWRITE || mode == ORDWR)){
797                 putfid(fs, f);
798                 return Eperm;
799         }
800         f->open = 1;
801         f->where = nil;
802         f->n = 0;
803         f->search = nil;
804         rpc->qid = f->qid;
805         rpc->iounit = messagesize-IOHDRSZ;
806         putfid(fs, f);
807         return nil;
808 }
809
810 char *
811 fscreate(Fs *, Fcall *)
812 {
813         return Eperm;
814 }
815
816 char*
817 fsread(Fs *fs, Fcall *rpc)
818 {
819         Fid *f;
820         int n, off, count, len;
821
822         f = getfid(fs, rpc->fid);
823         if(f == nil)
824                 return Enofid;
825         if(!f->open){
826                 putfid(fs, f);
827                 return Enotopen;
828         }
829         count = rpc->count;
830         off = rpc->offset;
831         rpc->count = 0;
832         if(f->qid.path == Qroot){
833                 if(off > 0)
834                         rpc->count = 0;
835                 else
836                         rpc->count = dostat(Qsearch, (uchar*)rpc->data, count);
837                 putfid(fs, f);
838                 if(off == 0 && rpc->count <= BIT16SZ)
839                         return "directory read count too small";
840                 return nil;
841         }
842         if(f->qid.path == Qstats){
843                 len = 0;
844         }else{
845                 for(len = 0; len < count; len += n){
846                         if(f->where == nil || f->search == nil)
847                                 break;
848                         if(f->n == 0)
849                                 f->where = searchsearch(f->search, f->where, edatabase, &f->n);
850                         n = f->n;
851                         if(n != 0){
852                                 if(n > count-len)
853                                         n = count-len;
854                                 memmove(rpc->data+len, f->where, n);
855                                 f->where += n;
856                                 f->n -= n;
857                         }
858                 }
859         }
860         putfid(fs, f);
861         rpc->count = len;
862         return nil;
863 }
864
865 char*
866 fswrite(Fs *fs, Fcall *rpc)
867 {
868         Fid *f;
869
870         f = getfid(fs, rpc->fid);
871         if(f == nil)
872                 return Enofid;
873         if(!f->open || f->qid.path != Qsearch){
874                 putfid(fs, f);
875                 return Enotopen;
876         }
877
878         if(f->search != nil)
879                 searchfree(f->search);
880         f->search = searchparse(rpc->data, rpc->data + rpc->count);
881         if(f->search == nil){
882                 putfid(fs, f);
883                 return Ebadsearch;
884         }
885         f->where = database;
886         f->n = 0;
887         putfid(fs, f);
888         return nil;
889 }
890
891 char *
892 fsclunk(Fs *fs, Fcall *rpc)
893 {
894         Fid *f;
895
896         f = getfid(fs, rpc->fid);
897         if(f != nil){
898                 f->attached = 0;
899                 putfid(fs, f);
900         }
901         return nil;
902 }
903
904 char *
905 fsremove(Fs *, Fcall *)
906 {
907         return Eperm;
908 }
909
910 char *
911 fsstat(Fs *fs, Fcall *rpc)
912 {
913         Fid *f;
914
915         f = getfid(fs, rpc->fid);
916         if(f == nil)
917                 return Enofid;
918         rpc->stat = fs->statbuf;
919         rpc->nstat = dostat(f->qid.path, rpc->stat, sizeof fs->statbuf);
920         putfid(fs, f);
921         if(rpc->nstat <= BIT16SZ)
922                 return "stat count too small";
923         return nil;
924 }
925
926 char *
927 fswstat(Fs *, Fcall *)
928 {
929         return Eperm;
930 }
931
932 int
933 dostat(int path, uchar *buf, int nbuf)
934 {
935         Dir d;
936
937         switch(path){
938         case Qroot:
939                 d.name = ".";
940                 d.mode = DMDIR|0555;
941                 d.qid.type = QTDIR;
942                 break;
943         case Qsearch:
944                 d.name = "search";
945                 d.mode = 0666;
946                 d.qid.type = QTFILE;
947                 break;
948         case Qstats:
949                 d.name = "stats";
950                 d.mode = 0666;
951                 d.qid.type = QTFILE;
952                 break;
953         }
954         d.qid.path = path;
955         d.qid.vers = 0;
956         d.length = 0;
957         d.uid = d.gid = d.muid = "none";
958         d.atime = d.mtime = time(nil);
959         return convD2M(&d, buf, nbuf);
960 }
961
962 char *
963 urlunesc(char *s, char *e)
964 {
965         char *t, *v;
966         int c, n;
967
968         v = emalloc((e - s) + 1);
969         for(t = v; s < e; s++){
970                 c = *s;
971                 if(c == '%'){
972                         if(s + 2 >= e)
973                                 break;
974                         n = s[1];
975                         if(n >= '0' && n <= '9')
976                                 n = n - '0';
977                         else if(n >= 'A' && n <= 'F')
978                                 n = n - 'A' + 10;
979                         else if(n >= 'a' && n <= 'f')
980                                 n = n - 'a' + 10;
981                         else
982                                 break;
983                         c = n;
984                         n = s[2];
985                         if(n >= '0' && n <= '9')
986                                 n = n - '0';
987                         else if(n >= 'A' && n <= 'F')
988                                 n = n - 'A' + 10;
989                         else if(n >= 'a' && n <= 'f')
990                                 n = n - 'a' + 10;
991                         else
992                                 break;
993                         s += 2;
994                         c = c * 16 + n;
995                 }
996                 *t++ = c;
997         }
998         *t = 0;
999         return v;
1000 }
1001
1002 int
1003 toupper(int c)
1004 {
1005         if(c >= 'a' && c <= 'z')
1006                 c += 'A' - 'a';
1007         return c;
1008 }
1009
1010 int
1011 tolower(int c)
1012 {
1013         if(c >= 'A' && c <= 'Z')
1014                 c += 'a' - 'A';
1015         return c;
1016 }
1017
1018 void
1019 fatal(char *fmt, ...)
1020 {
1021         va_list arg;
1022         char buf[1024];
1023
1024         write(2, "searchfs: ", 8);
1025         va_start(arg, fmt);
1026         vseprint(buf, buf+1024, fmt, arg);
1027         va_end(arg);
1028         write(2, buf, strlen(buf));
1029         write(2, "\n", 1);
1030         exits(fmt);
1031 }
1032
1033 void *
1034 emalloc(uint n)
1035 {
1036         void *p;
1037
1038         p = malloc(n);
1039         if(p == nil)
1040                 fatal("out of memory");
1041         memset(p, 0, n);
1042         return p;
1043 }
1044
1045 void
1046 usage(void)
1047 {
1048         fprint(2, "usage: searchfs [-m mountpoint] [-s srvfile] database\n");
1049         exits("usage");
1050 }