2014:03:18:more:files

De wiki-prog
Aller à : navigation, rechercher

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) !