0x00-What Is The 'ls'?
要实现ls,首先要对linux的文件系统,文件类型有一定了解,也要对ls及其各种常见参数的功能比较了解
-a, –all 列出目录下的所有文件,包括以 . 开头的隐含文件。
-i, inode 印出每个文件的 inode 号
-l, list列出文件的详细信息
-t, time以文件修改时间排序
-r, reverse 依相反次序排列
-s, size 以块大小为单位列出所有文件的大小
-R, –recursive 同时列出所有子目录层
-A, –almost-all 列出除了 . 及 .. 以外的任何项目
-d, –directory 将目录象文件一样显示,而不是显示其下的文件
-g 类似 -l,但不列出所有者
-h, –human-readable 以容易理解的格式列出文件大小 (例如 1K 234M 2G)
0x01-Linux Document Type
FIFO File
Directory File
/opt | 额外安装的可选应用程序包所放置的位置。一般情况下,我们可以把tomcat等都安装到这里。 |
/proc | 虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息。 |
/root | (系统管理员)的主目录(特权阶级o) |
/sbin | 存放二进制可执行文件,只有root才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如ifconfig等。 |
/dev | 用于存放设备文件。 |
/mnt | 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统。 |
/boot | 存放用于系统引导时使用的各种文件 |
/lib | 存放跟文件系统中的程序运行所需要的共享库及内核模块。共享库又叫动态链接共享库,作用类似windows里的.dll文件,存放了根文件系统程序运行所需的共享文件。 |
/tmp | 用于存放各种临时文件,是公用的临时文件存储点。 |
/var | 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等。)等。 |
/lost+found | 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows下叫什么.chk)就在这里 |
Symbolic Link
Normal File
Socket File
Character Device Drive
字符设备按照字符流的方式被有序访问,像串口和键盘就都属于字符设备。如果一个硬件设备是以字符流的方式被访问的话,那就应该将它归于字符设备;反过来,如果一个设备是随机(无序的)访问的,那么它就属于块设备。
字符设备特殊文件进行I/O操作不经过操作系统的缓冲区
字符特殊文件与外设进行I/o操作时每次只传输一个字符
Block Device Driver File
系统中能够随机(不需要按顺序)访问固定大小数据片(chunks)的设备被称作块设备,这些数据片就称作块。最常见的块设备是硬盘,除此以外,还有软盘驱动器、CD-ROM驱动器和闪存等等许多其他块设备。注意,它们都是以安装文件系统的方式使用的——这也是块设备的一般访问方式。
块设备特殊文件用来同外设进行定长的包传输
对于块设备特殊文件来说,它用了cache机制,在外设和内存之间一次可以传送一整块数据。
0x02-Struct Type
DIR(dirstream)
struct __dirstream { void *__fd; char *__data; int __entry_data; char *__ptr; int __entry_ptr; size_t __allocation; size_t __size; __libc_lock_define (, __lock) }; typedef struct __dirstream DIR;
group
struct group { char *gr_name; /* Group name */ char *gr_passwd; /* password */ __gid_t gr_gid; /* Group ID */ char **gr_mem; /* Member list */ }
stat
struct stat { dev_t st_dev; /* ID of device containing file */文件使用的设备号 ino_t st_ino; /* inode number */ 索引节点号 mode_t st_mode; /* protection */ 文件对应的模式,文件,目录等 nlink_t st_nlink; /* number of hard links */ 文件的硬连接数 uid_t st_uid; /* user ID of owner */ 所有者用户识别号 gid_t st_gid; /* group ID of owner */ 组识别号 dev_t st_rdev; /* device ID (if special file) */ 设备文件的设备号 off_t st_size; /* total size, in bytes */ 以字节为单位的文件容量 blksize_t st_blksize; /* blocksize for file system I/O */ 包含该文件的磁盘块的大小 blkcnt_t st_blocks; /* number of 512B blocks allocated */ 该文件所占的磁盘块 time_t st_atime; /* time of last access */ 最后一次访问该文件的时间 time_t st_mtime; /* time of last modification */ /最后一次修改该文件的时间 time_t st_ctime; /* time of last status change */ 最后一次改变该文件状态的时间 };
pwd
struct passwd { char *pw_name; /* username */ char *pw_passwd; /* user password */ uid_t pw_uid; /* user ID */ gid_t pw_gid; /* group ID */ char *pw_gecos; /* real name */ char *pw_dir; /* home directory */ char *pw_shell; /* shell program */ };
dirent
struct dirent { long d_ino; /* inode number 索引节点号 */ off_t d_off; /* offset to this dirent 在目录文件中的偏移 */ unsigned short d_reclen; /* length of this d_name 文件名长 */ unsigned char d_type; /* the type of d_name 文件类型 */ char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */ }
dirent不仅仅指向目录,还指向目录中的具体文件,readdir函数同样也读取目录下的文件
0x03-Important Lib & Func
<stdio.h>
Func
void perror ( const char * str );//函数原型
perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。
在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和现在的errno所对应的错误一起输出。
perror是用来输出库函数调用失败信息的。perror会通过error错误代码来输出相应的errormsg.
perror输出内容只依赖error变量。
errno宏定义为一个int型态的左值, 包含任何函式使用errno功能所产生的上一个错误码。
perror如果它觉得没有错误 会打印:success,,,,,
<sys/stat.h>
Macro Definition
S_ISLNK(st_mode):是否是一个连接.
S_ISREG是否是一个常规文件.
S_ISDIR是否是一个目录
S_ISCHR是否是一个字符设备.
S_ISBLK是否是一个块设备
S_ISFIFO是否是一个FIFO文件.
S_ISSOCK是否是一个SOCKET文件
Func
int stat(const char *file_name,struct stat *buf); 传入文件路径(名),传入定义好的stat类,获取文件信息
int lstat(const char *pathname,struct stat *buf)
lstat和stat的区别:lstat"不穿透",stat"穿透"lstat函数类似于stat.但是当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是由该符号链接引用文件的信息。
<pwd.h>
Func
struct passwd *getpwuid(uid_t uid);根据传入的uid返回uid对应的passwd结构体
<grp.h>
Func
strcut group * getgrgid(gid_t gid); 返回 group 结构数据, 如果返回NULL 则表示已无数据, 或有错误发生.
<dirent.h>
Func
struct dirent* readdir(DIR* dir); (个人理解循环读取dir,目录和文件都读)
DIR *opendir(const char *pathname),(获取path子目录下的所由文件和目录的列表,如果path是个文件则返回值为NULL),返回的就是指向DIR结构体的指针,而该指针由以下几个函数使用:
- struct dirent *readdir(DIR *dp);
- void rewinddir(DIR *dp);
- int closedir(DIR *dp);
- long telldir(DIR *dp);
- void seekdir(DIR *dp,long loc);
dirent不仅仅指向目录,还指向目录中的具体文件,readdir函数同样也读取目录下的文件
0x04-Coding Logic
1.error 错误处理函数
传入保存内容字符串指针*p,传入_LINE_报错行数line
2.take_out 提取文件名/尾目录名 函数
传入地址*path,和保存文件名字符串*name 一次调用产生一个文件名
遍历变量 i:遍历path j:操作name最终结果数组下标,若遍历过程中发现还有/则name从头起,并在结束时打上结束标志,后面的内容被抛弃
3.dispaly_attribute 属性函数
传入stat结构体buf,和文件名字符串*name
定义buf_time[32]存放通过ctime调用的时间
定义name_attribute[11]存放---------文件类型[0],用户权限[1-3],用户组权限[4-6],其他权限[7-9]
输出文件属性name_attribute,输出连接数
定义psd,grp通过调用getpwuid,getgrgid获得用户名组名
输出文件大小
去掉换行符???????????????????????????
4.display
传入 功能变量flag (都是从其他函数传flag_param)和文件路径字符串*pathname
定义stat类型结构体buf 定义文件名字符串name[260]
调用lstat函数,若无返回值则调用error
调用take_out函数把pathname中的文件名取出来
然后通过switch flag(flag_param)多值情况判断进行哪种功能(1纯ls,3 -a,5-l,7 -a -l,9 -R,11-aR,13 -lR ,15 -alR)
然后分别进行函数调用,并进行格式微调
5.display_R ls -R功能函数
传入功能变量 flag (都是从其他函数传flag_param)和文件路径字符串*path
定义stat类型的buf和buff 定义DIR类型结构体*dir接受调用opendir函数传入的文件流
定义dirent类型的结构体*ptr 读取path下的子目录,文件
定义字符串表allname[256][256],name[256][256] 和字符串a[260],b[260]
判断是否错误
判断当前路径是否是一个目录 若否则调用take_out取出文件名放入字符串a,如果不是.xxx开头的文件,且flag>11(lR,alR),则展现全部属性且文件名(即a),不然就只展现文件名,调用结束
若是打印当前目录,并定义一个count作为子目录文件(目录)计数器,并用DIR类型结构体dir接受调用opendir返回的文件流
如果返回为空就进行错误处理函数,若不空,再定义一个遍历变量i=0
用ptr接受调用readdir(dir)的文件,目录名,并while存在时,count++来记录有多少文件和目录
调用strncpy安全的将path放到allname[i]上,并给allname[strlen(path)]的位置打上‘/’,并给后面加上字符串结束符号
然后调用strncat将子目录文件名加到allname[i]后面,并打上字符串结束标记
结束while前对遍历变量i进行递增
最后,用count计数器遍历所有绝对路径名然后把文件名都take_out放入name表中
再次遍历所有文件,如果文件名name[i][0]即第一个字符不是.开头的(隐藏文件)若不是调用lstat(allname[i],&buff)函数获取信息,全部传入到buff中,若调用失败就进行错误处理,然后再判断buff是不是文件目录,若是,则给char *m开辟一个strlen(allname[i])*sizeof(char)大小的空间,m作为display_R( *path)路径进行递归调用,调用结束后free掉m
若不是,即buff是文件名了,如果flag>11,调用display_attribute(buff,allname[i])展示文件属性,if结束打印文件名
若是.开头的(隐藏文件)如果flag〉11,提取文件名输出属性,文件名
6.display_dir
7.main 主函数
0x05-Problem
1.take_out函数
(将文件名从绝对路径中提取出来的函数)中万一出先xxx/xxxx/xxx.txt这种,是否会在第一个/读取后面所有的内容当作一个文件名
self-ans:不会,一旦读入会‘/’会进入if自动置j=0
2.stdout/stderr
stdout(标准输出),输出方式是行缓冲。输出的字符会先存放在缓冲区,等按下回车键时才进行实际的I/O操作。
stderr(标准错误),是不带缓冲的,这使得出错信息可以直接尽快地显示出来。
sdtout、stderr详解_lukabruce的博客-CSDN博客_stderr
3.printf和sprintf的区别
打印的目的地不同,sprintf打印到字符串,printf打印到命令行;
4.关于perror函数,详见0x03
5.lstat和stat混淆
lstat函数的形参跟stat函数的形参一样。其功能也跟stat函数功能一样,仅有一点不同:lstat函数是穿透(追踪)函数,即对软链接文件进行操作时,操作的是链接到的那一个文件,不是软链接文件本身;而lstat函数是不穿透(不追踪)函数,对软链接文件进行操作时,操作的是软链接文件本身。
6.DIR和dirent
DIR结构体类似于FILE,是一个内部结构,以下几个函数用这个内部结构保存当前正在被读取的目录的有关信息(摘自《UNIX环境高级编程(第二版)》)。函数 DIR *opendir(const char *pathname),即打开文件目录,返回的就是指向DIR结构体的指针,而该指针由以下几个函数使用:
dirent这种文件包含了其他文件的名字以及指向与这些文件有关的信息的指针(摘自《UNIX环境高级编程(第二版)》)。从定义能够看出,dirent不仅仅指向目录,还指向目录中的具体文件,readdir函数同样也读取目录下的文件,这就是证据。
0x06-Code
/************************************************************************* // creattime:2021-10-17 19:04 // language:c // goal:跟进博客+实现ls // resource:https://blog.csdn.net/qq_33850438/article/details/61414593 // usr:CheAyuki13 // judge: NULL // problem: 不熟练 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<time.h> #include<unistd.h> #include<sys/types.h> #include<dirent.h> #include<grp.h> #include<pwd.h> #include<errno.h> #include<sys/stat.h> #include<limits.h> #define MAXROWLEN 80 //一行显示的最多字符数 void error(char *p,int line) //错误处理函数 { printf("error !:%d",line); perror(p); exit(1); } /* void take_out(char *path,char *name) //从路径名中解析出文件名 { int i,j; for(i=0,j=0;i<strlen(path);i++) { if(path[i] == '/')//发现path出现/ { j=0; continue;//直接进入下一次循环,下一次不会再进入if } name[j++]=path[i];//name保存path里面的文件名 } name[j]='\0';//文件名字符串结束 } /* void display_attribute(struct stat buf,char *name) //获取文件属性并打印(-l) 传入buf { char buf_time[32]; char name_attribute[11];//存放--------- for(int i=0;i<10;i++) { name_attribute[i]='-'; } name_attribute[10]='\0'; struct passwd *psd; //存放用户名 struct group *grp; //存放组名 //获取文件类型 if(S_ISLNK(buf.st_mode)) name_attribute[0]='l'; else if(S_ISREG(buf.st_mode)) name_attribute[0]='-'; else if(S_ISDIR(buf.st_mode)) name_attribute[0]='d'; else if(S_ISCHR(buf.st_mode)) name_attribute[0]='c'; else if(S_ISBLK(buf.st_mode)) name_attribute[0]='b'; else if(S_ISFIFO(buf.st_mode)) name_attribute[0]='f'; else if(S_ISSOCK(buf.st_mode)) name_attribute[0]='s'; //获取用户权限 if(buf.st_mode & S_IRUSR) name_attribute[1]='r'; if(buf.st_mode & S_IWUSR) name_attribute[2]='w'; if(buf.st_mode & S_IXUSR) name_attribute[3]='x'; //获取用户组权限 if(buf.st_mode & S_IRGRP) name_attribute[4]='r'; if(buf.st_mode & S_IWGRP) name_attribute[5]='w'; if(buf.st_mode & S_IXGRP) name_attribute[6]='x'; if(buf.st_mode & S_IROTH) //获取其他权限 name_attribute[7]='r'; if(buf.st_mode & S_IWOTH) name_attribute[8]='w'; if(buf.st_mode & S_IXOTH) name_attribute[9]='x'; //打印 printf("%s ",name_attribute); printf("%d ",(int)buf.st_nlink); //打印连接数 //得到用户名与用户组名 psd=getpwuid(buf.st_uid); grp=getgrgid(buf.st_gid); printf("%s %s ",psd->pw_name,grp->gr_name); printf("%-d ",(int)buf.st_size); //打印文件大小 strcpy(buf_time,ctime(&buf.st_mtime)); buf_time[strlen(buf_time)-1]='\0'; //去掉换行符 printf(" %-s",buf_time); //打印文件时间信息 } */ /* *根据命令行参数和完整路径名显示目标文件 *参数flag:命令参数 *参数pathname;包含了文件名的路径名 */ /* void display(int flag,char *pathname) { void display_R(int flag,char *path); int i,j; struct stat buf; char name[260]; //用lstat 因为它牛,多个l,可以解析链接文件 if(lstat(pathname,&buf)==-1) { error("display",__LINE__); } take_out(pathname,name); switch(flag) { case 1: //没有-a -l -R参数 if(name[0] !='.') { printf("%-6s ",name); } break; case 3: //-a参数,显示包括隐藏文件在内的所有文件 { printf("%-6s ",name); } break; case 5: //-l参数 { if(name[0]!='.') { display_attribute(buf,name); printf(" %s\n",name); } break; } case 7: //同时有-a -l参数 { display_attribute(buf,name); printf(" %-s\n",name); } break; case 9: //-R { display_R(flag,pathname); break; } case 11: //-aR { printf(". ..\n"); display_R(flag,pathname); break; } case 13: //-lR { display_attribute(buf,name); printf(" "); display_R(flag,pathname); } case 15: //-alR { display_attribute(buf,"."); printf(" .\n"); display_attribute(buf,".."); printf(" ..\n"); display_R(flag,pathname); break; } } } */ void display_R(int flag,char *path) //-R参数 传入flag { /* struct stat buf; struct stat buff; DIR *dir;//路传入路径文件流 struct dirent *ptr;//读取子目录文件 char allname[256][260],name[256][260],a[260],b[260]; int i,j,k,len,count; if(lstat(path,&buf) == -1)//错误处理 { if(errno==13) { return ; } else { printf("error di: %s\n",path); // //error("display_R",__LINE__); return ; } } if(S_ISDIR(buf.st_mode)) //为一个目录,还有文件或子目录 { printf("\n%s\n",path); //打印目录名 count=0; dir = opendir(path);//dir为路径下的文件流 if(dir == NULL) { error("display_R",__LINE__);//错误处理 } i=0;//遍历用 while((ptr = readdir(dir))!=NULL) //获取子目录下的文件 目录名,并连接成绝对路径名 { len=0; count++;//记录一共多少文件 strncpy(allname[i],path,strlen(path));//更安全的复制到allname[i] allname[i][strlen(path)]='/';//给每一个path后面加上/ allname[i][strlen(path)+1]='\0';//字符串结束 strncat(allname[i],ptr->d_name,strlen(ptr->d_name));//加上子目录文件名 allname[i][strlen(allname[i])]='\0';//字符串结束 i++; } */ /* for(i=0;i<count;i++) take_out(allname[i],name[i]);//遍历所有绝对路径名,然后把文件名takeout for(i=0;i<count;i++)//遍历所有文件 { if(name[i][0] != '.')//如果第i个文件名不是.开头 { if(lstat(allname[i],&buff) == -1)//?????是否是一个符号链接文件 { printf("error242"); } if(S_ISDIR(buff.st_mode))//是否是一个目录 { char *m=(char *)malloc(strlen(allname[i])*sizeof(char));// display_R(flag,m);//是目录!继续递归调用! free(m);//调用结束后free掉m } else//以上都不是,那就是文件名了 { if(flag > 11) { display_attribute(buff,allname[i]);//展示文件属性 } printf(" %s\n",name[i]); } } else { printf("\n"); continue;//进入下一次循环 } } } else //不是一个目录 { take_out(path,a);//把文件名字解析出来 if(a[0] != '.')//开头不是. { if(flag > 11) { display_attribute(buff,allname[i]);//展示全部属性 } printf(" %-s\n",a); } } } */ void display_dir(int flag_param,char *path) //传入参数类型和文件路径 { /* void take_out(char *path,char *name); // DIR *dir; struct dirent *ptr;//工作指针 int count=0; char filename[256][260],fullname[256][260],name[256][260]; char temp[PATH_MAX]; //获取该目录下文件总数 dir=opendir(path); if(dir==NULL) { error("oprndir",__LINE__); } int i = 0,j,k,len; while((ptr = readdir(dir))!=NULL)//遍历全部文件 { len=0;//文件名长度 count++;//统计文件个数 // memset(filename[i], 0, strlen(filename[i])); //memcpy(filename[i], ptr->d_name,sizeof(ptr->d_name)); strcpy(filename[i],ptr->d_name);//给二维char数组的每一行传文件名 len = strlen(ptr->d_name); filename[i][len]='\0';//设置结尾标志,变成字符串 i++; } closedir(dir);//关闭文件流 if(count>256)//溢出 { error("opendir",__LINE__); } */ /* //按字典序排序 for(i = 0;i<count-1;i++) for(j = 0;j<count-1;j++) { if(strcmp(filename[j],filename[j+1])>0)//比较哪个字符串更长,如果j更大 { strcpy(temp,filename[j]); strcpy(filename[j],filename[j+1]);//把短的放到前面 strcpy(filename[j+1],temp);//长的放后面 } } for(i=0;i<count;i++)//给fullname放上当前路径+/+文件名字符串 { strncat(fullname[i],path,strlen(path)); fullname[i][strlen(path)]='/'; fullname[i][strlen(path)+1]='\0'; strncat(fullname[i],filename[i],strlen(filename[i])); fullname[i][strlen(fullname[i])]='\0'; } for(i=0;i<count;i++) { take_out(fullname[i],name[i]); } for(i=0;i<count;i++) { if(flag_param == 9 || flag_param == 11 || flag_param == 15 || flag_param == 13) { int flag=1; if(name[i][0] == '.') { flag=0; } if(flag == 1) { display(flag_param,fullname[i]); } } else display(flag_param,fullname[i]); printf("\n"); } } */ /* int main(int argc,char **argv) { struct stat buf; int i,j,k; char path[260]; //保存路径名 char param[32]; //保存命令行参数,目标文件名和目录名不在此数组 int flag_param=1; //参数种类,即是否有-l -a -R选项 //解析命令行参数,分析-l -a -al -la(真正ls 参数顺序无影响,只是我们要让程序知道) j=0; for(i=1;i<argc;i++) { if(argv[i][0]=='-') { for(k=1;k<strlen(argv[i]);k++,j++) { param[j]=argv[i][k]; //获取-后面的参数保存到数组param中 } } } //只支持参数a l R如果有其他选项就报错 for(i=0;i<j;i++) { if(param[i] == 'a') flag_param+=2; else if(param[i] == 'l') { flag_param+=4; } else if(param[i] =='R') { flag_param+=8; } if(param[i] != 'a' && param[i] != 'l' && param[i] != 'R') //补-R { printf("我的ls无该功能"); exit(1); } } param[j]='\0'; */ /* if(argc==1)//如果只有ls这个命令 { strcpy(path, ".");//将.复制到path里面 display_dir(flag_param,path);//调用display——dir函数 return 0; } else if(argc==2)//有俩参数:ls + 另外一个 { if(flag_param==1) { strcpy(path,argv[1]);//另外一个参数不为 -l等而是地址,则cpy到path } else { strcpy(path,".");//如果不等于1即有其他操作,也先给path加上. } }*/ else if(argc==3) { strcpy(path,argv[2]);//三个参数, } //如果目标文件或目录不存在,报错并退出程序 if(stat(path,&buf)==-1) { error("it does not exist",__LINE__); } if(S_ISDIR(buf.st_mode)) //是一个目录 { display_dir(flag_param,path); } else //是一个文件 { display(flag_param,path); } return 0; }