Rendre stdin accessible en écriture de manière sécurisée et portable

J’essayais de lancer deux programmes.

Cas 1

#include  #include  #include  int main() { int n; int k = 10; int ret_val = 0; ret_val = write (0, &k, sizeof(int)); if (-1 == ret_val) { printf ("Failed to write"); exit (EXIT_FAILURE); } scanf ("%d", &n); printf ("Integer read is %d \n", n); return 0; } 

Puis j’ai essayé le suivant.

Cas 2

 #include  #include  #include  #include  #include  #include  int main() { int n; int k = 10; int ret_val = 0; /* Open file from which content shall be inserted to stdin_buffer */ int source_fd = open ("file_in.txt", O_CREAT | O_RDWR, S_IRWXU); if (-1 == source_fd) { printf ("Failed to open file for reading"); exit (EXIT_FAILURE); } int stdin_fd; /* Close STDIN_FILENO */ close(0); /* dup the source */ stdin_fd = dup (source_fd); if (-1 == stdin_fd) { printf ("Failed to dup"); exit (EXIT_FAILURE); } /* write to stdin_buffer (content will be taken from file_in.txt) */ ret_val = write (stdin_fd, &k, sizeof(int)); if (-1 == ret_val) { printf ("Failed to write to stdin_buffer"); exit (EXIT_FAILURE); } scanf ("%d", &n); printf ("Integer read is %d \n", n); close(source_fd); return 0; } 

Maintenant, dans le premier cas, je n’ai pas pu écrire à stdin. Dans le second cas, j’ai pu prendre l’entrée d’un fichier, “file_in.txt”, et envoyer le contenu dans le tampon d’entrée standard.

Je ne pouvais pas bien expliquer pourquoi mon premier cas n’avait pas fonctionné. Quelqu’un peut-il expliquer?

stdin devrait être comme n’importe quel autre fichier non? Si c’est protégé en écriture, très bien. Mais ensuite, lorsque j’ai redirigé l’entrée (dans le deuxième cas), il n’y avait pas de problème de “permission refusée”. Ce code semble être non-portable. Existe-t-il un moyen portable et sûr de redirect stdin à partir d’un fichier?


Après avoir passé en revue les commentaires, j’ai mis au point un meilleur code de travail . Je voudrais des commentaires sur ce code

 #include  #include  #include  #include  #include  #include  #include  #include  #define LEN 100 int main() { int n; char buffer[LEN]; memset (buffer, '\0', LEN); int ret_val = 0; /* Open file from which content shall be inserted to stdin_buffer */ int source_fd = open ("file_in.txt", O_CREAT | O_RDONLY, S_IRWXU); if (-1 == source_fd) { perror ("Failed to open file for reading"); exit (EXIT_FAILURE); } /* Temp stdin_buffer */ int temp_fd = open ("temp_in.txt", O_CREAT | O_RDWR, S_IRWXU); if (-1 == temp_fd) { perror ("Failed to open temp stdin"); exit (EXIT_FAILURE); } int stdin_fd; /* Close STDIN_FILENO */ close(0); /* dup the source */ stdin_fd = dup (temp_fd); if (-1 == stdin_fd) { perror ("Failed to dup"); exit (EXIT_FAILURE); } ret_val = read (source_fd, buffer, LEN); if (-1 == ret_val) { perror ("Failed to read from source"); exit (EXIT_FAILURE); } else { printf ("%s read from Source file\n", buffer); } /* write to stdin_buffer (content taken from file_in.txt) */ ret_val = write (stdin_fd, buffer, LEN); if (-1 == ret_val) { perror ("Failed to write to stdin_buffer"); exit (EXIT_FAILURE); } ret_val = lseek (stdin_fd, 0, SEEK_SET); if (-1 == ret_val) { perror ("Failed lseek"); exit (EXIT_FAILURE); } ret_val = scanf ("%d", &n); if (-1 == ret_val) { perror ("Failed to read stdin_buffer"); exit (EXIT_FAILURE); } printf ("Integer read is %d \n", n); close(source_fd); return 0; } 

Avant les mises à jour

Dans le premier programme, 3 octets nuls et une nouvelle ligne étaient (probablement) écrits à l’écran (pas nécessairement dans cet ordre); le programme essaie alors de lire à partir du clavier (en supposant qu’il n’y a pas de redirection d’E / S sur la ligne de commande). L’écriture sur l’entrée standard ne charge pas le tampon d’entrée. Vous pouvez très souvent écrire sur une entrée standard (et lire une sortie standard et une erreur standard), car la technique classique ouvre un descripteur de fichier avec O_RDWR , puis le connecte aux canaux d’E / S standard. Cependant, rien ne garantit que vous pourrez le faire. (Le premier programme a besoin de , incidemment).

