/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8   -*-â
âvi: set et ft=c ts=8 tw=8 fenc=utf-8                                       :viâ
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â                                                                              â
â  Musl Libc                                                                   â
â  Copyright Â© 2005-2014 Rich Felker, et al.                                   â
â                                                                              â
â  Permission is hereby granted, free of charge, to any person obtaining       â
â  a copy of this software and associated documentation files (the             â
â  "Software"), to deal in the Software without restriction, including         â
â  without limitation the rights to use, copy, modify, merge, publish,         â
â  distribute, sublicense, and/or sell copies of the Software, and to          â
â  permit persons to whom the Software is furnished to do so, subject to       â
â  the following conditions:                                                   â
â                                                                              â
â  The above copyright notice and this permission notice shall be              â
â  included in all copies or substantial portions of the Software.             â
â                                                                              â
â  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,             â
â  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF          â
â  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.      â
â  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY        â
â  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,        â
â  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE           â
â  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                      â
â                                                                              â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ*/
#include "libc/calls/weirdtypes.h"
#include "libc/errno.h"
#include "libc/mem/mem.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/thread/thread.h"
#include "third_party/musl/passwd.h"

asm(".ident\t\"\\n\\n\
Musl libc (MIT License)\\n\
Copyright 2005-2014 Rich Felker, et. al.\"");
asm(".include \"libc/disclaimer.inc\"");
/* clang-format off */

static unsigned
atou(char **s)
{
	unsigned x;
	for (x = 0; **s - '0' < 10U; ++*s) {
		x = 10 * x + (**s - '0');
	}
	return x;
}

static int
__getpwent_a(FILE *f, struct passwd *pw, char **line, size_t *size,
	     struct passwd **res)
{
	ssize_t l;
	char *s;
	int rv = 0;
	int cs;
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
	for (;;) {
		if ((l = getline(line, size, f)) < 0) {
			rv = ferror(f) ? errno : 0;
			free(*line);
			*line = 0;
			pw = 0;
			break;
		}
		line[0][l - 1] = 0;
		s = line[0];
		pw->pw_name = s++;
		if (!(s = strchr(s, ':'))) continue;
		*s++ = 0;
		pw->pw_passwd = s;
		if (!(s = strchr(s, ':'))) continue;
		*s++ = 0;
		pw->pw_uid = atou(&s);
		if (*s != ':') continue;
		*s++ = 0;
		pw->pw_gid = atou(&s);
		if (*s != ':') continue;
		*s++ = 0;
		pw->pw_gecos = s;
		if (!(s = strchr(s, ':'))) continue;
		*s++ = 0;
		pw->pw_dir = s;
		if (!(s = strchr(s, ':'))) continue;
		*s++ = 0;
		pw->pw_shell = s;
		break;
	}
	pthread_setcancelstate(cs, 0);
	*res = pw;
	if (rv) errno = rv;
	return rv;
}

static int
__getpw_a(const char *name, uid_t uid, struct passwd *pw, char **buf,
	  size_t *size, struct passwd **res)
{
	FILE *f;
	int cs;
	int rv = 0;
	*res = 0;
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
	if ((f = fopen("/etc/passwd", "rbe"))) {
		while (!(rv = __getpwent_a(f, pw, buf, size, res)) && *res) {
			if ((name && !strcmp(name, (*res)->pw_name)) ||
			    (!name && (*res)->pw_uid == uid)) {
				break;
			}
		}
		fclose(f);
	}
	pthread_setcancelstate(cs, 0);
	if (rv) errno = rv;
	return rv;
}

static int
getpw_r(const char *name, uid_t uid, struct passwd *pw, char *buf,
	size_t size, struct passwd **res)
{
#define FIX(x) (pw->pw_##x = pw->pw_##x - line + buf)
	char *line = 0;
	size_t len = 0;
	int rv = 0;
	int cs;
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
	rv = __getpw_a(name, uid, pw, &line, &len, res);
	if (*res && size < len) {
		*res = 0;
		rv = ERANGE;
	}
	if (*res) {
		memcpy(buf, line, len);
		FIX(name);
		FIX(passwd);
		FIX(gecos);
		FIX(dir);
		FIX(shell);
	}
	free(line);
	pthread_setcancelstate(cs, 0);
	if (rv) errno = rv;
	return rv;
#undef FIX
}

int
getpwnam_r(const char *name, struct passwd *pw, char *buf, size_t size,
	   struct passwd **res)
{
	return getpw_r(name, 0, pw, buf, size, res);
}

int
getpwuid_r(uid_t uid, struct passwd *pw, char *buf, size_t size,
	   struct passwd **res)
{
	return getpw_r(0, uid, pw, buf, size, res);
}

static struct GetpwentState {
	FILE *f;
	char *line;
	struct passwd pw;
	size_t size;
} g_getpwent[1];

void
endpwent()
{
	setpwent();
}

void
setpwent()
{
	if (g_getpwent->f) fclose(g_getpwent->f);
	g_getpwent->f = 0;
}

struct passwd *
getpwent()
{
	struct passwd *res;
	if (!g_getpwent->f) g_getpwent->f = fopen("/etc/passwd", "rbe");
	if (!g_getpwent->f) return 0;
	__getpwent_a(g_getpwent->f, &g_getpwent->pw, &g_getpwent->line,
		     &g_getpwent->size, &res);
	return res;
}

struct passwd *
getpwuid(uid_t uid)
{
	struct passwd *res;
	__getpw_a(0, uid, &g_getpwent->pw, &g_getpwent->line, &g_getpwent->size,
		  &res);
	return res;
}

struct passwd *
getpwnam(const char *name)
{
	struct passwd *res;
	__getpw_a(name, 0, &g_getpwent->pw, &g_getpwent->line,
		  &g_getpwent->size, &res);
	return res;
}
