Il y a une commande Unix Shell (udevadm info -q path -n /dev/ttyUSB2
) Que je veux appeler à partir d'un programme C. Avec probablement environ une semaine de lutte, je pourrais le réimplémenter moi-même, mais je ne veux pas le faire.
Est-ce une bonne pratique largement acceptée pour moi d'appeler simplement popen("my_command", "r");
, ou cela introduira-t-il des problèmes de sécurité inacceptables et transmettra-t-il des problèmes de compatibilité? Cela me fait du mal de faire quelque chose comme ça, mais je ne peux pas mettre le doigt sur pourquoi ce serait mauvais.
Ce n'est pas particulièrement mauvais, mais il y a quelques mises en garde.
LANG
etc.?;rm -rf /
(par exemple) (notez que certaines API vous permettront de spécifier des arguments de manière plus sécurisée que de simplement les fournir sur la ligne de commande)Je suis généralement heureux d'exécuter des binaires lorsque je suis dans un environnement connu que je peux prédire, lorsque la sortie binaire est facile à analyser (si nécessaire - vous pouvez simplement avoir besoin d'un code de sortie) et je n'ai pas besoin de le faire aussi souvent.
Comme vous l'avez noté, l'autre problème est la quantité de travail nécessaire pour reproduire ce que fait le binaire? Utilise-t-il une bibliothèque que vous pouvez également exploiter?
Il est extrêmement prudent de se prémunir contre les vulnérabilités d'injection une fois que vous avez introduit un vecteur potentiel. C'est à l'avant-plan de votre esprit maintenant, mais plus tard, vous devrez peut-être sélectionner ttyUSB0-3
, alors cette liste sera utilisée dans d'autres endroits afin qu'elle soit prise en compte pour suivre le principe de la responsabilité unique, puis un client devra mettre un appareil arbitraire dans la liste, et le développeur faisant que le changement n'aura aucune idée que la liste finira par être utilisée de manière dangereuse.
En d'autres termes, codez comme si le développeur le plus imprudent que vous connaissez effectuait un changement dangereux dans une partie apparemment sans rapport du code.
Deuxièmement, la sortie des outils CLI n'est généralement pas considérée comme une interface stable, sauf si la documentation les marque spécifiquement comme telles. Vous pouvez peut-être compter sur eux pour un script que vous exécutez que vous pouvez résoudre vous-même, mais pas pour quelque chose que vous déployez chez un client.
Troisièmement, si vous voulez extraire facilement une valeur de votre logiciel, il y a de fortes chances que quelqu'un d'autre le veuille aussi. Recherchez une bibliothèque qui fait déjà ce que vous voulez. libudev
était déjà installé sur mon système:
#include <libudev.h>
#include <sys/stat.h>
#include <stdio.h>
int main(int argc, char* argv[]) {
struct stat statbuf;
if (stat("dev/ttyUSB2", &statbuf) < 0)
return -1;
struct udev* udev = udev_new();
struct udev_device *dev = udev_device_new_from_devnum(udev, 'c', statbuf.st_rdev);
printf("%s\n", udev_device_get_devpath(dev));
udev_device_unref(dev);
udev_unref(udev);
return 0;
}
Il existe d'autres fonctionnalités utiles dans cette bibliothèque. Je suppose que si vous en avez besoin, certaines des fonctions associées peuvent également être utiles.
Dans votre cas spécifique, où vous souhaitez invoquer udevadm
, je soupçonne que vous pouvez extraire udev
en tant que bibliothèque et effectuer les appels de fonction appropriés comme alternative?
par exemple, vous pouvez jeter un œil à ce que fait udevadm lui-même lorsqu'il est invoqué en mode "info": https://github.com/gentoo/eudev/blob/master/src/udev/udevadm-info.c et faire des appels équivalents à ceux que fait udevadm.
Cela éviterait beaucoup des compromis négatifs énumérés dans excellente réponse de Brian Agnew - par exemple, ne pas se fier au binaire existant à un certain chemin, éviter les frais de bifurcation, etc.
Votre question semblait appeler une réponse sur la forêt, et les réponses ici ressemblent à des réponses d'arbre, alors j'ai pensé vous donner une réponse sur la forêt.
C'est très rarement comment les programmes C sont écrits. C'est toujours la façon dont les scripts Shell sont écrits, et parfois comment les programmes Python, Perl ou Ruby sont écrits.
Les gens écrivent généralement en C pour une utilisation facile des bibliothèques système et un accès direct de bas niveau aux appels système du système d'exploitation ainsi que pour la vitesse. Et C est un langage difficile à écrire, donc si les gens n'ont pas besoin de ces choses, ils n'utilisent pas C. De plus, les programmes C ne devraient généralement avoir que des dépendances sur les bibliothèques partagées et les fichiers de configuration.
Décortiquer un sous-processus n'est pas particulièrement rapide, et il ne nécessite pas un accès fin et contrôlé aux installations système de bas niveau, et il introduit une dépendance peut-être surprenante d'un exécutable externe, il est donc rare de voir dans les programmes C.
Il y a d'autres préoccupations. Les problèmes de sécurité et de portabilité mentionnés par les gens sont tout à fait valables. Bien sûr, ils sont également valables pour les scripts Shell, mais les gens s'attendent à ce genre de problèmes dans les scripts Shell. Mais les programmes C ne sont généralement pas censés avoir ce type de problème de sécurité, ce qui le rend plus dangereux.
Mais, à mon avis, les plus grandes inquiétudes concernent la manière dont popen
interagira avec le reste de votre programme. popen
doit créer un processus enfant, lire sa sortie et collecter son état de sortie. En attendant, le stderr de ce processus sera connecté au même stderr que votre programme, ce qui peut entraîner une sortie confuse, et son stdin sera le même que votre programme, ce qui pourrait entraîner d'autres problèmes intéressants. Vous pouvez résoudre ce problème en incluant </dev/null 2>/dev/null
dans la chaîne que vous passez à popen
car elle est interprétée par le Shell.
Et popen
crée un processus enfant. Si vous faites quoi que ce soit avec la gestion des signaux ou les processus de forking vous-même, vous risquez de recevoir des signaux SIGCHLD
étranges. Vos appels à wait
peuvent interagir étrangement avec popen
et éventuellement créer d'étranges conditions de concurrence.
Les problèmes de sécurité et de portabilité sont bien sûr là. Comme pour les scripts Shell ou tout ce qui démarre d'autres exécutables sur le système. Et vous devez faire attention à ce que les utilisateurs de votre programme ne puissent pas obtenir de méta-charcaters Shell dans la chaîne que vous passez dans popen
car cette chaîne est donnée directement à sh
avec sh -c <string from popen as a single argument>
.
Mais je ne pense pas qu'ils soient la raison pour laquelle il est étrange de voir un programme C utilisant popen
. La raison pour laquelle cela est étrange est que C est généralement un langage de bas niveau et que popen
n'est pas de bas niveau. Et parce que l'utilisation de popen
impose des contraintes de conception à votre programme car il interagira étrangement avec les entrées et sorties standard de votre programme et vous compliquera la gestion de processus ou la gestion des signaux. Et parce que les programmes C ne sont généralement pas censés avoir des dépendances sur les exécutables externes.
Votre programme peut être sujet au piratage, etc. Une façon de vous protéger de ce type d'activité consiste à créer une copie de l'environnement de votre machine actuelle et à exécuter votre programme à l'aide d'un système appelé chroot.
Du point de vue de votre programme, son exécution dans un environnement normal, d'un point de vue de la sécurité, si quelqu'un casse votre programme, il n'a accès qu'aux éléments que vous avez fournis lorsque vous avez fait la copie.
Une telle configuration est appelée une prison chroot pour plus de détails, voir prison chroot .
Il est normalement utilisé pour configurer des serveurs accessibles au public, etc.