/*
 * <pwd.h> wrapper functions.
 *
 * Authors:
 *   Jonathan Pryor (jonpryor@vt.edu)
 *
 * Copyright (C) 2004-2005 Jonathan Pryor
 */

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

#include "mph.h" /* Don't remove or move after map.h! Works around issues with Android SDK unified headers */
#include "map.h"

G_BEGIN_DECLS

static const mph_string_offset_t
passwd_offsets[] = {
	MPH_STRING_OFFSET (struct passwd, pw_name,    MPH_STRING_OFFSET_PTR),
	MPH_STRING_OFFSET (struct passwd, pw_passwd,  MPH_STRING_OFFSET_PTR),
#if HAVE_STRUCT_PASSWD_PW_GECOS
	MPH_STRING_OFFSET (struct passwd, pw_gecos,   MPH_STRING_OFFSET_PTR),
#endif  /* def HAVE_STRUCT_PASSWD_PW_GECOS */
	MPH_STRING_OFFSET (struct passwd, pw_dir,     MPH_STRING_OFFSET_PTR),
	MPH_STRING_OFFSET (struct passwd, pw_shell,   MPH_STRING_OFFSET_PTR)
};

static const mph_string_offset_t
mph_passwd_offsets[] = {
	MPH_STRING_OFFSET (struct Mono_Posix_Syscall__Passwd, pw_name,    MPH_STRING_OFFSET_PTR),
	MPH_STRING_OFFSET (struct Mono_Posix_Syscall__Passwd, pw_passwd,  MPH_STRING_OFFSET_PTR),
#if HAVE_STRUCT_PASSWD_PW_GECOS
	MPH_STRING_OFFSET (struct Mono_Posix_Syscall__Passwd, pw_gecos,   MPH_STRING_OFFSET_PTR),
#endif  /* def HAVE_STRUCT_PASSWD_PW_GECOS */
	MPH_STRING_OFFSET (struct Mono_Posix_Syscall__Passwd, pw_dir,     MPH_STRING_OFFSET_PTR),
	MPH_STRING_OFFSET (struct Mono_Posix_Syscall__Passwd, pw_shell,   MPH_STRING_OFFSET_PTR)
};

/*
 * Copy the native `passwd' structure to it's managed representation.
 *
 * To minimize separate mallocs, all the strings are allocated within the same
 * memory block (stored in _pw_buf_).
 */
static int
copy_passwd (struct Mono_Posix_Syscall__Passwd *to, struct passwd *from)
{
	char *buf;
	buf = _mph_copy_structure_strings (to, mph_passwd_offsets,
			from, passwd_offsets, sizeof(passwd_offsets)/sizeof(passwd_offsets[0]));

	to->pw_uid    = from->pw_uid;
	to->pw_gid    = from->pw_gid;

	to->_pw_buf_ = buf;
	if (buf == NULL) {
		return -1;
	}

	return 0;
}

gint32
Mono_Posix_Syscall_getpwnam (const char *name, struct Mono_Posix_Syscall__Passwd *pwbuf)
{
	struct passwd *pw;

	if (pwbuf == NULL) {
		errno = EFAULT;
		return -1;
	}

	errno = 0;
	pw = getpwnam (name);
	if (pw == NULL)
		return -1;

	if (copy_passwd (pwbuf, pw) == -1) {
		errno = ENOMEM;
		return -1;
	}
	return 0;
}

gint32
Mono_Posix_Syscall_getpwuid (mph_uid_t uid, struct Mono_Posix_Syscall__Passwd *pwbuf)
{
	struct passwd *pw;

	if (pwbuf == NULL) {
		errno = EFAULT;
		return -1;
	}

	errno = 0;
	pw = getpwuid (uid);
	if (pw == NULL) {
		return -1;
	}

	if (copy_passwd (pwbuf, pw) == -1) {
		errno = ENOMEM;
		return -1;
	}
	return 0;
}

