]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsipc: new command to list IPC facilities
authorKarel Zak <kzak@redhat.com>
Tue, 30 Jun 2015 11:02:36 +0000 (13:02 +0200)
committerKarel Zak <kzak@redhat.com>
Mon, 20 Jul 2015 09:48:09 +0000 (11:48 +0200)
Co-Author: Karel Zak <kzak@redhat.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
Signed-off-by: Ondrej Oprala <ooprala@redhat.com>
.gitignore
bash-completion/Makemodule.am
bash-completion/lsipc [new file with mode: 0644]
configure.ac
sys-utils/Makemodule.am
sys-utils/lsipc.1 [new file with mode: 0644]
sys-utils/lsipc.c [new file with mode: 0644]

index 6ef0c4da984b75da331f3ed1215a95d60f44e93f..0ef438a002c67144a921bc794f3e54032c66a33d 100644 (file)
@@ -115,6 +115,7 @@ update.log
 /look
 /losetup
 /lsblk
+/lsipc
 /lscpu
 /lslocks
 /lslogins
index d65cc8f51f7cb5e855b2117c6e44e7bd940e7068..5372ed09d0d3c52892b21968a5f6667ba5955832 100644 (file)
@@ -45,6 +45,9 @@ endif
 if BUILD_LOOK
 dist_bashcompletion_DATA += bash-completion/look
 endif
+if BUILD_LSIPC
+dist_bashcompletion_DATA += bash-completion/lsipc
+endif
 if BUILD_MCOOKIE
 dist_bashcompletion_DATA += bash-completion/mcookie
 endif
diff --git a/bash-completion/lsipc b/bash-completion/lsipc
new file mode 100644 (file)
index 0000000..6ce0551
--- /dev/null
@@ -0,0 +1,65 @@
+_lsipc_module()
+{
+       local cur prev OPTS ARG
+       COMPREPLY=()
+       cur="${COMP_WORDS[COMP_CWORD]}"
+       prev="${COMP_WORDS[COMP_CWORD-1]}"
+       case $prev in
+               '-i'|'--id')
+                       COMPREPLY=( $(compgen -W "id" -- $cur) )
+                       return 0
+                       ;;
+               '-h'|'--help'|'-V'|'--version')
+                       return 0
+                       ;;
+               '-o'|'--output')
+                       local prefix realcur OUTPUT_ALL OUTPUT
+                       realcur="${cur##*,}"
+                       prefix="${cur%$realcur}"
+                       OUTPUT_ALL="GENERAL KEY ID OWNER PERMS CUID
+                       CGID UID GID CHANGE MESSAGE USEDBYTES
+                       MSGS SEND RECV LSPID LRPID SHARED BYTES
+                       NATTCH STATUS ATTACH DETACH CPID LPID NSEMS
+                       LASTOP"
+                       for WORD in $OUTPUT_ALL; do
+                               if ! [[ $prefix == *"$WORD"* ]]; then
+                                       OUTPUT="$WORD $OUTPUT"
+                               fi
+                       done
+                       compopt -o nospace
+                       COMPREPLY=( $(compgen -P "$prefix" -W "$OUTPUT" -S ',' -- $realcur) )
+                       return 0
+                       ;;
+       esac
+       case $cur in
+               -*)
+                       OPTS="--id
+                               --help
+                               --version
+                               --shmems
+                               --queues
+                               --semaphores
+                               --colon-separate
+                               --creator
+                               --export
+                               --global
+                               --json
+                               --newline
+                               --noheadings
+                               --notruncate
+                               --output
+                               --pid
+                               --print0
+                               --raw
+                               --time
+                               --time-format"
+                       COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
+                       return 0
+                       ;;
+       esac
+       local IFS=$'\n'
+       compopt -o filenames
+       COMPREPLY=( $(compgen -f -- $cur) )
+       return 0
+}
+complete -F _lsipc_module lsipc
index 58feb2237b6070ce43b6e0deb9b8e50fc323c42c..c37d044014dcb77c43dd4f02b581a3fcd70ecdfc 100644 (file)
@@ -1388,6 +1388,11 @@ AM_CONDITIONAL([BUILD_IPCRM], [test "x$build_ipcrm" = xyes])
 UL_BUILD_INIT([ipcs], [yes])
 AM_CONDITIONAL([BUILD_IPCS], [test "x$build_ipcs" = xyes])
 
+UL_BUILD_INIT([lsipc], [check])
+UL_REQUIRES_LINUX([lsipc])
+UL_REQUIRES_BUILD([lsipc], [libsmartcols])
+AM_CONDITIONAL([BUILD_LSIPC], [test "x$build_lsipc" = xyes])
+
 UL_BUILD_INIT([renice], [yes])
 AM_CONDITIONAL([BUILD_RENICE], [test "x$build_renice" = xyes])
 
index fcc5c53efe793186c70b50f052aae65c6f6a8d8f..f306e6559c2745acbc035cd0dbf5dfca406d2696 100644 (file)
@@ -28,6 +28,16 @@ ipcs_SOURCES =       sys-utils/ipcs.c \
 ipcs_LDADD = $(LDADD) libcommon.la
 endif
 
+if BUILD_LSIPC
+usrbin_exec_PROGRAMS += lsipc
+dist_man_MANS += sys-utils/lsipc.1
+lsipc_SOURCES =        sys-utils/lsipc.c \
+               sys-utils/ipcutils.c \
+               sys-utils/ipcutils.h
+lsipc_LDADD = $(LDADD) libcommon.la libsmartcols.la
+lsipc_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+endif
+
 if BUILD_RENICE
 usrbin_exec_PROGRAMS += renice
 dist_man_MANS += sys-utils/renice.1
diff --git a/sys-utils/lsipc.1 b/sys-utils/lsipc.1
new file mode 100644 (file)
index 0000000..73753a5
--- /dev/null
@@ -0,0 +1,135 @@
+.\" Copyright 2015 Ondrej Oprala(ooprala@redhat.com)
+.\" May be distributed under the GNU General Public License
+.TH LSIPC "1" "June 2015" "util-linux" "User Commands"
+.SH NAME
+lsipc \- show information on IPC facilities currently employed in the system
+.SH SYNOPSIS
+.B lsipc
+[options]
+.SH DESCRIPTION
+.B lsipc
+shows information on the inter-process communication facilities
+for which the calling process has read access.
+.SH OPTIONS
+.TP
+\fB\-i\fR, \fB\-\-id\fR \fIid\fR
+Show full details on just the one resource element identified by
+.IR id .
+This option needs to be combined with one of the three resource options:
+.BR \-m ,
+.BR \-q " or"
+.BR \-s .
+.TP
+\fB\-g\fR, \fB\-\-global\fR
+Show system-wide usage and limits for a single IPC type.
+This option needs to be combined with one of the three resource options:
+.BR \-m ,
+.BR \-q " or"
+.BR \-s .
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.SS "Resource options"
+.TP
+\fB\-m\fR, \fB\-\-shmems\fR
+Write information about active shared memory segments.
+.TP
+\fB\-q\fR, \fB\-\-queues\fR
+Write information about active message queues.
+.TP
+\fB\-s\fR, \fB\-\-semaphores\fR
+Write information about active semaphore sets.
+.SS "Output formats"
+.TP
+\fB\-c\fR, \fB\-\-creator\fR
+Show creator and owner.
+.TP
+\fB\-\-colon\-separate\fR
+Separate info about each user with a colon instead of a newline.
+.TP
+\fB\-e\fR, \fB\-\-export\fR
+Output data in the format of NAME=VALUE.
+.TP
+\fB\-J\fR, \fB\-\-json\fR
+Use the JSON output format.
+.TP
+\fB\-n\fR, \fB\-\-newline\fR
+Display each piece of information on a separate line.
+.TP
+\fB\-\-noheadings\fR
+Do not print a header line.
+.TP
+\fB\-\-notruncate\fR
+Don't truncate output.
+.TP
+\fB\-o\fR, \fB\-\-output \fIlist\fP
+Specify which output columns to print.  Use
+.B \-\-help
+to get a list of all supported columns.
+.TP
+\fB\-p\fR, \fB\-\-pid\fR
+Show PIDs of creator and last operator.
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+Raw output (no columnation).
+.TP
+\fB\-t\fR, \fB\-\-time\fR
+Write time information.  The time of the last control operation that changed
+the access permissions for all facilities, the time of the last
+.I msgsnd()
+and
+.I msgrcv()
+operations on message queues, the time of the last
+.I shmat()
+and
+.I shmdt()
+operations on shared memory, and the time of the last
+.I semop()
+operation on semaphores.
+.TP
+\fB\-\-time\-format\fR \fItype\fP
+Display dates in short, full or iso format.  The default is short, this time
+format is designed to be space efficient and human readable.
+.TP
+\fB\-z\fR, \fB\-\-print0\fR
+Delimit user entries with a nul character, instead of a newline.
+
+.SH EXIT STATUS
+.TP
+0
+if OK,
+.TP
+1
+if incorrect arguments specified,
+.TP
+2
+if a serious error occurs.
+.SH SEE ALSO
+.BR ipcrm (1),
+.BR ipcmk (1),
+.BR msgrcv (2),
+.BR msgsnd (2),
+.BR semget (2),
+.BR semop (2),
+.BR shmat (2),
+.BR shmdt (2),
+.BR shmget (2)
+.SH HISTORY
+The \fBlsipc\fP utility is inspired by the \fBipcs\fP utility.
+.SH AUTHORS
+.MT ooprala@redhat.com
+Ondrej Oprala
+.ME
+.br
+.MT kzak@redhat.com
+Karel Zak
+.ME
+
+.SH AVAILABILITY
+The lsipc command is part of the util-linux package and is available from
+.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/sys-utils/lsipc.c b/sys-utils/lsipc.c
new file mode 100644 (file)
index 0000000..a30850c
--- /dev/null
@@ -0,0 +1,1259 @@
+/*
+ * lsipc - List information about IPC instances employed in the system
+ *
+ * Copyright (C) 2015 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2015 Karel Zak <ooprala@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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *
+ * lsipc is inspired by the ipcs utility. The aim is to create
+ * a utility unencumbered by a standard to provide more flexible
+ * means of controlling the output.
+ */
+
+#include <errno.h>
+#include <features.h>
+#include <getopt.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <libsmartcols.h>
+
+#include "c.h"
+#include "nls.h"
+#include "closestream.h"
+#include "strutils.h"
+#include "optutils.h"
+#include "xalloc.h"
+
+#include "ipcutils.h"
+
+/*
+ * time modes
+ * */
+enum {
+       TIME_INVALID = 0,
+       TIME_SHORT,
+       TIME_FULL,
+       TIME_ISO,
+};
+
+/*
+ * IDs
+ */
+enum {
+       /* common */
+       COL_KEY = 0,
+       COL_ID,
+       COL_OWNER,
+       COL_PERMS,
+       COL_CUID,
+       COL_CGID,
+       COL_UID,
+       COL_GID,
+       COL_CTIME,
+       /* msgq-specific */
+       COL_USEDBYTES,
+       COL_MSGS,
+       COL_SEND,
+       COL_RECV,
+       COL_LSPID,
+       COL_LRPID,
+       /* shm-specific */
+       COL_SIZE,
+       COL_NATTCH,
+       COL_STATUS,
+       COL_ATTACH,
+       COL_DETACH,
+       COL_CPID,
+       COL_LPID,
+       /* sem-specific */
+       COL_NSEMS,
+       COL_OTIME,
+       COL_RESOURCE,
+       COL_DESC,
+       COL_USED,
+       COL_LIMIT,
+};
+
+/* we use the value of outmode to determine
+ * appropriate flags for the libsmartcols table
+ * (e.g., a value of out_newline would imply a raw
+ * table with the column separator set to '\n').
+ */
+static int outmode;
+
+/* not all columns apply to all options, so we specify a legal range for each */
+static size_t LOWER, UPPER;
+
+/*
+ * output modes
+ */
+enum {
+       OUT_COLON = 1,
+       OUT_EXPORT,
+       OUT_NEWLINE,
+       OUT_RAW,
+       OUT_NUL,
+       OUT_PRETTY
+};
+
+struct lsipc_control {
+       unsigned int noheadings : 1,            /* don't print header line */
+                    notrunc : 1,               /* don't truncate columns */
+                    json : 1,                  /* JSON output */
+                    bytes : 1,                 /* SIZE in bytes */
+                    numperms : 1,              /* numeric permissions */
+                    time_mode : 2;
+};
+
+struct lsipc_coldesc {
+       const char *name;
+       const char *help;
+       const char *pretty_name;
+
+       double whint;   /* width hint */
+       long flag;
+};
+
+static const struct lsipc_coldesc coldescs[] =
+{
+       /* common */
+       [COL_KEY]       = { "KEY",      N_("Resource key"), N_("Key"), 1},
+       [COL_ID]        = { "ID",       N_("Resource ID"), N_("ID"), 1},
+       [COL_OWNER]     = { "OWNER",    N_("Owner"), N_("Owner"), 1, SCOLS_FL_RIGHT},
+       [COL_PERMS]     = { "PERMS",    N_("Permissions"), N_("Permissions"), 1, SCOLS_FL_RIGHT},
+       [COL_CUID]      = { "CUID",     N_("Creator UID"), N_("CUID"), 1, SCOLS_FL_RIGHT},
+       [COL_CGID]      = { "CGID",     N_("Creator GID"), N_("CGID"), 1, SCOLS_FL_RIGHT},
+       [COL_UID]       = { "UID",      N_("User ID"), N_("UID"), 1, SCOLS_FL_RIGHT},
+       [COL_GID]       = { "GID",      N_("Group ID"), N_("GID"), 1, SCOLS_FL_RIGHT},
+       [COL_CTIME]     = { "CTIME",    N_("Time of the last change"), N_("Last change"), 1, SCOLS_FL_RIGHT},
+
+       /* msgq-specific */
+       [COL_USEDBYTES] = { "USEDBYTES",N_("Bytes used"), N_("Bytes used"), 1, SCOLS_FL_RIGHT},
+       [COL_MSGS]      = { "MSGS",     N_("Number of messages"), N_("Messages"), 1},
+       [COL_SEND]      = { "SEND",     N_("Time of last msg sent"), N_("Msg sent"), 1, SCOLS_FL_RIGHT},
+       [COL_RECV]      = { "RECV",     N_("Time of last msg received"), N_("Msg received"), 1, SCOLS_FL_RIGHT},
+       [COL_LSPID]     = { "LSPID",    N_("PID of the last msg sender"), N_("Msg sender"), 1, SCOLS_FL_RIGHT},
+       [COL_LRPID]     = { "LRPID",    N_("PID of the last msg receiver"), N_("Msg receiver"), 1, SCOLS_FL_RIGHT},
+
+       /* shm-specific */
+       [COL_SIZE]      = { "SIZE",     N_("Segment size"), N_("Segment size"), 1, SCOLS_FL_RIGHT},
+       [COL_NATTCH]    = { "NATTCH",   N_("Number of attached processes"), N_("Attached processes"), 1, SCOLS_FL_RIGHT},
+       [COL_STATUS]    = { "STATUS",   N_("Status"), N_("Status"), 1, SCOLS_FL_NOEXTREMES},
+       [COL_ATTACH]    = { "ATTACH",   N_("Attach time"), N_("Attach time"), 1, SCOLS_FL_RIGHT},
+       [COL_DETACH]    = { "DETACH",   N_("Detach time"), N_("Detach time"), 1, SCOLS_FL_RIGHT},
+       [COL_CPID]      = { "CPID",     N_("PID of the creator"), N_("Creator PID"), 1, SCOLS_FL_RIGHT},
+       [COL_LPID]      = { "LPID",     N_("PID of last user"), N_("Last user PID"), 1, SCOLS_FL_RIGHT},
+
+       /* sem-specific */
+       [COL_NSEMS]     = { "NSEMS",    N_("Number of semaphores"), N_("Semaphores"), 1, SCOLS_FL_RIGHT},
+       [COL_OTIME]     = { "OTIME",    N_("Time of the last operation"), N_("Last operation"), 1, SCOLS_FL_RIGHT},
+
+       /* cols for summarized information */
+       [COL_RESOURCE]  = { "RESOURCE", N_("Resource name"), N_("Resource"), 1 },
+       [COL_DESC]      = { "DESCRIPTION",N_("Resource description"), N_("Description"), 1 },
+       [COL_USED]      = { "USED",     N_("Currently used"), N_("Used"), 1 },
+       [COL_LIMIT]     = { "LIMIT",    N_("System-wide limit"), N_("Limit"), 1, SCOLS_FL_RIGHT },
+};
+
+
+/* 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 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 (i > COL_CTIME) {
+                               if (i >= LOWER && i <= UPPER)
+                                       return i;
+                               else {
+                                       warnx(_("column %s does not apply to the specified IPC"), name);
+                                       return -1;
+                               }
+                       } else
+                               return i;
+               }
+       }
+       warnx(_("unknown column: %s"), name);
+       return -1;
+}
+
+static int parse_time_mode(const char *optarg)
+{
+       struct lsipc_timefmt {
+               const char *name;
+               const int val;
+       };
+       static const struct lsipc_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, optarg) == 0)
+                       return timefmts[i].val;
+       }
+       errx(EXIT_FAILURE, _("unknown time format: %s"), optarg);
+}
+
+static void __attribute__ ((__noreturn__)) usage(FILE * out)
+{
+       size_t i;
+
+       fputs(USAGE_HEADER, out);
+       fputs(USAGE_HEADER, out);
+       fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+
+       fputs(USAGE_SEPARATOR, out);
+       fputs(_("Show information on IPC facilities.\n"), out);
+
+       fputs(USAGE_OPTIONS, out);
+       fputs(USAGE_HELP, out);
+       fputs(USAGE_VERSION, out);
+
+       fputs(USAGE_SEPARATOR, out);
+       fputs(_("Resource options:\n"), out);
+       fputs(_(" -m, --shmems      shared memory segments\n"), out);
+       fputs(_(" -q, --queues      message queues\n"), out);
+       fputs(_(" -s, --semaphores  semaphores\n"), out);
+       fputs(_(" -i, --id <id>     print details on resource identified by <id>\n"), out);
+
+       fputs(USAGE_SEPARATOR, out);
+       fputs(_("Output options:\n"), out);
+       fputs(_("     --colon-separate     display data in a format similar to /etc/passwd\n"), out);
+       fputs(_("     --noheadings         don't print headings\n"), out);
+       fputs(_("     --notruncate         don't truncate output\n"), out);
+       fputs(_("     --time-format=<type> display dates in short, full or iso format\n"), out);
+       fputs(_(" -b, --bytes              print SIZE in bytes rather than in human readable format\n"), out);
+       fputs(_(" -c, --creator            show creator and owner\n"), out);
+       fputs(_(" -e, --export             display in an export-able output format\n"), out);
+       fputs(_(" -g, --global             display info about about system-wide usage\n"), out);
+       fputs(_(" -J, --json               use the JSON output format\n"), out);
+       fputs(_(" -n, --newline            display each piece of information on a new line\n"), out);
+       fputs(_(" -o, --output[=<list>]    define the columns to output\n"), out);
+       fputs(_(" -P, --numeric-perms      print numeric permissions (PERMS column)\n"), out);
+       fputs(_(" -r, --raw                display in raw mode\n"), out);
+       fputs(_(" -t, --time               show attach, detach and change times\n"), out);
+       fputs(_(" -z, --print0             delimit user entries with a nul character\n"), out);
+
+       fprintf(out, _("\nAvailable columns:\n"));
+
+       fprintf(out, _("GENERAL COLUMNS:\n"));
+       for (i = 0; i < ARRAY_SIZE(coldescs); i++) {
+               if (i == COL_USEDBYTES)
+                       fprintf(out, _("MESSAGE QUEUES:\n"));
+               if (i == COL_SIZE)
+                       fprintf(out, _("SHARED MEMORY:\n"));
+               if (i == COL_NSEMS)
+                       fprintf(out, _("SEMAPHORES:\n"));
+               if (i == COL_RESOURCE)
+                       fprintf(out, _("SUMMARIZED INFO:\n"));
+               fprintf(out, " %14s  %s\n", coldescs[i].name,
+                               _(coldescs[i].help));
+       }
+
+       fprintf(out, USAGE_MAN_TAIL("lsipc(1)"));
+
+       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+static struct libscols_table *setup_table(struct lsipc_control *ctl)
+{
+       struct libscols_table *table = scols_new_table();
+       int n = 0;
+
+       if (!table)
+               errx(EXIT_FAILURE, _("failed to initialize output table"));
+       if (ctl->noheadings)
+               scols_table_enable_noheadings(table, 1);
+       if (ctl->json)
+               scols_table_enable_json(table, 1);
+
+       switch(outmode) {
+       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) {
+               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 table;
+fail:
+       scols_unref_table(table);
+       return NULL;
+}
+
+static int print_pretty(struct libscols_table *table)
+{
+       struct libscols_iter *itr = scols_new_iter(SCOLS_ITER_FORWARD);
+       struct libscols_column *col;
+       struct libscols_cell *data;
+       struct libscols_line *ln;
+       const char *hstr, *dstr, *estr;
+       int n = 0;
+
+       ln = scols_table_get_line(table, 0);
+       while (!scols_table_next_column(table, itr, &col)) {
+
+               data = scols_line_get_cell(ln, n);
+
+               hstr = N_(coldescs[columns[n]].pretty_name);
+               dstr = scols_cell_get_data(data);
+
+               if (dstr)
+                       printf("%s:%*c%-36s\n", hstr, 35 - (int)strlen(hstr), ' ', dstr);
+               ++n;
+       }
+
+       /* this is used to pretty-print detailed info about a semaphore array */
+       if (ln) {
+               estr = scols_line_get_userdata(ln);
+               if (estr) {
+                       printf("%s", (const char *)estr);
+                       free((void *)estr);
+               }
+       }
+
+       scols_free_iter(itr);
+       return 0;
+
+}
+
+static int print_table(struct libscols_table *tb)
+{
+       if (outmode == OUT_PRETTY)
+               print_pretty(tb);
+       else
+               scols_print_table(tb);
+       return 0;
+}
+static struct timeval now;
+
+static int date_is_today(time_t t)
+{
+       if (now.tv_sec == 0)
+               gettimeofday(&now, NULL);
+       return t / (3600 * 24) == now.tv_sec / (3600 * 24);
+}
+
+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);
+}
+
+static char *make_time(int mode, time_t time)
+{
+       char *s;
+       struct tm tm;
+       char buf[64] = {0};
+
+       localtime_r(&time, &tm);
+
+       switch(mode) {
+       case TIME_FULL:
+               asctime_r(&tm, buf);
+               if (*(s = buf + strlen(buf) - 1) == '\n')
+                       *s = '\0';
+               break;
+       case TIME_SHORT:
+               if (date_is_today(time))
+                       strftime(buf, sizeof(buf), "%H:%M", &tm);
+               else if (date_is_thisyear(time))
+                       strftime(buf, sizeof(buf), "%b%d", &tm);
+               else
+                       strftime(buf, sizeof(buf), "%Y-%b%d", &tm);
+               break;
+       case TIME_ISO:
+               strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", &tm);
+               break;
+       default:
+               errx(EXIT_FAILURE, _("unsupported time type"));
+       }
+       return xstrdup(buf);
+}
+
+static void do_sem(int id, struct lsipc_control *ctl, struct libscols_table *tb)
+{
+       int n = 0;
+       struct libscols_line *ln;
+       struct passwd *pw = NULL, *cpw = NULL;
+       struct group *gr = NULL, *cgr = NULL;
+       struct sem_data *semds, *semdsp;
+       char *arg = NULL, *time;
+
+       if (ipc_sem_get_info(id, &semds) < 1) {
+               if (id > -1)
+                       warnx(_("id %d not found"), id);
+               return;
+       }
+       for (semdsp = semds;  semdsp->next != NULL || id > -1; semdsp = semdsp->next) {
+               ln = scols_table_new_line(tb, NULL);
+
+               /* no need to call getpwuid() for the same user */
+               if (!(pw && pw->pw_uid == semdsp->sem_perm.uid))
+                       pw = getpwuid(semdsp->sem_perm.uid);
+
+               /* no need to call getgrgid() for the same group */
+               if (!(gr && gr->gr_gid == semdsp->sem_perm.gid))
+                       gr = getgrgid(semdsp->sem_perm.gid);
+
+               /* no need to call getpwuid() for the same user */
+               if (!(cpw && cpw->pw_uid == semdsp->sem_perm.cuid))
+                       cpw = getpwuid(semdsp->sem_perm.cuid);
+
+               /* no need to call getcgrgid() for the same cgroup */
+               if (!(cgr && cgr->gr_gid == semdsp->sem_perm.cgid))
+                       cgr = getgrgid(semdsp->sem_perm.cgid);
+               n = 0;
+               while (n < ncolumns) {
+                       int rc = 0;
+
+                       switch (columns[n]) {
+                               case COL_KEY:
+                                       xasprintf(&arg, "0x%08x",semdsp->sem_perm.key);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_ID:
+                                       xasprintf(&arg, "%d",semdsp->sem_perm.id);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_OWNER:
+                                       if (pw)
+                                               xasprintf(&arg, "%s", pw->pw_name);
+                                       else
+                                               xasprintf(&arg, "%u", semdsp->sem_perm.uid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_PERMS:
+                                       if (ctl->numperms)
+                                               xasprintf(&arg, "%#o", semdsp->sem_perm.mode & 0777);
+                                       else {
+                                               arg = xmalloc(11);
+                                               strmode(semdsp->sem_perm.mode & 0777, arg);
+                                       }
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_CUID:
+                                       if (cpw)
+                                               xasprintf(&arg, "%s", cpw->pw_name);
+                                       else
+                                               xasprintf(&arg, "%u", semdsp->sem_perm.cuid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_CGID:
+                                       if (cgr)
+                                               xasprintf(&arg, "%s", cgr->gr_name);
+                                       else
+                                               xasprintf(&arg, "%u", semdsp->sem_perm.cuid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_UID:
+                                       if (pw)
+                                               xasprintf(&arg, "%s", pw->pw_name);
+                                       else
+                                               xasprintf(&arg, "%u", semdsp->sem_perm.uid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_GID:
+                                       if (gr)
+                                               xasprintf(&arg, "%s", gr->gr_name);
+                                       else
+                                               xasprintf(&arg, "%u", semdsp->sem_perm.gid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_CTIME:
+                                       if (semdsp->sem_ctime != 0) {
+                                               rc = scols_line_set_data(ln, n,
+                                                       time = make_time(ctl->time_mode,
+                                                                 (time_t)semdsp->sem_ctime));
+                                               free(time);
+                                       }
+                                       break;
+                               case COL_NSEMS:
+                                       xasprintf(&arg, "%ju", semdsp->sem_nsems);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_OTIME:
+                                       if (semdsp->sem_otime != 0) {
+                                               rc = scols_line_set_data(ln, n,
+                                                       time = make_time(ctl->time_mode,
+                                                                 (time_t)semdsp->sem_otime));
+                                               free(time);
+                                       }
+                                       break;
+                       }
+                       if (rc != 0)
+                               err(EXIT_FAILURE, _("failed to set data"));
+                       ++n;
+                       free(arg);
+                       arg = NULL;
+               }
+               /* no empty element when ID was specified */
+               if (id > -1) {
+                       size_t i = 0, offt;
+                       /* line length * (header + nsems) + '\0' */
+                       char *data = xcalloc(1, sizeof(char) * 55 * (semds->sem_nsems + 1) + 1);
+
+                       /* same as in ipcs.c */
+                       offt = sprintf(data, "%-10s %-10s %-10s %-10s %-10s\n",
+                              _("semnum"), _("value"), _("ncount"), _("zcount"), _("pid"));
+
+                       for (i = 0; i < semds->sem_nsems; i++) {
+                               struct sem_elem *e = &semds->elements[i];
+                               offt += sprintf(data + offt, "%-10zd %-10d %-10d %-10d %-10d\n",
+                                      i, e->semval, e->ncount, e->zcount, e->pid);
+                       }
+                       scols_line_set_userdata(ln, (void *)data);
+                       break;
+               }
+       }
+       ipc_sem_free_info(semds);
+}
+
+static void do_sem_global(struct libscols_table *tb)
+{
+       struct sem_data *semds, *semdsp;
+       struct ipc_limits lim;
+       int sems = 0, sets = 0, n = 0;
+       struct libscols_line *ln;
+       char *result = NULL;
+
+       if (ipc_sem_get_info(-1, &semds) < 1)
+               return;
+       ipc_sem_get_limits(&lim);
+
+       for (semdsp = semds; semdsp->next != NULL; semdsp = semdsp->next) {
+               ++sets;
+               sems += semds->sem_nsems;
+       }
+       ln = scols_table_new_line(tb, NULL);
+       scols_line_set_data(ln, n++, "SEMMNS");
+       scols_line_set_data(ln, n++, "Total number of semaphores");
+
+       xasprintf(&result, "%d", sems);
+       scols_line_set_data(ln, n++, result);
+       free(result);
+
+       xasprintf(&result, "%d", lim.semmns);
+       scols_line_set_data(ln, n, result);
+       free(result);
+
+
+       n = 0;
+       ln = scols_table_new_line(tb, NULL);
+
+       scols_line_set_data(ln, n++, "SEMMNI");
+       scols_line_set_data(ln, n++, "Number of Semaphore IDs");
+
+       xasprintf(&result, "%d", sets);
+       scols_line_set_data(ln, n++, result);
+       free(result);
+
+       xasprintf(&result, "%d", lim.semmni);
+       scols_line_set_data(ln, n, result);
+       free(result);
+
+       ipc_sem_free_info(semds);
+}
+
+static void do_msg(int id, struct lsipc_control *ctl, struct libscols_table *tb)
+{
+       int n = 0;
+       struct libscols_line *ln;
+       struct passwd *pw = NULL;
+       struct group *gr = NULL;
+       struct msg_data *msgds, *msgdsp;
+       char *arg = NULL, *time;
+
+       if (ipc_msg_get_info(id, &msgds) < 1) {
+               if (id > -1)
+                       warnx(_("id %d not found"), id);
+               return;
+       }
+
+       for (msgdsp = msgds; msgdsp->next != NULL || id > -1 ; msgdsp = msgdsp->next) {
+
+               ln = scols_table_new_line(tb, NULL);
+
+               /* no need to call getpwuid() for the same user */
+               if (!(pw && pw->pw_uid == msgdsp->msg_perm.uid))
+                       pw = getpwuid(msgdsp->msg_perm.uid);
+
+               /* no need to call getgrgid() for the same user */
+               if (!(gr && gr->gr_gid == msgdsp->msg_perm.gid))
+                       gr = getgrgid(msgdsp->msg_perm.gid);
+
+               n = 0;
+               while (n < ncolumns) {
+                       int rc = 0;
+
+                       switch (columns[n]) {
+                               case COL_KEY:
+                                       xasprintf(&arg, "0x%08x",msgdsp->msg_perm.key);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_ID:
+                                       xasprintf(&arg, "%d",msgdsp->msg_perm.id);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_OWNER:
+                                       if (pw)
+                                               xasprintf(&arg, "%s", pw->pw_name);
+                                       else
+                                               xasprintf(&arg, "%u", msgdsp->msg_perm.uid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_PERMS:
+                                       if (ctl->numperms)
+                                               xasprintf(&arg, "%#o", msgdsp->msg_perm.mode & 0777);
+                                       else {
+                                               arg = xmalloc(11);
+                                               strmode(msgdsp->msg_perm.mode & 0777, arg);
+                                               rc = scols_line_set_data(ln, n, arg);
+                                       }
+                                       break;
+                               case COL_CUID:
+                                       if (msgdsp->msg_perm.cuid == msgdsp->msg_perm.uid
+                                           || (pw = getpwuid(msgdsp->msg_perm.cuid)))
+                                               xasprintf(&arg, "%s", pw->pw_name);
+                                       else
+                                               xasprintf(&arg, "%u", msgdsp->msg_perm.cuid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_CGID:
+                                       if (msgdsp->msg_perm.cgid == msgdsp->msg_perm.gid
+                                           || (gr = getgrgid(msgdsp->msg_perm.cgid)))
+                                               xasprintf(&arg, "%s", gr->gr_name);
+                                       else
+                                               xasprintf(&arg, "%u", msgdsp->msg_perm.cuid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_UID:
+                                       if (pw)
+                                               xasprintf(&arg, "%s", pw->pw_name);
+                                       else
+                                               xasprintf(&arg, "%u", msgdsp->msg_perm.uid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_GID:
+                                       if (gr)
+                                               xasprintf(&arg, "%s", gr->gr_name);
+                                       else
+                                               xasprintf(&arg, "%u", msgdsp->msg_perm.gid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_CTIME:
+                                       if (msgdsp->q_ctime != 0) {
+                                               rc = scols_line_set_data(ln, n,
+                                                       time = make_time(ctl->time_mode,
+                                                                 (time_t)msgdsp->q_ctime));
+                                               free(time);
+                                       }
+                                       break;
+                               case COL_USEDBYTES:
+                                       xasprintf(&arg, "%ju", msgdsp->q_cbytes);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_MSGS:
+                                       xasprintf(&arg, "%ju", msgdsp->q_qnum);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_SEND:
+                                       if (msgdsp->q_stime != 0) {
+                                               rc = scols_line_set_data(ln, n,
+                                                       time = make_time(ctl->time_mode,
+                                                                 (time_t)msgdsp->q_stime));
+                                               free(time);
+                                       }
+                                       break;
+                               case COL_RECV:
+                                       if (msgdsp->q_rtime != 0) {
+                                               rc = scols_line_set_data(ln, n,
+                                                       time = make_time(ctl->time_mode,
+                                                                 (time_t)msgdsp->q_rtime));
+                                               free(time);
+                                       }
+                                       break;
+                               case COL_LSPID:
+                                       xasprintf(&arg, "%u", msgdsp->q_lspid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_LRPID:
+                                       xasprintf(&arg, "%u", msgdsp->q_lrpid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                       }
+                       if (rc != 0)
+                               err(EXIT_FAILURE, _("failed to set data"));
+                       ++n;
+                       free(arg);
+                       arg = NULL;
+               }
+               if (id > -1)
+                       break;
+       }
+       ipc_msg_free_info(msgds);
+}
+
+static void do_msg_global(struct libscols_table *tb)
+{
+       struct msg_data *msgds, *msgdsp;
+       struct ipc_limits lim;
+       struct libscols_line *ln = scols_table_new_line(tb, NULL);
+       int msgqs = 0, n = 0;
+       char *result;
+
+       if (ipc_msg_get_info(-1, &msgds) < 1)
+               return;
+       ipc_msg_get_limits(&lim);
+
+       for (msgdsp = msgds; msgdsp->next != NULL; msgdsp = msgdsp->next) {
+               ++msgqs;
+       }
+
+       scols_line_set_data(ln, n++, "MSGMNI");
+       scols_line_set_data(ln, n++, "Number of message queues");
+
+       xasprintf(&result, "%d", msgqs);
+       scols_line_set_data(ln, n++, result);
+       free(result);
+
+       xasprintf(&result, "%d", lim.msgmni);
+       scols_line_set_data(ln, n, result);
+       free(result);
+
+       ipc_msg_free_info(msgds);
+}
+
+static void do_shm(int id, struct lsipc_control *ctl, struct libscols_table *tb)
+{
+       int n = 0;
+       struct libscols_line *ln;
+       struct passwd *pw = NULL;
+       struct group *gr = NULL;
+       struct shm_data *shmds, *shmdsp;
+       char *arg = NULL, *time;
+
+       if (ipc_shm_get_info(id, &shmds) < 1) {
+               if (id > -1)
+                       warnx(_("id %d not found"), id);
+               return;
+       }
+
+       for (shmdsp = shmds; shmdsp->next != NULL || id > -1 ; shmdsp = shmdsp->next) {
+
+               ln = scols_table_new_line(tb, NULL);
+
+               /* no need to call getpwuid() for the same user */
+               if (!(pw && pw->pw_uid == shmdsp->shm_perm.uid))
+                       pw = getpwuid(shmdsp->shm_perm.uid);
+
+               /* no need to call getgrgid() for the same user */
+               if (!(gr && gr->gr_gid == shmdsp->shm_perm.gid))
+                       gr = getgrgid(shmdsp->shm_perm.gid);
+
+               n = 0;
+               while (n < ncolumns) {
+                       int rc = 0;
+
+                       switch (columns[n]) {
+                               case COL_KEY:
+                                       xasprintf(&arg, "0x%08x",shmdsp->shm_perm.key);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_ID:
+                                       xasprintf(&arg, "%d",shmdsp->shm_perm.id);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_OWNER:
+                                       if (pw)
+                                               xasprintf(&arg, "%s", pw->pw_name);
+                                       else
+                                               xasprintf(&arg, "%u", shmdsp->shm_perm.uid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_PERMS:
+                                       if (ctl->numperms)
+                                               xasprintf(&arg, "%#o", shmdsp->shm_perm.mode & 0777);
+                                       else {
+                                               arg = xmalloc(11);
+                                               strmode(shmdsp->shm_perm.mode & 0777, arg);
+                                       }
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_CUID:
+                                       if (shmdsp->shm_perm.cuid == shmdsp->shm_perm.uid
+                                                       || (pw = getpwuid(shmdsp->shm_perm.cuid)))
+                                               xasprintf(&arg, "%s", pw->pw_name);
+                                       else
+                                               xasprintf(&arg, "%u", shmdsp->shm_perm.cuid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_CGID:
+                                       if (shmdsp->shm_perm.cgid == shmdsp->shm_perm.gid
+                                                       || (gr = getgrgid(shmdsp->shm_perm.cgid)))
+                                               xasprintf(&arg, "%s", gr->gr_name);
+                                       else
+                                               xasprintf(&arg, "%u", shmdsp->shm_perm.cuid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_UID:
+                                       if (pw)
+                                               xasprintf(&arg, "%s", pw->pw_name);
+                                       else
+                                               xasprintf(&arg, "%u", shmdsp->shm_perm.uid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_GID:
+                                       if (gr)
+                                               xasprintf(&arg, "%s", gr->gr_name);
+                                       else
+                                               xasprintf(&arg, "%u", shmdsp->shm_perm.gid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_CTIME:
+                                       if (shmdsp->shm_ctim != 0) {
+                                               rc = scols_line_set_data(ln, n,
+                                                       time = make_time(ctl->time_mode,
+                                                                 (time_t)shmdsp->shm_ctim));
+                                               free(time);
+                                       }
+                                       break;
+                               case COL_SIZE:
+                                       if (ctl->bytes)
+                                               xasprintf(&arg, "%ju", shmdsp->shm_segsz);
+                                       else
+                                               arg = size_to_human_string(SIZE_SUFFIX_1LETTER, shmdsp->shm_segsz);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_NATTCH:
+                                       xasprintf(&arg, "%ju", shmdsp->shm_nattch);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_STATUS: {
+                                               int comma = 0;
+                                               size_t offt = 0;
+                                               free (arg);
+                                               arg = xcalloc(1, sizeof(char) * strlen(_("dest"))
+                                                               + strlen(_("locked"))
+                                                               + strlen(_("hugetlb"))
+                                                               + strlen(_("noreserve")) + 4);
+
+                                               if (shmdsp->shm_perm.mode & SHM_DEST) {
+                                                       offt += sprintf(arg, "%s", _("dest"));
+                                                       comma++;
+                                               }
+                                               if (shmdsp->shm_perm.mode & SHM_LOCKED) {
+                                                       if (comma)
+                                                               arg[offt++] = ',';
+                                                       offt += sprintf(arg + offt, "%s", _("locked"));
+                                               }
+                                               if (shmdsp->shm_perm.mode & SHM_HUGETLB) {
+                                                       if (comma)
+                                                               arg[offt++] = ',';
+                                                       offt += sprintf(arg + offt, "%s", _("hugetlb"));
+                                               }
+                                               if (shmdsp->shm_perm.mode & SHM_NORESERVE) {
+                                                       if (comma)
+                                                               arg[offt++] = ',';
+                                                       offt += sprintf(arg + offt, "%s", _("noreserve"));
+                                               }
+                                               rc = scols_line_set_data(ln, n, arg);
+                                       }
+                                       break;
+                               case COL_ATTACH:
+                                       if (shmdsp->shm_atim != 0) {
+                                               rc = scols_line_set_data(ln, n,
+                                                       time = make_time(ctl->time_mode,
+                                                                 (time_t)shmdsp->shm_atim));
+                                               free(time);
+                                       }
+                                       break;
+                               case COL_DETACH:
+                                       if (shmdsp->shm_dtim != 0) {
+                                               rc = scols_line_set_data(ln, n,
+                                                       time = make_time(ctl->time_mode,
+                                                                 (time_t)shmdsp->shm_dtim));
+                                               free(time);
+                                       }
+                                       break;
+                               case COL_CPID:
+                                       xasprintf(&arg, "%u", shmdsp->shm_cprid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                               case COL_LPID:
+                                       xasprintf(&arg, "%u", shmdsp->shm_lprid);
+                                       rc = scols_line_set_data(ln, n, arg);
+                                       break;
+                       }
+                       if (rc != 0)
+                               err(EXIT_FAILURE, _("failed to set data"));
+                       ++n;
+                       free(arg);
+                       arg = NULL;
+               }
+               if (id > -1)
+                       break;
+       }
+       ipc_shm_free_info(shmds);
+}
+
+static void do_shm_global(struct libscols_table *tb)
+{
+       struct shm_data *shmds, *shmdsp;
+       int n = 0;
+       uint64_t segs = 0, segsz = 0;
+       struct ipc_limits lim;
+       struct libscols_line *ln;
+       char *result = NULL;
+
+       if (ipc_shm_get_info(-1, &shmds) < 1)
+               return;
+       ipc_shm_get_limits(&lim);
+
+       for (shmdsp = shmds; shmdsp->next != NULL; shmdsp = shmdsp->next) {
+               ++segs;
+               segsz += shmdsp->shm_segsz;
+       }
+
+       ln = scols_table_new_line(tb, NULL);
+
+       scols_line_set_data(ln, n++, "SHMMNI");
+       scols_line_set_data(ln, n++, "Shared memory segments");
+
+       xasprintf(&result, "%lu", segs);
+       scols_line_set_data(ln, n++, result);
+       free(result);
+
+       xasprintf(&result, "%lu", lim.shmmni);
+       scols_line_set_data(ln, n, result);
+       free(result);
+
+
+       n = 0;
+       ln = scols_table_new_line(tb, NULL);
+
+       scols_line_set_data(ln, n++, "SHMALL");
+       scols_line_set_data(ln, n++, "Shared memory pages");
+
+       xasprintf(&result, "%lu", segsz / getpagesize());
+       scols_line_set_data(ln, n++, result);
+       free(result);
+
+       xasprintf(&result, "%lu", lim.shmall);
+       scols_line_set_data(ln, n, result);
+       free(result);
+
+       ipc_shm_free_info(shmds);
+}
+
+int main(int argc, char *argv[])
+{
+       int opt, msg = 0, sem = 0, shm = 0, opt_o = 0, id = -1;
+       int show_time = 0, show_creat = 0, global = 0;
+       size_t i;
+       struct lsipc_control *ctl = xcalloc(1, sizeof(struct lsipc_control));
+       static struct libscols_table *tb;
+       char *opts = NULL;
+
+       /* long only options. */
+       enum {
+               OPT_NOTRUNC = CHAR_MAX + 1,
+               OPT_NOHEAD,
+               OPT_TIME_FMT,
+               OPT_COLON,
+       };
+
+       static const struct option longopts[] = {
+               { "bytes",          no_argument,        0, 'b' },
+               { "colon-separate", no_argument,        0, OPT_COLON },
+               { "creator",        no_argument,        0, 'c' },
+               { "export",         no_argument,        0, 'e' },
+               { "global",         no_argument,        0, 'g' },
+               { "help",           no_argument,        0, 'h' },
+               { "id",             required_argument,  0, 'i' },
+               { "json",           no_argument,        0, 'J' },
+               { "newline",        no_argument,        0, 'n' },
+               { "noheadings",     no_argument,        0, OPT_NOHEAD },
+               { "notruncate",     no_argument,        0, OPT_NOTRUNC },
+               { "numeric-perms",  no_argument,        0, 'P' },
+               { "output",         required_argument,  0, 'o' },
+               { "pid",            no_argument,        0, 'p' },
+               { "print0",         no_argument,        0, 'z' },
+               { "queues",         no_argument,        0, 'q' },
+               { "raw",            no_argument,        0, 'r' },
+               { "semaphores",     no_argument,        0, 's' },
+               { "shmems",         no_argument,        0, 'm' },
+               { "time",           no_argument,        0, 't' },
+               { "time-format",    required_argument,  0, OPT_TIME_FMT },
+               { "version",        no_argument,        0, 'V' },
+               {NULL, 0, NULL, 0}
+       };
+
+       static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
+               { 'J', 'e', 'n', 'r', 'z', OPT_COLON },
+               { 'c', 'g', 'i', 'o', 't' },
+               { 'm', 'q', 's' },
+               { 0 }
+       };
+       int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+       setlocale(LC_ALL, "");
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+       atexit(close_stdout);
+
+       ctl->time_mode = TIME_SHORT;
+
+       while ((opt = getopt_long(argc, argv, "bceghi:Jmno:PqrstuVz", longopts, NULL)) != -1) {
+
+               err_exclusive_options(opt, longopts, excl, excl_st);
+
+               switch (opt) {
+                       case 'b':
+                               ctl->bytes = 1;
+                               break;
+                       case 'i':
+                               id = atoi (optarg);
+                               outmode = OUT_PRETTY;
+                               break;
+                       case OPT_COLON:
+                               outmode = OUT_COLON;
+                               break;
+                       case 'e':
+                               outmode = OUT_EXPORT;
+                               break;
+                       case 'r':
+                               outmode = OUT_RAW;
+                               break;
+                       case 'o':
+                               if (optarg) {
+                                       if (*optarg == '=')
+                                               optarg++;
+                                       opts = xstrdup(optarg);
+                               }
+                               opt_o = 1;
+                               break;
+                       case 'g':
+                               global = 1;
+                               break;
+                       case 'q':
+                               msg = 1;
+                               LOWER = COL_USEDBYTES;
+                               UPPER = COL_LRPID;
+                               break;
+                       case 'm':
+                               shm = 1;
+                               LOWER = COL_SIZE;
+                               UPPER = COL_LPID;
+                               break;
+                       case 'n':
+                               outmode = OUT_NEWLINE;
+                               break;
+                       case 'P':
+                               ctl->numperms = 1;
+                               break;
+                       case 's':
+                               sem = 1;
+                               LOWER = COL_NSEMS;
+                               UPPER = COL_OTIME;
+                               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 'J':
+                               ctl->json = 1;
+                               break;
+                       case 't':
+                               show_time = 1;
+                               break;
+                       case 'c':
+                               show_creat = 1;
+                               break;
+                       case 'h':
+                               usage(stdout);
+                       case 'V':
+                               printf(UTIL_LINUX_VERSION);
+                               return EXIT_SUCCESS;
+                       case 'z':
+                               outmode = OUT_NUL;
+                               break;
+                       default:
+                               usage(stderr);
+               }
+       }
+
+       if (msg + shm + sem != 1)
+               errx (EXIT_FAILURE,
+                     _("One of --shmems, --queues or --semaphores must be specified"));
+
+       if (global) {
+               add_column(columns, ncolumns++, COL_RESOURCE);
+               add_column(columns, ncolumns++, COL_DESC);
+               add_column(columns, ncolumns++, COL_USED);
+               add_column(columns, ncolumns++, COL_LIMIT);
+       } else if (opt_o) {
+               ncolumns = string_to_idarray(opts,
+                               columns, ARRAY_SIZE(columns),
+                               column_name_to_id);
+               if (ncolumns < 0)
+                       return EXIT_FAILURE;
+       } else {
+               if (outmode == OUT_PRETTY) {
+                       /* all columns for lsipc --<RESOURCE> --id <ID> */
+                       for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++)
+                                columns[ncolumns++] = i;
+               } else {
+                       /* default columns */
+                       add_column(columns, ncolumns++, COL_KEY);
+                       add_column(columns, ncolumns++, COL_ID);
+                       add_column(columns, ncolumns++, COL_PERMS);
+                       add_column(columns, ncolumns++, COL_OWNER);
+
+                       if (show_creat) {
+                               add_column(columns, ncolumns++, COL_CUID);
+                               add_column(columns, ncolumns++, COL_CGID);
+                               add_column(columns, ncolumns++, COL_UID);
+                               add_column(columns, ncolumns++, COL_GID);
+                       }
+
+                       if (msg) {
+                               add_column(columns, ncolumns++, COL_USEDBYTES);
+                               add_column(columns, ncolumns++, COL_MSGS);
+
+                               if (show_time) {
+                                       add_column(columns, ncolumns++, COL_SEND);
+                                       add_column(columns, ncolumns++, COL_RECV);
+                                       add_column(columns, ncolumns++, COL_CTIME);
+                               }
+
+                               add_column(columns, ncolumns++, COL_LSPID);
+                               add_column(columns, ncolumns++, COL_LRPID);
+                       }
+                       else if (shm) {
+                               add_column(columns, ncolumns++, COL_SIZE);
+                               add_column(columns, ncolumns++, COL_NATTCH);
+                               add_column(columns, ncolumns++, COL_STATUS);
+
+                               if (show_time) {
+                                       add_column(columns, ncolumns++, COL_ATTACH);
+                                       add_column(columns, ncolumns++, COL_DETACH);
+                                       add_column(columns, ncolumns++, COL_CTIME);
+                               }
+
+                               add_column(columns, ncolumns++, COL_CPID);
+                               add_column(columns, ncolumns++, COL_LPID);
+                       }
+                       else if (sem) {
+                               add_column(columns, ncolumns++, COL_NSEMS);
+
+                               if (show_time) {
+                                       add_column(columns, ncolumns++, COL_OTIME);
+                                       add_column(columns, ncolumns++, COL_CTIME);
+                               }
+                       }
+               }
+       }
+
+       tb = setup_table(ctl);
+       if (!tb)
+               return EXIT_FAILURE;
+
+       if (msg) {
+               if (global)
+                       do_msg_global(tb);
+               else
+                       do_msg(id, ctl, tb);
+       } else if (shm) {
+               if (global)
+                       do_shm_global(tb);
+               else
+                       do_shm(id, ctl, tb);
+       } else if (sem) {
+               if (global)
+                       do_sem_global(tb);
+               else
+               do_sem(id, ctl, tb);
+       }
+
+       print_table(tb);
+
+       scols_unref_table(tb);
+       free(ctl);
+
+       return EXIT_SUCCESS;
+}
+