File btrfs-subvolumes.patch of Package shadow.12022

commit 52ea836ffbfa4d6797cf89d6ada58f76bee9cf6b
Author: Adam Majer <amajer@suse.de>
Date:   Wed Jan 23 16:17:05 2019 +0100

    Add autotools support for BtrFS option
    
    Feature is enabled by default, if headers are available. It can be
    turned off explictly.

commit 81ead2042afcdb8d423da855cf1528618a4e0c01
Author: Adam Majer <amajer@suse.de>
Date:   Mon Jan 21 09:32:36 2019 +0100

    Add support for btrfs subvolumes for user homes
    
    new switch added to useradd command, --btrfs-subvolume-home. When
    specified *and* the filesystem is detected as btrfs, it will create a
    subvolume for user's home instead of a plain directory. This is done via
    `btrfs subvolume` command.  Specifying the new switch while trying to
    create home on non-btrfs will result in an error.
    
    userdel -r will handle and remove this subvolume transparently via
    `btrfs subvolume` command. Previosuly this failed as you can't rmdir a
    subvolume.
    
    usermod, when moving user's home across devices, will detect if the home
    is a subvolume and issue an error messages instead of copying it. Moving
    user's home (as subvolume) on same btrfs works transparently.


--- a/configure.ac
+++ b/configure.ac
@@ -256,6 +256,9 @@ AC_ARG_WITH(audit,
 AC_ARG_WITH(libpam,
 	[AC_HELP_STRING([--with-libpam], [use libpam for PAM support @<:@default=yes if found@:>@])],
 	[with_libpam=$withval], [with_libpam=maybe])
+AC_ARG_WITH(btrfs,
+	[AC_HELP_STRING([--with-btrfs], [add BtrFS support @<:@default=yes if found@:>@])],
+	[with_selinux=$withval], [with_selinux=maybe])
 AC_ARG_WITH(selinux,
 	[AC_HELP_STRING([--with-selinux], [use SELinux support @<:@default=yes if found@:>@])],
 	[with_selinux=$withval], [with_selinux=maybe])
@@ -453,6 +456,20 @@ if test "$with_libcrack" = "yes"; then
 		AC_DEFINE(HAVE_LIBCRACK_PW, 1, [Defined if it includes *Pw functions.]))
 fi
 
+if test "$with_btrfs" != "no"; then
+	AC_CHECK_HEADERS([sys/statfs.h linux/magic.h linux/btrfs_tree.h], \
+		[btrfs_headers="yes"], [btrfs_headers="no"])
+	if test "$btrfs_headers$with_btrfs" = "noyes" ; then
+		AC_MSG_ERROR([One of sys/statfs.h linux/magic.h linux/btrfs_tree.h is missing])
+	fi
+
+	if test "$btrfs_headers" = "yes" ; then
+		AC_DEFINE(WITH_BTRFS, 1, [Build shadow with BtrFS support])
+		with_btrfs="yes"
+	fi
+fi
+AM_CONDITIONAL(WITH_BTRFS, test x$with_btrfs = xyes)
+
 AC_SUBST(LIBSELINUX)
 AC_SUBST(LIBSEMANAGE)
 if test "$with_selinux" != "no"; then
@@ -672,6 +689,7 @@ if test "$with_libpam" = "yes"; then
 echo "	suid account management tools:	$enable_acct_tools_setuid"
 fi
 echo "	SELinux support:		$with_selinux"
+echo "	BtrFS support:			$with_btrfs"
 echo "	ACL support:			$with_acl"
 echo "	Extended Attributes support:	$with_attr"
 echo "	tcb support (incomplete):	$with_tcb"
