When triggered by pressing the VSTATUS key or calling the TIOCSTAT
ioctl, the n_tty line discipline will display a message on the user's
tty that provides basic information about the system and an
'interesting' process in the current foreground process group, eg:

  load: 0.58  cmd: sleep 744474 [sleeping] 0.36r 0.00u 0.00s 0% 772k

The status message provides:
 - System load average
 - Command name and process id (from the perspective of the session)
 - Scheduler state
 - Total wall-clock run time
 - User space run time
 - System space run time
 - Percentage of on-cpu time
 - Resident set size

The message is only displayed when the tty has the VSTATUS character
set, the local flags ICANON and IEXTEN are enabled and NOKERNINFO is
disabled; it is always displayed when TIOCSTAT is called regardless of
tty settings.

Signed-off-by: Walt Drummond <w...@drummond.us>
---
 drivers/tty/Makefile       |   2 +-
 drivers/tty/n_tty.c        |  34 +++++++
 drivers/tty/n_tty_status.c | 181 +++++++++++++++++++++++++++++++++++++
 drivers/tty/tty_io.c       |   2 +-
 include/linux/tty.h        |   5 +
 5 files changed, 222 insertions(+), 2 deletions(-)
 create mode 100644 drivers/tty/n_tty_status.c

diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index a2bd75fbaaa4..3539d7ab77e5 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 n_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 64a058a4c63b..fd70efc333d7 100644
--- a/drivers/tty/n_tty.c
+++ b/drivers/tty/n_tty.c
@@ -80,6 +80,7 @@
 #define ECHO_BLOCK             256
 #define ECHO_DISCARD_WATERMARK N_TTY_BUF_SIZE - (ECHO_BLOCK + 32)
 
+#define STATUS_LINE_LEN 160   /* tty status line will truncate at this length 
*/
 
 #undef N_TTY_TRACE
 #ifdef N_TTY_TRACE
@@ -127,6 +128,8 @@ struct n_tty_data {
        struct mutex output_lock;
 };
 
+static void n_tty_status(struct tty_struct *tty);
+
 #define MASK(x) ((x) & (N_TTY_BUF_SIZE - 1))
 
 static inline size_t read_cnt(struct n_tty_data *ldata)
@@ -1334,6 +1337,11 @@ static void n_tty_receive_char_special(struct tty_struct 
*tty, unsigned char c)
                        commit_echoes(tty);
                        return;
                }
