NAME

path_resolution - 경로명으로 파일을 정하는 방식

DESCRIPTION

어떤 유닉스/리눅스 시스템 호출에는 매개변수로 파일명이 한 개나 그 이상 있다. 파일명(즉 경로명)을 다음과 같이 해석한다.

1단계: 해석 과정의 시작

경로명이 '/' 문자로 시작하면 호출 프로세스의 루트 디렉터리가 탐색 개시 디렉터리다. 프로세스는 부모에게서 루트 디렉터리를 물려받는다. 보통은 파일 계층 구조의 루트 디렉터리일 것이다. 하지만 프로세스에서 chroot(2) 시스템 호출을 사용해 다른 루트 디렉터리를 가질 수도 있으며, RESOLVE_IN_ROOT 플래그 설정한 openat2(2)를 사용해 임시로 다른 루트 디렉터리를 쓸 수도 있다.

(프로세스 자신이나 어느 선조가) CLONE_NEWNS 플래그 설정한 clone(2) 시스템 호출을 통해 시작된 경우 완전히 독립적인 마운트 네임스페이스를 가질 수도 있다. 이렇게 경로명의 '/' 부분을 처리한다.

경로명이 '/' 문자로 시작하지 않으면 프로세스의 현재 작업 디렉터리가 (openat2(2) 방식 시스템 호출에서는 dfd 인자가, 단 dfd 인자로 AT_FDCWD를 준 경우에는 현재 작업 디렉터리가) 해석 과정의 탐색 개시 디렉터리다. 현재 작업 디렉터리는 부모에게서 물려받으며 chdir(2) 시스템 호출로 바꿀 수 있다.

'/' 문자로 시작하는 경로명을 절대 경로명이라고 한다. '/'로 시작하지 않는 경로명을 상대 경로명이라고 한다.

2단계: 경로 따라가기

탐색 개시 디렉터리를 현재 탐색 디렉터리로 삼는다. 그러고서 경로명의 구성 항목('/' 문자로 구분된 부분 문자열)들 중에서 마지막 항목을 제외한 각각에 대해 현재 탐색 디렉터리에서 그 항목을 찾아 본다.

프로세스가 현재 탐색 디렉터리에 대한 탐색 권한을 가지고 있지 않으면 EACCES("권한 거부") 오류를 반환한다.

그 항목을 찾지 못했으면 ENOENT("그런 파일이나 디렉터리 없음") 오류를 반환한다.

그 항목을 찾았지만 디렉터리도 아니고 심볼릭 링크도 아니면 ENOTDIR("디렉터리 아님") 오류를 반환한다.

그 항목을 찾았고 디렉터리이면 그 디렉터리를 현재 탐색 디렉터리로 삼고서 다음 구성 항목으로 넘어간다.

그 항목을 찾았고 심볼릭 링크면 먼저 (현재 탐색 디렉터리를 탐색 개시 디렉터리로 해서) 그 심볼릭 링크를 해석한다. 오류 발생 시 그 오류를 반환한다. 결과가 디렉터리가 아니면 ENOTDIR 오류를 반환한다. 심볼릭 링크 해석이 성공해서 디렉터리를 반환하면 그 디렉터리를 현재 탐색 디렉터리로 삼고 다음 구성 항목으로 넘어간다. 참고로 그 해석 과정 중에 경로명 선두부('dirname') 구성 항목에 심볼릭 링크인 파일명이 있어서 디렉터리로 해석되면 (그리고 또 다시 그 디렉터리의 선두부 구성 항목에 심볼릭 링크가 있고 하면) 재귀가 수반될 수 있다. 커널에서의 스택 넘침을 방지하기 위해, 또 서비스 거부를 방지하기 위해서 재귀 깊이와 심볼릭 링크를 따라가는 횟수에 상한이 있다. 그 최대치를 초과하면 ELOOP("심볼릭 링크 단계 과다") 오류를 반환한다.

현재 리눅스 구현에서 경로명을 해석하는 동안 심볼릭 링크를 따라가는 횟수는 최대 40번이다. 재귀 깊이에 대한 제한이 커널 2.6.18 전에는 5였다. 리눅스 2.6.18부터 그 제한이 8로 올라갔다. 리눅스 4.2에서 경로명 결정 코드를 새로 작업해서 재귀를 쓰지 않게 만들었고, 그래서 경로명 전체적으로 최대 40번 해석하는 제한만 남아 있다.

openat2(2)를 쓰면서 RESOLVE_NO_SYMLINKS 플래그를 설정해서 이 단계의 심볼릭 링크 해석을 막을 수 있다.

3단계: 마지막 구성 항목 찾기

경로명의 마지막 구성 항목을 확인하는 건 다른 항목들과 마찬가지로 앞 단계 설명처럼 이뤄지되 두 가지 차이가 있다. 첫째로, 마지막 구성 항목은 디렉터리일 필요가 없다. (적어도 경로 해석 과정과 관련해선 그렇다. 개별 시스템 호출에서의 요구에 따라 디렉터리여야 할 수도 있고 아니어야 할 수도 있다.) 둘째로, 그 구성 항목을 찾지 못한 경우가 꼭 오류인 건 아니다. 그 항목을 만들려는 참일 수도 있기 때문이다. 마지막 구성 항목 처리 방식에 대한 세부 내용은 개별 시스템 호출의 매뉴얼 페이지에서 설명한다.

