Может написать (2) вернуть 0 байт, написанный *, и что делать, если это так?
Я хотел бы реализовать правильный write(2)
цикл, который берет буфер и продолжает называть write
, пока весь буфер не будет написано.
Я предполагаю, что базовый подход - это что-то вроде:
/** write len bytes of buf to fd, returns 0 on success */
int write_fully(int fd, char *buf, size_t len) {
while (len > 0) {
ssize_t written = write(fd, buf, len);
if (written < 0) {
// some kind of error, probably should try again if its EINTR?
return written;
}
buf += written;
len -= written;
}
return 0;
}
... но это ставит вопрос о том, может ли write()
корректно вернуть 0 байтов и что делать в этом случае. Если ситуация сохранится, приведенный выше код будет просто горячим нажатием на вызов write
, который выглядит как плохая идея. Пока что возвращается что-то ненужное, вы продвигаетесь вперед.
Страница руководства для write
немного неоднозначна. В нем говорится, например:
При успехе возвращается количество записанных байтов (ноль указывает ничего не было написано).
Что, по-видимому, указывает на то, что это возможно в некоторых сценариях. Вызывается только один такой сценарий:
Если count равен нулю, а fd относится к обычному файлу, то write() может возвращает статус отказа, если обнаружена одна из ошибок ниже. Если нет ошибки обнаружены или обнаружение ошибок не выполняется, 0 будет возвращен без какого-либо другого эффекта. Если count равен нулю и fd относится к файлу, отличному от обычного файла, результаты не указано.
Этот случай исключается выше, потому что я никогда не звоню write
с len == 0
. Есть много других случаев, когда ничего нельзя было написать, но в целом все они имеют определенные коды ошибок, связанные с ними.
Сам файл будет open
ed из пути/имени, указанного в командной строке. Поэтому обычно это обычный файл, но пользователи могут, конечно, передавать такие вещи, как трубы, выполнять перенаправление ввода, передавать специальные устройства, такие как /dev/stdout
и так далее. Я, по крайней мере, контролирую вызов open
, а флаг O_NONBLOCK
не передается для открытия. Я не могу разумно проверить поведение для всех файловых систем, всех специальных устройств (и даже если бы мог, больше будет добавлено), поэтому я хочу знать , как обращаться с этим разумным и общим способом.
*... для ненулевого размера буфера.
Ответы
Ответ 1
TL; сводка DR
Если вы не используете свой путь для вызова неуказанного поведения, вы не получите нулевой результат от write()
, если, возможно, вы не попытаетесь записать нулевые байты (которые код в вопросе избегает делать).
POSIX говорит:
Спецификация POSIX для write()
охватывает эту проблему, я считаю.
Функция write() должна попытаться записать nbyte байты из буфера, на который указывает buf, в файл, связанный с дескриптором открытого файла, fildes.
Перед выполнением любых действий, описанных ниже, и если nbyte равно нулю, а файл является обычным файлом, функция write() может обнаруживать и возвращать ошибки, как описано ниже. В случае отсутствия ошибок или если обнаружение ошибок не выполняется, функция write() должна возвращать ноль и не иметь других результатов. Если nbyte равно нулю, а файл не является обычным файлом, результаты не заданы.
Это означает, что если вы запрашиваете запись нулевых байтов, вы можете получить возвращаемое значение ноль, но есть набор оговорок - он должен быть обычным файлом, и вы можете получить сообщение об ошибке, если такие ошибки, как EBADF
, и неизвестно, что произойдет, если файловый дескриптор не ссылается на обычный файл.
Если write() запрашивает, чтобы было записано больше байтов, чем есть место (например, [XSI] [Option Start] ограничение размера файла для процесса или [Option End] физический конец среды) должно быть записано только столько байтов, сколько есть места. Например, предположим, что в файле есть место для 20 байтов больше, прежде чем достигнуть предела. Запись 512 байт вернет 20. Следующая запись ненулевого числа байтов даст возврат отказа (кроме как указано ниже).
[XSI] ⌦ Если запрос приведет к тому, что размер файла превысит ограничение на размер мягкого файла для процесса, и нет места для записи каких-либо байтов, запрос будет терпеть неудачу, и реализация должна сгенерировать сигнал SIGXFSZ для нить. ⌫
Если write() прерывается сигналом перед записью любых данных, он должен возвращать -1 с errno, установленным в [EINTR].
Если write() прерывается сигналом после успешной записи некоторых данных, он должен вернуть количество записанных байтов.
Если значение nbyte больше, чем {SSIZE_MAX}, результат определяется реализацией.
Эти правила действительно не дают разрешения на возврат 0 (хотя педант может сказать, что значение nbyte
, которое слишком велико, может быть определено для возврата 0).
При попытке записать в дескриптор файла (кроме канала или FIFO), который поддерживает неблокирующие записи и не может немедленно принять данные:
-
Если флаг O_NONBLOCK свободен, write() блокирует вызывающий поток, пока данные не будут приняты.
-
Если флаг O_NONBLOCK установлен, write() не должен блокировать поток. Если некоторые данные могут быть записаны без блокировки потока, write() записывает то, что может, и возвращает количество записанных байтов. В противном случае он должен вернуть -1 и установить errno в [EAGAIN].
... подробности для неясных типов файлов - ряд из них с неопределенным поведением...
Возвращаемое значение
После успешного завершения эти функции возвращают количество байтов, фактически записанных в файл, связанный с файлами. Это число никогда не будет больше байта. В противном случае -1 должно быть возвращено и errno установлено для указания ошибки.
Итак, поскольку ваш код избегает попытки записать нулевые байты, если len
не больше, чем {SSIZE_MAX}, и до тех пор, пока вы не пишете, чтобы скрывать типы файлов (например, объект общей памяти или напечатанный объект памяти), вы не должны видеть нуль, возвращаемый write()
.
Обоснование POSIX говорит:
Далее на странице POSIX для write()
в разделе "Обоснование" есть информация:
Если для этого тома POSIX.1-2008 требуется вернуть -1
и errno
установить в [EAGAIN], большинство исторических реализаций возвращают ноль (с установленным флагом O_NDELAY
, который является историческим предшественником O_NONBLOCK
, но сам не входит в этот том POSIX.1-2008). Показатели ошибок в этом томе POSIX.1-2008 были выбраны так, чтобы приложение могло отличать эти случаи от конца файла. Пока write()
не может получить указание конца файла, read()
может, и две функции имеют одинаковые возвращаемые значения. Кроме того, некоторые существующие системы (например, Eighth Edition) позволяют записывать нулевые байты, что означает, что читатель должен получить индикацию конца файла; для этих систем возвращаемое значение нуля из write()
указывает на успешную запись индикации конца файла.
Таким образом, хотя POSIX (в основном, если не полностью) исключает возможность возврата нуля из write()
, существовал уровень техники в связанных системах, у которых write()
возвращался ноль.
Ответ 2
Это зависит от того, к чему относится дескриптор файла. Когда вы вызываете write
в файловом дескрипторе, ядро в конечном итоге заканчивает вызов подпрограммы записи в соответствующем векторном файле операций, который соответствует базовой файловой системе или устройству, на которое ссылается файловый дескриптор.
Большинство нормальных файловых систем никогда не вернут 0, но устройства могут делать что угодно. Вам нужно посмотреть документацию для рассматриваемого устройства, чтобы посмотреть, что он может сделать. Логический драйвер устройства должен вернуть 0 байт (ядро не будет отмечать его как ошибку или что-то еще), и если это произойдет, системный вызов записи вернет 0.
Ответ 3
Posix определяет его для труб, FIFO и FD, которые поддерживают неблокирующие операции, в случае, когда nbyte
(третий параметр) положителен и вызов не прерывался:
если O_NONBLOCK понятен... он должен вернуть nbyte
.
Другими словами, он не только не может вернуть 0, если nbyte
не равен нулю, он также не может вернуть короткую длину в упомянутых случаях.
Ответ 4
Я думаю, что единственный возможный подход (помимо игнорирования проблемы в целом, который, по-видимому, должен делать в соответствии с документацией) заключается в том, чтобы позволить "вращаться на месте".
Вы можете реализовать счетчик повторов, но если этот крайне маловероятный "возврат с ненулевой длиной" обусловлен некоторой временной ситуацией - возможно, очередь LapLink; Я помню, что водитель делает странные вещи - цикл, вероятно, будет настолько быстрым, что любой разумный счетчик попыток будет перегружен в любом случае; и неоправданно большой счетчик повторов не рекомендуется в случае, если у вас есть другие устройства, которые вместо этого берут не пренебрежимое время для возврата 0.
Итак, я бы попробовал что-то вроде этого. Вместо этого вы можете использовать gettimeofday() для большей точности.
(Мы вводим пренебрежимо высокую оценку эффективности для события, которое, кажется, имеет незначительную вероятность когда-либо произойти).
/** write len bytes of buf to fd, returns 0 on success */
int write_fully(int fd, char *buf, size_t len) {
time_t timeout = 0;
while (len > 0) {
ssize_t written = write(fd, buf, len);
if (written < 0) {
// some kind of error, probably should try again if its EINTR?
return written;
}
if (!written) {
if (!timeout) {
// First time around, set the timeout
timeout = time(NULL) + 2; // Prepare to wait "between" 1 and 2 seconds
// Add nanosleep() to reduce CPU load
} else {
if (time(NULL) >= timeout) {
// Weird status lasted too long
return -42;
}
}
} else {
timeout = 0; // reset timeout at every success, or the second zero-return after a recovery will immediately abort (which could be desirable, at that).
}
buf += written;
len -= written;
}
return 0;
}
Ответ 5
Я лично использую несколько подходов к этой проблеме.
Ниже приведены три примера, которые все ожидают работы над дескриптором блокировки. (То есть они считают ошибку EAGAIN
/EWOULDBLOCK
).
При сохранении важных пользовательских данных без ограничения по времени (и, следовательно, запись не должна прерываться подачей сигнала), я предпочитаю использовать
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int write_uninterruptible(const int descriptor, const void *const data, const size_t size)
{
const unsigned char *p = (const unsigned char *)data;
const unsigned char *const q = (const unsigned char *)data + size;
ssize_t n;
if (descriptor == -1)
return errno = EBADF;
while (p < q) {
n = write(descriptor, p, (size_t)(q - p));
if (n > 0)
p += n;
else
if (n != -1)
return errno = EIO;
else
if (errno != EINTR)
return errno;
}
if (p != q)
return errno = EIO;
return 0;
}
Это прервется, если произойдет ошибка (отличная от EINTR
), или если write()
возвращает ноль или отрицательное значение, отличное от -1
.
Потому что нет разумной причины для возврата частичного количества записи, вместо этого он возвращает 0
if success и ненулевой код ошибки errno.
При записи важных данных, но запись прерывается, если сигнал доставлен, интерфейс немного отличается:
size_t write_interruptible(const int descriptor, const void *const data, const size_t size)
{
const unsigned char *p = (const unsigned char *)data;
const unsigned char *const q = (const unsigned char *)data + size;
ssize_t n;
if (descriptor == -1) {
errno = EBADF;
return 0;
}
while (p < q) {
n = write(descriptor, p, (size_t)(q - p));
if (n > 0)
p += n;
else
if (n != -1) {
errno = EIO;
return (size_t)(p - (const unsigned char *)data);
} else
return (size_t)(p - (const unsigned char *)data);
}
errno = 0;
return (size_t)(p - (const unsigned char *)data);
}
В этом случае всегда записывается количество записанных данных. Эта версия также устанавливает errno
во всех случаях - обычно errno
не задается, кроме случаев с ошибками.
Хотя это означает, что если ошибка происходит частично, и функция вернет количество данных, которые были успешно записаны (с предшествующими вызовами write()
), причина всегда устанавливать errno
заключается в том, чтобы облегчить обнаружение ошибок, по существу, чтобы отделить статус (errno
) от числа записи.
Иногда мне нужна функция, которая записывает сообщение отладки в стандартную ошибку из обработчика сигнала. (Стандартный <stdio.h>
I/O не является безопасным для асинхронного сигнала, поэтому в любом случае требуется специальная функция.) Я хочу, чтобы эта функция прерывалась даже при доставке сигнала - это не имеет большого значения, если запись не удалась, так как это не futz с остальной частью программы, - но сохраняйте errno
без изменений. Это печатает строки исключительно, так как это предполагаемый прецедент. Обратите внимание, что strlen()
не является безопасным для асинхронного сигнала, поэтому вместо него используется явный цикл.
int stderr_note(const char *message)
{
int retval = 0;
if (message && *message) {
int saved_errno;
const char *ends = message;
ssize_t n;
saved_errno = errno;
while (*ends)
ends++;
while (message < ends) {
n = write(STDERR_FILENO, message, (size_t)(ends - message));
if (n > 0)
message += n;
else {
if (n == -1)
retval = errno;
else
retval = EIO;
break;
}
}
if (!retval && message != ends)
retval = EIO;
errno = saved_errno;
}
return retval;
}
Эта версия возвращает 0, если сообщение было успешно записано на стандартный вывод, а в противном случае - ненулевой код ошибки. Как уже упоминалось, он всегда сохраняет errno
без изменений, чтобы избежать неожиданных побочных эффектов в основной программе, если они используются в обработчике сигналов.
Я использую очень простые принципы при работе с непредвиденными ошибками или возвращаемыми значениями из системных вызовов. Основной принцип заключается в том, чтобы никогда не отбрасывать или калечить пользовательские данные. Если данные потеряны или искалечены, программа всегда должна уведомлять пользователя. Все непредвиденное следует считать ошибкой.
Только некоторые из записей в программе связаны с пользовательскими данными. Много информации, как информация об использовании, или отчет о ходе работы. Для тех, я предпочел бы либо игнорировать неожиданное условие, либо пропустить, что пишут вообще. Это зависит от характера записанных данных.
В целом, мне все равно, что стандарты говорят о возвращаемых значениях: я обрабатываю их все. Ответ на каждый (тип) результат зависит от записываемых данных - в частности, от важности этих данных для пользователя. Из-за этого я использую несколько разных реализаций даже в одной программе.
Ответ 6
Я бы сказал, что весь вопрос не нужен. Вы просто слишком осторожны. Вы ожидаете, что файл будет обычным файлом, а не сокетом, а не устройством, а не fifo и т.д. Я бы сказал, что любое возвращение из write
в обычный файл, не равный len
, является неисправимая ошибка. Не пытайтесь это исправить. Вероятно, вы заполнили файловую систему, или ваш диск сломан, или что-то в этом роде. (все это предполагает, что вы не настроили свои сигналы на прерывание системных вызовов)
Для обычных файлов я не знаю ни одного ядра, которое еще не выполнило всю необходимую повторную попытку, чтобы получить ваши данные, и если это не удастся, ошибка, скорее всего, достаточно серьезная, чтобы ее не исправить. Если пользователь решает передать нерегулярный файл в качестве аргумента, то что? Это их проблема. Их нога и пистолет, пусть они стреляют в нее.
Пытаясь исправить это в своем коде, вы гораздо более склонны к ухудшению ситуации, создавая бесконечный цикл, потребляющий CPU, или заполняющий журнал файловой системы или просто зависающий.
Не обрабатывайте 0 или другие короткие записи, просто распечатайте ошибку при любом возврате, отличном от len
, и выйдите. Как только вы получите правильный отчет об ошибке от пользователя, который действительно имеет законную причину неудачи записи, исправьте его. Скорее всего, этого никогда не произойдет, потому что это почти все.
Да, иногда полезно читать POSIX и находить крайние случаи и писать код, чтобы справиться с ними. Но разработчики операционной системы не попадают в тюрьму за нарушение POSIX, поэтому даже если ваш умный код отлично соответствует тому, что говорит стандарт, это не гарантирует, что все будет работать всегда. Иногда лучше всего делать все и полагаться на то, чтобы быть в хорошей компании, когда они ломаются. Если регулярные записи файлов начнут возвращаться, вы окажетесь в такой хорошей компании, что, скорее всего, она будет исправлена задолго до того, как любой из ваших пользователей заметит.
N.B. Почти 20 лет назад я работал над реализацией файловой системы, и мы пытались быть юристами стандартов по поводу поведения одной из операций (не write
, но применяется тот же принцип). Наш "законно возвращать данные в этом порядке" был заглушен потоками сообщений об ошибках приложений, которые ожидали чего-то определенным образом, и, в конце концов, просто было просто исправить это, вместо того чтобы бороться с тем же боем в каждый отчет об ошибках. Для тех, кто задается вопросом, многие вещи тогда (и, вероятно, все еще сегодня) ожидали, что readdir
вернет .
и ..
в качестве первых двух записей в каталоге, который (по крайней мере, тогда) не был назначен стандарт.