2010-11-13 8 views
28

In Unix, è possibile creare un handle per un file anonimo, ad esempio, creando e aprendolo con creat() e quindi rimuovendo il collegamento alla directory con unlink() - lasciandovi con un file con inode e memoria ma nessun modo possibile per riaprirlo. Tali file sono spesso usati come file temporanei (e in genere questo è ciò che tmpfile() restituisce).Ricollegamento di un file anonimo (scollegato ma aperto)

La mia domanda: c'è un modo per ricollegare un file come questo nella struttura delle directory? Se puoi farlo, significa che potresti, ad es. implementare le scritture di file in modo che il file appaia atomicamente e completamente formato. Questo fa appello alla mia pulizia compulsiva. ;)

Durante l'esecuzione delle relative funzioni di chiamata di sistema mi aspettavo di trovare una versione di link() chiamata flink() (confronta con chmod()/fchmod()) ma, almeno su Linux, questo non esiste .

Punti bonus per dirmi come creare il file anonimo senza esporre brevemente un nome file nella struttura di directory del disco.

risposta

30

A patch for a proposed Linux flink() system call è stato inviato diversi anni fa, ma quando Linus ha dichiarato "there is no way in HELL we can do this securely without major other incursions", questo ha praticamente chiuso il dibattito sull'opportunità di aggiungere questo.

Aggiornamento: Come di Linux 3.11, è ora possibile creare un file con alcuna voce directory utilizzando open() con il nuovo O_TMPFILE bandiera, e collegarlo al filesystem una volta che è completamente formata utilizzando linkat() su /proc/self/fd/fd con il flag AT_SYMLINK_FOLLOW.

Il seguente esempio è fornito sulla pagina di manuale 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); 

Nota che linkat() non permetterà file aperti per essere nuovamente attaccati dopo l'ultimo anello viene rimosso con unlink().

+0

Ta. Propone una soluzione che dovrebbe funzionare anche, attenzione. Anche se per la piena pulizia compulsiva probabilmente hai anche bisogno di un modo per chiamare creat() su una directory in modo che crei il file e l'inode ma non la voce della directory, in modo che non sia mai collegato in primo luogo. – ijw

+0

L'aggiornamento è pieno di vittorie. Non posso +2 tu ma lo farei se potessi. – ijw

+0

Confusamente, 'linkat()' fornisce 'ENOENT' sui tentativi di riattaccare un normale file aperto ma non collegato. (con o 'AT_SYMLINK_FOLLOW' o 'AT_EMPTY_PATH') –

1

La mia domanda: c'è un modo per ricollegare un file come questo nella struttura delle directory? Se puoi farlo, significa che potresti, ad es. implementare le scritture di file in modo che il file appaia atomicamente e completamente formato. Questo fa appello alla mia pulizia compulsiva. ;)

Se questo è l'unico obiettivo, è possibile ottenere questo in un modo molto più semplice e più ampiamente utilizzato. Se si viene passato a a.dat:

  1. Aperto a.dat.part per la scrittura.
  2. Scrivi i tuoi dati.
  3. Rinomina a.dat.part a a.dat.

Riesco a capire che voglio essere pulito, ma scollegare un file e ricollegarlo solo per essere "pulito" è una specie di sciocco.

This question on serverfault sembra indicare che questo tipo di ricollegamento non è sicuro e non è supportato.

+0

cdhowie ha ragione che scrivere in un file temporaneo è molto meglio. Nota che la domanda a cui ti colleghi in pratica dice che non può essere fatta però: non puoi eseguire il collegamento da '/ proc' a un altro file system. – poolie

+0

@poolie In qualche modo mi sono perso. Passa a una domanda più appropriata su serverfault. – cdhowie

+2

La differenza è che nella domanda serverfault il programma è una cosa opaca (essendo un forum di sysadmin e tutto - qui sto parlando di avere effettivamente il file che gestisce per giocare con programmaticamente dall'interno del processo.) Se è possibile escluderlo categoricamente anche noi abbiamo una risposta;) – ijw

-1

Chiaramente, questo è possibile - fsck lo fa, per esempio. Tuttavia, fsck lo fa con il mojo principale del file system localizzato e chiaramente non sarà portatile, né eseguibile come utente non privilegiato. È simile al commento debugfs sopra.

Scrivere che la chiamata flink(2) sarebbe un esercizio interessante. Come sottolinea Ijw, offrirebbe alcuni vantaggi rispetto alla pratica corrente di rinominazione file temporanea (rinomina, nota, è garantita atomica).

-2

Tipo di ritardo nel gioco ma ho appena trovato http://computer-forensics.sans.org/blog/2009/01/27/recovering-open-but-unlinked-file-data che può rispondere alla domanda. Non l'ho ancora testato, quindi, YMMV. Sembra sano.

+1

Come mi aspettavo, è solo 'cat/proc//fd/N> newfile'. Niente se non sapessi su/proc/fd, ma non la risposta a questa domanda. Ulteriori modifiche al file cancellato non saranno riflesse dopo l'istantanea ottenuta con 'cp' o' cat'. ('tail -c +1 -f/proc//fd/N> newfile' dovrebbe lasciarvi una copia dei contenuti, se il processo di scrittura li aggiunge, però.) –

1

Grazie al post @ mark4o su linkat(2), vedere la sua risposta per i dettagli.

Volevo fare un tentativo per vedere cosa è successo effettivamente quando si cerca di collegare effettivamente un file anonimo nel file system su cui è stato salvato. (spesso /tmp, ad esempio per i dati video su cui è in esecuzione Firefox).


A partire da Linux 3.16, sembra ancora che non ci sia modo di ripristinare un file cancellato che è ancora aperto. Né AT_SYMLINK_FOLLOWAT_EMPTY_PATH per linkat(2) esegui il trucco per i file eliminati che avevano un nome, anche come root.

L'unica alternativa è tail -c +1 -f /proc/19044/fd/1 > data.recov, che crea una copia separata, e devi ucciderla manualmente quando è terminata.


Ecco il wrapper perl che ho preparato per il test. Utilizzare strace -eopen,linkat linkat.pl - </proc/.../fd/123 newname per verificare che il sistema non sia ancora in grado di ripristinare i file aperti. (Lo stesso vale anche con sudo). Ovviamente dovresti leggere il codice che trovi su Internet prima di eseguirlo o utilizzare un account sandbox.

#!/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 
Problemi correlati