리눅스 프로그래밍 - 4주차

Lellow_Mellow·2022년 10월 11일
1
post-thumbnail

🔔 학교 강의를 바탕으로 개인적인 공부를 위해 정리한 글입니다. 혹여나 틀린 부분이 있다면 지적해주시면 감사드리겠습니다.

3.1 Files in a Multi-user Environment

Users and Ownerships

Owner(uid)

모든 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를 물려받는다.

Group(gid)

user는 최소한 하나의 group에 속해야 하며, 이에 대한 정보는 /etc/group에 존재한다. group-id, gid라고도 부르며, giduid는 사용자가 시작한 process에 의해 상속된다.

Real user-id(ruid) and Effective user-id(euid)

앞에서 언급한 process를 실행한 uid는 ruid라고 한다. euid는 특정 작업을 수행하기 위해 프로세스의 권한을 평가하는 데 사용되는 uid이며, 대부분의 경우 ruid와 다른 점이 없다. euid는 특수한 경우에는 program의 uid를 따라가게 된다.

우리가 일반적으로 file을 접근할 때는 euid를 기준으로 판단하며, 위 그림에서 오른쪽 경우와 같이 root만 접근 가능한 /etc/shadow (실제 password가 담겨있음)을 접근할 경우, euid를 기준으로 판단하여, euid가 0인 경우에 접근이 가능하다.

Permissions and File Modes

각 file에는 각 user별로 접근에 대한 권한이 설정되어 있으며, 크게 user, group, others로 나뉘며, 각각 세부적으로 read, write, execute로 나뉜다. (총 9bits로 이루어짐)

이는 ls -l command로 각 파일에 대해 확인이 가능하다.

<sys/stat.h>에는 각 권한 bit에 대한 symbol이 포함되어 있으며, 이는 아래와 같다.

File permission : open

존재하는 file을 열기 위해 open이 사용되었을 경우, 접근 권한을 확인하여 모두 맞지 않으면 errno = EACCESS를 return한다. kernel은 euidgid를 바탕으로 이를 판단한다.

kernel은 file에 접근 권한이 있는지를 판단할때, 우선 root인지 확인하고, root인경우면 접근을 허가해준다.root가 아닐 경우에는 euid가 소유자와 동일한지 확인한 후, 동일하면 상위 3개 bit를 확인한다. 이에 대한 접근 권한이 없을 경우네는 gid와 소유 그룹이 동일한지 판단하고, 동일하면 그 다음 3개 bit를 확인한다. 마지막으로 전부 해당되지 않을 경우, 가장 하위 bit 3개를 확인하고, 접근 권한이 존재하지 않을 경우, 접근이 거절된다.

Extra Permission for Executable Files

file 실행과 관련된 권한에 대한 9bits 상위에 3bits이 더 존재한다.

S_ISUIDeuid가 파일 소유자가 되며, S_ISGIDegid가 파일 그룹이 된다. S_ISUID가 설정되지 않은 경우에는 ruid가 파일 소유자가 된다.

usr/bin/passwd에는 S_ISUID가 설정되어있기 때문에 이를 통하여 /etc/shadow에 접근하여 password를 수정할 수 있다.

The File Creation Mask

무분별한 권한 부여 방지를 위하여 system 상에서 특정 권한을 막기 위한 역할을 하는 File creation mask가 사용된다. 지정된 권한이 켜지는 것을 방지하며 아래와 같은 방식으로 사용된다.

System Call : umask

#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를 해제한다.

System Call : access

#include <unistd.h>
int access(const char *pathname, int amode);

accessruidrgid를 바탕으로 접근 권한을 확인한다. 따라서 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를 이용하여 접근 권한을 확인할 수 있다.

System Call : chmod

#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개의 코드는 동일한 역할을 한다.

System Call : chown

#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) 권한은 보안상 이슈를 방지하고 의도치 않은 결과를 방지하기 위해 설정해도 자동으로 꺼지게 된다.

3.2 File with Multiple Names

i-node

각각의 file들은 하나의 i-node를 사용하며, 모든 파일은 i-node로 관리된다. 01은 사용되지 않으며, 0i-node가 없음을 의미하며, 1불량 디스크 블록을 수집하는데 사용된다. i-node 2root 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문의 코드는 연결을 끊는다.

Function : remove

#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

System Call : rename

#include <stdio.h>
int rename(const char *oldname, const char *newname);

rename은 file이나 directory의 이름을 변경하는 역할을 한다. C standardrename 역시 존재하나, 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);

symlinksymbolic 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

해당 주차에 추가로 학습한 command

  • id
    - 본인의 uid, gid와 속해있는 보조 그룹을 확인할 수 있음.
    - 어떤 파일의 그룹에 대한 접근 권한을 확인할 때는 본인의 gid와 보조그룹을 모두 확인함

Function : getopt

#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들이 뒤로 간다. 이에 대한 예시는 아래와 같다.

optind

다음 번에 파싱할 argument index를 저장하며, 실행파일을 나타내는 argument는 파싱 대상에서 제외되므로 초기값은 1이다. getopt를 호출할 때마다 1씩 증가하며, (:이 존재하여 추가 argument까지 같이 파싱하면 2 증가) getopt가 -1을 반환하면 optind는 -로 시작하지 않는 argument의 처음을 가리킨다. 위의 예시에서 getopt가 끝나고 난 뒤의 optind는 4의 값을 가진다.

profile
festina lenta

0개의 댓글