@@ -425,6 +425,20 @@ xfs_attr_complete_op(
return XFS_DAS_DONE;
args->attr_filter &= ~XFS_ATTR_INCOMPLETE;
+ if (xfs_attr_intent_op(attr) != XFS_ATTRI_OP_FLAGS_NVREPLACE)
+ return replace_state;
+
+ /*
+ * NVREPLACE operations require the caller to set the old and new names
+ * and values explicitly.
+ */
+ ASSERT(args->new_namelen > 0);
+
+ args->name = args->new_name;
+ args->namelen = args->new_namelen;
+ args->hashval = xfs_da_hashname(args->name, args->namelen);
+ args->value = args->new_value;
+ args->valuelen = args->new_valuelen;
return replace_state;
}
@@ -926,9 +940,13 @@ xfs_attr_defer_replace(
struct xfs_da_args *args)
{
struct xfs_attr_intent *new;
+ int op_flag = XFS_ATTRI_OP_FLAGS_REPLACE;
int error = 0;
- error = xfs_attr_intent_init(args, XFS_ATTRI_OP_FLAGS_REPLACE, &new);
+ if (args->op_flags & XFS_DA_OP_NVLOOKUP)
+ op_flag = XFS_ATTRI_OP_FLAGS_NVREPLACE;
+
+ error = xfs_attr_intent_init(args, op_flag, &new);
if (error)
return error;
@@ -510,8 +510,8 @@ struct xfs_attr_intent {
struct xfs_da_args *xattri_da_args;
/*
- * Shared buffer containing the attr name and value so that the logging
- * code can share large memory buffers between log items.
+ * Shared buffer containing the attr name, new name, and value so that
+ * the logging code can share large memory buffers between log items.
*/
struct xfs_attri_log_nameval *xattri_nameval;
@@ -54,11 +54,15 @@ enum xfs_dacmp {
*/
typedef struct xfs_da_args {
struct xfs_da_geometry *geo; /* da block geometry */
- const uint8_t *name; /* string (maybe not NULL terminated) */
+ const uint8_t *name; /* string (maybe not NULL terminated) */
+ const uint8_t *new_name; /* new attr name */
int namelen; /* length of string (maybe no NULL) */
+ int new_namelen; /* new attr name len */
uint8_t filetype; /* filetype of inode for directories */
void *value; /* set of bytes (maybe contain NULLs) */
+ void *new_value; /* new xattr value (may contain NULLs) */
int valuelen; /* length of value */
+ int new_valuelen; /* length of new attr value */
unsigned int attr_filter; /* XFS_ATTR_{ROOT,SECURE,INCOMPLETE} */
unsigned int attr_flags; /* XATTR_{CREATE,REPLACE} */
xfs_dahash_t hashval; /* hash value of name */
@@ -115,10 +115,11 @@ struct xfs_unmount_log_format {
#define XLOG_REG_TYPE_BUD_FORMAT 26
#define XLOG_REG_TYPE_ATTRI_FORMAT 27
#define XLOG_REG_TYPE_ATTRD_FORMAT 28
-#define XLOG_REG_TYPE_ATTR_NAME 29
+#define XLOG_REG_TYPE_ATTR_NAME 29
#define XLOG_REG_TYPE_ATTR_VALUE 30
-#define XLOG_REG_TYPE_MAX 30
-
+#define XLOG_REG_TYPE_ATTR_NEWNAME 31
+#define XLOG_REG_TYPE_ATTR_NEWVALUE 32
+#define XLOG_REG_TYPE_MAX 32
/*
* Flags to log operation header
@@ -959,6 +960,7 @@ struct xfs_icreate_log {
#define XFS_ATTRI_OP_FLAGS_REPLACE 3 /* Replace the attribute */
#define XFS_ATTRI_OP_FLAGS_NVREMOVE 4 /* Remove attr w/ vlookup */
#define XFS_ATTRI_OP_FLAGS_NVSET 5 /* Set attr with w/ vlookup */
+#define XFS_ATTRI_OP_FLAGS_NVREPLACE 6 /* Replace attr name and val */
#define XFS_ATTRI_OP_FLAGS_TYPE_MASK 0xFF /* Flags type mask */
/*
@@ -976,11 +978,27 @@ struct xfs_icreate_log {
struct xfs_attri_log_format {
uint16_t alfi_type; /* attri log item type */
uint16_t alfi_size; /* size of this item */
- uint32_t __pad; /* pad to 64 bit aligned */
+
+ /*
+ * For NVREPLACE, this is the length of the new xattr value.
+ * alfi_value_len contains the length of the old xattr value.
+ */
+ uint32_t alfi_new_value_len;
+
uint64_t alfi_id; /* attri identifier */
uint64_t alfi_ino; /* the inode for this attr operation */
uint32_t alfi_op_flags; /* marks the op as a set or remove */
- uint32_t alfi_name_len; /* attr name length */
+ union {
+ uint32_t alfi_name_len; /* attr name length */
+ struct {
+ /*
+ * For NVREPLACE, these are the lengths of the old and
+ * new attr name.
+ */
+ uint16_t alfi_old_name_len;
+ uint16_t alfi_new_name_len;
+ };
+ };
uint32_t alfi_value_len; /* attr value length */
uint32_t alfi_attr_filter;/* attr filter flags */
};
@@ -75,8 +75,12 @@ static inline struct xfs_attri_log_nameval *
xfs_attri_log_nameval_alloc(
const void *name,
unsigned int name_len,
+ const void *new_name,
+ unsigned int new_name_len,
const void *value,
- unsigned int value_len)
+ unsigned int value_len,
+ const void *new_value,
+ unsigned int new_value_len)
{
struct xfs_attri_log_nameval *nv;
@@ -85,15 +89,26 @@ xfs_attri_log_nameval_alloc(
* this. But kvmalloc() utterly sucks, so we use our own version.
*/
nv = xlog_kvmalloc(sizeof(struct xfs_attri_log_nameval) +
- name_len + value_len);
+ name_len + new_name_len + value_len +
+ new_value_len);
nv->name.i_addr = nv + 1;
nv->name.i_len = name_len;
nv->name.i_type = XLOG_REG_TYPE_ATTR_NAME;
memcpy(nv->name.i_addr, name, name_len);
+ if (new_name_len) {
+ nv->new_name.i_addr = nv->name.i_addr + name_len;
+ nv->new_name.i_len = new_name_len;
+ memcpy(nv->new_name.i_addr, new_name, new_name_len);
+ } else {
+ nv->new_name.i_addr = NULL;
+ nv->new_name.i_len = 0;
+ }
+ nv->new_name.i_type = XLOG_REG_TYPE_ATTR_NEWNAME;
+
if (value_len) {
- nv->value.i_addr = nv->name.i_addr + name_len;
+ nv->value.i_addr = nv->name.i_addr + name_len + new_name_len;
nv->value.i_len = value_len;
memcpy(nv->value.i_addr, value, value_len);
} else {
@@ -102,6 +117,17 @@ xfs_attri_log_nameval_alloc(
}
nv->value.i_type = XLOG_REG_TYPE_ATTR_VALUE;
+ if (new_value_len) {
+ nv->new_value.i_addr = nv->name.i_addr + name_len +
+ new_name_len + value_len;
+ nv->new_value.i_len = new_value_len;
+ memcpy(nv->new_value.i_addr, new_value, new_value_len);
+ } else {
+ nv->new_value.i_addr = NULL;
+ nv->new_value.i_len = 0;
+ }
+ nv->new_value.i_type = XLOG_REG_TYPE_ATTR_NEWVALUE;
+
refcount_set(&nv->refcount, 1);
return nv;
}
@@ -147,11 +173,20 @@ xfs_attri_item_size(
*nbytes += sizeof(struct xfs_attri_log_format) +
xlog_calc_iovec_len(nv->name.i_len);
- if (!nv->value.i_len)
- return;
+ if (nv->new_name.i_len) {
+ *nvecs += 1;
+ *nbytes += xlog_calc_iovec_len(nv->new_name.i_len);
+ }
- *nvecs += 1;
- *nbytes += xlog_calc_iovec_len(nv->value.i_len);
+ if (nv->value.i_len) {
+ *nvecs += 1;
+ *nbytes += xlog_calc_iovec_len(nv->value.i_len);
+ }
+
+ if (nv->new_value.i_len) {
+ *nvecs += 1;
+ *nbytes += xlog_calc_iovec_len(nv->new_value.i_len);
+ }
}
/*
@@ -181,15 +216,28 @@ xfs_attri_item_format(
ASSERT(nv->name.i_len > 0);
attrip->attri_format.alfi_size++;
+ if (nv->new_name.i_len > 0)
+ attrip->attri_format.alfi_size++;
+
if (nv->value.i_len > 0)
attrip->attri_format.alfi_size++;
+ if (nv->new_value.i_len > 0)
+ attrip->attri_format.alfi_size++;
+
xlog_copy_iovec(lv, &vecp, XLOG_REG_TYPE_ATTRI_FORMAT,
&attrip->attri_format,
sizeof(struct xfs_attri_log_format));
xlog_copy_from_iovec(lv, &vecp, &nv->name);
+
+ if (nv->new_name.i_len > 0)
+ xlog_copy_from_iovec(lv, &vecp, &nv->new_name);
+
if (nv->value.i_len > 0)
xlog_copy_from_iovec(lv, &vecp, &nv->value);
+
+ if (nv->new_value.i_len > 0)
+ xlog_copy_from_iovec(lv, &vecp, &nv->new_value);
}
/*
@@ -379,7 +427,15 @@ xfs_attr_log_item(
ASSERT(!(attr->xattri_op_flags & ~XFS_ATTRI_OP_FLAGS_TYPE_MASK));
attrp->alfi_op_flags = attr->xattri_op_flags;
attrp->alfi_value_len = attr->xattri_nameval->value.i_len;
- attrp->alfi_name_len = attr->xattri_nameval->name.i_len;
+
+ if (xfs_attr_log_item_op(attrp) == XFS_ATTRI_OP_FLAGS_NVREPLACE) {
+ attrp->alfi_old_name_len = attr->xattri_nameval->name.i_len;
+ attrp->alfi_new_name_len = attr->xattri_nameval->new_name.i_len;
+ attrp->alfi_new_value_len = attr->xattri_nameval->new_value.i_len;
+ } else {
+ attrp->alfi_name_len = attr->xattri_nameval->name.i_len;
+ }
+
ASSERT(!(attr->xattri_da_args->attr_filter & ~XFS_ATTRI_FILTER_MASK));
attrp->alfi_attr_filter = attr->xattri_da_args->attr_filter;
}
@@ -420,8 +476,11 @@ xfs_attr_create_intent(
* Transfer our reference to the name/value buffer to the
* deferred work state structure.
*/
- attr->xattri_nameval = xfs_attri_log_nameval_alloc(args->name,
- args->namelen, args->value, args->valuelen);
+ attr->xattri_nameval = xfs_attri_log_nameval_alloc(
+ args->name, args->namelen,
+ args->new_name, args->new_namelen,
+ args->value, args->valuelen,
+ args->new_value, args->new_valuelen);
}
attrip = xfs_attri_init(mp, attr->xattri_nameval);
@@ -508,9 +567,6 @@ xfs_attri_validate(
{
unsigned int op = xfs_attr_log_item_op(attrp);
- if (attrp->__pad != 0)
- return false;
-
if (attrp->alfi_op_flags & ~XFS_ATTRI_OP_FLAGS_TYPE_MASK)
return false;
@@ -519,23 +575,43 @@ xfs_attri_validate(
/* alfi_op_flags should be either a set or remove */
switch (op) {
+ case XFS_ATTRI_OP_FLAGS_REMOVE:
+ if (attrp->alfi_value_len != 0)
+ return false;
+ if (attrp->alfi_name_len == 0 ||
+ attrp->alfi_name_len > XATTR_NAME_MAX)
+ return false;
+ if (attrp->alfi_new_value_len != 0)
+ return false;
+ break;
case XFS_ATTRI_OP_FLAGS_SET:
case XFS_ATTRI_OP_FLAGS_REPLACE:
- case XFS_ATTRI_OP_FLAGS_REMOVE:
case XFS_ATTRI_OP_FLAGS_NVREMOVE:
case XFS_ATTRI_OP_FLAGS_NVSET:
+ if (attrp->alfi_name_len == 0 ||
+ attrp->alfi_name_len > XATTR_NAME_MAX)
+ return false;
+ if (attrp->alfi_value_len > XATTR_SIZE_MAX)
+ return false;
+ if (attrp->alfi_new_value_len != 0)
+ return false;
+ break;
+ case XFS_ATTRI_OP_FLAGS_NVREPLACE:
+ if (attrp->alfi_old_name_len == 0 ||
+ attrp->alfi_old_name_len > XATTR_NAME_MAX)
+ return false;
+ if (attrp->alfi_new_name_len == 0 ||
+ attrp->alfi_new_name_len > XATTR_NAME_MAX)
+ return false;
+ if (attrp->alfi_value_len > XATTR_SIZE_MAX)
+ return false;
+ if (attrp->alfi_new_value_len > XATTR_SIZE_MAX)
+ return false;
break;
default:
return false;
}
- if (attrp->alfi_value_len > XATTR_SIZE_MAX)
- return false;
-
- if ((attrp->alfi_name_len > XATTR_NAME_MAX) ||
- (attrp->alfi_name_len == 0))
- return false;
-
return xfs_verify_ino(mp, attrp->alfi_ino);
}
@@ -595,9 +671,13 @@ xfs_attri_item_recover(
args->whichfork = XFS_ATTR_FORK;
args->name = nv->name.i_addr;
args->namelen = nv->name.i_len;
+ args->new_name = nv->new_name.i_addr;
+ args->new_namelen = nv->new_name.i_len;
args->hashval = xfs_da_hashname(args->name, args->namelen);
args->value = nv->value.i_addr;
args->valuelen = nv->value.i_len;
+ args->new_value = nv->new_value.i_addr;
+ args->new_valuelen = nv->new_value.i_len;
args->attr_filter = attrp->alfi_attr_filter & XFS_ATTRI_FILTER_MASK;
args->op_flags = XFS_DA_OP_RECOVERY | XFS_DA_OP_OKNOENT |
XFS_DA_OP_LOGGED;
@@ -605,6 +685,7 @@ xfs_attri_item_recover(
ASSERT(xfs_sb_version_haslogxattrs(&mp->m_sb));
switch (xfs_attr_intent_op(attr)) {
+ case XFS_ATTRI_OP_FLAGS_NVREPLACE:
case XFS_ATTRI_OP_FLAGS_NVSET:
args->op_flags |= XFS_DA_OP_NVLOOKUP;
fallthrough;
@@ -699,7 +780,15 @@ xfs_attri_item_relog(
new_attrp->alfi_ino = old_attrp->alfi_ino;
new_attrp->alfi_op_flags = old_attrp->alfi_op_flags;
new_attrp->alfi_value_len = old_attrp->alfi_value_len;
- new_attrp->alfi_name_len = old_attrp->alfi_name_len;
+
+ if (xfs_attr_log_item_op(old_attrp) == XFS_ATTRI_OP_FLAGS_NVREPLACE) {
+ new_attrp->alfi_new_name_len = old_attrp->alfi_new_name_len;
+ new_attrp->alfi_old_name_len = old_attrp->alfi_old_name_len;
+ new_attrp->alfi_new_value_len = old_attrp->alfi_new_value_len;
+ } else {
+ new_attrp->alfi_name_len = old_attrp->alfi_name_len;
+ }
+
new_attrp->alfi_attr_filter = old_attrp->alfi_attr_filter;
xfs_trans_add_item(tp, &new_attrip->attri_item);
@@ -721,9 +810,13 @@ xlog_recover_attri_commit_pass2(
struct xfs_attri_log_nameval *nv;
const void *attr_name;
const void *attr_value = NULL;
+ const void *attr_new_name = NULL;
+ const void *attr_new_value = NULL;
size_t len;
unsigned int name_len = 0;
unsigned int value_len = 0;
+ unsigned int new_name_len = 0;
+ unsigned int new_value_len = 0;
unsigned int op, i = 0;
/* Validate xfs_attri_log_format before the large memory allocation */
@@ -775,6 +868,21 @@ xlog_recover_attri_commit_pass2(
}
name_len = attri_formatp->alfi_name_len;
break;
+ case XFS_ATTRI_OP_FLAGS_NVREPLACE:
+ /*
+ * Log item, attr name, new attr name, optional attr value,
+ * optional new attr value
+ */
+ if (item->ri_total < 3 || item->ri_total > 5) {
+ XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
+ attri_formatp, len);
+ return -EFSCORRUPTED;
+ }
+ name_len = attri_formatp->alfi_old_name_len;
+ new_name_len = attri_formatp->alfi_new_name_len;
+ value_len = attri_formatp->alfi_value_len;
+ new_value_len = attri_formatp->alfi_new_value_len;
+ break;
default:
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
attri_formatp, len);
@@ -797,12 +905,30 @@ xlog_recover_attri_commit_pass2(
}
i++;
+ /* Validate the new attr name */
+ if (new_name_len > 0) {
+ if (item->ri_buf[i].i_len != xlog_calc_iovec_len(new_name_len)) {
+ XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
+ item->ri_buf[i].i_addr,
+ item->ri_buf[i].i_len);
+ return -EFSCORRUPTED;
+ }
+
+ attr_new_name = item->ri_buf[i].i_addr;
+ if (!xfs_attr_namecheck(attr_new_name, new_name_len)) {
+ XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
+ item->ri_buf[i].i_addr,
+ item->ri_buf[i].i_len);
+ return -EFSCORRUPTED;
+ }
+ i++;
+ }
+
/* Validate the attr value, if present */
if (value_len != 0) {
if (item->ri_buf[i].i_len != xlog_calc_iovec_len(value_len)) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
- item->ri_buf[0].i_addr,
- item->ri_buf[0].i_len);
+ attri_formatp, len);
return -EFSCORRUPTED;
}
@@ -810,6 +936,18 @@ xlog_recover_attri_commit_pass2(
i++;
}
+ /* Validate the new attr value, if present */
+ if (new_value_len != 0) {
+ if (item->ri_buf[i].i_len != xlog_calc_iovec_len(new_value_len)) {
+ XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
+ attri_formatp, len);
+ return -EFSCORRUPTED;
+ }
+
+ attr_new_value = item->ri_buf[i].i_addr;
+ i++;
+ }
+
/*
* Make sure we got the correct number of buffers for the operation
* that we just loaded.
@@ -847,6 +985,23 @@ xlog_recover_attri_commit_pass2(
return -EFSCORRUPTED;
}
break;
+ case XFS_ATTRI_OP_FLAGS_NVREPLACE:
+ /*
+ * Name-value replace operations require the caller to
+ * specify the old and new names and values explicitly.
+ * Values are optional.
+ */
+ if (attr_name == NULL || name_len == 0) {
+ XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
+ attri_formatp, len);
+ return -EFSCORRUPTED;
+ }
+ if (attr_new_name == NULL || new_name_len == 0) {
+ XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
+ attri_formatp, len);
+ return -EFSCORRUPTED;
+ }
+ break;
}
/*
@@ -855,7 +1010,9 @@ xlog_recover_attri_commit_pass2(
* reference.
*/
nv = xfs_attri_log_nameval_alloc(attr_name, name_len,
- attr_value, value_len);
+ attr_new_name, new_name_len,
+ attr_value, value_len,
+ attr_new_value, new_value_len);
attrip = xfs_attri_init(mp, nv);
memcpy(&attrip->attri_format, attri_formatp, len);
@@ -13,7 +13,9 @@ struct kmem_zone;
struct xfs_attri_log_nameval {
struct xfs_log_iovec name;
+ struct xfs_log_iovec new_name; /* NVREPLACE only */
struct xfs_log_iovec value;
+ struct xfs_log_iovec new_value; /* NVREPLACE only */
refcount_t refcount;
/* name and value follow the end of this struct */