Merge "Add the optimized implementation of 18 math functions for x86 and x86_64 respectively"
diff --git a/libc/Android.mk b/libc/Android.mk
index cb1d8c0..293b70e 100644
--- a/libc/Android.mk
+++ b/libc/Android.mk
@@ -1324,6 +1324,10 @@
 LOCAL_CFLAGS := $(libc_common_cflags)
 LOCAL_CONLYFLAGS := $(libc_common_conlyflags)
 LOCAL_CPPFLAGS := $(libc_common_cppflags)
+
+# TODO: This is to work around b/19059885. Remove after root cause is fixed
+LOCAL_LDFLAGS_arm := -Wl,--hash-style=both
+
 LOCAL_C_INCLUDES := $(libc_common_c_includes)
 LOCAL_SRC_FILES := \
     $(libc_arch_dynamic_src_files) \
@@ -1474,6 +1478,10 @@
 LOCAL_C_INCLUDES := $(libc_common_c_includes) bionic/libstdc++/include
 LOCAL_CFLAGS := $(libc_common_cflags)
 LOCAL_CPPFLAGS := $(libc_common_cppflags)
+
+# TODO: This is to work around b/19059885. Remove after root cause is fixed
+LOCAL_LDFLAGS_arm := -Wl,--hash-style=both
+
 LOCAL_SRC_FILES := $(libstdcxx_common_src_files)
 LOCAL_MODULE:= libstdc++
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
diff --git a/libc/dns/resolv/res_send.c b/libc/dns/resolv/res_send.c
index 6439e31..a8da3ac 100644
--- a/libc/dns/resolv/res_send.c
+++ b/libc/dns/resolv/res_send.c
@@ -402,6 +402,10 @@
 	}
 
 	if (statp->nscount == 0) {
+		// We have no nameservers configured, so there's no point trying.
+		// Tell the cache the query failed, or any retries and anyone else asking the same
+		// question will block for PENDING_REQUEST_TIMEOUT seconds instead of failing fast.
+		_resolv_cache_query_failed(statp->netid, buf, buflen);
 		errno = ESRCH;
 		return (-1);
 	}
diff --git a/libc/include/sys/mount.h b/libc/include/sys/mount.h
index 3c35d31..fd7cf17 100644
--- a/libc/include/sys/mount.h
+++ b/libc/include/sys/mount.h
@@ -25,6 +25,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+
 #ifndef _SYS_MOUNT_H
 #define _SYS_MOUNT_H
 
@@ -35,9 +36,10 @@
 __BEGIN_DECLS
 
 /* umount2 flags. */
-#define MNT_FORCE	1	/* Forcibly unmount */
-#define MNT_DETACH	2	/* Detach from tree only */
-#define MNT_EXPIRE	4	/* Mark for expiry */
+#define MNT_FORCE 1
+#define MNT_DETACH 2
+#define MNT_EXPIRE 4
+#define UMOUNT_NOFOLLOW 8
 
 extern int mount(const char*, const char*, const char*, unsigned long, const void*);
 extern int umount(const char*);
diff --git a/libc/include/syslog.h b/libc/include/syslog.h
index cbceab2..8000f03 100644
--- a/libc/include/syslog.h
+++ b/libc/include/syslog.h
@@ -47,6 +47,7 @@
 
 #define LOG_PRIMASK 7
 #define LOG_PRI(x) ((x) & LOG_PRIMASK)
+#define LOG_MAKEPRI(fac, pri) ((fac) | (pri))
 
 /* Facilities are currently ignored on Android. */
 #define LOG_KERN     0000
diff --git a/libm/Android.mk b/libm/Android.mk
index 565d3fd..529dda8 100644
--- a/libm/Android.mk
+++ b/libm/Android.mk
@@ -513,6 +513,9 @@
 # -----------------------------------------------------------------------------
 include $(CLEAR_VARS)
 
+# TODO: This is to work around b/19059885. Remove after root cause is fixed
+LOCAL_LDFLAGS_arm := -Wl,--hash-style=both
+
 LOCAL_MODULE := libm
 LOCAL_CLANG := $(libm_clang)
 LOCAL_SYSTEM_SHARED_LIBRARIES := libc
diff --git a/linker/Android.mk b/linker/Android.mk
index 54535fc..0ab0fda 100644
--- a/linker/Android.mk
+++ b/linker/Android.mk
@@ -6,7 +6,7 @@
     debugger.cpp \
     dlfcn.cpp \
     linker.cpp \
