xref: /haiku/src/system/boot/loader/file_systems/fat/Directory.cpp (revision 21b533d448349b97e97efd71712a653ae99bc4e1)
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  */
6 
7 
8 #include "Directory.h"
9 
10 #include <stdint.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <strings.h>
14 #include <unistd.h>
15 
16 #include <new>
17 
18 #include <StorageDefs.h>
19 
20 #include "CachedBlock.h"
21 #include "File.h"
22 #include "Volume.h"
23 
24 
25 //#define TRACE(x) dprintf x
26 #define TRACE(x) do {} while (0)
27 
28 
29 using std::nothrow;
30 
31 
32 namespace FATFS {
33 
34 
35 struct dir_entry {
BufferFATFS::dir_entry36 	void *Buffer() const { return (void *)fName; };
BaseNameFATFS::dir_entry37 	const char *BaseName() const { return fName; };
ExtensionFATFS::dir_entry38 	const char *Extension() const { return fExt; };
FlagsFATFS::dir_entry39 	uint8		Flags() const { return fFlags; };
40 	uint32		Cluster(int32 fatBits) const;
41 	void		SetCluster(uint32 cluster, int32 fatBits);
SizeFATFS::dir_entry42 	uint32		Size() const { return B_LENDIAN_TO_HOST_INT32(fSize); };
43 	void		SetSize(uint32 size);
44 	bool		IsFile() const;
45 	bool		IsDir() const;
46 
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;
61 
62 
63 uint32
Cluster(int32 fatBits) const64 dir_entry::Cluster(int32 fatBits) const
65 {
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;
70 }
71 
72 
73 void
SetCluster(uint32 cluster,int32 fatBits)74 dir_entry::SetCluster(uint32 cluster, int32 fatBits)
75 {
76 	fClusterLSB = B_HOST_TO_LENDIAN_INT16((uint16)cluster);
77 	if (fatBits == 32)
78 		fClusterMSB = B_HOST_TO_LENDIAN_INT16(cluster >> 16);
79 }
80 
81 
82 void
SetSize(uint32 size)83 dir_entry::SetSize(uint32 size)
84 {
85 	fSize = B_HOST_TO_LENDIAN_INT32(size);
86 }
87 
88 
89 bool
IsFile() const90 dir_entry::IsFile() const
91 {
92 	return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == 0);
93 }
94 
95 
96 bool
IsDir() const97 dir_entry::IsDir() const
98 {
99 	return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_SUBDIR);
100 }
101 
102 
103 struct dir_cookie {
104 	enum {
105 		MAX_UTF16_NAME_LENGTH = 255
106 	};
107 
108 	int32	index;
109 	struct dir_entry entry;
110 	off_t	entryOffset;
111 	uint16	nameBuffer[MAX_UTF16_NAME_LENGTH];
112 	uint32	nameLength;
113 
OffsetFATFS::dir_cookie114 	off_t	Offset() const { return index * sizeof(struct dir_entry); }
NameFATFS::dir_cookie115 	char*	Name()	{ return (char*)nameBuffer; }
116 
117 	void	ResetName();
118 	bool	AddNameChars(const uint16* chars, uint32 count);
119 	bool	ConvertNameToUTF8();
120 	void	Set8_3Name(const char* baseName, const char* extension);
121 };
122 
123 
124 void
ResetName()125 dir_cookie::ResetName()
126 {
127 	nameLength = 0;
128 }
129 
130 
131 bool
AddNameChars(const uint16 * chars,uint32 count)132 dir_cookie::AddNameChars(const uint16* chars, uint32 count)
133 {
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;
139 		}
140 	}
141 
142 	if (count > 0) {
143 		if (count > (MAX_UTF16_NAME_LENGTH - nameLength))
144 			return false;
145 
146 		nameLength += count;
147 		memcpy(nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength),
148 			chars, count * 2);
149 	}
150 
151 	return true;
152 }
153 
154 
155 bool
ConvertNameToUTF8()156 dir_cookie::ConvertNameToUTF8()
157 {
158 	char name[B_FILE_NAME_LENGTH];
159 	uint32 nameOffset = 0;
160 
161 	const uint16* utf16 = nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength);
162 
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;
183 
184 			uint16 c2 = B_LENDIAN_TO_HOST_INT16(utf16[++i]);
185 			if ((c2 & 0xfc00) != 0xdc00)
186 				return false;
187 
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;
194 		}
195 
196 		if (nameOffset + count >= sizeof(name))
197 			return false;
198 
199 		memcpy(name + nameOffset, utf8, count);
200 		nameOffset += count;
201 	}
202 
203 	name[nameOffset] = '\0';
204 	strlcpy(Name(), name, sizeof(nameBuffer));
205 	return true;
206 }
207 
208 
209 void
Set8_3Name(const char * baseName,const char * extension)210 dir_cookie::Set8_3Name(const char* baseName, const char* extension)
211 {
212 	// trim base name
213 	uint32 baseNameLength = 8;
214 	while (baseNameLength > 0 && baseName[baseNameLength - 1] == ' ')
215 		baseNameLength--;
216 
217 	// trim extension
218 	uint32 extensionLength = 3;
219 	while (extensionLength > 0 && extension[extensionLength - 1] == ' ')
220 		extensionLength--;
221 
222 	// compose the name
223 	char* name = Name();
224 	memcpy(name, baseName, baseNameLength);
225 
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';
232 }
233 
234 
235 // #pragma mark -
236 
237 
238 static bool
is_valid_8_3_file_name_char(char c)239 is_valid_8_3_file_name_char(char c)
240 {
241 	if ((uint8)c >= 128)
242 		return true;
243 
244 	if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
245 		return true;
246 
247 	return strchr("*!#$%&'()-@^_`{}~ ", c) != NULL;
248 }
249 
250 
251 static bool
check_valid_8_3_file_name(const char * name,const char * & _baseName,uint32 & _baseNameLength,const char * & _extension,uint32 & _extensionLength)252 check_valid_8_3_file_name(const char* name, const char*& _baseName,
253 	uint32& _baseNameLength, const char*& _extension, uint32& _extensionLength)
254 {
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;
270 	}
271 
272 	// trim trailing space
273 	while (baseNameLength > 0 && name[baseNameLength - 1] == ' ')
274 		baseNameLength--;
275 	while (extensionLength > 0 && extension[extensionLength - 1] == ' ')
276 		extensionLength--;
277 
278 	if (baseNameLength == 0 || baseNameLength > 8 || extensionLength > 3)
279 		return false;
280 
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;
285 	}
286 
287 	for (size_t i = 0; i < extensionLength; i++) {
288 		if (!is_valid_8_3_file_name_char(extension[i]))
289 			return false;
290 	}
291 
292 	_baseName = name;
293 	_baseNameLength = baseNameLength;
294 	_extension = extension;
295 	_extensionLength = extensionLength;
296 
297 	return true;
298 }
299 
300 
301 // #pragma mark - Directory
302 
Directory(Volume & volume,off_t dirEntryOffset,uint32 cluster,const char * name)303 Directory::Directory(Volume &volume, off_t dirEntryOffset, uint32 cluster,
304 	const char *name)
305 	:
306 	fVolume(volume),
307 	fStream(volume, cluster, UINT32_MAX, name),
308 	fDirEntryOffset(dirEntryOffset)
309 {
310 	TRACE(("FASFS::Directory::(, %lu, %s)\n", cluster, name));
311 }
312 
313 
~Directory()314 Directory::~Directory()
315 {
316 	TRACE(("FASFS::Directory::~()\n"));
317 }
318 
319 
320 status_t
InitCheck()321 Directory::InitCheck()
322 {
323 	status_t err;
324 	err = fStream.InitCheck();
325 	if (err < B_OK)
326 		return err;
327 	return B_OK;
328 }
329 
330 
331 status_t
Open(void ** _cookie,int mode)332 Directory::Open(void **_cookie, int mode)
333 {
334 	TRACE(("FASFS::Directory::%s(, %d)\n", __FUNCTION__, mode));
335 	_inherited::Open(_cookie, mode);
336 
337 	dir_cookie *c = new(nothrow) dir_cookie;
338 	if (c == NULL)
339 		return B_NO_MEMORY;
340 
341 	c->index = -1;
342 	c->entryOffset = 0;
343 
344 	*_cookie = (void *)c;
345 	return B_OK;
346 }
347 
348 
349 status_t
Close(void * cookie)350 Directory::Close(void *cookie)
351 {
352 	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
353 	_inherited::Close(cookie);
354 
355 	delete (struct dir_cookie *)cookie;
356 	return B_OK;
357 }
358 
359 
360 Node*
LookupDontTraverse(const char * name)361 Directory::LookupDontTraverse(const char* name)
362 {
363 	TRACE(("FASFS::Directory::%s('%s')\n", __FUNCTION__, name));
364 	if (!strcmp(name, ".")) {
365 		Acquire();
366 		return this;
367 	}
368 
369 	status_t err;
370 	struct dir_cookie cookie;
371 	struct dir_cookie *c = &cookie;
372 	c->index = -1;
373 	c->entryOffset = 0;
374 
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;
384 		}
385 	} while (true);
386 
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);
391 	}
392 	if (c->entry.IsDir()) {
393 		TRACE(("IS DIR\n"));
394 		return new Directory(fVolume, c->entryOffset,
395 			c->entry.Cluster(fVolume.FatBits()), name);
396 	}
397 	return NULL;
398 }
399 
400 
401 status_t
GetNextEntry(void * cookie,char * name,size_t size)402 Directory::GetNextEntry(void *cookie, char *name, size_t size)
403 {
404 	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
405 	struct dir_cookie *c = (struct dir_cookie *)cookie;
406 	status_t err;
407 
408 	err = GetNextEntry(cookie);
409 	if (err < B_OK)
410 		return err;
411 
412 	strlcpy(name, c->Name(), size);
413 	return B_OK;
414 }
415 
416 
417 status_t
GetNextNode(void * cookie,Node ** _node)418 Directory::GetNextNode(void *cookie, Node **_node)
419 {
420 	return B_ERROR;
421 }
422 
423 
424 status_t
Rewind(void * cookie)425 Directory::Rewind(void *cookie)
426 {
427 	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
428 	struct dir_cookie *c = (struct dir_cookie *)cookie;
429 	c->index = -1;
430 	c->entryOffset = 0;
431 
432 	return B_OK;
433 }
434 
435 
436 bool
IsEmpty()437 Directory::IsEmpty()
438 {
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;
447 }
448 
449 
450 status_t
GetName(char * name,size_t size) const451 Directory::GetName(char *name, size_t size) const
452 {
453 	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
454 	if (this == fVolume.Root())
455 		return fVolume.GetName(name, size);
456 	return fStream.GetName(name, size);
457 }
458 
459 
460 ino_t
Inode() const461 Directory::Inode() const
462 {
463 	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
464 	return fStream.FirstCluster() << 16;
465 }
466 
467 
468 status_t
CreateFile(const char * name,mode_t permissions,Node ** _node)469 Directory::CreateFile(const char* name, mode_t permissions, Node** _node)
470 {
471 	if (Node* node = Lookup(name, false)) {
472 		node->Release();
473 		return B_FILE_EXISTS;
474 	}
475 
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;
484 	}
485 
486 	// prepare a directory entry for the new file
487 	dir_entry entry;
488 
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);
495 
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;
509 
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;
515 
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;
521 
522 	*_node = file;
523 	return B_OK;
524 }
525 
526 
527 /*static*/ status_t
UpdateDirEntry(Volume & volume,off_t dirEntryOffset,uint32 firstCluster,uint32 size)528 Directory::UpdateDirEntry(Volume& volume, off_t dirEntryOffset,
529 	uint32 firstCluster, uint32 size)
530 {
531 	if (dirEntryOffset == 0)
532 		return B_BAD_VALUE;
533 
534 	CachedBlock cachedBlock(volume);
535 	off_t block = volume.ToBlock(dirEntryOffset);
536 
537 	status_t error = cachedBlock.SetTo(block, CachedBlock::READ);
538 	if (error != B_OK)
539 		return error;
540 
541 	dir_entry* entry = (dir_entry*)(cachedBlock.Block()
542 		+ dirEntryOffset % volume.BlockSize());
543 
544 	entry->SetCluster(firstCluster, volume.FatBits());
545 	entry->SetSize(size);
546 
547 	return cachedBlock.Flush();
548 }
549 
550 
551 status_t
GetNextEntry(void * cookie,uint8 mask,uint8 match)552 Directory::GetNextEntry(void *cookie, uint8 mask, uint8 match)
553 {
554 	TRACE(("FASFS::Directory::%s(, %02x, %02x)\n", __FUNCTION__, mask, match));
555 	struct dir_cookie *c = (struct dir_cookie *)cookie;
556 
557 	bool longNameValid = false;
558 
559 	do {
560 		c->index++;
561 		size_t len = sizeof(c->entry);
562 		if (fStream.ReadAt(c->Offset(), (uint8 *)&c->entry, &len,
563 				&c->entryOffset) != B_OK || len != sizeof(c->entry)) {
564 			return B_ENTRY_NOT_FOUND;
565 		}
566 
567 		TRACE(("FASFS::Directory::%s: got one entry\n", __FUNCTION__));
568 		if ((uint8)c->entry.fName[0] == 0x00) // last one
569 			return B_ENTRY_NOT_FOUND;
570 		if ((uint8)c->entry.fName[0] == 0xe5) // deleted
571 			continue;
572 		if (c->entry.Flags() == 0x0f) { // LFN entry
573 			uint8* nameEntry = (uint8*)&c->entry;
574 			if ((*nameEntry & 0x40) != 0) {
575 				c->ResetName();
576 				longNameValid = true;
577 			}
578 
579 			uint16 nameChars[13];
580 			memcpy(nameChars, nameEntry + 0x01, 10);
581 			memcpy(nameChars + 5, nameEntry + 0x0e, 12);
582 			memcpy(nameChars + 11, nameEntry + 0x1c, 4);
583 			longNameValid |= c->AddNameChars(nameChars, 13);
584 			continue;
585 		}
586 		if ((c->entry.Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_VOLUME) {
587 			// TODO handle Volume name (set fVolume's name)
588 			continue;
589 		}
590 		TRACE(("FASFS::Directory::%s: checking '%8.8s.%3.3s', %02x\n", __FUNCTION__,
591 			c->entry.BaseName(), c->entry.Extension(), c->entry.Flags()));
592 		if ((c->entry.Flags() & mask) == match) {
593 			if (longNameValid)
594 				longNameValid = c->ConvertNameToUTF8();
595 			if (!longNameValid) {
596 				// copy 8.3 name to name buffer
597 				c->Set8_3Name(c->entry.BaseName(), c->entry.Extension());
598 			}
599 			break;
600 		}
601 	} while (true);
602 	TRACE(("FATFS::Directory::%s: '%8.8s.%3.3s'\n", __FUNCTION__,
603 		c->entry.BaseName(), c->entry.Extension()));
604 	return B_OK;
605 }
606 
607 
608 status_t
_AddEntry(dir_entry & entry,off_t & _entryOffset)609 Directory::_AddEntry(dir_entry& entry, off_t& _entryOffset)
610 {
611 	off_t dirSize = _GetStreamSize();
612 	if (dirSize < 0)
613 		return dirSize;
614 
615 	uint32 firstCluster = fStream.FirstCluster();
616 
617 	// First null-terminate the new entry list, so we don't leave the list in
618 	// a broken state, if writing the actual entry fails. We only need to do
619 	// that when the entry is not at the end of a cluster.
620 	if ((dirSize + sizeof(entry)) % fVolume.ClusterSize() != 0) {
621 		// TODO: Rather zero the complete remainder of the cluster?
622 		size_t size = 1;
623 		char terminator = 0;
624 		status_t error = fStream.WriteAt(dirSize + sizeof(entry), &terminator,
625 			&size);
626 		if (error != B_OK)
627 			return error;
628 		if (size != 1)
629 			return B_ERROR;
630 	}
631 
632 	// write the entry
633 	size_t size = sizeof(entry);
634 	status_t error = fStream.WriteAt(dirSize, &entry, &size, &_entryOffset);
635 	if (error != B_OK)
636 		return error;
637 	if (size != sizeof(entry))
638 		return B_ERROR;
639 		// TODO: Undo changes!
640 
641 	fStream.SetSize(dirSize + sizeof(entry));
642 
643 	// If the directory cluster has changed (which should only happen, if the
644 	// directory was empty before), we need to adjust the directory entry.
645 	if (firstCluster != fStream.FirstCluster()) {
646 		error = UpdateDirEntry(fVolume, fDirEntryOffset, fStream.FirstCluster(),
647 			0);
648 		if (error != B_OK)
649 			return error;
650 			// TODO: Undo changes!
651 	}
652 
653 	return B_OK;
654 }
655 
656 
657 off_t
_GetStreamSize()658 Directory::_GetStreamSize()
659 {
660 	off_t size = fStream.Size();
661 	if (size != UINT32_MAX)
662 		return size;
663 
664 	// iterate to the end of the directory
665 	size = 0;
666 	while (true) {
667 		dir_entry entry;
668 		size_t entrySize = sizeof(entry);
669 		status_t error = fStream.ReadAt(size, &entry, &entrySize);
670 		if (error != B_OK)
671 			return error;
672 
673 		if (entrySize != sizeof(entry) || entry.fName[0] == 0)
674 			break;
675 
676 		size += sizeof(entry);
677 	}
678 
679 	fStream.SetSize(size);
680 	return size;
681 }
682 
683 
684 }	// namespace FATFS
685