/* * This is called during restore to create the file (if necessary) We must return in rp->create_status: * * CF_ERROR -- error * CF_SKIP -- skip processing this file * CF_EXTRACT -- extract the file (i.e.call i/o routines) * CF_CREATED -- created, but no content to extract (typically directories) */ static bRC createFile(bpContext *ctx, struct restore_pkt *rp) { int status; plugin_ctx *p_ctx = (plugin_ctx *)ctx->pContext; if (!p_ctx) { return bRC_Error; } status = rados_stat(p_ctx->ioctx, rp->ofname, &p_ctx->object_size, &p_ctx->object_mtime); if (status < 0) { rp->create_status = CF_EXTRACT; return bRC_OK; } else { switch (rp->replace) { case REPLACE_IFNEWER: if (rp->statp.st_mtime <= p_ctx->object_mtime) { Jmsg(ctx, M_SKIPPED, 0, _("File skipped. Not newer: %s\n"), rp->ofname); rp->create_status = CF_SKIP; goto bail_out; } break; case REPLACE_IFOLDER: if (rp->statp.st_mtime >= p_ctx->object_mtime) { Jmsg(ctx, M_SKIPPED, 0, _("File skipped. Not older: %s\n"), rp->ofname); rp->create_status = CF_SKIP; goto bail_out; } break; case REPLACE_NEVER: Jmsg(ctx, M_SKIPPED, 0, _("File skipped. Already exists: %s\n"), rp->ofname); rp->create_status = CF_SKIP; goto bail_out; case REPLACE_ALWAYS: break; } } rp->create_status = CF_EXTRACT; bail_out: return bRC_OK; }
static int testrados(void) { char tmp[32]; int i, r; rados_t cl; if (rados_create(&cl, NULL) < 0) { printf("error initializing\n"); return 1; } if (rados_conf_read_file(cl, NULL)) { printf("error reading configuration file\n"); return 1; } // Try to set a configuration option that doesn't exist. // This should fail. if (!rados_conf_set(cl, "config option that doesn't exist", "some random value")) { printf("error: succeeded in setting nonexistent config option\n"); return 1; } if (rados_conf_get(cl, "log to stderr", tmp, sizeof(tmp))) { printf("error: failed to read log_to_stderr from config\n"); return 1; } // Can we change it? if (rados_conf_set(cl, "log to stderr", "2")) { printf("error: error setting log_to_stderr\n"); return 1; } if (rados_conf_get(cl, "log to stderr", tmp, sizeof(tmp))) { printf("error: failed to read log_to_stderr from config\n"); return 1; } if (tmp[0] != '2') { printf("error: new setting for log_to_stderr failed to take effect.\n"); return 1; } if (rados_connect(cl)) { printf("error connecting\n"); return 1; } if (rados_connect(cl) == 0) { printf("second connect attempt didn't return an error\n"); return 1; } /* create an io_ctx */ r = rados_pool_create(cl, "foo"); printf("rados_pool_create = %d\n", r); rados_ioctx_t io_ctx; r = rados_ioctx_create(cl, "foo", &io_ctx); printf("rados_ioctx_create = %d, io_ctx = %p\n", r, io_ctx); /* list all pools */ { int buf_sz = rados_pool_list(cl, NULL, 0); printf("need buffer size of %d\n", buf_sz); char buf[buf_sz]; int r = rados_pool_list(cl, buf, buf_sz); if (r != buf_sz) { printf("buffer size mismatch: got %d the first time, but %d " "the second.\n", buf_sz, r); return 1; } const char *b = buf; printf("begin pools.\n"); while (1) { if (b[0] == '\0') break; printf(" pool: '%s'\n", b); b += strlen(b) + 1; }; printf("end pools.\n"); } /* stat */ struct rados_pool_stat_t st; r = rados_ioctx_pool_stat(io_ctx, &st); printf("rados_ioctx_pool_stat = %d, %lld KB, %lld objects\n", r, (long long)st.num_kb, (long long)st.num_objects); /* snapshots */ r = rados_ioctx_snap_create(io_ctx, "snap1"); printf("rados_ioctx_snap_create snap1 = %d\n", r); rados_snap_t snaps[10]; r = rados_ioctx_snap_list(io_ctx, snaps, 10); for (i=0; i<r; i++) { char name[100]; rados_ioctx_snap_get_name(io_ctx, snaps[i], name, sizeof(name)); printf("rados_ioctx_snap_list got snap %lld %s\n", (long long)snaps[i], name); } rados_snap_t snapid; r = rados_ioctx_snap_lookup(io_ctx, "snap1", &snapid); printf("rados_ioctx_snap_lookup snap1 got %lld, result %d\n", (long long)snapid, r); r = rados_ioctx_snap_remove(io_ctx, "snap1"); printf("rados_ioctx_snap_remove snap1 = %d\n", r); /* sync io */ time_t tm; char buf[128], buf2[128]; time(&tm); snprintf(buf, 128, "%s", ctime(&tm)); const char *oid = "foo_object"; r = rados_write(io_ctx, oid, buf, strlen(buf) + 1, 0); printf("rados_write = %d\n", r); r = rados_read(io_ctx, oid, buf2, sizeof(buf2), 0); printf("rados_read = %d\n", r); if (memcmp(buf, buf2, r)) printf("*** content mismatch ***\n"); /* attrs */ if (do_rados_setxattr(io_ctx, oid, "b", "2")) return 1; if (do_rados_setxattr(io_ctx, oid, "a", "1")) return 1; if (do_rados_setxattr(io_ctx, oid, "c", "3")) return 1; if (do_rados_getxattr(io_ctx, oid, "a", "1")) return 1; if (do_rados_getxattr(io_ctx, oid, "b", "2")) return 1; if (do_rados_getxattr(io_ctx, oid, "c", "3")) return 1; const char *exkeys[] = { "a", "b", "c", NULL }; const char *exvals[] = { "1", "2", "3", NULL }; if (do_rados_getxattrs(io_ctx, oid, exkeys, exvals)) return 1; uint64_t size; time_t mtime; r = rados_stat(io_ctx, oid, &size, &mtime); printf("rados_stat size = %lld mtime = %d = %d\n", (long long)size, (int)mtime, r); r = rados_stat(io_ctx, "does_not_exist", NULL, NULL); printf("rados_stat(does_not_exist) = %d\n", r); /* exec */ rados_exec(io_ctx, oid, "crypto", "md5", buf, strlen(buf) + 1, buf, 128); printf("exec result=%s\n", buf); r = rados_read(io_ctx, oid, buf2, 128, 0); printf("read result=%s\n", buf2); printf("size=%d\n", r); /* aio */ rados_completion_t a, b; rados_aio_create_completion(0, 0, 0, &a); rados_aio_create_completion(0, 0, 0, &b); rados_aio_write(io_ctx, "a", a, buf, 100, 0); rados_aio_write(io_ctx, "../b/bb_bb_bb\\foo\\bar", b, buf, 100, 0); rados_aio_wait_for_safe(a); printf("a safe\n"); rados_aio_wait_for_safe(b); printf("b safe\n"); rados_aio_release(a); rados_aio_release(b); /* test flush */ printf("testing aio flush\n"); rados_completion_t c; rados_aio_create_completion(0, 0, 0, &c); rados_aio_write(io_ctx, "c", c, buf, 100, 0); int safe = rados_aio_is_safe(c); printf("a should not yet be safe and ... %s\n", safe ? "is":"is not"); assert(!safe); rados_aio_flush(io_ctx); safe = rados_aio_is_safe(c); printf("a should be safe and ... %s\n", safe ? "is":"is not"); assert(safe); rados_aio_release(c); rados_read(io_ctx, "../b/bb_bb_bb\\foo\\bar", buf2, 128, 0); /* list objects */ rados_list_ctx_t h; r = rados_objects_list_open(io_ctx, &h); printf("rados_list_objects_open = %d, h = %p\n", r, h); const char *poolname; while (rados_objects_list_next(h, &poolname) == 0) printf("rados_list_objects_next got object '%s'\n", poolname); rados_objects_list_close(h); /* stat */ r = rados_ioctx_pool_stat(io_ctx, &st); printf("rados_stat_pool = %d, %lld KB, %lld objects\n", r, (long long)st.num_kb, (long long)st.num_objects); /* delete a pool */ printf("rados_delete_pool = %d\n", r); rados_ioctx_destroy(io_ctx); r = rados_pool_delete(cl, "foo"); printf("rados_ioctx_pool_delete = %d\n", r); rados_shutdown(cl); return 0; }
/* * Get the next object to backup. * - Get the next objectname from the list iterator. * - Check using AcceptFile if it matches the fileset. */ static bRC get_next_object_to_backup(bpContext *ctx) { int status; struct save_pkt sp; plugin_ctx *p_ctx = (plugin_ctx *)ctx->pContext; while (1) { #if defined(HAVE_RADOS_NAMESPACES) && defined(HAVE_RADOS_NOBJECTS_LIST) status = rados_nobjects_list_next(p_ctx->list_iterator, &p_ctx->object_name, NULL, NULL); #else status = rados_objects_list_next(p_ctx->list_iterator, &p_ctx->object_name, NULL); #endif if (status < 0) { berrno be; switch (status) { case -ENOENT: #if defined(HAVE_RADOS_NAMESPACES) && defined(HAVE_RADOS_NOBJECTS_LIST) rados_nobjects_list_close(p_ctx->list_iterator); #else rados_objects_list_close(p_ctx->list_iterator); #endif p_ctx->list_iterator = NULL; return bRC_OK; default: #if defined(HAVE_RADOS_NAMESPACES) && defined(HAVE_RADOS_NOBJECTS_LIST) Jmsg(ctx, M_ERROR, "rados_nobjects_list_next() failed: %s\n", be.bstrerror(-status)); #else Jmsg(ctx, M_ERROR, "rados_objects_list_next() failed: %s\n", be.bstrerror(-status)); #endif return bRC_Error; } } else { Mmsg(p_ctx->next_filename, "%s/%s", p_ctx->rados_poolname, p_ctx->object_name); } /* * See if we accept this file under the currently loaded fileset. */ memset(&sp, 0, sizeof(sp)); sp.pkt_size = sizeof(sp); sp.pkt_end = sizeof(sp); sp.fname = p_ctx->next_filename; sp.statp.st_mode = 0700 | S_IFREG; if (bfuncs->AcceptFile(ctx, &sp) == bRC_Skip) { continue; } status = rados_stat(p_ctx->ioctx, p_ctx->object_name, &p_ctx->object_size, &p_ctx->object_mtime); if (status < 0) { berrno be; Jmsg(ctx, M_ERROR, "rados_stat(%s) failed: %s\n", p_ctx->object_name, be.bstrerror(-status)); return bRC_Error; } /* * If we made it here we have the next object to backup. */ break; } return bRC_More; }
static int uwsgi_rados_request(struct wsgi_request *wsgi_req) { char filename[PATH_MAX+1]; if (!wsgi_req->len) { uwsgi_log( "Empty request. skip.\n"); return -1; } if (uwsgi_parse_vars(wsgi_req)) { return -1; } // blocks empty paths if (wsgi_req->path_info_len == 0 || wsgi_req->path_info_len > PATH_MAX) { uwsgi_403(wsgi_req); return UWSGI_OK; } wsgi_req->app_id = uwsgi_get_app_id(wsgi_req, wsgi_req->appid, wsgi_req->appid_len, rados_plugin.modifier1); if (wsgi_req->app_id == -1 && !uwsgi.no_default_app && uwsgi.default_app > -1) { if (uwsgi_apps[uwsgi.default_app].modifier1 == rados_plugin.modifier1) { wsgi_req->app_id = uwsgi.default_app; } } if (wsgi_req->app_id == -1) { uwsgi_404(wsgi_req); return UWSGI_OK; } struct uwsgi_app *ua = &uwsgi_apps[wsgi_req->app_id]; if (wsgi_req->path_info_len > ua->mountpoint_len && memcmp(wsgi_req->path_info, ua->mountpoint, ua->mountpoint_len) == 0) { memcpy(filename, wsgi_req->path_info+ua->mountpoint_len, wsgi_req->path_info_len-ua->mountpoint_len); filename[wsgi_req->path_info_len-ua->mountpoint_len] = 0; } else { memcpy(filename, wsgi_req->path_info, wsgi_req->path_info_len); filename[wsgi_req->path_info_len] = 0; } // in multithread mode the memory is different (as we need a ctx for each thread) !!! rados_ioctx_t ctx; if (uwsgi.threads > 1) { rados_ioctx_t *ctxes = (rados_ioctx_t *) ua->responder0; ctx = ctxes[wsgi_req->async_id]; } else { ctx = (rados_ioctx_t) ua->responder0; } struct uwsgi_rados_mountpoint *urmp = (struct uwsgi_rados_mountpoint *) ua->responder1; uint64_t stat_size = 0; time_t stat_mtime = 0; struct uwsgi_rados_io *urio = &urados.urio[wsgi_req->async_id]; if (uwsgi.async > 0) { // no need to lock here (the rid protect us) if (pipe(urio->fds)) { uwsgi_error("uwsgi_rados_read_async()/pipe()"); uwsgi_500(wsgi_req); return UWSGI_OK; } } int ret = -1; int timeout = urmp->timeout ? urmp->timeout : urados.timeout; if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "OPTIONS", 7)) { if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6)) goto end; if (uwsgi_response_add_header(wsgi_req, "Dav", 3, "1", 1)) goto end; struct uwsgi_buffer *ub_allow = uwsgi_buffer_new(64); if (uwsgi_buffer_append(ub_allow, "OPTIONS, GET, HEAD", 18)) { uwsgi_buffer_destroy(ub_allow); goto end; } if (urmp->allow_put) { if (uwsgi_buffer_append(ub_allow, ", PUT", 5)) { uwsgi_buffer_destroy(ub_allow); goto end; } } if (urmp->allow_delete) { if (uwsgi_buffer_append(ub_allow, ", DELETE", 8)) { uwsgi_buffer_destroy(ub_allow); goto end; } } if (urmp->allow_mkcol) { if (uwsgi_buffer_append(ub_allow, ", MKCOL", 7)) { uwsgi_buffer_destroy(ub_allow); goto end; } } if (urmp->allow_propfind) { if (uwsgi_buffer_append(ub_allow, ", PROPFIND", 10)) { uwsgi_buffer_destroy(ub_allow); goto end; } } uwsgi_response_add_header(wsgi_req, "Allow", 5, ub_allow->buf, ub_allow->pos); uwsgi_buffer_destroy(ub_allow); goto end; } // empty paths are mapped to propfind if (wsgi_req->path_info_len == 1 && wsgi_req->path_info[0] == '/') { if (urmp->allow_propfind && !uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "PROPFIND", 8)) { uwsgi_rados_propfind(wsgi_req, ctx, NULL, 0, 0, timeout); goto end; } uwsgi_405(wsgi_req); goto end; } // MKCOL does not require stat if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "MKCOL", 5)) { if (!urmp->allow_mkcol) { uwsgi_405(wsgi_req); goto end; } ret = rados_pool_create(urmp->cluster, filename); if (ret < 0) { if (ret == -EEXIST) { uwsgi_405(wsgi_req); } else { uwsgi_500(wsgi_req); } goto end; } uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11); goto end; } if (uwsgi.async > 0) { ret = uwsgi_rados_async_stat(urio, ctx, filename, &stat_size, &stat_mtime, timeout); } else { ret = rados_stat(ctx, filename, &stat_size, &stat_mtime); } // PUT AND MKCOL can be used for non-existent objects if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "PUT", 3)) { if (!urmp->allow_put) { uwsgi_405(wsgi_req); goto end; } if (ret == 0) { if (uwsgi_rados_delete(wsgi_req, ctx, filename, timeout)) { uwsgi_500(wsgi_req); goto end; } } if (uwsgi_rados_put(wsgi_req, ctx, filename, urmp->put_buffer_size, timeout)) { uwsgi_500(wsgi_req); goto end; } uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11); goto end; } else if (ret < 0) { if (ret == -ENOENT) uwsgi_404(wsgi_req); else uwsgi_403(wsgi_req); goto end; } if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "DELETE", 6)) { if (!urmp->allow_delete) { uwsgi_405(wsgi_req); goto end; } if (uwsgi_rados_delete(wsgi_req, ctx, filename, timeout)) { uwsgi_403(wsgi_req); goto end; } uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6); goto end; } if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "PROPFIND", 8)) { if (!urmp->allow_propfind) { uwsgi_405(wsgi_req); goto end; } uwsgi_rados_propfind(wsgi_req, ctx, filename, stat_size, stat_mtime, timeout); goto end; } if (uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "HEAD", 4) && uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "GET", 3)) { uwsgi_405(wsgi_req); goto end; } uint64_t offset = 0; uint64_t remains = stat_size; uwsgi_request_fix_range_for_size(wsgi_req, remains); switch (wsgi_req->range_parsed) { case UWSGI_RANGE_INVALID: if (uwsgi_response_prepare_headers(wsgi_req, "416 Requested Range Not Satisfiable", 35)) goto end; if (uwsgi_response_add_content_range(wsgi_req, -1, -1, stat_size)) goto end; return 0; case UWSGI_RANGE_VALID: offset = wsgi_req->range_from; remains = wsgi_req->range_to - wsgi_req->range_from + 1; if (uwsgi_response_prepare_headers(wsgi_req, "206 Partial Content", 19)) goto end; break; default: /* UWSGI_RANGE_NOT_PARSED */ if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6)) return -1; } size_t mime_type_len = 0; char *mime_type = uwsgi_get_mime_type(wsgi_req->path_info, wsgi_req->path_info_len, &mime_type_len); if (mime_type) { if (uwsgi_response_add_content_type(wsgi_req, mime_type, mime_type_len)) goto end; } if (uwsgi_response_add_last_modified(wsgi_req, (uint64_t) stat_mtime)) goto end; // set Content-Length to actual result size if (uwsgi_response_add_content_length(wsgi_req, remains)) goto end; if (wsgi_req->range_parsed == UWSGI_RANGE_VALID) { // here use the original size !!! if (uwsgi_response_add_content_range(wsgi_req, wsgi_req->range_from, wsgi_req->range_to, stat_size)) goto end; } // skip body on HEAD if (uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "HEAD", 4)) { if (uwsgi.async > 0) { if (uwsgi_rados_read_async(wsgi_req, ctx, filename, offset, remains, urmp->buffer_size, timeout)) goto end; } else { if (uwsgi_rados_read_sync(wsgi_req, ctx, filename, offset, remains, urmp->buffer_size)) goto end; } } end: if (uwsgi.async > 0) { close(urio->fds[0]); close(urio->fds[1]); } return UWSGI_OK; }
static void uwsgi_rados_propfind(struct wsgi_request *wsgi_req, rados_ioctx_t ctx, char *key, uint64_t size, time_t mtime, int timeout) { // consume the body size_t remains = wsgi_req->post_cl; while(remains > 0) { ssize_t body_len = 0; char *body = uwsgi_request_body_read(wsgi_req, UMIN(remains, 32768), &body_len); if (!body || body == uwsgi.empty) break; remains -= body_len; } if (uwsgi_response_prepare_headers(wsgi_req, "207 Multi-Status", 16)) return; if (uwsgi_response_add_content_type(wsgi_req, "text/xml; charset=\"utf-8\"", 25)) return; struct uwsgi_buffer *ub = uwsgi_webdav_multistatus_new(); if (!ub) return; if (key) { size_t mime_type_len = 0; char *mime_type = uwsgi_get_mime_type(key, strlen(key), &mime_type_len); char *slashed = uwsgi_concat2("/", key); if (uwsgi_webdav_propfind_item_add(ub, slashed, strlen(key)+1, size, mtime, mime_type, mime_type_len, NULL, 0, NULL, 0)) { free(slashed); goto end; } free(slashed); if (uwsgi_webdav_multistatus_close(ub)) goto end; uwsgi_response_write_body_do(wsgi_req, ub->buf, ub->pos); goto end; } // request for / size_t depth = 0; uint16_t http_depth_len = 0; char *http_depth = uwsgi_get_var(wsgi_req, "HTTP_DEPTH", 10, &http_depth_len); if (http_depth) { depth = uwsgi_str_num(http_depth, http_depth_len); } if (depth == 0) { if (uwsgi_webdav_propfind_item_add(ub, "/", 1, 0, 0, NULL, 0, NULL, 0, NULL, 0)) { goto end; } if (uwsgi_webdav_multistatus_close(ub)) goto end; uwsgi_response_write_body_do(wsgi_req, ub->buf, ub->pos); goto end; } struct uwsgi_rados_io *urio = &urados.urio[wsgi_req->async_id]; rados_list_ctx_t ctx_list; if (rados_objects_list_open(ctx, &ctx_list) < 0) { goto end; } char *entry = NULL; while(rados_objects_list_next(ctx_list, (const char **)&entry, NULL) == 0) { uint64_t stat_size = 0; time_t stat_mtime = 0; if (uwsgi.async > 0) { if (uwsgi_rados_async_stat(urio, ctx, entry, &stat_size, &stat_mtime, timeout) < 0) goto end; } else { if (rados_stat(ctx, entry, &stat_size, &stat_mtime) < 0) goto end; } size_t mime_type_len = 0; char *mime_type = uwsgi_get_mime_type(entry, strlen(entry), &mime_type_len); char *slashed = uwsgi_concat2("/", entry); if (uwsgi_webdav_propfind_item_add(ub, slashed, strlen(entry)+1, stat_size, stat_mtime, mime_type, mime_type_len, NULL, 0, NULL, 0)) { free(slashed); goto end; } free(slashed); if (uwsgi_response_write_body_do(wsgi_req, ub->buf, ub->pos)) goto end; // reset buffer; ub->pos = 0; } rados_objects_list_close(ctx_list); if (uwsgi_webdav_multistatus_close(ub)) goto end; uwsgi_response_write_body_do(wsgi_req, ub->buf, ub->pos); end: uwsgi_buffer_destroy(ub); }
static int uwsgi_rados_request(struct wsgi_request *wsgi_req) { char filename[PATH_MAX+1]; if (!wsgi_req->uh->pktsize) { uwsgi_log( "Empty request. skip.\n"); return -1; } if (uwsgi_parse_vars(wsgi_req)) { return -1; } // blocks empty paths if (wsgi_req->path_info_len == 0 || wsgi_req->path_info_len > PATH_MAX) { uwsgi_403(wsgi_req); return UWSGI_OK; } wsgi_req->app_id = uwsgi_get_app_id(wsgi_req, wsgi_req->appid, wsgi_req->appid_len, rados_plugin.modifier1); if (wsgi_req->app_id == -1 && !uwsgi.no_default_app && uwsgi.default_app > -1) { if (uwsgi_apps[uwsgi.default_app].modifier1 == rados_plugin.modifier1) { wsgi_req->app_id = uwsgi.default_app; } } if (wsgi_req->app_id == -1) { uwsgi_404(wsgi_req); return UWSGI_OK; } struct uwsgi_app *ua = &uwsgi_apps[wsgi_req->app_id]; if (wsgi_req->path_info_len > ua->mountpoint_len && memcmp(wsgi_req->path_info, ua->mountpoint, ua->mountpoint_len) == 0) { memcpy(filename, wsgi_req->path_info+ua->mountpoint_len, wsgi_req->path_info_len-ua->mountpoint_len); } else { memcpy(filename, wsgi_req->path_info, wsgi_req->path_info_len); } filename[wsgi_req->path_info_len] = 0; struct { uint64_t size; time_t mtime; } st; rados_ioctx_t ctx = ua->responder1; int r = rados_stat(ctx, filename, &st.size, &st.mtime); if (r < 0) { if (r == -ENOENT) uwsgi_404(wsgi_req); else uwsgi_403(wsgi_req); return UWSGI_OK; } if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6)) goto end; size_t mime_type_len = 0; char *mime_type = uwsgi_get_mime_type(wsgi_req->path_info, wsgi_req->path_info_len, &mime_type_len); if (mime_type) { if (uwsgi_response_add_content_type(wsgi_req, mime_type, mime_type_len)) goto end; } if (uwsgi_response_add_last_modified(wsgi_req, (uint64_t) st.mtime)) goto end; if (uwsgi_response_add_content_length(wsgi_req, st.size)) goto end; // skip body on HEAD if (uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "HEAD", 4)) { size_t remains = st.size; if (uwsgi.async > 1) { if (uwsgi_rados_read_async(wsgi_req, ctx, filename, remains)) goto end; } else { if (uwsgi_rados_read_sync(wsgi_req, ctx, filename, remains)) goto end; } } end: return UWSGI_OK; }