]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - login-utils/lslogins.c
misc: cosmetics, remove argument from usage(FILE*)
[thirdparty/util-linux.git] / login-utils / lslogins.c
index fd8014d8d6f9c68027cea15834f2a0a4289ddbf7..c15b347f2df79dcca69036e79193b6ba8d776a6f 100644 (file)
 #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>
@@ -56,8 +55,8 @@
 #include "optutils.h"
 #include "pathnames.h"
 #include "logindefs.h"
-#include "readutmp.h"
 #include "procutils.h"
+#include "timeutils.h"
 
 /*
  * column description
@@ -143,6 +142,7 @@ enum {
        TIME_SHORT,
        TIME_FULL,
        TIME_ISO,
+       TIME_ISO_SHORT,
 };
 
 /*
@@ -212,17 +212,17 @@ static const char *const pretty_status[] = {
 
 #define get_status(x)  (outmode == OUT_PRETTY ? pretty_status[(x)] : status[(x)])
 
-static struct lslogins_coldesc coldescs[] =
+static const struct lslogins_coldesc coldescs[] =
 {
        [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 requiured"), N_("Password no required"), 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_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 },
@@ -236,17 +236,17 @@ static struct lslogins_coldesc coldescs[] =
        [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_("Minimal change time"), 0.1, SCOLS_FL_RIGHT },
-       [COL_PWD_CTIME_MAX] = { "PWD-MAX",      N_("max number of days a password may remain unchanged"), N_("Maximal change time"), 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 process"), 1, SCOLS_FL_RIGHT },
+       [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;
@@ -261,33 +261,38 @@ struct lslogins_control {
        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() */
 static struct libscols_table *tb;
-static int columns[ARRAY_SIZE(coldescs)];
-static int ncolumns;
 
-static struct timeval now;
+/* 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 date_is_today(time_t t)
+static inline size_t err_columns_index(size_t arysz, size_t idx)
 {
-       if (now.tv_sec == 0)
-               gettimeofday(&now, NULL);
-       return t / (3600 * 24) == now.tv_sec / (3600 * 24);
+       if (idx >= arysz)
+               errx(EXIT_FAILURE, _("too many columns specified, "
+                                    "the limit is %zu columns"),
+                               arysz - 1);
+       return idx;
 }
 
-static int date_is_thisyear(time_t t)
-{
-       if (now.tv_sec == 0)
-               gettimeofday(&now, NULL);
-       return t / (3600 * 24 * 365) == now.tv_sec / (3600 * 24 * 365);
-}
+#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)
 {
@@ -303,34 +308,44 @@ static int column_name_to_id(const char *name, size_t namesz)
        return -1;
 }
 
+static struct timeval now;
+
 static char *make_time(int mode, time_t time)
 {
-       char *s;
-       struct tm tm;
+       int rc = 0;
        char buf[64] = {0};
 
-       localtime_r(&time, &tm);
-
        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:
-               if (date_is_today(time))
-                       strftime(buf, sizeof(buf), "%H:%M:%S", &tm);
-               else if (date_is_thisyear(time))
-                       strftime(buf, sizeof(buf), "%b%d/%H:%M", &tm);
-               else
-                       strftime(buf, sizeof(buf), "%Y-%b%d", &tm);
+               rc = strtime_short(&time, &now, UL_SHORTTIME_THISYEAR_HHMM,
+                               buf, sizeof(buf));
                break;
        case TIME_ISO:
-               strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", &tm);
+               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, _("unssupported time type"));
+               errx(EXIT_FAILURE, _("unsupported time type"));
        }
+
+       if (rc)
+                errx(EXIT_FAILURE, _("failed to compose time string"));
+
        return xstrdup(buf);
 }
 
@@ -374,7 +389,7 @@ again:
                        x = snprintf(p, len, "%s,", grp->gr_name);
                }
 
-               if (x < 0 || (size_t) x + 1 > len) {
+               if (x < 0 || (size_t) x >= len) {
                        size_t cur = p - res;
 
                        maxlen *= 2;
@@ -395,19 +410,17 @@ again:
        return res;
 }
 
-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;
@@ -432,25 +445,54 @@ static int require_btmp(void)
        return 0;
 }
 
-static struct utmp *get_last_btmp(struct lslogins_control *ctl, const char *username)
+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;
@@ -474,21 +516,24 @@ static int parse_btmp(struct lslogins_control *ctl, char *path)
 static int get_sgroups(gid_t **list, size_t *len, struct passwd *pwd)
 {
        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, (int *) 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, (int *) 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)
@@ -498,6 +543,7 @@ static int get_sgroups(gid_t **list, size_t *len, struct passwd *pwd)
 
        if (*len)
                (*list)[n] = (*list)[--(*len)];
+
        return 0;
 }
 
@@ -521,7 +567,7 @@ static int valid_pwd(const char *str)
        const char *p;
 
        for (p = str; p && *p; p++)
-               if (!isalnum((unsigned int) *p))
+               if (!isalnum((unsigned char) *p))
                        return 0;
        return p > str ? 1 : 0;
 }
@@ -532,7 +578,7 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
        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;
        uid_t uid;
@@ -663,8 +709,8 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
                        if (strstr(pwd->pw_shell, "nologin"))
                                user->nologin = 1;
                        else if (pwd->pw_uid)
-                               user->nologin = access("/etc/nologin", F_OK) == 0 ||
-                                               access("/var/run/nologin", F_OK) == 0;
+                               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)
@@ -672,7 +718,8 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
                        break;
                case COL_PWD_EXPIR:
                        if (shadow && shadow->sp_expire >= 0)
-                               user->pwd_expire = make_time(TIME_SHORT,
+                               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:
@@ -680,7 +727,8 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
                         * (especially in non-GMT timezones) would only serve
                         * to confuse */
                        if (shadow)
