[RFC PATCH 8/8] signals: Support BSD VSTATUS, KERNINFO and SIGINFO

From: Walt Drummond
Date: Mon Jan 03 2022 - 13:35:33 EST


Support TTY VSTATUS character, NOKERNINFO local control bit and the
signal SIGINFO, all as in 4.3BSD.

Signed-off-by: Walt Drummond <walt@xxxxxxxxxxx>
---
arch/x86/include/asm/signal.h | 2 +-
arch/x86/include/uapi/asm/signal.h | 4 +-
drivers/tty/Makefile | 2 +-
drivers/tty/n_tty.c | 21 +++++
drivers/tty/tty_io.c | 10 ++-
drivers/tty/tty_ioctl.c | 4 +
drivers/tty/tty_status.c | 135 ++++++++++++++++++++++++++++
fs/proc/array.c | 29 +-----
include/asm-generic/termios.h | 4 +-
include/linux/sched.h | 52 ++++++++++-
include/linux/signal.h | 4 +
include/linux/tty.h | 8 ++
include/uapi/asm-generic/ioctls.h | 2 +
include/uapi/asm-generic/signal.h | 6 +-
include/uapi/asm-generic/termbits.h | 34 +++----
15 files changed, 264 insertions(+), 53 deletions(-)
create mode 100644 drivers/tty/tty_status.c

diff --git a/arch/x86/include/asm/signal.h b/arch/x86/include/asm/signal.h
index d8e2efe6cd46..0a01877c11ab 100644
--- a/arch/x86/include/asm/signal.h
+++ b/arch/x86/include/asm/signal.h
@@ -8,7 +8,7 @@
/* Most things should be clean enough to redefine this at will, if care
is taken to make libc match. */

-#define _NSIG 64
+#define _NSIG 65

#ifdef __i386__
# define _NSIG_BPW 32
diff --git a/arch/x86/include/uapi/asm/signal.h b/arch/x86/include/uapi/asm/signal.h
index 164a22a72984..60dca62d3dcf 100644
--- a/arch/x86/include/uapi/asm/signal.h
+++ b/arch/x86/include/uapi/asm/signal.h
@@ -60,7 +60,9 @@ typedef unsigned long sigset_t;

/* These should not be considered constants from userland. */
#define SIGRTMIN 32
-#define SIGRTMAX _NSIG
+#define SIGRTMAX 64
+
+#define SIGINFO 65

#define SA_RESTORER 0x04000000

diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index a2bd75fbaaa4..d50ba690bb87 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -2,7 +2,7 @@
obj-$(CONFIG_TTY) += tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \
tty_buffer.o tty_port.o tty_mutex.o \
tty_ldsem.o tty_baudrate.o tty_jobctrl.o \
- n_null.o
+ n_null.o tty_status.o
obj-$(CONFIG_LEGACY_PTYS) += pty.o
obj-$(CONFIG_UNIX98_PTYS) += pty.o
obj-$(CONFIG_AUDIT) += tty_audit.o
diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c
index 0ec93f1a61f5..b510e01289fd 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -1334,6 +1334,24 @@ static void n_tty_receive_char_special(struct tty_struct *tty, unsigned char c)
commit_echoes(tty);
return;
}
+#ifdef VSTATUS
+ if (c == STATUS_CHAR(tty)) {
+ /* Do the status message first and then send
+ * the signal, otherwise signal delivery can
+ * change the process state making the status
+ * message misleading. Also, use __isig() and
+ * not sig(), as if we flush the tty we can
+ * lose parts of the message.
+ */
+
+ if (!L_NOKERNINFO(tty))
+ tty_status(tty);
+# if defined(SIGINFO) && SIGINFO != SIGPWR
+ __isig(SIGINFO, tty);
+# endif
+ return;
+ }
+#endif /* VSTATUS */
if (c == '\n') {
if (L_ECHO(tty) || L_ECHONL(tty)) {
echo_char_raw('\n', ldata);
@@ -1763,6 +1781,9 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
set_bit(EOF_CHAR(tty), ldata->char_map);
set_bit('\n', ldata->char_map);
set_bit(EOL_CHAR(tty), ldata->char_map);
+#ifdef VSTATUS
+ set_bit(STATUS_CHAR(tty), ldata->char_map);
+#endif
if (L_IEXTEN(tty)) {
set_bit(WERASE_CHAR(tty), ldata->char_map);
set_bit(LNEXT_CHAR(tty), ldata->char_map);
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index 6616d4a0d41d..8e488ecba330 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -120,18 +120,26 @@
#define TTY_PARANOIA_CHECK 1
#define CHECK_TTY_COUNT 1

+/* Less ugly than an ifdef in the middle of the initalizer below, maybe? */
+#ifdef NOKERNINFO
+# define __NOKERNINFO NOKERNINFO
+#else
+# define __NOKERNINFO 0
+#endif
+
struct ktermios tty_std_termios = { /* for the benefit of tty drivers */
.c_iflag = ICRNL | IXON,
.c_oflag = OPOST | ONLCR,
.c_cflag = B38400 | CS8 | CREAD | HUPCL,
.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
- ECHOCTL | ECHOKE | IEXTEN,
+ ECHOCTL | ECHOKE | IEXTEN | __NOKERNINFO,
.c_cc = INIT_C_CC,
.c_ispeed = 38400,
.c_ospeed = 38400,
/* .c_line = N_TTY, */
};
EXPORT_SYMBOL(tty_std_termios);
+#undef __NOKERNINFO

/* This list gets poked at by procfs and various bits of boot up code. This
* could do with some rationalisation such as pulling the tty proc function
diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c
index 507a25d692bb..b250eabca1ba 100644
--- a/drivers/tty/tty_ioctl.c
+++ b/drivers/tty/tty_ioctl.c
@@ -809,6 +809,10 @@ int tty_mode_ioctl(struct tty_struct *tty, struct file *file,
if (get_user(arg, (unsigned int __user *) arg))
return -EFAULT;
return tty_change_softcar(real_tty, arg);
+#ifdef TIOCSTAT
+ case TIOCSTAT:
+ return tty_status(real_tty);
+#endif
default:
return -ENOIOCTLCMD;
}
diff --git a/drivers/tty/tty_status.c b/drivers/tty/tty_status.c
new file mode 100644
index 000000000000..a9600f5bd48c
--- /dev/null
+++ b/drivers/tty/tty_status.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-1.0+
+/*
+ * tty_status.c --- implements VSTATUS and TIOCSTAT from BSD4.3/4.4
+ *
+ */
+
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/tty.h>
+#include <linux/sched/cputime.h>
+#include <linux/sched/loadavg.h>
+#include <linux/pid.h>
+#include <linux/slab.h>
+#include <linux/math64.h>
+
+#define MSGLEN (160 + TASK_COMM_LEN)
+
+inline unsigned long getRSSk(struct mm_struct *mm)
+{
+ if (mm == NULL)
+ return 0;
+ return get_mm_rss(mm) * PAGE_SIZE / 1024;
+}
+
+inline long nstoms(long l)
+{
+ l /= NSEC_PER_MSEC * 10;
+ if (l < 10)
+ l *= 10;
+ return l;
+}
+
+inline struct task_struct *compare(struct task_struct *new,
+ struct task_struct *old)
+{
+ unsigned int ostate, nstate;
+
+ if (old == NULL)
+ return new;
+
+ ostate = task_state_index(old);
+ nstate = task_state_index(new);
+
+ if (ostate == nstate) {
+ if (old->start_time > new->start_time)
+ return old;
+ return new;
+ }
+
+ if (ostate < nstate)
+ return old;
+
+ return new;
+}
+
+struct task_struct *pick_process(struct pid *pgrp)
+{
+ struct task_struct *p, *winner = NULL;
+
+ read_lock(&tasklist_lock);
+ do_each_pid_task(pgrp, PIDTYPE_PGID, p) {
+ winner = compare(p, winner);
+ } while_each_pid_task(pgrp, PIDTYPE_PGID, p);
+ read_unlock(&tasklist_lock);
+
+ return winner;
+}
+
+int tty_status(struct tty_struct *tty)
+{
+ char tname[TASK_COMM_LEN];
+ unsigned long loadavg[3];
+ uint64_t pcpu, cputime, wallclock;
+ struct task_struct *p;
+ struct rusage rusage;
+ struct timespec64 utime, stime, rtime;
+ char msg[MSGLEN] = {0};
+ int len = 0;
+
+ if (tty == NULL)
+ return -ENOTTY;
+
+ get_avenrun(loadavg, FIXED_1/200, 0);
+ len += scnprintf((char *)&msg[len], MSGLEN - len, "load: %lu.%02lu ",
+ LOAD_INT(loadavg[0]), LOAD_FRAC(loadavg[0]));
+
+ if (tty->ctrl.session == NULL) {
+ len += scnprintf((char *)&msg[len], MSGLEN - len,
+ "not a controlling terminal");
+ goto print;
+ }
+
+ if (tty->ctrl.pgrp == NULL) {
+ len += scnprintf((char *)&msg[len], MSGLEN - len,
+ "no foreground process group");
+ goto print;
+ }
+
+ p = pick_process(tty->ctrl.pgrp);
+ if (p == NULL) {
+ len += scnprintf((char *)&msg[len], MSGLEN - len,
+ "empty foreground process group");
+ goto print;
+ }
+
+ get_task_comm(tname, p);
+ getrusage(p, RUSAGE_BOTH, &rusage);
+ wallclock = ktime_get_ns() - p->start_time;
+
+ utime.tv_sec = rusage.ru_utime.tv_sec;
+ utime.tv_nsec = rusage.ru_utime.tv_usec * NSEC_PER_USEC;
+ stime.tv_sec = rusage.ru_stime.tv_sec;
+ stime.tv_nsec = rusage.ru_stime.tv_usec * NSEC_PER_USEC;
+ rtime = ns_to_timespec64(wallclock);
+
+ cputime = timespec64_to_ns(&utime) + timespec64_to_ns(&stime);
+ pcpu = div64_u64(cputime * 100, wallclock);
+
+ len += scnprintf((char *)&msg[len], MSGLEN - len,
+ /* task, PID, task state */
+ "cmd: %s %d [%s] "
+ /* rtime, utime, stime, %cpu, rss */
+ "%llu.%02lur %llu.%02luu %llu.%02lus %llu%% %luk",
+ tname, task_pid_vnr(p), (char *)get_task_state_name(p),
+ rtime.tv_sec, nstoms(rtime.tv_nsec),
+ utime.tv_sec, nstoms(utime.tv_nsec),
+ stime.tv_sec, nstoms(stime.tv_nsec),
+ pcpu, getRSSk(p->mm));
+
+print:
+ len += scnprintf((char *)&msg[len], MSGLEN - len, "\r\n");
+ tty_write_message(tty, msg);
+
+ return 0;
+}
diff --git a/fs/proc/array.c b/fs/proc/array.c
index f37c03077b58..eb14306cdde2 100644
--- a/fs/proc/array.c
+++ b/fs/proc/array.c
@@ -62,6 +62,7 @@
#include <linux/tty.h>
#include <linux/string.h>
#include <linux/mman.h>
+#include <linux/sched.h>
#include <linux/sched/mm.h>
#include <linux/sched/numa_balancing.h>
#include <linux/sched/task_stack.h>
@@ -111,34 +112,6 @@ void proc_task_name(struct seq_file *m, struct task_struct *p, bool escape)
seq_printf(m, "%.64s", tcomm);
}

-/*
- * The task state array is a strange "bitmap" of
- * reasons to sleep. Thus "running" is zero, and
- * you can test for combinations of others with
- * simple bit tests.
- */
-static const char * const task_state_array[] = {
-
- /* states in TASK_REPORT: */
- "R (running)", /* 0x00 */
- "S (sleeping)", /* 0x01 */
- "D (disk sleep)", /* 0x02 */
- "T (stopped)", /* 0x04 */
- "t (tracing stop)", /* 0x08 */
- "X (dead)", /* 0x10 */
- "Z (zombie)", /* 0x20 */
- "P (parked)", /* 0x40 */
-
- /* states beyond TASK_REPORT: */
- "I (idle)", /* 0x80 */
-};
-
-static inline const char *get_task_state(struct task_struct *tsk)
-{
- BUILD_BUG_ON(1 + ilog2(TASK_REPORT_MAX) != ARRAY_SIZE(task_state_array));
- return task_state_array[task_state_index(tsk)];
-}
-
static inline void task_state(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *p)
{
diff --git a/include/asm-generic/termios.h b/include/asm-generic/termios.h
index b1398d0d4a1d..9b080e1a82d4 100644
--- a/include/asm-generic/termios.h
+++ b/include/asm-generic/termios.h
@@ -10,9 +10,9 @@
eof=^D vtime=\0 vmin=\1 sxtc=\0
start=^Q stop=^S susp=^Z eol=\0
reprint=^R discard=^U werase=^W lnext=^V
- eol2=\0
+ eol2=\0 status=^T
*/
-#define INIT_C_CC "\003\034\177\025\004\0\1\0\021\023\032\0\022\017\027\026\0"
+#define INIT_C_CC "\003\034\177\025\004\0\1\0\021\023\032\0\022\017\027\026\0\024"

/*
* Translate a "termio" structure into a "termios". Ugh.
diff --git a/include/linux/sched.h b/include/linux/sched.h
index c1a927ddec64..2171074ec8f5 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -70,7 +70,7 @@ struct task_group;

/*
* Task state bitmask. NOTE! These bits are also
- * encoded in fs/proc/array.c: get_task_state().
+ * encoded in get_task_state().
*
* We have two separate sets of flags: task->state
* is about runnability, while task->exit_state are
@@ -1643,6 +1643,56 @@ static inline char task_state_to_char(struct task_struct *tsk)
return task_index_to_char(task_state_index(tsk));
}

+static inline const char *get_task_state_name(struct task_struct *tsk)
+{
+ static const char * const task_state_array[] = {
+
+ /* states in TASK_REPORT: */
+ "running", /* 0x00 */
+ "sleeping", /* 0x01 */
+ "disk sleep", /* 0x02 */
+ "stopped", /* 0x04 */
+ "tracing stop", /* 0x08 */
+ "dead", /* 0x10 */
+ "zombie", /* 0x20 */
+ "parked", /* 0x40 */
+
+ /* states beyond TASK_REPORT: */
+ "idle", /* 0x80 */
+ };
+
+ BUILD_BUG_ON(1 + ilog2(TASK_REPORT_MAX) != ARRAY_SIZE(task_state_array));
+ return task_state_array[task_state_index(tsk)];
+}
+
+static inline const char *get_task_state(struct task_struct *tsk)
+{
+ /*
+ * The task state array is a strange "bitmap" of
+ * reasons to sleep. Thus "running" is zero, and
+ * you can test for combinations of others with
+ * simple bit tests.
+ */
+ static const char * const task_state_array[] = {
+
+ /* states in TASK_REPORT: */
+ "R (running)", /* 0x00 */
+ "S (sleeping)", /* 0x01 */
+ "D (disk sleep)", /* 0x02 */
+ "T (stopped)", /* 0x04 */
+ "t (tracing stop)", /* 0x08 */
+ "X (dead)", /* 0x10 */
+ "Z (zombie)", /* 0x20 */
+ "P (parked)", /* 0x40 */
+
+ /* states beyond TASK_REPORT: */
+ "I (idle)", /* 0x80 */
+ };
+
+ BUILD_BUG_ON(1 + ilog2(TASK_REPORT_MAX) != ARRAY_SIZE(task_state_array));
+ return task_state_array[task_state_index(tsk)];
+}
+
/**
* is_global_init - check if a task structure is init. Since init
* is free to have sub-threads we need to check tgid.
diff --git a/include/linux/signal.h b/include/linux/signal.h
index b77f9472a37c..76bda1a20578 100644
--- a/include/linux/signal.h
+++ b/include/linux/signal.h
@@ -541,6 +541,7 @@ extern bool unhandled_signal(struct task_struct *tsk, int sig);
* | non-POSIX signal | default action |
* +--------------------+------------------+
* | SIGEMT | coredump |
+ * | SIGINFO | ignore |
* +--------------------+------------------+
*
* (+) For SIGKILL and SIGSTOP the action is "always", not just "default".
@@ -567,6 +568,9 @@ static inline int sig_kernel_ignore(unsigned long sig)
return sig == SIGCONT ||
sig == SIGCHLD ||
sig == SIGWINCH ||
+#if defined(SIGINFO) && SIGINFO != SIGPWR
+ sig == SIGINFO ||
+#endif
sig == SIGURG;
}

diff --git a/include/linux/tty.h b/include/linux/tty.h
index 168e57e40bbb..943d85aa471c 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -49,6 +49,9 @@
#define WERASE_CHAR(tty) ((tty)->termios.c_cc[VWERASE])
#define LNEXT_CHAR(tty) ((tty)->termios.c_cc[VLNEXT])
#define EOL2_CHAR(tty) ((tty)->termios.c_cc[VEOL2])
+#ifdef VSTATUS
+#define STATUS_CHAR(tty) ((tty)->termios.c_cc[VSTATUS])
+#endif

#define _I_FLAG(tty, f) ((tty)->termios.c_iflag & (f))
#define _O_FLAG(tty, f) ((tty)->termios.c_oflag & (f))
@@ -114,6 +117,9 @@
#define L_PENDIN(tty) _L_FLAG((tty), PENDIN)
#define L_IEXTEN(tty) _L_FLAG((tty), IEXTEN)
#define L_EXTPROC(tty) _L_FLAG((tty), EXTPROC)
+#ifdef NOKERNINFO
+#define L_NOKERNINFO(tty) _L_FLAG((tty), NOKERNINFO)
+#endif

struct device;
struct signal_struct;
@@ -428,4 +434,6 @@ extern void tty_lock_slave(struct tty_struct *tty);
extern void tty_unlock_slave(struct tty_struct *tty);
extern void tty_set_lock_subclass(struct tty_struct *tty);

+extern int tty_status(struct tty_struct *tty);
+
#endif
diff --git a/include/uapi/asm-generic/ioctls.h b/include/uapi/asm-generic/ioctls.h
index cdc9f4ca8c27..baa2b8d42679 100644
--- a/include/uapi/asm-generic/ioctls.h
+++ b/include/uapi/asm-generic/ioctls.h
@@ -97,6 +97,8 @@

#define TIOCMIWAIT 0x545C /* wait for a change on serial input line(s) */
#define TIOCGICOUNT 0x545D /* read serial port inline interrupt counts */
+/* Some architectures use 0x545E for FIOQSIZE */
+#define TIOCSTAT 0x545F /* display process group stats on tty */

/*
* Some arches already define FIOQSIZE due to a historical
diff --git a/include/uapi/asm-generic/signal.h b/include/uapi/asm-generic/signal.h
index 3c4cc9b8378e..0b771eb1db94 100644
--- a/include/uapi/asm-generic/signal.h
+++ b/include/uapi/asm-generic/signal.h
@@ -4,7 +4,7 @@

#include <linux/types.h>

-#define _NSIG 64
+#define _NSIG 65
#define _NSIG_BPW __BITS_PER_LONG
#define _NSIG_WORDS ((_NSIG + _NSIG_BPW - 1) / _NSIG_BPW)

@@ -49,9 +49,11 @@
/* These should not be considered constants from userland. */
#define SIGRTMIN 32
#ifndef SIGRTMAX
-#define SIGRTMAX _NSIG
+#define SIGRTMAX 64
#endif

+#define SIGINFO 65
+
#if !defined MINSIGSTKSZ || !defined SIGSTKSZ
#define MINSIGSTKSZ 2048
#define SIGSTKSZ 8192
diff --git a/include/uapi/asm-generic/termbits.h b/include/uapi/asm-generic/termbits.h
index 2fbaf9ae89dd..cb4e9c6d629f 100644
--- a/include/uapi/asm-generic/termbits.h
+++ b/include/uapi/asm-generic/termbits.h
@@ -58,6 +58,7 @@ struct ktermios {
#define VWERASE 14
#define VLNEXT 15
#define VEOL2 16
+#define VSTATUS 17

/* c_iflag bits */
#define IGNBRK 0000001
@@ -164,22 +165,23 @@ struct ktermios {
#define IBSHIFT 16 /* Shift from CBAUD to CIBAUD */

/* c_lflag bits */
-#define ISIG 0000001
-#define ICANON 0000002
-#define XCASE 0000004
-#define ECHO 0000010
-#define ECHOE 0000020
-#define ECHOK 0000040
-#define ECHONL 0000100
-#define NOFLSH 0000200
-#define TOSTOP 0000400
-#define ECHOCTL 0001000
-#define ECHOPRT 0002000
-#define ECHOKE 0004000
-#define FLUSHO 0010000
-#define PENDIN 0040000
-#define IEXTEN 0100000
-#define EXTPROC 0200000
+#define ISIG 0000001
+#define ICANON 0000002
+#define XCASE 0000004
+#define ECHO 0000010
+#define ECHOE 0000020
+#define ECHOK 0000040
+#define ECHONL 0000100
+#define NOFLSH 0000200
+#define TOSTOP 0000400
+#define ECHOCTL 0001000
+#define ECHOPRT 0002000
+#define ECHOKE 0004000
+#define FLUSHO 0010000
+#define PENDIN 0040000
+#define IEXTEN 0100000
+#define EXTPROC 0200000
+#define NOKERNINFO 0400000

/* tcflow() and TCXONC use these */
#define TCOOFF 0
--
2.30.2