빌드노트
umask 022인데 새 파일은 왜 755가 아니라 644일까
umask는 생성 요청 모드에서 권한 비트를 지운다. Linux에서 0666·0777 요청과 022·077 마스크를 비교해 일반 파일이 644가 되는 이유, 단순 뺄셈의 오류, 기존 파일과 부모 프로세스에 적용되지 않는 범위를 확인한다.
Linux에서 umask 022인데 새 일반 파일이 644로 만들어진다면, 먼저 프로그램이 요청한 생성 모드를 봐야 한다. umask는 요청한 권한에서 지정한 비트를 지운다. 파일을 0666으로 요청했다면 처음부터 실행 비트가 없으므로 022를 적용해도 0755가 되지 않는다.
기본 ACL이 없는 디렉터리에서 일반 권한은 요청 모드 & ~umask로 결정된다. &는 비트 AND, ~는 비트 반전이다. 읽기·쓰기·실행에 해당하는 하위 9비트로 제한하면 ~0022는 0755이고, 0666 & 0755 = 0644다. 마스크 자체가 부여할 권한값인 것은 아니다. Linux open(2)의 생성 모드 규칙 (새 탭에서 열림)
권한 숫자는 8진수다. 소유자·그룹·그 밖의 사용자 순서로, 각 자리의 읽기는 4, 쓰기는 2, 실행은 1이다. 0666은 세 범위 모두 읽기·쓰기를 요청하고, 0022는 그룹과 그 밖의 사용자의 쓰기를 제거한다. 일반 파일에는 흔히 0666, 디렉터리에는 0777을 요청하므로 같은 마스크라도 결과가 다르다. umask(2) (새 탭에서 열림), mkdir(2) (새 탭에서 열림)
내 셸에서 새 파일과 디렉터리를 비교한다
Bash와 GNU coreutils의 mktemp, mkdir, stat, rm, rmdir가 있는 Linux에서 아래 전체 입력을 실행한다. 기본 ACL이 없는 Linux 파일시스템의 /tmp를 전제로 한다. WSL이라면 Windows 공유 드라이브 대신 Linux 쪽 /tmp를 쓴다. 새 전용 폴더 안에서만 파일을 만들고, 끝에서 그 파일과 빈 디렉터리를 지운다.
(
set -e
lab=$(mktemp -d /tmp/umask-demo.XXXXXX)
cd -- "$lab"
umask 022
: > file-022
mkdir dir-022
umask 077
: > file-077
mkdir dir-077
stat -c '%a %n' file-022 dir-022 file-077 dir-077
test "$(pwd -P)" = "$lab"
rm -- file-022 file-077
rmdir -- dir-022 dir-077 "$lab"
)
(...)는 자식 셸에서 실행하므로 바깥 셸의 마스크와 작업 디렉터리는 유지된다. : > 파일은 여기서는 새 빈 파일을 만든다. 이미 있는 파일에 쓰면 내용을 비우므로 기존 파일로 실험하지 않는다. Bash 매뉴얼의 자식 셸과 출력 리다이렉션 (새 탭에서 열림)
WSL2 Ubuntu 24.04.4 LTS, Linux 6.18.40.1, Bash 5.2.21, GNU coreutils 9.4에서 이 입력을 실행한 결과다. 실습 위치는 ext4였으며 기본 ACL이 없음을 별도로 확인했다.
644 file-022
755 dir-022
600 file-077
700 dir-077
이 셸의 새 파일은 022에서 644, 077에서 600이었다. 기본 옵션의 mkdir로 만든 디렉터리는 각각 755, 700이었다. GNU mkdir(1)의 기본 모드 설명 (새 탭에서 열림) 이처럼 예상대로 나오면, 일반 파일에 실행 비트가 생기지 않은 것을 마스크 설정 실패로 볼 이유가 없다.
오류가 나면 네 줄 비교와 정리가 모두 끝났다고 판단하지 않는다. /tmp의 쓰기 권한과 필요한 명령을 확인한다. 모드가 다르다면 기본 ACL과 파일시스템 조건부터 확인한다. 자기 프로그램에서만 다르다면 생성 API의 실제 요청 모드, 그 프로세스의 마스크, 생성 뒤 권한을 다시 바꾸는 코드 순서로 본다. Bash에서 인자 없는 umask는 현재 셸의 값을 출력한다. 서비스가 별도 프로세스라면 그 값까지 같다고 가정하지 않는다.
파일 종류보다 실제 요청값을 확인한다
Python 3.12.3의 별도 실험에서는 일반 파일의 os.open()과 디렉터리의 os.mkdir()에 요청 모드를 직접 지정했다. 기본 ACL 없는 ext4의 새 임시 폴더에서 자식만 마스크를 바꾸고, GNU stat으로 생성 직후 모드를 읽었다. 앞의 0은 8진수임을 드러내려고 붙였다.
umask 요청 모드 일반 파일 디렉터리
022 0666 0644 0644
022 0777 0755 0755
077 0666 0600 0600
077 0777 0700 0700
일반 파일도 0777로 요청하면 022 아래에서 0755가 됐다. 반대로 디렉터리도 0666으로 요청하면 0644였다. 파일 종류가 실행·탐색 비트를 자동으로 보충하거나 제거한 결과가 아니다. 이 실험은 초기 모드를 비교한 것이며, 그 디렉터리를 통해 파일에 접근할 수 있는지는 검사하지 않았다.
Python의 os.open()은 모드를 생략하면 0o777을 사용한다. 일반 파일을 만든다는 이유만으로 0o666이 기본값이라고 생각하면 위 셸과 다른 결과를 예상하게 된다. 이 비교에서는 파일 요청값을 직접 지정했다. Python의 0o는 8진수 표기다. Python 3.12 os.open 문서 (새 탭에서 열림)
단순 뺄셈으로 생각해도 0666 - 0022는 우연히 같은 값이 나온다. 하지만 마스크 0111에서 0666을 요청한 추가 실험은 파일과 디렉터리 모두 0666이었다. 요청에 없는 비트를 지우려 해도 요청값은 변하지 않는다. 8진수 뺄셈의 결과인 0555와는 다르다.
기존 파일과 부모 프로세스는 그대로였다
전체 조합 실험의 부모 마스크는 처음과 끝 모두 0022였고, 자식들은 이를 상속한 다음 자기 값만 바꿨다. 마스크를 바꾸지 않고 읽으려고 /proc/self/status의 Umask 필드를 사용했다. 이 필드는 Linux 4.7부터 제공된다. 별도로 실행한 위 Bash 입력에서도 바깥 셸의 마스크는 전후 0022였다. umask(2)의 상속과 조회 설명 (새 탭에서 열림)
부모가 먼저 만든 0644 파일도 각 자식에서 다시 열었다. 마스크를 바꾼 뒤 O_CREAT와 요청 모드 0777로 열어도 기존 파일은 계속 0644였다. 생성 모드는 새 파일을 만들 때 적용된다. 기존 파일의 권한을 바꾸려면 해당 파일에 필요한 변경을 따로 판단해야 한다. open(2)의 O_CREAT와 mode 설명 (새 탭에서 열림)
기본 ACL은 부모 디렉터리에서 새 항목으로 상속하는 접근 규칙이다. 이것이 있으면 umask 대신 상속 ACL과 요청 모드를 바탕으로 권한이 정해진다. 위 계산이 맞지 않는다고 마스크부터 더 바꾸기 전에 생성 위치를 확인해야 한다. umask(2)의 기본 ACL 설명 (새 탭에서 열림) 이번 실습은 그 조건을 제외했으며 기본 ACL이 있는 생성, 특수 모드 비트, 네트워크 파일시스템과 Windows 공유 드라이브에서의 생성은 검증하지 않았다.