2 * fat-handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007-2015 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.
13 #define AROS_ALMOST_COMPATIBLE
15 #include <aros/macros.h>
16 #include <exec/types.h>
18 #include <dos/notify.h>
19 #include <proto/exec.h>
22 #include "fat_protos.h"
24 #define DEBUG DEBUG_OPS
27 #define FREE_CLUSTER_CHAIN(sb,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; \
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
,
44 ULONG
*pnamelen
, struct Globals
*glob
)
47 UBYTE
*name
= *pname
, *base
, ch
, *p
;
48 ULONG namelen
= *pnamelen
, baselen
;
51 /* Skip device name (if any) */
52 for (ch
= *(p
= name
); ch
!= ':' && ch
!= '\0'; ch
= *(++p
));
55 namelen
-= (p
- name
) + 1;
59 /* we break the given name into two pieces - the name of the containing
60 * dir, and the name of the new dir to go within it. if the base ends up
61 * empty, then we just use the dirlock */
66 if (base
[baselen
- 1] != '/')
72 if (base
[baselen
- 1] == '/')
77 name
= &base
[baselen
];
80 bug("[fat] base is '");
81 RawPutChars(base
, baselen
); bug("', name is '");
82 RawPutChars(name
, namelen
);
88 if ((err
= GetDirEntryByPath(dh
, base
, baselen
, &de
, glob
)) != 0)
90 D(bug("[fat] base not found\n"));
94 if ((err
= InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(&de
), dh
,
105 LONG
OpLockFile(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
,
106 LONG access
, struct ExtFileLock
**filelock
, struct Globals
*glob
)
108 /* if they passed in a name, go searching for it */
110 return LockFileByName(dirlock
, name
, namelen
, access
, filelock
,
113 /* otherwise the empty filename, just make a copy */
114 else if (dirlock
!= NULL
)
115 return CopyLock(dirlock
, filelock
, glob
);
117 /* null dir lock means they want the root */
119 return LockRoot(access
, filelock
, glob
);
122 void OpUnlockFile(struct ExtFileLock
*lock
, struct Globals
*glob
)
125 FreeLock(lock
, glob
);
128 LONG
OpCopyLock(struct ExtFileLock
*lock
, struct ExtFileLock
**copy
,
129 struct Globals
*glob
)
132 return CopyLock(lock
, copy
, glob
);
134 return LockRoot(SHARED_LOCK
, copy
, glob
);
137 LONG
OpLockParent(struct ExtFileLock
*lock
, struct ExtFileLock
**parent
,
138 struct Globals
*glob
)
143 ULONG parent_cluster
;
145 /* the root has no parent, but as a special case we have to return success
146 * with the zero lock */
147 if (lock
== NULL
|| lock
->gl
== &glob
->sb
->info
->root_lock
)
153 /* if we're in the root directory, then the root is our parent */
154 if (lock
->gl
->dir_cluster
== glob
->sb
->rootdir_cluster
)
155 return LockRoot(SHARED_LOCK
, parent
, glob
);
157 /* get the parent dir */
158 InitDirHandle(glob
->sb
, lock
->gl
->dir_cluster
, &dh
, FALSE
, glob
);
159 if ((err
= GetDirEntryByPath(&dh
, "/", 1, &de
, glob
)) != 0)
161 ReleaseDirHandle(&dh
, glob
);
165 /* and its cluster */
166 if ((parent_cluster
= FIRST_FILE_CLUSTER(&de
)) == 0)
167 parent_cluster
= glob
->sb
->rootdir_cluster
;
169 /* then we go through the parent dir, looking for a link back to us. we do
170 * this so that we have an entry with the proper name for copying by
172 InitDirHandle(glob
->sb
, parent_cluster
, &dh
, TRUE
, glob
);
173 while ((err
= GetDirEntry(&dh
, dh
.cur_index
+ 1, &de
, glob
)) == 0)
175 /* don't go past the end */
176 if (de
.e
.entry
.name
[0] == 0x00)
178 err
= ERROR_OBJECT_NOT_FOUND
;
182 /* we found it if it's not empty, and it's not the volume id or a long
183 * name, and it is a directory, and it does point to us */
184 if (de
.e
.entry
.name
[0] != 0xe5 &&
185 !(de
.e
.entry
.attr
& ATTR_VOLUME_ID
) &&
186 de
.e
.entry
.attr
& ATTR_DIRECTORY
&&
187 FIRST_FILE_CLUSTER(&de
) == lock
->gl
->dir_cluster
)
190 LockFile(parent_cluster
, dh
.cur_index
, SHARED_LOCK
, parent
,
196 ReleaseDirHandle(&dh
, glob
);
201 * obtains a lock on the named file under the given dir. this is the service
202 * routine for DOS Open() (ie FINDINPUT/FINDOUTPUT/FINDUPDATE) and as such may
203 * only return a lock on a file, never on a dir.
205 LONG
OpOpenFile(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
,
206 LONG action
, struct ExtFileLock
**filelock
, struct Globals
*glob
)
209 struct ExtFileLock
*lock
;
214 bug("[fat] opening file '");
215 RawPutChars(name
, namelen
);
216 bug("' in dir at cluster %ld, action %s\n",
217 dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0,
218 action
== ACTION_FINDINPUT
? "FINDINPUT" :
219 action
== ACTION_FINDOUTPUT
? "FINDOUTPUT" :
220 action
== ACTION_FINDUPDATE
? "FINDUPDATE" : "[unknown]");
223 /* Explicitly mark the dirhandle as uninitialised */
226 /* no filename means they're trying to open whatever dirlock is (which
227 * despite the name may not actually be a dir). since there's already an
228 * extant lock, it's never going to be possible to get an exclusive lock,
229 * so this will only work for FINDINPUT (read-only) */
232 D(bug("[fat] trying to copy passed dir lock\n"));
234 if (action
!= ACTION_FINDINPUT
)
236 D(bug("[fat] can't copy lock for write (exclusive)\n"));
237 return ERROR_OBJECT_IN_USE
;
240 /* dirs can't be opened */
241 if (dirlock
== NULL
|| dirlock
->gl
->attr
& ATTR_DIRECTORY
)
243 D(bug("[fat] dir lock is a directory, which can't be opened\n"));
244 return ERROR_OBJECT_WRONG_TYPE
;
247 /* it's a file, just copy the lock */
248 return CopyLock(dirlock
, filelock
, glob
);
252 err
= LockFileByName(dirlock
, name
, namelen
,
253 action
== ACTION_FINDINPUT
? SHARED_LOCK
: EXCLUSIVE_LOCK
, &lock
,
259 D(bug("[fat] found existing file\n"));
261 /* can't open directories */
262 if (lock
->gl
->attr
& ATTR_DIRECTORY
)
264 D(bug("[fat] it's a directory, can't open it\n"));
265 FreeLock(lock
, glob
);
266 return ERROR_OBJECT_WRONG_TYPE
;
269 /* INPUT/UPDATE use the file as/is */
270 if (action
!= ACTION_FINDOUTPUT
)
272 D(bug("[fat] returning the lock\n"));
277 /* whereas OUTPUT truncates it */
278 D(bug("[fat] handling FINDOUTPUT, so truncating the file\n"));
280 if (lock
->gl
->attr
& ATTR_READ_ONLY
)
282 D(bug("[fat] file is write protected, doing nothing\n"));
283 FreeLock(lock
, glob
);
284 return ERROR_WRITE_PROTECTED
;
287 /* update the dir entry to make the file empty */
288 InitDirHandle(lock
->ioh
.sb
, lock
->gl
->dir_cluster
, &dh
, FALSE
, glob
);
289 GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
, glob
);
290 de
.e
.entry
.first_cluster_lo
= de
.e
.entry
.first_cluster_hi
= 0;
291 de
.e
.entry
.file_size
= 0;
292 de
.e
.entry
.attr
|= ATTR_ARCHIVE
;
293 UpdateDirEntry(&de
, glob
);
295 D(bug("[fat] set first cluster and size to 0 in directory entry\n"));
297 /* free the clusters */
298 FREE_CLUSTER_CHAIN(lock
->ioh
.sb
, lock
->ioh
.first_cluster
);
299 lock
->gl
->first_cluster
= lock
->ioh
.first_cluster
= 0xffffffff;
300 RESET_HANDLE(&lock
->ioh
);
303 D(bug("[fat] file truncated, returning the lock\n"));
305 /* file is empty, go */
311 /* any error other than "not found" should be taken as-is */
312 if (err
!= ERROR_OBJECT_NOT_FOUND
)
315 /* not found. for INPUT we bail out */
316 if (action
== ACTION_FINDINPUT
)
318 D(bug("[fat] file not found, and not creating it\n"));
319 return ERROR_OBJECT_NOT_FOUND
;
323 bug("[fat] trying to create '");
324 RawPutChars(name
, namelen
);
328 /* otherwise it's time to create the file. get a handle on the passed dir */
329 if ((err
= InitDirHandle(glob
->sb
,
330 dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
, TRUE
, glob
))
334 /* get down to the correct subdir */
335 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
, glob
)) != 0)
337 ReleaseDirHandle(&dh
, glob
);
341 /* if the dir is write protected, can't do anything. root dir is never
343 if (dh
.ioh
.first_cluster
!= dh
.ioh
.sb
->rootdir_cluster
)
345 GetDirEntry(&dh
, 0, &de
, glob
);
346 if (de
.e
.entry
.attr
& ATTR_READ_ONLY
)
348 D(bug("[fat] containing dir is write protected, doing nothing\n"));
349 ReleaseDirHandle(&dh
, glob
);
350 return ERROR_WRITE_PROTECTED
;
354 /* create the entry */
356 CreateDirEntry(&dh
, name
, namelen
, ATTR_ARCHIVE
, 0, &de
, glob
)) != 0)
358 ReleaseDirHandle(&dh
, glob
);
362 /* lock the new file */
363 err
= LockFile(de
.cluster
, de
.index
, EXCLUSIVE_LOCK
, filelock
, glob
);
366 ReleaseDirHandle(&dh
, glob
);
370 (*filelock
)->do_notify
= TRUE
;
371 D(bug("[fat] returning lock on new file\n"));
377 /* find the named file in the directory referenced by dirlock, and delete it.
378 * if the file is a directory, it will only be deleted if it's empty */
379 LONG
OpDeleteFile(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
,
380 struct Globals
*glob
)
383 struct ExtFileLock
*lock
;
388 bug("[fat] deleting file '");
389 RawPutChars(name
, namelen
);
390 bug("' in directory at cluster % ld\n",
391 dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0);
396 /* obtain a lock on the file. we need an exclusive lock as we don't want
397 * to delete the file if it's in use */
398 if ((err
= LockFileByName(dirlock
, name
, namelen
, EXCLUSIVE_LOCK
, &lock
,
401 D(bug("[fat] couldn't obtain exclusive lock on named file\n"));
405 if (lock
->gl
->attr
& ATTR_READ_ONLY
)
407 D(bug("[fat] file is write protected, doing nothing\n"));
408 FreeLock(lock
, glob
);
409 return ERROR_DELETE_PROTECTED
;
412 /* if it's a directory, we have to make sure it's empty */
413 if (lock
->gl
->attr
& ATTR_DIRECTORY
)
415 D(bug("[fat] file is a directory, making sure it's empty\n"));
417 if ((err
= InitDirHandle(lock
->ioh
.sb
, lock
->ioh
.first_cluster
, &dh
,
420 FreeLock(lock
, glob
);
424 /* loop over the entries, starting from entry 2 (the first real
425 * entry). skipping unused ones, we look for the end-of-directory
426 * marker. if we find it, the directory is empty. if we find a real
427 * name, it's in use */
429 while ((err
= GetDirEntry(&dh
, de
.index
+ 1, &de
, glob
)) == 0)
431 /* skip unused entries */
432 if (de
.e
.entry
.name
[0] == 0xe5)
435 /* end of directory, it's empty */
436 if (de
.e
.entry
.name
[0] == 0x00)
439 /* otherwise the directory is still in use */
440 D(bug("[fat] directory still has files in it, won't delete it\n"));
442 ReleaseDirHandle(&dh
, glob
);
443 FreeLock(lock
, glob
);
444 return ERROR_DIRECTORY_NOT_EMPTY
;
447 ReleaseDirHandle(&dh
, glob
);
450 /* open the containing directory */
451 if ((err
=InitDirHandle(lock
->ioh
.sb
, lock
->gl
->dir_cluster
, &dh
,
454 FreeLock(lock
, glob
);
458 /* if the dir is write protected, can't do anything. root dir is never
460 if (dh
.ioh
.first_cluster
!= dh
.ioh
.sb
->rootdir_cluster
)
462 GetDirEntry(&dh
, 0, &de
, glob
);
463 if (de
.e
.entry
.attr
& ATTR_READ_ONLY
)
465 D(bug("[fat] containing dir is write protected, doing nothing\n"));
466 ReleaseDirHandle(&dh
, glob
);
467 FreeLock(lock
, glob
);
468 return ERROR_WRITE_PROTECTED
;
472 /* get the entry for the file */
473 GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
, glob
);
476 DeleteDirEntry(&de
, glob
);
479 ReleaseDirHandle(&dh
, glob
);
481 /* now free the clusters the file was using */
482 FREE_CLUSTER_CHAIN(lock
->ioh
.sb
, lock
->ioh
.first_cluster
);
485 SendNotifyByLock(lock
->ioh
.sb
, lock
->gl
);
487 /* this lock is now completely meaningless */
488 FreeLock(lock
, glob
);
491 bug("[fat] deleted '");
492 RawPutChars(name
, namelen
);
499 LONG
OpRenameFile(struct ExtFileLock
*sdirlock
, UBYTE
*sname
,
500 ULONG snamelen
, struct ExtFileLock
*ddirlock
, UBYTE
*dname
,
501 ULONG dnamelen
, struct Globals
*glob
)
503 struct DirHandle sdh
, ddh
;
504 struct DirEntry sde
, dde
;
505 struct GlobalLock
*gl
;
509 /* get the source dir handle */
510 if ((err
= InitDirHandle(glob
->sb
,
511 sdirlock
!= NULL
? sdirlock
->ioh
.first_cluster
: 0, &sdh
,
515 /* get down to the correct subdir */
516 if ((err
= MoveToSubdir(&sdh
, &sname
, &snamelen
, glob
)) != 0)
518 ReleaseDirHandle(&sdh
, glob
);
523 if ((err
= GetDirEntryByName(&sdh
, sname
, snamelen
, &sde
, glob
)) != 0)
525 ReleaseDirHandle(&sdh
, glob
);
529 /* now get a handle on the passed dest dir */
530 if ((err
= InitDirHandle(glob
->sb
,
531 ddirlock
!= NULL
? ddirlock
->ioh
.first_cluster
: 0, &ddh
,
534 ReleaseDirHandle(&sdh
, glob
);
538 /* get down to the correct subdir */
539 if ((err
= MoveToSubdir(&ddh
, &dname
, &dnamelen
, glob
)) != 0)
541 ReleaseDirHandle(&ddh
, glob
);
542 ReleaseDirHandle(&sdh
, glob
);
546 /* check the source and dest dirs. if either is read-only, do nothing */
547 GetDirEntry(&sdh
, 0, &dde
, glob
);
548 if (dde
.e
.entry
.attr
& ATTR_READ_ONLY
)
550 D(bug("[fat] source dir is read only, doing nothing\n"));
551 ReleaseDirHandle(&ddh
, glob
);
552 ReleaseDirHandle(&sdh
, glob
);
553 return ERROR_WRITE_PROTECTED
;
555 GetDirEntry(&ddh
, 0, &dde
, glob
);
556 if (dde
.e
.entry
.attr
& ATTR_READ_ONLY
)
558 D(bug("[fat] dest dir is read only, doing nothing\n"));
559 ReleaseDirHandle(&ddh
, glob
);
560 ReleaseDirHandle(&sdh
, glob
);
561 return ERROR_WRITE_PROTECTED
;
564 /* now see if the wanted name is in this dir. if it exists, do nothing */
565 if ((err
= GetDirEntryByName(&ddh
, dname
, dnamelen
, &dde
, glob
)) == 0)
567 ReleaseDirHandle(&ddh
, glob
);
568 ReleaseDirHandle(&sdh
, glob
);
569 return ERROR_OBJECT_EXISTS
;
571 else if (err
!= ERROR_OBJECT_NOT_FOUND
)
573 ReleaseDirHandle(&ddh
, glob
);
574 ReleaseDirHandle(&sdh
, glob
);
578 /* at this point we have the source entry in sde, and we know the dest
581 /* XXX if sdh and ddh are the same dir and there's room in the existing
582 * entries for the new name, just overwrite the name */
584 /* make a new entry in the target dir */
585 if ((err
= CreateDirEntry(&ddh
, dname
, dnamelen
,
586 sde
.e
.entry
.attr
| ATTR_ARCHIVE
,
587 (sde
.e
.entry
.first_cluster_hi
<< 16) | sde
.e
.entry
.first_cluster_lo
,
590 ReleaseDirHandle(&ddh
, glob
);
591 ReleaseDirHandle(&sdh
, glob
);
594 /* copy in the leftover attributes */
595 dde
.e
.entry
.create_date
= sde
.e
.entry
.create_date
;
596 dde
.e
.entry
.create_time
= sde
.e
.entry
.create_time
;
597 dde
.e
.entry
.write_date
= sde
.e
.entry
.write_date
;
598 dde
.e
.entry
.write_time
= sde
.e
.entry
.write_time
;
599 dde
.e
.entry
.last_access_date
= sde
.e
.entry
.last_access_date
;
600 dde
.e
.entry
.create_time_tenth
= sde
.e
.entry
.create_time_tenth
;
601 dde
.e
.entry
.file_size
= sde
.e
.entry
.file_size
;
603 UpdateDirEntry(&dde
, glob
);
605 /* update the global lock (if present) with the new dir cluster/entry */
606 ForeachNode(&sdh
.ioh
.sb
->info
->locks
, gl
)
608 if (gl
->dir_cluster
== sde
.cluster
&& gl
->dir_entry
== sde
.index
)
610 D(bug("[fat] found lock with old dir entry (%ld/%ld),"
611 " changing to (%ld/%ld)\n",
612 sde
.cluster
, sde
.index
, dde
.cluster
, dde
.index
));
614 gl
->dir_cluster
= dde
.cluster
;
615 gl
->dir_entry
= dde
.index
;
617 /* update the filename too */
618 GetDirEntryShortName(&dde
, &(gl
->name
[1]), &len
, glob
);
619 gl
->name
[0] = (UBYTE
) len
;
620 GetDirEntryLongName(&dde
, &(gl
->name
[1]), &len
);
621 gl
->name
[0] = (UBYTE
) len
;
625 /* delete the original */
626 DeleteDirEntry(&sde
, glob
);
629 SendNotifyByDirEntry(sdh
.ioh
.sb
, &dde
);
631 ReleaseDirHandle(&ddh
, glob
);
632 ReleaseDirHandle(&sdh
, glob
);
637 LONG
OpCreateDir(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
,
638 struct ExtFileLock
**newdirlock
, struct Globals
*glob
)
642 struct DirHandle dh
, sdh
;
643 struct DirEntry de
, sde
;
646 bug("[fat] creating directory '");
647 RawPutChars(name
, namelen
);
648 bug("' in directory at cluster %ld\n",
649 dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0);
652 /* get a handle on the passed dir */
653 if ((err
= InitDirHandle(glob
->sb
,
654 dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
, FALSE
,
658 /* get down to the correct subdir */
659 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
, glob
)) != 0)
661 ReleaseDirHandle(&dh
, glob
);
665 /* Make sure 'name' is just the FilePart() */
666 for (i
= namelen
- 1; i
> 0; i
--)
668 if (name
[i
] == '/' || name
[i
] == ':')
676 /* if the dir is write protected, can't do anything. root dir is never
678 if (dh
.ioh
.first_cluster
!= dh
.ioh
.sb
->rootdir_cluster
)
680 GetDirEntry(&dh
, 0, &de
, glob
);
681 if (de
.e
.entry
.attr
& ATTR_READ_ONLY
)
683 D(bug("[fat] containing dir is write protected, doing nothing\n"));
684 ReleaseDirHandle(&dh
, glob
);
685 return ERROR_WRITE_PROTECTED
;
689 /* now see if the wanted name is in this dir. if it exists, then we do
691 if ((err
= GetDirEntryByName(&dh
, name
, namelen
, &de
, glob
)) == 0)
693 D(bug("[fat] name exists, can't do anything\n"));
694 ReleaseDirHandle(&dh
, glob
);
695 return ERROR_OBJECT_EXISTS
;
698 /* find a free cluster to store the dir in */
699 if ((err
= FindFreeCluster(dh
.ioh
.sb
, &cluster
)) != 0)
701 ReleaseDirHandle(&dh
, glob
);
706 AllocCluster(dh
.ioh
.sb
, cluster
);
708 D(bug("[fat] allocated cluster %ld for directory\n", cluster
));
710 /* create the entry, pointing to the new cluster */
711 if ((err
= CreateDirEntry(&dh
, name
, namelen
,
712 ATTR_DIRECTORY
| ATTR_ARCHIVE
, cluster
, &de
, glob
)) != 0)
714 /* deallocate the cluster */
715 FreeCluster(dh
.ioh
.sb
, cluster
);
717 ReleaseDirHandle(&dh
, glob
);
721 /* now get a handle on the new directory */
722 InitDirHandle(dh
.ioh
.sb
, cluster
, &sdh
, FALSE
, glob
);
724 /* create the dot entry. it's a direct copy of the just-created entry, but
725 * with a different name */
726 GetDirEntry(&sdh
, 0, &sde
, glob
);
727 CopyMem(&de
.e
.entry
, &sde
.e
.entry
, sizeof(struct FATDirEntry
));
728 CopyMem(". ", &sde
.e
.entry
.name
, FAT_MAX_SHORT_NAME
);
729 UpdateDirEntry(&sde
, glob
);
731 /* create the dot-dot entry. again, a copy, with the cluster pointer setup
732 * to point to the parent */
733 GetDirEntry(&sdh
, 1, &sde
, glob
);
734 CopyMem(&de
.e
.entry
, &sde
.e
.entry
, sizeof(struct FATDirEntry
));
735 CopyMem(".. ", &sde
.e
.entry
.name
, FAT_MAX_SHORT_NAME
);
736 cluster
= dh
.ioh
.first_cluster
;
737 if (cluster
== dh
.ioh
.sb
->rootdir_cluster
)
739 sde
.e
.entry
.first_cluster_lo
= cluster
& 0xffff;
740 sde
.e
.entry
.first_cluster_hi
= cluster
>> 16;
741 UpdateDirEntry(&sde
, glob
);
743 /* clear all remaining entries (the first of which marks the end of the
745 for (i
= 2; GetDirEntry(&sdh
, i
, &sde
, glob
) == 0; i
++)
747 memset(&sde
.e
.entry
, 0, sizeof(struct FATDirEntry
));
748 UpdateDirEntry(&sde
, glob
);
751 /* new dir created */
752 ReleaseDirHandle(&sdh
, glob
);
754 /* now obtain a lock on the new dir */
755 err
= LockFile(de
.cluster
, de
.index
, SHARED_LOCK
, newdirlock
, glob
);
758 ReleaseDirHandle(&dh
, glob
);
761 SendNotifyByLock((*newdirlock
)->ioh
.sb
, (*newdirlock
)->gl
);
766 LONG
OpRead(struct ExtFileLock
*lock
, UBYTE
*data
, ULONG want
,
767 ULONG
*read
, struct Globals
*glob
)
771 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want
,
777 if (want
+ lock
->pos
> lock
->gl
->size
)
779 want
= lock
->gl
->size
- lock
->pos
;
780 D(bug("[fat] full read would take us past end-of-file,"
781 " adjusted want to %ld bytes\n", want
));
784 if ((err
= ReadFileChunk(&(lock
->ioh
), lock
->pos
, want
, data
, read
)) == 0)
787 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read
,
794 LONG
OpWrite(struct ExtFileLock
*lock
, UBYTE
*data
, ULONG want
,
795 ULONG
*written
, struct Globals
*glob
)
798 BOOL update_entry
= FALSE
;
802 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want
,
805 /* need an exclusive lock */
806 if (lock
->gl
->access
!= EXCLUSIVE_LOCK
)
808 D(bug("[fat] can't modify global attributes via a shared lock\n"));
809 return ERROR_OBJECT_IN_USE
;
812 /* don't modify the file if it's protected */
813 if (lock
->gl
->attr
& ATTR_READ_ONLY
)
815 D(bug("[fat] file is write protected\n"));
816 return ERROR_WRITE_PROTECTED
;
825 /* if this is the first write, make a note as we'll have to store the
826 * first cluster in the directory entry later */
827 if (lock
->ioh
.first_cluster
== 0xffffffff)
830 if ((err
= WriteFileChunk(&(lock
->ioh
), lock
->pos
, want
, data
,
833 /* if nothing was written but success was returned (can that even
834 * happen?) then we don't want to mess with the dir entry */
837 D(bug("[fat] nothing successfully written (!),"
838 " nothing else to do\n"));
842 /* something changed, we need to tell people about it */
843 lock
->do_notify
= TRUE
;
845 /* move to the end of the area written */
846 lock
->pos
+= *written
;
848 /* update the dir entry if the size changed */
849 if (lock
->pos
> lock
->gl
->size
)
851 lock
->gl
->size
= lock
->pos
;
855 /* force an update if the file hasn't already got an archive bit. this
856 * will happen if this was the first write to an existing file that
857 * didn't cause it to grow */
858 else if (!(lock
->gl
->attr
& ATTR_ARCHIVE
))
861 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n",
862 *written
, lock
->pos
, lock
->gl
->size
));
866 D(bug("[fat] updating dir entry, first cluster is %ld,"
868 lock
->ioh
.first_cluster
, lock
->gl
->size
));
870 lock
->gl
->first_cluster
= lock
->ioh
.first_cluster
;
872 InitDirHandle(lock
->ioh
.sb
, lock
->gl
->dir_cluster
, &dh
, FALSE
,
874 GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
, glob
);
876 de
.e
.entry
.file_size
= lock
->gl
->size
;
877 de
.e
.entry
.first_cluster_lo
= lock
->gl
->first_cluster
& 0xffff;
878 de
.e
.entry
.first_cluster_hi
= lock
->gl
->first_cluster
>> 16;
880 de
.e
.entry
.attr
|= ATTR_ARCHIVE
;
881 UpdateDirEntry(&de
, glob
);
883 ReleaseDirHandle(&dh
, glob
);
890 LONG
OpSetFileSize(struct ExtFileLock
*lock
, LONG offset
, LONG whence
,
891 LONG
*newsize
, struct Globals
*glob
)
898 ULONG cl
, next
, first
, last
;
900 /* need an exclusive lock to do what is effectively a write */
901 if (lock
->gl
->access
!= EXCLUSIVE_LOCK
)
903 D(bug("[fat] can't modify global attributes via a shared lock\n"));
904 return ERROR_OBJECT_IN_USE
;
907 /* don't modify the file if it's protected */
908 if (lock
->gl
->attr
& ATTR_READ_ONLY
)
910 D(bug("[fat] file is write protected\n"));
911 return ERROR_WRITE_PROTECTED
;
914 /* calculate the new length based on the current position */
915 if (whence
== OFFSET_BEGINNING
&& offset
>= 0)
917 else if (whence
== OFFSET_CURRENT
&& lock
->pos
+ offset
>= 0)
918 size
= lock
->pos
+ offset
;
919 else if (whence
== OFFSET_END
&& offset
<= 0
920 && lock
->gl
->size
+ offset
>= 0)
921 size
= lock
->gl
->size
+ offset
;
923 return ERROR_SEEK_ERROR
;
925 if (lock
->gl
->size
== size
)
927 D(bug("[fat] new size matches old size, nothing to do\n"));
932 D(bug("[fat] old size was %ld bytes, new size is %ld bytes\n",
933 lock
->gl
->size
, size
));
935 /* get the dir that this file is in */
936 if ((err
= InitDirHandle(glob
->sb
, lock
->gl
->dir_cluster
, &dh
,
941 if ((err
= GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
, glob
)) != 0)
943 ReleaseDirHandle(&dh
, glob
);
947 /* calculate how many clusters we need */
948 want
= (size
>> glob
->sb
->clustersize_bits
)
949 + ((size
& (glob
->sb
->clustersize
- 1)) ? 1 : 0);
951 D(bug("[fat] want %ld clusters for file\n", want
));
953 /* we're getting three things here - the first cluster of the existing
954 * file, the last cluster of the existing file (which might be the same),
955 * and the number of clusters currently allocated to it (it's not safe to
956 * infer it from the current size as a broken fat implementation may have
957 * allocated it more than it needs). we handle file shrinking/truncation
958 * here as it falls out naturally from following the current cluster chain
961 cl
= FIRST_FILE_CLUSTER(&de
);
964 D(bug("[fat] file is empty\n"));
972 /* if we're fully truncating the file, then the below loop will
973 * actually not truncate the file at all (count will get incremented
974 * past want first time around the loop). it's a pain to incorporate a
975 * full truncate into the loop, not counting the change to the first
976 * cluster, so it's easier to just take care of it all here */
977 D(bug("[fat] want nothing, so truncating the entire file\n"));
979 FREE_CLUSTER_CHAIN(glob
->sb
, cl
);
981 /* now it has nothing */
991 /* do the actual count */
992 while ((last
= GET_NEXT_CLUSTER(glob
->sb
, cl
))
993 < glob
->sb
->eoc_mark
- 7)
998 /* if we get as many clusters as we want, kill everything after
1002 FREE_CLUSTER_CHAIN(glob
->sb
, GET_NEXT_CLUSTER(glob
->sb
, cl
));
1003 SET_NEXT_CLUSTER(glob
->sb
, cl
, glob
->sb
->eoc_mark
);
1005 D(bug("[fat] truncated file\n"));
1011 D(bug("[fat] file has %ld clusters\n", count
));
1014 /* now we know how big the current file is. if we don't have enough,
1015 * allocate more until we do */
1018 D(bug("[fat] growing file\n"));
1020 while (count
< want
)
1022 if ((err
= FindFreeCluster(glob
->sb
, &next
)) != 0)
1024 /* XXX probably no free clusters left. we should clean up the
1025 * extras we allocated before returning. it won't hurt
1026 * anything to leave them but it is dead space */
1027 ReleaseDirHandle(&dh
, glob
);
1031 /* mark the cluster used */
1032 AllocCluster(glob
->sb
, next
);
1034 /* if the file had no clusters, then this is the first and we
1035 * need to note it for later storage in the direntry */
1039 /* otherwise, hook it up to the current one */
1041 SET_NEXT_CLUSTER(glob
->sb
, cl
, next
);
1049 /* clusters are fixed, now update the directory entry */
1050 de
.e
.entry
.first_cluster_lo
= first
& 0xffff;
1051 de
.e
.entry
.first_cluster_hi
= first
>> 16;
1052 de
.e
.entry
.file_size
= size
;
1053 de
.e
.entry
.attr
|= ATTR_ARCHIVE
;
1054 UpdateDirEntry(&de
, glob
);
1056 D(bug("[fat] set file size to %ld, first cluster is %ld\n", size
,
1065 LONG
OpSetProtect(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
,
1066 ULONG prot
, struct Globals
*glob
)
1069 struct DirHandle dh
;
1072 /* get the dir handle */
1073 if ((err
= InitDirHandle(glob
->sb
,
1074 dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
, FALSE
,
1078 /* get down to the correct subdir */
1079 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
, glob
)) != 0)
1081 ReleaseDirHandle(&dh
, glob
);
1085 /* can't change permissions on the root */
1086 if (dh
.ioh
.first_cluster
== dh
.ioh
.sb
->rootdir_cluster
&& namelen
== 0)
1088 D(bug("[fat] can't set protection on root dir\n"));
1089 ReleaseDirHandle(&dh
, glob
);
1090 return ERROR_INVALID_LOCK
;
1094 if ((err
= GetDirEntryByName(&dh
, name
, namelen
, &de
, glob
)) != 0)
1096 ReleaseDirHandle(&dh
, glob
);
1100 /* set the attributes */
1101 de
.e
.entry
.attr
&= ~(ATTR_ARCHIVE
| ATTR_READ_ONLY
);
1102 de
.e
.entry
.attr
|= (prot
& FIBF_ARCHIVE
? ATTR_ARCHIVE
: 0);
1104 /* only set read-only if neither writable nor deletable */
1105 if ((prot
& (FIBF_WRITE
| FIBF_DELETE
)) == (FIBF_WRITE
| FIBF_DELETE
))
1106 de
.e
.entry
.attr
|= ATTR_READ_ONLY
;
1107 UpdateDirEntry(&de
, glob
);
1109 D(bug("[fat] new protection is 0x%08x\n", de
.e
.entry
.attr
));
1111 SendNotifyByDirEntry(glob
->sb
, &de
);
1113 /* if it's a directory, we also need to update the protections for the
1114 * directory's . entry */
1115 if (de
.e
.entry
.attr
& ATTR_DIRECTORY
)
1117 ULONG attr
= de
.e
.entry
.attr
;
1119 D(bug("[fat] setting protections for directory '.' entry\n"));
1121 InitDirHandle(glob
->sb
, FIRST_FILE_CLUSTER(&de
), &dh
, TRUE
, glob
);
1122 GetDirEntry(&dh
, 0, &de
, glob
);
1123 de
.e
.entry
.attr
= attr
;
1124 UpdateDirEntry(&de
, glob
);
1127 ReleaseDirHandle(&dh
, glob
);
1132 LONG
OpSetDate(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
,
1133 struct DateStamp
*ds
, struct Globals
*glob
)
1136 struct DirHandle dh
;
1139 /* get the dir handle */
1140 if ((err
= InitDirHandle(glob
->sb
,
1141 dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
, FALSE
,
1145 /* get down to the correct subdir */
1146 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
, glob
)) != 0)
1148 ReleaseDirHandle(&dh
, glob
);
1152 /* can't set date on the root */
1153 if (dh
.ioh
.first_cluster
== dh
.ioh
.sb
->rootdir_cluster
&& namelen
== 0)
1155 D(bug("[fat] can't set date on root dir\n"));
1156 ReleaseDirHandle(&dh
, glob
);
1157 return ERROR_INVALID_LOCK
;
1161 if ((err
= GetDirEntryByName(&dh
, name
, namelen
, &de
, glob
)) != 0)
1163 ReleaseDirHandle(&dh
, glob
);
1167 /* set and update the date */
1168 ConvertDOSDate(ds
, &de
.e
.entry
.write_date
, &de
.e
.entry
.write_time
,
1170 de
.e
.entry
.last_access_date
= de
.e
.entry
.write_date
;
1171 UpdateDirEntry(&de
, glob
);
1173 SendNotifyByDirEntry(glob
->sb
, &de
);
1175 ReleaseDirHandle(&dh
, glob
);
1180 LONG
OpAddNotify(struct NotifyRequest
*nr
, struct Globals
*glob
)
1183 struct DirHandle dh
;
1185 struct GlobalLock
*gl
= NULL
, *tmp
;
1186 struct NotifyNode
*nn
;
1187 BOOL exists
= FALSE
;
1189 D(bug("[fat] trying to add notification for '%s'\n", nr
->nr_FullName
));
1191 /* if the request is for the volume root, then we just link to the root
1193 if (nr
->nr_FullName
[strlen(nr
->nr_FullName
) - 1] == ':')
1195 D(bug("[fat] adding notify for root dir\n"));
1196 gl
= &glob
->sb
->info
->root_lock
;
1201 if ((err
= InitDirHandle(glob
->sb
, 0, &dh
, FALSE
, glob
)) != 0)
1204 /* look for the entry */
1206 GetDirEntryByPath(&dh
, nr
->nr_FullName
, strlen(nr
->nr_FullName
),
1208 if (err
!= 0 && err
!= ERROR_OBJECT_NOT_FOUND
)
1211 /* if it was found, then it might be open. try to find the global
1217 D(bug("[fat] file exists (%ld/%ld), looking for global lock\n",
1218 de
.cluster
, de
.index
));
1220 ForeachNode(&glob
->sb
->info
->locks
, tmp
)
1222 if (tmp
->dir_cluster
== de
.cluster
1223 && tmp
->dir_entry
== de
.index
)
1227 D(bug("[fat] found global lock 0x%0x\n", gl
));
1238 D(bug("[fat] file doesn't exist\n"));
1243 D(bug("[fat] file not currently locked\n"));
1245 /* allocate space for the notify node */
1246 if ((nn
= AllocVecPooled(glob
->sb
->info
->mem_pool
,
1247 sizeof(struct NotifyNode
))) == NULL
)
1248 return ERROR_NO_FREE_STORE
;
1250 /* plug the bits in */
1254 /* add to the list */
1255 ADDTAIL(&glob
->sb
->info
->notifies
, nn
);
1257 /* tell them that the file exists if they wanted to know */
1258 if (exists
&& nr
->nr_Flags
& NRF_NOTIFY_INITIAL
)
1259 SendNotify(nr
, glob
);
1261 D(bug("[fat] now reporting activity on '%s'\n", nr
->nr_FullName
));
1266 LONG
OpRemoveNotify(struct NotifyRequest
*nr
, struct Globals
*glob
)
1269 struct NotifyNode
*nn
, *nn2
;
1271 D(bug("[fat] trying to remove notification for '%s'\n",
1274 /* search inserted volume for the request */
1275 if (glob
->sb
!= NULL
)
1277 ForeachNodeSafe(&glob
->sb
->info
->notifies
, nn
, nn2
)
1281 D(bug("[fat] found notify request in list, removing it\n"));
1283 FreeVecPooled(glob
->sb
->info
->mem_pool
, nn
);
1289 /* search offline volumes for the request */
1290 ForeachNode(&glob
->sblist
, sb
)
1292 ForeachNodeSafe(&sb
->info
->notifies
, nn
, nn2
)
1296 D(bug("[fat] found notify request in list, removing it\n"));
1298 FreeVecPooled(sb
->info
->mem_pool
, nn
);
1299 AttemptDestroyVolume(sb
);
1305 D(bug("[fat] not found, doing nothing\n"));