Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
#include <u.h>
2
#include <libc.h>
3
#include <auth.h>
4
#include <fcall.h>
5
#include <String.h>
6
#include "ftpfs.h"
7
 
8
/* an active fid */
9
typedef struct Fid	Fid;
10
struct Fid
11
{
12
	int	fid;
13
	Node	*node;		/* path to remote file */
14
	int	busy;
15
	Fid	*next;
16
	int	open;
17
};
18
 
19
Fid	*fids;			/* linked list of fids */
20
char	errstring[128];		/* error to return */
21
int	mfd;			/* fd for 9fs */
22
int	messagesize = 4*1024*IOHDRSZ;
23
uchar	mdata[8*1024*IOHDRSZ];
24
uchar	mbuf[8*1024*IOHDRSZ];
25
Fcall	rhdr;
26
Fcall	thdr;
27
int	debug;
28
int	usenlst;
29
int	usetls;
30
char	*ext;
31
int	quiet;
32
int	kapid = -1;
33
int	dying;		/* set when any process decides to die */
34
int	dokeepalive;
35
 
36
char	*rflush(Fid*), *rnop(Fid*), *rversion(Fid*),
37
	*rattach(Fid*), *rclone(Fid*), *rwalk(Fid*),
38
	*rclwalk(Fid*), *ropen(Fid*), *rcreate(Fid*),
39
	*rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
40
	*rremove(Fid*), *rstat(Fid*), *rwstat(Fid*),
41
	*rauth(Fid*);;
42
void	mountinit(char*);
43
void	io(void);
44
int	readpdir(Node*);
45
 
46
char 	*(*fcalls[])(Fid*) = {
47
	[Tflush]	rflush,
48
	[Tversion]	rversion,
49
	[Tattach]	rattach,
50
	[Tauth]		rauth,
51
	[Twalk]		rwalk,
52
	[Topen]		ropen,
53
	[Tcreate]	rcreate,
54
	[Tread]		rread,
55
	[Twrite]	rwrite,
56
	[Tclunk]	rclunk,
57
	[Tremove]	rremove,
58
	[Tstat]		rstat,
59
	[Twstat]	rwstat,
60
};
61
 
62
/* these names are matched as prefixes, so VMS must precede VM */
63
OS oslist[] = {
64
	{ Plan9,	"Plan 9", },
65
	{ Plan9,	"Plan9", },
66
	{ Plan9,	"UNIX Type: L8 Version: Plan 9", },
67
	{ Unix,		"SUN", },
68
	{ Unix,		"UNIX", },
69
	{ VMS,		"VMS", },
70
	{ VM,		"VM", },
71
	{ Tops,		"TOPS", },
72
	{ MVS,		"MVS", },
73
	{ NetWare,	"NetWare", },
74
	{ NetWare,	"NETWARE", },
75
	{ OSĀ½,		"OS/2", },
76
	{ TSO,		"TSO", },
77
	{ NT,		"Windows_NT", },	/* DOS like interface */
78
	{ NT,		"WINDOWS_NT", },	/* Unix like interface */
79
	{ Unknown,	0 },
80
};
81
 
82
char *nouid = "?uid?";
83
 
84
#define S2P(x) (((ulong)(x)) & 0xffffff)
85
 
86
char *nosuchfile = "file does not exist";
87
char *keyspec = "";
88
 
89
void
90
usage(void)
91
{
92
	fprint(2, "ftpfs [-/dqnt] [-a passwd] [-m mountpoint] [-e ext] [-k keyspec] [-o os] [-r root] [net!]address\n");
93
	exits("usage");
94
}
95
 
