util-linux/sys-utils/lsipc.c
Sami Kerola d0355b2e90
lsipc: make default output byte sizes to be in human units
Recent request to make ipcs(1) list sizes in human format caused the
observation lsipc(1) is not doing that either.  This commit changes sizes to
human format, assuming --bytes option is not used.

Reference: https://github.com/karelzak/util-linux/issues/1199
Signed-off-by: Sami Kerola <kerolasa@iki.fi>
2020-12-28 09:53:13 +00:00

1348 lines
37 KiB
C

/*
* 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;
warnx(_("column %s does not apply to the specified IPC"), name);
return -1;
}
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 lsipc_control *ctl, struct libscols_table *tb,
const char *resource, const char *desc, uintmax_t used,
uintmax_t limit, int usage, int byte_unit)
{
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) {
if (!byte_unit || ctl->bytes)
xasprintf(&arg, "%ju", used);
else
arg = size_to_human_string(SIZE_SUFFIX_1LETTER, 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:
if (!byte_unit || ctl->bytes)
xasprintf(&arg, "%ju", limit);
else
arg = size_to_human_string(SIZE_SUFFIX_1LETTER, 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 lsipc_control *ctl, 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(ctl, tb, "SEMMNI", _("Number of semaphore identifiers"), nsets, lim.semmni, 1, 0);
global_set_data(ctl, tb, "SEMMNS", _("Total number of semaphores"), nsems, lim.semmns, 1, 0);
global_set_data(ctl, tb, "SEMMSL", _("Max semaphores per semaphore set."), 0, lim.semmsl, 0, 0);
global_set_data(ctl, tb, "SEMOPM", _("Max number of operations per semop(2)"), 0, lim.semopm, 0, 0);
global_set_data(ctl, tb, "SEMVMX", _("Semaphore max value"), 0, lim.semvmx, 0, 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:
if (ctl->bytes)
xasprintf(&arg, "%ju", msgdsp->q_cbytes);
else
arg = size_to_human_string(SIZE_SUFFIX_1LETTER, 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 lsipc_control *ctl, 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(ctl, tb, "MSGMNI", _("Number of message queues"), msgqs, lim.msgmni, 1, 0);
global_set_data(ctl, tb, "MSGMAX", _("Max size of message (bytes)"), 0, lim.msgmax, 0, 1);
global_set_data(ctl, tb, "MSGMNB", _("Default max size of queue (bytes)"), 0, lim.msgmnb, 0, 1);
}
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 lsipc_control *ctl, 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(ctl, tb, "SHMMNI", _("Shared memory segments"), nsegs, lim.shmmni, 1, 0);
global_set_data(ctl, tb, "SHMALL", _("Shared memory pages"), sum_segsz / getpagesize(), lim.shmall, 1, 0);
global_set_data(ctl, tb, "SHMMAX", _("Max size of shared memory segment (bytes)"), 0, lim.shmmax, 0, 1);
global_set_data(ctl, tb, "SHMMIN", _("Min size of shared memory segment (bytes)"), 0, lim.shmmin, 0, 1);
}
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);
close_stdout_atexit();
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(ctl, tb);
else
do_msg(id, ctl, tb);
}
if (shm) {
if (global)
do_shm_global(ctl ,tb);
else
do_shm(id, ctl, tb);
}
if (sem) {
if (global)
do_sem_global(ctl, tb);
else
do_sem(id, ctl, tb);
}
print_table(ctl, tb);
scols_unref_table(tb);
free(ctl);
return EXIT_SUCCESS;
}