[System Programming] 5.3 디렉터리
What is Directory
- A kind of file used to organize
- regular, pipe, special files and other directory as directory entries
- directory entry로 관리
- format imposed by Unix/Linux
- regular, pipe, special files and other directory as directory entries
- file names are contained only in directory entries
- i-노드에는 file 이름이 없음
- permissions
- r: readable
- w: writable
- x: searchable
- 파일에서는 실행이 검색 가능으로 대치
directory entry -> DIR 구조체
File/Directory access permissions
시험 내용
- Read
- file: file data를 읽을 수 있는가?(copy 가능?)
- Directory: file list를 read할 수 있는가?(ls 가능?)
- write
- File: file data를 수정할 수 있는가?
- Directory: directory의 file을 생성, 삭제할 수 있는가?
- Does not mean that we can write to the directory itself.
- write() 시스템콜을 이용해 디렉터리 엔트리 수정, 추가하는 것 불허
- We need some APIs that can deal with directory itself.
- execute
- File: file을 실행할 수 있는가?
- Directory: 이동할 수 있는가?(cd 가능?)
- 디렉토리에 write 권한과 execute 권한이 동시에 있어야 한다.
- 그 디렉토리에 파일을 생성할 수 있고
- 그 디렉토리의 파일을 삭제할 수 있다
- 삭제할 때는 그 파일에 대한 read권한은 없어도 됨 (시험)
Inode와 directory 관계
시험
- /home/obama/test.c
-
root directory의 i-노드를 찾아간다. -> 2 (In Linux)
- 2번 노드를 찾아가면 그것이 루트 디렉토리의 i-노드 이다.
- 파일에서는 0번이었는데 디렉토리에서는 2번 노드
- i-노드에는 UID, GID, Type, 권한, 블록 포인터 등이 들어있고 그 중에서 블록 포인터를 들어가 보면 그곳에 directory entry (file name + i-node)가 존재한다.
- directory data block에는 접근을 원하는 파일의 i-노드 정보가 담겨 있고 그 i-노드를 찾아 들어가 그 안의 블록포인터 정보를 보고 이러한 일련의 과정을 반복하여 원하는 위치의 data block에 접근하는 것이다.
위와 같은 일련의 과정을 반복하여 test.c 파일의 블록 포인터를 찾아갈 수 있게된다.
디렉터리 구현
- 디렉터리 내에는 무엇이 저장되어 있을까?
- 디렉터리 엔트리들(directory entry)
#include <dirent.h>
struct dirent {
ino_t d_ino; // i-노드 번호
char d_name[NAME_MAX + 1]; //파일 이름, null-terminated
}
Open directory
- opendir()
- 디렉터리 열기 위한 함수
- DIR 포인터(열린 디렉터리를 가리키는 포인터) 리턴
- 즉, DIR 구조체의 시작 주소를 리턴
- DIR: 열린 디렉토리 정보를 저장하는 구조체
- represents a directory stream(directory entry들이 나열된 것)
- Defined in
#include <sys/types.h>
#include <dirent.h>
DIR *opendir (const char *path);
//path 디렉터리를 열고 성공하면 DIR 구조체 포인터를, 실패하면 NULL을 리턴
Read directory
- readdir()
- 디렉터리 읽기 함수.
- 하나의 디렉터리 엔트리 정보 읽음, 읽기권한 필요
- On the first call, the first directory entry is read into dirent.
- On completion, the directory pointer is moved onto the next entry in the directory.
- If the end of the directory is reached, NULL is returned.
#include <sys/types.h>
#include <dirent.h>
struct dirent *readdir(DIR *dp);
//한 번에 디렉터리 엔트리를 하나씩 읽어서 리턴한다.
//디렉토리의 current file position은 읽을 때마다 읽은 디렉터리 엔트리 (구조체 dirent) 크기 만큼 이동한다
//Returns: pointer if OK, NULL at end of directory or error
parameter: directory stream의 주소
return: 특정 directory entry의 주소
Close directories
#include <dirent.h>
int closedir(DIR *dp);
// Returns: 0 if OK, -1 on error
- Open a directory / Close an open directory
- DIR: represents a directory stream
- Defined in
- Similar to FILE type in the standard I/O library.
- Defined in
- DIR: represents a directory stream
- closedir()
- 디렉토리 파일을 닫는다.
- 리턴 값: 성공하면 0, 실패하면 -1
Rewind directories
#include <dirent.h>
void rewinddir(DIR *dp);
- readdir() 시스템 콜로 directory entry를 읽다보면 directory stream을 가리키는 포인터가 계속 이동할 것인데 이 포인터를 다시 처음 위치로 되돌리고 싶을 때 사용하는 system call
- If you want to read from the beginning of directory, use rewinddir.
- 처음부터 다시 읽고 싶을 때
- 디렉토리 파일의 current file position을 처음 (first entry of the directory) 으로 옮긴다
Q) 구조체 포인터를 쓰는 이유
A) 포인터와 참조는 Call by Reference 용도로 많이 씁니다. 구조체도 별다를 것 없이 Call by Reference 로 값을 참조하는게 더 효율적인 경우, 혹은 필요한 경우에 포인터와 참조를 사용하는 것입니다.
즉, 구조체의 멤버 변수의 수정을 요할 때 사용한다.
Reading directories(example)
#include <stdio.h>
#include <dirent.h>
main (int argc, char ** argv)
{
char pathname[128]; //문자 배열 선언
if (argc == 1) { //실행 파일 이름만 주어진 경우, input argument가 없는 경우
strcpy(pathname, “.”); //대상 경로를 현재 디렉토리로 할 것이다.
}
else if (argc > 2) {
printf(“Too many parameter…\n”);
exit(1);
}
else { //argc = 2
strcpy(pathname, argv[1]); //인자를 pathname 변수에 저장
}
if (my_double_ls(pathname) == -1 )
printf("Could not open the directory\n");
}
int my_double_ls (const char *name) {
struct dirent *d;
DIR *dp; //디렉토리 스트림 포인터
if ((dp = opendir(name)) == NULL) //opendir() 시스템콜 호출
return (-1);
while (d = readdir(dp)) { //readdir의 포인터는 자동을 이동한다.
if (d->d_ino !=0)
printf ("%s\n", d->d_name);
}
rewinddir(dp); //첫 directory entry로 이동
while (d = readdir(dp)) {
if (d->d_ino != 0)
printf ("%s\n", d->d_name);
}
closedir(dp);
return (0);
}
- 실행
$ ls temp_dir/
abc bookmark fred
$ ./a.out temp_dir/
fred
abc
bookmark
..
. //rewind 이후
fred
abc
bookmark
..
.
$
출력에 나오는 ‘.’과 ‘..’은 각각 current directory와 parent directory를 의미한다.
Reading directories - list1.c
#include <sys/types.h> // primitive system data types
#include <sys/stat.h> // file status
#include <dirent.h> // directory entries
#include <stdio.h>
#include <stdlib.h>
/* 디렉터리 내의 파일 이름들을 리스트한다. */
int main(int argc, char **argv)
{
DIR *dp;
char *dir;
struct dirent *d;
struct stat st;
char path[BUFSIZ+1];
if (argc == 1)
dir = "."; // 인자가 없는 경우 현재 디렉터리를 대상으로
else dir = argv[1];
if ((dp = opendir(dir)) == NULL) // 디렉터리 열기
perror(dir);
while ((d = readdir(dp)) != NULL) // 각 디렉터리 엔트리에 대해
printf("%s \n", d->d_name); // 파일 이름 프린트
closedir(dp);
exit(0);
}
$ list1
.
..
file1
file2
파일 이름/크기 출력
- 디렉터리 내에 있는 파일 이름과 해당 파일의 크기(블록의 수)를 출력하도록 확장
while ((d = readdir(dp)) != NULL) { //디렉터리 내의 각 파일
sprintf(path, "%s/%s", dir, d->d_name); // 파일경로명 만들기 ;ex) home/etc
if (lstat(path, &st) < 0) // 파일 상태 정보 st에 가져오기
perror(path);
printf("%5d %s", st->st_blocks, d->name); // 블록 수, 파일 이름 출력
putchar('\n');
}
st_mode 구조체
- lstat() 시스템 호출
- 파일 타입과 사용권한 정보는 st->st_mode 필드에 함께 저장됨.
- st_mode 필드
- 4비트: 파일 타입
- 3비트: 특수용도
- 9비트: 사용권한
- 3비트: 파일 소유자의 사용권한
- 3비트: 그룹의 사용권한
- 3비트: 기타 사용자의 사용권한
디렉터리 리스트: 예
- list2.c
- ls –l 명령어처럼 파일의 모든 상태 정보를 프린트
- 프로그램 구성(함수 구성)
- main() : 메인 프로그램
- printStat() : 파일 상태 정보 프린트
- type() : 파일 타입 리턴
- perm() : 파일 사용권한 리턴
list2.c
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pwd.h> // password file
#include <grp.h> // group file (
#include <stdio.h>
#include <stdio.h>
char type(mode_t); // 함수 원형
char *perm(mode_t);
void printStat(char*, char*, struct stat*);
/* 디렉터리 내용을 자세히 리스트한다. */
int main(int argc, char **argv)
{
DIR *dp;
char *dir;
struct stat st;
struct dirent *d;
char path[BUFSIZ+1]; 103
if (argc == 1)
dir = "."; //current directory를 대상으로 지정
else dir = argv[1]; //인자값을 대상으로 지정
if ((dp = opendir(dir)) == NULL) // 디렉터리 열기
perror(dir);
//list1.c 로부터 수정된 부분!!!
while ((d = readdir(dp)) != NULL) { // 디렉터리의 각 파일에 대해
sprintf(path, "%s/%s", dir, d->d_name); // 파일경로명 만들기
if (lstat(path, &st) < 0) // 파일 상태 정보 가져오기
perror(path);
printStat(path, d->d_name, &st); // 상태 정보 출력
putchar('\n');
}
closedir(dp);
exit(0);
}
/* 파일 상태 정보를 출력 */
void printStat(char *pathname, char *file, struct stat *st) {
printf("%5d ", st->st_blocks);
printf("%c%s ", type(st->st_mode), perm(st->st_mode)); //type:d나 - 같은 , perm:rwx같은
printf("%3d ", st->st_nlink); //링크의 갯수
printf("%s %s ", getpwuid(st->st_uid)->pw_name, getgrgid(st->st_gid)->gr_name);
printf("%9d ", st->st_size); //바이트 단위의 크기
printf("%.12s ", ctime(&st->st_mtime)+4); //요일 정보를 빼기 위해 +4를 하였음
printf("%s", file);
}
/* 파일 타입을 리턴 by 매크로함수*/
char type(mode_t mode) {
if (S_ISREG(mode))
return('-');
if (S_ISDIR(mode))
return('d');
if (S_ISCHR(mode))
return('c');
if (S_ISBLK(mode))
return('b');
if (S_ISLNK(mode))
return('l');
if (S_ISFIFO(mode))
return('p');
if (S_ISSOCK(mode))
return('s');
}
/* 파일 사용권한을 리턴 */
char* perm(mode_t mode) {
int i;
static char perms[10] = "---------";
for (i=0; i < 3; i++) {
if (mode & (S_IRUSR >> i*3))
perms[i*3] = 'r'; //binary to character
if (mode & (S_IWUSR >> i*3))
perms[i*3+1] = 'w';
if (mode & (S_IXUSR >> i*3))
perms[i*3+2] = 'x';
}
return(perms);
}
char *perm(mode_t mode) 함수
-
소유자가 읽기 권한이 있는 경우
0000 000 100 000 000 -> 4+3+9
-
S_IRUSR 00400
0000 000 100 000 000
-
mode & (S_IRUSR » i*3)
- 0000 000 100 000 000 -> mode
- 0000 000 100 000 000 -> S_IRUSR
- 0000 000 001 000 000 -> S_IRUSR » i*3
그래서 서로 해당하는 mode끼리 and 연산을 통하면 해당 자리에는 1이 나오기 때문에 이를 이용한 것이다.
st_mode
- permission bits/sys/stat.h
#define S_IRUSR 00400 /* read permission: owner */
#define S_IWUSR 00200 /* write permission: owner */
#define S_IXUSR 00100 /* execute permission: owner */
#define S_IRGRP 00040 /* read permission: group */
#define S_IWGRP 00020 /* write permission: group */
#define S_IXGRP 00010 /* execute permission: group */
#define S_IROTH 00004 /* read permission: other */
#define S_IWOTH 00002 /* write permission: other */
#define S_IXOTH 00001 /* execute permission: other */
$list2
8 drw-r--r-- 2 chang faculty 4096 4월 16일 13:37 .
8 drw-r--r-- 2 chang faculty 4096 3월 26일 10:37 ..
2 -rw-r--r-- 2 chang faculty 2088 4월 16일 13:37 you.txt
2 -rw-r--r-- 2 chang faculty 2088 4월 16일 13:37 my.txt
getpwuid
get password user id
- 사용자 id와 일치하는 엔트리를 /etc/pswd 파일에서 찾아 그 엔트리에 대한 포인터를 반환
- 이 엔트리의 pw_name 필드가 사용자 이름을 가지고 있음
- uid는 integer 값
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
struct passwd{ /* pwd.h */
char *pw_name; /* Username. 결과적으로 이것을 가져오게 됨*/
char *pw_passwd; /* 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. */
};
getgrgid
- getgrgid: 그룹 id로 그룹 정보 제공
#include <grp.h>
struct group *getgrgid(gid_t gid);
ctime()
struct utimbuf {
time_t actime; /* access time */
time_t modtime; /* modification time */
}
- char *ctime(const time_t *timep);
- ctime()
- time_t 타입의 calendar time을 받아 “Wen Nov 18 18:53:20” 과 같은 형식의 문자열을 리턴
- calendar time은1970-1-1 00:00 부터 현재까지의 경과 시간을 초로 환산한 값
- 코드에서는 (요일+공백)을 생략하고 프린트하기위해 +4를 한 것임
- ctime(&st->st_mtime)+4);로 호출하였음
디렉터리 만들기
- mkdir() 시스템 호출
- path가 나타내는 새로운 디렉터리를 만든다.
- ”.”와 “..” 파일은 자동적으로 만들어진다.
- ”.”은 이 디렉토리 파일의 i-node를,
- ”..”은 부모 디렉토리 파일의 i-node를 가르킨다
- 디렉터리 쓰기권한이 있어도 write 함수로 쓸 수는 없다.
- mkdir, rmdir 를 사용해야 함(간접적으로)
#include <sys/types.h>
#include <sys/stat.h>
int mkdir (const char *path, mode_t mode); //위치, permission 정보
//새로운 디렉터리 만들기에 성공하면 0, 실패하면 -1을 리턴한다.
디렉터리 삭제
- rmdir() 시스템 호출
- path가 나타내는 디렉터리가 비어 있으면 삭제한다.
- An empty directory is one that contains entries only for dot and dot-dot.
- 디렉토리 파일의 link count를 감소 시킴
- link count가 0이 되면 삭제됨(0이 아니라는 것은 또 다른 entry가 존재한다는 것)
#include <unistd.h>
int rmdir (const char *path);
//디렉터리가 비어 있으면 삭제한다. 성공하면 0, 실패하면 -1을 리턴
chdir() and fchdir()
#include <unistd.h>
int chdir(const char *pathname);
int fchdir(int filedes);
// Both return: 0 if OK, -1 on error
- Change the current working directory. (like ‘cd’)
- Specify the new current working directory either as a pathname or file descriptor
- Example
#include "apue.h"
int main(void)
{
if (chdir("/tmp") < 0)
err_sys("chdir failed");
printf("chdir to /tmp succeeded\n");
exit(0);
}
$ gcc -o mycd chdir.c
- **실행**(이거 중요)
$ pwd
/usr/lib
$ mycd
chdir to /tmp succeeded
$ pwd
/usr/lib
$
- Current working directory of shell didn’t change after mycd.
- Each program is run in a separate process
- -> current working directory of shell is unaffected by chdir in mycd.
- mycd를 실행시키면 별도의 mycd의 프로세스로 실행되기 때문에 shell command의 process에는 영향을 끼치지 않는 것이다!!!
- 실행 파일을 실행시키면 shell command와는 별도의 프로세스가 실행되어 그 안에서 디렉토리를 바꾸는 것이기 때문에 실제 shell command 상에서는 바뀌지 않는다.
- Note that “cd” is a built-in shell command!
어찌 보면 당연한 건데 헷갈릴 수 있는 부분이다.
getcwd()
#include <unistd.h>
char *getcwd(char *buf, size_t size);
//Returns: buf if OK, NULL on error
- Obtain the current working directory.
- Copies an absolute pathname of the current working directory to the array pointed to by buf of length, size.
- size must be large enough to accommodate
- the absolute pathname + a terminating null byte(‘/0’)
- example
#include <stdio.h>
#include <unistd.h>
#define SIZE 200
void my_pwd (void);
int main()
{
my_pwd();
}
void my_pwd (void) {
char dirname[SIZE];
if (getcwd(dirname, SIZE) == NULL)
perror("getcwd error");
else
printf("%s\n", dirname);
}
- 실행
$ ./a.out
/home/obama/test
$
디렉터리 구현
- 그림 5.1의 파일 시스템 구조를 보자.
- 디렉터리를 위한 구조는 따로 없다.
- 파일 시스템 내에서 디렉터리를 어떻게 구현할 수 있을까?
- 디렉터리도 일종의 파일로 다른 파일처럼 구현된다.
- 디렉터리도 다른 파일처럼 하나의 i-노드로 표현된다.
- 디렉터리의 내용은 디렉터리 엔트리(파일이름, i-노드 번호)
2번에는 /(root directory)의 정보가 담겨져 있다.
그런 다음 4번 노드를 찾아가면 usr의 i-노드 정보가 있는데 블록포인터를 보고 202번 블락 번호의 내용으로 들어가 test.c의 i-노드 번호인 6번을 또 타고 들어가 204번 블럭을 찾아 들어간다.
directory entry는 file일 수도 directory일 수도 있다.
댓글남기기