Fixes bug#40694
This allows whitespace and any other text on the line when changing drive letters. Mostly I expect it crops up when commands are concatenated and the readability whitespace is added to the end of the command. For example C: & dir results in "C: " and "dir" as the two commands. We cannot unconditionally remove whitespace as some commands rely on it.
Signed-off-by: Jason Edmeades us@edmeades.me.uk --- programs/cmd/tests/test_builtins.cmd | 35 ++++++++++++++++++++++++ programs/cmd/tests/test_builtins.cmd.exp | 7 +++++ programs/cmd/wcmdmain.c | 9 ++++-- 3 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 6f2ef4a843..49c2d9e1c4 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -3042,6 +3042,41 @@ echo ------------ Testing start /W ------------ echo start /W failed to wait>foobar.txt start /W "" cmd /C "ping -n1 & echo start /W seems to really wait>foobar.txt"& type foobar.txt& del foobar.txt
+echo ------------ Testing changing the drive letter ---------- +pushd C:\ + +echo Normal: +call :setError 0 +C: +if errorlevel 1 echo Normal drive change failed + +echo Normal+space +call :setError 0 +C:@space@ +if errorlevel 1 echo Normal+space drive change failed + +echo Normal+space+garbage +call :setError 0 +C: garbage +if errorlevel 1 echo Normal+space+garbage drive change failed + +call :setError 0 +echo Quoted should fail +"C:" +if not errorlevel 1 echo quoted drive change unexpectedly worked + +echo Normal+tab +call :setError 0 +C:@tab@ +if errorlevel 1 echo Normal+tab drive change failed + +echo Normal+tab+garbage +call :setError 0 +C:@tab@garbagetab +if errorlevel 1 echo Normal+tab+garbage drive change failed + +popd + echo ------------ Testing combined CALLs/GOTOs ------------ echo @echo off>foo.cmd echo goto :eof>>foot.cmd diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 0eb5b966e8..cfde83b063 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1588,6 +1588,13 @@ PATH=try2 PATH=try3 ------------ Testing start /W ------------ start /W seems to really wait +------------ Testing changing the drive letter ---------- +Normal: +Normal+space +Normal+space+garbage +Quoted should fail +Normal+tab +Normal+tab+garbage ------------ Testing combined CALLs/GOTOs ------------ world cheball diff --git a/programs/cmd/wcmdmain.c b/programs/cmd/wcmdmain.c index 827ddd2121..0d02f1f388 100644 --- a/programs/cmd/wcmdmain.c +++ b/programs/cmd/wcmdmain.c @@ -1327,13 +1327,18 @@ void WCMD_execute (const WCHAR *command, const WCHAR *redirects, cmd = new_cmd;
/* - * Changing default drive has to be handled as a special case. + * Changing default drive has to be handled as a special case, anything + * else if it exists after whitespace is ignored */
- if ((strlenW(cmd) == 2) && (cmd[1] == ':') && IsCharAlphaW(cmd[0])) { + if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && + (!cmd[2] || cmd[2] == ' ' || cmd[2] == '\t')) { WCHAR envvar[5]; WCHAR dir[MAX_PATH];
+ /* Ignore potential garbage on the same line */ + cmd[2]=0x00; + /* According to MSDN CreateProcess docs, special env vars record the current directory on each drive, in the form =C: so see if one specified, and if so go back to it */
Fixes bug#42823
A call or a goto will find the next matching label not the first one in the file. This means it could be later in the file or it could be earlier in the file, so make goto (which 'call' also uses) first scan from current file position to the end of the file, and subsequently from the start of the file to the wrap point.
Signed-off-by: Jason Edmeades us@edmeades.me.uk --- programs/cmd/builtins.c | 67 +++++++++++++++++------- programs/cmd/tests/test_builtins.cmd | 51 ++++++++++++++++++ programs/cmd/tests/test_builtins.cmd.exp | 13 +++++ 3 files changed, 113 insertions(+), 18 deletions(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 35b68bd45a..3a0981d222 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -2582,30 +2582,61 @@ void WCMD_goto (CMD_LIST **cmdList) { if (labelend) *labelend = 0x00; WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
- SetFilePointer (context -> h, 0, NULL, FILE_BEGIN); - while (*paramStart && - WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) { - str = string; + /* Loop through potentially twice - once from current file position + through to the end, and second time from start to current file + position */ + if (*paramStart) { + int loop; + LARGE_INTEGER startli; + for (loop=0; loop<2; loop++) { + if (loop==0) { + /* On first loop, save the file size */ + startli.QuadPart = 0; + startli.u.LowPart = SetFilePointer(context -> h, startli.u.LowPart, + &startli.u.HighPart, FILE_CURRENT); + } else { + /* On second loop, start at the beginning of the file */ + WINE_TRACE("Label not found, trying from beginning of file\n"); + if (loop==1) SetFilePointer (context -> h, 0, NULL, FILE_BEGIN); + }
- /* Ignore leading whitespace or no-echo character */ - while (*str=='@' || isspaceW (*str)) str++; + while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) { + str = string;
- /* If the first real character is a : then this is a label */ - if (*str == ':') { - str++; + /* Ignore leading whitespace or no-echo character */ + while (*str=='@' || isspaceW (*str)) str++;
- /* Skip spaces between : and label */ - while (isspaceW (*str)) str++; - WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str)); + /* If the first real character is a : then this is a label */ + if (*str == ':') { + str++;
- /* Label ends at whitespace or redirection characters */ - labelend = strpbrkW(str, labelEndsW); - if (labelend) *labelend = 0x00; - WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str)); + /* Skip spaces between : and label */ + while (isspaceW (*str)) str++; + WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
- if (lstrcmpiW (str, paramStart) == 0) return; - } + /* Label ends at whitespace or redirection characters */ + labelend = strpbrkW(str, labelEndsW); + if (labelend) *labelend = 0x00; + WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str)); + + if (lstrcmpiW (str, paramStart) == 0) return; + } + + /* See if we have gone beyond the end point if second time through */ + if (loop==1) { + LARGE_INTEGER curli; + curli.QuadPart = 0; + curli.u.LowPart = SetFilePointer(context -> h, curli.u.LowPart, + &curli.u.HighPart, FILE_CURRENT); + if (curli.QuadPart > startli.QuadPart) { + WINE_TRACE("Reached wrap point, label not found\n"); + break; + } + } + } + } } + WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET)); context -> skip_rest = TRUE; } diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 49c2d9e1c4..0a8122c5e3 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -3027,6 +3027,57 @@ echo FAILURE at dest 10 :dest10:this is also ignored echo Correctly ignored trailing information
+rem Testing which label is reached when there are many options +echo Begin: +set nextlabel= +call :sub +set nextlabel=middle +goto :sub + +:sub +echo ..First sub +if not "%nextlabel%"=="" goto :%nextlabel% +goto :EOF + +:sub +echo ..Second sub +if not "%nextlabel%"=="" goto :%nextlabel% +goto :EOF + +:middle +echo Middle: +set nextlabel= +call :sub +set nextlabel=nearend +goto :sub + +:sub +echo ..Third sub +if not "%nextlabel%"=="" goto :%nextlabel% +goto :EOF + +:nearend +echo Near end: +set nextlabel= +call :sub +set nextlabel=end +goto :sub + +:sub +echo ..Fourth sub +if not "%nextlabel%"=="" goto :%nextlabel% +goto :EOF + +:end +echo At end: +set nextlabel= +call :sub +set nextlabel=done +goto :sub + +:done +echo Finished + echo ------------ Testing PATH ------------ set WINE_backup_path=%path% set path=original diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index cfde83b063..7f4f724c3b 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -1582,6 +1582,19 @@ goto with redirections worked Ignoring double colons worked label with mixed whitespace and no echo worked Correctly ignored trailing information +Begin: +..First sub +..First sub +Middle: +..Third sub +..Third sub +Near end: +..Fourth sub +..Fourth sub +At end: +..First sub +..First sub +Finished ------------ Testing PATH ------------ PATH=original PATH=try2
Fixes bug#38849
In an error condition, the wrong variable was being used for an insert, resulting in a read from uninitialized data. This could be triggered for example by 'ftype jason=', and the error message should have included jason but instead was just ''.
Signed-off-by: Jason Edmeades us@edmeades.me.uk --- programs/cmd/builtins.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index 3a0981d222..57a41c4752 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -4923,7 +4923,7 @@ void WCMD_assoc (const WCHAR *args, BOOL assoc) { LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR)); } - WCMD_output_stderr(msgbuffer, keyValue); + WCMD_output_stderr(msgbuffer, args); errorlevel = 2; }
Fixes bug#40706
xcopy should remove read only permissions from the destination file unless the /k option is supplied.
Signed-off-by: Jason Edmeades us@edmeades.me.uk --- programs/xcopy/tests/xcopy.c | 25 +++++++++++++++++++++++++ programs/xcopy/xcopy.c | 25 ++++++++++++++++++------- programs/xcopy/xcopy.h | 1 + programs/xcopy/xcopy.rc | 1 + 4 files changed, 45 insertions(+), 7 deletions(-)
diff --git a/programs/xcopy/tests/xcopy.c b/programs/xcopy/tests/xcopy.c index 94cede6f67..17b4406b74 100644 --- a/programs/xcopy/tests/xcopy.c +++ b/programs/xcopy/tests/xcopy.c @@ -108,6 +108,30 @@ static void test_parms_syntax(void) RemoveDirectoryA("xcopytest2"); }
+static void test_keep_attributes(void) +{ + DWORD rc; + + SetFileAttributesA("xcopy1", FILE_ATTRIBUTE_READONLY); + + rc = runcmd("xcopy xcopy1 xcopytest"); + ok(rc == 0, "xcopy failed to copy read only file\n"); + ok((GetFileAttributesA("xcopytest\xcopy1") & FILE_ATTRIBUTE_READONLY) != FILE_ATTRIBUTE_READONLY, + "xcopy should not have copied file permissions\n"); + SetFileAttributesA("xcopytest\xcopy1", FILE_ATTRIBUTE_NORMAL); + DeleteFileA("xcopytest\xcopy1"); + + rc = runcmd("xcopy /K xcopy1 xcopytest"); + ok(rc == 0, "xcopy failed to copy read only file with /k\n"); + ok((GetFileAttributesA("xcopytest\xcopy1") & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY, + "xcopy did not keep file permissions\n"); + SetFileAttributesA("xcopytest\xcopy1", FILE_ATTRIBUTE_NORMAL); + DeleteFileA("xcopytest\xcopy1"); + + SetFileAttributesA("xcopy1", FILE_ATTRIBUTE_NORMAL); + + } + START_TEST(xcopy) { char tmpdir[MAX_PATH]; @@ -130,6 +154,7 @@ START_TEST(xcopy)
test_date_format(); test_parms_syntax(); + test_keep_attributes();
DeleteFileA("xcopy1"); RemoveDirectoryA("xcopytest"); diff --git a/programs/xcopy/xcopy.c b/programs/xcopy/xcopy.c index e380cde226..cdf0bdfb26 100644 --- a/programs/xcopy/xcopy.c +++ b/programs/xcopy/xcopy.c @@ -570,15 +570,25 @@ static int XCOPY_DoCopy(WCHAR *srcstem, WCHAR *srcspec, ret = RC_WRITEERROR; goto cleanup; } - } + } else {
- /* If /M supplied, remove the archive bit after successful copy */ - if (!skipFile) { - if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) && - (flags & OPT_REMOVEARCH)) { - SetFileAttributesW(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE)); + if (!skipFile) { + /* If keeping attributes, update the destination attributes + otherwise remove the read only attribute */ + if (flags & OPT_KEEPATTRS) { + SetFileAttributesW(copyTo, srcAttribs | FILE_ATTRIBUTE_ARCHIVE); + } else { + SetFileAttributesW(copyTo, + (GetFileAttributesW(copyTo) & ~FILE_ATTRIBUTE_READONLY)); + } + + /* If /M supplied, remove the archive bit after successful copy */ + if ((srcAttribs & FILE_ATTRIBUTE_ARCHIVE) && + (flags & OPT_REMOVEARCH)) { + SetFileAttributesW(copyFrom, (srcAttribs & ~FILE_ATTRIBUTE_ARCHIVE)); + } + filesCopied++; } - filesCopied++; } } } @@ -764,6 +774,7 @@ static int XCOPY_ParseCommandLine(WCHAR *suppliedsource, case 'N': flags |= OPT_SHORTNAME; break; case 'U': flags |= OPT_MUSTEXIST; break; case 'R': flags |= OPT_REPLACEREAD; break; + case 'K': flags |= OPT_KEEPATTRS; break; case 'H': flags |= OPT_COPYHIDSYS; break; case 'C': flags |= OPT_IGNOREERRORS; break; case 'P': flags |= OPT_SRCPROMPT; break; diff --git a/programs/xcopy/xcopy.h b/programs/xcopy/xcopy.h index 3e9644efe8..9c6ee1176f 100644 --- a/programs/xcopy/xcopy.h +++ b/programs/xcopy/xcopy.h @@ -48,6 +48,7 @@ #define OPT_EXCLUDELIST 0x00020000 #define OPT_DATERANGE 0x00040000 #define OPT_DATENEWER 0x00080000 +#define OPT_KEEPATTRS 0x00100000
#define MAXSTRING 8192
diff --git a/programs/xcopy/xcopy.rc b/programs/xcopy/xcopy.rc index fac3519175..e303f35bdd 100644 --- a/programs/xcopy/xcopy.rc +++ b/programs/xcopy/xcopy.rc @@ -74,6 +74,7 @@ Where:\n\ [/A] Only copy files with archive attribute set.\n\ [/M] Only copy files with archive attribute set, removes the\n\ \tarchive attribute.\n\ +[/K] Copy file attributes, without this attributes are not preserved.\n\ [/D | /D:m-d-y] Copy new files or those modified after the supplied date.\n\ \t\tIf no date is supplied, only copy if destination is older\n\ \t\tthan source.\n\n"