]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/bzfs/oramfs.c
merge
[plan9front.git] / sys / src / cmd / bzfs / oramfs.c
1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4 #include <fcall.h>
5 #include "bzfs.h"
6
7 /*
8  * Rather than reading /adm/users, which is a lot of work for
9  * a toy program, we assume all groups have the form
10  *      NNN:user:user:
11  * meaning that each user is the leader of his own group.
12  */
13
14 enum
15 {
16         OPERM   = 0x3,          /* mask of all permission types in open mode */
17         Nram    = 512,
18         Maxsize = 512*1024*1024,
19         Maxfdata        = 8192,
20 };
21
22 typedef struct Fid Fid;
23 typedef struct Ram Ram;
24
25 struct Fid
26 {
27         short   busy;
28         short   open;
29         short   rclose;
30         int     fid;
31         Fid     *next;
32         char    *user;
33         Ram     *ram;
34 };
35
36 struct Ram
37 {
38         short   busy;
39         short   open;
40         long    parent;         /* index in Ram array */
41         Qid     qid;
42         long    perm;
43         char    *name;
44         ulong   atime;
45         ulong   mtime;
46         char    *user;
47         char    *group;
48         char    *muid;
49         char    *data;
50         long    ndata;
51 };
52
53 enum
54 {
55         Pexec =         1,
56         Pwrite =        2,
57         Pread =         4,
58         Pother =        1,
59         Pgroup =        8,
60         Powner =        64,
61 };
62
63 ulong   path;           /* incremented for each new file */
64 Fid     *fids;
65 Ram     ram[Nram];
66 int     nram;
67 int     mfd[2];
68 char    *user;
69 uchar   mdata[IOHDRSZ+Maxfdata];
70 uchar   rdata[Maxfdata];        /* buffer for data in reply */
71 uchar statbuf[STATMAX];
72 Fcall thdr;
73 Fcall   rhdr;
74 int     messagesize = sizeof mdata;
75
76 Fid *   newfid(int);
77 uint    ramstat(Ram*, uchar*, uint);
78 void    io(void);
79 void    *erealloc(void*, ulong);
80 void    *emalloc(ulong);
81 char    *estrdup(char*);
82 void    ramfsusage(void);
83 int     perm(Fid*, Ram*, int);
84 char *atom(char*);
85
86 char    *rflush(Fid*), *rversion(Fid*), *rauth(Fid*),
87         *rattach(Fid*), *rwalk(Fid*),
88         *ropen(Fid*), *rcreate(Fid*),
89         *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
90         *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*);
91
92 char    *(*fcalls[])(Fid*) = {
93         [Tversion]      rversion,
94         [Tflush]        rflush,
95         [Tauth] rauth,
96         [Tattach]       rattach,
97         [Twalk]         rwalk,
98         [Topen]         ropen,
99         [Tcreate]       rcreate,
100         [Tread]         rread,
101         [Twrite]        rwrite,
102         [Tclunk]        rclunk,
103         [Tremove]       rremove,
104         [Tstat]         rstat,
105         [Twstat]        rwstat,
106 };
107
108 char    Eperm[] =       "permission denied";
109 char    Enotdir[] =     "not a directory";
110 char    Enoauth[] =     "no authentication in ramfs";
111 char    Enotexist[] =   "file does not exist";
112 char    Einuse[] =      "file in use";
113 char    Eexist[] =      "file exists";
114 char    Eisdir[] =      "file is a directory";
115 char    Enotowner[] =   "not owner";
116 char    Eisopen[] =     "file already open for I/O";
117 char    Excl[] =        "exclusive use file already open";
118 char    Ename[] =       "illegal name";
119 char    Eversion[] =    "unknown 9P version";
120
121 int debug;
122
123 void
124 notifyf(void *a, char *s)
125 {
126         USED(a);
127         if(strncmp(s, "interrupt", 9) == 0)
128                 noted(NCONT);
129         noted(NDFLT);
130 }
131
132 void
133 ramfsmain(int argc, char *argv[])
134 {
135         Ram *r;
136         char *defmnt;
137         int p[2];
138         char buf[32];
139         int fd, srvfd;
140         int stdio = 0;
141
142         srvfd = -1;
143         defmnt = "/tmp";
144         ARGBEGIN{
145         case 'D':
146                 debug = 1;
147                 break;
148         case 'i':               /* this is DIFFERENT from normal ramfs; use 1 for both for kernel */
149                 defmnt = 0;
150                 stdio = 1;
151                 srvfd = 0;
152                 mfd[0] = 1;
153                 mfd[1] = 1;
154                 break;
155         case 's':
156                 defmnt = 0;
157                 break;
158         case 'm':
159                 defmnt = ARGF();
160                 break;
161         default:
162                 ramfsusage();
163         }ARGEND
164
165         if(!stdio){
166                 if(pipe(p) < 0)
167                         error("pipe failed");
168                 srvfd = p[1];
169                 mfd[0] = p[0];
170                 mfd[1] = p[0];
171                 if(defmnt == 0){
172                         fd = create("#s/ramfs", OWRITE, 0666);
173                         if(fd < 0)
174                                 error("create of /srv/ramfs failed");
175                         sprint(buf, "%d", p[1]);
176                         if(write(fd, buf, strlen(buf)) < 0)
177                                 error("writing /srv/ramfs");
178                 }
179         }
180
181         user = atom(getuser());
182         notify(notifyf);
183         nram = 1;
184         r = &ram[0];
185         r->busy = 1;
186         r->data = 0;
187         r->ndata = 0;
188         r->perm = DMDIR | 0775;
189         r->qid.type = QTDIR;
190         r->qid.path = 0LL;
191         r->qid.vers = 0;
192         r->parent = 0;
193         r->user = user;
194         r->group = user;
195         r->muid = user;
196         r->atime = time(0);
197         r->mtime = r->atime;
198         r->name = estrdup(".");
199
200         if(debug)
201                 fmtinstall('F', fcallfmt);
202         switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){
203         case -1:
204                 error("fork");
205         case 0:
206                 close(srvfd);
207                 io();
208                 break;
209         default:
210                 close(mfd[0]);  /* don't deadlock if child fails */
211                 if(defmnt && mount(srvfd, -1, defmnt, MREPL|MCREATE, "") < 0)
212                         error("mount failed: %r");
213         }
214 }
215
216 char*
217 rversion(Fid*)
218 {
219         Fid *f;
220
221         for(f = fids; f; f = f->next)
222                 if(f->busy)
223                         rclunk(f);
224         if(thdr.msize > sizeof mdata)
225                 rhdr.msize = sizeof mdata;
226         else
227                 rhdr.msize = thdr.msize;
228         messagesize = rhdr.msize;
229         if(strncmp(thdr.version, "9P2000", 6) != 0)
230                 return Eversion;
231         rhdr.version = "9P2000";
232         return 0;
233 }
234
235 char*
236 rauth(Fid*)
237 {
238         return "ramfs: no authentication required";
239 }
240
241 char*
242 rflush(Fid *f)
243 {
244         USED(f);
245         return 0;
246 }
247
248 char*
249 rattach(Fid *f)
250 {
251         /* no authentication! */
252         f->busy = 1;
253         f->rclose = 0;
254         f->ram = &ram[0];
255         rhdr.qid = f->ram->qid;
256         if(thdr.uname[0])
257                 f->user = atom(thdr.uname);
258         else
259                 f->user = atom("none");
260         if(strcmp(user, "none") == 0)
261                 user = f->user;
262         return 0;
263 }
264
265 char*
266 clone(Fid *f, Fid **nf)
267 {
268         if(f->open)
269                 return Eisopen;
270         if(f->ram->busy == 0)
271                 return Enotexist;
272         *nf = newfid(thdr.newfid);
273         (*nf)->busy = 1;
274         (*nf)->open = 0;
275         (*nf)->rclose = 0;
276         (*nf)->ram = f->ram;
277         (*nf)->user = f->user;  /* no ref count; the leakage is minor */
278         return 0;
279 }
280
281 char*
282 rwalk(Fid *f)
283 {
284         Ram *r, *fram;
285         char *name;
286         Ram *parent;
287         Fid *nf;
288         char *err;
289         ulong t;
290         int i;
291
292         err = nil;
293         nf = nil;
294         rhdr.nwqid = 0;
295         if(rhdr.newfid != rhdr.fid){
296                 err = clone(f, &nf);
297                 if(err)
298                         return err;
299                 f = nf; /* walk the new fid */
300         }
301         fram = f->ram;
302         if(thdr.nwname > 0){
303                 t = time(0);
304                 for(i=0; i<thdr.nwname && i<MAXWELEM; i++){
305                         if((fram->qid.type & QTDIR) == 0){
306                                 err = Enotdir;
307                                 break;
308                         }
309                         if(fram->busy == 0){
310                                 err = Enotexist;
311                                 break;
312                         }
313                         fram->atime = t;
314                         name = thdr.wname[i];
315                         if(strcmp(name, ".") == 0){
316     Found:
317                                 rhdr.nwqid++;
318                                 rhdr.wqid[i] = fram->qid;
319                                 continue;
320                         }
321                         parent = &ram[fram->parent];
322 #ifdef CHECKS
323                         if(!perm(f, parent, Pexec)){
324                                 err = Eperm;
325                                 break;
326                         }
327 #endif
328                         if(strcmp(name, "..") == 0){
329                                 fram = parent;
330                                 goto Found;
331                         }
332                         for(r=ram; r < &ram[nram]; r++)
333                                 if(r->busy && r->parent==fram-ram && strcmp(name, r->name)==0){
334                                         fram = r;
335                                         goto Found;
336                                 }
337                         break;
338                 }
339                 if(i==0 && err == nil)
340                         err = Enotexist;
341         }
342         if(nf != nil && (err!=nil || rhdr.nwqid<thdr.nwname)){
343                 /* clunk the new fid, which is the one we walked */
344                 f->busy = 0;
345                 f->ram = nil;
346         }
347         if(rhdr.nwqid == thdr.nwname)   /* update the fid after a successful walk */
348                 f->ram = fram;
349         return err;
350 }
351
352 char *
353 ropen(Fid *f)
354 {
355         Ram *r;
356         int mode, trunc;
357
358         if(f->open)
359                 return Eisopen;
360         r = f->ram;
361         if(r->busy == 0)
362                 return Enotexist;
363         if(r->perm & DMEXCL)
364                 if(r->open)
365                         return Excl;
366         mode = thdr.mode;
367         if(r->qid.type & QTDIR){
368                 if(mode != OREAD)
369                         return Eperm;
370                 rhdr.qid = r->qid;
371                 return 0;
372         }
373         if(mode & ORCLOSE){
374                 /* can't remove root; must be able to write parent */
375                 if(r->qid.path==0 || !perm(f, &ram[r->parent], Pwrite))
376                         return Eperm;
377                 f->rclose = 1;
378         }
379         trunc = mode & OTRUNC;
380         mode &= OPERM;
381         if(mode==OWRITE || mode==ORDWR || trunc)
382                 if(!perm(f, r, Pwrite))
383                         return Eperm;
384         if(mode==OREAD || mode==ORDWR)
385                 if(!perm(f, r, Pread))
386                         return Eperm;
387         if(mode==OEXEC)
388                 if(!perm(f, r, Pexec))
389                         return Eperm;
390         if(trunc && (r->perm&DMAPPEND)==0){
391                 r->ndata = 0;
392                 if(r->data)
393                         free(r->data);
394                 r->data = 0;
395                 r->qid.vers++;
396         }
397         rhdr.qid = r->qid;
398         rhdr.iounit = messagesize-IOHDRSZ;
399         f->open = 1;
400         r->open++;
401         return 0;
402 }
403
404 char *
405 rcreate(Fid *f)
406 {
407         Ram *r;
408         char *name;
409         long parent, prm;
410
411         if(f->open)
412                 return Eisopen;
413         if(f->ram->busy == 0)
414                 return Enotexist;
415         parent = f->ram - ram;
416         if((f->ram->qid.type&QTDIR) == 0)
417                 return Enotdir;
418         /* must be able to write parent */
419 #ifdef CHECKS
420         if(!perm(f, f->ram, Pwrite))
421                 return Eperm;
422 #endif
423         prm = thdr.perm;
424         name = thdr.name;
425         if(strcmp(name, ".")==0 || strcmp(name, "..")==0)
426                 return Ename;
427         for(r=ram; r<&ram[nram]; r++)
428                 if(r->busy && parent==r->parent)
429                 if(strcmp((char*)name, r->name)==0)
430                         return Einuse;
431         for(r=ram; r->busy; r++)
432                 if(r == &ram[Nram-1])
433                         return "no free ram resources";
434         r->busy = 1;
435         r->qid.path = ++path;
436         r->qid.vers = 0;
437         if(prm & DMDIR)
438                 r->qid.type |= QTDIR;
439         r->parent = parent;
440         free(r->name);
441         r->name = estrdup(name);
442         r->user = f->user;
443         r->group = f->ram->group;
444         r->muid = f->ram->muid;
445         if(prm & DMDIR)
446                 prm = (prm&~0777) | (f->ram->perm&prm&0777);
447         else
448                 prm = (prm&(~0777|0111)) | (f->ram->perm&prm&0666);
449         r->perm = prm;
450         r->ndata = 0;
451         if(r-ram >= nram)
452                 nram = r - ram + 1;
453         r->atime = time(0);
454         r->mtime = r->atime;
455         f->ram->mtime = r->atime;
456         f->ram = r;
457         rhdr.qid = r->qid;
458         rhdr.iounit = messagesize-IOHDRSZ;
459         f->open = 1;
460         if(thdr.mode & ORCLOSE)
461                 f->rclose = 1;
462         r->open++;
463         return 0;
464 }
465
466 char*
467 rread(Fid *f)
468 {
469         Ram *r;
470         uchar *buf;
471         long off;
472         int n, m, cnt;
473
474         if(f->ram->busy == 0)
475                 return Enotexist;
476         n = 0;
477         rhdr.count = 0;
478         off = thdr.offset;
479         buf = rdata;
480         cnt = thdr.count;
481         if(cnt > messagesize)   /* shouldn't happen, anyway */
482                 cnt = messagesize;
483         if(f->ram->qid.type & QTDIR){
484                 for(r=ram+1; off > 0; r++){
485                         if(r->busy && r->parent==f->ram-ram)
486                                 off -= ramstat(r, statbuf, sizeof statbuf);
487                         if(r == &ram[nram-1])
488                                 return 0;
489                 }
490                 for(; r<&ram[nram] && n < cnt; r++){
491                         if(!r->busy || r->parent!=f->ram-ram)
492                                 continue;
493                         m = ramstat(r, buf+n, cnt-n);
494                         if(m == 0)
495                                 break;
496                         n += m;
497                 }
498                 rhdr.data = (char*)rdata;
499                 rhdr.count = n;
500                 return 0;
501         }
502         r = f->ram;
503         if(off >= r->ndata)
504                 return 0;
505         r->atime = time(0);
506         n = cnt;
507         if(off+n > r->ndata)
508                 n = r->ndata - off;
509         rhdr.data = r->data+off;
510         rhdr.count = n;
511         return 0;
512 }
513
514 char*
515 rwrite(Fid *f)
516 {
517         Ram *r;
518         ulong off;
519         int cnt;
520
521         r = f->ram;
522         if(r->busy == 0)
523                 return Enotexist;
524         off = thdr.offset;
525         if(r->perm & DMAPPEND)
526                 off = r->ndata;
527         cnt = thdr.count;
528         if(r->qid.type & QTDIR)
529                 return Eisdir;
530         if(off+cnt >= Maxsize)          /* sanity check */
531                 return "write too big";
532         if(off+cnt > r->ndata)
533                 r->data = erealloc(r->data, off+cnt);
534         if(off > r->ndata)
535                 memset(r->data+r->ndata, 0, off-r->ndata);
536         if(off+cnt > r->ndata)
537                 r->ndata = off+cnt;
538         memmove(r->data+off, thdr.data, cnt);
539         r->qid.vers++;
540         r->mtime = time(0);
541         rhdr.count = cnt;
542         return 0;
543 }
544
545 void
546 realremove(Ram *r)
547 {
548         r->ndata = 0;
549         if(r->data)
550                 free(r->data);
551         r->data = 0;
552         r->parent = 0;
553         memset(&r->qid, 0, sizeof r->qid);
554         free(r->name);
555         r->name = nil;
556         r->busy = 0;
557 }
558
559 char *
560 rclunk(Fid *f)
561 {
562         if(f->open)
563                 f->ram->open--;
564         if(f->rclose)
565                 realremove(f->ram);
566         f->busy = 0;
567         f->open = 0;
568         f->ram = 0;
569         return 0;
570 }
571
572 char *
573 rremove(Fid *f)
574 {
575         Ram *r;
576
577         if(f->open)
578                 f->ram->open--;
579         f->busy = 0;
580         f->open = 0;
581         r = f->ram;
582         f->ram = 0;
583 #ifdef CHECKS
584         if(r->qid.path == 0 || !perm(f, &ram[r->parent], Pwrite))
585                 return Eperm;
586 #endif
587         ram[r->parent].mtime = time(0);
588         realremove(r);
589         return 0;
590 }
591
592 char *
593 rstat(Fid *f)
594 {
595         if(f->ram->busy == 0)
596                 return Enotexist;
597         rhdr.nstat = ramstat(f->ram, statbuf, sizeof statbuf);
598         rhdr.stat = statbuf;
599         return 0;
600 }
601
602 char *
603 rwstat(Fid *f)
604 {
605         Ram *r, *s;
606         Dir dir;
607
608         if(f->ram->busy == 0)
609                 return Enotexist;
610         convM2D(thdr.stat, thdr.nstat, &dir, (char*)statbuf);
611         r = f->ram;
612
613         /*
614          * To change length, must have write permission on file.
615          */
616 #ifdef CHECKS
617         if(dir.length!=~0 && dir.length!=r->ndata){
618                 if(!perm(f, r, Pwrite))
619                         return Eperm;
620         }
621 #endif
622
623         /*
624          * To change name, must have write permission in parent
625          * and name must be unique.
626          */
627         if(dir.name[0]!='\0' && strcmp(dir.name, r->name)!=0){
628 #ifdef CHECKS
629                 if(!perm(f, &ram[r->parent], Pwrite))
630                         return Eperm;
631 #endif
632                 for(s=ram; s<&ram[nram]; s++)
633                         if(s->busy && s->parent==r->parent)
634                         if(strcmp(dir.name, s->name)==0)
635                                 return Eexist;
636         }
637
638 #ifdef OWNERS
639         /*
640          * To change mode, must be owner or group leader.
641          * Because of lack of users file, leader=>group itself.
642          */
643         if(dir.mode!=~0 && r->perm!=dir.mode){
644                 if(strcmp(f->user, r->user) != 0)
645                 if(strcmp(f->user, r->group) != 0)
646                         return Enotowner;
647         }
648
649         /*
650          * To change group, must be owner and member of new group,
651          * or leader of current group and leader of new group.
652          * Second case cannot happen, but we check anyway.
653          */
654         if(dir.gid[0]!='\0' && strcmp(r->group, dir.gid)!=0){
655                 if(strcmp(f->user, r->user) == 0)
656                 if(strcmp(f->user, dir.gid) == 0)
657                         goto ok;
658                 if(strcmp(f->user, r->group) == 0)
659                 if(strcmp(f->user, dir.gid) == 0)
660                         goto ok;
661                 return Enotowner;
662                 ok:;
663         }
664 #endif
665
666         /* all ok; do it */
667         if(dir.mode != ~0){
668                 dir.mode &= ~DMDIR;     /* cannot change dir bit */
669                 dir.mode |= r->perm&DMDIR;
670                 r->perm = dir.mode;
671         }
672         if(dir.name[0] != '\0'){
673                 free(r->name);
674                 r->name = estrdup(dir.name);
675         }
676         if(dir.gid[0] != '\0')
677                 r->group = atom(dir.gid);
678
679         if(dir.uid[0] != '\0')
680                 r->user = atom(dir.uid);
681
682         if(dir.length!=~0 && dir.length!=r->ndata){
683                 r->data = erealloc(r->data, dir.length);
684                 if(r->ndata < dir.length)
685                         memset(r->data+r->ndata, 0, dir.length-r->ndata);
686                 r->ndata = dir.length;
687         }
688
689         if(dir.mtime != ~0)
690                 r->mtime = dir.mtime;
691
692         ram[r->parent].mtime = time(0);
693         return 0;
694 }
695
696 uint
697 ramstat(Ram *r, uchar *buf, uint nbuf)
698 {
699         Dir dir;
700
701         dir.name = r->name;
702         dir.qid = r->qid;
703         dir.mode = r->perm;
704         dir.length = r->ndata;
705         dir.uid = r->user;
706         dir.gid = r->group;
707         dir.muid = r->muid;
708         dir.atime = r->atime;
709         dir.mtime = r->mtime;
710         return convD2M(&dir, buf, nbuf);
711 }
712
713 Fid *
714 newfid(int fid)
715 {
716         Fid *f, *ff;
717
718         ff = 0;
719         for(f = fids; f; f = f->next)
720                 if(f->fid == fid)
721                         return f;
722                 else if(!ff && !f->busy)
723                         ff = f;
724         if(ff){
725                 ff->fid = fid;
726                 return ff;
727         }
728         f = emalloc(sizeof *f);
729         f->ram = nil;
730         f->fid = fid;
731         f->next = fids;
732         fids = f;
733         return f;
734 }
735
736 void
737 io(void)
738 {
739         char *err;
740         int n, pid;
741
742         pid = getpid();
743
744         for(;;){
745                 /*
746                  * reading from a pipe or a network device
747                  * will give an error after a few eof reads.
748                  * however, we cannot tell the difference
749                  * between a zero-length read and an interrupt
750                  * on the processes writing to us,
751                  * so we wait for the error.
752                  */
753                 n = read9pmsg(mfd[0], mdata, messagesize);
754                 if(n < 0)
755                         error("mount read: %r");
756                 if(n == 0)
757                         continue;
758                 if(convM2S(mdata, n, &thdr) == 0)
759                         continue;
760
761                 if(debug)
762                         fprint(2, "ramfs %d:<-%F\n", pid, &thdr);
763
764                 if(!fcalls[thdr.type])
765                         err = "bad fcall type";
766                 else
767                         err = (*fcalls[thdr.type])(newfid(thdr.fid));
768                 if(err){
769                         rhdr.type = Rerror;
770                         rhdr.ename = err;
771                 }else{
772                         rhdr.type = thdr.type + 1;
773                         rhdr.fid = thdr.fid;
774                 }
775                 rhdr.tag = thdr.tag;
776                 if(debug)
777                         fprint(2, "ramfs %d:->%F\n", pid, &rhdr);/**/
778                 n = convS2M(&rhdr, mdata, messagesize);
779                 if(n == 0)
780                         error("convS2M error on write");
781                 if(write(mfd[1], mdata, n) != n)
782                         error("mount write");
783         }
784 }
785
786 int
787 perm(Fid *f, Ram *r, int p)
788 {
789         if((p*Pother) & r->perm)
790                 return 1;
791         if(strcmp(f->user, r->group)==0 && ((p*Pgroup) & r->perm))
792                 return 1;
793         if(strcmp(f->user, r->user)==0 && ((p*Powner) & r->perm))
794                 return 1;
795         return 0;
796 }
797
798 void *
799 emalloc(ulong n)
800 {
801         void *p;
802
803         p = malloc(n);
804         if(!p)
805                 error("out of memory");
806         memset(p, 0, n);
807         return p;
808 }
809
810 void *
811 erealloc(void *p, ulong n)
812 {
813         p = realloc(p, n);
814         if(!p)
815                 error("out of memory");
816         return p;
817 }
818
819 char *
820 estrdup(char *q)
821 {
822         char *p;
823         int n;
824
825         n = strlen(q)+1;
826         p = malloc(n);
827         if(!p)
828                 error("out of memory");
829         memmove(p, q, n);
830         return p;
831 }
832
833 void
834 ramfsusage(void)
835 {
836         fprint(2, "usage: %s [-is] [-m mountpoint]\n", argv0);
837         exits("usage");
838 }
839
840 /*
841  *      Custom allocators to avoid malloc overheads on small objects.
842  *      We never free these.  (See below.)
843  */
844 typedef struct Stringtab        Stringtab;
845 struct Stringtab {
846         Stringtab *link;
847         char *str;
848 };
849 static Stringtab*
850 taballoc(void)
851 {
852         static Stringtab *t;
853         static uint nt;
854
855         if(nt == 0){
856                 t = malloc(64*sizeof(Stringtab));
857                 if(t == 0)
858                         sysfatal("out of memory");
859                 nt = 64;
860         }
861         nt--;
862         return t++;
863 }
864
865 static char*
866 xstrdup(char *s)
867 {
868         char *r;
869         int len;
870         static char *t;
871         static int nt;
872
873         len = strlen(s)+1;
874         if(len >= 8192)
875                 sysfatal("strdup big string");
876
877         if(nt < len){
878                 t = malloc(8192);
879                 if(t == 0)
880                         sysfatal("out of memory");
881                 nt = 8192;
882         }
883         r = t;
884         t += len;
885         nt -= len;
886         strcpy(r, s);
887         return r;
888 }
889
890 /*
891  *      Return a uniquely allocated copy of a string.
892  *      Don't free these -- they stay in the table for the 
893  *      next caller who wants that particular string.
894  *      String comparison can be done with pointer comparison 
895  *      if you know both strings are atoms.
896  */
897 static Stringtab *stab[1024];
898
899 static uint
900 hash(char *s)
901 {
902         uint h;
903         uchar *p;
904
905         h = 0;
906         for(p=(uchar*)s; *p; p++)
907                 h = h*37 + *p;
908         return h;
909 }
910
911 char*
912 atom(char *str)
913 {
914         uint h;
915         Stringtab *tab;
916         
917         h = hash(str) % nelem(stab);
918         for(tab=stab[h]; tab; tab=tab->link)
919                 if(strcmp(str, tab->str) == 0)
920                         return tab->str;
921
922         tab = taballoc();
923         tab->str = xstrdup(str);
924         tab->link = stab[h];
925         stab[h] = tab;
926         return tab->str;
927 }