xref: /haiku/src/system/boot/loader/file_systems/fat/Directory.cpp (revision 81ec973846ea4816c53ed8901822e43c8b06878d)
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 namespace FATFS {
30 
31 
32 struct dir_entry {
33 	void *Buffer() const { return (void *)fName; };
34 	const char *BaseName() const { return fName; };
35 	const char *Extension() const { return fExt; };
36 	uint8		Flags() const { return fFlags; };
37 	uint32		Cluster(int32 fatBits) const;
38 	void		SetCluster(uint32 cluster, int32 fatBits);
39 	uint32		Size() const { return B_LENDIAN_TO_HOST_INT32(fSize); };
40 	void		SetSize(uint32 size);
41 	bool		IsFile() const;
42 	bool		IsDir() const;
43 
44 	char fName[8];
45 	char fExt[3];
46 	uint8 fFlags;
47 	uint8 fReserved1;
48 	uint8 fCreateTime10ms;
49 	uint16 fCreateTime;
50 	uint16 fCreateDate;
51 	uint16 fAccessDate;
52 	uint16 fClusterMSB;
53 	uint16 fModifiedTime;
54 	uint16 fModifiedDate;
55 	uint16 fClusterLSB;
56 	uint32 fSize;
57 } _PACKED;
58 
59 
60 uint32
61 dir_entry::Cluster(int32 fatBits) const
62 {
63 	uint32 c = B_LENDIAN_TO_HOST_INT16(fClusterLSB);
64 	if (fatBits == 32)
65 		c += ((uint32)B_LENDIAN_TO_HOST_INT16(fClusterMSB) << 16);
66 	return c;
67 }
68 
69 
70 void
71 dir_entry::SetCluster(uint32 cluster, int32 fatBits)
72 {
73 	fClusterLSB = B_HOST_TO_LENDIAN_INT16((uint16)cluster);
74 	if (fatBits == 32)
75 		fClusterMSB = B_HOST_TO_LENDIAN_INT16(cluster >> 16);
76 }
77 
78 
79 void
80 dir_entry::SetSize(uint32 size)
81 {
82 	fSize = B_HOST_TO_LENDIAN_INT32(size);
83 }
84 
85 
86 bool
87 dir_entry::IsFile() const
88 {
89 	return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == 0);
90 }
91 
92 
93 bool
94 dir_entry::IsDir() const
95 {
96 	return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_SUBDIR);
97 }
98 
99 
100 struct dir_cookie {
101 	enum {
102 		MAX_UTF16_NAME_LENGTH = 255
103 	};
104 
105 	int32	index;
106 	struct dir_entry entry;
107 	off_t	entryOffset;
108 	uint16	nameBuffer[MAX_UTF16_NAME_LENGTH];
109 	uint32	nameLength;
110 
111 	off_t	Offset() const { return index * sizeof(struct dir_entry); }
112 	char*	Name()	{ return (char*)nameBuffer; }
113 
114 	void	ResetName();
115 	bool	AddNameChars(const uint16* chars, uint32 count);
116 	bool	ConvertNameToUTF8();
117 	void	Set8_3Name(const char* baseName, const char* extension);
118 };
119 
120 
121 void
122 dir_cookie::ResetName()
123 {
124 	nameLength = 0;
125 }
126 
127 
128 bool
129 dir_cookie::AddNameChars(const uint16* chars, uint32 count)
130 {
131 	// If there is a null character, we ignore it and all subsequent characters.
132 	for (uint32 i = 0; i < count; i++) {
133 		if (chars[i] == 0) {
134 			count = i;
135 			break;
136 		}
137 	}
138 
139 	if (count > 0) {
140 		if (count > (MAX_UTF16_NAME_LENGTH - nameLength))
141 			return false;
142 
143 		nameLength += count;
144 		memcpy(nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength),
145 			chars, count * 2);
146 	}
147 
148 	return true;
149 }
150 
151 
152 bool
153 dir_cookie::ConvertNameToUTF8()
154 {
155 	char name[B_FILE_NAME_LENGTH];
156 	uint32 nameOffset = 0;
157 
158 	const uint16* utf16 = nameBuffer + (MAX_UTF16_NAME_LENGTH - nameLength);
159 
160 	for (uint32 i = 0; i < nameLength; i++) {
161 		uint8 utf8[4];
162 		uint32 count;
163 		uint16 c = B_LENDIAN_TO_HOST_INT16(utf16[i]);
164 		if (c < 0x80) {
165 			utf8[0] = c;
166 			count = 1;
167 		} else if (c < 0xff80) {
168 			utf8[0] = 0xc0 | (c >> 6);
169 			utf8[1] = 0x80 | (c & 0x3f);
170 			count = 2;
171 		} else if ((c & 0xfc00) != 0xd800) {
172 			utf8[0] = 0xe0 | (c >> 12);
173 			utf8[1] = 0x80 | ((c >> 6) & 0x3f);
174 			utf8[2] = 0x80 | (c & 0x3f);
175 			count = 3;
176 		} else {
177 			// surrogate pair
178 			if (i + 1 >= nameLength)
179 				return false;
180 
181 			uint16 c2 = B_LENDIAN_TO_HOST_INT16(utf16[++i]);
182 			if ((c2 & 0xfc00) != 0xdc00)
183 				return false;
184 
185 			uint32 value = ((c - 0xd7c0) << 10) | (c2 & 0x3ff);
186 			utf8[0] = 0xf0 | (value >> 18);
187 			utf8[1] = 0x80 | ((value >> 12) & 0x3f);
188 			utf8[2] = 0x80 | ((value >> 6) & 0x3f);
189 			utf8[3] = 0x80 | (value & 0x3f);
190 			count = 4;
191 		}
192 
193 		if (nameOffset + count >= sizeof(name))
194 			return false;
195 
196 		memcpy(name + nameOffset, utf8, count);
197 		nameOffset += count;
198 	}
199 
200 	name[nameOffset] = '\0';
201 	strlcpy(Name(), name, sizeof(nameBuffer));
202 	return true;
203 }
204 
205 
206 void
207 dir_cookie::Set8_3Name(const char* baseName, const char* extension)
208 {
209 	// trim base name
210 	uint32 baseNameLength = 8;
211 	while (baseNameLength > 0 && baseName[baseNameLength - 1] == ' ')
212 		baseNameLength--;
213 
214 	// trim extension
215 	uint32 extensionLength = 3;
216 	while (extensionLength > 0 && extension[extensionLength - 1] == ' ')
217 		extensionLength--;
218 
219 	// compose the name
220 	char* name = Name();
221 	memcpy(name, baseName, baseNameLength);
222 
223 	if (extensionLength > 0) {
224 		name[baseNameLength] = '.';
225 		memcpy(name + baseNameLength + 1, extension, extensionLength);
226 		name[baseNameLength + 1 + extensionLength] = '\0';
227 	} else
228 		name[baseNameLength] = '\0';
229 }
230 
231 
232 // #pragma mark -
233 
234 
235 static bool
236 is_valid_8_3_file_name_char(char c)
237 {
238 	if ((uint8)c >= 128)
239 		return true;
240 
241 	if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
242 		return true;
243 
244 	return strchr("*!#$%&'()-@^_`{}~ ", c) != NULL;
245 }
246 
247 
248 static bool
249 check_valid_8_3_file_name(const char* name, const char*& _baseName,
250 	uint32& _baseNameLength, const char*& _extension, uint32& _extensionLength)
251 {
252 	// check length of base name and extension
253 	size_t nameLength = strlen(name);
254 	const char* extension = strchr(name, '.');
255 	size_t baseNameLength;
256 	size_t extensionLength;
257 	if (extension != NULL) {
258 		baseNameLength = extension - name;
259 		extensionLength = nameLength - baseNameLength - 1;
260 		if (extensionLength > 0)
261 			extension++;
262 		else
263 			extension = NULL;
264 	} else {
265 		baseNameLength = nameLength;
266 		extensionLength = 0;
267 	}
268 
269 	// trim trailing space
270 	while (baseNameLength > 0 && name[baseNameLength - 1] == ' ')
271 		baseNameLength--;
272 	while (extensionLength > 0 && extension[extensionLength - 1] == ' ')
273 		extensionLength--;
274 
275 	if (baseNameLength == 0 || baseNameLength > 8 || extensionLength > 3)
276 		return false;
277 
278 	// check the chars
279 	for (size_t i = 0; i < baseNameLength; i++) {
280 		if (!is_valid_8_3_file_name_char(name[i]))
281 			return false;
282 	}
283 
284 	for (size_t i = 0; i < extensionLength; i++) {
285 		if (!is_valid_8_3_file_name_char(extension[i]))
286 			return false;
287 	}
288 
289 	_baseName = name;
290 	_baseNameLength = baseNameLength;
291 	_extension = extension;
292 	_extensionLength = extensionLength;
293 
294 	return true;
295 }
296 
297 
298 // #pragma mark - Directory
299 
300 Directory::Directory(Volume &volume, off_t dirEntryOffset, uint32 cluster,
301 	const char *name)
302 	:
303 	fVolume(volume),
304 	fStream(volume, cluster, UINT32_MAX, name),
305 	fDirEntryOffset(dirEntryOffset)
306 {
307 	TRACE(("FASFS::Directory::(, %lu, %s)\n", cluster, name));
308 }
309 
310 
311 Directory::~Directory()
312 {
313 	TRACE(("FASFS::Directory::~()\n"));
314 }
315 
316 
317 status_t
318 Directory::InitCheck()
319 {
320 	status_t err;
321 	err = fStream.InitCheck();
322 	if (err < B_OK)
323 		return err;
324 	return B_OK;
325 }
326 
327 
328 status_t
329 Directory::Open(void **_cookie, int mode)
330 {
331 	TRACE(("FASFS::Directory::%s(, %d)\n", __FUNCTION__, mode));
332 	_inherited::Open(_cookie, mode);
333 
334 	dir_cookie *c = new(nothrow) dir_cookie;
335 	if (c == NULL)
336 		return B_NO_MEMORY;
337 
338 	c->index = -1;
339 	c->entryOffset = 0;
340 
341 	*_cookie = (void *)c;
342 	return B_OK;
343 }
344 
345 
346 status_t
347 Directory::Close(void *cookie)
348 {
349 	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
350 	_inherited::Close(cookie);
351 
352 	delete (struct dir_cookie *)cookie;
353 	return B_OK;
354 }
355 
356 
357 Node*
358 Directory::LookupDontTraverse(const char* name)
359 {
360 	TRACE(("FASFS::Directory::%s('%s')\n", __FUNCTION__, name));
361 	if (!strcmp(name, ".")) {
362 		Acquire();
363 		return this;
364 	}
365 
366 	status_t err;
367 	struct dir_cookie cookie;
368 	struct dir_cookie *c = &cookie;
369 	c->index = -1;
370 	c->entryOffset = 0;
371 
372 	do {
373 		err = GetNextEntry(c);
374 		if (err < B_OK)
375 			return NULL;
376 		TRACE(("FASFS::Directory::%s: %s <> '%s'\n", __FUNCTION__,
377 			name, c->Name()));
378 		if (strcasecmp(name, c->Name()) == 0) {
379 			TRACE(("GOT IT!\n"));
380 			break;
381 		}
382 	} while (true);
383 
384 	if (c->entry.IsFile()) {
385 		TRACE(("IS FILE\n"));
386 		return new File(fVolume, c->entryOffset,
387 			c->entry.Cluster(fVolume.FatBits()), c->entry.Size(), name);
388 	}
389 	if (c->entry.IsDir()) {
390 		TRACE(("IS DIR\n"));
391 		return new Directory(fVolume, c->entryOffset,
392 			c->entry.Cluster(fVolume.FatBits()), name);
393 	}
394 	return NULL;
395 }
396 
397 
398 status_t
399 Directory::GetNextEntry(void *cookie, char *name, size_t size)
400 {
401 	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
402 	struct dir_cookie *c = (struct dir_cookie *)cookie;
403 	status_t err;
404 
405 	err = GetNextEntry(cookie);
406 	if (err < B_OK)
407 		return err;
408 
409 	strlcpy(name, c->Name(), size);
410 	return B_OK;
411 }
412 
413 
414 status_t
415 Directory::GetNextNode(void *cookie, Node **_node)
416 {
417 	return B_ERROR;
418 }
419 
420 
421 status_t
422 Directory::Rewind(void *cookie)
423 {
424 	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
425 	struct dir_cookie *c = (struct dir_cookie *)cookie;
426 	c->index = -1;
427 	c->entryOffset = 0;
428 
429 	return B_OK;
430 }
431 
432 
433 bool
434 Directory::IsEmpty()
435 {
436 	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
437 	struct dir_cookie cookie;
438 	struct dir_cookie *c = &cookie;
439 	c->index = -1;
440 	c->entryOffset = 0;
441 	if (GetNextEntry(c) == B_OK)
442 		return false;
443 	return true;
444 }
445 
446 
447 status_t
448 Directory::GetName(char *name, size_t size) const
449 {
450 	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
451 	if (this == fVolume.Root())
452 		return fVolume.GetName(name, size);
453 	return fStream.GetName(name, size);
454 }
455 
456 
457 ino_t
458 Directory::Inode() const
459 {
460 	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
461 	return fStream.FirstCluster() << 16;
462 }
463 
464 
465 status_t
466 Directory::CreateFile(const char* name, mode_t permissions, Node** _node)
467 {
468 	if (Node* node = Lookup(name, false)) {
469 		node->Release();
470 		return B_FILE_EXISTS;
471 	}
472 
473 	// We only support 8.3 file names ATM.
474 	const char* baseName;
475 	const char* extension;
476 	uint32 baseNameLength;
477 	uint32 extensionLength;
478 	if (!check_valid_8_3_file_name(name, baseName, baseNameLength, extension,
479 			extensionLength)) {
480 		return B_UNSUPPORTED;
481 	}
482 
483 	// prepare a directory entry for the new file
484 	dir_entry entry;
485 
486 	memset(entry.fName, ' ', sizeof(entry.fName));
487 	memset(entry.fExt, ' ', sizeof(entry.fExt));
488 		// clear both base name and extension
489 	memcpy(entry.fName, baseName, baseNameLength);
490 	if (extensionLength > 0)
491 		memcpy(entry.fExt, extension, extensionLength);
492 
493 	entry.fFlags = 0;
494 	entry.fReserved1 = 0;
495 	entry.fCreateTime10ms = 199;
496 	entry.fCreateTime = B_HOST_TO_LENDIAN_INT16((23 << 11) | (59 << 5) | 29);
497 		// 23:59:59.9
498 	entry.fCreateDate = B_HOST_TO_LENDIAN_INT16((127 << 9) | (12 << 5) | 31);
499 		// 2107-12-31
500 	entry.fAccessDate = entry.fCreateDate;
501 	entry.fClusterMSB = 0;
502 	entry.fModifiedTime = entry.fCreateTime;
503 	entry.fModifiedDate = entry.fCreateDate;
504 	entry.fClusterLSB = 0;
505 	entry.fSize = 0;
506 
507 	// add the entry to the directory
508 	off_t entryOffset;
509 	status_t error = _AddEntry(entry, entryOffset);
510 	if (error != B_OK)
511 		return error;
512 
513 	// create a File object
514 	File* file = new(std::nothrow) File(fVolume, entryOffset,
515 		entry.Cluster(fVolume.FatBits()), entry.Size(), name);
516 	if (file == NULL)
517 		return B_NO_MEMORY;
518 
519 	*_node = file;
520 	return B_OK;
521 }
522 
523 
524 /*static*/ status_t
525 Directory::UpdateDirEntry(Volume& volume, off_t dirEntryOffset,
526 	uint32 firstCluster, uint32 size)
527 {
528 	if (dirEntryOffset == 0)
529 		return B_BAD_VALUE;
530 
531 	CachedBlock cachedBlock(volume);
532 	off_t block = volume.ToBlock(dirEntryOffset);
533 
534 	status_t error = cachedBlock.SetTo(block, CachedBlock::READ);
535 	if (error != B_OK)
536 		return error;
537 
538 	dir_entry* entry = (dir_entry*)(cachedBlock.Block()
539 		+ dirEntryOffset % volume.BlockSize());
540 
541 	entry->SetCluster(firstCluster, volume.FatBits());
542 	entry->SetSize(size);
543 
544 	return cachedBlock.Flush();
545 }
546 
547 
548 status_t
549 Directory::GetNextEntry(void *cookie, uint8 mask, uint8 match)
550 {
551 	TRACE(("FASFS::Directory::%s(, %02x, %02x)\n", __FUNCTION__, mask, match));
552 	struct dir_cookie *c = (struct dir_cookie *)cookie;
553 
554 	bool hasLongName = false;
555 	bool longNameValid = false;
556 
557 	do {
558 		c->index++;
559 		size_t len = sizeof(c->entry);
560 		if (fStream.ReadAt(c->Offset(), (uint8 *)&c->entry, &len,
561 				&c->entryOffset) != B_OK || len != sizeof(c->entry)) {
562 			return B_ENTRY_NOT_FOUND;
563 		}
564 
565 		TRACE(("FASFS::Directory::%s: got one entry\n", __FUNCTION__));
566 		if ((uint8)c->entry.fName[0] == 0x00) // last one
567 			return B_ENTRY_NOT_FOUND;
568 		if ((uint8)c->entry.fName[0] == 0xe5) // deleted
569 			continue;
570 		if (c->entry.Flags() == 0x0f) { // LFN entry
571 			uint8* nameEntry = (uint8*)&c->entry;
572 			if ((*nameEntry & 0x40) != 0) {
573 				c->ResetName();
574 				hasLongName = true;
575 				longNameValid = true;
576 			}
577 
578 			uint16 nameChars[13];
579 			memcpy(nameChars, nameEntry + 0x01, 10);
580 			memcpy(nameChars + 5, nameEntry + 0x0e, 12);
581 			memcpy(nameChars + 11, nameEntry + 0x1c, 4);
582 			longNameValid |= c->AddNameChars(nameChars, 13);
583 			continue;
584 		}
585 		if ((c->entry.Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_VOLUME) {
586 			// TODO handle Volume name (set fVolume's name)
587 			continue;
588 		}
589 		TRACE(("FASFS::Directory::%s: checking '%8.8s.%3.3s', %02x\n", __FUNCTION__,
590 			c->entry.BaseName(), c->entry.Extension(), c->entry.Flags()));
591 		if ((c->entry.Flags() & mask) == match) {
592 			if (longNameValid)
593 				longNameValid = c->ConvertNameToUTF8();
594 			if (!longNameValid) {
595 				// copy 8.3 name to name buffer
596 				c->Set8_3Name(c->entry.BaseName(), c->entry.Extension());
597 			}
598 			break;
599 		}
600 	} while (true);
601 	TRACE(("FATFS::Directory::%s: '%8.8s.%3.3s'\n", __FUNCTION__,
602 		c->entry.BaseName(), c->entry.Extension()));
603 	return B_OK;
604 }
605 
606 
607 status_t
608 Directory::_AddEntry(dir_entry& entry, off_t& _entryOffset)
609 {
610 	off_t dirSize = _GetStreamSize();
611 	if (dirSize < 0)
612 		return dirSize;
613 
614 	uint32 firstCluster = fStream.FirstCluster();
615 
616 	// First null-terminate the new entry list, so we don't leave the list in
617 	// a broken state, if writing the actual entry fails. We only need to do
618 	// that when the entry is not at the end of a cluster.
619 	if ((dirSize + sizeof(entry)) % fVolume.ClusterSize() != 0) {
620 		// TODO: Rather zero the complete remainder of the cluster?
621 		size_t size = 1;
622 		char terminator = 0;
623 		status_t error = fStream.WriteAt(dirSize + sizeof(entry), &terminator,
624 			&size);
625 		if (error != B_OK)
626 			return error;
627 		if (size != 1)
628 			return B_ERROR;
629 	}
630 
631 	// write the entry
632 	size_t size = sizeof(entry);
633 	status_t error = fStream.WriteAt(dirSize, &entry, &size, &_entryOffset);
634 	if (error != B_OK)
635 		return error;
636 	if (size != sizeof(entry))
637 		return B_ERROR;
638 		// TODO: Undo changes!
639 
640 	fStream.SetSize(dirSize + sizeof(entry));
641 
642 	// If the directory cluster has changed (which should only happen, if the
643 	// directory was empty before), we need to adjust the directory entry.
644 	if (firstCluster != fStream.FirstCluster()) {
645 		error = UpdateDirEntry(fVolume, fDirEntryOffset, fStream.FirstCluster(),
646 			0);
647 		if (error != B_OK)
648 			return error;
649 			// TODO: Undo changes!
650 	}
651 
652 	return B_OK;
653 }
654 
655 
656 off_t
657 Directory::_GetStreamSize()
658 {
659 	off_t size = fStream.Size();
660 	if (size != UINT32_MAX)
661 		return size;
662 
663 	// iterate to the end of the directory
664 	size = 0;
665 	while (true) {
666 		dir_entry entry;
667 		size_t entrySize = sizeof(entry);
668 		status_t error = fStream.ReadAt(size, &entry, &entrySize);
669 		if (error != B_OK)
670 			return error;
671 
672 		if (entrySize != sizeof(entry) || entry.fName[0] == 0)
673 			break;
674 
675 		size += sizeof(entry);
676 	}
677 
678 	fStream.SetSize(size);
679 	return size;
680 }
681 
682 
683 }	// namespace FATFS
684