--- a/lib/prototypes.h
+++ b/lib/prototypes.h
@@ -72,6 +72,14 @@ extern int expire (const struct passwd *
 /* isexpired.c */
 extern int isexpired (const struct passwd *, /*@null@*/const struct spwd *);
 
+/* btrfs.c */
+#ifdef WITH_BTRFS
+extern int btrfs_create_subvolume(const char *path);
+extern int btrfs_remove_subvolume(const char *path);
+extern int btrfs_is_subvolume(const char *path);
+extern int is_btrfs(const char *path);
+#endif
+
 /* basename() renamed to Basename() to avoid libc name space confusion */
 /* basename.c */
 extern /*@observer@*/const char *Basename (const char *str);
--- a/libmisc/Makefile.am
+++ b/libmisc/Makefile.am
@@ -72,3 +72,8 @@ libmisc_a_SOURCES = \
 	xgetspnam.c \
 	xmalloc.c \
 	yesno.c
+
+if WITH_BTRFS
+libmisc_a_SOURCES += btrfs.c
+endif
+
--- /dev/null
+++ b/libmisc/btrfs.c
@@ -0,0 +1,94 @@
+#include <linux/btrfs_tree.h>
+#include <linux/magic.h>
+#include <sys/statfs.h>
+
+#include "prototypes.h"
+
+
+static int run_btrfs_subvolume_cmd(const char *subcmd, const char *arg1, const char *arg2)
+{
+	int status = 0;
+	const char *cmd = "/sbin/btrfs";
+	const char *argv[] = {
+		strrchr(cmd, '/'),
+		"subvolume",
+		subcmd,
+		arg1,
+		arg2,
+		NULL
+	};
+
+	if (argv[0] == NULL)
+		argv[0] = cmd;
+	else
+		argv[0] = argv[0] + 1;
+
+	if (access(cmd, X_OK)) {
+		return 1;
+	}
+
+	if (run_command(cmd, argv, NULL, &status))
+		return -1;
+	return status;
+}
+
+
+int btrfs_create_subvolume(const char *path)
+{
+	return run_btrfs_subvolume_cmd("create", path, NULL);
+}
+
+
+int btrfs_remove_subvolume(const char *path)
+{
+	return run_btrfs_subvolume_cmd("delete", "-C", path);
+}
+
+
+/* Adapted from btrfsprogs */
+/*
+ * This intentionally duplicates btrfs_util_is_subvolume_fd() instead of opening
+ * a file descriptor and calling it, because fstat() and fstatfs() don't accept
+ * file descriptors opened with O_PATH on old kernels (before v3.6 and before
+ * v3.12, respectively), but stat() and statfs() can be called on a path that
+ * the user doesn't have read or write permissions to.
+ *
+ * returns:
+ *   1 - btrfs subvolume
+ *   0 - not btrfs subvolume
+ *  -1 - error
+ */
+int btrfs_is_subvolume(const char *path)
+{
+	struct stat st;
+	int ret;
+
+	ret = is_btrfs(path);
+	if (ret <= 0)
+		return ret;
+
+	ret = stat(path, &st);
+	if (ret == -1)
+		return -1;
+
+	if (st.st_ino != BTRFS_FIRST_FREE_OBJECTID || !S_ISDIR(st.st_mode)) {
+		return 0;
+	}
+
+	return 1;
+}
+
+
+/* Adapted from btrfsprogs */
+int is_btrfs(const char *path)
+{
+	struct statfs sfs;
+	int ret;
+
+	ret = statfs(path, &sfs);
+	if (ret == -1)
+		return -1;
+
+	return sfs.f_type == BTRFS_SUPER_MAGIC;
+}
+
--- a/src/useradd.c
+++ b/src/useradd.c
@@ -164,6 +164,7 @@ static bool
     oflg = false,		/* permit non-unique user ID to be specified with -u */
     rflg = false,		/* create a system account */
     sflg = false,		/* shell program for new account */
+    subvolflg = false,		/* create subvolume home on BTRFS */
     uflg = false,		/* specify user ID for new account */
     Uflg = false;		/* create a group having the same name as the user */
 
@@ -805,6 +806,9 @@ static void usage (int status)
 	                Prog, Prog, Prog);
 	(void) fputs (_("  -b, --base-dir BASE_DIR       base directory for the home directory of the\n"
 	                "                                new account\n"), usageout);
+#ifdef WITH_BTRFS
+	(void) fputs (_("      --btrfs-subvolume-home    use BTRFS subvolume for home directory\n"), usageout);
+#endif
 	(void) fputs (_("  -c, --comment COMMENT         GECOS field of the new account\n"), usageout);
 	(void) fputs (_("  -d, --home-dir HOME_DIR       home directory of the new account\n"), usageout);
 	(void) fputs (_("  -D, --defaults                print or change default useradd configuration\n"), usageout);
