413 lines
14 KiB
C
413 lines
14 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) 1988-1999 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
cpparse.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
Parsing for the copy command
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "cmd.h"
|
||
|
|
||
|
/* useful macro */
|
||
|
#define Wild(spec) ((spec)->flags & (CI_NAMEWILD))
|
||
|
|
||
|
/* Globals from cpwork.c */
|
||
|
extern int copy_mode;
|
||
|
extern unsigned DosErr ;
|
||
|
/* Globals from command */
|
||
|
|
||
|
extern TCHAR SwitChar;
|
||
|
extern BOOLEAN VerifyCurrent; // cpwork.c
|
||
|
|
||
|
|
||
|
/* parse_args
|
||
|
*
|
||
|
* This is the key function which decides how copy will react to any
|
||
|
* given invocation. It parses the arguments, fills the source and
|
||
|
* destination structures, and sets the copy_mode.
|
||
|
*
|
||
|
* ENTRY
|
||
|
*
|
||
|
* args - raw argument line entered by the user
|
||
|
*
|
||
|
* source - pointer to an initialized cpyinfo struct. This one isn't
|
||
|
* used; it's just a header to a linked list containing the
|
||
|
* actual source structures.
|
||
|
*
|
||
|
* dest - like source, but there will be at most one filled-in dest struct.
|
||
|
*
|
||
|
* EXIT
|
||
|
*
|
||
|
* source - the caller's source pointer has not, of course, been changed. It
|
||
|
* still points to the empty header (which might not be completely
|
||
|
* empty - see handle_switch for details). It is the head of a
|
||
|
* linked list of structures, terminated by one with a NULL in its
|
||
|
* next field. Each structure corresponds to a source spec.
|
||
|
*
|
||
|
* dest - if a destination was specified, the caller's dest pointer points
|
||
|
* to an empty header which points to the actual destination struct.
|
||
|
* If the destination is implicit, the fspec and next fields of the
|
||
|
* struct are still NULL, but the flag field has been filled in with
|
||
|
* the copy mode. If the user specified a copy mode (ascii or
|
||
|
* binary) one or more times, the last switch applies to the
|
||
|
* destination. If not, CI_NOTSET is used.
|
||
|
*
|
||
|
* copy_mode - set to type of copy being done - COPY, COMBINE, CONCAT, or
|
||
|
* TOUCH.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
parse_args(
|
||
|
PTCHAR args,
|
||
|
PCPYINFO source,
|
||
|
PCPYINFO dest)
|
||
|
{
|
||
|
TCHAR *tas; /* tokenized argument string */
|
||
|
TCHAR copydelims[4]; /* copy token delimters */
|
||
|
int parse_state = SEEN_NO_FILES; /* state of the parser */
|
||
|
int all_sources_wildcards = TRUE, /* flag to help decide copy mode */
|
||
|
number_of_sources = 0, /* number of specs seen so far */
|
||
|
current_copy_mode = CI_NOTSET, /* ascii, binary, or not set */
|
||
|
tlen; /* offset to next token */
|
||
|
BOOL ShortNameSwitch=FALSE;
|
||
|
BOOL RestartableSwitch=FALSE;
|
||
|
BOOL PromptOnOverwrite;
|
||
|
|
||
|
copydelims[0] = PLUS; /* delimiters for token parser */
|
||
|
copydelims[1] = COMMA;
|
||
|
copydelims[2] = SwitChar;
|
||
|
copydelims[3] = NULLC;
|
||
|
|
||
|
//
|
||
|
// Get default prompt okay flag from COPYCMD variable. Allow
|
||
|
// user to override with /Y or /-Y switch. Always assume /Y
|
||
|
// if command executed from inside batch script or via CMD.EXE
|
||
|
// command line switch (/C or /K)
|
||
|
//
|
||
|
if (SingleBatchInvocation || SingleCommandInvocation || CurrentBatchFile != 0)
|
||
|
PromptOnOverwrite = FALSE; // Assume /Y
|
||
|
else
|
||
|
PromptOnOverwrite = TRUE; // Assume /-Y
|
||
|
GetPromptOkay(MyGetEnvVarPtr(TEXT("COPYCMD")), &PromptOnOverwrite);
|
||
|
|
||
|
if (!*(tas = TokStr(args, copydelims, TS_SDTOKENS))) /* tokenize args */
|
||
|
|
||
|
copy_error(MSG_BAD_SYNTAX,CE_NOPCOUNT); /* M003 */
|
||
|
|
||
|
for ( ; *tas ; tas += tlen+1 ) /* cycle through tokens in args */
|
||
|
{
|
||
|
tlen = mystrlen(tas);
|
||
|
switch(*tas) {
|
||
|
case PLUS:
|
||
|
if (parse_state != JUST_SEEN_SOURCE_FILE)
|
||
|
|
||
|
/* M003 */ copy_error(MSG_BAD_SYNTAX,CE_NOPCOUNT);
|
||
|
parse_state = SEEN_PLUS_EXPECTING_SOURCE_FILE;
|
||
|
break;
|
||
|
|
||
|
case COMMA:
|
||
|
if (parse_state == SEEN_COMMA_EXPECTING_SECOND)
|
||
|
parse_state = SEEN_TWO_COMMAS;
|
||
|
else if ((parse_state == SEEN_PLUS_EXPECTING_SOURCE_FILE) &&
|
||
|
(number_of_sources == 1))
|
||
|
parse_state = SEEN_COMMA_EXPECTING_SECOND;
|
||
|
else if (parse_state != JUST_SEEN_SOURCE_FILE)
|
||
|
|
||
|
/* M003 */ copy_error(MSG_BAD_SYNTAX,CE_NOPCOUNT);
|
||
|
break;
|
||
|
|
||
|
default: /* file or switch */
|
||
|
if (*tas == SwitChar) {
|
||
|
handle_switch(tas,source,dest,parse_state,
|
||
|
¤t_copy_mode,
|
||
|
&ShortNameSwitch,
|
||
|
&RestartableSwitch,
|
||
|
&PromptOnOverwrite
|
||
|
);
|
||
|
tlen = 2 + _tcslen(&tas[2]); /* offset past switch */
|
||
|
}
|
||
|
else
|
||
|
/*509*/ { /* must be device or file */
|
||
|
/*509*/ mystrcpy(tas, StripQuotes(tas));
|
||
|
parse_state = found_file(tas,parse_state,&source,&dest,
|
||
|
&number_of_sources,&all_sources_wildcards,current_copy_mode);
|
||
|
/*509*/ }
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* set copy mode appropriately */
|
||
|
set_mode(number_of_sources,parse_state,all_sources_wildcards,dest);
|
||
|
|
||
|
|
||
|
if (ShortNameSwitch)
|
||
|
source->flags |= CI_SHORTNAME;
|
||
|
|
||
|
if (PromptOnOverwrite)
|
||
|
dest->flags |= CI_PROMPTUSER;
|
||
|
|
||
|
if (RestartableSwitch)
|
||
|
//
|
||
|
// If running on a platform that does not support CopyFileEx
|
||
|
// display an error message if they try to use /Z option
|
||
|
//
|
||
|
#ifndef WIN95_CMD
|
||
|
if (lpCopyFileExW != NULL)
|
||
|
source->flags |= CI_RESTARTABLE;
|
||
|
else
|
||
|
#endif
|
||
|
copy_error(MSG_NO_COPYFILEEX,CE_NOPCOUNT);
|
||
|
|
||
|
|
||
|
|
||
|
/* if no dest specified, put current copy mode in header */
|
||
|
if (number_of_sources != 0) /*M005 if sources specd */
|
||
|
{ /*M005 then */
|
||
|
if (parse_state != SEEN_DEST) /*M005 if seen a destspec*/
|
||
|
{ /*M005 then */
|
||
|
dest->flags = current_copy_mode; /*M005 save cur mode */
|
||
|
if (PromptOnOverwrite)
|
||
|
dest->flags |= CI_PROMPTUSER;
|
||
|
} /*M005 endif */
|
||
|
} /*M005 */
|
||
|
else /*M005 */
|
||
|
{ /*M005 else */
|
||
|
copy_error(MSG_BAD_SYNTAX,CE_NOPCOUNT); /*M005 disp inv#parms */
|
||
|
} /*M005 endif */
|
||
|
}
|
||
|
|
||
|
/* handle_switch
|
||
|
*
|
||
|
* There are four switches to handle: /A, /B, /F,
|
||
|
* /V
|
||
|
*
|
||
|
* /B and /A set the copy mode to binary and ascii respectively.
|
||
|
* This change applies to the previous filespec and all succeeding ones.
|
||
|
* Figure out which was the last filespec read and set its flags; then set
|
||
|
* the current copy mode.
|
||
|
*
|
||
|
* Note: If there was no previous filespec, the source pointer is pointing
|
||
|
* at an unitialized header. In this case, we set the flags in this
|
||
|
* struct to the current copy mode. This doesn't accomplish
|
||
|
* anything, but the code is simpler if it doesn't bother to check.
|
||
|
*
|
||
|
* /F indicates that the copy should fail if we can't copy the EAs.
|
||
|
*
|
||
|
* /V enables the much slower verified copy mode. This is very
|
||
|
* easy to handle; call a magic internal DOS routine. All writes are then
|
||
|
* verified automagically without our interference.
|
||
|
*
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
void handle_switch(
|
||
|
TCHAR *tas,
|
||
|
PCPYINFO source,
|
||
|
PCPYINFO dest,
|
||
|
int parse_state,
|
||
|
int *current_copy_mode,
|
||
|
PBOOL ShortNameSwitch,
|
||
|
PBOOL RestartableSwitch,
|
||
|
PBOOL PromptOnOverwrite
|
||
|
)
|
||
|
{
|
||
|
TCHAR ch = (TCHAR) _totupper(tas[2]);
|
||
|
TCHAR szTmp[16];
|
||
|
|
||
|
if (_tcslen(&tas[2]) < 14) {
|
||
|
_tcscpy(szTmp,tas);
|
||
|
_tcscat(szTmp,&tas[2]);
|
||
|
if (GetPromptOkay(szTmp, PromptOnOverwrite))
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (ch == TEXT( 'A' ) || ch == TEXT( 'B' )) {
|
||
|
*current_copy_mode = (ch == TEXT( 'A' ) ? CI_ASCII : CI_BINARY);
|
||
|
if (parse_state == SEEN_DEST) { /* then prev spec was dest */
|
||
|
dest->flags &= (~CI_ASCII) & (~CI_BINARY) & (~CI_NOTSET);
|
||
|
dest->flags |= *current_copy_mode;
|
||
|
}
|
||
|
else { /* set last source spec */
|
||
|
source->flags &= (~CI_ASCII) & (~CI_BINARY) & (~CI_NOTSET);
|
||
|
source->flags |= *current_copy_mode;
|
||
|
}
|
||
|
}
|
||
|
else if (ch == TEXT( 'V' )) {
|
||
|
VerifyCurrent = 1;
|
||
|
}
|
||
|
else if (ch == TEXT( 'N' )) {
|
||
|
*ShortNameSwitch = TRUE;
|
||
|
}
|
||
|
else if (ch == TEXT( 'Z' )) {
|
||
|
*RestartableSwitch = TRUE;
|
||
|
}
|
||
|
else if (ch == TEXT( 'D' )) {
|
||
|
*current_copy_mode |= CI_ALLOWDECRYPT;
|
||
|
dest->flags |= CI_ALLOWDECRYPT;
|
||
|
}
|
||
|
else {
|
||
|
copy_error(MSG_BAD_SYNTAX,CE_NOPCOUNT); /* M003 */
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* found_file
|
||
|
*
|
||
|
* Token was a file or device. Put it in the appropriate structure and
|
||
|
* run ScanFSpec on it. Figure out what the new parser state should be
|
||
|
* and return it. Note: This function has one inelegant side-effect; if
|
||
|
* it sees a destination file after a double-comma ("copy foo+,, bar"), it
|
||
|
* sets the copy_mode to TOUCH. Otherwise the copier wouldn't remember to
|
||
|
* use the current date and time with the copied file. Set_mode notices that
|
||
|
* the mode is TOUCH and doesn't change it. This works because the copy modes
|
||
|
* CONCAT, COMBINE, COPY, and TOUCH are mutually exclusive in all but this one
|
||
|
* case where we both copy and touch at once.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
found_file(
|
||
|
PTCHAR token,
|
||
|
int parse_state,
|
||
|
PCPYINFO *source,
|
||
|
PCPYINFO *dest,
|
||
|
int *num_sources,
|
||
|
int *all_sources_wild,
|
||
|
int mode)
|
||
|
{
|
||
|
PCPYINFO add_filespec_to_struct();
|
||
|
|
||
|
/* if it's a source, add to the list of source structures */
|
||
|
if ((parse_state == SEEN_NO_FILES) ||
|
||
|
(parse_state == SEEN_PLUS_EXPECTING_SOURCE_FILE)) {
|
||
|
*source = add_filespec_to_struct(*source,token,mode);
|
||
|
ScanFSpec(*source);
|
||
|
//
|
||
|
// Could have an abort from access to floppy etc. so get out
|
||
|
// of copy. If it is just an invalid name then proceed since
|
||
|
// it is a wild card. If is actually invalid we will catch
|
||
|
// this later.
|
||
|
if (DosErr != SUCCESS
|
||
|
&& DosErr != ERROR_INVALID_NAME
|
||
|
#ifdef WIN95_CMD
|
||
|
&& (!Wild(*source) || DosErr != ERROR_FILE_NOT_FOUND)
|
||
|
#endif
|
||
|
) {
|
||
|
copy_error(DosErr, CE_NOPCOUNT);
|
||
|
}
|
||
|
|
||
|
parse_state = JUST_SEEN_SOURCE_FILE;
|
||
|
(*num_sources)++;
|
||
|
if (!Wild(*source))
|
||
|
*all_sources_wild = FALSE;
|
||
|
}
|
||
|
|
||
|
/* if it's a dest, make it the dest structure */
|
||
|
else if ((parse_state == SEEN_TWO_COMMAS) ||
|
||
|
(parse_state == JUST_SEEN_SOURCE_FILE)) {
|
||
|
if (parse_state == SEEN_TWO_COMMAS)
|
||
|
copy_mode = TOUCH;
|
||
|
*dest = add_filespec_to_struct(*dest,token,mode);
|
||
|
ScanFSpec(*dest);
|
||
|
//
|
||
|
// Could have an abort from access to floppy etc. so get out
|
||
|
// of copy
|
||
|
//
|
||
|
if ((DosErr) && (DosErr != ERROR_INVALID_NAME)) {
|
||
|
copy_error(DosErr, CE_NOPCOUNT);
|
||
|
}
|
||
|
parse_state = SEEN_DEST;
|
||
|
}
|
||
|
|
||
|
/* if we have a dest or the syntax is messed up, complain */
|
||
|
else
|
||
|
|
||
|
copy_error(MSG_BAD_SYNTAX,CE_NOPCOUNT); /* M003 */
|
||
|
return(parse_state);
|
||
|
}
|
||
|
|
||
|
/* set_mode
|
||
|
*
|
||
|
* Given all the current state information, determine which kind of copy
|
||
|
* is being done and set the copy_mode. As explained in found_file, if
|
||
|
* the mode has been set to TOUCH, set_mode doesn't do anything.
|
||
|
*/
|
||
|
|
||
|
void set_mode(number_sources,parse_state,all_sources_wildcards,dest)
|
||
|
int number_sources,
|
||
|
parse_state,
|
||
|
all_sources_wildcards;
|
||
|
PCPYINFO dest;
|
||
|
{
|
||
|
if (copy_mode == TOUCH) /* tacky special case */
|
||
|
return;
|
||
|
|
||
|
/* If there was one source, we are doing a touch, a concatenate,
|
||
|
* or a copy. It's a touch if there was one file and we saw a "+" or a
|
||
|
* "+,,". If the source was a wildcard and the destination is a file,
|
||
|
* it's a concatenate. Otherwise it's a copy.
|
||
|
*/
|
||
|
|
||
|
if (number_sources == 1) {
|
||
|
if ((parse_state == SEEN_TWO_COMMAS) ||
|
||
|
(parse_state == SEEN_PLUS_EXPECTING_SOURCE_FILE))
|
||
|
copy_mode = TOUCH;
|
||
|
else if (all_sources_wildcards && dest->fspec && !Wild(dest) &&
|
||
|
!(*lastc(dest->fspec) == COLON))
|
||
|
copy_mode = CONCAT;
|
||
|
}
|
||
|
|
||
|
/* For more than one source, we are combining or concatenating. It's
|
||
|
* a combine if all sources were wildcards and the destination is either
|
||
|
* a wildcard, a directory, or implicit. Otherwise it's a concatenation.
|
||
|
*/
|
||
|
|
||
|
else {
|
||
|
if ((all_sources_wildcards) &&
|
||
|
((!dest->fspec) || Wild(dest) ||
|
||
|
(*lastc(dest->fspec) == COLON)))
|
||
|
copy_mode = COMBINE;
|
||
|
else
|
||
|
copy_mode = CONCAT;
|
||
|
}
|
||
|
DEBUG((FCGRP,COLVL,"Set flags: copy_mode = %d",copy_mode));
|
||
|
}
|
||
|
|
||
|
|
||
|
/* add_filespec_to_struct
|
||
|
*
|
||
|
* ENTRY
|
||
|
*
|
||
|
* spec - points to a filled structure with a NULL in its next field.
|
||
|
*
|
||
|
* file_spec - filename to put in new structure
|
||
|
*
|
||
|
* mode - copy mode
|
||
|
*
|
||
|
* EXIT
|
||
|
*
|
||
|
* Return a pointer to a new cpyinfo struct with its fields filled in
|
||
|
* appropriately. The old spec struct's next field points to this new
|
||
|
* structure.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
PCPYINFO
|
||
|
add_filespec_to_struct(spec,file_spec,mode)
|
||
|
PCPYINFO spec;
|
||
|
TCHAR *file_spec;
|
||
|
int mode;
|
||
|
{
|
||
|
spec->next = NewCpyInfo( );
|
||
|
spec = spec->next;
|
||
|
spec->fspec = file_spec;
|
||
|
spec->flags |= mode;
|
||
|
return(spec);
|
||
|
}
|