Skip to content

Commit d1bb029

Browse files
authored
Merge pull request #1782 from static-php/cli_sapi
suggestions for php_cli PR
2 parents 8045011 + e335244 commit d1bb029

File tree

7 files changed

+198
-35
lines changed

7 files changed

+198
-35
lines changed

caddy/php-cli.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package caddy
22

33
import (
4-
"errors"
54
"os"
65
"path/filepath"
6+
"strings"
77

88
caddycmd "github.com/caddyserver/caddy/v2/cmd"
99
"github.com/dunglas/frankenphp"
@@ -26,23 +26,16 @@ Executes a PHP script similarly to the CLI SAPI.`,
2626
}
2727

2828
func cmdPHPCLI(fs caddycmd.Flags) (int, error) {
29-
args := os.Args[2:]
30-
if len(args) < 1 {
31-
return 1, errors.New("the path to the PHP script is required")
32-
}
29+
// php's cli sapi expects the 0th arg to be the program itself, only filter out 'php-cli' arg
30+
args := append([]string{os.Args[0]}, os.Args[2:]...)
3331

34-
if frankenphp.EmbeddedAppPath != "" {
35-
if _, err := os.Stat(args[0]); err != nil {
36-
args[0] = filepath.Join(frankenphp.EmbeddedAppPath, args[0])
32+
if frankenphp.EmbeddedAppPath != "" && len(args) > 1 && !strings.HasPrefix(args[1], "-") && strings.HasSuffix(args[1], ".php") {
33+
if _, err := os.Stat(args[1]); err != nil {
34+
args[1] = filepath.Join(frankenphp.EmbeddedAppPath, args[1])
3735
}
3836
}
3937

40-
var status int
41-
if len(args) >= 2 && args[0] == "-r" {
42-
status = frankenphp.ExecutePHPCode(args[1])
43-
} else {
44-
status = frankenphp.ExecuteScriptCLI(args[0], args)
45-
}
38+
status := frankenphp.ExecuteScriptCLI(args)
4639

4740
os.Exit(status)
4841

emulate_php_cli.c

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#include <SAPI.h>
2+
#include <Zend/zend_alloc.h>
3+
#include <Zend/zend_exceptions.h>
4+
#include <Zend/zend_interfaces.h>
5+
#include <Zend/zend_types.h>
6+
#include <errno.h>
7+
#include <ext/spl/spl_exceptions.h>
8+
#include <ext/standard/head.h>
9+
#include <inttypes.h>
10+
#include <php.h>
11+
#include <php_config.h>
12+
#include <php_ini.h>
13+
#include <php_main.h>
14+
#include <php_output.h>
15+
#include <php_variables.h>
16+
#include <php_version.h>
17+
#include <pthread.h>
18+
#include <sapi/embed/php_embed.h>
19+
#include <signal.h>
20+
#include <stdint.h>
21+
#include <stdio.h>
22+
#include <stdlib.h>
23+
#include <unistd.h>
24+
#if defined(__linux__)
25+
#include <sys/prctl.h>
26+
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
27+
#include <pthread_np.h>
28+
#endif
29+
30+
typedef struct {
31+
char *script;
32+
int argc;
33+
char **argv;
34+
bool eval;
35+
} cli_exec_args_t;
36+
cli_exec_args_t *cli_args;
37+
38+
/* Function declaration to avoid implicit declaration error */
39+
void register_server_variable_filtered(const char *key, char **val, size_t *val_len, zval *track_vars_array);
40+
41+
/*
42+
* CLI code is adapted from
43+
* https://github.com/php/php-src/blob/master/sapi/cli/php_cli.c Copyright (c)
44+
* The PHP Group Licensed under The PHP License Original uthors: Edin Kadribasic
45+
* <edink@php.net>, Marcus Boerger <helly@php.net> and Johannes Schlueter
46+
* <johannes@php.net> Parts based on CGI SAPI Module by Rasmus Lerdorf, Stig
47+
* Bakken and Zeev Suraski
48+
*/
49+
static void cli_register_file_handles(bool no_close) /* {{{ */
50+
{
51+
php_stream *s_in, *s_out, *s_err;
52+
php_stream_context *sc_in = NULL, *sc_out = NULL, *sc_err = NULL;
53+
zend_constant ic, oc, ec;
54+
55+
s_in = php_stream_open_wrapper_ex("php://stdin", "rb", 0, NULL, sc_in);
56+
s_out = php_stream_open_wrapper_ex("php://stdout", "wb", 0, NULL, sc_out);
57+
s_err = php_stream_open_wrapper_ex("php://stderr", "wb", 0, NULL, sc_err);
58+
59+
if (s_in == NULL || s_out == NULL || s_err == NULL) {
60+
if (s_in)
61+
php_stream_close(s_in);
62+
if (s_out)
63+
php_stream_close(s_out);
64+
if (s_err)
65+
php_stream_close(s_err);
66+
return;
67+
}
68+
69+
if (no_close) {
70+
s_in->flags |= PHP_STREAM_FLAG_NO_CLOSE;
71+
s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE;
72+
s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE;
73+
}
74+
75+
/*s_in_process = s_in;*/
76+
77+
php_stream_to_zval(s_in, &ic.value);
78+
php_stream_to_zval(s_out, &oc.value);
79+
php_stream_to_zval(s_err, &ec.value);
80+
81+
ZEND_CONSTANT_SET_FLAGS(&ic, CONST_CS, 0);
82+
ic.name = zend_string_init_interned("STDIN", sizeof("STDIN") - 1, 0);
83+
zend_register_constant(&ic);
84+
85+
ZEND_CONSTANT_SET_FLAGS(&oc, CONST_CS, 0);
86+
oc.name = zend_string_init_interned("STDOUT", sizeof("STDOUT") - 1, 0);
87+
zend_register_constant(&oc);
88+
89+
ZEND_CONSTANT_SET_FLAGS(&ec, CONST_CS, 0);
90+
ec.name = zend_string_init_interned("STDERR", sizeof("STDERR") - 1, 0);
91+
zend_register_constant(&ec);
92+
}
93+
/* }}} */
94+
95+
static void sapi_cli_register_variables(zval *track_vars_array) /* {{{ */
96+
{
97+
size_t len = strlen(cli_args->script);
98+
char *docroot = "";
99+
100+
/*
101+
* In CGI mode, we consider the environment to be a part of the server
102+
* variables
103+
*/
104+
php_import_environment_variables(track_vars_array);
105+
106+
/* Build the special-case PHP_SELF variable for the CLI version */
107+
register_server_variable_filtered("PHP_SELF", &cli_args->script, &len,
108+
track_vars_array);
109+
register_server_variable_filtered("SCRIPT_NAME", &cli_args->script, &len,
110+
track_vars_array);
111+
112+
/* filenames are empty for stdin */
113+
register_server_variable_filtered("SCRIPT_FILENAME", &cli_args->script, &len,
114+
track_vars_array);
115+
register_server_variable_filtered("PATH_TRANSLATED", &cli_args->script, &len,
116+
track_vars_array);
117+
118+
/* just make it available */
119+
len = 0U;
120+
register_server_variable_filtered("DOCUMENT_ROOT", &docroot, &len,
121+
track_vars_array);
122+
}
123+
/* }}} */
124+
125+
void *emulate_script_cli(void *arg) {
126+
void *exit_status;
127+
cli_exec_args_t* args = arg;
128+
cli_args = args;
129+
bool eval = args->eval;
130+
131+
/*
132+
* The SAPI name "cli" is hardcoded into too many programs... let's usurp it.
133+
*/
134+
php_embed_module.name = "cli";
135+
php_embed_module.pretty_name = "PHP CLI embedded in FrankenPHP";
136+
php_embed_module.register_server_variables = sapi_cli_register_variables;
137+
138+
php_embed_init(cli_args->argc, cli_args->argv);
139+
140+
cli_register_file_handles(false);
141+
zend_first_try {
142+
if (eval) {
143+
/* evaluate the cli_args->script as literal PHP code (php-cli -r "...") */
144+
zend_eval_string_ex(cli_args->script, NULL, "Command line code", 1);
145+
} else {
146+
zend_file_handle file_handle;
147+
zend_stream_init_filename(&file_handle, cli_args->script);
148+
149+
CG(skip_shebang) = 1;
150+
php_execute_script(&file_handle);
151+
}
152+
}
153+
zend_end_try();
154+
155+
exit_status = (void *)(intptr_t)EG(exit_status);
156+
157+
php_embed_shutdown();
158+
159+
return exit_status;
160+
}

emulate_php_cli.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
void *emulate_script_cli(void *arg);

frankenphp.c

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <php_main.h>
1414
#include <php_output.h>
1515
#include <php_variables.h>
16+
#include <php_version.h>
1617
#include <pthread.h>
1718
#include <sapi/embed/php_embed.h>
1819
#include <signal.h>
@@ -25,7 +26,12 @@
2526
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
2627
#include <pthread_np.h>
2728
#endif
29+
30+
#if PHP_VERSION_ID >= 80500
2831
#include <sapi/cli/cli.h>
32+
#else
33+
#include "emulate_php_cli.h"
34+
#endif
2935

3036
#include "_cgo_export.h"
3137
#include "frankenphp_arginfo.h"
@@ -749,7 +755,7 @@ void frankenphp_register_variable_safe(char *key, char *val, size_t val_len,
749755
}
750756
}
751757

752-
static inline void register_server_variable_filtered(const char *key,
758+
void register_server_variable_filtered(const char *key,
753759
char **val,
754760
size_t *val_len,
755761
zval *track_vars_array) {
@@ -1016,13 +1022,23 @@ int frankenphp_execute_script(char *file_name) {
10161022
return status;
10171023
}
10181024

1019-
/* Use global variables to store CLI arguments to prevent useless allocations */
1020-
static char *cli_script;
1021-
static int cli_argc;
1022-
static char **cli_argv;
1025+
typedef struct {
1026+
char *script;
1027+
int argc;
1028+
char **argv;
1029+
bool eval;
1030+
} cli_exec_args_t;
10231031

10241032
static void *execute_script_cli(void *arg) {
1025-
return (void *)(intptr_t)do_php_cli(cli_argc, cli_argv);
1033+
cli_exec_args_t *args = (cli_exec_args_t *)arg;
1034+
volatile int v = PHP_VERSION_ID;
1035+
(void)v;
1036+
1037+
#if PHP_VERSION_ID >= 80500
1038+
return (void *)(intptr_t)do_php_cli(args->argc, args->argv);
1039+
#else
1040+
return (void *)(intptr_t)emulate_script_cli(args);
1041+
#endif
10261042
}
10271043

10281044
int frankenphp_execute_script_cli(char *script, int argc, char **argv,
@@ -1031,15 +1047,15 @@ int frankenphp_execute_script_cli(char *script, int argc, char **argv,
10311047
int err;
10321048
void *exit_status;
10331049

1034-
cli_script = script;
1035-
cli_argc = argc;
1036-
cli_argv = argv;
1050+
cli_exec_args_t args = {
1051+
.script = script, .argc = argc, .argv = argv, .eval = eval
1052+
};
10371053

10381054
/*
10391055
* Start the script in a dedicated thread to prevent conflicts between Go and
10401056
* PHP signal handlers
10411057
*/
1042-
err = pthread_create(&thread, NULL, execute_script_cli, (void *)eval);
1058+
err = pthread_create(&thread, NULL, execute_script_cli, &args);
10431059
if (err != 0) {
10441060
return err;
10451061
}

frankenphp.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -634,14 +634,11 @@ func go_is_context_done(threadIndex C.uintptr_t) C.bool {
634634

635635
// ExecuteScriptCLI executes the PHP script passed as parameter.
636636
// It returns the exit status code of the script.
637-
func ExecuteScriptCLI(script string, args []string) int {
638-
cScript := C.CString(script)
639-
defer C.free(unsafe.Pointer(cScript))
640-
637+
func ExecuteScriptCLI(args []string) int {
641638
argc, argv := convertArgs(args)
642639
defer freeArgs(argv)
643640

644-
return int(C.frankenphp_execute_script_cli(cScript, argc, (**C.char)(unsafe.Pointer(&argv[0])), false))
641+
return int(C.frankenphp_execute_script_cli(nil, argc, (**C.char)(unsafe.Pointer(&argv[0])), false))
645642
}
646643

647644
func ExecutePHPCode(phpCode string) int {

frankenphp_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -834,7 +834,7 @@ func ExampleExecuteScriptCLI() {
834834
os.Exit(1)
835835
}
836836

837-
os.Exit(frankenphp.ExecuteScriptCLI(os.Args[1], os.Args))
837+
os.Exit(frankenphp.ExecuteScriptCLI(os.Args))
838838
}
839839

840840
func BenchmarkHelloWorld(b *testing.B) {

internal/testcli/main.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,5 @@ func main() {
1313
os.Exit(1)
1414
}
1515

16-
if len(os.Args) == 3 && os.Args[1] == "-r" {
17-
os.Exit(frankenphp.ExecutePHPCode(os.Args[2]))
18-
}
19-
20-
os.Exit(frankenphp.ExecuteScriptCLI(os.Args[1], os.Args))
16+
os.Exit(frankenphp.ExecuteScriptCLI(os.Args))
2117
}

0 commit comments

Comments
 (0)