]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - login-utils/lslogins.c
misc: consolidate smartcols error messages
[thirdparty/util-linux.git] / login-utils / lslogins.c
index d0fe353210c816cfd97479de0c7ae82f223b2c50..ab04c10bb2214f738feed77219ce4ae6deec4e70 100644 (file)
@@ -2,6 +2,7 @@
  * lslogins - List information about users on the system
  *
  * Copyright (C) 2014 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include <shadow.h>
 #include <paths.h>
 #include <time.h>
-#include <utmp.h>
+#include <utmpx.h>
 #include <signal.h>
 #include <err.h>
 #include <limits.h>
-
 #include <search.h>
 
 #include <libsmartcols.h>
 #ifdef HAVE_LIBSELINUX
-#include <selinux/selinux.h>
+# include <selinux/selinux.h>
+#endif
+
+#ifdef HAVE_LIBSYSTEMD
+# include <systemd/sd-journal.h>
 #endif
 
 #include "c.h"
@@ -51,7 +55,8 @@
 #include "optutils.h"
 #include "pathnames.h"
 #include "logindefs.h"
-#include "readutmp.h"
+#include "procutils.h"
+#include "timeutils.h"
 
 /*
  * column description
@@ -82,12 +87,12 @@ static int outmode;
  * output modes
  */
 enum {
-       out_colon = 1,
-       out_export,
-       out_newline,
-       out_raw,
-       out_nul,
-       out_pretty,
+       OUT_COLON = 1,
+       OUT_EXPORT,
+       OUT_NEWLINE,
+       OUT_RAW,
+       OUT_NUL,
+       OUT_PRETTY
 };
 
 struct lslogins_user {
@@ -97,14 +102,17 @@ struct lslogins_user {
        gid_t gid;
        char *gecos;
 
-       int nopasswd;
+       int pwd_empty;
        int nologin;
-       int locked;
+       int pwd_lock;
+       int pwd_deny;
 
-       char *sgroups;
+       gid_t *sgroups;
+       size_t nsgroups;
 
        char *pwd_ctime;
        char *pwd_warn;
+       char *pwd_expire;
        char *pwd_ctime_min;
        char *pwd_ctime_max;
 
@@ -122,324 +130,369 @@ struct lslogins_user {
        char *shell;
        char *pwd_status;
        int   hushed;
+       char *nprocs;
 
 };
+
+/*
+ * time modes
+ * */
+enum {
+       TIME_INVALID = 0,
+       TIME_SHORT,
+       TIME_FULL,
+       TIME_ISO,
+       TIME_ISO_SHORT,
+};
+
 /*
  * flags
  */
 enum {
-       F_EXPIR = (1 << 0),
-       F_MORE  = (1 << 1),
-       F_NOPWD = (1 << 2),
        F_SYSAC = (1 << 3),
        F_USRAC = (1 << 4),
-       F_SORT  = (1 << 5),
-       F_EXTRA = (1 << 6),
-       F_FAIL  = (1 << 7),
-       F_LAST  = (1 << 8),
-       F_SELINUX = (1 << 9),
 };
 
 /*
  * IDs
  */
 enum {
-       COL_LOGIN = 0,
+       COL_USER = 0,
        COL_UID,
-       COL_PGRP,
-       COL_PGID,
-       COL_SGRPS,
+       COL_GECOS,
        COL_HOME,
        COL_SHELL,
-       COL_GECOS,
+       COL_NOLOGIN,
+       COL_PWDLOCK,
+       COL_PWDEMPTY,
+       COL_PWDDENY,
+       COL_GROUP,
+       COL_GID,
+       COL_SGROUPS,
+       COL_SGIDS,
        COL_LAST_LOGIN,
        COL_LAST_TTY,
        COL_LAST_HOSTNAME,
        COL_FAILED_LOGIN,
        COL_FAILED_TTY,
        COL_HUSH_STATUS,
-       COL_NOLOGIN,
-       COL_LOCKED,
-       COL_NOPASSWD,
        COL_PWD_WARN,
        COL_PWD_CTIME,
        COL_PWD_CTIME_MIN,
        COL_PWD_CTIME_MAX,
+       COL_PWD_EXPIR,
        COL_SELINUX,
+       COL_NPROCS,
+};
+
+#define is_wtmp_col(x) ((x) == COL_LAST_LOGIN     || \
+                        (x) == COL_LAST_TTY       || \
+                        (x) == COL_LAST_HOSTNAME)
+
+#define is_btmp_col(x) ((x) == COL_FAILED_LOGIN   || \
+                        (x) == COL_FAILED_TTY)
+
+enum {
+       STATUS_FALSE = 0,
+       STATUS_TRUE,
+       STATUS_UNKNOWN
 };
 
-static const char *const status[] = { "0", "1", "-" };
-static struct lslogins_coldesc coldescs[] =
+static const char *const status[] = {
+       [STATUS_FALSE]  = "0",
+       [STATUS_TRUE]   = "1",
+       [STATUS_UNKNOWN]= NULL
+};
+
+static const char *const pretty_status[] = {
+       [STATUS_FALSE]  = N_("no"),
+       [STATUS_TRUE]   = N_("yes"),
+       [STATUS_UNKNOWN]= NULL
+};
+
+#define get_status(x)  (outmode == OUT_PRETTY ? pretty_status[(x)] : status[(x)])
+
+static const struct lslogins_coldesc coldescs[] =
 {
-       [COL_LOGIN]             = { "LOGIN",            N_("user/system login"), "Login", 0.2, SCOLS_FL_NOEXTREMES },
-       [COL_UID]               = { "UID",              N_("user UID"), "UID", 0.05, SCOLS_FL_RIGHT},
-       [COL_NOPASSWD]          = { "NOPASSWD",         N_("account has a password?"), "No password", 1 },
-       [COL_NOLOGIN]           = { "NOLOGIN",          N_("account has a password?"), "No login", 1 },
-       [COL_LOCKED]            = { "LOCKED",           N_("account has a password?"), "Locked", 1 },
-       [COL_PGRP]              = { "GROUPS",           N_("primary group name"), "Primary group", 0.2 },
-       [COL_PGID]              = { "GID",              N_("primary group GID"), "GID", 0.05, SCOLS_FL_RIGHT },
-       [COL_SGRPS]             = { "SUPP-GROUPS",      N_("secondary group names and GIDs"), "Secondary groups", 0.5 },
-       [COL_HOME]              = { "HOMEDIR",          N_("home directory"), "Home directory", 0.3 },
-       [COL_SHELL]             = { "SHELL",            N_("login shell"), "Shell", 0.1 },
-       [COL_GECOS]             = { "GECOS",            N_("full user name"), "Comment field", 0.3, SCOLS_FL_TRUNC },
-       [COL_LAST_LOGIN]        = { "LAST-LOGIN",       N_("date of last login"), "Last login", 24 },
-       [COL_LAST_TTY]          = { "LAST-TTY",         N_("last tty used"), "Last terminal", 0.05 },
-       [COL_LAST_HOSTNAME]     = { "LAST-HOSTNAME",    N_("hostname during the last session"), "Last hostname",  0.2},
-       [COL_FAILED_LOGIN]      = { "FAILED-LOGIN",     N_("date of last failed login"), "Failed login", 24 },
-       [COL_FAILED_TTY]        = { "FAILED-TTY",       N_("where did the login fail?"), "Failed login terminal", 0.05 },
-       [COL_HUSH_STATUS]       = { "HUSHED",           N_("User's hush settings"), "Hushed", 1 },
-       [COL_PWD_WARN]          = { "PWD-WARN",         N_("password warn interval"), "Days to passwd warning", 24 },
-       [COL_PWD_CTIME]         = { "PWD-CHANGE",       N_("date of last password change"), "Password changed", 24 },
-       [COL_PWD_CTIME_MIN]     = { "PWD-MIN",          N_("number of days required between changes"), "Minimal change time", 24 },
-       [COL_PWD_CTIME_MAX]     = { "PWD-MAX",          N_("max number of days a password may remain unchanged"), "Maximal change time", 24 },
-       [COL_SELINUX]           = { "CONTEXT",          N_("the user's security context"), "Selinux context", 0.4 },
+       [COL_USER]          = { "USER",         N_("user name"), N_("Username"), 0.1, SCOLS_FL_NOEXTREMES },
+       [COL_UID]           = { "UID",          N_("user ID"), "UID", 1, SCOLS_FL_RIGHT},
+       [COL_PWDEMPTY]      = { "PWD-EMPTY",    N_("password not required"), N_("Password not required"), 1, SCOLS_FL_RIGHT },
+       [COL_PWDDENY]       = { "PWD-DENY",     N_("login by password disabled"), N_("Login by password disabled"), 1, SCOLS_FL_RIGHT },
+       [COL_PWDLOCK]       = { "PWD-LOCK",     N_("password defined, but locked"), N_("Password is locked"), 1, SCOLS_FL_RIGHT },
+       [COL_NOLOGIN]       = { "NOLOGIN",      N_("log in disabled by nologin(8) or pam_nologin(8)"), N_("No login"), 1, SCOLS_FL_RIGHT },
+       [COL_GROUP]         = { "GROUP",        N_("primary group name"), N_("Primary group"), 0.1 },
+       [COL_GID]           = { "GID",          N_("primary group ID"), "GID", 1, SCOLS_FL_RIGHT },
+       [COL_SGROUPS]       = { "SUPP-GROUPS",  N_("supplementary group names"), N_("Supplementary groups"), 0.1 },
+       [COL_SGIDS]         = { "SUPP-GIDS",    N_("supplementary group IDs"), N_("Supplementary group IDs"), 0.1 },
+       [COL_HOME]          = { "HOMEDIR",      N_("home directory"), N_("Home directory"), 0.1 },
+       [COL_SHELL]         = { "SHELL",        N_("login shell"), N_("Shell"), 0.1 },
+       [COL_GECOS]         = { "GECOS",        N_("full user name"), N_("Gecos field"), 0.1, SCOLS_FL_TRUNC },
+       [COL_LAST_LOGIN]    = { "LAST-LOGIN",   N_("date of last login"), N_("Last login"), 0.1, SCOLS_FL_RIGHT },
+       [COL_LAST_TTY]      = { "LAST-TTY",     N_("last tty used"), N_("Last terminal"), 0.05 },
+       [COL_LAST_HOSTNAME] = { "LAST-HOSTNAME",N_("hostname during the last session"), N_("Last hostname"),  0.1},
+       [COL_FAILED_LOGIN]  = { "FAILED-LOGIN", N_("date of last failed login"), N_("Failed login"), 0.1 },
+       [COL_FAILED_TTY]    = { "FAILED-TTY",   N_("where did the login fail?"), N_("Failed login terminal"), 0.05 },
+       [COL_HUSH_STATUS]   = { "HUSHED",       N_("user's hush settings"), N_("Hushed"), 1, SCOLS_FL_RIGHT },
+       [COL_PWD_WARN]      = { "PWD-WARN",     N_("days user is warned of password expiration"), N_("Password expiration warn interval"), 0.1, SCOLS_FL_RIGHT },
+       [COL_PWD_EXPIR]     = { "PWD-EXPIR",    N_("password expiration date"), N_("Password expiration"), 0.1, SCOLS_FL_RIGHT },
+       [COL_PWD_CTIME]     = { "PWD-CHANGE",   N_("date of last password change"), N_("Password changed"), 0.1, SCOLS_FL_RIGHT},
+       [COL_PWD_CTIME_MIN] = { "PWD-MIN",      N_("number of days required between changes"), N_("Minimum change time"), 0.1, SCOLS_FL_RIGHT },
+       [COL_PWD_CTIME_MAX] = { "PWD-MAX",      N_("max number of days a password may remain unchanged"), N_("Maximum change time"), 0.1, SCOLS_FL_RIGHT },
+       [COL_SELINUX]       = { "CONTEXT",      N_("the user's security context"), N_("Selinux context"), 0.1 },
+       [COL_NPROCS]        = { "PROC",         N_("number of processes run by the user"), N_("Running processes"), 1, SCOLS_FL_RIGHT },
 };
 
 struct lslogins_control {
-       struct utmp *wtmp;
+       struct utmpx *wtmp;
        size_t wtmp_size;
 
-       struct utmp *btmp;
+       struct utmpx *btmp;
        size_t btmp_size;
 
        void *usertree;
 
+       uid_t uid;
        uid_t UID_MIN;
        uid_t UID_MAX;
 
        uid_t SYS_UID_MIN;
        uid_t SYS_UID_MAX;
 
-       int (*cmp_fn) (const void *a, const void *b);
-
        char **ulist;
        size_t ulsiz;
 
-       int sel_enabled;
+       unsigned int time_mode;
+
+       const char *journal_path;
+
+       unsigned int selinux_enabled : 1,
+                    ulist_on : 1,
+                    noheadings : 1,
+                    notrunc : 1;
 };
