Skip to content

Commit 59968a0

Browse files
committed
MB-35079: Allow mix of multiple virtual xattrs
It should be possible to mix the order of the virtual xattrs (and request multiple of them) Change-Id: I2473d8151053c15b6fe1c8f0fef2455657031ca5 Reviewed-on: http://review.couchbase.org/111996 Reviewed-by: Dave Rigby <[email protected]> Tested-by: Trond Norbye <[email protected]>
1 parent c20e84a commit 59968a0

File tree

3 files changed

+120
-13
lines changed

3 files changed

+120
-13
lines changed

daemon/subdocument.cc

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,10 @@ static void create_multi_path_context(SubdocCmdContext& context,
251251
if (xattr) {
252252
size_t xattr_keylen;
253253
is_valid_xattr_key({path.data(), path.size()}, xattr_keylen);
254-
context.set_xattr_key({path.data(), xattr_keylen});
254+
cb::const_char_buffer key{path.data(), xattr_keylen};
255+
if (!cb::xattr::is_vattr(key)) {
256+
context.set_xattr_key(key);
257+
}
255258
}
256259

257260
const SubdocCmdContext::Phase phase = xattr ?
@@ -568,7 +571,7 @@ static cb::mcbp::Status subdoc_operate_one_path(
568571
}
569572

570573
if (context.getCurrentPhase() == SubdocCmdContext::Phase::XATTR &&
571-
spec.path.buf[0] == '$') {
574+
cb::xattr::is_vattr(spec.path)) {
572575
if (spec.path.buf[1] == 'd') {
573576
// This is a call to the "$document" (the validator stopped all of
574577
// the other ones), so replace the document with
@@ -772,11 +775,9 @@ static bool operate_single_doc(SubdocCmdContext& context,
772775
return true;
773776
}
774777

775-
static ENGINE_ERROR_CODE validate_vattr_privilege(SubdocCmdContext& context) {
776-
auto key = context.get_xattr_key();
777-
778+
static ENGINE_ERROR_CODE validate_vattr_privilege(
779+
SubdocCmdContext& context, const cb::const_char_buffer key) {
778780
// The $document vattr doesn't require any xattr permissions.
779-
780781
if (key.buf[1] == 'X') {
781782
// In the xtoc case we want to see which privileges the connection has
782783
// to determine which XATTRs we tell the user about
@@ -823,15 +824,22 @@ static ENGINE_ERROR_CODE validate_vattr_privilege(SubdocCmdContext& context) {
823824
}
824825

825826
static ENGINE_ERROR_CODE validate_xattr_privilege(SubdocCmdContext& context) {
827+
// Look at all of the operations we've got in there:
828+
for (const auto& op :
829+
context.getOperations(SubdocCmdContext::Phase::XATTR)) {
830+
if (cb::xattr::is_vattr(op.path)) {
831+
auto ret = validate_vattr_privilege(context, op.path);
832+
if (ret != ENGINE_SUCCESS) {
833+
return ret;
834+
}
835+
}
836+
}
837+
826838
auto key = context.get_xattr_key();
827839
if (key.empty()) {
828840
return ENGINE_SUCCESS;
829841
}
830842

831-
if (cb::xattr::is_vattr(key)) {
832-
return validate_vattr_privilege(context);
833-
}
834-
835843
cb::rbac::Privilege privilege;
836844
// We've got an XATTR..
837845
if (context.traits.is_mutator) {

include/xattr/utils.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,12 @@ static inline bool is_system_xattr(cb::const_char_buffer& attr) {
7575
/**
7676
* Check if the attribute is a virtual xattr or not
7777
*
78-
* @param attr the attribute to check (CAN'T BE EMPTY!)
78+
* @param attr the attribute to check
7979
*/
80-
static inline bool is_vattr(cb::const_char_buffer& attr) {
81-
return *attr.data() == '$';
80+
static inline bool is_vattr(cb::const_char_buffer attr) {
81+
return !attr.empty() && *attr.data() == '$';
8282
}
83+
8384
namespace macros {
8485
struct macro {
8586
cb::const_char_buffer name;

tests/testapp/testapp_xattr.cc

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,3 +1536,101 @@ TEST_P(XattrTest, MB_28524_TestReplaceWithXattrUncompressed) {
15361536
TEST_P(XattrTest, MB_28524_TestReplaceWithXattrCompressed) {
15371537
doReplaceWithXattrTest(true);
15381538
}
1539+
1540+
/**
1541+
* If the client tries to fetch a mix of multiple virtual xattrs it might not
1542+
* work as expected. Ex:
1543+
*
1544+
* $document
1545+
* tnx
1546+
* will work, but
1547+
*
1548+
* tnx
1549+
* $document
1550+
* will not work. In addition
1551+
*
1552+
* $XTOC
1553+
* tnx
1554+
* returns "null" for the toc
1555+
*/
1556+
TEST_P(XattrTest, MB35079_virtual_xattr_mix) {
1557+
// Store the document we want to operate on
1558+
{
1559+
BinprotSubdocMultiMutationCommand cmd;
1560+
cmd.setKey(name);
1561+
cmd.addMutation(cb::mcbp::ClientOpcode::SubdocDictUpsert,
1562+
SUBDOC_FLAG_XATTR_PATH,
1563+
"tnx",
1564+
"{\"id\":666}");
1565+
cmd.addMutation(cb::mcbp::ClientOpcode::SubdocDictUpsert,
1566+
SUBDOC_FLAG_MKDIR_P,
1567+
"value",
1568+
R"("value")");
1569+
auto& conn = getConnection();
1570+
ASSERT_EQ(cb::mcbp::Status::Success, conn.execute(cmd).getStatus());
1571+
}
1572+
1573+
// Problem 1 : The order of the virtual xattr and key matters:
1574+
{
1575+
BinprotSubdocMultiLookupCommand cmd;
1576+
cmd.setKey(name);
1577+
cmd.addGet("tnx", SUBDOC_FLAG_XATTR_PATH);
1578+
cmd.addGet("$document.CAS", SUBDOC_FLAG_XATTR_PATH);
1579+
cmd.addGet("value", SUBDOC_FLAG_NONE);
1580+
1581+
auto& conn = getConnection();
1582+
BinprotSubdocMultiLookupResponse resp{conn.execute(cmd)};
1583+
ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
1584+
1585+
auto& results = resp.getResults();
1586+
EXPECT_EQ(cb::mcbp::Status::Success, results[0].status);
1587+
EXPECT_EQ(R"({"id":666})", results[0].value);
1588+
EXPECT_EQ(cb::mcbp::Status::Success, results[1].status);
1589+
// We can't really check the CAS, but it should be a hex value
1590+
EXPECT_EQ(0, results[1].value.find("\"0x"));
1591+
EXPECT_EQ(cb::mcbp::Status::Success, results[2].status);
1592+
EXPECT_EQ(R"("value")", results[2].value);
1593+
}
1594+
// Try it with cas first and then the xattr
1595+
{
1596+
BinprotSubdocMultiLookupCommand cmd;
1597+
cmd.setKey(name);
1598+
cmd.addGet("$document.CAS", SUBDOC_FLAG_XATTR_PATH);
1599+
cmd.addGet("tnx", SUBDOC_FLAG_XATTR_PATH);
1600+
cmd.addGet("value", SUBDOC_FLAG_NONE);
1601+
1602+
auto& conn = getConnection();
1603+
BinprotSubdocMultiLookupResponse resp{conn.execute(cmd)};
1604+
ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
1605+
1606+
auto& results = resp.getResults();
1607+
EXPECT_EQ(cb::mcbp::Status::Success, results[0].status);
1608+
// We can't really check the CAS, but it should be a hex value
1609+
EXPECT_EQ(0, results[0].value.find("\"0x"));
1610+
EXPECT_EQ(cb::mcbp::Status::Success, results[1].status);
1611+
EXPECT_EQ(R"({"id":666})", results[1].value);
1612+
EXPECT_EQ(cb::mcbp::Status::Success, results[2].status);
1613+
EXPECT_EQ(R"("value")", results[2].value);
1614+
}
1615+
1616+
// Problem 2 : Requesting XTOC with another xattr fails to populate XTOC
1617+
{
1618+
BinprotSubdocMultiLookupCommand cmd;
1619+
cmd.setKey(name);
1620+
cmd.addGet("$XTOC", SUBDOC_FLAG_XATTR_PATH);
1621+
cmd.addGet("tnx", SUBDOC_FLAG_XATTR_PATH);
1622+
cmd.addGet("value", SUBDOC_FLAG_NONE);
1623+
1624+
auto& conn = getConnection();
1625+
BinprotSubdocMultiLookupResponse resp{conn.execute(cmd)};
1626+
ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
1627+
1628+
auto& results = resp.getResults();
1629+
EXPECT_EQ(cb::mcbp::Status::Success, results[0].status);
1630+
EXPECT_EQ(R"(["tnx"])", results[0].value);
1631+
EXPECT_EQ(cb::mcbp::Status::Success, results[1].status);
1632+
EXPECT_EQ(R"({"id":666})", results[1].value);
1633+
EXPECT_EQ(cb::mcbp::Status::Success, results[2].status);
1634+
EXPECT_EQ(R"("value")", results[2].value);
1635+
}
1636+
}

0 commit comments

Comments
 (0)