Unified · Split
| @@ -1,16 +1,8 @@ | ||
| 1 | −/* | |
| 2 | − * Disable Unicode until the calls to FindFirstFile etc | |
| 3 | − * are changed to use wide character strings. | |
| 4 | − */ | |
| 5 | −#undef UNICODE | |
| 6 | 1 | #include <windows.h> |
| 7 | 2 | #include <sys/types.h> |
| 8 | 3 | #include <sys/stat.h> |
| 9 | 4 | #include <fcntl.h> |
| 10 | 5 | |
| 11 | −#ifndef NAME_MAX | |
| 12 | −# define NAME_MAX 256 | |
| 13 | −#endif | |
| 14 | 6 | #include "u.h" |
| 15 | 7 | #include "lib.h" |
| 16 | 8 | #include "dat.h" |
| @@ -20,47 +12,148 @@ · 3 unmodified lines | ||
| 20 | 12 | typedef struct DIR DIR; |
| 21 | 13 | typedef struct Ufsinfo Ufsinfo; |
| 22 | 14 | |
| 15 | + | |
| 23 | 16 | enum |
| 24 | 17 | { |
| 25 | − NUID = 256, | |
| 26 | − NGID = 256, | |
| 27 | 18 | MAXPATH = 1024, |
| 28 | − MAXCOMP = 128 | |
| 19 | + TPATH_ROOT = 0, // "" | |
| 20 | + TPATH_VOLUME = 1, // "C:" | |
| 21 | + TPATH_FILE = 2, // "C:\bla" | |
| 29 | 22 | }; |
| 30 | 23 | |
| 31 | 24 | struct DIR |
| 32 | 25 | { |
| 33 | − HANDLE handle; | |
| 34 | − char* path; | |
| 35 | − int index; | |
| 26 | + // for FindFileFirst() | |
| 27 | + HANDLE handle; | |
| 36 | 28 | WIN32_FIND_DATA wfd; |
| 29 | + | |
| 30 | + // for GetLogicalDriveStrings() | |
| 31 | + TCHAR *drivebuf; | |
| 32 | + TCHAR *drivep; | |
| 33 | + | |
| 34 | + // dont move to the next item | |
| 35 | + int keep; | |
| 37 | 36 | }; |
| 38 | 37 | |
| 39 | 38 | struct Ufsinfo |
| 40 | 39 | { |
| 41 | 40 | int mode; |
| 42 | − int fd; | |
| 43 | − int uid; | |
| 44 | − int gid; | |
| 41 | + HANDLE fh; | |
| 45 | 42 | DIR* dir; |
| 46 | − ulong offset; | |
| 43 | + vlong offset; | |
| 47 | 44 | QLock oq; |
| 48 | − char nextname[NAME_MAX]; | |
| 49 | 45 | }; |
| 50 | 46 | |
| 51 | −DIR* opendir(char*); | |
| 52 | −int readdir(char*, DIR*); | |
| 53 | −void closedir(DIR*); | |
| 54 | −void rewinddir(DIR*); | |
| 47 | +static void fspath(Chan *, char *, TCHAR *, int); | |
| 48 | +static ulong fsdirread(Chan*, uchar*, int, vlong); | |
| 49 | +static int fsomode(int); | |
| 50 | +static ulong fsaccess(int); | |
| 51 | +static ulong pathtype(TCHAR *); | |
| 52 | +static int checkvolume(TCHAR *); | |
| 55 | 53 | |
| 56 | −char *base = "c:/."; | |
| 54 | +char *base = ""; | |
| 57 | 55 | |
| 58 | −static Qid fsqid(char*, struct stat *); | |
| 59 | −static void fspath(Chan*, char*, char*); | |
| 60 | −// static void fsperm(Chan*, int); | |
| 61 | −static ulong fsdirread(Chan*, uchar*, int, ulong); | |
| 62 | −static int fsomode(int); | |
| 63 | −static int chown(char *path, int uid, int); | |
| 56 | +static ulong | |
| 57 | +unixtime(FILETIME *ft) | |
| 58 | +{ | |
| 59 | + vlong t; | |
| 60 | + t = ((vlong)ft->dwHighDateTime << 32)|((vlong)ft->dwLowDateTime); | |
| 61 | + t -= 116444736000000000LL; | |
| 62 | + return ((t<0)?(-1 - (-t - 1)) : t)/10000000; | |
| 63 | +} | |
| 64 | + | |
| 65 | +static uvlong | |
| 66 | +pathhash(TCHAR *p) | |
| 67 | +{ | |
| 68 | + uvlong h; | |
| 69 | + h = 0LL; | |
| 70 | + for(; *p; p++) | |
| 71 | + h += *p * 13; | |
| 72 | + return h; | |
| 73 | +} | |
| 74 | + | |
| 75 | +static ulong | |
| 76 | +wfdtodmode(WIN32_FIND_DATA *wfd) | |
| 77 | +{ | |
| 78 | + int m; | |
| 79 | + m = DMREAD|DMWRITE|DMEXEC; | |
| 80 | + if(wfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) | |
| 81 | + m |= DMDIR; | |
| 82 | + if(wfd->dwFileAttributes & FILE_ATTRIBUTE_READONLY) | |
| 83 | + m &= ~DMWRITE; | |
| 84 | + m |= (m & 07)<<3; | |
| 85 | + m |= (m & 07)<<6; | |
| 86 | + return m; | |
| 87 | +} | |
| 88 | + | |
| 89 | +static Qid | |
| 90 | +wfdtoqid(TCHAR *path, WIN32_FIND_DATA *wfd) | |
| 91 | +{ | |
| 92 | + ulong t; | |
| 93 | + WIN32_FIND_DATA f; | |
| 94 | + Qid q; | |
| 95 | + | |
| 96 | + t = pathtype(path); | |
| 97 | + | |
| 98 | + switch(t){ | |
| 99 | + case TPATH_VOLUME: | |
| 100 | + case TPATH_ROOT: | |
| 101 | + q.type = QTDIR; | |
| 102 | + q.path = pathhash(path); | |
| 103 | + q.vers = 0; | |
| 104 | + break; | |
| 105 | + | |
| 106 | + case TPATH_FILE: | |
| 107 | + if(!wfd){ | |
| 108 | + HANDLE h; | |
| 109 | + if((h = FindFirstFile(path, &f))==INVALID_HANDLE_VALUE) | |
| 110 | + oserror(); | |
| 111 | + FindClose(h); | |
| 112 | + wfd = &f; | |
| 113 | + } | |
| 114 | + q.type = (wfd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? QTDIR : QTFILE; | |
| 115 | + q.path = pathhash(path); | |
| 116 | + q.vers = unixtime(&wfd->ftLastWriteTime); | |
| 117 | + break; | |
| 118 | + } | |
| 119 | + return q; | |
| 120 | +} | |
| 121 | + | |
| 122 | +static void | |
| 123 | +wfdtodir(TCHAR *path, Dir *d, WIN32_FIND_DATA *wfd) | |
| 124 | +{ | |
| 125 | + WIN32_FIND_DATA f; | |
| 126 | + | |
| 127 | + d->uid = "nul"; | |
| 128 | + d->gid = "nul"; | |
| 129 | + d->muid = "nul"; | |
| 130 | + | |
| 131 | + switch(pathtype(path)){ | |
| 132 | + case TPATH_VOLUME: | |
| 133 | + case TPATH_ROOT: | |
| 134 | + wfd = nil; | |
| 135 | + d->mode = 0777 | DMDIR; | |
| 136 | + d->atime = 0; | |
| 137 | + d->mtime = 0; | |
| 138 | + d->length = 0; | |
| 139 | + break; | |
| 140 | + | |
| 141 | + case TPATH_FILE: | |
| 142 | + if(!wfd){ | |
| 143 | + HANDLE h; | |
| 144 | + if((h = FindFirstFile(path, &f))==INVALID_HANDLE_VALUE) | |
| 145 | + oserror(); | |
| 146 | + FindClose(h); | |
| 147 | + wfd = &f; | |
| 148 | + } | |
| 149 | + d->mode = wfdtodmode(wfd); | |
| 150 | + d->atime = unixtime(&wfd->ftLastAccessTime); | |
| 151 | + d->mtime = unixtime(&wfd->ftLastWriteTime); | |
| 152 | + d->length = ((uvlong)wfd->nFileSizeHigh << 32)|((uvlong)wfd->nFileSizeLow); | |
| 153 | + break; | |
| 154 | + } | |
| 155 | + d->qid = wfdtoqid(path, wfd); | |
| 156 | +} | |
| 64 | 157 | |
| 65 | 158 | /* clumsy hack, but not worse than the Path stuff in the last one */ |
| 66 | 159 | static char* |
| @@ -69,10 +162,12 @@ uc2name(Chan *c) · 2 unmodified lines | ||
| 69 | 162 | char *s; |
| 70 | 163 | |
| 71 | 164 | if(c->name == nil) |
| 72 | − return "/"; | |
| 165 | + return ""; | |
| 73 | 166 | s = c2name(c); |
| 74 | 167 | if(s[0]=='#' && s[1]=='U') |
| 75 | − return s+2; | |
| 168 | + s += 2; | |
| 169 | + if(*s=='/') | |
| 170 | + s++; | |
| 76 | 171 | return s; |
| 77 | 172 | } |
| 78 | 173 | |
| @@ -88,29 +183,69 @@ lastelem(Chan *c) · 9 unmodified lines | ||
| 88 | 183 | return t; |
| 89 | 184 | return t+1; |
| 90 | 185 | } |
| 186 | + | |
| 187 | +static ulong | |
| 188 | +pathtype(TCHAR *path) | |
| 189 | +{ | |
| 190 | + int n; | |
| 191 | + n = wcslen(path); | |
| 192 | + if(n < 2){ | |
| 193 | + return TPATH_ROOT; | |
| 194 | + } | |
| 195 | + if(n==2){ | |
| 196 | + return TPATH_VOLUME; | |
| 197 | + } | |
| 198 | + return TPATH_FILE; | |
| 199 | +} | |
| 200 | + | |
| 201 | +static int | |
| 202 | +checkvolume(TCHAR *path) | |
| 203 | +{ | |
| 204 | + TCHAR vol[MAX_PATH]; | |
| 205 | + TCHAR volname[MAX_PATH]; | |
| 206 | + TCHAR fsysname[MAX_PATH]; | |
| 207 | + DWORD complen; | |
| 208 | + DWORD flags; | |
| 209 | + | |
| 210 | + wcsncpy(vol, path, MAX_PATH); | |
| 211 | +#ifdef UNICODE | |
| 212 | + wcsncat(vol, L"\\", MAXPATH); | |
| 213 | +#else | |
| 214 | + wcsncat(vol, "\\", MAXPATH); | |
| 215 | +#endif | |
| 216 | + | |
| 217 | + if(!GetVolumeInformation( | |
| 218 | + vol, | |
| 219 | + volname, | |
| 220 | + MAX_PATH, | |
| 221 | + NULL, | |
| 222 | + &complen, | |
| 223 | + &flags, | |
| 224 | + fsysname, | |
| 225 | + MAX_PATH)){ | |
| 226 | + return 0; | |
| 227 | + } | |
| 228 | + return 1; | |
| 229 | +} | |
| 230 | + | |
| 231 | +static int | |
| 232 | +getfileowner(TCHAR *path, char *owner, int nowner) | |
| 233 | +{ | |
| 234 | + strncpy(owner, "Bill", nowner); | |
| 235 | + return 1; | |
| 236 | +} | |
| 91 | 237 | |
| 92 | 238 | static Chan* |
| 93 | 239 | fsattach(char *spec) |
| 94 | 240 | { |
| 95 | 241 | Chan *c; |
| 96 | − struct stat stbuf; | |
| 97 | 242 | static int devno; |
| 98 | 243 | Ufsinfo *uif; |
| 99 | − | |
| 100 | − if(stat(base, &stbuf) < 0) | |
| 101 | − error(strerror(errno)); | |
| 102 | − | |
| 103 | 244 | c = devattach('U', spec); |
| 104 | − | |
| 105 | 245 | uif = mallocz(sizeof(Ufsinfo), 1); |
| 106 | − uif->gid = stbuf.st_gid; | |
| 107 | − uif->uid = stbuf.st_uid; | |
| 108 | − uif->mode = stbuf.st_mode; | |
| 109 | − | |
| 110 | 246 | c->aux = uif; |
| 111 | 247 | c->dev = devno++; |
| 112 | 248 | c->qid.type = QTDIR; |
| 113 | −/*print("fsattach %s\n", c2name(c));*/ | |
| 114 | 249 | |
| 115 | 250 | return c; |
| 116 | 251 | } |
| @@ -130,25 +265,27 @@ fsclone(Chan *c, Chan *nc) · 13 unmodified lines | ||
| 130 | 265 | static int |
| 131 | 266 | fswalk1(Chan *c, char *name) |
| 132 | 267 | { |
| 133 | − struct stat stbuf; | |
| 134 | − char path[MAXPATH]; | |
| 135 | − Ufsinfo *uif; | |
| 268 | + HANDLE h; | |
| 269 | + WIN32_FIND_DATA wfd; | |
| 270 | + TCHAR path[MAXPATH]; | |
| 136 | 271 | |
| 137 | − fspath(c, name, path); | |
| 138 | − | |
| 139 | − /* print("** fs walk '%s' -> %s\n", path, name); */ | |
| 140 | − | |
| 141 | − if(stat(path, &stbuf) < 0) | |
| 142 | − return 0; | |
| 272 | + fspath(c, name, path, MAXPATH); | |
| 143 | 273 | |
| 144 | − uif = c->aux; | |
| 145 | − | |
| 146 | − uif->gid = stbuf.st_gid; | |
| 147 | − uif->uid = stbuf.st_uid; | |
| 148 | − uif->mode = stbuf.st_mode; | |
| 149 | − | |
| 150 | − c->qid = fsqid(path, &stbuf); | |
| 274 | + switch(pathtype(path)){ | |
| 275 | + case TPATH_VOLUME: | |
| 276 | + if(!checkvolume(path)) | |
| 277 | + return 0; | |
| 278 | + case TPATH_ROOT: | |
| 279 | + c->qid = wfdtoqid(path, nil); | |
| 280 | + break; | |
| 151 | 281 | |
| 282 | + case TPATH_FILE: | |
| 283 | + if((h = FindFirstFile(path, &wfd)) == INVALID_HANDLE_VALUE) | |
| 284 | + return 0; | |
| 285 | + FindClose(h); | |
| 286 | + c->qid = wfdtoqid(path, &wfd); | |
| 287 | + break; | |
| 288 | + } | |
| 152 | 289 | return 1; |
| 153 | 290 | } |
| 154 | 291 | |
| @@ -190,25 +327,21 @@ static int · 35 unmodified lines | ||
| 190 | 327 | fsstat(Chan *c, uchar *buf, int n) |
| 191 | 328 | { |
| 192 | 329 | Dir d; |
| 193 | − struct stat stbuf; | |
| 194 | − char path[MAXPATH]; | |
| 330 | + TCHAR path[MAXPATH]; | |
| 331 | + char owner[MAXPATH]; | |
| 195 | 332 | |
| 196 | 333 | if(n < BIT16SZ) |
| 197 | 334 | error(Eshortstat); |
| 335 | + fspath(c, 0, path, MAXPATH); | |
| 336 | + d.name = lastelem(c); | |
| 337 | + wfdtodir(path, &d, nil); | |
| 198 | 338 | |
| 199 | − fspath(c, 0, path); | |
| 200 | − if(stat(path, &stbuf) < 0) | |
| 201 | − error(strerror(errno)); | |
| 339 | + if(getfileowner(path, owner, MAXPATH)){ | |
| 340 | + d.uid = owner; | |
| 341 | + d.gid = owner; | |
| 342 | + d.muid = owner; | |
| 343 | + } | |
| 202 | 344 | |
| 203 | − d.name = lastelem(c); | |
| 204 | − d.uid = "unknown"; | |
| 205 | − d.gid = "unknown"; | |
| 206 | − d.muid = "unknown"; | |
| 207 | − d.qid = c->qid; | |
| 208 | − d.mode = (c->qid.type<<24)|(stbuf.st_mode&0777); | |
| 209 | − d.atime = stbuf.st_atime; | |
| 210 | − d.mtime = stbuf.st_mtime; | |
| 211 | − d.length = stbuf.st_size; | |
| 212 | 345 | d.type = 'U'; |
| 213 | 346 | d.dev = c->dev; |
| 214 | 347 | return convD2M(&d, buf, n); |
| @@ -217,11 +350,11 @@ fsstat(Chan *c, uchar *buf, int n) · 2 unmodified lines | ||
| 217 | 350 | static Chan* |
| 218 | 351 | fsopen(Chan *c, int mode) |
| 219 | 352 | { |
| 220 | − char path[MAXPATH]; | |
| 353 | + TCHAR path[MAXPATH]; | |
| 354 | + ulong t; | |
| 221 | 355 | int m, isdir; |
| 222 | 356 | Ufsinfo *uif; |
| 223 | 357 | |
| 224 | −/*print("fsopen %s\n", c2name(c));*/ | |
| 225 | 358 | m = mode & (OTRUNC|3); |
| 226 | 359 | switch(m) { |
| 227 | 360 | case 0: |
| @@ -240,31 +373,55 @@ fsopen(Chan *c, int mode) · 12 unmodified lines | ||
| 240 | 373 | } |
| 241 | 374 | |
| 242 | 375 | isdir = c->qid.type & QTDIR; |
| 243 | − | |
| 244 | 376 | if(isdir && mode != OREAD) |
| 245 | 377 | error(Eperm); |
| 246 | − | |
| 247 | 378 | m = fsomode(m & 3); |
| 248 | 379 | c->mode = openmode(mode); |
| 249 | − | |
| 250 | 380 | uif = c->aux; |
| 251 | − | |
| 252 | − fspath(c, 0, path); | |
| 253 | − if(isdir) { | |
| 254 | − uif->dir = opendir(path); | |
| 255 | − if(uif->dir == 0) | |
| 256 | − error(strerror(errno)); | |
| 257 | − } | |
| 258 | − else { | |
| 259 | − if(mode & OTRUNC) | |
| 260 | − m |= O_TRUNC; | |
| 261 | − uif->fd = open(path, m|_O_BINARY, 0666); | |
| 262 | − | |
| 263 | − if(uif->fd < 0) | |
| 264 | − error(strerror(errno)); | |
| 265 | − } | |
| 266 | 381 | uif->offset = 0; |
| 267 | − | |
| 382 | + fspath(c, 0, path, MAXPATH); | |
| 383 | + t = pathtype(path); | |
| 384 | + if(isdir){ | |
| 385 | + DIR *d; | |
| 386 | + d = malloc(sizeof(*d)); | |
| 387 | + switch(t){ | |
| 388 | + case TPATH_ROOT: | |
| 389 | + d->drivebuf = malloc(sizeof(TCHAR)*MAX_PATH); | |
| 390 | + if(GetLogicalDriveStrings(MAX_PATH-1, d->drivebuf) == 0){ | |
| 391 | + free(d->drivebuf); | |
| 392 | + d->drivebuf = nil; | |
| 393 | + oserror(); | |
| 394 | + } | |
| 395 | + d->drivep = d->drivebuf; | |
| 396 | + break; | |
| 397 | + case TPATH_VOLUME: | |
| 398 | + case TPATH_FILE: | |
| 399 | +#ifdef UNICODE | |
| 400 | + wcsncat(path, L"\\*.*", MAXPATH); | |
| 401 | +#else | |
| 402 | + wcsncat(path, "\\*.*", MAXPATH); | |
| 403 | +#endif | |
| 404 | + if((d->handle = FindFirstFile(path, &d->wfd)) == INVALID_HANDLE_VALUE){ | |
| 405 | + free(d); | |
| 406 | + oserror(); | |
| 407 | + } | |
| 408 | + break; | |
| 409 | + } | |
| 410 | + d->keep = 1; | |
| 411 | + uif->dir = d; | |
| 412 | + } else { | |
| 413 | + uif->dir = nil; | |
| 414 | + if((uif->fh = CreateFile( | |
| 415 | + path, | |
| 416 | + fsaccess(mode), | |
| 417 | + FILE_SHARE_READ | FILE_SHARE_WRITE, | |
| 418 | + NULL, | |
| 419 | + (mode & OTRUNC) ? TRUNCATE_EXISTING : OPEN_EXISTING, | |
| 420 | + FILE_ATTRIBUTE_NORMAL, | |
| 421 | + 0)) == INVALID_HANDLE_VALUE){ | |
| 422 | + oserror(); | |
| 423 | + } | |
| 424 | + } | |
| 268 | 425 | c->offset = 0; |
| 269 | 426 | c->flag |= COPEN; |
| 270 | 427 | return c; |
| @@ -273,82 +430,88 @@ fsopen(Chan *c, int mode) · 2 unmodified lines | ||
| 273 | 430 | static void |
| 274 | 431 | fscreate(Chan *c, char *name, int mode, ulong perm) |
| 275 | 432 | { |
| 276 | − int fd, m; | |
| 277 | − char path[MAXPATH]; | |
| 278 | − struct stat stbuf; | |
| 433 | + int m; | |
| 434 | + TCHAR path[MAXPATH]; | |
| 279 | 435 | Ufsinfo *uif; |
| 436 | + ulong t; | |
| 280 | 437 | |
| 281 | 438 | m = fsomode(mode&3); |
| 282 | 439 | |
| 283 | − fspath(c, name, path); | |
| 440 | + fspath(c, name, path, MAXPATH); | |
| 441 | + t = pathtype(path); | |
| 284 | 442 | |
| 285 | 443 | uif = c->aux; |
| 286 | 444 | |
| 287 | 445 | if(perm & DMDIR) { |
| 288 | − if(m) | |
| 446 | + TCHAR *p; | |
| 447 | + DIR *d; | |
| 448 | + if(m || t!=TPATH_FILE) | |
| 289 | 449 | error(Eperm); |
| 290 | − | |
| 291 | − if(mkdir(path) < 0) | |
| 292 | − error(strerror(errno)); | |
| 293 | − | |
| 294 | − fd = open(path, 0); | |
| 295 | − if(fd >= 0) { | |
| 296 | − chmod(path, perm & 0777); | |
| 297 | − chown(path, uif->uid, uif->uid); | |
| 298 | − } | |
| 299 | − close(fd); | |
| 300 | − | |
| 301 | − uif->dir = opendir(path); | |
| 302 | − if(uif->dir == 0) | |
| 303 | − error(strerror(errno)); | |
| 304 | − } | |
| 305 | − else { | |
| 306 | − fd = open(path, _O_WRONLY|_O_BINARY|_O_CREAT|_O_TRUNC, 0666); | |
| 307 | − if(fd >= 0) { | |
| 308 | − if(m != 1) { | |
| 309 | − close(fd); | |
| 310 | − fd = open(path, m|_O_BINARY); | |
| 311 | − } | |
| 312 | − chmod(path, perm & 0777); | |
| 313 | − chown(path, uif->uid, uif->gid); | |
| 450 | + if(!CreateDirectory(path, NULL)) | |
| 451 | + oserror(); | |
| 452 | + d = malloc(sizeof(*d)); | |
| 453 | + p = &path[wcslen(path)]; | |
| 454 | +#ifdef UNICODE | |
| 455 | + wcsncat(path, L"\\*.*", MAXPATH); | |
| 456 | +#else | |
| 457 | + wcsncat(path, "\\*.*", MAXPATH); | |
| 458 | +#endif | |
| 459 | + if((d->handle = FindFirstFile(path, &d->wfd)) == INVALID_HANDLE_VALUE){ | |
| 460 | + free(d); | |
| 461 | + oserror(); | |
| 314 | 462 | } |
| 315 | − if(fd < 0) | |
| 316 | − error(strerror(errno)); | |
| 317 | − uif->fd = fd; | |
| 463 | + *p = 0; | |
| 464 | + d->keep = 1; | |
| 465 | + uif->dir = d; | |
| 466 | + } else { | |
| 467 | + uif->dir = nil; | |
| 468 | + if((uif->fh = CreateFile( | |
| 469 | + path, | |
| 470 | + fsaccess(mode), | |
| 471 | + FILE_SHARE_READ | FILE_SHARE_WRITE, | |
| 472 | + NULL, | |
| 473 | + CREATE_NEW, | |
| 474 | + FILE_ATTRIBUTE_NORMAL, | |
| 475 | + 0)) == INVALID_HANDLE_VALUE){ | |
| 476 | + oserror(); | |
| 477 | + } | |
| 318 | 478 | } |
| 319 | − | |
| 320 | − if(stat(path, &stbuf) < 0) | |
| 321 | − error(strerror(errno)); | |
| 322 | − c->qid = fsqid(path, &stbuf); | |
| 479 | + c->qid = wfdtoqid(path, nil); | |
| 323 | 480 | c->offset = 0; |
| 324 | 481 | c->flag |= COPEN; |
| 325 | 482 | c->mode = openmode(mode); |
| 326 | 483 | } |
| 327 | 484 | |
| 485 | + | |
| 328 | 486 | static void |
| 329 | 487 | fsclose(Chan *c) |
| 330 | 488 | { |
| 331 | 489 | Ufsinfo *uif; |
| 332 | 490 | |
| 333 | 491 | uif = c->aux; |
| 334 | − | |
| 335 | 492 | if(c->flag & COPEN) { |
| 336 | − if(c->qid.type & QTDIR) | |
| 337 | − closedir(uif->dir); | |
| 338 | − else | |
| 339 | − close(uif->fd); | |
| 493 | + if(uif->dir){ | |
| 494 | + if(uif->dir->drivebuf){ | |
| 495 | + free(uif->dir->drivebuf); | |
| 496 | + uif->dir->drivebuf = nil; | |
| 497 | + } else { | |
| 498 | + FindClose(uif->dir->handle); | |
| 499 | + free(uif->dir); | |
| 500 | + } | |
| 501 | + } else { | |
| 502 | + CloseHandle(uif->fh); | |
| 503 | + } | |
| 340 | 504 | } |
| 341 | − | |
| 342 | 505 | free(uif); |
| 343 | 506 | } |
| 344 | 507 | |
| 345 | 508 | static long |
| 346 | 509 | fsread(Chan *c, void *va, long n, vlong offset) |
| 347 | 510 | { |
| 348 | − int fd, r; | |
| 511 | + HANDLE fh; | |
| 512 | + DWORD r; | |
| 349 | 513 | Ufsinfo *uif; |
| 350 | 514 | |
| 351 | −/*print("fsread %s\n", c2name(c));*/ | |
| 352 | 515 | if(c->qid.type & QTDIR) |
| 353 | 516 | return fsdirread(c, va, n, offset); |
| 354 | 517 | |
| @@ -358,166 +521,158 @@ fsread(Chan *c, void *va, long n, vlong offset) · 3 unmodified lines | ||
| 358 | 521 | qunlock(&uif->oq); |
| 359 | 522 | nexterror(); |
| 360 | 523 | } |
| 361 | − fd = uif->fd; | |
| 524 | + fh = uif->fh; | |
| 362 | 525 | if(uif->offset != offset) { |
| 363 | − r = lseek(fd, offset, 0); | |
| 364 | − if(r < 0) | |
| 365 | − error(strerror(errno)); | |
| 526 | + LONG high; | |
| 527 | + high = offset>>32; | |
| 528 | + offset = SetFilePointer(fh, (LONG)(offset & 0xFFFFFFFF), &high, FILE_BEGIN); | |
| 529 | + offset |= (vlong)high<<32; | |
| 366 | 530 | uif->offset = offset; |
| 367 | 531 | } |
| 368 | − | |
| 369 | − n = read(fd, va, n); | |
| 370 | − if(n < 0) | |
| 371 | − error(strerror(errno)); | |
| 372 | − | |
| 532 | + r = 0; | |
| 533 | + if(!ReadFile(fh, va, (DWORD)n, &r, NULL)){ | |
| 534 | + oserror(); | |
| 535 | + } | |
| 536 | + n = r; | |
| 373 | 537 | uif->offset += n; |
| 374 | 538 | qunlock(&uif->oq); |
| 375 | 539 | poperror(); |
| 376 | − | |
| 377 | 540 | return n; |
| 378 | 541 | } |
| 379 | 542 | |
| 380 | 543 | static long |
| 381 | 544 | fswrite(Chan *c, void *va, long n, vlong offset) |
| 382 | 545 | { |
| 383 | − int fd, r; | |
| 546 | + HANDLE fh; | |
| 547 | + DWORD w; | |
| 384 | 548 | Ufsinfo *uif; |
| 385 | 549 | |
| 386 | − uif = c->aux; | |
| 550 | + if(c->qid.type & QTDIR) | |
| 551 | + return fsdirread(c, va, n, offset); | |
| 387 | 552 | |
| 553 | + uif = c->aux; | |
| 388 | 554 | qlock(&uif->oq); |
| 389 | 555 | if(waserror()) { |
| 390 | 556 | qunlock(&uif->oq); |
| 391 | 557 | nexterror(); |
| 392 | 558 | } |
| 393 | − fd = uif->fd; | |
| 559 | + fh = uif->fh; | |
| 394 | 560 | if(uif->offset != offset) { |
| 395 | − r = lseek(fd, offset, 0); | |
| 396 | − if(r < 0) | |
| 397 | − error(strerror(errno)); | |
| 561 | + LONG high; | |
| 562 | + high = offset>>32; | |
| 563 | + offset = SetFilePointer(fh, (LONG)(offset & 0xFFFFFFFF), &high, FILE_BEGIN); | |
| 564 | + offset |= (vlong)high<<32; | |
| 398 | 565 | uif->offset = offset; |
| 399 | 566 | } |
| 400 | − | |
| 401 | − n = write(fd, va, n); | |
| 402 | − if(n < 0) | |
| 403 | − error(strerror(errno)); | |
| 404 | − | |
| 567 | + w = 0; | |
| 568 | + if(!WriteFile(fh, va, (DWORD)n, &w, NULL)){ | |
| 569 | + oserror(); | |
| 570 | + } | |
| 571 | + n = w; | |
| 405 | 572 | uif->offset += n; |
| 406 | 573 | qunlock(&uif->oq); |
| 407 | 574 | poperror(); |
| 408 | − | |
| 409 | 575 | return n; |
| 410 | 576 | } |
| 411 | 577 | |
| 412 | 578 | static void |
| 413 | 579 | fsremove(Chan *c) |
| 414 | 580 | { |
| 415 | − int n; | |
| 416 | − char path[MAXPATH]; | |
| 417 | − | |
| 418 | − fspath(c, 0, path); | |
| 419 | − if(c->qid.type & QTDIR) | |
| 420 | − n = rmdir(path); | |
| 421 | − else | |
| 422 | − n = remove(path); | |
| 423 | − if(n < 0) | |
| 424 | − error(strerror(errno)); | |
| 581 | + TCHAR path[MAXPATH]; | |
| 582 | + | |
| 583 | + fspath(c, 0, path, MAXPATH); | |
| 584 | + if(c->qid.type & QTDIR){ | |
| 585 | + if(!RemoveDirectory(path)) | |
| 586 | + error("RemoveDirectory..."); | |
| 587 | + } else { | |
| 588 | + if(!DeleteFile(path)) | |
| 589 | + error("DeleteFile..."); | |
| 590 | + } | |
| 425 | 591 | } |
| 426 | 592 | |
| 427 | 593 | static int |
| 428 | 594 | fswstat(Chan *c, uchar *buf, int n) |
| 429 | 595 | { |
| 596 | + TCHAR path[MAXPATH]; | |
| 597 | + char strs[MAXPATH*3]; | |
| 430 | 598 | Dir d; |
| 431 | − struct stat stbuf; | |
| 432 | − char old[MAXPATH], new[MAXPATH]; | |
| 433 | − char strs[MAXPATH*3], *p; | |
| 434 | − Ufsinfo *uif; | |
| 599 | + ulong t; | |
| 435 | 600 | |
| 436 | 601 | if (convM2D(buf, n, &d, strs) != n) |
| 437 | 602 | error(Ebadstat); |
| 438 | − | |
| 439 | − fspath(c, 0, old); | |
| 440 | − if(stat(old, &stbuf) < 0) | |
| 441 | − error(strerror(errno)); | |
| 442 | − | |
| 443 | − uif = c->aux; | |
| 444 | − | |
| 445 | −// if(uif->uid != stbuf.st_uid) | |
| 446 | −// error(Eowner); | |
| 447 | − | |
| 448 | − if(d.name[0] && strcmp(d.name, lastelem(c)) != 0) { | |
| 449 | − fspath(c, 0, old); | |
| 450 | − strcpy(new, old); | |
| 451 | − p = strrchr(new, '/'); | |
| 452 | − strcpy(p+1, d.name); | |
| 453 | − if(rename(old, new) < 0) | |
| 454 | − error(strerror(errno)); | |
| 455 | − } | |
| 456 | − | |
| 457 | − fspath(c, 0, old); | |
| 458 | − if(~d.mode != 0 && (int)(d.mode&0777) != (int)(stbuf.st_mode&0777)) { | |
| 459 | − if(chmod(old, d.mode&0777) < 0) | |
| 460 | − error(strerror(errno)); | |
| 461 | − uif->mode &= ~0777; | |
| 462 | − uif->mode |= d.mode&0777; | |
| 603 | + fspath(c, 0, path, MAXPATH); | |
| 604 | + t = pathtype(path); | |
| 605 | + if(t != TPATH_FILE) | |
| 606 | + error(Ebadstat); | |
| 607 | + /* change name */ | |
| 608 | + if(d.name[0]){ | |
| 609 | + int l; | |
| 610 | + TCHAR newpath[MAXPATH]; | |
| 611 | + wcsncpy(newpath, path, MAXPATH); | |
| 612 | + | |
| 613 | + /* replace last path-element with d.name */ | |
| 614 | + l = wcslen(newpath)-1; | |
| 615 | + if(l <= 0) | |
| 616 | + error(Ebadstat); | |
| 617 | + for(;l>0; l--){ | |
| 618 | + if(newpath[l-1]=='\\') | |
| 619 | + break; | |
| 620 | + } | |
| 621 | + if(l <= 0) | |
| 622 | + error(Ebadstat); | |
| 623 | + newpath[l] = 0; | |
| 624 | +#ifdef UNICODE | |
| 625 | + if(!MultiByteToWideChar(CP_UTF8,0,d.name,-1,&newpath[l],MAXPATH-l-1)) | |
| 626 | + oserror(); | |
| 627 | +#else | |
| 628 | + wcsncpy(&newpath[l], d.name, MAXPATH-l-1); | |
| 629 | +#endif | |
| 630 | + if(wcscmp(path, newpath)!=0){ | |
| 631 | + if(!MoveFile(path, newpath)) | |
| 632 | + oserror(); | |
| 633 | + wcsncpy(path, newpath, MAXPATH); | |
| 634 | + } | |
| 463 | 635 | } |
| 464 | −/* | |
| 465 | − p = name2pass(gid, d.gid); | |
| 466 | − if(p == 0) | |
| 467 | − error(Eunknown); | |
| 468 | − | |
| 469 | − if(p->id != stbuf.st_gid) { | |
| 470 | − if(chown(old, stbuf.st_uid, p->id) < 0) | |
| 471 | − error(sys_errlist[errno]); | |
| 472 | 636 | |
| 473 | − uif->gid = p->id; | |
| 474 | − } | |
| 475 | −*/ | |
| 476 | − return n; | |
| 637 | + /* fixme: change attributes */ | |
| 638 | + c->qid = wfdtoqid(path, nil); | |
| 639 | + return n; | |
| 477 | 640 | } |
| 478 | 641 | |
| 479 | −static Qid | |
| 480 | −fsqid(char *p, struct stat *st) | |
| 481 | −{ | |
| 482 | − Qid q; | |
| 483 | − int dev; | |
| 484 | − ulong h; | |
| 485 | − static int nqdev; | |
| 486 | − static uchar *qdev; | |
| 487 | − | |
| 488 | − if(qdev == 0) | |
| 489 | − qdev = mallocz(65536U, 1); | |
| 490 | − | |
| 491 | − q.type = 0; | |
| 492 | − if((st->st_mode&S_IFMT) == S_IFDIR) | |
| 493 | − q.type = QTDIR; | |
| 494 | − | |
| 495 | − dev = st->st_dev & 0xFFFFUL; | |
| 496 | − if(qdev[dev] == 0) | |
| 497 | − qdev[dev] = ++nqdev; | |
| 498 | − | |
| 499 | − h = 0; | |
| 500 | − while(*p != '\0') | |
| 501 | − h += *p++ * 13; | |
| 502 | − | |
| 503 | − q.path = (vlong)qdev[dev]<<32; | |
| 504 | − q.path |= h; | |
| 505 | − q.vers = st->st_mtime; | |
| 506 | − | |
| 507 | − return q; | |
| 508 | −} | |
| 509 | 642 | |
| 510 | 643 | static void |
| 511 | −fspath(Chan *c, char *ext, char *path) | |
| 644 | +_fspath(Chan *c, char *ext, char *path, int npath) | |
| 512 | 645 | { |
| 513 | − strcpy(path, base); | |
| 514 | − strcat(path, "/"); | |
| 515 | − strcat(path, uc2name(c)); | |
| 646 | + *path = 0; | |
| 647 | + strncat(path, uc2name(c), npath); | |
| 516 | 648 | if(ext) { |
| 517 | − strcat(path, "/"); | |
| 518 | − strcat(path, ext); | |
| 649 | + if(*path) | |
| 650 | + strncat(path, "/", npath); | |
| 651 | + strncat(path, ext, npath); | |
| 519 | 652 | } |
| 520 | 653 | cleanname(path); |
| 654 | + if(*path == '.') | |
| 655 | + *path = 0; | |
| 656 | +} | |
| 657 | + | |
| 658 | +static void | |
| 659 | +fspath(Chan *c, char *ext, TCHAR *path, int npath) | |
| 660 | +{ | |
| 661 | + TCHAR *p; | |
| 662 | +#ifdef UNICODE | |
| 663 | + char buf[MAXPATH]; | |
| 664 | + _fspath(c, ext, buf, sizeof(buf)); | |
| 665 | + if(!MultiByteToWideChar(CP_UTF8,0,buf,-1,path,npath)){ | |
| 666 | + oserror(); | |
| 667 | + } | |
| 668 | +#else | |
| 669 | + _fspath(c, ext, path, npath); | |
| 670 | +#endif | |
| 671 | + /* make a DOS path */ | |
| 672 | + for(p=path; *p; p++){ | |
| 673 | + if(*p == '/') | |
| 674 | + *p = '\\'; | |
| 675 | + } | |
| 521 | 676 | } |
| 522 | 677 | |
| 523 | 678 | static int |
| @@ -534,78 +689,116 @@ isdots(char *name) · 10 unmodified lines | ||
| 534 | 689 | return 0; |
| 535 | 690 | } |
| 536 | 691 | |
| 537 | −static int | |
| 538 | −p9readdir(char *name, Ufsinfo *uif) | |
| 539 | −{ | |
| 540 | − if(uif->nextname[0]){ | |
| 541 | − strcpy(name, uif->nextname); | |
| 542 | − uif->nextname[0] = 0; | |
| 543 | − return 1; | |
| 544 | − } | |
| 545 | − | |
| 546 | − return readdir(name, uif->dir); | |
| 547 | −} | |
| 548 | − | |
| 549 | 692 | static ulong |
| 550 | −fsdirread(Chan *c, uchar *va, int count, ulong offset) | |
| 693 | +fsdirread(Chan *c, uchar *va, int count, vlong offset) | |
| 551 | 694 | { |
| 552 | 695 | int i; |
| 696 | + ulong t; | |
| 553 | 697 | Dir d; |
| 554 | 698 | long n; |
| 555 | − char de[NAME_MAX]; | |
| 556 | − struct stat stbuf; | |
| 557 | − char path[MAXPATH], dirpath[MAXPATH]; | |
| 558 | 699 | Ufsinfo *uif; |
| 700 | + char de[MAX_PATH*3]; | |
| 701 | + int ndirpath; | |
| 702 | + TCHAR dirpath[MAXPATH]; | |
| 559 | 703 | |
| 560 | −/*print("fsdirread %s\n", c2name(c));*/ | |
| 561 | 704 | i = 0; |
| 562 | 705 | uif = c->aux; |
| 563 | − | |
| 564 | 706 | errno = 0; |
| 707 | + | |
| 708 | + fspath(c, 0, dirpath, MAXPATH); | |
| 709 | + ndirpath = wcslen(dirpath); | |
| 710 | + t = pathtype(dirpath); | |
| 711 | + | |
| 565 | 712 | if(uif->offset != offset) { |
| 566 | 713 | if(offset != 0) |
| 567 | 714 | error("bad offset in fsdirread"); |
| 568 | 715 | uif->offset = offset; /* sync offset */ |
| 569 | − uif->nextname[0] = 0; | |
| 570 | − rewinddir(uif->dir); | |
| 716 | + switch(t){ | |
| 717 | + case TPATH_ROOT: | |
| 718 | + uif->dir->drivep = uif->dir->drivebuf; | |
| 719 | + break; | |
| 720 | + case TPATH_VOLUME: | |
| 721 | + case TPATH_FILE: | |
| 722 | + FindClose(uif->dir->handle); | |
| 723 | +#ifdef UNICODE | |
| 724 | + wcsncat(dirpath, L"\\*.*", MAXPATH); | |
| 725 | +#else | |
| 726 | + wcsncat(dirpath, "\\*.*", MAXPATH); | |
| 727 | +#endif | |
| 728 | + if((uif->dir->handle = FindFirstFile(dirpath, &uif->dir->wfd))==INVALID_HANDLE_VALUE){ | |
| 729 | + oserror(); | |
| 730 | + } | |
| 731 | + break; | |
| 732 | + } | |
| 733 | + uif->dir->keep = 1; | |
| 571 | 734 | } |
| 572 | 735 | |
| 573 | − fspath(c, 0, dirpath); | |
| 574 | − | |
| 575 | 736 | while(i+BIT16SZ < count) { |
| 576 | − if(!p9readdir(de, uif)) | |
| 577 | − break; | |
| 578 | − | |
| 737 | + char owner[MAXPATH]; | |
| 738 | + | |
| 739 | + if(!uif->dir->keep) { | |
| 740 | + switch(t){ | |
| 741 | + case TPATH_ROOT: | |
| 742 | + uif->dir->drivep += 4; | |
| 743 | + if(*uif->dir->drivep == 0) | |
| 744 | + goto out; | |
| 745 | + break; | |
| 746 | + case TPATH_VOLUME: | |
| 747 | + case TPATH_FILE: | |
| 748 | + if(!FindNextFile(uif->dir->handle, &uif->dir->wfd)) | |
| 749 | + goto out; | |
| 750 | + break; | |
| 751 | + } | |
| 752 | + } else { | |
| 753 | + uif->dir->keep = 0; | |
| 754 | + } | |
| 755 | + if(t == TPATH_ROOT){ | |
| 756 | + uif->dir->drivep[2] = 0; | |
| 757 | +#ifdef UNICODE | |
| 758 | + WideCharToMultiByte(CP_UTF8,0,uif->dir->drivep,-1,de,sizeof(de),0,0); | |
| 759 | +#else | |
| 760 | + strncpy(de, uif->dir->drivep, sizeof(de)); | |
| 761 | +#endif | |
| 762 | + } else { | |
| 763 | +#ifdef UNICODE | |
| 764 | + WideCharToMultiByte(CP_UTF8,0,uif->dir->wfd.cFileName,-1,de,sizeof(de),0,0); | |
| 765 | +#else | |
| 766 | + strncpy(de, uif->dir->wfd.cFileName, sizeof(de)); | |
| 767 | +#endif | |
| 768 | + } | |
| 579 | 769 | if(de[0]==0 || isdots(de)) |
| 580 | 770 | continue; |
| 581 | − | |
| 582 | 771 | d.name = de; |
| 583 | − sprint(path, "%s/%s", dirpath, de); | |
| 584 | − memset(&stbuf, 0, sizeof stbuf); | |
| 772 | + dirpath[ndirpath] = 0; | |
| 773 | + if(t == TPATH_ROOT){ | |
| 774 | + wcsncat(dirpath, uif->dir->drivep, MAXPATH); | |
| 775 | + wfdtodir(dirpath, &d, nil); | |
| 776 | + } else { | |
| 777 | +#ifdef UNICODE | |
| 778 | + wcsncat(dirpath, L"\\", MAXPATH); | |
| 779 | +#else | |
| 780 | + wcsncat(dirpath, "\\", MAXPATH); | |
| 781 | +#endif | |
| 782 | + wcsncat(dirpath, uif->dir->wfd.cFileName, MAXPATH); | |
| 783 | + wfdtodir(dirpath, &d, &uif->dir->wfd); | |
| 784 | + } | |
| 585 | 785 | |
| 586 | − if(stat(path, &stbuf) < 0) { | |
| 587 | − print("dir: bad path %s\n", path); | |
| 588 | − /* but continue... probably a bad symlink */ | |
| 786 | + if(getfileowner(dirpath, owner, MAXPATH)){ | |
| 787 | + d.uid = owner; | |
| 788 | + d.gid = owner; | |
| 789 | + d.muid = owner; | |
| 589 | 790 | } |
| 590 | 791 | |
| 591 | − d.uid = "unknown"; | |
| 592 | − d.gid = "unknown"; | |
| 593 | − d.muid = "unknown"; | |
| 594 | − d.qid = fsqid(path, &stbuf); | |
| 595 | − d.mode = (d.qid.type<<24)|(stbuf.st_mode&0777); | |
| 596 | − d.atime = stbuf.st_atime; | |
| 597 | − d.mtime = stbuf.st_mtime; | |
| 598 | − d.length = stbuf.st_size; | |
| 599 | 792 | d.type = 'U'; |
| 600 | 793 | d.dev = c->dev; |
| 601 | 794 | n = convD2M(&d, (uchar*)va+i, count-i); |
| 602 | 795 | if(n == BIT16SZ){ |
| 603 | − strcpy(uif->nextname, de); | |
| 796 | + uif->dir->keep = 1; | |
| 604 | 797 | break; |
| 605 | 798 | } |
| 606 | 799 | i += n; |
| 607 | 800 | } |
| 608 | −/*print("got %d\n", i);*/ | |
| 801 | +out: | |
| 609 | 802 | uif->offset += i; |
| 610 | 803 | return i; |
| 611 | 804 | } |
| @@ -625,65 +818,29 @@ fsomode(int m) · 13 unmodified lines | ||
| 625 | 818 | error(Ebadarg); |
| 626 | 819 | return 0; |
| 627 | 820 | } |
| 628 | −void | |
| 629 | −closedir(DIR *d) | |
| 630 | −{ | |
| 631 | − FindClose(d->handle); | |
| 632 | − free(d->path); | |
| 633 | −} | |
| 634 | 821 | |
| 635 | −int | |
| 636 | −readdir(char *name, DIR *d) | |
| 822 | +static ulong | |
| 823 | +fsaccess(int m) | |
| 637 | 824 | { |
| 638 | − if(d->index != 0) { | |
| 639 | − if(FindNextFile(d->handle, &d->wfd) == FALSE) | |
| 640 | − return 0; | |
| 825 | + ulong a; | |
| 826 | + a = 0; | |
| 827 | + switch(m & 3){ | |
| 828 | + default: | |
| 829 | + error(Eperm); | |
| 830 | + break; | |
| 831 | + case OREAD: | |
| 832 | + a = GENERIC_READ; | |
| 833 | + break; | |
| 834 | + case OWRITE: | |
| 835 | + a = GENERIC_WRITE; | |
| 836 | + break; | |
| 837 | + case ORDWR: | |
| 838 | + a = GENERIC_READ | GENERIC_WRITE; | |
| 839 | + break; | |
| 641 | 840 | } |
| 642 | − strcpy(name, (char*)d->wfd.cFileName); | |
| 643 | − d->index++; | |
| 644 | − | |
| 645 | − return 1; | |
| 646 | −} | |
| 647 | − | |
| 648 | −void | |
| 649 | −rewinddir(DIR *d) | |
| 650 | −{ | |
| 651 | − FindClose(d->handle); | |
| 652 | − d->handle = FindFirstFile(d->path, &d->wfd); | |
| 653 | − d->index = 0; | |
| 654 | −} | |
| 655 | − | |
| 656 | −static int | |
| 657 | −chown(char *path, int uid, int perm) | |
| 658 | −{ | |
| 659 | −/* panic("chown"); */ | |
| 660 | − return 0; | |
| 841 | + return a; | |
| 661 | 842 | } |
| 662 | 843 | |
| 663 | −DIR* | |
| 664 | −opendir(char *p) | |
| 665 | −{ | |
| 666 | − DIR *d; | |
| 667 | − char path[MAX_PATH]; | |
| 668 | − | |
| 669 | − | |
| 670 | − snprint(path, sizeof(path), "%s/*.*", p); | |
| 671 | − | |
| 672 | − d = mallocz(sizeof(DIR), 1); | |
| 673 | − if(d == 0) | |
| 674 | − return 0; | |
| 675 | − | |
| 676 | − d->index = 0; | |
| 677 | − | |
| 678 | − d->handle = FindFirstFile(path, &d->wfd); | |
| 679 | − if(d->handle == INVALID_HANDLE_VALUE) { | |
| 680 | − free(d); | |
| 681 | − return 0; | |
| 682 | − } | |
| 683 | − | |
| 684 | − d->path = strdup(path); | |
| 685 | − return d; | |
| 686 | −} | |
| 687 | 844 | |
| 688 | 845 | Dev fsdevtab = { |
| 689 | 846 | 'U', |