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