2010-11-13 4 views
28

In Unix ist es möglich, ein Handle für eine anonyme Datei zu erstellen, indem Sie es beispielsweise mit creat() erstellen und öffnen und dann den Verzeichnislink mit unlink() entfernen mit einer Datei mit einem Inode und Speicher, aber keine Möglichkeit, es wieder zu öffnen. Solche Dateien werden oft als temporäre Dateien verwendet (und normalerweise liefert tmpfile() dies an Sie zurück).Erneutes Verknüpfen einer anonymen (unverknüpften, aber geöffneten) Datei

Meine Frage: Gibt es eine Möglichkeit, eine Datei wie diese zurück in die Verzeichnisstruktur zu verbinden? Wenn Sie dies tun könnten, bedeutet dies, dass Sie z. Implementieren von Dateischreibvorgängen, sodass die Datei atomar und vollständig formatiert erscheint. Dies appelliert an meine zwanghafte Ordentlichkeit. ;)

Beim Durchstöbern der relevanten Systemaufruffunktionen erwartete ich, eine Version von link() namens flink() zu finden (vergleiche mit chmod()/fchmod()), aber zumindest unter Linux existiert das nicht .

Bonuspunkte für das Erstellen der anonymen Datei, ohne einen Dateinamen in der Verzeichnisstruktur des Datenträgers kurz anzuzeigen.

Antwort

30

A patch for a proposed Linux flink() system call wurde vor einigen Jahren eingereicht, aber als Linus "there is no way in HELL we can do this securely without major other incursions" erklärte, beendete das ziemlich die Debatte darüber, ob das hinzugefügt werden soll.

Update: Ab Linux 3.11 ist es nun möglich, eine Datei ohne Verzeichniseintrag erstellen mit open() mit der neuen O_TMPFILE Flagge, und verbinden Sie sie in das Dateisystem, sobald es vollständig mit linkat() auf /proc/self/fd/fd gebildet wird, mit der AT_SYMLINK_FOLLOW Flagge.

Im folgenden Beispiel wird auf der open() Handbuch Seite zur Verfügung gestellt:

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); 

Beachten Sie, dass linkat() das letzte Glied mit unlink() wird entfernt, nachdem ermöglicht Dateien nicht öffnen, wieder angebracht werden.

+0

Ta. Er schlägt eine Lösung vor, die auch funktionieren sollte, wohlgemerkt. Für eine zwanghafte Ordentlichkeit brauchen Sie wahrscheinlich auch einen Weg, creat() in einem Verzeichnis anzurufen, so dass es die Datei und den Inode erstellt, aber nicht den Verzeichniseintrag, so dass es nie an erster Stelle verlinkt ist. – ijw

+0

Das Update ist voller Gewinn. Ich kann dich nicht +2 aber ich würde wenn ich könnte. – ijw

+0

Verwirrend, 'linkat()' gibt 'ENOENT' bei Versuchen, eine normale offene aber nicht verknüpfte Datei erneut anzuhängen. (mit entweder 'AT_SYMLINK_FOLLOW' oder' AT_EMPTY_PATH') –

1

Meine Frage: Gibt es eine Möglichkeit, eine Datei wie diese zurück in die Verzeichnisstruktur zu verbinden? Wenn Sie dies tun könnten, bedeutet dies, dass Sie z. Implementieren von Dateischreibvorgängen, sodass die Datei atomar und vollständig formatiert erscheint. Dies appelliert an meine zwanghafte Ordentlichkeit. ;)

Wenn dies Ihr einziges Ziel ist, können Sie dies in einer viel einfacheren und weiter verbreiteten Weise erreichen. Wenn Sie die Ausgabe auf a.dat:

  1. öffnen a.dat.part für Schreib.
  2. Schreiben Sie Ihre Daten.
  3. Umbenennen a.dat.part zu a.dat.

