system() et open() (pipes) sûrs et portables
Le
Manuel Pégourié-Gonnard
Bonjour,
system() existe, si je comprends bien, sous deux formes essentiellement
différentes du point de vue de la sécurité : celle qui fait
potentiellement appel à un shell (l'argument est un scalaire ou une liste
à un élément), et celle qui ne fait jamais appel à un shell (l'argument
est une liste à plus d'un élément, ou il y a un premier argument suivi
sans virgule par un deuxième qui est alors interpété comme une liste).
Lorsqu'on lance une commande externe, et qu'une partie de la ligne de
commande est d'origine mal contrôlée (entrée utilisateur), si l'on veut
être sûr de savoir ce qui se passe, il faut mieux, il me semble,
utiliser la deuxième forme.
Ma question est : y a-t-il la moindre raison de ne pas préférer cette
deuxième forme, dès lors qu'on n'a pas réellement besoin d'appeler un
shell ? Par exemple en matière de portabilité (il s'agit d'un script qui
doit tourner au moins sous Unix, Windows, et Cygwin).
Même question quand il s'agit d'ouvrir une commande avec un pipe vers son
entrée standard ou depuis sa sortie standard : peut-on toujours utiliser
la forme à plus de trois arguments sur les plateformes citées ? Dans
perldoc -f open, je lis :
The last example in each block shows the pipe as "list form",
which is not yet supported on all platforms. A good rule of
thumb is that if your platform has true "fork()" (in other
words, if your platform is UNIX) you can use the list form.
qui m'inquiète un peu, parce qu'il me semble que celà exclut windows.
Visiblement il y a des trucs à lire à ce sujet dans perlipc, mais je
n'ai pas trouvé à quel endroit, je m'y attaquerai demain (ça a l'air
d'un gros morceau).
Merci d'avance pour vos conseils !
--
Manuel Pégourié-Gonnard Institut de mathématiques de Jussieu
http://weblog.elzevir.fr/ http://people.math.jussieu.fr/~mpg/
system() existe, si je comprends bien, sous deux formes essentiellement
différentes du point de vue de la sécurité : celle qui fait
potentiellement appel à un shell (l'argument est un scalaire ou une liste
à un élément), et celle qui ne fait jamais appel à un shell (l'argument
est une liste à plus d'un élément, ou il y a un premier argument suivi
sans virgule par un deuxième qui est alors interpété comme une liste).
Lorsqu'on lance une commande externe, et qu'une partie de la ligne de
commande est d'origine mal contrôlée (entrée utilisateur), si l'on veut
être sûr de savoir ce qui se passe, il faut mieux, il me semble,
utiliser la deuxième forme.
Ma question est : y a-t-il la moindre raison de ne pas préférer cette
deuxième forme, dès lors qu'on n'a pas réellement besoin d'appeler un
shell ? Par exemple en matière de portabilité (il s'agit d'un script qui
doit tourner au moins sous Unix, Windows, et Cygwin).
Même question quand il s'agit d'ouvrir une commande avec un pipe vers son
entrée standard ou depuis sa sortie standard : peut-on toujours utiliser
la forme à plus de trois arguments sur les plateformes citées ? Dans
perldoc -f open, je lis :
The last example in each block shows the pipe as "list form",
which is not yet supported on all platforms. A good rule of
thumb is that if your platform has true "fork()" (in other
words, if your platform is UNIX) you can use the list form.
qui m'inquiète un peu, parce qu'il me semble que celà exclut windows.
Visiblement il y a des trucs à lire à ce sujet dans perlipc, mais je
n'ai pas trouvé à quel endroit, je m'y attaquerai demain (ça a l'air
d'un gros morceau).
Merci d'avance pour vos conseils !
--
Manuel Pégourié-Gonnard Institut de mathématiques de Jussieu
http://weblog.elzevir.fr/ http://people.math.jussieu.fr/~mpg/

Poser une question


Finalement j'ai trouvé, mais je n'ai pas l'impression que ça répond à ma
question. D'une part parce que ce qui y est décrit comme "safe pipe
open" correspond à la syntaxe de open à plus de trois argument depuis
Perl 5.8, d'autre part parce que ça semble toujours très centré sur
Unix :
Note that these operations are full Unix forks, which means they may
not be correctly implemented on alien systems.
J'avoue que le "may not" me laisse un peu sur ma faim pour savoir si en
pratique ça marche sur windows ou pas.
J'en appele donc à votre expérience et à votre sagesse... (Et je
prévois une question subsidiaire : s'il n'y a pas moyen de gérer des
pipes sans passer par un shell sous windows, quelles sont les bonnes
pratiques concernant l'utilisation de fichiers temporaires ?)
--
Manuel Pégourié-Gonnard Institut de mathématiques de Jussieu
http://weblog.elzevir.fr/ http://people.math.jussieu.fr/~mpg/
A mon avis, il faut privilegier la securite: donc ecris des trucs qui soient
surs sous Unix... et apres, prevois des solutions de secours pour le reste.
(par exemple, tu peux assez facilement resynthetiser le system a 1 argument
a partir du systeme a plusieurs).
Pour avoir un comportement previsible, j'en suis a privilegier fork+exec
a system si, par exemple, je n'aurais besoin du shell que pour rediriger
stderr vers /dev/null.
Mais oui, effectivement, tout ce qui est fork ne tourne pas sous windows, et
il faut d'autres solutions, essentiellement a base de multi-thread pour les
machins internes, et a base de "system" simple pour le reste.
Par exemple, tu peux jeter un oeil a DBI::Proxy, qui contient des options
pour se lancer en multi-threade, explicitement pour que ca serve a quelque
chose sous windows...
Si tu t'interesses reellement a la portabilite de tes scripts, au final, tu
n'as pas le choix, tu vas etre bon pour installer perl sur cette merde de
windows et pour faire des essais...
Concernant les fichiers temporaires, il y a File::Temp, qui contient entres
autres un equivalent de mkstemp, qui est la bonne solution sur les Unix
(creation de temporaire sans race condition) et plein d'autres cochonneries.
Je t'avouerais que j'aimerais bien que ca soit nettoye un jour (si je trouve
le temps, je ferai un File::Temp::Light), car il y a a boire, a manger, et
a se pisser dessus dans File::Temp. Il n'y a qu'a voir la liste des
dependances qu'il ramene, c'est programme a la physicien, ce truc... au
point que ca m'est deja arrive d'en extraire le seul bout utile (mkstemp)
histoire d'avoir un script qui ne passe pas 3 secondes a charger tous les
trucs qui ne vont pas servir...
Post-scriptum sur la portabilite.
Ton attitude est exactement ce qu'il faut eviter, en fait (mais c'est un
travers extremement frequent).
Souvent, je croise des gens qui veulent bien faire, et qui s'inquietent du
fait que leur code devrait etre portable un peu partout, que ce soit du C,
du perl, encore autre chose. Souvent, ces gens n'ont que des notions assez
vagues de ce qui va se passer sur d'autres systemes. Et souvent, ces
gens se retrouvent a faire des compromis facheux pour que leur code puisse
eventuellement peut-etre tourner sur un systeme mythique auquel ils n'ont
pas acces...
En fait, il y a un ensemble des choses a considerer.
- les priorites dans ton cahier des charges (oui, meme si c'est pour le fun
et meme si ca n'est pas ecrit, c'est bien de se faire un cahier des charges).
Le truc le plus important, c'est pratiquement toujours d'avoir un truc qui
tourne CORRECTEMENT sur un premier systeme. Comme proprietes desirables,
il faut tres souvent mettre la clarte et la simplicite pas loin derriere,
histoire d'avoir un code maintenable (parce que de toutes facons, on va le
faire evoluer, tot ou tard, sauf si c'est un echec complet).
- la portabilite universelle n'existe pas. Il y aura toujours des systemes
avec des contraintes a la con, sans filesystem, avec des entiers bizarres,
avec pas beaucoup de memoire... faire de la portabilite sur tout et
n'importe quoi, ca ne sert a rien. Donc on se fixe un "perimetre de systemes"
sur lesquels ca doit tourner, et tant pis pour le reste. On pourra
eventuellement revisiter la decision plus tard, d'autant plus facilement
que le code sera clair.
- le code "portable" sortant directement des mains du programmeur n'existe
pas. Il n'y a que du code "porté". Tant qu'on n'a pas fait des tests sur
les systemes exotiques, on ne saura pas reellement ce qui va merder. Ce n'est
qu'avec de l'experience sur les systemes consideres qu'on ne se fera
pas avoir... typiquement, en perl, c'est plus important de bien connaitre
la bibliotheque histoire d'eviter tout appel inutile a system et de traiter
tous les noms de fichiers a coup de File::Spec que tout autre chose.
On peut deleguer un max de portabilite aux bouts de la bibliotheque standard
qui font ca. Ca evite d'avoir a se poser des questions.
En pratique, je croise des tonnes de code qui se veulent portables (souvent
autre chose que du perl, hein), avec des tonnes de trucs ou l'auteur original
s'est pris la tete pour sortir de son systeme, et qui echouent lamentablement,
parce qu'ils supposent que make est gnu-make (make -w croise recemment), ou
que sed est gnu-sed (trouve plein de sed -i dans un configure recemment,
le premier fait rire, les suivants un peu moins).
Nan, la bonne facon de faire portable, c'est reellement de mettre les mains
dans le cambouis et de porter son code sur le systeme cible... ou de
s'arreter avant, et de collaborer avec quelqu'un qui bosse sur le systeme
cible, et qui pourra pointer du doigt les vrais soucis. ;-)
Manuel Pégourié-Gonnard
C'est la dernière syntaxe qu'il faut privilégier (celle avec un
premier élément sans virgule derrière) pour être sûr de ne pas passer
par un shell (ou un pseudo-shell).
Non.
En fait, sous Windows et d'autres OS, le véritable souci n'est ni
system() ni open() mais fork(). Évidemment, puisque system() et open()
font appel à fork(), on récupère les mêmes soucis qu'avec fork() mais
c'est un effet de bord.
En fait, sous Windows, le fork() est émulé en utilisant du
multi-threading. Pour en savoir plus sur les limitations de cette
émulation, il faut commencer par lire 'perlfork'. Il y a une solution
pour émuler les tubes (pipes).
L'autre solution sous Windows consiste à ne pas utiliser fork() et à
faire appel aux modules Win32 qui permettent de créer de vrais
processus externes. En revanche, la communication entre les processus
ne pourra pas reposer sur des tubes....
--
Paul Gaborit - Perl en français -
(Marc Espie) écrivait (wrote):
[...]
[...]
Je suis complètement d'accord avec ce discours. Ceci étant, on peut
quand même distinguer différents périmètres de portabilité. Si on se
limite aux Unix (Linux, *BSD), à MacOS X et Windows, on n'est pas dans
la même situation que si on souhaite attaquer aussi des smartphones ou
des ordinateurs de bord... En reprenant tes exemples, on a de la
mémoire, on a un filesystem, on a des entiers décents et on a une
puissance de calcul décente. De plus Perl (ou plutôt, perl) intègre
beaucoup de mécanismes permettant de garantir une certaine portabilité
sans écriture de code spécifique.
Alors là : d'accord à 120% ! ;-)
Mais si on écrit le bout de code initial sous Unix, on peut quand même
préparer le terrain en isolant dans le code les parties qu'on connaît
déjà comme pouvant poser problème lors du portage.
--
Paul Gaborit - Perl en français -