@@ -730,5 +730,6 @@ attr_info = { FATTR4_SUPPORTED_ATTRS : A("r", "fs"),
FATTR4_RETENTION_HOLD : A("rw", "obj"),
FATTR4_MODE_SET_MASKED : A("w", "obj"),
FATTR4_FS_CHARSET_CAP : A("r", "fs"),
+ FATTR4_XATTR_SUPPORT : A("r", "obj"),
}
del A
@@ -24,4 +24,5 @@ __all__ = ["st_exchange_id.py", # draft 21
"st_current_stateid.py",
"st_sparse.py",
"st_flex.py",
+ "st_xattr.py",
]
@@ -107,6 +107,7 @@ class Environment(testmod.Environment):
AttrInfo('time_modify', 'r', nfstime4(0, 0)),
AttrInfo('time_modify_set', 'w', settime4(0)),
AttrInfo('mounted_on_fileid', 'r', 0),
+ AttrInfo('xattr_support', 'r', False),
]
home = property(lambda s: use_obj(s.opts.home))
new file mode 100644
@@ -0,0 +1,246 @@
+from .st_create_session import create_session
+from xdrdef.nfs4_const import *
+
+from .environment import check, fail, create_file, open_file, close_file
+from .environment import open_create_file_op, do_getattrdict
+from xdrdef.nfs4_type import open_owner4, openflag4, createhow4, open_claim4
+from xdrdef.nfs4_type import creatverfattr, fattr4, stateid4, locker4, lock_owner4
+from xdrdef.nfs4_type import open_to_lock_owner4
+import nfs_ops
+op = nfs_ops.NFS4ops()
+import threading
+
+current_stateid = stateid4(1, b'\0' * 12)
+
+def testGetXattrAttribute(t, env):
+ """Server with xattr support MUST support.
+
+ FLAGS: xattr
+ CODE: XATT1
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ res = sess.compound([op.putrootfh(), op.getattr(1 << FATTR4_SUPPORTED_ATTRS|1 <<FATTR4_XATTR_SUPPORT)])
+ check(res)
+
+ if FATTR4_SUPPORTED_ATTRS not in res.resarray[-1].obj_attributes:
+ fail("Requested bitmap of supported attributes not provided")
+
+ bitmask = res.resarray[-1].obj_attributes[FATTR4_SUPPORTED_ATTRS]
+ if bitmask & (1 << FATTR4_XATTR_SUPPORT) == 0:
+ fail("xattr_support is not included in the set of supported attributes")
+
+ if FATTR4_XATTR_SUPPORT not in res.resarray[-1].obj_attributes:
+ fail("Server doesn't support extended attributes")
+
+
+def testGetMissingAttr(t, env):
+ """Server MUST return NFS4ERR_NOXATTR if value is missing.
+
+ FLAGS: xattr
+ CODE: XATT2
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ open_op = open_create_file_op(sess, env.testname(t), open_create=OPEN4_CREATE)
+ res = sess.compound(open_op + [op.close(0, current_stateid)])
+ check(res, NFS4_OK)
+
+ fh = res.resarray[-2].object
+ res = sess.compound([op.putfh(fh), op.getxattr("user.attr1".encode("UTF-8"))])
+ check(res, NFS4ERR_NOXATTR)
+
+def testCreateNewAttr(t, env):
+ """Server MUST return NFS4_ON on create.
+
+ FLAGS: xattr
+ CODE: XATT3
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ open_op = open_create_file_op(sess, env.testname(t), open_create=OPEN4_CREATE)
+ res = sess.compound(open_op + [op.close(0, current_stateid)])
+ check(res, NFS4_OK)
+
+ fh = res.resarray[-2].object
+ key = "user.attr1".encode("UTF-8")
+ value = "value1".encode("UTF-8")
+ res = sess.compound([op.putfh(fh), op.setxattr(SETXATTR4_CREATE, key, value)])
+ check(res)
+
+ res = sess.compound([op.putfh(fh), op.getxattr(key)])
+ check(res)
+ if value != res.resarray[-1].gxr_value:
+ fail("Returned value doesn't")
+
+def testCreateNewIfMissingAttr(t, env):
+ """Server MUST update existing attribute with SETXATTR4_EITHER.
+
+ FLAGS: xattr
+ CODE: XATT4
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ open_op = open_create_file_op(sess, env.testname(t), open_create=OPEN4_CREATE)
+ res = sess.compound(open_op + [op.close(0, current_stateid)])
+ check(res, NFS4_OK)
+
+ fh = res.resarray[-2].object
+ key = "user.attr1".encode("UTF-8")
+ value = "value1".encode("UTF-8")
+ res = sess.compound([op.putfh(fh), op.setxattr(SETXATTR4_EITHER, key, value)])
+ check(res)
+
+ res = sess.compound([op.putfh(fh), op.getxattr(key)])
+ check(res)
+ if value != res.resarray[-1].gxr_value:
+ fail("Returned value doesn't match with expected one.")
+
+def testUpdateOfMissingAttr(t, env):
+ """Server MUST return NFS4ERR_NOXATTR on update of missing attribute.
+
+ FLAGS: xattr
+ CODE: XATT5
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ open_op = open_create_file_op(sess, env.testname(t), open_create=OPEN4_CREATE)
+ res = sess.compound(open_op + [op.close(0, current_stateid)])
+ check(res, NFS4_OK)
+
+ fh = res.resarray[-2].object
+ key = "user.attr1".encode("UTF-8")
+ value = "value1".encode("UTF-8")
+ res = sess.compound([op.putfh(fh), op.setxattr(SETXATTR4_REPLACE, key, value)])
+ check(res, NFS4ERR_NOXATTR)
+
+def testExclusiveCreateAttr(t, env):
+ """Server MUST return NFS4ERR_EXIST on create of existing attribute.
+
+ FLAGS: xattr
+ CODE: XATT6
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ open_op = open_create_file_op(sess, env.testname(t), open_create=OPEN4_CREATE)
+ res = sess.compound(open_op + [op.close(0, current_stateid)])
+ check(res, NFS4_OK)
+
+ fh = res.resarray[-2].object
+ key = "user.attr1".encode("UTF-8")
+ value = "value1".encode("UTF-8")
+ res = sess.compound([op.putfh(fh), op.setxattr(SETXATTR4_CREATE, key, value)])
+ check(res)
+
+ res = sess.compound([op.putfh(fh), op.setxattr(SETXATTR4_CREATE, key, value)])
+ check(res, NFS4ERR_EXIST)
+
+def testUpdateExistingAttr(t, env):
+ """Server MUST return NFS4_ON on update of existing attribute.
+
+ FLAGS: xattr
+ CODE: XATT7
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ open_op = open_create_file_op(sess, env.testname(t), open_create=OPEN4_CREATE)
+ res = sess.compound(open_op + [op.close(0, current_stateid)])
+ check(res, NFS4_OK)
+
+ fh = res.resarray[-2].object
+ key = "user.attr1".encode("UTF-8")
+ value1 = "value1".encode("UTF-8")
+ value2 = "value2".encode("UTF-8")
+ res = sess.compound([op.putfh(fh), op.setxattr(SETXATTR4_CREATE, key, value1)])
+ check(res)
+
+ res = sess.compound([op.putfh(fh), op.setxattr(SETXATTR4_REPLACE, key, value2)])
+ check(res)
+
+ res = sess.compound([op.putfh(fh), op.getxattr(key)])
+ check(res)
+ if value2 != res.resarray[-1].gxr_value:
+ fail("Returned value doesn't match with expected one.")
+
+def testRemoveNonExistingAttr(t, env):
+ """Server MUST return NFS4ERR_NOXATTR on remove of non existing attribute.
+
+ FLAGS: xattr
+ CODE: XATT8
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ open_op = open_create_file_op(sess, env.testname(t), open_create=OPEN4_CREATE)
+ res = sess.compound(open_op + [op.close(0, current_stateid)])
+ check(res, NFS4_OK)
+
+ fh = res.resarray[-2].object
+ key = "user.attr1".encode("UTF-8")
+
+ res = sess.compound([op.putfh(fh), op.removexattr(key)])
+ check(res, NFS4ERR_NOXATTR)
+
+def testRemoveExistingAttr(t, env):
+ """Server MUST return NFS4_ON on remove of existing attribute.
+
+ FLAGS: xattr
+ CODE: XATT9
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ open_op = open_create_file_op(sess, env.testname(t), open_create=OPEN4_CREATE)
+ res = sess.compound(open_op + [op.close(0, current_stateid)])
+ check(res, NFS4_OK)
+
+ fh = res.resarray[-2].object
+ key = "user.attr1".encode("UTF-8")
+ value = "value1".encode("UTF-8")
+ res = sess.compound([op.putfh(fh), op.setxattr(SETXATTR4_CREATE, key, value)])
+ check(res)
+
+ res = sess.compound([op.putfh(fh), op.removexattr(key)])
+ check(res)
+
+def testListNoAttrs(t, env):
+ """Server MUST return NFS4_ON an empty list if no attributes defined.
+
+ FLAGS: xattr
+ CODE: XATT10
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ open_op = open_create_file_op(sess, env.testname(t), open_create=OPEN4_CREATE)
+ res = sess.compound(open_op + [op.close(0, current_stateid)])
+ check(res, NFS4_OK)
+
+ fh = res.resarray[-2].object
+
+ res = sess.compound([op.putfh(fh), op.listxattrs(0, 8192)])
+ check(res)
+
+ if not res.resarray[-1].lxr_eof:
+ fail("EOF flag is not set")
+
+ if len(res.resarray[-1].lxr_names) > 0:
+ fail("Unexpected attributes returned")
+
+def testListAttrs(t, env):
+ """Server MUST return NFS4_ON and list of defined attributes.
+
+ FLAGS: xattr
+ CODE: XATT11
+ """
+ sess = env.c1.new_client_session(env.testname(t))
+ open_op = open_create_file_op(sess, env.testname(t), open_create=OPEN4_CREATE)
+ res = sess.compound(open_op + [op.close(0, current_stateid)])
+ check(res, NFS4_OK)
+
+ fh = res.resarray[-2].object
+
+ keys = ["user.attr1", "user.attr2", "user.attr3", "user.attr4", "user.attr5", "user.attr6"]
+
+ for key in keys:
+ value = "value".encode("UTF-8")
+ res = sess.compound([op.putfh(fh), op.setxattr(SETXATTR4_CREATE, key.encode("UTF-8"), value)])
+ check(res)
+
+ res = sess.compound([op.putfh(fh), op.listxattrs(0, 8192)])
+ check(res)
+
+ xattrs = [key.decode("UTF-8") for key in res.resarray[-1].lxr_names]
+ if len(xattrs) != len(keys):
+ fail("Invalid number of entries returuned <expected> %d, <actual> %d" % (len(keys), len(xattrs)))
+
+ for key in keys:
+ if key not in xattrs:
+ fail("Unexpected attribute received %s" % key)
@@ -259,7 +259,12 @@ enum nfsstat4 {
NFS4ERR_OFFLOAD_DENIED = 10091,/* dest not allowing copy */
NFS4ERR_WRONG_LFS = 10092,/* LFS not supported */
NFS4ERR_BADLABEL = 10093,/* incorrect label */
- NFS4ERR_OFFLOAD_NO_REQS= 10094 /* dest not meeting reqs */
+ NFS4ERR_OFFLOAD_NO_REQS= 10094, /* dest not meeting reqs */
+
+ /* rfc8276 (xattr) */
+
+NFS4ERR_NOXATTR = 10095, /* xattr does not exist */
+NFS4ERR_XATTR2BIG = 10096 /* xattr value is too big */
};
/*
@@ -809,6 +814,11 @@ struct write_response4 {
verifier4 wr_writeverf;
};
+/*
+ * rfc8276 (xattr)
+ */
+ typedef component4 xattrkey4;
+ typedef opaque xattrvalue4<>;
/*
* NFSv4.1 attributes
@@ -900,6 +910,10 @@ typedef change_attr_type4
fattr4_change_attr_type;
typedef sec_label4 fattr4_sec_label;
typedef uint32_t fattr4_clone_blksize;
+/*
+ * rfc8276 (xattr)
+ */
+typedef bool fattr4_xattr_support;
%/*
% * REQUIRED Attributes
@@ -1002,6 +1016,11 @@ const FATTR4_SPACE_FREED = 78;
const FATTR4_CHANGE_ATTR_TYPE = 79;
const FATTR4_SEC_LABEL = 80;
+%/*
+% * new in rfc 8276 (xattr)
+% */
+const FATTR4_XATTR_SUPPORT = 82;
+
/*
* File attribute container
*/
@@ -1335,6 +1354,16 @@ enum nfs_opnum4 {
OP_SEEK = 69,
OP_WRITE_SAME = 70,
OP_CLONE = 71,
+
+ %
+ % /* new in rfc8276 (xattr) */
+ %
+ OP_GETXATTR = 72,
+ OP_SETXATTR = 73,
+ OP_LISTXATTRS = 74,
+ OP_REMOVEXATTR = 75,
+
+
OP_ILLEGAL = 10044
};
@@ -3136,6 +3165,74 @@ enum ff_cb_recall_any_mask {
FF_RCA4_TYPE_MASK_RW = -1
};
+
+/*
+ * rfc8276(xattr)
+ */
+
+struct GETXATTR4args {
+ /* CURRENT_FH: file */
+ xattrkey4 gxa_name;
+};
+
+union GETXATTR4res switch (nfsstat4 gxr_status) {
+case NFS4_OK:
+ xattrvalue4 gxr_value;
+default:
+ void;
+};
+
+enum setxattr_option4 {
+ SETXATTR4_EITHER = 0,
+ SETXATTR4_CREATE = 1,
+ SETXATTR4_REPLACE = 2
+};
+
+struct SETXATTR4args {
+ /* CURRENT_FH: file */
+ setxattr_option4 sxa_option;
+ xattrkey4 sxa_key;
+ xattrvalue4 sxa_value;
+};
+
+union SETXATTR4res switch (nfsstat4 sxr_status) {
+ case NFS4_OK:
+ change_info4 sxr_info;
+ default:
+ void;
+};
+
+struct LISTXATTRS4args {
+ /* CURRENT_FH: file */
+ nfs_cookie4 lxa_cookie;
+ count4 lxa_maxcount;
+};
+
+struct LISTXATTRS4resok {
+ nfs_cookie4 lxr_cookie;
+ xattrkey4 lxr_names<>;
+ bool lxr_eof;
+};
+
+union LISTXATTRS4res switch (nfsstat4 lxr_status) {
+ case NFS4_OK:
+ LISTXATTRS4resok lxr_value;
+ default:
+ void;
+};
+
+struct REMOVEXATTR4args {
+ /* CURRENT_FH: file */
+ xattrkey4 rxa_name;
+};
+
+union REMOVEXATTR4res switch (nfsstat4 rxr_status) {
+ case NFS4_OK:
+ change_info4 rxr_info;
+ default:
+ void;
+};
+
/*
* Operation arrays (the rest)
*/
@@ -3257,6 +3354,12 @@ union nfs_argop4 switch (nfs_opnum4 argop) {
case OP_WRITE_SAME: WRITE_SAME4args opwrite_same;
case OP_CLONE: CLONE4args opclone;
+/* Operations new in rfc8276 (xattr) */
+case OP_GETXATTR: GETXATTR4args opgetxattr;
+case OP_SETXATTR: SETXATTR4args opsetxattr;
+case OP_LISTXATTRS: LISTXATTRS4args oplistxattrs;
+case OP_REMOVEXATTR: REMOVEXATTR4args opremovexattr;
+
/* Operations not new to NFSv4.1 */
case OP_ILLEGAL: void;
};
@@ -3386,6 +3489,12 @@ union nfs_resop4 switch (nfs_opnum4 resop) {
case OP_WRITE_SAME: WRITE_SAME4res opwrite_same;
case OP_CLONE: CLONE4res opclone;
+/* Operations new in rfc8276 (xattr) */
+case OP_GETXATTR: GETXATTR4res opgetxattr;
+case OP_SETXATTR: SETXATTR4res opsetxattr;
+case OP_LISTXATTRS: LISTXATTRS4res oplistxattrs;
+case OP_REMOVEXATTR: REMOVEXATTR4res opremovexattr;
+
/* Operations not new to NFSv4.1 */
case OP_ILLEGAL: ILLEGAL4res opillegal;
};
Signed-off-by: Tigran Mkrtchyan <tigran.mkrtchyan@desy.de> --- nfs4.1/nfs4lib.py | 1 + nfs4.1/server41tests/__init__.py | 1 + nfs4.1/server41tests/environment.py | 1 + nfs4.1/server41tests/st_xattr.py | 246 ++++++++++++++++++++++++++++ nfs4.1/xdrdef/nfs4.x | 111 ++++++++++++- 5 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 nfs4.1/server41tests/st_xattr.py