Ich kann verstehen, ordentlich zu sein, aber eine Datei zu entkoppeln und neu zu verknüpfen, nur um "ordentlich" zu sein, ist irgendwie albern.

This question on serverfault scheint anzuzeigen, dass diese Art der Neuverknüpfung unsicher ist und nicht unterstützt wird.

+0

cdhowie hat Recht, dass das Schreiben in eine temporäre Datei viel besser ist. Beachten Sie, dass die Frage, zu der Sie verlinken, grundsätzlich besagt, dass sie nicht ausgeführt werden kann: Sie können nicht von '/ proc' auf ein anderes Dateisystem verlinken. – poolie

+0

@poolie Irgendwie habe ich das vermisst. Switched Links zu einer geeigneteren Frage auf Serverfault. – cdhowie

+2

Der Unterschied ist, dass das Programm in der Serverfault-Frage ein undurchsichtiges Ding ist (ein Sysadmin-Forum und alle - hier spreche ich über das Dateihandle, mit dem programmatisch aus dem Prozess heraus gespielt wird. Wenn Sie das kategorisch ausschließen können auch, wir haben eine Antwort;) – ijw

-1

Klar ist dies möglich - fsck macht es zum Beispiel. Allerdings tut fsck es mit großen lokalisierten Dateisystem Mojo und wird eindeutig nicht tragbar sein, noch als nichtprivilegierter Benutzer ausführbar. Es ist ähnlich dem debugfs Kommentar oben.

Schreiben, dass flink(2) Aufruf wäre eine interessante Übung. Wie IJW hervorhebt, würde es einige Vorteile gegenüber der derzeitigen Praxis der temporären Dateiumbenennung bieten (Umbenennen, Notiz, ist atomar garantiert).

-2

Art von spät in das Spiel, aber ich habe gerade http://computer-forensics.sans.org/blog/2009/01/27/recovering-open-but-unlinked-file-data gefunden, die kann beantworten Sie die Frage. Ich habe es jedoch nicht getestet, also YMMV. Es sieht gut aus.

+1

Wie ich erwartet habe, ist das nur "cat/proc//fd/N> newfile". Ordentlich, wenn Sie nicht über/proc/fd wussten, aber nicht die Antwort auf diese Frage. Weitere Änderungen an der gelöschten Datei werden nach dem Snapshot, den Sie mit 'cp' oder' cat' erhalten, nicht wiedergegeben. ("tail -c + -f/proc//fd/N> newfile" sollte Ihnen eine Kopie des Inhalts hinterlassen, wenn der Prozess, der es schreibt, nur angehängt wird.) –

1

Dank @ Mark4o Posting über linkat(2), finden Sie in seiner Antwort für Details.

Ich wollte versuchen zu sehen, was tatsächlich passiert ist, als ich versuchte, eine anonyme Datei wieder in das Dateisystem zu verlinken, auf dem sie gespeichert ist. (oft /tmp, beispielsweise für Videodaten, die Firefox spielt).

Ab Linux 3.16 scheint es immer noch keine Möglichkeit zu geben, eine gelöschte Datei wiederherzustellen, die noch offen ist. Weder AT_SYMLINK_FOLLOW noch AT_EMPTY_PATH für linkat(2) machen Sie den Trick für gelöschte Dateien, die einen Namen hatten, auch als root.

Die einzige Alternative ist tail -c +1 -f /proc/19044/fd/1 > data.recov, die eine separate Kopie macht, und Sie müssen es manuell beenden, wenn es fertig ist.


Hier ist die Perl-Wrapper ich zum Testen gekocht. Verwenden Sie strace -eopen,linkat linkat.pl - </proc/.../fd/123 newname, um zu überprüfen, ob das System geöffnete Dateien nicht wiederherstellen kann. (Gleiches gilt auch für sudo). Offensichtlich sollten Sie den Code lesen, den Sie im Internet finden, bevor Sie ihn ausführen, oder ein Sandbox-Konto verwenden.

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