代码改变世界

Unix ls命令的实现

2012-11-21 13:13  wid  阅读(7010)  评论(6编辑  收藏  举报

说明一下: 这篇博文是我的一个好友借用我的账号发的, 我想让他注册个博客但是他觉得平时写博也不多, 所以就发到我博客里来了。

---------------

 

这是ls命令的实现,写的比较仓促,所以代码不是非常精简,望见谅。程序实现的参数有1ACFLHRacdfgilnoqrstu,大部分的参数和标准ls的功能一样,大家可参阅联机帮助来获取帮助。

程序的-n选项和标准ls命令不同,它会关闭-g,-o选项,程序所实现的分栏功能并不高明,没有标准ls命令要好。程序所使用的fts系列的函数大家可参阅联机帮助,man fts_open便可获得。另外应博主的要求写了些必要的注释,不过由于程序的水准并不高,实际上注释所起的用处不大。另外-U选项有一句是废话,相应的注释指出了。如果各位有谁对代码有兴趣而又看不大懂的可以找本人。最后程序借鉴了v6的实现。代码如下,大家可参阅。

/* ls -[1ACFLHRacdfgilnoqrstu] [file ...] */
/*  作者: 莫尘。 源码参考了Unix v6的实现 */

#include    <pwd.h>
#include    <grp.h>
#include     <fts.h> 
#include     <err.h>
#include    <time.h>
#include    <errno.h>
#include    <ctype.h>
#include    <stdio.h>
#include     <string.h>
#include     <stdlib.h>
#include    <unistd.h>
#include     <sys/stat.h>
#include    <sys/ioctl.h> 

#define IS_NOPRINT(b)     ((b)->fts_number == 1)    /* 1 表示为not print */
struct     buf{
    int num;
    int total;
    int bcfile;
    int maxlen;
    int usrlen;
    int grplen;
    int linklen;
    int sizelen;
    int inodelen;
    int blocklen;
    FTSENT *list;        /* FTS, FTSENT结构和fts函数具体参考/usr/include/fts.h,联机帮助会有更多的资料以供参考(man fts_open可以获得联机帮助的信息 */
}b;

typedef struct name{
    union{
        int uid;
        char *usr;
    }usr;        
    union{
        int gid;
        char *grp;
    }grp;
    char date[0];        /* 为了节省空间而做出的策略,date不占用任何空间 */
}NAME;

int    col;            
int     rflg = 1;
int    putout = 0;
int    seeusr = 1, seegrp = 1;
int    seeuid, seegid, singleflg, fts_options;
int     Aflg, Cflg, Fflg, Lflg, Rflg, Sflg, Uflg;
int    cflg, dflg, fflg, iflg, lflg, oflg, pflg, qflg, sflg, tflg, uflg;
static     char *com_name;    

void    printone(void);
void    printcol(void);
void    printlong(void);
void    q_copy(char *a, char *b);
void    do_ls(int argc, char *argv[]);
void    do_print(FTSENT *fp, FTSENT *list);
int    cmparg(const FTSENT **a, const FTSENT **b);
int    cmpname(const FTSENT *a, const FTSENT *b);
int    cmptime(const FTSENT *a, const FTSENT *b);
int    cmpsize(const FTSENT *a, const FTSENT *b);
static     void    (*printfcn)(void);    
static     void    printtime(time_t t);
static     void    printlink(FTSENT *p);
static     void    modetostr(mode_t mode, char *buf);
static     int    printtype(mode_t mode);
static     int    printname(FTSENT *b, int flg);
static     int    (*sortfcn)(const FTSENT *a, const FTSENT *b);

int main(int argc, char *argv[])
{
    char *p;
    register int c;
    struct winsize wbuf;
    static char dot[] = ".", *dotav[] = { dot, (char *)NULL };

    com_name = *argv;
    if(isatty(STDOUT_FILENO)){        /* 得出终端的宽度,默认宽度为80,如果获取失败,将激活-1选项 */
        if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &wbuf) == -1 || wbuf.ws_col == 0){
            if(p = getenv("COLUMNS"))
                col = atoi(p);
            else
                col = 80;
        }
        else
            col = wbuf.ws_col;
        Cflg = qflg = 1;
        
    }
    else
        singleflg = 1;
    sortfcn = cmpname;            
    printfcn = printcol;
    fts_options = FTS_PHYSICAL;
    while(--argc > 0 && (*++argv)[0] == '-'){
        while(c = *++argv[0])
        switch(c){
        case '1':
            Cflg = 0;
            singleflg = 1;
            printfcn = printone;
            break;
        case 'A':
            Aflg = 1;
            break;
        case 'C':
            lflg = 0;
            Cflg = 1;
            printfcn = printcol;
            break;
        case 'F':
            pflg = 0;    
            Fflg = 1;
            break;
        case 'H':
            fts_options |= FTS_COMFOLLOW;
            break;
        case 'L':
            fts_options &= ~FTS_PHYSICAL;
            fts_options |= FTS_LOGICAL;
            break;
        case 'R':    
            Rflg = 1;    
            dflg = 0;
            break;
        case 'S':
            Sflg = 1;
            sortfcn = cmpsize;
            break; 
        case 'U': 
            Uflg = 1;
            sortfcn = (int(*)(const FTSENT **, const FTSENT **))NULL;    /* 这一步实则不必要,编写的理由是个人的感情因素 */
            break; 
        case 'a':         
            Aflg = 1;
            fts_options |= FTS_SEEDOT;
            break;
        case 'c':        /* -u,-c,-t和标准ls命令的表现方式一样,详见联机帮助,可ls --help或man ls */
            uflg = 0;    
            cflg = 1;
            if(lflg == 0)
                sortfcn = cmptime;
            break;
        case 'd':
            dflg = 1;
            Rflg = 0;
            break;
        case 'f':        /* -f和标准ls一样,联机帮助有其说明,注意此选项的输出是没有颜色的 */
            fts_options |= FTS_SEEDOT;
            fflg = Aflg = Uflg = 1;
            sortfcn = (int(*)(const FTSENT **, const FTSENT **))NULL;
            lflg = sflg = tflg = 0;    
            break;    
        case 'g':
            lflg = 1;
            seeusr = 0;
            printfcn = printlong;
            break;
        case 'i':
            iflg = 1;
            break;
        case 'l':
            Cflg = 0;
            lflg = 1;
            if(tflg == 0)
                sortfcn = cmpname;
            printfcn = printlong;
            break;
        case 'n':
            lflg = 1;
            seeuid = seegid = 1;
            break; 
        case 'o':
            lflg = 1;
            seegrp = 0;
            printfcn = printlong;
            break;
        case 'p':         /* -p和-F的表现和标准ls命令一样,具体参阅联机帮助 */
            Fflg = 0;
            pflg = 1;
            break;
        case 'q':
            qflg = 1;
            break;    
        case 'r':
            rflg = -1;
            break;
        case 's':
            sflg = 1;
            break;
        case 't':
            tflg = 1;
            sortfcn = cmptime;
            break;
        case 'u':
            cflg = 0;
            uflg = 1;
            if(lflg == 0)
                sortfcn = cmptime;    
            break;
        default:
            fprintf(stderr, "Usage: %s -[1ACFLHRTacdfgilnoqrstu] [file ...]\n", com_name);
            exit(1);    
        }
    }
    if(lflg)    /* -l命令会导致-1选项的失效 */
        printfcn = printlong;
    if(argc)
        do_ls(argc, argv);
    else
        do_ls(1, dotav);                
    return 0;
}

