PHP FFI : CallBack – partie 3

Le CallBack

J’ai pu remarquer pendant la préparation de cet article que pas mal de ressources sur le net utilisent la libc directement pour faire leurs exemples. 
Vous trouverez facilement des exemples assez démonstratifs et simples. Mais pour faire cool et original, je me suis dit “allez moi aussi je vais trouver un truc à montrer avec cette libc”. 
M’enfin… si c’est juste passer une structure comme le font la plus part des exemples ça ne servira à rien. Et puis je tombe sur une fonction dans un forum ou les gens pestent en disant que c’est mal foutu, qu’on ne peut pas s’en servir facilement etc. 
Je me suis donc documenté, et effectivement les fonctions « nftw » et « ftw » ont un coté peu pratique. Elle ont besoin d’une fonction en paramètres.

Comme vous pouvez le voir dans la man page http://manpages.ubuntu.com/manpages/bionic/en/man3/ftw.3posix.html

#include <ftw.h>

       int ftw(const char *path, int (*fn)(const char *, const struct stat *ptr, int flag), int ndirs);

Nous avons donc un pointeur sur fonction en second paramètre. Okeyyyy… 
Donc à ce moment là on fait quoi ? FFI::new un truc comme ça ??
Allez je vous explique en quelques minutes comment on gère cela.

#!/usr/bin/php8.1
<?php
    /**
     * quelques références:
     * https://www.ibm.com/docs/en/zos/2.3.0?topic=functions-nftw-nftw64-traverse-file-tree
     * http://www.sde.cs.titech.ac.jp/~gondow/dwarf2-xml/HTML-rxref/app/gcc-3.3.2/lib/gcc-lib/sparc-sun-solaris2.8/3.3.2/include/sys/types.h.html
     * http://www.doc.ic.ac.uk/~svb/oslab/Minix/usr/include/sys/types.h
     * http://manpages.ubuntu.com/manpages/trusty/fr/man2/stat.2.html
     */
opcache_reset();

$ffi = FFI::cdef(
    "
            typedef long		blkcnt_t;	
            typedef	long		time_t;
            /* Types used in disk, inode, etc. data structures. */
            typedef short          dev_t;	   /* holds (major|minor) device pair */
            typedef char           gid_t;	   /* group id */
            typedef unsigned short ino_t; 	   /* i-node number */
            typedef unsigned short mode_t;	   /* file type and permissions bits */
            typedef char         nlink_t;	   /* number of links to a file */
            typedef unsigned long  off_t;	   /* offset within a file */
            typedef int            pid_t;	   /* process id (must be signed) */
            typedef short          uid_t;	   /* user id */
            typedef unsigned long zone_t;	   /* zone number */
            typedef unsigned long block_t;	   /* block number */
            typedef unsigned long  bit_t;	   /* bit number in a bit map */
            typedef unsigned short zone1_t;	   /* zone number for V1 file systems */
            typedef unsigned short bitchunk_t; /* collection of bits in a bitmap */
            
            typedef unsigned char   u8_t;	   /* 8 bit type */
            typedef unsigned short u16_t;	   /* 16 bit type */
            typedef unsigned long  u32_t;	   /* 32 bit type */
    

    
            typedef	int		blksize_t;	/* used for block sizes */
    
            struct timespec {
                         time_t          tv_sec;
                         long            tv_nsec;
            };
            
            /** 
             * http://manpages.ubuntu.com/manpages/trusty/fr/man2/stat.2.html
             * /usr/include/x86_64-linux-gnu/sys/types.h 
             */
            struct stat {
               dev_t     st_dev;         /* Périphérique                     */
               ino_t     st_ino;         /* Numéro d’inœud                   */
               mode_t    st_mode;        /* Protection                       */
               nlink_t   st_nlink;       /* Nombre de liens physiques        */
               uid_t     st_uid;         /* UID du propriétaire              */
               gid_t     st_gid;         /* GID du propriétaire              */
               dev_t     st_rdev;        /* Type de périphérique             */
               off_t     st_size;        /* Taille totale en octets          */
               blksize_t st_blksize;     /* Taille de bloc pour E/S          */
               blkcnt_t  st_blocks;      /* Nombre de blocs de 512 o alloués */
            
               /* Depuis Linux 2.6, le noyau permet une précision à la
                  nanoseconde pour les champs temporels suivants. Pour
                  plus de précisions avant Linux 2.6, consultez les NOTES. */
            
               struct timespec st_atim;  /* Heure dernier accès              */
               struct timespec st_mtim;  /* Heure dernière modification      */
               struct timespec st_ctim;  /* Heure dernier changement état    */
            
               #define st_atime st_atim.tv_sec      /* Rétrocompatibilité        */
               #define st_mtime st_mtim.tv_sec
               #define st_ctime st_ctim.tv_sec
            };

            typedef struct FTW{
                int base;
                int level;
            };

            typedef int (*fn)(const char *, const struct stat *, int, struct FTW *);
            int nftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *),  int fd_limit, int flags);
            int ftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *),  int fd_limit);
        ",
    "libc.so.6"
);

