All posts by yunes

Installation d’un environnement de travail avec Docker

Avant toute chose, des exemples de commandes sont données dans ce tutoriel. Afin de mieux identifier l’environnement dans lequel on se trouve, des invites (prompts) ont été utilisées.

Par exemple si on lit :

hôte> echo "toto"

Il faut ignorer la partie à gauche hôte>, on trouvera dans la suite l’invite container> qui est à ignorer de la même façon. La commande à taper est donc simple echo "toto".

Pourquoi ?

On a parfois la nécessité de travailler avec certaines versions de logiciels et de bibliothèques et de faire cohabiter ces différentes versions sur une même machine. Selon les logiciels, la recette est plus ou moins aisée. Pour Python par exemple, on utilise souvent conda pour confiner les bibliothèques. Pour des compilateurs, etc. c’est souvent plus délicat…

Docker une solution ?

Docker est un logiciel de conteneurisation. C’est-à-dire fournit à un ensemble logiciel le moyen de s’isoler proprement du reste de la machine hôte et donc libère des manipulations plus ou moins complexes, voire quasi-impossibles, de réaliser cela par des moyens traditionnels (jeu sur le nommage des fichiers et des commandes, utilisation abusive de variables d’environnement, etc.). Un conteneur fonctionne comme une machine autonome, correctement isolée de la machine qui l’accueille. Un conteneur est comme une machine sur laquelle on aurait installé que les logiciels nécessaires pour une tâche donnée.

Installation d’un environnement

Dans la suite, et pour notre exemple, on installera un «Docker» pour exécuter du code Python3.9 utilisant la bibliothèque spaCy sous Ubuntu 20.04.

Préliminaires

Il faut tout d’abord installer sur sa machine hôte le logiciel Docker, le logiciel de conteneurisation. En général, la procédure d’installation permet et encourage le test du logiciel; il vaut mieux s’y conformer.

MacOS

Le lien de téléchargement et la procédure d’installation sont ici. Le logiciel correspondant est Docker Desktop.

Windows

Le lien de téléchargement et la procédure d’installation sont ici. Le logiciel correspondant est Docker Desktop.

Linux

Le lien de téléchargement et la procédure d’installation sont ici. Le logiciel correspondant est Docker Engine. L’installation est plus brute, elle nécessite d’employer le terminal et une série d’apt install. Pour info, apt est une suite logicielle de gestion de packages. Un package est un ensemble de fichiers constituant la distribution du logiciel correspondant, il s’agit souvent de commandes ou de bibliothèques. La difficulté est liée à la gestion des dépendances que les logiciels ont entre eux et le système (version compatibles, etc).

Préparation

Installer un Docker, ou plutôt installer une image Docker consiste en la fabrication de l’image d’un système tel que le voudrait. C’est-à-dire quel système, quelles suites logicielles, etc. La définition de cette image passe par un fichier de description. Un simple fichier texte contenant des directives. Habituellement, ce fichier est nommé Dockerfile mais peut porter un nom quelconque.

Description de l’image : le Dockerfile

Voici un «Dockerfile» :

FROM ubuntu:focal

RUN apt update
RUN apt upgrade
RUN apt -y install python3.9
RUN apt -y install python3-pip
RUN python3.9 -m pip install spacy
RUN python3.9 -m spacy download en_core_web_sm

Celui-ci est assez simple. Il est constitué de deux parties.

FROM

La première (ligne 1) permet de spécifier le système d’exploitation qui sera utilisé pour l’image Docker. Une image Docker représentant une suite logicielle dans un système particulier, il est nécessaire de le spécifier.

Ici nous avons donc choisi de construire l’image à partir d’Ubuntu 20.04 dont le petit nom est Focal Fossa. C’est une distribution Linux très répandue au moment de l’écriture de ce tutoriel. Ce n’est pas la plus récente mais c’est la dernière dite LTS, Long Time Support, c’est-à-dire pour laquelle la stabilité, la qualité, etc sont assurés pour une longue période (5 ans pour celle-ci).

RUN

La seconde est une suite de directive RUN qui permettent d’exécuter des commandes à l’intérieur de ce système de base une fois que l’image de base a été chargée (il existe un dépôt central de nombreuses images Docker, le Docker Repository). C’est comme si on installait le système «nu» minimal et qu’on y installait ensuite des logiciels.

La première et la seconde (ligne 3 et 4) permettent donc mettre à jour le gestionnaire de paquets. Celui-ci étant lui-même un logiciel il est plus prudent de le mettre à jour. Donc on met à jour le gestionnaire (update) puis on demande de mettre à jour les paquets déjà installés (upgrade).

La troisième (ligne 5) permet d’installer python3.9 ainsi que tout ce qui est nécessaire à son fonctionnement. C’est-à-dire d’autres paquets (toutes les dépendances). Ils peuvent être assez nombreux. La commande apt est normalement interactive, c’est-à-dire qu’elle converse avec l’utilisateur en l’interrogeant de temps en temps sur le bien fondé d’une opération (êtes-vous sûr ? etc.). Évidemment, puisqu’il s’agit ici d’automatiser le processus, il n’est pas raisonnablement envisageable d’interroger l’utilisateur durant la procédure. C’est pourquoi l’option -y est utilisée, celle-ci permet de répondre automatiquement yes à toutes les questions qui pourrait être posées.

La quatrième (ligne 6) permet d’installer pip, le gestionnaire de bibliothèques Python. Oui Python possède son propre gestionnaire de bibliothèques (Python donc). Puisque nous installons python3.x, il s’agira de pip3.

