Posts Tagged ‘linux’

my_shell.c代码分析

16 7 月, 2010

大三的时候,那时候因为课程需要,我们调试过my_shell.c程序。当时只是对整个程序的结构了解,在涉及linux系统调用函数的时候就不是很清楚”为什么会这样做?”。my_shell.c程序核心函数便是do_cmd函数,此函数是整个程序的核心,负责对用户输入的命令进行执行。

本文便是分析do_cmd函数,以达到对my_shell.c程序有深入的理解。此函数会涉及文件操作中的相关系统调用函数,如果你和我一样调试并分析过前面所说的my_ls.c程序,那么接下来所述的内容对你来说并不困难。

我们按照如下方式定义do_cmd函数:

void do_cmd(int argcount, char arglist[100][256]);

其中,argcount是统计用户输入命令中选项的个数,arglist数组存储每个选项。比如输入:ls -l /那么此时argcount=3,而arglist[0],arglist[1],arglist[2]中分别存储的是:ls,-l,/。

首先此函数会检查用户输入的命令行参数中是否存在后台运行符。利用for循环,对arglist数组中的每个参数与”&”进行比较。由于arglist中的元素是字符串,那么应该选用strcmp函数,而不是利用==来判断。此外即便找到了后台运行符,还应该检查是否”&”位于参数末尾。如果i==argcount-1为假,那么便出错。实现代码如下:

//check if the command include character of running in the background
	for(i=0;i<\argcount;i++)
	{
		if(strncmp(arg[i],"&",1)==0)
		{
			if(i==argcount-1)
			{
				background=1;
				arg[argcount-1]=NULL;
				break;
			}
			else
			{
				printf("wrong command\n");
				return;
			}
		}
	}//for

接下来,检测命令行参数是否含有>,<,|符号,检测方法与上出代码原理类似。由于本程序要求用户输入的命令行参数只能包含上述符号其中之一,所以当flag大于一时,错误。参数中符号合法,接下来提取文件名,存于字符串file中。程序中分别有三个if语句依次针对出现>,<,|时的情况。我们只要分析出现管道符号的情况。

在分析之前首先弄起出管道符号的作用。比如下面命令:

edsionte@edsionte-laptop:~$ ls -l / | wc -c
1513

管道符号前后分别是两个shell命令,前命令的输出作为后命令的输入。

如果检测出命令行中含有管道符号,即how=have_pipe。然后将命令行中两个shell命令分离出来,前命令依然存于arg数组中,而后命令存于argnext数组中。实现命令如下:

if(how==have_pipe)
	{
		for(i=0;arg[i]!=NULL;i++)
		{
			if(strncmp(arg[i],"|",1)==0)
			{
				arg[i]=NULL;
				int j;
				for(j=i+1;arg[j]!=NULL;j++)
				{
					argnext[j-(i+1)]=arg[j];
				}
				argnext[j-(i+1)]=arg[j];
				break;
			}
		}
	}

接下来就要用到进程控制的内容了。我们fork一个新的进程,让它去执行命令行参数中的命令。当命令行中不包含管道符号时,只需fork一个进程,比如ls -l / > a;我们只需fork一个新进程,让其(if(pid==0))执行ls命令即可;否则,我们必须fork两个信进程,前一个进程执行|前的命令,另外一个进程执行|后的命令。至于另个进程如何”通信”,如何让前一个命令的输出称为后者的输入?不着急,后面会详细说明。

1.如果所输命令中不包含<,>,|,how==normal(0);那么调用exe族函数即可让子进程去执行所输的命令。如下:

			if(pid==0)
			{
				if(!(find_command(arg[0])))
				{
					printf("%s: command not found\n",arg[0]);
					exit(0);
				}
				execvp(arg[0],arg);
				exit(0);
			}

find_command函数是在指定目录下去查找命令arg[0]对应的可执行程序。

2.如果命令行中包括输出重定向符号>,how==out_redirect(1);那么需在上述代码中exe函数前添加下面的代码:

				fd=open(file,O_RDWR|O_CREAT|O_TRUNC,0644);
				dup2(fd,1);//make the file as a standard output stream
				execvp(arg[0],arg);//execute the command

前面我们已经提到file中存储的是输出重定向(或输入重定向)的文件名,因此首先应以可读可写方式打开一个文件,并且档要打开的文件存在时,会清空源文件数据,这些我们在前面的博文中都有解释。我们知道标准输出输入的文件描述符为:1,0,利用dup2函数就可以将fd,1同时指向file文件,实现将标准输出重定向到我们刚已打开的文件。

