=============================================================================
INFO 1 - Systèmes d’Exploitations - TP1 - Environnement et système de fichier
=============================================================================


Avant-propos : Gestion des erreurs
----------------------------------

Les appels système apparaissent, traditionnellement, soit comme des fonctions de
type `int` renvoyant −1 en cas d’échec, soit des fonctions renvoyant des
pointeurs, NULL étant le cas d’échec. Lors d’un tel échec, il est possible
d’obtenir une information plus précise sur la nature de l’erreur via la variable
(`extern int`) `errno`, définie dans `errno.h` (qui contient la liste des
erreurs susceptibles de provoquer l’écher d’un appel système).

Il est possible, lorsqu’un appel système échoue, de visualiser le message
correspondant à l’erreur rencontrée au moyen de la fonction `void perror (const
char *s)` qui affiche le message associé à l’erreur, précédé dela chaine
pointée par `s` (définie par l’utilisateur·ice), suivie du séparateur `: `.

Exemple :

```c
void main (int argc, char *argv[])
{
	…
	if (open(argv[1], O_RDONLY) == -1) {
		perror("Erreur d’Ouverture");
		exit(1);
	}
	…
}
```

Utiliser la gestion des erreurs dans les programmes à écrire par la suite.


Représentation interne des fichiers
-----------------------------------

1. Écrire un programme (C) qui prend en argument un fichier et renvoie :
   * son numéro d’inode
   * le nombre de liens qu’il possède
   * son propriétaire
   * son groupe
   * sa taille
   * son type
en utilisant la fonction `stat` ou `fstat` (`man 2 stat`).

2. Écrire un programme qui interprète ses deux arguments comme des noms de
   fichiers, et renvoie la valeur :
   −1 si le nombre de paramètres n’est pas correct,
    0 si les deux références correspondent à deux nœuds différents du système de
      gestion de fichiers,
    1 si elles correspondent au même nœud,
    2 si une erreur est rencontrée dans des appels à la primitive `stat`.

3. Écrire un programme qui recopie caractère caractère par caractère le fichier
   donné en argument sur la sortie standard, uniquement si le fichier donné en
   argument est un fichier régulier (en utilisant `fstat`).

4. Écrire un programme qui effectue le travail de la commande `cp` en I/O bas
   niveau.


Variables d’environnement
-------------------------

La forme la plus générale de la fonction principale suit le prototype suivant :

```c
int main(int argc, char *argv[], char **arge);
```

- Le paramètre `argc` est le nombre total de paramètres (invoqué dans la
  commande shell correspondante).
- Le paramètre `argv` est un tableau contenant les différents paramètres de la
  commande. Il contient `argc` pointeurs (plus un `NULL` pour marquer la fin),
  `argv[i]` est le i-ème argument de la commande, `argv[0]` étant le nom de la
  commande.
- Le paramètre `arge` est une liste de pointeurs permettant l’accès à
  *l’environnement* dans lequel le processus s’exécute. Chacun des pointeurs
  permet d’accéder à une chaîne de caractère `clef=valeur`.

### Accès par le paramètre de la fonction principale

```c
#include <stdio.h>

void main(int argc, char *argv[], char **arge)
{
	int i;
	for (i = 0; arge[i]; i++)
		puts(arge[i]);
}
```

5. Tester l’accès aux variables d’environnement selon cette méthode.

### Accès par la variable externe

L’environnement est également accessible au travers de la variable externe
`environ`. Le programme doit contenir la déclaration de cette variable sous la
forme `extern char **environ;`

```c
#include <stdio.h>

void main(int argc, char *argv[], char **arge)
{
	extern **environ;
	char **env;
	env = environ;
	while (*env)
		puts(*env++);
}
```

6. Tester l’accès aux variables d’environnement selon cette méthode.

### Accès par la fonction standard

La fonction `char *getenv(const char *name)` recherche, dans la liste des
variables d’environnement, une chaîne de la forme `clef=valeur` et retourne un
pointeur sur la chaîne `valeur`. Si la variable n’est pas définie, la valeur
retourne est `NULL`.

```c
#include <stdio.h>
#include <stdlib.h>

void main(int argc, char *argv[], char **arge)
{
	char *VAR;
	if ((VAR = getenv("PATH")))
		fprintf(stdout, "Valeur PATH = %s", VAR);
	else
		fprintf(stderr, "VARIABLE PATH NON DEFINIE !");
}
```