96
void
97
main(int argc, char *argv[])
98
{
99
	char *mountroot = 0;
100
	char *mountpoint = "/n/ftp";
101
	char *cpassword = 0;
102
	char *cp;
103
	int p[2];
104
	OS *o;
105
 
106
	defos = Unix;
107
	user = strdup(getuser());
108
	usetls = 0;
109
 
110
	ARGBEGIN {
111
	case '/':
112
		mountroot = "/";
113
		break;
114
	case 'a':
115
		cpassword = ARGF();
116
		break;
117
	case 'd':
118
		debug = 1;
119
		break;
120
	case 'k':
121
		keyspec = EARGF(usage());
122
		break;
123
	case 'K':
124
		dokeepalive = 1;
125
		break;
126
	case 'm':
127
		mountpoint = ARGF();
128
		break;
129
	case 'n':
130
		usenlst = 1;
131
		break;
132
	case 'e':
133
		ext = ARGF();
134
		break;
135
	case 't':
136
		usetls = 1;
137
		break;
138
	case 'o':
139
		cp = ARGF();
140
		for(o = oslist; o->os != Unknown; o++)
141
			if(strncmp(cp, o->name, strlen(o->name)) == 0){
142
				defos = o->os;
143
				break;
144
			}
145
		break;
146
	case 'r':
147
		mountroot = ARGF();
148
		break;
149
	case 'q':
150
		quiet = 1;
151
		break;
152
	} ARGEND
153
	if(argc != 1)
154
		usage();
155
 
156
	/* get a pipe to mount and run 9fs on */
157
	if(pipe(p) < 0)
158
		fatal("pipe failed: %r");
159
	mfd = p[0];
160
 
161
	/* initial handshakes with remote side */
162
	hello(*argv);
163
	if(cpassword == 0)
164
		rlogin(*argv, keyspec);
165
	else
166
		clogin("anonymous", cpassword);
167
	preamble(mountroot);
168
 
169
	/* start the 9fs protocol */
170
	switch(rfork(RFPROC|RFNAMEG|RFENVG|RFFDG|RFNOTEG|RFREND)){
171
	case -1:
172
		fatal("fork: %r");
173
	case 0:
174
		/* seal off standard input/output */
175
		close(0);
176
		open("/dev/null", OREAD);
177
		close(1);
178
		open("/dev/null", OWRITE);
179
 
180
		close(p[1]);
181
		fmtinstall('F', fcallfmt); /* debugging */
182
		fmtinstall('D', dirfmt); /* expected by %F */
183
		fmtinstall('M', dirmodefmt); /* expected by %F */
184
		io();
185
		quit();
186
		break;
187
	default:
188
		close(p[0]);
189
		if(mount(p[1], -1, mountpoint, MREPL|MCREATE, "") < 0)
190
			fatal("mount failed: %r");
191
	}
192
	exits(0);
193
}
194
 
195
/*
196
 *  lookup an fid. if not found, create a new one.
197
 */
198
Fid *
199
newfid(int fid)
200
{
201
	Fid *f, *ff;
202
 
203
	ff = 0;
204
	for(f = fids; f; f = f->next){
205
		if(f->fid == fid){
206
			if(f->busy)
207
				return f;
208
			else{
209
				ff = f;
210
				break;
211
			}
212
		} else if(!ff && !f->busy)
213
			ff = f;
214
	}
215
	if(ff == 0){
216
		ff = mallocz(sizeof(*f), 1);
217
		ff->next = fids;
218
		fids = ff;
219
	}
220
	ff->node = nil;
221
	ff->fid = fid;
222
	return ff;
223
}
224
 
225
/*
226
 *  a process that sends keep alive messages to
227
 *  keep the server from shutting down the connection
228
 */
229
int
230
kaproc(void)
231
{
232
	int pid;
233
 
234
	if(!dokeepalive)
235
		return -1;
236
 
237
	switch(pid = rfork(RFPROC|RFMEM)){
238
	case -1:
239
		return -1;
240
	case 0:
241
		break;
242
	default:
243
		return pid;
244
	}
245
 
246
	while(!dying){
247
		sleep(5000);
248
		nop();
249
	}
250
 
251
	_exits(0);
252
	return -1;
253
}
254
 
