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.