2010-11-13 38 views
28

Trong Unix, bạn có thể tạo một xử lý cho một tệp ẩn danh bằng cách tạo và mở nó bằng hàm create() rồi xóa liên kết thư mục bằng cách hủy liên kết() - để lại cho bạn với tệp có inode và bộ nhớ nhưng không thể mở lại được. Các tệp như vậy thường được sử dụng như các tệp tạm thời (và thông thường đây là những gì tmpfile() trả về cho bạn).Liên kết một tệp ẩn danh (chưa được liên kết nhưng mở)

Câu hỏi của tôi: có cách nào để đính kèm lại tệp như thế này trở lại cấu trúc thư mục không? Nếu bạn có thể làm điều này, điều đó có nghĩa là bạn có thể triển khai tệp ghi để tệp xuất hiện nguyên tử và được tạo thành đầy đủ. Điều này thu hút sự gọn gàng của tôi. ;)

Khi chọc qua các hàm gọi hệ thống có liên quan, tôi dự kiến ​​tìm một phiên bản liên kết() được gọi là flink() (so sánh với chmod()/fchmod()) nhưng ít nhất trên Linux .

Điểm thưởng để cho tôi biết cách tạo tệp ẩn danh mà không hiển thị tên tệp trong cấu trúc thư mục của đĩa.

Trả lời

30

A patch for a proposed Linux flink() system call đã được gửi cách đây vài năm, nhưng khi Linus tuyên bố "there is no way in HELL we can do this securely without major other incursions", điều đó đã kết thúc khá nhiều cuộc tranh luận về việc có nên thêm điều này hay không.

Cập nhật: Tính đến Linux 3.11, bây giờ có thể để tạo ra một tập tin với không có mục nhập thư mục sử dụng open() với O_TMPFILE cờ mới, và liên kết nó vào hệ thống tập tin khi nó hoàn toàn được hình thành bằng linkat() trên /proc/self/fd/fd với cờ AT_SYMLINK_FOLLOW.

Ví dụ sau đây được cung cấp trên open() trang hướng dẫn:

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

Lưu ý rằng linkat() sẽ không cho phép mở các tập tin phải được tái gắn sau khi liên kết cuối cùng được lấy ra với unlink().

+0

Ta. Ông đề xuất một giải pháp mà cũng nên làm việc, tâm trí bạn. Mặc dù cho sự gọn gàng đầy đủ ép buộc bạn cũng có thể cần một cách để gọi creat() trên một thư mục để nó tạo ra tệp và inode nhưng không phải là mục nhập thư mục, do đó nó không bao giờ được liên kết ở nơi đầu tiên. – ijw

+0

Bản cập nhật đầy chiến thắng. Tôi không thể +2 bạn nhưng tôi sẽ làm nếu tôi có thể. – ijw

+0

Nhạy cảm, 'linkat()' cung cấp cho 'ENOENT' trên các nỗ lực đính kèm lại tệp mở-nhưng-hủy liên kết bình thường. (với 'AT_SYMLINK_FOLLOW' hoặc 'AT_EMPTY_PATH') –

1

Câu hỏi của tôi: có cách nào để đính kèm lại tệp như thế này trở lại cấu trúc thư mục không? Nếu bạn có thể làm điều này, điều đó có nghĩa là bạn có thể triển khai tệp ghi để tệp xuất hiện nguyên tử và được tạo thành đầy đủ. Điều này thu hút sự gọn gàng của tôi. ;)

Nếu đây là mục tiêu duy nhất của bạn, bạn có thể đạt được điều này theo cách đơn giản hơn và được sử dụng rộng rãi hơn. Nếu bạn xuất ra a.dat:

  1. Mở a.dat.part để viết.
  2. Ghi dữ liệu của bạn.
  3. Đổi tên a.dat.part thành a.dat.

Tôi có thể hiểu muốn gọn gàng, nhưng việc hủy liên kết tệp và liên kết lại chỉ để "gọn gàng" là ngớ ngẩn.

