Ответ 1
Что касается POSIX, R.. ответ на соответствующий вопрос, то это очень ясно и красно: close()
является не перезапускаемым частным случаем, и цикл не должен использоваться.
Это было удивительно для меня, поэтому я решил описать свои выводы, после чего мои выводы и выбранное решение было завершено.
На самом деле это не ответ. Считайте это более похожим на мнение другого программиста, в том числе аргументы в пользу этого мнения.
POSIX.1-2001 и POSIX.1-2008 описывают три возможных значения errno, которые могут иметь место: EBADF
, EINTR
и EIO
. Состояние дескриптора после EINTR
и EIO
является "неуказанным", что означает, что оно может быть закрыто или, возможно, не было закрыто. EBADF
указывает, что fd
не является допустимым дескриптором. Другими словами, POSIX.1 явно рекомендует использовать
if (close(fd) == -1) {
/* An error occurred, see 'errno'. */
}
без повторной петли, чтобы закрыть дескрипторы файлов.
(Даже группа Austin Group дефект # 519, упомянутый R.. не помогает с восстановлением от ошибок close()
: он оставляет это не указано, возможен ли какой-либо ввод-вывод после ошибки EINTR
, даже если сам дескриптор оставлен открытым.)
Для Linux, syscall close()
определяется в fs/open.c, с __do_close()
в fs/file.c управление блокировкой таблицы дескрипторов и filp_close()
назад в fs/open.c заботясь о деталях.
Таким образом, запись дескриптора сначала удаляется из таблицы, а затем сбрасывается файловой системой (f_op->flush()
), за ней следует уведомление (dnotify/fsnotify hook) и, наконец, путем удаления любых блокировок записей или файлов. (Большинство локальных файловых систем, таких как ext2, ext3, ext4, xfs, bfs, tmpfs и т.д., Не имеют ->flush()
, поэтому с учетом действительного дескриптора close()
не может завершиться. Только ecryptfs, exofs, fuse, cifs и Насколько я знаю, nfs имеют обработчики ->flush()
в Linux-3.13.6.)
Это означает, что в Linux при возникновении ошибки записи в обработчике ->flush()
, специфичном для файловой системы, во время close()
невозможно повторить попытку; дескриптор файла всегда закрыт, как сказал Торвальдс.
В справочной странице FreeBSD close()
описывается то же самое поведение.
Ни OpenBSD, ни Mac OS X close()
описать, закрыт ли дескриптор в случае ошибок, но я считаю, что они разделяют поведение FreeBSD.
Мне кажется очевидным, что для закрытия файлового дескриптора не требуется или требуется цикл. Однако close()
может по-прежнему возвращать ошибку.
errno == EBADF
указывает, что дескриптор файла уже закрыт. Если мой код встречается неожиданно, для меня это указывает на существенную ошибку в логике кода, и процесс должен изящно выйти; Я предпочел бы, чтобы мои процессы умирали, а не мусор.
Любые другие значения errno
указывают на ошибку при завершении состояния файла. В Linux это определенно ошибка, связанная с очисткой любых оставшихся данных до фактического хранилища. В частности, я могу представить ENOMEM
, если нет места для буферизации данных, EIO
, если данные не могут быть отправлены или записаны на фактическое устройство или носитель, EPIPE
, если соединение с хранилищем было потеряно, ENOSPC
, если хранилище уже заполнено без резервирования нераспакованных данных и т.д. Если файл является файлом журнала, у меня будет отчет о сбое процесса и изящно выйти. Если содержимое файла все еще доступно в памяти, я удаляю (отсоединяю) весь файл и повторю попытку. В противном случае я сообщаю об ошибке пользователю.
(Помните, что в Linux и FreeBSD вы не "просачиваете" дескрипторы файлов в случае ошибки, они гарантированно закрываются, даже если возникает ошибка. Я предполагаю, что все другие операционные системы, которые я могу использовать, ведут себя одинаково.)
Вспомогательная функция, которую я буду использовать с этого момента, будет похожа на
#include <unistd.h>
#include <errno.h>
/**
* closefd - close file descriptor and return error (errno) code
*
* @descriptor: file descriptor to close
*
* Actual errno will stay unmodified.
*/
static int closefd(const int descriptor)
{
int saved_errno, result;
if (descriptor == -1)
return EBADF;
saved_errno = errno;
result = close(descriptor);
if (result == -1)
result = errno;
errno = saved_errno;
return result;
}
Я знаю, что это безопасно для Linux и FreeBSD, и я предполагаю, что он безопасен для всех других систем POSIX-y. Если я столкнулся с тем, что нет, я могу просто заменить выше с помощью специальной версии, завернув ее в подходящую #ifdef
для этой ОС. Причина, по которой это поддерживает errno
неизменной, - это просто причуда моего стиля кодирования; он сокращает короткие пути ошибок (менее повторяющийся код).
Если я закрываю файл, содержащий важную информацию пользователя, я сделаю fsync()
или fdatasync()
на нем до закрытие. Это гарантирует, что данные попадают в хранилище, но также вызывает задержку по сравнению с обычной работой; поэтому я не буду делать это для обычных файлов данных.
Если я не буду unlink()
ing закрытым файлом, я проверю возвращаемое значение closefd()
и действую соответственно. Если я смогу легко повторить попытку, я сделаю это, но не более одного или двух раз. Для файлов журналов и сгенерированных/потоковых файлов я предупреждаю пользователя.
Я хочу напомнить всем, кто читает это далеко, что мы не можем сделать что-либо полностью надежным; это просто невозможно. То, что мы можем делать, и, на мой взгляд, должно делать, - это обнаружить, когда происходит ошибка, насколько это возможно. Если мы сможем легко и с небрежным использованием ресурсов повторить, мы должны. Во всех случаях мы должны убедиться, что уведомление (об ошибке) распространяется на фактического пользователя. Пусть человек беспокоится о том, нужно ли делать какое-либо другое действие, возможно, сложное, до повторной попытки операции. В конце концов, многие инструменты используются только как часть более крупной задачи, и лучший способ действий обычно зависит от этой более крупной задачи.