void do_ls(int argc, char *argv[])
{
    FTS *ftsp;
    register FTSENT *p, *tmp;

    ftsp = fts_open(argv, fts_options, Uflg ? NULL : cmparg);    /* 把所有文件读入一棵树中, 详见联机帮助 */
    if(ftsp == (FTS *)NULL){
        fprintf(stderr, "%s: fts_open error\n", com_name);
        exit(1);
    }
    do_print(NULL, fts_children(ftsp, 0));
    if(dflg)
        return;
    while(p = fts_read(ftsp)){
        switch(p->fts_info){
        case FTS_DC:
            warnx("%s: directory causes a cycle", p->fts_name);    /* err, warn, warnx等函数具体可查看err.h头文件和man warn来了解其功能 */
            break;
        case FTS_ERR:
        case FTS_DNR:
            errno = p->fts_errno;    /* fts函数不会设置errno,函数具体功能参阅联机帮助 */
            warn("%s", p->fts_name);
            break; 
        case FTS_D:
            if(p->fts_level != 0 && p->fts_name == '.' && Aflg == 0)
                break;
            if(putout)
                printf("\n%s:\n", p->fts_path);
            else if(argc > 1){
                printf("%s:\n", p->fts_path);
                putout = 1;
            }
            tmp = fts_children(ftsp, 0);
            do_print(p, tmp);
            if(Rflg == 0 && tmp)
                fts_set(ftsp, p, FTS_SKIP);
            break;
        default:
            break;
        } 
    }
    fts_close(ftsp);    /* 分配的内存会自动释放 */
}

