본문 바로가기

C

[시스템프로그래밍] ls 명령어 구현 (-al, -alR)

Chapter.3: Directories and File Properties : Looking through ls


ls의 Main logic

opendir
while(readdir)
         print d_name
closedir

ls -l

파일에 대한 상세정보를 보여준다.

상세정보는 stat()이라는 system call을 통해 얻어올 수 있다.

Masking

st_mode: 파일 타입과 권한을 의미하는 16-bit.

  •  masking
    • st_mode를 위 그림의 mode처럼 rwx 형태로 나타내기 위해서
    • &연산으로 st_mode의 파일타입과 권한을 각각 decode한다.

ls -l 구현

  1. main 함수:
    • 프로그램의 진입점입니다. 사용자로부터 명령행 인수를 받고 주어진 디렉터리 또는 파일에 따라 다른 함수를 호출합니다.
  2. do_ls 함수:
    • 주어진 디렉터리 내의 파일과 디렉터리를 나열합니다.
    • opendir 함수로 디렉터리를 열고 readdir 함수로 디렉터리 내의 항목들을 읽어옵니다.
  3. do_ls_al 함수:
    • 주어진 파일에 대한 상세 정보를 표시합니다.
    • stat 함수를 사용하여 파일의 상태 정보를 얻어옵니다.
  4. do_ls_alR 함수:
    • 주어진 디렉터리 내의 파일과 디렉터리를 재귀적으로 나열하고 상세 정보를 표시합니다.
    • do_ls_al 함수와 유사하지만 재귀적으로 하위 디렉터리를 처리합니다.
  5. dostat 함수:
    • stat 함수를 사용하여 파일 또는 디렉터리의 상태 정보를 얻고, 그 정보를 show_file_info 함수에 전달합니다.
  6. show_file_info 함수:
    • 파일 또는 디렉터리의 상세 정보를 표시합니다. 이 정보에는 파일 유형, 권한, 링크 수, 소유자, 그룹, 파일 크기 및 수정 시간이 포함됩니다.
  7. mode_to_letters 함수:
    • 파일 권한 모드를 문자열로 변환합니다. 예를 들어, "drwxr-xr-x"와 같이 표현된 파일 권한을 생성합니다.
  8. uid_to_name 및 gid_to_name 함수:
    • 사용자 ID (UID) 및 그룹 ID (GID)를 사용자 이름과 그룹 이름으로 변환하는 함수입니다.
  9. print_curDIR 함수:
    • 현재 디렉터리 내의 파일 크기 합계를 계산하고 블록 단위로 출력합니다. 이 함수는 디렉터리의 내용을 보기 전에 디렉터리의 크기를 표시합니다.
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>

void dostat(char path[], char *filename);
void show_file_info(char *filename, struct stat *info_p);
void mode_to_letters(int mode, char str[]);
char *uid_to_name(uid_t uid);
char *gid_to_name(gid_t gid);
void print_curDIR(char dirname[]);
void do_ls(char dirname[]);
void do_ls_al(char *filename);
void do_ls_alR(char dirname[]);


int main(int argc, char* argv[]) {
    if (argc == 1) { // 명령어만 입력한 경우
        do_ls("."); // 현재 디렉터리의 항목들을 나열함
    }
    else {
        struct stat info;
        for (int i=1; i<argc; i++) {
            if(stat(argv[i], &info) == 0){
                if(S_ISDIR(info.st_mode)){  // 디렉터리명이 주어지면 -alR
                    do_ls_alR(argv[i]);
                }
                else if(S_ISREG(info.st_mode)){ // 파일명이 주어지면 -al
                    do_ls_al(argv[i]);
                }
            }
        }
    }
    return 0;
}

