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.
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 #define RESET_DIRHANDLE(dh) \
31 RESET_HANDLE(&(dh->ioh)); \
32 dh->cur_index = 0xffffffff; \
35 LONG
InitDirHandle(struct FSSuper
*sb
, ULONG cluster
, struct DirHandle
*dh
, BOOL reuse
) {
36 /* dh may or may not be initialised when this is called. if it is, then it
37 * probably has a valid cache block that we need to free, but we wouldn't
38 * know. we test the superblock pointer to figure out if it's valid or
40 if (reuse
&& (dh
->ioh
.sb
== sb
)) {
41 D(bug("[fat] reusing directory handle\n"));
42 if (dh
->ioh
.block
!= NULL
) {
43 cache_put_block(sb
->cache
, dh
->ioh
.block
, 0);
53 dh
->ioh
.first_cluster
= sb
->rootdir_cluster
;
54 dh
->ioh
.first_sector
= sb
->rootdir_sector
;
57 dh
->ioh
.first_cluster
= cluster
;
58 dh
->ioh
.first_sector
= 0;
62 dh
->ioh
.cur_sector
= dh
->ioh
.first_sector
;
63 dh
->ioh
.sector_offset
= 0;
65 D(bug("[fat] initialised dir handle, first cluster is %ld, first sector is %ld\n", dh
->ioh
.first_cluster
, dh
->ioh
.first_sector
));
70 LONG
ReleaseDirHandle(struct DirHandle
*dh
) {
71 D(bug("[fat] releasing dir handle (cluster %ld)\n", dh
->ioh
.first_cluster
));
76 LONG
GetDirEntry(struct DirHandle
*dh
, ULONG index
, struct DirEntry
*de
) {
80 D(bug("[fat] looking for dir entry %ld in dir starting at cluster %ld\n", index
, dh
->ioh
.first_cluster
));
82 /* fat dirs are limited to 2^16 entries */
83 if (index
>= 0x10000) {
84 D(bug("[fat] request for out-of-range index, returning not found\n"));
85 return ERROR_OBJECT_NOT_FOUND
;
88 /* setup the return object */
90 de
->cluster
= dh
->ioh
.first_cluster
;
92 de
->pos
= index
* sizeof(struct FATDirEntry
);
94 /* get the data directly into the entry */
95 err
= ReadFileChunk(&(dh
->ioh
), de
->pos
, sizeof(struct FATDirEntry
), (UBYTE
*) &(de
->e
.entry
), &nread
);
97 D(bug("[fat] dir entry lookup failed\n"));
101 /* remember where we are for GetNextDirEntry() */
102 dh
->cur_index
= index
;
108 LONG
GetNextDirEntry(struct DirHandle
*dh
, struct DirEntry
*de
) {
111 D(bug("[fat] looking for next entry after index %ld\n", dh
->cur_index
));
113 /* cur_index defaults to -1, so this will do the right thing even on a
115 while ((err
= GetDirEntry(dh
, dh
->cur_index
+ 1, de
)) == 0) {
116 /* end of directory, there is no next entry */
117 if (de
->e
.entry
.name
[0] == 0x00) {
118 D(bug("[fat] entry %ld is end-of-directory marker, we're done\n", dh
->cur_index
));
120 return ERROR_OBJECT_NOT_FOUND
;
123 /* skip unused entries */
124 if (de
->e
.entry
.name
[0] == 0xe5) {
125 D(bug("[fat] entry %ld is empty, skipping it\n", dh
->cur_index
));
129 /* this flag will be set for both volume name entries and long
130 * filename entries. either way we want to skip them */
131 if (de
->e
.entry
.attr
& ATTR_VOLUME_ID
) {
132 D(bug("[fat] entry %ld is a volume name or long filename, skipping it\n", dh
->cur_index
));
136 /* ignore the . and .. entries */
137 if (de
->e
.entry
.name
[0] == '.' &&
138 ((de
->index
== 0 && strncmp((char *) de
->e
.entry
.name
, ". ", 11) == 0) ||
139 (de
->index
== 1 && strncmp((char *) de
->e
.entry
.name
, ".. ", 11) == 0))) {
140 D(bug("[fat] skipping . or .. entry\n"));
144 D(bug("[fat] returning entry %ld\n", dh
->cur_index
));
152 LONG
GetParentDir(struct DirHandle
*dh
, struct DirEntry
*de
) {
153 D(bug("[fat] getting parent for directory at cluster %ld\n", dh
->ioh
.first_cluster
));
155 /* if we're already at the root, then we can't go any further */
156 if (dh
->ioh
.first_cluster
== dh
->ioh
.sb
->rootdir_cluster
) {
157 D(bug("[fat] trying go up past the root, so entry not found\n"));
158 return ERROR_OBJECT_NOT_FOUND
;
161 /* otherwise, the next cluster is held in the '..' entry, which is
163 GetDirEntry(dh
, 1, de
);
165 /* make sure it's actually the parent dir entry */
166 if (((de
->e
.entry
.attr
& ATTR_DIRECTORY
) == 0) ||
167 strncmp((char *) de
->e
.entry
.name
, ".. ", 11) != 0) {
168 D(bug("[fat] entry index 1 does not have name '..', can't go up\n"));
169 D(bug("[fat] actual name: '%.*s', attrs: 0x%x\n",
170 11, de
->e
.entry
.name
, de
->e
.entry
.attr
));
171 return ERROR_OBJECT_NOT_FOUND
;
175 InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(de
), dh
, TRUE
);
180 LONG
GetDirEntryByName(struct DirHandle
*dh
, STRPTR name
, ULONG namelen
, struct DirEntry
*de
) {
185 D(bug("[fat] looking for dir entry with name '%s'\n", name
));
187 /* start at the start */
190 /* loop through the entries until we find a match */
191 while ((err
= GetNextDirEntry(dh
, de
)) == 0) {
192 /* compare with the short name first, since we already have it */
193 GetDirEntryShortName(de
, buf
, &buflen
);
194 if (namelen
== buflen
&& strnicmp((char *) name
, (char *) buf
, buflen
) == 0) {
195 D(bug("[fat] matched short name '%s' at entry %ld, returning\n", buf
, dh
->cur_index
));
199 /* no match, extract the long name and compare with that instead */
200 GetDirEntryLongName(de
, buf
, &buflen
);
201 if (namelen
== buflen
&& strnicmp((char *) name
, (char *) buf
, buflen
) == 0) {
202 D(bug("[fat] matched long name '%s' at entry %ld, returning\n", buf
, dh
->cur_index
));
210 LONG
GetDirEntryByPath(struct DirHandle
*dh
, STRPTR path
, ULONG pathlen
, struct DirEntry
*de
) {
214 D(bug("[fat] looking for entry with path '"); RawPutChars(path
, pathlen
);
215 bug("' from dir at cluster %ld\n", dh
->ioh
.first_cluster
));
217 /* get back to the start of the dir */
220 /* if it starts with a volume specifier (or just a :), remove it and get
221 * us back to the root dir */
222 for (i
= 0; i
< pathlen
; i
++)
223 if (path
[i
] == ':') {
224 D(bug("[fat] path has volume specifier, moving to the root dir\n"));
229 InitDirHandle(dh
->ioh
.sb
, 0, dh
, TRUE
);
231 /* If we were called with simply ":" as the name we will return
232 immediately after this, so we prepare a fictional direntry for
234 Note that we fill only fields which are actually used in our handler */
236 de
->index
= -1; /* WARNING! Dummy index */
237 de
->e
.entry
.attr
= ATTR_DIRECTORY
;
238 de
->e
.entry
.first_cluster_hi
= 0;
239 de
->e
.entry
.first_cluster_lo
= 0;
244 D(bug("[fat] now looking for entry with path '"); RawPutChars(path
, pathlen
);
245 bug("' from dir at cluster %ld\n", dh
->ioh
.first_cluster
));
247 /* each time around the loop we find one dir/file in the full path */
248 while (pathlen
> 0) {
250 /* zoom forward and find the first dir separator */
251 for (len
= 0; len
< pathlen
&& path
[len
] != '/'; len
++);
253 D(bug("[fat] remaining path is '"); RawPutChars(path
, pathlen
);
254 bug("' (%d bytes), current chunk is '", pathlen
); RawPutChars(path
, len
);
255 bug("' (%d bytes)\n", len
));
257 /* if the first character is a /, then we have to go up a level */
260 /* get the parent dir, and bale if we've gone past it (i.e. we are
262 if ((err
= GetParentDir(dh
, de
)) != 0)
266 /* otherwise, we want to search the current directory for this name */
268 if ((err
= GetDirEntryByName(dh
, path
, len
, de
)) != 0)
269 return ERROR_OBJECT_NOT_FOUND
;
272 /* move up the buffer */
276 /* a / here is either the path separator or the directory we just went
277 * up. either way, we have to ignore it */
278 if (pathlen
> 0 && path
[0] == '/') {
284 /* more to do, so this entry had better be a directory */
285 if (!(de
->e
.entry
.attr
& ATTR_DIRECTORY
)) {
286 D(bug("[fat] '%.*s' is not a directory, so can't go any further\n", len
, path
));
287 return ERROR_OBJECT_WRONG_TYPE
;
290 InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(de
), dh
, TRUE
);
294 /* nothing left, so we've found it */
295 D(bug("[fat] found the entry, returning it\n"));
299 LONG
UpdateDirEntry(struct DirEntry
*de
) {
304 D(bug("[fat] writing dir entry %ld in dir starting at cluster %ld\n", de
->index
, de
->cluster
));
306 InitDirHandle(glob
->sb
, de
->cluster
, &dh
, FALSE
);
308 err
= WriteFileChunk(&(dh
.ioh
), de
->pos
, sizeof(struct FATDirEntry
), (UBYTE
*) &(de
->e
.entry
), &nwritten
);
310 D(bug("[fat] dir entry update failed\n"));
311 ReleaseDirHandle(&dh
);
315 ReleaseDirHandle(&dh
);
320 LONG
CreateDirEntry(struct DirHandle
*dh
, STRPTR name
, ULONG namelen
, UBYTE attr
, ULONG cluster
, struct DirEntry
*de
) {
326 D(bug("[fat] creating dir entry (name '"); RawPutChars(name
, namelen
);
327 bug("' attr 0x%02x cluster %ld)\n", attr
, cluster
));
329 /* find out how many entries we need */
330 nwant
= NumLongNameEntries(name
, namelen
) + 1;
332 D(bug("[fat] need to find room for %ld contiguous entries\n", nwant
));
334 /* get back to the start of the dir */
337 /* search the directory until we find a large enough gap */
340 while (nfound
< nwant
) {
341 err
= GetDirEntry(dh
, de
->index
+1, de
);
343 /* if we can't get the entry, then we ran off the end, so there's no
345 if (err
== ERROR_OBJECT_NOT_FOUND
) {
346 D(bug("[fat] ran off the end of the directory, no space left\n"));
347 return ERROR_NO_FREE_STORE
;
350 /* return any other error direct */
354 /* if it's unused, make a note */
355 if (de
->e
.entry
.name
[0] == 0xe5) {
360 /* if we hit end-of-directory, then we can shortcut it */
361 if (de
->e
.entry
.name
[0] == 0x00) {
364 if (de
->index
+ nwant
>= 0x10000) {
365 D(bug("[fat] hit end-of-directory marker, but there's not enough room left after it\n"));
366 return ERROR_NO_FREE_STORE
;
369 D(bug("[fat] found end-of-directory marker, making space after it\n"));
371 last
= de
->index
+ nwant
;
373 GetDirEntry(dh
, de
->index
+1, de
);
374 de
->e
.entry
.name
[0] = 0x00;
376 } while(de
->index
!= last
);
378 D(bug("[fat] new end-of-directory is entry %ld\n", de
->index
));
380 /* get the previous entry; this is this base (short name) entry */
381 GetDirEntry(dh
, de
->index
-1, de
);
386 /* anything else is a in-use entry, so reset our count */
390 D(bug("[fat] found a gap, base (short name) entry is %ld\n", de
->index
));
392 /* build the entry */
393 de
->e
.entry
.attr
= attr
;
394 de
->e
.entry
.nt_res
= 0;
397 ConvertAROSDate(ds
, &(de
->e
.entry
.create_date
), &(de
->e
.entry
.create_time
));
398 de
->e
.entry
.write_date
= de
->e
.entry
.create_date
;
399 de
->e
.entry
.write_time
= de
->e
.entry
.create_time
;
400 de
->e
.entry
.last_access_date
= de
->e
.entry
.create_date
;
402 /* XXX calculate this. I've just written ConvertAROSDate and I'm sick of
403 * dates and times for the moment */
404 de
->e
.entry
.create_time_tenth
= 0;
406 de
->e
.entry
.first_cluster_lo
= cluster
& 0xffff;
407 de
->e
.entry
.first_cluster_hi
= cluster
>> 16;
409 de
->e
.entry
.file_size
= 0;
411 SetDirEntryName(de
, name
, namelen
);
413 if ((err
= UpdateDirEntry(de
)) != 0) {
414 D(bug("[fat] couldn't update base directory entry, creation failed\n"));
418 D(bug("[fat] created dir entry %ld\n", de
->index
));
423 LONG
DeleteDirEntry(struct DirEntry
*de
) {
429 InitDirHandle(glob
->sb
, de
->cluster
, &dh
, FALSE
);
431 /* calculate the short name checksum before we trample on the name */
432 CALC_SHORT_NAME_CHECKSUM(de
->e
.entry
.name
, checksum
);
434 D(bug("[fat] short name checksum is 0x%02x\n", checksum
));
436 /* mark the short entry free */
437 de
->e
.entry
.name
[0] = 0xe5;
440 D(bug("[fat] deleted short name entry\n"));
442 /* now we loop over the previous entries, looking for matching long name
443 * entries and killing them */
445 while ((err
= GetDirEntry(&dh
, de
->index
-1, de
)) == 0) {
447 /* see if this is a matching long name entry. if it's not, we're done */
448 if (!((de
->e
.entry
.attr
& ATTR_LONG_NAME_MASK
) == ATTR_LONG_NAME
) ||
449 (de
->e
.long_entry
.order
& ~0x40) != order
||
450 de
->e
.long_entry
.checksum
!= checksum
)
455 de
->e
.entry
.name
[0] = 0xe5;
461 D(bug("[fat] deleted %ld long name entries\n", order
-1));
463 ReleaseDirHandle(&dh
);
472 LONG
FillFIB (struct ExtFileLock
*fl
, struct FileInfoBlock
*fib
) {
473 struct GlobalLock
*gl
= (fl
!= NULL
? fl
->gl
: &sb
->root_lock
);
479 D(bug("\tFilling FIB data.\n"));
481 if (gl
->dir_cluster
== FAT_ROOTDIR_MARK
) {
482 D(bug("\t\ttype: root directory\n"));
483 fib
->fib_DirEntryType
= ST_ROOT
;
485 else if (gl
->attr
& ATTR_DIRECTORY
) {
486 D(bug("\t\ttype: directory\n"));
487 fib
->fib_DirEntryType
= ST_USERDIR
;
490 D(bug("\t\ttype: file\n"));
491 fib
->fib_DirEntryType
= ST_FILE
;
494 D(bug("\t\tsize: %ld\n", gl
->size
));
496 fib
->fib_Size
= gl
->size
;
497 fib
->fib_NumBlocks
= ((gl
->size
+ (sb
->clustersize
- 1)) >> sb
->clustersize_bits
) << sb
->cluster_sectors_bits
;
498 fib
->fib_EntryType
= fib
->fib_DirEntryType
;
499 fib
->fib_DiskKey
= 0xfffffffflu
; //fl->entry;
501 if (fib
->fib_DirEntryType
== ST_ROOT
)
502 CopyMem(&sb
->volume
.create_time
, &fib
->fib_Date
, sizeof(struct DateStamp
));
504 InitDirHandle(sb
, gl
->dir_cluster
, &dh
, FALSE
);
505 GetDirEntry(&dh
, gl
->dir_entry
, &de
);
506 ConvertFATDate(de
.e
.entry
.write_date
, de
.e
.entry
.write_time
, &fib
->fib_Date
);
507 ReleaseDirHandle(&dh
);
510 len
= gl
->name
[0] <= 106 ? gl
->name
[0] : 106;
511 CopyMem(gl
->name
, fib
->fib_FileName
, len
+ 1);
512 fib
->fib_FileName
[len
+ 1] = '\0';
513 D(bug("\t\tname (len %ld) %s\n", len
, fib
->fib_FileName
+ 1));
515 fib
->fib_Protection
= 0;
516 if (gl
->attr
& ATTR_READ_ONLY
) fib
->fib_Protection
|= (FIBF_DELETE
| FIBF_WRITE
);
517 if (gl
->attr
& ATTR_ARCHIVE
) fib
->fib_Protection
|= FIBF_ARCHIVE
;
519 fib
->fib_Comment
[0] = 0;