La cinquième (ligne 7) permet d’installer dans l’environnement Python3.9 la bibliothèque spaCy (ici aucune version n’est précisée mais la dernière le sera (une 3.x aujourd’hui).

Et enfin la sixième directive (ligne 8) qui permet d’installer dans spaCy le pipeline de calcul pour la langue anglaise.

Installation de l’image

Pour créer l’image et l’installer dans l’environnement Docker, il faut utiliser une commande comme celle-ci :

hôte> docker build -t focpyt3spacyen -f Dockerfile .

docker est le nom de la commande à invoquer pour réaliser une opération «sous Docker».
build est la paramètre qui permet de construire une image.
L’option -t et la valeur associée focpyt3spacyen, permet de spécifier le nom que l’on souhaite donner à cette image. En effet, on peut disposer d’autant d’images que nécessaire (un avec python3, une autre avec perl, etc). On a choisi ici un nom un peu cabalistique, mais peu importe, n’importe quel nom est utilisable.
L’option -f et sa valeur associée Dockerfile n’est pas strictement obligatoire. Elle est donnée pour l’exemple. Elle permet de spécifier le nom du fichier décrivant la construction à réaliser. Si l’on ne positionne pas cette option le fichier Dockerfile sera utilisé. C’est donc clairement redondant ici.
Enfin le paramètre . sert à spécifier le répertoire dans lequel le fichier de description de l’image est présent. . est une valeur permettant de représenter le répertoire courant, celui dans lequel on est placé au moment où l’on lance la commande, mais n’importe quelle valeur est acceptable.

Cette commande prend un certain temps car il est alors bien souvent nécessaire de télécharger depuis divers sites les logiciels : le dépôt des images de base comme Ubuntu, les paquets Linux, les bibliothèques Python, etc. De plus, l’installation dans l’image requiert un certain temps. En général cela produit un résultat très verbeux. Y sont décrites toutes les étapes essentielles et les décisions prises lors de la construction.

Si la construction réussit une image de nom indiqué est créée, ce que l’on peut vérifier avec la commande suivante :

hôte> docker images

qui produit alors un résultat comme :

REPOSITORY       TAG       IMAGE ID       CREATED          SIZE
 focpyt3spacyen   latest    b9c8a941fb42   16 minutes ago   808MB
 ubuntu           focal     7e0aa2d69a15   7 days ago       72.7MB
 wordpress        latest    aeb0c3e39096   7 weeks ago      551MB
 mysql            5.7       d54bd1054823   2 months ago     449MB

On observe ici que l’environnement Docker contient 4 images. La première étant celle que nous venons de construire : focpyt3spacyen. La troisième colonne est l’identification Docker de cette image, ce par quoi l’image est identifiée de façon unique. En effet le nom sous la forme de caractères n’est qu’un raccourci plus facile à manipuler par les humains.

À partir de là nous avons enfin de quoi travailler…

Tester si l’image fonctionne

Une image Docker n’est juste rien de plus qu’une machine prête à être employée. Pour l’utiliser il faut exécuter cette image, un peu comme démarrer l’ordinateur qui en serait doté. Une telle exécution est un container. Il faut immédiatement noter que ‘on peut exécuter plusieurs container éventuellement en même temps de la même image.

La commande de base est docker run, que l’on doit éventuellement agrémenter de divers paramètres. Un premier test serait :

hôte> docker run focpyt3spacyen date

Le troisième argument est le nom de l’image, ici (focpy3spacyen) on peut utiliser le nom en lettre ou l’identifiant unique.

Le quatrième paramètre et les suivants constituent la commande à exécuter à l’intérieur du container. Ici nous nous contentons de demander la date courante dans le container.

Une commande à exécuter et qui comporterait plusieurs arguments pourrait être :

hôte> docker run focpyt3spacyen date --date='TZ="America/Los_Angeles"'

Ici, donc, à partir du quatrième argument (date --date='TZ="America/Los_Angeles"'), tout est transmis pour exécution au container qui l’interprètera comme si on avait la machine correspondante sous la main et qu’on entrait cette commande au clavier. Ici l’obtention de la date et heure courante dans le fuseau horaire de Los Angeles.

Tester le mode interactif

Il faut noter que les tests précédents consistent en l’exécution de chaque commande dans un nouveau container. C’est-à-dire qu’une fois l’exécution terminée, le container disparaît. Attention ceci a une conséquence très importante : les modifications du système qui auraient été apportées dans une exécution ne sont pas rapportées dans l’image!!! L’image est un point de départ stable. Il est possible d’obtenir une image à partir d’une exécution modifiée mais cela dépasse l’intention initiale de ce tutoriel.

On peut observer la trace de ces containers qui ont terminé à l’aide de la commande :

hôte> docker ps --filter "status=exited"

La commande docker ps permet d’obtenir la liste des exécutions en cours ou terminées de containers ainsis que des informations techniques associées. Ici on applique un filtre permettant de faire apparaître que les exécutions passées. On obtient un résultat comme :

On notera que les containers sont aussi identifiés (afin de les contrôler, par exemple arrêter une exécution sans fin, etc), que l’image utilisée pour l’exécution est présente ainsi que la commande qui a été utilisée pour l’exécution interne, qu’on a une indication le moment de l’exécution, etc. La dernière colonne est un nom attribué automatiquement par docker pour identifier le container (à la manière des images qui sont identifiées par un numéro et un nom). On peut choisir soi-même le nom du container, mais ce n’est pas franchement utile à tout coup.

Pour exécuter une commande interactive il faut y ajouter un argument. Par exemple pour exécuter un shell (bash) dans le container et en obtenir le contrôle normal, on peut utiliser la commande :

hôte> docker run -it focpyt3spacyen bash

Dans ce cas on obtient en général un nouveau prompt (invitation à taper quelque chose), en effet le shell s’exécute dans un nouveau container et on en a le contrôle clavier :

La première ligne est interprétée par la machine standard et demande l’exécution interactive du shell bash dans le container obtenu. Celui-ci affiche alors un prompt qui lui est propre (root@c8a5eb7a5c48:/#) et on est invité à interagir avec lui. À partir de là et jusqu’à ce que ce shell soit terminé, les commandes sont exécutées dans le container! Essayons d’exécuter notre installation de python3 :

container> python3.9

et l’on obtient bien le prompt de python :

Sortons de python par emploi de exit() puis du shell par exit. Le container termine et nous revenons sur la machine hôte.

Exécution d’un script

Il n’y a pas de difficulté particulière à exécuter un script, pourvu que l’image contienne les suites logicielles nécessaires. La difficulté réside dans l’injection du script à l’intérieur d’une image. En effet, les containers sont sévèrement confinés de sorte qu’ils ne voient pas la machine hôte, donc certainement pas ses fichiers, etc.

Heureusement Docker permet d’ouvrir les portes souhaitées, comme par exemple faire apparaître dans un container une partie des fichiers du système hôte.

Pour nous simplifier imaginons que l’on souhaite exécuter un script python qui lit une ligne dans un fichier de données et l’affiche à l’écran.

Voici le script python de nom myscript.py :

#!/usr/bin/env python3.9
print("Mon super script")
fo = open("foo.txt", "r")
print(fo.readline())
fo.close()

Puis le contenu du fichier foo.txt :

bonjour
au revoir

Pour (encore) simplifier l’exemple, on placera des ceux objets (le script et le fichier de données) dans un même répertoire, nommé test et pour l’exemple de nom complet /home/user1/workspace/test. De la sorte on va pouvoir faire apparaître le contenu du répertoire du système hôte (ici /home/user1/workspace/test) comme un répertoire dans un container :

hôte> docker run -it -v /home/user1/workspace/test:/toto focpyt3spacyen bash

La nouveauté sont les arguments -v /home/user1/workspace/test:/toto.
-v permet d’indiquer que l’on souhaite créer un volume (un système de fichier partagé entre l’hôte et le container).
Sa valeur /home/user1/workspace/test:/toto permet d’obtenir sous le nom /toto dans le container le contenu du répertoire /home/user1/workspace/test. Attention, c’est bien un partage pas une copie! Toute modification d’un côté où de l’autre sera visible de l’autre partie.

Essayons de voir si l’export fonctionne :

Oui, les fichiers du système hôte que l’on a partagés sont bien visibles!

Testons donc l’exécution du script dans le mode interactif du container :

C’est gagné! Quittons l’exécution du shell par exit, cela termine le container.

Pourvu que l’on respecte dans l’esprit l’environnement de cet exemple, il peut alors se construire facilement des tas d’images diverses afin d’exécuter du code dans des environnements particuliers.

TP iOS «gallerie»

En partant du template pour iOS Master/Detail, écrire une application permettant d’exploiter les données du fichier XML : images.xml.

Le fichier XML devra être chargé dynamiquement par l’application (il ne devra pas être contenu dans le déploiement).

La vue principale devra faire apparaître le nom des rubriques (niveau <category>) et dans chaque catégorie les items via leur titre (<title>) et leur icône (<thumbnail>) s’il elle existe.

En sélectionnant un item l’application ouvrira une vue de détail faisant apparaître :

  • le titre (<title>),
  • l’image grand format (dont l’url est donnée dans <image>) et,
  • un éventuel lien qui permettra d’accéder à la page web indiquée (si le lien existe sous le nom <weblink>).

Indications : le parsing du XML s’effectuera à l’aide de NSXMLParser, qui est un parser SAX.

  • commencer par créer un type de données pour représenter les items,
  • créer «en dur dans le code» quelques items avec les bonnes valeurs,
  • faire en sorte que le Master-Detail fonctionne avec ces données (on peut oublier la gestion de l’icône),
  • ensuite rajouter la fonctionnalité, dans la vue de détail, permettant en cliquant d’aller sur le la page web indiquée,
  • ensuite rajouter le parsing du XML,
  • ensuite rajouter la présentation de l’icône dans la vue maître.

Voilà à quoi cela pourrait ressembler :

TP MacOS «StarWars»

Écrire à l’aide de SpriteKit un jeu dans lequel :

  • le joueur se déplace verticalement mais sur une verticale située à gauche de l’écran
  • les aliens apparaissent au fur et à mesure par intervalles tirés au hasard, à droite à une position tirée au hasard et avec une vitesse variable selon les cas…
  • si le joueur touche un alien, son score est incrémenté de 1

On pourra y ajouter ce qu’il plaira selon les envies… Par exemple d’autres «aliens» qu’il faut éviter sous peine de mort immédiate, d’autres «aliens» qui lancent des balles qui peuvent détruire le joueur…

Pour vous aider :

  • l’appel à intervalles réguliers d’une méthode sur un objet peut-être obtenu à l’aide de la classe Timer (du framework Foundation) et en particulier de la factory init(timeInterval: TimerInterval, target: Any, selector: Selector, userInfo: Any?, repeats: Bool)

TTTFS — The Tiny Toy File System

Projet de programmation C et programmation Systèmes

Bas niveau

Cette couche logicielle a pour but de simuler la couche d’accès de bas niveau à un dispositif de stockage permanent et de masse (type «disque dur»).

Un «disque dur» sera représenté par un fichier ordinaire sur le système hôte. Le nom de ce fichier sera par défaut disk.tfs, mais pourra être tout autre nom approprié que l’utilisateur pourrait souhaiter. Pour cette couche, le contenu du fichier sera vu comme une collection ordonnée de N blocs contenant chacun 1024 octets. La taille N sera déterminée à la construction du disque. Aucune information particulière ne doit être inscrite dans les blocs du disque dur (peut importe), sauf dans le premier bloc (bloc de numéro 0).

Il est demandé de produire une bibliothèque de fonctions permettant de manipuler ce disque. Certaines de ces fonctions seront considérées comme des fonctions internes (non visibles des couches supérieures) :

  • error read_physical_block(disk_id id,block b,uint32_t num);, qui lit depuis le disque d’identité donnée (voir plus bas), le bloc de numéro indiqué;
  • error write_physical_block(disk_id id,block b,uint32_t num);, qui écrit sur le disque le bloc de numéro donné.

Les autres fonctions seront considérées comme faisant partie de l’API de manipulation du disque (dont les prototypes devront nécessairement aparaître par inclusion du fichier ll.h) :

  • error start_disk(char *name,disk_id *id); qui permet de manipuler un disque en lui associant une identité dynamique;
  • error read_block(disk_id id,block b,uint32_t num); qui permet de lire un bloc sur le disque (lire annexe sur la raison de différencier cette fonction et la fonction read_physical_block);
  • error write_block(disk_id id,block b,uint32_t num); qui permet d’écrire un bloc sur le disque (même remarque que la fonction précédente);
  • error sync_disk(disk_id id); (voir annexe – en première approximation cette fonction peut ne rien faire du tout;
  • error stop_disk(disk_id id) qui permet de terminer une session de travail sur un disque.

À l’aide de cette API, il est demandé d’écrire la commande tfs_create -s size [name] permettant de créer un fichier qui contiendra les données du disque de nom name (par défault disk.tfs) et où size est le nombre de blocs qui constitueront le disque. Cette commande aura pour effet de créer un fichier sur le système hôte de la taille adéquate et contenant les informations suivantes sur le premier bloc :

  • avant toute chose les nombres sont toujours sur 32 bits et stockés sur le disque en little-endian et toujours alignés sur 4-octets;
  • le premier nombre du bloc 0 sera le nombre total de blocs du disque.

À l’aide de cette API, il est démandé d’écrire la commande tfs_partition -p size [-p size]... [name] permettant de partitionner l’espace physique du disque en paquets de blocs. Cette partition consiste à :

  • écrire le nombre de partition en seconde position du bloc 0 (seconde position signifie à partir de l’octet 4…)
  • écrire pour chaque partition sa taille, dans l’ordre et à la suite.

À l’aide de cette API, il est demandé d’écrire une commande tfs_analyze [name] qui permet d’obtenir les informations d’un disque : sa taille, son nombre de partitions, la taille de chaque partition, etc.

La bibliothèque devra être accompagnée et les commandes devraont être accompagnés des fichiers d’entêtes adéquats ainsi que d’un petit manuel.

Un makefile devra être produit permettant de nettoyer l’arborescence (make clean) et de créer la bibliothèque dynamique (libll.so) ainsi que les exécutables de différentes commandes.

Un exemple de disque physique : 

TTTFS Volume

Attention: tout ce qui est désigné comme nombre entier dans la suite et qui est destiné à être stocké sur le périphérique doit être codé sur 32 bits en little-endian et calés sur 4 octets.

Le système de fichier est construit par dessus le disque physique par construction d’algorithmes qui manipulent des données structurées accessibles depuis/vers ce disque. On notera que la seule communication de base possible avec le disque est la lecture ou l’écriture de bloc (voir API bas niveau).

Attention: un volume doit être auto-contenu, ce qui signifie que tous les références désignent des choses à l’intérieur du volume lui-même. Par exemple, lorsqu’on parle d’un numéro de bloc, il s’agit d’un numéro de bloc de la partition (pas d’un numéro de bloc physique).

Un système de Fichier TTTFS ou volume TTTFS occupe une partition entière d’un disque dur et impose sa propre structure sur l’usage des blocs :

  1. le bloc de description (premier bloc de la partition) — TTTFS Description Block
  2. la table des fichiers (quelques blocs suivants) — TTTFS File Table
  3. les blocs de données (le reste) – TTTFS Data Blocks

Un volume TTTFS contient des structures logiques :

  • la liste des fichiers libres (voir plus loin TTTFS File Table),
  • la liste des blocs libres (voir plus loin TTTFS Free Blocks Chain).

TTTFS Description Block

Le bloc de description d’un volume TTTFS est constitué de nombres (32 bits écrits en little-endian et toujours sur des frontières de 4-octets). Il contient les informations suivantes consécutivement et dans l’ordre :

  • L’identification de la version TTTFS, le TTTFS_MAGIC_NUMBER qui est égal à 0x31534654,
  • La taille d’un block TTTFS mesuré en octets, TTTFS_VOLUME_BLOCK_SIZE, pour la version 1 de TTTFS c’est égal à 1024,
  • Le nombre total de blocs du volume, TTTFS_VOLUME_BLOCK_COUNT, pour TTFS version c’est la taille de la partition,
  • Le nombre actuel de blocs libres du volume, TTTFS_VOLUME_FREE_BLOCK_COUNT,
  • Le numéro du premier block libre du volume, TTTFS_VOLUME_FIRST_FREE_BLOCK,
  • Le nombre total de fichiers supportables par ce volume, TTTFS_VOLUME_MAX_FILE_COUNT,
  • Le nombre de fichiers actuellement libres dans ce volume, TTTFS_VOLUME_FREE_FILE_COUNT,
  • Le numéro du premier fichier libre du volume, TTTFS_VOLUME_FIRST_FREE_FILE,

Certaines de ces valeurs sont à déterminer en fonction des paramètres utilisés par le formatage TTFS (voir plus loin).

Ce bloc est toujours considéré comme occupé, il ne peut être utilisé pour autre chose.

Attention, tous les nombres sont représentés en little-endian et sur 32 bits et calés sur des frontières de 4 octets…

TTTFS File Table

Le table des fichiers d’un volume TTFS est constitué d’une table d’entrée de fichiers contenus dans un ensemble de blocs consécutifs du volume et situés à la suite du bloc de description. Le nombre d’entrée de cette table est contenu dans le bloc de description (TTTFS_VOLUME_MAX_FILE_COUNT). Les blocs qui contiennent cette table doivent toujours être considérés comme occupés, il ne peuvent servir à autre chose.

Une entrée de la table des fichiers est structurée de la manière suivante :

  • La taille du fichier, tfs_size, exprimée en octets
  • Le type du fichier, tfs_type, qui peut prendre les valeurs suivantes : TFS_REGULAR=0, TFS_DIRECTORY=1, TFS_PSEUDO=2
  • Le sous-type du fichier, tfs_subtype qui n’est utilisé que pour le type TFS_PSEUDO et qui dans la version 1 peut prendre les valeurs TFS_DATE=0, TFS_DISK=1
  • 10 numéros de blocs, le tableau tfs_direct, qui peuvent être utilisés pour retrouver des données du fichier.
  • 1 numéro de bloc, tfs_indirect1, qui contiendra des numéros de blocs qui contiennent des données du fichier.
  • 1 numéro de bloc, tfs_indirect2, qui contiendra des numéros de lbocs qui contiennent des numéros de blocs qui contiennent des données du fichier.
  • 1 numéro de fichier, tfs_next_free, utilisé pour le chaînage libre.

Il existe deux logiquement deux types d’entrées :

  • les entrées occupées, pour lesquelles tfs_next_free ne correspond à rien et les autres informations doivent être telles qu’elles puissent permettre de manipuler le contenu d’un fichier. Les numéros de bloc, directs ou indirects désignent les blocs de données du fichier.
  • les entrées libres, dont le nombre est contenu dans le bloc de description (TTTFS_VOLUME_FREE_FILE_COUNT) et la première d’entre elles à pour numéro celui contenu dans le bloc de description (TTTFS_VOLUME_FIRST_FREE_FILE). Les entrées-libres sont chaînées les unes aux autres, en utilisant le champ tfs_next_free. La valeur du champ correspondant pour l’une au numéro de l’entrée libre qui la suit logiquement. La dernière entrée de la liste aura pour suivante son propre numéro. Ainsi si la liste des entrées libres est (dans l’ordre) 13, 38, 47, 8. Le chaînage sera next(13)=38, next(38)=47, next(47)=8 et next(8)=8. TTTFS_VOLUME_FIRST_FREE_FILE devra être égal à 0 s’il n’y a plus d’entrées libres.

TTTFS Data blocks & Free Blocks Chain

Les blocs de cette zone du volume contiennent :

  • des blocs occupés par des données de fichiers, les seuls fichiers qui possèdent des données sont les fichiers orinaires (type TFS_REGULAR) et les répertoires (type TFS_DIRECTORY) (voir plus loin).
  • des blocs occupés par des blocs d’indirection, ceux-ci contiennent des numéros de blocs de données ou des blocs d’indirection de niveau supérieur.
  • des blocs libres. Ceux-ci sont chaînés les uns aux autres à la manière des entrées libres de la table des fichiers. Le premier de cette liste a pour numéro celui indiqué par le champ TTTFS_VOLUME_FIRST_FREE_BLOCK du bloc de description du volume. Ensuite les blocs sont chaînés les uns aux autres. Le numéro du bloc libre suivant est enregistré comme le dernier entier du boc précédent. Le dernier a pour suivant lui-même.
TFS Directories

Un répertoire est un fichier contenant des entrées de répertoire. Chaque entrée est constituée de deux données :

  • un numéro de fichier
  • une suite de 28 caractères constituant une chaîne de caractère à la C c’est-à-dire nécessairement terminée par ASCII-0.

Un répertoire dit vide contient deux entrées : l’une de nom “.” et désignant lui-même et l’autre nommée “..” désignant son répertoire parent. La racine du système de fichier est le fichier de numéro 0, ne peut être supprimé et est son propre parent. Pour des raisons pratiques, il est autorisé d’avoir des entrées de répertoire vides, c’est à dire ne correspondant à rien, dont le nom est la chaîne vide (réduite au seul caractère nul).

Un exemple de partition : 

TFS Special Files

Les fichiers de type pseudo, sont particulier puisqu’il n’ont pas vraiment de contenu. Leur contenu est virtuel, c’est-à-dire dynamiquement déterminé.

Pour le type TFS_DATE, un accès à ce fichier permet d’obtenir la date courante exprimée sous la forme d’un entier exprimant le nombre de secondes écoulées depuis le démarrage du disque.

Pour le type TFS_DISK, les accès correspondent aux données brutes du volume courant.

TFS Pathnames

Les chemins manipulables par l’API TFS ont tous la forme :

FILE://disk/volume/rep1/rep2/name où :

  • disk désigne le disque contenant un ou plusieurs volumes TFS. Lorsque disk est égal à “HOST”, les accès sont réalisés directement sur le système de fichiers hôte (l’Unix sous-jacent).
  • volume désigne le volume TFS sur le disque (0, 1, etc.)
  • la suite constitue le chemin pour se déplacer depuis la racine vers le fichier concerné, les “/” sont des séparateurs.

TODO

Il est demandé d’implémenter une commande tfs_format -p partition -mf file_count [disk] permettant de créer un système de fichier minimal sur la partition donnée du disk donné; système de fichiers ayant pour nombre maximal de fichiers le file_count` donné et contenant un répertoire racine vide.

Il est demandé d’implémenter une bibliothèque de fonctions diverses et variées utiles permettant de (liste non-exhaustive) réaliser des opérations de bas-niveau sur un volume donné :

  • mettre un bloc dans la liste des blocs libres
  • supprimer un bloc de la liste des blocs libres
  • mettre une entrée de répertoire dans la liste des entrées libres
  • supprimer une entrée des entrées libres
  • ajouter un bloc dans la liste des blocs d’un fichier
  • supprimer un bloc dans la liste des blocs d’un fichier
  • découpage d’un chemin par itération
  • libérer les blocs de données d’un fichier ou répertoire
  • etc.

Ces fonctions devraient permettre de créer une API permettant de manipuler le système de fichier de façon standard :

  • int tfs_mkdir(const char *path, mode_t mode);
  • int tfs_rmdir(const char *path);
  • int tfs_rename(const char *old, const char *new);
  • int tfs_open(const char *name,int oflag, ...);
  • ssize_t tfs_read(int fildes,void *buf,size_t nbytes);
  • ssize_t tfs_write(int fildes,void *buf,size_t nbytes);
  • int tfs_close(fildes);
  • off_t tfs_lseek(int fildes,off_t offset,int whence);
  • DIR *opendir(const char *filename);
  • struct dirent *readdir(DIR *dirp);
  • void rewinddir(DIR *dirp);
  • int closedir(DIR *dirp);

Ces fonctions devront se comporter comme les appels systèmes Unix usuels (même valeurs possibles, même comportement, etc). La différence majeure notable étant l’interprétation du paramètre name de la fonction tfs_open qui devra supporter le schéma de nommage proposé plus haut.

Ces fonctions devront être les seules fonctions visibles de la bibliothèque dynamique libtfs.so qui servira à obtenir un exécutable supportant le système de fichiers TFS.

Il est aussi demandé d’écrire des commandes comme (liste non-exhaustive):

  • tfs_cp
  • tfs_mv
  • tfs_rm
  • tfs_cat
  • tfs_mkdir

Pour copier un fichier du monde Unix au monde TFS on pourra utiliser une commande comme tfs_cp FILE://HOST/home/yunes/truc.txt FILE://disk.tfs/0/dir/toto.txt qui permet donc de copier le contenu du fichier /home/yunes/truc.txt du système de fichier Unix vers le fichier de référence /dir/toto.txt situé sur le volume numéro 0 du disque tfs de nom disk.tfs.

Il est demandé de décrire soigneusement l’ensemble des fonctions réalisées, permettre la compilation via l’outil make, utiliser git comme système de gestion de version (on rappelle que la machine moule.informatique.univ-paris-diderot.fr:8080 héberge un gitlab utilisable…).

Les documentations doivent être fournies dans un format raisonnable, donc autre que du simple texte, par exemple HTML (un regard vers l’outil Doxygen ne sera pas inutile).

Annexe

Système de cache…

Foody

Il est demandé d’utiliser SpriteKit pour réaliser un jeu. La vidéo suivante en serait une version primitive :

Le scénario est qu’une petite bestiole se déplace dans un labyrinthe (celui-ci n’est pas illustré dans la vidéo) et doit aller chercher de quoi manger (ceci augmente son score comme dans la vidéo) mais qu’en chemin il y a des pièges de différentes sortes. Lorsque le joueur n’a plus de points de vie, la partie est finie.

Les pièges peuvent être :

  • des trappes qui le font passer dans un autre labyrinthe (pour aller chercher d’autres choses à manger). Les trappes se rendent visibles pour un temps assez court mais sont normalement invisibles. Elles peuvent aussi changer de place au cours du temps
  • des pièges qui amputent la vie du joueur (lorsque la vie s’épuise la partie est terminée)

La nourriture peut être de différentes sortes :

  • de la nourriture simple qui ne fait qu’augmenter le score
  • de la nourriture magique qui fait apparaître les trappes
  • de la nourriture régénérante qui permet de récupérer quelques points de vie

On prendra soin d’essayer d’animer si possible agréablement le jeu par différents effets :

  • sonores
  • graphiques

VisuImage

Première partie : une prise en main (pas de code à écrire, enfin!?)

  1. Lancer xcode (qui se trouve normalement dans le répertoire /Applications) et créer une application de type Cocoa Application (langage Swift de préférence, sans StoryBoard, ni tests unitaires, ni Core Data). Compiler et tester le modèle par défaut afin de vérifier que l’environnement de développement est correctement installé et configuré.
  2. Explorer le projet afin d’en anaylser la structure (fichiers sources, ressources, frameworks, etc).
  3. Revenir à la sélection du premier élément représentant le projet et observer les différentes options de configuration du projet et de la cible (métadonnées, options de compilation, etc). Attention à ne rien changer que vous ne sauriez remettre en l’état…
  4. Sélectionner le fichier MainMenu.xib et éditer l’interface de sorte que lui soit rajoutée un objet permettant de visualiser une image (piste : NSImageView).
  5. Configurer ce visualiseur d’image de sorte qu’une image de votre choix y soit présente au lancement de l’application. Attention, cela nécessitera sans doute d’ajouter cette image dans le projet lui-même… Tester.
  6. Modifier les caractéristiques de cet objet visualiseur d’image de sorte qu’il soit autorisé de déposer une image quelconque depuis l’extérieur de l’application (piste : propriété Editable). Tester.

Seconde partie : un peu de code… (ah… Enfin!)

  1. Créer un nouveau projet vierge de type Cocoa Application (langage Swift de préférence). Le configurer.
  2. Y ajouter une nouvelle classe à l’aide du menu File > New > File.... Observer l’instanciation d’un modèle de source de classe Swift.
  3. Ajouter, dans la classe une action de nom click:. La signature devra en être @IBAction func click(sender: AnyObject?). Faire en sorte qu’un message soit affiché (via Swift.print) lorsque cette méthode est appelée.
  4. Éditer MainMenu.xib afin d’ajouter à l’interface un simple bouton.
  5. Dans la partie Objects de l’interface, rajouter une instance d’objet (NSObject). Modifier la classe de cet objet en choisissant la classe précédemment créée.
  6. En tenant le bouton droit de la souris, tirer un trait du bouton vers l’objet instancié. Dans le menu contextuel qui apparaît choisir l’action adéquate… Observer le lien créé via l’inspecteur du bouton et/ou de l’objet : onglet Connections inspector en partie droite de la fenêtre principale.
  7. Compiler et tester votre première application Mac.

Troisième partie : le monde est vaste… (internationalisation)

  1. Reprendre l’application précédente et modifier le projet de sorte qu’il soit localisable (aller sur le projet et modifier sa configuration générale pour obtenir des localisations)
  2. Ajouter un fichier de ressources de type Strings File de nom Localizable.
  3. Revenir sur le fichier de localisation créé précédemment et le rendre localisable (via l’inspecteur) en choisissant une langue parmi celles offertes.
  4. Une entrée d’un tel fichier est toujour au format "clé" = "valeur";. Modifier les fichiers de localisation de sorte qu’à la clé "clic" soit associé un texte particulier pour chaque langue.
  5. Modifier le source de la classe de sorte que Swift.print affiche un message localisée selon l’environnement. Pour cela, on peut utiliser la méthode NSLocalizedString (consulter la documentation pour en connaître les détails).
  6. Compiler et tester que le message s’affiche correctement.
  7. Modifier la langue de l’environnement (via les Préférences Systèmes…) et relancer l’application pour constater que la localisation fonctionne.

Quatrième partie : une image vaut mieux que cent discours…

Si ce n’est pas déjà récupéré depuis la première question:

  1. Ajouter dans le projet une image
  2. Dans l’interface, ajouter une ImageView
  3. Modifier les propriétés de l’Image View pour qu’elle affiche l’image contenue dans le projet.
  4. Compiler, tester.

Cinquième partie : qu’est-ce qu’il y a au menu aujourd’hui ?

  1. Vérifier qu’à l’exécution l’option Open du menu est désactivée.
  2. Observer les liaisons du FirstResponder et de l’option de menu Open
  3. Créer une sous-classe Swift de NSImageView
  4. Modifier la classe de l’instance précédente de l’Image View de votre interface
  5. Ajouter à cette classe une méthode de signature @IBAction func openDocument:(sender: AnyObject?) qui affiche un message simple sur la console (Swift.print you know?)
  6. Faire une liaison de l’option de menu vers l’instance de la classe
  7. Compiler et vérifier que l’option du menu est active!
  8. Implanter de quoi sélectionner un fichier image (NSOpenPanel) et qu’il s’affiche dans l’Image View
  9. Compiler, tester

Sixième partie :

  1. Livrer le code à l’enseignant. Code expurgé de tout exécutable, etc. Vérifier que votre livraison est correcte (par exemple en testant sur une autre machine et avec le compte d’un camarade, celui-ci devrait pouvoir compiler et tester sans erreur aucune!). Pour les détails de la livraison, s’adresser à l’enseignant.

SuperDestructor

On souhaite réaliser un jeu à l’aide du framework SpriteKit.

Le principe général du jeu

Le jeu se situe dans l’espace que tout le monde sait être hostile. Le joueur doit pouvoir contrôler à l’aide des flèches de clavier le déplacement d’un vaisseau spatial. Ce vaisseau spatial est équipé d’un système de défense très perfectionné permettant d’envoyer des missiles vers l’avant.

L’hostilité rencontrée par le vaisseau durant ce jeu est constituée d’un autre vaisseau mais ennemi. Celui-ci se déplace aléatoirement dans l’espace et a pour activité principale de lancer des obus vers le vaisseau du joueur.

Description précise

Le vaisseau du joueur peut se déplacer dans les 4 directions : gauche, droit, haut, bas à l’aide des flèches du clavier. Mais son déplacement est limité à une partie précise de l’espace, uniquement la moitié gauche de la fenêtre de jeu; impossible de sortir de cette zone. Le lancement de missile s’effectue à l’aide de la touche d’espacement. La fréquence de tir est limitée, mais cette limitation doit s’alléger au cours du temps afin d’améliorer les possibilités de défense du joueur. La vitesse du missile peut aussi être variable au cours du temps.

Le vaisseau ennemi se déplace aléatoirement (mais pas trop tout de même) sur une ligne verticale située à droite de l’écran, mais il ne sort jamais de l’écran. À intervalles réguliers il lance un obus à l’horizontale de sa position. La vitesse de déplacement du vaisseau ennemi, la fréquence de tir et la vitesse des missiles doivent augmenter au cours du jeu pour augmenter la difficulté de jeu.

Le joueur gagne un point lorsqu’il arrive à toucher le vaisseau spatial ennemi mais perd 10% de sa force vitale à chaque fois qu’un obus l’atteind. Le joueur possède deux vies au départ. Lorsqu’il n’a plus de vie le joueur a perdu et son score doit être enregistré.

Réalisation

Il est impératif d’employer SpriteKit pour gérer l’ensemble des aspects du jeu.

Il est possible de modifier légèrement le jeu mais il est impératif d’en respecter l’esprit.

Il est possible de rajouter des difficultés, des décors, des animations, des effets sonores en tout genre; mais ce n’est pas le but principal! Le jeu prime!

iOS+XML


On souhaite réaliser une application iOS permettant de visualiser des données et de naviguer en leur sein. Ces données décriront les attributs associés aux cours dispensés à l’UFR d’Informatique et à leurs enseignants affectés mais aussi les relations entre ces différentes entités. L’application sera réalisée par étapes successives, au final on devra pouvoir naviguer et obtenir différents effets (envoyer un mail à un enseignant, obtenir sa photo, naviguer sur sa page web, etc, tout cela depuis la page d’un cours ou d’un diplôme. Ce TP débouchera (au final) sur un rendu de la réalisation qui pourra être faite par groupe de 2.

XML

  1. Dans un premier temps il est nécessaire de structurer les données. Il est donc demandé de spécifier un format de données XML (pas besoin de DTD on ne fera pas de validation), balises, attributs permettant de décrire l’ensemble du cursus d’informatique de l’UFR, licence, master, avec pour chaque diplôme les années, L1, L2, L3, M1, M2, pour chaque année la liste des UE/cours (intitulé, horaires, salles, etc), et pour chaque cours les chargés de cours et TD et TP. Pour chaque enseignant sa page web, son mail, sa photo, une présentation audio, vidéo, etc. Il peut (doit?) s’agir de fichier XML distincts (cours.xmlenseignants.xmlhoraires.xml?).
  2. Dans un second temps, il faut créer quelques données utilisables (inutile de décrire vraiment l’ensemble des cours et cursus) en quantité suffisantes.
  3. Déposer ses fichiers sur un serveur web (vous devez normalement avoir accès à votre propre page web de l’UFR en créant un dossier personnel de nom public_html, si ce n’est pas le cas trouvez une solution (avec les enseignants, etc).

iOS

  1. Créez une application iOS de base (choisissez par exemple la version la plus élémentaire), compilez, excutez afin de vérfier que tout fonctionne bien.
  2. Créez des structures de données (des classes?) permettant de représenter facilement dans votre application les données extraites du fichier XML
  3. Modifiez le délégué d’application de sorte que le fichier XML soit chargé et parsé de façon à remplir votre structure de donnée (via NSXMLParser?)
  4. L’interface dont vous aurez besoin ne devra pour l’instant contenir rien d’autre que des informations relatives au chargement :
    • nombre de cours
    • nombre d’enseignants
    • etc.

Présentation de données

Attention le travail suivant est (très) conséquent et nécessitera probablement plusieurs séances… Ce sera l’objet des séances suivantes…

  1. Présentez les données de façon à rendre ergonomique au possible votre application, nous vous suggérons d’utiliser des mélanges subtils de
    • webviews pour présenter des données HTML (page web des enseignants?)
    • tableviews pour présenter des listes longues/variables d’items (cours?)
    • tabbars pour présenter des collections restreintes et fixes de catégories (diplômes?)
    • pickerviews (diplômes/cours/années?)
    • imageviews (photos?)
    • objets du framework AVFoundation pour présenter de l’audio/vidéo

Mix xcode+quartz

On souhaite réaliser un diaporama avec effets graphiques. L’effet de base est illustré dans la vidéo suivante : exemple. La réalisation est en deux parties :

  1. réaliser une composition Quartz permettant d’animer l’affichage d’un ensemble d’images
  2. intégrer et piloter cette composition depuis une application MacOSX

Première partie : Quartz

Revenir sur votre composition Quartz d’animation (si vous n’en avez pas une fonctionelle, fabriquez en une simple).

  1. Modifiez votre composition de sorte à exporter certains paramètres comme des paramètres d’entrée, c’est-à-dire des paramètres dont les valeurs pourront être injectées depuis l’extérieur de la composition. Commencer par le paramètre contrôlant la vitesse de rotation. Sélectionner par clic doirt l’entrée du patch Quartz et choisir Publish Inputs. Choisir un nom adéquat pour l’export.
  2. Tester que ce paramètre est effectivement utilisable en sélectionnant l’onglet Parameters dans la barre d’outils. Modifiez la valeur directement dans le formulaire présenté et vérifier que cela fonctionne comme attendu.

Seconde partie : XCode

  1. Créer ou modifier une application xcode intégrant la composition Quartz précédente. Ajouter deux boutons, l’un de libellé +vite l’autre -vite.
  2. Ajouter une classe Cocoa descendante de NSObject contenant un attribut entier correspondant à la valeur vitesse et un Outlet de type QCView.
  3. Instancier via le constructeur d’interface un objet de la classe précédente. Établir le lien Outlet entre l’objet et la QCView.
  4. Établir un lien réactionnel (une Action) entre les boutons +vite et -vite et la classe de l’objet métier. Une action s’appellera plusVite et l’autre moinsVite.
  5. Modifier le code de ces méthodes de sorte à modifier en conséquence la valeur de l’attribut stocké par l’objet (afficher cette valeur via un appel à NSLog. Tester. Question : comment est initialisé l’attribut ? Est-ce la bonne valeur de départ ?
  6. Rechercher dans la document de la QCView comment modifier la valeur d’un paramètre exporté d’une composition Quartz jouée dans une QCView. Faire en sorte que les deux actions modifient le paramètre. Tester.

Troisième partie : XCode

  1. Rechercher dans la documentation comment une QCView peut en retour appeler du code contenu dans l’application (piste: cet appel est effectué sur une base temporelle). Implanter du code de sorte qu’une méthode de l’objet métier soit appelée sur une base temporelle (y placer un appel à NSLog).
  2. Rechercher dans la documentation comment les valeurs de paramètres de sortie de composition Quartz peuvent être consultées dans un code applicatif. Modifier la composition Quartz pour produire une valeur indiquant qu’il est temps de changer d’image. Rajouter une entrée publique à la composition permettant d’injeter une image NSImage.
  3. Modifier le code de sorte que lorsque cet indicateur est positionné, l’application change d’image tournante (on s’autorisera à basculer entre deux images pour commencer). Consulter la documentation de NSImage pour retrouver comment des images peuvent être lues depuis le système de fichiers.
  4. Modifier le code de sorte à ajouter un NSOpenPanel lorsqu’on choisit l’option Ouvrir du menu de l’application. Ce Panel devra permettre de choisir dans le système de fichiers un répertoire contenant des images (on pourra supposer que le répertoire contient effectivement des images et uniquement des images). Les images du répertoire seront utilisées dans l’ordre comme suite d’image à afficher par la composition animée.

Quatrième partie : cosmétique + extension…

  • Fournir une application au standard attendu : icône, etc
  • Définir une extension de fichier .dpr (.diaporama) reconnue par l’application dont le contenu pourra être une liste d’URL désignant des images qui seront utilisées lors de la projection