}
 
 
+constexpr std::initializer_list<int> RGWBulkUploadOp::terminal_errors;
+
 int RGWBulkUploadOp::verify_permission()
 {
   if (s->auth.identity->is_anonymous()) {
           op_ret = handle_file(header->get_filename(),
                                header->get_filesize(),
                                body);
+          if (! op_ret) {
+            /* Only regular files counts. */
+            num_created++;
+          } else {
+            failures.emplace_back(op_ret, header->get_filename().to_string());
+          }
           break;
         }
         case rgw::tar::FileType::DIRECTORY: {
           ldout(s->cct, 2) << "bulk upload: handling regular directory" << dendl;
 
           op_ret = handle_dir(header->get_filename());
+          if (op_ret < 0 && op_ret != -ERR_BUCKET_EXISTS) {
+            failures.emplace_back(op_ret, header->get_filename().to_string());
+          }
           break;
         }
         default: {
           break;
         }
       }
+
+      /* In case of any problems with sub-request authorization Swift simply
+       * terminates whole upload immediately. */
+      if (boost::algorithm::contains(std::initializer_list<int>{ op_ret },
+                                     terminal_errors)) {
+        ldout(s->cct, 2) << "bulk upload: terminating due to ret=" << op_ret
+                         << dendl;
+        break;
+      }
     } else {
       ldout(s->cct, 2) << "bulk upload: an empty block" << dendl;
       op_ret = 0;
 
 #include <string>
 #include <set>
 #include <map>
+#include <vector>
 
 #include <boost/optional.hpp>
 #include <boost/utility/in_place_factory.hpp>
   boost::optional<RGWObjectCtx> dir_ctx;
 
 protected:
+  class fail_desc_t {
+  public:
+    fail_desc_t(const int err, std::string path)
+      : err(err),
+        path(std::move(path)) {
+    }
+
+    const int err;
+    const std::string path;
+  };
+
+  static constexpr std::initializer_list<int> terminal_errors = {
+    -EACCES, -EPERM
+  };
+
+  /* FIXME:  boost::container::small_vector<fail_desc_t, 4> failures; */
+  std::vector<fail_desc_t> failures;
+  size_t num_created;
+
   class StreamGetter;
   class DecoratedStreamGetter;
   class AlignedStreamGetter;
   int handle_dir(boost::string_ref path);
 
 public:
+  RGWBulkUploadOp()
+    : num_created(0) {
+  }
+
   void init(RGWRados* const store,
             struct req_state* const s,
             RGWHandler* const h) override {
 
 
   const string& mm = s->info.args.get("multipart-manifest");
   const bool multipart_delete = (mm.compare("delete") == 0);
-
+  const bool swift_bulkupload = s->prot_flags & RGW_REST_SWIFT &&
+                                s->info.args.exists("extract-archive");
   switch (s->format) {
     case RGW_FORMAT_PLAIN:
       {
-        const bool use_kv_syntax = s->info.args.exists("bulk-delete") || multipart_delete;
+        const bool use_kv_syntax = s->info.args.exists("bulk-delete") ||
+                                   multipart_delete || swift_bulkupload;
         s->formatter = new RGWFormatter_Plain(use_kv_syntax);
         break;
       }
     case RGW_FORMAT_XML:
       {
-        const bool lowercase_underscore = s->info.args.exists("bulk-delete") || multipart_delete;
+        const bool lowercase_underscore = s->info.args.exists("bulk-delete") ||
+                                          multipart_delete || swift_bulkupload;
+
         s->formatter = new XMLFormatter(false, lowercase_underscore);
         break;
       }
 
   end_header(s, this /* RGWOp */, nullptr /* contype */,
              CHUNKED_TRANSFER_ENCODING);
   rgw_flush_formatter_and_reset(s, s->formatter);
+
+  s->formatter->open_object_section("delete");
+
+  std::string resp_status;
+  std::string resp_body;
+
+  if (! failures.empty()) {
+    rgw_err err;
+
+    const auto last_err = { failures.back().err };
+    if (boost::algorithm::contains(last_err, terminal_errors)) {
+      /* The terminal errors are affecting the status of the whole upload. */
+      set_req_state_err(err, failures.back().err, s->prot_flags);
+    } else {
+      set_req_state_err(err, ERR_INVALID_REQUEST, s->prot_flags);
+    }
+
+    dump_errno(err, resp_status);
+  } else if (0 == num_created && failures.empty()) {
+    /* Nothing created, nothing failed. This means the archive contained no
+     * entity we could understand (regular file or directory). We need to
+     * send 400 Bad Request to an HTTP client in the internal status field. */
+    dump_errno(400, resp_status);
+    resp_body = "Invalid Tar File: No Valid Files";
+  } else {
+    /* 200 OK */
+    dump_errno(201, resp_status);
+  }
+
+  encode_json("Number Files Created", num_created, s->formatter);
+  encode_json("Response Body", resp_body, s->formatter);
+  encode_json("Response Status", resp_status, s->formatter);
+
+  s->formatter->open_array_section("Errors");
+  for (const auto& fail_desc : failures) {
+    s->formatter->open_array_section("object");
+
+    encode_json("Name", fail_desc.path, s->formatter);
+
+    rgw_err err;
+    set_req_state_err(err, fail_desc.err, s->prot_flags);
+    std::string status;
+    dump_errno(err, status);
+    encode_json("Status", status, s->formatter);
+
+    s->formatter->close_section();
+  }
+  s->formatter->close_section();
+
+  s->formatter->close_section();
+  rgw_flush_formatter_and_reset(s, s->formatter);
 }