如何编程实现Linux中的who命令?

版权声明:本文为博主原创文章,未经博主允许不得转载。有事联系:[email protected] https://blog.csdn.net/qq_17550379/article/details/89385496

0x00 who命令是什么?

我使用的系统环境是Ubuntu 18.04,通过who命令我们可以知道谁在使用系统:

who命令还有其他3种形式:

  • who am iwho am Iwhoami

0x01 who命令是如何工作的?

我们可以从unix的帮助文档中(man who)找到登录用户信息

If FILE is not specified, use /var/run/utmp. /var/log/wtmp as FILE is common. If ARG1 ARG2 given, -m presumed: ‘am i’ or ‘mom likes’ are usual.

从上述内容中,可以得知相关内容在/var/run/utmp文件中,不同的操作系统不一样。

我们可以在/usr/include/bits/utmp.h文件中查看到相关的文件信息。

struct utmp
{
  short int ut_type;            /* Type of login.  */
  pid_t ut_pid;                 /* Process ID of login process.  */
  char ut_line[UT_LINESIZE]
    __attribute_nonstring__;    /* Devicename.  */
  char ut_id[4];                /* Inittab ID.  */
  char ut_user[UT_NAMESIZE]
    __attribute_nonstring__;    /* Username.  */
  char ut_host[UT_HOSTSIZE]
    __attribute_nonstring__;    /* Hostname for remote login.  */
  struct exit_status ut_exit;   /* Exit status of a process marked
                                   as DEAD_PROCESS.  */
/* 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_TIME64_COMPAT32
  int32_t ut_session;           /* Session ID, used for windowing.  */
  struct
  {
    int32_t tv_sec;             /* Seconds.  */
    int32_t tv_usec;            /* Microseconds.  */
  } ut_tv;                      /* Time entry was made.  */
#else
  long int ut_session;          /* Session ID, used for windowing.  */
  struct timeval ut_tv;         /* Time entry was made.  */
#endif

  int32_t ut_addr_v6[4];        /* Internet address of remote host.  */
  char __glibc_reserved[20];            /* Reserved for future use.  */
};

0x02 使用函数介绍

因为有读写文件操作,我们这里的操作主要有

0x0201 文件打开

所需要的头文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

函数的格式

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname参数是要打开或创建的文件名,和fopen一样,pathname既可以是相对路径也可以是绝对路径。flags参数有一系列常数值可供选择,可以同时选择多个常数用按位或运算符连接起来,所以这些常数的宏定义都以O_开头,表示ormode表示我们创建文件时,赋予该文件的访问权限。常用flags选项

  • O_RDONLY只读打开
  • O_RDWR可读可写打开
  • O_WRONLY只写打开
  • O_APPEND表示追加。如果要打开的文件已有内容,那么此次打开文件写入的数据追加到文件末尾,不进行覆盖。
  • O_CREAT表示如果文件不存在则创建它。要注意的是,使用该选项的话,我们需要同时提供第三个参数mode,也就是需要提供该文件的访问权限。
  • O_EXCL常和O_CREAT一起使用(单独使用无意义),也就是打开文件的时候,如果文件已经存在,那么出错返回。
  • O_TRUNC如果文件已经存在,并且以只写可读可写方式打开,则将其长度截断为0
  • O_NONBLOCK对于设备文件,以O_NONBLOCK方式打开为非阻塞I/O。

0x0202 文件的读写操作

所需的头文件

#include <unistd.h>

函数原型

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

fd表示我们读写操作对应的文件描述符,buf用来存放数据的缓冲区,count要读取和写入的字节数。返回值为实际读取和写入的字节数,出错返回-1

0x0203 文件的关闭操作

所需的头文件

#include <unistd.h>

函数原型

int close(int fd);

fd表示我们需要关闭文件的文件描述符,打开后的文件一定要关闭。关闭的过程中出现错误返回-1

0x0204 关于时间的操作

所需头文件

#include <time.h>

用到的函数

struct tm *localtime(const time_t *timep);

timep是一个指向time_t类型的指针,而time_t实际上是一个整形数据。返回的是一个tm结构体指针

