]> git.lizzy.rs Git - plan9front.git/blob - sys/src/libdisk/proto.c
sed: add -u flag that flushes output buffers before reading in further input
[plan9front.git] / sys / src / libdisk / proto.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include <fcall.h>
6 #include <disk.h>
7 #include <regexp.h>
8
9 enum {
10         LEN     = 8*1024,
11         HUNKS   = 128,
12 };
13
14 typedef struct File File;
15 struct File{
16         char    *new;
17         char    *elem;
18         char    *old;
19         char    *uid;
20         char    *gid;
21         ulong   mode;
22 };
23
24 typedef void Mkfserr(char*, void*);
25 typedef void Mkfsenum(char*, char*, Dir*, void*);
26
27 typedef struct Name Name;
28 struct Name {
29         int n;
30         char *s;
31 };
32
33 typedef struct Opt Opt;
34 struct Opt {
35         int level;
36         long mode;
37         long mask;
38         Reprog *skip;
39         char *uid;
40         char *gid;
41         Opt *prev;
42 };
43
44 typedef struct Mkaux Mkaux;
45 struct Mkaux {
46         Mkfserr *warn;
47         Mkfsenum *mkenum;
48         char *root;
49         char *proto;
50         jmp_buf jmp;
51         Biobuf *b;
52
53         Name oldfile;
54         Name fullname;
55         int     lineno;
56         int     indent;
57
58         Opt *opt;
59
60         void *a;
61 };
62
63 static void domkfs(Mkaux *mkaux, File *me, int level);
64
65 static int      copyfile(Mkaux*, File*, Dir*, int);
66 static void     freefile(File*);
67 static File*    getfile(Mkaux*, File*);
68 static char*    getmode(Mkaux*, char*, ulong*);
69 static char*    getname(Mkaux*, char*, char**);
70 static char*    getpath(Mkaux*, char*);
71 static int      mkfile(Mkaux*, File*);
72 static char*    mkpath(Mkaux*, char*, char*);
73 static void     mktree(Mkaux*, File*, int);
74 static void     setname(Mkaux*, Name*, File*);
75 static void     skipdir(Mkaux*);
76 static void     warn(Mkaux*, char *, ...);
77 static void     popopt(Mkaux *mkaux);
78
79 //static void
80 //mprint(char *new, char *old, Dir *d, void*)
81 //{
82 //      print("%s %s %D\n", new, old, d);
83 //}
84
85 int
86 rdproto(char *proto, char *root, Mkfsenum *mkenum, Mkfserr *mkerr, void *a)
87 {
88         Mkaux mx, *m;
89         File file;
90         int rv;
91
92         m = &mx;
93         memset(&mx, 0, sizeof mx);
94         if(root == nil)
95                 root = "/";
96
97         m->root = root;
98         m->warn = mkerr;
99         m->mkenum = mkenum;
100         m->a = a;
101         m->proto = proto;
102         m->lineno = 0;
103         m->indent = 0;
104         m->opt = nil;
105         if((m->b = Bopen(proto, OREAD)) == nil) {
106                 werrstr("open '%s': %r", proto);
107                 return -1;
108         }
109
110         memset(&file, 0, sizeof file);
111         file.new = "";
112         file.old = nil;
113
114         rv = 0;
115         if(setjmp(m->jmp) == 0)
116                 domkfs(m, &file, -1);
117         else
118                 rv = -1;
119         free(m->oldfile.s);
120         free(m->fullname.s);
121         m->indent = -1;
122         popopt(m);
123         return rv;
124 }
125
126 static void*
127 emalloc(Mkaux *mkaux, ulong n)
128 {
129         void *v;
130
131         v = malloc(n);
132         if(v == nil)
133                 longjmp(mkaux->jmp, 1); /* memory leak */
134         memset(v, 0, n);
135         return v;
136 }
137
138 static char*
139 estrdup(Mkaux *mkaux, char *s)
140 {
141         s = strdup(s);
142         if(s == nil)
143                 longjmp(mkaux->jmp, 1); /* memory leak */
144         return s;
145 }
146
147 static void
148 domkfs(Mkaux *mkaux, File *me, int level)
149 {
150         File *child;
151         int rec;
152
153         child = getfile(mkaux, me);
154         if(!child)
155                 return;
156         if((child->elem[0] == '+' || child->elem[0] == '*') && child->elem[1] == '\0'){
157                 rec = child->elem[0] == '+';
158                 free(child->new);
159                 child->new = estrdup(mkaux, me->new);
160                 setname(mkaux, &mkaux->oldfile, child);
161                 mktree(mkaux, child, rec);
162                 freefile(child);
163                 child = getfile(mkaux, me);
164         }
165         while(child && mkaux->indent > level){
166                 if(mkfile(mkaux, child))
167                         domkfs(mkaux, child, mkaux->indent);
168                 freefile(child);
169                 child = getfile(mkaux, me);
170         }
171         if(child){
172                 freefile(child);
173                 Bseek(mkaux->b, -Blinelen(mkaux->b), 1);
174                 mkaux->lineno--;
175         }
176 }
177
178 static void
179 mktree(Mkaux *mkaux, File *me, int rec)
180 {
181         File child;
182         Dir *d;
183         int i, n, fd;
184
185         fd = open(mkaux->oldfile.s, OREAD);
186         if(fd < 0){
187                 warn(mkaux, "can't open %s: %r", mkaux->oldfile.s);
188                 return;
189         }
190         child = *me;
191         while((n = dirread(fd, &d)) > 0){
192                 for(i = 0; i < n; i++){
193                         if(mkaux->opt && mkaux->opt->skip){
194                                 Resub m[8];
195
196                                 memset(m, 0, sizeof(m));
197                                 if(regexec(mkaux->opt->skip, d[i].name, m, nelem(m)))
198                                         continue;
199                         }
200                         child.new = mkpath(mkaux, me->new, d[i].name);
201                         if(me->old)
202                                 child.old = mkpath(mkaux, me->old, d[i].name);
203                         child.elem = d[i].name;
204                         setname(mkaux, &mkaux->oldfile, &child);
205                         if((!(d[i].mode&DMDIR) || rec) && copyfile(mkaux, &child, &d[i], 1) && rec)
206                                 mktree(mkaux, &child, rec);
207                         free(child.new);
208                         if(child.old)
209                                 free(child.old);
210                 }
211                 free(d);
212         }
213         close(fd);
214 }
215
216 static int
217 mkfile(Mkaux *mkaux, File *f)
218 {
219         Dir *d;
220
221         if((d = dirstat(mkaux->oldfile.s)) == nil){
222                 warn(mkaux, "can't stat file %s: %r", mkaux->oldfile.s);
223                 skipdir(mkaux);
224                 return 0;
225         }
226         return copyfile(mkaux, f, d, 0);
227 }
228
229 enum {
230         SLOP = 30
231 };
232
233 static void
234 setname(Mkaux *mkaux, Name *name, File *f)
235 {
236         char *s1, *s2, *ss;
237         int l;
238         
239         s1 = mkaux->root;
240         s2 = "";
241         if(f->old){
242                 /* if old is not a absolute path, dont append root to it */
243                 if(f->old[0] != '/')
244                         s1 = f->old;
245                 else
246                         s2 = f->old;
247         }else
248                 s2 = f->new;
249
250         l = strlen(s1);
251         ss = (*s1 && *s2 && *s2 != '/' && s1[l-1] != '/') ? "/" : "";
252         l += strlen(ss);
253         l += strlen(s2);
254         l++;
255         if(name->n < l+SLOP/2) {
256                 free(name->s);
257                 name->s = emalloc(mkaux, l+SLOP);
258                 name->n = l+SLOP;
259         }
260         snprint(name->s, name->n, "%s%s%s", s1, ss, s2);
261 }
262
263
264 static int
265 copyfile(Mkaux *mkaux, File *f, Dir *d, int permonly)
266 {
267         Dir *nd;
268         Opt *o;
269         ulong xmode;
270         char *p;
271
272         setname(mkaux, &mkaux->fullname, f);
273
274         /*
275          * Extra stat here is inefficient but accounts for binds.
276          */
277         if((nd = dirstat(mkaux->fullname.s)) != nil)
278                 d = nd;
279
280         d->name = f->elem;
281         o = mkaux->opt;
282         if(strcmp(f->uid, "-") != 0)
283                 d->uid = f->uid;
284         else if(o && o->uid)
285                 d->uid = o->uid;
286         if(strcmp(f->gid, "-") != 0)
287                 d->gid = f->gid;
288         else if(o && o->gid)
289                 d->gid = o->gid;
290         if(f->mode != ~0){
291                 if(permonly)
292                         d->mode = (d->mode & ~0666) | (f->mode & 0666);
293                 else if((d->mode&DMDIR) != (f->mode&DMDIR))
294                         warn(mkaux, "inconsistent mode for %s", f->new);
295                 else
296                         d->mode = f->mode;
297         } else if(o && o->mask)
298                 d->mode = (d->mode & ~o->mask) | (o->mode & o->mask);
299
300         if(p = strrchr(f->new, '/'))
301                 d->name = p+1;
302         else
303                 d->name = f->new;
304         mkaux->mkenum(f->new, mkaux->fullname.s, d, mkaux->a);
305         xmode = d->mode;
306         free(nd);
307         return (xmode&DMDIR) != 0;
308 }
309
310 static char *
311 mkpath(Mkaux *mkaux, char *prefix, char *elem)
312 {
313         char *p;
314         int n;
315
316         n = strlen(prefix) + strlen(elem) + 2;
317         p = emalloc(mkaux, n);
318         strcpy(p, prefix);
319         strcat(p, "/");
320         strcat(p, elem);
321         return p;
322 }
323
324 static int
325 parsemode(char *spec, long *pmask, long *pmode)
326 {
327         char op, set, *s;
328         long mode;
329         long mask;
330
331         s = spec;
332         op = set = 0;
333         mode = 0;
334         mask = DMAPPEND | DMEXCL | DMTMP;
335
336         if(*s >= '0' && *s <= '7'){
337                 mask = 0666;
338                 mode = strtoul(s, 0, 8);
339                 op = '=';
340                 s = "!";
341         }
342
343         for(; *s && op == 0; s++){
344                 switch(*s){
345                 case 'a':
346                         mask |= 0666;
347                         break;
348                 case 'u':
349                         mask |= 0600;
350                         break;
351                 case 'g':
352                         mask |= 060;
353                         break;
354                 case 'o':
355                         mask |= 06;
356                         break;
357                 case '-':
358                 case '+':
359                 case '=':
360                         op = *s;
361                         break;
362                 default:
363                         return 0;
364                 }
365         }
366         if(s == spec)
367                 mask |= 0666;
368
369         for(; *s; s++){
370                 switch(*s){
371                 case 'r':
372                         mode |= 0444;
373                         break;
374                 case 'w':
375                         mode |= 0222;
376                         break;
377                 case 'x':
378                         mode |= 0111;
379                         break;
380                 case 'a':
381                         mode |= DMAPPEND;
382                         break;
383                 case 'l':
384                         mode |= DMEXCL;
385                         break;
386                 case 't':
387                         mode |= DMTMP;
388                         break;
389                 case '!':
390                         set = 1;
391                         break;
392                 default:
393                         return 0;
394                 }
395         }
396
397         if(op == '+' || op == '-')
398                 mask &= mode;
399         if(op == '-')
400                 mode = ~mode;
401         if(set)
402                 *pmask = 0;
403
404         *pmask |= mask;
405         *pmode = (*pmode & ~mask) | (mode & mask);
406
407         return 1;
408 }
409
410 static void
411 setopt(Mkaux *mkaux, char *key, char *val)
412 {
413         Opt *o;
414
415         o = mkaux->opt;
416         if(o == nil || mkaux->indent > o->level){
417                 o = emalloc(mkaux, sizeof(*o));
418                 if(o == nil)
419                         longjmp(mkaux->jmp, 1);
420                 if(mkaux->opt){
421                         *o = *mkaux->opt;
422                         if(o->uid)
423                                 o->uid = estrdup(mkaux, o->uid);
424                         if(o->gid)
425                                 o->gid = estrdup(mkaux, o->gid);
426                 }else
427                         memset(o, 0, sizeof(*o));
428                 o->level = mkaux->indent;
429                 o->prev = mkaux->opt;
430                 mkaux->opt = o;
431         } else if(mkaux->indent < o->level)
432                 return;
433         if(strcmp(key, "skip") == 0){
434                 o->skip = regcomp(val);
435         } else if(strcmp(key, "uid") == 0){
436                 free(o->uid); 
437                 o->uid = *val ? estrdup(mkaux, val) : nil;
438         } else if(strcmp(key, "gid") == 0){
439                 free(o->gid); 
440                 o->gid = *val ? estrdup(mkaux, val) : nil;
441         } else if(strcmp(key, "mode") == 0){
442                 if(!parsemode(val, &o->mask, &o->mode))
443                         warn(mkaux, "bad mode specification %s", val);
444         } else {
445                 warn(mkaux, "bad option %s=%s", key, val);
446         }
447 }
448
449 static void
450 popopt(Mkaux *mkaux)
451 {
452         Opt *o;
453
454         while(o = mkaux->opt){
455                 if(o->level <= mkaux->indent)
456                         break;
457                 mkaux->opt = o->prev;
458                 free(o->uid);
459                 free(o->gid);
460                 free(o);
461         }
462 }
463
464 static void
465 freefile(File *f)
466 {
467         if(f->old)
468                 free(f->old);
469         if(f->new)
470                 free(f->new);
471         free(f);
472 }
473
474 /*
475  * skip all files in the proto that
476  * could be in the current dir
477  */
478 static void
479 skipdir(Mkaux *mkaux)
480 {
481         char *p, c;
482         int level;
483
484         if(mkaux->indent < 0)
485                 return;
486         level = mkaux->indent;
487         for(;;){
488                 mkaux->indent = 0;
489                 p = Brdline(mkaux->b, '\n');
490                 mkaux->lineno++;
491                 if(!p){
492                         mkaux->indent = -1;
493                         return;
494                 }
495                 while((c = *p++) != '\n')
496                         if(c == ' ')
497                                 mkaux->indent++;
498                         else if(c == '\t')
499                                 mkaux->indent += 8;
500                         else
501                                 break;
502                 if(mkaux->indent <= level){
503                         popopt(mkaux);
504                         Bseek(mkaux->b, -Blinelen(mkaux->b), 1);
505                         mkaux->lineno--;
506                         return;
507                 }
508         }
509 }
510
511 static File*
512 getfile(Mkaux *mkaux, File *old)
513 {
514         File *f;
515         char *elem;
516         char *p, *s;
517         int c;
518
519         if(mkaux->indent < 0)
520                 return 0;
521 loop:
522         mkaux->indent = 0;
523         p = Brdline(mkaux->b, '\n');
524         mkaux->lineno++;
525         if(!p){
526                 mkaux->indent = -1;
527                 return 0;
528         }
529         while((c = *p++) != '\n')
530                 if(c == ' ')
531                         mkaux->indent++;
532                 else if(c == '\t')
533                         mkaux->indent += 8;
534                 else
535                         break;
536         if(c == '\n' || c == '#')
537                 goto loop;
538         p--;
539         popopt(mkaux);
540         *strchr(p, '\n') = 0;
541         if(s = strchr(p, '=')){
542                 *s++ = 0;
543                 setopt(mkaux, p, s);
544                 goto loop;
545         }else
546                 p[strlen(p)] = '\n';
547         f = emalloc(mkaux, sizeof *f);
548         p = getname(mkaux, p, &elem);
549         if(p == nil)
550                 return nil;
551
552         f->new = mkpath(mkaux, old->new, elem);
553         free(elem);
554         f->elem = utfrrune(f->new, L'/') + 1;
555         p = getmode(mkaux, p, &f->mode);
556         p = getname(mkaux, p, &f->uid); /* LEAK */
557         if(p == nil)
558                 return nil;
559
560         if(!*f->uid)
561                 strcpy(f->uid, "-");
562         p = getname(mkaux, p, &f->gid); /* LEAK */
563         if(p == nil)
564                 return nil;
565
566         if(!*f->gid)
567                 strcpy(f->gid, "-");
568         f->old = getpath(mkaux, p);
569         if(f->old && strcmp(f->old, "-") == 0){
570                 free(f->old);
571                 f->old = 0;
572         }
573         setname(mkaux, &mkaux->oldfile, f);
574
575         return f;
576 }
577
578 static char*
579 getpath(Mkaux *mkaux, char *p)
580 {
581         char *q, *new;
582         int c, n;
583
584         while((c = *p) == ' ' || c == '\t')
585                 p++;
586         q = p;
587         while((c = *q) != '\n' && c != ' ' && c != '\t')
588                 q++;
589         if(q == p)
590                 return 0;
591         n = q - p;
592         new = emalloc(mkaux, n + 1);
593         memcpy(new, p, n);
594         new[n] = 0;
595         return new;
596 }
597
598 static char*
599 getname(Mkaux *mkaux, char *p, char **buf)
600 {
601         char *s, *start;
602         int c;
603
604         while((c = *p) == ' ' || c == '\t')
605                 p++;
606
607         start = p;
608         while((c = *p) != '\n' && c != ' ' && c != '\t')
609                 p++;
610
611         *buf = malloc(p+2-start);       /* +2: need at least 2 bytes; might strcpy "-" into buf */
612         if(*buf == nil)
613                 return nil;
614         memmove(*buf, start, p-start);
615
616         (*buf)[p-start] = '\0';
617
618         if(**buf == '$'){
619                 s = getenv(*buf+1);
620                 if(s == 0){
621                         warn(mkaux, "can't read environment variable %s", *buf+1);
622                         skipdir(mkaux);
623                         free(*buf);
624                         return nil;
625                 }
626                 free(*buf);
627                 *buf = s;
628         }
629         return p;
630 }
631
632 static char*
633 getmode(Mkaux *mkaux, char *p, ulong *xmode)
634 {
635         char *buf, *s;
636         ulong m;
637
638         *xmode = ~0;
639         p = getname(mkaux, p, &buf);
640         if(p == nil)
641                 return nil;
642
643         s = buf;
644         if(!*s || strcmp(s, "-") == 0)
645                 return p;
646         m = 0;
647         if(*s == 'd'){
648                 m |= DMDIR;
649                 s++;
650         }
651         if(*s == 'a'){
652                 m |= DMAPPEND;
653                 s++;
654         }
655         if(*s == 'l'){
656                 m |= DMEXCL;
657                 s++;
658         }
659         if(s[0] < '0' || s[0] > '7'
660         || s[1] < '0' || s[1] > '7'
661         || s[2] < '0' || s[2] > '7'
662         || s[3]){
663                 warn(mkaux, "bad mode specification %s", buf);
664                 free(buf);
665                 return p;
666         }
667         *xmode = m | strtoul(s, 0, 8);
668         free(buf);
669         return p;
670 }
671
672 static void
673 warn(Mkaux *mkaux, char *fmt, ...)
674 {
675         char buf[256];
676         va_list va;
677
678         va_start(va, fmt);
679         vseprint(buf, buf+sizeof(buf), fmt, va);
680         va_end(va);
681
682         if(mkaux->warn)
683                 mkaux->warn(buf, mkaux->a);
684         else
685                 fprint(2, "warning: %s\n", buf);
686 }