wtop
原理
/var/run/utmputmp
#define UT_LINESIZE 32
#define UT_NAMESIZE 32
#define UT_HOSTSIZE 256
struct exit_status { /* Type for ut_exit, below */
short e_termination; /* Process termination status */
short e_exit; /* Process exit status */
};
struct utmp {
short ut_type; /* Type of record */
pid_t ut_pid; /* PID of login process */
char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */
char ut_id[4]; /* Terminal name suffix,
or inittab(5) ID */
char ut_user[UT_NAMESIZE]; /* Username */
char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or
kernel version for run-level
messages */
struct exit_status ut_exit; /* Exit status of a process
marked as DEAD_PROCESS; not
used by Linux init(1) */
/* The ut_session and ut_tv fields must be the same size when
compiled 32- and 64-bit. This allows data files and shared
memory to be shared between 32- and 64-bit applications. */
#if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32
int32_t ut_session; /* Session ID (getsid(2)),
used for windowing */
struct {
int32_t tv_sec; /* Seconds */
int32_t tv_usec; /* Microseconds */
} ut_tv; /* Time entry was made */
#else
long ut_session; /* Session ID */
struct timeval ut_tv; /* Time entry was made */
#endif
int32_t ut_addr_v6[4]; /* Internet address of remote
host; IPv4 address uses
just ut_addr_v6[0] */
char __unused[20]; /* Reserved for future use */
};
Go代码
package main
import (
"encoding/binary"
"fmt"
"io"
"os"
)
const (
UT_LINESIZE = 32
UT_NAMESIZE = 32
UT_HOSTSIZE = 256
)
type ExitStatus struct {
Termination int16
Exit int16
}
type TimeVal struct {
Sec int32
Usec int32
}
type Utmp struct {
// https://man7.org/linux/man-pages/man5/utmp.5.html
Type int16 /* Type of record */
_ [2]byte /* 内存对齐,不可少*/
Pid int32 /* PID of login process */
Device [UT_LINESIZE]byte /* Device name of tty - "/dev/" */
Id [4]byte /* Terminal name suffix, or inittab(5) ID */
User [UT_NAMESIZE]byte /* Username */
Host [UT_HOSTSIZE]byte /* Hostname for remote login, or kernel version for run-level messages */
Exit ExitStatus /* Exit status of a process marked as DEAD_PROCESS; not used by Linux init(1) */
Session int32 /* Session ID (getsid(2)), used for windowing */
Time TimeVal /* Time entry was made */
AddrV6 [16]byte /* Internet address of remote host; IPv4 address uses just ut_addr_v6[0] */
Reserved [20]byte
}
func main() {
f, err := os.Open("/var/run/utmp")
if err != nil {
panic("open /var/run/tmp failed: %s" + err.Error())
}
var utmps []*Utmp
for {
utmp := new(Utmp)
err = binary.Read(f, binary.LittleEndian, utmp)
if err == io.EOF {
break
} else if err != nil {
panic("read /var/run/tmp failed: " + err.Error())
}
// #define USER_PROCESS 7 /* Normal process. */
if utmp.Type != 7 || utmp.User[0] == 0 {
// 忽略特殊用户,参考 procps-ng/contrib/utmp.c 源码
continue
}
utmps = append(utmps, utmp)
}
fmt.Printf("%s\t%s\t%s\n", "USER", "TTY", "FROM")
for _, utmp := range utmps {
fmt.Printf("%s\t%s\t%s\n", string(utmp.User[:]), string(utmp.Device[:]), string(utmp.Host[:]))
}
}
测试验证
wgo