static int iterate_user_manifest_parts(CephContext * const cct,
                                       RGWRados * const store,
                                       const off_t ofs,
                                       const off_t end,
                                       RGWBucketInfo *pbucket_info,
                                       const string& obj_prefix,
                                       RGWAccessControlPolicy * const bucket_acl,
                                       const boost::optional<Policy>& bucket_policy,
                                       uint64_t * const ptotal_len,
                                       uint64_t * const pobj_size,
                                       string * const pobj_sum,
                                       int (*cb)(rgw_bucket& bucket,
                                                 const rgw_bucket_dir_entry& ent,
                                                 RGWAccessControlPolicy * const bucket_acl,
                                                 const boost::optional<Policy>& bucket_policy,
                                                 off_t start_ofs,
                                                 off_t end_ofs,
                                                 void *param),
                                       void * const cb_param)
{
  rgw_bucket& bucket = pbucket_info->bucket;
  uint64_t obj_ofs = 0, len_count = 0;
  bool found_start = false, found_end = false, handled_end = false;
  string delim;
  bool is_truncated;
  vector<rgw_bucket_dir_entry> objs;

  utime_t start_time = ceph_clock_now();

  RGWRados::Bucket target(store, *pbucket_info);
  RGWRados::Bucket::List list_op(&target);

  list_op.params.prefix = obj_prefix;
  list_op.params.delim = delim;

  MD5 etag_sum;
  do {
#define MAX_LIST_OBJS 100
    int r = list_op.list_objects(MAX_LIST_OBJS, &objs, NULL, &is_truncated);
    if (r < 0) {
      return r;
    }

    for (rgw_bucket_dir_entry& ent : objs) {
      const uint64_t cur_total_len = obj_ofs;
      const uint64_t obj_size = ent.meta.accounted_size;
      uint64_t start_ofs = 0, end_ofs = obj_size;

      if ((ptotal_len || cb) && !found_start && cur_total_len + obj_size > (uint64_t)ofs) {
	start_ofs = ofs - obj_ofs;
	found_start = true;
      }

      obj_ofs += obj_size;
      if (pobj_sum) {
        etag_sum.Update((const unsigned char *)ent.meta.etag.c_str(),
                        ent.meta.etag.length());
      }

      if ((ptotal_len || cb) && !found_end && obj_ofs > (uint64_t)end) {
	end_ofs = end - cur_total_len + 1;
	found_end = true;
      }

      perfcounter->tinc(l_rgw_get_lat,
			(ceph_clock_now() - start_time));

      if (found_start && !handled_end) {
        len_count += end_ofs - start_ofs;

        if (cb) {
          r = cb(bucket, ent, bucket_acl, bucket_policy, start_ofs, end_ofs, cb_param);
          if (r < 0) {
            return r;
          }
        }
      }

      handled_end = found_end;
      start_time = ceph_clock_now();
    }
  } while (is_truncated);

  if (ptotal_len) {
    *ptotal_len = len_count;
  }
  if (pobj_size) {
    *pobj_size = obj_ofs;
  }
  if (pobj_sum) {
    complete_etag(etag_sum, pobj_sum);
  }

  return 0;
}