1 /*
2  *  Copyright (C) 2002-2010  The DOSBox Team
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 /* $Id: drive_iso.cpp,v 1.27 2009-09-22 21:48:08 c2woody Exp $ */
20 
21 #include <cctype>
22 #include <cstring>
23 #include "cdrom.h"
24 #include "dosbox.h"
25 #include "dos_system.h"
26 #include "support.h"
27 #include "drives.h"
28 
29 using namespace std;
30 
31 class isoFile : public DOS_File {
32 public:
33 	isoFile(isoDrive *drive, const char *name, FileStat_Block *stat, Bit32u offset);
34 	bool Read(Bit8u *data, Bit16u *size);
35 	bool Write(Bit8u *data, Bit16u *size);
36 	bool Seek(Bit32u *pos, Bit32u type);
37 	bool Close();
38 	Bit16u GetInformation(void);
39 private:
40 	isoDrive *drive;
41 	Bit8u buffer[ISO_FRAMESIZE];
42 	int cachedSector;
43 	Bit32u fileBegin;
44 	Bit32u filePos;
45 	Bit32u fileEnd;
46 	Bit16u info;
47 };
48 
isoFile(isoDrive * drive,const char * name,FileStat_Block * stat,Bit32u offset)49 isoFile::isoFile(isoDrive *drive, const char *name, FileStat_Block *stat, Bit32u offset) {
50 	this->drive = drive;
51 	time = stat->time;
52 	date = stat->date;
53 	attr = stat->attr;
54 	fileBegin = offset;
55 	filePos = fileBegin;
56 	fileEnd = fileBegin + stat->size;
57 	cachedSector = -1;
58 	open = true;
59 	this->name = NULL;
60 	SetName(name);
61 }
62 
Read(Bit8u * data,Bit16u * size)63 bool isoFile::Read(Bit8u *data, Bit16u *size) {
64 	if (filePos + *size > fileEnd)
65 		*size = (Bit16u)(fileEnd - filePos);
66 
67 	Bit16u nowSize = 0;
68 	int sector = filePos / ISO_FRAMESIZE;
69 	Bit16u sectorPos = (Bit16u)(filePos % ISO_FRAMESIZE);
70 
71 	if (sector != cachedSector) {
72 		if (drive->readSector(buffer, sector)) cachedSector = sector;
73 		else { *size = 0; cachedSector = -1; }
74 	}
75 	while (nowSize < *size) {
76 		Bit16u remSector = ISO_FRAMESIZE - sectorPos;
77 		Bit16u remSize = *size - nowSize;
78 		if(remSector < remSize) {
79 			memcpy(&data[nowSize], &buffer[sectorPos], remSector);
80 			nowSize += remSector;
81 			sectorPos = 0;
82 			sector++;
83 			cachedSector++;
84 			if (!drive->readSector(buffer, sector)) {
85 				*size = nowSize;
86 				cachedSector = -1;
87 			}
88 		} else {
89 			memcpy(&data[nowSize], &buffer[sectorPos], remSize);
90 			nowSize += remSize;
91 		}
92 
93 	}
94 
95 	*size = nowSize;
96 	filePos += *size;
97 	return true;
98 }
99 
Write(Bit8u *,Bit16u *)100 bool isoFile::Write(Bit8u* /*data*/, Bit16u* /*size*/) {
101 	return false;
102 }
103 
Seek(Bit32u * pos,Bit32u type)104 bool isoFile::Seek(Bit32u *pos, Bit32u type) {
105 	switch (type) {
106 		case DOS_SEEK_SET:
107 			filePos = fileBegin + *pos;
108 			break;
109 		case DOS_SEEK_CUR:
110 			filePos += *pos;
111 			break;
112 		case DOS_SEEK_END:
113 			filePos = fileEnd + *pos;
114 			break;
115 		default:
116 			return false;
117 	}
118 	if (filePos > fileEnd || filePos < fileBegin)
119 		filePos = fileEnd;
120 
121 	*pos = filePos - fileBegin;
122 	return true;
123 }
124 
Close()125 bool isoFile::Close() {
126 	if (refCtr == 1) open = false;
127 	return true;
128 }
129 
GetInformation(void)130 Bit16u isoFile::GetInformation(void) {
131 	return 0x40;		// read-only drive
132 }
133 
134 int   MSCDEX_RemoveDrive(char driveLetter);
135 int   MSCDEX_AddDrive(char driveLetter, const char* physicalPath, Bit8u& subUnit);
136 void  MSCDEX_ReplaceDrive(CDROM_Interface* cdrom, Bit8u subUnit);
137 bool  MSCDEX_HasDrive(char driveLetter);
138 bool  MSCDEX_GetVolumeName(Bit8u subUnit, char* name);
139 Bit8u MSCDEX_GetSubUnit(char driveLetter);
140 
isoDrive(char driveLetter,const char * fileName,Bit8u mediaid,int & error)141 isoDrive::isoDrive(char driveLetter, const char *fileName, Bit8u mediaid, int &error)
142          :dataCD(false),
143           mediaid(0),
144           subUnit(0),
145           driveLetter('\0')
146  {
147 	this->fileName[0]  = '\0';
148 	this->discLabel[0] = '\0';
149 	nextFreeDirIterator = 0;
150 	memset(dirIterators, 0, sizeof(dirIterators));
151 	memset(sectorHashEntries, 0, sizeof(sectorHashEntries));
152 	memset(&rootEntry, 0, sizeof(isoDirEntry));
153 
154 	safe_strncpy(this->fileName, fileName, CROSS_LEN);
155 	error = UpdateMscdex(driveLetter, fileName, subUnit);
156 
157 	if (!error) {
158 		if (loadImage()) {
159 			strcpy(info, "isoDrive ");
160 			strcat(info, fileName);
161 			this->driveLetter = driveLetter;
162 			this->mediaid = mediaid;
163 			char buffer[32] = { 0 };
164 			if (!MSCDEX_GetVolumeName(subUnit, buffer)) strcpy(buffer, "");
165 			Set_Label(buffer,discLabel,true);
166 
167 		} else if (CDROM_Interface_Image::images[subUnit]->HasDataTrack() == false) { //Audio only cdrom
168 			strcpy(info, "isoDrive ");
169 			strcat(info, fileName);
170 			this->driveLetter = driveLetter;
171 			this->mediaid = mediaid;
172 			char buffer[32] = { 0 };
173 			strcpy(buffer, "Audio_CD");
174 			Set_Label(buffer,discLabel,true);
175 		} else error = 6; //Corrupt image
176 	}
177 }
178 
~isoDrive()179 isoDrive::~isoDrive() { }
180 
UpdateMscdex(char driveLetter,const char * path,Bit8u & subUnit)181 int isoDrive::UpdateMscdex(char driveLetter, const char* path, Bit8u& subUnit) {
182 	if (MSCDEX_HasDrive(driveLetter)) {
183 		subUnit = MSCDEX_GetSubUnit(driveLetter);
184 		CDROM_Interface_Image* oldCdrom = CDROM_Interface_Image::images[subUnit];
185 		CDROM_Interface* cdrom = new CDROM_Interface_Image(subUnit);
186 		char pathCopy[CROSS_LEN];
187 		safe_strncpy(pathCopy, path, CROSS_LEN);
188 		if (!cdrom->SetDevice(pathCopy, 0)) {
189 			CDROM_Interface_Image::images[subUnit] = oldCdrom;
190 			delete cdrom;
191 			return 3;
192 		}
193 		MSCDEX_ReplaceDrive(cdrom, subUnit);
194 		return 0;
195 	} else {
196 		return MSCDEX_AddDrive(driveLetter, path, subUnit);
197 	}
198 }
199 
Activate(void)200 void isoDrive::Activate(void) {
201 	UpdateMscdex(driveLetter, fileName, subUnit);
202 }
203 
FileOpen(DOS_File ** file,char * name,Bit32u flags)204 bool isoDrive::FileOpen(DOS_File **file, char *name, Bit32u flags) {
205 	if ((flags & 0x0f) == OPEN_WRITE) {
206 		DOS_SetError(DOSERR_ACCESS_DENIED);
207 		return false;
208 	}
209 
210 	isoDirEntry de;
211 	bool success = lookup(&de, name) && !IS_DIR(de.fileFlags);
212 
213 	if (success) {
214 		FileStat_Block file_stat;
215 		file_stat.size = DATA_LENGTH(de);
216 		file_stat.attr = DOS_ATTR_ARCHIVE | DOS_ATTR_READ_ONLY;
217 		file_stat.date = DOS_PackDate(1900 + de.dateYear, de.dateMonth, de.dateDay);
218 		file_stat.time = DOS_PackTime(de.timeHour, de.timeMin, de.timeSec);
219 		*file = new isoFile(this, name, &file_stat, EXTENT_LOCATION(de) * ISO_FRAMESIZE);
220 		(*file)->flags = flags;
221 	}
222 	return success;
223 }
224 
FileCreate(DOS_File **,char *,Bit16u)225 bool isoDrive::FileCreate(DOS_File** /*file*/, char* /*name*/, Bit16u /*attributes*/) {
226 	DOS_SetError(DOSERR_ACCESS_DENIED);
227 	return false;
228 }
229 
FileUnlink(char *)230 bool isoDrive::FileUnlink(char* /*name*/) {
231 	DOS_SetError(DOSERR_ACCESS_DENIED);
232 	return false;
233 }
234 
RemoveDir(char *)235 bool isoDrive::RemoveDir(char* /*dir*/) {
236 	DOS_SetError(DOSERR_ACCESS_DENIED);
237 	return false;
238 }
239 
MakeDir(char *)240 bool isoDrive::MakeDir(char* /*dir*/) {
241 	DOS_SetError(DOSERR_ACCESS_DENIED);
242 	return false;
243 }
244 
TestDir(char * dir)245 bool isoDrive::TestDir(char *dir) {
246 	isoDirEntry de;
247 	return (lookup(&de, dir) && IS_DIR(de.fileFlags));
248 }
249 
FindFirst(char * dir,DOS_DTA & dta,bool fcb_findfirst)250 bool isoDrive::FindFirst(char *dir, DOS_DTA &dta, bool fcb_findfirst) {
251 	isoDirEntry de;
252 	if (!lookup(&de, dir)) {
253 		DOS_SetError(DOSERR_PATH_NOT_FOUND);
254 		return false;
255 	}
256 
257 	// get a directory iterator and save its id in the dta
258 	int dirIterator = GetDirIterator(&de);
259 	bool isRoot = (*dir == 0);
260 	dirIterators[dirIterator].root = isRoot;
261 	dta.SetDirID((Bit16u)dirIterator);
262 
263 	Bit8u attr;
264 	char pattern[ISO_MAXPATHNAME];
265 	dta.GetSearchParams(attr, pattern);
266 
267 	if (attr == DOS_ATTR_VOLUME) {
268 		if (strlen(discLabel) != 0) {
269 			dta.SetResult(discLabel, 0, 0, 0, DOS_ATTR_VOLUME);
270 			return true;
271 		} else {
272 			DOS_SetError(DOSERR_NO_MORE_FILES);
273 			return false;
274 		}
275 	} else if ((attr & DOS_ATTR_VOLUME) && isRoot && !fcb_findfirst) {
276 		if (WildFileCmp(discLabel,pattern)) {
277 			// Get Volume Label (DOS_ATTR_VOLUME) and only in basedir and if it matches the searchstring
278 			dta.SetResult(discLabel, 0, 0, 0, DOS_ATTR_VOLUME);
279 			return true;
280 		}
281 	}
282 
283 	return FindNext(dta);
284 }
285 
FindNext(DOS_DTA & dta)286 bool isoDrive::FindNext(DOS_DTA &dta) {
287 	Bit8u attr;
288 	char pattern[DOS_NAMELENGTH_ASCII];
289 	dta.GetSearchParams(attr, pattern);
290 
291 	int dirIterator = dta.GetDirID();
292 	bool isRoot = dirIterators[dirIterator].root;
293 
294 	isoDirEntry de;
295 	while (GetNextDirEntry(dirIterator, &de)) {
296 		Bit8u findAttr = 0;
297 		if (IS_DIR(de.fileFlags)) findAttr |= DOS_ATTR_DIRECTORY;
298 		else findAttr |= DOS_ATTR_ARCHIVE;
299 		if (IS_HIDDEN(de.fileFlags)) findAttr |= DOS_ATTR_HIDDEN;
300 
301 		if (!(isRoot && de.ident[0]=='.') && WildFileCmp((char*)de.ident, pattern)
302 			&& !(~attr & findAttr & (DOS_ATTR_DIRECTORY | DOS_ATTR_HIDDEN | DOS_ATTR_SYSTEM))) {
303 
304 			/* file is okay, setup everything to be copied in DTA Block */
305 			char findName[DOS_NAMELENGTH_ASCII];
306 			findName[0] = 0;
307 			if(strlen((char*)de.ident) < DOS_NAMELENGTH_ASCII) {
308 				strcpy(findName, (char*)de.ident);
309 				upcase(findName);
310 			}
311 			Bit32u findSize = DATA_LENGTH(de);
312 			Bit16u findDate = DOS_PackDate(1900 + de.dateYear, de.dateMonth, de.dateDay);
313 			Bit16u findTime = DOS_PackTime(de.timeHour, de.timeMin, de.timeSec);
314 			dta.SetResult(findName, findSize, findDate, findTime, findAttr);
315 			return true;
316 		}
317 	}
318 	// after searching the directory, free the iterator
319 	FreeDirIterator(dirIterator);
320 
321 	DOS_SetError(DOSERR_NO_MORE_FILES);
322 	return false;
323 }
324 
Rename(char *,char *)325 bool isoDrive::Rename(char* /*oldname*/, char* /*newname*/) {
326 	DOS_SetError(DOSERR_ACCESS_DENIED);
327 	return false;
328 }
329 
GetFileAttr(char * name,Bit16u * attr)330 bool isoDrive::GetFileAttr(char *name, Bit16u *attr) {
331 	*attr = 0;
332 	isoDirEntry de;
333 	bool success = lookup(&de, name);
334 	if (success) {
335 		*attr = DOS_ATTR_ARCHIVE | DOS_ATTR_READ_ONLY;
336 		if (IS_HIDDEN(de.fileFlags)) *attr |= DOS_ATTR_HIDDEN;
337 		if (IS_DIR(de.fileFlags)) *attr |= DOS_ATTR_DIRECTORY;
338 	}
339 	return success;
340 }
341 
AllocationInfo(Bit16u * bytes_sector,Bit8u * sectors_cluster,Bit16u * total_clusters,Bit16u * free_clusters)342 bool isoDrive::AllocationInfo(Bit16u *bytes_sector, Bit8u *sectors_cluster, Bit16u *total_clusters, Bit16u *free_clusters) {
343 	*bytes_sector = 2048;
344 	*sectors_cluster = 1; // cluster size for cdroms ?
345 	*total_clusters = 60000;
346 	*free_clusters = 0;
347 	return true;
348 }
349 
FileExists(const char * name)350 bool isoDrive::FileExists(const char *name) {
351 	isoDirEntry de;
352 	return (lookup(&de, name) && !IS_DIR(de.fileFlags));
353 }
354 
FileStat(const char * name,FileStat_Block * const stat_block)355 bool isoDrive::FileStat(const char *name, FileStat_Block *const stat_block) {
356 	isoDirEntry de;
357 	bool success = lookup(&de, name);
358 
359 	if (success) {
360 		stat_block->date = DOS_PackDate(1900 + de.dateYear, de.dateMonth, de.dateDay);
361 		stat_block->time = DOS_PackTime(de.timeHour, de.timeMin, de.timeSec);
362 		stat_block->size = DATA_LENGTH(de);
363 		stat_block->attr = DOS_ATTR_ARCHIVE | DOS_ATTR_READ_ONLY;
364 		if (IS_DIR(de.fileFlags)) stat_block->attr |= DOS_ATTR_DIRECTORY;
365 	}
366 
367 	return success;
368 }
369 
GetMediaByte(void)370 Bit8u isoDrive::GetMediaByte(void) {
371 	return mediaid;
372 }
373 
isRemote(void)374 bool isoDrive::isRemote(void) {
375 	return true;
376 }
377 
isRemovable(void)378 bool isoDrive::isRemovable(void) {
379 	return true;
380 }
381 
UnMount(void)382 Bits isoDrive::UnMount(void) {
383 	if(MSCDEX_RemoveDrive(driveLetter)) {
384 		delete this;
385 		return 0;
386 	}
387 	return 2;
388 }
389 
GetDirIterator(const isoDirEntry * de)390 int isoDrive::GetDirIterator(const isoDirEntry* de) {
391 	int dirIterator = nextFreeDirIterator;
392 
393 	// get start and end sector of the directory entry (pad end sector if necessary)
394 	dirIterators[dirIterator].currentSector = EXTENT_LOCATION(*de);
395 	dirIterators[dirIterator].endSector =
396 		EXTENT_LOCATION(*de) + DATA_LENGTH(*de) / ISO_FRAMESIZE - 1;
397 	if (DATA_LENGTH(*de) % ISO_FRAMESIZE != 0)
398 		dirIterators[dirIterator].endSector++;
399 
400 	// reset position and mark as valid
401 	dirIterators[dirIterator].pos = 0;
402 	dirIterators[dirIterator].valid = true;
403 
404 	// advance to next directory iterator (wrap around if necessary)
405 	nextFreeDirIterator = (nextFreeDirIterator + 1) % MAX_OPENDIRS;
406 
407 	return dirIterator;
408 }
409 
GetNextDirEntry(const int dirIteratorHandle,isoDirEntry * de)410 bool isoDrive::GetNextDirEntry(const int dirIteratorHandle, isoDirEntry* de) {
411 	bool result = false;
412 	Bit8u* buffer = NULL;
413 	DirIterator& dirIterator = dirIterators[dirIteratorHandle];
414 
415 	// check if the directory entry is valid
416 	if (dirIterator.valid && ReadCachedSector(&buffer, dirIterator.currentSector)) {
417 		// check if the next sector has to be read
418 		if ((dirIterator.pos >= ISO_FRAMESIZE)
419 		 || (buffer[dirIterator.pos] == 0)
420 		 || (dirIterator.pos + buffer[dirIterator.pos] > ISO_FRAMESIZE)) {
421 
422 			// check if there is another sector available
423 		 	if (dirIterator.currentSector < dirIterator.endSector) {
424 			 	dirIterator.pos = 0;
425 			 	dirIterator.currentSector++;
426 			 	if (!ReadCachedSector(&buffer, dirIterator.currentSector)) {
427 			 		return false;
428 			 	}
429 		 	} else {
430 		 		return false;
431 		 	}
432 		 }
433 		 // read sector and advance sector pointer
434 		 int length = readDirEntry(de, &buffer[dirIterator.pos]);
435 		 result = length >= 0;
436 		 dirIterator.pos += length;
437 	}
438 	return result;
439 }
440 
FreeDirIterator(const int dirIterator)441 void isoDrive::FreeDirIterator(const int dirIterator) {
442 	dirIterators[dirIterator].valid = false;
443 
444 	// if this was the last aquired iterator decrement nextFreeIterator
445 	if ((dirIterator + 1) % MAX_OPENDIRS == nextFreeDirIterator) {
446 		if (nextFreeDirIterator>0) {
447 			nextFreeDirIterator--;
448 		} else {
449 			nextFreeDirIterator = MAX_OPENDIRS-1;
450 		}
451 	}
452 }
453 
ReadCachedSector(Bit8u ** buffer,const Bit32u sector)454 bool isoDrive::ReadCachedSector(Bit8u** buffer, const Bit32u sector) {
455 	// get hash table entry
456 	int pos = sector % ISO_MAX_HASH_TABLE_SIZE;
457 	SectorHashEntry& he = sectorHashEntries[pos];
458 
459 	// check if the entry is valid and contains the correct sector
460 	if (!he.valid || he.sector != sector) {
461 		if (!CDROM_Interface_Image::images[subUnit]->ReadSector(he.data, false, sector)) {
462 			return false;
463 		}
464 		he.valid = true;
465 		he.sector = sector;
466 	}
467 
468 	*buffer = he.data;
469 	return true;
470 }
471 
readSector(Bit8u * buffer,Bit32u sector)472 inline bool isoDrive :: readSector(Bit8u *buffer, Bit32u sector) {
473 	return CDROM_Interface_Image::images[subUnit]->ReadSector(buffer, false, sector);
474 }
475 
readDirEntry(isoDirEntry * de,Bit8u * data)476 int isoDrive :: readDirEntry(isoDirEntry *de, Bit8u *data) {
477 	// copy data into isoDirEntry struct, data[0] = length of DirEntry
478 //	if (data[0] > sizeof(isoDirEntry)) return -1;//check disabled as isoDirentry is currently 258 bytes large. So it always fits
479 	memcpy(de, data, data[0]);//Perharps care about a zero at the end.
480 
481 	// xa not supported
482 	if (de->extAttrLength != 0) return -1;
483 	// interleaved mode not supported
484 	if (de->fileUnitSize != 0 || de->interleaveGapSize != 0) return -1;
485 
486 	// modify file identifier for use with dosbox
487 	if ((de->length < 33 + de->fileIdentLength)) return -1;
488 	if (IS_DIR(de->fileFlags)) {
489 		if (de->fileIdentLength == 1 && de->ident[0] == 0) strcpy((char*)de->ident, ".");
490 		else if (de->fileIdentLength == 1 && de->ident[0] == 1) strcpy((char*)de->ident, "..");
491 		else {
492 			if (de->fileIdentLength > 200) return -1;
493 			de->ident[de->fileIdentLength] = 0;
494 		}
495 	} else {
496 		if (de->fileIdentLength > 200) return -1;
497 		de->ident[de->fileIdentLength] = 0;
498 		// remove any file version identifiers as there are some cdroms that don't have them
499 		strreplace((char*)de->ident, ';', 0);
500 		// if file has no extension remove the trailing dot
501 		size_t tmp = strlen((char*)de->ident);
502 		if (tmp > 0) {
503 			if (de->ident[tmp - 1] == '.') de->ident[tmp - 1] = 0;
504 		}
505 	}
506 	const char* dotpos = strchr((char*)de->ident, '.');
507 	if (dotpos!=NULL) {
508 		if (dotpos-(char*)de->ident>8) {
509 			strcpy((char*)(&de->ident[8]),dotpos);
510 		}
511 	}
512 	if (strlen((char*)de->ident)>12) de->ident[12]=0;
513 	return de->length;
514 }
515 
loadImage()516 bool isoDrive :: loadImage() {
517 	isoPVD pvd;
518 	dataCD = false;
519 	readSector((Bit8u*)(&pvd), ISO_FIRST_VD);
520 	if (pvd.type != 1 || strncmp((char*)pvd.standardIdent, "CD001", 5) || pvd.version != 1) return false;
521 	if (readDirEntry(&this->rootEntry, pvd.rootEntry)>0) {
522 		dataCD = true;
523 		return true;
524 	}
525 	return false;
526 }
527 
lookup(isoDirEntry * de,const char * path)528 bool isoDrive :: lookup(isoDirEntry *de, const char *path) {
529 	if (!dataCD) return false;
530 	*de = this->rootEntry;
531 	if (!strcmp(path, "")) return true;
532 
533 	char isoPath[ISO_MAXPATHNAME];
534 	safe_strncpy(isoPath, path, ISO_MAXPATHNAME);
535 	strreplace(isoPath, '\\', '/');
536 
537 	// iterate over all path elements (name), and search each of them in the current de
538 	for(char* name = strtok(isoPath, "/"); NULL != name; name = strtok(NULL, "/")) {
539 
540 		bool found = false;
541 		// current entry must be a directory, abort otherwise
542 		if (IS_DIR(de->fileFlags)) {
543 
544 			// remove the trailing dot if present
545 			size_t nameLength = strlen(name);
546 			if (nameLength > 0) {
547 				if (name[nameLength - 1] == '.') name[nameLength - 1] = 0;
548 			}
549 
550 			// look for the current path element
551 			int dirIterator = GetDirIterator(de);
552 			while (!found && GetNextDirEntry(dirIterator, de)) {
553 				if (0 == strncasecmp((char*) de->ident, name, ISO_MAX_FILENAME_LENGTH)) {
554 					found = true;
555 				}
556 			}
557 			FreeDirIterator(dirIterator);
558 		}
559 		if (!found) return false;
560 	}
561 	return true;
562 }
563