cmogstored 1.8.1 - use default system stack size
[cmogstored.git] / http_get.c
blobc1a0324a3c86cf82fa014bbc82512ffc9486209f
1 /*
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>
4 */
6 #include "cmogstored.h"
7 #include "http.h"
9 #if defined(HAVE_SYS_SENDFILE_H) && !defined(HAVE_BSD_SENDFILE)
10 # include <sys/sendfile.h>
11 #endif
13 #if defined(__linux__)
14 /* all good */
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)
26 int flags = 0;
27 off_t sbytes = 0;
28 int rc;
30 rc = sendfile(filefd, sockfd, *off, count, NULL, &sbytes, flags);
31 if (sbytes > 0) {
32 *off += sbytes;
33 return (ssize_t)sbytes;
36 return (ssize_t)rc;
39 # if defined(HAVE_BSD_SENDFILE) /* Debian GNU/kFreeBSD */
40 # undef sendfile
41 # endif /* HAVE_BSD_SENDFILE */
42 # define sendfile(out_fd, in_fd, offset, count) \
43 linux_sendfile((out_fd),(in_fd),(offset),(count))
44 #else
45 # include "compat_sendfile.h"
46 #endif
48 #define ERR416 "416 Requested Range Not Satisfiable"
50 static void
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);
61 * TODO: refactor this
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;
71 char *buf, *modified;
72 size_t len;
73 struct mog_now *now = mog_now();
74 long long count;
75 int rc;
77 http_hdr_prepare(&buf, &modified, &len, &sb->st_mtime);
79 /* validate ranges */
80 if (http->_p.has_range) {
81 long long offset;
83 if (http->_p.range_end < 0 && http->_p.range_beg < 0)
84 goto bad_range;
85 if (http->_p.range_beg >= sb->st_size)
86 goto bad_range;
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)
91 goto bad_range;
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 */
99 /* last N bytes */
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 */
104 if (offset < 0)
105 goto resp_200;
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");
122 if (http->forward) {
123 struct mog_file *file = &http->forward->as.file;
125 file->foff = offset;
126 file->fsize = (off_t)(offset + count);
129 rc = snprintf(buf, len,
130 "HTTP/1.1 206 Partial Content\r\n"
131 "Date: %s\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"
136 "Connection: %s\r\n"
137 "\r\n",
138 now->httpdate,
139 modified,
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) {
145 goto bad_range;
146 } else {
147 resp_200:
148 count = (long long)sb->st_size;
149 rc = snprintf(buf, len,
150 "HTTP/1.1 200 OK\r\n"
151 "Date: %s\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"
156 "Connection: %s\r\n"
157 "\r\n",
158 now->httpdate,
159 modified,
160 count,
161 http->_p.persistent ? "keep-alive" : "close");
164 /* TODO: put down the crack pipe and refactor this */
165 if (0) {
166 bad_range:
167 count = 0;
168 if (http->forward) {
169 mog_file_close(http->forward);
170 http->forward = NULL;
171 } else {
172 assert(http->_p.http_method == MOG_HTTP_METHOD_HEAD
173 && "not HTTP HEAD");
175 rc = snprintf(buf, len,
176 "HTTP/1.1 " ERR416 "\r\n"
177 "Date: %s\r\n"
178 "Content-Length: 0\r\n"
179 "Content-Type: text/plain\r\n"
180 "Content-Range: bytes */%lld\r\n"
181 "Connection: %s\r\n"
182 "\r\n",
183 now->httpdate,
184 (long long)sb->st_size,
185 http->_p.persistent ? "keep-alive" : "close");
188 assert(rc > 0 && rc < len && "we suck at snprintf");
189 len = (size_t)rc;
190 assert(http->wbuf == NULL && "tried to write to a busy client");
192 if (http->_p.http_method == MOG_HTTP_METHOD_HEAD)
193 count = 0;
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);
199 return (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);
206 void *ok = NULL;
208 if (dev) {
209 char *buf, *modified;
210 size_t len, ilen;
211 struct mog_now *now;
212 int rc;
213 bool retried = false;
214 struct iovec iov;
216 retry:
217 now = mog_now();
218 CHECK(int, 0, pthread_mutex_lock(&dev->usage_lock));
219 ok = dev->usage_txt;
220 if (!ok) {
221 if (retried)
222 goto out_unlock;
223 retried = true;
224 CHECK(int, 0, pthread_mutex_unlock(&dev->usage_lock));
225 mog_dev_usage_update(dev, http->svc);
226 goto retry;
229 http_hdr_prepare(&buf, &modified, &len,
230 &dev->usage_mtime);
231 ilen = len;
232 rc = snprintf(buf, len,
233 "HTTP/1.1 200 OK\r\n"
234 "Date: %s\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"
239 "Connection: %s\r\n"
240 "\r\n",
241 now->httpdate,
242 modified,
243 dev->usage_len,
244 http->_p.persistent ? "keep-alive" : "close");
246 ok = NULL;
247 if (rc > 0) {
248 len -= rc;
249 if (http->_p.http_method == MOG_HTTP_METHOD_HEAD) {
250 ok = iov.iov_base = buf;
251 iov.iov_len = rc;
252 } else if (len >= dev->usage_len && len < ilen) {
253 memcpy(buf + rc, dev->usage_txt,
254 dev->usage_len);
255 ok = iov.iov_base = buf;
256 iov.iov_len = rc + dev->usage_len;
259 out_unlock:
260 CHECK(int, 0, pthread_mutex_unlock(&dev->usage_lock));
261 if (ok)
262 http->wbuf = mog_trywritev(mfd->fd, &iov, 1);
264 if (!dev || !ok)
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;
271 struct stat sb;
272 struct mog_file *file = NULL;
273 char *path;
274 off_t len;
276 if (http->_p.usage_txt) {
277 emit_dev_usage(mfd);
278 return;
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 */
290 sb.st_mtime = 0;
291 sb.st_size = 0;
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;
295 } else {
296 http->forward = mog_file_open_read(http->svc, path);
297 if (http->forward == NULL)
298 goto err;
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;
305 goto err;
307 if (!S_ISREG(sb.st_mode)) {
308 mog_file_close(http->forward);
309 http->forward = NULL;
310 goto forbidden;
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);
324 return;
325 err:
326 switch (errno) {
327 case EACCES:
328 forbidden:
329 mog_http_resp(mfd, "403 Forbidden", true);
330 return;
331 case ENOENT:
332 mog_http_resp(mfd, "404 Not Found", true);
333 return;
335 PRESERVE_ERRNO(do {
336 mog_http_resp(mfd, "500 Internal Server Error", true);
337 } while(0));
340 static void sendfile_error(struct mog_fd *file_mfd, int sferr)
342 struct mog_file *file = &file_mfd->as.file;
343 struct stat st;
345 if (!fstat(file_mfd->fd, &st)) {
346 errno = sferr;
347 syslog(LOG_ERR,
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);
352 else {
353 syslog(LOG_ERR,
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;
364 ssize_t w;
365 off_t count;
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;
377 if (count == 0)
378 goto done;
379 retry:
380 w = sendfile(mfd->fd, file_mfd->fd, &file->foff, (size_t)count);
381 if (w > 0) {
382 if (file->foff == file->fsize) goto done;
383 return MOG_NEXT_ACTIVE;
384 } else if (w < 0) {
385 switch (errno) {
386 case_EAGAIN: return MOG_NEXT_WAIT_WR;
387 case EINTR: goto retry;
388 case EPIPE:
389 case ENOTCONN:
390 case ECONNRESET:
391 break;
392 default:
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;
402 syslog(LOG_ERR,
403 "sendfile()-d 0 bytes at offset=%lld; file truncated?",
404 (long long)file->foff);
406 done:
407 TRACE(CMOGSTORED_HTTP_BYTES_XFER(mfd->fd, file->foff));
408 mog_file_close(http->forward);
409 if (http->_p.persistent) {
410 mog_http_reset(mfd);
411 return MOG_NEXT_ACTIVE;
413 return MOG_NEXT_CLOSE;