blob: e8ada571e784aa7f214153e28298dca4807736df [file] [log] [blame]
/*
* 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':
printf(UTIL_LINUX_VERSION);
return 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;
}