HOME | Links | About | Read

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

Table of Contents

内容

“一切皆文件”,目录也是文件,opendir() 打开目录,readdir()从目录中读取内容,closedir()关闭目录。stat()函数可以得到文件的属性,在将st_mode(文件类型和许可权限)显示为rwx这种形式时,使用了掩码屏蔽不需要的字段,得到需要的字段。st_mode中有三个特殊的位,分别是执行时设置用户id,执行时设置组id,对于目录受限制删除标志。最后展示了几个相关的系统函数,chmod(),chown(),utime(),rename()。getpwnam(username)可以将username转换成uid,getpwuid(uid)可以将uid转换成username。

习题

3.1

man 3 readdir 可得到答案,大意是因为posix定义d_name为 char d_name[]数组。

3.3

usermode,useradd都无法使用户的ID相同,可以先新建两个用户,再修改etc/passwd 和/etc/group中的内容。实践证明,系统是以ID判别用户的,相同ID即为相同用户。

3.8

对于目录来说,执行权限就是切换和搜索目录的权限。关掉目录执行权限的话,可以查看目录里的内容,但是无法切换到此目录和搜索目录内容。

3.9

login源码在util-linux包中,修改终端设备文件所有者的代码是chown_tty。注销时,终端设备文件所有者改回成root的程序暂时不知道。

3.10 ls分列输出

man 4 tty_ioctl可得到获取终端大小的信息。如下代码实现了ls的分列输出。

#include	<stdio.h>
#include	<stdlib.h>
#include	<sys/types.h>
#include	<dirent.h>
#include	<termios.h>
#include	<sys/ioctl.h>
#include	<unistd.h>
#include	<string.h>

char filename[128][128];//存储文件名
int lenoffilename[128];//存储文件名长度
int max[128];//存储每列文件名的最大长度

int getcol( int tmpnumfile ,int tmpwscol )
{
	int col = 0; //最终输出的列数
	int sum = 0; //存储文件明长度之合
	int k = 0;
	int i = 0;

	//得到初始的列数
	for (i = 0; i < tmpnumfile; i++)
	{
		max[i] = lenoffilename[i];
		sum += lenoffilename[i]+2;
		if (sum >= tmpwscol)
		{
			col = i;
			max[i] = 0;
			break;
		}
	}
	//所有文件不足一行,返回col
	if (i >= tmpnumfile)
	{
		col = i;
		return col;
	}
	
	memset(max, 0, 128*sizeof(int)); //数组置0
	for (i = 0; i < col; i++)
	{
		//取得这一列的最大文件长度,存储到max数组
		for (int j = 0; ; j++)
		{
			if ( (i+j*col) <= tmpnumfile )
			{
				if (lenoffilename[i+j*col] > max[i])
					max[i] = lenoffilename[i+j*col];
			}
			else
				break;
		}
		
		/*判断每列最大文件名长度之和是否超出终端宽度
		 *如果是,则col减1, i设为-1(for循环i++,i就变成0),max数组置0, 跳出for k循环
		 *如果否,不做处理,继续
		 */
		sum = 0;
		for (k = 0; k < col; k++)
		{
			sum += max[k]+2;
			if (sum >=tmpwscol)
			{
				col--;
				i = -1;
				memset(max, 0, 128*sizeof(int));
				break;

			}
		}
	}

	return col;
}

/* 得到文件名,存储到filename数组
 * 得到文件名长度,存储到lenoffilename数组
 * 返回文件数目
 */