void do_print(FTSENT *fp, FTSENT *list)
{
    NAME *np;
    char buf[20];
    struct group *gp;
    struct passwd *pw;
    register FTSENT *p;
    register struct stat *q;
    int num, total, needstat;
    int n, m, in, im, sn, sm;
    int un, um, gn, gm, ln, lm, bn, bm, bcfile; 

    if(list == NULL)
        return;
    num = total = bcfile = 0;
    n = m = in = im = sn = sm = 0;
    un = um = gn = gm = ln = lm = bn = bm = 0;
    needstat = iflg | sflg | lflg;
    for(p = list; p; p = p->fts_link){
        if(p->fts_info == FTS_ERR || p->fts_info == FTS_NS){
            errno = p->fts_errno;
            warn("%s", p->fts_name);
            p->fts_number = 1;
            continue;
        }
        if(fp == NULL){    /* 若为命令行参数 */
            if(p->fts_info == FTS_D && dflg == 0){
                p->fts_number = 1;    
                continue;
            }
        }
        else{
            if(p->fts_name[0] == '.' && Aflg == 0){
                p->fts_number = 1;
                continue;
            }
        }
        if(qflg)    /* 以?代替不可打印的字符 */
            q_copy(p->fts_name, p->fts_name);
        if(m < (n = p->fts_namelen))
            m = n;
        if(needstat){
            q = p->fts_statp;
            if(iflg && im < (in = q->st_ino))
                im = in;        
            if(sflg && bm < (bn = q->st_blocks))
                bm = bn;
            if(sm < (sn = q->st_size))
                sm = sn;
            if(lm < (ln = q->st_nlink))
                lm = ln;
            total += q->st_blocks;
            if(lflg){
                if(seeuid){        /* 与标准ls不同,本程序,-n选项会使-g -o选项失效 */
                    if(um < (un = q->st_uid))
                        um = un;
                }
                else if(seeusr){
                    pw = getpwuid(q->st_uid);
                    if(pw && um < (un = strlen(pw->pw_name)))
                        um = un;
                }
                else
                    um = 0;
                if(seegid){
                    if(gm < (gn = q->st_gid))
                        gm = gn;                
                }
                else if(seegrp){
                    gp = getgrgid(q->st_gid);
                    if(gp && gm < (gn = strlen(gp->gr_name)))
                        gm = gn;
                }
                else
                    gm = 0;
                if(seeuid && seegid){
                    np = malloc(sizeof(NAME));
                    np->usr.uid = un;
                    np->grp.gid = gn;
                }
                else{
                    if((np = malloc(sizeof(NAME) + un + gn + !(!un) + !(!gn))) == NULL)    /* !(!un) + !(!gn)用来判断是否有设置seeusr和seegrp */
                        err(1, NULL);
                    if(seeusr){
                        np->usr.usr = &np->date[0];    
                        strcpy(np->usr.usr, pw->pw_name);
                    }
                    if(seegrp){
                        np->grp.grp = &np->date[un + !(!un)];
                        strcpy(np->grp.grp, gp->gr_name);
                    }
                }
                if(S_ISCHR(q->st_mode) || S_ISBLK(q->st_mode))    
                    bcfile = 1;        
                p->fts_pointer = np;    
            }
        }    
        ++num;            
    }
    if(num == 0)
        return;
    b.list = list;
    b.num = num;
    b.maxlen = m;
    if(needstat){        
        b.total = total;
        b.bcfile = bcfile;
        snprintf(buf, sizeof(buf), "%u", lm);
        b.linklen = strlen(buf);
        snprintf(buf, sizeof(buf), "%u", sm);
        b.sizelen = strlen(buf);    
        snprintf(buf, sizeof(buf), "%u", im);
        b.inodelen = strlen(buf);
        snprintf(buf, sizeof(buf), "%u", bm);
        b.blocklen = strlen(buf);
        if(seeuid && seegid){
            snprintf(buf, sizeof(buf), "%u", um);            
            b.usrlen = strlen(buf);
            snprintf(buf, sizeof(buf), "%u", gm);
            b.grplen = strlen(buf);
        }
        else{
            b.usrlen = um;
            b.grplen = gm;
        }
    }
    printfcn();
    putout = 1;    
    if(lflg)
        for(p = list; p; p = p->fts_link)
            free(p->fts_pointer);
}    

