一:背景

    linux下的ls可以实现什么效果呢,ls有很多的选项,最为常用的选项莫过于是-l选项,列出所有文件的详细信息。本文也着重去实现ls -l。首先看下ls -l的效果。本文将会完整的去描述怎么样一步一步去实现

[root@bogon unix]# ls -l
total 116
-rw-r--r--. 1 root root 1063 Jul  6 20:18 aaaa
-rwxr-xr-x. 1 root root 9811 Jul 18 22:17 a.out
-rw-r--r--. 1 root root 1474 Jul 10 21:58 cp1.c
-rw-r--r--. 1 root root  386 Jul 10 21:54 exis.c
-rw-r--r--. 1 root root  601 Jul 15 22:22 fileinfo.c
-rw-r--r--. 1 root root  515 Jul  6 21:39 logout_tty.c
-rw-r--r--. 1 root root  755 Jul 12 15:21 ls1.c
-rw-r--r--. 1 root root 2625 Jul 18 22:17 ls2.c
----------. 1 root root 1063 Jul  3 21:31 more01.c
-rwxrwxrwx. 1 root root 1651 Jul  5 21:48 more02.c
-rw-r--r--. 1 root root  270 Jul 15 22:16 stattest.c
-rw-r--r--. 1 root root  262 Jul  4 21:51 test1.c
-rw-r--r--. 1 root root 1337 Jul 12 14:19 test2.c
-rw-r--r--. 1 root root  140 Jul 12 14:11 test3.c
-rw-r--r--. 1 root root  527 Jul  3 22:19 test.c
-rw-r--r--. 1 root root  169 Jul  7 22:42 ttytest.c
-rw-r--r--. 1 root root  955 Jul  7 20:54 utmplib.c
-rw-r--r--. 1 root root   87 Jul  6 21:13 utmplib.h
-rw-r--r--. 1 root root 2688 Jul  6 21:12 utmplib.o
-rwxr-xr-x. 1 root root  980 Jul 12 14:22 who1.c
-rwxr-xr-x. 1 root root 9576 Jul  6 21:13 who2
-rw-r--r--. 1 root root 1791 Jul 12 13:15 who2.c
-rw-r--r--. 1 root root 2720 Jul  6 21:13 who2.o
-rw-r--r--. 1 root root 1424 Jul 12 12:45 who_am_i.c
-rw-r--r--. 1 root root   80 Jul  7 22:43 whoami.c

    这是一个使用ls -l选项列出的的文件信息,可以看出ls -l列出了所有文件的 属性,连接,属主,属组,大小,ctime以及文件名等信息。要获取文件的这些信息并进去适当的格式化,就是本文所要做的事情了。


二:开始实现

      要想自己实现ls -l就得知道可以通过什么系统调用可以获取到这些文件信息。

首先自然是要通过man来查找相关的系统调用。

man -k file|grep status
man -k file|grep infomation
man -k file |grep info

    不停的换上多个关键词来搜索,通过上面的搜索就可以得到stat这个系统调用来获取文件信息。

紧接着就可以man 2 stat来获取系统调用的详细使用方法。

       #include <sys/types.h>
       #include <sys/stat.h>
       #include <unistd.h>
       int stat(const char *path, struct stat *buf);
           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 */
           };

到了这里处理起来似乎很顺畅了,只要获取相应字段进行格式处理就OK了。


三:权限处理

    st_mode就是文件的权限部分,对于这个部分要把其处理成ls -l列出的那种形式。

st_mode本身就是一个16位的二进制,前四位是文件的类型,紧接着三位是文件的特殊权限,最后的九位就是ls -l列出来的九个权限。如何把st_mode转换成对应的权限就是权限处理这块的关键了。

linux本身提供了很多测试宏来测试文件的类型的。

#define __S_IFMT        0170000 /* These bits determine file type.  */

/* File types.  */
#define __S_IFDIR       0040000 /* Directory.  */
#define __S_IFCHR       0020000 /* Character device.  */
#define __S_IFBLK       0060000 /* Block device.  */
#define __S_IFREG       0100000 /* Regular file.  */
#define __S_IFIFO       0010000 /* FIFO.  */
#define __S_IFLNK       0120000 /* Symbolic link.  */
#define __S_IFSOCK      0140000 /* Socket.  */

# define S_IFMT         __S_IFMT
# define S_IFDIR        __S_IFDIR
# define S_IFCHR        __S_IFCHR
# define S_IFBLK        __S_IFBLK
# define S_IFREG        __S_IFREG

    利用上面的测试宏就可以判断文件的类型,至于文件的权限部分可以使用掩码的方式来处理。

具体代码如下:

void mode_to_letters(int mode,char str[])
{
//S_IS***测试宏
        strcpy(str,"----------");
        if(S_ISDIR(mode))str[0] = 'd';
        if(S_ISCHR(mode))str[0] = 'c';
        if(S_ISBLK(mode))str[0] = 'b';

//与 掩码
        if(mode&S_IRUSR)str[1] = 'r';
        if(mode&S_IWUSR)str[2] = 'w';
        if(mode&S_IXUSR)str[3] = 'x';

        if(mode&S_IRGRP)str[4] = 'r';
        if(mode&S_IWGRP)str[5] = 'w';
        if(mode&S_IXGRP)str[6] = 'x';

        if(mode&S_IROTH)str[7] = 'r';
        if(mode&S_IWOTH)str[8] = 'w';
        if(mode&S_IXOTH)str[9] = 'x';
}


四:ctime时间处理

    stat中的st_mtime是一个时间戳,而ls -l显示出来的是一个格式化后的字符串,所以需要对st_mtime字段进行格式化处理。