-/* these have to remain global since there's no other
- * reasonable way to pass them for each call of fill_table()
- * via twalk() */
+
+/* these have to remain global since there's no other reasonable way to pass
+ * them for each call of fill_table() via twalk() */
 static struct libscols_table *tb;
-static int columns[ARRAY_SIZE(coldescs)];
+
+/* columns[] array specifies all currently wanted output column. The columns
+ * are defined by coldescs[] array and you can specify (on command line) each
+ * column twice. That's enough, dynamically allocated array of the columns is
+ * unnecessary overkill and over-engineering in this case */
+static int columns[ARRAY_SIZE(coldescs) * 2];
 static int ncolumns;
 
-static int
-column_name_to_id(const char *name, size_t namesz)
+static inline size_t err_columns_index(size_t arysz, size_t idx)
+{
+       if (idx >= arysz)
+               errx(EXIT_FAILURE, _("too many columns specified, "
+                                    "the limit is %zu columns"),
+                               arysz - 1);
+       return idx;
+}
+
+#define add_column(ary, n, id) \
+               ((ary)[ err_columns_index(ARRAY_SIZE(ary), (n)) ] = (id))
+
+static int column_name_to_id(const char *name, size_t namesz)
 {
        size_t i;
 
        for (i = 0; i < ARRAY_SIZE(coldescs); i++) {
                const char *cn = coldescs[i].name;
 
-               if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) {
+               if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
                        return i;
-               }
        }
        warnx(_("unknown column: %s"), name);
        return -1;
 }
 
-static char *make_time(struct tm *tm)
-{
-       char *t, *s;
-
-       if (!tm)
-               return NULL;
+static struct timeval now;
 
-       s = asctime(tm);
-       if (!s)
-               return NULL;
-
-       if (*(t = s + strlen(s) - 1) == '\n')
-               *t = '\0';
-       return xstrdup(s);
-}
-
-static void __attribute__((__noreturn__)) usage(FILE *out)
+static char *make_time(int mode, time_t time)
 {
-       size_t i;
-
-       fputs(USAGE_HEADER, out);
-       fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
-
-       fputs(USAGE_OPTIONS, out);
-       fputs(_(" -a, --acc-expiration     Display data\n"), out);
-       fputs(_(" -c, --colon-separate     Display data in a format similar to /etc/passwd\n"), out);
-       fputs(_(" -e, --export             Display in an export-able output format\n"), out);
-       fputs(_(" -f, --failed             Display data about the last users' failed logins\n"), out);
-       fputs(_(" -g, --groups=<groups>    Display users belonging to a group in GROUPS\n"), out);
-       fputs(_(" -l, --logins=<logins>    Display only users from LOGINS\n"), out);
-       fputs(_(" --last                   Show info about the users' last login sessions\n"), out);
-       fputs(_(" -m, --supp-groups        Display supplementary groups as well\n"), out);
-       fputs(_(" -n, --newline            Display each piece of information on a new line\n"), out);
-       fputs(_(" --notrunc                Don't truncate output\n"), out);
-       fputs(_(" -o, --output[=<list>]    Define the columns to output\n"), out);
-       fputs(_(" -r, --raw                Display the raw table\n"), out);
-       fputs(_(" -s, --system-accs        Display system accounts\n"), out);
-       fputs(_(" -t, --sort               Sort output by login instead of UID\n"), out);
-       fputs(_(" -u, --user-accs          Display user accounts\n"), out);
-       fputs(_(" -x, --extra              Display extra information\n"), out);
-       fputs(_(" -z, --print0             Delimit user entries with a nul character\n"), out);
-       fputs(_(" -Z, --context            Display the users' security context\n"), out);
-       fputs(_(" --wtmp-file              Set an alternate path for wtmp\n"), out);
-       fputs(_(" --btmp-file              Set an alternate path for btmp\n"), out);
-       fputs(USAGE_SEPARATOR, out);
-       fputs(USAGE_HELP, out);
-       fputs(USAGE_VERSION, out);
-
-       fprintf(out, _("\nAvailable columns:\n"));
-
-       for (i = 0; i < ARRAY_SIZE(coldescs); i++)
-               fprintf(out, " %14s  %s\n", coldescs[i].name, _(coldescs[i].help));
+       int rc = 0;
+       char buf[64] = {0};
+
+       switch(mode) {
+       case TIME_FULL:
+       {
+               char *s;
+               struct tm tm;
+               localtime_r(&time, &tm);
+
+               asctime_r(&tm, buf);
+               if (*(s = buf + strlen(buf) - 1) == '\n')
+                       *s = '\0';
+               rc = 0;
+               break;
+       }
+       case TIME_SHORT:
+               rc = strtime_short(&time, &now, UL_SHORTTIME_THISYEAR_HHMM,
+                               buf, sizeof(buf));
+               break;
+       case TIME_ISO:
+               rc = strtime_iso(&time, ISO_8601_DATE|ISO_8601_TIME|ISO_8601_TIMEZONE,
+                                  buf, sizeof(buf));
+               break;
+       case TIME_ISO_SHORT:
+               rc = strtime_iso(&time, ISO_8601_DATE, buf, sizeof(buf));
+               break;
+       default:
+               errx(EXIT_FAILURE, _("unsupported time type"));
+       }
 
-       fprintf(out, _("\nFor more details see lslogins(1).\n"));
+       if (rc)
+                errx(EXIT_FAILURE, _("failed to compose time string"));
 
-       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+       return xstrdup(buf);
 }
-struct lslogins_sgroups {
-       char *gid;
-       char *uname;
-       struct lslogins_sgroups *next;
-};
+
 
 static char *uidtostr(uid_t uid)
 {
        char *str_uid = NULL;
-       return (0 > xasprintf(&str_uid, "%u", uid)) ? NULL : str_uid;
+       xasprintf(&str_uid, "%u", uid);
+       return str_uid;
 }
 
 static char *gidtostr(gid_t gid)
 {
        char *str_gid = NULL;
-       return (0 > xasprintf(&str_gid, "%u", gid)) ? NULL : str_gid;
+       xasprintf(&str_gid, "%u", gid);
+       return str_gid;
 }
 
