Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ libshadow_la_SOURCES += tcbfuncs.c tcbfuncs.h
endif

if WITH_BTRFS
libshadow_la_SOURCES += btrfs.c
libshadow_la_SOURCES += btrfs.c btrfs.h
endif

if ENABLE_LASTLOG
Expand Down
31 changes: 14 additions & 17 deletions lib/btrfs.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#include "config.h"

#include "btrfs.h"

#include <linux/btrfs_tree.h>
#include <linux/magic.h>
#include <sys/statfs.h>
Expand Down Expand Up @@ -60,15 +64,15 @@ int btrfs_remove_subvolume(const char *path)
*/
int btrfs_is_subvolume(const char *path)
{
struct stat st;
int ret;
struct stat st;
struct statfs sfs;

ret = is_btrfs(path);
if (ret <= 0)
return ret;
if (statfs(path, &sfs) == -1)
return -1;
if (!is_btrfs(&sfs))
return 0;

ret = stat(path, &st);
if (ret == -1)
if (stat(path, &st) == -1)
return -1;

if (st.st_ino != BTRFS_FIRST_FREE_OBJECTID || !S_ISDIR(st.st_mode)) {
Expand All @@ -80,15 +84,8 @@ int btrfs_is_subvolume(const char *path)


/* Adapted from btrfsprogs */
int is_btrfs(const char *path)
bool
is_btrfs(const struct statfs *sfs)
{
struct statfs sfs;
int ret;

ret = statfs(path, &sfs);
if (ret == -1)
return -1;

return sfs.f_type == BTRFS_SUPER_MAGIC;
return sfs->f_type == BTRFS_SUPER_MAGIC;
}

23 changes: 23 additions & 0 deletions lib/btrfs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2026, Alejandro Colomar <alx@kernel.org>
// SPDX-License-Identifier: BSD-3-Clause


#ifndef SHADOW_INCLUDE_LIB_BTRFS_H_
#define SHADOW_INCLUDE_LIB_BTRFS_H_


#include "config.h"

#include <stdbool.h>
#include <sys/statfs.h>


#ifdef WITH_BTRFS
int btrfs_create_subvolume(const char *path);
int btrfs_remove_subvolume(const char *path);
int btrfs_is_subvolume(const char *path);
bool is_btrfs(const struct statfs *sfs);
#endif


#endif // include guard
8 changes: 0 additions & 8 deletions lib/prototypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,6 @@ extern int expire (const struct passwd *, /*@null@*/const struct spwd *);
/* 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);
Expand Down
33 changes: 28 additions & 5 deletions man/useradd.8.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<!--
SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
SPDX-FileCopyrightText: 2007 - 2011, Nicolas François
SPDX-FileCopyrightText: 2025 - 2026, Hadi Chokr
SPDX-License-Identifier: BSD-3-Clause
-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.5//EN"
Expand Down Expand Up @@ -156,8 +157,30 @@
regardless of any configuration file settings.
</para>
<para>
Note: this feature works only if the underlying filesystem supports
Btrfs subvolumes.
If the parent directory of the user's home directory is
<emphasis>not</emphasis> on a Btrfs filesystem,
<command>useradd</command> will <emphasis>not</emphasis> create a
subvolume.
Instead, it creates a regular directory,
prints a warning to standard error,
and logs the event via syslog at level
<constant>LOG_WARN</constant>.
The user account is still created successfully.
</para>
<para>
If the filesystem type cannot be determined (e.g., because of
insufficient permissions, an I/O error,
or a
<citerefentry>
<refentrytitle>statfs</refentrytitle>
<manvolnum>2</manvolnum>
</citerefentry>
failure),
<command>useradd</command> treats this as a fatal error:
the home directory is not created,
the command exits with a non‑zero status
(<literal>E_HOMEDIR</literal>, 12),
and an error message is printed.
</para>
</listitem>
</varlistentry>
Expand All @@ -184,7 +207,7 @@
user's login directory. The default is to append the
<replaceable>LOGIN</replaceable> name to
<replaceable>BASE_DIR</replaceable> and use that as the
login directory name.
login directory name.
The directory <replaceable>HOME_DIR</replaceable> is not created by
default. However it will be created for non-system users if either the
<option>-m</option> flag is specified or
Expand Down Expand Up @@ -399,7 +422,7 @@
</term>
<listitem>
<para>
Create the user's home directory if it does not exist.
Create the user's home directory if it does not exist.
The files and directories contained in the skeleton directory
(which can be defined with the <option>-k</option> option)
will be copied to the home directory.
Expand Down Expand Up @@ -489,7 +512,7 @@
password himself.
</para>
<para>
<emphasis role="bold">Note:</emphasis>Avoid this option on the command
<emphasis role="bold">Note:</emphasis>Avoid this option on the command
line because the password (or encrypted password) will
be visible by users listing the processes.
</para>
Expand Down
59 changes: 33 additions & 26 deletions src/useradd.c
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
* SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
* SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
* SPDX-FileCopyrightText: 2007 - 2012, Nicolas François
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// SPDX-FileCopyrightText: 1991-1994, Julianne Frances Haugh
// SPDX-FileCopyrightText: 1996-2000, Marek Michałkiewicz
// SPDX-FileCopyrightText: 2000-2006, Tomasz Kłoczko
// SPDX-FileCopyrightText: 2007-2012, Nicolas François
// SPDX-FileCopyrightText: 2025-2026, Hadi Chokr
// SPDX-FileCopyrightText: 2026, Alejandro Colomar <alx@kernel.org>
// SPDX-License-Identifier: BSD-3-Clause


#include "config.h"

Expand All @@ -25,6 +25,7 @@
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
Expand Down Expand Up @@ -2249,6 +2250,8 @@ static void create_home(const struct option_flags *flags)
owner root:root.
*/
for (cp = strtok(bhome, "/"); cp != NULL; cp = strtok(NULL, "/")) {
bool dir_created;

/* Avoid turning a relative path into an absolute path. */
if (strprefix(bhome, "/") || !streq(path, ""))
strcat(path, "/");
Expand All @@ -2258,13 +2261,11 @@ static void create_home(const struct option_flags *flags)
continue;
}

/* Check if parent directory is BTRFS, fail if requesting
subvolume but no BTRFS. The paths could be different by the
trailing slash
*/
dir_created = false;
#if WITH_BTRFS
if (subvolflg && (strlen(prefix_user_home) - (int)strlen(path)) <= 1) {
char *btrfs_check = strdup(path);
struct statfs sfs;

if (!btrfs_check) {
fprintf(stderr,
Expand All @@ -2273,27 +2274,33 @@ static void create_home(const struct option_flags *flags)
fail_exit(E_HOMEDIR, process_selinux);
}
stpcpy(&btrfs_check[strlen(path) - strlen(cp) - 1], "");
if (is_btrfs(btrfs_check) <= 0) {
fprintf(stderr,
_("%s: home directory \"%s\" must be mounted on BTRFS\n"),
Prog, path);
if (statfs(btrfs_check, &sfs) == -1) {
fprintf(stderr, "%s: statfs(\"%s\"): %s\n",
Prog, btrfs_check, strerrno());
fail_exit(E_HOMEDIR, process_selinux);
}
free(btrfs_check);
// make subvolume to mount for user instead of directory
if (btrfs_create_subvolume(path)) {
if (!is_btrfs(&sfs)) {
fprintf(stderr,
_("%s: failed to create BTRFS subvolume: %s\n"),
Prog, path);
fail_exit(E_HOMEDIR, process_selinux);
_("%s: warning: \"%s\" is not on BTRFS; creating regular directory instead of subvolume\n"),
Prog, prefix_user_home);
} else {
if (btrfs_create_subvolume(path)) {
fprintf(stderr,
_("%s: failed to create BTRFS subvolume: %s\n"),
Prog, path);
fail_exit(E_HOMEDIR, process_selinux);
}
dir_created = true;
}
}
else
#endif
if (mkdir(path, 0) != 0) {
fprintf(stderr, _("%s: cannot create directory %s\n"),
Prog, path);
fail_exit(E_HOMEDIR, process_selinux);
if (!dir_created) {
if (mkdir(path, 0) != 0) {
fprintf(stderr, _("%s: cannot create directory %s\n"),
Prog, path);
fail_exit(E_HOMEDIR, process_selinux);
}
}
if (chown(path, 0, 0) < 0) {
fprintf(stderr,
Expand Down
1 change: 1 addition & 0 deletions src/usermod.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "alloc/malloc.h"
#include "atoi/a2i.h"
#include "atoi/getnum.h"
#include "btrfs.h"
#include "chkname.h"
#include "defines.h"
#include "faillog.h"
Expand Down
Loading