Le deuxième programme a tellement de comportements indéfinis qu’il est difficile à parsingr. L’appel open() nécessite trois arguments car il inclut O_CREAT ; le troisième argument est le mode du fichier (par exemple 0644 ). Vous ne vérifiez pas que open() réussit. Vous ne vérifiez pas que l’écriture réussit; ce ne sera pas le cas, car le descripteur de fichier est ouvert O_RDONLY (ou plutôt, le source_fd est ouvert O_RDONLY , et dup() copiera ce mode dans le descripteur de fichier 0), ce qui signifie que write() échouera. L’opération d’entrée n’est pas vérifiée (vous ne vous assurez pas que scanf() réussit). (Le second programme n’a pas vraiment besoin de ou de .)

Fondamentalement, vous ne savez rien de ce qui se passe car vous n’avez vérifié aucun des appels de fonction critiques.

Après la mise à jour 1

Notez que les messages d’erreur doivent être écrits en erreur standard et être terminés par des retours à la ligne.

Je fais fonctionner le premier programme comme indiqué (Mac OS X 10.10 Yosemite, GCC 4.8.1), bien qu’il soit difficile de prouver que les octets nuls ont été écrits sur une entrée standard (mais une nouvelle ligne y a été écrite). Je pourrais alors taper 10 (ou 20, ou 100, ou…) plus Retour et cet entier serait alors imprimé.

Le second programme échoue sur le scanf() car le pointeur de fichier se trouve à la fin du fichier lorsque vous essayez de lire. Vous pouvez voir avec cette variante de votre programme:

 #include  #include  #include  #include  int main(void) { /* Open file from which content shall be inserted to stdin_buffer */ int source_fd = open ("file_in.txt", O_CREAT | O_RDWR, S_IRWXU); if (-1 == source_fd) { printf ("Failed to open file for reading\n"); exit (EXIT_FAILURE); } close(0); int stdin_fd = dup (source_fd); if (-1 == stdin_fd) { printf("Failed to dup\n"); exit(EXIT_FAILURE); } int k = 10; int ret_val = write(stdin_fd, &k, sizeof(int)); if (-1 == ret_val) { printf("Failed to write to stdin_buffer\n"); exit(EXIT_FAILURE); } int rc; int n; if ((rc = scanf("%d", &n)) != 1) printf("Failed to read from standard input: rc = %d\n", rc); else printf("Integer read is %d (0x%08x)\n", n, n); close(source_fd); return 0; } 

Cela produit:

 Failed to read from standard input: rc = -1 

Si vous rembobinez le fichier avant la lecture, vous obtiendrez 0 renvoyé; les données binarys écrites dans le fichier ne constituent pas une représentation sous forme de chaîne valide d’un entier.

Après la mise à jour 2

J’ai écrit une petite fonction err_exit() car elle permet au code d’être plus petit sur la page. J’ai modifié votre code à plusieurs endroits pour signaler la valeur de retour d’une fonction précédente. L’absence de saisie n’est pas une erreur. Lorsque vous obtenez 0 octet lu, ce n’est pas une erreur; c’est EOF. Lorsqu’il y a des données à lire mais que ce n’est pas le format de texte d’une valeur entière, aucune conversion n’a lieu, mais ce n’est pas une erreur.

 #include  #include  #include  #include  #include  #include  #define LEN 100 static void err_exit(const char *msg) { perror(msg); exit(EXIT_FAILURE); } int main(void) { int n = 99; char buffer[LEN]; memset(buffer, '\0', LEN); int ret_val = 0; /* Open file from which content shall be inserted to stdin_buffer */ int source_fd = open("file_in.txt", O_CREAT | O_RDONLY, S_IRWXU); if (-1 == source_fd) err_exit("Failed to open file for reading"); /* Temp stdin_buffer */ int temp_fd = open("temp_in.txt", O_CREAT | O_RDWR, S_IRWXU); if (-1 == temp_fd) err_exit("Failed to open temp stdin"); /* Close STDIN_FILENO */ close(0); /* dup the source */ int stdin_fd = dup(temp_fd); if (-1 == stdin_fd) err_exit("Failed to dup"); ret_val = read(source_fd, buffer, LEN); if (-1 == ret_val) err_exit("Failed to read from source"); else printf("(%d bytes) <<%s>> read from Source file\n", ret_val, buffer); /* write to stdin_buffer (content taken from file_in.txt) */ ret_val = write(stdin_fd, buffer, LEN); if (-1 == ret_val) err_exit("Failed to write to stdin_buffer"); ret_val = lseek(stdin_fd, 0, SEEK_SET); if (-1 == ret_val) err_exit("Failed lseek"); ret_val = scanf("%d", &n); if (-1 == ret_val) err_exit("Failed to read stdin_buffer"); printf("Integer read is %d (ret_val = %d)\n", n, ret_val); close(source_fd); return 0; } 

