windows-nt/Source/XPSP1/NT/base/ntsetup/textmode/cmdcons/expand.c
2020-09-26 16:20:57 +08:00

786 lines
19 KiB
C

/*++
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 ) {
//
// 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
//
if (AllowRemovableMedia == FALSE && RcIsFileOnRemovableMedia(DstNtPath) == STATUS_SUCCESS) {
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++;
}
}
}