]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: implement GetObjectAttributes
authorMatt Benjamin <mbenjamin@redhat.com>
Mon, 8 Jan 2024 02:33:07 +0000 (21:33 -0500)
committerMatt Benjamin <mbenjamin@redhat.com>
Wed, 8 Jan 2025 00:16:35 +0000 (19:16 -0500)
Implements the corresponding S3 operation, and
introduces a new Object::list_parts SAL interface to support it.

Includes Casey Bodley <cbodley@redhat.com>:
  * use uncompressed part size
  * local variable shadowed a member variable and broke handling of
      PartNumberMarker in request and response

Fixes: https://tracker.ceph.com/issues/64109
Signed-off-by: Matt Benjamin <mbenjamin@redhat.com>
18 files changed:
src/rgw/driver/posix/rgw_sal_posix.cc
src/rgw/driver/posix/rgw_sal_posix.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/driver/rados/rgw_sal_rados.h
src/rgw/rgw_iam_policy.h
src/rgw/rgw_op.cc
src/rgw/rgw_op.h
src/rgw/rgw_op_type.h
src/rgw/rgw_rest.h
src/rgw/rgw_rest_s3.cc
src/rgw/rgw_rest_s3.h
src/rgw/rgw_sal.h
src/rgw/rgw_sal_dbstore.cc
src/rgw/rgw_sal_dbstore.h
src/rgw/rgw_sal_filter.cc
src/rgw/rgw_sal_filter.h

index 1345468210f12800f1e12f442ca5201e5ef015f9..9d76462baa093cc9959241a3999bafcba6b77501 100644 (file)
@@ -2893,6 +2893,14 @@ int POSIXObject::copy_object(const ACLOwner& owner,
   return dobj->set_obj_attrs(dpp, &attrs, nullptr, y, rgw::sal::FLAG_LOG_OP);
 }
 
+int POSIXObject::list_parts(const DoutPrefixProvider* dpp, CephContext* cct,
+                           int max_parts, int marker, int* next_marker,
+                           bool* truncated, list_parts_each_t each_func,
+                           optional_yield y)
+{
+  return -EOPNOTSUPP;
+}
+
 int POSIXObject::load_obj_state(const DoutPrefixProvider* dpp, optional_yield y, bool follow_olh)
 {
   int ret = stat(dpp);
index 8ec72bbc1bcb2f1e8e5060fd8e9fb855f7086244..bf3478ad6aba8f1de43ba235f231969e6347bf49 100644 (file)
@@ -653,6 +653,13 @@ public:
                const DoutPrefixProvider* dpp, optional_yield y) override;
   virtual RGWAccessControlPolicy& get_acl(void) override { return acls; }
   virtual int set_acl(const RGWAccessControlPolicy& acl) override { acls = acl; return 0; }
+
+  /** If multipart, enumerate (a range [marker..marker+[min(max_parts, parts_count-1)] of) parts of the object */
+  virtual int list_parts(const DoutPrefixProvider* dpp, CephContext* cct,
+                        int max_parts, int marker, int* next_marker,
+                        bool* truncated, list_parts_each_t each_func,
+                        optional_yield y) override;
+
   virtual int load_obj_state(const DoutPrefixProvider* dpp, optional_yield y, bool follow_olh = true) override;
   virtual int set_obj_attrs(const DoutPrefixProvider* dpp, Attrs* setattrs,
                            Attrs* delattrs, optional_yield y, uint32_t flags) override;
index 0b77bca1da76aec3ef9464aa1f00b2902c038aa1..14882e6d07ccaa3f56e23d824c243c263ac07ed2 100644 (file)
@@ -6962,13 +6962,13 @@ int RGWRados::set_attrs(const DoutPrefixProvider *dpp, RGWObjectCtx* octx, RGWBu
   }
 
   return 0;
-}
+} /* RGWRados::set_attrs() */
 
-static int get_part_obj_state(const DoutPrefixProvider* dpp, optional_yield y,
-                              RGWRados* store, RGWBucketInfo& bucket_info,
-                              RGWObjectCtx* rctx, RGWObjManifest* manifest,
-                              int part_num, int* parts_count, bool prefetch,
-                              RGWObjState** pstate, RGWObjManifest** pmanifest)
+int RGWRados::get_part_obj_state(const DoutPrefixProvider* dpp, optional_yield y,
+                                RGWRados* store, RGWBucketInfo& bucket_info,
+                                RGWObjectCtx* rctx, RGWObjManifest* manifest,
+                                int part_num, int* parts_count, bool prefetch,
+                                RGWObjState** pstate, RGWObjManifest** pmanifest)
 {
   if (!manifest) {
     return -ERR_INVALID_PART;
index b24823b60dcdbfe5a64bf1b553ea4506cc3eea19..fe79916392fcef8403a7ac4fb3b1c2bac52daf54 100644 (file)
@@ -1071,6 +1071,12 @@ public:
     }; // class RGWRados::Bucket::List
   }; // class RGWRados::Bucket
 
