HOME | Links | About | Read

《Unix/Linux编程实践教程》笔记(1)

Table of Contents

内容

第二章讲了文件操作函数(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年后可以加上。