int getfilename(char *dirname )
{
	DIR		*dir_ptr;		/* the directory */
	struct dirent	*direntp;		/* each entry	 */
	int numfile = 0;
	if ( ( dir_ptr = opendir( dirname ) ) == NULL )
		fprintf(stderr,"ls1: cannot open %s\n", dirname);
	else
	{
		while ( ( direntp = readdir( dir_ptr ) ) != NULL )
		{
			//printf("%s\n", direntp->d_name );
			strcpy(filename[numfile], direntp->d_name);
			lenoffilename[numfile++] = strlen(direntp->d_name);
		}
		closedir(dir_ptr);
	}

	return numfile;

}
int main(int ac, char *av[])
{
	struct winsize size;
	int tmpnumfile, col;
	if (isatty(STDOUT_FILENO) == 0)
		exit;
	//得到终端的size
	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0)
	{
		perror("ioctl TIOCGWINSZ error");
		exit(1);
	}

	if ( ac == 1 )
	{
		tmpnumfile = getfilename( "." );
		col = getcol( tmpnumfile, size.ws_col);
		do_ls(col, tmpnumfile);
	}
	else
		while ( --ac )
		{
			printf("%s:\n", *++av );
			tmpnumfile = getfilename( *av );
			col = getcol( tmpnumfile, size.ws_col );
			do_ls(col, tmpnumfile);
		}

	return 0;
}

void do_ls( int col, int tmpnumfile)
{
	for (int i = 0; i < tmpnumfile; i++)
	{
		printf ("%s  ", filename[i]);

		//输出空格补全
		for( int j = 0; j < (max[i%col]-lenoffilename[i]); j++)
			printf (" ");
		if ( (i+1)%col == 0)
			printf ("\n");

	}
	printf ("\n");
	return;
}

3.11 使ls接收目录参数

do_ls中传给dostat()的只是一个文件名,没有将路径一并传进去。为了让ls2能接收其他目录的作为参数,所以修改了do_ls函数,拼接好一个完整的文件路径,传给dostat函数。do_ls函数代码如下:

do_ls( char dirname[] )
/*
 *	list files in directory called dirname
 */
{
	DIR		*dir_ptr;		/* the directory */
	struct dirent	*direntp;		/* each entry	 */
	char *tmpdirname;
	int len = strlen(dirname);
	char *sep = "/";

	if ( ( dir_ptr = opendir( dirname ) ) == NULL )
		fprintf(stderr,"ls2: cannot open %s\n", dirname);
	else
	{
		while ( ( direntp = readdir( dir_ptr ) ) != NULL )
		{
			tmpdirname = (char *)malloc(strlen(direntp->d_name)+len+2);
			memset(tmpdirname, 0, strlen(direntp->d_name)+len+2);
			
			strcat (tmpdirname, dirname);

			if (tmpdirname[len-1] != '/')
				strcat(tmpdirname, sep);
			
			strcat(tmpdirname, direntp->d_name);
			dostat(tmpdirname);
			free(tmpdirname);
			tmpdirname = NULL;
		}
		closedir(dir_ptr);
	}
}

3.12 ls显示特殊权限

man 2 stat可以得到特殊权限的掩码,输出显示时还需要注意,如果文件或者目录没有对应的x权限,对应位置上的s或者t应是大写的S或者T。实现代码如下:

permbits( permval, specpos, bit0, string )
char *string;
char bit0;
/*
 *	convert bits in permval into chars rw and x
 */
{
	//printf ("bit0 is%c\n", bit0);
	
	if ( permval & 4 ) //这里是xxx & 0000000100
		string[0] = 'r';
	if ( permval & 2 )
		string[1] = 'w';
	if ( permval & 1 )
	{
		if (specpos & 1)
		{
			if (bit0 == 'd')
				string[2] = 't';
			else
				string[2] = 's';
		}
		string[2] = 'x';
	}
	else
	{
		if (specpos & 1)
		{
			if (bit0 == 'd')
				string[2] = 'T';
			else
				string[2] = 'S';
		}
	}
}

3.13 使cp支持第二个参数是目录

代码如下:

main(int ac, char *av[])
{
        int     in_fd, out_fd, n_chars, len;
        char    buf[BUFFERSIZE];
		struct stat info;
		char *pathtofile;
		char *sep = "/";
						/* check args 	*/
        if ( ac != 3 ){
                fprintf( stderr, "usage: %s source destination\n", *av);
                exit(1);
        }
						/* open files	*/

        if ( (in_fd=open(av[1], O_RDONLY)) == -1 )
                oops("Cannot open ", av[1]);

		//加判断,判断第二个参数是否是目录

		if (stat(av[2], &info) == -1)
			perror( av[2]);
		if ( (info.st_mode & S_IFMT) == S_IFDIR )
		{
			len = strlen(av[2]);
			pathtofile =  (char *)malloc(strlen(av[1])+len+2);
			memset(pathtofile, 0, strlen(av[1]+len+2));

			strcat(pathtofile, av[2]);
			if (pathtofile[len-1] != '/')
				strcat(pathtofile, sep);

			strcat(pathtofile, av[1]);

			if ( (out_fd=creat( pathtofile, COPYMODE)) == -1 )
                		oops( "Cannot creat", av[2]);
			free(pathtofile);
			pathtofile = NULL;
				
		}
		else 
		{
			if ( (out_fd=creat( av[2], COPYMODE)) == -1 )
                oops( "Cannot creat", av[2]);
		}
	
						/* copy files	*/

        while ( (n_chars = read(in_fd , buf, BUFFERSIZE)) > 0 )
                if ( write( out_fd, buf, n_chars ) != n_chars )
                        oops("Write error to ", av[2]);
	if ( n_chars == -1 )
			oops("Read error from ", av[1]);

						/* close files	*/

        if ( close(in_fd) == -1 || close(out_fd) == -1 )
                oops("Error closing files","");
}

3.14 使cp支持第一个参数使目录

修改cp1.c,使得可以将第一个目录下的所有文件拷贝到第二个目录下

include        <stdio.h>
#include        <unistd.h>
#include        <fcntl.h>
#include		<sys/stat.h>
#include		<string.h>
#include		<stdlib.h>
#include		<dirent.h>

#define BUFFERSIZE      4096
#define COPYMODE        0644

void oops(char *, char *);
void cpfile(const char *pf, const char *todir);
char *combpath(const char *dir, const char *pf);

char *sep = "/";
char    buf[BUFFERSIZE];
main(int ac, char *av[])
{
        int     in_fd, out_fd, n_chars, len;
		char *pathtofile;
		struct stat info1, info2;
		DIR		*dir_ptr;
		struct dirent *direntp;
						/* check args 	*/
        if ( ac != 3 ){
                fprintf( stderr, "usage: %s source destination\n", *av);
                exit(1);
        }
						/* open files	*/
		/*
        if ( (in_fd=open(av[1], O_RDONLY)) == -1 )
                oops("Cannot open ", av[1]);
		*/

		//加判断,判断第二个参数是否是目录
		
		if (stat(av[2], &info2) == -1)
			perror( av[2]);
		if ( (info2.st_mode & S_IFMT) == S_IFDIR )
		{
			//如果第一个参数是目录,将这个目录下所有文件拷贝到第二个参数的目录下
			if (stat(av[1], &info1) == -1)
				perror( av[1]);
			if ((info1.st_mode & S_IFMT) == S_IFDIR )
			{
				//循环读取av[1]目录下文件,写到新的目录下
				if (( dir_ptr = opendir( av[1] )) == NULL)
					fprintf (stderr, "opendir: cannot open %s\n", av[1]);
				char *tmppathtofile = NULL;
				while ( ( direntp = readdir( dir_ptr )) != NULL)
				{
					printf (" in %s is %s\n", av[1], direntp->d_name);
					if (strcmp(direntp->d_name, ".") == 0 || strcmp(direntp->d_name, "..") == 0)
						continue;

					pathtofile = combpath(av[1], direntp->d_name);
					tmppathtofile = combpath(av[2], direntp->d_name);
					
					cpfile(pathtofile, tmppathtofile);

					free(pathtofile);
					free(tmppathtofile);
					pathtofile = NULL;
					tmppathtofile = NULL;
				}
				return;
			}
			else
			{
				//如果av[1]是文件,av[2]是目录,拼接完整文件路径
				pathtofile = combpath(av[2], av[1]);
			}
				
				
		}
		else 
		{
			//如果av[2]是文件
			cpfile(av[1], av[2]);
			return;
		}
		//av[1]是文件,av[2]是目录,将av[1]拷贝到av[2]目录下
		cpfile(av[1], pathtofile);
		free(pathtofile);
}