255
void
256
io(void)
257
{
258
	char *err, buf[ERRMAX];
259
	int n;
260
 
261
	kapid = kaproc();
262
 
263
	while(!dying){
264
		n = read9pmsg(mfd, mdata, messagesize);
265
		if(n <= 0){
266
			errstr(buf, sizeof buf);
267
			if(buf[0]=='\0' || strstr(buf, "hungup"))
268
				exits("");
269
			fatal("mount read: %s\n", buf);
270
		}
271
		if(convM2S(mdata, n, &thdr) == 0)
272
			continue;
273
 
274
		if(debug)
275
			fprint(2, "<-%F\n", &thdr);/**/
276
 
277
		if(!fcalls[thdr.type])
278
			err = "bad fcall type";
279
		else
280
			err = (*fcalls[thdr.type])(newfid(thdr.fid));
281
		if(err){
282
			rhdr.type = Rerror;
283
			rhdr.ename = err;
284
		}else{
285
			rhdr.type = thdr.type + 1;
286
			rhdr.fid = thdr.fid;
287
		}
288
		rhdr.tag = thdr.tag;
289
		if(debug)
290
			fprint(2, "->%F\n", &rhdr);/**/
291
		n = convS2M(&rhdr, mdata, messagesize);
292
		if(write(mfd, mdata, n) != n)
293
			fatal("mount write");
294
	}
295
}
296
 
297
char*
298
rnop(Fid *f)
299
{
300
	USED(f);
301
	return 0;
302
}
303
 
304
char*
305
rversion(Fid*)
306
{
307
	if(thdr.msize > sizeof(mdata))
308
		rhdr.msize = messagesize;
309
	else
310
		rhdr.msize = thdr.msize;
311
	messagesize = thdr.msize;
312
 
313
	if(strncmp(thdr.version, "9P2000", 6) != 0)
314
		return "unknown 9P version";
315
	rhdr.version = "9P2000";
316
	return nil;
317
}
318
 
319
char*
320
rflush(Fid*)
321
{
322
	return 0;
323
}
324
 
325
char*
326
rauth(Fid*)
327
{
328
	return "auth unimplemented";
329
}
330
 
331
char*
332
rattach(Fid *f)
333
{
334
	f->busy = 1;
335
	f->node = remroot;
336
	rhdr.qid = f->node->d->qid;
337
	return 0;
338
}
339
 
340
char*
341
rwalk(Fid *f)
342
{
343
	Node *np;
344
	Fid *nf;
345
	char **elems;
346
	int i, nelems;
347
	char *err;
348
	Node *node;
349
 
350
	/* clone fid */
351
	nf = nil;
352
	if(thdr.newfid != thdr.fid){
353
		nf = newfid(thdr.newfid);
354
		if(nf->busy)
355
			return "newfid in use";
356
		nf->busy = 1;
357
		nf->node = f->node;
358
		f = nf;
359
	}
360
 
361
	err = nil;
362
	elems = thdr.wname;
363
	nelems = thdr.nwname;
364
	node = f->node;
365
	rhdr.nwqid = 0;
366
	if(nelems > 0){
367
		/* walk fid */
368
		for(i=0; i<nelems && i<MAXWELEM; i++){
369
			if((node->d->qid.type & QTDIR) == 0){
370
				err = "not a directory";
371
				break;
372
			}
373
			if(strcmp(elems[i], ".") == 0){
374
   Found:
375
				rhdr.wqid[i] = node->d->qid;
376
				rhdr.nwqid++;
377
				continue;
378
			}
379
			if(strcmp(elems[i], "..") == 0){
380
				node = node->parent;
381
				goto Found;
382
			}
383
			if(strcmp(elems[i], ".flush.ftpfs") == 0){
384
				/* hack to flush the cache */
385
				invalidate(node);
386
				readdir(node);
387
				goto Found;
388
			}
389
 
390
			/* some top level names are special */
391
			if((os == Tops || os == VM || os == VMS) && node == remroot){
392
				np = newtopsdir(elems[i]);
393
				if(np){
394
					node = np;
395
					goto Found;
396
				} else {
397
					err = nosuchfile;
398
					break;
399
				}
400
			}
401
 
402
			/* everything else */
403
			node = extendpath(node, s_copy(elems[i]));
404
			if(ISCACHED(node->parent)){
405
				/* the cache of the parent is good, believe it */
406
				if(!ISVALID(node)){
407
					err = nosuchfile;
408
					break;
409
				}
410
				if(node->parent->chdirunknown || (node->d->mode & DMSYML))
411
					fixsymbolic(node);
412
			} else if(!ISVALID(node)){
413
				/* this isn't a valid node, try cd'ing */
414
				if(changedir(node) == 0){
415
					node->d->qid.type = QTDIR;
416
					node->d->mode |= DMDIR;
417
				}else{
418
					node->d->qid.type = QTFILE;
419
					node->d->mode &= ~DMDIR;
420
				}
421
			}
422
			goto Found;
423
		}
424
		if(i == 0 && err == 0)
425
			err = "file does not exist";
426
	}
427
 
428
	/* clunk a newly cloned fid if the walk didn't succeed */
429
	if(nf != nil && (err != nil || rhdr.nwqid<nelems)){
430
		nf->busy = 0;
431
		nf->fid = 0;
432
	}
433
 
434
	/* if it all worked, point the fid to the enw node */
435
	if(err == nil)
436
		f->node = node;
437
 
438
	return err;
439
}
440
 