int cmparg(const FTSENT **a, const FTSENT **b)
{
    FTSENT *ap, *bp;

    ap = *(FTSENT **)a;
    if(ap->fts_info == FTS_ERR)
        return 0;    
    bp = *(FTSENT **)b;
    if(bp->fts_info == FTS_ERR)
        return 0;
    if(ap->fts_info == FTS_NS || bp->fts_info == FTS_NS)
        return cmpname(ap, bp);
    if(ap->fts_info == bp->fts_info)
        sortfcn(ap, bp);
    if(ap->fts_level == 0){        /* 命令行参数的目录默认大于文件, 详见标准ls命令的说明文档 */
        if(ap->fts_info == FTS_D){
            if(bp->fts_info != FTS_D)
                return 1;
        }
        else if(bp->fts_info == FTS_D)
            return -1;
    }
    return sortfcn(ap, bp);
}
    
int cmpname(const FTSENT *a, const FTSENT *b)
{
    return rflg * strcmp(a->fts_name, b->fts_name);
}

int cmpsize(const FTSENT *a, const FTSENT *b)
{
    return rflg * (b->fts_statp->st_size - a->fts_statp->st_size);
}

int cmptime(const FTSENT *a, const FTSENT *b)
{
    if(cflg)
        return rflg * (b->fts_statp->st_ctime - a->fts_statp->st_ctime);    
    else if(uflg)
        return rflg * (b->fts_statp->st_atime - a->fts_statp->st_atime);
    else
        return rflg * (b->fts_statp->st_mtime - a->fts_statp->st_mtime);
}

void q_copy(char *a, char *b)
{
    register char *p, *q;

    for(p = a, q = b; *p; p++, q++){
        if(isprint(*p))
            *q = *p;
        else
            *q = '?';
    }
}
    
void printone(void)
{
    register FTSENT *p;

    for(p = b.list; p; p = p->fts_link){
        if(IS_NOPRINT(p))
            continue;
        printname(p, 1);
        putchar('\n');
    }
}

