]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
RGW | fix conditional Delete and MultiDelete
authorAli Masarwa <amasarwa@redhat.com>
Mon, 30 Jun 2025 13:07:01 +0000 (16:07 +0300)
committerYuma Ogami <yuma-ogami@cybozu.co.jp>
Thu, 16 Oct 2025 02:50:01 +0000 (02:50 +0000)
size_match supports size 0
checks_preconditions checks for last_modified and size as well
supports versioned object

Signed-off-by: Ali Masarwa <amasarwa@redhat.com>
(cherry picked from commit 55f5b762c67fd7c177835e1a488692f012042d94)

src/rgw/driver/dbstore/common/dbstore.h
src/rgw/driver/rados/rgw_rados.cc
src/rgw/driver/rados/rgw_rados.h
src/rgw/driver/rados/rgw_sal_rados.cc
src/rgw/rgw_multi_del.cc
src/rgw/rgw_multi_del.h
src/rgw/rgw_op.cc
src/rgw/rgw_op.h
src/rgw/rgw_rest_s3.cc
src/rgw/rgw_sal.h
src/rgw/rgw_sal_dbstore.cc

index e488d56ff74fe5bca552d2862fad10ed55a79d5f..e865900088c5eb42be7957e71aa34705d4ffa869 100644 (file)
@@ -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<uint64_t> olh_epoch;
           ceph::real_time delete_at;
           bool canceled;
@@ -1915,7 +1915,11 @@ class DB {
           std::list<rgw_obj_index_key> *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<uint64_t> size_match;
+          const char *if_match{nullptr};
+          const char *if_nomatch{nullptr};
           bool high_precision_time;
           rgw_zone_set *zones_trace;
           bool abortmp;
index ca4f7d9a3641427821cb6aaf6c8105a48c90072e..5f66d523da4d4074e9ee0739e9a77bea61034073 100644 (file)
@@ -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,
+                           &current_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<uint64_t> 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 {
index 221738174fa7d231d6020fbde342500420049e55..ee56530fdcdbf27d286114399b98b080ea7418a8 100644 (file)
@@ -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<uint64_t> 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<uint64_t> olh_epoch;
         ceph::real_time delete_at;
         bool canceled;
@@ -877,7 +881,10 @@ public:
         std::list<rgw_obj_index_key> *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<uint64_t> size_match;
+        const char *if_match{nullptr};
         bool high_precision_time;
         rgw_zone_set *zones_trace;
        bool abortmp;
index 37eebb39c6776dd5971beaf68276e848846001ad..753ea956a4ebfc9d19635b8e8d5ef69ac37e34be 100644 (file)
@@ -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;
index 443ffd60a8a1b2833ea8f56a0de7fb32db92c29f..bc9e064dde4470e7de2f2d9235ec537a8cee6456 100644 (file)
@@ -18,6 +18,9 @@ bool RGWMultiDelObject::xml_end(const char *el)
 {
   RGWMultiDelKey *key_obj = static_cast<RGWMultiDelKey *>(find_first("Key"));
   RGWMultiDelVersionId *vid = static_cast<RGWMultiDelVersionId *>(find_first("VersionId"));
+  XMLObj *etag_match = static_cast<XMLObj *>(find_first("ETag"));
+  XMLObj *last_modified_time = static_cast<XMLObj *>(find_first("LastModifiedTime"));
+  XMLObj *size = static_cast<XMLObj *>(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<RGWMultiDelObject *>(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<RGWMultiDelObject *>(iter.get_next());
   }
   return true;
index b060decf420ad632aed2f9efa6debfd0f630cf0d..a5f87adf886ca5051822c02bfbb08f22ac714233 100644 (file)
@@ -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<uint64_t> 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<uint64_t> 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<rgw_obj_key> objects;
+  std::vector<RGWMultiDelObject> 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:
index eb182271ae6f111d952cf32156af068d70acd6a4..399c0f3ea1ccdddca83800dd1f79f80d7de72302 100644 (file)
@@ -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<rgw_obj_key>& objects,
+void RGWDeleteMultiObj::handle_versioned_objects(const std::vector<RGWMultiDelObject>& objects,
                                                  uint32_t max_aio,
                                                  boost::asio::yield_context yield)
 {
   auto group = ceph::async::spawn_throttle{yield, max_aio};
-  std::map<std::string, std::vector<rgw_obj_key>> grouped_objects;
+  std::map<std::string, std::vector<RGWMultiDelObject>> 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<rgw_obj_key>&
   group.wait();
 }
 
-void RGWDeleteMultiObj::handle_non_versioned_objects(const std::vector<rgw_obj_key>& objects,
+void RGWDeleteMultiObj::handle_non_versioned_objects(const std::vector<RGWMultiDelObject>& objects,
                                                      uint32_t max_aio,
                                                      boost::asio::yield_context yield)
 {
@@ -7681,7 +7690,7 @@ void RGWDeleteMultiObj::handle_non_versioned_objects(const std::vector<rgw_obj_k
   group.wait();
 }
 
-void RGWDeleteMultiObj::handle_objects(const std::vector<rgw_obj_key>& objects,
+void RGWDeleteMultiObj::handle_objects(const std::vector<RGWMultiDelObject>& 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;
       }
index 5bb6f486ca127ba17c36c30de2ccb03d5f2bb2d0..ba2f1a7663f973f22274a2f3d0fe0990687bb229 100644 (file)
@@ -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<uint64_t> size_match; /* if size match */
+  const char *if_match{nullptr}; /* if etag match */
   bool no_precondition_error;
   std::unique_ptr<RGWBulkDelete::Deleter> 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<rgw_obj_key>& objects,
+  void handle_versioned_objects(const std::vector<RGWMultiDelObject>& objects,
                                 uint32_t max_aio, boost::asio::yield_context yield);
-  void handle_non_versioned_objects(const std::vector<rgw_obj_key>& objects,
+  void handle_non_versioned_objects(const std::vector<RGWMultiDelObject>& objects,
                                     uint32_t max_aio, boost::asio::yield_context yield);
-  void handle_objects(const std::vector<rgw_obj_key>& objects,
+  void handle_objects(const std::vector<RGWMultiDelObject>& objects,
                       uint32_t max_aio, boost::asio::yield_context yield);
 
 protected:
index 0ff827f22e62441c4a251e8b5dd3aeb1911d3201..be9919c8812b74ea860018b8896d4daa981638c3 100644 (file)
@@ -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);
index 780aa70d7559b5a4169fb15064e3d3f5e72fb39c..8f6024077a9849963494588e3f60454db1e401ac 100644 (file)
@@ -1161,7 +1161,10 @@ class Object {
         std::list<rgw_obj_index_key>* 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<uint64_t> size_match;
+        const char *if_match{nullptr};
         bool high_precision_time{false};
         rgw_zone_set* zones_trace{nullptr};
        bool abortmp{false};
index e1bbe2cdf891aa1dfe79cc15929a034482ac8f2f..a73766b2dd51c920a15f6d083f5ef2c2a52be2f2 100644 (file)
@@ -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;