void do_ls(char dirname[]) {
    DIR *dir_ptr;
    struct dirent *direntp;

    if ((dir_ptr = opendir(dirname)) == NULL) {
        fprintf(stderr, "lsl: cannot open %s\n", dirname);
        return;
    }
    while((direntp = readdir(dir_ptr)) != NULL)
        printf("%s\n", direntp->d_name);
    closedir(dir_ptr);
}

void do_ls_al(char *filename) {
    struct stat info;
    if (stat(filename, &info) == -1)
        perror(filename);
    else {
        show_file_info(filename, &info);
    }
}

void do_ls_alR(char dirname[]) {
    //주어진 디렉터리 내의 파일 및 디렉터리를 나열하고 출력하는 함수
    DIR *dir_ptr;
    struct dirent *direntp;

    //opendir: 디렉터리 열기, 실패하면 오류메시지 출력
    if ((dir_ptr = opendir(dirname)) == NULL) {
        fprintf(stderr, "lsl: cannot open %s\n", dirname);
        return;
    }

    // 주어진 디렉터리명과 항목들의 총 크기를 출력함 
    print_curDIR(dirname);

    // 다음 탐색을 위해 하위 디렉터리를 저장할 배열
    char* nextdirs[1024];
    int dir_cnt = 0;

    // readdir: 디렉터리 내의 항목 읽기
    while ((direntp = readdir(dir_ptr)) != NULL) {
        char fullpath[1024];  // 파일 및 디렉터리의 전체 경로
        snprintf(fullpath, sizeof(fullpath), "%s/%s", dirname, direntp->d_name);

        // 현재 디렉터리 또는 상위 디렉터리인 경우
        if (strcmp(direntp->d_name, ".") == 0 || strcmp(direntp->d_name, "..") == 0) {
            dostat(dirname, direntp->d_name);
        }
        // 하위 디렉터리거나 파일인 경우
        else if (strcmp(direntp->d_name, ".") != 0 && strcmp(direntp->d_name, "..") != 0) {
            if (direntp->d_type == DT_DIR) {
                // 하위 디렉터리인 경우
                nextdirs[dir_cnt] = strdup(fullpath); // 다음 탐색을 위해 저장
                dostat(fullpath, direntp->d_name);
                dir_cnt++;
            }
            else { //파일인 경우
                dostat(fullpath, direntp->d_name);
            }
        }
    }
    printf("\n");
    closedir(dir_ptr); // 디렉터리 닫기
    // 하위 디렉터리 탐색
    for (int i = 0; i < dir_cnt; i++) {
        do_ls_alR(nextdirs[i]);
    }
}

void dostat(char path[], char *filename) {
    //stat()이 파일 정보를 가져와서 info구조체에 저장하는 함수
    struct stat info;
    if (stat(path, &info) == -1)
        perror(filename);
    else {
        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);
}

void mode_to_letters(int mode, char str[]) {
    //st_mode 값을 문자열로 변환하는 함수
    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';    
}

char *uid_to_name(uid_t uid) {
    // getpwuid 함수를 사용하여 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;
}

char *gid_to_name(gid_t gid) {
    // getgrgid 함수를 사용하여 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;
}

void print_curDIR(char dirname[]) {
    // 디렉터리 내의 파일 크기 합계를 계산하여 블록 단위로 출력하는 함수
    DIR *dir_ptr;
    struct dirent *direntp;

    long long total_blocks = 0;

    if ((dir_ptr = opendir(dirname)) == NULL) {
        fprintf(stderr, "lsl: cannot open %s\n", dirname);
        return;
    }

    while ((direntp = readdir(dir_ptr)) != NULL) {
        char subpath[1024];
        snprintf(subpath, sizeof(subpath), "%s/%s", dirname, direntp->d_name);

        struct stat info;
        if (stat(subpath, &info) != -1) {
            // st_blocks: 파일의 크기
            total_blocks += info.st_blocks;
        }
    }
    printf("%s:\n", dirname);
    printf("합계 %lld\n", total_blocks / 2);
    
    closedir(dir_ptr);
}