Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 24 Apr 2019 17:30:51 +0000 (UTC)
From:      Alan Somers <asomers@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-projects@freebsd.org
Subject:   svn commit: r346642 - in projects/fuse2: sys/fs/fuse tests/sys/fs/fusefs
Message-ID:  <201904241730.x3OHUpbN075579@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: asomers
Date: Wed Apr 24 17:30:50 2019
New Revision: 346642
URL: https://svnweb.freebsd.org/changeset/base/346642

Log:
  fusefs: handle ENOSYS for FUSE_INTERRUPT
  
  Though it's not documented, Linux will interpret a FUSE_INTERRUPT response
  of ENOSYS as "the file system does not support FUSE_INTERRUPT".
  Subsequently it will never send FUSE_INTERRUPT again to the same mount
  point.  This change matches Linux's behavior.
  
  PR:		346357
  Sponsored by:	The FreeBSD Foundation

Modified:
  projects/fuse2/sys/fs/fuse/fuse_ipc.c
  projects/fuse2/tests/sys/fs/fusefs/interrupt.cc

Modified: projects/fuse2/sys/fs/fuse/fuse_ipc.c
==============================================================================
--- projects/fuse2/sys/fs/fuse/fuse_ipc.c	Wed Apr 24 16:03:35 2019	(r346641)
+++ projects/fuse2/sys/fs/fuse/fuse_ipc.c	Wed Apr 24 17:30:50 2019	(r346642)
@@ -130,16 +130,13 @@ static uma_zone_t ticket_zone;
 /* 
  * TODO: figure out how to timeout INTERRUPT requests, because the daemon may
  * leagally never respond
- * 
- * TODO: remove an INTERRUPT request if the daemon responds to the original
  */
 static int
 fuse_interrupt_callback(struct fuse_ticket *tick, struct uio *uio)
 {
 	struct fuse_ticket *otick, *x_tick;
 	struct fuse_interrupt_in *fii;
-	struct fuse_data *data;
-	data = tick->tk_data;
+	struct fuse_data *data = tick->tk_data;
 	bool found = false;
 
 	fii = (struct fuse_interrupt_in*)((char*)tick->tk_ms_fiov.base +
@@ -162,7 +159,10 @@ fuse_interrupt_callback(struct fuse_ticket *tick, stru
 	/* Clear the original ticket's interrupt association */
 	otick->irq_unique = 0;
 
-	if (tick->tk_aw_ohead.error == EAGAIN) {
+	if (tick->tk_aw_ohead.error == ENOSYS) {
+		fsess_set_notimpl(data->mp, FUSE_INTERRUPT);
+		return 0;
+	} else if (tick->tk_aw_ohead.error == EAGAIN) {
 		/* 
 		 * There are two reasons we might get this:
 		 * 1) the daemon received the INTERRUPT request before the
@@ -219,6 +219,13 @@ fuse_interrupt_send(struct fuse_ticket *otick, int err
 			}
 		}
 		fuse_lck_mtx_unlock(data->ms_mtx);
+
+		/*
+		 * If the fuse daemon doesn't support interrupts, then there's
+		 * nothing more that we can do
+		 */
+		if (!fsess_isimpl(data->mp, FUSE_INTERRUPT))
+			return;
 
 		/* 
 		 * If the fuse daemon has already received otick, then we must

Modified: projects/fuse2/tests/sys/fs/fusefs/interrupt.cc
==============================================================================
--- projects/fuse2/tests/sys/fs/fusefs/interrupt.cc	Wed Apr 24 16:03:35 2019	(r346641)
+++ projects/fuse2/tests/sys/fs/fusefs/interrupt.cc	Wed Apr 24 17:30:50 2019	(r346642)
@@ -49,6 +49,8 @@ const off_t FILESIZE = 1000;
 const mode_t MODE = 0755;
 const char FULLDIRPATH0[] = "mountpoint/some_dir";
 const char RELDIRPATH0[] = "some_dir";
+const char FULLDIRPATH1[] = "mountpoint/other_dir";
+const char RELDIRPATH1[] = "other_dir";
 
 static sem_t *signaled_semaphore;
 
@@ -163,6 +165,29 @@ void TearDown() {
 }
 };
 
+static void* mkdir0(void* arg __unused) {
+	ssize_t r;
+
+	r = mkdir(FULLDIRPATH0, MODE);
+	if (r >= 0)
+		return 0;
+	else
+		return (void*)(intptr_t)errno;
+}
+
+static void* read1(void* arg) {
+	const size_t bufsize = FILESIZE;
+	char buf[bufsize];
+	int fd = (int)(intptr_t)arg;
+	ssize_t r;
+
+	r = read(fd, buf, bufsize);
+	if (r >= 0)
+		return 0;
+	else
+		return (void*)(intptr_t)errno;
+}
+
 /* 
  * An interrupt operation that gets received after the original command is
  * complete should generate an EAGAIN response.
@@ -206,6 +231,89 @@ TEST_F(Interrupt, already_complete)
 }
 
 /*
+ * If a FUSE file system returns ENOSYS for a FUSE_INTERRUPT operation, the
+ * kernel should not attempt to interrupt any other operations on that mount
+ * point.
+ */
+TEST_F(Interrupt, enosys)
+{
+	uint64_t ino0 = 42, ino1 = 43;;
+	uint64_t mkdir_unique;
+	pthread_t self, th0;
+	sem_t sem0, sem1;
+	void *thr0_value;
+	Sequence seq;
+
+	self = pthread_self();
+	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
+	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
+
+	EXPECT_LOOKUP(1, RELDIRPATH1).WillOnce(Invoke(ReturnErrno(ENOENT)));
+	EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
+	expect_mkdir(&mkdir_unique);
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([&](auto in) {
+			return (in->header.opcode == FUSE_INTERRUPT &&
+				in->body.interrupt.unique == mkdir_unique);
+		}, Eq(true)),
+		_)
+	).InSequence(seq)
+	.WillOnce(Invoke([&](auto in, auto &out) {
+		// reject FUSE_INTERRUPT and respond to the FUSE_WRITE
+		auto out0 = new mockfs_buf_out;
+		auto out1 = new mockfs_buf_out;
+
+		out0->header.unique = in->header.unique;
+		out0->header.error = -ENOSYS;
+		out0->header.len = sizeof(out0->header);
+		out.push_back(out0);
+
+		SET_OUT_HEADER_LEN(out1, entry);
+		out1->body.create.entry.attr.mode = S_IFDIR | MODE;
+		out1->body.create.entry.nodeid = ino1;
+		out1->header.unique = mkdir_unique;
+		out.push_back(out1);
+	}));
+	EXPECT_CALL(*m_mock, process(
+		ResultOf([&](auto in) {
+			return (in->header.opcode == FUSE_MKDIR);
+		}, Eq(true)),
+		_)
+	).InSequence(seq)
+	.WillOnce(Invoke([&](auto in, auto &out) {
+		auto out0 = new mockfs_buf_out;
+
+		sem_post(&sem0);
+		sem_wait(&sem1);
+
+		SET_OUT_HEADER_LEN(out0, entry);
+		out0->body.create.entry.attr.mode = S_IFDIR | MODE;
+		out0->body.create.entry.nodeid = ino0;
+		out0->header.unique = in->header.unique;
+		out.push_back(out0);
+	}));
+
+	setup_interruptor(self);
+	/* First mkdir operation should finish synchronously */
+	ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
+
+	ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
+		<< strerror(errno);
+
+	sem_wait(&sem0);
+	/*
+	 * th0 should be blocked waiting for the fuse daemon thread.
+	 * Signal it.  No FUSE_INTERRUPT should result
+	 */
+	pthread_kill(th0, SIGUSR1);
+	/* Allow the daemon thread to proceed */
+	sem_post(&sem1);
+	pthread_join(th0, &thr0_value);
+	/* Second mkdir should've finished without error */
+	EXPECT_EQ(0, (intptr_t)thr0_value);
+}
+
+/*
  * Upon receipt of a fatal signal, fusefs should return ASAP after sending
  * FUSE_INTERRUPT.
  */
@@ -279,7 +387,7 @@ TEST_F(Interrupt, ignore)
 		}, Eq(true)),
 		_)
 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
