LINUX.ORG.RU

writen() из книги Стивенса


0

0

Добрый день!

В широко известной книге Стивенса "UNIX разработка сетевых
приложений", есть следующий код:

01: ssize_t writen(int fd, const void *vptr, size_t n)
02: {
03:     size_t nleft;
04:     ssize_t nwritten;
05:     const char *ptr;
06:
07:     ptr = vptr;
08:     nleft = n;
09:     while(nleft > 0) {
10:         if((nwritten = write(fd, ptr, nleft)) <= 0) {
11:             if(errno == EINTR) nwritten = 0;
12:             else return -1;
13:         }
14:         nleft -= nwritten;
15:        ptr += nwritten;
16:     }
17:     return n;
18: }

Мой вопрос касается строки под номером 10. Представим себе ситуацию,
когда узел закрывает сокет. Тогда write() вернет 0 и дальнейшее
поведение программы будет зависеть от значения errno, которое может
оказаться любым. Т.к. ошибка в книжке маловероятна, хотелось бы, чтобы
знающие люди подсказали где ошибка в моих рассуждениях.

> Представим себе ситуацию, когда узел закрывает сокет. Тогда write() вернет 0 и дальнейшее поведение программы будет зависеть от значения errno, которое может оказаться любым.

правильно

> Т.к. ошибка в книжке маловероятна,

правильно

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

и тем не менее, отчасти ошибка в книге есть.

http://www.opengroup.org/onlinepubs/009695399/functions/write.html

--- cut ---
Where this volume of IEEE Std 1003.1-2001 requires -1 to be returned and errno set to [EAGAIN], most historical implementations return zero (with the O_NDELAY flag set, which is the historical predecessor of O_NONBLOCK, but is not itself in this volume of IEEE Std 1003.1-2001). The error indications in this volume of IEEE Std 1003.1-2001 were chosen so that an application can distinguish these cases from end-of-file. While write() cannot receive an indication of end-of-file, read() can, and the two functions have similar return values. Also, some existing systems (for example, Eighth Edition) permit a write of zero bytes to mean that the reader should get an end-of-file indication; for those systems, a return value of zero from write() indicates a successful write of an end-of-file indication.
--- cut ---

так что получение нуля скорее означает явное указание на EOF и AFAIU никто не гарантирует, что errno в этом случае будет выставлен.

ps: тем более, что при указанном методе обработки EINTR все попытки прервать ваше приложение, зависшее в мертвом цикле на write по сигналу ни к чему не приведут, что не есть хорошо :-/

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

В общем-то, такой способ обработки EINTR меня устраивает :). Выходит, все-таки ошибка в книге? Странно, в readn() из той же книги все ОК. Может, все-таки, какая-то тонкость есть?

syomin
() автор топика
Ответ на: комментарий от syomin

> Выходит, все-таки ошибка в книге?

читайте стандарты, они рулез. выводы делайте сами.

> Странно, в readn() из той же книги все ОК.

а именно? в смысле код.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от syomin

> Да, там результат read() отдельно проверяется на < 0 и отдельно на 0.

допустим, судя по коду, NetBSD вернет 0 из write(2) в случае, если какая-то часть данных была уже записана [но не вся] но в процессе записи процесс был прерван по сигналу. очевидно, что значение errno в этом случае не меняется и что там будет лишь Аллаху известно.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

А почему в NetBSD возвращается 0? Мне кажется, что в случае, когда часть данных уже записана и процесс прерывается по сигналу, write() должен вернуть количество отправленных байт. В противном случае, не понятно, сколько байт отправлено до получения сигнала и что делать дальше?...

syomin
() автор топика
Ответ на: комментарий от syomin

> А почему в NetBSD возвращается 0? Мне кажется, что в случае, когда часть данных уже записана и процесс прерывается по сигналу, write() должен вернуть количество отправленных байт. В противном случае, не понятно, сколько байт отправлено до получения сигнала и что делать дальше?...

впрочем, судя по коду:

http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/kern/sys_generic.c?rev=1.96&cont
ent-type=text/x-cvsweb-markup

--- cut ---
/*
 * Write system call
 */
