Merge "Add POSIX fexecve."
diff --git a/libc/bionic/exec.cpp b/libc/bionic/exec.cpp
index 2001106..1cf3a58 100644
--- a/libc/bionic/exec.cpp
+++ b/libc/bionic/exec.cpp
@@ -39,6 +39,8 @@
 #include <string.h>
 #include <unistd.h>
 
+#include "private/FdPath.h"
+
 extern "C" char** environ;
 
 enum ExecVariant { kIsExecL, kIsExecLE, kIsExecLP };
@@ -170,3 +172,10 @@
   if (saw_EACCES) errno = EACCES;
   return -1;
 }
+
+int fexecve(int fd, char* const* argv, char* const* envp) {
+  // execveat with AT_EMPTY_PATH (>= 3.19) seems to offer no advantages.
+  execve(FdPath(fd).c_str(), argv, envp);
+  if (errno == ENOENT) errno = EBADF;
+  return -1;
+}
diff --git a/libc/bionic/fchmod.cpp b/libc/bionic/fchmod.cpp
index ace8c6b..a486aae 100644
--- a/libc/bionic/fchmod.cpp
+++ b/libc/bionic/fchmod.cpp
@@ -33,13 +33,14 @@
 #include <unistd.h>
 #include <stdio.h>
 
+#include "private/FdPath.h"
+
 extern "C" int ___fchmod(int, mode_t);
 
 int fchmod(int fd, mode_t mode) {
   int saved_errno = errno;
   int result = ___fchmod(fd, mode);
-
-  if ((result == 0) || (errno != EBADF)) {
+  if (result == 0 || errno != EBADF) {
     return result;
   }
 
@@ -52,16 +53,14 @@
   // on an O_PATH file descriptor, and "man open" documents fchmod
   // on O_PATH file descriptors as returning EBADF.
   int fd_flag = fcntl(fd, F_GETFL);
-  if ((fd_flag == -1) || ((fd_flag & O_PATH) == 0)) {
+  if (fd_flag == -1 || (fd_flag & O_PATH) == 0) {
     errno = EBADF;
     return -1;
   }
 
-  char buf[40];
-  snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
   errno = saved_errno;
-  result = chmod(buf, mode);
-  if ((result == -1) && (errno == ELOOP)) {
+  result = chmod(FdPath(fd).c_str(), mode);
+  if (result == -1 && errno == ELOOP) {
     // Linux does not support changing the mode of a symlink.
     // For fchmodat(AT_SYMLINK_NOFOLLOW), POSIX requires a return
     // value of ENOTSUP. Assume that's true here too.
diff --git a/libc/bionic/fgetxattr.cpp b/libc/bionic/fgetxattr.cpp
index 6d999bf..38b7ac3 100644
--- a/libc/bionic/fgetxattr.cpp
+++ b/libc/bionic/fgetxattr.cpp
@@ -33,13 +33,15 @@
 #include <fcntl.h>
 #include <stdio.h>
 
+#include "private/FdPath.h"
+
 extern "C" ssize_t ___fgetxattr(int, const char*, void*, size_t);
 
 ssize_t fgetxattr(int fd, const char *name, void *value, size_t size) {
   int saved_errno = errno;
   ssize_t result = ___fgetxattr(fd, name, value, size);
 
-  if ((result != -1) || (errno != EBADF)) {
+  if (result != -1 || errno != EBADF) {
     return result;
   }
 
@@ -47,13 +49,11 @@
   // may not directly support fgetxattr() on such a file descriptor.
   // Use /proc/self/fd instead to emulate this support.
   int fd_flag = fcntl(fd, F_GETFL);
-  if ((fd_flag == -1) || ((fd_flag & O_PATH) == 0)) {
+  if (fd_flag == -1 || (fd_flag & O_PATH) == 0) {
     errno = EBADF;
     return -1;
   }
 
-  char buf[40];
-  snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
   errno = saved_errno;
-  return getxattr(buf, name, value, size);
+  return getxattr(FdPath(fd).c_str(), name, value, size);
 }
diff --git a/libc/bionic/flistxattr.cpp b/libc/bionic/flistxattr.cpp
index 05a96d2..8ad9b85 100644
--- a/libc/bionic/flistxattr.cpp
+++ b/libc/bionic/flistxattr.cpp
@@ -33,13 +33,14 @@
 #include <fcntl.h>
 #include <stdio.h>
 
+#include "private/FdPath.h"
+
 extern "C" ssize_t ___flistxattr(int, char*, size_t);
 
 ssize_t flistxattr(int fd, char *list, size_t size) {
   int saved_errno = errno;
   ssize_t result = ___flistxattr(fd, list, size);
-
-  if ((result != -1) || (errno != EBADF)) {
+  if (result != -1 || errno != EBADF) {
     return result;
   }
 
@@ -47,13 +48,11 @@
   // may not directly support fgetxattr() on such a file descriptor.
   // Use /proc/self/fd instead to emulate this support.
   int fd_flag = fcntl(fd, F_GETFL);
-  if ((fd_flag == -1) || ((fd_flag & O_PATH) == 0)) {
+  if (fd_flag == -1 || (fd_flag & O_PATH) == 0) {
     errno = EBADF;
     return -1;
   }
 
-  char buf[40];
-  snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
   errno = saved_errno;
-  return listxattr(buf, list, size);
+  return listxattr(FdPath(fd).c_str(), list, size);
 }
diff --git a/libc/bionic/fsetxattr.cpp b/libc/bionic/fsetxattr.cpp
index 6d2e868..9ad0c76 100644
--- a/libc/bionic/fsetxattr.cpp
+++ b/libc/bionic/fsetxattr.cpp
@@ -33,13 +33,14 @@
 #include <fcntl.h>
 #include <stdio.h>
 
+#include "private/FdPath.h"
+
 extern "C" int ___fsetxattr(int, const char*, const void*, size_t, int);
 
 int fsetxattr(int fd, const char* name, const void* value, size_t size, int flags) {
   int saved_errno = errno;
   int result = ___fsetxattr(fd, name, value, size, flags);
-
-  if ((result == 0) || (errno != EBADF)) {
+  if (result == 0 || errno != EBADF) {
     return result;
   }
 
@@ -47,13 +48,11 @@
   // may not directly support fsetxattr() on such a file descriptor.
   // Use /proc/self/fd instead to emulate this support.
   int fd_flag = fcntl(fd, F_GETFL);
-  if ((fd_flag == -1) || ((fd_flag & O_PATH) == 0)) {
+  if (fd_flag == -1 || (fd_flag & O_PATH) == 0) {
     errno = EBADF;
     return -1;
   }
 
-  char buf[40];
-  snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
   errno = saved_errno;
-  return setxattr(buf, name, value, size, flags);
+  return setxattr(FdPath(fd).c_str(), name, value, size, flags);
 }
diff --git a/libc/bionic/pty.cpp b/libc/bionic/pty.cpp
index bdabf36..599cbd2 100644
--- a/libc/bionic/pty.cpp
+++ b/libc/bionic/pty.cpp
@@ -37,6 +37,7 @@
 #include <utmp.h>
 
 #include "bionic/pthread_internal.h"
+#include "private/FdPath.h"
 
 int getpt() {
   return posix_openpt(O_RDWR|O_NOCTTY);
@@ -94,10 +95,7 @@
     return errno;
   }
 
-  char path[64];
-  snprintf(path, sizeof(path), "/proc/self/fd/%d", fd);
-
-  ssize_t count = readlink(path, buf, len);
+  ssize_t count = readlink(FdPath(fd).c_str(), buf, len);
   if (count == -1) {
     return errno;
   }
diff --git a/libc/include/unistd.h b/libc/include/unistd.h
index 9cfb918..fe98c10 100644
--- a/libc/include/unistd.h
+++ b/libc/include/unistd.h
@@ -98,6 +98,7 @@
 int execlp(const char* __file, const char* __arg0, ...) __attribute__((__sentinel__));
 int execle(const char* __path, const char* __arg0, ... /*,  char* const* __envp */)
     __attribute__((__sentinel__(1)));
+int fexecve(int __fd, char* const* __argv, char* const* __envp) __INTRODUCED_IN_FUTURE;
 
 int nice(int __incr);
 
diff --git a/libc/libc.arm.map b/libc/libc.arm.map
index 981dd59..af4efb9 100644
--- a/libc/libc.arm.map
+++ b/libc/libc.arm.map
@@ -1325,6 +1325,7 @@
     endhostent;
     endnetent;
     endprotoent;
+    fexecve;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.arm64.map b/libc/libc.arm64.map
index 29c5235..5c7f726 100644
--- a/libc/libc.arm64.map
+++ b/libc/libc.arm64.map
@@ -1245,6 +1245,7 @@
     endhostent;
     endnetent;
     endprotoent;
+    fexecve;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.map.txt b/libc/libc.map.txt
index eafbbd7..33ecbed 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -1350,6 +1350,7 @@
     endhostent;
     endnetent;
     endprotoent;
+    fexecve;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.mips.map b/libc/libc.mips.map
index a32131f..579491a 100644
--- a/libc/libc.mips.map
+++ b/libc/libc.mips.map
@@ -1309,6 +1309,7 @@
     endhostent;
     endnetent;
     endprotoent;
+    fexecve;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.mips64.map b/libc/libc.mips64.map
index 29c5235..5c7f726 100644
--- a/libc/libc.mips64.map
+++ b/libc/libc.mips64.map
@@ -1245,6 +1245,7 @@
     endhostent;
     endnetent;
     endprotoent;
+    fexecve;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.x86.map b/libc/libc.x86.map
index f1308ea..7d1d3ef 100644
--- a/libc/libc.x86.map
+++ b/libc/libc.x86.map
@@ -1307,6 +1307,7 @@
     endhostent;
     endnetent;
     endprotoent;
+    fexecve;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/libc.x86_64.map b/libc/libc.x86_64.map
index 29c5235..5c7f726 100644
--- a/libc/libc.x86_64.map
+++ b/libc/libc.x86_64.map
@@ -1245,6 +1245,7 @@
     endhostent;
     endnetent;
     endprotoent;
+    fexecve;
     getentropy;
     getnetent;
     getprotoent;
diff --git a/libc/private/FdPath.h b/libc/private/FdPath.h
new file mode 100644
index 0000000..4a6a2d5
--- /dev/null
+++ b/libc/private/FdPath.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+class FdPath {
+ public:
+  explicit FdPath(int fd) {
+    snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
+  }
+
+  const char* c_str() {
+    return buf;
+  }
+
+ private:
+  char buf[40];
+};
diff --git a/tests/unistd_test.cpp b/tests/unistd_test.cpp
index 81c6d92..c79ed59 100644
--- a/tests/unistd_test.cpp
+++ b/tests/unistd_test.cpp
@@ -1402,6 +1402,41 @@
               "<unknown>: usage: run-as");
 }
 
+TEST(UNISTD_TEST, fexecve_failure) {
+  ExecTestHelper eth;
+  errno = 0;
+  int fd = open("/", O_RDONLY);
+  ASSERT_NE(-1, fd);
+  ASSERT_EQ(-1, fexecve(fd, eth.GetArgs(), eth.GetEnv()));
+  ASSERT_EQ(EACCES, errno);
+  close(fd);
+}
+
+TEST(UNISTD_TEST, fexecve_bad_fd) {
+  ExecTestHelper eth;
+  errno = 0;
+  ASSERT_EQ(-1, fexecve(-1, eth.GetArgs(), eth.GetEnv()));
+  ASSERT_EQ(EBADF, errno);
+}
+
+TEST(UNISTD_TEST, fexecve_args) {
+  // Test basic argument passing.
+  int echo_fd = open(BIN_DIR "echo", O_RDONLY | O_CLOEXEC);
+  ASSERT_NE(-1, echo_fd);
+  ExecTestHelper eth;
+  eth.SetArgs({"echo", "hello", "world", nullptr});
+  eth.Run([&]() { fexecve(echo_fd, eth.GetArgs(), eth.GetEnv()); }, 0, "hello world\n");
+  close(echo_fd);
+
+  // Test environment variable setting too.
+  int printenv_fd = open(BIN_DIR "printenv", O_RDONLY | O_CLOEXEC);
+  ASSERT_NE(-1, printenv_fd);
+  eth.SetArgs({"printenv", nullptr});
+  eth.SetEnv({"A=B", nullptr});
+  eth.Run([&]() { fexecve(printenv_fd, eth.GetArgs(), eth.GetEnv()); }, 0, "A=B\n");
+  close(printenv_fd);
+}
+
 TEST(UNISTD_TEST, getlogin_r) {
   char buf[LOGIN_NAME_MAX] = {};
   EXPECT_EQ(ERANGE, getlogin_r(buf, 0));