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
29using std::nothrow;
30
31
32namespace FATFS {
33
34
35struct 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;
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
63uint32
64dir_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
73void
74dir_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
82void
83dir_entry::SetSize(uint32 size)
84{
85	fSize = B_HOST_TO_LENDIAN_INT32(size);
86}
87
88
89bool
90dir_entry::IsFile() const
91{
92	return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == 0);
93}
94
95
96bool
97dir_entry::IsDir() const
98{
99	return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_SUBDIR);
100}
101
102
103struct 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
114	off_t	Offset() const { return index * sizeof(struct dir_entry); }
115	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
124void
125dir_cookie::ResetName()
126{
127	nameLength = 0;
128}
129
130
131bool
132dir_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
155bool
156dir_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
209void
210dir_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
238static bool
239is_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
251static bool
252check_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
303Directory::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
314Directory::~Directory()
315{
316	TRACE(("FASFS::Directory::~()\n"));
317}
318
319
320status_t
321Directory::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
331status_t
332Directory::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
349status_t
350Directory::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
360Node*
361Directory::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
401status_t
402Directory::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
417status_t
418Directory::GetNextNode(void *cookie, Node **_node)
419{
420	return B_ERROR;
421}
422
423
424status_t
425Directory::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
436bool
437Directory::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
450status_t
451Directory::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
460ino_t
461Directory::Inode() const
462{
463	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
464	return fStream.FirstCluster() << 16;
465}
466
467
468status_t
469Directory::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
528Directory::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
551status_t
552Directory::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
608status_t
609Directory::_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
657off_t
658Directory::_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