Implement POSIX lockf.
This has been requested a few times over the years. This is basically
a very late rebase of https://android-review.googlesource.com/45470
which was abandoned years ago. One addition is that this version has
_FILE_OFFSET_BITS=64 support.
POSIX puts this in <unistd.h>. glibc also has it in <fcntl.h>.
Bug: http://b/13077650
Change-Id: I5862b1dc326e326c01ad92438ecc1578d19ba739
diff --git a/tests/unistd_test.cpp b/tests/unistd_test.cpp
index 5f412ce..5ef54a4 100644
--- a/tests/unistd_test.cpp
+++ b/tests/unistd_test.cpp
@@ -872,3 +872,132 @@
ASSERT_EQ(-1, dup2(fd, fd));
ASSERT_EQ(EBADF, errno);
}
+
+static void WaitForChildExit() {
+ int status;
+ wait(&status);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(0, WEXITSTATUS(status));
+}
+
+TEST(UNISTD_TEST, lockf_smoke) {
+ constexpr off64_t file_size = 32*1024LL;
+
+ TemporaryFile tf;
+ ASSERT_EQ(0, ftruncate(tf.fd, file_size));
+
+ // Lock everything.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_LOCK, file_size));
+
+ // Try-lock everything, this should succeed too.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_TLOCK, file_size));
+
+ // Check status.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_TEST, file_size));
+
+ // Unlock file.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_ULOCK, file_size));
+}
+
+TEST(UNISTD_TEST, lockf_zero) {
+ constexpr off64_t file_size = 32*1024LL;
+
+ TemporaryFile tf;
+ ASSERT_EQ(0, ftruncate(tf.fd, file_size));
+
+ // Lock everything by specifying a size of 0 (meaning "to the end, even if it changes").
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_LOCK, 0));
+
+ // Check that it's locked.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_TEST, file_size));
+
+ // Move the end.
+ ASSERT_EQ(0, ftruncate(tf.fd, 2*file_size));
+
+ // Check that the new section is locked too.
+ ASSERT_EQ(file_size, lseek64(tf.fd, file_size, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_TEST, 2*file_size));
+}
+
+TEST(UNISTD_TEST, lockf_negative) {
+ constexpr off64_t file_size = 32*1024LL;
+
+ TemporaryFile tf;
+ ASSERT_EQ(0, ftruncate(tf.fd, file_size));
+
+ // Lock everything, but specifying the range in reverse.
+ ASSERT_EQ(file_size, lseek64(tf.fd, file_size, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_LOCK, -file_size));
+
+ // Check that it's locked.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_TEST, file_size));
+}
+
+TEST(UNISTD_TEST, lockf_with_child) {
+ constexpr off64_t file_size = 32*1024LL;
+
+ TemporaryFile tf;
+ ASSERT_EQ(0, ftruncate(tf.fd, file_size));
+
+ // Lock everything.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_LOCK, file_size));
+
+ // Fork a child process
+ pid_t pid = fork();
+ ASSERT_NE(-1, pid);
+ if (pid == 0) {
+ // Check that the child cannot lock the file.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(-1, lockf64(tf.fd, F_TLOCK, file_size));
+ ASSERT_EQ(EAGAIN, errno);
+ // Check also that it reports itself as locked.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(-1, lockf64(tf.fd, F_TEST, file_size));
+ ASSERT_EQ(EACCES, errno);
+ _exit(0);
+ }
+ WaitForChildExit();
+}
+
+TEST(UNISTD_TEST, lockf_partial_with_child) {
+ constexpr off64_t file_size = 32*1024LL;
+
+ TemporaryFile tf;
+ ASSERT_EQ(0, ftruncate(tf.fd, file_size));
+
+ // Lock the first half of the file.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_LOCK, file_size/2));
+
+ // Fork a child process.
+ pid_t pid = fork();
+ ASSERT_NE(-1, pid);
+ if (pid == 0) {
+ // Check that the child can lock the other half.
+ ASSERT_EQ(file_size/2, lseek64(tf.fd, file_size/2, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_TLOCK, file_size/2));
+ // Check that the child cannot lock the first half.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(-1, lockf64(tf.fd, F_TEST, file_size/2));
+ ASSERT_EQ(EACCES, errno);
+ // Check also that it reports itself as locked.
+ ASSERT_EQ(0, lseek64(tf.fd, 0, SEEK_SET));
+ ASSERT_EQ(-1, lockf64(tf.fd, F_TEST, file_size/2));
+ ASSERT_EQ(EACCES, errno);
+ _exit(0);
+ }
+ WaitForChildExit();
+
+ // The second half was locked by the child, but the lock disappeared
+ // when the process exited, so check it can be locked now.
+ ASSERT_EQ(file_size/2, lseek64(tf.fd, file_size/2, SEEK_SET));
+ ASSERT_EQ(0, lockf64(tf.fd, F_TLOCK, file_size/2));
+}