Mantener (o restaurar) los permisos de archivo al reemplazar el archivo

11

Tengo un comando que acepta un archivo como argumento, modifica el archivo y luego lo escribe con el nombre de archivo especificado en el segundo argumento. Llamaré a ese programa modifyfile.

Quería que funcionara "en su lugar", así que escribí un script de shell (bash) que lo modifica a un archivo temporal y luego lo mueve hacia atrás:

TMP=`mktemp`
modifyfile "$original" "$TMP"
mv -v "$TMP" "$original"

Esto tiene el desafortunado efecto secundario de destruir los permisos en este archivo. El archivo se vuelve a crear con permisos predeterminados.

¿Hay alguna manera de decirle al mvcomando que sobrescriba el destino sin alterar sus permisos? O, alternativamente, ¿hay alguna manera de guardar el usuario, el grupo y los permisos del original y restaurarlos?

Stephen Ostermiller
fuente

Respuestas:

10

En lugar de usar mv, solo redirige cat. Por ejemplo:

TMP=$(mktemp)
modifyfile "$original" "$TMP"
cat "$TMP" > "$original"

Esto se sobrescribe $originalcon el contenido de $TMP, sin tocar nada a nivel de archivo.

Strugee
fuente
¿No podría ser problemático si algún programa tiene un identificador de archivo abierto para el archivo?
Martin von Wittich
2
Entonces también tengo que hacerlo rm "$TMP", pero parece hacer exactamente lo que quiero.
Stephen Ostermiller
@MartinvonWittich probablemente sería un problema si estuviera usando en su mvlugar. No veo una manera de resolver ese problema.
strugee
2
@MartinvonWittich Sí. Create-new-then-move le ofrece un cambio atómico y no afecta a los programas que tienen el archivo abierto, pero dado que crea un nuevo archivo, la propiedad y los permisos del archivo se pierden. Truncar-existente-luego-escribir conserva los permisos y la propiedad, pero pierde datos en caso de un bloqueo y desliza la alfombra debajo de los pies de los programas que tienen el archivo abierto. No puedes combinar las partes buenas de ambos.
Gilles 'SO- deja de ser malvado'
1
@MartinvonWittich chownsolo funciona como root. chmody chgrppuede o no funcionar dependiendo de los permisos del usuario. Ninguno copia otros atributos como ACL o atributos extendidos específicos del sistema de archivos.
Gilles 'SO- deja de ser malvado'
10

Hay dos estrategias para reemplazar un archivo por una nueva versión:

  1. Cree un archivo temporal con la nueva versión, luego muévalo a su lugar.

    • Ventaja: si un programa abre ese archivo, leerá el contenido anterior o el nuevo contenido, dependiendo de si abrió el archivo antes o después del movimiento. No hay confusión.
    • Ventaja: en caso de bloqueo, se conserva el contenido antiguo.
    • Desventaja: dado que se crea un nuevo archivo, los atributos del archivo (propiedad, permiso, etc.) no se conservan.
  2. Sobrescriba el archivo antiguo en su lugar.

    • Ventaja: se preservan los atributos del archivo.
    • Desventaja: en caso de accidente, el archivo puede dejarse medio escrito.
    • Desventaja: si un programa tiene el archivo abierto cuando se está actualizando, este programa puede leer datos inconsistentes.

Si puede, use el método 1, pero primero replique los atributos del archivo original con cp -p --attributes-only. Esto requiere GNU coreutils (es decir, Linux no incrustado o entornos suficientemente similares a Linux). Si cpno tiene --attributes-only, omita esta opción: funcionará pero también replicará los datos.

tmp=$(mktemp)
cp -p --attributes-only "$original" "$tmp"
modifyfile "$original" "$tmp"
mv -f "$tmp" "$original"

Si no puede replicar los atributos del archivo existente, por ejemplo, porque tiene permisos de escritura pero no lo posee y desea conservar el propietario, entonces solo es posible el método 2. Para minimizar el riesgo de pérdida de datos:

  • Haga que la ventana durante la cual el archivo estará incompleto sea lo más pequeña posible. Prepare los datos primero en un archivo temporal, luego cópielos en su lugar.
  • Haga una copia de seguridad del archivo anterior primero.

tmp=$(mktemp)
backup="${original}~"
modifyfile "$original" "$tmp"
cp -p "$original" "$backup"
cp -f "$tmp" "$original"
Gilles 'SO- deja de ser malvado'
fuente
¡Buena respuesta! Hoy en día sugeriría usar el argumento --attributes-only con el comando cp en el Método 1 . De esta forma, cp -p --attributes-only "$original" "$tmp"no utilizará recursos para copiar el contenido del archivo. No pude encontrar de qué versión se agregó este argumento.
Marcelo Barros
@MarceloBarros Se agregó en GNU coreutils 8.6 lanzado el 15/10/2010, por lo que en estos días si tiene GNU coreutils debería tenerlo. Todavía no existe tal cosa con otras cpimplementaciones.
Gilles 'SO- deja de ser malvado'
5

Después de nuestra discusión sobre la primera respuesta, propongo una respuesta diferente:

TMP="$(mktemp "$original".XXXXXXXXXX)"
modifyfile "$original" "$TMP"
chmod --reference="$original" "$TMP"
chown --reference="$original" "$TMP"
mv -f "$TMP" "$original"

Observaciones:

  • Lo uso $originalen la mktempplantilla para asegurarme de que el archivo temporal no se encuentre /tmpen la misma carpeta que en $original. Creo que si /tmpse monta en un sistema de archivos diferente, la operación ya no sería atómica.
  • El resultado de mktempahora se cita en caso de que contenga espacios en blanco.
  • Lo uso en $()lugar de `` porque lo considero más limpio.
  • ch{mod,own} --referencese utilizan para transferir los permisos de $originala $TMP. Si alguien tiene ideas adicionales sobre qué metadatos pueden y deben transferirse, edite mi publicación y agréguela.
  • Oh, bueno, esto requiere permisos de root como Gilles señaló. Bueno, no voy a descartar esto ahora que lo he escrito: P
Martin von Wittich
fuente