/* This file is part of GNU Pies.
   Copyright (C) 2013-2022 Sergey Poznyakoff

   GNU Pies 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, or (at your option)
   any later version.

   GNU Pies 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 GNU Pies.  If not, see <http://www.gnu.org/licenses/>. */

#include "pies.h"
#include <fcntl.h>
#include <sys/utsname.h>
#include <paths.h>

#ifdef  HAVE_UTMPX_H
# include <utmpx.h>
typedef struct utmpx UTMPX;
# if HAVE_UPDWTMPX
#  define pies_updwtmpx updwtmpx
# endif
# define GETUTXID getutxid
# if defined HAVE_STRUCT_UTMPX_UT_NAME
#  define UT_NAME(u) ((u)->ut_name)
# elif defined HAVE_STRUCT_UTMPX_UT_USER
#  define UT_NAME(u) ((u)->ut_user)
# endif
#else
# include <utmp.h>
typedef struct utmp UTMPX;
# if defined HAVE_STRUCT_UTMP_UT_NAME
#  define UT_NAME(u) ((u)->ut_name)
# elif defined HAVE_STRUCT_UTMP_UT_USER
#  define UT_NAME(u) ((u)->ut_user)
# endif
# if HAVE_UPDWTMP
#  define pies_updwtmpx updwtmp
# endif
# define GETUTXID getutid
# define setutxent setutent
# define pututxline pututline
# define endutxent endutent
#endif

#if !defined(UTMPX_FILE) || !defined(WTMPX_FILE)
# if !defined(UTMP_FILE) || !defined(WTMP_FILE)
#  ifndef  HAVE_UTMPX_H
#   include <utmp.h>
#  endif
#  if !defined(UTMP_FILE)
#   if !defined(_PATH_UTMP)
#    error "None of UTMPX_FILE, UTMP_FILE or _PATH_UTMP defined"
#   else
#    define UTMP_FILE _PATH_UTMP
#   endif
#  endif
#  if !defined(WTMP_FILE)
#   if !defined(_PATH_WTMP)
#    error "None of WTMPX_FILE, WTMP_FILE or _PATH_WTMP defined"
#   else
#    define WTMP_FILE _PATH_WTMP
#   endif
#  endif
# endif
# if !defined(UTMPX_FILE)
#  define UTMPX_FILE UTMP_FILE
# endif
# if !defined(WTMPX_FILE)
#  define WTMPX_FILE WTMP_FILE
# endif
#endif

#ifndef pies_updwtmpx
static void
pies_updwtmpx (const char *wtmp_file, const UTMPX *ut)
{
  int fd = open (wtmp_file, O_WRONLY|O_APPEND);
  if (fd == -1)
    return;
  if (write (fd, ut, sizeof (*ut)) == -1)
    logmsg (LOG_ERR, "write %s: %s", wtmp_file, strerror (errno));
  close (fd);
}
#endif

static UTMPX *
pies_getutxid (const UTMPX *ut)
{
  static UTMPX tmp;
  memcpy (&tmp, ut, sizeof (*ut));
  return GETUTXID (&tmp);
}

void
fill_utmp (UTMPX *utmp,
	   int type, const char *user, const char *id, pid_t pid,
	   const char *line)
{
  memset (utmp, 0, sizeof (*utmp));
#if defined(HAVE_STRUCT_UTMP_UT_TV) || defined(HAVE_STRUCT_UTMPX_UT_TV)
  {
    struct timeval tv;
    gettimeofday (&tv, 0);
    utmp->ut_tv.tv_sec = tv.tv_sec;
    utmp->ut_tv.tv_usec = tv.tv_usec;
  }
#elif defined(HAVE_STRUCT_UTMP_UT_TIME) || defined(HAVE_STRUCT_UTMPX_UT_TIME)
  time (&utmp->ut_time);
#endif
  utmp->ut_type = type;
  strncpy (UT_NAME (utmp), user, sizeof (UT_NAME (utmp)));
  strncpy (utmp->ut_id, id, sizeof (utmp->ut_id));
  utmp->ut_pid  = pid;
  strncpy (utmp->ut_line, line, sizeof(utmp->ut_line));
#if defined(HAVE_STRUCT_UTMP_UT_HOST) || defined(HAVE_STRUCT_UTMPX_UT_HOST)
  {
    struct utsname uts;
    if (uname (&uts) == 0)
      strncpy (utmp->ut_host, uts.release, sizeof(utmp->ut_host));
  }
#endif
}
  
void
write_wtmpx (int type, const char *user, const char *id, pid_t pid,
	     const char *line)
{
  UTMPX utmp;
  static int wtmpxreboot = 0;

  debug (2, (_("cannot open %s for writing"), WTMPX_FILE));
  if (access (WTMPX_FILE, W_OK))
    {
      if (type == BOOT_TIME)
	wtmpxreboot++;
      return;
    }

  if (wtmpxreboot)
    {
      write_wtmpx (BOOT_TIME, "reboot", "~~", 0, "~");
      --wtmpxreboot;
    }
  fill_utmp (&utmp, type, user, id, pid, line);
  pies_updwtmpx (WTMPX_FILE, &utmp);
}  

void
write_utmpx (int type, const char *user, const char *id, pid_t pid,
	     const char *line)
{
  UTMPX utmp;
  static int utmpreboot = 0;

  if (access (UTMPX_FILE, W_OK))
    {
      debug (1, (_("cannot open %s for writing"), UTMPX_FILE));
      if (type == BOOT_TIME)
	utmpreboot++;
      return;
    }

  if (utmpreboot)
    {
      write_utmpx (BOOT_TIME, "reboot", "~~", 0, "~");
      --utmpreboot;
    }
  fill_utmp (&utmp, type, user, id, pid, line);

  if (type == DEAD_PROCESS)
    {
      UTMPX *p = pies_getutxid (&utmp);
      if (p)
	strncpy (utmp.ut_line, p->ut_line, sizeof (utmp.ut_line));
    }
  setutxent ();
  pututxline (&utmp);
  endutxent ();
}  

void
sysvinit_acct (int what, const char *user, const char *id, pid_t pid,
	       const char *line)
{
  static int types[] = {
    BOOT_TIME,
    RUN_LVL,
    INIT_PROCESS,
    DEAD_PROCESS
  };
  write_utmpx (types[what], user, id, pid, line);
  write_wtmpx (types[what], user, id, pid, line);
}