+               if (c == STATUS_CHAR(tty) && L_IEXTEN(tty)) {
+                       if (!L_NOKERNINFO(tty))
+                               n_tty_status(tty);
+                       return;
+               }
                if (c == '\n') {
                        if (L_ECHO(tty) || L_ECHONL(tty)) {
                                echo_char_raw('\n', ldata);
@@ -1763,6 +1771,7 @@ 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);
+                       set_bit(STATUS_CHAR(tty), ldata->char_map);
                        if (L_IEXTEN(tty)) {
                                set_bit(WERASE_CHAR(tty), ldata->char_map);
                                set_bit(LNEXT_CHAR(tty), ldata->char_map);
@@ -2413,6 +2422,26 @@ static unsigned long inq_canon(struct n_tty_data *ldata)
        return nr;
 }
 
+static void n_tty_status(struct tty_struct *tty)
+{
+       struct n_tty_data *ldata = tty->disc_data;
+       char *msg;
+       size_t len;
+
+       msg = kzalloc(STATUS_LINE_LEN, GFP_KERNEL);
+
+       if (ldata->column != 0) {
+               *msg = '\n';
+               len = n_tty_get_status(tty, msg + 1, STATUS_LINE_LEN - 1);
+       } else {
+               len = n_tty_get_status(tty, msg, STATUS_LINE_LEN);
+       }
+
+       do_n_tty_write(tty, NULL, msg, len);
+
+       kfree(msg);
+}
+
 static int n_tty_ioctl(struct tty_struct *tty, struct file *file,
                       unsigned int cmd, unsigned long arg)
 {
@@ -2430,6 +2459,11 @@ static int n_tty_ioctl(struct tty_struct *tty, struct 
file *file,
                        retval = read_cnt(ldata);
                up_write(&tty->termios_rwsem);
                return put_user(retval, (unsigned int __user *) arg);
+       case TIOCSTAT:
+               down_read(&tty->termios_rwsem);
+               n_tty_status(tty);
+               up_read(&tty->termios_rwsem);
+               return 0;
        default:
                return n_tty_ioctl_helper(tty, file, cmd, arg);
        }
diff --git a/drivers/tty/n_tty_status.c b/drivers/tty/n_tty_status.c
new file mode 100644
index 000000000000..f0e053651368
--- /dev/null
+++ b/drivers/tty/n_tty_status.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-1.0+
+/*
+ * n_tty_status.c --- implements VSTATUS and TIOCSTAT from BSD
+ *
+ * Display a basic status message containing information about the
+ * foreground process and system load on the users tty, triggered by
+ * the VSTATUS character or TIOCSTAT. Ex,
+ *
+ *   load: 14.11  cmd: tcsh 19623 [running] 185756.62r 88.00u 17.50s 0% 4260k
+ *
+ */
+
+#include <linux/tty.h>
+#include <linux/mm.h>
+#include <linux/sched/loadavg.h>
+#include <linux/sched/mm.h>
+
+/* Convert nanoseconds into centiseconds */
+static inline long ns_to_cs(long l)
+{
+       return l / (NSEC_PER_MSEC * 10);
+
+}
+
+/* We want the pid from the context of session */
+static inline pid_t __get_pid(struct task_struct *tsk, struct tty_struct *tty)
+{
+       struct pid_namespace *ns;
+
+       spin_lock_irq(&tty->ctrl.lock);
+       ns = ns_of_pid(tty->ctrl.session);
+       spin_unlock_irq(&tty->ctrl.lock);
+
+       return __task_pid_nr_ns(tsk, PIDTYPE_PID, ns);
+}
+
+/* This is the same odd "bitmap" described in
+ * fs/proc/array.c:get_task_state().  Consistency with standard
+ * implementations of VSTATUS requires a different set of state
+ * names.
+ */
+static const char * const task_state_name_array[] = {
+       "running",
+       "sleeping",
+       "disk sleep",
+       "stopped",
+       "tracing stop",
+       "dead",
+       "zombie",
+       "parked",
+       "idle",
+};
+
+static inline const char *get_task_state_name(struct task_struct *tsk)
+{
+       BUILD_BUG_ON(1 + ilog2(TASK_REPORT_MAX) != 
ARRAY_SIZE(task_state_name_array));
+       return task_state_name_array[task_state_index(tsk)];
+}
+
+static 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;
+}
+
+static struct task_struct *pick_process(struct tty_struct *tty)
+{
+       struct task_struct *new, *winner = NULL;
+
+       read_lock(&tasklist_lock);
+       spin_lock_irq(&tty->ctrl.lock);
+
+       do_each_pid_task(tty->ctrl.pgrp, PIDTYPE_PGID, new) {
+               winner = compare(new, winner);
+       } while_each_pid_task(tty->ctrl.pgrp, PIDTYPE_PGID, new);
+
+       spin_unlock_irq(&tty->ctrl.lock);
+
+       if (winner)
+               winner = get_task_struct(winner);
+
+       read_unlock(&tasklist_lock);
+
+       return winner;
+}
+
+size_t n_tty_get_status(struct tty_struct *tty, char *msg, size_t msglen)
+{
+       struct task_struct *p;
+       struct mm_struct *mm;
+       struct rusage rusage;
+       unsigned long loadavg[3];
+       uint64_t pcpu, cputime, wallclock;
+       struct timespec64 utime, stime, rtime;
+       char tname[TASK_COMM_LEN];
+       unsigned int pid;
+       char *state;
+       unsigned long rss = 0;
+       size_t len = 0;
+
+       get_avenrun(loadavg, FIXED_1/200, 0);
+       len = scnprintf(msg + len, msglen - len, "load: %lu.%02lu  ",
+                       LOAD_INT(loadavg[0]), LOAD_FRAC(loadavg[0]));
+
+       if (tty->ctrl.session == NULL) {
+               len += scnprintf(msg + len, msglen - len,
+                                "not a controlling terminal\n");
+               goto out;
+       }
+
+       if (tty->ctrl.pgrp == NULL) {
+               len += scnprintf(msg + len, msglen - len,
+                                "no foreground process group\n");
+               goto out;
+       }
+
+       /* Note that if p is refcounted */
+       p = pick_process(tty);
+       if (p == NULL) {
+               len += scnprintf(msg + len, msglen - len,
+                                "empty foreground process group\n");
+               goto out;
+       }
+
+       mm = get_task_mm(p);
+       if (mm) {
+               rss = get_mm_rss(mm) * PAGE_SIZE / 1024;
+               mmput(mm);
+       }
+       get_task_comm(tname, p);
+       getrusage(p, RUSAGE_BOTH, &rusage);
+       pid = __get_pid(p, tty);
+       state = (char *) get_task_state_name(p);
+       wallclock = ktime_get_ns() - p->start_time;
+       put_task_struct(p);
+
+       /* After this point, any of the information we have on p might
+        * become stale.  It's OK if the status message is a little bit
+        * lossy.
+        */
+
+       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(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\n",
+                        tname, pid, state,
+                        rtime.tv_sec, ns_to_cs(rtime.tv_nsec),
+                        utime.tv_sec, ns_to_cs(utime.tv_nsec),
+                        stime.tv_sec, ns_to_cs(stime.tv_nsec),
+                        pcpu, rss);
+
+out:
+       return len;
+}
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index 6616d4a0d41d..f2f4f48ea502 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -125,7 +125,7 @@ struct ktermios tty_std_termios = { /* for the benefit of 
tty drivers  */
        .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,
diff --git a/include/linux/tty.h b/include/linux/tty.h
index cbe5d535a69d..2e483708608c 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -49,6 +49,7 @@
 #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])
+#define STATUS_CHAR(tty) ((tty)->termios.c_cc[VSTATUS])
 
 #define _I_FLAG(tty, f)        ((tty)->termios.c_iflag & (f))
 #define _O_FLAG(tty, f)        ((tty)->termios.c_oflag & (f))
@@ -114,6 +115,7 @@
 #define L_PENDIN(tty)  _L_FLAG((tty), PENDIN)
 #define L_IEXTEN(tty)  _L_FLAG((tty), IEXTEN)
 #define L_EXTPROC(tty) _L_FLAG((tty), EXTPROC)
+#define L_NOKERNINFO(tty) _L_FLAG((tty), NOKERNINFO)
 
 struct device;
 struct signal_struct;
@@ -389,6 +391,9 @@ extern void __init n_tty_init(void);
 static inline void n_tty_init(void) { }
 #endif
 
+/* n_tty_status.c */
+size_t n_tty_get_status(struct tty_struct *tty, char *msg, size_t msglen);
+
 /* tty_audit.c */
 #ifdef CONFIG_AUDIT
 extern void tty_audit_exit(void);
-- 
2.30.2

Reply via email to