includes: Add definitions for AROS_UFP2S to the generic asmcall.h
[AROS.git] / rom / filesys / fat / ops.c
blobda81e4bdda9d59561eec6abd0e239969d9645847
1 /*
2 * fat.handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007-2011 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;
46 ULONG namelen = *pnamelen, baselen;
47 struct DirEntry de;
49 /* we break the given name into two pieces - the name of the containing
50 * dir, and the name of the new dir to go within it. if the base ends up
51 * empty, then we just use the dirlock */
52 baselen = namelen;
53 base = name;
54 while (baselen > 0) {
55 if (base[baselen-1] != '/')
56 break;
57 baselen--;
59 while (baselen > 0) {
60 if (base[baselen-1] == '/')
61 break;
62 baselen--;
64 namelen -= baselen;
65 name = &base[baselen];
67 D(bug("[fat] base is '"); RawPutChars(base, baselen);
68 bug("', name is '"); RawPutChars(name, namelen); bug("'\n"));
70 if (baselen > 0) {
71 if ((err = GetDirEntryByPath(dh, base, baselen, &de)) != 0) {
72 D(bug("[fat] base not found\n"));
73 return err;
76 if ((err = InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(&de), dh, TRUE)) != 0)
77 return err;
80 *pname = name;
81 *pnamelen = namelen;
83 return 0;
86 LONG OpLockFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, LONG access, struct ExtFileLock **filelock) {
87 /* if they passed in a name, go searching for it */
88 if (namelen != 0)
89 return LockFileByName(dirlock, name, namelen, access, filelock);
91 /* otherwise the empty filename, just make a copy */
92 else if (dirlock != NULL)
93 return CopyLock(dirlock, filelock);
95 /* null dir lock means they want the root */
96 else
97 return LockRoot(access, filelock);
100 void OpUnlockFile(struct ExtFileLock *lock) {
101 if (lock != NULL)
102 FreeLock(lock);
105 LONG OpCopyLock(struct ExtFileLock *lock, struct ExtFileLock **copy) {
106 if (lock != NULL)
107 return CopyLock(lock, copy);
108 else
109 return LockRoot(SHARED_LOCK, copy);
112 LONG OpLockParent(struct ExtFileLock *lock, struct ExtFileLock **parent) {
113 LONG err;
114 struct DirHandle dh;
115 struct DirEntry de;
116 ULONG parent_cluster;
118 /* the root has no parent, but as a special case we have to return success
119 * with the zero lock */
120 if (lock == NULL || lock->gl == &glob->sb->info->root_lock) {
121 *parent = NULL;
122 return 0;
125 /* if we're in the root directory, then the root is our parent */
126 if (lock->gl->dir_cluster == glob->sb->rootdir_cluster)
127 return LockRoot(SHARED_LOCK, parent);
129 /* get the parent dir */
130 InitDirHandle(glob->sb, lock->gl->dir_cluster, &dh, FALSE);
131 if ((err = GetDirEntryByPath(&dh, "/", 1, &de)) != 0) {
132 ReleaseDirHandle(&dh);
133 return err;
136 /* and its cluster */
137 parent_cluster = FIRST_FILE_CLUSTER(&de);
139 /* then we go through the parent dir, looking for a link back to us. we do
140 * this so that we have an entry with the proper name for copying by
141 * LockFile() */
142 InitDirHandle(glob->sb, parent_cluster, &dh, TRUE);
143 while ((err = GetDirEntry(&dh, dh.cur_index + 1, &de)) == 0) {
144 /* don't go past the end */
145 if (de.e.entry.name[0] == 0x00) {
146 err = ERROR_OBJECT_NOT_FOUND;
147 break;
150 /* we found it if it's not empty, and it's not the volume id or a long
151 * name, and it is a directory, and it does point to us */
152 if (de.e.entry.name[0] != 0xe5 &&
153 !(de.e.entry.attr & ATTR_VOLUME_ID) &&
154 de.e.entry.attr & ATTR_DIRECTORY &&
155 FIRST_FILE_CLUSTER(&de) == lock->gl->dir_cluster) {
157 err = LockFile(parent_cluster, dh.cur_index, SHARED_LOCK, parent);
158 break;
162 ReleaseDirHandle(&dh);
163 return err;
167 * obtains a lock on the named file under the given dir. this is the service
168 * routine for DOS Open() (ie FINDINPUT/FINDOUTPUT/FINDUPDATE) and as such may
169 * only return a lock on a file, never on a dir.
171 LONG OpOpenFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, LONG action, struct ExtFileLock **filelock) {
172 LONG err;
173 struct ExtFileLock *lock;
174 struct DirHandle dh;
175 struct DirEntry de;
177 D(bug("[fat] opening file '"); RawPutChars(name, namelen);
178 bug("' in dir at cluster %ld, action %s\n",
179 dirlock != NULL ? dirlock->ioh.first_cluster : 0,
180 action == ACTION_FINDINPUT ? "FINDINPUT" :
181 action == ACTION_FINDOUTPUT ? "FINDOUTPUT" :
182 action == ACTION_FINDUPDATE ? "FINDUPDATE" : "[unknown]"));
184 dh.ioh.sb = NULL; /* Explicitly mark the dirhandle as uninitialised */
186 /* no filename means they're trying to open whatever dirlock is (which
187 * despite the name may not actually be a dir). since there's already an
188 * extant lock, it's never going to be possible to get an exclusive lock,
189 * so this will only work for FINDINPUT (read-only) */
190 if (namelen == 0) {
191 D(bug("[fat] trying to copy passed dir lock\n"));
193 if (action != ACTION_FINDINPUT) {
194 D(bug("[fat] can't copy lock for write (exclusive)\n"));
195 return ERROR_OBJECT_IN_USE;
198 /* dirs can't be opened */
199 if (dirlock == NULL || dirlock->gl->attr & ATTR_DIRECTORY) {
200 D(bug("[fat] dir lock is a directory, which can't be opened\n"));
201 return ERROR_OBJECT_WRONG_TYPE;
204 /* it's a file, just copy the lock */
205 return CopyLock(dirlock, filelock);
208 /* lock the file */
209 err = LockFileByName(dirlock, name, namelen, action == ACTION_FINDINPUT ? SHARED_LOCK : EXCLUSIVE_LOCK, &lock);
211 /* found it */
212 if (err == 0) {
213 D(bug("[fat] found existing file\n"));
215 /* can't open directories */
216 if (lock->gl->attr & ATTR_DIRECTORY) {
217 D(bug("[fat] it's a directory, can't open it\n"));
218 FreeLock(lock);
219 return ERROR_OBJECT_WRONG_TYPE;
222 /* INPUT/UPDATE use the file as/is */
223 if (action != ACTION_FINDOUTPUT) {
224 D(bug("[fat] returning the lock\n"));
225 *filelock = lock;
226 return 0;
229 /* whereas OUTPUT truncates it */
230 D(bug("[fat] handling FINDOUTPUT, so truncating the file\n"));
232 if (lock->gl->attr & ATTR_READ_ONLY) {
233 D(bug("[fat] file is write protected, doing nothing\n"));
234 FreeLock(lock);
235 return ERROR_WRITE_PROTECTED;
238 /* update the dir entry to make the file empty */
239 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh, FALSE);
240 GetDirEntry(&dh, lock->gl->dir_entry, &de);
241 de.e.entry.first_cluster_lo = de.e.entry.first_cluster_hi = 0;
242 de.e.entry.file_size = 0;
243 de.e.entry.attr |= ATTR_ARCHIVE;
244 UpdateDirEntry(&de);
246 D(bug("[fat] set first cluster and size to 0 in directory entry\n"));
248 /* free the clusters */
249 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
250 lock->gl->first_cluster = lock->ioh.first_cluster = 0xffffffff;
251 RESET_HANDLE(&lock->ioh);
252 lock->gl->size = 0;
254 D(bug("[fat] file truncated, returning the lock\n"));
256 /* file is empty, go */
257 *filelock = lock;
259 return 0;
262 /* any error other than "not found" should be taken as-is */
263 if (err != ERROR_OBJECT_NOT_FOUND)
264 return err;
266 /* not found. for INPUT we bail out */
267 if (action == ACTION_FINDINPUT) {
268 D(bug("[fat] file not found, and not creating it\n"));
269 return ERROR_OBJECT_NOT_FOUND;
272 D(bug("[fat] trying to create '"); RawPutChars(name, namelen); bug("'\n"));
274 /* otherwise it's time to create the file. get a handle on the passed dir */
275 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, TRUE)) != 0)
276 return err;
278 /* get down to the correct subdir */
279 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
280 ReleaseDirHandle(&dh);
281 return err;
284 /* if the dir is write protected, can't do anything. root dir is never
285 * write protected */
286 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster) {
287 GetDirEntry(&dh, 0, &de);
288 if (de.e.entry.attr & ATTR_READ_ONLY) {
289 D(bug("[fat] containing dir is write protected, doing nothing\n"));
290 ReleaseDirHandle(&dh);
291 return ERROR_WRITE_PROTECTED;
295 /* create the entry */
296 if ((err = CreateDirEntry(&dh, name, namelen, ATTR_ARCHIVE, 0, &de)) != 0) {
297 ReleaseDirHandle(&dh);
298 return err;
301 /* lock the new file */
302 err = LockFile(de.cluster, de.index, EXCLUSIVE_LOCK, filelock);
304 /* done */
305 ReleaseDirHandle(&dh);
307 if (err == 0) {
308 (*filelock)->do_notify = TRUE;
309 D(bug("[fat] returning lock on new file\n"));
312 return err;
315 /* find the named file in the directory referenced by dirlock, and delete it.
316 * if the file is a directory, it will only be deleted if it's empty */
317 LONG OpDeleteFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen) {
318 LONG err;
319 struct ExtFileLock *lock;
320 struct DirHandle dh;
321 struct DirEntry de;
323 D(bug("[fat] deleting file '"); RawPutChars(name, namelen);
324 bug("' in directory at cluster % ld\n", dirlock != NULL ? dirlock->ioh.first_cluster : 0));
326 dh.ioh.sb = NULL;
328 /* obtain a lock on the file. we need an exclusive lock as we don't want
329 * to delete the file if it's in use */
330 if ((err = LockFileByName(dirlock, name, namelen, EXCLUSIVE_LOCK, &lock)) != 0) {
331 D(bug("[fat] couldn't obtain exclusive lock on named file\n"));
332 return err;
335 if (lock->gl->attr & ATTR_READ_ONLY) {
336 D(bug("[fat] file is write protected, doing nothing\n"));
337 FreeLock(lock);
338 return ERROR_DELETE_PROTECTED;
341 /* if it's a directory, we have to make sure it's empty */
342 if (lock->gl->attr & ATTR_DIRECTORY) {
343 D(bug("[fat] file is a directory, making sure it's empty\n"));
345 if ((err = InitDirHandle(lock->ioh.sb, lock->ioh.first_cluster, &dh, FALSE)) != 0) {
346 FreeLock(lock);
347 return err;
350 /* loop over the entries, starting from entry 2 (the first real
351 * entry). skipping unused ones, we look for the end-of-directory
352 * marker. if we find it, the directory is empty. if we find a real
353 * name, it's in use */
354 de.index = 1;
355 while ((err = GetDirEntry(&dh, de.index+1, &de)) == 0) {
356 /* skip unused entries */
357 if (de.e.entry.name[0] == 0xe5)
358 continue;
360 /* end of directory, it's empty */
361 if (de.e.entry.name[0] == 0x00)
362 break;
364 /* otherwise the directory is still in use */
365 D(bug("[fat] directory still has files in it, won't delete it\n"));
367 ReleaseDirHandle(&dh);
368 FreeLock(lock);
369 return ERROR_DIRECTORY_NOT_EMPTY;
372 ReleaseDirHandle(&dh);
375 /* open the containing directory */
376 if ((err = InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh, TRUE)) != 0) {
377 FreeLock(lock);
378 return err;
381 /* if the dir is write protected, can't do anything. root dir is never
382 * write protected */
383 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster) {
384 GetDirEntry(&dh, 0, &de);
385 if (de.e.entry.attr & ATTR_READ_ONLY) {
386 D(bug("[fat] containing dir is write protected, doing nothing\n"));
387 ReleaseDirHandle(&dh);
388 FreeLock(lock);
389 return ERROR_WRITE_PROTECTED;
393 /* get the entry for the file */
394 GetDirEntry(&dh, lock->gl->dir_entry, &de);
396 /* kill it */
397 DeleteDirEntry(&de);
399 /* it's all good */
400 ReleaseDirHandle(&dh);
402 /* now free the clusters the file was using */
403 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
405 /* notify */
406 SendNotifyByLock(lock->ioh.sb, lock->gl);
408 /* this lock is now completely meaningless */
409 FreeLock(lock);
411 D(bug("[fat] deleted '"); RawPutChars(name, namelen); bug("'\n"));
413 return 0;
416 LONG OpRenameFile(struct ExtFileLock *sdirlock, UBYTE *sname, ULONG snamelen, struct ExtFileLock *ddirlock, UBYTE *dname, ULONG dnamelen) {
417 struct DirHandle sdh, ddh;
418 struct DirEntry sde, dde;
419 struct GlobalLock *gl;
420 LONG err;
421 ULONG len;
423 /* get the source dir handle */
424 if ((err = InitDirHandle(glob->sb, sdirlock != NULL ? sdirlock->ioh.first_cluster : 0, &sdh, FALSE)) != 0)
425 return err;
427 /* get down to the correct subdir */
428 if ((err = MoveToSubdir(&sdh, &sname, &snamelen)) != 0) {
429 ReleaseDirHandle(&sdh);
430 return err;
433 /* get the entry */
434 if ((err = GetDirEntryByName(&sdh, sname, snamelen, &sde)) != 0) {
435 ReleaseDirHandle(&sdh);
436 return err;
439 /* now get a handle on the passed dest dir */
440 if ((err = InitDirHandle(glob->sb, ddirlock != NULL ? ddirlock->ioh.first_cluster : 0, &ddh, FALSE)) != 0) {
441 ReleaseDirHandle(&sdh);
442 return err;
445 /* get down to the correct subdir */
446 if ((err = MoveToSubdir(&ddh, &dname, &dnamelen)) != 0) {
447 ReleaseDirHandle(&ddh);
448 ReleaseDirHandle(&sdh);
449 return err;
452 /* check the source and dest dirs. if either is read-only, do nothing */
453 GetDirEntry(&sdh, 0, &dde);
454 if (dde.e.entry.attr & ATTR_READ_ONLY) {
455 D(bug("[fat] source dir is read only, doing nothing\n"));
456 ReleaseDirHandle(&ddh);
457 ReleaseDirHandle(&sdh);
458 return ERROR_WRITE_PROTECTED;
460 GetDirEntry(&ddh, 0, &dde);
461 if (dde.e.entry.attr & ATTR_READ_ONLY) {
462 D(bug("[fat] dest dir is read only, doing nothing\n"));
463 ReleaseDirHandle(&ddh);
464 ReleaseDirHandle(&sdh);
465 return ERROR_WRITE_PROTECTED;
468 /* now see if the wanted name is in this dir. if it exists, do nothing */
469 if ((err = GetDirEntryByName(&ddh, dname, dnamelen, &dde)) == 0) {
470 ReleaseDirHandle(&ddh);
471 ReleaseDirHandle(&sdh);
472 return ERROR_OBJECT_EXISTS;
474 else if (err != ERROR_OBJECT_NOT_FOUND) {
475 ReleaseDirHandle(&ddh);
476 ReleaseDirHandle(&sdh);
477 return err;
480 /* at this point we have the source entry in sde, and we know the dest
481 * doesn't exist */
483 /* XXX if sdh and ddh are the same dir and there's room in the existing
484 * entries for the new name, just overwrite the name */
486 /* make a new entry in the target dir */
487 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) {
488 ReleaseDirHandle(&ddh);
489 ReleaseDirHandle(&sdh);
492 /* copy in the leftover attributes */
493 dde.e.entry.create_date = sde.e.entry.create_date;
494 dde.e.entry.create_time = sde.e.entry.create_time;
495 dde.e.entry.write_date = sde.e.entry.write_date;
496 dde.e.entry.write_time = sde.e.entry.write_time;
497 dde.e.entry.last_access_date = sde.e.entry.last_access_date;
498 dde.e.entry.create_time_tenth = sde.e.entry.create_time_tenth;
499 dde.e.entry.file_size = sde.e.entry.file_size;
501 UpdateDirEntry(&dde);
503 /* update the global lock (if present) with the new dir cluster/entry */
504 ForeachNode(&sdh.ioh.sb->info->locks, gl)
505 if (gl->dir_cluster == sde.cluster && gl->dir_entry == sde.index) {
506 D(bug("[fat] found lock with old dir entry (%ld/%ld), changing to (%ld/%ld)\n", sde.cluster, sde.index, dde.cluster, dde.index));
508 gl->dir_cluster = dde.cluster;
509 gl->dir_entry = dde.index;
511 /* update the filename too */
512 GetDirEntryShortName(&dde, &(gl->name[1]), &len); gl->name[0] = (UBYTE) len;
513 GetDirEntryLongName(&dde, &(gl->name[1]), &len); gl->name[0] = (UBYTE) len;
516 /* delete the original */
517 DeleteDirEntry(&sde);
519 /* notify */
520 SendNotifyByDirEntry(sdh.ioh.sb, &dde);
522 ReleaseDirHandle(&ddh);
523 ReleaseDirHandle(&sdh);
525 return 0;
528 LONG OpCreateDir(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, struct ExtFileLock **newdirlock) {
529 LONG err, i;
530 ULONG cluster;
531 struct DirHandle dh, sdh;
532 struct DirEntry de, sde;
534 D(bug("[fat] creating directory '"); RawPutChars(name, namelen);
535 bug("' in directory at cluster %ld\n", dirlock != NULL ? dirlock->ioh.first_cluster : 0));
537 /* get a handle on the passed dir */
538 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE)) != 0)
539 return err;
541 /* get down to the correct subdir */
542 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
543 ReleaseDirHandle(&dh);
544 return err;
547 /* Make sure 'name' is just the FilePart() */
548 for (i = namelen-1; i > 0; i--) {
549 if (name[i] == '/' ||
550 name[i] == ':') {
551 namelen -= (i + 1);
552 name += (i + 1);
553 break;
557 /* if the dir is write protected, can't do anything. root dir is never
558 * write protected */
559 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster) {
560 GetDirEntry(&dh, 0, &de);
561 if (de.e.entry.attr & ATTR_READ_ONLY) {
562 D(bug("[fat] containing dir is write protected, doing nothing\n"));
563 ReleaseDirHandle(&dh);
564 return ERROR_WRITE_PROTECTED;
568 /* now see if the wanted name is in this dir. if it exists, then we do
569 * nothing */
570 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) == 0) {
571 D(bug("[fat] name exists, can't do anything\n"));
572 ReleaseDirHandle(&dh);
573 return ERROR_OBJECT_EXISTS;
576 /* find a free cluster to store the dir in */
577 if ((err = FindFreeCluster(dh.ioh.sb, &cluster)) != 0) {
578 ReleaseDirHandle(&dh);
579 return err;
582 /* allocate it */
583 AllocCluster(dh.ioh.sb, cluster);
585 D(bug("[fat] allocated cluster %ld for directory\n", cluster));
587 /* create the entry, pointing to the new cluster */
588 if ((err = CreateDirEntry(&dh, name, namelen, ATTR_DIRECTORY | ATTR_ARCHIVE, cluster, &de)) != 0) {
589 /* deallocate the cluster */
590 FreeCluster(dh.ioh.sb, cluster);
592 ReleaseDirHandle(&dh);
593 return err;
596 /* now get a handle on the new directory */
597 InitDirHandle(dh.ioh.sb, cluster, &sdh, FALSE);
599 /* create the dot entry. it's a direct copy of the just-created entry, but
600 * with a different name */
601 GetDirEntry(&sdh, 0, &sde);
602 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
603 CopyMem(". ", &sde.e.entry.name, 11);
604 UpdateDirEntry(&sde);
606 /* create the dot-dot entry. again, a copy, with the cluster pointer setup
607 * to point to the parent */
608 GetDirEntry(&sdh, 1, &sde);
609 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
610 CopyMem(".. ", &sde.e.entry.name, 11);
611 cluster = dh.ioh.first_cluster;
612 if (cluster == dh.ioh.sb->rootdir_cluster)
613 cluster = 0;
614 sde.e.entry.first_cluster_lo = cluster & 0xffff;
615 sde.e.entry.first_cluster_hi = cluster >> 16;
616 UpdateDirEntry(&sde);
618 /* clear all remaining entries (the first of which marks the end of the
619 * directory) */
620 for (i = 2; GetDirEntry(&sdh, i, &sde) == 0; i++) {
621 memset(&sde.e.entry, 0, sizeof(struct FATDirEntry));
622 UpdateDirEntry(&sde);
625 /* new dir created */
626 ReleaseDirHandle(&sdh);
628 /* now obtain a lock on the new dir */
629 err = LockFile(de.cluster, de.index, SHARED_LOCK, newdirlock);
631 /* done */
632 ReleaseDirHandle(&dh);
634 /* notify */
635 SendNotifyByLock((*newdirlock)->ioh.sb, (*newdirlock)->gl);
637 return err;
640 LONG OpRead(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *read) {
641 LONG err;
643 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want, lock->pos));
645 if (want == 0)
646 return 0;
648 if (want + lock->pos > lock->gl->size) {
649 want = lock->gl->size - lock->pos;
650 D(bug("[fat] full read would take us past end-of-file, adjusted want to %ld bytes\n", want));
653 if ((err = ReadFileChunk(&(lock->ioh), lock->pos, want, data, read)) == 0) {
654 lock->pos += *read;
655 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read, lock->pos));
658 return err;
661 LONG OpWrite(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *written) {
662 LONG err;
663 BOOL update_entry = FALSE;
664 struct DirHandle dh;
665 struct DirEntry de;
667 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want, lock->pos));
669 /* need an exclusive lock */
670 if (lock->gl->access != EXCLUSIVE_LOCK) {
671 D(bug("[fat] can't modify global attributes via a shared lock\n"));
672 return ERROR_OBJECT_IN_USE;
675 /* don't modify the file if it's protected */
676 if (lock->gl->attr & ATTR_READ_ONLY) {
677 D(bug("[fat] file is write protected\n"));
678 return ERROR_WRITE_PROTECTED;
681 if (want == 0) {
682 *written = 0;
683 return 0;
686 /* if this is the first write, make a note as we'll have to store the
687 * first cluster in the directory entry later */
688 if (lock->ioh.first_cluster == 0xffffffff)
689 update_entry = TRUE;
691 if ((err = WriteFileChunk(&(lock->ioh), lock->pos, want, data, written)) == 0) {
692 /* if nothing was written but success was returned (can that even
693 * happen?) then we don't want to mess with the dir entry */
694 if (*written == 0) {
695 D(bug("[fat] nothing successfully written (!), nothing else to do\n"));
696 return 0;
699 /* something changed, we need to tell people about it */
700 lock->do_notify = TRUE;
702 /* move to the end of the area written */
703 lock->pos += *written;
705 /* update the dir entry if the size changed */
706 if (lock->pos > lock->gl->size) {
707 lock->gl->size = lock->pos;
708 update_entry = TRUE;
711 /* force an update if the file hasn't already got an archive bit. this
712 * will happen if this was the first write to an existing file that
713 * didn't cause it to grow */
714 else if (!(lock->gl->attr & ATTR_ARCHIVE))
715 update_entry = TRUE;
717 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n", *written, lock->pos, lock->gl->size));
719 if (update_entry) {
720 D(bug("[fat] updating dir entry, first cluster is %ld, size is %ld\n", lock->ioh.first_cluster, lock->gl->size));
722 lock->gl->first_cluster = lock->ioh.first_cluster;
724 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh, FALSE);
725 GetDirEntry(&dh, lock->gl->dir_entry, &de);
727 de.e.entry.file_size = lock->gl->size;
728 de.e.entry.first_cluster_lo = lock->gl->first_cluster & 0xffff;
729 de.e.entry.first_cluster_hi = lock->gl->first_cluster >> 16;
731 de.e.entry.attr |= ATTR_ARCHIVE;
732 UpdateDirEntry(&de);
734 ReleaseDirHandle(&dh);
738 return err;
741 LONG OpSetFileSize(struct ExtFileLock *lock, LONG offset, LONG whence, LONG *newsize) {
742 LONG err;
743 LONG size;
744 struct DirHandle dh;
745 struct DirEntry de;
746 ULONG want, count;
747 ULONG cl, next, first, last;
749 /* need an exclusive lock to do what is effectively a write */
750 if (lock->gl->access != EXCLUSIVE_LOCK) {
751 D(bug("[fat] can't modify global attributes via a shared lock\n"));
752 return ERROR_OBJECT_IN_USE;
755 /* don't modify the file if it's protected */
756 if (lock->gl->attr & ATTR_READ_ONLY) {
757 D(bug("[fat] file is write protected\n"));
758 return ERROR_WRITE_PROTECTED;
761 /* calculate the new length based on the current position */
762 if (whence == OFFSET_BEGINNING && offset >= 0)
763 size = offset;
764 else if (whence == OFFSET_CURRENT && lock->pos + offset>= 0)
765 size = lock->pos + offset;
766 else if (whence == OFFSET_END && offset <= 0 && lock->gl->size + offset >= 0)
767 size = lock->gl->size + offset;
768 else
769 return ERROR_SEEK_ERROR;
771 if (lock->gl->size == size) {
772 D(bug("[fat] new size matches old size, nothing to do\n"));
773 return 0;
776 D(bug("[fat] old size was %ld bytes, new size is %ld bytes\n", lock->gl->size, size));
778 /* get the dir that this file is in */
779 if ((err = InitDirHandle(glob->sb, lock->gl->dir_cluster, &dh, FALSE)) != 0)
780 return err;
782 /* and the entry */
783 if ((err = GetDirEntry(&dh, lock->gl->dir_entry, &de)) != 0) {
784 ReleaseDirHandle(&dh);
785 return err;
788 /* calculate how many clusters we need */
789 want = (size >> glob->sb->clustersize_bits) + ((size & (glob->sb->clustersize-1)) ? 1 : 0);
791 D(bug("[fat] want %ld clusters for file\n", want));
793 /* we're getting three things here - the first cluster of the existing
794 * file, the last cluster of the existing file (which might be the same),
795 * and the number of clusters currently allocated to it (it's not safe to
796 * infer it from the current size as a broken fat implementation may have
797 * allocated it more than it needs). we handle file shrinking/truncation
798 * here as it falls out naturally from following the current cluster chain
801 cl = FIRST_FILE_CLUSTER(&de);
802 if (cl == 0) {
803 D(bug("[fat] file is empty\n"));
805 first = 0;
806 count = 0;
809 else if (want == 0) {
810 /* if we're fully truncating the file, then the below loop will
811 * actually not truncate the file at all (count will get incremented
812 * past want first time around the loop). it's a pain to incorporate a
813 * full truncate into the loop, not counting the change to the first
814 * cluster, so it's easier to just take care of it all here */
815 D(bug("[fat] want nothing, so truncating the entire file\n"));
817 FREE_CLUSTER_CHAIN(glob->sb, cl);
819 /* now it has nothing */
820 first = 0;
821 count = 0;
824 else {
825 first = cl;
826 count = 0;
828 /* do the actual count */
829 while ((last = GET_NEXT_CLUSTER(glob->sb, cl))
830 < glob->sb->eoc_mark - 7) {
831 count++;
832 cl = last;
834 /* if we get as many clusters as we want, kill everything after it */
835 if (count == want) {
836 FREE_CLUSTER_CHAIN(glob->sb, GET_NEXT_CLUSTER(glob->sb, cl));
837 SET_NEXT_CLUSTER(glob->sb, cl, glob->sb->eoc_mark);
839 D(bug("[fat] truncated file\n"));
841 break;
845 D(bug("[fat] file has %ld clusters\n", count));
848 /* now we know how big the current file is. if we don't have enough,
849 * allocate more until we do */
850 if (count < want) {
851 D(bug("[fat] growing file\n"));
853 while (count < want) {
854 if ((err = FindFreeCluster(glob->sb, &next)) != 0) {
855 /* XXX probably no free clusters left. we should clean up the
856 * extras we allocated before returning. it won't hurt
857 * anything to leave them but it is dead space */
858 ReleaseDirHandle(&dh);
859 return err;
862 /* mark the cluster used */
863 AllocCluster(glob->sb, next);
865 /* if the file had no clusters, then this is the first and we
866 * need to note it for later storage in the direntry */
867 if (cl == 0)
868 first = next;
870 /* otherwise, hook it up to the current one */
871 else
872 SET_NEXT_CLUSTER(glob->sb, cl, next);
874 /* one more */
875 count++;
876 cl = next;
880 /* clusters are fixed, now update the directory entry */
881 de.e.entry.first_cluster_lo = first & 0xffff;
882 de.e.entry.first_cluster_hi = first >> 16;
883 de.e.entry.file_size = size;
884 de.e.entry.attr |= ATTR_ARCHIVE;
885 UpdateDirEntry(&de);
887 D(bug("[fat] set file size to %ld, first cluster is %ld\n", size, first));
889 /* done! */
890 *newsize = size;
892 return 0;
895 LONG OpSetProtect(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, ULONG prot) {
896 LONG err;
897 struct DirHandle dh;
898 struct DirEntry de;
900 /* get the dir handle */
901 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE)) != 0)
902 return err;
904 /* get down to the correct subdir */
905 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
906 ReleaseDirHandle(&dh);
907 return err;
910 /* can't change permissions on the root */
911 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0) {
912 D(bug("[fat] can't set protection on root dir\n"));
913 ReleaseDirHandle(&dh);
914 return ERROR_INVALID_LOCK;
917 /* get the entry */
918 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) != 0) {
919 ReleaseDirHandle(&dh);
920 return err;
923 /* set the attributes */
924 de.e.entry.attr &= ~(ATTR_ARCHIVE | ATTR_READ_ONLY);
925 de.e.entry.attr |= (prot & FIBF_ARCHIVE ? ATTR_ARCHIVE : 0);
927 /* only set read-only if neither writable nor deletable */
928 if ((prot & (FIBF_WRITE | FIBF_DELETE)) == (FIBF_WRITE | FIBF_DELETE))
929 de.e.entry.attr |= ATTR_READ_ONLY;
930 UpdateDirEntry(&de);
932 D(bug("[fat] new protection is 0x%08x\n", de.e.entry.attr));
934 SendNotifyByDirEntry(glob->sb, &de);
936 /* if it's a directory, we also need to update the protections for the
937 * directory's . entry */
938 if (de.e.entry.attr & ATTR_DIRECTORY) {
939 ULONG attr = de.e.entry.attr;
941 D(bug("[fat] setting protections for directory '.' entry\n"));
943 InitDirHandle(glob->sb, FIRST_FILE_CLUSTER(&de), &dh, TRUE);
944 GetDirEntry(&dh, 0, &de);
945 de.e.entry.attr = attr;
946 UpdateDirEntry(&de);
949 ReleaseDirHandle(&dh);
951 return 0;
954 LONG OpSetDate(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, struct DateStamp *ds) {
955 LONG err;
956 struct DirHandle dh;
957 struct DirEntry de;
959 /* get the dir handle */
960 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE)) != 0)
961 return err;
963 /* get down to the correct subdir */
964 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
965 ReleaseDirHandle(&dh);
966 return err;
969 /* can't set date on the root */
970 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0) {
971 D(bug("[fat] can't set date on root dir\n"));
972 ReleaseDirHandle(&dh);
973 return ERROR_INVALID_LOCK;
976 /* get the entry */
977 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) != 0) {
978 ReleaseDirHandle(&dh);
979 return err;
982 /* set and update the date */
983 ConvertAROSDate(ds, &de.e.entry.write_date, &de.e.entry.write_time);
984 de.e.entry.last_access_date = de.e.entry.write_date;
985 UpdateDirEntry(&de);
987 SendNotifyByDirEntry(glob->sb, &de);
989 ReleaseDirHandle(&dh);
991 return 0;
994 LONG OpAddNotify(struct NotifyRequest *nr) {
995 LONG err;
996 struct DirHandle dh;
997 struct DirEntry de;
998 struct GlobalLock *gl = NULL, *tmp;
999 struct NotifyNode *nn;
1000 BOOL exists = FALSE;
1002 D(bug("[fat] trying to add notification for '%s'\n", nr->nr_FullName));
1004 /* if the request is for the volume root, then we just link to the root lock */
1005 if (nr->nr_FullName[strlen(nr->nr_FullName)-1] == ':') {
1006 D(bug("[fat] adding notify for root dir\n"));
1007 gl = &glob->sb->info->root_lock;
1010 else {
1011 if ((err = InitDirHandle(glob->sb, 0, &dh, FALSE)) != 0)
1012 return err;
1014 /* look for the entry */
1015 err = GetDirEntryByPath(&dh, nr->nr_FullName, strlen(nr->nr_FullName), &de);
1016 if (err != 0 && err != ERROR_OBJECT_NOT_FOUND)
1017 return err;
1019 /* if it was found, then it might be open. try to find the global lock */
1020 if (err == 0) {
1021 exists = TRUE;
1023 D(bug("[fat] file exists (%ld/%ld), looking for global lock\n", de.cluster, de.index));
1025 ForeachNode(&glob->sb->info->locks, tmp)
1026 if (tmp->dir_cluster == de.cluster && tmp->dir_entry == de.index) {
1027 gl = tmp;
1029 D(bug("[fat] found global lock 0x%0x\n", gl));
1031 break;
1035 else {
1036 exists = FALSE;
1038 D(bug("[fat] file doesn't exist\n"));
1042 if (gl == NULL)
1043 D(bug("[fat] file not currently locked\n"));
1045 /* allocate space for the notify node */
1046 if ((nn = AllocVecPooled(glob->sb->info->mem_pool,
1047 sizeof(struct NotifyNode))) == NULL)
1048 return ERROR_NO_FREE_STORE;
1050 /* plug the bits in */
1051 nn->gl = gl;
1052 nn->nr = nr;
1054 /* add to the list */
1055 ADDTAIL(&glob->sb->info->notifies, nn);
1057 /* tell them that the file exists if they wanted to know */
1058 if (exists && nr->nr_Flags & NRF_NOTIFY_INITIAL)
1059 SendNotify(nr);
1061 D(bug("[fat] now reporting activity on '%s'\n", nr->nr_FullName));
1063 return 0;
1066 LONG OpRemoveNotify(struct NotifyRequest *nr) {
1067 struct FSSuper *sb;
1068 struct NotifyNode *nn, *nn2;
1070 D(bug("[fat] trying to remove notification for '%s'\n", nr->nr_FullName));
1072 /* search inserted volume for the request */
1073 if (glob->sb != NULL) {
1074 ForeachNodeSafe(&glob->sb->info->notifies, nn, nn2) {
1075 if (nn->nr == nr) {
1076 D(bug("[fat] found notify request in list, removing it\n"));
1077 REMOVE(nn);
1078 FreeVecPooled(glob->sb->info->mem_pool, nn);
1079 return 0;
1084 /* search offline volumes for the request */
1085 ForeachNode(&glob->sblist, sb) {
1086 ForeachNodeSafe(&sb->info->notifies, nn, nn2) {
1087 if (nn->nr == nr) {
1088 D(bug("[fat] found notify request in list, removing it\n"));
1089 REMOVE(nn);
1090 FreeVecPooled(sb->info->mem_pool, nn);
1091 AttemptDestroyVolume(sb);
1092 return 0;
1097 D(bug("[fat] not found, doing nothing\n"));
1099 return 0;