수업/System Programming

[System Programming] ls 공부

hw-ani 2022. 10. 1. 14:51

directory 내의 files을 출력하는 ls를 구현하기위해선 우선 directory가 무엇인지 알아야한다.

UNIX/LINUX에서 "file"은 byte들이 모인 것일뿐이다. 어떤 프로그램으로 해석을 하느냐가 그것에 의미를 부여할 뿐이다.(매우 당연한 관점같지만 UNIX에서부터 시작된 것이다.)

LINUX에선 directory도 file로 취급한다. directory file엔 해당 directory 내부 정보나 다른 하위 directory 정보가 담긴다.

각 directory들은 tree구조로 엮인다.(뭐 node만들어서 엮고 하는 그런 특별한게 아니라 그냥 하위 directory 정보를 포함할 뿐이지 상위 directory 정보를 포함하진 않으니 tree 구조란 얘기인듯)

 

이제 System Call이 무엇인지 알아야한다. system call이란 OS 자체에서 제공하는, 운영체제를 이용하기위한 interface이다.

C언어 함수를 통해 이 system call을 이용할 수 있다. 우리는 많은 system call 중에 directory와 file의 정보를 가져올 수 있는 system call을 이용할 것이다.

참고: system call은 C로만 제한되나?
System call이 꼭 C와 연관되는건 아니다.
그런데도 주로 C를 통해 쓰는 이유는 아마 역사적인 것 때문이지 싶은데,,,
UNIX는 결국 C로 쓰였다.(초반엔 B로도 쓰이긴했다는데 뭐...)
그말인즉, UNIX(OS)가 있기 전에 C compiler가 먼저 있었다고 봐도 된다.(시간순서를 말하는게 아니다, 처음엔 C compiler가 있어야 C로 쓴 UNIX source code를 컴파일할 수 있다는 의미이다.)
아마 어느 정도 이런 이유에서 system call이 C를 통해 사용되도록 된게 아닐까 생각해본다.

주의해야할 것이 C의 system call 함수들은 실제 system call이 아니다.
system call은 OS에서 제공하는 것이다. C와는 연관이 없다. 그저 machine instruction(s)일 뿐이다.
우리가 C library를 통해 사용하는 system call은 실제 system call의 tiny wrapping function이다.

즉, system call이 C에 국한된 것은 아니지만 대부분 programming language들이 C의 system call library를 이용하는 방식으로 구현된다. 그 이유로는 POSIX에서 system 함수들을 C 코드 관점에서 기술하는 것도 있고, portability 이슈 때문도 있다.

 


아래는 `ls -l` 명령어의 출력 형식이다.

 

1. 명시된 directory 내의 files 목록을 받아와서

2. 각 file의 특성에 접근해 위와 같이 print

하면 된다.

 

 

1. directory 내의 files 목록 받아오기

DIR* opendir(char *);
struct dirent readdir(DIR *);
int closedir(DIR*)

위 세 함수를 이용한다.

opendir을 통해 directory를 열고, readdir로 해당 directory내의 files 정보를 하나씩 받아온다.

여기서 DIR은 FILE 처럼 directory 정보를 포함하는 structure이다.

file 정보는 dirent structure에 담겨 반환된다.

 

dirent structure의 members는 아래와 같다.

struct dirent {
    ino_t	d_ino;
    __uint16_t	d_reclen;
    __uint8_t	d_type;
    __uint8_t	d_namlen;
    char	d_name[255+1];
};

지금 우리에게 필요한 정보는 file 이름인 d_name이다.

 

 

2. file 상세 정보 접근

file 이름을 알았으니 이를 이용해 더 자세한 나머지 정보를 찾을 수 있다.

int stat(char *, struct stat)

위 함수(system call)를 호출하면 넘겨준 stat structure에 해당 file의 상세정보가 담겨서 나온다.

 

stat structure의 members는 아래와 같다.

