/*
 * 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 <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 "procutils.h"
#include "ipcutils.h"
#include "timeutils.h"

/*
 * time modes
 * */
enum {
	TIME_INVALID = 0,
	TIME_SHORT,
	TIME_FULL,
	TIME_ISO
};

/*
 * IDs
 */
enum {
	/* generic */
	COLDESC_IDX_GEN_FIRST = 0,
		COL_KEY = COLDESC_IDX_GEN_FIRST,
		COL_ID,
		COL_OWNER,
		COL_PERMS,
		COL_CUID,
		COL_CUSER,
		COL_CGID,
		COL_CGROUP,
		COL_UID,
		COL_USER,
		COL_GID,
		COL_GROUP,
		COL_CTIME,
	COLDESC_IDX_GEN_LAST = COL_CTIME,

	/* msgq-specific */
	COLDESC_IDX_MSG_FIRST,
		COL_USEDBYTES = COLDESC_IDX_MSG_FIRST,
		COL_MSGS,
		COL_SEND,
		COL_RECV,
		COL_LSPID,
		COL_LRPID,
	COLDESC_IDX_MSG_LAST = COL_LRPID,

	/* shm-specific */
	COLDESC_IDX_SHM_FIRST,
		COL_SIZE = COLDESC_IDX_SHM_FIRST,
		COL_NATTCH,
		COL_STATUS,
		COL_ATTACH,
		COL_DETACH,
		COL_COMMAND,
		COL_CPID,
		COL_LPID,
	COLDESC_IDX_SHM_LAST = COL_LPID,

	/* sem-specific */
	COLDESC_IDX_SEM_FIRST,
		COL_NSEMS = COLDESC_IDX_SEM_FIRST,
		COL_OTIME,
	COLDESC_IDX_SEM_LAST = COL_OTIME,

	/* summary (--global) */
	COLDESC_IDX_SUM_FIRST,
		COL_RESOURCE = COLDESC_IDX_SUM_FIRST,
		COL_DESC,
		COL_LIMIT,
		COL_USED,
		COL_USEPERC,
	COLDESC_IDX_SUM_LAST = COL_USEPERC
};

/* not all columns apply to all options, so we specify a legal range for each */
static size_t LOWER, UPPER;

/*
 * output modes
 */
enum {
	OUT_EXPORT = 1,
	OUT_NEWLINE,
	OUT_RAW,
	OUT_JSON,
	OUT_PRETTY,
	OUT_LIST
};

