git: 6b6aa45f9a1a - main - pw: fix at job removal when deleting a user
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 04 Sep 2026 14:28:56 UTC
The branch main has been updated by bapt:
URL: https://cgit.FreeBSD.org/src/commit/?id=6b6aa45f9a1a35acb7fef68824c1a5eb08503909
commit 6b6aa45f9a1a35acb7fef68824c1a5eb08503909
Author: Baptiste Daroussin <bapt@FreeBSD.org>
AuthorDate: 2026-09-04 12:12:03 +0000
Commit: Baptiste Daroussin <bapt@FreeBSD.org>
CommitDate: 2026-09-04 14:28:47 +0000
pw: fix at job removal when deleting a user
rmat() used stat() with a path relative to the current working
directory, so it never found the job files in /var/at/jobs and the
at(1) jobs of a deleted user were never removed.
ef7d0eb9489f also broke it by introducing a typo: /usr/sbin/atrm instead
of /usr/bin/artm.
Use fstatat() with the directory fd to stat the job files relative to
the at jobs directory, and unlinkat() them directly instead of spawning
atrm.
Those changes allow us to make it works with pw -R.
MFC After: 1 week
---
usr.sbin/pw/pw_user.c | 27 +++++++++++++--------------
usr.sbin/pw/tests/pw_userdel_test.sh | 29 +++++++++++++++++++++++++++++
2 files changed, 42 insertions(+), 14 deletions(-)
diff --git a/usr.sbin/pw/pw_user.c b/usr.sbin/pw/pw_user.c
index 269344232912..9c48ef3fcb5e 100644
--- a/usr.sbin/pw/pw_user.c
+++ b/usr.sbin/pw/pw_user.c
@@ -680,31 +680,30 @@ pw_checkname(char *name, int gecos)
static void
rmat(uid_t uid)
{
- DIR *d = opendir("/var/at/jobs");
+ DIR *d;
struct dirent *e;
- const char *argv[] = { "/usr/sbin/atrm", NULL, NULL };
+ int atfd;
- if (d == NULL)
+ atfd = openat(conf.rootfd, "var/at/jobs", O_DIRECTORY | O_CLOEXEC);
+ if (atfd == -1)
return;
+ d = fdopendir(atfd);
+ if (d == NULL) {
+ close(atfd);
+ return;
+ }
while ((e = readdir(d)) != NULL) {
struct stat st;
- pid_t pid;
if (strncmp(e->d_name, ".lock", 5) == 0)
continue;
- if (stat(e->d_name, &st) != 0)
+ if (fstatat(atfd, e->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0)
continue;
if (S_ISDIR(st.st_mode) || st.st_uid != uid)
continue;
- argv[1] = e->d_name;
- 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);
- }
+ if (unlinkat(atfd, e->d_name, 0) != 0)
+ warn("Failed to remove at job '%s'", e->d_name);
}
closedir(d);
}
@@ -1008,7 +1007,7 @@ pw_user_del(int argc, char **argv, char *arg1)
unlinkat(conf.rootfd, file + 1, 0);
/* Remove at jobs */
- if (!PWALTDIR() && getpwuid(id) == NULL)
+ if (PWALTDIR() != PWF_ALT && GETPWUID(id) == NULL)
rmat(id);
/* Remove home directory and contents */
diff --git a/usr.sbin/pw/tests/pw_userdel_test.sh b/usr.sbin/pw/tests/pw_userdel_test.sh
index 2e11c3534d58..647384615fb4 100755
--- a/usr.sbin/pw/tests/pw_userdel_test.sh
+++ b/usr.sbin/pw/tests/pw_userdel_test.sh
@@ -82,6 +82,34 @@ home_regular_dir_body() {
[ ! -d ${HOME}/foo ] || atf_fail "Home has not been removed"
}
+atf_test_case delete_at_jobs cleanup
+delete_at_jobs_body() {
+ populate_root_etc_skel
+
+ mkdir -p ${HOME}/var/at/jobs
+
+ atf_check -s exit:0 ${RPW} useradd foo
+
+ uid=$(awk -F: '/^foo:/ {print $3}' ${HOME}/etc/master.passwd)
+
+ job="c00001000000000"
+ atf_check -s exit:0 touch ${HOME}/var/at/jobs/${job}
+ atf_check -s exit:0 chown ${uid} ${HOME}/var/at/jobs/${job}
+
+ # A job owned by root must not be removed
+ otherjob="c00002000000000"
+ atf_check -s exit:0 touch ${HOME}/var/at/jobs/${otherjob}
+ atf_check -s exit:0 chown 0 ${HOME}/var/at/jobs/${otherjob}
+
+ atf_check -s exit:0 ${RPW} userdel foo
+
+ [ ! -e ${HOME}/var/at/jobs/${job} ] || atf_fail "at job not removed"
+ [ -e ${HOME}/var/at/jobs/${otherjob} ] || atf_fail "at job of another user was removed"
+}
+delete_at_jobs_cleanup() {
+ rm -rf ${HOME}/var/at/jobs
+}
+
atf_init_test_cases() {
atf_add_test_case rmuser_seperate_group
atf_add_test_case user_do_not_try_to_delete_root_if_user_unknown
@@ -90,4 +118,5 @@ atf_init_test_cases() {
atf_add_test_case home_not_a_dir
atf_add_test_case home_shared
atf_add_test_case home_regular_dir
+ atf_add_test_case delete_at_jobs
}