/* Provide a working getlogin for systems which lack it.

   Copyright (C) 2010-2026 Free Software Foundation, Inc.

   This file is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published
   by the Free Software Foundation, either version 3 of the License,
   or (at your option) any later version.

   This file is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Written by Bruno Haible, 2010, 2025.  */

#include <config.h>

/* Specification.  */
#include <unistd.h>

#if defined _WIN32 && ! defined __CYGWIN__
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
/* Don't assume that UNICODE is not defined.  */
# undef GetUserName
# define GetUserName GetUserNameA
#endif

#if defined __linux__ || defined __ANDROID__
# include <fcntl.h>
# include <pwd.h>
# include <stdlib.h>
# include <string.h>
# include <sys/stat.h>
# include "readutmp.h"
#endif

char *
getlogin (void)
{
#if defined _WIN32 && ! defined __CYGWIN__
  /* Native Windows platform.  */
  static char login_name[1024];
  DWORD sz = sizeof (login_name);

  if (GetUserName (login_name, &sz))
    return login_name;
#elif defined __linux__ || defined __ANDROID__
  /* Linux.  */
  {
    /* Read the login uid from the /proc file system.  */
    int fd = open ("/proc/self/loginuid", O_RDONLY);
    if (fd >= 0)
      {
        char buf[20 + 1];
        int n = read (fd, buf, sizeof (buf) - 1);
        if (n > 0)
          {
            buf[n] = '\0';
            char *endptr;
            unsigned long uid = strtoul (buf, &endptr, 10);
            if (endptr == buf + n && uid != (uid_t) -1)
              {
                /* Convert the uid to a user name.  */
                struct passwd *p = getpwuid (uid);
                if (p != NULL && strlen (p->pw_name) < 64)
                  {
                    static char resultbuf[64];
                    strcpy (resultbuf, p->pw_name);
                    close (fd);
                    return resultbuf;
                  }
              }
          }
        close (fd);
      }
  }
  {
    /* Find the tty connected to the current process.  */
    char *tty = ttyname (STDIN_FILENO);
    if (tty != NULL)
      {
        /* Try to see to which user it is allocated, via utmp.  */
        if (strncmp (tty, "/dev/tty", 8) == 0)
          {
            idx_t n;
            STRUCT_UTMP *entries;
            if (read_utmp (UTMP_FILE, &n, &entries, READ_UTMP_USER_PROCESS)
                == 0)
              {
                for (idx_t i = 0; i < n; i++)
                  if (streq (entries[i].ut_line, tty + 5))
                    {
                      if (strlen (entries[i].ut_user) < 64)
                        {
                          static char resultbuf[64];
                          strcpy (resultbuf, entries[i].ut_user);
                          free (entries);
                          return resultbuf;
                        }
                      break;
                    }
                free (entries);
              }
          }
        /* Fallback for systems which don't maintain an utmp database
           or for ttys that are not recorded in that the utmp database:
           Look at the owner of that tty.  */
        struct stat statbuf;
        if (stat (tty, &statbuf) >= 0)
          {
            uid_t uid = statbuf.st_uid;
            /* Convert the uid to a user name.  */
            struct passwd *p = getpwuid (uid);
            if (p != NULL && strlen (p->pw_name) < 64)
              {
                static char resultbuf[64];
                strcpy (resultbuf, p->pw_name);
                return resultbuf;
              }
          }
      }
  }
#endif
  return NULL;
}
