OILS / demo / noclobber-race.sh View on Github | oils.pub

128 lines, 50 significant
1#!bin/osh
2#
3# Usage:
4# demo/noclobber-race.sh setup-adversary-fast
5# demo/noclobber-race.sh demo bin/osh
6# demo/noclobber-race.sh demo _bin/cxx-opt/osh # Will take longer, but will eventually get a race
7# # Always under ~150 times on my machine.
8# demo/noclobber-race.sh demo bash # Should never exit having found a race
9
10## Create a special file _tmp/special so that echo a >_tmp/special is VALID under noclobber
11create-special() {
12 #echo normal
13 ln -sf /dev/null _tmp/special
14 #echo special
15}
16
17## Create a normal file _tmp/special so that echo a >_tmp/special is INVALID under noclobber
18create-normal() {
19 #echo special
20 ln -sf $PWD/_tmp/protectme _tmp/special
21 #echo normal
22}
23
24## Try to write to _tmp/special without clobbering
25noclobber-process() {
26 set -C # enable noclobber
27 echo howdy > _tmp/special
28}
29
30## Try to break the noclobber
31adversary() {
32 # Cycle _tmp/special between a symlink to /dev/null and a normal file really fast
33 #for _ in {0..1000}; do
34 while true; do
35 create-special
36 #sleep 0.1
37 create-normal
38 #sleep 0.1
39 done
40
41 echo adversary dead
42}
43
44## Create a faster version of the adversary function, but in C
45setup-adversary-fast() {
46 cat >_tmp/adversary.c <<EOF
47#include <unistd.h>
48#include <stdio.h>
49#include <stdlib.h>
50
51#define CHECK(x) \
52 do { \
53 if (x) exit(1); \
54 } while (0)
55
56int main(int argc, const char** argv) {
57 if (argc != 3) {
58 fprintf(stderr, "Usage: %s <path-special> <path-protectme>\n", argv[0]);
59 return 2;
60 }
61
62 const char* pathSpecial = argv[1];
63 const char* pathProtectMe = argv[2];
64
65 while (1) {
66 // Note: we symlink into a temporary and then rename as otherwise
67 // symlink(2) will fail with EEXIST.
68
69 // create-special
70 CHECK(symlink("/dev/null", "intermediate"));
71 CHECK(rename("intermediate", pathSpecial));
72
73 // create-normal
74 CHECK(symlink(pathProtectMe, "intermediate"));
75 CHECK(rename("intermediate", pathSpecial));
76 }
77
78 return 0;
79}
80
81EOF
82
83 cc _tmp/adversary.c -o _tmp/adversary
84}
85
86## Sometimes ^C will stop the _temp/adversary program in a state where we have
87## leftover files. This removes them.
88cleanup() {
89 rm -f intermediate
90}
91
92## Run the faster version of `adversary`
93adversary-fast() {
94 cleanup
95 _tmp/adversary _tmp/special "$PWD/_tmp/protectme"
96}
97
98## The actual demo
99demo() {
100 if [[ $# != 1 ]]; then
101 echo "Usage: $0 demo <shell>" >&2
102 return 2
103 fi
104 local SH=$1
105
106 echo secret > _tmp/protectme
107
108 #adversary & -- Too slow!
109 adversary-fast &
110 local adversary_pid=$!
111
112 tries=0
113 while diff <(echo secret) _tmp/protectme; do
114 printf .
115 tries=$(($tries + 1))
116 $SH "$0" noclobber-process 2>/dev/null
117 done
118 echo
119 echo 'got it!'
120
121 kill -9 "$adversary_pid"
122 wait
123 cleanup
124
125 echo "Took $tries tries"
126}
127
128"$@"