xref: /freebsd/tests/sys/fs/fusefs/create.cc (revision ede571e40a114351f3c3cd5540f4d9f8596863f6)
19821f1d3SAlan Somers /*-
29821f1d3SAlan Somers  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
39821f1d3SAlan Somers  *
49821f1d3SAlan Somers  * Copyright (c) 2019 The FreeBSD Foundation
59821f1d3SAlan Somers  *
69821f1d3SAlan Somers  * This software was developed by BFF Storage Systems, LLC under sponsorship
79821f1d3SAlan Somers  * from the FreeBSD Foundation.
89821f1d3SAlan Somers  *
99821f1d3SAlan Somers  * Redistribution and use in source and binary forms, with or without
109821f1d3SAlan Somers  * modification, are permitted provided that the following conditions
119821f1d3SAlan Somers  * are met:
129821f1d3SAlan Somers  * 1. Redistributions of source code must retain the above copyright
139821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer.
149821f1d3SAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
159821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer in the
169821f1d3SAlan Somers  *    documentation and/or other materials provided with the distribution.
179821f1d3SAlan Somers  *
189821f1d3SAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
199821f1d3SAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
209821f1d3SAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
219821f1d3SAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
229821f1d3SAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
239821f1d3SAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
249821f1d3SAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
259821f1d3SAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
269821f1d3SAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
279821f1d3SAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
289821f1d3SAlan Somers  * SUCH DAMAGE.
299821f1d3SAlan Somers  */
309821f1d3SAlan Somers 
319821f1d3SAlan Somers extern "C" {
329821f1d3SAlan Somers #include <fcntl.h>
33*ede571e4SAlan Somers #include <sys/socket.h>
34*ede571e4SAlan Somers #include <sys/un.h>
359821f1d3SAlan Somers }
369821f1d3SAlan Somers 
379821f1d3SAlan Somers #include "mockfs.hh"
389821f1d3SAlan Somers #include "utils.hh"
399821f1d3SAlan Somers 
409821f1d3SAlan Somers using namespace testing;
419821f1d3SAlan Somers 
422d445be1SAlan Somers class Create: public FuseTest {
432d445be1SAlan Somers public:
442d445be1SAlan Somers 
45*ede571e4SAlan Somers void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
462d445be1SAlan Somers {
472d445be1SAlan Somers 	EXPECT_CALL(*m_mock, process(
482d445be1SAlan Somers 		ResultOf([=](auto in) {
492d445be1SAlan Somers 			const char *name = (const char*)in->body.bytes +
502d445be1SAlan Somers 				sizeof(fuse_open_in);
512d445be1SAlan Somers 			return (in->header.opcode == FUSE_CREATE &&
52*ede571e4SAlan Somers 				in->body.open.mode == mode &&
532d445be1SAlan Somers 				(0 == strcmp(relpath, name)));
542d445be1SAlan Somers 		}, Eq(true)),
552d445be1SAlan Somers 		_)
562d445be1SAlan Somers 	).WillOnce(Invoke(r));
572d445be1SAlan Somers }
582d445be1SAlan Somers 
592d445be1SAlan Somers };
609821f1d3SAlan Somers 
619821f1d3SAlan Somers /*
629821f1d3SAlan Somers  * If FUSE_CREATE sets the attr_valid, then subsequent GETATTRs should use the
639821f1d3SAlan Somers  * attribute cache
649821f1d3SAlan Somers  */
65cad67791SAlan Somers TEST_F(Create, attr_cache)
669821f1d3SAlan Somers {
679821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
689821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
69*ede571e4SAlan Somers 	mode_t mode = S_IFREG | 0755;
709821f1d3SAlan Somers 	uint64_t ino = 42;
719821f1d3SAlan Somers 	int fd;
729821f1d3SAlan Somers 
739821f1d3SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
74*ede571e4SAlan Somers 	expect_create(RELPATH, mode,
75*ede571e4SAlan Somers 		ReturnImmediate([=](auto in __unused, auto out) {
769821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
77*ede571e4SAlan Somers 		out->body.create.entry.attr.mode = mode;
789821f1d3SAlan Somers 		out->body.create.entry.nodeid = ino;
799821f1d3SAlan Somers 		out->body.create.entry.entry_valid = UINT64_MAX;
809821f1d3SAlan Somers 		out->body.create.entry.attr_valid = UINT64_MAX;
812d445be1SAlan Somers 	}));
829821f1d3SAlan Somers 
839821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
849821f1d3SAlan Somers 		ResultOf([=](auto in) {
859821f1d3SAlan Somers 			return (in->header.opcode == FUSE_GETATTR &&
869821f1d3SAlan Somers 				in->header.nodeid == ino);
879821f1d3SAlan Somers 		}, Eq(true)),
889821f1d3SAlan Somers 		_)
899821f1d3SAlan Somers 	).Times(0);
909821f1d3SAlan Somers 
919821f1d3SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
929821f1d3SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
939821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
949821f1d3SAlan Somers }
959821f1d3SAlan Somers 
969821f1d3SAlan Somers /*
979821f1d3SAlan Somers  * The fuse daemon fails the request with EEXIST.  This usually indicates a
989821f1d3SAlan Somers  * race condition: some other FUSE client created the file in between when the
999821f1d3SAlan Somers  * kernel checked for it with lookup and tried to create it with create
1009821f1d3SAlan Somers  */
1019821f1d3SAlan Somers TEST_F(Create, eexist)
1029821f1d3SAlan Somers {
1039821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1049821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
105*ede571e4SAlan Somers 	mode_t mode = S_IFREG | 0755;
1069821f1d3SAlan Somers 
1079821f1d3SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
108*ede571e4SAlan Somers 	expect_create(RELPATH, mode, ReturnErrno(EEXIST));
1099821f1d3SAlan Somers 	EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
1109821f1d3SAlan Somers 	EXPECT_EQ(EEXIST, errno);
1119821f1d3SAlan Somers }
1129821f1d3SAlan Somers 
1139821f1d3SAlan Somers /*
1149821f1d3SAlan Somers  * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback
1159821f1d3SAlan Somers  * to FUSE_MKNOD/FUSE_OPEN
1169821f1d3SAlan Somers  */
11719ef317dSAlan Somers TEST_F(Create, Enosys)
1189821f1d3SAlan Somers {
1199821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1209821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
121*ede571e4SAlan Somers 	mode_t mode = S_IFREG | 0755;
1229821f1d3SAlan Somers 	uint64_t ino = 42;
1239821f1d3SAlan Somers 	int fd;
1249821f1d3SAlan Somers 
1259821f1d3SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
126*ede571e4SAlan Somers 	expect_create(RELPATH, mode, ReturnErrno(ENOSYS));
1279821f1d3SAlan Somers 
1289821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
1299821f1d3SAlan Somers 		ResultOf([=](auto in) {
1309821f1d3SAlan Somers 			const char *name = (const char*)in->body.bytes +
1319821f1d3SAlan Somers 				sizeof(fuse_mknod_in);
1329821f1d3SAlan Somers 			return (in->header.opcode == FUSE_MKNOD &&
1339821f1d3SAlan Somers 				in->body.mknod.mode == (S_IFREG | mode) &&
1349821f1d3SAlan Somers 				in->body.mknod.rdev == 0 &&
1359821f1d3SAlan Somers 				(0 == strcmp(RELPATH, name)));
1369821f1d3SAlan Somers 		}, Eq(true)),
1379821f1d3SAlan Somers 		_)
1389821f1d3SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
13919ef317dSAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
140*ede571e4SAlan Somers 		out->body.entry.attr.mode = mode;
14119ef317dSAlan Somers 		out->body.entry.nodeid = ino;
14219ef317dSAlan Somers 		out->body.entry.entry_valid = UINT64_MAX;
14319ef317dSAlan Somers 		out->body.entry.attr_valid = UINT64_MAX;
1449821f1d3SAlan Somers 	})));
1459821f1d3SAlan Somers 
1469821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
1479821f1d3SAlan Somers 		ResultOf([=](auto in) {
1489821f1d3SAlan Somers 			return (in->header.opcode == FUSE_OPEN &&
1499821f1d3SAlan Somers 				in->header.nodeid == ino);
1509821f1d3SAlan Somers 		}, Eq(true)),
1519821f1d3SAlan Somers 		_)
1529821f1d3SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
1539821f1d3SAlan Somers 		out->header.len = sizeof(out->header);
1549821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, open);
1559821f1d3SAlan Somers 	})));
1569821f1d3SAlan Somers 
1579821f1d3SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
1589821f1d3SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
1599821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
1609821f1d3SAlan Somers }
1619821f1d3SAlan Somers 
1629821f1d3SAlan Somers /*
1639821f1d3SAlan Somers  * Creating a new file after FUSE_LOOKUP returned a negative cache entry
1649821f1d3SAlan Somers  */
1656248288eSAlan Somers TEST_F(Create, entry_cache_negative)
1669821f1d3SAlan Somers {
1679821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1689821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
169*ede571e4SAlan Somers 	mode_t mode = S_IFREG | 0755;
1709821f1d3SAlan Somers 	uint64_t ino = 42;
1719821f1d3SAlan Somers 	int fd;
1729821f1d3SAlan Somers 	/*
1739821f1d3SAlan Somers 	 * Set entry_valid = 0 because this test isn't concerned with whether
1749821f1d3SAlan Somers 	 * or not we actually cache negative entries, only with whether we
1759821f1d3SAlan Somers 	 * interpret negative cache responses correctly.
1769821f1d3SAlan Somers 	 */
1779821f1d3SAlan Somers 	struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
1789821f1d3SAlan Somers 
1799821f1d3SAlan Somers 	/* create will first do a LOOKUP, adding a negative cache entry */
1809821f1d3SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid));
181*ede571e4SAlan Somers 	expect_create(RELPATH, mode,
182*ede571e4SAlan Somers 		ReturnImmediate([=](auto in __unused, auto out) {
1839821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
184*ede571e4SAlan Somers 		out->body.create.entry.attr.mode = mode;
1859821f1d3SAlan Somers 		out->body.create.entry.nodeid = ino;
1869821f1d3SAlan Somers 		out->body.create.entry.entry_valid = UINT64_MAX;
1879821f1d3SAlan Somers 		out->body.create.entry.attr_valid = UINT64_MAX;
1882d445be1SAlan Somers 	}));
1899821f1d3SAlan Somers 
1909821f1d3SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
1919821f1d3SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1929821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
1939821f1d3SAlan Somers }
1949821f1d3SAlan Somers 
1959821f1d3SAlan Somers /*
1969821f1d3SAlan Somers  * Creating a new file should purge any negative namecache entries
1979821f1d3SAlan Somers  */
1986248288eSAlan Somers TEST_F(Create, entry_cache_negative_purge)
1999821f1d3SAlan Somers {
2009821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2019821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
202*ede571e4SAlan Somers 	mode_t mode = S_IFREG | 0755;
2039821f1d3SAlan Somers 	uint64_t ino = 42;
2049821f1d3SAlan Somers 	int fd;
2059821f1d3SAlan Somers 	struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
2069821f1d3SAlan Somers 
2079821f1d3SAlan Somers 	/* create will first do a LOOKUP, adding a negative cache entry */
2089821f1d3SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).Times(1)
2099821f1d3SAlan Somers 	.WillOnce(Invoke(ReturnNegativeCache(&entry_valid)))
2109821f1d3SAlan Somers 	.RetiresOnSaturation();
2119821f1d3SAlan Somers 
2129821f1d3SAlan Somers 	/* Then the CREATE should purge the negative cache entry */
213*ede571e4SAlan Somers 	expect_create(RELPATH, mode,
214*ede571e4SAlan Somers 		ReturnImmediate([=](auto in __unused, auto out) {
2159821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
216*ede571e4SAlan Somers 		out->body.create.entry.attr.mode = mode;
2179821f1d3SAlan Somers 		out->body.create.entry.nodeid = ino;
2189821f1d3SAlan Somers 		out->body.create.entry.attr_valid = UINT64_MAX;
2192d445be1SAlan Somers 	}));
2209821f1d3SAlan Somers 
2219821f1d3SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
2229821f1d3SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
2239821f1d3SAlan Somers 
2249821f1d3SAlan Somers 	/* Finally, a subsequent lookup should query the daemon */
2259821f1d3SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1);
2269821f1d3SAlan Somers 
2279821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
2289821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
2299821f1d3SAlan Somers }
2309821f1d3SAlan Somers 
2319821f1d3SAlan Somers /*
2329821f1d3SAlan Somers  * The daemon is responsible for checking file permissions (unless the
2339821f1d3SAlan Somers  * default_permissions mount option was used)
2349821f1d3SAlan Somers  */
2359821f1d3SAlan Somers TEST_F(Create, eperm)
2369821f1d3SAlan Somers {
2379821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2389821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
239*ede571e4SAlan Somers 	mode_t mode = S_IFREG | 0755;
2409821f1d3SAlan Somers 
2419821f1d3SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
242*ede571e4SAlan Somers 	expect_create(RELPATH, mode, ReturnErrno(EPERM));
2439821f1d3SAlan Somers 
2449821f1d3SAlan Somers 	EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
2459821f1d3SAlan Somers 	EXPECT_EQ(EPERM, errno);
2469821f1d3SAlan Somers }
2479821f1d3SAlan Somers 
2489821f1d3SAlan Somers TEST_F(Create, ok)
2499821f1d3SAlan Somers {
2509821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2519821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
252*ede571e4SAlan Somers 	mode_t mode = S_IFREG | 0755;
2539821f1d3SAlan Somers 	uint64_t ino = 42;
2549821f1d3SAlan Somers 	int fd;
2559821f1d3SAlan Somers 
2569821f1d3SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
257*ede571e4SAlan Somers 	expect_create(RELPATH, mode,
258*ede571e4SAlan Somers 		ReturnImmediate([=](auto in __unused, auto out) {
2599821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
260*ede571e4SAlan Somers 		out->body.create.entry.attr.mode = mode;
2619821f1d3SAlan Somers 		out->body.create.entry.nodeid = ino;
2629821f1d3SAlan Somers 		out->body.create.entry.entry_valid = UINT64_MAX;
2639821f1d3SAlan Somers 		out->body.create.entry.attr_valid = UINT64_MAX;
2642d445be1SAlan Somers 	}));
2659821f1d3SAlan Somers 
2669821f1d3SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
2679821f1d3SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
2689821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
2699821f1d3SAlan Somers }
2702d445be1SAlan Somers 
271*ede571e4SAlan Somers /* Create a unix-domain socket */
272*ede571e4SAlan Somers TEST_F(Create, socket)
273*ede571e4SAlan Somers {
274*ede571e4SAlan Somers 	const char FULLPATH[] = "mountpoint/some_sock";
275*ede571e4SAlan Somers 	const char RELPATH[] = "some_sock";
276*ede571e4SAlan Somers 	mode_t mode = S_IFSOCK | 0755;
277*ede571e4SAlan Somers 	struct sockaddr_un sa;
278*ede571e4SAlan Somers 	uint64_t ino = 42;
279*ede571e4SAlan Somers 	int fd;
280*ede571e4SAlan Somers 
281*ede571e4SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
282*ede571e4SAlan Somers 	expect_create(RELPATH, mode,
283*ede571e4SAlan Somers 		ReturnImmediate([=](auto in __unused, auto out) {
284*ede571e4SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
285*ede571e4SAlan Somers 		out->body.create.entry.attr.mode = mode;
286*ede571e4SAlan Somers 		out->body.create.entry.nodeid = ino;
287*ede571e4SAlan Somers 		out->body.create.entry.entry_valid = UINT64_MAX;
288*ede571e4SAlan Somers 		out->body.create.entry.attr_valid = UINT64_MAX;
289*ede571e4SAlan Somers 	}));
290*ede571e4SAlan Somers 
291*ede571e4SAlan Somers 	fd = socket(AF_UNIX, SOCK_STREAM, 0);
292*ede571e4SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
293*ede571e4SAlan Somers 	sa.sun_family = AF_UNIX;
294*ede571e4SAlan Somers 	strlcpy(sa.sun_path, FULLPATH, sizeof(sa.sun_path));
295*ede571e4SAlan Somers 	ASSERT_EQ(0, bind(fd, (struct sockaddr*)&sa, sizeof(sa)))
296*ede571e4SAlan Somers 		<< strerror(errno);
297*ede571e4SAlan Somers }
298*ede571e4SAlan Somers 
2992d445be1SAlan Somers /*
3002d445be1SAlan Somers  * A regression test for a bug that affected old FUSE implementations:
3012d445be1SAlan Somers  * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming
3022d445be1SAlan Somers  * contradiction between O_WRONLY and 0444
3032d445be1SAlan Somers  *
3042d445be1SAlan Somers  * For example:
3052d445be1SAlan Somers  * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886
3062d445be1SAlan Somers  */
3072d445be1SAlan Somers TEST_F(Create, wronly_0444)
3082d445be1SAlan Somers {
3092d445be1SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
3102d445be1SAlan Somers 	const char RELPATH[] = "some_file.txt";
311*ede571e4SAlan Somers 	mode_t mode = S_IFREG | 0444;
3122d445be1SAlan Somers 	uint64_t ino = 42;
3132d445be1SAlan Somers 	int fd;
3142d445be1SAlan Somers 
3152d445be1SAlan Somers 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
316*ede571e4SAlan Somers 	expect_create(RELPATH, mode,
317*ede571e4SAlan Somers 		ReturnImmediate([=](auto in __unused, auto out) {
3182d445be1SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
319*ede571e4SAlan Somers 		out->body.create.entry.attr.mode = mode;
3202d445be1SAlan Somers 		out->body.create.entry.nodeid = ino;
3212d445be1SAlan Somers 		out->body.create.entry.entry_valid = UINT64_MAX;
3222d445be1SAlan Somers 		out->body.create.entry.attr_valid = UINT64_MAX;
3232d445be1SAlan Somers 	}));
3242d445be1SAlan Somers 
3252d445be1SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_WRONLY, mode);
3262d445be1SAlan Somers 	EXPECT_LE(0, fd) << strerror(errno);
3272d445be1SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
3282d445be1SAlan Somers }
329