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
27namespace FATFS {
28
29struct 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
57uint32
58dir_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
67void
68dir_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
76void
77dir_entry::SetSize(uint32 size)
78{
79	fSize = B_HOST_TO_LENDIAN_INT32(size);
80}
81
82
83bool
84dir_entry::IsFile() const
85{
86	return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == 0);
87}
88
89
90bool
91dir_entry::IsDir() const
92{
93	return ((Flags() & (FAT_VOLUME|FAT_SUBDIR)) == FAT_SUBDIR);
94}
95
96
97struct 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
118void
119dir_cookie::ResetName()
120{
121	nameLength = 0;
122}
123
124
125bool
126dir_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
149bool
150dir_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
203void
204dir_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
232static bool
233is_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
245static bool
246check_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
297Directory::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
308Directory::~Directory()
309{
310	TRACE(("FASFS::Directory::~()\n"));
311}
312
313
314status_t
315Directory::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
325status_t
326Directory::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
343status_t
344Directory::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
354Node *
355Directory::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
395status_t
396Directory::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
411status_t
412Directory::GetNextNode(void *cookie, Node **_node)
413{
414	return B_ERROR;
415}
416
417
418status_t
419Directory::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
430bool
431Directory::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
444status_t
445Directory::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
454ino_t
455Directory::Inode() const
456{
457	TRACE(("FASFS::Directory::%s()\n", __FUNCTION__));
458	return fStream.FirstCluster() << 16;
459}
460
461
462status_t
463Directory::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
521Directory::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
544status_t
545Directory::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
603status_t
604Directory::_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
652off_t
653Directory::_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