오늘 학교 수행평가로 파일의 접근 권한을 변경하는 C 프로그램을 작성하고 제출했는데 제대로 구현하지 못했다.
문제 내용
명령행 인자로 파일과 접근 권한을 입력받아 파일의 접근 권한을 변경하는 코드를 작성해라.
예: ./mychmod g+w data.txt
접근 권한을 8진수 숫자로 받는 게 아니라 문자 모드로 입력받는 게 조건이라 까다로웠다.
접근 권한 문자 모드로 변경
사실 문자 모드로도 변경할 수 있는지 몰랐는데;
우선 파일 만들면 기본으로 설정되는 0644 = 644는 차례대로 u, g, o의 권한을 가리키는 것이고 r = 4, w = 2, x = 1이기 때문에 이렇게 해석할 수 있다.
- 파일 소유자 u의 접근 권한은 rw- (4 + 2 = 6),
- 그룹 g의 접근 권한은 r-- (4),
- 나머지 o의 접근 권한은 r-- (4)
그런데 이렇게 숫자로 외워서 하는게 번거롭기 때문인지 문자 모드로도 변경할 수 있다.
g+x ug+wx o-x u+rwx
u | 파일 소유자 |
g | 소유자 그룹 |
o | 나머지 |
a | 모든 사용자 |
모든 사용자 |
표의 마지막에 있는 비어있는 컬럼은 실수는 아니고 접근 권한을 부여할 대상을 생략하면 모든 사용자로 인식된다. a와 똑같다고 생각하면 된다.
+ | 권한 부여 |
- | 권한 제거 |
= | 권한 설정 |
여기서 +와 =의 차이가 헷갈릴 수 있는데, +는 뒤에 따라오는 접근 권한 기호들을 기존 권한에 더해주는 것이고 =는 뒤에 따라오는 접근 권한 기호대로 그대로 설정해주는 것이다. 예를 들어 기존의 권한이 634(rw- -wx r--)일 때 g=rw라고 입력한다면 664(rw- rw- r--)가 되는 것이다. 만약 g+rw라고 입력했다면 674(rw- rwx r--)가 된다.
코드 플로우
- 기존의 접근 권한을 알아내는 함수를 호출한다: stat()
- 명령행 인자로 받은 문자 모드 문자열을 while문으로 하나씩 순회하며 해석한다.
- 해석한대로 접근 권한을 변경하는 함수에 인자 넣고 호출한다: chmod()
코드 내용
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
int main(int argc, char** argv) {
struct stat buf;
int mode = 0;
if(argc != 3) {
printf("Usage : mychmod perm file");
exit(1);
}
stat(argv[2], &buf);
printf("기존 접근 권한: %o\n", buf.st_mode);
char* cmd = argv[1];
int cmd_len = strlen(cmd);
char op;
int i;
for(i=cmd_len-1; i>=0; i--) {
switch(cmd[i]) {
case 'r': mode += 04; break;
case 'w': mode += 02; break;
case 'x': mode += 01; break;
}
if(cmd[i] == '+' || cmd[i] == '-' || cmd[i] == '=') {
op = cmd[i];
break;
}
}
printf("중간점검: mode: %o\n", mode);
int ans = 0;
int j;
for(j=0; j<i; j++) {
switch(cmd[j]) {
case 'u': ans += (mode * 0100); break;
case 'g': ans += (mode * 010); break;
case 'o': ans += mode; break;
case 'a': ans += ((mode * 0100) + (mode * 010) + mode); break;
}
}
switch(op) {
case '+': case '=': buf.st_mode |= ans; break;
case '-': buf.st_mode &= ~(ans); break;
}
printf("변경 후 접근 권한..이 되어야 할 것: %o\n", buf.st_mode);
chmod(argv[2], buf.st_mode);
return 0;
}
기존 접근 권한 알아내기: stat()
struct stat buf;
stat(argv[2], &buf);
printf("기존 접근 권한: %o\n", buf.st_mode); // 출력: 100644
- stat()
- int stat(const char *path, struct stat *buf);
- 첫 번째 인자로 받은 파일(argv[2])의 접근 권한을 알아낼 수 있다.
- 접근 권한 이외의 파일 정보를 stat 구조체 내부 변수를 통해 알아낼 수 있다. 여기선 생략.
- st_mode
- mode_t st_mode
- stat 구조체 내부의 변수로 접근 권한 정보를 가지고 있다.
- mode_t 타입은 unsigned int로 정의되어 있다.
우선 명령행 인자로 받은 텍스트 파일의 권한을 출력해보면 우리에게 친근하던 접근 권한의 숫자 형식인 세 자리의 8진수 숫자 644가 아니라 100644가 출력된 것을 알 수 있다. 그건 mode_t 타입이 9비트의 접근 권한(644) 외의 다른 정보들도 포함하고 있기 때문이다.
상위 4비트인 파일 종류는 사실 일반 파일이나 디렉토리 말고는 거의 다룰 일이 없을 듯 하다. 적어도 지금으로서는... 우선 일반 파일(normal file)은 디렉토리가 아닌 파일이라 생각하면 편하다. 텍스트 파일도 c 파일도 이미지도 일반 파일에 속한다. (신기한 건 이미지의 접근 권한을 출력해보면 100600이라 뜨는데 이미지를 쓰거나=리터칭하거나 실행(?)할 수 없기 때문인 듯.) 일반 파일은 16진수로는 8000, 8진수로는 100000이다. 디렉토리까진 외워도 괜찮을 듯한데, 디렉토리는 16진수로는 4000, 8진수로는 40000이다.
특수 접근 권한은 지금 이 글에서는 알 필요 없는 정보이긴 한데... 예를 들어 내가 파일의 소유자가 아니고 기타 사용자(o)일 때, 일시적으로 파일 소유자의 권한으로 파일을 실행할 때 사용된다. 리눅스에서 permission denied가 뜨면 명령어 앞에 sudo를 붙이면 잘 실행될 때가 많은데 이 경우도 특수 접근 권한이 있기 때문에 가능하다. 지금 내게는 파일을 실행할 수 있는 권한이 없지만 root의 권한을 빌려서 일시적으로 실행할 수 있게 하는 것이다.
나머지 9비트는 각각 소유자, 그룹, 기타 사용자의 권한이다. 위에서 설명했으니 생략한다.
결과적으로 100644는 [일반 파일 + 특수 권한 없음 + 소유자 읽기/쓰기 + 그룹 읽기 + 기타 읽기]로 해석할 수 있다.
입력받은 문자 모드를 8진수 숫자로 변환해서 계산하기
char* cmd = argv[1];
int cmd_len = strlen(cmd);
char op;
int i;
for(i=cmd_len-1; i>=0; i--) {
switch(cmd[i]) {
case 'r': mode += 04; break;
case 'w': mode += 02; break;
case 'x': mode += 01; break;
}
if(cmd[i] == '+' || cmd[i] == '-' || cmd[i] == '=') {
op = cmd[i];
break;
}
}
printf("중간점검: mode: %o\n", mode);
여기서부턴 알고리즘의 문제인 것 같아서 어떻게 설명해야될 지 헷갈리는데... 우선 ug+rwx 이런 식으로 다수의 권한 부여 대상을 정할 수 있는 게 걸림돌이었다. 그래서 문자열의 맨 뒤에서부터 순회를 시작해서 +, -, = 중 하나의 기호가 나올 때까지 반복문을 돌린다. 기호의 뒤에는 반드시 rwx가 나오기 때문에 그 부분만 신경쓰면 된다.
권한 부여 대상에 따라 mode 변수(권한)의 단위를 다르게 계산해줘야하는데 그 부분도 그냥... switch문으로 하면 된다. mode를 먼저 계산한 게 이것 때문이다.
참고로 8진수 숫자는 숫자의 앞에 0을 붙여야 한다. 16진수는 0x를 붙이면 된다는 건 알고 있었는데 8진수는 잘 안 다뤄봐서 몰랐다. 0을 안붙이면 10진수 기준으로 계산이 돼서 이상한 값이 나온다.
사용자에 따라 각각 권한에 단위 적용하기
int ans = 0;
int j;
for(j=0; j<i; j++) {
switch(cmd[j]) {
case 'u': ans += (mode * 0100); break;
case 'g': ans += (mode * 010); break;
case 'o': ans += mode; break;
case 'a': ans += ((mode * 0100) + (mode * 010) + mode); break;
}
}
세 자리 8진수에서 첫 번째는 u, 두 번째는 g, 세 번째는 o의 권한을 의미한다고 앞서 언급했다. 그렇기 때문에 단위를 다르게 해줘야 한다. 예를 들어 명령어로 들어온 문자 모드 문자열이 ug+rwx라고 가정하면, mode는 7이 되고, u는 7에 100을 곱한 700이어야 u의 rwx 권한을 의미할 수 있게 되지만 g는 7에 10을 곱한 70이어야 g의 rwx 권한을 의미할 수 있게 된다.
a(모든 사용자)일 경우에는 모든 경우를 계산해서 더해주면 된다.
사실
'리눅스' 카테고리의 다른 글
GCC 컴파일 에러 undefined reference to '함수' & 컴파일 과정 (0) | 2022.04.28 |
---|---|
[리눅스] 저수준 파일 입출력으로 파일 복사하는 프로그램 만들기 (0) | 2022.04.15 |