diff --git a/adb/SOCKET-ACTIVATION.txt b/adb/SOCKET-ACTIVATION.txt new file mode 100644 index 000000000..4ef62ac9d --- /dev/null +++ b/adb/SOCKET-ACTIVATION.txt @@ -0,0 +1,42 @@ +adb can be configured to work with systemd-style socket activation, +allowing the daemon to start automatically when the adb control port +is forwarded across a network. You need two files, placed in the usual +systemd service directories (e.g., ~/.config/systemd/user for a user +service). + +adb.service: + +--- START adb.service CUT HERE --- +[Unit] +Description=adb +After=adb.socket +Requires=adb.socket +[Service] +Type=simple +# FD 3 is part of the systemd interface +ExecStart=/path/to/adb server nodaemon -L acceptfd:3 +--- END adb.service CUT HERE --- + +--- START adb.socket CUT HERE --- +[Unit] +Description=adb +PartOf=adb.service +[Socket] +ListenStream=127.0.0.1:5037 +Accept=no +[Install] +WantedBy=sockets.target +--- END adb.socket CUT HERE --- + +After installing the adb service, the adb server will be started +automatically on any connection to 127.0.0.1:5037 (the default adb +control port), even after adb kill-server kills the server. + +Other "superserver" launcher systems (like macOS launchd) can be +configured analogously. The important part is that adb be started with +"server" and "nodaemon" command line arguments and that the listen +address (passed to -L) name a file descriptor that's ready to +accept(2) connections and that's already bound to the desired address +and listening. inetd-style pre-accepted sockets do _not_ work in this +configuration: the file descriptor passed to acceptfd must be the +serve socket, not the accepted connection socket. diff --git a/adb/client/adb_client.cpp b/adb/client/adb_client.cpp index d91ae35ce..f724cb522 100644 --- a/adb/client/adb_client.cpp +++ b/adb/client/adb_client.cpp @@ -222,7 +222,7 @@ std::optional adb_get_server_executable_path() { int port; std::string error; if (!parse_tcp_socket_spec(__adb_server_socket_spec, nullptr, &port, nullptr, &error)) { - LOG(FATAL) << "failed to parse server socket spec: " << error; + return {}; } return adb_get_android_dir_path() + OS_PATH_SEPARATOR + "adb." + std::to_string(port); diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp index 0ffdbc28b..6465ffe6d 100644 --- a/adb/client/commandline.cpp +++ b/adb/client/commandline.cpp @@ -107,6 +107,7 @@ static void help() { " localfilesystem:\n" " dev:\n" " jdwp: (remote only)\n" + " acceptfd: (listen only)\n" " forward --remove LOCAL remove specific forward socket connection\n" " forward --remove-all remove all forward socket connections\n" " ppp TTY [PARAMETER...] run PPP over USB\n" diff --git a/adb/socket_spec.cpp b/adb/socket_spec.cpp index 27e8c4605..9ce443e36 100644 --- a/adb/socket_spec.cpp +++ b/adb/socket_spec.cpp @@ -16,6 +16,7 @@ #include "socket_spec.h" +#include #include #include #include @@ -28,10 +29,12 @@ #include #include "adb.h" +#include "adb_utils.h" #include "sysdeps.h" using namespace std::string_literals; +using android::base::ConsumePrefix; using android::base::StringPrintf; #if defined(__linux__) @@ -131,7 +134,7 @@ bool is_socket_spec(std::string_view spec) { return true; } } - return spec.starts_with("tcp:"); + return spec.starts_with("tcp:") || spec.starts_with("acceptfd:"); } bool is_local_socket_spec(std::string_view spec) { @@ -235,6 +238,9 @@ bool socket_spec_connect(unique_fd* fd, std::string_view address, int* port, std *error = "vsock is only supported on linux"; return false; #endif // ADB_LINUX + } else if (address.starts_with("acceptfd:")) { + *error = "cannot connect to acceptfd"; + return false; } for (const auto& it : kLocalSocketTypes) { @@ -334,6 +340,46 @@ int socket_spec_listen(std::string_view spec, std::string* error, int* resolved_ *error = "vsock is only supported on linux"; return -1; #endif // ADB_LINUX + } else if (ConsumePrefix(&spec, "acceptfd:")) { +#if ADB_WINDOWS + *error = "socket activation not supported under Windows"; + return -1; +#else + // We inherited the socket from some kind of launcher. It's already bound and + // listening. Return a copy of the FD instead of the FD itself so we implement the + // normal "listen" contract and can succeed more than once. + unsigned int fd_u; + if (!ParseUint(&fd_u, spec) || fd_u > std::numeric_limits::max()) { + *error = "invalid fd"; + return -1; + } + int fd = static_cast(fd_u); + int flags = get_fd_flags(fd); + if (flags < 0) { + *error = android::base::StringPrintf("could not get flags of inherited fd %d: '%s'", fd, + strerror(errno)); + return -1; + } + if (flags & FD_CLOEXEC) { + *error = android::base::StringPrintf("fd %d was not inherited from parent", fd); + return -1; + } + + int dummy_sock_type; + socklen_t dummy_sock_type_size = sizeof(dummy_sock_type); + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &dummy_sock_type, &dummy_sock_type_size)) { + *error = android::base::StringPrintf("fd %d does not refer to a socket", fd); + return -1; + } + + int new_fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); + if (new_fd < 0) { + *error = android::base::StringPrintf("could not dup inherited fd %d: '%s'", fd, + strerror(errno)); + return -1; + } + return new_fd; +#endif } for (const auto& it : kLocalSocketTypes) { diff --git a/adb/sysdeps.h b/adb/sysdeps.h index 466c2cedb..0c5a6b4c1 100644 --- a/adb/sysdeps.h +++ b/adb/sysdeps.h @@ -349,8 +349,15 @@ static __inline__ bool adb_is_separator(char c) { return c == '/'; } +static __inline__ int get_fd_flags(borrowed_fd fd) { + return fcntl(fd.get(), F_GETFD); +} + static __inline__ void close_on_exec(borrowed_fd fd) { - fcntl(fd.get(), F_SETFD, FD_CLOEXEC); + int flags = get_fd_flags(fd); + if (flags >= 0 && (flags & FD_CLOEXEC) == 0) { + fcntl(fd.get(), F_SETFD, flags | FD_CLOEXEC); + } } // Open a file and return a file descriptor that may be used with unix_read(),