]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/aux/searchfs.c
dc: fix off by one in stack overflow check (thanks BurnZeZ)
[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         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, "9P2000", 6) != 0)
691                 return "unrecognized 9P version";
692         rpc->version = "9P2000";
693         return nil;
694 }
695
696 char*
697 fsauth(Fs *, Fcall *)
698 {
699         return "searchfs: authentication not required";
700 }
701
702 char*
703 fsattach(Fs *fs, Fcall *rpc)
704 {
705         Fid *f;
706
707         f = mkfid(fs, rpc->fid);
708         if(f == nil)
709                 return Einuse;
710         f->open = 0;
711         f->qid.type = QTDIR;
712         f->qid.path = Qroot;
713         f->qid.vers = 0;
714         rpc->qid = f->qid;
715         putfid(fs, f);
716         return nil;
717 }
718
719 char*
720 fswalk(Fs *fs, Fcall *rpc)
721 {
722         Fid *f, *nf;
723         int nqid, nwname, type;
724         char *err, *name;
725         ulong path;
726
727         f = getfid(fs, rpc->fid);
728         if(f == nil)
729                 return Enofid;
730         nf = nil;
731         if(rpc->fid != rpc->newfid){
732                 nf = mkfid(fs, rpc->newfid);
733                 if(nf == nil){
734                         putfid(fs, f);
735                         return Einuse;
736                 }
737                 nf->qid = f->qid;
738                 putfid(fs, f);
739                 f = nf; /* walk f */
740         }
741
742         err = nil;
743         path = f->qid.path;
744         nwname = rpc->nwname;
745         for(nqid=0; nqid<nwname; nqid++){
746                 if(path != Qroot){
747                         err = Enotdir;
748                         break;
749                 }
750                 name = rpc->wname[nqid];
751                 if(strcmp(name, "search") == 0){
752                         type = QTFILE;
753                         path = Qsearch;
754                 }else if(strcmp(name, "stats") == 0){
755                         type = QTFILE;
756                         path = Qstats;
757                 }else if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0){
758                         type = QTDIR;
759                         path = path;
760                 }else{
761                         err = Enotexist;
762                         break;
763                 }
764                 rpc->wqid[nqid] = (Qid){path, 0, type};
765         }
766
767         if(nwname > 0){
768                 if(nf != nil && nqid < nwname)
769                         nf->attached = 0;
770                 if(nqid == nwname)
771                         f->qid = rpc->wqid[nqid-1];
772         }
773
774         putfid(fs, f);
775         rpc->nwqid = nqid;
776         f->open = 0;
777         return err;
778 }
779
780 char *
781 fsopen(Fs *fs, Fcall *rpc)
782 {
783         Fid *f;
784         int mode;
785
786         f = getfid(fs, rpc->fid);
787         if(f == nil)
788                 return Enofid;
789         if(f->open){
790                 putfid(fs, f);
791                 return Eisopen;
792         }
793         mode = rpc->mode & OPERM;
794         if(mode == OEXEC
795         || f->qid.path == Qroot && (mode == OWRITE || mode == ORDWR)){
796                 putfid(fs, f);
797                 return Eperm;
798         }
799         f->open = 1;
800         f->where = nil;
801         f->n = 0;
802         f->search = nil;
803         rpc->qid = f->qid;
804         rpc->iounit = messagesize-IOHDRSZ;
805         putfid(fs, f);
806         return nil;
807 }
808
809 char *
810 fscreate(Fs *, Fcall *)
811 {
812         return Eperm;
813 }
814
815 char*
816 fsread(Fs *fs, Fcall *rpc)
817 {
818         Fid *f;
819         int n, off, count, len;
820
821         f = getfid(fs, rpc->fid);
822         if(f == nil)
823                 return Enofid;
824         if(!f->open){
825                 putfid(fs, f);
826                 return Enotopen;
827         }
828         count = rpc->count;
829         off = rpc->offset;
830         rpc->count = 0;
831         if(f->qid.path == Qroot){
832                 if(off > 0)
833                         rpc->count = 0;
834                 else
835                         rpc->count = dostat(Qsearch, (uchar*)rpc->data, count);
836                 putfid(fs, f);
837                 if(off == 0 && rpc->count <= BIT16SZ)
838                         return "directory read count too small";
839                 return nil;
840         }
841         if(f->qid.path == Qstats){
842                 len = 0;
843         }else{
844                 for(len = 0; len < count; len += n){
845                         if(f->where == nil || f->search == nil)
846                                 break;
847                         if(f->n == 0)
848                                 f->where = searchsearch(f->search, f->where, edatabase, &f->n);
849                         n = f->n;
850                         if(n != 0){
851                                 if(n > count-len)
852                                         n = count-len;
853                                 memmove(rpc->data+len, f->where, n);
854                                 f->where += n;
855                                 f->n -= n;
856                         }
857                 }
858         }
859         putfid(fs, f);
860         rpc->count = len;
861         return nil;
862 }
863
864 char*
865 fswrite(Fs *fs, Fcall *rpc)
866 {
867         Fid *f;
868
869         f = getfid(fs, rpc->fid);
870         if(f == nil)
871                 return Enofid;
872         if(!f->open || f->qid.path != Qsearch){
873                 putfid(fs, f);
874                 return Enotopen;
875         }
876
877         if(f->search != nil)
878                 searchfree(f->search);
879         f->search = searchparse(rpc->data, rpc->data + rpc->count);
880         if(f->search == nil){
881                 putfid(fs, f);
882                 return Ebadsearch;
883         }
884         f->where = database;
885         f->n = 0;
886         putfid(fs, f);
887         return nil;
888 }
889
890 char *
891 fsclunk(Fs *fs, Fcall *rpc)
892 {
893         Fid *f;
894
895         f = getfid(fs, rpc->fid);
896         if(f != nil){
897                 f->attached = 0;
898                 putfid(fs, f);
899         }
900         return nil;
901 }
902
903 char *
904 fsremove(Fs *, Fcall *)
905 {
906         return Eperm;
907 }
908
909 char *
910 fsstat(Fs *fs, Fcall *rpc)
911 {
912         Fid *f;
913
914         f = getfid(fs, rpc->fid);
915         if(f == nil)
916                 return Enofid;
917         rpc->stat = fs->statbuf;
918         rpc->nstat = dostat(f->qid.path, rpc->stat, sizeof fs->statbuf);
919         putfid(fs, f);
920         if(rpc->nstat <= BIT16SZ)
921                 return "stat count too small";
922         return nil;
923 }
924
925 char *
926 fswstat(Fs *, Fcall *)
927 {
928         return Eperm;
929 }
930
931 int
932 dostat(int path, uchar *buf, int nbuf)
933 {
934         Dir d;
935
936         switch(path){
937         case Qroot:
938                 d.name = ".";
939                 d.mode = DMDIR|0555;
940                 d.qid.type = QTDIR;
941                 break;
942         case Qsearch:
943                 d.name = "search";
944                 d.mode = 0666;
945                 d.qid.type = QTFILE;
946                 break;
947         case Qstats:
948                 d.name = "stats";
949                 d.mode = 0666;
950                 d.qid.type = QTFILE;
951                 break;
952         }
953         d.qid.path = path;
954         d.qid.vers = 0;
955         d.length = 0;
956         d.uid = d.gid = d.muid = "none";
957         d.atime = d.mtime = time(nil);
958         return convD2M(&d, buf, nbuf);
959 }
960
961 char *
962 urlunesc(char *s, char *e)
963 {
964         char *t, *v;
965         int c, n;
966
967         v = emalloc((e - s) + 1);
968         for(t = v; s < e; s++){
969                 c = *s;
970                 if(c == '%'){
971                         if(s + 2 >= e)
972                                 break;
973                         n = s[1];
974                         if(n >= '0' && n <= '9')
975                                 n = n - '0';
976                         else if(n >= 'A' && n <= 'F')
977                                 n = n - 'A' + 10;
978                         else if(n >= 'a' && n <= 'f')
979                                 n = n - 'a' + 10;
980                         else
981                                 break;
982                         c = n;
983                         n = s[2];
984                         if(n >= '0' && n <= '9')
985                                 n = n - '0';
986                         else if(n >= 'A' && n <= 'F')
987                                 n = n - 'A' + 10;
988                         else if(n >= 'a' && n <= 'f')
989                                 n = n - 'a' + 10;
990                         else
991                                 break;
992                         s += 2;
993                         c = c * 16 + n;
994                 }
995                 *t++ = c;
996         }
997         *t = 0;
998         return v;
999 }
1000
1001 int
1002 toupper(int c)
1003 {
1004         if(c >= 'a' && c <= 'z')
1005                 c += 'A' - 'a';
1006         return c;
1007 }
1008
1009 int
1010 tolower(int c)
1011 {
1012         if(c >= 'A' && c <= 'Z')
1013                 c += 'a' - 'A';
1014         return c;
1015 }
1016
1017 void
1018 fatal(char *fmt, ...)
1019 {
1020         va_list arg;
1021         char buf[1024];
1022
1023         write(2, "searchfs: ", 8);
1024         va_start(arg, fmt);
1025         vseprint(buf, buf+1024, fmt, arg);
1026         va_end(arg);
1027         write(2, buf, strlen(buf));
1028         write(2, "\n", 1);
1029         exits(fmt);
1030 }
1031
1032 void *
1033 emalloc(uint n)
1034 {
1035         void *p;
1036
1037         p = malloc(n);
1038         if(p == nil)
1039                 fatal("out of memory");
1040         memset(p, 0, n);
1041         return p;
1042 }
1043
1044 void
1045 usage(void)
1046 {
1047         fprint(2, "usage: searchfs [-m mountpoint] [-s srvfile] database\n");
1048         exits("usage");
1049 }