Linux系统编程22-简单的who命令实现

发布时间 2023-06-27 15:35:49作者: 言叶以上

实现who命令:

  • 从文件中读取数据结构
  • 将结构中的信息以合适的形式显示出来
  • who的时间表示格式:["2011-01-17 13:40"]

需要用到的函数(unistd.h):

  • 打开一个文件: open(filename, how), 建立文件描述符,连接文件与进程
  • 从文件读取数据: read(fd, buffer, amt),返回所读取的字节数目, 结尾时继续读会返回0
  • 关闭文件: close()

需要访问的结构体:

struct utmp 包含以下成员:

ut_type:登录类型,包括如 INIT_PROCESS、LOGIN_PROCESS、USER_PROCESS 和 DEAD_PROCESS 等。
ut_pid:登录进程的进程 ID。
ut_line:登录用户所使用的设备名,例如 tty1、pts/1 等。
ut_id:登录进程的 ID,通常指在 /etc/inittab 文件中定义的 id。
ut_user:登录用户的用户名。
ut_host:远程登录的主机名。
ut_exit:记录进程退出状态的结构体,只有 ut_type 为 DEAD_PROCESS 时才有意义。
ut_session:会话 ID,用于窗口管理。
ut_tv:记录记录登录或注销时间的时间结构体,包括秒数和微秒数。
ut_addr_v6:远程主机的 IPv6 地址。
__glibc_reserved:保留字段,目前没有使用。

utmp 数据库通常可以在 /var/run/utmp 或者 /var/log/wtmp 文件中找到。
使用 utmp 数据库可以监控用户登录和注销信息,以及查看当前系统中登录的用户信息。

Unix存储时间的方式:time_t类型

  • typedef long int time_t;
  • ctime 将表示时间的整数值转换成人们日常所使用的时间形式:"Wed Jun 30 21:49:08 1993\n"

char* ctime(const time_t* timep);

  • time_t* : 就是long int
  • timep: 要转换的时间戳(秒)

返回: 格式为 www mmm dd hh:mm:ss yyyy\n 的时间字符串指针

  • www是星期几的缩写,如“Mon”、“Tue”等。
  • mmm是月份的缩写,如“Jan”、“Feb”等。
  • dd表示一个月中的第几天。
  • hh表示小时(24小时制)。
  • mm表示分钟。
  • ss表示秒。
  • yyyy表示年份。

strftime 将时间格式化为指定格式的字符串 例如:"%Y-%m-%d %H:%M"
需要struct tm*结构体, 由localtime()根据当前时区转换

版本一:基本的who命令

//suppresses empty records
//formats time nicely

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <utmp.h>
#include <time.h>
#include <fcntl.h>

#define SHOWHOST

void show_time(long int);
void show_time2(long int);
void show_info(struct utmp*);

int main(int argc, char const *argv[])
{
    struct utmp utbuf;
    int utmpfd;

    if((utmpfd = open(UTMP_FILE, O_RDONLY)) == -1){
        perror(UTMP_FILE);
        exit(1);
    }

    //UTMP_FILE 文件中有多条结果,循环读取,一次读取一个 struct utmp
    while(read(utmpfd, &utbuf, sizeof(utbuf)) == sizeof(utbuf))
        show_info(&utbuf);
    
    close(utmpfd);

    return 0;
}

//displays the contents of the utmp struct
//in human readable form
//displays nothing if recond has no user name
void show_info(struct utmp* utbufp)
{
    if(utbufp->ut_type != USER_PROCESS)
        return;
    
    printf("%-8.8s", utbufp->ut_user);
    printf(" ");
    printf("%-12.8s", utbufp->ut_line);
    printf(" ");
    show_time2(utbufp->ut_tv.tv_sec);

    #ifdef SHOWHOST
    //只显示有地址的结果
    if(utbufp->ut_host[0] != '\0')
        printf("(%s)", utbufp->ut_host);
    #endif
    
    printf("\n");
    //joe      pts/0        2023-03-27 13:40 (192.168.111.1)
}

//old version
//format the time 
void show_time(long int timeval){
    char* cp = ctime(&timeval); //"Wed Jun 30 21:49:08 1993\n"
    printf("%12.12s", cp+4);  
    //Jun 30 21:49  
}

