Skip to content
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

Option to store output of php_execute_script #9330

Closed
ghost opened this issue Aug 13, 2022 · 7 comments
Closed

Option to store output of php_execute_script #9330

ghost opened this issue Aug 13, 2022 · 7 comments

Comments

@ghost
Copy link

ghost commented Aug 13, 2022

Description

I'm embedding PHP and I need to store the output of php_execute_script using the API. Can we have this feature?

@ghost ghost changed the title Option to store return value of php_execute_script Option to store output of php_execute_script Aug 13, 2022
@KapitanOczywisty
Copy link

Something like that?

#include <sapi/embed/php_embed.h>

static size_t embed_ub_write(const char *str, size_t str_length){
	printf("Data (%lu bytes): [%s]\n", str_length, str);
	return str_length;
}

int main(int argc, char **argv)
{
	php_embed_module.ub_write = embed_ub_write;

	PHP_EMBED_START_BLOCK(argc, argv)

	zend_file_handle file_handle;
	zend_stream_init_filename(&file_handle, "example.php");

	if (php_execute_script(&file_handle) == FAILURE) {
		php_printf("Failed to execute PHP script.\n");
	}

	PHP_EMBED_END_BLOCK()
}
<?php
echo 'Hello world!' . PHP_EOL;
Data (13 bytes): [Hello world!
]

@ghost
Copy link
Author

ghost commented Aug 14, 2022

Well, sort of...

#include <sapi/embed/php_embed.h>

char *string[999] = {NULL};
int i = 0;

static size_t embed_ub_write(const char *str, size_t str_length){
	string[i] = (char *) str; i++;
	return str_length;
}

int main(int argc, char **argv)
{
	php_embed_module.ub_write = embed_ub_write;

	PHP_EMBED_START_BLOCK(argc, argv)

	zend_file_handle file_handle;
	zend_stream_init_filename(&file_handle, "example.php");

	if (php_execute_script(&file_handle) == FAILURE) {
		php_printf("Failed to execute PHP script.\n");
	}
	for (i = 0; string[i] != NULL; i++) {
		printf("%s", string[i]);
	}

	PHP_EMBED_END_BLOCK()
}

Where did you learn that, anyways? Is there some documentation or something I can't find or...
Seriously, is the PHP SAPI (or whatever you call it) not documented or it's just me being blind and not finding it?
And why won't a stdout buffer redirect work (I tried it)?

Does that mean this feature already exists?

@KapitanOczywisty
Copy link

Embed is poorly documented, you need to look into source, especially sapis folder and tools like grep.app for other implementations.

‘stdout‘ goes directly to filedescriptor, I think there was a way to override said descriptor, but I didn't dive into this.

@KapitanOczywisty
Copy link

KapitanOczywisty commented Aug 14, 2022

Another POC, probably not the cleanest way:

#include <sapi/embed/php_embed.h>
#include <fcntl.h> // O_NONBLOCK

#ifndef STDOUT_FILENO
#define STDOUT_FILENO 1
#endif

int cstdout = STDOUT_FILENO;

// `ub_write` disabled for this example
static size_t embed_ub_write(const char *str, size_t str_length){
	// own stdout to display data
	dprintf(cstdout , "Data (%lu bytes): [%s]\n", str_length, str);
	return str_length;
}

int main(int argc, char **argv)
{
	int pipefd[2];

	// pipe to buffer php stdout
	if (pipe2(pipefd, O_NONBLOCK) == -1) {
		perror("pipe");
		exit(EXIT_FAILURE);
	}

	// copy stdout before overriding
	cstdout = dup(STDOUT_FILENO);

	// override stdout with pipe
	if(dup2(pipefd[1], STDOUT_FILENO) == -1){
		perror("dup2");
		exit(EXIT_FAILURE);
	}

	// // can be skipped if all should go to stdout
	// php_embed_module.ub_write = embed_ub_write;

	PHP_EMBED_START_BLOCK(argc, argv)

	zend_file_handle file_handle;
	zend_stream_init_filename(&file_handle, "example.php");

	if (php_execute_script(&file_handle) == FAILURE) {
		php_printf("Failed to execute PHP script.\n");
	}

	PHP_EMBED_END_BLOCK()

	// restore stdout and close pipe input
	if(dup2(cstdout, STDOUT_FILENO) == -1){
		perror("dup2 restore");
		exit(EXIT_FAILURE);
	}

	printf("Script ended\n");

	char buf[2048];
	int bytes;
	while ((bytes = read(pipefd[0], &buf, 2048)) != -1)
	{
		// strings are *not* null terminated here
		printf( "Stdout (%u bytes): [%.*s]\n", bytes, bytes, (char*)&buf);
	}

	// close pipe output
	close(pipefd[0]);
}
<?php
echo 'Hello world!' . PHP_EOL;
file_put_contents("php://stdout", "Hello stdout!" . PHP_EOL);
Script ended
Stdout (27 bytes): [Hello world!
Hello stdout!
]

If you create pipe with O_DIRECT (if (pipe2(pipefd, O_NONBLOCK | O_DIRECT ) == -1) {; since Linux 3.4), you will get:

Script ended
Stdout (13 bytes): [Hello world!
]
Stdout (14 bytes): [Hello stdout!
]

Pipe has also limit to how much data can take, in my case it was 65536 bytes.

@ghost
Copy link
Author

ghost commented Aug 14, 2022

Oh, I thought it used some sort of other method of output (stupid expectation, really) and that was why it wasn't working.

(For those who didn't understand the code, you need to pipe into stdout as there are two programs playing with it, which are your program and PHP, and then use the buffer swap thingy with pipe). Thanks, Kapitan .

I'll keep this open to see what they have to say about it, perhaps they can make this a built-in feature or something, IDK if it's useful or not though (as I thought this wasn't even possible when I opened this issue).

@KapitanOczywisty
Copy link

I'll keep this open to see what they have to say about it, perhaps they can make this a built-in feature or something

I can't speak for maintainers, but I don't think so, and stdout swap is probably best way to do this anyway. Remember that you can do stdout swap after forking process, and then you can also handle data as they come.

as I thought this wasn't even possible when I opened this issue

As I mentioned documentation is lacking, but I'm not sure if embedding is popular enough to improve it.

@ghost
Copy link
Author

ghost commented Aug 15, 2022

I can't speak for maintainers, but I don't think so, and stdout swap is probably best way to do this anyway. Remember that you can do stdout swap after forking process, and then you can also handle data as they come.

Makes sense. Closing.

As I mentioned documentation is lacking, but I'm not sure if embedding is popular enough to improve it.

Yeah, I thought I was the only one who thought that way.
I might try adding documentation to the API sometime... once I get familiar with it.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants