|
|
/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
expand.c
Abstract:
This module implements the file expand command.
Author:
Mike Sliger (msliger) 29-Apr-1999
Revision History:
--*/
#include "cmdcons.h"
#pragma hdrstop
//
// structure tunneled thru SpExpandFile to carry info to/from callback
//
typedef struct { LPWSTR FileSpec; BOOLEAN DisplayFiles; BOOLEAN MatchedAnyFiles; ULONG NumberOfFilesDone; BOOLEAN UserAborted; BOOLEAN OverwriteExisting; } EXPAND_CONTEXT;
BOOLEAN pRcCheckForBreak( VOID );
EXPAND_CALLBACK_RESULT pRcExpandCallback( EXPAND_CALLBACK_MESSAGE Message, PWSTR FileName, PLARGE_INTEGER FileSize, PLARGE_INTEGER FileTime, ULONG FileAttributes, PVOID UserData );
BOOL pRcPatternMatch( LPWSTR pszString, LPWSTR pszPattern, IN BOOL fImplyDotAtEnd );
ULONG RcCmdExpand( IN PTOKENIZED_LINE TokenizedLine ) { PLINE_TOKEN Token; LPWSTR Arg; LPWSTR SrcFile = NULL; LPWSTR DstFile = NULL; LPWSTR FileSpec = NULL; LPWSTR SrcNtFile = NULL; LPWSTR DstNtPath = NULL; LPWSTR s; IO_STATUS_BLOCK IoStatusBlock; UNICODE_STRING UnicodeString; HANDLE Handle; OBJECT_ATTRIBUTES Obja; NTSTATUS Status; LPWSTR YesNo; WCHAR Text[3]; IO_STATUS_BLOCK status_block; FILE_BASIC_INFORMATION fileInfo; WCHAR * pos; ULONG CopyFlags = 0; BOOLEAN DisplayFileList = FALSE; BOOLEAN OverwriteExisting = NoCopyPrompt; EXPAND_CONTEXT Context;
ASSERT(TokenizedLine->TokenCount >= 1);
if (RcCmdParseHelp( TokenizedLine, MSG_EXPAND_HELP )) { goto exit; }
//
// Parse command line
//
for( Token = TokenizedLine->Tokens->Next; Token != NULL; Token = Token->Next ) {
Arg = Token->String; if(( Arg[0] == L'-' ) || ( Arg[0] == L'/' )) { switch( Arg[1] ) { case L'F': case L'f': if(( Arg[2] == L':' ) && ( FileSpec == NULL )) { FileSpec = &Arg[3]; } else { RcMessageOut(MSG_SYNTAX_ERROR); goto exit; } break;
case L'D': case L'd': if ( Arg[2] == L'\0' ) { DisplayFileList = TRUE; } else { RcMessageOut(MSG_SYNTAX_ERROR); goto exit; } break;
case L'Y': case L'y': if ( Arg[2] == L'\0' ) { OverwriteExisting = TRUE; } else { RcMessageOut(MSG_SYNTAX_ERROR); goto exit; } break;
default: RcMessageOut(MSG_SYNTAX_ERROR); goto exit; } } else if( SrcFile == NULL ) { SrcFile = Arg; } else if( DstFile == NULL ) { DstFile = SpDupStringW( Arg ); } else { RcMessageOut(MSG_SYNTAX_ERROR); goto exit; } }
if(( SrcFile == NULL ) || (( DstFile != NULL ) && ( DisplayFileList == TRUE ))) {
RcMessageOut(MSG_SYNTAX_ERROR); goto exit; }
if ( RcDoesPathHaveWildCards( SrcFile )) { RcMessageOut(MSG_DIR_WILDCARD_NOT_SUPPORTED); goto exit; }
//
// Translate the source name to the NT namespace
//
if (!RcFormFullPath( SrcFile, _CmdConsBlock->TemporaryBuffer, TRUE )) { RcMessageOut(MSG_INVALID_PATH); goto exit; }
SrcNtFile = SpDupStringW( _CmdConsBlock->TemporaryBuffer );
if ( !DisplayFileList ) { BOOLEAN OnRemovableMedia;
//
// Create a destination path name when the user did not
// provide one. We use the current drive and directory.
//
if( DstFile == NULL ) { RcGetCurrentDriveAndDir( _CmdConsBlock->TemporaryBuffer ); DstFile = SpDupStringW( _CmdConsBlock->TemporaryBuffer ); }
//
// create the destination paths
//
if (!RcFormFullPath( DstFile, _CmdConsBlock->TemporaryBuffer, FALSE )) { RcMessageOut(MSG_INVALID_PATH); goto exit; }
if (!RcIsPathNameAllowed(_CmdConsBlock->TemporaryBuffer,FALSE,FALSE)) { RcMessageOut(MSG_ACCESS_DENIED); goto exit; }
if (!RcFormFullPath( DstFile, _CmdConsBlock->TemporaryBuffer, TRUE )) { RcMessageOut(MSG_INVALID_PATH); goto exit; }
DstNtPath = SpDupStringW( _CmdConsBlock->TemporaryBuffer );
//
// check for removable media
//
Status = RcIsFileOnRemovableMedia(DstNtPath, &OnRemovableMedia);
if (AllowRemovableMedia == FALSE && (!NT_SUCCESS(Status) || OnRemovableMedia)) { RcMessageOut(MSG_ACCESS_DENIED); goto exit; } }
//
// setup context for callbacks
//
RtlZeroMemory(&Context, sizeof(Context)); Context.FileSpec = FileSpec; Context.DisplayFiles = DisplayFileList; Context.OverwriteExisting = OverwriteExisting;
if ( DisplayFileList ) { pRcEnableMoreMode(); }
Status = SpExpandFile( SrcNtFile, DstNtPath, pRcExpandCallback, &Context );
pRcDisableMoreMode();
if( !NT_SUCCESS(Status) && !Context.UserAborted ) {
RcNtError( Status, MSG_CANT_EXPAND_FILE );
} else if (( Context.NumberOfFilesDone == 0 ) && ( Context.MatchedAnyFiles == FALSE ) && ( Context.FileSpec != NULL )) {
RcMessageOut( MSG_EXPAND_NO_MATCH, Context.FileSpec, SrcFile ); }
if ( Context.MatchedAnyFiles ) { if ( DisplayFileList ) { RcMessageOut( MSG_EXPAND_SHOWN, Context.NumberOfFilesDone ); } else { RcMessageOut( MSG_EXPAND_COUNT, Context.NumberOfFilesDone ); } }
exit:
if( SrcNtFile ) { SpMemFree( SrcNtFile ); }
if( DstFile ) { SpMemFree( DstFile ); }
if( DstNtPath ) { SpMemFree( DstNtPath ); }
return 1; }
EXPAND_CALLBACK_RESULT pRcExpandCallback( EXPAND_CALLBACK_MESSAGE Message, PWSTR FileName, PLARGE_INTEGER FileSize, PLARGE_INTEGER FileTime, ULONG FileAttributes, PVOID UserData ) { EXPAND_CONTEXT * Context = (EXPAND_CONTEXT * ) UserData; LPWSTR YesNo; EXPAND_CALLBACK_RESULT rc; WCHAR Text[3];
switch ( Message ) { case EXPAND_COPY_FILE:
//
// Watch for Ctl-C or ESC while processing
//
if ( pRcCheckForBreak() ) { Context->UserAborted = TRUE; return( EXPAND_ABORT ); }
//
// See if filename matches filespec pattern, if any
//
if ( Context->FileSpec != NULL ) {
//
// To be "*.*"-friendly, we need to know if there is a real
// dot in the last element of the string to be matched
//
BOOL fAllowImpliedDot = TRUE; LPWSTR p;
for ( p = FileName; *p != L'\0'; p++ ) { if ( *p == L'.' ) { fAllowImpliedDot = FALSE; } else if ( *p == L'\\' ) { fAllowImpliedDot = TRUE; } } if ( !pRcPatternMatch( FileName, Context->FileSpec, fAllowImpliedDot )) { //
// File doesn't match given spec: skip it
//
return( EXPAND_SKIP_THIS_FILE ); } }
Context->MatchedAnyFiles = TRUE; // don't report "no matches"
if ( Context->DisplayFiles ) {
//
// We're just listing file names, and we must do it now, because
// we're going to tell ExpandFile to skip this one, so this will
// be the last we here about it.
//
WCHAR LineOut[50]; WCHAR *p;
//
// Format the date and time, which go first.
//
RcFormatDateTime(FileTime,LineOut); RcTextOut(LineOut);
//
// 2 spaces for separation
//
RcTextOut(L" ");
//
// File attributes.
//
p = LineOut;
*p++ = L'-';
if(FileAttributes & FILE_ATTRIBUTE_ARCHIVE) { *p++ = L'a'; } else { *p++ = L'-'; } if(FileAttributes & FILE_ATTRIBUTE_READONLY) { *p++ = L'r'; } else { *p++ = L'-'; } if(FileAttributes & FILE_ATTRIBUTE_HIDDEN) { *p++ = L'h'; } else { *p++ = L'-'; } if(FileAttributes & FILE_ATTRIBUTE_SYSTEM) { *p++ = L's'; } else { *p++ = L'-'; }
*p++ = L'-'; *p++ = L'-'; *p++ = L'-'; *p = 0;
RcTextOut(LineOut);
//
// 2 spaces for separation
//
RcTextOut(L" ");
//
// Now, put the size in there. Right justified and space padded
// up to 8 chars. Otherwise unjustified or padded.
//
RcFormat64BitIntForOutput(FileSize->QuadPart,LineOut,TRUE); if(FileSize->QuadPart > 99999999i64) { RcTextOut(LineOut); } else { RcTextOut(LineOut+11); // outputs 8 chars
}
RcTextOut(L" ");
//
// Finally, put the filename on the line.
//
if( !RcTextOut( FileName ) || !RcTextOut( L"\r\n" )) {
Context->UserAborted = TRUE; return( EXPAND_ABORT ); /* user aborted display output */ }
Context->NumberOfFilesDone++;
return( EXPAND_SKIP_THIS_FILE );
} // end if DisplayFiles
//
// This file qualified, and we're not just displaying, so tell
// ExpandFile to do it.
//
return( EXPAND_COPY_THIS_FILE );
case EXPAND_COPIED_FILE:
//
// Notification that a file has been copied successfully.
//
RcMessageOut( MSG_EXPANDED, FileName); Context->NumberOfFilesDone++;
return( EXPAND_NO_ERROR );
case EXPAND_QUERY_OVERWRITE:
//
// Query for approval to overwrite an existing file.
//
if ( Context->OverwriteExisting ) { return( EXPAND_COPY_THIS_FILE ); }
rc = EXPAND_SKIP_THIS_FILE;
YesNo = SpRetreiveMessageText( ImageBase, MSG_YESNOALLQUIT, NULL, 0 ); if ( YesNo ) {
RcMessageOut( MSG_COPY_OVERWRITE_QUIT, FileName ); if( RcLineIn( Text, 2 ) ) { if (( Text[0] == YesNo[2] ) || ( Text[0] == YesNo[3] )) {
//
// Yes, we may overwrite this file
//
rc = EXPAND_COPY_THIS_FILE;
} else if (( Text[0] == YesNo[4] ) || ( Text[0] == YesNo[5] )) {
//
// All, we may overwrite this file, and don't prompt again
//
Context->OverwriteExisting = TRUE; rc = EXPAND_COPY_THIS_FILE;
} else if (( Text[0] == YesNo[6] ) || ( Text[0] == YesNo[7] )) {
//
// No, and stop too.
//
Context->UserAborted = TRUE; rc = EXPAND_ABORT; } } SpMemFree( YesNo ); }
return( rc );
case EXPAND_NOTIFY_MULTIPLE:
//
// We're being advised that the source contains multiple files.
// If we don't have a selective filespec, we'll abort.
//
if ( Context->FileSpec == NULL ) {
RcMessageOut( MSG_FILESPEC_REQUIRED ); Context->UserAborted = TRUE; return ( EXPAND_ABORT ); }
return ( EXPAND_CONTINUE );
case EXPAND_NOTIFY_CANNOT_EXPAND:
//
// We're being advised that the source file format is not
// recognized. We display the file name and abort.
//
RcMessageOut( MSG_CANT_EXPAND_FILE, FileName ); Context->UserAborted = TRUE;
return ( EXPAND_ABORT );
case EXPAND_NOTIFY_CREATE_FAILED:
//
// We're being advised that the current target file cannot be
// created. We display the file name and abort.
//
RcMessageOut( MSG_EXPAND_FAILED, FileName ); Context->UserAborted = TRUE;
return ( EXPAND_ABORT );
default:
//
// Ignore any unexpected callback.
//
return( EXPAND_NO_ERROR ); } }
BOOLEAN pRcCheckForBreak( VOID ) { while ( SpInputIsKeyWaiting() ) {
ULONG Key = SpInputGetKeypress();
switch ( Key ) {
case ASCI_ETX: case ASCI_ESC: RcMessageOut( MSG_BREAK ); return TRUE;
default: break; } }
return FALSE; }
//
// pRcPatternMatch() & helpers
//
#define WILDCARD L'*' /* zero or more of any character */
#define WILDCHAR L'?' /* one of any character (does not match END) */
#define END L'\0' /* terminal character */
#define DOT L'.' /* may be implied at end ("hosts" matches "*.") */
static int __inline Lower(c) { if ((c >= L'A') && (c <= L'Z')) { return(c + (L'a' - L'A')); } else { return(c); } }
static int __inline CharacterMatch(WCHAR chCharacter, WCHAR chPattern) { if (Lower(chCharacter) == Lower(chPattern)) { return(TRUE); } else { return(FALSE); } }
BOOL pRcPatternMatch( LPWSTR pszString, LPWSTR pszPattern, IN BOOL fImplyDotAtEnd ) { /* RECURSIVE */
//
// This function does not deal with 8.3 conventions which might
// be expected for filename comparisons. (In an 8.3 environment,
// "alongfilename.html" would match "alongfil.htm")
//
// This code is NOT MBCS-enabled
//
for ( ; ; ) { switch (*pszPattern) {
case END:
//
// Reached end of pattern, so we're done. Matched if
// end of string, no match if more string remains.
//
return(*pszString == END);
case WILDCHAR:
//
// Next in pattern is a wild character, which matches
// anything except end of string. If we reach the end
// of the string, the implied DOT would also match.
//
if (*pszString == END) { if (fImplyDotAtEnd == TRUE) { fImplyDotAtEnd = FALSE; } else { return(FALSE); } } else { pszString++; }
pszPattern++;
break;
case WILDCARD:
//
// Next in pattern is a wildcard, which matches anything.
// Find the required character that follows the wildcard,
// and search the string for it. At each occurence of the
// required character, try to match the remaining pattern.
//
// There are numerous equivalent patterns in which multiple
// WILDCARD and WILDCHAR are adjacent. We deal with these
// before our search for the required character.
//
// Each WILDCHAR burns one non-END from the string. An END
// means we have a match. Additional WILDCARDs are ignored.
//
for ( ; ; ) { pszPattern++;
if (*pszPattern == END) { return(TRUE); } else if (*pszPattern == WILDCHAR) { if (*pszString == END) { if (fImplyDotAtEnd == TRUE) { fImplyDotAtEnd = FALSE; } else { return(FALSE); } } else { pszString++; } } else if (*pszPattern != WILDCARD) { break; } }
//
// Now we have a regular character to search the string for.
//
while (*pszString != END) { //
// For each match, use recursion to see if the remainder
// of the pattern accepts the remainder of the string.
// If it does not, continue looking for other matches.
//
if (CharacterMatch(*pszString, *pszPattern) == TRUE) { if (pRcPatternMatch(pszString + 1, pszPattern + 1, fImplyDotAtEnd) == TRUE) { return(TRUE); } }
pszString++; }
//
// Reached end of string without finding required character
// which followed the WILDCARD. If the required character
// is a DOT, consider matching the implied DOT.
//
// Since the remaining string is empty, the only pattern which
// could match after the DOT would be zero or more WILDCARDs,
// so don't bother with recursion.
//
if ((*pszPattern == DOT) && (fImplyDotAtEnd == TRUE)) { pszPattern++;
while (*pszPattern != END) { if (*pszPattern != WILDCARD) { return(FALSE); }
pszPattern++; }
return(TRUE); }
//
// Reached end of the string without finding required character.
//
return(FALSE); break;
default:
//
// Nothing special about the pattern character, so it
// must match source character.
//
if (CharacterMatch(*pszString, *pszPattern) == FALSE) { if ((*pszPattern == DOT) && (*pszString == END) && (fImplyDotAtEnd == TRUE)) { fImplyDotAtEnd = FALSE; } else { return(FALSE); } }
if (*pszString != END) { pszString++; }
pszPattern++; } } }
|