1
2 /*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
5 */
6
7
8 #include <ngx_config.h>
9 #include <ngx_core.h>
10 #include <ngx_event.h>
11
12
13 /*
14 * It seems that Darwin 9.4 (Mac OS X 1.5) sendfile() has the same
15 * old bug as early FreeBSD sendfile() syscall:
16 * http://bugs.freebsd.org/33771
17 *
18 * Besides sendfile() has another bug: if one calls sendfile()
19 * with both a header and a trailer, then sendfile() ignores a file part
20 * at all and sends only the header and the trailer together.
21 * For this reason we send a trailer only if there is no a header.
22 *
23 * Although sendfile() allows to pass a header or a trailer,
24 * it may send the header or the trailer and a part of the file
25 * in different packets. And FreeBSD workaround (TCP_NOPUSH option)
26 * does not help.
27 */
28
29
30 ngx_chain_t *
ngx_darwin_sendfile_chain(ngx_connection_t * c,ngx_chain_t * in,off_t limit)31 ngx_darwin_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
32 {
33 int rc;
34 off_t send, prev_send, sent;
35 off_t file_size;
36 ssize_t n;
37 ngx_uint_t eintr;
38 ngx_err_t err;
39 ngx_buf_t *file;
40 ngx_event_t *wev;
41 ngx_chain_t *cl;
42 ngx_iovec_t header, trailer;
43 struct sf_hdtr hdtr;
44 struct iovec headers[NGX_IOVS_PREALLOCATE];
45 struct iovec trailers[NGX_IOVS_PREALLOCATE];
46
47 wev = c->write;
48
49 if (!wev->ready) {
50 return in;
51 }
52
53 #if (NGX_HAVE_KQUEUE)
54
55 if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
56 (void) ngx_connection_error(c, wev->kq_errno,
57 "kevent() reported about an closed connection");
58 wev->error = 1;
59 return NGX_CHAIN_ERROR;
60 }
61
62 #endif
63
64 /* the maximum limit size is the maximum size_t value - the page size */
65
66 if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
67 limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
68 }
69
70 send = 0;
71
72 header.iovs = headers;
73 header.nalloc = NGX_IOVS_PREALLOCATE;
74
75 trailer.iovs = trailers;
76 trailer.nalloc = NGX_IOVS_PREALLOCATE;
77
78 for ( ;; ) {
79 eintr = 0;
80 prev_send = send;
81
82 /* create the header iovec and coalesce the neighbouring bufs */
83
84 cl = ngx_output_chain_to_iovec(&header, in, limit - send, c->log);
85
86 if (cl == NGX_CHAIN_ERROR) {
87 return NGX_CHAIN_ERROR;
88 }
89
90 send += header.size;
91
92 if (cl && cl->buf->in_file && send < limit) {
93 file = cl->buf;
94
95 /* coalesce the neighbouring file bufs */
96
97 file_size = ngx_chain_coalesce_file(&cl, limit - send);
98
99 send += file_size;
100
101 if (header.count == 0 && send < limit) {
102
103 /*
104 * create the trailer iovec and coalesce the neighbouring bufs
105 */
106
107 cl = ngx_output_chain_to_iovec(&trailer, cl, limit - send,
108 c->log);
109 if (cl == NGX_CHAIN_ERROR) {
110 return NGX_CHAIN_ERROR;
111 }
112
113 send += trailer.size;
114
115 } else {
116 trailer.count = 0;
117 }
118
119 /*
120 * sendfile() returns EINVAL if sf_hdtr's count is 0,
121 * but corresponding pointer is not NULL
122 */
123
124 hdtr.headers = header.count ? header.iovs : NULL;
125 hdtr.hdr_cnt = header.count;
126 hdtr.trailers = trailer.count ? trailer.iovs : NULL;
127 hdtr.trl_cnt = trailer.count;
128
129 sent = header.size + file_size;
130
131 ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
132 "sendfile: @%O %O h:%uz",
133 file->file_pos, sent, header.size);
134
135 rc = sendfile(file->file->fd, c->fd, file->file_pos,
136 &sent, &hdtr, 0);
137
138 if (rc == -1) {
139 err = ngx_errno;
140
141 switch (err) {
142 case NGX_EAGAIN:
143 break;
144
145 case NGX_EINTR:
146 eintr = 1;
147 break;
148
149 default:
150 wev->error = 1;
151 (void) ngx_connection_error(c, err, "sendfile() failed");
152 return NGX_CHAIN_ERROR;
153 }
154
155 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
156 "sendfile() sent only %O bytes", sent);
157 }
158
159 if (rc == 0 && sent == 0) {
160
161 /*
162 * if rc and sent equal to zero, then someone
163 * has truncated the file, so the offset became beyond
164 * the end of the file
165 */
166
167 ngx_log_error(NGX_LOG_ALERT, c->log, 0,
168 "sendfile() reported that \"%s\" was truncated",
169 file->file->name.data);
170
171 return NGX_CHAIN_ERROR;
172 }
173
174 ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
175 "sendfile: %d, @%O %O:%O",
176 rc, file->file_pos, sent, file_size + header.size);
177
178 } else {
179 n = ngx_writev(c, &header);
180
181 if (n == NGX_ERROR) {
182 return NGX_CHAIN_ERROR;
183 }
184
185 sent = (n == NGX_AGAIN) ? 0 : n;
186 }
187
188 c->sent += sent;
189
190 in = ngx_chain_update_sent(in, sent);
191
192 if (eintr) {
193 send = prev_send + sent;
194 continue;
195 }
196
197 if (send - prev_send != sent) {
198 wev->ready = 0;
199 return in;
200 }
201
202 if (send >= limit || in == NULL) {
203 return in;
204 }
205 }
206 }
207