3.如果命令行中包含输入重定向符号<,how==out_redirect(2);那么添加如下代码:

				fd=open(file,O_RDONLY);
				dup2(fd,0);//make the file as a standard output stream
				execvp(arg[0],arg);//execute the command

这里只涉及到读取文件,因此以只读方式打开文件。

4.如果命令行中包含管道符号,how==have_pipe(3);如同我们在上面所述的,子进程还需再fork一个进程(暂且叫它子子进程,child_child_process)。首先让子子进程去执行管道符号前面的shell命令,并将输出结果暂存于tempfile文件中;让子进程去执行管道符号后面的shell命令,并读取tempfile文件,将其作为第二个命令的输入。这样,子进程和子子进程实现了管道”通信”。具体代码实现其实是上述2和3的结合,具体如下:

			if(pid==0)//child_process
			{
				int pid2;
				int status2;
				int fd2;

				if((pid2=fork())<\0)
				{
					printf("fork2 error\n");
					return;
				}
				else if(pid2==0)//child_child_process
				{
					if(!(find_command(arg[0])))
					{
						printf("%s: command not found\n",arg[0]);
						exit(0);
					}
					fd2=open("/tmp/tempfile",O_WRONLY|O_CREAT|O_TRUNC,0644);
					dup2(fd2,1);
					execvp(arg[0],arg);
					exit(0);
				}

				if(waitpid(pid2,&status2,0)==-1)//waiting for child_child_process
				{
					printf("wait for child_child process error\n");
				}

				if(!(find_command(argnext[0])))
				{
					printf("%s:command not found\n",argnext[0]);
					exit(0);
				}

				fd2=open("/tmp/tempfile",O_RDONLY);
				dup2(fd2,0);
				execvp(argnext[0],argnext);

				if(remove("/tmp/tempfile"))
				{
					printf("remove error\n");
				}
				exit(0);
			}

上出1~4处代码需要一个switch语句来组织,根据how参数来选择相应代码,进行执行。另外,由于上述代码中设计两次fork进程,因此要弄清楚父子进程的关系,其次还需了解代码4中waitpid函数的作用。由于子子进程和子进程同组,因此子进程等待子子进程结束。

同样,父进程需要等待子进程执行完毕。但是如果命令行中包含后台运行父&,那么附近成可直接返回,不用等待子进程。这些功能的实现代码如下:

	if(background==1)
	{
		printf("process id %d\n",pid);
	}

	if(waitpid(pid,&status,0)==-1)
	{
		printf("wait for child process error\n");
	}

至此,我们分析完了do_cmd函数的功能,完整代码可以参考这里。

丰富你的ls(updating……)

3 7 月, 2010

如果我们已经对my_ls.c的结构有了深刻了解,以及对此程序中所涉及系统调用函数都已理解,那么不妨继续增添选选型,让你的ls命令程序不断完整。正如你所理解的那样,前面我们实现的my_ls.c属于一个“插座”,要实现其他选项,基本上都是在my_ls.c源码中添加新代码。我们将不断完善选项功能的那个源码称为my_ls_plus.c。

1.增加-i选项

ls -i可以显示出每个文件的i节点编号。具体的实现办法是利用stat族函数,将文件的属性返回到类型为struct stat的参数buf中,然后在需要的位置打印st_ino字段即可。注意st_ino打印的时候需要长整型。

获取文件属性举例:

if(lstat(pathname,&buf)==-1)
	{
		my_err("lstat",errno,__LINE__);
	}

打印举例:

if(if_i)
	{
		printf("%ld ",buf.st_ino);
	}

2.增加-t选项

ls -t 按每个文件最后一次被修改的时间(st_mtime)排列显示文件。这里对文件属性中三个关于时间的字段做以说明。

st_atime:文件最后一次被访问(access)的时间。这个好理解。

st_mtime:文件最后一次被修改(modification)的时间。

st_ctime:文件字后一次被更改(create)的时间。

mtime和ctime其实都是修改时间,但是前者指文件内容被修改后的时间,而后者更倾向于文件所有者、所属组以及文件权限等属性被更改后的时间。当然如果文件内容修改,两者的时间都会被修改。我们一般用ls -l所显示的时间指mtime,而要显示出ctime则可以用: ls -lc。显示出atime则可以用:ls -lu。

因此,要实现ls -t命令的作用,我们必须对文件按照mtime进行排序。我们可以设置全局变量if_t来标记是否出现了 -t选项。如果出现则调用按照时间排序的函数sort_by_mtime(char filename[][PATH_MAX+1],int count);此函数的大致流程是:首先利用stat族函数获取每个文件的mtime,存于字符串数组mtime[256][20]中;再利用某种排序算法排序即可。如果你对C语言基本知识掌握的还算可以,那么要实现上述过程并不困难。

