《Unix/Linux编程实践教程》笔记(1)
内容
第二章讲了文件操作函数(open,read,write,lseek,close),并且编程实现了系统命令who的部份功能,通过man手册,知道who命令就是通过读取var/run/utmp得到用户登录信息,随后man utmp得到文件中存储的结构体源码,最后实现一个0.1版本的who命令。接下来解决了空白记录和时间显示的问题,实现了一个更加好用的who命令。2.6节实现了cp命令,read一个文件的内容在write到另一个文件。2.7节,用缓冲区减少文件读写的次数来提高文件IO效率。
习题
2.2
系统重启时,会清除utmp中的登录信息
2.10
whoami命令只会打印执行命令的有效用户,who命令则显示当前系统登录的用户,终端和登录时间等信息。 实现whoami命令需要用到系统函数geteuid,代码如下
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <pwd.h> int main (void){ uid_t uid; struct passwd *puser = NULL; uid = geteuid(); if ((puser = getpwuid(uid)) != NULL) printf ("%s\n",puser->pw_name); else printf ("error\n"); return 0; }
2.11
Linux系统通过inode号来区分文件,所以这里用了系统函数getinode,比较两个文件的inode号就可以判断原文件和目标文件是否是同一个文件。
项目
ac命令
ac命令通过读取wtmp文件,来统计用户登录时间。man utmp可以得到utmp的结构体定义,ut_type USER_PREOCESS 记录用户登入时间,登出时间有几种情况,一是用户正常登出即ut_type为DEAD_PROCESS,二是系统reboot或者shutdown(reboot或者shutdown时,wtmp会增加有ut_name为reboot或者shutdown的记录),三是正登录的用户,此时将这些记录的登出时间设为当前时间。我仅实现了统计所有登录时间的功能,没有系统时间被更改的情况,完整代码如下:
代码
#include <stdio.h> #include <sys/types.h> #include <utmp.h> #include <fcntl.h> #include <time.h> //#define INFOFILE "/var/run/utmp" #define INFOFILE "/var/log/wtmp" struct logutmp { char ut_name[UT_NAMESIZE]; char ut_line[UT_LINESIZE]; time_t logintime; time_t logoutime; }; struct logutmp tmplogutmp[256]; int numuse = 0; long alltime = 0; void countalltime(char *ut_namep, struct utmp *utbufp) { if (utbufp == ((struct utmp*) NULL)) //已经读到wtmp的结尾,把正登录用户的登出时间设为当前时间 { for ( int i =0; i < numuse; i++) { if ( strcmp (tmplogutmp[i].ut_name, "houye") == 0 && tmplogutmp[i].logintime != 0 && tmplogutmp[i].logoutime ==0) { time(&tmplogutmp[i].logoutime); alltime += (long)(tmplogutmp[i].logoutime - tmplogutmp[i].logintime); } } return; } if ( utbufp->ut_type == USER_PROCESS && strcmp(utbufp->ut_name, ut_namep) == 0) //读到wtmp中的登录记录 { int i; for ( i = 0; i < numuse; i++ ) { if ( strcmp ( tmplogutmp[i].ut_line, utbufp->ut_line ) == 0) { tmplogutmp[i].logintime = utbufp->ut_time; tmplogutmp[i].logoutime = 0; break; } } if ( i >= numuse ) { strcpy ( tmplogutmp[numuse].ut_name, utbufp->ut_name ); strcpy ( tmplogutmp[numuse].ut_line, utbufp->ut_line ); tmplogutmp[numuse].logintime = utbufp->ut_time; tmplogutmp[numuse].logoutime = 0; numuse ++; } } if ( utbufp->ut_type == DEAD_PROCESS ) //读到wtmp中的登出记录 ,ut_name 为空,ut_type 设为 { for ( int i = 0; i < numuse; i++ ) { if ( strcmp ( tmplogutmp[i].ut_line, utbufp->ut_line ) == 0 && tmplogutmp[i].logintime != 0) { tmplogutmp[i].logoutime = utbufp->ut_time; printf ("%s %d -- %d\n", tmplogutmp[i].ut_name, tmplogutmp[i].logintime, tmplogutmp[i].logoutime ); alltime += (long)(tmplogutmp[i].logoutime - tmplogutmp[i].logintime); tmplogutmp[i].logintime = 0; break; } } } if ( strcmp(utbufp->ut_name, "reboot" ) == 0 || strcmp(utbufp->ut_name, "shutdown") == 0 ) //读到wtmp中的关机重启记录,将所有记录的登出时间设为关机重启时间 { for (int i = 0; i < numuse; i++) { if ( tmplogutmp[i].logintime != 0) { tmplogutmp[i].logoutime = utbufp->ut_time; alltime += (long)(tmplogutmp[i].logoutime - tmplogutmp[i].logintime); tmplogutmp[i].logintime = 0; } } } return ; } int main(int ac, char **av) { struct utmp *utbufp; /* read info into here */ if ( utmp_open( INFOFILE ) == -1 ) { fprintf(stderr,"%s: cannot open %s\n", *av, UTMP_FILE); exit(1); } do { utbufp = utmp_next(); countalltime( "houye", utbufp ); }while(utbufp != ((struct utmp*) NULL)); utmp_close( ); double t=alltime/3600.0; printf ("%f hours\n", t); return 0; /* went ok */ }
tail命令
tail 是用来输出文件最后部份的命令,可以指定参数输出末尾指定行数,下面的程序即是实现了这一功能。首先定位到末尾,通过判断换行符来判断行数,定位到指定行数的位置,再从此位置读到文件末尾,打印输出。
代码
#include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> off_t nextnewline(int fd) { off_t oldlocal = lseek (fd, 0 ,SEEK_CUR); char buff[16]; off_t local = oldlocal; int i, numofzero; while (lseek(fd, -16, SEEK_CUR)) { memset (buff, '\0', sizeof(buff)); read ( fd, buff, 16 ); for ( i=15, numofzero = 0; i>=0; i--) { if ( buff[i] == '\0' ) numofzero++; if ( buff[i] == '\n' ) break; } if ( i >= 0) break; local -= 16; lseek (fd, -16, SEEK_CUR ); } local = local - (16 - numofzero - i ); lseek ( fd, oldlocal, SEEK_SET); return local; } int main (int argc, char **argv) { int i; int fd; int numline = 10; off_t local = 0; char buff[1025]; if ( argc == 1 ) { printf ("too few argument\n"); exit (-1); } if (argc == 3 && argv[1][0] == '-') { numline = atoi(argv[1]+1); if ( ( fd = open(argv[2], O_RDONLY ) ) == -1 ) { perror( argv[1] ); exit(-1); } } else if ( ( fd = open(argv[1], O_RDONLY ) ) == -1 ) { perror( argv[1] ); exit(-1); } lseek ( fd, 0, SEEK_END); for (i = 0; i <= numline; i++ ) //找到第11个换行符的前一个字符的位置 { if ( (local = nextnewline ( fd )) <= 0) break; lseek (fd, local, SEEK_SET); } lseek (fd, local + 1, SEEK_SET); memset ( buff, '\0', sizeof(buff) ); while ( read (fd, buff, 1024 ) > 0) { printf ("%s", buff); memset (buff, '\0', sizeof(buff) ); } close ( fd ); return 0; }
od命令
od 用于输出文件的八进制,十六进制或者其他格式编码的字节。C语言的%o可以用于输出字符的八进制。下面的程序仅实现了八进制的输出。
代码
#include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> int main (int argc, char **argv) { int fd, i; char buff[16]; int num = 0; if ( argc == 1 ) { printf ("too few argument\n"); exit (-1); } if ( ( fd = open(argv[1], O_RDONLY ) ) == -1 ) { perror( argv[1] ); exit(-1); } memset ( buff, '\0', sizeof(buff)); while (read (fd, buff, 16) > 0) { printf ("%8.8o", num); for ( i = 0; i < 16; i++) { if ( buff[i] == '\0' ) break; printf (" %03o", buff[i]); num ++; } printf ("\n"); memset ( buff, '\0', sizeof(buff)); } printf ("%8.8o\n", num); close ( fd ); return 0; }
结束
一直想好好看看《Unix/Linux编程实践》,奈何没有毅力坚持下去,这可能是第三次开始看这本书,打算用博客来简单的记录下学习的过程。
网络限制越来越严格,Disqus彻底访问不了了,博客访问速度也很慢,有时候甚至打不开页面,暂时去掉Disqus评论框,希望5年后可以加上。