-static struct lslogins_sgroups *build_sgroups_list(int len, gid_t *list, int *slen)
+static char *build_sgroups_string(gid_t *sgroups, size_t nsgroups, int want_names)
 {
-       int n = 0;
-       struct lslogins_sgroups *sgrps, *retgrps;
-       struct group *grp;
-       char *buf = NULL;
+       size_t n = 0, maxlen, len;
+       char *res, *p;
 
-       if (!len || !list)
+       if (!nsgroups)
                return NULL;
 
-       *slen = 0;
-
-       retgrps = sgrps = xcalloc(1, sizeof(struct lslogins_sgroups));
-       while (n < len) {
-               if (sgrps->next)
-                       sgrps = sgrps->next;
-               /* TODO: rewrite */
-               sgrps->gid = gidtostr(list[n]);
+       len = maxlen = nsgroups * 10;
+       res = p = xmalloc(maxlen);
 
-               grp = getgrgid(list[n]);
-               if (!grp) {
-                       free(retgrps);
-                       return NULL;
+       while (n < nsgroups) {
+               int x;
+again:
+               if (!want_names)
+                       x = snprintf(p, len, "%u,", sgroups[n]);
+               else {
+                       struct group *grp = getgrgid(sgroups[n]);
+                       if (!grp) {
+                               free(res);
+                               return NULL;
+                       }
+                       x = snprintf(p, len, "%s,", grp->gr_name);
                }
-               sgrps->uname = xstrdup(grp->gr_name);
 
-               *slen += strlen(sgrps->gid) + strlen(sgrps->uname);
+               if (x < 0 || (size_t) x >= len) {
+                       size_t cur = p - res;
 
-               sgrps->next = xcalloc(1, sizeof(struct lslogins_sgroups));
+                       maxlen *= 2;
+                       res = xrealloc(res, maxlen);
+                       p = res + cur;
+                       len = maxlen - cur;
+                       goto again;
+               }
 
+               len -= x;
+               p += x;
                ++n;
        }
 
-       /* space for a pair of parentheses for each gname + (n - 1) commas in between */
-       slen += 3 * n - 1;
+       if (p > res)
+               *(p - 1) = '\0';
 
-       free(buf);
-       free(sgrps->next);
-       sgrps->next = NULL;
-
-       return retgrps;
+       return res;
 }
 
-static void free_sgroups_list(struct lslogins_sgroups *sgrps)
-{
-       struct lslogins_sgroups *tmp;
-
-       if (!sgrps)
-               return;
-
-       tmp = sgrps->next;
-       while (tmp) {
-               free(sgrps->gid);
-               free(sgrps->uname);
-               free(sgrps);
-               sgrps = tmp;
-               tmp = tmp->next;
-       }
-}
-
-static char *build_sgroups_string(int len, gid_t *list)
-{
-       char *ret = NULL, *slist;
-       int slen, prlen;
-       struct lslogins_sgroups *sgrps;
-
-       sgrps = build_sgroups_list(len, list, &slen);
-
-       if (!sgrps)
-               return NULL;
-
-       ret = slist = xcalloc(1, sizeof(char) * (slen + 1));
-
-       while (sgrps->next) {
-               prlen = sprintf(slist, "%s(%s),", sgrps->gid, sgrps->uname);
-               if (prlen < 0) {
-                       free_sgroups_list(sgrps);
-                       return NULL;
-               }
-               slist += prlen;
-               sgrps = sgrps->next;
-       }
-       prlen = sprintf(slist, "%s(%s)", sgrps->gid, sgrps->uname);
-
-       free_sgroups_list(sgrps);
-       return ret;
-}
-
-static struct utmp *get_last_wtmp(struct lslogins_control *ctl, const char *username)
+static struct utmpx *get_last_wtmp(struct lslogins_control *ctl, const char *username)
 {
        size_t n = 0;
-       size_t len;
 
        if (!username)
                return NULL;
 
-       len = strlen(username);
        n = ctl->wtmp_size - 1;
        do {
                if (!strncmp(username, ctl->wtmp[n].ut_user,
-                   len < UT_NAMESIZE ? len : UT_NAMESIZE))
+                            sizeof(ctl->wtmp[0].ut_user)))
                        return ctl->wtmp + n;
        } while (n--);
        return NULL;
 
 }
-static struct utmp *get_last_btmp(struct lslogins_control *ctl, const char *username)
+
+static int require_wtmp(void)
+{
+       size_t i;
+       for (i = 0; i < (size_t) ncolumns; i++)
+               if (is_wtmp_col(columns[i]))
+                       return 1;
+       return 0;
+}
+
+static int require_btmp(void)
+{
+       size_t i;
+       for (i = 0; i < (size_t) ncolumns; i++)
+               if (is_btmp_col(columns[i]))
+                       return 1;
+       return 0;
+}
+
+static struct utmpx *get_last_btmp(struct lslogins_control *ctl, const char *username)
 {
        size_t n = 0;
-       size_t len;
 
        if (!username)
                return NULL;
 
-       len = strlen(username);
        n = ctl->btmp_size - 1;
        do {
                if (!strncmp(username, ctl->btmp[n].ut_user,
-                   len < UT_NAMESIZE ? len : UT_NAMESIZE))
+                            sizeof(ctl->wtmp[0].ut_user)))
                        return ctl->btmp + n;
        }while (n--);
        return NULL;
 
 }
 
+static int read_utmp(char const *file, size_t *nents, struct utmpx **res)
+{
+       size_t n_read = 0, n_alloc = 0;
+       struct utmpx *utmp = NULL, *u;
+
+       if (utmpxname(file) < 0)
+               return -errno;
+
+       setutxent();
+       errno = 0;
+
+       while ((u = getutxent()) != NULL) {
+               if (n_read == n_alloc) {
+                       n_alloc += 32;
+                       utmp = xrealloc(utmp, n_alloc * sizeof (struct utmpx));
+               }
+               utmp[n_read++] = *u;
+       }
+       if (!u && errno) {
+               free(utmp);
+               return -errno;
+       }
+
+       endutxent();
+
+       *nents = n_read;
+       *res = utmp;
+
+       return 0;
+}
+
 static int parse_wtmp(struct lslogins_control *ctl, char *path)
 {
        int rc = 0;
@@ -459,80 +512,98 @@ static int parse_btmp(struct lslogins_control *ctl, char *path)
                err(EXIT_FAILURE, "%s", path);
        return rc;
 }
-static int get_sgroups(int *len, gid_t **list, struct passwd *pwd)
+
+static int get_sgroups(gid_t **list, size_t *len, struct passwd *pwd)
 {
-       int n = 0;
-       gid_t *safelist;
+       size_t n = 0;
+       int ngroups = 0;
 
        *len = 0;
        *list = NULL;
 
        /* first let's get a supp. group count */
-       getgrouplist(pwd->pw_name, pwd->pw_gid, *list, len);
-       if (!*len)
+       getgrouplist(pwd->pw_name, pwd->pw_gid, *list, &ngroups);
+       if (!ngroups)
                return -1;
 
-       *list = xcalloc(1, *len * sizeof(gid_t));
+       *list = xcalloc(1, ngroups * sizeof(gid_t));
 
        /* now for the actual list of GIDs */
-       if (-1 == getgrouplist(pwd->pw_name, pwd->pw_gid, *list, len))
+       if (-1 == getgrouplist(pwd->pw_name, pwd->pw_gid, *list, &ngroups))
                return -1;
 
+       *len = (size_t) ngroups;
+
        /* getgroups also returns the user's primary GID - dispose of it */
        while (n < *len) {
                if ((*list)[n] == pwd->pw_gid)
                        break;
                ++n;
        }
-       (*list)[n] = (*list)[--(*len)];
 
-       safelist = xrealloc(*list, *len * sizeof(gid_t));
-       if (!safelist && *len) {
-               free(*list);
-               return -1;
-       }
-       *list = safelist;
+       if (*len)
+               (*list)[n] = (*list)[--(*len)];
 
        return 0;
 }
 