获取文件的mtime示例代码如下:(由于代码插件问题,请忽略<后面的‘\’)

for(i=0;i<\count;i++)
	{
		if(stat(filename[i],&buf)==-1)
		{
			my_err("stat",errno,__LINE__);
		}
		strcpy(buf_time,ctime(&buf.st_mtime));
		buf_time[strlen(buf_time)-1]='\0';
		convert_time(mtime[i],buf_time);
	}

下面的排序代码采用选择排序法进行排序,注意我们在比较的时候必然是对时间进行比较,而在交换两个数据的时候,必须对文件名和时间一起交换。当初我只交换了文件名,找了好久的”错误”。

//sorting
	for(i=0;i<\count;i++)
	{
		k=i;
		for(j=i;j<\count;j++)
               {
                        if(strcmp(mtime[j],mtime[k])>\0) k=j;
		}
		if(k!=i)
		{
			//exchange mtime
			strcpy(temp,mtime[i]);
			strcpy(mtime[i],mtime[k]);
			strcpy(mtime[k],temp);
			//exchange filename
			strcpy(temp,filename[i]);
			strcpy(filename[i],filename[k]);
			strcpy(filename[k],temp);
		}
	}

这样,你就可以实现-t选项的功能。

3.增加-A选项

这个选项理解比较简单。我们知道ls -a是显示包括隐藏文件在内的所有文件。而ls -A则显示除了.和..之外的所有文件。我们知道在my_ls.c中void display(int flag,char* pathname)是根据flag对单个文件pathname进行处理,根据flag的情况来判断是以什么方式显示所有文件名。因为我们可以在此基础上加入对-A选项的判断:

if((if_A&&!strcmp(pathname,"."))||(if_A&&!strcmp(pathname,"..")))
	{
		//blank sentence,do not list this file
	}
	else
	{
		//省略switch语句
	}

这样就可以实现-A选项了。

4.增加-B选项

如果你完成了-A选项,那么此选项对你来说轻而易举。-B选项是不显示以~结尾的备份文件。那么只要在-A选项的基础上修改源代码,示例如下:

if((if_A&&!strcmp(pathname,"."))||(if_A&&!strcmp(pathname,"..")))
	{
		//blank sentence,do not list this file
	}
	else if(if_B&&pathname[strlen(pathname)-1]=='~')
	{
		//blank sentence,do not list this file
	}
	else
	{
		//switch语句省略
        }

5.增加-d选项

-d选项只会显示当前目录(.),而不是目录下的文件。如果加入-l则会显示当前目录的各种属性。前面的-B等选项我们在添加的时候都是在display函数中添加某些代码,因为最终结果还要与-a和-l选项相结合。比如ls -laA,虽然不显示.和..,但是还是要显示以.开始的其他文件。而-d选项则不用看-a的“脸色‘。因为不论你是否加入-a选项,此选项只会显示当前目录。如下:

edsionte@edsionte-laptop:~/code/file$ ls -laAd
drwxr-xr-x 2 edsionte edsionte 4096 2010-07-06 14:50 .
edsionte@edsionte-laptop:~/code/file$ ls -ld
drwxr-xr-x 2 edsionte edsionte 4096 2010-07-06 14:50 .
edsionte@edsionte-laptop:~/code/file$ ls -d
.

因此关于此选项的代码应添加在display_dir函数中,具体位置如下:

//if the command include '-d'
	if(if_d)
	{
		if(stat(".",&buf)<\0)
		{
			my_err("stat",errno,__LINE__);
		}
		if(flag_param==PARAM_L||flag_param==PARAM_L+PARAM+A)
			display_attribute(buf,".");
		else
			display_single(buf,".");
	}
	else
	{
		for(i=0;i<\count;i++)
        	{
			display(flag_param,filename[i]);
        	}
	}

这样添加即可,通过以上几个选项的添加,你应该大致了解应该在何处添加代码了。

待续。。

creat只能以只写方式打开文件

1 7 月, 2010

在《linuxC编程实战》书中,有一个my_rwl.c的小程序(详见P151):首先利用open函数或者creat函数创建一个文件,利用write函数将数据写入文件,再利用read函数读出文件中数据;用lseek移动文件指针,再写入相同的数据,最终读出所有数据。创建代码如下:

// if((fd=creat("example_63.c",S_IRWXU))==-1)
	if((fd=open("example_63.c",O_CREAT|O_TRUNC|O_RDWR,S_IRWXU))==-1)
	{
		my_err2("creat",errno,__LINE__);
	}

