2014:03:18:more:files
Introduction
L'objectif de ce TP est de voir les interractions entre fork(2), execvp(3) et les opérations sur les fichiers.
Écrire ensemble
Nous allons commencer par un petit exemple de code à faire tourner pour voir.
#define _GNU_SOURCE #include <err.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> void write_op(int fd, unsigned long iter) { char *prefix; asprintf(&prefix, "%d>\tline ", getpid()); for (unsigned long i = 0; i < iter; ++i) { // to avoid buffers interference, we use only write // not buffered output from stdio, that's why we // use asprintf to build messages. char *msg; int len; len = asprintf(&msg,"%s%lu\n", prefix, i); if ( write(fd, msg, len) == -1 ) err(3, "%s error while writing line %lu", prefix, i); free(msg); usleep(10); } free(prefix); } int main(int ac, char *av[]) { char *fname = "output"; unsigned long iter = 20; int fd; // options if (ac > 1) fname = av[1]; if (ac > 2) iter = strtoul(av[2], NULL, 10); // file opening if ( (fd = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) == -1 ) err(3, "error while opening %s", fname); // Both process will call the same function, so we don't need // to separate parent and child process. if (fork() < 0) err(4, "can't fork"); // Now writing write_op(fd, iter); // done, close the file properly close(fd); // and leave return 0; }
Compiler et exécuter ce programme (lisez le code aussi, il y a des option éventuellement … )
> clang -Wall -Wextra -std=c99 -O3 -o concur_write concur_write.c > ./concur_write > cat output ...
Redirections
Notre objectif maintenant est de faire quelques redirections, voici l'un des exemples présentés dans le cours, il vous servira de doc.
#define _XOPEN_SOURCE 500 #include <err.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { int fd; char *fname = "output"; if (argc > 1) fname = argv[1]; if ( (fd = open(fname,O_WRONLY|O_CREAT|O_TRUNC, 0666)) == -1) err(3,"error opening %s", fname); dup2(fd, STDOUT_FILENO); close(fd); printf("Normally this is in %s and not on the TTY !\n", fname); return 0; }
Votre objectif est maintenant de d'écrire un programme, appelé redir_exec qui:
- prendre sur la ligne de commande un nom de fichier, puis une commande avec ses éventuelles options
- se divise (fork(2))
- le père attend la mort du fils avec wait(3)
- le fils:
- ouvre le fichier en écriture (avec création ou écrasement)
- redirige sa sortie standard sur le fichier ouvert
- exécute la commande en argument avec execvp
Un exemple d'utilisation serait:
> ./redir_exec ls.output ls > cat ls.output ... > ./redir_exec ls-l.output ls -l > cat ls-l.output ...
Écrire des données
Jusque là nous n'avons fait qu'écrire du texte, mais il est tout à fait possible d'écrire des données !
Voici une structure représentant des personnes, accompagnée par une fonction de construction (qui lit les infos via le clavier) et une fonction d'affichage.
struct person { char firstname[24]; char lastname[24]; char login[8]; unsigned age; }; void read_person(struct person *p) { char tmp[25]; printf("Enter first name: "); scanf(" %24s", tmp); strncpy(p->firstname, tmp, 24); printf("Enter last name: "); scanf(" %24s", tmp); strncpy(p->lastname, tmp, 24); printf("Enter login: "); scanf(" %8s", tmp); strncpy(p->login, tmp, 8); printf("Enter age: "); scanf(" %u", &(p->age)); } void display_person(struct person *p) { printf("****************************************\n"); printf("first name:\t%.24s\n", p->firstname); printf("last name:\t%.24s\n", p->lastname); printf("login:\t\t%.8s\n", p->login); printf("age:\t\t%u\n", p->age); printf("****************************************\n"); }
Notre objectif est de stocker un tableau de person dans un fichier pour le rouvrir à chaque lancement de programme.
Voici ce que vous devez faire:
- le programme prend en paramètre le nom du fichier de sauvegarde
- le fichier contient:
- un entier (que nous nommerons size) au format unsigned (donc 4 octets)
- un tableau de taille size contenant des structures de type struct person
- si le fichier existe, il est chargé dans un espace mémoire de taille size + sizeof (struct person) (un tableau avec une case en plus)
- si le fichier n'existe pas ou est vide on crée un tableau de taille 1
- on affiche le contenu du tableau si celui-ci n'est pas vide
- on ajoute à la fin une nouvelle entrée (avec read_person)
- on écrase le contenu du fichier avec le nouveau tableau (utiliser write(2) et lseek(2))
On notera que write(2) prend un pointeur de type void*, on peut donc lui donner un pointeur sur ce que l'on a envie (ici le tableau) !