/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:2;tab-width:8;coding:utf-8   -*-│
│vi: set et ft=c ts=2 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 "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\"");

#define PTHREAD_CANCEL_DISABLE       0
#define pthread_setcancelstate(x, y) (void)y

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

static int __getgrent_a(FILE *f, struct group *gr, char **line, size_t *size,
                        char ***mem, size_t *nmem, struct group **res) {
  ssize_t l;
  char *s, *mems;
  size_t i;
  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;
      gr = 0;
      goto end;
    }
    line[0][l - 1] = 0;
    s = line[0];
    gr->gr_name = s++;
    if (!(s = strchr(s, ':'))) continue;
    *s++ = 0;
    gr->gr_passwd = s;
    if (!(s = strchr(s, ':'))) continue;
    *s++ = 0;
    gr->gr_gid = atou(&s);
    if (*s != ':') continue;
    *s++ = 0;
    mems = s;
    break;
  }
  for (*nmem = !!*s; *s; s++)
    if (*s == ',') ++*nmem;
  free(*mem);
  *mem = calloc(sizeof(char *), *nmem + 1);
  if (!*mem) {
    rv = errno;
    free(*line);
    *line = 0;
    gr = 0;
    goto end;
  }
  if (*mems) {
    mem[0][0] = mems;
    for (s = mems, i = 0; *s; s++)
      if (*s == ',') *s++ = 0, mem[0][++i] = s;
    mem[0][++i] = 0;
  } else {
    mem[0][0] = 0;
  }
  gr->gr_mem = *mem;
end:
  pthread_setcancelstate(cs, 0);
  *res = gr;
  if (rv) errno = rv;
  return rv;
}

static int __getgr_a(const char *name, gid_t gid, struct group *gr, char **buf,
                     size_t *size, char ***mem, size_t *nmem,
                     struct group **res) {
  FILE *f;
  int rv = 0;
  int cs;
  *res = 0;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
  f = fopen("/etc/group", "rbe");
  if (!f) {
    rv = errno;
    goto done;
  }
  while (!(rv = __getgrent_a(f, gr, buf, size, mem, nmem, res)) && *res) {
    if ((name && !strcmp(name, (*res)->gr_name)) ||
        (!name && (*res)->gr_gid == gid)) {
      break;
    }
  }
done:
  pthread_setcancelstate(cs, 0);
  if (rv) errno = rv;
  return rv;
}

static int getgr_r(const char *name, gid_t gid, struct group *gr, char *buf,
                   size_t size, struct group **res) {
#define FIX(x) (gr->gr_##x = gr->gr_##x - line + buf)
  char *line = 0;
  size_t len = 0;
  char **mem = 0;
  size_t nmem = 0;
  int rv = 0;
  size_t i;
  int cs;
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
  rv = __getgr_a(name, gid, gr, &line, &len, &mem, &nmem, res);
  if (*res && size < len + (nmem + 1) * sizeof(char *) + 32) {
    *res = 0;
    rv = ERANGE;
  }
  if (*res) {
    buf += (16 - (uintptr_t)buf) % 16;
    gr->gr_mem = (void *)buf;
    buf += (nmem + 1) * sizeof(char *);
    memcpy(buf, line, len);
    FIX(name);
    FIX(passwd);
    for (i = 0; mem[i]; i++) gr->gr_mem[i] = mem[i] - line + buf;
    gr->gr_mem[i] = 0;
  }
  free(mem);
  free(line);
  pthread_setcancelstate(cs, 0);
  if (rv) errno = rv;
  return rv;
#undef FIX
}

int getgrouplist(const char *user, gid_t gid, gid_t *groups, int *ngroups) {
  int rv, nlim, ret = -1;
  ssize_t i, n = 1;
  struct group gr;
  struct group *res;
  FILE *f;
  char *buf = 0;
  char **mem = 0;
  size_t nmem = 0;
  size_t size;
  nlim = *ngroups;
  if (nlim >= 1) *groups++ = gid;
  f = fopen("/etc/group", "rbe");
  if (!f && errno != ENOENT && errno != ENOTDIR) goto cleanup;
  if (f) {
    while (!(rv = __getgrent_a(f, &gr, &buf, &size, &mem, &nmem, &res)) &&
           res) {
      for (i = 0; gr.gr_mem[i] && strcmp(user, gr.gr_mem[i]); i++)
        ;
      if (!gr.gr_mem[i]) continue;
      if (++n <= nlim) *groups++ = gr.gr_gid;
    }
    if (rv) {
      errno = rv;
      goto cleanup;
    }
  }
  ret = n > nlim ? -1 : n;
  *ngroups = n;
cleanup:
  if (f) fclose(f);
  free(buf);
  free(mem);
  return ret;
}

int getgrnam_r(const char *name, struct group *gr, char *buf, size_t size,
               struct group **res) {
  return getgr_r(name, 0, gr, buf, size, res);
}

int getgrgid_r(gid_t gid, struct group *gr, char *buf, size_t size,
               struct group **res) {
  return getgr_r(0, gid, gr, buf, size, res);
}

static struct GetgrentState {
  FILE *f;
  char *line;
  char **mem;
  struct group gr;
} g_getgrent[1];

void endgrent() {
  setgrent();
}
void setgrent() {
  if (g_getgrent->f) fclose(g_getgrent->f);
  g_getgrent->f = 0;
}

struct group *getgrent() {
  struct group *res;
  size_t size = 0, nmem = 0;
  if (!g_getgrent->f) g_getgrent->f = fopen("/etc/group", "rbe");
  if (!g_getgrent->f) return 0;
  __getgrent_a(g_getgrent->f, &g_getgrent->gr, &g_getgrent->line, &size,
               &g_getgrent->mem, &nmem, &res);
  return res;
}

struct group *getgrgid(gid_t gid) {
  struct group *res;
  size_t size = 0, nmem = 0;
  __getgr_a(0, gid, &g_getgrent->gr, &g_getgrent->line, &size, &g_getgrent->mem,
            &nmem, &res);
  return res;
}

struct group *getgrnam(const char *name) {
  struct group *res;
  size_t size = 0, nmem = 0;
  __getgr_a(name, 0, &g_getgrent->gr, &g_getgrent->line, &size,
            &g_getgrent->mem, &nmem, &res);
  return res;
}