void oops(char *s1, char *s2)
{
        fprintf(stderr,"Error: %s ", s1);
        perror(s2);
        exit(1);
}

void cpfile(const char *inpf, const char *outpf)
{
	int out_fd, in_fd, n_chars;
	if ( (out_fd=creat( outpf, COPYMODE)) == -1 )
		oops( "Cannot creat", outpf);

	if ( (in_fd=open( inpf, COPYMODE)) == -1 )
		oops( "Cannot open", inpf);
	//printf ("%s \n", inpf);

	while ( (n_chars = read(in_fd , buf, BUFFERSIZE)) > 0 )
			if ( write( out_fd, buf, n_chars ) != n_chars )
					oops("Write error to ", outpf);
	if ( n_chars == -1 )
		oops("Read error from ", inpf);

	if ( close(in_fd) == -1 || close(out_fd) == -1 )
			oops("Error closing files","");

}
char * combpath(const char *dir, const char *pf)
{
	int len;
	len = strlen(dir);
	char *pathtofile =  (char *)malloc(strlen(pf)+len+2);
	memset(pathtofile, 0, strlen(pf)+len+2);

	strcat(pathtofile, dir);
	if (pathtofile[len-1] != '/')
		strcat(pathtofile, sep);

	strcat(pathtofile, pf);

	return pathtofile;

}

3.15 ls根据文件名排序输出

把文件名储存在数组里,再用快排排序。

#include	<stdio.h>
#include	<sys/types.h>
#include	<dirent.h>
#include	<string.h>
#include	<stdlib.h>
char dirlist[128][128];


int cmp(const void *a, const void *b);
main(int ac, char *av[])
{
	if ( ac == 1 )
		do_ls( "." );
	else
		while ( --ac ){
			printf("%s:\n", *++av );
			do_ls( *av );
		}
}

do_ls( char *dirname )
{
	DIR		*dir_ptr;		/* the directory */
	struct dirent	*direntp;		/* each entry	 */

	if ( ( dir_ptr = opendir( dirname ) ) == NULL )
		fprintf(stderr,"ls1: cannot open %s\n", dirname);
	else
	{
		int i = 0;
		while ( ( direntp = readdir( dir_ptr ) ) != NULL )
		{
			strcpy(dirlist[i++], direntp->d_name);
		}
		printf ("sizeof () is %d\n", sizeof(dirlist[0]));
		qsort(dirlist, i, sizeof(dirlist[0]), cmp);
		for (int j = 0; j<i; j++)
			printf ("%s\n", dirlist[j]);
		closedir(dir_ptr);
	}
}
int cmp(const void *ap, const void *bp)
{
	return strcmp((char *)ap, (char *)bp);
}

3.17 权限检查在open进行

用vim打开文件后,将文件的读权限去掉,依然可以正常读写文件。用open函数打开文件,读取一个字节并输出,然后sleep10秒,再读取一个字节输出,在这10秒间把文件的读权限去掉。发现依然能正常读取。 这说明在从文件描述符read的时候没有再去检察文件权限。

3.18 使ls支持递归

让ls1.c支持递规列出文件。观察系统ls命令,是根据目录一层一层的列出文件,所以用数组做了个栈,列完一层后,再去递归列出其中的目录。

#include	<stdio.h>
#include	<sys/types.h>
#include	<dirent.h>
#include	<unistd.h>
#include	<sys/stat.h>
#include	<string.h>

char * combpath(const char *dir, const char *pf);
int main(int ac, char *av[])
{
	if ( ac == 1 )
		do_ls( "." );
	else
		while ( --ac ){
			printf("%s:\n", *++av );
			do_ls( *av );
		}
	return 0;
}

