test-76ee5803.sh (5271B)
1 #!/bin/sh 2 # 3 # Test for commit 76ee5803: "Take care of other shell escape issues" 4 # 5 # This commit fixes quoting of temp file paths (%s -> '%s') in shell 6 # commands used by batch_rename() and cpmv_rename(). The bug only 7 # manifests when the temp-file directory contains spaces. Since nnn 8 # prefers /tmp (which never has spaces), we use Linux user+mount 9 # namespaces (unshare -mr) to make /tmp inaccessible, forcing nnn's 10 # set_tmp_path() to fall back to $TMPDIR — which we point at a path 11 # that contains spaces. 12 # 13 # Requirements: Linux with user-namespace support, unshare(1) 14 # 15 # Strategy: 16 # 1. Create $TMPDIR with spaces; make /tmp inaccessible via namespace 17 # 2. Create test files and trigger batch rename (r -> c) 18 # 3. A mock EDITOR renames "aaa.txt" -> "zzz.txt" in the temp file 19 # 4. Verify the rename happened on the filesystem 20 # 21 # Expected: PASS with the fix, FAIL without (the unquoted paste/sed 22 # commands choke on the spaced temp path). 23 24 set -e 25 26 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 27 NNN_TEST="$SCRIPT_DIR/nnn_runner.sh" 28 29 # Locate nnn binary 30 NNN_BIN="${1:-./nnn}" 31 case "$NNN_BIN" in 32 /*) ;; 33 *) NNN_BIN="$(cd "$(dirname "$NNN_BIN")" && pwd)/$(basename "$NNN_BIN")" ;; 34 esac 35 36 [ -x "$NNN_BIN" ] || { echo "error: nnn not found at $NNN_BIN" >&2; exit 1; } 37 38 # ---------- check for namespace support ---------- 39 if ! unshare -mr true 2>/dev/null; then 40 printf "SKIP: user/mount namespaces not available\n" 41 exit 0 42 fi 43 44 # ---------- set up test environment ---------- 45 # All temp data lives under TESTROOT. We place it under $HOME rather 46 # than /tmp because /tmp will be masked inside the namespace. 47 TESTROOT="$(mktemp -d "${HOME}/nnn-shell-esc.XXXXXX")" 48 trap 'rm -rf "$TESTROOT"' EXIT 49 50 # TMPDIR for nnn — path deliberately contains a space 51 SPACE_TMP="$TESTROOT/tmp dir" 52 mkdir -p "$SPACE_TMP" 53 54 # NNN_SEL — also under a path with spaces 55 SELDIR="$TESTROOT/path with spaces" 56 mkdir -p "$SELDIR" 57 58 # Clean XDG_CONFIG_HOME so the .nmv plugin is NOT found, 59 # forcing nnn to use the built-in batch_rename() 60 FAKECFG="$TESTROOT/config" 61 mkdir -p "$FAKECFG/nnn/plugins" 62 63 # Working directory with test files 64 WORKDIR="$TESTROOT/workdir" 65 mkdir -p "$WORKDIR" 66 printf 'content_a\n' > "$WORKDIR/aaa.txt" 67 printf 'content_b\n' > "$WORKDIR/bbb.txt" 68 69 # Mock EDITOR: renames aaa.txt -> zzz.txt in the batch-rename temp file 70 MOCK_EDITOR="$TESTROOT/mock-editor.sh" 71 cat > "$MOCK_EDITOR" << 'EDEOF' 72 #!/bin/sh 73 sed -i 's/^aaa\.txt$/zzz.txt/' "$1" 74 EDEOF 75 chmod +x "$MOCK_EDITOR" 76 77 # nnn-test input file 78 INPUT_FILE="$TESTROOT/test-input.txt" 79 cat > "$INPUT_FILE" << 'INPUTEOF' 80 # Wait for nnn to render 81 @sleep 300 82 @assert aaa.txt 83 @assert bbb.txt 84 85 # Trigger batch rename: r key then c (current dir) 86 r 87 @sleep 300 88 c 89 @sleep 800 90 91 # After rename, nnn should refresh 92 @sleep 500 93 @mark 94 @snapshot after-rename 95 96 # Quit 97 q 98 INPUTEOF 99 100 # ---------- inner script that runs inside the namespace ---------- 101 # Two levels of namespace nesting: 102 # Outer (unshare -mr): gives root to mount a mode-000 tmpfs on /tmp 103 # Inner (unshare --user): drops back to nobody — /tmp is now blocked, 104 # but TESTROOT dirs (owned by the real user) map to nobody and remain 105 # writable. nnn's xdiraccess("/tmp") fails → falls back to $TMPDIR. 106 INNER="$TESTROOT/inner.sh" 107 cat > "$INNER" << INNEREOF 108 #!/bin/sh 109 set -e 110 111 # Make /tmp inaccessible so nnn falls back to \$TMPDIR 112 mount -t tmpfs -o size=1M,mode=000 tmpfs /tmp 113 114 # Run the actual test in a nested user namespace (drops to nobody) 115 exec unshare --user sh -c ' 116 export TMPDIR="$SPACE_TMP" 117 export NNN_SEL="$SELDIR/.selection" 118 export XDG_CONFIG_HOME="$FAKECFG" 119 export EDITOR="$MOCK_EDITOR" 120 export VISUAL="$MOCK_EDITOR" 121 122 OUT_DIR="$TESTROOT/output" 123 "$NNN_TEST" -v -e "$NNN_BIN" -d "$WORKDIR" -o "\$OUT_DIR" "$INPUT_FILE" 124 ' 125 INNEREOF 126 chmod +x "$INNER" 127 128 # ---------- run test ---------- 129 printf "Testing commit 76ee5803: shell escape with spaced TMPDIR...\n" 130 printf " NNN binary: %s\n" "$NNN_BIN" 131 printf " TMPDIR: %s\n" "$SPACE_TMP" 132 printf " WORKDIR: %s\n" "$WORKDIR" 133 134 unshare -mr "$INNER" 135 TEST_EXIT=$? 136 137 # ---------- verify filesystem ---------- 138 printf "\n--- Filesystem verification ---\n" 139 PASS=0 140 FAIL=0 141 142 if [ -f "$WORKDIR/zzz.txt" ]; then 143 printf " PASS: zzz.txt exists (renamed from aaa.txt)\n" 144 PASS=$((PASS + 1)) 145 else 146 printf " FAIL: zzz.txt not found (batch rename failed)\n" 147 FAIL=$((FAIL + 1)) 148 fi 149 150 if [ ! -f "$WORKDIR/aaa.txt" ]; then 151 printf " PASS: aaa.txt no longer exists\n" 152 PASS=$((PASS + 1)) 153 else 154 printf " FAIL: aaa.txt still exists (rename didn't happen)\n" 155 FAIL=$((FAIL + 1)) 156 fi 157 158 if [ -f "$WORKDIR/zzz.txt" ]; then 159 CONTENT="$(cat "$WORKDIR/zzz.txt")" 160 if [ "$CONTENT" = "content_a" ]; then 161 printf " PASS: zzz.txt has correct content\n" 162 PASS=$((PASS + 1)) 163 else 164 printf " FAIL: zzz.txt has wrong content: %s\n" "$CONTENT" 165 FAIL=$((FAIL + 1)) 166 fi 167 fi 168 169 if [ -f "$WORKDIR/bbb.txt" ]; then 170 printf " PASS: bbb.txt unchanged\n" 171 PASS=$((PASS + 1)) 172 else 173 printf " FAIL: bbb.txt missing\n" 174 FAIL=$((FAIL + 1)) 175 fi 176 177 printf "\n=== Shell escape test: %d passed, %d failed ===\n" "$PASS" "$FAIL" 178 179 if [ "$FAIL" -gt 0 ] || [ "$TEST_EXIT" -ne 0 ]; then 180 printf "Snapshots: %s/output\n" "$TESTROOT" 181 ls -la "$WORKDIR"/ 182 exit 2 183 fi 184 185 exit 0