-                               user->pwd_ctime = make_time(TIME_SHORT,
+                               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:
@@ -692,15 +740,15 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
                                xasprintf(&user->pwd_ctime_max, "%ld", shadow->sp_max);
                        break;
                case COL_SELINUX:
-               {
 #ifdef HAVE_LIBSELINUX
+                       if (ctl->selinux_enabled) {
                                /* typedefs and pointers are pure evil */
-                       security_context_t con = NULL;
-                       if (getcon(&con) == 0)
-                               user->context = con;
+                               security_context_t con = NULL;
+                               if (getcon(&con) == 0)
+                                       user->context = con;
+                       }
 #endif
                        break;
-               }
                case COL_NPROCS:
                        xasprintf(&user->nprocs, "%d", get_nprocs(pwd->pw_uid));
                        break;
@@ -714,14 +762,6 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
        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)
-
-/* get a definitive list of users we want info about... */
-
 static int str_to_uint(char *s, unsigned int *ul)
 {
        char *end;
@@ -733,6 +773,7 @@ static int str_to_uint(char *s, unsigned int *ul)
        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;
@@ -750,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;
-
-               /* user specified by UID? */
-               if (!str_to_uint(u, &uid)) {
-                       pwd = getpwuid(uid);
-                       if (!pwd)
-                               continue;
-                       u = pwd->pw_name;
-               }
-               (*ar)[i++] = xstrdup(u);
-
-               if (i == *arsiz)
-                       *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
-       }
-       /* 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;
+       if (logins) {
+               while ((u = strtok(logins, ","))) {
+                       logins = NULL;
 
-               while ((u = grp->gr_mem[n++])) {
+                       /* user specified by UID? */
+                       if (!str_to_uint(u, &uid)) {
+                               pwd = getpwuid(uid);
+                               if (!pwd)
+                                       continue;
+                               u = pwd->pw_name;
+                       }
                        (*ar)[i++] = xstrdup(u);
 
                        if (i == *arsiz)
                                *ar = xrealloc(*ar, sizeof(char *) * (*arsiz += 32));
                }
+               ctl->ulist_on = 1;
+       }
+
+       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;
+
+                       /* 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;
@@ -818,13 +867,18 @@ static struct lslogins_user *get_next_user(struct lslogins_control *ctl)
        return u;
 }
 
+/* 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;
 }
 
@@ -840,7 +894,7 @@ 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;
@@ -855,46 +909,56 @@ static int create_usertree(struct lslogins_control *ctl)
        return 0;
 }
 
-static struct libscols_table *setup_table(void)
+static struct libscols_table *setup_table(struct lslogins_control *ctl)
 {
-       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, ":");
+               scols_table_enable_raw(table, 1);
+               scols_table_set_column_separator(table, ":");
                break;
        case OUT_NEWLINE:
-               scols_table_set_column_separator(tb, "\n");
+               scols_table_set_column_separator(table, "\n");
                /* fallthrough */
        case OUT_EXPORT:
-               scols_table_enable_export(tb, 1);
+               scols_table_enable_export(table, 1);
                break;
        case OUT_NUL:
-               scols_table_set_line_separator(tb, "\0");
+               scols_table_set_line_separator(table, "\0");
                /* fallthrough */
        case OUT_RAW:
-               scols_table_enable_raw(tb, 1);
+               scols_table_enable_raw(table, 1);
                break;
        case OUT_PRETTY:
-               scols_table_enable_noheadings(tb, 1);
+               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;
 }
 
@@ -908,6 +972,9 @@ static void fill_table(const void *u, const VISIT which, const int depth __attri
                return;
 
        ln = scols_table_new_line(tb, NULL);
+       if (!ln)
+               err(EXIT_FAILURE, _("failed to allocate output line"));
+
        while (n < ncolumns) {
                int rc = 0;
 
@@ -1003,17 +1070,17 @@ static void fill_table(const void *u, const VISIT which, const int depth __attri
                        err(EXIT_FAILURE, _("internal error: unknown column"));
                }
 
-               if (rc != 0)
-                       err(EXIT_FAILURE, _("failed to set data"));
+               if (rc)
+                       err(EXIT_FAILURE, _("failed to add output data"));
                ++n;
        }
        return;
 }
 #ifdef HAVE_LIBSYSTEMD
-static void print_journal_tail(const char *journal_path, uid_t uid, size_t len)
+static void print_journal_tail(const char *journal_path, uid_t uid, size_t len, int time_mode)
 {
        sd_journal *j;
-       char *match, *buf;
+       char *match, *timestamp;
        uint64_t x;
        time_t t;
        const char *identifier, *pid, *message;
@@ -1024,7 +1091,6 @@ static void print_journal_tail(const char *journal_path, uid_t uid, size_t len)
        else
                sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
 
-       buf = xmalloc(sizeof(char) * 16);
        xasprintf(&match, "_UID=%d", uid);
 
        sd_journal_add_match(j, match, 0);
@@ -1034,37 +1100,35 @@ static void print_journal_tail(const char *journal_path, uid_t uid, size_t len)
        do {
                if (0 > sd_journal_get_data(j, "SYSLOG_IDENTIFIER",
                                (const void **) &identifier, &identifier_len))
-                       return;
+                       goto done;
                if (0 > sd_journal_get_data(j, "_PID",
                                (const void **) &pid, &pid_len))
-                       return;
+                       goto done;
                if (0 > sd_journal_get_data(j, "MESSAGE",
                                (const void **) &message, &message_len))
-                       return;
+                       goto done;
 
                sd_journal_get_realtime_usec(j, &x);
                t = x / 1000000;
-               strftime(buf, 16, "%b %d %H:%M:%S", localtime(&t));
-
-               fprintf(stdout, "%s", buf);
-
+               timestamp = make_time(time_mode, t);
+               /* Get rid of journal entry field identifiers */
                identifier = strchr(identifier, '=') + 1;
-               pid = strchr(pid, '=') + 1              ;
+               pid = strchr(pid, '=') + 1;
                message = strchr(message, '=') + 1;
 
-               fprintf(stdout, " %s", identifier);
-               fprintf(stdout, "[%s]:", pid);
-               fprintf(stdout, "%s\n", message);
+               fprintf(stdout, "%s %s[%s]: %s\n", timestamp, identifier, pid,
+                       message);
+               free(timestamp);
        } while (sd_journal_next(j));
 
-       free(buf);
+done:
        free(match);
        sd_journal_flush_matches(j);
        sd_journal_close(j);
 }
 #endif
 
-static int print_pretty(struct libscols_table *tb)
+static int print_pretty(struct libscols_table *table)
 {
        struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
        struct libscols_column *col;
@@ -1073,8 +1137,8 @@ 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);
 
@@ -1093,7 +1157,7 @@ static int print_pretty(struct libscols_table *tb)
 
 static int print_user_table(struct lslogins_control *ctl)
 {
-       tb = setup_table();
+       tb = setup_table(ctl);
        if (!tb)
                return -1;
 
@@ -1102,7 +1166,7 @@ static int print_user_table(struct lslogins_control *ctl)
                print_pretty(tb);
 #ifdef HAVE_LIBSYSTEMD
                fprintf(stdout, _("\nLast logs:\n"));
-               print_journal_tail(ctl->journal_path, ctl->uid, 3);
+               print_journal_tail(ctl->journal_path, ctl->uid, 3, ctl->time_mode);
                fputc('\n', stdout);
 #endif
        } else
@@ -1135,35 +1199,48 @@ static void free_user(void *f)
        free(u);
 }
 
-struct lslogins_timefmt {
-       const char *name;
-       int val;
-};
+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;
 
-static struct lslogins_timefmt timefmts[] = {
-       { "short", TIME_SHORT },
-       { "full", TIME_FULL },
-       { "iso", TIME_ISO },
-};
+       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)
+static void __attribute__((__noreturn__)) usage(void)
 {
+       FILE *out = stdout;
        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 last users' failed logins\n"), out);
-       fputs(_(" -G, --groups-info        display information about groups\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(_(" -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(_("     --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);
@@ -1179,14 +1256,13 @@ static void __attribute__((__noreturn__)) usage(FILE *out)
        fputs(USAGE_HELP, out);
        fputs(USAGE_VERSION, out);
 
-       fprintf(out, _("\nAvailable columns:\n"));
-
+       fputs(USAGE_COLUMNS, out);
        for (i = 0; i < ARRAY_SIZE(coldescs); i++)
                fprintf(out, " %14s  %s\n", coldescs[i].name, _(coldescs[i].help));
 
-       fprintf(out, _("\nFor more details see lslogins(1).\n"));
+       fprintf(out, USAGE_MAN_TAIL("lslogins(1)"));
 
-       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+       exit(EXIT_SUCCESS);
 }
 
 int main(int argc, char *argv[])
@@ -1199,10 +1275,10 @@ int main(int argc, char *argv[])
 
        /* long only options. */
        enum {
-               OPT_VER = CHAR_MAX + 1,
-               OPT_WTMP,
+               OPT_WTMP = CHAR_MAX + 1,
                OPT_BTMP,
                OPT_NOTRUNC,
+               OPT_NOHEAD,
                OPT_TIME_FMT,
        };
 
@@ -1217,6 +1293,7 @@ int main(int argc, char *argv[])
                { "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, 'L', },
                { "raw",            no_argument,        0, 'r' },
@@ -1253,21 +1330,21 @@ int main(int argc, char *argv[])
        ctl->time_mode = TIME_SHORT;
 
        /* very basic default */
-       columns[ncolumns++] = COL_UID;
-       columns[ncolumns++] = COL_USER;
+       add_column(columns, ncolumns++, COL_UID);
+       add_column(columns, ncolumns++, COL_USER);
 
-       while ((c = getopt_long(argc, argv, "acfGg:hLl:no:prsuVxzZ",
+       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':
-                       columns[ncolumns++] = COL_PWD_WARN;
-                       columns[ncolumns++] = COL_PWD_CTIME_MIN;
-                       columns[ncolumns++] = COL_PWD_CTIME_MAX;
-                       columns[ncolumns++] = COL_PWD_CTIME;
-                       columns[ncolumns++] = COL_PWD_EXPIR;
+                       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;
@@ -1276,25 +1353,25 @@ int main(int argc, char *argv[])
                        outmode = OUT_EXPORT;
                        break;
                case 'f':
-                       columns[ncolumns++] = COL_FAILED_LOGIN;
-                       columns[ncolumns++] = COL_FAILED_TTY;
+                       add_column(columns, ncolumns++, COL_FAILED_LOGIN);
+                       add_column(columns, ncolumns++, COL_FAILED_TTY);
                        break;
                case 'G':
-                       columns[ncolumns++] = COL_GID;
-                       columns[ncolumns++] = COL_GROUP;
-                       columns[ncolumns++] = COL_SGIDS;
-                       columns[ncolumns++] = COL_SGROUPS;
+                       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);
+                       usage();
                        break;
                case 'L':
-                       columns[ncolumns++] = COL_LAST_TTY;
-                       columns[ncolumns++] = COL_LAST_HOSTNAME;
-                       columns[ncolumns++] = COL_LAST_LOGIN;
+                       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;
@@ -1303,15 +1380,12 @@ int main(int argc, char *argv[])
                        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;
-                       }
+                       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':
@@ -1328,11 +1402,11 @@ int main(int argc, char *argv[])
                        lslogins_flag |= F_USRAC;
                        break;
                case 'p':
-                       columns[ncolumns++] = COL_PWDEMPTY;
-                       columns[ncolumns++] = COL_PWDLOCK;
-                       columns[ncolumns++] = COL_PWDDENY;
-                       columns[ncolumns++] = COL_NOLOGIN;
-                       columns[ncolumns++] = COL_HUSH_STATUS;
+                       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;
@@ -1344,35 +1418,31 @@ int main(int argc, char *argv[])
                        path_btmp = optarg;
                        break;
                case OPT_NOTRUNC:
-                       coldescs[COL_GECOS].flag = 0;
+                       ctl->notrunc = 1;
+                       break;
+               case OPT_NOHEAD:
+                       ctl->noheadings = 1;
                        break;
                case OPT_TIME_FMT:
-                       {
-                               size_t i;
-
-                               for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
-                                       if (strcmp(timefmts[i].name, optarg) == 0) {
-                                               ctl->time_mode = timefmts[i].val;
-                                               break;
-                                       }
-                               }
-                               if (ctl->time_mode == TIME_INVALID)
-                                       usage(stderr);
-                       }
+                       ctl->time_mode = parse_time_mode(optarg);
                        break;
                case 'V':
                        printf(UTIL_LINUX_VERSION);
                        return EXIT_SUCCESS;
                case 'Z':
-                       columns[ncolumns++] = COL_SELINUX;
+               {
 #ifdef HAVE_LIBSELINUX
-                       ctl->sel_enabled = is_selinux_enabled();
-                       if (ctl->sel_enabled == -1)
-                               err(EXIT_FAILURE, _("failed to request selinux state"));
+                       int sl = is_selinux_enabled();
+                       if (sl < 0)
+                               warn(_("failed to request selinux state"));
+                       else
+                               ctl->selinux_enabled = sl == 1;
 #endif
+                       add_column(columns, ncolumns++, COL_SELINUX);
                        break;
+               }
                default:
-                       usage(stderr);
+                       errtryhelp(EXIT_FAILURE);
                }
        }
 
@@ -1382,24 +1452,26 @@ int main(int argc, char *argv[])
                logins = argv[optind];
                outmode = OUT_PRETTY;
        } else if (argc != optind)
-               usage(stderr);
+               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 && outmode == OUT_PRETTY) {
+       if (outmode == OUT_PRETTY && !opt_o) {
                /* all columns for lslogins <username> */
-               for (i = 0; i < ARRAY_SIZE(coldescs); i++)
+               for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++)
                         columns[ncolumns++] = i;
 
        } else if (ncolumns == 2 && !opt_o) {
                /* default colummns */
-               columns[ncolumns++] = COL_NPROCS;
-               columns[ncolumns++] = COL_PWDLOCK;
-               columns[ncolumns++] = COL_PWDDENY;
-               columns[ncolumns++] = COL_LAST_LOGIN;
-               columns[ncolumns++] = COL_GECOS;
+               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 (require_wtmp())
@@ -1407,7 +1479,8 @@ int main(int argc, char *argv[])
        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;