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  * Although FreeBSD sendfile() allows to pass a header and a trailer,
15  * it cannot send a header with a part of the file in one packet until
16  * FreeBSD 5.3.  Besides, over the fast ethernet connection sendfile()
17  * may send the partially filled packets, i.e. the 8 file pages may be sent
18  * as the 11 full 1460-bytes packets, then one incomplete 324-bytes packet,
19  * and then again the 11 full 1460-bytes packets.
20  *
21  * Therefore we use the TCP_NOPUSH option (similar to Linux's TCP_CORK)
22  * to postpone the sending - it not only sends a header and the first part of
23  * the file in one packet, but also sends the file pages in the full packets.
24  *
25  * But until FreeBSD 4.5 turning TCP_NOPUSH off does not flush a pending
26  * data that less than MSS, so that data may be sent with 5 second delay.
27  * So we do not use TCP_NOPUSH on FreeBSD prior to 4.5, although it can be used
28  * for non-keepalive HTTP connections.
29  */
30 
31 
32 ngx_chain_t *
ngx_freebsd_sendfile_chain(ngx_connection_t * c,ngx_chain_t * in,off_t limit)33 ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
34 {
35     int               rc, flags;
36     off_t             send, prev_send, sent;
37     size_t            file_size;
38     ssize_t           n;
39     ngx_uint_t        eintr, eagain;
40     ngx_err_t         err;
41     ngx_buf_t        *file;
42     ngx_event_t      *wev;
43     ngx_chain_t      *cl;
44     ngx_iovec_t       header, trailer;
45     struct sf_hdtr    hdtr;
46     struct iovec      headers[NGX_IOVS_PREALLOCATE];
47     struct iovec      trailers[NGX_IOVS_PREALLOCATE];
48 #if (NGX_HAVE_AIO_SENDFILE)
49     ngx_uint_t        ebusy;
50     ngx_event_aio_t  *aio;
51 #endif
52 
53     wev = c->write;
54 
55     if (!wev->ready) {
56         return in;
57     }
58 
59 #if (NGX_HAVE_KQUEUE)
60 
61     if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
62         (void) ngx_connection_error(c, wev->kq_errno,
63                                "kevent() reported about an closed connection");
64         wev->error = 1;
65         return NGX_CHAIN_ERROR;
66     }
67 
68 #endif
69 
70     /* the maximum limit size is the maximum size_t value - the page size */
71 
72     if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
73         limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
74     }
75 
76     send = 0;
77     eagain = 0;
78     flags = 0;
79 
80 #if (NGX_HAVE_AIO_SENDFILE && NGX_SUPPRESS_WARN)
81     aio = NULL;
82     file = NULL;
83 #endif
84 
85     header.iovs = headers;
86     header.nalloc = NGX_IOVS_PREALLOCATE;
87 
88     trailer.iovs = trailers;
89     trailer.nalloc = NGX_IOVS_PREALLOCATE;
90 
91     for ( ;; ) {
92         eintr = 0;
93 #if (NGX_HAVE_AIO_SENDFILE)
94         ebusy = 0;
95 #endif
96         prev_send = send;
97 
98         /* create the header iovec and coalesce the neighbouring bufs */
99 
100         cl = ngx_output_chain_to_iovec(&header, in, limit - send, c->log);
101 
102         if (cl == NGX_CHAIN_ERROR) {
103             return NGX_CHAIN_ERROR;
104         }
105 
106         send += header.size;
107 
108         if (cl && cl->buf->in_file && send < limit) {
109             file = cl->buf;
110 
111             /* coalesce the neighbouring file bufs */
112 
113             file_size = (size_t) ngx_chain_coalesce_file(&cl, limit - send);
114 
115             send += file_size;
116 
117             if (send < limit) {
118 
119                 /*
120                  * create the trailer iovec and coalesce the neighbouring bufs
121                  */
122 
123                 cl = ngx_output_chain_to_iovec(&trailer, cl, limit - send,
124                                                c->log);
125                 if (cl == NGX_CHAIN_ERROR) {
126                     return NGX_CHAIN_ERROR;
127                 }
128 
129                 send += trailer.size;
130 
131             } else {
132                 trailer.count = 0;
133             }
134 
135             if (ngx_freebsd_use_tcp_nopush
136                 && c->tcp_nopush == NGX_TCP_NOPUSH_UNSET)
137             {
138                 if (ngx_tcp_nopush(c->fd) == -1) {
139                     err = ngx_socket_errno;
140 
141                     /*
142                      * there is a tiny chance to be interrupted, however,
143                      * we continue a processing without the TCP_NOPUSH
144                      */
145 
146                     if (err != NGX_EINTR) {
147                         wev->error = 1;
148                         (void) ngx_connection_error(c, err,
149                                                     ngx_tcp_nopush_n " failed");
150                         return NGX_CHAIN_ERROR;
151                     }
152 
153                 } else {
154                     c->tcp_nopush = NGX_TCP_NOPUSH_SET;
155 
156                     ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
157                                    "tcp_nopush");
158                 }
159             }
160 
161             /*
162              * sendfile() does unneeded work if sf_hdtr's count is 0,
163              * but corresponding pointer is not NULL
164              */
165 
166             hdtr.headers = header.count ? header.iovs : NULL;
167             hdtr.hdr_cnt = header.count;
168             hdtr.trailers = trailer.count ? trailer.iovs : NULL;
169             hdtr.trl_cnt = trailer.count;
170 
171             /*
172              * the "nbytes bug" of the old sendfile() syscall:
173              * http://bugs.freebsd.org/33771
174              */
175 
176             if (!ngx_freebsd_sendfile_nbytes_bug) {
177                 header.size = 0;
178             }
179 
180             sent = 0;
181 
182 #if (NGX_HAVE_AIO_SENDFILE)
183             aio = file->file->aio;
184             flags = (aio && aio->preload_handler) ? SF_NODISKIO : 0;
185 #endif
186 
187             rc = sendfile(file->file->fd, c->fd, file->file_pos,
188                           file_size + header.size, &hdtr, &sent, flags);
189 
190             if (rc == -1) {
191                 err = ngx_errno;
192 
193                 switch (err) {
194                 case NGX_EAGAIN:
195                     eagain = 1;
196                     break;
197 
198                 case NGX_EINTR:
199                     eintr = 1;
200                     break;
201 
202 #if (NGX_HAVE_AIO_SENDFILE)
203                 case NGX_EBUSY:
204                     ebusy = 1;
205                     break;
206 #endif
207 
208                 default:
209                     wev->error = 1;
210                     (void) ngx_connection_error(c, err, "sendfile() failed");
211                     return NGX_CHAIN_ERROR;
212                 }
213 
214                 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
215                                "sendfile() sent only %O bytes", sent);
216 
217             /*
218              * sendfile() in FreeBSD 3.x-4.x may return value >= 0
219              * on success, although only 0 is documented
220              */
221 
222             } else if (rc >= 0 && sent == 0) {
223 
224                 /*
225                  * if rc is OK and sent equal to zero, then someone
226                  * has truncated the file, so the offset became beyond
227                  * the end of the file
228                  */
229 
230                 ngx_log_error(NGX_LOG_ALERT, c->log, 0,
231                          "sendfile() reported that \"%s\" was truncated at %O",
232                          file->file->name.data, file->file_pos);
233 
234                 return NGX_CHAIN_ERROR;
235             }
236 
237             ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
238                            "sendfile: %d, @%O %O:%uz",
239                            rc, file->file_pos, sent, file_size + header.size);
240 
241         } else {
242             n = ngx_writev(c, &header);
243 
244             if (n == NGX_ERROR) {
245                 return NGX_CHAIN_ERROR;
246             }
247 
248             sent = (n == NGX_AGAIN) ? 0 : n;
249         }
250 
251         c->sent += sent;
252 
253         in = ngx_chain_update_sent(in, sent);
254 
255 #if (NGX_HAVE_AIO_SENDFILE)
256 
257         if (ebusy) {
258             if (aio->event.active) {
259                 /*
260                  * tolerate duplicate calls; they can happen due to subrequests
261                  * or multiple calls of the next body filter from a filter
262                  */
263 
264                 if (sent) {
265                     c->busy_count = 0;
266                 }
267 
268                 return in;
269             }
270 
271             if (sent == 0) {
272                 c->busy_count++;
273 
274                 if (c->busy_count > 2) {
275                     ngx_log_error(NGX_LOG_ALERT, c->log, 0,
276                                   "sendfile(%V) returned busy again",
277                                   &file->file->name);
278 
279                     c->busy_count = 0;
280                     aio->preload_handler = NULL;
281 
282                     send = prev_send;
283                     continue;
284                 }
285 
286             } else {
287                 c->busy_count = 0;
288             }
289 
290             n = aio->preload_handler(file);
291 
292             if (n > 0) {
293                 send = prev_send + sent;
294                 continue;
295             }
296 
297             return in;
298         }
299 
300         if (flags == SF_NODISKIO) {
301             c->busy_count = 0;
302         }
303 
304 #endif
305 
306         if (eagain) {
307 
308             /*
309              * sendfile() may return EAGAIN, even if it has sent a whole file
310              * part, it indicates that the successive sendfile() call would
311              * return EAGAIN right away and would not send anything.
312              * We use it as a hint.
313              */
314 
315             wev->ready = 0;
316             return in;
317         }
318 
319         if (eintr) {
320             send = prev_send + sent;
321             continue;
322         }
323 
324         if (send - prev_send != sent) {
325             wev->ready = 0;
326             return in;
327         }
328 
329         if (send >= limit || in == NULL) {
330             return in;
331         }
332     }
333 }
334