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