\section{Communication entre processus}

Dans un premier temps, il faut choisir un mécanisme de communication entre les
différents processus. Nous allons donc lister rapidement les méthodes
utilisables.

\subsection{Les canaux unix}

Les canaux unix ou pipes unix permettent de réaliser une communication
multi-processus unidirectionnelle. D'un côté, un producteur écrit dans le tube,
ou bloque jusqu'à ce qu'il puisse avoir la place d'écrire. De l'autre côté, un
consommateur lit dans le tube ou bloque si aucune donnée n'est disponible. Les
canaux unix ont l'avantage d'être très faciles à manipuler et être globalement
disponibles sur toutes les plateformes importantes sans exception. Néanmoins, il
ne s'agit que du transfert de données et il faut donc un mécanisme d'accès pour
les ressources. Les canaux Unix peuvent être créés de façon anonyme avec la
fonction \inltype{pipe}, soit être nommés via la fonction \inltype{mknod} ou
\inltype{mkfifo}.

\subsection{Les files de message}

Les files de message POSIX, ou message queues sont assez proches des canaux
unix, mais sont forcément nommées et permettent d'ajouter des priorités tout en
segmentant les messages. Ici la propriété de segmentation est très intéressante
pour pouvoir déboguer et parser les messages envoyés d'une partie de
l'application à l'autre. Néanmoins, aucun mécanisme ne permet d'envoyer des
ressources sur une file de message. Il faut donc également réaliser une
passerelle et un contrôle d'identité pour pouvoir l'utiliser dans notre cas.

\subsection{Les mémoires partagées}

Les mémoires partagés POSIX fournissent un moyen de communication extrêmement
performant pour dialoguer. Elles ont besoin d'un mécanisme de synchronisation
mais n'ont absolument pas besoin de passer par des appels systèmes hormis lors
de leur création. C'est donc un candidat de choix pour une communication entre
deux processus. En revanche, elle demande l'introduction de beaucoup de contrôle
autour pour éviter les bogues classiques en C (dépassements ou écrasements de
mémoire) et les ressources ne peuvent être transmises qu'à travers un autre
mécanisme ou une redirection d'IO vers la mémoire partagée.
        %TODO: expliquer redirection au dessus en citant la partie général: qqun
%fait les IO et redirige le résultat sur la shared memory

\subsection{Les primitives Sys V}

Sys V fournit des alternatives pour chacune des méthodes citées ci-dessus.
Malheureusement, les primitives de Sys V fonctionnent avec leur propre API et ne
se rapprochent pas du tout de la philosophie unix. Ces API fournissent un objet
Sys V au lieu d'un descripteur de fichier et bien qu'étant compatibles plus
facilement avec d'autres systèmes grâce à son ancienneté, nous allons voir que
les autres solutions apportent bien plus d'avantages.

\subsection{Les sockets unix}

Les sockets unix permettent d'envoyer des messages, soit de façon segmentée
avec les mécanismes de \og{}end of record\fg{}, soit par l'envoi de datagrammes. Les
performances en tant qu'IPC sont les mêmes que les deux premières méthodes,
mais les sockets Unix disposent d'une caractéristique unique qui correspond
très exactement à notre problème. Ils disposent d'un moyen de transmettre des
descripteurs de fichiers d'un processus à l'autre, en y joignant le message qui
permet d'interpréter la ressource. Cette interface est néanmoins difficile à
manipuler comme on va le voir dans la suite.

\subsection{Choix de la solution}

L'IPC qui s'applique le plus simplement à notre solution tout en conservant des
performances correctes apparait clairement comme étant les sockets unix. Ils
fournissent une interface de transfert plus intéressante et mieux contrôlable,
mais surtout permettent de transférer des descripteurs de fichiers entre
processus. Ce dernier point permet une gestion des accès et des ressources
particulièrement fine et va être à la base de mon implémentation Linux.

\section{Partage de ressources entre processus}
\subsection{Objectifs}

Profitant de la caractéristique des sockets unix, nous allons implémenter le
transfert de descripteur de fichier entre processus. L'objectif derrière cette
fonctionnalité est de pouvoir utiliser les descripteurs de fichier comme des
jetons inforgeables qui apportent des accès aux ressources, soit par
communication, soit par accès direct.

