Skip to content

Commit d31b2b6

Browse files
committed
XrdS3: support 'virtual' directory objects tagged by content-type from clients
1 parent eeb7eca commit d31b2b6

File tree

4 files changed

+59
-16
lines changed

4 files changed

+59
-16
lines changed

src/XrdS3/XrdS3Api.cc

+17-8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
//------------------------------------------------------------------------------
23
// Copyright (c) 2024 by European Organization for Nuclear Research (CERN)
34
// Author: Mano Segransan / CERN EOS Project <[email protected]>
@@ -444,18 +445,17 @@ int S3Api::ListObjectVersionsHandler(S3::XrdS3Req& req) {
444445
//------------------------------------------------------------------------------
445446
int S3Api::CopyObjectHandler(XrdS3Req& req) {
446447
VALIDATE_REQUEST(Action::CopyObject)
447-
448448
auto source =
449449
req.ctx->utils.UriDecode(req.lowercase_headers["x-amz-copy-source"]);
450-
auto pos = source.find('/');
450+
auto pos = source.find('/',1);
451451
if (pos == std::string::npos) {
452452
return req.S3ErrorResponse(S3Error::InvalidArgument);
453453
}
454454
auto bucket_src = source.substr(0, pos);
455455
auto object_src = source.substr(pos + 1);
456-
457456
auto [error, source_bucket] =
458457
auth.ValidateRequest(req, Action::GetObject, bucket_src, object_src);
458+
459459
if (error != S3Error::None) {
460460
return req.S3ErrorResponse(error);
461461
}
@@ -467,9 +467,10 @@ int S3Api::CopyObjectHandler(XrdS3Req& req) {
467467
S3ObjectStore::Object obj;
468468
RET_ON_ERROR(objectStore.GetObject(source_bucket, object_src, obj))
469469

470-
if (!S3Utils::MapHasKey(obj.GetAttributes(), "etag")) {
471-
return req.S3ErrorResponse(S3Error::InternalError);
472-
}
470+
// TODO: fix me, we disable this for the time being
471+
// if (!S3Utils::MapHasKey(obj.GetAttributes(), "etag")) {
472+
// return req.S3ErrorResponse(S3Error::InternalError);
473+
// }
473474

474475
// std::string etag = headers["etag"];
475476
// time_t last_modified = std::stoul(headers["last-modified"]);
@@ -486,6 +487,7 @@ int S3Api::CopyObjectHandler(XrdS3Req& req) {
486487
Headers hd = {{"Content-Type", "application/xml"}};
487488
req.StartChunkedResp(200, hd);
488489

490+
std::cerr << "copyObject" << std::endl;
489491
std::map<std::string, std::string> headers;
490492
err = objectStore.CopyObject(bucket, req.object, obj, req.lowercase_headers,
491493
headers);
@@ -546,7 +548,8 @@ int S3Api::PutObjectHandler(XrdS3Req& req) {
546548
}
547549

548550
std::map<std::string, std::string> headers;
549-
RET_ON_ERROR(objectStore.PutObject(req, bucket, length, chunked, headers))
551+
552+
RET_ON_ERROR(objectStore.PutObject(req, bucket, length, chunked, req.lowercase_headers))
550553

551554
return req.S3Response(200, headers, "");
552555
}
@@ -577,7 +580,13 @@ int S3Api::HeadObjectHandler(XrdS3Req& req) {
577580

578581
// content length is added in S3Response
579582
headers["Last-Modified"] = S3Utils::timestampToRFC7231(last_modified);
580-
headers["Accept-Ranges"] = "\"bytes\"";
583+
headers["Accept-Ranges"] = "bytes";
584+
585+
// add directory flag , if the object has a / in the end
586+
if (obj.Directory()) {
587+
headers["Content-Type"] = "application/x-directory";
588+
}
589+
581590
return req.S3Response(200, headers, nullptr, content_length);
582591
}
583592

src/XrdS3/XrdS3Auth.cc

-1
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,6 @@ std::pair<S3Error, S3Auth::Bucket> S3Auth::ValidateRequest(
310310
if (err != S3Error::None) {
311311
return {err, {}};
312312
}
313-
314313
return AuthorizeRequest(req, action, bucket, object);
315314
}
316315

src/XrdS3/XrdS3ObjectStore.cc

+40-7
Original file line numberDiff line numberDiff line change
@@ -355,13 +355,15 @@ S3Error S3ObjectStore::Object::Init(const std::filesystem::path &p, uid_t uid,
355355

356356
// Do the backend operations with the users filesystem id
357357
ScopedFsId scope(uid, gid, id);
358-
if (XrdPosix_Stat(p.c_str(), &buf) || S_ISDIR(buf.st_mode)) {
358+
if (XrdPosix_Stat(p.c_str(), &buf)) {
359359
S3::S3Handler::Logger()->Log(S3::ERROR, "ObjectStore::Object::Init",
360360
"no such object - object-path=%s owner(%u:%u)",
361361
p.c_str(), uid, gid);
362362
return S3Error::NoSuchKey;
363363
}
364364

365+
// Flag directories
366+
this->directory = S_ISDIR(buf.st_mode);
365367
std::vector<std::string> attrnames;
366368
std::vector<char> attrlist;
367369
auto attrlen = XrdPosix_Listxattr(p.c_str(), nullptr, 0);
@@ -464,16 +466,21 @@ S3Error S3ObjectStore::GetObject(const S3Auth::Bucket &bucket,
464466
S3Error S3ObjectStore::DeleteObject(const S3Auth::Bucket &bucket,
465467
const std::string &key) {
466468
std::string base, obj;
467-
469+
ScopedFsId scope(bucket.owner.uid, bucket.owner.gid, bucket.owner.id);
468470
auto full_path = bucket.path / key;
469471
S3::S3Handler::Logger()->Log(S3::DEBUG, "ObjectStore::DeleteObject",
470472
"object-path=%s", full_path.c_str());
471473

472474
if (XrdPosix_Unlink(full_path.c_str())) {
473-
S3::S3Handler::Logger()->Log(S3::ERROR, "ObjectStore::DeleteObject",
474-
"failed to delete object-path=%s",
475-
full_path.c_str());
476-
return S3Error::NoSuchKey;
475+
if (errno == EISDIR) {
476+
if (XrdPosix_Rmdir(full_path.c_str())) {
477+
// TODO: error handling
478+
S3::S3Handler::Logger()->Log(S3::ERROR, "ObjectStore::DeleteObject",
479+
"failed to delete object-path=%s",
480+
full_path.c_str());
481+
return S3Error::NoSuchKey;
482+
}
483+
}
477484
}
478485

479486
do {
@@ -564,7 +571,7 @@ S3Error S3ObjectStore::CopyObject(const S3Auth::Bucket &bucket,
564571
auto final_path = bucket.path / key;
565572

566573
struct stat buf;
567-
if (!XrdPosix_Stat(final_path.c_str(), &buf) && S_ISDIR(buf.st_mode)) {
574+
if (!XrdPosix_Stat(final_path.c_str(), &buf)) {
568575
S3::S3Handler::Logger()->Log(
569576
S3::ERROR, "ObjectStore::CopyObject",
570577
"target:%s is directory => bucket:%s key:%s src=:%s",
@@ -576,6 +583,11 @@ S3Error S3ObjectStore::CopyObject(const S3Auth::Bucket &bucket,
576583
bucket.path / ("." + key + "." + std::to_string(std::time(nullptr)) +
577584
std::to_string(std::rand()));
578585

586+
if (reqheaders.count("x-amz-metadata-directive") && reqheaders.at("x-amz-metadata-directive") == "REPLACE") {
587+
// do the meta-data replacement
588+
return S3Error::None;
589+
}
590+
579591
auto err = S3Utils::makePath((char *)final_path.parent_path().c_str(),
580592
S_IRWXU | S_IRWXG);
581593

@@ -1149,6 +1161,26 @@ S3Error S3ObjectStore::PutObject(XrdS3Req &req, const S3Auth::Bucket &bucket,
11491161
return S3Error::ObjectExistAsDir;
11501162
}
11511163

1164+
// special treatment for 'non-standard' directory creation
1165+
auto it = headers.find("content-type");
1166+
if (it != headers.end() && it->second == "application/x-directory") {
1167+
// this is a 'virtual 'directory creation request
1168+
auto err = S3Utils::makePath((char *)final_path.c_str(),
1169+
S_IRWXU | S_IRGRP);
1170+
if (err == ENOTDIR) {
1171+
S3::S3Handler::Logger()->Log(S3::ERROR, "ObjectStore::PutObject",
1172+
"object exists in object path - path:%s",
1173+
final_path.c_str());
1174+
return S3Error::ObjectExistInObjectPath;
1175+
} else if (err != 0) {
1176+
S3::S3Handler::Logger()->Log(S3::ERROR, "ObjectStore::PutObject",
1177+
"internal error makeing parent : path:%s",
1178+
final_path.c_str());
1179+
return S3Error::InternalError;
1180+
}
1181+
return S3Error::None;
1182+
}
1183+
11521184
auto tmp_path =
11531185
final_path.parent_path() /
11541186
("." + final_path.filename().string() + "." +
@@ -1375,6 +1407,7 @@ ListObjectsInfo S3ObjectStore::ListObjectsCommon(
13751407
std::vector<S3Utils::BasicPath> ent;
13761408
std::deque<S3Utils::BasicPath> entries;
13771409

1410+
ScopedFsId scope(bucket.owner.uid, bucket.owner.gid, bucket.owner.id);
13781411
int n;
13791412
// get full listing
13801413
if ((n = S3Utils::ScanDir((fullpath/basedir), basedir, ent)) <= 0) {

src/XrdS3/XrdS3ObjectStore.hh

+2
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ class S3ObjectStore {
106106
ssize_t Read(size_t length, char **data);
107107
off_t Lseek(off_t offset, int whence);
108108
std::string Name() const { return name; }
109+
bool Directory() const { return directory; }
109110

110111
const std::map<std::string, std::string> &GetAttributes() const {
111112
return attributes;
@@ -123,6 +124,7 @@ class S3ObjectStore {
123124
uid_t uid;
124125
gid_t gid;
125126
std::string id;
127+
bool directory;
126128
std::map<std::string, std::string> attributes{};
127129
};
128130

0 commit comments

Comments
 (0)