do_ls( char *dirname )
/*
 *	list files in directory called dirname
 */
{
	DIR		*dir_ptr;		/* the directory */
	struct stat info;
	struct dirent	*direntp;		/* each entry	 */
	char tmpchar;
	char *pathtofile;
	char *stack[128]; //存储目录路径的栈
	int num=0;//栈中的元素个数
	if (stat(dirname, &info) == -1) 
		perror(dirname);
	if ((info.st_mode & S_IFMT) == S_IFDIR)
	{
		printf("%s:\n", dirname );
		if ( ( dir_ptr = opendir( dirname ) ) == NULL )
			fprintf(stderr,"ls1: cannot open %s\n", dirname);
		else
		{
			while ( ( direntp = readdir( dir_ptr ) ) != NULL )
			{
				//将目录名和文件名组成完整路径
				pathtofile=combpath(dirname, direntp->d_name);
				tmpchar = direntp->d_name[0];

				//忽略隐藏文件
				if ( tmpchar == '.' )
					continue;

				if (stat(pathtofile, &info) == -1) 
					perror(pathtofile);

				//如果是目录,压栈
				if ((info.st_mode & S_IFMT) == S_IFDIR)
					stack[num++] = pathtofile;
				printf("%s  ", direntp->d_name );
			}
			printf ("\n");
			//处理栈中的目录
			if (num-- >0)
				do_ls(stack[num]);
			closedir(dir_ptr);
		}
	}
}

char * combpath(const char *dir, const char *pf)
{
	    int len;
		len = strlen(dir);
		char *sep = "/";
		char *pathtofile =  (char *)malloc(strlen(pf)+len+2);
		memset(pathtofile, 0, strlen(pf)+len+2);

		strcat(pathtofile, dir);
		if (pathtofile[len-1] != '/')
		strcat(pathtofile, sep);

		strcat(pathtofile, pf);

		return pathtofile;
}

3.20 用户名和用户id互相转换

getpwnam(username)可以将用户名转换成用户id,getpwuid(uid)可以将用户id转换成用户名,具体用法可以在man手册中查到。chown(path, owner, group)可以修改文件所有者和组。

3.21 utime修改文件最后修改时间和访问时间

系统调用utime可以修改文件最后修改时间和最后访问时间。代码如下:

#include        <stdio.h>
#include        <unistd.h>
#include        <fcntl.h>
#include		<time.h>
#include		<sys/types.h>
#include		<sys/stat.h>
#include		<utime.h>

#define BUFFERSIZE      4096
#define COPYMODE        0644

void oops(char *, char *);

main(int ac, char *av[])
{
        int     in_fd, out_fd, n_chars;
        char    buf[BUFFERSIZE];
		time_t	nowtime;
		struct stat info;
		struct utimbuf timebuf;
						/* check args 	*/
        if ( ac != 3 ){
                fprintf( stderr, "usage: %s source destination\n", *av);
                exit(1);
        }
						/* open files	*/

        if ( (in_fd=open(av[1], O_RDONLY)) == -1 )
                oops("Cannot open ", av[1]);

        if ( (out_fd=creat( av[2], COPYMODE)) == -1 )
                oops( "Cannot creat", av[2]);
	
						/* copy files	*/
		stat(av[1], &info);

        while ( (n_chars = read(in_fd , buf, BUFFERSIZE)) > 0 )
                if ( write( out_fd, buf, n_chars ) != n_chars )
                        oops("Write error to ", av[2]);
		if ( n_chars == -1 )
			oops("Read error from ", av[1]);


        if ( close(in_fd) == -1 || close(out_fd) == -1 )
                oops("Error closing files","");

		timebuf.actime = info.st_atime;
		timebuf.modtime = info.st_mtime;
		utime(av[2], &timebuf);
}

void oops(char *s1, char *s2)
{
        fprintf(stderr,"Error: %s ", s1);
        perror(s2);
        exit(1);
}

项目

file命令

man file man magic 可以知道file命令的基本原理。

file命令会对文件进行filesystem tests,magic tests,and language tests,filesystem tests依靠stat()得到文件类型(比如 链接,sockets),magic tests按照系统上的magic file 去匹配可执行文件开头得到一个magic number,如果不是可执行文件,会测试它是否是文本文件,编码是什么。如果是文本类型,会进行language tests,去“猜测”文件内容的语言(比如C源码文件)。

结束

通过这一章的学习,知道了目录组成结构(目录也是文件,目录可以看成文件的列表),知道了如何获取文件详细信息和更改文件属性。