2 * Copyright 2003-2013, Axel Dörfler, axeld@pinc-software.de.
3 * Copyright 2008, François Revol <revol@free.fr>
4 * Distributed under the terms of the MIT License.
18 #include <StorageDefs.h>
20 #include "CachedBlock.h"
25 //#define TRACE(x) dprintf x
26 #define TRACE(x) do {} while (0)
36 void *Buffer() const { return (void *)fName
; };
37 const char *BaseName() const { return fName
; };
38 const char *Extension() const { return fExt
; };
39 uint8
Flags() const { return fFlags
; };
40 uint32
Cluster(int32 fatBits
) const;
41 void SetCluster(uint32 cluster
, int32 fatBits
);
42 uint32
Size() const { return B_LENDIAN_TO_HOST_INT32(fSize
); };
43 void SetSize(uint32 size
);
51 uint8 fCreateTime10ms
;
64 dir_entry::Cluster(int32 fatBits
) const
66 uint32 c
= B_LENDIAN_TO_HOST_INT16(fClusterLSB
);
68 c
+= ((uint32
)B_LENDIAN_TO_HOST_INT16(fClusterMSB
) << 16);
74 dir_entry::SetCluster(uint32 cluster
, int32 fatBits
)
76 fClusterLSB
= B_HOST_TO_LENDIAN_INT16((uint16
)cluster
);
78 fClusterMSB
= B_HOST_TO_LENDIAN_INT16(cluster
>> 16);
83 dir_entry::SetSize(uint32 size
)
85 fSize
= B_HOST_TO_LENDIAN_INT32(size
);
90 dir_entry::IsFile() const
92 return ((Flags() & (FAT_VOLUME
|FAT_SUBDIR
)) == 0);
97 dir_entry::IsDir() const
99 return ((Flags() & (FAT_VOLUME
|FAT_SUBDIR
)) == FAT_SUBDIR
);
105 MAX_UTF16_NAME_LENGTH
= 255
109 struct dir_entry entry
;
111 uint16 nameBuffer
[MAX_UTF16_NAME_LENGTH
];
114 off_t
Offset() const { return index
* sizeof(struct dir_entry
); }
115 char* Name() { return (char*)nameBuffer
; }
118 bool AddNameChars(const uint16
* chars
, uint32 count
);
119 bool ConvertNameToUTF8();
120 void Set8_3Name(const char* baseName
, const char* extension
);
125 dir_cookie::ResetName()
132 dir_cookie::AddNameChars(const uint16
* chars
, uint32 count
)
134 // If there is a null character, we ignore it and all subsequent characters.
135 for (uint32 i
= 0; i
< count
; i
++) {
143 if (count
> (MAX_UTF16_NAME_LENGTH
- nameLength
))
147 memcpy(nameBuffer
+ (MAX_UTF16_NAME_LENGTH
- nameLength
),
156 dir_cookie::ConvertNameToUTF8()
158 char name
[B_FILE_NAME_LENGTH
];
159 uint32 nameOffset
= 0;
161 const uint16
* utf16
= nameBuffer
+ (MAX_UTF16_NAME_LENGTH
- nameLength
);
163 for (uint32 i
= 0; i
< nameLength
; i
++) {
166 uint16 c
= B_LENDIAN_TO_HOST_INT16(utf16
[i
]);
170 } else if (c
< 0xff80) {
171 utf8
[0] = 0xc0 | (c
>> 6);
172 utf8
[1] = 0x80 | (c
& 0x3f);
174 } else if ((c
& 0xfc00) != 0xd800) {
175 utf8
[0] = 0xe0 | (c
>> 12);
176 utf8
[1] = 0x80 | ((c
>> 6) & 0x3f);
177 utf8
[2] = 0x80 | (c
& 0x3f);
181 if (i
+ 1 >= nameLength
)
184 uint16 c2
= B_LENDIAN_TO_HOST_INT16(utf16
[++i
]);
185 if ((c2
& 0xfc00) != 0xdc00)
188 uint32 value
= ((c
- 0xd7c0) << 10) | (c2
& 0x3ff);
189 utf8
[0] = 0xf0 | (value
>> 18);
190 utf8
[1] = 0x80 | ((value
>> 12) & 0x3f);
191 utf8
[2] = 0x80 | ((value
>> 6) & 0x3f);
192 utf8
[3] = 0x80 | (value
& 0x3f);
196 if (nameOffset
+ count
>= sizeof(name
))
199 memcpy(name
+ nameOffset
, utf8
, count
);
203 name
[nameOffset
] = '\0';
204 strlcpy(Name(), name
, sizeof(nameBuffer
));
210 dir_cookie::Set8_3Name(const char* baseName
, const char* extension
)
213 uint32 baseNameLength
= 8;
214 while (baseNameLength
> 0 && baseName
[baseNameLength
- 1] == ' ')
218 uint32 extensionLength
= 3;
219 while (extensionLength
> 0 && extension
[extensionLength
- 1] == ' ')
224 memcpy(name
, baseName
, baseNameLength
);
226 if (extensionLength
> 0) {
227 name
[baseNameLength
] = '.';
228 memcpy(name
+ baseNameLength
+ 1, extension
, extensionLength
);
229 name
[baseNameLength
+ 1 + extensionLength
] = '\0';
231 name
[baseNameLength
] = '\0';
239 is_valid_8_3_file_name_char(char c
)
244 if ((c
>= 'A' && c
<= 'Z') || (c
>= '0' && c
<= '9'))
247 return strchr("*!#$%&'()-@^_`{}~ ", c
) != NULL
;
252 check_valid_8_3_file_name(const char* name
, const char*& _baseName
,
253 uint32
& _baseNameLength
, const char*& _extension
, uint32
& _extensionLength
)
255 // check length of base name and extension
256 size_t nameLength
= strlen(name
);
257 const char* extension
= strchr(name
, '.');
258 size_t baseNameLength
;
259 size_t extensionLength
;
260 if (extension
!= NULL
) {
261 baseNameLength
= extension
- name
;
262 extensionLength
= nameLength
- baseNameLength
- 1;
263 if (extensionLength
> 0)
268 baseNameLength
= nameLength
;
272 // trim trailing space
273 while (baseNameLength
> 0 && name
[baseNameLength
- 1] == ' ')
275 while (extensionLength
> 0 && extension
[extensionLength
- 1] == ' ')
278 if (baseNameLength
== 0 || baseNameLength
> 8 || extensionLength
> 3)
282 for (size_t i
= 0; i
< baseNameLength
; i
++) {
283 if (!is_valid_8_3_file_name_char(name
[i
]))
287 for (size_t i
= 0; i
< extensionLength
; i
++) {
288 if (!is_valid_8_3_file_name_char(extension
[i
]))
293 _baseNameLength
= baseNameLength
;
294 _extension
= extension
;
295 _extensionLength
= extensionLength
;
301 // #pragma mark - Directory
303 Directory::Directory(Volume
&volume
, off_t dirEntryOffset
, uint32 cluster
,
307 fStream(volume
, cluster
, UINT32_MAX
, name
),
308 fDirEntryOffset(dirEntryOffset
)
310 TRACE(("FASFS::Directory::(, %lu, %s)\n", cluster
, name
));
314 Directory::~Directory()
316 TRACE(("FASFS::Directory::~()\n"));
321 Directory::InitCheck()
324 err
= fStream
.InitCheck();
332 Directory::Open(void **_cookie
, int mode
)
334 TRACE(("FASFS::Directory::%s(, %d)\n", __FUNCTION__
, mode
));
335 _inherited::Open(_cookie
, mode
);
337 dir_cookie
*c
= new(nothrow
) dir_cookie
;
344 *_cookie
= (void *)c
;
350 Directory::Close(void *cookie
)
352 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__
));
353 _inherited::Close(cookie
);
355 delete (struct dir_cookie
*)cookie
;
361 Directory::LookupDontTraverse(const char* name
)
363 TRACE(("FASFS::Directory::%s('%s')\n", __FUNCTION__
, name
));
364 if (!strcmp(name
, ".")) {
370 struct dir_cookie cookie
;
371 struct dir_cookie
*c
= &cookie
;
376 err
= GetNextEntry(c
);
379 TRACE(("FASFS::Directory::%s: %s <> '%s'\n", __FUNCTION__
,
381 if (strcasecmp(name
, c
->Name()) == 0) {
382 TRACE(("GOT IT!\n"));
387 if (c
->entry
.IsFile()) {
388 TRACE(("IS FILE\n"));
389 return new File(fVolume
, c
->entryOffset
,
390 c
->entry
.Cluster(fVolume
.FatBits()), c
->entry
.Size(), name
);
392 if (c
->entry
.IsDir()) {
394 return new Directory(fVolume
, c
->entryOffset
,
395 c
->entry
.Cluster(fVolume
.FatBits()), name
);
402 Directory::GetNextEntry(void *cookie
, char *name
, size_t size
)
404 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__
));
405 struct dir_cookie
*c
= (struct dir_cookie
*)cookie
;
408 err
= GetNextEntry(cookie
);
412 strlcpy(name
, c
->Name(), size
);
418 Directory::GetNextNode(void *cookie
, Node
**_node
)
425 Directory::Rewind(void *cookie
)
427 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__
));
428 struct dir_cookie
*c
= (struct dir_cookie
*)cookie
;
439 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__
));
440 struct dir_cookie cookie
;
441 struct dir_cookie
*c
= &cookie
;
444 if (GetNextEntry(c
) == B_OK
)
451 Directory::GetName(char *name
, size_t size
) const
453 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__
));
454 if (this == fVolume
.Root())
455 return fVolume
.GetName(name
, size
);
456 return fStream
.GetName(name
, size
);
461 Directory::Inode() const
463 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__
));
464 return fStream
.FirstCluster() << 16;
469 Directory::CreateFile(const char* name
, mode_t permissions
, Node
** _node
)
471 if (Node
* node
= Lookup(name
, false)) {
473 return B_FILE_EXISTS
;
476 // We only support 8.3 file names ATM.
477 const char* baseName
;
478 const char* extension
;
479 uint32 baseNameLength
;
480 uint32 extensionLength
;
481 if (!check_valid_8_3_file_name(name
, baseName
, baseNameLength
, extension
,
483 return B_UNSUPPORTED
;
486 // prepare a directory entry for the new file
489 memset(entry
.fName
, ' ', sizeof(entry
.fName
));
490 memset(entry
.fExt
, ' ', sizeof(entry
.fExt
));
491 // clear both base name and extension
492 memcpy(entry
.fName
, baseName
, baseNameLength
);
493 if (extensionLength
> 0)
494 memcpy(entry
.fExt
, extension
, extensionLength
);
497 entry
.fReserved1
= 0;
498 entry
.fCreateTime10ms
= 199;
499 entry
.fCreateTime
= B_HOST_TO_LENDIAN_INT16((23 << 11) | (59 << 5) | 29);
501 entry
.fCreateDate
= B_HOST_TO_LENDIAN_INT16((127 << 9) | (12 << 5) | 31);
503 entry
.fAccessDate
= entry
.fCreateDate
;
504 entry
.fClusterMSB
= 0;
505 entry
.fModifiedTime
= entry
.fCreateTime
;
506 entry
.fModifiedDate
= entry
.fCreateDate
;
507 entry
.fClusterLSB
= 0;
510 // add the entry to the directory
512 status_t error
= _AddEntry(entry
, entryOffset
);
516 // create a File object
517 File
* file
= new(nothrow
) File(fVolume
, entryOffset
,
518 entry
.Cluster(fVolume
.FatBits()), entry
.Size(), name
);
528 Directory::UpdateDirEntry(Volume
& volume
, off_t dirEntryOffset
,
529 uint32 firstCluster
, uint32 size
)
531 if (dirEntryOffset
== 0)
534 CachedBlock
cachedBlock(volume
);
535 off_t block
= volume
.ToBlock(dirEntryOffset
);
537 status_t error
= cachedBlock
.SetTo(block
, CachedBlock::READ
);
541 dir_entry
* entry
= (dir_entry
*)(cachedBlock
.Block()
542 + dirEntryOffset
% volume
.BlockSize());
544 entry
->SetCluster(firstCluster
, volume
.FatBits());
545 entry
->SetSize(size
);
547 return cachedBlock
.Flush();
552 Directory::GetNextEntry(void *cookie
, uint8 mask
, uint8 match
)
554 TRACE(("FASFS::Directory::%s(, %02x, %02x)\n", __FUNCTION__
, mask
, match
));
555 struct dir_cookie
*c
= (struct dir_cookie
*)cookie
;
557 bool hasLongName
= false;
558 bool longNameValid
= false;
562 size_t len
= sizeof(c
->entry
);
563 if (fStream
.ReadAt(c
->Offset(), (uint8
*)&c
->entry
, &len
,
564 &c
->entryOffset
) != B_OK
|| len
!= sizeof(c
->entry
)) {
565 return B_ENTRY_NOT_FOUND
;
568 TRACE(("FASFS::Directory::%s: got one entry\n", __FUNCTION__
));
569 if ((uint8
)c
->entry
.fName
[0] == 0x00) // last one
570 return B_ENTRY_NOT_FOUND
;
571 if ((uint8
)c
->entry
.fName
[0] == 0xe5) // deleted
573 if (c
->entry
.Flags() == 0x0f) { // LFN entry
574 uint8
* nameEntry
= (uint8
*)&c
->entry
;
575 if ((*nameEntry
& 0x40) != 0) {
578 longNameValid
= true;
581 uint16 nameChars
[13];
582 memcpy(nameChars
, nameEntry
+ 0x01, 10);
583 memcpy(nameChars
+ 5, nameEntry
+ 0x0e, 12);
584 memcpy(nameChars
+ 11, nameEntry
+ 0x1c, 4);
585 longNameValid
|= c
->AddNameChars(nameChars
, 13);
588 if ((c
->entry
.Flags() & (FAT_VOLUME
|FAT_SUBDIR
)) == FAT_VOLUME
) {
589 // TODO handle Volume name (set fVolume's name)
592 TRACE(("FASFS::Directory::%s: checking '%8.8s.%3.3s', %02x\n", __FUNCTION__
,
593 c
->entry
.BaseName(), c
->entry
.Extension(), c
->entry
.Flags()));
594 if ((c
->entry
.Flags() & mask
) == match
) {
596 longNameValid
= c
->ConvertNameToUTF8();
597 if (!longNameValid
) {
598 // copy 8.3 name to name buffer
599 c
->Set8_3Name(c
->entry
.BaseName(), c
->entry
.Extension());
604 TRACE(("FATFS::Directory::%s: '%8.8s.%3.3s'\n", __FUNCTION__
,
605 c
->entry
.BaseName(), c
->entry
.Extension()));
611 Directory::_AddEntry(dir_entry
& entry
, off_t
& _entryOffset
)
613 off_t dirSize
= _GetStreamSize();
617 uint32 firstCluster
= fStream
.FirstCluster();
619 // First null-terminate the new entry list, so we don't leave the list in
620 // a broken state, if writing the actual entry fails. We only need to do
621 // that when the entry is not at the end of a cluster.
622 if ((dirSize
+ sizeof(entry
)) % fVolume
.ClusterSize() != 0) {
623 // TODO: Rather zero the complete remainder of the cluster?
626 status_t error
= fStream
.WriteAt(dirSize
+ sizeof(entry
), &terminator
,
635 size_t size
= sizeof(entry
);
636 status_t error
= fStream
.WriteAt(dirSize
, &entry
, &size
, &_entryOffset
);
639 if (size
!= sizeof(entry
))
641 // TODO: Undo changes!
643 fStream
.SetSize(dirSize
+ sizeof(entry
));
645 // If the directory cluster has changed (which should only happen, if the
646 // directory was empty before), we need to adjust the directory entry.
647 if (firstCluster
!= fStream
.FirstCluster()) {
648 error
= UpdateDirEntry(fVolume
, fDirEntryOffset
, fStream
.FirstCluster(),
652 // TODO: Undo changes!
660 Directory::_GetStreamSize()
662 off_t size
= fStream
.Size();
663 if (size
!= UINT32_MAX
)
666 // iterate to the end of the directory
670 size_t entrySize
= sizeof(entry
);
671 status_t error
= fStream
.ReadAt(size
, &entry
, &entrySize
);
675 if (entrySize
!= sizeof(entry
) || entry
.fName
[0] == 0)
678 size
+= sizeof(entry
);
681 fStream
.SetSize(size
);