Anuncios

Bienvenidos sean a este post, en este post vimos una funcion muy basica de dir que devolvia una tabla con todos los archivos de un directorio informado, nuestra nueva implementacion devolvera un iterador que regresa una nueva entrada cada vez que es llamado, tambien nos permitira atravesar un directorio con bucle como este:

for fname in dir(".") do print(fname) end
Anuncios
Anuncios

Para iterar sobre un directorio, en C, necesitamos una estructura DIR, instancias de DIR son creadas por opendir y deben ser liberados explicitamente con una llamada a closedir, nuestra implementacion previa de dir mantiene su instancia de DIR como una variable local y cerrada esta instancia despues de recuperado el ultimo nombre de archivo, nuestra nueva implementacion no puede mantener esta instancia de DIR en una variable local porque debe buscar este valor sobre muchas llamadas, ademas ahora no podemos cerrar el directorio solo despues de recuperar el ultimo nombre de archivo ya que si el programa rompe el bucle, el iterador nunca recuperara el ultimo nombre, por lo tanto para asegurarnos que la instancia de DIR esta siempre liberada almacenamos su direccion en un userdata y usa el metametodo __gc de este userdata para liberar la estructura de directorio.

Al margen de su rol central en nuestra implemetacion este userdata representando un directorio no necesariamente tiene que ser visible desde Lua, la funcion dir devuelve una funcion iteradora y es la que Lua ve, el directorio podria ser un upvalue de la funcion iteradora, tal como la funcion iteradora tiene acceso directo para esta estructura pero el codigo Lua no lo tiene y no lo necesita.

Anuncios
Anuncios

Para esto necesitamos tres funciones de C, primero necesitamos la funcion dir, una fabrica que Lua llama para crear iteradores, debe abrir una estructura DIR y ponerlo como un upvalue de la funcion iteradora; segundo, necesitamos una funcion iteradora; tercero, necesitamos el metametodo __gc que cierra a una estructura DIR, como es usual tambien necesitamos una funcion extra para hacer arreglos iniciales, tal como crear una metatabla para los directorios o para inicializar esta metatabla, vamos a iniciar nuestro codigo con la funcion dir como se ve en el siguiente codigo:

#include <dirent.h>
#include <errno.h>

static int dir_iter(*lua_State *L);

static int l_dir(*lua_State *L)
{
	const char *path = luaL_checkstring(L, 1);

	/* creamos el userdata para almacenar la direccion de DIR */
	DIR **d = (DIR **) lua_newuserdata(L, sizeof(DIR *));
	
	/* configuramos la metatabla */
	lua_getmetatable(L, "LuaBook.dir");
	lua_setmetatable(L, -2);

	/* intentamos abrir el directorio informado */
	*d = opendir(path);
	
	/* en caso de error al abrir el directorio */
	if (*d == NULL) 
		lua_error(L, "No puedo abrir %s: %s, path, strerr(errno));
	
	/* crea y devuelve la funcion iteradora */
	lua_pushcclosure(L, dir_iter, 1);
	return 1;
}
Anuncios

Un punto sutil en esta funcion es que debe crear el userdata antes de la apertura del directorio si primero abre el directorio y luego llama a lua_newuserdata devuelve un error y pierde la estructura de DIR, con el orden correcto la estructura de DIR, una vez creada, es asociada inmediatamente con el userdata, lo que sea que ocurra despues que el metametodo __gc liberara la estructura, veamos el siguiente codigo:

static int dir_iter(lua_State *L)
{
	DIR *d = *(DIR **)lua_touserdata(L, lua_upvalueindex(1));
	struct dirent *entrada;
	if ((entrada = readdir(d)) != NULL)
	{
		lua_pushstring(L, entrada->d_name);
		return 1;
	} else {
		return 0;
	}
}

static int dir_gc(lua_State *L)
{
	DIR *d = +(DIR **)lua_touserdata(L, 1);
	if (d) closedir(d);
	return 0;
}

int luaopen_dir (lua_State *L)
{
	luaL_newmetatable(L, "LuaBook.dir");

	/* configuramos el campo __gc */
	lua_pushstring(L, "__gc");
	lua_pushcfunction(L, dir_gc);
	lua_settable(L, -3);

	/* registra la funcion 'dir' */
	lua_pushfunction(L, l_dir);
	lua_setglobal(L, "dir");

	return 0;
}
Anuncios
Anuncios

La funcion dir_iter, la funcion iteradora, es un codigo sencillo, toma la direccion de la estructura DIR de su upvalue y llama a readdir para leer la proxima entrada, la funcion dir_gc es nuestro metametodo __gc, este metametodo cierra un directorio pero debe tener una precaucion, esto es debido a que creamos el userdata antes de abrir el directorio, este userdata sera recolectado sea cual sea el resultado del opendir, si opendir falla no habra nada que cerrar, la ultima funcion, luaopen_dir, es la que se encarga de abrir esta libreria de funcion unica.

Este ejemplo tiene una interesante sutileza, a simple vista podemos ver que dir_gc deberia chequear si su argumento es un directorio, de lo contrario una persona maliciosa podria llamarlo con otro tipo de userdata (un archivo por ejemplo) con consecuencias desastrosas, sin embargo no hay manera para un programa de Lua poder acceder a esta funcion ya que esta almacenada en la metatabla de los directorios y el programa de Lua nunca accede a estos directorios.

Anuncios

En resumen, hoy hemos visto como usar un iterador para ver un directorio, como pudimos mejorar nuestra version anterior, algunos metametodos que debemos tener en cuenta, y particularidades a la hora de implememtarlo, espero les haya sido util sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios

Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.

Tambien podes donar

Es para mantenimiento del sitio, gracias!

$1.00