L'implémentation technique de ce système est fortement inspirée des méthodes
utilisées initialement par Nginx. Nginx est un serveur web et proxy inverse
programmé avec un paradigme asynchrone. Comme pour la sandbox, il fonctionne
avec un processus orchestrateur qui contrôle les processus serveurs, ou workers.
Chaque worker fonctionne en tant qu'utilisateur non privilégié.

Cette solution doit être compatible avec le bus de communication que l'on veut
mettre en place.

Les sockets unix sont alors bien la solution de communication inter-processus
correspondant parfaitement au problème. Ils sont gérés efficacement, peuvent
être anonymes et donc parfaitement contrôlés et sont représentés dans le
programme par des descripteurs de fichiers, permettant ainsi de les transmettre
entre processus.

\subsection{Envoi de données}

Le mécanisme de données auxiliaires mentionné plus haut est générique pour
toutes les classes de sockets. En fait, on peut s'intéresser aux structures
utilisées lors de l'envoi d'un message.

\begin{code}{c}{Structure \inltype{msghdr} utilisée dans
\inltype{sendmsg}/\inltype{recvmsg}}
/* from man 2 send */
struct msghdr {
   void         *msg_name;       /* optional address */
   socklen_t     msg_namelen;    /* size of address */
   struct iovec *msg_iov;        /* scatter/gather array */
   size_t        msg_iovlen;     /* # elements in msg_iov */
   void         *msg_control;    /* ancillary data, see below */
   size_t        msg_controllen; /* ancillary data buffer len */
   int           msg_flags;      /* flags (unused) */
};
\end{code}

% TODO reformuler
Il y a en réalité deux types de buffers envoyés via cette structure. Le premier
type concerne \inltype{msg_iov} qui est en réalité un tableau de
\inltype{iovec}. Chaque \inltype{iovec} contient un pointeur vers un buffer et
une taille. Ces \inltype{iovec} représentent les données qui seront envoyées à
travers le socket. Le fait que \inltype{msg_iov} soit un tableau de ces buffers
permet d'éviter de copier un paquet morcelé côté utilisateur avant de le
recopier du côté du noyau.

L'autre tampon mémoire  est en réalité dédié à la pile logicielle manipulant le
socket dans le noyau. Il s'agit d'un buffer de configuration, ou pour reprendre
le vocabulaire utilisé dans la structure, un buffer de contrôle de l'interface
bas niveau. Il peut être utilisé pour donner des paramètres dépendants de chaque
type de socket. Pour les sockets unix, cela va permettre de manipuler
l'interface SCM (socket control message) qui est capable de copier les
descripteurs de fichier.

\begin{code}{c}{Structure \inltype{cmsghdr} représentant un élément auxiliaire}
/* from man 3 cmsg */
struct cmsghdr {
    size_t cmsg_len;    /* Data byte count, including header
                          (type is socklen_t in POSIX) */
    int    cmsg_level;  /* Originating protocol */
    int    cmsg_type;   /* Protocol-specific type */
    /* followed by
    unsigned char cmsg_data[]; */
};
\end{code}

\subsection{Manipulation des données auxiliaires}

La manipulation de ce buffer est un peu particulière. Il s'agit en fait
d'allouer des structures de données au seins du buffer même, tout en respectant
les contraintes demandées par le système. En pratique, l'API POSIX fournit les
macros suivantes pour manipuler ce buffer.

\begin{itemize}
    \item \inltype{CMSG_FIRSTHDR} retourne un pointeur vers le premier
    \inltype{cmsghdr} dans le tampon de données auxiliaires associé au
    \inltype{msghdr} passé en paramètre.

    \item \inltype{CMSG_NXTHDR} retourne le prochain \inltype{cmsghdr} après
    celui passé en paramètre, ou \inltype{NULL} si le tampon est plein.

    \item \inltype{CMSG_SPACE} retourne la taille occupée par un élément
    auxiliaire.

    \item \inltype{CMSG_DATA} retourne un pointeur vers la partie donnée d'un
    \inltype{cmsghdr}.

    \item \inltype{CMSG_LEN} retourne la valeur à stocker dans le champ
    \inltype{cmsg_len} en comptant l'alignement, à partir de la taille des
    données.
\end{itemize}

Deux problématiques ont dû être prises en compte lors de l'utilisation de cette
API. D'un côté, il faut faire attention aux accès non alignés. De l'autre, il
faut respecter les règles de strict aliasing, c'est-à-dire qu'il ne doit pas
exister deux pointeurs de types incompatibles vers le même espace mémoire. Tout
cela va se résumer à correctement suivre les recommandations de l'API tout en
utilisant \inltype{memcpy} pour copier les données depuis et vers le tampon
mémoire.

