Создать жесткую ссылку из дескриптора файла в Unix?

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

Например, что-то вроде этого:

fd = fopen("/tmp/foo", "w");
unlink("/tmp/foo");
fwrite(fd, "Hello, world!\n");
create_link_from_fd(fd, "/tmp/hello");
fclose(fd);

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

Ответы

Ответ 1

Как правило, нет. [ Изменить: начиная с Linux 3.11 теперь есть linkat; см. ответ safsaf32. Это не работает в системах POSIX вообще, поскольку POSIX linkat ограничивается только каталогами.] Здесь есть соображения безопасности: кто-то может передать вам дескриптор открытого файла, который вы обычно не могли бы open, например:

mkdir lock; chmod 700 lock
echo secret contents > lock/in
sudoish cmd < lock/in

Здесь cmd работает как пользователь, у которого нет разрешения на open входной файл (lock/in) по имени, но он все равно может читать его. Если cmd может создать новое имя в той же файловой системе, оно может передать содержимое файла на более поздний процесс. (Очевидно, что он может скопировать это содержимое, так что эта проблема скорее связана с "передачей содержимого по ошибке", чем "передайте содержимое по назначению".)

Тем не менее, люди придумали способы "переливания" файлов inode/vnode изнутри (это довольно легко сделать в большинстве файловых систем), поэтому вы можете сделать свой собственный системный вызов для него. Разумеется, дескриптор должен ссылаться на реальный файл в соответствующей точке монтирования - нет никакого способа "перезапуска" трубы или сокета или устройства в качестве обычного файла.

В противном случае вы застряли с "сигналами уловов и убираете и надеетесь на лучшее" или похожим трюком: "открутите подпроцесс, запустите его, и если он преуспеет/не удастся, выполните соответствующие шаги/очистку действие".


Изменить добавление исторической заметки: пример выше lock не особенно хорош, но еще во времена V6 Unix, MDQS использовал более интересную версию этого трюка. Сегодня битвы и куски MDQS выживают в различных формах.

Ответ 2

Недавно выпущенный linux 3.11 предлагает решение этой проблемы с новым флагом O_TMPFILE open(2). С помощью этого флага вы можете создать "невидимый" файл (т.е. Inode без hardlinks) в некоторой файловой системе (указанный в каталоге в этой файловой системе). Затем, после того как файл будет полностью настроен, вы можете создать жесткую ссылку, используя linkat. Он работает следующим образом:

fd = open("/tmp", O_TMPFILE | O_RDWR, 0600);
// write something to the file here
// fchown()/fchmod() it
linkat(fd, "", AT_FDCWD, "/tmp/test", AT_EMPTY_PATH);

Обратите внимание, что помимо требования ядрa >= 3.11 это также требует поддержки от базовой файловой системы (я пробовал приведенный выше фрагмент на ext3, и он работал, но он не работал на btrfs).

Ответ 3

В Linux вы можете попробовать unportable трюк использования /proc/self/fd, попробовав вызвать

 char pbuf[64];
 snprintf (pbuf, sizeof(pbuf), "/proc/self/fd/%d", fd);
 link(pbuf, "/tmp/hello");

Я был бы удивлен, если бы этот трюк работал после unlink("/tmp/foo")... Я не пробовал этого.

Более портативный (но менее надежный) способ заключается в создании "уникального временного пути", возможно, как

 int p = (int) getpid();
 int t = (int) time(0);
 int r = (int) random();
 sprintf(pbuf, sizeof(pbuf), "/tmp/out-p%d-r%d-t%d.tmp", p, r, t);
 int fd = open  (pbuf, O_CREAT|O_WRONLY);

После того, как файл был написан и закрыт, вы rename(2) переходите к более разумному пути. Вы можете использовать atexit в своей программе для переименования (или удаления).

И попробуйте выполнить cron задание [old] /tmp/out*.tmp каждый час...