Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
wine
Manage
Activity
Members
Labels
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Package registry
Container Registry
Model registry
Operate
Terraform modules
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Lorenzo Ferrillo
wine
Commits
8a7bc4c7
Commit
8a7bc4c7
authored
13 years ago
by
Francois Gouget
Committed by
Alexandre Julliard
13 years ago
Browse files
Options
Downloads
Patches
Plain Diff
xcopy: Reorder the functions to avoid forward declarations.
parent
0655a6e5
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
programs/xcopy/xcopy.c
+660
-675
660 additions, 675 deletions
programs/xcopy/xcopy.c
with
660 additions
and
675 deletions
programs/xcopy/xcopy.c
+
660
−
675
View file @
8a7bc4c7
...
...
@@ -48,22 +48,6 @@
WINE_DEFAULT_DEBUG_CHANNEL
(
xcopy
);
/* Prototypes */
static
int
XCOPY_ParseCommandLine
(
WCHAR
*
suppliedsource
,
WCHAR
*
supplieddestination
,
DWORD
*
flags
);
static
int
XCOPY_ProcessSourceParm
(
WCHAR
*
suppliedsource
,
WCHAR
*
stem
,
WCHAR
*
spec
,
DWORD
flags
);
static
int
XCOPY_ProcessDestParm
(
WCHAR
*
supplieddestination
,
WCHAR
*
stem
,
WCHAR
*
spec
,
WCHAR
*
srcspec
,
DWORD
flags
);
static
int
XCOPY_DoCopy
(
WCHAR
*
srcstem
,
WCHAR
*
srcspec
,
WCHAR
*
deststem
,
WCHAR
*
destspec
,
DWORD
flags
);
static
BOOL
XCOPY_CreateDirectory
(
const
WCHAR
*
path
);
static
BOOL
XCOPY_ProcessExcludeList
(
WCHAR
*
parms
);
static
BOOL
XCOPY_ProcessExcludeFile
(
WCHAR
*
filename
,
WCHAR
*
endOfName
);
static
WCHAR
*
XCOPY_LoadMessage
(
UINT
id
);
static
void
XCOPY_FailMessage
(
DWORD
err
);
static
int
XCOPY_wprintf
(
const
WCHAR
*
format
,
...);
/* Typedefs */
typedef
struct
_EXCLUDELIST
...
...
@@ -82,8 +66,6 @@ static const WCHAR wchr_star[] = {'*', 0};
static
const
WCHAR
wchr_dot
[]
=
{
'.'
,
0
};
static
const
WCHAR
wchr_dotdot
[]
=
{
'.'
,
'.'
,
0
};
/* Constants (Mostly for widechars) */
/* To minimize stack usage during recursion, some temporary variables
made global */
...
...
@@ -92,490 +74,260 @@ static WCHAR copyTo[MAX_PATH];
/* =========================================================================
main - Main entrypoint for the xcopy command
Processes the args, and drives the actual copying
========================================================================= */
int
wmain
(
int
argc
,
WCHAR
*
argvW
[])
{
int
rc
=
0
;
WCHAR
suppliedsource
[
MAX_PATH
]
=
{
0
};
/* As supplied on the cmd line */
WCHAR
supplieddestination
[
MAX_PATH
]
=
{
0
};
WCHAR
sourcestem
[
MAX_PATH
]
=
{
0
};
/* Stem of source */
WCHAR
sourcespec
[
MAX_PATH
]
=
{
0
};
/* Filespec of source */
WCHAR
destinationstem
[
MAX_PATH
]
=
{
0
};
/* Stem of destination */
WCHAR
destinationspec
[
MAX_PATH
]
=
{
0
};
/* Filespec of destination */
WCHAR
copyCmd
[
MAXSTRING
];
/* COPYCMD env var */
DWORD
flags
=
0
;
/* Option flags */
const
WCHAR
PROMPTSTR1
[]
=
{
'/'
,
'Y'
,
0
};
const
WCHAR
PROMPTSTR2
[]
=
{
'/'
,
'y'
,
0
};
const
WCHAR
COPYCMD
[]
=
{
'C'
,
'O'
,
'P'
,
'Y'
,
'C'
,
'M'
,
'D'
,
0
};
* Load a string from the resource file, handling any error
* Returns string retrieved from resource file
* ========================================================================= */
static
WCHAR
*
XCOPY_LoadMessage
(
UINT
id
)
{
static
WCHAR
msg
[
MAXSTRING
];
const
WCHAR
failedMsg
[]
=
{
'F'
,
'a'
,
'i'
,
'l'
,
'e'
,
'd'
,
'!'
,
0
};
/* Preinitialize flags based on COPYCMD */
if
(
GetEnvironmentVariableW
(
COPYCMD
,
copyCmd
,
MAXSTRING
))
{
if
(
wcsstr
(
copyCmd
,
PROMPTSTR1
)
!=
NULL
||
wcsstr
(
copyCmd
,
PROMPTSTR2
)
!=
NULL
)
{
flags
|=
OPT_NOPROMPT
;
}
if
(
!
LoadStringW
(
GetModuleHandleW
(
NULL
),
id
,
msg
,
sizeof
(
msg
)
/
sizeof
(
WCHAR
)))
{
WINE_FIXME
(
"LoadString failed with %d
\n
"
,
GetLastError
());
lstrcpyW
(
msg
,
failedMsg
);
}
return
msg
;
}
/* FIXME: On UNIX, files starting with a '.' are treated as hidden under
wine, but on windows these can be normal files. At least one installer
uses files such as .packlist and (validly) expects them to be copied.
Under wine, if we do not copy hidden files by default then they get
lose */
flags
|=
OPT_COPYHIDSYS
;
/* =========================================================================
* Output a formatted unicode string. Ideally this will go to the console
* and hence required WriteConsoleW to output it, however if file i/o is
* redirected, it needs to be WriteFile'd using OEM (not ANSI) format
* ========================================================================= */
static
int
XCOPY_wprintf
(
const
WCHAR
*
format
,
...)
{
static
WCHAR
*
output_bufW
=
NULL
;
static
char
*
output_bufA
=
NULL
;
static
BOOL
toConsole
=
TRUE
;
static
BOOL
traceOutput
=
FALSE
;
#define MAX_WRITECONSOLE_SIZE 65535
va_list
parms
;
DWORD
nOut
;
int
len
;
DWORD
res
=
0
;
/*
* Parse the command line
* Allocate buffer to use when writing to console
* Note: Not freed - memory will be allocated once and released when
* xcopy ends
*/
if
((
rc
=
XCOPY_ParseCommandLine
(
suppliedsource
,
supplieddestination
,
&
flags
))
!=
RC_OK
)
{
if
(
rc
==
RC_HELP
)
return
RC_OK
;
else
return
rc
;
}
/* Trace out the supplied information */
WINE_TRACE
(
"Supplied parameters:
\n
"
);
WINE_TRACE
(
"Source : '%s'
\n
"
,
wine_dbgstr_w
(
suppliedsource
));
WINE_TRACE
(
"Destination : '%s'
\n
"
,
wine_dbgstr_w
(
supplieddestination
));
/* Extract required information from source specification */
rc
=
XCOPY_ProcessSourceParm
(
suppliedsource
,
sourcestem
,
sourcespec
,
flags
);
if
(
rc
!=
RC_OK
)
return
rc
;
if
(
!
output_bufW
)
output_bufW
=
HeapAlloc
(
GetProcessHeap
(),
0
,
MAX_WRITECONSOLE_SIZE
);
if
(
!
output_bufW
)
{
WINE_FIXME
(
"Out of memory - could not allocate 2 x 64K buffers
\n
"
);
return
0
;
}
/* Extract required information from destination specification */
rc
=
XCOPY_ProcessDestParm
(
supplieddestination
,
destinationstem
,
destinationspec
,
sourcespec
,
flags
);
if
(
rc
!=
RC_OK
)
return
rc
;
va_start
(
parms
,
format
);
len
=
vsnprintfW
(
output_bufW
,
MAX_WRITECONSOLE_SIZE
/
sizeof
(
WCHAR
),
format
,
parms
);
va_end
(
parms
);
if
(
len
<
0
)
{
WINE_FIXME
(
"String too long.
\n
"
);
return
0
;
}
/* Trace out the resulting information */
WINE_TRACE
(
"Resolved parameters:
\n
"
);
WINE_TRACE
(
"Source Stem : '%s'
\n
"
,
wine_dbgstr_w
(
sourcestem
));
WINE_TRACE
(
"Source Spec : '%s'
\n
"
,
wine_dbgstr_w
(
sourcespec
));
WINE_TRACE
(
"Dest Stem : '%s'
\n
"
,
wine_dbgstr_w
(
destinationstem
));
WINE_TRACE
(
"Dest Spec : '%s'
\n
"
,
wine_dbgstr_w
(
destinationspec
));
/* Try to write as unicode whenever we think it's a console */
if
(
toConsole
)
{
res
=
WriteConsoleW
(
GetStdHandle
(
STD_OUTPUT_HANDLE
),
output_bufW
,
len
,
&
nOut
,
NULL
);
}
/* Pause if necessary */
if
(
flags
&
OPT_PAUSE
)
{
DWORD
count
;
char
pausestr
[
10
];
/* If writing to console has failed (ever) we assume it's file
i/o so convert to OEM codepage and output */
if
(
!
res
)
{
BOOL
usedDefaultChar
=
FALSE
;
DWORD
convertedChars
;
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_PAUSE
));
ReadFile
(
GetStdHandle
(
STD_INPUT_HANDLE
),
pausestr
,
sizeof
(
pausestr
),
&
count
,
NULL
);
}
toConsole
=
FALSE
;
/* Now do the hard work... */
rc
=
XCOPY_DoCopy
(
sourcestem
,
sourcespec
,
destinationstem
,
destinationspec
,
flags
);
/*
* Allocate buffer to use when writing to file. Not freed, as above
*/
if
(
!
output_bufA
)
output_bufA
=
HeapAlloc
(
GetProcessHeap
(),
0
,
MAX_WRITECONSOLE_SIZE
);
if
(
!
output_bufA
)
{
WINE_FIXME
(
"Out of memory - could not allocate 2 x 64K buffers
\n
"
);
return
0
;
}
/* C
lear up exclude list allocated memory
*/
while
(
excludeList
)
{
EXCLUDELIST
*
pos
=
excludeList
;
excludeList
=
excludeList
->
next
;
HeapFree
(
GetProcessHeap
(),
0
,
pos
->
name
);
HeapFree
(
GetProcessHeap
(),
0
,
pos
);
/* C
onvert to OEM, then output
*/
convertedChars
=
WideCharToMultiByte
(
GetConsoleOutputCP
(),
0
,
output_bufW
,
len
,
output_bufA
,
MAX_WRITECONSOLE_SIZE
,
"?"
,
&
usedDefaultChar
)
;
WriteFile
(
GetStdHandle
(
STD_OUTPUT_HANDLE
),
output_bufA
,
convertedChars
,
&
nOut
,
FALSE
);
}
/* Finished - print trailer and exit */
if
(
flags
&
OPT_SIMULATE
)
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_SIMCOPY
),
filesCopied
);
}
else
if
(
!
(
flags
&
OPT_NOCOPY
))
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_COPY
),
filesCopied
);
/* Trace whether screen or console */
if
(
!
traceOutput
)
{
WINE_TRACE
(
"Writing to console? (%d)
\n
"
,
toConsole
);
traceOutput
=
TRUE
;
}
if
(
rc
==
RC_OK
&&
filesCopied
==
0
)
rc
=
RC_NOFILES
;
return
rc
;
return
nOut
;
}
/* =========================================================================
XCOPY_ParseCommandLine - Parses the command line
========================================================================= */
static
BOOL
is_whitespace
(
WCHAR
c
)
{
return
c
==
' '
||
c
==
'\t'
;
}
static
WCHAR
*
skip_whitespace
(
WCHAR
*
p
)
{
for
(;
*
p
&&
is_whitespace
(
*
p
);
p
++
);
return
p
;
}
* Load a string for a system error and writes it to the screen
* Returns string retrieved from resource file
* ========================================================================= */
static
void
XCOPY_FailMessage
(
DWORD
err
)
{
LPWSTR
lpMsgBuf
;
int
status
;
/* Windows XCOPY uses a simplified command line parsing algorithm
that lacks the escaped-quote logic of build_argv(), because
literal double quotes are illegal in any of its arguments.
Example: 'XCOPY "c:\DIR A" "c:DIR B\"' is OK. */
static
int
find_end_of_word
(
const
WCHAR
*
word
,
WCHAR
**
end
)
{
BOOL
in_quotes
=
0
;
const
WCHAR
*
ptr
=
word
;
for
(;;)
{
for
(;
*
ptr
!=
'\0'
&&
*
ptr
!=
'"'
&&
(
in_quotes
||
!
is_whitespace
(
*
ptr
));
ptr
++
);
if
(
*
ptr
==
'"'
)
{
in_quotes
=
!
in_quotes
;
ptr
++
;
}
/* Odd number of double quotes is illegal for XCOPY */
if
(
in_quotes
&&
*
ptr
==
'\0'
)
return
RC_INITERROR
;
if
(
*
ptr
==
'\0'
||
(
!
in_quotes
&&
is_whitespace
(
*
ptr
)))
break
;
status
=
FormatMessageW
(
FORMAT_MESSAGE_ALLOCATE_BUFFER
|
FORMAT_MESSAGE_FROM_SYSTEM
,
NULL
,
err
,
0
,
(
LPWSTR
)
&
lpMsgBuf
,
0
,
NULL
);
if
(
!
status
)
{
WINE_FIXME
(
"FIXME: Cannot display message for error %d, status %d
\n
"
,
err
,
GetLastError
());
}
else
{
const
WCHAR
infostr
[]
=
{
'%'
,
's'
,
'\n'
,
0
};
XCOPY_wprintf
(
infostr
,
lpMsgBuf
);
LocalFree
((
HLOCAL
)
lpMsgBuf
);
}
*
end
=
(
WCHAR
*
)
ptr
;
return
RC_OK
;
}
/* Remove all double quotes from a word */
static
void
strip_quotes
(
WCHAR
*
word
,
WCHAR
**
end
)
{
WCHAR
*
rp
,
*
wp
;
for
(
rp
=
word
,
wp
=
word
;
*
rp
!=
'\0'
;
rp
++
)
{
if
(
*
rp
==
'"'
)
continue
;
if
(
wp
<
rp
)
*
wp
=
*
rp
;
wp
++
;
}
*
wp
=
'\0'
;
*
end
=
wp
;
}
static
int
XCOPY_ParseCommandLine
(
WCHAR
*
suppliedsource
,
WCHAR
*
supplieddestination
,
DWORD
*
pflags
)
/* =========================================================================
* Routine copied from cmd.exe md command -
* This works recursively. so creating dir1\dir2\dir3 will create dir1 and
* dir2 if they do not already exist.
* ========================================================================= */
static
BOOL
XCOPY_CreateDirectory
(
const
WCHAR
*
path
)
{
const
WCHAR
EXCLUDE
[]
=
{
'E'
,
'X'
,
'C'
,
'L'
,
'U'
,
'D'
,
'E'
,
':'
,
0
};
DWORD
flags
=
*
pflags
;
WCHAR
*
cmdline
,
*
word
,
*
end
,
*
next
;
int
rc
=
RC_INITERROR
;
int
len
;
WCHAR
*
new_path
;
BOOL
ret
=
TRUE
;
cmdline
=
_wcsdup
(
GetCommandLineW
());
if
(
cmdline
==
NULL
)
return
rc
;
new_path
=
HeapAlloc
(
GetProcessHeap
(),
0
,
sizeof
(
WCHAR
)
*
(
lstrlenW
(
path
)
+
1
));
lstrcpyW
(
new_path
,
path
);
/* Skip first arg, which is the program name */
if
((
rc
=
find_end_of_word
(
cmdline
,
&
word
))
!=
RC_OK
)
goto
out
;
word
=
skip_whitespace
(
word
);
while
((
len
=
lstrlenW
(
new_path
))
&&
new_path
[
len
-
1
]
==
'\\'
)
new_path
[
len
-
1
]
=
0
;
while
(
*
word
)
while
(
!
CreateDirectoryW
(
new_path
,
NULL
)
)
{
WCHAR
first
;
if
((
rc
=
find_end_of_word
(
word
,
&
end
))
!=
RC_OK
)
goto
out
;
next
=
skip_whitespace
(
end
);
first
=
word
[
0
];
*
end
=
'\0'
;
strip_quotes
(
word
,
&
end
);
WINE_TRACE
(
"Processing Arg: '%s'
\n
"
,
wine_dbgstr_w
(
word
));
/* First non-switch parameter is source, second is destination */
if
(
first
!=
'/'
)
{
if
(
suppliedsource
[
0
]
==
0x00
)
{
lstrcpyW
(
suppliedsource
,
word
);
}
else
if
(
supplieddestination
[
0
]
==
0x00
)
{
lstrcpyW
(
supplieddestination
,
word
);
}
else
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_INVPARMS
));
goto
out
;
}
}
else
{
/* Process all the switch options
Note: Windows docs say /P prompts when dest is created
but tests show it is done for each src file
regardless of the destination */
switch
(
toupper
(
word
[
1
]))
{
case
'I'
:
flags
|=
OPT_ASSUMEDIR
;
break
;
case
'S'
:
flags
|=
OPT_RECURSIVE
;
break
;
case
'Q'
:
flags
|=
OPT_QUIET
;
break
;
case
'F'
:
flags
|=
OPT_FULL
;
break
;
case
'L'
:
flags
|=
OPT_SIMULATE
;
break
;
case
'W'
:
flags
|=
OPT_PAUSE
;
break
;
case
'T'
:
flags
|=
OPT_NOCOPY
|
OPT_RECURSIVE
;
break
;
case
'Y'
:
flags
|=
OPT_NOPROMPT
;
break
;
case
'N'
:
flags
|=
OPT_SHORTNAME
;
break
;
case
'U'
:
flags
|=
OPT_MUSTEXIST
;
break
;
case
'R'
:
flags
|=
OPT_REPLACEREAD
;
break
;
case
'H'
:
flags
|=
OPT_COPYHIDSYS
;
break
;
case
'C'
:
flags
|=
OPT_IGNOREERRORS
;
break
;
case
'P'
:
flags
|=
OPT_SRCPROMPT
;
break
;
case
'A'
:
flags
|=
OPT_ARCHIVEONLY
;
break
;
case
'M'
:
flags
|=
OPT_ARCHIVEONLY
|
OPT_REMOVEARCH
;
break
;
/* E can be /E or /EXCLUDE */
case
'E'
:
if
(
CompareStringW
(
LOCALE_USER_DEFAULT
,
NORM_IGNORECASE
|
SORT_STRINGSORT
,
&
word
[
1
],
8
,
EXCLUDE
,
-
1
)
==
2
)
{
if
(
XCOPY_ProcessExcludeList
(
&
word
[
9
]))
{
XCOPY_FailMessage
(
ERROR_INVALID_PARAMETER
);
goto
out
;
}
else
flags
|=
OPT_EXCLUDELIST
;
}
else
flags
|=
OPT_EMPTYDIR
|
OPT_RECURSIVE
;
break
;
/* D can be /D or /D: */
case
'D'
:
if
(
word
[
2
]
==
':'
&&
isdigit
(
word
[
3
]))
{
SYSTEMTIME
st
;
WCHAR
*
pos
=
&
word
[
3
];
BOOL
isError
=
FALSE
;
memset
(
&
st
,
0x00
,
sizeof
(
st
));
/* Parse the arg : Month */
st
.
wMonth
=
_wtol
(
pos
);
while
(
*
pos
&&
isdigit
(
*
pos
))
pos
++
;
if
(
*
pos
++
!=
'-'
)
isError
=
TRUE
;
/* Parse the arg : Day */
if
(
!
isError
)
{
st
.
wDay
=
_wtol
(
pos
);
while
(
*
pos
&&
isdigit
(
*
pos
))
pos
++
;
if
(
*
pos
++
!=
'-'
)
isError
=
TRUE
;
}
/* Parse the arg : Day */
if
(
!
isError
)
{
st
.
wYear
=
_wtol
(
pos
);
while
(
*
pos
&&
isdigit
(
*
pos
))
pos
++
;
if
(
st
.
wYear
<
100
)
st
.
wYear
+=
2000
;
}
if
(
!
isError
&&
SystemTimeToFileTime
(
&
st
,
&
dateRange
))
{
SYSTEMTIME
st
;
WCHAR
datestring
[
32
],
timestring
[
32
];
flags
|=
OPT_DATERANGE
;
/* Debug info: */
FileTimeToSystemTime
(
&
dateRange
,
&
st
);
GetDateFormatW
(
0
,
DATE_SHORTDATE
,
&
st
,
NULL
,
datestring
,
sizeof
(
datestring
)
/
sizeof
(
WCHAR
));
GetTimeFormatW
(
0
,
TIME_NOSECONDS
,
&
st
,
NULL
,
timestring
,
sizeof
(
timestring
)
/
sizeof
(
WCHAR
));
WINE_TRACE
(
"Date being used is: %s %s
\n
"
,
wine_dbgstr_w
(
datestring
),
wine_dbgstr_w
(
timestring
));
}
else
{
XCOPY_FailMessage
(
ERROR_INVALID_PARAMETER
);
goto
out
;
}
}
else
{
flags
|=
OPT_DATENEWER
;
}
break
;
WCHAR
*
slash
;
DWORD
last_error
=
GetLastError
();
if
(
last_error
==
ERROR_ALREADY_EXISTS
)
break
;
case
'-'
:
if
(
toupper
(
word
[
2
])
==
'Y'
)
flags
&=
~
OPT_NOPROMPT
;
break
;
case
'?'
:
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_HELP
));
rc
=
RC_HELP
;
goto
out
;
default:
WINE_TRACE
(
"Unhandled parameter '%s'
\n
"
,
wine_dbgstr_w
(
word
));
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_INVPARM
),
word
);
goto
out
;
}
if
(
last_error
!=
ERROR_PATH_NOT_FOUND
)
{
ret
=
FALSE
;
break
;
}
word
=
next
;
}
/* Default the destination if not supplied */
if
(
supplieddestination
[
0
]
==
0x00
)
lstrcpyW
(
supplieddestination
,
wchr_dot
);
*
pflags
=
flags
;
rc
=
RC_OK
;
out:
free
(
cmdline
);
return
rc
;
}
/* =========================================================================
XCOPY_ProcessSourceParm - Takes the supplied source parameter, and
converts it into a stem and a filespec
========================================================================= */
static
int
XCOPY_ProcessSourceParm
(
WCHAR
*
suppliedsource
,
WCHAR
*
stem
,
WCHAR
*
spec
,
DWORD
flags
)
{
WCHAR
actualsource
[
MAX_PATH
];
WCHAR
*
starPos
;
WCHAR
*
questPos
;
DWORD
attribs
;
/*
* Validate the source, expanding to full path ensuring it exists
*/
if
(
GetFullPathNameW
(
suppliedsource
,
MAX_PATH
,
actualsource
,
NULL
)
==
0
)
{
WINE_FIXME
(
"Unexpected failure expanding source path (%d)
\n
"
,
GetLastError
());
return
RC_INITERROR
;
}
/* If full names required, convert to using the full path */
if
(
flags
&
OPT_FULL
)
{
lstrcpyW
(
suppliedsource
,
actualsource
);
}
/*
* Work out the stem of the source
*/
/* If a directory is supplied, use that as-is (either fully or
partially qualified)
If a filename is supplied + a directory or drive path, use that
as-is
Otherwise
If no directory or path specified, add eg. C:
stem is Drive/Directory is bit up to last \ (or first :)
spec is bit after that */
starPos
=
wcschr
(
suppliedsource
,
'*'
);
questPos
=
wcschr
(
suppliedsource
,
'?'
);
if
(
starPos
||
questPos
)
{
attribs
=
0x00
;
/* Ensures skips invalid or directory check below */
}
else
{
attribs
=
GetFileAttributesW
(
actualsource
);
}
if
(
attribs
==
INVALID_FILE_ATTRIBUTES
)
{
XCOPY_FailMessage
(
GetLastError
());
return
RC_INITERROR
;
/* Directory:
stem should be exactly as supplied plus a '\', unless it was
eg. C: in which case no slash required */
}
else
if
(
attribs
&
FILE_ATTRIBUTE_DIRECTORY
)
{
WCHAR
lastChar
;
WINE_TRACE
(
"Directory supplied
\n
"
);
lstrcpyW
(
stem
,
suppliedsource
);
lastChar
=
stem
[
lstrlenW
(
stem
)
-
1
];
if
(
lastChar
!=
'\\'
&&
lastChar
!=
':'
)
{
lstrcatW
(
stem
,
wchr_slash
);
if
(
!
(
slash
=
wcsrchr
(
new_path
,
'\\'
))
&&
!
(
slash
=
wcsrchr
(
new_path
,
'/'
)))
{
ret
=
FALSE
;
break
;
}
lstrcpyW
(
spec
,
wchr_star
);
/* File or wildcard search:
stem should be:
Up to and including last slash if directory path supplied
If c:filename supplied, just the c:
Otherwise stem should be the current drive letter + ':' */
}
else
{
WCHAR
*
lastDir
;
WINE_TRACE
(
"Filename supplied
\n
"
);
lastDir
=
wcsrchr
(
suppliedsource
,
'\\'
);
if
(
lastDir
)
{
lstrcpyW
(
stem
,
suppliedsource
);
stem
[(
lastDir
-
suppliedsource
)
+
1
]
=
0x00
;
lstrcpyW
(
spec
,
(
lastDir
+
1
));
}
else
if
(
suppliedsource
[
1
]
==
':'
)
{
lstrcpyW
(
stem
,
suppliedsource
);
stem
[
2
]
=
0x00
;
lstrcpyW
(
spec
,
suppliedsource
+
2
);
}
else
{
WCHAR
curdir
[
MAXSTRING
];
GetCurrentDirectoryW
(
sizeof
(
curdir
)
/
sizeof
(
WCHAR
),
curdir
);
stem
[
0
]
=
curdir
[
0
];
stem
[
1
]
=
curdir
[
1
];
stem
[
2
]
=
0x00
;
lstrcpyW
(
spec
,
suppliedsource
);
len
=
slash
-
new_path
;
new_path
[
len
]
=
0
;
if
(
!
XCOPY_CreateDirectory
(
new_path
))
{
ret
=
FALSE
;
break
;
}
new_path
[
len
]
=
'\\'
;
}
return
RC_OK
;
HeapFree
(
GetProcessHeap
(),
0
,
new_path
);
return
ret
;
}
/* =========================================================================
XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
converts it into a stem
========================================================================= */
static
int
XCOPY_ProcessDestParm
(
WCHAR
*
supplieddestination
,
WCHAR
*
stem
,
WCHAR
*
spec
,
WCHAR
*
srcspec
,
DWORD
flags
)
{
WCHAR
actualdestination
[
MAX_PATH
];
DWORD
attribs
;
BOOL
isDir
=
FALSE
;
* Process a single file from the /EXCLUDE: file list, building up a list
* of substrings to avoid copying
* Returns TRUE on any failure
* ========================================================================= */
static
BOOL
XCOPY_ProcessExcludeFile
(
WCHAR
*
filename
,
WCHAR
*
endOfName
)
{
/*
* Validate the source, expanding to full path ensuring it exists
*/
if
(
GetFullPathNameW
(
supplieddestination
,
MAX_PATH
,
actualdestination
,
NULL
)
==
0
)
{
WINE_FIXME
(
"Unexpected failure expanding source path (%d)
\n
"
,
GetLastError
());
return
RC_INITERROR
;
WCHAR
endChar
=
*
endOfName
;
WCHAR
buffer
[
MAXSTRING
];
FILE
*
inFile
=
NULL
;
const
WCHAR
readTextMode
[]
=
{
'r'
,
't'
,
0
};
/* Null terminate the filename (temporarily updates the filename hence
parms not const) */
*
endOfName
=
0x00
;
/* Open the file */
inFile
=
_wfopen
(
filename
,
readTextMode
);
if
(
inFile
==
NULL
)
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_OPENFAIL
),
filename
);
*
endOfName
=
endChar
;
return
TRUE
;
}
/* Destination is either a directory or a file */
attribs
=
GetFileAttributesW
(
actualdestination
);
/* Process line by line */
while
(
fgetws
(
buffer
,
sizeof
(
buffer
)
/
sizeof
(
WCHAR
),
inFile
)
!=
NULL
)
{
EXCLUDELIST
*
thisEntry
;
int
length
=
lstrlenW
(
buffer
);
if
(
attribs
==
INVALID_FILE_ATTRIBUTES
)
{
/* Strip CRLF */
buffer
[
length
-
1
]
=
0x00
;
/* If /I supplied and wildcard copy, assume directory */
/* Also if destination ends with backslash */
if
((
flags
&
OPT_ASSUMEDIR
&&
(
wcschr
(
srcspec
,
'?'
)
||
wcschr
(
srcspec
,
'*'
)))
||
(
supplieddestination
[
lstrlenW
(
supplieddestination
)
-
1
]
==
'\\'
))
{
/* If more than CRLF */
if
(
length
>
1
)
{
thisEntry
=
HeapAlloc
(
GetProcessHeap
(),
0
,
sizeof
(
EXCLUDELIST
));
thisEntry
->
next
=
excludeList
;
excludeList
=
thisEntry
;
thisEntry
->
name
=
HeapAlloc
(
GetProcessHeap
(),
0
,
(
length
*
sizeof
(
WCHAR
))
+
1
);
lstrcpyW
(
thisEntry
->
name
,
buffer
);
CharUpperBuffW
(
thisEntry
->
name
,
length
);
WINE_TRACE
(
"Read line : '%s'
\n
"
,
wine_dbgstr_w
(
thisEntry
->
name
));
}
}
isDir
=
TRUE
;
/* See if EOF or error occurred */
if
(
!
feof
(
inFile
))
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_READFAIL
),
filename
);
*
endOfName
=
endChar
;
return
TRUE
;
}
}
else
{
DWORD
count
;
char
answer
[
10
]
=
""
;
WCHAR
fileChar
[
2
]
;
WCHAR
dirChar
[
2
];
/* Revert the input string to original form, and cleanup + return */
*
endOfName
=
endChar
;
fclose
(
inFile
)
;
return
FALSE
;
}
/* Read the F and D characters from the resource file */
wcscpy
(
fileChar
,
XCOPY_LoadMessage
(
STRING_FILE_CHAR
));
wcscpy
(
dirChar
,
XCOPY_LoadMessage
(
STRING_DIR_CHAR
));
/* =========================================================================
* Process the /EXCLUDE: file list, building up a list of substrings to
* avoid copying
* Returns TRUE on any failure
* ========================================================================= */
static
BOOL
XCOPY_ProcessExcludeList
(
WCHAR
*
parms
)
{
while
(
answer
[
0
]
!=
fileChar
[
0
]
&&
answer
[
0
]
!=
dirChar
[
0
])
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_QISDIR
),
supplieddestination
);
WCHAR
*
filenameStart
=
parms
;
ReadFile
(
GetStdHandle
(
STD_INPUT_HANDLE
),
answer
,
sizeof
(
answer
),
&
count
,
NULL
);
WINE_TRACE
(
"User answer %c
\n
"
,
answer
[
0
])
;
WINE_TRACE
(
"/EXCLUDE parms: '%s'
\n
"
,
wine_dbgstr_w
(
parms
)
);
excludeList
=
NULL
;
answer
[
0
]
=
toupper
(
answer
[
0
]);
}
while
(
*
parms
&&
*
parms
!=
' '
&&
*
parms
!=
'/'
)
{
if
(
answer
[
0
]
==
dirChar
[
0
])
{
i
sDir
=
TRUE
;
}
else
{
isDir
=
FALS
E
;
/* If found '+' then process the file found so far */
i
f
(
*
parms
==
'+'
)
{
if
(
XCOPY_ProcessExcludeFile
(
filenameStart
,
parms
))
{
return
TRU
E
;
}
filenameStart
=
parms
+
1
;
}
}
else
{
isDir
=
(
attribs
&
FILE_ATTRIBUTE_DIRECTORY
);
parms
++
;
}
if
(
isDir
)
{
lstrcpyW
(
stem
,
actualdestination
);
*
spec
=
0x00
;
/* Ensure ends with a '\' */
if
(
stem
[
lstrlenW
(
stem
)
-
1
]
!=
'\\'
)
{
lstrcatW
(
stem
,
wchr_slash
);
if
(
filenameStart
!=
parms
)
{
if
(
XCOPY_ProcessExcludeFile
(
filenameStart
,
parms
))
{
return
TRUE
;
}
}
else
{
WCHAR
drive
[
MAX_PATH
];
WCHAR
dir
[
MAX_PATH
];
WCHAR
fname
[
MAX_PATH
];
WCHAR
ext
[
MAX_PATH
];
_wsplitpath
(
actualdestination
,
drive
,
dir
,
fname
,
ext
);
lstrcpyW
(
stem
,
drive
);
lstrcatW
(
stem
,
dir
);
lstrcpyW
(
spec
,
fname
);
lstrcatW
(
spec
,
ext
);
}
return
RC_OK
;
return
FALSE
;
}
/* =========================================================================
...
...
@@ -842,300 +594,533 @@ static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec,
h
=
FindFirstFileW
(
inputpath
,
finddata
);
while
(
h
!=
INVALID_HANDLE_VALUE
&&
findres
)
{
/* Only looking for dirs */
if
((
finddata
->
dwFileAttributes
&
FILE_ATTRIBUTE_DIRECTORY
)
&&
(
lstrcmpW
(
finddata
->
cFileName
,
wchr_dot
)
!=
0
)
&&
(
lstrcmpW
(
finddata
->
cFileName
,
wchr_dotdot
)
!=
0
))
{
/* Only looking for dirs */
if
((
finddata
->
dwFileAttributes
&
FILE_ATTRIBUTE_DIRECTORY
)
&&
(
lstrcmpW
(
finddata
->
cFileName
,
wchr_dot
)
!=
0
)
&&
(
lstrcmpW
(
finddata
->
cFileName
,
wchr_dotdot
)
!=
0
))
{
WINE_TRACE
(
"Handling subdir: %s
\n
"
,
wine_dbgstr_w
(
finddata
->
cFileName
));
/* Make up recursive information */
lstrcpyW
(
inputpath
,
srcstem
);
lstrcatW
(
inputpath
,
finddata
->
cFileName
);
lstrcatW
(
inputpath
,
wchr_slash
);
lstrcpyW
(
outputpath
,
deststem
);
if
(
*
destspec
==
0x00
)
{
lstrcatW
(
outputpath
,
finddata
->
cFileName
);
/* If /E is supplied, create the directory now */
if
((
flags
&
OPT_EMPTYDIR
)
&&
!
(
flags
&
OPT_SIMULATE
))
XCOPY_CreateDirectory
(
outputpath
);
lstrcatW
(
outputpath
,
wchr_slash
);
}
XCOPY_DoCopy
(
inputpath
,
srcspec
,
outputpath
,
destspec
,
flags
);
}
/* Find next one */
findres
=
FindNextFileW
(
h
,
finddata
);
}
}
cleanup:
/* free up memory */
HeapFree
(
GetProcessHeap
(),
0
,
finddata
);
HeapFree
(
GetProcessHeap
(),
0
,
inputpath
);
HeapFree
(
GetProcessHeap
(),
0
,
outputpath
);
return
ret
;
}
/* =========================================================================
XCOPY_ParseCommandLine - Parses the command line
========================================================================= */
static
BOOL
is_whitespace
(
WCHAR
c
)
{
return
c
==
' '
||
c
==
'\t'
;
}
static
WCHAR
*
skip_whitespace
(
WCHAR
*
p
)
{
for
(;
*
p
&&
is_whitespace
(
*
p
);
p
++
);
return
p
;
}
/* Windows XCOPY uses a simplified command line parsing algorithm
that lacks the escaped-quote logic of build_argv(), because
literal double quotes are illegal in any of its arguments.
Example: 'XCOPY "c:\DIR A" "c:DIR B\"' is OK. */
static
int
find_end_of_word
(
const
WCHAR
*
word
,
WCHAR
**
end
)
{
BOOL
in_quotes
=
0
;
const
WCHAR
*
ptr
=
word
;
for
(;;)
{
for
(;
*
ptr
!=
'\0'
&&
*
ptr
!=
'"'
&&
(
in_quotes
||
!
is_whitespace
(
*
ptr
));
ptr
++
);
if
(
*
ptr
==
'"'
)
{
in_quotes
=
!
in_quotes
;
ptr
++
;
}
/* Odd number of double quotes is illegal for XCOPY */
if
(
in_quotes
&&
*
ptr
==
'\0'
)
return
RC_INITERROR
;
if
(
*
ptr
==
'\0'
||
(
!
in_quotes
&&
is_whitespace
(
*
ptr
)))
break
;
}
*
end
=
(
WCHAR
*
)
ptr
;
return
RC_OK
;
}
/* Remove all double quotes from a word */
static
void
strip_quotes
(
WCHAR
*
word
,
WCHAR
**
end
)
{
WCHAR
*
rp
,
*
wp
;
for
(
rp
=
word
,
wp
=
word
;
*
rp
!=
'\0'
;
rp
++
)
{
if
(
*
rp
==
'"'
)
continue
;
if
(
wp
<
rp
)
*
wp
=
*
rp
;
wp
++
;
}
*
wp
=
'\0'
;
*
end
=
wp
;
}
static
int
XCOPY_ParseCommandLine
(
WCHAR
*
suppliedsource
,
WCHAR
*
supplieddestination
,
DWORD
*
pflags
)
{
const
WCHAR
EXCLUDE
[]
=
{
'E'
,
'X'
,
'C'
,
'L'
,
'U'
,
'D'
,
'E'
,
':'
,
0
};
DWORD
flags
=
*
pflags
;
WCHAR
*
cmdline
,
*
word
,
*
end
,
*
next
;
int
rc
=
RC_INITERROR
;
cmdline
=
_wcsdup
(
GetCommandLineW
());
if
(
cmdline
==
NULL
)
return
rc
;
/* Skip first arg, which is the program name */
if
((
rc
=
find_end_of_word
(
cmdline
,
&
word
))
!=
RC_OK
)
goto
out
;
word
=
skip_whitespace
(
word
);
while
(
*
word
)
{
WCHAR
first
;
if
((
rc
=
find_end_of_word
(
word
,
&
end
))
!=
RC_OK
)
goto
out
;
next
=
skip_whitespace
(
end
);
first
=
word
[
0
];
*
end
=
'\0'
;
strip_quotes
(
word
,
&
end
);
WINE_TRACE
(
"Processing Arg: '%s'
\n
"
,
wine_dbgstr_w
(
word
));
/* First non-switch parameter is source, second is destination */
if
(
first
!=
'/'
)
{
if
(
suppliedsource
[
0
]
==
0x00
)
{
lstrcpyW
(
suppliedsource
,
word
);
}
else
if
(
supplieddestination
[
0
]
==
0x00
)
{
lstrcpyW
(
supplieddestination
,
word
);
}
else
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_INVPARMS
));
goto
out
;
}
}
else
{
/* Process all the switch options
Note: Windows docs say /P prompts when dest is created
but tests show it is done for each src file
regardless of the destination */
switch
(
toupper
(
word
[
1
]))
{
case
'I'
:
flags
|=
OPT_ASSUMEDIR
;
break
;
case
'S'
:
flags
|=
OPT_RECURSIVE
;
break
;
case
'Q'
:
flags
|=
OPT_QUIET
;
break
;
case
'F'
:
flags
|=
OPT_FULL
;
break
;
case
'L'
:
flags
|=
OPT_SIMULATE
;
break
;
case
'W'
:
flags
|=
OPT_PAUSE
;
break
;
case
'T'
:
flags
|=
OPT_NOCOPY
|
OPT_RECURSIVE
;
break
;
case
'Y'
:
flags
|=
OPT_NOPROMPT
;
break
;
case
'N'
:
flags
|=
OPT_SHORTNAME
;
break
;
case
'U'
:
flags
|=
OPT_MUSTEXIST
;
break
;
case
'R'
:
flags
|=
OPT_REPLACEREAD
;
break
;
case
'H'
:
flags
|=
OPT_COPYHIDSYS
;
break
;
case
'C'
:
flags
|=
OPT_IGNOREERRORS
;
break
;
case
'P'
:
flags
|=
OPT_SRCPROMPT
;
break
;
case
'A'
:
flags
|=
OPT_ARCHIVEONLY
;
break
;
case
'M'
:
flags
|=
OPT_ARCHIVEONLY
|
OPT_REMOVEARCH
;
break
;
/* E can be /E or /EXCLUDE */
case
'E'
:
if
(
CompareStringW
(
LOCALE_USER_DEFAULT
,
NORM_IGNORECASE
|
SORT_STRINGSORT
,
&
word
[
1
],
8
,
EXCLUDE
,
-
1
)
==
2
)
{
if
(
XCOPY_ProcessExcludeList
(
&
word
[
9
]))
{
XCOPY_FailMessage
(
ERROR_INVALID_PARAMETER
);
goto
out
;
}
else
flags
|=
OPT_EXCLUDELIST
;
}
else
flags
|=
OPT_EMPTYDIR
|
OPT_RECURSIVE
;
break
;
/* D can be /D or /D: */
case
'D'
:
if
(
word
[
2
]
==
':'
&&
isdigit
(
word
[
3
]))
{
SYSTEMTIME
st
;
WCHAR
*
pos
=
&
word
[
3
];
BOOL
isError
=
FALSE
;
memset
(
&
st
,
0x00
,
sizeof
(
st
));
/* Parse the arg : Month */
st
.
wMonth
=
_wtol
(
pos
);
while
(
*
pos
&&
isdigit
(
*
pos
))
pos
++
;
if
(
*
pos
++
!=
'-'
)
isError
=
TRUE
;
/* Parse the arg : Day */
if
(
!
isError
)
{
st
.
wDay
=
_wtol
(
pos
);
while
(
*
pos
&&
isdigit
(
*
pos
))
pos
++
;
if
(
*
pos
++
!=
'-'
)
isError
=
TRUE
;
}
WINE_TRACE
(
"Handling subdir: %s
\n
"
,
wine_dbgstr_w
(
finddata
->
cFileName
));
/* Parse the arg : Day */
if
(
!
isError
)
{
st
.
wYear
=
_wtol
(
pos
);
while
(
*
pos
&&
isdigit
(
*
pos
))
pos
++
;
if
(
st
.
wYear
<
100
)
st
.
wYear
+=
2000
;
}
/* Make up recursive information */
lstrcpyW
(
inputpath
,
srcstem
);
lstrcatW
(
inputpath
,
finddata
->
cFileName
);
lstrcatW
(
inputpath
,
wchr_slash
);
if
(
!
isError
&&
SystemTimeToFileTime
(
&
st
,
&
dateRange
))
{
SYSTEMTIME
st
;
WCHAR
datestring
[
32
],
timestring
[
32
];
lstrcpyW
(
outputpath
,
deststem
);
if
(
*
destspec
==
0x00
)
{
lstrcatW
(
outputpath
,
finddata
->
cFileName
);
flags
|=
OPT_DATERANGE
;
/* If /E is supplied, create the directory now */
if
((
flags
&
OPT_EMPTYDIR
)
&&
!
(
flags
&
OPT_SIMULATE
))
XCOPY_CreateDirectory
(
outputpath
);
/* Debug info: */
FileTimeToSystemTime
(
&
dateRange
,
&
st
);
GetDateFormatW
(
0
,
DATE_SHORTDATE
,
&
st
,
NULL
,
datestring
,
sizeof
(
datestring
)
/
sizeof
(
WCHAR
));
GetTimeFormatW
(
0
,
TIME_NOSECONDS
,
&
st
,
NULL
,
timestring
,
sizeof
(
timestring
)
/
sizeof
(
WCHAR
));
lstrcatW
(
outputpath
,
wchr_slash
);
}
WINE_TRACE
(
"Date being used is: %s %s
\n
"
,
wine_dbgstr_w
(
datestring
),
wine_dbgstr_w
(
timestring
));
}
else
{
XCOPY_FailMessage
(
ERROR_INVALID_PARAMETER
);
goto
out
;
}
}
else
{
flags
|=
OPT_DATENEWER
;
}
break
;
XCOPY_DoCopy
(
inputpath
,
srcspec
,
outputpath
,
destspec
,
flags
);
case
'-'
:
if
(
toupper
(
word
[
2
])
==
'Y'
)
flags
&=
~
OPT_NOPROMPT
;
break
;
case
'?'
:
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_HELP
));
rc
=
RC_HELP
;
goto
out
;
default:
WINE_TRACE
(
"Unhandled parameter '%s'
\n
"
,
wine_dbgstr_w
(
word
));
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_INVPARM
),
word
);
goto
out
;
}
/* Find next one */
findres
=
FindNextFileW
(
h
,
finddata
);
}
word
=
next
;
}
cleanup:
/* Default the destination if not supplied */
if
(
supplieddestination
[
0
]
==
0x00
)
lstrcpyW
(
supplieddestination
,
wchr_dot
);
/* free up memory */
HeapFree
(
GetProcessHeap
(),
0
,
finddata
);
HeapFree
(
GetProcessHeap
(),
0
,
inputpath
);
HeapFree
(
GetProcessHeap
(),
0
,
outputpath
);
*
pflags
=
flags
;
rc
=
RC_OK
;
return
ret
;
out:
free
(
cmdline
);
return
rc
;
}
/* =========================================================================
* Routine copied from cmd.exe md comm
and
-
* This works recursively. so creating dir1\dir2\dir3 will create dir1 and
* dir2 if they do not already exist.
* ========================================================================= */
static
BOOL
XCOPY_CreateDirectory
(
const
WCHAR
*
path
)
XCOPY_ProcessSourceParm - Takes the supplied source parameter,
and
converts it into a stem and a filespec
========================================================================= */
static
int
XCOPY_ProcessSourceParm
(
WCHAR
*
suppliedsource
,
WCHAR
*
stem
,
WCHAR
*
spec
,
DWORD
flags
)
{
int
len
;
WCHAR
*
new_path
;
BOOL
ret
=
TRUE
;
new_path
=
HeapAlloc
(
GetProcessHeap
(),
0
,
sizeof
(
WCHAR
)
*
(
lstrlenW
(
path
)
+
1
));
lstrcpyW
(
new_path
,
path
);
WCHAR
actualsource
[
MAX_PATH
];
WCHAR
*
starPos
;
WCHAR
*
questPos
;
DWORD
attribs
;
while
((
len
=
lstrlenW
(
new_path
))
&&
new_path
[
len
-
1
]
==
'\\'
)
new_path
[
len
-
1
]
=
0
;
/*
* Validate the source, expanding to full path ensuring it exists
*/
if
(
GetFullPathNameW
(
suppliedsource
,
MAX_PATH
,
actualsource
,
NULL
)
==
0
)
{
WINE_FIXME
(
"Unexpected failure expanding source path (%d)
\n
"
,
GetLastError
());
return
RC_INITERROR
;
}
while
(
!
CreateDirectoryW
(
new_path
,
NULL
))
{
WCHAR
*
slash
;
DWORD
last_error
=
GetLastError
();
if
(
last_error
==
ERROR_ALREADY_EXISTS
)
break
;
/* If full names required, convert to using the full path */
if
(
flags
&
OPT_FULL
)
{
lstrcpyW
(
suppliedsource
,
actualsource
);
}
if
(
last_error
!=
ERROR_PATH_NOT_FOUND
)
{
ret
=
FALSE
;
break
;
}
/*
* Work out the stem of the source
*/
if
(
!
(
slash
=
wcsrchr
(
new_path
,
'\\'
))
&&
!
(
slash
=
wcsrchr
(
new_path
,
'/'
)))
{
ret
=
FALSE
;
break
;
}
/* If a directory is supplied, use that as-is (either fully or
partially qualified)
If a filename is supplied + a directory or drive path, use that
as-is
Otherwise
If no directory or path specified, add eg. C:
stem is Drive/Directory is bit up to last \ (or first :)
spec is bit after that */
len
=
slash
-
new_path
;
new_path
[
len
]
=
0
;
if
(
!
XCOPY_CreateDirectory
(
new_path
))
{
ret
=
FALSE
;
break
;
}
new_path
[
len
]
=
'\\'
;
starPos
=
wcschr
(
suppliedsource
,
'*'
);
questPos
=
wcschr
(
suppliedsource
,
'?'
);
if
(
starPos
||
questPos
)
{
attribs
=
0x00
;
/* Ensures skips invalid or directory check below */
}
else
{
attribs
=
GetFileAttributesW
(
actualsource
);
}
HeapFree
(
GetProcessHeap
(),
0
,
new_path
);
return
ret
;
}
/* =========================================================================
* Process the /EXCLUDE: file list, building up a list of substrings to
* avoid copying
* Returns TRUE on any failure
* ========================================================================= */
static
BOOL
XCOPY_ProcessExcludeList
(
WCHAR
*
parms
)
{
if
(
attribs
==
INVALID_FILE_ATTRIBUTES
)
{
XCOPY_FailMessage
(
GetLastError
());
return
RC_INITERROR
;
WCHAR
*
filenameStart
=
parms
;
/* Directory:
stem should be exactly as supplied plus a '\', unless it was
eg. C: in which case no slash required */
}
else
if
(
attribs
&
FILE_ATTRIBUTE_DIRECTORY
)
{
WCHAR
lastChar
;
WINE_TRACE
(
"/EXCLUDE parms: '%s'
\n
"
,
wine_dbgstr_w
(
parms
));
excludeList
=
NULL
;
WINE_TRACE
(
"Directory supplied
\n
"
);
lstrcpyW
(
stem
,
suppliedsource
);
lastChar
=
stem
[
lstrlenW
(
stem
)
-
1
];
if
(
lastChar
!=
'\\'
&&
lastChar
!=
':'
)
{
lstrcatW
(
stem
,
wchr_slash
);
}
lstrcpyW
(
spec
,
wchr_star
);
while
(
*
parms
&&
*
parms
!=
' '
&&
*
parms
!=
'/'
)
{
/* File or wildcard search:
stem should be:
Up to and including last slash if directory path supplied
If c:filename supplied, just the c:
Otherwise stem should be the current drive letter + ':' */
}
else
{
WCHAR
*
lastDir
;
/* If found '+' then process the file found so far */
if
(
*
parms
==
'+'
)
{
if
(
XCOPY_ProcessExcludeFile
(
filenameStart
,
parms
))
{
return
TRUE
;
}
filenameStart
=
parms
+
1
;
}
parms
++
;
}
WINE_TRACE
(
"Filename supplied
\n
"
);
lastDir
=
wcsrchr
(
suppliedsource
,
'\\'
);
if
(
filenameStart
!=
parms
)
{
if
(
XCOPY_ProcessExcludeFile
(
filenameStart
,
parms
))
{
return
TRUE
;
if
(
lastDir
)
{
lstrcpyW
(
stem
,
suppliedsource
);
stem
[(
lastDir
-
suppliedsource
)
+
1
]
=
0x00
;
lstrcpyW
(
spec
,
(
lastDir
+
1
));
}
else
if
(
suppliedsource
[
1
]
==
':'
)
{
lstrcpyW
(
stem
,
suppliedsource
);
stem
[
2
]
=
0x00
;
lstrcpyW
(
spec
,
suppliedsource
+
2
);
}
else
{
WCHAR
curdir
[
MAXSTRING
];
GetCurrentDirectoryW
(
sizeof
(
curdir
)
/
sizeof
(
WCHAR
),
curdir
);
stem
[
0
]
=
curdir
[
0
];
stem
[
1
]
=
curdir
[
1
];
stem
[
2
]
=
0x00
;
lstrcpyW
(
spec
,
suppliedsource
);
}
}
return
FALSE
;
return
RC_OK
;
}
/* =========================================================================
* Process a single file from the /EXCLUDE: file list, building up a list
* of substrings to avoid copying
* Returns TRUE on any failure
* ========================================================================= */
static
BOOL
XCOPY_ProcessExcludeFile
(
WCHAR
*
filename
,
WCHAR
*
endOfName
)
{
WCHAR
endChar
=
*
endOfName
;
WCHAR
buffer
[
MAXSTRING
];
FILE
*
inFile
=
NULL
;
const
WCHAR
readTextMode
[]
=
{
'r'
,
't'
,
0
};
/* Null terminate the filename (temporarily updates the filename hence
parms not const) */
*
endOfName
=
0x00
;
XCOPY_ProcessDestParm - Takes the supplied destination parameter, and
converts it into a stem
========================================================================= */
static
int
XCOPY_ProcessDestParm
(
WCHAR
*
supplieddestination
,
WCHAR
*
stem
,
WCHAR
*
spec
,
WCHAR
*
srcspec
,
DWORD
flags
)
{
WCHAR
actualdestination
[
MAX_PATH
];
DWORD
attribs
;
BOOL
isDir
=
FALSE
;
/*
Open the file */
inFile
=
_wfopen
(
filename
,
readTextMode
);
if
(
inFile
==
NULL
)
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_OPENFAIL
),
filename
);
*
endOfName
=
endChar
;
return
TRUE
;
/*
* Validate the source, expanding to full path ensuring it exists
*/
if
(
GetFullPathNameW
(
supplieddestination
,
MAX_PATH
,
actualdestination
,
NULL
)
==
0
)
{
WINE_FIXME
(
"Unexpected failure expanding source path (%d)
\n
"
,
GetLastError
())
;
return
RC_INITERROR
;
}
/* Process line by line */
while
(
fgetws
(
buffer
,
sizeof
(
buffer
)
/
sizeof
(
WCHAR
),
inFile
)
!=
NULL
)
{
EXCLUDELIST
*
thisEntry
;
int
length
=
lstrlenW
(
buffer
);
/* Destination is either a directory or a file */
attribs
=
GetFileAttributesW
(
actualdestination
);
/* Strip CRLF */
buffer
[
length
-
1
]
=
0x00
;
if
(
attribs
==
INVALID_FILE_ATTRIBUTES
)
{
/* If more than CRLF */
if
(
length
>
1
)
{
thisEntry
=
HeapAlloc
(
GetProcessHeap
(),
0
,
sizeof
(
EXCLUDELIST
));
thisEntry
->
next
=
excludeList
;
excludeList
=
thisEntry
;
thisEntry
->
name
=
HeapAlloc
(
GetProcessHeap
(),
0
,
(
length
*
sizeof
(
WCHAR
))
+
1
);
lstrcpyW
(
thisEntry
->
name
,
buffer
);
CharUpperBuffW
(
thisEntry
->
name
,
length
);
WINE_TRACE
(
"Read line : '%s'
\n
"
,
wine_dbgstr_w
(
thisEntry
->
name
));
}
}
/* If /I supplied and wildcard copy, assume directory */
/* Also if destination ends with backslash */
if
((
flags
&
OPT_ASSUMEDIR
&&
(
wcschr
(
srcspec
,
'?'
)
||
wcschr
(
srcspec
,
'*'
)))
||
(
supplieddestination
[
lstrlenW
(
supplieddestination
)
-
1
]
==
'\\'
))
{
/* See if EOF or error occurred */
if
(
!
feof
(
inFile
))
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_READFAIL
),
filename
);
*
endOfName
=
endChar
;
return
TRUE
;
}
isDir
=
TRUE
;
/* Revert the input string to original form, and cleanup + return */
*
endOfName
=
endChar
;
fclose
(
inFile
)
;
return
FALSE
;
}
}
else
{
DWORD
count
;
char
answer
[
10
]
=
""
;
WCHAR
fileChar
[
2
]
;
WCHAR
dirChar
[
2
];
/* =========================================================================
* Load a string from the resource file, handling any error
* Returns string retrieved from resource file
* ========================================================================= */
static
WCHAR
*
XCOPY_LoadMessage
(
UINT
id
)
{
static
WCHAR
msg
[
MAXSTRING
];
const
WCHAR
failedMsg
[]
=
{
'F'
,
'a'
,
'i'
,
'l'
,
'e'
,
'd'
,
'!'
,
0
};
/* Read the F and D characters from the resource file */
wcscpy
(
fileChar
,
XCOPY_LoadMessage
(
STRING_FILE_CHAR
));
wcscpy
(
dirChar
,
XCOPY_LoadMessage
(
STRING_DIR_CHAR
));
if
(
!
LoadStringW
(
GetModuleHandleW
(
NULL
),
id
,
msg
,
sizeof
(
msg
)
/
sizeof
(
WCHAR
)))
{
WINE_FIXME
(
"LoadString failed with %d
\n
"
,
GetLastError
());
lstrcpyW
(
msg
,
failedMsg
);
while
(
answer
[
0
]
!=
fileChar
[
0
]
&&
answer
[
0
]
!=
dirChar
[
0
])
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_QISDIR
),
supplieddestination
);
ReadFile
(
GetStdHandle
(
STD_INPUT_HANDLE
),
answer
,
sizeof
(
answer
),
&
count
,
NULL
);
WINE_TRACE
(
"User answer %c
\n
"
,
answer
[
0
]);
answer
[
0
]
=
toupper
(
answer
[
0
]);
}
if
(
answer
[
0
]
==
dirChar
[
0
])
{
isDir
=
TRUE
;
}
else
{
isDir
=
FALSE
;
}
}
}
else
{
isDir
=
(
attribs
&
FILE_ATTRIBUTE_DIRECTORY
);
}
return
msg
;
}
/* =========================================================================
* Load a string for a system error and writes it to the screen
* Returns string retrieved from resource file
* ========================================================================= */
static
void
XCOPY_FailMessage
(
DWORD
err
)
{
LPWSTR
lpMsgBuf
;
int
status
;
if
(
isDir
)
{
lstrcpyW
(
stem
,
actualdestination
);
*
spec
=
0x00
;
/* Ensure ends with a '\' */
if
(
stem
[
lstrlenW
(
stem
)
-
1
]
!=
'\\'
)
{
lstrcatW
(
stem
,
wchr_slash
);
}
status
=
FormatMessageW
(
FORMAT_MESSAGE_ALLOCATE_BUFFER
|
FORMAT_MESSAGE_FROM_SYSTEM
,
NULL
,
err
,
0
,
(
LPWSTR
)
&
lpMsgBuf
,
0
,
NULL
);
if
(
!
status
)
{
WINE_FIXME
(
"FIXME: Cannot display message for error %d, status %d
\n
"
,
err
,
GetLastError
());
}
else
{
const
WCHAR
infostr
[]
=
{
'%'
,
's'
,
'\n'
,
0
};
XCOPY_wprintf
(
infostr
,
lpMsgBuf
);
LocalFree
((
HLOCAL
)
lpMsgBuf
);
WCHAR
drive
[
MAX_PATH
];
WCHAR
dir
[
MAX_PATH
];
WCHAR
fname
[
MAX_PATH
];
WCHAR
ext
[
MAX_PATH
];
_wsplitpath
(
actualdestination
,
drive
,
dir
,
fname
,
ext
);
lstrcpyW
(
stem
,
drive
);
lstrcatW
(
stem
,
dir
);
lstrcpyW
(
spec
,
fname
);
lstrcatW
(
spec
,
ext
);
}
return
RC_OK
;
}
/* =========================================================================
* Output a formatted unicode string. Ideally this will go to the console
* and hence required WriteConsoleW to output it, however if file i/o is
* redirected, it needs to be WriteFile'd using OEM (not ANSI) format
* ========================================================================= */
int
XCOPY_wprintf
(
const
WCHAR
*
format
,
...)
{
main - Main entrypoint for the xcopy command
static
WCHAR
*
output_bufW
=
NULL
;
static
char
*
output_bufA
=
NULL
;
static
BOOL
toConsole
=
TRUE
;
static
BOOL
traceOutput
=
FALSE
;
#define MAX_WRITECONSOLE_SIZE 65535
Processes the args, and drives the actual copying
========================================================================= */
int
wmain
(
int
argc
,
WCHAR
*
argvW
[])
{
int
rc
=
0
;
WCHAR
suppliedsource
[
MAX_PATH
]
=
{
0
};
/* As supplied on the cmd line */
WCHAR
supplieddestination
[
MAX_PATH
]
=
{
0
};
WCHAR
sourcestem
[
MAX_PATH
]
=
{
0
};
/* Stem of source */
WCHAR
sourcespec
[
MAX_PATH
]
=
{
0
};
/* Filespec of source */
WCHAR
destinationstem
[
MAX_PATH
]
=
{
0
};
/* Stem of destination */
WCHAR
destinationspec
[
MAX_PATH
]
=
{
0
};
/* Filespec of destination */
WCHAR
copyCmd
[
MAXSTRING
];
/* COPYCMD env var */
DWORD
flags
=
0
;
/* Option flags */
const
WCHAR
PROMPTSTR1
[]
=
{
'/'
,
'Y'
,
0
};
const
WCHAR
PROMPTSTR2
[]
=
{
'/'
,
'y'
,
0
};
const
WCHAR
COPYCMD
[]
=
{
'C'
,
'O'
,
'P'
,
'Y'
,
'C'
,
'M'
,
'D'
,
0
};
va_list
parms
;
DWORD
nOut
;
int
len
;
DWORD
res
=
0
;
/* Preinitialize flags based on COPYCMD */
if
(
GetEnvironmentVariableW
(
COPYCMD
,
copyCmd
,
MAXSTRING
))
{
if
(
wcsstr
(
copyCmd
,
PROMPTSTR1
)
!=
NULL
||
wcsstr
(
copyCmd
,
PROMPTSTR2
)
!=
NULL
)
{
flags
|=
OPT_NOPROMPT
;
}
}
/* FIXME: On UNIX, files starting with a '.' are treated as hidden under
wine, but on windows these can be normal files. At least one installer
uses files such as .packlist and (validly) expects them to be copied.
Under wine, if we do not copy hidden files by default then they get
lose */
flags
|=
OPT_COPYHIDSYS
;
/*
* Allocate buffer to use when writing to console
* Note: Not freed - memory will be allocated once and released when
* xcopy ends
* Parse the command line
*/
if
(
!
output_bufW
)
output_bufW
=
HeapAlloc
(
GetProcessHeap
(),
0
,
MAX_WRITECONSOLE_SIZE
);
if
(
!
output_bufW
)
{
WINE_FIXME
(
"Out of memory - could not allocate 2 x 64K buffers
\n
"
);
return
0
;
if
((
rc
=
XCOPY_ParseCommandLine
(
suppliedsource
,
supplieddestination
,
&
flags
))
!=
RC_OK
)
{
if
(
rc
==
RC_HELP
)
return
RC_OK
;
else
return
rc
;
}
va_start
(
parms
,
format
);
len
=
vsnprintfW
(
output_bufW
,
MAX_WRITECONSOLE_SIZE
/
sizeof
(
WCHAR
),
format
,
parms
);
va_end
(
parms
);
if
(
len
<
0
)
{
WINE_FIXME
(
"String too long.
\n
"
);
return
0
;
}
/* Trace out the supplied information */
WINE_TRACE
(
"Supplied parameters:
\n
"
);
WINE_TRACE
(
"Source : '%s'
\n
"
,
wine_dbgstr_w
(
suppliedsource
));
WINE_TRACE
(
"Destination : '%s'
\n
"
,
wine_dbgstr_w
(
supplieddestination
));
/* Try to write as unicode whenever we think it's a console */
if
(
toConsole
)
{
res
=
WriteConsoleW
(
GetStdHandle
(
STD_OUTPUT_HANDLE
),
output_bufW
,
len
,
&
nOut
,
NULL
);
}
/* Extract required information from source specification */
rc
=
XCOPY_ProcessSourceParm
(
suppliedsource
,
sourcestem
,
sourcespec
,
flags
);
if
(
rc
!=
RC_OK
)
return
rc
;
/* If writing to console has failed (ever) we assume it's file
i/o so convert to OEM codepage and output */
if
(
!
res
)
{
BOOL
usedDefaultChar
=
FALSE
;
DWORD
convertedChars
;
/* Extract required information from destination specification */
rc
=
XCOPY_ProcessDestParm
(
supplieddestination
,
destinationstem
,
destinationspec
,
sourcespec
,
flags
);
if
(
rc
!=
RC_OK
)
return
rc
;
toConsole
=
FALSE
;
/* Trace out the resulting information */
WINE_TRACE
(
"Resolved parameters:
\n
"
);
WINE_TRACE
(
"Source Stem : '%s'
\n
"
,
wine_dbgstr_w
(
sourcestem
));
WINE_TRACE
(
"Source Spec : '%s'
\n
"
,
wine_dbgstr_w
(
sourcespec
));
WINE_TRACE
(
"Dest Stem : '%s'
\n
"
,
wine_dbgstr_w
(
destinationstem
));
WINE_TRACE
(
"Dest Spec : '%s'
\n
"
,
wine_dbgstr_w
(
destinationspec
));
/*
* Allocate buffer to use when writing to file. Not freed, as above
*/
if
(
!
output_bufA
)
output_bufA
=
HeapAlloc
(
GetProcessHeap
(),
0
,
MAX_WRITECONSOLE_SIZE
);
if
(
!
output_bufA
)
{
WINE_FIXME
(
"Out of memory - could not allocate 2 x 64K buffers
\n
"
);
return
0
;
}
/* Pause if necessary */
if
(
flags
&
OPT_PAUSE
)
{
DWORD
count
;
char
pausestr
[
10
];
/* Convert to OEM, then output */
convertedChars
=
WideCharToMultiByte
(
GetConsoleOutputCP
(),
0
,
output_bufW
,
len
,
output_bufA
,
MAX_WRITECONSOLE_SIZE
,
"?"
,
&
usedDefaultChar
);
WriteFile
(
GetStdHandle
(
STD_OUTPUT_HANDLE
),
output_bufA
,
convertedChars
,
&
nOut
,
FALSE
);
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_PAUSE
));
ReadFile
(
GetStdHandle
(
STD_INPUT_HANDLE
),
pausestr
,
sizeof
(
pausestr
),
&
count
,
NULL
);
}
/* Trace whether screen or console */
if
(
!
traceOutput
)
{
WINE_TRACE
(
"Writing to console? (%d)
\n
"
,
toConsole
);
traceOutput
=
TRUE
;
/* Now do the hard work... */
rc
=
XCOPY_DoCopy
(
sourcestem
,
sourcespec
,
destinationstem
,
destinationspec
,
flags
);
/* Clear up exclude list allocated memory */
while
(
excludeList
)
{
EXCLUDELIST
*
pos
=
excludeList
;
excludeList
=
excludeList
->
next
;
HeapFree
(
GetProcessHeap
(),
0
,
pos
->
name
);
HeapFree
(
GetProcessHeap
(),
0
,
pos
);
}
return
nOut
;
/* Finished - print trailer and exit */
if
(
flags
&
OPT_SIMULATE
)
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_SIMCOPY
),
filesCopied
);
}
else
if
(
!
(
flags
&
OPT_NOCOPY
))
{
XCOPY_wprintf
(
XCOPY_LoadMessage
(
STRING_COPY
),
filesCopied
);
}
if
(
rc
==
RC_OK
&&
filesCopied
==
0
)
rc
=
RC_NOFILES
;
return
rc
;
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment