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