sábado, 20 de diciembre de 2008

Scripting en lisp

Hola:

Quería escribir esta entrada para comentar algo que me ha sucedido recientemente y que viene al caso de la utilidad de lisp para escribir scripts para la administración de sistemas, al estilo de lo que se suele hacer en perl.

Veamos la situación: recientemente me compré un Acer Aspire One ZG5 con Linux Linpus preinstalado. Por circunstancias que no vienen al caso tuve que cambiar el sistema operativo para poner Debian el Pc. Pues bien, el problema fue que quise instalar un entorno gráfico ligero ( WindowMaker ) en el cuál pretendía usar wicd para configurar mi red inalámbrica. Para mi sorpresa, wicd no funcionó. Lejos de suponer un problema, configuré la red editando manualmente el archivo /etc/network/interfaces y obtuve conexión a internet. Esto sucedió en el trabajo.

Al llegar a mi casa, más de lo mismo. Wicd no conectaba a mi red wireless y tuve que crear un nuevo archivo de configuración para mi red casera. Resumiendo: la situación era la siguiente: 2 archivos de configuración (/etc/network/interfaces.wep y /etc/network/interfaces.wpa ) y un enlace simbólico al archivo correcto que debía crear manualmente cada vez que quería cambiar de red, además de llamar al dichoso /etc/init.d/networking restart cada vez, para conectar.

Aunque no es algo excesivamente tedioso, enseguida pensé en escribir un script que automatizara el proceso de conectar a la red que quisiera, y se me ocurrió escribirlo en lisp en lugar de hacer el típico shell script. La idea era crear un pequeño programa que borrara el enlace existente, substituyéndolo por un nuevo enlace al archivo de configuración correcto y luego llamara a /etc/init.d/networking restart para conectarme a la red. No parecía difícil hacerlo en lisp, pues.

Para ejecutar procesos del linux desde lisp necesitamos la función (escribí esto para sbcl, no debería ser difícil reescribirlo para cualquier otro lisp) (sb-ext:run-program programa lista_de_argumentos ) . Si queremos llamar a la función sin argumentos, simplemente usaremos como segundo parámetro la lista vacía (nil).

Ahora nuestro programa debería ser como sigue:

Función activar_conexion
Parámetros: nombre de la conexion a activar

Ejecuta rm -f /etc/network/interfaces
Ejecuta ln -s /etc/network/nombre_archivo_configuracion /etc/network/interfaces
Ejecuta /etc/init.d/networking restart

El resto del programa debía consistir en algo de código para guardar los nombres de los archivos de configuración. Ahí se me ocurrió la idea de hacer un programa gráfico que usara ltk. Me mostraría todos los archivos de configuración de que dispongo y yo sólo tendría que clickear en un botón para activar la conexión a internet.

Finalmente, decidí que mantendría una tabla hash llamada *conexiones* con entradas del tipo ( nombre fichero_configuracion ) dónde guardaría las conexiones de que dispongo, y que el procedimiento anadir-conexion me serviría para añadir entradas a la hash. Una vez hecho click en el botón correspondiente la función activar-conexion me activaría la conexión a internet haciendo lo que hemos dicho antes.

Pues bien, aquí tenéis el código fuente:

(require :ltk )
(use-package :ltk )

(defvar *directorio-base* "/etc/network/" )
(defvar *nombre-link* "/etc/network/interfaces" )
(defvar *conexiones* (make-hash-table :test 'equal ))

(defun anadir-nueva-conexion ( nombre nombre_arxivo )
(setf (gethash nombre *conexiones* ) nom_arxivo )
)

(defun activar-conexion ( nombre )
(if (gethash nombre *conexiones* )
(progn
(let
(
(nombre_arxivo (concatenate 'string *directorio-base* (gethash nombre *conexiones* )))
)
(print nombre_arxivo )
(sb-ext:run-program "/bin/rm" (list "-f" *nombre-link* ))
(sb-ext:run-program "/bin/ln" (list "-s" nombre_arxivo *nombre-link* ))
(sb-ext:run-program "/etc/init.d/networking" (list "restart" ))
)
)
)
)

(anadir-nueva-conexion "Eduroam UV" "interfaces.wpa" )
(anadir-nueva-conexion "Red casa" "interfaces.wep" )

(defun main ()
(setq *wish-args* (list "-name" "NetworkSwitcher" ))
(with-ltk ()
(let*
(
(marco (make-instance 'frame :master nil ))
etiqueta
)
(maphash (lambda (key value )
(declare (ignore value ))
(setq etiqueta (make-instance 'button :master marc :width 12 :text key
:command (lambda ()
(print key )
(activar-conexion key ))
))
(pack etiqueta )
)
*conexiones* )
(pack marco )
)
)
)


(main)

Al arrancar el programa se ven 2 botones, uno con la etiqueta "Red casa" y otro con la etiqueta "Eduroam UV". Al hacerles click activo la red correspondiente.

Sencillo, no? Mucha gente pensará que debería leerme la faq de wicd en lugar de perder el tiempo en estas cosas, pero realmente he aprendido y disfrutado haciéndolo, así que me doy por satisfecho.

Adiós!!! Felices fiestas a todos!!