[fs] Ensure Vnode::Close is called on unclean teardown
The contract of ulib/fs is that every call to "Open"
will be paired with an accompanying call to "Close",
in the event of either:
1) A client sends a close message explicitly, making
their channel defunct (and no longer observed), or
2) A client closes their channel, implicitly closing
their connection to the filesystem.
Although the Connection class in ulib/fs would properly
cancel waits and tear down the connection object,
it previoulsy missed sending a close in the second
condition, since it invoked "CallHandler" while the
channel was not yet ZX_HANDLE_INVALID.
This patch ensures that the close handler is invoked
in this condition.
ZX-1853 #done
Change-Id: I291192cae32e8250f7cef932cbf1a4de4b8b2d2d
diff --git a/system/ulib/fdio/remoteio.c b/system/ulib/fdio/remoteio.c
index 1234980..cff470d 100644
--- a/system/ulib/fdio/remoteio.c
+++ b/system/ulib/fdio/remoteio.c
@@ -109,7 +109,7 @@
msg.hcount = 0;
#endif
cb(&msg, cookie);
- return ZX_OK;
+ return ERR_DISPATCHER_DONE;
}
zx_status_t zxrio_handler(zx_handle_t h, zxrio_cb_t cb, void* cookie) {
diff --git a/system/ulib/fs/connection.cpp b/system/ulib/fs/connection.cpp
index ea79d3d..bbb6ba6 100644
--- a/system/ulib/fs/connection.cpp
+++ b/system/ulib/fs/connection.cpp
@@ -150,7 +150,7 @@
// Give the dispatcher a chance to clean up.
if (status != ERR_DISPATCHER_DONE) {
- CallHandler();
+ CallClose();
}
// Tell the VFS that the connection closed remotely.
@@ -167,7 +167,7 @@
ZX_DEBUG_ASSERT_MSG(status == ZX_OK, "Could not cancel wait: status=%d", status);
wait_.set_object(ZX_HANDLE_INVALID);
- CallHandler();
+ CallClose();
}
// Release the token associated with this connection's vnode since the connection
@@ -192,6 +192,11 @@
return zxrio_handler(channel_.get(), &Connection::HandleMessageThunk, this);
}
+void Connection::CallClose() {
+ channel_.reset();
+ CallHandler();
+}
+
zx_status_t Connection::HandleMessageThunk(zxrio_msg_t* msg, void* cookie) {
Connection* connection = static_cast<Connection*>(cookie);
return connection->HandleMessage(msg);
diff --git a/system/ulib/fs/include/fs/connection.h b/system/ulib/fs/include/fs/connection.h
index 5ac20a4..3a3e91a 100644
--- a/system/ulib/fs/include/fs/connection.h
+++ b/system/ulib/fs/include/fs/connection.h
@@ -57,6 +57,11 @@
private:
zx_status_t CallHandler();
+ // Sends an explicit close message to the underlying vnode.
+ // Only necessary if the handler has not returned ERR_DISPATCHER_DONE
+ // and has been opened.
+ void CallClose();
+
static zx_status_t HandleMessageThunk(zxrio_msg_t* msg, void* cookie);
zx_status_t HandleMessage(zxrio_msg_t* msg);
diff --git a/system/utest/fs/test-basic.c b/system/utest/fs/test-basic.c
index 300ca75..b8e2a11 100644
--- a/system/utest/fs/test-basic.c
+++ b/system/utest/fs/test-basic.c
@@ -9,7 +9,10 @@
#include <sys/stat.h>
#include <unistd.h>
+#include <fdio/limits.h>
+#include <fdio/util.h>
#include <unittest/unittest.h>
+#include <zircon/syscalls.h>
#include "filesystems.h"
@@ -68,6 +71,29 @@
END_TEST;
}
+bool test_unclean_close(void) {
+ BEGIN_TEST;
+
+ int fd = open("::foobar", O_CREAT | O_RDWR);
+ ASSERT_GT(fd, 0, "");
+
+ // Try closing a connection to a file with an "unclean" shutdown,
+ // noticed by the filesystem server as a closed handle rather than
+ // an explicit "Close" call.
+ zx_handle_t handles[FDIO_MAX_HANDLES];
+ uint32_t types[FDIO_MAX_HANDLES];
+ zx_status_t r = fdio_transfer_fd(fd, 0, handles, types);
+ ASSERT_GE(fd, 0, "");
+ for (size_t i = 0; i < (size_t) r; i++) {
+ ASSERT_EQ(zx_handle_close(handles[i]), ZX_OK, "");
+ }
+
+ ASSERT_EQ(unlink("::foobar"), 0, "");
+
+ END_TEST;
+}
+
RUN_FOR_ALL_FILESYSTEMS(basic_tests,
RUN_TEST_MEDIUM(test_basic)
+ RUN_TEST_MEDIUM(test_unclean_close)
)