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, ¤t_record, reclen) == reclen) { show_info(¤t_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