상세 컨텐츠

본문 제목

UNIX - directory, directory library

Computer Science/UNIX

by 2021. 10. 7. 15:05

본문

반응형

Directory

directory안에 있는 파일들의 목록을 directory entries라고 한다. 

파일 시스템의 system call은 directory에도 사용할 수는 있지만, open이나 create와 같은 system call은 사용할 수 없다. open으로 directory를 열려고 할 경우 errno로 EISDIR이라는 디렉토리라서 열 수 없다는 에러가 뜬다. directory는 또한 write로 내용을 update할 수 없고, kernel만이 directory에 write할 수 있다. 

directory 밑에는 file과 directory가 있고, directory entry는 inode number와 character field로 구성되어있다.

 

link and unlink

system call link와 unlink를 다시 보자.

link("abc", "xyz");

로 link를 실행하면, abc가 가리키는 파일을 xyz도 가리키게 되므로 "abc"와 "xyz"는 같은 i-node를 갖게 되고, i-node의 link count는 2가 된다. unlink의 경우 i-node의 link count가 0이 되는 경우 해당 i-node와 파일이름은 clear된다. 

Dot double-dot

‘.’ : current working directory
‘..’ : parent directory

'.'은 현재 디렉토리, '..'은 부모 디렉토리를 의미한다.

만약 디렉토리 A의 하위 디렉토리 B와 C가 존재한다고 하면, A의 '.' 디렉토리에 해당하는 i-node 즉 자신의 i-node와 B,C의 '..' 디렉토리 부모 디렉토리에 해당하는 i-node는 같다.

example

위와 같이 i-node block이 있고, directory block이 있는 data block이 있다고 하자. 

i-node 2549가 가리키는 directory block의 현재 디렉토리 '.'의 i-node는 2549이다. 그리고 부모 디렉토리의 i-node는 1267이다.

i-node 1267과 대응되는 부모 디렉토리의 내용을 보면 자식 디렉토리 중에 testdir이라는 i-node 2549에 대응되는 디렉토리가 있고 이것이 앞서 보았던 directory block에 해당한다.

 

아래 예시를 보자

user% cat > test1.txt
xxxxxxx ^D
user% mkdir tempdir
user%

cat file1 > file2의 경우 file1의 내용을 file2로 copy하는 역할을 하지만, 위와 같이 cat 다음이 비어있는 경우 keyboard로 부터 입력을 받아 test1.txt에 write한다.

Ctrl + D는 EOF을 하여 test1.txt로의 입력이 끝나게 된다.

 

directory block의 directory entry 내용중 현재 디렉토리 i-node가 1이고, 부모 디렉토리 i-node가 0인 상황에서, test1.txt라는 파일을 생성하면 i-node 2에 text1.txt이 link되고, 디렉토리 tempdir을 생성하여 i-node 3에 tempdir이 link되었다. 

tempdir의 directory block의 내용을 확인하면 현재 디렉토리의 i-node는 tempdir의 i-node인 3번이 link되었고, 부모 디렉토리는 앞선 directory block의 현재 디렉토리 i-node였던 1임을 알 수 있다.

Directory permissions

directory의 권한은 file의 권한과 약간 다르다.

directory에서의 read permission은 디렉토리 안의 file이나 directory의 목록을 볼 수 있는 권한을 의미하고, write permission은 새 파일을 생성하거나, 존재하는 파일을 지우는 등의 권한을 의미한다.

execute permission은 file에서 실행권한이었지만, directory에서는 해당 directory에 chdir system call을 실행할 수 있는 권한을 의미한다. chdir system call은 해당 디렉토리를 current working directory로 설정하는 역할을 하는 system call이다.

 

만약 file을 open하려고 한다면, 해당 파일이 존재하는 디렉토리와 그 상위 디렉토리 모두에 대한 execute permission이 있어야 한다. 

directory에 대해 read permission이 있다는 것은 해당 디렉토리의 파일목록을 볼 수 있다는 것이고, execute permission은 해당 디렉토리를 찾아갈 수 있는 권한 즉 탐색할 수 있는 권한이다.

 

