Ok, let it be as you decide. But maybe this last argument will change your
mind. Imagine a user who has two machines, say, A and B. The machine A has
a PFS mounted to /home (or /home/user) and B has a slave mirror of that PFS
mounted to /backup/user. If that user wants to update his mirror on B, he
must login as root and launch 'hammer mirror-copy /home/user
B:/backup/user' which means he must run hammer utility as root and also ssh
as root on both machines. Is it 100% secure to have a root access over ssh?
Has not ssh any yet unseen bugs? I propose something like that:

On A:
su                            (it will be necessary as only root has the
right to change permissions)
<root password>
hammer -u user add-perm /home mirror-read

On B:
su
<root password>
hammer -u user add-perm /backup/home mirror-write

And now the mirror can be updated not only by root, but also by user. As
the permissions are set on per-PFS basis, user will not be able to make a
mirror of any PFS, but only of his own home. zfs allow/unallow works the
same way.

Here is a résumé:
1) Only root has the right to change permissions.
2) Only root has the right to make any changes to filesystem, which are not
on per-PFS basis (like volume management, upgrading filesystem to a new
version).
3) Permissions a per-PFS.

I attach a proof-of-concept. Apply this patch to DragonFly_RELEASE_4_0. You
will be able to set the following permissions: mirror-read, mirror-write,
snap-add, snap-del as shown above. Three new commands are added to hammer
utility: add-perm, del-perm, show-perm. Let me know, if you are interested.

2015-10-01 6:56 GMT+03:00 Matthew Dillon <dil...@backplane.com>:

> Well, I think its a bit too dangerous to give snapshotting power to the
> user in this case.  The snapshots are managed on a per-PFS  basis so the
> user would be able to interfere with whatever root intended on doing with
> the capability.
>
> -Matt
>
> On Sat, Sep 26, 2015 at 7:06 AM, Vasily Postnicov <shamaz.ma...@gmail.com>
> wrote:
>
>> Hello. I have noticed, that some ioctls, like HAMMERIOC_GETHISTORY or
>> HAMMERIOC_GET_INFO can be made by any user, and there are some like
>> HAMMERIOC_ADD_SNAPSHOT, which only root can do. I find this somewhat
>> "unfair", because why a user cannot, for example, make a snapshot of his
>> own home directory, if there is a PFS mounted to that directory? I think
>> something like zfs allow/unallow is needed here. Any ideas how I can
>> implement this?
>>
>> Maybe I should add a new record type to vfs/hammer/hammer_disk.h, say
>> HAMMER_RECTYPE_PERM, and use it in the similar way to
>> HAMMER_RECTYPE_CONFIG, like writing functions similar to
>> hammer_ioc_get/set_config? So when a user calls ioctl() it will be like
>> this in the kernel space:
>>
>> 1) Start a new transactions and initialize a cursor.
>> 2) setup the cursor. Set cursor.key_beg.rec_type = HAMMER_RECTYPE_PERM;
>> 3) do hammer_btree_lookup(&cursor);
>> 4) If lookup succeeded, extract permission info and act accordingly to it.
>>
>> So what you think? Will it work? Maybe I need to cache the results
>> somehow and do not call hammer_btree_lookup() each time ioctl is called? Or
>> it is already done automatically?
>>
>
>
diff --git a/sbin/hammer/Makefile b/sbin/hammer/Makefile
index 3578807..4d2aa3f 100644
--- a/sbin/hammer/Makefile
+++ b/sbin/hammer/Makefile
@@ -5,7 +5,7 @@ SRCS=	hammer.c ondisk.c blockmap.c cache.c misc.c cycle.c \
 	cmd_synctid.c cmd_stats.c cmd_remote.c \
 	cmd_pseudofs.c cmd_snapshot.c cmd_mirror.c \
 	cmd_cleanup.c cmd_info.c cmd_version.c cmd_volume.c \
-	cmd_config.c cmd_recover.c cmd_dedup.c
+	cmd_config.c cmd_recover.c cmd_dedup.c cmd_perm.c
 MAN=	hammer.8
 
 CFLAGS+= -I${.CURDIR}/../../sys -DALIST_NO_DEBUG
