1 /*  Copyright 1996-1999,2001-2003,2007-2009,2011 Alain Knaff.
2  *  This file is part of mtools.
3  *
4  *  Mtools 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 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  Mtools 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 Mtools.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "sysincludes.h"
19 #include "msdos.h"
20 #include "stream.h"
21 #include "mtools.h"
22 #include "fsP.h"
23 #include "file.h"
24 #include "htable.h"
25 #include "dirCache.h"
26 
27 typedef struct File_t {
28 	Class_t *Class;
29 	int refs;
30 	struct Fs_t *Fs;	/* Filesystem that this fat file belongs to */
31 	Stream_t *Buffer;
32 
33 	int (*map)(struct File_t *this, off_t where, size_t *len, int mode,
34 			   mt_off_t *res);
35 	size_t FileSize;
36 
37 	size_t preallocatedSize;
38 	int preallocatedClusters;
39 
40 	/* Absolute position of first cluster of file */
41 	unsigned int FirstAbsCluNr;
42 
43 	/* Absolute position of previous cluster */
44 	unsigned int PreviousAbsCluNr;
45 
46 	/* Relative position of previous cluster */
47 	unsigned int PreviousRelCluNr;
48 	direntry_t direntry;
49 	size_t hint;
50 	struct dirCache_t *dcp;
51 
52 	unsigned int loopDetectRel;
53 	unsigned int loopDetectAbs;
54 } File_t;
55 
56 static Class_t FileClass;
57 static T_HashTable *filehash;
58 
getUnbufferedFile(Stream_t * Stream)59 static File_t *getUnbufferedFile(Stream_t *Stream)
60 {
61 	while(Stream->Class != &FileClass)
62 		Stream = Stream->Next;
63 	return (File_t *) Stream;
64 }
65 
getFs(Stream_t * Stream)66 Fs_t *getFs(Stream_t *Stream)
67 {
68 	return getUnbufferedFile(Stream)->Fs;
69 }
70 
getDirCacheP(Stream_t * Stream)71 struct dirCache_t **getDirCacheP(Stream_t *Stream)
72 {
73 	return &getUnbufferedFile(Stream)->dcp;
74 }
75 
getDirentry(Stream_t * Stream)76 direntry_t *getDirentry(Stream_t *Stream)
77 {
78 	return &getUnbufferedFile(Stream)->direntry;
79 }
80 
81 
recalcPreallocSize(File_t * This)82 static int recalcPreallocSize(File_t *This)
83 {
84 	size_t currentClusters, neededClusters;
85 	unsigned int clus_size;
86 	int neededPrealloc;
87 	Fs_t *Fs = This->Fs;
88 	int r;
89 
90 #if 0
91 	if(This->FileSize & 0xc0000000) {
92 		fprintf(stderr, "Bad filesize\n");
93 	}
94 	if(This->preallocatedSize & 0xc0000000) {
95 		fprintf(stderr, "Bad preallocated size %x\n",
96 				(int) This->preallocatedSize);
97 	}
98 #endif
99 	clus_size = Fs->cluster_size * Fs->sector_size;
100 
101 	currentClusters = (This->FileSize + clus_size - 1) / clus_size;
102 	neededClusters = (This->preallocatedSize + clus_size - 1) / clus_size;
103 	neededPrealloc = neededClusters - currentClusters;
104 	if(neededPrealloc < 0)
105 		neededPrealloc = 0;
106 	r = fsPreallocateClusters(Fs, neededPrealloc - This->preallocatedClusters);
107 	if(r)
108 		return r;
109 	This->preallocatedClusters = neededPrealloc;
110 	return 0;
111 }
112 
_loopDetect(unsigned int * oldrel,unsigned int rel,unsigned int * oldabs,unsigned int absol)113 static int _loopDetect(unsigned int *oldrel, unsigned int rel,
114 		       unsigned int *oldabs, unsigned int absol)
115 {
116 	if(*oldrel && rel > *oldrel && absol == *oldabs) {
117 		fprintf(stderr, "loop detected! oldrel=%d newrel=%d abs=%d\n",
118 				*oldrel, rel, absol);
119 		return -1;
120 	}
121 
122 	if(rel >= 2 * *oldrel + 1) {
123 		*oldrel = rel;
124 		*oldabs = absol;
125 	}
126 	return 0;
127 }
128 
129 
loopDetect(File_t * This,unsigned int rel,unsigned int absol)130 static int loopDetect(File_t *This, unsigned int rel, unsigned int absol)
131 {
132 	return _loopDetect(&This->loopDetectRel, rel, &This->loopDetectAbs, absol);
133 }
134 
_countBlocks(Fs_t * This,unsigned int block)135 static unsigned int _countBlocks(Fs_t *This, unsigned int block)
136 {
137 	unsigned int blocks;
138 	unsigned int rel, oldabs, oldrel;
139 
140 	blocks = 0;
141 
142 	oldabs = oldrel = rel = 0;
143 
144 	while (block <= This->last_fat && block != 1 && block) {
145 		blocks++;
146 		block = fatDecode(This, block);
147 		rel++;
148 		if(_loopDetect(&oldrel, rel, &oldabs, block) < 0)
149 			block = 1;
150 	}
151 	return blocks;
152 }
153 
countBlocks(Stream_t * Dir,unsigned int block)154 unsigned int countBlocks(Stream_t *Dir, unsigned int block)
155 {
156 	Stream_t *Stream = GetFs(Dir);
157 	DeclareThis(Fs_t);
158 
159 	return _countBlocks(This, block);
160 }
161 
162 /* returns number of bytes in a directory.  Represents a file size, and
163  * can hence be not bigger than 2^32
164  */
countBytes(Stream_t * Dir,unsigned int block)165 static size_t countBytes(Stream_t *Dir, unsigned int block)
166 {
167 	Stream_t *Stream = GetFs(Dir);
168 	DeclareThis(Fs_t);
169 
170 	return _countBlocks(This, block) *
171 		This->sector_size * This->cluster_size;
172 }
173 
printFat(Stream_t * Stream)174 void printFat(Stream_t *Stream)
175 {
176 	File_t *This = getUnbufferedFile(Stream);
177 	unsigned long n;
178 	unsigned int rel;
179 	unsigned long begin, end;
180 	int first;
181 
182 	n = This->FirstAbsCluNr;
183 	if(!n) {
184 		printf("Root directory or empty file\n");
185 		return;
186 	}
187 
188 	rel = 0;
189 	first = 1;
190 	begin = end = 0;
191 	do {
192 		if (first || n != end+1) {
193 			if (!first) {
194 				if (begin != end)
195 					printf("-%lu", end);
196 				printf("> ");
197 			}
198 			begin = end = n;
199 			printf("<%lu", begin);
200 		} else {
201 			end++;
202 		}
203 		first = 0;
204 		n = fatDecode(This->Fs, n);
205 		rel++;
206 		if(loopDetect(This, rel, n) < 0)
207 			n = 1;
208 	} while (n <= This->Fs->last_fat && n != 1);
209 	if(!first) {
210 		if (begin != end)
211 			printf("-%lu", end);
212 		printf(">");
213 	}
214 }
215 
printFatWithOffset(Stream_t * Stream,off_t offset)216 void printFatWithOffset(Stream_t *Stream, off_t offset) {
217 	File_t *This = getUnbufferedFile(Stream);
218 	unsigned long n;
219 	int rel;
220 	off_t clusSize;
221 
222 	n = This->FirstAbsCluNr;
223 	if(!n) {
224 		printf("Root directory or empty file\n");
225 		return;
226 	}
227 
228 	clusSize = This->Fs->cluster_size * This->Fs->sector_size;
229 
230 	rel = 0;
231 	while(offset >= clusSize) {
232 		n = fatDecode(This->Fs, n);
233 		rel++;
234 		if(loopDetect(This, rel, n) < 0)
235 			return;
236 		if(n > This->Fs->last_fat)
237 			return;
238 		offset -= clusSize;
239 	}
240 
241 	printf("%lu", n);
242 }
243 
normal_map(File_t * This,off_t where,size_t * len,int mode,mt_off_t * res)244 static int normal_map(File_t *This, off_t where, size_t *len, int mode,
245 						   mt_off_t *res)
246 {
247 	unsigned int offset;
248 	size_t end;
249 	int NrClu; /* number of clusters to read */
250 	unsigned int RelCluNr;
251 	unsigned int CurCluNr;
252 	unsigned int NewCluNr;
253 	unsigned int AbsCluNr;
254 	unsigned int clus_size;
255 	Fs_t *Fs = This->Fs;
256 
257 	*res = 0;
258 	clus_size = Fs->cluster_size * Fs->sector_size;
259 	offset = where % clus_size;
260 
261 	if (mode == MT_READ)
262 		maximize(*len, This->FileSize - where);
263 	if (*len == 0 )
264 		return 0;
265 
266 	if (This->FirstAbsCluNr < 2){
267 		if( mode == MT_READ || *len == 0){
268 			*len = 0;
269 			return 0;
270 		}
271 		NewCluNr = get_next_free_cluster(This->Fs, 1);
272 		if (NewCluNr == 1 ){
273 			errno = ENOSPC;
274 			return -2;
275 		}
276 		hash_remove(filehash, (void *) This, This->hint);
277 		This->FirstAbsCluNr = NewCluNr;
278 		hash_add(filehash, (void *) This, &This->hint);
279 		fatAllocate(This->Fs, NewCluNr, Fs->end_fat);
280 	}
281 
282 	RelCluNr = where / clus_size;
283 
284 	if (RelCluNr >= This->PreviousRelCluNr){
285 		CurCluNr = This->PreviousRelCluNr;
286 		AbsCluNr = This->PreviousAbsCluNr;
287 	} else {
288 		CurCluNr = 0;
289 		AbsCluNr = This->FirstAbsCluNr;
290 	}
291 
292 
293 	NrClu = (offset + *len - 1) / clus_size;
294 	while (CurCluNr <= RelCluNr + NrClu){
295 		if (CurCluNr == RelCluNr){
296 			/* we have reached the beginning of our zone. Save
297 			 * coordinates */
298 			This->PreviousRelCluNr = RelCluNr;
299 			This->PreviousAbsCluNr = AbsCluNr;
300 		}
301 		NewCluNr = fatDecode(This->Fs, AbsCluNr);
302 		if (NewCluNr == 1 || NewCluNr == 0){
303 			fprintf(stderr,"Fat problem while decoding %d %x\n",
304 				AbsCluNr, NewCluNr);
305 			exit(1);
306 		}
307 		if(CurCluNr == RelCluNr + NrClu)
308 			break;
309 		if (NewCluNr > Fs->last_fat && mode == MT_WRITE){
310 			/* if at end, and writing, extend it */
311 			NewCluNr = get_next_free_cluster(This->Fs, AbsCluNr);
312 			if (NewCluNr == 1 ){ /* no more space */
313 				errno = ENOSPC;
314 				return -2;
315 			}
316 			fatAppend(This->Fs, AbsCluNr, NewCluNr);
317 		}
318 
319 		if (CurCluNr < RelCluNr && NewCluNr > Fs->last_fat){
320 			*len = 0;
321 			return 0;
322 		}
323 
324 		if (CurCluNr >= RelCluNr && NewCluNr != AbsCluNr + 1)
325 			break;
326 		CurCluNr++;
327 		AbsCluNr = NewCluNr;
328 		if(loopDetect(This, CurCluNr, AbsCluNr)) {
329 			errno = EIO;
330 			return -2;
331 		}
332 	}
333 
334 	maximize(*len, (1 + CurCluNr - RelCluNr) * clus_size - offset);
335 
336 	end = where + *len;
337 	if(batchmode &&
338 	   mode == MT_WRITE &&
339 	   end >= This->FileSize) {
340 		*len += ROUND_UP(end, clus_size) - end;
341 	}
342 
343 	if((*len + offset) / clus_size + This->PreviousAbsCluNr-2 >
344 		Fs->num_clus) {
345 		fprintf(stderr, "cluster too big\n");
346 		exit(1);
347 	}
348 
349 	*res = sectorsToBytes((Stream_t*)Fs,
350 						  (This->PreviousAbsCluNr-2) * Fs->cluster_size +
351 						  Fs->clus_start) + offset;
352 	return 1;
353 }
354 
355 
root_map(File_t * This,off_t where,size_t * len,int mode UNUSEDP,mt_off_t * res)356 static int root_map(File_t *This, off_t where, size_t *len, int mode UNUSEDP,
357 		    mt_off_t *res)
358 {
359 	Fs_t *Fs = This->Fs;
360 
361 	if(Fs->dir_len * Fs->sector_size < (size_t) where) {
362 		*len = 0;
363 		errno = ENOSPC;
364 		return -2;
365 	}
366 
367 	sizemaximize(*len, Fs->dir_len * Fs->sector_size - where);
368         if (*len == 0)
369             return 0;
370 
371 	*res = sectorsToBytes((Stream_t*)Fs, Fs->dir_start) + where;
372 	return 1;
373 }
374 
375 
read_file(Stream_t * Stream,char * buf,mt_off_t iwhere,size_t len)376 static int read_file(Stream_t *Stream, char *buf, mt_off_t iwhere,
377 					 size_t len)
378 {
379 	DeclareThis(File_t);
380 	mt_off_t pos;
381 	int err;
382 	off_t where = truncBytes32(iwhere);
383 
384 	Stream_t *Disk = This->Fs->Next;
385 
386 	err = This->map(This, where, &len, MT_READ, &pos);
387 	if(err <= 0)
388 		return err;
389 	return READS(Disk, buf, pos, len);
390 }
391 
write_file(Stream_t * Stream,char * buf,mt_off_t iwhere,size_t len)392 static int write_file(Stream_t *Stream, char *buf, mt_off_t iwhere, size_t len)
393 {
394 	DeclareThis(File_t);
395 	mt_off_t pos;
396 	int ret;
397 	size_t requestedLen;
398 	Stream_t *Disk = This->Fs->Next;
399 	off_t where = truncBytes32(iwhere);
400 	int err;
401 
402 	requestedLen = len;
403 	err = This->map(This, where, &len, MT_WRITE, &pos);
404 	if( err <= 0)
405 		return err;
406 	if(batchmode)
407 		ret = force_write(Disk, buf, pos, len);
408 	else
409 		ret = WRITES(Disk, buf, pos, len);
410 	if(ret > (signed int) requestedLen)
411 		ret = requestedLen;
412 	if (ret > 0 &&
413 	    where + ret > (off_t) This->FileSize )
414 		This->FileSize = where + ret;
415 	recalcPreallocSize(This);
416 	return ret;
417 }
418 
419 
420 /*
421  * Convert an MSDOS time & date stamp to the Unix time() format
422  */
423 
424 static int month[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
425 					  0, 0, 0 };
conv_stamp(struct directory * dir)426 static __inline__ time_t conv_stamp(struct directory *dir)
427 {
428 	struct tm *tmbuf;
429 	long tzone, dst;
430 	time_t accum, tmp;
431 
432 	accum = DOS_YEAR(dir) - 1970; /* years past */
433 
434 	/* days passed */
435 	accum = accum * 365L + month[DOS_MONTH(dir)-1] + DOS_DAY(dir);
436 
437 	/* leap years */
438 	accum += (DOS_YEAR(dir) - 1972) / 4L;
439 
440 	/* back off 1 day if before 29 Feb */
441 	if (!(DOS_YEAR(dir) % 4) && DOS_MONTH(dir) < 3)
442 	        accum--;
443 	accum = accum * 24L + DOS_HOUR(dir); /* hours passed */
444 	accum = accum * 60L + DOS_MINUTE(dir); /* minutes passed */
445 	accum = accum * 60L + DOS_SEC(dir); /* seconds passed */
446 
447 	/* correct for Time Zone */
448 #ifdef HAVE_GETTIMEOFDAY
449 	{
450 		struct timeval tv;
451 		struct timezone tz;
452 
453 		gettimeofday(&tv, &tz);
454 		tzone = tz.tz_minuteswest * 60L;
455 	}
456 #else
457 #if defined HAVE_TZSET && !defined OS_mingw32msvc
458 	{
459 #if !defined OS_ultrix && !defined OS_cygwin
460 		/* Ultrix defines this to be a different type */
461 		extern long timezone;
462 #endif
463 		tzset();
464 		tzone = (long) timezone;
465 	}
466 #else
467 	tzone = 0;
468 #endif /* HAVE_TZSET */
469 #endif /* HAVE_GETTIMEOFDAY */
470 
471 	accum += tzone;
472 
473 	/* correct for Daylight Saving Time */
474 	tmp = accum;
475 	tmbuf = localtime(&tmp);
476 	if(tmbuf) {
477 		dst = (tmbuf->tm_isdst) ? (-60L * 60L) : 0L;
478 		accum += dst;
479 	}
480 	return accum;
481 }
482 
483 
get_file_data(Stream_t * Stream,time_t * date,mt_size_t * size,int * type,int * address)484 static int get_file_data(Stream_t *Stream, time_t *date, mt_size_t *size,
485 			 int *type, int *address)
486 {
487 	DeclareThis(File_t);
488 
489 	if(date)
490 		*date = conv_stamp(& This->direntry.dir);
491 	if(size)
492 		*size = (mt_size_t) This->FileSize;
493 	if(type)
494 		*type = This->direntry.dir.attr & ATTR_DIR;
495 	if(address)
496 		*address = This->FirstAbsCluNr;
497 	return 0;
498 }
499 
500 
free_file(Stream_t * Stream)501 static int free_file(Stream_t *Stream)
502 {
503 	DeclareThis(File_t);
504 	Fs_t *Fs = This->Fs;
505 	fsPreallocateClusters(Fs, -This->preallocatedClusters);
506 	FREE(&This->direntry.Dir);
507 	freeDirCache(Stream);
508 	return hash_remove(filehash, (void *) Stream, This->hint);
509 }
510 
511 
flush_file(Stream_t * Stream)512 static int flush_file(Stream_t *Stream)
513 {
514 	DeclareThis(File_t);
515 	direntry_t *entry = &This->direntry;
516 
517 	if(isRootDir(Stream)) {
518 		return 0;
519 	}
520 
521 	if(This->FirstAbsCluNr != getStart(entry->Dir, &entry->dir)) {
522 		set_word(entry->dir.start, This->FirstAbsCluNr & 0xffff);
523 		set_word(entry->dir.startHi, This->FirstAbsCluNr >> 16);
524 		dir_write(entry);
525 	}
526 	return 0;
527 }
528 
529 
pre_allocate_file(Stream_t * Stream,mt_size_t isize)530 static int pre_allocate_file(Stream_t *Stream, mt_size_t isize)
531 {
532 	DeclareThis(File_t);
533 
534 	size_t size = truncBytes32(isize);
535 
536 	if(size > This->FileSize &&
537 	   size > This->preallocatedSize) {
538 		This->preallocatedSize = size;
539 		return recalcPreallocSize(This);
540 	} else
541 		return 0;
542 }
543 
544 static Class_t FileClass = {
545 	read_file,
546 	write_file,
547 	flush_file, /* flush */
548 	free_file, /* free */
549 	0, /* get_geom */
550 	get_file_data,
551 	pre_allocate_file,
552 	get_dosConvert_pass_through,
553 	0 /* discard */
554 };
555 
getAbsCluNr(File_t * This)556 static unsigned int getAbsCluNr(File_t *This)
557 {
558 	if(This->FirstAbsCluNr)
559 		return This->FirstAbsCluNr;
560 	if(isRootDir((Stream_t *) This))
561 		return 0;
562 	return 1;
563 }
564 
func1(void * Stream)565 static size_t func1(void *Stream)
566 {
567 	DeclareThis(File_t);
568 
569 	return getAbsCluNr(This) ^ (long) This->Fs;
570 }
571 
func2(void * Stream)572 static size_t func2(void *Stream)
573 {
574 	DeclareThis(File_t);
575 
576 	return getAbsCluNr(This);
577 }
578 
comp(void * Stream,void * Stream2)579 static int comp(void *Stream, void *Stream2)
580 {
581 	DeclareThis(File_t);
582 
583 	File_t *This2 = (File_t *) Stream2;
584 
585 	return This->Fs != This2->Fs ||
586 		getAbsCluNr(This) != getAbsCluNr(This2);
587 }
588 
init_hash(void)589 static void init_hash(void)
590 {
591 	static int is_initialised=0;
592 
593 	if(!is_initialised){
594 		make_ht(func1, func2, comp, 20, &filehash);
595 		is_initialised = 1;
596 	}
597 }
598 
599 
_internalFileOpen(Stream_t * Dir,unsigned int first,size_t size,direntry_t * entry)600 static Stream_t *_internalFileOpen(Stream_t *Dir, unsigned int first,
601 				   size_t size, direntry_t *entry)
602 {
603 	Stream_t *Stream = GetFs(Dir);
604 	DeclareThis(Fs_t);
605 	File_t Pattern;
606 	File_t *File;
607 
608 	init_hash();
609 	This->refs++;
610 
611 	if(first != 1){
612 		/* we use the illegal cluster 1 to mark newly created files.
613 		 * do not manage those by hashtable */
614 		Pattern.Fs = This;
615 		Pattern.Class = &FileClass;
616 		if(first || (entry && !IS_DIR(entry)))
617 			Pattern.map = normal_map;
618 		else
619 			Pattern.map = root_map;
620 		Pattern.FirstAbsCluNr = first;
621 		Pattern.loopDetectRel = 0;
622 		Pattern.loopDetectAbs = first;
623 		if(!hash_lookup(filehash, (T_HashTableEl) &Pattern,
624 				(T_HashTableEl **)&File, 0)){
625 			File->refs++;
626 			This->refs--;
627 			return (Stream_t *) File;
628 		}
629 	}
630 
631 	File = New(File_t);
632 	if (!File)
633 		return NULL;
634 	File->dcp = 0;
635 	File->preallocatedClusters = 0;
636 	File->preallocatedSize = 0;
637 	/* memorize dir for date and attrib */
638 	File->direntry = *entry;
639 	if(entry->entry == -3)
640 		File->direntry.Dir = (Stream_t *) File; /* root directory */
641 	else
642 		COPY(File->direntry.Dir);
643 
644 	File->Class = &FileClass;
645 	File->Fs = This;
646 	if(first || (entry && !IS_DIR(entry)))
647 		File->map = normal_map;
648 	else
649 		File->map = root_map; /* FAT 12/16 root directory */
650 	if(first == 1)
651 		File->FirstAbsCluNr = 0;
652 	else
653 		File->FirstAbsCluNr = first;
654 
655 	File->loopDetectRel = 0;
656 	File->loopDetectAbs = 0;
657 
658 	File->PreviousRelCluNr = 0xffff;
659 	File->FileSize = size;
660 	File->refs = 1;
661 	File->Buffer = 0;
662 	hash_add(filehash, (void *) File, &File->hint);
663 	return (Stream_t *) File;
664 }
665 
OpenRoot(Stream_t * Dir)666 Stream_t *OpenRoot(Stream_t *Dir)
667 {
668 	unsigned int num;
669 	direntry_t entry;
670 	size_t size;
671 	Stream_t *file;
672 
673 	memset(&entry, 0, sizeof(direntry_t));
674 
675 	num = fat32RootCluster(Dir);
676 
677 	/* make the directory entry */
678 	entry.entry = -3;
679 	entry.name[0] = '\0';
680 	mk_entry_from_base("/", ATTR_DIR, num, 0, 0, &entry.dir);
681 
682 	if(num)
683 		size = countBytes(Dir, num);
684 	else {
685 		Fs_t *Fs = (Fs_t *) GetFs(Dir);
686 		size = Fs->dir_len * Fs->sector_size;
687 	}
688 	file = _internalFileOpen(Dir, num, size, &entry);
689 	bufferize(&file);
690 	return file;
691 }
692 
693 
OpenFileByDirentry(direntry_t * entry)694 Stream_t *OpenFileByDirentry(direntry_t *entry)
695 {
696 	Stream_t *file;
697 	unsigned int first;
698 	size_t size;
699 
700 	first = getStart(entry->Dir, &entry->dir);
701 
702 	if(!first && IS_DIR(entry))
703 		return OpenRoot(entry->Dir);
704 	if (IS_DIR(entry))
705 		size = countBytes(entry->Dir, first);
706 	else
707 		size = FILE_SIZE(&entry->dir);
708 	file = _internalFileOpen(entry->Dir, first, size, entry);
709 	if(IS_DIR(entry)) {
710 		bufferize(&file);
711 		if(first == 1)
712 			dir_grow(file, 0);
713 	}
714 
715 	return file;
716 }
717 
718 
isRootDir(Stream_t * Stream)719 int isRootDir(Stream_t *Stream)
720 {
721 	File_t *This = getUnbufferedFile(Stream);
722 
723 	return This->map == root_map;
724 }
725