From: Ali Masarwa Date: Mon, 30 Jun 2025 13:07:01 +0000 (+0300) Subject: RGW | fix conditional Delete and MultiDelete X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=f28f0d9147d79cf0e08dc0e953760b755cf0ee3f;p=ceph.git RGW | fix conditional Delete and MultiDelete size_match supports size 0 checks_preconditions checks for last_modified and size as well supports versioned object Signed-off-by: Ali Masarwa (cherry picked from commit 55f5b762c67fd7c177835e1a488692f012042d94) --- diff --git a/src/rgw/driver/dbstore/common/dbstore.h b/src/rgw/driver/dbstore/common/dbstore.h index e488d56ff74..e865900088c 100644 --- a/src/rgw/driver/dbstore/common/dbstore.h +++ b/src/rgw/driver/dbstore/common/dbstore.h @@ -1829,8 +1829,8 @@ class DB { bool high_precision_time; uint32_t mod_zone_id; uint64_t mod_pg_ver; - const char *if_match; - const char *if_nomatch; + const char *if_match{nullptr}; + const char *if_nomatch{nullptr}; ConditionParams() : mod_ptr(NULL), unmod_ptr(NULL), high_precision_time(false), mod_zone_id(0), mod_pg_ver(0), @@ -1872,8 +1872,8 @@ class DB { rgw_user owner; RGWObjCategory category; int flags; - const char *if_match; - const char *if_nomatch; + const char *if_match{nullptr}; + const char *if_nomatch{nullptr}; std::optional olh_epoch; ceph::real_time delete_at; bool canceled; @@ -1915,7 +1915,11 @@ class DB { std::list *remove_objs; ceph::real_time expiration_time; ceph::real_time unmod_since; + ceph::real_time last_mod_time_match; ceph::real_time mtime; /* for setting delete marker mtime */ + std::optional size_match; + const char *if_match{nullptr}; + const char *if_nomatch{nullptr}; bool high_precision_time; rgw_zone_set *zones_trace; bool abortmp; diff --git a/src/rgw/driver/rados/rgw_rados.cc b/src/rgw/driver/rados/rgw_rados.cc index ca4f7d9a364..5f66d523da4 100644 --- a/src/rgw/driver/rados/rgw_rados.cc +++ b/src/rgw/driver/rados/rgw_rados.cc @@ -3257,7 +3257,27 @@ int RGWRados::Object::Write::_do_write_meta(uint64_t size, uint64_t accounted_si if (!ptag && !index_op->get_optag()->empty()) { ptag = index_op->get_optag(); } - r = target->prepare_atomic_modification(rctx.dpp, op, reset_obj, ptag, meta.if_match, meta.if_nomatch, false, meta.modify_tail, rctx.y); + + r = target->get_state(rctx.dpp, &target->state, &target->manifest, false, rctx.y); + if (r < 0) + return r; + RGWObjState* current_state = target->state; + if (!target->obj.key.instance.empty()) { + r = target->get_current_version_state(rctx.dpp, current_state, rctx.y); + if (r == -ENOENT) { + current_state = target->state; + } else if (r < 0) { + return r; + } + } + + r = target->check_preconditions(rctx.dpp, std::nullopt, real_clock::zero(), false, meta.if_match, meta.if_nomatch, *current_state, rctx.y); + if (r < 0) { + return r; + } + bool guard = ((target->manifest) || (target->state->obj_tag.length() != 0)) && (!target->state->fake_tag); + bool set_attr_id_tag = guard && target->obj.key.instance.empty() && (meta.if_nomatch == nullptr || meta.if_nomatch != "*"sv); + r = target->prepare_atomic_modification(rctx.dpp, op, reset_obj, ptag, meta.modify_tail, set_attr_id_tag, rctx.y); if (r < 0) return r; @@ -6400,7 +6420,24 @@ int RGWRados::Object::Delete::delete_obj(optional_yield y, meta.mtime = params.mtime; } - int r = store->set_olh(dpp, target->get_ctx(), target->get_bucket_info(), marker, true, + int r = target->get_state(dpp, &target->state, &target->manifest, false, y); + if (r < 0) + return r; + RGWObjState* current_state = target->state; + r = target->get_current_version_state(dpp, current_state, y); + if (r == -ENOENT) { + current_state = target->state; + } else if (r < 0) { + return r; + } + + r = target->check_preconditions(dpp, params.size_match, params.last_mod_time_match, params.high_precision_time, + params.if_match, nullptr, *current_state, y); + if (r < 0) { + return r; + } + + r = store->set_olh(dpp, target->get_ctx(), target->get_bucket_info(), marker, true, &meta, params.olh_epoch, params.unmod_since, params.high_precision_time, y, params.zones_trace, add_log, skip_olh_obj_update); if (r < 0) { @@ -6413,6 +6450,34 @@ int RGWRados::Object::Delete::delete_obj(optional_yield y, if (r < 0) { return r; } + + using namespace std::string_literals; + if (params.if_match && params.if_match != "*"sv) { + if(string if_match = rgw_string_unquote(params.if_match); dirent.meta.etag != if_match) { + return -ERR_PRECONDITION_FAILED; + } + } + + if (params.size_match.has_value()) { + if (params.size_match != dirent.meta.size) { + return -ERR_PRECONDITION_FAILED; + } + } + + if (!real_clock::is_zero(params.last_mod_time_match)) { + struct timespec ctime = ceph::real_clock::to_timespec(dirent.meta.mtime); + struct timespec last_mod_time = ceph::real_clock::to_timespec(params.last_mod_time_match); + if (!params.high_precision_time) { + ctime.tv_nsec = 0; + last_mod_time.tv_nsec = 0; + } + + ldpp_dout(dpp, 10) << "If-Match-Last-Modified-Time: " << params.last_mod_time_match << " Last-Modified: " << ctime << dendl; + if (ctime != last_mod_time) { + return -ERR_PRECONDITION_FAILED; + } + } + result.delete_marker = dirent.is_delete_marker(); r = store->unlink_obj_instance( dpp, target->get_ctx(), target->get_bucket_info(), obj, @@ -6454,8 +6519,18 @@ int RGWRados::Object::Delete::delete_obj(optional_yield y, return r; } - ObjectWriteOperation op; + if (!state->exists) { + if (!force) { + target->invalidate_state(); + return -ENOENT; + } else { + ldpp_dout(dpp, 5) << "WARNING: head for \"" << src_obj << + "\" does not exist; will continue with deleting bucket " + "index entry(ies)" << dendl; + } + } + ObjectWriteOperation op; if (!real_clock::is_zero(params.unmod_since)) { struct timespec ctime = ceph::real_clock::to_timespec(state->mtime); struct timespec unmod = ceph::real_clock::to_timespec(params.unmod_since); @@ -6510,7 +6585,12 @@ int RGWRados::Object::Delete::delete_obj(optional_yield y, } } - r = target->prepare_atomic_modification(dpp, op, false, NULL, NULL, NULL, true, false, y); + r = target->check_preconditions(dpp, params.size_match, params.last_mod_time_match, params.high_precision_time, + params.if_match, nullptr, *state, y); + if (!real_clock::is_zero(params.last_mod_time_match)) { + /* only delete object if mtime is equal to params.last_mod_time_match */ + store->cls_obj_check_mtime(op, params.last_mod_time_match, params.high_precision_time, CLS_RGW_CHECK_TIME_MTIME_EQ); + } if (r < 0) { return r; } @@ -7080,68 +7160,112 @@ void RGWRados::Object::invalidate_state() ctx.invalidate(obj); } -int RGWRados::Object::prepare_atomic_modification(const DoutPrefixProvider *dpp, - ObjectWriteOperation& op, bool reset_obj, const string *ptag, - const char *if_match, const char *if_nomatch, bool removal_op, - bool modify_tail, optional_yield y) +int RGWRados::Object::get_current_version_state(const DoutPrefixProvider *dpp, RGWObjState*& current_state, optional_yield y) { - int r = get_state(dpp, &state, &manifest, false, y); - if (r < 0) + // objects in versioning-enabled buckets don't get overwritten. + // preconditions refer to the current version instead + current_state = state; + rgw_obj current_obj = obj; + current_obj.key.instance.clear(); + constexpr bool follow_olh = true; // look up current version + int r = store->get_obj_state(dpp, &ctx, bucket_info, current_obj, + ¤t_state, nullptr, follow_olh, y); + if (r < 0) { + ldpp_dout(dpp, 4) << "failed to load current version or no current object version for preconditions on " << current_obj << dendl; + current_state = nullptr; return r; + } else { + ldpp_dout(dpp, 4) << "loaded current object " << current_state->obj + << " for preconditions" << dendl; + } - bool need_guard = ((manifest) || (state->obj_tag.length() != 0) || - if_match != NULL || if_nomatch != NULL) && - (!state->fake_tag); + return 0; +} - if (!state->is_atomic) { - ldpp_dout(dpp, 20) << "prepare_atomic_modification: state is not atomic. state=" << (void *)state << dendl; +int RGWRados::Object::check_preconditions(const DoutPrefixProvider *dpp, std::optional size_match, + ceph::real_time last_mod_time_match, bool high_precision_time, + const char *if_match, const char *if_nomatch, RGWObjState& current_state, optional_yield y) +{ + if (size_match.has_value() && current_state.size != size_match) { + return -ERR_PRECONDITION_FAILED; + } - if (reset_obj) { - op.create(false); - store->remove_rgw_head_obj(op); // we're not dropping reference here, actually removing object + if (!real_clock::is_zero(last_mod_time_match)) { + struct timespec ctime = ceph::real_clock::to_timespec(current_state.mtime); + struct timespec last_mod_time = ceph::real_clock::to_timespec(last_mod_time_match); + if (!high_precision_time) { + ctime.tv_nsec = 0; + last_mod_time.tv_nsec = 0; } - return 0; - } - - if (need_guard) { - /* first verify that the object wasn't replaced under */ - if (if_nomatch == NULL || strcmp(if_nomatch, "*") != 0) { - op.cmpxattr(RGW_ATTR_ID_TAG, LIBRADOS_CMPXATTR_OP_EQ, state->obj_tag); - // FIXME: need to add FAIL_NOTEXIST_OK for racing deletion + ldpp_dout(dpp, 10) << "If-Match-Last-Modified-Time: " << last_mod_time_match << " Last-Modified: " << ctime << dendl; + if (ctime != last_mod_time) { + return -ERR_PRECONDITION_FAILED; } + } - if (if_match) { - if (strcmp(if_match, "*") == 0) { - // test the object is existing - if (!state->exists) { + using namespace std::string_literals; + if (if_match) { + if (if_match == "*"sv) { + // test the object is existing + if (!current_state.exists) { + return -ENOENT; + } + } else { + bufferlist bl; + if (current_state.get_attr(RGW_ATTR_ETAG, bl)) { + string if_match_str = rgw_string_unquote(if_match); + string etag = string(bl.c_str(), bl.length()); + if (if_match_str.compare(0, etag.length(), etag.c_str(), etag.length()) != 0) { return -ERR_PRECONDITION_FAILED; } } else { - bufferlist bl; - if (!state->get_attr(RGW_ATTR_ETAG, bl) || - strncmp(if_match, bl.c_str(), bl.length()) != 0) { - return -ERR_PRECONDITION_FAILED; - } + return (!current_state.exists)? -ENOENT: -ERR_PRECONDITION_FAILED; } } + } - if (if_nomatch) { - if (strcmp(if_nomatch, "*") == 0) { - // test the object is NOT existing - if (state->exists) { - return -ERR_PRECONDITION_FAILED; - } - } else { - bufferlist bl; - if (!state->get_attr(RGW_ATTR_ETAG, bl) || - strncmp(if_nomatch, bl.c_str(), bl.length()) == 0) { + if (if_nomatch) { + if (if_nomatch == "*"sv) { + // test the object is NOT existing + if (current_state.exists) { + return -ERR_PRECONDITION_FAILED; + } + } else { + bufferlist bl; + if (current_state.get_attr(RGW_ATTR_ETAG, bl)) { + string if_nomatch_str = rgw_string_unquote(if_nomatch); + string etag = string(bl.c_str(), bl.length()); + if (if_nomatch_str.compare(0, etag.length(), etag.c_str(), etag.length()) == 0) { return -ERR_PRECONDITION_FAILED; } } } } + return 0; +} + +int RGWRados::Object::prepare_atomic_modification(const DoutPrefixProvider *dpp, ObjectWriteOperation& op, bool reset_obj, + const string *ptag, bool modify_tail, bool set_attr_id_tag, optional_yield y) +{ + if (!state->is_atomic) { + ldpp_dout(dpp, 20) << "prepare_atomic_modification: state is not atomic. state=" << (void *) state << dendl; + + if (reset_obj) { + op.create(false); + store->remove_rgw_head_obj(op); // we're not dropping reference here, actually removing object + } + + return 0; + } + + if (set_attr_id_tag) { + /* first verify that the object wasn't replaced under */ + op.cmpxattr(RGW_ATTR_ID_TAG, LIBRADOS_CMPXATTR_OP_EQ, state->obj_tag); + // FIXME: need to add FAIL_NOTEXIST_OK for racing deletion + } + if (reset_obj) { if (state->exists) { op.create(false); @@ -7151,11 +7275,6 @@ int RGWRados::Object::prepare_atomic_modification(const DoutPrefixProvider *dpp, } } - if (removal_op) { - /* the object is being removed, no need to update its tag */ - return 0; - } - if (ptag) { state->write_tag = *ptag; } else { diff --git a/src/rgw/driver/rados/rgw_rados.h b/src/rgw/driver/rados/rgw_rados.h index 221738174fa..ee56530fdcd 100644 --- a/src/rgw/driver/rados/rgw_rados.h +++ b/src/rgw/driver/rados/rgw_rados.h @@ -717,8 +717,12 @@ public: int get_state(const DoutPrefixProvider *dpp, RGWObjState **pstate, RGWObjManifest **pmanifest, bool follow_olh, optional_yield y, bool assume_noent = false); void invalidate_state(); - int prepare_atomic_modification(const DoutPrefixProvider *dpp, librados::ObjectWriteOperation& op, bool reset_obj, const std::string *ptag, - const char *ifmatch, const char *ifnomatch, bool removal_op, bool modify_tail, optional_yield y); + int get_current_version_state(const DoutPrefixProvider *dpp, RGWObjState*& current_state, optional_yield y); + int check_preconditions(const DoutPrefixProvider *dpp, std::optional size_match, + ceph::real_time last_mod_time_match, bool high_precision_time, + const char *if_match, const char *if_nomatch, RGWObjState& current_state, optional_yield y); + int prepare_atomic_modification(const DoutPrefixProvider *dpp, librados::ObjectWriteOperation& op, bool reset_obj, + const std::string *ptag, bool modify_tail, bool set_attr_id_tag, optional_yield y); int complete_atomic_modification(const DoutPrefixProvider *dpp, bool keep_tail, optional_yield y); public: @@ -782,8 +786,8 @@ public: bool high_precision_time; uint32_t mod_zone_id; uint64_t mod_pg_ver; - const char *if_match; - const char *if_nomatch; + const char *if_match{nullptr}; + const char *if_nomatch{nullptr}; ConditionParams() : mod_ptr(NULL), unmod_ptr(NULL), high_precision_time(false), mod_zone_id(0), mod_pg_ver(0), @@ -829,8 +833,8 @@ public: ACLOwner owner; // owner/owner_display_name for bucket index RGWObjCategory category; int flags; - const char *if_match; - const char *if_nomatch; + const char *if_match{nullptr}; + const char *if_nomatch{nullptr}; std::optional olh_epoch; ceph::real_time delete_at; bool canceled; @@ -877,7 +881,10 @@ public: std::list *remove_objs; ceph::real_time expiration_time; ceph::real_time unmod_since; + ceph::real_time last_mod_time_match; ceph::real_time mtime; /* for setting delete marker mtime */ + std::optional size_match; + const char *if_match{nullptr}; bool high_precision_time; rgw_zone_set *zones_trace; bool abortmp; diff --git a/src/rgw/driver/rados/rgw_sal_rados.cc b/src/rgw/driver/rados/rgw_sal_rados.cc index 37eebb39c67..753ea956a4e 100644 --- a/src/rgw/driver/rados/rgw_sal_rados.cc +++ b/src/rgw/driver/rados/rgw_sal_rados.cc @@ -3567,7 +3567,10 @@ int RadosObject::RadosDeleteOp::delete_obj(const DoutPrefixProvider* dpp, option parent_op.params.remove_objs = params.remove_objs; parent_op.params.expiration_time = params.expiration_time; parent_op.params.unmod_since = params.unmod_since; + parent_op.params.last_mod_time_match = params.last_mod_time_match; parent_op.params.mtime = params.mtime; + parent_op.params.size_match = params.size_match; + parent_op.params.if_match = params.if_match; parent_op.params.high_precision_time = params.high_precision_time; parent_op.params.zones_trace = params.zones_trace; parent_op.params.abortmp = params.abortmp; diff --git a/src/rgw/rgw_multi_del.cc b/src/rgw/rgw_multi_del.cc index 443ffd60a8a..bc9e064dde4 100644 --- a/src/rgw/rgw_multi_del.cc +++ b/src/rgw/rgw_multi_del.cc @@ -18,6 +18,9 @@ bool RGWMultiDelObject::xml_end(const char *el) { RGWMultiDelKey *key_obj = static_cast(find_first("Key")); RGWMultiDelVersionId *vid = static_cast(find_first("VersionId")); + XMLObj *etag_match = static_cast(find_first("ETag")); + XMLObj *last_modified_time = static_cast(find_first("LastModifiedTime")); + XMLObj *size = static_cast(find_first("Size")); if (!key_obj) return false; @@ -32,6 +35,29 @@ bool RGWMultiDelObject::xml_end(const char *el) version_id = vid->get_data(); } + if (etag_match) { + if_match = etag_match->get_data().c_str(); + } + + if(last_modified_time) { + string last_modified_time_str = last_modified_time->get_data(); + if (last_modified_time_str.empty()) + return false; + + string last_modified_time_str_decoded = url_decode(last_modified_time_str); + if (parse_time(last_modified_time_str_decoded.c_str(), &last_mod_time) < 0) + return false; + } + + if (size) { + string err; + long long size_tmp = strict_strtoll(size->get_data(), 10, &err); + if (!err.empty()) { + return false; + } + size_match = uint64_t(size_tmp); + } + return true; } @@ -45,10 +71,7 @@ bool RGWMultiDelDelete::xml_end(const char *el) { XMLObjIter iter = find("Object"); RGWMultiDelObject *object = static_cast(iter.get_next()); while (object) { - const string& key = object->get_key(); - const string& instance = object->get_version_id(); - rgw_obj_key k(key, instance); - objects.push_back(k); + objects.push_back(*object); object = static_cast(iter.get_next()); } return true; diff --git a/src/rgw/rgw_multi_del.h b/src/rgw/rgw_multi_del.h index b060decf420..a5f87adf886 100644 --- a/src/rgw/rgw_multi_del.h +++ b/src/rgw/rgw_multi_del.h @@ -7,6 +7,25 @@ #include "rgw_xml.h" #include "rgw_common.h" +class RGWMultiDelObject : public XMLObj +{ + std::string key; + std::string version_id; + const char *if_match{nullptr}; + ceph::real_time last_mod_time; + std::optional size_match; +public: + RGWMultiDelObject() {} + ~RGWMultiDelObject() override {} + bool xml_end(const char *el) override; + + const std::string& get_key() const { return key; } + const std::string& get_version_id() const { return version_id; } + const char* get_if_match() const { return if_match; } + const ceph::real_time& get_last_mod_time() const { return last_mod_time; } + const std::optional get_size_match() const { return size_match; } +}; + class RGWMultiDelDelete : public XMLObj { public: @@ -14,7 +33,7 @@ public: ~RGWMultiDelDelete() override {} bool xml_end(const char *el) override; - std::vector objects; + std::vector objects; bool quiet; bool is_quiet() { return quiet; } }; @@ -26,19 +45,6 @@ public: ~RGWMultiDelQuiet() override {} }; -class RGWMultiDelObject : public XMLObj -{ - std::string key; - std::string version_id; -public: - RGWMultiDelObject() {} - ~RGWMultiDelObject() override {} - bool xml_end(const char *el) override; - - const std::string& get_key() { return key; } - const std::string& get_version_id() { return version_id; } -}; - class RGWMultiDelKey : public XMLObj { public: diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index eb182271ae6..399c0f3ea1c 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -5658,10 +5658,13 @@ void RGWDeleteObj::execute(optional_yield y) del_op->params.bucket_owner = s->bucket_owner.id; del_op->params.versioning_status = s->bucket->get_info().versioning_status(); del_op->params.unmod_since = unmod_since; + del_op->params.last_mod_time_match = last_mod_time_match; del_op->params.high_precision_time = s->system_request; del_op->params.olh_epoch = epoch; del_op->params.marker_version_id = version_id; del_op->params.null_verid = null_verid; + del_op->params.size_match = size_match; + del_op->params.if_match = if_match; op_ret = del_op->delete_obj(this, y, rgw::sal::FLAG_LOG_OP); if (op_ret >= 0) { @@ -7520,8 +7523,11 @@ void RGWDeleteMultiObj::write_ops_log_entry(rgw_log_entry& entry) const { entry.delete_multi_obj_meta.objects = std::move(ops_log_entries); } -void RGWDeleteMultiObj::handle_individual_object(const rgw_obj_key& o, optional_yield y, const bool skip_olh_obj_update) +void RGWDeleteMultiObj::handle_individual_object(const RGWMultiDelObject& object, optional_yield y, const bool skip_olh_obj_update) { + const string& key = object.get_key(); + const string& instance = object.get_version_id(); + rgw_obj_key o(key, instance); // add the object key to the dout prefix so we can trace concurrent calls struct ObjectPrefix : public DoutPrefixPipe { const rgw_obj_key& o; @@ -7604,6 +7610,9 @@ void RGWDeleteMultiObj::handle_individual_object(const rgw_obj_key& o, optional_ del_op->params.obj_owner = s->owner; del_op->params.bucket_owner = s->bucket_owner.id; del_op->params.marker_version_id = version_id; + del_op->params.last_mod_time_match = object.get_last_mod_time(); + del_op->params.if_match = object.get_if_match(); + del_op->params.size_match = object.get_size_match(); op_ret = del_op->delete_obj(dpp, y, rgw::sal::FLAG_LOG_OP | (skip_olh_obj_update ? rgw::sal::FLAG_SKIP_UPDATE_OLH : 0)); @@ -7628,16 +7637,16 @@ void RGWDeleteMultiObj::handle_individual_object(const rgw_obj_key& o, optional_ send_partial_response(o, del_op->result.delete_marker, del_op->result.version_id, op_ret); } -void RGWDeleteMultiObj::handle_versioned_objects(const std::vector& objects, +void RGWDeleteMultiObj::handle_versioned_objects(const std::vector& objects, uint32_t max_aio, boost::asio::yield_context yield) { auto group = ceph::async::spawn_throttle{yield, max_aio}; - std::map> grouped_objects; + std::map> grouped_objects; // group objects by their keys for (const auto& object : objects) { - const std::string& key = object.name; + const std::string& key = object.get_key(); grouped_objects[key].push_back(object); } @@ -7665,7 +7674,7 @@ void RGWDeleteMultiObj::handle_versioned_objects(const std::vector& group.wait(); } -void RGWDeleteMultiObj::handle_non_versioned_objects(const std::vector& objects, +void RGWDeleteMultiObj::handle_non_versioned_objects(const std::vector& objects, uint32_t max_aio, boost::asio::yield_context yield) { @@ -7681,7 +7690,7 @@ void RGWDeleteMultiObj::handle_non_versioned_objects(const std::vector& objects, +void RGWDeleteMultiObj::handle_objects(const std::vector& objects, uint32_t max_aio, boost::asio::yield_context yield) { @@ -7742,8 +7751,9 @@ void RGWDeleteMultiObj::execute(optional_yield y) if (s->bucket->get_info().mfa_enabled()) { bool has_versioned = false; - for (auto i : multi_delete->objects) { - if (!i.instance.empty()) { + for (auto object : multi_delete->objects) { + const string& instance = object.get_version_id(); + if (instance.empty()) { has_versioned = true; break; } diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h index 5bb6f486ca1..ba2f1a7663f 100644 --- a/src/rgw/rgw_op.h +++ b/src/rgw/rgw_op.h @@ -67,6 +67,7 @@ class RGWOp; class RGWRados; class RGWMultiCompleteUpload; class RGWPutObj_Torrent; +class RGWMultiDelObject; namespace rgw::auth::registry { class StrategyRegistry; } @@ -376,8 +377,8 @@ protected: const char *range_str; const char *if_mod; const char *if_unmod; - const char *if_match; - const char *if_nomatch; + const char *if_match{nullptr}; + const char *if_nomatch{nullptr}; uint32_t mod_zone_id; uint64_t mod_pg_ver; off_t ofs; @@ -1219,8 +1220,8 @@ protected: off_t ofs; const char *supplied_md5_b64; const char *supplied_etag; - const char *if_match; - const char *if_nomatch; + const char *if_match{nullptr}; + const char *if_nomatch{nullptr}; std::string copy_source; const char *copy_source_range; RGWBucketInfo copy_source_bucket_info; @@ -1494,6 +1495,9 @@ protected: bool multipart_delete; std::string version_id; ceph::real_time unmod_since; /* if unmodified since */ + ceph::real_time last_mod_time_match; /* if modified time match */ + std::optional size_match; /* if size match */ + const char *if_match{nullptr}; /* if etag match */ bool no_precondition_error; std::unique_ptr deleter; bool bypass_perm; @@ -1529,8 +1533,8 @@ protected: RGWAccessControlPolicy dest_policy; const char *if_mod; const char *if_unmod; - const char *if_match; - const char *if_nomatch; + const char *if_match{nullptr}; + const char *if_nomatch{nullptr}; // Required or it is not a copy operation std::string_view copy_source; // Not actually required @@ -2113,15 +2117,15 @@ class RGWDeleteMultiObj : public RGWOp { * Handles the deletion of an individual object and uses * set_partial_response to record the outcome. */ - void handle_individual_object(const rgw_obj_key& object, + void handle_individual_object(const RGWMultiDelObject& object, optional_yield y, const bool skip_olh_obj_update = false); - void handle_versioned_objects(const std::vector& objects, + void handle_versioned_objects(const std::vector& objects, uint32_t max_aio, boost::asio::yield_context yield); - void handle_non_versioned_objects(const std::vector& objects, + void handle_non_versioned_objects(const std::vector& objects, uint32_t max_aio, boost::asio::yield_context yield); - void handle_objects(const std::vector& objects, + void handle_objects(const std::vector& objects, uint32_t max_aio, boost::asio::yield_context yield); protected: diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 0ff827f22e6..be9919c8812 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -3744,6 +3744,9 @@ void RGWRestoreObj_ObjStore_S3::send_response() int RGWDeleteObj_ObjStore_S3::get_params(optional_yield y) { const char *if_unmod = s->info.env->get("HTTP_X_AMZ_DELETE_IF_UNMODIFIED_SINCE"); + const char *if_last_mod_time_match = s->info.env->get("HTTP_X_AMZ_IF_MATCH_LAST_MODIFIED_TIME"); + const char *if_size_match = s->info.env->get("HTTP_X_AMZ_IF_MATCH_SIZE"); + if_match = s->info.env->get("HTTP_IF_MATCH"); if (s->system_request) { s->info.args.get_bool(RGW_SYS_PARAM_PREFIX "no-precondition-error", &no_precondition_error, false); @@ -3760,6 +3763,25 @@ int RGWDeleteObj_ObjStore_S3::get_params(optional_yield y) unmod_since = utime_t(epoch, nsec).to_real_time(); } + if (if_last_mod_time_match) { + std::string if_last_mod_match_decoded = url_decode(if_last_mod_time_match); + int r = parse_time(if_last_mod_match_decoded.c_str(), &last_mod_time_match); + if (r < 0) { + ldpp_dout(this, 10) << "failed to parse time: " << if_last_mod_match_decoded << dendl; + return r; + } + } + + if(if_size_match) { + string err; + long long size_tmp = strict_strtoll(if_size_match, 10, &err); + if (!err.empty()) { + ldpp_dout(s, 10) << "bad size: " << if_size_match << ": " << err << dendl; + return -EINVAL; + } + size_match = uint64_t(size_tmp); + } + const char *bypass_gov_header = s->info.env->get("HTTP_X_AMZ_BYPASS_GOVERNANCE_RETENTION"); if (bypass_gov_header) { std::string bypass_gov_decoded = url_decode(bypass_gov_header); diff --git a/src/rgw/rgw_sal.h b/src/rgw/rgw_sal.h index 780aa70d755..8f6024077a9 100644 --- a/src/rgw/rgw_sal.h +++ b/src/rgw/rgw_sal.h @@ -1161,7 +1161,10 @@ class Object { std::list* remove_objs{nullptr}; ceph::real_time expiration_time; ceph::real_time unmod_since; + ceph::real_time last_mod_time_match; ceph::real_time mtime; + std::optional size_match; + const char *if_match{nullptr}; bool high_precision_time{false}; rgw_zone_set* zones_trace{nullptr}; bool abortmp{false}; diff --git a/src/rgw/rgw_sal_dbstore.cc b/src/rgw/rgw_sal_dbstore.cc index e1bbe2cdf89..a73766b2dd5 100644 --- a/src/rgw/rgw_sal_dbstore.cc +++ b/src/rgw/rgw_sal_dbstore.cc @@ -709,7 +709,9 @@ namespace rgw::sal { parent_op.params.remove_objs = params.remove_objs; parent_op.params.expiration_time = params.expiration_time; parent_op.params.unmod_since = params.unmod_since; + parent_op.params.last_mod_time_match = params.last_mod_time_match; parent_op.params.mtime = params.mtime; + parent_op.params.size_match = params.size_match; parent_op.params.high_precision_time = params.high_precision_time; parent_op.params.zones_trace = params.zones_trace; parent_op.params.abortmp = params.abortmp;