$values = [];
$fn = function ($fpath, $stat, $tflag, $ftwbuf) {
    global $values;

    /**
     * https://sites.uclouvain.be/SystInfo/usr/include/ftw.h.html
     * FTW_CONTINUE = 0,     Continue with next sibling or for FTW_D with the  first child.
     * FTW_STOP = 1,         Return from `ftw' or `nftw' with FTW_STOP as return value.
     * FTW_SKIP_SUBTREE = 2  Only meaningful for FTW_D: Don't walk through the  subtree, instead just continue with its next sibling.
     * FTW_SKIP_SIBLINGS = 3 Continue with FTW_DP callback for current directory (if FTW_DEPTH) and then its siblings.
     */
    $values[] = sprintf(
        "level: %s, Path: %-40s",
        $ftwbuf->level,
        $fpath
    );
    return 0;
};

$ffi->nftw('/var/lib', $fn, 1, 0);
print_r(array_slice($values, 0, 5));
echo PHP_EOL;


$ffi->ftw('/var/lib', $fn, 1);
print_r(array_slice($values, 0, 5));
echo PHP_EOL;

Je vous pose le code tel quel, avec les références qui m’ont permis de monter l’exemple. Pour la faire courte,  je suis partis de la man page qui me donne les premières informations. On peut donc commencer par poser la définition avec les 2 fonctions.


int nftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *),  int fd_limit, int flags);
int ftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *),  int fd_limit);

On remarque vite que nous avons un pointeur sur fonction donc on l’ajoute à notre définition :


typedef int (*fn)(const char *, const struct stat *, int, struct FTW *);

Là on identifie qu’on a besoin de spécifier 2 structures : FTW mais aussi stat

typedef struct FTW{
                int base;
                int level;
};
            struct stat {
               dev_t     st_dev;         /* Périphérique                     */
               ino_t     st_ino;         /* Numéro d’inœud                   */
               mode_t    st_mode;        /* Protection                       */
               nlink_t   st_nlink;       /* Nombre de liens physiques        */
               uid_t     st_uid;         /* UID du propriétaire              */
               gid_t     st_gid;         /* GID du propriétaire              */
               dev_t     st_rdev;        /* Type de périphérique             */
               off_t     st_size;        /* Taille totale en octets          */
               blksize_t st_blksize;     /* Taille de bloc pour E/S          */
               blkcnt_t  st_blocks;      /* Nombre de blocs de 512 o alloués */
            
               /* Depuis Linux 2.6, le noyau permet une précision à la
                  nanoseconde pour les champs temporels suivants. Pour
                  plus de précisions avant Linux 2.6, consultez les NOTES. */
            
               struct timespec st_atim;  /* Heure dernier accès              */
               struct timespec st_mtim;  /* Heure dernière modification      */
               struct timespec st_ctim;  /* Heure dernier changement état    */
            
               #define st_atime st_atim.tv_sec      /* Rétrocompatibilité        */
               #define st_mtime st_mtim.tv_sec
               #define st_ctime st_ctim.tv_sec
            };

            typedef struct FTW{
                int base;
                int level;
            };

