define __KERNEL_STRICT_NAMES to avoid inclusion of kernel types on systems that carry...
[cake.git] / workbench / fs / fat / ops.c
blob0cf8ea6c63622dc73df7ed8682700ffd7e473cdf
1 /*
2 * fat.handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007-2008 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) { \
31 ULONG next_cluster = GET_NEXT_CLUSTER(sb, cluster); \
32 SET_NEXT_CLUSTER(sb, cluster, 0); \
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. ie 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->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 /* make sure we can delete it */
440 if (sde.e.entry.attr & ATTR_READ_ONLY) {
441 D(bug("[fat] source file is write protected, doing nothing\n"));
442 ReleaseDirHandle(&sdh);
443 return ERROR_DELETE_PROTECTED;
446 /* now get a handle on the passed dest dir */
447 if ((err = InitDirHandle(glob->sb, ddirlock != NULL ? ddirlock->ioh.first_cluster : 0, &ddh, FALSE)) != 0) {
448 ReleaseDirHandle(&sdh);
449 return err;
452 /* get down to the correct subdir */
453 if ((err = MoveToSubdir(&ddh, &dname, &dnamelen)) != 0) {
454 ReleaseDirHandle(&ddh);
455 ReleaseDirHandle(&sdh);
456 return err;
459 /* check the source and dest dirs. if either are readonly, do nothing */
460 GetDirEntry(&sdh, 0, &dde);
461 if (dde.e.entry.attr & ATTR_READ_ONLY) {
462 D(bug("[fat] source dir is read only, doing nothing\n"));
463 ReleaseDirHandle(&ddh);
464 ReleaseDirHandle(&sdh);
465 return ERROR_WRITE_PROTECTED;
467 GetDirEntry(&ddh, 0, &dde);
468 if (dde.e.entry.attr & ATTR_READ_ONLY) {
469 D(bug("[fat] dest dir is read only, doing nothing\n"));
470 ReleaseDirHandle(&ddh);
471 ReleaseDirHandle(&sdh);
472 return ERROR_WRITE_PROTECTED;
475 /* now see if the wanted name is in this dir. if it exists, do nothing */
476 if ((err = GetDirEntryByName(&ddh, dname, dnamelen, &dde)) == 0) {
477 ReleaseDirHandle(&ddh);
478 ReleaseDirHandle(&sdh);
479 return ERROR_OBJECT_EXISTS;
481 else if (err != ERROR_OBJECT_NOT_FOUND) {
482 ReleaseDirHandle(&ddh);
483 ReleaseDirHandle(&sdh);
484 return err;
487 /* at this point we have the source entry in sde, and we know the dest
488 * doesn't exist */
490 /* XXX if sdh and ddh are the same dir and there's room in the existing
491 * entries for the new name, just overwrite the name */
493 /* make a new entry in the target dir */
494 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) {
495 ReleaseDirHandle(&ddh);
496 ReleaseDirHandle(&sdh);
499 /* copy in the leftover attributes */
500 dde.e.entry.create_date = sde.e.entry.create_date;
501 dde.e.entry.create_time = sde.e.entry.create_time;
502 dde.e.entry.write_date = sde.e.entry.write_date;
503 dde.e.entry.write_time = sde.e.entry.write_time;
504 dde.e.entry.last_access_date = sde.e.entry.last_access_date;
505 dde.e.entry.create_time_tenth = sde.e.entry.create_time_tenth;
506 dde.e.entry.file_size = sde.e.entry.file_size;
508 UpdateDirEntry(&dde);
510 /* update the global lock (if present) with the new dir cluster/entry */
511 ForeachNode(&sdh.ioh.sb->locks, gl)
512 if (gl->dir_cluster == sde.cluster && gl->dir_entry == sde.index) {
513 D(bug("[fat] found lock with old dir entry (%ld/%ld), changing to (%ld/%ld)\n", sde.cluster, sde.index, dde.cluster, dde.index));
515 gl->dir_cluster = dde.cluster;
516 gl->dir_entry = dde.index;
518 /* update the filename too */
519 GetDirEntryShortName(&dde, &(gl->name[1]), &len); gl->name[0] = (UBYTE) len;
520 GetDirEntryLongName(&dde, &(gl->name[1]), &len); gl->name[0] = (UBYTE) len;
523 /* delete the original */
524 DeleteDirEntry(&sde);
526 /* notify */
527 SendNotifyByDirEntry(sdh.ioh.sb, &dde);
529 ReleaseDirHandle(&ddh);
530 ReleaseDirHandle(&sdh);
532 return 0;
535 LONG OpCreateDir(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, struct ExtFileLock **newdirlock) {
536 LONG err;
537 ULONG cluster;
538 struct DirHandle dh, sdh;
539 struct DirEntry de, sde;
541 D(bug("[fat] creating directory '"); RawPutChars(name, namelen);
542 bug("' in directory at cluster %ld\n", dirlock != NULL ? dirlock->ioh.first_cluster : 0));
544 /* get a handle on the passed dir */
545 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE)) != 0)
546 return err;
548 /* get down to the correct subdir */
549 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
550 ReleaseDirHandle(&dh);
551 return err;
554 /* if the dir is write protected, can't do anything. root dir is never
555 * write protected */
556 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster) {
557 GetDirEntry(&dh, 0, &de);
558 if (de.e.entry.attr & ATTR_READ_ONLY) {
559 D(bug("[fat] containing dir is write protected, doing nothing\n"));
560 ReleaseDirHandle(&dh);
561 return ERROR_WRITE_PROTECTED;
565 /* now see if the wanted name is in this dir. if it exists, then we do
566 * nothing */
567 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) == 0) {
568 D(bug("[fat] name exists, can't do anything\n"));
569 ReleaseDirHandle(&dh);
570 return ERROR_OBJECT_EXISTS;
573 /* find a free cluster to store the dir in */
574 if ((err = FindFreeCluster(dh.ioh.sb, &cluster)) != 0) {
575 ReleaseDirHandle(&dh);
576 return err;
579 /* allocate it */
580 SET_NEXT_CLUSTER(dh.ioh.sb, cluster, dh.ioh.sb->eoc_mark);
582 D(bug("[fat] allocated cluster %ld for directory\n", cluster));
584 /* create the entry, pointing to the new cluster */
585 if ((err = CreateDirEntry(&dh, name, namelen, ATTR_DIRECTORY | ATTR_ARCHIVE, cluster, &de)) != 0) {
586 /* deallocate the cluster */
587 SET_NEXT_CLUSTER(dh.ioh.sb, cluster, 0);
589 ReleaseDirHandle(&dh);
590 return err;
593 /* now get a handle on the new directory */
594 InitDirHandle(dh.ioh.sb, cluster, &sdh, FALSE);
596 /* create the dot entry. it's a direct copy of the just-created entry, but
597 * with a different name */
598 GetDirEntry(&sdh, 0, &sde);
599 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
600 CopyMem(". ", &sde.e.entry.name, 11);
601 UpdateDirEntry(&sde);
603 /* create the dot-dot entry. again, a copy, with the cluster pointer setup
604 * to point to the parent */
605 GetDirEntry(&sdh, 1, &sde);
606 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
607 CopyMem(".. ", &sde.e.entry.name, 11);
608 sde.e.entry.first_cluster_lo = dh.ioh.first_cluster & 0xffff;
609 sde.e.entry.first_cluster_hi = dh.ioh.first_cluster >> 16;
610 UpdateDirEntry(&sde);
612 /* put an empty entry at the end to mark end of directory */
613 GetDirEntry(&sdh, 2, &sde);
614 memset(&sde.e.entry, 0, sizeof(struct FATDirEntry));
615 UpdateDirEntry(&sde);
617 /* new dir created */
618 ReleaseDirHandle(&sdh);
620 /* now obtain a lock on the new dir */
621 err = LockFile(de.cluster, de.index, SHARED_LOCK, newdirlock);
623 /* done */
624 ReleaseDirHandle(&dh);
626 /* notify */
627 SendNotifyByLock((*newdirlock)->ioh.sb, (*newdirlock)->gl);
629 return err;
632 LONG OpRead(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *read) {
633 LONG err;
635 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want, lock->pos));
637 if (want == 0)
638 return 0;
640 if (want + lock->pos > lock->gl->size) {
641 want = lock->gl->size - lock->pos;
642 D(bug("[fat] full read would take us past end-of-file, adjusted want to %ld bytes\n", want));
645 if ((err = ReadFileChunk(&(lock->ioh), lock->pos, want, data, read)) == 0) {
646 lock->pos += *read;
647 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read, lock->pos));
650 return err;
653 LONG OpWrite(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *written) {
654 LONG err;
655 BOOL update_entry = FALSE;
656 struct DirHandle dh;
657 struct DirEntry de;
659 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want, lock->pos));
661 /* need an exclusive lock */
662 if (lock->gl->access != EXCLUSIVE_LOCK) {
663 D(bug("[fat] can't modify global attributes via a shared lock\n"));
664 return ERROR_OBJECT_IN_USE;
667 /* don't modify the file if it's protected */
668 if (lock->gl->attr & ATTR_READ_ONLY) {
669 D(bug("[fat] file is write protected\n"));
670 return ERROR_WRITE_PROTECTED;
673 if (want == 0)
674 return 0;
676 /* if this is the first write, make a note as we'll have to store the
677 * first cluster in the directory entry later */
678 if (lock->ioh.first_cluster == 0xffffffff)
679 update_entry = TRUE;
681 if ((err = WriteFileChunk(&(lock->ioh), lock->pos, want, data, written)) == 0) {
682 /* if nothing was written but success was returned (can that even
683 * happen?) then we don't want to mess with the dir entry */
684 if (*written == 0) {
685 D(bug("[fat] nothing successfully written (!), nothing else to do\n"));
686 return 0;
689 /* something changed, we need to tell people about it */
690 lock->do_notify = TRUE;
692 /* move to the end of the area written */
693 lock->pos += *written;
695 /* update the dir entry if the size changed */
696 if (lock->pos > lock->gl->size) {
697 lock->gl->size = lock->pos;
698 update_entry = TRUE;
701 /* force an update if the file hasn't already got an archive bit. this
702 * will happen if this was the first write to an existing file that
703 * didn't cause it to grow */
704 else if (!(lock->gl->attr & ATTR_ARCHIVE))
705 update_entry = TRUE;
707 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n", *written, lock->pos, lock->gl->size));
709 if (update_entry) {
710 D(bug("[fat] updating dir entry, first cluster is %ld, size is %ld\n", lock->ioh.first_cluster, lock->gl->size));
712 lock->gl->first_cluster = lock->ioh.first_cluster;
714 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh, FALSE);
715 GetDirEntry(&dh, lock->gl->dir_entry, &de);
717 de.e.entry.file_size = lock->gl->size;
718 de.e.entry.first_cluster_lo = lock->gl->first_cluster & 0xffff;
719 de.e.entry.first_cluster_hi = lock->gl->first_cluster >> 16;
721 de.e.entry.attr |= ATTR_ARCHIVE;
722 UpdateDirEntry(&de);
724 ReleaseDirHandle(&dh);
728 return err;
731 LONG OpSetFileSize(struct ExtFileLock *lock, LONG offset, LONG whence, LONG *newsize) {
732 LONG err;
733 LONG size;
734 struct DirHandle dh;
735 struct DirEntry de;
736 ULONG want, count;
737 ULONG cl, next, first, last;
739 /* need an exclusive lock to do what is effectively a write */
740 if (lock->gl->access != EXCLUSIVE_LOCK) {
741 D(bug("[fat] can't modify global attributes via a shared lock\n"));
742 return ERROR_OBJECT_IN_USE;
745 /* don't modify the file if it's protected */
746 if (lock->gl->attr & ATTR_READ_ONLY) {
747 D(bug("[fat] file is write protected\n"));
748 return ERROR_WRITE_PROTECTED;
751 /* calculate the new length based on the current position */
752 if (whence == OFFSET_BEGINNING && offset >= 0)
753 size = offset;
754 else if (whence == OFFSET_CURRENT && lock->pos + offset>= 0)
755 size = lock->pos + offset;
756 else if (whence == OFFSET_END && offset <= 0 && lock->gl->size + offset >= 0)
757 size = lock->gl->size + offset;
758 else
759 return ERROR_SEEK_ERROR;
761 if (lock->gl->size == size) {
762 D(bug("[fat] new size matches old size, nothing to do\n"));
763 return 0;
766 D(bug("[fat] old size was %ld bytes, new size is %ld bytes\n", lock->gl->size, size));
768 /* get the dir that this file is in */
769 if ((err = InitDirHandle(glob->sb, lock->gl->dir_cluster, &dh, FALSE)) != 0)
770 return err;
772 /* and the entry */
773 if ((err = GetDirEntry(&dh, lock->gl->dir_entry, &de)) != 0) {
774 ReleaseDirHandle(&dh);
775 return err;
778 /* calculate how many clusters we need */
779 want = (size >> glob->sb->clustersize_bits) + ((size & (glob->sb->clustersize-1)) ? 1 : 0);
781 D(bug("[fat] want %ld clusters for file\n", want));
783 /* we're getting three things here - the first cluster of the existing
784 * file, the last cluster of the existing file (which might be the same),
785 * and the number of clusters currently allocated to it (it's not safe to
786 * infer it from the current size as a broken fat implementation may have
787 * allocated it more than it needs). we handle file shrinking/truncation
788 * here as it falls out naturally from following the current cluster chain
791 cl = FIRST_FILE_CLUSTER(&de);
792 if (cl == 0) {
793 D(bug("[fat] file is empty\n"));
795 first = 0;
796 count = 0;
799 else if (want == 0) {
800 /* if we're fully truncating the file, then the below loop will
801 * actually not truncate the file at all (count will get incremented
802 * past want first time around the loop). it's a pain to incorporate a
803 * full truncate into the loop, not counting the change to the first
804 * cluster, so it's easier to just take care of it all here */
805 D(bug("[fat] want nothing, so truncating the entire file\n"));
807 FREE_CLUSTER_CHAIN(glob->sb, cl);
809 /* now it has nothing */
810 first = 0;
811 count = 0;
814 else {
815 first = cl;
816 count = 0;
818 /* do the actual count */
819 while ((last = GET_NEXT_CLUSTER(glob->sb, cl)) < glob->sb->eoc_mark) {
820 count++;
821 cl = last;
823 /* if we get as many clusters as we want, kill everything after it */
824 if (count == want) {
825 FREE_CLUSTER_CHAIN(glob->sb, GET_NEXT_CLUSTER(glob->sb, cl));
826 SET_NEXT_CLUSTER(glob->sb, cl, glob->sb->eoc_mark);
828 D(bug("[fat] truncated file\n"));
830 break;
834 D(bug("[fat] file has %ld clusters\n", count));
837 /* now we know how big the current file is. if we don't have enough,
838 * allocate more until we do */
839 if (count < want) {
840 D(bug("[fat] growing file\n"));
842 while (count < want) {
843 if ((err = FindFreeCluster(glob->sb, &next)) != 0) {
844 /* XXX probably no free clusters left. we should clean up the
845 * extras we allocated before returning. it won't hurt
846 * anything to leave them but it is dead space */
847 ReleaseDirHandle(&dh);
848 return err;
851 /* mark the cluster used */
852 SET_NEXT_CLUSTER(glob->sb, next, glob->sb->eoc_mark);
854 /* if the file had no clusters, then this is the first and we
855 * need to note it for later storage in the direntry */
856 if (cl == 0)
857 first = next;
859 /* otherwise, hook it up to the current one */
860 else
861 SET_NEXT_CLUSTER(glob->sb, cl, next);
863 /* one more */
864 count++;
865 cl = next;
869 /* clusters are fixed, now update the directory entry */
870 de.e.entry.first_cluster_lo = first & 0xffff;
871 de.e.entry.first_cluster_hi = first >> 16;
872 de.e.entry.file_size = size;
873 de.e.entry.attr |= ATTR_ARCHIVE;
874 UpdateDirEntry(&de);
876 D(bug("[fat] set file size to %ld, first cluster is %ld\n", size, first));
878 /* done! */
879 *newsize = size;
881 return 0;
884 LONG OpSetProtect(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, ULONG prot) {
885 LONG err;
886 struct DirHandle dh;
887 struct DirEntry de;
889 /* fat barely supports anything. all protection bits must be 0, except
890 * ARCHIVE, DELETE and WRITE. additionally, DELETE and WRITE must be the
891 * same */
892 if ((prot & ~(FIBF_ARCHIVE | FIBF_DELETE | FIBF_WRITE)) ||
893 (!(prot & FIBF_DELETE) ^ !(prot & FIBF_WRITE)))
895 return ERROR_BAD_NUMBER;
897 /* get the dir handle */
898 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE)) != 0)
899 return err;
901 /* get down to the correct subdir */
902 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
903 ReleaseDirHandle(&dh);
904 return err;
907 /* can't change permissions on the root */
908 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0) {
909 D(bug("[fat] can't set protection on root dir\n"));
910 ReleaseDirHandle(&dh);
911 return ERROR_INVALID_LOCK;
914 /* get the entry */
915 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) != 0) {
916 ReleaseDirHandle(&dh);
917 return err;
920 /* set the attributes */
921 de.e.entry.attr &= ~(ATTR_ARCHIVE | ATTR_READ_ONLY);
922 de.e.entry.attr |= (prot & FIBF_ARCHIVE ? ATTR_ARCHIVE : 0);
923 de.e.entry.attr |= (prot & FIBF_WRITE ? ATTR_READ_ONLY : 0);
924 UpdateDirEntry(&de);
926 D(bug("[fat] new protection is 0x%08x\n", de.e.entry.attr));
928 SendNotifyByDirEntry(glob->sb, &de);
930 /* if it's a directory, we also need to update the protections for the
931 * directory's . entry */
932 if (de.e.entry.attr & ATTR_DIRECTORY) {
933 ULONG attr = de.e.entry.attr;
935 D(bug("[fat] setting protections for directory '.' entry\n"));
937 InitDirHandle(glob->sb, FIRST_FILE_CLUSTER(&de), &dh, TRUE);
938 GetDirEntry(&dh, 0, &de);
939 de.e.entry.attr = attr;
940 UpdateDirEntry(&de);
943 ReleaseDirHandle(&dh);
945 return 0;
948 LONG OpSetDate(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, struct DateStamp *ds) {
949 LONG err;
950 struct DirHandle dh;
951 struct DirEntry de;
953 /* get the dir handle */
954 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE)) != 0)
955 return err;
957 /* get down to the correct subdir */
958 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
959 ReleaseDirHandle(&dh);
960 return err;
963 /* can't set date on the root */
964 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0) {
965 D(bug("[fat] can't set date on root dir\n"));
966 ReleaseDirHandle(&dh);
967 return ERROR_INVALID_LOCK;
970 /* get the entry */
971 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) != 0) {
972 ReleaseDirHandle(&dh);
973 return err;
976 /* set and update the date */
977 ConvertAROSDate(*ds, &de.e.entry.write_date, &de.e.entry.write_time);
978 de.e.entry.last_access_date = de.e.entry.write_date;
979 UpdateDirEntry(&de);
981 SendNotifyByDirEntry(glob->sb, &de);
983 ReleaseDirHandle(&dh);
985 return 0;
988 LONG OpAddNotify(struct NotifyRequest *nr) {
989 LONG err;
990 struct DirHandle dh;
991 struct DirEntry de;
992 struct GlobalLock *gl = NULL, *tmp;
993 struct NotifyNode *nn;
994 BOOL exists = FALSE;
996 D(bug("[fat] trying to add notification for '%s'\n", nr->nr_FullName));
998 /* if the request is for the volume root, then we just link to the root lock */
999 if (nr->nr_FullName[strlen(nr->nr_FullName)-1] == ':') {
1000 D(bug("[fat] adding notify for root dir\n"));
1001 gl = &glob->sb->root_lock;
1004 else {
1005 if ((err = InitDirHandle(glob->sb, 0, &dh, FALSE)) != 0)
1006 return err;
1008 /* look for the entry */
1009 err = GetDirEntryByPath(&dh, nr->nr_FullName, strlen(nr->nr_FullName), &de);
1010 if (err != 0 && err != ERROR_OBJECT_NOT_FOUND)
1011 return err;
1013 /* if it was found, then it might be open. try to find the global lock */
1014 if (err == 0) {
1015 exists = TRUE;
1017 D(bug("[fat] file exists (%ld/%ld), looking for global lock\n", de.cluster, de.index));
1019 ForeachNode(&glob->sb->locks, tmp)
1020 if (tmp->dir_cluster == de.cluster && tmp->dir_entry == de.index) {
1021 gl = tmp;
1023 D(bug("[fat] found global lock 0x%0x\n", gl));
1025 break;
1029 else {
1030 exists = FALSE;
1032 D(bug("[fat] file doesn't exist\n"));
1036 if (gl == NULL)
1037 D(bug("[fat] file not currently locked\n"));
1039 /* allocate space to for the notify node */
1040 if ((nn = AllocVecPooled(glob->mempool, sizeof(struct NotifyNode))) == NULL)
1041 return ERROR_NO_FREE_STORE;
1043 /* plug the bits in */
1044 nn->gl = gl;
1045 nn->nr = nr;
1047 /* add to the list */
1048 ADDTAIL(&glob->sb->notifies, nn);
1050 /* tell them that the file exists if they wanted to know */
1051 if (exists && nr->nr_Flags & NRF_NOTIFY_INITIAL)
1052 SendNotify(nr);
1054 D(bug("[fat] now reporting activity on '%s'\n", nr->nr_FullName));
1056 return 0;
1059 LONG OpRemoveNotify(struct NotifyRequest *nr) {
1060 struct NotifyNode *nn;
1062 D(bug("[fat] trying to remove notification for '%s'\n", nr->nr_FullName));
1064 ForeachNode(&glob->sb->notifies, nn)
1065 if (nn->nr == nr) {
1066 D(bug("[fat] found notify request in list, removing it\n"));
1067 REMOVE(nn);
1068 FreeVecPooled(glob->mempool, nn);
1069 return 0;
1072 D(bug("[fat] not found, doing nothing\n"));
1074 return 0;