Browse Source

Fix more inconsistent behaviour with symlinks in paths.

Both redo and minimal/do were doing slightly weird things with
symlinked directories, especially when combined with "..".  For
example, if x is a link to ., then x/x/x/x/../y should resolve to
"../y", which is quite non-obvious.

Added some tests to make sure this stays fixed.
pull/24/head
Avery Pennarun 2 years ago
parent
commit
686c381109
13 changed files with 121 additions and 8 deletions
  1. +1
    -0
      .gitignore
  2. +1
    -1
      clean.do
  3. +5
    -0
      do
  4. +34
    -2
      minimal/do
  5. +11
    -0
      minimal/do.test
  6. +19
    -3
      redo/state.py
  7. +2
    -0
      t/.gitignore
  8. +5
    -0
      t/105-sympath/.gitignore
  9. +31
    -0
      t/105-sympath/all.do
  10. +2
    -0
      t/105-sympath/clean.do
  11. +2
    -0
      t/105-sympath/default.dyn.do
  12. +2
    -2
      t/clean.do
  13. +6
    -0
      t/shelltest.od

+ 1
- 0
.gitignore View File

@ -3,6 +3,7 @@
/.do_built.dir
/minimal/.do_built
/minimal/.do_built.dir
/minimal/y
*~
*.tmp
*.did


+ 1
- 1
clean.do View File

@ -6,7 +6,7 @@ if [ -e .do_built ]; then
done <.do_built
fi
[ -z "$DO_BUILT" ] && rm -rf .do_built .do_built.dir
rm -rf minimal/.do_built minimal/.do_built.dir docs.out
rm -rf minimal/.do_built minimal/.do_built.dir minimal/y docs.out
redo t/clean docs/clean redo/clean
rm -f *~ .*~ */*~ */.*~ *.pyc install.wrapper
find . -name '*.tmp' -exec rm -f {} \;


+ 5
- 0
do View File

@ -66,6 +66,11 @@ case $target in
build && bin/redo $args "$target"
;;
test)
# Be intentionally confusing about paths, to try to
# detect bugs.
rm -f 't/symlink path'
ln -s .. 't/symlink path' || die 'failed to make test dir.'
cd 't/symlink path/t/symlink path'
# First test minimal/do
build
# Add ./redo to PATH so we launch with redo/sh as the shell


+ 34
- 2
minimal/do View File

