2010-11-13 18 views
28

En Unix, es posible crear un identificador para un archivo anónimo, por ejemplo, crear y abrirlo con creat() y luego eliminar el enlace de directorio con unlink() - dejándolo con un archivo con inodo y almacenamiento, pero no hay forma posible de volver a abrirlo. Dichos archivos a menudo se usan como archivos temporales (y típicamente esto es lo que tmpfile() le devuelve).Volver a vincular un archivo anónimo (no vinculado pero abierto)

Mi pregunta: ¿hay alguna forma de volver a adjuntar un archivo como este en la estructura del directorio? Si pudiera hacer esto, significa que podría, por ejemplo, implemente las escrituras de archivo para que el archivo aparezca atómico y completamente formado. Esto apela a mi pulcritud compulsiva. ;)

Al examinar las funciones de llamada al sistema pertinentes esperaba encontrar una versión de link() llamada flink() (compárese con chmod()/fchmod()) pero, al menos en Linux, esto no existe .

Puntos de bonificación por decirme cómo crear el archivo anónimo sin exponer brevemente un nombre de archivo en la estructura de directorios del disco.

Respuesta

30

A patch for a proposed Linux flink() system call se presentó hace varios años, pero cuando Linus declaró "there is no way in HELL we can do this securely without major other incursions", eso prácticamente terminó el debate sobre si agregar esto.

Actualización: A partir de Linux 3.11, ahora es posible crear un archivo con ninguna entrada de directorio utilizando open() con el nuevo O_TMPFILE bandera, y vincularlo en el sistema de archivos, una vez que está completamente formado usando linkat() en /proc/self/fd/fd con la bandera AT_SYMLINK_FOLLOW.

El siguiente ejemplo se proporciona en la página del manual open():

char path[PATH_MAX]; 
    fd = open("/path/to/dir", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); 

    /* File I/O on 'fd'... */ 

    snprintf(path, PATH_MAX, "/proc/self/fd/%d", fd); 
    linkat(AT_FDCWD, path, AT_FDCWD, "/path/for/file", AT_SYMLINK_FOLLOW); 

Tenga en cuenta que linkat() no permitirá abrir archivos que se vuelven a unir después de que el último eslabón se elimina con unlink().

+0

Ta. Él propone una solución que también debería funcionar, fíjate. Aunque para la pulcritud compulsiva completa probablemente también necesites una forma de llamar a creat() en un directorio para que cree el archivo y el inodo pero no la entrada del directorio, de modo que nunca se vincule en primer lugar. – ijw

+0

La actualización está llena de victoria. No puedo +2 pero lo haría si pudiera. – ijw

+0

Confusamente, 'linkat()' da 'ENOENT' en intentos de volver a adjuntar un archivo normal abierto pero no vinculado. (con 'AT_SYMLINK_FOLLOW' o 'AT_EMPTY_PATH') –

1

Mi pregunta: ¿hay alguna manera de volver a adjuntar un archivo como este a la estructura del directorio? Si pudiera hacer esto, significa que podría, por ejemplo, implemente las escrituras de archivo para que el archivo aparezca atómico y completamente formado. Esto apela a mi pulcritud compulsiva. ;)

Si este es su único objetivo, puede lograrlo de una manera mucho más simple y más ampliamente utilizada. Si está dando salida a a.dat:

  1. abierto a.dat.part para escritura.
  2. Escriba sus datos.
  3. Renombre a.dat.part a a.dat.

Entiendo que quieras ser limpio, pero desvincular un archivo y volver a vincularlo solo para ser "ordenado" es algo tonto.

This question on serverfault parece indicar que este tipo de enlace no es seguro y no es compatible.

+0

cdhowie es justo que simplemente escribir en un archivo temporal es mucho mejor. Tenga en cuenta que la pregunta con la que se vincula básicamente dice que no se puede hacer: no se puede enlazar de '/ proc' a otro sistema de archivos. – poolie

+0

@poolie De alguna manera me lo perdí. Cambió los enlaces a una pregunta más apropiada sobre serverfault. – cdhowie

+2

La diferencia es que en la pregunta de default del servidor, el programa es algo opaco (al ser un foro sysadmin y todo, aquí estoy hablando de tener el manejador del archivo a punto de jugar programáticamente desde el proceso. Si puedes excluirlo categóricamente también, tenemos una respuesta;) – ijw

-1

Claramente, esto es posible - fsck lo hace, por ejemplo. Sin embargo, fsck lo hace con el principal sistema de archivos localizado mojo y claramente no será portátil ni ejecutable como un usuario sin privilegios. Es similar al comentario debugfs anterior.

Escribir que flink(2) llamada sería un ejercicio interesante. Como señala ijw, ofrecería algunas ventajas sobre la práctica actual de cambio de nombre de archivo temporal (cambiar el nombre, nota, se garantiza atómico).

-2

Llegué tarde al juego pero acabo de encontrar http://computer-forensics.sans.org/blog/2009/01/27/recovering-open-but-unlinked-file-data que puede responde la pregunta. No lo he probado, así que, YMMV. Se ve bien.

+1

