| /* |
| * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank |
| * Copyright (c) 1995 Martin Husemann |
| * Some structure declaration borrowed from Paul Popelka |
| * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. All advertising materials mentioning features or use of this software |
| * must display the following acknowledgement: |
| * This product includes software developed by Martin Husemann |
| * and Wolfgang Solfrank. |
| * 4. Neither the name of the University nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| |
| #include <sys/cdefs.h> |
| #include "fatcache.h" |
| #include "fragment.h" |
| #ifndef lint |
| __RCSID("$NetBSD: dir.c,v 1.14 1998/08/25 19:18:15 ross Exp $"); |
| static const char rcsid[] = |
| "$FreeBSD: src/sbin/fsck_msdosfs/dir.c,v 1.3 2003/12/26 17:24:37 trhodes Exp $"; |
| #endif /* not lint */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <time.h> |
| |
| #include <sys/param.h> |
| |
| #include "ext.h" |
| #include "fsutil.h" |
| #include "dosfs.h" |
| #define SLOT_EMPTY 0x00 /* slot has never been used */ |
| #define SLOT_E5 0x05 /* the real value is 0xe5 */ |
| #define SLOT_DELETED 0xe5 /* file in this slot deleted */ |
| |
| #define ATTR_NORMAL 0x00 /* normal file */ |
| #define ATTR_READONLY 0x01 /* file is readonly */ |
| #define ATTR_HIDDEN 0x02 /* file is hidden */ |
| #define ATTR_SYSTEM 0x04 /* file is a system file */ |
| #define ATTR_VOLUME 0x08 /* entry is a volume label */ |
| #define ATTR_DIRECTORY 0x10 /* entry is a directory name */ |
| #define ATTR_ARCHIVE 0x20 /* file is new or modified */ |
| |
| #define ATTR_WIN95 0x0f /* long name record */ |
| |
| /* |
| * This is the format of the contents of the deTime field in the direntry |
| * structure. |
| * We don't use bitfields because we don't know how compilers for |
| * arbitrary machines will lay them out. |
| */ |
| #define DT_2SECONDS_MASK 0x1F /* seconds divided by 2 */ |
| #define DT_2SECONDS_SHIFT 0 |
| #define DT_MINUTES_MASK 0x7E0 /* minutes */ |
| #define DT_MINUTES_SHIFT 5 |
| #define DT_HOURS_MASK 0xF800 /* hours */ |
| #define DT_HOURS_SHIFT 11 |
| |
| /* |
| * This is the format of the contents of the deDate field in the direntry |
| * structure. |
| */ |
| #define DD_DAY_MASK 0x1F /* day of month */ |
| #define DD_DAY_SHIFT 0 |
| #define DD_MONTH_MASK 0x1E0 /* month */ |
| #define DD_MONTH_SHIFT 5 |
| #define DD_YEAR_MASK 0xFE00 /* year - 1980 */ |
| #define DD_YEAR_SHIFT 9 |
| |
| |
| /* dir.c */ |
| static struct dosDirEntry *newDosDirEntry(void); |
| static void freeDosDirEntry(struct dosDirEntry *); |
| static struct dirTodoNode *newDirTodo(void); |
| static void freeDirTodo(struct dirTodoNode *); |
| static char *fullpath(struct dosDirEntry *); |
| static u_char calcShortSum(u_char *); |
| static int delete(int, struct bootblock *,struct cluster_chain_descriptor* ,cl_t, int, |
| cl_t, int, int); |
| static int removede(int, struct bootblock *, struct cluster_chain_descriptor*,u_char *, |
| u_char *, cl_t, cl_t, cl_t, char *, int); |
| static int checksize(struct bootblock *, u_char *, |
| struct dosDirEntry *); |
| static int readDosDirSection(int, struct bootblock *, |
| struct dosDirEntry *); |
| |
| /* |
| * Manage free dosDirEntry structures. |
| */ |
| static struct dosDirEntry *freede; |
| |
| static struct dosDirEntry * |
| newDosDirEntry(void) |
| { |
| struct dosDirEntry *de; |
| |
| if (!(de = freede)) { |
| if (!(de = (struct dosDirEntry *)malloc(sizeof *de))) |
| return 0; |
| } else |
| freede = de->next; |
| return de; |
| } |
| |
| static void |
| freeDosDirEntry(struct dosDirEntry *de) |
| { |
| de->next = freede; |
| freede = de; |
| } |
| |
| /* |
| * The same for dirTodoNode structures. |
| */ |
| static struct dirTodoNode *freedt; |
| |
| static struct dirTodoNode * |
| newDirTodo(void) |
| { |
| struct dirTodoNode *dt; |
| |
| if (!(dt = freedt)) { |
| if (!(dt = (struct dirTodoNode *)malloc(sizeof *dt))) |
| return 0; |
| } else |
| freedt = dt->next; |
| return dt; |
| } |
| |
| static void |
| freeDirTodo(struct dirTodoNode *dt) |
| { |
| dt->next = freedt; |
| freedt = dt; |
| } |
| |
| /* |
| * The stack of unread directories |
| */ |
| struct dirTodoNode *pendingDirectories = NULL; |
| |
| /* |
| * Return the full pathname for a directory entry. |
| */ |
| static char * |
| fullpath(struct dosDirEntry *dir) |
| { |
| static char namebuf[MAXPATHLEN + 1]; |
| char *cp, *np; |
| int nl; |
| |
| cp = namebuf + sizeof namebuf - 1; |
| *cp = '\0'; |
| do { |
| np = dir->lname[0] ? dir->lname : dir->name; |
| nl = strlen(np); |
| if ((cp -= nl) <= namebuf + 1) |
| break; |
| memcpy(cp, np, nl); |
| *--cp = '/'; |
| } while ((dir = dir->parent) != NULL); |
| if (dir) |
| *--cp = '?'; |
| else |
| cp++; |
| return cp; |
| } |
| |
| /* |
| * Calculate a checksum over an 8.3 alias name |
| */ |
| static u_char |
| calcShortSum(u_char *p) |
| { |
| u_char sum = 0; |
| int i; |
| |
| for (i = 0; i < 11; i++) { |
| sum = (sum << 7)|(sum >> 1); /* rotate right */ |
| sum += p[i]; |
| } |
| |
| return sum; |
| } |
| |
| /* |
| * Global variables temporarily used during a directory scan |
| */ |
| static char longName[DOSLONGNAMELEN] = ""; |
| static u_char *buffer = NULL; |
| static u_char *delbuf = NULL; |
| |
| struct dosDirEntry *rootDir; |
| static struct dosDirEntry *lostDir; |
| |
| /* |
| * Init internal state for a new directory scan. |
| */ |
| int |
| resetDosDirSection(struct bootblock *boot) |
| { |
| int b1, b2; |
| int ret = FSOK; |
| struct cluster_chain_descriptor *fat,tofind,*insert; |
| struct fragment *frag,tofind_frag; |
| struct fatcache *cache; |
| b1 = boot->RootDirEnts * 32; |
| b2 = boot->SecPerClust * boot->BytesPerSec; |
| |
| if (!(buffer = malloc(b1 > b2 ? b1 : b2)) |
| || !(delbuf = malloc(b2)) |
| || !(rootDir = newDosDirEntry())) { |
| perror("No space for directory"); |
| return FSFATAL; |
| } |
| memset(rootDir, 0, sizeof *rootDir); |
| if (boot->flags & FAT32) { |
| if (boot->RootCl < CLUST_FIRST || boot->RootCl >= boot->NumClusters) { |
| pfatal("Root directory starts with cluster out of range(%u)", |
| boot->RootCl); |
| return FSFATAL; |
| } |
| //In fat12 and fat16, the boot->RootCl is zero,you can not find its chain |
| tofind.head = boot->RootCl; |
| fat = RB_FIND(FSCK_MSDOS_CACHE,&rb_root,&tofind); |
| /*we have check the cluster chain in readfatAndcheckfat*/ |
| if(!fat){ |
| pwarn("can not find Root direntory in cluster chain\n"); |
| if(ask(1, "Fix")){ |
| tofind_frag.head = boot->RootCl; |
| frag = RB_FIND(FSCK_MSDOS_FRAGMENT,&rb_free_root,&tofind_frag); |
| if(!frag){ |
| fsck_err("can not find Root direntory in free chain\n"); |
| ret = FSFATAL; |
| goto out; |
| } |
| /*find it in free rb tree,now move it to cluster chain*/ |
| fat = New_fatentry(); |
| if(!fat) |
| return FSFATAL; |
| fat->head = boot->RootCl; |
| fat->head = 1; |
| cache = New_fatcache(); |
| if(!cache) |
| return FSFATAL; |
| cache->head = boot->RootCl; |
| cache->length = 1; |
| add_fatcache_To_ClusterChain(fat,cache); |
| insert = RB_INSERT(FSCK_MSDOS_CACHE,&rb_root,fat); |
| if(insert){ |
| fsck_err("%s:fatentry(head:0x%x) exist\n",__func__,fat->head); |
| return FSFATAL; |
| } |
| RB_REMOVE(FSCK_MSDOS_FRAGMENT,&rb_free_root,frag); |
| free(frag); |
| ret = FSFATMOD; |
| goto out; |
| }else{ |
| ret = FSFATAL; |
| goto out; |
| } |
| } |
| fat->flag |= FAT_USED; |
| rootDir->head = boot->RootCl; |
| } |
| out: |
| return ret; |
| } |
| |
| /* |
| * Cleanup after a directory scan |
| */ |
| void |
| finishDosDirSection(void) |
| { |
| struct dirTodoNode *p, *np; |
| struct dosDirEntry *d, *nd; |
| |
| for (p = pendingDirectories; p; p = np) { |
| np = p->next; |
| freeDirTodo(p); |
| } |
| pendingDirectories = 0; |
| for (d = rootDir; d; d = nd) { |
| if ((nd = d->child) != NULL) { |
| d->child = 0; |
| continue; |
| } |
| if (!(nd = d->next)) |
| nd = d->parent; |
| freeDosDirEntry(d); |
| } |
| rootDir = lostDir = NULL; |
| free(buffer); |
| free(delbuf); |
| buffer = NULL; |
| delbuf = NULL; |
| } |
| |
| /* |
| * Delete directory entries between startcl, startoff and endcl, endoff. |
| */ |
| static int |
| delete(int f, struct bootblock *boot, struct cluster_chain_descriptor *fat,cl_t startcl, |
| int startoff, cl_t endcl, int endoff, int notlast) |
| { |
| u_char *s, *e; |
| loff_t off; |
| int clsz = boot->SecPerClust * boot->BytesPerSec; |
| struct fatcache *cache; |
| fsck_debug("delete: %u:%u -->> %u:%u\n",startcl,startoff,endcl,endoff); |
| s = delbuf + startoff; |
| e = delbuf + clsz; |
| if(!fat){ |
| pwarn("Can't find cl %d in cluster chain\n",startcl); |
| return FSFATAL; |
| } |
| while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) { |
| if (startcl == endcl) { |
| if (notlast) |
| break; |
| e = delbuf + endoff; |
| } |
| off = startcl * boot->SecPerClust + boot->ClusterOffset; |
| off *= boot->BytesPerSec; |
| if (lseek64(f, off, SEEK_SET) != off) { |
| perror("Unable to lseek64"); |
| return FSFATAL; |
| } |
| if (read(f, delbuf, clsz) != clsz) { |
| perror("Unable to read directory"); |
| return FSFATAL; |
| } |
| while (s < e) { |
| *s = SLOT_DELETED; |
| s += 32; |
| } |
| if (lseek64(f, off, SEEK_SET) != off) { |
| perror("Unable to lseek64"); |
| return FSFATAL; |
| } |
| if (write(f, delbuf, clsz) != clsz) { |
| perror("Unable to write directory"); |
| return FSFATAL; |
| } |
| if (startcl == endcl) |
| break; |
| //if startcl = 0.it means no next cluster |
| cache = Find_nextclus(fat,startcl,&startcl); |
| if(!cache && !startcl) |
| return FSFATAL; |
| s = delbuf; |
| } |
| return FSOK; |
| } |
| |
| static int |
| removede(int f, struct bootblock *boot,struct cluster_chain_descriptor *fat, u_char *start, |
| u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path, int type) |
| { |
| fsck_debug("removede : %u:%p --->> %u:%p \n",startcl,start,endcl,end); |
| switch (type) { |
| case 0: |
| pwarn("Invalid long filename entry for %s\n", path); |
| break; |
| case 1: |
| pwarn("Invalid long filename entry at end of directory %s\n", path); |
| break; |
| case 2: |
| pwarn("Invalid long filename entry for volume label\n"); |
| break; |
| case 3: |
| pwarn("Can't find the associated cluster chain\n"); |
| } |
| if (ask(1, "Remove")) { |
| if (startcl != curcl) { |
| if (delete(f, boot, fat, |
| startcl, start - buffer, |
| endcl, end - buffer, |
| endcl == curcl) == FSFATAL) |
| return FSFATAL; |
| start = buffer; |
| } |
| if (endcl == curcl) |
| for (; start < end; start += 32) |
| *start = SLOT_DELETED; |
| return FSDIRMOD; |
| } |
| return FSERROR; |
| } |
| |
| /* |
| * Check an in-memory file entry |
| */ |
| static int |
| checksize(struct bootblock *boot, u_char *p, |
| struct dosDirEntry *dir) |
| { |
| /* |
| * Check size on ordinary files |
| */ |
| struct cluster_chain_descriptor *fat = NULL,tofind; |
| struct fatcache *cache = NULL; |
| u_int64_t physicalSize; |
| const u_int64_t max_physical_size = 0x100000000; |
| |
| if (dir->head == CLUST_FREE) |
| physicalSize = 0; |
| else { |
| if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters) |
| return FSERROR; |
| tofind.head = dir->head; |
| fat = RB_FIND(FSCK_MSDOS_CACHE,&rb_root,&tofind); |
| if(!fat){ |
| pwarn("Can't find the cluster chain with head(%u) \n",dir->head); |
| return FSERROR; |
| } |
| physicalSize = ((u_int64_t)fat->length) * ((u_int64_t)boot->ClusterSize); |
| } |
| /* |
| *The file size means how much useful data the file contains |
| *FAT use 4 bytes to describe the file size, so the max file size is (4GBytes - 1). |
| *The physical size means how much space the file use in disk. The max physical |
| *size is 4GBytes. |
| * |
| *For example, we create a new file in WINDOWS,which size is 100Bytes, but i |
| *use 4KB in disk. Here 100B is file size ,and 4KB is physical size. |
| * |
| *Physical size = (file size + boot->ClusterSize - 1) & (boot->ClusterSize - 1) |
| */ |
| if (physicalSize > max_physical_size) { |
| fsck_err("file %s physical size exceed 4 GBytes. %llu\n", |
| fullpath(dir), physicalSize); |
| return FSERROR; |
| } |
| |
| if (physicalSize < (u_int64_t)dir->size) { |
| pwarn("size of %s is %u, should at most be %llu\n", |
| fullpath(dir), dir->size, physicalSize); |
| fsck_debug("physicalSize:%llu ,dir->size = %d ,dir->head:0x%x\n",physicalSize,dir->size,dir->head); |
| if (ask(1, "Truncate")) { |
| dir->size = physicalSize; |
| p[28] = (u_char)physicalSize; |
| p[29] = (u_char)(physicalSize >> 8); |
| p[30] = (u_char)(physicalSize >> 16); |
| p[31] = (u_char)(physicalSize >> 24); |
| return FSDIRMOD; |
| } else |
| return FSERROR; |
| } else if (physicalSize - (u_int64_t)dir->size >= (u_int64_t)boot->ClusterSize) { |
| pwarn("%s has too many clusters allocated\n", |
| fullpath(dir)); |
| fsck_debug("physicalSize:%llu ,dir->size = %d ,dir->head:0x%x\n",physicalSize,dir->size,dir->head); |
| if (ask(1, "Drop superfluous clusters")) { |
| cl_t cl; |
| u_int32_t sz = 0; |
| |
| for (cl = dir->head; (sz += boot->ClusterSize) < dir->size;){ |
| cache = Find_nextclus(fat,cl,&cl); |
| if(!cl ) |
| return FSERROR; |
| if(cl == CLUST_EOF) |
| break; |
| } |
| if(!cache && !cl) |
| return FSERROR; |
| |
| Trunc(boot,fat,cl); |
| fsck_debug("after truncate ,fat->length = %d \n",fat->length); |
| return FSFATMOD; |
| } else |
| return FSERROR; |
| } |
| return FSOK; |
| } |
| |
| |
| static u_char dot_header[16]={0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00}; |
| static u_char dot_dot_header[16]={0x2E, 0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00}; |
| |
| /* |
| * Check for missing or broken '.' and '..' entries. |
| */ |
| static int |
| check_dot_dot(int f, struct bootblock *boot,struct dosDirEntry *dir) |
| { |
| u_char *p, *buf; |
| loff_t off; |
| int last; |
| cl_t cl; |
| int rc=0, n_count; |
| struct cluster_chain_descriptor *fat,tofind; |
| struct fatcache *cache; |
| int dot, dotdot; |
| dot = dotdot = 0; |
| cl = dir->head; |
| if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { |
| return rc; |
| } |
| tofind.head = dir->head; |
| fat = RB_FIND(FSCK_MSDOS_CACHE,&rb_root,&tofind); |
| if(!fat){ |
| pwarn("%s:cannot find cluster chain(%d)\n",__func__,dir->head); |
| return FSFATAL; |
| } |
| do { |
| if (!(boot->flags & FAT32) && !dir->parent) { |
| last = boot->RootDirEnts * 32; |
| off = boot->ResSectors + boot->FATs * boot->FATsecs; |
| } else { |
| last = boot->SecPerClust * boot->BytesPerSec; |
| off = cl * boot->SecPerClust + boot->ClusterOffset; |
| } |
| |
| off *= boot->BytesPerSec; |
| buf = malloc(last); |
| if (!buf) { |
| perror("Unable to malloc"); |
| return FSFATAL; |
| } |
| if (lseek64(f, off, SEEK_SET) != off) { |
| perror("Unable to lseek64"); |
| return FSFATAL; |
| } |
| if (read(f, buf, last) != last) { |
| perror("Unable to read"); |
| return FSFATAL; |
| } |
| last /= 32; |
| p = buf; |
| for (n_count=0, rc=0; n_count < 11; n_count++) { |
| if (dot_header[n_count] != p[n_count]) { |
| rc=-1; |
| break; |
| } |
| } |
| if(!rc) |
| dot=1; |
| |
| for (n_count = 0, rc = 0; n_count < 11; n_count++) { |
| if (dot_dot_header[n_count] != p[n_count+32]) { |
| rc=-1; |
| break; |
| } |
| } |
| if(!rc) |
| dotdot=1; |
| free(buf); |
| cache = Find_nextclus(fat,cl,&cl); |
| if(!cache && !cl) |
| return FSFATAL; |
| } while (cl >= CLUST_FIRST && cl < boot->NumClusters); |
| |
| if (!dot || !dotdot) { |
| if (!dot) |
| pwarn("%s: '.' absent for %s.\n",__func__,dir->name); |
| |
| if (!dotdot) |
| pwarn("%s: '..' absent for %s. \n",__func__,dir->name); |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* |
| * Read a directory and |
| * - resolve long name records |
| * - enter file and directory records into the parent's list |
| * - push directories onto the todo-stack |
| */ |
| static int |
| readDosDirSection(int f, struct bootblock *boot, |
| struct dosDirEntry *dir) |
| { |
| struct dosDirEntry dirent, *d; |
| u_char *p, *vallfn, *invlfn, *empty; |
| loff_t off; |
| int i, j, k, last; |
| cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0; |
| char *t; |
| u_int lidx = 0; |
| int shortSum; |
| int mod = FSOK; |
| struct cluster_chain_descriptor *fat = NULL,tofind; |
| struct fatcache *cache = NULL; |
| #define THISMOD 0x8000 /* Only used within this routine */ |
| cl = dir->head; |
| if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { |
| /* |
| * Already handled somewhere else. |
| */ |
| return FSOK; |
| } |
| shortSum = -1; |
| vallfn = invlfn = empty = NULL; |
| int dot,dotdot; |
| dot = dotdot = 0; |
| //FAT32 has boot->Rootcl |
| if((boot->ClustMask == CLUST32_MASK) || dir->parent){ |
| tofind.head = dir->head; |
| fat = RB_FIND(FSCK_MSDOS_CACHE,&rb_root,&tofind); |
| if(!fat){ |
| fsck_err("%s:can not find cluster chain(head = %u)\n",__func__,dir->head); |
| return FSFATMOD; |
| } |
| } |
| do { |
| struct cluster_chain_descriptor *c_fat; |
| if (!(boot->flags & FAT32) && !dir->parent) { |
| last = boot->RootDirEnts * 32; |
| off = boot->ResSectors + boot->FATs * boot->FATsecs; |
| } else { |
| last = boot->SecPerClust * boot->BytesPerSec; |
| off = cl * boot->SecPerClust + boot->ClusterOffset; |
| } |
| |
| off *= boot->BytesPerSec; |
| if (lseek64(f, off, SEEK_SET) != off) { |
| printf("off = %llu\n", off); |
| perror("Unable to lseek64"); |
| return FSFATAL; |
| } |
| if (read(f, buffer, last) != last) { |
| perror("Unable to read"); |
| return FSFATAL; |
| } |
| last /= 32; |
| for (p = buffer, i = 0; i < last; i++, p += 32) { |
| if (dir->fsckflags & DIREMPWARN) { |
| *p = SLOT_EMPTY; |
| continue; |
| } |
| |
| if (*p == SLOT_EMPTY || *p == SLOT_DELETED) { |
| if (*p == SLOT_EMPTY) { |
| dir->fsckflags |= DIREMPTY; |
| empty = p; |
| empcl = cl; |
| } |
| continue; |
| } |
| |
| if (dir->fsckflags & DIREMPTY) { |
| if (!(dir->fsckflags & DIREMPWARN)) { |
| pwarn("%s has entries after end of directory\n", |
| fullpath(dir)); |
| if (ask(1, "Extend")) { |
| u_char *q; |
| |
| dir->fsckflags &= ~DIREMPTY; |
| if (delete(f, boot, fat, |
| empcl, empty - buffer, |
| cl, p - buffer, 1) == FSFATAL) |
| return FSFATAL; |
| q = empcl == cl ? empty : buffer; |
| for (; q < p; q += 32) |
| *q = SLOT_DELETED; |
| mod |= THISMOD|FSDIRMOD; |
| } else if (ask(1, "Truncate")) |
| dir->fsckflags |= DIREMPWARN; |
| } |
| if (dir->fsckflags & DIREMPWARN) { |
| *p = SLOT_DELETED; |
| mod |= THISMOD|FSDIRMOD; |
| continue; |
| } else if (dir->fsckflags & DIREMPTY) |
| mod |= FSERROR; |
| empty = NULL; |
| } |
| |
| if (p[11] == ATTR_WIN95) { |
| if (*p & LRFIRST) { |
| if (shortSum != -1) { |
| if (!invlfn) { |
| invlfn = vallfn; |
| invcl = valcl; |
| } |
| } |
| memset(longName, 0, sizeof longName); |
| shortSum = p[13]; |
| vallfn = p; |
| valcl = cl; |
| } else if (shortSum != p[13] |
| || lidx != (*p & LRNOMASK)) { |
| if (!invlfn) { |
| invlfn = vallfn; |
| invcl = valcl; |
| } |
| if (!invlfn) { |
| invlfn = p; |
| invcl = cl; |
| } |
| vallfn = NULL; |
| } |
| lidx = *p & LRNOMASK; |
| t = longName + --lidx * 13; |
| for (k = 1; k < 11 && t < longName + sizeof(longName); k += 2) { |
| if (!p[k] && !p[k + 1]) |
| break; |
| *t++ = p[k]; |
| /* |
| * Warn about those unusable chars in msdosfs here? XXX |
| */ |
| if (p[k + 1]) |
| t[-1] = '?'; |
| } |
| if (k >= 11) |
| for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) { |
| if (!p[k] && !p[k + 1]) |
| break; |
| *t++ = p[k]; |
| if (p[k + 1]) |
| t[-1] = '?'; |
| } |
| if (k >= 26) |
| for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) { |
| if (!p[k] && !p[k + 1]) |
| break; |
| *t++ = p[k]; |
| if (p[k + 1]) |
| t[-1] = '?'; |
| } |
| if (t >= longName + sizeof(longName)) { |
| pwarn("long filename too long\n"); |
| if (!invlfn) { |
| invlfn = vallfn; |
| invcl = valcl; |
| } |
| vallfn = NULL; |
| } |
| if (p[26] | (p[27] << 8)) { |
| pwarn("long filename record cluster start != 0\n"); |
| if (!invlfn) { |
| invlfn = vallfn; |
| invcl = cl; |
| } |
| vallfn = NULL; |
| } |
| continue; /* long records don't carry further |
| * information */ |
| } |
| |
| /* |
| * This is a standard msdosfs directory entry. |
| */ |
| memset(&dirent, 0, sizeof dirent); |
| |
| /* |
| * it's a short name record, but we need to know |
| * more, so get the flags first. |
| */ |
| dirent.flags = p[11]; |
| |
| /* |
| * Translate from 850 to ISO here XXX |
| */ |
| for (j = 0; j < 8; j++) |
| dirent.name[j] = p[j]; |
| dirent.name[8] = '\0'; |
| for (k = 7; k >= 0 && dirent.name[k] == ' '; k--) |
| dirent.name[k] = '\0'; |
| if (dirent.name[k] != '\0') |
| k++; |
| if (dirent.name[0] == SLOT_E5) |
| dirent.name[0] = 0xe5; |
| |
| if (dirent.flags & ATTR_VOLUME) { |
| if (vallfn || invlfn) { |
| mod |= removede(f, boot, fat, |
| invlfn ? invlfn : vallfn, p, |
| invlfn ? invcl : valcl, -1, 0, |
| fullpath(dir), 2); |
| vallfn = NULL; |
| invlfn = NULL; |
| } |
| continue; |
| } |
| |
| if (p[8] != ' ') |
| dirent.name[k++] = '.'; |
| for (j = 0; j < 3; j++) |
| dirent.name[k++] = p[j+8]; |
| dirent.name[k] = '\0'; |
| for (k--; k >= 0 && dirent.name[k] == ' '; k--) |
| dirent.name[k] = '\0'; |
| |
| if (vallfn && shortSum != calcShortSum(p)) { |
| if (!invlfn) { |
| invlfn = vallfn; |
| invcl = valcl; |
| } |
| vallfn = NULL; |
| } |
| dirent.head = p[26] | (p[27] << 8); |
| if (boot->ClustMask == CLUST32_MASK) |
| dirent.head |= (p[20] << 16) | (p[21] << 24); |
| dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24); |
| if (vallfn) { |
| strcpy(dirent.lname, longName); |
| longName[0] = '\0'; |
| shortSum = -1; |
| } |
| |
| dirent.parent = dir; |
| dirent.next = dir->child; |
| |
| if (invlfn) { |
| mod |= k = removede(f, boot, fat, |
| invlfn, vallfn ? vallfn : p, |
| invcl, vallfn ? valcl : cl, cl, |
| fullpath(&dirent), 0); |
| if (mod & FSFATAL) |
| return FSFATAL; |
| if (vallfn |
| ? (valcl == cl && vallfn != buffer) |
| : p != buffer) |
| if (k & FSDIRMOD) |
| mod |= THISMOD; |
| } |
| |
| if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) { |
| if (dirent.head != 0) { |
| pwarn("%s has clusters, but size 0\n", |
| fullpath(&dirent)); |
| if (ask(1, "Drop allocated clusters")) { |
| p[26] = p[27] = 0; |
| if (boot->ClustMask == CLUST32_MASK) |
| p[20] = p[21] = 0; |
| tofind.head = dirent.head; |
| c_fat = RB_FIND(FSCK_MSDOS_CACHE,&rb_root,&tofind); |
| if(c_fat) |
| clearchain(boot,c_fat, dirent.head); |
| else |
| mod |= FSERROR; |
| dirent.head = 0; |
| mod |= THISMOD|FSDIRMOD|FSFATMOD; |
| } else |
| mod |= FSERROR; |
| } |
| } else if (dirent.head == 0 |
| && !strcmp(dirent.name, "..") |
| && dir->parent /* XXX */ |
| && !dir->parent->parent) { |
| /* |
| * Do nothing, the parent is the root |
| */ |
| } else if (dirent.head < CLUST_FIRST |
| || dirent.head >= boot->NumClusters |
| ) |
| { |
| if (dirent.head == 0) |
| pwarn("%s has no clusters\n", |
| fullpath(&dirent)); |
| else if (dirent.head < CLUST_FIRST |
| || dirent.head >= boot->NumClusters) |
| pwarn("%s starts with cluster out of range(%u)\n", |
| fullpath(&dirent), |
| dirent.head); |
| |
| if (dirent.flags & ATTR_DIRECTORY) { |
| if (ask(1, "Remove")) { |
| *p = SLOT_DELETED; |
| mod |= THISMOD|FSDIRMOD; |
| } else |
| mod |= FSERROR; |
| continue; |
| } else { |
| if (ask(1, "Truncate")) { |
| p[28] = p[29] = p[30] = p[31] = 0; |
| p[26] = p[27] = 0; |
| if (boot->ClustMask == CLUST32_MASK) |
| p[20] = p[21] = 0; |
| dirent.size = 0; |
| mod |= THISMOD|FSDIRMOD; |
| } else |
| mod |= FSERROR; |
| } |
| } |
| |
| if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters){ |
| tofind.head = dirent.head; |
| c_fat = RB_FIND(FSCK_MSDOS_CACHE,&rb_root,&tofind); |
| if (c_fat) { |
| c_fat->flag |= FAT_USED; |
| } |
| else{ |
| fsck_warn("can't find cluster chain(head:0x%x) of file:%s \n",dirent.head,fullpath(&dirent)); |
| if (vallfn || invlfn) { |
| fsck_warn("Invalid long directory\n"); |
| mod |= removede(f, boot, fat,invlfn ? invlfn : vallfn, p,invlfn ? invcl : valcl, -1, 0,fullpath(dir), 3); |
| } else { |
| fsck_warn("Invalid short directory\n"); |
| if(ask(1, "Delete")){ |
| *p = SLOT_DELETED; |
| mod |= THISMOD|FSDIRMOD; |
| }else |
| mod |= FSERROR; |
| } |
| } |
| } |
| vallfn = NULL; /* not used any longer */ |
| invlfn = NULL; |
| |
| if (dirent.flags & ATTR_DIRECTORY) { |
| /* |
| * gather more info for directories |
| */ |
| struct dirTodoNode *n; |
| |
| if (dirent.size) { |
| pwarn("Directory %s has size != 0\n", |
| fullpath(&dirent)); |
| if (ask(1, "Correct")) { |
| p[28] = p[29] = p[30] = p[31] = 0; |
| dirent.size = 0; |
| mod |= THISMOD|FSDIRMOD; |
| } else |
| mod |= FSERROR; |
| } |
| /* |
| * handle '.' and '..' specially |
| */ |
| if (strcmp(dirent.name, ".") == 0) { |
| if (dirent.head != dir->head) { |
| pwarn("'.' entry in %s has incorrect start cluster\n", |
| fullpath(dir)); |
| if (ask(1, "Correct")) { |
| dirent.head = dir->head; |
| p[26] = (u_char)dirent.head; |
| p[27] = (u_char)(dirent.head >> 8); |
| if (boot->ClustMask == CLUST32_MASK) { |
| p[20] = (u_char)(dirent.head >> 16); |
| p[21] = (u_char)(dirent.head >> 24); |
| } |
| mod |= THISMOD|FSDIRMOD; |
| } else |
| mod |= FSERROR; |
| } |
| continue; |
| } else if (strcmp(dirent.name, "..") == 0) { |
| if (dir->parent) { /* XXX */ |
| if (!dir->parent->parent) { |
| if (dirent.head) { |
| pwarn("'..' entry in %s has non-zero start cluster\n", |
| fullpath(dir)); |
| if (ask(1, "Correct")) { |
| dirent.head = 0; |
| p[26] = p[27] = 0; |
| if (boot->ClustMask == CLUST32_MASK) |
| p[20] = p[21] = 0; |
| mod |= THISMOD|FSDIRMOD; |
| } else |
| mod |= FSERROR; |
| } |
| } else if (dirent.head != dir->parent->head) { |
| pwarn("'..' entry in %s has incorrect start cluster\n", |
| fullpath(dir)); |
| if (ask(1, "Correct")) { |
| dirent.head = dir->parent->head; |
| p[26] = (u_char)dirent.head; |
| p[27] = (u_char)(dirent.head >> 8); |
| if (boot->ClustMask == CLUST32_MASK) { |
| p[20] = (u_char)(dirent.head >> 16); |
| p[21] = (u_char)(dirent.head >> 24); |
| } |
| mod |= THISMOD|FSDIRMOD; |
| } else |
| mod |= FSERROR; |
| } |
| } |
| continue; |
| } else { //only one directory entry can point to dir->head, it's '.' |
| if (dirent.head == dir->head) { |
| pwarn("%s entry in %s has incorrect start cluster.remove\n", |
| dirent.name, fullpath(dir)); |
| //we have to remove this directory entry rigth now rigth here |
| if (ask(1, "Remove")) { |
| *p = SLOT_DELETED; |
| mod |= THISMOD|FSDIRMOD; |
| } else |
| mod |= FSERROR; |
| continue; |
| } |
| /* Consistency checking. a directory must have at least two entries: |
| a dot (.) entry that points to itself, and a dot-dot (..) |
| entry that points to its parent. |
| */ |
| if (check_dot_dot(f,boot,&dirent)) { |
| //mark directory entry as deleted. |
| if (ask(1, "Remove")) { |
| *p = SLOT_DELETED; |
| mod |= THISMOD|FSDIRMOD; |
| } else |
| mod |= FSERROR; |
| continue; |
| } |
| } |
| |
| /* create directory tree node */ |
| if (!(d = newDosDirEntry())) { |
| perror("No space for directory"); |
| return FSFATAL; |
| } |
| memcpy(d, &dirent, sizeof(struct dosDirEntry)); |
| /* link it into the tree */ |
| dir->child = d; |
| /* Enter this directory into the todo list */ |
| if (!(n = newDirTodo())) { |
| perror("No space for todo list"); |
| return FSFATAL; |
| } |
| n->next = pendingDirectories; |
| n->dir = d; |
| pendingDirectories = n; |
| } else { |
| mod |= k = checksize(boot, p, &dirent); |
| if (k & FSDIRMOD) |
| mod |= THISMOD; |
| } |
| boot->NumFiles++; |
| } |
| if (mod & THISMOD) { |
| last *= 32; |
| if (lseek64(f, off, SEEK_SET) != off |
| || write(f, buffer, last) != last) { |
| perror("Unable to write directory"); |
| return FSFATAL; |
| } |
| mod &= ~THISMOD; |
| } |
| if(boot->ClustMask == CLUST32_MASK || dir->parent){ |
| cache = Find_nextclus(fat,cl,&cl); |
| if(!cache && !cl){ |
| fsck_err("%s :Find nextclus error \n",__func__); |
| return FSFATAL; |
| } |
| } |
| } while (cl >= CLUST_FIRST && cl < boot->NumClusters); |
| if (invlfn || vallfn) |
| mod |= removede(f, boot, fat, |
| invlfn ? invlfn : vallfn, p, |
| invlfn ? invcl : valcl, -1, 0, |
| fullpath(dir), 1); |
| return mod & ~THISMOD; |
| } |
| |
| int |
| handleDirTree(int dosfs, struct bootblock *boot) |
| { |
| int mod; |
| fsck_debug("rootDir->head :%d\n",rootDir->head); |
| mod = readDosDirSection(dosfs, boot, rootDir); |
| if (mod & FSFATAL) |
| return FSFATAL; |
| /* |
| * process the directory todo list |
| */ |
| while (pendingDirectories) { |
| struct dosDirEntry *dir = pendingDirectories->dir; |
| struct dirTodoNode *n = pendingDirectories->next; |
| |
| /* |
| * remove TODO entry now, the list might change during |
| * directory reads |
| */ |
| freeDirTodo(pendingDirectories); |
| pendingDirectories = n; |
| |
| /* |
| * handle subdirectory |
| */ |
| mod |= readDosDirSection(dosfs, boot, dir); |
| if (mod & FSFATAL) |
| return FSFATAL; |
| } |
| return mod; |
| } |
| |
| /* |
| * Try to reconnect a FAT chain into dir |
| */ |
| static u_char *lfbuf; |
| static cl_t lfcl; |
| static loff_t lfoff; |
| static struct cluster_chain_descriptor *lostdirfat; |
| int |
| reconnect(int dosfs, struct bootblock *boot, struct cluster_chain_descriptor *fat, cl_t head) |
| { |
| struct dosDirEntry d; |
| struct fatcache *cache = NULL; |
| struct cluster_chain_descriptor tofind; |
| u_char *p; |
| |
| if (!ask(1, "Reconnect")) |
| return FSERROR; |
| //find the lost dir in root directory |
| if (!lostDir) { |
| for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) { |
| if (!strcmp(lostDir->name, LOSTDIR)){ |
| tofind.head = lostDir->head; |
| lostdirfat = RB_FIND(FSCK_MSDOS_CACHE,&rb_root,&tofind); |
| break; |
| } |
| } |
| if (!lostDir) { /* Create LOSTDIR? XXX */ |
| pwarn("No %s directory\n", LOSTDIR); |
| return FSERROR; |
| } |
| } |
| if (!lfbuf) { |
| lfbuf = malloc(boot->ClusterSize); |
| if (!lfbuf) { |
| perror("No space for buffer"); |
| return FSFATAL; |
| } |
| p = NULL; |
| } else |
| p = lfbuf; |
| while (1) { |
| if (p) |
| //find an empty or deleted direntry in lostdir |
| for (; p < lfbuf + boot->ClusterSize; p += 32) |
| if (*p == SLOT_EMPTY |
| || *p == SLOT_DELETED) |
| break; |
| if (p && p < lfbuf + boot->ClusterSize) |
| break; |
| if(p){ |
| cache = Find_nextclus(lostdirfat,lfcl,&lfcl); |
| } |
| else{ |
| lfcl = lostDir->head; |
| } |
| // lfcl = p ? fat[lfcl].next : lostDir->head; |
| if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) { |
| /* Extend LOSTDIR? XXX */ |
| pwarn("No space in %s\n", LOSTDIR); |
| lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0; |
| return FSERROR; |
| } |
| lfoff = lfcl * boot->ClusterSize |
| + boot->ClusterOffset * boot->BytesPerSec; |
| if (lseek64(dosfs, lfoff, SEEK_SET) != lfoff |
| || read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { |
| perror("could not read LOST.DIR"); |
| return FSFATAL; |
| } |
| p = lfbuf; |
| } |
| |
| boot->NumFiles++; |
| /* Ensure uniqueness of entry here! XXX */ |
| memset(&d, 0, sizeof d); |
| (void)snprintf(d.name, sizeof(d.name), "%u", head); |
| d.flags = 0; |
| d.head = head; |
| d.size = fat->length * boot->ClusterSize; |
| |
| memset(p, 0, 32); |
| memset(p, ' ', 11); |
| memcpy(p, d.name, strlen(d.name)); |
| p[26] = (u_char)d.head; |
| p[27] = (u_char)(d.head >> 8); |
| if (boot->ClustMask == CLUST32_MASK) { |
| p[20] = (u_char)(d.head >> 16); |
| p[21] = (u_char)(d.head >> 24); |
| } |
| p[28] = (u_char)d.size; |
| p[29] = (u_char)(d.size >> 8); |
| p[30] = (u_char)(d.size >> 16); |
| p[31] = (u_char)(d.size >> 24); |
| fat->flag |= FAT_USED; |
| if (lseek64(dosfs, lfoff, SEEK_SET) != lfoff |
| || write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { |
| perror("could not write LOST.DIR"); |
| return FSFATAL; |
| } |
| return FSDIRMOD; |
| } |
| |
| void |
| finishlf(void) |
| { |
| if (lfbuf) |
| free(lfbuf); |
| lfbuf = NULL; |
| } |