1/*
2 * Copyright 2011, J��r��me Duval, korli@users.berlios.de.
3 * Copyright 2014 Haiku, Inc. All rights reserved.
4 *
5 * Distributed under the terms of the MIT License.
6 *
7 * Authors:
8 *		J��r��me Duval, korli@users.berlios.de
9 *		John Scipione, jscipione@gmail.com
10 */
11
12
13#include "DirectoryIterator.h"
14
15#include <stdlib.h>
16
17#include "convertutf.h"
18
19#include "Inode.h"
20
21
22//#define TRACE_EXFAT
23#ifdef TRACE_EXFAT
24#	define TRACE(x...) dprintf("\33[34mexfat:\33[0m " x)
25#else
26#	define TRACE(x...) ;
27#endif
28
29#define ERROR(x...) dprintf("\33[34mexfat:\33[0m " x)
30
31
32//	#pragma mark - DirectoryIterator
33
34
35DirectoryIterator::DirectoryIterator(Inode* inode)
36	:
37	fOffset(-2),
38	fCluster(inode->StartCluster()),
39	fInode(inode),
40	fBlock(inode->GetVolume()),
41	fCurrent(NULL)
42{
43	TRACE("DirectoryIterator::DirectoryIterator() %" B_PRIu32 "\n", fCluster);
44}
45
46
47DirectoryIterator::~DirectoryIterator()
48{
49}
50
51
52status_t
53DirectoryIterator::InitCheck()
54{
55	return B_OK;
56}
57
58
59status_t
60DirectoryIterator::GetNext(char* name, size_t* _nameLength, ino_t* _id,
61	EntryVisitor* visitor)
62{
63	if (fCluster == EXFAT_CLUSTER_END)
64		return B_ENTRY_NOT_FOUND;
65
66	if (fOffset == -2) {
67		if (*_nameLength < 3)
68			return B_BUFFER_OVERFLOW;
69
70		*_nameLength = 2;
71		strlcpy(name, "..", *_nameLength + 1);
72		if (fInode->ID() == 1)
73			*_id = fInode->ID();
74		else
75			*_id = fInode->Parent();
76
77		fOffset = -1;
78		TRACE("DirectoryIterator::GetNext() found \"..\"\n");
79
80		return B_OK;
81	} else if (fOffset == -1) {
82		if (*_nameLength < 2)
83			return B_BUFFER_OVERFLOW;
84
85		*_nameLength = 1;
86		strlcpy(name, ".", *_nameLength + 1);
87		*_id = fInode->ID();
88		fOffset = 0;
89		TRACE("DirectoryIterator::GetNext() found \".\"\n");
90
91		return B_OK;
92	}
93
94	size_t utf16CodeUnitCount = EXFAT_FILENAME_MAX_LENGTH / sizeof(uint16);
95	uint16 utf16Name[utf16CodeUnitCount];
96	status_t status = _GetNext(utf16Name, &utf16CodeUnitCount, _id, visitor);
97	if (status == B_OK && utf16CodeUnitCount > 0) {
98		ssize_t lengthOrStatus = utf16le_to_utf8(utf16Name, utf16CodeUnitCount,
99			name, *_nameLength);
100		if (lengthOrStatus < 0) {
101			status = (status_t)lengthOrStatus;
102			if (status == B_NAME_TOO_LONG)
103				*_nameLength = strlen(name);
104		} else
105			*_nameLength = (size_t)lengthOrStatus;
106	}
107
108	if (status == B_OK) {
109		TRACE("DirectoryIterator::GetNext() cluster: %" B_PRIu32 " id: "
110			"%" B_PRIdINO " name: \"%s\", length: %zu\n", fInode->Cluster(),
111			*_id, name, *_nameLength);
112	} else if (status != B_ENTRY_NOT_FOUND) {
113		ERROR("DirectoryIterator::GetNext() (%s)\n", strerror(status));
114	}
115
116	return status;
117}
118
119
120status_t
121DirectoryIterator::Lookup(const char* name, size_t nameLength, ino_t* _id)
122{
123	if (strcmp(name, ".") == 0) {
124		*_id = fInode->ID();
125		return B_OK;
126	} else if (strcmp(name, "..") == 0) {
127		if (fInode->ID() == 1)
128			*_id = fInode->ID();
129		else
130			*_id = fInode->Parent();
131
132		return B_OK;
133	}
134
135	Rewind();
136	fOffset = 0;
137
138	size_t utf16CodeUnitCount = EXFAT_FILENAME_MAX_LENGTH / sizeof(uint16);
139	uint16 utf16Name[utf16CodeUnitCount];
140	while (_GetNext(utf16Name, &utf16CodeUnitCount, _id) == B_OK) {
141		char utf8Name[nameLength + 1];
142		ssize_t lengthOrStatus = utf16le_to_utf8(utf16Name, utf16CodeUnitCount,
143			utf8Name, sizeof(utf8Name));
144		if (lengthOrStatus > 0 && (size_t)lengthOrStatus == nameLength
145			&& strncmp(utf8Name, name, nameLength) == 0) {
146			TRACE("DirectoryIterator::Lookup() found ID %" B_PRIdINO "\n",
147				*_id);
148			return B_OK;
149		}
150		utf16CodeUnitCount = EXFAT_FILENAME_MAX_LENGTH / sizeof(uint16);
151	}
152
153	TRACE("DirectoryIterator::Lookup() not found %s\n", name);
154
155	return B_ENTRY_NOT_FOUND;
156}
157
158
159status_t
160DirectoryIterator::LookupEntry(EntryVisitor* visitor)
161{
162	fCluster = fInode->Cluster();
163	fOffset = fInode->Offset();
164
165	size_t utf16CodeUnitCount = EXFAT_FILENAME_MAX_LENGTH / sizeof(uint16);
166	uint16 utf16Name[utf16CodeUnitCount];
167	return _GetNext(utf16Name, &utf16CodeUnitCount, NULL, visitor);
168}
169
170
171status_t
172DirectoryIterator::Rewind()
173{
174	fOffset = -2;
175	fCluster = fInode->StartCluster();
176	return B_OK;
177}
178
179
180void
181DirectoryIterator::Iterate(EntryVisitor &visitor)
182{
183	fOffset = 0;
184	fCluster = fInode->StartCluster();
185
186	while (_NextEntry() != B_ENTRY_NOT_FOUND) {
187		switch (fCurrent->type) {
188			case EXFAT_ENTRY_TYPE_BITMAP:
189				visitor.VisitBitmap(fCurrent);
190				break;
191			case EXFAT_ENTRY_TYPE_UPPERCASE:
192				visitor.VisitUppercase(fCurrent);
193				break;
194			case EXFAT_ENTRY_TYPE_LABEL:
195				visitor.VisitLabel(fCurrent);
196				break;
197			case EXFAT_ENTRY_TYPE_FILE:
198				visitor.VisitFile(fCurrent);
199				break;
200			case EXFAT_ENTRY_TYPE_FILEINFO:
201				visitor.VisitFileInfo(fCurrent);
202				break;
203			case EXFAT_ENTRY_TYPE_FILENAME:
204				visitor.VisitFilename(fCurrent);
205				break;
206		}
207	}
208}
209
210
211status_t
212DirectoryIterator::_GetNext(uint16* utf16Name, size_t* _codeUnitCount,
213	ino_t* _id, EntryVisitor* visitor)
214{
215	size_t nameMax = *_codeUnitCount;
216	size_t nameIndex = 0;
217	status_t status;
218	int32 chunkCount = 1;
219
220	while ((status = _NextEntry()) == B_OK) {
221		TRACE("DirectoryIterator::_GetNext() %" B_PRIu32 "/%p, type 0x%x, "
222			"offset %" B_PRId64 "\n", fInode->Cluster(), fCurrent,
223			fCurrent->type, fOffset);
224		if (fCurrent->type == EXFAT_ENTRY_TYPE_FILE) {
225			chunkCount = fCurrent->file.chunkCount;
226			if (_id != NULL) {
227				*_id = fInode->GetVolume()->GetIno(fCluster, fOffset - 1,
228					fInode->ID());
229			}
230			TRACE("DirectoryIterator::_GetNext() File chunkCount %" B_PRId32
231				"\n", chunkCount);
232			if (visitor != NULL)
233				visitor->VisitFile(fCurrent);
234		} else if (fCurrent->type == EXFAT_ENTRY_TYPE_FILEINFO) {
235			chunkCount--;
236			*_codeUnitCount = (size_t)fCurrent->file_info.name_length;
237			TRACE("DirectoryIterator::_GetNext() Filename chunk: %" B_PRId32
238				", code unit count: %" B_PRIu8 "\n", chunkCount, *_codeUnitCount);
239			if (visitor != NULL)
240				visitor->VisitFileInfo(fCurrent);
241		} else if (fCurrent->type == EXFAT_ENTRY_TYPE_FILENAME) {
242			chunkCount--;
243			size_t utf16Length = sizeof(fCurrent->file_name.name);
244			memcpy(utf16Name + nameIndex, fCurrent->file_name.name, utf16Length);
245			nameIndex += utf16Length / sizeof(uint16);
246			TRACE("DirectoryIterator::_GetNext() Filename index: %zu\n",
247				nameIndex);
248			if (visitor != NULL)
249				visitor->VisitFilename(fCurrent);
250		}
251
252		if (chunkCount == 0 || nameIndex >= nameMax)
253			break;
254	}
255
256#ifdef TRACE_EXFAT
257	if (status == B_OK) {
258		size_t utf8Length = B_FILE_NAME_LENGTH * 4;
259		char utf8Name[utf8Length + 1];
260		ssize_t length = utf16le_to_utf8(utf16Name, *_codeUnitCount, utf8Name,
261			utf8Length);
262		if (length > 0) {
263			TRACE("DirectoryIterator::_GetNext() found name: \"%s\", "
264				"length: %d\n", utf8Name, length);
265		}
266	}
267#endif
268
269	return status;
270}
271
272
273status_t
274DirectoryIterator::_NextEntry()
275{
276	if (fCurrent == NULL) {
277		fsblock_t block;
278		if (fInode->GetVolume()->ClusterToBlock(fCluster, block) != B_OK)
279			return B_BAD_DATA;
280		block += (fOffset / fInode->GetVolume()->EntriesPerBlock())
281			% (1 << fInode->GetVolume()->SuperBlock().BlocksPerClusterShift());
282		TRACE("DirectoryIterator::_NextEntry() init to block %" B_PRIu64 "\n",
283			block);
284		fCurrent = (struct exfat_entry*)fBlock.SetTo(block)
285			+ fOffset % fInode->GetVolume()->EntriesPerBlock();
286	} else if ((fOffset % fInode->GetVolume()->EntriesPerBlock()) == 0) {
287		fsblock_t block;
288		if ((fOffset % fInode->GetVolume()->EntriesPerCluster()) == 0) {
289			fCluster = fInode->NextCluster(fCluster);
290			if (fCluster == EXFAT_CLUSTER_END)
291				return B_ENTRY_NOT_FOUND;
292
293			if (fInode->GetVolume()->ClusterToBlock(fCluster, block) != B_OK)
294				return B_BAD_DATA;
295		} else
296			block = fBlock.BlockNumber() + 1;
297
298		TRACE("DirectoryIterator::_NextEntry() block %" B_PRIu64 "\n", block);
299		fCurrent = (struct exfat_entry*)fBlock.SetTo(block);
300	} else
301		fCurrent++;
302
303	fOffset++;
304	return fCurrent->type == 0 ? B_ENTRY_NOT_FOUND : B_OK;
305}
306