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=b66fb1dfe683d066fe72b74fea8e2562d6366e33;p=ceph-ci.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 Resolves: rhbz#2375000 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 8ab13b3124f..92b3f9388d8 100644 --- a/src/rgw/driver/rados/rgw_rados.cc +++ b/src/rgw/driver/rados/rgw_rados.cc @@ -3254,7 +3254,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; @@ -6396,7 +6416,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); if (r < 0) { @@ -6409,6 +6446,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, @@ -6450,8 +6515,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); @@ -6506,7 +6581,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; } @@ -7075,68 +7155,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); @@ -7146,11 +7270,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 01a8b255395..13acd2cae84 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 9563d452914..77d0ccb2d47 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 74aff105174..3c723761796 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -5659,10 +5659,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) { @@ -7521,8 +7524,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) +void RGWDeleteMultiObj::handle_individual_object(const RGWMultiDelObject& object, optional_yield y) { + 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; @@ -7605,6 +7611,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); if (op_ret == -ENOENT) { @@ -7678,8 +7687,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; } @@ -7697,9 +7707,9 @@ void RGWDeleteMultiObj::execute(optional_yield y) const uint32_t max_aio = std::max(1, s->cct->_conf->rgw_multi_obj_del_max_aio); auto group = ceph::async::spawn_throttle{y, max_aio}; - for (const auto& key : multi_delete->objects) { - group.spawn([this, &key] (boost::asio::yield_context yield) { - handle_individual_object(key, yield); + for (const auto& object : multi_delete->objects) { + group.spawn([this, &object] (boost::asio::yield_context yield) { + handle_individual_object(object, yield); }); rgw_flush_formatter(s, s->formatter); diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h index d7f206cea8a..a6ef42089e2 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,7 +2117,7 @@ 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& o, optional_yield y); + void handle_individual_object(const RGWMultiDelObject& object, optional_yield y); protected: std::vector ops_log_entries; diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index a6d4bba93b0..4b277f15ce8 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -3719,6 +3719,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); @@ -3735,6 +3738,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 0f2571b0fac..b68129509fb 100644 --- a/src/rgw/rgw_sal.h +++ b/src/rgw/rgw_sal.h @@ -1160,7 +1160,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;