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.
13 #include <aros/macros.h>
14 #include <exec/types.h>
16 #include <proto/exec.h>
17 #include <proto/dos.h>
24 #include "fat_protos.h"
26 #define DEBUG DEBUG_DIRENTRY
29 LONG
InitDirHandle(struct FSSuper
*sb
, ULONG cluster
, struct DirHandle
*dh
, BOOL reuse
) {
30 /* dh may or may not be initialised when this is called. if it is, then it
31 * probably has a valid cache block that we need to free, but we wouldn't
32 * know. we test the superblock pointer to figure out if it's valid or
34 if (reuse
&& (dh
->ioh
.sb
== sb
)) {
35 D(bug("[fat] reusing directory handle\n"));
36 if (dh
->ioh
.block
!= NULL
) {
37 Cache_FreeBlock(sb
->cache
, dh
->ioh
.block
);
47 dh
->ioh
.first_cluster
= sb
->rootdir_cluster
;
48 dh
->ioh
.first_sector
= sb
->rootdir_sector
;
51 dh
->ioh
.first_cluster
= cluster
;
52 dh
->ioh
.first_sector
= 0;
56 dh
->ioh
.cur_sector
= dh
->ioh
.first_sector
;
57 dh
->ioh
.sector_offset
= 0;
59 D(bug("[fat] initialised dir handle, first cluster is %ld, first sector is %ld\n", dh
->ioh
.first_cluster
, dh
->ioh
.first_sector
));
64 LONG
ReleaseDirHandle(struct DirHandle
*dh
) {
65 D(bug("[fat] releasing dir handle (cluster %ld)\n", dh
->ioh
.first_cluster
));
70 LONG
GetDirEntry(struct DirHandle
*dh
, ULONG index
, struct DirEntry
*de
) {
74 D(bug("[fat] looking for dir entry %ld in dir starting at cluster %ld\n", index
, dh
->ioh
.first_cluster
));
76 /* fat dirs are limited to 2^16 entries */
77 if (index
>= 0x10000) {
78 D(bug("[fat] request for out-of-range index, returning not found\n"));
79 return ERROR_OBJECT_NOT_FOUND
;
82 /* setup the return object */
84 de
->cluster
= dh
->ioh
.first_cluster
;
86 de
->pos
= index
* sizeof(struct FATDirEntry
);
88 /* get the data directly into the entry */
89 err
= ReadFileChunk(&(dh
->ioh
), de
->pos
, sizeof(struct FATDirEntry
), (UBYTE
*) &(de
->e
.entry
), &nread
);
91 D(bug("[fat] dir entry lookup failed\n"));
95 /* remember where we are for GetNextDirEntry() */
96 dh
->cur_index
= index
;
102 LONG
GetNextDirEntry(struct DirHandle
*dh
, struct DirEntry
*de
) {
105 D(bug("[fat] looking for next entry after index %ld\n", dh
->cur_index
));
107 /* cur_index defaults to -1, so this will do the right thing even on a
109 while ((err
= GetDirEntry(dh
, dh
->cur_index
+ 1, de
)) == 0) {
110 /* end of directory, there is no next entry */
111 if (de
->e
.entry
.name
[0] == 0x00) {
112 D(bug("[fat] entry %ld is end-of-directory marker, we're done\n", dh
->cur_index
));
114 return ERROR_OBJECT_NOT_FOUND
;
117 /* skip unused entries */
118 if (de
->e
.entry
.name
[0] == 0xe5) {
119 D(bug("[fat] entry %ld is empty, skipping it\n", dh
->cur_index
));
123 /* this flag will be set for both volume name entries and long
124 * filename entries. either way we want to skip them */
125 if (de
->e
.entry
.attr
& ATTR_VOLUME_ID
) {
126 D(bug("[fat] entry %ld is a volume name or long filename, skipping it\n", dh
->cur_index
));
130 /* ignore the . and .. entries */
131 if (de
->e
.entry
.name
[0] == '.' &&
132 ((de
->index
== 0 && strncmp((char *) de
->e
.entry
.name
, ". ", 11) == 0) ||
133 (de
->index
== 1 && strncmp((char *) de
->e
.entry
.name
, ".. ", 11) == 0))) {
134 D(bug("[fat] skipping . or .. entry\n"));
138 D(bug("[fat] returning entry %ld\n", dh
->cur_index
));
146 LONG
GetParentDir(struct DirHandle
*dh
, struct DirEntry
*de
) {
150 D(bug("[fat] getting parent for directory at cluster %ld\n", dh
->ioh
.first_cluster
));
152 /* if we're already at the root, then we can't go any further */
153 if (dh
->ioh
.first_cluster
== dh
->ioh
.sb
->rootdir_cluster
) {
154 D(bug("[fat] trying to go up past the root, so entry not found\n"));
155 return ERROR_OBJECT_NOT_FOUND
;
158 /* otherwise, the next cluster is held in the '..' entry, which is
160 GetDirEntry(dh
, 1, de
);
162 /* make sure it's actually the parent dir entry */
163 if (((de
->e
.entry
.attr
& ATTR_DIRECTORY
) == 0) ||
164 strncmp((char *) de
->e
.entry
.name
, ".. ", 11) != 0) {
165 D(bug("[fat] entry index 1 does not have name '..', can't go up\n"));
166 D(bug("[fat] actual name: '%.*s', attrs: 0x%x\n",
167 11, de
->e
.entry
.name
, de
->e
.entry
.attr
));
168 return ERROR_OBJECT_NOT_FOUND
;
172 InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(de
), dh
, TRUE
);
174 /* get handle on grandparent dir so we can find entry with parent's
176 if (dh
->ioh
.first_cluster
!= dh
->ioh
.sb
->rootdir_cluster
) {
177 D(bug("[fat] getting grandparent, first cluster is %ld, root cluster is %ld\n", dh
->ioh
.first_cluster
, dh
->ioh
.sb
->rootdir_cluster
));
178 cluster
= dh
->ioh
.first_cluster
;
179 GetDirEntry(dh
, 1, de
);
180 InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(de
), dh
, TRUE
);
182 err
= GetDirEntryByCluster(dh
, cluster
, de
);
188 LONG
GetDirEntryByCluster(struct DirHandle
*dh
, ULONG cluster
,
189 struct DirEntry
*de
) {
192 D(bug("[fat] looking for dir entry with first cluster %lu\n", cluster
));
194 /* start at the start */
197 /* loop through the entries until we find a match */
198 while ((err
= GetNextDirEntry(dh
, de
)) == 0) {
199 if (de
->e
.entry
.first_cluster_hi
== (cluster
>> 16) &&
200 de
->e
.entry
.first_cluster_lo
== (cluster
& 0xffff)) {
201 D(bug("[fat] matched starting cluster at entry %ld, returning\n",
207 D(bug("[fat] dir entry with first cluster %lu not found\n", cluster
));
211 LONG
GetDirEntryByName(struct DirHandle
*dh
, STRPTR name
, ULONG namelen
, struct DirEntry
*de
) {
216 D(bug("[fat] looking for dir entry with name '%s'\n", name
));
218 /* start at the start */
221 /* loop through the entries until we find a match */
222 while ((err
= GetNextDirEntry(dh
, de
)) == 0) {
223 /* compare with the short name first, since we already have it */
224 GetDirEntryShortName(de
, buf
, &buflen
);
225 if (namelen
== buflen
&& strnicmp((char *) name
, (char *) buf
, buflen
) == 0) {
226 D(bug("[fat] matched short name '%s' at entry %ld, returning\n", buf
, dh
->cur_index
));
230 /* no match, extract the long name and compare with that instead */
231 GetDirEntryLongName(de
, buf
, &buflen
);
232 if (namelen
== buflen
&& strnicmp((char *) name
, (char *) buf
, buflen
) == 0) {
233 D(bug("[fat] matched long name '%s' at entry %ld, returning\n", buf
, dh
->cur_index
));
241 LONG
GetDirEntryByPath(struct DirHandle
*dh
, STRPTR path
, ULONG pathlen
, struct DirEntry
*de
) {
245 D(bug("[fat] looking for entry with path '"); RawPutChars(path
, pathlen
);
246 bug("' from dir at cluster %ld\n", dh
->ioh
.first_cluster
));
248 /* get back to the start of the dir */
251 /* if it starts with a volume specifier (or just a :), remove it and get
252 * us back to the root dir */
253 for (i
= 0; i
< pathlen
; i
++)
254 if (path
[i
] == ':') {
255 D(bug("[fat] path has volume specifier, moving to the root dir\n"));
260 InitDirHandle(dh
->ioh
.sb
, 0, dh
, TRUE
);
262 /* If we were called with simply ":" as the name we will return
263 immediately after this, so we prepare a fictional direntry for
265 Note that we fill only fields which are actually used in our handler */
267 de
->index
= -1; /* WARNING! Dummy index */
268 de
->e
.entry
.attr
= ATTR_DIRECTORY
;
269 de
->e
.entry
.first_cluster_hi
= 0;
270 de
->e
.entry
.first_cluster_lo
= 0;
275 D(bug("[fat] now looking for entry with path '"); RawPutChars(path
, pathlen
);
276 bug("' from dir at cluster %ld\n", dh
->ioh
.first_cluster
));
278 /* each time around the loop we find one dir/file in the full path */
279 while (pathlen
> 0) {
281 /* zoom forward and find the first dir separator */
282 for (len
= 0; len
< pathlen
&& path
[len
] != '/'; len
++);
284 D(bug("[fat] remaining path is '"); RawPutChars(path
, pathlen
);
285 bug("' (%d bytes), current chunk is '", pathlen
); RawPutChars(path
, len
);
286 bug("' (%d bytes)\n", len
));
288 /* if the first character is a /, then we have to go up a level */
291 /* get the parent dir, and bale if we've gone past it (i.e. we are
293 if ((err
= GetParentDir(dh
, de
)) != 0)
297 /* otherwise, we want to search the current directory for this name */
299 if ((err
= GetDirEntryByName(dh
, path
, len
, de
)) != 0)
300 return ERROR_OBJECT_NOT_FOUND
;
303 /* move up the buffer */
307 /* a / here is either the path separator or the directory we just went
308 * up. either way, we have to ignore it */
309 if (pathlen
> 0 && path
[0] == '/') {
315 /* more to do, so this entry had better be a directory */
316 if (!(de
->e
.entry
.attr
& ATTR_DIRECTORY
)) {
317 D(bug("[fat] '%.*s' is not a directory, so can't go any further\n", len
, path
));
318 return ERROR_OBJECT_WRONG_TYPE
;
321 InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(de
), dh
, TRUE
);
325 /* nothing left, so we've found it */
326 D(bug("[fat] found the entry, returning it\n"));
330 LONG
UpdateDirEntry(struct DirEntry
*de
) {
335 D(bug("[fat] writing dir entry %ld in dir starting at cluster %ld\n", de
->index
, de
->cluster
));
337 InitDirHandle(glob
->sb
, de
->cluster
, &dh
, FALSE
);
339 err
= WriteFileChunk(&(dh
.ioh
), de
->pos
, sizeof(struct FATDirEntry
), (UBYTE
*) &(de
->e
.entry
), &nwritten
);
341 D(bug("[fat] dir entry update failed\n"));
342 ReleaseDirHandle(&dh
);
346 ReleaseDirHandle(&dh
);
351 LONG
AllocDirEntry(struct DirHandle
*dh
, ULONG gap
, struct DirEntry
*de
) {
355 BOOL clusteradded
= FALSE
;
357 /* find out how many entries we need */
360 D(bug("[fat] need to find room for %ld contiguous entries\n", nwant
));
362 /* get back to the start of the dir */
365 /* search the directory until we find a large enough gap */
368 while (nfound
< nwant
) {
369 err
= GetDirEntry(dh
, de
->index
+1, de
);
371 /* if we can't get the entry, then we ran off the end, so there's no
373 if (err
== ERROR_OBJECT_NOT_FOUND
) {
374 D(bug("[fat] ran off the end of the directory, no space left\n"));
375 return ERROR_NO_FREE_STORE
;
378 /* return any other error direct */
382 /* if it's unused, make a note */
383 if (de
->e
.entry
.name
[0] == 0xe5) {
388 /* if we hit end-of-directory, then we can shortcut it */
389 if (de
->e
.entry
.name
[0] == 0x00) {
392 if (de
->index
+ nwant
>= 0x10000) {
393 D(bug("[fat] hit end-of-directory marker, but there's not enough room left after it\n"));
394 return ERROR_NO_FREE_STORE
;
397 D(bug("[fat] found end-of-directory marker, making space after it\n"));
399 last
= de
->index
+ nwant
;
401 if (GetDirEntry(dh
, de
->index
+ 1, de
) != 0)
403 de
->e
.entry
.name
[0] = 0x00;
405 } while(de
->index
!= last
);
407 D(bug("[fat] new end-of-directory is entry %ld\n", de
->index
));
409 /* clear all remaining entries in any new cluster added */
411 while (GetDirEntry(dh
, de
->index
+ 1, de
) == 0) {
412 memset(&de
->e
.entry
, 0, sizeof(struct FATDirEntry
));
416 /* get the previous entry; this is the base (short name) entry */
417 GetDirEntry(dh
, last
- 1, de
);
422 /* anything else is an in-use entry, so reset our count */
426 D(bug("[fat] found a gap, base (short name) entry is %ld\n", de
->index
));
430 LONG
CreateDirEntry(struct DirHandle
*dh
, STRPTR name
, ULONG namelen
,
431 UBYTE attr
, ULONG cluster
, struct DirEntry
*de
) {
436 D(bug("[fat] creating dir entry (name '"); RawPutChars(name
, namelen
);
437 bug("' attr 0x%02x cluster %ld)\n", attr
, cluster
));
439 /* find out how many extra entries we need for the long name */
440 gap
= NumLongNameEntries(name
, namelen
);
442 /* search for a suitable unused entry */
443 err
= AllocDirEntry(dh
, gap
, de
);
447 /* build the entry */
448 de
->e
.entry
.attr
= attr
;
449 de
->e
.entry
.nt_res
= 0;
452 ConvertAROSDate(&ds
, &(de
->e
.entry
.create_date
), &(de
->e
.entry
.create_time
));
453 de
->e
.entry
.write_date
= de
->e
.entry
.create_date
;
454 de
->e
.entry
.write_time
= de
->e
.entry
.create_time
;
455 de
->e
.entry
.last_access_date
= de
->e
.entry
.create_date
;
456 de
->e
.entry
.create_time_tenth
= ds
.ds_Tick
% (TICKS_PER_SECOND
* 2)
457 / (TICKS_PER_SECOND
/ 10);
459 de
->e
.entry
.first_cluster_lo
= cluster
& 0xffff;
460 de
->e
.entry
.first_cluster_hi
= cluster
>> 16;
462 de
->e
.entry
.file_size
= 0;
464 SetDirEntryName(de
, name
, namelen
);
466 if ((err
= UpdateDirEntry(de
)) != 0) {
467 D(bug("[fat] couldn't update base directory entry, creation failed\n"));
471 D(bug("[fat] created dir entry %ld\n", de
->index
));
476 LONG
DeleteDirEntry(struct DirEntry
*de
) {
482 InitDirHandle(glob
->sb
, de
->cluster
, &dh
, FALSE
);
484 /* calculate the short name checksum before we trample on the name */
485 CALC_SHORT_NAME_CHECKSUM(de
->e
.entry
.name
, checksum
);
487 D(bug("[fat] short name checksum is 0x%02x\n", checksum
));
489 /* mark the short entry free */
490 de
->e
.entry
.name
[0] = 0xe5;
493 D(bug("[fat] deleted short name entry\n"));
495 /* now we loop over the previous entries, looking for matching long name
496 * entries and killing them */
498 while ((err
= GetDirEntry(&dh
, de
->index
-1, de
)) == 0) {
500 /* see if this is a matching long name entry. if it's not, we're done */
501 if (!((de
->e
.entry
.attr
& ATTR_LONG_NAME_MASK
) == ATTR_LONG_NAME
) ||
502 (de
->e
.long_entry
.order
& ~0x40) != order
||
503 de
->e
.long_entry
.checksum
!= checksum
)
508 de
->e
.entry
.name
[0] = 0xe5;
514 D(bug("[fat] deleted %ld long name entries\n", order
-1));
516 ReleaseDirHandle(&dh
);
525 LONG
FillFIB (struct ExtFileLock
*fl
, struct FileInfoBlock
*fib
) {
526 struct GlobalLock
*gl
= (fl
!= NULL
? fl
->gl
: &sb
->info
->root_lock
);
532 D(bug("\tFilling FIB data.\n"));
534 if (gl
->dir_cluster
== FAT_ROOTDIR_MARK
) {
535 D(bug("\t\ttype: root directory\n"));
536 fib
->fib_DirEntryType
= ST_ROOT
;
538 else if (gl
->attr
& ATTR_DIRECTORY
) {
539 D(bug("\t\ttype: directory\n"));
540 fib
->fib_DirEntryType
= ST_USERDIR
;
543 D(bug("\t\ttype: file\n"));
544 fib
->fib_DirEntryType
= ST_FILE
;
547 D(bug("\t\tsize: %ld\n", gl
->size
));
549 fib
->fib_Size
= gl
->size
;
550 fib
->fib_NumBlocks
= ((gl
->size
+ (sb
->clustersize
- 1)) >> sb
->clustersize_bits
) << sb
->cluster_sectors_bits
;
551 fib
->fib_EntryType
= fib
->fib_DirEntryType
;
552 fib
->fib_DiskKey
= 0xfffffffflu
; //fl->entry;
554 if (fib
->fib_DirEntryType
== ST_ROOT
)
555 CopyMem(&sb
->volume
.create_time
, &fib
->fib_Date
, sizeof(struct DateStamp
));
557 InitDirHandle(sb
, gl
->dir_cluster
, &dh
, FALSE
);
558 GetDirEntry(&dh
, gl
->dir_entry
, &de
);
559 ConvertFATDate(de
.e
.entry
.write_date
, de
.e
.entry
.write_time
, &fib
->fib_Date
);
560 ReleaseDirHandle(&dh
);
563 len
= gl
->name
[0] <= 106 ? gl
->name
[0] : 106;
564 CopyMem(gl
->name
, fib
->fib_FileName
, len
+ 1);
565 fib
->fib_FileName
[len
+ 1] = '\0';
566 D(bug("\t\tname (len %ld) %s\n", len
, fib
->fib_FileName
+ 1));
568 fib
->fib_Protection
= 0;
569 if (gl
->attr
& ATTR_READ_ONLY
) fib
->fib_Protection
|= (FIBF_DELETE
| FIBF_WRITE
);
570 if (gl
->attr
& ATTR_ARCHIVE
) fib
->fib_Protection
|= FIBF_ARCHIVE
;
572 fib
->fib_Comment
[0] = 0;