디렉토리에 대해 S_ISVTX sticky bit가 설정되어있다면, 디렉토리의 파일을 remove / rename 하기 위해서 write permission 뿐 아니라 다음 중 최소 하나의 조건이 필요하다.

Owner of the file : 파일의 owner
Owner of the directory : 디렉토리의 owner
Superuser

sticky bit를 설정하면 permission bit 중 other의 execute bit 'x'가 't'로 표시된다.

추가로 set-user-id의 경우 user bit 'x'가 's'로, set-group-id의 경우 group bit 'x'가 'g'로 표시된다. 

 

directory를 이용한 프로그래밍에 대해서 알아보자.

dirent structure

#include <dirent.h>

struct dirent {
        ino_t d_ino;                /* i-node number */
        char  d_name[NAME_MAX + 1]; /* null-terminated filename */
      }

d_ino는 i-node값이 들어간다.

d_name은 파일이름이 들어가는 변수인데, +1의 의미는 array에서의 null character 자리를 의미한다. d_ino에 0이 들어간다는 것은 대응되는 파일이 없다 즉 empty의 의미이다. 

mkdir(2)

#include <sys/stat.h>

int mkdir(const char *pathname, mode_t mode);
// Returns: 0 if OK, -1 on error

command line의 mkdir와 같은 역할의 system call이다. 

mkdir로 디렉토리를 생성하면 '.'과 '..'은 default로 지정된다. 

file에 대해서는 보통 read와 write permission만을 지정해주지만, directory의 경우 접근하는데 execute permission이 필요하기 때문에, 적어도 하나의 execute bit 가 enabled 되어야 한다. 

 

예를 들어,

$ mkdir dir
$ cat > dir/file.txt
test test test ^D
$ chmod 666 dir

위처럼 dir/file.txt의 권한을 666 즉 모든 execute permission을 disable하면,

$ ls dir   //read entries
dir/file.txt
$ cat dir/file.txt
cat: dir/file.txt: 허가 거부됨

위와 같이 ls dir로 파일의 내용을 읽을 순 있지만, cat dir/file.txt로 디렉토리에 접근해서 file을 읽어오려고 하면, execute permission이 없으므로 접근할 수 없다. 

$ chmod 111 dir
$ ls dir  :허가 거부됨
$ cat dir/file.txt
test test test

반대로 execute permission이 있지만, read permission이 없는 경우, cat로 디렉토리에 접근해서 file을 읽을 수는 있지만, ls로 디렉토리의 내용을 읽을 수는 없다.

rmdir(2)

#include <unistd.h>

int rmdir(const char *pathname);
// Returns: 0 if OK, -1 on error

command line의 rmdir와 같은 역할의 system call이다.

rmdir로 비어있는 디렉토리만 delete할 수 있으며, '.'와 '..' 디렉토리만 있는 것은 empty directory로 간주한다. 

opendir(3)

#include <dirent.h>

DIR *opendir(const char *dirname);
// Returns: pointer if OK, NULL on error

opendir은 라이브러리이다. open system call의 경우 directory를 열 수 없기에 opendir이라는 라이브러리를 사용한다. dirname에 디렉토리 경로를 넣으면 DIR이라는 standard I/O library에서 정의된 디렉토리 포인터를 반환한다. 

디렉토리 open이 실패하면 null이 반환된다. 

closedir(3)

#include <dirent.h>

int closedir(DIR *dirptr);
// Returns: 0 if OK, -1 on error

마찬가지로 close도 라이브러리가 있다. opendir을 통해 얻은 DIR 디렉토리 포인터를 넣어 close시킨다. 

readdir(3)

#include <dirent.h>

struct dirent *readdir(DIR *dp);
// Returns: pointer if OK, NULL at end of directory or error

디렉토리 내용을 읽는 readdir 라이브러리는 DIR 디렉토리 포인터를 통해서 디렉토리의 내용을 읽고 dirent 구조체를 반환한다. read system call에서 file offset이 이동하는 것과 같이 readdir에서도 directory offset이 읽은만큼 이동한다. 

