Symlink 재귀 - "재설정"이 되는 이유는 무엇입니까?

Symlink 재귀 - "재설정"이 되는 이유는 무엇입니까?

동일한 디렉토리를 가리키는 심볼릭 링크를 계속 따라갈 때 어떤 일이 발생하는지 확인하기 위해 작은 bash 스크립트를 작성했습니다. 나는 그것이 매우 긴 작업 디렉토리를 생성하거나 충돌을 일으킬 것으로 예상했습니다. 하지만 그 결과는 나를 놀라게 했다...

mkdir a
cd a

ln -s ./. a

for i in `seq 1 1000`
do
  cd a
  pwd
done

일부 출력은

${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a
${HOME}/a/a
${HOME}/a/a/a
${HOME}/a/a/a/a
${HOME}/a/a/a/a/a
${HOME}/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a

여기서 무슨 일이 일어나고 있는 걸까요?

답변1

Patrice는 문제의 원인을 식별합니다.그의 대답, 그러나 거기에서 어떻게 도달하는지, 왜 이것을 얻는지 궁금하다면 이야기가 길어집니다.

프로세스의 현재 작업 디렉터리는 복잡하지 않습니다. 이는 프로세스의 속성이며 상대 경로(프로세스가 수행한 시스템 호출)에서 시작하는 디렉터리 유형 파일에 대한 핸들입니다. 상대 경로를 확인할 때 커널은 현재 디렉터리의 전체 경로를 알 필요가 없으며 단순히 해당 디렉터리의 파일에서 디렉터리 항목을 읽어 상대 경로의 첫 번째 구성 요소( ..다른 파일과 마찬가지로)를 찾은 다음 거기 계속.

이제 사용자는 디렉터리 트리에서 이 디렉터리가 어디에 있는지 알고 싶을 때가 있습니다. 대부분의 Unices의 경우 디렉터리 트리는 루프가 없는 단일 트리입니다. 즉, /트리의 루트( )에서 특정 파일까지의 경로는 단 하나뿐입니다. 이 경로를 흔히 표준 경로라고 합니다.

현재 작업 디렉토리에 대한 경로를 얻으려면 프로세스가 해야 할 일은 위로 올라가는 것뿐입니다.아래에뿌리가 아래쪽에 있는 나무를 보고 싶다면) 나무를 뿌리로 되돌리고 도중에 있는 노드의 이름을 찾으세요.

예를 들어, 현재 디렉토리가 무엇인지 알아내려는 프로세스는 해당 디렉토리(현재 디렉토리의 항목과 같은 상대 경로 )를 /a/b/c열고 동일한 inode 번호를 가진 디렉토리 유형 파일을 찾고 일치하는 항목을 찾은 다음 그것을 열어 보세요. 그리고 당신이 그것을 찾을 때까지 계속됩니다 . 거기에는 모호함이 없습니다......c../../

이것이 C 함수가 하는 getwd()getcwd(), 또는 적어도 예전에는 했던 일입니다.

일부 시스템(예: 최신 Linux)에는 커널 공간에서 조회되는 현재 디렉터리의 정식 경로를 반환하는 시스템 호출이 있습니다. 모든 항목에 대한 읽기 액세스 권한이 없더라도 현재 디렉터리를 찾을 수 있습니다. 그 구성 요소) , 이것을 이라고 합니다 getcwd(). 최신 Linux에서는 의 readlink()를 통해 현재 디렉터리의 경로를 찾을 수도 있습니다 /proc/self/cwd.

이는 대부분의 언어와 초기 쉘이 현재 디렉토리에 대한 경로를 반환할 때 수행하는 작업입니다.

귀하의 경우에는 cd a원하는 만큼 여러 번 호출할 수 있습니다. 에 대한 심볼릭 링크이기 때문에 .현재 디렉토리는 변경되지 않으므로 모든 getcwd(), pwd -P, python -c 'import os; print os.getcwd()'perl -MPOSIX -le 'print getcwd'귀하에게 반환됩니다 ${HOME}.

이제 기호 링크가 이 모든 것을 복잡하게 만듭니다.

symlinks디렉토리 트리 내에서 점프할 수 있습니다. 에서 또는 가 심볼릭 링크 /a/b/c인 경우 정식 경로는 완전히 달라집니다. 특히, 의 항목이 반드시 는 아닙니다 ./a/a/b/a/b/c/a/b/c../a/b/c/a/b

Bourne 쉘에서 다음을 수행하면:

cd /a/b/c
cd ..

심지어:

cd /a/b/c/..

최종적으로 합격한다는 보장은 없습니다 /a/b.

좋다:

vi /a/b/c/../d

반드시 다음과 동일하지는 않습니다.

vi /a/b/d

ksh개념을 도입했다논리적 현재 작업 디렉터리이 문제를 어떻게든 해결해보세요. 사람들은 이에 익숙해졌고 POSIX는 결국 이 동작을 지정했습니다. 즉, 이제 대부분의 쉘도 이 동작을 수행합니다.

및 내장 명령 cd(pwd그리고 그들만을 위해서popd(이는 / 가 있는 쉘 에도 적용되지만 pushd)) 쉘은 현재 작업 디렉토리에 대한 자체 아이디어를 유지합니다. $PWD특수 변수 에 저장됩니다 .

이 작업을 수행할 때:

cd c/d

cor 가c/d 심볼릭 링크라도 $PWD포함 하면 끝에 /a/b추가되어 가 됩니다 . 이 작업을 수행할 때:c/d$PWD/a/b/c/d

cd ../e

하고 있는 것이 아니라 chdir("../e")하고 있습니다 chdir("/a/b/c/e").

