565 lines
16 KiB
C
565 lines
16 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1997 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
pin.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Chuck Lenzmeier (chuckl)
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
#define UNICODE
|
|||
|
#include <nt.h>
|
|||
|
#include <ntrtl.h>
|
|||
|
#include <nturtl.h>
|
|||
|
#include <windows.h>
|
|||
|
#include <stdlib.h>
|
|||
|
#include <stdio.h>
|
|||
|
#include <g:\nt\private\ntos\rdr2\csc\inc\cscapi.h>
|
|||
|
|
|||
|
|
|||
|
#if 1
|
|||
|
DWORD DebugLevel = 1;
|
|||
|
#define dprintf(_lvl_,_x_) if ((_lvl_) <= DebugLevel) printf _x_
|
|||
|
#define DEBUG(_x_) _x_
|
|||
|
#else
|
|||
|
#define dprintf(_lvl_,_x_)
|
|||
|
#define DEBUG(_x_)
|
|||
|
#endif
|
|||
|
|
|||
|
typedef enum {
|
|||
|
OP_PIN = 1,
|
|||
|
OP_UNPIN,
|
|||
|
OP_DELETE,
|
|||
|
OP_QUERY,
|
|||
|
OP_READ,
|
|||
|
OP_QUERY_SPARSE,
|
|||
|
OP_QUERY_FULL,
|
|||
|
OP_QUERY_FREE,
|
|||
|
OP_QUERY_NOT_IN_DB,
|
|||
|
OP_QUERY_SYSTEM,
|
|||
|
OP_QUERY_NOT_SYSTEM
|
|||
|
} OPERATION;
|
|||
|
|
|||
|
OPERATION Operation;
|
|||
|
BOOL Recurse = TRUE;
|
|||
|
BOOL TotalsOnly = FALSE;
|
|||
|
BOOL SkipSymbols = TRUE;
|
|||
|
BOOL SkipPagefile = TRUE;
|
|||
|
|
|||
|
#define PAGE_SIZE 4096
|
|||
|
|
|||
|
//
|
|||
|
// Common header for container entries (directories and keys).
|
|||
|
//
|
|||
|
|
|||
|
typedef struct _CONTAINER_ENTRY {
|
|||
|
LIST_ENTRY SiblingListEntry;
|
|||
|
LIST_ENTRY ContainerList;
|
|||
|
struct _CONTAINER_ENTRY *Parent;
|
|||
|
} CONTAINER_ENTRY, *PCONTAINER_ENTRY;
|
|||
|
|
|||
|
//
|
|||
|
// Macros for manipulating containers and objects.
|
|||
|
//
|
|||
|
|
|||
|
#define InitializeContainer(_container,_parent) { \
|
|||
|
InitializeListHead(&(_container)->ContainerList); \
|
|||
|
(_container)->Parent = (PCONTAINER_ENTRY)(_parent); \
|
|||
|
}
|
|||
|
|
|||
|
#define InsertContainer(_container,_subcontainer) { \
|
|||
|
dprintf( 3, ("inserting subcontainer %x on container %x, list head at %x = %x,%x\n",\
|
|||
|
_subcontainer, _container, &_container->ContainerList, \
|
|||
|
_container->ContainerList.Flink, _container->ContainerList.Blink) ); \
|
|||
|
InsertTailList(&(_container)->ContainerList,&(_subcontainer)->SiblingListEntry); \
|
|||
|
dprintf( 3, ("inserted subcontainer %x on container %x, list head at %x = %x,%x\n", \
|
|||
|
_subcontainer, _container, &_container->ContainerList, \
|
|||
|
_container->ContainerList.Flink, _container->ContainerList.Blink) ); \
|
|||
|
}
|
|||
|
|
|||
|
#define RemoveContainer(_container) RemoveEntryList(&(_container)->SiblingListEntry)
|
|||
|
|
|||
|
#define GetFirstContainer(_container) \
|
|||
|
((_container)->ContainerList.Flink != &(_container)->ContainerList ? \
|
|||
|
CONTAINING_RECORD( (_container)->ContainerList.Flink, \
|
|||
|
CONTAINER_ENTRY, \
|
|||
|
SiblingListEntry ) : NULL)
|
|||
|
|
|||
|
#define GetNextContainer(_container) \
|
|||
|
((_container)->SiblingListEntry.Flink != &(_container)->Parent->ContainerList ? \
|
|||
|
CONTAINING_RECORD( (_container)->SiblingListEntry.Flink, \
|
|||
|
CONTAINER_ENTRY, \
|
|||
|
SiblingListEntry ) : NULL)
|
|||
|
|
|||
|
#define GetParent(_container) (_container)->Parent
|
|||
|
|
|||
|
//
|
|||
|
// Structures for entries in the watch tree.
|
|||
|
//
|
|||
|
|
|||
|
typedef struct _DIRECTORY_ENTRY {
|
|||
|
CONTAINER_ENTRY ;
|
|||
|
WCHAR Name[1];
|
|||
|
} DIRECTORY_ENTRY, *PDIRECTORY_ENTRY;
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
OpenAndReadFile (
|
|||
|
PWCH File
|
|||
|
)
|
|||
|
{
|
|||
|
HANDLE fileHandle;
|
|||
|
DWORD fileSize;
|
|||
|
HANDLE mappingHandle;
|
|||
|
PUCHAR mappedBase;
|
|||
|
DWORD i;
|
|||
|
PUCHAR p;
|
|||
|
DWORD j;
|
|||
|
|
|||
|
fileHandle = CreateFile(
|
|||
|
File,
|
|||
|
GENERIC_READ,
|
|||
|
0,
|
|||
|
NULL,
|
|||
|
OPEN_EXISTING,
|
|||
|
0,
|
|||
|
NULL
|
|||
|
);
|
|||
|
if ( fileHandle == INVALID_HANDLE_VALUE ) {
|
|||
|
printf( "Couldn't open %ws: %d\n", File, GetLastError() );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
fileSize = SetFilePointer( fileHandle, 0, NULL, FILE_END );
|
|||
|
|
|||
|
if ( fileSize == 0xFFFFFFFF ) {
|
|||
|
|
|||
|
printf( "SetFilePointer(END) failed with %d\n", GetLastError() );
|
|||
|
|
|||
|
} else if ( fileSize != 0 ) {
|
|||
|
|
|||
|
mappingHandle = CreateFileMapping(
|
|||
|
fileHandle,
|
|||
|
NULL,
|
|||
|
PAGE_READONLY,
|
|||
|
0,
|
|||
|
fileSize,
|
|||
|
NULL
|
|||
|
);
|
|||
|
if ( mappingHandle == NULL ) {
|
|||
|
|
|||
|
printf( "Couldn't create mapping %ws: %d\n", File, GetLastError() );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
mappedBase = MapViewOfFile( mappingHandle, FILE_MAP_READ, 0, 0, fileSize );
|
|||
|
|
|||
|
if ( mappedBase == NULL ) {
|
|||
|
|
|||
|
printf( "Couldn't map view %ws: %d\n", File, GetLastError() );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//printf( "OpenAndReadFile: fileSize = %x, mappedBase = %x\n", fileSize, mappedBase );
|
|||
|
|
|||
|
j = 0;
|
|||
|
|
|||
|
for ( i = 0, p = mappedBase;
|
|||
|
i < fileSize;
|
|||
|
i += PAGE_SIZE, p += PAGE_SIZE ) {
|
|||
|
j += *p;
|
|||
|
}
|
|||
|
|
|||
|
UnmapViewOfFile( mappedBase );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
CloseHandle( mappingHandle );
|
|||
|
}
|
|||
|
|
|||
|
CloseHandle( fileHandle );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
OperateOnFile (
|
|||
|
PWCH Directory,
|
|||
|
PWCH File
|
|||
|
)
|
|||
|
{
|
|||
|
DWORD pathLength;
|
|||
|
DWORD status;
|
|||
|
DWORD pinCount;
|
|||
|
DWORD hintFlags;
|
|||
|
BOOL inDatabase;
|
|||
|
BOOL printThis;
|
|||
|
DWORD count = 0;
|
|||
|
|
|||
|
pathLength = wcslen( Directory );
|
|||
|
if ( File != NULL ) {
|
|||
|
wcscat( Directory, L"\\" );
|
|||
|
wcscat( Directory, File );
|
|||
|
}
|
|||
|
|
|||
|
inDatabase = CSCQueryFileStatusW( Directory, &status, &pinCount, &hintFlags );
|
|||
|
|
|||
|
if ( Operation == OP_PIN ) {
|
|||
|
|
|||
|
if ( !inDatabase || (pinCount == 0) ) {
|
|||
|
inDatabase = CSCPinFileW( Directory, hintFlags, &status, &pinCount, &hintFlags );
|
|||
|
if ( !TotalsOnly ) printf( "%ws : PINNED : pin count %d, hint flags %x, status %x\n", Directory, pinCount, hintFlags, status );
|
|||
|
count = 1;
|
|||
|
} else if ( (status & FLAG_CSC_COPY_STATUS_SPARSE) != 0 ) {
|
|||
|
printf( "%ws : SPARSE : pin count %d, hint flags %x, status %x\n", Directory, pinCount, hintFlags, status );
|
|||
|
}
|
|||
|
|
|||
|
} else if ( Operation == OP_UNPIN ) {
|
|||
|
|
|||
|
if ( inDatabase && (pinCount != 0) ) {
|
|||
|
inDatabase = CSCUnpinFileW( Directory, hintFlags, &status, &pinCount, &hintFlags );
|
|||
|
if ( !TotalsOnly ) printf( "%ws : UNPINNED : pin count %d, hintFlags %x, status %x\n", Directory, pinCount, hintFlags, status );
|
|||
|
count = 1;
|
|||
|
}
|
|||
|
|
|||
|
} else if ( Operation == OP_DELETE ) {
|
|||
|
|
|||
|
if ( inDatabase ) {
|
|||
|
inDatabase = CSCDeleteW( Directory );
|
|||
|
if ( inDatabase ) {
|
|||
|
if ( !TotalsOnly ) printf( "%ws : DELETED\n", Directory );
|
|||
|
} else {
|
|||
|
printf( "%ws : DELETE FAILED\n", Directory );
|
|||
|
}
|
|||
|
count = 1;
|
|||
|
}
|
|||
|
|
|||
|
} else if ( Operation == OP_READ ) {
|
|||
|
|
|||
|
if ( inDatabase &&
|
|||
|
((hintFlags & FLAG_CSC_HINT_PIN_SYSTEM) != 0) &&
|
|||
|
((status & FLAG_CSC_COPY_STATUS_SPARSE) != 0) ) {
|
|||
|
if ( !TotalsOnly ) printf( "%ws : READING\n", Directory );
|
|||
|
OpenAndReadFile( Directory );
|
|||
|
count = 1;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if ( inDatabase ) {
|
|||
|
|
|||
|
printThis = FALSE;
|
|||
|
|
|||
|
if ( Operation == OP_QUERY ) {
|
|||
|
printThis = TRUE;
|
|||
|
} else {
|
|||
|
if ( (Operation == OP_QUERY_FREE) &&
|
|||
|
((hintFlags & FLAG_CSC_HINT_PIN_SYSTEM) == 0) ) {
|
|||
|
printThis = TRUE;
|
|||
|
}
|
|||
|
if ( (Operation == OP_QUERY_SPARSE) &&
|
|||
|
((status & FLAG_CSC_COPY_STATUS_SPARSE) != 0) ) {
|
|||
|
printThis = TRUE;
|
|||
|
}
|
|||
|
if ( (Operation == OP_QUERY_FULL) &&
|
|||
|
((status & FLAG_CSC_COPY_STATUS_SPARSE) == 0) ) {
|
|||
|
printThis = TRUE;
|
|||
|
}
|
|||
|
if ( (Operation == OP_QUERY_SYSTEM) &&
|
|||
|
((hintFlags & FLAG_CSC_HINT_PIN_SYSTEM) != 0) ) {
|
|||
|
printThis = TRUE;
|
|||
|
}
|
|||
|
if ( (Operation == OP_QUERY_NOT_SYSTEM) &&
|
|||
|
((hintFlags & FLAG_CSC_HINT_PIN_SYSTEM) == 0) ) {
|
|||
|
printThis = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( printThis ) {
|
|||
|
if ( !TotalsOnly ) printf( "%ws : pin count %d, hint flags %x, status %x\n", Directory, pinCount, hintFlags, status );
|
|||
|
count = 1;
|
|||
|
}
|
|||
|
|
|||
|
} else if ( (Operation == OP_QUERY) || (Operation == OP_QUERY_NOT_IN_DB) ) {
|
|||
|
if ( !TotalsOnly ) printf( "%ws : not in database\n", Directory );
|
|||
|
count = 1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Directory[pathLength] = 0;
|
|||
|
|
|||
|
return count;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
WalkDirectory (
|
|||
|
IN PWCH Directory
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
DWORD - Win32 status of the operation.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
DIRECTORY_ENTRY rootDirectory;
|
|||
|
PDIRECTORY_ENTRY currentDirectory;
|
|||
|
PDIRECTORY_ENTRY newDirectory;
|
|||
|
WIN32_FIND_DATA fileData;
|
|||
|
HANDLE findHandle;
|
|||
|
DWORD attributes;
|
|||
|
DWORD error;
|
|||
|
BOOL ok;
|
|||
|
WCHAR currentPath[MAX_PATH + 1];
|
|||
|
DWORD count = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Get the address of the root directory entry.
|
|||
|
//
|
|||
|
|
|||
|
currentDirectory = &rootDirectory;
|
|||
|
InitializeContainer( currentDirectory, NULL );
|
|||
|
|
|||
|
wcscpy( currentPath, Directory );
|
|||
|
if ( currentPath[wcslen(currentPath)-1] == '\\' ) {
|
|||
|
currentPath[wcslen(currentPath)-1] = 0;
|
|||
|
}
|
|||
|
|
|||
|
attributes = GetFileAttributes( currentPath );
|
|||
|
if ( attributes == 0xffffffff ) {
|
|||
|
printf( "Error querying %ws: %d\n", currentPath, GetLastError() );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if ( (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0 ) {
|
|||
|
|
|||
|
//
|
|||
|
// The input name represents a file, not a directory. Process the file.
|
|||
|
//
|
|||
|
|
|||
|
dprintf( 2, (" found file %ws\n", currentPath) );
|
|||
|
count += OperateOnFile( currentPath, NULL );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
do {
|
|||
|
|
|||
|
//
|
|||
|
// Look for files/directories in the current directory.
|
|||
|
//
|
|||
|
|
|||
|
wcscat( currentPath, L"\\*" );
|
|||
|
dprintf( 2, ("FindFirst for %ws\n", currentPath) );
|
|||
|
findHandle = FindFirstFile( currentPath, &fileData );
|
|||
|
currentPath[wcslen(currentPath) - 2] = 0;
|
|||
|
|
|||
|
if ( findHandle != INVALID_HANDLE_VALUE ) {
|
|||
|
|
|||
|
do {
|
|||
|
|
|||
|
if ( (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 ) {
|
|||
|
|
|||
|
//
|
|||
|
// The entry returned is for a file. Process the file.
|
|||
|
//
|
|||
|
|
|||
|
if ( !SkipPagefile || (_wcsicmp(fileData.cFileName,L"pagefile.sys") != 0) ) {
|
|||
|
dprintf( 2, (" found file %ws\\%ws\n", currentPath, fileData.cFileName) );
|
|||
|
count += OperateOnFile( currentPath, fileData.cFileName );
|
|||
|
}
|
|||
|
|
|||
|
} else if (Recurse &&
|
|||
|
(wcscmp(fileData.cFileName,L".") != 0) &&
|
|||
|
(wcscmp(fileData.cFileName,L"..") != 0) &&
|
|||
|
(!SkipSymbols || (_wcsicmp(fileData.cFileName,L"symbols") != 0))) {
|
|||
|
|
|||
|
//
|
|||
|
// The entry returned is for a directory. Add it to the tree.
|
|||
|
//
|
|||
|
|
|||
|
dprintf( 2, (" found directory %ws\\%ws\n", currentPath, fileData.cFileName) );
|
|||
|
newDirectory = malloc( sizeof(DIRECTORY_ENTRY) - sizeof(WCHAR) +
|
|||
|
((wcslen(fileData.cFileName) + 1) * sizeof(WCHAR)) );
|
|||
|
if ( newDirectory == NULL ) {
|
|||
|
FindClose( findHandle );
|
|||
|
printf( "Out of memory\n" );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
InitializeContainer( newDirectory, currentDirectory );
|
|||
|
wcscpy( newDirectory->Name, fileData.cFileName );
|
|||
|
InsertContainer( currentDirectory, newDirectory );
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Find another entry in the directory.
|
|||
|
//
|
|||
|
|
|||
|
ok = FindNextFile( findHandle, &fileData );
|
|||
|
|
|||
|
} while ( ok );
|
|||
|
|
|||
|
//
|
|||
|
// All entries found. Close the find handle.
|
|||
|
//
|
|||
|
|
|||
|
FindClose( findHandle );
|
|||
|
|
|||
|
} // findHandle != INVALID_HANDLE_VALUE
|
|||
|
|
|||
|
//
|
|||
|
// If the current directory has subdirectories, recurse into the
|
|||
|
// first one.
|
|||
|
//
|
|||
|
|
|||
|
newDirectory = (PDIRECTORY_ENTRY)GetFirstContainer( currentDirectory );
|
|||
|
dprintf( 3, ("done with directory %ws; first child %ws\n", currentPath, newDirectory == NULL ? L"(none)" : newDirectory->Name ));
|
|||
|
if ( newDirectory != NULL ) {
|
|||
|
|
|||
|
currentDirectory = newDirectory;
|
|||
|
wcscat( currentPath, L"\\" );
|
|||
|
wcscat( currentPath, currentDirectory->Name );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// The directory has no subdirectories. Walk back up the
|
|||
|
// tree looking for a sibling directory to process.
|
|||
|
//
|
|||
|
|
|||
|
while ( TRUE ) {
|
|||
|
|
|||
|
//
|
|||
|
// If the current directory is the root directory, we're done.
|
|||
|
//
|
|||
|
|
|||
|
if ( currentDirectory == &rootDirectory ) {
|
|||
|
currentDirectory = NULL;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Strip the name of the current directory off of the path.
|
|||
|
//
|
|||
|
|
|||
|
*wcsrchr(currentPath, L'\\') = 0;
|
|||
|
|
|||
|
//
|
|||
|
// If the parent directory has more subdirectories,
|
|||
|
// recurse into the next one. Otherwise, move up
|
|||
|
// to the parent directory and try again.
|
|||
|
//
|
|||
|
|
|||
|
newDirectory = (PDIRECTORY_ENTRY)GetNextContainer( currentDirectory );
|
|||
|
if ( newDirectory != NULL ) {
|
|||
|
currentDirectory = newDirectory;
|
|||
|
wcscat( currentPath, L"\\" );
|
|||
|
wcscat( currentPath, currentDirectory->Name );
|
|||
|
break;
|
|||
|
} else {
|
|||
|
currentDirectory = (PDIRECTORY_ENTRY)GetParent( currentDirectory );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} while ( currentDirectory != NULL );
|
|||
|
}
|
|||
|
|
|||
|
if ( count != 0 ) {
|
|||
|
printf( "%d files%s\n", count, TotalsOnly ? "" : " listed" );
|
|||
|
}
|
|||
|
|
|||
|
return;
|
|||
|
|
|||
|
} // WalkDirectory
|
|||
|
|
|||
|
int
|
|||
|
__cdecl
|
|||
|
wmain (
|
|||
|
int argc,
|
|||
|
PWCH argv[]
|
|||
|
)
|
|||
|
{
|
|||
|
int c = --argc;
|
|||
|
PWCH *v = &argv[1];
|
|||
|
PWCH a;
|
|||
|
|
|||
|
while ( c != 0 ) {
|
|||
|
a = *v;
|
|||
|
if ( *a != '-' ) {
|
|||
|
break;
|
|||
|
}
|
|||
|
a++;
|
|||
|
while ( *a != 0 ) {
|
|||
|
if ( tolower(*a) == 'd' ) {
|
|||
|
Recurse = FALSE;
|
|||
|
} else if ( tolower(*a) == 't' ) {
|
|||
|
TotalsOnly = TRUE;
|
|||
|
} else {
|
|||
|
goto usage;
|
|||
|
}
|
|||
|
a++;
|
|||
|
}
|
|||
|
c--;
|
|||
|
v++;
|
|||
|
}
|
|||
|
|
|||
|
if ( c < 2 ) {
|
|||
|
goto usage;
|
|||
|
}
|
|||
|
|
|||
|
if ( _wcsicmp(v[1],L"pin") == 0 ) {
|
|||
|
Operation = OP_PIN;
|
|||
|
} else if ( _wcsicmp(v[1],L"unpin") == 0 ) {
|
|||
|
Operation = OP_UNPIN;
|
|||
|
} else if ( _wcsicmp(v[1],L"delete") == 0 ) {
|
|||
|
Operation = OP_DELETE;
|
|||
|
} else if ( _wcsicmp(v[1],L"query") == 0 ) {
|
|||
|
Operation = OP_QUERY;
|
|||
|
} else if ( _wcsicmp(v[1],L"sparse") == 0 ) {
|
|||
|
Operation = OP_QUERY_SPARSE;
|
|||
|
} else if ( _wcsicmp(v[1],L"full") == 0 ) {
|
|||
|
Operation = OP_QUERY_FULL;
|
|||
|
} else if ( _wcsicmp(v[1],L"free") == 0 ) {
|
|||
|
Operation = OP_QUERY_FREE;
|
|||
|
} else if ( _wcsicmp(v[1],L"read") == 0 ) {
|
|||
|
Operation = OP_READ;
|
|||
|
} else if ( _wcsicmp(v[1],L"nid") == 0 ) {
|
|||
|
Operation = OP_QUERY_NOT_IN_DB;
|
|||
|
} else if ( _wcsicmp(v[1],L"sys") == 0 ) {
|
|||
|
Operation = OP_QUERY_SYSTEM;
|
|||
|
} else if ( _wcsicmp(v[1],L"nosys") == 0 ) {
|
|||
|
Operation = OP_QUERY_NOT_SYSTEM;
|
|||
|
} else {
|
|||
|
goto usage;
|
|||
|
}
|
|||
|
|
|||
|
WalkDirectory( v[0] );
|
|||
|
|
|||
|
return 0;
|
|||
|
|
|||
|
usage:
|
|||
|
|
|||
|
printf( "usage: %ws [-d] [-t] <directory> <pin|unpin|delete|query|sparse|full|free|read|nid|sys|nosys>\n", argv[0] );
|
|||
|
return 1;
|
|||
|
}
|