2 * fat.handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007 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 #include <exec/types.h>
15 #include <dos/notify.h>
16 #include <proto/exec.h>
19 #include "fat_protos.h"
21 #if defined(DEBUG_FULL) && DEBUG_FULL != 0
26 #include <aros/debug.h>
28 #define FREE_CLUSTER_CHAIN(sb,cl) \
31 while (cluster >= 0 && cluster < sb->eoc_mark) { \
32 ULONG next_cluster = GET_NEXT_CLUSTER(sb, cluster); \
33 SET_NEXT_CLUSTER(sb, cluster, 0); \
34 cluster = next_cluster; \
39 * this takes a full path and moves to the directory that would contain the
40 * last file in the path. ie calling with (dh, "foo/bar/baz", 11) will move to
41 * directory "foo/bar" under the dir specified by dh. dh will become a handle
42 * to the new dir. after the return name will be "baz" and namelen will be 3
44 static LONG
MoveToSubdir(struct DirHandle
*dh
, UBYTE
**pname
, ULONG
*pnamelen
) {
46 UBYTE
*name
= *pname
, *base
;
47 ULONG namelen
= *pnamelen
, baselen
;
51 /* if it starts with a volume specifier (or just a :), remove it and get
52 * us back to the root dir */
53 for (i
= 0; i
< namelen
; i
++)
55 D(bug("[fat] name has volume specifier, moving to the root dir\n"));
60 InitDirHandle(dh
->ioh
.sb
, 0, dh
);
65 /* we break the given name into two pieces - the name of the containing
66 * dir, and the name of the new dir to go within it. if the base ends up
67 * empty, then we just use the dirlock */
71 if (base
[baselen
-1] != '/')
76 if (base
[baselen
-1] == '/')
81 name
= &base
[baselen
];
83 D(bug("[fat] base is '%.*s', name is '%.*s'\n", baselen
, base
, namelen
, name
));
86 if ((err
= GetDirEntryByPath(dh
, base
, baselen
, &de
)) != 0) {
87 D(bug("[fat] base not found\n"));
91 if ((err
= InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(&de
), dh
)) != 0)
102 * obtains a lock on the named file under the given dir. this is the service
103 * routine for DOS Open() (ie FINDINPUT/FINDOUTPUT/FINDUPDATE) and as such may
104 * only return a lock on a file, never on a dir.
106 LONG
OpOpenFile(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
, LONG action
, struct ExtFileLock
**filelock
) {
108 struct ExtFileLock
*lock
;
112 D(bug("[fat] opening file '%.*s' in dir at cluster %ld, action %s\n",
113 namelen
, name
, dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0,
114 action
== ACTION_FINDINPUT
? "FINDINPUT" :
115 action
== ACTION_FINDOUTPUT
? "FINDOUTPUT" :
116 action
== ACTION_FINDUPDATE
? "FINDUPDATE" : "[unknown]"));
118 /* no filename means they're trying to open whatever dirlock is (which
119 * despite the name may not actually be a dir). since there's already an
120 * extant lock, its never going be possible to get an exclusive lock, so
121 * this will only work for FINDINPUT (read-only) */
123 D(bug("[fat] trying to copy passed dir lock\n"));
125 if (action
!= ACTION_FINDINPUT
) {
126 D(bug("[fat] can't copy lock for write (exclusive)\n"));
127 return ERROR_OBJECT_IN_USE
;
130 /* dirs can't be opened */
131 if (dirlock
== NULL
|| dirlock
->gl
->attr
& ATTR_DIRECTORY
) {
132 D(bug("[fat] dir lock is a directory, which can't be opened\n"));
133 return ERROR_OBJECT_WRONG_TYPE
;
136 /* its a file, just copy the lock */
137 return CopyLock(dirlock
, filelock
);
141 err
= LockFileByName(dirlock
, name
, namelen
, action
== ACTION_FINDINPUT
? SHARED_LOCK
: EXCLUSIVE_LOCK
, &lock
);
145 D(bug("[fat] found existing file\n"));
147 /* can't open directories */
148 if (lock
->gl
->attr
& ATTR_DIRECTORY
) {
149 D(bug("[fat] its a directory, can't open it\n"));
151 return ERROR_OBJECT_WRONG_TYPE
;
154 /* INPUT/UPDATE use the file as/is */
155 if (action
!= ACTION_FINDOUTPUT
) {
156 D(bug("[fat] returning the lock\n"));
161 /* whereas OUTPUT truncates it */
162 D(bug("[fat] handling FINDOUTPUT, so truncating the file\n"));
164 /* update the dir entry to make the file empty */
165 InitDirHandle(lock
->ioh
.sb
, lock
->gl
->dir_cluster
, &dh
);
166 GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
);
167 de
.e
.entry
.first_cluster_lo
= de
.e
.entry
.first_cluster_hi
= 0;
168 de
.e
.entry
.file_size
= 0;
171 D(bug("[fat] set first cluster and size to 0 in directory entry\n"));
173 /* free the clusters */
174 FREE_CLUSTER_CHAIN(lock
->ioh
.sb
, lock
->ioh
.first_cluster
);
175 lock
->gl
->first_cluster
= lock
->ioh
.first_cluster
= 0xffffffff;
176 RESET_HANDLE(&lock
->ioh
);
179 D(bug("[fat] file truncated, returning the lock\n"));
181 /* file is empty, go */
187 /* any error other than "not found" should be taken as-is */
188 if (err
!= ERROR_OBJECT_NOT_FOUND
)
191 /* not found. for INPUT we bail out */
192 if (action
== ACTION_FINDINPUT
) {
193 D(bug("[fat] file not found, and not creating it\n"));
194 return ERROR_OBJECT_NOT_FOUND
;
197 D(bug("[fat] trying to create '%.*s'\n", namelen
, name
));
199 /* otherwise its time to create the file. get a handle on the passed dir */
200 if ((err
= InitDirHandle(glob
->sb
, dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
)) != 0)
203 /* get down to the correct subdir */
204 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
)) != 0) {
205 ReleaseDirHandle(&dh
);
209 /* create the entry */
210 if ((err
= CreateDirEntry(&dh
, name
, namelen
, 0, 0, &de
)) != 0) {
211 ReleaseDirHandle(&dh
);
215 /* lock the new file */
216 err
= LockFile(de
.cluster
, de
.index
, EXCLUSIVE_LOCK
, filelock
);
219 ReleaseDirHandle(&dh
);
222 (*filelock
)->do_notify
= TRUE
;
223 D(bug("[fat] returning lock on new file\n"));
229 /* find the named file in the directory referenced by dirlock, and delete it.
230 * if the file is a directory, it will only be deleted if its empty */
231 LONG
OpDeleteFile(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
) {
233 struct ExtFileLock
*lock
;
237 D(bug("[fat] deleting file '%.*s' in directory at cluster %ld\n", namelen
, name
, dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0));
239 /* obtain a lock on the file. we need an exclusive lock as we don't want
240 * to delete the file if its in use */
241 if ((err
= LockFileByName(dirlock
, name
, namelen
, EXCLUSIVE_LOCK
, &lock
)) != 0) {
242 D(bug("[fat] couldn't obtain exclusive lock on named file\n"));
246 /* if its a directory, we have to make sure its empty */
247 if (lock
->gl
->attr
& ATTR_DIRECTORY
) {
248 D(bug("[fat] file is a directory, making sure its empty\n"));
250 if ((err
= InitDirHandle(lock
->ioh
.sb
, lock
->ioh
.first_cluster
, &dh
)) != 0) {
255 /* loop over the entries, starting from entry 2 (the first real
256 * entry). skipping unused ones, we look for the end-of-directory
257 * marker. if we find it, the directory is empty. if we find a real
258 * name, its in use */
260 while ((err
= GetDirEntry(&dh
, de
.index
+1, &de
)) == 0) {
261 /* skip unused entries */
262 if (de
.e
.entry
.name
[0] == 0xe5)
265 /* end of directory, its empty */
266 if (de
.e
.entry
.name
[0] == 0x00)
269 /* otherwise the directory is still in use */
270 D(bug("[fat] directory still has files in it, won't delete it\n"));
272 ReleaseDirHandle(&dh
);
274 return ERROR_DIRECTORY_NOT_EMPTY
;
277 ReleaseDirHandle(&dh
);
280 /* open the containing directory */
281 if ((err
= InitDirHandle(lock
->ioh
.sb
, lock
->gl
->dir_cluster
, &dh
)) != 0) {
286 /* get the entry for the file */
287 GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
);
293 ReleaseDirHandle(&dh
);
295 /* now free the clusters the file was using */
296 FREE_CLUSTER_CHAIN(lock
->ioh
.sb
, lock
->ioh
.first_cluster
);
299 SendNotifyByLock(lock
->gl
);
301 /* this lock is now completely meaningless */
304 D(bug("[fat] deleted '%.*s'\n", namelen
, name
));
309 LONG
OpRenameFile(struct ExtFileLock
*sdirlock
, UBYTE
*sname
, ULONG snamelen
, struct ExtFileLock
*ddirlock
, UBYTE
*dname
, ULONG dnamelen
) {
310 struct DirHandle sdh
, ddh
;
311 struct DirEntry sde
, dde
;
312 struct GlobalLock
*gl
;
316 /* get the source dir handle */
317 if ((err
= InitDirHandle(glob
->sb
, sdirlock
!= NULL
? sdirlock
->ioh
.first_cluster
: 0, &sdh
)) != 0)
320 /* get down to the correct subdir */
321 if ((err
= MoveToSubdir(&sdh
, &sname
, &snamelen
)) != 0) {
322 ReleaseDirHandle(&sdh
);
327 if ((err
= GetDirEntryByName(&sdh
, sname
, snamelen
, &sde
)) != 0) {
328 ReleaseDirHandle(&sdh
);
332 /* now get a handle on the passed dest dir */
333 if ((err
= InitDirHandle(glob
->sb
, ddirlock
!= NULL
? ddirlock
->ioh
.first_cluster
: 0, &ddh
)) != 0) {
334 ReleaseDirHandle(&sdh
);
338 /* get down to the correct subdir */
339 if ((err
= MoveToSubdir(&ddh
, &dname
, &dnamelen
)) != 0) {
340 ReleaseDirHandle(&ddh
);
341 ReleaseDirHandle(&sdh
);
345 /* now see if the wanted name is in this dir. if it exists, do nothing */
346 if ((err
= GetDirEntryByName(&ddh
, dname
, dnamelen
, &dde
)) == 0) {
347 ReleaseDirHandle(&ddh
);
348 ReleaseDirHandle(&sdh
);
349 return ERROR_OBJECT_EXISTS
;
351 else if (err
!= ERROR_OBJECT_NOT_FOUND
) {
352 ReleaseDirHandle(&ddh
);
353 ReleaseDirHandle(&sdh
);
357 /* at this point we have the source entry in sde, and we know the dest
360 /* XXX if sdh and ddh are the same dir and there's room in the existing
361 * entries for the new name, just overwrite the name */
363 /* make a new entry in the target dir */
364 if ((err
= CreateDirEntry(&ddh
, dname
, dnamelen
, sde
.e
.entry
.attr
, (sde
.e
.entry
.first_cluster_hi
<< 16) | sde
.e
.entry
.first_cluster_lo
, &dde
)) != 0) {
365 ReleaseDirHandle(&ddh
);
366 ReleaseDirHandle(&sdh
);
369 /* copy in the leftover attributes */
370 dde
.e
.entry
.create_date
= sde
.e
.entry
.create_date
;
371 dde
.e
.entry
.create_time
= sde
.e
.entry
.create_time
;
372 dde
.e
.entry
.write_date
= sde
.e
.entry
.write_date
;
373 dde
.e
.entry
.write_time
= sde
.e
.entry
.write_time
;
374 dde
.e
.entry
.last_access_date
= sde
.e
.entry
.last_access_date
;
375 dde
.e
.entry
.create_time_tenth
= sde
.e
.entry
.create_time_tenth
;
376 dde
.e
.entry
.file_size
= sde
.e
.entry
.file_size
;
378 UpdateDirEntry(&dde
);
380 /* update the global lock (if present) with the new dir cluster/entry */
381 ForeachNode(&sdh
.ioh
.sb
->locks
, gl
)
382 if (gl
->dir_cluster
== sde
.cluster
&& gl
->dir_entry
== sde
.index
) {
383 D(bug("[fat] found lock with old dir entry (%ld/%ld), changing to (%ld/%ld)\n", sde
.cluster
, sde
.index
, dde
.cluster
, dde
.index
));
385 gl
->dir_cluster
= dde
.cluster
;
386 gl
->dir_entry
= dde
.index
;
388 /* update the filename too */
389 GetDirEntryShortName(&dde
, &(gl
->name
[1]), &len
); gl
->name
[0] = (UBYTE
) len
;
390 GetDirEntryLongName(&dde
, &(gl
->name
[1]), &len
); gl
->name
[0] = (UBYTE
) len
;
393 /* delete the original */
394 DeleteDirEntry(&sde
);
397 SendNotifyByDirEntry(&dde
);
399 ReleaseDirHandle(&ddh
);
400 ReleaseDirHandle(&sdh
);
405 LONG
OpCreateDir(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
, struct ExtFileLock
**newdirlock
) {
408 struct DirHandle dh
, sdh
;
409 struct DirEntry de
, sde
;
411 D(bug("[fat] creating directory '%.*s' in directory at cluster %ld\n", namelen
, name
, dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0));
413 /* get a handle on the passed dir */
414 if ((err
= InitDirHandle(glob
->sb
, dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
)) != 0)
417 /* get down to the correct subdir */
418 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
)) != 0) {
419 ReleaseDirHandle(&dh
);
423 /* now see if the wanted name is in this dir. if it exists, then we do
425 if ((err
= GetDirEntryByName(&dh
, name
, namelen
, &de
)) == 0) {
426 D(bug("[fat] name exists, can't do anything\n"));
427 ReleaseDirHandle(&dh
);
428 return ERROR_OBJECT_EXISTS
;
431 /* find a free cluster to store the dir in */
432 if ((err
= FindFreeCluster(dh
.ioh
.sb
, &cluster
)) != 0) {
433 ReleaseDirHandle(&dh
);
438 SET_NEXT_CLUSTER(dh
.ioh
.sb
, cluster
, dh
.ioh
.sb
->eoc_mark
);
440 D(bug("[fat] allocated cluster %ld for directory\n", cluster
));
442 /* create the entry, pointing to the new cluster */
443 if ((err
= CreateDirEntry(&dh
, name
, namelen
, ATTR_DIRECTORY
, cluster
, &de
)) != 0) {
444 /* deallocate the cluster */
445 SET_NEXT_CLUSTER(dh
.ioh
.sb
, cluster
, 0);
447 ReleaseDirHandle(&dh
);
451 /* now get a handle on the new directory */
452 InitDirHandle(dh
.ioh
.sb
, cluster
, &sdh
);
454 /* create the dot entry. its a direct copy of the just-created entry, but
455 * with a different name */
456 GetDirEntry(&sdh
, 0, &sde
);
457 CopyMem(&de
.e
.entry
, &sde
.e
.entry
, sizeof(struct FATDirEntry
));
458 CopyMem(". ", &sde
.e
.entry
.name
, 11);
459 UpdateDirEntry(&sde
);
461 /* create the dot-dot entry. again, a copy, with the cluster pointer setup
462 * to point to the parent */
463 GetDirEntry(&sdh
, 1, &sde
);
464 CopyMem(&de
.e
.entry
, &sde
.e
.entry
, sizeof(struct FATDirEntry
));
465 CopyMem(".. ", &sde
.e
.entry
.name
, 11);
466 sde
.e
.entry
.first_cluster_lo
= dh
.ioh
.first_cluster
& 0xffff;
467 sde
.e
.entry
.first_cluster_hi
= dh
.ioh
.first_cluster
>> 16;
468 UpdateDirEntry(&sde
);
470 /* put an empty entry at the end to mark end of directory */
471 GetDirEntry(&sdh
, 2, &sde
);
472 memset(&sde
.e
.entry
, 0, sizeof(struct FATDirEntry
));
473 UpdateDirEntry(&sde
);
475 /* new dir created */
476 ReleaseDirHandle(&sdh
);
478 /* now obtain a lock on the new dir */
479 err
= LockFile(de
.cluster
, de
.index
, SHARED_LOCK
, newdirlock
);
482 ReleaseDirHandle(&dh
);
485 SendNotifyByLock((*newdirlock
)->gl
);
490 LONG
OpRead(struct ExtFileLock
*lock
, UBYTE
*data
, ULONG want
, ULONG
*read
) {
493 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want
, lock
->pos
));
498 if (want
+ lock
->pos
> lock
->gl
->size
) {
499 want
= lock
->gl
->size
- lock
->pos
;
500 D(bug("[fat] full read would take us past end-of-file, adjusted want to %ld bytes\n", want
));
503 if ((err
= ReadFileChunk(&(lock
->ioh
), lock
->pos
, want
, data
, read
)) == 0) {
505 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read
, lock
->pos
));
511 LONG
OpWrite(struct ExtFileLock
*lock
, UBYTE
*data
, ULONG want
, ULONG
*written
) {
513 BOOL update_entry
= FALSE
;
517 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want
, lock
->pos
));
522 /* if this is the first write, make a note as we'll have to store the
523 * first cluster in the directory entry later */
524 if (lock
->ioh
.first_cluster
== 0xffffffff)
527 if ((err
= WriteFileChunk(&(lock
->ioh
), lock
->pos
, want
, data
, written
)) == 0) {
528 /* if nothing was written but success was returned (can that even
529 * happen?) then we don't want to mess with the dir entry */
531 D(bug("[fat] nothing successfully written (!), nothing else to do\n"));
535 lock
->do_notify
= TRUE
;
537 lock
->pos
+= *written
;
538 if (lock
->pos
> lock
->gl
->size
) {
539 lock
->gl
->size
= lock
->pos
;
543 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n", *written
, lock
->pos
, lock
->gl
->size
));
546 D(bug("[fat] updating dir entry, first cluster is %ld, size is %ld\n", lock
->ioh
.first_cluster
, lock
->gl
->size
));
548 lock
->gl
->first_cluster
= lock
->ioh
.first_cluster
;
550 InitDirHandle(lock
->ioh
.sb
, lock
->gl
->dir_cluster
, &dh
);
551 GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
);
553 de
.e
.entry
.file_size
= lock
->gl
->size
;
554 de
.e
.entry
.first_cluster_lo
= lock
->gl
->first_cluster
& 0xffff;
555 de
.e
.entry
.first_cluster_hi
= lock
->gl
->first_cluster
>> 16;
559 ReleaseDirHandle(&dh
);
566 LONG
OpAddNotify(struct NotifyRequest
*nr
) {
570 struct GlobalLock
*gl
= NULL
, *tmp
;
571 struct NotifyNode
*nn
;
573 D(bug("[fat] trying to add notification for '%s'\n", nr
->nr_FullName
));
575 /* if the request is for the volume root, then we just link to the root lock */
576 if (nr
->nr_FullName
[strlen(nr
->nr_FullName
)-1] == ':') {
577 D(bug("[fat] adding notify for root dir\n"));
578 gl
= &glob
->sb
->root_lock
;
582 if ((err
= InitDirHandle(glob
->sb
, 0, &dh
)) != 0)
585 /* look for the entry */
586 err
= GetDirEntryByPath(&dh
, nr
->nr_FullName
, strlen(nr
->nr_FullName
), &de
);
587 if (err
!= 0 && err
!= ERROR_OBJECT_NOT_FOUND
)
590 /* if it was found, then it might be open. try to find the global lock */
592 D(bug("[fat] file exists (%ld/%ld), looking for global lock\n", de
.cluster
, de
.index
));
594 ForeachNode(&glob
->sb
->locks
, tmp
)
595 if (tmp
->dir_cluster
== de
.cluster
&& tmp
->dir_entry
== de
.index
) {
598 D(bug("[fat] found global lock 0x%0x\n", gl
));
604 D(bug("[fat] file doesn't exist\n"));
608 D(bug("[fat] file not currently locked\n"));
610 /* allocate space to for the notify node */
611 if ((nn
= AllocVecPooled(glob
->mempool
, sizeof(struct NotifyNode
))) == NULL
)
612 return ERROR_NO_FREE_STORE
;
614 /* plug the bits in */
618 /* add to the list */
619 ADDTAIL(&glob
->sb
->notifies
, nn
);
621 D(bug("[fat] now reporting activity on '%s'\n", nr
->nr_FullName
));
626 LONG
OpRemoveNotify(struct NotifyRequest
*nr
) {
627 struct NotifyNode
*nn
;
629 D(bug("[fat] trying to remove notification for '%s'\n", nr
->nr_FullName
));
631 ForeachNode(&glob
->sb
->notifies
, nn
)
633 D(bug("[fat] found notify request in list, removing it\n"));
635 FreeVecPooled(glob
->mempool
, nn
);
639 D(bug("[fat] not found, doing nothing\n"));