diff --git a/sbin/hammer/cmd_perm.c b/sbin/hammer/cmd_perm.c
new file mode 100644
index 0000000..ebb662f
--- /dev/null
+++ b/sbin/hammer/cmd_perm.c
@@ -0,0 +1,128 @@
+#include <pwd.h>
+#include "hammer.h"
+
+static const struct {
+    const char *command;
+    const char *desc;
+    u_int64_t permission;
+} perm_command[] = {
+    {"snap-add", "Snapshot creation", HAMMER_PERM_ADD_SNAPSHOT},
+    {"snap-del", "Snapshot deletion", HAMMER_PERM_DEL_SNAPSHOT},
+    {"mirror-write", "Mirror write",  HAMMER_PERM_MIRROR_WRITE},
+    {"mirror-read", "Mirror read",    HAMMER_PERM_MIRROR_READ},
+};
+
+static void perm_usage(void);
+static int get_perm (const char *cmd);
+
+void hammer_cmd_show_perm(char **av, int ac)
+{
+    int fd;
+    unsigned int i;
+    int error = 0;
+    struct hammer_ioc_perm perm;
+    struct passwd *pass;
+
+    if (!(UserOpt) && !(GroupOpt))
+        perm_usage();
+
+    if (ac != 1)
+        perm_usage();
+
+    if (UserOpt) {
+        pass = getpwnam (UserOpt);
+        if (pass == NULL) {
+            err (2, "show-perm: Cannot get user info");
+        }
+        perm.uid = pass->pw_uid;
+    }
+
+    if (GroupOpt) {
+        fprintf (stderr, "Not implemented yet\n");
+        exit (1);
+    }
+
+    fd = open (av[0], O_RDONLY);
+    if (fd < 0)
+        err(2, "Unable to open %s", av[0]);
+    if (ioctl (fd, HAMMERIOC_GET_PERM, &perm))
+        error = errno;
+    if (perm.head.error)
+        error = perm.head.error;
+    close (fd);
+    if (error)
+        errx(1, "show-perm %s failed: %s", av[0], strerror (error));
+
+    printf ("User permissions:\n");
+    for (i=0; i<sizeof(perm_command)/sizeof(perm_command[0]); i++) {
+        if ((perm_command[i].permission & perm.perm) == perm_command[i].permission)
+            printf ("%s\n", perm_command[i].desc);
+    }
+}
+
+void hammer_cmd_change_perm(char **av, int ac, int add)
+{
+    int fd;
+    int error = 0;
+    struct hammer_ioc_perm perm;
+    struct passwd *pass;
+
+    if (!(UserOpt) && !(GroupOpt))
+        perm_usage();
+
+    if (ac != 2)
+        perm_usage();
+
+    if (UserOpt) {
+        pass = getpwnam (UserOpt);
+        if (pass == NULL) {
+            err (2, "%s: Cannot get user info", (add) ? "add-perm" : "del-perm");
+        }
+        perm.uid = pass->pw_uid;
+    }
+
+    if (GroupOpt) {
+        fprintf (stderr, "Not implemented yet\n");
+        exit (1);
+    }
+
+    fd = open (av[0], O_RDONLY);
+    if (fd < 0)
+        err(2, "Unable to open %s", av[0]);
+
+    perm.changed_perm = get_perm (av[1]);
+    if (ioctl (fd, (add) ? HAMMERIOC_ADD_PERM : HAMMERIOC_DEL_PERM, &perm))
+        error = errno;
+    if (perm.head.error)
+        error = perm.head.error;
+    close (fd);
+    if (error)
+        errx(1, "%s %s failed: %s", (add) ? "add-perm" : "del-perm",
+             av[0], strerror (error));
+}
+
+static int get_perm (const char *cmd)
+{
+    unsigned int i;
+    for (i=0; i<sizeof(perm_command)/sizeof(perm_command[0]); i++) {
+        if (strcmp (cmd, perm_command[i].command) == 0)
+            return perm_command[i].permission;
+    }
+    fprintf (stderr, "%s: no such permission\n", cmd);
+    perm_usage ();
+    return 0;
+}
+
+static void perm_usage(void)
+{
+    unsigned i;
+
+    fprintf (stderr, "hammer -u user | -g group show-perm <filesystem>\n");
+    fprintf (stderr, "hammer -u user | -g group add-perm <filesystem> <perm>\n");
+    fprintf (stderr, "hammer -u user | -g group del-perm <filesystem> <perm>\n");
+    fprintf (stderr, "Available permissions are:\n");
+    for (i=0; i<sizeof(perm_command)/sizeof(perm_command[0]); i++) {
+        fprintf (stderr, "    %s: %s\n", perm_command[i].command, perm_command[i].desc);
+    }
+    exit (1);
+}
diff --git a/sbin/hammer/hammer.c b/sbin/hammer/hammer.c
index 2db8f96..187d2fd 100644
--- a/sbin/hammer/hammer.c
+++ b/sbin/hammer/hammer.c
@@ -58,6 +58,8 @@ int ForceOpt;
 int RunningIoctl;
 int DidInterrupt;
 int BulkOpt;
+char *UserOpt;
+char *GroupOpt;
 u_int64_t BandwidthOpt;
 u_int64_t SplitupOpt = 4ULL * 1024ULL * 1024ULL * 1024ULL;
 u_int64_t MemoryLimit = 1024LLU * 1024 * 1024;
