/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* fsck_pcfs -- routines for manipulating directories.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libintl.h>
#include <ctype.h>
#include <time.h>
#include <sys/byteorder.h>
#include "pcfs_common.h"
#include "fsck_pcfs.h"
extern int32_t HiddenClusterCount;
extern int32_t FileClusterCount;
extern int32_t DirClusterCount;
extern int32_t HiddenFileCount;
extern int32_t LastCluster;
extern off64_t PartitionOffset;
extern bpb_t TheBIOSParameterBlock;
extern int ReadOnly;
extern int IsFAT32;
extern int Verbose;
int RootDirModified;
/*
* We have a bunch of routines for handling CHK names. A CHK name is
* simply a file name of the form "FILEnnnn.CHK", where the n's are the
* digits in the numbers from 1 to 9999. There are always four digits
* used, leading zeros are added as necessary.
*
* We use CHK names to link orphaned cluster chains back into the file
* system's root directory under an auspicious name so that the user
* may be able to recover some of their data.
*
* We use these routines to ensure CHK names we use don't conflict
* with any already present in the file system.
*/
static int
{
}
void
{
/* silent failure on bogus value */
return;
}
static void
{
int chknum;
}
static int
{
}
static void
{
int i = 0;
/*
* Sometimes caller doesn't care about keeping track of the path
*/
return;
/*
* Prepend /
*/
if (*theLen < MAXPATHLEN)
/*
* Print out the file name part, but only up to the first
* space.
*/
/*
* When we start seeing spaces we assume that's the
* end of the interesting characters in the name.
*/
break;
}
/*
* Leave now, if we don't have an extension (or room for one)
*/
return;
/*
* Tack on the extension
*/
i = 0;
break;
}
}
static void
{
int i;
for (i = 0; i < PCFNAMESIZE; i++) {
break;
}
for (i = 0; i < PCFEXTSIZE; i++) {
break;
}
}
/*
* sanityCheckSize
* Make sure the size in the directory entry matches what is
* actually allocated. If there is a mismatch, orphan all
* the allocated clusters. Returns SIZE_MATCHED if everything matches
* up, TRUNCATED to indicate truncation was necessary.
*/
static int
struct pcdir **orphanEntry)
{
if (isDir) {
if (sizeFromDir == 0)
return (SIZE_MATCHED);
} else {
return (SIZE_MATCHED);
}
if (fullPathName != NULL) {
}
gettext("Truncating chain due to incorrect size "
"in directory. Size from directory = %u bytes,\n"), sizeFromDir);
if (actualClusterCount == 0) {
gettext("Zero bytes are allocated to the file.\n"));
} else {
gettext("Allocated size in range %llu - %llu bytes.\n"),
(actualClusterCount * bpc));
}
/*
* Use splitChain() to make an orphan that is the entire allocation
* chain.
*/
return (TRUNCATED);
}
static int
struct nameinfo *fullPathName)
{
int savePathNextIteration = 0;
int haveBad = 0;
break;
count++;
if (savePathNextIteration == 1) {
if (fullPathName != NULL)
}
if (isMarkedBad(chain)) {
haveBad = 1;
}
if (isHidden)
else if (isDir)
else
}
/*
* Do a sanity check on the file size in the directory entry.
* This may create an orphaned cluster chain.
*/
/*
* The pre-existing directory entry has been truncated,
* so the chain associated with it no longer has any
* bad clusters. Instead, the new orphan has them.
*/
if (haveBad > 0) {
}
haveBad = 0;
}
return (haveBad);
}
static void
{
int haveBad;
int i;
if (hidden)
else if (dir)
DirCount++;
else
FileCount++;
/*
* Make a copy of the name at this point. We may want it to
* note the original source of an orphaned cluster.
*/
if ((pathCopy =
pathCopy->references = 0;
} else {
}
}
if (Verbose) {
for (i = 0; i < depth; i++)
if (hidden)
else if (dir)
else
if (hidden)
gettext(", %u bytes, start cluster %d"),
}
if (haveBad > 0) {
gettext("Adjusting for bad allocation units in "
"the meta-data of:\n "));
}
}
}
}
static void
{
/*
* XXX eventually depth should be passed to this routine just
* as it is with storeInfoAboutEntry(). If it isn't zero, then
* we've got a bogus directory entry.
*/
if (Verbose) {
}
}
static void
{
/*
* We support these searching operations:
*
* PCFS_FIND_ATTR
* look for the first file with a certain attribute
* (e.g, find all hidden files)
* PCFS_FIND_STATUS
* look for the first file with a certain status
* (e.g., the file has been marked deleted; making
* its directory entry reusable)
* PCFS_FIND_CHKS
* look for all files with short names of the form
* FILENNNN.CHK. These are the file names we give
* to chains of orphaned clusters we relink into the
* file system. This find facility allows us to seek
* out all existing files of this naming form so that
* we may create unique file names for new orphans.
*/
} else if (operation == PCFS_FIND_STATUS &&
}
}
static void
{
} else {
}
}
/*
* visitNodes()
*
* This is the main workhouse routine for traversing pcfs metadata.
* There isn't a lot to the metadata. Basically there is a root
* directory somewhere (either in its own special place outside the
* data area or in a data cluster). The root directory (and all other
* directories) are filled with a number of fixed size entries. An
* entry has the filename and extension, the file's attributes, the
* file's size, and the starting data cluster of the storage allocated
* to the file. To determine which clusters are assigned to the file,
* you start at the starting cluster entry in the FAT, and follow the
* chain of entries in the FAT.
*
* Arguments are:
* fd
* descriptor for accessing the raw file system data
* currentCluster
* original caller supplies the initial starting cluster,
* subsequent recursive calls are made with updated
* cluster numbers for the sub-directories.
* dirData
* pointer to the directory data bytes
* dirDataLen
* size of the whole buffer of data bytes (usually it is
* the size of a cluster, but the root directory on
* FAT12/16 is not necessarily the same size as a cluster).
* depth
* original caller should set it to zero (assuming they are
* starting from the root directory). This number is used to
* change the indentation of file names presented as debug info.
* descend
* boolean indicates if we should descend into subdirectories.
* operation
* what, if any, matching should be performed.
* The PCFS_TRAVERSE_ALL operation is a depth first traversal
* of all nodes in the metadata tree, that tracks all the
* clusters in use (according to the meta-data, at least)
* matchRequired
* value to be matched (if any)
* found
* output parameter
* used to return pointer to a directory entry that matches
* the search requirement
* original caller should pass in a pointer to a NULL pointer.
* lastDirCluster
* output parameter
* if no match found, last cluster num of starting directory
* dirEnd
* output parameter
* if no match found, return parameter stores pointer to where
* new directory entry could be appended to existing directory
* recordPath
* output parameter
* as files are discovered, and directories traversed, this
* buffer is used to store the current full path name.
* pathLen
* output parameter
* this is in the integer length of the current full path name.
*/
static void
{
int withinLongName = 0;
/*
* A directory entry where the first character of the name is
* PCD_UNUSED indicates the end of the directory.
*/
/*
* Handle the special case find operations.
*/
if (*found)
break;
/*
* Are we looking at part of a long file name entry?
* If so, we may need to note the start of the name.
* We don't do any further processing of long file
* name entries.
*
* We also skip deleted entries and the '.' and '..'
* entries.
*/
if (!withinLongName) {
}
dp++;
continue;
/*
* XXX - if we were within a long name, then
* its existence is bogus, because it is not
* attached to any real file.
*/
withinLongName = 0;
dp++;
continue;
}
withinLongName = 0;
if (operation == PCFS_TRAVERSE_ALL)
longStart = 0;
if (*found)
break;
}
dp++;
}
if (*found)
return;
/*
* We reached the end of directory before the end of
* our provided data (a cluster). That means this cluster
* is the last one in this directory's chain. It also
* means we've just looked at the last directory entry.
*/
return;
}
/*
* If there is more to the directory we'll go get it otherwise we
* are done traversing this directory.
*/
if ((currentCluster == FAKE_ROOTDIR_CLUST) ||
(lastInFAT(currentCluster))) {
return;
} else {
}
}
/*
* traverseFromRoot()
* For use with 12 and 16 bit FATs that have a root directory outside
* of the file system. This is a general purpose routine that
* can be used simply to visit all of the nodes in the metadata or
* to find the first instance of something, e.g., the first directory
* entry where the file is marked deleted.
*
* Inputs are described in the commentary for visitNodes() above.
*/
void
{
}
/*
* traverseDir()
* For use with all FATs outside of the initial root directory on
* 12 and 16 bit FAT file systems. This is a general purpose routine
* that can be used simply to visit all of the nodes in the metadata or
* to find the first instance of something, e.g., the first directory
* entry where the file is marked deleted.
*
* Unique Input is:
* startAt
* starting cluster of the directory
*
* This is the cluster that is the first one in this directory.
* We read it right away, so we can provide it as data to visitNodes().
* Note that we cache this cluster as we read it, because it is
* metadata and we cache all metadata. By doing so, we can
* keep pointers to directory entries for quickly moving around and
* fixing up any problems we find. Of course if we get a big
* filesystem with a huge amount of metadata we may be hosed, as
* we'll likely run out of memory.
*
* I believe in the future this will have to be addressed. It
* may be possible to do more of the processing of problems
* within directories as they are cached, so that when memory
* runs short we can free cached directories we are already
* finished visiting.
*
* The remainder of inputs are described in visitNodes() comments.
*/
void
{
return;
RDCLUST_DO_CACHE) != RDCLUST_GOOD) {
gettext("Unable to get more directory entries!\n"));
return;
}
if (operation == PCFS_TRAVERSE_ALL) {
if (Verbose)
gettext("Directory traversal enters "
"allocation unit %d.\n"), startAt);
}
}
void
{
int ignoreint = 0;
return;
/*
* Allocate an array to keep a bit map of the integer
* values used in CHK names.
*/
if ((CHKsList =
OkayToRelink = 0;
return;
}
/*
* Search the root directory for all the files with names of
* the form FILEXXXX.CHK. 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) {
&ignoreint);
} else {
DirCount++;
}
}
char *
{
int i;
if (!OkayToRelink)
return (NULL);
for (i = 1; i <= MAXCHKVAL; i++) {
if (!inUseCHKName(i))
break;
}
if (i <= MAXCHKVAL) {
*chosen = i;
return (nameBuf);
} else {
gettext("Sorry, no names available for "
"relinking orphan chains!\n"));
OkayToRelink = 0;
return (NULL);
}
}
{
return (returnMe);
}
{
if (IsFAT32) {
} else {
}
}
static struct pcdir *
{
int ignore = 0;
*clusterWithSlot = 0;
/*
* First off, try to find an erased entry in the root
* directory. 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) {
} else {
DirCount++;
&ignore);
}
/*
* If we found a deleted file in the directory we'll overwrite
* that entry.
*/
if (deletedEntry)
return (deletedEntry);
/*
* If there is room at the end of the existing directory, we
* should place the new entry there.
*/
if (appendPoint)
return (appendPoint);
/*
* XXX need to grow the directory
*/
return (NULL);
}
static void
{
}
/*
* Convert current UNIX time into a PCFS timestamp (which is in local time).
*
* Since the "seconds" field of that is only accurate to 2sec precision,
* we allow for the optional (used only for creation times on FAT) "msec"
* parameter that takes the fractional part.
*/
static void
{
/*
* Disable daylight savings corrections - Solaris PCFS doesn't
* support such conversions yet. Save timestamps in local time.
*/
daylight = 0;
/*
* Sanity check. If we overflow the PCFS timestamp range
* we set the time to 01/01/1980, 00:00:00
*/
if (msec)
}
/*
* FAT file systems store the following time information in a directory
* entry:
* timestamp member of "struct pcdir"
* ======================================================================
* creation time pcd_crtime.pct_time
* creation date pcd_crtime.pct_date
* last access date pcd_ladate
* last modify time pcd_mtime.pct_time
* last modify date pcd_mtime.pct_date
*
* No access time is kept.
*/
static void
{
}
static void
{
}
struct pcdir *
{
return (added);
}
return (NULL);
}
/*
* FAT12 and FAT16 have a root directory outside the normal file space,
* so we have separate routines for finding and reading the root directory.
*/
static off64_t
{
/*
* The RootDir immediately follows the FATs, which in
* turn immediately follow the reserved sectors.
*/
if (Verbose)
}
void
{
return;
exit(8);
}
if (seekRootDirectory(fd) < 0) {
exit(8);
}
if (Verbose)
gettext("Reading root directory.\n"));
RootDirSize) {
if (bytesRead < 0) {
} else {
gettext("Short read of RootDir\n"));
}
exit(8);
}
if (Verbose) {
gettext("Dump of root dir's first 256 bytes.\n"));
}
}
void
{
if (!TheRootDir.bytes) {
gettext("Internal error: No Root directory to write\n"));
exit(12);
}
if (!RootDirModified) {
if (Verbose) {
gettext("No root directory changes need to "
"be written.\n"));
}
return;
}
if (ReadOnly)
return;
if (Verbose)
gettext("Writing root directory.\n"));
if (seekRootDirectory(fd) < 0) {
exit(12);
}
RootDirSize) {
if (bytesWritten < 0) {
} else {
gettext("Short write of root directory\n"));
}
exit(12);
}
RootDirModified = 0;
}
struct pcdir *
{
"new directory entry!\n"));
return (ndp);
}
if (copyme)
return (ndp);
}
void
{
store_32_bits(&p, newSize);
}
void
{
if (IsFAT32) {
}
}
void
{
int i;
for (i = 0; i < PCFNAMESIZE; i++) {
if (*newName)
else
}
}