@ -204,6 +204,38 @@ _normpath()
)
# Prints a "real" path, with all symlinks resolved where possible.
_realpath()
{
local path="$1" relto="$2" isabs= rest=
if _startswith "$path" "/"; then
isabs=1
else
path="${relto%/}/$path"
fi
(
for d in $(seq 100); do
#echo "Trying: $PWD--$path" >&2
if cd -P "$path" 2>/dev/null; then
# success
pwd=$(/bin/pwd)
#echo " chdir ok: $pwd--$rest" >&2
np=$(_normpath "${pwd%/}/$rest" "$relto")
if [ -n "$isabs" ]; then
echo "$np"
else
_relpath "$np" "$relto"
fi
break
fi
_dirsplit "${path%/}"
path=$_dirsplit_dir
rest="$_dirsplit_base/$rest"
done
)
}
# List the possible names for default*.do files in dir $1 matching the target
# pattern in $2. We stop searching when we find the first one that exists.
_find_dofiles_pwd()
@ -241,7 +273,7 @@ _find_dofiles()
[ -n "$dodir" ] && dodir=${dodir%/}/
#echo "_find_dofiles: '$dodir' '$dofile'" >&2
_find_dofiles_pwd "$dodir" "$dofile" && return 0
newdir=$(_normpath "${dodir}.." "$PWD")
newdir=$(_realpath "${dodir}.." "$PWD")
[ "$newdir" = "$dodir" ] && break
dodir=$newdir
done
@ -369,7 +401,7 @@ _redo()
i=$(_abspath "$i" "$startdir")
(
cd "$DO_STARTDIR" || return 99
i=$(_normpath "$(_relpath "$i" "$PWD")" "$PWD")
i=$(_realpath "$(_relpath "$i" "$PWD")" "$PWD")
_dirsplit "$i"
dir=$_dirsplit_dir base=$_dirsplit_base
_do "$dir" "$base"


+ 11
- 0
minimal/do.test View File

@ -130,6 +130,17 @@ check ".." _normpath ../ "$x"
check ".." _normpath .. "$x"
SECTION _realpath
rm -rf y
mkdir y
ln -s . y/x
check "/usr/__does_not/b" _realpath "/usr/__does_not/a/../b" "$x"
check "foo" _realpath "y/x/x/x/x/x/../foo" "$PWD"
check "$(/bin/pwd)/foo" _realpath "$PWD/y/x/x/x/x/x/../foo" "$PWD"
check "foo/blam" _realpath "y/x/x/x/x/x/../foo/spam/../blam" "$PWD"
check "$(/bin/pwd)/foo/blam" _realpath "$PWD/y/x/x/../foo/spam/../blam" "$PWD"
SECTION _find_dofile
check "test.do" _find_dofiles test
check "test.do" _find_dofile test


+ 19
- 3
redo/state.py View File

@ -151,14 +151,28 @@ def check_sane():
return not _insane
def _realdirpath(t):
"""Like realpath(), but don't follow symlinks for the last element.
redo needs this because targets can be symlinks themselves, and we want
to talk about the symlink, not what it points at. However, all the path
elements along the way could result in pathname aliases for a *particular*
target, so we want to resolve it to one unique name.
"""
dname, fname = os.path.split(t)
if dname:
dname = os.path.realpath(dname)
return os.path.join(dname, fname)
_cwd = None
def relpath(t, base):
"""Given a relative or absolute path t, express it relative to base."""
global _cwd
if not _cwd:
_cwd = os.getcwd()
t = os.path.normpath(os.path.join(_cwd, t))
base = os.path.normpath(base)
t = os.path.normpath(_realdirpath(os.path.join(_cwd, t)))
base = os.path.normpath(_realdirpath(base))
tparts = t.split('/')
bparts = base.split('/')
for tp, bp in zip(tparts, bparts):
@ -172,7 +186,9 @@ def relpath(t, base):
return join('/', tparts)
# Return a path for t, if cwd were the dirname of env.v.TARGET.
# Return a relative path for t that will work after we do
# chdir(dirname(env.v.TARGET)).
#
# This is tricky! STARTDIR+PWD is the directory for the *dofile*, when
# the dofile was started. However, inside the dofile, someone may have done
# a chdir to anywhere else. env.v.TARGET is relative to the dofile path, so


+ 2
- 0
t/.gitignore View File

@ -3,5 +3,7 @@
/shellfail
/shelltest.warned
/shelltest.failed
/shlink
/stress.log
/symlink path
/flush-cache

+ 5
- 0
t/105-sympath/.gitignore View File

@ -0,0 +1,5 @@
/*.dyn
/src
/x
/y

+ 31
- 0
t/105-sympath/all.do View File

@ -0,0 +1,31 @@
redo-ifchange ../flush-cache
rm -f src
: >src
for iter in 10 20; do
rm -rf y
rm -f x *.dyn static
mkdir y
: >y/static
ln -s . y/x
../flush-cache
(
cd y/x/x/x/x/x
IFS=$(printf '\n')
redo-ifchange static x/x/x/static $PWD/static \
$(/bin/pwd)/static /etc/passwd
redo-ifchange $PWD/../static 2>/dev/null && exit 35
redo-ifchange 1.dyn x/x/x/2.dyn $PWD/3.dyn \
$PWD/../4.dyn $(/bin/pwd)/5.dyn
)
[ -e y/1.dyn ] || exit $((iter + 1))
[ -e y/2.dyn ] || exit $((iter + 2))
[ -e y/3.dyn ] || exit $((iter + 3))
[ -e 4.dyn ] || exit $((iter + 4))
[ -e y/5.dyn ] || exit $((iter + 5))
# Second iteration won't work in minimal/do since it only ever
# builds things once.
. ../skip-if-minimal-do.sh
done

+ 2
- 0
t/105-sympath/clean.do View File

@ -0,0 +1,2 @@
rm -rf y
rm -f src x *.dyn *~ .*~

+ 2
- 0
t/105-sympath/default.dyn.do View File

@ -0,0 +1,2 @@
redo-ifchange src
echo dynamic >$3

+ 2
- 2
t/clean.do View File

@ -2,6 +2,6 @@
sed 's/\.do$//' |
xargs redo
rm -f broken shellfile shellfail shelltest.warned shelltest.failed \
*~ .*~ stress.log flush-cache
rm -f broken shellfile shellfail shelltest.warned shelltest.failed shlink \
*~ .*~ stress.log flush-cache 'symlink path'
rm -rf 'space home dir'

+ 6
- 0
t/shelltest.od View File

@ -474,6 +474,12 @@ set x y z
x=$(printf "a%-5sc" "b")
[ "$x" = "ab c" ] || warn 119
# Make sure cd supports -L and -P options properly
rm -f shlink
ln -s . shlink
(cd -L shlink/shlink/shlink/../shlink) || fail 120
(cd -P shlink/shlink/shlink/../shlink) && fail 121
[ -e shelltest.failed ] && exit 41
[ -e shelltest.warned ] && exit 42
exit 40

Loading…
Cancel
Save