add remtask before addtask
[AROS.git] / rom / filesys / fat / ops.c
blob8754f80c87bb0f999bc787bf38d1d4a3e344f02b
1 /*
2 * fat-handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007-2014 The AROS Development Team
7 * This program is free software; you can redistribute it and/or modify it
8 * under the same terms as AROS itself.
10 * $Id$
13 #define AROS_ALMOST_COMPATIBLE
15 #include <aros/macros.h>
16 #include <exec/types.h>
17 #include <dos/dos.h>
18 #include <dos/notify.h>
19 #include <proto/exec.h>
21 #include "fat_fs.h"
22 #include "fat_protos.h"
24 #define DEBUG DEBUG_OPS
25 #include "debug.h"
27 #define FREE_CLUSTER_CHAIN(sb,cl) \
28 do { \
29 ULONG cluster = cl; \
30 while (cluster >= 0 && cluster < sb->eoc_mark - 7) { \
31 ULONG next_cluster = GET_NEXT_CLUSTER(sb, cluster); \
32 FreeCluster(sb, cluster); \
33 cluster = next_cluster; \
34 } \
35 } while(0)
38 * this takes a full path and moves to the directory that would contain the
39 * last file in the path. e.g. calling with (dh, "foo/bar/baz", 11) will move to
40 * directory "foo/bar" under the dir specified by dh. dh will become a handle
41 * to the new dir. after the return name will be "baz" and namelen will be 3
43 static LONG MoveToSubdir(struct DirHandle *dh, UBYTE **pname, ULONG *pnamelen) {
44 LONG err;
45 UBYTE *name = *pname, *base, ch, *p;
46 ULONG namelen = *pnamelen, baselen;
47 struct DirEntry de;
49 /* Skip device name (if any) */
50 for (ch = *(p = name); ch != ':' && ch != '\0'; ch = *(++p));
51 if (ch == ':')
53 namelen -= (p - name) + 1;
54 name = p + 1;
57 /* we break the given name into two pieces - the name of the containing
58 * dir, and the name of the new dir to go within it. if the base ends up
59 * empty, then we just use the dirlock */
60 baselen = namelen;
61 base = name;
62 while (baselen > 0) {
63 if (base[baselen-1] != '/')
64 break;
65 baselen--;
67 while (baselen > 0) {
68 if (base[baselen-1] == '/')
69 break;
70 baselen--;
72 namelen -= baselen;
73 name = &base[baselen];
75 D(bug("[fat] base is '"); RawPutChars(base, baselen);
76 bug("', name is '"); RawPutChars(name, namelen); bug("'\n"));
78 if (baselen > 0) {
79 if ((err = GetDirEntryByPath(dh, base, baselen, &de)) != 0) {
80 D(bug("[fat] base not found\n"));
81 return err;
84 if ((err = InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(&de), dh, TRUE)) != 0)
85 return err;
88 *pname = name;
89 *pnamelen = namelen;
91 return 0;
94 LONG OpLockFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, LONG access, struct ExtFileLock **filelock, struct Globals *glob) {
95 /* if they passed in a name, go searching for it */
96 if (namelen != 0)
97 return LockFileByName(dirlock, name, namelen, access, filelock, glob);
99 /* otherwise the empty filename, just make a copy */
100 else if (dirlock != NULL)
101 return CopyLock(dirlock, filelock, glob);
103 /* null dir lock means they want the root */
104 else
105 return LockRoot(access, filelock, glob);
108 void OpUnlockFile(struct ExtFileLock *lock, struct Globals *glob) {
109 if (lock != NULL)
110 FreeLock(lock, glob);
113 LONG OpCopyLock(struct ExtFileLock *lock, struct ExtFileLock **copy, struct Globals *glob) {
114 if (lock != NULL)
115 return CopyLock(lock, copy, glob);
116 else
117 return LockRoot(SHARED_LOCK, copy, glob);
120 LONG OpLockParent(struct ExtFileLock *lock, struct ExtFileLock **parent, struct Globals *glob) {
121 LONG err;
122 struct DirHandle dh;
123 struct DirEntry de;
124 ULONG parent_cluster;
126 /* the root has no parent, but as a special case we have to return success
127 * with the zero lock */
128 if (lock == NULL || lock->gl == &glob->sb->info->root_lock) {
129 *parent = NULL;
130 return 0;
133 /* if we're in the root directory, then the root is our parent */
134 if (lock->gl->dir_cluster == glob->sb->rootdir_cluster)
135 return LockRoot(SHARED_LOCK, parent, glob);
137 /* get the parent dir */
138 InitDirHandle(glob->sb, lock->gl->dir_cluster, &dh, FALSE);
139 if ((err = GetDirEntryByPath(&dh, "/", 1, &de)) != 0) {
140 ReleaseDirHandle(&dh);
141 return err;
144 /* and its cluster */
145 if ((parent_cluster = FIRST_FILE_CLUSTER(&de)) == 0)
146 parent_cluster = glob->sb->rootdir_cluster;
148 /* then we go through the parent dir, looking for a link back to us. we do
149 * this so that we have an entry with the proper name for copying by
150 * LockFile() */
151 InitDirHandle(glob->sb, parent_cluster, &dh, TRUE);
152 while ((err = GetDirEntry(&dh, dh.cur_index + 1, &de)) == 0) {
153 /* don't go past the end */
154 if (de.e.entry.name[0] == 0x00) {
155 err = ERROR_OBJECT_NOT_FOUND;
156 break;
159 /* we found it if it's not empty, and it's not the volume id or a long
160 * name, and it is a directory, and it does point to us */
161 if (de.e.entry.name[0] != 0xe5 &&
162 !(de.e.entry.attr & ATTR_VOLUME_ID) &&
163 de.e.entry.attr & ATTR_DIRECTORY &&
164 FIRST_FILE_CLUSTER(&de) == lock->gl->dir_cluster) {
166 err = LockFile(parent_cluster, dh.cur_index, SHARED_LOCK, parent, glob);
167 break;
171 ReleaseDirHandle(&dh);
172 return err;
176 * obtains a lock on the named file under the given dir. this is the service
177 * routine for DOS Open() (ie FINDINPUT/FINDOUTPUT/FINDUPDATE) and as such may
178 * only return a lock on a file, never on a dir.
180 LONG OpOpenFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, LONG action, struct ExtFileLock **filelock, struct Globals *glob) {
181 LONG err;
182 struct ExtFileLock *lock;
183 struct DirHandle dh;
184 struct DirEntry de;
186 D(bug("[fat] opening file '"); RawPutChars(name, namelen);
187 bug("' in dir at cluster %ld, action %s\n",
188 dirlock != NULL ? dirlock->ioh.first_cluster : 0,
189 action == ACTION_FINDINPUT ? "FINDINPUT" :
190 action == ACTION_FINDOUTPUT ? "FINDOUTPUT" :
191 action == ACTION_FINDUPDATE ? "FINDUPDATE" : "[unknown]"));
193 dh.ioh.sb = NULL; /* Explicitly mark the dirhandle as uninitialised */
195 /* no filename means they're trying to open whatever dirlock is (which
196 * despite the name may not actually be a dir). since there's already an
197 * extant lock, it's never going to be possible to get an exclusive lock,
198 * so this will only work for FINDINPUT (read-only) */
199 if (namelen == 0) {
200 D(bug("[fat] trying to copy passed dir lock\n"));
202 if (action != ACTION_FINDINPUT) {
203 D(bug("[fat] can't copy lock for write (exclusive)\n"));
204 return ERROR_OBJECT_IN_USE;
207 /* dirs can't be opened */
208 if (dirlock == NULL || dirlock->gl->attr & ATTR_DIRECTORY) {
209 D(bug("[fat] dir lock is a directory, which can't be opened\n"));
210 return ERROR_OBJECT_WRONG_TYPE;
213 /* it's a file, just copy the lock */
214 return CopyLock(dirlock, filelock, glob);
217 /* lock the file */
218 err = LockFileByName(dirlock, name, namelen, action == ACTION_FINDINPUT ? SHARED_LOCK : EXCLUSIVE_LOCK, &lock, glob);
220 /* found it */
221 if (err == 0) {
222 D(bug("[fat] found existing file\n"));
224 /* can't open directories */
225 if (lock->gl->attr & ATTR_DIRECTORY) {
226 D(bug("[fat] it's a directory, can't open it\n"));
227 FreeLock(lock, glob);
228 return ERROR_OBJECT_WRONG_TYPE;
231 /* INPUT/UPDATE use the file as/is */
232 if (action != ACTION_FINDOUTPUT) {
233 D(bug("[fat] returning the lock\n"));
234 *filelock = lock;
235 return 0;
238 /* whereas OUTPUT truncates it */
239 D(bug("[fat] handling FINDOUTPUT, so truncating the file\n"));
241 if (lock->gl->attr & ATTR_READ_ONLY) {
242 D(bug("[fat] file is write protected, doing nothing\n"));
243 FreeLock(lock, glob);
244 return ERROR_WRITE_PROTECTED;
247 /* update the dir entry to make the file empty */
248 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh, FALSE);
249 GetDirEntry(&dh, lock->gl->dir_entry, &de);
250 de.e.entry.first_cluster_lo = de.e.entry.first_cluster_hi = 0;
251 de.e.entry.file_size = 0;
252 de.e.entry.attr |= ATTR_ARCHIVE;
253 UpdateDirEntry(&de);
255 D(bug("[fat] set first cluster and size to 0 in directory entry\n"));
257 /* free the clusters */
258 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
259 lock->gl->first_cluster = lock->ioh.first_cluster = 0xffffffff;
260 RESET_HANDLE(&lock->ioh);
261 lock->gl->size = 0;
263 D(bug("[fat] file truncated, returning the lock\n"));
265 /* file is empty, go */
266 *filelock = lock;
268 return 0;
271 /* any error other than "not found" should be taken as-is */
272 if (err != ERROR_OBJECT_NOT_FOUND)
273 return err;
275 /* not found. for INPUT we bail out */
276 if (action == ACTION_FINDINPUT) {
277 D(bug("[fat] file not found, and not creating it\n"));
278 return ERROR_OBJECT_NOT_FOUND;
281 D(bug("[fat] trying to create '"); RawPutChars(name, namelen); bug("'\n"));
283 /* otherwise it's time to create the file. get a handle on the passed dir */
284 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, TRUE)) != 0)
285 return err;
287 /* get down to the correct subdir */
288 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
289 ReleaseDirHandle(&dh);
290 return err;
293 /* if the dir is write protected, can't do anything. root dir is never
294 * write protected */
295 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster) {
296 GetDirEntry(&dh, 0, &de);
297 if (de.e.entry.attr & ATTR_READ_ONLY) {
298 D(bug("[fat] containing dir is write protected, doing nothing\n"));
299 ReleaseDirHandle(&dh);
300 return ERROR_WRITE_PROTECTED;
304 /* create the entry */
305 if ((err = CreateDirEntry(&dh, name, namelen, ATTR_ARCHIVE, 0, &de)) != 0) {
306 ReleaseDirHandle(&dh);
307 return err;
310 /* lock the new file */
311 err = LockFile(de.cluster, de.index, EXCLUSIVE_LOCK, filelock, glob);
313 /* done */
314 ReleaseDirHandle(&dh);
316 if (err == 0) {
317 (*filelock)->do_notify = TRUE;
318 D(bug("[fat] returning lock on new file\n"));
321 return err;
324 /* find the named file in the directory referenced by dirlock, and delete it.
325 * if the file is a directory, it will only be deleted if it's empty */
326 LONG OpDeleteFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, struct Globals *glob) {
327 LONG err;
328 struct ExtFileLock *lock;
329 struct DirHandle dh;
330 struct DirEntry de;
332 D(bug("[fat] deleting file '"); RawPutChars(name, namelen);
333 bug("' in directory at cluster % ld\n", dirlock != NULL ? dirlock->ioh.first_cluster : 0));
335 dh.ioh.sb = NULL;
337 /* obtain a lock on the file. we need an exclusive lock as we don't want
338 * to delete the file if it's in use */
339 if ((err = LockFileByName(dirlock, name, namelen, EXCLUSIVE_LOCK, &lock, glob)) != 0) {
340 D(bug("[fat] couldn't obtain exclusive lock on named file\n"));
341 return err;
344 if (lock->gl->attr & ATTR_READ_ONLY) {
345 D(bug("[fat] file is write protected, doing nothing\n"));
346 FreeLock(lock, glob);
347 return ERROR_DELETE_PROTECTED;
350 /* if it's a directory, we have to make sure it's empty */
351 if (lock->gl->attr & ATTR_DIRECTORY) {
352 D(bug("[fat] file is a directory, making sure it's empty\n"));
354 if ((err = InitDirHandle(lock->ioh.sb, lock->ioh.first_cluster, &dh, FALSE)) != 0) {
355 FreeLock(lock, glob);
356 return err;
359 /* loop over the entries, starting from entry 2 (the first real
360 * entry). skipping unused ones, we look for the end-of-directory
361 * marker. if we find it, the directory is empty. if we find a real
362 * name, it's in use */
363 de.index = 1;
364 while ((err = GetDirEntry(&dh, de.index+1, &de)) == 0) {
365 /* skip unused entries */
366 if (de.e.entry.name[0] == 0xe5)
367 continue;
369 /* end of directory, it's empty */
370 if (de.e.entry.name[0] == 0x00)
371 break;
373 /* otherwise the directory is still in use */
374 D(bug("[fat] directory still has files in it, won't delete it\n"));
376 ReleaseDirHandle(&dh);
377 FreeLock(lock, glob);
378 return ERROR_DIRECTORY_NOT_EMPTY;
381 ReleaseDirHandle(&dh);
384 /* open the containing directory */
385 if ((err = InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh, TRUE)) != 0) {
386 FreeLock(lock, glob);
387 return err;
390 /* if the dir is write protected, can't do anything. root dir is never
391 * write protected */
392 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster) {
393 GetDirEntry(&dh, 0, &de);
394 if (de.e.entry.attr & ATTR_READ_ONLY) {
395 D(bug("[fat] containing dir is write protected, doing nothing\n"));
396 ReleaseDirHandle(&dh);
397 FreeLock(lock, glob);
398 return ERROR_WRITE_PROTECTED;
402 /* get the entry for the file */
403 GetDirEntry(&dh, lock->gl->dir_entry, &de);
405 /* kill it */
406 DeleteDirEntry(&de);
408 /* it's all good */
409 ReleaseDirHandle(&dh);
411 /* now free the clusters the file was using */
412 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
414 /* notify */
415 SendNotifyByLock(lock->ioh.sb, lock->gl);
417 /* this lock is now completely meaningless */
418 FreeLock(lock, glob);
420 D(bug("[fat] deleted '"); RawPutChars(name, namelen); bug("'\n"));
422 return 0;
425 LONG OpRenameFile(struct ExtFileLock *sdirlock, UBYTE *sname, ULONG snamelen, struct ExtFileLock *ddirlock, UBYTE *dname, ULONG dnamelen, struct Globals *glob) {
426 struct DirHandle sdh, ddh;
427 struct DirEntry sde, dde;
428 struct GlobalLock *gl;
429 LONG err;
430 ULONG len;
432 /* get the source dir handle */
433 if ((err = InitDirHandle(glob->sb, sdirlock != NULL ? sdirlock->ioh.first_cluster : 0, &sdh, FALSE)) != 0)
434 return err;
436 /* get down to the correct subdir */
437 if ((err = MoveToSubdir(&sdh, &sname, &snamelen)) != 0) {
438 ReleaseDirHandle(&sdh);
439 return err;
442 /* get the entry */
443 if ((err = GetDirEntryByName(&sdh, sname, snamelen, &sde)) != 0) {
444 ReleaseDirHandle(&sdh);
445 return err;
448 /* now get a handle on the passed dest dir */
449 if ((err = InitDirHandle(glob->sb, ddirlock != NULL ? ddirlock->ioh.first_cluster : 0, &ddh, FALSE)) != 0) {
450 ReleaseDirHandle(&sdh);
451 return err;
454 /* get down to the correct subdir */
455 if ((err = MoveToSubdir(&ddh, &dname, &dnamelen)) != 0) {
456 ReleaseDirHandle(&ddh);
457 ReleaseDirHandle(&sdh);
458 return err;
461 /* check the source and dest dirs. if either is read-only, do nothing */
462 GetDirEntry(&sdh, 0, &dde);
463 if (dde.e.entry.attr & ATTR_READ_ONLY) {
464 D(bug("[fat] source dir is read only, doing nothing\n"));
465 ReleaseDirHandle(&ddh);
466 ReleaseDirHandle(&sdh);
467 return ERROR_WRITE_PROTECTED;
469 GetDirEntry(&ddh, 0, &dde);
470 if (dde.e.entry.attr & ATTR_READ_ONLY) {
471 D(bug("[fat] dest dir is read only, doing nothing\n"));
472 ReleaseDirHandle(&ddh);
473 ReleaseDirHandle(&sdh);
474 return ERROR_WRITE_PROTECTED;
477 /* now see if the wanted name is in this dir. if it exists, do nothing */
478 if ((err = GetDirEntryByName(&ddh, dname, dnamelen, &dde)) == 0) {
479 ReleaseDirHandle(&ddh);
480 ReleaseDirHandle(&sdh);
481 return ERROR_OBJECT_EXISTS;
483 else if (err != ERROR_OBJECT_NOT_FOUND) {
484 ReleaseDirHandle(&ddh);
485 ReleaseDirHandle(&sdh);
486 return err;
489 /* at this point we have the source entry in sde, and we know the dest
490 * doesn't exist */
492 /* XXX if sdh and ddh are the same dir and there's room in the existing
493 * entries for the new name, just overwrite the name */
495 /* make a new entry in the target dir */
496 if ((err = CreateDirEntry(&ddh, dname, dnamelen, sde.e.entry.attr | ATTR_ARCHIVE, (sde.e.entry.first_cluster_hi << 16) | sde.e.entry.first_cluster_lo, &dde)) != 0) {
497 ReleaseDirHandle(&ddh);
498 ReleaseDirHandle(&sdh);
501 /* copy in the leftover attributes */
502 dde.e.entry.create_date = sde.e.entry.create_date;
503 dde.e.entry.create_time = sde.e.entry.create_time;
504 dde.e.entry.write_date = sde.e.entry.write_date;
505 dde.e.entry.write_time = sde.e.entry.write_time;
506 dde.e.entry.last_access_date = sde.e.entry.last_access_date;
507 dde.e.entry.create_time_tenth = sde.e.entry.create_time_tenth;
508 dde.e.entry.file_size = sde.e.entry.file_size;
510 UpdateDirEntry(&dde);
512 /* update the global lock (if present) with the new dir cluster/entry */
513 ForeachNode(&sdh.ioh.sb->info->locks, gl)
514 if (gl->dir_cluster == sde.cluster && gl->dir_entry == sde.index) {
515 D(bug("[fat] found lock with old dir entry (%ld/%ld), changing to (%ld/%ld)\n", sde.cluster, sde.index, dde.cluster, dde.index));
517 gl->dir_cluster = dde.cluster;
518 gl->dir_entry = dde.index;
520 /* update the filename too */
521 GetDirEntryShortName(&dde, &(gl->name[1]), &len); gl->name[0] = (UBYTE) len;
522 GetDirEntryLongName(&dde, &(gl->name[1]), &len); gl->name[0] = (UBYTE) len;
525 /* delete the original */
526 DeleteDirEntry(&sde);
528 /* notify */
529 SendNotifyByDirEntry(sdh.ioh.sb, &dde);
531 ReleaseDirHandle(&ddh);
532 ReleaseDirHandle(&sdh);
534 return 0;
537 LONG OpCreateDir(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, struct ExtFileLock **newdirlock, struct Globals *glob) {
538 LONG err, i;
539 ULONG cluster;
540 struct DirHandle dh, sdh;
541 struct DirEntry de, sde;
543 D(bug("[fat] creating directory '"); RawPutChars(name, namelen);
544 bug("' in directory at cluster %ld\n", dirlock != NULL ? dirlock->ioh.first_cluster : 0));
546 /* get a handle on the passed dir */
547 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE)) != 0)
548 return err;
550 /* get down to the correct subdir */
551 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
552 ReleaseDirHandle(&dh);
553 return err;
556 /* Make sure 'name' is just the FilePart() */
557 for (i = namelen-1; i > 0; i--) {
558 if (name[i] == '/' ||
559 name[i] == ':') {
560 namelen -= (i + 1);
561 name += (i + 1);
562 break;
566 /* if the dir is write protected, can't do anything. root dir is never
567 * write protected */
568 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster) {
569 GetDirEntry(&dh, 0, &de);
570 if (de.e.entry.attr & ATTR_READ_ONLY) {
571 D(bug("[fat] containing dir is write protected, doing nothing\n"));
572 ReleaseDirHandle(&dh);
573 return ERROR_WRITE_PROTECTED;
577 /* now see if the wanted name is in this dir. if it exists, then we do
578 * nothing */
579 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) == 0) {
580 D(bug("[fat] name exists, can't do anything\n"));
581 ReleaseDirHandle(&dh);
582 return ERROR_OBJECT_EXISTS;
585 /* find a free cluster to store the dir in */
586 if ((err = FindFreeCluster(dh.ioh.sb, &cluster)) != 0) {
587 ReleaseDirHandle(&dh);
588 return err;
591 /* allocate it */
592 AllocCluster(dh.ioh.sb, cluster);
594 D(bug("[fat] allocated cluster %ld for directory\n", cluster));
596 /* create the entry, pointing to the new cluster */
597 if ((err = CreateDirEntry(&dh, name, namelen, ATTR_DIRECTORY | ATTR_ARCHIVE, cluster, &de)) != 0) {
598 /* deallocate the cluster */
599 FreeCluster(dh.ioh.sb, cluster);
601 ReleaseDirHandle(&dh);
602 return err;
605 /* now get a handle on the new directory */
606 InitDirHandle(dh.ioh.sb, cluster, &sdh, FALSE);
608 /* create the dot entry. it's a direct copy of the just-created entry, but
609 * with a different name */
610 GetDirEntry(&sdh, 0, &sde);
611 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
612 CopyMem(". ", &sde.e.entry.name, 11);
613 UpdateDirEntry(&sde);
615 /* create the dot-dot entry. again, a copy, with the cluster pointer setup
616 * to point to the parent */
617 GetDirEntry(&sdh, 1, &sde);
618 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
619 CopyMem(".. ", &sde.e.entry.name, 11);
620 cluster = dh.ioh.first_cluster;
621 if (cluster == dh.ioh.sb->rootdir_cluster)
622 cluster = 0;
623 sde.e.entry.first_cluster_lo = cluster & 0xffff;
624 sde.e.entry.first_cluster_hi = cluster >> 16;
625 UpdateDirEntry(&sde);
627 /* clear all remaining entries (the first of which marks the end of the
628 * directory) */
629 for (i = 2; GetDirEntry(&sdh, i, &sde) == 0; i++) {
630 memset(&sde.e.entry, 0, sizeof(struct FATDirEntry));
631 UpdateDirEntry(&sde);
634 /* new dir created */
635 ReleaseDirHandle(&sdh);
637 /* now obtain a lock on the new dir */
638 err = LockFile(de.cluster, de.index, SHARED_LOCK, newdirlock, glob);
640 /* done */
641 ReleaseDirHandle(&dh);
643 /* notify */
644 SendNotifyByLock((*newdirlock)->ioh.sb, (*newdirlock)->gl);
646 return err;
649 LONG OpRead(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *read, struct Globals *glob) {
650 LONG err;
652 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want, lock->pos));
654 if (want == 0)
655 return 0;
657 if (want + lock->pos > lock->gl->size) {
658 want = lock->gl->size - lock->pos;
659 D(bug("[fat] full read would take us past end-of-file, adjusted want to %ld bytes\n", want));
662 if ((err = ReadFileChunk(&(lock->ioh), lock->pos, want, data, read)) == 0) {
663 lock->pos += *read;
664 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read, lock->pos));
667 return err;
670 LONG OpWrite(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *written, struct Globals *glob) {
671 LONG err;
672 BOOL update_entry = FALSE;
673 struct DirHandle dh;
674 struct DirEntry de;
676 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want, lock->pos));
678 /* need an exclusive lock */
679 if (lock->gl->access != EXCLUSIVE_LOCK) {
680 D(bug("[fat] can't modify global attributes via a shared lock\n"));
681 return ERROR_OBJECT_IN_USE;
684 /* don't modify the file if it's protected */
685 if (lock->gl->attr & ATTR_READ_ONLY) {
686 D(bug("[fat] file is write protected\n"));
687 return ERROR_WRITE_PROTECTED;
690 if (want == 0) {
691 *written = 0;
692 return 0;
695 /* if this is the first write, make a note as we'll have to store the
696 * first cluster in the directory entry later */
697 if (lock->ioh.first_cluster == 0xffffffff)
698 update_entry = TRUE;
700 if ((err = WriteFileChunk(&(lock->ioh), lock->pos, want, data, written)) == 0) {
701 /* if nothing was written but success was returned (can that even
702 * happen?) then we don't want to mess with the dir entry */
703 if (*written == 0) {
704 D(bug("[fat] nothing successfully written (!), nothing else to do\n"));
705 return 0;
708 /* something changed, we need to tell people about it */
709 lock->do_notify = TRUE;
711 /* move to the end of the area written */
712 lock->pos += *written;
714 /* update the dir entry if the size changed */
715 if (lock->pos > lock->gl->size) {
716 lock->gl->size = lock->pos;
717 update_entry = TRUE;
720 /* force an update if the file hasn't already got an archive bit. this
721 * will happen if this was the first write to an existing file that
722 * didn't cause it to grow */
723 else if (!(lock->gl->attr & ATTR_ARCHIVE))
724 update_entry = TRUE;
726 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n", *written, lock->pos, lock->gl->size));
728 if (update_entry) {
729 D(bug("[fat] updating dir entry, first cluster is %ld, size is %ld\n", lock->ioh.first_cluster, lock->gl->size));
731 lock->gl->first_cluster = lock->ioh.first_cluster;
733 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh, FALSE);
734 GetDirEntry(&dh, lock->gl->dir_entry, &de);
736 de.e.entry.file_size = lock->gl->size;
737 de.e.entry.first_cluster_lo = lock->gl->first_cluster & 0xffff;
738 de.e.entry.first_cluster_hi = lock->gl->first_cluster >> 16;
740 de.e.entry.attr |= ATTR_ARCHIVE;
741 UpdateDirEntry(&de);
743 ReleaseDirHandle(&dh);
747 return err;
750 LONG OpSetFileSize(struct ExtFileLock *lock, LONG offset, LONG whence, LONG *newsize, struct Globals *glob) {
751 LONG err;
752 LONG size;
753 struct DirHandle dh;
754 struct DirEntry de;
755 ULONG want, count;
756 ULONG cl, next, first, last;
758 /* need an exclusive lock to do what is effectively a write */
759 if (lock->gl->access != EXCLUSIVE_LOCK) {
760 D(bug("[fat] can't modify global attributes via a shared lock\n"));
761 return ERROR_OBJECT_IN_USE;
764 /* don't modify the file if it's protected */
765 if (lock->gl->attr & ATTR_READ_ONLY) {
766 D(bug("[fat] file is write protected\n"));
767 return ERROR_WRITE_PROTECTED;
770 /* calculate the new length based on the current position */
771 if (whence == OFFSET_BEGINNING && offset >= 0)
772 size = offset;
773 else if (whence == OFFSET_CURRENT && lock->pos + offset>= 0)
774 size = lock->pos + offset;
775 else if (whence == OFFSET_END && offset <= 0 && lock->gl->size + offset >= 0)
776 size = lock->gl->size + offset;
777 else
778 return ERROR_SEEK_ERROR;
780 if (lock->gl->size == size) {
781 D(bug("[fat] new size matches old size, nothing to do\n"));
782 *newsize = size;
783 return 0;
786 D(bug("[fat] old size was %ld bytes, new size is %ld bytes\n", lock->gl->size, size));
788 /* get the dir that this file is in */
789 if ((err = InitDirHandle(glob->sb, lock->gl->dir_cluster, &dh, FALSE)) != 0)
790 return err;
792 /* and the entry */
793 if ((err = GetDirEntry(&dh, lock->gl->dir_entry, &de)) != 0) {
794 ReleaseDirHandle(&dh);
795 return err;
798 /* calculate how many clusters we need */
799 want = (size >> glob->sb->clustersize_bits) + ((size & (glob->sb->clustersize-1)) ? 1 : 0);
801 D(bug("[fat] want %ld clusters for file\n", want));
803 /* we're getting three things here - the first cluster of the existing
804 * file, the last cluster of the existing file (which might be the same),
805 * and the number of clusters currently allocated to it (it's not safe to
806 * infer it from the current size as a broken fat implementation may have
807 * allocated it more than it needs). we handle file shrinking/truncation
808 * here as it falls out naturally from following the current cluster chain
811 cl = FIRST_FILE_CLUSTER(&de);
812 if (cl == 0) {
813 D(bug("[fat] file is empty\n"));
815 first = 0;
816 count = 0;
819 else if (want == 0) {
820 /* if we're fully truncating the file, then the below loop will
821 * actually not truncate the file at all (count will get incremented
822 * past want first time around the loop). it's a pain to incorporate a
823 * full truncate into the loop, not counting the change to the first
824 * cluster, so it's easier to just take care of it all here */
825 D(bug("[fat] want nothing, so truncating the entire file\n"));
827 FREE_CLUSTER_CHAIN(glob->sb, cl);
829 /* now it has nothing */
830 first = 0;
831 count = 0;
834 else {
835 first = cl;
836 count = 0;
838 /* do the actual count */
839 while ((last = GET_NEXT_CLUSTER(glob->sb, cl))
840 < glob->sb->eoc_mark - 7) {
841 count++;
842 cl = last;
844 /* if we get as many clusters as we want, kill everything after it */
845 if (count == want) {
846 FREE_CLUSTER_CHAIN(glob->sb, GET_NEXT_CLUSTER(glob->sb, cl));
847 SET_NEXT_CLUSTER(glob->sb, cl, glob->sb->eoc_mark);
849 D(bug("[fat] truncated file\n"));
851 break;
855 D(bug("[fat] file has %ld clusters\n", count));
858 /* now we know how big the current file is. if we don't have enough,
859 * allocate more until we do */
860 if (count < want) {
861 D(bug("[fat] growing file\n"));
863 while (count < want) {
864 if ((err = FindFreeCluster(glob->sb, &next)) != 0) {
865 /* XXX probably no free clusters left. we should clean up the
866 * extras we allocated before returning. it won't hurt
867 * anything to leave them but it is dead space */
868 ReleaseDirHandle(&dh);
869 return err;
872 /* mark the cluster used */
873 AllocCluster(glob->sb, next);
875 /* if the file had no clusters, then this is the first and we
876 * need to note it for later storage in the direntry */
877 if (cl == 0)
878 first = next;
880 /* otherwise, hook it up to the current one */
881 else
882 SET_NEXT_CLUSTER(glob->sb, cl, next);
884 /* one more */
885 count++;
886 cl = next;
890 /* clusters are fixed, now update the directory entry */
891 de.e.entry.first_cluster_lo = first & 0xffff;
892 de.e.entry.first_cluster_hi = first >> 16;
893 de.e.entry.file_size = size;
894 de.e.entry.attr |= ATTR_ARCHIVE;
895 UpdateDirEntry(&de);
897 D(bug("[fat] set file size to %ld, first cluster is %ld\n", size, first));
899 /* done! */
900 *newsize = size;
902 return 0;
905 LONG OpSetProtect(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, ULONG prot, struct Globals *glob) {
906 LONG err;
907 struct DirHandle dh;
908 struct DirEntry de;
910 /* get the dir handle */
911 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE)) != 0)
912 return err;
914 /* get down to the correct subdir */
915 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
916 ReleaseDirHandle(&dh);
917 return err;
920 /* can't change permissions on the root */
921 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0) {
922 D(bug("[fat] can't set protection on root dir\n"));
923 ReleaseDirHandle(&dh);
924 return ERROR_INVALID_LOCK;
927 /* get the entry */
928 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) != 0) {
929 ReleaseDirHandle(&dh);
930 return err;
933 /* set the attributes */
934 de.e.entry.attr &= ~(ATTR_ARCHIVE | ATTR_READ_ONLY);
935 de.e.entry.attr |= (prot & FIBF_ARCHIVE ? ATTR_ARCHIVE : 0);
937 /* only set read-only if neither writable nor deletable */
938 if ((prot & (FIBF_WRITE | FIBF_DELETE)) == (FIBF_WRITE | FIBF_DELETE))
939 de.e.entry.attr |= ATTR_READ_ONLY;
940 UpdateDirEntry(&de);
942 D(bug("[fat] new protection is 0x%08x\n", de.e.entry.attr));
944 SendNotifyByDirEntry(glob->sb, &de);
946 /* if it's a directory, we also need to update the protections for the
947 * directory's . entry */
948 if (de.e.entry.attr & ATTR_DIRECTORY) {
949 ULONG attr = de.e.entry.attr;
951 D(bug("[fat] setting protections for directory '.' entry\n"));
953 InitDirHandle(glob->sb, FIRST_FILE_CLUSTER(&de), &dh, TRUE);
954 GetDirEntry(&dh, 0, &de);
955 de.e.entry.attr = attr;
956 UpdateDirEntry(&de);
959 ReleaseDirHandle(&dh);
961 return 0;
964 LONG OpSetDate(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, struct DateStamp *ds, struct Globals *glob) {
965 LONG err;
966 struct DirHandle dh;
967 struct DirEntry de;
969 /* get the dir handle */
970 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE)) != 0)
971 return err;
973 /* get down to the correct subdir */
974 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
975 ReleaseDirHandle(&dh);
976 return err;
979 /* can't set date on the root */
980 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0) {
981 D(bug("[fat] can't set date on root dir\n"));
982 ReleaseDirHandle(&dh);
983 return ERROR_INVALID_LOCK;
986 /* get the entry */
987 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) != 0) {
988 ReleaseDirHandle(&dh);
989 return err;
992 /* set and update the date */
993 ConvertDOSDate(ds, &de.e.entry.write_date, &de.e.entry.write_time, glob);
994 de.e.entry.last_access_date = de.e.entry.write_date;
995 UpdateDirEntry(&de);
997 SendNotifyByDirEntry(glob->sb, &de);
999 ReleaseDirHandle(&dh);
1001 return 0;
1004 LONG OpAddNotify(struct NotifyRequest *nr, struct Globals *glob) {
1005 LONG err;
1006 struct DirHandle dh;
1007 struct DirEntry de;
1008 struct GlobalLock *gl = NULL, *tmp;
1009 struct NotifyNode *nn;
1010 BOOL exists = FALSE;
1012 D(bug("[fat] trying to add notification for '%s'\n", nr->nr_FullName));
1014 /* if the request is for the volume root, then we just link to the root lock */
1015 if (nr->nr_FullName[strlen(nr->nr_FullName)-1] == ':') {
1016 D(bug("[fat] adding notify for root dir\n"));
1017 gl = &glob->sb->info->root_lock;
1020 else {
1021 if ((err = InitDirHandle(glob->sb, 0, &dh, FALSE)) != 0)
1022 return err;
1024 /* look for the entry */
1025 err = GetDirEntryByPath(&dh, nr->nr_FullName, strlen(nr->nr_FullName), &de);
1026 if (err != 0 && err != ERROR_OBJECT_NOT_FOUND)
1027 return err;
1029 /* if it was found, then it might be open. try to find the global lock */
1030 if (err == 0) {
1031 exists = TRUE;
1033 D(bug("[fat] file exists (%ld/%ld), looking for global lock\n", de.cluster, de.index));
1035 ForeachNode(&glob->sb->info->locks, tmp)
1036 if (tmp->dir_cluster == de.cluster && tmp->dir_entry == de.index) {
1037 gl = tmp;
1039 D(bug("[fat] found global lock 0x%0x\n", gl));
1041 break;
1045 else {
1046 exists = FALSE;
1048 D(bug("[fat] file doesn't exist\n"));
1052 if (gl == NULL)
1053 D(bug("[fat] file not currently locked\n"));
1055 /* allocate space for the notify node */
1056 if ((nn = AllocVecPooled(glob->sb->info->mem_pool,
1057 sizeof(struct NotifyNode))) == NULL)
1058 return ERROR_NO_FREE_STORE;
1060 /* plug the bits in */
1061 nn->gl = gl;
1062 nn->nr = nr;
1064 /* add to the list */
1065 ADDTAIL(&glob->sb->info->notifies, nn);
1067 /* tell them that the file exists if they wanted to know */
1068 if (exists && nr->nr_Flags & NRF_NOTIFY_INITIAL)
1069 SendNotify(nr, glob);
1071 D(bug("[fat] now reporting activity on '%s'\n", nr->nr_FullName));
1073 return 0;
1076 LONG OpRemoveNotify(struct NotifyRequest *nr, struct Globals *glob) {
1077 struct FSSuper *sb;
1078 struct NotifyNode *nn, *nn2;
1080 D(bug("[fat] trying to remove notification for '%s'\n", nr->nr_FullName));
1082 /* search inserted volume for the request */
1083 if (glob->sb != NULL) {
1084 ForeachNodeSafe(&glob->sb->info->notifies, nn, nn2) {
1085 if (nn->nr == nr) {
1086 D(bug("[fat] found notify request in list, removing it\n"));
1087 REMOVE(nn);
1088 FreeVecPooled(glob->sb->info->mem_pool, nn);
1089 return 0;
1094 /* search offline volumes for the request */
1095 ForeachNode(&glob->sblist, sb) {
1096 ForeachNodeSafe(&sb->info->notifies, nn, nn2) {
1097 if (nn->nr == nr) {
1098 D(bug("[fat] found notify request in list, removing it\n"));
1099 REMOVE(nn);
1100 FreeVecPooled(sb->info->mem_pool, nn);
1101 AttemptDestroyVolume(sb);
1102 return 0;
1107 D(bug("[fat] not found, doing nothing\n"));
1109 return 0;