Подтвердить что ты не робот

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

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

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

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

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

4b9b3361

Ответ 1

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

Обновление: Начиная с Linux 3.11, теперь можно создать файл без записи каталога, используя open() с новым флагом O_TMPFILE и связать его с файловой системой, когда она полностью сформирована с помощью linkat() на /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().

Ответ 2

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

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

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

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

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

Ответ 3

Благодаря сообщению @mark4o о linkat(2), см. его ответ для деталей.

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


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

Единственная альтернатива - это 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

Ответ 4

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

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