windows-nt/Source/XPSP1/NT/base/cmd/tree.c

873 lines
25 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1988-1999 Microsoft Corporation
Module Name:
tree.c
Abstract:
Tree walking
--*/
#include "cmd.h"
extern TCHAR CurDrvDir[] ;
extern TCHAR *SaveDir ;
extern DWORD DosErr ;
extern BOOL CtrlCSeen;
PTCHAR SetWildCards( PTCHAR, BOOLEAN );
BOOLEAN IsFATDrive( PTCHAR );
VOID SortFileList( PFS, PSORTDESC, ULONG);
BOOLEAN FindFirstNt( PTCHAR, PWIN32_FIND_DATA, PHANDLE );
BOOLEAN FindNextNt ( PWIN32_FIND_DATA, HANDLE );
STATUS
BuildFSFromPatterns (
IN PDRP pdpr,
IN BOOLEAN fPrintErrors,
IN BOOLEAN fAddWild,
OUT PFS * ppfs
)
{
PCPYINFO pcisFile;
TCHAR szCurDir[MAX_PATH + 2];
TCHAR szFilePattern[MAX_PATH + 2];
PTCHAR pszPatternCur;
PPATDSC ppatdscCur;
PFS pfsFirst;
PFS pfsCur;
ULONG cbPath;
BOOLEAN fFatDrive;
ULONG i;
PTCHAR pszT;
//
// determine FAT drive from original pattern.
// Used in several places to control name format etc.
//
DosErr = 0;
//
// Run through each pattern making all sorts of FAT etc. specific
// changes to it and creating the directory list for it. Then
// combine groups of patterns into common directories and recurse
// for each directory group.
//
*ppfs = pfsFirst = (PFS)gmkstr(sizeof(FS));
pfsFirst->pfsNext = NULL;
pfsFirst->pszDir = NULL;
pfsCur = pfsFirst;
pfsCur->cpatdsc = 1;
for(i = 1, ppatdscCur = &(pdpr->patdscFirst);
i <= pdpr->cpatdsc;
i++, ppatdscCur = ppatdscCur->ppatdscNext) {
pszPatternCur = ppatdscCur->pszPattern;
if (!(fFatDrive = IsFATDrive(pszPatternCur)) && DosErr) {
//
// Error in determining file system type so get out.
//
if (fPrintErrors) PutStdErr(DosErr, NOARGS);
return( FAILURE );
}
ppatdscCur->fIsFat = fFatDrive;
//
// Do any alterations that require wild cards for searching
// such as change .xxx to *.xxx for FAT file system requests
//
// Note that if the return values is a different buffer then
// the input the input will be freed when we are done with the
// Dir command.
//
//
// Note that though SetWildCards will allocate heap for the
// modified pattern this will get freed when FreeStack is
// called at the end of the Dir call.
//
// An out of memory is the only reason to fail and we would not
// return from that but go through the abort call in gmstr
//
if (fAddWild) {
pszT = SetWildCards(pszPatternCur, fFatDrive);
FreeStr(pszPatternCur);
pszPatternCur = pszT;
}
//
// Convert the current pattern into a path and file part
//
// Save the current directory in SaveDir, change to new directory
// and parse pattern into a copy information structure. This also
// converts pszPatternCur into the current directory which also produces
// a fully qualified name.
//
DosErr = 0;
DEBUG((ICGRP, DILVL, "PrintPattern pattern `%ws'", pszPatternCur));
if ((pcisFile = SetFsSetSaveDir(pszPatternCur)) == (PCPYINFO) FAILURE) {
//
// DosErr is set in SetFs.. from GetLastError
//
if (fPrintErrors)
PutStdErr(DosErr, NOARGS);
return( FAILURE );
}
DEBUG((ICGRP, DILVL, "PrintPattern fullname `%ws'", pcisFile->fnptr));
//
// CurDrvDir ends in '\' (old code also and a DOT but I do not
// understand where this would come from I will leave it in for now.
// Remove the final '\' from a copy of the current directory and
// print that version out.
//
mystrcpy(szCurDir,CurDrvDir);
//
// SetFsSetSaveDir changes directories as a side effect. Since all
// work will be in fully qualified paths we do not need this. Also
// since we will change directories for each pattern that is examined
// we will force the directory back to the original each time.
//
// This can not be done until after all use of the current directory
// is made.
//
RestoreSavedDirectory( );
DEBUG((ICGRP, DILVL, "PrintPattern Current Drive `%ws'", szCurDir));
cbPath = mystrlen(szCurDir);
if (cbPath > 3) {
if (fFatDrive && *penulc(szCurDir) == DOT) {
szCurDir[cbPath-2] = NULLC;
} else {
szCurDir[cbPath-1] = NULLC;
}
}
//
// If no room for filename then return
//
if (cbPath >= MAX_PATH -1) {
if (fPrintErrors) PutStdErr( ERROR_FILE_NOT_FOUND, NOARGS );
return(FAILURE);
}
//
// Add filename and possibly ext to szSearchPath
// if no filename or ext, use "*"
//
// If pattern was just extension the SetWildCard had already
// added * to front of extension.
//
if (*(pcisFile->fnptr) == NULLC) {
mystrcpy(szFilePattern, TEXT("*"));
} else {
mystrcpy(szFilePattern, pcisFile->fnptr);
}
DEBUG((ICGRP, DILVL, "DIR:PrintPattern Pattern to search for `%ws'", szFilePattern));
//
// Is name too long
//
if ((cbPath + mystrlen(szFilePattern) + 1) > MAX_PATH ) {
if (fPrintErrors) PutStdErr(ERROR_BUFFER_OVERFLOW, NOARGS);
return( FAILURE );
} else {
//
// If this is a FAT drive and there was a filename with
// no extension then add '.*' (and there is room)
//
if (*pcisFile->fnptr && (!pcisFile->extptr || !*pcisFile->extptr) &&
((mystrlen(szFilePattern) + 2) < MAX_PATH) && fFatDrive && fAddWild) {
mystrcat(szFilePattern, TEXT(".*")) ;
}
}
//
// ppatdscCur->pszPattern will be freed at end of command when everything
// else is freed.
//
ppatdscCur->pszPattern = (PTCHAR)gmkstr(_tcslen(szFilePattern)*sizeof(TCHAR) + sizeof(TCHAR));
mystrcpy(ppatdscCur->pszPattern, szFilePattern);
ppatdscCur->pszDir = (PTCHAR)gmkstr(_tcslen(szCurDir)*sizeof(TCHAR) + sizeof(TCHAR));
mystrcpy(ppatdscCur->pszDir, szCurDir);
if (pfsCur->pszDir) {
//
// changing directories so change directory grouping.
//
if (_tcsicmp(pfsCur->pszDir, ppatdscCur->pszDir)) {
pfsCur->pfsNext = (PFS)gmkstr(sizeof(FS));
pfsCur = pfsCur->pfsNext;
pfsCur->pszDir = (PTCHAR)gmkstr(_tcslen(ppatdscCur->pszDir)*sizeof(TCHAR) + sizeof(TCHAR));
mystrcpy(pfsCur->pszDir, ppatdscCur->pszDir);
pfsCur->pfsNext = NULL;
pfsCur->fIsFat = ppatdscCur->fIsFat;
pfsCur->ppatdsc = ppatdscCur;
pfsCur->cpatdsc = 1;
} else {
pfsCur->cpatdsc++;
}
} else {
//
// Have not filled in current fs descriptor yet.
//
pfsCur->pszDir = (PTCHAR)gmkstr(_tcslen(ppatdscCur->pszDir)*sizeof(TCHAR) + 2*sizeof(TCHAR));
mystrcpy(pfsCur->pszDir, ppatdscCur->pszDir);
pfsCur->fIsFat = ppatdscCur->fIsFat;
pfsCur->ppatdsc = ppatdscCur;
}
} // while for running through pattern list
return( SUCCESS );
}
STATUS
AppendPath(
OUT PTCHAR Buffer,
IN ULONG BufferCount,
IN PTCHAR Prefix,
IN PTCHAR Suffix
)
{
if (mystrlen( Prefix ) + 1 + mystrlen( Suffix ) + 1 > BufferCount) {
return(ERROR_BUFFER_OVERFLOW);
}
mystrcpy( Buffer, Prefix );
//
// Append a \ if there isn't one already at the end of the prefix
//
if (*lastc( Buffer ) != TEXT('\\')) {
mystrcat( Buffer, TEXT( "\\" ));
}
mystrcat( Buffer, Suffix );
return( SUCCESS );
}
STATUS
ExpandAndApplyToFS(
IN PFS FileSpec,
IN PSCREEN pscr,
IN ULONG AttribMask,
IN ULONG AttribValues,
IN PVOID Data OPTIONAL,
IN VOID (*ErrorFunction) (STATUS, PTCHAR, PVOID) OPTIONAL,
IN STATUS (*PreScanFunction) (PFS, PSCREEN, PVOID) OPTIONAL,
IN STATUS (*ScanFunction) (PFS, PFF, PSCREEN, PVOID) OPTIONAL,
IN STATUS (*PostScanFunction) (PFS, PSCREEN, PVOID) OPTIONAL
)
/*++
Routine Description:
Expand a given FS and apply the dispatch functions to it. The pff field is
set to point to the packed set of Win32 find records and the array of pointers
is set up as well.
Arguments:
FileSpec - FS pointer to expand.
AttribMask - mask for attributes we care about when matching
AttribValues - attributes that must match to satisfy the enumeration
Data - pointer to caller data passed to functions
ErrorFunction - routine to call on pattern matching errors
PreScanFunction - routine to call before enumeration begins
ScanFunction - routine to call during enumeration
PostScanFunction - routine to call after enumeration is complete
Return Value:
If any of the applied functions returns a non-SUCCESS status, we return that.
If no matching file is ever found then ERROR_FILE_NOT_FOUND.
Otherwise SUCCESS.
--*/
{
PFF CurrentFF; // Pointer to FF begin built
HANDLE FindHandle; // Find handle
ULONG MaxFFSize = CBFILEINC; // Current max size of FF buffer
ULONG CurrentFFSize = 0; // Bytes in use in FF
PPATDSC CurrentPattern; // Current pattern being expanded
#define SEARCHBUFFERLENGTH (MAX_PATH + 2)
TCHAR szSearchPath[SEARCHBUFFERLENGTH];
ULONG i; // loop count for patterns
PTCHAR p;
STATUS Status;
BOOL FoundAnyFile = FALSE;
DosErr = SUCCESS;
//
// Initialize the FS structure:
// Allocate default size FF array
// Indicate no files stored
//
FileSpec->pff = CurrentFF = (PFF)gmkstr( MaxFFSize );
FileSpec->cff = 0;
FileSpec->FileCount = 0;
FileSpec->DirectoryCount = 0;
FileSpec->cbFileTotal.QuadPart = 0;
for(i = 1, CurrentPattern = FileSpec->ppatdsc;
i <= FileSpec->cpatdsc;
i++, CurrentPattern = CurrentPattern->ppatdscNext ) {
//
// Check immediately if a control-c was hit before
// doing file I/O (which may take a long time on a slow link)
//
if (CtrlCSeen) {
return FAILURE;
}
//
// Build enumeration path. Handle buffer overflow.
//
if (AppendPath( szSearchPath, SEARCHBUFFERLENGTH,
FileSpec->pszDir, CurrentPattern->pszPattern) != SUCCESS) {
return ERROR_BUFFER_OVERFLOW;
}
if (PreScanFunction != 0) {
Status = PreScanFunction( FileSpec, pscr, Data );
if (Status != SUCCESS) {
return Status;
}
}
//
// Fetch all files since we may looking for a file with a attribute that
// is not set (a non-directory etc.
//
if (!FindFirstNt(szSearchPath, &(CurrentFF->data), &FindHandle)) {
if (DosErr) {
//
// Map NO_MORE_FILES/ACCESS_DENIED into FILE_NOT_FOUND
//
if (DosErr == ERROR_NO_MORE_FILES
|| DosErr == ERROR_ACCESS_DENIED) {
DosErr = ERROR_FILE_NOT_FOUND;
}
if (DosErr == ERROR_FILE_NOT_FOUND || DosErr == ERROR_PATH_NOT_FOUND) {
if (ErrorFunction != NULL) {
ErrorFunction( DosErr, szSearchPath, Data );
}
}
}
} else {
do {
//
// Check immediately if a control-c was hit before
// doing file I/O (which may take a long time on a slow link)
//
if (CtrlCSeen) {
findclose( FindHandle );
return(FAILURE);
}
//
// Before allowing this entry to be put in the list check it
// for the proper attribs
//
// AttribMask is a bit mask of attribs we care to look at
// AttribValues is the state these selected bits must be in
// for them to be selected.
//
// IMPORTANT: both of these must be in the same bit order
//
DEBUG((ICGRP, DILVL, " found %ws", CurrentFF->data.cFileName)) ;
DEBUG((ICGRP, DILVL, " attribs %x", CurrentFF->data.dwFileAttributes)) ;
if ((CurrentFF->data.dwFileAttributes & AttribMask) !=
(AttribValues & AttribMask) ) {
continue;
}
//
// We have an entry that matches. Set up FF
//
// Compute the true size of the ff entry and don't forget the zero
// and the DWORD alignment factor.
// Note that CurrentFF->cb is a USHORT to save space. The
// assumption is that MAX_PATH is quite a bit less then 32k
//
// To compute remove the size of the filename field since it is at MAX_PATH.
// also take out the size of the alternative name field
// then add back in the actual size of the field plus 1 byte termination
//
FoundAnyFile = TRUE;
p = (PTCHAR)CurrentFF->data.cFileName;
p += mystrlen( p ) + 1;
mystrcpy( p, CurrentFF->data.cAlternateFileName );
if (*p == TEXT('\0')) {
CurrentFF->obAlternate = 0;
} else {
CurrentFF->obAlternate = (USHORT)(p - CurrentFF->data.cFileName);
}
p += mystrlen( p ) + 1;
CurrentFF->cb = (USHORT)((PBYTE)p - (PBYTE)CurrentFF);
//
// Adjust count to align on DWORD boundaries for mips and risc
// machines
//
CurrentFF->cb = (CurrentFF->cb + sizeof( DWORD ) - 1) & (-(int)sizeof( DWORD ));
if ((CurrentFF->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
FileSpec->FileCount++;
} else {
FileSpec->DirectoryCount++;
}
//
// The FF is built. Call the enumeration function
//
if (ScanFunction != NULL) {
Status = ScanFunction( FileSpec, CurrentFF, pscr, Data );
if (Status != SUCCESS) {
findclose( FindHandle );
return Status;
}
}
//
// If there's no scanning function, save the results
//
if (ScanFunction == NULL) {
FileSpec->cff ++;
//
// Update the accounting information for file buffer info.
//
CurrentFFSize += CurrentFF->cb;
CurrentFF = (PFF) ((PBYTE)CurrentFF + CurrentFF->cb);
//
// Make sure we can handle a max sized entry. If not, resize the buffer.
//
if (CurrentFFSize + sizeof( FF ) >= MaxFFSize ) {
MaxFFSize += 64 * 1024;
DEBUG((ICGRP, DILVL, "\t size of new pff %d", MaxFFSize ));
FileSpec->pff = (PFF)resize( FileSpec->pff, MaxFFSize );
if (FileSpec->pff == NULL) {
DEBUG((ICGRP, DILVL, "\t Could not resize pff" ));
return MSG_NO_MEMORY;
}
CurrentFF = (PFF)((PBYTE)FileSpec->pff + CurrentFFSize);
DEBUG((ICGRP, DILVL, "\t resized CurrentFF new value %lx", CurrentFF)) ;
}
}
} while (FindNextNt(&CurrentFF->data, FindHandle));
findclose( FindHandle );
}
//
// We have an error left over from the FindNext. If it is NO_MORE_FILES then we
// continue only if we have more than one directory.
//
if (DosErr != SUCCESS && DosErr != ERROR_NO_MORE_FILES) {
//
// If not doing multiple file list then error
// If multiple have failed but still have files from previous pattern
//
if (FileSpec->cpatdsc <= 1) {
return DosErr ;
}
}
}
//
// If we did no scan processing, then we must create the pointers since
// SOMEONE is interested in this data.
//
if (ScanFunction == NULL && FileSpec->cff != 0) {
FileSpec->prgpff = (PPFF)gmkstr( sizeof(PFF) * FileSpec->cff );
CurrentFF = FileSpec->pff;
for (i = 0; i < FileSpec->cff; i++) {
FileSpec->prgpff[i] = CurrentFF;
CurrentFF = (PFF) ((PBYTE)CurrentFF + CurrentFF->cb);
}
}
//
// Perform post processing
//
Status = SUCCESS;
if (PostScanFunction != NULL) {
Status = PostScanFunction( FileSpec, pscr, Data );
}
if (Status == SUCCESS && !FoundAnyFile) {
return ERROR_FILE_NOT_FOUND;
} else {
return Status;
}
}
STATUS
WalkTree(
IN PFS FileSpec,
IN PSCREEN pscr,
IN ULONG AttribMask,
IN ULONG AttribValues,
IN BOOL Recurse,
IN PVOID Data OPTIONAL,
IN VOID (*ErrorFunction) (STATUS, PTCHAR, PVOID) OPTIONAL,
IN STATUS (*PreScanFunction) (PFS, PSCREEN, PVOID) OPTIONAL,
IN STATUS (*ScanFunction) (PFS, PFF, PSCREEN, PVOID) OPTIONAL,
IN STATUS (*PostScanFunction) (PFS, PSCREEN, PVOID) OPTIONAL
)
/*++
Routine Description:
Expand a given FS and apply the dispatch functions to it. The pff field is
set to point to the packed set of Win32 find records and the array of pointers
is set up as well. Recurse if necessary.
Arguments:
FileSpec - FS pointer to expand.
pscr - screen for output
AttribMask - mask for attributes we care about when matching
AttribValues - attributes that must match to satisfy the enumeration
Recurse - TRUE => perform the operation in a directory and then descend to
children
Data - pointer to caller data passed to functions
ErrorFunction - routine to call on pattern matching errors
PreScanFunction - routine to call before enumeration begins
ScanFunction - routine to call during enumeration
PostScanFunction - routine to call after enumeration is complete
Return Value:
If any of the applied functions returns a non-SUCCESS status, we return that.
If no matching file is ever found then ERROR_FILE_NOT_FOUND.
Otherwise SUCCESS.
--*/
{
STATUS Status;
FS DirectorySpec;
FS ChildFileSpec;
ULONG i;
BOOL FoundAnyFile = FALSE;
//
// Check for ^C often
//
if (CtrlCSeen) {
return FAILURE;
}
Status = ExpandAndApplyToFS( FileSpec,
pscr,
AttribMask,
AttribValues,
Data,
ErrorFunction,
PreScanFunction,
ScanFunction,
PostScanFunction );
//
// If we succeeded, remember that we did some work
//
if (Status == SUCCESS) {
FoundAnyFile = TRUE;
//
// If we got an unknown error, or we got FILE_NOT_FOUND and we're not
// recursing, return that error
//
} else if ((Status != ERROR_FILE_NOT_FOUND && Status != ERROR_PATH_NOT_FOUND)
|| !Recurse) {
return Status;
}
//
// Free up buffer holding files since we no longer need these.
// Move on to determine if we needed to go to another directory
//
FreeStr((PTCHAR)(FileSpec->pff));
FileSpec->pff = NULL;
if (CtrlCSeen) {
return FAILURE;
}
if (!Recurse)
return SUCCESS;
//
// Build up a copy of the FileSpec and build a list of all
// immediate child directories
//
DirectorySpec.pszDir = (PTCHAR)gmkstr( (_tcslen( FileSpec->pszDir ) + 1 ) * sizeof( TCHAR ));
mystrcpy( DirectorySpec.pszDir, FileSpec->pszDir );
DirectorySpec.ppatdsc = (PPATDSC)gmkstr( sizeof( PATDSC ) );
DirectorySpec.cpatdsc = 1;
DirectorySpec.fIsFat = FileSpec->fIsFat;
DirectorySpec.pfsNext = NULL;
if (FileSpec->fIsFat) {
DirectorySpec.ppatdsc->pszPattern = TEXT("*.*");
} else {
DirectorySpec.ppatdsc->pszPattern = TEXT("*");
}
DirectorySpec.ppatdsc->pszDir = (PTCHAR)gmkstr( (_tcslen( FileSpec->pszDir ) + 1) * sizeof(TCHAR));
mystrcpy( DirectorySpec.ppatdsc->pszDir, DirectorySpec.pszDir );
DirectorySpec.ppatdsc->ppatdscNext = NULL;
Status = ExpandAndApplyToFS( &DirectorySpec,
pscr,
FILE_ATTRIBUTE_DIRECTORY,
FILE_ATTRIBUTE_DIRECTORY,
NULL,
NULL,
NULL,
NULL,
NULL );
//
// If we got an error enumerating the directories, pretend that
// we just didn't find any at all.
//
if (Status != SUCCESS) {
DirectorySpec.cff = 0;
Status = SUCCESS;
}
//
// Check for CtrlC again after calling GetFS because
// GetFS may have returned failure because CtrlC was hit
// inside the GetFS function call
//
if (CtrlCSeen) {
return( FAILURE );
}
//
// Walk the list of found directories, processing each one
//
for (i = 0; i < DirectorySpec.cff; i++) {
PTCHAR DirectoryName;
ULONG NameLength;
//
// Skip recursing on . and ..
//
DirectoryName = DirectorySpec.prgpff[i]->data.cFileName;
if (!_tcscmp( DirectoryName, TEXT(".") )
|| !_tcscmp( DirectoryName, TEXT("..") )) {
continue;
}
//
// Form the new name we will descend into
//
NameLength = _tcslen( FileSpec->pszDir ) + 1 +
_tcslen( DirectoryName ) + 1;
if (NameLength > MAX_PATH) {
PutStdErr( MSG_DIR_TOO_LONG, TWOARGS, FileSpec->pszDir, DirectoryName );
return ERROR_BUFFER_OVERFLOW;
}
memset( &ChildFileSpec, 0, sizeof( ChildFileSpec ));
ChildFileSpec.pszDir = (PTCHAR)gmkstr( NameLength * sizeof( TCHAR ));
AppendPath( ChildFileSpec.pszDir, NameLength, FileSpec->pszDir, DirectoryName );
ChildFileSpec.ppatdsc = FileSpec->ppatdsc;
ChildFileSpec.cpatdsc = FileSpec->cpatdsc;
ChildFileSpec.fIsFat = FileSpec->fIsFat;
Status = WalkTree( &ChildFileSpec,
pscr,
AttribMask,
AttribValues,
Recurse,
Data,
ErrorFunction,
PreScanFunction,
ScanFunction,
PostScanFunction );
FreeStr( (PTCHAR) ChildFileSpec.pff );
ChildFileSpec.pff = NULL;
FreeStr( (PTCHAR) ChildFileSpec.prgpff );
ChildFileSpec.prgpff = NULL;
FreeStr( ChildFileSpec.pszDir );
ChildFileSpec.pszDir = NULL;
//
// If we succeeded, then remember that we actually did something
//
if (Status == SUCCESS) {
FoundAnyFile = TRUE;
//
// If we just couldn't find what we wanted, keep on working
//
} else if (Status == ERROR_BUFFER_OVERFLOW
|| Status == ERROR_FILE_NOT_FOUND
|| Status == ERROR_PATH_NOT_FOUND) {
Status = SUCCESS;
} else if ((DirectorySpec.prgpff[i]->data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
Status = SUCCESS;
} else {
break;
}
}
//
// At bottom of directory tree, free buffer holding
// list of directories.
//
FreeStr( (PTCHAR)DirectorySpec.pszDir );
FreeStr( (PTCHAR)DirectorySpec.pff );
FreeStr( (PTCHAR)DirectorySpec.prgpff );
if (Status == SUCCESS && !FoundAnyFile) {
return ERROR_FILE_NOT_FOUND;
} else {
return Status;
}
}