Linux下who命令的实现

1. 实验简介

Linux系统的正常运作需要使用大量与系统有关的数据文件,例如,口令文件 /etc/passwd 和组文件 /etc/group 就是经常被多个程序频繁使用的两个文件。用户每次登陆 Linux系统,以及每次执行 ls -l 命令时都要使用口令文件。本实验中的程序就是这样一个需要调用系统数据文件的程序,只不过调用的是 /var/run/utmp (记录机器的启动时间以及当前登陆用户)和 /var/log/wtmp 文件。由于历史原因,这些数据文件都是 ASCII 文本文件,并且使用标准 I/O 库读这些文件。但是,对于较大的系统,顺序扫描口令文件很花费时间,我们需要能够以非 ASCII 文本格式存放这些文件,但仍向使用其他文本格式的应用程序提供接口。对于这些数据文件的可移植性接口的使用是本次实验的主题。

2. 实验原理

2.1 who命令介绍

who命令的作用是显示目前登录系统的用户信息。执行who命令可得知目前有那些用户登入系统,单独执行who命令会列出登入帐号、使用的终端机、登入时间以及从何处登入或正在使用哪个显示器。

2.2 参数说明

who -H –heading:显示各栏位的标题信息列;
who -q–count:只显示登入系统的帐号名称和总人数。

2.3 utmp 和 wtmp 文件概述

Linux 系统维护着两个包含与用户登录和登出系统有关的信息的数据文件。

  • utmp 文件 维护着当前登录进系统的用户记录以及其他一些信息。
    每一个用户登录进系统时都会向 utmp 文件写入一条记录。在这条记录中包含一个ut_user 字段,它记录着用户的登录名。当用户登出的时候该条记录会被删除。像who之类的程序会使用 utmp 文件中的信息来显示当前登录进系统的用户列表。
  • wtmp 文件 包含着所有用户登录和登出行为的留痕信息以供审计之用。
    每一个用户登录进系统时,写入 utmp 文件中的记录同时会被附加到 wtmp 文件中。当用户登出系统的时候还会向这个文件附加一条记录。这条记录包含的信息与登录记录相同,但 ut_user 字段会被置零。last命令可以用来显示和过滤 wtmp 文件中的内容。
    在这里插入图片描述utmp 和 wtmp 文件包含 对utmpx结构的记录。utmpx结构在<utmpx.h>中定义。关于utmpx结构的定义如下(摘录自《Linux/Unix系统编程手册》一书》):
    在这里插入图片描述
    在这里插入图片描述

2.4 实现原理

读取utmp文件的内容,然后显示记录,最后关闭utmp。实际上就是一个“打开文件——保存文件id——根据文件id读取用户登陆信息——显示在终端 ”的过程。

3. 实验步骤

3.1 main函数

main 函数的主要作用是接收命令行传过来的参数,然后根据不同的参数标记不同的变量,以便 mywho()函数能够根据这些标记的变量分类处理。
main函数里主要涉及到 getopt()这个函数。该函数的定义如下(摘录自《Linux/Unix系统编程手册》一书》):
在这里插入图片描述
getopt() 是一个标准库函数,我们可以通过连续调用getopt()来解析命令行。每次调用都会返回下一个未处理选项的信息。如果找到了选项,那么代表该选项的字符就作为函数结果返回。如果到达了选项列表的结尾,getopt()就返回−1。每次调用 getopt()时,全局变量 optind 都会得到更新,其中包含着参数列表 argv 中未处理的下一个元素的索引。在首次调用 getopt()之前,变量 optind 会自动设为 1(因为argv[0]表示的是程序名,所以argc起始的值为1,且命令行选项是从argv[1]开始算起的)。若检索完所有选项后 argc 不等于 optind,说明存在错误的选项。

main函数代码如下:

int main(int argc, char *argv[])
{
    int c;
    int run;   //判断mywho()是否正常运行的标志
   
    while ((c = getopt(argc, argv, "Hqb")) != -1)
    {
        switch (c)
        {
        case 'H':
            is_H = true;
            break;
        case 'q':
            is_q = true;
            break;
        default:
            exit(1);
        }
    }

    //命令行检错
    if (argc != optind)
        printf("Fault commend rose!\n");

    //调用mywho程序,并检查是否正常运行
    run = mywho();
    if (!run)
        exit(EXIT_SUCCESS);
    else
        exit(1);
}

3.2 设计mywho函数

3.2.1 -q参数解析

