-
Notifications
You must be signed in to change notification settings - Fork 274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PHP : Dispatch available descriptor specs in js_open_process function #963
PHP : Dispatch available descriptor specs in js_open_process function #963
Conversation
|
||
expect(result.text).toEqual('stdout: WordPress\n'); | ||
}); | ||
sleep(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good spot, I just ran into the same thing, this test passes randomly if cat > out
finishes before this point.
proc_close($res); | ||
`, | ||
|
||
it('Uses only stdin and stdout descriptor specs', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Thank you for adding test coverage
stderr_pipe[0], | ||
stderr_pipe[1] | ||
cmd, | ||
descv, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! TIL you can pass arrays like this.
Edit: A-ha, you still decode that on the JS side by looping over the memory. Gotcha!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about this more, this seems to add quite a few lines of code and some indirection that needs to be mentally processed when reading the code. What do you think about preserving the long js_open_process
signature and, whenever a descriptor is missing, passing NULLs directly as function args?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! What is passed is the array pointer in C, and I had to loop over the memory to find all the data in JS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the php method, you can add more or less than 3 descriptors. If you want to have the same behavior, this will need a dynamic array. Keeping the old way will prevent adding new descriptors, but you're the boss. :).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A-ha! I missed the fact PHP actually uses those additional descriptors. Indeed this is what the documentation says:
The file descriptor numbers are not limited to 0, 1 and 2 - you may specify any valid file descriptor number and it will be passed to the child process. This allows your script to interoperate with other scripts that run as "co-processes". In particular, this is useful for passing passphrases to programs like PGP, GPG and openssl in a more secure manner. It is also useful for reading status information provided by those programs on auxiliary file descriptors.
This is really a good spot, thank you! Let's use the array, then. I just checked, and this can actually be handled by Playground as Node.js child_process.spawn()
supports arbitrary file descriptors via the stdio
option.
@@ -90,8 +91,10 @@ EMSCRIPTEN_KEEPALIVE FILE *wasm_popen(const char *cmd, const char *mode) | |||
int current_procopen_call_id = ++procopen_call_id; | |||
char *device_path = js_create_input_device(current_procopen_call_id); | |||
int stdin_childend = current_procopen_call_id; | |||
int stdin_parentend = open(device_path, O_WRONLY); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting! What does this line change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually add a child and parent in every descriptors found. No matter which one it is. So I had to find a parent end for the stdin in wasm_popen to use the same parameters for js_open_process
. And when I analyzed the proc_open7.4.c
file I found out that for the stdin
parentend
it was that. You can find this in proc_open7.4.c
line 489. I copy pasted. I dont think the parentend is used in the js_open_process
but it allows the function to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha! It seems like leaning on the multi-arg signature would remove the need for adding this line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, could the open()
call be avoided by assigning NULL instead of stdin_parentend
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep ! I tried it right now and it works without that unused stdin_parentend
. I will remove that part.
@@ -270,6 +260,20 @@ const LibraryExample = { | |||
return 0; | |||
} | |||
|
|||
if (descriptorsLength < 2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use a comment to explain why short-circuit in this case. Is this what native PHP does? Or is this a limitation of this implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh no, I added this condition because I thought it had no sense to add only one descriptor. Sorry, subjective thought. What I know too is that when you add only one element in a dynamic array, the array has to add 16
to find the first element in memory. Let me give you an explanation about this : [ elements added in array -> pointer offset ] :
1 -> 16
2 -> 16
3 -> 16
4 -> 24
5 -> 24
6 -> 32
7 -> 32
8 -> 48
9 -> 48
...
This means you can understand how to find the correct data in the memory even if it is dynamic. But there is a minimum of 16. And that if( descriptorsLength < 2 )
allows to avoid 'if one element then + 16 instead of 8.
I hope I explained this well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh no, I added this condition because I thought it had no sense to add only one descriptor. Sorry, subjective thought.
All good! I just checked and it seems like you can even pass zero descriptors and proc_open
will still work:
$pipes = []; proc_open('echo "test123" > out', [], $pipes);
This is awesome @mho22, thank you for working on this! I left a few notes, mostly about the code style. It seems like preserving the multi-arg signature would make this PR much shorter. Otherwise, I think this PR makes a great addition to the codebase! A quick note – I'd like to merge #957 today. It fixes some more |
Hi @adamziel, I answered to your comments the best I could. Sorry if it is not clear. IMO, I think keeping the dynamic descriptors array would enable people to add other descriptors, for the future. But you decide, of course. I actually also use a dynamic array in the Don't hesitate to merge #957 and I will try to correct the merge conflicts in the two PRs. |
@mho22 Yeah I think you're right about using an array of descriptors as I mentioned in one of the discussion threads 👍 |
@adamziel I encounter some problems with the new
I use it to test custom php files outside the main repository. here is the error :
It appears when Am I doing something wrong now ? The process has changed with #957 ? |
@mho22 the process should be the same. This looks either like a mismatch between a php.js and php.wasm OR like a build issue. How can I reproduce that? Also, how are you running the command? Sometimes it helps to run |
@adamziel Sorry, I made a new pull request and I thought you commented on the new one. I will investigate the problem later. |
Ah, all good, let's just continue in the new one. Should this one be closed, then? |
@adamziel If it is ok for you, we should close this one and the older one too. |
…969) Based on the issue #930 ### Support for passing an array of strings as a `$command` when calling `proc_open` Without this PR, this `proc_open` call doesn't work: ``` $process = proc_open( [ 'ls', '-a' ], $descriptorspec, $pipes ); ``` With this PR, it does and the `php.setSpawnHandler( )` callback receives all the information from the `[ 'ls', '-a' ]` array. ### Support for an arbitrary number of file descriptors in `proc_open` Without this PR, this `proc_open` call will error out because, internally, PHP-WASM always expects three file descriptors: ``` $process = proc_open( 'stty', [ 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] ], $pipes ); ``` This PR removes that limitation so that `proc_open()` can work when 0 or more file descriptors are passed ## Testing Instructions ### Support for passing an array of strings as a `$command` when calling `proc_open` `proc.php` ``` $process = proc_open( [ 'ls', '-a' ], [ 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] ], $pipes ); ``` `cli.js` ``` import { NodePHP } from '@php-wasm/node'; const php = await NodePHP.load( '8.2' ); php.setSpawnHandler( (cmd, args) => console.log( cmd, args ) ); php.cli( [ 'php', ...process.argv.slice( 2 ) ] ).finally( () => process.exit( 0 ) ); ``` running : `node cli.js proc.php` returns : ``` ls [ '-a' ] Node.js v20.11.0 ``` ### Support for an arbitrary number of file descriptors in `proc_open` `proc.php` ``` <?php $command = 'ls -a'; $descriptorspec = [ 0 => [ 'pipe', 'r' ], 1 => [ 'pipe', 'w' ] ]; $process = proc_open( $command, $descriptorspec, $pipes ); var_dump( stream_get_contents( $pipes[ 1 ] ) ); ``` `cli.js` ``` import { NodePHP } from '@php-wasm/node'; import { spawn } from 'child_process'; const php = await NodePHP.load( '8.2' ); php.setSpawnHandler( (command) => spawn( command, [], { shell : true, stdio : [ 'pipe', 'pipe', 'pipe' ], timeout : undefined } ) ); php.cli( [ 'php', ...process.argv.slice( 2 ) ] ).finally( () => process.exit( 0 ) ); ``` running : `node cli.js proc.php` returns : ``` string(61) "cli.js node_modules package-lock.json package.json proc.php " ``` ___ Cfr : - [PHP : Dispatch available descriptor specs in js_open_process function](#963) - [PHP : Give access to command arguments if array type is given in php ^7.4 proc_open function](#944) --------- Co-authored-by: Adam Zieliński <[email protected]>
What is this PR doing?
Based on the issue #930
For now, if you have this PHP code :
$process = proc_open( 'stty', [ 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] ], $pipes );
You get an error because of the lack of
0
descriptor in$descriptorspec
parameter.This PR is giving the opportunity to
proc_open()
users to add more, less or exactly 3 descriptors.What problem is it solving?
It solves the problem of inevitably adding 3 descriptors, even if only 2 sometimes are enough.
Testing Instructions
proc.php
cli.js
node cli.js proc.php
returns [ in any case with more than 1 descriptor ] :
Next steps if validated :
@adamziel New in
php.spec.ts
:'popen("cat", "w")'
test works now.'popen("cat", "w")'
and'echo – stdin=file (empty), stdout=pipe, stderr=pipe'
seem to still needsleep(1)
while it is not needed when usingphp
.