Introduction to Linux Programming - Implementation of who instructions

Posted by blanius on Fri, 10 Dec 2021 02:19:37 +0100

The previous article briefly introduced some concepts and knowledge of Linux system programming. Starting from this article, we explained the functions of system commands, and gradually explained Linux system programming from simple to deep.

It is suggested that learners should have a certain foundation of C language and understand the concepts of array, structure, pointer and linked list.

Code experiment environment

Operating system: Ubuntu 18.04 LTS

Compiler gcc version: gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 0

Learning objectives

By analyzing who instructions, we can learn the file reading operation of Linux.

who instruction introduction

Linux is a multi-user operating system. Sometimes you need to check whether the system is busy and whether someone is using the system. You can use the who instruction to check the active users in the Linux system.

Commands are also programs. In Linux system, almost all commands are man-made programs. Adding new commands to the Linux system is very simple. Put the executable files in any of the following directories: / bin, / usr/bin, / usr/local/bin. These directories store many system commands.

If you want to know who is using the system, enter the who command and the output is as follows:

$ who

user :0 2021-10-31 21:42 (:0)

test pts/1 2021-10-31 23:19 (192.168.0.104)

Each row represents a logged in user. The first column is the user name, the second column is the terminal name, the third column is the login time, and the fourth column is the user's login address.

who instruction details

We can view the usage and detailed explanation of who through the online help instruction man. To view who's help, you can enter:

$ man who

Online help content for Linux system:

Name: the name of the command and a brief description of the command.

SYNOPSIS: give the usage description of the command, including the command format, parameter and OPTION list. Square brackets ([OPTION]) are optional. The OPTION is a short line plus any combination of abdHlmpqrstTu. There can also be a file parameter or two parameters at the end of the command.

DESCRIPTION: a detailed DESCRIPTION of an instruction. The DESCRIPTION varies according to the instruction and platform.

OPTIONS: gives a description of each option on the command line.

AUTHOR: the AUTHOR of the command.

SEE ALSO: contains other topics related to this command.

How does the who directive work

Scroll down to the man who command to see the help information. There are the following information

The circled content indicates that if the who command does not specify a file, / var/run/utmp and / var/log/wtmp are usually used as option files.

/The var/run/utmp file saves the user information of the current login system

/The var/log/utmp file saves the user information that has logged in to the system

who obtains the login user information of the current system by reading the file / var/run/utmp.

The utmp file stores the structure array. The array elements are utmp type structures. You can find the definition of utmp type in utmp.h. The utmp.h file is stored in the / usr/include directory.

The contents of the file * * / usr/include/utmp.h * * are as follows (irrelevant codes have been deleted):

#ifndef    _UTMP_H
#define    _UTMP_H 1

#include <features.h>
#include <sys/types.h>

__BEGIN_DECLS

/* Get system dependent values and data structures.  */
#include <bits/utmp.h>

/* Compatibility names for the strings of the canonical file names.  */
#define UTMP_FILE    _PATH_UTMP
#define UTMP_FILENAME    _PATH_UTMP
#define WTMP_FILE    _PATH_WTMP
#define WTMP_FILENAME    _PATH_WTMP

#endif    /* Use misc.  */

__END_DECLS

#endif /* utmp.h  */

The specific structure of utmp is defined in bits/utmp.h

#define EMPTY            0 
#define BOOT_TIME        2 
#define NEW_TIME         3 
#define OLD_TIME         4 
#define INIT_PROCESS     5 
#define LOGIN_PROCESS    6 
#define USER_PROCESS     7 
#define DEAD_PROCESS     8 
#define ACCOUNTING       9 

#define UT_LINESIZE     32
#define UT_NAMESIZE     32
#define UT_HOSTSIZE     256

struct exit_status {
  short int e_termination; 
  short int e_exit;
};

struct utmp {
  short   ut_type; 
  pid_t   ut_pid;
  char    ut_line[UT_LINESIZE]; 
  char    ut_id[4]; 

  char    ut_user[UT_NAMESIZE]; 
  struct  exit_status ut_exit;
#if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32
  int32_t ut_session;
  struct {
    int32_t tv_sec;
    int32_t tv_usec;
  } ut_tv;
#else
  long   ut_session; 
  struct timeval ut_tv;
#endif

  int32_t ut_addr_v6[4];
  char __unused[20];
};

/* Backward compatibility definition */
#define ut_name ut_user
#ifndef _NO_UT_TIME
#define ut_time ut_tv.tv_sec
#endif
#define ut_xtime ut_tv.tv_sec
#define ut_addr ut_addr_v6[0]

It can be seen from the above analysis that who obtains the required information by reading files, and each logged in user has corresponding records in the files. The workflow of who can be represented by the following figure:

/The structure array in var/run/utmp file stores the information of logged in users. Is the implementation of who instruction to read and display the records one by one? Let's continue to analyze.

Implement the who command

When writing who programs, you need to do two things:

  • Read data structure information from the file (/ var/run/utmp)
  • Display the information in the structure in an appropriate form
Step 1: read information

To read data from a file, the Linux system provides three system functions: open(), read(), and close().

  • open() -- open a file

The definition of open under Linux and the header file required to call the function are as follows:

#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);

The first parameter pathname of the function is the pathname or file name of the file to be opened.

The second parameter, flags, indicates the operation modes of opening files (there are three kinds): read-only (O_RDONLY), write only (O_WRONLY), read-write (O_RDWR). When calling this function, you must specify one of them. There are other optional modes, which will not be introduced at the moment.

The third parameter, mode, indicates the initial value of setting file access permission, which is related to the user mask umask. This parameter is not used for the time being.

