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 <walt@xxxxxxxxxxx> --- 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