ref: 9ced712a2ad70557a8ceb89ff3b969b92c85f68c
dir: /sys/src/cmd/cifs/main.c/
#include <u.h> #include <libc.h> #include <fcall.h> #include <thread.h> #include <libsec.h> #include <9p.h> #include "cifs.h" #define max(a,b) (((a) > (b))? (a): (b)) #define min(a,b) (((a) < (b))? (a): (b)) typedef struct Aux Aux; struct Aux { Aux *next; Aux *prev; char *path; /* full path fo file */ Share *sp; /* this share's info */ long expire; /* expiration time of cache */ long off; /* file pos of start of cache */ long end; /* file pos of end of cache */ long mtime; /* last modification time - windows updates it only on close */ char *cache; int fh; /* file handle */ int sh; /* search handle */ long srch; /* find first's internal state */ }; extern int chatty9p; int Checkcase = 1; /* enforce case significance on filenames */ int Dfstout = 100; /* timeout (in ms) for ping of dfs servers (assume they are local) */ int Billtrog = 1; /* enable file owner/group resolution */ int Attachpid; /* pid of proc that attaches (ugh !) */ char *Debug = nil; /* messages */ Qid Root; /* root of remote system */ Share Ipc; /* Share info of IPC$ share */ Session *Sess; /* current session */ int Active = IDLE_TIME; /* secs until next keepalive is sent */ static int Keeppid; /* process ID of keepalive thread */ Share Shares[MAX_SHARES]; /* table of connected shares */ int Nshares = 0; /* number of Shares connected */ Aux *Openfiles = nil; /* linked list of Aux structs */ char *Host = nil; /* host we are connected to */ static char *Ipcname = "IPC$"; #define ptype(x) (((x) & 0xf)) #define pindex(x) (((x) & 0xff0) >> 4) void setup(void) { /* * This is revolting but I cannot see any other way to get * the pid of the server. We need this as Windows doesn't * drop the TCP connection when it closes a connection. * Thus we keepalive() to detect when/if we are thrown off. */ Attachpid = getpid(); procsetname("%s network", Host); } int filetableinfo(Fmt *f) { Aux *ap; char *type; if((ap = Openfiles) != nil) do{ type = "walked"; if(ap->sh != -1) type = "opendir"; if(ap->fh != -1) type = "openfile"; fmtprint(f, "%-9s %s\n", type, ap->path); ap = ap->next; }while(ap != Openfiles); return 0; } Qid mkqid(char *s, int is_dir, long vers, int subtype, long path) { Qid q; union { /* align digest suitably */ uchar digest[SHA1dlen]; uvlong uvl; } u; sha1((uchar *)s, strlen(s), u.digest, nil); q.type = (is_dir)? QTDIR: 0; q.vers = vers; if(subtype){ q.path = *((uvlong *)u.digest) & ~0xfffL; q.path |= ((path & 0xff) << 4); q.path |= (subtype & 0xf); } else q.path = *((uvlong *)u.digest) & ~0xfL; return q; } /* * used only for root dir and shares */ static void V2D(Dir *d, Qid qid, char *name) { memset(d, 0, sizeof(Dir)); d->type = 'C'; d->dev = 1; d->name = estrdup9p(name); d->uid = estrdup9p("bill"); d->muid = estrdup9p("boyd"); d->gid = estrdup9p("trog"); d->mode = 0755 | DMDIR; d->atime = time(nil); d->mtime = d->atime; d->length = 0; d->qid = qid; } static void I2D(Dir *d, Share *sp, char *path, long mtime, FInfo *fi) { char *name; if((name = strrchr(fi->name, '\\')) != nil) name++; else name = fi->name; d->name = estrdup9p(name); d->type = 'C'; d->dev = sp->tid; d->uid = estrdup9p("bill"); d->gid = estrdup9p("trog"); d->muid = estrdup9p("boyd"); d->atime = fi->accessed; if(mtime > fi->written) d->mtime = mtime; else d->mtime = fi->written; if(fi->attribs & ATTR_READONLY) d->mode = 0444; else d->mode = 0666; d->length = fi->size; d->qid = mkqid(path, fi->attribs & ATTR_DIRECTORY, fi->changed, 0, 0); if(fi->attribs & ATTR_DIRECTORY){ d->length = 0; d->mode |= DMDIR|0111; } } static void responderrstr(Req *r) { char e[ERRMAX]; *e = 0; rerrstr(e, sizeof e); respond(r, e); } static char * newpath(char *path, char *name) { char *p, *q; assert((p = strrchr(path, '/')) != nil); if(strcmp(name, "..") == 0){ if(p == path) return estrdup9p("/"); q = emalloc9p((p-path)+1); strecpy(q, q+(p-path)+1, path); return q; } if(strcmp(path, "/") == 0) return smprint("/%s", name); return smprint("%s/%s", path, name); } /* * get the last write time if the file is open - * windows only updates mtime when the file is closed * which is not good enough for acme. */ static long realmtime(char *path) { Aux *a; if((a = Openfiles) == nil) return 0; do{ if(a->fh != -1 && cistrcmp(path, a->path) == 0) return a->mtime; a = a->next; }while(a != Openfiles); return 0; } /* remove "." and ".." from the cache */ static int rmdots(Aux *a, int got) { int i, num; FInfo *fi; num = 0; fi = (FInfo *)a->cache; for(i = 0; i < got; i++){ if(strcmp(fi->name, ".") == 0 || strcmp(fi->name, "..") == 0){ memmove(fi, fi+1, got * sizeof(FInfo)); continue; } fi++; num++; } return num; } static int dirgen(int slot, Dir *d, void *aux) { long off; FInfo *fi; int rc, got; Aux *a = aux; char *npath; int numinf = numinfo(); int slots; slots = 32; /* number of dir entries to fetch at one time */ if(strcmp(a->path, "/") == 0){ if(slot < numinf){ dirgeninfo(slot, d); return 0; } else slot -= numinf; if(slot >= Nshares) return -1; V2D(d, mkqid(Shares[slot].name, 1, 1, Pshare, slot), Shares[slot].name); return 0; } off = slot * sizeof(FInfo); if(off >= a->off && off < a->end && time(nil) < a->expire) goto from_cache; if(off == 0){ npath = smprint("%s/*", mapfile(a->path)); a->sh = T2findfirst(Sess, a->sp, slots, npath, &got, &a->srch, (FInfo *)a->cache); free(npath); if(a->sh == -1) return -1; got = rmdots(a, got); a->off = 0; a->end = got * sizeof(FInfo); goto from_cache; } while(off >= a->end && a->sh != -1){ fi = (FInfo *)(a->cache + (a->end - a->off) - sizeof(FInfo)); a->off = a->end; npath = smprint("%s/%s", mapfile(a->path), fi->name); rc = T2findnext(Sess, a->sp, slots, npath, &got, &a->srch, (FInfo *)a->cache, a->sh); free(npath); if(rc == -1 || got == 0) break; got = rmdots(a, got); a->end = a->off + got * sizeof(FInfo); } a->expire = time(nil) + CACHETIME; if(got < slots){ if(a->sh != -1) CIFSfindclose2(Sess, a->sp, a->sh); a->sh = -1; } from_cache: if(off >= a->end) return -1; fi = (FInfo *)(a->cache + (off - a->off)); npath = smprint("%s/%s", mapfile(a->path), fi->name); I2D(d, a->sp, npath, realmtime(npath), fi); if(Billtrog == 0) upd_names(Sess, a->sp, npath, d); free(npath); return 0; } static void fsattach(Req *r) { Aux *a; static int first = 1; char *spec = r->ifcall.aname; if(first) setup(); if(spec && *spec){ respond(r, "invalid attach specifier"); return; } r->ofcall.qid = mkqid("/", 1, 1, Proot, 0); r->fid->qid = r->ofcall.qid; a = r->fid->aux = emalloc9p(sizeof(Aux)); a->path = estrdup9p("/"); a->sp = nil; a->fh = -1; a->sh = -1; if(Openfiles){ a->prev = Openfiles; a->next = Openfiles->next; Openfiles->next->prev = a; Openfiles->next = a; } else { Openfiles = a; a->next = a; a->prev = a; } respond(r, nil); } static char* fsclone(Fid *ofid, Fid *fid) { Aux *oa = ofid->aux; Aux *a = emalloc9p(sizeof(Aux)); fid->aux = a; a->sh = -1; a->fh = -1; a->sp = oa->sp; a->path = estrdup9p(oa->path); if(Openfiles){ a->prev = Openfiles; a->next = Openfiles->next; Openfiles->next->prev = a; Openfiles->next = a; } else { Openfiles = a; a->next = a; a->prev = a; } return nil; } /* * for some weird reason T2queryall() returns share names * in lower case so we have to do an extra test against * our share table to validate filename case. * * on top of this here (snell & Wilcox) most of our * redirections point to a share of the same name, * but some do not, thus the tail of the filename * returned by T2queryall() is not the same as * the name we wanted. * * We work around this by not validating the names * or files which resolve to share names as they must * be correct, having been enforced in the dfs layer. */ static int validfile(char *found, char *want, char *winpath, Share *sp) { char *share; if(strcmp(want, "..") == 0) return 1; if(strcmp(winpath, "/") == 0){ share = trimshare(sp->name); if(cistrcmp(want, share) == 0) return strcmp(want, share) == 0; /* * OK, a DFS redirection points us from a directory XXX * to a share named YYY. There is no case checking we can * do so we allow either case - it's all we can do. */ return 1; } if(cistrcmp(found, want) != 0) return 0; if(!Checkcase) return 1; if(strcmp(found, want) == 0) return 1; return 0; } static char* fswalk1(Fid *fid, char *name, Qid *qid) { FInfo fi; int rc, n, i; Aux *a = fid->aux; static char e[ERRMAX]; char *p, *npath, *winpath; *e = 0; npath = newpath(a->path, name); if(strcmp(npath, "/") == 0){ /* root dir */ *qid = mkqid("/", 1, 1, Proot, 0); free(a->path); a->path = npath; fid->qid = *qid; return nil; } if(strrchr(npath, '/') == npath){ /* top level dir */ if((n = walkinfo(name)) != -1){ /* info file */ *qid = mkqid(npath, 0, 1, Pinfo, n); } else { /* volume name */ for(i = 0; i < Nshares; i++){ n = strlen(Shares[i].name); if(cistrncmp(npath+1, Shares[i].name, n) != 0) continue; if(Checkcase && strncmp(npath+1, Shares[i].name, n) != 0) continue; if(npath[n+1] != 0 && npath[n+1] != '/') continue; break; } if(i >= Nshares){ free(npath); return "not found"; } a->sp = Shares+i; *qid = mkqid(npath, 1, 1, Pshare, i); } free(a->path); a->path = npath; fid->qid = *qid; return nil; } /* must be a vanilla file or directory */ again: if(mapshare(npath, &a->sp) == -1){ rerrstr(e, sizeof(e)); free(npath); return e; } winpath = mapfile(npath); memset(&fi, 0, sizeof fi); if(Sess->caps & CAP_NT_SMBS) rc = T2queryall(Sess, a->sp, winpath, &fi); else rc = T2querystandard(Sess, a->sp, winpath, &fi); if(rc == -1){ rerrstr(e, sizeof(e)); free(npath); return e; } if((a->sp->options & SMB_SHARE_IS_IN_DFS) != 0 && (fi.attribs & ATTR_REPARSE) != 0){ if(redirect(Sess, a->sp, npath) != -1) goto again; } if((p = strrchr(fi.name, '/')) == nil && (p = strrchr(fi.name, '\\')) == nil) p = fi.name; else p++; if(! validfile(p, name, winpath, a->sp)){ free(npath); return "not found"; } *qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0); a->mtime = realmtime(npath); free(a->path); a->path = npath; fid->qid = *qid; return nil; } static void fsstat(Req *r) { int rc; FInfo fi; Aux *a = r->fid->aux; if(ptype(r->fid->qid.path) == Proot) V2D(&r->d, r->fid->qid, ""); else if(ptype(r->fid->qid.path) == Pinfo) dirgeninfo(pindex(r->fid->qid.path), &r->d); else if(ptype(r->fid->qid.path) == Pshare) V2D(&r->d, r->fid->qid, a->path +1); else{ memset(&fi, 0, sizeof fi); if(Sess->caps & CAP_NT_SMBS) rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi); else rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi); if(rc == -1){ responderrstr(r); return; } I2D(&r->d, a->sp, a->path, a->mtime, &fi); if(Billtrog == 0) upd_names(Sess, a->sp, mapfile(a->path), &r->d); } respond(r, nil); } static int smbcreateopen(Aux *a, char *path, int mode, int perm, int is_create, int is_dir, FInfo *fip) { int rc, action, attrs, access, result; if(is_create && is_dir){ if(CIFScreatedirectory(Sess, a->sp, path) == -1) return -1; return 0; } if(mode & DMAPPEND) { werrstr("filesystem does not support DMAPPEND"); return -1; } if(is_create) action = 0x12; else if(mode & OTRUNC) action = 0x02; else action = 0x01; if(perm & 0222) attrs = ATTR_NORMAL; else attrs = ATTR_NORMAL|ATTR_READONLY; switch (mode & OMASK){ case OREAD: access = 0; break; case OWRITE: access = 1; break; case ORDWR: access = 2; break; case OEXEC: access = 3; break; default: werrstr("%d bad open mode", mode & OMASK); return -1; break; } if((mode & DMEXCL) == 0) access |= 0x10; else access |= 0x40; if((a->fh = CIFS_SMB_opencreate(Sess, a->sp, path, access, attrs, action, &result)) == -1) return -1; if(Sess->caps & CAP_NT_SMBS) rc = T2queryall(Sess, a->sp, mapfile(a->path), fip); else rc = T2querystandard(Sess, a->sp, mapfile(a->path), fip); if(rc == -1){ fprint(2, "internal error: stat of newly open/created file failed\n"); return -1; } if((mode & OEXCL) && (result & 0x8000) == 0){ werrstr("%d bad open mode", mode & OMASK); return -1; } return 0; } /* Uncle Bill, you have a lot to answer for... */ static int ntcreateopen(Aux *a, char *path, int mode, int perm, int is_create, int is_dir, FInfo *fip) { int options, result, attrs, flags, access, action, share; if(mode & DMAPPEND){ werrstr("CIFSopen, DMAPPEND not supported"); return -1; } if(is_create){ if(mode & OEXCL) action = FILE_OPEN; else if(mode & OTRUNC) action = FILE_CREATE; else action = FILE_OVERWRITE_IF; } else { if(mode & OTRUNC) action = FILE_OVERWRITE_IF; else action = FILE_OPEN_IF; } flags = 0; /* FIXME: really not sure */ if(mode & OEXCL) share = FILE_NO_SHARE; else share = FILE_SHARE_ALL; switch (mode & OMASK){ case OREAD: access = GENERIC_READ; break; case OWRITE: access = GENERIC_WRITE; break; case ORDWR: access = GENERIC_ALL; break; case OEXEC: access = GENERIC_EXECUTE; break; default: werrstr("%d bad open mode", mode & OMASK); return -1; break; } if(is_dir){ action = FILE_CREATE; options = FILE_DIRECTORY_FILE; if(perm & 0222) attrs = ATTR_DIRECTORY; else attrs = ATTR_DIRECTORY|ATTR_READONLY; } else { options = FILE_NON_DIRECTORY_FILE; if(perm & 0222) attrs = ATTR_NORMAL; else attrs = ATTR_NORMAL|ATTR_READONLY; } if(mode & ORCLOSE){ options |= FILE_DELETE_ON_CLOSE; attrs |= ATTR_DELETE_ON_CLOSE; } if((a->fh = CIFS_NT_opencreate(Sess, a->sp, path, flags, options, attrs, access, share, action, &result, fip)) == -1) return -1; if((mode & OEXCL) && (result & 0x8000) == 0){ werrstr("%d bad open mode", mode & OMASK); return -1; } return 0; } static void fscreate(Req *r) { FInfo fi; int rc, is_dir; char *npath; Aux *a = r->fid->aux; a->end = a->off = 0; a->cache = emalloc9p(max(Sess->mtu, MTU)); is_dir = (r->ifcall.perm & DMDIR) == DMDIR; npath = smprint("%s/%s", a->path, r->ifcall.name); if(Sess->caps & CAP_NT_SMBS) rc = ntcreateopen(a, mapfile(npath), r->ifcall.mode, r->ifcall.perm, 1, is_dir, &fi); else rc = smbcreateopen(a, mapfile(npath), r->ifcall.mode, r->ifcall.perm, 1, is_dir, &fi); if(rc == -1){ free(npath); responderrstr(r); return; } r->fid->qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0); r->ofcall.qid = r->fid->qid; free(a->path); a->path = npath; respond(r, nil); } static void fsopen(Req *r) { int rc; FInfo fi; Aux *a = r->fid->aux; a->end = a->off = 0; a->cache = emalloc9p(max(Sess->mtu, MTU)); if(ptype(r->fid->qid.path) == Pinfo){ if(makeinfo(pindex(r->fid->qid.path)) != -1) respond(r, nil); else respond(r, "cannot generate info"); return; } if(r->fid->qid.type & QTDIR){ respond(r, nil); return; } if(Sess->caps & CAP_NT_SMBS) rc = ntcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777, 0, 0, &fi); else rc = smbcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777, 0, 0, &fi); if(rc == -1){ responderrstr(r); return; } respond(r, nil); } static void fswrite(Req *r) { vlong n, m, got; Aux *a = r->fid->aux; vlong len = r->ifcall.count; vlong off = r->ifcall.offset; char *buf = r->ifcall.data; got = 0; n = Sess->mtu -OVERHEAD; do{ if(len - got < n) n = len - got; m = CIFSwrite(Sess, a->sp, a->fh, off + got, buf + got, n); if(m != -1) got += m; } while(got < len && m >= n); r->ofcall.count = got; a->mtime = time(nil); if(m == -1) responderrstr(r); else respond(r, nil); } static void fsread(Req *r) { vlong n, m, got; Aux *a = r->fid->aux; char *buf = r->ofcall.data; vlong len = r->ifcall.count; vlong off = r->ifcall.offset; if(ptype(r->fid->qid.path) == Pinfo){ r->ofcall.count = readinfo(pindex(r->fid->qid.path), buf, len, off); respond(r, nil); return; } if(r->fid->qid.type & QTDIR){ dirread9p(r, dirgen, a); respond(r, nil); return; } got = 0; n = Sess->mtu -OVERHEAD; do{ if(len - got < n) n = len - got; m = CIFSread(Sess, a->sp, a->fh, off + got, buf + got, n, len); if(m != -1) got += m; } while(got < len && m >= n); r->ofcall.count = got; if(m == -1) responderrstr(r); else respond(r, nil); } static void fsdestroyfid(Fid *f) { Aux *a = f->aux; if(ptype(f->qid.path) == Pinfo) freeinfo(pindex(f->qid.path)); f->omode = -1; if(! a) return; if(a->fh != -1) if(CIFSclose(Sess, a->sp, a->fh) == -1) fprint(2, "%s: close failed fh=%d %r\n", argv0, a->fh); if(a->sh != -1) if(CIFSfindclose2(Sess, a->sp, a->sh) == -1) fprint(2, "%s: findclose failed sh=%d %r\n", argv0, a->sh); if(a->path) free(a->path); if(a->cache) free(a->cache); if(a == Openfiles) Openfiles = a->next; a->prev->next = a->next; a->next->prev = a->prev; if(a->next == a->prev) Openfiles = nil; if(a) free(a); } int rdonly(Session *s, Share *sp, char *path, int rdonly) { int rc; FInfo fi; if(Sess->caps & CAP_NT_SMBS) rc = T2queryall(s, sp, path, &fi); else rc = T2querystandard(s, sp, path, &fi); if(rc == -1) return -1; if((rdonly && !(fi.attribs & ATTR_READONLY)) || (!rdonly && (fi.attribs & ATTR_READONLY))){ fi.attribs &= ~ATTR_READONLY; fi.attribs |= rdonly? ATTR_READONLY: 0; rc = CIFSsetinfo(s, sp, path, &fi); } return rc; } static void fsremove(Req *r) { int try, rc; char e[ERRMAX]; Aux *ap, *a = r->fid->aux; *e = 0; if(ptype(r->fid->qid.path) == Proot || ptype(r->fid->qid.path) == Pshare){ respond(r, "illegal operation"); return; } /* close all instences of this file/dir */ if((ap = Openfiles) != nil) do{ if(strcmp(ap->path, a->path) == 0){ if(ap->sh != -1) CIFSfindclose2(Sess, ap->sp, ap->sh); ap->sh = -1; if(ap->fh != -1) CIFSclose(Sess, ap->sp, ap->fh); ap->fh = -1; } ap = ap->next; }while(ap != Openfiles); try = 0; again: if(r->fid->qid.type & QTDIR) rc = CIFSdeletedirectory(Sess, a->sp, mapfile(a->path)); else rc = CIFSdeletefile(Sess, a->sp, mapfile(a->path)); rerrstr(e, sizeof(e)); if(rc == -1 && try++ == 0 && strcmp(e, "permission denied") == 0 && rdonly(Sess, a->sp, mapfile(a->path), 0) == 0) goto again; if(rc == -1) responderrstr(r); else respond(r, nil); } static void fswstat(Req *r) { int fh, result, rc; FInfo fi, tmpfi; char *p, *from, *npath; Aux *a = r->fid->aux; if(ptype(r->fid->qid.path) == Proot || ptype(r->fid->qid.path) == Pshare){ respond(r, "illegal operation"); return; } if((r->d.uid && r->d.uid[0]) || (r->d.gid && r->d.gid[0])){ respond(r, "cannot change ownership"); return; } /* * get current info */ if(Sess->caps & CAP_NT_SMBS) rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi); else rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi); if(rc == -1){ werrstr("(query) - %r"); responderrstr(r); return; } /* * always clear the readonly attribute if set, * before trying to set any other fields. * wstat() fails if the file/dir is readonly * and this function is so full of races - who cares about one more? */ rdonly(Sess, a->sp, mapfile(a->path), 0); /* * rename - one piece of joy, renaming open files * is legal (sharing permitting). */ if(r->d.name && r->d.name[0]){ if((p = strrchr(a->path, '/')) == nil){ respond(r, "illegal path"); return; } npath = emalloc9p((p-a->path)+strlen(r->d.name)+2); strecpy(npath, npath+(p- a->path)+2, a->path); strcat(npath, r->d.name); from = estrdup9p(mapfile(a->path)); if(CIFSrename(Sess, a->sp, from, mapfile(npath)) == -1){ werrstr("(rename) - %r"); responderrstr(r); free(npath); free(from); return; } free(from); free(a->path); a->path = npath; } /* * set the files length, do this before setting * the file times as open() will alter them */ if(~r->d.length){ fi.size = r->d.length; if(Sess->caps & CAP_NT_SMBS){ if((fh = CIFS_NT_opencreate(Sess, a->sp, mapfile(a->path), 0, FILE_NON_DIRECTORY_FILE, ATTR_NORMAL, GENERIC_WRITE, FILE_SHARE_ALL, FILE_OPEN_IF, &result, &tmpfi)) == -1){ werrstr("(set length, open) - %r"); responderrstr(r); return; } rc = T2setfilelength(Sess, a->sp, fh, &fi); CIFSclose(Sess, a->sp, fh); if(rc == -1){ werrstr("(set length), set) - %r"); responderrstr(r); return; } } else { if((fh = CIFS_SMB_opencreate(Sess, a->sp, mapfile(a->path), 1, ATTR_NORMAL, 1, &result)) == -1){ werrstr("(set length, open) failed - %r"); responderrstr(r); return; } rc = CIFSwrite(Sess, a->sp, fh, fi.size, 0, 0); CIFSclose(Sess, a->sp, fh); if(rc == -1){ werrstr("(set length, write) - %r"); responderrstr(r); return; } } } /* * This doesn't appear to set length or * attributes, no idea why, so I do those seperately */ if(~r->d.mtime || ~r->d.atime){ if(~r->d.mtime) fi.written = r->d.mtime; if(~r->d.atime) fi.accessed = r->d.atime; if(T2setpathinfo(Sess, a->sp, mapfile(a->path), &fi) == -1){ werrstr("(set path info) - %r"); responderrstr(r); return; } } /* * always update the readonly flag as * we may have cleared it above. */ if(~r->d.mode){ if(r->d.mode & 0222) fi.attribs &= ~ATTR_READONLY; else fi.attribs |= ATTR_READONLY; } if(rdonly(Sess, a->sp, mapfile(a->path), fi.attribs & ATTR_READONLY) == -1){ werrstr("(set info) - %r"); responderrstr(r); return; } /* * Win95 has a broken write-behind cache for metadata * on open files (writes go to the cache, reads bypass * the cache), so we must flush the file. */ if(r->fid->omode != -1 && CIFSflush(Sess, a->sp, a->fh) == -1){ werrstr("(flush) %r"); responderrstr(r); return; } respond(r, nil); } static void fsend(Srv *srv) { int i; USED(srv); for(i = 0; i < Nshares; i++) CIFStreedisconnect(Sess, Shares+i); CIFSlogoff(Sess); postnote(PNPROC, Keeppid, "die"); } Srv fs = { .destroyfid = fsdestroyfid, .attach= fsattach, .open= fsopen, .create= fscreate, .read= fsread, .write= fswrite, .remove= fsremove, .stat= fsstat, .wstat= fswstat, .clone= fsclone, .walk1= fswalk1, .end= fsend, }; void usage(void) { fprint(2, "usage: %s [-d name] [-Dvb] [-a auth-method] [-s srvname] " "[-n called-name] [-k factotum-params] [-m mntpnt] " "host [share...]\n", argv0); exits("usage"); } /* * SMBecho looks like the function to use for keepalives, * sadly the echo packet does not seem to reload the * idle timer in Microsoft's servers. Instead we use * "get file system size" on each share until we get one that succeeds. */ static void keepalive(void) { uvlong tot, fre; int i, slot, rc; procsetname("%s keepalive", Host); rc = 0; slot = 0; do{ sleep(6000); if(Active-- != 0) continue; for(i = 0; i < Nshares; i++){ if((rc = T2fssizeinfo(Sess, &Shares[slot], &tot, &fre)) == 0) break; if(++slot >= Nshares) slot = 0; } }while(rc != -1); postnote(PNPROC, Attachpid, "die"); } static void ding(void *u, char *msg) { USED(u); if(strstr(msg, "alarm") != nil) noted(NCONT); noted(NDFLT); } void dmpkey(char *s, void *v, int n) { int i; unsigned char *p = (unsigned char *)v; print("%s", s); for(i = 0; i < n; i++) print("%02ux ", *p++); print("\n"); } void main(int argc, char **argv) { int i, n, local; long svrtime; char windom[64], cname[64]; char *p, *method, *sysname, *keyp, *mtpt, *svs; static char *sh[1024]; local = 0; *cname = 0; keyp = ""; method = nil; strcpy(windom, "unknown"); mtpt = svs = nil; notify(ding); ARGBEGIN{ case 'a': method = EARGF(autherr()); break; case 'b': Billtrog ^= 1; break; case 'D': chatty9p++; break; case 'd': Debug = EARGF(usage()); break; case 'i': Checkcase = 0; break; case 'k': keyp = EARGF(usage()); break; case 'l': local++; break; case 'm': mtpt = EARGF(usage()); break; case 'n': strncpy(cname, EARGF(usage()), sizeof(cname)); cname[sizeof(cname) - 1] = 0; break; case 's': svs = EARGF(usage()); break; case 't': Dfstout = atoi(EARGF(usage())); break; default: usage(); break; }ARGEND if(argc < 1) usage(); Host = argv[0]; if(mtpt == nil && svs == nil){ if((p = strchr(Host, '!')) != nil) mtpt = smprint("/n/%s", p+1); else mtpt = smprint("/n/%s", Host); } if((sysname = getenv("sysname")) == nil) sysname = "unknown"; if(*cname && (Sess = cifsdial(Host, cname, sysname)) != nil) goto connected; if(calledname(Host, cname) == 0 && (Sess = cifsdial(Host, cname, sysname)) != nil) goto connected; strcpy(cname, Host); if((p = strchr(cname, '!')) != nil) strcpy(cname, p+1); if((Sess = cifsdial(Host, cname, sysname)) != nil || (Sess = cifsdial(Host, "*SMBSERVER", sysname)) != nil) goto connected; sysfatal("%s - cannot dial, %r\n", Host); connected: if(CIFSnegotiate(Sess, &svrtime, windom, sizeof windom, cname, sizeof cname) == -1) sysfatal("%s - cannot negioate common protocol, %r\n", Host); #ifndef DEBUG_MAC Sess->secmode &= ~SECMODE_SIGN_ENABLED; #endif if(local) strcpy(windom, "."); Sess->auth = getauth(method, windom, keyp, Sess->secmode, Sess->chal, Sess->challen); if(CIFSsession(Sess) < 0) sysfatal("session authentication failed, %r\n"); Sess->slip = svrtime - time(nil); Sess->cname = estrdup9p(cname); if(CIFStreeconnect(Sess, cname, Ipcname, &Ipc) == -1) fprint(2, "%s, %r - can't connect\n", Ipcname); Nshares = 0; if(argc == 1){ Share *sip; if((n = RAPshareenum(Sess, &Ipc, &sip)) < 1) sysfatal("can't enumerate shares: %r - specify share " "names on command line\n"); for(i = 0; i < n; i++){ #ifdef NO_HIDDEN_SHARES int l = strlen(sip[i].name); if(l > 1 && sip[i].name[l-1] == '$'){ free(sip[i].name); continue; } #endif memcpy(Shares+Nshares, sip+i, sizeof(Share)); if(CIFStreeconnect(Sess, Sess->cname, Shares[Nshares].name, Shares+Nshares) == -1){ fprint(2, "%s: %s %q - can't connect to share" ", %r\n", argv0, Host, Shares[Nshares].name); free(Shares[Nshares].name); continue; } Nshares++; } free(sip); } else for(i = 1; i < argc; i++){ if(CIFStreeconnect(Sess, Sess->cname, argv[i], Shares+Nshares) == -1){ fprint(2, "%s: %s %q - can't connect to share" ", %r\n", argv0, Host, argv[i]); continue; } Shares[Nshares].name = strlwr(estrdup9p(argv[i])); Nshares++; } if(Nshares == 0) fprint(2, "no available shares\n"); if((i = rfork(RFPROC|RFMEM|RFNOTEG|RFFDG|RFNAMEG)) == 0){ keepalive(); exits(nil); } Keeppid = i; postmountsrv(&fs, svs, mtpt, MREPL|MCREATE); exits(nil); }