Parsear archivos INI en C

Hola chicos,

Hoy os vengo a hablar de mi último proyecto de fin de semana, que ha sido propiciado por un proyecto más grande que exigía el uso de éste o uno similar. Se trata de una librería de parseo de archivos INI.

Los archivos INI pueden encontrarse en muchos entornos. Por ejemplo, los archivos de .gitconfig de git o los archivos de configuración de Asterisk son archivos INI.

Los archivos INI son archivos de texto plano (siguiendo la filosofía UNIX) que tienen la siguiente forma:

[sección]
clave=valor
; Comentario

Hay diferentes implementaciones, algunas soportan el uso de almohadilla '#' para hacer comentarios además del habitual ';' y/o soportan el uso de ':' para separar clave-valor en lugar de '='. A parte de todo esto, se descartan todos los espacios del archivo, menos los internos de los campos.

En mi caso, necesito una librería simple para leer este tipo de archivos y, como nunca he desarrollado parsers en C me he decidido por hacerlo. Para ello me he fijado bastante en esta librería de parseo de INIs. He reescrito todo, después de mirar un poco cómo lo hacía, no porque yo lo haga mejor (nada más lejos de la realidad), si no porque quiero aprender y probar. Una vez he visto como lo enfoca, me he encargado de hacerlo todo yo mismo tomando, en algunos casos, decisiones que me han separado de su implementación. En general, mi librería es más simple y soporta menos cosas, pero tengo un Makefile muy chulo. 😀

Lo que os quiero contar hoy no es sólo de qué va la librería, también quiero hablar un poco de las herramientas que he usado para testearla, que es lo que creo que tiene un interés real. Después de esta introducción kilométrica, vamos allá.

Como está hecha en C es mucho más complicada de lo que me gustaría. Para resumir mucho, voy a ir al grano. Se basa en 3 cosas importantes.

  • Punteros a funciones. Sólo se usa en el callback, en seguida cuento que es eso.
  • Tratamiento de strings. ¿Qué es el '\0'?
  • Uso agresivo de punteros.

Voy a contar un poco de forma general como funciona la librería a nivel de usuario y luego cuento cómo se consigue.

La librería te aporta una función (parse_ini) a aplicar a tu archivo, a la que le tienes que introducir: el path, el puntero de una estructura en la que quieres volcar los datos extraídos y un callback.

El callback es una función que se llamará cada vez que se encuentre una pieza de datos (una pareja clave-valor con su sección) en el archivo. La tiene que definir el usuario. Las reglas que tiene que cumplir son: que devuelva 0 si todo ha ido bien y 1, -1 u otro número si ha habido algún error y que reciba los siguientes argumentos de entrada: char * section, char * key, char * value, void * data. Cada uno de ellos es bastante claro, menos el último, que sería un puntero vacío a la estructura de datos que le hemos enviado a la función de parseo. La librería te da acceso a ella en el callback para que puedas llenarla a tu manera con los otros datos.

Como usuario tendrás que crear el callback que llene esa estructura de datos y definir cuando hay un caso de error. Una vez lo tengas, llamarás a la función parse_ini en tu programa y listo. Esto te llenará tu estructura de datos y ya los tendrás accesibles. Si queréis ver un ejemplo de uso podéis entrar en el proyecto y ver el apartado de ejemplo.

Como veis se basa en los punteros a funciones para que el usuario pueda valerse de la librería. Hemos cubierto el punto 1.

¿Pero cómo se consigue esto?

No os lo voy a contar realmente a fondo. Sólo voy a contar un par de cosas y luego os leéis el código si queréis y lo probáis.

Cada vez que se encuentra una nueva pareja clave-valor se preparan variables (ahora explicamos cómo) que serán las que entren en el callback del usuario. Estas variables se hacen en copias para que el usuario no pueda romper nada. Para utilizar el callback, la librería hace una declaración del prototipo pero nunca llega a definirlo, deja al usuario esa labor.