//new version
void show_time2(long int timeval){
    struct tm* time_info;
    char time_str[20];

    time_info = localtime(&timeval);
    // 将时间格式化为指定格式的字符串
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M", time_info);
    //2023-03-27 13:40 

    printf("%s ",time_str);
}

版本二:使用缓冲,优化I/O效率

  • 程序cp1读取磁盘上的数据只能通过系统调用read, 而read的代码在内核中,
  • 当read调用发生,执行权会从用户代码转移到内核代码,
  • 频繁在用户空间和系统空间切换很消耗时间,所以减少程序中系统调用的次数可以提高运行效率

优化代码:编写缓冲utmp文件, 让新的who命令使用缓冲

utmplib.c

// functions to buffer reads from utmp file

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <utmp.h>

#define NRECS 16                        // 缓冲区最多存储16条记录
#define NULLUT ((struct utmp*)NULL)     // utmp空指针
#define UTSIZE (sizeof(struct utmp))    // 每条utmp记录的字节数

static char utmpbuf[NRECS * UTSIZE];    //静态字符数组
static int num_recs;                    //缓冲区中记录数
static int cur_rec;                     //当前记录在缓冲区中的下标
static int fd_utmp = -1;                //utmp文件的文件描述符

//打开utmp文件并初始化静态变量
int utmp_open(char* filename){
    fd_utmp = open(filename, O_RDONLY);
    cur_rec = num_recs = 0;

    return fd_utmp;
}

//返回一个指向下一个 utmp 记录的指针
struct utmp* utmp_next(){
    struct utmp* recp;
    //读取出错
    if(fd_utmp == -1)
        return NULLUT;

    //文件的结尾
    if(cur_rec == num_recs && utmp_reload() == 0)
        return NULLUT;
    
    //取到了下一项utmp结构指针
    recp = (struct utmp*)&utmpbuf[cur_rec*UTSIZE];
    
    //当前下标+1
    cur_rec++;

    return recp;
}

//重新加载 utmp 文件
int utmp_reload()
{
    int amt_read;
    amt_read = read(fd_utmp, utmpbuf, NRECS*UTSIZE);
    num_recs = amt_read/UTSIZE;
    cur_rec = 0;

    return num_recs;
}

void utmp_close(){
    if(fd_utmp != -1)
        close(fd_utmp);
}

who.c

//do with buffered reads

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <utmp.h>

#define SHOWHOST

void show_time(long int);
void show_time2(long int);
void show_info(struct utmp*);

int main(int argc, char const *argv[])
{
    struct utmp* utbufp;
    struct utmp* utmp_next();

    if(utmp_open(UTMP_FILE) == -1){
        perror(UTMP_FILE);
        exit(1);
    }

    while((utbufp = utmp_next()) != (struct utmp*)NULL)
        show_info(utbufp);
    
    utmp_close();

    return 0;
}

//displays the contents of the utmp struct
//in human readable form
//displays nothing if recond has no user name
void show_info(struct utmp* utbufp)
{
    if(utbufp->ut_type != USER_PROCESS)
        return;
    
    printf("%-8.8s", utbufp->ut_user);
    printf(" ");
    printf("%-12.8s", utbufp->ut_line);
    printf(" ");
    show_time2(utbufp->ut_tv.tv_sec);

    #ifdef SHOWHOST
    //只显示有地址的结果
    if(utbufp->ut_host[0] != '\0')
        printf("(%s)", utbufp->ut_host);
    #endif
    
    printf("\n");
    //joe      pts/0        2023-03-27 13:40 (192.168.111.1)
}

//old version
//format the time 
void show_time(long int timeval){
    char* cp = ctime(&timeval); //"Wed Jun 30 21:49:08 1993\n"
    printf("%12.12s", cp+4);  
    //Jun 30 21:49 
}

//new version
void show_time2(long int timeval){
    struct tm* time_info;
    char time_str[20];

    time_info = localtime(&timeval);
    // 将时间格式化为指定格式的字符串
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M", time_info);
    //2023-03-27 13:40 

    printf("%s ",time_str);
}