Como esperaba, eso es solo 'cat/proc//fd/N> newfile'. Limpio si no sabías sobre/proc/fd, pero no la respuesta a esta pregunta. Los cambios posteriores en el archivo eliminado no se reflejarán después de la instantánea que se obtiene con 'cp' o' cat'. ('tail -c +1 -f/proc//fd/N> newfile' debe dejarle una copia de los contenidos, si el proceso de escritura solo se agrega). –

1

Gracias a @ mark4o publicando sobre linkat(2), vea su respuesta para más detalles.

Quería probarlo para ver qué sucedió realmente al intentar vincular un archivo anónimo al sistema de archivos en el que está almacenado. (a menudo /tmp, por ejemplo, para datos de video que Firefox está reproduciendo).


A partir de Linux 3.16, parece que todavía no hay forma de recuperar un archivo eliminado que aún se mantiene abierto. Ni AT_SYMLINK_FOLLOW ni AT_EMPTY_PATH para linkat(2) hacen el truco para los archivos eliminados que solían tener un nombre, incluso como raíz.

La única alternativa es tail -c +1 -f /proc/19044/fd/1 > data.recov, que hace una copia por separado, y tiene que eliminarla manualmente cuando termina.


Aquí está la envoltura de perl que preparé para la prueba. Use strace -eopen,linkat linkat.pl - </proc/.../fd/123 newname para verificar que su sistema aún no pueda recuperar los archivos abiertos. (Lo mismo aplica incluso con sudo). Obviamente, debería leer el código que encuentra en Internet antes de ejecutarlo, o usar una cuenta de espacio aislado.

#!/usr/bin/perl -w 
# 2015 Peter Cordes <[email protected]> 
# public domain. If it breaks, you get to keep both pieces. Share and enjoy 

# Linux-only linkat(2) wrapper (opens "." to get a directory FD for relative paths) 
if ($#ARGV != 1) { 
    print "wrong number of args. Usage:\n"; 
    print "linkat old new \t# will use AT_SYMLINK_FOLLOW\n"; 
    print "linkat - <old new\t# to use the AT_EMPTY_PATH flag (requires root, and still doesn't re-link arbitrary files)\n"; 
    exit(1); 
} 

# use POSIX qw(linkat AT_EMPTY_PATH AT_SYMLINK_FOLLOW); #nope, not even POSIX linkat is there 

require 'syscall.ph'; 
use Errno; 
# /usr/include/linux/fcntl.h 
# #define AT_SYMLINK_NOFOLLOW 0x100 /* Do not follow symbolic links. */ 
# #define AT_SYMLINK_FOLLOW 0x400 /* Follow symbolic links. */ 
# #define AT_EMPTY_PATH  0x1000 /* Allow empty relative pathname */ 
unless (defined &AT_SYMLINK_NOFOLLOW) { sub AT_SYMLINK_NOFOLLOW() { 0x0100 } } 
unless (defined &AT_SYMLINK_FOLLOW ) { sub AT_SYMLINK_FOLLOW () { 0x0400 } } 
unless (defined &AT_EMPTY_PATH  ) { sub AT_EMPTY_PATH  () { 0x1000 } } 


sub my_linkat ($$$$$) { 
    # tmp copies: perl doesn't know that the string args won't be modified. 
    my ($oldp, $newp, $flags) = ($_[1], $_[3], $_[4]); 
    return !syscall(&SYS_linkat, fileno($_[0]), $oldp, fileno($_[2]), $newp, $flags); 
} 

sub linkat_dotpaths ($$$) { 
    open(DOTFD, ".") or die "open . $!"; 
    my $ret = my_linkat(DOTFD, $_[0], DOTFD, $_[1], $_[2]); 
    close DOTFD; 
    return $ret; 
} 

sub link_stdin ($) { 
    my ($newp,) = @_; 
    open(DOTFD, ".") or die "open . $!"; 
    my $ret = my_linkat(0, "", DOTFD, $newp, &AT_EMPTY_PATH); 
    close DOTFD; 
    return $ret; 
} 

sub linkat_follow_dotpaths ($$) { 
    return linkat_dotpaths($_[0], $_[1], &AT_SYMLINK_FOLLOW); 
} 


## main 
my $oldp = $ARGV[0]; 
my $newp = $ARGV[1]; 

# link($oldp, $newp) or die "$!"; 
# my_linkat(fileno(DIRFD), $oldp, fileno(DIRFD), $newp, AT_SYMLINK_FOLLOW) or die "$!"; 

if ($oldp eq '-') { 
    print "linking stdin to '$newp'. You will get ENOENT without root (or CAP_DAC_READ_SEARCH). Even then doesn't work when links=0\n"; 
    $ret = link_stdin($newp); 
} else { 
    $ret = linkat_follow_dotpaths($oldp, $newp); 
} 
# either way, you still can't re-link deleted files (tested Linux 3.16 and 4.2). 

# print STDERR 
die "error: linkat: $!.\n" . ($!{ENOENT} ? "ENOENT is the error you get when trying to re-link a deleted file\n" : '') unless $ret; 

# if you want to see exactly what happened, run 
# strace -eopen,linkat linkat.pl 
Cuestiones relacionadas