@@ -77,7 +79,7 @@ main(int ac, char **av)
 	int cacheSize = 0;
 
 	while ((ch = getopt(ac, av,
-			    "b:c:de:hf:i:m:p:qrs:t:v2yBC:FR:S:T:X")) != -1) {
+			    "b:c:de:hf:i:m:p:qrs:t:v2yBC:FR:S:T:Xu:g:")) != -1) {
 		switch(ch) {
 		case '2':
 			TwoWayPipeOpt = 1;
@@ -249,6 +251,12 @@ main(int ac, char **av)
 		case 'X':
 			CompressOpt = 1;
 			break;
+        case 'u':
+            UserOpt = optarg;
+            break;
+        case 'g':
+            GroupOpt = optarg;
+            break;
 		default:
 			usage(1);
 			/* not reached */
@@ -552,6 +560,18 @@ main(int ac, char **av)
 		hammer_cmd_checkmap();
 		exit(0);
 	}
+    if (strcmp(av[0], "add-perm") == 0) {
+		hammer_cmd_change_perm(av + 1, ac - 1, 1);
+		exit(0);
+	}
+    if (strcmp(av[0], "del-perm") == 0) {
+		hammer_cmd_change_perm(av + 1, ac - 1, 0);
+		exit(0);
+	}
+    if (strcmp(av[0], "show-perm") == 0) {
+		hammer_cmd_show_perm(av + 1, ac - 1);
+		exit(0);
+	}
 	usage(1);
 	/* not reached */
 	return(0);
@@ -652,6 +672,9 @@ usage(int exit_code)
 		"hammer volume-add <device> <filesystem>\n"
 		"hammer volume-del <device> <filesystem>\n"
 		"hammer volume-list <filesystem>\n"
+        "hammer show-perm -u user | -g group <filesystem>\n"
+        "hammer add-perm -u user | -g group <filesystem> <perm>\n"
+        "hammer del-perm -u user | -g group <filesystem> <perm>\n"
 	);
 
 	fprintf(stderr, "\nHAMMER utility version 3+ commands:\n");
diff --git a/sbin/hammer/hammer.h b/sbin/hammer/hammer.h
index d66fd4b..bfc4d8e 100644
--- a/sbin/hammer/hammer.h
+++ b/sbin/hammer/hammer.h
@@ -84,6 +84,8 @@ extern int RunningIoctl;
 extern int DidInterrupt;
 extern int ForceOpt;
 extern int BulkOpt;
+extern char *UserOpt;
+extern char *GroupOpt;
 extern u_int64_t BandwidthOpt;
 extern u_int64_t SplitupOpt;
 extern u_int64_t MemoryLimit;
@@ -132,6 +134,8 @@ void hammer_cmd_volume_del(char **av, int ac);
 void hammer_cmd_volume_list(char **av, int ac);
 void hammer_cmd_dedup_simulate(char **av, int ac);
 void hammer_cmd_dedup(char **av, int ac);
+void hammer_cmd_change_perm(char **av, int ac, int add);
+void hammer_cmd_show_perm(char **av, int ac);
 
 void hammer_get_cycle(hammer_base_elm_t base, hammer_tid_t *tidp);
 void hammer_set_cycle(hammer_base_elm_t base, hammer_tid_t tid);
diff --git a/sys/conf/files b/sys/conf/files
index 68fecf1..afc51cb 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1632,6 +1632,7 @@ vfs/hammer/hammer_redo.c	optional hammer
 vfs/hammer/hammer_vfsops.c	optional hammer
 vfs/hammer/hammer_vnops.c	optional hammer
 vfs/hammer/hammer_dedup.c	optional hammer
+vfs/hammer/hammer_perm.c        optional hammer
 vfs/puffs/puffs_io.c		optional puffs
 vfs/puffs/puffs_msgif.c		optional puffs
 vfs/puffs/puffs_node.c		optional puffs
diff --git a/sys/sys/priv.h b/sys/sys/priv.h
index ee3471d..670beed 100644
--- a/sys/sys/priv.h
+++ b/sys/sys/priv.h
@@ -470,7 +470,6 @@
  * Hammer privileges.
  */
 #define PRIV_HAMMER_IOCTL	650	/* can hammer_ioctl(). */
-#define PRIV_HAMMER_VOLUME	651	/* HAMMER volume management */
 
 /*
  * Track end of privilege list.
diff --git a/sys/vfs/hammer/Makefile b/sys/vfs/hammer/Makefile
index 4d33e40..75be32b 100644
--- a/sys/vfs/hammer/Makefile
+++ b/sys/vfs/hammer/Makefile
@@ -11,7 +11,7 @@ SRCS=	hammer_vfsops.c hammer_vnops.c hammer_inode.c \
 	hammer_reblock.c hammer_rebalance.c \
 	hammer_flusher.c hammer_mirror.c \
 	hammer_pfs.c hammer_prune.c hammer_volume.c \
-	hammer_dedup.c
+	hammer_dedup.c hammer_perm.c
 SRCS+=	opt_ktr.h
 
 .include <bsd.kmod.mk>
diff --git a/sys/vfs/hammer/hammer.h b/sys/vfs/hammer/hammer.h
index 151f1c5..c17971b 100644
--- a/sys/vfs/hammer/hammer.h
+++ b/sys/vfs/hammer/hammer.h
@@ -1425,6 +1425,8 @@ int  hammer_unload_pseudofs(hammer_transaction_t trans, u_int32_t localization);
 void hammer_rel_pseudofs(hammer_mount_t hmp, hammer_pseudofs_inmem_t pfsm);
 int hammer_ioctl(hammer_inode_t ip, u_long com, caddr_t data, int fflag,
 			struct ucred *cred);
+int hammer_checkperm(hammer_transaction_t trans, hammer_inode_t ip,
+                     u_long com, struct ucred *cred);
 
 void hammer_io_init(hammer_io_t io, hammer_volume_t volume,
 			enum hammer_io_type type);
@@ -1492,6 +1494,12 @@ int hammer_ioc_volume_list(hammer_transaction_t trans, hammer_inode_t ip,
 			struct hammer_ioc_volume_list *ioc);
 int hammer_ioc_dedup(hammer_transaction_t trans, hammer_inode_t ip,
 			struct hammer_ioc_dedup *dedup);
+int hammer_ioc_get_perm(hammer_transaction_t trans, hammer_inode_t ip,
+                        struct hammer_ioc_perm *perm);
+int hammer_ioc_add_perm(hammer_transaction_t trans, hammer_inode_t ip,
+                        struct hammer_ioc_perm *perm);
+int hammer_ioc_del_perm(hammer_transaction_t trans, hammer_inode_t ip,
+                        struct hammer_ioc_perm *perm);
 
 int hammer_signal_check(hammer_mount_t hmp);
 
diff --git a/sys/vfs/hammer/hammer_disk.h b/sys/vfs/hammer/hammer_disk.h
index 136aa1c..18f97f5 100644
--- a/sys/vfs/hammer/hammer_disk.h
+++ b/sys/vfs/hammer/hammer_disk.h
@@ -673,6 +673,7 @@ typedef struct hammer_volume_ondisk *hammer_volume_ondisk_t;
 #define HAMMER_RECTYPE_PFS		0x0015	/* PFS management */
 #define HAMMER_RECTYPE_SNAPSHOT		0x0016	/* Snapshot management */
 #define HAMMER_RECTYPE_CONFIG		0x0017	/* hammer cleanup config */