Sortie:

 (0 bytes) <<>> read from Source file Integer read is 99 (ret_val = 0) 

Lorsque scanf() ne parvient pas à lire une valeur, il n’écrit généralement rien dans la variable correspondante. C’est pourquoi le 99 survit. Si vous voulez des données pouvant être lues par scanf() sous forme d’entier, vous avez besoin des éléments suivants:

 #include  #include  #include  #include  #include  #include  #define LEN 100 static void err_exit(const char *msg) { perror(msg); exit(EXIT_FAILURE); } int main(void) { int n = 99; char buffer[LEN] = ""; int ret_val = 0; /* Open file from which content shall be inserted to stdin */ int source_fd = open("file_in.txt", O_CREAT | O_RDONLY, S_IRWXU); if (-1 == source_fd) err_exit("Failed to open file for reading"); /* Temp stdin */ int temp_fd = open("temp_in.txt", O_CREAT | O_RDWR, S_IRWXU); if (-1 == temp_fd) err_exit("Failed to open temp stdin"); /* Close STDIN_FILENO */ close(0); /* dup the source */ int stdin_fd = dup(temp_fd); if (-1 == stdin_fd) err_exit("Failed to dup"); ret_val = read(source_fd, buffer, LEN); if (-1 == ret_val) err_exit("Failed to read from source"); else printf("(%d bytes) <<%s>> read from Source file\n", ret_val, buffer); /* write to stdin (content taken from file_in.txt) */ ret_val = write(stdin_fd, "10\n", sizeof("10\n")-1); if (-1 == ret_val) err_exit("Failed to write to stdin"); ret_val = lseek(stdin_fd, 0, SEEK_SET); if (-1 == ret_val) err_exit("Failed lseek"); ret_val = scanf("%d", &n); if (-1 == ret_val) err_exit("Failed to read stdin"); printf("Integer read is %d (ret_val = %d)\n", n, ret_val); close(source_fd); return 0; } 

Sortie:

 (0 bytes) <<>> read from Source file Integer read is 10 (ret_val = 1) 

Voyons ce que font vos trois programmes …

Programme 1

Le premier programme écrit dans la commande filedescriptor 0. Par défaut, il s’agit de stdout . Par conséquent, la macro STDOUT_FILENO a cette valeur. stdout étant un stream unidirectionnel qui s’exécute dans le processus, l’écriture échoue.

Programme 2

Dans ce programme, vous ouvrez un fichier qui obtient probablement FD (filedescriptor) 3 (après 0-2 pour les stream standard). Ensuite, vous fermez stdout avec FD 0. Ensuite, vous dup() FD 3 et puisque le premier point ouvert est à l’indice 0, c’est le nouveau FD du fichier. Puisque scanf () utilise uniquement la macro (inchangée) STDIN_FILENO, le contenu de ce fichier y sera récupéré.

Programme 3

Dans le programme 3, vous faites à peu près la même chose que dans le programme 2, sauf que vous ouvrez le fichier deux fois. L’explication de ce qui se passe suit à peu près les deux ci-dessus.

La question maintenant est ce que vous voulez dire quand vous dites “un meilleur code de travail”. Le fait est qu’il est impossible de dire “meilleur” à moins d’indiquer ce que vous voulez réellement. En devinant de votre sujet et de votre commentaire, vous voulez contrôler à distance un second processus, comme si vous utilisiez le symbole du tuyau dans bash.

Pour ce faire, vous devrez recourir à des moyens spécifiques à votre système d’exploitation. Il vous suffit de rechercher «redirection entrée-sortie» pour pouvoir trouver des informations. Puisque vous avez déjà mentionné execl (), la portabilité au-delà de POSIX (par exemple vers win32) ne semble pas être un problème, et pour cela, vous devriez trouver beaucoup d’exemples de code, bien mieux que ce que je peux écrire ici.

En tout cas, cela se résume en gros à ces étapes:

  • pipe() pour créer une paire de FD
  • fork() pour créer un nouveau processus
  • child: dup2() pour réaffecter le FD d’entrée de pipe() à stdin
  • child: execl () le nouveau processus
  • parent: écrit dans la FD de sortie pour générer une entrée pour le processus enfant

En outre, vous devez fermer les stream inutilisés ou les configurer avec fcntl(FD_CLOEXEC) pour les fermer automatiquement pour vous. Le point important est que les deux FD restnt connectés au même canal (!), Bien que vous ayez les deux extrémités dans les deux processus. La fermeture d’une extrémité dans chaque laisse un canal unidirectionnel entre les deux processus.