This question on serverfault dường như chỉ ra rằng loại liên kết lại này không an toàn và không được hỗ trợ.

+0

cdhowie đúng là chỉ viết vào tệp tạm thời tốt hơn nhiều. Lưu ý rằng câu hỏi bạn liên kết về cơ bản nói rằng nó không thể được thực hiện mặc dù: bạn không thể hardlink từ '/ proc' để hệ thống tập tin khác. – poolie

+0

@poolie Bằng cách nào đó tôi đã bỏ lỡ điều đó. Chuyển các liên kết đến một câu hỏi thích hợp hơn trên serverfault. – cdhowie

+2

Sự khác biệt là trong câu hỏi serverfault chương trình là một điều mờ đục (là một diễn đàn sysadmin và tất cả - ở đây tôi đang nói về thực sự có các tập tin xử lý về để chơi với programatically từ bên trong quá trình này.Nếu bạn categorically loại trừ rằng chúng tôi cũng có câu trả lời;) – ijw

-1

Rõ ràng, điều này là có thể - ví dụ: fsck. Tuy nhiên, fsck thực hiện với mojo hệ thống tệp được bản địa hoá lớn và rõ ràng không thể di chuyển được, cũng như không thể thực thi như một người dùng không có đặc quyền. Nó tương tự như chú thích debugfs ở trên.

Viết rằng flink(2) cuộc gọi sẽ là một bài tập thú vị. Như ijw chỉ ra, nó sẽ cung cấp một số lợi thế hơn thực tế hiện tại của đổi tên tập tin tạm thời (đổi tên, lưu ý, được đảm bảo nguyên tử).

-2

Loại cuối trò chơi nhưng tôi chỉ tìm thấy http://computer-forensics.sans.org/blog/2009/01/27/recovering-open-but-unlinked-file-datacó thể trả lời câu hỏi. Tôi chưa thử nghiệm nó, vì vậy, YMMV. Có vẻ như âm thanh.

+1

Như tôi mong đợi, đó chỉ là 'cat/proc//fd/N> newfile'. Neat nếu bạn không biết về/proc/fd, nhưng không phải là câu trả lời cho câu hỏi này. Các thay đổi khác đối với tệp đã xóa sẽ không được phản ánh sau ảnh chụp bạn nhận được bằng 'cp' hoặc' cat'. ('đuôi -c +1 -f/proc//fd/N> newfile' sẽ để lại cho bạn một bản sao của nội dung, nếu quá trình viết nó chỉ chắp thêm, mặc dù.) –

1

Nhờ @ mark4o đăng bài về số linkat(2), hãy xem câu trả lời của anh ấy để biết chi tiết.

Tôi muốn thử xem điều gì đã thực sự xảy ra khi cố gắng liên kết tệp ẩn danh với hệ thống tệp được lưu trữ trên đó. (thường là /tmp, ví dụ: đối với dữ liệu video mà firefox đang phát).


Kể từ Linux 3.16, dường như không có cách nào để lấy lại tệp đã xóa vẫn được mở. Không phải là AT_SYMLINK_FOLLOW cũng không phải AT_EMPTY_PATH cho linkat(2) thực hiện thủ thuật cho các tệp đã xóa đã từng có tên, ngay cả dưới dạng gốc.

Cách thay thế duy nhất là tail -c +1 -f /proc/19044/fd/1 > data.recov, tạo bản sao riêng biệt và bạn phải tự xóa nó khi hoàn thành.


Đây là trình bao bọc perl mà tôi đã nấu để thử nghiệm. Sử dụng strace -eopen,linkat linkat.pl - </proc/.../fd/123 newname để xác minh rằng hệ thống của bạn vẫn không thể phục hồi các tệp đang mở. (Tương tự áp dụng ngay cả với sudo). Rõ ràng bạn nên đọc mã bạn tìm thấy trên Internet trước khi chạy nó, hoặc sử dụng một tài khoản sandboxed.

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