Files
linux-apfs-oot/xattr.c
T
Ernesto A. Fernández 72e6425af6 Set up a standalone repository for the APFS module
Copy the code of the APFS module into its own repository, without the
rest of the kernel tree.  Development will continue upstream, but the
intention is to make life easier for potential users.

To get the module to build independently, rewrite the Makefile and
add a definition for the APFS_SUPER_MAGIC macro.  Since the intention is
to support a range of kernel versions, use preprocessor checks to handle
kernels without statx, without iversion, and without SB_RDONLY.

Provide a README file based on the upstream documentation, but with
additional build and mount instructions.  Add a LICENSE file as well.

Signed-off-by: Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
2019-03-03 17:29:23 -03:00

326 lines
8.7 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/apfs/xattr.c
*
* Copyright (C) 2018 Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
*/
#include <linux/buffer_head.h>
#include <linux/xattr.h>
#include "apfs.h"
#include "btree.h"
#include "extents.h"
#include "key.h"
#include "super.h"
#include "node.h"
#include "message.h"
#include "xattr.h"
/**
* apfs_xattr_from_query - Read the xattr record found by a successful query
* @query: the query that found the record
* @xattr: Return parameter. The xattr record found.
*
* Reads the xattr record into @xattr and performs some basic sanity checks
* as a protection against crafted filesystems. Returns 0 on success or
* -EFSCORRUPTED otherwise.
*
* The caller must not free @query while @xattr is in use, because @xattr->name
* and @xattr->xdata point to data on disk.
*/
static int apfs_xattr_from_query(struct apfs_query *query,
struct apfs_xattr *xattr)
{
struct apfs_xattr_val *xattr_val;
struct apfs_xattr_key *xattr_key;
char *raw = query->node->object.bh->b_data;
int datalen = query->len - sizeof(*xattr_val);
int namelen = query->key_len - sizeof(*xattr_key);
if (namelen < 1 || datalen < 0)
return -EFSCORRUPTED;
xattr_val = (struct apfs_xattr_val *)(raw + query->off);
xattr_key = (struct apfs_xattr_key *)(raw + query->key_off);
if (namelen != le16_to_cpu(xattr_key->name_len))
return -EFSCORRUPTED;
/* The xattr name must be NULL-terminated */
if (xattr_key->name[namelen - 1] != 0)
return -EFSCORRUPTED;
xattr->has_dstream = le16_to_cpu(xattr_val->flags) &
APFS_XATTR_DATA_STREAM;
if (xattr->has_dstream && datalen != sizeof(struct apfs_xattr_dstream))
return -EFSCORRUPTED;
if (!xattr->has_dstream && datalen != le16_to_cpu(xattr_val->xdata_len))
return -EFSCORRUPTED;
xattr->name = xattr_key->name;
xattr->name_len = namelen - 1; /* Don't count the NULL termination */
xattr->xdata = xattr_val->xdata;
xattr->xdata_len = datalen;
return 0;
}
/**
* apfs_xattr_extents_read - Read the value of a xattr from its extents
* @parent: inode the attribute belongs to
* @xattr: the xattr structure
* @buffer: where to copy the attribute value
* @size: size of @buffer
*
* Copies the value of @xattr to @buffer, if provided. If @buffer is NULL, just
* computes the size of the buffer required.
*
* Returns the number of bytes used/required, or a negative error code in case
* of failure.
*/
static int apfs_xattr_extents_read(struct inode *parent,
struct apfs_xattr *xattr,
void *buffer, size_t size)
{
struct super_block *sb = parent->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_xattr_dstream *xdata;
u64 extent_id;
int length;
int ret;
int i;
xdata = (struct apfs_xattr_dstream *) xattr->xdata;
length = le64_to_cpu(xdata->dstream.size);
if (length < 0 || length < le64_to_cpu(xdata->dstream.size))
return -E2BIG;
if (!buffer) /* All we want is the length */
return length;
if (length > size) /* xattr won't fit in the buffer */
return -ERANGE;
extent_id = le64_to_cpu(xdata->xattr_obj_id);
/* We will read all the extents, starting with the last one */
apfs_init_file_extent_key(extent_id, 0 /* offset */, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
/*
* The logic in this loop would allow a crafted filesystem with a large
* number of redundant extents to become stuck for a long time. We use
* the xattr length to put a limit on the number of iterations.
*/
ret = -EFSCORRUPTED;
for (i = 0; i < (length >> parent->i_blkbits) + 2; i++) {
struct apfs_file_extent ext;
u64 block_count, file_off;
int err;
int j;
err = apfs_btree_query(sb, &query);
if (err == -ENODATA) { /* No more records to search */
ret = length;
goto done;
}
if (err) {
ret = err;
goto done;
}
err = apfs_extent_from_query(query, &ext);
if (err) {
apfs_alert(sb, "bad extent for xattr in inode 0x%llx",
(unsigned long long) parent->i_ino);
ret = err;
goto done;
}
block_count = ext.len >> sb->s_blocksize_bits;
file_off = ext.logical_addr;
for (j = 0; j < block_count; ++j) {
struct buffer_head *bh;
int bytes;
if (length <= file_off) /* Read the whole extent */
break;
bytes = min(sb->s_blocksize,
(unsigned long)(length - file_off));
bh = sb_bread(sb, ext.phys_block_num + j);
if (!bh) {
ret = -EIO;
goto done;
}
memcpy(buffer + file_off, bh->b_data, bytes);
brelse(bh);
file_off = file_off + bytes;
}
}
done:
apfs_free_query(sb, query);
return ret;
}
/**
* apfs_xattr_inline_read - Read the value of an inline xattr
* @parent: inode the attribute belongs to
* @xattr: the xattr structure
* @buffer: where to copy the attribute value
* @size: size of @buffer
*
* Copies the inline value of @xattr to @buffer, if provided. If @buffer is
* NULL, just computes the size of the buffer required.
*
* Returns the number of bytes used/required, or a negative error code in case
* of failure.
*/
static int apfs_xattr_inline_read(struct inode *parent,
struct apfs_xattr *xattr,
void *buffer, size_t size)
{
int length = xattr->xdata_len;
if (!buffer) /* All we want is the length */
return length;
if (length > size) /* xattr won't fit in the buffer */
return -ERANGE;
memcpy(buffer, xattr->xdata, length);
return length;
}
/**
* apfs_xattr_get - Find and read a named attribute
* @inode: inode the attribute belongs to
* @name: name of the attribute
* @buffer: where to copy the attribute value
* @size: size of @buffer
*
* Finds an extended attribute and copies its value to @buffer, if provided. If
* @buffer is NULL, just computes the size of the buffer required.
*
* Returns the number of bytes used/required, or a negative error code in case
* of failure.
*/
int apfs_xattr_get(struct inode *inode, const char *name, void *buffer,
size_t size)
{
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
struct apfs_xattr xattr;
u64 cnid = inode->i_ino;
int ret;
apfs_init_xattr_key(cnid, name, &key);
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
query->key = &key;
query->flags |= APFS_QUERY_CAT | APFS_QUERY_EXACT;
ret = apfs_btree_query(sb, &query);
if (ret)
goto done;
ret = apfs_xattr_from_query(query, &xattr);
if (ret) {
apfs_alert(sb, "bad xattr record in inode 0x%llx", cnid);
goto done;
}
if (xattr.has_dstream)
ret = apfs_xattr_extents_read(inode, &xattr, buffer, size);
else
ret = apfs_xattr_inline_read(inode, &xattr, buffer, size);
done:
apfs_free_query(sb, query);
return ret;
}
static int apfs_xattr_osx_get(const struct xattr_handler *handler,
struct dentry *unused, struct inode *inode,
const char *name, void *buffer, size_t size)
{
/* Ignore the fake 'osx' prefix */
return apfs_xattr_get(inode, name, buffer, size);
}
static const struct xattr_handler apfs_xattr_osx_handler = {
.prefix = XATTR_MAC_OSX_PREFIX,
.get = apfs_xattr_osx_get,
};
/* On-disk xattrs have no namespace; use a fake 'osx' prefix in the kernel */
const struct xattr_handler *apfs_xattr_handlers[] = {
&apfs_xattr_osx_handler,
NULL
};
ssize_t apfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
{
struct inode *inode = d_inode(dentry);
struct super_block *sb = inode->i_sb;
struct apfs_sb_info *sbi = APFS_SB(sb);
struct apfs_key key;
struct apfs_query *query;
u64 cnid = inode->i_ino;
size_t free = size;
ssize_t ret;
query = apfs_alloc_query(sbi->s_cat_root, NULL /* parent */);
if (!query)
return -ENOMEM;
/* We want all the xattrs for the cnid, regardless of the name */
apfs_init_xattr_key(cnid, NULL /* name */, &key);
query->key = &key;
query->flags = APFS_QUERY_CAT | APFS_QUERY_MULTIPLE | APFS_QUERY_EXACT;
while (1) {
struct apfs_xattr xattr;
ret = apfs_btree_query(sb, &query);
if (ret == -ENODATA) { /* Got all the xattrs */
ret = size - free;
break;
}
if (ret)
break;
ret = apfs_xattr_from_query(query, &xattr);
if (ret) {
apfs_alert(sb, "bad xattr key in inode %llx", cnid);
break;
}
if (buffer) {
/* Prepend the fake 'osx' prefix before listing */
if (xattr.name_len + XATTR_MAC_OSX_PREFIX_LEN + 1 >
free) {
ret = -ERANGE;
break;
}
memcpy(buffer, XATTR_MAC_OSX_PREFIX,
XATTR_MAC_OSX_PREFIX_LEN);
buffer += XATTR_MAC_OSX_PREFIX_LEN;
memcpy(buffer, xattr.name, xattr.name_len + 1);
buffer += xattr.name_len + 1;
}
free -= xattr.name_len + XATTR_MAC_OSX_PREFIX_LEN + 1;
}
apfs_free_query(sb, query);
return ret;
}