🔔 학교 강의를 바탕으로 개인적인 공부를 위해 정리한 글입니다. 혹여나 틀린 부분이 있다면 지적해주시면 감사드리겠습니다.
모든 file에는 owner가 존재하며, 일반적으로는 file을 만든 user가 owner가 된다. user는 user-id
로 관리가 되며, /etc/passwd
에 해당 정보를 저장한다.
각 process 역시 owner가 존재하며, 일반적으로는 실행시킨 user의 uid
이다. ownership을 변경할 수 있는 것은 file의 소유자나 root만 가능하며, 여기서 root는 superuser
이며, uid = 0
이다.
user의 shell에서 실행한 process의 owner은 user shell의 uid를 물려받는다.
user는 최소한 하나의 group
에 속해야 하며, 이에 대한 정보는 /etc/group
에 존재한다. group-id
, gid
라고도 부르며, gid
와 uid
는 사용자가 시작한 process에 의해 상속된다.
앞에서 언급한 process를 실행한 uid는 ruid
라고 한다. euid
는 특정 작업을 수행하기 위해 프로세스의 권한을 평가하는 데 사용되는 uid
이며, 대부분의 경우 ruid
와 다른 점이 없다. euid
는 특수한 경우에는 program의 uid
를 따라가게 된다.
우리가 일반적으로 file을 접근할 때는 euid
를 기준으로 판단하며, 위 그림에서 오른쪽 경우와 같이 root만 접근 가능한 /etc/shadow (실제 password가 담겨있음)
을 접근할 경우, euid를 기준으로 판단하여, euid
가 0인 경우에 접근이 가능하다.
각 file에는 각 user별로 접근에 대한 권한이 설정되어 있으며, 크게 user
, group
, others
로 나뉘며, 각각 세부적으로 read
, write
, execute
로 나뉜다. (총 9bits로 이루어짐)
이는 ls -l
command로 각 파일에 대해 확인이 가능하다.
<sys/stat.h>
에는 각 권한 bit에 대한 symbol이 포함되어 있으며, 이는 아래와 같다.
존재하는 file을 열기 위해 open
이 사용되었을 경우, 접근 권한을 확인하여 모두 맞지 않으면 errno = EACCESS
를 return한다. kernel은 euid
와 gid
를 바탕으로 이를 판단한다.
kernel은 file에 접근 권한이 있는지를 판단할때, 우선 root
인지 확인하고, root인경우면 접근을 허가해준다.root
가 아닐 경우에는 euid
가 소유자와 동일한지 확인한 후, 동일하면 상위 3개 bit를 확인한다. 이에 대한 접근 권한이 없을 경우네는 gid
와 소유 그룹이 동일한지 판단하고, 동일하면 그 다음 3개 bit를 확인한다. 마지막으로 전부 해당되지 않을 경우, 가장 하위 bit 3개를 확인하고, 접근 권한이 존재하지 않을 경우, 접근이 거절된다.
file 실행과 관련된 권한에 대한 9bits 상위에 3bits이 더 존재한다.
S_ISUID
는 euid
가 파일 소유자가 되며, S_ISGID
는 egid
가 파일 그룹이 된다. S_ISUID
가 설정되지 않은 경우에는 ruid
가 파일 소유자가 된다.
usr/bin/passwd
에는 S_ISUID
가 설정되어있기 때문에 이를 통하여 /etc/shadow
에 접근하여 password를 수정할 수 있다.
무분별한 권한 부여 방지를 위하여 system 상에서 특정 권한을 막기 위한 역할을 하는 File creation mask
가 사용된다. 지정된 권한이 켜지는 것을 방지하며 아래와 같은 방식으로 사용된다.
#include <sys/stat.h>
mode_t umask(mode_t cmask);
umask
는 process에 대한 file 생성 mask를 설정하며, 기존의 mask를 return한다.
mode_t oldmask;
oldmask = umask(022);
mask를 원래대로 돌리기 위해서는 return받은 이전 mask 값을 저장해둘 필요가 있다.
이에 대한 예시 코드는 아래와 같다.
#include <fcntl.h>
#include <sys/stat.h>
int specialcreat (const char *pathname, mode_t mode) {
mode_t oldu;
int filedes:
if ( (oldu = umask(0)) == ―1) {
perror ("saving old mask");
return (-1);
}
if((filedes=open(pathname, O_WRONLY|O_CREAT|O_EXCL, mode))== -1)
perror ("opening file");
if (umask (oldu) == -1)
perror ("restoring old mask");
return filedes;
}
위 코드에서 umask(0)
는 모든 mask를 해제한다.
#include <unistd.h>
int access(const char *pathname, int amode);
access
는 ruid
와 rgid
를 바탕으로 접근 권한을 확인한다. 따라서 access 권한을 확인할 수는 있지만 실질적으로 access가 가능한지는 확실하지 않다.
argument
- const char *pathname : 파일 경로
- int amode
- R_OK : read 권한에 대해 확인
- W_OK : write 권한에 대해 확인
- X_OK : execute 권한에 대해 확인
- F_OK : 파일 존재 유무 확인
return
- 성공 시 0 return
- error 발생 시 -1 return
이에 대한 예시 코드는 아래와 같다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char *filename = "afile";
if (access (filename, R_OK) == -1) {
fprintf (stderr, "User cannot read file %s\n", filename);
exit (1);
}
printf ("%s readable, proceeding\n", filename);
... /* the rest of the program */
}
위와 같이 특정 파일에 대해 access
를 이용하여 접근 권한을 확인할 수 있다.
#include <unistd.h>
int chmod(const char *pathname, mode_t newmode);
chmod
는 특정 파일의 권한을 변경할 수 있다.
argument
- const char *pathname : 파일 경로
- mode_t newmode : 새로운 permission
return
- 성공 시 0 return
- error 발생 시 -1 return
if (chmod(pathname, 0644) == -1)
perror(“call to chmod failed”);
if (chmod(pathname, 04644) == -1)
perror(“call to chmod failed”);
위의 2개의 코드는 동일한 역할을 한다.
#include <unistd.h>
int chown(const char *pathname, uid_t owner_id, gid_t group_id);
argument
- const char *pathname : 파일 경로
- uid_t owner_id : 새로운 owner id
- gid_t group_id : 새로운 group id
return
- 성공 시 0 return
- error 발생 시 -1 return
잘못된 file 소유자 변경 시도에 대해서는 항상 error = EPENRM
이 return된다. 또한 set-user-id(S_ISUID)
와 set-group-id(S_ISGID)
권한은 보안상 이슈를 방지하고 의도치 않은 결과를 방지하기 위해 설정해도 자동으로 꺼지게 된다.
각각의 file들은 하나의 i-node
를 사용하며, 모든 파일은 i-node
로 관리된다. 0
과 1
은 사용되지 않으며, 0
은 i-node가 없음
을 의미하며, 1
은 불량 디스크 블록을 수집
하는데 사용된다. i-node 2
는 root directory
로 사용된다.
Hard Link는 file로의 직접적인 pointer이다. Link Count는 i-node를 가리키는 directory 항목의 수를 의미한다. link count
가 0이 되면, 해당 파일은 지워진다. 이러한 type의 link를 hard link
라 부른다.
symbolic link
는 file로의 간접적인 pointer다. symbolic link
로는 link count가 증가하지 않으며, symbolic link
에 대한 hard link
가 아래 그림과 같이 생성되며, 이 hard link
가 가리키는 것은 path가 담겨있는 block이다.
#include <unistd.h>
int link(const char *original_path, const char *new_path);
link
는 새로운 directory entry를 생성한 후, link count를 증가시킨다. system loop
를 방지하기 위하여 (ex) cp r
) superuser만 directory에 대해 link
사용이 가능하다.
argument
- cconst char *original_path : 기존 경로
- const char *new_path : 새로운 경로
-> 둘 다 동일한 file system에 존재하는 경로여야 한다.
return
- 성공 시 0 return
- error 발생 시 -1 return
#include <unistd.h>
int unlink(const char *pathname);
unlink
는 기존에 존재하는 directory entry를 제거한다. 제거와 같이 link count도 1 감소시키며, link count가 0인 경우에는 해당 file을 삭제한다.
argument
- const char *pathname : 삭제할 경로
return
- 성공 시 0 return
- error 발생 시 -1 return
위 2가지 system call에 대한 예제 코드는 아래와 같다.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
char *usage = "usage: move file1 file2\n";
int main (int argc, char **argv) {
if (argc != 3){
fprintf (stderr, usage); exit (1);
}
if ( link (argv[1], argv[2]) == -1) {
perror ("link failed");
return 1;
}
if ( unlink(argv[1]) == -1) {
perror ("unlink failed");
unlink (argv[2]);
return 2;
}
printf ("Succeeded\n");
return 0;
}
2번째 if문의 코드는 1번에 연결된 file을 2번으로 연결되도록 하고, 3번째 if문의 코드는 연결을 끊는다.
#include <stdio.h>
int remove(const char *pathname);
unlink
와 동일하나 system call은 아니며, C standard
에 존재한다. file의 경우만 동일하게 동작하며, directory의 경우는 다르게 동작한다.
argument
- const char *pathname : 삭제할 경로
return
- 성공 시 0 return
- error 발생 시 -1 return
#include <stdio.h>
int rename(const char *oldname, const char *newname);
rename
은 file이나 directory의 이름을 변경하는 역할을 한다. C standard
에 rename
역시 존재하나, remove
와 마찬가지로 directory에 대해서는 사용이 불가능하다.
argument
- const char *oldname : 기존 이름
- const char *newname : 변경할 새로운 이름
return
- 성공 시 0 return
- error 발생 시 -1 return
#include <unistd.h>
int symlink(const char *realname, const char *symname);
symlink
는 symbolic link
를 생성한다. symbolic link
를 open하면 path를 따라가 실질적은 real name을 열게 된다. 원본 파일이 제거되더라도 symbolic link
는 살아있지만, 이 경우에는 path를 따라가지 않으며 errno = EEXIST
와 함께 -1을 return한다. symname
자체의 data를 보기 위해서는 readlink
를 사용해야 한다.
argument
- const char *oldname : 기존 이름
- const char *newname : 설정할 symbolic link의 이름
return
- 성공 시 0 return
- error 발생 시 -1 return
id
- 본인의uid
,gid
와 속해있는 보조 그룹을 확인할 수 있음.
- 어떤 파일의 그룹에 대한 접근 권한을 확인할 때는 본인의gid
와 보조그룹을 모두 확인함
#include <unistd.h>
int getopt(int argc, char *const argv[], const char *optstring);
optstring
에 따라 argv
를 파싱해주는 함수이며, 호출 한 번에 한 option character
가 파싱되므로 파싱하고자 하는 개수만큼 반복적으로 호출해야한다.
return
- 성공 시 option character를 return
- 지정되지 않은 character이면 ?를 return
- argv의 끝에서 -1을 return
파싱하고자 하는 option character은 -
로 시작하며, optstring
에서 :
이 붙는다면, 해당 option은 추가 argument를 필요로 하는 것이며, 이는 getopt
반환 이후에 optarg
에 저장된다. 또한, getopt가 argv를 전부 파싱하고 나면 argv가 재배치되며, 파싱된 argv들이 앞으로 오고 파싱되지 않은 argv들이 뒤로 간다. 이에 대한 예시는 아래와 같다.
다음 번에 파싱할 argument index를 저장하며, 실행파일을 나타내는 argument는 파싱 대상에서 제외되므로 초기값은 1이다. getopt를 호출할 때마다 1씩 증가하며, (:
이 존재하여 추가 argument까지 같이 파싱하면 2 증가) getopt가 -1을 반환하면 optind는 -
로 시작하지 않는 argument의 처음을 가리킨다. 위의 예시에서 getopt가 끝나고 난 뒤의 optind는 4
의 값을 가진다.