Delete the Name, the File Lives On
한국어 원문으로 표시합니다.
목표
읽기에서 "이름과 실체는 따로 있다" 를 읽었습니다. 여기서는 그 분리가 실제로 어떤 동작을 만들어 내는지 손으로 확인합니다.
여덟 단계를 마치면 이런 질문에 답할 수 있게 됩니다. df 는 여유가 있다는데
왜 쓰기가 실패하는가, 로그를 지웠는데 왜 공간이 안 돌아오는가, 설정 파일을
어떻게 갈아 끼워야 읽는 쪽이 반쯤 쓰인 파일을 보지 않는가.
작업 디렉터리
mkdir -p /root/fs && cd /root/fs
모든 산출물은 여기에 만듭니다.
쓸 도구
stat -c '%i %h %d %n' 파일 # 아이노드, 링크 수, 장치 번호, 이름
ls -i # 아이노드 번호를 함께 보기
ls -l /proc/<PID>/fd # 그 프로세스가 열어 둔 파일들
stat 의 %i 가 실체의 신분증입니다. 두 이름의 %i 가 같으면 같은 파일입니다.
크기와 내용이 같은 것과는 다릅니다.
채점 방식
세 단계는 채점기가 여러분이 만든 스크립트를 직접 돌립니다.
hold.py 채점기가 만든 파일을 넘겨 주고, 이름을 뗀 뒤에도
읽어 내는지 봅니다
findleak.sh 채점기가 직접 유출을 만들어 놓고, 그것을 찾아내는지
봅니다. 유출이 없어진 뒤에도 계속 보고하면 실패입니다
swap.sh 채점기가 대상 파일을 열어 둔 채로 여러분의 교체를
시킵니다. 열어 둔 디스크립터로 새 내용이 보이면
대상에 직접 쓴 것이므로 실패입니다
말로 설명한 것은 채점하지 않습니다. 동작하는 것만 봅니다.
같은 파일인지 어떻게 아는가
orig.txt 를 만들고, 같은 실체를 가리키는 두 번째 이름 hard.txt 와 내용만 같은 사본 copy.txt 를 만드세요. 셋의 아이노드 번호를 확인해 identity.txt 에 적습니다.
하드 링크는 ln 원본 새이름, 사본은 cp 원본 새이름 입니다.
확인은 stat -c '%i %h %n' orig.txt hard.txt copy.txt.
내용이 같은 것과 같은 파일인 것은 다릅니다. diff 로는 셋 다 같아 보입니다. 아이노드 번호만이 실체가 하나인지 둘인지 말해 줍니다.
링크 수는 무엇을 세는가
같은 실체를 가리키는 세 번째 이름 third.txt 를 만드세요. orig.txt 의 링크 수가 어떻게 되는지, 그리고 그 값이 얼마가 되어야 블록이 회수되는지를 nlink.txt 에 적습니다.
stat -c %h orig.txt 가 링크 수입니다. 이름이 셋이면 3 입니다.
rm 은 파일을 지우는 명령이 아니라 이름을 떼는 명령입니다. 시스템 콜 이름이 unlink 인 이유입니다. 이름이 하나도 안 남고, 그 파일을 연 프로세스도 없을 때 비로소 블록이 회수됩니다.
지운 파일을 읽는다
hold.py 를 만드세요. 인자로 받은 파일을 먼저 열고, 그다음 이름을 뗀 뒤, 그 디스크립터로 내용을 읽어 출력합니다. 그 디스크립터가 아직 무엇을 붙잡고 있는지도 함께 출력하세요.
파이썬에서 이름 떼기는 os.unlink(경로) 입니다. 순서가 전부입니다 — 열고, 떼고, 읽습니다.
디스크립터가 무엇을 붙잡고 있는지는 os.readlink('/proc/self/fd/%d' % f.fileno()) 로 볼 수 있습니다. 이름이 떨어진 파일이면 커널이 뒤에 (deleted) 를 붙여 줍니다.
내용을 통째로 읽어 두고 지우는 것은 다릅니다. 그것은 그냥 복사이고, 여기서 보려는 것은 이름이 없어도 실체가 살아 있다는 사실입니다.
공간을 붙잡고 있는 범인을 찾는다
findleak.sh 를 만드세요. 지금 이 시스템에서 이름은 없어졌는데 아직 열려 있는 파일을 전부 찾아, 붙잡고 있는 프로세스와 경로를 출력합니다. 실행 권한을 주세요.
/proc/<PID>/fd 는 그 프로세스가 열어 둔 파일들을 심볼릭 링크로 보여 줍니다. 이름이 떨어진 것에는 뒤에 (deleted) 가 붙습니다.
ls -l /proc/[0-9]*/fd 2>/dev/null | grep deleted 에서 출발하세요. 권한이 없는 프로세스는 못 읽으므로 오류는 버려야 합니다.
결과를 박아 넣으면 안 됩니다. 채점기가 유출을 만들었다가 없앤 뒤에도 계속 보고하는지 확인합니다.
원본을 옮기면 무엇이 깨지는가
base.txt 를 만들고, 그것의 하드 링크 keep.txt 와 심볼릭 링크 soft.txt 를 만드세요. 그다음 base.txt 를 moved.txt 로 옮깁니다. 어느 쪽이 깨졌고 왜 그런지 links.txt 에 적으세요. (1~2단계에서 만든 파일은 그대로 둡니다.)
심볼릭 링크는 ln -s, 하드 링크는 -s 없이 ln 입니다.
옮긴 뒤 cat soft.txt 와 cat keep.txt 를 각각 해 보세요. ls -l soft.txt 로 그것이 무엇을 가리키고 있는지도 보세요.
하드 링크는 아이노드를, 심볼릭 링크는 경로 문자열을 담고 있습니다. 심볼릭 링크는 접근할 때마다 그 경로를 새로 따라갑니다.
설정 파일을 안전하게 갈아 끼운다
swap.sh <새내용파일> <대상파일> 을 만드세요. 대상을 읽고 있는 쪽이 반쯤 쓰인 파일을 절대 보지 않도록 갈아 끼웁니다. 실행 권한을 주고, 임시 파일을 남기지 마세요.
대상에 직접 쓰면 안 됩니다. 같은 디렉터리의 임시 파일에 새 내용을 쓰고, mv 로 이름을 갈아 끼웁니다.
같은 파일시스템 안에서 mv 는 디렉터리 항목 하나만 고치므로 원자적입니다. 읽는 쪽은 옛 실체나 새 실체 중 하나를 보고, 그 중간은 존재하지 않습니다.
채점기는 대상 파일을 열어 둔 채로 여러분의 스크립트를 돌립니다. 이름을 갈아 끼웠다면 그 디스크립터에는 옛 내용이 계속 보여야 합니다.
이름 갈아 끼우기가 멈추는 곳
작업 디렉터리와 /dev/shm 의 장치 번호를 확인하세요. 그 경계를 넘겨 이름을 갈아 끼우려 하면 어떤 오류가 나는지 직접 시켜 보고, 그때 mv 는 실제로 무엇을 하는지를 exdev.txt 에 적으세요.
장치 번호는 stat -c %d 경로 입니다. 값이 다르면 다른 파일시스템입니다.
시켜 보기는 python3 -c "import os; os.rename('a.txt', '/dev/shm/a.txt')". mv 는 이 오류를 만나면 조용히 복사하고 원본을 지우므로, 실패하는 모습을 보려면 rename 을 직접 불러야 합니다.
이것이 안전한 교체를 반드시 같은 파일시스템 안에서 해야 하는 이유입니다. 복사는 시간이 걸리고, 도중에 끊기면 반쯤 쓰인 파일이 남습니다.
df 가 거짓말처럼 보일 때
report.md 에 진단서를 쓰세요. df 는 여유가 있다는데 쓰기가 실패하는 두 가지 경우, 각각을 확인하는 명령, 공간을 되찾는 방법, 그리고 이 사고가 주로 어디서 나는지를 담습니다.
두 경우는 앞 단계에서 이미 다뤘습니다. 하나는 블록이 아니라 다른 것이 동난 경우이고(df -i), 다른 하나는 4단계에서 직접 찾아낸 그것입니다.
원인을 찾는 것으로 끝내면 사고는 계속됩니다. 공간을 지금 되찾는 방법과, 다시 안 나게 하는 방법을 나눠서 적으세요.
다음 사람이 읽고 그대로 따라 할 수 있는 문서를 목표로 합니다.