441
char *
442
ropen(Fid *f)
443
{
444
	int mode;
445
 
446
	mode = thdr.mode;
447
	if(f->node->d->qid.type & QTDIR)
448
		if(mode != OREAD)
449
			return "permission denied";
450
 
451
	if(mode & OTRUNC){
452
		uncache(f->node);
453
		uncache(f->node->parent);
454
		filedirty(f->node);
455
	} else {
456
		/* read the remote file or directory */
457
		if(!ISCACHED(f->node)){
458
			filefree(f->node);
459
			if(f->node->d->qid.type & QTDIR){
460
				invalidate(f->node);
461
				if(readdir(f->node) < 0)
462
					return nosuchfile;
463
			} else {
464
				if(readfile(f->node) < 0)
465
					return errstring;
466
			}
467
			CACHED(f->node);
468
		}
469
	}
470
 
471
	rhdr.qid = f->node->d->qid;
472
	f->open = 1;
473
	f->node->opens++;
474
	return 0;
475
}
476
 
477
char*
478
rcreate(Fid *f)
479
{
480
	char *name;
481
 
482
	if((f->node->d->qid.type&QTDIR) == 0)
483
		return "not a directory";
484
 
485
	name = thdr.name;
486
	f->node = extendpath(f->node, s_copy(name));
487
	uncache(f->node);
488
	if(thdr.perm & DMDIR){
489
		if(createdir(f->node) < 0)
490
			return "permission denied";
491
	} else
492
		filedirty(f->node);
493
	invalidate(f->node->parent);
494
	uncache(f->node->parent);
495
 
496
	rhdr.qid = f->node->d->qid;
497
	f->open = 1;
498
	f->node->opens++;
499
	return 0;
500
}
501
 
502
char*
503
rread(Fid *f)
504
{
505
	long off;
506
	int n, cnt, rv;
507
	Node *np;
508
 
509
	rhdr.count = 0;
510
	off = thdr.offset;
511
	cnt = thdr.count;
512
	if(cnt > messagesize-IOHDRSZ)
513
		cnt = messagesize-IOHDRSZ;
514
 
515
	if(f->node->d->qid.type & QTDIR){
516
		rv = 0;
517
		for(np = f->node->children; np != nil; np = np->sibs){
518
			if(!ISVALID(np))
519
				continue;
520
			if(off <= BIT16SZ)
521
				break;
522
			n = convD2M(np->d, mbuf, messagesize-IOHDRSZ);
523
			off -= n;
524
		}
525
		for(; rv < cnt && np != nil; np = np->sibs){
526
			if(!ISVALID(np))
527
				continue;
528
			if(np->d->mode & DMSYML)
529
				fixsymbolic(np);
530
			n = convD2M(np->d, mbuf + rv, cnt-rv);
531
			if(n <= BIT16SZ)
532
				break;
533
			rv += n;
534
		}
535
	} else {
536
		/* reread file if it's fallen out of the cache */
537
		if(!ISCACHED(f->node))
538
			if(readfile(f->node) < 0)
539
				return errstring;
540
		CACHED(f->node);
541
		rv = fileread(f->node, (char*)mbuf, off, cnt);
542
		if(rv < 0)
543
			return errstring;
544
	}
545
 
546
	rhdr.data = (char*)mbuf;
547
	rhdr.count = rv;
548
	return 0;
549
}
550
 
