xref: /haiku/src/add-ons/kernel/file_systems/exfat/DirectoryIterator.cpp (revision 1d3959edfb0741a8a8ac768638133add307f8a19)
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 
DirectoryIterator(Inode * inode)35 DirectoryIterator::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 
~DirectoryIterator()47 DirectoryIterator::~DirectoryIterator()
48 {
49 }
50 
51 
52 status_t
InitCheck()53 DirectoryIterator::InitCheck()
54 {
55 	return B_OK;
56 }
57 
58 
59 status_t
GetNext(char * name,size_t * _nameLength,ino_t * _id,EntryVisitor * visitor)60 DirectoryIterator::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 
120 status_t
Lookup(const char * name,size_t nameLength,ino_t * _id)121 DirectoryIterator::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 
159 status_t
LookupEntry(EntryVisitor * visitor)160 DirectoryIterator::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 
171 status_t
Rewind()172 DirectoryIterator::Rewind()
173 {
174 	fOffset = -2;
175 	fCluster = fInode->StartCluster();
176 	return B_OK;
177 }
178 
179 
180 void
Iterate(EntryVisitor & visitor)181 DirectoryIterator::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 
211 status_t
_GetNext(uint16 * utf16Name,size_t * _codeUnitCount,ino_t * _id,EntryVisitor * visitor)212 DirectoryIterator::_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 
273 status_t
_NextEntry()274 DirectoryIterator::_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