Повторное связывание анонимного (несвязанного, но открытого) файла

В Unix можно создать дескриптор анонимного файла, например, создав и открыв его с помощью creat (), а затем удалив ссылку на каталог с помощью unlink () - оставив вам файл с индексным дескриптором и хранилищем, но без возможности чтобы снова открыть его. Такие файлы часто используются как временные файлы (и обычно это то, что вам возвращает tmpfile ()).

Мой вопрос: есть ли способ повторно прикрепить такой файл обратно в структуру каталогов? Если бы вы могли это сделать, это означает, что вы могли бы, например, реализовать запись в файл так, чтобы файл выглядел атомарно и полностью сформированным. Это взывает к моей навязчивой аккуратности. ;)

Просматривая соответствующие функции системного вызова, я ожидал найти версию link () под названием flink () (сравните с chmod () / fchmod ()), но, по крайней мере, в Linux этого не существует.

Бонусные баллы за то, что рассказали мне, как создать анонимный файл без краткого раскрытия имени файла в структуре каталогов диска.


person ijw    schedule 13.11.2010    source источник


Ответы (5)


Патч для предложенного flink() системного вызова Linux был представлен несколько лет назад, но когда Линус заявил "в АД нет способа сделать это безопасно без серьезных других вторжений" , что в значительной степени положило конец дебатам о том, стоит ли это добавлять.

Обновление. Начиная с Linux 3.11, теперь можно создать файл без записи в каталоге, используя _ 2_ с новым флагом O_TMPFILE и связать его с файловой системой, как только она будет полностью сформирована, с помощью _ 4_ на /proc/self/fd/ fd с флагом AT_SYMLINK_FOLLOW.

Следующий пример представлен на странице руководства 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);

Обратите внимание, что linkat() не позволит повторно прикрепить открытые файлы после удаления последней ссылки с помощью unlink().

person mark4o    schedule 13.11.2010
comment
Та. Он предлагает решение, которое, заметьте, тоже должно сработать. Хотя для полной компульсивной аккуратности вам, вероятно, также понадобится способ вызова creat () в каталоге, чтобы он создавал файл и индексный дескриптор, но не запись в каталоге, чтобы он никогда не был связан. - person ijw; 23.11.2010
comment
Обновление полно побед. Я не могу +2 тебе, но я бы сделал, если бы мог. - person ijw; 31.05.2014
comment
Как ни странно, linkat() выдает ENOENT при попытках повторно прикрепить обычный открытый, но несвязанный файл. (с AT_SYMLINK_FOLLOW или AT_EMPTY_PATH) - person Peter Cordes; 22.02.2015
comment
Я опубликовал оболочку perl (которая на самом деле бесполезна, поскольку вы все еще не можете повторно связать файлы без существующих ссылок) в качестве отдельного ответа. - person Peter Cordes; 22.02.2015
comment
Можно ли linkat также использовать в системе, в которой нет /proc (например, macOS)? Если да, то каким будет первый аргумент пути? - person splicer; 25.05.2017
comment
@splicer / dev / fd / fd. Но это (похоже) не работает, так как выдает Cross-device link ошибку. - person mattsven; 12.08.2019

Мой вопрос: есть ли способ повторно прикрепить такой файл обратно в структуру каталогов? Если бы вы могли это сделать, это означает, что вы могли бы, например, реализовать запись в файл так, чтобы файл выглядел атомарно и полностью сформированным. Это взывает к моей навязчивой аккуратности. ;)

Если это ваша единственная цель, вы можете достичь ее гораздо более простым и более широко используемым способом. Если вы выводите на a.dat:

  1. Откройте a.dat.part для записи.
  2. Напишите свои данные.
  3. Переименуйте a.dat.part в a.dat.

Я понимаю, что хочу быть аккуратным, но разорвать связь с файлом и заново связать его просто для «аккуратности» - это своего рода глупость.

