Incorrect quoting of command-line arguments for all programs in Perl\exec

Hi,

It seems all executables under Perl\exec have their command-line arguments interpreted by the system even if they are quoted. For instance, any of the following commands:
podchecker "a<b"
shasum "a<b"
exiftool -ver "-testName<DateTimeOriginal"
result in the following output, and no further processing:
The system cannot find the file specified.

This is a serious problem because it prevents passing the very common "-destination_tag<source_tag" construct to exiftool, to copy a source_tag to a destination_tag. The exiftool program isn’t called at all.

Bypassing the “exec” version of the program, like this:
C:\Perl\site\bin\exiftool -ver "-testName<DateTimeOriginal"
results in this message from exiftool, as expected:
12.76
No file specified

To be clear, this isn’t an exiftool problem, because all programs in the Perl\exec directory have the same problem. The behavior (and error message) is the same as if the quotes were removed:
exiftool -ver -testName<DateTimeOriginal (note the missing quotes)
yields:
The system cannot find the file specified.

Hypothesis:
Given that all programs under Perl\exec are in fact the same binary (bit-for-bit), my guess is this “surrogate” executable sets up a number of environment variables, reads the command line arguments, looks at ARGV0 to find its name, and calls the program of the same name down the path. But, when reading the command line arguments, the surrogate tries to interpret them, or it simply drops the quotes before calling the final executable. In any case, it’s a show stopper.

Any suggestions? Should I remove all programs from Perl\exec, except for the core Perl executables? By core Perl executables, I mean perl.exe, perl5.36.3.exe, runperl.exe, wperl.exe, par.exe, etc.

Thanks,

Martin

Hi @gamin-mb ,

That’s a very strange issue. The quotes being lost shouldn’t really be an issue, since we don’t run the underlying executable from a shell environment. We’re just passing the args as they are received by our executable onto the actual executable you’re trying to invoke. For what it’s worth, and since you seem technically inclined, this is where the “magic” happens:

I did a quick test to verify, and indeed it seems to work as expected.

I wonder if there might be some other issue with your runtime or your local checkout of it. Does state exec which exiftool return you a valid executable path?

Note state exec essentially runs whatever you give it within the context of your runtime. So you could also use it to run eg. state exec -- exiftool -ver "-testName<DateTimeOriginal".

Could you also share your State Tool version? state --version.

Hi Nathan,

Here’s the requested information, and some more for context:

C:\Users\root>state exec which exiftool
Operating on project gamin-mb/Perl-5.36-Windows, located at C:\Users\root\Perl-5.36-Windows.

/cygdrive/c/Perl/site/bin/exiftool

Obviously, I have Cygwin installed, because the “which” command is not available on Windows. This explains the “Linux” path format. Here’s the origin of my “which” command, and below is the output of the equivalent Windows command, “where”:

C:\Users\root>where which
C:\cygwin64\bin\which.exe

C:\Users\root>where exiftool
C:\Perl\exec\exiftool.exe
C:\Perl\site\bin\exiftool
C:\Perl\site\bin\exiftool.bat
C:\Users\root\AppData\Local\activestate\cache\bin\exiftool.exe

C:\Users\root>state exec -- exiftool -ver "-testName<DateTimeOriginal"
Operating on project gamin-mb/Perl-5.36-Windows, located at C:\Users\root\Perl-5.36-Windows.

The system cannot find the file specified.

C:\Users\root>state --version
ActiveState CLI by ActiveState Software Inc.
License BSD 3
Version 0.42.0-SHAcbce75e
Revision cbce75e6c093fd3195cbe18e6ed0c26a853459b4
Branch release
Built Mon Nov 27 2023 17:56:19 +0000 UTC

C:\Users\root>exiftool -ver "-testName<DateTimeOriginal"
The system cannot find the file specified.

C:\Users\root>C:\Perl\exec\exiftool.exe -ver "-testName<DateTimeOriginal"
The system cannot find the file specified.

C:\Users\root>C:\Users\root\AppData\Local\activestate\cache\bin\exiftool.exe -ver "-testName<DateTimeOriginal"
The system cannot find the file specified.

