linux/fs/hfsplus/ioctl.c
Matthew Garrett a051f71ce9 hfsplus: add an ioctl to bless files
Making an hfsplus partition bootable requires the ability to "bless" a
file by putting its inode number in the volume header. Doing this from
userspace on a mounted filesystem is impractical since the kernel will
write back the original values on unmount. Add an ioctl to allow userspace
to update the volume header information based on the target file.

Signed-off-by: Matthew Garrett <mjg@redhat.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2012-03-20 21:29:53 -04:00

255 lines
6 KiB
C

/*
* linux/fs/hfsplus/ioctl.c
*
* Copyright (C) 2003
* Ethan Benson <erbenson@alaska.net>
* partially derived from linux/fs/ext2/ioctl.c
* Copyright (C) 1993, 1994, 1995
* Remy Card (card@masi.ibp.fr)
* Laboratoire MASI - Institut Blaise Pascal
* Universite Pierre et Marie Curie (Paris VI)
*
* hfsplus ioctls
*/
#include <linux/capability.h>
#include <linux/fs.h>
#include <linux/mount.h>
#include <linux/sched.h>
#include <linux/xattr.h>
#include <asm/uaccess.h>
#include "hfsplus_fs.h"
/*
* "Blessing" an HFS+ filesystem writes metadata to the superblock informing
* the platform firmware which file to boot from
*/
static int hfsplus_ioctl_bless(struct file *file, int __user *user_flags)
{
struct dentry *dentry = file->f_path.dentry;
struct inode *inode = dentry->d_inode;
struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb);
struct hfsplus_vh *vh = sbi->s_vhdr;
struct hfsplus_vh *bvh = sbi->s_backup_vhdr;
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
mutex_lock(&sbi->vh_mutex);
/* Directory containing the bootable system */
vh->finder_info[0] = bvh->finder_info[0] =
cpu_to_be32(parent_ino(dentry));
/* Bootloader */
vh->finder_info[1] = bvh->finder_info[1] = cpu_to_be32(inode->i_ino);
/* Per spec, the OS X system folder - same as finder_info[0] here */
vh->finder_info[5] = bvh->finder_info[5] =
cpu_to_be32(parent_ino(dentry));
mutex_unlock(&sbi->vh_mutex);
return 0;
}
static int hfsplus_ioctl_getflags(struct file *file, int __user *user_flags)
{
struct inode *inode = file->f_path.dentry->d_inode;
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
unsigned int flags = 0;
if (inode->i_flags & S_IMMUTABLE)
flags |= FS_IMMUTABLE_FL;
if (inode->i_flags & S_APPEND)
flags |= FS_APPEND_FL;
if (hip->userflags & HFSPLUS_FLG_NODUMP)
flags |= FS_NODUMP_FL;
return put_user(flags, user_flags);
}
static int hfsplus_ioctl_setflags(struct file *file, int __user *user_flags)
{
struct inode *inode = file->f_path.dentry->d_inode;
struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
unsigned int flags;
int err = 0;
err = mnt_want_write_file(file);
if (err)
goto out;
if (!inode_owner_or_capable(inode)) {
err = -EACCES;
goto out_drop_write;
}
if (get_user(flags, user_flags)) {
err = -EFAULT;
goto out_drop_write;
}
mutex_lock(&inode->i_mutex);
if ((flags & (FS_IMMUTABLE_FL|FS_APPEND_FL)) ||
inode->i_flags & (S_IMMUTABLE|S_APPEND)) {
if (!capable(CAP_LINUX_IMMUTABLE)) {
err = -EPERM;
goto out_unlock_inode;
}
}
/* don't silently ignore unsupported ext2 flags */
if (flags & ~(FS_IMMUTABLE_FL|FS_APPEND_FL|FS_NODUMP_FL)) {
err = -EOPNOTSUPP;
goto out_unlock_inode;
}
if (flags & FS_IMMUTABLE_FL)
inode->i_flags |= S_IMMUTABLE;
else
inode->i_flags &= ~S_IMMUTABLE;
if (flags & FS_APPEND_FL)
inode->i_flags |= S_APPEND;
else
inode->i_flags &= ~S_APPEND;
if (flags & FS_NODUMP_FL)
hip->userflags |= HFSPLUS_FLG_NODUMP;
else
hip->userflags &= ~HFSPLUS_FLG_NODUMP;
inode->i_ctime = CURRENT_TIME_SEC;
mark_inode_dirty(inode);
out_unlock_inode:
mutex_unlock(&inode->i_mutex);
out_drop_write:
mnt_drop_write_file(file);
out:
return err;
}
long hfsplus_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
switch (cmd) {
case HFSPLUS_IOC_EXT2_GETFLAGS:
return hfsplus_ioctl_getflags(file, argp);
case HFSPLUS_IOC_EXT2_SETFLAGS:
return hfsplus_ioctl_setflags(file, argp);
case HFSPLUS_IOC_BLESS:
return hfsplus_ioctl_bless(file, argp);
default:
return -ENOTTY;
}
}
int hfsplus_setxattr(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
struct inode *inode = dentry->d_inode;
struct hfs_find_data fd;
hfsplus_cat_entry entry;
struct hfsplus_cat_file *file;
int res;
if (!S_ISREG(inode->i_mode) || HFSPLUS_IS_RSRC(inode))
return -EOPNOTSUPP;
res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &fd);
if (res)
return res;
res = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd);
if (res)
goto out;
hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
sizeof(struct hfsplus_cat_file));
file = &entry.file;
if (!strcmp(name, "hfs.type")) {
if (size == 4)
memcpy(&file->user_info.fdType, value, 4);
else
res = -ERANGE;
} else if (!strcmp(name, "hfs.creator")) {
if (size == 4)
memcpy(&file->user_info.fdCreator, value, 4);
else
res = -ERANGE;
} else
res = -EOPNOTSUPP;
if (!res) {
hfs_bnode_write(fd.bnode, &entry, fd.entryoffset,
sizeof(struct hfsplus_cat_file));
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_CAT_DIRTY);
}
out:
hfs_find_exit(&fd);
return res;
}
ssize_t hfsplus_getxattr(struct dentry *dentry, const char *name,
void *value, size_t size)
{
struct inode *inode = dentry->d_inode;
struct hfs_find_data fd;
hfsplus_cat_entry entry;
struct hfsplus_cat_file *file;
ssize_t res = 0;
if (!S_ISREG(inode->i_mode) || HFSPLUS_IS_RSRC(inode))
return -EOPNOTSUPP;
if (size) {
res = hfs_find_init(HFSPLUS_SB(inode->i_sb)->cat_tree, &fd);
if (res)
return res;
res = hfsplus_find_cat(inode->i_sb, inode->i_ino, &fd);
if (res)
goto out;
hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
sizeof(struct hfsplus_cat_file));
}
file = &entry.file;
if (!strcmp(name, "hfs.type")) {
if (size >= 4) {
memcpy(value, &file->user_info.fdType, 4);
res = 4;
} else
res = size ? -ERANGE : 4;
} else if (!strcmp(name, "hfs.creator")) {
if (size >= 4) {
memcpy(value, &file->user_info.fdCreator, 4);
res = 4;
} else
res = size ? -ERANGE : 4;
} else
res = -EOPNOTSUPP;
out:
if (size)
hfs_find_exit(&fd);
return res;
}
#define HFSPLUS_ATTRLIST_SIZE (sizeof("hfs.creator")+sizeof("hfs.type"))
ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size)
{
struct inode *inode = dentry->d_inode;
if (!S_ISREG(inode->i_mode) || HFSPLUS_IS_RSRC(inode))
return -EOPNOTSUPP;
if (!buffer || !size)
return HFSPLUS_ATTRLIST_SIZE;
if (size < HFSPLUS_ATTRLIST_SIZE)
return -ERANGE;
strcpy(buffer, "hfs.type");
strcpy(buffer + sizeof("hfs.type"), "hfs.creator");
return HFSPLUS_ATTRLIST_SIZE;
}