+#define HAMMER_RECTYPE_PERM         0x0018  /* ioctl() per-PFS permissions */
 #define HAMMER_RECTYPE_MOVED		0x8000	/* special recovery flag */
 #define HAMMER_RECTYPE_MAX		0xFFFF
 
@@ -885,6 +886,17 @@ struct hammer_config_data {
 };
 
 /*
+ * Per-PFS ioctl() permissions for a specific user.
+ * { ObjId = HAMMER_OBJID_ROOT, Key = <uid>, rectype = PERM }.
+ * This data is not mirrored, like CONFIG data
+ */
+#define HAMMER_PERM_ADD_SNAPSHOT 1
+#define HAMMER_PERM_DEL_SNAPSHOT 2
+#define HAMMER_PERM_MIRROR_READ  4
+#define HAMMER_PERM_MIRROR_WRITE 8
+#define HAMMER_MAX_PERM_MASK 15
+
+/*
  * Rollup various structures embedded as record data
  */
 union hammer_data_ondisk {
@@ -894,6 +906,7 @@ union hammer_data_ondisk {
 	struct hammer_pseudofs_data pfsd;
 	struct hammer_snapshot_data snap;
 	struct hammer_config_data config;
+    u_int64_t perm;
 };
 
 typedef union hammer_data_ondisk *hammer_data_ondisk_t;
diff --git a/sys/vfs/hammer/hammer_ioctl.c b/sys/vfs/hammer/hammer_ioctl.c
index 0eea44f..1fef9e6 100644
--- a/sys/vfs/hammer/hammer_ioctl.c
+++ b/sys/vfs/hammer/hammer_ioctl.c
@@ -71,25 +71,26 @@ hammer_ioctl(hammer_inode_t ip, u_long com, caddr_t data, int fflag,
 	int error;
 
 	error = priv_check_cred(cred, PRIV_HAMMER_IOCTL, 0);
-
 	hammer_start_transaction(&trans, ip->hmp);
+    if (error)
+        error = hammer_checkperm(&trans, ip, com, cred);
+    if (error) {
+        hammer_done_transaction(&trans);
+        return error;
+    }
 
 	switch(com) {
 	case HAMMERIOC_PRUNE:
-		if (error == 0) {
-			error = hammer_ioc_prune(&trans, ip,
+        error = hammer_ioc_prune(&trans, ip,
 					(struct hammer_ioc_prune *)data);
-		}
 		break;
 	case HAMMERIOC_GETHISTORY:
 		error = hammer_ioc_gethistory(&trans, ip,
 					(struct hammer_ioc_history *)data);
 		break;
 	case HAMMERIOC_REBLOCK:
-		if (error == 0) {
-			error = hammer_ioc_reblock(&trans, ip,
+        error = hammer_ioc_reblock(&trans, ip,
 					(struct hammer_ioc_reblock *)data);
-		}
 		break;
 	case HAMMERIOC_REBALANCE:
 		/*
@@ -97,7 +98,7 @@ hammer_ioctl(hammer_inode_t ip, u_long com, caddr_t data, int fflag,
 		 * children and children's children.  Systems with very
 		 * little memory will not be able to do it.
 		 */
-		if (error == 0 && nbuf < HAMMER_REBALANCE_MIN_BUFS) {
+		if (nbuf < HAMMER_REBALANCE_MIN_BUFS) {
 			kprintf("hammer: System has insufficient buffers "
 				"to rebalance the tree.  nbuf < %d\n",
 				HAMMER_REBALANCE_MIN_BUFS);
@@ -117,46 +118,32 @@ hammer_ioctl(hammer_inode_t ip, u_long com, caddr_t data, int fflag,
 				    (struct hammer_ioc_pseudofs_rw *)data);
 		break;
 	case HAMMERIOC_SET_PSEUDOFS:
-		if (error == 0) {
-			error = hammer_ioc_set_pseudofs(&trans, ip, cred,
+        error = hammer_ioc_set_pseudofs(&trans, ip, cred,
 				    (struct hammer_ioc_pseudofs_rw *)data);
-		}
 		break;
 	case HAMMERIOC_UPG_PSEUDOFS:
-		if (error == 0) {
-			error = hammer_ioc_upgrade_pseudofs(&trans, ip, 
+        error = hammer_ioc_upgrade_pseudofs(&trans, ip, 
 				    (struct hammer_ioc_pseudofs_rw *)data);
-		}
 		break;
 	case HAMMERIOC_DGD_PSEUDOFS:
-		if (error == 0) {
-			error = hammer_ioc_downgrade_pseudofs(&trans, ip,
+        error = hammer_ioc_downgrade_pseudofs(&trans, ip,
 				    (struct hammer_ioc_pseudofs_rw *)data);
-		}
 		break;
 	case HAMMERIOC_RMR_PSEUDOFS:
-		if (error == 0) {
-			error = hammer_ioc_destroy_pseudofs(&trans, ip,
+        error = hammer_ioc_destroy_pseudofs(&trans, ip,
 				    (struct hammer_ioc_pseudofs_rw *)data);
-		}
 		break;
 	case HAMMERIOC_WAI_PSEUDOFS:
-		if (error == 0) {
-			error = hammer_ioc_wait_pseudofs(&trans, ip,
+        error = hammer_ioc_wait_pseudofs(&trans, ip,
 				    (struct hammer_ioc_pseudofs_rw *)data);
-		}
 		break;
 	case HAMMERIOC_MIRROR_READ:
-		if (error == 0) {
-			error = hammer_ioc_mirror_read(&trans, ip,
+        error = hammer_ioc_mirror_read(&trans, ip,
 				    (struct hammer_ioc_mirror_rw *)data);
-		}
 		break;
 	case HAMMERIOC_MIRROR_WRITE:
-		if (error == 0) {
-			error = hammer_ioc_mirror_write(&trans, ip,
+        error = hammer_ioc_mirror_write(&trans, ip,
 				    (struct hammer_ioc_mirror_rw *)data);
-		}
 		break;
 	case HAMMERIOC_GET_VERSION:
 		error = hammer_ioc_get_version(&trans, ip, 
@@ -167,42 +154,28 @@ hammer_ioctl(hammer_inode_t ip, u_long com, caddr_t data, int fflag,
 				    (struct hammer_ioc_info *)data);
 		break;
 	case HAMMERIOC_SET_VERSION:
-		if (error == 0) {
-			error = hammer_ioc_set_version(&trans, ip, 
+        error = hammer_ioc_set_version(&trans, ip, 
 					    (struct hammer_ioc_version *)data);
-		}
 		break;
 	case HAMMERIOC_ADD_VOLUME:
-		if (error == 0) {
-			error = priv_check_cred(cred, PRIV_HAMMER_VOLUME, 0);
-			if (error == 0)
-				error = hammer_ioc_volume_add(&trans, ip,
+        error = hammer_ioc_volume_add(&trans, ip,
 					    (struct hammer_ioc_volume *)data);
-		}
 		break;
 	case HAMMERIOC_DEL_VOLUME:
-		if (error == 0) {
-			error = priv_check_cred(cred, PRIV_HAMMER_VOLUME, 0);
-			if (error == 0)
-				error = hammer_ioc_volume_del(&trans, ip,
+        error = hammer_ioc_volume_del(&trans, ip,
 					    (struct hammer_ioc_volume *)data);
-		}
 		break;
 	case HAMMERIOC_LIST_VOLUMES:
 		error = hammer_ioc_volume_list(&trans, ip,
 		    (struct hammer_ioc_volume_list *)data);
 		break;
 	case HAMMERIOC_ADD_SNAPSHOT:
-		if (error == 0) {
-			error = hammer_ioc_add_snapshot(
+        error = hammer_ioc_add_snapshot(
 					&trans, ip, (struct hammer_ioc_snapshot *)data);
-		}
 		break;
 	case HAMMERIOC_DEL_SNAPSHOT:
-		if (error == 0) {
-			error = hammer_ioc_del_snapshot(
+        error = hammer_ioc_del_snapshot(
 					&trans, ip, (struct hammer_ioc_snapshot *)data);
-		}
 		break;
 	case HAMMERIOC_GET_SNAPSHOT:
 		error = hammer_ioc_get_snapshot(
@@ -213,27 +186,33 @@ hammer_ioctl(hammer_inode_t ip, u_long com, caddr_t data, int fflag,
 					&trans, ip, (struct hammer_ioc_config *)data);
 		break;
 	case HAMMERIOC_SET_CONFIG:
-		if (error == 0) {
-			error = hammer_ioc_set_config(
+        error = hammer_ioc_set_config(
 					&trans, ip, (struct hammer_ioc_config *)data);
-		}
 		break;
 	case HAMMERIOC_DEDUP:
-		if (error == 0) {
-			error = hammer_ioc_dedup(
+        error = hammer_ioc_dedup(
 					&trans, ip, (struct hammer_ioc_dedup *)data);
-		}
 		break;
 	case HAMMERIOC_GET_DATA:
-		if (error == 0) {
-			error = hammer_ioc_get_data(
+        error = hammer_ioc_get_data(
 					&trans, ip, (struct hammer_ioc_data *)data);
-		}
 		break;
 	case HAMMERIOC_PFS_ITERATE:
 		error = hammer_ioc_pfs_iterate(
 			&trans, (struct hammer_ioc_pfs_iterate *)data);
 		break;
+    case HAMMERIOC_GET_PERM:
+		error = hammer_ioc_get_perm(
+			&trans, ip, (struct hammer_ioc_perm *)data);
+		break;
+    case HAMMERIOC_ADD_PERM:
+		error = hammer_ioc_add_perm(
+			&trans, ip, (struct hammer_ioc_perm *)data);
+		break;
+    case HAMMERIOC_DEL_PERM:
+		error = hammer_ioc_del_perm(
+			&trans, ip, (struct hammer_ioc_perm *)data);
+		break;
 	default:
 		error = EOPNOTSUPP;
 		break;
diff --git a/sys/vfs/hammer/hammer_ioctl.h b/sys/vfs/hammer/hammer_ioctl.h
index 0196a96..d39454b 100644
--- a/sys/vfs/hammer/hammer_ioctl.h
+++ b/sys/vfs/hammer/hammer_ioctl.h
@@ -452,6 +452,25 @@ struct hammer_ioc_config {
 };
 
 /*
+ * HAMMERIOC_GET_PERM
+ * HAMMERIOC_ADD_PERM
+ * HAMMERIOC_DEL_PERM
+ *
+ * Per-PFS ioctl() permissions.
+ *
+ * The configuration space is NOT mirrored.  mirror-write will ignore
+ * configuration space records.
+ */
+struct hammer_ioc_perm {
+    struct hammer_ioc_head head;
+    uid_t uid;
+    gid_t gid;
+    u_int64_t changed_perm;
+    u_int64_t perm;
+    u_int64_t reserved;
+};
+
+/*
  * HAMMERIOC_DEDUP
  */
 struct hammer_ioc_dedup {
@@ -505,6 +524,9 @@ struct hammer_ioc_data {
 #define HAMMERIOC_GET_DATA	_IOWR('h',26,struct hammer_ioc_data)
 #define HAMMERIOC_LIST_VOLUMES	_IOWR('h',27,struct hammer_ioc_volume_list)
 #define HAMMERIOC_PFS_ITERATE	_IOWR('h',28,struct hammer_ioc_pfs_iterate)
+#define HAMMERIOC_GET_PERM	_IOWR('h',29,struct hammer_ioc_perm)
+#define HAMMERIOC_ADD_PERM	_IOWR('h',30,struct hammer_ioc_perm)
+#define HAMMERIOC_DEL_PERM	_IOWR('h',31,struct hammer_ioc_perm)
 
 #endif
 
diff --git a/sys/vfs/hammer/hammer_mirror.c b/sys/vfs/hammer/hammer_mirror.c
index c99850a..882f799 100644
--- a/sys/vfs/hammer/hammer_mirror.c
+++ b/sys/vfs/hammer/hammer_mirror.c
@@ -805,7 +805,8 @@ hammer_mirror_nomirror(struct hammer_base_elm *base)
 	 * Certain types of records are never updated when mirroring.
 	 * Slaves have their own configuration space.
 	 */
-	if (base->rec_type == HAMMER_RECTYPE_CONFIG)
+	if ((base->rec_type == HAMMER_RECTYPE_CONFIG) ||
+        (base->rec_type == HAMMER_RECTYPE_PERM))
 		return(1);
 	return(0);
 }
diff --git a/sys/vfs/hammer/hammer_ondisk.c b/sys/vfs/hammer/hammer_ondisk.c
index 876c5fe..035bb39 100644
--- a/sys/vfs/hammer/hammer_ondisk.c
+++ b/sys/vfs/hammer/hammer_ondisk.c
@@ -1664,6 +1664,7 @@ hammer_alloc_data(hammer_transaction_t trans, int32_t data_len,
 		case HAMMER_RECTYPE_PFS:
 		case HAMMER_RECTYPE_SNAPSHOT:
 		case HAMMER_RECTYPE_CONFIG:
+        case HAMMER_RECTYPE_PERM:
 			zone = HAMMER_ZONE_META_INDEX;
 			break;
 		case HAMMER_RECTYPE_DATA:
diff --git a/sys/vfs/hammer/hammer_perm.c b/sys/vfs/hammer/hammer_perm.c
new file mode 100644
index 0000000..9dd1fa3
--- /dev/null
+++ b/sys/vfs/hammer/hammer_perm.c
@@ -0,0 +1,278 @@
+#include "hammer.h"
+
+static int hammer_get_perm (hammer_transaction_t trans, hammer_inode_t ip,
+                            uid_t uid, gid_t gid, u_int64_t *perm);
+static int hammer_set_perm (hammer_transaction_t trans, hammer_inode_t ip,
+                            uid_t uid, gid_t gid, u_int64_t *perm);
+
+/* This function does not fail in the case of ENOENT
+ * creating dummy permission entry. Returns EINVAL if permission entry
+   is requested for root.
+ */
+int hammer_ioc_get_perm(hammer_transaction_t trans, hammer_inode_t ip,
+                        struct hammer_ioc_perm *perm)
+{
+    int error;
+    perm->head.error = 0;
+
+    /* Sanity checks */
+    if ((perm->gid == 0) && (perm->uid == 0)) {
+        perm->head.error = EINVAL;
+        return 0;
+    }
+
+    error = hammer_get_perm (trans, ip,
+                             perm->uid, perm->gid, &(perm->perm));
+    if (error == ENOENT) {
+        error = 0;
+        bzero(&(perm->perm), sizeof (perm->perm));
+    }
+    return error;
+}
+
+/* Returns an error in case of HAMMER operations failure.
+ * Sets the following errors in perm->head.error:
+ *
+ * EINVAL: Permission is out of range or both uid and gid are set to 0
+ *         (root has not a permission entry). User tries to set the same
+ *         permission twice.
+ */
+int hammer_ioc_add_perm(hammer_transaction_t trans, hammer_inode_t ip,
+                        struct hammer_ioc_perm *perm)
+{
+    int error;
+    u_int64_t perm_data;
+
+    perm->head.error = 0;
+    /* Sanity checks */
+    if (perm->changed_perm > HAMMER_MAX_PERM_MASK) {
+        perm->head.error = EINVAL;
+        return 0;
+    }
+    if ((perm->gid == 0) && (perm->uid == 0)) {
+        perm->head.error = EINVAL;
+        return 0;
+    }
+
+    error = hammer_get_perm (trans, ip, perm->uid, perm->gid, &perm_data);
+    if (error == ENOENT) {
+        error = 0;
+        bzero (&perm_data, sizeof (perm_data));
+    }
+    if (error)
+        return error;
+
+    /* Check if we must add anything at all */
+    if ((perm_data & perm->changed_perm) == perm->changed_perm) {
+        perm->head.error = EINVAL;
+        return 0;
+    }
+
+    perm_data |= perm->changed_perm;
+
+    error = hammer_set_perm (trans, ip, perm->uid, perm->gid, &perm_data);
+    return error;
+}
+
+/* Returns an error in case of HAMMER operations failure.
+ * Sets the following errors in perm->head.error:
+ *
+ * EINVAL: Permission is out of range or both uid and gid are set to 0
+ *         (root has not a permission entry). User tries to delete
+ *          a permission which is not set earlier.
+ */
+int hammer_ioc_del_perm(hammer_transaction_t trans, hammer_inode_t ip,
+                        struct hammer_ioc_perm *perm)
+{
+    int error;
+    u_int64_t perm_data;
+
+    perm->head.error = 0;
+    /* Sanity checks */
+    if (perm->changed_perm > HAMMER_MAX_PERM_MASK) {
+        perm->head.error = EINVAL;
+        return 0;
+    }
+    if ((perm->gid == 0) && (perm->uid == 0)) {
+        perm->head.error = EINVAL;
+        return 0;
+    }
+
+    error = hammer_get_perm (trans, ip, perm->uid, perm->gid, &perm_data);
+    if (error == ENOENT) {
+        perm->head.error = EINVAL;
+        return 0;
+    }
+    if (error)
+        return error;
+
+    /* Nothing to delete */
+    if ((perm->changed_perm & perm_data) != perm->changed_perm) {
+        perm->head.error = EINVAL;
+        return 0;
+    }
+
+    perm_data &= ~perm->changed_perm;
+
+    error = hammer_set_perm (trans, ip, perm->uid, perm->gid, &perm_data);
+    return error;
+}
+
+int hammer_checkperm(hammer_transaction_t trans, hammer_inode_t ip,
+                     u_long com, struct ucred *cred)
+{
+    int error;
+    int p = -1;
+    u_int64_t perm;
+
+    switch (com) {
+        /* Unprivileged ioctl()'s */
+    case HAMMERIOC_GETHISTORY:
+    case HAMMERIOC_SYNCTID:
+    case HAMMERIOC_GET_PSEUDOFS:
+    case HAMMERIOC_GET_VERSION:
+    case HAMMERIOC_GET_INFO:
+    case HAMMERIOC_LIST_VOLUMES:
+    case HAMMERIOC_GET_SNAPSHOT:
+    case HAMMERIOC_GET_CONFIG:
+    case HAMMERIOC_PFS_ITERATE:
+    case HAMMERIOC_GET_PERM:
+        return 0;
+    case HAMMERIOC_ADD_SNAPSHOT:
+        p = HAMMER_PERM_ADD_SNAPSHOT;
+        break;
+    case HAMMERIOC_DEL_SNAPSHOT:
+        p = HAMMER_PERM_DEL_SNAPSHOT;
+        break;
+    case HAMMERIOC_MIRROR_READ:
+        p = HAMMER_PERM_MIRROR_READ;
+        break;
+    case HAMMERIOC_MIRROR_WRITE:
+        p = HAMMER_PERM_MIRROR_WRITE;
+        break;
+    case HAMMERIOC_SET_PSEUDOFS:
+        p = HAMMER_PERM_MIRROR_WRITE;
+        break;
+    case HAMMERIOC_WAI_PSEUDOFS:
+        p = HAMMER_PERM_MIRROR_READ;
+        break;
+        /* 'Global' (non per-PFS) ioctl()'s */
+    case HAMMERIOC_SET_VERSION:
+    case HAMMERIOC_ADD_VOLUME:
+    case HAMMERIOC_DEL_VOLUME:
+    default:
+        return EPERM;
+    }
+
+    if (p == -1)
+        return EINVAL; /* Unknown ioctl */
+
+    error = hammer_get_perm(trans, ip, cred->cr_uid, cred->cr_gid, &perm);
+    if (error == ENOENT)
+        return EPERM;
+    if (error)
+        return error;
+
+    if ((perm & p) == p)
+        return 0;
+    return EPERM;
+}
+
+static int hammer_get_perm (hammer_transaction_t trans, hammer_inode_t ip,
+                            uid_t uid, gid_t gid, u_int64_t *perm)
+{
+    int error;
+    struct hammer_cursor cursor;
+
+    error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL);
+    if (error) {
+        hammer_done_cursor(&cursor);
+        return error;
+    }
+
+	cursor.key_beg.obj_id = HAMMER_OBJID_ROOT;
+	cursor.key_beg.create_tid = 0;
+	cursor.key_beg.delete_tid = 0;
+	cursor.key_beg.obj_type = 0;
+	cursor.key_beg.rec_type = HAMMER_RECTYPE_PERM;
+	cursor.key_beg.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE;
+	cursor.key_beg.key = uid;
+
+	cursor.asof = HAMMER_MAX_TID;
+	cursor.flags |= HAMMER_CURSOR_ASOF;
+
+	error = hammer_btree_lookup(&cursor);
+	if (error == 0) {
+		error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF |
+						      HAMMER_CURSOR_GET_DATA);
+		if (error == 0) {
+			*perm = cursor.data->perm;
+            KASSERT (*perm <= HAMMER_MAX_PERM_MASK, ("Permissions are invalid %lu", *perm));
+        }
+	}
+
+    hammer_done_cursor(&cursor);
+    return error;
+}
+
+static int hammer_set_perm (hammer_transaction_t trans, hammer_inode_t ip,
+                            uid_t uid, gid_t gid, u_int64_t *perm)
+{
+	struct hammer_btree_leaf_elm leaf;
+	struct hammer_cursor cursor;
+	hammer_mount_t hmp = ip->hmp;
+	int error;
+
+again:
+	error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL);
+	if (error) {
+		hammer_done_cursor(&cursor);
+		return(error);
+	}
+
+	bzero(&leaf, sizeof(leaf));
+	leaf.base.obj_id = HAMMER_OBJID_ROOT;
+	leaf.base.rec_type = HAMMER_RECTYPE_PERM;
+	leaf.base.create_tid = hammer_alloc_tid(hmp, 1);
+	leaf.base.btype = HAMMER_BTREE_TYPE_RECORD;
+	leaf.base.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE;
+	leaf.base.key = uid;
+	leaf.data_len = sizeof(*perm);
+
+	cursor.key_beg = leaf.base;
+
+	cursor.asof = HAMMER_MAX_TID;
+	cursor.flags |= HAMMER_CURSOR_BACKEND | HAMMER_CURSOR_ASOF;
+
+	error = hammer_btree_lookup(&cursor);
+	if (error == 0) {
+		error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF |
+						      HAMMER_CURSOR_GET_DATA);
+		error = hammer_delete_at_cursor(&cursor, HAMMER_DELETE_DESTROY,
+						0, 0, 0, NULL);
+		if (error == EDEADLK) {
+			hammer_done_cursor(&cursor);
+			goto again;
+		}
+	}
+	if (error == ENOENT)
+		error = 0;
+	if (error == 0) {
+		/*
+		 * NOTE: Must reload key_beg after an ASOF search because
+		 *	 the create_tid may have been modified during the
+		 *	 search.
+		 */
+		cursor.flags &= ~HAMMER_CURSOR_ASOF;
+		cursor.key_beg = leaf.base;
+		error = hammer_create_at_cursor(&cursor, &leaf,
+                        perm,
+						HAMMER_CREATE_MODE_SYS);
+		if (error == EDEADLK) {
+			hammer_done_cursor(&cursor);
+			goto again;
+		}
+	}
+	hammer_done_cursor(&cursor);
+	return error;
+}
diff --git a/sys/vfs/hammer/hammer_reblock.c b/sys/vfs/hammer/hammer_reblock.c
index 65d987a..fa7971b 100644
--- a/sys/vfs/hammer/hammer_reblock.c
+++ b/sys/vfs/hammer/hammer_reblock.c
@@ -272,6 +272,7 @@ hammer_reblock_helper(struct hammer_ioc_reblock *reblock,
 	case HAMMER_RECTYPE_INODE:
 	case HAMMER_RECTYPE_SNAPSHOT:
 	case HAMMER_RECTYPE_CONFIG:
+    case HAMMER_RECTYPE_PERM:
 		iocflags = HAMMER_IOC_DO_INODES;
 		break;
 	case HAMMER_RECTYPE_EXT:

Reply via email to