struct lsipc_control {
	int outmode;
	unsigned int noheadings : 1,		/* don't print header line */
		     notrunc : 1,		/* don't truncate columns */
		     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's username or UID"), N_("Owner"), 1, SCOLS_FL_RIGHT},
	[COL_PERMS]	= { "PERMS",	N_("Permissions"), N_("Permissions"), 1, SCOLS_FL_RIGHT},
	[COL_CUID]	= { "CUID",	N_("Creator UID"), N_("Creator UID"), 1, SCOLS_FL_RIGHT},
	[COL_CUSER]     = { "CUSER",    N_("Creator user"), N_("Creator user"), 1 },
	[COL_CGID]	= { "CGID",	N_("Creator GID"), N_("Creator GID"), 1, SCOLS_FL_RIGHT},
	[COL_CGROUP]    = { "CGROUP",   N_("Creator group"), N_("Creator group"), 1 },
	[COL_UID]	= { "UID",	N_("User ID"), N_("UID"), 1, SCOLS_FL_RIGHT},
	[COL_USER]	= { "USER",	N_("User name"), N_("User name"), 1},
	[COL_GID]	= { "GID",	N_("Group ID"), N_("GID"), 1, SCOLS_FL_RIGHT},
	[COL_GROUP]	= { "GROUP",	N_("Group name"), N_("Group name"), 1},
	[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_COMMAND]	= { "COMMAND",  N_("Creator command line"), N_("Creator command"), 0, SCOLS_FL_TRUNC},
	[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, SCOLS_FL_RIGHT },
	[COL_USEPERC]	= { "USE%",     N_("Currently use percentage"), N_("Use"), 1, SCOLS_FL_RIGHT },
	[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 size_t 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 get_column_id(int num)
{
	assert(num >= 0);
	assert((size_t) num < ncolumns);
	assert((size_t) columns[num] < ARRAY_SIZE(coldescs));
	return columns[num];
}

static const struct lsipc_coldesc *get_column_desc(int num)
{
	return &coldescs[ get_column_id(num) ];
}

static char *get_username(struct passwd **pw, uid_t id)
{
	if (!*pw || (*pw)->pw_uid != id)
		*pw = getpwuid(id);

	return *pw ? xstrdup((*pw)->pw_name) : NULL;
}

static char *get_groupname(struct group **gr, gid_t id)
{
	if (!*gr || (*gr)->gr_gid != id)
		*gr = getgrgid(id);

	return *gr ? xstrdup((*gr)->gr_name) : NULL;
}

static int parse_time_mode(const char *s)
{
	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, s) == 0)
			return timefmts[i].val;
	}
	errx(EXIT_FAILURE, _("unknown time format: %s"), s);
}

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(_("Show information on IPC facilities.\n"), 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(_(" -g, --global      info about system-wide usage (may be used with -m, -q and -s)\n"), out);
	fputs(_(" -i, --id <id>     print details on resource identified by <id>\n"), out);

	fputs(USAGE_OPTIONS, 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(_(" -J, --json               use the JSON output format\n"), out);
	fputs(_(" -n, --newline            display each piece of information on a new line\n"), out);
	fputs(_(" -l, --list               force list output format (for example with --id)\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(USAGE_SEPARATOR, out);
	printf(USAGE_HELP_OPTIONS(26));

	fprintf(out, _("\nGeneric columns:\n"));
	for (i = COLDESC_IDX_GEN_FIRST; i <= COLDESC_IDX_GEN_LAST; i++)
		fprintf(out, " %14s  %s\n", coldescs[i].name, _(coldescs[i].help));

	fprintf(out, _("\nShared-memory columns (--shmems):\n"));
	for (i = COLDESC_IDX_SHM_FIRST; i <= COLDESC_IDX_SHM_LAST; i++)
		fprintf(out, " %14s  %s\n", coldescs[i].name, _(coldescs[i].help));

	fprintf(out, _("\nMessage-queue columns (--queues):\n"));
	for (i = COLDESC_IDX_MSG_FIRST; i <= COLDESC_IDX_MSG_LAST; i++)
		fprintf(out, " %14s  %s\n", coldescs[i].name, _(coldescs[i].help));

	fprintf(out, _("\nSemaphore columns (--semaphores):\n"));
	for (i = COLDESC_IDX_SEM_FIRST; i <= COLDESC_IDX_SEM_LAST; i++)
		fprintf(out, " %14s  %s\n", coldescs[i].name, _(coldescs[i].help));

	fprintf(out, _("\nSummary columns (--global):\n"));
	for (i = COLDESC_IDX_SUM_FIRST; i <= COLDESC_IDX_SUM_LAST; i++)
		fprintf(out, " %14s  %s\n", coldescs[i].name, _(coldescs[i].help));

	printf(USAGE_MAN_TAIL("lsipc(1)"));
	exit(EXIT_SUCCESS);
}

static struct libscols_table *new_table(struct lsipc_control *ctl)
{
	struct libscols_table *table = scols_new_table();

	if (!table)
		err(EXIT_FAILURE, _("failed to allocate output table"));

	if (ctl->noheadings)
		scols_table_enable_noheadings(table, 1);

	switch(ctl->outmode) {
	case OUT_NEWLINE:
		scols_table_set_column_separator(table, "\n");
		/* fallthrough */
	case OUT_EXPORT:
		scols_table_enable_export(table, 1);
		break;
	case OUT_RAW:
		scols_table_enable_raw(table, 1);
		break;
	case OUT_PRETTY:
		scols_table_enable_noheadings(table, 1);
		break;
	case OUT_JSON:
		scols_table_enable_json(table, 1);
		break;
	default:
		break;
	}
	return table;
}

static struct libscols_table *setup_table(struct lsipc_control *ctl)
{
	struct libscols_table *table = new_table(ctl);
	size_t n;

	for (n = 0; n < ncolumns; n++) {
		const struct lsipc_coldesc *desc = get_column_desc(n);
		int flags = desc->flag;

		if (ctl->notrunc)
			flags &= ~SCOLS_FL_TRUNC;
		if (!scols_table_new_column(table, desc->name, desc->whint, flags))
			goto fail;
	}
	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;
	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_(get_column_desc(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) {
		struct libscols_table *subtab = scols_line_get_userdata(ln);
		if (subtab) {
			printf(_("Elements:\n\n"));
			scols_print_table(subtab);
		}
	}

	scols_free_iter(itr);
	return 0;

}

static int print_table(struct lsipc_control *ctl, struct libscols_table *tb)
{
	if (ctl->outmode == OUT_PRETTY)
		print_pretty(tb);
	else
		scols_print_table(tb);
	return 0;
}
static struct timeval now;

static char *make_time(int mode, time_t time)
{
	char buf[64] = {0};

	switch(mode) {
	case TIME_FULL:
	{
		struct tm tm;
		char *s;

		localtime_r(&time, &tm);
		asctime_r(&tm, buf);
		if (*(s = buf + strlen(buf) - 1) == '\n')
			*s = '\0';
		break;
	}
	case TIME_SHORT:
		strtime_short(&time, &now, 0, buf, sizeof(buf));
		break;
	case TIME_ISO:
		strtime_iso(&time, ISO_TIMESTAMP_T, buf, sizeof(buf));
		break;
	default:
		errx(EXIT_FAILURE, _("unsupported time type"));
	}
	return xstrdup(buf);
}

static void global_set_data(struct libscols_table *tb, const char *resource,
			    const char *desc, uintmax_t used, uintmax_t limit, int usage)
{
	struct libscols_line *ln;
	size_t n;

	ln = scols_table_new_line(tb, NULL);
	if (!ln)
		err(EXIT_FAILURE, _("failed to allocate output line"));

	for (n = 0; n < ncolumns; n++) {
		int rc = 0;
		char *arg = NULL;

		switch (get_column_id(n)) {
		case COL_RESOURCE:
			rc = scols_line_set_data(ln, n, resource);
			break;
		case COL_DESC:
			rc = scols_line_set_data(ln, n, desc);
			break;
		case COL_USED:
			if (usage) {
				xasprintf(&arg, "%ju", used);
				rc = scols_line_refer_data(ln, n, arg);
			} else
				rc = scols_line_set_data(ln, n, "-");
			break;
		case COL_USEPERC:
			if (usage) {
				xasprintf(&arg, "%2.2f%%", (double) used / limit * 100);
				rc = scols_line_refer_data(ln, n, arg);
			} else
				rc = scols_line_set_data(ln, n, "-");
			break;
		case COL_LIMIT:
			xasprintf(&arg, "%ju", limit);
			rc = scols_line_refer_data(ln, n, arg);
			break;
		}

		if (rc != 0)
			err(EXIT_FAILURE, _("failed to add output data"));
	}
}

static void setup_sem_elements_columns(struct libscols_table *tb)
{
	scols_table_set_name(tb, "elements");
	if (!scols_table_new_column(tb, "SEMNUM", 0, SCOLS_FL_RIGHT))
		err_oom();
	if (!scols_table_new_column(tb, "VALUE", 0, SCOLS_FL_RIGHT))
		err_oom();
	if (!scols_table_new_column(tb, "NCOUNT", 0, SCOLS_FL_RIGHT))
		err_oom();
	if (!scols_table_new_column(tb, "ZCOUNT", 0, SCOLS_FL_RIGHT))
		err_oom();
	if (!scols_table_new_column(tb, "PID", 0, SCOLS_FL_RIGHT))
		err_oom();
	if (!scols_table_new_column(tb, "COMMAND", 0, SCOLS_FL_RIGHT))
		err_oom();
}

static void do_sem(int id, struct lsipc_control *ctl, struct libscols_table *tb)
{
	struct libscols_line *ln;
	struct passwd *pw = NULL, *cpw = NULL;
	struct group *gr = NULL, *cgr = NULL;
	struct sem_data *semds, *semdsp;
	char *arg = NULL;

	scols_table_set_name(tb, "semaphores");

	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) {
		size_t n;

		ln = scols_table_new_line(tb, NULL);
		if (!ln)
			err(EXIT_FAILURE, _("failed to allocate output line"));

		for (n = 0; n < ncolumns; n++) {
			int rc = 0;
			switch (get_column_id(n)) {
			case COL_KEY:
				xasprintf(&arg, "0x%08x",semdsp->sem_perm.key);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_ID:
				xasprintf(&arg, "%d",semdsp->sem_perm.id);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_OWNER:
				arg = get_username(&pw, semdsp->sem_perm.uid);
				if (!arg)
					xasprintf(&arg, "%u", semdsp->sem_perm.uid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_PERMS:
				if (ctl->numperms)
					xasprintf(&arg, "%#o", semdsp->sem_perm.mode & 0777);
				else {
					arg = xmalloc(11);
					xstrmode(semdsp->sem_perm.mode & 0777, arg);
				}
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CUID:
				xasprintf(&arg, "%u", semdsp->sem_perm.cuid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CUSER:
				arg = get_username(&cpw, semdsp->sem_perm.cuid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CGID:
				xasprintf(&arg, "%u", semdsp->sem_perm.cgid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CGROUP:
				arg = get_groupname(&cgr, semdsp->sem_perm.cgid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_UID:
				xasprintf(&arg, "%u", semdsp->sem_perm.uid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_USER:
				arg = get_username(&pw, semdsp->sem_perm.uid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_GID:
				xasprintf(&arg, "%u", semdsp->sem_perm.gid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_GROUP:
				arg = get_groupname(&gr, semdsp->sem_perm.gid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CTIME:
				if (semdsp->sem_ctime != 0) {
					rc = scols_line_refer_data(ln, n,
							make_time(ctl->time_mode,
							  (time_t)semdsp->sem_ctime));
				}
				break;
			case COL_NSEMS:
				xasprintf(&arg, "%ju", semdsp->sem_nsems);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_OTIME:
				if (semdsp->sem_otime != 0) {
					rc = scols_line_refer_data(ln, n,
							make_time(ctl->time_mode,
							  (time_t)semdsp->sem_otime));
				}
				break;
			}
			if (rc != 0)
				err(EXIT_FAILURE, _("failed to add output data"));
			arg = NULL;
		}

		if (id > -1 && semds->sem_nsems) {
			/* Create extra table with ID specific semaphore elements */
			struct libscols_table *sub = new_table(ctl);
			size_t i;
			int rc = 0;

			scols_table_enable_noheadings(sub, 0);
			setup_sem_elements_columns(sub);

			for (i = 0; i < semds->sem_nsems; i++) {
				struct sem_elem *e = &semds->elements[i];
				struct libscols_line *sln = scols_table_new_line(sub, NULL);

				if (!sln)
					err(EXIT_FAILURE, _("failed to allocate output line"));

				/* SEMNUM */
				xasprintf(&arg, "%zu", i);
				rc = scols_line_refer_data(sln, 0, arg);
				if (rc)
					break;

				/* VALUE */
				xasprintf(&arg, "%d", e->semval);
				rc = scols_line_refer_data(sln, 1, arg);
				if (rc)
					break;

				/* NCOUNT */
				xasprintf(&arg, "%d", e->ncount);
				rc = scols_line_refer_data(sln, 2, arg);
				if (rc)
					break;

				/* ZCOUNT */
				xasprintf(&arg, "%d", e->zcount);
				rc = scols_line_refer_data(sln, 3, arg);
				if (rc)
					break;

				/* PID */
				xasprintf(&arg, "%d", e->pid);
				rc = scols_line_refer_data(sln, 4, arg);
				if (rc)
					break;

				/* COMMAND */
				arg = proc_get_command(e->pid);
				rc = scols_line_refer_data(sln, 5, arg);
				if (rc)
					break;
			}

			if (rc != 0)
				err(EXIT_FAILURE, _("failed to set data"));

			scols_line_set_userdata(ln, (void *)sub);
			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 nsems = 0, nsets = 0;

	ipc_sem_get_limits(&lim);

	if (ipc_sem_get_info(-1, &semds) > 0) {
		for (semdsp = semds; semdsp->next != NULL; semdsp = semdsp->next) {
			++nsets;
			nsems += semds->sem_nsems;
		}
		ipc_sem_free_info(semds);
	}

	global_set_data(tb, "SEMMNI", _("Number of semaphore identifiers"), nsets, lim.semmni, 1);
	global_set_data(tb, "SEMMNS", _("Total number of semaphores"), nsems, lim.semmns, 1);
	global_set_data(tb, "SEMMSL", _("Max semaphores per semaphore set."), 0, lim.semmsl, 0);
	global_set_data(tb, "SEMOPM", _("Max number of operations per semop(2)"), 0, lim.semopm, 0);
	global_set_data(tb, "SEMVMX", _("Semaphore max value"), 0, lim.semvmx, 0);
}

static void do_msg(int id, struct lsipc_control *ctl, struct libscols_table *tb)
{
	struct libscols_line *ln;
	struct passwd *pw = NULL;
	struct group *gr = NULL;
	struct msg_data *msgds, *msgdsp;
	char *arg = NULL;

	if (ipc_msg_get_info(id, &msgds) < 1) {
		if (id > -1)
			warnx(_("id %d not found"), id);
		return;
	}
	scols_table_set_name(tb, "messages");

	for (msgdsp = msgds; msgdsp->next != NULL || id > -1 ; msgdsp = msgdsp->next) {
		size_t n;
		ln = scols_table_new_line(tb, NULL);

		if (!ln)
			err(EXIT_FAILURE, _("failed to allocate output line"));

		/* 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);

		for (n = 0; n < ncolumns; n++) {
			int rc = 0;

			switch (get_column_id(n)) {
			case COL_KEY:
				xasprintf(&arg, "0x%08x",msgdsp->msg_perm.key);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_ID:
				xasprintf(&arg, "%d",msgdsp->msg_perm.id);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_OWNER:
				arg = get_username(&pw, msgdsp->msg_perm.uid);
				if (!arg)
					xasprintf(&arg, "%u", msgdsp->msg_perm.uid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_PERMS:
				if (ctl->numperms)
					xasprintf(&arg, "%#o", msgdsp->msg_perm.mode & 0777);
				else {
					arg = xmalloc(11);
					xstrmode(msgdsp->msg_perm.mode & 0777, arg);
					rc = scols_line_refer_data(ln, n, arg);
				}
				break;
			case COL_CUID:
				xasprintf(&arg, "%u", msgdsp->msg_perm.cuid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CUSER:
				arg = get_username(&pw, msgdsp->msg_perm.cuid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CGID:
				xasprintf(&arg, "%u", msgdsp->msg_perm.cuid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CGROUP:
				arg = get_groupname(&gr, msgdsp->msg_perm.cgid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_UID:
				xasprintf(&arg, "%u", msgdsp->msg_perm.uid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_USER:
				arg = get_username(&pw, msgdsp->msg_perm.uid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_GID:
				xasprintf(&arg, "%u", msgdsp->msg_perm.gid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_GROUP:
				arg = get_groupname(&gr,msgdsp->msg_perm.gid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CTIME:
				if (msgdsp->q_ctime != 0)
					rc = scols_line_refer_data(ln, n,
						make_time(ctl->time_mode,
							  (time_t)msgdsp->q_ctime));
				break;
			case COL_USEDBYTES:
				xasprintf(&arg, "%ju", msgdsp->q_cbytes);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_MSGS:
				xasprintf(&arg, "%ju", msgdsp->q_qnum);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_SEND:
				if (msgdsp->q_stime != 0)
					rc = scols_line_refer_data(ln, n,
						make_time(ctl->time_mode,
							  (time_t)msgdsp->q_stime));
				break;
			case COL_RECV:
				if (msgdsp->q_rtime != 0)
					rc = scols_line_refer_data(ln, n,
						make_time(ctl->time_mode,
							  (time_t)msgdsp->q_rtime));
				break;
			case COL_LSPID:
				xasprintf(&arg, "%u", msgdsp->q_lspid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_LRPID:
				xasprintf(&arg, "%u", msgdsp->q_lrpid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			}
			if (rc != 0)
				err(EXIT_FAILURE, _("failed to set data"));
			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;
	int msgqs = 0;

	ipc_msg_get_limits(&lim);

	/* count number of used queues */
	if (ipc_msg_get_info(-1, &msgds) > 0) {
		for (msgdsp = msgds; msgdsp->next != NULL; msgdsp = msgdsp->next)
			++msgqs;
		ipc_msg_free_info(msgds);
	}

	global_set_data(tb, "MSGMNI", _("Number of message queues"), msgqs, lim.msgmni, 1);
	global_set_data(tb, "MSGMAX", _("Max size of message (bytes)"),	0, lim.msgmax, 0);
	global_set_data(tb, "MSGMNB", _("Default max size of queue (bytes)"), 0, lim.msgmnb, 0);
}


static void do_shm(int id, struct lsipc_control *ctl, struct libscols_table *tb)
{
	struct libscols_line *ln;
	struct passwd *pw = NULL;
	struct group *gr = NULL;
	struct shm_data *shmds, *shmdsp;
	char *arg = NULL;

	if (ipc_shm_get_info(id, &shmds) < 1) {
		if (id > -1)
			warnx(_("id %d not found"), id);
		return;
	}

	scols_table_set_name(tb, "sharedmemory");

	for (shmdsp = shmds; shmdsp->next != NULL || id > -1 ; shmdsp = shmdsp->next) {
		size_t n;
		ln = scols_table_new_line(tb, NULL);

		if (!ln)
			err(EXIT_FAILURE, _("failed to allocate output line"));

		for (n = 0; n < ncolumns; n++) {
			int rc = 0;

			switch (get_column_id(n)) {
			case COL_KEY:
				xasprintf(&arg, "0x%08x",shmdsp->shm_perm.key);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_ID:
				xasprintf(&arg, "%d",shmdsp->shm_perm.id);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_OWNER:
				arg = get_username(&pw, shmdsp->shm_perm.uid);
				if (!arg)
					xasprintf(&arg, "%u", shmdsp->shm_perm.uid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_PERMS:
				if (ctl->numperms)
					xasprintf(&arg, "%#o", shmdsp->shm_perm.mode & 0777);
				else {
					arg = xmalloc(11);
					xstrmode(shmdsp->shm_perm.mode & 0777, arg);
				}
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CUID:
				xasprintf(&arg, "%u", shmdsp->shm_perm.cuid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CUSER:
				arg = get_username(&pw, shmdsp->shm_perm.cuid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CGID:
				xasprintf(&arg, "%u", shmdsp->shm_perm.cuid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CGROUP:
				arg = get_groupname(&gr, shmdsp->shm_perm.cgid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_UID:
				xasprintf(&arg, "%u", shmdsp->shm_perm.uid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_USER:
				arg = get_username(&pw, shmdsp->shm_perm.uid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_GID:
				xasprintf(&arg, "%u", shmdsp->shm_perm.gid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_GROUP:
				arg = get_groupname(&gr, shmdsp->shm_perm.gid);
				if (arg)
					rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_CTIME:
				if (shmdsp->shm_ctim != 0)
					rc = scols_line_refer_data(ln, n,
						make_time(ctl->time_mode,
							  (time_t)shmdsp->shm_ctim));
				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_refer_data(ln, n, arg);
				break;
			case COL_NATTCH:
				xasprintf(&arg, "%ju", shmdsp->shm_nattch);
				rc = scols_line_refer_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);
#ifdef SHM_DEST
					if (shmdsp->shm_perm.mode & SHM_DEST) {
						offt += sprintf(arg, "%s", _("dest"));
						comma++;
					}
#endif
#ifdef SHM_LOCKED
					if (shmdsp->shm_perm.mode & SHM_LOCKED) {
						if (comma)
							arg[offt++] = ',';
						offt += sprintf(arg + offt, "%s", _("locked"));
					}
#endif
#ifdef SHM_HUGETLB
					if (shmdsp->shm_perm.mode & SHM_HUGETLB) {
						if (comma)
							arg[offt++] = ',';
						offt += sprintf(arg + offt, "%s", _("hugetlb"));
					}
#endif
#ifdef SHM_NORESERVE
					if (shmdsp->shm_perm.mode & SHM_NORESERVE) {
						if (comma)
							arg[offt++] = ',';
						sprintf(arg + offt, "%s", _("noreserve"));
					}
#endif
					rc = scols_line_refer_data(ln, n, arg);
				}
				break;
			case COL_ATTACH:
				if (shmdsp->shm_atim != 0)
					rc = scols_line_refer_data(ln, n,
							make_time(ctl->time_mode,
							  (time_t)shmdsp->shm_atim));
				break;
			case COL_DETACH:
				if (shmdsp->shm_dtim != 0)
					rc = scols_line_refer_data(ln, n,
							make_time(ctl->time_mode,
							  (time_t)shmdsp->shm_dtim));
				break;
			case COL_CPID:
				xasprintf(&arg, "%u", shmdsp->shm_cprid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_LPID:
				xasprintf(&arg, "%u", shmdsp->shm_lprid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			case COL_COMMAND:
				arg = proc_get_command(shmdsp->shm_cprid);
				rc = scols_line_refer_data(ln, n, arg);
				break;
			}
			if (rc != 0)
				err(EXIT_FAILURE, _("failed to set data"));
			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;
	uint64_t nsegs = 0, sum_segsz = 0;
	struct ipc_limits lim;

	ipc_shm_get_limits(&lim);

	if (ipc_shm_get_info(-1, &shmds) > 0) {
		for (shmdsp = shmds; shmdsp->next != NULL; shmdsp = shmdsp->next) {
			++nsegs;
			sum_segsz += shmdsp->shm_segsz;
		}
		ipc_shm_free_info(shmds);
	}

	global_set_data(tb, "SHMMNI", _("Shared memory segments"), nsegs, lim.shmmni, 1);
	global_set_data(tb, "SHMALL", _("Shared memory pages"), sum_segsz / getpagesize(), lim.shmall, 1);
	global_set_data(tb, "SHMMAX", _("Max size of shared memory segment (bytes)"), 0, lim.shmmax, 0);
	global_set_data(tb, "SHMMIN", _("Min size of shared memory segment (bytes)"), 0, lim.shmmin, 0);
}

int main(int argc, char *argv[])
{
	int opt, msg = 0, sem = 0, shm = 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 *outarg = NULL;

	/* long only options. */
	enum {
		OPT_NOTRUNC = CHAR_MAX + 1,
		OPT_NOHEAD,
		OPT_TIME_FMT
	};

	static const struct option longopts[] = {
		{ "bytes",          no_argument,        NULL, 'b' },
		{ "creator",        no_argument,	NULL, 'c' },
		{ "export",         no_argument,	NULL, 'e' },
		{ "global",         no_argument,	NULL, 'g' },
		{ "help",           no_argument,	NULL, 'h' },
		{ "id",             required_argument,	NULL, 'i' },
		{ "json",           no_argument,	NULL, 'J' },
		{ "list",           no_argument,        NULL, 'l' },
		{ "newline",        no_argument,	NULL, 'n' },
		{ "noheadings",     no_argument,	NULL, OPT_NOHEAD },
		{ "notruncate",     no_argument,	NULL, OPT_NOTRUNC },
		{ "numeric-perms",  no_argument,	NULL, 'P' },
		{ "output",         required_argument,	NULL, 'o' },
		{ "queues",         no_argument,	NULL, 'q' },
		{ "raw",            no_argument,	NULL, 'r' },
		{ "semaphores",     no_argument,	NULL, 's' },
		{ "shmems",         no_argument,	NULL, 'm' },
		{ "time",           no_argument,	NULL, 't' },
		{ "time-format",    required_argument,	NULL, OPT_TIME_FMT },
		{ "version",        no_argument,	NULL, 'V' },
		{NULL, 0, NULL, 0}
	};

	static const ul_excl_t excl[] = {	/* rows and cols in ASCII order */
		{ 'J', 'e', 'l', 'n', 'r' },
		{ 'g', 'i' },
		{ 'c', '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 = 0;

	scols_init_debug(0);

	while ((opt = getopt_long(argc, argv, "bceghi:Jlmno:PqrstV", longopts, NULL)) != -1) {

		err_exclusive_options(opt, longopts, excl, excl_st);

		switch (opt) {
		case 'b':
			ctl->bytes = 1;
			break;
		case 'i':
			id = strtos32_or_err(optarg, _("failed to parse IPC identifier"));
			break;
		case 'e':
			ctl->outmode = OUT_EXPORT;
			break;
		case 'r':
			ctl->outmode = OUT_RAW;
			break;
		case 'o':
			outarg = optarg;
			break;
		case 'g':
			global = 1;
			break;
		case 'q':
			msg = 1;
			add_column(columns, ncolumns++, COL_KEY);
			add_column(columns, ncolumns++, COL_ID);
			add_column(columns, ncolumns++, COL_PERMS);
			add_column(columns, ncolumns++, COL_OWNER);
			add_column(columns, ncolumns++, COL_USEDBYTES);
			add_column(columns, ncolumns++, COL_MSGS);
			add_column(columns, ncolumns++, COL_LSPID);
			add_column(columns, ncolumns++, COL_LRPID);
			LOWER = COLDESC_IDX_MSG_FIRST;
			UPPER = COLDESC_IDX_MSG_LAST;
			break;
		case 'l':
			ctl->outmode = OUT_LIST;
			break;
		case 'm':
			shm = 1;
			add_column(columns, ncolumns++, COL_KEY);
			add_column(columns, ncolumns++, COL_ID);
			add_column(columns, ncolumns++, COL_PERMS);
			add_column(columns, ncolumns++, COL_OWNER);
			add_column(columns, ncolumns++, COL_SIZE);
			add_column(columns, ncolumns++, COL_NATTCH);
			add_column(columns, ncolumns++, COL_STATUS);
			add_column(columns, ncolumns++, COL_CTIME);
			add_column(columns, ncolumns++, COL_CPID);
			add_column(columns, ncolumns++, COL_LPID);
			add_column(columns, ncolumns++, COL_COMMAND);
			LOWER = COLDESC_IDX_SHM_FIRST;
			UPPER = COLDESC_IDX_SHM_LAST;
			break;
		case 'n':
			ctl->outmode = OUT_NEWLINE;
			break;
		case 'P':
			ctl->numperms = 1;
			break;
		case 's':
			sem = 1;
			add_column(columns, ncolumns++, COL_KEY);
			add_column(columns, ncolumns++, COL_ID);
			add_column(columns, ncolumns++, COL_PERMS);
			add_column(columns, ncolumns++, COL_OWNER);
			add_column(columns, ncolumns++, COL_NSEMS);
			LOWER = COLDESC_IDX_SEM_FIRST;
			UPPER = COLDESC_IDX_SEM_LAST;
			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->outmode = OUT_JSON;
			break;
		case 't':
			show_time = 1;
			break;
		case 'c':
			show_creat = 1;
			break;

		case 'h':
			usage();
		case 'V':
			print_version(EXIT_SUCCESS);
		default:
			errtryhelp(EXIT_FAILURE);
		}
	}

	/* default is global */
	if (msg + shm + sem == 0) {
		msg = shm = sem = global = 1;
		if (show_time || show_creat || id != -1)
			errx(EXIT_FAILURE, _("--global is mutually exclusive with --creator, --id and --time"));
	}
	if (global) {
		add_column(columns, ncolumns++, COL_RESOURCE);
		add_column(columns, ncolumns++, COL_DESC);
		add_column(columns, ncolumns++, COL_LIMIT);
		add_column(columns, ncolumns++, COL_USED);
		add_column(columns, ncolumns++, COL_USEPERC);
		LOWER = COLDESC_IDX_SUM_FIRST;
		UPPER = COLDESC_IDX_SUM_LAST;
	}

	/* default to pretty-print if --id specified */
	if (id != -1 && !ctl->outmode)
		ctl->outmode = OUT_PRETTY;

	if (!ctl->time_mode)
		ctl->time_mode = ctl->outmode == OUT_PRETTY ? TIME_FULL : TIME_SHORT;

	if (ctl->outmode == OUT_PRETTY && !(optarg || show_creat || show_time)) {
		/* all columns for lsipc --<RESOURCE> --id <ID> */
		for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++)
			 columns[ncolumns++] = i;
	} else {
		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 && show_time) {
			add_column(columns, ncolumns++, COL_SEND);
			add_column(columns, ncolumns++, COL_RECV);
			add_column(columns, ncolumns++, COL_CTIME);
		}
		if (shm && show_time) {
			/* keep "COMMAND" as last column */
			size_t cmd = columns[ncolumns - 1] == COL_COMMAND;

			if (cmd)
				ncolumns--;
			add_column(columns, ncolumns++, COL_ATTACH);
			add_column(columns, ncolumns++, COL_DETACH);
			if (cmd)
				add_column(columns, ncolumns++, COL_COMMAND);
		}
		if (sem && show_time) {
			add_column(columns, ncolumns++, COL_OTIME);
			add_column(columns, ncolumns++, COL_CTIME);
		}
	}

	if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
					 &ncolumns, column_name_to_id) < 0)
		return EXIT_FAILURE;

	tb = setup_table(ctl);
	if (!tb)
		return EXIT_FAILURE;

	if (global)
		scols_table_set_name(tb, "ipclimits");

	if (msg) {
		if (global)
			do_msg_global(tb);
		else
			do_msg(id, ctl, tb);
	}
	if (shm) {
		if (global)
			do_shm_global(tb);
		else
			do_shm(id, ctl, tb);
	}
	if (sem) {
		if (global)
			do_sem_global(tb);
		else
			do_sem(id, ctl, tb);
	}

	print_table(ctl, tb);

	scols_unref_table(tb);
	free(ctl);

	return EXIT_SUCCESS;
}

