inicio mail me! sindicaci;ón

Archive for November, 2006

“Cerveza abierta”

/*
* —————————————————————————-
* “THE BEER-WARE LICENSE” (Revision 42):
* wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
* —————————————————————————-
*/

El OpenBeer Project trata básicamente de tener una lista de contactos de geeks que conoces vía IRC (p.ej.) y que viven en otras ciudades, dispuestos a invitarte a una cerveza cuando por alguna razón te pases por su localidad. O eso entendí yo :P.

Gran idea. Que me recuerda que a la Lorena le dejé pendiente unas cervezas por un meneo que me dió en su día, así que ya sabes, si algún día te pasas por Salamanca, o yo por Bilbao, cervezas tocan.

Sobre la licencia esa de más arriba, la beerware, probablemente ya hubiérais oído antes de ella, no es mala idea tampoco. Hay muchos programas/textos que por lo reducidos que son no merecen colocarles una licencia enorme como una GPL o una BSD. Ni siquiera una Creative Commons. ¿Un howto de 26 páginas merece un copyleft?

Y sin embargo, esta licencia nos recuerda que detrás de ese programa o howto hay al menos un programador que no sólo no vive del aire, sino que además gusta de una cerveza (p.ej.) de vez en cuando. ¿Cuántos de los que usamos habitualmente software libre apenas nos acordamos de sus autores (y no vale las veces que te acuerdas por que no funciona como debiera)? ¿Cuánta gente dona a los proyectos de los que hace uso: Gentoo, OpenBSD, OpenSSH, Firefox, WordPress, etc.?

Ya sea dinero, hardware o algún otro útil es buena idea mojarse de lleno con el software libre, y no limitarse a usarlo sin más, especialmente en todas aquellas empresas donde se usa o se vive del software libre. No hace mucho Theo de Raadt, líder del proyecto OpenBSD se quejaba de la falta de dinero que tenían algunos proyectos (el mismo OpenBSD, u OpenSSH, que está muy relacionado con OpenBSD) cuando son proyectos que sí se usan en muchas empresas. Dreamhost usa openssh (¿qué empresa de hosting no lo usa?), Novell y Red Hat venden sendas distribuciones de linux, que harán uso de Mozillas varios, Gnome, KDE, OpenNTPD, Apache, etc. y he de decir que no conozco la situación, pero mucho me temo que ni Novell ni Red Hat contribuyen en gran medida a estos proyectos. Sé que hace años había gente de SuSE que desarrollaba y mantenía algún que otro driver del kernel de linux, y supongo que hoy en día Novell seguirá colaborando (p.ej. con Xgl).

Pero en general, opino que la mayor parte de las empresas que hace uso del software libre colabora mucho menos de lo que debería.

¿Se puede vivir del software libre? Es la pregunta que surge automáticamente en estos casos. ¿Vender vino sin botellas es posible? Supuestamente del software libre se puede vivir a base de dar soporte, formación y vender el código o mejoras a éste. Conlleva varias ventajas a saber, individuales:

  • uno se promociona mejor y se da a conocer más y tal y como es: como buen o como mal programador (o analista o documentador)
  • tu propio código está más protegido frente a fallos de seguridad, ya que vas a tener unos cuantos ojos más ayudándote a buscar bugs y mejoras
  • en algunos de casos uno se resuelve los problemas propios
  • te permite competir mejor, o competir sin más. ¿Si OpenOffice hubiera sido cerrado habría llegado al nivel de uso al que ha llegado hoy en día? No creo que le haga la competencia al Microsoft Office, pero es que si hubiera sido cerrado, imho, hoy en día estaría prácticamente muerto. Frente a una empresa fuerte puede que la única forma de hacerte un hueco sea mediante software libre.
  • tu código mejora si la competencia hace sus productos a partir de él, y eliges una licencia que le obligue a publicar las mejoras (y la competencia es legal, claro)

y comunes:

  • cuando un programador se resuelve sus problemas (se rasca su propia comezón) posiblemente también se los esté resolviendo a otro
  • la seguridad de los sistemas en su conjunto y en potencia es mayor. Digo en potencia por que la seguridad de un sistema depende en gran medida de cómo se administre, de cómo se haga uso de la capacidad para ser seguro.
  • favorece enormemente la reutilización de código y el ahorro (al menos) de tiempo que conlleva. Desarrollando a partir del producto de otros puedes llegar a hacerles la competencia en poco tiempo, dando via libre a que gane el mejor, a que sobrevivan los proyectos mejor gestionados y mejor programados, y no aquellos que en su día se ganaron su nicho de mercado más por suerte que por ser buenos (léase MS-DOS, por nombrar un ejemplo, aunque sí hay que reconocerle que era fácil de usar, al menos más que un UNIX o un VMS)
  • ahorra muchos costes tanto a empresas y particulares como a la administración