rewinddir(3)

#include <dirent.h>

void rewinddir(DIR *dp);
// Returns: 0 if OK, -1 on error

rewinddir은 readdir을 할 때 읽을 directory pointer를 처음위치로 이동시키는 역할을 한다. 

 

example

다음 예시코드를 보자

#include <dirent.h>

int my_double_ls (const char *name){
   struct dirent *d;
   DIR *dp;

   /* 디렉토리를 개방하고, 실패여부를 점검함 */
   if ((dp=opendir(name)) == NULL)
   return (-1);

   /* 디렉토리를 살피면서 루프, 이때 inode번호가 유효하면 디렉토리항을 프린트 */
   while (d = readdir(dp)){
      if (d->d_ino !=0)
         printf ("%s\n", d->d_name);
   }

   rewinddir(dp); /*이제 디렉토리의 시작으로 되돌아간다 ... */
 
   while (d = readdir(dp)){/* ... 그리고 디렉토리를 다시 프린트한다. */
      if (d->d_ino != 0)
         printf ("%s\n", d->d_name);
   }
  closedir (dp);
}

name을 통해 디렉토리 이름을 받고 opendir로 directory pointer를 받는다. 받은 directory pointer가 NULL이 아니라면 성공적으로 open한 것이고, readdir을 통해 directory pointer값을 읽는다. 읽은 dirent에서 d_ino 즉 i-node값을 확인하여 0이라면 i-node에 연결된 것이 없지만 0이 아니라면 연결된 디렉토리의 이름을 출력한다. 

 rewinddir을 통해 디렉토리의 시작으로 되돌아가서 readdir을 통해 다시 내용을 읽고 출력한다.

 

다음 예시 코드를 보자

#include <stdio.h> /* NULL을 정의 */
#include <dirent.h>
#include <string.h> /* 스트링 함수를 정의 */
int match(const char *, const char *);

char *find_entry(char *dirname, char *suffix, int cont){
   static DIR *dp=NULL;		struct dirent *d;

   if (dp == NULL || cont == 0){
      if (dp != NULL)
         closedir (dp);
      if ((dp = opendir (dirname)) == NULL)
         return (NULL);
   }

   while (d = readdir(dp)){
      if (d->d_ino == 0)
         continue;
      if (match (d->d_name, suffix))
         return (d->d_name);
   }
   closedir (dp);
   dp = NULL
   return (NULL);
}

int match (const char *s1, const char *s2)
{
   int diff = strlen(s1)- strlen(s2);

   if (strlen(s1) > strlen(s2))
      return (strcmp(&s1[diff], s2) == 0);
   else
      return (0);
}

위 코드는 dirname아래 파일에서 주어진 접미사(suffix)로 끝나는 첫 번째 파일을 찾는다.

$ ls *.txt

위와 같은 명령어로 .txt로 끝나는 파일들을 찾는 것과 같은 의미이다.

첫 번째 검색 findentry에 cont 매개변수가 0이면 처음부터 찾고, 1이면 이전에 찾았던 디렉토리의 다음 파일을 찾는다. 

 directory pointer를 저장하는 dp 변수가 static으로 선언되어있기 때문에 이전에 찾았던 디렉토리가 있다면 함수를 재호출 했을 때 해당 directory pointer가 저장되어있어 가능한 코드이다. 

if문을 통해 dp가 비어있는지 (이전에 호출했던 적이 있었는지), cont가 0인지 (처음부터 읽을 것인지)를 판단하고, while문에서 i-node가 0이 아닌 연결되어있고 다음으로 마주하는 directory 의 이름을 반환한다. 

 match 함수에서는 s1이 파일 이름이고, s2가 suffix로 s1문자열에 s2가 포함되는지를 확인한다. diff를 통해 글자수 차이를 구하고,  s1에서는 diff부터 시작하는 문자열과 s2 suffix 문자열이 같은지를 strcmp를 통해 확인한다.

반응형

관련글 더보기