Pour répondre à votre phrase :
En termes de performance et de sécurité, il est préférable de ne pas appeler de shell du tout entre le processus du serveur web et l'exécutable. entre le processus du serveur web et l'exécutable.
En ce qui concerne les performances, eh bien, oui, les internes de php fourchent, et la coque elle-même fourche aussi, donc c'est un peu lourd. Mais il faut vraiment exécuter un grand nombre de processus pour envisager ces problèmes de performances.
En ce qui concerne la sécurité, je ne vois pas de problème ici. PHP a la escapeshellarg pour assainir les arguments.
Le seul vrai problème que j'ai rencontré exec
sans pcntl n'est pas un problème de ressource ni de sécurité : il est vraiment difficile de créer réel deamons (sans aucun attachement à son parent, en particulier Apache ). J'ai résolu ce problème en utilisant at
après avoir doublement échappé à mon commandement :
$arg1 = escapeshellarg($arg1);
$arg2 = escapeshellarg($arg2);
$command = escapeshellarg("/some/bin $arg1 $arg2 > /dev/null 2>&1 &");
exec("$command | at now -M");
Pour en revenir à votre question, la seule façon que je connaisse d'exécuter des programmes dans une standard (fork+exec) est d'utiliser la fonction PCNTL extension (comme déjà mentionné). En tout cas, bonne chance !
Pour compléter ma réponse, vous pouvez créer un exec
vous-même qui fait la même chose que pcntl_fork
+ pcntl_exec
.
J'ai fait un my_exec
extension qui fait un classique exec+fork, mais en fait, Je ne pense pas que cela résoudra vos problèmes si vous exécutez cette fonction sous apache. car le même comportement que pcntl_fork
s'appliquera (apache2 sera bifurqué et il peut y avoir des comportements inattendus avec la capture de signaux et ainsi de suite quand execv
ne réussit pas).
config.m4 le site phpize
fichier de configuration
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 l'extension
#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 un échantillon d'utilisation
<?php
dl("my_exec.so");
$output = my_exec("/bin/ls", array("-l", "/"));
var_dump($output);
shell script exécuter ces commandes, en utilisant bien sûr votre propre répertoire de modules
phpize
./configure
make
sudo cp modules/my_exec_extension.so /opt/local/lib/php/extensions/no-debug-non-zts-20090626/my_exec.so
Result
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"
Je ne suis pas un développeur C, je pense donc qu'il existe des moyens plus propres d'y parvenir. Mais vous comprenez l'idée.
4 votes
La première fois que j'ai découvert que PHP ne semblait offrir aucun mécanisme pour appeler un programme externe sans passer par une ligne de commande via un shell, j'ai été alarmé et étonné. Un langage de programmation polyvalent et populaire, qui dispose de commandes telles que
exec()
ysystem()
a ce défaut et qu'il n'a jamais été corrigé ? VRAIMENT ? Mais... Je suppose que c'est vrai.8 votes
@celada : vous devez réaliser que PHP n'est qu'une enveloppe autour d'un grand nombre de fonctions standard glib/libc. PHP lui-même ne peut rien faire que glib/libc ne fassent eux-mêmes, et l'une de ces fonctions est l'exécution d'un autre processus via un shell.
0 votes
L'umask de PHP ne fonctionnerait-il pas ? us2.php.net/umask
0 votes
Je suppose que je ne sais pas comment un programme CGI pourrait exécuter l'appel autrement que par le shell. Si les méthodes de l'interpréteur de commandes ne conviennent pas, peut-être qu'un démon et une file d'attente de messages sont votre solution.
pcntl_fork
Je pense que ce que je ne suis pas sûr s'applique.1 votes
@MarcB ce que vous dites n'a pas de sens. libc peut très certainement exécuter des processus externes à la fois via un shell (par ex.
system()
,popen()
) et directement (execv()
etc...). Seul ce dernier est en général un moyen sûr d'exécuter des processus externes si des données non fiables sont introduites dans la ligne de commande.0 votes
Les fonctions pcntl_* sont uniquement sont disponibles sur la version CLI de PHP, pas sur la version CGI, donc ils mourront sûrement si vous les appelez depuis un serveur web exécutant PHP.
0 votes
En fait, pcntl_fork va seulement forker votre processus, et jamais appeler "un autre programme", donc si le programme que vous voulez lancer n'est pas un truc PHP, vous n'avez pas d'autre choix que d'ouvrir un shell pour l'exécuter. BTW, le shell est appelé sous le MEME utilisateur que celui qui exécute le script de PHP, je ne vois aucune sécurité dans ceci puisque le PHP lui-même peut faire n'importe quoi sur le système de fichiers par le langage sous ce même utilisateur.
6 votes
Le modèle vieux de plusieurs décennies est : appeler fork, puis l'enfant appelle exec. Ce que PHP fait ici est d'exécuter un shell au lieu du programme prévu. L'interpréteur de commandes peut être utile de temps en temps si vous avez réellement besoin des constructions de l'interpréteur. Mais dans le cas où vous voulez juste exécuter un programme, l'interpréteur de commandes ne sera utilisé que pour diviser une chaîne d'arguments séparés par des espaces dans un tableau, introduisant des failles de sécurité dans le processus, puis fork+exec à nouveau. Beaucoup plus simple et plus sûr de passer directement le tableau d'arguments souhaité et d'appeler exec. Pour la situation équivalente en Perl, voir par exemple docstore.mik.ua/orelly/perl/cookbook/ch19_07.htm .
0 votes
learn.perl.org/faq/
1 votes
Je suis tout à fait d'accord avec Stéphane. Et si vous êtes propriétaire du processus spawné, il vaut mieux implémenter un pipe ou des paramètres cryptés pour éviter les failles de sécurité sur les paramètres que vous passez au sous-processus. Cependant, cela dépend d'un lot sur le type de paramètres que vous allez utiliser. Passer un nombre de pages n'implique pas vraiment une faille de sécurité, alors que passer un mot de passe le fera définitivement. Je suppose que vous devez analyser chaque étape de ce que vous devez faire pour adapter la meilleure méthode. Il existe des milliers de solutions, il suffit de prendre la meilleure pour ce que vous avez à faire.
0 votes
@Celada. PHP n'est pas un langage à usage général. Il est conçu pour fonctionner dans un environnement d'hébergement partagé. Vous ne pouvez pas, bon gré mal gré, ajouter une fonction de bas niveau à l'installation par défaut.
0 votes
Vous devez être prévenu que j'ai constaté que PHP est limité à un seul shell fonctionnant en arrière-plan. Si vous avez 2 ou plusieurs scripts qui tentent d'exécuter un shell en même temps, alors le premier bloquera les autres.
0 votes
Pour moi, la question est la suivante : pourquoi lancer un programme externe ? Qu'est-ce qui peut être fait par un programme externe et qui ne peut pas être fait avec PHP ? Si vous avez une solution tierce compilée et que vous voulez récupérer la sortie, cela peut être le cas. J'essaierais alors de trouver une solution sans invoquer de processus enfant sur un serveur web, comme encapsuler le programme dans un service ou le lancer périodiquement, mettre le contenu dans un fichier et le récupérer à partir de là ou quelque chose de similaire et applicable.
0 votes
@AxelAmthor : ce n'est pas seulement une question de "programme externe vs PHP", c'est aussi une question de "programme externe vs gestionnaire de réponse du serveur web". Si nécessaire, un programme externe peut s'exécuter de manière asynchrone aussi longtemps qu'il le souhaite, sans se soucier des délais de réponse d'Apache/PHP ou des redémarrages d'Apache.
0 votes
Mais si vous voulez lancer un programme externe de manière asynchrone et revenir immédiatement dans l'application Web, vous pouvez opter pour une file d'attente de messages plutôt que de lancer ce processus vous-même ?
0 votes
@AxelAmthor : Oui, c'est une meilleure option, si vous avez suffisamment de contrôle sur le serveur pour avoir tout le shebang de la file d'attente des messages installé.
0 votes
File d'attente de messages pour les pauvres : videz les paramètres dans une table DB. Faites en sorte qu'un travail cron lise cette table toutes les x minutes, effectue le travail et signale que l'entrée est terminée. Ceci peut être implémenté en Java, avec un meilleur contrôle des threads et des processus qu'en PHP.
0 votes
Vous pouvez essayer de migrer vers nginx + php-fpm en fonction de votre lien avec Apache. Non seulement nginx est susceptible d'être plus rapide, et
pcntl_fork()
semble fonctionner (en tant que CLI au moins).0 votes
Vous pouvez essayer de migrer vers nginx + php-fpm en fonction de votre lien avec Apache. Non seulement nginx est susceptible d'être plus rapide, mais pcntl_fork() peut fonctionner mieux (il faut compiler avec pcntl ). Vous pourriez également écrire une extension PHP - ce n'est pas si grave...
3 votes
@MathewFoscarini ce n'est pas vrai, PHP peut exécuter autant de shells que vous voulez si vous les détachez correctement. Il y a deux choses à faire : mettre le processus en arrière-plan et rediriger les sorties standard. Voir cette réponse pour plus de détails.
1 votes
C'est peut-être une remarque stupide, mais pourquoi n'avez-vous pas testé avec
popen
?0 votes
@hakre : Merci pour cette suggestion.
popen()
appelle également une coquille. Mise à jour des pastebins.0 votes
Pouvez-vous montrer les ressources où il est dit que les autres langues ne génèrent pas de shell lors de l'exécution d'un exécutable. J'ai fait quelques recherches sur Google et je n'ai pas trouvé de langue qui ne lance pas de shell lors de l'exécution d'une commande système.
0 votes
@CMCDragonkai c'était aussi simple que de googler "languagename exec command without shell" et de regarder les premiers résultats. J'ai déjà mentionné dans un autre commentaire le cas de Perl : docstore.mik.ua/orelly/perl/cookbook/ch19_07.htm . Pour Ruby ruby-doc.org/core-2.1.2/Kernel.html#method-i-system on voit que le premier formulaire exécute un shell, les deux autres non. Pour python, voir docs.python.org/2/library/subprocess.html vous avez un
shell
que vous pouvez définir comme vrai ou faux. Pensiez-vous à d'autres langues ?0 votes
La plupart des liens que vous avez envoyés disent qu'en désactivant le shell, ou en exécutant ces fonctions, on peut rendre l'exécution "plus sûre" en désactivant toutes les capacités du shell (celle de python et celle de perl). Je n'ai rien lu qui disait explicitement qu'un shell n'était pas instancié. Pour rendre les commandes du shell plus sûres en PHP, on pourrait utiliser php.net/manual/fr/function.escapeshellarg.php o php.net/manual/fr/function.escapeshellcmd.php Peut-être que je ne comprends pas bien, mais je pensais que toutes les commandes système devaient passer par le shell.
0 votes
Et aussi, qu'en est-il de ça ? php.net/manual/fr/function.pcntl-exec.php
0 votes
@CMCDragonkai. l'échappement shell est "balayé sous le tapis" : pas réellement évite le coup de performance, et la sécurité est en danger. En ce qui concerne vos croyances, peut-être venez-vous d'un environnement Windows, où vous ne pouvez éviter que vos arguments soient interprétés mais d'une certaine manière plus simplement que dans un vrai shell, voir msdn.microsoft.com/fr/us/library/Windows/desktop/ . Concernant
pcntl_exec
c'est seulement la seconde moitié du bas niveau. fork+exec modèle. D'autres langages ont fait une fonction de haut niveau simple et portable, pas PHP.0 votes
Je suppose que je ne comprends pas ce que signifie lancer un programme sans le shell CLI. Mais tous ces liens ci-dessus que vous avez envoyés, sonnent comme s'ils lançaient juste le programme via le shell CLI également. Aussi, si l'exécutable était un shell script, cela ne signifie-t-il pas invoquer le shell pour savoir quel est l'interpréteur de l'exécutable ? Après tout, comment savoir en quoi l'exécutable est écrit ?
2 votes
@CMCDragonkai "ils ne font que lancer le programme via le shell CLI" -> non, ils ne le font pas, par ex. sur les plates-formes POSIX, python utilise par défaut execvp sans coquille, c'est tout l'intérêt. "découvrir ce qu'est l'interpréteur" -> dérive hors sujet, le chargeur de programme le fait, cf. fr.wikipedia.org/wiki/Shebang_%28Unix%29 . Vous ne voulez pas d'un shell de plus pour savoir quel interpréteur/shell lancer, n'est-ce pas ? Pensez à sortir de l'état d'esprit "tout doit commencer par un shell". Il n'y a pas de cuillère, je veux dire pas de shell ;-)
2 votes
@MarcB "PHP lui-même ne peut rien faire que glib/libc ne fassent eux-mêmes" C'est une affirmation absurde. La plupart du langage PHP et de l'API font des choses qui ne sont pas intégrées dans la stdlib de C.
3 votes
@StéphaneGourichon Une autre façon de tester les coquillages :
php -r 'system("sleep 1000");'
et ensuite dans un autre terminalpstree | grep sleep
. Cet exemple montre le shell interposé entre php et sleep :|-konsole-+-bash---php---sh---sleep
0 votes
@StéphaneGourichon Je vois maintenant. Eh bien je suppose que vos choix sont de s'abstenir au-dessus du pcntl_exec vous-même, ou d'écrire une extension C qui fait l'appel système de execvp. Mais je vois que vous utilisez PHP comme un module Apache. PHP est plus puissant lorsqu'il est utilisé indépendamment ou comme partie de FPM.