headers/bsd: Add sys/queue.h.
[haiku.git] / src / system / boot / loader / file_systems / fat / Directory.cpp
blob8aa1e9f44066d48fd4847be9c5c73ca59f0b8e02
1 /*
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.
5 */
8 #include "Directory.h"
10 #include <stdint.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <strings.h>
14 #include <unistd.h>
16 #include <new>
18 #include <StorageDefs.h>
20 #include "CachedBlock.h"
21 #include "File.h"
22 #include "Volume.h"
25 //#define TRACE(x) dprintf x
26 #define TRACE(x) do {} while (0)
29 using std::nothrow;
32 namespace FATFS {
35 struct dir_entry {
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);
44 bool IsFile() const;
45 bool IsDir() const;
47 char fName[8];
48 char fExt[3];
49 uint8 fFlags;
50 uint8 fReserved1;
51 uint8 fCreateTime10ms;
52 uint16 fCreateTime;
53 uint16 fCreateDate;
54 uint16 fAccessDate;
55 uint16 fClusterMSB;
56 uint16 fModifiedTime;
57 uint16 fModifiedDate;
58 uint16 fClusterLSB;
59 uint32 fSize;
60 } _PACKED;
63 uint32
64 dir_entry::Cluster(int32 fatBits) const
66 uint32 c = B_LENDIAN_TO_HOST_INT16(fClusterLSB);
67 if (fatBits == 32)
68 c += ((uint32)B_LENDIAN_TO_HOST_INT16(fClusterMSB) << 16);
69 return c;
73 void
74 dir_entry::SetCluster(uint32 cluster, int32 fatBits)
76 fClusterLSB = B_HOST_TO_LENDIAN_INT16((uint16)cluster);
77 if (fatBits == 32)
78 fClusterMSB = B_HOST_TO_LENDIAN_INT16(cluster >> 16);
82 void
83 dir_entry::SetSize(uint32 size)
85 fSize = B_HOST_TO_LENDIAN_INT32(size);
89 bool
90 dir_entry::IsFile() const
92 return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == 0);
96 bool
97 dir_entry::IsDir() const
99 return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_SUBDIR);
103 struct dir_cookie {
104 enum {
105 MAX_UTF16_NAME_LENGTH = 255
108 int32 index;
109 struct dir_entry entry;
110 off_t entryOffset;
111 uint16 nameBuffer[MAX_UTF16_NAME_LENGTH];
112 uint32 nameLength;
114 off_t Offset() const { return index * sizeof(struct dir_entry); }
115 char* Name() { return (char*)nameBuffer; }
117 void ResetName();
118 bool AddNameChars(const uint16* chars, uint32 count);
119 bool ConvertNameToUTF8();
120 void Set8_3Name(const char* baseName, const char* extension);
124 void
125 dir_cookie::ResetName()
127 nameLength = 0;
131 bool
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++) {
136 if (chars[i] == 0) {
137 count = i;
138 break;
142 if (count > 0) {
143 if (count > (MAX_UTF16_NAME_LENGTH - nameLength))
144 return false;
146 nameLength += count;
147 memcpy(nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength),
148 chars, count * 2);
151 return true;
155 bool
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++) {
164 uint8 utf8[4];
165 uint32 count;
166 uint16 c = B_LENDIAN_TO_HOST_INT16(utf16[i]);
167 if (c < 0x80) {
168 utf8[0] = c;
169 count = 1;
170 } else if (c < 0xff80) {
171 utf8[0] = 0xc0 | (c >> 6);
172 utf8[1] = 0x80 | (c & 0x3f);
173 count = 2;
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);
178 count = 3;
179 } else {
180 // surrogate pair
181 if (i + 1 >= nameLength)
182 return false;
184 uint16 c2 = B_LENDIAN_TO_HOST_INT16(utf16[++i]);
185 if ((c2 & 0xfc00) != 0xdc00)
186 return false;
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);
193 count = 4;
196 if (nameOffset + count >= sizeof(name))
197 return false;
199 memcpy(name + nameOffset, utf8, count);
200 nameOffset += count;
203 name[nameOffset] = '\0';
204 strlcpy(Name(), name, sizeof(nameBuffer));
205 return true;
209 void
210 dir_cookie::Set8_3Name(const char* baseName, const char* extension)
212 // trim base name
213 uint32 baseNameLength = 8;
214 while (baseNameLength > 0 && baseName[baseNameLength - 1] == ' ')
215 baseNameLength--;
217 // trim extension
218 uint32 extensionLength = 3;
219 while (extensionLength > 0 && extension[extensionLength - 1] == ' ')
220 extensionLength--;
222 // compose the name
223 char* name = Name();
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';
230 } else
231 name[baseNameLength] = '\0';
235 // #pragma mark -
238 static bool
239 is_valid_8_3_file_name_char(char c)
241 if ((uint8)c >= 128)
242 return true;
244 if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
245 return true;
247 return strchr("*!#$%&'()-@^_`{}~ ", c) != NULL;
251 static bool
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)
264 extension++;
265 else
266 extension = NULL;
267 } else {
268 baseNameLength = nameLength;
269 extensionLength = 0;
272 // trim trailing space
273 while (baseNameLength > 0 && name[baseNameLength - 1] == ' ')
274 baseNameLength--;
275 while (extensionLength > 0 && extension[extensionLength - 1] == ' ')
276 extensionLength--;
278 if (baseNameLength == 0 || baseNameLength > 8 || extensionLength > 3)
279 return false;
281 // check the chars
282 for (size_t i = 0; i < baseNameLength; i++) {
283 if (!is_valid_8_3_file_name_char(name[i]))
284 return false;
287 for (size_t i = 0; i < extensionLength; i++) {
288 if (!is_valid_8_3_file_name_char(extension[i]))
289 return false;
292 _baseName = name;
293 _baseNameLength = baseNameLength;
294 _extension = extension;
295 _extensionLength = extensionLength;
297 return true;
301 // #pragma mark - Directory
303 Directory::Directory(Volume &volume, off_t dirEntryOffset, uint32 cluster,
304 const char *name)
306 fVolume(volume),
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"));
320 status_t
321 Directory::InitCheck()
323 status_t err;
324 err = fStream.InitCheck();
325 if (err < B_OK)
326 return err;
327 return B_OK;
331 status_t
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;
338 if (c == NULL)
339 return B_NO_MEMORY;
341 c->index = -1;
342 c->entryOffset = 0;
344 *_cookie = (void *)c;
345 return B_OK;
349 status_t
350 Directory::Close(void *cookie)
352 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
353 _inherited::Close(cookie);
355 delete (struct dir_cookie *)cookie;
356 return B_OK;
360 Node*
361 Directory::LookupDontTraverse(const char* name)
363 TRACE(("FASFS::Directory::%s('%s')\n", __FUNCTION__, name));
364 if (!strcmp(name, ".")) {
365 Acquire();
366 return this;
369 status_t err;
370 struct dir_cookie cookie;
371 struct dir_cookie *c = &cookie;
372 c->index = -1;
373 c->entryOffset = 0;
375 do {
376 err = GetNextEntry(c);
377 if (err < B_OK)
378 return NULL;
379 TRACE(("FASFS::Directory::%s: %s <> '%s'\n", __FUNCTION__,
380 name, c->Name()));
381 if (strcasecmp(name, c->Name()) == 0) {
382 TRACE(("GOT IT!\n"));
383 break;
385 } while (true);
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()) {
393 TRACE(("IS DIR\n"));
394 return new Directory(fVolume, c->entryOffset,
395 c->entry.Cluster(fVolume.FatBits()), name);
397 return NULL;
401 status_t
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;
406 status_t err;
408 err = GetNextEntry(cookie);
409 if (err < B_OK)
410 return err;
412 strlcpy(name, c->Name(), size);
413 return B_OK;
417 status_t
418 Directory::GetNextNode(void *cookie, Node **_node)
420 return B_ERROR;
424 status_t
425 Directory::Rewind(void *cookie)
427 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
428 struct dir_cookie *c = (struct dir_cookie *)cookie;
429 c->index = -1;
430 c->entryOffset = 0;
432 return B_OK;
436 bool
437 Directory::IsEmpty()
439 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
440 struct dir_cookie cookie;
441 struct dir_cookie *c = &cookie;
442 c->index = -1;
443 c->entryOffset = 0;
444 if (GetNextEntry(c) == B_OK)
445 return false;
446 return true;
450 status_t
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);
460 ino_t
461 Directory::Inode() const
463 TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
464 return fStream.FirstCluster() << 16;
468 status_t
469 Directory::CreateFile(const char* name, mode_t permissions, Node** _node)
471 if (Node* node = Lookup(name, false)) {
472 node->Release();
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,
482 extensionLength)) {
483 return B_UNSUPPORTED;
486 // prepare a directory entry for the new file
487 dir_entry entry;
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);
496 entry.fFlags = 0;
497 entry.fReserved1 = 0;
498 entry.fCreateTime10ms = 199;
499 entry.fCreateTime = B_HOST_TO_LENDIAN_INT16((23 << 11) | (59 << 5) | 29);
500 // 23:59:59.9
501 entry.fCreateDate = B_HOST_TO_LENDIAN_INT16((127 << 9) | (12 << 5) | 31);
502 // 2107-12-31
503 entry.fAccessDate = entry.fCreateDate;
504 entry.fClusterMSB = 0;
505 entry.fModifiedTime = entry.fCreateTime;
506 entry.fModifiedDate = entry.fCreateDate;
507 entry.fClusterLSB = 0;
508 entry.fSize = 0;
510 // add the entry to the directory
511 off_t entryOffset;
512 status_t error = _AddEntry(entry, entryOffset);
513 if (error != B_OK)
514 return error;
516 // create a File object
517 File* file = new(nothrow) File(fVolume, entryOffset,
518 entry.Cluster(fVolume.FatBits()), entry.Size(), name);
519 if (file == NULL)
520 return B_NO_MEMORY;
522 *_node = file;
523 return B_OK;
527 /*static*/ status_t
528 Directory::UpdateDirEntry(Volume& volume, off_t dirEntryOffset,
529 uint32 firstCluster, uint32 size)
531 if (dirEntryOffset == 0)
532 return B_BAD_VALUE;
534 CachedBlock cachedBlock(volume);
535 off_t block = volume.ToBlock(dirEntryOffset);
537 status_t error = cachedBlock.SetTo(block, CachedBlock::READ);
538 if (error != B_OK)
539 return error;
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();
551 status_t
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;
560 do {
561 c->index++;
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
572 continue;
573 if (c->entry.Flags() == 0x0f) { // LFN entry
574 uint8* nameEntry = (uint8*)&c->entry;
575 if ((*nameEntry & 0x40) != 0) {
576 c->ResetName();
577 hasLongName = true;
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);
586 continue;
588 if ((c->entry.Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_VOLUME) {
589 // TODO handle Volume name (set fVolume's name)
590 continue;
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) {
595 if (longNameValid)
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());
601 break;
603 } while (true);
604 TRACE(("FATFS::Directory::%s: '%8.8s.%3.3s'\n", __FUNCTION__,
605 c->entry.BaseName(), c->entry.Extension()));
606 return B_OK;
610 status_t
611 Directory::_AddEntry(dir_entry& entry, off_t& _entryOffset)
613 off_t dirSize = _GetStreamSize();
614 if (dirSize < 0)
615 return dirSize;
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?
624 size_t size = 1;
625 char terminator = 0;
626 status_t error = fStream.WriteAt(dirSize + sizeof(entry), &terminator,
627 &size);
628 if (error != B_OK)
629 return error;
630 if (size != 1)
631 return B_ERROR;
634 // write the entry
635 size_t size = sizeof(entry);
636 status_t error = fStream.WriteAt(dirSize, &entry, &size, &_entryOffset);
637 if (error != B_OK)
638 return error;
639 if (size != sizeof(entry))
640 return B_ERROR;
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(),
650 if (error != B_OK)
651 return error;
652 // TODO: Undo changes!
655 return B_OK;
659 off_t
660 Directory::_GetStreamSize()
662 off_t size = fStream.Size();
663 if (size != UINT32_MAX)
664 return size;
666 // iterate to the end of the directory
667 size = 0;
668 while (true) {
669 dir_entry entry;
670 size_t entrySize = sizeof(entry);
671 status_t error = fStream.ReadAt(size, &entry, &entrySize);
672 if (error != B_OK)
673 return error;
675 if (entrySize != sizeof(entry) || entry.fName[0] == 0)
676 break;
678 size += sizeof(entry);
681 fStream.SetSize(size);
682 return size;
686 } // namespace FATFS