-    linker_allocator.cpp \
+    linker_block_allocator.cpp \
     linker_environ.cpp \
     linker_libc_support.c \
     linker_phdr.cpp \
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 9ba83ec..ea7d637 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -50,7 +50,7 @@
 #include "private/UniquePtr.h"
 
 #include "linker.h"
-#include "linker_allocator.h"
+#include "linker_block_allocator.h"
 #include "linker_debug.h"
 #include "linker_environ.h"
 #include "linker_leb128.h"
@@ -92,8 +92,8 @@
 
 static ElfW(Addr) get_elf_exec_load_bias(const ElfW(Ehdr)* elf);
 
-static LinkerAllocator<soinfo> g_soinfo_allocator;
-static LinkerAllocator<LinkedListEntry<soinfo>> g_soinfo_links_allocator;
+static LinkerTypeAllocator<soinfo> g_soinfo_allocator;
+static LinkerTypeAllocator<LinkedListEntry<soinfo>> g_soinfo_links_allocator;
 
 static soinfo* solist;
 static soinfo* sonext;
@@ -421,26 +421,41 @@
   uint32_t word_num = (hash / bloom_mask_bits) & gnu_maskwords_;
   ElfW(Addr) bloom_word = gnu_bloom_filter_[word_num];
 
+  TRACE_TYPE(LOOKUP, "SEARCH %s in %s@%p (gnu)",
+      symbol_name.get_name(), name, reinterpret_cast<void*>(base));
+
   // test against bloom filter
   if ((1 & (bloom_word >> (hash % bloom_mask_bits)) & (bloom_word >> (h2 % bloom_mask_bits))) == 0) {
+    TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p",
+        symbol_name.get_name(), name, reinterpret_cast<void*>(base));
+
     return nullptr;
   }
 
   // bloom test says "probably yes"...
-  uint32_t n = bucket_[hash % nbucket_];
+  uint32_t n = gnu_bucket_[hash % gnu_nbucket_];
 
   if (n == 0) {
+    TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p",
+        symbol_name.get_name(), name, reinterpret_cast<void*>(base));
+
     return nullptr;
   }
 
   do {
     ElfW(Sym)* s = symtab_ + n;
-    if (((chain_[n] ^ hash) >> 1) == 0 &&
+    if (((gnu_chain_[n] ^ hash) >> 1) == 0 &&
         strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 &&
         is_symbol_global_and_defined(this, s)) {
+      TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd",
+          symbol_name.get_name(), name, reinterpret_cast<void*>(s->st_value),
+          static_cast<size_t>(s->st_size));
       return s;
     }
-  } while ((chain_[n++] & 1) == 0);
+  } while ((gnu_chain_[n++] & 1) == 0);
+
+  TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p",
+             symbol_name.get_name(), name, reinterpret_cast<void*>(base));
 
   return nullptr;
 }
@@ -802,8 +817,8 @@
 ElfW(Sym)* soinfo::gnu_addr_lookup(const void* addr) {
   ElfW(Addr) soaddr = reinterpret_cast<ElfW(Addr)>(addr) - load_bias;
 
-  for (size_t i = 0; i < nbucket_; ++i) {
-    uint32_t n = bucket_[i];
+  for (size_t i = 0; i < gnu_nbucket_; ++i) {
+    uint32_t n = gnu_bucket_[i];
 
     if (n == 0) {
       continue;
@@ -814,7 +829,7 @@
       if (symbol_matches_soaddr(sym, soaddr)) {
         return sym;
       }
-    } while ((chain_[n++] & 1) == 0);
+    } while ((gnu_chain_[n++] & 1) == 0);
   }
 
   return nullptr;
@@ -1941,11 +1956,6 @@
         break;
 
       case DT_HASH:
-        if (nbucket_ != 0) {
-          // in case of --hash-style=both, we prefer gnu
-          break;
-        }
-
         nbucket_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[0];
         nchain_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[1];
         bucket_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr + 8);
@@ -1953,20 +1963,15 @@
         break;
 
       case DT_GNU_HASH:
