《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年后可以加上。