-		// Ignore FUSE_INTERRUPT; respond to the FUSE_WRITE
+		// Ignore FUSE_INTERRUPT; respond to the FUSE_MKDIR
 		auto out0 = new mockfs_buf_out;
 		out0->header.unique = mkdir_unique;
 		SET_OUT_HEADER_LEN(out0, entry);
@@ -292,42 +400,6 @@ TEST_F(Interrupt, ignore)
 	ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
 }
 
-void* mkdir0(void* arg __unused) {
-	ssize_t r;
-
-	r = mkdir(FULLDIRPATH0, MODE);
-	if (r >= 0)
-		return 0;
-	else
-		return (void*)(intptr_t)errno;
-}
-
-void* setxattr0(void* arg) {
-	const char *CONTENTS = "abcdefgh";
-	ssize_t bufsize = strlen(CONTENTS);
-	int fd = (int)(intptr_t)arg;
-	ssize_t r;
-
-	r = write(fd, CONTENTS, bufsize);
-	if (r >= 0)
-		return 0;
-	else
-		return (void*)(intptr_t)errno;
-}
-
-void* read1(void* arg) {
-	const size_t bufsize = FILESIZE;
-	char buf[bufsize];
-	int fd = (int)(intptr_t)arg;
-	ssize_t r;
-
-	r = read(fd, buf, bufsize);
-	if (r >= 0)
-		return 0;
-	else
-		return (void*)(intptr_t)errno;
-}
-
 /*
  * A restartable operation (basically, anything except write or setextattr)
  * that hasn't yet been sent to userland can be interrupted without sending
@@ -536,15 +608,11 @@ TEST_F(Interrupt, in_progress_read)
 	setup_interruptor(self);
 	ASSERT_EQ(-1, read(fd, buf, bufsize));
 	EXPECT_EQ(EINTR, errno);
-
-	/* Deliberately leak fd.  close(2) will be tested in release.cc */
 }
 
 /* FUSE_INTERRUPT operations should take priority over other pending ops */
 TEST_F(Interrupt, priority)
 {
-	const char FULLPATH1[] = "mountpoint/other_dir";
-	const char RELPATH1[] = "other_dir";
 	Sequence seq;
 	uint64_t ino1 = 43;
 	uint64_t mkdir_unique;
@@ -556,8 +624,7 @@ TEST_F(Interrupt, priority)
 	self = pthread_self();
 
 	EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
-	EXPECT_LOOKUP(1, RELPATH1).WillOnce(Invoke(ReturnErrno(ENOENT)));
-	//expect_mkdir(&mkdir_unique);
+	EXPECT_LOOKUP(1, RELDIRPATH1).WillOnce(Invoke(ReturnErrno(ENOENT)));
 	EXPECT_CALL(*m_mock, process(
 		ResultOf([=](auto in) {
 			return (in->header.opcode == FUSE_MKDIR);
@@ -579,8 +646,8 @@ TEST_F(Interrupt, priority)
 		out->header.len = sizeof(out->header);
 	})));
 	/* 
-	 * FUSE_INTERRUPT should be received before the second FUSE_MKDIR, even
-	 * though it was generated later
+	 * FUSE_INTERRUPT should be received before the second FUSE_MKDIR,
+	 * even though it was generated later
 	 */
 	EXPECT_CALL(*m_mock, process(
 		ResultOf([&](auto in) {
@@ -610,7 +677,7 @@ TEST_F(Interrupt, priority)
 
 	sem_wait(&sem1);	/* Sequence the two mkdirs */
 	setup_interruptor(th0);
-	ASSERT_EQ(0, mkdir(FULLPATH1, MODE)) << strerror(errno);
+	ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
 
 	/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
 	usleep(250'000);
@@ -669,8 +736,6 @@ TEST_F(Interrupt, too_soon)
 	setup_interruptor(self);
 	ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
 	EXPECT_EQ(EINTR, errno);
-
-	/* Deliberately leak fd.  close(2) will be tested in release.cc */
 }
 
 



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201904241730.x3OHUpbN075579>