-q 参数的原理为:先读取 utmp 文件的数据, 过滤出 utmpx 结构 ut_type 字段为 USER_PROCESS 的记录,然后计数。

  • ut_type 字段是一个整数,它定义了写入文件的记录类型。这里要用到的是USER_PROCESS (7)。它用来记录用户进程(通常是登录会话),用户名会出现在 ut_user 字段中。
  • getutxent() 函数用来顺序读取 utmp 文件中的下一个记录,并以utmpx 结构返回。第一次调用时会取得第一位用户数据,之后每调用一次就会返回下一项数据, 直到已无任何数据时返回NULL。代码如下:
	while ((ut = getutxent())
	{
    	if (ut->ut_type != USER_PROCESS)
        	continue;
    	printf("%-2s  ", ut->ut_user);
    	users += 1;
	}
	printf("\n# users=%d\n", users);
3.2.2 -H 参数解析

-H 选项用于打印输出的各列的含义:

	if (opt_H)
        printf("%-12s%-12s%-20.20s  %s\n", "NAME", "LINE", "TIME", "COMMENT");
3.2.3 不带任何选项解析

不带任何选项用于分别读取 utmp 文件关于用户登陆的信息。这里重点介绍一下timeval结构体和strftime()函数。

  • timeval结构体
    在这里插入图片描述其中,time_t 表示从1970 年 1月 1 日早晨零点(UNIX 系统问世的大致日期)开始到当前时间所间隔的秒数。这个数字显然是相当庞大的。因此我们要把它转换成可读的格式。函数 strftime() 可以为这种转换提供了精确的控制。

  • strftime()函数
    在这里插入图片描述timeptr 指向分解时间,strftime()会将以 null 结尾、由日期和时间组成的相应字符串置于 outstr 所指向的缓冲区中;
    outstr 中返回的字符串按照 format 参数定义的格式做了格式化;
    format参数表示转化的格式有很多种,这里不全部列举,只介绍这里会用到的两种:

    • %F:ISO 日期格式(%Y-%m-%d 格式),形如2019-2-14
    • %R:24 小时制的时间(%H:%M格式 ),形如13:30

    Maxsize 参数指定 outstr 的最大长度。
    如果转化成功,strftime()返回 outstr 所指缓冲区的字节长度,且不包括终止空字节。如果结果字符串的总长度(含终止空字节)超过了 maxsize 参数,那么 strftime()会返回 0 以示出错,且此时无法确定 outstr 的内容。

这部分的代码如下:

    while ((ut = getutxent()))
    {
        if (ut->ut_type != USER_PROCESS)
            continue;
        time_t tm;
        tm = (time_t)(um->ut_tv.tv_sec);
        //将时间转化为可读性较好的形式
        strftime(timebuf, 24, "%F %R", localtime(&tm));
        //打印各类信息
        printf("%-12s%-12s%-20.20s  (%s)\n", ut->ut_user, ut->ut_line, 
        	timebuf, um->ut_host);
    }
    endutxent();
    exit(EXIT_SUCCESS);
3.2.4 mywho()函数汇总

mywho() 函数整合了上面几段代码。首先调用系统数据文件的接口函数,然后逐条将其数据保存下来,再根据选项的标记输出不同的信息,最后关闭系统数据文件。代码如下:

static int mywho()
{
    struct utmpx *ut;

    //用来保存可读性较好的时间字符串
    char timebuf[24];

    //当命令行选项为 -q 时, 显示用户名并保存用户数量
    int users = 0;
    if (is_q)
    {
        while ((ut = getutxent()))
        {
            if (ut->ut_type != USER_PROCESS)
                continue;
            printf("%-2s  ", ut->ut_user);
            users += 1;
        }
        printf("\n# users=%d\n", users);
        // 关闭文件
        endutxent();
        exit(EXIT_SUCCESS);
    }

    //打印各栏标题头部
    if (is_H)
        printf("%-12s%-12s%-20.20s  %s\n", "NAME", "LINE", "TIME", "COMMENT");
    
    //不带选项的设计    
    while ((ut = getutxent()))
    {
        if (ut->ut_type != USER_PROCESS)
            continue;
        //日历时间存储于类型为 time_t
        time_t tm;
        tm = (time_t)(ut->ut_tv.tv_sec);
        //将时间转化为可读性较好的形式
        strftime(timebuf, 24, "%F %R", localtime(&tm));
        //打印各类信息
        printf("%-12s%-12s%-20.20s  (%s)\n", ut->ut_user, ut->ut_line, 
        	timebuf, ut->ut_host);
    }
    endutxent();
    exit(EXIT_SUCCESS);
}

4. 完整代码

将上述所有代码块汇总成who.c文件。完整代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <utmp.h>
#include <time.h>
#include <string.h>
#include <stdbool.h>

//设置标志位,标识命令行参数,默认不使用,设为false
bool is_H = false;
bool is_q = false;

static int mywho()
{
    struct utmpx *ut;
    //用来保存可读性较好的时间字符串
    char timebuf[24];
    //当命令行选项为 -q 时, 显示用户名并保存用户数量
    int users = 0;
    if (is_q)
    {
        while ((ut = getutxent()))
        {
            if (ut->ut_type != USER_PROCESS)
                continue;
            printf("%-2s  ", ut->ut_user);
            users += 1;
        }
        printf("\n# users=%d\n", users);
        // 关闭文件
        endutxent();
        exit(EXIT_SUCCESS);
    }

    //打印各栏标题头部
    if (is_H)
        printf("%-12s%-12s%-20.20s  %s\n", "NAME", "LINE", "TIME", "COMMENT");
        
    //不带选项的设计   
    while ((ut = getutxent()))
    {
        if (ut->ut_type != USER_PROCESS)
            continue;
        //日历时间存储于类型为 time_t
        time_t tm;
        tm = (time_t)(ut->ut_tv.tv_sec);
        //将时间转化为可读性较好的形式
        strftime(timebuf, 24, "%F %R", localtime(&tm));
        //打印各类信息
        printf("%-12s%-12s%-20.20s  (%s)\n", ut->ut_user, ut->ut_line, 
        	timebuf, ut->ut_host);
    }
    endutxent();
    exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[])
{
    int c;
    int run;
    //分析命令行选项
    while ((c = getopt(argc, argv, "Hqb")) != -1)
    {
        switch (c)
        {
        case 'H':
            is_H = true;
            break;
        case 'q':
            is_q = true;
            break;
        default:
            exit(1);
        }
    }
    //命令行检错
    if (argc != optind)
        printf("fault command!\n");
    //调用mywho程序,并检查是否正常运行
    run = mywho();
    if (!run)
        exit(EXIT_SUCCESS);
    else
        exit(1);
}

5. 运行结果

在这里插入图片描述

发布了13 篇原创文章 · 获赞 43 · 访问量 2768

猜你喜欢

转载自blog.csdn.net/qq_42554780/article/details/104294695
今日推荐