PHP FFI: Callback - Parte 3

El Callback

Durante la preparación de este artículo, noté que muchos recursos en internet usan directamente el libc para sus ejemplos. 
Puedes encontrar fácilmente ejemplos bastante demostrativos y sencillos. Pero para hacer algo genial y original, pensé "vamos a encontrar algo para mostrar con este libc también". 
Después de todo… si solo es pasar una estructura como la mayoría de los ejemplos, no será útil. Y luego encontré una función en un foro donde la gente se quejaba de que está mal hecha, que no se puede usar fácilmente, etc. 
Así que hice algunas investigaciones, y efectivamente las funciones “nftw” y “ftw” son algo imprácticas. Requieren una función como parámetros.

Como puedes ver en la página man 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);

Entonces tenemos un puntero a función como el segundo parámetro. Está bien… 
¿Entonces qué hacemos en este punto? ¿FFI::new algo así ??
Déjame explicarte en unos minutos cómo manejar esto.

#!/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;

Presento el código tal cual, con las referencias que me permitieron configurar el ejemplo. Para resumir, comencé desde la página man que me da la primera información. Podemos empezar estableciendo la definición con las 2 funciones.


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);

Rápidamente notamos que tenemos un puntero a función por lo que lo añadimos a nuestra definición:


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

Aquí identificamos que necesitamos especificar 2 estructuras : FTW pero también 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;
            };

Una búsqueda rápida en internet y encontramos fácilmente dónde copiar y pegar la información :

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

Cómodo, ya tengo la información en la mano 🙂 Continuamos desvelando el hilo y añadimos todos los tipos. Fue un poco difícil encontrarlos…

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

Allí, ¡nuestra definición está hecha! Finalmente llegamos a la increíble funcionalidad que estoy tratando de demostrar aquí: ¡El Callback!
Sí, has oído bien. 
Como no tenemos limitaciones, vamos a pasar una función anónima de PHP directamente a nuestra función ftw... vaya vaya WOOOOW  WOOOOW!!!

Me estoy emocionando, pero en realidad es súper genial, mira el código :

$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, bueno, no pensé que iba a reescribir una global en mi vida, la última vez fue quizás hace 15 años, y aun así… Pero para los fines de la demostración, lo que sea , YOLO (queda a tu criterio proponer otro acrónimo…). 
Como puedes ver, no es necesario pasar un FFI::addr, la función anónima es suficiente por sí misma.

El resultado :

./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 documentación sí nos advierte sobre los callbacks. No se deben usar en exceso.

En nuestra siguiente parte del archivo, abordaremos cómo jugar la carta de la interoperabilidad con lenguajes diseñados para móviles. KMP (Kotlin Multiplataforma).

PHP FFI: usando una biblioteca Kotlin Multiplataforma - parte 4

Gracias a Thomas Bourdin, Cédric Le Jallé, Stéphane Péchard por su ayuda, consejos y correcciones.