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/libc/Android.mk b/libc/Android.mk
index 828a227..ffa8a27 100644
--- a/libc/Android.mk
+++ b/libc/Android.mk
@@ -109,6 +109,7 @@
     bionic/ftruncate.cpp \
     bionic/futimens.cpp \
     bionic/getcwd.cpp \
+    bionic/getdomainname.cpp \
     bionic/gethostname.cpp \
     bionic/getpgrp.cpp \
     bionic/getpid.cpp \
diff --git a/libc/SYSCALLS.TXT b/libc/SYSCALLS.TXT
index d5dd206..b774dbc 100644
--- a/libc/SYSCALLS.TXT
+++ b/libc/SYSCALLS.TXT
@@ -327,6 +327,7 @@
 int setfsgid(gid_t)  all
 int setfsuid(uid_t)  all
 
+int setdomainname(const char*, size_t)  all
 int sethostname(const char*, size_t)  all
 
 pid_t wait4(pid_t, int*, int, struct rusage*)  all
diff --git a/libc/arch-arm/syscalls/setdomainname.S b/libc/arch-arm/syscalls/setdomainname.S
new file mode 100644
index 0000000..20f1f6f
--- /dev/null
+++ b/libc/arch-arm/syscalls/setdomainname.S
@@ -0,0 +1,14 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(setdomainname)
+    mov     ip, r7
+    ldr     r7, =__NR_setdomainname
+    swi     #0
+    mov     r7, ip
+    cmn     r0, #(MAX_ERRNO + 1)
+    bxls    lr
+    neg     r0, r0
+    b       __set_errno_internal
+END(setdomainname)
diff --git a/libc/arch-arm64/syscalls/setdomainname.S b/libc/arch-arm64/syscalls/setdomainname.S
new file mode 100644
index 0000000..18f8d4b
--- /dev/null
+++ b/libc/arch-arm64/syscalls/setdomainname.S
@@ -0,0 +1,14 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(setdomainname)
+    mov     x8, __NR_setdomainname
+    svc     #0
+
+    cmn     x0, #(MAX_ERRNO + 1)
+    cneg    x0, x0, hi
+    b.hi    __set_errno_internal
+
+    ret
+END(setdomainname)
diff --git a/libc/arch-mips/syscalls/setdomainname.S b/libc/arch-mips/syscalls/setdomainname.S
new file mode 100644
index 0000000..ec0504c
--- /dev/null
+++ b/libc/arch-mips/syscalls/setdomainname.S
@@ -0,0 +1,19 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(setdomainname)
+    .set noreorder
+    .cpload t9
+    li v0, __NR_setdomainname
+    syscall
+    bnez a3, 1f
+    move a0, v0
+    j ra
+    nop
+1:
+    la t9,__set_errno_internal
+    j t9
+    nop
+    .set reorder
+END(setdomainname)
diff --git a/libc/arch-mips64/syscalls/setdomainname.S b/libc/arch-mips64/syscalls/setdomainname.S
new file mode 100644
index 0000000..6cee88e
--- /dev/null
+++ b/libc/arch-mips64/syscalls/setdomainname.S
@@ -0,0 +1,25 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(setdomainname)
+    .set push
+    .set noreorder
+    li v0, __NR_setdomainname
+    syscall
+    bnez a3, 1f
+    move a0, v0
+    j ra
+    nop
+1:
+    move t0, ra
+    bal     2f
+    nop
+2:
+    .cpsetup ra, t1, 2b
+    LA t9,__set_errno_internal
+    .cpreturn
+    j t9
+    move ra, t0
+    .set pop
+END(setdomainname)
diff --git a/libc/arch-x86/syscalls/setdomainname.S b/libc/arch-x86/syscalls/setdomainname.S
new file mode 100644
index 0000000..57189fe
--- /dev/null
+++ b/libc/arch-x86/syscalls/setdomainname.S
@@ -0,0 +1,26 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(setdomainname)
+    pushl   %ebx
+    .cfi_def_cfa_offset 8
+    .cfi_rel_offset ebx, 0
+    pushl   %ecx
+    .cfi_adjust_cfa_offset 4
+    .cfi_rel_offset ecx, 0
+    mov     12(%esp), %ebx
+    mov     16(%esp), %ecx
+    movl    $__NR_setdomainname, %eax
+    int     $0x80
+    cmpl    $-MAX_ERRNO, %eax
+    jb      1f
+    negl    %eax
+    pushl   %eax
+    call    __set_errno_internal
+    addl    $4, %esp
+1:
+    popl    %ecx
+    popl    %ebx
+    ret
+END(setdomainname)
diff --git a/libc/arch-x86_64/syscalls/setdomainname.S b/libc/arch-x86_64/syscalls/setdomainname.S
new file mode 100644
index 0000000..40d1a33
--- /dev/null
+++ b/libc/arch-x86_64/syscalls/setdomainname.S
@@ -0,0 +1,15 @@
+/* Generated by gensyscalls.py. Do not edit. */
+
+#include <private/bionic_asm.h>
+
+ENTRY(setdomainname)
+    movl    $__NR_setdomainname, %eax
+    syscall
+    cmpq    $-MAX_ERRNO, %rax
+    jb      1f
+    negl    %eax
+    movl    %eax, %edi
+    call    __set_errno_internal
+1:
+    ret
+END(setdomainname)
diff --git a/libc/bionic/getdomainname.cpp b/libc/bionic/getdomainname.cpp
new file mode 100644
index 0000000..761a999
--- /dev/null
+++ b/libc/bionic/getdomainname.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+
+int getdomainname(char* name, size_t len) {
+  utsname uts;
+  if (uname(&uts) == -1) return -1;
+
+  // Note: getdomainname()'s behavior varies across implementations when len is
+  // too small.  bionic follows the historical libc policy of returning EINVAL,
+  // instead of glibc's policy of copying the first len bytes without a NULL
+  // terminator.
+  if (strlen(uts.domainname) >= len) {
+      errno = EINVAL;
+      return -1;
+  }
+
+  strncpy(name, uts.domainname, len);
+  return 0;
+}
diff --git a/libc/include/unistd.h b/libc/include/unistd.h
index 5045267..2d62c1f 100644
--- a/libc/include/unistd.h
+++ b/libc/include/unistd.h
@@ -292,6 +292,9 @@
 __errordecl(__readlinkat_size_toobig_error, "readlinkat called with size > SSIZE_MAX");
 extern ssize_t __readlinkat_real(int dirfd, const char*, char*, size_t) __RENAME(readlinkat);
 