7. Tester l’accès aux variables d’environnement selon cette méthode.

### Création ou modification de la valeur d’une variable d’environnement

Via l’appel de la fonction `int putenv(const char *name)`. La chaîne de
caractères donnée en paramètre a la forme `VAR=valeur`. La variable de nom `VAR`
est soit créée si elle n’existait pas déjà, soit modifiée dans l’environnement
du processus. La valeur de retour de la fonction est nulle si ça s’est bien
passé, non-nulle sinon.

```c
#include <stdio.h>
#include <stdlib.h>

void main(int argc, char *argv[], char **arge)
{
	putenv("MYVAR=titi");
	fprintf(stdout, "Vefir MYVAR = %s\n", getenv("MYVAR"));
}
```
8. Tester la création et la modification des variables d’environnement selon
   cette méthode.

Habituellement, un processus hérite de l’environnement du processus parent.
La fonction `putenv` manipule l’environnement pointé par la variable externe
`environ`, elle ne modifie pas l’environnement accessible par `**arge`, elle
permet uniquement de créer ou de modifier l’environnement qui sera passé à un
processus enfant.

L’action effectuée sur une variable d’environnement par un processus n’est
jamais répercuté sur l’environnement du processus parent.

9. Vérifier les affirmations précédentes.


Appels système et gestion des fichiers
--------------------------------------

### Création d’un fichier

10. Écrire un programme qui crée un fichier en lecture-écriture, via l’appel

```c
int creat(const char *pathname, mod_t mode);
```

si le fichier existe déjà, une erreur doit être retournée. Quel est le code
d’erreur retourné lorsque le fichier existe déjà ? Si l’argument `mode` spécifié
est 755, est-ce que le fichier est créé avec exactement ces droits ?

### Obtention des caractéristiques d’un fichier

11. Écrire un programme qui récupère les caractéristiques de fichiers donnés,
    via les appels aux fonctions `stat`, `fstat` et `lstat`. Pour un fichier
    donné, afficher les caractéristiques suivantes :
    - le numéro d’inode,
    - la taille du fichier,
    - le mode,
    - la taille de bloc,
    - le nombre de liens physiques,
    - le nombre de blocs,
    - l’UID du proprio,
    - le *timestamp* de dernier accès,
    - le GID proprio.
    On pourra utiliser la fonction `ctime` pour afficher un *timestamp* de
    manière lisible.

### Ouverture d’un fichier, lecture et écriture dedans

Utilisation de : `int open()`, `ssize_t read()`, `ssize_t write()`.

12. Écrire un programme qui recopie un fichier source dans un fichier
    destinataire. Le programme doit vérifier que le fichier source est un
    fichier régulier (`S_ISREG`, cf. `int stat()`). Le programme doit également
    vérifier qu’il n’existe pas déjà de fichier de même nom que le fichier
    destinataire.
    Tester différentes taille de *buffer* dans `read()` (1B, 1024B) et
    chronométrer le résultat (à l’aide de `/bin/time`).

### Duplication de descripteurs

Utilisation de : `ind dup()`.

13. Écrire un programme qui redirige la sortie d’erreur standard vers un fichier
    préalablement créé. C’est-à-dire que toute écriture de la forme `write(2,
    …)` doit se faire dans ce fichier.
    Identifier la propriété de la fonction `dup()` qui est exploitée pour ainsi
    rediriger I/O standards.

### Positionnement de la tête de lecture-écriture dans un fichier

Utilisation de : `off_t lseek()`.

14. Écrire un programme qui crée un fichier vide. Positionner la tête de
    lecture/écrite sur le 10'000e octet à partir du début du fichier. Écrire un
    caractère à cette position.
    Quelle doit être la taille du fichier ?
    Est-ce cette taille qui est retournée par la commande `ls -l` ?
    Est-ce que les blocs correspondant au trou de 9'999 caractères ont été
    alloués (utiliser `df`) ?
    La primitive `lseek()` permet de déplacer l’*offset* courant d’un fichier et
    retourne le nouvel *offset*.
    Écrire un programme qui, après un certain nombre de lectures/écritures sur
    un fichier, retourne la valeur de l’*offset* courant. L’*offset* est associé
    à un fichier et non pas à un descripteur, si deux descripteurs référencent
    un même fichier, la modification (lecteur/écriture ou `lseek()`) de
    l’*offset* du fichier via un descripteur est “visible” via l’autre.
    Vérifier cette affirmation.

