1#!/bin/sh
2
3#
4# Copyright (c) 2012 Peter Holm
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26# SUCH DAMAGE.
27#
28
29# Demonstrate that close() of an flock'd file is not atomic.
30# Fails with "flock_open_close: execv(/mnt/test): Text file busy"
31
32# Test scenario by: jhb
33
34[ `id -u ` -ne 0 ] && echo "Must be root!" && exit 1
35
36. ../default.cfg
37
38odir=`pwd`
39cd /tmp
40sed '1,/^EOF/d' < $odir/$0 > flock_open_close.c
41rm -f /tmp/flock_open_close
42mycc -o flock_open_close -Wall -Wextra -O2 -g flock_open_close.c -lpthread || exit 1
43rm -f flock_open_close.c
44
45mount | grep $mntpoint | grep -q /dev/md && umount -f $mntpoint
46mdconfig -l | grep -q md$mdstart &&  mdconfig -d -u $mdstart
47
48mdconfig -a -t swap -s 1g -u $mdstart || exit 1
49newfs $newfs_flags md$mdstart > /dev/null
50mount /dev/md$mdstart $mntpoint
51chmod 777 $mntpoint
52
53cp /bin/test $mntpoint
54chown $testuser $mntpoint/test
55chmod +w $mntpoint/test
56
57su $testuser -c "/tmp/flock_open_close $mntpoint/test" &
58pid=$!
59while kill -0 $! 2>/dev/null; do
60	mksnap_ffs $mntpoint $mntpoint/.snap/snap
61	sleep 2
62	rm -f $mntpoint/.snap/snap
63	sleep 1
64done
65wait $pid
66s=$?
67
68for i in `jot 10`; do
69	mount | grep -q md$mdstart  && \
70		umount $mntpoint && mdconfig -d -u $mdstart && break
71	sleep 2
72done
73if mount | grep -q md$mdstart; then
74	fstat $mntpoint
75	echo "umount $mntpoint failed"
76	exit 1
77fi
78rm -f /tmp/flock_open_close
79exit $s
80EOF
81
82#include <sys/types.h>
83#include <sys/stat.h>
84#include <sys/wait.h>
85#include <err.h>
86#include <errno.h>
87#include <fcntl.h>
88#include <stdio.h>
89#include <stdlib.h>
90#include <unistd.h>
91
92static void
93usage(void)
94{
95	fprintf(stderr, "Usage: flock_close_race <binary> [args]\n");
96	exit(1);
97}
98
99static void
100child(const char *binary)
101{
102	int fd;
103
104	/* Exit as soon as our parent exits. */
105	while (getppid() != 1) {
106		fd = open(binary, O_RDWR | O_EXLOCK);
107		if (fd < 0) {
108			/*
109			 * This may get ETXTBSY since exit() will
110			 * close its open fd's (thus releasing the
111			 * lock), before it releases the vmspace (and
112			 * mapping of the binary).
113			 */
114			if (errno == ETXTBSY)
115				continue;
116			err(2, "can't open %s", binary);
117		}
118		close(fd);
119	}
120	exit(0);
121}
122
123static void
124exec_child(char **av)
125{
126
127	(void)open(av[0], O_RDONLY | O_SHLOCK);
128	execv(av[0], av);
129	/* "flock_open_close: execv(/mnt/test): Text file busy" seen */
130	err(127, "execv(%s)", av[0]);
131}
132
133int
134main(int ac, char **av)
135{
136	struct stat sb;
137	pid_t pid;
138	int e, i, status;
139
140	if (ac < 2)
141		usage();
142	if (stat(av[1], &sb) != 0)
143		err(1, "stat(%s)", av[1]);
144	if (!S_ISREG(sb.st_mode))
145		errx(1, "%s not an executable", av[1]);
146
147	pid = fork();
148	if (pid < 0)
149		err(1, "fork");
150	if (pid == 0)
151		child(av[1]);
152	e = 0;
153	for (i = 0; i < 200000; i++) {
154		pid = fork();
155		if (pid < 0)
156			err(1, "vfork");
157		if (pid == 0)
158			exec_child(av + 1);
159		wait(&status);
160		if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
161			fprintf(stderr, "FAIL\n");
162			e = 1;
163			break;
164		}
165		if (WIFEXITED(status) && WEXITSTATUS(status) != 1) {
166			/* /bin/test returns 1 */
167			e = 1;
168			break;
169		}
170	}
171	return (e);
172}
173