Exercice

Il faut simplement noter que chaque processus possède un répertoire de travail (ou répertoire courant) utilisé pour résoudre les références relatives. La modification du répertoire courant d'un processus n'affecte pas celui des autres processus. D'autre part un processus ne peut modifier son répertoire de travail que de sa propre initiative (appel à chdir()).
Lors de l'exécution de la commande mon_cd par le shell, celui-ci lance un nouveau processus chargé d'exécuter le code de la commande, c'est donc ce nouveau processus qui change de répertoire (après avoir hérité des propriétés de son père et donc de son répertoire courant). Lors de la terminaison du fils le shell reprend son exécution sans que rien n'ait été modifié en ce qui concerne son propre répertoire de travail.
C'est pourquoi on distingue habituellement deux types de commandes dans les shells : les commandes externes et les commandes internes permettant justement de modifier diverses propriétés du shell lui-même.

Exercice

Le SET_UID bit est utilisé afin de rendre propriétaire effectif d'un processus le propriétaire de l'exécutable.
Le propriétaire effectif d'un processus est celui utilisé par le système pour contrôler les accès aux ressources.
La commande passwd permet à tout utilisateur de modifier son mot de passe, mais sachant que ceux-ci sont tous contenus dans un même fichier (/etc/passwd ou /etc/shadow) il n'est pas possible d'imaginer donner le droit d'écriture sur ces fichiers a tous les utilisateurs (sinon ceux-ci pourraient modifier le mot de passe de quelqu'un d'autre). La solution est donc de n'autoriser qu'un utilisateur particulier (généralement root) à écrire dans le fichier /etc/passwd (ou /etc/shadow) et de rendre cet utilisateur propriétaire de l'exécutable tout en positionnant le SET_UID bit. Ainsi, le système utilisera les droits d'accès relatifs à root (autorisé à écrire) lors de l'exécution de la commande.

Exercice

1.
On suppose que tous les appels fonctionnent correctement :
2.
on peut remarquer qu'après avoir créé ses n fils un processus attend leur terminaison (seconde boucle for). La terminaison s'effectue donc en remontant des feuilles jusqu'à la racine de l'arbre en respectant la propriété précédemment indiquée.
3.
L'application retourne EXIT_FAILURE lorsque au moins l'une des conditions suivantes est vérifiée :

Problème

1.
void ajoute_commande(int *n,struct commande **tc,struct commande *c) {
  int i, j;

  /* On alloue de la place pour une commande supplémentaire */
  (*n)++;
  *tc = realloc(*tc,*n * sizeof(struct commande));

  /* On recherche la position de la nouvelle commande */
  for (i=0; i<(*n)-1 && (*tc)[i].quand<=c->quand; i++);

  /* On décale les commandes suivantes */
  for (j=(*n); j>i; j--)
    (*tc)[j] = (*tc)[j-1];

  /* On insère la nouvelle commande à sa place */
  (*tc)[i] = *c;
}

void enleve_premier(int *n,struct commande **tc) {
  int i;

  /* On libère la mémoire utilisée par la première commande */
  for (i=0; i<(*tc)[0].argc; i++)
    free((*tc)[0].argv[i]);
  free((*tc)[0].argv);

  /* On décale les élements d'indice 1 à n-1 en 0 à n-2 */
  for (i=1; i<*n; i++) (*tc)[i-1] = (*tc)[i];

  /* On libère la place inutilement occupée */
  *tc = realloc(*tc, *n * sizeof(struct commande));
  (*n)--;
}
2.
void lance(char *argv[]) {
  switch(fork()) {
  case -1:
    fprintf(stderr,"fork() raté\n");
    break;
  case 0:
    execvp(argv[0],argv);
    fprintf(stderr,"exec() raté\n");
    exit(EXIT_FAILURE);
  }
}
3.
struct entete_requete_tube {
  size_t taille;
};

struct requete_tube {
  time_t quand;
  int argc;
};

void cree_requete(int *tr,int quand,char **buffer,int argc,char *argv[]) {
  struct entete_requete_tube *ert;
  struct requete_tube *rt;
  int i;

  *tr = sizeof(struct entete_requete_tube)+sizeof(struct requete_tube);
  *buffer = malloc(*tr);
  for (i=0; i<argc; i++) {
    *buffer = realloc(*buffer,*tr+strlen(argv[i])+1);
    strcpy((*buffer)+*tr,argv[i]);
    *tr += strlen(argv[i])+1;
  }
  ert = (struct entete_requete_tube *)(*buffer);
  rt = (struct requete_tube *)(ert+1);
  ert->taille = *tr-sizeof(struct entete_requete_tube);
  rt->quand = quand;
  rt->argc = argc;
}
4.
int main(int argc,char *argv[]) {
  int tube;
  char *buffer;
  int quand;
  size_t taille;

  if (argc<3) {
    fprintf(stderr,"Il manque des arguments\n");
    exit(EXIT_FAILURE);
  }
  quand = -1;
  sscanf(argv[1],"%d",&quand);
  if (quand<1) {
    fprintf(stderr,"%d valeur impossible\n",quand);
    exit(EXIT_FAILURE);
  }
  tube = open(TUBE_SERVEUR,O_WRONLY);
  if (tube==-1) {
    fprintf(stderr,"Erreur ouverture de %s\n",TUBE_SERVEUR);
    exit(EXIT_FAILURE);
  }
  cree_requete(&taille,quand,&buffer,argc-2,argv+2);
  if (write(tube,buffer,taille)!=taille) {
    fprintf(stderr,"Probleme d'ecriture\n");
    free(buffer);
    close(tube);
    exit(EXIT_FAILURE);
  }
  free(buffer);
  close(tube);
  exit(EXIT_SUCCESS);
}
5.
void a_faire(int signal) {
  int i;
  time_t reference;

  /* On exécute toutes les commandes qui devaient l'être */
  reference = liste_commandes[0].quand;
  while (n_requetes && liste_commandes[0].quand==reference) {
    lance(liste_commandes[0].argv);
    enleve_premier(&n_requetes,&liste_commandes);
  }

  /* On modifie le délai des requêtes suivantes car une partie
     du délai a été consommé */
  for (i=0; i<n_requetes; i++) {
    liste_commandes[i].quand -= reference;
  }

  /* S'il reste des requêtes on enregistre une alarme */
  if (n_requetes) alarm(liste_commandes[0].quand);
}