struct stat {
    mode_t st_mode;	//파일 타입과 퍼미션
    ino_t st_ino;	//i-node 번호
    dev_t st_dev;	//장치 번호
    dev_t st_rdev;	//특수 파일의 장치 번호
    nlink_t st_nlink;	//링크 수
    uid_t st_uid;	//소유자의 USER ID
    gid_t st_gid;	//소유자의 GROUP ID
    off_t st_size;	//정규파일의 바이트 수
    time_t st_atime;	//마지막 접근 시각
    time_t st_mtime:	//마지막 수정 시각
    time_t st_ctime;	//마지막 상태 변경 시각
    long st_blksize;	// I/O 블록 크기
    long st_blocks;	//할당한 블록의 개수
};

여기에 위 `ls -l` 출력 형식대로 출력하기위한 모든 정보가 담겨있다.

 

 

 

위 정보들 중 추가로 해석해야하는 것도 있다.

1) st_uid와 st_gid는 ID가 아닌 user name과 group name 형식으로 바꿔야한다.

이를 위해 getpwuid/getgrgid 함수와 passwd/group 구조체를 알아야한다.

위 두 함수는 passwd 구조체와 group 구조체를 반환하는데, 해당 구조체의 멤버 중 pw_name과 gr_name이 char* type으로 실제 name을 나타낸다.

따라서 getpwuid(~)->pw_name과 getgrgid(~)->gr_name 식으로 함수를 사용해서 user와 group의 name을 얻을 수 있다.

 

2)st_mode의 각 값들의 의미에 대해 알아야 한다.

st_mode는 16비트 값이다.

각 값들을 해석해서 ls -l의 첫번째 부분인 `drwxrwxr--` 같은 것으로 나타내면 된다.

suid/sgid/sticky를 special bits라고 하는데, 설명은 여기서..

type은 `-`는 일반 파일, `d`는 directory, `b`는 device file 등.. file의 type을 나타낸다. 이것은 한번 만들면 수정이 안된다.

 

bit-masking 방법으로 각 bit들을 추출해서 해석하면된다.(이미 정의된 macro 변수나 함수가 있으니 잘 이용하면 됨)

뒤에 9비트 뿐만 아니라, 링크 보면 알겠지만 suid/sgid/sticky도 ls -l에 10비트로 나타나는 형식에 영향을 주니(s, t, ...) 뒤에 12비트는 같이 잘 해석하면 되고,

type을 나타내는 앞 4비트는 따로 해석해서 `-`인지 `d`인지 작성해주면 된다. file의 type이 4종류가 아니므로 앞쪽과는 좀 다르게 bit 하나만 켜져있다고 특정 type의 파일이 되는게 아니라 전부 살펴봐야한다.

 

참고로 file은 종류에 따라 만드는 함수가 다르다. 그래서 mode의 권한 비트들은 나중에라도 `chmod` 같은 함수로 수정이 되지만, file type을 바꾸고 싶다면 지우고 다시 만들어야한다.

 

 

※ 참고 ※
"컴파일"시 header file은 "컴파일"하지 않는다. 물론 linking을 할때도 `.h` 파일을 같이 linking하진 않는다.
header file은 source file에 include돼므로 사실상 그 source file을 컴파일할때, 필요한 정보들이 같이 목적 파일로 컴파일이 된다.
Standard library를 include했다면 보통 사용되는 그 library 함수들의 declarations 같은게 같이 컴파일된다.
(이후 linking 과정에서 어딘가에 컴파일돼있는 standard library 함수 definition들과 같이 linking 되도록 컴파일러가 구현돼있을 것)

 

자세한 구현 코드는 생략

 

 


참고) effective user가 뭐지?

https://www.it-note.kr/240

위 링크된 글에 잘 정리돼있다. 유저 A가 어떤 파일 T에 쓰기 권한이 없는데, 그 T에 쓰기 작업을 하는 프로세스를 A가 실행시킨다면? 권한이 없어서 잘 작동하지 않는다. 이때 사용할 수 있는 게 effective user이다. 자세한 건 위 글을 참고!