-        if (nbucket_ != 0) {
-          // in case of --hash-style=both, we prefer gnu
-          nchain_ = 0;
-        }
-
-        nbucket_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[0];
+        gnu_nbucket_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[0];
         // skip symndx
         gnu_maskwords_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[2];
         gnu_shift2_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[3];
 
         gnu_bloom_filter_ = reinterpret_cast<ElfW(Addr)*>(load_bias + d->d_un.d_ptr + 16);
-        bucket_ = reinterpret_cast<uint32_t*>(gnu_bloom_filter_ + gnu_maskwords_);
+        gnu_bucket_ = reinterpret_cast<uint32_t*>(gnu_bloom_filter_ + gnu_maskwords_);
         // amend chain for symndx = header[1]
-        chain_ = bucket_ + nbucket_ - reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[1];
+        gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[1];
 
         if (!powerof2(gnu_maskwords_)) {
           DL_ERR("invalid maskwords for gnu_hash = 0x%x, in \"%s\" expecting power to two", gnu_maskwords_, name);
@@ -2278,7 +2283,7 @@
     DL_ERR("linker cannot have DT_NEEDED dependencies on other libraries");
     return false;
   }
-  if (nbucket_ == 0) {
+  if (nbucket_ == 0 && gnu_nbucket_ == 0) {
     DL_ERR("empty/missing DT_HASH/DT_GNU_HASH in \"%s\" (new hash type from the future?)", name);
     return false;
   }
diff --git a/linker/linker.h b/linker/linker.h
index f8640a0..e4681eb 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -309,6 +309,10 @@
   size_t strtab_size_;
 
   // version >= 2
+
+  size_t gnu_nbucket_;
+  uint32_t* gnu_bucket_;
+  uint32_t* gnu_chain_;
   uint32_t gnu_maskwords_;
   uint32_t gnu_shift2_;
   ElfW(Addr)* gnu_bloom_filter_;
diff --git a/linker/linker_allocator.cpp b/linker/linker_block_allocator.cpp
similarity index 81%
rename from linker/linker_allocator.cpp
rename to linker/linker_block_allocator.cpp
index ac11b97..fc9a75b 100644
--- a/linker/linker_allocator.cpp
+++ b/linker/linker_block_allocator.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "linker_allocator.h"
+#include "linker_block_allocator.h"
 #include <inttypes.h>
 #include <string.h>
 #include <sys/mman.h>
@@ -22,9 +22,9 @@
 
 #include "private/bionic_prctl.h"
 
-struct LinkerAllocatorPage {
-  LinkerAllocatorPage* next;
-  uint8_t bytes[PAGE_SIZE-sizeof(LinkerAllocatorPage*)];
+struct LinkerBlockAllocatorPage {
+  LinkerBlockAllocatorPage* next;
+  uint8_t bytes[PAGE_SIZE-sizeof(LinkerBlockAllocatorPage*)];
 };
 
 struct FreeBlockInfo {
@@ -64,7 +64,7 @@
     return;
   }
 
-  LinkerAllocatorPage* page = find_page(block);
+  LinkerBlockAllocatorPage* page = find_page(block);
 
   if (page == nullptr) {
     abort();
@@ -87,7 +87,7 @@
 }
 
 void LinkerBlockAllocator::protect_all(int prot) {
-  for (LinkerAllocatorPage* page = page_list_; page != nullptr; page = page->next) {
+  for (LinkerBlockAllocatorPage* page = page_list_; page != nullptr; page = page->next) {
     if (mprotect(page, PAGE_SIZE, prot) == -1) {
       abort();
     }
@@ -95,8 +95,9 @@
 }
 
 void LinkerBlockAllocator::create_new_page() {
-  LinkerAllocatorPage* page = reinterpret_cast<LinkerAllocatorPage*>(mmap(nullptr, PAGE_SIZE,
-      PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0));
+  LinkerBlockAllocatorPage* page = reinterpret_cast<LinkerBlockAllocatorPage*>(
+      mmap(nullptr, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0));
+
   if (page == MAP_FAILED) {
     abort(); // oom
   }
@@ -107,7 +108,7 @@
 
   FreeBlockInfo* first_block = reinterpret_cast<FreeBlockInfo*>(page->bytes);
   first_block->next_block = free_block_list_;
-  first_block->num_free_blocks = (PAGE_SIZE - sizeof(LinkerAllocatorPage*))/block_size_;
+  first_block->num_free_blocks = (PAGE_SIZE - sizeof(LinkerBlockAllocatorPage*))/block_size_;
 
   free_block_list_ = first_block;
 
@@ -115,12 +116,12 @@
   page_list_ = page;
 }
 
-LinkerAllocatorPage* LinkerBlockAllocator::find_page(void* block) {
+LinkerBlockAllocatorPage* LinkerBlockAllocator::find_page(void* block) {
   if (block == nullptr) {
     abort();
   }
 
-  LinkerAllocatorPage* page = page_list_;
+  LinkerBlockAllocatorPage* page = page_list_;
   while (page != nullptr) {
     const uint8_t* page_ptr = reinterpret_cast<const uint8_t*>(page);
     if (block >= (page_ptr + sizeof(page->next)) && block < (page_ptr + PAGE_SIZE)) {
diff --git a/linker/linker_allocator.h b/linker/linker_block_allocator.h
similarity index 87%
rename from linker/linker_allocator.h
rename to linker/linker_block_allocator.h
index 5d3563f..1d41806 100644
--- a/linker/linker_allocator.h
+++ b/linker/linker_block_allocator.h
@@ -21,7 +21,7 @@
 #include <limits.h>
 #include "private/bionic_macros.h"
 
-struct LinkerAllocatorPage;
+struct LinkerBlockAllocatorPage;
 
 /*
  * This class is a non-template version of the LinkerAllocator
@@ -40,10 +40,10 @@
 
  private:
   void create_new_page();
-  LinkerAllocatorPage* find_page(void* block);
+  LinkerBlockAllocatorPage* find_page(void* block);
 
   size_t block_size_;
-  LinkerAllocatorPage* page_list_;
+  LinkerBlockAllocatorPage* page_list_;
   void* free_block_list_;
 
   DISALLOW_COPY_AND_ASSIGN(LinkerBlockAllocator);
@@ -57,14 +57,15 @@
  * anonymous mmaps.
  */
 template<typename T>
-class LinkerAllocator {
+class LinkerTypeAllocator {
  public:
-  LinkerAllocator() : block_allocator_(sizeof(T)) {}
+  LinkerTypeAllocator() : block_allocator_(sizeof(T)) {}
   T* alloc() { return reinterpret_cast<T*>(block_allocator_.alloc()); }
   void free(T* t) { block_allocator_.free(t); }
   void protect_all(int prot) { block_allocator_.protect_all(prot); }
  private:
   LinkerBlockAllocator block_allocator_;
-  DISALLOW_COPY_AND_ASSIGN(LinkerAllocator);
+  DISALLOW_COPY_AND_ASSIGN(LinkerTypeAllocator);
 };
+
 #endif // __LINKER_ALLOCATOR_H
diff --git a/linker/tests/Android.mk b/linker/tests/Android.mk
index aa9491e..9a08bec 100644
--- a/linker/tests/Android.mk
+++ b/linker/tests/Android.mk
@@ -28,7 +28,7 @@
 
 LOCAL_SRC_FILES := \
   linked_list_test.cpp \
-  linker_allocator_test.cpp \
-  ../linker_allocator.cpp
+  linker_block_allocator_test.cpp \
+  ../linker_block_allocator.cpp
 
 include $(BUILD_NATIVE_TEST)
diff --git a/linker/tests/linker_allocator_test.cpp b/linker/tests/linker_block_allocator_test.cpp
similarity index 92%
rename from linker/tests/linker_allocator_test.cpp
rename to linker/tests/linker_block_allocator_test.cpp
index 9292a05..3ef0f36 100644
--- a/linker/tests/linker_allocator_test.cpp
+++ b/linker/tests/linker_block_allocator_test.cpp
@@ -20,7 +20,7 @@
 
 #include <gtest/gtest.h>
 
-#include "../linker_allocator.h"
+#include "../linker_block_allocator.h"
 
 #include <unistd.h>
 
@@ -49,7 +49,7 @@
 };
 
 TEST(linker_allocator, test_nominal) {
-  LinkerAllocator<test_struct_nominal> allocator;
+  LinkerTypeAllocator<test_struct_nominal> allocator;
 
   test_struct_nominal* ptr1 = allocator.alloc();
   ASSERT_TRUE(ptr1 != nullptr);
@@ -65,7 +65,7 @@
 }
 
 TEST(linker_allocator, test_small) {
-  LinkerAllocator<test_struct_small> allocator;
+  LinkerTypeAllocator<test_struct_small> allocator;
 
   char* ptr1 = reinterpret_cast<char*>(allocator.alloc());
   char* ptr2 = reinterpret_cast<char*>(allocator.alloc());
@@ -76,7 +76,7 @@
 }
 
 TEST(linker_allocator, test_larger) {
-  LinkerAllocator<test_struct_larger> allocator;
+  LinkerTypeAllocator<test_struct_larger> allocator;
 
   test_struct_larger* ptr1 = allocator.alloc();
   test_struct_larger* ptr2 = allocator.alloc();
@@ -99,7 +99,7 @@
 }
 
 static void protect_all() {
-  LinkerAllocator<test_struct_larger> allocator;
+  LinkerTypeAllocator<test_struct_larger> allocator;
 
   // number of allocs to reach the end of first page
   size_t n = kPageSize/sizeof(test_struct_larger) - 1;
diff --git a/tests/gtest_main.cpp b/tests/gtest_main.cpp
index b1953fc..bf2b695 100644
--- a/tests/gtest_main.cpp
+++ b/tests/gtest_main.cpp
@@ -741,7 +741,8 @@
 
 // We choose to use multi-fork and multi-wait here instead of multi-thread, because it always
 // makes deadlock to use fork in multi-thread.
-static void RunTestInSeparateProc(int argc, char** argv, std::vector<TestCase>& testcase_list,
+// Returns true if all tests run successfully, otherwise return false.
+static bool RunTestInSeparateProc(int argc, char** argv, std::vector<TestCase>& testcase_list,
                                   size_t iteration_count, size_t job_count,
                                   const std::string& xml_output_filename) {
   // Stop default result printer to avoid environment setup/teardown information for each test.
@@ -759,6 +760,8 @@
     exit(1);
   }
 
+  bool all_tests_passed = true;
+
   for (size_t iteration = 1; iteration <= iteration_count; ++iteration) {
     OnTestIterationStartPrint(testcase_list, iteration, iteration_count);
     int64_t iteration_start_time_ns = NanoTime();
@@ -806,6 +809,9 @@
           if (++finished_test_count_list[testcase_id] == testcase.TestCount()) {
             ++finished_testcase_count;
           }
+          if (testcase.GetTestResult(test_id) != TEST_SUCCESS) {
+            all_tests_passed = false;
+          }
 
           it = child_proc_list.erase(it);
         } else {
@@ -827,6 +833,8 @@
     perror("sigprocmask SIG_SETMASK");
     exit(1);
   }
+
+  return all_tests_passed;
 }
 
 static size_t GetProcessorCount() {
@@ -1061,15 +1069,15 @@
     if (EnumerateTests(argc, arg_list.data(), testcase_list) == false) {
       return 1;
     }
-    RunTestInSeparateProc(argc, arg_list.data(), testcase_list, options.gtest_repeat,
-                          options.job_count, options.gtest_output);
+    bool all_test_passed =  RunTestInSeparateProc(argc, arg_list.data(), testcase_list,
+                              options.gtest_repeat, options.job_count, options.gtest_output);
+    return all_test_passed ? 0 : 1;
   } else {
     argc = static_cast<int>(arg_list.size());
     arg_list.push_back(NULL);
     testing::InitGoogleTest(&argc, arg_list.data());
     return RUN_ALL_TESTS();
   }
-  return 0;
 }
 
 //################################################################################
diff --git a/tests/sys_stat_test.cpp b/tests/sys_stat_test.cpp
index 28c7c52..7c387ba 100644
--- a/tests/sys_stat_test.cpp
+++ b/tests/sys_stat_test.cpp
@@ -138,13 +138,18 @@
 #endif
 }
 
+static void AssertFileModeEquals(mode_t expected_mode, const char* filename) {
+  struct stat sb;
+  ASSERT_EQ(0, stat(filename, &sb));
+  mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO;
+  ASSERT_EQ(expected_mode & mask, static_cast<mode_t>(sb.st_mode) & mask);
+}
+
 TEST(sys_stat, fchmodat_file) {
   TemporaryFile tf;
-  struct stat sb;
 
   ASSERT_EQ(0, fchmodat(AT_FDCWD, tf.filename, 0751, 0));
-  ASSERT_EQ(0, fstat(tf.fd, &sb));
-  ASSERT_TRUE(0751 == (sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)));
+  AssertFileModeEquals(0751, tf.filename);
 }
 
 TEST(sys_stat, fchmodat_AT_SYMLINK_NOFOLLOW_file) {
@@ -153,11 +158,9 @@
   int result = fchmodat(AT_FDCWD, tf.filename, 0751, AT_SYMLINK_NOFOLLOW);
 
 #if defined(__BIONIC__)
-  struct stat sb;
   ASSERT_EQ(0, result);
   ASSERT_EQ(0, errno);
-  ASSERT_EQ(0, fstat(tf.fd, &sb));
-  ASSERT_TRUE(0751 == (sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)));
+  AssertFileModeEquals(0751, tf.filename);
 #else
   // glibc 2.19 does not implement AT_SYMLINK_NOFOLLOW and always
   // returns ENOTSUP
@@ -169,14 +172,12 @@
 TEST(sys_stat, fchmodat_symlink) {
   TemporaryFile tf;
   char linkname[255];
-  struct stat sb;
 
   snprintf(linkname, sizeof(linkname), "%s.link", tf.filename);
 
   ASSERT_EQ(0, symlink(tf.filename, linkname));
   ASSERT_EQ(0, fchmodat(AT_FDCWD, linkname, 0751, 0));
-  ASSERT_EQ(0, fstat(tf.fd, &sb));
-  ASSERT_TRUE(0751 == (sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)));
+  AssertFileModeEquals(0751, tf.filename);
   unlink(linkname);
 }
 
@@ -194,29 +195,54 @@
   unlink(linkname);
 }
 
+static void AssertSymlinkModeEquals(mode_t expected_mode, const char* linkname) {
+  struct stat sb;
+  ASSERT_EQ(0, fstatat(AT_FDCWD, linkname, &sb, AT_SYMLINK_NOFOLLOW));
+  mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO;
+  ASSERT_EQ(expected_mode & mask, static_cast<mode_t>(sb.st_mode) & mask);
+}
+
 TEST(sys_stat, fchmodat_AT_SYMLINK_NOFOLLOW_with_symlink) {
   TemporaryFile tf;
-  char linkname[255];
+  struct stat tf_sb;
+  ASSERT_EQ(0, stat(tf.filename, &tf_sb));
 
+  char linkname[255];
   snprintf(linkname, sizeof(linkname), "%s.link", tf.filename);
 
   ASSERT_EQ(0, symlink(tf.filename, linkname));
-  ASSERT_EQ(-1, fchmodat(AT_FDCWD, linkname, 0751, AT_SYMLINK_NOFOLLOW));
-  ASSERT_EQ(ENOTSUP, errno);
+  int result = fchmodat(AT_FDCWD, linkname, 0751, AT_SYMLINK_NOFOLLOW);
+  // It depends on the kernel whether chmod operation on symlink is allowed.
+  if (result == 0) {
+    AssertSymlinkModeEquals(0751, linkname);
+  } else {
+    ASSERT_EQ(-1, result);
+    ASSERT_EQ(ENOTSUP, errno);
+  }
+
+  // Target file mode shouldn't be modified.
+  AssertFileModeEquals(tf_sb.st_mode, tf.filename);
   unlink(linkname);
 }
 
 TEST(sys_stat, fchmodat_AT_SYMLINK_NOFOLLOW_with_dangling_symlink) {
   TemporaryFile tf;
+
   char linkname[255];
   char target[255];
-
   snprintf(linkname, sizeof(linkname), "%s.link", tf.filename);
   snprintf(target, sizeof(target), "%s.doesnotexist", tf.filename);
 
   ASSERT_EQ(0, symlink(target, linkname));
-  ASSERT_EQ(-1, fchmodat(AT_FDCWD, linkname, 0751, AT_SYMLINK_NOFOLLOW));
-  ASSERT_EQ(ENOTSUP, errno);
+  int result = fchmodat(AT_FDCWD, linkname, 0751, AT_SYMLINK_NOFOLLOW);
+  // It depends on the kernel whether chmod operation on symlink is allowed.
+  if (result == 0) {
+    AssertSymlinkModeEquals(0751, linkname);
+  } else {
+    ASSERT_EQ(-1, result);
+    ASSERT_EQ(ENOTSUP, errno);
+  }
+
   unlink(linkname);
 }