Этот вопрос о сбое сервера, похоже, указывает на то, что такое повторное связывание небезопасно и не поддерживается.

person cdhowie    schedule 13.11.2010
comment
cdhowie прав в том, что намного лучше просто писать во временный файл. Обратите внимание, что вопрос, на который вы ссылаетесь, в основном говорит, что это невозможно сделать: вы не можете жестко привязать /proc к другой файловой системе. - person poolie; 13.11.2010
comment
@poolie Как-то я это пропустил. Переключил ссылки на более подходящий вопрос по serverfault. - person cdhowie; 13.11.2010
comment
Разница в том, что в вопросе о сбое сервера программа является непрозрачной вещью (это форум системных администраторов и все такое - здесь я говорю о том, чтобы дескриптор файла мог программно поиграть внутри процесса. Если вы можете категорически исключить это тоже , у нас есть ответ;) - person ijw; 13.11.2010
comment
Кроме того, я не думаю, что это глупо. Файл имеет все преимущества временного файла - в основном отсутствует, гарантирован один писатель / читатель и т. Д. - до тех пор, пока вы не решите, что пора предоставить его пользователю. И он уйдет, если ваша программа тоже выйдет из строя, вместо того, чтобы быть поврежденным наполовину написанным файлом. - person ijw; 13.11.2010
comment
Желать это по своей сути не глупо, но немного глупо хотеть это делать, учитывая модель Unix и API, с которыми мы работаем сегодня. - person poolie; 17.11.2010

Благодаря публикации @ mark4o о linkat(2), подробности см. В его ответе.

Я хотел попробовать посмотреть, что на самом деле произошло при попытке связать анонимный файл обратно с файловой системой, в которой он хранится. (часто /tmp, например, для видеоданных, воспроизводимых firefox).


Начиная с Linux 3.16, по-прежнему нет возможности восстановить удаленный файл, который все еще остается открытым. Ни AT_SYMLINK_FOLLOW, ни AT_EMPTY_PATH для linkat(2) не работают с удаленными файлами, у которых раньше было имя, даже если оно было корневым.

Единственная альтернатива - tail -c +1 -f /proc/19044/fd/1 > data.recov, которая делает отдельную копию, и вам нужно убить ее вручную, когда это будет сделано.


Вот оболочка perl, которую я приготовил для тестирования. Используйте strace -eopen,linkat linkat.pl - </proc/.../fd/123 newname, чтобы убедиться, что ваша система по-прежнему не может восстановить открытые файлы. (То же самое применимо даже к sudo). Очевидно, вам следует прочитать код, который вы найдете в Интернете, перед его запуском или использовать изолированную учетную запись.

#!/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
person Peter Cordes    schedule 21.02.2015

Ясно, что это возможно - fsck это делает, например. Однако fsck делает это с основной локализованной файловой системой mojo и явно не будет ни переносимой, ни исполняемой от непривилегированного пользователя. Это похоже на комментарий debugfs выше.

Написание этого flink(2) вызова было бы интересным упражнением. Как указывает ijw, это даст некоторые преимущества по сравнению с нынешней практикой переименования временных файлов (переименование, примечание, гарантировано атомарно).

person mpez0    schedule 13.11.2010

Немного поздно в игре, но я только что нашел http://computer-forensics.sans.org/blog/2009/01/27/recovering-open-but-unlinked-file-data, который может ответить на вопрос . Я не тестировал его, так что YMMV. Выглядит неплохо.

person Jim    schedule 30.08.2013
comment
Как я и ожидал, это всего лишь cat /proc/<pid>/fd/N > newfile. Хорошо, если вы не знали о / proc / fd, но не знаете ответа на этот вопрос. Дальнейшие изменения удаленного файла не будут отражены после снимка, полученного с помощью cp или cat. (tail -c +1 -f /proc/<pid>/fd/N > newfile должен оставить вам копию содержимого, хотя процесс, записывающий его, только добавляет.) - person Peter Cordes; 21.02.2015