그리고 이 pwd명령은 변수의 내용만 반환합니다 $PWD.

이는 대화형 셸에서 유용합니다. pwd현재 디렉터리에 대한 경로를 출력하여 거기에 도달하는 방법에 대한 정보를 제공하고 다른 명령이 아닌 ..인수로만 사용하는 한 cd놀랄 일이 적습니다 cd a; cd ... cd a/..일반적으로 원래 있던 곳으로 돌아갑니다.

이제 를 $PWD호출하기 전 이나 cd다음에 호출할 때 많은 일이 발생할 수 있습니다 . 현재 디렉터리는 절대 변경되지 않지만(삭제 가능하더라도 항상 동일한 인덱스 노드임) 디렉터리 트리의 해당 경로는 완전히 변경될 수 있습니다. 현재 디렉토리는 호출할 때마다 디렉토리 트리를 탐색하여 계산되므로 해당 정보는 항상 정확하지만 POSIX 쉘로 구현된 논리 디렉토리의 경우 정보가 최신이 아닐 수 있습니다. 따라서 일부 쉘 에서는 또는 .cdpwd$PWDgetcwd()$PWDcdpwd

이 특정 인스턴스에서는 다른 셸에서 다른 동작을 볼 수 있습니다.

어떤 사람들은 ksh93문제를 완전히 무시하기를 좋아하므로 문제를 호출한 후에도 잘못된 정보가 반환됩니다 cd(그리고 거기서 나타나는 동작을 볼 수 없습니다 bash).

어떤 사람들은 그것이 여전히 현재 디렉토리에 대한 경로 인지 확인하는 것을 좋아 bash하거나 zsh확인 하지만 그렇지 않습니다 .$PWDcdpwd

pwdpdksh는 합계를 확인합니다 cd(그러나 pwd업데이트는 확인하지 않음 $PWD).

ash(적어도 데비안에서는) 확인하지 않습니다. 이렇게 하면 cd a실제로 확인합니다 cd "$PWD/a". 따라서 현재 디렉터리가 변경되어 더 이상 현재 디렉터리를 가리키지 않으면 실제로 현재 디렉터리 내의 디렉터리 $PWD로 변경되지 않습니다. a디렉토리 중 하나입니다 $PWD(존재하지 않으면 오류를 반환합니다).

그것을 가지고 놀고 싶다면 다음과 같이 할 수 있습니다.

cd
mkdir -p a/b
cd a
pwd
mv ~/a ~/b 
pwd
echo "$PWD"
cd b
pwd; echo "$PWD"; pwd -P # (and notice the bug in ksh93)

다양한 껍질에.

bash귀하의 경우에는 a 이후에 사용하고 있으므로 여전히 현재 디렉토리 cd a를 가리키는지 bash확인하십시오 . $PWD이를 위해 stat()의 값을 호출하여 $PWDinode 번호를 확인하고 이를 의 값과 비교합니다 ..

그러나 $PWD조회 경로에 너무 많은 기호 링크가 포함되어 있으면 오류가 반환되므로 stat()쉘은 $PWD현재 디렉토리에 해당하는지 확인할 수 없으므로 이를 다시 계산 getcwd()하고 그에 따라 업데이트합니다 $PWD.

이제 Patrice의 대답을 명확히 하기 위해 경로를 찾을 때 발견되는 심볼릭 링크 수를 확인하는 것은 심볼릭 링크 주기를 방지하는 것입니다. 가장 간단한 루프를 사용할 수 있습니다

rm -f a b
ln -s a b
ln -s b a

이러한 보호 장치가 없으면 Windows에서 cd a/x시스템은 a연결된 위치를 찾고 b연결된 심볼릭 링크를 찾아야 하며 a이는 무한정 계속됩니다. 이를 방지하는 가장 쉬운 방법은 여러 개의 심볼릭 링크를 해결한 후 포기하는 것입니다.

이제 다시논리적 현재 작업 디렉터리그리고 왜 그것은 그다지 좋은 기능이 아닌가. cd다른 명령이 아닌 셸에서만 작동한다는 점을 인식하는 것이 중요합니다 .

예를 들어:

cd -- "$dir" &&  vi -- "$file"

항상 다음과 같지는 않습니다.

vi -- "$dir/$file"

cd -P이것이 바로 혼란을 피하기 위해 사람들이 항상 스크립트에서 사용하도록 권장하는 이유입니다( 단지 다른 언어가 아닌 쉘에 작성되었다는 이유로 소프트웨어가 다른 명령과 다르게 인수를 처리하는 것을 원하지 않습니다 ).../x

-P옵션은 비활성화되어 있습니다논리적 디렉터리이렇게 처리하면 cd -P -- "$var"실제로 chdir()콘텐츠가 호출됩니다(적어도 콘텐츠가 설정되지 않은 $var한 , 다음 경우 를 제외하고 (또는 일부 셸에서는 ...일 수도 있지만 ) 이는 또 다른 이야기입니다). 이후에는 정식 경로가 포함됩니다.$CDPATH$var--2+3cd -P$PWD

답변2

이는 서비스 거부를 방지하기 위해 Linux 커널 소스 코드에 하드 코딩된 제한으로 인해 발생합니다. 중첩된 심볼릭 링크의 수는 40개로 제한됩니다(다음에서 사용 가능).follow_link()기능inside fs/namei.c, nested_symlink()커널 소스 코드에 의해 호출됨).

심볼릭 링크를 지원하는 다른 커널의 경우 유사한 동작이 나타날 수 있습니다(40 이외의 제한도 있을 수 있음).

관련 정보