www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - std.path.shell throws exception with garbage string

reply Andrej Mitrovic <none none.none> writes:
import std.process;

void main()
{
    char[] chBuffer = new char[](256);
    chBuffer[] = '\0';
    chBuffer[0..3] = "dir".dup;
    
    auto result = shell(chBuffer.idup);
}

It does two things:
1. It prints out the result of the shell invocation to stdout. This shouldn't
happen.

2. It throws this:
std.file.FileException std\file.d(295):
5a5785b9a9ef300e292f021170a6bb2e34b80c86bb8decbb6b9b8d3b5e852cd

This sample works:
import std.process;

void main()
{
    string chBuffer = "dir";
    auto result = shell(chBuffer);
}

Here the shell invocation isn't printed to the screen, but stored in result 
like it should be.

The problem is I'm working with the win32 API and I can't use lovely D strings.
I've tried using to!string and .idup on the call to 'shell', I've tried
appending null's to the char[] but nothing seems to help. What am I doing wrong?
Mar 09 2011
next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Found myself a solution. And probably the cause of the issue. shell()
doesn't expect a null-terminated string, but just a string with the
shell command without any newlines or nulls.

So I can do this (importing std.algorithm for until):
    auto command = to!string(chBuffer[].until('\n'));
    auto result = shell(command);

Not too shabby.

Still that error message wasn't of much help. shell() internally
creates a temp file with a random file name, temporarily redirects
stdout to that file to catch the contents of a system() invocation,
and then reads that file with readtext(). Somehow, something errors
out if there's a newline or null in the string, and it all explodes
with a weird error message.
Mar 09 2011
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, March 09, 2011 15:55:07 Andrej Mitrovic wrote:
 import std.process;
 
 void main()
 {
     char[] chBuffer = new char[](256);
     chBuffer[] = '\0';
     chBuffer[0..3] = "dir".dup;
 
     auto result = shell(chBuffer.idup);
 }
 
 It does two things:
 1. It prints out the result of the shell invocation to stdout. This
 shouldn't happen.
 
 2. It throws this:
 std.file.FileException std\file.d(295):
 5a5785b9a9ef300e292f021170a6bb2e34b80c86bb8decbb6b9b8d3b5e852cd
 
 This sample works:
 import std.process;
 
 void main()
 {
     string chBuffer = "dir";
     auto result = shell(chBuffer);
 }
 
 Here the shell invocation isn't printed to the screen, but stored in result
  like it should be.
 
 The problem is I'm working with the win32 API and I can't use lovely D
 strings. I've tried using to!string and .idup on the call to 'shell', I've
 tried appending null's to the char[] but nothing seems to help. What am I
 doing wrong?

How about not feeding any strings with stray '\0' characters in it. shell takes a D string, which is _not_ null terminated. Looking at the implementation for cmd on Windows, it ultimately calls std.c.process.system which _does_ take a null terminated string, but the string that is fed to system has stuff _after_ the string that you give it. That means that feeding it a string with '\0' in it like that is going to give a bad string to system, so it fails. In order to get the output of the command that you run, shell attempts to redirect the output to file, but your nulls screw it up and it doesn't get redirected and the file never gets created. The filename is a randomly generated number - hence the bizarre exception message. If you put '\0' in the middle of a string, D will just treat it as data, whereas functions will cut off at that point and assume that the string ended there. If you're passing a string to a C function, make sure it's null-terminated. If you're passing a string to a D function, do _not_ null-terminate it. You're passing a null-terminated string to a D function. That's just asking for trouble. What you need to do is only pass the string up to - but not including - the null terminator to shell. If you have a char*, then to!string should do the right thing. But if you have a char[], then you either need to slice it right before the null terminator or use its ptr property and use to!string on _that_. - Jonathan M Davis
Mar 09 2011