551
char*
552
rwrite(Fid *f)
553
{
554
	long off;
555
	int cnt;
556
 
557
	if(f->node->d->qid.type & QTDIR)
558
		return "directories are not writable";
559
 
560
	rhdr.count = 0;
561
	off = thdr.offset;
562
	cnt = thdr.count;
563
	cnt = filewrite(f->node, thdr.data, off, cnt);
564
	if(cnt < 0)
565
		return errstring;
566
	filedirty(f->node);
567
	rhdr.count = cnt;
568
	return 0;
569
}
570
 
571
char *
572
rclunk(Fid *f)
573
{
574
	if(fileisdirty(f->node)){
575
		if(createfile(f->node) < 0)
576
			fprint(2, "ftpfs: couldn't create %s\n", f->node->d->name);
577
		fileclean(f->node);
578
		uncache(f->node);
579
	}
580
	if(f->open){
581
		f->open = 0;
582
		f->node->opens--;
583
	}
584
	f->busy = 0;
585
	return 0;
586
}
587
 
588
/*
589
 *  remove is an implicit clunk
590
 */
591
char *
592
rremove(Fid *f)
593
{
594
	char *e;
595
	e = nil;
596
	if(QTDIR & f->node->d->qid.type){
597
		if(removedir(f->node) < 0)
598
			e = errstring;
599
	} else {
600
		if(removefile(f->node) < 0)
601
			e = errstring;
602
	}
603
	uncache(f->node->parent);
604
	uncache(f->node);
605
	if(e == nil)
606
		INVALID(f->node);
607
	f->busy = 0;
608
	return e;
609
}
610
 
611
char *
612
rstat(Fid *f)
613
{
614
	Node *p;
615
 
616
	p = f->node->parent;
617
	if(!ISCACHED(p)){
618
		invalidate(p);
619
		readdir(p);
620
		CACHED(p);
621
	}
622
	if(!ISVALID(f->node))
623
		return nosuchfile;
624
	if(p->chdirunknown)
625
		fixsymbolic(f->node);
626
	rhdr.nstat = convD2M(f->node->d, mbuf, messagesize-IOHDRSZ);
627
	rhdr.stat = mbuf;
628
	return 0;
629
}
630
 
631
char *
632
rwstat(Fid *f)
633
{
634
	USED(f);
635
	return "wstat not implemented";
636
}
637
 
638
/*
639
 *  print message and die
640
 */
641
void
642
fatal(char *fmt, ...)
643
{
644
	va_list arg;
645
	char buf[8*1024];
646
 
647
	dying = 1;
648
 
649
	va_start(arg, fmt);
650
	vseprint(buf, buf + (sizeof(buf)-1) / sizeof(*buf), fmt, arg);
651
	va_end(arg);
652
 
653
	fprint(2, "ftpfs: %s\n", buf);
654
	if(kapid > 0)
655
		postnote(PNGROUP, kapid, "die");
656
	exits(buf);
657
}
658
 
659
/*
660
 *  like strncpy but make sure there's a terminating null
661
 */
662
void*
663
safecpy(void *to, void *from, int n)
664
{
665
	char *a = ((char*)to) + n - 1;
666
 
667
	strncpy(to, from, n);
668
	*a = 0;
669
	return to;
670
}
671
 
672
/*
673
 *  set the error string
674
 */
