| 1 | #!/bin/sh |
| 2 | |
| 3 | test_description='apply-one-time-script CGI helper is safe under concurrent requests' |
| 4 | |
| 5 | . ./test-lib.sh |
| 6 | |
| 7 | HELPER="$TEST_DIRECTORY/lib-httpd/apply-one-time-script.sh" |
| 8 | |
| 9 | test_expect_success PIPE 'concurrent requests: one rewritten, one passed through, neither empty' ' |
| 10 | mkdir workdir fakebin && |
| 11 | ENTERED="$PWD/entered" && |
| 12 | GATE="$PWD/gate" && |
| 13 | export ENTERED GATE && |
| 14 | mkfifo "$ENTERED" "$GATE" && |
| 15 | |
| 16 | # Stand in for git-http-backend. The modify role returns a response |
| 17 | # containing "packfile", which the one-time script rewrites. The |
| 18 | # passthrough role returns a response that is left untouched, but first |
| 19 | # announces that it has entered the helper and then blocks, so that it |
| 20 | # is still in flight when the modify role claims and removes the marker. |
| 21 | write_script fakebin/git-http-backend <<-\EOF && |
| 22 | printf "Status: 200 OK\r\n" |
| 23 | printf "Content-Type: application/x-git-result\r\n" |
| 24 | printf "\r\n" |
| 25 | if test "$ROLE" = modify |
| 26 | then |
| 27 | printf "packfile\n" |
| 28 | else |
| 29 | echo entered >"$ENTERED" |
| 30 | read -r released <"$GATE" |
| 31 | printf "refs\n" |
| 32 | fi |
| 33 | EOF |
| 34 | |
| 35 | # The transform that replace_packfile would install as one-time-script: |
| 36 | # rewrite responses that contain "packfile", leave the rest alone. |
| 37 | write_script workdir/one-time-script <<-\EOF && |
| 38 | if grep packfile "$1" >/dev/null |
| 39 | then |
| 40 | sed "/packfile/q" "$1" && |
| 41 | printf "REPLACED\n" |
| 42 | else |
| 43 | cat "$1" |
| 44 | fi |
| 45 | EOF |
| 46 | |
| 47 | GIT_EXEC_PATH="$PWD/fakebin" && |
| 48 | export GIT_EXEC_PATH && |
| 49 | |
| 50 | # Hold GATE open read-write on fd 9 for the duration, so releasing the |
| 51 | # passthrough request below cannot block even if that request has |
| 52 | # already exited (it keeps a reader on the FIFO). |
| 53 | exec 9<>"$GATE" && |
| 54 | |
| 55 | # Launch the passthrough request in the background. It enters the |
| 56 | # helper, signals us through ENTERED, then blocks on GATE inside the |
| 57 | # fake backend. The braces keep the && chain intact while backgrounding |
| 58 | # only the subshell, so "wait" can reap it by pid; kill it on any exit |
| 59 | # so a stray blocked child cannot hold the test output open and stall a |
| 60 | # reader such as prove. |
| 61 | { ( |
| 62 | cd workdir && |
| 63 | ROLE=passthrough sh "$HELPER" >../passthrough.out 2>../passthrough.err |
| 64 | ) & } && |
| 65 | passthrough_pid=$! && |
| 66 | test_when_finished "kill $passthrough_pid 2>/dev/null || :" && |
| 67 | |
| 68 | # Wait until the passthrough request is past the marker check. |
| 69 | read -r entered <"$ENTERED" && |
| 70 | |
| 71 | # Run the modifying request to completion while the passthrough request |
| 72 | # is still blocked. |
| 73 | ( |
| 74 | cd workdir && |
| 75 | ROLE=modify sh "$HELPER" >../modify.out 2>../modify.err |
| 76 | ) && |
| 77 | |
| 78 | # Release the passthrough request and let it finish. Ignore the helper |
| 79 | # exit status here so a broken helper is diagnosed by the assertions |
| 80 | # below rather than aborting the test. |
| 81 | echo released >&9 && |
| 82 | { wait "$passthrough_pid" || :; } && |
| 83 | |
| 84 | # Neither request may error out or produce an empty (HTTP 500) body, |
| 85 | # and each must have played its role: the modify request rewrote its |
| 86 | # response and the passthrough request came through untouched. |
| 87 | test_must_be_empty passthrough.err && |
| 88 | test_must_be_empty modify.err && |
| 89 | test_grep "Status: 200 OK" passthrough.out && |
| 90 | test_grep "Status: 200 OK" modify.out && |
| 91 | test_grep REPLACED modify.out && |
| 92 | test_grep ! REPLACED passthrough.out && |
| 93 | test_grep refs passthrough.out |
| 94 | ' |
| 95 | |
| 96 | test_done |