Creo que como indica Barahona, el software libre mejora la competencia, pone a todo el mundo más o menos al mismo nivel, grandes o pequeñas empresas.

Y si se puede vivir del software libre… imho sí es posible, aunque de forma diferente a como estábamos acostumbrados con el software propietario con el que asociábamos vivir del software con vender una caja con unos CDs o disquetes y un manual.

A todo esto, volviendo a las cervezas: OpenCola y OpenBeer. Y os recomiendo que paséis por la página de Poul-Henning Kamp que cuenta con cierta documentación interesante y literatura sobre LORAN-C (un sistema de posicionamiento similar al GPS, pero terrestre y no satelital, con menos alcance, creo que anda en desuso).

Comenzando con Aleli

Fue ya hace casi un mes, el 24 de octubre, cuando recibí el comunicado de Forja-RedIRIS de que se había aprobado el proyecto con el que participo en el I Concurso Universitario de Software Libre. Y desde entonces, entre asignaturas, cursos y compromisos apenas he conseguido tiempo para rebañar un poco de documentación sobre lex y yacc, que son unos de los recursos que pienso emplear para desarrollar este proyecto.

Aleli es un proyecto orientado a obtener estadísticas sobre una lista de correo, de forma que a partir de su histórico en formato mbox (p.ej. este) se pueda ver cuántos mensajes se han enviado por día del mes, día de la semana, semana, mes, usuario, etc. La salida sería en el formato de gnuplot para representarla fácilmente.
El primer objetivo será ser capaz de parsear una entrada en formato mbox y abstraerla en una estructura de datos (aparte de verificar si la entrada es correcta). Precisamente para esto es para lo que voy a recurrir a lex y yacc.
Los siguientes objetivos poco a poco los voy perfilando, y mejor ir poco a poco: más vale pájaro en mano que ciento volando, ¿no? Además de que ir maleando los objetivos “en caliente” permite ajustarse mucho mejor a las necesidades reales.

Mandatory locking

Me ha venido eso a la cabeza, el mandatory locking. Y como no tengo una pila entrañable de apuntes por estudiar, ejercicios y prácticas por hacer, y libros que leer, y además me sobra el tiempo, me he propuesto revisitar todo el panorama de bloqueos de ficheros. Por supuesto, esto no es la biblia de los bloqueos de ficheros, así que de lo que leas créete la mitad, de lo que no leas no te creas nada :P

1. ¿Por qué debe un programa bloquear un fichero? Esencialmente para poder acceder a él en entornos con concurrencia asegurándose que tras el acceso, especialmente si es de escritura el fichero guarda un estado coherente.

Os ilustraré con un ejemplo: supongamos que estamos haciendo un programa para una librería (de las que venden libros, no una .dll, ni un .so) que gestiona pedidos de libros. Tenemos una base de datos donde se almacena el inventario de libros tal que así:

Practical Common Lisp:Peter Seibel:500:Apress:1590592395:34
The Pragmatic Programmer, From Journeyman to Master:Andrew Hunt, David Thomas:352:Addison-Wesley Professional:020161622X:52

Como vemos cada entrada (o tupla que se diría en el modelo relacional si esto fuera una base de datos, que dista de serlo) tiene varios campos (atributos): el título del libro, los autores, el número de páginas, la editorial, el ISBN y por último el número de copias que tenemos en stock. En realidad una base de datos de una librería sería mucho más compleja, con más atributos (peso y dimensiones, para envíos por correo, tipo de tapas, etc.) y varias tablas interrelacionadas entre sí.

Hasta aquí todo bien, llega un cliente, compra una copia del Practical Common Lisp, y lo obvio: nuestro programa decrementa el número de copias que nos quedan del libro (el último campo) en uno, tras asegurar de que quedaban libros antes de cobrarle al cliente, e incluso avisando a los proveedores de que traigan más si ve que quedan pocos.

Paso a paso nuestro programa lo que hace es:

1.Leer el registro para Practical Common Lisp

2. ¿nº copias > 0? no=>dar mensaje de error y se acabó

3. ¿nº copias < lim_inferior_copias? si=> poner en la cola de avisos uno para el proveedor de ese libro, que nos traiga más, que se vende bien.

4. Decrementar en 1 el nº de copias (lo que viene a ser vender una copia)
5. Guardar datos en la base de datos

Ahora se repite la operación, pero esta vez con dos clientes que compran el mismo libro simultáneamente. Cada cliente manda ejecutar una instancia del programa, la instancia A y la instancia B, y cada instancia sigue uno de esos 5 pasos citados anteriormente:

A1. Leer el registro para Practical Common Lisp. Leerá “Practical Common Lisp:Peter Seibel:500:Apress:1590592395:33″, así que pensará que quedan 33 copias (la 34 se la llevó el del ejemplo anterior)
B1. Leer el registro para Practical Common Lisp. Esta operación se hace a la vez que la anterior y resultará en leer “Practical Common Lisp:Peter Seibel:500:Apress:1590592395:33″, por lo que para la instancia B del programa vendedor también quedan 33 libros, lo cual es correcto, ¿no?

Pasos A2, B2, A3 y B3: ambas instancias las pasan con éxito, no tienen relevancia para el ejemplo.

A4: Decrementar en uno el número de copias que quedan: ahora quedan 32

B4: Ídem que A4, lo único es que el número de copias que la instancia B tiene en mente es la que leyó en el paso B1, 33 por tanto. Así que decrementando nos quedamos también en 32, sin embargo hemos vendido dos libros a sendos clientes.

A5 y B5: Guardar los nuevos datos en la base de datos.

El resultado es que después de esta operación hemos vendido 2 libros y en la base de datos parece que sólo hemos vendido uno. Tras esta operación la base de datos no es consistente.

¿Cuál ha sido el problema? Ha sido que esas dos operaciones no se podían hacer simultáneamente, sino secuencialmente: primero deberíamos hacer una y luego la otra. Para ello sigamos este nuevo esquema:

0. ¿Base de datos desbloqueada? no => esperas un ratitín y vuelves a comprobar. Si después de haber comprobado 3 veces no hay tu tía, das un mensaje de error.
1. Leer registro de la base de datos Y bloquear la base de datos, que nadie más pueda acceder a ella, ni siquiera para leer.

2 y 3. Comprobar si quedan copias o si hay que reponer libros

4. Decrementar el número de unidades que quedan

5. Guardar nuevo registro y desbloquear la base de datos

Vamos, que la solución son los bloqueos: permitir bloquear y desbloquear la base de datos y comprobar si está bloqueada o no.

2. Varios sistemas para desarrollar bloqueos
2.1. Ficheros de bloqueo

Inicialmente los sistemas Unix no proveían un método para llevar a cabo los bloqueos, así que alguien se rascó su propia comezón y se le ocurrió que podría crear un fichero que indicara si otro fichero (la base de datos) estaba en uso o no por su mera presencia.

Dicho de otra forma, para comprobar si la base de datos está bloqueada yo comprobaría si puedo acceder al fichero de bloqueo, que llamaremos /var/lock/vendedor.lock, p.ej.:

int comprueba_bloqueo(const char *fn)
{
FILE *fd;

if( (fd=fopen(fn,”r”)) ==NULL )
return 0;

fclose(fd);
return 1;
}

Así que tratamos de acceder al fichero que nos pasan como parámetro para lectura, ¿que falla? pues eso es que o bien no existe (no hay bloqueo) o bien no tenemos permisos para acceder (vigilad este último detalle, que a lo mejor os da quebraderos de cabeza), y en ese caso nos devolvería 0. En caso de que si exista, y por tanto haya bloqueo, devolvería 1 (previo cierre del fichero).

En PHP uno se puede apañar con la función file_exists. En fin, cada lenguaje tendrá su método, aquí no quiero centrarme en un lenguaje ni en un sistema en concreto (aunque lo acabaré haciendo), sino que prefiero dar un vistazo general.

Hasta aquí hemos llegado a comprobar si está o no bloqueado. ¿Y qué hacer después? Vamos a mejorar un poco lo anterior para el supuesto caso de que el fichero esté bloqueado:

int
main(int argc, char **argv)
{
int bloqueado;
int n_prueba;

printf(“Fichero de bloqueo: %s\n”,LOCK_FILE);
n_prueba=0;
while( (bloqueado=comprueba_bloqueo(LOCK_FILE)) && n_prueba++
usleep(PAUSA);
/*comprobamos hasta INTENTOS veces si está bloqueado o no, esperando PAUSA microsegundos entre cada intento */
if(bloqueado) {
printf(“La base de datos permanece bloqueada (%d intentos, %.0f segundos)\n”,INTENTOS,INTENTOS*PAUSA/1000000.0);
exit(EXIT_FAILURE);
}
/*si sigue bloqueado nos largamos*/
printf(“Fichero bloqueado: %s\n”,bloqueado?”si”:”no”);

exit(EXIT_SUCCESS);
}

Realmente no queremos que nos imprima esas trazas para saber cómo van las cosas, sino que queremos que monte un bloqueo en caso de que esté desbloqueada, y posteriormente, una vez que acabe, que lo desmonte.

Para bloquear, simplemente tenemos que crear el fichero de bloqueo:

int bloquea(const char *fn)
{
FILE *fd;

if( (fd=fopen(fn,”w”)) ==NULL)
return -1;

fclose(fd);
return 1;
}

si se creó devolverá 1, en caso de error devuelve -1.

Y para desbloquear, simplemente borraremos el fichero:

int desbloquea(const char *fn)
{
if(unlink(fn))
return 0;
else
return 1;
}

Lo que devolverá 0 si hubo fallos, o 1 si todo fue bien.

Estos son los principios básicos de los ficheros de bloqueo. Sin embargo, existe aún un pequeño problema: qué ocurre si una vez que hemos bloqueado el fichero, y antes de desbloquearlo, nuestro programa falla, o es matado, o se va la luz. Simplemente que el fichero de bloqueo se quedará hasta que alguien manualmente lo borre, por lo que la base de datos seguirá bloqueada hasta entonces. Es la pega de este método.

Estas funciones presentan posbiles mejoras, p.ej. yo debería comprobar que existe un fichero de bloqueo antes de tratar quitarlo, y además en vez de ver por un lado si no existe fichero de bloqueo y luego crearlo, sería más práctico que la función bloquea() se encargara ella sola de hacer la comprobación, y por ejemplo, si se sobrepasa el número de intentos límite borrar el fichero de bloqueo (dando por supuesto que el proceso que lo creó, y responsable de borrarlo ha muerto), crear el fichero con O_EXCL, guardar el PID en el fichero, y leerlo, etc.

2.2 Advisory y mandatory lockings: el sistema operativo soporta bloqueos.
Precisamente para evitar el inconveniente de que se mantuviera el bloqueo una vez el proceso se ha estrellado, System V y BSD implementaron sendos métodos: System V mediante fcntl() y BSD mediante una llamada específica al sistema de nombre flock(). En estos casos es el sistema operativo el que se hace responsable de que el bloqueo sea consistente. La llamada lockf() en el fondo es más o menos un alias para fcntl().
Para ambas opciones existen dos tipos de bloqueos, los exclusivos que sólo permiten un proceso por fichero, y los compartidos, que permiten varios. En el caso concreto de fcntl() uno puede bloquear diferentes regiones del fichero, p.ej. del byte 1 al 512 por un proceso, y del 726 al 1024 por otro proceso.

El bloqueo que proveen estas dos llamadas es lo que se viene llamando advisory locking. Cuando un proceso trata de escribir se bloquea el fichero (o el área del fichero) para los demás procesos. ¿Y si simultáneamente se trata de escribir por parte de otro proceso? Pues el otro proceso tratará de bloquear el fichero, pero si no lo consigue nada nos asegura que no haga una llamada a write(), lo que nos dará problemas. El advisory locking (o unenforced locking) requiere cooperación por parte de ambos procesos.

Para resolver este problema se desarrolló el mandatory locking (o enforced locking) por parte de los diseñadores de System V, que es un atributo que tiene el archivo. Si un fichero tiene activado este atributo, automáticamente en cada llamada a write() o read() se ejecutará un fcntl() previo que bloqueará el área de fichero que se va a leer y aún no está bloqueada, y posteriormente otro fcntl() desbloqueante. Si antes de haber hecho las llamadas hubiéramos ejecutado un fcntl() sobre todo el fichero, entonces no se ejecutaría ningún fcntl() implícito.

Todo aquel que tiene permiso para abrir el fichero, tiene permiso para aplicarle un mandatory locking, así que hay que tener cuidado a ver a quién se le da permisos, por que si no le da la gana desbloquear nos estará ocasionando un ataque de denegación de servicio.

La forma de representar el atributo del mandatory locking es activando el bit de ejecución SGID y desactivando el permiso de ejecución para el grupo. Por ser una combinación sin significado se eligió para esta tarea. Esto es válido para SunOS, Solaris, HP-UX y Linux, aunque cada uno lo implementa a su manera y con sus detalles.

Los bloqueos obligatorios (mandatory) no son POSIX.

En Linux los bloqueos pueden hacerse de lectura o de escritura, entre otros tipos. La llamada mmap() también es alterada según el fichero a mapear esté bloqueado o no. Ved la página del manual de fcntl().
Por supuesto, cada caso es un problema aparte ya que no es lo mismo usar un sistema de ficheros que otro, y menos si uno de ellos es por red, como p.ej. NFS, donde no funcionan los flock(). También hay que tener en cuenta el sistema operativo y su versión, y quizá la arquitectura.
En Windows las cosas son ligeramente distintas, ya que todos los bloqueos son enforced, y además el modo de acceder a un fichero varía. Para más información, sobre todo de Unix, leed la bibliografía.

Bibliografía:

Haré algo de código en C para ver qué tal se me dan los bloqueos, si se me resisten o no. Lo postearé por aquí

« Previous entries