typedef int (*callback)( char * section, char * key, char * value, void * data_structure );

Investigad por ahí un poco sobre typedef si queréis.

De esta forma, llamamos al callback como si fuese nuestro, ya que le hemos dicho al usuario cómo tiene que usarlo y conocemos la forma de sus parámetros de salida.

Tratamiento de strings y uso agresivo de punteros los voy a tratar juntos, si puedo. Los strings son un churro (o un array, si vas de listo) de caracteres, finalizado con la letra \0. Es así como los interpreta C.

Si queremos saber la longitud de un string iremos a su primera posición (que es su dirección en memoria), y contaremos hasta encontrarnos \0. Podemos trucar esto para eliminar, por ejemplo, los espacios al final del string: Si sustituimos los espacios finales por ‘\0’, pensaremos que el string acaba antes. Lo mismo con los espacios al principio: si guardamos la dirección del primer elemento que no sea un espacio en un puntero, para ese puntero el string empieza más adelante.

Así es como funcionan las funciones rstrip y lstrip.

A la hora de encontrar los pares clave-valor se busca el elemento separador ( = o :), se pisa con un \0 para que el string termine ahí (ahora ese string sería la clave) y se asigna la posición siguiente a un puntero nuevo que definirá el valor. Con el mismo churrete, tenemos dos mini-strings que separan perfectamente las dos partes. Son perfectamente independientes además y podremos aplicarles las funciones rstrip y lstrip para eliminar los espacios que no nos gustan.

Lo ilustro mejor con esta imagen (click to enlarge). Tenéis en cada paso lo que se hace en la memoria (los cambios están en negrita). Arriba el contenido, debajo los punteros. Lo que se marca en colores es lo que se consideraría cada uno de los strings nombrados en cada puntero. En el código fuente cambian los nombres pero la filosofía es la misma:
screenshot_20160624_122527Las secciones no disparan el callback, éste sólo se dispara cuando hay una pareja clave valor y se le añade también la última sección que se ha encontrado (el procedimiento para encontrar secciones es muy similar) ya que el fichero se evalúa de arriba a abajo, linea por línea.

Eso es todo lo que  voy a contar de la implementación, el resto con el código lo podéis deducir sabiendo lo que he contado aquí.

Para terminar, os comento un poco cosas que he utilizado para encontrar errores y asegurarme de que no hay memory leaks (porque los había, y muchos, sobre todo en el ejemplo).

He utilizado GDB, el GNU-Debugger. Para poder usarlo bien hay que compilar flags de debug, que se consigue añadiendo un parámetro a la hora de compilar. Este debugger nos permite poner breakpoints y hacer análisis de lo que ocurre. En mi caso, yo lo uso sobre todo para ver el estado del programa en el momento del fallo y entender qué ha pasado.

Otra herramienta interesante es Valgrind. Yo no le he sacado todo el partido, pero me ha venido muy bien. Utilizarla en modo testeo de memoria (memcheck) analiza si estás escribiendo fuera de memoria alquilada o si no liberas la memoria bien. Es muy buena herramienta para este tipo de casos. Os la dejo y la investigáis.

Eso es un poco todo lo que quería contar.

Espero que esto os haya ayudado, como a mí, a entender conceptos algo más avanzados (punteros vacíos, punteros a funciones…), pegaros con strings en C y conocer alguna herramienta nueva que no habíais probado mientras que descubrís lo fácil que es parsear un archivo de configuración como estos que tratamos aquí.

Espero vuestros comentarios y proyectos utilizando el conocimiento adquirido.

Después de mucho tiempo sin contar nada, creo que aquí hay chicha para compensar todo este tiempo, ¿no?

Un abrazo.

Anuncios

Un pensamiento en “Parsear archivos INI en C

  1. droWMark, postea en WordPress desde Vim – Free Hacks!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s