From nobody Wed May 29 23:55:33 2024 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4VqR8j4fPvz5LZHM; Wed, 29 May 2024 23:55:33 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4VqR8j44Brz485K; Wed, 29 May 2024 23:55:33 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1717026933; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=ZzW2fw8Ef+Dml2qnJeKN5YPnH45FaqQNuqtdRRjWM58=; b=yT7zGA7+uU4oIdwT4t/IKMNCcqVhlQMSzaiRv1aMVlgK4MD7kNtmUg2ySumx4pJtA4DUo5 egJXoR5pjVuaOgr8MbVGCeH5tfp6wviKrJw12Af7zEti4J0txk8IE/4UnAVP/LHqhFGNc1 egqeMzAkJUVmdNso9TcC/OQ/sWoMHMz2bbbmsZ+40aXJL6VWogT35on85tZPIahrQrXjUz SjDhJXX4vWTh9Xl8+S5G5PaG4P49DKeIBAWH0Tb8PCoS3YumbWyx4G5d6u1z22VcBQlZqj XR8NDEY/02vgHCM2E5nvKAMFLINvAxusvDsIkQWJDKML/GaHGCE/MfVtlfYl7g== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1717026933; a=rsa-sha256; cv=none; b=To6K4hXj5HF6wCEe/qoiKs8W/IibFkPTUQc+NF9ti8aqGn3n+0G5CKfiHzrlO1P87R3Dnp gY0hhMGilnEiiphkL00yf/ED464hBzFkx6AOA6omZ4K4TeMoy05pMP5Vf7EtPpaLAtE5eS ixPpJKxAT1Jj4U49L/8buUuwDH3pcPbxv9jvSkq58g2pOPPRZs9/N8VUkyAAwqMcFgEspA /2++Dlxuz7Toh6OBZXiq/R/NosUTyJOAUdfeYiCXDkEagfvtPgxnDTVlp6q9doA/SPe04P UbSqKL2cDxLZHcQms7hu9LyDnHOIsHutQZKFoTXgAqGq9h73cADzDAtu6/zipA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1717026933; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=ZzW2fw8Ef+Dml2qnJeKN5YPnH45FaqQNuqtdRRjWM58=; b=EuvnLd8DWg5QlgJuxuzNADzLZ+Uic/4MuBZkAkj0tKMC0WA5X0xs8ySdu58u7PuCYqhRtk ca0G80NAGwdE6Gk+PJ3tHn96IoYLr1pRCVIZWXFo6E2g5S+HEZQM+0NscdHr27PX5B2dGA Mur3jS2+Uf2hdLBUphk8obt33lPIbyUpNmn6xdjtO/VMeDWpF/Lni3mcnGU7Tz7eiZ4jdB ZUrWuTMpOm6ulaqOugEDgtcGPlFaKDmABkSwFQb0BvfI3Xi582yY0dtsjDWcpFjFmfVKDA mj2owXjY6kKRyMwWYMFpJXaO+isBWNb7J34d1VjOjb11+Y8xydi2n9FtwaZEJw== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4VqR8j3YgXzH74; Wed, 29 May 2024 23:55:33 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.17.1/8.17.1) with ESMTP id 44TNtXPf043712; Wed, 29 May 2024 23:55:33 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.17.1/8.17.1/Submit) id 44TNtXlc043709; Wed, 29 May 2024 23:55:33 GMT (envelope-from git) Date: Wed, 29 May 2024 23:55:33 GMT Message-Id: <202405292355.44TNtXlc043709@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Mike Karels Subject: git: d2f1f71ec8c6 - main - pw userdel: destroy home dataset if empty List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: karels X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: d2f1f71ec8c62dd26d6169d0d671a5aa5a933c1a Auto-Submitted: auto-generated The branch main has been updated by karels: URL: https://cgit.FreeBSD.org/src/commit/?id=d2f1f71ec8c62dd26d6169d0d671a5aa5a933c1a commit d2f1f71ec8c62dd26d6169d0d671a5aa5a933c1a Author: Mike Karels AuthorDate: 2024-05-29 23:55:14 +0000 Commit: Mike Karels CommitDate: 2024-05-29 23:55:14 +0000 pw userdel: destroy home dataset if empty When removing a user's home directory, if the directory is a ZFS dataset, it cannot be removed. If the directory has been emptied, use "zfs destroy" to destroy it. This complements the automatic dataset creation in adduser. Note that datasets within the directory and snapshots are not handled, as the complete path is not constructed. While here, add waitpid() calls to rmat() and pw_user_del(). Reviewed by: des Differential Revision: https://reviews.freebsd.org/D45348 --- usr.sbin/pw/pw.8 | 6 ++- usr.sbin/pw/pw_user.c | 15 ++++--- usr.sbin/pw/pwupd.h | 2 +- usr.sbin/pw/rm_r.c | 113 +++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 118 insertions(+), 18 deletions(-) diff --git a/usr.sbin/pw/pw.8 b/usr.sbin/pw/pw.8 index 5d461fc6464e..5ba35c193811 100644 --- a/usr.sbin/pw/pw.8 +++ b/usr.sbin/pw/pw.8 @@ -741,6 +741,9 @@ Secondly, it will only remove files and directories that are actually owned by the user, or symbolic links owned by anyone under the user's home directory. Finally, after deleting all contents owned by the user only empty directories will be removed. +If the home directory is a ZFS dataset and has been emptied, +the dataset will be destroyed. +ZFS datasets within the home directory and snapshots are not handled. If any additional cleanup work is required, this is left to the administrator. .El .Pp @@ -1077,7 +1080,8 @@ No base home directory configured. .Xr passwd 5 , .Xr pw.conf 5 , .Xr pwd_mkdb 8 , -.Xr vipw 8 +.Xr vipw 8 , +.Xr zfs 8 .Sh HISTORY The .Nm diff --git a/usr.sbin/pw/pw_user.c b/usr.sbin/pw/pw_user.c index 89354b249935..d9fd8c77c13e 100644 --- a/usr.sbin/pw/pw_user.c +++ b/usr.sbin/pw/pw_user.c @@ -28,7 +28,7 @@ */ #include -#include +#include #include #include @@ -669,6 +669,7 @@ rmat(uid_t uid) while ((e = readdir(d)) != NULL) { struct stat st; + pid_t pid; if (strncmp(e->d_name, ".lock", 5) != 0 && stat(e->d_name, &st) == 0 && @@ -679,11 +680,12 @@ rmat(uid_t uid) e->d_name, NULL }; - if (posix_spawn(NULL, argv[0], NULL, NULL, + if (posix_spawn(&pid, argv[0], NULL, NULL, (char *const *) argv, environ)) { warn("Failed to execute '%s %s'", argv[0], argv[1]); - } + } else + (void) waitpid(pid, NULL, 0); } } closedir(d); @@ -919,11 +921,14 @@ pw_user_del(int argc, char **argv, char *arg1) "-r", NULL }; - if (posix_spawnp(NULL, argv[0], NULL, NULL, + pid_t pid; + + if (posix_spawnp(&pid, argv[0], NULL, NULL, (char *const *) argv, environ)) { warn("Failed to execute '%s %s'", argv[0], argv[1]); - } + } else + (void) waitpid(pid, NULL, 0); } } diff --git a/usr.sbin/pw/pwupd.h b/usr.sbin/pw/pwupd.h index d174a4a20200..262b044e07fc 100644 --- a/usr.sbin/pw/pwupd.h +++ b/usr.sbin/pw/pwupd.h @@ -139,7 +139,7 @@ void vendgrent(void); void copymkdir(int rootfd, char const * dir, int skelfd, mode_t mode, uid_t uid, gid_t gid, int flags); -void rm_r(int rootfd, char const * dir, uid_t uid); +bool rm_r(int rootfd, char const * dir, uid_t uid); __END_DECLS #endif /* !_PWUPD_H */ diff --git a/usr.sbin/pw/rm_r.c b/usr.sbin/pw/rm_r.c index 14218d68215b..f25da8ce6ad3 100644 --- a/usr.sbin/pw/rm_r.c +++ b/usr.sbin/pw/rm_r.c @@ -26,35 +26,58 @@ * SUCH DAMAGE. */ +#include +#include #include +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include #include "pwupd.h" -void +static bool try_dataset_remove(const char *home); +extern char **environ; + +/* + * "rm -r" a directory tree. If the top-level directory cannot be removed + * due to EBUSY, indicating that it is a ZFS dataset, and we have emptied + * it, destroy the dataset. Return true if any files or directories + * remain. + */ +bool rm_r(int rootfd, const char *path, uid_t uid) { int dirfd; DIR *d; struct dirent *e; struct stat st; + const char *fullpath; + bool skipped = false; + fullpath = path; if (*path == '/') path++; dirfd = openat(rootfd, path, O_DIRECTORY); if (dirfd == -1) { - return; + return (true); } d = fdopendir(dirfd); if (d == NULL) { (void)close(dirfd); - return; + return (true); } while ((e = readdir(d)) != NULL) { if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) @@ -62,16 +85,84 @@ rm_r(int rootfd, const char *path, uid_t uid) if (fstatat(dirfd, e->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0) continue; - if (S_ISDIR(st.st_mode)) - rm_r(dirfd, e->d_name, uid); - else if (S_ISLNK(st.st_mode) || st.st_uid == uid) + if (S_ISDIR(st.st_mode)) { + if (rm_r(dirfd, e->d_name, uid) == true) + skipped = true; + } else if (S_ISLNK(st.st_mode) || st.st_uid == uid) unlinkat(dirfd, e->d_name, 0); + else + skipped = true; } closedir(d); if (fstatat(rootfd, path, &st, AT_SYMLINK_NOFOLLOW) != 0) - return; - if (S_ISLNK(st.st_mode)) - unlinkat(rootfd, path, 0); - else if (st.st_uid == uid) - unlinkat(rootfd, path, AT_REMOVEDIR); + return (skipped); + if (S_ISLNK(st.st_mode)) { + if (unlinkat(rootfd, path, 0) == -1) + skipped = true; + } else if (st.st_uid == uid) { + if (unlinkat(rootfd, path, AT_REMOVEDIR) == -1) { + if (errno == EBUSY && skipped == false) + skipped = try_dataset_remove(fullpath); + else + skipped = true; + } + } else + skipped = true; + + return (skipped); +} + +/* + * If the home directory is a ZFS dataset, attempt to destroy it. + * Return true if the dataset is not destroyed. + * This would be more straightforward as a shell script. + */ +static bool +try_dataset_remove(const char *path) +{ + bool skipped = true; + struct statfs stat; + const char *argv[] = { + "/sbin/zfs", + "destroy", + NULL, + NULL + }; + int status; + pid_t pid; + + /* see if this is an absolute path (top-level directory) */ + if (*path != '/') + return (skipped); + /* see if ZFS is loaded */ + if (kld_isloaded("zfs") == 0) + return (skipped); + /* This won't work if root dir is not / (-R option) */ + if (strcmp(conf.rootdir, "/") != 0) { + warnx("cannot destroy home dataset when -R was used"); + return (skipped); + } + /* if so, find dataset name */ + if (statfs(path, &stat) != 0) { + warn("statfs %s", path); + return (skipped); + } + /* + * Check that the path refers to the dataset itself, + * not a subdirectory. + */ + if (strcmp(stat.f_mntonname, path) != 0) + return (skipped); + argv[2] = stat.f_mntfromname; + if ((skipped = posix_spawn(&pid, argv[0], NULL, NULL, + (char *const *) argv, environ)) != 0) { + warn("Failed to execute '%s %s %s'", + argv[0], argv[1], argv[2]); + } else { + if (waitpid(pid, &status, 0) != -1 && status != 0) { + warnx("'%s %s %s' exit status %d\n", + argv[0], argv[1], argv[2], status); + } + } + return (skipped); }