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 it
33 * probably has a valid cache block that we need to free, but we wouldn't
34 * know. we test the superblock pointer to figure out if it's valid or
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
323 Note that we fill only fields which are actually 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 */
357 /* get the parent dir, and bale if we've gone past it (i.e. we are
359 if ((err
= GetParentDir(dh
, de
, glob
)) != 0)
363 /* otherwise, we want to search the current directory for this name */
366 if ((err
= GetDirEntryByName(dh
, path
, len
, de
, glob
)) != 0)
367 return ERROR_OBJECT_NOT_FOUND
;
370 /* move up the buffer */
374 /* a / here is either the path separator or the directory we just went
375 * up. either way, we have to ignore it */
376 if (pathlen
> 0 && path
[0] == '/')
384 /* more to do, so this entry had better be a directory */
385 if (!(de
->e
.entry
.attr
& ATTR_DIRECTORY
))
387 D(bug("[fat] '%.*s' is not a directory,"
388 " so can't go any further\n", len
, path
));
389 return ERROR_OBJECT_WRONG_TYPE
;
392 InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(de
), dh
, TRUE
,
397 /* nothing left, so we've found it */
398 D(bug("[fat] found the entry, returning it\n"));
402 LONG
UpdateDirEntry(struct DirEntry
*de
, struct Globals
*glob
)
408 D(bug("[fat] writing dir entry %ld in dir starting at cluster %ld\n",
409 de
->index
, de
->cluster
));
411 InitDirHandle(glob
->sb
, de
->cluster
, &dh
, FALSE
, glob
);
414 WriteFileChunk(&(dh
.ioh
), de
->pos
, sizeof(struct FATDirEntry
),
415 (UBYTE
*) &(de
->e
.entry
), &nwritten
);
418 D(bug("[fat] dir entry update failed\n"));
419 ReleaseDirHandle(&dh
, glob
);
423 ReleaseDirHandle(&dh
, glob
);
428 LONG
AllocDirEntry(struct DirHandle
*dh
, ULONG gap
, struct DirEntry
*de
,
429 struct Globals
*glob
)
434 BOOL clusteradded
= FALSE
;
436 /* find out how many entries we need */
439 D(bug("[fat] need to find room for %ld contiguous entries\n", nwant
));
441 /* get back to the start of the dir */
444 /* search the directory until we find a large enough gap */
447 while (nfound
< nwant
)
449 err
= GetDirEntry(dh
, de
->index
+ 1, de
, glob
);
451 /* if we can't get the entry, then we ran off the end, so there's no
453 if (err
== ERROR_OBJECT_NOT_FOUND
)
455 D(bug("[fat] ran off the end of the directory, no space left\n"));
456 return ERROR_NO_FREE_STORE
;
459 /* return any other error direct */
463 /* if it's unused, make a note */
464 if (de
->e
.entry
.name
[0] == 0xe5)
470 /* if we hit end-of-directory, then we can shortcut it */
471 if (de
->e
.entry
.name
[0] == 0x00)
475 if (de
->index
+ nwant
>= 0x10000)
477 D(bug("[fat] hit end-of-directory marker,"
478 " but there's not enough room left after it\n"));
479 return ERROR_NO_FREE_STORE
;
482 D(bug("[fat] found end-of-directory marker,"
483 " making space after it\n"));
485 last
= de
->index
+ nwant
;
488 if (GetDirEntry(dh
, de
->index
+ 1, de
, glob
) != 0)
490 de
->e
.entry
.name
[0] = 0x00;
491 UpdateDirEntry(de
, glob
);
493 while (de
->index
!= last
);
495 D(bug("[fat] new end-of-directory is entry %ld\n", de
->index
));
497 /* clear all remaining entries in any new cluster added */
499 while (GetDirEntry(dh
, de
->index
+ 1, de
, glob
) == 0)
501 memset(&de
->e
.entry
, 0, sizeof(struct FATDirEntry
));
502 UpdateDirEntry(de
, glob
);
505 /* get the previous entry; this is the base (short name) entry */
506 GetDirEntry(dh
, last
- 1, de
, glob
);
511 /* anything else is an in-use entry, so reset our count */
515 D(bug("[fat] found a gap, base (short name) entry is %ld\n",
520 LONG
CreateDirEntry(struct DirHandle
*dh
, STRPTR name
, ULONG namelen
,
521 UBYTE attr
, ULONG cluster
, struct DirEntry
*de
, struct Globals
*glob
)
527 bug("[fat] creating dir entry (name '");
528 RawPutChars(name
, namelen
);
529 bug("' attr 0x%02x cluster %ld)\n", attr
, cluster
);
532 /* find out how many extra entries we need for the long name */
533 gap
= NumLongNameEntries(name
, namelen
);
535 /* search for a suitable unused entry */
536 err
= AllocDirEntry(dh
, gap
, de
, glob
);
540 /* build the entry */
541 FillDirEntry(de
, attr
, cluster
, glob
);
543 SetDirEntryName(de
, name
, namelen
);
545 if ((err
= UpdateDirEntry(de
, glob
)) != 0)
548 "[fat] couldn't update base directory entry, creation failed\n"));
552 D(bug("[fat] created dir entry %ld\n", de
->index
));
557 void FillDirEntry(struct DirEntry
*de
, UBYTE attr
, ULONG cluster
,
558 struct Globals
*glob
)
562 de
->e
.entry
.attr
= attr
;
563 de
->e
.entry
.nt_res
= 0;
566 ConvertDOSDate(&ds
, &(de
->e
.entry
.create_date
),
567 &(de
->e
.entry
.create_time
), glob
);
568 de
->e
.entry
.write_date
= de
->e
.entry
.create_date
;
569 de
->e
.entry
.write_time
= de
->e
.entry
.create_time
;
570 de
->e
.entry
.last_access_date
= de
->e
.entry
.create_date
;
571 de
->e
.entry
.create_time_tenth
= ds
.ds_Tick
% (TICKS_PER_SECOND
* 2)
572 / (TICKS_PER_SECOND
/ 10);
574 de
->e
.entry
.first_cluster_lo
= cluster
& 0xffff;
575 de
->e
.entry
.first_cluster_hi
= cluster
>> 16;
577 de
->e
.entry
.file_size
= 0;
580 LONG
DeleteDirEntry(struct DirEntry
*de
, struct Globals
*glob
)
587 InitDirHandle(glob
->sb
, de
->cluster
, &dh
, FALSE
, glob
);
589 /* calculate the short name checksum before we trample on the name */
590 CALC_SHORT_NAME_CHECKSUM(de
->e
.entry
.name
, checksum
);
592 D(bug("[fat] short name checksum is 0x%02x\n", checksum
));
594 /* mark the short entry free */
595 de
->e
.entry
.name
[0] = 0xe5;
596 UpdateDirEntry(de
, glob
);
598 D(bug("[fat] deleted short name entry\n"));
600 /* now we loop over the previous entries, looking for matching long name
601 * entries and killing them */
603 while ((err
= GetDirEntry(&dh
, de
->index
- 1, de
, glob
)) == 0)
606 /* see if this is a matching long name entry. if it's not, we're done */
607 if (!((de
->e
.entry
.attr
& ATTR_LONG_NAME_MASK
) == ATTR_LONG_NAME
) ||
608 (de
->e
.long_entry
.order
& ~0x40) != order
||
609 de
->e
.long_entry
.checksum
!= checksum
)
614 de
->e
.entry
.name
[0] = 0xe5;
615 UpdateDirEntry(de
, glob
);
620 D(bug("[fat] deleted %ld long name entries\n", order
- 1));
622 ReleaseDirHandle(&dh
, glob
);
627 LONG
FillFIB(struct ExtFileLock
*fl
, struct FileInfoBlock
*fib
,
628 struct Globals
*glob
)
630 struct FSSuper
*sb
= glob
->sb
;
631 struct GlobalLock
*gl
= (fl
!= NULL
? fl
->gl
: &sb
->info
->root_lock
);
637 D(bug("\tFilling FIB data.\n"));
639 if (gl
== &sb
->info
->root_lock
)
641 D(bug("\t\ttype: root directory\n"));
642 fib
->fib_DirEntryType
= ST_ROOT
;
644 else if (gl
->attr
& ATTR_DIRECTORY
)
646 D(bug("\t\ttype: directory\n"));
647 fib
->fib_DirEntryType
= ST_USERDIR
;
651 D(bug("\t\ttype: file\n"));
652 fib
->fib_DirEntryType
= ST_FILE
;
655 D(bug("\t\tsize: %ld\n", gl
->size
));
657 fib
->fib_Size
= gl
->size
;
658 fib
->fib_NumBlocks
= ((gl
->size
+ (sb
->clustersize
- 1))
659 >> sb
->clustersize_bits
) << sb
->cluster_sectors_bits
;
660 fib
->fib_EntryType
= fib
->fib_DirEntryType
;
661 fib
->fib_DiskKey
= 0xfffffffflu
; //fl->entry;
663 if (fib
->fib_DirEntryType
== ST_ROOT
)
664 CopyMem(&sb
->volume
.create_time
, &fib
->fib_Date
,
665 sizeof(struct DateStamp
));
668 InitDirHandle(sb
, gl
->dir_cluster
, &dh
, FALSE
, glob
);
669 GetDirEntry(&dh
, gl
->dir_entry
, &de
, glob
);
670 ConvertFATDate(de
.e
.entry
.write_date
, de
.e
.entry
.write_time
,
671 &fib
->fib_Date
, glob
);
672 ReleaseDirHandle(&dh
, glob
);
675 len
= gl
->name
[0] <= 106 ? gl
->name
[0] : 106;
676 CopyMem(gl
->name
, fib
->fib_FileName
, len
+ 1);
677 fib
->fib_FileName
[len
+ 1] = '\0';
678 D(bug("\t\tname (len %ld) %s\n", len
, fib
->fib_FileName
+ 1));
680 fib
->fib_Protection
= 0;
681 if (gl
->attr
& ATTR_READ_ONLY
)
682 fib
->fib_Protection
|= (FIBF_DELETE
| FIBF_WRITE
);
683 if (gl
->attr
& ATTR_ARCHIVE
)
684 fib
->fib_Protection
|= FIBF_ARCHIVE
;
686 fib
->fib_Comment
[0] = 0;
688 CopyMem(gl
->shortname
, fib
->fib_Comment
, gl
->shortname
[0] + 1);