+static int get_nprocs(const uid_t uid)
+{
+       int nprocs = 0;
+       pid_t pid;
+       struct proc_processes *proc = proc_open_processes();
+
+       proc_processes_filter_by_uid(proc, uid);
+
+       while (!proc_next_pid(proc, &pid))
+               ++nprocs;
+
+       proc_close_processes(proc);
+       return nprocs;
+}
+
+static int valid_pwd(const char *str)
+{
+       const char *p;
+
+       for (p = str; p && *p; p++)
+               if (!isalnum((unsigned char) *p))
+                       return 0;
+       return p > str ? 1 : 0;
+}
+
 static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const char *username)
 {
        struct lslogins_user *user;
        struct passwd *pwd;
        struct group *grp;
        struct spwd *shadow;
-       struct utmp *user_wtmp = NULL, *user_btmp = NULL;
+       struct utmpx *user_wtmp = NULL, *user_btmp = NULL;
        int n = 0;
        time_t time;
-       struct tm tm;
        uid_t uid;
        errno = 0;
 
-       if (username)
-               pwd = getpwnam(username);
-       else
-               pwd = getpwent();
-
+       pwd = username ? getpwnam(username) : getpwent();
        if (!pwd)
                return NULL;
 
-       uid = pwd->pw_uid;
-       /* nfsnobody is an exception to the UID_MAX limit.
-        * This is "nobody" on some systems; the decisive
-        * point is the UID - 65534 */
+       ctl->uid = uid = pwd->pw_uid;
+
+       /* nfsnobody is an exception to the UID_MAX limit.  This is "nobody" on
+        * some systems; the decisive point is the UID - 65534 */
        if ((lslogins_flag & F_USRAC) &&
-           strcmp("nfsnobody", pwd->pw_name)) {
+           strcmp("nfsnobody", pwd->pw_name) != 0 &&
+           uid != 0) {
                if (uid < ctl->UID_MIN || uid > ctl->UID_MAX) {
                        errno = EAGAIN;
                        return NULL;
                }
-       }
-       else if (lslogins_flag & F_SYSAC) {
-               if (uid < ctl->SYS_UID_MIN || uid > ctl->SYS_UID_MAX) {
-                       errno = EAGAIN;
-                       return NULL;
-               }
+
+       } else if ((lslogins_flag & F_SYSAC) &&
+                  (uid < ctl->SYS_UID_MIN || uid > ctl->SYS_UID_MAX)) {
+               errno = EAGAIN;
+               return NULL;
        }
 
        user = xcalloc(1, sizeof(struct lslogins_user));
@@ -546,207 +617,150 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
        if (ctl->btmp)
                user_btmp = get_last_btmp(ctl, pwd->pw_name);
 
-       /* sufficient permissions to get a shadow entry? */
-       errno = 0;
        lckpwdf();
        shadow = getspnam(pwd->pw_name);
        ulckpwdf();
 
-       if (!shadow) {
-               if (errno != EACCES)
-                       err(EXIT_FAILURE, "%s", strerror(errno));
-       }
-       else {
-               /* we want these dates in seconds */
-               shadow->sp_lstchg *= 86400;
-       }
+       /* required  by tseach() stuff */
+       user->uid = pwd->pw_uid;
 
        while (n < ncolumns) {
                switch (columns[n++]) {
-                       case COL_LOGIN:
-                               user->login = xstrdup(pwd->pw_name);
-                               break;
-                       case COL_UID:
-                               user->uid = pwd->pw_uid;
-                               break;
-                       case COL_PGRP:
-                               user->group = xstrdup(grp->gr_name);
-                               break;
-                       case COL_PGID:
-                               user->gid = pwd->pw_gid;
-                               break;
-                       case COL_SGRPS:
-                               {
-                                       int n = 0;
-                                       gid_t *list = NULL;
-
-                                       if (get_sgroups(&n, &list, pwd))
-                                               err(1, NULL);
-
-                                       user->sgroups = build_sgroups_string(n, list);
-
-                                       if (!user->sgroups)
-                                               user->sgroups = xstrdup(status[2]);
-                                       break;
-                               }
-                       case COL_HOME:
-                               user->homedir = xstrdup(pwd->pw_dir);
-                               break;
-                       case COL_SHELL:
-                               user->shell = xstrdup(pwd->pw_shell);
-                               break;
-                       case COL_GECOS:
-                               user->gecos = xstrdup(pwd->pw_gecos);
-                               break;
-                       case COL_LAST_LOGIN:
-                               if (user_wtmp) {
-                                       time = user_wtmp->ut_tv.tv_sec;
-                                       localtime_r(&time, &tm);
-                                       user->last_login = make_time(&tm);
-                               }
-                               else
-                                       user->last_login = xstrdup(status[2]);
-                               break;
-                       case COL_LAST_TTY:
-                               if (user_wtmp)
-                                       user->last_tty = xstrdup(user_wtmp->ut_line);
-                               else
-                                       user->last_tty = xstrdup(status[2]);
-                               break;
-                       case COL_LAST_HOSTNAME:
-                               if (user_wtmp)
-                                       user->last_hostname = xstrdup(user_wtmp->ut_host);
-                               else
-                                       user->last_hostname = xstrdup(status[2]);
-                               break;
-                       case COL_FAILED_LOGIN:
-                               if (user_btmp) {
-                                       time = user_btmp->ut_tv.tv_sec;
-                                       localtime_r(&time, &tm);
-                                       user->failed_login = make_time(&tm);
-                               }
-                               else
-                                       user->failed_login = xstrdup(status[2]);
-                               break;
-                       case COL_FAILED_TTY:
-                               if (user_btmp)
-                                       user->failed_tty = xstrdup(user_btmp->ut_line);
-                               else
-                                       user->failed_tty = xstrdup(status[2]);
-                               break;
-                       case COL_HUSH_STATUS:
-                               user->hushed = get_hushlogin_status(pwd, 0);
-                               if (user->hushed == -1)
-                                       user->hushed = 2;
-                               break;
-                       case COL_NOPASSWD:
-                               if (shadow) {
-                                       if (!*shadow->sp_pwdp) /* '\0' */
-                                               user->nopasswd = 1;
-                               }
-                               else
-                                       user->nopasswd = 2;
-                               break;
-                       case COL_NOLOGIN:
-                               if ((pwd->pw_uid && !(close(open("/etc/nologin", O_RDONLY)))) ||
-                                   strstr(pwd->pw_shell, "nologin")) {
-                                       user->nologin = 1;
-                               }
-                               break;
-                       case COL_LOCKED:
-                               if (shadow) {
-                                       if (*shadow->sp_pwdp == '!')
-                                               user->locked = 1;
-                               }
-                               else
-                                       user->locked = 2;
-                               break;
-                       case COL_PWD_WARN:
-                               if (shadow && shadow->sp_warn!= -1) {
-                                       xasprintf(&user->pwd_warn, "%ld", shadow->sp_warn);
-                               }
-                               else
-                                       user->pwd_warn = xstrdup(status[2]);
-                               break;
-                       case COL_PWD_CTIME:
-                               /* sp_lstchg is specified in days, showing hours (especially in non-GMT
-                                * timezones) would only serve to confuse */
-                               if (shadow) {
-                                       const int date_len = 16;
-
-                                       user->pwd_ctime = xcalloc(1, sizeof(char) * date_len);
-
-                                       localtime_r(&shadow->sp_lstchg, &tm);
-                                       strftime(user->pwd_ctime, date_len, "%a %b %d %Y", &tm);
-                               }
-                               else
-                                       user->pwd_ctime = xstrdup(status[2]);
-                               break;
-                       case COL_PWD_CTIME_MIN:
-                               if (shadow) {
-                                       if (shadow->sp_min <= 0)
-                                               user->pwd_ctime_min = xstrdup("unlimited");
-                                       else
-                                               xasprintf(&user->pwd_ctime_min, "%ld", shadow->sp_min);
-                               }
-                               else
-                                       user->pwd_ctime_min = xstrdup(status[2]);
-                               break;
-                       case COL_PWD_CTIME_MAX:
-                               if (shadow) {
-                                       if (shadow->sp_max <= 0)
-                                               user->pwd_ctime_max = xstrdup("unlimited");
-                                       else
-                                               xasprintf(&user->pwd_ctime_max, "%ld", shadow->sp_max);
-                               }
-                               else
-                                       user->pwd_ctime_max = xstrdup(status[2]);
-                               break;
-                       case COL_SELINUX:
-                               {
+               case COL_USER:
+                       user->login = xstrdup(pwd->pw_name);
+                       break;
+               case COL_UID:
+                       user->uid = pwd->pw_uid;
+                       break;
+               case COL_GROUP:
+                       user->group = xstrdup(grp->gr_name);
+                       break;
+               case COL_GID:
+                       user->gid = pwd->pw_gid;
+                       break;
+               case COL_SGROUPS:
+               case COL_SGIDS:
+                       if (get_sgroups(&user->sgroups, &user->nsgroups, pwd))
+                               err(EXIT_FAILURE, _("failed to get supplementary groups"));
+                       break;
+               case COL_HOME:
+                       user->homedir = xstrdup(pwd->pw_dir);
+                       break;
+               case COL_SHELL:
+                       user->shell = xstrdup(pwd->pw_shell);
+                       break;
+               case COL_GECOS:
+                       user->gecos = xstrdup(pwd->pw_gecos);
+                       break;
+               case COL_LAST_LOGIN:
+                       if (user_wtmp) {
+                               time = user_wtmp->ut_tv.tv_sec;
+                               user->last_login = make_time(ctl->time_mode, time);
+                       }
+                       break;
+               case COL_LAST_TTY:
+                       if (user_wtmp)
+                               user->last_tty = xstrdup(user_wtmp->ut_line);
+                       break;
+               case COL_LAST_HOSTNAME:
+                       if (user_wtmp)
+                               user->last_hostname = xstrdup(user_wtmp->ut_host);
+                       break;
+               case COL_FAILED_LOGIN:
+                       if (user_btmp) {
+                               time = user_btmp->ut_tv.tv_sec;
+                               user->failed_login = make_time(ctl->time_mode, time);
+                       }
+                       break;
+               case COL_FAILED_TTY:
+                       if (user_btmp)
+                               user->failed_tty = xstrdup(user_btmp->ut_line);
+                       break;
+               case COL_HUSH_STATUS:
+                       user->hushed = get_hushlogin_status(pwd, 0);
+                       if (user->hushed == -1)
+                               user->hushed = STATUS_UNKNOWN;
+                       break;
+               case COL_PWDEMPTY:
+                       if (shadow) {
+                               if (!*shadow->sp_pwdp) /* '\0' */
+                                       user->pwd_empty = STATUS_TRUE;
+                       } else
+                               user->pwd_empty = STATUS_UNKNOWN;
+                       break;
+               case COL_PWDDENY:
+                       if (shadow) {
+                               if ((*shadow->sp_pwdp == '!' ||
+                                    *shadow->sp_pwdp == '*') &&
+                                   !valid_pwd(shadow->sp_pwdp + 1))
+                                       user->pwd_deny = STATUS_TRUE;
+                       } else
+                               user->pwd_deny = STATUS_UNKNOWN;
+                       break;
+
+               case COL_PWDLOCK:
+                       if (shadow) {
+                               if (*shadow->sp_pwdp == '!' && valid_pwd(shadow->sp_pwdp + 1))
+                                       user->pwd_lock = STATUS_TRUE;
+                       } else
+                               user->pwd_lock = STATUS_UNKNOWN;
+                       break;
+               case COL_NOLOGIN:
+                       if (strstr(pwd->pw_shell, "nologin"))
+                               user->nologin = 1;
+                       else if (pwd->pw_uid)
+                               user->nologin = access(_PATH_NOLOGIN, F_OK) == 0 ||
+                                               access(_PATH_VAR_NOLOGIN, F_OK) == 0;
+                       break;
+               case COL_PWD_WARN:
+                       if (shadow && shadow->sp_warn >= 0)
+                               xasprintf(&user->pwd_warn, "%ld", shadow->sp_warn);
+                       break;
+               case COL_PWD_EXPIR:
+                       if (shadow && shadow->sp_expire >= 0)
+                               user->pwd_expire = make_time(ctl->time_mode == TIME_ISO ?
+                                               TIME_ISO_SHORT : ctl->time_mode,
+                                               shadow->sp_expire * 86400);
+                       break;
+               case COL_PWD_CTIME:
+                       /* sp_lstchg is specified in days, showing hours
+                        * (especially in non-GMT timezones) would only serve
+                        * to confuse */
+                       if (shadow)
+                               user->pwd_ctime = make_time(ctl->time_mode == TIME_ISO ?
+                                               TIME_ISO_SHORT : ctl->time_mode,
+                                               shadow->sp_lstchg * 86400);
+                       break;
+               case COL_PWD_CTIME_MIN:
+                       if (shadow && shadow->sp_min > 0)
+                               xasprintf(&user->pwd_ctime_min, "%ld", shadow->sp_min);
+                       break;
+               case COL_PWD_CTIME_MAX:
+                       if (shadow && shadow->sp_max > 0)
+                               xasprintf(&user->pwd_ctime_max, "%ld", shadow->sp_max);
+                       break;
+               case COL_SELINUX:
 #ifdef HAVE_LIBSELINUX
-                                       /* typedefs and pointers are pure evil */
-                                       security_context_t con = NULL;
-                                       if (getcon(&con))
-                                               user->context = xstrdup(status[2]);
-                                       else
-                                               user->context = con;
+                       if (ctl->selinux_enabled) {
+                               /* typedefs and pointers are pure evil */
+                               security_context_t con = NULL;
+                               if (getcon(&con) == 0)
+                                       user->context = con;
+                       }
 #endif
-                               }
-                               break;
-                       default:
-                               /* something went very wrong here */
-                               err(EXIT_FAILURE, "fatal: unknown error");
+                       break;
+               case COL_NPROCS:
+                       xasprintf(&user->nprocs, "%d", get_nprocs(pwd->pw_uid));
+                       break;
+               default:
+                       /* something went very wrong here */
+                       err(EXIT_FAILURE, "fatal: unknown error");
+                       break;
                }
        }
-       /* check if we have the info needed to sort */
-       if (lslogins_flag & F_SORT) { /* sorting by username */
-               if (!user->login)
-                       user->login = xstrdup(pwd->pw_name);
-       }
-       else /* sorting by UID */
-               user->uid = pwd->pw_uid;
 
        return user;
 }