When opening a file, if the operation is successful, the kernel will return a positive integer value, which is called the file descriptor. If the kernel detects a task error, the system call will return - 1.

To operate on a file (read or write), you must first open the file. After the file is successfully opened, you can operate on the file through the file descriptor.

  • read() -- read data from a file

The definition of read in Linux and the header file required to call the function are as follows:

#include <unistd.h>

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

The first parameter fd of the function is the file descriptor, which is returned by the open function.

The second parameter buf is the memory space for reading data.

The third parameter, count, is the number of data you want to read.

If the reading is successful, the number of bytes of the read data is returned; otherwise, - 1 is returned.

Note: the final data read may not be as much as required. For example, if the number of data remaining in the file is less than the number required to be read, the program can only read the number of data remaining in the file. When reading to the end of the file, the function returns 0.

  • close() -- close the file

The definition of clsoe under Linux and the header files required to call functions are as follows:

#include <unistd.h>

int close(int fd);

Close this system function will close the opened file, and fd will open the descriptor returned by the file for the open() function. If the closing is wrong, the function returns - 1. If the closing is successful, it returns 0.

After the operation on the file is completed, the file needs to be closed to reduce the occupation of memory resources.

Step 2: display information

The utmp record information is displayed in a fixed width format through the printf function.

Step 3: code implementation

The preliminary code implementation is as follows:

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

#define SHOWHOST

void show_info(struct utmp *utbufp);

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;
}

// display information
void show_info(struct utmp *utbufp)
{
    printf("%-8.8s", utbufp->ut_name);
    printf(" ");
    printf("%-8.8s", utbufp->ut_line);
    printf("%10d", utbufp->ut_time);
    printf(" ");

#ifdef SHOWHOST
    printf("(%s)", utbufp->ut_host);
#endif

    printf("\n");
}

compile

$gcc who1.c -o who1

The operation results are as follows

$./who1
reboot ~ 1635728912 (4.15.0-161-generic)

runlevel ~ 1635729058 (4.15.0-161-generic)

user :0 1635729148 (:0)

test pts/2 1635763291 (192.168.0.104)

Compare the above output results with the system who command output:

$ who

user :0 2021-11-01 09:12 (:0)

test pts/2 2021-11-01 18:41 (192.168.0.104)

The WHO written by yourself can work and display user name, terminal name and remote host name. However, compared with the who of the root system, it is not perfect. There are two aspects that need to be improved:

(1) Eliminate Blank Records

(2) Correctly display the login time

Program code optimization

  • Eliminate Blank Records

The system who command only lists the information of logged in users. The code we write will not only list the logged in users, but also display other information in the utmp file. In fact, utmp contains the information of all terminals, and the terminal information that has not been used will also be stored in utmp.

There is a member ut in the utmp structure_ Type, when its value is 7 (USER_PROCESS), it indicates that this is a logged in user. Accordingly, the information function show is displayed for the original program_ Add user type judgment at the beginning of info() function to eliminate Blank Records:

if(utbufp->ut_type != USER_PROCESS)
{
    return;
}
  • Make the display of login time readable

Time in Linux is represented by an integer of type time_t. Its value is the number of seconds since 0:00 on January 1, 1970. Structure of storage time_t is actually long int. Type time_t is defined as

typedef long int time_t;

You need to convert the integer value of time into an easy to understand form. The time format displayed by who instruction in the experimental environment system is as follows

2021-10-31 23:19

We need to get the information such as year, month, day, hour, minute, etc. from the second value of the stored time. That is, you need to convert the time seconds stored in Linux into decomposition time. The decomposition time storage structure type is tm, and its structure is defined as follows

struct tm 
{  
  int tm_sec;    /* Seconds (0-59) */  
  int tm_min;    /* Min (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;   /* Years since 1900 */  int tm_wday;   /* Day of the week (0-6, Sunday = 0) */  
  int tm_yday;   /* Day of the year (0-365, 1 Jan = 0) */  
  int tm_isdst;  /* Daylight saving time */  
};

Use the localtime() function to convert the time seconds into the decomposition time, which is expressed in the local time zone. It is defined as follows

#include <time.h>

struct tm *localtime(const time_t *timep);

The argument to the function is a pointer to time_t, which returns a pointer to the tm structure.

  • Code optimization

Combine the above two points to optimize the code.

Optimization information display function show_info is as follows:

void show_info(struct utmp *utbufp)
{    
	if(utbufp->ut_type != USER_PROCESS) 
	{       
		return; 
	}   
	printf("%-8.8s", utbufp->ut_name);
	printf(" ");
	printf("%-8.8s", utbufp->ut_line);
	show_time(utbufp->ut_time); printf(" ");
#ifdef SHOWHOST 
	printf("(%s)", utbufp->ut_host);
#endif
	printf("\n");
}

Add time display function show_time is as follows

void show_time(time_t timeval)
{
	struct tm *info = NULL;
	info = localtime(&timeval);
	printf("%4d-%2d-%02d %02d:%02d", (info->tm_year + 1900), (info->tm_mon + 1), \
				info->tm_mday, info->tm_hour, info->tm_min);
}

After compilation, the running results are as follows

$./who2

user :0 2021-11-01 09:12 (:0)

user pts/2 2021-11-01 18:41 (192.168.0.104)

The displayed results are basically consistent with the who command of the system.

Summary

This article introduces the working principle of who command in Linux system, and realizes who command by yourself to learn the file reading operation of Linux programming. And learned the utmp file structure of login information and the time processing of Linux.

System functions involved: open, read, close, localtime

Relevant instructions: man, who

follow-up

Next, learn the write file operation of Linux file operation.

--------------

The official account [learning embedded together], learning together and growing together

Topics: Linux