在调试此程序的时候,总是出现这样的情况:利用open函数创建文件的时候,一些正常。利用creat函数创建文件的时候,读出的数据总是乱码,并且提示读写失败的错误信息。在自己寻求结果无望的时候,在chinaUnix论坛得到了帮助:creat只能以只写方式打开文件。其实书上有这句话,可是在我寻求上述错误答案的时候,也没有注意到它的存在。而且我还有有这样的错误概念:即便creat函数只能以只写方式打开文件,可是我创建的时候有S_IRWXU参数,那么它便可以写了。

那么,让我重新理解它:

1.文件的打开方式是指文件以只读、只写、可读写那种方式打开,返回的文件描述符就可以对此文件进行相应的读写操作。比如上述的creat函数对example_63.c以只写方式打开,那么就只能对这个文件进行写,因此后续的读操作都失败。而open创建文件的时候,有O_RDWR参数,因此可以成功对文件读写。

2.文件的操作权限是指系统中各用户对此文件的使用权限,依参数而定。

3.以创建的角度来看,creat和open的作用相同。但以打开文件的方式来看,前者只能以只写方式,后者由于参数的关系,以可读写方式打开。

如果要解决上述的错误,那么可以在creat创建完文件后,关闭文件,再以可读写的方式,open这个文件。这么做是很麻烦,但是这里我们为了搞清楚两者的区别,也无所谓了。

把错误捕获函数放入error.h

22 6 月, 2010

之前我写过一个关于linux下C编程中错误捕获函数的文章,即我们可以自己编写一个函数来捕获一些错误,让程序员处理错误的时候更简单。如果将这个函数放入error.h头文件中,那么以后就可以直接调用了。本文便是和大家一起讨论如何将自己编写的错误捕获函数放入头文件error.h中。

头文件放在/usr/include目录下,进入这个目录,然后用vi编辑器打开error.h文件。要添加的两个函数如下:

void my_err1(int error)
{
	printf("error:  %s with errno: %d\n",strerror(error),error);
	exit(1);
}
void my_err2(const char* err_string,int line,int error)
{
	printf("error:  line:%d %s():%s with errno:%d\n",line,err_string,strerror(error),error);
        exit(1);
}

将上述两函数添加至文件末尾。由于strerror()函数涉及到头文件string.h,所以在已打开的error.h文件中还得添加string.h头文件。具体添加位置可参考下面:

 20 #ifndef _ERROR_H
 21 #define _ERROR_H 1
 22
 23 #include "features.h"
 24
 25 //personal addition
 26 #include "string.h"
 27 //personal addition end
 28
 29 __BEGIN_DECLS
 30

 59 //personal addition
 60 //brief function
 61 void my_err1(int error)
 62 {
 63                 printf("error:  %s with errno: %d\n",strerror(error),error);
 64                 exit(1);
 65 }
 66
 67 //detailed function
 68 void my_err2(const char* err_string,int line,int error)
 69 {
 70                 printf("error:  line:%d %s():%s with errno:%d\n",line,err_string,strerror(error),error);
 71                 exit(1);
 72 }
 73 //persoanl addition end
 74 __END_DECLS
 75
 76 #endif /* error.h */

其中my_err1和my_err2显示的错误信息稍有不同。添加好后就可以保存了。但是现在问题来了:按照平时我们的保存方法:wq保存时,会提示:E45: 已设定选项 ‘readonly’ (请加 ! 强制执行)。这是因为头文件属于系统文件,linux为了安全起见,不允许普通用户对其进行修改。我们可以用ls -l查看其属性:

edsionte@edsionte-laptop:/usr/include$ ls -l error.h
-rw-r--r-- 1 root root 2557 2010-06-23 12:11 error.h

从上面的文件存取权限可以看出,root用户可以对其读写,root所在的root组可对其读,其他组用户只可对其读。现在我们更改这个文件的权限,让非root用户可对其修改。

edsionte@edsionte-laptop:/usr/include$ sudo chmod 777 error.h
[sudo] password for edsionte:
edsionte@edsionte-laptop:/usr/include$ ls -l error.h
-rwxrwxrwx 1 root root 2557 2010-06-23 12:11 error.h

修改好权限后,就可以用上面的方法对error.h文件进行修改并保存。
下面进行测试:
测试源码如下。

#include “errno.h”
#include “fcntl.h“
#include "unistd.h”
#include "stdlib.h“
#include "stdio.h”
#include ”sys/types.h“
#include “sys/stat.h”
#include ”string.h“
#include “error.h”

