#include #include #include #include #include "u.h" #include "lib.h" #include "dat.h" #include "fns.h" #include "error.h" typedef struct DIR DIR; typedef struct Ufsinfo Ufsinfo; enum { MAXPATH = 1024, TPATH_ROOT = 0, // "" TPATH_VOLUME = 1, // "C:" TPATH_FILE = 2, // "C:\bla" }; struct DIR { // for FindFileFirst() HANDLE handle; WIN32_FIND_DATA wfd; // for GetLogicalDriveStrings() TCHAR *drivebuf; TCHAR *drivep; // dont move to the next item int keep; }; struct Ufsinfo { int mode; HANDLE fh; DIR* dir; ulong offset; QLock oq; }; static void fspath(Chan *, char *, TCHAR *, int); static ulong fsdirread(Chan*, uchar*, int, ulong); static int fsomode(int); static ulong fsaccess(int); static ulong pathtype(TCHAR *); static int checkvolume(TCHAR *); static ulong unixtime(FILETIME *ft) { vlong t; t = ((vlong)ft->dwHighDateTime << 32)|((vlong)ft->dwLowDateTime); t -= 116444736000000000LL; return ((t<0)?(-1 - (-t - 1)) : t)/10000000; } static uvlong pathhash(TCHAR *p) { uvlong h; h = 0LL; for(; *p; p++) h += *p * 13; return h; } static ulong wfdtodmode(WIN32_FIND_DATA *wfd) { int m; m = DMREAD|DMWRITE|DMEXEC; if(wfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) m |= DMDIR; if(wfd->dwFileAttributes & FILE_ATTRIBUTE_READONLY) m &= ~DMWRITE; m |= (m & 07)<<3; m |= (m & 07)<<6; return m; } static Qid wfdtoqid(TCHAR *path, WIN32_FIND_DATA *wfd) { ulong t; WIN32_FIND_DATA f; Qid q; t = pathtype(path); switch(t){ case TPATH_VOLUME: case TPATH_ROOT: q.type = QTDIR; q.path = pathhash(path); q.vers = 0; break; case TPATH_FILE: if(!wfd){ HANDLE h; if((h = FindFirstFile(path, &f))==INVALID_HANDLE_VALUE) oserror(); FindClose(h); wfd = &f; } q.type = (wfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? QTDIR : QTFILE; q.path = pathhash(path); q.vers = unixtime(&wfd->ftLastWriteTime); break; } return q; } static void wfdtodir(TCHAR *path, Dir *d, WIN32_FIND_DATA *wfd) { WIN32_FIND_DATA f; d->uid = "nul"; d->gid = "nul"; d->muid = "nul"; switch(pathtype(path)){ case TPATH_VOLUME: case TPATH_ROOT: wfd = nil; d->mode = 0777 | DMDIR; d->atime = 0; d->mtime = 0; d->length = 0; break; case TPATH_FILE: if(!wfd){ HANDLE h; if((h = FindFirstFile(path, &f))==INVALID_HANDLE_VALUE) oserror(); FindClose(h); wfd = &f; } d->mode = wfdtodmode(wfd); d->atime = unixtime(&wfd->ftLastAccessTime); d->mtime = unixtime(&wfd->ftLastWriteTime); d->length = ((uvlong)wfd->nFileSizeHigh << 32)|((uvlong)wfd->nFileSizeLow); break; } d->qid = wfdtoqid(path, wfd); } /* clumsy hack, but not worse than the Path stuff in the last one */ static char* uc2name(Chan *c) { char *s; if(c->name == nil) return ""; s = c2name(c); if(s[0]=='#' && s[1]=='U') s += 2; if(*s=='/') s++; return s; } static char* lastelem(Chan *c) { char *s, *t; s = uc2name(c); if((t = strrchr(s, '/')) == nil) return s; if(t[1] == 0) return t; return t+1; } static ulong pathtype(TCHAR *path) { int n; n = wcslen(path); if(n < 2){ return TPATH_ROOT; } if(n==2){ return TPATH_VOLUME; } return TPATH_FILE; } static int checkvolume(TCHAR *path) { TCHAR vol[MAX_PATH]; TCHAR volname[MAX_PATH]; TCHAR fsysname[MAX_PATH]; DWORD complen; DWORD flags; wcsncpy(vol, path, MAX_PATH); #ifdef UNICODE wcsncat(vol, L"\\", MAXPATH); #else wcsncat(vol, "\\", MAXPATH); #endif if(!GetVolumeInformation( vol, volname, MAX_PATH, NULL, &complen, &flags, fsysname, MAX_PATH)){ return 0; } return 1; } static int getfileowner(TCHAR *path, char *owner, int nowner) { strncpy(owner, "Bill", nowner); return 1; } static Chan* fsattach(char *spec) { Chan *c; static int devno; Ufsinfo *uif; c = devattach('U', spec); uif = mallocz(sizeof(Ufsinfo), 1); c->aux = uif; c->dev = devno++; c->qid.type = QTDIR; return c; } static Chan* fsclone(Chan *c, Chan *nc) { Ufsinfo *uif; uif = mallocz(sizeof(Ufsinfo), 1); *uif = *(Ufsinfo*)c->aux; nc->aux = uif; return nc; } static int fswalk1(Chan *c, char *name) { HANDLE h; WIN32_FIND_DATA wfd; TCHAR path[MAXPATH]; fspath(c, name, path, MAXPATH); switch(pathtype(path)){ case TPATH_VOLUME: if(!checkvolume(path)) return 0; case TPATH_ROOT: c->qid = wfdtoqid(path, nil); break; case TPATH_FILE: if((h = FindFirstFile(path, &wfd)) == INVALID_HANDLE_VALUE) return 0; FindClose(h); c->qid = wfdtoqid(path, &wfd); break; } return 1; } extern Cname* addelem(Cname*, char*); static Walkqid* fswalk(Chan *c, Chan *nc, char **name, int nname) { int i; Cname *cname; Walkqid *wq; if(nc != nil) panic("fswalk: nc != nil"); wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid)); nc = devclone(c); cname = c->name; incref(&cname->ref); fsclone(c, nc); wq->clone = nc; for(i=0; iname = cname; if(fswalk1(nc, name[i]) == 0) break; cname = addelem(cname, name[i]); wq->qid[i] = nc->qid; } nc->name = cname; if(i != nname){ cclose(nc); wq->clone = nil; } wq->nqid = i; return wq; } static int fsstat(Chan *c, uchar *buf, int n) { Dir d; TCHAR path[MAXPATH]; char owner[MAXPATH]; if(n < BIT16SZ) error(Eshortstat); fspath(c, 0, path, MAXPATH); d.name = lastelem(c); wfdtodir(path, &d, nil); if(getfileowner(path, owner, MAXPATH)){ d.uid = owner; d.gid = owner; d.muid = owner; } d.type = 'U'; d.dev = c->dev; return convD2M(&d, buf, n); } static Chan* fsopen(Chan *c, int mode) { TCHAR path[MAXPATH]; ulong t; int m, isdir; Ufsinfo *uif; m = mode & (OTRUNC|3); switch(m) { case 0: break; case 1: case 1|16: break; case 2: case 0|16: case 2|16: break; case 3: break; default: error(Ebadarg); } isdir = c->qid.type & QTDIR; if(isdir && mode != OREAD) error(Eperm); m = fsomode(m & 3); c->mode = openmode(mode); uif = c->aux; uif->offset = 0; fspath(c, 0, path, MAXPATH); t = pathtype(path); if(isdir){ DIR *d; d = malloc(sizeof(*d)); switch(t){ case TPATH_ROOT: d->drivebuf = malloc(sizeof(TCHAR)*MAX_PATH); if(GetLogicalDriveStrings(MAX_PATH-1, d->drivebuf) == 0){ free(d->drivebuf); d->drivebuf = nil; oserror(); } d->drivep = d->drivebuf; break; case TPATH_VOLUME: case TPATH_FILE: #ifdef UNICODE wcsncat(path, L"\\*.*", MAXPATH); #else wcsncat(path, "\\*.*", MAXPATH); #endif if((d->handle = FindFirstFile(path, &d->wfd)) == INVALID_HANDLE_VALUE){ free(d); oserror(); } break; } d->keep = 1; uif->dir = d; } else { uif->dir = nil; if((uif->fh = CreateFile( path, fsaccess(mode), FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, (mode & OTRUNC) ? TRUNCATE_EXISTING : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)) == INVALID_HANDLE_VALUE){ oserror(); } } c->offset = 0; c->flag |= COPEN; return c; } static void fscreate(Chan *c, char *name, int mode, ulong perm) { int m; TCHAR path[MAXPATH]; Ufsinfo *uif; ulong t; m = fsomode(mode&3); fspath(c, name, path, MAXPATH); t = pathtype(path); uif = c->aux; if(perm & DMDIR) { TCHAR *p; DIR *d; if(m || t!=TPATH_FILE) error(Eperm); if(!CreateDirectory(path, NULL)) oserror(); d = malloc(sizeof(*d)); p = &path[wcslen(path)]; #ifdef UNICODE wcsncat(path, L"\\*.*", MAXPATH); #else wcsncat(path, "\\*.*", MAXPATH); #endif if((d->handle = FindFirstFile(path, &d->wfd)) == INVALID_HANDLE_VALUE){ free(d); oserror(); } *p = 0; d->keep = 1; uif->dir = d; } else { uif->dir = nil; if((uif->fh = CreateFile( path, fsaccess(mode), FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0)) == INVALID_HANDLE_VALUE){ oserror(); } } c->qid = wfdtoqid(path, nil); c->offset = 0; c->flag |= COPEN; c->mode = openmode(mode); } static void fsclose(Chan *c) { Ufsinfo *uif; uif = c->aux; if(c->flag & COPEN) { if(uif->dir){ if(uif->dir->drivebuf){ free(uif->dir->drivebuf); uif->dir->drivebuf = nil; } else { FindClose(uif->dir->handle); free(uif->dir); } } else { CloseHandle(uif->fh); } } free(uif); } static long fsread(Chan *c, void *va, long n, vlong offset) { HANDLE fh; DWORD r; Ufsinfo *uif; if(c->qid.type & QTDIR) return fsdirread(c, va, n, offset); uif = c->aux; qlock(&uif->oq); if(waserror()) { qunlock(&uif->oq); nexterror(); } fh = uif->fh; if(uif->offset != offset) { LONG high; high = offset>>32; offset = SetFilePointer(fh, (LONG)(offset & 0xFFFFFFFF), &high, FILE_BEGIN); offset |= (vlong)high<<32; uif->offset = offset; } r = 0; if(!ReadFile(fh, va, (DWORD)n, &r, NULL)){ oserror(); } n = r; uif->offset += n; qunlock(&uif->oq); poperror(); return n; } static long fswrite(Chan *c, void *va, long n, vlong offset) { HANDLE fh; DWORD w; Ufsinfo *uif; if(c->qid.type & QTDIR) return fsdirread(c, va, n, offset); uif = c->aux; qlock(&uif->oq); if(waserror()) { qunlock(&uif->oq); nexterror(); } fh = uif->fh; if(uif->offset != offset) { LONG high; high = offset>>32; offset = SetFilePointer(fh, (LONG)(offset & 0xFFFFFFFF), &high, FILE_BEGIN); offset |= (vlong)high<<32; uif->offset = offset; } w = 0; if(!WriteFile(fh, va, (DWORD)n, &w, NULL)){ oserror(); } n = w; uif->offset += n; qunlock(&uif->oq); poperror(); return n; } static void fsremove(Chan *c) { TCHAR path[MAXPATH]; fspath(c, 0, path, MAXPATH); if(c->qid.type & QTDIR){ if(!RemoveDirectory(path)) error("RemoveDirectory..."); } else { if(!DeleteFile(path)) error("DeleteFile..."); } } static int fswstat(Chan *c, uchar *buf, int n) { TCHAR path[MAXPATH]; char strs[MAXPATH*3]; Ufsinfo *uif; Dir d; ulong t; uif = c->aux; if (convM2D(buf, n, &d, strs) != n) error(Ebadstat); fspath(c, 0, path, MAXPATH); t = pathtype(path); if(t != TPATH_FILE) error(Ebadstat); /* change name */ if(d.name[0]){ int l; TCHAR newpath[MAXPATH]; wcsncpy(newpath, path, MAXPATH); /* replace last path-element with d.name */ l = wcslen(newpath)-1; if(l <= 0) error(Ebadstat); for(;l>0; l--){ if(newpath[l-1]=='\\') break; } if(l <= 0) error(Ebadstat); newpath[l] = 0; #ifdef UNICODE if(!MultiByteToWideChar(CP_UTF8,0,d.name,-1,&newpath[l],MAXPATH-l-1)) oserror(); #else wcsncpy(&newpath[l], d.name, MAXPATH-l-1); #endif if(wcscmp(path, newpath)!=0){ if(!MoveFile(path, newpath)) oserror(); wcsncpy(path, newpath, MAXPATH); } } /* fixme: change attributes */ c->qid = wfdtoqid(path, nil); return n; } static void _fspath(Chan *c, char *ext, char *path, int npath) { *path = 0; strncat(path, uc2name(c), npath); if(ext) { if(*path) strncat(path, "/", npath); strncat(path, ext, npath); } cleanname(path); if(*path == '.') *path = 0; } static void fspath(Chan *c, char *ext, TCHAR *path, int npath) { TCHAR *p; #ifdef UNICODE char buf[MAXPATH]; _fspath(c, ext, buf, sizeof(buf)); if(!MultiByteToWideChar(CP_UTF8,0,buf,-1,path,npath)){ oserror(); } #else _fspath(c, ext, path, npath); #endif /* make a DOS path */ for(p=path; *p; p++){ if(*p == '/') *p = '\\'; } } static int isdots(char *name) { if(name[0] != '.') return 0; if(name[1] == '\0') return 1; if(name[1] != '.') return 0; if(name[2] == '\0') return 1; return 0; } static ulong fsdirread(Chan *c, uchar *va, int count, ulong offset) { int i; ulong t; Dir d; long n; Ufsinfo *uif; char de[MAX_PATH*3]; int ndirpath; TCHAR dirpath[MAXPATH]; i = 0; uif = c->aux; errno = 0; fspath(c, 0, dirpath, MAXPATH); ndirpath = wcslen(dirpath); t = pathtype(dirpath); if(uif->offset != offset) { if(offset != 0) error("bad offset in fsdirread"); uif->offset = offset; /* sync offset */ switch(t){ case TPATH_ROOT: uif->dir->drivep = uif->dir->drivebuf; break; case TPATH_VOLUME: case TPATH_FILE: FindClose(uif->dir->handle); #ifdef UNICODE wcsncat(dirpath, L"\\*.*", MAXPATH); #else wcsncat(dirpath, "\\*.*", MAXPATH); #endif if((uif->dir->handle = FindFirstFile(dirpath, &uif->dir->wfd))==INVALID_HANDLE_VALUE){ oserror(); } break; } uif->dir->keep = 1; } while(i+BIT16SZ < count) { char owner[MAXPATH]; if(!uif->dir->keep) { switch(t){ case TPATH_ROOT: uif->dir->drivep += 4; if(*uif->dir->drivep == 0) goto out; break; case TPATH_VOLUME: case TPATH_FILE: if(!FindNextFile(uif->dir->handle, &uif->dir->wfd)) goto out; break; } } else { uif->dir->keep = 0; } if(t == TPATH_ROOT){ uif->dir->drivep[2] = 0; #ifdef UNICODE WideCharToMultiByte(CP_UTF8,0,uif->dir->drivep,-1,de,sizeof(de),0,0); #else strncpy(de, uif->dir->drivep, sizeof(de)); #endif } else { #ifdef UNICODE WideCharToMultiByte(CP_UTF8,0,uif->dir->wfd.cFileName,-1,de,sizeof(de),0,0); #else strncpy(de, uif->dir->wfd.cFileName, sizeof(de)); #endif } if(de[0]==0 || isdots(de)) continue; d.name = de; dirpath[ndirpath] = 0; if(t == TPATH_ROOT){ wcsncat(dirpath, uif->dir->drivep, MAXPATH); wfdtodir(dirpath, &d, nil); } else { #ifdef UNICODE wcsncat(dirpath, L"\\", MAXPATH); #else wcsncat(dirpath, "\\", MAXPATH); #endif wcsncat(dirpath, uif->dir->wfd.cFileName, MAXPATH); wfdtodir(dirpath, &d, &uif->dir->wfd); } if(getfileowner(dirpath, owner, MAXPATH)){ d.uid = owner; d.gid = owner; d.muid = owner; } d.type = 'U'; d.dev = c->dev; n = convD2M(&d, (char*)va+i, count-i); if(n == BIT16SZ){ uif->dir->keep = 1; break; } i += n; } out: uif->offset += i; return i; } static int fsomode(int m) { switch(m) { case 0: /* OREAD */ case 3: /* OEXEC */ return 0; case 1: /* OWRITE */ return 1; case 2: /* ORDWR */ return 2; } error(Ebadarg); return 0; } static ulong fsaccess(int m) { ulong a; a = 0; switch(m & 3){ default: error(Eperm); break; case OREAD: a = GENERIC_READ; break; case OWRITE: a = GENERIC_WRITE; break; case ORDWR: a = GENERIC_READ | GENERIC_WRITE; break; } return a; } Dev fsdevtab = { 'U', "fs", devreset, devinit, devshutdown, fsattach, fswalk, fsstat, fsopen, fscreate, fsclose, fsread, devbread, fswrite, devbwrite, fsremove, fswstat, };