. 및 ..

관례상 각 디렉터리에는 "." 및 ".." 항목이 있으며 이는 각각 그 디렉터리 자체와 부모 디렉터리를 가리킨다.

경로 해석 과정에서는 물리적 파일 시스템에 그 항목들이 실제 있는지 여부와 관계없이 그 항목들이 관례적 의미를 가진다고 상정한다.

루트 너머로는 내려갈 수 없다. 즉 "/.."는 "/"와 같다.

마운트 지점

"mount dev path" 명령을 실행하고 나면 경로명 "path"가 장치 "dev"의 파일 시스템 계층 구조의 루트를 가리키게 되며 이전에 가리키던 건 더이상 가리키지 않는다.

마운트 된 파일 시스템 밖으로 빠져나갈 수 있다. "path/.."는 "path"의 부모 디렉터리, 즉 "dev"의 파일 시스템 계층 구조 밖을 가리킨다.

openat2(2)를 쓰면서 RESOLVE_NO_XDEV 플래그를 설정해서 마운트 지점 횡단을 막을 수 있다. (하지만 그러면 바인드 마운트 횡단도 제한된다.)

뒤에 붙은 슬래시

경로명이 '/'로 끝나면 바로 앞 구성 항목을 2번 단계에서처럼 해석한다. 즉 존재해야 하고 디렉터리로 정해져야 한다. 그렇지 않으면 뒤에 붙은 '/'를 무시한다. (다시 말해 뒤에 '/'가 붙은 경로명은 거기 '.'를 덧붙여서 얻은 경로명과 동등하다.)

마지막의 심볼릭 링크

경로명의 마지막 구성 항목이 심볼릭 링크인 경우에 그 심볼릭 링크를 가리키는 것인지 아니면 링크 경로를 해석해서 얻은 파일을 가리키는 것인지는 시스템 호출에 따라 다르다. 예를 들어 시스템 호출 lstat(2)은 심볼릭 링크에 대해 동작하는 반면 stat(2)은 심볼릭 링크가 가리키는 파일에 대해 동작한다.

길이 제한

경로명 길이에 최댓값이 있다. 경로명이 (또는 심볼릭 링크 해석 중 얻은 중간 경로명이) 너무 길면 ENAMETOOLONG("파일명 너무 긺") 오류를 반환한다.

빈 경로명

원래 유닉스에서 빈 경로명은 현재 디렉터리를 가리켰다. 요즘의 POSIX에서는 빈 경로명이 성공적으로 해석돼선 안 된다고 한다. 리눅스에서는 이 경우 ENOENT를 반환한다.

권한

파일의 권한 비트는 3비트 묶음 세 개로 이뤄져 있다. chmod(1)stat(2)을 참고하라. 호출 프로세스의 실효 사용자 ID가 파일의 소유자 ID와 같을 때 첫 번째 묶음을 쓴다. 파일의 그룹 ID가 호출 프로세스의 실효 그룹 ID와 같거나 호출 프로세스의 (setgroups(2)로 설정한) 추가 그룹 ID들 중 하나와 같을 때 두 번째 묶음을 쓴다. 어느 쪽에도 해당하지 않을 때 세 번째 그룹을 쓴다.

사용하는 세 비트 중에 첫 번째 비트가 읽기 권한을, 두 번째가 쓰기 권한을 결정하며, 마지막은 일반 파일이면 실행 권한을, 디렉터리면 탐색 권한을 결정한다.

리눅스에서는 권한 검사 시 실효 사용자 ID가 아니라 fsuid를 쓴다. 보통은 fsuid가 실효 사용자 ID와 같지만 setfsuid(2) 시스템 호출로 fsuid를 바꿀 수 있다.

(여기서 "fsuid"는 "파일 시스템 사용자 ID" 정도의 뜻이다. 프로세스가 같은 실효 사용자 ID를 가진 프로세스에게 시그널을 보낼 수 있었던 시절에 사용자 공간 NFS 서버 구현을 위해 이 개념이 필요했다. 지금은 구식이 되었다. 아무도 setfsuid(2)를 쓰지 말아야 한다.)

마찬가지로 리눅스에서는 실효 그룹 ID가 아니라 fsgid("파일 시스템 그룹 ID")를 쓴다. setfsgid(2) 참고.

권한 검사 우회: 수퍼유저와 역능

전통적인 유닉스 시스템에서 수퍼유저(root, 사용자 ID 0)는 전능하며 파일 접근 시 모든 권한 제약을 우회한다.

리눅스에서는 수퍼유저 특권이 여러 역능(capabilities(7) 참고)들로 나눠져 있다. 두 가지 역능이 파일 권한 검사와 관련있는데, CAP_DAC_OVERRIDECAP_DAC_READ_SEARCH이다. (프로세스의 fsuid가 0이면 이 역능들을 가진다.)

CAP_DAC_OVERRIDE 역능은 모든 권한 검사를 무시하되 파일의 세 실행 권한 비트 중 하나라도 설정돼 있을 때만 실행 권한을 부여한다.

CAP_DAC_READ_SEARCH 역능은 디렉터리에 대한 읽기 및 탐색 권한, 그리고 일반 파일에 대한 읽기 권한을 부여한다.

SEE ALSO

readlink(2), capabilities(7), credentials(7), symlink(7)


2020-04-11