/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 1999,2000 by Sun Microsystems, Inc.
* All rights reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* fsck_pcfs -- routines for manipulating clusters.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libintl.h>
#include <errno.h>
#include "pcfs_common.h"
#include "fsck_pcfs.h"
extern ClusterContents TheRootDir;
extern off64_t FirstClusterOffset;
extern off64_t PartitionOffset;
extern int32_t BytesPerCluster;
extern int32_t TotalClusters;
extern int32_t LastCluster;
extern int32_t RootDirSize;
extern bpb_t TheBIOSParameterBlock;
extern short FATEntrySize;
extern int RootDirModified;
extern int OkayToRelink;
extern int ReadOnly;
extern int IsFAT32;
extern int Verbose;
/*
* Internal statistics
*/
static void
{
/* silent failure for bogus clusters */
return;
} else {
}
}
}
static void
{
/* silent failure for bogus clusters */
return;
"allocated to:\n"));
(void) printf("%s\n",
} else {
"allocated to an unknown file or directory:\n"));
"unit %d.\n"), clusterNum);
}
}
static void
{
if (size > 0) {
"allocation units.\n"), size);
if (Verbose) {
"unit %d]\n"), clusterNum);
}
}
}
static void
{
}
static int
{
/*
* If it is not OkayToRelink, we haven't already printed the size
* of the orphaned chain.
*/
if (!OkayToRelink)
/*
* If we are in preen mode, preenBail won't return.
*/
preenBail("Need user confirmation to free orphaned chain.\n");
(void) printf(
gettext("Free the allocation units in the orphaned chain ? "
"(y/n) "));
return (yes());
}
static int
{
/*
* Display the size of the chain for the user to consider.
*/
/*
* If we are in preen mode, preenBail won't return.
*/
preenBail("Need user confirmation to re-link orphaned chain.\n");
"(y/n) "));
return (yes());
}
static int
{
/* silent failure for bogus clusters */
return (0);
return (0);
}
static int
{
/* silent failure for bogus clusters */
return (0);
}
/*
* Caller's may request that we cache the data from a readCluster.
* The xxxClusterxxxCachexxx routines handle looking for cached data
* or initially caching the data.
*
* XXX - facilitate releasing cached data for low memory situations.
*/
static CachedCluster *
{
return (loop);
}
return (NULL);
}
static uchar_t *
{
while (loop) {
}
return (NULL);
}
static uchar_t *
{
return (buf);
}
return (buf);
}
if (Verbose) {
}
if (ClusterCache == NULL) {
ClusterCache = new;
ClusterCache = new;
} else {
}
if (loop) {
} else {
}
}
}
static int
{
int saveError;
gettext("Seek to Allocation unit #%d failed: "),
return (0);
}
return (1);
}
/*
* getcluster
* Get cluster bytes off the disk. We always read those bytes into
* the same static buffer. If the caller wants his own copy of the
* data he'll have to make his own copy. We'll return all the data
* read, even if it's short of a full cluster. This is for future use
* when we might want to relocate any salvagable data from bad clusters.
*/
static int
{
int saveError;
int try;
*datasize = 0;
return (RDCLUST_BADINPUT);
if (clusterBuffer == NULL &&
return (RDCLUST_MEMERR);
}
return (RDCLUST_FAIL);
*data = clusterBuffer;
return (RDCLUST_GOOD);
}
}
if (*datasize >= 0) {
*data = clusterBuffer;
} else {
}
return (RDCLUST_FAIL);
}
static void
{
if (ReadOnly)
return;
if (Verbose)
gettext("Allocation unit %d modified.\n"),
return;
BytesPerCluster)) != BytesPerCluster) {
if (bytesWritten < 0) {
"allocation unit"));
} else {
gettext("Short write of allocation unit %d\n"),
}
exit(13);
}
}
/*
* It's cheaper to allocate a lot at a time; malloc overhead pushes
* you over the brink much more quickly if you don't.
* This numbers seems to be a fair trade-off between reduced malloc overhead
* and additional overhead by over-allocating.
*/
static ClusterInfo *
newClusterInfo(void)
{
int i;
gettext("Out of memory for cluster information"));
exit(9);
}
for (i = 0; i < CHUNKSIZE - 1; i++)
}
return (ret);
}
/* Should be called with verified arguments */
static ClusterInfo *
{
}
}
static void
{
}
static void
{
}
}
}
/*
* Allocate entries in our sparse array of cluster information.
* Returns non-zero if the structure already has been allocated
* (for those keeping score at home).
*
* The template parameter, if non-NULL, is used to facilitate sharing
* the ClusterInfo nodes for the clusters belonging to the same file.
* The first call to allocInUse for a new file should have *template
* set to 0; on return, *template then points to the newly allocated
* ClusterInfo. Second and further calls keep the same value
* in *template and that ClusterInfo ndoe is then used for all
* entries in the file. Code that modifies the ClusterInfo nodes
* should take care proper sharing semantics are maintained (i.e.,
* copy-on-write using cloneClusterInfo())
*
* The ClusterInfo used in the template is guaranted to be in use in
* at least one other cluster as we never return a value if we didn't
* set it first. So we can overwrite it without the possibility of a leak.
*/
static int
{
return (CLINFO_PREVIOUSLY_ALLOCED);
else {
newCl = newClusterInfo();
if (template)
}
return (CLINFO_NEWLY_ALLOCED);
}
static void
{
/* silent failure for bogus clusters */
return;
}
}
static void
{
/* silent failure for bogus clusters */
return;
}
static void
{
/* silent failure for bogus clusters */
return;
if (recoveredLen) {
(void) cloneClusterInfo(clusterNum);
}
if (Verbose)
}
static void
{
/* silent failure for bogus clusters */
if (c < FIRST_CLUSTER || c > LastCluster)
return;
updateFlags(c,
}
static void
{
/* silent failure for bogus clusters */
if (c < FIRST_CLUSTER || c > LastCluster)
return;
}
}
static void
{
clearInUse(cc);
}
}
static void
makeUseTable(void)
{
return;
}
if ((InUse = (ClusterInfo **)
exit(9);
}
}
static void
countClusters(void)
{
int32_t c;
for (c = FIRST_CLUSTER; c < LastCluster; c++) {
if (badInFAT(c)) {
} else if (isMarkedBad(c)) {
/*
* This catches the bad sectors found
* during thorough verify that have never been
* allocated to a file. Without this check, we
* count these guys as free.
*/
markBadInFAT(c);
} else if (isHidden(c)) {
} else if (isInUse(c)) {
} else {
}
}
}
/*
* summarizeFAT
* Mark orphans without directory entries as allocated.
* XXX - these chains should be reclaimed!
* XXX - merge this routine with countClusters (same loop, duh.)
*/
static void
{
int32_t c;
for (c = FIRST_CLUSTER; c < LastCluster; c++) {
!isInUse(c)) {
&tmpl);
}
}
}
static void
{
if (!IsFAT32)
}
static void
{
char ignore;
int pathlen;
ReservedClusterCount = 0;
AllocedClusterCount = 0;
HiddenClusterCount = 0;
FileClusterCount = 0;
FreeClusterCount = 0;
DirClusterCount = 0;
BadClusterCount = 0;
HiddenFileCount = 0;
FileCount = 0;
DirCount = 0;
ignore = '\0';
PathName[0] = '\0';
pathlen = 0;
/*
* Traverse the full meta-data tree to talley what clusters
* are in use. The root directory is an area outside of the
* file space on FAT12 and FAT16 file systems. On FAT32 file
* systems, the root directory is in a file area cluster just
* like any other directory.
*/
if (!IsFAT32) {
&pathlen);
} else {
DirCount++;
}
if (includeFAT)
}
int
{
/* silent failure for bogus clusters */
return (0);
return (0);
}
static int
{
/* silent failure for bogus clusters */
return (0);
return (0);
}
static void
{
/* silent failure for bogus clusters */
if (c < FIRST_CLUSTER || c > LastCluster)
return;
clearInUse(c);
c = nextInChain(c);
while (c != 0) {
clearInUse(c);
clearOrphan(c);
c = nextInChain(c);
}
}
static int32_t
{
for (;;) {
break;
}
if (look == LastCluster)
else
look++;
break;
}
return (look);
else
return (0);
}
static void
{
}
static void
{
RDCLUST_DO_CACHE) != RDCLUST_GOOD) {
gettext("Unable to read allocation unit %d.\n"),
gettext("Cannot allocate a new allocation unit to hold an"
" end-of-directory marker.\nCannot access allocation unit"
" to overwrite existing directory entry with\nthe marker."
" Needed directory truncation has failed. Giving up.\n"));
exit(11);
}
}
static void
{
/*
* There are two scenarios. One is that we truncated the
* directory in the very beginning. The other is that we
* truncated it in the middle or at the end. In the first
* scenario, the secondToLast argument is not a valid cluster
* (it's zero), and so we actually need to change the start
* cluster for the directory to this new start cluster. In
* the second scenario, the secondToLast cluster we received
* as an argument needs to be pointed at the new end of
* directory.
*/
if (secondToLast == 0) {
} else {
}
}
static void
{
if (Verbose) {
gettext("Grabbed allocation unit #%d "
"for truncated\ndirectory's new end "
"of directory.\n"), freeCluster);
}
freeCluster, &dirdata);
return;
}
}
if (secondToLast == 0) {
if (freeCluster == 0) {
} else {
gettext("Unable to read allocation unit %d.\n"),
}
gettext("Cannot allocate a new allocation unit to hold "
"an end-of-directory marker.\nNo existing directory "
"entries can be overwritten with the marker,\n"
"the only unit allocated to the directory is "
"inaccessible.\nNeeded directory truncation has failed. "
"Giving up.\n"));
exit(11);
}
}
/*
* truncAtCluster
* Given a directory entry and a cluster number, search through
* the cluster chain for the entry and make the cluster previous
* to the given cluster in the chain the last cluster in the file.
* The number of orphaned bytes is returned. For a chain that's
* a directory we need to do some special handling, since we'll be
* getting rid of the end of directory notice by truncating.
*/
static int64_t
{
follow <= LastCluster) {
count++;
}
/*
* We didn't find the cluster they wanted to trunc at
* anywhere in the entry's chain. So we'll leave the
* entry alone, and return a negative value so they
* can know something is wrong.
*/
return (-1);
}
if (Verbose) {
}
if (!dir) {
if (newSize == 0)
updateDirEnt_Start(entry, 0);
} else {
newSize = 0;
}
if (dir) {
} else if (prev != 0) {
}
if (dir) {
/*
* We don't really know what the size of a directory is
* but it is important for us to know if this truncation
* results in an orphan with any size. The value we
* return from this routine for a normal file is the
* number of bytes left in the chain. For a directory
* we can't be exact, and the caller doesn't really
* expect us to be. For a directory the caller only
* cares if there are zero bytes left or more than
* zero bytes left. We'll return 1 to indicate
* more than zero.
*/
return (1);
else
return (0);
}
/*
* newSize should always be smaller than the old one, since
* we are decreasing the number of clusters allocated to the file.
*/
}
static struct pcdir *
int isBad)
{
int chosenName;
/*
* If the truncation fails, (which ought not to happen),
* there's no need to go any further, we just return
* a null value for the new directory entry pointer.
*/
if (remainder < 0)
return (ndp);
/*
* Subtract out the bad cluster from the remaining size
* We always assume the cluster being deleted from the
* file is full size, but that might not be the case
* for the last cluster of the file, so that is why
* we check for negative remainder value.
*/
if (remainder < 0)
remainder = 0;
}
/*
* Build a new directory entry for the rest of the chain.
* Later, if the user okays it, we'll link this entry into the
* root directory. The new entry will start out as a
* copy of the truncated entry.
*/
if ((remainder != 0) &&
if (Verbose) {
if (dir)
gettext("Orphaned directory chain.\n"));
else
gettext("Orphaned chain, %u bytes.\n"),
}
if (!dir)
if (isBad)
else
}
return (ndp);
}
/*
* splitChain()
*
* split a cluster allocation chain into two cluster chains
* around a given cluster (problemCluster). This results in two
* separate directory entries; the original (dp), and one we hope
* to create and return a pointer to to the caller (*newdp).
* This second entry is the orphan chain, and it may end up in
* the root directory as a FILEnnnn.CHK file. We also return the
* starting cluster of the orphan chain to the caller (*orphanStart).
*/
void
{
if (isBad) {
} else {
}
}
/*
* freeOrphan
*
* User has requested that an orphaned cluster chain be freed back
* into the file area.
*/
static void
{
int32_t n;
/*
* Free the directory entry we explicitly created for
* the orphaned clusters.
*/
/*
* Then mark the clusters themselves as available.
*/
do {
n = nextInChain(c);
markFreeInFAT(c);
markFree(c);
c = n;
} while (c != 0);
}
/*
* Rewrite the InUse field for a cluster chain. Can be used on a partial
* chain if provided with a stopAtCluster.
*/
static void
{
while (c && c != stopAtCluster) {
clearInUse(c);
c = nextInChain(c);
}
}
static struct pcdir *
{
return (NULL);
if (isInUse(clusterNum)) {
} else {
return (NULL);
}
}
static int32_t
{
/* silent failure for bogus clusters */
return (-1);
if (isInUse(clusterNum)) {
} else {
return (-1);
}
}
/*
* linkOrphan
*
* User has requested that an orphaned cluster chain be brought back
* into the file system. So we have to make a new directory entry
* in the root directory and point it at the cluster chain.
*/
static void
{
} else {
" Allocation units will remain orphaned.\n"));
}
/*
* A cluster isn't really InUse() unless it is referenced,
* so if newEnt is NULL here, we are in effect using markInUse()
* to note that the cluster is NOT in use.
*/
}
/*
* relinkCreatedOrphans
*
* While marking clusters as bad, we can create orphan cluster
* chains. Since we were the ones doing the marking, we were able to
* keep track of the orphans we created. Now we want to go through
* all those chains and either get them back into the file system or
* free them depending on the user's input.
*/
static void
{
int32_t c;
for (c = FIRST_CLUSTER; c < LastCluster; c++) {
if (isMarkedOrphan(c)) {
if (OkayToRelink && askAboutRelink(c)) {
linkOrphan(fd, c);
} else if (askAboutFreeing(c)) {
freeOrphan(c);
}
clearOrphan(c);
}
}
}
/*
* relinkFATOrphans
*
* We want to find orphans not represented in the meta-data.
* These are chains marked in the FAT as being in use but
* not referenced anywhere by any directory entries.
* We'll go through the whole FAT and mark the first cluster
* in any such chain as an orphan. Then we can just use
* the relinkCreatedOrphans routine to get them back into the
* file system or free'ed depending on the user's input.
*/
static void
{
char *newName;
int chosenName;
for (c = FIRST_CLUSTER; c < LastCluster; c++) {
reservedInFAT(c) || isInUse(c))
continue;
cc = 1;
n = c;
while (n = nextInChain(n))
cc++;
updateDirEnt_Start(ndp, c);
}
}
}
static void
{
}
static void
{
return;
follow <= LastCluster) {
}
if (follow == clusterNum) {
/*
* We found a loop. Eradicate it by changing
* the last cluster in the loop to be last
* in the chain instead instead of pointing
* back to the first cluster.
*/
}
}
static void
{
/*
* If we have shared clusters, it is either because the
* because of a loop in the cluster chain. In either
* case we want to truncate the offending file at the
* cluster of contention. Then, we will want to run
* through the remainder of the chain. If we find ourselves
* back at the top, we will know there is a loop in the
* FAT we need to remove.
*/
if (Verbose)
gettext("Truncating chain due to duplicate allocation of "
"unit %d.\n"), clusterNum);
/*
* Note that we don't orphan anything here, because the duplicate
* part of the chain may be part of another valid chain.
*/
}
void
{
int32_t c = startCluster;
while (c != 0) {
if (isMarkedBad(c)) {
/*
* splitChain() truncates the current guy and
* then makes an orphan chain out of the remaining
* clusters. When we come back from the split
* we'll want to continue looking for bad clusters
* in the orphan chain.
*/
/*
* There is a chance that we weren't able or weren't
* required to make a directory entry for the
* remaining clusters. In that case we won't go
* on, because we couldn't make any more splits
* anyway.
*/
if (orphanEntry == NULL)
break;
c = orphanStartCluster;
dp = orphanEntry;
continue;
}
c = nextInChain(c);
}
}
{
/* silent failure for bogus clusters */
return (0);
/*
* Look up FAT entry of next link in cluster chain,
* if this one is the last one return 0 as the next link.
*/
return (0);
return (nextCluster);
}
/*
* findImpactedCluster
*
* Called when someone modifies what they believe might be a cached
* cluster entry, but when they only have a directory entry pointer
* and not the cluster number. We have to go dig up what cluster
* they are modifying.
*/
{
/*
* Check to see if it's in the root directory first
*/
return (FAKE_ROOTDIR_CLUST);
loop = ClusterCache;
while (loop) {
return (loop->clusterNum);
}
}
/*
* Guess it wasn't cached after all...
*/
return (0);
}
void
{
while (loop) {
}
}
void
{
/* silent failure for bogus clusters */
return;
return;
}
int
{
int alreadyMarked;
/* silent failure for bogus clusters */
return (CLINFO_NEWLY_ALLOCED);
if ((alreadyMarked == CLINFO_PREVIOUSLY_ALLOCED) &&
(isInUse(clusterNum))) {
return (CLINFO_PREVIOUSLY_ALLOCED);
}
/*
* If Cl is newly allocated (refcnt <= 1) we must fill in the fields.
* If Cl has different fields, we must clone it.
*/
if (isHiddenFile)
/*
* Return cl as the template to use for other clusters in
* this file
*/
if (template)
}
return (CLINFO_NEWLY_ALLOCED);
}
void
{
CachedCluster *c;
if (clusterNum == FAKE_ROOTDIR_CLUST) {
RootDirModified = 1;
return;
}
/* silent failure for bogus clusters */
return;
if (c = findClusterCacheEntry(clusterNum)) {
c->modified = 1;
} else {
gettext("Unexpected internal error: "
"Missing cache entry [%d]\n"), clusterNum);
exit(10);
}
}
/*
* readCluster
* caller wants to read cluster clusterNum. We should return
* a pointer to the read data in "data", and fill in the number
* of bytes read in "datasize". If shouldCache is non-zero
* we should allocate cache space to the cluster, otherwise we
* just return a pointer to a buffer we re-use whenever cacheing
* is not requested.
*/
int
int shouldCache)
{
int rv;
return (RDCLUST_GOOD);
}
if (rv != RDCLUST_GOOD)
return (rv);
/*
* Caller requested we NOT cache the data from this read.
* So, we just return a pointer to the common data buffer.
*/
if (shouldCache == 0) {
return (rv);
}
/*
* Caller requested we cache the data from this read.
* So, if we have some data, add it to the cache by
* copying it out of the common buffer into new storage.
*/
if (*datasize > 0)
return (rv);
}
void
{
BadClusterCount = 0;
makeUseTable();
for (clusterCount = FIRST_CLUSTER;
if (Verbose)
gettext("\nUnreadable allocation unit %d.\n"),
}
/*
* Progress meter, display a '.' for every 1000 clusters
* processed. We don't want to display this when
* we are in verbose mode; verbose mode progress is
* shown by displaying each file name as it is found.
*/
(void) printf(".");
}
}
void
{
/*
* First we initialize a few things.
*/
makeUseTable();
/*
* Make initial scan, taking into account any effect that
* the bad clusters we may have already discovered have
* on meta-data. We may break up some cluster chains
* during this period. The relinkCreatedOrphans() call
* will then give the user the chance to recover stuff
* we've created.
*/
if (Verbose)
/*
* Clear our usage table and go back over everything, this
* time including looking for clusters floating free in the FAT.
* This may include clusters the user chose to free during the
* relink phase.
*/
makeUseTable();
}
void
{
gettext("%llu bytes.\n"),
(uint64_t)
gettext("%llu bytes in bad sectors.\n"),
(uint64_t)
gettext("%llu bytes in %d directories.\n"),
(uint64_t)
if (HiddenClusterCount) {
gettext("%llu bytes in %d hidden files.\n"),
}
gettext("%llu bytes in %d files.\n"),
(uint64_t)
gettext("%d bytes per allocation unit.\n"),
if (ReservedClusterCount)
}