Skip to content

Commit

Permalink
Ensure correct operation with read-only target dirs and .do file dirs.
Browse files Browse the repository at this point in the history
Although I expect this is rather rare, some people may want to build in
a read-write subdir of a read-only tree.  Other than some confusing
error reporting, this works fine in redo after the recent changes to
temp file handling, but let's add a test to make sure it stays that
way.  The test found a bug in minimal/do, so let's fix that.

Reported-by: Jeff Stearns <[email protected]>
  • Loading branch information
apenwarr committed Dec 13, 2018
1 parent d95277d commit 39e0178
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 16 deletions.
7 changes: 4 additions & 3 deletions minimal/do
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ _dirsplit()
}

# Like /usr/bin/dirname, but avoids a fork and uses _dirsplit semantics.
dirname()
qdirname()
(
_dirsplit "$1"
dir=${_dirsplit_dir%/}
Expand Down Expand Up @@ -283,7 +283,7 @@ _run_dofile()
# done.
_do()
{
local dir="$1" target="$1$2" tmp="$1$2.redo.tmp"
local dir="$1" target="$1$2" tmp="$1$2.redo.tmp" tdir=
local dopath= dodir= dofile= ext=
if [ "$_cmd" = "redo" ] ||
( [ ! -e "$target" -o -d "$target" ] &&
Expand All @@ -309,7 +309,8 @@ _do()
target=$(_relpath "$target" "$PWD") || return 98
tmp=$(_relpath "$tmp" "$PWD") || return 97
base=${target%$ext}
[ ! -e "$DO_BUILT" ] || [ ! -d "$(dirname "$target")" ] ||
tdir=$(qdirname "$target")
[ ! -e "$DO_BUILT" ] || [ ! -w "$tdir/." ] ||
: >>"$target.did.tmp"
# $qtmp is a temporary file used to capture stdout.
# Since it might be accidentally deleted as a .do file
Expand Down
23 changes: 10 additions & 13 deletions redo/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,23 +374,22 @@ def _record_new_state(self, t, rv, argv):
# FIXME: race condition here between updating stamp/is_generated
# and actually renaming the files into place. There needs to
# be some kind of two-stage commit, I guess.
if st1.st_size > 0:
if st1.st_size > 0 and not st2:
# script wrote to stdout. Copy its contents to the tmpfile.
unlink(self.tmpname)
try:
newf = open(self.tmpname, 'w')
except OSError, e:
dnt = os.path.dirname(t)
except IOError, e:
dnt = os.path.dirname(os.path.abspath(t))
if not os.path.exists(dnt):
# This could happen, so report a simple error message
# that gives a hint for how to fix your .do script.
err('%s: target dir %r does not exist!\n' % (t, dnt))
else:
# I don't know why this would happen, so raise the
# full exception if it ever does.
err('%s: save stdout to %s: %s\n'
% (t, self.tmpname, e))
raise
# This could happen for, eg. a permissions error on
# the target directory.
err('%s: copy stdout: %s\n' % (t, e))
rv = 209
else:
self.outfile.seek(0)
while 1:
Expand All @@ -407,12 +406,10 @@ def _record_new_state(self, t, rv, argv):
# Atomically replace the target file
os.rename(self.tmpname, t)
except OSError, e:
# Nowadays self.tmpname is in the same directory as
# t, so there's no very good reason for this to
# fail. Thus, raise the full exception if it ever
# does.
# This could happen for, eg. a permissions error on
# the target directory.
err('%s: rename %s: %s\n' % (t, self.tmpname, e))
raise
rv = 209
else: # no output generated at all; that's ok
unlink(t)
sf = self.sf
Expand Down
1 change: 1 addition & 0 deletions t/204-readonly/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/rodir
25 changes: 25 additions & 0 deletions t/204-readonly/all.do
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[ -e rodir ] && chmod u+w rodir
[ -e rodir/rwdir ] && chmod u+w rodir/rwdir
rm -rf rodir
mkdir rodir rodir/rwdir

cd rodir
cat >default.ro1.do <<-EOF
chmod u+w "\$(dirname "\$1")"
echo 'redir' >\$3
EOF
cat >default.ro2.do <<-EOF
chmod u+w "\$(dirname "\$1")"
echo 'stdout'
EOF

# Check that:
# - redo works when the .do file is in a read-only directory.
# - redo works when the target is in a read-only directory that becomes
# writable only *after* launching the .do script. (For example, the .do
# might mount a new read-write filesystem in an otherwise read-only
# tree.)
chmod a-w . rwdir
redo rwdir/a.ro1
chmod a-w . rwdir
redo rwdir/a.ro2
4 changes: 4 additions & 0 deletions t/204-readonly/clean.do
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[ -e rodir ] && chmod u+w rodir
[ -e rodir/rwdir ] && chmod u+w rodir/rwdir
rm -rf rodir
rm -f *~ .*~

0 comments on commit 39e0178

Please sign in to comment.