Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion server/api/src/rsDataObjRename.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ namespace
dataObjInfo_t* dataObjInfoHead{};
irods::at_scope_exit free_dataObjInfo{[&dataObjInfoHead] { freeAllDataObjInfo(dataObjInfoHead); }};

const auto force_flag_provided =
source_cond_input.contains(FORCE_FLAG_KW) || destination_cond_input.contains(FORCE_FLAG_KW);
if ( srcDataObjInp->oprType == RENAME_DATA_OBJ ) {
status = getDataObjInfo( rsComm, srcDataObjInp, &dataObjInfoHead, ACCESS_DELETE_OBJECT, 0 );
if ( status >= 0 || NULL != dataObjInfoHead ) {
Expand All @@ -124,6 +126,30 @@ namespace
"{}: src data {} does not exist, status = {}", __FUNCTION__, srcDataObjInp->objPath, status);
return status;
}

if (const auto ret = ill::try_lock(*dataObjInfoHead, ill::lock_type::write); ret < 0) {
const auto msg = fmt::format("rename not allowed because source data object is locked "
"[error code=[{}], source path=[{}]",
ret,
srcDataObjInp->objPath);
log_api::info("{}: {}", __func__, msg);
return ret;
}

if (isData(rsComm, static_cast<char*>(destDataObjInp->objPath), &destId) >= 0 && force_flag_provided) {
// The force flag ensures that unlink will not send the object to the trash.
destination_cond_input[FORCE_FLAG_KW] = "";

if (const auto unlink_ec = rsDataObjUnlink(rsComm, destDataObjInp); unlink_ec < 0) {
const auto msg = fmt::format("failed to unlink destination data object for overwrite via rename "
"[error code=[{}], source path=[{}], destination path=[{}]]",
unlink_ec,
srcDataObjInp->objPath,
destDataObjInp->objPath);
log_api::info("{}: {}", __func__, msg);
return unlink_ec;
}
}
}
else if ( srcDataObjInp->oprType == RENAME_COLL ) {
status = isColl( rsComm, srcDataObjInp->objPath, &srcId );
Expand Down Expand Up @@ -154,7 +180,6 @@ namespace
return ret;
}