\begin{code}{c}{Implémentation du traitement des données auxiliaires}
static int
ipc_internal_SendMsg( int socket_fd, unsigned nb_buffer,
                      struct process_msg_internal_t *buffers[],
                      unsigned nb_fd, int* fds)
{
    /* On utilise une union pour que la contrainte d'alignement du type cmsghdr
     * s'applique au tampon buf */
    union {
        char buf[512];
        struct cmsghdr align;
    } ctlbuf;
\end{code}

Les tampons mémoires sont convertis de la représentation de l'application vers
celle de l'API Linux.

\begin{code}{c}{Conversion des tampons et initialisation du msghdr}
    struct iovec iov[nb_buffer];
    for (unsigned i=0; i<nb_buffer; ++i) {
        iov[i].iov_base = buffers[i]->buffer;
        iov[i].iov_len  = buffers[i]->length;
    }

    struct msghdr msgh = {
        .msg_name = NULL,
        .msg_namelen = 0,
        .msg_iov = iov,
        .msg_iovlen = nb_buffer,
        .msg_control = ctlbuf.buf,
        .msg_controllen = sizeof( ctlbuf ),
        .msg_flags = 0
    };
\end{code}

Puis les descripteurs de fichier sont copiés dans la structure.
\inltype{SCM_RIGHTS} permet d'indiquer ici que ce sont bien des descripteurs de
fichier et qu'il va falloir les dupliquer dans le processus qui reçoit le
paquet.

\begin{code}{c}{Création des données auxiliaires}
    if( nb_fd > 0 )
    {
        struct cmsghdr *cmsg = CMSG_FIRSTHDR( &msgh );
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
        cmsg->cmsg_len = CMSG_LEN( nb_fd * sizeof( int ) );
        void *p_data = CMSG_DATA( cmsg );
        memcpy( p_data, fds, nb_fd * sizeof( int ) );
        msgh.msg_controllen = cmsg->cmsg_len;
    }
    else
    {
        msgh.msg_controllen = 0;
        msgh.msg_control = NULL;
    }
\end{code}

Enfin le message est envoyé. On utilise \inltype{MSG_EOR} pour pouvoir signaler
la fin du message, comme il s'agit de \inltype{SOCK_SEQPACKET} mais d'autres
solutions sont possibles.

\begin{code}{c}{Envoi du message}
    ssize_t i_size = sendmsg( socket_fd, &msgh, MSG_EOR );
\end{code}

%     if( i_size == -1 )
%     {
%         fprintf( stderr, "Couldn't send message, error at sendmsg: %d\n", errno);
%         return VLC_EGENERIC;
%     }
%     else if( i_size == 0 )
%     {
%         // Déconnexion
%         return VLC_ENOOBJ;
%     }
%
%     return VLC_SUCCESS;
% }

% TODO développer problématiques

Pour la réception, la méthode est similaire, à l'exception de l'apparition de
\inltype{CMSG_LEN(0)} pour remplacer \inltype{CSMG_ALIGN} qui n'existe que sous
Linux:

\begin{code}{c}{Réception des descripteurs de fichier}
    struct cmsghdr *cmsg = CMSG_FIRSTHDR( &msgh );

    while( cmsg != NULL )
    {
        if( cmsg->cmsg_level == SOL_SOCKET
            && cmsg->cmsg_type == SCM_RIGHTS )
        {
            // TODO: check len, it takes headers into account
            // TODO: change assert in error handling
            // TODO: alloc msg->fds
            size_t len = cmsg->cmsg_len - CMSG_LEN(0);
            assert( len >= sizeof( int ) );
            assert( len % sizeof( int ) == 0 );
            len /= sizeof( int );
            ARRAY_INIT( (*msg)->fds );

            for( int i = 0; i < len; ++i )
            {
                int fd;
                memcpy( &fd, CMSG_DATA( cmsg ) + i * sizeof( int ),
                        sizeof( int ));
                ARRAY_APPEND( (*msg)->fds, fd );
                (*msg)->i_nb_fds++;
            }
        }
        cmsg = CMSG_NXTHDR( &msgh, cmsg );
    }
\end{code}

Finalement, nous avons ainsi conçu un moyen de construire des jetons
inforgeables pouvant soit transmettre d'autres jetons, soit servir de RPC vers
un accès particulier.