void printcol(void)
{
    static int lastnum = -1;
    static FTSENT **array;    
    register FTSENT *p;
    int i, j, k, tm, cn, chn, num, len, nrow, ncol, diff, left, tmcol;

    if(b.num > lastnum){
        lastnum = b.num;
        if((array = realloc(array, sizeof(FTSENT *) * b.num)) == NULL){
            err(1, NULL);
            printone();
        }
    }
    num = 0;
    for(p = b.list; p; p = p->fts_link) 
        if(p->fts_number != 1) 
            array[num++] = p; i = b.maxlen;
    if(iflg)
        i += b.inodelen + 1;
    if(sflg)
        i += b.blocklen + 1;
    if(pflg || Fflg)
        i += 1;
    len = (i + 8) & ~7;    /* 规整到下一个8的倍数 */
    if(col < 2 * len){    
        printone();
        return;
    }
    ncol = col / len;
    nrow =  num / ncol;
    if(num % ncol)
        nrow++;
    tmcol = num / nrow;        
    left = num - (tmcol * nrow);    
    diff = left / nrow;
    diff += 1;            /* 每行需另外输出的的个数 */    
    if(sflg)
        printf("total = %u\n", b.total);
    tm = 0;
    for(i = 0; i < nrow; ++i){
        k = len;            /* 输出应该停止处 */
        for(j = chn = 0;  j < ncol; ++j){
            if(tm >= num)
                break;
            if(j >= tmcol){    /* 用来调整使每行的个数尽可能一样 */
                if(j < tmcol + diff && left)
                    --left;
                else 
                    break;
            }
            chn += printname(array[tm++], 1);    
            while((cn = (chn + 8 & ~7)) <= k){
                putchar('\t');
                chn = cn;
            }
            k += len;
        }
        putchar('\n');
    }
}

void printlong(void)
{
    NAME *np;
    int mode;
    char buf[10];
    register FTSENT *p;
    register struct stat *sp;

    if(b.list->fts_level != 0)
        printf("total %u\n", b.total);
    for(p = b.list; p; p = p->fts_link){
        if(IS_NOPRINT(p))
            continue;
        sp = p->fts_statp;
        mode = sp->st_mode;
        if(iflg)
            printf("%*lu ", b.inodelen, sp->st_ino);
        if(sflg)
            printf("%*lu ", b.blocklen, sp->st_blocks);
        modetostr(mode, buf);    
        np = p->fts_pointer;
        printf("%s ", buf);
        printf("%*u ", b.linklen, sp->st_nlink);
        if(seeuid)
            printf("%*u ", b.usrlen, np->usr.uid); 
        else if(seeusr)
            printf("%*s ", b.usrlen, np->usr.usr);
        if(seegid)
            printf("%*u ", b.grplen, np->grp.gid);
        else if(seegrp)
            printf("%*s ", b.grplen, np->grp.grp);
        if(S_ISCHR(mode) || S_ISBLK(mode))    
            printf("%3d,%3d ", major(sp->st_rdev), minor(sp->st_rdev));
        else if(b.bcfile)
            printf("%*s%*lu ", 8 - b.sizelen, "", b.sizelen, sp->st_size);
        else
            printf("%*lu ", b.sizelen, sp->st_size);
        if(cflg)
            printtime(sp->st_ctime);
        else if(uflg)
            printtime(sp->st_atime);
        else
            printtime(sp->st_mtime);
        printname(p, 0);
        if(Fflg || pflg)
            printtype(mode);        
        if(S_ISLNK(mode))
            printlink(p);
        putchar('\n');
    }
}

static void printlink(FTSENT *p)
{
    int lnklen;
    char name[256], path[256];

    if(p->fts_level == 0)
        snprintf(name, sizeof(name), "%s", p->fts_name);
    else 
        snprintf(name, sizeof(name), "%s/%s", p->fts_parent->fts_accpath, p->fts_name);    
    if((lnklen = readlink(name, path, sizeof(path) - 1)) == -1){        /* readlink详见联机帮助 */
        fprintf(stderr, "\n%s: %s: %s\n", com_name, name, strerror(errno));
        return;
    }
    path[lnklen] = '\0';
    printf(" -> %s", path);
}

static void printtime(time_t t)
{
    char *s;
    time_t year, tm;

    time(&tm);
    year = tm - 60 * 30 * 24 * 60 * 60;
    s = ctime(&t);
    if(t < year)
        printf("%.7s %.4s ", s + 4, s + 20);
    else
        printf("%.12s ", s + 4);
}
    