const auto force_flag_provided = source_cond_input.contains(FORCE_FLAG_KW) || destination_cond_input.contains(FORCE_FLAG_KW);
if (isData(rsComm, destDataObjInp->objPath, &destId) >= 0 && force_flag_provided) {
// The force flag ensures that unlink will not send the object to the trash.
destination_cond_input[FORCE_FLAG_KW] = "";
Expand Down
50 changes: 42 additions & 8 deletions unit_tests/src/test_logical_locking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -710,8 +710,9 @@ TEST_CASE("rename", "[write_lock]")
// This is done in an irods::at_scope_exit because catch will return immediately if
// a REQUIRE clause fails. This ensures that the data object is properly closed for
// cleanup purposes.
const auto close_source_object = irods::at_scope_exit{[&]
{
const auto close_source_object = irods::at_scope_exit{[&] {
rename_inp.srcDataObjInp.oprType = 0;

openedDataObjInp_t close_inp{};
close_inp.l1descInx = fd;
REQUIRE(rcDataObjClose(&comm, &close_inp) >= 0);
Expand All @@ -726,6 +727,13 @@ TEST_CASE("rename", "[write_lock]")
REQUIRE(ir::replica_exists(new_comm, target_object.c_str(), opened_replica_resc));
REQUIRE(ir::replica_exists(new_comm, target_object.c_str(), other_replica_resc));
REQUIRE(!fs::client::exists(comm, other_target_object));

// Now do it again with the oprType set. The API takes a different code path in this case, for some reason.
rename_inp.srcDataObjInp.oprType = RENAME_DATA_OBJ;
REQUIRE(LOCKED_DATA_OBJECT_ACCESS == rcDataObjRename(&new_comm, &rename_inp));
REQUIRE(ir::replica_exists(new_comm, target_object.c_str(), opened_replica_resc));
REQUIRE(ir::replica_exists(new_comm, target_object.c_str(), other_replica_resc));
REQUIRE(!fs::client::exists(comm, other_target_object));
}

SECTION("existing data object")
Expand All @@ -743,6 +751,7 @@ TEST_CASE("rename", "[write_lock]")
REQUIRE(GOOD_REPLICA == ir::replica_status(comm, other_target_object, 0));

// source locked, destination unlocked
SECTION("source locked, destination unlocked")
{
// Open source object to lock it
DataObjInp source_open_inp{};
Expand All @@ -760,8 +769,9 @@ TEST_CASE("rename", "[write_lock]")
// This is done in an irods::at_scope_exit because catch will return immediately if
// a REQUIRE clause fails. This ensures that the data object is properly closed for
// cleanup purposes.
const auto close_source_object = irods::at_scope_exit{[&]
{
const auto close_source_object = irods::at_scope_exit{[&] {
rename_inp.srcDataObjInp.oprType = 0;

openedDataObjInp_t close_inp{};
close_inp.l1descInx = fd;
REQUIRE(rcDataObjClose(&comm, &close_inp) >= 0);
Expand All @@ -775,9 +785,17 @@ TEST_CASE("rename", "[write_lock]")
REQUIRE(fs::client::exists(comm, target_object));
REQUIRE(fs::client::exists(comm, other_target_object));
REQUIRE(contents.size() == fs::client::data_object_size(new_comm, other_target_object));

// Now do it again with the oprType set. The API takes a different code path in this case, for some reason.
rename_inp.srcDataObjInp.oprType = RENAME_DATA_OBJ;
REQUIRE(LOCKED_DATA_OBJECT_ACCESS == rcDataObjRename(&new_comm, &rename_inp));
REQUIRE(fs::client::exists(comm, target_object));
REQUIRE(fs::client::exists(comm, other_target_object));
REQUIRE(contents.size() == fs::client::data_object_size(new_comm, other_target_object));
}

// source unlocked, destination locked
SECTION("source unlocked, destination locked")
{
// Open destination object to lock it
DataObjInp dest_open_inp{};
Expand All @@ -793,8 +811,9 @@ TEST_CASE("rename", "[write_lock]")
// This is done in an irods::at_scope_exit because catch will return immediately if
// a REQUIRE clause fails. This ensures that the data object is properly closed for
// cleanup purposes.
const auto close_dest_object = irods::at_scope_exit{[&]
{
const auto close_dest_object = irods::at_scope_exit{[&] {
rename_inp.srcDataObjInp.oprType = 0;

openedDataObjInp_t close_inp{};
close_inp.l1descInx = dest_fd;
REQUIRE(rcDataObjClose(&comm, &close_inp) >= 0);
Expand All @@ -808,9 +827,17 @@ TEST_CASE("rename", "[write_lock]")
REQUIRE(LOCKED_DATA_OBJECT_ACCESS == rcDataObjRename(&new_comm, &rename_inp));
REQUIRE(fs::client::exists(comm, target_object));
REQUIRE(fs::client::exists(comm, other_target_object));

// Now do it again with the oprType set. Locking should still be enforced at the point of unlinking the
// destination object.
rename_inp.srcDataObjInp.oprType = RENAME_DATA_OBJ;
REQUIRE(LOCKED_DATA_OBJECT_ACCESS == rcDataObjRename(&new_comm, &rename_inp));
REQUIRE(fs::client::exists(comm, target_object));
REQUIRE(fs::client::exists(comm, other_target_object));
}

// source locked, destination locked
SECTION("source locked, destination locked")
{
// Open source object to lock it
DataObjInp source_open_inp{};
Expand Down Expand Up @@ -854,8 +881,9 @@ TEST_CASE("rename", "[write_lock]")
// This is done in an irods::at_scope_exit because catch will return immediately if
// a REQUIRE clause fails. This ensures that the data object is properly closed for
// cleanup purposes.
const auto close_dest_object = irods::at_scope_exit{[&]
{
const auto close_dest_object = irods::at_scope_exit{[&] {
rename_inp.srcDataObjInp.oprType = 0;

openedDataObjInp_t close_inp{};
close_inp.l1descInx = dest_fd;
REQUIRE(rcDataObjClose(&comm, &close_inp) >= 0);
Expand All @@ -869,6 +897,12 @@ TEST_CASE("rename", "[write_lock]")
REQUIRE(LOCKED_DATA_OBJECT_ACCESS == rcDataObjRename(&new_comm, &rename_inp));
REQUIRE(fs::client::exists(comm, target_object));
REQUIRE(fs::client::exists(comm, other_target_object));

// Now do it again with the oprType set. The API takes a different code path in this case, for some reason.
rename_inp.srcDataObjInp.oprType = RENAME_DATA_OBJ;
REQUIRE(LOCKED_DATA_OBJECT_ACCESS == rcDataObjRename(&new_comm, &rename_inp));
REQUIRE(fs::client::exists(comm, target_object));
REQUIRE(fs::client::exists(comm, other_target_object));
}
}
}
Expand Down
Loading