Une petite recherche sur le net et on trouve rapidement d’où copier coller les infos :

http://manpages.ubuntu.com/manpages/trusty/fr/man2/stat.2.html mais surtout /usr/include/x86_64-linux-gnu/sys/types.h.

Pratique, j’ai à priori déjà l’info sur moi 🙂 On continue de dérouler le fil et on ajoute l’ensemble des types. J’ai eu un peu de mal à les trouver…

http://www.sde.cs.titech.ac.jp/~gondow/dwarf2-xml/HTML-rxref/app/gcc-3.3.2/lib/gcc-lib/sparc-sun-solaris2.8/3.3.2/include/sys/types.h.html 
http://www.doc.ic.ac.uk/~svb/oslab/Minix/usr/include/sys/types.h

Voila notre définition faite ! On arrive enfin à la fonctionnalité géniale que j’essaie de démontrer ici : Le callback !
Oui oui vous avez bien entendu. 
Comme nous n’avons aucune limite, nous allons passer directement une fonction anonyme PHP à notre fonction ftw… wow wow WOOOOW  WOOOOW!!!

Je m’emballe, mais en vrai c’est super cool, regardez le code :

$values = [];
$fn = function ($fpath, $stat, $tflag, $ftwbuf) {
    global $values;

    /**
     * https://sites.uclouvain.be/SystInfo/usr/include/ftw.h.html
     * FTW_CONTINUE = 0,     Continue with next sibling or for FTW_D with the  first child.
     * FTW_STOP = 1,         Return from `ftw' or `nftw' with FTW_STOP as return value.
     * FTW_SKIP_SUBTREE = 2  Only meaningful for FTW_D: Don't walk through the  subtree, instead just continue with its next sibling.
     * FTW_SKIP_SIBLINGS = 3 Continue with FTW_DP callback for current directory (if FTW_DEPTH) and then its siblings.
     */
    $values[] = sprintf(
        "level: %s, Path: %-40s",
        $ftwbuf->level,
        $fpath
    );
    return 0;
};

$ffi->nftw('/var/lib', $fn, 1, 0);

Ok, bon, je ne pensais pas réécrire un global dans ma vie, le dernier remonte à il y a 15 ans peut être, et encore… Mais pour les besoins de la démo, OSEF , YOLO (à vous de trouver un autre acronyme…). 
Comme vous le voyez pas besoin de passer un FFI::addr, la fonction anonyme se suffit à elle même.

Le résultat :

./nftw.php 
Array
(
    [0] => level: 0, Path: /var/lib                                
    [1] => level: 1, Path: /var/lib/php                            
    [2] => level: 2, Path: /var/lib/php/sessions                   
    [3] => level: 2, Path: /var/lib/php/modules                    
    [4] => level: 3, Path: /var/lib/php/modules/7.4                
)

Array
(
    [0] => level: 0, Path: /var/lib                                
    [1] => level: 1, Path: /var/lib/php                            
    [2] => level: 2, Path: /var/lib/php/sessions                   
    [3] => level: 2, Path: /var/lib/php/modules                    
    [4] => level: 3, Path: /var/lib/php/modules/7.4                
)

La doc nous met tout de même en garde sur les callbacks. Il ne faut pas en abuser.

Dans notre prochaine partie de dossier, nous allons aborder comment jouer la carte de l’interopérabilité avec des langages prévus pour le mobile. KMP (Kotlin Multiplateform).

PHP FFI : utilisation d’une lib Kotlin Multiplateforme – partie 4

Merci à Thomas Bourdin, Cédric Le Jallé, Stéphane Péchard pour leur aide, conseils et relecture.