#ifdef HAVE_GETPWNAM_R
gint32
Mono_Posix_Syscall_getpwnam_r (const char *name, 
	struct Mono_Posix_Syscall__Passwd *pwbuf,
	void **pwbufp)
{
	char *buf, *buf2;
	size_t buflen;
	int r;
	struct passwd _pwbuf;

	if (pwbuf == NULL) {
		errno = EFAULT;
		return -1;
	}

	buf = buf2 = NULL;
	buflen = 2;

	do {
		buf2 = realloc (buf, buflen *= 2);
		if (buf2 == NULL) {
			free (buf);
			errno = ENOMEM;
			return -1;
		}
		buf = buf2;
		errno = 0;
	} while ((r = getpwnam_r (name, &_pwbuf, buf, buflen, (struct passwd**) pwbufp)) && 
			recheck_range (r));

	if (r == 0 && !(*pwbufp))
		/* On solaris, this function returns 0 even if the entry was not found */
		r = errno = ENOENT;

	if (r == 0 && copy_passwd (pwbuf, &_pwbuf) == -1)
		r = errno = ENOMEM;
	free (buf);

	return r;
}
#endif /* ndef HAVE_GETPWNAM_R */

#ifdef HAVE_GETPWUID_R
gint32
Mono_Posix_Syscall_getpwuid_r (mph_uid_t uid,
	struct Mono_Posix_Syscall__Passwd *pwbuf,
	void **pwbufp)
{
	char *buf, *buf2;
	size_t buflen;
	int r;
	struct passwd _pwbuf;

	if (pwbuf == NULL) {
		errno = EFAULT;
		return -1;
	}

	buf = buf2 = NULL;
	buflen = 2;

	do {
		buf2 = realloc (buf, buflen *= 2);
		if (buf2 == NULL) {
			free (buf);
			errno = ENOMEM;
			return -1;
		}
		buf = buf2;
		errno = 0;
	} while ((r = getpwuid_r (uid, &_pwbuf, buf, buflen, (struct passwd**) pwbufp)) && 
			recheck_range (r));

	if (r == 0 && !(*pwbufp))
		/* On solaris, this function returns 0 even if the entry was not found */
		r = errno = ENOENT;

	if (r == 0 && copy_passwd (pwbuf, &_pwbuf) == -1)
		r = errno = ENOMEM;
	free (buf);

	return r;
}
#endif /* ndef HAVE_GETPWUID_R */

#if HAVE_GETPWENT
gint32
Mono_Posix_Syscall_getpwent (struct Mono_Posix_Syscall__Passwd *pwbuf)
{
	struct passwd *pw;

	if (pwbuf == NULL) {
		errno = EFAULT;
		return -1;
	}

	errno = 0;
	pw = getpwent ();
	if (pw == NULL)
		return -1;

	if (copy_passwd (pwbuf, pw) == -1) {
		errno = ENOMEM;
		return -1;
	}
	return 0;
}
#endif  /* def HAVE_GETPWENT */

#ifdef HAVE_FGETPWENT
gint32
Mono_Posix_Syscall_fgetpwent (void *stream, struct Mono_Posix_Syscall__Passwd *pwbuf)
{
	struct passwd *pw;

	if (pwbuf == NULL) {
		errno = EFAULT;
		return -1;
	}

	errno = 0;
	pw = fgetpwent ((FILE*) stream);
	if (pw == NULL)
		return -1;

	if (copy_passwd (pwbuf, pw) == -1) {
		errno = ENOMEM;
		return -1;
	}
	return 0;
}
#endif /* ndef HAVE_FGETPWENT */

#if HAVE_SETPWENT
int
Mono_Posix_Syscall_setpwent (void)
{
	errno = 0;
	do {
		setpwent ();
	} while (errno == EINTR);
	mph_return_if_val_in_list5(errno, EIO, EMFILE, ENFILE, ENOMEM, ERANGE);
	return 0;
}
#endif  /* def HAVE_SETPWENT */

#if HAVE_ENDPWENT
int
Mono_Posix_Syscall_endpwent (void)
{
	errno = 0;
	endpwent ();
	if (errno == EIO)
		return -1;
	return 0;
}
#endif  /* def HAVE_ENDPWENT */

G_END_DECLS

/*
 * vim: noexpandtab
 */