]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/aux/searchfs.c
exec(2): fix prototypes
[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, "") < 0){
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         for(;;){
596                 /*
597                  * reading from a pipe or a network device
598                  * will give an error after a few eof reads
599                  * however, we cannot tell the difference
600                  * between a zero-length read and an interrupt
601                  * on the processes writing to us,
602                  * so we wait for the error
603                  */
604                 n = read9pmsg(fd, buf, messagesize);
605                 if(n == 0)
606                         continue;
607                 if(n < 0)
608                         fatal("mount read");
609
610                 rpc.data = (char*)buf + IOHDRSZ;
611                 if(convM2S(buf, n, &rpc) == 0)
612                         continue;
613                 // fprint(2, "recv: %F\n", &rpc);
614
615
616                 /*
617                  * flushes are way too hard.
618                  * a reply to the original message seems to work
619                  */
620                 if(rpc.type == Tflush)
621                         continue;
622                 else if(rpc.type >= Tmax || !fcalls[rpc.type])
623                         err = "bad fcall type";
624                 else
625                         err = (*fcalls[rpc.type])(fs, &rpc);
626                 if(err){
627                         rpc.type = Rerror;
628                         rpc.ename = err;
629                 }else
630                         rpc.type++;
631                 n = convS2M(&rpc, buf, messagesize);
632                 // fprint(2, "send: %F\n", &rpc);
633                 if(write(fd, buf, n) != n)
634                         fatal("mount write");
635         }
636 }
637
638 Fid*
639 mkfid(Fs *fs, uint fid)
640 {
641         Fid *f;
642         int h;
643
644         h = fid % Nfidhash;
645         for(f = fs->hash[h]; f; f = f->next){
646                 if(f->fid == fid)
647                         return nil;
648         }
649
650         f = emalloc(sizeof *f);
651         f->next = fs->hash[h];
652         if(f->next != nil)
653                 f->next->last = &f->next;
654         f->last = &fs->hash[h];
655         fs->hash[h] = f;
656
657         f->fid = fid;
658         f->ref = 1;
659         f->attached = 1;
660         f->open = 0;
661         return f;
662 }
663
664 Fid*
665 getfid(Fs *fs, uint fid)
666 {
667         Fid *f;
668         int h;
669
670         h = fid % Nfidhash;
671         for(f = fs->hash[h]; f; f = f->next){
672                 if(f->fid == fid){
673                         if(f->attached == 0)
674                                 break;
675                         f->ref++;
676                         return f;
677                 }
678         }
679         return nil;
680 }
681
682 void
683 putfid(Fs *, Fid *f)
684 {
685         f->ref--;
686         if(f->ref == 0 && f->attached == 0){
687                 *f->last = f->next;
688                 if(f->next != nil)
689                         f->next->last = f->last;
690                 if(f->search != nil)
691                         searchfree(f->search);
692                 free(f);
693         }
694 }
695
696 char*
697 fsversion(Fs *, Fcall *rpc)
698 {
699         if(rpc->msize < 256)
700                 return "version: message size too small";
701         if(rpc->msize > messagesize)
702                 rpc->msize = messagesize;
703         messagesize = rpc->msize;
704         if(strncmp(rpc->version, "9P2000", 6) != 0)
705                 return "unrecognized 9P version";
706         rpc->version = "9P2000";
707         return nil;
708 }
709
710 char*
711 fsauth(Fs *, Fcall *)
712 {
713         return "searchfs: authentication not required";
714 }
715
716 char*
717 fsattach(Fs *fs, Fcall *rpc)
718 {
719         Fid *f;
720
721         f = mkfid(fs, rpc->fid);
722         if(f == nil)
723                 return Einuse;
724         f->open = 0;
725         f->qid.type = QTDIR;
726         f->qid.path = Qroot;
727         f->qid.vers = 0;
728         rpc->qid = f->qid;
729         putfid(fs, f);
730         return nil;
731 }
732
733 char*
734 fswalk(Fs *fs, Fcall *rpc)
735 {
736         Fid *f, *nf;
737         int nqid, nwname, type;
738         char *err, *name;
739         ulong path;
740
741         f = getfid(fs, rpc->fid);
742         if(f == nil)
743                 return Enofid;
744         nf = nil;
745         if(rpc->fid != rpc->newfid){
746                 nf = mkfid(fs, rpc->newfid);
747                 if(nf == nil){
748                         putfid(fs, f);
749                         return Einuse;
750                 }
751                 nf->qid = f->qid;
752                 putfid(fs, f);
753                 f = nf; /* walk f */
754         }
755
756         err = nil;
757         path = f->qid.path;
758         nwname = rpc->nwname;
759         for(nqid=0; nqid<nwname; nqid++){
760                 if(path != Qroot){
761                         err = Enotdir;
762                         break;
763                 }
764                 name = rpc->wname[nqid];
765                 if(strcmp(name, "search") == 0){
766                         type = QTFILE;
767                         path = Qsearch;
768                 }else if(strcmp(name, "stats") == 0){
769                         type = QTFILE;
770                         path = Qstats;
771                 }else if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0){
772                         type = QTDIR;
773                         path = path;
774                 }else{
775                         err = Enotexist;
776                         break;
777                 }
778                 rpc->wqid[nqid] = (Qid){path, 0, type};
779         }
780
781         if(nwname > 0){
782                 if(nf != nil && nqid < nwname)
783                         nf->attached = 0;
784                 if(nqid == nwname)
785                         f->qid = rpc->wqid[nqid-1];
786         }
787
788         putfid(fs, f);
789         rpc->nwqid = nqid;
790         f->open = 0;
791         return err;
792 }
793
794 char *
795 fsopen(Fs *fs, Fcall *rpc)
796 {
797         Fid *f;
798         int mode;
799
800         f = getfid(fs, rpc->fid);
801         if(f == nil)
802                 return Enofid;
803         if(f->open){
804                 putfid(fs, f);
805                 return Eisopen;
806         }
807         mode = rpc->mode & OPERM;
808         if(mode == OEXEC
809         || f->qid.path == Qroot && (mode == OWRITE || mode == ORDWR)){
810                 putfid(fs, f);
811                 return Eperm;
812         }
813         f->open = 1;
814         f->where = nil;
815         f->n = 0;
816         f->search = nil;
817         rpc->qid = f->qid;
818         rpc->iounit = messagesize-IOHDRSZ;
819         putfid(fs, f);
820         return nil;
821 }
822
823 char *
824 fscreate(Fs *, Fcall *)
825 {
826         return Eperm;
827 }
828
829 char*
830 fsread(Fs *fs, Fcall *rpc)
831 {
832         Fid *f;
833         int n, off, count, len;
834
835         f = getfid(fs, rpc->fid);
836         if(f == nil)
837                 return Enofid;
838         if(!f->open){
839                 putfid(fs, f);
840                 return Enotopen;
841         }
842         count = rpc->count;
843         off = rpc->offset;
844         rpc->count = 0;
845         if(f->qid.path == Qroot){
846                 if(off > 0)
847                         rpc->count = 0;
848                 else
849                         rpc->count = dostat(Qsearch, (uchar*)rpc->data, count);
850                 putfid(fs, f);
851                 if(off == 0 && rpc->count <= BIT16SZ)
852                         return "directory read count too small";
853                 return nil;
854         }
855         if(f->qid.path == Qstats){
856                 len = 0;
857         }else{
858                 for(len = 0; len < count; len += n){
859                         if(f->where == nil || f->search == nil)
860                                 break;
861                         if(f->n == 0)
862                                 f->where = searchsearch(f->search, f->where, edatabase, &f->n);
863                         n = f->n;
864                         if(n != 0){
865                                 if(n > count-len)
866                                         n = count-len;
867                                 memmove(rpc->data+len, f->where, n);
868                                 f->where += n;
869                                 f->n -= n;
870                         }
871                 }
872         }
873         putfid(fs, f);
874         rpc->count = len;
875         return nil;
876 }
877
878 char*
879 fswrite(Fs *fs, Fcall *rpc)
880 {
881         Fid *f;
882
883         f = getfid(fs, rpc->fid);
884         if(f == nil)
885                 return Enofid;
886         if(!f->open || f->qid.path != Qsearch){
887                 putfid(fs, f);
888                 return Enotopen;
889         }
890
891         if(f->search != nil)
892                 searchfree(f->search);
893         f->search = searchparse(rpc->data, rpc->data + rpc->count);
894         if(f->search == nil){
895                 putfid(fs, f);
896                 return Ebadsearch;
897         }
898         f->where = database;
899         f->n = 0;
900         putfid(fs, f);
901         return nil;
902 }
903
904 char *
905 fsclunk(Fs *fs, Fcall *rpc)
906 {
907         Fid *f;
908
909         f = getfid(fs, rpc->fid);
910         if(f != nil){
911                 f->attached = 0;
912                 putfid(fs, f);
913         }
914         return nil;
915 }
916
917 char *
918 fsremove(Fs *, Fcall *)
919 {
920         return Eperm;
921 }
922
923 char *
924 fsstat(Fs *fs, Fcall *rpc)
925 {
926         Fid *f;
927
928         f = getfid(fs, rpc->fid);
929         if(f == nil)
930                 return Enofid;
931         rpc->stat = fs->statbuf;
932         rpc->nstat = dostat(f->qid.path, rpc->stat, sizeof fs->statbuf);
933         putfid(fs, f);
934         if(rpc->nstat <= BIT16SZ)
935                 return "stat count too small";
936         return nil;
937 }
938
939 char *
940 fswstat(Fs *, Fcall *)
941 {
942         return Eperm;
943 }
944
945 int
946 dostat(int path, uchar *buf, int nbuf)
947 {
948         Dir d;
949
950         switch(path){
951         case Qroot:
952                 d.name = ".";
953                 d.mode = DMDIR|0555;
954                 d.qid.type = QTDIR;
955                 break;
956         case Qsearch:
957                 d.name = "search";
958                 d.mode = 0666;
959                 d.qid.type = QTFILE;
960                 break;
961         case Qstats:
962                 d.name = "stats";
963                 d.mode = 0666;
964                 d.qid.type = QTFILE;
965                 break;
966         }
967         d.qid.path = path;
968         d.qid.vers = 0;
969         d.length = 0;
970         d.uid = d.gid = d.muid = "none";
971         d.atime = d.mtime = time(nil);
972         return convD2M(&d, buf, nbuf);
973 }
974
975 char *
976 urlunesc(char *s, char *e)
977 {
978         char *t, *v;
979         int c, n;
980
981         v = emalloc((e - s) + 1);
982         for(t = v; s < e; s++){
983                 c = *s;
984                 if(c == '%'){
985                         if(s + 2 >= e)
986                                 break;
987                         n = s[1];
988                         if(n >= '0' && n <= '9')
989                                 n = n - '0';
990                         else if(n >= 'A' && n <= 'F')
991                                 n = n - 'A' + 10;
992                         else if(n >= 'a' && n <= 'f')
993                                 n = n - 'a' + 10;
994                         else
995                                 break;
996                         c = n;
997                         n = s[2];
998                         if(n >= '0' && n <= '9')
999                                 n = n - '0';
1000                         else if(n >= 'A' && n <= 'F')
1001                                 n = n - 'A' + 10;
1002                         else if(n >= 'a' && n <= 'f')
1003                                 n = n - 'a' + 10;
1004                         else
1005                                 break;
1006                         s += 2;
1007                         c = c * 16 + n;
1008                 }
1009                 *t++ = c;
1010         }
1011         *t = 0;
1012         return v;
1013 }
1014
1015 int
1016 toupper(int c)
1017 {
1018         if(c >= 'a' && c <= 'z')
1019                 c += 'A' - 'a';
1020         return c;
1021 }
1022
1023 int
1024 tolower(int c)
1025 {
1026         if(c >= 'A' && c <= 'Z')
1027                 c += 'a' - 'A';
1028         return c;
1029 }
1030
1031 void
1032 fatal(char *fmt, ...)
1033 {
1034         va_list arg;
1035         char buf[1024];
1036
1037         write(2, "searchfs: ", 8);
1038         va_start(arg, fmt);
1039         vseprint(buf, buf+1024, fmt, arg);
1040         va_end(arg);
1041         write(2, buf, strlen(buf));
1042         write(2, "\n", 1);
1043         exits(fmt);
1044 }
1045
1046 void *
1047 emalloc(uint n)
1048 {
1049         void *p;
1050
1051         p = malloc(n);
1052         if(p == nil)
1053                 fatal("out of memory");
1054         memset(p, 0, n);
1055         return p;
1056 }
1057
1058 void
1059 usage(void)
1060 {
1061         fprint(2, "usage: searchfs [-m mountpoint] [-s srvfile] database\n");
1062         exits("usage");
1063 }