commit: 2379db5af2efc295cc3f138c4c1cbc06ac082840
parent 0eb20a5bb5c4019d5dc8523e4e38833efd118fb8
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Sat, 12 Mar 2022 01:27:21 +0100
bin/id: Add
Diffstat:
4 files changed, 327 insertions(+), 1 deletion(-)
diff --git a/bin/id.c b/bin/id.c
@@ -0,0 +1,269 @@
+// Collection of Unix tools, comparable to coreutils
+// Copyright 2017-2022 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+
+#define _POSIX_C_SOURCE 200809L
+#define _DEFAULT_SOURCE // for getgrouplist
+
+#include <assert.h> // assert
+#include <grp.h> // getgrgid, getgrouplist
+#include <pwd.h> // getpwuid
+#include <stdbool.h> // bool
+#include <stdio.h> // printf, perror
+#include <stdlib.h> // malloc, free
+#include <sys/types.h> // uid_t
+#include <unistd.h> // getuid, getgid, getopt, opt*
+
+bool name_flag = false;
+
+static int
+list_groups(struct passwd *pw)
+{
+ int ngroups = (int)sysconf(_SC_NGROUPS_MAX) + 1;
+ gid_t *groups = malloc(sizeof(gid_t) * ngroups);
+ if(groups == NULL)
+ {
+ perror("groups malloc");
+ return 1;
+ }
+
+ // getgrouplist(3) BSD extension might not be in all Unixes
+ int grls = getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups);
+ if(grls >= 0)
+ {
+ printf(" groups=");
+
+ for(int i = 0; i < ngroups; i++)
+ {
+ struct group *lgr = getgrgid(groups[i]);
+
+ if(name_flag)
+ {
+ if(lgr == NULL)
+ {
+ goto failure;
+ }
+
+ int sep = ' ';
+ if(i == ngroups - 1)
+ {
+ sep = '\0';
+ }
+
+ int ret = printf("%s%c", lgr->gr_name, sep);
+ if(ret < 0)
+ {
+ goto failure;
+ }
+ }
+ else
+ {
+ int ret = printf("%d", groups[i]);
+ if(ret < 0)
+ {
+ goto failure;
+ }
+
+ if(lgr != NULL)
+ {
+ int ret = printf("(%s)", lgr->gr_name);
+ if(ret < 0)
+ {
+ goto failure;
+ }
+ }
+
+ if(i != ngroups - 1)
+ {
+ printf(",");
+ }
+ }
+ }
+ }
+
+ free(groups);
+ return 0;
+
+failure:
+ free(groups);
+ return 1;
+}
+
+int
+print_gid(char *field, char *name, gid_t gid)
+{
+ if(name_flag)
+ {
+ if(name)
+ {
+ return printf("%s=%s", field, name);
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ if(name)
+ {
+ return printf("%s=%d(%s)", field, gid, name);
+ }
+ else
+ {
+ return printf("%s=%d", field, gid);
+ }
+}
+
+int
+print_uid(char *field, char *name, uid_t uid)
+{
+ if(name_flag)
+ {
+ if(name)
+ {
+ return printf("%s=%s", field, name);
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ if(name)
+ {
+ return printf("%s=%d(%s)", field, uid, name);
+ }
+ else
+ {
+ return printf("%s=%d", field, uid);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ret, c;
+ bool euid_flag = false;
+ bool G_flag = false;
+
+ /* flawfinder: ignore. Old implementations of getopt should fix themselves */
+ while((c = getopt(argc, argv, ":Ggunr")) != EOF)
+ {
+ switch(c)
+ {
+ case 'G':
+ G_flag = true;
+ break;
+ case 'n':
+ name_flag = true;
+ break;
+ case 'u':
+ euid_flag = true;
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ // geteuid, getuid, getegid, getgid shall always be successful
+ uid_t uid = getuid();
+ uid_t euid = geteuid();
+ gid_t gid = getgid();
+ gid_t egid = getegid();
+
+ struct passwd *pw = getpwuid(uid);
+ struct passwd *epw = getpwuid(euid);
+ // can return NULL (ie. without /etc/passwd)
+
+ if(pw != NULL)
+ {
+ assert(pw->pw_uid == uid);
+
+ ret = print_uid("uid", pw->pw_name, uid);
+ }
+ else
+ {
+ ret = print_uid("uid", NULL, uid);
+ }
+
+ if(ret < 0)
+ {
+ return 1;
+ }
+
+ if(euid != uid)
+ {
+ if(epw != NULL)
+ {
+ assert(epw->pw_uid == euid);
+
+ ret = print_uid(" euid", epw->pw_name, euid);
+ }
+ else
+ {
+ ret = print_uid(" euid", NULL, euid);
+ }
+
+ if(ret < 0)
+ {
+ return 1;
+ }
+ }
+
+ struct group *gr = getgrgid(gid);
+
+ if(gr != NULL)
+ {
+ assert(gr->gr_gid == gid);
+
+ ret = print_gid(" gid", gr->gr_name, gid);
+ }
+ else
+ {
+ ret = print_gid(" gid", NULL, gid);
+ }
+
+ if(ret < 0)
+ {
+ return 1;
+ }
+
+ struct group *egr = getgrgid(egid);
+ if(egid != gid)
+ {
+ if(egr != NULL)
+ {
+ assert(egr->gr_gid == egid);
+
+ ret = print_gid(" egid", egr->gr_name, egid);
+ }
+ else
+ {
+ ret = print_gid(" egid", NULL, egid);
+ }
+
+ if(ret < 0)
+ {
+ return 1;
+ }
+ }
+
+ if(pw != NULL)
+ {
+ if(list_groups(pw) != 0)
+ {
+ return 1;
+ }
+ }
+
+ ret = printf("\n");
+ if(ret < 0)
+ {
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/coreutils.txt b/coreutils.txt
@@ -33,7 +33,7 @@ fmt: ?
fold: ?
head: No, use sed
hostid: No, get a better id(ea)
-id: Todo
+id: Done
install: No?
join: ?
link: Todo
diff --git a/test-bin/Kyuafile b/test-bin/Kyuafile
@@ -16,6 +16,7 @@ atf_test_program{name="env", required_files=basedir.."/bin/env"}
atf_test_program{name="errno", required_files=basedir.."/bin/errno"}
atf_test_program{name="false", required_files=basedir.."/bin/false"}
atf_test_program{name="humanize", required_files=basedir.."/bin/humanize"}
+atf_test_program{name="id", required_files=basedir.."/bin/id"}
atf_test_program{name="mdate", required_files=basedir.."/bin/mdate"}
atf_test_program{name="pwd", required_files=basedir.."/bin/pwd"}
atf_test_program{name="seq", required_files=basedir.."/bin/seq"}
diff --git a/test-bin/id b/test-bin/id
@@ -0,0 +1,56 @@
+#!/usr/bin/env atf-sh
+atf_test_case noargs cleanup
+noargs_body() {
+ atf_check -o save:noargs.out ../bin/id
+ atf_check grep -q "uid=$(id -u)($(id -un)) gid=$(id -g)($(id -gn)) groups=" noargs.out
+}
+noargs_cleanup() {
+ rm -f noargs.out
+}
+
+atf_test_case names cleanup
+names_body() {
+ atf_check -o save:names.out ../bin/id -n
+ atf_check grep -q "uid=$(id -un) gid=$(id -gn) groups=" names.out
+}
+names_cleanup() {
+ rm -f names.out
+}
+
+atf_test_case devfull
+devfull_body() {
+ has_glibc && atf_expect_fail "glibc ignoring write errors for puts()"
+ [ "$(uname -s)" = "NetBSD" ] && atf_expect_fail "NetBSD ignoring write errors for puts()"
+ [ "$(uname -s)" = "FreeBSD" ] && atf_expect_fail "FreeBSD ignoring write errors for puts()"
+
+ atf_check -s exit:1 sh -c '../bin/id >/dev/full'
+ atf_check -s exit:1 sh -c '../bin/id -n >/dev/full'
+}
+
+atf_test_case noetc
+noetc_body() {
+ bwrap_args="--ro-bind-try /usr /usr --ro-bind-try /lib /lib --ro-bind-try /lib64 /lib64 --ro-bind ../bin /bin"
+
+ command -v bwrap >/dev/null 2>/dev/null || atf_skip "'bwrap' command not found"
+
+ set -f
+
+ # shellcheck disable=SC2086
+ atf_check -o "inline:uid=$(id -u) gid=$(id -g)\n" -x -- bwrap ${bwrap_args} /bin/id
+
+ # shellcheck disable=SC2086
+ atf_check -s exit:1 -x -- bwrap ${bwrap_args} /bin/id -n
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)" || exit 1
+
+ . ../test_functions.sh
+
+ atf_add_test_case devfull
+
+ atf_add_test_case noargs
+ atf_add_test_case names
+
+ atf_add_test_case noetc
+}