Мне нужно вызвать исполняемый файл из наложенного контекста скрипта PHP. Как производительность, так и безопасность мудрее, лучше не вызывать оболочку между процессом веб-сервера и исполняемым файлом.
Конечно, я искал в Интернете без успеха (в таком контексте PHP). Многие другие языки позволяют это и четко документировать.
Увы, backticks, exec()
, shell_exec()
, passthru()
, system()
, proc_open()
, popen()
вызывают оболочку. И pcntl_fork()
кажется недоступным.
Это было протестировано на 64-битном Debian с PHP 5.3.3-7 + squeeze15. Тестовый код на http://pastebin.com/y4C7MeJz
Чтобы получить осмысленный тест, я использовал трюк, который должен попросить выполнить команду оболочки, также недоступную в качестве исполняемого файла. Хорошим примером является umask
. Любая функция, возвращающая что-то вроде 0022, определенно называется оболочкой. exec()
, shell_exec()
, passthru()
, system()
, proc_open()
все сделали. Подробные результаты см. На странице http://pastebin.com/RBcBz02F .
Теперь вернемся к цели: как выполнить произвольную программу без запуска оболочки?
Исполнение php принимает как ожидалось массив строковых args вместо уникальной строки. Но pcntl_fork просто останавливает запросы без даже журнала.
Изменить: сбой pcntl_fork заключается в том, что сервер использует mod_php Apache, см. Http://www.php.net/manual/en/function.pcntl-fork.php#49949 .
Изменить: добавлено popen()
к тестам, после предложения @hakre.
Любой намек оценили.
Чтобы ответить на ваше предложение:
Как производительность, так и безопасность мудрее, лучше не вызывать оболочку между процессом веб-сервера и исполняемым файлом.
О спектаклях, ну да, php внутренних вилок, и сама оболочка вилки тоже так немного тяжело. Но вам действительно нужно выполнить много процессов, чтобы рассмотреть эти проблемы с исполнением.
О безопасности я здесь не вижу. PHP имеет функцию escapeshellarg для дезинфекции аргументов.
Единственная реальная проблема, с которой я столкнулся с exec
без pcntl, – это не проблема ресурса и безопасности: создавать настоящие деамоны (без привязанности к ее родителям, особенно Apache ) действительно сложно. Я решил это, используя, после двойной сбегающей моей команды:
$arg1 = escapeshellarg($arg1); $arg2 = escapeshellarg($arg2); $command = escapeshellarg("/some/bin $arg1 $arg2 > /dev/null 2>&1 &"); exec("$command | at now -M");
Чтобы вернуться к вашему вопросу, единственный способ реализовать программы в стандартном (fork + exec) пути – использовать расширение PCNTL (как уже упоминалось). Во всяком случае, удачи!
Чтобы завершить мой ответ, вы можете самостоятельно создать функцию exec
которая делает то же самое, что и pcntl_fork
+ pcntl_exec
.
Я сделал расширение my_exec
которое делает классическую конструкцию exec +, но на самом деле я не думаю, что это решит ваши проблемы, если вы используете эту функцию под apache , потому что будет применяться то же поведение, что и pcntl_fork
(apache2 будет разветвлен и там может быть неожиданным поведением с execv
сигнала и т. д., когда execv
не удался).
config.m4 phpize
конфигурации phpize
PHP_ARG_ENABLE(my_exec_extension, whether to enable my extension, [ --enable-my-extension Enable my extension]) if test "$PHP_MY_EXEC_EXTENSION" = "yes"; then AC_DEFINE(HAVE_MY_EXEC_EXTENSION, 1, [Whether you have my extension]) PHP_NEW_EXTENSION(my_exec_extension, my_exec_extension.c, $ext_shared) fi
my_exec_extension.c расширение
#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #define PHP_MY_EXEC_EXTENSION_VERSION "1.0" #define PHP_MY_EXEC_EXTENSION_EXTNAME "my_exec_extension" extern zend_module_entry my_exec_extension_module_entry; #define phpext_my_exec_extension_ptr &my_exec_extension_module_entry // declaration of a custom my_exec() PHP_FUNCTION(my_exec); // list of custom PHP functions provided by this extension // set {NULL, NULL, NULL} as the last record to mark the end of list static function_entry my_functions[] = { PHP_FE(my_exec, NULL) {NULL, NULL, NULL} }; // the following code creates an entry for the module and registers it with Zend. zend_module_entry my_exec_extension_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_MY_EXEC_EXTENSION_EXTNAME, my_functions, NULL, // name of the MINIT function or NULL if not applicable NULL, // name of the MSHUTDOWN function or NULL if not applicable NULL, // name of the RINIT function or NULL if not applicable NULL, // name of the RSHUTDOWN function or NULL if not applicable NULL, // name of the MINFO function or NULL if not applicable #if ZEND_MODULE_API_NO >= 20010901 PHP_MY_EXEC_EXTENSION_VERSION, #endif STANDARD_MODULE_PROPERTIES }; ZEND_GET_MODULE(my_exec_extension) char *concat(char *old, char *buf, int buf_len) { int str_size = strlen(old) + buf_len; char *str = malloc((str_size + 1) * sizeof(char)); snprintf(str, str_size, "%s%s", old, buf); str[str_size] = '\0'; free(old); return str; } char *exec_and_return(char *command, char **argv) { int link[2], readlen; pid_t pid; char buffer[4096]; char *output; output = strdup(""); if (pipe(link) < 0) { return strdup("Could not pipe!"); } if ((pid = fork()) < 0) { return strdup("Could not fork!"); } if (pid == 0) { dup2(link[1], STDOUT_FILENO); close(link[0]); if (execv(command, argv) < 0) { printf("Command not found or access denied: %s\n", command); exit(1); } } else { close(link[1]); while ((readlen = read(link[0], buffer, sizeof(buffer))) > 0) { output = concat(output, buffer, readlen); } wait(NULL); } return output; } PHP_FUNCTION(my_exec) { char *command; int command_len, argc, i; zval *arguments, **data; HashTable *arr_hash; HashPosition pointer; char **argv; // recovers a string (s) and an array (a) from arguments if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &command, &command_len, &arguments) == FAILURE) { RETURN_NULL(); } arr_hash = Z_ARRVAL_P(arguments); // creating argc and argv from our argument array argc = zend_hash_num_elements(arr_hash); argv = malloc((argc + 1) * sizeof(char *)); argv[argc] = NULL; for ( i = 0, zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS; zend_hash_move_forward_ex(arr_hash, &pointer) ) { if (Z_TYPE_PP(data) == IS_STRING) { argv[i] = malloc((Z_STRLEN_PP(data) + 1) * sizeof(char)); argv[i][Z_STRLEN_PP(data)] = '\0'; strncpy(argv[i], Z_STRVAL_PP(data), Z_STRLEN_PP(data)); i++; } } char *output = exec_and_return(command, argv); // freeing allocated memory for (i = 0; (i < argc); i++) { free(argv[i]); } free(argv); // WARNING! I guess there is a memory leak here. // Second arguemnt to 1 means to PHP: do not free memory // But if I put 0, I get a segmentation fault // So I think I do not malloc correctly for a PHP extension. RETURN_STRING(output, 1); }
test.php пример использования
<?php dl("my_exec.so"); $output = my_exec("/bin/ls", array("-l", "/")); var_dump($output);
shell-скрипт запускает эти команды, конечно же, используя свой собственный каталог модулей
phpize ./configure make sudo cp modules/my_exec_extension.so /opt/local/lib/php/extensions/no-debug-non-zts-20090626/my_exec.so
результат
KolyMac:my_fork ninsuo$ php test.php string(329) ".DS_Store .Spotlight-V100 .Trashes .file .fseventsd .hidden .hotfiles.btree .vol AppleScript Applications Developer Installer Log File Library Microsoft Excel Documents Microsoft Word Documents Network System Users Volumes bin cores dev etc home lost+found mach_kernel net opt private sbin tmp usr var vc_command.txt vidotask.txt"
Я не C dev, поэтому я думаю, что для этого есть более чистые способы. Но ты получил идею.
Id рассмотрим попытку pcntl_exec ()
Вы не можете запускать какую-либо произвольную программу без оболочки – оболочка является оберткой вокруг исполняющей программы, которая предоставляет свои настройки среды, такие как PWD и PATH.
Если вам нужна возможность запускать системные команды без запуска оболочки, перезапишите системные команды в виде модулей PHP и установите их на свой PHP-сервер и запустите их как функции.