以前做的shell的项目,都有些忘了,今天把代码和思路贴出来复习一下
实现机制是调用系统函数
实现方法是fork+exec
实现的功能有ls,cd,pwd,su等
好了。代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <pwd.h>
#define spath "/home/luying/Desktop/work/"
#define MAXSIZE 10
void getinfo()//此函数用来显示打开终端时左边的信息
{
uid_t id = getuid();
char *last = "$";
if(id == 0)
{
last = "#";
}
struct passwd *p = getpwuid(id);
char hostname[128]={0};
if(gethostname(hostname,128)==0)
{
hostname[128] = "myshell";
}
char path[256] = {0};
getcwd(path,256);
char *s = strtok(path,"/");
if(s == NULL)
{
continue;
}
char *w = s;
while((s = strtok(NULL,"/")) != NULL)
{
w = s;
}
printf("[%s@ %s %s]%s ",p->pw_name,hostname,w,last);
fflush(stdout);
}
void main()
{
char buff[128] = {0};
char *myargv[MAXSIZE] = {0};
while(1)
{
//printf("[stu@shell~ ]$ ");
//fflush(stdout);
getinfo();
fgets(buff,128,stdin);//接受到命令
buff[strlen(buff)-1]=0;//去掉最后的\0
char *s = strtok(buff," ");//分割,比如rm -r有两个命令
if(s == NULL)
{
continue;
}
myargv[0]=s;//s为分割好的命令
int i = 1;
while((s = strtok(NULL," "))!= NULL)
{
myargv[i++] = s;
}
if(strcmp(myargv[0],"cd")==0)//if分支处理cd命令
{
if(chdir(myargv[1]) == -1)//chdir是系统函数
{
perror("myshell cd error!");
}
continue;
}
if(strcmp(myargv[0],"exit")==0)//退出终端
{
exit(0);
}
pid_t pid = fork();//创建子进程处理其他命令
if(fork < 0)
{
printf("fork error!");
exit(-1);
}
if(pid == 0)
{
char path[512] = {0};
if(strncmp(myargv[0],"/",1) != 0 && strncmp(myargv[0],"./",2) != 0)//判断输入的命令为相对路径还是绝对路径
{
strcpy(path,spath);
}
strcat(path,myargv[0]); //spath是我写的替换函数的路径
execv(path,myargv);//调用exec替换进程,处理命令
perror("error");
exit(-1);
}
wait(NULL);//父进程处理结束的子进程
}
}
我/home/luying/Desktop/work/路径下的pwd处理函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pwd.h>
int main()
{
char path[256];
getcwd(path,256);
printf("%s\n",path);
return 0;
}
只有这几行。是的,主要是getcwd这个函数,return之后,进入shell.c中的wait(NULL)处回收其资源。shell.c又进入while循环接收命令
看下复杂的su的处理:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <pwd.h> //getpwnam()函数
#include <shadow.h>//getspnam()函数
#include <termios.h>//tcsetattr()函数
int main(int argc,char *argv[])
{
char *s = "root";
if( argc == 2)
{
s = argv[1];//比如:su liming,则s=liming
}
struct passwd * p = getpwnam(s);//获取用户登录相关信息
if( p == NULL)
{
perror("username error");
exit(0);
}
struct spwd * sp = getspnam(s);
/*
getspnam()函数可访问shadow口令。所以科普一下shadow
几乎所有的类Unix操作系统的口令文件的格式都雷同,Linux亦不例外。口令安全是Linux操作系统的传统安全问题之一。Linux使用不可逆的加密算法如DES来加密口令,由于加密算法是不可逆的,所以从密文是得不到明文的。但问题在于,/etc/passwd文件是全局可读的,加密的算法是公开的,如果有恶意用户取得了/etc/passwd文件,他就可以穷举所有可能的明文通过相同的算法计算出密文进行比较,直到相同,于是他就破解了口令。因此,针对这种安全问题,Linux/Unix广泛采用了“shadow(影子)”机制,将加密的口令转移到/etc/shadow文件里,该文件只为root超级用户可读,而同时/etc/passwd文件的密文域显示为一个x,从而最大限度减少密文泄露的机会。
*/
if(sp == NULL)
{
perror("userpasswd error");
exit(0);
}
//printf("system:%s\n",sp->sp_pwdp);
printf("input passwd:");
fflush(stdout);
struct termios ts,nts;
tcsettattr(0,&ts);//用于获取和修改终端参数
nts = ts;
nts.c_lflag &= ~ECHO;
tcsetattr(0,TCSANOW,&nts);
/*
函数原型:
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
tcsetattr函数用于设置终端参数。函数在成功的时候返回0,失败的时候返回-1,并设置errno的值。参数fd为打开的终端 文件描述符,参数optional_actions用于控制修改起作用的时间,而 结构体termios_p中保存了要修改的参数。optional_actions可以取如下的值。
TCSANOW:不等数据传输完毕就立即改变属性。
TCSADRAIN:等待所有数据传输结束才改变属性。
TCSAFLUSH:清空输入输出缓冲区才改变属性。
*/
char buff[128]= {0};
fgets(buff,128,stdin);
buff[strlen(buff)-1] = 0;
tcsetattr(0,TCSANOW,&ts);
char lt[128] = {0};
int i = 0;
int num = 0;
for(; i< strlen(sp->sp_pwdp);i++)
{
if( sp->sp_pwdp[i] == '$')
{
num += 1;
if(num == 3)
{
break;
}
lt[i] = sp->sp_pwdp[i];
}
}
pid_t pid = fork();
assert(pid != -1);
if( pid == 0 )
{
setuid(p->pw_uid);
setenv("HOME",p->pw_dir,1);
execl(p->pw_shell,p->pw_shell,(char *)0);
perror("exec error");
exit(0);
}
wait(NULL);
}
这里需要解释一下,为什么su的时候需要fork()
发现su之后终端已经被替换
当前终端的PPID是之前的进程,exit后有会回到su之前所在的终端。
太晚了,其实还没分析完,明天继续!!!