ctime接受一个time_t类型的值,可将其转换为Fri Jul 18 22:12:43 2014这样的格式,其中最前面的星期几不是我们要的,只要其后的值。代码如下

       printf("%.12s",4+ctime(&info_p->st_mtime));

info_p是一个文件的stat结构体。


五:属组和属主处理

    通过stat结构体的注释信息可以看出,stat只能获取文件的uid和gid并不是uid gid对应的用户或组的名称。

uid找到对应的用户名是可以通过getpwuid这个系统调用获取,这个系统调用接受一个uid返回一个passwd的结构体,这个结构体成员如下:

              char    *pw_name   User’s login name.
              uid_t    pw_uid    Numerical user ID.
              gid_t    pw_gid    Numerical group ID.
              char    *pw_dir    Initial working directory.
              char    *pw_shell  Program to use as shell.

pw_name字段就是我们要的。实现代码如下:

char *uid_to_name(uid_t uid)
{
        struct passwd *getpwuid(),*pw_ptr;
        static char numstr[10];
        if((pw_ptr = getpwuid(uid)) == NULL){
                sprintf(numstr,"%d",uid);
                return numstr;
        }
        else
                return pw_ptr->pw_name;
}

  
      gid对应的组名同样可以找到一个系统调用来获取,通过man我发现可以通过getgrgid()系统调用传入一个gid
返回这个gid对应的一个group结构体,这个结构体成员如下:
              char   *gr_name The name of the group.
              gid_t   gr_gid  Numerical group ID.
              char  **gr_mem  Pointer to a null-terminated array of character
                              pointers to member names.

实现gid到组名的转换的代码如下:

char *gid_to_name(gid_t gid)
{
         struct group *getgrgid(),*grp_ptr;
        static char numstr[10];
        if((grp_ptr = getgrgid(gid)) == NULL){
                sprintf(numstr,"%d",gid);
                return numstr;
        }
        else
                return grp_ptr->gr_name;
}

到此为止,ls -l基本就完成了。后续再进行完善。


六:完整代码示例

#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<dirent.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void do_ls(char []);
void dostat(char *);
void show_file_info(char *,struct stat *);
void mode_to_letters(int,char[]);
char *uid_to_name(uid_t);
char *gid_to_name(uid_t);

int main(int argc,char *argv[])
{
        if(argc == 1)
                do_ls(".");
        else
/*
 *依次遍历每一个参数,打印目录下的所有文件
 */
                while(--argc){
                        printf("%s:\n",*++argv);
                        do_ls(*argv);
                }
}

void do_ls(char dirname[])
{
/*
 *定义一个目录流,和目录流结构体保存读到的结果。
 */
        DIR *dir_ptr;
        struct dirent *direntp;
        if((dir_ptr = opendir(dirname)) == NULL)
                fprintf(stderr,"ls1:cannot open %s\n",dirname);
        else
        {
                while((direntp = readdir(dir_ptr)) != NULL)
        //打印结果
                        dostat(direntp->d_name);

//printf("%s\n",direntp->d_name);
        //关闭目录流
                closedir(dir_ptr);
        }
}

//获取文件信息stat结构体
void dostat(char *filename)
{
        struct stat info;
        if(stat(filename,&info) == -1)
                perror(filename);
        else

//分析stat结构体
                show_file_info(filename,&info);
}

void show_file_info(char *filename,struct stat *info_p)
{
        char *uid_to_name(),*ctime(),*gid_to_name(),*filemode();
//      void mode_to_letters();
        char modestr[11];
        mode_to_letters(info_p->st_mode,modestr);
        printf("%s",modestr);
        printf("%4d",(int)info_p->st_nlink);
        printf("%-8s",uid_to_name(info_p->st_uid));
        printf("%-8s",gid_to_name(info_p->st_gid));
        printf("%8ld",(long)info_p->st_size);
        printf("%.12s",4+ctime(&info_p->st_mtime));
        printf(" %s\n",filename);
}

//分析mode权限
void mode_to_letters(int mode,char str[])
{
//S_IS***测试宏
        strcpy(str,"----------");
        if(S_ISDIR(mode))str[0] = 'd';
        if(S_ISCHR(mode))str[0] = 'c';
        if(S_ISBLK(mode))str[0] = 'b';

//与 掩码
        if(mode&S_IRUSR)str[1] = 'r';
        if(mode&S_IWUSR)str[2] = 'w';
        if(mode&S_IXUSR)str[3] = 'x';

        if(mode&S_IRGRP)str[4] = 'r';
        if(mode&S_IWGRP)str[5] = 'w';
        if(mode&S_IXGRP)str[6] = 'x';

        if(mode&S_IROTH)str[7] = 'r';
        if(mode&S_IWOTH)str[8] = 'w';
        if(mode&S_IXOTH)str[9] = 'x';
}
// uid gid to name group
#include<pwd.h>
char *uid_to_name(uid_t uid)
{
        struct passwd *getpwuid(),*pw_ptr;
        static char numstr[10];
        if((pw_ptr = getpwuid(uid)) == NULL){
                sprintf(numstr,"%d",uid);
                return numstr;
        }
        else
                return pw_ptr->pw_name;
}

#include<grp.h>
char *gid_to_name(gid_t gid)
{
         struct group *getgrgid(),*grp_ptr;
        static char numstr[10];
        if((grp_ptr = getgrgid(gid)) == NULL){
                sprintf(numstr,"%d",gid);
                return numstr;
        }
        else
                return grp_ptr->gr_name;
}