]> git.saurik.com Git - apple/libc.git/blobdiff - gen/utmpx-darwin.c
Libc-498.tar.gz
[apple/libc.git] / gen / utmpx-darwin.c
diff --git a/gen/utmpx-darwin.c b/gen/utmpx-darwin.c
new file mode 100644 (file)
index 0000000..312f4e5
--- /dev/null
@@ -0,0 +1,1172 @@
+/*
+ * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * "Portions Copyright (c) 2004 Apple Computer, Inc.  All Rights
+ * Reserved.  This file contains Original Code and/or Modifications of
+ * Original Code as defined in and that are subject to the Apple Public
+ * Source License Version 1.0 (the 'License').  You may not use this file
+ * except in compliance with the License.  Please obtain a copy of the
+ * License at http://www.apple.com/publicsource and read it before using
+ * this file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
+ * License for the specific language governing rights and limitations
+ * under the License."
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <utmp.h>
+#include <utmpx.h>
+#include <utmpx-darwin.h>
+#include <asl.h>
+#include <asl_private.h>
+#include <pwd.h>
+#include <stddef.h>
+
+#include <mach/mach.h>
+#include <mach/std_types.h>
+#include <mach/mig.h>
+#include <mach/mach_types.h>
+#include <servers/bootstrap.h>
+#include <pthread.h>
+#include <asl_ipc.h>
+
+#ifdef UTMP_COMPAT
+#include <ttyent.h>
+#endif /* UTMP_COMPAT */
+
+extern const char _utmpx_vers[];       /* in utmpx.c */
+extern char *asl_list_to_string(asl_search_result_t *, uint32_t *);
+extern asl_search_result_t *asl_list_from_string(const char *);
+
+static void msg2lastlogx(const aslmsg, struct lastlogx *);
+static void msg2utmpx(const aslmsg, struct utmpx *);
+static void utmpx2msg(const struct utmpx *, aslmsg);
+
+static mach_port_t asl_server_port = MACH_PORT_NULL;
+static int pw_size = 0;
+
+#define ASL_SERVICE_NAME       "com.apple.system.logger"
+#define FACILITY               "Facility"
+#define WTMP_COUNT             32
+
+/* ASL timeout in milliseconds */
+#define ASL_QUERY_TIMEOUT 4000
+
+/* indirection causes argument to be substituted before stringification */
+#define STR(x)         __STRING(x)
+
+static char *
+_pwnam_r(const char *user, struct passwd *pw)
+{
+       struct passwd *p;
+       char *buf;
+
+       if (pw_size <= 0) {
+               pw_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+               if (pw_size <= 0)
+                       return NULL;
+       }
+       if ((buf = malloc(pw_size)) == NULL)
+               return NULL;
+
+       getpwnam_r(user, pw, buf, pw_size, &p);
+       if (!p) {
+               free(buf);
+               return NULL;
+       }
+       return buf;
+}
+
+static char *
+_pwuid_r(uid_t uid, struct passwd *pw)
+{
+       struct passwd *p;
+       char *buf;
+
+       if (pw_size <= 0) {
+               pw_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+               if (pw_size <= 0)
+                       return NULL;
+       }
+       if ((buf = malloc(pw_size)) == NULL)
+               return NULL;
+
+       getpwuid_r(uid, pw, buf, pw_size, &p);
+       if (!p) {
+               free(buf);
+               return NULL;
+       }
+       return buf;
+}
+
+struct lastlogx *
+getlastlogx(uid_t uid, struct lastlogx *lx)
+{
+       char *buf;
+       struct passwd pw;
+       struct lastlogx *l;
+
+       if ((buf = _pwuid_r(uid, &pw)) == NULL)
+               return NULL;
+
+       l = getlastlogxbyname(pw.pw_name, lx);
+       free(buf);
+       return l;
+}
+
+struct lastlogx *
+getlastlogxbyname(const char *user, struct lastlogx *lx)
+{
+       aslmsg q;
+       asl_msg_t *m[1];
+       asl_search_result_t s, *l;
+       char *qstr, *res;
+       uint32_t len, reslen, status;
+       uint64_t cmax;
+       security_token_t sec;
+       caddr_t vmstr;
+       struct lastlogx *result = NULL;
+       mach_port_t port;
+
+       if (!user || !*user)
+               return NULL;
+
+       if (bootstrap_look_up(bootstrap_port, ASL_SERVICE_NAME, &port) != KERN_SUCCESS)
+               return NULL;
+
+       /*
+        * We search for the last LASTLOG_FACILITY entry that has the
+        * ut_user entry matching the user's name.
+        */
+       if ((q = asl_new(ASL_TYPE_QUERY)) == NULL)
+               goto out;
+       asl_set_query(q, FACILITY, LASTLOG_FACILITY, ASL_QUERY_OP_EQUAL);
+       asl_set_query(q, "ut_user", user, ASL_QUERY_OP_EQUAL);
+       m[0] = q;
+       s.count = 1;
+       s.msg = m;
+
+       len = 0;
+       qstr = asl_list_to_string(&s, &len);
+       asl_free(q);
+
+       if (qstr == NULL)
+               goto out;
+
+       /* the server frees this memory */
+       if (vm_allocate(mach_task_self(), (vm_address_t *)&vmstr, len, TRUE) != KERN_SUCCESS) {
+               free(qstr);
+               goto out;
+       }
+
+       strcpy(vmstr, qstr);
+       free(qstr);
+
+       res = NULL;
+       reslen = 0;
+       cmax = 0;
+       sec.val[0] = -1;
+       sec.val[1] = -1;
+       status = 0;
+
+       _asl_server_query_timeout(port, vmstr, len, -1, 1, 1, ASL_QUERY_TIMEOUT, (caddr_t *)&res, &reslen, &cmax, (int *)&status, &sec);
+
+       if (res == NULL)
+               goto out;
+       l = asl_list_from_string(res);
+       vm_deallocate(mach_task_self(), (vm_address_t)res, reslen);
+       q = aslresponse_next(l);
+       if (q == NULL) {
+               aslresponse_free(l);
+               goto out;
+       }
+
+       if (lx == NULL) {
+               if ((lx = (struct lastlogx *)malloc(sizeof(*lx))) == NULL) {
+                       aslresponse_free(l);
+                       goto out;
+               }
+       }
+       msg2lastlogx(q, lx);
+       aslresponse_free(l);
+       result = lx;
+out:
+       mach_port_deallocate(mach_task_self(), port);
+       return result;
+}
+
+#define IGET(e,p)      if ((cp = asl_get(m, __STRING(ut_##e))) != NULL) \
+                               u->p##_##e = strtol(cp, NULL, 10);
+#define LGET(e,p)      IGET(e,p)
+#define SGET(e,p)      if ((cp = asl_get(m, __STRING(ut_##e))) != NULL) \
+                               strncpy(u->p##_##e, cp, sizeof(u->p##_##e))
+
+/* fill in a struct lastlogx from a aslmsg */
+static void
+msg2lastlogx(const aslmsg m, struct lastlogx *u)
+{
+       const char *cp;
+
+       bzero(u, sizeof(*u));
+       SGET(line, ll);
+       LGET(tv.tv_sec, ll);
+       IGET(tv.tv_usec, ll);
+       SGET(host, ll);
+}
+
+/* fill in a struct utmpx from a aslmsg */
+static void
+msg2utmpx(const aslmsg m, struct utmpx *u)
+{
+       const char *cp;
+
+       bzero(u, sizeof(*u));
+       SGET(user, ut);
+       SGET(id, ut);
+       SGET(line, ut);
+       IGET(pid, ut);
+       IGET(type, ut);
+       LGET(tv.tv_sec, ut);
+       IGET(tv.tv_usec, ut);
+       SGET(host, ut);
+}
+
+/* fill in a aslmsg from a struct utmpx */
+static void
+utmpx2msg(const struct utmpx *u, aslmsg m)
+{
+       char buf[_UTX_HOSTSIZE + 1];    /* the largest string in struct utmpx */
+       char *cp;
+#define ISET(e)        { snprintf(buf, sizeof(buf), "%d", u->e); \
+               asl_set(m, #e, buf); }
+#define LSET(e)        { snprintf(buf, sizeof(buf), "%ld", u->e); \
+               asl_set(m, #e, buf); }
+#define SSET(e)        if (*(u->e)) { \
+                       strncpy(buf, u->e, sizeof(u->e)); \
+                       buf[sizeof(u->e)] = 0; \
+                       asl_set(m, #e, buf); \
+               }
+
+       SSET(ut_user);
+       cp = u->ut_id + sizeof(u->ut_id);
+       while(--cp >= u->ut_id && isprint(*cp)) {}
+       if(cp < u->ut_id) {
+           SSET(ut_id);
+       } else {
+           snprintf(buf, sizeof(buf), "0x%02x 0x%02x 0x%02x 0x%02x",
+               (unsigned)u->ut_id[0], (unsigned)u->ut_id[1],
+               (unsigned)u->ut_id[2], (unsigned)u->ut_id[3]);
+           asl_set(m, "ut_id", buf);
+       }
+       SSET(ut_line);
+       if (u->ut_pid > 0)
+               ISET(ut_pid);
+       ISET(ut_type);
+       LSET(ut_tv.tv_sec);
+       ISET(ut_tv.tv_usec);
+       SSET(ut_host);
+}
+
+static const char *utmpx_types[] = {
+       "EMPTY",                /* 0 */
+       "RUN_LVL",              /* 1 */
+       "BOOT_TIME",            /* 2 */
+       "OLD_TIME",             /* 3 */
+       "NEW_TIME",             /* 4 */
+       "INIT_PROCESS",         /* 5 */
+       "LOGIN_PROCESS",        /* 6 */
+       "USER_PROCESS",         /* 7 */
+       "DEAD_PROCESS",         /* 8 */
+       "ACCOUNTING",           /* 9 */
+       "SIGNATURE",            /* 10 */
+       "SHUTDOWN_TIME",        /* 11 */
+};
+
+/* send a struct utmpx record using asl */
+__private_extern__ void
+_utmpx_asl(const struct utmpx *u)
+{
+       aslclient asl = asl_open(NULL, NULL, ASL_OPT_NO_REMOTE); /* could be NULL, but still works */
+       aslmsg m;
+       char msg[64];
+
+       if (u->ut_type == EMPTY)
+               return;
+       if ((m = asl_new(ASL_TYPE_MSG)) == NULL) {
+               asl_close(asl);
+               return;
+       }
+       /*
+        * If the ut_type is USER_PROCESS, we use the LASTLOG_FACILITY,
+        * otherwise we use the UTMPX_FACILITY.  This makes it easy to
+        * search for lastlog entries, but for wtmp, we have to search
+        * for both facilities.
+        */
+       if (u->ut_type == USER_PROCESS)
+               asl_set(m, FACILITY, LASTLOG_FACILITY);
+       else
+               asl_set(m, FACILITY, UTMPX_FACILITY);
+       asl_set(m, ASL_KEY_LEVEL, STR(ASL_LEVEL_NOTICE));
+       utmpx2msg(u, m);
+
+       /* Make a visible message for system.log */
+       switch (u->ut_type) {
+       case BOOT_TIME:
+       case OLD_TIME:
+       case NEW_TIME:
+       case SHUTDOWN_TIME:
+               sprintf(msg, "%s: %ld %d", utmpx_types[u->ut_type], u->ut_tv.tv_sec, u->ut_tv.tv_usec);
+               break;
+       case INIT_PROCESS:
+       case LOGIN_PROCESS:
+               sprintf(msg, "%s: %d", utmpx_types[u->ut_type], (int)u->ut_pid);
+               break;
+       case USER_PROCESS:
+       case DEAD_PROCESS:
+               sprintf(msg, "%s: %d %.*s", utmpx_types[u->ut_type], (int)u->ut_pid, (int)sizeof(u->ut_line), u->ut_line);
+               break;
+       default:
+               if (u->ut_type >= 0 && u->ut_type < (sizeof(utmpx_types) / sizeof(*utmpx_types)))
+                       sprintf(msg, "%s", utmpx_types[u->ut_type]);
+               else
+                       sprintf(msg, "ut_type=%d", (int)u->ut_type);
+               break;
+       }
+       asl_set(m, ASL_KEY_MSG, msg);
+       asl_send(asl, m);
+       asl_free(m);
+       if (asl)
+               asl_close(asl);
+}
+
+#define UT_USER        (1 << 0)
+#define UT_ID  (1 << 1)
+#define UT_LINE        (1 << 2)
+#define UT_PID (1 << 3)
+#define UT_TV  (1 << 4)
+
+__private_extern__ const struct utmpx *
+_utmpx_working_copy(const struct utmpx *utx, struct utmpx *temp, int onlyid)
+{
+       int which;
+       static char idzero[_UTX_IDSIZE];
+
+       if ((utx->ut_type & (UTMPX_AUTOFILL_MASK | UTMPX_DEAD_IF_CORRESPONDING_MASK)) == 0)
+               return utx;
+       memcpy(temp, utx, sizeof(*temp));
+       temp->ut_type &= ~(UTMPX_AUTOFILL_MASK | UTMPX_DEAD_IF_CORRESPONDING_MASK);
+
+       if ((utx->ut_type & UTMPX_AUTOFILL_MASK) == 0)
+               return temp;
+
+       which = UT_TV; /* they all need time */
+       switch(temp->ut_type) {
+       case EMPTY:
+               return temp;
+       case USER_PROCESS:
+               which |= (UT_USER | UT_LINE | UT_PID);
+               /* Set UT_ID if ut_id isn't there */
+               if (memcmp(temp->ut_id, idzero, sizeof(temp->ut_id)) == 0)
+                       which |= UT_ID;
+               break;
+       case INIT_PROCESS:
+               which |= UT_PID;
+               break;
+       case LOGIN_PROCESS:
+               which |= (UT_USER | UT_PID);
+               break;
+       case DEAD_PROCESS:
+               which |= UT_PID;
+               /* Set UT_ID if ut_id isn't there.  We will also need UT_LINE */
+               if (memcmp(temp->ut_id, idzero, sizeof(temp->ut_id)) == 0)
+                       which |= (UT_ID | UT_LINE);
+               break;
+       }
+       /*
+        * If onlyid is set: if ut_id isn't set but is needed, then set
+        * which to (UT_LINE | UT_ID), otherwise zero
+        */
+       if (onlyid)
+               which = (which & UT_ID) ? (UT_LINE | UT_ID) : 0;
+       if ((which & UT_LINE) && !*temp->ut_line) {
+               char buf[256];
+               char *cp;
+#if __DARWIN_UNIX03
+               int err;
+
+               err = ttyname_r(0, buf, sizeof(buf));
+               if (err)
+                       err = ttyname_r(1, buf, sizeof(buf));
+               if (err)
+                       err = ttyname_r(2, buf, sizeof(buf));
+               if (err)
+                       return NULL;
+#else /* !__DARWIN_UNIX03 */
+               cp = ttyname_r(0, buf, sizeof(buf));
+               if (!cp)
+                       cp = ttyname_r(1, buf, sizeof(buf));
+               if (!cp)
+                       cp = ttyname_r(2, buf, sizeof(buf));
+               if (!cp)
+                       return NULL;
+#endif /* __DARWIN_UNIX03 */
+               cp = strrchr(buf, '/');
+               if (cp)
+                       cp++;
+               else
+                       cp = buf;
+               strncpy(temp->ut_line, cp, sizeof(temp->ut_line));
+       }
+       /* UT_ID is set only if we already know we need to add it */
+       if ((which & UT_ID)) {
+               char *cp;
+               int i = sizeof(temp->ut_line);
+
+               for(cp = temp->ut_line; i > 0 && *cp; i--)
+                       cp++;
+               i = cp - temp->ut_line;
+               if(i >= sizeof(temp->ut_id))
+                       memcpy(temp->ut_id, cp - sizeof(temp->ut_id), sizeof(temp->ut_id));
+               else
+                       memcpy(temp->ut_id, temp->ut_line, i);
+       }
+       if ((which & UT_PID) && !temp->ut_pid)
+               temp->ut_pid = getpid();
+       if ((which & UT_USER) && !*temp->ut_user) {
+               char *buf;
+               struct passwd pw;
+
+               if ((buf = _pwuid_r(getuid(), &pw)) == NULL)
+                       return NULL;
+               strncpy(temp->ut_user, pw.pw_name, sizeof(temp->ut_user));
+               free(buf);
+       }
+       if ((which & UT_TV) && !temp->ut_tv.tv_sec && !temp->ut_tv.tv_usec)
+               gettimeofday(&temp->ut_tv, NULL);
+       return temp;
+}
+
+/*
+ * We can read from either asl or from a file, so we need to switch between
+ * the two.
+ */
+static void end_asl(void);
+static void end_file(void);
+static struct utmpx *get_asl(void);
+static struct utmpx *get_file(void);
+static void set_asl(int);
+static void set_file(int);
+
+enum {WTMP_ASL, WTMP_FILE};
+
+static struct {
+       int which;
+       void (*end)(void);
+       struct utmpx *(*get)(void);
+       void (*set)(int);
+} wtmp_func = {
+       WTMP_ASL,
+       end_asl,
+       get_asl,
+       set_asl
+};
+static struct {
+       uint64_t start;
+       int dir;
+       asl_search_result_t *res;
+       char *str;
+       uint32_t len;
+       char inited;
+       char done;
+} wtmp_asl = {-1, 1};
+static struct {
+       int fd;
+       int dir;
+       char file[MAXPATHLEN];
+       off_t off;
+       size_t count;
+#ifdef __LP64__
+       struct utmpx32 *buf;
+       struct utmpx32 *next;
+#else /* __LP64__ */
+       struct utmpx *buf;
+       struct utmpx *next;
+#endif /* __LP64__ */
+       int left;
+} wtmp_file = {-1, -1};
+
+void
+endutxent_wtmp(void)
+{
+       wtmp_func.end();
+}
+
+struct utmpx *
+getutxent_wtmp(void)
+{
+       return wtmp_func.get();
+}
+
+void
+setutxent_wtmp(int dir)
+{
+       wtmp_func.set(dir);
+}
+
+/* use the given file, or if NULL, read from asl */
+int
+wtmpxname(const char *fname)
+{
+       size_t len;
+
+       if (fname == NULL) {
+               if (wtmp_func.which == WTMP_ASL) {
+                       end_asl();
+                       return 1;
+               }
+               end_file();
+               wtmp_func.which = WTMP_ASL;
+               wtmp_func.end = end_asl;
+               wtmp_func.get = get_asl;
+               wtmp_func.set = set_asl;
+               return 1;
+       }
+
+       len = strlen(fname);
+       if (len >= sizeof(wtmp_file.file))
+               return 0;
+
+       /* must end in x! */
+       if (fname[len - 1] != 'x')
+               return 0;
+
+       (void)strlcpy(wtmp_file.file, fname, sizeof(wtmp_file.file));
+
+       if (wtmp_func.which == WTMP_ASL)
+               end_asl();
+       else if (wtmp_file.fd >= 0) {
+               close(wtmp_file.fd);
+               wtmp_file.fd = -1;
+       }
+       wtmp_func.which = WTMP_FILE;
+       wtmp_func.end = end_file;
+       wtmp_func.get = get_file;
+       wtmp_func.set = set_file;
+       return 1;
+}
+
+static void
+end_asl(void)
+{
+       if (wtmp_asl.res) {
+               aslresponse_free(wtmp_asl.res);
+               wtmp_asl.res = NULL;
+       }
+       wtmp_asl.inited = 0;
+       wtmp_asl.done = 0;
+       if (asl_server_port != MACH_PORT_NULL) {
+               mach_port_deallocate(mach_task_self(), asl_server_port);
+               asl_server_port = MACH_PORT_NULL;
+       }
+}
+
+static void
+end_file(void)
+{
+       if (wtmp_file.fd >= 0) {
+               close(wtmp_file.fd);
+               wtmp_file.fd = -1;
+       }
+       if (wtmp_file.buf) {
+               free(wtmp_file.buf);
+               wtmp_file.buf = NULL;
+       }
+}
+
+static struct utmpx *
+get_asl(void)
+{
+       aslmsg q;
+       char *res;
+       uint32_t reslen, status;
+       security_token_t sec;
+       caddr_t vmstr;
+       static struct utmpx utx;
+
+get_asl_repeat:
+       if (wtmp_asl.res) {
+               if ((q = aslresponse_next(wtmp_asl.res)) != NULL) {
+                       msg2utmpx(q, &utx);
+                       return &utx;
+               }
+               aslresponse_free(wtmp_asl.res);
+               wtmp_asl.res = NULL;
+       } else if (!wtmp_asl.inited) {
+               set_asl(-1);
+               if (!wtmp_asl.inited)
+                       return NULL;
+       }
+
+       if (wtmp_asl.done)
+               return NULL;
+
+       if (asl_server_port == MACH_PORT_NULL) {
+               if (bootstrap_look_up(bootstrap_port, ASL_SERVICE_NAME, &asl_server_port) != KERN_SUCCESS) {
+get_asl_done:
+                   wtmp_asl.done = 1;
+                   return NULL;
+               }
+       }
+
+       /* the server frees this memory */
+       if (vm_allocate(mach_task_self(), (vm_address_t *)&vmstr, wtmp_asl.len, TRUE) != KERN_SUCCESS)
+               goto get_asl_done;
+
+       /* the search string is defined in set_asl */
+       strcpy(vmstr, wtmp_asl.str);
+
+       res = NULL;
+       reslen = 0;
+       sec.val[0] = -1;
+       sec.val[1] = -1;
+       status = 0;
+
+       _asl_server_query_timeout(asl_server_port, vmstr, wtmp_asl.len, wtmp_asl.start, WTMP_COUNT, wtmp_asl.dir, ASL_QUERY_TIMEOUT, (caddr_t *)&res, &reslen, &wtmp_asl.start, (int *)&status, &sec);
+
+       if (res == NULL)
+               goto get_asl_done;
+       wtmp_asl.res = asl_list_from_string(res);
+       vm_deallocate(mach_task_self(), (vm_address_t)res, reslen);
+       if(!wtmp_asl.res)
+               goto get_asl_done;
+       goto get_asl_repeat;
+}
+
+static struct utmpx *
+get_file(void)
+{
+       int n, r;
+       char *cp;
+#ifdef __LP64__
+       static struct utmpx ux;
+#endif /* __LP64__ */
+
+get_file_repeat:
+       if (wtmp_file.left > 0) {
+#ifdef __LP64__
+               struct utmpx32 *u = wtmp_file.next;
+#else /* __LP64__ */
+               struct utmpx *u = wtmp_file.next;
+#endif /* __LP64__ */
+               wtmp_file.next += wtmp_file.dir;
+               wtmp_file.left--;
+#ifdef __LP64__
+               _utmpx32_64(u, &ux);
+               return &ux;
+#else /* __LP64__ */
+               return u;
+#endif /* __LP64__ */
+       } else if (wtmp_file.fd < 0) {
+               set_file(-1); /* keep current read direction */
+               if (wtmp_file.fd < 0)
+                       return NULL;
+               goto get_file_repeat;
+       }
+       if (wtmp_file.count <= 0)
+               return NULL;
+
+#ifdef __LP64__
+       n = WTMP_COUNT * sizeof(struct utmpx32);
+#else /* __LP64__ */
+       n = WTMP_COUNT * sizeof(struct utmpx);
+#endif /* __LP64__ */
+       if (wtmp_file.dir > 0)
+               wtmp_file.next = wtmp_file.buf;
+       else {
+               wtmp_file.next = wtmp_file.buf + WTMP_COUNT - 1;
+               wtmp_file.off -= n;
+               if (lseek(wtmp_file.fd, wtmp_file.off, SEEK_SET) < 0) {
+get_file_done:
+                       wtmp_file.count = 0;
+                       return NULL;
+               }
+       }
+
+       cp = (char *)wtmp_file.buf;
+       do {
+               if((r = read(wtmp_file.fd, cp, n)) <= 0) {
+                       if (r < 0 && (errno == EINTR || errno == EAGAIN))
+                               continue;
+                       goto get_file_done;
+               }
+               cp += r;
+       } while((n -= r) > 0);
+
+       wtmp_file.left = WTMP_COUNT;
+       wtmp_file.count -= WTMP_COUNT;
+
+       goto get_file_repeat;
+}
+
+/*
+ * This sets the directions for both asl and reading from a file.  If forward
+ * is negative, skip.
+ */
+static void
+_set_dir(int forward)
+{
+       if (forward < 0)
+               return;
+       if (forward) {
+               wtmp_asl.dir = 0;
+               wtmp_asl.start = 0;
+               wtmp_file.dir = 1;
+       } else {
+               wtmp_asl.dir = 1;
+               wtmp_asl.start = -1;
+               wtmp_file.dir = -1;
+       }
+}
+
+static void
+set_asl(int forward)
+{
+       _set_dir(forward);
+       if (!wtmp_asl.str) {
+               aslmsg q0, q1;
+               asl_msg_t *m[2];
+               asl_search_result_t s;
+
+               /*
+                * Create a search string that matches either UTMPX_FACILITY
+                * or LASTLOG_FACILITY.
+                */
+               if ((q0 = asl_new(ASL_TYPE_QUERY)) == NULL)
+                       return;
+               if ((q1 = asl_new(ASL_TYPE_QUERY)) == NULL) {
+                       asl_free(q0);
+                       return;
+               }
+               asl_set_query(q0, FACILITY, UTMPX_FACILITY, ASL_QUERY_OP_EQUAL);
+               asl_set_query(q1, FACILITY, LASTLOG_FACILITY, ASL_QUERY_OP_EQUAL);
+
+               m[0] = q0;
+               m[1] = q1;
+               s.count = 2;
+               s.msg = m;
+
+               wtmp_asl.len = 0;
+               wtmp_asl.str = asl_list_to_string(&s, &wtmp_asl.len);
+               asl_free(q1);
+               asl_free(q0);
+               if(!wtmp_asl.str)
+                       return;
+       }
+       if (wtmp_asl.res) {
+               aslresponse_free(wtmp_asl.res);
+               wtmp_asl.res = NULL;
+       }
+       wtmp_asl.inited = 1;
+       wtmp_asl.done = 0;
+}
+
+static void
+set_file(int forward)
+{
+       struct stat s;
+       size_t c;
+       int n, r;
+       char *cp;
+
+       _set_dir(forward);
+#ifdef __LP64__
+       if (wtmp_file.buf == NULL &&
+           (wtmp_file.buf = (struct utmpx32 *)malloc(WTMP_COUNT * sizeof(struct utmpx32))) == NULL)
+#else /* __LP64__ */
+       if (wtmp_file.buf == NULL &&
+           (wtmp_file.buf = (struct utmpx *)malloc(WTMP_COUNT * sizeof(struct utmpx))) == NULL)
+#endif /* __LP64__ */
+               return;
+       if (wtmp_file.fd >= 0)
+               close(wtmp_file.fd);
+       if ((wtmp_file.fd = open(wtmp_file.file, O_RDONLY, 0)) < 0)
+               return;
+       if (fstat(wtmp_file.fd, &s) < 0)
+               goto set_file_error;
+       /*
+        * We must have a file at least 2 sizeof(struct utmpx) in size,
+        * with the first struct utmpx matching a signature record.
+        */
+#ifdef __LP64__
+       if ((wtmp_file.count = s.st_size / sizeof(struct utmpx32)) <= 1)
+#else /* __LP64__ */
+       if ((wtmp_file.count = s.st_size / sizeof(struct utmpx)) <= 1)
+#endif /* __LP64__ */
+               goto set_file_error;
+#ifdef __LP64__
+       if (read(wtmp_file.fd, wtmp_file.buf, sizeof(struct utmpx32)) != sizeof(struct utmpx32))
+#else /* __LP64__ */
+       if (read(wtmp_file.fd, wtmp_file.buf, sizeof(struct utmpx)) != sizeof(struct utmpx))
+#endif /* __LP64__ */
+               goto set_file_error;
+       if (strcmp(wtmp_file.buf->ut_user, _utmpx_vers) != 0 ||
+           wtmp_file.buf->ut_type != SIGNATURE)
+               goto set_file_error;
+       wtmp_file.count--;
+
+       /*
+        * We will first read any records modulo WTMP_COUNT (or WTMP_COUNT),
+        * either at the beginning or the end, so that all subsequent reads
+        * must contain WTMP_COUNT records.
+        */
+       c = WTMP_COUNT * ((wtmp_file.count - 1) / WTMP_COUNT);
+       wtmp_file.left = wtmp_file.count - c;
+       wtmp_file.count -= wtmp_file.left;
+
+       /* Seek to the end for reverse reading */
+       if (wtmp_file.dir < 0) {
+#ifdef __LP64__
+               wtmp_file.off = (c + 1) * sizeof(struct utmpx32);
+#else /* __LP64__ */
+               wtmp_file.off = (c + 1) * sizeof(struct utmpx);
+#endif /* __LP64__ */
+               if (lseek(wtmp_file.fd, wtmp_file.off, SEEK_SET) < 0)
+                       goto set_file_error;
+       }
+#ifdef __LP64__
+       n = wtmp_file.left * sizeof(struct utmpx32);
+#else /* __LP64__ */
+       n = wtmp_file.left * sizeof(struct utmpx);
+#endif /* __LP64__ */
+       cp = (char *)wtmp_file.buf;
+       do {
+               if((r = read(wtmp_file.fd, cp, n)) <= 0) {
+                       if (r < 0 && (errno == EINTR || errno == EAGAIN))
+                               continue;
+                       goto set_file_error;
+               }
+               cp += r;
+       } while((n -= r) > 0);
+
+       /* Point to either the beginning or end of the buffer */
+       if(wtmp_file.dir > 0)
+               wtmp_file.next = wtmp_file.buf;
+       else
+               wtmp_file.next = wtmp_file.buf + wtmp_file.left - 1;
+       return;
+
+set_file_error:
+       wtmp_file.left = 0;
+       close(wtmp_file.fd);
+       wtmp_file.fd = -1;
+       return;
+}
+
+#ifdef __LP64__
+/*
+ * these routines assume natural alignment so that struct utmpx32 has
+ * the same size and layout as the 32-bit struct utmpx
+ */
+__private_extern__ void
+_utmpx32_64(const struct utmpx32 *u32, struct utmpx *u)
+{
+       bzero(u, sizeof(*u));
+       memcpy(u, u32, offsetof(struct utmpx, ut_type) + sizeof(u->ut_type));
+       u->ut_tv.tv_sec = u32->ut_tv.tv_sec;
+       u->ut_tv.tv_usec = u32->ut_tv.tv_usec;
+       memcpy((char *)u + offsetof(struct utmpx, ut_host),
+           (char *)u32 + offsetof(struct utmpx32, ut_host),
+           sizeof(struct utmpx) - offsetof(struct utmpx, ut_host));
+}
+
+__private_extern__ void
+_utmpx64_32(const struct utmpx *u, struct utmpx32 *u32)
+{
+       bzero(u32, sizeof(*u32));
+       memcpy(u32, u, offsetof(struct utmpx32, ut_type) + sizeof(u32->ut_type));
+       u32->ut_tv.tv_sec = u->ut_tv.tv_sec;
+       u32->ut_tv.tv_usec = u->ut_tv.tv_usec;
+       memcpy((char *)u32 + offsetof(struct utmpx32, ut_host),
+           (char *)u + offsetof(struct utmpx, ut_host),
+           sizeof(struct utmpx32) - offsetof(struct utmpx32, ut_host));
+}
+#endif /* __LP64__ */
+
+#ifdef UTMP_COMPAT
+#ifdef __LP64__
+__private_extern__ void
+_getutmp32(const struct utmpx *ux, struct utmp32 *u)
+{
+
+       bzero(u, sizeof(*u));
+       (void)memcpy(u->ut_name, ux->ut_user, sizeof(u->ut_name));
+       (void)memcpy(u->ut_line, ux->ut_line, sizeof(u->ut_line));
+       (void)memcpy(u->ut_host, ux->ut_host, sizeof(u->ut_host));
+       u->ut_time = ux->ut_tv.tv_sec;
+}
+#endif /* __LP64__ */
+
+/*
+ * _utmp_compat converts a struct utmpx to a struct utmp, using the conventions
+ * described in utmp(5).  It then returns a value that specifies what
+ * combination of utmp, wtmp and lastlog to write.  UTMP_COMPAT_UTMP1 will
+ * write utmp only if a matching record with the same ut_line value is found;
+ * UTMP_COMPAT_UTMP0 replaces an existing record or writes a new one.
+ */
+__private_extern__ int
+#ifdef __LP64__
+_utmp_compat(const struct utmpx *ux, struct utmp32 *u)
+#else /* __LP64__ */
+_utmp_compat(const struct utmpx *ux, struct utmp *u)
+#endif /* __LP64__ */
+{
+#ifdef __LP64__
+       _getutmp32(ux, u);
+#else /* __LP64__ */
+       getutmp(ux, u);
+#endif /* __LP64__ */
+
+       switch (ux->ut_type) {
+       case BOOT_TIME:
+       case SHUTDOWN_TIME:
+               bzero(u->ut_line, sizeof(u->ut_line));
+               u->ut_line[0] = '~';
+               bzero(u->ut_name, sizeof(u->ut_name));
+               strcpy(u->ut_name, (ux->ut_type == BOOT_TIME ? "reboot" : "shutdown"));
+               return UTMP_COMPAT_WTMP;
+       case OLD_TIME:
+       case NEW_TIME:
+               bzero(u->ut_line, sizeof(u->ut_line));
+               u->ut_line[0] = (ux->ut_type == OLD_TIME ? '|' : '{');
+               bzero(u->ut_name, sizeof(u->ut_name));
+               strcpy(u->ut_name, "date");
+               return UTMP_COMPAT_WTMP;
+       case USER_PROCESS:
+               return UTMP_COMPAT_UTMP0 | UTMP_COMPAT_WTMP | UTMP_COMPAT_LASTLOG;
+       case DEAD_PROCESS:
+               bzero(u->ut_name, sizeof(u->ut_name));
+               bzero(u->ut_host, sizeof(u->ut_host));
+               return UTMP_COMPAT_UTMP1 | UTMP_COMPAT_WTMP;
+       }
+       return 0;       /* skip */;
+}
+
+/*
+ * Write _PATH_LASTLOG given a struct utmp record.  We use
+ * advisory record locking.
+ */
+__private_extern__ void
+#ifdef __LP64__
+_write_lastlog(const struct utmp32 *u, const struct utmpx *ux)
+#else /* __LP64__ */
+_write_lastlog(const struct utmp *u, const struct utmpx *ux)
+#endif /* __LP64__ */
+{
+       int fd;
+#ifdef __LP64__
+       struct lastlog32 l;
+#else /* __LP64__ */
+       struct lastlog l;
+#endif /* __LP64__ */
+       struct flock lock;
+       struct passwd pw;
+       // sizeof(ux->ut_user) > sizeof(u->ut_name)
+       char name[sizeof(ux->ut_user) + 1];
+       char *buf;
+       off_t off;
+       int retry = 10;
+
+       if (ux) {
+               if(!*ux->ut_user)
+                       return;
+               strncpy(name, ux->ut_user, sizeof(ux->ut_user));
+               name[sizeof(ux->ut_user)] = 0;
+       } else {
+               if (!*u->ut_name)
+                       return;
+               strncpy(name, u->ut_name, sizeof(u->ut_name));
+               name[sizeof(u->ut_name)] = 0;
+       }
+       if ((buf = _pwnam_r(name, &pw)) == NULL)
+               return;
+#ifdef __LP64__
+       off = (off_t)pw.pw_uid * sizeof(struct lastlog32);
+#else /* __LP64__ */
+       off = (off_t)pw.pw_uid * sizeof(struct lastlog);
+#endif /* __LP64__ */
+       free(buf);
+
+       if ((fd = open(_PATH_LASTLOG, O_WRONLY, 0)) < 0)
+               return;
+       (void)lseek(fd, off, SEEK_SET);
+       bzero(&lock, sizeof(lock));
+       lock.l_type = F_WRLCK;
+       lock.l_whence = SEEK_SET;
+       lock.l_start = off;
+#ifdef __LP64__
+       lock.l_len = sizeof(struct lastlog32);
+#else /* __LP64__ */
+       lock.l_len = sizeof(struct lastlog);
+#endif /* __LP64__ */
+       /* try to lock, but give up after retry times, and write anyways */
+       while(retry-- > 0) {
+               if (fcntl(fd, F_SETLK, &lock) == 0)
+                       break;
+               usleep(10000);
+       }
+       l.ll_time = u->ut_time;
+       strncpy(l.ll_line, u->ut_line, sizeof(l.ll_line));
+       strncpy(l.ll_host, u->ut_host, sizeof(l.ll_host));
+       (void) write(fd, &l, sizeof(l));
+       lock.l_type = F_UNLCK;
+       (void) fcntl(fd, F_SETLK, &lock);
+       (void) close(fd);
+}
+
+/*
+ * Write _PATH_UTMP, given a struct utmp, depending on the value of
+ * "mustexist".
+ */
+__private_extern__ void
+#ifdef __LP64__
+_write_utmp(const struct utmp32 *u, int mustexist)
+#else /* __LP64__ */
+_write_utmp(const struct utmp *u, int mustexist)
+#endif /* __LP64__ */
+{
+       int fd, slot;
+       struct ttyent *ttyp;
+#ifdef __LP64__
+       struct utmp32 tmp;
+#else /* __LP64__ */
+       struct utmp tmp;
+#endif /* __LP64__ */
+       int found = 0;
+       static struct {
+               char line[sizeof(u->ut_line)];
+               int slot;
+       } cache;
+
+       if ((fd = open(_PATH_UTMP, O_RDWR, 0)) < 0)
+               return;
+
+       if (!strncmp(cache.line, u->ut_line, sizeof(u->ut_line))) {
+               slot = cache.slot;
+               found++;
+       }
+       /* do equivalent of ttyslot(), but using u->ut_line */
+       if (!found) {
+               setttyent();
+               slot = 1;
+               for(;;) {
+                       if ((ttyp = getttyent()) == NULL)
+                               break;
+                       if (!strncmp(ttyp->ty_name, u->ut_line, sizeof(u->ut_line))) {
+                               strncpy(cache.line, u->ut_line, sizeof(u->ut_line));
+                               cache.slot = slot;
+                               found++;
+                               break;
+                       }
+                       slot++;
+               }
+               endttyent();
+       }
+
+       if (!found) {   /* no assigned slot */
+#ifdef __LP64__
+               (void)lseek(fd, (off_t)slot * sizeof(struct utmp32), SEEK_SET);
+#else /* __LP64__ */
+               (void)lseek(fd, (off_t)slot * sizeof(struct utmp), SEEK_SET);
+#endif /* __LP64__ */
+               for(;;) {
+                       if (read(fd, &tmp, sizeof(tmp)) != sizeof(tmp))
+                               break;
+                       if (!strncmp(tmp.ut_line, u->ut_line, sizeof(u->ut_line))) {
+                               strncpy(cache.line, u->ut_line, sizeof(u->ut_line));
+                               cache.slot = slot;
+                               found++;
+                               break;
+                       }
+                       slot++;
+               }
+       }
+
+       if (!found && mustexist) {
+               (void)close(fd);
+               return;
+       }
+#ifdef __LP64__
+       (void)lseek(fd, (off_t)slot * sizeof(struct utmp32), SEEK_SET);
+       (void)write(fd, u, sizeof(struct utmp32));
+#else /* __LP64__ */
+       (void)lseek(fd, (off_t)slot * sizeof(struct utmp), SEEK_SET);
+       (void)write(fd, u, sizeof(struct utmp));
+#endif /* __LP64__ */
+       (void)close(fd);
+}
+
+/*
+ * Write all the necessary files (utmp, wtmp, lastlog), depending on the
+ * given struct utmpx.
+ */
+__private_extern__ void
+_write_utmp_compat(const struct utmpx *ux)
+{
+#ifdef __LP64__
+       struct utmp32 u;
+#else /* __LP64__ */
+       struct utmp u;
+#endif /* __LP64__ */
+       int which;
+
+       which = _utmp_compat(ux, &u);
+       if (which & UTMP_COMPAT_UTMP0)
+               _write_utmp(&u, 0);
+       else if (which & UTMP_COMPAT_UTMP1)
+               _write_utmp(&u, 1);
+       if (which & UTMP_COMPAT_WTMP)
+               _write_wtmp(&u);
+       if (which & UTMP_COMPAT_LASTLOG)
+               _write_lastlog(&u, ux);
+}
+
+/* Append a struct wtmp to _PATH_WTMP */
+__private_extern__ void
+#ifdef __LP64__
+_write_wtmp(const struct utmp32 *u)
+#else /* __LP64__ */
+_write_wtmp(const struct utmp *u)
+#endif /* __LP64__ */
+{
+       int fd;
+       struct stat buf;
+
+       if ((fd = open(_PATH_WTMP, O_WRONLY | O_APPEND, 0)) < 0)
+               return;
+       if (fstat(fd, &buf) == 0) {
+               if (write(fd, u, sizeof(*u)) != sizeof(*u))
+                       (void) ftruncate(fd, buf.st_size);
+       }
+       (void) close(fd);
+}
+#endif /* UTMP_COMPAT */