freebsd-nq/sys/fs/ext2fs/ext2_extattr.c
Pedro F. Giffuni 34f4388801 ext2fs: add read-write support for Extended Attributes.
Extended attributes and their particular implementation in linux are
different from FreeBSD so in this case we have started diverging from
the  UFS EA implementation, which would be the natural reference.

Depending on future progress implementing ACLs this approach may change
but for now bring to the tree an implementation that is consistent and
can be tested.

Submitted by:	Fedor Uporov

Differential Revision:	https://reviews.freebsd.org/D10460
2017-05-03 22:35:41 +00:00

1055 lines
25 KiB
C

/*-
* Copyright (c) 2017, Fedor Uporov
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/vnode.h>
#include <sys/bio.h>
#include <sys/buf.h>
#include <sys/endian.h>
#include <sys/conf.h>
#include <sys/extattr.h>
#include <fs/ext2fs/fs.h>
#include <fs/ext2fs/ext2fs.h>
#include <fs/ext2fs/inode.h>
#include <fs/ext2fs/ext2_dinode.h>
#include <fs/ext2fs/ext2_mount.h>
#include <fs/ext2fs/ext2_extattr.h>
#include <fs/ext2fs/ext2_extern.h>
static int
ext2_extattr_index_to_bsd(int index)
{
switch (index) {
case EXT4_XATTR_INDEX_SYSTEM:
return (EXTATTR_NAMESPACE_SYSTEM);
case EXT4_XATTR_INDEX_USER:
return (EXTATTR_NAMESPACE_USER);
}
return (EXTATTR_NAMESPACE_EMPTY);
}
static int
ext2_extattr_index_to_linux(int index)
{
switch (index) {
case EXTATTR_NAMESPACE_SYSTEM:
return (EXT4_XATTR_INDEX_SYSTEM);
case EXTATTR_NAMESPACE_USER:
return (EXT4_XATTR_INDEX_USER);
}
return (-1);
}
int
ext2_extattr_valid_attrname(int attrnamespace, const char *attrname)
{
if (attrnamespace == EXTATTR_NAMESPACE_EMPTY)
return (EINVAL);
if (strlen(attrname) == 0)
return (EINVAL);
if (strlen(attrname) + 1 > EXT2_EXTATTR_NAMELEN_MAX)
return (ENAMETOOLONG);
return (0);
}
static int
ext2_extattr_check(struct ext2fs_extattr_entry *entry, char *end)
{
struct ext2fs_extattr_entry *next;
while (!EXT2_IS_LAST_ENTRY(entry)) {
next = EXT2_EXTATTR_NEXT(entry);
if ((char *)next >= end)
return (EIO);
entry = next;
}
return (0);
}
int
ext2_extattr_inode_list(struct inode *ip, int attrnamespace,
struct uio *uio, size_t *size)
{
struct m_ext2fs *fs;
struct buf *bp;
struct ext2fs_extattr_dinode_header *header;
struct ext2fs_extattr_entry *entry;
int error;
fs = ip->i_e2fs;
if ((error = bread(ip->i_devvp,
fsbtodb(fs, ino_to_fsba(fs, ip->i_number)),
(int)fs->e2fs_bsize, NOCRED, &bp)) != 0) {
brelse(bp);
return (error);
}
struct ext2fs_dinode *dinode = (struct ext2fs_dinode *)
((char *)bp->b_data +
EXT2_INODE_SIZE(fs) * ino_to_fsbo(fs, ip->i_number));
/* Check attributes magic value */
header = (struct ext2fs_extattr_dinode_header *)((char *)dinode +
E2FS_REV0_INODE_SIZE + dinode->e2di_extra_isize);
if (header->h_magic != EXTATTR_MAGIC) {
brelse(bp);
return (0);
}
error = ext2_extattr_check(EXT2_IFIRST(header),
(char *)dinode + EXT2_INODE_SIZE(fs));
if (error) {
brelse(bp);
return (error);
}
for (entry = EXT2_IFIRST(header); !EXT2_IS_LAST_ENTRY(entry);
entry = EXT2_EXTATTR_NEXT(entry)) {
if (ext2_extattr_index_to_bsd(entry->e_name_index) != attrnamespace)
continue;
if (uio == NULL)
*size += entry->e_name_len + 1;
else {
char *attr_name = malloc(entry->e_name_len + 1, M_TEMP, M_WAITOK);
attr_name[0] = entry->e_name_len;
memcpy(&attr_name[1], entry->e_name, entry->e_name_len);
error = uiomove(attr_name, entry->e_name_len + 1, uio);
free(attr_name, M_TEMP);
if (error)
break;
}
}
brelse(bp);
return (error);
}
int
ext2_extattr_block_list(struct inode *ip, int attrnamespace,
struct uio *uio, size_t *size)
{
struct m_ext2fs *fs;
struct buf *bp;
struct ext2fs_extattr_header *header;
struct ext2fs_extattr_entry *entry;
int error;
fs = ip->i_e2fs;
error = bread(ip->i_devvp, fsbtodb(fs, ip->i_facl),
fs->e2fs_bsize, NOCRED, &bp);
if (error) {
brelse(bp);
return (error);
}
/* Check attributes magic value */
header = EXT2_HDR(bp);
if (header->h_magic != EXTATTR_MAGIC || header->h_blocks != 1) {
brelse(bp);
return (EINVAL);
}
error = ext2_extattr_check(EXT2_FIRST_ENTRY(bp), bp->b_data + bp->b_bufsize);
if (error) {
brelse(bp);
return (error);
}
for (entry = EXT2_FIRST_ENTRY(bp); !EXT2_IS_LAST_ENTRY(entry);
entry = EXT2_EXTATTR_NEXT(entry)) {
if (ext2_extattr_index_to_bsd(entry->e_name_index) != attrnamespace)
continue;
if (uio == NULL)
*size += entry->e_name_len + 1;
else {
char *attr_name = malloc(entry->e_name_len + 1, M_TEMP, M_WAITOK);
attr_name[0] = entry->e_name_len;
memcpy(&attr_name[1], entry->e_name, entry->e_name_len);
error = uiomove(attr_name, entry->e_name_len + 1, uio);
free(attr_name, M_TEMP);
if (error)
break;
}
}
brelse(bp);
return (error);
}
int
ext2_extattr_inode_get(struct inode *ip, int attrnamespace,
const char *name, struct uio *uio, size_t *size)
{
struct m_ext2fs *fs;
struct buf *bp;
struct ext2fs_extattr_dinode_header *header;
struct ext2fs_extattr_entry *entry;
int error;
fs = ip->i_e2fs;
if ((error = bread(ip->i_devvp,
fsbtodb(fs, ino_to_fsba(fs, ip->i_number)),
(int)fs->e2fs_bsize, NOCRED, &bp)) != 0) {
brelse(bp);
return (error);
}
struct ext2fs_dinode *dinode = (struct ext2fs_dinode *)
((char *)bp->b_data +
EXT2_INODE_SIZE(fs) * ino_to_fsbo(fs, ip->i_number));
/* Check attributes magic value */
header = (struct ext2fs_extattr_dinode_header *)((char *)dinode +
E2FS_REV0_INODE_SIZE + dinode->e2di_extra_isize);
if (header->h_magic != EXTATTR_MAGIC) {
brelse(bp);
return (ENOATTR);
}
error = ext2_extattr_check(EXT2_IFIRST(header),
(char *)dinode + EXT2_INODE_SIZE(fs));
if (error) {
brelse(bp);
return (error);
}
for (entry = EXT2_IFIRST(header); !EXT2_IS_LAST_ENTRY(entry);
entry = EXT2_EXTATTR_NEXT(entry)) {
if (ext2_extattr_index_to_bsd(entry->e_name_index) != attrnamespace)
continue;
if (strlen(name) == entry->e_name_len &&
0 == strncmp(entry->e_name, name, entry->e_name_len)) {
if (uio == NULL)
*size += entry->e_value_size;
else {
error = uiomove(((char *)EXT2_IFIRST(header)) +
entry->e_value_offs, entry->e_value_size, uio);
}
brelse(bp);
return (error);
}
}
brelse(bp);
return (ENOATTR);
}
int
ext2_extattr_block_get(struct inode *ip, int attrnamespace,
const char *name, struct uio *uio, size_t *size)
{
struct m_ext2fs *fs;
struct buf *bp;
struct ext2fs_extattr_header *header;
struct ext2fs_extattr_entry *entry;
int error;
fs = ip->i_e2fs;
error = bread(ip->i_devvp, fsbtodb(fs, ip->i_facl),
fs->e2fs_bsize, NOCRED, &bp);
if (error) {
brelse(bp);
return (error);
}
/* Check attributes magic value */
header = EXT2_HDR(bp);
if (header->h_magic != EXTATTR_MAGIC || header->h_blocks != 1) {
brelse(bp);
return (EINVAL);
}
error = ext2_extattr_check(EXT2_FIRST_ENTRY(bp), bp->b_data + bp->b_bufsize);
if (error) {
brelse(bp);
return (error);
}
for (entry = EXT2_FIRST_ENTRY(bp); !EXT2_IS_LAST_ENTRY(entry);
entry = EXT2_EXTATTR_NEXT(entry)) {
if (ext2_extattr_index_to_bsd(entry->e_name_index) != attrnamespace)
continue;
if (strlen(name) == entry->e_name_len &&
0 == strncmp(entry->e_name, name, entry->e_name_len)) {
if (uio == NULL)
*size += entry->e_value_size;
else {
error = uiomove(bp->b_data + entry->e_value_offs,
entry->e_value_size, uio);
}
brelse(bp);
return (error);
}
}
brelse(bp);
return (ENOATTR);
}
static uint16_t
ext2_extattr_delete_value(char *off,
struct ext2fs_extattr_entry *first_entry,
struct ext2fs_extattr_entry *entry, char *end)
{
uint16_t min_offs;
struct ext2fs_extattr_entry *next;
min_offs = end - off;
next = first_entry;
while (!EXT2_IS_LAST_ENTRY(next)) {
if (min_offs > next->e_value_offs && next->e_value_offs > 0)
min_offs = next->e_value_offs;
next = EXT2_EXTATTR_NEXT(next);
}
if (entry->e_value_size == 0)
return (min_offs);
memmove(off + min_offs + EXT2_EXTATTR_SIZE(entry->e_value_size),
off + min_offs, entry->e_value_offs - min_offs);
/* Adjust all value offsets */
next = first_entry;
while (!EXT2_IS_LAST_ENTRY(next))
{
if (next->e_value_offs > 0 &&
next->e_value_offs < entry->e_value_offs)
next->e_value_offs +=
EXT2_EXTATTR_SIZE(entry->e_value_size);
next = EXT2_EXTATTR_NEXT(next);
}
min_offs += EXT2_EXTATTR_SIZE(entry->e_value_size);
return (min_offs);
}
static void
ext2_extattr_delete_entry(char *off,
struct ext2fs_extattr_entry *first_entry,
struct ext2fs_extattr_entry *entry, char *end)
{
char *pad;
struct ext2fs_extattr_entry *next;
/* Clean entry value */
ext2_extattr_delete_value(off, first_entry, entry, end);
/* Clean the entry */
next = first_entry;
while (!EXT2_IS_LAST_ENTRY(next))
next = EXT2_EXTATTR_NEXT(next);
pad = (char*)next + sizeof(uint32_t);
memmove(entry, (char *)entry + EXT2_EXTATTR_LEN(entry->e_name_len),
pad - ((char *)entry + EXT2_EXTATTR_LEN(entry->e_name_len)));
}
int
ext2_extattr_inode_delete(struct inode *ip, int attrnamespace, const char *name)
{
struct m_ext2fs *fs;
struct buf *bp;
struct ext2fs_extattr_dinode_header *header;
struct ext2fs_extattr_entry *entry;
int error;
fs = ip->i_e2fs;
if ((error = bread(ip->i_devvp,
fsbtodb(fs, ino_to_fsba(fs, ip->i_number)),
(int)fs->e2fs_bsize, NOCRED, &bp)) != 0) {
brelse(bp);
return (error);
}
struct ext2fs_dinode *dinode = (struct ext2fs_dinode *)
((char *)bp->b_data +
EXT2_INODE_SIZE(fs) * ino_to_fsbo(fs, ip->i_number));
/* Check attributes magic value */
header = (struct ext2fs_extattr_dinode_header *)((char *)dinode +
E2FS_REV0_INODE_SIZE + dinode->e2di_extra_isize);
if (header->h_magic != EXTATTR_MAGIC) {
brelse(bp);
return (ENOATTR);
}
error = ext2_extattr_check(EXT2_IFIRST(header),
(char *)dinode + EXT2_INODE_SIZE(fs));
if (error) {
brelse(bp);
return (error);
}
/* If I am last entry, just make magic zero */
entry = EXT2_IFIRST(header);
if (EXT2_IS_LAST_ENTRY(EXT2_EXTATTR_NEXT(entry))) {
if (strlen(name) == entry->e_name_len &&
0 == strncmp(entry->e_name, name, entry->e_name_len)) {
memset(header, 0, sizeof(struct ext2fs_extattr_dinode_header));
return (bwrite(bp));
}
}
for (entry = EXT2_IFIRST(header); !EXT2_IS_LAST_ENTRY(entry);
entry = EXT2_EXTATTR_NEXT(entry)) {
if (ext2_extattr_index_to_bsd(entry->e_name_index) != attrnamespace)
continue;
if (strlen(name) == entry->e_name_len &&
0 == strncmp(entry->e_name, name, entry->e_name_len)) {
ext2_extattr_delete_entry((char *)EXT2_IFIRST(header),
EXT2_IFIRST(header), entry,
(char *)dinode + EXT2_INODE_SIZE(fs));
return (bwrite(bp));
}
}
brelse(bp);
return (ENOATTR);
}
static int
ext2_extattr_block_clone(struct inode *ip, struct buf **bpp)
{
struct m_ext2fs *fs;
struct buf *sbp;
struct buf *cbp;
struct ext2fs_extattr_header *header;
uint64_t facl;
fs = ip->i_e2fs;
sbp = *bpp;
header = EXT2_HDR(sbp);
if (header->h_magic != EXTATTR_MAGIC || header->h_refcount == 1)
return (EINVAL);
facl = ext2_allocfacl(ip);
if (!facl)
return (ENOSPC);
cbp = getblk(ip->i_devvp, fsbtodb(fs, facl), fs->e2fs_bsize, 0, 0, 0);
if (!cbp) {
ext2_blkfree(ip, facl, fs->e2fs_bsize);
return (EIO);
}
memcpy(cbp->b_data, sbp->b_data, fs->e2fs_bsize);
header->h_refcount--;
bwrite(sbp);
ip->i_facl = facl;
ext2_update(ip->i_vnode, 1);
header = EXT2_HDR(cbp);
header->h_refcount = 1;
*bpp = cbp;
return (0);
}
int
ext2_extattr_block_delete(struct inode *ip, int attrnamespace, const char *name)
{
struct m_ext2fs *fs;
struct buf *bp;
struct ext2fs_extattr_header *header;
struct ext2fs_extattr_entry *entry;
int error;
fs = ip->i_e2fs;
error = bread(ip->i_devvp, fsbtodb(fs, ip->i_facl),
fs->e2fs_bsize, NOCRED, &bp);
if (error) {
brelse(bp);
return (error);
}
/* Check attributes magic value */
header = EXT2_HDR(bp);
if (header->h_magic != EXTATTR_MAGIC || header->h_blocks != 1) {
brelse(bp);
return (EINVAL);
}
error = ext2_extattr_check(EXT2_FIRST_ENTRY(bp), bp->b_data + bp->b_bufsize);
if (error) {
brelse(bp);
return (error);
}
if (header->h_refcount > 1) {
error = ext2_extattr_block_clone(ip, &bp);
if (error) {
brelse(bp);
return (error);
}
}
/* If I am last entry, clean me and free the block */
entry = EXT2_FIRST_ENTRY(bp);
if (EXT2_IS_LAST_ENTRY(EXT2_EXTATTR_NEXT(entry))) {
if (strlen(name) == entry->e_name_len &&
0 == strncmp(entry->e_name, name, entry->e_name_len)) {
ip->i_blocks -= btodb(fs->e2fs_bsize);
ext2_blkfree(ip, ip->i_facl, fs->e2fs_bsize);
ip->i_facl = 0;
error = ext2_update(ip->i_vnode, 1);
brelse(bp);
return (error);
}
}
for (entry = EXT2_FIRST_ENTRY(bp); !EXT2_IS_LAST_ENTRY(entry);
entry = EXT2_EXTATTR_NEXT(entry)) {
if (ext2_extattr_index_to_bsd(entry->e_name_index) != attrnamespace)
continue;
if (strlen(name) == entry->e_name_len &&
0 == strncmp(entry->e_name, name, entry->e_name_len)) {
ext2_extattr_delete_entry(bp->b_data,
EXT2_FIRST_ENTRY(bp), entry,
bp->b_data + bp->b_bufsize);
return (bwrite(bp));
}
}
brelse(bp);
return (ENOATTR);
}
static struct ext2fs_extattr_entry *
allocate_entry(const char *name, int attrnamespace, uint16_t offs,
uint32_t size, uint32_t hash)
{
size_t name_len;
struct ext2fs_extattr_entry *entry;
name_len = strlen(name);
entry = malloc(sizeof(struct ext2fs_extattr_entry) + name_len,
M_TEMP, M_WAITOK);
entry->e_name_len = name_len;
entry->e_name_index = ext2_extattr_index_to_linux(attrnamespace);
entry->e_value_offs = offs;
entry->e_value_block = 0;
entry->e_value_size = size;
entry->e_hash = hash;
memcpy(entry->e_name, name, name_len);
return (entry);
}
static void
free_entry(struct ext2fs_extattr_entry *entry)
{
free(entry, M_TEMP);
}
static int
ext2_extattr_get_size(struct ext2fs_extattr_entry *first_entry,
struct ext2fs_extattr_entry *exist_entry, int header_size,
int name_len, int new_size)
{
struct ext2fs_extattr_entry *entry;
int size;
size = header_size;
size += sizeof(uint32_t);
if (NULL == exist_entry) {
size += EXT2_EXTATTR_LEN(name_len);
size += EXT2_EXTATTR_SIZE(new_size);
}
if (first_entry)
for (entry = first_entry; !EXT2_IS_LAST_ENTRY(entry);
entry = EXT2_EXTATTR_NEXT(entry)) {
if (entry != exist_entry)
size += EXT2_EXTATTR_LEN(entry->e_name_len) +
EXT2_EXTATTR_SIZE(entry->e_value_size);
else
size += EXT2_EXTATTR_LEN(entry->e_name_len) +
EXT2_EXTATTR_SIZE(new_size);
}
return (size);
}
static void
ext2_extattr_set_exist_entry(char *off,
struct ext2fs_extattr_entry *first_entry,
struct ext2fs_extattr_entry *entry,
char *end, struct uio *uio)
{
uint16_t min_offs;
min_offs = ext2_extattr_delete_value(off, first_entry, entry, end);
entry->e_value_size = uio->uio_resid;
if (entry->e_value_size)
entry->e_value_offs = min_offs -
EXT2_EXTATTR_SIZE(uio->uio_resid);
else
entry->e_value_offs = 0;
uiomove(off + entry->e_value_offs, entry->e_value_size, uio);
}
static struct ext2fs_extattr_entry *
ext2_extattr_set_new_entry(char *off, struct ext2fs_extattr_entry *first_entry,
const char *name, int attrnamespace, char *end, struct uio *uio)
{
int name_len;
char *pad;
uint16_t min_offs;
struct ext2fs_extattr_entry *entry;
struct ext2fs_extattr_entry *new_entry;
/* Find pad's */
min_offs = end - off;
entry = first_entry;
while (!EXT2_IS_LAST_ENTRY(entry)) {
if (min_offs > entry->e_value_offs && entry->e_value_offs > 0)
min_offs = entry->e_value_offs;
entry = EXT2_EXTATTR_NEXT(entry);
}
pad = (char*)entry + sizeof(uint32_t);
/* Find entry insert position */
name_len = strlen(name);
entry = first_entry;
while (!EXT2_IS_LAST_ENTRY(entry)) {
if (!(attrnamespace - entry->e_name_index) &&
!(name_len - entry->e_name_len))
if (memcmp(name, entry->e_name, name_len) <= 0)
break;
entry = EXT2_EXTATTR_NEXT(entry);
}
/* Create new entry and insert it */
new_entry = allocate_entry(name, attrnamespace, 0, uio->uio_resid, 0);
memmove((char *)entry + EXT2_EXTATTR_LEN(new_entry->e_name_len), entry,
pad - (char*)entry);
memcpy(entry, new_entry, EXT2_EXTATTR_LEN(new_entry->e_name_len));
free_entry(new_entry);
new_entry = entry;
if (new_entry->e_value_size > 0)
new_entry->e_value_offs = min_offs -
EXT2_EXTATTR_SIZE(new_entry->e_value_size);
uiomove(off + new_entry->e_value_offs, new_entry->e_value_size, uio);
return (new_entry);
}
int
ext2_extattr_inode_set(struct inode *ip, int attrnamespace,
const char *name, struct uio *uio)
{
struct m_ext2fs *fs;
struct buf *bp;
struct ext2fs_extattr_dinode_header *header;
struct ext2fs_extattr_entry *entry;
size_t size = 0, max_size;
int error;
fs = ip->i_e2fs;
if ((error = bread(ip->i_devvp,
fsbtodb(fs, ino_to_fsba(fs, ip->i_number)),
(int)fs->e2fs_bsize, NOCRED, &bp)) != 0) {
brelse(bp);
return (error);
}
struct ext2fs_dinode *dinode = (struct ext2fs_dinode *)
((char *)bp->b_data +
EXT2_INODE_SIZE(fs) * ino_to_fsbo(fs, ip->i_number));
/* Check attributes magic value */
header = (struct ext2fs_extattr_dinode_header *)((char *)dinode +
E2FS_REV0_INODE_SIZE + dinode->e2di_extra_isize);
if (header->h_magic != EXTATTR_MAGIC) {
brelse(bp);
return (ENOSPC);
}
error = ext2_extattr_check(EXT2_IFIRST(header), (char *)dinode +
EXT2_INODE_SIZE(fs));
if (error) {
brelse(bp);
return (error);
}
/* Find if entry exist */
for (entry = EXT2_IFIRST(header); !EXT2_IS_LAST_ENTRY(entry);
entry = EXT2_EXTATTR_NEXT(entry)) {
if (ext2_extattr_index_to_bsd(entry->e_name_index) != attrnamespace)
continue;
if (strlen(name) == entry->e_name_len &&
0 == strncmp(entry->e_name, name, entry->e_name_len))
break;
}
max_size = EXT2_INODE_SIZE(fs) - E2FS_REV0_INODE_SIZE -
dinode->e2di_extra_isize;
if (!EXT2_IS_LAST_ENTRY(entry)) {
size = ext2_extattr_get_size(EXT2_IFIRST(header), entry,
sizeof(struct ext2fs_extattr_dinode_header),
entry->e_name_len, uio->uio_resid);
if (size > max_size) {
brelse(bp);
return (ENOSPC);
}
ext2_extattr_set_exist_entry((char *)EXT2_IFIRST(header),
EXT2_IFIRST(header), entry, (char *)header + max_size, uio);
} else {
/* Ensure that the same entry does not exist in the block */
if (ip->i_facl) {
error = ext2_extattr_block_get(ip, attrnamespace, name,
NULL, &size);
if (error != ENOATTR || size > 0) {
brelse(bp);
if (size > 0)
error = ENOSPC;
return (error);
}
}
size = ext2_extattr_get_size(EXT2_IFIRST(header), NULL,
sizeof(struct ext2fs_extattr_dinode_header),
entry->e_name_len, uio->uio_resid);
if (size > max_size) {
brelse(bp);
return (ENOSPC);
}
ext2_extattr_set_new_entry((char *)EXT2_IFIRST(header),
EXT2_IFIRST(header), name, attrnamespace,
(char *)header + max_size, uio);
}
return (bwrite(bp));
}
static void
ext2_extattr_hash_entry(struct ext2fs_extattr_header *header,
struct ext2fs_extattr_entry *entry)
{
uint32_t hash = 0;
char *name = entry->e_name;
int n;
for (n=0; n < entry->e_name_len; n++) {
hash = (hash << EXT2_EXTATTR_NAME_HASH_SHIFT) ^
(hash >> (8*sizeof(hash) - EXT2_EXTATTR_NAME_HASH_SHIFT)) ^
(*name++);
}
if (entry->e_value_block == 0 && entry->e_value_size != 0) {
uint32_t *value = (uint32_t *)((char *)header + entry->e_value_offs);
for (n = (entry->e_value_size +
EXT2_EXTATTR_ROUND) >> EXT2_EXTATTR_PAD_BITS; n; n--) {
hash = (hash << EXT2_EXTATTR_VALUE_HASH_SHIFT) ^
(hash >> (8*sizeof(hash) - EXT2_EXTATTR_VALUE_HASH_SHIFT)) ^
(*value++);
}
}
entry->e_hash = hash;
}
static void
ext2_extattr_rehash(struct ext2fs_extattr_header *header,
struct ext2fs_extattr_entry *entry)
{
struct ext2fs_extattr_entry *here;
uint32_t hash = 0;
ext2_extattr_hash_entry(header, entry);
here = EXT2_ENTRY(header+1);
while (!EXT2_IS_LAST_ENTRY(here)) {
if (!here->e_hash) {
/* Block is not shared if an entry's hash value == 0 */
hash = 0;
break;
}
hash = (hash << EXT2_EXTATTR_BLOCK_HASH_SHIFT) ^
(hash >> (8*sizeof(hash) - EXT2_EXTATTR_BLOCK_HASH_SHIFT)) ^
here->e_hash;
here = EXT2_EXTATTR_NEXT(here);
}
header->h_hash = hash;
}
int
ext2_extattr_block_set(struct inode *ip, int attrnamespace,
const char *name, struct uio *uio)
{
struct m_ext2fs *fs;
struct buf *bp;
struct ext2fs_extattr_header *header;
struct ext2fs_extattr_entry *entry;
size_t size;
int error;
fs = ip->i_e2fs;
if (ip->i_facl) {
error = bread(ip->i_devvp, fsbtodb(fs, ip->i_facl),
fs->e2fs_bsize, NOCRED, &bp);
if (error) {
brelse(bp);
return (error);
}
/* Check attributes magic value */
header = EXT2_HDR(bp);
if (header->h_magic != EXTATTR_MAGIC || header->h_blocks != 1) {
brelse(bp);
return (EINVAL);
}
error = ext2_extattr_check(EXT2_FIRST_ENTRY(bp),
bp->b_data + bp->b_bufsize);
if (error) {
brelse(bp);
return (error);
}
if (header->h_refcount > 1) {
error = ext2_extattr_block_clone(ip, &bp);
if (error) {
brelse(bp);
return (error);
}
header = EXT2_HDR(bp);
}
/* Find if entry exist */
for (entry = EXT2_FIRST_ENTRY(bp); !EXT2_IS_LAST_ENTRY(entry);
entry = EXT2_EXTATTR_NEXT(entry)) {
if (ext2_extattr_index_to_bsd(entry->e_name_index) != attrnamespace)
continue;
if (strlen(name) == entry->e_name_len &&
0 == strncmp(entry->e_name, name, entry->e_name_len))
break;
}
if (!EXT2_IS_LAST_ENTRY(entry)) {
size = ext2_extattr_get_size(EXT2_FIRST_ENTRY(bp), entry,
sizeof(struct ext2fs_extattr_header),
entry->e_name_len, uio->uio_resid);
if (size > bp->b_bufsize) {
brelse(bp);
return (ENOSPC);
}
ext2_extattr_set_exist_entry(bp->b_data, EXT2_FIRST_ENTRY(bp),
entry, bp->b_data + bp->b_bufsize, uio);
} else {
size = ext2_extattr_get_size(EXT2_FIRST_ENTRY(bp), NULL,
sizeof(struct ext2fs_extattr_header),
strlen(name), uio->uio_resid);
if (size > bp->b_bufsize) {
brelse(bp);
return (ENOSPC);
}
entry = ext2_extattr_set_new_entry(bp->b_data, EXT2_FIRST_ENTRY(bp),
name, attrnamespace, bp->b_data + bp->b_bufsize, uio);
/* Clean the same entry in the inode */
error = ext2_extattr_inode_delete(ip, attrnamespace, name);
if (error && error != ENOATTR) {
brelse(bp);
return (error);
}
}
ext2_extattr_rehash(header, entry);
return (bwrite(bp));
}
size = ext2_extattr_get_size(NULL, NULL,
sizeof(struct ext2fs_extattr_header), strlen(name), uio->uio_resid);
if (size > fs->e2fs_bsize)
return (ENOSPC);
/* Allocate block, fill EA header and insert entry */
ip->i_facl = ext2_allocfacl(ip);
if (0 == ip->i_facl)
return (ENOSPC);
ip->i_blocks += btodb(fs->e2fs_bsize);
ext2_update(ip->i_vnode, 1);
bp = getblk(ip->i_devvp, fsbtodb(fs, ip->i_facl), fs->e2fs_bsize, 0, 0, 0);
if (!bp) {
ext2_blkfree(ip, ip->i_facl, fs->e2fs_bsize);
ip->i_blocks -= btodb(fs->e2fs_bsize);
ip->i_facl = 0;
ext2_update(ip->i_vnode, 1);
return (EIO);
}
header = EXT2_HDR(bp);
header->h_magic = EXTATTR_MAGIC;
header->h_refcount = 1;
header->h_blocks = 1;
header->h_hash = 0;
memset(header->h_reserved, 0, sizeof(header->h_reserved));
memcpy(bp->b_data, header, sizeof(struct ext2fs_extattr_header));
memset(EXT2_FIRST_ENTRY(bp), 0, sizeof(uint32_t));
entry = ext2_extattr_set_new_entry(bp->b_data, EXT2_FIRST_ENTRY(bp),
name, attrnamespace, bp->b_data + bp->b_bufsize, uio);
/* Clean the same entry in the inode */
error = ext2_extattr_inode_delete(ip, attrnamespace, name);
if (error && error != ENOATTR) {
brelse(bp);
return (error);
}
ext2_extattr_rehash(header, entry);
return (bwrite(bp));
}
int ext2_extattr_free(struct inode *ip)
{
struct m_ext2fs *fs;
struct buf *bp;
struct ext2fs_extattr_header *header;
int error;
fs = ip->i_e2fs;
if (!ip->i_facl)
return (0);
error = bread(ip->i_devvp, fsbtodb(fs, ip->i_facl),
fs->e2fs_bsize, NOCRED, &bp);
if (error) {
brelse(bp);
return (error);
}
/* Check attributes magic value */
header = EXT2_HDR(bp);
if (header->h_magic != EXTATTR_MAGIC || header->h_blocks != 1) {
brelse(bp);
return (EINVAL);
}
error = ext2_extattr_check(EXT2_FIRST_ENTRY(bp), bp->b_data + bp->b_bufsize);
if (error) {
brelse(bp);
return (error);
}
if (header->h_refcount > 1) {
header->h_refcount--;
bwrite(bp);
} else {
ext2_blkfree(ip, ip->i_facl, ip->i_e2fs->e2fs_bsize);
brelse(bp);
}
ip->i_blocks -= btodb(ip->i_e2fs->e2fs_bsize);
ip->i_facl = 0;
ext2_update(ip->i_vnode, 1);
return (0);
}