10Sigor@sysoev.ru 
20Sigor@sysoev.ru /*
30Sigor@sysoev.ru  * Copyright (C) Igor Sysoev
40Sigor@sysoev.ru  * Copyright (C) NGINX, Inc.
50Sigor@sysoev.ru  */
60Sigor@sysoev.ru 
70Sigor@sysoev.ru #include <nxt_main.h>
80Sigor@sysoev.ru 
90Sigor@sysoev.ru 
100Sigor@sysoev.ru /* sendfile() has been introduced in MacOSX 10.5 (Leopard) */
110Sigor@sysoev.ru 
120Sigor@sysoev.ru #ifdef NXT_TEST_BUILD_MACOSX_SENDFILE
130Sigor@sysoev.ru 
140Sigor@sysoev.ru ssize_t nxt_macosx_event_conn_io_sendfile(nxt_event_conn_t *c, nxt_buf_t *b,
150Sigor@sysoev.ru     size_t limit);
160Sigor@sysoev.ru 
170Sigor@sysoev.ru static int nxt_sys_sendfile(int fd, int s, off_t offset, off_t *len,
180Sigor@sysoev.ru     struct sf_hdtr *hdtr, int flags)
190Sigor@sysoev.ru {
200Sigor@sysoev.ru     return -1;
210Sigor@sysoev.ru }
220Sigor@sysoev.ru 
230Sigor@sysoev.ru #else
240Sigor@sysoev.ru #define nxt_sys_sendfile  sendfile
250Sigor@sysoev.ru #endif
260Sigor@sysoev.ru 
270Sigor@sysoev.ru 
280Sigor@sysoev.ru ssize_t
290Sigor@sysoev.ru nxt_macosx_event_conn_io_sendfile(nxt_event_conn_t *c, nxt_buf_t *b,
300Sigor@sysoev.ru     size_t limit)
310Sigor@sysoev.ru {
320Sigor@sysoev.ru     size_t                  hd_size, file_size;
330Sigor@sysoev.ru     ssize_t                 n;
340Sigor@sysoev.ru     nxt_buf_t               *fb;
350Sigor@sysoev.ru     nxt_err_t               err;
360Sigor@sysoev.ru     nxt_off_t               sent;
370Sigor@sysoev.ru     nxt_uint_t              nhd, ntr;
380Sigor@sysoev.ru     struct iovec            hd[NXT_IOBUF_MAX], tr[NXT_IOBUF_MAX];
390Sigor@sysoev.ru     struct sf_hdtr          hdtr, *ht;
400Sigor@sysoev.ru     nxt_sendbuf_coalesce_t  sb;
410Sigor@sysoev.ru 
420Sigor@sysoev.ru     sb.buf = b;
430Sigor@sysoev.ru     sb.iobuf = hd;
440Sigor@sysoev.ru     sb.nmax = NXT_IOBUF_MAX;
450Sigor@sysoev.ru     sb.sync = 0;
460Sigor@sysoev.ru     sb.size = 0;
470Sigor@sysoev.ru     sb.limit = limit;
480Sigor@sysoev.ru 
49*1Sigor@sysoev.ru     nhd = nxt_sendbuf_mem_coalesce(c->socket.task, &sb);
500Sigor@sysoev.ru 
510Sigor@sysoev.ru     if (nhd == 0 && sb.sync) {
520Sigor@sysoev.ru         return 0;
530Sigor@sysoev.ru     }
540Sigor@sysoev.ru 
550Sigor@sysoev.ru     if (sb.buf == NULL || !nxt_buf_is_file(sb.buf)) {
560Sigor@sysoev.ru         return nxt_event_conn_io_writev(c, hd, nhd);
570Sigor@sysoev.ru     }
580Sigor@sysoev.ru 
590Sigor@sysoev.ru     hd_size = sb.size;
600Sigor@sysoev.ru     fb = sb.buf;
610Sigor@sysoev.ru 
620Sigor@sysoev.ru     file_size = nxt_sendbuf_file_coalesce(&sb);
630Sigor@sysoev.ru 
640Sigor@sysoev.ru     if (file_size == 0) {
650Sigor@sysoev.ru         return nxt_event_conn_io_writev(c, hd, nhd);
660Sigor@sysoev.ru     }
670Sigor@sysoev.ru 
680Sigor@sysoev.ru     sb.iobuf = tr;
690Sigor@sysoev.ru 
70*1Sigor@sysoev.ru     ntr = nxt_sendbuf_mem_coalesce(c->socket.task, &sb);
710Sigor@sysoev.ru 
720Sigor@sysoev.ru     /*
730Sigor@sysoev.ru      * Disposal of surplus kernel operations if there are no headers
740Sigor@sysoev.ru      * and trailers.  Besides sendfile() returns EINVAL if a sf_hdtr's
750Sigor@sysoev.ru      * count is 0, but corresponding pointer is not NULL.
760Sigor@sysoev.ru      */
770Sigor@sysoev.ru 
780Sigor@sysoev.ru     nxt_memzero(&hdtr, sizeof(struct sf_hdtr));
790Sigor@sysoev.ru     ht = NULL;
800Sigor@sysoev.ru 
810Sigor@sysoev.ru     if (nhd != 0) {
820Sigor@sysoev.ru         ht = &hdtr;
830Sigor@sysoev.ru         hdtr.headers = hd;
840Sigor@sysoev.ru         hdtr.hdr_cnt = nhd;
850Sigor@sysoev.ru     }
860Sigor@sysoev.ru 
870Sigor@sysoev.ru     if (ntr != 0) {
880Sigor@sysoev.ru         ht = &hdtr;
890Sigor@sysoev.ru         hdtr.trailers = tr;
900Sigor@sysoev.ru         hdtr.trl_cnt = ntr;
910Sigor@sysoev.ru     }
920Sigor@sysoev.ru 
930Sigor@sysoev.ru     /*
940Sigor@sysoev.ru      * MacOSX has the same bug as old FreeBSD (http://bugs.freebsd.org/33771).
950Sigor@sysoev.ru      * However this bug has never been fixed and instead of this it has been
960Sigor@sysoev.ru      * documented as a feature in MacOSX 10.7 (Lion) sendfile(2):
970Sigor@sysoev.ru      *
980Sigor@sysoev.ru      *   When a header or trailer is specified, the value of len argument
990Sigor@sysoev.ru      *   indicates the maximum number of bytes in the header and/or file
1000Sigor@sysoev.ru      *   to be sent.  It does not control the trailer; if a trailer exists,
1010Sigor@sysoev.ru      *   all of it will be sent.
1020Sigor@sysoev.ru      */
1030Sigor@sysoev.ru     sent = hd_size + file_size;
1040Sigor@sysoev.ru 
1050Sigor@sysoev.ru     nxt_log_debug(c->socket.log,
1060Sigor@sysoev.ru                   "sendfile(%FD, %d, @%O, %O) hd:%ui tr:%ui hs:%uz",
1070Sigor@sysoev.ru                   fb->file->fd, c->socket.fd, fb->file_pos, sent,
1080Sigor@sysoev.ru                   nhd, ntr, hd_size);
1090Sigor@sysoev.ru 
1100Sigor@sysoev.ru     n = nxt_sys_sendfile(fb->file->fd, c->socket.fd,
1110Sigor@sysoev.ru                          fb->file_pos, &sent, ht, 0);
1120Sigor@sysoev.ru 
1130Sigor@sysoev.ru     err = (n == -1) ? nxt_errno : 0;
1140Sigor@sysoev.ru 
1150Sigor@sysoev.ru     nxt_log_debug(c->socket.log, "sendfile(): %d sent:%O", n, sent);
1160Sigor@sysoev.ru 
1170Sigor@sysoev.ru     if (n == -1) {
1180Sigor@sysoev.ru         switch (err) {
1190Sigor@sysoev.ru 
1200Sigor@sysoev.ru         case NXT_EAGAIN:
1210Sigor@sysoev.ru             c->socket.write_ready = 0;
1220Sigor@sysoev.ru             break;
1230Sigor@sysoev.ru 
1240Sigor@sysoev.ru         case NXT_EINTR:
1250Sigor@sysoev.ru             break;
1260Sigor@sysoev.ru 
1270Sigor@sysoev.ru         default:
1280Sigor@sysoev.ru             c->socket.error = err;
1290Sigor@sysoev.ru             nxt_log_error(nxt_socket_error_level(err, c->socket.log_error),
1300Sigor@sysoev.ru                           c->socket.log, "sendfile(%FD, %d, %O, %O) failed "
1310Sigor@sysoev.ru                           "%E \"%FN\" hd:%ui tr:%ui", fb->file->fd,
1320Sigor@sysoev.ru                           c->socket.fd, fb->file_pos, sent, err,
1330Sigor@sysoev.ru                           fb->file->name, nhd, ntr);
1340Sigor@sysoev.ru 
1350Sigor@sysoev.ru             return NXT_ERROR;
1360Sigor@sysoev.ru         }
1370Sigor@sysoev.ru 
1380Sigor@sysoev.ru         nxt_log_debug(c->socket.log, "sendfile() %E", err);
1390Sigor@sysoev.ru 
1400Sigor@sysoev.ru         return sent;
1410Sigor@sysoev.ru     }
1420Sigor@sysoev.ru 
1430Sigor@sysoev.ru     if (sent == 0) {
1440Sigor@sysoev.ru         nxt_log_error(NXT_LOG_ERR, c->socket.log,
1450Sigor@sysoev.ru                       "file \"%FN\" was truncated while sendfile()",
1460Sigor@sysoev.ru                       fb->file->name);
1470Sigor@sysoev.ru 
1480Sigor@sysoev.ru         return NXT_ERROR;
1490Sigor@sysoev.ru     }
1500Sigor@sysoev.ru 
1510Sigor@sysoev.ru     if (sent < (nxt_off_t) sb.size) {
1520Sigor@sysoev.ru         c->socket.write_ready = 0;
1530Sigor@sysoev.ru     }
1540Sigor@sysoev.ru 
1550Sigor@sysoev.ru     return sent;
1560Sigor@sysoev.ru }
1570Sigor@sysoev.ru 
1580Sigor@sysoev.ru 
1590Sigor@sysoev.ru #if 0
1600Sigor@sysoev.ru 
1610Sigor@sysoev.ru typedef struct {
1620Sigor@sysoev.ru     nxt_socket_t  socket;
1630Sigor@sysoev.ru     nxt_err_t     error;
1640Sigor@sysoev.ru 
1650Sigor@sysoev.ru     uint8_t       write_ready;  /* 1 bit */
1660Sigor@sysoev.ru     uint8_t       log_error;
1670Sigor@sysoev.ru } nxt_sendbuf_t;
1680Sigor@sysoev.ru 
1690Sigor@sysoev.ru 
1700Sigor@sysoev.ru ssize_t nxt_macosx_sendfile(nxt_thread_t *thr, nxt_sendbuf_t *sb, nxt_buf_t *b,
1710Sigor@sysoev.ru     size_t limit);
1720Sigor@sysoev.ru ssize_t nxt_writev(nxt_thread_t *thr, nxt_sendbuf_t *sb, nxt_iobuf_t *iob,
1730Sigor@sysoev.ru     nxt_uint_t niob);
1740Sigor@sysoev.ru ssize_t nxt_send(nxt_thread_t *thr, nxt_sendbuf_t *sb, void *buf, size_t size);
1750Sigor@sysoev.ru 
1760Sigor@sysoev.ru 
1770Sigor@sysoev.ru ssize_t
1780Sigor@sysoev.ru nxt_macosx_sendfile(nxt_thread_t *thr, nxt_sendbuf_t *sb, nxt_buf_t *b,
1790Sigor@sysoev.ru     size_t limit)
1800Sigor@sysoev.ru {
1810Sigor@sysoev.ru     size_t                  hd_size, file_size;
1820Sigor@sysoev.ru     ssize_t                 n;
1830Sigor@sysoev.ru     nxt_buf_t               *buf;
1840Sigor@sysoev.ru     nxt_err_t               err;
1850Sigor@sysoev.ru     nxt_off_t               sent;
1860Sigor@sysoev.ru     nxt_uint_t              nhd, ntr;
1870Sigor@sysoev.ru     struct iovec            hd[NXT_IOBUF_MAX], tr[NXT_IOBUF_MAX];
1880Sigor@sysoev.ru     struct sf_hdtr          hdtr, *ht;
1890Sigor@sysoev.ru     nxt_sendbuf_coalesce_t  sbc;
1900Sigor@sysoev.ru 
1910Sigor@sysoev.ru     sbc.buf = b;
1920Sigor@sysoev.ru     sbc.iobuf = hd;
1930Sigor@sysoev.ru     sbc.nmax = NXT_IOBUF_MAX;
1940Sigor@sysoev.ru     sbc.sync = 0;
1950Sigor@sysoev.ru     sbc.size = 0;
1960Sigor@sysoev.ru     sbc.limit = limit;
1970Sigor@sysoev.ru 
1980Sigor@sysoev.ru     nhd = nxt_sendbuf_mem_coalesce(&sbc);
1990Sigor@sysoev.ru 
2000Sigor@sysoev.ru     if (nhd == 0 && sbc.sync) {
2010Sigor@sysoev.ru         return 0;
2020Sigor@sysoev.ru     }
2030Sigor@sysoev.ru 
2040Sigor@sysoev.ru     if (sbc.buf == NULL || !nxt_buf_is_file(sbc.buf)) {
2050Sigor@sysoev.ru         return nxt_writev(thr, sb, hd, nhd);
2060Sigor@sysoev.ru     }
2070Sigor@sysoev.ru 
2080Sigor@sysoev.ru     hd_size = sbc.size;
2090Sigor@sysoev.ru     buf = sbc.buf;
2100Sigor@sysoev.ru 
2110Sigor@sysoev.ru     file_size = nxt_sendbuf_file_coalesce(&sbc);
2120Sigor@sysoev.ru 
2130Sigor@sysoev.ru     if (file_size == 0) {
2140Sigor@sysoev.ru         return nxt_writev(thr, sb, hd, nhd);
2150Sigor@sysoev.ru     }
2160Sigor@sysoev.ru 
2170Sigor@sysoev.ru     sbc.iobuf = tr;
2180Sigor@sysoev.ru 
2190Sigor@sysoev.ru     ntr = nxt_sendbuf_mem_coalesce(&sbc);
2200Sigor@sysoev.ru 
2210Sigor@sysoev.ru     /*
2220Sigor@sysoev.ru      * Disposal of surplus kernel operations if there are no headers
2230Sigor@sysoev.ru      * and trailers.  Besides sendfile() returns EINVAL if a sf_hdtr's
2240Sigor@sysoev.ru      * count is 0, but corresponding pointer is not NULL.
2250Sigor@sysoev.ru      */
2260Sigor@sysoev.ru 
2270Sigor@sysoev.ru     nxt_memzero(&hdtr, sizeof(struct sf_hdtr));
2280Sigor@sysoev.ru     ht = NULL;
2290Sigor@sysoev.ru 
2300Sigor@sysoev.ru     if (nhd != 0) {
2310Sigor@sysoev.ru         ht = &hdtr;
2320Sigor@sysoev.ru         hdtr.headers = hd;
2330Sigor@sysoev.ru         hdtr.hdr_cnt = nhd;
2340Sigor@sysoev.ru     }
2350Sigor@sysoev.ru 
2360Sigor@sysoev.ru     if (ntr != 0) {
2370Sigor@sysoev.ru         ht = &hdtr;
2380Sigor@sysoev.ru         hdtr.trailers = tr;
2390Sigor@sysoev.ru         hdtr.trl_cnt = ntr;
2400Sigor@sysoev.ru     }
2410Sigor@sysoev.ru 
2420Sigor@sysoev.ru     /*
2430Sigor@sysoev.ru      * MacOSX has the same bug as old FreeBSD (http://bugs.freebsd.org/33771).
2440Sigor@sysoev.ru      * However this bug has never been fixed and instead of this it has been
2450Sigor@sysoev.ru      * documented as a feature in MacOSX 10.7 (Lion) sendfile(2):
2460Sigor@sysoev.ru      *
2470Sigor@sysoev.ru      *   When a header or trailer is specified, the value of len argument
2480Sigor@sysoev.ru      *   indicates the maximum number of bytes in the header and/or file
2490Sigor@sysoev.ru      *   to be sent.  It does not control the trailer; if a trailer exists,
2500Sigor@sysoev.ru      *   all of it will be sent.
2510Sigor@sysoev.ru      */
2520Sigor@sysoev.ru     sent = hd_size + file_size;
2530Sigor@sysoev.ru 
2540Sigor@sysoev.ru     nxt_log_debug(thr->log, "sendfile(%FD, %d, @%O, %O) hd:%ui tr:%ui hs:%uz",
2550Sigor@sysoev.ru                   buf->file->fd, sb->socket, buf->file_pos, sent,
2560Sigor@sysoev.ru                   nhd, ntr, hd_size);
2570Sigor@sysoev.ru 
2580Sigor@sysoev.ru     n = nxt_sys_sendfile(buf->file->fd, sb->socket,
2590Sigor@sysoev.ru                          buf->file_pos, &sent, ht, 0);
2600Sigor@sysoev.ru 
2610Sigor@sysoev.ru     err = (n == -1) ? nxt_errno : 0;
2620Sigor@sysoev.ru 
2630Sigor@sysoev.ru     nxt_log_debug(thr->log, "sendfile(): %d sent:%O", n, sent);
2640Sigor@sysoev.ru 
2650Sigor@sysoev.ru     if (n == -1) {
2660Sigor@sysoev.ru         switch (err) {
2670Sigor@sysoev.ru 
2680Sigor@sysoev.ru         case NXT_EAGAIN:
2690Sigor@sysoev.ru             sb->write_ready = 0;
2700Sigor@sysoev.ru             break;
2710Sigor@sysoev.ru 
2720Sigor@sysoev.ru         case NXT_EINTR:
2730Sigor@sysoev.ru             break;
2740Sigor@sysoev.ru 
2750Sigor@sysoev.ru         default:
2760Sigor@sysoev.ru             sb->error = err;
2770Sigor@sysoev.ru             nxt_log_error(nxt_socket_error_level(err, sb->log_error), thr->log,
2780Sigor@sysoev.ru                           "sendfile(%FD, %d, %O, %O) failed %E \"%FN\" "
2790Sigor@sysoev.ru                           "hd:%ui tr:%ui", buf->file->fd, sb->socket,
2800Sigor@sysoev.ru                           buf->file_pos, sent, err, buf->file->name, nhd, ntr);
2810Sigor@sysoev.ru 
2820Sigor@sysoev.ru             return NXT_ERROR;
2830Sigor@sysoev.ru         }
2840Sigor@sysoev.ru 
2850Sigor@sysoev.ru         nxt_log_debug(thr->log, "sendfile() %E", err);
2860Sigor@sysoev.ru 
2870Sigor@sysoev.ru         return sent;
2880Sigor@sysoev.ru     }
2890Sigor@sysoev.ru 
2900Sigor@sysoev.ru     if (sent == 0) {
2910Sigor@sysoev.ru         nxt_log_error(NXT_LOG_ERR, thr->log,
2920Sigor@sysoev.ru                       "file \"%FN\" was truncated while sendfile()",
2930Sigor@sysoev.ru                       buf->file->name);
2940Sigor@sysoev.ru 
2950Sigor@sysoev.ru         return NXT_ERROR;
2960Sigor@sysoev.ru     }
2970Sigor@sysoev.ru 
2980Sigor@sysoev.ru     if (sent < (nxt_off_t) sbc.size) {
2990Sigor@sysoev.ru         sb->write_ready = 0;
3000Sigor@sysoev.ru     }
3010Sigor@sysoev.ru 
3020Sigor@sysoev.ru     return sent;
3030Sigor@sysoev.ru }
3040Sigor@sysoev.ru 
3050Sigor@sysoev.ru 
3060Sigor@sysoev.ru ssize_t
3070Sigor@sysoev.ru nxt_writev(nxt_thread_t *thr, nxt_sendbuf_t *sb, nxt_iobuf_t *iob,
3080Sigor@sysoev.ru     nxt_uint_t niob)
3090Sigor@sysoev.ru {
3100Sigor@sysoev.ru     ssize_t    n;
3110Sigor@sysoev.ru     nxt_err_t  err;
3120Sigor@sysoev.ru 
3130Sigor@sysoev.ru     if (niob == 1) {
3140Sigor@sysoev.ru         /* Disposal of surplus kernel iovec copy-in operation. */
3150Sigor@sysoev.ru         return nxt_send(thr, sb, iob->iov_base, iob->iov_len);
3160Sigor@sysoev.ru     }
3170Sigor@sysoev.ru 
3180Sigor@sysoev.ru     for ( ;; ) {
3190Sigor@sysoev.ru         n = writev(sb->socket, iob, niob);
3200Sigor@sysoev.ru 
3210Sigor@sysoev.ru         err = (n == -1) ? nxt_socket_errno : 0;
3220Sigor@sysoev.ru 
3230Sigor@sysoev.ru         nxt_log_debug(thr->log, "writev(%d, %ui): %d", sb->socket, niob, n);
3240Sigor@sysoev.ru 
3250Sigor@sysoev.ru         if (n > 0) {
3260Sigor@sysoev.ru             return n;
3270Sigor@sysoev.ru         }
3280Sigor@sysoev.ru 
3290Sigor@sysoev.ru         /* n == -1 */
3300Sigor@sysoev.ru 
3310Sigor@sysoev.ru         switch (err) {
3320Sigor@sysoev.ru 
3330Sigor@sysoev.ru         case NXT_EAGAIN:
3340Sigor@sysoev.ru             nxt_log_debug(thr->log, "writev() %E", err);
3350Sigor@sysoev.ru             sb->write_ready = 0;
3360Sigor@sysoev.ru             return NXT_AGAIN;
3370Sigor@sysoev.ru 
3380Sigor@sysoev.ru         case NXT_EINTR:
3390Sigor@sysoev.ru             nxt_log_debug(thr->log, "writev() %E", err);
3400Sigor@sysoev.ru             continue;
3410Sigor@sysoev.ru 
3420Sigor@sysoev.ru         default:
3430Sigor@sysoev.ru             sb->error = err;
3440Sigor@sysoev.ru             nxt_log_error(nxt_socket_error_level(err, sb->log_error), thr->log,
3450Sigor@sysoev.ru                           "writev(%d, %ui) failed %E", sb->socket, niob, err);
3460Sigor@sysoev.ru             return NXT_ERROR;
3470Sigor@sysoev.ru         }
3480Sigor@sysoev.ru     }
3490Sigor@sysoev.ru }
3500Sigor@sysoev.ru 
3510Sigor@sysoev.ru 
3520Sigor@sysoev.ru ssize_t
3530Sigor@sysoev.ru nxt_send(nxt_thread_t *thr, nxt_sendbuf_t *sb, void *buf, size_t size)
3540Sigor@sysoev.ru {
3550Sigor@sysoev.ru     ssize_t    n;
3560Sigor@sysoev.ru     nxt_err_t  err;
3570Sigor@sysoev.ru 
3580Sigor@sysoev.ru     for ( ;; ) {
3590Sigor@sysoev.ru         n = send(sb->socket, buf, size, 0);
3600Sigor@sysoev.ru 
3610Sigor@sysoev.ru         err = (n == -1) ? nxt_socket_errno : 0;
3620Sigor@sysoev.ru 
3630Sigor@sysoev.ru         nxt_log_debug(thr->log, "send(%d, %p, %uz): %z",
3640Sigor@sysoev.ru                       sb->socket, buf, size, n);
3650Sigor@sysoev.ru 
3660Sigor@sysoev.ru         if (n > 0) {
3670Sigor@sysoev.ru             return n;
3680Sigor@sysoev.ru         }
3690Sigor@sysoev.ru 
3700Sigor@sysoev.ru         /* n == -1 */
3710Sigor@sysoev.ru 
3720Sigor@sysoev.ru         switch (err) {
3730Sigor@sysoev.ru 
3740Sigor@sysoev.ru         case NXT_EAGAIN:
3750Sigor@sysoev.ru             nxt_log_debug(thr->log, "send() %E", err);
3760Sigor@sysoev.ru             sb->write_ready = 0;
3770Sigor@sysoev.ru             return NXT_AGAIN;
3780Sigor@sysoev.ru 
3790Sigor@sysoev.ru         case NXT_EINTR:
3800Sigor@sysoev.ru             nxt_log_debug(thr->log, "send() %E", err);
3810Sigor@sysoev.ru             continue;
3820Sigor@sysoev.ru 
3830Sigor@sysoev.ru         default:
3840Sigor@sysoev.ru             sb->error = err;
3850Sigor@sysoev.ru             nxt_log_error(nxt_socket_error_level(err, sb->log_error), thr->log,
3860Sigor@sysoev.ru                           "send(%d, %p, %uz) failed %E",
3870Sigor@sysoev.ru                           sb->socket, buf, size, err);
3880Sigor@sysoev.ru             return NXT_ERROR;
3890Sigor@sysoev.ru         }
3900Sigor@sysoev.ru     }
3910Sigor@sysoev.ru }
3920Sigor@sysoev.ru 
3930Sigor@sysoev.ru #endif
394