struct tm {
               int tm_sec;    /* Seconds (0-60) */
               int tm_min;    /* Minutes (0-59) */
               int tm_hour;   /* Hours (0-23) */
               int tm_mday;   /* Day of the month (1-31) */
               int tm_mon;    /* Month (0-11) */
               int tm_year;   /* Year - 1900 */
               int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
               int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
               int tm_isdst;  /* Daylight saving time */
           };

通过tm结构体我们可以获取时间数据。

size_t strftime(char *s, size_t max, const char *format,
                       const struct tm *tm);

s指向我们格式化时间字符串后的存储空间,max为该存储空间的大小。format表示我们按照什么格式进行转化,tm就是上面说的结构体。

0x03 编写一个who

0x0301 第一版

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

int main()
{
    struct utmp current_record;
    int utmpfd;
    int reclen = sizeof current_record;
    if ((utmpfd = open(UTMP_FILE, O_RDONLY)) == -1)
    {
        perror(UTMP_FILE);
        exit(1);
    }

    while (read(utmpfd, &current_record, reclen) == reclen)
    {
        show_info(&current_record);
    }
    close(utmpfd);
    return 0;
}

我们需要写一个打印信息的函数show_info

#define SHOWHOST

void show_info(struct utmp* utbufp)
{
    printf("%-8.8s", utbufp->ut_name);
    printf(" ");
    printf("%-8.8s", utbufp->ut_line);
    printf(" ");
#ifdef SHOWHOST
    printf("(%s)", utbufp->ut_host);
#endif
    printf("\n");
}

此时我们的结果是

reboot ~ (4.15.0-47-generic)
lly :0 (:0)
runlevel ~ (4.15.0-47-generic)

而使用系统函数who的结果是

lly :0 2019-04-18 15:31 (:0)

我们需要完善我们的程序,也就是在打印函数show_info部分添加打印时间的功能。

0x0302 第二版

我使用的机器是64位的操作系统,通过查看struct utmp,我们知道时间是存在struct timeval ut_tv;中,我们现在需要查看timeval是个什么东西?linux不同版本位置不同,我是在/usr/include/bits/types/struct_timeval.h位置下找到的这个文件

struct timeval
{
  __time_t tv_sec;              /* Seconds.  */
  __suseconds_t tv_usec;        /* Microseconds.  */
};

我们常用的时间操作类型是time_t,所以我们需要将timeval转化为time_t,然后通过调用ctime函数就可以得到真实时间。

我们可以写出show_time函数

void show_time(struct timeval* utimep)
{
   struct tm* lt = localtime((time_t*)&(utimep->tv_sec));
   char str_time[100];
   strftime(str_time, sizeof str_time, "%Y-%m-%d %H:%M:%S", lt);
   printf("%s", str_time);
}

但是我们上面的代码依旧不好,因为我们每次值是读取一条数据,这显然非常低效率,怎么快速高效的读取多条记录呢?添加缓冲机制。

0x0303 第三版

我们可以编写一个缓冲区,设置缓冲区的大小可以容纳16utmp结构体。

#define NRECS 16
#define NULLUT ((struct utmp *)NULL)
#define UTSIZE (sizeof(struct utmp))

static char utmpbuf[NRECS * UTSIZE];
static char num_recs;
static int cur_rec;
static int fd_utmp = -1;

utmp_open(char *filename)
{
    fd_utmp = open(filename, O_RDONLY);
    cur_rec = num_recs = 0;
    return fd_utmp;
}

struct utmp* utmp_next()
{
    if (fd_utmp == -1) return NULLUT;
    if (cur_rec == num_recs && utmp_reload() == 0) return NULLUT;

    struct utmp* recp = (struct utmp*)&utmpbuf[cur_rec * UTSIZE];
    ++cur_rec;
    return recp;
}

int utmp_reload()
{
    int 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);
}

我们的主函数需要稍加修改

int main()
{
    struct utmp *utbufp, *utmp_next();

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

    while ((utbufp = utmp_next()) != NULL)
    {
        show_info(utbufp);
    }
    utmp_close();
    return 0;
}

我将该问题的其他语言版本添加到了我的GitHub Linux

如有问题,希望大家指出!!!

猜你喜欢

转载自blog.csdn.net/qq_17550379/article/details/89385496