diff --git a/src/common/config_opts.h b/src/common/config_opts.h index c3d056d175f007..b30f1dc4605f2b 100644 --- a/src/common/config_opts.h +++ b/src/common/config_opts.h @@ -1465,6 +1465,9 @@ OPTION(rgw_torrent_encoding, OPT_STR, "") // torrent field encoding OPTION(rgw_torrent_origin, OPT_STR, "") // torrent origin OPTION(rgw_torrent_sha_unit, OPT_INT, 512*1024) //torrent field piece length 521K +OPTION(rgw_multipart_copy, OPT_BOOL, false) // enable/disable put multipart copy +OPTION(rgw_multipart_copy_env, OPT_BOOL, false) // enable/disable env args + // This will be set to true when it is safe to start threads. // Once it is true, it will never change. OPTION(internal_safe_to_start_threads, OPT_BOOL, false) diff --git a/src/rgw/Makefile.am b/src/rgw/Makefile.am index 613fa6a21711b7..0230478b683905 100644 --- a/src/rgw/Makefile.am +++ b/src/rgw/Makefile.am @@ -90,7 +90,8 @@ librgw_la_SOURCES = \ rgw/rgw_xml.cc \ rgw/rgw_xml_enc.cc \ rgw/rgw_website.cc \ - rgw/rgw_torrent.cc + rgw/rgw_torrent.cc \ + rgw/rgw_multi_copy.cc if WITH_OPENLDAP librgw_la_SOURCES += rgw/rgw_ldap.cc @@ -270,7 +271,8 @@ noinst_HEADERS += \ rgw/rgw_civetweb_log.h \ rgw/rgw_website.h \ rgw/rgw_rest_s3website.h \ - rgw/rgw_torrent.h \ + rgw/rgw_torrent.h \ + rgw/rgw_multi_copy.h \ civetweb/civetweb.h \ civetweb/include/civetweb.h \ civetweb/include/civetweb_conf.h \ diff --git a/src/rgw/rgw_multi_copy.cc b/src/rgw/rgw_multi_copy.cc new file mode 100644 index 00000000000000..113f71d3c5156d --- /dev/null +++ b/src/rgw/rgw_multi_copy.cc @@ -0,0 +1,314 @@ +#include +#include + +#include + +#include "rgw_op.h" +#include "include/rados/librados.hpp" + +#define dout_subsys ceph_subsys_rgw + +using namespace std; +using namespace librados; +using namespace boost; + +RGWPutMultipartCopy::RGWPutMultipartCopy() +{ + begin = 0; + end = 0; + have_read = 0; + total = 0; + copy_permission = false; + copy_all = false; + if_mod = NULL; + if_unmod = NULL; + if_match = NULL; + if_nomatch = NULL; + mod_ptr = NULL; + unmod_ptr = NULL; + store = NULL; + s = NULL; + op_target = NULL; + read_op = NULL; +} + +RGWPutMultipartCopy::~RGWPutMultipartCopy() +{ + if (read_op) + { + delete read_op; + } + if (op_target) + { + delete op_target; + } +} + +void RGWPutMultipartCopy::init(struct req_state *p_req, RGWRados *p_store) +{ + s = p_req; + store = p_store; +} + +bool RGWPutMultipartCopy::get_permission() +{ + return copy_permission; +} + +int RGWPutMultipartCopy::get_read_len(off_t ofs) +{ + size_t len = 0; + uint64_t chunk_size = s->cct->_conf->rgw_max_chunk_size; + if (total) + { + len = total - ofs; + if (len > chunk_size) + { + len = chunk_size; + } + } + else + { + len = 0; + } + + if (len <= 0) + { + string part_num = s->info.args.get("partNumber"); + ldout(s->cct, 0) << "NOTICE: get_read_len() partNum = " << part_num << " copy finish" << dendl; + return 0; + } + + return len; +} + +int RGWPutMultipartCopy::copy_data(int len, bufferlist &bl) +{ + off_t read_begin = begin; + off_t read_end = read_begin + len -1; + int read_len = read_op->read(read_begin, read_end, bl); + begin += read_len; + have_read += read_len; + return read_len; +} + +int RGWPutMultipartCopy::get_data(off_t ofs, bufferlist &bl) +{ + int len = get_read_len(ofs); + if (len == 0) + { + return 0; + } + + return copy_data(len, bl); +} + +int RGWPutMultipartCopy::get_data_ready() +{ + RGWObjectCtx& obj_ctx = *static_cast(s->obj_ctx); + rgw_bucket src_bucket; + RGWBucketInfo src_bucket_info; + rgw_obj src_obj(src_bucket, src_object); + obj_ctx.set_atomic(src_obj); + + map src_attrs; + int op_ret = store->get_bucket_info(obj_ctx, src_tenant_name, src_bucket_name, + src_bucket_info, NULL, &src_attrs); + if (op_ret < 0) + { + ldout(s->cct, 0) << "ERROR: read_op->get_bucket_info() returned op_ret = " << op_ret << dendl; + return op_ret; + } + + rgw_bucket bucket; + bucket = src_bucket_info.bucket; + rgw_obj obj(bucket, src_object.name); + + string oid; + string key; + get_obj_bucket_and_oid_loc(obj, bucket, oid, key); + + op_target = new RGWRados::Object(store, src_bucket_info, *static_cast(s->obj_ctx), obj); + read_op = new RGWRados::Object::Read(op_target); + + ceph::real_time delete_at; + map attrs; + encode_delete_at_attr(delete_at, attrs); + + bool high_precision_time = (s->system_request); + uint64_t total_len, obj_size; + ceph::real_time src_mtime; + + read_op->conds.mod_ptr = mod_ptr; + read_op->conds.unmod_ptr = unmod_ptr; + read_op->conds.high_precision_time = high_precision_time; + read_op->conds.if_match = if_match; + read_op->conds.if_nomatch = if_nomatch; + read_op->params.attrs = &attrs; + read_op->params.lastmod = &src_mtime; + read_op->params.read_size = &total_len; + read_op->params.obj_size = &obj_size; + read_op->params.perr = &s->err; + + off_t ofs_start = 0; + off_t ofs_end = -1; + op_ret = read_op->prepare(&ofs_start, &ofs_end); + if (op_ret < 0) + { + ldout(s->cct, 0) << "ERROR: read_op->prepare() returned op_ret = " << op_ret << dendl; + return op_ret; + } + + if (end >= ofs_end) + { + end = ofs_end; + } + + if (begin <= ofs_start) + { + begin = ofs_start; + } + + if (copy_all) + { + begin = ofs_start; + end = ofs_end; + } + + total = end - begin + 1; + ldout(s->cct, 0) << "NOTICE: get_data_ready() begin = " << begin << dendl; + ldout(s->cct, 0) << "NOTICE: get_data_ready() end = " << end << dendl; + ldout(s->cct, 0) << "NOTICE: get_data_ready() total =" << total << dendl; + return 0; +} + +int RGWPutMultipartCopy::get_range() +{ + const char *copy_range = NULL; + copy_range = s->info.env->get("HTTP_X_AMZ_COPY_SOURCE_RANGE"); + if (!copy_range) + { + copy_all = true; + return 0; + } + + ldout(s->cct, 0) << "NOTICE: get_range() copy_range = " << copy_range << dendl; + string range_str = copy_range; + + int pos_pre = range_str.find('='); + if (pos_pre < 0) + { + return -EINVAL; + } + + int pos = range_str.find('-'); + if (pos < 0) + { + return -EINVAL; + } + + string range_begin = range_str.substr(pos_pre + 1, pos); + begin = atoi(range_begin.c_str()); + ldout(s->cct, 0) << "NOTICE: get_range() begin = " << begin << dendl; + + string range_end = range_str.substr(pos + 1); + end = atoi(range_end.c_str()); + + if (begin > end) + { + return -EINVAL; + } + + ldout(s->cct, 0) << "NOTICE: get_range() end = " << end << dendl; + return 0; +} + +int RGWPutMultipartCopy::get_source_bucket_info(string &src_tenant_name, + string &src_bucket_name, + rgw_obj_key &src_object) +{ + const char *copy_source = NULL; + copy_source = s->info.env->get("HTTP_X_AMZ_COPY_SOURCE"); + if (!copy_source) + { + return -1; + } + + ldout(s->cct, 0) << "NOTICE: get_obj_bucket() copy_source = " << copy_source << dendl; + + string prefix_str = copy_source + 1; + int pos = prefix_str.find('/'); + if (pos < 0) + { + return -EINVAL; + } + + src_tenant_name = s->src_tenant_name; + src_bucket_name = prefix_str.substr(0, pos); + src_object.name = prefix_str.substr(pos + 1); + src_object.instance = s->info.args.get("versionId", NULL); + + return 0; +} + +int RGWPutMultipartCopy::get_obj_bucket() +{ + int ret = get_source_bucket_info(src_tenant_name, src_bucket_name, src_object); + if (ret < 0) + { + return ret; + } + + if_mod = s->info.env->get("HTTP_X_AMZ_COPY_IF_MODIFIED_SINCE"); + if_unmod = s->info.env->get("HTTP_X_AMZ_COPY_IF_UNMODIFIED_SINCE"); + if_match = s->info.env->get("HTTP_X_AMZ_COPY_IF_MATCH"); + if_nomatch = s->info.env->get("HTTP_X_AMZ_COPY_IF_NONE_MATCH"); + + if (if_mod) + { + if (parse_time(if_mod, &mod_time) < 0) + { + return -EINVAL; + } + mod_ptr = &mod_time; + } + + if (if_unmod) + { + if (parse_time(if_unmod, &unmod_time) < 0) + { + return -EINVAL; + } + unmod_ptr = &unmod_time; + } + + return 0; +} + +int RGWPutMultipartCopy::get_params() +{ + int op_ret = 0; + op_ret = get_range(); + if (op_ret < 0) + { + ldout(s->cct, 0) << "ERROR: get_range() op_ret = " << op_ret << dendl; + return op_ret; + } + + op_ret = get_obj_bucket(); + if (op_ret < 0) + { + ldout(s->cct, 0) << "ERROR: get_obj_bucket() op_ret = " << op_ret << dendl; + return op_ret; + } + + op_ret = get_data_ready(); + if (op_ret < 0) + { + return op_ret; + } + + copy_permission = true; + ldout(s->cct, 0) << "NOTICE: get_params() copy_permission = " << copy_permission << dendl; + return 0; +} \ No newline at end of file diff --git a/src/rgw/rgw_multi_copy.h b/src/rgw/rgw_multi_copy.h new file mode 100644 index 00000000000000..4fd720dca9c8cf --- /dev/null +++ b/src/rgw/rgw_multi_copy.h @@ -0,0 +1,66 @@ +#ifndef CEPH_RGW_MULTI_COPY_H +#define CEPH_RGW_MULTI_COPY_H + +#include +#include +#include +#include + +#include "common/ceph_time.h" + +#include "rgw_rados.h" +#include "rgw_common.h" + +using namespace std; + +struct req_state; +class RGWPutObj; + +class RGWPutMultipartCopy +{ +private: + off_t begin; + off_t end; + off_t have_read; + off_t total; + + bool copy_permission; + bool copy_all; + + ceph::real_time mod_time; + ceph::real_time unmod_time; + ceph::real_time *mod_ptr; + ceph::real_time *unmod_ptr; + const char *if_mod; + const char *if_unmod; + const char *if_match; + const char *if_nomatch; + + string src_tenant_name; + string src_bucket_name; + rgw_obj_key src_object; + + struct req_state *s; + RGWRados *store; + RGWRados::Object *op_target; + RGWRados::Object::Read *read_op; + +private: + int get_range(); + int get_read_len(off_t ofs); + int get_obj_bucket(); + int get_data_ready(); + +public: + RGWPutMultipartCopy(); + ~RGWPutMultipartCopy(); + + void init(struct req_state *p_req, RGWRados *p_store); + int copy_data(int len, bufferlist &bl); + bool get_permission(); + int get_params(); + int get_data(off_t off, bufferlist &bl); + int get_source_bucket_info(string &src_tenant_name, string &src_bucket_name, + rgw_obj_key &src_object); +}; +#endif /* CEPH_RGW_MULTI_COPY_H */ \ No newline at end of file diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index f4f0760909209f..a8857c7da6f28e 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -216,7 +216,6 @@ static int get_obj_policy_from_attr(CephContext *cct, ret = rgw_get_user_info_by_uid(store, bucket_info.owner, uinfo); if (ret < 0) return ret; - policy->create_default(bucket_info.owner, uinfo.display_name); } return ret; @@ -241,7 +240,6 @@ static int get_policy_from_attr(CephContext *cct, if (obj.bucket.name.empty()) { return 0; } - if (obj.get_object().empty()) { rgw_obj instance_obj; store->get_bucket_instance_obj(bucket_info.bucket, instance_obj); @@ -294,11 +292,11 @@ static int read_policy(RGWRados *store, } if (!object.empty() && !upload_id.empty()) { - /* multipart upload */ + /* multipart upload */ RGWMPObj mp(object.name, upload_id); string oid = mp.get_meta(); obj.init_ns(bucket, oid, mp_ns); - obj.set_in_extra_data(true); + obj.set_in_extra_data(true); } else { obj = rgw_obj(bucket, object); } @@ -323,6 +321,20 @@ static int read_policy(RGWRados *store, } else { ret = -ENOENT; } + rgw_user& owner = bucket_policy.get_owner().get_id(); + + if (!s->system_request && owner.compare(s->user->user_id) != 0 && + !bucket_policy.verify_permission(s->user->user_id, s->perm_mask, + RGW_PERM_READ)) + { + ret = -EACCES; + } + else + { + ret = -ENOENT; + } + +>>>>>>> Uploads a part by copying data from an existing object as data source. You specify the data source by adding the request header x-amz-copy-source in your request and a byte range by adding the request header x-amz-copy-source-range in your request } else if (ret == -ENOENT) { ret = -ERR_NO_SUCH_BUCKET; } @@ -2407,8 +2419,6 @@ void RGWDeleteBucket::execute() if (op_ret < 0) { return; } - - } int RGWPutObj::verify_permission() @@ -2417,6 +2427,140 @@ int RGWPutObj::verify_permission() return -EACCES; } + /*if request is upload part copy, need verify permission like copy object verify_permission*/ + bool multipart = s->info.args.exists("uploadId"); + bool partnumber = s->info.args.exists("partNumber"); + const char *copy_source = NULL; + copy_source = s->info.env->get("HTTP_X_AMZ_COPY_SOURCE"); + /* put object */ + if (multipart && partnumber && !copy_source) + { + return 0; + } + + multipartcp.init(s, store); + + int op_ret = 0; + uint64_t olh_epoch; + string version_id; + RGWAccessControlPolicy src_policy(s->cct); + op_ret = get_system_versioning_params(s, &olh_epoch, &version_id); + if (op_ret < 0) + { + return op_ret; + } + + map src_attrs; + RGWObjectCtx& obj_ctx = *static_cast(s->obj_ctx); + + string src_tenant_name; + string src_bucket_name; + rgw_bucket src_bucket; + rgw_obj_key src_object; + + op_ret = multipartcp.get_source_bucket_info(src_tenant_name, src_bucket_name, src_object); + if (op_ret < 0) + { + return op_ret; + } + + RGWBucketInfo src_bucket_info; + if (s->bucket_instance_id.empty()) + { + op_ret = store->get_bucket_info(obj_ctx, src_tenant_name, src_bucket_name, + src_bucket_info, NULL, &src_attrs); + } + + if (op_ret < 0) + { + if (op_ret == -ENOENT) + { + op_ret = -ERR_NO_SUCH_BUCKET; + } + return op_ret; + } + + src_bucket = src_bucket_info.bucket; + string source_zone = s->info.args.get(RGW_SYS_PARAM_PREFIX "source-zone"); + /* get buckets info (source and dest) */ + if (s->local_source && source_zone.empty()) + { + rgw_obj src_obj(src_bucket, src_object); + store->set_atomic(s->obj_ctx, src_obj); + store->set_prefetch_data(s->obj_ctx, src_obj); + + /* check source object permissions */ + op_ret = read_policy(store, s, src_bucket_info, src_attrs, &src_policy, + src_bucket, src_object); + if (op_ret < 0) + { + return op_ret; + } + + if (!s->system_request && /* system request overrides permission checks */ + !src_policy.verify_permission(s->user->user_id, s->perm_mask, + RGW_PERM_READ)) + { + return -EACCES; + } + } + + RGWAccessControlPolicy dest_bucket_policy(s->cct); + map dest_attrs; + RGWBucketInfo dest_bucket_info; + + string dest_tenant_name; + string dest_bucket_name; + rgw_bucket dest_bucket; + rgw_obj_key dest_object; + + dest_tenant_name = s->bucket.tenant; + dest_bucket_name = s->bucket.name; + dest_object = s->object.name; + + /* will only happen if s->local_source or intra region sync */ + if (src_bucket_name.compare(dest_bucket_name) == 0) + { + dest_bucket_info = src_bucket_info; + dest_attrs = src_attrs; + } + else + { + op_ret = store->get_bucket_info(obj_ctx, dest_tenant_name, dest_bucket_name, + dest_bucket_info, NULL, &dest_attrs); + if (op_ret < 0) + { + if (op_ret == -ENOENT) + { + op_ret = -ERR_NO_SUCH_BUCKET; + } + return op_ret; + } + } + + dest_bucket = dest_bucket_info.bucket; + + rgw_obj dest_obj(dest_bucket, dest_object); + store->set_atomic(s->obj_ctx, dest_obj); + + rgw_obj_key no_obj; + + /* check dest bucket permissions */ + op_ret = read_policy(store, s, dest_bucket_info, dest_attrs, + &dest_bucket_policy, dest_bucket, no_obj); + if (op_ret < 0) + { + return op_ret; + } + + /* system request overrides permission checks */ + if (!s->system_request && + !dest_bucket_policy.verify_permission(s->user->user_id, s->perm_mask, + RGW_PERM_WRITE)) + { + return -EACCES; + } + return 0; } @@ -2603,6 +2747,7 @@ void RGWPutObj::execute() int len; map::iterator iter; bool multipart; + bool upload_part_copy; bool need_calc_md5 = (dlo_manifest == NULL) && (slo_info == NULL); @@ -2682,9 +2827,23 @@ void RGWPutObj::execute() goto done; } + /* get put multipart from source object flag*/ + upload_part_copy = multipartcp.get_permission(); + do { bufferlist data_in; len = get_data(data_in); + + /* put multipart from source object */ + if (upload_part_copy) + { + len = multipartcp.get_data(ofs, data_in); + } + else + { + len = get_data(data_in); + } + if (len < 0) { op_ret = len; goto done; @@ -2738,6 +2897,7 @@ void RGWPutObj::execute() oid_rand.append(buf); op_ret = processor->prepare(store, &oid_rand); + //op_ret = processor->prepare(store, &oid); if (op_ret < 0) { ldout(s->cct, 0) << "ERROR: processor->prepare() returned " << op_ret << dendl; @@ -2755,7 +2915,8 @@ void RGWPutObj::execute() if (!chunked_upload && ofs != s->content_length && - !s->aws4_auth_streaming_mode) { + !s->aws4_auth_streaming_mode && + !upload_part_copy) { op_ret = -ERR_REQUEST_TIMEOUT; goto done; } @@ -3465,7 +3626,6 @@ int RGWCopyObj::verify_permission() map src_attrs; RGWObjectCtx& obj_ctx = *static_cast(s->obj_ctx); - if (s->bucket_instance_id.empty()) { op_ret = store->get_bucket_info(obj_ctx, src_tenant_name, src_bucket_name, src_bucket_info, NULL, &src_attrs); } else { diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h index 8d201a27963e4b..7524a253b24068 100644 --- a/src/rgw/rgw_op.h +++ b/src/rgw/rgw_op.h @@ -35,12 +35,15 @@ #include "rgw_cors.h" #include "rgw_quota.h" #include "rgw_torrent.h" +#include "rgw_multi_copy.h" #include "include/assert.h" using namespace std; using ceph::crypto::SHA1; +#define INFO dout(1) <<__FILE__<<":"<<__func__<<":"<<__LINE__<<":"<< + struct req_state; class RGWHandler; @@ -646,6 +649,7 @@ class RGWPutObj : public RGWOp { protected: seed torrent; + RGWPutMultipartCopy multipartcp; off_t ofs; const char *supplied_md5_b64; const char *supplied_etag; diff --git a/src/rgw/rgw_rest.cc b/src/rgw/rgw_rest.cc index 59c96a77d143ab..265798fc8d39c5 100644 --- a/src/rgw/rgw_rest.cc +++ b/src/rgw/rgw_rest.cc @@ -1022,6 +1022,21 @@ int RGWPutObj_ObjStore::get_params() } torrent.set_info_name((s->object).name); /* end gettorrent */ + + /* put multi part copy */ + bool multipart = s->info.args.exists("uploadId"); + bool partnumber = s->info.args.exists("partNumber"); + const char *copy_source = NULL; + copy_source = s->info.env->get("HTTP_X_AMZ_COPY_SOURCE"); + if (multipart && partnumber && copy_source) + { + ret = multipartcp.get_params(); + if (ret < 0) + { + return ret; + } + } + supplied_md5_b64 = s->info.env->get("HTTP_CONTENT_MD5"); return 0; diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 23267199c78bdf..d6e113babd983b 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -1225,12 +1225,37 @@ static int get_success_retcode(int code) void RGWPutObj_ObjStore_S3::send_response() { + /* put multipart from source object response*/ + if (multipartcp.get_permission()) { + if (op_ret) { + set_req_state_err(s, op_ret); + } else { + if (s->cct->_conf->rgw_s3_success_create_obj_status) { + op_ret = get_success_retcode(s->cct->_conf->rgw_s3_success_create_obj_status); + set_req_state_err(s, op_ret); + } + dump_content_length(s, 0); + dump_errno(s); + end_header(s, this); + } + if (op_ret == 0) { + s->formatter->open_object_section_in_ns("CopyPartResult", XMLNS_AWS_S3); + dump_time(s, "LastModified", &mtime); + if (!etag.empty()) { + s->formatter->dump_string("ETag", etag); + } + s->formatter->close_section(); + rgw_flush_formatter(s, s->formatter); + } + return; + } + if (op_ret) { set_req_state_err(s, op_ret); } else { if (s->cct->_conf->rgw_s3_success_create_obj_status) { op_ret = get_success_retcode( - s->cct->_conf->rgw_s3_success_create_obj_status); + s->cct->_conf->rgw_s3_success_create_obj_status); set_req_state_err(s, op_ret); } dump_etag(s, etag.c_str()); @@ -2913,6 +2938,7 @@ RGWOp *RGWHandler_REST_Obj_S3::op_put() if (is_acl_op()) { return new RGWPutACLs_ObjStore_S3; } + if (s->init_state.src_bucket.empty()) return new RGWPutObj_ObjStore_S3; else @@ -3071,7 +3097,37 @@ int RGWHandler_REST_S3::init(RGWRados *store, struct req_state *s, s->has_acl_header = s->info.env->exists_prefix("HTTP_X_AMZ_GRANT"); - const char *copy_source = s->info.env->get("HTTP_X_AMZ_COPY_SOURCE"); + /* args have HTTP_X_AMZ_COPY_SOURCE, uploadId, partNumber, this is upload part copy*/ + /* put multipart copy */ + bool multipart = s->info.args.exists("uploadId"); + bool partnumber = s->info.args.exists("partNumber"); + const char *source = NULL; + source = s->info.env->get("HTTP_X_AMZ_COPY_SOURCE"); + if (multipart && partnumber && source) + { + ldout(s->cct, 0) << "NOTICE: put multipart copy open "<< dendl; + if (g_conf->rgw_multipart_copy_env) + { + map &val_map = (s->info).args.get_params(); + map::iterator iter_map; + ldout(s->cct, 0) << "NOTICE: print args======= "<< dendl; + for (iter_map = val_map.begin(); iter_map != val_map.end(); ++iter_map) + { + ldout(s->cct, 0) << "NOTICE: [" << iter_map->first << "]"<< " = "<< iter_map->second <cct, 0) << "NOTICE: print env======== " << dendl; + map &env_map = (s->info).env->get_map(); + map::iterator iter_env_map; + for (iter_env_map = env_map.begin(); iter_env_map != env_map.end(); ++iter_env_map) + { + ldout(s->cct, 0) << "NOTICE: [" << iter_env_map->first << "]"<< " = "<< iter_env_map->second <info.env->get("HTTP_X_AMZ_COPY_SOURCE"); if (copy_source) { ret = RGWCopyObj::parse_copy_location(copy_source, s->init_state.src_bucket, @@ -4114,6 +4170,7 @@ RGWHandler_REST* RGWRESTMgr_S3::get_handler(struct req_state *s) if (s->init_state.url_bucket.empty()) { handler = new RGWHandler_REST_Service_S3; } else if (s->object.empty()) { + handler = new RGWHandler_REST_Bucket_S3; } else { handler = new RGWHandler_REST_Obj_S3;