PHP FFI: Callback - Parte 3

Il Callback

Durante la preparazione di questo articolo, ho notato che molte risorse su internet usano direttamente la libc per i loro esempi. 
È possibile trovare facilmente esempi abbastanza dimostrativi e semplici. Ma per fare qualcosa di figo e originale, ho pensato "troviamo qualcosa da mostrare con questa libc anch'io". 
Dopotutto… se si tratta solo di passare una struttura come nella maggior parte degli esempi, non sarà utile. E poi mi sono imbattuto in una funzione in un forum dove la gente si lamentava che fosse fatta male, che non si possa usare facilmente, ecc. 
Allora ho fatto alcune ricerche, e infatti le funzioni “nftw” e “ftw” sono un po' scomode. Richiedono una funzione come parametri.

Come puoi vedere sulla pagina 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);

Quindi abbiamo un puntatore a funzione come secondo parametro. Vabbè… 
Cosa facciamo a questo punto? FFI::new qualcosa del genere ??
Permettimi di spiegare in pochi minuti come gestire questo.

#!/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 il codice così com'è, con i riferimenti che mi hanno permesso di impostare l'esempio. Per farla breve, sono partito dalla pagina man che mi dà le prime informazioni. Possiamo iniziare delineando la definizione con le 2 funzioni.


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

Presto ci accorgiamo che abbiamo un puntatore a funzione quindi lo aggiungiamo alla nostra definizione:


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

Qui identifichiamo che dobbiamo specificare 2 strutture : FTW ma anche 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 rapida ricerca su internet e troviamo facilmente dove copiare e incollare le informazioni :

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

Comodo, ho già le informazioni a portata di mano 🙂 Continuiamo a svolgere il filo e aggiungiamo tutti i tipi. È stato un po' difficile trovarli…

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

A questo punto la nostra definizione è completa! Finalmente arriviamo alla fantastica funzionalità che sto cercando di dimostrare qui: Il callback!
Sì, hai capito bene. 
Non avendo limiti, passeremo una funzione anonima di PHP direttamente alla nostra funzione ftw... wow wow WOOOOW  WOOOOW!!!

Sto diventando euforico, ma in realtà è super figo, guarda il codice :

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

Okay, beh, non pensavo di dover riscrivere una global nella mia vita, l'ultima volta è stato forse 15 anni fa, e anche allora… Ma per gli scopi della dimostrazione, vada per il YOLO (sta a te trovare un altro acronimo…). 
Come puoi vedere, non c'è bisogno di passare un FFI::addr, la funzione anonima è sufficiente di per sé.

Il risultato :

./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 documentazione ci avverte sui callback. Non bisogna abusarne.

Nella nostra prossima parte del file, affronteremo come giocare la carta dell'interoperabilità con i linguaggi progettati per il mobile. KMP (Kotlin Multiplatform).

PHP FFI: utilizzare una libreria Kotlin Multiplatform - parte 4

Grazie a Thomas Bourdin, Cédric Le Jallé, Stéphane Péchard per il loro aiuto, consigli e revisione del testo.