From 9f643efe377d2a39929f19cc09e8890afc74d9a4 Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Fri, 24 Jun 2016 12:57:13 +0200 Subject: [PATCH 78/84] lsipc: backport new command Addresses: http://bugzilla.redhat.com/show_bug.cgi?id=1153770 Signed-off-by: Karel Zak --- bash-completion/lsipc | 64 +++ configure.ac | 6 + include/xalloc.h | 7 + sys-utils/Makemodule.am | 10 + sys-utils/ipcs.c | 2 +- sys-utils/ipcutils.c | 116 ++--- sys-utils/ipcutils.h | 13 +- sys-utils/lsipc.1 | 133 +++++ sys-utils/lsipc.c | 1316 +++++++++++++++++++++++++++++++++++++++++++++++ tests/functions.sh | 16 +- tests/ts/ipcs/limits2 | 9 +- 11 files changed, 1613 insertions(+), 79 deletions(-) create mode 100644 bash-completion/lsipc create mode 100644 sys-utils/lsipc.1 create mode 100644 sys-utils/lsipc.c diff --git a/bash-completion/lsipc b/bash-completion/lsipc new file mode 100644 index 0000000..6a87393 --- /dev/null +++ b/bash-completion/lsipc @@ -0,0 +1,64 @@ +_lsipc_module() +{ + local cur prev OPTS ARG + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + case $prev in + '-i'|'--id') + COMPREPLY=( $(compgen -W "id" -- $cur) ) + return 0 + ;; + '-h'|'--help'|'-V'|'--version') + return 0 + ;; + '-o'|'--output') + local prefix realcur OUTPUT_ALL OUTPUT + realcur="${cur##*,}" + prefix="${cur%$realcur}" + OUTPUT_ALL="GENERAL KEY ID OWNER PERMS CUID + CGID UID GID CHANGE MESSAGE USEDBYTES + MSGS SEND RECV LSPID LRPID SHARED BYTES + NATTCH STATUS ATTACH DETACH CPID LPID NSEMS + LASTOP" + for WORD in $OUTPUT_ALL; do + if ! [[ $prefix == *"$WORD"* ]]; then + OUTPUT="$WORD $OUTPUT" + fi + done + compopt -o nospace + COMPREPLY=( $(compgen -P "$prefix" -W "$OUTPUT" -S ',' -- $realcur) ) + return 0 + ;; + esac + case $cur in + -*) + OPTS="--id + --help + --version + --shmems + --queues + --semaphores + --colon-separate + --creator + --export + --global + --newline + --noheadings + --notruncate + --output + --pid + --print0 + --raw + --time + --time-format" + COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) ) + return 0 + ;; + esac + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $(compgen -f -- $cur) ) + return 0 +} +complete -F _lsipc_module lsipc diff --git a/configure.ac b/configure.ac index 5d9ea39..fe0a011 100644 --- a/configure.ac +++ b/configure.ac @@ -1038,6 +1038,12 @@ UL_REQUIRES_BUILD([lsns], [libsmartcols]) AM_CONDITIONAL([BUILD_LSNS], [test "x$build_lsns" = xyes]) +UL_BUILD_INIT([lsipc], [check]) +UL_REQUIRES_LINUX([lsipc]) +UL_REQUIRES_BUILD([lsipc], [libsmartcols]) +AM_CONDITIONAL([BUILD_LSIPC], [test "x$build_lsipc" = xyes]) + + UL_BUILD_INIT([chcpu], [check]) UL_REQUIRES_LINUX([chcpu]) UL_REQUIRES_HAVE([chcpu], [cpu_set_t], [cpu_set_t type]) diff --git a/include/xalloc.h b/include/xalloc.h index 6342793..1a1799a 100644 --- a/include/xalloc.h +++ b/include/xalloc.h @@ -19,6 +19,13 @@ # define XALLOC_EXIT_CODE EXIT_FAILURE #endif +static inline void __err_oom(const char *file, unsigned int line) +{ + err(XALLOC_EXIT_CODE, "%s: %u: cannot allocate memory", file, line); +} + +#define err_oom() __err_oom(__FILE__, __LINE__) + static inline __ul_alloc_size(1) void *xmalloc(const size_t size) { diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am index 9baf5a3..6badd17 100644 --- a/sys-utils/Makemodule.am +++ b/sys-utils/Makemodule.am @@ -22,6 +22,16 @@ ipcs_SOURCES = sys-utils/ipcs.c \ ipcs_LDADD = $(LDADD) libcommon.la +if BUILD_LSIPC +usrbin_exec_PROGRAMS += lsipc +dist_man_MANS += sys-utils/lsipc.1 +lsipc_SOURCES = sys-utils/lsipc.c \ + sys-utils/ipcutils.c \ + sys-utils/ipcutils.h +lsipc_LDADD = $(LDADD) libcommon.la libsmartcols.la +lsipc_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) +endif + usrbin_exec_PROGRAMS += renice dist_man_MANS += sys-utils/renice.1 renice_SOURCES = sys-utils/renice.c diff --git a/sys-utils/ipcs.c b/sys-utils/ipcs.c index 14f5f0b..1843cd5 100644 --- a/sys-utils/ipcs.c +++ b/sys-utils/ipcs.c @@ -201,7 +201,7 @@ static void do_shm (char format, int unit) _("max seg size"), lim.shmmax, "\n", 0); ipc_print_size(unit == IPC_UNIT_DEFAULT ? IPC_UNIT_KB : unit, _("max total shared memory"), - lim.shmall * getpagesize(), "\n", 0); + (uint64_t) lim.shmall * getpagesize(), "\n", 0); ipc_print_size(unit == IPC_UNIT_DEFAULT ? IPC_UNIT_BYTES : unit, _("min seg size"), lim.shmmin, "\n", 0); return; diff --git a/sys-utils/ipcutils.c b/sys-utils/ipcutils.c index 62d7428..51fce7b 100644 --- a/sys-utils/ipcutils.c +++ b/sys-utils/ipcutils.c @@ -1,4 +1,3 @@ - #include #include "c.h" @@ -54,8 +53,8 @@ int ipc_sem_get_limits(struct ipc_limits *lim) } - if (rc == 4) { - struct seminfo seminfo; + if (rc != 4) { + struct seminfo seminfo = { .semmni = 0 }; union semun arg = { .array = (ushort *) &seminfo }; if (semctl(0, 0, IPC_INFO, arg) < 0) @@ -82,12 +81,15 @@ int ipc_shm_get_limits(struct ipc_limits *lim) lim->shmmni = path_read_u64(_PATH_PROC_IPC_SHMMNI); } else { - struct shminfo shminfo; + struct shminfo *shminfo; + struct shmid_ds shmbuf; - if (shmctl(0, IPC_INFO, (struct shmid_ds *) &shminfo) < 0) + if (shmctl(0, IPC_INFO, &shmbuf) < 0) return 1; - lim->shmmni = shminfo.shmmni; - lim->shmall = shminfo.shmall; + shminfo = (struct shminfo *) &shmbuf; + lim->shmmni = shminfo->shmmni; + lim->shmall = shminfo->shmall; + lim->shmmax = shminfo->shmmax; } return 0; @@ -97,20 +99,24 @@ int ipc_shm_get_info(int id, struct shm_data **shmds) { FILE *f; int i = 0, maxid; + char buf[BUFSIZ]; struct shm_data *p; - struct shm_info dummy; + struct shmid_ds dummy; p = *shmds = xcalloc(1, sizeof(struct shm_data)); p->next = NULL; f = path_fopen("r", 0, _PATH_PROC_SYSV_SHM); if (!f) - goto fallback; + goto shm_fallback; while (fgetc(f) != '\n'); /* skip header */ - while (feof(f) == 0) { - if (fscanf(f, + while (fgets(buf, sizeof(buf), f) != NULL) { + /* scan for the first 14-16 columns (e.g. Linux 2.6.32 has 14) */ + p->shm_rss = 0xdead; + p->shm_swp = 0xdead; + if (sscanf(buf, "%d %d %o %"SCNu64 " %u %u " "%"SCNu64 " %u %u %u %u %"SCNi64 " %"SCNi64 " %"SCNi64 " %"SCNu64 " %"SCNu64 "\n", @@ -129,8 +135,8 @@ int ipc_shm_get_info(int id, struct shm_data **shmds) &p->shm_dtim, &p->shm_ctim, &p->shm_rss, - &p->shm_swp) != 16) - continue; + &p->shm_swp) < 14) + continue; /* invalid line, skipped */ if (id > -1) { /* ID specified */ @@ -153,28 +159,20 @@ int ipc_shm_get_info(int id, struct shm_data **shmds) return i; /* Fallback; /proc or /sys file(s) missing. */ -fallback: - i = id < 0 ? 0 : id; - - maxid = shmctl(0, SHM_INFO, (struct shmid_ds *) &dummy); - if (maxid < 0) - return 0; +shm_fallback: + maxid = shmctl(0, SHM_INFO, &dummy); - while (i <= maxid) { + for (int j = 0; j <= maxid; j++) { int shmid; struct shmid_ds shmseg; struct ipc_perm *ipcp = &shmseg.shm_perm; - shmid = shmctl(i, SHM_STAT, &shmseg); - if (shmid < 0) { - if (-1 < id) { - free(*shmds); - return 0; - } - i++; + shmid = shmctl(j, SHM_STAT, &shmseg); + if (shmid < 0 || (id > -1 && shmid != id)) { continue; } + i++; p->shm_perm.key = ipcp->KEY; p->shm_perm.id = shmid; p->shm_perm.mode = ipcp->mode; @@ -196,11 +194,12 @@ fallback: p->next = xcalloc(1, sizeof(struct shm_data)); p = p->next; p->next = NULL; - i++; } else - return 1; + break; } + if (i == 0) + free(*shmds); return i; } @@ -299,30 +298,22 @@ int ipc_sem_get_info(int id, struct sem_data **semds) return i; /* Fallback; /proc or /sys file(s) missing. */ - sem_fallback: - i = id < 0 ? 0 : id; - +sem_fallback: arg.array = (ushort *) (void *)&dummy; maxid = semctl(0, 0, SEM_INFO, arg); - if (maxid < 0) - return 0; - while (i <= maxid) { + for (int j = 0; j <= maxid; j++) { int semid; struct semid_ds semseg; struct ipc_perm *ipcp = &semseg.sem_perm; arg.buf = (struct semid_ds *)&semseg; - semid = semctl(i, 0, SEM_STAT, arg); - if (semid < 0) { - if (-1 < id) { - free(*semds); - return 0; - } - i++; + semid = semctl(j, 0, SEM_STAT, arg); + if (semid < 0 || (id > -1 && semid != id)) { continue; } + i++; p->sem_perm.key = ipcp->KEY; p->sem_perm.id = semid; p->sem_perm.mode = ipcp->mode; @@ -341,10 +332,12 @@ int ipc_sem_get_info(int id, struct sem_data **semds) i++; } else { get_sem_elements(p); - return 1; + break; } } + if (i == 0) + free(*semds); return i; } @@ -398,10 +391,6 @@ int ipc_msg_get_info(int id, struct msg_data **msgds) if (id > -1) { /* ID specified */ if (id == p->msg_perm.id) { - /* - * FIXME: q_qbytes are not in /proc - * - */ if (msgctl(id, IPC_STAT, &msgseg) != -1) p->q_qbytes = msgseg.msg_qbytes; i = 1; @@ -422,27 +411,19 @@ int ipc_msg_get_info(int id, struct msg_data **msgds) return i; /* Fallback; /proc or /sys file(s) missing. */ - msg_fallback: - i = id < 0 ? 0 : id; - - maxid = msgctl(id, MSG_STAT, &dummy); - if (maxid < 0) - return 0; +msg_fallback: + maxid = msgctl(0, MSG_INFO, &dummy); - while (i <= maxid) { + for (int j = 0; j <= maxid; j++) { int msgid; struct ipc_perm *ipcp = &msgseg.msg_perm; - msgid = msgctl(i, MSG_STAT, &msgseg); - if (msgid < 0) { - if (-1 < id) { - free(*msgds); - return 0; - } - i++; + msgid = msgctl(j, MSG_STAT, &msgseg); + if (msgid < 0 || (id > -1 && msgid != id)) { continue; } + i++; p->msg_perm.key = ipcp->KEY; p->msg_perm.id = msgid; p->msg_perm.mode = ipcp->mode; @@ -463,11 +444,12 @@ int ipc_msg_get_info(int id, struct msg_data **msgds) p->next = xcalloc(1, sizeof(struct msg_data)); p = p->next; p->next = NULL; - i++; } else - return 1; + break; } + if (i == 0) + free(*msgds); return i; } @@ -508,10 +490,10 @@ void ipc_print_perms(FILE *f, struct ipc_stat *is) fprintf(f, " %-10u\n", is->gid); } -void ipc_print_size(int unit, char *msg, size_t size, const char *end, +void ipc_print_size(int unit, char *msg, uint64_t size, const char *end, int width) { - char format[16]; + char format[32]; if (!msg) /* NULL */ ; @@ -527,11 +509,11 @@ void ipc_print_size(int unit, char *msg, size_t size, const char *end, switch (unit) { case IPC_UNIT_DEFAULT: case IPC_UNIT_BYTES: - sprintf(format, "%%%dzu", width); + sprintf(format, "%%%dju", width); printf(format, size); break; case IPC_UNIT_KB: - sprintf(format, "%%%dzu", width); + sprintf(format, "%%%dju", width); printf(format, size / 1024); break; case IPC_UNIT_HUMAN: diff --git a/sys-utils/ipcutils.h b/sys-utils/ipcutils.h index d2e5972..444065a 100644 --- a/sys-utils/ipcutils.h +++ b/sys-utils/ipcutils.h @@ -12,6 +12,7 @@ #include #include #include +#include /* * SHM_DEST and SHM_LOCKED are defined in kernel headers, but inside @@ -34,11 +35,11 @@ # define SHM_INFO 14 struct shm_info { int used_ids; - ulong shm_tot; /* total allocated shm */ - ulong shm_rss; /* total resident shm */ - ulong shm_swp; /* total swapped shm */ - ulong swap_attempts; - ulong swap_successes; + unsigned long shm_tot; /* total allocated shm */ + unsigned long shm_rss; /* total resident shm */ + unsigned long shm_swp; /* total swapped shm */ + unsigned long swap_attempts; + unsigned long swap_successes; }; #endif @@ -118,7 +119,7 @@ struct ipc_stat { }; extern void ipc_print_perms(FILE *f, struct ipc_stat *is); -extern void ipc_print_size(int unit, char *msg, size_t size, const char *end, int width); +extern void ipc_print_size(int unit, char *msg, uint64_t size, const char *end, int width); /* See 'struct shmid_kernel' in kernel sources */ diff --git a/sys-utils/lsipc.1 b/sys-utils/lsipc.1 new file mode 100644 index 0000000..98449cb --- /dev/null +++ b/sys-utils/lsipc.1 @@ -0,0 +1,133 @@ +.\" Copyright 2015 Ondrej Oprala(ooprala@redhat.com) +.\" May be distributed under the GNU General Public License +.TH LSIPC "1" "November 2015" "util-linux" "User Commands" +.SH NAME +lsipc \- show information on IPC facilities currently employed in the system +.SH SYNOPSIS +.B lsipc +[options] +.SH DESCRIPTION +.B lsipc +shows information on the inter-process communication facilities +for which the calling process has read access. +.SH OPTIONS +.TP +\fB\-i\fR, \fB\-\-id\fR \fIid\fR +Show full details on just the one resource element identified by +.IR id . +This option needs to be combined with one of the three resource options: +.BR \-m , +.BR \-q " or" +.BR \-s . +It is possible to override the default output format for this option with the +\fB\-\-list\fR, \fB\-\-raw\fR, \fB\-\-json\fR or \fB\-\-export\fR option. +.TP +\fB\-g\fR, \fB\-\-global\fR +Show system-wide usage and limits of IPC resources. +This option may be combined with one of the three resource options: +.BR \-m , +.BR \-q " or" +.BR \-s . +The default is to show information about all resources. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help text and exit. +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version information and exit. +.SS "Resource options" +.TP +\fB\-m\fR, \fB\-\-shmems\fR +Write information about active shared memory segments. +.TP +\fB\-q\fR, \fB\-\-queues\fR +Write information about active message queues. +.TP +\fB\-s\fR, \fB\-\-semaphores\fR +Write information about active semaphore sets. +.SS "Output formatting" +.TP +\fB\-c\fR, \fB\-\-creator\fR +Show creator and owner. +.TP +\fB\-e\fR, \fB\-\-export\fR +Output data in the format of NAME=VALUE. +.TP +\fB\-l\fR, \fB\-\-list\fR +Use the list output format. This is the default, except when \fB\-\-id\fR +is used. +.TP +\fB\-n\fR, \fB\-\-newline\fR +Display each piece of information on a separate line. +.TP +\fB\-\-noheadings\fR +Do not print a header line. +.TP +\fB\-\-notruncate\fR +Don't truncate output. +.TP +\fB\-o\fR, \fB\-\-output \fIlist\fP +Specify which output columns to print. Use +.B \-\-help +to get a list of all supported columns. +.TP +\fB\-p\fR, \fB\-\-pid\fR +Show PIDs of creator and last operator. +.TP +\fB\-r\fR, \fB\-\-raw\fR +Raw output (no columnation). +.TP +\fB\-t\fR, \fB\-\-time\fR +Write time information. The time of the last control operation that changed +the access permissions for all facilities, the time of the last +.I msgsnd() +and +.I msgrcv() +operations on message queues, the time of the last +.I shmat() +and +.I shmdt() +operations on shared memory, and the time of the last +.I semop() +operation on semaphores. +.TP +\fB\-\-time\-format\fR \fItype\fP +Display dates in short, full or iso format. The default is short, this time +format is designed to be space efficient and human readable. + +.SH EXIT STATUS +.TP +0 +if OK, +.TP +1 +if incorrect arguments specified, +.TP +2 +if a serious error occurs. +.SH SEE ALSO +.BR ipcrm (1), +.BR ipcmk (1), +.BR msgrcv (2), +.BR msgsnd (2), +.BR semget (2), +.BR semop (2), +.BR shmat (2), +.BR shmdt (2), +.BR shmget (2) +.SH HISTORY +The \fBlsipc\fP utility is inspired by the \fBipcs\fP utility. +.SH AUTHORS +.MT ooprala@redhat.com +Ondrej Oprala +.ME +.br +.MT kzak@redhat.com +Karel Zak +.ME + +.SH AVAILABILITY +The lsipc command is part of the util-linux package and is available from +.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/sys-utils/lsipc.c b/sys-utils/lsipc.c new file mode 100644 index 0000000..0be9d91 --- /dev/null +++ b/sys-utils/lsipc.c @@ -0,0 +1,1316 @@ +/* + * lsipc - List information about IPC instances employed in the system + * + * Copyright (C) 2015 Ondrej Oprala + * Copyright (C) 2015 Karel Zak + * + * 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 +#include +#include +#include + +#include + +#include "c.h" +#include "nls.h" +#include "closestream.h" +#include "strutils.h" +#include "optutils.h" +#include "xalloc.h" +#include "procutils.h" +#include "ipcutils.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_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 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 *optarg) +{ + struct lsipc_timefmt { + const char *name; + const int val; + }; + static const struct lsipc_timefmt timefmts[] = { + {"iso", TIME_ISO}, + {"full", TIME_FULL}, + {"short", TIME_SHORT}, + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(timefmts); i++) { + if (strcmp(timefmts[i].name, optarg) == 0) + return timefmts[i].val; + } + errx(EXIT_FAILURE, _("unknown time format: %s"), optarg); +} + +static void __attribute__ ((__noreturn__)) usage(FILE * out) +{ + size_t i; + + fputs(USAGE_HEADER, out); + 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 print details on resource identified by \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= 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(_(" -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[=] 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); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + + 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)); + + fprintf(out, USAGE_MAN_TAIL("lsipc(1)")); + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +static struct libscols_table *new_table(struct lsipc_control *ctl) +{ + struct libscols_table *table = scols_new_table(); + + if (!table) + errx(EXIT_FAILURE, _("failed to initialize 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; + 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++) { + int flags = coldescs[columns[n]].flag; + + if (ctl->notrunc) + flags &= ~SCOLS_FL_TRUNC; + + if (!scols_table_new_column(table, + coldescs[columns[n]].name, + coldescs[columns[n]].whint, + flags)) + goto fail; + } + 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_(coldescs[columns[n]].pretty_name); + dstr = scols_cell_get_data(data); + + if (dstr) + printf("%s:%*c%-36s\n", hstr, 35 - (int)strlen(hstr), ' ', dstr); + ++n; + } + + /* this is used to pretty-print detailed info about a semaphore array */ + if (ln) { + 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 int date_is_today(time_t t) +{ + if (now.tv_sec == 0) + gettimeofday(&now, NULL); + return t / (3600 * 24) == now.tv_sec / (3600 * 24); +} + +static int date_is_thisyear(time_t t) +{ + if (now.tv_sec == 0) + gettimeofday(&now, NULL); + return t / (3600 * 24 * 365) == now.tv_sec / (3600 * 24 * 365); +} + +static char *make_time(int mode, time_t time) +{ + char *s; + struct tm tm; + char buf[64] = {0}; + + localtime_r(&time, &tm); + + switch(mode) { + case TIME_FULL: + asctime_r(&tm, buf); + if (*(s = buf + strlen(buf) - 1) == '\n') + *s = '\0'; + break; + case TIME_SHORT: + if (date_is_today(time)) + strftime(buf, sizeof(buf), "%H:%M", &tm); + else if (date_is_thisyear(time)) + strftime(buf, sizeof(buf), "%b%d", &tm); + else + strftime(buf, sizeof(buf), "%Y-%b%d", &tm); + break; + case TIME_ISO: + strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", &tm); + break; + default: + errx(EXIT_FAILURE, _("unsupported time type")); + } + return xstrdup(buf); +} + +static void 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_oom(); + + for (n = 0; n < ncolumns; n++) { + int rc = 0; + char *arg = NULL; + + switch (columns[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 set data")); + } +} + +static void setup_sem_elements_columns(struct libscols_table *tb) +{ + 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; + + 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); + + for (n = 0; n < ncolumns; n++) { + int rc = 0; + switch (columns[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); + strmode(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 set 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); + + /* 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; + } + + for (msgdsp = msgds; msgdsp->next != NULL || id > -1 ; msgdsp = msgdsp->next) { + size_t n; + ln = scols_table_new_line(tb, NULL); + + /* no need to call getpwuid() for the same user */ + if (!(pw && pw->pw_uid == msgdsp->msg_perm.uid)) + pw = getpwuid(msgdsp->msg_perm.uid); + + /* no need to call getgrgid() for the same user */ + if (!(gr && gr->gr_gid == msgdsp->msg_perm.gid)) + gr = getgrgid(msgdsp->msg_perm.gid); + + for (n = 0; n < ncolumns; n++) { + int rc = 0; + + switch (columns[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); + strmode(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; + } + + for (shmdsp = shmds; shmdsp->next != NULL || id > -1 ; shmdsp = shmdsp->next) { + size_t n; + ln = scols_table_new_line(tb, NULL); + if (!ln) + err_oom(); + + for (n = 0; n < ncolumns; n++) { + int rc = 0; + + switch (columns[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); + strmode(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, 0, 'b' }, + { "creator", no_argument, 0, 'c' }, + { "export", no_argument, 0, 'e' }, + { "global", no_argument, 0, 'g' }, + { "help", no_argument, 0, 'h' }, + { "id", required_argument, 0, 'i' }, + { "list", no_argument, 0, 'l' }, + { "newline", no_argument, 0, 'n' }, + { "noheadings", no_argument, 0, OPT_NOHEAD }, + { "notruncate", no_argument, 0, OPT_NOTRUNC }, + { "numeric-perms", no_argument, 0, 'P' }, + { "output", required_argument, 0, 'o' }, + { "pid", no_argument, 0, 'p' }, + { "queues", no_argument, 0, 'q' }, + { "raw", no_argument, 0, 'r' }, + { "semaphores", no_argument, 0, 's' }, + { "shmems", no_argument, 0, 'm' }, + { "time", no_argument, 0, 't' }, + { "time-format", required_argument, 0, OPT_TIME_FMT }, + { "version", no_argument, 0, 'V' }, + {NULL, 0, NULL, 0} + }; + + static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ + { 'J', 'e', '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:lmno:PqrstuV", 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 't': + show_time = 1; + break; + case 'c': + show_creat = 1; + break; + case 'h': + usage(stdout); + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + default: + usage(stderr); + } + } + + /* 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 -- --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), + (int *) &ncolumns, column_name_to_id) < 0) + return EXIT_FAILURE; + + tb = setup_table(ctl); + if (!tb) + return EXIT_FAILURE; + + 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; +} + diff --git a/tests/functions.sh b/tests/functions.sh index 123f6c3..b2d493c 100644 --- a/tests/functions.sh +++ b/tests/functions.sh @@ -50,16 +50,25 @@ function ts_skip_nonroot { } function ts_failed_subtest { + local msg="FAILED" + local ret=1 + if [ "$TS_KNOWN_FAIL" = "yes" ]; then + msg="KNOWN FAILED" + ret=0 + fi + if [ x"$1" == x"" ]; then - echo " FAILED ($TS_NS)" + echo " $msg ($TS_NS)" else - echo " FAILED ($1)" + echo " $msg ($1)" fi + + return $ret } function ts_failed { ts_failed_subtest "$1" - exit 1 + exit $? } function ts_ok_subtest { @@ -150,6 +159,7 @@ function ts_init_env { TS_SUBDIR=$(dirname $TS_SCRIPT) TS_TESTNAME=$(basename $TS_SCRIPT) TS_COMPONENT=$(basename $TS_SUBDIR) + TS_KNOWN_FAIL="no" TS_NSUBTESTS=0 TS_NSUBFAILED=0 diff --git a/tests/ts/ipcs/limits2 b/tests/ts/ipcs/limits2 index f99a354..63f834d 100755 --- a/tests/ts/ipcs/limits2 +++ b/tests/ts/ipcs/limits2 @@ -16,15 +16,20 @@ # GNU General Public License for more details. # -TS_TOPDIR="$(dirname $0)/../.." +TS_TOPDIR="${0%/*}/../.." TS_DESC="basic limits" . $TS_TOPDIR/functions.sh ts_init "$*" -type bc >/dev/null 2>&1 || ts_skip "cannot find bc command" . $TS_SELF/functions.sh +# TODO https://github.com/karelzak/util-linux/issues/51 +SHMALL=$(> $TS_OUTPUT -- 2.7.4