+  static int get_part_obj_state(const DoutPrefixProvider* dpp, optional_yield y,
+                      RGWRados* store, RGWBucketInfo& bucket_info,
+                      RGWObjectCtx* rctx, RGWObjManifest* manifest,
+                      int part_num, int* parts_count, bool prefetch,
+                      RGWObjState** pstate, RGWObjManifest** pmanifest);
+
   int on_last_entry_in_listing(const DoutPrefixProvider *dpp,
                                RGWBucketInfo& bucket_info,
                                const std::string& obj_prefix,
index 88da446c3dee940c83e982b75f5e10e8a79b62f6..0c25316567b1883a6f3dfb3aa064f479477215be 100644 (file)
@@ -2471,7 +2471,102 @@ bool RadosObject::is_sync_completed(const DoutPrefixProvider* dpp,
 
   const rgw_bi_log_entry& earliest_marker = entries.front();
   return earliest_marker.timestamp > obj_mtime;
-}
+} /* is_sync_completed */
+
+int RadosObject::list_parts(const DoutPrefixProvider* dpp, CephContext* cct,
+                          int max_parts, int marker, int* next_marker,
+                          bool* truncated, list_parts_each_t each_func,
+                          optional_yield y)
+{
+  int ret{0};
+
+  /* require an object with a manifest, so call to get_obj_state() must precede this */
+  if (! manifest) {
+    return -EINVAL;
+  }
+
+  RGWObjManifest::obj_iterator end = manifest->obj_end(dpp);
+  if (end.get_cur_part_id() == 0) { // not multipart
+    ldpp_dout(dpp, 20) << __func__ << " object does not have a multipart manifest"
+                      << dendl;
+    return 0;
+  }
+
+  auto end_part_id = end.get_cur_part_id();
+  auto parts_count = (end_part_id == 1) ? 1 : end_part_id - 1;
+  if (marker > (parts_count - 1)) {
+    return 0;
+  }
+
+  RGWObjManifest::obj_iterator part_iter = manifest->obj_begin(dpp);
+
+  if (marker != 0) {
+    ldpp_dout_fmt(dpp, 20,
+                 "{} seeking to part #{} in the object manifest",
+                 __func__, marker);
+
+    part_iter  = manifest->obj_find_part(dpp, marker);
+
+    if (part_iter == end) {
+      ldpp_dout_fmt(dpp, 5,
+                   "{} failed to find part #{} in the object manifest",
+                   __func__, marker);
+      return 0;
+    }
+  }
+
+  RGWObjectCtx& obj_ctx = get_ctx();
+  RGWBucketInfo& bucket_info = get_bucket()->get_info();
+
+  Object::Part obj_part{};
+  for (; part_iter != manifest->obj_end(dpp); ++part_iter) {
+
+    /* we're only interested in the first object in each logical part */
+    auto cur_part_id = part_iter.get_cur_part_id();
+    if (cur_part_id == obj_part.part_number) {
+      continue;
+    }
+
+    /* get_part_obj_state alters the passed manifest** to point to a part
+     * manifest, which we don't want to leak out here */
+    RGWObjManifest* obj_m = manifest;
+    RGWObjState* astate;
+    bool part_prefetch = false;
+    ret = RGWRados::get_part_obj_state(dpp, y, store->getRados(), bucket_info, &obj_ctx,
+                                      obj_m, cur_part_id, &parts_count,
+                                      part_prefetch, &astate, &obj_m);
+
+    if (ret < 0) {
+      ldpp_dout_fmt(dpp, 4,
+                   "{} get_part_obj_state() failed ret={}",
+                   __func__, ret);
+      break;
+    }
+
+    obj_part.part_number = part_iter.get_cur_part_id();
+    obj_part.part_size = astate->accounted_size;
+
+    if (auto iter = astate->attrset.find(RGW_ATTR_CKSUM);
+       iter != astate->attrset.end()) {
+          try {
+           rgw::cksum::Cksum part_cksum;
+           auto ck_iter = iter->second.cbegin();
+           part_cksum.decode(ck_iter);
+           obj_part.cksum = std::move(part_cksum);
+         } catch (buffer::error& err) {
+           ldpp_dout_fmt(dpp, 4,
+                         "WARN: {} could not decode stored cksum, "
+                         "caught buffer::error",
+                         __func__);
+         }
+    }
+
+    each_func(obj_part);
+    *next_marker = ++marker;
+  } /* each part */
+  
+  return ret;
+} /* RadosObject::list_parts */
 
 int RadosObject::load_obj_state(const DoutPrefixProvider* dpp, optional_yield y, bool follow_olh)
 {
index 23d81a934b09c940f6e96854722fd26646d748bd..27ec908825ddbdf21cc9ea91dd24159a1e419281 100644 (file)
@@ -593,12 +593,18 @@ class RadosObject : public StoreObject {
       StoreObject::set_compressed();
     }
 
-
     virtual bool is_sync_completed(const DoutPrefixProvider* dpp,
       const ceph::real_time& obj_mtime) override;
     /* For rgw_admin.cc */
     RGWObjState& get_state() { return state; }
     virtual int load_obj_state(const DoutPrefixProvider* dpp, optional_yield y, bool follow_olh = true) override;
+
+    /** If multipart, enumerate (a range [marker..marker+[min(max_parts, parts_count-1)] of) parts of the object */
+    virtual int list_parts(const DoutPrefixProvider* dpp, CephContext* cct,
+                          int max_parts, int marker, int* next_marker,
+                          bool* truncated, list_parts_each_t each_func,
+                          optional_yield y) override;
+
     virtual int set_obj_attrs(const DoutPrefixProvider* dpp, Attrs* setattrs, Attrs* delattrs, optional_yield y, uint32_t flags) override;
     virtual int get_obj_attrs(optional_yield y, const DoutPrefixProvider* dpp, rgw_obj* target_obj = NULL) override;
     virtual int modify_obj_attrs(const char* attr_name, bufferlist& attr_val, optional_yield y, const DoutPrefixProvider* dpp) override;
index 0476926143f16893827fae31e383bde0aabca35b..2fb32a5e25e1483ab47c3748a6a6f1a089a62505 100644 (file)
@@ -115,6 +115,8 @@ enum {
   s3GetBucketEncryption,
   s3PutBucketEncryption,
   s3DescribeJob,
+  s3GetObjectAttributes,
+  s3GetObjectVersionAttributes,
   s3All,
 
   s3objectlambdaGetObject,
index 5ce0033de478bcca758d39409ff475f47160ea57..7082b3ec7511695229977cb58e45ea1058c04f47 100644 (file)
@@ -27,6 +27,7 @@
 #include "common/perf_counters_key.h"
 #include "rgw_cksum_digest.h"
 #include "rgw_common.h"
+#include "common/split.h"
 #include "rgw_tracer.h"
 
 #include "rgw_rados.h"
@@ -2373,7 +2374,8 @@ void RGWGetObj::execute(optional_yield y)
     goto done_err;
 
   /* STAT ops don't need data, and do no i/o */
-  if (get_type() == RGW_OP_STAT_OBJ) {
+  if ((get_type() == RGW_OP_STAT_OBJ) ||
+      (get_type() == RGW_OP_GET_OBJ_ATTRS)) {
     return;
   }
   if (s->info.env->exists("HTTP_X_RGW_AUTH")) {
@@ -5969,8 +5971,6 @@ void RGWGetACLs::execute(optional_yield y)
   acls = ss.str();
 }
 
-
-
 int RGWPutACLs::verify_permission(optional_yield y)
 {
   bool perm;
@@ -5992,6 +5992,74 @@ int RGWPutACLs::verify_permission(optional_yield y)
   return 0;
 }
 
+uint16_t RGWGetObjAttrs::recognize_attrs(const std::string& hdr, uint16_t deflt)
+{
+  auto attrs{deflt};
+  auto sa = ceph::split(hdr, ",");
+  for (auto& k : sa) {
+    if (boost::iequals(k, "etag")) {
+      attrs |= as_flag(ReqAttributes::Etag);
+    }
+    if (boost::iequals(k, "checksum")) {
+      attrs |= as_flag(ReqAttributes::Checksum);
+    }
+    if (boost::iequals(k, "objectparts")) {
+      attrs |= as_flag(ReqAttributes::ObjectParts);
+    }
+    if (boost::iequals(k, "objectsize")) {
+      attrs |= as_flag(ReqAttributes::ObjectSize);
+    }
+    if (boost::iequals(k, "storageclass")) {
+      attrs |= as_flag(ReqAttributes::StorageClass);
+    }
+  }
+  return attrs;
+} /* RGWGetObjAttrs::recognize_attrs */
+
+int RGWGetObjAttrs::verify_permission(optional_yield y)
+{
+  bool perm = false;
+  auto [has_s3_existing_tag, has_s3_resource_tag] =
+    rgw_check_policy_condition(this, s);
+
+  if (! rgw::sal::Object::empty(s->object.get())) {
+
+    auto iam_action1 = s->object->get_instance().empty() ?
+      rgw::IAM::s3GetObject :
+      rgw::IAM::s3GetObjectVersion;
+
+    auto iam_action2 = s->object->get_instance().empty() ?
+      rgw::IAM::s3GetObjectAttributes :
+      rgw::IAM::s3GetObjectVersionAttributes;
+
+    if (has_s3_existing_tag || has_s3_resource_tag) {
+      rgw_iam_add_objtags(this, s, has_s3_existing_tag, has_s3_resource_tag);
+    }
+
+    /* XXXX the following conjunction should be &&--but iam_action2 is currently not
+     * hooked up and always fails (but should succeed if the requestor has READ
+     * acess to the object) */
+    perm = (verify_object_permission(this, s, iam_action1) || /* && */
+           verify_object_permission(this, s, iam_action2));
+  }
+
+  if (! perm) {
+    return -EACCES;
+  }
+
+  return 0;
+}
+
+void RGWGetObjAttrs::pre_exec()
+{
+  rgw_bucket_object_pre_exec(s);
+}
+
+void RGWGetObjAttrs::execute(optional_yield y)
+{
+  RGWGetObj::execute(y);
+} /* RGWGetObjAttrs::execute */
+
 int RGWGetLC::verify_permission(optional_yield y)
 {
   auto [has_s3_existing_tag, has_s3_resource_tag] = rgw_check_policy_condition(this, s, false);
index 9f747501729e0766d087837879f7ff8ef17fb40f..3d525b32c4ece4cb1aa4cd717c4889e082b74e4e 100644 (file)
@@ -12,6 +12,7 @@
 
 #pragma once
 
+#include <cstdint>
 #include <limits.h>
 
 #include <array>
@@ -1644,6 +1645,50 @@ public:
   uint32_t op_mask() override { return RGW_OP_TYPE_WRITE; }
 };
 
+class RGWGetObjAttrs : public RGWGetObj {
+protected:
+  std::string version_id;
+  std::string expected_bucket_owner;
+  int marker;
+  int max_parts;
+  uint16_t requested_attributes;
+#if 0
+  /* used to decrypt attributes for objects stored with SSE-C */
+  x-amz-server-side-encryption-customer-algorithm
+  x-amz-server-side-encryption-customer-key
+  x-amz-server-side-encryption-customer-key-MD5
+#endif
+public:
+
+  enum class ReqAttributes : uint16_t {
+    None = 0,
+    Etag,
+    Checksum,
+    ObjectParts,
+    StorageClass,
+    ObjectSize
+  };
+
+  static uint16_t as_flag(ReqAttributes attr) {
+    return 1 << (uint16_t(attr) ? uint16_t(attr) - 1 : 0);
+  }
+
+  static uint16_t recognize_attrs(const std::string& hdr, uint16_t deflt = 0);
+
+  RGWGetObjAttrs() : RGWGetObj()
+  {
+    RGWGetObj::get_data = false; // it's extra false
+  }
+
+  int verify_permission(optional_yield y) override;
+  void pre_exec() override;
+  void execute(optional_yield y) override;
+  void send_response() override = 0;
+  const char* name() const override { return "get_obj_attrs"; }
+  RGWOpType get_type() override { return RGW_OP_GET_OBJ_ATTRS; }
+  uint32_t op_mask() override { return RGW_OP_TYPE_READ; }
+}; /* RGWGetObjAttrs */
+
 class RGWGetLC : public RGWOp {
 protected:
 
index 49faea6403d48c4ca609e8c4ac8723c92c8ca51d..2c8225d289e0952e09d28ae77b9e18b7ffa39367 100644 (file)
@@ -30,6 +30,7 @@ enum RGWOpType {
   RGW_OP_COPY_OBJ,
   RGW_OP_GET_ACLS,
   RGW_OP_PUT_ACLS,
+  RGW_OP_GET_OBJ_ATTRS,
   RGW_OP_GET_CORS,
   RGW_OP_PUT_CORS,
   RGW_OP_DELETE_CORS,
index aa33080af563c1cb5c2b29221a5a49dbf3758ee7..9111696453e514d804b1cbbd06e30c11398fd3c0 100644 (file)
@@ -403,6 +403,17 @@ public:
   virtual std::string canonical_name() const override { return fmt::format("REST.{}.ACL", s->info.method); }
 };
 
+class RGWGetObjAttrs_ObjStore : public RGWGetObjAttrs {
+public:
+  RGWGetObjAttrs_ObjStore() {}
+  ~RGWGetObjAttrs_ObjStore() override {}
+
+  int get_params(optional_yield y) = 0;
+  /* not actually used */
+  int send_response_data_error(optional_yield y) override { return 0; };
+  int send_response_data(bufferlist& bl, off_t ofs, off_t len) override { return 0; };
+};
+
 class RGWGetLC_ObjStore : public RGWGetLC {
 public:
   RGWGetLC_ObjStore() {}
index 30ebe8e8965c38b82e4dcad4d878e380c8c30a52..20569f7ecc962572a982b6cfc5dd031bfee238cc 100644 (file)
@@ -9,6 +9,7 @@
 #include <string_view>
 
 #include "common/ceph_crypto.h"
+#include "common/dout.h"
 #include "common/split.h"
 #include "common/Formatter.h"
 #include "common/utf8.h"
@@ -807,7 +808,6 @@ void RGWGetObjTags_ObjStore_S3::send_response_data(bufferlist& bl)
   }
 }
 
-
 int RGWPutObjTags_ObjStore_S3::get_params(optional_yield y)
 {
   RGWXMLParser parser;
@@ -3815,6 +3815,168 @@ void RGWPutACLs_ObjStore_S3::send_response()
   dump_start(s);
 }
 
+int RGWGetObjAttrs_ObjStore_S3::get_params(optional_yield y)
+{
+  string err;
+  auto& env = s->info.env;
+  version_id = s->info.args.get("versionId");
+
+  auto hdr = env->get_optional("HTTP_X_AMZ_EXPECTED_BUCKET_OWNER");
+  if (hdr) {
+    expected_bucket_owner = *hdr;
+  }
+
+  hdr = env->get_optional("HTTP_X_AMZ_MAX_PARTS");
+  if (hdr) {
+    max_parts = strict_strtol(hdr->c_str(), 10, &err);
+    if (!err.empty()) {
+      s->err.message = "Invalid value for MaxParts: " + err;
+      ldpp_dout(s, 10) << "Invalid value for MaxParts " << *hdr << ": "
+                      << err << dendl;
+      return -ERR_INVALID_PART;
+    }
+  }
+
+  hdr = env->get_optional("HTTP_X_AMZ_PART_NUMBER_MARKER");
+  if (hdr) {
+    marker = strict_strtol(hdr->c_str(), 10, &err);
+    if (!err.empty()) {
+      s->err.message = "Invalid value for PartNumberMarker: " + err;
+      ldpp_dout(s, 10) << "Invalid value for PartNumberMarker " << *hdr << ": "
+                      << err << dendl;
+      return -ERR_INVALID_PART;
+    }
+  }
+
+  hdr = env->get_optional("HTTP_X_AMZ_OBJECT_ATTRIBUTES");
+  if (hdr) {
+    requested_attributes = recognize_attrs(*hdr);
+  }
+
+  /* XXX skipping SSE-C params for now */
+
+  return 0;
+} /* RGWGetObjAttrs_ObjStore_S3::get_params(...) */
+
+void RGWGetObjAttrs_ObjStore_S3::send_response()
+{
+  if (op_ret)
+    set_req_state_err(s, op_ret);
+  dump_errno(s);
+
+  if (op_ret == 0) {
+    // x-amz-delete-marker: DeleteMarker // not sure we can plausibly do this?
+    dump_last_modified(s, lastmod);
+    dump_header_if_nonempty(s, "x-amz-version-id", version_id);
+    // x-amz-request-charged: RequestCharged
+  }
+
+  end_header(s, this, to_mime_type(s->format));
+  dump_start(s);
+
+  if (op_ret == 0) {
+    s->formatter->open_object_section("GetObjectAttributes");
+    if (requested_attributes & as_flag(ReqAttributes::Etag)) {
+      if (lo_etag.empty()) {
+       auto iter = attrs.find(RGW_ATTR_ETAG);
+       if (iter != attrs.end()) {
+        lo_etag = iter->second.to_str();
+       }
+      }
+      s->formatter->dump_string("ETag", lo_etag);
+    }
+
+    if (requested_attributes & as_flag(ReqAttributes::Checksum)) {
+      s->formatter->open_object_section("Checksum");
+      auto iter = attrs.find(RGW_ATTR_CKSUM);
+      if (iter != attrs.end()) {
+       try {
+         rgw::cksum::Cksum cksum;
+         auto bliter = iter->second.cbegin();
+         cksum.decode(bliter);
+          if (multipart_parts_count && multipart_parts_count > 0) {
+           s->formatter->dump_string(cksum.element_name(),
+               fmt::format("{}-{}", cksum.to_armor(), *multipart_parts_count));
+         } else {
+           s->formatter->dump_string(cksum.element_name(), cksum.to_armor());
+         }
+       } catch (buffer::error& err) {
+         ldpp_dout(this, 0)
+           << "ERROR: could not decode stored cksum, caught buffer::error" << dendl;
+       }
+      }
+      s->formatter->close_section(); /* Checksum */
+    } /* Checksum */
+
+    if (requested_attributes & as_flag(ReqAttributes::ObjectParts)) {
+      if (multipart_parts_count && multipart_parts_count > 0) {
+
+       /* XXX the following was needed to see a manifest at list_parts()! */
+       op_ret = s->object->load_obj_state(s, s->yield);
+       if (op_ret < 0) {
+         ldpp_dout_fmt(this, 0,
+                       "ERROR: {} load_obj_state() failed ret={}", __func__,
+                       op_ret);
+       }
+
+       ldpp_dout_fmt(this, 16,
+                     "{} attr flags={} parts_count={}",
+                     __func__, requested_attributes, *multipart_parts_count);
+
+        s->formatter->open_object_section("ObjectParts");
+
+       bool truncated = false;
+       int next_marker;
+
+       using namespace rgw::sal;
+
+       int ret =
+         s->object->list_parts(
+            this, s->cct, max_parts, marker,
+           &next_marker, &truncated,
+           [&](const Object::Part& part) -> int {
+             s->formatter->open_object_section("Part");
+             s->formatter->dump_int("PartNumber", part.part_number);
+             s->formatter->dump_unsigned("Size", part.part_size);
+             s->formatter->dump_string(part.cksum.element_name(), part.cksum.to_armor());
+             s->formatter->close_section(); /* Part */
+             return 0;
+           }, s->yield);
+
+       if (ret < 0) {
+         ldpp_dout_fmt(this, 0,
+                       "ERROR: {} list-parts failed for {}",
+                       __func__, s->object->get_name());
+       }
+       /* AWS docs disagree on the name of this element */
+       s->formatter->dump_int("PartsCount", *multipart_parts_count);
+       s->formatter->dump_int("TotalPartsCount", *multipart_parts_count);
+       s->formatter->dump_bool("IsTruncated", truncated);
+       s->formatter->dump_int("MaxParts", max_parts);
+       s->formatter->dump_int("NextPartNumberMarker", next_marker);
+       s->formatter->dump_int("PartNumberMarker", marker);
+       s->formatter->close_section();
+      } /* multipart_parts_count positive */
+    } /* ObjectParts */
+
+    if (requested_attributes & as_flag(ReqAttributes::ObjectSize)) {
+      s->formatter->dump_int("ObjectSize", s->obj_size);
+    }
+
+    if (requested_attributes & as_flag(ReqAttributes::StorageClass)) {
+      auto iter = attrs.find(RGW_ATTR_STORAGE_CLASS);
+      if (iter != attrs.end()) {
+       s->formatter->dump_string("StorageClass", iter->second.to_str());
+      } else {
+       s->formatter->dump_string("StorageClass", "STANDARD");
+      }
+    }
+    s->formatter->close_section();
+  } /* op_ret == 0 */
+
+  rgw_flush_formatter_and_reset(s, s->formatter);
+} /* RGWGetObjAttrs_ObjStore_S3::send_response */
+
 void RGWGetLC_ObjStore_S3::execute(optional_yield y)
 {
   config.set_ctx(s->cct);
@@ -4794,6 +4956,7 @@ RGWOp *RGWHandler_REST_Bucket_S3::get_obj_op(bool get_data) const
 
 RGWOp *RGWHandler_REST_Bucket_S3::op_get()
 {
+  /* XXX maybe we could replace this with an indexing operation */
   if (s->info.args.sub_resource_exists("encryption"))
     return nullptr;
 
@@ -4990,6 +5153,8 @@ RGWOp *RGWHandler_REST_Obj_S3::op_get()
     return new RGWGetObjLayout_ObjStore_S3;
   } else if (is_tagging_op()) {
     return new RGWGetObjTags_ObjStore_S3;
+  } else if (is_attributes_op()) {
+    return new RGWGetObjAttrs_ObjStore_S3;
   } else if (is_obj_retention_op()) {
     return new RGWGetObjRetention_ObjStore_S3;
   } else if (is_obj_legal_hold_op()) {
index 50160d79a42396afb8b6aec627114f34b0552bab..37dbf90f203c0ca66f0988a3c179890739c7faa2 100644 (file)
@@ -374,6 +374,15 @@ public:
   int get_params(optional_yield y) override;
 };
 
+class RGWGetObjAttrs_ObjStore_S3 : public RGWGetObjAttrs_ObjStore {
+public:
+  RGWGetObjAttrs_ObjStore_S3() {}
+  ~RGWGetObjAttrs_ObjStore_S3() override {}
+
+  int get_params(optional_yield y) override;
+  void send_response() override;
+};
+
 class RGWGetLC_ObjStore_S3 : public RGWGetLC_ObjStore {
 protected:
   RGWLifecycleConfiguration_S3 config;
@@ -701,6 +710,9 @@ protected:
   bool is_acl_op() const {
     return s->info.args.exists("acl");
   }
+  bool is_attributes_op() const {
+    return s->info.args.exists("attributes");
+  }
   bool is_cors_op() const {
       return s->info.args.exists("cors");
   }
@@ -759,6 +771,9 @@ protected:
   bool is_acl_op() const {
     return s->info.args.exists("acl");
   }
+  bool is_attributes_op() const {
+    return s->info.args.exists("attributes");
+  }
   bool is_tagging_op() const {
     return s->info.args.exists("tagging");
   }
index e098c4decf71a2d4089c9f213a6e44304c711f5d..9ddb692089ebd7b8296b8318c1767e7d44969a24 100644 (file)
@@ -15,6 +15,7 @@
 
 #pragma once
 
+#include <cstdint>
 #include <optional>
 #include <boost/intrusive_ptr.hpp>
 #include <boost/smart_ptr/intrusive_ref_counter.hpp>
@@ -26,6 +27,7 @@
 #include "rgw_notify_event_type.h"
 #include "rgw_req_context.h"
 #include "include/random.h"
+#include "include/function2.hpp"
 
 // FIXME: following subclass dependencies
 #include "driver/rados/rgw_user.h"
@@ -1169,6 +1171,9 @@ class Object {
                std::string* version_id, std::string* tag, std::string* etag,
                void (*progress_cb)(off_t, void *), void* progress_data,
                const DoutPrefixProvider* dpp, optional_yield y) = 0;
+
+    /** return logging subsystem */
+    virtual unsigned get_subsys() { return ceph_subsys_rgw; };
     /** Get the ACL for this object */
     virtual RGWAccessControlPolicy& get_acl(void) = 0;
     /** Set the ACL for this object */
@@ -1249,6 +1254,28 @@ class Object {
     /** Dump driver-specific object layout info in JSON */
     virtual int dump_obj_layout(const DoutPrefixProvider *dpp, optional_yield y, Formatter* f) = 0;
 
+  /* A transfer data type describing metadata specific to one part of a
+   * completed multipart upload object, following the GetObjectAttributes
+   * response syntax for Object::Parts here:
+   * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributes.html */
+    class Part
+    {
+    public:
+      int part_number;
+      uint32_t part_size;
+      rgw::cksum::Cksum cksum;
+    }; /* Part */
+
+    /* callback function/object used by list_parts */
+    using list_parts_each_t =
+      const fu2::unique_function<int(const Part&) const>;
+  
+    /** If multipart, enumerate (a range [marker..marker+[min(max_parts, parts_count-1)] of) parts of the object */
+    virtual int list_parts(const DoutPrefixProvider* dpp, CephContext* cct,
+                          int max_parts, int marker, int* next_marker,
+                          bool* truncated, list_parts_each_t each_func,
+                          optional_yield y) = 0;
+
     /** Get the cached attributes for this object */
     virtual Attrs& get_attrs(void) = 0;
     /** Get the (const) cached attributes for this object */
@@ -1447,7 +1474,7 @@ public:
   virtual int init(const DoutPrefixProvider* dpp, optional_yield y, ACLOwner& owner, rgw_placement_rule& dest_placement, rgw::sal::Attrs& attrs) = 0;
   /** List all the parts of this upload, filling the parts cache */
   virtual int list_parts(const DoutPrefixProvider* dpp, CephContext* cct,
-                        int num_parts, int marker,
+                        int max_parts, int marker,
                         int* next_marker, bool* truncated, optional_yield y,
                         bool assume_unsorted = false) = 0;
   /** Abort this upload */
index 0e4f95846d189475ca2c48ef0a615d6d6ba9bfe7..96c3f001fd10af6f4edb986f476faa52e76c1e4d 100644 (file)
@@ -496,6 +496,14 @@ namespace rgw::sal {
     return std::make_unique<DBLuaManager>(this);
   }
 
+  int DBObject::list_parts(const DoutPrefixProvider* dpp, CephContext* cct,
+                          int max_parts, int marker, int* next_marker,
+                          bool* truncated, list_parts_each_t each_func,
+                          optional_yield y)
+  {
+    return -EOPNOTSUPP;
+  }
+
   int DBObject::load_obj_state(const DoutPrefixProvider* dpp, optional_yield y, bool follow_olh)
   {
     RGWObjState* astate;
index b54249df0319d20b09e01bdda22dd27cfb2aafd1..d6182abeaaaaa551ca20c57a9d66763c48b992b1 100644 (file)
@@ -529,6 +529,7 @@ protected:
 
       DBObject(DBObject& _o) = default;
 
+      virtual unsigned get_subsys() { return ceph_subsys_rgw_dbstore; };
       virtual int delete_object(const DoutPrefixProvider* dpp,
           optional_yield y,
           uint32_t flags,
@@ -554,6 +555,13 @@ protected:
       virtual int set_acl(const RGWAccessControlPolicy& acl) override { acls = acl; return 0; }
 
       virtual int set_obj_attrs(const DoutPrefixProvider* dpp, Attrs* setattrs, Attrs* delattrs, optional_yield y, uint32_t flags) override;
+
+      /** If multipart, enumerate (a range [marker..marker+[min(max_parts, parts_count-1)] of) parts of the object */
+      virtual int list_parts(const DoutPrefixProvider* dpp, CephContext* cct,
+                          int max_parts, int marker, int* next_marker,
+                          bool* truncated, list_parts_each_t each_func,
+                          optional_yield y) override;
+
       virtual int load_obj_state(const DoutPrefixProvider* dpp, optional_yield y, bool follow_olh = true) override;
       virtual int get_obj_attrs(optional_yield y, const DoutPrefixProvider* dpp, rgw_obj* target_obj = NULL) override;
       virtual int modify_obj_attrs(const char* attr_name, bufferlist& attr_val, optional_yield y, const DoutPrefixProvider* dpp) override;
index 733bfa39ee2e947ca31b5253bbbedeb96caeb8de..15da580988eb141621104ad758077e0096cf03ff 100644 (file)
@@ -1046,6 +1046,17 @@ RGWAccessControlPolicy& FilterObject::get_acl()
   return next->get_acl();
 }
 
+int FilterObject::list_parts(const DoutPrefixProvider* dpp, CephContext* cct,
+                            int max_parts, int marker, int* next_marker,
+                            bool* truncated, list_parts_each_t each_func,
+                            optional_yield y)
+{
+  return next->list_parts(dpp, cct, max_parts, marker, next_marker,
+                         truncated,
+                         sal::Object::list_parts_each_t(each_func),
+                         y);
+}
+
 int FilterObject::load_obj_state(const DoutPrefixProvider *dpp,
                                  optional_yield y, bool follow_olh) {
   return next->load_obj_state(dpp, y, follow_olh);
index 43a440e8b100b634af656cfa1c12e6a68b05740b..7eb87d35ebd4a736d76c15b2441151a51f827f27 100644 (file)
@@ -781,6 +781,12 @@ public:
   virtual bool empty() const override { return next->empty(); }
   virtual const std::string &get_name() const override { return next->get_name(); }
 
+  /** If multipart, enumerate (a range [marker..marker+[min(max_parts, parts_count-1)] of) parts of the object */
+  virtual int list_parts(const DoutPrefixProvider* dpp, CephContext* cct,
+                        int max_parts, int marker, int* next_marker,
+                        bool* truncated, list_parts_each_t each_func,
+                        optional_yield y) override;
+
   virtual int load_obj_state(const DoutPrefixProvider *dpp, optional_yield y,
                              bool follow_olh = true) override;
   virtual int set_obj_attrs(const DoutPrefixProvider* dpp, Attrs* setattrs,