@@ -1085,6 +1089,9 @@ static void process_flags (int argc, cha
 		int c;
 		static struct option long_options[] = {
 			{"base-dir",       required_argument, NULL, 'b'},
+#ifdef WITH_BTRFS
+			{"btrfs-subvolume-home", no_argument, NULL, 200},
+#endif
 			{"comment",        required_argument, NULL, 'c'},
 			{"home-dir",       required_argument, NULL, 'd'},
 			{"defaults",       no_argument,       NULL, 'D'},
@@ -1131,6 +1138,9 @@ static void process_flags (int argc, cha
 				def_home = optarg;
 				bflg = true;
 				break;
+			case 200:
+				subvolflg = true;
+				break;
 			case 'c':
 				if (!VALID (optarg)) {
 					fprintf (stderr,
@@ -2049,6 +2059,37 @@ static void create_home (void)
 			strcat (path, "/");
 			strcat (path, cp);
 			if (access (path, F_OK) != 0) {
+				/* Check if parent directory is BTRFS, fail if requesting
+				   subvolume but no BTRFS. The paths cound be different by the
+				   trailing slash
+				 */
+#if WITH_BTRFS
+				if (subvolflg && (strlen(prefix_user_home) - (int)strlen(path)) <= 1) {
+					char *btrfs_check = strdup(path);
+
+					if (!btrfs_check) {
+						fprintf (stderr,
+						         _("%s: error while duplicating string in BTRFS check %s\n"),
+						         Prog, path);
+						fail_exit (E_HOMEDIR);
+					}
+					btrfs_check[strlen(path) - strlen(cp) - 1] = '\0';
+					if (is_btrfs(btrfs_check) <= 0) {
+						fprintf (stderr,
+						         _("%s: home directory \"%s\" must be mounted on BTRFS\n"),
+						         Prog, path);
+						fail_exit (E_HOMEDIR);
+					}
+					// make subvolume to mount for user instead of directory
+					if (btrfs_create_subvolume(path)) {
+						fprintf (stderr,
+						         _("%s: failed to create BTRFS subvolume: %s\n"),
+						         Prog, path);
+						fail_exit (E_HOMEDIR);
+					}
+				}
+				else
+#endif
 				if (mkdir (path, 0) != 0) {
 			fprintf (stderr,
 			         _("%s: cannot create directory %s\n"),
--- a/src/userdel.c
+++ b/src/userdel.c
@@ -1273,6 +1273,23 @@ int main (int argc, char **argv)
 #endif				/* EXTRA_CHECK_HOME_DIR */
 
 	if (rflg) {
+#ifdef WITH_BTRFS
+		int is_subvolume = btrfs_is_subvolume (user_home);
+		if (is_subvolume < 0) {
+		    errors++;
+		    /* continue */
+		}
+		else if (is_subvolume > 0) {
+			if (btrfs_remove_subvolume (user_home)) {
+				fprintf (stderr,
+				         _("%s: error removing subvolume %s\n"),
+				         Prog, user_home);
+				errors++;
+				/* continue */
+			}
+		}
+		else
+#endif
 		if (remove_tree (user_home, true) != 0) {
 			fprintf (stderr,
 			         _("%s: error removing directory %s\n"),
--- a/src/usermod.c
+++ b/src/usermod.c
@@ -1818,6 +1818,15 @@ static void move_home (void)
 			return;
 		} else {
 			if (EXDEV == errno) {
+#ifdef WITH_BTRFS
+				if (btrfs_is_subvolume (prefix_user_home) > 0) {
+					fprintf (stderr,
+					        _("%s: error: cannot move subvolume from %s to %s - different device\n"),
+					        Prog, prefix_user_home, prefix_user_newhome);
+					fail_exit (E_HOMEDIR);
+				}
+#endif
+
 				if (copy_tree (prefix_user_home, prefix_user_newhome, true,
 				               true,
 				               user_id,
openSUSE Build Service is sponsored by