675
int
676
seterr(char *fmt, ...)
677
{
678
	va_list arg;
679
 
680
	va_start(arg, fmt);
681
	vsnprint(errstring, sizeof errstring, fmt, arg);
682
	va_end(arg);
683
	return -1;
684
}
685
 
686
/*
687
 *  create a new node
688
 */
689
Node*
690
newnode(Node *parent, String *name)
691
{
692
	Node *np;
693
	static ulong path;
694
	Dir d;
695
 
696
	np = mallocz(sizeof(Node), 1);
697
	if(np == 0)
698
		fatal("out of memory");
699
 
700
	np->children = 0;
701
	if(parent){
702
		np->parent = parent;
703
		np->sibs = parent->children;
704
		parent->children = np;
705
		np->depth = parent->depth + 1;
706
		d.dev = 0;		/* not stat'd */
707
	} else {
708
		/* the root node */
709
		np->parent = np;
710
		np->sibs = 0;
711
		np->depth = 0;
712
		d.dev = 1;
713
	}
714
	np->remname = name;
715
	d.name = s_to_c(name);
716
	d.atime = time(0);
717
	d.mtime = d.atime;
718
	d.uid = nouid;
719
	d.gid = nouid;
720
	d.muid = nouid;
721
	np->fp = 0;
722
	d.qid.path = ++path;
723
	d.qid.vers = 0;
724
	d.qid.type = QTFILE;
725
	d.type = 0;
726
	np->d = reallocdir(&d, 0);
727
 
728
	return np;
729
}
730
 
731
/*
732
 *  walk one down the local mirror of the remote directory tree
733
 */
734
Node*
735
extendpath(Node *parent, String *elem)
736
{
737
	Node *np;
738
 
739
	for(np = parent->children; np; np = np->sibs)
740
		if(strcmp(s_to_c(np->remname), s_to_c(elem)) == 0){
741
			s_free(elem);
742
			return np;
743
		}
744
 
745
	return newnode(parent, elem);
746
}
747
 
748
/*
749
 *  flush the cached file, write it back if it's dirty
750
 */
751
void
752
uncache(Node *np)
753
{
754
	if(fileisdirty(np))
755
		createfile(np);
756
	filefree(np);
757
	UNCACHED(np);
758
}
759
 
760
/*
761
 *  invalidate all children of a node
762
 */
763
void
764
invalidate(Node *node)
765
{
766
	Node *np;
767
 
768
	if(node->opens)
769
		return;		/* don't invalidate something that's open */
770
 
771
	uncachedir(node, 0);
772
 
773
	/* invalidate children */
774
	for(np = node->children; np; np = np->sibs){
775
		if(np->opens)
776
			continue;	/* don't invalidate something that's open */
777
		UNCACHED(np);
778
		invalidate(np);
779
		np->d->dev = 0;
780
	}
781
}
782
 
783
/*
784
 *  make a top level tops-20 directory.  They are automaticly valid.
785
 */
786
Node*
787
newtopsdir(char *name)
788
{
789
	Node *np;
790
 
791
	np = extendpath(remroot, s_copy(name));
792
	if(!ISVALID(np)){
793
		np->d->qid.type = QTDIR;
794
		np->d->atime = time(0);
795
		np->d->mtime = np->d->atime;
796
		np->d->uid = "?uid?";
797
		np->d->gid = "?uid?";
798
		np->d->muid = "?uid?";
799
		np->d->mode = DMDIR|0777;
800
		np->d->length = 0;
801
		np->d = reallocdir(np->d, 1);
802
 
803
		if(changedir(np) >= 0)
804
			VALID(np);
805
	}
806
	return np;
807
}
808
 
809
/*
810
 *  figure out if a symbolic link is to a directory or a file
811
 */
812
void
813
fixsymbolic(Node *node)
814
{
815
	if(changedir(node) == 0){
816
		node->d->mode |= DMDIR;
817
		node->d->qid.type = QTDIR;
818
	} else
819
		node->d->qid.type = QTFILE;
820
	node->d->mode &= ~DMSYML; 
821
}