Odd behavior: Command() is different from GetCommandLine() if an objman path with \?? to an existing file is in cmdline

Windows specific questions.
Post Reply
Cherry
Posts: 358
Joined: Oct 23, 2007 12:06
Location: Austria
Contact:

Odd behavior: Command() is different from GetCommandLine() if an objman path with \?? to an existing file is in cmdline

Post by Cherry »

I ran into this super weird issue right now. I wrote a tool to update some links in the object manager namespace in Windows.

This is when I noticed that if I pass an object manager path that starts with \?? and points to an existing file - and only then! - the command line argument as seen with Command() is messed up and the first parts of the path are missing, up to (but not including) the last backslash! Looking at Windows' GetCommandLine() API confirms that this is indeed happening on FreeBasic's side of things and not in my shell or Windows.

To reproduce, compile this program:

Code: Select all

#Include "windows.bi"
Print *GetCommandLine()
Print Command()
Print Command(1)
Print Command(2)
Print Command(3)
Observe the following strange results:

Normal:

Code: Select all

C:\>test.exe a b c
C:\test.exe a b c
a b c
a
b
c
Bug (assuming C:\Windows\System32\calc.exe exists):

Code: Select all

C:\>test.exe a \??\C:\Windows\System32\calc.exe c
C:\test.exe a \??\C:\Windows\System32\calc.exe c
a \calc.exe c
a
\calc.exe
c
Note how "\??\C:\Windows\System32\calc.exe" got truncated to "\calc.exe"!

Also happening if I put the value in doublequotes:

Code: Select all

C:\>test.exe a "\??\C:\Windows\System32\calc.exe" c
C:\test.exe a "\??\C:\Windows\System32\calc.exe" c
a \calc.exe c
a
\calc.exe
c
Also happening with device files:

Code: Select all

C:\>test.exe a \??\NUL c
C:\test.exe a \??\NUL c
a \NUL c
a
\NUL
c
Even happening when the string doesn't even contain a regular path but still eventually resolves to a filesystem file somehow and still goes through the \?? directory at the beginning:

Code: Select all

C:\>test.exe a \??\GLOBALROOT\SystemRoot\System32\calc.exe b
C:\test.exe a "\??\GLOBALROOT\SystemRoot\System32\calc.exe b
a \calc.exe c
a
\calc.exe
c
Not happening with non-existing files: (This makes it extra weird!)

Code: Select all

C:\>test.exe a \??\C:\Windows\System32\IDontExist.exe c
C:\test.exe a \??\C:\Windows\System32\IDontExist.exe c
a \??\C:\Windows\System32\IDontExist.exe c
a
\??\C:\Windows\System32\IDontExist.exe
c
Not happening with other object manager paths which resolve to the same file but don't use \??:

Code: Select all

C:\>test.exe a \GLOBAL??\C:\Windows\System32\calc.exe c
C:\test.exe a \GLOBAL??\C:\Windows\System32\calc.exe c
a \GLOBAL??\C:\Windows\System32\calc.exe c
a
\GLOBAL??\C:\Windows\System32\calc.exe
c

Code: Select all

C:\>test.exe a \Device\HarddiskVolume3\Windows\System32\calc.exe c
C:\test.exe a \Device\HarddiskVolume3\Windows\System32\calc.exe c
a \Device\HarddiskVolume3\Windows\System32\calc.exe c
a
\Device\HarddiskVolume3\Windows\System32\calc.exe
c

Code: Select all

C:\>test.exe a \SystemRoot\System32\calc.exe c
C:\test.exe a \SystemRoot\System32\calc.exe c
a \SystemRoot\System32\calc.exe c
a
\SystemRoot\System32\calc.exe
c
What the heck is going on here and how can I prevent it?

If it was just always happening when the commandline argument started with "\??", I'd assume some kind of weird parsing bug, but the fact that it only happens for existing files makes this seem intentional but extremely unexpected.

---

EDIT: I looked at Process Monitor while starting the program and saw that it opens the file, and this is the stack:
Image

This even happens with an entirely empty FreeBasic program, if I pass any argument starting with \??. I think this is very unexpected!

EDIT2: The Stack suggests it's part of _getmainargs which is part of msvcrt... so I'd assume that the same would happen with a C++ program compiled with Visual Studio, but it does not! I created this program to test:

Code: Select all

#include <iostream>
#include <windows.h>

int main(int argc, char* argv[]) {
    std::cout << GetCommandLineA() << std::endl;

    for (int i = 1; i < argc; i++) {
        std::cout << argv[i] << std::endl;
    }

    return 0;
}
...and it doesn't exhibit this behavior.
Cherry
Posts: 358
Joined: Oct 23, 2007 12:06
Location: Austria
Contact:

Re: Odd behavior: Command() is different from GetCommandLine() if an objman path with \?? to an existing file is in cmdl

Post by Cherry »

Ah. I see. OK this has nothing to do with real globbing, I'd say, as the result makes no sense. But I guess "backslashes in arguments can cause problems" is broad enough to apply. Thanks, this solved the issue!
hhr
Posts: 211
Joined: Nov 29, 2019 10:41

Re: Odd behavior: Command() is different from GetCommandLine() if an objman path with \?? to an existing file is in cmdl

Post by hhr »

Pasting

Code: Select all

Extern _dowildcard Alias "_dowildcard" As Long
Dim Shared _dowildcard As Long = 0
into the program works for me.

@fxm
The second (For MinGW-w64 runtime) works with Win32/64 and gas, gas64, gcc.
Maybe 'Win32' in 'Disabling filename globbing under Win32' is outdated. I think 'Windows' or Win32/64 or something similar would be better.
Thanks in advance.
Post Reply