最近在一边学习C一边看《Unix-Linux编程实践教程》。这本书在去年就已经看过一遍了,其中的例子当时也跟着编过。本来是最近是准备看《UNIX环境高级编程》这本书的,突然发现《Unix-Linux编程实践教程》的内容都已忘的差不多了,就决定先再把它看一遍。
既然要拿起来在学,就好好学一下吧。决定自己先不看书,自己动手先再重写一遍。根据书中的套路(这个现在居然还记得):1、这个程序能干嘛?2、这个程序是如何实现的?3、自己动手写一个。在这几个过程中终于是体会到在linux底下写程序的方便了,主要是依靠man。
程序涉及到的主要有utmp结构体:
struct utmp {
short ut_type; /* Type of record */
pid_t ut_pid; /* PID of login process */
char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */
char ut_id[4]; /* Terminal name suffix,
or inittab(5) ID */
char ut_user[UT_NAMESIZE]; /* Username */
char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or
kernel version for run-level
messages */
struct exit_status ut_exit; /* Exit status of a process
marked as DEAD_PROCESS; not
used by Linux init(8) */
/* 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 == 64 && defined __WORDSIZE_COMPAT32
int32_t ut_session; /* Session ID (getsid(2)),
used for windowing */
struct {
int32_t tv_sec; /* Seconds */
int32_t tv_usec; /* Microseconds */
} ut_tv; /* Time entry was made */
#else
long ut_session; /* Session ID */
struct timeval ut_tv; /* Time entry was made */
#endif
int32_t ut_addr_v6[4]; /* Internet address of remote
host; IPv4 address uses
just ut_addr_v6[0] */
char __unused[20]; /* Reserved for future use */
};
可以看到who命令所需要展示的几乎所有信息在这个结构体中都有。那这个结构体中的数据又要去哪里找呢?继续查看utmp.h 文件,发现有下面一个宏定义:
#define UTMP_FILE _PATH_UTMP
继续用grep命令查找发现_PATH_UTMP 是在这里定义的
/usr/include/paths.h:64:#define _PATH_UTMP "/var/run/utmp"
到现在应该是可以肯定utmp结构体书储存在/var/run/utmp 这个文件中的了。
为了从文件中读取数据,就需要了解几个同文件读取有关的函数了。我们需要的操作应该是打开UTMP_FILE,读取其中的数据,最后关闭这个文件。作为一个C语言初学者,一开始想到的可能是fopen、fscanf和fclose这三个函数。但是fscanf无法读取一个数据结构的数据。很自然的就会用到open、read和close这几个系统调用了。下面是这几个函数的基本信息:
int open(const char *pathname, int flags);
ssize_t read(int fd, void *buf, size_t count);
int close(int fd);
其中read是根据字节数来读取文件中内容的,我们当然可以获取一个结构体的大小。有了以上的这些内容就可以开始写who程序了:
#include
#include
#include
#include
#include
#define UTMP_SIZE (sizeof(struct utmp))
void inline disUInfo(struct utmp uInfo)
{
time_t tmpTime;
struct tm *newTm;
tmpTime = uInfo.ut_time;
newTm = localtime(&tmpTime);
printf("%10.10s ", uInfo.ut_user);
printf("%10.10s ", uInfo.ut_line);
printf("%4d-%02d-%02d %02d:%02d ", newTm->tm_year + 1900,
newTm->tm_mon + 1,
newTm->tm_mday,
newTm->tm_hour,
newTm->tm_min);
// printf("%20.20s ", ctime(&tmpTime));
printf("[%20.20s]", uInfo.ut_host);
printf("\n");
}
int main(int argc, char *argv[])
{
int fd; //file discriptor
/*
* you can just define a struct but not a pointer
*/
struct utmp *uInfo = malloc(UTMP_SIZE);
fd = open(UTMP_FILE, O_RDONLY);
if (-1 == fd) {
perror(UTMP_FILE);
exit(EXIT_SUCCESS);
}
while (UTMP_SIZE == read(fd, uInfo, UTMP_SIZE)) {
if (uInfo->ut_type == USER_PROCESS) { // Normal process
disUInfo(*uInfo);//trans value do not trans the pointer
}
}
close(fd); //don't forget close the fd
free(uInfo);//don't forget to free the memory
return 0;
}
将这个程序和之前我写的程序进行比较时有两点是需要总结的:
1、在之前写的程序中指向结构体utmp的指针uinfo我没有初始化就直接使用了。OMG,程序跑起来当然是没什么问题的,但是肯定是修改系统中其他地方的内存了(跑了这么多次系统居然没崩溃…)。这就是C语言不强制要求指针使用之前必须初始化,并且自己不小心的结果吧。下面只将uinfo指针的值初始化为null,看运行程序有什么结果。
struct utmp *uinfo = NULL //只将上面的程序修改这一行
如果这么改的话main函数一进去就退出了,也没报错感觉到有点奇怪。这个问题暂且记下以后找个时间专门研究一下。
2、在之前的程序中我写了一个函数,现在看能没有任何作用。现在我也看不出有什么作用了,主要还是因为没写好注释的缘故。以后再写程序不能在忽视注释了。
在写这个程序的过程中有几点需要总结的:
1、在写的过程中居然不大确定printf格式符 %m.nX 中m、n的含义了。在这里在写一遍:a、对于基本类型:m指定了要显示的最少字符数。如果要显示的数值所需的字符数少于m,那么值在显示宽度内是右对齐的。如果要显示的值所需的字符数多于m,那么显示宽度会自动扩展为所需的尺寸。n表示小数点后的位数。b、m的含义同基本类型相同,n表示只显示字符串的前n个字符。
2、关于时间的显示转换,我最开始用的是ctime、后来用gmtime最后在用localtime的。ctime的返回值是一个字符串,其格式是:"Wed Jun 30 21:49:08 1993\n";gmtime和localtime的返回值都是一个tm结构体(可以去查man)。不同的是localtime的tm_hour会加上你所在的时区。这两个函数的年份你要加上1900,月份是从0开始算的要正常显示的话得加上1.