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