int
sys_write(struct lwp *l, void *v, register_t *retval)
{
	struct sys_write_args /* {
		syscallarg(int)			fd;
		syscallarg(const void *)	buf;
		syscallarg(size_t)		nbyte;
	} */ *uap = v;
	int		fd;
	struct file	*fp;
	struct proc	*p;
	struct filedesc	*fdp;

	fd = SCARG(uap, fd);
	p = l->l_proc;
	fdp = p->p_fd;

	if ((fp = fd_getfile(fdp, fd)) == NULL)
		return (EBADF);

	if ((fp->f_flag & FWRITE) == 0) {
		simple_unlock(&fp->f_slock);
		return (EBADF);
	}

	FILE_USE(fp);

	/* dofilewrite() will unuse the descriptor for us */
	return (dofilewrite(l, fd, fp, SCARG(uap, buf), SCARG(uap, nbyte),
	    &fp->f_offset, FOF_UPDATE_OFFSET, retval));
}

int
dofilewrite(struct lwp *l, int fd, struct file *fp, const void *buf,
	size_t nbyte, off_t *offset, int flags, register_t *retval)
{
	struct iovec aiov;
	struct uio auio;
	struct proc *p;
	struct vmspace *vm;
	size_t cnt;
	int error;
#ifdef KTRACE
	struct iovec	ktriov = { .iov_base = NULL, };
#endif

	p = l->l_proc;
	error = proc_vmspace_getref(p, &vm);
	if (error) {
		goto out;
	}
	aiov.iov_base = __UNCONST(buf);		/* XXXUNCONST kills const */
	aiov.iov_len = nbyte;
	auio.uio_iov = &aiov;
	auio.uio_iovcnt = 1;
	auio.uio_resid = nbyte;
	auio.uio_rw = UIO_WRITE;
	auio.uio_vmspace = vm;

	/*
	 * Writes return ssize_t because -1 is returned on error.  Therefore
	 * we must restrict the length to SSIZE_MAX to avoid garbage return
	 * values.
	 */
	if (auio.uio_resid > SSIZE_MAX) {
		error = EINVAL;
		goto out;
	}

#ifdef KTRACE
	/*
	 * if tracing, save a copy of iovec
	 */
	if (KTRPOINT(p, KTR_GENIO))
		ktriov = aiov;
#endif
	cnt = auio.uio_resid;
	error = (*fp->f_ops->fo_write)(fp, offset, &auio, fp->f_cred, flags);
	if (error) {
		if (auio.uio_resid != cnt && (error == ERESTART ||
		    error == EINTR || error == EWOULDBLOCK))
			error = 0;
		if (error == EPIPE)
			psignal(p, SIGPIPE);
	}
	cnt -= auio.uio_resid;
#ifdef KTRACE
	if (KTRPOINT(p, KTR_GENIO) && error == 0)
		ktrgenio(l, fd, UIO_WRITE, &ktriov, cnt, error);
#endif
	*retval = cnt;
 out:
	FILE_UNUSE(fp, l);
	uvmspace_free(vm);
	return (error);
}
--- cut ---

а именно:

--- cut ---
	if (error) {
		if (auio.uio_resid != cnt && (error == ERESTART ||
		    error == EINTR || error == EWOULDBLOCK))
			error = 0;
		if (error == EPIPE)
			psignal(p, SIGPIPE);
	}
	cnt -= auio.uio_resid;
#ifdef KTRACE
	if (KTRPOINT(p, KTR_GENIO) && error == 0)
		ktrgenio(l, fd, UIO_WRITE, &ktriov, cnt, error);
#endif
	*retval = cnt;
--- cut ---

...получается, что я все-таки переврал, и NetBSD делает так,
как POSIX прописал, i.e. если было записана лишь часть буфера
и операция прервана сигналом, то write(2) возвращает количество
байт, которое было реально записано.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от klalafuda

Да, так похоже на правду.... :)

Ну так что, в качестве резюме дискуссии - ошибка в книге? Нужно отдельно проверять код возврата на 0 (завершение соединения) и отдельно на -1 (возникла ошибка).

syomin
() автор топика
Ответ на: комментарий от syomin

> Ну так что, в качестве резюме дискуссии - ошибка в книге? Нужно отдельно проверять код возврата на 0 (завершение соединения) и отдельно на -1 (возникла ошибка).

я бы отдельно проверил на 0 и отдельно на -1. затрат это не требует никаких, зато совесть чиста.

// wbr

klalafuda ★☆☆
()
Ответ на: комментарий от syomin

А вроде там же написано что ошибки записи всплывают при чтении (относительно сокетов).

OxiD ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.