1/*  Copyright 1996-1999,2001-2003,2007-2009 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
27typedef 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	int hint;
50	struct dirCache_t *dcp;
51
52	unsigned int loopDetectRel;
53	unsigned int loopDetectAbs;
54} File_t;
55
56static Class_t FileClass;
57T_HashTable *filehash;
58
59static File_t *getUnbufferedFile(Stream_t *Stream)
60{
61	while(Stream->Class != &FileClass)
62		Stream = Stream->Next;
63	return (File_t *) Stream;
64}
65
66Fs_t *getFs(Stream_t *Stream)
67{
68	return getUnbufferedFile(Stream)->Fs;
69}
70
71struct dirCache_t **getDirCacheP(Stream_t *Stream)
72{
73	return &getUnbufferedFile(Stream)->dcp;
74}
75
76direntry_t *getDirentry(Stream_t *Stream)
77{
78	return &getUnbufferedFile(Stream)->direntry;
79}
80
81
82static int recalcPreallocSize(File_t *This)
83{
84	size_t currentClusters, neededClusters;
85	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
113static 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
130static int loopDetect(File_t *This, unsigned int rel, unsigned int absol)
131{
132	return _loopDetect(&This->loopDetectRel, rel, &This->loopDetectAbs, absol);
133}
134
135static 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
154unsigned 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 */
165static 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
174void printFat(Stream_t *Stream)
175{
176	File_t *This = getUnbufferedFile(Stream);
177	unsigned long n;
178	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
216static int normal_map(File_t *This, off_t where, size_t *len, int mode,
217						   mt_off_t *res)
218{
219	int offset;
220	size_t end;
221	int NrClu; /* number of clusters to read */
222	unsigned int RelCluNr;
223	unsigned int CurCluNr;
224	unsigned int NewCluNr;
225	unsigned int AbsCluNr;
226	int clus_size;
227	Fs_t *Fs = This->Fs;
228
229	*res = 0;
230	clus_size = Fs->cluster_size * Fs->sector_size;
231	offset = where % clus_size;
232
233	if (mode == MT_READ)
234		maximize(*len, This->FileSize - where);
235	if (*len == 0 )
236		return 0;
237
238	if (This->FirstAbsCluNr < 2){
239		if( mode == MT_READ || *len == 0){
240			*len = 0;
241			return 0;
242		}
243		NewCluNr = get_next_free_cluster(This->Fs, 1);
244		if (NewCluNr == 1 ){
245			errno = ENOSPC;
246			return -2;
247		}
248		hash_remove(filehash, (void *) This, This->hint);
249		This->FirstAbsCluNr = NewCluNr;
250		hash_add(filehash, (void *) This, &This->hint);
251		fatAllocate(This->Fs, NewCluNr, Fs->end_fat);
252	}
253
254	RelCluNr = where / clus_size;
255
256	if (RelCluNr >= This->PreviousRelCluNr){
257		CurCluNr = This->PreviousRelCluNr;
258		AbsCluNr = This->PreviousAbsCluNr;
259	} else {
260		CurCluNr = 0;
261		AbsCluNr = This->FirstAbsCluNr;
262	}
263
264
265	NrClu = (offset + *len - 1) / clus_size;
266	while (CurCluNr <= RelCluNr + NrClu){
267		if (CurCluNr == RelCluNr){
268			/* we have reached the beginning of our zone. Save
269			 * coordinates */
270			This->PreviousRelCluNr = RelCluNr;
271			This->PreviousAbsCluNr = AbsCluNr;
272		}
273		NewCluNr = fatDecode(This->Fs, AbsCluNr);
274		if (NewCluNr == 1 || NewCluNr == 0){
275			fprintf(stderr,"Fat problem while decoding %d %x\n",
276				AbsCluNr, NewCluNr);
277			exit(1);
278		}
279		if(CurCluNr == RelCluNr + NrClu)
280			break;
281		if (NewCluNr > Fs->last_fat && mode == MT_WRITE){
282			/* if at end, and writing, extend it */
283			NewCluNr = get_next_free_cluster(This->Fs, AbsCluNr);
284			if (NewCluNr == 1 ){ /* no more space */
285				errno = ENOSPC;
286				return -2;
287			}
288			fatAppend(This->Fs, AbsCluNr, NewCluNr);
289		}
290
291		if (CurCluNr < RelCluNr && NewCluNr > Fs->last_fat){
292			*len = 0;
293			return 0;
294		}
295
296		if (CurCluNr >= RelCluNr && NewCluNr != AbsCluNr + 1)
297			break;
298		CurCluNr++;
299		AbsCluNr = NewCluNr;
300		if(loopDetect(This, CurCluNr, AbsCluNr)) {
301			errno = EIO;
302			return -2;
303		}
304	}
305
306	maximize(*len, (1 + CurCluNr - RelCluNr) * clus_size - offset);
307
308	end = where + *len;
309	if(batchmode &&
310	   mode == MT_WRITE &&
311	   end >= This->FileSize) {
312		*len += ROUND_UP(end, clus_size) - end;
313	}
314
315	if((*len + offset) / clus_size + This->PreviousAbsCluNr-2 >
316		Fs->num_clus) {
317		fprintf(stderr, "cluster too big\n");
318		exit(1);
319	}
320
321	*res = sectorsToBytes((Stream_t*)Fs,
322						  (This->PreviousAbsCluNr-2) * Fs->cluster_size +
323						  Fs->clus_start) + offset;
324	return 1;
325}
326
327
328static int root_map(File_t *This, off_t where, size_t *len, int mode,
329					mt_off_t *res)
330{
331	Fs_t *Fs = This->Fs;
332
333	if(Fs->dir_len * Fs->sector_size < (size_t) where) {
334		*len = 0;
335		errno = ENOSPC;
336		return -2;
337	}
338
339	maximize(*len, Fs->dir_len * Fs->sector_size - where);
340        if (*len == 0)
341            return 0;
342
343	*res = sectorsToBytes((Stream_t*)Fs, Fs->dir_start) + where;
344	return 1;
345}
346
347
348static int read_file(Stream_t *Stream, char *buf, mt_off_t iwhere,
349					 size_t len)
350{
351	DeclareThis(File_t);
352	mt_off_t pos;
353	int err;
354	off_t where = truncBytes32(iwhere);
355
356	Stream_t *Disk = This->Fs->Next;
357
358	err = This->map(This, where, &len, MT_READ, &pos);
359	if(err <= 0)
360		return err;
361	return READS(Disk, buf, pos, len);
362}
363
364static int write_file(Stream_t *Stream, char *buf, mt_off_t iwhere, size_t len)
365{
366	DeclareThis(File_t);
367	mt_off_t pos;
368	int ret;
369	size_t requestedLen;
370	Stream_t *Disk = This->Fs->Next;
371	off_t where = truncBytes32(iwhere);
372	int err;
373
374	requestedLen = len;
375	err = This->map(This, where, &len, MT_WRITE, &pos);
376	if( err <= 0)
377		return err;
378	if(batchmode)
379		ret = force_write(Disk, buf, pos, len);
380	else
381		ret = WRITES(Disk, buf, pos, len);
382	if(ret > (signed int) requestedLen)
383		ret = requestedLen;
384	if (ret > 0 &&
385	    where + ret > (off_t) This->FileSize )
386		This->FileSize = where + ret;
387	recalcPreallocSize(This);
388	return ret;
389}
390
391
392/*
393 * Convert an MSDOS time & date stamp to the Unix time() format
394 */
395
396static int month[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
397					  0, 0, 0 };
398static __inline__ time_t conv_stamp(struct directory *dir)
399{
400	struct tm *tmbuf;
401	long tzone, dst;
402	time_t accum, tmp;
403
404	accum = DOS_YEAR(dir) - 1970; /* years past */
405
406	/* days passed */
407	accum = accum * 365L + month[DOS_MONTH(dir)-1] + DOS_DAY(dir);
408
409	/* leap years */
410	accum += (DOS_YEAR(dir) - 1972) / 4L;
411
412	/* back off 1 day if before 29 Feb */
413	if (!(DOS_YEAR(dir) % 4) && DOS_MONTH(dir) < 3)
414	        accum--;
415	accum = accum * 24L + DOS_HOUR(dir); /* hours passed */
416	accum = accum * 60L + DOS_MINUTE(dir); /* minutes passed */
417	accum = accum * 60L + DOS_SEC(dir); /* seconds passed */
418
419	/* correct for Time Zone */
420#ifdef HAVE_GETTIMEOFDAY
421	{
422		struct timeval tv;
423		struct timezone tz;
424
425		gettimeofday(&tv, &tz);
426		tzone = tz.tz_minuteswest * 60L;
427	}
428#else
429#if defined HAVE_TZSET && !defined OS_mingw32msvc
430	{
431#if !defined OS_ultrix && !defined OS_cygwin
432		/* Ultrix defines this to be a different type */
433		extern long timezone;
434#endif
435		tzset();
436		tzone = (long) timezone;
437	}
438#else
439	tzone = 0;
440#endif /* HAVE_TZSET */
441#endif /* HAVE_GETTIMEOFDAY */
442
443	accum += tzone;
444
445	/* correct for Daylight Saving Time */
446	tmp = accum;
447	tmbuf = localtime(&tmp);
448	dst = (tmbuf->tm_isdst) ? (-60L * 60L) : 0L;
449	accum += dst;
450
451	return accum;
452}
453
454
455static int get_file_data(Stream_t *Stream, time_t *date, mt_size_t *size,
456			 int *type, int *address)
457{
458	DeclareThis(File_t);
459
460	if(date)
461		*date = conv_stamp(& This->direntry.dir);
462	if(size)
463		*size = (mt_size_t) This->FileSize;
464	if(type)
465		*type = This->direntry.dir.attr & ATTR_DIR;
466	if(address)
467		*address = This->FirstAbsCluNr;
468	return 0;
469}
470
471
472static int free_file(Stream_t *Stream)
473{
474	DeclareThis(File_t);
475	Fs_t *Fs = This->Fs;
476	fsPreallocateClusters(Fs, -This->preallocatedClusters);
477	FREE(&This->direntry.Dir);
478	freeDirCache(Stream);
479	return hash_remove(filehash, (void *) Stream, This->hint);
480}
481
482
483static int flush_file(Stream_t *Stream)
484{
485	DeclareThis(File_t);
486	direntry_t *entry = &This->direntry;
487
488	if(isRootDir(Stream)) {
489		return 0;
490	}
491
492	if(This->FirstAbsCluNr != getStart(entry->Dir, &entry->dir)) {
493		set_word(entry->dir.start, This->FirstAbsCluNr & 0xffff);
494		set_word(entry->dir.startHi, This->FirstAbsCluNr >> 16);
495		dir_write(entry);
496	}
497	return 0;
498}
499
500
501static int pre_allocate_file(Stream_t *Stream, mt_size_t isize)
502{
503	DeclareThis(File_t);
504
505	size_t size = truncBytes32(isize);
506
507	if(size > This->FileSize &&
508	   size > This->preallocatedSize) {
509		This->preallocatedSize = size;
510		return recalcPreallocSize(This);
511	} else
512		return 0;
513}
514
515static Class_t FileClass = {
516	read_file,
517	write_file,
518	flush_file, /* flush */
519	free_file, /* free */
520	0, /* get_geom */
521	get_file_data,
522	pre_allocate_file,
523	get_dosConvert_pass_through
524};
525
526static unsigned int getAbsCluNr(File_t *This)
527{
528	if(This->FirstAbsCluNr)
529		return This->FirstAbsCluNr;
530	if(isRootDir((Stream_t *) This))
531		return 0;
532	return 1;
533}
534
535static unsigned int func1(void *Stream)
536{
537	DeclareThis(File_t);
538
539	return getAbsCluNr(This) ^ (long) This->Fs;
540}
541
542static unsigned int func2(void *Stream)
543{
544	DeclareThis(File_t);
545
546	return getAbsCluNr(This);
547}
548
549static int comp(void *Stream, void *Stream2)
550{
551	DeclareThis(File_t);
552
553	File_t *This2 = (File_t *) Stream2;
554
555	return This->Fs != This2->Fs ||
556		getAbsCluNr(This) != getAbsCluNr(This2);
557}
558
559static void init_hash(void)
560{
561	static int is_initialised=0;
562
563	if(!is_initialised){
564		make_ht(func1, func2, comp, 20, &filehash);
565		is_initialised = 1;
566	}
567}
568
569
570static Stream_t *_internalFileOpen(Stream_t *Dir, unsigned int first,
571				   size_t size, direntry_t *entry)
572{
573	Stream_t *Stream = GetFs(Dir);
574	DeclareThis(Fs_t);
575	File_t Pattern;
576	File_t *File;
577
578	init_hash();
579	This->refs++;
580
581	if(first != 1){
582		/* we use the illegal cluster 1 to mark newly created files.
583		 * do not manage those by hashtable */
584		Pattern.Fs = This;
585		Pattern.Class = &FileClass;
586		if(first || (entry && !IS_DIR(entry)))
587			Pattern.map = normal_map;
588		else
589			Pattern.map = root_map;
590		Pattern.FirstAbsCluNr = first;
591		Pattern.loopDetectRel = 0;
592		Pattern.loopDetectAbs = first;
593		if(!hash_lookup(filehash, (T_HashTableEl) &Pattern,
594				(T_HashTableEl **)&File, 0)){
595			File->refs++;
596			This->refs--;
597			return (Stream_t *) File;
598		}
599	}
600
601	File = New(File_t);
602	if (!File)
603		return NULL;
604	File->dcp = 0;
605	File->preallocatedClusters = 0;
606	File->preallocatedSize = 0;
607	/* memorize dir for date and attrib */
608	File->direntry = *entry;
609	if(entry->entry == -3)
610		File->direntry.Dir = (Stream_t *) File; /* root directory */
611	else
612		COPY(File->direntry.Dir);
613
614	File->Class = &FileClass;
615	File->Fs = This;
616	if(first || (entry && !IS_DIR(entry)))
617		File->map = normal_map;
618	else
619		File->map = root_map; /* FAT 12/16 root directory */
620	if(first == 1)
621		File->FirstAbsCluNr = 0;
622	else
623		File->FirstAbsCluNr = first;
624
625	File->loopDetectRel = 0;
626	File->loopDetectAbs = 0;
627
628	File->PreviousRelCluNr = 0xffff;
629	File->FileSize = size;
630	File->refs = 1;
631	File->Buffer = 0;
632	hash_add(filehash, (void *) File, &File->hint);
633	return (Stream_t *) File;
634}
635
636Stream_t *OpenRoot(Stream_t *Dir)
637{
638	unsigned int num;
639	direntry_t entry;
640	size_t size;
641	Stream_t *file;
642
643	memset(&entry, 0, sizeof(direntry_t));
644
645	num = fat32RootCluster(Dir);
646
647	/* make the directory entry */
648	entry.entry = -3;
649	entry.name[0] = '\0';
650	mk_entry_from_base("/", ATTR_DIR, num, 0, 0, &entry.dir);
651
652	if(num)
653		size = countBytes(Dir, num);
654	else {
655		Fs_t *Fs = (Fs_t *) GetFs(Dir);
656		size = Fs->dir_len * Fs->sector_size;
657	}
658	file = _internalFileOpen(Dir, num, size, &entry);
659	bufferize(&file);
660	return file;
661}
662
663
664Stream_t *OpenFileByDirentry(direntry_t *entry)
665{
666	Stream_t *file;
667	unsigned int first;
668	size_t size;
669
670	first = getStart(entry->Dir, &entry->dir);
671
672	if(!first && IS_DIR(entry))
673		return OpenRoot(entry->Dir);
674	if (IS_DIR(entry))
675		size = countBytes(entry->Dir, first);
676	else
677		size = FILE_SIZE(&entry->dir);
678	file = _internalFileOpen(entry->Dir, first, size, entry);
679	if(IS_DIR(entry)) {
680		bufferize(&file);
681		if(first == 1)
682			dir_grow(file, 0);
683	}
684
685	return file;
686}
687
688
689int isRootDir(Stream_t *Stream)
690{
691	File_t *This = getUnbufferedFile(Stream);
692
693	return This->map == root_map;
694}
695