-/* some UNIX implementations set errno iff a passwd/grp/...
- * entry was not found. The original UNIX logins(1) utility always
- * ignores invalid login/group names, so we're going to as well.*/
-#define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
-               (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
-
-/*
-static void *user_in_tree(void **rootp, struct lslogins_user *u)
-{
-       void *rc;
-       rc = tfind(u, rootp, ctl->cmp_fn);
-       if (!rc)
-               tdelete(u, rootp, ctl->cmp_fn);
-       return rc;
-}
-*/
-
-/* get a definitive list of users we want info about... */
 
 static int str_to_uint(char *s, unsigned int *ul)
 {
@@ -758,6 +772,8 @@ static int str_to_uint(char *s, unsigned int *ul)
                return 0;
        return 1;
 }
+
+/* get a definitive list of users we want info about... */
 static int get_ulist(struct lslogins_control *ctl, char *logins, char *groups)
 {
        char *u, *g;
@@ -775,41 +791,49 @@ static int get_ulist(struct lslogins_control *ctl, char *logins, char *groups)
        *arsiz = 32;
        *ar = xcalloc(1, sizeof(char *) * (*arsiz));
 
-       while ((u = strtok(logins, ","))) {
-               logins = NULL;
+       if (logins) {
+               while ((u = strtok(logins, ","))) {
+                       logins = NULL;
+
+                       /* user specified by UID? */
+                       if (!str_to_uint(u, &uid)) {
+                               pwd = getpwuid(uid);
+                               if (!pwd)
+                                       continue;
+                               u = pwd->pw_name;
+                       }
+                       (*ar)[i++] = xstrdup(u);
 
-               /* user specified by UID? */
-               if (!str_to_uint(u, &uid)) {
-                       pwd = getpwuid(uid);
-                       if (!pwd)
-                               continue;
-                       u = pwd->pw_name;
+                       if (i == *arsiz)
+                               *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
                }
-               (*ar)[i++] = xstrdup(u);
-
-               if (i == *arsiz)
-                       *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
+               ctl->ulist_on = 1;
        }
-       /* FIXME: this might lead to duplicit entries, although not visible
-        * in output, crunching a user's info multiple times is very redundant */
-       while ((g = strtok(groups, ","))) {
-               groups = NULL;
-
-               /* user specified by GID? */
-               if (!str_to_uint(g, &gid))
-                       grp = getgrgid(gid);
-               else
-                       grp = getgrnam(g);
-
-               if (!grp)
-                       continue;
 
-               while ((u = grp->gr_mem[n++])) {
-                       (*ar)[i++] = xstrdup(u);
+       if (groups) {
+               /* FIXME: this might lead to duplicate entries, although not visible
+                * in output, crunching a user's info multiple times is very redundant */
+               while ((g = strtok(groups, ","))) {
+                       n = 0;
+                       groups = NULL;
 
-                       if (i == *arsiz)
-                               *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
+                       /* user specified by GID? */
+                       if (!str_to_uint(g, &gid))
+                               grp = getgrgid(gid);
+                       else
+                               grp = getgrnam(g);
+
+                       if (!grp)
+                               continue;
+
+                       while ((u = grp->gr_mem[n++])) {
+                               (*ar)[i++] = xstrdup(u);
+
+                               if (i == *arsiz)
+                                       *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
+                       }
                }
+               ctl->ulist_on = 1;
        }
        *arsiz = i;
        return 0;
@@ -843,88 +867,98 @@ static struct lslogins_user *get_next_user(struct lslogins_control *ctl)
        return u;
 }
 
-static int get_user(struct lslogins_control *ctl, struct lslogins_user **user, const char *username)
+/* some UNIX implementations set errno iff a passwd/grp/...
+ * entry was not found. The original UNIX logins(1) utility always
+ * ignores invalid login/group names, so we're going to as well.*/
+#define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
+               (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
+
+static int get_user(struct lslogins_control *ctl, struct lslogins_user **user,
+                   const char *username)
 {
        *user = get_user_info(ctl, username);
-       if (!*user && errno)
-               if (IS_REAL_ERRNO(errno))
-                       return -1;
+       if (!*user && IS_REAL_ERRNO(errno))
+               return -1;
        return 0;
 }
+
+static int cmp_uid(const void *a, const void *b)
+{
+       uid_t x = ((struct lslogins_user *)a)->uid;
+       uid_t z = ((struct lslogins_user *)b)->uid;
+       return x > z ? 1 : (x < z ? -1 : 0);
+}
+
 static int create_usertree(struct lslogins_control *ctl)
 {
        struct lslogins_user *user = NULL;
        size_t n = 0;
 
-       if (*ctl->ulist) {
+       if (ctl->ulist_on) {
                while (n < ctl->ulsiz) {
                        if (get_user(ctl, &user, ctl->ulist[n]))
                                return -1;
                        if (user) /* otherwise an invalid user name has probably been given */
-                               tsearch(user, &ctl->usertree, ctl->cmp_fn);
+                               tsearch(user, &ctl->usertree, cmp_uid);
                        ++n;
                }
-       }
-       else {
+       } else {
                while ((user = get_next_user(ctl)))
-                       tsearch(user, &ctl->usertree, ctl->cmp_fn);
+                       tsearch(user, &ctl->usertree, cmp_uid);
        }
        return 0;
 }
 
-static int cmp_uname(const void *a, const void *b)
+static struct libscols_table *setup_table(struct lslogins_control *ctl)
 {
-       return strcmp(((struct lslogins_user *)a)->login,
-                     ((struct lslogins_user *)b)->login);
-}
-
-static int cmp_uid(const void *a, const void *b)
-{
-       uid_t x = ((struct lslogins_user *)a)->uid;
-       uid_t z = ((struct lslogins_user *)b)->uid;
-       return x > z ? 1 : (x < z ? -1 : 0);
-}
-
-static struct libscols_table *setup_table(void)
-{
-       struct libscols_table *tb = scols_new_table();
+       struct libscols_table *table = scols_new_table();
        int n = 0;
-       if (!tb)
-               return NULL;
+
+       if (!table)
+               err(EXIT_FAILURE, _("failed to allocate output table"));
+       if (ctl->noheadings)
+               scols_table_enable_noheadings(table, 1);
 
        switch(outmode) {
-               case out_colon:
-                       scols_table_enable_raw(tb, 1);
-                       scols_table_set_column_separator(tb, ":");
-                       break;
-               case out_newline:
-                       scols_table_set_column_separator(tb, "\n");
-                       /* fallthrough */
-               case out_export:
-                       scols_table_enable_export(tb, 1);
-                       break;
-               case out_nul:
-                       scols_table_set_line_separator(tb, "\0");
-                       /* fallthrough */
-               case out_raw:
-                       scols_table_enable_raw(tb, 1);
-                       break;
-               case out_pretty:
-                       scols_table_enable_noheadings(tb, 1);
-               default:
-                       break;
+       case OUT_COLON:
+               scols_table_enable_raw(table, 1);
+               scols_table_set_column_separator(table, ":");
+               break;
+       case OUT_NEWLINE:
+               scols_table_set_column_separator(table, "\n");
+               /* fallthrough */
+       case OUT_EXPORT:
+               scols_table_enable_export(table, 1);
+               break;
+       case OUT_NUL:
+               scols_table_set_line_separator(table, "\0");
+               /* fallthrough */
+       case OUT_RAW:
+               scols_table_enable_raw(table, 1);
+               break;
+       case OUT_PRETTY:
+               scols_table_enable_noheadings(table, 1);
+       default:
+               break;
        }
 
        while (n < ncolumns) {
-               if (!scols_table_new_column(tb, coldescs[columns[n]].name,
-                                           coldescs[columns[n]].whint, coldescs[columns[n]].flag))
+               int flags = coldescs[columns[n]].flag;
+
+               if (ctl->notrunc)
+                       flags &= ~SCOLS_FL_TRUNC;
+
+               if (!scols_table_new_column(table,
+                               coldescs[columns[n]].name,
+                               coldescs[columns[n]].whint,
+                               flags))
                        goto fail;
                ++n;
        }
 
-       return tb;
+       return table;
 fail:
-       scols_unref_table(tb);
+       scols_unref_table(table);
        return NULL;
 }
 
@@ -934,121 +968,167 @@ static void fill_table(const void *u, const VISIT which, const int depth __attri
        struct lslogins_user *user = *(struct lslogins_user **)u;
        int n = 0;
 
-       if ((which == preorder) || (which == endorder))
+       if (which == preorder || which == endorder)
                return;
 
        ln = scols_table_new_line(tb, NULL);
+       if (!ln)
+               err(EXIT_FAILURE, _("failed to allocate output line"));
+
        while (n < ncolumns) {
+               int rc = 0;
+
                switch (columns[n]) {
-                       case COL_LOGIN:
-                               if (scols_line_set_data(ln, n, user->login))
-                                       goto fail;
-                               break;
-                       case COL_UID:
-                               {
-                                       char *str_uid = uidtostr(user->uid);
-                                       if (!str_uid || scols_line_set_data(ln, n, str_uid))
-                                               goto fail;
-                                       free(str_uid);
-                                       break;
-                               }
-                       case COL_NOPASSWD:
-                               if (scols_line_set_data(ln, n, status[user->nopasswd]))
-                                       goto fail;
-                               break;
-                       case COL_NOLOGIN:
-                               if (scols_line_set_data(ln, n, status[user->nologin]))
-                                       goto fail;
-                               break;
-                       case COL_LOCKED:
-                               if (scols_line_set_data(ln, n, status[user->locked]))
-                                       goto fail;
-                               break;
-                       case COL_PGRP:
-                               if (scols_line_set_data(ln, n, user->group))
-                                       goto fail;
-                               break;
-                       case COL_PGID:
-                               {
-                                       char *str_gid = gidtostr(user->gid);
-                                       if (!str_gid || scols_line_set_data(ln, n, str_gid))
-                                               goto fail;
-                                       free(str_gid);
-                                       break;
-                               }
-                       case COL_SGRPS:
-                               if (scols_line_set_data(ln, n, user->sgroups))
-                                       goto fail;
-                               break;
-                       case COL_HOME:
-                               if (scols_line_set_data(ln, n, user->homedir))
-                                       goto fail;
-                               break;
-                       case COL_SHELL:
-                               if (scols_line_set_data(ln, n, user->shell))
-                                       goto fail;
-                               break;
-                       case COL_GECOS:
-                               if (scols_line_set_data(ln, n, user->gecos))
-                                       goto fail;
-                               break;
-                       case COL_LAST_LOGIN:
-                               if (scols_line_set_data(ln, n, user->last_login))
-                                       goto fail;
-                               break;
-                       case COL_LAST_TTY:
-                               if (scols_line_set_data(ln, n, user->last_tty))
-                                       goto fail;
-                               break;
-                       case COL_LAST_HOSTNAME:
-                               if (scols_line_set_data(ln, n, user->last_hostname))
-                                       goto fail;
-                               break;
-                       case COL_FAILED_LOGIN:
-                               if (scols_line_set_data(ln, n, user->failed_login))
-                                       goto fail;
-                               break;
-                       case COL_FAILED_TTY:
-                               if (scols_line_set_data(ln, n, user->failed_tty))
-                                       goto fail;
-                               break;
-                       case COL_HUSH_STATUS:
-                               if (scols_line_set_data(ln, n, status[user->hushed]))
-                                       goto fail;
-                               break;
-                       case COL_PWD_WARN:
-                               if (scols_line_set_data(ln, n, user->pwd_warn))
-                                       goto fail;
-                               break;
-                       case COL_PWD_CTIME:
-                               if (scols_line_set_data(ln, n, user->pwd_ctime))
-                                       goto fail;
-                               break;
-                       case COL_PWD_CTIME_MIN:
-                               if (scols_line_set_data(ln, n, user->pwd_ctime_min))
-                                       goto fail;
-                               break;
-                       case COL_PWD_CTIME_MAX:
-                               if (scols_line_set_data(ln, n, user->pwd_ctime_max))
-                                       goto fail;
-                               break;
+               case COL_USER:
+                       rc = scols_line_set_data(ln, n, user->login);
+                       break;
+               case COL_UID:
+                       rc = scols_line_refer_data(ln, n, uidtostr(user->uid));
+                       break;
+               case COL_PWDEMPTY:
+                       rc = scols_line_set_data(ln, n, get_status(user->pwd_empty));
+                       break;
+               case COL_NOLOGIN:
+                       rc = scols_line_set_data(ln, n, get_status(user->nologin));
+                       break;
+               case COL_PWDLOCK:
+                       rc = scols_line_set_data(ln, n, get_status(user->pwd_lock));
+                       break;
+               case COL_PWDDENY:
+                       rc = scols_line_set_data(ln, n, get_status(user->pwd_deny));
+                       break;
+               case COL_GROUP:
+                       rc = scols_line_set_data(ln, n, user->group);
+                       break;
+               case COL_GID:
+                       rc = scols_line_refer_data(ln, n, gidtostr(user->gid));
+                       break;
+               case COL_SGROUPS:
+                       rc = scols_line_refer_data(ln, n,
+                               build_sgroups_string(user->sgroups,
+                                                    user->nsgroups,
+                                                    TRUE));
+                       break;
+               case COL_SGIDS:
+                       rc = scols_line_refer_data(ln, n,
+                               build_sgroups_string(user->sgroups,
+                                                    user->nsgroups,
+                                                    FALSE));
+                       break;
+               case COL_HOME:
+                       rc = scols_line_set_data(ln, n, user->homedir);
+                       break;
+               case COL_SHELL:
+                       rc = scols_line_set_data(ln, n, user->shell);
+                       break;
+               case COL_GECOS:
+                       rc = scols_line_set_data(ln, n, user->gecos);
+                       break;
+               case COL_LAST_LOGIN:
+                       rc = scols_line_set_data(ln, n, user->last_login);
+                       break;
+               case COL_LAST_TTY:
+                       rc = scols_line_set_data(ln, n, user->last_tty);
+                       break;
+               case COL_LAST_HOSTNAME:
+                       rc = scols_line_set_data(ln, n, user->last_hostname);
+                       break;
+               case COL_FAILED_LOGIN:
+                       rc = scols_line_set_data(ln, n, user->failed_login);
+                       break;
+               case COL_FAILED_TTY:
+                       rc = scols_line_set_data(ln, n, user->failed_tty);
+                       break;
+               case COL_HUSH_STATUS:
+                       rc = scols_line_set_data(ln, n, get_status(user->hushed));
+                       break;
+               case COL_PWD_WARN:
+                       rc = scols_line_set_data(ln, n, user->pwd_warn);
+                       break;
+               case COL_PWD_EXPIR:
+                       rc = scols_line_set_data(ln, n, user->pwd_expire);
+                       break;
+               case COL_PWD_CTIME:
+                       rc = scols_line_set_data(ln, n, user->pwd_ctime);
+                       break;
+               case COL_PWD_CTIME_MIN:
+                       rc = scols_line_set_data(ln, n, user->pwd_ctime_min);
+                       break;
+               case COL_PWD_CTIME_MAX:
+                       rc = scols_line_set_data(ln, n, user->pwd_ctime_max);
+                       break;
+               case COL_SELINUX:
 #ifdef HAVE_LIBSELINUX
-                       case COL_SELINUX:
-                               if (scols_line_set_data(ln, n, user->context))
-                                       goto fail;
-                               break;
+                       rc = scols_line_set_data(ln, n, user->context);
 #endif
-                       default:
-                               /* something went very wrong here */
-                               err(EXIT_FAILURE, "fatal: unknown error");
+                       break;
+               case COL_NPROCS:
+                       rc = scols_line_set_data(ln, n, user->nprocs);
+                       break;
+               default:
+                       /* something went very wrong here */
+                       err(EXIT_FAILURE, _("internal error: unknown column"));
                }
+
+               if (rc)
+                       err(EXIT_FAILURE, _("failed to add output data"));
                ++n;
        }
        return;
-fail:
-       exit(1);
 }
-static int print_pretty(struct libscols_table *tb)
+#ifdef HAVE_LIBSYSTEMD
+static void print_journal_tail(const char *journal_path, uid_t uid, size_t len, int time_mode)
+{
+       sd_journal *j;
+       char *match, *timestamp;
+       uint64_t x;
+       time_t t;
+       const char *identifier, *pid, *message;
+       size_t identifier_len, pid_len, message_len;
+
+       if (journal_path)
+               sd_journal_open_directory(&j, journal_path, 0);
+       else
+               sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
+
+       xasprintf(&match, "_UID=%d", uid);
+
+       sd_journal_add_match(j, match, 0);
+       sd_journal_seek_tail(j);
+       sd_journal_previous_skip(j, len);
+
+       do {
+               if (0 > sd_journal_get_data(j, "SYSLOG_IDENTIFIER",
+                               (const void **) &identifier, &identifier_len))
+                       goto done;
+               if (0 > sd_journal_get_data(j, "_PID",
+                               (const void **) &pid, &pid_len))
+                       goto done;
+               if (0 > sd_journal_get_data(j, "MESSAGE",
+                               (const void **) &message, &message_len))
+                       goto done;
+
+               sd_journal_get_realtime_usec(j, &x);
+               t = x / 1000000;
+               timestamp = make_time(time_mode, t);
+               /* Get rid of journal entry field identifiers */
+               identifier = strchr(identifier, '=') + 1;
+               pid = strchr(pid, '=') + 1;
+               message = strchr(message, '=') + 1;
+
+               fprintf(stdout, "%s %s[%s]: %s\n", timestamp, identifier, pid,
+                       message);
+               free(timestamp);
+       } while (sd_journal_next(j));
+
+done:
+       free(match);
+       sd_journal_flush_matches(j);
+       sd_journal_close(j);
+}
+#endif
+
+static int print_pretty(struct libscols_table *table)
 {
        struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
        struct libscols_column *col;
@@ -1057,15 +1137,16 @@ static int print_pretty(struct libscols_table *tb)
        const char *hstr, *dstr;
        int n = 0;
 
-       ln = scols_table_get_line(tb, 0);
-       while (!scols_table_next_column(tb, itr, &col)) {
+       ln = scols_table_get_line(table, 0);
+       while (!scols_table_next_column(table, itr, &col)) {
 
                data = scols_line_get_cell(ln, n);
 
-               hstr = coldescs[columns[n]].pretty_name;
+               hstr = _(coldescs[columns[n]].pretty_name);
                dstr = scols_cell_get_data(data);
 
-               printf("%s:%*c%-36s\n", hstr, 26 - (int)strlen(hstr), ' ', dstr);
+               if (dstr)
+                       printf("%s:%*c%-36s\n", hstr, 35 - (int)strlen(hstr), ' ', dstr);
                ++n;
        }
 
@@ -1073,16 +1154,22 @@ static int print_pretty(struct libscols_table *tb)
        return 0;
 
 }
+
 static int print_user_table(struct lslogins_control *ctl)
 {
-       tb = setup_table();
+       tb = setup_table(ctl);
        if (!tb)
                return -1;
 
        twalk(ctl->usertree, fill_table);
-       if (outmode == out_pretty)
+       if (outmode == OUT_PRETTY) {
                print_pretty(tb);
-       else
+#ifdef HAVE_LIBSYSTEMD
+               fprintf(stdout, _("\nLast logs:\n"));
+               print_journal_tail(ctl->journal_path, ctl->uid, 3, ctl->time_mode);
+               fputc('\n', stdout);
+#endif
+       } else
                scols_print_table(tb);
        return 0;
 }
@@ -1112,45 +1199,111 @@ static void free_user(void *f)
        free(u);
 }
 
+static int parse_time_mode(const char *s)
+{
+       struct lslogins_timefmt {
+               const char *name;
+               const int val;
+       };
+       static const struct lslogins_timefmt timefmts[] = {
+               {"iso", TIME_ISO},
+               {"full", TIME_FULL},
+               {"short", TIME_SHORT},
+       };
+       size_t i;
+
+       for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
+               if (strcmp(timefmts[i].name, s) == 0)
+                       return timefmts[i].val;
+       }
+       errx(EXIT_FAILURE, _("unknown time format: %s"), s);
+}
+
+static void __attribute__((__noreturn__)) usage(FILE *out)
+{
+       size_t i;
+
+       fputs(USAGE_HEADER, out);
+       fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+
+       fputs(USAGE_SEPARATOR, out);
+       fputs(_("Display information about known users in the system.\n"), out);
+
+       fputs(USAGE_OPTIONS, out);
+       fputs(_(" -a, --acc-expiration     display info about passwords expiration\n"), out);
+       fputs(_(" -c, --colon-separate     display data in a format similar to /etc/passwd\n"), out);
+       fputs(_(" -e, --export             display in an export-able output format\n"), out);
+       fputs(_(" -f, --failed             display data about the users' last failed logins\n"), out);
+       fputs(_(" -G, --supp-groups        display information about groups\n"), out);
+       fputs(_(" -g, --groups=<groups>    display users belonging to a group in <groups>\n"), out);
+       fputs(_(" -L, --last               show info about the users' last login sessions\n"), out);
+       fputs(_(" -l, --logins=<logins>    display only users from <logins>\n"), out);
+       fputs(_(" -n, --newline            display each piece of information on a new line\n"), out);
+       fputs(_("     --noheadings         don't print headings\n"), out);
+       fputs(_("     --notruncate         don't truncate output\n"), out);
+       fputs(_(" -o, --output[=<list>]    define the columns to output\n"), out);
+       fputs(_(" -p, --pwd                display information related to login by password.\n"), out);
+       fputs(_(" -r, --raw                display in raw mode\n"), out);
+       fputs(_(" -s, --system-accs        display system accounts\n"), out);
+       fputs(_("     --time-format=<type> display dates in short, full or iso format\n"), out);
+       fputs(_(" -u, --user-accs          display user accounts\n"), out);
+       fputs(_(" -Z, --context            display SELinux contexts\n"), out);
+       fputs(_(" -z, --print0             delimit user entries with a nul character\n"), out);
+       fputs(_("     --wtmp-file <path>   set an alternate path for wtmp\n"), out);
+       fputs(_("     --btmp-file <path>   set an alternate path for btmp\n"), out);
+       fputs(USAGE_SEPARATOR, out);
+       fputs(USAGE_HELP, out);
+       fputs(USAGE_VERSION, out);
+
+       fprintf(out, _("\nAvailable columns:\n"));
+
+       for (i = 0; i < ARRAY_SIZE(coldescs); i++)
+               fprintf(out, " %14s  %s\n", coldescs[i].name,
+                               _(coldescs[i].help));
+
+       fprintf(out, USAGE_MAN_TAIL("lslogins(1)"));
+
+       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
 int main(int argc, char *argv[])
 {
-       int c, want_wtmp = 0, want_btmp = 0;
+       int c, opt_o = 0;
        char *logins = NULL, *groups = NULL;
        char *path_wtmp = _PATH_WTMP, *path_btmp = _PATH_BTMP;
        struct lslogins_control *ctl = xcalloc(1, sizeof(struct lslogins_control));
+       size_t i;
 
        /* long only options. */
        enum {
-               OPT_LAST = CHAR_MAX + 1,
-               OPT_VER,
-               OPT_WTMP,
+               OPT_WTMP = CHAR_MAX + 1,
                OPT_BTMP,
                OPT_NOTRUNC,
+               OPT_NOHEAD,
+               OPT_TIME_FMT,
        };
 
        static const struct option longopts[] = {
                { "acc-expiration", no_argument,        0, 'a' },
-               { "colon",          no_argument,        0, 'c' },
+               { "colon-separate", no_argument,        0, 'c' },
                { "export",         no_argument,        0, 'e' },
                { "failed",         no_argument,        0, 'f' },
                { "groups",         required_argument,  0, 'g' },
                { "help",           no_argument,        0, 'h' },
                { "logins",         required_argument,  0, 'l' },
-               { "supp-groups",    no_argument,        0, 'm' },
+               { "supp-groups",    no_argument,        0, 'G' },
                { "newline",        no_argument,        0, 'n' },
                { "notruncate",     no_argument,        0, OPT_NOTRUNC },
+               { "noheadings",     no_argument,        0, OPT_NOHEAD },
                { "output",         required_argument,  0, 'o' },
-               { "last",           no_argument,        0, OPT_LAST },
+               { "last",           no_argument,        0, 'L', },
                { "raw",            no_argument,        0, 'r' },
                { "system-accs",    no_argument,        0, 's' },
-               { "sort-by-name",   no_argument,        0, 't' },
+               { "time-format",    required_argument,  0, OPT_TIME_FMT },
                { "user-accs",      no_argument,        0, 'u' },
-               { "version",        no_argument,        0, OPT_VER },
-               { "extra",          no_argument,        0, 'x' },
+               { "version",        no_argument,        0, 'V' },
+               { "pwd",            no_argument,        0, 'p' },
                { "print0",         no_argument,        0, 'z' },
-               /* TODO: find a reasonable way to do this for passwd/group/shadow,
-                * as libc itself doesn't supply any way to get a specific
-                * entry from a user-specified file */
                { "wtmp-file",      required_argument,  0, OPT_WTMP },
                { "btmp-file",      required_argument,  0, OPT_BTMP },
 #ifdef HAVE_LIBSELINUX
@@ -1160,7 +1313,12 @@ int main(int argc, char *argv[])
        };
 
        static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
-               { 'c','e','n','r','z' },
+               { 'G', 'o' },
+               { 'L', 'o' },
+               { 'Z', 'o' },
+               { 'a', 'o' },
+               { 'c','n','r','z' },
+               { 'o', 'p' },
                { 0 }
        };
        int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
@@ -1170,180 +1328,163 @@ int main(int argc, char *argv[])
        textdomain(PACKAGE);
        atexit(close_stdout);
 
-       ctl->cmp_fn = cmp_uid;
+       ctl->time_mode = TIME_SHORT;
+
+       /* very basic default */
+       add_column(columns, ncolumns++, COL_UID);
+       add_column(columns, ncolumns++, COL_USER);
 
-       while ((c = getopt_long(argc, argv, "acefg:hl:mno:rstuxzZ",
+       while ((c = getopt_long(argc, argv, "acefGg:hLl:no:prsuVzZ",
                                longopts, NULL)) != -1) {
 
                err_exclusive_options(c, longopts, excl, excl_st);
 
                switch (c) {
-                       case 'a':
-                               lslogins_flag |= F_EXPIR;
-                               break;
-                       case 'c':
-                               outmode = out_colon;
-                               break;
-                       case 'e':
-                               outmode = out_export;
-                               break;
-                       case 'f':
-                               lslogins_flag |= F_FAIL;
-                               break;
-                       case 'g':
-                               groups = optarg;
-                               break;
-                       case 'h':
-                               usage(stdout);
-                       case 'l':
-                               logins = optarg;
-                               break;
-                       case 'm':
-                               lslogins_flag |= F_MORE;
-                               break;
-                       case 'n':
-                               outmode = out_newline;
-                               break;
-                       case 'o':
-                               if (optarg) {
-                                       if (*optarg == '=')
-                                               optarg++;
-                                       ncolumns = string_to_idarray(optarg,
-                                                       columns, ARRAY_SIZE(columns),
-                                                       column_name_to_id);
-                                       if (ncolumns < 0)
-                                               return EXIT_FAILURE;
-                               }
-                               break;
-                       case 'r':
-                               outmode = out_raw;
-                               break;
-                       case OPT_LAST:
-                               lslogins_flag |= F_LAST;
-                               break;
-                       case 's':
-                               ctl->SYS_UID_MIN = getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN);
-                               ctl->SYS_UID_MAX = getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX);
-                               lslogins_flag |= F_SYSAC;
-                               break;
-                       case 't':
-                               ctl->cmp_fn = cmp_uname;
-                               lslogins_flag |= F_SORT;
-                               break;
-                       case 'u':
-                               ctl->UID_MIN = getlogindefs_num("UID_MIN", UL_UID_MIN);
-                               ctl->UID_MAX = getlogindefs_num("UID_MAX", UL_UID_MAX);
-                               lslogins_flag |= F_USRAC;
-                               break;
-                       case OPT_VER:
-                               printf(_("%s from %s\n"), program_invocation_short_name,
-                                      PACKAGE_STRING);
-                               return EXIT_SUCCESS;
-                       case 'x':
-                               lslogins_flag |= F_EXTRA;
-                               break;
-                       case 'z':
-                               outmode = out_nul;
-                               break;
-                       case OPT_WTMP:
-                               path_wtmp = optarg;
-                               break;
-                       case OPT_BTMP:
-                               path_btmp = optarg;
-                               break;
-                       case OPT_NOTRUNC:
-                               coldescs[COL_GECOS].flag = 0;
-                               break;
-                       case 'Z':
+               case 'a':
+                       add_column(columns, ncolumns++, COL_PWD_WARN);
+                       add_column(columns, ncolumns++, COL_PWD_CTIME_MIN);
+                       add_column(columns, ncolumns++, COL_PWD_CTIME_MAX);
+                       add_column(columns, ncolumns++, COL_PWD_CTIME);
+                       add_column(columns, ncolumns++, COL_PWD_EXPIR);
+                       break;
+               case 'c':
+                       outmode = OUT_COLON;
+                       break;
+               case 'e':
+                       outmode = OUT_EXPORT;
+                       break;
+               case 'f':
+                       add_column(columns, ncolumns++, COL_FAILED_LOGIN);
+                       add_column(columns, ncolumns++, COL_FAILED_TTY);
+                       break;
+               case 'G':
+                       add_column(columns, ncolumns++, COL_GID);
+                       add_column(columns, ncolumns++, COL_GROUP);
+                       add_column(columns, ncolumns++, COL_SGIDS);
+                       add_column(columns, ncolumns++, COL_SGROUPS);
+                       break;
+               case 'g':
+                       groups = optarg;
+                       break;
+               case 'h':
+                       usage(stdout);
+                       break;
+               case 'L':
+                       add_column(columns, ncolumns++, COL_LAST_TTY);
+                       add_column(columns, ncolumns++, COL_LAST_HOSTNAME);
+                       add_column(columns, ncolumns++, COL_LAST_LOGIN);
+                       break;
+               case 'l':
+                       logins = optarg;
+                       break;
+               case 'n':
+                       outmode = OUT_NEWLINE;
+                       break;
+               case 'o':
+                       if (optarg) {
+                               if (*optarg == '=')
+                                       optarg++;
+                               ncolumns = string_to_idarray(optarg,
+                                               columns, ARRAY_SIZE(columns),
+                                               column_name_to_id);
+                               if (ncolumns < 0)
+                                       return EXIT_FAILURE;
+                       }
+                       opt_o = 1;
+                       break;
+               case 'r':
+                       outmode = OUT_RAW;
+                       break;
+               case 's':
+                       ctl->SYS_UID_MIN = getlogindefs_num("SYS_UID_MIN", UL_SYS_UID_MIN);
+                       ctl->SYS_UID_MAX = getlogindefs_num("SYS_UID_MAX", UL_SYS_UID_MAX);
+                       lslogins_flag |= F_SYSAC;
+                       break;
+               case 'u':
+                       ctl->UID_MIN = getlogindefs_num("UID_MIN", UL_UID_MIN);
+                       ctl->UID_MAX = getlogindefs_num("UID_MAX", UL_UID_MAX);
+                       lslogins_flag |= F_USRAC;
+                       break;
+               case 'p':
+                       add_column(columns, ncolumns++, COL_PWDEMPTY);
+                       add_column(columns, ncolumns++, COL_PWDLOCK);
+                       add_column(columns, ncolumns++, COL_PWDDENY);
+                       add_column(columns, ncolumns++, COL_NOLOGIN);
+                       add_column(columns, ncolumns++, COL_HUSH_STATUS);
+                       break;
+               case 'z':
+                       outmode = OUT_NUL;
+                       break;
+               case OPT_WTMP:
+                       path_wtmp = optarg;
+                       break;
+               case OPT_BTMP:
+                       path_btmp = optarg;
+                       break;
+               case OPT_NOTRUNC:
+                       ctl->notrunc = 1;
+                       break;
+               case OPT_NOHEAD:
+                       ctl->noheadings = 1;
+                       break;
+               case OPT_TIME_FMT:
+                       ctl->time_mode = parse_time_mode(optarg);
+                       break;
+               case 'V':
+                       printf(UTIL_LINUX_VERSION);
+                       return EXIT_SUCCESS;
+               case 'Z':
+               {
 #ifdef HAVE_LIBSELINUX
-                               lslogins_flag |= F_SELINUX;
-                               ctl->sel_enabled = is_selinux_enabled();
-                               if (ctl->sel_enabled == -1)
-                                       exit(1);
+                       int sl = is_selinux_enabled();
+                       if (sl < 0)
+                               warn(_("failed to request selinux state"));
+                       else
+                               ctl->selinux_enabled = sl == 1;
 #endif
-                               break;
-                       default:
-                               usage(stderr);
+                       add_column(columns, ncolumns++, COL_SELINUX);
+                       break;
+               }
+               default:
+                       errtryhelp(EXIT_FAILURE);
                }
        }
+
        if (argc - optind == 1) {
                if (strchr(argv[optind], ','))
-                       err(EXIT_FAILURE, "%s", "Only one user may be specified. Use -l for multiple users");
+                       errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
                logins = argv[optind];
-               outmode = out_pretty;
-       }
-       else if (argc != optind)
-               usage(stderr);
+               outmode = OUT_PRETTY;
+       } else if (argc != optind)
+               errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
+
+       scols_init_debug(0);
 
        /* lslogins -u -s == lslogins */
        if (lslogins_flag & F_USRAC && lslogins_flag & F_SYSAC)
                lslogins_flag &= ~(F_USRAC | F_SYSAC);
 
-       if (!ncolumns) {
-               if (lslogins_flag & F_SORT) {
-                       columns[ncolumns++] = COL_LOGIN;
-                       columns[ncolumns++] = COL_UID;
-               }
-               else {
-                       columns[ncolumns++] = COL_UID;
-                       columns[ncolumns++] = COL_LOGIN;
-               }
-               columns[ncolumns++] = COL_PGRP;
-               columns[ncolumns++] = COL_PGID;
-               columns[ncolumns++] = COL_LAST_LOGIN;
-
-               want_wtmp = 1;
-
-               if (lslogins_flag & F_NOPWD) {
-                       columns[ncolumns++] = COL_NOPASSWD;
-               }
-               if (lslogins_flag & F_MORE) {
-                       columns[ncolumns++] = COL_SGRPS;
-               }
-               if (lslogins_flag & F_EXPIR) {
-                       columns[ncolumns++] = COL_PWD_CTIME;
-                       columns[ncolumns++] = COL_PWD_WARN;
-               }
-               if (lslogins_flag & F_LAST) {
-                       columns[ncolumns++] = COL_LAST_TTY;
-                       columns[ncolumns++] = COL_LAST_HOSTNAME;
-               }
-               if (lslogins_flag & F_FAIL) {
-                       columns[ncolumns++] = COL_FAILED_LOGIN;
-                       columns[ncolumns++] = COL_FAILED_TTY;
-                       want_btmp = 1;
-               }
-               if (lslogins_flag & F_EXTRA) {
-                       columns[ncolumns++] = COL_HOME;
-                       columns[ncolumns++] = COL_SHELL;
-                       columns[ncolumns++] = COL_NOPASSWD;
-                       columns[ncolumns++] = COL_NOLOGIN;
-                       columns[ncolumns++] = COL_LOCKED;
-                       columns[ncolumns++] = COL_HUSH_STATUS;
-                       columns[ncolumns++] = COL_PWD_CTIME_MIN;
-                       columns[ncolumns++] = COL_PWD_CTIME_MAX;
-                       columns[ncolumns++] = COL_GECOS;
-               }
-               if (lslogins_flag & F_SELINUX)
-                       columns[ncolumns++] = COL_SELINUX;
-       }
-       else {
-               int n = 0, i;
-               while (n < ncolumns) {
-                       i = columns[n++];
-                       if (i <= COL_LAST_HOSTNAME && i >= COL_LAST_LOGIN)
-                               want_wtmp = 1;
-                       if (i == COL_FAILED_TTY && i >= COL_FAILED_LOGIN)
-                               want_btmp = 1;
-               }
+       if (outmode == OUT_PRETTY && !opt_o) {
+               /* all columns for lslogins <username> */
+               for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++)
+                        columns[ncolumns++] = i;
+
+       } else if (ncolumns == 2 && !opt_o) {
+               /* default colummns */
+               add_column(columns, ncolumns++, COL_NPROCS);
+               add_column(columns, ncolumns++, COL_PWDLOCK);
+               add_column(columns, ncolumns++, COL_PWDDENY);
+               add_column(columns, ncolumns++, COL_LAST_LOGIN);
+               add_column(columns, ncolumns++, COL_GECOS);
        }
 
-       if (want_wtmp)
+       if (require_wtmp())
                parse_wtmp(ctl, path_wtmp);
-       if (want_btmp)
+       if (require_btmp())
                parse_btmp(ctl, path_btmp);
 
-       get_ulist(ctl, logins, groups);
+       if (logins || groups)
+               get_ulist(ctl, logins, groups);
 
        if (create_usertree(ctl))
                return EXIT_FAILURE;
@@ -1354,6 +1495,5 @@ int main(int argc, char *argv[])
        tdestroy(ctl->usertree, free_user);
        free_ctl(ctl);
 
-
        return EXIT_SUCCESS;
 }