#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static struct passwd pwd;
static FILE* f { nullptr };
static char buf[4096];

// Variant of strtok that returns empty tokens when there are several delimiters, instead of skipping all at once.
// Example:
// strtok("hello:world::!:", ":") -> "hello", "world", "!"
// _strtok_once("hello:world::!:", ":") -> "hello", "world", "", "!"
char* _strtok_once(char* str, const char* delim)
{
    static char* s = nullptr;
    if (str) s = str;
    if (!s) return nullptr;

    if (*s)
    {
        if (*s == 0) return nullptr;

        size_t use = strcspn(s, delim);
        char* result = s;

        if (s[use] != 0)
        {
            s[use] = 0;
            s += (use + 1);
        }
        else { s = nullptr; }

        return result;
    }

    return nullptr;
}

extern "C"
{
    struct passwd* getpwent()
    {
        if (!f)
        {
            f = fopen("/etc/passwd", "r");
            if (!f) return nullptr;
            fcntl(fileno(f), F_SETFD, FD_CLOEXEC);
        }

        while (true)
        {
            char* rc = fgets(buf, sizeof(buf), f);
            if (!rc) return nullptr;

            char* name = _strtok_once(rc, ":\n");
            if (!name) continue;

            char* passwd = _strtok_once(nullptr, ":\n");
            if (!passwd) continue;

            char* uid = _strtok_once(nullptr, ":\n");
            if (!uid) continue;

            char* gid = _strtok_once(nullptr, ":\n");
            if (!gid) continue;

            char* gecos = _strtok_once(nullptr, ":\n");
            if (!gecos) continue;

            char* dir = _strtok_once(nullptr, ":\n");
            if (!dir) continue;

            char* shell = _strtok_once(nullptr, ":\n");
            if (!shell) continue;

            pwd.pw_name = name;
            pwd.pw_passwd = passwd;
            pwd.pw_uid = (uid_t)strtoul(uid, NULL, 10);
            pwd.pw_gid = (gid_t)strtoul(gid, NULL, 10);
            pwd.pw_gecos = gecos;
            pwd.pw_dir = dir;
            pwd.pw_shell = shell;

            return &pwd;
        }
    }

    struct passwd* getpwnam(const char* name)
    {
        setpwent();

        struct passwd* entry;

        while ((entry = getpwent()))
        {
            if (!strcmp(entry->pw_name, name)) { return entry; }
        }

        return entry;
    }

    struct passwd* getpwuid(uid_t uid)
    {
        setpwent();

        struct passwd* entry;

        while ((entry = getpwent()))
        {
            if (entry->pw_uid == uid) { return entry; }
        }

        return entry;
    }

    void setpwent()
    {
        if (f) rewind(f);
    }

    void endpwent()
    {
        if (f)
        {
            fclose(f);
            f = nullptr;
        }
    }
}