File: close-on-exec.sh

package info (click to toggle)
shepherd 1.0.9-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,828 kB
  • sloc: lisp: 8,779; sh: 3,586; makefile: 290; ansic: 50
file content (197 lines) | stat: -rw-r--r-- 5,668 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# GNU Shepherd --- Ensure file descriptors are not leaked to children.
# Copyright © 2022-2025 Ludovic Courtès <ludo@gnu.org>
#
# This file is part of the GNU Shepherd.
#
# The GNU Shepherd is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at
# your option) any later version.
#
# The GNU Shepherd is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with the GNU Shepherd.  If not, see <https://www.gnu.org/licenses/>.

shepherd --version
herd --version

socket="t-socket-$$"
conf="t-conf-$$"
log="t-log-$$"
pid="t-pid-$$"
c_file="t-count-file-descriptors-$$.c"
exe="$PWD/t-count-file-descriptors-$$"
fd_count="$PWD/t-fd-count-$$"

herd="herd -s $socket"

trap "cat $log || true; rm -f $socket $conf $log $fd_count $c_file $exe;
      test -f $pid && kill \`cat $pid\` || true; rm -f $pid" EXIT

# GNU/Hurd lacks /proc/self/fd so far.
[ -d /proc/self/fd ] || exit 77

cat > "$c_file" <<EOF
/* This program counts its own open file descriptors and writes
   that number to $fd_count.  It's more reliable than using the
   shell or Guile since those may open additional file descriptors.  */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <sys/socket.h>
#include <assert.h>

int
main (int argc, char *argv[])
{
  DIR *dir;
  struct dirent *ent;
  size_t count;
  FILE *log;

  if (getenv ("LISTEN_FDS") != NULL)  /* systemd */
    {
      struct sockaddr_storage address;
      socklen_t len = sizeof address;
      int fd = accept (3, (struct sockaddr *) &address, &len);
      assert (fd >= 0);
    }

  dir = opendir ("/proc/self/fd");
  chdir ("/proc/self/fd");
  for (count = 0, ent = NULL; ent = readdir (dir), ent != NULL; )
    {
      if (strcmp (ent->d_name, ".") == 0 || strcmp (ent->d_name, "..") == 0)
        continue;

      char target[1024];
      ssize_t size;
      size = readlink (ent->d_name, target, sizeof target);
      target[size < 0 ? 0 : size] = '\0';
      printf ("%s -> %s\n", ent->d_name, target);

      count++;
    }
  closedir (dir);

  log = fopen ("$fd_count.tmp", "w");
  fprintf (log, "%zi\n", count);
  fclose (log);
  rename ("$fd_count.tmp", "$fd_count");

  return EXIT_SUCCESS;
}
EOF

"${CC:-gcc}" -Wall "$c_file" -o "$exe"
"$exe"				# try it out

cat > "$conf" <<EOF
(register-services
 (list (service
	 '(system-ctor)
	 #:start (make-system-constructor "$exe")
	 #:stop  (const #f)
	 #:one-shot? #t)
       (service
	 '(forkexec-ctor)
	 #:start (make-forkexec-constructor '("sleep" "100"))
	 #:stop (make-kill-destructor))
       (service
	 '(inetd-ctor)
	 #:start (make-inetd-constructor '("$exe")
					 (list
					  (endpoint (make-socket-address
						     AF_INET
						     INADDR_LOOPBACK
						     5555))))
	 #:stop  (make-inetd-destructor))
       (service
	 '(systemd-ctor)
	 #:start (make-systemd-constructor '("$exe")
					   (list
					    (endpoint (make-socket-address
						       AF_INET
						       INADDR_LOOPBACK
						       5556))))
	 #:stop  (make-systemd-destructor))))
EOF

rm -f "$pid" "$fd_count"
shepherd -I -s "$socket" -c "$conf" -l "$log" --pid="$pid" &

# Wait till it's ready.
while ! test -f "$pid" ; do sleep 0.3 ; done

shepherd_pid="`cat $pid`"
kill -0 $shepherd_pid

# Open listening sockets, which should all be SOCK_CLOEXEC.
$herd start inetd-ctor

ls -l /proc/$shepherd_pid/fd

# Start 'system-ctor' and check how many open file descriptors it sees.
$herd start system-ctor
$herd status system-ctor
while ! test -f "$fd_count" ; do sleep 0.3 ; done

# The process running $exe must have seen the three standard file descriptors
# plus an open descriptor on /proc/self/fd.  Note that $exe is executed by
# 'system' so whether file descriptors are closed depends entirely on properly
# mapping all of shepherd's internal-use file descriptors as O_CLOEXEC.
test $(cat "$fd_count") -eq 4

# Same test, this time with a process started with 'make-forkexec-constructor'
# and thus 'exec-command'.
$herd start forkexec-ctor
$herd status forkexec-ctor

pid="$($herd status forkexec-ctor | grep "PID: [0-9]\+" \
  | sed -e's/^.* \([0-9]\+\)$/\1/g')"
kill -0 "$pid"

ls -l "/proc/$pid/fd"
test "$(cd "/proc/$pid/fd"; echo *)" = "0 1 2"
$herd stop forkexec-ctor

# Likewise for inetd and systemd services.

connect_to_server ()
{
    rm -f "$fd_count"
    "$GUILE" -c "(use-modules (ice-9 match))
      (define IN6ADDR_LOOPBACK 1)
      (define address (make-socket-address AF_INET INADDR_LOOPBACK $1))
      (define sock (socket (sockaddr:fam address) SOCK_STREAM 0))
      (connect sock address)"
    until test -s "$fd_count"; do sleep 0.3; done
}

for i in $(seq 1 3)
do
    # Spawn the inetd service process by connecting to the endpoint.  It must
    # have nothing but the 3 standard file descriptors open (plus one for
    # /proc/self/fd).
    connect_to_server 5555
    test $(cat "$fd_count") -eq 4

    # Spawn the systemd service by starting it.  This one must have
    # 5 open file descriptors: one for the listening socket, and one for the
    # accepted client connection (plus one for /proc/self/fd).
    $herd enable systemd-ctor
    $herd start systemd-ctor
    connect_to_server 5556
    test $(cat "$fd_count") -eq 6
    $herd stop systemd-ctor

    $herd restart forkexec-ctor
done