Commit 0acef032 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] reiserfs: ACL support

From: Chris Mason <mason@suse.com>

From: jeffm@suse.com

reiserfs acl support
parent 0b1a6a8c
......@@ -254,6 +254,18 @@ config REISERFS_FS_XATTR
If unsure, say N.
config REISERFS_FS_POSIX_ACL
bool "ReiserFS POSIX Access Control Lists"
depends on REISERFS_FS_XATTR
help
Posix Access Control Lists (ACLs) support permissions for users and
groups beyond the owner/group/world scheme.
To learn more about Access Control Lists, visit the Posix ACLs for
Linux website <http://acl.bestbits.at/>.
If you don't know what Access Control Lists are, say N
config JFS_FS
tristate "JFS filesystem support"
select NLS
......@@ -292,13 +304,13 @@ config JFS_STATISTICS
to be made available to the user in the /proc/fs/jfs/ directory.
config FS_POSIX_ACL
# Posix ACL utility routines (for now, only ext2/ext3/jfs)
# Posix ACL utility routines (for now, only ext2/ext3/jfs/reiserfs)
#
# NOTE: you can implement Posix ACLs without these helpers (XFS does).
# Never use this symbol for ifdefs.
#
bool
depends on EXT2_FS_POSIX_ACL || EXT3_FS_POSIX_ACL || JFS_POSIX_ACL
depends on EXT2_FS_POSIX_ACL || EXT3_FS_POSIX_ACL || JFS_POSIX_ACL || REISERFS_FS_POSIX_ACL
default y
config XFS_FS
......
......@@ -13,6 +13,10 @@ ifeq ($(CONFIG_REISERFS_FS_XATTR),y)
reiserfs-objs += xattr.o xattr_user.o
endif
ifeq ($(CONFIG_REISERFS_FS_POSIX_ACL),y)
reiserfs-objs += xattr_acl.o
endif
# gcc -O2 (the kernel default) is overaggressive on ppc32 when many inline
# functions are used. This causes the compiler to advance the stack
# pointer out of the available stack space, corrupting kernel space,
......
......@@ -5,6 +5,7 @@
#include <linux/time.h>
#include <linux/reiserfs_fs.h>
#include <linux/reiserfs_acl.h>
#include <linux/reiserfs_xattr.h>
#include <linux/smp_lock.h>
#include <asm/uaccess.h>
......
......@@ -5,6 +5,7 @@
#include <linux/config.h>
#include <linux/time.h>
#include <linux/reiserfs_fs.h>
#include <linux/reiserfs_acl.h>
#include <linux/reiserfs_xattr.h>
#include <linux/smp_lock.h>
#include <linux/pagemap.h>
......@@ -976,6 +977,8 @@ static void init_inode (struct inode * inode, struct path * path)
REISERFS_I(inode)->i_prealloc_count = 0;
REISERFS_I(inode)->i_trans_id = 0;
REISERFS_I(inode)->i_jl = NULL;
REISERFS_I(inode)->i_acl_access = NULL;
REISERFS_I(inode)->i_acl_default = NULL;
if (stat_data_v1 (ih)) {
struct stat_data_v1 * sd = (struct stat_data_v1 *)B_I_PITEM (bh, ih);
......@@ -1637,6 +1640,8 @@ int reiserfs_new_inode (struct reiserfs_transaction_handle *th,
REISERFS_I(inode)->i_attrs =
REISERFS_I(dir)->i_attrs & REISERFS_INHERIT_MASK;
sd_attrs_to_i_attrs( REISERFS_I(inode) -> i_attrs, inode );
REISERFS_I(inode)->i_acl_access = NULL;
REISERFS_I(inode)->i_acl_default = NULL;
if (old_format_only (sb))
make_le_item_head (&ih, 0, KEY_FORMAT_3_5, SD_OFFSET, TYPE_STAT_DATA, SD_V1_SIZE, MAX_US_INT);
......@@ -1721,6 +1726,19 @@ int reiserfs_new_inode (struct reiserfs_transaction_handle *th,
goto out_inserted_sd;
}
/* XXX CHECK THIS */
if (reiserfs_posixacl (inode->i_sb)) {
retval = reiserfs_inherit_default_acl (dir, dentry, inode);
if (retval) {
err = retval;
reiserfs_check_path(&path_to_key) ;
journal_end(th, th->t_super, th->t_blocks_allocated);
goto out_inserted_sd;
}
} else if (inode->i_sb->s_flags & MS_POSIXACL) {
reiserfs_warning ("ACLs aren't enabled in the fs, but vfs thinks they are!\n");
}
insert_inode_hash (inode);
reiserfs_update_sd(th, inode);
reiserfs_check_path(&path_to_key) ;
......@@ -2565,6 +2583,11 @@ int reiserfs_setattr(struct dentry *dentry, struct iattr *attr) {
}
if (!error && reiserfs_posixacl (inode->i_sb)) {
if (attr->ia_valid & ATTR_MODE)
error = reiserfs_acl_chmod (inode);
}
out:
reiserfs_write_unlock(inode->i_sb);
return error ;
......
......@@ -15,6 +15,7 @@
#include <linux/time.h>
#include <linux/bitops.h>
#include <linux/reiserfs_fs.h>
#include <linux/reiserfs_acl.h>
#include <linux/reiserfs_xattr.h>
#include <linux/smp_lock.h>
......@@ -579,6 +580,7 @@ static int reiserfs_create (struct inode * dir, struct dentry *dentry, int mode,
struct inode * inode;
int jbegin_count = JOURNAL_PER_BALANCE_CNT * 2 ;
struct reiserfs_transaction_handle th ;
int locked;
if (!(inode = new_inode(dir->i_sb))) {
return -ENOMEM ;
......@@ -587,9 +589,19 @@ static int reiserfs_create (struct inode * dir, struct dentry *dentry, int mode,
if (retval)
return retval;
locked = reiserfs_cache_default_acl (dir);
reiserfs_write_lock(dir->i_sb);
if (locked)
reiserfs_write_lock_xattrs (dir->i_sb);
journal_begin(&th, dir->i_sb, jbegin_count) ;
retval = reiserfs_new_inode (&th, dir, mode, 0, 0/*i_size*/, dentry, inode);
if (locked)
reiserfs_write_unlock_xattrs (dir->i_sb);
if (retval) {
goto out_failed;
}
......@@ -625,6 +637,7 @@ static int reiserfs_mknod (struct inode * dir, struct dentry *dentry, int mode,
struct inode * inode;
struct reiserfs_transaction_handle th ;
int jbegin_count = JOURNAL_PER_BALANCE_CNT * 3;
int locked;
if (!new_valid_dev(rdev))
return -EINVAL;
......@@ -636,10 +649,20 @@ static int reiserfs_mknod (struct inode * dir, struct dentry *dentry, int mode,
if (retval)
return retval;
locked = reiserfs_cache_default_acl (dir);
reiserfs_write_lock(dir->i_sb);
if (locked)
reiserfs_write_lock_xattrs (dir->i_sb);
journal_begin(&th, dir->i_sb, jbegin_count) ;
retval = reiserfs_new_inode (&th, dir, mode, 0, 0/*i_size*/, dentry, inode);
if (locked)
reiserfs_write_unlock_xattrs (dir->i_sb);
if (retval) {
goto out_failed;
}
......@@ -678,6 +701,7 @@ static int reiserfs_mkdir (struct inode * dir, struct dentry *dentry, int mode)
struct inode * inode;
struct reiserfs_transaction_handle th ;
int jbegin_count = JOURNAL_PER_BALANCE_CNT * 3;
int locked;
#ifdef DISPLACE_NEW_PACKING_LOCALITIES
/* set flag that new packing locality created and new blocks for the content * of that directory are not displaced yet */
......@@ -691,7 +715,11 @@ static int reiserfs_mkdir (struct inode * dir, struct dentry *dentry, int mode)
if (retval)
return retval;
locked = reiserfs_cache_default_acl (dir);
reiserfs_write_lock(dir->i_sb);
if (locked)
reiserfs_write_lock_xattrs (dir->i_sb);
journal_begin(&th, dir->i_sb, jbegin_count) ;
/* inc the link count now, so another writer doesn't overflow it while
......@@ -703,6 +731,9 @@ static int reiserfs_mkdir (struct inode * dir, struct dentry *dentry, int mode)
old_format_only (dir->i_sb) ?
EMPTY_DIR_SIZE_V1 : EMPTY_DIR_SIZE,
dentry, inode);
if (locked)
reiserfs_write_unlock_xattrs (dir->i_sb);
if (retval) {
dir->i_nlink-- ;
goto out_failed;
......@@ -945,6 +976,8 @@ static int reiserfs_symlink (struct inode * parent_dir,
memcpy (name, symname, strlen (symname));
padd_item (name, item_len, strlen (symname));
/* We would inherit the default ACL here, but symlinks don't get ACLs */
journal_begin(&th, parent_dir->i_sb, jbegin_count) ;
retval = reiserfs_new_inode (&th, parent_dir, mode, name, strlen (symname),
......
......@@ -17,6 +17,7 @@
#include <linux/time.h>
#include <asm/uaccess.h>
#include <linux/reiserfs_fs.h>
#include <linux/reiserfs_acl.h>
#include <linux/reiserfs_xattr.h>
#include <linux/smp_lock.h>
#include <linux/init.h>
......@@ -433,6 +434,8 @@ static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags)
SLAB_CTOR_CONSTRUCTOR) {
INIT_LIST_HEAD(&ei->i_prealloc_list) ;
inode_init_once(&ei->vfs_inode);
ei->i_acl_access = NULL;
ei->i_acl_default = NULL;
}
}
......@@ -473,6 +476,22 @@ static void reiserfs_dirty_inode (struct inode * inode) {
reiserfs_write_unlock(inode->i_sb);
}
static void reiserfs_clear_inode (struct inode *inode)
{
struct posix_acl *acl;
acl = REISERFS_I(inode)->i_acl_access;
if (acl && !IS_ERR (acl))
posix_acl_release (acl);
REISERFS_I(inode)->i_acl_access = NULL;
acl = REISERFS_I(inode)->i_acl_default;
if (acl && !IS_ERR (acl))
posix_acl_release (acl);
REISERFS_I(inode)->i_acl_default = NULL;
}
struct super_operations reiserfs_sops =
{
.alloc_inode = reiserfs_alloc_inode,
......@@ -480,6 +499,7 @@ struct super_operations reiserfs_sops =
.write_inode = reiserfs_write_inode,
.dirty_inode = reiserfs_dirty_inode,
.delete_inode = reiserfs_delete_inode,
.clear_inode = reiserfs_clear_inode,
.put_super = reiserfs_put_super,
.write_super = reiserfs_write_super,
.write_super_lockfs = reiserfs_write_super_lockfs,
......@@ -682,6 +702,10 @@ static int reiserfs_parse_options (struct super_block * s, char * options, /* st
{"noattrs", 0, 0, 0, 1<<REISERFS_ATTRS},
{"user_xattr", 0, 0, 1<<REISERFS_XATTRS_USER, 0},
{"nouser_xattr", 0, 0, 0, 1<<REISERFS_XATTRS_USER},
#ifdef CONFIG_REISERFS_FS_POSIX_ACL
{"acl", 0, 0, 1<<REISERFS_POSIXACL, 0},
{"noacl", 0, 0, 0, 1<<REISERFS_POSIXACL},
#endif
{"nolog", 0, 0, 0, 0}, /* This is unsupported */
{"replayonly", 0, 0, 1<<REPLAYONLY, 0},
{"block-allocator", 'a', balloc, 0, 0},
......@@ -827,6 +851,7 @@ static int reiserfs_remount (struct super_block * s, int * mount_flags, char * a
safe_mask |= 1 << REISERFS_TEST4;
safe_mask |= 1 << REISERFS_ATTRS;
safe_mask |= 1 << REISERFS_XATTRS_USER;
safe_mask |= 1 << REISERFS_POSIXACL;
/* Update the bitmask, taking care to keep
* the bits we're not allowed to change here */
......
......@@ -6,7 +6,7 @@
*/
/*
* In order to implement EAs in a clean, backwards compatible manner,
* In order to implement EA/ACLs in a clean, backwards compatible manner,
* they are implemented as files in a "private" directory.
* Each EA is in it's own file, with the directory layout like so (/ is assumed
* to be relative to fs root). Inside the /.reiserfs_priv/xattrs directory,
......@@ -15,12 +15,18 @@
* named with the name of the extended attribute.
*
* So, for objectid 12648430, we could have:
* /.reiserfs_priv/xattrs/C0FFEE.0/system.posix_acl_access
* /.reiserfs_priv/xattrs/C0FFEE.0/system.posix_acl_default
* /.reiserfs_priv/xattrs/C0FFEE.0/user.Content-Type
* .. or similar.
*
* The file contents are the text of the EA. The size is known based on the
* stat data describing the file.
*
* In the case of system.posix_acl_access and system.posix_acl_default, since
* these are special cases for filesystem ACLs, they are interpreted by the
* kernel, in addition, they are negatively and positively cached and attached
* to the inode so that unnecessary lookups are avoided.
*/
#include <linux/reiserfs_fs.h>
......@@ -32,6 +38,7 @@
#include <linux/pagemap.h>
#include <linux/xattr.h>
#include <linux/reiserfs_xattr.h>
#include <linux/reiserfs_acl.h>
#include <linux/mbcache.h>
#include <asm/uaccess.h>
#include <asm/checksum.h>
......@@ -1169,6 +1176,10 @@ reiserfs_xattr_register_handlers (void)
/* Add the handlers */
list_add_tail (&user_handler.handlers, &xattr_handlers);
#ifdef CONFIG_REISERFS_FS_POSIX_ACL
list_add_tail (&posix_acl_access_handler.handlers, &xattr_handlers);
list_add_tail (&posix_acl_default_handler.handlers, &xattr_handlers);
#endif
/* Run initializers, if available */
list_for_each (p, &xattr_handlers) {
......@@ -1231,7 +1242,7 @@ reiserfs_xattr_init (struct super_block *s, int mount_flags)
} else if (reiserfs_xattrs_optional (s)) {
/* Old format filesystem, but optional xattrs have been enabled
* at mount time. Error out. */
reiserfs_warning ("reiserfs: xattrs not supported on pre v3.6 "
reiserfs_warning ("reiserfs: xattrs/ACLs not supported on pre v3.6 "
"format filesystem. Failing mount.\n");
err = -EOPNOTSUPP;
goto error;
......@@ -1276,7 +1287,7 @@ reiserfs_xattr_init (struct super_block *s, int mount_flags)
/* If we're read-only it just means that the dir hasn't been
* created. Not an error -- just no xattrs on the fs. We'll
* check again if we go read-write */
reiserfs_warning ("reiserfs: xattrs enabled and couldn't "
reiserfs_warning ("reiserfs: xattrs/ACLs enabled and couldn't "
"find/create .reiserfs_priv. Failing mount.\n");
err = -EOPNOTSUPP;
}
......@@ -1288,12 +1299,20 @@ reiserfs_xattr_init (struct super_block *s, int mount_flags)
if (err) {
clear_bit (REISERFS_XATTRS, &(REISERFS_SB(s)->s_mount_opt));
clear_bit (REISERFS_XATTRS_USER, &(REISERFS_SB(s)->s_mount_opt));
clear_bit (REISERFS_POSIXACL, &(REISERFS_SB(s)->s_mount_opt));
}
/* The super_block MS_POSIXACL must mirror the (no)acl mount option. */
s->s_flags = s->s_flags & ~MS_POSIXACL;
if (reiserfs_posixacl (s))
s->s_flags |= MS_POSIXACL;
return err;
}
int
reiserfs_permission (struct inode *inode, int mask, struct nameidata *nd)
static int
__reiserfs_permission (struct inode *inode, int mask, struct nameidata *nd,
int need_lock)
{
umode_t mode = inode->i_mode;
......@@ -1317,10 +1336,45 @@ reiserfs_permission (struct inode *inode, int mask, struct nameidata *nd)
if (is_reiserfs_priv_object (inode))
return 0;
if (current->fsuid == inode->i_uid)
if (current->fsuid == inode->i_uid) {
mode >>= 6;
else if (in_group_p(inode->i_gid))
mode >>= 3;
#ifdef CONFIG_REISERFS_FS_POSIX_ACL
} else if (reiserfs_posixacl(inode->i_sb) &&
get_inode_sd_version (inode) != STAT_DATA_V1) {
struct posix_acl *acl;
/* ACL can't contain additional permissions if
the ACL_MASK entry is 0 */
if (!(mode & S_IRWXG))
goto check_groups;
if (need_lock)
reiserfs_read_lock_xattrs (inode->i_sb);
acl = reiserfs_get_acl (inode, ACL_TYPE_ACCESS);
if (need_lock)
reiserfs_read_unlock_xattrs (inode->i_sb);
if (IS_ERR (acl)) {
if (PTR_ERR (acl) == -ENODATA)
goto check_groups;
return PTR_ERR (acl);
}
if (acl) {
int err = posix_acl_permission (inode, acl, mask);
posix_acl_release (acl);
if (err == -EACCES) {
goto check_capabilities;
}
return err;
} else {
goto check_groups;
}
#endif
} else {
check_groups:
if (in_group_p(inode->i_gid))
mode >>= 3;
}
/*
* If the DACs are ok we don't need any capability check.
......@@ -1328,6 +1382,7 @@ reiserfs_permission (struct inode *inode, int mask, struct nameidata *nd)
if (((mode & mask & (MAY_READ|MAY_WRITE|MAY_EXEC)) == mask))
return 0;
check_capabilities:
/*
* Read/write DACs are always overridable.
* Executable DACs are overridable if at least one exec bit is set.
......@@ -1344,5 +1399,16 @@ reiserfs_permission (struct inode *inode, int mask, struct nameidata *nd)
return 0;
return -EACCES;
}
int
reiserfs_permission (struct inode *inode, int mask, struct nameidata *nd)
{
return __reiserfs_permission (inode, mask, nd, 1);
}
int
reiserfs_permission_locked (struct inode *inode, int mask, struct nameidata *nd)
{
return __reiserfs_permission (inode, mask, nd, 0);
}
This diff is collapsed.
......@@ -6,6 +6,10 @@
#include <linux/reiserfs_xattr.h>
#include <asm/uaccess.h>
#ifdef CONFIG_REISERFS_FS_POSIX_ACL
# include <linux/reiserfs_acl.h>
#endif
#define XATTR_USER_PREFIX "user."
static int
......@@ -20,7 +24,7 @@ user_get (struct inode *inode, const char *name, void *buffer, size_t size)
if (!reiserfs_xattrs_user (inode->i_sb))
return -EOPNOTSUPP;
error = permission (inode, MAY_READ, NULL);
error = reiserfs_permission_locked (inode, MAY_READ, NULL);
if (error)
return error;
......@@ -44,7 +48,7 @@ user_set (struct inode *inode, const char *name, const void *buffer,
(!S_ISDIR (inode->i_mode) || inode->i_mode & S_ISVTX))
return -EPERM;
error = permission (inode, MAY_WRITE, NULL);
error = reiserfs_permission_locked (inode, MAY_WRITE, NULL);
if (error)
return error;
......@@ -66,7 +70,7 @@ user_del (struct inode *inode, const char *name)
(!S_ISDIR (inode->i_mode) || inode->i_mode & S_ISVTX))
return -EPERM;
error = permission (inode, MAY_WRITE, NULL);
error = reiserfs_permission_locked (inode, MAY_WRITE, NULL);
if (error)
return error;
......
#include <linux/init.h>
#include <linux/posix_acl.h>
#include <linux/xattr_acl.h>
#define REISERFS_ACL_VERSION 0x0001
typedef struct {
__u16 e_tag;
__u16 e_perm;
__u32 e_id;
} reiserfs_acl_entry;
typedef struct {
__u16 e_tag;
__u16 e_perm;
} reiserfs_acl_entry_short;
typedef struct {
__u32 a_version;
} reiserfs_acl_header;
static inline size_t reiserfs_acl_size(int count)
{
if (count <= 4) {
return sizeof(reiserfs_acl_header) +
count * sizeof(reiserfs_acl_entry_short);
} else {
return sizeof(reiserfs_acl_header) +
4 * sizeof(reiserfs_acl_entry_short) +
(count - 4) * sizeof(reiserfs_acl_entry);
}
}
static inline int reiserfs_acl_count(size_t size)
{
ssize_t s;
size -= sizeof(reiserfs_acl_header);
s = size - 4 * sizeof(reiserfs_acl_entry_short);
if (s < 0) {
if (size % sizeof(reiserfs_acl_entry_short))
return -1;
return size / sizeof(reiserfs_acl_entry_short);
} else {
if (s % sizeof(reiserfs_acl_entry))
return -1;
return s / sizeof(reiserfs_acl_entry) + 4;
}
}
#ifdef CONFIG_REISERFS_FS_POSIX_ACL
struct posix_acl * reiserfs_get_acl(struct inode *inode, int type);
int reiserfs_set_acl(struct inode *inode, int type, struct posix_acl *acl);
int reiserfs_acl_chmod (struct inode *inode);
int reiserfs_inherit_default_acl (struct inode *dir, struct dentry *dentry, struct inode *inode);
int reiserfs_cache_default_acl (struct inode *dir);
extern int reiserfs_xattr_posix_acl_init (void) __init;
extern int reiserfs_xattr_posix_acl_exit (void);
extern struct reiserfs_xattr_handler posix_acl_default_handler;
extern struct reiserfs_xattr_handler posix_acl_access_handler;
#else
#define reiserfs_set_acl NULL
#define reiserfs_get_acl NULL
#define reiserfs_cache_default_acl(inode) 0
static inline int
reiserfs_xattr_posix_acl_init (void)
{
return 0;
}
static inline int
reiserfs_xattr_posix_acl_exit (void)
{
return 0;
}
static inline int
reiserfs_acl_chmod (struct inode *inode)
{
return 0;
}
static inline int
reiserfs_inherit_default_acl (const struct inode *dir, struct dentry *dentry, struct inode *inode)
{
return 0;
}
#endif
......@@ -52,6 +52,9 @@ struct reiserfs_inode_info {
** flushed */
unsigned long i_trans_id ;
struct reiserfs_journal_list *i_jl;
struct posix_acl *i_acl_access;
struct posix_acl *i_acl_default;
struct inode vfs_inode;
};
......
......@@ -443,6 +443,7 @@ enum reiserfs_mount_options {
REISERFS_ATTRS,
REISERFS_XATTRS,
REISERFS_XATTRS_USER,
REISERFS_POSIXACL,
REISERFS_TEST1,
REISERFS_TEST2,
......@@ -470,7 +471,8 @@ enum reiserfs_mount_options {
#define reiserfs_data_writeback(s) (REISERFS_SB(s)->s_mount_opt & (1 << REISERFS_DATA_WRITEBACK))
#define reiserfs_xattrs(s) (REISERFS_SB(s)->s_mount_opt & (1 << REISERFS_XATTRS))
#define reiserfs_xattrs_user(s) (REISERFS_SB(s)->s_mount_opt & (1 << REISERFS_XATTRS_USER))
#define reiserfs_xattrs_optional(s) reiserfs_xattrs_user(s)
#define reiserfs_posixacl(s) (REISERFS_SB(s)->s_mount_opt & (1 << REISERFS_POSIXACL))
#define reiserfs_xattrs_optional(s) (reiserfs_xattrs_user(s) || reiserfs_posixacl(s))
void reiserfs_file_buffer (struct buffer_head * bh, int list);
extern struct file_system_type reiserfs_fs_type;
......
......@@ -42,6 +42,7 @@ int reiserfs_delete_xattrs (struct inode *inode);
int reiserfs_chown_xattrs (struct inode *inode, struct iattr *attrs);
int reiserfs_xattr_init (struct super_block *sb, int mount_flags);
int reiserfs_permission (struct inode *inode, int mask, struct nameidata *nd);
int reiserfs_permission_locked (struct inode *inode, int mask, struct nameidata *nd);
int reiserfs_xattr_del (struct inode *, const char *);
int reiserfs_xattr_get (const struct inode *, const char *, void *, size_t);
......@@ -94,7 +95,11 @@ reiserfs_read_unlock_xattrs(struct super_block *sb)
static inline int reiserfs_delete_xattrs (struct inode *inode) { return 0; };
static inline int reiserfs_chown_xattrs (struct inode *inode, struct iattr *attrs) { return 0; };
static inline int reiserfs_xattr_init (struct super_block *sb, int mount_flags) { return 0; };
static inline int reiserfs_xattr_init (struct super_block *sb, int mount_flags)
{
sb->s_flags = (sb->s_flags & ~MS_POSIXACL); /* to be sure */
return 0;
};
#endif
#endif /* __KERNEL__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment