2 * Copyright (C) 2012-2020 all contributors <cmogstored-public@yhbt.net>
3 * License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
6 #include "cmogstored.h"
9 #if defined(HAVE_SYS_SENDFILE_H) && !defined(HAVE_BSD_SENDFILE)
10 # include <sys/sendfile.h>
13 #if defined(__linux__)
15 #elif defined(HAVE_SENDFILE) || defined(HAVE_BSD_SENDFILE)
16 # if defined(HAVE_BSD_SENDFILE) && !defined(HAVE_SENDFILE)/* Debian kFBSD */
17 # define sendfile(fd,s,offset,nbytes,hdtr,sbytes,flags) \
18 bsd_sendfile((fd),(s),(offset),(nbytes),(hdtr),(sbytes),(flags))
19 # endif /* HAVE_BSD_SENDFILE */
21 * make BSD sendfile look like Linux for now...
22 * we can support SF_NODISKIO later
24 static ssize_t
linux_sendfile(int sockfd
, int filefd
, off_t
*off
, size_t count
)
30 rc
= sendfile(filefd
, sockfd
, *off
, count
, NULL
, &sbytes
, flags
);
33 return (ssize_t
)sbytes
;
39 # if defined(HAVE_BSD_SENDFILE) /* Debian GNU/kFreeBSD */
41 # endif /* HAVE_BSD_SENDFILE */
42 # define sendfile(out_fd, in_fd, offset, count) \
43 linux_sendfile((out_fd),(in_fd),(offset),(count))
45 # include "compat_sendfile.h"
48 #define ERR416 "416 Requested Range Not Satisfiable"
51 http_hdr_prepare(char **buf
, char **modified
, size_t *len
, time_t *mtime
)
53 /* single buffer so we can use MSG_MORE */
54 *buf
= mog_fsbuf_get(len
);
55 *modified
= *buf
+ *len
/ 2;
56 assert((*len
/ 2) > MOG_HTTPDATE_CAPA
&& "fsbuf too small");
57 mog_http_date(*modified
, MOG_HTTPDATE_CAPA
, mtime
);
63 * snprintf() usage here is a hot spot in profiling. Perhaps one day,
64 * link-time optimization will be able to work on *printf() functions
65 * so we won't hurt code maintainability by optimizing away snprintf()
66 * ourselves. This function is ugly enough already
68 static off_t
http_get_resp_hdr(struct mog_fd
*mfd
, struct stat
*sb
)
70 struct mog_http
*http
= &mfd
->as
.http
;
73 struct mog_now
*now
= mog_now();
77 http_hdr_prepare(&buf
, &modified
, &len
, &sb
->st_mtime
);
80 if (http
->_p
.has_range
) {
83 if (http
->_p
.range_end
< 0 && http
->_p
.range_beg
< 0)
85 if (http
->_p
.range_beg
>= sb
->st_size
)
88 /* bytes=M-N where M > N */
89 if (http
->_p
.range_beg
>= 0 && http
->_p
.range_end
>= 0
90 && http
->_p
.range_beg
> http
->_p
.range_end
)
93 if (http
->_p
.range_end
< 0) { /* bytes=M- */
94 /* bytes starting at M until EOF */
95 assert(http
->_p
.range_beg
>= 0 && "should've sent 416");
96 offset
= (long long)http
->_p
.range_beg
;
97 count
= (long long)(sb
->st_size
- offset
);
98 } else if (http
->_p
.range_beg
< 0) { /* bytes=-N */
100 assert(http
->_p
.range_end
>= 0 && "should've sent 416");
101 offset
= (long long)(sb
->st_size
- http
->_p
.range_end
);
103 /* serve the entire file if client requested too much */
106 count
= (long long)(sb
->st_size
- offset
);
107 } else { /* bytes=M-N*/
108 assert(http
->_p
.range_beg
>= 0
109 && http
->_p
.range_end
>= 0
110 && "should've sent 416");
111 offset
= (long long)http
->_p
.range_beg
;
113 /* truncate responses to current file size */
114 if (http
->_p
.range_end
>= sb
->st_size
)
115 http
->_p
.range_end
= sb
->st_size
- 1;
116 count
= (long long)http
->_p
.range_end
+ 1 - offset
;
119 assert(count
> 0 && "bad count for 206 response");
120 assert(offset
>= 0 && "bad offset for 206 response");
123 struct mog_file
*file
= &http
->forward
->as
.file
;
126 file
->fsize
= (off_t
)(offset
+ count
);
129 rc
= snprintf(buf
, len
,
130 "HTTP/1.1 206 Partial Content\r\n"
132 "Last-Modified: %s\r\n"
133 "Content-Length: %lld\r\n"
134 "Content-Type: application/octet-stream\r\n"
135 "Content-Range: bytes %lld-%lld/%lld\r\n"
140 count
, /* Content-Length */
141 offset
, offset
+ count
- 1, /* bytes M-N */
142 (long long)sb
->st_size
,
143 http
->_p
.persistent
? "keep-alive" : "close");
144 } else if (http
->_p
.bad_range
) {
148 count
= (long long)sb
->st_size
;
149 rc
= snprintf(buf
, len
,
150 "HTTP/1.1 200 OK\r\n"
152 "Last-Modified: %s\r\n"
153 "Content-Length: %lld\r\n"
154 "Content-Type: application/octet-stream\r\n"
155 "Accept-Ranges: bytes\r\n"
161 http
->_p
.persistent
? "keep-alive" : "close");
164 /* TODO: put down the crack pipe and refactor this */
169 mog_file_close(http
->forward
);
170 http
->forward
= NULL
;
172 assert(http
->_p
.http_method
== MOG_HTTP_METHOD_HEAD
175 rc
= snprintf(buf
, len
,
176 "HTTP/1.1 " ERR416
"\r\n"
178 "Content-Length: 0\r\n"
179 "Content-Type: text/plain\r\n"
180 "Content-Range: bytes */%lld\r\n"
184 (long long)sb
->st_size
,
185 http
->_p
.persistent
? "keep-alive" : "close");
188 assert(rc
> 0 && rc
< len
&& "we suck at snprintf");
190 assert(http
->wbuf
== NULL
&& "tried to write to a busy client");
192 if (http
->_p
.http_method
== MOG_HTTP_METHOD_HEAD
)
196 TRACE(CMOGSTORED_HTTP_RES_START(mfd
->fd
, buf
+ sizeof("HTTP/1.1")));
197 http
->wbuf
= mog_trysend(mfd
->fd
, buf
, len
, (off_t
)count
);
202 static void emit_dev_usage(struct mog_fd
*mfd
)
204 struct mog_http
*http
= &mfd
->as
.http
;
205 struct mog_dev
*dev
= mog_dev_for(http
->svc
, http
->_p
.mog_devid
, false);
209 char *buf
, *modified
;
213 bool retried
= false;
218 CHECK(int, 0, pthread_mutex_lock(&dev
->usage_lock
));
224 CHECK(int, 0, pthread_mutex_unlock(&dev
->usage_lock
));
225 mog_dev_usage_update(dev
, http
->svc
);
229 http_hdr_prepare(&buf
, &modified
, &len
,
232 rc
= snprintf(buf
, len
,
233 "HTTP/1.1 200 OK\r\n"
235 "Last-Modified: %s\r\n"
236 "Content-Length: %u\r\n"
237 "Content-Type: text/plain\r\n"
238 "Accept-Ranges: bytes\r\n"
244 http
->_p
.persistent
? "keep-alive" : "close");
249 if (http
->_p
.http_method
== MOG_HTTP_METHOD_HEAD
) {
250 ok
= iov
.iov_base
= buf
;
252 } else if (len
>= dev
->usage_len
&& len
< ilen
) {
253 memcpy(buf
+ rc
, dev
->usage_txt
,
255 ok
= iov
.iov_base
= buf
;
256 iov
.iov_len
= rc
+ dev
->usage_len
;
260 CHECK(int, 0, pthread_mutex_unlock(&dev
->usage_lock
));
262 http
->wbuf
= mog_trywritev(mfd
->fd
, &iov
, 1);
265 mog_http_resp(mfd
, "404 Not Found", true);
268 void mog_http_get_open(struct mog_fd
*mfd
, char *buf
)
270 struct mog_http
*http
= &mfd
->as
.http
;
272 struct mog_file
*file
= NULL
;
276 if (http
->_p
.usage_txt
) {
281 path
= mog_http_path(http
, buf
);
282 if (!path
) goto forbidden
; /* path traversal attack */
283 assert(http
->forward
== NULL
&& "already have http->forward");
284 assert(path
[0] == '/' && "bad path");
286 TRACE(CMOGSTORED_HTTP_REQ_START(mfd
->fd
,
287 http
->_p
.http_method
== MOG_HTTP_METHOD_HEAD
?
288 "HEAD" : "GET", path
));
289 if (path
[1] == '\0') { /* keep "mogadm check" happy */
292 } else if (http
->_p
.http_method
== MOG_HTTP_METHOD_HEAD
) {
293 if (mog_stat(http
->svc
, path
, &sb
) < 0) goto err
;
294 if (!S_ISREG(sb
.st_mode
)) goto forbidden
;
296 http
->forward
= mog_file_open_read(http
->svc
, path
);
297 if (http
->forward
== NULL
)
300 file
= &http
->forward
->as
.file
;
301 assert(file
->path
== NULL
&& "build system bug");
302 if (fstat(http
->forward
->fd
, &sb
) < 0) {
303 PRESERVE_ERRNO( mog_file_close(http
->forward
) );
304 http
->forward
= NULL
;
307 if (!S_ISREG(sb
.st_mode
)) {
308 mog_file_close(http
->forward
);
309 http
->forward
= NULL
;
312 file
->fsize
= sb
.st_size
;
315 len
= http_get_resp_hdr(mfd
, &sb
);
317 /* http->forward may be NULL even if file is set if we had an error */
318 if (http
->wbuf
== NULL
&& http
->forward
) {
319 assert(file
&& "file unset but http->forward is set");
321 if (len
> (256 * 1024))
322 mog_fadv_sequential(http
->forward
->fd
, file
->foff
, len
);
329 mog_http_resp(mfd
, "403 Forbidden", true);
332 mog_http_resp(mfd
, "404 Not Found", true);
336 mog_http_resp(mfd
, "500 Internal Server Error", true);
340 static void sendfile_error(struct mog_fd
*file_mfd
, int sferr
)
342 struct mog_file
*file
= &file_mfd
->as
.file
;
345 if (!fstat(file_mfd
->fd
, &st
)) {
348 "sendfile on (dev=%lu,ino=%lu) failed at offset=%lld: %m",
349 (unsigned long)st
.st_dev
, (unsigned long)st
.st_ino
,
350 (long long)file
->foff
);
354 "sendfile failed at offset=%lld: %s (fstat error: %m)",
355 (long long)file
->foff
, strerror(sferr
));
359 enum mog_next
mog_http_get_in_progress(struct mog_fd
*mfd
)
361 struct mog_http
*http
= &mfd
->as
.http
;
362 struct mog_fd
*file_mfd
;
363 struct mog_file
*file
;
366 off_t max_sendfile
= (mog_ioq_contended() ? 1 : 100) * 1024 * 1024;
368 assert(http
->wbuf
== NULL
&& "can't serve file with http->wbuf");
369 assert(http
->forward
&& http
->forward
!= MOG_IOSTAT
&& "bad forward");
370 file_mfd
= http
->forward
;
371 file
= &file_mfd
->as
.file
;
373 assert(file
->fsize
>= 0 && "fsize is negative");
374 assert(file
->foff
>= 0 && "foff is negative");
375 count
= file
->fsize
- file
->foff
;
376 count
= count
> max_sendfile
? max_sendfile
: count
;
380 w
= sendfile(mfd
->fd
, file_mfd
->fd
, &file
->foff
, (size_t)count
);
382 if (file
->foff
== file
->fsize
) goto done
;
383 return MOG_NEXT_ACTIVE
;
386 case_EAGAIN
: return MOG_NEXT_WAIT_WR
;
387 case EINTR
: goto retry
;
393 sendfile_error(file_mfd
, errno
);
395 http
->_p
.persistent
= 0;
396 } else { /* w == 0 */
398 * if we can't fulfill the value set by our Content-Length:
399 * header, we must kill the TCP connection
401 http
->_p
.persistent
= 0;
403 "sendfile()-d 0 bytes at offset=%lld; file truncated?",
404 (long long)file
->foff
);
407 TRACE(CMOGSTORED_HTTP_BYTES_XFER(mfd
->fd
, file
->foff
));
408 mog_file_close(http
->forward
);
409 if (http
->_p
.persistent
) {
411 return MOG_NEXT_ACTIVE
;
413 return MOG_NEXT_CLOSE
;