static void modetostr(mode_t mode, char *buf)
{
    strcpy(buf, "----------");
    if(S_ISDIR(mode))
        buf[0] = 'd';
    if(S_ISCHR(mode))
        buf[0] = 'c';
    if(S_ISBLK(mode))
        buf[0] = 'b';
    if(S_ISLNK(mode))
        buf[0] = 'l';
    if(S_ISSOCK(mode))
        buf[0] = 's';
    if(S_ISFIFO(mode))
        buf[0] = 'p';
    if(mode & S_IRUSR)
        buf[1] = 'r';
    if(mode & S_IWUSR)
        buf[2] = 'w';
    if(mode & S_IXUSR)
        buf[3] = 'x';
    if(mode & S_ISUID)
        buf[3] = 's';
    if(mode & S_IRGRP)
        buf[4] = 'r';
    if(mode & S_IWGRP)
        buf[5] = 'w';
    if(mode & S_IXGRP)
        buf[6] = 'x';
    if(mode & S_ISGID)
        buf[6] = 'x';
    if(mode & S_IROTH)
        buf[7] = 'r';
    if(mode & S_IWOTH)
        buf[8] = 'w';
    if(mode & S_IXOTH)
        buf[9] = 'x';
    if(mode & S_ISVTX)
        buf[9] = 't';
}

static int printname(FTSENT *p, int flg)
{
    int np, mode;
    struct stat *sp;

    np = 0;
    sp = p->fts_statp;
    if(flg){
        if(iflg)
            np += printf("%*lu ", b.inodelen, sp->st_ino);
        if(sflg)
            np += printf("%*lu ", b.blocklen, sp->st_blocks);
    }
    mode = sp->st_mode;
    if(fflg)
        np += printf("%s", p->fts_name); 
    else{                            /* 注意printf的返回值 */
        if(mode & S_ISUID){
            printf("\033[37;41m");    
            np += printf("%s", p->fts_name);
            printf("\033[0m");
        }
        else if(mode & S_ISGID){
            printf("\033[30;43m");
            np += printf("%s", p->fts_name);
            printf("\033[0m");
        }
        else if(mode & S_ISVTX){
            printf("\033[37;44m");
            np += printf("%s", p->fts_name);
            printf("\033[0m");
        }
        else{
            switch(mode & S_IFMT){
            case S_IFDIR:
                printf("\033[01;34m");
                np += printf("%s", p->fts_name);
                printf("\033[0m");
                break;
            case S_IFIFO:
                printf("\033[33m");
                np += printf("%s", p->fts_name);
                printf("\033[0m");
                break;
            case S_IFSOCK:
                printf("\033[01;35m");
                np += printf("%s", p->fts_name);
                printf("\033[0m");
                break;    
            case S_IFLNK:
                printf("\033[01;36m");
                np += printf("%s", p->fts_name);
                printf("\033[0m");
                break;
            case S_IFCHR:
            case S_IFBLK:
                printf("\033[01;33m");
                np += printf("%s", p->fts_name);
                printf("\033[0m");
                break;
            default:
                if(mode & S_IXUSR || mode & S_IXGRP || mode & S_IXOTH){
                                printf("\033[01;32m");
                    np += printf("%s", p->fts_name);
                    printf("\033[0m");
                }
                else
                    np += printf("%s", p->fts_name);
                break;
            }
        }
    }
    if(Fflg || pflg)
        np += printtype(mode);    
    return np;    
} 

static int printtype(mode_t mode)
{
    switch(mode & S_IFMT){
    case S_IFDIR:
        putchar('/');
        return 1;
    case S_IFLNK:
        if(Fflg == 0)
            break;
        putchar('@');
        return 1;
    case S_IFSOCK:
        if(Fflg == 0)
            break;
        putchar('=');
        return 1;
    default:
        if(Fflg == 0)
            break;
        if(mode & (S_IXUSR | S_IXGRP | S_IXOTH)){
            putchar('*');
            return 1;
        }
        break;
    }
    return 0;
}