890cae1977
As far as we know, there is no FAT implementation that supported hard links, and our msdosfs driver assumed one cluster chain is only referenced by one directory entry and clears it out when the file is deleted. On the other hand, the current code would proceed with checkchain() when the directory entry's head cluster is a valid numbered cluster without checking if it was a valid head node of a cluster chain. So if the cluster do not being a chain (e.g. CLUST_FREE, CLUST_BAD), or was already referenced by another directory entry, this would trigger an assertion in check_chain() at a later time. Fix this by giving the user an option to truncate the directory entry when the head cluster is an invalid cluster, an visited head node, or not a head node. Reported by: NetApp (kevans@) Reviewed by: kevans, emaste (no objection) MFC after: 2 weeks Differential Revision: https://reviews.freebsd.org/D32699
1182 lines
29 KiB
C
1182 lines
29 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2019 Google LLC
|
|
* 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.
|
|
*
|
|
* 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>
|
|
#ifndef lint
|
|
__RCSID("$NetBSD: dir.c,v 1.20 2006/06/05 16:51:18 christos Exp $");
|
|
static const char rcsid[] =
|
|
"$FreeBSD$";
|
|
#endif /* not lint */
|
|
|
|
#include <assert.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include "ext.h"
|
|
#include "fsutil.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(struct fat_descriptor *, cl_t, int, cl_t, int, int);
|
|
static int removede(struct fat_descriptor *, u_char *, u_char *,
|
|
cl_t, cl_t, cl_t, char *, int);
|
|
static int checksize(struct fat_descriptor *, u_char *, struct dosDirEntry *);
|
|
static int readDosDirSection(struct fat_descriptor *, struct dosDirEntry *);
|
|
|
|
/*
|
|
* Manage free dosDirEntry structures.
|
|
*/
|
|
static struct dosDirEntry *freede;
|
|
|
|
static struct dosDirEntry *
|
|
newDosDirEntry(void)
|
|
{
|
|
struct dosDirEntry *de;
|
|
|
|
if (!(de = freede)) {
|
|
if (!(de = malloc(sizeof *de)))
|
|
return (NULL);
|
|
} 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 = 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
|
|
*/
|
|
static 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;
|
|
*--cp = '\0';
|
|
|
|
for(;;) {
|
|
np = dir->lname[0] ? dir->lname : dir->name;
|
|
nl = strlen(np);
|
|
if (cp <= namebuf + 1 + nl) {
|
|
*--cp = '?';
|
|
break;
|
|
}
|
|
cp -= nl;
|
|
memcpy(cp, np, nl);
|
|
dir = dir->parent;
|
|
if (!dir)
|
|
break;
|
|
*--cp = '/';
|
|
}
|
|
|
|
return cp;
|
|
}
|
|
|
|
/*
|
|
* Calculate a checksum over an 8.3 alias name
|
|
*/
|
|
static inline 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;
|
|
|
|
static struct dosDirEntry *rootDir;
|
|
static struct dosDirEntry *lostDir;
|
|
|
|
/*
|
|
* Init internal state for a new directory scan.
|
|
*/
|
|
int
|
|
resetDosDirSection(struct fat_descriptor *fat)
|
|
{
|
|
int rootdir_size, cluster_size;
|
|
int ret = FSOK;
|
|
size_t len;
|
|
struct bootblock *boot;
|
|
|
|
boot = fat_get_boot(fat);
|
|
|
|
rootdir_size = boot->bpbRootDirEnts * 32;
|
|
cluster_size = boot->bpbSecPerClust * boot->bpbBytesPerSec;
|
|
|
|
if ((buffer = malloc(len = MAX(rootdir_size, cluster_size))) == NULL) {
|
|
perr("No space for directory buffer (%zu)", len);
|
|
return FSFATAL;
|
|
}
|
|
|
|
if ((delbuf = malloc(len = cluster_size)) == NULL) {
|
|
free(buffer);
|
|
perr("No space for directory delbuf (%zu)", len);
|
|
return FSFATAL;
|
|
}
|
|
|
|
if ((rootDir = newDosDirEntry()) == NULL) {
|
|
free(buffer);
|
|
free(delbuf);
|
|
perr("No space for directory entry");
|
|
return FSFATAL;
|
|
}
|
|
|
|
memset(rootDir, 0, sizeof *rootDir);
|
|
if (boot->flags & FAT32) {
|
|
if (!fat_is_cl_head(fat, boot->bpbRootClust)) {
|
|
pfatal("Root directory doesn't start a cluster chain");
|
|
return FSFATAL;
|
|
}
|
|
rootDir->head = boot->bpbRootClust;
|
|
}
|
|
|
|
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 = NULL;
|
|
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(struct fat_descriptor *fat, cl_t startcl,
|
|
int startoff, cl_t endcl, int endoff, int notlast)
|
|
{
|
|
u_char *s, *e;
|
|
off_t off;
|
|
int clsz, fd;
|
|
struct bootblock *boot;
|
|
|
|
boot = fat_get_boot(fat);
|
|
fd = fat_get_fd(fat);
|
|
clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec;
|
|
|
|
s = delbuf + startoff;
|
|
e = delbuf + clsz;
|
|
while (fat_is_valid_cl(fat, startcl)) {
|
|
if (startcl == endcl) {
|
|
if (notlast)
|
|
break;
|
|
e = delbuf + endoff;
|
|
}
|
|
off = (startcl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
|
|
|
|
off *= boot->bpbBytesPerSec;
|
|
if (lseek(fd, off, SEEK_SET) != off) {
|
|
perr("Unable to lseek to %" PRId64, off);
|
|
return FSFATAL;
|
|
}
|
|
if (read(fd, delbuf, clsz) != clsz) {
|
|
perr("Unable to read directory");
|
|
return FSFATAL;
|
|
}
|
|
while (s < e) {
|
|
*s = SLOT_DELETED;
|
|
s += 32;
|
|
}
|
|
if (lseek(fd, off, SEEK_SET) != off) {
|
|
perr("Unable to lseek to %" PRId64, off);
|
|
return FSFATAL;
|
|
}
|
|
if (write(fd, delbuf, clsz) != clsz) {
|
|
perr("Unable to write directory");
|
|
return FSFATAL;
|
|
}
|
|
if (startcl == endcl)
|
|
break;
|
|
startcl = fat_get_cl_next(fat, startcl);
|
|
s = delbuf;
|
|
}
|
|
return FSOK;
|
|
}
|
|
|
|
static int
|
|
removede(struct fat_descriptor *fat, u_char *start,
|
|
u_char *end, cl_t startcl, cl_t endcl, cl_t curcl,
|
|
char *path, int type)
|
|
{
|
|
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;
|
|
}
|
|
if (ask(0, "Remove")) {
|
|
if (startcl != curcl) {
|
|
if (delete(fat,
|
|
startcl, start - buffer,
|
|
endcl, end - buffer,
|
|
endcl == curcl) == FSFATAL)
|
|
return FSFATAL;
|
|
start = buffer;
|
|
}
|
|
/* startcl is < CLUST_FIRST for !FAT32 root */
|
|
if ((endcl == curcl) || (startcl < CLUST_FIRST))
|
|
for (; start < end; start += 32)
|
|
*start = SLOT_DELETED;
|
|
return FSDIRMOD;
|
|
}
|
|
return FSERROR;
|
|
}
|
|
|
|
/*
|
|
* Check an in-memory file entry
|
|
*/
|
|
static int
|
|
checksize(struct fat_descriptor *fat, u_char *p, struct dosDirEntry *dir)
|
|
{
|
|
int ret = FSOK;
|
|
size_t chainsize;
|
|
u_int64_t physicalSize;
|
|
struct bootblock *boot;
|
|
|
|
boot = fat_get_boot(fat);
|
|
|
|
/*
|
|
* Check size on ordinary files
|
|
*/
|
|
if (dir->head == CLUST_FREE) {
|
|
physicalSize = 0;
|
|
} else {
|
|
if (!fat_is_valid_cl(fat, dir->head) || !fat_is_cl_head(fat, dir->head)) {
|
|
pwarn("Directory entry %s of size %u referencing invalid cluster %u\n",
|
|
fullpath(dir), dir->size, dir->head);
|
|
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;
|
|
dir->size = 0;
|
|
dir->head = CLUST_FREE;
|
|
return FSDIRMOD;
|
|
} else {
|
|
return FSERROR;
|
|
}
|
|
}
|
|
ret = checkchain(fat, dir->head, &chainsize);
|
|
/*
|
|
* Upon return, chainsize would hold the chain length
|
|
* that checkchain() was able to validate, but if the user
|
|
* refused the proposed repair, it would be unsafe to
|
|
* proceed with directory entry fix, so bail out in that
|
|
* case.
|
|
*/
|
|
if (ret == FSERROR) {
|
|
return (FSERROR);
|
|
}
|
|
/*
|
|
* The maximum file size on FAT32 is 4GiB - 1, which
|
|
* will occupy a cluster chain of exactly 4GiB in
|
|
* size. On 32-bit platforms, since size_t is 32-bit,
|
|
* it would wrap back to 0.
|
|
*/
|
|
physicalSize = (u_int64_t)chainsize * boot->ClusterSize;
|
|
}
|
|
if (physicalSize < dir->size) {
|
|
pwarn("size of %s is %u, should at most be %ju\n",
|
|
fullpath(dir), dir->size, (uintmax_t)physicalSize);
|
|
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 - dir->size >= boot->ClusterSize) {
|
|
pwarn("%s has too many clusters allocated\n",
|
|
fullpath(dir));
|
|
if (ask(1, "Drop superfluous clusters")) {
|
|
cl_t cl;
|
|
u_int32_t sz, len;
|
|
|
|
for (cl = dir->head, len = sz = 0;
|
|
(sz += boot->ClusterSize) < dir->size; len++)
|
|
cl = fat_get_cl_next(fat, cl);
|
|
clearchain(fat, fat_get_cl_next(fat, cl));
|
|
ret = fat_set_cl_next(fat, cl, CLUST_EOF);
|
|
return (FSFATMOD | ret);
|
|
} else
|
|
return FSERROR;
|
|
}
|
|
return FSOK;
|
|
}
|
|
|
|
static const u_char dot_name[11] = ". ";
|
|
static const u_char dotdot_name[11] = ".. ";
|
|
|
|
/*
|
|
* Basic sanity check if the subdirectory have good '.' and '..' entries,
|
|
* and they are directory entries. Further sanity checks are performed
|
|
* when we traverse into it.
|
|
*/
|
|
static int
|
|
check_subdirectory(struct fat_descriptor *fat, struct dosDirEntry *dir)
|
|
{
|
|
u_char *buf, *cp;
|
|
off_t off;
|
|
cl_t cl;
|
|
int retval = FSOK;
|
|
int fd;
|
|
struct bootblock *boot;
|
|
|
|
boot = fat_get_boot(fat);
|
|
fd = fat_get_fd(fat);
|
|
|
|
cl = dir->head;
|
|
if (dir->parent && !fat_is_valid_cl(fat, cl)) {
|
|
return FSERROR;
|
|
}
|
|
|
|
if (!(boot->flags & FAT32) && !dir->parent) {
|
|
off = boot->bpbResSectors + boot->bpbFATs *
|
|
boot->FATsecs;
|
|
} else {
|
|
off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
|
|
}
|
|
|
|
/*
|
|
* We only need to check the first two entries of the directory,
|
|
* which is found in the first sector of the directory entry,
|
|
* so read in only the first sector.
|
|
*/
|
|
buf = malloc(boot->bpbBytesPerSec);
|
|
if (buf == NULL) {
|
|
perr("No space for directory buffer (%u)",
|
|
boot->bpbBytesPerSec);
|
|
return FSFATAL;
|
|
}
|
|
|
|
off *= boot->bpbBytesPerSec;
|
|
if (lseek(fd, off, SEEK_SET) != off ||
|
|
read(fd, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) {
|
|
perr("Unable to read directory");
|
|
free(buf);
|
|
return FSFATAL;
|
|
}
|
|
|
|
/*
|
|
* Both `.' and `..' must be present and be the first two entries
|
|
* and be ATTR_DIRECTORY of a valid subdirectory.
|
|
*/
|
|
cp = buf;
|
|
if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 ||
|
|
(cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
|
|
pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name);
|
|
retval |= FSERROR;
|
|
}
|
|
cp += 32;
|
|
if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 ||
|
|
(cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
|
|
pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name);
|
|
retval |= FSERROR;
|
|
}
|
|
|
|
free(buf);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* 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(struct fat_descriptor *fat, struct dosDirEntry *dir)
|
|
{
|
|
struct bootblock *boot;
|
|
struct dosDirEntry dirent, *d;
|
|
u_char *p, *vallfn, *invlfn, *empty;
|
|
off_t off;
|
|
int fd, i, j, k, iosize, entries;
|
|
bool is_legacyroot;
|
|
cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
|
|
char *t;
|
|
u_int lidx = 0;
|
|
int shortSum;
|
|
int mod = FSOK;
|
|
size_t dirclusters;
|
|
#define THISMOD 0x8000 /* Only used within this routine */
|
|
|
|
boot = fat_get_boot(fat);
|
|
fd = fat_get_fd(fat);
|
|
|
|
cl = dir->head;
|
|
if (dir->parent && (!fat_is_valid_cl(fat, cl))) {
|
|
/*
|
|
* Already handled somewhere else.
|
|
*/
|
|
return FSOK;
|
|
}
|
|
shortSum = -1;
|
|
vallfn = invlfn = empty = NULL;
|
|
|
|
/*
|
|
* If we are checking the legacy root (for FAT12/FAT16),
|
|
* we will operate on the whole directory; otherwise, we
|
|
* will operate on one cluster at a time, and also take
|
|
* this opportunity to examine the chain.
|
|
*
|
|
* Derive how many entries we are going to encounter from
|
|
* the I/O size.
|
|
*/
|
|
is_legacyroot = (dir->parent == NULL && !(boot->flags & FAT32));
|
|
if (is_legacyroot) {
|
|
iosize = boot->bpbRootDirEnts * 32;
|
|
entries = boot->bpbRootDirEnts;
|
|
} else {
|
|
iosize = boot->bpbSecPerClust * boot->bpbBytesPerSec;
|
|
entries = iosize / 32;
|
|
mod |= checkchain(fat, dir->head, &dirclusters);
|
|
}
|
|
|
|
do {
|
|
if (is_legacyroot) {
|
|
/*
|
|
* Special case for FAT12/FAT16 root -- read
|
|
* in the whole root directory.
|
|
*/
|
|
off = boot->bpbResSectors + boot->bpbFATs *
|
|
boot->FATsecs;
|
|
} else {
|
|
/*
|
|
* Otherwise, read in a cluster of the
|
|
* directory.
|
|
*/
|
|
off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
|
|
}
|
|
|
|
off *= boot->bpbBytesPerSec;
|
|
if (lseek(fd, off, SEEK_SET) != off ||
|
|
read(fd, buffer, iosize) != iosize) {
|
|
perr("Unable to read directory");
|
|
return FSFATAL;
|
|
}
|
|
|
|
for (p = buffer, i = 0; i < entries; 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(fat,
|
|
empcl, empty - buffer,
|
|
cl, p - buffer, 1) == FSFATAL)
|
|
return FSFATAL;
|
|
q = ((empcl == cl) ? empty : buffer);
|
|
assert(q != NULL);
|
|
for (; q < p; q += 32)
|
|
*q = SLOT_DELETED;
|
|
mod |= THISMOD|FSDIRMOD;
|
|
} else if (ask(0, "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;
|
|
if (lidx == 0) {
|
|
pwarn("invalid long name\n");
|
|
if (!invlfn) {
|
|
invlfn = vallfn;
|
|
invcl = valcl;
|
|
}
|
|
vallfn = NULL;
|
|
continue;
|
|
}
|
|
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 (k < 0 || 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(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) {
|
|
strlcpy(dirent.lname, longName,
|
|
sizeof(dirent.lname));
|
|
longName[0] = '\0';
|
|
shortSum = -1;
|
|
}
|
|
|
|
dirent.parent = dir;
|
|
dirent.next = dir->child;
|
|
|
|
if (invlfn) {
|
|
mod |= k = removede(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;
|
|
}
|
|
|
|
vallfn = NULL; /* not used any longer */
|
|
invlfn = NULL;
|
|
|
|
/*
|
|
* Check if the directory entry is sane.
|
|
*
|
|
* '.' and '..' are skipped, their sanity is
|
|
* checked somewhere else.
|
|
*
|
|
* For everything else, check if we have a new,
|
|
* valid cluster chain (beginning of a file or
|
|
* directory that was never previously claimed
|
|
* by another file) when it's a non-empty file
|
|
* or a directory. The sanity of the cluster
|
|
* chain is checked at a later time when we
|
|
* traverse into the directory, or examine the
|
|
* file's directory entry.
|
|
*
|
|
* The only possible fix is to delete the entry
|
|
* if it's a directory; for file, we have to
|
|
* truncate the size to 0.
|
|
*/
|
|
if (!(dirent.flags & ATTR_DIRECTORY) ||
|
|
(strcmp(dirent.name, ".") != 0 &&
|
|
strcmp(dirent.name, "..") != 0)) {
|
|
if ((dirent.size != 0 || (dirent.flags & ATTR_DIRECTORY)) &&
|
|
((!fat_is_valid_cl(fat, dirent.head) ||
|
|
!fat_is_cl_head(fat, dirent.head)))) {
|
|
if (!fat_is_valid_cl(fat, dirent.head)) {
|
|
pwarn("%s starts with cluster out of range(%u)\n",
|
|
fullpath(&dirent),
|
|
dirent.head);
|
|
} else {
|
|
pwarn("%s doesn't start a new cluster chain\n",
|
|
fullpath(&dirent));
|
|
}
|
|
|
|
if (dirent.flags & ATTR_DIRECTORY) {
|
|
if (ask(0, "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;
|
|
dirent.head = 0;
|
|
mod |= THISMOD|FSDIRMOD;
|
|
} else
|
|
mod |= FSERROR;
|
|
}
|
|
}
|
|
}
|
|
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\n",
|
|
dirent.name, fullpath(dir));
|
|
if (ask(1, "Remove")) {
|
|
*p = SLOT_DELETED;
|
|
mod |= THISMOD|FSDIRMOD;
|
|
} else
|
|
mod |= FSERROR;
|
|
continue;
|
|
} else if ((check_subdirectory(fat,
|
|
&dirent) & FSERROR) == FSERROR) {
|
|
/*
|
|
* A subdirectory should have
|
|
* a dot (.) entry and a dot-dot
|
|
* (..) entry of ATTR_DIRECTORY,
|
|
* we will inspect further when
|
|
* traversing into it.
|
|
*/
|
|
if (ask(1, "Remove")) {
|
|
*p = SLOT_DELETED;
|
|
mod |= THISMOD|FSDIRMOD;
|
|
} else
|
|
mod |= FSERROR;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* create directory tree node */
|
|
if (!(d = newDosDirEntry())) {
|
|
perr("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())) {
|
|
perr("No space for todo list");
|
|
return FSFATAL;
|
|
}
|
|
n->next = pendingDirectories;
|
|
n->dir = d;
|
|
pendingDirectories = n;
|
|
} else {
|
|
mod |= k = checksize(fat, p, &dirent);
|
|
if (k & FSDIRMOD)
|
|
mod |= THISMOD;
|
|
}
|
|
boot->NumFiles++;
|
|
}
|
|
|
|
if (is_legacyroot) {
|
|
/*
|
|
* Don't bother to write back right now because
|
|
* we may continue to make modification to the
|
|
* non-FAT32 root directory below.
|
|
*/
|
|
break;
|
|
} else if (mod & THISMOD) {
|
|
if (lseek(fd, off, SEEK_SET) != off
|
|
|| write(fd, buffer, iosize) != iosize) {
|
|
perr("Unable to write directory");
|
|
return FSFATAL;
|
|
}
|
|
mod &= ~THISMOD;
|
|
}
|
|
} while (fat_is_valid_cl(fat, (cl = fat_get_cl_next(fat, cl))));
|
|
if (invlfn || vallfn)
|
|
mod |= removede(fat,
|
|
invlfn ? invlfn : vallfn, p,
|
|
invlfn ? invcl : valcl, -1, 0,
|
|
fullpath(dir), 1);
|
|
|
|
/*
|
|
* The root directory of non-FAT32 filesystems is in a special
|
|
* area and may have been modified above removede() without
|
|
* being written out.
|
|
*/
|
|
if ((mod & FSDIRMOD) && is_legacyroot) {
|
|
if (lseek(fd, off, SEEK_SET) != off
|
|
|| write(fd, buffer, iosize) != iosize) {
|
|
perr("Unable to write directory");
|
|
return FSFATAL;
|
|
}
|
|
mod &= ~THISMOD;
|
|
}
|
|
return mod & ~THISMOD;
|
|
}
|
|
|
|
int
|
|
handleDirTree(struct fat_descriptor *fat)
|
|
{
|
|
int mod;
|
|
|
|
mod = readDosDirSection(fat, 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(fat, 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 off_t lfoff;
|
|
|
|
int
|
|
reconnect(struct fat_descriptor *fat, cl_t head, size_t length)
|
|
{
|
|
struct bootblock *boot = fat_get_boot(fat);
|
|
struct dosDirEntry d;
|
|
int len, dosfs;
|
|
u_char *p;
|
|
|
|
dosfs = fat_get_fd(fat);
|
|
|
|
if (!ask(1, "Reconnect"))
|
|
return FSERROR;
|
|
|
|
if (!lostDir) {
|
|
for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
|
|
if (!strcmp(lostDir->name, LOSTDIR))
|
|
break;
|
|
}
|
|
if (!lostDir) { /* Create LOSTDIR? XXX */
|
|
pwarn("No %s directory\n", LOSTDIR);
|
|
return FSERROR;
|
|
}
|
|
}
|
|
if (!lfbuf) {
|
|
lfbuf = malloc(boot->ClusterSize);
|
|
if (!lfbuf) {
|
|
perr("No space for buffer");
|
|
return FSFATAL;
|
|
}
|
|
p = NULL;
|
|
} else
|
|
p = lfbuf;
|
|
while (1) {
|
|
if (p)
|
|
for (; p < lfbuf + boot->ClusterSize; p += 32)
|
|
if (*p == SLOT_EMPTY
|
|
|| *p == SLOT_DELETED)
|
|
break;
|
|
if (p && p < lfbuf + boot->ClusterSize)
|
|
break;
|
|
lfcl = p ? fat_get_cl_next(fat, lfcl) : 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 - CLUST_FIRST) * boot->ClusterSize
|
|
+ boot->FirstCluster * boot->bpbBytesPerSec;
|
|
|
|
if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
|
|
|| (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
|
|
perr("could not read LOST.DIR");
|
|
return FSFATAL;
|
|
}
|
|
p = lfbuf;
|
|
}
|
|
|
|
boot->NumFiles++;
|
|
/* Ensure uniqueness of entry here! XXX */
|
|
memset(&d, 0, sizeof d);
|
|
/* worst case -1 = 4294967295, 10 digits */
|
|
len = snprintf(d.name, sizeof(d.name), "%u", head);
|
|
d.flags = 0;
|
|
d.head = head;
|
|
d.size = length * boot->ClusterSize;
|
|
|
|
memcpy(p, d.name, len);
|
|
memset(p + len, ' ', 11 - len);
|
|
memset(p + 11, 0, 32 - 11);
|
|
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);
|
|
if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
|
|
|| (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
|
|
perr("could not write LOST.DIR");
|
|
return FSFATAL;
|
|
}
|
|
return FSDIRMOD;
|
|
}
|
|
|
|
void
|
|
finishlf(void)
|
|
{
|
|
if (lfbuf)
|
|
free(lfbuf);
|
|
lfbuf = NULL;
|
|
}
|