6

Consider the following folder structure:

.
├── test1
│   ├── nested1
│   ├── testfile11
│   └── testfile12
└── test2
    ├── nested1 -> /path/to/dir/test1/nested1
    └── testfile21

test2/nested1 is a symlink to the directory test1/nested1. I would expect, if it were the cwd, .. would resolve to test2. However, I have noticed this inconsistency:

$ cd test2/nested1/
$ ls ..
nested1  testfile11  testfile12
$ cd ..
$ ls
nested1  testfile21

touch also behaves like ls, creating a file in test1.

Why does .. as an argument to cd refer to the parent of the symlink, while to (all?) others refers to the parent of the linked dir? Is there some simple way to force it to refer to paths relative to the symlink? I.e. the "opposite" of readlink?

# fictional command
ls $(linkpath ..)

EDIT: Using bash

Eric Haynes
  • 171
  • 6

3 Answers3

5

The commands cd and pwd have two operational modes.

  • -L logical mode: symlinks are not resolved

  • -P physical mode: symlinks are resolved before doing the operation

The important thing to know here is that cd .. does not call the syscall chdir("..") but rather shortens the $PWD variable of the shell and then chdirs to that absolute path.

If you are in physical mode, this is identical to calling chdir(".."), but when in logical mode, this is different.

The main problem here: POSIX decided to use the less safe logical mode as default.

If you call cd -P instead of just cd, then after a chdir() operation, the return value from getcwd() is put into the shell variable $PWD and a following cd .. will get you to the directory that is physically above the current directory.

So why is the POSIX default less secure?

If you crossed a symlink in POSIX default mode and do the following:

ls ../*.c
cd ..
rm *.c

you will probably remove different files than those that have been listed by the ls command before.

If you like the safer physical mode, set up the following aliases:

alias cd='cd -P'
alias pwd='pwd -P'

Since when using more than one option from -L and -P the last option wins, you still may be able to get the other behavior.

Historical background:

The Bourne Shell and ksh88 did get directory tracking code at the same time.

The Bourne Shell did get the safer physical behavior while ksh88 at the same time did get the less safe logical mode as default and the options -L and -P. It may be that ksh88 used the csh behavior as reference.

POSIX took the ksh88 behavior without discussing whether this is a good decision.

BTW: some shells are unable to keep track of $PWD values that are longer than PATH_MAX and drive you crazy when you chdir into a directory with an absolute path longer than PATH_MAX. dash is such a defective shell.

schily
  • 18,806
  • 5
  • 38
  • 60
  • Do you know when exactly that was added to Bourne/Korn? I can see it was in ksh88g, not in ksh88d (SVR4). Note `csh` didn't have anything like that. tcsh has something similar (not enabled by default) added in the 90s (`set symlinks = ignore` or `set symlinks = expand` which goes even further and is even more broken) – Stéphane Chazelas Aug 01 '18 at 09:31
  • In 1988, the `Bourne Shell` did have code to handle `$PWD` but not code to verify it's content using `lstat()`. This code was added on November 1989 – schily Aug 01 '18 at 10:05
  • Just verified: `csh` does not have similar code. I was confused by the fact that csh tracks the logical view of the location in a variable `cwd`. This is why my `bsh` had a variable `$CWD` since 1986. – schily Aug 01 '18 at 10:15
  • Regarding ksh88: The code for -L/-P has been in in Summer 1989. Do you have ksh88 source code from different times? – schily Aug 01 '18 at 10:26
  • I have ksh88d from the SVR4 source on archive.org, and the changelog on Sven Mascheck's site mentions pwd -P (presumably added in earlier versions) for ksh88g – Stéphane Chazelas Aug 01 '18 at 10:27
  • My impression is that these options for `cd` have been present in ksh88c already. – schily Aug 01 '18 at 10:58
  • sorry, my bad `ksh88d` from https://archive.org/download/ATTUNIXSystemVRelease4Version2/sysvr4.tar.bz2 did have them indeed, I didn't check properly earlier. – Stéphane Chazelas Aug 01 '18 at 11:15
  • To the last part of the question, is there a simple way to get the parent of the symlink without `cd` and `pwd`? Something equivalent to: `echo $(cd test2/nested1/; dirname \`pwd -L\`)` – Eric Haynes Aug 01 '18 at 22:47
4

An important paper to read is http://doc.cat-v.org/plan_9/4th_edition/papers/lexnames where Rob Pike talks about this issue.

You also need to specify your shell, for instance bash tracks it's idea of the current directory and if you say cd .. it just cuts off the last component and changes there, rather than actually issuing a chdir("..") system call.

Sorry on my phone at the moment, will edit later...

icarus
  • 17,420
  • 1
  • 37
  • 54
1

You might want to give -p a try, which will follow the physical directory structure without following symbolic links

http://linuxcommand.org/lc3_man_pages/cdh.html

Nur
  • 131
  • 4