xref: /freebsd/tests/sys/fs/fusefs/interrupt.cc (revision f0f7fc1b)
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" {
32f0f7fc1bSAlan Somers #include <sys/types.h>
33f0f7fc1bSAlan Somers #include <sys/extattr.h>
34a1542146SAlan Somers #include <sys/wait.h>
359821f1d3SAlan Somers #include <fcntl.h>
369821f1d3SAlan Somers #include <pthread.h>
373d070fdcSAlan Somers #include <semaphore.h>
389821f1d3SAlan Somers #include <signal.h>
399821f1d3SAlan Somers }
409821f1d3SAlan Somers 
419821f1d3SAlan Somers #include "mockfs.hh"
429821f1d3SAlan Somers #include "utils.hh"
439821f1d3SAlan Somers 
449821f1d3SAlan Somers using namespace testing;
459821f1d3SAlan Somers 
46f0f7fc1bSAlan Somers /* Initial size of files used by these tests */
47f0f7fc1bSAlan Somers const off_t FILESIZE = 1000;
48f0f7fc1bSAlan Somers 
499821f1d3SAlan Somers /* Don't do anything; all we care about is that the syscall gets interrupted */
509821f1d3SAlan Somers void sigusr2_handler(int __unused sig) {
51723c7768SAlan Somers 	if (verbosity > 1) {
52723c7768SAlan Somers 		printf("Signaled!  thread %p\n", pthread_self());
53723c7768SAlan Somers 	}
54723c7768SAlan Somers 
559821f1d3SAlan Somers }
569821f1d3SAlan Somers 
579821f1d3SAlan Somers void* killer(void* target) {
589821f1d3SAlan Somers 	/*
599821f1d3SAlan Somers 	 * Sleep for awhile so we can be mostly confident that the main thread
609821f1d3SAlan Somers 	 * is already blocked in write(2)
619821f1d3SAlan Somers 	 */
629821f1d3SAlan Somers 	usleep(250'000);
639821f1d3SAlan Somers 	if (verbosity > 1)
64723c7768SAlan Somers 		printf("Signalling!  thread %p\n", target);
65723c7768SAlan Somers 	pthread_kill((pthread_t)target, SIGUSR2);
669821f1d3SAlan Somers 
679821f1d3SAlan Somers 	return(NULL);
689821f1d3SAlan Somers }
699821f1d3SAlan Somers 
709821f1d3SAlan Somers class Interrupt: public FuseTest {
719821f1d3SAlan Somers public:
729821f1d3SAlan Somers pthread_t m_child;
739821f1d3SAlan Somers 
749821f1d3SAlan Somers Interrupt(): m_child(NULL) {};
759821f1d3SAlan Somers 
769821f1d3SAlan Somers void expect_lookup(const char *relpath, uint64_t ino)
779821f1d3SAlan Somers {
78f0f7fc1bSAlan Somers 	FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, FILESIZE, 1);
79a1542146SAlan Somers }
80a1542146SAlan Somers 
81a1542146SAlan Somers /*
82a1542146SAlan Somers  * Expect a FUSE_READ but don't reply.  Instead, just record the unique value
83a1542146SAlan Somers  * to the provided pointer
84a1542146SAlan Somers  */
85a1542146SAlan Somers void expect_read(uint64_t ino, uint64_t *read_unique)
86a1542146SAlan Somers {
87a1542146SAlan Somers 	EXPECT_CALL(*m_mock, process(
88a1542146SAlan Somers 		ResultOf([=](auto in) {
89a1542146SAlan Somers 			return (in->header.opcode == FUSE_READ &&
90a1542146SAlan Somers 				in->header.nodeid == ino);
91a1542146SAlan Somers 		}, Eq(true)),
92a1542146SAlan Somers 		_)
93a1542146SAlan Somers 	).WillOnce(Invoke([=](auto in, auto &out __unused) {
94a1542146SAlan Somers 		*read_unique = in->header.unique;
95a1542146SAlan Somers 	}));
969821f1d3SAlan Somers }
979821f1d3SAlan Somers 
989821f1d3SAlan Somers /*
999821f1d3SAlan Somers  * Expect a FUSE_WRITE but don't reply.  Instead, just record the unique value
1009821f1d3SAlan Somers  * to the provided pointer
1019821f1d3SAlan Somers  */
1029821f1d3SAlan Somers void expect_write(uint64_t ino, uint64_t *write_unique)
1039821f1d3SAlan Somers {
1049821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
1059821f1d3SAlan Somers 		ResultOf([=](auto in) {
1069821f1d3SAlan Somers 			return (in->header.opcode == FUSE_WRITE &&
1079821f1d3SAlan Somers 				in->header.nodeid == ino);
1089821f1d3SAlan Somers 		}, Eq(true)),
1099821f1d3SAlan Somers 		_)
1109821f1d3SAlan Somers 	).WillOnce(Invoke([=](auto in, auto &out __unused) {
1119821f1d3SAlan Somers 		*write_unique = in->header.unique;
1129821f1d3SAlan Somers 	}));
1139821f1d3SAlan Somers }
1149821f1d3SAlan Somers 
1159821f1d3SAlan Somers void setup_interruptor(pthread_t self)
1169821f1d3SAlan Somers {
117a1542146SAlan Somers 	ASSERT_NE(SIG_ERR, signal(SIGUSR2, sigusr2_handler)) << strerror(errno);
1189821f1d3SAlan Somers 	ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)self))
1199821f1d3SAlan Somers 		<< strerror(errno);
1209821f1d3SAlan Somers }
1219821f1d3SAlan Somers 
1229821f1d3SAlan Somers void TearDown() {
123723c7768SAlan Somers 	struct sigaction sa;
124723c7768SAlan Somers 
1259821f1d3SAlan Somers 	if (m_child != NULL) {
1269821f1d3SAlan Somers 		pthread_join(m_child, NULL);
1279821f1d3SAlan Somers 	}
128723c7768SAlan Somers 	bzero(&sa, sizeof(sa));
129723c7768SAlan Somers 	sa.sa_handler = SIG_DFL;
130723c7768SAlan Somers 	sigaction(SIGUSR2, &sa, NULL);
1319821f1d3SAlan Somers 
1329821f1d3SAlan Somers 	FuseTest::TearDown();
1339821f1d3SAlan Somers }
1349821f1d3SAlan Somers };
1359821f1d3SAlan Somers 
1369821f1d3SAlan Somers /*
1379821f1d3SAlan Somers  * An interrupt operation that gets received after the original command is
1389821f1d3SAlan Somers  * complete should generate an EAGAIN response.
1399821f1d3SAlan Somers  */
1409821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
141723c7768SAlan Somers TEST_F(Interrupt, already_complete)
1429821f1d3SAlan Somers {
1439821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1449821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
1459821f1d3SAlan Somers 	const char *CONTENTS = "abcdefgh";
1469821f1d3SAlan Somers 	uint64_t ino = 42;
1479821f1d3SAlan Somers 	int fd;
1489821f1d3SAlan Somers 	ssize_t bufsize = strlen(CONTENTS);
1499821f1d3SAlan Somers 	pthread_t self;
1509821f1d3SAlan Somers 	uint64_t write_unique = 0;
1519821f1d3SAlan Somers 
1529821f1d3SAlan Somers 	self = pthread_self();
1539821f1d3SAlan Somers 
1549821f1d3SAlan Somers 	expect_lookup(RELPATH, ino);
1559821f1d3SAlan Somers 	expect_open(ino, 0, 1);
1569821f1d3SAlan Somers 	expect_write(ino, &write_unique);
1579821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
1589821f1d3SAlan Somers 		ResultOf([&](auto in) {
1599821f1d3SAlan Somers 			return (in->header.opcode == FUSE_INTERRUPT &&
1609821f1d3SAlan Somers 				in->body.interrupt.unique == write_unique);
1619821f1d3SAlan Somers 		}, Eq(true)),
1629821f1d3SAlan Somers 		_)
1639821f1d3SAlan Somers 	).WillOnce(Invoke([&](auto in, auto &out) {
1649821f1d3SAlan Somers 		// First complete the write request
1659821f1d3SAlan Somers 		auto out0 = new mockfs_buf_out;
1669821f1d3SAlan Somers 		out0->header.unique = write_unique;
1679821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out0, write);
1689821f1d3SAlan Somers 		out0->body.write.size = bufsize;
1699821f1d3SAlan Somers 		out.push_back(out0);
1709821f1d3SAlan Somers 
1719821f1d3SAlan Somers 		// Then, respond EAGAIN to the interrupt request
1729821f1d3SAlan Somers 		auto out1 = new mockfs_buf_out;
1739821f1d3SAlan Somers 		out1->header.unique = in->header.unique;
1749821f1d3SAlan Somers 		out1->header.error = -EAGAIN;
1759821f1d3SAlan Somers 		out1->header.len = sizeof(out1->header);
1769821f1d3SAlan Somers 		out.push_back(out1);
1779821f1d3SAlan Somers 	}));
1789821f1d3SAlan Somers 
1799821f1d3SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1809821f1d3SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1819821f1d3SAlan Somers 
1829821f1d3SAlan Somers 	setup_interruptor(self);
183723c7768SAlan Somers 	EXPECT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
1849821f1d3SAlan Somers 
1859821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
1869821f1d3SAlan Somers }
1879821f1d3SAlan Somers 
1889821f1d3SAlan Somers /*
189a1542146SAlan Somers  * Upon receipt of a fatal signal, fusefs should return ASAP after sending
190a1542146SAlan Somers  * FUSE_INTERRUPT.
191a1542146SAlan Somers  */
192a1542146SAlan Somers TEST_F(Interrupt, fatal_signal)
193a1542146SAlan Somers {
194a1542146SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
195a1542146SAlan Somers 	const char *CONTENTS = "abcdefgh";
196a1542146SAlan Somers 	const char RELPATH[] = "some_file.txt";
197a1542146SAlan Somers 	ssize_t bufsize = strlen(CONTENTS);
198a1542146SAlan Somers 	uint64_t ino = 42;
199a1542146SAlan Somers 	int status;
200a1542146SAlan Somers 	pthread_t self;
201a1542146SAlan Somers 	uint64_t write_unique;
202a1542146SAlan Somers 
203a1542146SAlan Somers 	self = pthread_self();
204a1542146SAlan Somers 
205a1542146SAlan Somers 	expect_lookup(RELPATH, ino);
206a1542146SAlan Somers 	expect_open(ino, 0, 1);
207a1542146SAlan Somers 	expect_write(ino, &write_unique);
208a1542146SAlan Somers 	EXPECT_CALL(*m_mock, process(
209a1542146SAlan Somers 		ResultOf([&](auto in) {
210a1542146SAlan Somers 			return (in->header.opcode == FUSE_INTERRUPT &&
211a1542146SAlan Somers 				in->body.interrupt.unique == write_unique);
212a1542146SAlan Somers 		}, Eq(true)),
213a1542146SAlan Somers 		_)
214a1542146SAlan Somers 	).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
215a1542146SAlan Somers 		/* Don't respond.  The process should exit anyway */
216a1542146SAlan Somers 	}));
217a1542146SAlan Somers 	expect_flush(ino, 1, ReturnErrno(0));
218a1542146SAlan Somers 	expect_release(ino, FH);
219a1542146SAlan Somers 
220a1542146SAlan Somers 	fork(false, &status, [&] {
221a1542146SAlan Somers 	}, [&]() {
222a1542146SAlan Somers 		struct sigaction sa;
223a1542146SAlan Somers 		int fd, r;
224a1542146SAlan Somers 		pthread_t killer_th;
225a1542146SAlan Somers 		pthread_t self;
226a1542146SAlan Somers 
227a1542146SAlan Somers 		fd = open(FULLPATH, O_WRONLY);
228a1542146SAlan Somers 		if (fd < 0) {
229a1542146SAlan Somers 			perror("open");
230a1542146SAlan Somers 			return 1;
231a1542146SAlan Somers 		}
232a1542146SAlan Somers 
233a1542146SAlan Somers 		/* SIGUSR2 terminates the process by default */
234a1542146SAlan Somers 		bzero(&sa, sizeof(sa));
235a1542146SAlan Somers 		sa.sa_handler = SIG_DFL;
236a1542146SAlan Somers 		r = sigaction(SIGUSR2, &sa, NULL);
237a1542146SAlan Somers 		if (r != 0) {
238a1542146SAlan Somers 			perror("sigaction");
239a1542146SAlan Somers 			return 1;
240a1542146SAlan Somers 		}
241a1542146SAlan Somers 		self = pthread_self();
242a1542146SAlan Somers 		r = pthread_create(&killer_th, NULL, killer, (void*)self);
243a1542146SAlan Somers 		if (r != 0) {
244a1542146SAlan Somers 			perror("pthread_create");
245a1542146SAlan Somers 			return 1;
246a1542146SAlan Somers 		}
247a1542146SAlan Somers 
248a1542146SAlan Somers 		write(fd, CONTENTS, bufsize);
249a1542146SAlan Somers 		return 1;
250a1542146SAlan Somers 	});
251a1542146SAlan Somers 	ASSERT_EQ(SIGUSR2, WTERMSIG(status));
252a1542146SAlan Somers 
253a1542146SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
254a1542146SAlan Somers }
255a1542146SAlan Somers 
256a1542146SAlan Somers /*
2579821f1d3SAlan Somers  * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and
2589821f1d3SAlan Somers  * complete the original operation whenever it damn well pleases.
2599821f1d3SAlan Somers  */
2609821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
261723c7768SAlan Somers TEST_F(Interrupt, ignore)
2629821f1d3SAlan Somers {
2639821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2649821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2659821f1d3SAlan Somers 	const char *CONTENTS = "abcdefgh";
2669821f1d3SAlan Somers 	uint64_t ino = 42;
2679821f1d3SAlan Somers 	int fd;
2689821f1d3SAlan Somers 	ssize_t bufsize = strlen(CONTENTS);
2699821f1d3SAlan Somers 	pthread_t self;
2709821f1d3SAlan Somers 	uint64_t write_unique;
2719821f1d3SAlan Somers 
2729821f1d3SAlan Somers 	self = pthread_self();
2739821f1d3SAlan Somers 
2749821f1d3SAlan Somers 	expect_lookup(RELPATH, ino);
2759821f1d3SAlan Somers 	expect_open(ino, 0, 1);
2769821f1d3SAlan Somers 	expect_write(ino, &write_unique);
2779821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
278723c7768SAlan Somers 		ResultOf([&](auto in) {
2799821f1d3SAlan Somers 			return (in->header.opcode == FUSE_INTERRUPT &&
2809821f1d3SAlan Somers 				in->body.interrupt.unique == write_unique);
2819821f1d3SAlan Somers 		}, Eq(true)),
2829821f1d3SAlan Somers 		_)
2839821f1d3SAlan Somers 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
2849821f1d3SAlan Somers 		// Ignore FUSE_INTERRUPT; respond to the FUSE_WRITE
2859821f1d3SAlan Somers 		auto out0 = new mockfs_buf_out;
2869821f1d3SAlan Somers 		out0->header.unique = write_unique;
2879821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out0, write);
2889821f1d3SAlan Somers 		out0->body.write.size = bufsize;
2899821f1d3SAlan Somers 		out.push_back(out0);
2909821f1d3SAlan Somers 	}));
2919821f1d3SAlan Somers 
2929821f1d3SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
2939821f1d3SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
2949821f1d3SAlan Somers 
2959821f1d3SAlan Somers 	setup_interruptor(self);
2969821f1d3SAlan Somers 	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
2979821f1d3SAlan Somers 
2989821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
2999821f1d3SAlan Somers }
3009821f1d3SAlan Somers 
3013d070fdcSAlan Somers void* write0(void* arg) {
3023d070fdcSAlan Somers 	const char *CONTENTS = "abcdefgh";
3033d070fdcSAlan Somers 	ssize_t bufsize = strlen(CONTENTS);
3043d070fdcSAlan Somers 	int fd = (int)(intptr_t)arg;
3053d070fdcSAlan Somers 	ssize_t r;
3063d070fdcSAlan Somers 
3073d070fdcSAlan Somers 	r = write(fd, CONTENTS, bufsize);
3083d070fdcSAlan Somers 	if (r >= 0)
3093d070fdcSAlan Somers 		return 0;
3103d070fdcSAlan Somers 	else
3113d070fdcSAlan Somers 		return (void*)(intptr_t)errno;
3123d070fdcSAlan Somers }
3133d070fdcSAlan Somers 
314f0f7fc1bSAlan Somers void* read1(void* arg) {
315f0f7fc1bSAlan Somers 	const size_t bufsize = FILESIZE;
316f0f7fc1bSAlan Somers 	char buf[bufsize];
317f0f7fc1bSAlan Somers 	int fd = (int)(intptr_t)arg;
318f0f7fc1bSAlan Somers 	ssize_t r;
319f0f7fc1bSAlan Somers 
320f0f7fc1bSAlan Somers 	r = read(fd, buf, bufsize);
321f0f7fc1bSAlan Somers 	if (r >= 0)
322f0f7fc1bSAlan Somers 		return 0;
323f0f7fc1bSAlan Somers 	else
324f0f7fc1bSAlan Somers 		return (void*)(intptr_t)errno;
325f0f7fc1bSAlan Somers }
326f0f7fc1bSAlan Somers 
3273d070fdcSAlan Somers /*
3283d070fdcSAlan Somers  * An operation that hasn't yet been sent to userland can be interrupted
3293d070fdcSAlan Somers  * without sending FUSE_INTERRUPT
3303d070fdcSAlan Somers  */
3313d070fdcSAlan Somers TEST_F(Interrupt, in_kernel)
3323d070fdcSAlan Somers {
3333d070fdcSAlan Somers 	const char FULLPATH0[] = "mountpoint/some_file.txt";
3343d070fdcSAlan Somers 	const char RELPATH0[] = "some_file.txt";
3353d070fdcSAlan Somers 	const char FULLPATH1[] = "mountpoint/other_file.txt";
3363d070fdcSAlan Somers 	const char RELPATH1[] = "other_file.txt";
3373d070fdcSAlan Somers 	const char *CONTENTS = "ijklmnop";
3383d070fdcSAlan Somers 	ssize_t bufsize = strlen(CONTENTS);
3393d070fdcSAlan Somers 	uint64_t ino0 = 42, ino1 = 43;
3403d070fdcSAlan Somers 	int fd0, fd1;
3413d070fdcSAlan Somers 	pthread_t self, th0;
3423d070fdcSAlan Somers 	sem_t sem0, sem1;
3433d070fdcSAlan Somers 	void *thr0_value;
3443d070fdcSAlan Somers 
3453d070fdcSAlan Somers 	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
3463d070fdcSAlan Somers 	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
3473d070fdcSAlan Somers 	self = pthread_self();
3483d070fdcSAlan Somers 
3493d070fdcSAlan Somers 	expect_lookup(RELPATH0, ino0);
3503d070fdcSAlan Somers 	expect_open(ino0, 0, 1);
3513d070fdcSAlan Somers 	expect_lookup(RELPATH1, ino1);
3523d070fdcSAlan Somers 	expect_open(ino1, 0, 1);
3533d070fdcSAlan Somers 	EXPECT_CALL(*m_mock, process(
3543d070fdcSAlan Somers 		ResultOf([=](auto in) {
3553d070fdcSAlan Somers 			return (in->header.opcode == FUSE_WRITE &&
3563d070fdcSAlan Somers 				in->header.nodeid == ino0);
3573d070fdcSAlan Somers 		}, Eq(true)),
3583d070fdcSAlan Somers 		_)
3593d070fdcSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([&](auto in, auto out) {
3603d070fdcSAlan Somers 		/* Let the next write proceed */
3613d070fdcSAlan Somers 		sem_post(&sem1);
3623d070fdcSAlan Somers 		/* Pause the daemon thread so it won't read the next op */
3633d070fdcSAlan Somers 		sem_wait(&sem0);
3643d070fdcSAlan Somers 
3653d070fdcSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
3663d070fdcSAlan Somers 		out->body.write.size = in->body.write.size;
3673d070fdcSAlan Somers 	})));
3683d070fdcSAlan Somers 
3693d070fdcSAlan Somers 	fd0 = open(FULLPATH0, O_WRONLY);
3703d070fdcSAlan Somers 	ASSERT_LE(0, fd0) << strerror(errno);
3713d070fdcSAlan Somers 	fd1 = open(FULLPATH1, O_WRONLY);
3723d070fdcSAlan Somers 	ASSERT_LE(0, fd1) << strerror(errno);
3733d070fdcSAlan Somers 
3743d070fdcSAlan Somers 	/* Use a separate thread for the first write */
3753d070fdcSAlan Somers 	ASSERT_EQ(0, pthread_create(&th0, NULL, write0, (void*)(intptr_t)fd0))
3763d070fdcSAlan Somers 		<< strerror(errno);
3773d070fdcSAlan Somers 
3783d070fdcSAlan Somers 	setup_interruptor(self);
3793d070fdcSAlan Somers 
3803d070fdcSAlan Somers 	sem_wait(&sem1);	/* Sequence the two writes */
3813d070fdcSAlan Somers 	ASSERT_EQ(-1, write(fd1, CONTENTS, bufsize));
3823d070fdcSAlan Somers 	EXPECT_EQ(EINTR, errno);
3833d070fdcSAlan Somers 
3843d070fdcSAlan Somers 	/* Unstick the daemon */
3853d070fdcSAlan Somers 	ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
3863d070fdcSAlan Somers 
3873d070fdcSAlan Somers 	/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
3883d070fdcSAlan Somers 	usleep(250'000);
3893d070fdcSAlan Somers 
3903d070fdcSAlan Somers 	pthread_join(th0, &thr0_value);
3913d070fdcSAlan Somers 	EXPECT_EQ(0, (intptr_t)thr0_value);
3923d070fdcSAlan Somers 	sem_destroy(&sem1);
3933d070fdcSAlan Somers 	sem_destroy(&sem0);
3943d070fdcSAlan Somers }
3953d070fdcSAlan Somers 
3969821f1d3SAlan Somers /*
397f0f7fc1bSAlan Somers  * A restartable operation (basically, anything except write or setextattr)
398f0f7fc1bSAlan Somers  * that hasn't yet been sent to userland can be interrupted without sending
399f0f7fc1bSAlan Somers  * FUSE_INTERRUPT, and will be automatically restarted.
400f0f7fc1bSAlan Somers  */
401f0f7fc1bSAlan Somers TEST_F(Interrupt, in_kernel_restartable)
402f0f7fc1bSAlan Somers {
403f0f7fc1bSAlan Somers 	const char FULLPATH0[] = "mountpoint/some_file.txt";
404f0f7fc1bSAlan Somers 	const char RELPATH0[] = "some_file.txt";
405f0f7fc1bSAlan Somers 	const char FULLPATH1[] = "mountpoint/other_file.txt";
406f0f7fc1bSAlan Somers 	const char RELPATH1[] = "other_file.txt";
407f0f7fc1bSAlan Somers 	uint64_t ino0 = 42, ino1 = 43;
408f0f7fc1bSAlan Somers 	int fd0, fd1;
409f0f7fc1bSAlan Somers 	pthread_t self, th0, th1;
410f0f7fc1bSAlan Somers 	sem_t sem0, sem1;
411f0f7fc1bSAlan Somers 	void *thr0_value, *thr1_value;
412f0f7fc1bSAlan Somers 
413f0f7fc1bSAlan Somers 	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
414f0f7fc1bSAlan Somers 	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
415f0f7fc1bSAlan Somers 	self = pthread_self();
416f0f7fc1bSAlan Somers 
417f0f7fc1bSAlan Somers 	expect_lookup(RELPATH0, ino0);
418f0f7fc1bSAlan Somers 	expect_open(ino0, 0, 1);
419f0f7fc1bSAlan Somers 	expect_lookup(RELPATH1, ino1);
420f0f7fc1bSAlan Somers 	expect_open(ino1, 0, 1);
421f0f7fc1bSAlan Somers 	EXPECT_CALL(*m_mock, process(
422f0f7fc1bSAlan Somers 		ResultOf([=](auto in) {
423f0f7fc1bSAlan Somers 			return (in->header.opcode == FUSE_WRITE &&
424f0f7fc1bSAlan Somers 				in->header.nodeid == ino0);
425f0f7fc1bSAlan Somers 		}, Eq(true)),
426f0f7fc1bSAlan Somers 		_)
427f0f7fc1bSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([&](auto in, auto out) {
428f0f7fc1bSAlan Somers 		/* Let the next write proceed */
429f0f7fc1bSAlan Somers 		sem_post(&sem1);
430f0f7fc1bSAlan Somers 		/* Pause the daemon thread so it won't read the next op */
431f0f7fc1bSAlan Somers 		sem_wait(&sem0);
432f0f7fc1bSAlan Somers 
433f0f7fc1bSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
434f0f7fc1bSAlan Somers 		out->body.write.size = in->body.write.size;
435f0f7fc1bSAlan Somers 	})));
436f0f7fc1bSAlan Somers 	FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL);
437f0f7fc1bSAlan Somers 
438f0f7fc1bSAlan Somers 	fd0 = open(FULLPATH0, O_WRONLY);
439f0f7fc1bSAlan Somers 	ASSERT_LE(0, fd0) << strerror(errno);
440f0f7fc1bSAlan Somers 	fd1 = open(FULLPATH1, O_RDONLY);
441f0f7fc1bSAlan Somers 	ASSERT_LE(0, fd1) << strerror(errno);
442f0f7fc1bSAlan Somers 
443f0f7fc1bSAlan Somers 	/* Use a separate thread for each operation */
444f0f7fc1bSAlan Somers 	ASSERT_EQ(0, pthread_create(&th0, NULL, write0, (void*)(intptr_t)fd0))
445f0f7fc1bSAlan Somers 		<< strerror(errno);
446f0f7fc1bSAlan Somers 
447f0f7fc1bSAlan Somers 	sem_wait(&sem1);	/* Sequence the two operations */
448f0f7fc1bSAlan Somers 
449f0f7fc1bSAlan Somers 	ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1))
450f0f7fc1bSAlan Somers 		<< strerror(errno);
451f0f7fc1bSAlan Somers 
452f0f7fc1bSAlan Somers 	setup_interruptor(self);
453f0f7fc1bSAlan Somers 
454f0f7fc1bSAlan Somers 	pause();		/* Wait for signal */
455f0f7fc1bSAlan Somers 
456f0f7fc1bSAlan Somers 	/* Unstick the daemon */
457f0f7fc1bSAlan Somers 	ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
458f0f7fc1bSAlan Somers 
459f0f7fc1bSAlan Somers 	/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
460f0f7fc1bSAlan Somers 	usleep(250'000);
461f0f7fc1bSAlan Somers 
462f0f7fc1bSAlan Somers 	pthread_join(th1, &thr1_value);
463f0f7fc1bSAlan Somers 	pthread_join(th0, &thr0_value);
464f0f7fc1bSAlan Somers 	EXPECT_EQ(0, (intptr_t)thr1_value);
465f0f7fc1bSAlan Somers 	EXPECT_EQ(0, (intptr_t)thr0_value);
466f0f7fc1bSAlan Somers 	sem_destroy(&sem1);
467f0f7fc1bSAlan Somers 	sem_destroy(&sem0);
468f0f7fc1bSAlan Somers }
469f0f7fc1bSAlan Somers 
470f0f7fc1bSAlan Somers /*
471f0f7fc1bSAlan Somers  * Like FUSE_WRITE, FUSE_SETXATTR is non-restartable because it calls uiomove
472f0f7fc1bSAlan Somers  * before blocking in fticket_wait_answ
473f0f7fc1bSAlan Somers  */
474f0f7fc1bSAlan Somers TEST_F(Interrupt, in_kernel_setxattr)
475f0f7fc1bSAlan Somers {
476f0f7fc1bSAlan Somers 	const char FULLPATH0[] = "mountpoint/some_file.txt";
477f0f7fc1bSAlan Somers 	const char RELPATH0[] = "some_file.txt";
478f0f7fc1bSAlan Somers 	const char FULLPATH1[] = "mountpoint/other_file.txt";
479f0f7fc1bSAlan Somers 	const char RELPATH1[] = "other_file.txt";
480f0f7fc1bSAlan Somers 	const char value[] = "whatever";
481f0f7fc1bSAlan Somers 	ssize_t value_len = strlen(value) + 1;
482f0f7fc1bSAlan Somers 	uint64_t ino0 = 42, ino1 = 43;
483f0f7fc1bSAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
484f0f7fc1bSAlan Somers 	int fd0, fd1;
485f0f7fc1bSAlan Somers 	pthread_t self, th0;
486f0f7fc1bSAlan Somers 	sem_t sem0, sem1;
487f0f7fc1bSAlan Somers 	void *thr0_value;
488f0f7fc1bSAlan Somers 	ssize_t r;
489f0f7fc1bSAlan Somers 
490f0f7fc1bSAlan Somers 	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
491f0f7fc1bSAlan Somers 	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
492f0f7fc1bSAlan Somers 	self = pthread_self();
493f0f7fc1bSAlan Somers 
494f0f7fc1bSAlan Somers 	expect_lookup(RELPATH0, ino0);
495f0f7fc1bSAlan Somers 	expect_open(ino0, 0, 1);
496f0f7fc1bSAlan Somers 	expect_lookup(RELPATH1, ino1);
497f0f7fc1bSAlan Somers 	expect_open(ino1, 0, 1);
498f0f7fc1bSAlan Somers 	EXPECT_CALL(*m_mock, process(
499f0f7fc1bSAlan Somers 		ResultOf([=](auto in) {
500f0f7fc1bSAlan Somers 			return (in->header.opcode == FUSE_WRITE &&
501f0f7fc1bSAlan Somers 				in->header.nodeid == ino0);
502f0f7fc1bSAlan Somers 		}, Eq(true)),
503f0f7fc1bSAlan Somers 		_)
504f0f7fc1bSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([&](auto in, auto out) {
505f0f7fc1bSAlan Somers 		/* Let the next write proceed */
506f0f7fc1bSAlan Somers 		sem_post(&sem1);
507f0f7fc1bSAlan Somers 		/* Pause the daemon thread so it won't read the next op */
508f0f7fc1bSAlan Somers 		sem_wait(&sem0);
509f0f7fc1bSAlan Somers 
510f0f7fc1bSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
511f0f7fc1bSAlan Somers 		out->body.write.size = in->body.write.size;
512f0f7fc1bSAlan Somers 	})));
513f0f7fc1bSAlan Somers 
514f0f7fc1bSAlan Somers 	fd0 = open(FULLPATH0, O_WRONLY);
515f0f7fc1bSAlan Somers 	ASSERT_LE(0, fd0) << strerror(errno);
516f0f7fc1bSAlan Somers 	fd1 = open(FULLPATH1, O_WRONLY);
517f0f7fc1bSAlan Somers 	ASSERT_LE(0, fd1) << strerror(errno);
518f0f7fc1bSAlan Somers 
519f0f7fc1bSAlan Somers 	/* Use a separate thread for the first write */
520f0f7fc1bSAlan Somers 	ASSERT_EQ(0, pthread_create(&th0, NULL, write0, (void*)(intptr_t)fd0))
521f0f7fc1bSAlan Somers 		<< strerror(errno);
522f0f7fc1bSAlan Somers 
523f0f7fc1bSAlan Somers 	setup_interruptor(self);
524f0f7fc1bSAlan Somers 
525f0f7fc1bSAlan Somers 	sem_wait(&sem1);	/* Sequence the two operations */
526f0f7fc1bSAlan Somers 	r = extattr_set_fd(fd1, ns, "foo", (void*)value, value_len);
527f0f7fc1bSAlan Somers 	EXPECT_EQ(EINTR, errno);
528f0f7fc1bSAlan Somers 
529f0f7fc1bSAlan Somers 	/* Unstick the daemon */
530f0f7fc1bSAlan Somers 	ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
531f0f7fc1bSAlan Somers 
532f0f7fc1bSAlan Somers 	/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
533f0f7fc1bSAlan Somers 	usleep(250'000);
534f0f7fc1bSAlan Somers 
535f0f7fc1bSAlan Somers 	pthread_join(th0, &thr0_value);
536f0f7fc1bSAlan Somers 	EXPECT_EQ(0, (intptr_t)thr0_value);
537f0f7fc1bSAlan Somers 	sem_destroy(&sem1);
538f0f7fc1bSAlan Somers 	sem_destroy(&sem0);
539f0f7fc1bSAlan Somers }
540f0f7fc1bSAlan Somers 
541f0f7fc1bSAlan Somers /*
5429821f1d3SAlan Somers  * A syscall that gets interrupted while blocking on FUSE I/O should send a
5439821f1d3SAlan Somers  * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR
5449821f1d3SAlan Somers  * in response to the _original_ operation.  The kernel should ultimately
5459821f1d3SAlan Somers  * return EINTR to userspace
5469821f1d3SAlan Somers  */
5479821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
548723c7768SAlan Somers TEST_F(Interrupt, in_progress)
5499821f1d3SAlan Somers {
5509821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
5519821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
5529821f1d3SAlan Somers 	const char *CONTENTS = "abcdefgh";
5539821f1d3SAlan Somers 	uint64_t ino = 42;
5549821f1d3SAlan Somers 	int fd;
5559821f1d3SAlan Somers 	ssize_t bufsize = strlen(CONTENTS);
5569821f1d3SAlan Somers 	pthread_t self;
5579821f1d3SAlan Somers 	uint64_t write_unique;
5589821f1d3SAlan Somers 
5599821f1d3SAlan Somers 	self = pthread_self();
5609821f1d3SAlan Somers 
5619821f1d3SAlan Somers 	expect_lookup(RELPATH, ino);
5629821f1d3SAlan Somers 	expect_open(ino, 0, 1);
5639821f1d3SAlan Somers 	expect_write(ino, &write_unique);
5649821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
565723c7768SAlan Somers 		ResultOf([&](auto in) {
5669821f1d3SAlan Somers 			return (in->header.opcode == FUSE_INTERRUPT &&
5679821f1d3SAlan Somers 				in->body.interrupt.unique == write_unique);
5689821f1d3SAlan Somers 		}, Eq(true)),
5699821f1d3SAlan Somers 		_)
5709821f1d3SAlan Somers 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
5719821f1d3SAlan Somers 		auto out0 = new mockfs_buf_out;
5729821f1d3SAlan Somers 		out0->header.error = -EINTR;
5739821f1d3SAlan Somers 		out0->header.unique = write_unique;
5749821f1d3SAlan Somers 		out0->header.len = sizeof(out0->header);
5759821f1d3SAlan Somers 		out.push_back(out0);
5769821f1d3SAlan Somers 	}));
5779821f1d3SAlan Somers 
5789821f1d3SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
5799821f1d3SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
5809821f1d3SAlan Somers 
5819821f1d3SAlan Somers 	setup_interruptor(self);
5829821f1d3SAlan Somers 	ASSERT_EQ(-1, write(fd, CONTENTS, bufsize));
5839821f1d3SAlan Somers 	EXPECT_EQ(EINTR, errno);
5849821f1d3SAlan Somers 
5859821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
5869821f1d3SAlan Somers }
5879821f1d3SAlan Somers 
588a1542146SAlan Somers /* Reads should also be interruptible */
589a1542146SAlan Somers TEST_F(Interrupt, in_progress_read)
590a1542146SAlan Somers {
591a1542146SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
592a1542146SAlan Somers 	const char RELPATH[] = "some_file.txt";
593a1542146SAlan Somers 	const size_t bufsize = 80;
594a1542146SAlan Somers 	char buf[bufsize];
595a1542146SAlan Somers 	uint64_t ino = 42;
596a1542146SAlan Somers 	int fd;
597a1542146SAlan Somers 	pthread_t self;
598a1542146SAlan Somers 	uint64_t read_unique;
599a1542146SAlan Somers 
600a1542146SAlan Somers 	self = pthread_self();
601a1542146SAlan Somers 
602a1542146SAlan Somers 	expect_lookup(RELPATH, ino);
603a1542146SAlan Somers 	expect_open(ino, 0, 1);
604a1542146SAlan Somers 	expect_read(ino, &read_unique);
605a1542146SAlan Somers 	EXPECT_CALL(*m_mock, process(
606a1542146SAlan Somers 		ResultOf([&](auto in) {
607a1542146SAlan Somers 			return (in->header.opcode == FUSE_INTERRUPT &&
608a1542146SAlan Somers 				in->body.interrupt.unique == read_unique);
609a1542146SAlan Somers 		}, Eq(true)),
610a1542146SAlan Somers 		_)
611a1542146SAlan Somers 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
612a1542146SAlan Somers 		auto out0 = new mockfs_buf_out;
613a1542146SAlan Somers 		out0->header.error = -EINTR;
614a1542146SAlan Somers 		out0->header.unique = read_unique;
615a1542146SAlan Somers 		out0->header.len = sizeof(out0->header);
616a1542146SAlan Somers 		out.push_back(out0);
617a1542146SAlan Somers 	}));
618a1542146SAlan Somers 
619a1542146SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
620a1542146SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
621a1542146SAlan Somers 
622a1542146SAlan Somers 	setup_interruptor(self);
623a1542146SAlan Somers 	ASSERT_EQ(-1, read(fd, buf, bufsize));
624a1542146SAlan Somers 	EXPECT_EQ(EINTR, errno);
625a1542146SAlan Somers 
626a1542146SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
627a1542146SAlan Somers }
628a1542146SAlan Somers 
6299821f1d3SAlan Somers /*
6309821f1d3SAlan Somers  * If the FUSE filesystem receives the FUSE_INTERRUPT operation before
6319821f1d3SAlan Somers  * processing the original, then it should wait for "some timeout" for the
6329821f1d3SAlan Somers  * original operation to arrive.  If not, it should send EAGAIN to the
6339821f1d3SAlan Somers  * INTERRUPT operation, and the kernel should requeue the INTERRUPT.
6349821f1d3SAlan Somers  *
6359821f1d3SAlan Somers  * In this test, we'll pretend that the INTERRUPT arrives too soon, gets
6369821f1d3SAlan Somers  * EAGAINed, then the kernel requeues it, and the second time around it
6379821f1d3SAlan Somers  * successfully interrupts the original
6389821f1d3SAlan Somers  */
6399821f1d3SAlan Somers /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
640723c7768SAlan Somers TEST_F(Interrupt, too_soon)
6419821f1d3SAlan Somers {
6429821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
6439821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
6449821f1d3SAlan Somers 	const char *CONTENTS = "abcdefgh";
645723c7768SAlan Somers 	Sequence seq;
6469821f1d3SAlan Somers 	uint64_t ino = 42;
6479821f1d3SAlan Somers 	int fd;
6489821f1d3SAlan Somers 	ssize_t bufsize = strlen(CONTENTS);
6499821f1d3SAlan Somers 	pthread_t self;
6509821f1d3SAlan Somers 	uint64_t write_unique;
6519821f1d3SAlan Somers 
6529821f1d3SAlan Somers 	self = pthread_self();
6539821f1d3SAlan Somers 
6549821f1d3SAlan Somers 	expect_lookup(RELPATH, ino);
6559821f1d3SAlan Somers 	expect_open(ino, 0, 1);
6569821f1d3SAlan Somers 	expect_write(ino, &write_unique);
6579821f1d3SAlan Somers 
6589821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
659723c7768SAlan Somers 		ResultOf([&](auto in) {
6609821f1d3SAlan Somers 			return (in->header.opcode == FUSE_INTERRUPT &&
6619821f1d3SAlan Somers 				in->body.interrupt.unique == write_unique);
6629821f1d3SAlan Somers 		}, Eq(true)),
6639821f1d3SAlan Somers 		_)
664723c7768SAlan Somers 	).InSequence(seq)
665723c7768SAlan Somers 	.WillOnce(Invoke(ReturnErrno(EAGAIN)));
6669821f1d3SAlan Somers 
6679821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
668723c7768SAlan Somers 		ResultOf([&](auto in) {
6699821f1d3SAlan Somers 			return (in->header.opcode == FUSE_INTERRUPT &&
6709821f1d3SAlan Somers 				in->body.interrupt.unique == write_unique);
6719821f1d3SAlan Somers 		}, Eq(true)),
6729821f1d3SAlan Somers 		_)
673723c7768SAlan Somers 	).InSequence(seq)
674723c7768SAlan Somers 	.WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
6759821f1d3SAlan Somers 		auto out0 = new mockfs_buf_out;
6769821f1d3SAlan Somers 		out0->header.error = -EINTR;
6779821f1d3SAlan Somers 		out0->header.unique = write_unique;
6789821f1d3SAlan Somers 		out0->header.len = sizeof(out0->header);
6799821f1d3SAlan Somers 		out.push_back(out0);
6809821f1d3SAlan Somers 	}));
6819821f1d3SAlan Somers 
6829821f1d3SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
6839821f1d3SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
6849821f1d3SAlan Somers 
6859821f1d3SAlan Somers 	setup_interruptor(self);
6869821f1d3SAlan Somers 	ASSERT_EQ(-1, write(fd, CONTENTS, bufsize));
6879821f1d3SAlan Somers 	EXPECT_EQ(EINTR, errno);
6889821f1d3SAlan Somers 
6899821f1d3SAlan Somers 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
6909821f1d3SAlan Somers }
691723c7768SAlan Somers 
692723c7768SAlan Somers 
693723c7768SAlan Somers // TODO: add a test where write returns EWOULDBLOCK
694