int main()
{
	int fd;
	if((fd=open("example_test.c",O_CREAT|O_EXCL,S_IRUSR|S_IWUSR))==-1)
	{
		my_err1(errno);
//		my_err2("open",__LINE__,errno);
	}
	close(fd);
	return 0;
}
}

首先测试my_err1,结果如下:

edsionte@edsionte-laptop:~/code$ ./my_error
error:  File exists with errno: 17

然后将my_err1注释掉,测试my_err2,结果如下:

edsionte@edsionte-laptop:~/code$ ./my_error
error:  line:17 open():File exists with errno:17

结果和前面介绍捕获函数时的测试结果一样。你以为现在就结束了吗?当然没有,我们要把error.h头文件的权限修改成原样。

edsionte@edsionte-laptop:/usr/include$ sudo chmod 644 error.h
[sudo] password for edsionte:
edsionte@edsionte-laptop:/usr/include$ ls -l error.h
-rw-r--r-- 1 root root 2557 2010-06-23 12:50 error.h

大功告成。

实现自己ls命令(下)

21 6 月, 2010

在本文上部分中,主要陈述my_ls.c的大致结构,本文主要来分析my_ls中主函数是如何实现的。

(1)进入main函数。首先对命令行参数进行解析,即提取命令行参数中‘-’后的选项。用户的输入有多样性,如ls -l -a;ls -la。我们用两层循环类来解析参数,外层循环对argv[]数组中的元素依次进行内层循环的解析,二层循环对以‘-’为首的字符串进行选项提取,并把每个选项存于param[]数组当中,用num来记下‘-’的数目,以备后用。而命令行参数中的总选项数目则用j计数。实现代码如下:

     j=0;
	num=0;
	for(i=1;i<\argc;i++)
	{
		if(argv[i][0]=='-')
		{
			for(k=1;k<\strlen(argv[i]);k++)
			{
				param[j++]=argv[i][k];
			}
			num++;//count the number of '-'
		}
	}

接下来,我们来检查刚刚提取的选项是否合法。本程序我们只支持选项-l和-a,因此如果我们增添其他选项,只要适当添加代码即可。并且我们用或运算记录参数,以备后用。最后为选项数组的末尾元素赋‘\0’。

//check the argument because of only supporting -a and -l
	for(i=0;i<\j;i++)
	{
		if(param[i]=='a')
		{
			flag_param|=PARAM_A;
			continue;
		}
		else if(param[i]=='l')
		{
			flag_param|=PARAM_L;
			continue;
		}
		else
		{
			printf("error:my_ls invalid option -%c\n",param[i]);
			exit(1);
		}
	}
	param[j]='\0';

由上面我们所知num记录的是参数中‘-’的数量,因此如果num+1==argc,那说明用户所输入的命令行参数不包含目录或文件名。只是显示当前目录下的文件。因为这是我们必须自动将path赋值为当前目录。为了使其称为一个字符串,必须在末尾加‘\0’。然后进入display_dir函数。实现代码如下:

//print the information of  current directory if the command without the name of target file and current directory
        if((num+1)==argc)
	{
		strcpy(path,"./");
		path[2]='\0';
		display_dir(flag_param,path);
		return 0;
	}

如果命令行参数包含目录或者文件名,那么我们要检查其合法性(参数中的目录或者文件是否存在)。这里我们利用stat族函数来获取文件的属性,实现上述功能。stat族函数通常有两个参数:文件路径/文件描述符,struct stat *buf类型的结构体。如果操作成功,那么buf将保存文件的属性。若合法,利用宏S_ISDIR(buf.st_mode);判断此文件是否为目录文件。若为目录文件则进入display_dir函数,否则进入display函数。通常情况,display_dir函数是获取path目录下所有文件的完整路径名,在使每个文件执行dsiplay函数。因此如果参数中是指定的文件名,则可绕过display_dir函数,直接进入display函数。

        i=1;
	do
	{
		//the current argument doesn't comprise the target file name and dirctory name
		if(argv[i][0]=='-')
		{
			i++;
			continue;
		}
		else
		{
			strcpy(path,argv[i]);
			//detect if the "path" exsit
			if(stat(path,&buf)==-1)
			{
				my_err("stat",errno,__LINE__);
			}
			//detect the "path" is a file or a directory
			if(S_ISDIR(buf.st_mode))
			{
				//directory
				//detect if the last character of the "path" is '/'
				if(path[strlen(path)-1]=='/')
				{
					path[strlen(path)]=='\0';
				}
				else
				{
					path[strlen(path)]='/';
					path[strlen(path)]='\0';
				}
				display_dir(flag_param,path);
				i++;
			}
			else
			{
				//file
				display(flag_param,path);
				i++;

			}
		}

	}while(i<\argc);

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key