diff --git a/Base/usr/share/man/man8/groupdel.md b/Base/usr/share/man/man8/groupdel.md new file mode 100644 index 0000000000..133738e8f2 --- /dev/null +++ b/Base/usr/share/man/man8/groupdel.md @@ -0,0 +1,45 @@ +## Name + +groupdel - delete a group + +## Synopsis + +```**sh +# groupdel +``` + +## Description + +This program deletes a group in the system. + +This program must be run as root. + +## Caveats + +You may not remove the primary group of any existing user. You must remove the user before you remove the group. + +You should manually check all file systems to ensure that no files remain owned by this group. + +You should manually check all users to ensure that no user remain in this group. + +## Exit Values + +* 0 - Success +* 1 - Couldn't update the group file +* 6 - Specified group doesn't exist +* 8 - Can't remove user's primary group + +## Files + +* `/etc/group` - group information (such as GID) in this file is deleted. + +## Examples + +```sh +# groupdel alice +``` + +## See Also + +* [`useradd`(8)](groupadd.md) + diff --git a/Userland/Utilities/groupdel.cpp b/Userland/Utilities/groupdel.cpp new file mode 100644 index 0000000000..a22dee2e52 --- /dev/null +++ b/Userland/Utilities/groupdel.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020, Fei Wu + * Copyright (c) 2021, Brandon Pruitt + * Copyright (c) 2021, Maxime Friess + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio wpath rpath cpath fattr proc exec", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/etc/", "rwc") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/bin/rm", "x") < 0) { + perror("unveil"); + return 1; + } + + char const* groupname = nullptr; + gid_t gid = 0; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(groupname, "Group name", "group"); + args_parser.parse(argc, argv); + + setgrent(); + auto* g = getgrnam(groupname); + gid = g->gr_gid; + endgrent(); + + // Check if the group exists + if (!g) { + warnln("group {} does not exist", groupname); + return 6; + } + + // Search if the group is the primary group of an user + setpwent(); + struct passwd* pw; + for (pw = getpwent(); pw; pw = getpwent()) { + if (pw->pw_gid == gid) + break; + } + + // If pw is not NULL it means we ended prematuraly, aka. the group was found as primary group of an user + if (pw) { + warnln("cannot remove the primary group of user '{}'", pw->pw_name); + endpwent(); + return 8; + } + + endpwent(); + // We can now safely delete the group + + // Create a temporary group file + char temp_group[] = "/etc/group.XXXXXX"; + + auto unlink_temp_files = [&] { + if (unlink(temp_group) < 0) + perror("unlink"); + }; + + ArmedScopeGuard unlink_temp_files_guard = [&] { + unlink_temp_files(); + }; + + auto temp_group_fd = mkstemp(temp_group); + if (temp_group_fd == -1) { + perror("failed to create temporary group file"); + return 1; + } + + FILE* temp_group_file = fdopen(temp_group_fd, "w"); + if (!temp_group_file) { + perror("fdopen"); + return 1; + } + + setgrent(); + for (auto* gr = getgrent(); gr; gr = getgrent()) { + if (gr->gr_gid != gid) { + if (putgrent(gr, temp_group_file) != 0) { + perror("failed to put an entry in the temporary group file"); + return 1; + } + } + } + endgrent(); + + if (fclose(temp_group_file)) { + perror("fclose"); + return 1; + } + + if (chmod(temp_group, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) { + perror("chmod"); + return 1; + } + + if (rename(temp_group, "/etc/group") < 0) { + perror("failed to rename the temporary group file"); + return 1; + } + + unlink_temp_files_guard.disarm(); + + return 0; +}