+extern int getdomainname(char*, size_t);
+extern int setdomainname(const char*, size_t);
+
 #if defined(__BIONIC_FORTIFY)
 
 __BIONIC_FORTIFY_INLINE
diff --git a/libc/libc.arm.brillo.map b/libc/libc.arm.brillo.map
index bb5aaa0..2776b91 100644
--- a/libc/libc.arm.brillo.map
+++ b/libc/libc.arm.brillo.map
@@ -1273,7 +1273,9 @@
 
 LIBC_O {
   global:
+    getdomainname;
     pthread_getname_np;
+    setdomainname;
 } LIBC_N;
 
 LIBC_PRIVATE {
diff --git a/libc/libc.arm.map b/libc/libc.arm.map
index e77471e..5f7ae2d 100644
--- a/libc/libc.arm.map
+++ b/libc/libc.arm.map
@@ -1273,7 +1273,9 @@
 
 LIBC_O {
   global:
+    getdomainname;
     pthread_getname_np;
+    setdomainname;
 } LIBC_N;
 
 LIBC_PRIVATE {
diff --git a/libc/libc.arm64.map b/libc/libc.arm64.map
index cef6d4a..0890b59 100644
--- a/libc/libc.arm64.map
+++ b/libc/libc.arm64.map
@@ -1195,7 +1195,9 @@
 
 LIBC_O {
   global:
+    getdomainname;
     pthread_getname_np;
+    setdomainname;
 } LIBC_N;
 
 LIBC_PRIVATE {
diff --git a/libc/libc.map.txt b/libc/libc.map.txt
index 1f523d7..38edff0 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -1299,7 +1299,9 @@
 
 LIBC_O {
   global:
+    getdomainname;
     pthread_getname_np;
+    setdomainname;
 } LIBC_N;
 
 LIBC_PRIVATE {
diff --git a/libc/libc.mips.brillo.map b/libc/libc.mips.brillo.map
index bdd9faa..c5b1bd3 100644
--- a/libc/libc.mips.brillo.map
+++ b/libc/libc.mips.brillo.map
@@ -1257,7 +1257,9 @@
 
 LIBC_O {
   global:
+    getdomainname;
     pthread_getname_np;
+    setdomainname;
 } LIBC_N;
 
 LIBC_PRIVATE {
diff --git a/libc/libc.mips.map b/libc/libc.mips.map
index 08a0a1c..dde4cb4 100644
--- a/libc/libc.mips.map
+++ b/libc/libc.mips.map
@@ -1257,7 +1257,9 @@
 
 LIBC_O {
   global:
+    getdomainname;
     pthread_getname_np;
+    setdomainname;
 } LIBC_N;
 
 LIBC_PRIVATE {
diff --git a/libc/libc.mips64.map b/libc/libc.mips64.map
index cef6d4a..0890b59 100644
--- a/libc/libc.mips64.map
+++ b/libc/libc.mips64.map
@@ -1195,7 +1195,9 @@
 
 LIBC_O {
   global:
+    getdomainname;
     pthread_getname_np;
+    setdomainname;
 } LIBC_N;
 
 LIBC_PRIVATE {
diff --git a/libc/libc.x86.brillo.map b/libc/libc.x86.brillo.map
index d50fd48..eb99079 100644
--- a/libc/libc.x86.brillo.map
+++ b/libc/libc.x86.brillo.map
@@ -1256,7 +1256,9 @@
 
 LIBC_O {
   global:
+    getdomainname;
     pthread_getname_np;
+    setdomainname;
 } LIBC_N;
 
 LIBC_PRIVATE {
diff --git a/libc/libc.x86.map b/libc/libc.x86.map
index 667ee63..8f6c64e 100644
--- a/libc/libc.x86.map
+++ b/libc/libc.x86.map
@@ -1256,7 +1256,9 @@
 
 LIBC_O {
   global:
+    getdomainname;
     pthread_getname_np;
+    setdomainname;
 } LIBC_N;
 
 LIBC_PRIVATE {
diff --git a/libc/libc.x86_64.map b/libc/libc.x86_64.map
index cef6d4a..0890b59 100644
--- a/libc/libc.x86_64.map
+++ b/libc/libc.x86_64.map
@@ -1195,7 +1195,9 @@
 
 LIBC_O {
   global:
+    getdomainname;
     pthread_getname_np;
+    setdomainname;
 } LIBC_N;
 
 LIBC_PRIVATE {
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";
+  }
+}