Add {get,set}domainname(2)

{get,set}domainname aren't in POSIX but are widely-implemented
extensions.

The Linux kernel provides a setdomainname syscall but not a symmetric
getdomainname syscall, since it expects userspace to get the domain name
from uname(2).

Change-Id: I96726c242f4bb646c130b361688328b0b97269a0
Signed-off-by: Greg Hackmann <ghackmann@google.com>
diff --git a/tests/unistd_test.cpp b/tests/unistd_test.cpp
index 62e39fd..79b0fa5 100644
--- a/tests/unistd_test.cpp
+++ b/tests/unistd_test.cpp
@@ -25,6 +25,7 @@
 #include <fcntl.h>
 #include <limits.h>
 #include <stdint.h>
+#include <sys/capability.h>
 #include <sys/param.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
@@ -986,3 +987,46 @@
   ASSERT_EQ(file_size/2, lseek64(tf.fd, file_size/2, SEEK_SET));
   ASSERT_EQ(0, lockf64(tf.fd, F_TLOCK, file_size/2));
 }
+
+TEST(UNISTD_TEST, getdomainname) {
+  struct utsname u;
+  ASSERT_EQ(0, uname(&u));
+
+  char buf[sizeof(u.domainname)];
+  ASSERT_EQ(0, getdomainname(buf, sizeof(buf)));
+  EXPECT_STREQ(u.domainname, buf);
+
+#if defined(__BIONIC__)
+  // bionic and glibc have different behaviors when len is too small
+  ASSERT_EQ(-1, getdomainname(buf, strlen(u.domainname)));
+  EXPECT_EQ(EINVAL, errno);
+#endif
+}
+
+TEST(UNISTD_TEST, setdomainname) {
+  __user_cap_header_struct header;
+  memset(&header, 0, sizeof(header));
+  header.version = _LINUX_CAPABILITY_VERSION_3;
+
+  __user_cap_data_struct old_caps[_LINUX_CAPABILITY_U32S_3];
+  ASSERT_EQ(0, capget(&header, &old_caps[0]));
+
+  auto admin_idx = CAP_TO_INDEX(CAP_SYS_ADMIN);
+  auto admin_mask = CAP_TO_MASK(CAP_SYS_ADMIN);
+  bool has_admin = old_caps[admin_idx].effective & admin_mask;
+  if (has_admin) {
+    __user_cap_data_struct new_caps[_LINUX_CAPABILITY_U32S_3];
+    memcpy(new_caps, old_caps, sizeof(new_caps));
+    new_caps[admin_idx].effective &= ~admin_mask;
+
+    ASSERT_EQ(0, capset(&header, &new_caps[0])) << "failed to drop admin privileges";
+  }
+
+  const char* name = "newdomainname";
+  ASSERT_EQ(-1, setdomainname(name, strlen(name)));
+  ASSERT_EQ(EPERM, errno);
+
+  if (has_admin) {
+    ASSERT_EQ(0, capset(&header, &old_caps[0])) << "failed to restore admin privileges";
+  }
+}