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 #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
,
30 BOOL reuse
, struct Globals
*glob
)
32 /* 'dh' may or may not be initialised when this is called. if it is, then
33 * it probably has a valid cache block that we need to free, but we
34 * wouldn't know. we test the superblock pointer to figure out if it's
36 if (reuse
&& (dh
->ioh
.sb
== sb
))
38 D(bug("[fat] reusing directory handle\n"));
39 if (dh
->ioh
.block
!= NULL
)
41 Cache_FreeBlock(sb
->cache
, dh
->ioh
.block
);
53 dh
->ioh
.first_cluster
= sb
->rootdir_cluster
;
54 dh
->ioh
.first_sector
= sb
->rootdir_sector
;
58 dh
->ioh
.first_cluster
= cluster
;
59 dh
->ioh
.first_sector
= 0;
63 dh
->ioh
.cur_sector
= dh
->ioh
.first_sector
;
64 dh
->ioh
.sector_offset
= 0;
66 D(bug("[fat] initialised dir handle, first cluster is %ld,"
67 " first sector is %ld\n", dh
->ioh
.first_cluster
,
68 dh
->ioh
.first_sector
));
73 LONG
ReleaseDirHandle(struct DirHandle
*dh
, struct Globals
*glob
)
75 D(bug("[fat] releasing dir handle (cluster %ld)\n",
76 dh
->ioh
.first_cluster
));
82 LONG
GetDirEntry(struct DirHandle
*dh
, ULONG index
, struct DirEntry
*de
,
88 D(bug("[fat] looking for dir entry %ld in dir starting at cluster %ld\n",
89 index
, dh
->ioh
.first_cluster
));
91 /* FAT dirs are limited to 2^16 entries */
94 D(bug("[fat] request for out-of-range index, returning not found\n"));
95 return ERROR_OBJECT_NOT_FOUND
;
98 /* Set up the return object */
100 de
->cluster
= dh
->ioh
.first_cluster
;
102 de
->pos
= index
* sizeof(struct FATDirEntry
);
104 /* Get the data directly into the entry */
105 err
= ReadFileChunk(&(dh
->ioh
), de
->pos
, sizeof(struct FATDirEntry
),
106 (UBYTE
*) &(de
->e
.entry
), &nread
);
109 D(bug("[fat] dir entry lookup failed\n"));
113 /* Remember where we are for GetNextDirEntry() */
114 dh
->cur_index
= index
;
120 LONG
GetNextDirEntry(struct DirHandle
*dh
, struct DirEntry
*de
,
121 struct Globals
*glob
)
125 D(bug("[fat] looking for next entry after index %ld\n", dh
->cur_index
));
127 /* 'cur_index' defaults to -1, so this will do the right thing even on a
129 while ((err
= GetDirEntry(dh
, dh
->cur_index
+ 1, de
, glob
)) == 0)
131 /* End of directory, there is no next entry */
132 if (de
->e
.entry
.name
[0] == 0x00)
134 D(bug("[fat] entry %ld is end-of-directory marker, we're done\n",
137 return ERROR_OBJECT_NOT_FOUND
;
140 /* Skip unused entries */
141 if (de
->e
.entry
.name
[0] == 0xe5)
143 D(bug("[fat] entry %ld is empty, skipping it\n", dh
->cur_index
));
147 /* This flag will be set for both volume name entries and long
148 * filename entries. Either way we want to skip them */
149 if (de
->e
.entry
.attr
& ATTR_VOLUME_ID
)
151 D(bug("[fat] entry %ld is a volume name or long filename,"
152 " skipping it\n", dh
->cur_index
));
156 /* Ignore the . and .. entries */
157 if (de
->e
.entry
.name
[0] == '.' && ((de
->index
== 0
158 && strncmp((char *)de
->e
.entry
.name
, ". ",
159 FAT_MAX_SHORT_NAME
) == 0)
161 && strncmp((char *)de
->e
.entry
.name
, ".. ",
162 FAT_MAX_SHORT_NAME
) == 0)))
164 D(bug("[fat] skipping . or .. entry\n"));
168 D(bug("[fat] returning entry %ld\n", dh
->cur_index
));
176 LONG
GetParentDir(struct DirHandle
*dh
, struct DirEntry
*de
,
177 struct Globals
*glob
)
182 D(bug("[fat] getting parent for directory at cluster %ld\n",
183 dh
->ioh
.first_cluster
));
185 /* If we're already at the root, then we can't go any further */
186 if (dh
->ioh
.first_cluster
== dh
->ioh
.sb
->rootdir_cluster
)
188 D(bug("[fat] trying to go up past the root, so entry not found\n"));
189 return ERROR_OBJECT_NOT_FOUND
;
192 /* Otherwise, the next cluster is held in the '..' entry, which is
194 GetDirEntry(dh
, 1, de
, glob
);
196 /* Make sure it's actually the parent dir entry */
197 if (((de
->e
.entry
.attr
& ATTR_DIRECTORY
) == 0) ||
198 strncmp((char *)de
->e
.entry
.name
, ".. ", FAT_MAX_SHORT_NAME
)
201 D(bug("[fat] entry index 1 does not have name '..', can't go up\n"));
202 D(bug("[fat] actual name: '%.*s', attrs: 0x%x\n", FAT_MAX_SHORT_NAME
,
203 de
->e
.entry
.name
, de
->e
.entry
.attr
));
204 return ERROR_OBJECT_NOT_FOUND
;
208 InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(de
), dh
, TRUE
, glob
);
210 /* Get handle on grandparent dir so we can find entry with parent's
212 if (dh
->ioh
.first_cluster
!= dh
->ioh
.sb
->rootdir_cluster
)
214 D(bug("[fat] getting grandparent, first cluster is %ld,"
215 " root cluster is %ld\n", dh
->ioh
.first_cluster
,
216 dh
->ioh
.sb
->rootdir_cluster
));
217 cluster
= dh
->ioh
.first_cluster
;
218 GetDirEntry(dh
, 1, de
, glob
);
219 InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(de
), dh
, TRUE
, glob
);
221 err
= GetDirEntryByCluster(dh
, cluster
, de
, glob
);
227 LONG
GetDirEntryByCluster(struct DirHandle
*dh
, ULONG cluster
,
228 struct DirEntry
*de
, struct Globals
*glob
)
232 D(bug("[fat] looking for dir entry with first cluster %lu\n", cluster
));
234 /* Start at the start */
237 /* Loop through the entries until we find a match */
238 while ((err
= GetNextDirEntry(dh
, de
, glob
)) == 0)
240 if (de
->e
.entry
.first_cluster_hi
== (cluster
>> 16)
241 && de
->e
.entry
.first_cluster_lo
== (cluster
& 0xffff))
243 D(bug("[fat] matched starting cluster at entry %ld, returning\n",
249 D(bug("[fat] dir entry with first cluster %lu not found\n", cluster
));
253 LONG
GetDirEntryByName(struct DirHandle
*dh
, STRPTR name
, ULONG namelen
,
254 struct DirEntry
*de
, struct Globals
*glob
)
260 D(bug("[fat] looking for dir entry with name '%s'\n", name
));
262 /* Start at the start */
265 /* Loop through the entries until we find a match */
266 while ((err
= GetNextDirEntry(dh
, de
, glob
)) == 0)
268 /* Compare with the short name first, since we already have it */
269 GetDirEntryShortName(de
, buf
, &buflen
, glob
);
270 if (namelen
== buflen
271 && strnicmp((char *)name
, (char *)buf
, buflen
) == 0)
273 D(bug("[fat] matched short name '%s' at entry %ld, returning\n",
274 buf
, dh
->cur_index
));
278 /* No match, extract the long name and compare with that instead */
279 GetDirEntryLongName(de
, buf
, &buflen
);
280 if (namelen
== buflen
281 && strnicmp((char *)name
, (char *)buf
, buflen
) == 0)
283 D(bug("[fat] matched long name '%s' at entry %ld, returning\n",
284 buf
, dh
->cur_index
));
292 LONG
GetDirEntryByPath(struct DirHandle
*dh
, STRPTR path
, ULONG pathlen
,
293 struct DirEntry
*de
, struct Globals
*glob
)
299 bug("[fat] looking for entry with path '");
300 RawPutChars(path
, pathlen
);
301 bug("' from dir at cluster %ld\n", dh
->ioh
.first_cluster
);
304 /* Get back to the start of the dir */
307 /* If it starts with a volume specifier (or just a :), remove it and get
308 * us back to the root dir */
309 for (i
= 0; i
< pathlen
; i
++)
313 "[fat] path has volume specifier, moving to the root dir\n"));
318 InitDirHandle(dh
->ioh
.sb
, 0, dh
, TRUE
, glob
);
320 /* If we were called with simply ":" as the name we will return
321 immediately after this, so we prepare a fictional direntry for
322 such a case. Note that we fill only fields which are actually
323 used in our handler */
325 de
->index
= -1; /* WARNING! Dummy index */
326 de
->e
.entry
.attr
= ATTR_DIRECTORY
;
327 de
->e
.entry
.first_cluster_hi
= 0;
328 de
->e
.entry
.first_cluster_lo
= 0;
334 bug("[fat] now looking for entry with path '");
335 RawPutChars(path
, pathlen
);
336 bug("' from dir at cluster %ld\n", dh
->ioh
.first_cluster
);
339 /* Each time around the loop we find one dir/file in the full path */
343 /* Zoom forward and find the first dir separator */
344 for (len
= 0; len
< pathlen
&& path
[len
] != '/'; len
++);
347 bug("[fat] remaining path is '");
348 RawPutChars(path
, pathlen
);
349 bug("' (%d bytes), current chunk is '", pathlen
);
350 RawPutChars(path
, len
); bug("' (%d bytes)\n", len
);
353 /* If the first character is a '/', then we have to go up a level */
356 /* Get the parent dir, and bale if we've gone past it (i.e. we are
358 if ((err
= GetParentDir(dh
, de
, glob
)) != 0)
362 /* Otherwise, we want to search the current directory for this name */
365 if ((err
= GetDirEntryByName(dh
, path
, len
, de
, glob
)) != 0)
366 return ERROR_OBJECT_NOT_FOUND
;
369 /* Move up the buffer */
373 /* A '/' here is either the path separator or the directory we just
374 * went up. Either way, we have to ignore it */
375 if (pathlen
> 0 && path
[0] == '/')
383 /* More to do, so this entry had better be a directory */
384 if (!(de
->e
.entry
.attr
& ATTR_DIRECTORY
))
386 D(bug("[fat] '%.*s' is not a directory,"
387 " so can't go any further\n", len
, path
));
388 return ERROR_OBJECT_WRONG_TYPE
;
391 InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(de
), dh
, TRUE
,
396 /* Nothing left, so we've found it */
397 D(bug("[fat] found the entry, returning it\n"));
401 LONG
UpdateDirEntry(struct DirEntry
*de
, struct Globals
*glob
)
407 D(bug("[fat] writing dir entry %ld in dir starting at cluster %ld\n",
408 de
->index
, de
->cluster
));
410 InitDirHandle(glob
->sb
, de
->cluster
, &dh
, FALSE
, glob
);
413 WriteFileChunk(&(dh
.ioh
), de
->pos
, sizeof(struct FATDirEntry
),
414 (UBYTE
*) &(de
->e
.entry
), &nwritten
);
417 D(bug("[fat] dir entry update failed\n"));
418 ReleaseDirHandle(&dh
, glob
);
422 ReleaseDirHandle(&dh
, glob
);
427 LONG
AllocDirEntry(struct DirHandle
*dh
, ULONG gap
, struct DirEntry
*de
,
428 struct Globals
*glob
)
433 BOOL clusteradded
= FALSE
;
435 /* Find out how many entries we need */
438 D(bug("[fat] need to find room for %ld contiguous entries\n", nwant
));
440 /* Get back to the start of the dir */
443 /* Search the directory until we find a large enough gap */
446 while (nfound
< nwant
)
448 err
= GetDirEntry(dh
, de
->index
+ 1, de
, glob
);
450 /* If we can't get the entry, then we ran off the end, so there's no
452 if (err
== ERROR_OBJECT_NOT_FOUND
)
454 D(bug("[fat] ran off the end of the directory, no space left\n"));
455 return ERROR_NO_FREE_STORE
;
458 /* Return any other error direct */
462 /* If it's unused, make a note */
463 if (de
->e
.entry
.name
[0] == 0xe5)
469 /* If we hit end-of-directory, then we can shortcut it */
470 if (de
->e
.entry
.name
[0] == 0x00)
474 if (de
->index
+ nwant
>= 0x10000)
476 D(bug("[fat] hit end-of-directory marker,"
477 " but there's not enough room left after it\n"));
478 return ERROR_NO_FREE_STORE
;
481 D(bug("[fat] found end-of-directory marker,"
482 " making space after it\n"));
484 last
= de
->index
+ nwant
;
487 if (GetDirEntry(dh
, de
->index
+ 1, de
, glob
) != 0)
489 de
->e
.entry
.name
[0] = 0x00;
490 UpdateDirEntry(de
, glob
);
492 while (de
->index
!= last
);
494 D(bug("[fat] new end-of-directory is entry %ld\n", de
->index
));
496 /* Clear all remaining entries in any new cluster added */
498 while (GetDirEntry(dh
, de
->index
+ 1, de
, glob
) == 0)
500 memset(&de
->e
.entry
, 0, sizeof(struct FATDirEntry
));
501 UpdateDirEntry(de
, glob
);
504 /* Get the previous entry; this is the base (short name) entry */
505 GetDirEntry(dh
, last
- 1, de
, glob
);
510 /* Anything else is an in-use entry, so reset our count */
514 D(bug("[fat] found a gap, base (short name) entry is %ld\n",
519 LONG
CreateDirEntry(struct DirHandle
*dh
, STRPTR name
, ULONG namelen
,
520 UBYTE attr
, ULONG cluster
, struct DirEntry
*de
, struct Globals
*glob
)
526 bug("[fat] creating dir entry (name '");
527 RawPutChars(name
, namelen
);
528 bug("' attr 0x%02x cluster %ld)\n", attr
, cluster
);
531 /* Find out how many extra entries we need for the long name */
532 gap
= NumLongNameEntries(name
, namelen
);
534 /* Search for a suitable unused entry */
535 err
= AllocDirEntry(dh
, gap
, de
, glob
);
539 /* Build the entry */
540 FillDirEntry(de
, attr
, cluster
, glob
);
542 SetDirEntryName(de
, name
, namelen
);
544 if ((err
= UpdateDirEntry(de
, glob
)) != 0)
547 "[fat] couldn't update base directory entry, creation failed\n"));
551 D(bug("[fat] created dir entry %ld\n", de
->index
));
556 void FillDirEntry(struct DirEntry
*de
, UBYTE attr
, ULONG cluster
,
557 struct Globals
*glob
)
561 de
->e
.entry
.attr
= attr
;
562 de
->e
.entry
.nt_res
= 0;
565 ConvertDOSDate(&ds
, &(de
->e
.entry
.create_date
),
566 &(de
->e
.entry
.create_time
), glob
);
567 de
->e
.entry
.write_date
= de
->e
.entry
.create_date
;
568 de
->e
.entry
.write_time
= de
->e
.entry
.create_time
;
569 de
->e
.entry
.last_access_date
= de
->e
.entry
.create_date
;
570 de
->e
.entry
.create_time_tenth
= ds
.ds_Tick
% (TICKS_PER_SECOND
* 2)
571 / (TICKS_PER_SECOND
/ 10);
573 de
->e
.entry
.first_cluster_lo
= cluster
& 0xffff;
574 de
->e
.entry
.first_cluster_hi
= cluster
>> 16;
576 de
->e
.entry
.file_size
= 0;
579 LONG
DeleteDirEntry(struct DirEntry
*de
, struct Globals
*glob
)
586 InitDirHandle(glob
->sb
, de
->cluster
, &dh
, FALSE
, glob
);
588 /* Calculate the short name checksum before we trample on the name */
589 CALC_SHORT_NAME_CHECKSUM(de
->e
.entry
.name
, checksum
);
591 D(bug("[fat] short name checksum is 0x%02x\n", checksum
));
593 /* Mark the short entry free */
594 de
->e
.entry
.name
[0] = 0xe5;
595 UpdateDirEntry(de
, glob
);
597 D(bug("[fat] deleted short name entry\n"));
599 /* Now we loop over the previous entries, looking for matching long name
600 * entries and killing them */
602 while ((err
= GetDirEntry(&dh
, de
->index
- 1, de
, glob
)) == 0)
605 /* See if this is a matching long name entry. If it's not, we're done */
606 if (!((de
->e
.entry
.attr
& ATTR_LONG_NAME_MASK
) == ATTR_LONG_NAME
) ||
607 (de
->e
.long_entry
.order
& ~0x40) != order
||
608 de
->e
.long_entry
.checksum
!= checksum
)
613 de
->e
.entry
.name
[0] = 0xe5;
614 UpdateDirEntry(de
, glob
);
619 D(bug("[fat] deleted %ld long name entries\n", order
- 1));
621 ReleaseDirHandle(&dh
, glob
);
626 LONG
FillFIB(struct ExtFileLock
*fl
, struct FileInfoBlock
*fib
,
627 struct Globals
*glob
)
629 struct FSSuper
*sb
= glob
->sb
;
630 struct GlobalLock
*gl
= (fl
!= NULL
? fl
->gl
: &sb
->info
->root_lock
);
636 D(bug("\tFilling FIB data.\n"));
638 if (gl
== &sb
->info
->root_lock
)
640 D(bug("\t\ttype: root directory\n"));
641 fib
->fib_DirEntryType
= ST_ROOT
;
643 else if (gl
->attr
& ATTR_DIRECTORY
)
645 D(bug("\t\ttype: directory\n"));
646 fib
->fib_DirEntryType
= ST_USERDIR
;
650 D(bug("\t\ttype: file\n"));
651 fib
->fib_DirEntryType
= ST_FILE
;
654 D(bug("\t\tsize: %ld\n", gl
->size
));
656 fib
->fib_Size
= gl
->size
;
657 fib
->fib_NumBlocks
= ((gl
->size
+ (sb
->clustersize
- 1))
658 >> sb
->clustersize_bits
) << sb
->cluster_sectors_bits
;
659 fib
->fib_EntryType
= fib
->fib_DirEntryType
;
660 fib
->fib_DiskKey
= 0xfffffffflu
; //fl->entry;
662 if (fib
->fib_DirEntryType
== ST_ROOT
)
663 CopyMem(&sb
->volume
.create_time
, &fib
->fib_Date
,
664 sizeof(struct DateStamp
));
667 InitDirHandle(sb
, gl
->dir_cluster
, &dh
, FALSE
, glob
);
668 GetDirEntry(&dh
, gl
->dir_entry
, &de
, glob
);
669 ConvertFATDate(de
.e
.entry
.write_date
, de
.e
.entry
.write_time
,
670 &fib
->fib_Date
, glob
);
671 ReleaseDirHandle(&dh
, glob
);
674 len
= gl
->name
[0] <= 106 ? gl
->name
[0] : 106;
675 CopyMem(gl
->name
, fib
->fib_FileName
, len
+ 1);
676 fib
->fib_FileName
[len
+ 1] = '\0';
677 D(bug("\t\tname (len %ld) %s\n", len
, fib
->fib_FileName
+ 1));
679 fib
->fib_Protection
= 0;
680 if (gl
->attr
& ATTR_READ_ONLY
)
681 fib
->fib_Protection
|= (FIBF_DELETE
| FIBF_WRITE
);
682 if (gl
->attr
& ATTR_ARCHIVE
)
683 fib
->fib_Protection
|= FIBF_ARCHIVE
;
685 fib
->fib_Comment
[0] = 0;
687 CopyMem(gl
->shortname
, fib
->fib_Comment
, gl
->shortname
[0] + 1);