19a747e4fSDavid du Colombier #include <u.h>
29a747e4fSDavid du Colombier #include <libc.h>
39a747e4fSDavid du Colombier #include <auth.h>
49a747e4fSDavid du Colombier #include <fcall.h>
59a747e4fSDavid du Colombier #include <String.h>
69a747e4fSDavid du Colombier #include "ftpfs.h"
79a747e4fSDavid du Colombier
89a747e4fSDavid du Colombier /* an active fid */
99a747e4fSDavid du Colombier typedef struct Fid Fid;
109a747e4fSDavid du Colombier struct Fid
119a747e4fSDavid du Colombier {
129a747e4fSDavid du Colombier int fid;
139a747e4fSDavid du Colombier Node *node; /* path to remote file */
149a747e4fSDavid du Colombier int busy;
159a747e4fSDavid du Colombier Fid *next;
169a747e4fSDavid du Colombier int open;
179a747e4fSDavid du Colombier };
189a747e4fSDavid du Colombier
199a747e4fSDavid du Colombier Fid *fids; /* linked list of fids */
209a747e4fSDavid du Colombier char errstring[128]; /* error to return */
219a747e4fSDavid du Colombier int mfd; /* fd for 9fs */
229a747e4fSDavid du Colombier int messagesize = 4*1024*IOHDRSZ;
239a747e4fSDavid du Colombier uchar mdata[8*1024*IOHDRSZ];
249a747e4fSDavid du Colombier uchar mbuf[8*1024*IOHDRSZ];
259a747e4fSDavid du Colombier Fcall rhdr;
269a747e4fSDavid du Colombier Fcall thdr;
279a747e4fSDavid du Colombier int debug;
289a747e4fSDavid du Colombier int usenlst;
291269a55eSDavid du Colombier int usetls;
309a747e4fSDavid du Colombier char *ext;
319a747e4fSDavid du Colombier int quiet;
329a747e4fSDavid du Colombier int kapid = -1;
339a747e4fSDavid du Colombier int dying; /* set when any process decides to die */
349a747e4fSDavid du Colombier int dokeepalive;
359a747e4fSDavid du Colombier
369a747e4fSDavid du Colombier char *rflush(Fid*), *rnop(Fid*), *rversion(Fid*),
379a747e4fSDavid du Colombier *rattach(Fid*), *rclone(Fid*), *rwalk(Fid*),
389a747e4fSDavid du Colombier *rclwalk(Fid*), *ropen(Fid*), *rcreate(Fid*),
399a747e4fSDavid du Colombier *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
409a747e4fSDavid du Colombier *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*),
419a747e4fSDavid du Colombier *rauth(Fid*);;
429a747e4fSDavid du Colombier void mountinit(char*);
439a747e4fSDavid du Colombier void io(void);
449a747e4fSDavid du Colombier int readpdir(Node*);
459a747e4fSDavid du Colombier
469a747e4fSDavid du Colombier char *(*fcalls[])(Fid*) = {
479a747e4fSDavid du Colombier [Tflush] rflush,
489a747e4fSDavid du Colombier [Tversion] rversion,
499a747e4fSDavid du Colombier [Tattach] rattach,
509a747e4fSDavid du Colombier [Tauth] rauth,
519a747e4fSDavid du Colombier [Twalk] rwalk,
529a747e4fSDavid du Colombier [Topen] ropen,
539a747e4fSDavid du Colombier [Tcreate] rcreate,
549a747e4fSDavid du Colombier [Tread] rread,
559a747e4fSDavid du Colombier [Twrite] rwrite,
569a747e4fSDavid du Colombier [Tclunk] rclunk,
579a747e4fSDavid du Colombier [Tremove] rremove,
589a747e4fSDavid du Colombier [Tstat] rstat,
599a747e4fSDavid du Colombier [Twstat] rwstat,
609a747e4fSDavid du Colombier };
619a747e4fSDavid du Colombier
6214cc0f53SDavid du Colombier /* these names are matched as prefixes, so VMS must precede VM */
639a747e4fSDavid du Colombier OS oslist[] = {
649a747e4fSDavid du Colombier { Plan9, "Plan 9", },
659a747e4fSDavid du Colombier { Plan9, "Plan9", },
669a747e4fSDavid du Colombier { Plan9, "UNIX Type: L8 Version: Plan 9", },
679a747e4fSDavid du Colombier { Unix, "SUN", },
689a747e4fSDavid du Colombier { Unix, "UNIX", },
699a747e4fSDavid du Colombier { VMS, "VMS", },
709a747e4fSDavid du Colombier { VM, "VM", },
719a747e4fSDavid du Colombier { Tops, "TOPS", },
729a747e4fSDavid du Colombier { MVS, "MVS", },
739a747e4fSDavid du Colombier { NetWare, "NetWare", },
745d459b5aSDavid du Colombier { NetWare, "NETWARE", },
759a747e4fSDavid du Colombier { OS½, "OS/2", },
769a747e4fSDavid du Colombier { TSO, "TSO", },
779a747e4fSDavid du Colombier { NT, "Windows_NT", }, /* DOS like interface */
789a747e4fSDavid du Colombier { NT, "WINDOWS_NT", }, /* Unix like interface */
799a747e4fSDavid du Colombier { Unknown, 0 },
809a747e4fSDavid du Colombier };
819a747e4fSDavid du Colombier
829a747e4fSDavid du Colombier char *nouid = "?uid?";
839a747e4fSDavid du Colombier
849a747e4fSDavid du Colombier #define S2P(x) (((ulong)(x)) & 0xffffff)
859a747e4fSDavid du Colombier
869a747e4fSDavid du Colombier char *nosuchfile = "file does not exist";
87eaa278a2SDavid du Colombier char *keyspec = "";
889a747e4fSDavid du Colombier
899a747e4fSDavid du Colombier void
usage(void)909a747e4fSDavid du Colombier usage(void)
919a747e4fSDavid du Colombier {
92*887f280bSDavid du Colombier fprint(2, "ftpfs [-/dqnt] [-a passwd] [-m mountpoint] [-e ext] [-k keyspec] [-o os] [-r root] [net!]address\n");
939a747e4fSDavid du Colombier exits("usage");
949a747e4fSDavid du Colombier }
959a747e4fSDavid du Colombier
969a747e4fSDavid du Colombier void
main(int argc,char * argv[])979a747e4fSDavid du Colombier main(int argc, char *argv[])
989a747e4fSDavid du Colombier {
999a747e4fSDavid du Colombier char *mountroot = 0;
1009a747e4fSDavid du Colombier char *mountpoint = "/n/ftp";
1019a747e4fSDavid du Colombier char *cpassword = 0;
1029a747e4fSDavid du Colombier char *cp;
1039a747e4fSDavid du Colombier int p[2];
1049a747e4fSDavid du Colombier OS *o;
1059a747e4fSDavid du Colombier
1069a747e4fSDavid du Colombier defos = Unix;
1079a747e4fSDavid du Colombier user = strdup(getuser());
1081269a55eSDavid du Colombier usetls = 0;
1099a747e4fSDavid du Colombier
1109a747e4fSDavid du Colombier ARGBEGIN {
1119a747e4fSDavid du Colombier case '/':
1129a747e4fSDavid du Colombier mountroot = "/";
1139a747e4fSDavid du Colombier break;
1149a747e4fSDavid du Colombier case 'a':
1159a747e4fSDavid du Colombier cpassword = ARGF();
1169a747e4fSDavid du Colombier break;
1179a747e4fSDavid du Colombier case 'd':
1189a747e4fSDavid du Colombier debug = 1;
1199a747e4fSDavid du Colombier break;
1209a747e4fSDavid du Colombier case 'k':
121eaa278a2SDavid du Colombier keyspec = EARGF(usage());
122eaa278a2SDavid du Colombier break;
123eaa278a2SDavid du Colombier case 'K':
1249a747e4fSDavid du Colombier dokeepalive = 1;
1259a747e4fSDavid du Colombier break;
1269a747e4fSDavid du Colombier case 'm':
1279a747e4fSDavid du Colombier mountpoint = ARGF();
1289a747e4fSDavid du Colombier break;
1299a747e4fSDavid du Colombier case 'n':
1309a747e4fSDavid du Colombier usenlst = 1;
1319a747e4fSDavid du Colombier break;
1329a747e4fSDavid du Colombier case 'e':
1339a747e4fSDavid du Colombier ext = ARGF();
1349a747e4fSDavid du Colombier break;
1351269a55eSDavid du Colombier case 't':
1361269a55eSDavid du Colombier usetls = 1;
1371269a55eSDavid du Colombier break;
1389a747e4fSDavid du Colombier case 'o':
1399a747e4fSDavid du Colombier cp = ARGF();
1409a747e4fSDavid du Colombier for(o = oslist; o->os != Unknown; o++)
1419a747e4fSDavid du Colombier if(strncmp(cp, o->name, strlen(o->name)) == 0){
1429a747e4fSDavid du Colombier defos = o->os;
1439a747e4fSDavid du Colombier break;
1449a747e4fSDavid du Colombier }
1459a747e4fSDavid du Colombier break;
1469a747e4fSDavid du Colombier case 'r':
1479a747e4fSDavid du Colombier mountroot = ARGF();
1489a747e4fSDavid du Colombier break;
1499a747e4fSDavid du Colombier case 'q':
1509a747e4fSDavid du Colombier quiet = 1;
1519a747e4fSDavid du Colombier break;
1529a747e4fSDavid du Colombier } ARGEND
1539a747e4fSDavid du Colombier if(argc != 1)
1549a747e4fSDavid du Colombier usage();
1559a747e4fSDavid du Colombier
1569a747e4fSDavid du Colombier /* get a pipe to mount and run 9fs on */
1579a747e4fSDavid du Colombier if(pipe(p) < 0)
1589a747e4fSDavid du Colombier fatal("pipe failed: %r");
1599a747e4fSDavid du Colombier mfd = p[0];
1609a747e4fSDavid du Colombier
1619a747e4fSDavid du Colombier /* initial handshakes with remote side */
1629a747e4fSDavid du Colombier hello(*argv);
1639a747e4fSDavid du Colombier if(cpassword == 0)
164eaa278a2SDavid du Colombier rlogin(*argv, keyspec);
1659a747e4fSDavid du Colombier else
1669a747e4fSDavid du Colombier clogin("anonymous", cpassword);
1679a747e4fSDavid du Colombier preamble(mountroot);
1689a747e4fSDavid du Colombier
1699a747e4fSDavid du Colombier /* start the 9fs protocol */
1709a747e4fSDavid du Colombier switch(rfork(RFPROC|RFNAMEG|RFENVG|RFFDG|RFNOTEG|RFREND)){
1719a747e4fSDavid du Colombier case -1:
1729a747e4fSDavid du Colombier fatal("fork: %r");
1739a747e4fSDavid du Colombier case 0:
1749a747e4fSDavid du Colombier /* seal off standard input/output */
1759a747e4fSDavid du Colombier close(0);
1769a747e4fSDavid du Colombier open("/dev/null", OREAD);
1779a747e4fSDavid du Colombier close(1);
1789a747e4fSDavid du Colombier open("/dev/null", OWRITE);
1799a747e4fSDavid du Colombier
1809a747e4fSDavid du Colombier close(p[1]);
1819a747e4fSDavid du Colombier fmtinstall('F', fcallfmt); /* debugging */
1824d44ba9bSDavid du Colombier fmtinstall('D', dirfmt); /* expected by %F */
1834d44ba9bSDavid du Colombier fmtinstall('M', dirmodefmt); /* expected by %F */
1849a747e4fSDavid du Colombier io();
1859a747e4fSDavid du Colombier quit();
1869a747e4fSDavid du Colombier break;
1879a747e4fSDavid du Colombier default:
1889a747e4fSDavid du Colombier close(p[0]);
1899a747e4fSDavid du Colombier if(mount(p[1], -1, mountpoint, MREPL|MCREATE, "") < 0)
1909a747e4fSDavid du Colombier fatal("mount failed: %r");
1919a747e4fSDavid du Colombier }
1929a747e4fSDavid du Colombier exits(0);
1939a747e4fSDavid du Colombier }
1949a747e4fSDavid du Colombier
1959a747e4fSDavid du Colombier /*
1969a747e4fSDavid du Colombier * lookup an fid. if not found, create a new one.
1979a747e4fSDavid du Colombier */
1989a747e4fSDavid du Colombier Fid *
newfid(int fid)1999a747e4fSDavid du Colombier newfid(int fid)
2009a747e4fSDavid du Colombier {
2019a747e4fSDavid du Colombier Fid *f, *ff;
2029a747e4fSDavid du Colombier
2039a747e4fSDavid du Colombier ff = 0;
2049a747e4fSDavid du Colombier for(f = fids; f; f = f->next){
2059a747e4fSDavid du Colombier if(f->fid == fid){
2069a747e4fSDavid du Colombier if(f->busy)
2079a747e4fSDavid du Colombier return f;
2089a747e4fSDavid du Colombier else{
2099a747e4fSDavid du Colombier ff = f;
2109a747e4fSDavid du Colombier break;
2119a747e4fSDavid du Colombier }
2129a747e4fSDavid du Colombier } else if(!ff && !f->busy)
2139a747e4fSDavid du Colombier ff = f;
2149a747e4fSDavid du Colombier }
2159a747e4fSDavid du Colombier if(ff == 0){
2169a747e4fSDavid du Colombier ff = mallocz(sizeof(*f), 1);
2179a747e4fSDavid du Colombier ff->next = fids;
2189a747e4fSDavid du Colombier fids = ff;
2199a747e4fSDavid du Colombier }
2209a747e4fSDavid du Colombier ff->node = nil;
2219a747e4fSDavid du Colombier ff->fid = fid;
2229a747e4fSDavid du Colombier return ff;
2239a747e4fSDavid du Colombier }
2249a747e4fSDavid du Colombier
2259a747e4fSDavid du Colombier /*
2269a747e4fSDavid du Colombier * a process that sends keep alive messages to
2279a747e4fSDavid du Colombier * keep the server from shutting down the connection
2289a747e4fSDavid du Colombier */
2299a747e4fSDavid du Colombier int
kaproc(void)2309a747e4fSDavid du Colombier kaproc(void)
2319a747e4fSDavid du Colombier {
2329a747e4fSDavid du Colombier int pid;
2339a747e4fSDavid du Colombier
2349a747e4fSDavid du Colombier if(!dokeepalive)
2359a747e4fSDavid du Colombier return -1;
2369a747e4fSDavid du Colombier
2379a747e4fSDavid du Colombier switch(pid = rfork(RFPROC|RFMEM)){
2389a747e4fSDavid du Colombier case -1:
2399a747e4fSDavid du Colombier return -1;
2409a747e4fSDavid du Colombier case 0:
2419a747e4fSDavid du Colombier break;
2429a747e4fSDavid du Colombier default:
2439a747e4fSDavid du Colombier return pid;
2449a747e4fSDavid du Colombier }
2459a747e4fSDavid du Colombier
2469a747e4fSDavid du Colombier while(!dying){
2479a747e4fSDavid du Colombier sleep(5000);
2489a747e4fSDavid du Colombier nop();
2499a747e4fSDavid du Colombier }
2509a747e4fSDavid du Colombier
2519a747e4fSDavid du Colombier _exits(0);
2529a747e4fSDavid du Colombier return -1;
2539a747e4fSDavid du Colombier }
2549a747e4fSDavid du Colombier
2559a747e4fSDavid du Colombier void
io(void)2569a747e4fSDavid du Colombier io(void)
2579a747e4fSDavid du Colombier {
25824e2e655SDavid du Colombier char *err, buf[ERRMAX];
2599a747e4fSDavid du Colombier int n;
2609a747e4fSDavid du Colombier
2619a747e4fSDavid du Colombier kapid = kaproc();
2629a747e4fSDavid du Colombier
2639a747e4fSDavid du Colombier while(!dying){
2649a747e4fSDavid du Colombier n = read9pmsg(mfd, mdata, messagesize);
26524e2e655SDavid du Colombier if(n <= 0){
26624e2e655SDavid du Colombier errstr(buf, sizeof buf);
26724e2e655SDavid du Colombier if(buf[0]=='\0' || strstr(buf, "hungup"))
26824e2e655SDavid du Colombier exits("");
26924e2e655SDavid du Colombier fatal("mount read: %s\n", buf);
27024e2e655SDavid du Colombier }
2719a747e4fSDavid du Colombier if(convM2S(mdata, n, &thdr) == 0)
2729a747e4fSDavid du Colombier continue;
2739a747e4fSDavid du Colombier
2749a747e4fSDavid du Colombier if(debug)
2759a747e4fSDavid du Colombier fprint(2, "<-%F\n", &thdr);/**/
2769a747e4fSDavid du Colombier
2779a747e4fSDavid du Colombier if(!fcalls[thdr.type])
2789a747e4fSDavid du Colombier err = "bad fcall type";
2799a747e4fSDavid du Colombier else
2809a747e4fSDavid du Colombier err = (*fcalls[thdr.type])(newfid(thdr.fid));
2819a747e4fSDavid du Colombier if(err){
2829a747e4fSDavid du Colombier rhdr.type = Rerror;
2839a747e4fSDavid du Colombier rhdr.ename = err;
2849a747e4fSDavid du Colombier }else{
2859a747e4fSDavid du Colombier rhdr.type = thdr.type + 1;
2869a747e4fSDavid du Colombier rhdr.fid = thdr.fid;
2879a747e4fSDavid du Colombier }
2889a747e4fSDavid du Colombier rhdr.tag = thdr.tag;
2899a747e4fSDavid du Colombier if(debug)
2909a747e4fSDavid du Colombier fprint(2, "->%F\n", &rhdr);/**/
2919a747e4fSDavid du Colombier n = convS2M(&rhdr, mdata, messagesize);
2929a747e4fSDavid du Colombier if(write(mfd, mdata, n) != n)
2939a747e4fSDavid du Colombier fatal("mount write");
2949a747e4fSDavid du Colombier }
2959a747e4fSDavid du Colombier }
2969a747e4fSDavid du Colombier
2979a747e4fSDavid du Colombier char*
rnop(Fid * f)2989a747e4fSDavid du Colombier rnop(Fid *f)
2999a747e4fSDavid du Colombier {
3009a747e4fSDavid du Colombier USED(f);
3019a747e4fSDavid du Colombier return 0;
3029a747e4fSDavid du Colombier }
3039a747e4fSDavid du Colombier
3049a747e4fSDavid du Colombier char*
rversion(Fid *)3059a747e4fSDavid du Colombier rversion(Fid*)
3069a747e4fSDavid du Colombier {
3079a747e4fSDavid du Colombier if(thdr.msize > sizeof(mdata))
3089a747e4fSDavid du Colombier rhdr.msize = messagesize;
3099a747e4fSDavid du Colombier else
3109a747e4fSDavid du Colombier rhdr.msize = thdr.msize;
3119a747e4fSDavid du Colombier messagesize = thdr.msize;
3129a747e4fSDavid du Colombier
3139a747e4fSDavid du Colombier if(strncmp(thdr.version, "9P2000", 6) != 0)
3149a747e4fSDavid du Colombier return "unknown 9P version";
3159a747e4fSDavid du Colombier rhdr.version = "9P2000";
3169a747e4fSDavid du Colombier return nil;
3179a747e4fSDavid du Colombier }
3189a747e4fSDavid du Colombier
3199a747e4fSDavid du Colombier char*
rflush(Fid *)3209a747e4fSDavid du Colombier rflush(Fid*)
3219a747e4fSDavid du Colombier {
3229a747e4fSDavid du Colombier return 0;
3239a747e4fSDavid du Colombier }
3249a747e4fSDavid du Colombier
3259a747e4fSDavid du Colombier char*
rauth(Fid *)3269a747e4fSDavid du Colombier rauth(Fid*)
3279a747e4fSDavid du Colombier {
3289a747e4fSDavid du Colombier return "auth unimplemented";
3299a747e4fSDavid du Colombier }
3309a747e4fSDavid du Colombier
3319a747e4fSDavid du Colombier char*
rattach(Fid * f)3329a747e4fSDavid du Colombier rattach(Fid *f)
3339a747e4fSDavid du Colombier {
3349a747e4fSDavid du Colombier f->busy = 1;
3359a747e4fSDavid du Colombier f->node = remroot;
3369a747e4fSDavid du Colombier rhdr.qid = f->node->d->qid;
3379a747e4fSDavid du Colombier return 0;
3389a747e4fSDavid du Colombier }
3399a747e4fSDavid du Colombier
3409a747e4fSDavid du Colombier char*
rwalk(Fid * f)3419a747e4fSDavid du Colombier rwalk(Fid *f)
3429a747e4fSDavid du Colombier {
3439a747e4fSDavid du Colombier Node *np;
3449a747e4fSDavid du Colombier Fid *nf;
3459a747e4fSDavid du Colombier char **elems;
3469a747e4fSDavid du Colombier int i, nelems;
3479a747e4fSDavid du Colombier char *err;
3489a747e4fSDavid du Colombier Node *node;
3499a747e4fSDavid du Colombier
3509a747e4fSDavid du Colombier /* clone fid */
3519a747e4fSDavid du Colombier nf = nil;
3529a747e4fSDavid du Colombier if(thdr.newfid != thdr.fid){
3539a747e4fSDavid du Colombier nf = newfid(thdr.newfid);
3549a747e4fSDavid du Colombier if(nf->busy)
3559a747e4fSDavid du Colombier return "newfid in use";
3569a747e4fSDavid du Colombier nf->busy = 1;
3579a747e4fSDavid du Colombier nf->node = f->node;
3589a747e4fSDavid du Colombier f = nf;
3599a747e4fSDavid du Colombier }
3609a747e4fSDavid du Colombier
3619a747e4fSDavid du Colombier err = nil;
3629a747e4fSDavid du Colombier elems = thdr.wname;
3639a747e4fSDavid du Colombier nelems = thdr.nwname;
3649a747e4fSDavid du Colombier node = f->node;
3659a747e4fSDavid du Colombier rhdr.nwqid = 0;
3669a747e4fSDavid du Colombier if(nelems > 0){
3679a747e4fSDavid du Colombier /* walk fid */
3689a747e4fSDavid du Colombier for(i=0; i<nelems && i<MAXWELEM; i++){
3699a747e4fSDavid du Colombier if((node->d->qid.type & QTDIR) == 0){
3709a747e4fSDavid du Colombier err = "not a directory";
3719a747e4fSDavid du Colombier break;
3729a747e4fSDavid du Colombier }
3739a747e4fSDavid du Colombier if(strcmp(elems[i], ".") == 0){
3749a747e4fSDavid du Colombier Found:
3759a747e4fSDavid du Colombier rhdr.wqid[i] = node->d->qid;
3769a747e4fSDavid du Colombier rhdr.nwqid++;
3779a747e4fSDavid du Colombier continue;
3789a747e4fSDavid du Colombier }
3799a747e4fSDavid du Colombier if(strcmp(elems[i], "..") == 0){
3809a747e4fSDavid du Colombier node = node->parent;
3819a747e4fSDavid du Colombier goto Found;
3829a747e4fSDavid du Colombier }
3839a747e4fSDavid du Colombier if(strcmp(elems[i], ".flush.ftpfs") == 0){
3849a747e4fSDavid du Colombier /* hack to flush the cache */
3859a747e4fSDavid du Colombier invalidate(node);
3869a747e4fSDavid du Colombier readdir(node);
3879a747e4fSDavid du Colombier goto Found;
3889a747e4fSDavid du Colombier }
3899a747e4fSDavid du Colombier
3909a747e4fSDavid du Colombier /* some top level names are special */
3919a747e4fSDavid du Colombier if((os == Tops || os == VM || os == VMS) && node == remroot){
3929a747e4fSDavid du Colombier np = newtopsdir(elems[i]);
3939a747e4fSDavid du Colombier if(np){
3949a747e4fSDavid du Colombier node = np;
3959a747e4fSDavid du Colombier goto Found;
3969a747e4fSDavid du Colombier } else {
3979a747e4fSDavid du Colombier err = nosuchfile;
3989a747e4fSDavid du Colombier break;
3999a747e4fSDavid du Colombier }
4009a747e4fSDavid du Colombier }
4019a747e4fSDavid du Colombier
4029a747e4fSDavid du Colombier /* everything else */
4039a747e4fSDavid du Colombier node = extendpath(node, s_copy(elems[i]));
4049a747e4fSDavid du Colombier if(ISCACHED(node->parent)){
4059a747e4fSDavid du Colombier /* the cache of the parent is good, believe it */
4069a747e4fSDavid du Colombier if(!ISVALID(node)){
4079a747e4fSDavid du Colombier err = nosuchfile;
4089a747e4fSDavid du Colombier break;
4099a747e4fSDavid du Colombier }
4109a747e4fSDavid du Colombier if(node->parent->chdirunknown || (node->d->mode & DMSYML))
4119a747e4fSDavid du Colombier fixsymbolic(node);
4129a747e4fSDavid du Colombier } else if(!ISVALID(node)){
4139a747e4fSDavid du Colombier /* this isn't a valid node, try cd'ing */
4149a747e4fSDavid du Colombier if(changedir(node) == 0){
4159a747e4fSDavid du Colombier node->d->qid.type = QTDIR;
4169a747e4fSDavid du Colombier node->d->mode |= DMDIR;
4179a747e4fSDavid du Colombier }else{
4189a747e4fSDavid du Colombier node->d->qid.type = QTFILE;
4199a747e4fSDavid du Colombier node->d->mode &= ~DMDIR;
4209a747e4fSDavid du Colombier }
4219a747e4fSDavid du Colombier }
4229a747e4fSDavid du Colombier goto Found;
4239a747e4fSDavid du Colombier }
4249a747e4fSDavid du Colombier if(i == 0 && err == 0)
4259a747e4fSDavid du Colombier err = "file does not exist";
4269a747e4fSDavid du Colombier }
4279a747e4fSDavid du Colombier
4289a747e4fSDavid du Colombier /* clunk a newly cloned fid if the walk didn't succeed */
4299a747e4fSDavid du Colombier if(nf != nil && (err != nil || rhdr.nwqid<nelems)){
4309a747e4fSDavid du Colombier nf->busy = 0;
4319a747e4fSDavid du Colombier nf->fid = 0;
4329a747e4fSDavid du Colombier }
4339a747e4fSDavid du Colombier
4349a747e4fSDavid du Colombier /* if it all worked, point the fid to the enw node */
4359a747e4fSDavid du Colombier if(err == nil)
4369a747e4fSDavid du Colombier f->node = node;
4379a747e4fSDavid du Colombier
4389a747e4fSDavid du Colombier return err;
4399a747e4fSDavid du Colombier }
4409a747e4fSDavid du Colombier
4419a747e4fSDavid du Colombier char *
ropen(Fid * f)4429a747e4fSDavid du Colombier ropen(Fid *f)
4439a747e4fSDavid du Colombier {
4449a747e4fSDavid du Colombier int mode;
4459a747e4fSDavid du Colombier
4469a747e4fSDavid du Colombier mode = thdr.mode;
4479a747e4fSDavid du Colombier if(f->node->d->qid.type & QTDIR)
4489a747e4fSDavid du Colombier if(mode != OREAD)
4499a747e4fSDavid du Colombier return "permission denied";
4509a747e4fSDavid du Colombier
4519a747e4fSDavid du Colombier if(mode & OTRUNC){
4529a747e4fSDavid du Colombier uncache(f->node);
4539a747e4fSDavid du Colombier uncache(f->node->parent);
4549a747e4fSDavid du Colombier filedirty(f->node);
4559a747e4fSDavid du Colombier } else {
4569a747e4fSDavid du Colombier /* read the remote file or directory */
4579a747e4fSDavid du Colombier if(!ISCACHED(f->node)){
4589a747e4fSDavid du Colombier filefree(f->node);
4599a747e4fSDavid du Colombier if(f->node->d->qid.type & QTDIR){
4609a747e4fSDavid du Colombier invalidate(f->node);
4619a747e4fSDavid du Colombier if(readdir(f->node) < 0)
4629a747e4fSDavid du Colombier return nosuchfile;
4639a747e4fSDavid du Colombier } else {
4649a747e4fSDavid du Colombier if(readfile(f->node) < 0)
4659a747e4fSDavid du Colombier return errstring;
4669a747e4fSDavid du Colombier }
4679a747e4fSDavid du Colombier CACHED(f->node);
4689a747e4fSDavid du Colombier }
4699a747e4fSDavid du Colombier }
4709a747e4fSDavid du Colombier
4719a747e4fSDavid du Colombier rhdr.qid = f->node->d->qid;
4729a747e4fSDavid du Colombier f->open = 1;
4739a747e4fSDavid du Colombier f->node->opens++;
4749a747e4fSDavid du Colombier return 0;
4759a747e4fSDavid du Colombier }
4769a747e4fSDavid du Colombier
4779a747e4fSDavid du Colombier char*
rcreate(Fid * f)4789a747e4fSDavid du Colombier rcreate(Fid *f)
4799a747e4fSDavid du Colombier {
4809a747e4fSDavid du Colombier char *name;
4819a747e4fSDavid du Colombier
4829a747e4fSDavid du Colombier if((f->node->d->qid.type&QTDIR) == 0)
4839a747e4fSDavid du Colombier return "not a directory";
4849a747e4fSDavid du Colombier
4859a747e4fSDavid du Colombier name = thdr.name;
4869a747e4fSDavid du Colombier f->node = extendpath(f->node, s_copy(name));
4879a747e4fSDavid du Colombier uncache(f->node);
4885d459b5aSDavid du Colombier if(thdr.perm & DMDIR){
4899a747e4fSDavid du Colombier if(createdir(f->node) < 0)
4909a747e4fSDavid du Colombier return "permission denied";
4919a747e4fSDavid du Colombier } else
4929a747e4fSDavid du Colombier filedirty(f->node);
4939a747e4fSDavid du Colombier invalidate(f->node->parent);
4949a747e4fSDavid du Colombier uncache(f->node->parent);
4959a747e4fSDavid du Colombier
4969a747e4fSDavid du Colombier rhdr.qid = f->node->d->qid;
4979a747e4fSDavid du Colombier f->open = 1;
4989a747e4fSDavid du Colombier f->node->opens++;
4999a747e4fSDavid du Colombier return 0;
5009a747e4fSDavid du Colombier }
5019a747e4fSDavid du Colombier
5029a747e4fSDavid du Colombier char*
rread(Fid * f)5039a747e4fSDavid du Colombier rread(Fid *f)
5049a747e4fSDavid du Colombier {
5059a747e4fSDavid du Colombier long off;
5069a747e4fSDavid du Colombier int n, cnt, rv;
5079a747e4fSDavid du Colombier Node *np;
5089a747e4fSDavid du Colombier
5099a747e4fSDavid du Colombier rhdr.count = 0;
5109a747e4fSDavid du Colombier off = thdr.offset;
5119a747e4fSDavid du Colombier cnt = thdr.count;
5129a747e4fSDavid du Colombier if(cnt > messagesize-IOHDRSZ)
5139a747e4fSDavid du Colombier cnt = messagesize-IOHDRSZ;
5149a747e4fSDavid du Colombier
5159a747e4fSDavid du Colombier if(f->node->d->qid.type & QTDIR){
5169a747e4fSDavid du Colombier rv = 0;
5179a747e4fSDavid du Colombier for(np = f->node->children; np != nil; np = np->sibs){
5189a747e4fSDavid du Colombier if(!ISVALID(np))
5199a747e4fSDavid du Colombier continue;
5209a747e4fSDavid du Colombier if(off <= BIT16SZ)
5219a747e4fSDavid du Colombier break;
5229a747e4fSDavid du Colombier n = convD2M(np->d, mbuf, messagesize-IOHDRSZ);
5239a747e4fSDavid du Colombier off -= n;
5249a747e4fSDavid du Colombier }
5259a747e4fSDavid du Colombier for(; rv < cnt && np != nil; np = np->sibs){
5269a747e4fSDavid du Colombier if(!ISVALID(np))
5279a747e4fSDavid du Colombier continue;
5289a747e4fSDavid du Colombier if(np->d->mode & DMSYML)
5299a747e4fSDavid du Colombier fixsymbolic(np);
5309a747e4fSDavid du Colombier n = convD2M(np->d, mbuf + rv, cnt-rv);
5319a747e4fSDavid du Colombier if(n <= BIT16SZ)
5329a747e4fSDavid du Colombier break;
5339a747e4fSDavid du Colombier rv += n;
5349a747e4fSDavid du Colombier }
5359a747e4fSDavid du Colombier } else {
5369a747e4fSDavid du Colombier /* reread file if it's fallen out of the cache */
5379a747e4fSDavid du Colombier if(!ISCACHED(f->node))
5389a747e4fSDavid du Colombier if(readfile(f->node) < 0)
5399a747e4fSDavid du Colombier return errstring;
5409a747e4fSDavid du Colombier CACHED(f->node);
5419a747e4fSDavid du Colombier rv = fileread(f->node, (char*)mbuf, off, cnt);
5429a747e4fSDavid du Colombier if(rv < 0)
5439a747e4fSDavid du Colombier return errstring;
5449a747e4fSDavid du Colombier }
5459a747e4fSDavid du Colombier
5469a747e4fSDavid du Colombier rhdr.data = (char*)mbuf;
5479a747e4fSDavid du Colombier rhdr.count = rv;
5489a747e4fSDavid du Colombier return 0;
5499a747e4fSDavid du Colombier }
5509a747e4fSDavid du Colombier
5519a747e4fSDavid du Colombier char*
rwrite(Fid * f)5529a747e4fSDavid du Colombier rwrite(Fid *f)
5539a747e4fSDavid du Colombier {
5549a747e4fSDavid du Colombier long off;
5559a747e4fSDavid du Colombier int cnt;
5569a747e4fSDavid du Colombier
5579a747e4fSDavid du Colombier if(f->node->d->qid.type & QTDIR)
5589a747e4fSDavid du Colombier return "directories are not writable";
5599a747e4fSDavid du Colombier
5609a747e4fSDavid du Colombier rhdr.count = 0;
5619a747e4fSDavid du Colombier off = thdr.offset;
5629a747e4fSDavid du Colombier cnt = thdr.count;
5639a747e4fSDavid du Colombier cnt = filewrite(f->node, thdr.data, off, cnt);
5649a747e4fSDavid du Colombier if(cnt < 0)
5659a747e4fSDavid du Colombier return errstring;
5669a747e4fSDavid du Colombier filedirty(f->node);
5679a747e4fSDavid du Colombier rhdr.count = cnt;
5689a747e4fSDavid du Colombier return 0;
5699a747e4fSDavid du Colombier }
5709a747e4fSDavid du Colombier
5719a747e4fSDavid du Colombier char *
rclunk(Fid * f)5729a747e4fSDavid du Colombier rclunk(Fid *f)
5739a747e4fSDavid du Colombier {
5749a747e4fSDavid du Colombier if(fileisdirty(f->node)){
5759a747e4fSDavid du Colombier if(createfile(f->node) < 0)
5769a747e4fSDavid du Colombier fprint(2, "ftpfs: couldn't create %s\n", f->node->d->name);
5779a747e4fSDavid du Colombier fileclean(f->node);
5789a747e4fSDavid du Colombier uncache(f->node);
5799a747e4fSDavid du Colombier }
5809a747e4fSDavid du Colombier if(f->open){
5819a747e4fSDavid du Colombier f->open = 0;
5829a747e4fSDavid du Colombier f->node->opens--;
5839a747e4fSDavid du Colombier }
5849a747e4fSDavid du Colombier f->busy = 0;
5859a747e4fSDavid du Colombier return 0;
5869a747e4fSDavid du Colombier }
5879a747e4fSDavid du Colombier
5889a747e4fSDavid du Colombier /*
5899a747e4fSDavid du Colombier * remove is an implicit clunk
5909a747e4fSDavid du Colombier */
5919a747e4fSDavid du Colombier char *
rremove(Fid * f)5929a747e4fSDavid du Colombier rremove(Fid *f)
5939a747e4fSDavid du Colombier {
594c0eadb1cSDavid du Colombier char *e;
595c0eadb1cSDavid du Colombier e = nil;
5969a747e4fSDavid du Colombier if(QTDIR & f->node->d->qid.type){
5979a747e4fSDavid du Colombier if(removedir(f->node) < 0)
598c0eadb1cSDavid du Colombier e = errstring;
5999a747e4fSDavid du Colombier } else {
6009a747e4fSDavid du Colombier if(removefile(f->node) < 0)
601c0eadb1cSDavid du Colombier e = errstring;
6029a747e4fSDavid du Colombier }
6039a747e4fSDavid du Colombier uncache(f->node->parent);
6049a747e4fSDavid du Colombier uncache(f->node);
605c0eadb1cSDavid du Colombier if(e == nil)
6069a747e4fSDavid du Colombier INVALID(f->node);
6079a747e4fSDavid du Colombier f->busy = 0;
608c0eadb1cSDavid du Colombier return e;
6099a747e4fSDavid du Colombier }
6109a747e4fSDavid du Colombier
6119a747e4fSDavid du Colombier char *
rstat(Fid * f)6129a747e4fSDavid du Colombier rstat(Fid *f)
6139a747e4fSDavid du Colombier {
6149a747e4fSDavid du Colombier Node *p;
6159a747e4fSDavid du Colombier
6169a747e4fSDavid du Colombier p = f->node->parent;
6179a747e4fSDavid du Colombier if(!ISCACHED(p)){
6189a747e4fSDavid du Colombier invalidate(p);
6199a747e4fSDavid du Colombier readdir(p);
6209a747e4fSDavid du Colombier CACHED(p);
6219a747e4fSDavid du Colombier }
6229a747e4fSDavid du Colombier if(!ISVALID(f->node))
6239a747e4fSDavid du Colombier return nosuchfile;
6249a747e4fSDavid du Colombier if(p->chdirunknown)
6259a747e4fSDavid du Colombier fixsymbolic(f->node);
6269a747e4fSDavid du Colombier rhdr.nstat = convD2M(f->node->d, mbuf, messagesize-IOHDRSZ);
6279a747e4fSDavid du Colombier rhdr.stat = mbuf;
6289a747e4fSDavid du Colombier return 0;
6299a747e4fSDavid du Colombier }
6309a747e4fSDavid du Colombier
6319a747e4fSDavid du Colombier char *
rwstat(Fid * f)6329a747e4fSDavid du Colombier rwstat(Fid *f)
6339a747e4fSDavid du Colombier {
6349a747e4fSDavid du Colombier USED(f);
6359a747e4fSDavid du Colombier return "wstat not implemented";
6369a747e4fSDavid du Colombier }
6379a747e4fSDavid du Colombier
6389a747e4fSDavid du Colombier /*
6399a747e4fSDavid du Colombier * print message and die
6409a747e4fSDavid du Colombier */
6419a747e4fSDavid du Colombier void
fatal(char * fmt,...)6429a747e4fSDavid du Colombier fatal(char *fmt, ...)
6439a747e4fSDavid du Colombier {
6449a747e4fSDavid du Colombier va_list arg;
6459a747e4fSDavid du Colombier char buf[8*1024];
6469a747e4fSDavid du Colombier
6479a747e4fSDavid du Colombier dying = 1;
6489a747e4fSDavid du Colombier
6499a747e4fSDavid du Colombier va_start(arg, fmt);
6509a747e4fSDavid du Colombier vseprint(buf, buf + (sizeof(buf)-1) / sizeof(*buf), fmt, arg);
6519a747e4fSDavid du Colombier va_end(arg);
6529a747e4fSDavid du Colombier
6539a747e4fSDavid du Colombier fprint(2, "ftpfs: %s\n", buf);
6549a747e4fSDavid du Colombier if(kapid > 0)
6559a747e4fSDavid du Colombier postnote(PNGROUP, kapid, "die");
6569a747e4fSDavid du Colombier exits(buf);
6579a747e4fSDavid du Colombier }
6589a747e4fSDavid du Colombier
6599a747e4fSDavid du Colombier /*
6609a747e4fSDavid du Colombier * like strncpy but make sure there's a terminating null
6619a747e4fSDavid du Colombier */
6629a747e4fSDavid du Colombier void*
safecpy(void * to,void * from,int n)6639a747e4fSDavid du Colombier safecpy(void *to, void *from, int n)
6649a747e4fSDavid du Colombier {
6659a747e4fSDavid du Colombier char *a = ((char*)to) + n - 1;
6669a747e4fSDavid du Colombier
6679a747e4fSDavid du Colombier strncpy(to, from, n);
6689a747e4fSDavid du Colombier *a = 0;
6699a747e4fSDavid du Colombier return to;
6709a747e4fSDavid du Colombier }
6719a747e4fSDavid du Colombier
6729a747e4fSDavid du Colombier /*
6739a747e4fSDavid du Colombier * set the error string
6749a747e4fSDavid du Colombier */
6759a747e4fSDavid du Colombier int
seterr(char * fmt,...)6769a747e4fSDavid du Colombier seterr(char *fmt, ...)
6779a747e4fSDavid du Colombier {
6789a747e4fSDavid du Colombier va_list arg;
6799a747e4fSDavid du Colombier
6809a747e4fSDavid du Colombier va_start(arg, fmt);
6819a747e4fSDavid du Colombier vsnprint(errstring, sizeof errstring, fmt, arg);
6829a747e4fSDavid du Colombier va_end(arg);
6839a747e4fSDavid du Colombier return -1;
6849a747e4fSDavid du Colombier }
6859a747e4fSDavid du Colombier
6869a747e4fSDavid du Colombier /*
6879a747e4fSDavid du Colombier * create a new node
6889a747e4fSDavid du Colombier */
6899a747e4fSDavid du Colombier Node*
newnode(Node * parent,String * name)6909a747e4fSDavid du Colombier newnode(Node *parent, String *name)
6919a747e4fSDavid du Colombier {
6929a747e4fSDavid du Colombier Node *np;
6939a747e4fSDavid du Colombier static ulong path;
6949a747e4fSDavid du Colombier Dir d;
6959a747e4fSDavid du Colombier
6969a747e4fSDavid du Colombier np = mallocz(sizeof(Node), 1);
6979a747e4fSDavid du Colombier if(np == 0)
6989a747e4fSDavid du Colombier fatal("out of memory");
6999a747e4fSDavid du Colombier
7009a747e4fSDavid du Colombier np->children = 0;
7019a747e4fSDavid du Colombier if(parent){
7029a747e4fSDavid du Colombier np->parent = parent;
7039a747e4fSDavid du Colombier np->sibs = parent->children;
7049a747e4fSDavid du Colombier parent->children = np;
7059a747e4fSDavid du Colombier np->depth = parent->depth + 1;
7069a747e4fSDavid du Colombier d.dev = 0; /* not stat'd */
7079a747e4fSDavid du Colombier } else {
7089a747e4fSDavid du Colombier /* the root node */
7099a747e4fSDavid du Colombier np->parent = np;
7109a747e4fSDavid du Colombier np->sibs = 0;
7119a747e4fSDavid du Colombier np->depth = 0;
7129a747e4fSDavid du Colombier d.dev = 1;
7139a747e4fSDavid du Colombier }
7149a747e4fSDavid du Colombier np->remname = name;
7159a747e4fSDavid du Colombier d.name = s_to_c(name);
7169a747e4fSDavid du Colombier d.atime = time(0);
7179a747e4fSDavid du Colombier d.mtime = d.atime;
7189a747e4fSDavid du Colombier d.uid = nouid;
7199a747e4fSDavid du Colombier d.gid = nouid;
7209a747e4fSDavid du Colombier d.muid = nouid;
7219a747e4fSDavid du Colombier np->fp = 0;
7229a747e4fSDavid du Colombier d.qid.path = ++path;
7239a747e4fSDavid du Colombier d.qid.vers = 0;
7249a747e4fSDavid du Colombier d.qid.type = QTFILE;
7259a747e4fSDavid du Colombier d.type = 0;
7269a747e4fSDavid du Colombier np->d = reallocdir(&d, 0);
7279a747e4fSDavid du Colombier
7289a747e4fSDavid du Colombier return np;
7299a747e4fSDavid du Colombier }
7309a747e4fSDavid du Colombier
7319a747e4fSDavid du Colombier /*
7329a747e4fSDavid du Colombier * walk one down the local mirror of the remote directory tree
7339a747e4fSDavid du Colombier */
7349a747e4fSDavid du Colombier Node*
extendpath(Node * parent,String * elem)7359a747e4fSDavid du Colombier extendpath(Node *parent, String *elem)
7369a747e4fSDavid du Colombier {
7379a747e4fSDavid du Colombier Node *np;
7389a747e4fSDavid du Colombier
7399a747e4fSDavid du Colombier for(np = parent->children; np; np = np->sibs)
7409a747e4fSDavid du Colombier if(strcmp(s_to_c(np->remname), s_to_c(elem)) == 0){
7419a747e4fSDavid du Colombier s_free(elem);
7429a747e4fSDavid du Colombier return np;
7439a747e4fSDavid du Colombier }
7449a747e4fSDavid du Colombier
7459a747e4fSDavid du Colombier return newnode(parent, elem);
7469a747e4fSDavid du Colombier }
7479a747e4fSDavid du Colombier
7489a747e4fSDavid du Colombier /*
7499a747e4fSDavid du Colombier * flush the cached file, write it back if it's dirty
7509a747e4fSDavid du Colombier */
7519a747e4fSDavid du Colombier void
uncache(Node * np)7529a747e4fSDavid du Colombier uncache(Node *np)
7539a747e4fSDavid du Colombier {
7549a747e4fSDavid du Colombier if(fileisdirty(np))
7559a747e4fSDavid du Colombier createfile(np);
7569a747e4fSDavid du Colombier filefree(np);
7579a747e4fSDavid du Colombier UNCACHED(np);
7589a747e4fSDavid du Colombier }
7599a747e4fSDavid du Colombier
7609a747e4fSDavid du Colombier /*
7619a747e4fSDavid du Colombier * invalidate all children of a node
7629a747e4fSDavid du Colombier */
7639a747e4fSDavid du Colombier void
invalidate(Node * node)7649a747e4fSDavid du Colombier invalidate(Node *node)
7659a747e4fSDavid du Colombier {
7669a747e4fSDavid du Colombier Node *np;
7679a747e4fSDavid du Colombier
7689a747e4fSDavid du Colombier if(node->opens)
7699a747e4fSDavid du Colombier return; /* don't invalidate something that's open */
7709a747e4fSDavid du Colombier
7719a747e4fSDavid du Colombier uncachedir(node, 0);
7729a747e4fSDavid du Colombier
7739a747e4fSDavid du Colombier /* invalidate children */
7749a747e4fSDavid du Colombier for(np = node->children; np; np = np->sibs){
7759a747e4fSDavid du Colombier if(np->opens)
7769a747e4fSDavid du Colombier continue; /* don't invalidate something that's open */
7779a747e4fSDavid du Colombier UNCACHED(np);
7789a747e4fSDavid du Colombier invalidate(np);
7799a747e4fSDavid du Colombier np->d->dev = 0;
7809a747e4fSDavid du Colombier }
7819a747e4fSDavid du Colombier }
7829a747e4fSDavid du Colombier
7839a747e4fSDavid du Colombier /*
7849a747e4fSDavid du Colombier * make a top level tops-20 directory. They are automaticly valid.
7859a747e4fSDavid du Colombier */
7869a747e4fSDavid du Colombier Node*
newtopsdir(char * name)7879a747e4fSDavid du Colombier newtopsdir(char *name)
7889a747e4fSDavid du Colombier {
7899a747e4fSDavid du Colombier Node *np;
7909a747e4fSDavid du Colombier
7919a747e4fSDavid du Colombier np = extendpath(remroot, s_copy(name));
7929a747e4fSDavid du Colombier if(!ISVALID(np)){
7939a747e4fSDavid du Colombier np->d->qid.type = QTDIR;
7949a747e4fSDavid du Colombier np->d->atime = time(0);
7959a747e4fSDavid du Colombier np->d->mtime = np->d->atime;
7969a747e4fSDavid du Colombier np->d->uid = "?uid?";
7979a747e4fSDavid du Colombier np->d->gid = "?uid?";
7989a747e4fSDavid du Colombier np->d->muid = "?uid?";
7999a747e4fSDavid du Colombier np->d->mode = DMDIR|0777;
8009a747e4fSDavid du Colombier np->d->length = 0;
8019a747e4fSDavid du Colombier np->d = reallocdir(np->d, 1);
8029a747e4fSDavid du Colombier
8039a747e4fSDavid du Colombier if(changedir(np) >= 0)
8049a747e4fSDavid du Colombier VALID(np);
8059a747e4fSDavid du Colombier }
8069a747e4fSDavid du Colombier return np;
8079a747e4fSDavid du Colombier }
8089a747e4fSDavid du Colombier
8099a747e4fSDavid du Colombier /*
8109a747e4fSDavid du Colombier * figure out if a symbolic link is to a directory or a file
8119a747e4fSDavid du Colombier */
8129a747e4fSDavid du Colombier void
fixsymbolic(Node * node)8139a747e4fSDavid du Colombier fixsymbolic(Node *node)
8149a747e4fSDavid du Colombier {
8159a747e4fSDavid du Colombier if(changedir(node) == 0){
8169a747e4fSDavid du Colombier node->d->mode |= DMDIR;
8179a747e4fSDavid du Colombier node->d->qid.type = QTDIR;
8189a747e4fSDavid du Colombier } else
8199a747e4fSDavid du Colombier node->d->qid.type = QTFILE;
8209a747e4fSDavid du Colombier node->d->mode &= ~DMSYML;
8219a747e4fSDavid du Colombier }
822