Integridad de ficheros (Respuesta larga)
Salvador Ortiz Garcia
sog@msg.com.mx
Fri, 6 Mar 1998 21:10:12 -0600 (CST)
On Fri, 6 Mar 1998, Jose Ignacio wrote:
> Bueno a ver si ahora voy bien. Lo que quiero es leer todo el fichero,
> modificar una variable y volver a escribir todo el fichero. He recibido
> varios mails con lo cual observo que es un problema común a varias
> personas.
Por fín sé lo que quieres hacer, y así es más fácil ayudarte.
Primero y por completez, algunos comentarios generales respecto a la
lectura y escritura en un fichero (archivo).
Cuando abres un fichero para lectura/escritura necesitas mover el
apuntador interno del archivo para cambiar de una a otra.
Del manual de fopen(2):
Reads and writes may be intermixed on read/write streams
in any order. Note that ANSI C requires that a file posi-
tioning function intervene between output and input,
unless an input operation encounters end-of-file. (If
this condition is not met, then a read is allowed to
return the result of writes other than the most recent.)
Therefore it is good practice to put an fseek or fgetpos
operation between write and read operations on such a
stream. This operation may be an apparent no-op (as in
fseek(..., 0L, SEEK_CUR) called for its synchronizing side
effect.
Lo anterior traducido a perl, quiere decir que:
1) No necesitas abrir nuevamente el archivo, de hecho, si lo haces
te metes en otros lios.
2) Si quieres reescribir sobre lo leído necesitas posicionarte al
principio del archivo con:
seek(FF,0,0);
Ahora, para tu caso en particular, la cosa se complica con este esquema
por que si escribes menos de lo que leíste, al final de lo reescrito
quedará en el archivo como basura parte del contenido anterior.
Para lograr lo que quieres necesitas usar un esquema parecido al usado por
los programas que manipulan en UNIX el archivo /etc/passwd.
La idea general es la siguiente:
1. Cualquiera puede abrir y leer del archivo sin preocuparse por la
concurrencia de procesos.
2. Cuando algún proceso necesita modificar el archivo, crea un pseudo-lock
que debe ser respetado por otros procesos que quieran hacer
modificaciones.
3. Teniendo el control del pseudo-lock, se lee todo el archivo y se
escribe la versión modificada en otro archivo temporal.
4. Al terminar, se sustituye el original por el creado y se libera el
pseudo-lock.
Al implementar el esquema descrito se tiene que tener cuidado de evitar
"carreras" entre los procesos cuidando la atomizidad de algunas
operaciones, lo que hace que la implementación sea dependiente del sistema
operativo usado.
A continuación el código de una implementación usando perl 5.004 estándar
o superior, para cualquier sistema operativo que cumpla POSIX a la que
sólo le falta que pongas tu código en los huecos indicados:
======= Corta aquí ==========
#!/usr/bin/perl -w
use IO::File;
sub create_new {
# Función para crear archivo temporal y pseudo-lock en
# forma atómica.
# Recibe nombre del archivo original
my $file = shift;
my $fh;
while(1) {
$fh = IO::Open("$file.tmptmp", O_WRONLY|O_CREAT|O_EXCL,0644);
last if(defined($fh) || link("$file.tmptmp","$file.tmp"));
# Error en creación de pseudo-lock, alguien está modificando
# $file, me espero un segundo y reintento.
if(defined($fh)) {
$fh->close;
unlink("$file.tmptmp");
}
sleep(1);
}
unlink("$file.tmptmp"); # Borro el temporal, sin cerrarlo!
return $fh; # Regreso el FILEHANDLE del temporal
}
sub actualiza {
my $file = shift;
my $fh = shift;
$fh->close; # Cierra temporal
rename("$file.tmp",$file); # Cambia atómicamente el viejo
# liberando al vuelo el pseudo-lock
}
# El código siguiente es para actualizar el archivo.
$archivo = '/el/nombre/de/tu/archivo'; # Nombre completo de tu archivo
$NUEVO = create_new($archivo); # Creas temporal y pseudo-lock
open(FF,"<$archivo"); # Abres tu archivo normalmente
while(<FF>) { # Lees tu archivo normalmente
...
}
for(noseque) { # Escribes en $NUEVO lo que quieras
print $NUEVO "lo que quieras";
...
}
close(FF); # Cierro FF normalmente
actualiza($archivo,$NUEVO); # Actualiza y cierra $NUEVO
# Listo.
======= Fin de código =======
Si las caracteristicas de tu plataforma no corresponden a las mencionadas
(por ejemplo en MSWindows) puede requerir algunos ajustes (o que cambies
de plataforma ;-) , pero en principio, ahí está lo que buscas.
El por qué el método funciona, queda como ejercicio para el lector :-)
Saludos y suerte
Salvador Ortiz.