1497 lines
32 KiB
C++
1497 lines
32 KiB
C++
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
chksis.cpp
|
|
|
|
Abstract:
|
|
|
|
This module implements a utility that examines all SIS files on a volume
|
|
looking for errors and optionally displaying file information.
|
|
|
|
Author:
|
|
|
|
Scott Cutshall Fall, 1997
|
|
|
|
--*/
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <ntioapi.h>
|
|
|
|
#include <windows.h>
|
|
|
|
using namespace std;
|
|
|
|
bool verbose = false;
|
|
|
|
typedef LONGLONG INDEX;
|
|
|
|
//
|
|
// Convert a 32bit value to a base 36 representation in
|
|
// the caller provided string.
|
|
//
|
|
|
|
void IntegerToBase36String(ULONG val, string& s) {
|
|
|
|
//
|
|
// Maximum number of "digits" in a base 36 representation of a 32 bit
|
|
// value is 7.
|
|
//
|
|
|
|
char rs[8];
|
|
ULONG v = val;
|
|
|
|
rs[7] = 0;
|
|
|
|
for (int i = 7; i == 7 || v != 0;) {
|
|
|
|
ULONG d = v % 36;
|
|
v = v / 36;
|
|
|
|
--i;
|
|
if (d < 10)
|
|
rs[i] = '0' + d;
|
|
else
|
|
rs[i] = 'a' + d - 10;
|
|
|
|
}
|
|
|
|
s.assign(&rs[i]);
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// A put operator for INDEX types. Implemented as IndexToSISFileName().
|
|
//
|
|
|
|
#ifndef _WIN64
|
|
ostream& operator<<(ostream& out, INDEX& index)
|
|
{
|
|
|
|
unsigned long lo = static_cast<unsigned long> (index);
|
|
long hi = static_cast<long> (index >> 32);
|
|
string s("1234567");
|
|
|
|
IntegerToBase36String(lo, s);
|
|
|
|
out << s << '.';
|
|
|
|
IntegerToBase36String(hi, s);
|
|
|
|
out << s;
|
|
|
|
return out;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// A common store file object. Holds the file's index, name, internal refcount,
|
|
// external refcount, and identity operations.
|
|
//
|
|
|
|
class CsFile {
|
|
|
|
public:
|
|
|
|
CsFile(INDEX i = 0, int r = 0, string n = "") :
|
|
index(i), internalRefCount(r), name(n), externalRefCount(0) {}
|
|
|
|
void Validate() {
|
|
if (internalRefCount != externalRefCount) {
|
|
cout << name << " Reference Count: " << internalRefCount;
|
|
cout << ". " << externalRefCount << " external references identified." << endl;
|
|
}
|
|
}
|
|
|
|
friend bool operator<(const CsFile& a, const CsFile& b) {
|
|
return a.index < b.index;
|
|
}
|
|
|
|
friend bool operator>(const CsFile& a, const CsFile& b) {
|
|
return a.index > b.index;
|
|
}
|
|
|
|
friend bool operator==(const CsFile& a, const CsFile& b) {
|
|
return a.index == b.index;
|
|
}
|
|
|
|
void IncRefCount() {
|
|
++externalRefCount;
|
|
}
|
|
|
|
void display() {
|
|
cout << "CS Index: " << (INDEX) index << " Ref Count: " << internalRefCount << endl;
|
|
}
|
|
|
|
private:
|
|
|
|
//
|
|
// Index of this entry's file.
|
|
//
|
|
|
|
INDEX index;
|
|
|
|
//
|
|
// The file name. This is somewhat redundant with the index (ie. the
|
|
// name is derived from the index), so it isn't absolutely necessary.
|
|
//
|
|
|
|
string name;
|
|
|
|
//
|
|
// Reference count read from the file's refcount stream.
|
|
//
|
|
|
|
int internalRefCount;
|
|
|
|
//
|
|
// Number of valid references to this file detected during scan.
|
|
//
|
|
|
|
int externalRefCount;
|
|
|
|
};
|
|
|
|
|
|
//
|
|
// The SIS Common Store object. Holds all common store file objects, and
|
|
// validation and query operations.
|
|
//
|
|
|
|
class CommonStore {
|
|
|
|
public:
|
|
|
|
CommonStore(int vsize = 0) : maxIndex(0) {
|
|
if (vsize > 0) csFiles.resize(vsize);
|
|
}
|
|
|
|
//
|
|
// Method to create a common store on a volume.
|
|
//
|
|
|
|
bool Create(string& Volume);
|
|
|
|
//
|
|
// Validate the common store directory and initialize this class.
|
|
//
|
|
|
|
void Validate(string& Volume);
|
|
|
|
//
|
|
// Validate the reference counts. Assumes all external references
|
|
// have been identified.
|
|
//
|
|
|
|
void ValidateRefCounts();
|
|
|
|
//
|
|
// All indices must be less than maxIndex;
|
|
//
|
|
|
|
bool ValidateIndex(INDEX i) {
|
|
return i <= maxIndex;
|
|
}
|
|
|
|
//
|
|
// Lookup a common store index and add a ref if found.
|
|
//
|
|
|
|
CsFile *Query(INDEX index);
|
|
|
|
private:
|
|
|
|
bool FileNameToIndex(string& fileName, INDEX& csIndex);
|
|
|
|
//
|
|
// Index from the MaxIndex file.
|
|
//
|
|
|
|
INDEX maxIndex;
|
|
|
|
//
|
|
// Database of content files. All CS files are examined and added to the database,
|
|
// sorted, and subsequently used during the SIS link scan.
|
|
//
|
|
|
|
vector<CsFile> csFiles;
|
|
|
|
};
|
|
|
|
//
|
|
// Various SIS file and directory names.
|
|
//
|
|
|
|
const string maxIndexFileName("MaxIndex");
|
|
const string logFileName("LogFile");
|
|
const string csDir("\\SIS Common Store\\");
|
|
|
|
//
|
|
// Create a common store directory on a volume.
|
|
//
|
|
// todo:
|
|
// - Verify that the volume is ntfs.
|
|
// - Verify that the SIS driver is loaded.
|
|
//
|
|
|
|
bool
|
|
CommonStore::Create(string& Volume)
|
|
{
|
|
const string CommonStoreDir = Volume + "\\SIS Common Store";
|
|
USHORT comp = COMPRESSION_FORMAT_DEFAULT;
|
|
DWORD transferCount;
|
|
bool rc;
|
|
|
|
if (! CreateDirectory(CommonStoreDir.c_str(), NULL) ) {
|
|
|
|
cout << "Cannot create Common Store directory, " << GetLastError() << endl;
|
|
return false;
|
|
|
|
}
|
|
|
|
if (verbose)
|
|
cout << CommonStoreDir << " created" << endl;
|
|
|
|
//
|
|
// Open the Common Store directory and enable compression.
|
|
//
|
|
|
|
HANDLE CSDirHandle = CreateFile(
|
|
CommonStoreDir.c_str(),
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL);
|
|
|
|
if (CSDirHandle == INVALID_HANDLE_VALUE) {
|
|
|
|
cout << "Can't open Common Store directory." << endl;
|
|
rc = false;
|
|
|
|
} else {
|
|
|
|
rc = 0 != DeviceIoControl(
|
|
CSDirHandle,
|
|
FSCTL_SET_COMPRESSION,
|
|
&comp,
|
|
sizeof(comp),
|
|
NULL,
|
|
0,
|
|
&transferCount,
|
|
NULL);
|
|
|
|
CloseHandle(CSDirHandle);
|
|
|
|
}
|
|
|
|
if (!rc)
|
|
cout << "Cannot enable compression on Common Store directory, " << GetLastError() << endl;
|
|
|
|
//
|
|
// Chdir into the common store directory.
|
|
//
|
|
|
|
if (SetCurrentDirectory(CommonStoreDir.c_str()) == 0) {
|
|
|
|
//
|
|
// Unable to chdir into the common store.
|
|
//
|
|
|
|
cout << "\"\\SIS Common Store\" directory not found" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
rc = true;
|
|
|
|
//
|
|
// Create the MaxIndex file.
|
|
//
|
|
|
|
HANDLE hMaxIndex = CreateFile(
|
|
maxIndexFileName.c_str(),
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_NEW,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
|
|
if (hMaxIndex == INVALID_HANDLE_VALUE) {
|
|
|
|
cout << "Can't create \"\\SIS Common Store\\MaxIndex\"" << endl;
|
|
|
|
rc = false;
|
|
|
|
} else {
|
|
|
|
DWORD bytesWritten;
|
|
|
|
maxIndex = 1;
|
|
|
|
if (! WriteFile(
|
|
hMaxIndex,
|
|
&maxIndex,
|
|
sizeof maxIndex,
|
|
&bytesWritten,
|
|
NULL) ||
|
|
(bytesWritten < sizeof maxIndex)) {
|
|
|
|
cout << "Can't write MaxIndex, " << GetLastError() << endl;
|
|
|
|
rc = false;
|
|
|
|
} else {
|
|
|
|
CloseHandle(hMaxIndex);
|
|
|
|
if (verbose)
|
|
cout << "MaxIndex: " << (INDEX) maxIndex << endl;
|
|
|
|
rc = true;
|
|
}
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Validate the common store directory.
|
|
//
|
|
|
|
void
|
|
CommonStore::Validate(string& Volume)
|
|
{
|
|
|
|
WIN32_FIND_DATA findData;
|
|
HANDLE findHandle;
|
|
const string fileNameMatchAny = "*";
|
|
const string CommonStoreDir = Volume + "\\SIS Common Store";
|
|
|
|
cout << "Checking Common Store" << endl;
|
|
|
|
//
|
|
// Chdir into the common store directory.
|
|
//
|
|
|
|
if (SetCurrentDirectory(CommonStoreDir.c_str()) == 0) {
|
|
|
|
//
|
|
// Unable to chdir into the common store.
|
|
//
|
|
|
|
cout << "\"\\SIS Common Store\" directory not found" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//
|
|
// Validate and read the contents of the MaxIndex file.
|
|
//
|
|
|
|
HANDLE hMaxIndex = CreateFile(
|
|
maxIndexFileName.c_str(),
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
|
|
if (hMaxIndex == INVALID_HANDLE_VALUE) {
|
|
|
|
cout << "Can't open \"\\SIS Common Store\\MaxIndex\"" << endl;
|
|
|
|
} else {
|
|
|
|
DWORD bytesRead;
|
|
|
|
if (! ReadFile(
|
|
hMaxIndex,
|
|
&maxIndex,
|
|
sizeof maxIndex,
|
|
&bytesRead,
|
|
NULL)) {
|
|
|
|
cout << "Can't read MaxIndex, " << GetLastError() << endl;
|
|
|
|
}
|
|
|
|
if (bytesRead < sizeof maxIndex) {
|
|
|
|
cout << "Invalid MaxIndex" << endl;
|
|
|
|
}
|
|
|
|
CloseHandle(hMaxIndex);
|
|
|
|
if (verbose)
|
|
cout << "MaxIndex: " << (INDEX) maxIndex << endl;
|
|
}
|
|
|
|
//
|
|
// Enumerate and validate all files in the common store directory.
|
|
// Save the file name and reference count for later lookup when validating
|
|
// the SIS link files.
|
|
//
|
|
|
|
findHandle = FindFirstFile( fileNameMatchAny.c_str(), &findData );
|
|
|
|
if (INVALID_HANDLE_VALUE == findHandle) {
|
|
|
|
cout << CommonStoreDir << " is empty." << endl;
|
|
return;
|
|
|
|
}
|
|
|
|
do {
|
|
|
|
ULONG refCount;
|
|
string fileName;
|
|
|
|
fileName = findData.cFileName;
|
|
|
|
if ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
|
|
|
|
//
|
|
// Ignore . and ..
|
|
//
|
|
|
|
if ( findData.cFileName[0] == '.' ) {
|
|
|
|
if (( findData.cFileName[1] == 0 ) ||
|
|
(( findData.cFileName[1] == '.' ) && ( findData.cFileName[2] == 0 )))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
cout << "Common Store directory skipped: " << fileName << endl;
|
|
continue;
|
|
|
|
}
|
|
|
|
if ((_stricmp(maxIndexFileName.c_str(),fileName.c_str()) == 0) ||
|
|
(_stricmp(logFileName.c_str(),fileName.c_str()) == 0)) {
|
|
|
|
//
|
|
// Skip the MaxIndex and LogFile files.
|
|
//
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
//
|
|
// Verify that:
|
|
// - the file name is a valid index.
|
|
// - this is a normal file (ie. not a reparse point).
|
|
// - there is a refcount stream of proper format.
|
|
//
|
|
|
|
INDEX csIndex;
|
|
|
|
refCount = 0;
|
|
|
|
if (! FileNameToIndex(fileName, csIndex)) {
|
|
|
|
cout << "Unknown file in Common Store: " << fileName << endl;
|
|
continue;
|
|
|
|
}
|
|
|
|
if (! ValidateIndex(csIndex)) {
|
|
|
|
cout << "Invalid CSIndex: " << fileName << endl;
|
|
|
|
}
|
|
|
|
if ( IO_REPARSE_TAG_SIS == findData.dwReserved0 ) {
|
|
|
|
cout << "SIS link found in Common Store: " << fileName << endl;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Read in the refcount;
|
|
//
|
|
|
|
string refName(fileName + ":sisrefs$");
|
|
|
|
HANDLE hRefCount = CreateFile(
|
|
refName.c_str(),
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
|
|
if (hRefCount == INVALID_HANDLE_VALUE) {
|
|
|
|
cout << "Can't open ref count stream, " << refName << ", " << GetLastError() << endl;
|
|
|
|
} else {
|
|
|
|
DWORD bytesRead;
|
|
|
|
if (! ReadFile(
|
|
hRefCount,
|
|
&refCount,
|
|
sizeof refCount,
|
|
&bytesRead,
|
|
NULL)) {
|
|
|
|
cout << "Can't read " << refName << ", " << GetLastError() << endl;
|
|
|
|
}
|
|
|
|
if (bytesRead < sizeof refCount) {
|
|
|
|
cout << "Invalid ref count in " << refName << endl;
|
|
|
|
}
|
|
|
|
CloseHandle(hRefCount);
|
|
|
|
}
|
|
|
|
CsFile csFile(csIndex, refCount, fileName);
|
|
|
|
//
|
|
// Add this file to our database. Expand the database if necessary.
|
|
//
|
|
|
|
if (0 == csFiles.capacity())
|
|
csFiles.reserve(csFiles.size() + 200);
|
|
|
|
csFiles.push_back(csFile);
|
|
|
|
if (verbose)
|
|
csFile.display();
|
|
}
|
|
|
|
} while ( FindNextFile( findHandle, &findData ) );
|
|
|
|
FindClose( findHandle );
|
|
|
|
|
|
//
|
|
// Sort the database for subsequent lookups.
|
|
//
|
|
|
|
sort(csFiles.begin(), csFiles.end());
|
|
}
|
|
|
|
|
|
//
|
|
// Validate the reference counts. Assumes all external references
|
|
// have been identified.
|
|
//
|
|
|
|
void
|
|
CommonStore::ValidateRefCounts() {
|
|
|
|
vector<CsFile>::iterator p;
|
|
|
|
for (p = csFiles.begin(); p != csFiles.end(); ++p) {
|
|
|
|
p->Validate();
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Lookup the specified index in the common store.
|
|
//
|
|
|
|
CsFile *
|
|
CommonStore::Query(INDEX index)
|
|
{
|
|
CsFile key(index);
|
|
|
|
//
|
|
// Use a binary search to lookup the index.
|
|
//
|
|
|
|
vector<CsFile>::iterator p = lower_bound(csFiles.begin(), csFiles.end(), key);
|
|
|
|
if (p == csFiles.end() || *p > key)
|
|
return NULL; // not found
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
//
|
|
// Extract the index from a common store file name.
|
|
//
|
|
|
|
bool
|
|
CommonStore::FileNameToIndex(string& fileName, INDEX& csIndex)
|
|
{
|
|
char c;
|
|
const size_t len = fileName.length();
|
|
ULONG hi = 0, lo = 0;
|
|
|
|
//
|
|
// Format: "_low.high", where low.high is the base 36 representation of
|
|
// the index value.
|
|
//
|
|
|
|
size_t i = 0;
|
|
|
|
if (len < 2 || fileName.at(i) != '_') {
|
|
|
|
cout << "Invalid Common Store file name: " << fileName << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
while (++i < len && (c = fileName.at(i)) != '.') {
|
|
|
|
INDEX d;
|
|
|
|
if (c >= '0' && c <= '9') {
|
|
|
|
d = c - '0';
|
|
|
|
} else if (c >= 'a' && c <= 'z') {
|
|
|
|
d = c - 'a' + 10;
|
|
|
|
} else {
|
|
|
|
cout << "Invalid Common Store file name: " << fileName << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
lo = lo * 36 + d;
|
|
|
|
}
|
|
|
|
if (c != '.') {
|
|
|
|
cout << "Invalid Common Store file name: " << fileName << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
while (++i < len) {
|
|
|
|
INDEX d;
|
|
|
|
c = fileName.at(i);
|
|
|
|
if (c >= '0' && c <= '9') {
|
|
|
|
d = c - '0';
|
|
|
|
} else if (c >= 'a' && c <= 'z') {
|
|
|
|
d = c - 'a' + 10;
|
|
|
|
} else {
|
|
|
|
cout << "Invalid Common Store file name: " << fileName << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
hi = hi * 36 + d;
|
|
|
|
}
|
|
|
|
csIndex = (INDEX) hi << 32 | lo;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
class LinkFile {
|
|
|
|
public:
|
|
|
|
LinkFile(INDEX i = 0, LONGLONG id = 0, INDEX cs = 0, int v = 0, string n = 0) :
|
|
index(i), NtfsId(id), csIndex(cs), version(v), name(n) {}
|
|
|
|
friend bool operator<(const LinkFile& a, const LinkFile& b) {
|
|
return a.index < b.index;
|
|
}
|
|
|
|
friend bool operator>(const LinkFile& a, const LinkFile& b) {
|
|
return a.index > b.index;
|
|
}
|
|
|
|
friend bool operator==(const LinkFile& a, const LinkFile& b) {
|
|
return a.index == b.index;
|
|
}
|
|
|
|
INDEX& LinkIndex() {
|
|
return index;
|
|
}
|
|
|
|
string& FileName() {
|
|
return name;
|
|
}
|
|
|
|
void display() {
|
|
cout << "Link: " << name <<
|
|
" CS Index: " << csIndex <<
|
|
" Link Index:" << index <<
|
|
" Id:" << NtfsId <<
|
|
" Version: " << version << endl;
|
|
}
|
|
|
|
private:
|
|
|
|
//
|
|
// This file's Ntfs Id.
|
|
//
|
|
|
|
LONGLONG NtfsId;
|
|
|
|
//
|
|
// Link index associated with this file.
|
|
//
|
|
|
|
INDEX index;
|
|
|
|
//
|
|
// The common store file (index) associated with this link.
|
|
//
|
|
|
|
INDEX csIndex;
|
|
|
|
//
|
|
// The revision number of this link file.
|
|
//
|
|
|
|
ULONG version;
|
|
|
|
//
|
|
// The fully qualified file name.
|
|
//
|
|
|
|
string name;
|
|
};
|
|
|
|
//
|
|
// The SIS Volume object.
|
|
//
|
|
|
|
class SISVolume {
|
|
|
|
public:
|
|
|
|
//
|
|
// Validate all SIS files on the volume.
|
|
//
|
|
|
|
void Validate(string& Volume);
|
|
|
|
//
|
|
// Set up a volume for use with SIS.
|
|
//
|
|
|
|
bool Create(string& Volume);
|
|
|
|
private:
|
|
|
|
//
|
|
// The bits that are actually in a SIS reparse point.
|
|
//
|
|
//
|
|
// Version 1
|
|
//
|
|
typedef struct _SI_REPARSE_BUFFER_V1 {
|
|
//
|
|
// A version number so that we can change the reparse point format
|
|
// and still properly handle old ones. This structure describes
|
|
// version 1.
|
|
//
|
|
ULONG ReparsePointFormatVersion;
|
|
|
|
//
|
|
// The index of the common store file.
|
|
//
|
|
INDEX CSIndex;
|
|
|
|
//
|
|
// The index of this link file.
|
|
//
|
|
INDEX LinkIndex;
|
|
|
|
} SI_REPARSE_BUFFER_V1, *PSI_REPARSE_BUFFER_V1;
|
|
|
|
//
|
|
// Version 2
|
|
//
|
|
typedef struct _SI_REPARSE_BUFFER_V2 {
|
|
//
|
|
// A version number so that we can change the reparse point format
|
|
// and still properly handle old ones. This structure describes
|
|
// version 2.
|
|
//
|
|
ULONG ReparsePointFormatVersion;
|
|
|
|
//
|
|
// The index of the common store file.
|
|
//
|
|
INDEX CSIndex;
|
|
|
|
//
|
|
// The index of this link file.
|
|
//
|
|
INDEX LinkIndex;
|
|
|
|
//
|
|
// The file ID of the link file.
|
|
//
|
|
LONGLONG LinkFileNtfsId;
|
|
|
|
//
|
|
// A "131 hash" checksum of this structure.
|
|
// N.B. Must be last.
|
|
//
|
|
LARGE_INTEGER Checksum;
|
|
|
|
} SI_REPARSE_BUFFER_V2, *PSI_REPARSE_BUFFER_V2;
|
|
|
|
//
|
|
// The bits that are actually in a SIS reparse point. Version 3.
|
|
//
|
|
typedef struct _SI_REPARSE_BUFFER {
|
|
|
|
//
|
|
// A version number so that we can change the reparse point format
|
|
// and still properly handle old ones. This structure describes
|
|
// version 1.
|
|
//
|
|
ULONG ReparsePointFormatVersion;
|
|
|
|
//
|
|
// The index of the common store file.
|
|
//
|
|
INDEX CSIndex;
|
|
|
|
//
|
|
// The index of this link file.
|
|
//
|
|
INDEX LinkIndex;
|
|
|
|
//
|
|
// The file ID of the link file.
|
|
//
|
|
LONGLONG LinkFileNtfsId;
|
|
|
|
//
|
|
// The file ID of the common store file.
|
|
//
|
|
LONGLONG CSFileNtfsId;
|
|
|
|
//
|
|
// A "131 hash" checksum of this structure.
|
|
// N.B. Must be last.
|
|
//
|
|
LARGE_INTEGER Checksum;
|
|
|
|
} SI_REPARSE_BUFFER, *PSI_REPARSE_BUFFER;
|
|
|
|
#define SIS_REPARSE_BUFFER_FORMAT_VERSION_1 1
|
|
#define SIS_REPARSE_BUFFER_FORMAT_VERSION_2 2
|
|
#define SIS_REPARSE_BUFFER_FORMAT_VERSION 3
|
|
#define SIS_MAX_REPARSE_DATA_VALUE_LENGTH (sizeof(SI_REPARSE_BUFFER))
|
|
#define SIS_REPARSE_DATA_SIZE (sizeof(REPARSE_DATA_BUFFER)+SIS_MAX_REPARSE_DATA_VALUE_LENGTH)
|
|
|
|
void Walk(string& dirName);
|
|
|
|
bool GetLinkInfo(string& fileName, SI_REPARSE_BUFFER& linkInfo);
|
|
|
|
void ComputeChecksum(PVOID buffer, ULONG size, PLARGE_INTEGER checksum);
|
|
|
|
void ValidateLink();
|
|
|
|
//
|
|
// The common store object associated with this volume.
|
|
//
|
|
|
|
CommonStore cs;
|
|
|
|
//
|
|
// Database of link files. The link files are recorded to verify that
|
|
// duplicate link indices do not occur, and also to be able to identify
|
|
// all link files associated with a particular common store file.
|
|
//
|
|
|
|
vector<LinkFile> linkFiles;
|
|
};
|
|
|
|
|
|
void
|
|
SISVolume::Validate(string& Volume)
|
|
{
|
|
string ntVolume("\\\\.\\" + Volume);
|
|
|
|
//
|
|
// See if we can open the volume.
|
|
//
|
|
|
|
HANDLE hVolume = CreateFile(
|
|
ntVolume.c_str(),
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
|
|
if (hVolume == INVALID_HANDLE_VALUE) {
|
|
|
|
cout << "Can't open " << Volume << endl;
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
CloseHandle(hVolume);
|
|
|
|
}
|
|
|
|
//
|
|
// Check the common store directory and it's files. This will also build
|
|
// a database of common store files that will be used to validate the link
|
|
// files.
|
|
//
|
|
|
|
cs.Validate(Volume);
|
|
|
|
cout << "Checking Link Files" << endl;
|
|
|
|
//
|
|
// Enumerate all of the files on the volume looking for SIS links.
|
|
//
|
|
// if the file is a SIS reparse point then validate it:
|
|
// - link index (against MaxIndex and other link indices)
|
|
// - CS index (lookup in CommonStore)
|
|
//
|
|
|
|
Walk( Volume + "\\" );
|
|
|
|
//
|
|
// Now we can check the reference counts in the common store files.
|
|
//
|
|
|
|
cout << "Checking Reference Counts" << endl;
|
|
|
|
cs.ValidateRefCounts();
|
|
|
|
//
|
|
// Check for duplicate link indices.
|
|
//
|
|
|
|
cout << "Checking Link Indices" << endl;
|
|
|
|
sort(linkFiles.begin(), linkFiles.end());
|
|
|
|
vector<LinkFile>::iterator p = linkFiles.begin();
|
|
|
|
if (p != linkFiles.end()) {
|
|
|
|
for (++p; p != linkFiles.end(); ++p) {
|
|
|
|
if (p == (p-1)) {
|
|
|
|
cout << "Duplicate link index (" << (INDEX) p->LinkIndex() << "): ";
|
|
cout << p->FileName() << ", " << (p-1)->FileName() << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
SISVolume::Walk(string& dirName)
|
|
{
|
|
WIN32_FIND_DATA findData;
|
|
HANDLE findHandle;
|
|
const string fileNameMatchAny = dirName + "*";
|
|
|
|
//
|
|
// Enumerate all files in the specified directory, looking for SIS links.
|
|
//
|
|
|
|
findHandle = FindFirstFile( fileNameMatchAny.c_str(), &findData );
|
|
|
|
if (INVALID_HANDLE_VALUE == findHandle) {
|
|
|
|
//
|
|
// Empty directory.
|
|
//
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
do {
|
|
|
|
//
|
|
// Check for a SIS link.
|
|
//
|
|
|
|
if (( findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) &&
|
|
( findData.dwReserved0 == IO_REPARSE_TAG_SIS )) {
|
|
|
|
if ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
|
|
|
|
//
|
|
// File is both a directory and a SIS link -- illegal.
|
|
//
|
|
|
|
cout << dirName << findData.cFileName << " SIS link directory." << endl;
|
|
|
|
}
|
|
|
|
SI_REPARSE_BUFFER linkInfo;
|
|
|
|
//
|
|
// Read the reparse point data to get the link index and
|
|
// common store index.
|
|
//
|
|
|
|
if (! GetLinkInfo(dirName + findData.cFileName, linkInfo)) {
|
|
|
|
cout << dirName << findData.cFileName << " : invalid link information." << endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
//
|
|
// Create a LinkFile object.
|
|
//
|
|
|
|
LinkFile lf(linkInfo.LinkIndex,
|
|
linkInfo.LinkFileNtfsId,
|
|
linkInfo.CSIndex,
|
|
linkInfo.ReparsePointFormatVersion,
|
|
dirName + findData.cFileName);
|
|
|
|
//
|
|
// And add it to our database. Expand the database first if necessary.
|
|
//
|
|
|
|
if (0 == linkFiles.capacity())
|
|
linkFiles.reserve(linkFiles.size() + 200);
|
|
|
|
linkFiles.push_back(lf);
|
|
|
|
if (! cs.ValidateIndex(linkInfo.LinkIndex)) {
|
|
|
|
cout << "Invalid Link index: " << lf.FileName() << "(" << (INDEX) linkInfo.LinkIndex << ")" << endl;
|
|
|
|
}
|
|
|
|
//
|
|
// Find the common store file.
|
|
//
|
|
|
|
CsFile *pcsFile = cs.Query(linkInfo.CSIndex);
|
|
|
|
if (pcsFile == 0) {
|
|
|
|
//
|
|
// cs file was not found.
|
|
//
|
|
|
|
cout << "Common Store file " << (INDEX) linkInfo.CSIndex << " not found." << endl;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Update the external reference count on the common store file.
|
|
//
|
|
|
|
pcsFile->IncRefCount();
|
|
|
|
}
|
|
|
|
//
|
|
// Make sure the link index isn't in use as a common store index.
|
|
//
|
|
|
|
pcsFile = cs.Query(linkInfo.LinkIndex);
|
|
|
|
if (pcsFile != 0) {
|
|
|
|
cout << "Link index collision with common store file. Link: ";
|
|
cout << lf.FileName() << ", index: " << (INDEX) linkInfo.LinkIndex << endl;
|
|
|
|
}
|
|
|
|
if (verbose)
|
|
lf.display();
|
|
|
|
} else if ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
|
|
|
|
//
|
|
// Ignore \. and \..
|
|
//
|
|
|
|
if ( findData.cFileName[0] == '.' ) {
|
|
|
|
if (( findData.cFileName[1] == 0 ) ||
|
|
(( findData.cFileName[1] == '.' ) && ( findData.cFileName[2] == 0 )))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
//
|
|
// Walk down this directory.
|
|
//
|
|
|
|
Walk( dirName + findData.cFileName + "\\" );
|
|
|
|
}
|
|
|
|
} while ( FindNextFile( findHandle, &findData ) );
|
|
|
|
FindClose( findHandle );
|
|
|
|
}
|
|
|
|
#define SHARE_ALL (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
|
|
|
|
bool
|
|
SISVolume::GetLinkInfo(string& fileName, SI_REPARSE_BUFFER& linkInfo)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
HANDLE fileHandle;
|
|
|
|
UNICODE_STRING ufileName,
|
|
uNTName;
|
|
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
|
|
PREPARSE_DATA_BUFFER ReparseBufferHeader = NULL;
|
|
UCHAR ReparseBuffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
|
|
|
LARGE_INTEGER Checksum;
|
|
|
|
//
|
|
// Allocate and initialize Unicode string.
|
|
//
|
|
|
|
RtlCreateUnicodeStringFromAsciiz( &ufileName, fileName.c_str() );
|
|
|
|
RtlDosPathNameToNtPathName_U(
|
|
ufileName.Buffer,
|
|
&uNTName,
|
|
NULL,
|
|
NULL );
|
|
|
|
//
|
|
// Open the file.
|
|
// Notice that if there are symbolic links in the path they are
|
|
// traversed silently.
|
|
//
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectAttributes,
|
|
&uNTName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL );
|
|
|
|
//
|
|
// Make sure that we call open with the appropriate flags for:
|
|
//
|
|
// (1) directory versus non-directory
|
|
// (2) reparse point
|
|
//
|
|
|
|
ULONG OpenOptions = FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE;
|
|
|
|
Status = NtOpenFile(
|
|
&fileHandle,
|
|
FILE_READ_DATA | SYNCHRONIZE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
SHARE_ALL,
|
|
OpenOptions );
|
|
|
|
RtlFreeUnicodeString( &ufileName );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
cout << "Unable to open SIS link file: " << fileName << endl;
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Get the reparse point.
|
|
//
|
|
|
|
Status = NtFsControlFile(
|
|
fileHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
FSCTL_GET_REPARSE_POINT,
|
|
NULL, // Input buffer
|
|
0, // Input buffer length
|
|
ReparseBuffer, // Output buffer
|
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE ); // Output buffer length
|
|
|
|
NtClose( fileHandle );
|
|
|
|
if (!NT_SUCCESS( Status )) {
|
|
|
|
cout << "FSCTL_GET_REPARSE_POINT failed, " << (ULONG)IoStatusBlock.Information << ", " << fileName << endl;
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Copy the SIS link info from the reparse buffer to the caller's buffer.
|
|
//
|
|
|
|
ReparseBufferHeader = (PREPARSE_DATA_BUFFER) ReparseBuffer;
|
|
|
|
if (ReparseBufferHeader->ReparseTag == IO_REPARSE_TAG_SIS) {
|
|
|
|
PSI_REPARSE_BUFFER sisReparseBuffer = (PSI_REPARSE_BUFFER) ReparseBufferHeader->GenericReparseBuffer.DataBuffer;
|
|
|
|
linkInfo = *sisReparseBuffer;
|
|
|
|
//
|
|
// Now check to be sure that we understand this reparse point format version and
|
|
// that it has the correct size.
|
|
//
|
|
if (ReparseBufferHeader->ReparseDataLength != sizeof(SI_REPARSE_BUFFER)
|
|
|| (sisReparseBuffer->ReparsePointFormatVersion != SIS_REPARSE_BUFFER_FORMAT_VERSION)) {
|
|
//
|
|
// We don't understand it, so either its corrupt or from a newer version of SIS.
|
|
// Either way, we can't understand it, so punt.
|
|
//
|
|
cout << "Invalid format version in " << fileName
|
|
<< " Version: " << sisReparseBuffer->ReparsePointFormatVersion
|
|
<< ", expected: " << SIS_REPARSE_BUFFER_FORMAT_VERSION << endl;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Now check the checksum.
|
|
//
|
|
ComputeChecksum(
|
|
sisReparseBuffer,
|
|
sizeof(SI_REPARSE_BUFFER) - sizeof sisReparseBuffer->Checksum,
|
|
&Checksum);
|
|
|
|
if (Checksum.QuadPart != sisReparseBuffer->Checksum.QuadPart) {
|
|
|
|
cout << "Invalid checksum in " << fileName << endl;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
} else {
|
|
|
|
cout << "Unexpected error. " << fileName << " : expected SIS link file, tag: " << ReparseBufferHeader->ReparseTag << endl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
VOID
|
|
SISVolume::ComputeChecksum(
|
|
IN PVOID buffer,
|
|
IN ULONG size,
|
|
OUT PLARGE_INTEGER checksum)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Compute a checksum for a buffer. We use the "131 hash," which
|
|
works by keeping a 64 bit running total, and for each 32 bits of
|
|
data multiplying the 64 bits by 131 and adding in the next 32
|
|
bits. Must be called at PASSIVE_LEVEL, and all aruments
|
|
may be pagable.
|
|
|
|
Arguments:
|
|
|
|
buffer - pointer to the data to be checksummed
|
|
|
|
size - size of the data to be checksummed
|
|
|
|
checksum - pointer to large integer to receive the checksum. This
|
|
may be within the buffer, and SipComputeChecksum guarantees that
|
|
the initial value will be used in computing the checksum.
|
|
|
|
Return Value:
|
|
|
|
Returns STATUS_SUCCESS or an error returned from the actual disk write.
|
|
--*/
|
|
{
|
|
LARGE_INTEGER runningTotal;
|
|
ULONG *ptr = (ULONG *)buffer;
|
|
ULONG bytesRemaining = size;
|
|
|
|
runningTotal.QuadPart = 0;
|
|
|
|
while (bytesRemaining >= sizeof(*ptr)) {
|
|
runningTotal.QuadPart = runningTotal.QuadPart * 131 + *ptr;
|
|
bytesRemaining -= sizeof(*ptr);
|
|
ptr++;
|
|
}
|
|
|
|
if (bytesRemaining > 0) {
|
|
ULONG extra;
|
|
|
|
extra = 0;
|
|
memmove(&extra, ptr, bytesRemaining);
|
|
|
|
runningTotal.QuadPart = runningTotal.QuadPart * 131 + extra;
|
|
}
|
|
|
|
*checksum = runningTotal;
|
|
}
|
|
|
|
bool
|
|
SISVolume::Create(string& Volume)
|
|
{
|
|
string ntVolume("\\\\.\\" + Volume);
|
|
|
|
//
|
|
// See if we can open the volume.
|
|
//
|
|
|
|
HANDLE hVolume = CreateFile(
|
|
ntVolume.c_str(),
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
|
|
if (hVolume == INVALID_HANDLE_VALUE) {
|
|
|
|
cout << "Can't open " << Volume << endl;
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
CloseHandle(hVolume);
|
|
|
|
}
|
|
|
|
//
|
|
// The common store is the only thing we need to create.
|
|
//
|
|
|
|
return cs.Create(Volume);
|
|
|
|
}
|
|
|
|
void
|
|
usage()
|
|
{
|
|
cout << "Usage: chksis [-vc] [drive:]\n -v: verbose\n -c: create SIS volume" << endl;
|
|
}
|
|
|
|
|
|
int
|
|
__cdecl
|
|
main(int argc, char *argv[])
|
|
{
|
|
string volume("C:");
|
|
bool volumeArgSeen = false;
|
|
bool create = false;
|
|
SISVolume sis;
|
|
|
|
for (int i = 1; i < argc; ++i) {
|
|
|
|
if (argv[i][0] == '-') {
|
|
|
|
if (volumeArgSeen) {
|
|
usage();
|
|
exit(1);
|
|
}
|
|
|
|
switch (argv[i][1]) {
|
|
case 'v':
|
|
verbose = true;
|
|
break;
|
|
case 'c':
|
|
create = true;
|
|
break;
|
|
default:
|
|
usage();
|
|
exit(1);
|
|
}
|
|
|
|
} else {
|
|
|
|
volumeArgSeen = true;
|
|
|
|
volume.assign(argv[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (create) {
|
|
|
|
if (! volumeArgSeen) {
|
|
cout << "Must specify volume with -c" << endl;
|
|
exit(1);
|
|
}
|
|
|
|
sis.Create(volume);
|
|
exit(0);
|
|
|
|
}
|
|
|
|
if (! volumeArgSeen)
|
|
cout << "Checking " << volume << endl;
|
|
|
|
|
|
sis.Validate(volume);
|
|
|
|
return 0;
|
|
}
|