int main(int argc,char *argv[]) {
  int tube, i;
  ssize_t lu;
  struct entete_requete_tube ert;
  struct requete_tube *rt;
  char *argv_tube;
  char *buffer;
  struct sigaction action;
  unsigned int reste;
  struct commande c;

  if ( access(TUBE_SERVEUR,F_OK)==0 ) {
    fprintf(stderr,"Le tube %s existe deja. Y a t'il un serveur ?\n",
	    TUBE_SERVEUR);
    exit(EXIT_FAILURE);
  }
  if ( mkfifo(TUBE_SERVEUR,S_IRUSR|S_IWUSR)!=0 ) {
    fprintf(stderr,"Impossible de créer %s\n",TUBE_SERVEUR);
    exit(EXIT_FAILURE);
  }
  tube = open(TUBE_SERVEUR,O_RDONLY);
  if ( tube==-1 ) {
    fprintf(stderr,"Impossible d'ouvrir %s\n",TUBE_SERVEUR);
    unlink(TUBE_SERVEUR);
    exit(EXIT_FAILURE);
  }

  buffer = NULL;
  liste_commandes = NULL;
  n_requetes = 0;

  /* On enregistre l'action à réaliser à l'épuisement d'une alarme */    
  action.sa_handler = a_faire;
  sigemptyset(&(action.sa_mask));
  action.sa_flags = 0;
  sigaction(SIGALRM,&action,NULL);

  /* Ce pauvre serveur est condammé */
  while (1) {
    /* On tente de lire une requête (sa longueur) */
    do {
      lu = read(tube,&ert,sizeof(struct entete_requete_tube));
    } while (lu==-1 && errno==EINTR);
    /* S'il n'y a plus d'écrivain on resynchronize sur l'ouverture */
    if (lu==0) {
      close(tube);
      do {
	tube = open(TUBE_SERVEUR,O_RDONLY);
      } while (tube==-1 && errno==EINTR);
      if ( tube==-1 ) {
	fprintf(stderr,"Impossible d'ouvrir %s dans la boucle\n",TUBE_SERVEUR);
	unlink(TUBE_SERVEUR);
	exit(EXIT_FAILURE);
      }
      continue;
    }

    if (lu!=sizeof(struct entete_requete_tube)) {
      fprintf(stderr,"Erreur de lecture entete\n");
      break;
    }

    /* On annule l'alarme précédente */
    reste = alarm(0);

    /* On met à jour les délais */
    for (i=0; i<n_requetes; i++) {
      liste_commandes[i].quand -= liste_commandes[0].quand-reste;
    }

    /* On lit le reste */
    buffer = malloc(ert.taille);
    if ( buffer== NULL ) {
      fprintf(stderr,"Erreur d'allocation buffer\n");
      break;
    }
    if ( read(tube,buffer,ert.taille)!=ert.taille ) {
      fprintf(stderr,"Erreur de lecture des donnees %d\n",ert.taille);
      break;
    }

    /* On décode la requête pour fabriquer la structure adéquate */
    rt = (struct requete_tube *)buffer;
    buffer += sizeof(struct requete_tube);
    c.quand = rt->quand;
    c.argc = rt->argc;
    c.argv = malloc((c.argc+1)*sizeof(char *));
    if ( c.argv==NULL ) {
      fprintf(stderr,"Erreur allocation argv\n");
      break;
    }
    for (i=0; i<c.argc; i++) {
      c.argv[i] = malloc(strlen(buffer)+1);
      strcpy(c.argv[i],buffer);
      buffer += strlen(buffer)+1;
    }
    free(rt);
    c.argv[c.argc] = NULL;

    /* Une commande de plus à faire */
    ajoute_commande(&n_requetes,&liste_commandes,&c);

    /* On enregistre la nouvelle alarme */
    alarm(liste_commandes[0].quand);
  }
  fprintf(stderr,"Échec\n");
  close(tube);
  unlink(TUBE_SERVEUR);
  exit(EXIT_FAILURE);
}