C:\Users\root>C:\Perl\site\bin\exiftool -ver "-testName<DateTimeOriginal"
12.76
No file specified

C:\Users\root>C:\Perl\site\bin\exiftool -ver -testName<DateTimeOriginal
The system cannot find the file specified.

So, of all the available exiftool executables, only the ones in site\bin runs properly; none of the other versions seem to even be invoked. We also get the error with the site\bin executable if the quotes are missing. I think the error message doesn’t come from Perl, because it’s a known Windows error string, but not a common Perl error string.

About the state tool:

All your questions involving the state tool puzzle me. Only the admin user (root) has the state tool installed on my computer, and I switch to a non-admin user for development. Consequently, state shouldn’t be a factor in deciding which executable runs. Indeed, I get the same results with both the admin and the non-admin users (except that invoking state as you asked fails for the non-admin user).

By the way, state update answers with “Update not found for release”, and I updated in early 2024, so I’m sure I had the latest version before my last state pull.

My personal assumptions and analysis:

It seems this is programmed in Go, which I don’t know. But I’m pretty sure the magic actually happens one line above and a few lines below:

10:        userArgs := os.Args[1:]
17:        if err := cmd.Run(); err != nil {

I wouldn’t be surprised if os.Args[] had no quotes left, and if cmd.Run() passed its then-unquoted arguments to some shell or system call which then interpreted the < as a standard input redirection, trying to read the file DateTimeOriginal. It would explain why “The system cannot find the file specified”.

On Windows, the Perl system() function sends its arguments to a shell, unless you carefully manage the argument list, so perhaps the Go Run() function does something similar.

Indeed, if I create an empty file named ‘DateTimeOriginal’ in the current directory, the behavior is consistent with the system looking for and finding a file named DateTimeOriginal (as if there were no quotes), as in this example:

C:\Users\root>dir /B DateTimeOriginal
DateTimeOriginal

C:\Users\root>exiftool -ver "-testName<DateTimeOriginal"
12.76
No file specified

I am really curious to understand why you don’t have the problem.

Just to be sure it’s not some interaction with Cygwin, I renamed my top C:\cygwin64 directory, to make it inaccessible; this didn’t change the behavior of the programs under Perl\exec.

For completeness: if I rename Perl\site\bin\exiftool.bat to something else, then I cannot run exiftool at all:

C:\Users\root>exiftool -ver
state-exec: run failed: cannot run command: command “c:\perl\site\bin\exiftool.bat” failed: exec: “c:\perl\site\bin\exiftool.bat”: file does not exist
state-exec: Not user serviceable; Please contact support for assistance.

This indicates that C:\Perl\exec\exiftool.exe wants to call C:\Perl\site\bin\exiftool.bat, but will not call C:\Perl\site\bin\exiftool (that’s probably a Windows PathExt thing). If I restore Perl\site\bin\exiftool.bat and call it directly, I get this:

C:\Users\root>C:\Perl\site\bin\exiftool.bat -ver "-testName<DateTimeOriginal"
12.76
No file specified

So the problem isn’t specifically with the batch file; it works if called directly, but not if C:\Perl\exec\exiftool.exe invokes it.

That’s as far as I can go. I hope this helps.

Thanks,

Martin

Hey @gamin-mb, so I’m a little confused as to why you think ActiveState is involved at all here. From what you’ve shared you’re running an exiftool that doesn’t come from us.

C:\Users\root>where exiftool
C:\Perl\exec\exiftool.exe
C:\Perl\site\bin\exiftool
C:\Perl\site\bin\exiftool.bat
C:\Users\root\AppData\Local\activestate\cache\bin\exiftool.exe

Only the last path here comes from ActiveState, unless you have an older installer that installs to C:\Perl? New installs with the State Tool do not use this path. If indeed you used an older installer, can you share how you installed this version of Perl?

Note state exec will essentially set up your runtime environment and then run the executable you passed it, but that environment will also include your global system environment, which contains exiftool. So in this case when you run state exec exiftool .. it’s really just running the same one as without state exec ...

Hi Nathan,

If you find the paths misleading, perhaps you didn’t realize that I chose my own --runtime-path? Let me go into details…

  1. I did request and install exiftool from ActiveState (the 4th package in the list):
C:\Users\root>state packages
█ Listing Packages

Operating on project gamin-mb/Perl-5.36-Windows, located at C:\Users\root\Perl-5.36-Windows.

  Name                   Version
──────────────────────────────────
  Excel-Writer-XLSX      Auto
  File-chdir             Auto
  File-Copy-Recursive    Auto
  Image-ExifTool         Auto
  PAR-Packer             Auto
  PadWalker              Auto
  Pod-Simple-Wiki        Auto
  Spreadsheet-Read       Auto
  Term-ProgressBar       Auto
  Test-Cmd               Auto
  Text-CSV_XS            Auto
  Win32-API              Auto
  Win32-Console          Auto
  Win32-File             Auto
  XML-LibXML             Auto

In addition, there are references to “exiftool.exe” and “c:\perl\site\bin\exiftool” in C:\Perl\exec\meta.as and in files under C:\Users\root\AppData\Local\ActiveState\cache\artifacts, which are files or folders created by ActiveState.

  1. I installed Perl as instructed by your support team, as discussed here: How do I get rid of extra runtimes?. More specifically, I did the following, in this order, on January 23, 2024:
I deleted C:\Perl
state clean uninstall --all
I deleted C:\Users\root\Perl-5.36.0-Windows
I downloaded state-remote-installer.exe from ActiveState
state-remote-installer.exe
state checkout gamin-mb/Perl-5.36-Windows --runtime-path C:/Perl
state use Perl-5.36-Windows

I also performed a few state pull; on January 25, February 16, and February 26 or 27.

  1. As I pointed out at the beginning of my first message, the error occurs not only with exiftool but with every program installed by the state tool in C:\Perl\exec or in …\AppData\Local\activestate\cache\bin. None of these programs seem to be allowed to run when passed a command-line argument containing an input redirection character (<), even if the character is quoted. See for instance what happens with as basic a command as this:

C:\Users\root>C:\Users\root\AppData\Local\activestate\cache\bin\pod2text.exe "a<b"
The system cannot find the file specified.

To simplify:

Let’s eliminate all these variables, and use a simple script that we can share: put the following line in a file called testarg.txt:
printf "Argument: '%s'.\n", $ARGV[0];
This script should print the first command-line argument and exit. Note that I chose to give the script a .txt extension, and I call perl explicitly on it to prevent Windows from choosing some command from the registry that might quote (or not) its arguments.

This is the output I get. The first one is expected, the second one is an error, the third one is an error and it also created a file named “b”, whose contents is “Argument: ‘a’.”:

C:\Users\root>C:\Users\root\AppData\Local\activestate\cache\bin\perl.exe testarg.txt “a<b”
Argument: ‘a<b’.

C:\Users\root>state exec perl testarg.txt “a<b”
Operating on project gamin-mb/Perl-5.36-Windows, located at C:\Users\root\Perl-5.36-Windows.

The system cannot find the file specified.

C:\Users\root>state exec perl testarg.txt “a>b”
Operating on project gamin-mb/Perl-5.36-Windows, located at C:\Users\root\Perl-5.36-Windows.

The second and third cases are consistent with Windows interpreting the argument as if the quotes weren’t present, which gives me a strong suspicion that state-exec drops the quotes around its arguments. I guess most of the executables under Perl\exec are in fact renamed copies of state-exec, and therefore they inherit the problem.

When you run my testarg.txt under the same conditions, what do you get? If it works for you, then I’ll scratch my head to find another explanation.

Thanks,

Martin

Thanks so much for going through the trouble of providing a test case, that is incredibly helpful! I was able to reproduce your issue. I’ve filed a bug for us to address this. We’ll likely get this fixed in version 0.44.0, which is due by the end of April. We’ll do some investigating and if the severity of this is high enough we might even get it into the next version which is due end of this month. If you like I can share an early beta version with you here.

For now all I can offer as a workaround is to use the underlying executable directly. You can obtain this by running:

state exec where exiftool

Though I’m pretty sure you already figured this out going by your previous messages.

Really the only downside of doing this is that it does not set up your environment for you. But that’s kind of how it is normally anyway, the fact that we set up your environment for you as the executable is ran is a quality of life feature that’s sadly just missbehaving.

Apologies for the inconvenience. And thanks for sticking with me!

1 Like

Well, thanks for sticking with me too.

I find I’m getting very good and timely support from ActiveState, especially given that I’m not paying for it. The least I can do is to be precise and helpful (and sometimes insistent :wink:).

Sure, I’ll be happy to test a beta, provided I can revert to a stable release if the beta is a show stopper.

Martin

1 Like

Hi,

A few related questions:

  1. Is this issue fixed?
  2. Is version 0.44.0 of the State tool available?
  3. Is there a Changelog or list of fixed issues for the State tool?

As far as I know, the only way to update the State tool is with state update, but I don’t know of any way of knowing what changes this would introduce and I’d like to evaluate the risks/benefits of the next version before committing to it.

Thanks,

Martin

Sorry for the delay

  1. " There is a note about us using powershell now that should fix this user’s issue. Note this is just on beta at the moment." so unless you want to run beta i’d say wait a little
  2. yes
  3. cli/changelog.md at beta ¡ ActiveState/cli ¡ GitHub

Thanks Nicole.

  1. Please confirm that I understand this correctly: the Changelog says

state run and state exec now use powershell under the hood instead of cmd.

and

Fixed issue where state run and state exec would not forward arguments correctly on Windows. This is a limitation of the cmd shell. We now utilize powershell instead to facilitate commands.

but it doesn’t mean users need to use Powershell to invoke scripts; this is transparent to the users (except for the cmd built-ins).

  1. How does one know what version will be installed before issuing a state update?
    Is is always defined by whatever is in https://github.com/ActiveState/cli/blob/master/changelog.md?

Thanks again.

Martin

Hi @gamin-mb, that’s correct; this should be transparent to the user. Also the next version after this one (ie. version 45) will be disabling the use of shell built-ins for state exec, as this causes unintended side-effects for an unintended use-case. Note version 44 should be releasing very soon.

You are correct in that state update will update to whatever the latest version is, although you would have to look at the corresponding branch for the channel you’re on. In your case you are probably on the release channel, which maps to cli/changelog.md at release · ActiveState/cli · GitHub

1 Like

Hey @gamin-mb, State Tool v0.44 has just been released, which includes a fix for the issue you raised. Please take it for a spin and let us know if you’re still having issues.

Hi,

There’s some improvement, but I still have issues.

This is fixed:

C:\Users\root>.\testarg.pl "a<b"
Argument: 'a<b'.

C:\Users\root>type testarg.pl
printf "Argument: '%s'.\n", $ARGV[0];

The rest is still incorrect; no change with respect to state version 0.42:

C:\Users\root>shasum "a<b"
The system cannot find the file specified.

C:\Users\root>where shasum
C:\Perl\exec\shasum.exe
C:\Users\root\AppData\Local\activestate\cache\bin\shasum.exe

C:\Users\root>exiftool -ver "-testName<DateTimeOriginal"
The system cannot find the file specified.

C:\Users\root>where exiftool
C:\Perl\exec\exiftool.exe
C:\Perl\site\bin\exiftool
C:\Perl\site\bin\exiftool.bat
C:\Users\root\AppData\Local\activestate\cache\bin\exiftool.exe

I can obtain the correct behavior if I invoke the ultimate scripts themselves (I believe this bypasses the state framework):

C:\Users\root>C:\Perl\bin\shasum "a<b"
shasum: a<b: Invalid argument

C:\Users\root>C:\Perl\site\bin\exiftool.bat -ver "a<b"
12.76
Error: File not found - a<b

As noted in my previous posts, both shasum and exiftool come from ActiveState. ExifTool is an optional package, but shasum may be part of the standard Perl utilities. These programs are not the cause of the bug; they simply receive arguments stripped of their quotes when called through the state framework.

Before the above tests, I ran state update, but I didn’t run state pull because of a build error (on PAR and PAR-Packer; which happens with every new version of these packages…). This is what I’m currently running:

C:\Users\root>state --version
ActiveState CLI by ActiveState Software Inc.
License BSD 3
Version 0.44.0-SHA872fdb7
Revision 872fdb7465958cd0fa904d0724138ac21d90c1fb
Channel release
Built Mon May 27 2024 20:14:01 +0000 UTC

Would running state pull fix the problems? I cannot do that yet because I need a fully functional build.

Thanks,

Martin

Thanks for the update @gamin-mb, sorry you’re still (partly) affected by this issue.

It’s very strange, because the executable you’re running doesn’t do anything special with argument handling. You can see for yourself here:

I wonder if perhaps the executors for your project are still from an older version of the state tool. We update these automatically when you source the runtime for your project, but perhaps that hasn’t happened yet?

Could you try running state refresh from C:\Perl? And if that doesn’t work, run state clean cache and then again state refresh. The latter will fully delete your runtime files, and state refresh will then reinstall them.

Hopefully that fixes your issue, and if not we’ll keep digging.

Sure, I can do a state refresh, but before I do, can you explain what it does compared to state pull? The documentation says it “Updates the given project runtime based on its current configuration”, but I cannot update my project because the build failed (I filed a separate ticket for that).

Thanks.

@gamin-mb Sure thing. state refresh doesn’t do anything except source your local runtime, it does not make any changes to your local checkout or remote project. state pull instead pulls in any changes from the remote project to your local checkout, and then sources your local runtime. Basically you shouldn’t need to run state refresh after running state pull, because state pull already did what state refresh would do.

Think of it like downloading and running an installer. state pull downloads AND runs the installer. state refresh just runs the installer. Bit overly simplified but hopefully that helps.

Further useful context: When I say “local checkout” I am talking about the directory with your activestate.yaml. This yaml holds a commit ID that basically tells the State Tool “this is what I want to be using”. state refresh then takes that ID and actually ensures that the related runtime is sourced and ready for you to be used. state pull may update this ID first, according to what your remote project looks like.

Unfortunately, that didn’t fix the problem (remember that the correct error message would be about an invalid argument, not that the system cannot find the file):

C:\Users\root\Perl-5.36-Windows>state clean cache
█ Cleaning Cached Runtimes


Please Confirm
You are about to reset the State Tool cache. Continue? (y/N)
> y
Successfully cleaned cache.

C:\Users\root\Perl-5.36-Windows>state refresh
Runtime updated for project gamin-mb/Perl-5.36-Windows, located at C:\Users\root\Perl-5.36-Windows.
For editors and other tooling use the executables at: C:\Perl\exec.


C:\Users\root\Perl-5.36-Windows>shasum "a<b"
The system cannot find the file specified.

Some additional information:

  1. C:\Perl\exec\shasum.exe is dated June 3, so it was indeed updated with state 0.44.
  2. Running the same command in a PowerShell window makes no difference.
  3. I tried using various PowerShell quoting rules, to no avail:
PS C:\Users\root> shasum "a<b"
The system cannot find the file specified.
PS C:\Users\root> shasum `"a<b`"
The system cannot find the file specified.
PS C:\Users\root> shasum ""a<b""
The system cannot find the file specified.

I was waiting for this ticket to be fixed: Build error for new versions of PAR and PAR-Packer, but I just checked and the build seems to have passed. I did a state pull, checked again and the results are the same; all programs in C:\Perl\exec behave as if their arguments were stripped of their quotes. Everything in C:\Perl\exec is dated June 11, at the time I did a state pull.

C:\Users\root>where shasum
C:\Perl\exec\shasum.exe

C:\Users\root>shasum "a<b"
The system cannot find the file specified.

C:\Users\root>C:\Perl\bin\shasum "a<b"
shasum: a<b: Invalid argument

C:\Users\root>where exiftool
C:\Perl\exec\exiftool.exe
C:\Perl\site\bin\exiftool
C:\Perl\site\bin\exiftool.bat

C:\Users\root>exiftool -ver "a<b"
The system cannot find the file specified.

C:\Users\root>C:\Perl\site\bin\exiftool -ver "a<b"
12.76
Error: File not found - a<b

You mentioned this is very strange. Do you mean that you cannot reproduce this problem?

Ahhh I see what’s happening! So the fix we had done for state exec was to replace our underlying mechanic of using batch scripts to instead use powershell scripts. The reason for this is that batch scripts have some HARD limitations with regards to how arguments are passed, and they straight up could not facilitate the use-case you had encountered.

Now the executables in question that you are experiencing issues with are ALL batch scripts. Our executors are essentially turning your batch script into an executable that sets up the environment, and then pass the arguments along to the batch script.

I haven’t dug deeper yet, but my guess is running batch scripts through a command prompt gets around the argument passing problem, (maybe command prompt passes the quotes along to the batch script? Not sure yet).

I’m going to schedule some work for us to dig deeper here. I’m not confident that we will be able to address batch scripts using the method we have now without hard limitations, so we have some digging and discussing to do. I’ll keep you updated.

For now a simple workaround would be to always run these commands with the .bat suffix. Then you’re bypassing the executor, and you just need to make sure you run it from within an environment that’s already set up (eg. use state shell or state use). So instead of exiftool -ver "a<b" you’d run exiftool.bat -ver "a<b".

Apologies again for the inconvenience. Thanks so much for sticking with me!

Hi Nathan,

Thanks for the extra information. I helps.

Unfortunately, your workaround doesn’t meet my general needs. Just one example; I invoke exiftool from other programs or scripts, so changing its name or providing an explicit path isn’t portable, or not even possible if the invoking program is a binary that I cannot modify.

I strongly suspect the problem is this:

  1. state-exec, as a surrogate for the batch script, is invoked by the command-line shell and receives its arguments already interpreted by this command-line shell. The quotes and (unquoted) metacharacters are handled there.
  2. state-exec then calls the operating system to run its matching binary (exec.Command(meta.MatchingBin, userArgs…)). It turns out that this matching binary is a batch script, so the operating system invokes a sub-shell to run this batch script. But by now the quotes are gone, and the sub-shell invoked by state-exec interprets the arguments again, and it balks at the sight of things like a<b. The batch script doesn’t even get a chance to start.

I would try the following:

  1. In state-exec/cmd.go, add code to print the userArgs on stdout. This will tell you if they have been interpreted/modified by the shell. I suspect that any quoted argument is received untouched.
  2. Add code to one batch script (say, shasum.bat), to print “Hello world” at the very beginning, observe that the batch script isn’t invoked at all if “a<b” is given as a command-line argument when going through state-exec.

Some possible solutions:

  1. In state-exec, individually quote each argument to exec.Command(). Because arguments have already been parsed by the command-line shell, they shouldn’t be parsed again. If you need to do this, the correct way is to craft an explicit call to a shell, something like system('bash shasum "a<b"'). I’m not sure if the quoting is specific to the type of program invoked (cmd and PowerShell use different quotes).
  2. In state-exec, use a command other than exec.Command() to run the batch script, one that wouldn’t parse the arguments before invoking the ultimate program/script.
  3. In state-exec, insert a stop-parsing token (e.g. --% for PowerShell, don’t know of anything similar for cmd.exe) before the userArgs list, to prevent parsing the arguments. This would be applied only for commands that we know are batch scripts (i.e. .bat files), which may be complicated to implement reliably.
  4. Don’t use state-exec. It’s certainly an option for those like me who have only one runtime. This may be as simple as removing C:\Perl\exec from PATH, or moving it at the end.

If any solution works, test it on all use cases: .pl, .exe, .bat, .ps1, .sh, no-extension, whatever.

Regards,

Martin

Thanks @gamin-mb, yeah at this point we haven’t fully diagnosed the issue yet, so you could be right that I’m off. cmd.exe is a special beast; on any other shell it’s literally impossible to see the quotes you passed, because the shell handles those. But cmd.exe and batch scripts might have a special relationship I’m not aware of.

Thanks for the ideas, we’ll be debugging!