]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/cifs/main.c
vblade, cifs, usbuhci: fix parenthesis
[plan9front.git] / sys / src / cmd / cifs / main.c
1 #include <u.h>
2 #include <libc.h>
3 #include <fcall.h>
4 #include <thread.h>
5 #include <libsec.h>
6 #include <9p.h>
7 #include "cifs.h"
8
9 #define max(a,b)        (((a) > (b))? (a): (b))
10 #define min(a,b)        (((a) < (b))? (a): (b))
11
12 typedef struct Aux Aux;
13 struct Aux {
14         Aux     *next;
15         Aux     *prev;
16         char    *path;          /* full path fo file */
17         Share   *sp;            /* this share's info */
18         long    expire;         /* expiration time of cache */
19         long    off;            /* file pos of start of cache */
20         long    end;            /* file pos of end of cache */
21         char    *cache;
22         int     fh;             /* file handle */
23         int     sh;             /* search handle */
24         long    srch;           /* find first's internal state */
25 };
26
27 extern int chatty9p;
28
29 int Checkcase = 1;              /* enforce case significance on filenames */
30 int Dfstout = 100;              /* timeout (in ms) for ping of dfs servers (assume they are local)  */
31 int Billtrog = 1;               /* enable file owner/group resolution */
32 int Attachpid;                  /* pid of proc that attaches (ugh !) */
33 char *Debug = nil;              /* messages */
34 Qid Root;                       /* root of remote system */
35 Share Ipc;                      /* Share info of IPC$ share */
36 Session *Sess;                  /* current session */
37 int Active = IDLE_TIME;         /* secs until next keepalive is sent */
38 static int Keeppid;             /* process ID of keepalive thread */
39 Share Shares[MAX_SHARES];       /* table of connected shares */
40 int Nshares = 0;                /* number of Shares connected */
41 Aux *Auxroot = nil;             /* linked list of Aux structs */
42 char *Host = nil;               /* host we are connected to */
43
44 static char *Ipcname = "IPC$";
45
46 #define ptype(x)        (((x) & 0xf))
47 #define pindex(x)       (((x) & 0xff0) >> 4)
48
49 void
50 setup(void)
51 {
52         int fd;
53         char buf[32];
54
55         /*
56          * This is revolting but I cannot see any other way to get
57          * the pid of the server.  We need this as Windows doesn't
58          * drop the TCP connection when it closes a connection.
59          * Thus we keepalive() to detect when/if we are thrown off.
60          */
61         Attachpid = getpid();
62
63         snprint(buf, sizeof buf, "#p/%d/args", getpid());
64         if((fd = open(buf, OWRITE)) >= 0){
65                 fprint(fd, "%s network", Host);
66                 close(fd);
67         }
68 }
69
70 int
71 filetableinfo(Fmt *f)
72 {
73         Aux *ap;
74         char *type;
75
76         if((ap = Auxroot) != nil)
77                 do{
78                         type = "walked";
79                         if(ap->sh != -1)
80                                 type = "opendir";
81                         if(ap->fh != -1)
82                                 type = "openfile";
83                         fmtprint(f, "%-9s %s\n", type, ap->path);
84                         ap = ap->next;
85                 }while(ap != Auxroot);
86         return 0;
87 }
88
89 Qid
90 mkqid(char *s, int is_dir, long vers, int subtype, long path)
91 {
92         Qid q;
93         union {                         /* align digest suitably */
94                 uchar   digest[SHA1dlen];
95                 uvlong  uvl;
96         } u;
97
98         sha1((uchar *)s, strlen(s), u.digest, nil);
99         q.type = (is_dir)? QTDIR: 0;
100         q.vers = vers;
101         if(subtype){
102                 q.path = *((uvlong *)u.digest) & ~0xfffL;
103                 q.path |= ((path & 0xff) << 4);
104                 q.path |= (subtype & 0xf);
105         }
106         else
107                 q.path = *((uvlong *)u.digest) & ~0xfL;
108         return q;
109 }
110
111 /*
112  * used only for root dir and shares
113  */
114 static void
115 V2D(Dir *d, Qid qid, char *name)
116 {
117         memset(d, 0, sizeof(Dir));
118         d->type = 'C';
119         d->dev = 1;
120         d->name = estrdup9p(name);
121         d->uid = estrdup9p("bill");
122         d->muid = estrdup9p("boyd");
123         d->gid = estrdup9p("trog");
124         d->mode = 0755 | DMDIR;
125         d->atime = time(nil);
126         d->mtime = d->atime;
127         d->length = 0;
128         d->qid = qid;
129 }
130
131 static void
132 I2D(Dir *d, Share *sp, char *path, FInfo *fi)
133 {
134         char *name;
135
136         if((name = strrchr(fi->name, '\\')) != nil)
137                 name++;
138         else
139                 name = fi->name;
140         d->name = estrdup9p(name);
141         d->type = 'C';
142         d->dev = sp->tid;
143         d->uid = estrdup9p("bill");
144         d->gid = estrdup9p("trog");
145         d->muid = estrdup9p("boyd");
146         d->atime = fi->accessed;
147         d->mtime = fi->written;
148
149         if(fi->attribs & ATTR_READONLY)
150                 d->mode = 0444;
151         else
152                 d->mode = 0666;
153
154         d->length = fi->size;
155         d->qid = mkqid(path, fi->attribs & ATTR_DIRECTORY, fi->changed, 0, 0);
156
157         if(fi->attribs & ATTR_DIRECTORY){
158                 d->length = 0;
159                 d->mode |= DMDIR|0111;
160         }
161 }
162
163 static void
164 responderrstr(Req *r)
165 {
166         char e[ERRMAX];
167
168         *e = 0;
169         rerrstr(e, sizeof e);
170         respond(r, e);
171 }
172
173 static char *
174 newpath(char *path, char *name)
175 {
176         char *p, *q;
177
178         assert((p = strrchr(path, '/')) != nil);
179
180         if(strcmp(name, "..") == 0){
181                 if(p == path)
182                         return estrdup9p("/");
183                 q = emalloc9p((p-path)+1);
184                 strecpy(q, q+(p-path)+1, path);
185                 return q;
186         }
187         if(strcmp(path, "/") == 0)
188                 return smprint("/%s", name);
189         return smprint("%s/%s", path, name);
190 }
191
192 static int
193 dirgen(int slot, Dir *d, void *aux)
194 {
195         long off;
196         FInfo *fi;
197         int rc, got;
198         Aux *a = aux;
199         char *npath;
200         int numinf = numinfo();
201         int slots;
202
203         slots = 128;            /* number of dir entries to fetch at one time */
204
205         if(strcmp(a->path, "/") == 0){
206                 if(slot < numinf){
207                         dirgeninfo(slot, d);
208                         return 0;
209                 } else
210                         slot -= numinf;
211
212                 if(slot >= Nshares)
213                         return -1;
214                 V2D(d, mkqid(Shares[slot].name, 1, 1, Pshare, slot),
215                         Shares[slot].name);
216                 return 0;
217         }
218
219         off = slot * sizeof(FInfo);
220         if(off >= a->off && off < a->end && time(nil) < a->expire)
221                 goto from_cache;
222
223         if(off == 0){
224                 fi = (FInfo *)a->cache;
225                 npath = smprint("%s/*", mapfile(a->path));
226                 a->sh = T2findfirst(Sess, a->sp, slots, npath, &got, &a->srch,
227                         (FInfo *)a->cache);
228                 free(npath);
229                 if(a->sh == -1)
230                         return -1;
231
232                 a->off = 0;
233                 a->end = got * sizeof(FInfo);
234
235                 if(got >= 2 && strcmp(fi[0].name, ".") == 0 &&
236                     strcmp(fi[1].name, "..") == 0){
237                         a->end = (got - 2) * sizeof(FInfo);
238                         memmove(a->cache, a->cache + sizeof(FInfo)*2,
239                                 a->end - a->off);
240                 }
241         }
242
243         while(off >= a->end && a->sh != -1){
244                 fi = (FInfo *)(a->cache + (a->end - a->off) - sizeof(FInfo));
245                 a->off = a->end;
246                 npath = smprint("%s/%s", mapfile(a->path), fi->name);
247                 rc = T2findnext(Sess, a->sp, slots, npath,
248                         &got, &a->srch, (FInfo *)a->cache, a->sh);
249                 free(npath);
250                 if(rc == -1 || got == 0)
251                         break;
252                 a->end = a->off + got * sizeof(FInfo);
253         }
254         a->expire = time(nil) + CACHETIME;
255
256         if(got < slots){
257                 if(a->sh != -1)
258                         CIFSfindclose2(Sess, a->sp, a->sh);
259                 a->sh = -1;
260         }
261
262         if(off >= a->end)
263                 return -1;
264
265 from_cache:
266         fi = (FInfo *)(a->cache + (off - a->off));
267         npath = smprint("%s/%s", mapfile(a->path), fi->name);
268         I2D(d, a->sp, npath, fi);
269         if(Billtrog == 0)
270                 upd_names(Sess, a->sp, npath, d);
271         free(npath);
272         return 0;
273 }
274
275 static void
276 fsattach(Req *r)
277 {
278         Aux *a;
279         static int first = 1;
280         char *spec = r->ifcall.aname;
281
282         if(first)
283                 setup();
284
285         if(spec && *spec){
286                 respond(r, "invalid attach specifier");
287                 return;
288         }
289
290         r->ofcall.qid = mkqid("/", 1, 1, Proot, 0);
291         r->fid->qid = r->ofcall.qid;
292
293         a = r->fid->aux = emalloc9p(sizeof(Aux));
294         memset(a, 0, sizeof(Aux));
295         a->path = estrdup9p("/");
296         a->sp = nil;
297         a->fh = -1;
298         a->sh = -1;
299
300         if(Auxroot){
301                 a->prev = Auxroot;
302                 a->next = Auxroot->next;
303                 Auxroot->next->prev = a;
304                 Auxroot->next = a;
305         } else {
306                 Auxroot = a;
307                 a->next = a;
308                 a->prev = a;
309         }
310         respond(r, nil);
311 }
312
313 static char*
314 fsclone(Fid *ofid, Fid *fid)
315 {
316         Aux *oa = ofid->aux;
317         Aux *a = emalloc9p(sizeof(Aux));
318
319         fid->aux = a;
320
321         memset(a, 0, sizeof(Aux));
322         a->sh = -1;
323         a->fh = -1;
324         a->sp = oa->sp;
325         a->path = estrdup9p(oa->path);
326
327         if(Auxroot){
328                 a->prev = Auxroot;
329                 a->next = Auxroot->next;
330                 Auxroot->next->prev = a;
331                 Auxroot->next = a;
332         } else {
333                 Auxroot = a;
334                 a->next = a;
335                 a->prev = a;
336         }
337         return nil;
338 }
339
340 /*
341  * for some weird reason T2queryall() returns share names
342  * in lower case so we have to do an extra test against
343  * our share table to validate filename case.
344  *
345  * on top of this here (snell & Wilcox) most of our
346  * redirections point to a share of the same name,
347  * but some do not, thus the tail of the filename
348  * returned by T2queryall() is not the same as
349  * the name we wanted.
350  *
351  * We work around this by not validating the names
352  * or files which resolve to share names as they must
353  * be correct, having been enforced in the dfs layer.
354  */
355 static int
356 validfile(char *found, char *want, char *winpath, Share *sp)
357 {
358         char *share;
359
360         if(strcmp(want, "..") == 0)
361                 return 1;
362         if(strcmp(winpath, "/") == 0){
363                 share = trimshare(sp->name);
364                 if(cistrcmp(want, share) == 0)
365                         return strcmp(want, share) == 0;
366                 /*
367                  * OK, a DFS redirection points us from a directory XXX
368                  * to a share named YYY.  There is no case checking we can
369                  * do so we allow either case - it's all we can do.
370                  */
371                 return 1;
372         }
373         if(cistrcmp(found, want) != 0)
374                 return 0;
375         if(!Checkcase)
376                 return 1;
377         if(strcmp(found, want) == 0)
378                 return 1;
379         return 0;
380 }
381
382
383 static char*
384 fswalk1(Fid *fid, char *name, Qid *qid)
385 {
386         FInfo fi;
387         int rc, n, i;
388         Aux *a = fid->aux;
389         static char e[ERRMAX];
390         char *p, *npath, *winpath;
391
392         *e = 0;
393         npath = newpath(a->path, name);
394         if(strcmp(npath, "/") == 0){                    /* root dir */
395                 *qid = mkqid("/", 1, 1, Proot, 0);
396                 free(a->path);
397                 a->path = npath;
398                 fid->qid = *qid;
399                 return nil;
400         }
401
402         if(strrchr(npath, '/') == npath){               /* top level dir */
403                 if((n = walkinfo(name)) != -1){         /* info file */
404                         *qid = mkqid(npath, 0, 1, Pinfo, n);
405                 }
406                 else {                                  /* volume name */
407                         for(i = 0; i < Nshares; i++){
408                                 n = strlen(Shares[i].name);
409                                 if(cistrncmp(npath+1, Shares[i].name, n) != 0)
410                                         continue;
411                                 if(Checkcase && strncmp(npath+1, Shares[i].name, n) != 0)
412                                         continue;
413                                 if(npath[n+1] != 0 && npath[n+1] != '/')
414                                         continue;
415                                 break;
416                         }
417                         if(i >= Nshares){
418                                 free(npath);
419                                 return "not found";
420                         }
421                         a->sp = Shares+i;
422                         *qid = mkqid(npath, 1, 1, Pshare, i);
423                 }
424                 free(a->path);
425                 a->path = npath;
426                 fid->qid = *qid;
427                 return nil;
428         }
429
430         /* must be a vanilla file or directory */
431 again:
432         if(mapshare(npath, &a->sp) == -1){
433                 rerrstr(e, sizeof(e));
434                 free(npath);
435                 return e;
436         }
437
438         winpath = mapfile(npath);
439         memset(&fi, 0, sizeof fi);
440         if(Sess->caps & CAP_NT_SMBS)
441                 rc = T2queryall(Sess, a->sp, winpath, &fi);
442         else
443                 rc = T2querystandard(Sess, a->sp, winpath, &fi);
444
445         if(rc == -1){
446                 rerrstr(e, sizeof(e));
447                 free(npath);
448                 return e;
449         }
450
451         if((a->sp->options & SMB_SHARE_IS_IN_DFS) != 0 &&
452             (fi.attribs & ATTR_REPARSE) != 0){
453                 if(redirect(Sess, a->sp, npath) != -1)
454                         goto again;
455         }
456
457         if((p = strrchr(fi.name, '/')) == nil && (p = strrchr(fi.name, '\\')) == nil)
458                 p = fi.name;
459         else
460                 p++;
461
462         if(! validfile(p, name, winpath, a->sp)){
463                 free(npath);
464                 return "not found";
465
466         }
467         *qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0);
468
469         free(a->path);
470         a->path = npath;
471         fid->qid = *qid;
472         return nil;
473 }
474
475 static void
476 fsstat(Req *r)
477 {
478         int rc;
479         FInfo fi;
480         Aux *a = r->fid->aux;
481
482         if(ptype(r->fid->qid.path) == Proot)
483                 V2D(&r->d, r->fid->qid, "");
484         else if(ptype(r->fid->qid.path) == Pinfo)
485                 dirgeninfo(pindex(r->fid->qid.path), &r->d);
486         else if(ptype(r->fid->qid.path) == Pshare)
487                 V2D(&r->d, r->fid->qid, a->path +1);
488         else{
489                 memset(&fi, 0, sizeof fi);
490                 if(Sess->caps & CAP_NT_SMBS)
491                         rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi);
492                 else
493                         rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi);
494                 if(rc == -1){
495                         responderrstr(r);
496                         return;
497                 }
498                 I2D(&r->d, a->sp, a->path, &fi);
499                 if(Billtrog == 0)
500                         upd_names(Sess, a->sp, mapfile(a->path), &r->d);
501         }
502         respond(r, nil);
503 }
504
505 static int
506 smbcreateopen(Aux *a, char *path, int mode, int perm, int is_create,
507         int is_dir, FInfo *fip)
508 {
509         int rc, action, attrs, access, result;
510
511         if(is_create && is_dir){
512                 if(CIFScreatedirectory(Sess, a->sp, path) == -1)
513                         return -1;
514                 return 0;
515         }
516
517         if(mode & DMAPPEND) {
518                 werrstr("filesystem does not support DMAPPEND");
519                 return -1;
520         }
521
522         if(is_create)
523                 action = 0x12;
524         else if(mode & OTRUNC)
525                 action = 0x02;
526         else
527                 action = 0x01;
528
529         if(perm & 0222)
530                 attrs = ATTR_NORMAL;
531         else
532                 attrs = ATTR_NORMAL|ATTR_READONLY;
533
534         switch (mode & OMASK){
535         case OREAD:
536                 access = 0;
537                 break;
538         case OWRITE:
539                 access = 1;
540                 break;
541         case ORDWR:
542                 access = 2;
543                 break;
544         case OEXEC:
545                 access = 3;
546                 break;
547         default:
548                 werrstr("%d bad open mode", mode & OMASK);
549                 return -1;
550                 break;
551         }
552
553         if((mode & DMEXCL) == 0)
554                 access |= 0x10;
555         else
556                 access |= 0x40;
557
558         if((a->fh = CIFS_SMB_opencreate(Sess, a->sp, path, access, attrs,
559             action, &result)) == -1)
560                 return -1;
561
562         if(Sess->caps & CAP_NT_SMBS)
563                 rc = T2queryall(Sess, a->sp, mapfile(a->path), fip);
564         else
565                 rc = T2querystandard(Sess, a->sp, mapfile(a->path), fip);
566         if(rc == -1){
567                 fprint(2, "internal error: stat of newly open/created file failed\n");
568                 return -1;
569         }
570
571         if((mode & OEXCL) && (result & 0x8000) == 0){
572                 werrstr("%d bad open mode", mode & OMASK);
573                 return -1;
574         }
575         return 0;
576 }
577
578 /* Uncle Bill, you have a lot to answer for... */
579 static int
580 ntcreateopen(Aux *a, char *path, int mode, int perm, int is_create,
581         int is_dir, FInfo *fip)
582 {
583         int options, result, attrs, flags, access, action, share;
584
585         if(mode & DMAPPEND){
586                 werrstr("CIFSopen, DMAPPEND not supported");
587                 return -1;
588         }
589
590         if(is_create){
591                 if(mode & OEXCL)
592                         action = FILE_OPEN;
593                 else if(mode & OTRUNC)
594                         action = FILE_CREATE;
595                 else
596                         action = FILE_OVERWRITE_IF;
597         } else {
598                 if(mode & OTRUNC)
599                         action = FILE_OVERWRITE_IF;
600                 else
601                         action = FILE_OPEN_IF;
602         }
603
604         flags = 0;              /* FIXME: really not sure */
605
606         if(mode & OEXCL)
607                 share = FILE_NO_SHARE;
608         else
609                 share = FILE_SHARE_ALL;
610
611         switch (mode & OMASK){
612         case OREAD:
613                 access = GENERIC_READ;
614                 break;
615         case OWRITE:
616                 access = GENERIC_WRITE;
617                 break;
618         case ORDWR:
619                 access = GENERIC_ALL;
620                 break;
621         case OEXEC:
622                 access = GENERIC_EXECUTE;
623                 break;
624         default:
625                 werrstr("%d bad open mode", mode & OMASK);
626                 return -1;
627                 break;
628         }
629
630         if(is_dir){
631                 action = FILE_CREATE;
632                 options = FILE_DIRECTORY_FILE;
633                 if(perm & 0222)
634                         attrs = ATTR_DIRECTORY;
635                 else
636                         attrs = ATTR_DIRECTORY|ATTR_READONLY;
637         } else {
638                 options = FILE_NON_DIRECTORY_FILE;
639                 if(perm & 0222)
640                         attrs = ATTR_NORMAL;
641                 else
642                         attrs = ATTR_NORMAL|ATTR_READONLY;
643         }
644
645         if(mode & ORCLOSE){
646                 options |= FILE_DELETE_ON_CLOSE;
647                 attrs |= ATTR_DELETE_ON_CLOSE;
648         }
649
650         if((a->fh = CIFS_NT_opencreate(Sess, a->sp, path, flags, options,
651             attrs, access, share, action, &result, fip)) == -1)
652                 return -1;
653
654         if((mode & OEXCL) && (result & 0x8000) == 0){
655                 werrstr("%d bad open mode", mode & OMASK);
656                 return -1;
657         }
658
659         return 0;
660 }
661
662 static void
663 fscreate(Req *r)
664 {
665         FInfo fi;
666         int rc, is_dir;
667         char *npath;
668         Aux *a = r->fid->aux;
669
670         a->end = a->off = 0;
671         a->cache = emalloc9p(max(Sess->mtu, MTU));
672
673         is_dir = (r->ifcall.perm & DMDIR) == DMDIR;
674         npath = smprint("%s/%s", a->path, r->ifcall.name);
675
676         if(Sess->caps & CAP_NT_SMBS)
677                 rc = ntcreateopen(a, mapfile(npath), r->ifcall.mode,
678                         r->ifcall.perm, 1, is_dir, &fi);
679         else
680                 rc = smbcreateopen(a, mapfile(npath), r->ifcall.mode,
681                         r->ifcall.perm, 1, is_dir, &fi);
682         if(rc == -1){
683                 free(npath);
684                 responderrstr(r);
685                 return;
686         }
687
688         r->fid->qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0);
689
690         r->ofcall.qid = r->fid->qid;
691         free(a->path);
692         a->path = npath;
693
694         respond(r, nil);
695 }
696
697 static void
698 fsopen(Req *r)
699 {
700         int rc;
701         FInfo fi;
702         Aux *a = r->fid->aux;
703
704         a->end = a->off = 0;
705         a->cache = emalloc9p(max(Sess->mtu, MTU));
706
707         if(ptype(r->fid->qid.path) == Pinfo){
708                 if(makeinfo(pindex(r->fid->qid.path)) != -1)
709                         respond(r, nil);
710                 else
711                         respond(r, "cannot generate info");
712                 return;
713         }
714
715         if(r->fid->qid.type & QTDIR){
716                 respond(r, nil);
717                 return;
718         }
719
720         if(Sess->caps & CAP_NT_SMBS)
721                 rc = ntcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777,
722                         0, 0, &fi);
723         else
724                 rc = smbcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777,
725                         0, 0, &fi);
726         if(rc == -1){
727                 responderrstr(r);
728                 return;
729         }
730         respond(r, nil);
731 }
732
733 static void
734 fswrite(Req *r)
735 {
736         vlong n, m, got;
737         Aux *a = r->fid->aux;
738         vlong len = r->ifcall.count;
739         vlong off = r->ifcall.offset;
740         char *buf = r->ifcall.data;
741
742         got = 0;
743         n = Sess->mtu -OVERHEAD;
744         do{
745                 if(len - got < n)
746                         n = len - got;
747                 m = CIFSwrite(Sess, a->sp, a->fh, off + got, buf + got, n);
748                 if(m != -1)
749                         got += m;
750         } while(got < len && m >= n);
751
752         r->ofcall.count = got;
753         if(m == -1)
754                 responderrstr(r);
755         else
756                 respond(r, nil);
757 }
758
759 static void
760 fsread(Req *r)
761 {
762         vlong n, m, got;
763         Aux *a = r->fid->aux;
764         char *buf = r->ofcall.data;
765         vlong len = r->ifcall.count;
766         vlong off = r->ifcall.offset;
767
768         if(ptype(r->fid->qid.path) == Pinfo){
769                 r->ofcall.count = readinfo(pindex(r->fid->qid.path), buf, len,
770                         off);
771                 respond(r, nil);
772                 return;
773         }
774
775         if(r->fid->qid.type & QTDIR){
776                 dirread9p(r, dirgen, a);
777                 respond(r, nil);
778                 return;
779         }
780
781         got = 0;
782         n = Sess->mtu -OVERHEAD;
783         do{
784                 if(len - got < n)
785                         n = len - got;
786                 m = CIFSread(Sess, a->sp, a->fh, off + got, buf + got, n, len);
787                 if(m != -1)
788                         got += m;
789         } while(got < len && m >= n);
790
791         r->ofcall.count = got;
792         if(m == -1)
793                 responderrstr(r);
794         else
795                 respond(r, nil);
796 }
797
798 static void
799 fsdestroyfid(Fid *f)
800 {
801         Aux *a = f->aux;
802
803         if(ptype(f->qid.path) == Pinfo)
804                 freeinfo(pindex(f->qid.path));
805         f->omode = -1;
806         if(! a)
807                 return;
808         if(a->fh != -1)
809                 if(CIFSclose(Sess, a->sp, a->fh) == -1)
810                         fprint(2, "%s: close failed fh=%d %r\n", argv0, a->fh);
811         if(a->sh != -1)
812                 if(CIFSfindclose2(Sess, a->sp, a->sh) == -1)
813                         fprint(2, "%s: findclose failed sh=%d %r\n",
814                                 argv0, a->sh);
815         if(a->path)
816                 free(a->path);
817         if(a->cache)
818                 free(a->cache);
819
820         if(a == Auxroot)
821                 Auxroot = a->next;
822         a->prev->next = a->next;
823         a->next->prev = a->prev;
824         if(a->next == a->prev)
825                 Auxroot = nil;
826         if(a)
827                 free(a);
828 }
829
830 int
831 rdonly(Session *s, Share *sp, char *path, int rdonly)
832 {
833         int rc;
834         FInfo fi;
835
836         if(Sess->caps & CAP_NT_SMBS)
837                 rc = T2queryall(s, sp, path, &fi);
838         else
839                 rc = T2querystandard(s, sp, path, &fi);
840         if(rc == -1)
841                 return -1;
842
843         if((rdonly && !(fi.attribs & ATTR_READONLY)) ||
844             (!rdonly && (fi.attribs & ATTR_READONLY))){
845                 fi.attribs &= ~ATTR_READONLY;
846                 fi.attribs |= rdonly? ATTR_READONLY: 0;
847                 rc = CIFSsetinfo(s, sp, path, &fi);
848         }
849         return rc;
850 }
851
852 static void
853 fsremove(Req *r)
854 {
855         int try, rc;
856         char e[ERRMAX];
857         Aux *ap, *a = r->fid->aux;
858
859         *e = 0;
860         if(ptype(r->fid->qid.path) == Proot ||
861            ptype(r->fid->qid.path) == Pshare){
862                 respond(r, "illegal operation");
863                 return;
864         }
865
866         /* close all instences of this file/dir */
867         if((ap = Auxroot) != nil)
868                 do{
869                         if(strcmp(ap->path, a->path) == 0){
870                                 if(ap->sh != -1)
871                                         CIFSfindclose2(Sess, ap->sp, ap->sh);
872                                 ap->sh = -1;
873                                 if(ap->fh != -1)
874                                         CIFSclose(Sess, ap->sp, ap->fh);
875                                 ap->fh = -1;
876                         }
877                         ap = ap->next;
878                 }while(ap != Auxroot);
879         try = 0;
880 again:
881         if(r->fid->qid.type & QTDIR)
882                 rc = CIFSdeletedirectory(Sess, a->sp, mapfile(a->path));
883         else
884                 rc = CIFSdeletefile(Sess, a->sp, mapfile(a->path));
885
886         rerrstr(e, sizeof(e));
887         if(rc == -1 && try++ == 0 && strcmp(e, "permission denied") == 0 &&
888             rdonly(Sess, a->sp, mapfile(a->path), 0) == 0)
889                 goto again;
890         if(rc == -1)
891                 responderrstr(r);
892         else
893                 respond(r, nil);
894 }
895
896 static void
897 fswstat(Req *r)
898 {
899         int fh, result, rc;
900         FInfo fi, tmpfi;
901         char *p, *from, *npath;
902         Aux *a = r->fid->aux;
903
904         if(ptype(r->fid->qid.path) == Proot ||
905            ptype(r->fid->qid.path) == Pshare){
906                 respond(r, "illegal operation");
907                 return;
908         }
909
910         if((r->d.uid && r->d.uid[0]) || (r->d.gid && r->d.gid[0])){
911                 respond(r, "cannot change ownership");
912                 return;
913         }
914
915         /*
916          * get current info
917          */
918         if(Sess->caps & CAP_NT_SMBS)
919                 rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi);
920         else
921                 rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi);
922         if(rc == -1){
923                 werrstr("(query) - %r");
924                 responderrstr(r);
925                 return;
926         }
927
928         /*
929          * always clear the readonly attribute if set,
930          * before trying to set any other fields.
931          * wstat() fails if the file/dir is readonly
932          * and this function is so full of races - who cares about one more?
933          */
934         rdonly(Sess, a->sp, mapfile(a->path), 0);
935
936         /*
937          * rename - one piece of joy, renaming open files
938          * is legal (sharing permitting).
939          */
940         if(r->d.name && r->d.name[0]){
941                 if((p = strrchr(a->path, '/')) == nil){
942                         respond(r, "illegal path");
943                         return;
944                 }
945                 npath = emalloc9p((p-a->path)+strlen(r->d.name)+2);
946                 strecpy(npath, npath+(p- a->path)+2, a->path);
947                 strcat(npath, r->d.name);
948
949                 from = estrdup9p(mapfile(a->path));
950                 if(CIFSrename(Sess, a->sp, from, mapfile(npath)) == -1){
951                         werrstr("(rename) - %r");
952                         responderrstr(r);
953                         free(npath);
954                         free(from);
955                         return;
956                 }
957                 free(from);
958                 free(a->path);
959                 a->path = npath;
960         }
961
962         /*
963          * set the files length, do this before setting
964          * the file times as open() will alter them
965          */
966         if(~r->d.length){
967                 fi.size = r->d.length;
968
969                 if(Sess->caps & CAP_NT_SMBS){
970                         if((fh = CIFS_NT_opencreate(Sess, a->sp, mapfile(a->path),
971                             0, FILE_NON_DIRECTORY_FILE,
972                             ATTR_NORMAL, GENERIC_WRITE, FILE_SHARE_ALL,
973                             FILE_OPEN_IF, &result, &tmpfi)) == -1){
974                                 werrstr("(set length, open) - %r");
975                                 responderrstr(r);
976                                 return;
977                         }
978                         rc = T2setfilelength(Sess, a->sp, fh, &fi);
979                         CIFSclose(Sess, a->sp, fh);
980                         if(rc == -1){
981                                 werrstr("(set length), set) - %r");
982                                 responderrstr(r);
983                                 return;
984                         }
985                 } else {
986                         if((fh = CIFS_SMB_opencreate(Sess, a->sp, mapfile(a->path),
987                             1, ATTR_NORMAL, 1, &result)) == -1){
988                                 werrstr("(set length, open) failed - %r");
989                                 responderrstr(r);
990                                 return;
991                         }
992                         rc = CIFSwrite(Sess, a->sp, fh, fi.size, 0, 0);
993                         CIFSclose(Sess, a->sp, fh);
994                         if(rc == -1){
995                                 werrstr("(set length, write) - %r");
996                                 responderrstr(r);
997                                 return;
998                         }
999                 }
1000         }
1001
1002         /*
1003          * This doesn't appear to set length or
1004          * attributes, no idea why, so I do those seperately
1005          */
1006         if(~r->d.mtime || ~r->d.atime){
1007                 if(~r->d.mtime)
1008                         fi.written = r->d.mtime;
1009                 if(~r->d.atime)
1010                         fi.accessed = r->d.atime;
1011                 if(T2setpathinfo(Sess, a->sp, mapfile(a->path), &fi) == -1){
1012                         werrstr("(set path info) - %r");
1013                         responderrstr(r);
1014                         return;
1015                 }
1016         }
1017
1018         /*
1019          * always update the readonly flag as
1020          * we may have cleared it above.
1021          */
1022         if(~r->d.mode){
1023                 if(r->d.mode & 0222)
1024                         fi.attribs &= ~ATTR_READONLY;
1025                 else
1026                         fi.attribs |= ATTR_READONLY;
1027         }
1028         if(rdonly(Sess, a->sp, mapfile(a->path), fi.attribs & ATTR_READONLY) == -1){
1029                 werrstr("(set info) - %r");
1030                 responderrstr(r);
1031                 return;
1032         }
1033
1034         /*
1035          * Win95 has a broken write-behind cache for metadata
1036          * on open files (writes go to the cache, reads bypass
1037          * the cache), so we must flush the file.
1038          */
1039         if(r->fid->omode != -1 && CIFSflush(Sess, a->sp, a->fh) == -1){
1040                 werrstr("(flush) %r");
1041                 responderrstr(r);
1042                 return;
1043         }
1044         respond(r, nil);
1045 }
1046
1047 static void
1048 fsend(Srv *srv)
1049 {
1050         int i;
1051         USED(srv);
1052
1053         for(i = 0; i < Nshares; i++)
1054                 CIFStreedisconnect(Sess, Shares+i);
1055         CIFSlogoff(Sess);
1056         postnote(PNPROC, Keeppid, "die");
1057 }
1058
1059 Srv fs = {
1060         .destroyfid =   fsdestroyfid,
1061         .attach=        fsattach,
1062         .open=          fsopen,
1063         .create=        fscreate,
1064         .read=          fsread,
1065         .write=         fswrite,
1066         .remove=        fsremove,
1067         .stat=          fsstat,
1068         .wstat=         fswstat,
1069         .clone=         fsclone,
1070         .walk1=         fswalk1,
1071         .end=           fsend,
1072 };
1073
1074 void
1075 usage(void)
1076 {
1077         fprint(2, "usage: %s [-d name] [-Dvb] [-a auth-method] [-s srvname] "
1078                 "[-n called-name] [-k factotum-params] [-m mntpnt] "
1079                 "host [share...]\n", argv0);
1080         exits("usage");
1081 }
1082
1083 /*
1084  * SMBecho looks like the function to use for keepalives,
1085  * sadly the echo packet does not seem to reload the
1086  * idle timer in Microsoft's servers.  Instead we use
1087  * "get file system size" on each share until we get one that succeeds.
1088  */
1089 static void
1090 keepalive(void)
1091 {
1092         char buf[32];
1093         uvlong tot, fre;
1094         int fd, i, slot, rc;
1095
1096         snprint(buf, sizeof buf, "#p/%d/args", getpid());
1097         if((fd = open(buf, OWRITE)) >= 0){
1098                 fprint(fd, "%s keepalive", Host);
1099                 close(fd);
1100         }
1101
1102         rc = 0;
1103         slot = 0;
1104         do{
1105                 sleep(6000);
1106                 if(Active-- != 0)
1107                         continue;
1108
1109                 for(i = 0; i < Nshares; i++){
1110                         if((rc = T2fssizeinfo(Sess, &Shares[slot], &tot, &fre)) == 0)
1111                                 break;
1112                         if(++slot >= Nshares)
1113                                 slot = 0;
1114                 }
1115         }while(rc != -1);
1116         postnote(PNPROC, Attachpid, "die");
1117 }
1118
1119
1120 static void
1121 ding(void *u, char *msg)
1122 {
1123         USED(u);
1124         if(strstr(msg, "alarm") != nil)
1125                 noted(NCONT);
1126         noted(NDFLT);
1127 }
1128
1129 void
1130 dmpkey(char *s, void *v, int n)
1131 {
1132         int i;
1133         unsigned char *p = (unsigned char *)v;
1134
1135         print("%s", s);
1136         for(i = 0; i < n; i++)
1137                 print("%02ux ", *p++);
1138         print("\n");
1139 }
1140
1141 void
1142 main(int argc, char **argv)
1143 {
1144         int i, n;
1145         long svrtime;
1146         char windom[64], cname[64];
1147         char *method, *sysname, *keyp, *mtpt, *svs;
1148         static char *sh[1024];
1149
1150         *cname = 0;
1151         keyp = "";
1152         method = nil;
1153         strcpy(windom, "unknown");
1154         mtpt = svs = nil;
1155
1156         notify(ding);
1157
1158         ARGBEGIN{
1159         case 'a':
1160                 method = EARGF(autherr());
1161                 break;
1162         case 'b':
1163                 Billtrog ^= 1;
1164                 break;
1165         case 'D':
1166                 chatty9p++;
1167                 break;
1168         case 'd':
1169                 Debug = EARGF(usage());
1170                 break;
1171         case 'i':
1172                 Checkcase = 0;
1173                 break;
1174         case 'k':
1175                 keyp = EARGF(usage());
1176                 break;
1177         case 'm':
1178                 mtpt = EARGF(usage());
1179                 break;
1180         case 'n':
1181                 strncpy(cname, EARGF(usage()), sizeof(cname));
1182                 cname[sizeof(cname) - 1] = 0;
1183                 break;
1184         case 's':
1185                 svs = EARGF(usage());
1186                 break;
1187         case 't':
1188                 Dfstout = atoi(EARGF(usage()));
1189                 break;
1190         default:
1191                 usage();
1192                 break;
1193         }ARGEND
1194
1195         if(argc < 1)
1196                 usage();
1197
1198         Host = argv[0];
1199
1200         if(mtpt == nil && svs == nil)
1201                 mtpt = smprint("/n/%s", Host);
1202
1203         if((sysname = getenv("sysname")) == nil)
1204                 sysname = "unknown";
1205
1206         if(*cname && (Sess = cifsdial(Host, cname, sysname)) != nil)
1207                 goto connected;
1208
1209         if(calledname(Host, cname) == 0 &&
1210             (Sess = cifsdial(Host, cname, sysname)) != nil)
1211                 goto connected;
1212
1213         strcpy(cname, Host);
1214         if((Sess = cifsdial(Host, Host, sysname)) != nil ||
1215            (Sess = cifsdial(Host, "*SMBSERVER", sysname)) != nil)
1216                 goto connected;
1217
1218         sysfatal("%s - cannot dial, %r\n", Host);
1219 connected:
1220         if(CIFSnegotiate(Sess, &svrtime, windom, sizeof windom, cname, sizeof cname) == -1)
1221                 sysfatal("%s - cannot negioate common protocol, %r\n", Host);
1222
1223 #ifndef DEBUG_MAC
1224         Sess->secmode &= ~SECMODE_SIGN_ENABLED;
1225 #endif
1226
1227         Sess->auth = getauth(method, windom, keyp, Sess->secmode, Sess->chal,
1228                 Sess->challen);
1229
1230         if(CIFSsession(Sess) < 0)
1231                 sysfatal("session authentication failed, %r\n");
1232
1233         Sess->slip = svrtime - time(nil);
1234         Sess->cname = estrdup9p(cname);
1235
1236         if(CIFStreeconnect(Sess, cname, Ipcname, &Ipc) == -1)
1237                 fprint(2, "%s, %r - can't connect\n", Ipcname);
1238
1239         Nshares = 0;
1240         if(argc == 1){
1241                 Share *sip;
1242
1243                 if((n = RAPshareenum(Sess, &Ipc, &sip)) < 1)
1244                         sysfatal("can't enumerate shares: %r - specify share "
1245                                 "names on command line\n");
1246
1247                 for(i = 0; i < n; i++){
1248 #ifdef NO_HIDDEN_SHARES
1249                         int l = strlen(sip[i].name);
1250
1251                         if(l > 1 && sip[i].name[l-1] == '$'){
1252                                 free(sip[i].name);
1253                                 continue;
1254                         }
1255 #endif
1256                         memcpy(Shares+Nshares, sip+i, sizeof(Share));
1257                         if(CIFStreeconnect(Sess, Sess->cname,
1258                             Shares[Nshares].name, Shares+Nshares) == -1){
1259                                 fprint(2, "%s: %s %q - can't connect to share"
1260                                         ", %r\n", argv0, Host, Shares[Nshares].name);
1261                                 free(Shares[Nshares].name);
1262                                 continue;
1263                         }
1264                         Nshares++;
1265                 }
1266                 free(sip);
1267         } else
1268                 for(i = 1; i < argc; i++){
1269                         if(CIFStreeconnect(Sess, Sess->cname, argv[i],
1270                             Shares+Nshares) == -1){
1271                                 fprint(2, "%s: %s %q - can't connect to share"
1272                                         ", %r\n", argv0, Host, argv[i]);
1273                                 continue;
1274                         }
1275                         Shares[Nshares].name = strlwr(estrdup9p(argv[i]));
1276                         Nshares++;
1277                 }
1278
1279         if(Nshares == 0)
1280                 fprint(2, "no available shares\n");
1281
1282         if((Keeppid = rfork(RFPROC|RFMEM|RFNOTEG|RFFDG|RFNAMEG)) == 0){
1283                 keepalive();
1284                 exits(nil);
1285         }
1286         postmountsrv(&fs, svs, mtpt, MREPL|MCREATE);
1287         exits(nil);
1288 }