diff --git a/.github/workflows/artifacts.yaml b/.github/workflows/artifacts.yaml new file mode 100644 index 0000000000..0146a73470 --- /dev/null +++ b/.github/workflows/artifacts.yaml @@ -0,0 +1,52 @@ +name: Artifacts +on: + pull_request: + paths: + - '.github/workflows/artifacts.yaml' + - 'tools/buildimage/**' + push: + branches: + - main +jobs: + export-debs: + runs-on: ubuntu-22.04 + container: + image: debian@sha256:4cb3f4198e4af2d03dffe6bfa4f3686773596494ef298f3882553d52e885634b # debian:bullseye-20240110 + steps: + - name: apt update + run: apt update -y && apt upgrade -y + - name: install sudo + run: apt install -y sudo + - name: Checkout repository + uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675 # aka v2 + - name: Build debs + run: bash build_debs.sh + - name: Build debs.tar + run: find . -name 'cuttlefish-*.deb' -print0 | tar -cvf debs.tar --null --files-from - + - name: Publish debs.tar + uses: actions/upload-artifact@v3 + with: + name: debs + path: debs.tar + export-gce-image: + needs: export-debs + runs-on: ubuntu-22.04 + steps: + - name: Free space + run: rm -rf /opt/hostedtoolcache + - name: Checkout repository + uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675 # aka v2 + - name: Download debs.tar + uses: actions/download-artifact@v3 + with: + name: debs + - name: Untar debs.tar + run: tar -xf debs.tar + - name: Build image + run: bash tools/buildimage/main.sh + - name: Publish image + uses: actions/upload-artifact@v3 + with: + name: image_gce_debian11_amd64 + path: image.tar.gz + diff --git a/.github/workflows/presubmit.yaml b/.github/workflows/presubmit.yaml index fbaaa76f57..97d1f1bbfa 100644 --- a/.github/workflows/presubmit.yaml +++ b/.github/workflows/presubmit.yaml @@ -16,7 +16,7 @@ jobs: - name: Vet Test Build run: cd frontend/src/host_orchestrator && go vet ./... && go test ./... && go build ./... build-debian-package: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 if: ${{ always() && needs.build-orchestrator.result == 'success' }} needs: [build-orchestrator] container: @@ -35,6 +35,8 @@ jobs: uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675 # aka v2 with: path: "android-cuttlefish" + - name: install package build dependencies + run: cd android-cuttlefish/base && mk-build-deps -i -t 'apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends -y' - name: build base debian package run: cd android-cuttlefish/base && debuild -i -us -uc -b - name: install base debian package @@ -43,3 +45,20 @@ jobs: run: cd android-cuttlefish/frontend && debuild -i -us -uc -b - name: install user debian package run: dpkg -i android-cuttlefish/cuttlefish-user_*_*64.deb || apt-get install -f -y + build-cvd: + runs-on: ubuntu-latest + container: + image: debian@sha256:3b6053ca925336c804e2d3f080af177efcdc9f51198a627569bfc7c7e730ef7e + steps: + - name: Checkout repository + uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675 # aka v2 + - name: Setup apt + run: apt update -y && apt upgrade -y + - name: Install dependencies + run: apt install -y clang pkg-config meson libfmt-dev libgflags-dev libjsoncpp-dev protobuf-compiler libgtest-dev libcurl4-openssl-dev libprotobuf-c-dev libgoogle-glog-dev libssl-dev libxml2-dev openssl uuid-dev + - name: Setup meson + run: mkdir base/cvd/build && cd base/cvd/build && meson setup + - name: Build cvd + run: cd base/cvd/build && meson compile + - name: Test cvd + run: cd base/cvd/build && meson test -v diff --git a/.kokoro/continuous.cfg b/.kokoro/continuous.cfg new file mode 100644 index 0000000000..22a9dfa9d6 --- /dev/null +++ b/.kokoro/continuous.cfg @@ -0,0 +1 @@ +build_file: "android-cuttlefish/.kokoro/public_test.sh" diff --git a/.kokoro/presubmit.cfg b/.kokoro/presubmit.cfg new file mode 100644 index 0000000000..22a9dfa9d6 --- /dev/null +++ b/.kokoro/presubmit.cfg @@ -0,0 +1 @@ +build_file: "android-cuttlefish/.kokoro/public_test.sh" diff --git a/.kokoro/public_test.sh b/.kokoro/public_test.sh new file mode 100755 index 0000000000..d091ab8a33 --- /dev/null +++ b/.kokoro/public_test.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Fail on errors +set -e + +echo "Cuttlefish debian package build and testing placeholder script for kokoro" diff --git a/README.md b/README.md index 530af9fe43..ba343f08f1 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ The packages can be built with the following command: ```bash for dir in base frontend; do pushd $dir + # Install build dependencies + sudo mk-build-deps -i dpkg-buildpackage -uc -us popd done diff --git a/allocd-port/CMakeLists.txt b/allocd-port/CMakeLists.txt deleted file mode 100644 index 98307e7871..0000000000 --- a/allocd-port/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright (C) 2023 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. -# - -cmake_minimum_required(VERSION 3.18) - -set(CMAKE_CXX_COMPILER "clang++") -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -project(allocd_separation) - -set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) -set(PROJECT_INCLUDE_DIR ${PROJECT_ROOT}/include) -set(PROJECT_LIBRARY_DIR ${PROJECT_ROOT}/lib) -set(PROJECT_BIN_DIR ${PROJECT_ROOT}/bin) -include_directories(${PROJECT_INCLUDE_DIR}) - -find_package(fmt REQUIRED) -find_package(glog REQUIRED) - -add_subdirectory(srcs) diff --git a/allocd-port/README.md b/allocd-port/README.md deleted file mode 100644 index fcd2c88402..0000000000 --- a/allocd-port/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Build AllocD - -For now, AllocD is built as a standalone executable - -## Dependency - -### packages -1. libfmt-dev -2. libgoogle-glog-dev -3. libjsoncpp-dev -4. libgflags-dev (installed by libgoogle-glog-dev) - -## Configure - ```bash - $ mkdir build && cmake -B build - ``` - `build` directory will be created. - -## Build - ```bash - $ cmake --build build - ``` - -## Local Install - ```bash - $ mkdir bin lib && cmake --install build - ``` - `bin` and `lib` directories are populated. - -# Directory Structure - -`include` directory keeps the headers in its subdirectories. -`srcs` keeps `.c`, `.cc`, `.cpp` files in its subdirectories. - -```bash -$ tree -d -. -├── include -│   ├── android-base -│   └── cuttlefish -│   ├── allocd -│   ├── fs -│   └── utils -└── srcs - ├── cuttlefish - │   ├── allocd - │   └── fs - └── testings -``` diff --git a/allocd-port/bin/.gitignore b/allocd-port/bin/.gitignore deleted file mode 100644 index 5e7d2734cf..0000000000 --- a/allocd-port/bin/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore diff --git a/allocd-port/build/.gitignore b/allocd-port/build/.gitignore deleted file mode 100644 index 5e7d2734cf..0000000000 --- a/allocd-port/build/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore diff --git a/allocd-port/include/android-base/expected.h b/allocd-port/include/android-base/expected.h deleted file mode 100644 index 77ee867e43..0000000000 --- a/allocd-port/include/android-base/expected.h +++ /dev/null @@ -1,787 +0,0 @@ -/* - * Copyright (C) 2019 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 - -#include -#include -#include -#include -#include - -// android::base::expected is an Android implementation of the std::expected -// proposal. -// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0323r7.html -// -// Usage: -// using android::base::expected; -// using android::base::unexpected; -// -// expected safe_divide(double i, double j) { -// if (j == 0) return unexpected("divide by zero"); -// else return i / j; -// } -// -// void test() { -// auto q = safe_divide(10, 0); -// if (q.ok()) { printf("%f\n", q.value()); } -// else { printf("%s\n", q.error().c_str()); } -// } -// -// When the proposal becomes part of the standard and is implemented by -// libcxx, this will be removed and android::base::expected will be -// type alias to std::expected. -// - -namespace android { -namespace base { - -// Synopsis -template -class expected; - -template -class unexpected; -template -unexpected(E) -> unexpected; - -template -class bad_expected_access; - -template <> -class bad_expected_access; - -struct unexpect_t { - explicit unexpect_t() = default; -}; -inline constexpr unexpect_t unexpect{}; - -// macros for SFINAE -#define _ENABLE_IF(...) , std::enable_if_t<(__VA_ARGS__)>* = nullptr - -// Define NODISCARD_EXPECTED to prevent expected from being -// ignored when used as a return value. This is off by default. -#ifdef NODISCARD_EXPECTED -#define _NODISCARD_ [[nodiscard]] -#else -#define _NODISCARD_ -#endif - -// Class expected -template -class _NODISCARD_ expected { - public: - using value_type = T; - using error_type = E; - using unexpected_type = unexpected; - - template - using rebind = expected; - - // constructors - constexpr expected() = default; - constexpr expected(const expected& rhs) = default; - constexpr expected(expected&& rhs) noexcept = default; - - template && - std::is_constructible_v && - !std::is_constructible_v&> && - !std::is_constructible_v&&> && - !std::is_constructible_v&> && - !std::is_constructible_v&&> && - !std::is_convertible_v&, T> && - !std::is_convertible_v&&, T> && - !std::is_convertible_v&, T> && - !std::is_convertible_v&&, T> && - !(!std::is_convertible_v || - !std::is_convertible_v)/* non-explicit */ - )> - // NOLINTNEXTLINE(google-explicit-constructor) - constexpr expected(const expected& rhs) { - if (rhs.has_value()) - var_ = rhs.value(); - else - var_ = unexpected(rhs.error()); - } - - template && - std::is_constructible_v && - !std::is_constructible_v&> && - !std::is_constructible_v&&> && - !std::is_constructible_v&> && - !std::is_constructible_v&&> && - !std::is_convertible_v&, T> && - !std::is_convertible_v&&, T> && - !std::is_convertible_v&, T> && - !std::is_convertible_v&&, T> && - (!std::is_convertible_v || - !std::is_convertible_v)/* explicit */ - )> - constexpr explicit expected(const expected& rhs) { - if (rhs.has_value()) - var_ = rhs.value(); - else - var_ = unexpected(rhs.error()); - } - - template && - std::is_constructible_v && - !std::is_constructible_v&> && - !std::is_constructible_v&&> && - !std::is_constructible_v&> && - !std::is_constructible_v&&> && - !std::is_convertible_v&, T> && - !std::is_convertible_v&&, T> && - !std::is_convertible_v&, T> && - !std::is_convertible_v&&, T> && - !(!std::is_convertible_v || - !std::is_convertible_v)/* non-explicit */ - )> - // NOLINTNEXTLINE(google-explicit-constructor) - constexpr expected(expected&& rhs) { - if (rhs.has_value()) - var_ = std::move(rhs.value()); - else - var_ = unexpected(std::move(rhs.error())); - } - - template && - std::is_constructible_v && - !std::is_constructible_v&> && - !std::is_constructible_v&&> && - !std::is_constructible_v&> && - !std::is_constructible_v&&> && - !std::is_convertible_v&, T> && - !std::is_convertible_v&&, T> && - !std::is_convertible_v&, T> && - !std::is_convertible_v&&, T> && - (!std::is_convertible_v || - !std::is_convertible_v)/* explicit */ - )> - constexpr explicit expected(expected&& rhs) { - if (rhs.has_value()) - var_ = std::move(rhs.value()); - else - var_ = unexpected(std::move(rhs.error())); - } - - template && - !std::is_same_v>, - std::in_place_t> && - !std::is_same_v, - std::remove_cv_t>> && - !std::is_same_v, - std::remove_cv_t>> && - std::is_convertible_v /* non-explicit */ - )> - // NOLINTNEXTLINE(google-explicit-constructor,bugprone-forwarding-reference-overload) - constexpr expected(U&& v) - : var_(std::in_place_index<0>, std::forward(v)) {} - - template && - !std::is_same_v>, - std::in_place_t> && - !std::is_same_v, - std::remove_cv_t>> && - !std::is_same_v, - std::remove_cv_t>> && - !std::is_convertible_v /* explicit */ - )> - // NOLINTNEXTLINE(bugprone-forwarding-reference-overload) - constexpr explicit expected(U&& v) - : var_(std::in_place_index<0>, T(std::forward(v))) {} - - template && - std::is_convertible_v /* non-explicit - */ - )> - // NOLINTNEXTLINE(google-explicit-constructor) - constexpr expected(const unexpected& e) - : var_(std::in_place_index<1>, e.value()) {} - - template < - class G = E _ENABLE_IF(std::is_constructible_v && - !std::is_convertible_v /* explicit - */ - )> - constexpr explicit expected(const unexpected& e) - : var_(std::in_place_index<1>, E(e.value())) {} - - template && std:: - is_convertible_v /* non-explicit - */ - )> - // NOLINTNEXTLINE(google-explicit-constructor) - constexpr expected(unexpected&& e) - : var_(std::in_place_index<1>, std::move(e.value())) {} - - template && - !std::is_convertible_v /* explicit */ - )> - constexpr explicit expected(unexpected&& e) - : var_(std::in_place_index<1>, E(std::move(e.value()))) {} - - template )> - constexpr explicit expected(std::in_place_t, Args&&... args) - : var_(std::in_place_index<0>, std::forward(args)...) {} - - template &, Args...>)> - constexpr explicit expected(std::in_place_t, std::initializer_list il, - Args&&... args) - : var_(std::in_place_index<0>, il, std::forward(args)...) {} - - template )> - constexpr explicit expected(unexpect_t, Args&&... args) - : var_(unexpected_type(std::forward(args)...)) {} - - template &, Args...>)> - constexpr explicit expected(unexpect_t, std::initializer_list il, - Args&&... args) - : var_(unexpected_type(il, std::forward(args)...)) {} - - // destructor - ~expected() = default; - - // assignment - // Note: SFNAIE doesn't work here because assignment operator should be - // non-template. We could workaround this by defining a templated parent class - // having the assignment operator. This incomplete implementation however - // doesn't allow us to copy assign expected even when T is non-copy - // assignable. The copy assignment will fail by the underlying std::variant - // anyway though the error message won't be clear. - expected& operator=(const expected& rhs) = default; - - // Note for SFNAIE above applies to here as well - expected& operator=(expected&& rhs) noexcept( - std::is_nothrow_move_assignable_v&& - std::is_nothrow_move_assignable_v) = default; - - template && - !std::is_same_v, - std::remove_cv_t>> && - !std::conjunction_v, - std::is_same>> && - std::is_constructible_v && std::is_assignable_v && - std::is_nothrow_move_constructible_v)> - expected& operator=(U&& rhs) { - var_ = T(std::forward(rhs)); - return *this; - } - - template - expected& operator=(const unexpected& rhs) { - var_ = rhs; - return *this; - } - - template && - std::is_move_assignable_v)> - expected& operator=(unexpected&& rhs) { - var_ = std::move(rhs); - return *this; - } - - // modifiers - template < - class... Args _ENABLE_IF(std::is_nothrow_constructible_v)> - T& emplace(Args&&... args) { - expected(std::in_place, std::forward(args)...).swap(*this); - return value(); - } - - template &, Args...>)> - T& emplace(std::initializer_list il, Args&&... args) { - expected(std::in_place, il, std::forward(args)...).swap(*this); - return value(); - } - - // swap - template && - std::is_swappable_v && - (std::is_move_constructible_v || - std::is_move_constructible_v))>> - void swap(expected& rhs) noexcept( - std::is_nothrow_move_constructible_v&& std::is_nothrow_swappable_v&& - std::is_nothrow_move_constructible_v&& - std::is_nothrow_swappable_v) { - var_.swap(rhs.var_); - } - - // observers - constexpr const T* operator->() const { return std::addressof(value()); } - constexpr T* operator->() { return std::addressof(value()); } - constexpr const T& operator*() const& { return value(); } - constexpr T& operator*() & { return value(); } - constexpr const T&& operator*() const&& { - return std::move(std::get(var_)); - } - constexpr T&& operator*() && { return std::move(std::get(var_)); } - - constexpr bool has_value() const noexcept { return var_.index() == 0; } - constexpr bool ok() const noexcept { return has_value(); } - - constexpr const T& value() const& { return std::get(var_); } - constexpr T& value() & { return std::get(var_); } - constexpr const T&& value() const&& { return std::move(std::get(var_)); } - constexpr T&& value() && { return std::move(std::get(var_)); } - - constexpr const E& error() const& { - return std::get(var_).value(); - } - constexpr E& error() & { return std::get(var_).value(); } - constexpr const E&& error() const&& { - return std::move(std::get(var_)).value(); - } - constexpr E&& error() && { - return std::move(std::get(var_)).value(); - } - - template && std::is_convertible_v)> - constexpr T value_or(U&& v) const& { - if (has_value()) - return value(); - else - return static_cast(std::forward(v)); - } - - template && std::is_convertible_v)> - constexpr T value_or(U&& v) && { - if (has_value()) - return std::move(value()); - else - return static_cast(std::forward(v)); - } - - // expected equality operators - template - friend constexpr bool operator==(const expected& x, - const expected& y); - template - friend constexpr bool operator!=(const expected& x, - const expected& y); - - // Comparison with unexpected - template - friend constexpr bool operator==(const expected&, - const unexpected&); - template - friend constexpr bool operator==(const unexpected&, - const expected&); - template - friend constexpr bool operator!=(const expected&, - const unexpected&); - template - friend constexpr bool operator!=(const unexpected&, - const expected&); - - // Specialized algorithms - template - friend void swap(expected& x, - expected& y) noexcept(noexcept(x.swap(y))); - - private: - std::variant var_; -}; - -template -constexpr bool operator==(const expected& x, - const expected& y) { - if (x.has_value() != y.has_value()) return false; - if (!x.has_value()) return x.error() == y.error(); - return *x == *y; -} - -template -constexpr bool operator!=(const expected& x, - const expected& y) { - return !(x == y); -} - -// Comparison with unexpected -template -constexpr bool operator==(const expected& x, const unexpected& y) { - return !x.has_value() && (x.error() == y.value()); -} -template -constexpr bool operator==(const unexpected& x, const expected& y) { - return !y.has_value() && (x.value() == y.error()); -} -template -constexpr bool operator!=(const expected& x, const unexpected& y) { - return x.has_value() || (x.error() != y.value()); -} -template -constexpr bool operator!=(const unexpected& x, const expected& y) { - return y.has_value() || (x.value() != y.error()); -} - -template -void swap(expected& x, - expected& y) noexcept(noexcept(x.swap(y))) { - x.swap(y); -} - -template -class _NODISCARD_ expected { - public: - using value_type = void; - using error_type = E; - using unexpected_type = unexpected; - - // constructors - constexpr expected() = default; - constexpr expected(const expected& rhs) = default; - constexpr expected(expected&& rhs) noexcept = default; - - template && std::is_convertible_v< - const G&, E> /* non-explicit */ - )> - // NOLINTNEXTLINE(google-explicit-constructor) - constexpr expected(const expected& rhs) { - if (!rhs.has_value()) var_ = unexpected(rhs.error()); - } - - template && - !std::is_convertible_v /* explicit */ - )> - constexpr explicit expected(const expected& rhs) { - if (!rhs.has_value()) var_ = unexpected(rhs.error()); - } - - template && std::is_convertible_v< - const G&&, E> /* non-explicit */ - )> - // NOLINTNEXTLINE(google-explicit-constructor) - constexpr expected(expected&& rhs) { - if (!rhs.has_value()) var_ = unexpected(std::move(rhs.error())); - } - - template && - !std::is_convertible_v /* explicit */ - )> - constexpr explicit expected(expected&& rhs) { - if (!rhs.has_value()) var_ = unexpected(std::move(rhs.error())); - } - - template && - std::is_convertible_v /* non-explicit - */ - )> - // NOLINTNEXTLINE(google-explicit-constructor) - constexpr expected(const unexpected& e) - : var_(std::in_place_index<1>, e.value()) {} - - template < - class G = E _ENABLE_IF(std::is_constructible_v && - !std::is_convertible_v /* explicit - */ - )> - constexpr explicit expected(const unexpected& e) - : var_(std::in_place_index<1>, E(e.value())) {} - - template && std:: - is_convertible_v /* non-explicit - */ - )> - // NOLINTNEXTLINE(google-explicit-constructor) - constexpr expected(unexpected&& e) - : var_(std::in_place_index<1>, std::move(e.value())) {} - - template && - !std::is_convertible_v /* explicit */ - )> - constexpr explicit expected(unexpected&& e) - : var_(std::in_place_index<1>, E(std::move(e.value()))) {} - - template - constexpr explicit expected(std::in_place_t, Args&&...) {} - - template )> - constexpr explicit expected(unexpect_t, Args&&... args) - : var_(unexpected_type(std::forward(args)...)) {} - - template &, Args...>)> - constexpr explicit expected(unexpect_t, std::initializer_list il, - Args&&... args) - : var_(unexpected_type(il, std::forward(args)...)) {} - - // destructor - ~expected() = default; - - // assignment - // Note: SFNAIE doesn't work here because assignment operator should be - // non-template. We could workaround this by defining a templated parent class - // having the assignment operator. This incomplete implementation however - // doesn't allow us to copy assign expected even when T is non-copy - // assignable. The copy assignment will fail by the underlying std::variant - // anyway though the error message won't be clear. - expected& operator=(const expected& rhs) = default; - - // Note for SFNAIE above applies to here as well - expected& operator=(expected&& rhs) noexcept( - std::is_nothrow_move_assignable_v) = default; - - template - expected& operator=(const unexpected& rhs) { - var_ = rhs; - return *this; - } - - template && - std::is_move_assignable_v)> - expected& operator=(unexpected&& rhs) { - var_ = std::move(rhs); - return *this; - } - - // modifiers - void emplace() { var_ = std::monostate(); } - - // swap - template >> - void swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v) { - var_.swap(rhs.var_); - } - - // observers - constexpr bool has_value() const noexcept { return var_.index() == 0; } - constexpr bool ok() const noexcept { return has_value(); } - - constexpr void value() const& { - if (!has_value()) std::get<0>(var_); - } - - constexpr const E& error() const& { - return std::get(var_).value(); - } - constexpr E& error() & { return std::get(var_).value(); } - constexpr const E&& error() const&& { - return std::move(std::get(var_)).value(); - } - constexpr E&& error() && { - return std::move(std::get(var_)).value(); - } - - // expected equality operators - template - friend constexpr bool operator==(const expected& x, - const expected& y); - - // Specialized algorithms - template - friend void swap(expected& x, - expected& y) noexcept(noexcept(x.swap(y))); - - private: - std::variant var_; -}; - -template -constexpr bool operator==(const expected& x, - const expected& y) { - if (x.has_value() != y.has_value()) return false; - if (!x.has_value()) return x.error() == y.error(); - return true; -} - -template -constexpr bool operator==(const expected& x, - const expected& y) { - if (x.has_value() != y.has_value()) return false; - if (!x.has_value()) return x.error() == y.error(); - return false; -} - -template -constexpr bool operator==(const expected& x, - const expected& y) { - if (x.has_value() != y.has_value()) return false; - if (!x.has_value()) return x.error() == y.error(); - return false; -} - -template -class unexpected { - public: - // constructors - constexpr unexpected(const unexpected&) = default; - constexpr unexpected(unexpected&&) noexcept( - std::is_nothrow_move_constructible_v) = default; - - template && - !std::is_same_v>, - std::in_place_t> && - !std::is_same_v>, - unexpected>)> - // NOLINTNEXTLINE(google-explicit-constructor,bugprone-forwarding-reference-overload) - constexpr unexpected(Err&& e) : val_(std::forward(e)) {} - - template &, Args...>)> - constexpr explicit unexpected(std::in_place_t, std::initializer_list il, - Args&&... args) - : val_(il, std::forward(args)...) {} - - template && - !std::is_constructible_v&> && - !std::is_constructible_v> && - !std::is_constructible_v&> && - !std::is_constructible_v> && - !std::is_convertible_v&, E> && - !std::is_convertible_v, E> && - !std::is_convertible_v&, E> && - !std::is_convertible_v, E> && - std::is_convertible_v /* non-explicit */ - )> - // NOLINTNEXTLINE(google-explicit-constructor) - constexpr unexpected(const unexpected& rhs) : val_(rhs.value()) {} - - template && - !std::is_constructible_v&> && - !std::is_constructible_v> && - !std::is_constructible_v&> && - !std::is_constructible_v> && - !std::is_convertible_v&, E> && - !std::is_convertible_v, E> && - !std::is_convertible_v&, E> && - !std::is_convertible_v, E> && - !std::is_convertible_v /* explicit */ - )> - constexpr explicit unexpected(const unexpected& rhs) - : val_(E(rhs.value())) {} - - template && - !std::is_constructible_v&> && - !std::is_constructible_v> && - !std::is_constructible_v&> && - !std::is_constructible_v> && - !std::is_convertible_v&, E> && - !std::is_convertible_v, E> && - !std::is_convertible_v&, E> && - !std::is_convertible_v, E> && - std::is_convertible_v /* non-explicit */ - )> - // NOLINTNEXTLINE(google-explicit-constructor) - constexpr unexpected(unexpected&& rhs) : val_(std::move(rhs.value())) {} - - template && - !std::is_constructible_v&> && - !std::is_constructible_v> && - !std::is_constructible_v&> && - !std::is_constructible_v> && - !std::is_convertible_v&, E> && - !std::is_convertible_v, E> && - !std::is_convertible_v&, E> && - !std::is_convertible_v, E> && - !std::is_convertible_v /* explicit */ - )> - constexpr explicit unexpected(unexpected&& rhs) - : val_(E(std::move(rhs.value()))) {} - - // assignment - constexpr unexpected& operator=(const unexpected&) = default; - constexpr unexpected& operator=(unexpected&&) noexcept( - std::is_nothrow_move_assignable_v) = default; - template - constexpr unexpected& operator=(const unexpected& rhs) { - val_ = rhs.value(); - return *this; - } - template - constexpr unexpected& operator=(unexpected&& rhs) { - val_ = std::forward(rhs.value()); - return *this; - } - - // observer - constexpr const E& value() const& noexcept { return val_; } - constexpr E& value() & noexcept { return val_; } - constexpr const E&& value() const&& noexcept { return std::move(val_); } - constexpr E&& value() && noexcept { return std::move(val_); } - - void swap(unexpected& other) noexcept(std::is_nothrow_swappable_v) { - std::swap(val_, other.val_); - } - - template - friend constexpr bool operator==(const unexpected& e1, - const unexpected& e2); - template - friend constexpr bool operator!=(const unexpected& e1, - const unexpected& e2); - - template - friend void swap(unexpected& x, - unexpected& y) noexcept(noexcept(x.swap(y))); - - private: - E val_; -}; - -template -constexpr bool operator==(const unexpected& e1, const unexpected& e2) { - return e1.value() == e2.value(); -} - -template -constexpr bool operator!=(const unexpected& e1, const unexpected& e2) { - return e1.value() != e2.value(); -} - -template -void swap(unexpected& x, unexpected& y) noexcept(noexcept(x.swap(y))) { - x.swap(y); -} - -// TODO: bad_expected_access class - -#undef _ENABLE_IF -#undef _NODISCARD_ - -} // namespace base -} // namespace android diff --git a/allocd-port/lib/.gitignore b/allocd-port/lib/.gitignore deleted file mode 100644 index 5e7d2734cf..0000000000 --- a/allocd-port/lib/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore diff --git a/allocd-port/srcs/CMakeLists.txt b/allocd-port/srcs/CMakeLists.txt deleted file mode 100644 index 14eaf7659e..0000000000 --- a/allocd-port/srcs/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright (C) 2023 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. -# - -cmake_minimum_required(VERSION 3.18) -project(allocd_separation) -add_subdirectory(fs) -add_subdirectory(test) diff --git a/allocd-port/srcs/fs/CMakeLists.txt b/allocd-port/srcs/fs/CMakeLists.txt deleted file mode 100644 index 1854d25973..0000000000 --- a/allocd-port/srcs/fs/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# Copyright (C) 2023 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. -# - -cmake_minimum_required(VERSION 3.18) -project(allocd_separation) - -add_library( - cuttlefish_fs STATIC - epoll.cpp - shared_buf.cpp - shared_fd.cpp - shared_fd_stream.cpp - ) - -install(TARGETS cuttlefish_fs - ARCHIVE DESTINATION ${PROJECT_LIBRARY_DIR}) - - diff --git a/allocd-port/srcs/test/CMakeLists.txt b/allocd-port/srcs/test/CMakeLists.txt deleted file mode 100644 index b4f2a3fda3..0000000000 --- a/allocd-port/srcs/test/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# Copyright (C) 2023 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. -# - -cmake_minimum_required(VERSION 3.18) -project(allocd_separation) - -add_executable( - result_test - result_test.cc - ) - -install(TARGETS result_test - RUNTIME DESTINATION ${PROJECT_BIN_DIR}) - diff --git a/allocd-port/srcs/test/result_test.cc b/allocd-port/srcs/test/result_test.cc deleted file mode 100644 index 35bf1f34ca..0000000000 --- a/allocd-port/srcs/test/result_test.cc +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2023 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. - */ - -#include - -#include "cuttlefish/utils/result.h" - -namespace cuttlefish { - -Result>> Bar(const std::string& s1) { - CF_EXPECT(s1.size() != 2, std::string("") << " test " << 2 << " is not " - << "size of " << s1 << ", which is " - << s1.size()); - return std::vector>{std::nullopt}; -} - -Result Foo() { - CF_EXPECT(std::string("Hello").size()); - return {}; -} - -} // namespace cuttlefish - -int main() { - auto result = cuttlefish::Foo(); - std::cout << std::boolalpha << result.ok() << std::endl; - auto result2 = cuttlefish::Bar("Hi"); - if (!result2.ok()) { - std::cout << result2.error().Trace() << std::endl; - } - return 0; -} diff --git a/base/cvd/allocd/alloc_utils.cpp b/base/cvd/allocd/alloc_utils.cpp new file mode 100644 index 0000000000..6288f24f77 --- /dev/null +++ b/base/cvd/allocd/alloc_utils.cpp @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2020 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. + */ +#include "alloc_utils.h" + +#include +#include + +#include "android-base/logging.h" + +namespace cuttlefish { + +int RunExternalCommand(const std::string& command) { + FILE* fp; + LOG(INFO) << "Running external command: " << command; + fp = popen(command.c_str(), "r"); + + if (fp == nullptr) { + LOG(WARNING) << "Error running external command"; + return -1; + } + + int status = pclose(fp); + int ret = -1; + if (status == -1) { + LOG(WARNING) << "pclose error"; + } else { + if (WIFEXITED(status)) { + LOG(INFO) << "child process exited normally"; + ret = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + LOG(WARNING) << "child process was terminated by a signal"; + } else { + LOG(WARNING) << "child process did not terminate normally"; + } + } + return ret; +} + +bool AddTapIface(const std::string& name) { + std::stringstream ss; + ss << "ip tuntap add dev " << name << " mode tap group cvdnetwork vnet_hdr"; + auto add_command = ss.str(); + LOG(INFO) << "Create tap interface: " << add_command; + int status = RunExternalCommand(add_command); + return status == 0; +} + +bool ShutdownIface(const std::string& name) { + std::stringstream ss; + ss << "ip link set dev " << name << " down"; + auto link_command = ss.str(); + LOG(INFO) << "Shutdown tap interface: " << link_command; + int status = RunExternalCommand(link_command); + + return status == 0; +} + +bool BringUpIface(const std::string& name) { + std::stringstream ss; + ss << "ip link set dev " << name << " up"; + auto link_command = ss.str(); + LOG(INFO) << "Bring up tap interface: " << link_command; + int status = RunExternalCommand(link_command); + + return status == 0; +} + +bool CreateEthernetIface(const std::string& name, const std::string& bridge_name, + bool has_ipv4_bridge, bool has_ipv6_bridge, + bool use_ebtables_legacy) { + // assume bridge exists + + EthernetNetworkConfig config{false, false, false}; + + if (!CreateTap(name)) { + return false; + } + + config.has_tap = true; + + if (!LinkTapToBridge(name, bridge_name)) { + CleanupEthernetIface(name, config); + return false; + } + + if (!has_ipv4_bridge) { + if (!CreateEbtables(name, true, use_ebtables_legacy)) { + CleanupEthernetIface(name, config); + return false; + } + config.has_broute_ipv4 = true; + } + + if (!has_ipv6_bridge) { + if (CreateEbtables(name, false, use_ebtables_legacy)) { + CleanupEthernetIface(name, config); + return false; + } + config.has_broute_ipv6 = true; + } + + return true; +} + +std::string MobileGatewayName(const std::string& ipaddr, uint16_t id) { + std::stringstream ss; + ss << ipaddr << "." << (4 * id + 1); + return ss.str(); +} + +std::string MobileNetworkName(const std::string& ipaddr, + const std::string& netmask, uint16_t id) { + std::stringstream ss; + ss << ipaddr << "." << (4 * id) << netmask; + return ss.str(); +} + +bool CreateMobileIface(const std::string& name, uint16_t id, + const std::string& ipaddr) { + if (id > kMaxIfaceNameId) { + LOG(ERROR) << "ID exceeds maximum value to assign a netmask: " << id; + return false; + } + + auto netmask = "/30"; + auto gateway = MobileGatewayName(ipaddr, id); + auto network = MobileNetworkName(ipaddr, netmask, id); + + if (!CreateTap(name)) { + return false; + } + + if (!AddGateway(name, gateway, netmask)) { + DestroyIface(name); + } + + if (!IptableConfig(network, true)) { + DestroyGateway(name, gateway, netmask); + DestroyIface(name); + return false; + }; + + return true; +} + +bool DestroyMobileIface(const std::string& name, uint16_t id, + const std::string& ipaddr) { + if (id > 63) { + LOG(ERROR) << "ID exceeds maximum value to assign a netmask: " << id; + return false; + } + + auto netmask = "/30"; + auto gateway = MobileGatewayName(ipaddr, id); + auto network = MobileNetworkName(ipaddr, netmask, id); + + IptableConfig(network, false); + DestroyGateway(name, gateway, netmask); + return DestroyIface(name); +} + +bool AddGateway(const std::string& name, const std::string& gateway, + const std::string& netmask) { + std::stringstream ss; + ss << "ip addr add " << gateway << netmask << " broadcast + dev " << name; + auto command = ss.str(); + LOG(INFO) << "setup gateway: " << command; + int status = RunExternalCommand(command); + + return status == 0; +} + +bool DestroyGateway(const std::string& name, const std::string& gateway, + const std::string& netmask) { + std::stringstream ss; + ss << "ip addr del " << gateway << netmask << " broadcast + dev " << name; + auto command = ss.str(); + LOG(INFO) << "removing gateway: " << command; + int status = RunExternalCommand(command); + + return status == 0; +} + +bool DestroyEthernetIface(const std::string& name, bool has_ipv4_bridge, + bool has_ipv6_bridge, bool use_ebtables_legacy) { + if (!has_ipv6_bridge) { + DestroyEbtables(name, false, use_ebtables_legacy); + } + + if (!has_ipv4_bridge) { + DestroyEbtables(name, true, use_ebtables_legacy); + } + + return DestroyIface(name); +} + +void CleanupEthernetIface(const std::string& name, + const EthernetNetworkConfig& config) { + if (config.has_broute_ipv6) { + DestroyEbtables(name, false, config.use_ebtables_legacy); + } + + if (config.has_broute_ipv4) { + DestroyEbtables(name, true, config.use_ebtables_legacy); + } + + if (config.has_tap) { + DestroyIface(name); + } +} + +bool CreateEbtables(const std::string& name, bool use_ipv4, + bool use_ebtables_legacy) { + return EbtablesBroute(name, use_ipv4, true, use_ebtables_legacy) && + EbtablesFilter(name, use_ipv4, true, use_ebtables_legacy); +} + +bool DestroyEbtables(const std::string& name, bool use_ipv4, + bool use_ebtables_legacy) { + return EbtablesBroute(name, use_ipv4, false, use_ebtables_legacy) && + EbtablesFilter(name, use_ipv4, false, use_ebtables_legacy); +} + +bool EbtablesBroute(const std::string& name, bool use_ipv4, bool add, + bool use_ebtables_legacy) { + std::stringstream ss; + // we don't know the name of the ebtables program, but since we're going to + // exec this program name, make sure they can only choose between the two + // options we currently support, and not something they can overwrite + if (use_ebtables_legacy) { + ss << kEbtablesLegacyName; + } else { + ss << kEbtablesName; + } + + ss << " -t broute " << (add ? "-A" : "-D") << " BROUTING -p " + << (use_ipv4 ? "ipv4" : "ipv6") << " --in-if " << name << " -j DROP"; + auto command = ss.str(); + int status = RunExternalCommand(command); + + return status == 0; +} + +bool EbtablesFilter(const std::string& name, bool use_ipv4, bool add, + bool use_ebtables_legacy) { + std::stringstream ss; + if (use_ebtables_legacy) { + ss << kEbtablesLegacyName; + } else { + ss << kEbtablesName; + } + + ss << " -t filter " << (add ? "-A" : "-D") << " FORWARD -p " + << (use_ipv4 ? "ipv4" : "ipv6") << " --out-if " << name << " -j DROP"; + auto command = ss.str(); + int status = RunExternalCommand(command); + + return status == 0; +} + +bool LinkTapToBridge(const std::string& tap_name, + const std::string& bridge_name) { + std::stringstream ss; + ss << "ip link set dev " << tap_name << " master " << bridge_name; + auto command = ss.str(); + int status = RunExternalCommand(command); + + return status == 0; +} + +bool CreateTap(const std::string& name) { + LOG(INFO) << "Attempt to create tap interface: " << name; + if (!AddTapIface(name)) { + LOG(WARNING) << "Failed to create tap interface: " << name; + return false; + } + + if (!BringUpIface(name)) { + LOG(WARNING) << "Failed to bring up tap interface: " << name; + DeleteIface(name); + return false; + } + + return true; +} + +bool DeleteIface(const std::string& name) { + std::stringstream ss; + ss << "ip link delete " << name; + auto link_command = ss.str(); + LOG(INFO) << "Delete tap interface: " << link_command; + int status = RunExternalCommand(link_command); + + return status == 0; +} + +bool DestroyIface(const std::string& name) { + if (!ShutdownIface(name)) { + LOG(WARNING) << "Failed to shutdown tap interface: " << name; + // the interface might have already shutdown ... so ignore and try to remove + // the interface. In the future we could read from the pipe and handle this + // case more elegantly + } + + if (!DeleteIface(name)) { + LOG(WARNING) << "Failed to delete tap interface: " << name; + return false; + } + + return true; +} + +std::optional GetUserName(uid_t uid) { + passwd* pw = getpwuid(uid); + if (pw) { + std::string ret(pw->pw_name); + return ret; + } + return std::nullopt; +} + +bool CreateBridge(const std::string& name) { + std::stringstream ss; + ss << "ip link add name " << name + << " type bridge forward_delay 0 stp_state 0"; + + auto command = ss.str(); + LOG(INFO) << "create bridge: " << command; + int status = RunExternalCommand(command); + + if (status != 0) { + return false; + } + + return BringUpIface(name); +} + +bool DestroyBridge(const std::string& name) { return DeleteIface(name); } + +bool SetupBridgeGateway(const std::string& bridge_name, + const std::string& ipaddr) { + GatewayConfig config{false, false, false}; + auto gateway = ipaddr + ".1"; + auto netmask = "/24"; + auto network = ipaddr + ".0" + netmask; + auto dhcp_range = ipaddr + ".2," + ipaddr + ".255"; + + if (!AddGateway(bridge_name, gateway, netmask)) { + return false; + } + + config.has_gateway = true; + + if (StartDnsmasq(bridge_name, gateway, dhcp_range)) { + CleanupBridgeGateway(bridge_name, ipaddr, config); + return false; + } + + config.has_dnsmasq = true; + + auto ret = IptableConfig(network, true); + if (!ret) { + CleanupBridgeGateway(bridge_name, ipaddr, config); + LOG(WARNING) << "Failed to setup ip tables"; + } + + return ret; +} + +void CleanupBridgeGateway(const std::string& name, const std::string& ipaddr, + const GatewayConfig& config) { + auto gateway = ipaddr + ".1"; + auto netmask = "/24"; + auto network = ipaddr + ".0" + netmask; + auto dhcp_range = ipaddr + ".2," + ipaddr + ".255"; + + if (config.has_iptable) { + IptableConfig(network, false); + } + + if (config.has_dnsmasq) { + StopDnsmasq(name); + } + + if (config.has_gateway) { + DestroyGateway(name, gateway, netmask); + } +} + +bool StartDnsmasq(const std::string& bridge_name, const std::string& gateway, + const std::string& dhcp_range) { + auto dns_servers = "8.8.8.8,8.8.4.4"; + auto dns6_servers = "2001:4860:4860::8888,2001:4860:4860::8844"; + std::stringstream ss; + + // clang-format off + ss << + "dnsmasq" + " --port=0" + " --strict-order" + " --except-interface=lo" + " --interface=" << bridge_name << + " --listen-address=" << gateway << + " --bind-interfaces" + " --dhcp-range=" << dhcp_range << + " --dhcp-option=\"option:dns-server," << dns_servers << "\"" + " --dhcp-option=\"option6:dns-server," << dns6_servers << "\"" + " --conf-file=\"\"" + " --pid-file=/var/run/cuttlefish-dnsmasq-" << bridge_name << ".pid" + " --dhcp-leasefile=/var/run/cuttlefish-dnsmasq-" << bridge_name << ".leases" + " --dhcp-no-override "; + // clang-format on + + auto command = ss.str(); + LOG(INFO) << "start_dnsmasq: " << command; + int status = RunExternalCommand(command); + + return status == 0; +} + +bool StopDnsmasq(const std::string& name) { + std::ifstream file; + std::string filename = "/var/run/cuttlefish-dnsmasq-" + name + ".pid"; + LOG(INFO) << "stopping dsnmasq for interface: " << name; + file.open(filename); + if (file.is_open()) { + LOG(INFO) << "dnsmasq file:" << filename + << " could not be opened, assume dnsmaq has already stopped"; + return true; + } + + std::string pid; + file >> pid; + file.close(); + std::string command = "kill " + pid; + int status = RunExternalCommand(command); + auto ret = (status == 0); + + if (ret) { + LOG(INFO) << "dsnmasq for:" << name << "successfully stopped"; + } else { + LOG(WARNING) << "Failed to stop dsnmasq for:" << name; + } + return ret; +} + +bool IptableConfig(const std::string& network, bool add) { + std::stringstream ss; + ss << "iptables -t nat " << (add ? "-A" : "-D") << " POSTROUTING -s " + << network << " -j MASQUERADE"; + + auto command = ss.str(); + LOG(INFO) << "iptable_config: " << command; + int status = RunExternalCommand(command); + + return status == 0; +} + +bool CreateEthernetBridgeIface(const std::string& name, + const std::string& ipaddr) { + if (!CreateBridge(name)) { + return false; + } + + if (!SetupBridgeGateway(name, ipaddr)) { + DestroyBridge(name); + return false; + } + + return true; +} + +bool DestroyEthernetBridgeIface(const std::string& name, + const std::string& ipaddr) { + GatewayConfig config{true, true, true}; + + // Don't need to check if removing some part of the config failed, we need to + // remove the entire interface, so just ignore any error until the end + CleanupBridgeGateway(name, ipaddr, config); + + return DestroyBridge(name); +} + +} // namespace cuttlefish diff --git a/base/cvd/allocd/alloc_utils.h b/base/cvd/allocd/alloc_utils.h new file mode 100644 index 0000000000..8227d01632 --- /dev/null +++ b/base/cvd/allocd/alloc_utils.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2020 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 + +#include +#include +#include + +#include +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "request.h" + +namespace cuttlefish { + +constexpr char kEbtablesName[] = "ebtables"; +constexpr char kEbtablesLegacyName[] = "ebtables-legacy"; + +// Wireless network prefix +constexpr char kWirelessIp[] = "192.168.96"; +// Mobile network prefix +constexpr char kMobileIp[] = "192.168.97"; +// Ethernet network prefix +constexpr char kEthernetIp[] = "192.168.98"; +// permission bits for socket +constexpr int kSocketMode = 0666; + +// Max ID an interface can have +// Note: Interface names only have 2 digits in addition to the username prefix +// Additionally limited by available netmask values in MobileNetworkName +// Exceeding 63 would result in an overflow when calculating the netmask +constexpr uint32_t kMaxIfaceNameId = 63; + +// struct for managing configuration state +struct EthernetNetworkConfig { + bool has_broute_ipv4 = false; + bool has_broute_ipv6 = false; + bool has_tap = false; + bool use_ebtables_legacy = false; +}; + +// struct for managing configuration state +struct GatewayConfig { + bool has_gateway = false; + bool has_dnsmasq = false; + bool has_iptable = false; +}; + +int RunExternalCommand(const std::string& command); +std::optional GetUserName(uid_t uid); + +bool AddTapIface(const std::string& name); +bool CreateTap(const std::string& name); + +bool BringUpIface(const std::string& name); +bool ShutdownIface(const std::string& name); + +bool DestroyIface(const std::string& name); +bool DeleteIface(const std::string& name); + +bool CreateBridge(const std::string& name); +bool DestroyBridge(const std::string& name); + +bool CreateEbtables(const std::string& name, bool use_ipv, + bool use_ebtables_legacy); +bool DestroyEbtables(const std::string& name, bool use_ipv4, + bool use_ebtables_legacy); +bool EbtablesBroute(const std::string& name, bool use_ipv4, bool add, + bool use_ebtables_legacy); +bool EbtablesFilter(const std::string& name, bool use_ipv4, bool add, + bool use_ebtables_legacy); + +bool CreateMobileIface(const std::string& name, uint16_t id, + const std::string& ipaddr); +bool DestroyMobileIface(const std::string& name, uint16_t id, + const std::string& ipaddr); + +bool CreateEthernetIface(const std::string& name, const std::string& bridge_name, + bool has_ipv4_bridge, bool has_ipv6_bridge, + bool use_ebtables_legacy); +bool DestroyEthernetIface(const std::string& name, + bool has_ipv4_bridge, bool use_ipv6, + bool use_ebtables_legacy); +void CleanupEthernetIface(const std::string& name, + const EthernetNetworkConfig& config); + +bool IptableConfig(const std::string& network, bool add); + +bool LinkTapToBridge(const std::string& tap_name, + const std::string& bridge_name); + +bool SetupBridgeGateway(const std::string& name, const std::string& ipaddr); +void CleanupBridgeGateway(const std::string& name, const std::string& ipaddr, + const GatewayConfig& config); + +bool CreateEthernetBridgeIface(const std::string& name, + const std::string &ipaddr); +bool DestroyEthernetBridgeIface(const std::string& name, + const std::string &ipaddr); + +bool AddGateway(const std::string& name, const std::string& gateway, + const std::string& netmask); +bool DestroyGateway(const std::string& name, const std::string& gateway, + const std::string& netmask); + +bool StartDnsmasq(const std::string& bridge_name, const std::string& gateway, + const std::string& dhcp_range); +bool StopDnsmasq(const std::string& name); + +} // namespace cuttlefish diff --git a/base/cvd/allocd/allocd.cpp b/base/cvd/allocd/allocd.cpp new file mode 100644 index 0000000000..04b4fc2bf0 --- /dev/null +++ b/base/cvd/allocd/allocd.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc_utils.h" +#include "common/libs/fs/shared_fd.h" +#include "host/libs/config/logging.h" +#include "request.h" +#include "resource_manager.h" + +DEFINE_string(socket_path, cuttlefish::kDefaultLocation, "Socket path"); +DEFINE_bool(ebtables_legacy, false, "use ebtables-legacy instead of ebtables"); + +int main(int argc, char* argv[]) { + ::android::base::InitLogging(argv, android::base::StderrLogger); + + google::ParseCommandLineFlags(&argc, &argv, true); + + { + cuttlefish::ResourceManager m; + m.SetSocketLocation(FLAGS_socket_path); + m.SetUseEbtablesLegacy(FLAGS_ebtables_legacy); + m.JsonServer(); + } + + return 0; // EXIT_SUCCESS? or status +} diff --git a/base/cvd/allocd/request.h b/base/cvd/allocd/request.h new file mode 100644 index 0000000000..184ecb5cd7 --- /dev/null +++ b/base/cvd/allocd/request.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 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 + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace cuttlefish { + +/// Defines operations supported by allocd +enum class RequestType : uint16_t { + Invalid = 0, // Invalid Request + ID, // Allocate and return a new Session ID + CreateInterface, // Request to create new network interface + DestroyInterface, // Request to destroy a managed network interface + StopSession, // Request all resources within a session be released + Shutdown, // request allocd to shutdown and clean up all resources +}; + +/// Defines interface types supported by allocd +enum class IfaceType : uint16_t { + Invalid = 0, // an invalid interface + mtap, // mobile tap + wtap, // bridged wireless tap + wifiap, // non bridged wireless tap + etap, // ethernet tap + wbr, // wireless bridge + ebr // ethernet bridge +}; + +enum class RequestStatus : uint16_t { + Invalid = 0, // Invalid status + Pending, // Request which has not been attempted + Success, // Request was satisfied + Failure // Request failed +}; + +/// Defines the format for allocd Request messages +struct RequestHeader { + uint16_t version; /// used to differentiate between allocd feature sets + uint16_t len; /// length in bytes of the message payload +}; + +/// Provides a wrapper around libjson's Reader to additionally log errors +class JsonRequestReader { + public: + JsonRequestReader() = default; + + ~JsonRequestReader() = default; + + std::optional parse(std::string msg) { + Json::Value ret; + std::unique_ptr reader(reader_builder.newCharReader()); + std::string errorMessage; + if (!reader->parse(&*msg.begin(), &*msg.end(), &ret, &errorMessage)) { + LOG(WARNING) << "Received invalid JSON object in input channel: " + << errorMessage; + LOG(INFO) << "Invalid JSON: " << msg; + return std::nullopt; + } + return ret; + } + + private: + Json::CharReaderBuilder reader_builder; +}; + +} // namespace cuttlefish diff --git a/base/cvd/allocd/resource.cpp b/base/cvd/allocd/resource.cpp new file mode 100644 index 0000000000..e5bbb84e52 --- /dev/null +++ b/base/cvd/allocd/resource.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "resource.h" + +#include + +#include "alloc_utils.h" + +namespace cuttlefish { + +bool MobileIface::AcquireResource() { + return CreateMobileIface(GetName(), iface_id_, ipaddr_); +} + +bool MobileIface::ReleaseResource() { + return DestroyMobileIface(GetName(), iface_id_, ipaddr_); +} + +bool EthernetIface::AcquireResource() { + return CreateEthernetIface(GetName(), GetBridgeName(), has_ipv4_, has_ipv6_, + use_ebtables_legacy_); +} + +bool EthernetIface::ReleaseResource() { + return DestroyEthernetIface(GetName(), has_ipv4_, has_ipv6_, + use_ebtables_legacy_); +} + +} // namespace cuttlefish diff --git a/base/cvd/allocd/resource.h b/base/cvd/allocd/resource.h new file mode 100644 index 0000000000..c30e9cc5ca --- /dev/null +++ b/base/cvd/allocd/resource.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2020 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 + +#include + +#include +#include + +namespace cuttlefish { + +enum class ResourceType { + Invalid = 0, + MobileIface, + EthernetIface, + EthernetBridge, +}; + +class StaticResource { + public: + StaticResource() = default; + StaticResource(const std::string& name, uid_t uid, ResourceType ty, + uint32_t global_id) + : name_(name), uid_(uid), global_id_(global_id), ty_(ty){}; + virtual ~StaticResource() = default; + virtual bool ReleaseResource() = 0; + virtual bool AcquireResource() = 0; + + std::string GetName() { return name_; } + uid_t GetUid() { return uid_; } + ResourceType GetResourceType() { return ty_; } + uint32_t GetGlobalID() { return global_id_; } + + private: + std::string name_{}; + uid_t uid_{}; + uint32_t global_id_{}; + ResourceType ty_ = ResourceType::Invalid; +}; + +class MobileIface : public StaticResource { + public: + MobileIface() = default; + ~MobileIface() = default; + MobileIface(const std::string& name, uid_t uid, uint16_t iface_id, + uint32_t global_id, std::string ipaddr) + : StaticResource(name, uid, ResourceType::MobileIface, global_id), + iface_id_(iface_id), + ipaddr_(ipaddr) {} + + bool ReleaseResource() override; + bool AcquireResource() override; + + uint16_t GetIfaceId() { return iface_id_; } + std::string GetIpAddr() { return ipaddr_; } + + static constexpr char kNetmask[] = "/30"; + + private: + uint16_t iface_id_; + std::string ipaddr_; +}; + +class EthernetIface : public StaticResource { + public: + EthernetIface() = default; + ~EthernetIface() = default; + + EthernetIface(const std::string& name, uid_t uid, uint16_t iface_id, + uint32_t global_id, std::string bridge_name, + std::string ipaddr) + : StaticResource(name, uid, ResourceType::MobileIface, global_id), + iface_id_(iface_id), + bridge_name_(bridge_name), + ipaddr_(ipaddr) {} + + bool ReleaseResource() override; + bool AcquireResource() override; + + uint16_t GetIfaceId() { return iface_id_; } + + std::string GetBridgeName() { return bridge_name_; } + std::string GetIpAddr() { return ipaddr_; } + + void SetHasIpv4(bool ipv4) { has_ipv4_ = ipv4; } + void SetHasIpv6(bool ipv6) { has_ipv6_ = ipv6; } + void SetUseEbtablesLegacy(bool use_legacy) { + use_ebtables_legacy_ = use_legacy; + } + + bool GetHasIpv4() { return has_ipv4_; } + bool GetHasIpv6() { return has_ipv6_; } + bool GetUseEbtablesLegacy() { return use_ebtables_legacy_; } + + private: + static constexpr char kNetmask[] = "/24"; + uint16_t iface_id_; + std::string bridge_name_; + std::string ipaddr_; + bool has_ipv4_ = true; + bool has_ipv6_ = true; + bool use_ebtables_legacy_ = false; +}; + +} // namespace cuttlefish diff --git a/base/cvd/allocd/resource_manager.cpp b/base/cvd/allocd/resource_manager.cpp new file mode 100644 index 0000000000..ae5d0c7441 --- /dev/null +++ b/base/cvd/allocd/resource_manager.cpp @@ -0,0 +1,623 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "resource_manager.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc_utils.h" +#include "common/libs/fs/shared_fd.h" +#include "json/forwards.h" +#include "json/value.h" +#include "json/writer.h" +#include "request.h" +#include "utils.h" + +namespace cuttlefish { + +uid_t GetUserIDFromSock(SharedFD client_socket); + +ResourceManager::~ResourceManager() { + bool success = true; + for (auto& res : managed_sessions_) { + success &= res.second->ReleaseAllResources(); + } + + Json::Value resp; + resp["request_type"] = "shutdown"; + auto status = success ? RequestStatus::Success : RequestStatus::Failure; + resp["request_status"] = StatusToStr(status); + SendJsonMsg(shutdown_socket_, resp); + LOG(INFO) << "Daemon Shutdown complete"; + unlink(location_.c_str()); +} + +void ResourceManager::SetSocketLocation(const std::string& sock_name) { + location_ = sock_name; +} + +void ResourceManager::SetUseEbtablesLegacy(bool use_legacy) { + use_ebtables_legacy_ = use_legacy; +} + +uint32_t ResourceManager::AllocateResourceID() { + return global_resource_id_.fetch_add(1, std::memory_order_relaxed); +} + +uint32_t ResourceManager::AllocateSessionID() { + return session_id_.fetch_add(1, std::memory_order_relaxed); +} + +bool ResourceManager::AddInterface(const std::string& iface, IfaceType ty, + uint32_t resource_id, uid_t uid) { + bool allocatedIface = false; + std::shared_ptr res = nullptr; + + bool didInsert = active_interfaces_.insert(iface).second; + if (didInsert) { + const char* idp = iface.c_str() + (iface.size() - 3); + int small_id = atoi(idp); + switch (ty) { + case IfaceType::wifiap: + // TODO(seungjaeyoo) : Support AddInterface for wifiap + break; + case IfaceType::mtap: + // TODO(seungjaeyoo) : Support AddInterface for mtap uses IP prefix + // different from kMobileIp. + res = std::make_shared(iface, uid, small_id, resource_id, + kMobileIp); + allocatedIface = res->AcquireResource(); + pending_add_.insert({resource_id, res}); + break; + case IfaceType::wtap: { + auto w = std::make_shared( + iface, uid, small_id, resource_id, "cvd-wbr", kWirelessIp); + w->SetUseEbtablesLegacy(use_ebtables_legacy_); + w->SetHasIpv4(use_ipv4_bridge_); + w->SetHasIpv6(use_ipv6_bridge_); + res = w; + allocatedIface = res->AcquireResource(); + pending_add_.insert({resource_id, res}); + break; + } + case IfaceType::etap: { + auto w = std::make_shared( + iface, uid, small_id, resource_id, "cvd-ebr", kEthernetIp); + w->SetUseEbtablesLegacy(use_ebtables_legacy_); + w->SetHasIpv4(use_ipv4_bridge_); + w->SetHasIpv6(use_ipv6_bridge_); + res = w; + allocatedIface = res->AcquireResource(); + pending_add_.insert({resource_id, res}); + break; + } + case IfaceType::wbr: + case IfaceType::ebr: + allocatedIface = CreateBridge(iface); + break; + case IfaceType::Invalid: + break; + } + } else { + LOG(WARNING) << "Interface already in use: " << iface; + } + + if (didInsert && !allocatedIface) { + LOG(WARNING) << "Failed to allocate interface: " << iface; + active_interfaces_.erase(iface); + auto it = pending_add_.find(resource_id); + it->second->ReleaseResource(); + pending_add_.erase(it); + } + + LOG(INFO) << "Finish CreateInterface Request"; + + return allocatedIface; +} + +bool ResourceManager::RemoveInterface(const std::string& iface, IfaceType ty) { + bool isManagedIface = active_interfaces_.erase(iface) > 0; + bool removedIface = false; + if (isManagedIface) { + switch (ty) { + case IfaceType::wifiap: + // TODO(seungjaeyoo) : Support RemoveInterface for wifiap + break; + case IfaceType::mtap: { + // TODO(seungjaeyoo) : Support RemoveInterface for mtap uses IP prefix + // different from kMobileIp. + const char* idp = iface.c_str() + (iface.size() - 3); + int id = atoi(idp); + removedIface = DestroyMobileIface(iface, id, kMobileIp); + break; + } + case IfaceType::wtap: + case IfaceType::etap: + removedIface = DestroyEthernetIface( + iface, use_ipv4_bridge_, use_ipv6_bridge_, use_ebtables_legacy_); + break; + case IfaceType::wbr: + case IfaceType::ebr: + removedIface = DestroyBridge(iface); + break; + case IfaceType::Invalid: + break; + } + + } else { + LOG(WARNING) << "Interface not managed: " << iface; + } + + if (removedIface) { + LOG(INFO) << "Removed interface: " << iface; + } else { + LOG(WARNING) << "Could not remove interface: " << iface; + } + + return isManagedIface; +} + +bool ResourceManager::ValidateRequestList(const Json::Value& config) { + if (!config.isMember("request_list") || !config["request_list"].isArray()) { + LOG(WARNING) << "Request has invalid 'request_list' field"; + return false; + } + + auto request_list = config["request_list"]; + + Json::ArrayIndex size = request_list.size(); + if (size == 0) { + LOG(WARNING) << "Request has empty 'request_list' field"; + return false; + } + + for (Json::ArrayIndex i = 0; i < size; ++i) { + if (!ValidateRequest(request_list[i])) { + return false; + } + } + + return true; +} + +bool ResourceManager::ValidateConfigRequest(const Json::Value& config) { + if (!config.isMember("config_request") || + !config["config_request"].isObject()) { + LOG(WARNING) << "Request has invalid 'config_request' field"; + return false; + } + + Json::Value config_request = config["config_request"]; + + return ValidateRequestList(config_request); +} + +bool ResourceManager::ValidateRequest(const Json::Value& request) { + if (!request.isMember("request_type") || + !request["request_type"].isString() || + StrToReqTy(request["request_type"].asString()) == RequestType::Invalid) { + LOG(WARNING) << "Request has invalid 'request_type' field"; + return false; + } + return true; +} + +void ResourceManager::JsonServer() { + LOG(INFO) << "Starting server on " << location_; + auto server = + SharedFD::SocketLocalServer(location_, false, SOCK_STREAM, kSocketMode); + CHECK(server->IsOpen()) << "Could not start server at " << location_; + LOG(INFO) << "Accepting client connections"; + + while (true) { + auto client_socket = SharedFD::Accept(*server); + CHECK(client_socket->IsOpen()) << "Error creating client socket"; + + struct timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + + int err = client_socket->SetSockOpt(SOL_SOCKET, SO_RCVTIMEO, &timeout, + sizeof(timeout)); + if (err < 0) { + LOG(WARNING) << "Could not set socket timeout"; + continue; + } + + auto req_opt = RecvJsonMsg(client_socket); + + if (!req_opt) { + LOG(WARNING) << "Invalid JSON Request, closing connection"; + continue; + } + + Json::Value req = req_opt.value(); + + if (!ValidateConfigRequest(req)) { + continue; + } + + Json::Value req_list = req["config_request"]["request_list"]; + + Json::Value config_response; + Json::Value response_list; + Json::ArrayIndex req_list_size = req_list.size(); + + // sentinel value, so we can populate the list of responses correctly + // without trying to satisfy requests that will be aborted + bool transaction_failed = false; + + for (Json::ArrayIndex i = 0; i < req_list_size; ++i) { + LOG(INFO) << "Processing Request: " << i; + auto req = req_list[i]; + auto req_ty_str = req["request_type"].asString(); + auto req_ty = StrToReqTy(req_ty_str); + + Json::Value response; + if (transaction_failed) { + response["request_type"] = req_ty_str; + response["request_status"] = "pending"; + response["error"] = ""; + response_list.append(response); + continue; + } + + switch (req_ty) { + case RequestType::ID: { + response = JsonHandleIdRequest(); + break; + } + case RequestType::Shutdown: { + if (i != 0 || req_list_size != 1) { + response["request_type"] = req_ty_str; + response["request_status"] = "failed"; + response["error"] = + "Shutdown requests cannot be processed with other " + "configuration requests"; + response_list.append(response); + break; + } else { + response = JsonHandleShutdownRequest(client_socket); + response_list.append(response); + return; + } + } + case RequestType::CreateInterface: { + response = JsonHandleCreateInterfaceRequest(client_socket, req); + break; + } + case RequestType::DestroyInterface: { + response = JsonHandleDestroyInterfaceRequest(req); + break; + } + case RequestType::StopSession: { + response = JsonHandleStopSessionRequest( + req, GetUserIDFromSock(client_socket)); + break; + } + case RequestType::Invalid: { + LOG(WARNING) << "Invalid Request Type: " << req["request_type"]; + break; + } + } + + response_list.append(response); + if (!(response["request_status"].asString() == + StatusToStr(RequestStatus::Success))) { + LOG(INFO) << "Request failed:" << req; + transaction_failed = true; + continue; + } + } + + config_response["response_list"] = response_list; + + auto status = + transaction_failed ? RequestStatus::Failure : RequestStatus::Success; + config_response["config_status"] = StatusToStr(status); + + if (!transaction_failed) { + auto session_id = AllocateSessionID(); + config_response["session_id"] = session_id; + auto s = std::make_shared(session_id, + GetUserIDFromSock(client_socket)); + + // commit the resources + s->Insert(pending_add_); + pending_add_.clear(); + managed_sessions_.insert({session_id, s}); + } else { + // be sure to release anything we've acquired if the transaction failed + for (auto& droped_resource : pending_add_) { + droped_resource.second->ReleaseResource(); + } + } + + SendJsonMsg(client_socket, config_response); + LOG(INFO) << "Closing connection to client"; + client_socket->Close(); + } + server->Close(); +} + +uid_t GetUserIDFromSock(SharedFD client_socket) { + struct ucred ucred {}; + socklen_t len = sizeof(struct ucred); + + if (-1 == client_socket->GetSockOpt(SOL_SOCKET, SO_PEERCRED, &ucred, &len)) { + LOG(WARNING) << "Failed to get Socket Credentials"; + return -1; + } + + return ucred.uid; +} + +bool ResourceManager::CheckCredentials(SharedFD client_socket, uid_t uid) { + uid_t sock_uid = GetUserIDFromSock(client_socket); + + if (sock_uid == -1) { + LOG(WARNING) << "Invalid Socket UID: " << uid; + return false; + } + + if (uid != sock_uid) { + LOG(WARNING) << "Message UID: " << uid + << " does not match socket's EUID: " << sock_uid; + return false; + } + + return true; +} + +Json::Value ResourceManager::JsonHandleIdRequest() { + Json::Value resp; + resp["request_type"] = "allocate_id"; + resp["request_status"] = StatusToStr(RequestStatus::Success); + resp["id"] = AllocateSessionID(); + return resp; +} + +Json::Value ResourceManager::JsonHandleShutdownRequest(SharedFD client_socket) { + LOG(INFO) << "Received Shutdown Request"; + shutdown_socket_ = client_socket; + + Json::Value resp; + resp["request_type"] = "shutdown"; + resp["request_status"] = "pending"; + resp["error"] = ""; + + return resp; +} + +Json::Value ResourceManager::JsonHandleCreateInterfaceRequest( + SharedFD client_socket, const Json::Value& request) { + LOG(INFO) << "Received CreateInterface Request"; + + Json::Value resp; + resp["request_type"] = "create_interface"; + resp["iface_name"] = ""; + resp["request_status"] = StatusToStr(RequestStatus::Failure); + resp["error"] = "unknown"; + + if (!request.isMember("uid") || !request["uid"].isUInt()) { + auto err_msg = "Input event doesn't have a valid 'uid' field"; + LOG(WARNING) << err_msg; + resp["error"] = err_msg; + return resp; + } + + if (!request.isMember("iface_type") || !request["iface_type"].isString()) { + auto err_msg = "Input event doesn't have a valid 'iface_type' field"; + LOG(WARNING) << err_msg; + resp["error"] = err_msg; + return resp; + } + + auto uid = request["uid"].asUInt(); + + if (!CheckCredentials(client_socket, uid)) { + auto err_msg = "Credential check failed"; + LOG(WARNING) << err_msg; + resp["error"] = err_msg; + return resp; + } + + auto user_opt = GetUserName(uid); + + bool addedIface = false; + std::stringstream ss; + if (!user_opt) { + auto err_msg = "UserName could not be matched to UID"; + LOG(WARNING) << err_msg; + resp["error"] = err_msg; + return resp; + } else { + auto iface_ty_name = request["iface_type"].asString(); + resp["iface_type"] = iface_ty_name; + auto iface_type = StrToIfaceTy(iface_ty_name); + auto attempts = kMaxIfaceNameId; + do { + auto id = AllocateResourceID(); + resp["resource_id"] = id; + ss << "cvd-" << iface_ty_name << "-" << user_opt.value().substr(0, 4) + << std::setfill('0') << std::setw(2) << (id % kMaxIfaceNameId); + addedIface = AddInterface(ss.str(), iface_type, id, uid); + --attempts; + } while (!addedIface && (attempts > 0)); + } + + if (addedIface) { + resp["request_status"] = StatusToStr(RequestStatus::Success); + resp["iface_name"] = ss.str(); + resp["error"] = ""; + } + + return resp; +} + +Json::Value ResourceManager::JsonHandleDestroyInterfaceRequest( + const Json::Value& request) { + Json::Value resp; + resp["request_type"] = "destroy_interface"; + resp["request_status"] = StatusToStr(RequestStatus::Failure); + if (!request.isMember("iface_name") || !request["iface_name"].isString()) { + auto err_msg = "Input event doesn't have a valid 'iface_name' field"; + LOG(WARNING) << err_msg; + resp["error"] = err_msg; + return resp; + } + + auto iface_name = request["iface_name"].asString(); + + bool isManagedIface = active_interfaces_.erase(iface_name) > 0; + + if (!isManagedIface) { + auto msg = "Interface not managed: " + iface_name; + LOG(WARNING) << msg; + resp["error"] = msg; + return resp; + } + + if (!request.isMember("session_id") || !request["session_id"].isUInt()) { + auto err_msg = "Input event doesn't have a valid 'session_id' field"; + LOG(WARNING) << err_msg; + resp["error"] = err_msg; + return resp; + } + + auto session_id = request["session_id"].asUInt(); + + auto resource_id = request["resource_id"].asUInt(); + + LOG(INFO) << "Received DestroyInterface Request for " << iface_name + << " in session: " << session_id + << ", resource_id: " << resource_id; + + auto sess_opt = FindSession(session_id); + if (!sess_opt) { + auto msg = "Interface " + iface_name + + " was not managed in session: " + std::to_string(session_id) + + " with resource_id: " + std::to_string(resource_id); + LOG(WARNING) << msg; + resp["error"] = msg; + return resp; + } + + auto s = sess_opt.value(); + + // while we could wait to see if any acquisitions fail and delay releasing + // resources until they are all finished, this operation is inherently + // destructive, so should a release operation fail, there is no satisfactory + // method for aborting the transaction. Instead, we try to release the + // resource and then can signal to the rest of the transaction the failure + // state, which can then just stop the transaction, and revert any newly + // acquired resources, but any successful drop requests will persist + auto did_drop_resource = s->ReleaseResource(resource_id); + + if (did_drop_resource) { + resp["request_status"] = StatusToStr(RequestStatus::Success); + } else { + auto msg = "Interface " + iface_name + + " was not managed in session: " + std::to_string(session_id) + + " with resource_id: " + std::to_string(resource_id); + LOG(WARNING) << msg; + resp["error"] = msg; + } + + return resp; +} + +Json::Value ResourceManager::JsonHandleStopSessionRequest( + const Json::Value& request, uid_t uid) { + Json::Value resp; + resp["request_type"] = ReqTyToStr(RequestType::StopSession); + resp["request_status"] = StatusToStr(RequestStatus::Failure); + if (!request.isMember("session_id") || !request["session_id"].isUInt()) { + auto err_msg = "Input event doesn't have a valid 'session_id' field"; + LOG(WARNING) << err_msg; + resp["error"] = err_msg; + return resp; + } + + auto session_id = request["session_id"].asUInt(); + LOG(INFO) << "Received StopSession Request for Session ID: " << session_id; + + auto it = managed_sessions_.find(session_id); + if (it == managed_sessions_.end()) { + auto msg = "Session not managed: " + std::to_string(session_id); + LOG(WARNING) << msg; + resp["error"] = msg; + return resp; + } + + if (it->second->GetUID() != uid) { + auto msg = "Effective user ID does not match session owner. socket uid: " + + std::to_string(uid); + LOG(WARNING) << msg; + resp["error"] = msg; + return resp; + } + + // while we could wait to see if any acquisitions fail and delay releasing + // resources until they are all finished, this operation is inherently + // destructive, so should a release operation fail, there is no satisfactory + // method for aborting the transaction. Instead, we try to release the + // resource and then can signal to the rest of the transaction the failure + // state + auto success = it->second->ReleaseAllResources(); + + // release the names from the global list for reuse in future requests + for (auto& iface : it->second->GetActiveInterfaces()) { + active_interfaces_.erase(iface); + } + + if (success) { + managed_sessions_.erase(it); + resp["request_status"] = StatusToStr(RequestStatus::Success); + } else { + resp["error"] = + "unknown, allocd experienced an error ending the session id: " + + std::to_string(session_id); + } + + return resp; +} + +std::optional> ResourceManager::FindSession( + uint32_t id) { + auto it = managed_sessions_.find(id); + if (it == managed_sessions_.end()) { + return std::nullopt; + } else { + return it->second; + } +} + +} // namespace cuttlefish diff --git a/base/cvd/allocd/resource_manager.h b/base/cvd/allocd/resource_manager.h new file mode 100644 index 0000000000..22a1d1cb15 --- /dev/null +++ b/base/cvd/allocd/resource_manager.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2020 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 + +#include + +#include +#include +#include +#include +#include +#include + +#include "alloc_utils.h" +#include "common/libs/fs/shared_fd.h" +#include "request.h" +#include "resource.h" +#include "utils.h" + +namespace cuttlefish { + +class Session { + public: + explicit Session(uint32_t session_id, uid_t uid) + : session_id_(session_id), uid_(uid) {} + ~Session() { ReleaseAllResources(); } + + uint32_t GetSessionID() { return session_id_; } + uid_t GetUID() { return uid_; } + + const std::set& GetActiveInterfaces() { + return active_interfaces_; + } + + void Insert( + const std::map>& resources) { + managed_resources_.insert(resources.begin(), resources.end()); + } + + bool ReleaseAllResources() { + bool success = true; + for (auto& res : managed_resources_) { + success &= res.second->ReleaseResource(); + } + managed_resources_.clear(); + + return success; + } + + bool ReleaseResource(uint32_t resource_id) { + auto it = managed_resources_.find(resource_id); + if (it == managed_resources_.end()) { + return false; + } + + auto success = it->second->ReleaseResource(); + if (success) { + managed_resources_.erase(it); + } + + return success; + } + + private: + uint32_t session_id_{}; + uid_t uid_{}; + std::set active_interfaces_; + std::map> managed_resources_; +}; + +/* Manages static resources while the daemon is running. + * When resources, such as network interfaces are requested the ResourceManager + * allocates the resources and takes ownership of them. It will keep maintain + * the resource, until requested to release it(i.e. destroy it and/or tear down + * related config). When the daemon is stopped, it will walk its list of owned + * resources, and deallocate them from the system. + * + * Clients can request new resources by connecting to a socket, and sending a + * JSON request, detailing the type of resource required. + */ +struct ResourceManager { + public: + ResourceManager() = default; + + ~ResourceManager(); + + void SetSocketLocation(const std::string& sock_name); + + void SetUseEbtablesLegacy(bool use_legacy); + + void JsonServer(); + + private: + uint32_t AllocateResourceID(); + uint32_t AllocateSessionID(); + + bool AddInterface(const std::string& iface, IfaceType ty, uint32_t id, + uid_t uid); + + bool RemoveInterface(const std::string& iface, IfaceType ty); + + bool ValidateRequest(const Json::Value& request); + + bool ValidateRequestList(const Json::Value& config); + + bool ValidateConfigRequest(const Json::Value& config); + + Json::Value JsonHandleIdRequest(); + + Json::Value JsonHandleShutdownRequest(SharedFD client_socket); + + Json::Value JsonHandleCreateInterfaceRequest(SharedFD client_socket, + const Json::Value& request); + + Json::Value JsonHandleDestroyInterfaceRequest(const Json::Value& request); + + Json::Value JsonHandleStopSessionRequest(const Json::Value& request, + uid_t uid); + + bool CheckCredentials(SharedFD client_socket, uid_t uid); + + void SetUseIpv4Bridge(bool ipv4) { use_ipv4_bridge_ = ipv4; } + + void SetUseIpv6Bridge(bool ipv6) { use_ipv6_bridge_ = ipv6; } + + std::optional> FindSession(uint32_t id); + + private: + std::atomic_uint32_t global_resource_id_ = 0; + std::atomic_uint32_t session_id_ = 0; + std::set active_interfaces_; + std::map> managed_sessions_; + std::map> pending_add_; + std::string location_ = kDefaultLocation; + bool use_ipv4_bridge_ = true; + bool use_ipv6_bridge_ = true; + bool use_ebtables_legacy_ = false; + cuttlefish::SharedFD shutdown_socket_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/allocd/test_client.cpp b/base/cvd/allocd/test_client.cpp new file mode 100644 index 0000000000..ac2051c129 --- /dev/null +++ b/base/cvd/allocd/test_client.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2020 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. + */ + +#include +#include +#include +#include + +#include + +#include "common/libs/fs/shared_fd.h" +#include "host/libs/config/logging.h" +#include "request.h" +#include "utils.h" + +using namespace cuttlefish; + +DEFINE_string(socket_path, kDefaultLocation, "Socket path"); +DEFINE_bool(id, false, "Request new UUID"); +DEFINE_bool(ifcreate, false, "Request a new Interface"); +DEFINE_bool(shutdown, false, "Shutdown Resource Allocation Server"); +DEFINE_bool(stop_session, false, "Remove all resources from session"); +DEFINE_string(ifdestroy, "", "Request an interface be destroyed"); +DEFINE_uint32(ifid, -1, "Global Resource ID"); +DEFINE_uint32(session, -1, "Session ID"); + +int main(int argc, char* argv[]) { + cuttlefish::DefaultSubprocessLogging(argv); + google::ParseCommandLineFlags(&argc, &argv, true); + + SharedFD monitor_socket = cuttlefish::SharedFD::SocketLocalClient( + FLAGS_socket_path, false, SOCK_STREAM); + if (!monitor_socket->IsOpen()) { + LOG(ERROR) << "Unable to connect to launcher monitor on " + << FLAGS_socket_path << ": " << monitor_socket->StrError(); + return 1; + } + + if (FLAGS_id) { + Json::Value req; + req["request_type"] = "allocate_id"; + SendJsonMsg(monitor_socket, req); + + auto resp_opt = RecvJsonMsg(monitor_socket); + if (!resp_opt.has_value()) { + std::cout << "Bad Response from server\n"; + return -1; + } + + auto resp = resp_opt.value(); + std::cout << resp << "\n"; + std::cout << "New ID operation: " << resp["request_status"] << std::endl; + std::cout << "New ID: " << resp["id"] << std::endl; + } + + Json::Value config; + Json::Value request_list; + + if (FLAGS_ifcreate) { + Json::Value req; + req["request_type"] = "create_interface"; + req["uid"] = geteuid(); + req["iface_type"] = "mtap"; + request_list.append(req); + req["iface_type"] = "wtap"; + request_list.append(req); + req["iface_type"] = "wifiap"; + request_list.append(req); + config["config_request"]["request_list"] = request_list; + + std::cout << config << "\n"; + SendJsonMsg(monitor_socket, config); + + auto resp_opt = RecvJsonMsg(monitor_socket); + if (!resp_opt.has_value()) { + std::cout << "Bad Response from server\n"; + return -1; + } + + auto resp = resp_opt.value(); + + std::cout << resp << "\n"; + std::cout << "Create Interface operation: " << resp["request_status"] + << std::endl; + std::cout << resp["iface_name"] << std::endl; + } + + if (!FLAGS_ifdestroy.empty() && (FLAGS_ifid != -1) && (FLAGS_session != -1)) { + Json::Value req; + req["request_type"] = "destroy_interface"; + req["iface_name"] = FLAGS_ifdestroy; + req["resource_id"] = FLAGS_ifid; + req["session_id"] = FLAGS_session; + request_list.append(req); + config["config_request"]["request_list"] = request_list; + SendJsonMsg(monitor_socket, config); + + LOG(INFO) << "Request Interface : '" << FLAGS_ifdestroy << "' be removed"; + + auto resp_opt = RecvJsonMsg(monitor_socket); + if (!resp_opt.has_value()) { + std::cout << "Bad Response from server\n"; + return -1; + } + + auto resp = resp_opt.value(); + + std::cout << resp << "\n"; + + std::cout << "Destroy Interface operation: " << resp["request_status"] + << std::endl; + std::cout << resp["iface_name"] << std::endl; + } + + if (FLAGS_stop_session && (FLAGS_session != -1)) { + Json::Value req; + req["request_type"] = "stop_session"; + req["session_id"] = FLAGS_session; + request_list.append(req); + config["config_request"]["request_list"] = request_list; + SendJsonMsg(monitor_socket, config); + + LOG(INFO) << "Request Session : '" << FLAGS_session << "' be stopped"; + + auto resp_opt = RecvJsonMsg(monitor_socket); + if (!resp_opt.has_value()) { + std::cout << "Bad Response from server\n"; + return -1; + } + + auto resp = resp_opt.value(); + + std::cout << resp << "\n"; + std::cout << "Stop Session operation: " << resp["config_status"]; + } + + if (FLAGS_shutdown) { + Json::Value req; + req["request_type"] = "shutdown"; + + request_list.append(req); + config["config_request"]["request_list"] = request_list; + cuttlefish::SendJsonMsg(monitor_socket, config); + + auto resp_opt = cuttlefish::RecvJsonMsg(monitor_socket); + if (!resp_opt.has_value()) { + std::cout << "Bad Response from server\n"; + return -1; + } + + auto resp = resp_opt.value(); + + std::cout << resp << "\n"; + std::cout << "Shutdown operation: " << resp["request_status"] << std::endl; + } + + return 0; +} diff --git a/base/cvd/allocd/utils.cpp b/base/cvd/allocd/utils.cpp new file mode 100644 index 0000000000..71d3eb3b1b --- /dev/null +++ b/base/cvd/allocd/utils.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "utils.h" + +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "request.h" + +namespace cuttlefish { + +// While the JSON schema and payload structure are designed to be extensible, +// and avoid version incompatibility. However, should project requirements +// change, it is necessary that we have a mechanism to handle incompatibilities +// that arise over time. If an incompatibility should come about, the +// kMinHeaderVersion constant should be increased to match the new minimal set +// of features that are supported. + +/// Current supported Header version number +constexpr uint16_t kCurHeaderVersion = 1; + +/// Oldest compatible header version number +constexpr uint16_t kMinHeaderVersion = 1; + +const std::map RequestTyToStrMap = { + {RequestType::ID, "alloc_id"}, + {RequestType::CreateInterface, "create_interface"}, + {RequestType::DestroyInterface, "destroy_interface"}, + {RequestType::StopSession, "stop_session"}, + {RequestType::Shutdown, "shutdown"}, + {RequestType::Invalid, "invalid"}}; + +const std::map StrToRequestTyMap = { + {"alloc_id", RequestType::ID}, + {"create_interface", RequestType::CreateInterface}, + {"destroy_interface", RequestType::DestroyInterface}, + {"stop_session", RequestType::StopSession}, + {"shutdown", RequestType::Shutdown}, + {"invalid", RequestType::Invalid}}; + +const std::map StrToIfaceTyMap = { + {"invalid", IfaceType::Invalid}, {"mtap", IfaceType::mtap}, + {"wtap", IfaceType::wtap}, {"wifiap", IfaceType::wifiap}, + {"etap", IfaceType::etap}, {"wbr", IfaceType::wbr}, + {"ebr", IfaceType::ebr}}; + +const std::map IfaceTyToStrMap = { + {IfaceType::Invalid, "invalid"}, {IfaceType::mtap, "mtap"}, + {IfaceType::wtap, "wtap"}, {IfaceType::wifiap, "wifiap"}, + {IfaceType::etap, "etap"}, {IfaceType::wbr, "wbr"}, + {IfaceType::ebr, "ebr"}}; + +const std::map ReqStatusToStrMap = { + {RequestStatus::Invalid, "invalid"}, + {RequestStatus::Pending, "pending"}, + {RequestStatus::Failure, "failure"}, + {RequestStatus::Success, "success"}}; + +const std::map StrToReqStatusMap = { + {"invalid", RequestStatus::Invalid}, + {"pending", RequestStatus::Pending}, + {"failure", RequestStatus::Failure}, + {"success", RequestStatus::Success}}; + +bool SendJsonMsg(SharedFD client_socket, const Json::Value& resp) { + LOG(INFO) << "Sending JSON message"; + Json::StreamWriterBuilder factory; + auto resp_str = Json::writeString(factory, resp); + + std::string header_buff(sizeof(RequestHeader), 0); + + // fill in header + RequestHeader* header = reinterpret_cast(header_buff.data()); + header->len = resp_str.size(); + header->version = kCurHeaderVersion; + + auto payload = header_buff + resp_str; + + return SendAll(client_socket, payload); +} + +std::optional RecvJsonMsg(SharedFD client_socket) { + LOG(INFO) << "Receiving JSON message"; + RequestHeader header; + client_socket->Recv(&header, sizeof(header), kRecvFlags); + + if (header.version < kMinHeaderVersion) { + LOG(WARNING) << "bad request header version: " << header.version; + return std::nullopt; + } + + std::string payload = RecvAll(client_socket, header.len); + + JsonRequestReader reader; + return reader.parse(payload); +} + +std::string ReqTyToStr(RequestType req_ty) { + switch (req_ty) { + case RequestType::Invalid: + return "invalid"; + case RequestType::Shutdown: + return "shutdown"; + case RequestType::StopSession: + return "stop_session"; + case RequestType::DestroyInterface: + return "destroy_interface"; + case RequestType::CreateInterface: + return "create_interface"; + case RequestType::ID: + return "id"; + } +} + +RequestType StrToReqTy(const std::string& req) { + auto it = StrToRequestTyMap.find(req); + if (it == StrToRequestTyMap.end()) { + return RequestType::Invalid; + } else { + return it->second; + } +} + +RequestStatus StrToStatus(const std::string& st) { + auto it = StrToReqStatusMap.find(st); + if (it == StrToReqStatusMap.end()) { + return RequestStatus::Invalid; + } else { + return it->second; + } +} + +std::string StatusToStr(RequestStatus st) { + switch (st) { + case RequestStatus::Invalid: + return "invalid"; + case RequestStatus::Pending: + return "pending"; + case RequestStatus::Success: + return "success"; + case RequestStatus::Failure: + return "failure"; + } +} + +std::string IfaceTyToStr(IfaceType iface) { + switch (iface) { + case IfaceType::Invalid: + return "invalid"; + case IfaceType::mtap: + return "mtap"; + case IfaceType::wtap: + return "wtap"; + case IfaceType::wifiap: + return "wifiap"; + case IfaceType::etap: + return "etap"; + case IfaceType::wbr: + return "wbr"; + case IfaceType::ebr: + return "ebr"; + } +} + +IfaceType StrToIfaceTy(const std::string& iface) { + auto it = StrToIfaceTyMap.find(iface); + if (it == StrToIfaceTyMap.end()) { + return IfaceType::Invalid; + } else { + return it->second; + } +} + +} // namespace cuttlefish diff --git a/base/cvd/allocd/utils.h b/base/cvd/allocd/utils.h new file mode 100644 index 0000000000..60e57f6894 --- /dev/null +++ b/base/cvd/allocd/utils.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 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 + +#include +#include + +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "host/libs/config/logging.h" +#include "request.h" + +namespace cuttlefish { + +constexpr char kDefaultLocation[] = + "/var/run/cuttlefish/cuttlefish_allocd.sock"; + +// Default flags for send and receive. +static constexpr int kSendFlags = 0; +static constexpr int kRecvFlags = 0; + +/// Sends a Json value over client_socket +/// +/// returns true if successfully sent the whole JSON object +/// returns false otherwise +bool SendJsonMsg(cuttlefish::SharedFD client_socket, const Json::Value& resp); + +/// Receives a single Json value over client_socket +/// +/// The returned option will contain the JSON object when successful, +/// or an std::nullopt if an error is reported +std::optional RecvJsonMsg(cuttlefish::SharedFD client_socket); + +// Helper functions mapping between Enum types and std::string + +RequestType StrToReqTy(const std::string& req); + +std::string ReqTyToStr(RequestType req_ty); + +IfaceType StrToIfaceTy(const std::string& iface); + +std::string IfaceTyToStr(IfaceType iface); + +RequestStatus StrToStatus(const std::string& st); + +std::string StatusToStr(RequestStatus st); + +} // namespace cuttlefish diff --git a/base/cvd/android-base/cmsg.h b/base/cvd/android-base/cmsg.h new file mode 100644 index 0000000000..e4197b109b --- /dev/null +++ b/base/cvd/android-base/cmsg.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2019 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 + +#include +#include + +#include +#include + +#include +#include +#include + +namespace android { +namespace base { + +#if !defined(_WIN32) + +// Helpers for sending and receiving file descriptors across Unix domain sockets. +// +// The cmsg(3) API is very hard to get right, with multiple landmines that can +// lead to death. Almost all of the uses of cmsg in Android make at least one of +// the following mistakes: +// +// - not aligning the cmsg buffer +// - leaking fds if more fds are received than expected +// - blindly dereferencing CMSG_DATA without checking the header +// - using CMSG_SPACE instead of CMSG_LEN for .cmsg_len +// - using CMSG_LEN instead of CMSG_SPACE for .msg_controllen +// - using a length specified in number of fds instead of bytes +// +// These functions wrap the hard-to-use cmsg API with an easier to use abstraction. + +// Send file descriptors across a Unix domain socket. +// +// Note that the write can return short if the socket type is SOCK_STREAM. When +// this happens, file descriptors are still sent to the other end, but with +// truncated data. For this reason, using SOCK_SEQPACKET or SOCK_DGRAM is recommended. +ssize_t SendFileDescriptorVector(borrowed_fd sock, const void* data, size_t len, + const std::vector& fds); + +// Receive file descriptors from a Unix domain socket. +// +// If more FDs (or bytes, for datagram sockets) are received than expected, +// -1 is returned with errno set to EMSGSIZE, and all received FDs are thrown away. +ssize_t ReceiveFileDescriptorVector(borrowed_fd sock, void* data, size_t len, size_t max_fds, + std::vector* fds); + +// Helper for SendFileDescriptorVector that constructs a std::vector for you, e.g.: +// SendFileDescriptors(sock, "foo", 3, std::move(fd1), std::move(fd2)) +template +ssize_t SendFileDescriptors(borrowed_fd sock, const void* data, size_t len, Args&&... sent_fds) { + // Do not allow implicit conversion to int: people might try to do something along the lines of: + // SendFileDescriptors(..., std::move(a_unique_fd)) + // and be surprised when the unique_fd isn't closed afterwards. + AssertType(std::forward(sent_fds)...); + std::vector fds; + Append(fds, std::forward(sent_fds)...); + return SendFileDescriptorVector(sock, data, len, fds); +} + +// Helper for ReceiveFileDescriptorVector that receives an exact number of file descriptors. +// If more file descriptors are received than requested, -1 is returned with errno set to EMSGSIZE. +// If fewer file descriptors are received than requested, -1 is returned with errno set to ENOMSG. +// In both cases, all arguments are cleared and any received FDs are thrown away. +template +ssize_t ReceiveFileDescriptors(borrowed_fd sock, void* data, size_t len, Args&&... received_fds) { + std::vector fds; + Append(fds, std::forward(received_fds)...); + + std::vector result; + ssize_t rc = ReceiveFileDescriptorVector(sock, data, len, fds.size(), &result); + if (rc == -1 || result.size() != fds.size()) { + int err = rc == -1 ? errno : ENOMSG; + for (unique_fd* fd : fds) { + fd->reset(); + } + errno = err; + return -1; + } + + for (size_t i = 0; i < fds.size(); ++i) { + *fds[i] = std::move(result[i]); + } + return rc; +} + +#endif + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/collections.h b/base/cvd/android-base/collections.h new file mode 100644 index 0000000000..be0683ab96 --- /dev/null +++ b/base/cvd/android-base/collections.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 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 + +#include + +namespace android { +namespace base { + +// Helpers for converting a variadic template parameter pack to a homogeneous collection. +// Parameters must be implictly convertible to the contained type (including via move/copy ctors). +// +// Use as follows: +// +// template +// std::vector CreateVector(Args&&... args) { +// std::vector result; +// Append(result, std::forward(args)...); +// return result; +// } +template +void Append(CollectionType& collection, T&& arg) { + collection.push_back(std::forward(arg)); +} + +template +void Append(CollectionType& collection, T&& arg, Args&&... args) { + collection.push_back(std::forward(arg)); + return Append(collection, std::forward(args)...); +} + +// Assert that all of the arguments in a variadic template parameter pack are of a given type +// after std::decay. +template +void AssertType(Arg&&) { + static_assert(std::is_same::type>::value); +} + +template +void AssertType(Arg&&, Args&&... args) { + static_assert(std::is_same::type>::value); + AssertType(std::forward(args)...); +} + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/endian.h b/base/cvd/android-base/endian.h new file mode 100644 index 0000000000..b47494b59c --- /dev/null +++ b/base/cvd/android-base/endian.h @@ -0,0 +1,100 @@ +/* + * 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 + +/* A cross-platform equivalent of bionic's . */ + +/* For __BIONIC__ and __GLIBC__ */ +#include + +#if defined(__BIONIC__) + +#include + +#elif defined(__GLIBC__) || defined(ANDROID_HOST_MUSL) + +/* glibc and musl's are like bionic's . */ +#include + +/* glibc and musl keep htons and htonl in . */ +#include + +/* glibc and musl don't have the 64-bit variants. */ +#define htonq(x) htobe64(x) +#define ntohq(x) be64toh(x) + +#if defined(__GLIBC__) +/* glibc has different names to BSD for these. */ +#define betoh16(x) be16toh(x) +#define betoh32(x) be32toh(x) +#define betoh64(x) be64toh(x) +#define letoh16(x) le16toh(x) +#define letoh32(x) le32toh(x) +#define letoh64(x) le64toh(x) +#endif + +#else + +#if defined(__APPLE__) +/* macOS has some of the basics. */ +#include +#else +/* Windows has some of the basics as well. */ +#include +#include +/* winsock2.h *must* be included before the following four macros. */ +#define htons(x) __builtin_bswap16(x) +#define htonl(x) __builtin_bswap32(x) +#define ntohs(x) __builtin_bswap16(x) +#define ntohl(x) __builtin_bswap32(x) +#endif + +/* Neither macOS nor Windows have the rest. */ + +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#define __BYTE_ORDER __LITTLE_ENDIAN + +#define htonq(x) __builtin_bswap64(x) + +#define ntohq(x) __builtin_bswap64(x) + +#define htobe16(x) __builtin_bswap16(x) +#define htobe32(x) __builtin_bswap32(x) +#define htobe64(x) __builtin_bswap64(x) + +#define betoh16(x) __builtin_bswap16(x) +#define betoh32(x) __builtin_bswap32(x) +#define betoh64(x) __builtin_bswap64(x) + +#define htole16(x) (x) +#define htole32(x) (x) +#define htole64(x) (x) + +#define letoh16(x) (x) +#define letoh32(x) (x) +#define letoh64(x) (x) + +#define be16toh(x) __builtin_bswap16(x) +#define be32toh(x) __builtin_bswap32(x) +#define be64toh(x) __builtin_bswap64(x) + +#define le16toh(x) (x) +#define le32toh(x) (x) +#define le64toh(x) (x) + +#endif diff --git a/base/cvd/android-base/errno_restorer.h b/base/cvd/android-base/errno_restorer.h new file mode 100644 index 0000000000..2689505aff --- /dev/null +++ b/base/cvd/android-base/errno_restorer.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 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 + +#include "errno.h" + +#include "android-base/macros.h" + +namespace android { +namespace base { + +class ErrnoRestorer { + public: + ErrnoRestorer() : saved_errno_(errno) {} + + ~ErrnoRestorer() { errno = saved_errno_; } + + // Allow this object to be used as part of && operation. + explicit operator bool() const { return true; } + + private: + const int saved_errno_; + + DISALLOW_COPY_AND_ASSIGN(ErrnoRestorer); +}; + +} // namespace base +} // namespace android diff --git a/allocd-port/include/android-base/errors.h b/base/cvd/android-base/errors.h similarity index 66% rename from allocd-port/include/android-base/errors.h rename to base/cvd/android-base/errors.h index 2467686cf7..99029d16de 100644 --- a/allocd-port/include/android-base/errors.h +++ b/base/cvd/android-base/errors.h @@ -44,8 +44,8 @@ std::string SystemErrorCodeToString(int error_code); } // namespace base } // namespace android -// Convenient macros for evaluating a statement, checking if the result is -// error, and returning it to the caller. +// Convenient macros for evaluating a statement, checking if the result is error, and returning it +// to the caller. // // Usage with Result: // @@ -67,53 +67,54 @@ std::string SystemErrorCodeToString(int error_code); // return OK; // } // -// Actually this can be used for any type as long as the OkOrFail contract is -// satisfied. See below. If implicit conversion compilation errors occur -// involving a value type with a templated forwarding ref ctor, compilation with -// cpp20 or explicitly converting to the desired return type is required. -#define OR_RETURN(expr) \ - ({ \ - decltype(expr)&& tmp = (expr); \ - typedef android::base::OkOrFail> \ - ok_or_fail; \ - if (!ok_or_fail::IsOk(tmp)) { \ - return ok_or_fail::Fail(std::move(tmp)); \ - } \ - ok_or_fail::Unwrap(std::move(tmp)); \ +// Actually this can be used for any type as long as the OkOrFail contract is satisfied. See +// below. +// If implicit conversion compilation errors occur involving a value type with a templated +// forwarding ref ctor, compilation with cpp20 or explicitly converting to the desired +// return type is required. +#define OR_RETURN(expr) \ + ({ \ + decltype(expr)&& __or_return_expr = (expr); \ + typedef android::base::OkOrFail> \ + ok_or_fail; \ + if (!ok_or_fail::IsOk(__or_return_expr)) { \ + return ok_or_fail::Fail(std::move(__or_return_expr)); \ + } \ + ok_or_fail::Unwrap(std::move(__or_return_expr)); \ }) // Same as OR_RETURN, but aborts if expr is a failure. #if defined(__BIONIC__) -#define OR_FATAL(expr) \ - ({ \ - decltype(expr)&& tmp = (expr); \ - typedef android::base::OkOrFail> \ - ok_or_fail; \ - if (!ok_or_fail::IsOk(tmp)) { \ - __assert(__FILE__, __LINE__, ok_or_fail::ErrorMessage(tmp).c_str()); \ - } \ - ok_or_fail::Unwrap(std::move(tmp)); \ +#define OR_FATAL(expr) \ + ({ \ + decltype(expr)&& __or_fatal_expr = (expr); \ + typedef android::base::OkOrFail> \ + ok_or_fail; \ + if (!ok_or_fail::IsOk(__or_fatal_expr)) { \ + __assert(__FILE__, __LINE__, ok_or_fail::ErrorMessage(__or_fatal_expr).c_str()); \ + } \ + ok_or_fail::Unwrap(std::move(__or_fatal_expr)); \ }) #else -#define OR_FATAL(expr) \ - ({ \ - decltype(expr)&& tmp = (expr); \ - typedef android::base::OkOrFail> \ - ok_or_fail; \ - if (!ok_or_fail::IsOk(tmp)) { \ - fprintf(stderr, "%s:%d: assertion \"%s\" failed", __FILE__, __LINE__, \ - ok_or_fail::ErrorMessage(tmp).c_str()); \ - abort(); \ - } \ - ok_or_fail::Unwrap(std::move(tmp)); \ +#define OR_FATAL(expr) \ + ({ \ + decltype(expr)&& __or_fatal_expr = (expr); \ + typedef android::base::OkOrFail> \ + ok_or_fail; \ + if (!ok_or_fail::IsOk(__or_fatal_expr)) { \ + fprintf(stderr, "%s:%d: assertion \"%s\" failed", __FILE__, __LINE__, \ + ok_or_fail::ErrorMessage(__or_fatal_expr).c_str()); \ + abort(); \ + } \ + ok_or_fail::Unwrap(std::move(__or_fatal_expr)); \ }) #endif namespace android { namespace base { -// The OkOrFail contract for a type T. This must be implemented for a type T if -// you want to use OR_RETURN(stmt) where stmt evalues to a value of type T. +// The OkOrFail contract for a type T. This must be implemented for a type T if you want to use +// OR_RETURN(stmt) where stmt evalues to a value of type T. template struct OkOrFail { // Checks if T is ok or fail. @@ -128,15 +129,15 @@ struct OkOrFail { OkOrFail() = delete; OkOrFail(const T&) = delete; - // And there need to be one or more conversion operators that turns the error - // value of T into a target type. For example, for T = Result, there can - // be ... + // And there need to be one or more conversion operators that turns the error value of T into a + // target type. For example, for T = Result, there can be ... // // // for the case where OR_RETURN is called in a function expecting E // operator E()&& { return val_.error().code(); } // - // // for the case where OR_RETURN is called in a function expecting Result template operator Result()&& { return val_.error(); } + // // for the case where OR_RETURN is called in a function expecting Result + // template + // operator Result()&& { return val_.error(); } // Returns the string representation of the fail value. static std::string ErrorMessage(const T& v); diff --git a/base/cvd/android-base/expected.h b/base/cvd/android-base/expected.h new file mode 100644 index 0000000000..3b9d45f18a --- /dev/null +++ b/base/cvd/android-base/expected.h @@ -0,0 +1,747 @@ +/* + * Copyright (C) 2019 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 + +#include +#include +#include +#include +#include + +// android::base::expected is an Android implementation of the std::expected +// proposal. +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0323r7.html +// +// Usage: +// using android::base::expected; +// using android::base::unexpected; +// +// expected safe_divide(double i, double j) { +// if (j == 0) return unexpected("divide by zero"); +// else return i / j; +// } +// +// void test() { +// auto q = safe_divide(10, 0); +// if (q.ok()) { printf("%f\n", q.value()); } +// else { printf("%s\n", q.error().c_str()); } +// } +// +// When the proposal becomes part of the standard and is implemented by +// libcxx, this will be removed and android::base::expected will be +// type alias to std::expected. +// + +namespace android { +namespace base { + +// Synopsis +template + class expected; + +template + class unexpected; +template + unexpected(E) -> unexpected; + +template + class bad_expected_access; + +template<> + class bad_expected_access; + +struct unexpect_t { + explicit unexpect_t() = default; +}; +inline constexpr unexpect_t unexpect{}; + +// macros for SFINAE +#define _ENABLE_IF(...) \ + , std::enable_if_t<(__VA_ARGS__)>* = nullptr + +// Define NODISCARD_EXPECTED to prevent expected from being +// ignored when used as a return value. This is off by default. +#ifdef NODISCARD_EXPECTED +#define _NODISCARD_ [[nodiscard]] +#else +#define _NODISCARD_ +#endif + +// Class expected +template +class _NODISCARD_ expected { + public: + using value_type = T; + using error_type = E; + using unexpected_type = unexpected; + + template + using rebind = expected; + + // constructors + constexpr expected() = default; + constexpr expected(const expected& rhs) = default; + constexpr expected(expected&& rhs) noexcept = default; + + template && + std::is_constructible_v && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !(!std::is_convertible_v || + !std::is_convertible_v) /* non-explicit */ + )> + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr expected(const expected& rhs) { + if (rhs.has_value()) var_ = rhs.value(); + else var_ = unexpected(rhs.error()); + } + + template && + std::is_constructible_v && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + (!std::is_convertible_v || + !std::is_convertible_v) /* explicit */ + )> + constexpr explicit expected(const expected& rhs) { + if (rhs.has_value()) var_ = rhs.value(); + else var_ = unexpected(rhs.error()); + } + + template && + std::is_constructible_v && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !(!std::is_convertible_v || + !std::is_convertible_v) /* non-explicit */ + )> + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr expected(expected&& rhs) { + if (rhs.has_value()) var_ = std::move(rhs.value()); + else var_ = unexpected(std::move(rhs.error())); + } + + template && + std::is_constructible_v && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_constructible_v&> && + !std::is_constructible_v&&> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + !std::is_convertible_v&, T> && + !std::is_convertible_v&&, T> && + (!std::is_convertible_v || + !std::is_convertible_v) /* explicit */ + )> + constexpr explicit expected(expected&& rhs) { + if (rhs.has_value()) var_ = std::move(rhs.value()); + else var_ = unexpected(std::move(rhs.error())); + } + + template && + !std::is_same_v>, std::in_place_t> && + !std::is_same_v, std::remove_cv_t>> && + !std::is_same_v, std::remove_cv_t>> && + std::is_convertible_v /* non-explicit */ + )> + // NOLINTNEXTLINE(google-explicit-constructor,bugprone-forwarding-reference-overload) + constexpr expected(U&& v) : var_(std::in_place_index<0>, std::forward(v)) {} + + template && + !std::is_same_v>, std::in_place_t> && + !std::is_same_v, std::remove_cv_t>> && + !std::is_same_v, std::remove_cv_t>> && + !std::is_convertible_v /* explicit */ + )> + // NOLINTNEXTLINE(bugprone-forwarding-reference-overload) + constexpr explicit expected(U&& v) : var_(std::in_place_index<0>, T(std::forward(v))) {} + + template && + std::is_convertible_v /* non-explicit */ + )> + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr expected(const unexpected& e) + : var_(std::in_place_index<1>, e.value()) {} + + template && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit expected(const unexpected& e) + : var_(std::in_place_index<1>, E(e.value())) {} + + template && + std::is_convertible_v /* non-explicit */ + )> + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr expected(unexpected&& e) + : var_(std::in_place_index<1>, std::move(e.value())) {} + + template && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit expected(unexpected&& e) + : var_(std::in_place_index<1>, E(std::move(e.value()))) {} + + template + )> + constexpr explicit expected(std::in_place_t, Args&&... args) + : var_(std::in_place_index<0>, std::forward(args)...) {} + + template&, Args...> + )> + constexpr explicit expected(std::in_place_t, std::initializer_list il, Args&&... args) + : var_(std::in_place_index<0>, il, std::forward(args)...) {} + + template + )> + constexpr explicit expected(unexpect_t, Args&&... args) + : var_(unexpected_type(std::forward(args)...)) {} + + template&, Args...> + )> + constexpr explicit expected(unexpect_t, std::initializer_list il, Args&&... args) + : var_(unexpected_type(il, std::forward(args)...)) {} + + // destructor + ~expected() = default; + + // assignment + // Note: SFNAIE doesn't work here because assignment operator should be + // non-template. We could workaround this by defining a templated parent class + // having the assignment operator. This incomplete implementation however + // doesn't allow us to copy assign expected even when T is non-copy + // assignable. The copy assignment will fail by the underlying std::variant + // anyway though the error message won't be clear. + expected& operator=(const expected& rhs) = default; + + // Note for SFNAIE above applies to here as well + expected& operator=(expected&& rhs) noexcept( + std::is_nothrow_move_assignable_v&& std::is_nothrow_move_assignable_v) = default; + + template && + !std::is_same_v, std::remove_cv_t>> && + !std::conjunction_v, std::is_same>> && + std::is_constructible_v && std::is_assignable_v && + std::is_nothrow_move_constructible_v)> + expected& operator=(U&& rhs) { + var_ = T(std::forward(rhs)); + return *this; + } + + template + expected& operator=(const unexpected& rhs) { + var_ = rhs; + return *this; + } + + template && + std::is_move_assignable_v + )> + expected& operator=(unexpected&& rhs) { + var_ = std::move(rhs); + return *this; + } + + // modifiers + template + )> + T& emplace(Args&&... args) { + expected(std::in_place, std::forward(args)...).swap(*this); + return value(); + } + + template&, Args...> + )> + T& emplace(std::initializer_list il, Args&&... args) { + expected(std::in_place, il, std::forward(args)...).swap(*this); + return value(); + } + + // swap + template && + std::is_swappable_v && + (std::is_move_constructible_v || + std::is_move_constructible_v))>> + void swap(expected& rhs) noexcept( + std::is_nothrow_move_constructible_v && + std::is_nothrow_swappable_v && + std::is_nothrow_move_constructible_v && + std::is_nothrow_swappable_v) { + var_.swap(rhs.var_); + } + + // observers + constexpr const T* operator->() const { return std::addressof(value()); } + constexpr T* operator->() { return std::addressof(value()); } + constexpr const T& operator*() const& { return value(); } + constexpr T& operator*() & { return value(); } + constexpr const T&& operator*() const&& { return std::move(std::get(var_)); } + constexpr T&& operator*() && { return std::move(std::get(var_)); } + + constexpr bool has_value() const noexcept { return var_.index() == 0; } + constexpr bool ok() const noexcept { return has_value(); } + + constexpr const T& value() const& { return std::get(var_); } + constexpr T& value() & { return std::get(var_); } + constexpr const T&& value() const&& { return std::move(std::get(var_)); } + constexpr T&& value() && { return std::move(std::get(var_)); } + + constexpr const E& error() const& { return std::get(var_).value(); } + constexpr E& error() & { return std::get(var_).value(); } + constexpr const E&& error() const&& { return std::move(std::get(var_)).value(); } + constexpr E&& error() && { return std::move(std::get(var_)).value(); } + + template && + std::is_convertible_v + )> + constexpr T value_or(U&& v) const& { + if (has_value()) return value(); + else return static_cast(std::forward(v)); + } + + template && + std::is_convertible_v + )> + constexpr T value_or(U&& v) && { + if (has_value()) return std::move(value()); + else return static_cast(std::forward(v)); + } + + // expected equality operators + template + friend constexpr bool operator==(const expected& x, const expected& y); + template + friend constexpr bool operator!=(const expected& x, const expected& y); + + // Comparison with unexpected + template + friend constexpr bool operator==(const expected&, const unexpected&); + template + friend constexpr bool operator==(const unexpected&, const expected&); + template + friend constexpr bool operator!=(const expected&, const unexpected&); + template + friend constexpr bool operator!=(const unexpected&, const expected&); + + // Specialized algorithms + template + friend void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))); + + private: + std::variant var_; +}; + +template +constexpr bool operator==(const expected& x, const expected& y) { + if (x.has_value() != y.has_value()) return false; + if (!x.has_value()) return x.error() == y.error(); + return *x == *y; +} + +template +constexpr bool operator!=(const expected& x, const expected& y) { + return !(x == y); +} + +// Comparison with unexpected +template +constexpr bool operator==(const expected& x, const unexpected& y) { + return !x.has_value() && (x.error() == y.value()); +} +template +constexpr bool operator==(const unexpected& x, const expected& y) { + return !y.has_value() && (x.value() == y.error()); +} +template +constexpr bool operator!=(const expected& x, const unexpected& y) { + return x.has_value() || (x.error() != y.value()); +} +template +constexpr bool operator!=(const unexpected& x, const expected& y) { + return y.has_value() || (x.value() != y.error()); +} + +template +void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))) { + x.swap(y); +} + +template +class _NODISCARD_ expected { + public: + using value_type = void; + using error_type = E; + using unexpected_type = unexpected; + + // constructors + constexpr expected() = default; + constexpr expected(const expected& rhs) = default; + constexpr expected(expected&& rhs) noexcept = default; + + template && + std::is_convertible_v /* non-explicit */ + )> + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr expected(const expected& rhs) { + if (!rhs.has_value()) var_ = unexpected(rhs.error()); + } + + template && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit expected(const expected& rhs) { + if (!rhs.has_value()) var_ = unexpected(rhs.error()); + } + + template && + std::is_convertible_v /* non-explicit */ + )> + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr expected(expected&& rhs) { + if (!rhs.has_value()) var_ = unexpected(std::move(rhs.error())); + } + + template && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit expected(expected&& rhs) { + if (!rhs.has_value()) var_ = unexpected(std::move(rhs.error())); + } + + template && + std::is_convertible_v /* non-explicit */ + )> + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr expected(const unexpected& e) + : var_(std::in_place_index<1>, e.value()) {} + + template && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit expected(const unexpected& e) + : var_(std::in_place_index<1>, E(e.value())) {} + + template && + std::is_convertible_v /* non-explicit */ + )> + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr expected(unexpected&& e) + : var_(std::in_place_index<1>, std::move(e.value())) {} + + template && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit expected(unexpected&& e) + : var_(std::in_place_index<1>, E(std::move(e.value()))) {} + + template + constexpr explicit expected(std::in_place_t, Args&&...) {} + + template + )> + constexpr explicit expected(unexpect_t, Args&&... args) + : var_(unexpected_type(std::forward(args)...)) {} + + template&, Args...> + )> + constexpr explicit expected(unexpect_t, std::initializer_list il, Args&&... args) + : var_(unexpected_type(il, std::forward(args)...)) {} + + // destructor + ~expected() = default; + + // assignment + // Note: SFNAIE doesn't work here because assignment operator should be + // non-template. We could workaround this by defining a templated parent class + // having the assignment operator. This incomplete implementation however + // doesn't allow us to copy assign expected even when T is non-copy + // assignable. The copy assignment will fail by the underlying std::variant + // anyway though the error message won't be clear. + expected& operator=(const expected& rhs) = default; + + // Note for SFNAIE above applies to here as well + expected& operator=(expected&& rhs) noexcept(std::is_nothrow_move_assignable_v) = default; + + template + expected& operator=(const unexpected& rhs) { + var_ = rhs; + return *this; + } + + template && + std::is_move_assignable_v + )> + expected& operator=(unexpected&& rhs) { + var_ = std::move(rhs); + return *this; + } + + // modifiers + void emplace() { + var_ = std::monostate(); + } + + // swap + template> + > + void swap(expected& rhs) noexcept(std::is_nothrow_move_constructible_v) { + var_.swap(rhs.var_); + } + + // observers + constexpr bool has_value() const noexcept { return var_.index() == 0; } + constexpr bool ok() const noexcept { return has_value(); } + + constexpr void value() const& { if (!has_value()) std::get<0>(var_); } + + constexpr const E& error() const& { return std::get(var_).value(); } + constexpr E& error() & { return std::get(var_).value(); } + constexpr const E&& error() const&& { return std::move(std::get(var_)).value(); } + constexpr E&& error() && { return std::move(std::get(var_)).value(); } + + // expected equality operators + template + friend constexpr bool operator==(const expected& x, const expected& y); + + // Specialized algorithms + template + friend void swap(expected& x, expected& y) noexcept(noexcept(x.swap(y))); + + private: + std::variant var_; +}; + +template +constexpr bool operator==(const expected& x, const expected& y) { + if (x.has_value() != y.has_value()) return false; + if (!x.has_value()) return x.error() == y.error(); + return true; +} + +template +constexpr bool operator==(const expected& x, const expected& y) { + if (x.has_value() != y.has_value()) return false; + if (!x.has_value()) return x.error() == y.error(); + return false; +} + +template +constexpr bool operator==(const expected& x, const expected& y) { + if (x.has_value() != y.has_value()) return false; + if (!x.has_value()) return x.error() == y.error(); + return false; +} + +template +class unexpected { + public: + // constructors + constexpr unexpected(const unexpected&) = default; + constexpr unexpected(unexpected&&) noexcept(std::is_nothrow_move_constructible_v) = default; + + template && + !std::is_same_v>, std::in_place_t> && + !std::is_same_v>, unexpected>)> + // NOLINTNEXTLINE(google-explicit-constructor,bugprone-forwarding-reference-overload) + constexpr unexpected(Err&& e) : val_(std::forward(e)) {} + + template&, Args...> + )> + constexpr explicit unexpected(std::in_place_t, std::initializer_list il, Args&&... args) + : val_(il, std::forward(args)...) {} + + template && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + std::is_convertible_v /* non-explicit */ + )> + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr unexpected(const unexpected& rhs) + : val_(rhs.value()) {} + + template && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit unexpected(const unexpected& rhs) + : val_(E(rhs.value())) {} + + template && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + std::is_convertible_v /* non-explicit */ + )> + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr unexpected(unexpected&& rhs) + : val_(std::move(rhs.value())) {} + + template && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_constructible_v&> && + !std::is_constructible_v> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v&, E> && + !std::is_convertible_v, E> && + !std::is_convertible_v /* explicit */ + )> + constexpr explicit unexpected(unexpected&& rhs) + : val_(E(std::move(rhs.value()))) {} + + // assignment + constexpr unexpected& operator=(const unexpected&) = default; + constexpr unexpected& operator=(unexpected&&) noexcept(std::is_nothrow_move_assignable_v) = + default; + template + constexpr unexpected& operator=(const unexpected& rhs) { + val_ = rhs.value(); + return *this; + } + template + constexpr unexpected& operator=(unexpected&& rhs) { + val_ = std::forward(rhs.value()); + return *this; + } + + // observer + constexpr const E& value() const& noexcept { return val_; } + constexpr E& value() & noexcept { return val_; } + constexpr const E&& value() const&& noexcept { return std::move(val_); } + constexpr E&& value() && noexcept { return std::move(val_); } + + void swap(unexpected& other) noexcept(std::is_nothrow_swappable_v) { + std::swap(val_, other.val_); + } + + template + friend constexpr bool + operator==(const unexpected& e1, const unexpected& e2); + template + friend constexpr bool + operator!=(const unexpected& e1, const unexpected& e2); + + template + friend void swap(unexpected& x, unexpected& y) noexcept(noexcept(x.swap(y))); + + private: + E val_; +}; + +template +constexpr bool +operator==(const unexpected& e1, const unexpected& e2) { + return e1.value() == e2.value(); +} + +template +constexpr bool +operator!=(const unexpected& e1, const unexpected& e2) { + return e1.value() != e2.value(); +} + +template +void swap(unexpected& x, unexpected& y) noexcept(noexcept(x.swap(y))) { + x.swap(y); +} + +// TODO: bad_expected_access class + +#undef _ENABLE_IF +#undef _NODISCARD_ + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/file.cpp b/base/cvd/android-base/file.cpp new file mode 100644 index 0000000000..e433a0771b --- /dev/null +++ b/base/cvd/android-base/file.cpp @@ -0,0 +1,680 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "android-base/file.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if defined(__APPLE__) +#include +#endif +#if defined(_WIN32) +#include +#include +#define O_NOFOLLOW 0 +#define OS_PATH_SEPARATOR '\\' +#else +#define OS_PATH_SEPARATOR '/' +#endif + +#include "android-base/logging.h" // and must be after windows.h for ERROR +#include "android-base/macros.h" // For TEMP_FAILURE_RETRY on Darwin. +#include "android-base/unique_fd.h" +#include "android-base/utf8.h" + +namespace { + +#ifdef _WIN32 +static int mkstemp(char* name_template, size_t size_in_chars) { + std::wstring path; + CHECK(android::base::UTF8ToWide(name_template, &path)) + << "path can't be converted to wchar: " << name_template; + if (_wmktemp_s(path.data(), path.size() + 1) != 0) { + return -1; + } + + // Use open() to match the close() that TemporaryFile's destructor does. + // Use O_BINARY to match base file APIs. + int fd = _wopen(path.c_str(), O_CREAT | O_EXCL | O_RDWR | O_BINARY, S_IRUSR | S_IWUSR); + if (fd < 0) { + return -1; + } + + std::string path_utf8; + CHECK(android::base::WideToUTF8(path, &path_utf8)) << "path can't be converted to utf8"; + CHECK(strcpy_s(name_template, size_in_chars, path_utf8.c_str()) == 0) + << "utf8 path can't be assigned back to name_template"; + + return fd; +} + +static char* mkdtemp(char* name_template, size_t size_in_chars) { + std::wstring path; + CHECK(android::base::UTF8ToWide(name_template, &path)) + << "path can't be converted to wchar: " << name_template; + + if (_wmktemp_s(path.data(), path.size() + 1) != 0) { + return nullptr; + } + + if (_wmkdir(path.c_str()) != 0) { + return nullptr; + } + + std::string path_utf8; + CHECK(android::base::WideToUTF8(path, &path_utf8)) << "path can't be converted to utf8"; + CHECK(strcpy_s(name_template, size_in_chars, path_utf8.c_str()) == 0) + << "utf8 path can't be assigned back to name_template"; + + return name_template; +} +#endif + +std::string GetSystemTempDir() { +#if defined(__ANDROID__) + const auto* tmpdir = getenv("TMPDIR"); + if (tmpdir == nullptr) tmpdir = "/data/local/tmp"; + if (access(tmpdir, R_OK | W_OK | X_OK) == 0) { + return tmpdir; + } + // Tests running in app context can't access /data/local/tmp, + // so try current directory if /data/local/tmp is not accessible. + return "."; +#elif defined(_WIN32) + wchar_t tmp_dir_w[MAX_PATH]; + DWORD result = GetTempPathW(std::size(tmp_dir_w), tmp_dir_w); // checks TMP env + CHECK_NE(result, 0ul) << "GetTempPathW failed, error: " << GetLastError(); + CHECK_LT(result, std::size(tmp_dir_w)) << "path truncated to: " << result; + + // GetTempPath() returns a path with a trailing slash, but init() + // does not expect that, so remove it. + if (tmp_dir_w[result - 1] == L'\\') { + tmp_dir_w[result - 1] = L'\0'; + } + + std::string tmp_dir; + CHECK(android::base::WideToUTF8(tmp_dir_w, &tmp_dir)) << "path can't be converted to utf8"; + + return tmp_dir; +#else + const auto* tmpdir = getenv("TMPDIR"); + if (tmpdir == nullptr) tmpdir = "/tmp"; + return tmpdir; +#endif +} + +} // namespace + +TemporaryFile::TemporaryFile() { + init(GetSystemTempDir()); +} + +TemporaryFile::TemporaryFile(const std::string& tmp_dir) { + init(tmp_dir); +} + +TemporaryFile::~TemporaryFile() { + if (fd != -1) { + close(fd); + } + if (remove_file_) { + unlink(path); + } +} + +int TemporaryFile::release() { + int result = fd; + fd = -1; + return result; +} + +void TemporaryFile::init(const std::string& tmp_dir) { + snprintf(path, sizeof(path), "%s%cTemporaryFile-XXXXXX", tmp_dir.c_str(), OS_PATH_SEPARATOR); +#if defined(_WIN32) + fd = mkstemp(path, sizeof(path)); +#else + fd = mkstemp(path); +#endif +} + +TemporaryDir::TemporaryDir() { + init(GetSystemTempDir()); +} + +TemporaryDir::~TemporaryDir() { + if (!remove_dir_and_contents_) return; + + auto callback = [](const char* child, const struct stat*, int file_type, struct FTW*) -> int { + switch (file_type) { + case FTW_D: + case FTW_DP: + case FTW_DNR: + if (rmdir(child) == -1) { + PLOG(ERROR) << "rmdir " << child; + } + break; + case FTW_NS: + default: + if (rmdir(child) != -1) break; + // FALLTHRU (for gcc, lint, pcc, etc; and following for clang) + FALLTHROUGH_INTENDED; + case FTW_F: + case FTW_SL: + case FTW_SLN: + if (unlink(child) == -1) { + PLOG(ERROR) << "unlink " << child; + } + break; + } + return 0; + }; + + nftw(path, callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); +} + +bool TemporaryDir::init(const std::string& tmp_dir) { + snprintf(path, sizeof(path), "%s%cTemporaryDir-XXXXXX", tmp_dir.c_str(), OS_PATH_SEPARATOR); +#if defined(_WIN32) + return (mkdtemp(path, sizeof(path)) != nullptr); +#else + return (mkdtemp(path) != nullptr); +#endif +} + +namespace android { +namespace base { + +// Versions of standard library APIs that support UTF-8 strings. +using namespace android::base::utf8; + +bool ReadFdToString(borrowed_fd fd, std::string* content) { + content->clear(); + + // Although original we had small files in mind, this code gets used for + // very large files too, where the std::string growth heuristics might not + // be suitable. https://code.google.com/p/android/issues/detail?id=258500. + struct stat sb; + if (fstat(fd.get(), &sb) != -1 && sb.st_size > 0) { + content->reserve(sb.st_size); + } + + char buf[4096] __attribute__((__uninitialized__)); + ssize_t n; + while ((n = TEMP_FAILURE_RETRY(read(fd.get(), &buf[0], sizeof(buf)))) > 0) { + content->append(buf, n); + } + return (n == 0) ? true : false; +} + +bool ReadFileToString(const std::string& path, std::string* content, bool follow_symlinks) { + content->clear(); + + int flags = O_RDONLY | O_CLOEXEC | O_BINARY | (follow_symlinks ? 0 : O_NOFOLLOW); + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), flags))); + if (fd == -1) { + return false; + } + return ReadFdToString(fd, content); +} + +bool WriteStringToFd(std::string_view content, borrowed_fd fd) { + const char* p = content.data(); + size_t left = content.size(); + while (left > 0) { + ssize_t n = TEMP_FAILURE_RETRY(write(fd.get(), p, left)); + if (n == -1) { + return false; + } + p += n; + left -= n; + } + return true; +} + +static bool CleanUpAfterFailedWrite(const std::string& path) { + // Something went wrong. Let's not leave a corrupt file lying around. + int saved_errno = errno; + unlink(path.c_str()); + errno = saved_errno; + return false; +} + +#if !defined(_WIN32) +bool WriteStringToFile(const std::string& content, const std::string& path, + mode_t mode, uid_t owner, gid_t group, + bool follow_symlinks) { + int flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY | + (follow_symlinks ? 0 : O_NOFOLLOW); + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), flags, mode))); + if (fd == -1) { + PLOG(ERROR) << "android::WriteStringToFile open failed"; + return false; + } + + // We do an explicit fchmod here because we assume that the caller really + // meant what they said and doesn't want the umask-influenced mode. + if (fchmod(fd, mode) == -1) { + PLOG(ERROR) << "android::WriteStringToFile fchmod failed"; + return CleanUpAfterFailedWrite(path); + } + if (fchown(fd, owner, group) == -1) { + PLOG(ERROR) << "android::WriteStringToFile fchown failed"; + return CleanUpAfterFailedWrite(path); + } + if (!WriteStringToFd(content, fd)) { + PLOG(ERROR) << "android::WriteStringToFile write failed"; + return CleanUpAfterFailedWrite(path); + } + return true; +} +#endif + +bool WriteStringToFile(const std::string& content, const std::string& path, + bool follow_symlinks) { + int flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY | + (follow_symlinks ? 0 : O_NOFOLLOW); + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), flags, 0666))); + if (fd == -1) { + return false; + } + return WriteStringToFd(content, fd) || CleanUpAfterFailedWrite(path); +} + +bool ReadFully(borrowed_fd fd, void* data, size_t byte_count) { + uint8_t* p = reinterpret_cast(data); + size_t remaining = byte_count; + while (remaining > 0) { + ssize_t n = TEMP_FAILURE_RETRY(read(fd.get(), p, remaining)); + if (n <= 0) return false; + p += n; + remaining -= n; + } + return true; +} + +#if defined(_WIN32) +// Windows implementation of pread. Note that this DOES move the file descriptors read position, +// but it does so atomically. +static ssize_t pread(borrowed_fd fd, void* data, size_t byte_count, off64_t offset) { + DWORD bytes_read; + OVERLAPPED overlapped; + memset(&overlapped, 0, sizeof(OVERLAPPED)); + overlapped.Offset = static_cast(offset); + overlapped.OffsetHigh = static_cast(offset >> 32); + if (!ReadFile(reinterpret_cast(_get_osfhandle(fd.get())), data, + static_cast(byte_count), &bytes_read, &overlapped)) { + // In case someone tries to read errno (since this is masquerading as a POSIX call) + errno = EIO; + return -1; + } + return static_cast(bytes_read); +} + +static ssize_t pwrite(borrowed_fd fd, const void* data, size_t byte_count, off64_t offset) { + DWORD bytes_written; + OVERLAPPED overlapped; + memset(&overlapped, 0, sizeof(OVERLAPPED)); + overlapped.Offset = static_cast(offset); + overlapped.OffsetHigh = static_cast(offset >> 32); + if (!WriteFile(reinterpret_cast(_get_osfhandle(fd.get())), data, + static_cast(byte_count), &bytes_written, &overlapped)) { + // In case someone tries to read errno (since this is masquerading as a POSIX call) + errno = EIO; + return -1; + } + return static_cast(bytes_written); +} +#endif + +bool ReadFullyAtOffset(borrowed_fd fd, void* data, size_t byte_count, off64_t offset) { + uint8_t* p = reinterpret_cast(data); + while (byte_count > 0) { + ssize_t n = TEMP_FAILURE_RETRY(pread(fd.get(), p, byte_count, offset)); + if (n <= 0) return false; + p += n; + byte_count -= n; + offset += n; + } + return true; +} + +bool WriteFullyAtOffset(borrowed_fd fd, const void* data, size_t byte_count, off64_t offset) { + const uint8_t* p = reinterpret_cast(data); + size_t remaining = byte_count; + while (remaining > 0) { + ssize_t n = TEMP_FAILURE_RETRY(pwrite(fd.get(), p, remaining, offset)); + if (n == -1) return false; + p += n; + remaining -= n; + offset += n; + } + return true; +} + +bool WriteFully(borrowed_fd fd, const void* data, size_t byte_count) { + const uint8_t* p = reinterpret_cast(data); + size_t remaining = byte_count; + while (remaining > 0) { + ssize_t n = TEMP_FAILURE_RETRY(write(fd.get(), p, remaining)); + if (n == -1) return false; + p += n; + remaining -= n; + } + return true; +} + +bool RemoveFileIfExists(const std::string& path, std::string* err) { + struct stat st; +#if defined(_WIN32) + // TODO: Windows version can't handle symbolic links correctly. + int result = stat(path.c_str(), &st); + bool file_type_removable = (result == 0 && S_ISREG(st.st_mode)); +#else + int result = lstat(path.c_str(), &st); + bool file_type_removable = (result == 0 && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))); +#endif + if (result == -1) { + if (errno == ENOENT || errno == ENOTDIR) return true; + if (err != nullptr) *err = strerror(errno); + return false; + } + + if (result == 0) { + if (!file_type_removable) { + if (err != nullptr) { + *err = "is not a regular file or symbolic link"; + } + return false; + } + if (unlink(path.c_str()) == -1) { + if (err != nullptr) { + *err = strerror(errno); + } + return false; + } + } + return true; +} + +#if !defined(_WIN32) +bool Readlink(const std::string& path, std::string* result) { + result->clear(); + + // Most Linux file systems (ext2 and ext4, say) limit symbolic links to + // 4095 bytes. Since we'll copy out into the string anyway, it doesn't + // waste memory to just start there. We add 1 so that we can recognize + // whether it actually fit (rather than being truncated to 4095). + std::vector buf(4095 + 1); + while (true) { + ssize_t size = readlink(path.c_str(), &buf[0], buf.size()); + // Unrecoverable error? + if (size == -1) return false; + // It fit! (If size == buf.size(), it may have been truncated.) + if (static_cast(size) < buf.size()) { + result->assign(&buf[0], size); + return true; + } + // Double our buffer and try again. + buf.resize(buf.size() * 2); + } +} +#endif + +#if !defined(_WIN32) +bool Realpath(const std::string& path, std::string* result) { + result->clear(); + + // realpath may exit with EINTR. Retry if so. + char* realpath_buf = nullptr; + do { + realpath_buf = realpath(path.c_str(), nullptr); + } while (realpath_buf == nullptr && errno == EINTR); + + if (realpath_buf == nullptr) { + return false; + } + result->assign(realpath_buf); + free(realpath_buf); + return true; +} +#endif + +std::string GetExecutablePath() { +#if defined(__linux__) + std::string path; + android::base::Readlink("/proc/self/exe", &path); + return path; +#elif defined(__APPLE__) + char path[PATH_MAX + 1]; + uint32_t path_len = sizeof(path); + int rc = _NSGetExecutablePath(path, &path_len); + if (rc < 0) { + std::unique_ptr path_buf(new char[path_len]); + _NSGetExecutablePath(path_buf.get(), &path_len); + return path_buf.get(); + } + return path; +#elif defined(_WIN32) + char path[PATH_MAX + 1]; + DWORD result = GetModuleFileName(NULL, path, sizeof(path) - 1); + if (result == 0 || result == sizeof(path) - 1) return ""; + path[PATH_MAX - 1] = 0; + return path; +#elif defined(__EMSCRIPTEN__) + abort(); +#else +#error unknown OS +#endif +} + +std::string GetExecutableDirectory() { + return Dirname(GetExecutablePath()); +} + +#if defined(_WIN32) +std::string Basename(std::string_view path) { + // TODO: how much of this is actually necessary for mingw? + + // Copy path because basename may modify the string passed in. + std::string result(path); + + // Use lock because basename() may write to a process global and return a + // pointer to that. Note that this locking strategy only works if all other + // callers to basename in the process also grab this same lock, but its + // better than nothing. Bionic's basename returns a thread-local buffer. + static std::mutex& basename_lock = *new std::mutex(); + std::lock_guard lock(basename_lock); + + // Note that if std::string uses copy-on-write strings, &str[0] will cause + // the copy to be made, so there is no chance of us accidentally writing to + // the storage for 'path'. + char* name = basename(&result[0]); + + // In case basename returned a pointer to a process global, copy that string + // before leaving the lock. + result.assign(name); + + return result; +} +#else +// Copied from bionic so that Basename() below can be portable and thread-safe. +static int _basename_r(const char* path, size_t path_size, char* buffer, size_t buffer_size) { + const char* startp = nullptr; + const char* endp = nullptr; + int len; + int result; + + // Empty or NULL string gets treated as ".". + if (path == nullptr || path_size == 0) { + startp = "."; + len = 1; + goto Exit; + } + + // Strip trailing slashes. + endp = path + path_size - 1; + while (endp > path && *endp == '/') { + endp--; + } + + // All slashes becomes "/". + if (endp == path && *endp == '/') { + startp = "/"; + len = 1; + goto Exit; + } + + // Find the start of the base. + startp = endp; + while (startp > path && *(startp - 1) != '/') { + startp--; + } + + len = endp - startp +1; + + Exit: + result = len; + if (buffer == nullptr) { + return result; + } + if (len > static_cast(buffer_size) - 1) { + len = buffer_size - 1; + result = -1; + errno = ERANGE; + } + + if (len >= 0) { + memcpy(buffer, startp, len); + buffer[len] = 0; + } + return result; +} +std::string Basename(std::string_view path) { + char buf[PATH_MAX] __attribute__((__uninitialized__)); + const auto size = _basename_r(path.data(), path.size(), buf, sizeof(buf)); + return size > 0 ? std::string(buf, size) : std::string(); +} +#endif + +#if defined(_WIN32) +std::string Dirname(std::string_view path) { + // TODO: how much of this is actually necessary for mingw? + + // Copy path because dirname may modify the string passed in. + std::string result(path); + + // Use lock because dirname() may write to a process global and return a + // pointer to that. Note that this locking strategy only works if all other + // callers to dirname in the process also grab this same lock, but its + // better than nothing. Bionic's dirname returns a thread-local buffer. + static std::mutex& dirname_lock = *new std::mutex(); + std::lock_guard lock(dirname_lock); + + // Note that if std::string uses copy-on-write strings, &str[0] will cause + // the copy to be made, so there is no chance of us accidentally writing to + // the storage for 'path'. + char* parent = dirname(&result[0]); + + // In case dirname returned a pointer to a process global, copy that string + // before leaving the lock. + result.assign(parent); + + return result; +} +#else +// Copied from bionic so that Dirname() below can be portable and thread-safe. +static int _dirname_r(const char* path, size_t path_size, char* buffer, size_t buffer_size) { + const char* endp = nullptr; + int len; + int result; + + // Empty or NULL string gets treated as ".". + if (path == nullptr || path_size == 0) { + path = "."; + len = 1; + goto Exit; + } + + // Strip trailing slashes. + endp = path + path_size - 1; + while (endp > path && *endp == '/') { + endp--; + } + + // Find the start of the dir. + while (endp > path && *endp != '/') { + endp--; + } + + // Either the dir is "/" or there are no slashes. + if (endp == path) { + path = (*endp == '/') ? "/" : "."; + len = 1; + goto Exit; + } + + do { + endp--; + } while (endp > path && *endp == '/'); + + len = endp - path + 1; + + Exit: + result = len; + if (len + 1 > MAXPATHLEN) { + errno = ENAMETOOLONG; + return -1; + } + if (buffer == nullptr) { + return result; + } + + if (len > static_cast(buffer_size) - 1) { + len = buffer_size - 1; + result = -1; + errno = ERANGE; + } + + if (len >= 0) { + memcpy(buffer, path, len); + buffer[len] = 0; + } + return result; +} +std::string Dirname(std::string_view path) { + char buf[PATH_MAX] __attribute__((__uninitialized__)); + const auto size = _dirname_r(path.data(), path.size(), buf, sizeof(buf)); + return size > 0 ? std::string(buf, size) : std::string(); +} +#endif + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/file.h b/base/cvd/android-base/file.h new file mode 100644 index 0000000000..b11b305a47 --- /dev/null +++ b/base/cvd/android-base/file.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2015 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 + +#include +#include + +#include + +#include "android-base/macros.h" +#include "android-base/off64_t.h" +#include "android-base/unique_fd.h" + +#if !defined(_WIN32) && !defined(O_BINARY) +/** Windows needs O_BINARY, but Unix never mangles line endings. */ +#define O_BINARY 0 +#endif + +#if defined(_WIN32) && !defined(O_CLOEXEC) +/** Windows has O_CLOEXEC but calls it O_NOINHERIT for some reason. */ +#define O_CLOEXEC O_NOINHERIT +#endif + +class TemporaryFile { + public: + TemporaryFile(); + explicit TemporaryFile(const std::string& tmp_dir); + ~TemporaryFile(); + + // Release the ownership of fd, caller is reponsible for closing the + // fd or stream properly. + int release(); + // Don't remove the temporary file in the destructor. + void DoNotRemove() { remove_file_ = false; } + + int fd; + char path[1024]; + + private: + void init(const std::string& tmp_dir); + + bool remove_file_ = true; + + DISALLOW_COPY_AND_ASSIGN(TemporaryFile); +}; + +class TemporaryDir { + public: + TemporaryDir(); + ~TemporaryDir(); + // Don't remove the temporary dir in the destructor. + void DoNotRemove() { remove_dir_and_contents_ = false; } + + char path[1024]; + + private: + bool init(const std::string& tmp_dir); + + bool remove_dir_and_contents_ = true; + + DISALLOW_COPY_AND_ASSIGN(TemporaryDir); +}; + +namespace android { +namespace base { + +bool ReadFdToString(borrowed_fd fd, std::string* content); +bool ReadFileToString(const std::string& path, std::string* content, + bool follow_symlinks = false); + +bool WriteStringToFile(const std::string& content, const std::string& path, + bool follow_symlinks = false); +bool WriteStringToFd(std::string_view content, borrowed_fd fd); + +#if !defined(_WIN32) +bool WriteStringToFile(const std::string& content, const std::string& path, + mode_t mode, uid_t owner, gid_t group, + bool follow_symlinks = false); +#endif + +bool ReadFully(borrowed_fd fd, void* data, size_t byte_count); + +// Reads `byte_count` bytes from the file descriptor at the specified offset. +// Returns false if there was an IO error or EOF was reached before reading `byte_count` bytes. +// +// NOTE: On Linux/Mac, this function wraps pread, which provides atomic read support without +// modifying the read pointer of the file descriptor. On Windows, however, the read pointer does +// get modified. This means that ReadFullyAtOffset can be used concurrently with other calls to the +// same function, but concurrently seeking or reading incrementally can lead to unexpected +// behavior. +bool ReadFullyAtOffset(borrowed_fd fd, void* data, size_t byte_count, off64_t offset); + +bool WriteFully(borrowed_fd fd, const void* data, size_t byte_count); +bool WriteFullyAtOffset(borrowed_fd fd, const void* data, size_t byte_count, off64_t offset); + +bool RemoveFileIfExists(const std::string& path, std::string* err = nullptr); + +#if !defined(_WIN32) +bool Realpath(const std::string& path, std::string* result); +bool Readlink(const std::string& path, std::string* result); +#endif + +std::string GetExecutablePath(); +std::string GetExecutableDirectory(); + +// Like the regular basename and dirname, but thread-safe on all +// platforms and capable of correctly handling exotic Windows paths. +std::string Basename(std::string_view path); +std::string Dirname(std::string_view path); + +} // namespace base +} // namespace android diff --git a/allocd-port/include/android-base/format.h b/base/cvd/android-base/format.h similarity index 91% rename from allocd-port/include/android-base/format.h rename to base/cvd/android-base/format.h index 34e999647a..330040d8a1 100644 --- a/allocd-port/include/android-base/format.h +++ b/base/cvd/android-base/format.h @@ -16,8 +16,8 @@ #pragma once -// We include fmtlib here as an alias, since libbase will have fmtlib statically -// linked already. It is accessed through its normal fmt:: namespace. +// We include fmtlib here as an alias, since libbase will have fmtlib statically linked already. +// It is accessed through its normal fmt:: namespace. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wshadow" #include diff --git a/base/cvd/android-base/logging.cpp b/base/cvd/android-base/logging.cpp new file mode 100644 index 0000000000..daa5b4c3fe --- /dev/null +++ b/base/cvd/android-base/logging.cpp @@ -0,0 +1,572 @@ +/* + * Copyright (C) 2015 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. + */ + +#if defined(_WIN32) +#include +#endif + +#include "android-base/logging.h" + +#include +#include +#include +#include + +// For getprogname(3) or program_invocation_short_name. +#if defined(__ANDROID__) || defined(__APPLE__) +#include +#elif defined(__GLIBC__) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef __ANDROID__ +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include "logging_splitters.h" + +namespace android { +namespace base { + +// BSD-based systems like Android/macOS have getprogname(). Others need us to provide one. +#if !defined(__APPLE__) && !defined(__BIONIC__) +static const char* getprogname() { +#ifdef _WIN32 + static bool first = true; + static char progname[MAX_PATH] = {}; + + if (first) { + snprintf(progname, sizeof(progname), "%s", + android::base::Basename(android::base::GetExecutablePath()).c_str()); + first = false; + } + + return progname; +#else + return program_invocation_short_name; +#endif +} +#endif + +static const char* GetFileBasename(const char* file) { + // We can't use basename(3) even on Unix because the Mac doesn't + // have a non-modifying basename. + const char* last_slash = strrchr(file, '/'); + if (last_slash != nullptr) { + return last_slash + 1; + } +#if defined(_WIN32) + const char* last_backslash = strrchr(file, '\\'); + if (last_backslash != nullptr) { + return last_backslash + 1; + } +#endif + return file; +} + +#if defined(__linux__) +static int OpenKmsg() { +#if defined(__ANDROID__) + // pick up 'file /dev/kmsg w' environment from daemon's init rc file + const auto val = getenv("ANDROID_FILE__dev_kmsg"); + if (val != nullptr) { + int fd; + if (android::base::ParseInt(val, &fd, 0)) { + auto flags = fcntl(fd, F_GETFL); + if ((flags != -1) && ((flags & O_ACCMODE) == O_WRONLY)) return fd; + } + } +#endif + return TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC)); +} +#endif + +static LogId log_id_tToLogId(int32_t buffer_id) { + switch (buffer_id) { + case LOG_ID_MAIN: + return MAIN; + case LOG_ID_SYSTEM: + return SYSTEM; + case LOG_ID_RADIO: + return RADIO; + case LOG_ID_CRASH: + return CRASH; + case LOG_ID_DEFAULT: + default: + return DEFAULT; + } +} + +static int32_t LogIdTolog_id_t(LogId log_id) { + switch (log_id) { + case MAIN: + return LOG_ID_MAIN; + case SYSTEM: + return LOG_ID_SYSTEM; + case RADIO: + return LOG_ID_RADIO; + case CRASH: + return LOG_ID_CRASH; + case DEFAULT: + default: + return LOG_ID_DEFAULT; + } +} + +static LogSeverity PriorityToLogSeverity(int priority) { + switch (priority) { + case ANDROID_LOG_DEFAULT: + return INFO; + case ANDROID_LOG_VERBOSE: + return VERBOSE; + case ANDROID_LOG_DEBUG: + return DEBUG; + case ANDROID_LOG_INFO: + return INFO; + case ANDROID_LOG_WARN: + return WARNING; + case ANDROID_LOG_ERROR: + return ERROR; + case ANDROID_LOG_FATAL: + return FATAL; + default: + return FATAL; + } +} + +static int32_t LogSeverityToPriority(LogSeverity severity) { + switch (severity) { + case VERBOSE: + return ANDROID_LOG_VERBOSE; + case DEBUG: + return ANDROID_LOG_DEBUG; + case INFO: + return ANDROID_LOG_INFO; + case WARNING: + return ANDROID_LOG_WARN; + case ERROR: + return ANDROID_LOG_ERROR; + case FATAL_WITHOUT_ABORT: + case FATAL: + default: + return ANDROID_LOG_FATAL; + } +} + +static LogFunction& Logger() { +#ifdef __ANDROID__ + static auto& logger = *new LogFunction(LogdLogger()); +#else + static auto& logger = *new LogFunction(StderrLogger); +#endif + return logger; +} + +static AbortFunction& Aborter() { + static auto& aborter = *new AbortFunction(DefaultAborter); + return aborter; +} + +// Only used for Q fallback. +static std::recursive_mutex& TagLock() { + static auto& tag_lock = *new std::recursive_mutex(); + return tag_lock; +} +// Only used for Q fallback. +static std::string* gDefaultTag; + +void SetDefaultTag(const std::string& tag) { + if (__builtin_available(android 30, *)) { + __android_log_set_default_tag(tag.c_str()); + } else { + std::lock_guard lock(TagLock()); + if (gDefaultTag != nullptr) { + delete gDefaultTag; + gDefaultTag = nullptr; + } + if (!tag.empty()) { + gDefaultTag = new std::string(tag); + } + } +} + +static bool gInitialized = false; + +// Only used for Q fallback. +static LogSeverity gMinimumLogSeverity = INFO; + +#if defined(__linux__) +static void KernelLogLine(const char* msg, int length, android::base::LogSeverity severity, + const char* tag) { + // clang-format off + static constexpr int kLogSeverityToKernelLogLevel[] = { + [android::base::VERBOSE] = 7, // KERN_DEBUG (there is no verbose kernel log + // level) + [android::base::DEBUG] = 7, // KERN_DEBUG + [android::base::INFO] = 6, // KERN_INFO + [android::base::WARNING] = 4, // KERN_WARNING + [android::base::ERROR] = 3, // KERN_ERROR + [android::base::FATAL_WITHOUT_ABORT] = 2, // KERN_CRIT + [android::base::FATAL] = 2, // KERN_CRIT + }; + // clang-format on + static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1, + "Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity"); + + static int klog_fd = OpenKmsg(); + if (klog_fd == -1) return; + + int level = kLogSeverityToKernelLogLevel[severity]; + + // The kernel's printk buffer is only |1024 - PREFIX_MAX| bytes, where + // PREFIX_MAX could be 48 or 32. + // Reference: kernel/printk/printk.c + static constexpr int LOG_LINE_MAX = 1024 - 48; + char buf[LOG_LINE_MAX] __attribute__((__uninitialized__)); + size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %.*s\n", level, tag, length, msg); + TEMP_FAILURE_RETRY(write(klog_fd, buf, std::min(size, sizeof(buf)))); + + if (size > sizeof(buf)) { + size_t truncated = size - sizeof(buf); + size = snprintf( + buf, sizeof(buf), + "<%d>%s: **previous message missing %zu bytes** %zu-byte message too long for printk\n", + level, tag, truncated, size); + TEMP_FAILURE_RETRY(write(klog_fd, buf, std::min(size, sizeof(buf)))); + } +} + +void KernelLogger(android::base::LogId, android::base::LogSeverity severity, const char* tag, + const char*, unsigned int, const char* full_message) { + SplitByLines(full_message, KernelLogLine, severity, tag); +} +#endif + +void StderrLogger(LogId, LogSeverity severity, const char* tag, const char* file, unsigned int line, + const char* message) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + auto output_string = + StderrOutputGenerator(ts, getpid(), GetThreadId(), severity, tag, file, line, message); + + fputs(output_string.c_str(), stderr); +} + +void StdioLogger(LogId, LogSeverity severity, const char* /*tag*/, const char* /*file*/, + unsigned int /*line*/, const char* message) { + if (severity >= WARNING) { + fflush(stdout); + fprintf(stderr, "%s: %s\n", GetFileBasename(getprogname()), message); + } else { + fprintf(stdout, "%s\n", message); + } +} + +void DefaultAborter(const char* abort_message) { +#ifdef __ANDROID__ + android_set_abort_message(abort_message); +#else + UNUSED(abort_message); +#endif + abort(); +} + +static void LogdLogChunk(LogId, LogSeverity, const char*, const char*) { + /* + int32_t lg_id = LogIdTolog_id_t(id); + int32_t priority = LogSeverityToPriority(severity); + + if (__builtin_available(android 30, *)) { + __android_log_message log_message = {sizeof(__android_log_message), lg_id, priority, tag, + static_cast(nullptr), 0, message}; + __android_log_logd_logger(&log_message); + } else { + __android_log_buf_print(lg_id, priority, tag, "%s", message); + } + */ +} + +LogdLogger::LogdLogger(LogId default_log_id) : default_log_id_(default_log_id) {} + +void LogdLogger::operator()(LogId id, LogSeverity severity, const char* tag, const char* file, + unsigned int line, const char* message) { + if (id == DEFAULT) { + id = default_log_id_; + } + + SplitByLogdChunks(id, severity, tag, file, line, message, LogdLogChunk); +} + +void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) { + SetLogger(std::forward(logger)); + SetAborter(std::forward(aborter)); + + if (gInitialized) { + return; + } + + gInitialized = true; + + // Stash the command line for later use. We can use /proc/self/cmdline on + // Linux to recover this, but we don't have that luxury on the Mac/Windows, + // and there are a couple of argv[0] variants that are commonly used. + if (argv != nullptr) { + SetDefaultTag(basename(argv[0])); + } + + const char* tags = getenv("ANDROID_LOG_TAGS"); + if (tags == nullptr) { + return; + } + + std::vector specs = Split(tags, " "); + for (size_t i = 0; i < specs.size(); ++i) { + // "tag-pattern:[vdiwefs]" + std::string spec(specs[i]); + if (spec.size() == 3 && StartsWith(spec, "*:")) { + switch (spec[2]) { + case 'v': + SetMinimumLogSeverity(VERBOSE); + continue; + case 'd': + SetMinimumLogSeverity(DEBUG); + continue; + case 'i': + SetMinimumLogSeverity(INFO); + continue; + case 'w': + SetMinimumLogSeverity(WARNING); + continue; + case 'e': + SetMinimumLogSeverity(ERROR); + continue; + case 'f': + SetMinimumLogSeverity(FATAL_WITHOUT_ABORT); + continue; + // liblog will even suppress FATAL if you say 's' for silent, but fatal should + // never be suppressed. + case 's': + SetMinimumLogSeverity(FATAL_WITHOUT_ABORT); + continue; + } + } + LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags + << ")"; + } +} + +LogFunction SetLogger(LogFunction&& logger) { + LogFunction old_logger = std::move(Logger()); + Logger() = std::move(logger); + + if (__builtin_available(android 30, *)) { + __android_log_set_logger([](const struct __android_log_message* log_message) { + auto log_id = log_id_tToLogId(log_message->buffer_id); + auto severity = PriorityToLogSeverity(log_message->priority); + + Logger()(log_id, severity, log_message->tag, log_message->file, log_message->line, + log_message->message); + }); + } + return old_logger; +} + +AbortFunction SetAborter(AbortFunction&& aborter) { + AbortFunction old_aborter = std::move(Aborter()); + Aborter() = std::move(aborter); + + if (__builtin_available(android 30, *)) { + __android_log_set_aborter([](const char* abort_message) { Aborter()(abort_message); }); + } + return old_aborter; +} + +// This indirection greatly reduces the stack impact of having lots of +// checks/logging in a function. +class LogMessageData { + public: + LogMessageData(const char* file, unsigned int line, LogSeverity severity, const char* tag, + int error) + : file_(GetFileBasename(file)), + line_number_(line), + severity_(severity), + tag_(tag), + error_(error) {} + + const char* GetFile() const { + return file_; + } + + unsigned int GetLineNumber() const { + return line_number_; + } + + LogSeverity GetSeverity() const { + return severity_; + } + + const char* GetTag() const { return tag_; } + + int GetError() const { + return error_; + } + + std::ostream& GetBuffer() { + return buffer_; + } + + std::string ToString() const { + return buffer_.str(); + } + + private: + std::ostringstream buffer_; + const char* const file_; + const unsigned int line_number_; + const LogSeverity severity_; + const char* const tag_; + const int error_; + + DISALLOW_COPY_AND_ASSIGN(LogMessageData); +}; + +LogMessage::LogMessage(const char* file, unsigned int line, LogId, LogSeverity severity, + const char* tag, int error) + : LogMessage(file, line, severity, tag, error) {} + +LogMessage::LogMessage(const char* file, unsigned int line, LogSeverity severity, const char* tag, + int error) + : data_(new LogMessageData(file, line, severity, tag, error)) {} + +LogMessage::~LogMessage() { + // Check severity again. This is duplicate work wrt/ LOG macros, but not LOG_STREAM. + if (!WOULD_LOG(data_->GetSeverity())) { + return; + } + + // Finish constructing the message. + if (data_->GetError() != -1) { + data_->GetBuffer() << ": " << strerror(data_->GetError()); + } + std::string msg(data_->ToString()); + + if (data_->GetSeverity() == FATAL) { +#ifdef __ANDROID__ + // Set the bionic abort message early to avoid liblog doing it + // with the individual lines, so that we get the whole message. + android_set_abort_message(msg.c_str()); +#endif + } + + LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetSeverity(), data_->GetTag(), + msg.c_str()); + + // Abort if necessary. + if (data_->GetSeverity() == FATAL) { + if (__builtin_available(android 30, *)) { + __android_log_call_aborter(msg.c_str()); + } else { + Aborter()(msg.c_str()); + } + } +} + +std::ostream& LogMessage::stream() { + return data_->GetBuffer(); +} + +void LogMessage::LogLine(const char* file, unsigned int line, LogSeverity severity, const char* tag, + const char* message) { + int32_t priority = LogSeverityToPriority(severity); + if (__builtin_available(android 30, *)) { + __android_log_message log_message = { + sizeof(__android_log_message), LOG_ID_DEFAULT, priority, tag, file, line, message}; + __android_log_write_log_message(&log_message); + } else { + if (tag == nullptr) { + std::lock_guard lock(TagLock()); + if (gDefaultTag == nullptr) { + gDefaultTag = new std::string(getprogname()); + } + + Logger()(DEFAULT, severity, gDefaultTag->c_str(), file, line, message); + } else { + Logger()(DEFAULT, severity, tag, file, line, message); + } + } +} + +LogSeverity GetMinimumLogSeverity() { + if (__builtin_available(android 30, *)) { + return PriorityToLogSeverity(__android_log_get_minimum_priority()); + } else { + return gMinimumLogSeverity; + } +} + +bool ShouldLog(LogSeverity severity, const char* tag) { + // Even though we're not using the R liblog functions in this function, if we're running on Q, + // we need to fall back to using gMinimumLogSeverity, since __android_log_is_loggable() will not + // take into consideration the value from SetMinimumLogSeverity(). + if (__builtin_available(android 30, *)) { + int32_t priority = LogSeverityToPriority(severity); + return __android_log_is_loggable(priority, tag, ANDROID_LOG_INFO); + } else { + return severity >= gMinimumLogSeverity; + } +} + +LogSeverity SetMinimumLogSeverity(LogSeverity new_severity) { + if (__builtin_available(android 30, *)) { + int32_t priority = LogSeverityToPriority(new_severity); + return PriorityToLogSeverity(__android_log_set_minimum_priority(priority)); + } else { + LogSeverity old_severity = gMinimumLogSeverity; + gMinimumLogSeverity = new_severity; + return old_severity; + } +} + +ScopedLogSeverity::ScopedLogSeverity(LogSeverity new_severity) { + old_ = SetMinimumLogSeverity(new_severity); +} + +ScopedLogSeverity::~ScopedLogSeverity() { + SetMinimumLogSeverity(old_); +} + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/logging.h b/base/cvd/android-base/logging.h new file mode 100644 index 0000000000..422fe08480 --- /dev/null +++ b/base/cvd/android-base/logging.h @@ -0,0 +1,484 @@ +/* + * Copyright (C) 2015 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 + +#define __builtin_available(v1, v2) false + +// +// Google-style C++ logging. +// + +// This header provides a C++ stream interface to logging. +// +// To log: +// +// LOG(INFO) << "Some text; " << some_value; +// +// Replace `INFO` with any severity from `enum LogSeverity`. +// Most devices filter out VERBOSE logs by default, run +// `adb shell setprop log.tag. V` to see them in adb logcat. +// +// To log the result of a failed function and include the string +// representation of `errno` at the end: +// +// PLOG(ERROR) << "Write failed"; +// +// The output will be something like `Write failed: I/O error`. +// Remember this as 'P' as in perror(3). +// +// To output your own types, simply implement operator<< as normal. +// +// By default, output goes to logcat on Android and stderr on the host. +// A process can use `SetLogger` to decide where all logging goes. +// Implementations are provided for logcat, stderr, and dmesg. +// +// By default, the process' name is used as the log tag. +// Code can choose a specific log tag by defining LOG_TAG +// before including this header. + +// This header also provides assertions: +// +// CHECK(must_be_true); +// CHECK_EQ(a, b) << z_is_interesting_too; + +// NOTE: For Windows, you must include logging.h after windows.h to allow the +// following code to suppress the evil ERROR macro: +#ifdef _WIN32 +// windows.h includes wingdi.h which defines an evil macro ERROR. +#ifdef ERROR +#undef ERROR +#endif +#endif + +#include +#include +#include + +#include "android-base/errno_restorer.h" +#include "android-base/macros.h" + +// Note: DO NOT USE DIRECTLY. Use LOG_TAG instead. +#ifdef _LOG_TAG_INTERNAL +#error "_LOG_TAG_INTERNAL must not be defined" +#endif +#ifdef LOG_TAG +#define _LOG_TAG_INTERNAL LOG_TAG +#else +#define _LOG_TAG_INTERNAL nullptr +#endif + +namespace android { +namespace base { + +enum LogSeverity { + VERBOSE, + DEBUG, + INFO, + WARNING, + ERROR, + FATAL_WITHOUT_ABORT, // For loggability tests, this is considered identical to FATAL. + FATAL, +}; + +enum LogId { + DEFAULT, + MAIN, + SYSTEM, + RADIO, + CRASH, +}; + +using LogFunction = std::function; +using AbortFunction = std::function; + +// Loggers for use with InitLogging/SetLogger. + +// Log to the kernel log (dmesg). +void KernelLogger(LogId log_buffer_id, LogSeverity severity, const char* tag, const char* file, unsigned int line, const char* message); +// Log to stderr in the full logcat format (with pid/tid/time/tag details). +void StderrLogger(LogId log_buffer_id, LogSeverity severity, const char* tag, const char* file, unsigned int line, const char* message); +// Log just the message to stdout/stderr (without pid/tid/time/tag details). +// The choice of stdout versus stderr is based on the severity. +// Errors are also prefixed by the program name (as with err(3)/error(3)). +// Useful for replacing printf(3)/perror(3)/err(3)/error(3) in command-line tools. +void StdioLogger(LogId log_buffer_id, LogSeverity severity, const char* tag, const char* file, unsigned int line, const char* message); + +void DefaultAborter(const char* abort_message); + +void SetDefaultTag(const std::string& tag); + +// The LogdLogger sends chunks of up to ~4000 bytes at a time to logd. It does not prevent other +// threads from writing to logd between sending each chunk, so other threads may interleave their +// messages. If preventing interleaving is required, then a custom logger that takes a lock before +// calling this logger should be provided. +class LogdLogger { + public: + explicit LogdLogger(LogId default_log_id = android::base::MAIN); + + void operator()(LogId, LogSeverity, const char* tag, const char* file, + unsigned int line, const char* message); + + private: + LogId default_log_id_; +}; + +// Configure logging based on ANDROID_LOG_TAGS environment variable. +// We need to parse a string that looks like +// +// *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i +// +// The tag (or '*' for the global level) comes first, followed by a colon and a +// letter indicating the minimum priority level we're expected to log. This can +// be used to reveal or conceal logs with specific tags. +#ifdef __ANDROID__ +#define INIT_LOGGING_DEFAULT_LOGGER LogdLogger() +#else +#define INIT_LOGGING_DEFAULT_LOGGER StderrLogger +#endif +void InitLogging(char* argv[], + LogFunction&& logger = INIT_LOGGING_DEFAULT_LOGGER, + AbortFunction&& aborter = DefaultAborter); +#undef INIT_LOGGING_DEFAULT_LOGGER + +// Replace the current logger and return the old one. +LogFunction SetLogger(LogFunction&& logger); + +// Replace the current aborter and return the old one. +AbortFunction SetAborter(AbortFunction&& aborter); + +// A helper macro that produces an expression that accepts both a qualified name and an +// unqualified name for a LogSeverity, and returns a LogSeverity value. +// Note: DO NOT USE DIRECTLY. This is an implementation detail. +#define SEVERITY_LAMBDA(severity) ([&]() { \ + using ::android::base::VERBOSE; \ + using ::android::base::DEBUG; \ + using ::android::base::INFO; \ + using ::android::base::WARNING; \ + using ::android::base::ERROR; \ + using ::android::base::FATAL_WITHOUT_ABORT; \ + using ::android::base::FATAL; \ + return (severity); }()) + +#ifdef __clang_analyzer__ +// Clang's static analyzer does not see the conditional statement inside +// LogMessage's destructor that will abort on FATAL severity. +#define ABORT_AFTER_LOG_FATAL for (;; abort()) + +struct LogAbortAfterFullExpr { + ~LogAbortAfterFullExpr() __attribute__((noreturn)) { abort(); } + explicit operator bool() const { return false; } +}; +// Provides an expression that evaluates to the truthiness of `x`, automatically +// aborting if `c` is true. +#define ABORT_AFTER_LOG_EXPR_IF(c, x) (((c) && ::android::base::LogAbortAfterFullExpr()) || (x)) +// Note to the static analyzer that we always execute FATAL logs in practice. +#define MUST_LOG_MESSAGE(severity) (SEVERITY_LAMBDA(severity) == ::android::base::FATAL) +#else +#define ABORT_AFTER_LOG_FATAL +#define ABORT_AFTER_LOG_EXPR_IF(c, x) (x) +#define MUST_LOG_MESSAGE(severity) false +#endif +#define ABORT_AFTER_LOG_FATAL_EXPR(x) ABORT_AFTER_LOG_EXPR_IF(true, x) + +// Defines whether the given severity will be logged or silently swallowed. +#define WOULD_LOG(severity) \ + (UNLIKELY(::android::base::ShouldLog(SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL)) || \ + MUST_LOG_MESSAGE(severity)) + +// Get an ostream that can be used for logging at the given severity and to the default +// destination. +// +// Notes: +// 1) This will not check whether the severity is high enough. One should use WOULD_LOG to filter +// usage manually. +// 2) This does not save and restore errno. +#define LOG_STREAM(severity) \ + ::android::base::LogMessage(__FILE__, __LINE__, SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL, \ + -1) \ + .stream() + +// Logs a message to logcat on Android otherwise to stderr. If the severity is +// FATAL it also causes an abort. For example: +// +// LOG(FATAL) << "We didn't expect to reach here"; +#define LOG(severity) LOGGING_PREAMBLE(severity) && LOG_STREAM(severity) + +// Checks if we want to log something, and sets up appropriate RAII objects if +// so. +// Note: DO NOT USE DIRECTLY. This is an implementation detail. +#define LOGGING_PREAMBLE(severity) \ + (WOULD_LOG(severity) && \ + ABORT_AFTER_LOG_EXPR_IF((SEVERITY_LAMBDA(severity)) == ::android::base::FATAL, true) && \ + ::android::base::ErrnoRestorer()) + +// A variant of LOG that also logs the current errno value. To be used when +// library calls fail. +#define PLOG(severity) \ + LOGGING_PREAMBLE(severity) && \ + ::android::base::LogMessage(__FILE__, __LINE__, SEVERITY_LAMBDA(severity), \ + _LOG_TAG_INTERNAL, errno) \ + .stream() + +// Marker that code is yet to be implemented. +#define UNIMPLEMENTED(level) \ + LOG(level) << __PRETTY_FUNCTION__ << " unimplemented " + +// Check whether condition x holds and LOG(FATAL) if not. The value of the +// expression x is only evaluated once. Extra logging can be appended using << +// after. For example: +// +// CHECK(false == true) results in a log message of +// "Check failed: false == true". +#define CHECK(x) \ + LIKELY((x)) || ABORT_AFTER_LOG_FATAL_EXPR(false) || \ + ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::FATAL, _LOG_TAG_INTERNAL, \ + -1) \ + .stream() \ + << "Check failed: " #x << " " + +// clang-format off +// Helper for CHECK_xx(x,y) macros. +#define CHECK_OP(LHS, RHS, OP) \ + for (auto _values = ::android::base::MakeEagerEvaluator(LHS, RHS); \ + UNLIKELY(!(_values.lhs.v OP _values.rhs.v)); \ + /* empty */) \ + ABORT_AFTER_LOG_FATAL \ + ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::FATAL, _LOG_TAG_INTERNAL, -1) \ + .stream() \ + << "Check failed: " << #LHS << " " << #OP << " " << #RHS << " (" #LHS "=" \ + << ::android::base::LogNullGuard::Guard(_values.lhs.v) \ + << ", " #RHS "=" \ + << ::android::base::LogNullGuard::Guard(_values.rhs.v) \ + << ") " +// clang-format on + +// Check whether a condition holds between x and y, LOG(FATAL) if not. The value +// of the expressions x and y is evaluated once. Extra logging can be appended +// using << after. For example: +// +// CHECK_NE(0 == 1, false) results in +// "Check failed: false != false (0==1=false, false=false) ". +#define CHECK_EQ(x, y) CHECK_OP(x, y, == ) +#define CHECK_NE(x, y) CHECK_OP(x, y, != ) +#define CHECK_LE(x, y) CHECK_OP(x, y, <= ) +#define CHECK_LT(x, y) CHECK_OP(x, y, < ) +#define CHECK_GE(x, y) CHECK_OP(x, y, >= ) +#define CHECK_GT(x, y) CHECK_OP(x, y, > ) + +// clang-format off +// Helper for CHECK_STRxx(s1,s2) macros. +#define CHECK_STROP(s1, s2, sense) \ + while (UNLIKELY((strcmp(s1, s2) == 0) != (sense))) \ + ABORT_AFTER_LOG_FATAL \ + ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::FATAL, \ + _LOG_TAG_INTERNAL, -1) \ + .stream() \ + << "Check failed: " << "\"" << (s1) << "\"" \ + << ((sense) ? " == " : " != ") << "\"" << (s2) << "\"" +// clang-format on + +// Check for string (const char*) equality between s1 and s2, LOG(FATAL) if not. +#define CHECK_STREQ(s1, s2) CHECK_STROP(s1, s2, true) +#define CHECK_STRNE(s1, s2) CHECK_STROP(s1, s2, false) + +// Perform the pthread function call(args), LOG(FATAL) on error. +#define CHECK_PTHREAD_CALL(call, args, what) \ + do { \ + int rc = call args; \ + if (rc != 0) { \ + errno = rc; \ + ABORT_AFTER_LOG_FATAL \ + PLOG(FATAL) << #call << " failed for " << (what); \ + } \ + } while (false) + +// DCHECKs are debug variants of CHECKs only enabled in debug builds. Generally +// CHECK should be used unless profiling identifies a CHECK as being in +// performance critical code. +#if defined(NDEBUG) && !defined(__clang_analyzer__) +static constexpr bool kEnableDChecks = false; +#else +static constexpr bool kEnableDChecks = true; +#endif + +#define DCHECK(x) \ + if (::android::base::kEnableDChecks) CHECK(x) +#define DCHECK_EQ(x, y) \ + if (::android::base::kEnableDChecks) CHECK_EQ(x, y) +#define DCHECK_NE(x, y) \ + if (::android::base::kEnableDChecks) CHECK_NE(x, y) +#define DCHECK_LE(x, y) \ + if (::android::base::kEnableDChecks) CHECK_LE(x, y) +#define DCHECK_LT(x, y) \ + if (::android::base::kEnableDChecks) CHECK_LT(x, y) +#define DCHECK_GE(x, y) \ + if (::android::base::kEnableDChecks) CHECK_GE(x, y) +#define DCHECK_GT(x, y) \ + if (::android::base::kEnableDChecks) CHECK_GT(x, y) +#define DCHECK_STREQ(s1, s2) \ + if (::android::base::kEnableDChecks) CHECK_STREQ(s1, s2) +#define DCHECK_STRNE(s1, s2) \ + if (::android::base::kEnableDChecks) CHECK_STRNE(s1, s2) + +namespace log_detail { + +// Temporary storage for a single eagerly evaluated check expression operand. +template struct Storage { + template explicit constexpr Storage(U&& u) : v(std::forward(u)) {} + explicit Storage(const Storage& t) = delete; + explicit Storage(Storage&& t) = delete; + T v; +}; + +// Partial specialization for smart pointers to avoid copying. +template struct Storage> { + explicit constexpr Storage(const std::unique_ptr& ptr) : v(ptr.get()) {} + const T* v; +}; +template struct Storage> { + explicit constexpr Storage(const std::shared_ptr& ptr) : v(ptr.get()) {} + const T* v; +}; + +// Type trait that checks if a type is a (potentially const) char pointer. +template struct IsCharPointer { + using Pointee = std::remove_cv_t>; + static constexpr bool value = std::is_pointer_v && + (std::is_same_v || std::is_same_v || + std::is_same_v); +}; + +// Counterpart to Storage that depends on both operands. This is used to prevent +// char pointers being treated as strings in the log output - they might point +// to buffers of unprintable binary data. +template struct StorageTypes { + static constexpr bool voidptr = IsCharPointer::value && IsCharPointer::value; + using LHSType = std::conditional_t; + using RHSType = std::conditional_t; +}; + +// Temporary class created to evaluate the LHS and RHS, used with +// MakeEagerEvaluator to infer the types of LHS and RHS. +template +struct EagerEvaluator { + template constexpr EagerEvaluator(A&& l, B&& r) + : lhs(std::forward(l)), rhs(std::forward(r)) {} + const Storage::LHSType> lhs; + const Storage::RHSType> rhs; +}; + +} // namespace log_detail + +// Converts std::nullptr_t and null char pointers to the string "null" +// when writing the failure message. +template struct LogNullGuard { + static const T& Guard(const T& v) { return v; } +}; +template <> struct LogNullGuard { + static const char* Guard(const std::nullptr_t&) { return "(null)"; } +}; +template <> struct LogNullGuard { + static const char* Guard(const char* v) { return v ? v : "(null)"; } +}; +template <> struct LogNullGuard { + static const char* Guard(const char* v) { return v ? v : "(null)"; } +}; + +// Helper function for CHECK_xx. +template +constexpr auto MakeEagerEvaluator(LHS&& lhs, RHS&& rhs) { + return log_detail::EagerEvaluator, std::decay_t>( + std::forward(lhs), std::forward(rhs)); +} + +// Data for the log message, not stored in LogMessage to avoid increasing the +// stack size. +class LogMessageData; + +// A LogMessage is a temporarily scoped object used by LOG and the unlikely part +// of a CHECK. The destructor will abort if the severity is FATAL. +class LogMessage { + public: + // LogId has been deprecated, but this constructor must exist for prebuilts. + LogMessage(const char* file, unsigned int line, LogId, LogSeverity severity, const char* tag, + int error); + LogMessage(const char* file, unsigned int line, LogSeverity severity, const char* tag, int error); + + ~LogMessage(); + + // Returns the stream associated with the message, the LogMessage performs + // output when it goes out of scope. + std::ostream& stream(); + + // The routine that performs the actual logging. + static void LogLine(const char* file, unsigned int line, LogSeverity severity, const char* tag, + const char* msg); + + private: + const std::unique_ptr data_; + + DISALLOW_COPY_AND_ASSIGN(LogMessage); +}; + +// Get the minimum severity level for logging. +LogSeverity GetMinimumLogSeverity(); + +// Set the minimum severity level for logging, returning the old severity. +LogSeverity SetMinimumLogSeverity(LogSeverity new_severity); + +// Return whether or not a log message with the associated tag should be logged. +bool ShouldLog(LogSeverity severity, const char* tag); + +// Allows to temporarily change the minimum severity level for logging. +class ScopedLogSeverity { + public: + explicit ScopedLogSeverity(LogSeverity level); + ~ScopedLogSeverity(); + + private: + LogSeverity old_; +}; + +} // namespace base +} // namespace android + +namespace std { // NOLINT(cert-dcl58-cpp) + +// Emit a warning of ostream<< with std::string*. The intention was most likely to print *string. +// +// Note: for this to work, we need to have this in a namespace. +// Note: using a pragma because "-Wgcc-compat" (included in "-Weverything") complains about +// diagnose_if. +// Note: to print the pointer, use "<< static_cast(string_pointer)" instead. +// Note: a not-recommended alternative is to let Clang ignore the warning by adding +// -Wno-user-defined-warnings to CPPFLAGS. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" +#define OSTREAM_STRING_POINTER_USAGE_WARNING \ + __attribute__((diagnose_if(true, "Unexpected logging of string pointer", "warning"))) +inline OSTREAM_STRING_POINTER_USAGE_WARNING +std::ostream& operator<<(std::ostream& stream, const std::string* string_pointer) { + return stream << static_cast(string_pointer); +} +#pragma clang diagnostic pop + +} // namespace std diff --git a/base/cvd/android-base/macros.h b/base/cvd/android-base/macros.h new file mode 100644 index 0000000000..f141f34e6c --- /dev/null +++ b/base/cvd/android-base/macros.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2015 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 + +#include // for size_t +#include // for TEMP_FAILURE_RETRY + +#include + +// bionic and glibc both have TEMP_FAILURE_RETRY, but eg Mac OS' libc doesn't. +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(exp) \ + ({ \ + decltype(exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; \ + }) +#endif + +// A macro to disallow the copy constructor and operator= functions +// This must be placed in the private: declarations for a class. +// +// For disallowing only assign or copy, delete the relevant operator or +// constructor, for example: +// void operator=(const TypeName&) = delete; +// Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken +// semantically, one should either use disallow both or neither. Try to +// avoid these in new code. +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +// +// This should be used in the private: declarations for a class +// that wants to prevent anyone from instantiating it. This is +// especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// The arraysize(arr) macro returns the # of elements in an array arr. +// The expression is a compile-time constant, and therefore can be +// used in defining new arrays, for example. If you use arraysize on +// a pointer by mistake, you will get a compile-time error. +// +// One caveat is that arraysize() doesn't accept any array of an +// anonymous type or a type defined inside a function. In these rare +// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is +// due to a limitation in C++'s template system. The limitation might +// eventually be removed, but it hasn't happened yet. + +// This template function declaration is used in defining arraysize. +// Note that the function doesn't need an implementation, as we only +// use its type. +template +char(&ArraySizeHelper(T(&array)[N]))[N]; // NOLINT(readability/casting) + +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +#define SIZEOF_MEMBER(t, f) sizeof(std::declval().f) + +// Changing this definition will cause you a lot of pain. A majority of +// vendor code defines LIKELY and UNLIKELY this way, and includes +// this header through an indirect path. +#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) +#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) + +#define WARN_UNUSED __attribute__((warn_unused_result)) + +// A deprecated function to call to create a false use of the parameter, for +// example: +// int foo(int x) { UNUSED(x); return 10; } +// to avoid compiler warnings. Going forward we prefer ATTRIBUTE_UNUSED. +template +void UNUSED(const T&...) { +} + +// An attribute to place on a parameter to a function, for example: +// int foo(int x ATTRIBUTE_UNUSED) { return 10; } +// to avoid compiler warnings. +#define ATTRIBUTE_UNUSED __attribute__((__unused__)) + +// The FALLTHROUGH_INTENDED macro can be used to annotate implicit fall-through +// between switch labels: +// switch (x) { +// case 40: +// case 41: +// if (truth_is_out_there) { +// ++x; +// FALLTHROUGH_INTENDED; // Use instead of/along with annotations in +// // comments. +// } else { +// return x; +// } +// case 42: +// ... +// +// As shown in the example above, the FALLTHROUGH_INTENDED macro should be +// followed by a semicolon. It is designed to mimic control-flow statements +// like 'break;', so it can be placed in most places where 'break;' can, but +// only if there are no statements on the execution path between it and the +// next switch label. +// +// When compiled with clang, the FALLTHROUGH_INTENDED macro is expanded to +// [[clang::fallthrough]] attribute, which is analysed when performing switch +// labels fall-through diagnostic ('-Wimplicit-fallthrough'). See clang +// documentation on language extensions for details: +// http://clang.llvm.org/docs/LanguageExtensions.html#clang__fallthrough +// +// When used with unsupported compilers, the FALLTHROUGH_INTENDED macro has no +// effect on diagnostics. +// +// In either case this macro has no effect on runtime behavior and performance +// of code. +#ifndef FALLTHROUGH_INTENDED +#define FALLTHROUGH_INTENDED [[clang::fallthrough]] // NOLINT +#endif + +// Current ABI string +#if defined(__arm__) +#define ABI_STRING "arm" +#elif defined(__aarch64__) +#define ABI_STRING "arm64" +#elif defined(__i386__) +#define ABI_STRING "x86" +#elif defined(__riscv) +#define ABI_STRING "riscv64" +#elif defined(__x86_64__) +#define ABI_STRING "x86_64" +#endif diff --git a/base/cvd/android-base/off64_t.h b/base/cvd/android-base/off64_t.h new file mode 100644 index 0000000000..e6b71b81e1 --- /dev/null +++ b/base/cvd/android-base/off64_t.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2018 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 + +#if defined(__APPLE__) +/** Mac OS has always had a 64-bit off_t, so it doesn't have off64_t. */ +typedef off_t off64_t; +#endif diff --git a/base/cvd/android-base/parsebool.cpp b/base/cvd/android-base/parsebool.cpp new file mode 100644 index 0000000000..ff96fe9a9a --- /dev/null +++ b/base/cvd/android-base/parsebool.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 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. + */ + +#include "android-base/parsebool.h" +#include + +namespace android { +namespace base { + +ParseBoolResult ParseBool(std::string_view s) { + if (s == "1" || s == "y" || s == "yes" || s == "on" || s == "true") { + return ParseBoolResult::kTrue; + } + if (s == "0" || s == "n" || s == "no" || s == "off" || s == "false") { + return ParseBoolResult::kFalse; + } + return ParseBoolResult::kError; +} + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/parsebool.h b/base/cvd/android-base/parsebool.h new file mode 100644 index 0000000000..b2bd0217e9 --- /dev/null +++ b/base/cvd/android-base/parsebool.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 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 + +#include + +namespace android { +namespace base { + +// Parse the given string as yes or no inactivation of some sort. Return one of the +// ParseBoolResult enumeration values. +// +// The following values parse as true: +// +// 1 +// on +// true +// y +// yes +// +// +// The following values parse as false: +// +// 0 +// false +// n +// no +// off +// +// Anything else is a parse error. +// +// The purpose of this function is to have a single canonical parser for yes-or-no indications +// throughout the system. + +enum class ParseBoolResult { + kError, + kFalse, + kTrue, +}; + +ParseBoolResult ParseBool(std::string_view s); + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/parseint.h b/base/cvd/android-base/parseint.h new file mode 100644 index 0000000000..c76d625ade --- /dev/null +++ b/base/cvd/android-base/parseint.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2015 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 + +#include +#include +#include + +#include +#include +#include + +namespace android { +namespace base { + +// Parses the unsigned decimal or hexadecimal integer in the string 's' and sets +// 'out' to that value if it is specified. Optionally allows the caller to define +// a 'max' beyond which otherwise valid values will be rejected. Returns boolean +// success; 'out' is untouched if parsing fails. +template +bool ParseUint(const char* s, T* out, T max = std::numeric_limits::max(), + bool allow_suffixes = false) { + static_assert(std::is_unsigned::value, "ParseUint can only be used with unsigned types"); + while (isspace(*s)) { + s++; + } + + if (s[0] == '-') { + errno = EINVAL; + return false; + } + + // This is never out of bounds. If string is zero-sized, s[0] == '\0' + // so the second condition is not checked. If string is "0", + // s[1] will compare against the '\0'. + int base = (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10; + errno = 0; + char* end; + unsigned long long int result = strtoull(s, &end, base); + if (errno != 0) return false; + if (end == s) { + errno = EINVAL; + return false; + } + if (*end != '\0') { + const char* suffixes = "bkmgtpe"; + const char* suffix; + if ((!allow_suffixes || (suffix = strchr(suffixes, tolower(*end))) == nullptr) || + __builtin_mul_overflow(result, 1ULL << (10 * (suffix - suffixes)), &result)) { + errno = EINVAL; + return false; + } + } + if (max < result) { + errno = ERANGE; + return false; + } + if (out != nullptr) { + *out = static_cast(result); + } + return true; +} + +// TODO: string_view +template +bool ParseUint(const std::string& s, T* out, T max = std::numeric_limits::max(), + bool allow_suffixes = false) { + return ParseUint(s.c_str(), out, max, allow_suffixes); +} + +template +bool ParseByteCount(const char* s, T* out, T max = std::numeric_limits::max()) { + return ParseUint(s, out, max, true); +} + +// TODO: string_view +template +bool ParseByteCount(const std::string& s, T* out, T max = std::numeric_limits::max()) { + return ParseByteCount(s.c_str(), out, max); +} + +// Parses the signed decimal or hexadecimal integer in the string 's' and sets +// 'out' to that value if it is specified. Optionally allows the caller to define +// a 'min' and 'max' beyond which otherwise valid values will be rejected. Returns +// boolean success; 'out' is untouched if parsing fails. +template +bool ParseInt(const char* s, T* out, + T min = std::numeric_limits::min(), + T max = std::numeric_limits::max()) { + static_assert(std::is_signed::value, "ParseInt can only be used with signed types"); + while (isspace(*s)) { + s++; + } + + int base = (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10; + errno = 0; + char* end; + long long int result = strtoll(s, &end, base); + if (errno != 0) { + return false; + } + if (s == end || *end != '\0') { + errno = EINVAL; + return false; + } + if (result < min || max < result) { + errno = ERANGE; + return false; + } + if (out != nullptr) { + *out = static_cast(result); + } + return true; +} + +// TODO: string_view +template +bool ParseInt(const std::string& s, T* out, + T min = std::numeric_limits::min(), + T max = std::numeric_limits::max()) { + return ParseInt(s.c_str(), out, min, max); +} + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/posix_strerror_r.cpp b/base/cvd/android-base/posix_strerror_r.cpp new file mode 100644 index 0000000000..6428a98033 --- /dev/null +++ b/base/cvd/android-base/posix_strerror_r.cpp @@ -0,0 +1,34 @@ +// +// Copyright (C) 2021 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. +// + +/* Undefine _GNU_SOURCE so that this compilation unit can access the + * posix version of strerror_r */ +#undef _GNU_SOURCE +#include + +namespace android { +namespace base { + +extern "C" int posix_strerror_r(int errnum, char* buf, size_t buflen) { +#ifdef _WIN32 + return strerror_s(buf, buflen, errnum); +#else + return strerror_r(errnum, buf, buflen); +#endif +} + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/result.h b/base/cvd/android-base/result.h new file mode 100644 index 0000000000..be368781ee --- /dev/null +++ b/base/cvd/android-base/result.h @@ -0,0 +1,447 @@ +/* + * 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. + */ + +// Result is the type that is used to pass a success value of type T or an error code of type +// E, optionally together with an error message. T and E can be any type. If E is omitted it +// defaults to int, which is useful when errno(3) is used as the error code. +// +// Passing a success value or an error value: +// +// Result readFile() { +// std::string content; +// if (base::ReadFileToString("path", &content)) { +// return content; // ok case +// } else { +// return ErrnoError() << "failed to read"; // error case +// } +// } +// +// Checking the result and then unwrapping the value or propagating the error: +// +// Result hasAWord() { +// auto content = readFile(); +// if (!content.ok()) { +// return Error() << "failed to process: " << content.error(); +// } +// return (*content.find("happy") != std::string::npos); +// } +// +// Using custom error code type: +// +// enum class MyError { A, B }; // assume that this is the error code you already have +// +// // To use the error code with Result, define a wrapper class that provides the following +// operations and use the wrapper class as the second type parameter (E) when instantiating +// Result +// +// 1. default constructor +// 2. copy constructor / and move constructor if copying is expensive +// 3. conversion operator to the error code type +// 4. value() function that return the error code value +// 5. print() function that gives a string representation of the error ode value +// +// struct MyErrorWrapper { +// MyError val_; +// MyErrorWrapper() : val_(/* reasonable default value */) {} +// MyErrorWrapper(MyError&& e) : val_(std:forward(e)) {} +// operator const MyError&() const { return val_; } +// MyError value() const { return val_; } +// std::string print() const { +// switch(val_) { +// MyError::A: return "A"; +// MyError::B: return "B"; +// } +// } +// }; +// +// #define NewMyError(e) Error(MyError::e) +// +// Result val = NewMyError(A) << "some message"; +// +// Formatting the error message using fmtlib: +// +// Errorf("{} errors", num); // equivalent to Error() << num << " errors"; +// ErrnoErrorf("{} errors", num); // equivalent to ErrnoError() << num << " errors"; +// +// Returning success or failure, but not the value: +// +// Result doSomething() { +// if (success) return {}; +// else return Error() << "error occurred"; +// } +// +// Extracting error code: +// +// Result val = Error(3) << "some error occurred"; +// assert(3 == val.error().code()); +// + +#pragma once + +#include +#include + +#include +#include +#include + +#include "android-base/errors.h" +#include "android-base/expected.h" +#include "android-base/format.h" + +namespace android { +namespace base { + +// Errno is a wrapper class for errno(3). Use this type instead of `int` when instantiating +// `Result` and `Error` template classes. This is required to distinguish errno from other +// integer-based error code types like `status_t`. +struct Errno { + Errno() : val_(0) {} + Errno(int e) : val_(e) {} + int value() const { return val_; } + operator int() const { return value(); } + std::string print() const { return strerror(value()); } + + int val_; + + // TODO(b/209929099): remove this conversion operator. This currently is needed to not break + // existing places where error().code() is used to construct enum values. + template >> + operator E() const { + return E(val_); + } +}; + +template +struct ResultError { + template >> + ResultError(T&& message, P&& code) + : message_(std::forward(message)), code_(E(std::forward

(code))) {} + + template + // NOLINTNEXTLINE(google-explicit-constructor) + operator android::base::expected>() const { + return android::base::unexpected(ResultError(message_, code_)); + } + + const std::string& message() const { return message_; } + const E& code() const { return code_; } + + private: + std::string message_; + E code_; +}; + +template +struct ResultError { + template >> + ResultError(P&& code) : code_(E(std::forward

(code))) {} + + template + operator android::base::expected>() const { + return android::base::unexpected(ResultError(code_)); + } + + const E& code() const { return code_; } + + private: + E code_; +}; + +template +inline bool operator==(const ResultError& lhs, const ResultError& rhs) { + return lhs.message() == rhs.message() && lhs.code() == rhs.code(); +} + +template +inline bool operator!=(const ResultError& lhs, const ResultError& rhs) { + return !(lhs == rhs); +} + +template +inline std::ostream& operator<<(std::ostream& os, const ResultError& t) { + os << t.message(); + return os; +} + +namespace internal { +// Stream class that does nothing and is has zero (actually 1) size. It is used instead of +// std::stringstream when include_message is false so that we use less on stack. +// sizeof(std::stringstream) is 280 on arm64. +struct DoNothingStream { + template + DoNothingStream& operator<<(T&&) { + return *this; + } + + std::string str() const { return ""; } +}; +} // namespace internal + +template >> +class Error { + public: + Error() : code_(0), has_code_(false) {} + template >> + // NOLINTNEXTLINE(google-explicit-constructor) + Error(P&& code) : code_(std::forward

(code)), has_code_(true) {} + + template >> + // NOLINTNEXTLINE(google-explicit-constructor) + operator android::base::expected>() const { + return android::base::unexpected(ResultError

(str(), static_cast

(code_))); + } + + template >> + // NOLINTNEXTLINE(google-explicit-constructor) + operator android::base::expected>() const { + return android::base::unexpected(ResultError(static_cast

(code_))); + } + + template + Error& operator<<(T&& t) { + static_assert(include_message, "<< not supported when include_message = false"); + // NOLINTNEXTLINE(bugprone-suspicious-semicolon) + if constexpr (std::is_same_v>, ResultError>) { + if (!has_code_) { + code_ = t.code(); + } + return (*this) << t.message(); + } + int saved = errno; + ss_ << t; + errno = saved; + return *this; + } + + const std::string str() const { + static_assert(include_message, "str() not supported when include_message = false"); + std::string str = ss_.str(); + if (has_code_) { + if (str.empty()) { + return code_.print(); + } + return std::move(str) + ": " + code_.print(); + } + return str; + } + + Error(const Error&) = delete; + Error(Error&&) = delete; + Error& operator=(const Error&) = delete; + Error& operator=(Error&&) = delete; + + template + friend Error ErrorfImpl(const T&& fmt, const Args&... args); + + template + friend Error ErrnoErrorfImpl(const T&& fmt, const Args&... args); + + private: + Error(bool has_code, E code, const std::string& message) : code_(code), has_code_(has_code) { + (*this) << message; + } + + std::conditional_t ss_; + E code_; + const bool has_code_; +}; + +inline Error ErrnoError() { + return Error(Errno{errno}); +} + +template +inline E ErrorCode(E code) { + return code; +} + +// Return the error code of the last ResultError object, if any. +// Otherwise, return `code` as it is. +template +inline E ErrorCode(E code, T&& t, const Args&... args) { + if constexpr (std::is_same_v>, ResultError>) { + return ErrorCode(t.code(), args...); + } + return ErrorCode(code, args...); +} + +template +inline Error ErrorfImpl(const T&& fmt, const Args&... args) { + return Error(false, ErrorCode(Errno{}, args...), fmt::format(fmt, args...)); +} + +template +inline Error ErrnoErrorfImpl(const T&& fmt, const Args&... args) { + return Error(true, Errno{errno}, fmt::format(fmt, args...)); +} + +#define Errorf(fmt, ...) android::base::ErrorfImpl(FMT_STRING(fmt), ##__VA_ARGS__) +#define ErrnoErrorf(fmt, ...) android::base::ErrnoErrorfImpl(FMT_STRING(fmt), ##__VA_ARGS__) + +template +using Result = android::base::expected>; + +// Specialization of android::base::OkOrFail for V = Result. See android-base/errors.h +// for the contract. + +namespace impl { +template +using Code = std::decay_t().error().code())>; + +template +using ErrorType = std::decay_t().error())>; + +template +constexpr bool IsNumeric = std::is_integral_v || std::is_floating_point_v || + (std::is_enum_v && std::is_convertible_v); + +// This base class exists to take advantage of shadowing +// We include the conversion in this base class so that if the conversion in NumericConversions +// overlaps, we (arbitrarily) choose the implementation in NumericConversions due to shadowing. +template +struct ConversionBase { + ErrorType error_; + // T is a expected>. + operator const T() const && { + return unexpected(std::move(error_)); + } + + operator const Code() const && { + return error_.code(); + } + +}; + +// User defined conversions can be followed by numeric conversions +// Although we template specialize for the exact code type, we need +// specializations for conversions to all numeric types to avoid an +// ambiguous conversion sequence. +template +struct NumericConversions : public ConversionBase {}; +template +struct NumericConversions>> + > : public ConversionBase +{ +#pragma push_macro("SPECIALIZED_CONVERSION") +#define SPECIALIZED_CONVERSION(type)\ + operator const expected>() const &&\ + { return unexpected(std::move(this->error_));} + + SPECIALIZED_CONVERSION(int) + SPECIALIZED_CONVERSION(short int) + SPECIALIZED_CONVERSION(unsigned short int) + SPECIALIZED_CONVERSION(unsigned int) + SPECIALIZED_CONVERSION(long int) + SPECIALIZED_CONVERSION(unsigned long int) + SPECIALIZED_CONVERSION(long long int) + SPECIALIZED_CONVERSION(unsigned long long int) + SPECIALIZED_CONVERSION(bool) + SPECIALIZED_CONVERSION(char) + SPECIALIZED_CONVERSION(unsigned char) + SPECIALIZED_CONVERSION(signed char) + SPECIALIZED_CONVERSION(wchar_t) + SPECIALIZED_CONVERSION(char16_t) + SPECIALIZED_CONVERSION(char32_t) + SPECIALIZED_CONVERSION(float) + SPECIALIZED_CONVERSION(double) + SPECIALIZED_CONVERSION(long double) + +#undef SPECIALIZED_CONVERSION +#pragma pop_macro("SPECIALIZED_CONVERSION") + // For debugging purposes + using IsNumericT = std::true_type; +}; + +#ifdef __cpp_concepts +template +// Define a concept which **any** type matches to +concept Universal = std::is_same_v; +#endif +} // namespace impl + +template +struct OkOrFail> + : public impl::NumericConversions> { + using V = Result; + using Err = impl::ErrorType; + using C = impl::Code; +private: + OkOrFail(Err&& v): impl::NumericConversions{std::move(v)} {} + OkOrFail(const OkOrFail& other) = delete; + OkOrFail(const OkOrFail&& other) = delete; +public: + // Checks if V is ok or fail + static bool IsOk(const V& val) { return val.ok(); } + + // Turns V into a success value + static T Unwrap(V&& val) { + if constexpr (std::is_same_v) { + assert(IsOk(val)); + return; + } else { + return std::move(val.value()); + } + } + + // Consumes V when it's a fail value + static const OkOrFail Fail(V&& v) { + assert(!IsOk(v)); + return OkOrFail{std::move(v.error())}; + } + + // We specialize as much as possible to avoid ambiguous conversion with + // templated expected ctor + operator const Result() const && { + return unexpected(std::move(this->error_)); + } +#ifdef __cpp_concepts + // The idea here is to match this template method to any type (not simply trivial types). + // The reason for including a constraint is to take advantage of the fact that a constrained + // method always has strictly lower precedence than a non-constrained method in template + // specialization rules (thus avoiding ambiguity). So we use a universally matching constraint to + // mark this function as less preferable (but still accepting of all types). + template +#else + template +#endif + operator const Result() const&& { + return unexpected(std::move(this->error_)); + } + + static std::string ErrorMessage(const V& val) { return val.error().message(); } +}; + +// Macros for testing the results of functions that return android::base::Result. +// These also work with base::android::expected. +// For advanced matchers and customized error messages, see result-gtest.h. + +#define ASSERT_RESULT_OK(stmt) \ + if (const auto& tmp = (stmt); !tmp.ok()) \ + FAIL() << "Value of: " << #stmt << "\n" \ + << " Actual: " << tmp.error().message() << "\n" \ + << "Expected: is ok\n" + +#define EXPECT_RESULT_OK(stmt) \ + if (const auto& tmp = (stmt); !tmp.ok()) \ + ADD_FAILURE() << "Value of: " << #stmt << "\n" \ + << " Actual: " << tmp.error().message() << "\n" \ + << "Expected: is ok\n" + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/scopeguard.h b/base/cvd/android-base/scopeguard.h new file mode 100644 index 0000000000..8293b2c733 --- /dev/null +++ b/base/cvd/android-base/scopeguard.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 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 + +#include // for std::move, std::forward + +namespace android { +namespace base { + +// ScopeGuard ensures that the specified functor is executed no matter how the +// current scope exits. +template +class ScopeGuard { + public: + constexpr ScopeGuard(F&& f) : f_(std::forward(f)), active_(true) {} + + constexpr ScopeGuard(ScopeGuard&& that) noexcept : f_(std::move(that.f_)), active_(that.active_) { + that.active_ = false; + } + + template + constexpr ScopeGuard(ScopeGuard&& that) : f_(std::move(that.f_)), active_(that.active_) { + that.active_ = false; + } + + ~ScopeGuard() { + if (active_) f_(); + } + + ScopeGuard() = delete; + ScopeGuard(const ScopeGuard&) = delete; + void operator=(const ScopeGuard&) = delete; + void operator=(ScopeGuard&& that) = delete; + + void Disable() { active_ = false; } + + constexpr bool active() const { return active_; } + + private: + template + friend class ScopeGuard; + + F f_; + bool active_; +}; + +template +ScopeGuard make_scope_guard(F&& f) { + return ScopeGuard(std::forward(f)); +} + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/stringprintf.cpp b/base/cvd/android-base/stringprintf.cpp new file mode 100644 index 0000000000..e83ab1316f --- /dev/null +++ b/base/cvd/android-base/stringprintf.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2011 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. + */ + +#include "android-base/stringprintf.h" + +#include + +#include + +namespace android { +namespace base { + +void StringAppendV(std::string* dst, const char* format, va_list ap) { + // First try with a small fixed size buffer + char space[1024] __attribute__((__uninitialized__)); + + // It's possible for methods that use a va_list to invalidate + // the data in it upon use. The fix is to make a copy + // of the structure before using it and use that copy instead. + va_list backup_ap; + va_copy(backup_ap, ap); + int result = vsnprintf(space, sizeof(space), format, backup_ap); + va_end(backup_ap); + + if (result < static_cast(sizeof(space))) { + if (result >= 0) { + // Normal case -- everything fit. + dst->append(space, result); + return; + } + + if (result < 0) { + // Just an error. + return; + } + } + + // Increase the buffer size to the size requested by vsnprintf, + // plus one for the closing \0. + int length = result + 1; + char* buf = new char[length]; + + // Restore the va_list before we use it again + va_copy(backup_ap, ap); + result = vsnprintf(buf, length, format, backup_ap); + va_end(backup_ap); + + if (result >= 0 && result < length) { + // It fit + dst->append(buf, result); + } + delete[] buf; +} + +std::string StringPrintf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string result; + StringAppendV(&result, fmt, ap); + va_end(ap); + return result; +} + +void StringAppendF(std::string* dst, const char* format, ...) { + va_list ap; + va_start(ap, format); + StringAppendV(dst, format, ap); + va_end(ap); +} + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/stringprintf.h b/base/cvd/android-base/stringprintf.h new file mode 100644 index 0000000000..93c56afd74 --- /dev/null +++ b/base/cvd/android-base/stringprintf.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 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 + +#include +#include + +namespace android { +namespace base { + +// These printf-like functions are implemented in terms of vsnprintf, so they +// use the same attribute for compile-time format string checking. + +// Returns a string corresponding to printf-like formatting of the arguments. +std::string StringPrintf(const char* fmt, ...) __attribute__((__format__(__printf__, 1, 2))); + +// Appends a printf-like formatting of the arguments to 'dst'. +void StringAppendF(std::string* dst, const char* fmt, ...) + __attribute__((__format__(__printf__, 2, 3))); + +// Appends a printf-like formatting of the arguments to 'dst'. +void StringAppendV(std::string* dst, const char* format, va_list ap) + __attribute__((__format__(__printf__, 2, 0))); + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/strings.cpp b/base/cvd/android-base/strings.cpp new file mode 100644 index 0000000000..5ff2a520b9 --- /dev/null +++ b/base/cvd/android-base/strings.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2015 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. + */ + +#include "android-base/strings.h" + +#include "android-base/stringprintf.h" + +#include +#include + +#include +#include + +// Wraps the posix version of strerror_r to make it available in translation units +// that define _GNU_SOURCE. +extern "C" int posix_strerror_r(int errnum, char* buf, size_t buflen); + +namespace android { +namespace base { + +#define CHECK_NE(a, b) \ + if ((a) == (b)) abort(); + +std::vector Split(const std::string& s, + const std::string& delimiters) { + CHECK_NE(delimiters.size(), 0U); + + std::vector result; + + size_t base = 0; + size_t found; + while (true) { + found = s.find_first_of(delimiters, base); + result.push_back(s.substr(base, found - base)); + if (found == s.npos) break; + base = found + 1; + } + + return result; +} + +std::vector Tokenize(const std::string& s, const std::string& delimiters) { + CHECK_NE(delimiters.size(), 0U); + + std::vector result; + size_t end = 0; + + while (true) { + size_t base = s.find_first_not_of(delimiters, end); + if (base == s.npos) { + break; + } + end = s.find_first_of(delimiters, base); + result.push_back(s.substr(base, end - base)); + } + return result; +} + +[[deprecated("Retained only for binary compatibility (symbol name)")]] +std::string Trim(const std::string& s) { + return Trim(std::string_view(s)); +} + +template std::string Trim(const char*&); +template std::string Trim(const char*&&); +template std::string Trim(const std::string&); +template std::string Trim(const std::string&&); +template std::string Trim(std::string_view&); +template std::string Trim(std::string_view&&); + +// These cases are probably the norm, so we mark them extern in the header to +// aid compile time and binary size. +template std::string Join(const std::vector&, char); +template std::string Join(const std::vector&, char); +template std::string Join(const std::vector&, const std::string&); +template std::string Join(const std::vector&, const std::string&); + +bool StartsWith(std::string_view s, std::string_view prefix) { + return s.substr(0, prefix.size()) == prefix; +} + +bool StartsWith(std::string_view s, char prefix) { + return !s.empty() && s.front() == prefix; +} + +bool StartsWithIgnoreCase(std::string_view s, std::string_view prefix) { + return s.size() >= prefix.size() && strncasecmp(s.data(), prefix.data(), prefix.size()) == 0; +} + +bool EndsWith(std::string_view s, std::string_view suffix) { + return s.size() >= suffix.size() && s.substr(s.size() - suffix.size(), suffix.size()) == suffix; +} + +bool EndsWith(std::string_view s, char suffix) { + return !s.empty() && s.back() == suffix; +} + +bool EndsWithIgnoreCase(std::string_view s, std::string_view suffix) { + return s.size() >= suffix.size() && + strncasecmp(s.data() + (s.size() - suffix.size()), suffix.data(), suffix.size()) == 0; +} + +bool EqualsIgnoreCase(std::string_view lhs, std::string_view rhs) { + return lhs.size() == rhs.size() && strncasecmp(lhs.data(), rhs.data(), lhs.size()) == 0; +} + +std::string StringReplace(std::string_view s, std::string_view from, std::string_view to, + bool all) { + if (from.empty()) return std::string(s); + + std::string result; + std::string_view::size_type start_pos = 0; + do { + std::string_view::size_type pos = s.find(from, start_pos); + if (pos == std::string_view::npos) break; + + result.append(s.data() + start_pos, pos - start_pos); + result.append(to.data(), to.size()); + + start_pos = pos + from.size(); + } while (all); + result.append(s.data() + start_pos, s.size() - start_pos); + return result; +} + +std::string ErrnoNumberAsString(int errnum) { + char buf[100]; + buf[0] = '\0'; + int strerror_err = posix_strerror_r(errnum, buf, sizeof(buf)); + if (strerror_err < 0) { + return StringPrintf("Failed to convert errno %d to string: %d", errnum, strerror_err); + } + return buf; +} + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/strings.h b/base/cvd/android-base/strings.h new file mode 100644 index 0000000000..9557fad616 --- /dev/null +++ b/base/cvd/android-base/strings.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 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 + +#include + +#include +#include +#include +#include +#include +#include + +namespace android { +namespace base { + +// Splits a string into a vector of strings. +// +// The string is split at each occurrence of a character in delimiters. +// +// The empty string is not a valid delimiter list. +std::vector Split(const std::string& s, + const std::string& delimiters); + +// Splits a string into a vector of string tokens. +// +// The string is split at each occurrence of a character in delimiters. +// Coalesce runs of delimiter bytes and ignore delimiter bytes at the start or +// end of string. In other words, return only nonempty string tokens. +// Use when you don't care about recovering the original string with Join(). +// +// Example: +// Tokenize(" foo bar ", " ") => {"foo", "bar"} +// Join(Tokenize(" foo bar", " "), " ") => "foo bar" +// +// The empty string is not a valid delimiter list. +std::vector Tokenize(const std::string& s, const std::string& delimiters); + +namespace internal { +template +constexpr bool always_false_v = false; +} + +template +std::string Trim(T&& t) { + std::string_view sv; + std::string s; + if constexpr (std::is_convertible_v) { + sv = std::forward(t); + } else if constexpr (std::is_convertible_v) { + // The previous version of this function allowed for types which are implicitly convertible + // to std::string but not to std::string_view. For these types we go through std::string first + // here in order to retain source compatibility. + s = t; + sv = s; + } else { + static_assert(internal::always_false_v, + "Implicit conversion to std::string or std::string_view not possible"); + } + + // Skip initial whitespace. + while (!sv.empty() && isspace(sv.front())) { + sv.remove_prefix(1); + } + + // Skip terminating whitespace. + while (!sv.empty() && isspace(sv.back())) { + sv.remove_suffix(1); + } + + return std::string(sv); +} + +// We instantiate the common cases in strings.cpp. +extern template std::string Trim(const char*&); +extern template std::string Trim(const char*&&); +extern template std::string Trim(const std::string&); +extern template std::string Trim(const std::string&&); +extern template std::string Trim(std::string_view&); +extern template std::string Trim(std::string_view&&); + +// Joins a container of things into a single string, using the given separator. +template +std::string Join(const ContainerT& things, SeparatorT separator) { + if (things.empty()) { + return ""; + } + + std::ostringstream result; + result << *things.begin(); + for (auto it = std::next(things.begin()); it != things.end(); ++it) { + result << separator << *it; + } + return result.str(); +} + +// We instantiate the common cases in strings.cpp. +extern template std::string Join(const std::vector&, char); +extern template std::string Join(const std::vector&, char); +extern template std::string Join(const std::vector&, const std::string&); +extern template std::string Join(const std::vector&, const std::string&); + +// Tests whether 's' starts with 'prefix'. +bool StartsWith(std::string_view s, std::string_view prefix); +bool StartsWith(std::string_view s, char prefix); +bool StartsWithIgnoreCase(std::string_view s, std::string_view prefix); + +// Tests whether 's' ends with 'suffix'. +bool EndsWith(std::string_view s, std::string_view suffix); +bool EndsWith(std::string_view s, char suffix); +bool EndsWithIgnoreCase(std::string_view s, std::string_view suffix); + +// Tests whether 'lhs' equals 'rhs', ignoring case. +bool EqualsIgnoreCase(std::string_view lhs, std::string_view rhs); + +// Removes `prefix` from the start of the given string and returns true (if +// it was present), false otherwise. +inline bool ConsumePrefix(std::string_view* s, std::string_view prefix) { + if (!StartsWith(*s, prefix)) return false; + s->remove_prefix(prefix.size()); + return true; +} + +// Removes `suffix` from the end of the given string and returns true (if +// it was present), false otherwise. +inline bool ConsumeSuffix(std::string_view* s, std::string_view suffix) { + if (!EndsWith(*s, suffix)) return false; + s->remove_suffix(suffix.size()); + return true; +} + +// Replaces `from` with `to` in `s`, once if `all == false`, or as many times as +// there are matches if `all == true`. +[[nodiscard]] std::string StringReplace(std::string_view s, std::string_view from, + std::string_view to, bool all); + +// Converts an errno number to its error message string. +std::string ErrnoNumberAsString(int errnum); + +} // namespace base +} // namespace android diff --git a/base/cvd/android-base/threads.cpp b/base/cvd/android-base/threads.cpp new file mode 100644 index 0000000000..4197ff80ab --- /dev/null +++ b/base/cvd/android-base/threads.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 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. + */ + +#include + +#include +#include + +#if defined(__APPLE__) +#include +#elif defined(__linux__) && !defined(__ANDROID__) +#include +#elif defined(_WIN32) +#include +#endif + +namespace android { +namespace base { + +uint64_t GetThreadId() { +#if defined(__BIONIC__) || defined(__EMSCRIPTEN__) + return gettid(); +#elif defined(__APPLE__) + uint64_t tid; + pthread_threadid_np(NULL, &tid); + return tid; +#elif defined(__linux__) + return syscall(__NR_gettid); +#elif defined(_WIN32) + return GetCurrentThreadId(); +#endif +} + +} // namespace base +} // namespace android + +#if defined(__GLIBC__) || defined(ANDROID_HOST_MUSL) +int tgkill(int tgid, int tid, int sig) { + return syscall(__NR_tgkill, tgid, tid, sig); +} +#endif diff --git a/base/cvd/android-base/threads.h b/base/cvd/android-base/threads.h new file mode 100644 index 0000000000..dbf1b4704b --- /dev/null +++ b/base/cvd/android-base/threads.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 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 + +#include + +namespace android { +namespace base { +uint64_t GetThreadId(); +} +} // namespace android + +#if defined(__GLIBC__) || defined(ANDROID_HOST_MUSL) +// bionic has this Linux-specifix call, but glibc and musl don't. +extern "C" int tgkill(int tgid, int tid, int sig); +#endif diff --git a/base/cvd/android-base/unique_fd.h b/base/cvd/android-base/unique_fd.h new file mode 100644 index 0000000000..1ffe02f6a6 --- /dev/null +++ b/base/cvd/android-base/unique_fd.h @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2015 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 + +#include +#include +#include + +#include +#include +#include + +// DO NOT INCLUDE OTHER LIBBASE HEADERS HERE! +// This file gets used in libbinder, and libbinder is used everywhere. +// Including other headers from libbase frequently results in inclusion of +// android-base/macros.h, which causes macro collisions. + +#if defined(__BIONIC__) +#include +#endif +#if !defined(_WIN32) && !defined(__TRUSTY__) +#include +#endif + +namespace android { +namespace base { + +// Container for a file descriptor that automatically closes the descriptor as +// it goes out of scope. +// +// unique_fd ufd(open("/some/path", "r")); +// if (ufd.get() == -1) return error; +// +// // Do something useful, possibly including 'return'. +// +// return 0; // Descriptor is closed for you. +// +// See also the Pipe()/Socketpair()/Fdopen()/Fdopendir() functions in this file +// that provide interoperability with the libc functions with the same (but +// lowercase) names. +// +// unique_fd is also known as ScopedFd/ScopedFD/scoped_fd; mentioned here to help +// you find this class if you're searching for one of those names. +// +// unique_fd itself is a specialization of unique_fd_impl with a default closer. +template +class unique_fd_impl final { + public: + unique_fd_impl() {} + + explicit unique_fd_impl(int fd) { reset(fd); } + ~unique_fd_impl() { reset(); } + + unique_fd_impl(const unique_fd_impl&) = delete; + void operator=(const unique_fd_impl&) = delete; + unique_fd_impl(unique_fd_impl&& other) noexcept { reset(other.release()); } + unique_fd_impl& operator=(unique_fd_impl&& s) noexcept { + int fd = s.fd_; + s.fd_ = -1; + reset(fd, &s); + return *this; + } + + [[clang::reinitializes]] void reset(int new_value = -1) { reset(new_value, nullptr); } + + int get() const { return fd_; } + +#if !defined(ANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION) + // unique_fd's operator int is dangerous, but we have way too much code that + // depends on it, so make this opt-in at first. + operator int() const { return get(); } // NOLINT +#endif + + bool operator>=(int rhs) const { return get() >= rhs; } + bool operator<(int rhs) const { return get() < rhs; } + bool operator==(int rhs) const { return get() == rhs; } + bool operator!=(int rhs) const { return get() != rhs; } + bool operator==(const unique_fd_impl& rhs) const { return get() == rhs.get(); } + bool operator!=(const unique_fd_impl& rhs) const { return get() != rhs.get(); } + + // Catch bogus error checks (i.e.: "!fd" instead of "fd != -1"). + bool operator!() const = delete; + + bool ok() const { return get() >= 0; } + + int release() __attribute__((warn_unused_result)) { + tag(fd_, this, nullptr); + int ret = fd_; + fd_ = -1; + return ret; + } + + private: + void reset(int new_value, void* previous_tag) { + int previous_errno = errno; + + if (fd_ != -1) { + close(fd_, this); + } + + fd_ = new_value; + if (new_value != -1) { + tag(new_value, previous_tag, this); + } + + errno = previous_errno; + } + + int fd_ = -1; + + // Template magic to use Closer::Tag if available, and do nothing if not. + // If Closer::Tag exists, this implementation is preferred, because int is a better match. + // If not, this implementation is SFINAEd away, and the no-op below is the only one that exists. + template + static auto tag(int fd, void* old_tag, void* new_tag) + -> decltype(T::Tag(fd, old_tag, new_tag), void()) { + T::Tag(fd, old_tag, new_tag); + } + + template + static void tag(long, void*, void*) { + // No-op. + } + + // Same as above, to select between Closer::Close(int) and Closer::Close(int, void*). + template + static auto close(int fd, void* tag_value) -> decltype(T::Close(fd, tag_value), void()) { + T::Close(fd, tag_value); + } + + template + static auto close(int fd, void*) -> decltype(T::Close(fd), void()) { + T::Close(fd); + } +}; + +// The actual details of closing are factored out to support unusual cases. +// Almost everyone will want this DefaultCloser, which handles fdsan on bionic. +struct DefaultCloser { +#if defined(__BIONIC__) + static void Tag(int fd, void* old_addr, void* new_addr) { + if (android_fdsan_exchange_owner_tag) { + uint64_t old_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD, + reinterpret_cast(old_addr)); + uint64_t new_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD, + reinterpret_cast(new_addr)); + android_fdsan_exchange_owner_tag(fd, old_tag, new_tag); + } + } + static void Close(int fd, void* addr) { + if (android_fdsan_close_with_tag) { + uint64_t tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD, + reinterpret_cast(addr)); + android_fdsan_close_with_tag(fd, tag); + } else { + close(fd); + } + } +#else + static void Close(int fd) { + // Even if close(2) fails with EINTR, the fd will have been closed. + // Using TEMP_FAILURE_RETRY will either lead to EBADF or closing someone + // else's fd. + // http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html + ::close(fd); + } +#endif +}; + +using unique_fd = unique_fd_impl; + +#if !defined(_WIN32) && !defined(__TRUSTY__) + +// Inline functions, so that they can be used header-only. + +// See pipe(2). +// This helper hides the details of converting to unique_fd, and also hides the +// fact that macOS doesn't support O_CLOEXEC or O_NONBLOCK directly. +template +inline bool Pipe(unique_fd_impl* read, unique_fd_impl* write, + int flags = O_CLOEXEC) { + int pipefd[2]; + +#if defined(__linux__) + if (pipe2(pipefd, flags) != 0) { + return false; + } +#else // defined(__APPLE__) + if (flags & ~(O_CLOEXEC | O_NONBLOCK)) { + return false; + } + if (pipe(pipefd) != 0) { + return false; + } + + if (flags & O_CLOEXEC) { + if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 || fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) { + close(pipefd[0]); + close(pipefd[1]); + return false; + } + } + if (flags & O_NONBLOCK) { + if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) != 0 || fcntl(pipefd[1], F_SETFL, O_NONBLOCK) != 0) { + close(pipefd[0]); + close(pipefd[1]); + return false; + } + } +#endif + + read->reset(pipefd[0]); + write->reset(pipefd[1]); + return true; +} + +// See socketpair(2). +// This helper hides the details of converting to unique_fd. +template +inline bool Socketpair(int domain, int type, int protocol, unique_fd_impl* left, + unique_fd_impl* right) { + int sockfd[2]; + if (socketpair(domain, type, protocol, sockfd) != 0) { + return false; + } + left->reset(sockfd[0]); + right->reset(sockfd[1]); + return true; +} + +// See socketpair(2). +// This helper hides the details of converting to unique_fd. +template +inline bool Socketpair(int type, unique_fd_impl* left, unique_fd_impl* right) { + return Socketpair(AF_UNIX, type, 0, left, right); +} + +// See fdopen(3). +// Using fdopen with unique_fd correctly is more annoying than it should be, +// because fdopen doesn't close the file descriptor received upon failure. +inline FILE* Fdopen(unique_fd&& ufd, const char* mode) { + int fd = ufd.release(); + FILE* file = fdopen(fd, mode); + if (!file) { + close(fd); + } + return file; +} + +// See fdopendir(3). +// Using fdopendir with unique_fd correctly is more annoying than it should be, +// because fdopen doesn't close the file descriptor received upon failure. +inline DIR* Fdopendir(unique_fd&& ufd) { + int fd = ufd.release(); + DIR* dir = fdopendir(fd); + if (dir == nullptr) { + close(fd); + } + return dir; +} + +#endif // !defined(_WIN32) && !defined(__TRUSTY__) + +// A wrapper type that can be implicitly constructed from either int or +// unique_fd. This supports cases where you don't actually own the file +// descriptor, and can't take ownership, but are temporarily acting as if +// you're the owner. +// +// One example would be a function that needs to also allow +// STDERR_FILENO, not just a newly-opened fd. Another example would be JNI code +// that's using a file descriptor that's actually owned by a +// ParcelFileDescriptor or whatever on the Java side, but where the JNI code +// would like to enforce this weaker sense of "temporary ownership". +// +// If you think of unique_fd as being like std::string in that represents +// ownership, borrowed_fd is like std::string_view (and int is like const +// char*). +struct borrowed_fd { + /* implicit */ borrowed_fd(int fd) : fd_(fd) {} // NOLINT + template + /* implicit */ borrowed_fd(const unique_fd_impl& ufd) : fd_(ufd.get()) {} // NOLINT + + int get() const { return fd_; } + + bool operator>=(int rhs) const { return get() >= rhs; } + bool operator<(int rhs) const { return get() < rhs; } + bool operator==(int rhs) const { return get() == rhs; } + bool operator!=(int rhs) const { return get() != rhs; } + + private: + int fd_ = -1; +}; +} // namespace base +} // namespace android + +template +int close(const android::base::unique_fd_impl&) + __attribute__((__unavailable__("close called on unique_fd"))); + +template +FILE* fdopen(const android::base::unique_fd_impl&, const char* mode) + __attribute__((__unavailable__("fdopen takes ownership of the fd passed in; either dup the " + "unique_fd, or use android::base::Fdopen to pass ownership"))); + +template +DIR* fdopendir(const android::base::unique_fd_impl&) __attribute__(( + __unavailable__("fdopendir takes ownership of the fd passed in; either dup the " + "unique_fd, or use android::base::Fdopendir to pass ownership"))); diff --git a/base/cvd/android-base/utf8.h b/base/cvd/android-base/utf8.h new file mode 100644 index 0000000000..1a414ec795 --- /dev/null +++ b/base/cvd/android-base/utf8.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 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 + +#ifdef _WIN32 +#include +#include +#else +// Bring in prototypes for standard APIs so that we can import them into the utf8 namespace. +#include // open +#include // fopen +#include // mkdir +#include // unlink +#endif + +namespace android { +namespace base { + +// Only available on Windows because this is only needed on Windows. +#ifdef _WIN32 +// Convert size number of UTF-16 wchar_t's to UTF-8. Returns whether the +// conversion was done successfully. +bool WideToUTF8(const wchar_t* utf16, const size_t size, std::string* utf8); + +// Convert a NULL-terminated string of UTF-16 characters to UTF-8. Returns +// whether the conversion was done successfully. +bool WideToUTF8(const wchar_t* utf16, std::string* utf8); + +// Convert a UTF-16 std::wstring (including any embedded NULL characters) to +// UTF-8. Returns whether the conversion was done successfully. +bool WideToUTF8(const std::wstring& utf16, std::string* utf8); + +// Convert size number of UTF-8 char's to UTF-16. Returns whether the conversion +// was done successfully. +bool UTF8ToWide(const char* utf8, const size_t size, std::wstring* utf16); + +// Convert a NULL-terminated string of UTF-8 characters to UTF-16. Returns +// whether the conversion was done successfully. +bool UTF8ToWide(const char* utf8, std::wstring* utf16); + +// Convert a UTF-8 std::string (including any embedded NULL characters) to +// UTF-16. Returns whether the conversion was done successfully. +bool UTF8ToWide(const std::string& utf8, std::wstring* utf16); + +// Convert a file system path, represented as a NULL-terminated string of +// UTF-8 characters, to a UTF-16 string representing the same file system +// path using the Windows extended-lengh path representation. +// +// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#MAXPATH: +// ```The Windows API has many functions that also have Unicode versions to +// permit an extended-length path for a maximum total path length of 32,767 +// characters. To specify an extended-length path, use the "\\?\" prefix. +// For example, "\\?\D:\very long path".``` +// +// Returns whether the conversion was done successfully. +bool UTF8PathToWindowsLongPath(const char* utf8, std::wstring* utf16); +#endif + +// The functions in the utf8 namespace take UTF-8 strings. For Windows, these +// are wrappers, for non-Windows these just expose existing APIs. To call these +// functions, use: +// +// // anonymous namespace to avoid conflict with existing open(), unlink(), etc. +// namespace { +// // Import functions into anonymous namespace. +// using namespace android::base::utf8; +// +// void SomeFunction(const char* name) { +// int fd = open(name, ...); // Calls android::base::utf8::open(). +// ... +// unlink(name); // Calls android::base::utf8::unlink(). +// } +// } +namespace utf8 { + +#ifdef _WIN32 +FILE* fopen(const char* name, const char* mode); +int mkdir(const char* name, mode_t mode); +int open(const char* name, int flags, ...); +int unlink(const char* name); +#else +using ::fopen; +using ::mkdir; +using ::open; +using ::unlink; +#endif + +} // namespace utf8 +} // namespace base +} // namespace android diff --git a/base/cvd/android/log.h b/base/cvd/android/log.h new file mode 100644 index 0000000000..5dc365a4dd --- /dev/null +++ b/base/cvd/android/log.h @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2009 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 + +/** + * @addtogroup Logging + * @{ + */ + +/** + * \file + * + * Support routines to send messages to the Android log buffer, + * which can later be accessed through the `logcat` utility. + * + * Each log message must have + * - a priority + * - a log tag + * - some text + * + * The tag normally corresponds to the component that emits the log message, + * and should be reasonably small. + * + * Log message text may be truncated to less than an implementation-specific + * limit (1023 bytes). + * + * Note that a newline character ("\n") will be appended automatically to your + * log message, if not already there. It is not possible to send several + * messages and have them appear on a single line in logcat. + * + * Please use logging in moderation: + * + * - Sending log messages eats CPU and slow down your application and the + * system. + * + * - The circular log buffer is pretty small, so sending many messages + * will hide other important log messages. + * + * - In release builds, only send log messages to account for exceptional + * conditions. + */ + +#include +#include +#include +#include + +#if !defined(__BIONIC__) && !defined(__INTRODUCED_IN) +#define __INTRODUCED_IN(x) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Android log priority values, in increasing order of priority. + */ +typedef enum android_LogPriority { + /** For internal use only. */ + ANDROID_LOG_UNKNOWN = 0, + /** The default priority, for internal use only. */ + ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */ + /** Verbose logging. Should typically be disabled for a release apk. */ + ANDROID_LOG_VERBOSE, + /** Debug logging. Should typically be disabled for a release apk. */ + ANDROID_LOG_DEBUG, + /** Informational logging. Should typically be disabled for a release apk. */ + ANDROID_LOG_INFO, + /** Warning logging. For use with recoverable failures. */ + ANDROID_LOG_WARN, + /** Error logging. For use with unrecoverable failures. */ + ANDROID_LOG_ERROR, + /** Fatal logging. For use when aborting. */ + ANDROID_LOG_FATAL, + /** For internal use only. */ + ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */ +} android_LogPriority; + +/** + * Writes the constant string `text` to the log, with priority `prio` and tag + * `tag`. + */ +int __android_log_write(int prio, const char* tag, const char* text); + +/** + * Writes a formatted string to the log, with priority `prio` and tag `tag`. + * The details of formatting are the same as for + * [printf(3)](http://man7.org/linux/man-pages/man3/printf.3.html). + */ +int __android_log_print(int prio, const char* tag, const char* fmt, ...) + __attribute__((__format__(printf, 3, 4))); + +/** + * Equivalent to `__android_log_print`, but taking a `va_list`. + * (If `__android_log_print` is like `printf`, this is like `vprintf`.) + */ +int __android_log_vprint(int prio, const char* tag, const char* fmt, va_list ap) + __attribute__((__format__(printf, 3, 0))); + +/** + * Writes an assertion failure to the log (as `ANDROID_LOG_FATAL`) and to + * stderr, before calling + * [abort(3)](http://man7.org/linux/man-pages/man3/abort.3.html). + * + * If `fmt` is non-null, `cond` is unused. If `fmt` is null, the string + * `Assertion failed: %s` is used with `cond` as the string argument. + * If both `fmt` and `cond` are null, a default string is provided. + * + * Most callers should use + * [assert(3)](http://man7.org/linux/man-pages/man3/assert.3.html) from + * `<assert.h>` instead, or the `__assert` and `__assert2` functions + * provided by bionic if more control is needed. They support automatically + * including the source filename and line number more conveniently than this + * function. + */ +void __android_log_assert(const char* cond, const char* tag, const char* fmt, ...) + __attribute__((__noreturn__)) __attribute__((__format__(printf, 3, 4))); + +/** + * Identifies a specific log buffer for __android_log_buf_write() + * and __android_log_buf_print(). + */ +typedef enum log_id { + LOG_ID_MIN = 0, + + /** The main log buffer. This is the only log buffer available to apps. */ + LOG_ID_MAIN = 0, + /** The radio log buffer. */ + LOG_ID_RADIO = 1, + /** The event log buffer. */ + LOG_ID_EVENTS = 2, + /** The system log buffer. */ + LOG_ID_SYSTEM = 3, + /** The crash log buffer. */ + LOG_ID_CRASH = 4, + /** The statistics log buffer. */ + LOG_ID_STATS = 5, + /** The security log buffer. */ + LOG_ID_SECURITY = 6, + /** The kernel log buffer. */ + LOG_ID_KERNEL = 7, + + LOG_ID_MAX, + + /** Let the logging function choose the best log target. */ + LOG_ID_DEFAULT = 0x7FFFFFFF +} log_id_t; + +/** + * Writes the constant string `text` to the log buffer `id`, + * with priority `prio` and tag `tag`. + * + * Apps should use __android_log_write() instead. + */ +int __android_log_buf_write(int bufID, int prio, const char* tag, const char* text); + +/** + * Writes a formatted string to log buffer `id`, + * with priority `prio` and tag `tag`. + * The details of formatting are the same as for + * [printf(3)](http://man7.org/linux/man-pages/man3/printf.3.html). + * + * Apps should use __android_log_print() instead. + */ +int __android_log_buf_print(int bufID, int prio, const char* tag, const char* fmt, ...) + __attribute__((__format__(printf, 4, 5))); + +/** + * Logger data struct used for writing log messages to liblog via __android_log_write_logger_data() + * and sending log messages to user defined loggers specified in __android_log_set_logger(). + */ +struct __android_log_message { + /** Must be set to sizeof(__android_log_message) and is used for versioning. */ + size_t struct_size; + + /** {@link log_id_t} values. */ + int32_t buffer_id; + + /** {@link android_LogPriority} values. */ + int32_t priority; + + /** The tag for the log message. */ + const char* tag; + + /** Optional file name, may be set to nullptr. */ + const char* file; + + /** Optional line number, ignore if file is nullptr. */ + uint32_t line; + + /** The log message itself. */ + const char* message; +}; + +/** + * Prototype for the 'logger' function that is called for every log message. + */ +typedef void (*__android_logger_function)(const struct __android_log_message* log_message); +/** + * Prototype for the 'abort' function that is called when liblog will abort due to + * __android_log_assert() failures. + */ +typedef void (*__android_aborter_function)(const char* abort_message); + +/** + * Writes the log message specified by log_message. log_message includes additional file name and + * line number information that a logger may use. log_message is versioned for backwards + * compatibility. + * This assumes that loggability has already been checked through __android_log_is_loggable(). + * Higher level logging libraries, such as libbase, first check loggability, then format their + * buffers, then pass the message to liblog via this function, and therefore we do not want to + * duplicate the loggability check here. + * + * @param log_message the log message itself, see __android_log_message. + * + * Available since API level 30. + */ +void __android_log_write_log_message(struct __android_log_message* log_message) __INTRODUCED_IN(30); + +/** + * Sets a user defined logger function. All log messages sent to liblog will be set to the + * function pointer specified by logger for processing. It is not expected that log messages are + * already terminated with a new line. This function should add new lines if required for line + * separation. + * + * @param logger the new function that will handle log messages. + * + * Available since API level 30. + */ +void __android_log_set_logger(__android_logger_function logger) __INTRODUCED_IN(30); + +/** + * Writes the log message to logd. This is an __android_logger_function and can be provided to + * __android_log_set_logger(). It is the default logger when running liblog on a device. + * + * @param log_message the log message to write, see __android_log_message. + * + * Available since API level 30. + */ +void __android_log_logd_logger(const struct __android_log_message* log_message) __INTRODUCED_IN(30); + +/** + * Writes the log message to stderr. This is an __android_logger_function and can be provided to + * __android_log_set_logger(). It is the default logger when running liblog on host. + * + * @param log_message the log message to write, see __android_log_message. + * + * Available since API level 30. + */ +void __android_log_stderr_logger(const struct __android_log_message* log_message) + __INTRODUCED_IN(30); + +/** + * Sets a user defined aborter function that is called for __android_log_assert() failures. This + * user defined aborter function is highly recommended to abort and be noreturn, but is not strictly + * required to. + * + * @param aborter the new aborter function, see __android_aborter_function. + * + * Available since API level 30. + */ +void __android_log_set_aborter(__android_aborter_function aborter) __INTRODUCED_IN(30); + +/** + * Calls the stored aborter function. This allows for other logging libraries to use the same + * aborter function by calling this function in liblog. + * + * @param abort_message an additional message supplied when aborting, for example this is used to + * call android_set_abort_message() in __android_log_default_aborter(). + * + * Available since API level 30. + */ +void __android_log_call_aborter(const char* abort_message) __INTRODUCED_IN(30); + +/** + * Sets android_set_abort_message() on device then aborts(). This is the default aborter. + * + * @param abort_message an additional message supplied when aborting. This functions calls + * android_set_abort_message() with its contents. + * + * Available since API level 30. + */ +void __android_log_default_aborter(const char* abort_message) __attribute__((noreturn)) +__INTRODUCED_IN(30); + +/** + * Use the per-tag properties "log.tag." along with the minimum priority from + * __android_log_set_minimum_priority() to determine if a log message with a given prio and tag will + * be printed. A non-zero result indicates yes, zero indicates false. + * + * If both a priority for a tag and a minimum priority are set by + * __android_log_set_minimum_priority(), then the lowest of the two values are to determine the + * minimum priority needed to log. If only one is set, then that value is used to determine the + * minimum priority needed. If none are set, then default_priority is used. + * + * @param prio the priority to test, takes android_LogPriority values. + * @param tag the tag to test. + * @param default_prio the default priority to use if no properties or minimum priority are set. + * @return an integer where 1 indicates that the message is loggable and 0 indicates that it is not. + * + * Available since API level 30. + */ +int __android_log_is_loggable(int prio, const char* tag, int default_prio) __INTRODUCED_IN(30); + +/** + * Use the per-tag properties "log.tag." along with the minimum priority from + * __android_log_set_minimum_priority() to determine if a log message with a given prio and tag will + * be printed. A non-zero result indicates yes, zero indicates false. + * + * If both a priority for a tag and a minimum priority are set by + * __android_log_set_minimum_priority(), then the lowest of the two values are to determine the + * minimum priority needed to log. If only one is set, then that value is used to determine the + * minimum priority needed. If none are set, then default_priority is used. + * + * @param prio the priority to test, takes android_LogPriority values. + * @param tag the tag to test. + * @param len the length of the tag. + * @param default_prio the default priority to use if no properties or minimum priority are set. + * @return an integer where 1 indicates that the message is loggable and 0 indicates that it is not. + * + * Available since API level 30. + */ +int __android_log_is_loggable_len(int prio, const char* tag, size_t len, int default_prio) + __INTRODUCED_IN(30); + +/** + * Sets the minimum priority that will be logged for this process. + * + * @param priority the new minimum priority to set, takes android_LogPriority values. + * @return the previous set minimum priority as android_LogPriority values, or + * ANDROID_LOG_DEFAULT if none was set. + * + * Available since API level 30. + */ +int32_t __android_log_set_minimum_priority(int32_t priority) __INTRODUCED_IN(30); + +/** + * Gets the minimum priority that will be logged for this process. If none has been set by a + * previous __android_log_set_minimum_priority() call, this returns ANDROID_LOG_DEFAULT. + * + * @return the current minimum priority as android_LogPriority values, or + * ANDROID_LOG_DEFAULT if none is set. + * + * Available since API level 30. + */ +int32_t __android_log_get_minimum_priority(void) __INTRODUCED_IN(30); + +/** + * Sets the default tag if no tag is provided when writing a log message. Defaults to + * getprogname(). This truncates tag to the maximum log message size, though appropriate tags + * should be much smaller. + * + * @param tag the new log tag. + * + * Available since API level 30. + */ +void __android_log_set_default_tag(const char* tag) __INTRODUCED_IN(30); + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/base/cvd/build_version.h.in b/base/cvd/build_version.h.in new file mode 100644 index 0000000000..b561894654 --- /dev/null +++ b/base/cvd/build_version.h.in @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef BUILD_VERSION_H +#define BUILD_VERSION_H + +#include + +namespace android { +namespace build { + +constexpr char* GetBuildNumber() { + return "@VCS_TAG@"; +} + +} // namespace build +} // namespace android + +#endif // BUILD_VERSION_H diff --git a/base/cvd/clientanalytics.proto b/base/cvd/clientanalytics.proto new file mode 100644 index 0000000000..13e2d071d8 --- /dev/null +++ b/base/cvd/clientanalytics.proto @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 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. + */ +syntax = "proto2"; +package cuttlefish; + +message LogRequest { + optional ClientInfo client_info = 1; + optional int32 log_source = 2; + optional int64 request_time_ms = 4; + repeated LogEvent log_event = 3; + optional string log_source_name = 6; +} + +message ClientInfo { + optional int32 client_type = 1; +} + +// TODO(moelsherif) : This message is not used yet. It will be used to send back the next request wait time. +message LogResponse { + optional int64 next_request_wait_millis = 1; +} + +message LogEvent { + optional int64 event_time_ms = 1; + optional bytes source_extension = 6; +} diff --git a/allocd-port/srcs/fs/epoll.cpp b/base/cvd/cuttlefish/common/libs/fs/epoll.cpp similarity index 96% rename from allocd-port/srcs/fs/epoll.cpp rename to base/cvd/cuttlefish/common/libs/fs/epoll.cpp index e3799162bc..e78f90e1f2 100644 --- a/allocd-port/srcs/fs/epoll.cpp +++ b/base/cvd/cuttlefish/common/libs/fs/epoll.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "cuttlefish/fs/epoll.h" +#include "common/libs/fs/epoll.h" #include @@ -23,9 +23,10 @@ #include #include #include +#include -#include "cuttlefish/fs/shared_fd.h" -#include "cuttlefish/utils/result.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" namespace cuttlefish { @@ -149,7 +150,7 @@ Result> Epoll::Wait() { { std::shared_lock lock(epoll_mutex_); CF_EXPECT(epoll_fd_->IsOpen(), "Empty Epoll instance"); - success = epoll_wait(epoll_fd_->fd_, &event, 1, -1); + success = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd_->fd_, &event, 1, -1)); } if (success == -1) { return CF_ERRNO("epoll_wait failed"); diff --git a/allocd-port/include/cuttlefish/fs/epoll.h b/base/cvd/cuttlefish/common/libs/fs/epoll.h similarity index 94% rename from allocd-port/include/cuttlefish/fs/epoll.h rename to base/cvd/cuttlefish/common/libs/fs/epoll.h index 7fa67bf46a..21bc61874f 100644 --- a/allocd-port/include/cuttlefish/fs/epoll.h +++ b/base/cvd/cuttlefish/common/libs/fs/epoll.h @@ -23,8 +23,8 @@ #include #include -#include "cuttlefish/fs/shared_fd.h" -#include "cuttlefish/utils/result.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" namespace cuttlefish { @@ -36,7 +36,7 @@ struct EpollEvent { class Epoll { public: static Result Create(); - Epoll(); // Invalid instance + Epoll(); // Invalid instance Epoll(Epoll&&); Epoll& operator=(Epoll&&); diff --git a/allocd-port/srcs/fs/shared_buf.cpp b/base/cvd/cuttlefish/common/libs/fs/shared_buf.cc similarity index 95% rename from allocd-port/srcs/fs/shared_buf.cpp rename to base/cvd/cuttlefish/common/libs/fs/shared_buf.cc index 3e4619e8e9..f4b24dd8d0 100644 --- a/allocd-port/srcs/fs/shared_buf.cpp +++ b/base/cvd/cuttlefish/common/libs/fs/shared_buf.cc @@ -19,8 +19,8 @@ #include #include -#include "cuttlefish/fs/shared_buf.h" -#include "cuttlefish/fs/shared_fd.h" +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" namespace cuttlefish { @@ -28,7 +28,7 @@ namespace { const size_t BUFF_SIZE = 1 << 14; -} // namespace +} // namespace ssize_t WriteAll(SharedFD fd, const char* buf, size_t size) { size_t total_written = 0; @@ -69,8 +69,7 @@ ssize_t ReadAll(SharedFD fd, std::string* buf) { std::stringstream ss; ssize_t read; while ((read = fd->Read(buff, BUFF_SIZE - 1)) > 0) { - // this is necessary to avoid problems with having a '\0' in the middle of - // the buffer + // this is necessary to avoid problems with having a '\0' in the middle of the buffer ss << std::string(buff, read); } if (read < 0) { @@ -129,4 +128,4 @@ std::string RecvAll(SharedFD sock, const size_t count) { return {data.get(), count}; } -} // namespace cuttlefish +} // namespace cuttlefish diff --git a/allocd-port/include/cuttlefish/fs/shared_buf.h b/base/cvd/cuttlefish/common/libs/fs/shared_buf.h similarity index 95% rename from allocd-port/include/cuttlefish/fs/shared_buf.h rename to base/cvd/cuttlefish/common/libs/fs/shared_buf.h index 898097855d..f1a02c22fe 100644 --- a/allocd-port/include/cuttlefish/fs/shared_buf.h +++ b/base/cvd/cuttlefish/common/libs/fs/shared_buf.h @@ -19,7 +19,7 @@ #include #include -#include "cuttlefish/fs/shared_fd.h" +#include "common/libs/fs/shared_fd.h" namespace cuttlefish { @@ -87,9 +87,9 @@ ssize_t ReadExact(SharedFD fd, char* buf, size_t size); * If a read error is encountered, returns -1. buf will contain any data read * up until that point and errno will be set. */ -template +template ssize_t ReadExactBinary(SharedFD fd, T* binary_data) { - return ReadExact(fd, (char*)binary_data, sizeof(*binary_data)); + return ReadExact(fd, (char*) binary_data, sizeof(*binary_data)); } /** @@ -154,9 +154,9 @@ ssize_t WriteAll(SharedFD fd, const char* buf, size_t size); * -1 is returned. If not detected, 0 is returned with errno unchanged. * */ -template +template ssize_t WriteAllBinary(SharedFD fd, const T* binary_data) { - return WriteAll(fd, (const char*)binary_data, sizeof(*binary_data)); + return WriteAll(fd, (const char*) binary_data, sizeof(*binary_data)); } /** @@ -178,4 +178,4 @@ bool SendAll(SharedFD sock, const std::string& msg); */ std::string RecvAll(SharedFD sock, const size_t count); -} // namespace cuttlefish +} // namespace cuttlefish diff --git a/allocd-port/srcs/fs/shared_fd.cpp b/base/cvd/cuttlefish/common/libs/fs/shared_fd.cpp similarity index 74% rename from allocd-port/srcs/fs/shared_fd.cpp rename to base/cvd/cuttlefish/common/libs/fs/shared_fd.cpp index 044bf921a5..0ce12519a1 100644 --- a/allocd-port/srcs/fs/shared_fd.cpp +++ b/base/cvd/cuttlefish/common/libs/fs/shared_fd.cpp @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "cuttlefish/fs/shared_fd.h" +#include "common/libs/fs/shared_fd.h" #include #include @@ -30,13 +30,15 @@ #include #include +#include #include -#include +#include +#include -#include "cuttlefish/fs/shared_buf.h" -#include "cuttlefish/fs/shared_select.h" -#include "cuttlefish/utils/result.h" +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_select.h" +#include "common/libs/utils/result.h" // #define ENABLE_GCE_SHARED_FD_LOGGING 1 @@ -70,25 +72,30 @@ void CheckMarked(fd_set* in_out_mask, SharedFDSet* in_out_set) { * so we consider it safe to use the low-level arbitrary syscall wrapper. */ #ifndef __NR_memfd_create -#if defined(__x86_64__) -#define __NR_memfd_create 319 -#elif defined(__i386__) -#define __NR_memfd_create 356 -#elif defined(__aarch64__) -#define __NR_memfd_create 279 -#else +# if defined(__x86_64__) +# define __NR_memfd_create 319 +# elif defined(__i386__) +# define __NR_memfd_create 356 +# elif defined(__aarch64__) +# define __NR_memfd_create 279 +# else /* No interest in other architectures. */ -#error "Unknown architecture." -#endif +# error "Unknown architecture." +# endif #endif int memfd_create_wrapper(const char* name, unsigned int flags) { +#ifdef __linux__ #ifdef CUTTLEFISH_HOST // TODO(schuffelen): Use memfd_create with a newer host libc. return syscall(__NR_memfd_create, name, flags); #else return memfd_create(name, flags); #endif +#else + (void)flags; + return shm_open(name, O_RDWR); +#endif } bool IsRegularFile(const int fd) { @@ -103,9 +110,40 @@ constexpr size_t kPreferredBufferSize = 8192; } // namespace -bool FileInstance::CopyFrom(FileInstance& in, size_t length) { +bool FileInstance::CopyFrom(FileInstance& in, size_t length, FileInstance* stop) { std::vector buffer(kPreferredBufferSize); while (length > 0) { + int nfds = stop == nullptr ? 2 : 3; + // Wait until either in becomes readable or our fd closes. + constexpr ssize_t IN = 0; + constexpr ssize_t OUT = 1; + constexpr ssize_t STOP = 2; + struct pollfd pollfds[3]; + pollfds[IN].fd = in.fd_; + pollfds[IN].events = POLLIN; + pollfds[IN].revents = 0; + pollfds[OUT].fd = fd_; + pollfds[OUT].events = 0; + pollfds[OUT].revents = 0; + if (stop) { + pollfds[STOP].fd = stop->fd_; + pollfds[STOP].events = POLLIN; + pollfds[STOP].revents = 0; + } + int res = poll(pollfds, nfds, -1 /* indefinitely */); + if (res < 0) { + errno_ = errno; + return false; + } + if (stop && pollfds[STOP].revents & POLLIN) { + return false; + } + if (pollfds[OUT].revents != 0) { + // destination was either closed, invalid or errored, either way there is no + // point in continuing. + return false; + } + ssize_t num_read = in.Read(buffer.data(), std::min(buffer.size(), length)); if (num_read <= 0) { return false; @@ -114,23 +152,25 @@ bool FileInstance::CopyFrom(FileInstance& in, size_t length) { ssize_t written = 0; do { + // No need to use poll for writes: even if the source closes, the data + // needs to be delivered to the other side. auto res = Write(buffer.data(), num_read); if (res <= 0) { // The caller will have to log an appropriate message. return false; } written += res; - } while (written < num_read); + } while(written < num_read); } return true; } -bool FileInstance::CopyAllFrom(FileInstance& in) { +bool FileInstance::CopyAllFrom(FileInstance& in, FileInstance* stop) { // FileInstance may have been constructed with a non-zero errno_ value because // the errno variable is not zeroed out before. errno_ = 0; in.errno_ = 0; - while (CopyFrom(in, kPreferredBufferSize)) { + while (CopyFrom(in, kPreferredBufferSize, stop)) { } // Only return false if there was an actual error. return !GetErrno() && !in.GetErrno(); @@ -143,8 +183,7 @@ void FileInstance::Close() { } else if (close(fd_) == -1) { errno_ = errno; if (identity_.size()) { - message << __FUNCTION__ << ": " << identity_ << " failed (" << StrError() - << ")"; + message << __FUNCTION__ << ": " << identity_ << " failed (" << StrError() << ")"; std::string message_str = message.str(); Log(message_str.c_str()); } @@ -193,7 +232,7 @@ int FileInstance::ConnectWithTimeout(const struct sockaddr* addr, } if (GetErrno() != EAGAIN && GetErrno() != EINPROGRESS) { - DLOG(INFO) << "Immediate connection failure: " << StrError(); + LOG(DEBUG) << "Immediate connection failure: " << StrError(); if (Fcntl(F_SETFL, original_flags) == -1) { LOG(ERROR) << "Failed to restore original flags: " << StrError(); } @@ -240,7 +279,9 @@ bool FileInstance::IsSet(fd_set* in) const { } #if ENABLE_GCE_SHARED_FD_LOGGING -void FileInstance::Log(const char* message) { LOG(INFO) << message; } +void FileInstance::Log(const char* message) { + LOG(INFO) << message; +} #else void FileInstance::Log(const char*) {} #endif @@ -283,6 +324,17 @@ int Select(SharedFDSet* read_set, SharedFDSet* write_set, return rval; } +SharedFD::SharedFD(SharedFD&& other) { + value_ = std::move(other.value_); + other.value_.reset(new FileInstance(-1, EBADF)); +} + +SharedFD& SharedFD::operator=(SharedFD&& other) { + value_ = std::move(other.value_); + other.value_.reset(new FileInstance(-1, EBADF)); + return *this; +} + int SharedFD::Poll(std::vector& fds, int timeout) { return Poll(fds.data(), fds.size(), timeout); } @@ -340,8 +392,7 @@ SharedFD SharedFD::Accept(const FileInstance& listener) { SharedFD SharedFD::Dup(int unmanaged_fd) { int fd = fcntl(unmanaged_fd, F_DUPFD_CLOEXEC, 3); int error_num = errno; - return SharedFD( - std::shared_ptr(new FileInstance(fd, error_num))); + return SharedFD(std::shared_ptr(new FileInstance(fd, error_num))); } bool SharedFD::Pipe(SharedFD* fd0, SharedFD* fd1) { @@ -355,10 +406,12 @@ bool SharedFD::Pipe(SharedFD* fd0, SharedFD* fd1) { return false; } +#ifdef __linux__ SharedFD SharedFD::Event(int initval, int flags) { int fd = eventfd(initval, flags); return std::shared_ptr(new FileInstance(fd, errno)); } +#endif SharedFD SharedFD::MemfdCreate(const std::string& name, unsigned int flags) { int fd = memfd_create_wrapper(name.c_str(), flags); @@ -366,9 +419,7 @@ SharedFD SharedFD::MemfdCreate(const std::string& name, unsigned int flags) { return std::shared_ptr(new FileInstance(fd, error_num)); } -SharedFD SharedFD::MemfdCreateWithData(const std::string& name, - const std::string& data, - unsigned int flags) { +SharedFD SharedFD::MemfdCreateWithData(const std::string& name, const std::string& data, unsigned int flags) { auto memfd = MemfdCreate(name, flags); if (WriteAll(memfd, data) != data.size()) { return ErrorFD(errno); @@ -382,8 +433,8 @@ SharedFD SharedFD::MemfdCreateWithData(const std::string& name, return memfd; } -bool SharedFD::SocketPair(int domain, int type, int protocol, SharedFD* fd0, - SharedFD* fd1) { +bool SharedFD::SocketPair(int domain, int type, int protocol, + SharedFD* fd0, SharedFD* fd1) { int fds[2]; int rval = socketpair(domain, type, protocol, fds); if (rval != -1) { @@ -394,8 +445,21 @@ bool SharedFD::SocketPair(int domain, int type, int protocol, SharedFD* fd0, return false; } +Result> SharedFD::SocketPair(int domain, int type, + int protocol) { + SharedFD a, b; + if (!SharedFD::SocketPair(domain, type, protocol, &a, &b)) { + return CF_ERR("socketpair failed: " << strerror(errno)); + } + return std::make_pair(std::move(a), std::move(b)); +} + SharedFD SharedFD::Open(const std::string& path, int flags, mode_t mode) { - int fd = TEMP_FAILURE_RETRY(open(path.c_str(), flags, mode)); + return Open(path.c_str(), flags, mode); +} + +SharedFD SharedFD::Open(const char* path, int flags, mode_t mode) { + int fd = TEMP_FAILURE_RETRY(open(path, flags, mode)); if (fd == -1) { return SharedFD(std::shared_ptr(new FileInstance(fd, errno))); } else { @@ -404,7 +468,7 @@ SharedFD SharedFD::Open(const std::string& path, int flags, mode_t mode) { } SharedFD SharedFD::Creat(const std::string& path, mode_t mode) { - return SharedFD::Open(path, O_CREAT | O_WRONLY | O_TRUNC, mode); + return SharedFD::Open(path, O_CREAT|O_WRONLY|O_TRUNC, mode); } int SharedFD::Fchdir(SharedFD shared_fd) { @@ -417,19 +481,19 @@ int SharedFD::Fchdir(SharedFD shared_fd) { return rval; } -SharedFD SharedFD::Fifo(const std::string& path, mode_t mode) { - struct stat st; +Result SharedFD::Fifo(const std::string& path, mode_t mode) { + struct stat st {}; if (TEMP_FAILURE_RETRY(stat(path.c_str(), &st)) == 0) { - if (TEMP_FAILURE_RETRY(remove(path.c_str())) != 0) { - return ErrorFD(errno); - } + CF_EXPECTF(TEMP_FAILURE_RETRY(remove(path.c_str())) == 0, + "Failed to delete old file at '{}': '{}'", path, + strerror(errno)); } - int fd = TEMP_FAILURE_RETRY(mkfifo(path.c_str(), mode)); - if (fd == -1) { - return ErrorFD(errno); - } - return Open(path, mode); + CF_EXPECTF(TEMP_FAILURE_RETRY(mkfifo(path.c_str(), mode)) == 0, + "Failed to mkfifo('{}', {:o})", path, mode); + auto ret = Open(path, O_RDWR); + CF_EXPECTF(ret->IsOpen(), "Failed to open '{}': '{}'", path, ret->StrError()); + return ret; } SharedFD SharedFD::Socket(int domain, int socket_type, int protocol) { @@ -485,14 +549,14 @@ SharedFD SharedFD::SocketLocalClient(int port, int type) { if (!rval->IsOpen()) { return rval; } - if (rval->Connect(reinterpret_cast(&addr), sizeof addr) < - 0) { + if (rval->Connect(reinterpret_cast(&addr), sizeof addr) < 0) { return SharedFD::ErrorFD(rval->GetErrno()); } return rval; } -SharedFD SharedFD::SocketClient(const std::string& host, int port, int type) { +SharedFD SharedFD::SocketClient(const std::string& host, int port, int type, + std::chrono::seconds timeout) { sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_port = htons(port); @@ -501,16 +565,16 @@ SharedFD SharedFD::SocketClient(const std::string& host, int port, int type) { if (!rval->IsOpen()) { return rval; } - if (rval->Connect(reinterpret_cast(&addr), sizeof addr) < - 0) { + struct timeval timeout_timeval = {static_cast(timeout.count()), 0}; + if (rval->ConnectWithTimeout(reinterpret_cast(&addr), + sizeof addr, &timeout_timeval) < 0) { return SharedFD::ErrorFD(rval->GetErrno()); } return rval; } -SharedFD SharedFD::Socket6Client(const std::string& host, - const std::string& interface, int port, - int type) { +SharedFD SharedFD::Socket6Client(const std::string& host, const std::string& interface, + int port, int type, std::chrono::seconds timeout) { sockaddr_in6 addr{}; addr.sin6_family = AF_INET6; addr.sin6_port = htons(port); @@ -521,17 +585,26 @@ SharedFD SharedFD::Socket6Client(const std::string& host, } if (!interface.empty()) { +#ifdef __linux__ ifreq ifr{}; snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface.c_str()); - if (rval->SetSockOpt(SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == - -1) { + if (rval->SetSockOpt(SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1) { + return SharedFD::ErrorFD(rval->GetErrno()); + } +#elif defined(__APPLE__) + int idx = if_nametoindex(interface.c_str()); + if (rval->SetSockOpt(IPPROTO_IP, IP_BOUND_IF, &idx, sizeof(idx)) == -1) { return SharedFD::ErrorFD(rval->GetErrno()); } +#else +#error "Unsupported operating system" +#endif } - if (rval->Connect(reinterpret_cast(&addr), sizeof addr) < - 0) { + struct timeval timeout_timeval = {static_cast(timeout.count()), 0}; + if (rval->ConnectWithTimeout(reinterpret_cast(&addr), + sizeof addr, &timeout_timeval) < 0) { return SharedFD::ErrorFD(rval->GetErrno()); } return rval; @@ -544,7 +617,7 @@ SharedFD SharedFD::SocketLocalServer(int port, int type) { addr.sin_port = htons(port); addr.sin_addr.s_addr = htonl(INADDR_ANY); SharedFD rval = SharedFD::Socket(AF_INET, type, 0); - if (!rval->IsOpen()) { + if(!rval->IsOpen()) { return rval; } int n = 1; @@ -552,7 +625,7 @@ SharedFD SharedFD::SocketLocalServer(int port, int type) { LOG(ERROR) << "SetSockOpt failed " << rval->StrError(); return SharedFD::ErrorFD(rval->GetErrno()); } - if (rval->Bind(reinterpret_cast(&addr), sizeof(addr)) < 0) { + if(rval->Bind(reinterpret_cast(&addr), sizeof(addr)) < 0) { LOG(ERROR) << "Bind failed " << rval->StrError(); return SharedFD::ErrorFD(rval->GetErrno()); } @@ -611,7 +684,24 @@ SharedFD SharedFD::SocketLocalServer(const std::string& name, bool abstract, return rval; } -SharedFD SharedFD::VsockServer(unsigned int port, int type, unsigned int cid) { +#ifdef __linux__ +SharedFD SharedFD::VsockServer( + unsigned int port, int type, + std::optional vhost_user_vsock_listening_cid, unsigned int cid) { +#ifndef CUTTLEFISH_HOST + CHECK(!vhost_user_vsock_listening_cid) + << "vhost_user_vsock_listening_cid is supposed to be nullopt in the " + "guest"; +#endif + if (vhost_user_vsock_listening_cid) { + // TODO(b/277909042): better path than /tmp/vsock_{}/vm.vsock_{} + return SharedFD::SocketLocalServer( + fmt::format("/tmp/vsock_{}_{}/vm.vsock_{}", + *vhost_user_vsock_listening_cid, std::to_string(getuid()), + port), + false /* abstract */, type, 0666 /* mode */); + } + auto vsock = SharedFD::Socket(AF_VSOCK, type, 0); if (!vsock->IsOpen()) { return vsock; @@ -636,11 +726,39 @@ SharedFD SharedFD::VsockServer(unsigned int port, int type, unsigned int cid) { return vsock; } -SharedFD SharedFD::VsockServer(int type) { - return VsockServer(VMADDR_PORT_ANY, type); +SharedFD SharedFD::VsockServer( + int type, std::optional vhost_user_vsock_listening_cid) { + return VsockServer(VMADDR_PORT_ANY, type, vhost_user_vsock_listening_cid); } -SharedFD SharedFD::VsockClient(unsigned int cid, unsigned int port, int type) { +SharedFD SharedFD::VsockClient(unsigned int cid, unsigned int port, int type, + bool vhost_user) { +#ifndef CUTTLEFISH_HOST + CHECK(!vhost_user) << "vhost_user is supposed to be false in the guest"; +#endif + if (vhost_user) { + // TODO(b/277909042): better path than /tmp/vsock_{}/vm.vsock + auto client = SharedFD::SocketLocalClient( + fmt::format("/tmp/vsock_{}_{}/vm.vsock", cid, std::to_string(getuid())), + false /* abstract */, type); + const std::string msg = fmt::format("connect {}\n", port); + SendAll(client, msg); + + const std::string expected_res = fmt::format("OK {}\n", port); + std::string actual_res(expected_res.length(), ' '); + if (ReadExact(client, &actual_res) != expected_res.length()) { + client->Close(); + LOG(ERROR) << "cannot connect to " << cid << ":" << port; + return client; + } + if (actual_res != expected_res) { + client->Close(); + LOG(ERROR) << "response from server: " << actual_res << ", but expect " + << expected_res; + return client; + } + return client; + } auto vsock = SharedFD::Socket(AF_VSOCK, type, 0); if (!vsock->IsOpen()) { return vsock; @@ -655,6 +773,7 @@ SharedFD SharedFD::VsockClient(unsigned int cid, unsigned int port, int type) { } return vsock; } +#endif SharedFD WeakFD::lock() const { auto locked_file_instance = value_.lock(); @@ -719,6 +838,13 @@ int FileInstance::Fcntl(int command, int value) { return rval; } +int FileInstance::Fsync() { + errno = 0; + int rval = TEMP_FAILURE_RETRY(fsync(fd_)); + errno_ = errno; + return rval; +} + Result FileInstance::Flock(int operation) { errno = 0; int rval = TEMP_FAILURE_RETRY(flock(fd_, operation)); @@ -736,12 +862,14 @@ int FileInstance::GetSockName(struct sockaddr* addr, socklen_t* addrlen) { return rval; } +#ifdef __linux__ unsigned int FileInstance::VsockServerPort() { struct sockaddr_vm vm_socket; socklen_t length = sizeof(vm_socket); GetSockName(reinterpret_cast(&vm_socket), &length); return vm_socket.svm_port; } +#endif int FileInstance::Ioctl(int request, void* val) { errno = 0; @@ -795,12 +923,14 @@ ssize_t FileInstance::Read(void* buf, size_t count) { return rval; } +#ifdef __linux__ int FileInstance::EventfdRead(eventfd_t* value) { errno = 0; auto rval = eventfd_read(fd_, value); errno_ = errno; return rval; } +#endif ssize_t FileInstance::Send(const void* buf, size_t len, int flags) { errno = 0; @@ -850,6 +980,22 @@ int FileInstance::SetTerminalRaw() { cfmakeraw(&terminal_settings); rval = tcsetattr(fd_, TCSANOW, &terminal_settings); errno_ = errno; + if (rval < 0) { + return rval; + } + + // tcsetattr() success if any of the requested change success. + // So double check whether everything is applied. + termios raw_settings; + rval = tcgetattr(fd_, &raw_settings); + errno_ = errno; + if (rval < 0) { + return rval; + } + if (memcmp(&terminal_settings, &raw_settings, sizeof(terminal_settings))) { + errno_ = EPROTO; + return -1; + } return rval; } @@ -883,12 +1029,14 @@ ssize_t FileInstance::Write(const void* buf, size_t count) { return rval; } +#ifdef __linux__ int FileInstance::EventfdWrite(eventfd_t value) { errno = 0; int rval = eventfd_write(fd_, value); errno_ = errno; return rval; } +#endif bool FileInstance::IsATTY() { errno = 0; @@ -897,6 +1045,26 @@ bool FileInstance::IsATTY() { return rval; } +int FileInstance::Futimens(const struct timespec times[2]) { + errno = 0; + int rval = TEMP_FAILURE_RETRY(futimens(fd_, times)); + errno_ = errno; + return rval; +} + +#ifdef __linux__ +Result FileInstance::ProcFdLinkTarget() const { + std::stringstream output_composer; + output_composer << "/proc/" << getpid() << "/fd/" << fd_; + const std::string mem_fd_link = output_composer.str(); + std::string mem_fd_target; + CF_EXPECT( + android::base::Readlink(mem_fd_link, &mem_fd_target), + "Getting link for the memory file \"" << mem_fd_link << "\" failed"); + return mem_fd_target; +} +#endif + FileInstance::FileInstance(int fd, int in_errno) : fd_(fd), errno_(in_errno), is_regular_file_(IsRegularFile(fd_)) { // Ensure every file descriptor managed by a FileInstance has the CLOEXEC diff --git a/allocd-port/include/cuttlefish/fs/shared_fd.h b/base/cvd/cuttlefish/common/libs/fs/shared_fd.h similarity index 80% rename from allocd-port/include/cuttlefish/fs/shared_fd.h rename to base/cvd/cuttlefish/common/libs/fs/shared_fd.h index 297f242c60..be10b7a507 100644 --- a/allocd-port/include/cuttlefish/fs/shared_fd.h +++ b/base/cvd/cuttlefish/common/libs/fs/shared_fd.h @@ -19,21 +19,25 @@ #ifndef CUTTLEFISH_COMMON_COMMON_LIBS_FS_SHARED_FD_H_ #define CUTTLEFISH_COMMON_COMMON_LIBS_FS_SHARED_FD_H_ +#ifdef __linux__ #include #include +#endif #include #include #include #include #include #include -#include #include #include #include +#include #include #include +#include +#include #include #include @@ -42,8 +46,13 @@ #include #include -#include "cuttlefish/fs/vm_sockets.h" -#include "cuttlefish/utils/result.h" +#include + +#ifdef __linux__ +#include +#endif + +#include "common/libs/utils/result.h" /** * Classes to to enable safe access to files. @@ -69,6 +78,8 @@ namespace cuttlefish { struct PollSharedFd; class Epoll; class FileInstance; +struct VhostUserVsockCid; +struct VsockCid; /** * Counted reference to a FileInstance. @@ -116,10 +127,13 @@ class FileInstance; class SharedFD { // Give WeakFD access to the underlying shared_ptr. friend class WeakFD; - public: inline SharedFD(); SharedFD(const std::shared_ptr& in) : value_(in) {} + SharedFD(SharedFD const&) = default; + SharedFD(SharedFD&& other); + SharedFD& operator=(SharedFD const&) = default; + SharedFD& operator=(SharedFD&& other); // Reference the listener as a FileInstance to make this FD type agnostic. static SharedFD Accept(const FileInstance& listener, struct sockaddr* addr, socklen_t* addrlen); @@ -127,38 +141,61 @@ class SharedFD { static SharedFD Dup(int unmanaged_fd); // All SharedFDs have the O_CLOEXEC flag after creation. To remove use the // Fcntl or Dup functions. + static SharedFD Open(const char* pathname, int flags, mode_t mode = 0); static SharedFD Open(const std::string& pathname, int flags, mode_t mode = 0); static SharedFD Creat(const std::string& pathname, mode_t mode); static int Fchdir(SharedFD); - static SharedFD Fifo(const std::string& pathname, mode_t mode); + static Result Fifo(const std::string& pathname, mode_t mode); static bool Pipe(SharedFD* fd0, SharedFD* fd1); +#ifdef __linux__ static SharedFD Event(int initval = 0, int flags = 0); +#endif static SharedFD MemfdCreate(const std::string& name, unsigned int flags = 0); - static SharedFD MemfdCreateWithData(const std::string& name, - const std::string& data, - unsigned int flags = 0); + static SharedFD MemfdCreateWithData(const std::string& name, const std::string& data, unsigned int flags = 0); static SharedFD Mkstemp(std::string* path); static int Poll(PollSharedFd* fds, size_t num_fds, int timeout); static int Poll(std::vector& fds, int timeout); static bool SocketPair(int domain, int type, int protocol, SharedFD* fd0, SharedFD* fd1); + static Result> SocketPair(int domain, int type, + int protocol); static SharedFD Socket(int domain, int socket_type, int protocol); static SharedFD SocketLocalClient(const std::string& name, bool is_abstract, int in_type); static SharedFD SocketLocalClient(const std::string& name, bool is_abstract, int in_type, int timeout_seconds); static SharedFD SocketLocalClient(int port, int type); - static SharedFD SocketClient(const std::string& host, int port, int type); - static SharedFD Socket6Client(const std::string& host, - const std::string& interface, int port, - int type); + static SharedFD SocketClient(const std::string& host, int port, + int type, std::chrono::seconds timeout = std::chrono::seconds(0)); + static SharedFD Socket6Client(const std::string& host, const std::string& interface, int port, + int type, std::chrono::seconds timeout = std::chrono::seconds(0)); static SharedFD SocketLocalServer(const std::string& name, bool is_abstract, int in_type, mode_t mode); static SharedFD SocketLocalServer(int port, int type); + +#ifdef __linux__ + // For binding in vsock, svm_cid from `cid` param would be either + // VMADDR_CID_ANY, VMADDR_CID_LOCAL, VMADDR_CID_HOST or their own CID, and it + // is used for indicating connections which it accepts from. + // * VMADDR_CID_ANY: accept from any + // * VMADDR_CID_LOCAL: accept from local + // * VMADDR_CID_HOST: accept from child vm + // * their own CID: accept from parent vm + // With vhost-user-vsock, it is basically similar to VMADDR_CID_HOST, but for + // now it has limitations that it should bind to a specific socket file which + // is for a certain cid. So for vhost-user-vsock, we need to specify the + // expected client's cid. That's why vhost_user_vsock_listening_cid is + // necessary. + // TODO: combining them when vhost-user-vsock impl supports a kind of + // VMADDR_CID_HOST static SharedFD VsockServer(unsigned int port, int type, + std::optional vhost_user_vsock_listening_cid, unsigned int cid = VMADDR_CID_ANY); - static SharedFD VsockServer(int type); - static SharedFD VsockClient(unsigned int cid, unsigned int port, int type); + static SharedFD VsockServer( + int type, std::optional vhost_user_vsock_listening_cid); + static SharedFD VsockClient(unsigned int cid, unsigned int port, int type, + bool vhost_user); +#endif bool operator==(const SharedFD& rhs) const { return value_ == rhs.value_; } @@ -268,21 +305,24 @@ class FileInstance { // Otherwise an error will be set either on this file or the input. // The non-const reference is needed to avoid binding this to a particular // reference type. - bool CopyFrom(FileInstance& in, size_t length); + bool CopyFrom(FileInstance& in, size_t length, FileInstance* stop = nullptr); // Same as CopyFrom, but reads from input until EOF is reached. - bool CopyAllFrom(FileInstance& in); + bool CopyAllFrom(FileInstance& in, FileInstance* stop = nullptr); int UNMANAGED_Dup(); int UNMANAGED_Dup2(int newfd); int Fchdir(); int Fcntl(int command, int value); + int Fsync(); Result Flock(int operation); int GetErrno() const { return errno_; } int GetSockName(struct sockaddr* addr, socklen_t* addrlen); +#ifdef __linux__ unsigned int VsockServerPort(); +#endif int Ioctl(int request, void* val = nullptr); bool IsOpen() const { return fd_ != -1; } @@ -309,10 +349,22 @@ class FileInstance { ssize_t Recv(void* buf, size_t len, int flags); ssize_t RecvMsg(struct msghdr* msg, int flags); ssize_t Read(void* buf, size_t count); +#ifdef __linux__ int EventfdRead(eventfd_t* value); +#endif ssize_t Send(const void* buf, size_t len, int flags); ssize_t SendMsg(const struct msghdr* msg, int flags); + template + ssize_t SendFileDescriptors(const void* buf, size_t len, Args&&... sent_fds) { + std::vector fds; + android::base::Append(fds, std::forward(sent_fds->fd_)...); + errno = 0; + auto ret = android::base::SendFileDescriptorVector(fd_, buf, len, fds); + errno_ = errno; + return ret; + } + int Shutdown(int how); void Set(fd_set* dest, int* max_index) const; int SetSockOpt(int level, int optname, const void* optval, socklen_t optlen); @@ -330,9 +382,17 @@ class FileInstance { * */ ssize_t Write(const void* buf, size_t count); +#ifdef __linux__ int EventfdWrite(eventfd_t value); +#endif bool IsATTY(); + int Futimens(const struct timespec times[2]); + + // Returns the target of "/proc/getpid()/fd/" + std::to_string(fd_) + // if appropriate + Result ProcFdLinkTarget() const; + private: FileInstance(int fd, int in_errno); FileInstance* Accept(struct sockaddr* addr, socklen_t* addrlen) const; @@ -352,7 +412,7 @@ struct PollSharedFd { /* Methods that need both a fully defined SharedFD and a fully defined FileInstance. */ -inline SharedFD::SharedFD() : value_(FileInstance::ClosedInstance()) {} +SharedFD::SharedFD() : value_(FileInstance::ClosedInstance()) {} } // namespace cuttlefish diff --git a/allocd-port/srcs/fs/shared_fd_stream.cpp b/base/cvd/cuttlefish/common/libs/fs/shared_fd_stream.cpp similarity index 75% rename from allocd-port/srcs/fs/shared_fd_stream.cpp rename to base/cvd/cuttlefish/common/libs/fs/shared_fd_stream.cpp index 5c83836552..6eef1f55e3 100644 --- a/allocd-port/srcs/fs/shared_fd_stream.cpp +++ b/base/cvd/cuttlefish/common/libs/fs/shared_fd_stream.cpp @@ -14,17 +14,17 @@ * limitations under the License. */ -#include "cuttlefish/fs/shared_fd_stream.h" +#include "common/libs/fs/shared_fd_stream.h" #include #include -#include "cuttlefish/fs/shared_buf.h" +#include "common/libs/fs/shared_buf.h" namespace cuttlefish { SharedFDStreambuf::SharedFDStreambuf(SharedFD shared_fd) - : shared_fd_(shared_fd) {} + : shared_fd_(shared_fd) {} int SharedFDStreambuf::underflow() { if (gptr() < egptr()) { @@ -38,13 +38,16 @@ int SharedFDStreambuf::underflow() { } else { unget_size = std::min(gptr() - eback(), kUngetSize); std::memcpy(read_buffer_.get(), - read_buffer_.get() + kBufferSize - unget_size, unget_size); + read_buffer_.get() + kBufferSize - unget_size, + unget_size); } - ssize_t bytes_read = - ReadExact(shared_fd_, read_buffer_.get() + unget_size, bytes_to_read); + ssize_t bytes_read = ReadExact(shared_fd_, + read_buffer_.get() + unget_size, + bytes_to_read); - setg(read_buffer_.get(), read_buffer_.get() + unget_size, + setg(read_buffer_.get(), + read_buffer_.get() + unget_size, read_buffer_.get() + unget_size + bytes_read); if (bytes_read <= 0 || in_avail() == 0) { @@ -73,10 +76,10 @@ std::streamsize SharedFDStreambuf::xsgetn(char* dst, std::streamsize count) { int SharedFDStreambuf::overflow(int c) { if (c != EOF) { - char z = c; - if (WriteAll(shared_fd_, &z, 1) != 1) { - return EOF; - } + char z = c; + if (WriteAll(shared_fd_, &z, 1) != 1) { + return EOF; + } } return c; } @@ -84,7 +87,7 @@ int SharedFDStreambuf::overflow(int c) { std::streamsize SharedFDStreambuf::xsputn(const char* src, std::streamsize count) { return static_cast( - WriteAll(shared_fd_, src, static_cast(count))); + WriteAll(shared_fd_, src, static_cast(count))); } int SharedFDStreambuf::pbackfail(int c) { @@ -99,13 +102,9 @@ int SharedFDStreambuf::pbackfail(int c) { } SharedFDOstream::SharedFDOstream(SharedFD shared_fd) - : std::ostream(nullptr), buf_(shared_fd) { - rdbuf(&buf_); -} + : std::ostream(nullptr), buf_(shared_fd) { rdbuf(&buf_); } SharedFDIstream::SharedFDIstream(SharedFD shared_fd) - : std::istream(nullptr), buf_(shared_fd) { - rdbuf(&buf_); -} + : std::istream(nullptr), buf_(shared_fd) { rdbuf(&buf_); } -} // namespace cuttlefish +} // namespace cuttlefish \ No newline at end of file diff --git a/allocd-port/include/cuttlefish/fs/shared_fd_stream.h b/base/cvd/cuttlefish/common/libs/fs/shared_fd_stream.h similarity index 97% rename from allocd-port/include/cuttlefish/fs/shared_fd_stream.h rename to base/cvd/cuttlefish/common/libs/fs/shared_fd_stream.h index 0ec2b5346c..d5381d03dd 100644 --- a/allocd-port/include/cuttlefish/fs/shared_fd_stream.h +++ b/base/cvd/cuttlefish/common/libs/fs/shared_fd_stream.h @@ -25,7 +25,7 @@ #include #include -#include "cuttlefish/fs/shared_fd.h" +#include "common/libs/fs/shared_fd.h" namespace cuttlefish { @@ -70,4 +70,4 @@ class SharedFDOstream : public std::ostream { } // namespace cuttlefish -#endif +#endif \ No newline at end of file diff --git a/base/cvd/cuttlefish/common/libs/fs/shared_fd_test.cpp b/base/cvd/cuttlefish/common/libs/fs/shared_fd_test.cpp new file mode 100644 index 0000000000..ff43654631 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/fs/shared_fd_test.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/fs/shared_select.h" + +#include +#include +#include + +#include + +namespace cuttlefish { + +char pipe_message[] = "Testing the pipe"; + +TEST(SendFD, Basic) { + SharedFD fds[2]; + SharedFD::Pipe(fds, fds + 1); + EXPECT_TRUE(fds[0]->IsOpen()); + EXPECT_TRUE(fds[1]->IsOpen()); + EXPECT_EQ(sizeof(pipe_message), fds[1]->Write(pipe_message, sizeof(pipe_message))); + char buf[80]; + EXPECT_EQ(sizeof(pipe_message), fds[0]->Read(buf, sizeof(buf))); + EXPECT_EQ(0, strcmp(buf, pipe_message)); +} + +} diff --git a/allocd-port/include/cuttlefish/fs/shared_select.h b/base/cvd/cuttlefish/common/libs/fs/shared_select.h similarity index 81% rename from allocd-port/include/cuttlefish/fs/shared_select.h rename to base/cvd/cuttlefish/common/libs/fs/shared_select.h index 605f857422..9e0e0abff2 100644 --- a/allocd-port/include/cuttlefish/fs/shared_select.h +++ b/base/cvd/cuttlefish/common/libs/fs/shared_select.h @@ -18,7 +18,7 @@ #include -#include "cuttlefish/fs/shared_fd.h" +#include "common/libs/fs/shared_fd.h" namespace cuttlefish { /** @@ -43,15 +43,25 @@ class SharedFDSet { const_iterator begin() const { return value_.begin(); } const_iterator end() const { return value_.end(); } - void swap(SharedFDSet* rhs) { value_.swap(rhs->value_); } + void swap(SharedFDSet* rhs) { + value_.swap(rhs->value_); + } - void Clr(const SharedFD& in) { value_.erase(in); } + void Clr(const SharedFD& in) { + value_.erase(in); + } - bool IsSet(const SharedFD& in) const { return value_.count(in) != 0; } + bool IsSet(const SharedFD& in) const { + return value_.count(in) != 0; + } - void Set(const SharedFD& in) { value_.insert(in); } + void Set(const SharedFD& in) { + value_.insert(in); + } - void Zero() { value_.clear(); } + void Zero() { + value_.clear(); + } private: std::set value_; @@ -60,8 +70,8 @@ class SharedFDSet { /** * SharedFD version of select. * - * read_set, write_set, and timeout are in/out parameters. This caller should - * keep a copy of the original values if it wants to preserve them. + * read_set, write_set, and timeout are in/out parameters. This caller should keep + * a copy of the original values if it wants to preserve them. */ int Select(SharedFDSet* read_set, SharedFDSet* write_set, SharedFDSet* error_set, struct timeval* timeout); diff --git a/allocd-port/include/cuttlefish/fs/vm_sockets.h b/base/cvd/cuttlefish/common/libs/fs/vm_sockets.h similarity index 78% rename from allocd-port/include/cuttlefish/fs/vm_sockets.h rename to base/cvd/cuttlefish/common/libs/fs/vm_sockets.h index f92091b1f5..151f6769d1 100644 --- a/allocd-port/include/cuttlefish/fs/vm_sockets.h +++ b/base/cvd/cuttlefish/common/libs/fs/vm_sockets.h @@ -21,23 +21,21 @@ #define SO_VM_SOCKETS_TRUSTED 5 #define SO_VM_SOCKETS_CONNECT_TIMEOUT 6 #define SO_VM_SOCKETS_NONBLOCK_TXRX 7 -#define VMADDR_CID_ANY -1U -#define VMADDR_PORT_ANY -1U +#define VMADDR_CID_ANY - 1U +#define VMADDR_PORT_ANY - 1U #define VMADDR_CID_HYPERVISOR 0 #define VMADDR_CID_RESERVED 1 #define VMADDR_CID_HOST 2 -#define VM_SOCKETS_INVALID_VERSION -1U -#define VM_SOCKETS_VERSION_EPOCH(_v) (((_v)&0xFF000000) >> 24) -#define VM_SOCKETS_VERSION_MAJOR(_v) (((_v)&0x00FF0000) >> 16) -#define VM_SOCKETS_VERSION_MINOR(_v) (((_v)&0x0000FFFF)) +#define VM_SOCKETS_INVALID_VERSION - 1U +#define VM_SOCKETS_VERSION_EPOCH(_v) (((_v) & 0xFF000000) >> 24) +#define VM_SOCKETS_VERSION_MAJOR(_v) (((_v) & 0x00FF0000) >> 16) +#define VM_SOCKETS_VERSION_MINOR(_v) (((_v) & 0x0000FFFF)) struct sockaddr_vm { __kernel_sa_family_t svm_family; unsigned short svm_reserved1; unsigned int svm_port; unsigned int svm_cid; - unsigned char svm_zero[sizeof(struct sockaddr) - sizeof(sa_family_t) - - sizeof(unsigned short) - sizeof(unsigned int) - - sizeof(unsigned int)]; + unsigned char svm_zero[sizeof(struct sockaddr) - sizeof(sa_family_t) - sizeof(unsigned short) - sizeof(unsigned int) - sizeof(unsigned int)]; }; #define IOCTL_VM_SOCKETS_GET_LOCAL_CID _IO(7, 0xb9) #ifndef AF_VSOCK diff --git a/base/cvd/cuttlefish/common/libs/utils/archive.cpp b/base/cvd/cuttlefish/common/libs/utils/archive.cpp new file mode 100644 index 0000000000..7c0df4acb8 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/archive.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2019 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. + */ + +#include "common/libs/utils/archive.h" + +#include + +#include +#include +#include + +#include +#include + +#include "common/libs/utils/subprocess.h" + +namespace cuttlefish { +namespace { + +Result> ExtractHelper( + std::vector& files, const std::string& archive_filepath, + const std::string& target_directory, const bool keep_archive) { + CF_EXPECT(!files.empty(), "No files extracted from " << archive_filepath); + + auto it = files.begin(); + while (it != files.end()) { + if (*it == "" || android::base::EndsWith(*it, "/")) { + it = files.erase(it); + } else { + *it = target_directory + "/" + *it; + it++; + } + } + + if (!keep_archive && unlink(archive_filepath.data()) != 0) { + LOG(ERROR) << "Could not delete " << archive_filepath; + files.push_back(archive_filepath); + } + + return {files}; +} + +} // namespace + +Archive::Archive(const std::string& file) : file_(file) {} + +Archive::~Archive() {} + +std::vector Archive::Contents() { + Command bsdtar_cmd("/usr/bin/bsdtar"); + bsdtar_cmd.AddParameter("-tf"); + bsdtar_cmd.AddParameter(file_); + std::string bsdtar_input, bsdtar_output; + auto bsdtar_ret = RunWithManagedStdio(std::move(bsdtar_cmd), &bsdtar_input, + &bsdtar_output, nullptr); + if (bsdtar_ret != 0) { + LOG(ERROR) << "`bsdtar -tf \"" << file_ << "\"` returned " << bsdtar_ret; + } + return bsdtar_ret == 0 + ? android::base::Split(bsdtar_output, "\n") + : std::vector(); +} + +bool Archive::ExtractAll(const std::string& target_directory) { + return ExtractFiles({}, target_directory); +} + +bool Archive::ExtractFiles(const std::vector& to_extract, + const std::string& target_directory) { + Command bsdtar_cmd("/usr/bin/bsdtar"); + bsdtar_cmd.AddParameter("-x"); + bsdtar_cmd.AddParameter("-v"); + bsdtar_cmd.AddParameter("-C"); + bsdtar_cmd.AddParameter(target_directory); + bsdtar_cmd.AddParameter("-f"); + bsdtar_cmd.AddParameter(file_); + bsdtar_cmd.AddParameter("-S"); + for (const auto& extract : to_extract) { + bsdtar_cmd.AddParameter(extract); + } + bsdtar_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, + Subprocess::StdIOChannel::kStdErr); + std::string bsdtar_output; + int bsdtar_ret = RunWithManagedStdio(std::move(bsdtar_cmd), nullptr, nullptr, + &bsdtar_output); + LOG(DEBUG) << bsdtar_output; + if (bsdtar_ret != 0) { + LOG(ERROR) << "bsdtar extraction on \"" << file_ << "\" returned " + << bsdtar_ret; + } + return bsdtar_ret == 0; +} + +std::string Archive::ExtractToMemory(const std::string& path) { + Command bsdtar_cmd("/usr/bin/bsdtar"); + bsdtar_cmd.AddParameter("-xf"); + bsdtar_cmd.AddParameter(file_); + bsdtar_cmd.AddParameter("-O"); + bsdtar_cmd.AddParameter(path); + std::string stdout_str; + auto ret = + RunWithManagedStdio(std::move(bsdtar_cmd), nullptr, &stdout_str, nullptr); + if (ret != 0) { + LOG(ERROR) << "Could not extract \"" << path << "\" from \"" << file_ + << "\" to memory."; + return ""; + } + return stdout_str; +} + +Result> ExtractImages( + const std::string& archive_filepath, const std::string& target_directory, + const std::vector& images, const bool keep_archive) { + Archive archive(archive_filepath); + CF_EXPECT(archive.ExtractFiles(images, target_directory), + "Could not extract images from \"" << archive_filepath << "\" to \"" + << target_directory << "\""); + + std::vector files = images; + return ExtractHelper(files, archive_filepath, target_directory, keep_archive); +} + +Result ExtractImage(const std::string& archive_filepath, + const std::string& target_directory, + const std::string& image, + const bool keep_archive) { + std::vector result = CF_EXPECT( + ExtractImages(archive_filepath, target_directory, {image}, keep_archive)); + return {result.front()}; +} + +Result> ExtractArchiveContents( + const std::string& archive_filepath, const std::string& target_directory, + const bool keep_archive) { + Archive archive(archive_filepath); + CF_EXPECT(archive.ExtractAll(target_directory), + "Could not extract \"" << archive_filepath << "\" to \"" + << target_directory << "\""); + + std::vector files = archive.Contents(); + return ExtractHelper(files, archive_filepath, target_directory, keep_archive); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/archive.h b/base/cvd/cuttlefish/common/libs/utils/archive.h new file mode 100644 index 0000000000..5d3fdc4af7 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/archive.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 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 + +#include +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +// Operations on archive files +class Archive { + std::string file_; + + public: + Archive(const std::string& file); + ~Archive(); + + std::vector Contents(); + bool ExtractAll(const std::string& target_directory = "."); + bool ExtractFiles(const std::vector& files, + const std::string& target_directory = "."); + std::string ExtractToMemory(const std::string& path); +}; + +Result> ExtractImages( + const std::string& archive_filepath, const std::string& target_directory, + const std::vector& images, const bool keep_archive); + +Result ExtractImage(const std::string& archive_filepath, + const std::string& target_directory, + const std::string& image, + const bool keep_archive = true); + +Result> ExtractArchiveContents( + const std::string& archive_filepath, const std::string& target_directory, + const bool keep_archive); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/base64.cpp b/base/cvd/cuttlefish/common/libs/utils/base64.cpp new file mode 100644 index 0000000000..2f1441dce9 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/base64.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "common/libs/utils/base64.h" + +#include +#include +#include +#include +#include + +#include + +namespace cuttlefish { + +namespace { + +// EVP_EncodedLength is boringssl specific so it can't be used outside of +// android. +std::optional EncodedLength(size_t len) { + if (len + 2 < len) { + return std::nullopt; + } + len += 2; + len /= 3; + + if (((len << 2) >> 2) != len) { + return std::nullopt; + } + len <<= 2; + + if (len + 1 < len) { + return std::nullopt; + } + len++; + + return {len}; +} + +// EVP_DecodedLength is boringssl specific so it can't be used outside of +// android. +std::optional DecodedLength(size_t len) { + if (len % 4 != 0) { + return std::nullopt; + } + + return {(len / 4) * 3}; +} + +} // namespace + +bool EncodeBase64(const void *data, std::size_t size, std::string *out) { + auto len_res = EncodedLength(size); + if (!len_res) { + return false; + } + out->resize(*len_res); + auto enc_res = + EVP_EncodeBlock(reinterpret_cast(out->data()), + reinterpret_cast(data), size); + if (enc_res < 0) { + return false; + } + out->resize(enc_res); // Don't count the terminating \0 character + return true; +} + +bool DecodeBase64(const std::string &data, std::vector *buffer) { + auto len_res = DecodedLength(data.size()); + if (!len_res) { + return false; + } + auto out_len = *len_res; + buffer->resize(out_len); + auto actual_len = EVP_DecodeBlock(buffer->data(), + reinterpret_cast(data.data()), + data.size()); + if (actual_len < 0) { + return false; + } + + // DecodeBlock leaves null characters at the end of the buffer when the + // decoded message is not a multiple of 3. + while (!buffer->empty() && buffer->back() == '\0') { + buffer->pop_back(); + } + + return true; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/base64.h b/base/cvd/cuttlefish/common/libs/utils/base64.h new file mode 100644 index 0000000000..c3902327db --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/base64.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 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 + +#include +#include +#include +#include + +namespace cuttlefish { + +bool EncodeBase64(const void* _data, std::size_t size, std::string* out); + +bool DecodeBase64(const std::string& data, std::vector* buffer); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/cf_endian.h b/base/cvd/cuttlefish/common/libs/utils/cf_endian.h new file mode 100644 index 0000000000..6eb5dd1928 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/cf_endian.h @@ -0,0 +1,56 @@ +// +// Copyright (C) 2020 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 + +#include + +#include + +// The utilities in android-base/endian.h still require the use of regular int +// types to store values with any endianness, which requires the user to +// remember to manually do the required conversions, which is prone to errors. +// The types introduced here allow handling these values safely. + +namespace cuttlefish { + +#define DECLARE_TYPE(new_type, base_type, to_new, to_base) \ + class new_type { \ + public: \ + new_type() = default; \ + explicit new_type(base_type val) : inner_(to_new(val)) {} \ + new_type(const new_type&) = default; \ + new_type& operator=(const new_type& other) = default; \ + volatile new_type& operator=(const new_type& other) volatile { \ + inner_ = other.inner_; \ + return *this; \ + } \ + base_type as_##base_type() const volatile { return to_base(inner_); } \ + \ + private: \ + base_type inner_; \ + }; \ + static_assert(sizeof(new_type) == sizeof(base_type)) + +DECLARE_TYPE(Le16, uint16_t, htole16, le16toh); +DECLARE_TYPE(Le32, uint32_t, htole32, le32toh); +DECLARE_TYPE(Le64, uint64_t, htole64, le64toh); +DECLARE_TYPE(Be16, uint16_t, htobe16, be16toh); +DECLARE_TYPE(Be32, uint32_t, htobe32, be32toh); +DECLARE_TYPE(Be64, uint64_t, htobe64, be64toh); + +#undef DECLARE_TYPE + +} // namespace cuttlefish \ No newline at end of file diff --git a/base/cvd/cuttlefish/common/libs/utils/collect.h b/base/cvd/cuttlefish/common/libs/utils/collect.h new file mode 100644 index 0000000000..08d5287b11 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/collect.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 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 + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +/** + * return all the elements in container that satisfies predicate. + * + * Container could be mostly any type, and Set should be any sort of set. + */ +template +Set Collect(const Container& container, + std::function predicate) { + Set output; + std::copy_if(container.cbegin(), container.cend(), + std::inserter(output, output.end()), predicate); + return output; +} + +/** + * Collect all Ts from each container inside the "Containers" + * + * Containers are a set/list of Container. Container can be viewed as a set/list + * of Ts. + * + */ +template +Set Flatten(const Containers& containers) { + Set output; + for (const auto& container : containers) { + output.insert(container.cbegin(), container.cend()); + } + return output; +} + +template +Result::type> AtMostN(S&& s, const size_t n) { + CF_EXPECT(s.size() <= n); + return {std::forward(s)}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/contains.h b/base/cvd/cuttlefish/common/libs/utils/contains.h new file mode 100644 index 0000000000..97361c1262 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/contains.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include +#include +#include + +/** + * @file: Implement Contains(container, key) + * + * The function returns true if container has the key, or false. + * + * If the container has a find(key) method (e.g. set, unordered_set, std::map, + * etc), the find method is used. Otherwise, the std::find function from + * algorithm is used, which may result in a linear search. + * + * See go/cf-utils-contains for more details. + */ +namespace cuttlefish { +namespace contains_internal_impl { + +/* + * If Container does not have find key, will be a compiler error used + * by SFINAE. If it does have one, this is equivalent to the "void" type. + */ +template +using VoidTypeIfHasFind = + decltype(void(std::declval().find(std::declval()))); + +/* + * Here is how this works: + * + * Given that + * HasFindImpl is used in the code + * + * 1. The input is effectively regarded as HasFindImpl. + * The specialized version below isn't looked up yet; whether the specialized + * version below is used or not, the compiler front-end needs all three + * template parameters to match against either special or generic version. + * When obtaining "all three," the front-end only looks up the base template + * definition. The default type of the third template parameter is void, so + * the given type is expanded/deduced to HasFindImpl. + * + * 2. Now, given HasFindImpl, the compiler front-end + * tries matching against the specialized and generic/original versions. If + * the input could matches both a generic and a specialized one, the compiler + * chooses the specialized one. Thus, particularly, HasFindImpl + * implementation's third parameter in the specialized version must be the + * same as the default type of the third template parameter to the original/ + * generic version, which is "void." + */ +template +struct HasFindImpl : std::false_type {}; + +template +struct HasFindImpl> + : std::true_type {}; + +template +using RemoveCvref = + typename std::remove_cv_t>; + +template +using IsSame = typename std::is_same, RemoveCvref>; + +template +struct IsString : IsSame {}; + +template +struct IsStringView : IsSame {}; + +} // namespace contains_internal_impl + +// TODO(kwstephenkim): Replace these when C++20 starts to be used. +template ::value && + (!contains_internal_impl::IsString::value && + !contains_internal_impl::IsStringView::value), + void>> +constexpr bool Contains(Container&& container, U&& u) { + // using O(1) or O(lgN) find() + return container.find(std::forward(u)) != container.end(); +} + +template < + typename Container, typename U, + std::enable_if_t::value, + int> = 0> +constexpr bool Contains(Container&& container, U&& u) { + // falls back to a generic, likely linear search + const auto itr = + std::find(std::begin(container), std::end(container), std::forward(u)); + return itr != std::end(container); +} + +// std::string:: or std::string_view::find() returns index, not iterator +template +constexpr bool Contains(const std::string& s, T&& t) { + return s.find(std::forward(t)) != std::string::npos; +} + +template +constexpr bool Contains(const std::string_view& s, T&& t) { + return s.find(std::forward(t)) != std::string_view::npos; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/environment.cpp b/base/cvd/cuttlefish/common/libs/utils/environment.cpp new file mode 100644 index 0000000000..5fc7819cf2 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/environment.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "common/libs/utils/environment.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/utils/files.h" + +namespace cuttlefish { + +std::string StringFromEnv(const std::string& varname, + const std::string& defval) { + const char* const valstr = getenv(varname.c_str()); + if (!valstr) { + return defval; + } + return valstr; +} + +/** + * at runtime, return the arch of the host: e.g. aarch64, x86_64, etc + * + * uses "`which uname` -m" + * + * @return arch string on success, "" on failure + */ +std::string HostArchStr() { + static std::string arch; + if (!arch.empty()) { + return arch; + } + + // good to check if uname exists and is executable + // or, guarantee uname is available by dependency list + FILE* pip = popen("uname -m", "r"); + if (!pip) { + return std::string{}; + } + + auto read_from_file = + [](FILE* fp, size_t len) { + /* + * to see if input is longer than len, + * we read up to len+1. If the length is len+1, + * then the input is too long + */ + decltype(len) upper = len + 1; + std::string format("%"); + format.append(std::to_string(upper)).append("s"); + // 1 extra character needed for the terminating null + // character added by fscanf. + std::shared_ptr buf(new char[upper + 1], + std::default_delete()); + if (fscanf(fp, format.c_str(), buf.get()) == EOF) { + return std::string{}; + } + std::string result(buf.get()); + return (result.length() < upper) ? result : std::string{}; + }; + arch = android::base::Trim(std::string_view{read_from_file(pip, 20)}); + pclose(pip); + return arch; +} + +Arch HostArch() { + std::string arch_str = HostArchStr(); + if (arch_str == "aarch64" || arch_str == "arm64") { + return Arch::Arm64; + } else if (arch_str == "arm") { + return Arch::Arm; + } else if (arch_str == "riscv64") { + return Arch::RiscV64; + } else if (arch_str == "x86_64") { + return Arch::X86_64; + } else if (arch_str.size() == 4 && arch_str[0] == 'i' && arch_str[2] == '8' && + arch_str[3] == '6') { + return Arch::X86; + } else { + LOG(FATAL) << "Unknown host architecture: " << arch_str; + return Arch::X86; + } +} + +bool IsHostCompatible(Arch arch) { + Arch host_arch = HostArch(); + return arch == host_arch || (arch == Arch::Arm && host_arch == Arch::Arm64) || + (arch == Arch::X86 && host_arch == Arch::X86_64); +} + +static bool IsRunningInDocker() { + // if /.dockerenv exists, it's inside a docker container + static std::string docker_env_path("/.dockerenv"); + static bool ret = + FileExists(docker_env_path) || DirectoryExists(docker_env_path); + return ret; +} + +bool IsRunningInContainer() { + // TODO: add more if we support other containers than docker + return IsRunningInDocker(); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/environment.h b/base/cvd/cuttlefish/common/libs/utils/environment.h new file mode 100644 index 0000000000..b84c036169 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/environment.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 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 + +#include + +namespace cuttlefish { + +enum class Arch { + Arm, + Arm64, + RiscV64, + X86, + X86_64, +}; + +std::string StringFromEnv(const std::string& varname, + const std::string& defval); + +std::string HostArchStr(); +Arch HostArch(); +bool IsHostCompatible(Arch arch); + +bool IsRunningInContainer(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/files.cpp b/base/cvd/cuttlefish/common/libs/utils/files.cpp new file mode 100644 index 0000000000..f6c5b38f2c --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/files.cpp @@ -0,0 +1,735 @@ +/* + * 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. + */ + +#include "common/libs/utils/files.h" + +#ifdef __linux__ +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/inotify.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/subprocess.h" +#include "common/libs/utils/users.h" + +#ifdef __APPLE__ +#define off64_t off_t +#define ftruncate64 ftruncate +#endif + +namespace cuttlefish { + +bool FileExists(const std::string& path, bool follow_symlinks) { + struct stat st {}; + return (follow_symlinks ? stat : lstat)(path.c_str(), &st) == 0; +} + +bool FileHasContent(const std::string& path) { + return FileSize(path) > 0; +} + +Result> DirectoryContents(const std::string& path) { + std::vector ret; + std::unique_ptr dir(opendir(path.c_str()), closedir); + CF_EXPECTF(dir != nullptr, "Could not read from dir \"{}\"", path); + struct dirent* ent{}; + while ((ent = readdir(dir.get()))) { + ret.emplace_back(ent->d_name); + } + return ret; +} + +bool DirectoryExists(const std::string& path, bool follow_symlinks) { + struct stat st {}; + if ((follow_symlinks ? stat : lstat)(path.c_str(), &st) == -1) { + return false; + } + if ((st.st_mode & S_IFMT) != S_IFDIR) { + return false; + } + return true; +} + +Result EnsureDirectoryExists(const std::string& directory_path, + const mode_t mode, + const std::string& group_name) { + if (DirectoryExists(directory_path, /* follow_symlinks */ true)) { + return {}; + } + const auto parent_dir = android::base::Dirname(directory_path); + if (parent_dir.size() > 1) { + EnsureDirectoryExists(parent_dir, mode, group_name); + } + LOG(VERBOSE) << "Setting up " << directory_path; + if (mkdir(directory_path.c_str(), mode) < 0 && errno != EEXIST) { + return CF_ERRNO("Failed to create directory: \"" << directory_path << "\"" + << strerror(errno)); + } + + if (group_name != "") { + ChangeGroup(directory_path, group_name); + } + + return {}; +} + +Result ChangeGroup(const std::string& path, + const std::string& group_name) { + auto groupId = GroupIdFromName(group_name); + + if (groupId == -1) { + return CF_ERR("Failed to get group id: ") << group_name; + } + + if (chown(path.c_str(), -1, groupId) != 0) { + return CF_ERRNO("Feailed to set group for path: " + << path << ", " << group_name << ", " << strerror(errno)); + } + + return {}; +} + +bool CanAccess(const std::string& path, const int mode) { + return access(path.c_str(), mode) == 0; +} + +bool IsDirectoryEmpty(const std::string& path) { + auto direc = ::opendir(path.c_str()); + if (!direc) { + LOG(ERROR) << "IsDirectoryEmpty test failed with " << path + << " as it failed to be open" << std::endl; + return false; + } + + decltype(::readdir(direc)) sub = nullptr; + int cnt {0}; + while ( (sub = ::readdir(direc)) ) { + cnt++; + if (cnt > 2) { + LOG(ERROR) << "IsDirectoryEmpty test failed with " << path + << " as it exists but not empty" << std::endl; + return false; + } + } + return true; +} + +bool RecursivelyRemoveDirectory(const std::string& path) { + // Copied from libbase TemporaryDir destructor. + auto callback = [](const char* child, const struct stat*, int file_type, + struct FTW*) -> int { + switch (file_type) { + case FTW_D: + case FTW_DP: + case FTW_DNR: + if (rmdir(child) == -1) { + PLOG(ERROR) << "rmdir " << child; + } + break; + case FTW_NS: + default: + if (rmdir(child) != -1) { + break; + } + // FALLTHRU (for gcc, lint, pcc, etc; and following for clang) + FALLTHROUGH_INTENDED; + case FTW_F: + case FTW_SL: + case FTW_SLN: + if (unlink(child) == -1) { + PLOG(ERROR) << "unlink " << child; + } + break; + } + return 0; + }; + + return nftw(path.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) == + 0; +} + +namespace { + +bool SendFile(int out_fd, int in_fd, off64_t* offset, size_t count) { + while (count > 0) { +#ifdef __linux__ + const auto bytes_written = + TEMP_FAILURE_RETRY(sendfile(out_fd, in_fd, offset, count)); + if (bytes_written <= 0) { + return false; + } +#elif defined(__APPLE__) + off_t bytes_written = count; + auto success = TEMP_FAILURE_RETRY( + sendfile(in_fd, out_fd, *offset, &bytes_written, nullptr, 0)); + *offset += bytes_written; + if (success < 0 || bytes_written == 0) { + return false; + } +#endif + count -= bytes_written; + } + return true; +} + +} // namespace + +bool Copy(const std::string& from, const std::string& to) { + android::base::unique_fd fd_from( + open(from.c_str(), O_RDONLY | O_CLOEXEC)); + android::base::unique_fd fd_to( + open(to.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)); + + if (fd_from.get() < 0 || fd_to.get() < 0) { + return false; + } + + off_t farthest_seek = lseek(fd_from.get(), 0, SEEK_END); + if (farthest_seek == -1) { + PLOG(ERROR) << "Could not lseek in \"" << from << "\""; + return false; + } + if (ftruncate64(fd_to.get(), farthest_seek) < 0) { + PLOG(ERROR) << "Failed to ftruncate " << to; + } + off_t offset = 0; + while (offset < farthest_seek) { + off_t new_offset = lseek(fd_from.get(), offset, SEEK_HOLE); + if (new_offset == -1) { + // ENXIO is returned when there are no more blocks of this type + // coming. + if (errno == ENXIO) { + return true; + } + PLOG(ERROR) << "Could not lseek in \"" << from << "\""; + return false; + } + auto data_bytes = new_offset - offset; + if (lseek(fd_to.get(), offset, SEEK_SET) < 0) { + PLOG(ERROR) << "lseek() on " << to << " failed"; + return false; + } + if (!SendFile(fd_to.get(), fd_from.get(), &offset, data_bytes)) { + PLOG(ERROR) << "sendfile() failed"; + return false; + } + CHECK_EQ(offset, new_offset); + if (offset >= farthest_seek) { + return true; + } + new_offset = lseek(fd_from.get(), offset, SEEK_DATA); + if (new_offset == -1) { + // ENXIO is returned when there are no more blocks of this type + // coming. + if (errno == ENXIO) { + return true; + } + PLOG(ERROR) << "Could not lseek in \"" << from << "\""; + return false; + } + offset = new_offset; + } + return true; +} + +std::string AbsolutePath(const std::string& path) { + if (path.empty()) { + return {}; + } + if (path[0] == '/') { + return path; + } + if (path[0] == '~') { + LOG(WARNING) << "Tilde expansion in path " << path <<" is not supported"; + return {}; + } + + std::array buffer{}; + if (!realpath(".", buffer.data())) { + LOG(WARNING) << "Could not get real path for current directory \".\"" + << ": " << strerror(errno); + return {}; + } + return std::string{buffer.data()} + "/" + path; +} + +off_t FileSize(const std::string& path) { + struct stat st {}; + if (stat(path.c_str(), &st) == -1) { + return 0; + } + return st.st_size; +} + +bool MakeFileExecutable(const std::string& path) { + LOG(DEBUG) << "Making " << path << " executable"; + return chmod(path.c_str(), S_IRWXU) == 0; +} + +// TODO(schuffelen): Use std::filesystem::last_write_time when on C++17 +std::chrono::system_clock::time_point FileModificationTime(const std::string& path) { + struct stat st {}; + if (stat(path.c_str(), &st) == -1) { + return std::chrono::system_clock::time_point(); + } +#ifdef __linux__ + std::chrono::seconds seconds(st.st_mtim.tv_sec); +#elif defined(__APPLE__) + std::chrono::seconds seconds(st.st_mtimespec.tv_sec); +#else +#error "Unsupported operating system" +#endif + return std::chrono::system_clock::time_point(seconds); +} + +Result RenameFile(const std::string& current_filepath, + const std::string& target_filepath) { + if (current_filepath != target_filepath) { + CF_EXPECT(rename(current_filepath.c_str(), target_filepath.c_str()) == 0, + "rename " << current_filepath << " to " << target_filepath + << " failed: " << strerror(errno)); + } + return target_filepath; +} + +bool RemoveFile(const std::string& file) { + LOG(DEBUG) << "Removing file " << file; + if (remove(file.c_str()) == 0) { + return true; + } + LOG(ERROR) << "Failed to remove file " << file << " : " + << std::strerror(errno); + return false; +} + +std::string ReadFile(const std::string& file) { + std::string contents; + std::ifstream in(file, std::ios::in | std::ios::binary); + in.seekg(0, std::ios::end); + if (in.fail()) { + // TODO(schuffelen): Return a failing Result instead + return ""; + } + if (in.tellg() == std::ifstream::pos_type(-1)) { + PLOG(ERROR) << "Failed to seek on " << file; + return ""; + } + contents.resize(in.tellg()); + in.seekg(0, std::ios::beg); + in.read(&contents[0], contents.size()); + in.close(); + return(contents); +} + +Result ReadFileContents(const std::string& filepath) { + CF_EXPECTF(FileExists(filepath), "The file at \"{}\" does not exist.", + filepath); + auto file = SharedFD::Open(filepath, O_RDONLY); + CF_EXPECTF(file->IsOpen(), "Failed to open file \"{}\". Error:\n", filepath, + file->StrError()); + std::string file_content; + auto size = ReadAll(file, &file_content); + CF_EXPECTF(size >= 0, "Failed to read file contents. Error:\n", + file->StrError()); + return file_content; +} + +std::string CurrentDirectory() { + std::unique_ptr cwd(getcwd(nullptr, 0), &free); + std::string process_cwd(cwd.get()); + if (!cwd) { + PLOG(ERROR) << "`getcwd(nullptr, 0)` failed"; + return ""; + } + return process_cwd; +} + +FileSizes SparseFileSizes(const std::string& path) { + auto fd = SharedFD::Open(path, O_RDONLY); + if (!fd->IsOpen()) { + LOG(ERROR) << "Could not open \"" << path << "\": " << fd->StrError(); + return {}; + } + off_t farthest_seek = fd->LSeek(0, SEEK_END); + LOG(VERBOSE) << "Farthest seek: " << farthest_seek; + if (farthest_seek == -1) { + LOG(ERROR) << "Could not lseek in \"" << path << "\": " << fd->StrError(); + return {}; + } + off_t data_bytes = 0; + off_t offset = 0; + while (offset < farthest_seek) { + off_t new_offset = fd->LSeek(offset, SEEK_HOLE); + if (new_offset == -1) { + // ENXIO is returned when there are no more blocks of this type coming. + if (fd->GetErrno() == ENXIO) { + break; + } else { + LOG(ERROR) << "Could not lseek in \"" << path << "\": " << fd->StrError(); + return {}; + } + } else { + data_bytes += new_offset - offset; + offset = new_offset; + } + if (offset >= farthest_seek) { + break; + } + new_offset = fd->LSeek(offset, SEEK_DATA); + if (new_offset == -1) { + // ENXIO is returned when there are no more blocks of this type coming. + if (fd->GetErrno() == ENXIO) { + break; + } else { + LOG(ERROR) << "Could not lseek in \"" << path << "\": " << fd->StrError(); + return {}; + } + } else { + offset = new_offset; + } + } + return (FileSizes) { .sparse_size = farthest_seek, .disk_size = data_bytes }; +} + +std::string cpp_basename(const std::string& str) { + char* copy = strdup(str.c_str()); // basename may modify its argument + std::string ret(basename(copy)); + free(copy); + return ret; +} + +std::string cpp_dirname(const std::string& str) { + return android::base::Dirname(str); +} + +bool FileIsSocket(const std::string& path) { + struct stat st {}; + return stat(path.c_str(), &st) == 0 && S_ISSOCK(st.st_mode); +} + +int GetDiskUsage(const std::string& path) { + Command du_cmd("du"); + du_cmd.AddParameter("-b"); + du_cmd.AddParameter("-k"); + du_cmd.AddParameter("-s"); + du_cmd.AddParameter(path); + SharedFD read_fd; + SharedFD write_fd; + SharedFD::Pipe(&read_fd, &write_fd); + du_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, write_fd); + auto subprocess = du_cmd.Start(); + std::array text_output{}; + const auto bytes_read = read_fd->Read(text_output.data(), text_output.size()); + CHECK_GT(bytes_read, 0) << "Failed to read from pipe " << strerror(errno); + std::move(subprocess).Wait(); + return atoi(text_output.data()) * 1024; +} + +/** + * Find an image file through the input path and pattern. + * + * If it finds the file, return the path string. + * If it can't find the file, return empty string. + */ +std::string FindImage(const std::string& search_path, + const std::vector& pattern) { + const std::string& search_path_extend = search_path + "/"; + for (const auto& name : pattern) { + std::string image = search_path_extend + name; + if (FileExists(image)) { + return image; + } + } + return ""; +} + +std::string FindFile(const std::string& path, const std::string& target_name) { + std::string ret; + WalkDirectory(path, + [&ret, &target_name](const std::string& filename) mutable { + if (cpp_basename(filename) == target_name) { + ret = filename; + } + return true; + }); + return ret; +} + +// Recursively enumerate files in |dir|, and invoke the callback function with +// path to each file/directory. +Result WalkDirectory( + const std::string& dir, + const std::function& callback) { + const auto files = CF_EXPECT(DirectoryContents(dir)); + for (const auto& filename : files) { + if (filename == "." || filename == "..") { + continue; + } + auto file_path = dir + "/"; + file_path.append(filename); + callback(file_path); + if (DirectoryExists(file_path)) { + WalkDirectory(file_path, callback); + } + } + return {}; +} + +#ifdef __linux__ +class InotifyWatcher { + public: + InotifyWatcher(int inotify, const std::string& path, int watch_mode) + : inotify_(inotify) { + watch_ = inotify_add_watch(inotify_, path.c_str(), watch_mode); + } + virtual ~InotifyWatcher() { inotify_rm_watch(inotify_, watch_); } + + private: + int inotify_; + int watch_; +}; + +static Result WaitForFileInternal(const std::string& path, int timeoutSec, + int inotify) { + CF_EXPECT_NE(path, "", "Path is empty"); + + if (FileExists(path, true)) { + return {}; + } + + const auto targetTime = + std::chrono::system_clock::now() + std::chrono::seconds(timeoutSec); + + const auto parentPath = cpp_dirname(path); + const auto filename = cpp_basename(path); + + CF_EXPECT(WaitForFile(parentPath, timeoutSec), + "Error while waiting for parent directory creation"); + + auto watcher = InotifyWatcher(inotify, parentPath.c_str(), IN_CREATE); + + if (FileExists(path, true)) { + return {}; + } + + while (true) { + const auto currentTime = std::chrono::system_clock::now(); + + if (currentTime >= targetTime) { + return CF_ERR("Timed out"); + } + + const auto timeRemain = + std::chrono::duration_cast(targetTime - + currentTime) + .count(); + const auto secondInUsec = + std::chrono::microseconds(std::chrono::seconds(1)).count(); + struct timeval timeout; + + timeout.tv_sec = timeRemain / secondInUsec; + timeout.tv_usec = timeRemain % secondInUsec; + + fd_set readfds; + + FD_ZERO(&readfds); + FD_SET(inotify, &readfds); + + auto ret = select(inotify + 1, &readfds, NULL, NULL, &timeout); + + if (ret == 0) { + return CF_ERR("select() timed out"); + } else if (ret < 0) { + return CF_ERRNO("select() failed"); + } + + auto names = GetCreatedFileListFromInotifyFd(inotify); + + CF_EXPECT(names.size() > 0, + "Failed to get names from inotify " << strerror(errno)); + + if (Contains(names, filename)) { + return {}; + } + } + + return CF_ERR("This shouldn't be executed"); +} + +auto WaitForFile(const std::string& path, int timeoutSec) + -> decltype(WaitForFileInternal(path, timeoutSec, 0)) { + android::base::unique_fd inotify(inotify_init1(IN_CLOEXEC)); + + CF_EXPECT(WaitForFileInternal(path, timeoutSec, inotify.get())); + + return {}; +} + +Result WaitForUnixSocket(const std::string& path, int timeoutSec) { + const auto targetTime = + std::chrono::system_clock::now() + std::chrono::seconds(timeoutSec); + + CF_EXPECT(WaitForFile(path, timeoutSec), + "Waiting for socket path creation failed"); + CF_EXPECT(FileIsSocket(path), "Specified path is not a socket"); + + while (true) { + const auto currentTime = std::chrono::system_clock::now(); + + if (currentTime >= targetTime) { + return CF_ERR("Timed out"); + } + + const auto timeRemain = std::chrono::duration_cast( + targetTime - currentTime) + .count(); + auto testConnect = + SharedFD::SocketLocalClient(path, false, SOCK_STREAM, timeRemain); + + if (testConnect->IsOpen()) { + return {}; + } + + sched_yield(); + } + + return CF_ERR("This shouldn't be executed"); +} +#endif + +namespace { + +std::vector FoldPath(std::vector elements, + std::string token) { + static constexpr std::array kIgnored = {".", "..", ""}; + if (token == ".." && !elements.empty()) { + elements.pop_back(); + } else if (!Contains(kIgnored, token)) { + elements.emplace_back(token); + } + return elements; +} + +Result> CalculatePrefix( + const InputPathForm& path_info) { + const auto& path = path_info.path_to_convert; + std::string working_dir; + if (path_info.current_working_dir) { + working_dir = *path_info.current_working_dir; + } else { + working_dir = CurrentDirectory(); + } + std::vector prefix; + if (path == "~" || android::base::StartsWith(path, "~/")) { + const auto home_dir = + path_info.home_dir.value_or(CF_EXPECT(SystemWideUserHome())); + prefix = android::base::Tokenize(home_dir, "/"); + } else if (!android::base::StartsWith(path, "/")) { + prefix = android::base::Tokenize(working_dir, "/"); + } + return prefix; +} + +} // namespace + +Result EmulateAbsolutePath(const InputPathForm& path_info) { + const auto& path = path_info.path_to_convert; + std::string working_dir; + if (path_info.current_working_dir) { + working_dir = *path_info.current_working_dir; + } else { + working_dir = CurrentDirectory(); + } + CF_EXPECT(android::base::StartsWith(working_dir, '/'), + "Current working directory should be given in an absolute path."); + + if (path.empty()) { + LOG(ERROR) << "The requested path to convert an absolute path is empty."; + return ""; + } + + auto prefix = CF_EXPECT(CalculatePrefix(path_info)); + std::vector components; + components.insert(components.end(), prefix.begin(), prefix.end()); + auto tokens = android::base::Tokenize(path, "/"); + // remove first ~ + if (!tokens.empty() && tokens.at(0) == "~") { + tokens.erase(tokens.begin()); + } + components.insert(components.end(), tokens.begin(), tokens.end()); + + std::string combined = android::base::Join(components, "/"); + CF_EXPECTF(!Contains(components, "~"), + "~ is not allowed in the middle of the path: {}", combined); + + auto processed_tokens = std::accumulate(components.begin(), components.end(), + std::vector{}, FoldPath); + + const auto processed_path = "/" + android::base::Join(processed_tokens, "/"); + + std::string real_path = processed_path; + if (path_info.follow_symlink && FileExists(processed_path)) { + CF_EXPECTF(android::base::Realpath(processed_path, &real_path), + "Failed to effectively conduct readpath -f {}", processed_path); + } + return real_path; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/files.h b/base/cvd/cuttlefish/common/libs/utils/files.h new file mode 100644 index 0000000000..b4cfd1b585 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/files.h @@ -0,0 +1,116 @@ +/* + * 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 + +#include +#include + +#include +#include +#include +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { +bool FileExists(const std::string& path, bool follow_symlinks = true); +bool FileHasContent(const std::string& path); +Result> DirectoryContents(const std::string& path); +bool DirectoryExists(const std::string& path, bool follow_symlinks = true); +Result EnsureDirectoryExists(const std::string& directory_path, + const mode_t mode = S_IRWXU | S_IRWXG | + S_IROTH | S_IXOTH, + const std::string& group_name = ""); +Result ChangeGroup(const std::string& path, + const std::string& group_name); +bool CanAccess(const std::string& path, const int mode); +bool IsDirectoryEmpty(const std::string& path); +bool RecursivelyRemoveDirectory(const std::string& path); +bool Copy(const std::string& from, const std::string& to); +off_t FileSize(const std::string& path); +bool RemoveFile(const std::string& file); +Result RenameFile(const std::string& current_filepath, + const std::string& target_filepath); +std::string ReadFile(const std::string& file); +Result ReadFileContents(const std::string& filepath); +bool MakeFileExecutable(const std::string& path); +std::chrono::system_clock::time_point FileModificationTime(const std::string& path); +std::string cpp_dirname(const std::string& str); +std::string cpp_basename(const std::string& str); +// Whether a file exists and is a unix socket +bool FileIsSocket(const std::string& path); +// Get disk usage of a path. If this path is a directory, disk usage will +// account for all files under this folder(recursively). +int GetDiskUsage(const std::string& path); + +// acloud related API +std::string FindImage(const std::string& search_path, + const std::vector& pattern); + +// The returned value may contain .. or . if these are present in the path +// argument. +// path must not contain ~ +std::string AbsolutePath(const std::string& path); + +std::string CurrentDirectory(); + +struct FileSizes { + off_t sparse_size; + off_t disk_size; +}; +FileSizes SparseFileSizes(const std::string& path); + +// Find file with name |target_name| under directory |path|, return path to +// found file(if any) +std::string FindFile(const std::string& path, const std::string& target_name); + +Result WalkDirectory( + const std::string& dir, + const std::function& callback); + +#ifdef __linux__ +Result WaitForFile(const std::string& path, int timeoutSec); +Result WaitForUnixSocket(const std::string& path, int timeoutSec); +#endif + +// parameter to EmulateAbsolutePath +struct InputPathForm { + /** If nullopt, uses the process' current working dir + * But if there is no preceding .. or ., this field is not used. + */ + std::optional current_working_dir; + /** If nullopt, use SystemWideUserHome() + * But, if there's no preceding ~, this field is not used. + */ + std::optional home_dir; + std::string path_to_convert; + bool follow_symlink; +}; + +/** + * Returns emulated absolute path with a different process'/thread's + * context. + * + * This is useful when daemon(0, 0)-started server process wants to + * figure out a relative path that came from its client. + * + * The call mostly succeeds. It fails only if: + * home_dir isn't given so supposed to relies on the local SystemWideUserHome() + * but SystemWideUserHome() call fails. + */ +Result EmulateAbsolutePath(const InputPathForm& path_info); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/files_test.cpp b/base/cvd/cuttlefish/common/libs/utils/files_test.cpp new file mode 100644 index 0000000000..ea03cf6a46 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/files_test.cpp @@ -0,0 +1,103 @@ +// +// Copyright (C) 2023 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. + +#include "common/libs/utils/files_test_helper.h" + +namespace cuttlefish { + +TEST_P(EmulateAbsolutePathBase, NoHomeNoPwd) { + const bool follow_symlink = false; + auto emulated_absolute_path = + EmulateAbsolutePath({.current_working_dir = std::nullopt, + .home_dir = std::nullopt, + .path_to_convert = input_path_, + .follow_symlink = follow_symlink}); + + ASSERT_TRUE(emulated_absolute_path.ok()) + << emulated_absolute_path.error().Trace(); + ASSERT_EQ(*emulated_absolute_path, expected_path_); +} + +INSTANTIATE_TEST_SUITE_P( + CommonUtilsTest, EmulateAbsolutePathBase, + testing::Values(InputOutput{.path_to_convert_ = "/", .expected_ = "/"}, + InputOutput{.path_to_convert_ = "", .expected_ = ""}, + InputOutput{.path_to_convert_ = "/a/b/c/", + .expected_ = "/a/b/c"}, + InputOutput{.path_to_convert_ = "/a", .expected_ = "/a"})); + +TEST_P(EmulateAbsolutePathWithPwd, NoHomeYesPwd) { + const bool follow_symlink = false; + auto emulated_absolute_path = + EmulateAbsolutePath({.current_working_dir = current_dir_, + .home_dir = "/a/b/c", + .path_to_convert = input_path_, + .follow_symlink = follow_symlink}); + + ASSERT_TRUE(emulated_absolute_path.ok()) + << emulated_absolute_path.error().Trace(); + ASSERT_EQ(*emulated_absolute_path, expected_path_); +} + +INSTANTIATE_TEST_SUITE_P( + CommonUtilsTest, EmulateAbsolutePathWithPwd, + testing::Values(InputOutput{.working_dir_ = "/x/y/z", + .path_to_convert_ = "", + .expected_ = ""}, + InputOutput{.working_dir_ = "/x/y/z", + .path_to_convert_ = "a", + .expected_ = "/x/y/z/a"}, + InputOutput{.working_dir_ = "/x/y/z", + .path_to_convert_ = ".", + .expected_ = "/x/y/z"}, + InputOutput{.working_dir_ = "/x/y/z", + .path_to_convert_ = "..", + .expected_ = "/x/y"}, + InputOutput{.working_dir_ = "/x/y/z", + .path_to_convert_ = "./k/../../t/./q", + .expected_ = "/x/y/t/q"})); + +TEST_P(EmulateAbsolutePathWithHome, YesHomeNoPwd) { + const bool follow_symlink = false; + auto emulated_absolute_path = + EmulateAbsolutePath({.current_working_dir = std::nullopt, + .home_dir = home_dir_, + .path_to_convert = input_path_, + .follow_symlink = follow_symlink}); + + ASSERT_TRUE(emulated_absolute_path.ok()) + << emulated_absolute_path.error().Trace(); + ASSERT_EQ(*emulated_absolute_path, expected_path_); +} + +INSTANTIATE_TEST_SUITE_P( + CommonUtilsTest, EmulateAbsolutePathWithHome, + testing::Values(InputOutput{.home_dir_ = "/x/y/z", + .path_to_convert_ = "~", + .expected_ = "/x/y/z"}, + InputOutput{.home_dir_ = "/x/y/z", + .path_to_convert_ = "~/a", + .expected_ = "/x/y/z/a"}, + InputOutput{.home_dir_ = "/x/y/z", + .path_to_convert_ = "~/.", + .expected_ = "/x/y/z"}, + InputOutput{.home_dir_ = "/x/y/z", + .path_to_convert_ = "~/..", + .expected_ = "/x/y"}, + InputOutput{.home_dir_ = "/x/y/z", + .path_to_convert_ = "~/k/../../t/./q", + .expected_ = "/x/y/t/q"})); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/files_test_helper.cpp b/base/cvd/cuttlefish/common/libs/utils/files_test_helper.cpp new file mode 100644 index 0000000000..4cda8319e7 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/files_test_helper.cpp @@ -0,0 +1,37 @@ +// +// Copyright (C) 2023 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. + +#include "common/libs/utils/files_test_helper.h" + +namespace cuttlefish { + +EmulateAbsolutePathBase::EmulateAbsolutePathBase() { + input_path_ = GetParam().path_to_convert_; + expected_path_ = GetParam().expected_; +} + +EmulateAbsolutePathWithPwd::EmulateAbsolutePathWithPwd() { + input_path_ = GetParam().path_to_convert_; + expected_path_ = GetParam().expected_; + current_dir_ = GetParam().working_dir_; +} + +EmulateAbsolutePathWithHome::EmulateAbsolutePathWithHome() { + input_path_ = GetParam().path_to_convert_; + expected_path_ = GetParam().expected_; + home_dir_ = GetParam().home_dir_; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/files_test_helper.h b/base/cvd/cuttlefish/common/libs/utils/files_test_helper.h new file mode 100644 index 0000000000..e70ba47ec6 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/files_test_helper.h @@ -0,0 +1,60 @@ +// +// Copyright (C) 2023 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 + +#include +#include + +#include + +#include "common/libs/utils/files.h" + +namespace cuttlefish { + +struct InputOutput { + std::string path_to_convert_; + std::string working_dir_; + std::string home_dir_; + std::string expected_; +}; + +class EmulateAbsolutePathBase : public testing::TestWithParam { + protected: + EmulateAbsolutePathBase(); + + std::string input_path_; + std::string expected_path_; +}; + +class EmulateAbsolutePathWithPwd : public testing::TestWithParam { + protected: + EmulateAbsolutePathWithPwd(); + + std::string input_path_; + std::string current_dir_; + std::string expected_path_; +}; + +class EmulateAbsolutePathWithHome : public EmulateAbsolutePathBase { + protected: + EmulateAbsolutePathWithHome(); + + std::string input_path_; + std::string home_dir_; + std::string expected_path_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/flag_parser.cpp b/base/cvd/cuttlefish/common/libs/utils/flag_parser.cpp new file mode 100644 index 0000000000..7ad1e558e7 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/flag_parser.cpp @@ -0,0 +1,619 @@ +/* + * Copyright (C) 2021 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. + */ + +#include "common/libs/utils/flag_parser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "common/libs/utils/tee_logging.h" + +namespace cuttlefish { + +std::ostream& operator<<(std::ostream& out, const FlagAlias& alias) { + switch (alias.mode) { + case FlagAliasMode::kFlagExact: + return out << alias.name; + case FlagAliasMode::kFlagPrefix: + return out << alias.name << "*"; + case FlagAliasMode::kFlagConsumesFollowing: + return out << alias.name << " *"; + default: + LOG(FATAL) << "Unexpected flag alias mode " << (int)alias.mode; + } + return out; +} + +Flag& Flag::UnvalidatedAlias(const FlagAlias& alias) & { + aliases_.push_back(alias); + return *this; +} +Flag Flag::UnvalidatedAlias(const FlagAlias& alias) && { + aliases_.push_back(alias); + return *this; +} + +void Flag::ValidateAlias(const FlagAlias& alias) { + using android::base::EndsWith; + using android::base::StartsWith; + + CHECK(StartsWith(alias.name, "-")) << "Flags should start with \"-\""; + if (alias.mode == FlagAliasMode::kFlagPrefix) { + CHECK(EndsWith(alias.name, "=")) << "Prefix flags shold end with \"=\""; + } + + CHECK(!HasAlias(alias)) << "Duplicate flag alias: " << alias.name; + if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) { + CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name})) + << "Overlapping flag aliases for " << alias.name; + CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name})) + << "Overlapping flag aliases for " << alias.name; + } else if (alias.mode == FlagAliasMode::kFlagExact) { + CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name})) + << "Overlapping flag aliases for " << alias.name; + CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name})) + << "Overlapping flag aliases for " << alias.name; + } else if (alias.mode == FlagAliasMode::kFlagConsumesArbitrary) { + CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name})) + << "Overlapping flag aliases for " << alias.name; + CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name})) + << "Overlapping flag aliases for " << alias.name; + } +} + +Flag& Flag::Alias(const FlagAlias& alias) & { + ValidateAlias(alias); + aliases_.push_back(alias); + return *this; +} +Flag Flag::Alias(const FlagAlias& alias) && { + ValidateAlias(alias); + aliases_.push_back(alias); + return *this; +} + +Flag& Flag::Help(const std::string& help) & { + help_ = help; + return *this; +} +Flag Flag::Help(const std::string& help) && { + help_ = help; + return *this; +} + +Flag& Flag::Getter(std::function fn) & { + getter_ = std::move(fn); + return *this; +} +Flag Flag::Getter(std::function fn) && { + getter_ = std::move(fn); + return *this; +} + +Flag& Flag::Setter(std::function(const FlagMatch&)> setter) & { + setter_ = std::move(setter); + return *this; +} +Flag Flag::Setter(std::function(const FlagMatch&)> setter) && { + setter_ = std::move(setter); + return *this; +} + +static bool LikelyFlag(const std::string& next_arg) { + return android::base::StartsWith(next_arg, "-"); +} + +Result ParseBool(const std::string& value, const std::string& name) { + auto result = android::base::ParseBool(value); + CF_EXPECT(result != android::base::ParseBoolResult::kError, + "Failed to parse value \"" << value << "\" for " << name); + if (result == android::base::ParseBoolResult::kTrue) { + return true; + } + return false; +} + +Result Flag::Process( + const std::string& arg, const std::optional& next_arg) const { + using android::base::StringReplace; + auto normalized_arg = StringReplace(arg, "-", "_", true); + if (!setter_ && aliases_.size() > 0) { + return CF_ERRF("No setter for flag with alias {}", aliases_[0].name); + } + for (auto& alias : aliases_) { + auto normalized_alias = StringReplace(alias.name, "-", "_", true); + switch (alias.mode) { + case FlagAliasMode::kFlagConsumesArbitrary: + if (normalized_arg != normalized_alias) { + continue; + } + if (!next_arg || LikelyFlag(*next_arg)) { + CF_EXPECTF((*setter_)({arg, ""}), "Processing \"{}\" failed", arg); + return FlagProcessResult::kFlagConsumed; + } + CF_EXPECTF((*setter_)({arg, *next_arg}), + "Processing \"{}\" \"{}\" failed", arg, *next_arg); + return FlagProcessResult::kFlagConsumedOnlyFollowing; + case FlagAliasMode::kFlagConsumesFollowing: + if (normalized_arg != normalized_alias) { + continue; + } + CF_EXPECTF(next_arg.has_value(), "Expected an argument after \"{}\"", + arg); + CF_EXPECTF((*setter_)({arg, *next_arg}), + "Processing \"{}\" \"{}\" failed", arg, *next_arg); + return FlagProcessResult::kFlagConsumedWithFollowing; + case FlagAliasMode::kFlagExact: + if (normalized_arg != normalized_alias) { + continue; + } + CF_EXPECTF((*setter_)({arg, arg}), "Processing \"{}\" failed", arg); + return FlagProcessResult::kFlagConsumed; + case FlagAliasMode::kFlagPrefix: + if (!android::base::StartsWith(normalized_arg, normalized_alias)) { + continue; + } + CF_EXPECTF((*setter_)({alias.name, arg.substr(alias.name.size())}), + "Processing \"{}\" failed", arg); + return FlagProcessResult::kFlagConsumed; + default: + return CF_ERRF("Unknown flag alias mode: {}", (int)alias.mode); + } + } + return FlagProcessResult::kFlagSkip; +} + +Result Flag::Parse(std::vector& arguments) const { + for (int i = 0; i < arguments.size();) { + std::string arg = arguments[i]; + std::optional next_arg; + if (i < arguments.size() - 1) { + next_arg = arguments[i + 1]; + } + switch (CF_EXPECT(Process(arg, next_arg))) { + case FlagProcessResult::kFlagConsumed: + arguments.erase(arguments.begin() + i); + break; + case FlagProcessResult::kFlagConsumedWithFollowing: + arguments.erase(arguments.begin() + i, arguments.begin() + i + 2); + break; + case FlagProcessResult::kFlagConsumedOnlyFollowing: + arguments.erase(arguments.begin() + i + 1, arguments.begin() + i + 2); + break; + case FlagProcessResult::kFlagSkip: + i++; + break; + } + } + return {}; +} +Result Flag::Parse(std::vector&& arguments) const { + CF_EXPECT(Parse(static_cast&>(arguments))); + return {}; +} + +bool Flag::HasAlias(const FlagAlias& test) const { + for (const auto& alias : aliases_) { + if (alias.mode == test.mode && alias.name == test.name) { + return true; + } + } + return false; +} + +static std::string XmlEscape(const std::string& s) { + using android::base::StringReplace; + return StringReplace(StringReplace(s, "<", "<", true), ">", ">", true); +} + +bool Flag::WriteGflagsCompatXml(std::ostream& out) const { + std::unordered_set name_guesses; + for (const auto& alias : aliases_) { + std::string_view name = alias.name; + if (!android::base::ConsumePrefix(&name, "-")) { + continue; + } + android::base::ConsumePrefix(&name, "-"); + if (alias.mode == FlagAliasMode::kFlagExact) { + android::base::ConsumePrefix(&name, "no"); + name_guesses.insert(std::string{name}); + } else if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) { + name_guesses.insert(std::string{name}); + } else if (alias.mode == FlagAliasMode::kFlagPrefix) { + if (!android::base::ConsumeSuffix(&name, "=")) { + continue; + } + name_guesses.insert(std::string{name}); + } + } + bool found_alias = false; + for (const auto& name : name_guesses) { + bool has_bool_aliases = + HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) && + HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) && + HasAlias({FlagAliasMode::kFlagExact, "-" + name}) && + HasAlias({FlagAliasMode::kFlagExact, "--" + name}) && + HasAlias({FlagAliasMode::kFlagExact, "-no" + name}) && + HasAlias({FlagAliasMode::kFlagExact, "--no" + name}); + bool has_other_aliases = + HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) && + HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) && + HasAlias({FlagAliasMode::kFlagConsumesFollowing, "-" + name}) && + HasAlias({FlagAliasMode::kFlagConsumesFollowing, "--" + name}); + bool has_help_aliases = HasAlias({FlagAliasMode::kFlagExact, "-help"}) && + HasAlias({FlagAliasMode::kFlagExact, "--help"}); + std::vector has_aliases = {has_bool_aliases, has_other_aliases, + has_help_aliases}; + const auto true_count = + std::count(has_aliases.cbegin(), has_aliases.cend(), true); + if (true_count > 1) { + LOG(ERROR) << "Expected exactly one of has_bool_aliases, " + << "has_other_aliases, and has_help_aliases, got " + << true_count << " for \"" << name << "\"."; + return false; + } + if (true_count == 0) { + continue; + } + found_alias = true; + std::string type_str = + (has_bool_aliases || has_help_aliases) ? "bool" : "string"; + // Lifted from external/gflags/src/gflags_reporting.cc:DescribeOneFlagInXML + out << "\n"; + out << " file.cc\n"; + out << " " << XmlEscape(name) << "\n"; + auto help = help_ ? XmlEscape(*help_) : std::string{""}; + out << " " << help << "\n"; + auto value = getter_ ? XmlEscape((*getter_)()) : std::string{""}; + out << " " << value << "\n"; + out << " " << value << "\n"; + out << " " << type_str << "\n"; + out << "\n"; + } + return found_alias; +} + +std::ostream& operator<<(std::ostream& out, const Flag& flag) { + out << "["; + for (auto it = flag.aliases_.begin(); it != flag.aliases_.end(); it++) { + if (it != flag.aliases_.begin()) { + out << ", "; + } + out << *it; + } + out << "]\n"; + if (flag.help_) { + out << "(" << *flag.help_ << ")\n"; + } + if (flag.getter_) { + out << "(Current value: \"" << (*flag.getter_)() << "\")\n"; + } + return out; +} + +std::vector ArgsToVec(int argc, char** argv) { + std::vector args; + args.reserve(argc); + for (int i = 0; i < argc; i++) { + args.push_back(argv[i]); + } + return args; +} + +struct Separated { + std::vector args_before_mark; + std::vector args_after_mark; +}; +static Separated SeparateByEndOfOptionMark(std::vector args) { + std::vector args_before_mark; + std::vector args_after_mark; + + auto itr = std::find(args.begin(), args.end(), "--"); + bool has_mark = (itr != args.end()); + if (!has_mark) { + args_before_mark = std::move(args); + } else { + args_before_mark.insert(args_before_mark.end(), args.begin(), itr); + args_after_mark.insert(args_after_mark.end(), itr + 1, args.end()); + } + + return Separated{ + .args_before_mark = std::move(args_before_mark), + .args_after_mark = std::move(args_after_mark), + }; +} + +static Result ConsumeFlagsImpl(const std::vector& flags, + std::vector& args) { + for (const auto& flag : flags) { + CF_EXPECT(flag.Parse(args)); + } + return {}; +} + +static Result ConsumeFlagsImpl(const std::vector& flags, + std::vector&& args) { + for (const auto& flag : flags) { + CF_EXPECT(flag.Parse(args)); + } + return {}; +} + +Result ConsumeFlags(const std::vector& flags, + std::vector& args, + const bool recognize_end_of_option_mark) { + if (!recognize_end_of_option_mark) { + CF_EXPECT(ConsumeFlagsImpl(flags, args)); + return {}; + } + auto separated = SeparateByEndOfOptionMark(std::move(args)); + args.clear(); + auto result = ConsumeFlagsImpl(flags, separated.args_before_mark); + args = std::move(separated.args_before_mark); + args.insert(args.end(), + std::make_move_iterator(separated.args_after_mark.begin()), + std::make_move_iterator(separated.args_after_mark.end())); + CF_EXPECT(std::move(result)); + return {}; +} + +Result ConsumeFlags(const std::vector& flags, + std::vector&& args, + const bool recognize_end_of_option_mark) { + if (!recognize_end_of_option_mark) { + CF_EXPECT(ConsumeFlagsImpl(flags, std::move(args))); + return {}; + } + auto separated = SeparateByEndOfOptionMark(std::move(args)); + CF_EXPECT(ConsumeFlagsImpl(flags, std::move(separated.args_before_mark))); + return {}; +} + +bool WriteGflagsCompatXml(const std::vector& flags, std::ostream& out) { + for (const auto& flag : flags) { + if (!flag.WriteGflagsCompatXml(out)) { + return false; + } + } + return true; +} + +Flag VerbosityFlag(android::base::LogSeverity& value) { + return GflagsCompatFlag("verbosity") + .Getter([&value]() { return FromSeverity(value); }) + .Setter([&value](const FlagMatch& match) -> Result { + value = CF_EXPECT(ToSeverity(match.value)); + return {}; + }) + .Help("Used to set the verbosity level for logging."); +} + +Flag HelpFlag(const std::vector& flags, std::string text) { + auto setter = [&flags, text](FlagMatch) -> Result { + if (text.size() > 0) { + LOG(INFO) << text; + } + for (const auto& flag : flags) { + LOG(INFO) << flag; + } + return CF_ERR("user requested early exit"); + }; + return Flag() + .Alias({FlagAliasMode::kFlagExact, "-help"}) + .Alias({FlagAliasMode::kFlagExact, "--help"}) + .Setter(setter); +} + +static Result GflagsCompatBoolFlagSetter(const std::string& name, + bool& value, + const FlagMatch& match) { + const auto& key = match.key; + if (key == "-" + name || key == "--" + name) { + value = true; + return {}; + } else if (key == "-no" + name || key == "--no" + name) { + value = false; + return {}; + } else if (key == "-" + name + "=" || key == "--" + name + "=") { + if (match.value == "true") { + value = true; + return {}; + } else if (match.value == "false") { + value = false; + return {}; + } else { + return CF_ERRF("Unexpected boolean value \"{}\" for \{}\"", match.value, + name); + } + } + return CF_ERRF("Unexpected key \"{}\" for \"{}\"", match.key, name); +} + +static Flag GflagsCompatBoolFlagBase(const std::string& name) { + return Flag() + .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) + .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) + .Alias({FlagAliasMode::kFlagExact, "-" + name}) + .Alias({FlagAliasMode::kFlagExact, "--" + name}) + .Alias({FlagAliasMode::kFlagExact, "-no" + name}) + .Alias({FlagAliasMode::kFlagExact, "--no" + name}); +} + +Flag HelpXmlFlag(const std::vector& flags, std::ostream& out, bool& value, + std::string text) { + const std::string name = "helpxml"; + auto setter = [name, &out, &value, text, + &flags](const FlagMatch& match) -> Result { + bool print_xml = false; + CF_EXPECT(GflagsCompatBoolFlagSetter(name, print_xml, match)); + if (!print_xml) { + return {}; + } + if (!text.empty()) { + out << text << std::endl; + } + value = print_xml; + out << "" << std::endl << "" << std::endl; + WriteGflagsCompatXml(flags, out); + out << "" << std::flush; + return CF_ERR("Requested early exit"); + }; + return GflagsCompatBoolFlagBase(name).Setter(setter); +} + +Flag InvalidFlagGuard() { + return Flag() + .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, "-"}) + .Help( + "This executable only supports the flags in `-help`. Positional " + "arguments may be supported.") + .Setter([](const FlagMatch& match) -> Result { + return CF_ERRF("Unknown flag \"{}\"", match.value); + }); +} + +Flag UnexpectedArgumentGuard() { + return Flag() + .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, ""}) + .Help( + "This executable only supports the flags in `-help`. Positional " + "arguments are not supported.") + .Setter([](const FlagMatch& match) -> Result { + return CF_ERRF("Unexpected argument \"{}\"", match.value); + }); +} + +Flag GflagsCompatFlag(const std::string& name) { + return Flag() + .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) + .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) + .Alias({FlagAliasMode::kFlagConsumesFollowing, "-" + name}) + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--" + name}); +}; + +Flag GflagsCompatFlag(const std::string& name, std::string& value) { + return GflagsCompatFlag(name) + .Getter([&value]() { return value; }) + .Setter([&value](const FlagMatch& match) -> Result { + value = match.value; + return {}; + }); +} + +template +std::optional ParseInteger(const std::string& value) { + if (value.size() == 0) { + return {}; + } + const char* base = value.c_str(); + char* end = nullptr; + errno = 0; + auto r = strtoll(base, &end, /* auto-detect */ 0); + if (errno != 0 || end != base + value.size()) { + return {}; + } + if (static_cast(r) != r) { + return {}; + } + return r; +} + +template +static Flag GflagsCompatNumericFlagGeneric(const std::string& name, T& value) { + return GflagsCompatFlag(name) + .Getter([&value]() { return std::to_string(value); }) + .Setter([&value](const FlagMatch& match) -> Result { + value = CF_EXPECTF(ParseInteger(match.value), + "Failed to parse \"{}\" as an integer", match.value); + return {}; + }); +} + +Flag GflagsCompatFlag(const std::string& name, int32_t& value) { + return GflagsCompatNumericFlagGeneric(name, value); +} + +Flag GflagsCompatFlag(const std::string& name, bool& value) { + return GflagsCompatBoolFlagBase(name) + .Getter([&value]() { return fmt::format("{}", value); }) + .Setter([name, &value](const FlagMatch& match) { + return GflagsCompatBoolFlagSetter(name, value, match); + }); +}; + +Flag GflagsCompatFlag(const std::string& name, + std::vector& value) { + return GflagsCompatFlag(name) + .Getter([&value]() { return android::base::Join(value, ','); }) + .Setter([&value](const FlagMatch& match) -> Result { + if (match.value.empty()) { + value.clear(); + return {}; + } + std::vector str_vals = + android::base::Split(match.value, ","); + value = std::move(str_vals); + return {}; + }); +} + +Flag GflagsCompatFlag(const std::string& name, std::vector& value, + const bool def_val) { + return GflagsCompatFlag(name) + .Getter([&value]() { return fmt::format("{}", fmt::join(value, ",")); }) + .Setter([&name, &value, def_val](const FlagMatch& match) -> Result { + if (match.value.empty()) { + value.clear(); + return {}; + } + std::vector str_vals = + android::base::Split(match.value, ","); + value.clear(); + std::vector output_vals; + output_vals.reserve(str_vals.size()); + for (const auto& str_val : str_vals) { + if (str_val.empty()) { + output_vals.push_back(def_val); + } else { + output_vals.push_back(CF_EXPECT(ParseBool(str_val, name))); + } + } + value = output_vals; + return {}; + }); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/flag_parser.h b/base/cvd/cuttlefish/common/libs/utils/flag_parser.h new file mode 100644 index 0000000000..b47b6f97cb --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/flag_parser.h @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2021 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 + +#include +#include +#include +#include +#include +#include + +#include "common/libs/utils/result.h" + +/* Support for parsing individual flags out of a larger list of flags. This + * supports externally determining the order that flags are evaluated in, and + * incrementally integrating with existing flag parsing implementations. + * + * Start with Flag() or one of the GflagsCompatFlag(...) functions to create new + * flags. These flags should be aggregated through the application through some + * other mechanism and then evaluated individually with Flag::Parse or together + * with ConsumeFlags on arguments. */ + +namespace cuttlefish { + +/* The matching behavior used with the name in `FlagAlias::name`. */ +enum class FlagAliasMode { + /* Match arguments of the form ``. In practice, usually + * looks like "-flag=" or "--flag=", where the "-" and "=" are included in + * parsing. */ + kFlagPrefix, + /* Match arguments of the form ``. In practice, will look like + * "-flag" or "--flag". */ + kFlagExact, + /* Match a pair of arguments of the form `` ``. In practice, + * will look like "-flag" or "--flag". */ + kFlagConsumesFollowing, + /* Match a sequence of arguments of the form `` `` ``. + * This uses heuristics to try to determine when `` is actually another + * flag. */ + kFlagConsumesArbitrary, +}; + +/* A single matching rule for a `Flag`. One `Flag` can have multiple rules. */ +struct FlagAlias { + FlagAliasMode mode; + std::string name; +}; + +std::ostream& operator<<(std::ostream&, const FlagAlias&); + +/* A successful match in an argument list from a `FlagAlias` inside a `Flag`. + * The `key` value corresponds to `FlagAlias::name`. For a match of + * `FlagAliasMode::kFlagExact`, `key` and `value` will both be the `name`. */ +struct FlagMatch { + std::string key; + std::string value; +}; + +class Flag { + public: + /* Add an alias that triggers matches and calls to the `Setter` function. */ + Flag& Alias(const FlagAlias& alias) &; + Flag Alias(const FlagAlias& alias) &&; + /* Set help text, visible in the class ostream writer method. Optional. */ + Flag& Help(const std::string&) &; + Flag Help(const std::string&) &&; + /* Set a loader that displays the current value in help text. Optional. */ + Flag& Getter(std::function) &; + Flag Getter(std::function) &&; + /* Set the callback for matches. The callback may be invoked multiple times. + */ + Flag& Setter(std::function(const FlagMatch&)>) &; + Flag Setter(std::function(const FlagMatch&)>) &&; + + /* Examines a list of arguments, removing any matches from the list and + * invoking the `Setter` for every match. Returns `false` if the callback ever + * returns `false`. Non-matches are left in place. */ + Result Parse(std::vector& flags) const; + Result Parse(std::vector&& flags) const; + + /* Write gflags `--helpxml` style output for a string-type flag. */ + bool WriteGflagsCompatXml(std::ostream&) const; + + private: + /* Reports whether `Process` wants to consume zero, one, or two arguments. */ + enum class FlagProcessResult { + /* Error in handling a flag, exit flag handling with an error result. */ + kFlagSkip, /* Flag skipped; consume no arguments. */ + kFlagConsumed, /* Flag processed; consume one argument. */ + kFlagConsumedWithFollowing, /* Flag processed; consume 2 arguments. */ + kFlagConsumedOnlyFollowing, /* Flag processed; consume next argument. */ + }; + + void ValidateAlias(const FlagAlias& alias); + Flag& UnvalidatedAlias(const FlagAlias& alias) &; + Flag UnvalidatedAlias(const FlagAlias& alias) &&; + + /* Attempt to match a single argument. */ + Result Process( + const std::string& argument, + const std::optional& next_arg) const; + + bool HasAlias(const FlagAlias&) const; + + friend std::ostream& operator<<(std::ostream&, const Flag&); + friend Flag InvalidFlagGuard(); + friend Flag UnexpectedArgumentGuard(); + + std::vector aliases_; + std::optional help_; + std::optional> getter_; + std::optional(const FlagMatch&)>> setter_; +}; + +std::ostream& operator<<(std::ostream&, const Flag&); + +std::vector ArgsToVec(int argc, char** argv); + +Result ParseBool(const std::string& value, const std::string& name); + +/* Handles a list of flags. Flags are matched in the order given in case two + * flags match the same argument. Matched flags are removed, leaving only + * unmatched arguments. */ +Result ConsumeFlags(const std::vector& flags, + std::vector& args, + const bool recognize_end_of_option_mark = false); +Result ConsumeFlags(const std::vector& flags, + std::vector&&, + const bool recognize_end_of_option_mark = false); + +bool WriteGflagsCompatXml(const std::vector&, std::ostream&); + +/* If -verbosity or --verbosity flags have a value, translates it to an android + * LogSeverity */ +Flag VerbosityFlag(android::base::LogSeverity& value); + +/* If any of these are used, they should be evaluated after all other flags, and + * in the order defined here (help before invalid flags, invalid flags before + * unexpected arguments). */ + +/* If a "-help" or "--help" flag is present, prints all the flags and fails. */ +Flag HelpFlag(const std::vector& flags, std::string text = ""); + +/* If a "-helpxml" is present, prints all the flags in XML and fails. */ +Flag HelpXmlFlag(const std::vector& flags, std::ostream&, bool& value, + std::string text = ""); + +/* Catches unrecognized arguments that begin with `-`, and errors out. This + * effectively denies unknown flags. */ +Flag InvalidFlagGuard(); +/* Catches any arguments not extracted by other Flag matchers and errors out. + * This effectively denies unknown flags and any positional arguments. */ +Flag UnexpectedArgumentGuard(); + +// Create a flag resembling a gflags argument of the given type. This includes +// "-[-]flag=*",support for all types, "-[-]noflag" support for booleans, and +// "-flag *", "--flag *", support for other types. The value passed in the flag +// is saved to the defined reference. +Flag GflagsCompatFlag(const std::string& name); +Flag GflagsCompatFlag(const std::string& name, std::string& value); +Flag GflagsCompatFlag(const std::string& name, std::int32_t& value); +Flag GflagsCompatFlag(const std::string& name, bool& value); +Flag GflagsCompatFlag(const std::string& name, std::vector& value); +Flag GflagsCompatFlag(const std::string& name, std::vector& value, + const bool default_value); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/flag_parser_test.cpp b/base/cvd/cuttlefish/common/libs/utils/flag_parser_test.cpp new file mode 100644 index 0000000000..f4a83beaa2 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/flag_parser_test.cpp @@ -0,0 +1,460 @@ +/* + * Copyright (C) 2021 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. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "common/libs/utils/result_matchers.h" +#include "gmock/gmock-matchers.h" + +namespace cuttlefish { + +TEST(FlagParser, DuplicateAlias) { + FlagAlias alias = {FlagAliasMode::kFlagExact, "--flag"}; + ASSERT_DEATH({ Flag().Alias(alias).Alias(alias); }, "Duplicate flag alias"); +} + +TEST(FlagParser, ConflictingAlias) { + FlagAlias exact_alias = {FlagAliasMode::kFlagExact, "--flag"}; + FlagAlias following_alias = {FlagAliasMode::kFlagConsumesFollowing, "--flag"}; + ASSERT_DEATH({ Flag().Alias(exact_alias).Alias(following_alias); }, + "Overlapping flag aliases"); +} + +TEST(FlagParser, StringFlag) { + std::string value; + auto flag = GflagsCompatFlag("myflag", value); + ASSERT_THAT(flag.Parse({"-myflag=a"}), IsOk()); + ASSERT_EQ(value, "a"); + ASSERT_THAT(flag.Parse({"--myflag=b"}), IsOk()); + ASSERT_EQ(value, "b"); + ASSERT_THAT(flag.Parse({"-myflag", "c"}), IsOk()); + ASSERT_EQ(value, "c"); + ASSERT_THAT(flag.Parse({"--myflag", "d"}), IsOk()); + ASSERT_EQ(value, "d"); + ASSERT_THAT(flag.Parse({"--myflag="}), IsOk()); + ASSERT_EQ(value, ""); +} + +TEST(FlagParser, NormalizedStringFlag) { + std::string value; + auto flag = GflagsCompatFlag("my_flag", value); + ASSERT_THAT(flag.Parse({"-my-flag=a"}), IsOk()); + ASSERT_EQ(value, "a"); + ASSERT_THAT(flag.Parse({"--my-flag=b"}), IsOk()); + ASSERT_EQ(value, "b"); + ASSERT_THAT(flag.Parse({"-my-flag", "c"}), IsOk()); + ASSERT_EQ(value, "c"); + ASSERT_THAT(flag.Parse({"--my-flag", "d"}), IsOk()); + ASSERT_EQ(value, "d"); + ASSERT_THAT(flag.Parse({"--my-flag="}), IsOk()); + ASSERT_EQ(value, ""); +} + +std::optional> flagXml(const Flag& f) { + std::stringstream xml_stream; + if (!f.WriteGflagsCompatXml(xml_stream)) { + return {}; + } + auto xml = xml_stream.str(); + // Holds all memory for the parsed structure. + std::unique_ptr doc( + xmlReadMemory(xml.c_str(), xml.size(), nullptr, nullptr, 0), xmlFree); + if (!doc) { + return {}; + } + xmlNodePtr root_element = xmlDocGetRootElement(doc.get()); + std::map elements_map; + for (auto elem = root_element->children; elem != nullptr; elem = elem->next) { + if (elem->type != xmlElementType::XML_ELEMENT_NODE) { + continue; + } + elements_map[(char*)elem->name] = ""; + if (elem->children == nullptr) { + continue; + } + if (elem->children->type != XML_TEXT_NODE) { + continue; + } + elements_map[(char*)elem->name] = (char*)elem->children->content; + } + return elements_map; +} + +TEST(FlagParser, GflagsIncompatibleFlag) { + auto flag = Flag().Alias({FlagAliasMode::kFlagExact, "--flag"}); + ASSERT_FALSE(flagXml(flag)); +} + +TEST(FlagParser, StringFlagXml) { + std::string value = "somedefault"; + auto flag = GflagsCompatFlag("myflag", value).Help("somehelp"); + auto xml = flagXml(flag); + ASSERT_TRUE(xml); + ASSERT_NE((*xml)["file"], ""); + ASSERT_EQ((*xml)["name"], "myflag"); + ASSERT_EQ((*xml)["meaning"], "somehelp"); + ASSERT_EQ((*xml)["default"], "somedefault"); + ASSERT_EQ((*xml)["current"], "somedefault"); + ASSERT_EQ((*xml)["type"], "string"); +} + +TEST(FlagParser, RepeatedStringFlag) { + std::string value; + auto flag = GflagsCompatFlag("myflag", value); + ASSERT_THAT(flag.Parse({"-myflag=a", "--myflag", "b"}), IsOk()); + ASSERT_EQ(value, "b"); +} + +TEST(FlagParser, RepeatedListFlag) { + std::vector elems; + auto flag = GflagsCompatFlag("myflag"); + flag.Setter([&elems](const FlagMatch& match) -> Result { + elems.push_back(match.value); + return {}; + }); + ASSERT_THAT(flag.Parse({"-myflag=a", "--myflag", "b"}), IsOk()); + ASSERT_EQ(elems, (std::vector{"a", "b"})); +} + +TEST(FlagParser, FlagRemoval) { + std::string value; + auto flag = GflagsCompatFlag("myflag", value); + std::vector flags = {"-myflag=a", "-otherflag=c"}; + ASSERT_THAT(flag.Parse(flags), IsOk()); + ASSERT_EQ(value, "a"); + ASSERT_EQ(flags, std::vector{"-otherflag=c"}); + flags = {"-otherflag=a", "-myflag=c"}; + ASSERT_THAT(flag.Parse(flags), IsOk()); + ASSERT_EQ(value, "c"); + ASSERT_EQ(flags, std::vector{"-otherflag=a"}); +} + +TEST(FlagParser, IntFlag) { + std::int32_t value = 0; + auto flag = GflagsCompatFlag("myflag", value); + ASSERT_THAT(flag.Parse({"-myflag=5"}), IsOk()); + ASSERT_EQ(value, 5); + ASSERT_THAT(flag.Parse({"--myflag=6"}), IsOk()); + ASSERT_EQ(value, 6); + ASSERT_THAT(flag.Parse({"-myflag", "7"}), IsOk()); + ASSERT_EQ(value, 7); + ASSERT_THAT(flag.Parse({"--myflag", "8"}), IsOk()); + ASSERT_EQ(value, 8); +} + +TEST(FlagParser, IntFlagXml) { + int value = 5; + auto flag = GflagsCompatFlag("myflag", value).Help("somehelp"); + auto xml = flagXml(flag); + ASSERT_TRUE(xml); + ASSERT_NE((*xml)["file"], ""); + ASSERT_EQ((*xml)["name"], "myflag"); + ASSERT_EQ((*xml)["meaning"], "somehelp"); + ASSERT_EQ((*xml)["default"], "5"); + ASSERT_EQ((*xml)["current"], "5"); + ASSERT_EQ((*xml)["type"], "string"); +} + +TEST(FlagParser, BoolFlag) { + bool value = false; + auto flag = GflagsCompatFlag("myflag", value); + ASSERT_THAT(flag.Parse({"-myflag"}), IsOk()); + ASSERT_TRUE(value); + + value = false; + ASSERT_THAT(flag.Parse({"--myflag"}), IsOk()); + ASSERT_TRUE(value); + + value = false; + ASSERT_THAT(flag.Parse({"-myflag=true"}), IsOk()); + ASSERT_TRUE(value); + + value = false; + ASSERT_THAT(flag.Parse({"--myflag=true"}), IsOk()); + ASSERT_TRUE(value); + + value = true; + ASSERT_THAT(flag.Parse({"-nomyflag"}), IsOk()); + ASSERT_FALSE(value); + + value = true; + ASSERT_THAT(flag.Parse({"--nomyflag"}), IsOk()); + ASSERT_FALSE(value); + + value = true; + ASSERT_THAT(flag.Parse({"-myflag=false"}), IsOk()); + ASSERT_FALSE(value); + + value = true; + ASSERT_THAT(flag.Parse({"--myflag=false"}), IsOk()); + ASSERT_FALSE(value); + + ASSERT_THAT(flag.Parse({"--myflag=nonsense"}), IsError()); +} + +TEST(FlagParser, BoolFlagXml) { + bool value = true; + auto flag = GflagsCompatFlag("myflag", value).Help("somehelp"); + auto xml = flagXml(flag); + ASSERT_TRUE(xml); + ASSERT_NE((*xml)["file"], ""); + ASSERT_EQ((*xml)["name"], "myflag"); + ASSERT_EQ((*xml)["meaning"], "somehelp"); + ASSERT_EQ((*xml)["default"], "true"); + ASSERT_EQ((*xml)["current"], "true"); + ASSERT_EQ((*xml)["type"], "bool"); +} + +TEST(FlagParser, StringIntFlag) { + std::int32_t int_value = 0; + std::string string_value; + auto int_flag = GflagsCompatFlag("int", int_value); + auto string_flag = GflagsCompatFlag("string", string_value); + std::vector flags = {int_flag, string_flag}; + EXPECT_THAT(ConsumeFlags(flags, {"-int=5", "-string=a"}), IsOk()); + ASSERT_EQ(int_value, 5); + ASSERT_EQ(string_value, "a"); + EXPECT_THAT(ConsumeFlags(flags, {"--int=6", "--string=b"}), IsOk()); + ASSERT_EQ(int_value, 6); + ASSERT_EQ(string_value, "b"); + EXPECT_THAT(ConsumeFlags(flags, {"-int", "7", "-string", "c"}), IsOk()); + ASSERT_EQ(int_value, 7); + ASSERT_EQ(string_value, "c"); + EXPECT_THAT(ConsumeFlags(flags, {"--int", "8", "--string", "d"}), IsOk()); + ASSERT_EQ(int_value, 8); + ASSERT_EQ(string_value, "d"); +} + +TEST(FlagParser, StringVectorFlag) { + std::vector value; + auto flag = GflagsCompatFlag("myflag", value); + + ASSERT_THAT(flag.Parse({"--myflag="}), IsOk()); + ASSERT_TRUE(value.empty()); + + ASSERT_THAT(flag.Parse({"--myflag=foo"}), IsOk()); + ASSERT_EQ(value, std::vector({"foo"})); + + ASSERT_THAT(flag.Parse({"--myflag=foo,bar"}), IsOk()); + ASSERT_EQ(value, std::vector({"foo", "bar"})); + + ASSERT_THAT(flag.Parse({"--myflag=,bar"}), IsOk()); + ASSERT_EQ(value, std::vector({"", "bar"})); + + ASSERT_THAT(flag.Parse({"--myflag=foo,"}), IsOk()); + ASSERT_EQ(value, std::vector({"foo", ""})); + + ASSERT_THAT(flag.Parse({"--myflag=,"}), IsOk()); + ASSERT_EQ(value, std::vector({"", ""})); +} + +TEST(FlagParser, BoolVectorFlag) { + std::vector value; + bool default_value = true; + auto flag = GflagsCompatFlag("myflag", value, default_value); + + ASSERT_THAT(flag.Parse({"--myflag="}), IsOk()); + ASSERT_TRUE(value.empty()); + + ASSERT_THAT(flag.Parse({"--myflag=foo"}), IsError()); + ASSERT_TRUE(value.empty()); + + ASSERT_THAT(flag.Parse({"--myflag=true,bar"}), IsError()); + ASSERT_TRUE(value.empty()); + + ASSERT_THAT(flag.Parse({"--myflag=true"}), IsOk()); + ASSERT_EQ(value, std::vector({true})); + ASSERT_TRUE(flagXml(flag)); + ASSERT_EQ((*flagXml(flag))["default"], "true"); + + ASSERT_THAT(flag.Parse({"--myflag=true,false"}), IsOk()); + ASSERT_EQ(value, std::vector({true, false})); + ASSERT_TRUE(flagXml(flag)); + ASSERT_EQ((*flagXml(flag))["default"], "true,false"); + + ASSERT_THAT(flag.Parse({"--myflag=,false"}), IsOk()); + ASSERT_EQ(value, std::vector({true, false})); + ASSERT_TRUE(flagXml(flag)); + ASSERT_EQ((*flagXml(flag))["default"], "true,false"); + + ASSERT_THAT(flag.Parse({"--myflag=true,"}), IsOk()); + ASSERT_EQ(value, std::vector({true, true})); + ASSERT_TRUE(flagXml(flag)); + ASSERT_EQ((*flagXml(flag))["default"], "true,true"); + + ASSERT_THAT(flag.Parse({"--myflag=,"}), IsOk()); + ASSERT_EQ(value, std::vector({true, true})); + ASSERT_TRUE(flagXml(flag)); + ASSERT_EQ((*flagXml(flag))["default"], "true,true"); +} + +TEST(FlagParser, InvalidStringFlag) { + std::string value; + auto flag = GflagsCompatFlag("myflag", value); + ASSERT_THAT(flag.Parse({"-myflag"}), IsError()); + ASSERT_THAT(flag.Parse({"--myflag"}), IsError()); +} + +TEST(FlagParser, InvalidIntFlag) { + int value; + auto flag = GflagsCompatFlag("myflag", value); + ASSERT_THAT(flag.Parse({"-myflag"}), IsError()); + ASSERT_THAT(flag.Parse({"--myflag"}), IsError()); + ASSERT_THAT(flag.Parse({"-myflag=abc"}), IsError()); + ASSERT_THAT(flag.Parse({"--myflag=def"}), IsError()); + ASSERT_THAT(flag.Parse({"-myflag", "abc"}), IsError()); + ASSERT_THAT(flag.Parse({"--myflag", "def"}), IsError()); +} + +TEST(FlagParser, VerbosityFlag) { + android::base::LogSeverity value = android::base::VERBOSE; + auto flag = VerbosityFlag(value); + ASSERT_THAT(flag.Parse({"-verbosity=DEBUG"}), IsOk()); + ASSERT_EQ(value, android::base::DEBUG); + ASSERT_THAT(flag.Parse({"--verbosity=INFO"}), IsOk()); + ASSERT_EQ(value, android::base::INFO); + ASSERT_THAT(flag.Parse({"--verbosity=WARNING"}), IsOk()); + ASSERT_EQ(value, android::base::WARNING); + ASSERT_THAT(flag.Parse({"--verbosity=ERROR"}), IsOk()); + ASSERT_EQ(value, android::base::ERROR); + ASSERT_THAT(flag.Parse({"--verbosity=FATAL_WITHOUT_ABORT"}), IsOk()); + ASSERT_EQ(value, android::base::FATAL_WITHOUT_ABORT); + ASSERT_THAT(flag.Parse({"--verbosity=FATAL"}), IsOk()); + ASSERT_EQ(value, android::base::FATAL); + ASSERT_THAT(flag.Parse({"--verbosity=VERBOSE"}), IsOk()); + ASSERT_EQ(value, android::base::VERBOSE); +} + +TEST(FlagParser, InvalidVerbosityFlag) { + android::base::LogSeverity value = android::base::VERBOSE; + auto flag = VerbosityFlag(value); + ASSERT_THAT(flag.Parse({"-verbosity"}), IsError()); + ASSERT_EQ(value, android::base::VERBOSE); + ASSERT_THAT(flag.Parse({"--verbosity"}), IsError()); + ASSERT_EQ(value, android::base::VERBOSE); + ASSERT_THAT(flag.Parse({"-verbosity="}), IsError()); + ASSERT_EQ(value, android::base::VERBOSE); + ASSERT_THAT(flag.Parse({"--verbosity="}), IsError()); + ASSERT_EQ(value, android::base::VERBOSE); + ASSERT_THAT(flag.Parse({"-verbosity=not_a_severity"}), IsError()); + ASSERT_EQ(value, android::base::VERBOSE); + ASSERT_THAT(flag.Parse({"--verbosity=not_a_severity"}), IsError()); + ASSERT_EQ(value, android::base::VERBOSE); + ASSERT_THAT(flag.Parse({"-verbosity", "not_a_severity"}), IsError()); + ASSERT_EQ(value, android::base::VERBOSE); + ASSERT_THAT(flag.Parse({"--verbosity", "not_a_severity"}), IsError()); + ASSERT_EQ(value, android::base::VERBOSE); +} + +TEST(FlagParser, InvalidFlagGuard) { + auto flag = InvalidFlagGuard(); + ASSERT_THAT(flag.Parse({}), IsOk()); + ASSERT_THAT(flag.Parse({"positional"}), IsOk()); + ASSERT_THAT(flag.Parse({"positional", "positional2"}), IsOk()); + ASSERT_THAT(flag.Parse({"-flag"}), IsError()); + ASSERT_THAT(flag.Parse({"-"}), IsError()); +} + +TEST(FlagParser, UnexpectedArgumentGuard) { + auto flag = UnexpectedArgumentGuard(); + ASSERT_THAT(flag.Parse({}), IsOk()); + ASSERT_THAT(flag.Parse({"positional"}), IsError()); + ASSERT_THAT(flag.Parse({"positional", "positional2"}), IsError()); + ASSERT_THAT(flag.Parse({"-flag"}), IsError()); + ASSERT_THAT(flag.Parse({"-"}), IsError()); +} + +TEST(FlagParser, EndOfOptionMark) { + std::vector args{"-flag", "--", "-invalid_flag"}; + bool flag = false; + std::vector flags{GflagsCompatFlag("flag", flag), InvalidFlagGuard()}; + + EXPECT_THAT(ConsumeFlags(flags, args), IsError()); + EXPECT_THAT(ConsumeFlags(flags, args, + /* recognize_end_of_option_mark */ true), + IsOk()); + ASSERT_TRUE(flag); +} + +class FlagConsumesArbitraryTest : public ::testing::Test { + protected: + void SetUp() override { + elems_.clear(); + flag_ = Flag() + .Alias({FlagAliasMode::kFlagConsumesArbitrary, "--flag"}) + .Setter([this](const FlagMatch& match) -> Result { + elems_.push_back(match.value); + return {}; + }); + } + Flag flag_; + std::vector elems_; +}; + +TEST_F(FlagConsumesArbitraryTest, NoValues) { + std::vector inputs = {"--flag"}; + ASSERT_THAT(flag_.Parse(inputs), IsOk()); + ASSERT_EQ(inputs, (std::vector{})); + ASSERT_EQ(elems_, (std::vector{""})); +} + +TEST_F(FlagConsumesArbitraryTest, OneValue) { + std::vector inputs = {"--flag", "value"}; + ASSERT_THAT(flag_.Parse(inputs), IsOk()); + ASSERT_EQ(inputs, (std::vector{})); + ASSERT_EQ(elems_, (std::vector{"value", ""})); +} + +TEST_F(FlagConsumesArbitraryTest, TwoValues) { + std::vector inputs = {"--flag", "value1", "value2"}; + ASSERT_THAT(flag_.Parse(inputs), IsOk()); + ASSERT_EQ(inputs, (std::vector{})); + ASSERT_EQ(elems_, (std::vector{"value1", "value2", ""})); +} + +TEST_F(FlagConsumesArbitraryTest, NoValuesOtherFlag) { + std::vector inputs = {"--flag", "--otherflag"}; + ASSERT_THAT(flag_.Parse(inputs), IsOk()); + ASSERT_EQ(inputs, (std::vector{"--otherflag"})); + ASSERT_EQ(elems_, (std::vector{""})); +} + +TEST_F(FlagConsumesArbitraryTest, OneValueOtherFlag) { + std::vector inputs = {"--flag", "value", "--otherflag"}; + ASSERT_THAT(flag_.Parse(inputs), IsOk()); + ASSERT_EQ(inputs, (std::vector{"--otherflag"})); + ASSERT_EQ(elems_, (std::vector{"value", ""})); +} + +TEST_F(FlagConsumesArbitraryTest, TwoValuesOtherFlag) { + std::vector inputs = {"--flag", "v1", "v2", "--otherflag"}; + ASSERT_THAT(flag_.Parse(inputs), IsOk()); + ASSERT_EQ(inputs, (std::vector{"--otherflag"})); + ASSERT_EQ(elems_, (std::vector{"v1", "v2", ""})); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/flags_validator.cpp b/base/cvd/cuttlefish/common/libs/utils/flags_validator.cpp new file mode 100644 index 0000000000..89f9097f4c --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/flags_validator.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 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. + */ + +#include + +#include "common/libs/utils/flags_validator.h" + +namespace cuttlefish { +Result ValidateSetupWizardMode(const std::string& setupwizard_mode) { + // One of DISABLED,OPTIONAL,REQUIRED + bool result = setupwizard_mode == "DISABLED" || + setupwizard_mode == "OPTIONAL" || + setupwizard_mode == "REQUIRED"; + + CF_EXPECT(result == true, "Invalid value for setupwizard_mode config"); + return {}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/flags_validator.h b/base/cvd/cuttlefish/common/libs/utils/flags_validator.h new file mode 100644 index 0000000000..1d188f1957 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/flags_validator.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 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 + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +Result ValidateSetupWizardMode(const std::string& setupwizard_mode); +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/inotify.cpp b/base/cvd/cuttlefish/common/libs/utils/inotify.cpp new file mode 100644 index 0000000000..345216f935 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/inotify.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 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. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "inotify.h" + +namespace cuttlefish { + +std::vector GetCreatedFileListFromInotifyFd(int fd) { + return GetFileListFromInotifyFd(fd, IN_CREATE); +} + +#define INOTIFY_MAX_EVENT_SIZE (sizeof(struct inotify_event) + NAME_MAX + 1) + +std::vector GetFileListFromInotifyFd(int fd, uint32_t mask) { + char event_readout[INOTIFY_MAX_EVENT_SIZE]; + int bytes_parsed = 0; + std::vector result; + // Each successful read can contain one or more of inotify_event events + // Note: read() on inotify returns 'whole' events, will never partially + // populate the buffer. + int event_read_out_length = read(fd, event_readout, INOTIFY_MAX_EVENT_SIZE); + + if (event_read_out_length == -1) { + LOG(ERROR) << __FUNCTION__ + << ": Couldn't read out inotify event due to error: '" + << strerror(errno) << "' (" << errno << ")"; + return std::vector(); + } + + while (bytes_parsed < event_read_out_length) { + struct inotify_event* event = + reinterpret_cast(event_readout + bytes_parsed); + bytes_parsed += sizeof(struct inotify_event) + event->len; + + // No file name was present + if (event->len == 0) { + LOG(ERROR) << __FUNCTION__ << ": inotify event didn't contain filename"; + continue; + } + if (!(event->mask & mask)) { + LOG(ERROR) << __FUNCTION__ + << ": inotify event didn't pertain to the event"; + continue; + } + result.push_back(std::string(event->name)); + } + + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/inotify.h b/base/cvd/cuttlefish/common/libs/utils/inotify.h new file mode 100644 index 0000000000..c9a48acad9 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/inotify.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +namespace cuttlefish { + +std::vector GetCreatedFileListFromInotifyFd(int fd); +std::vector GetFileListFromInotifyFd(int fd, uint32_t mask); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/json.cpp b/base/cvd/cuttlefish/common/libs/utils/json.cpp new file mode 100644 index 0000000000..ba6389e242 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/json.cpp @@ -0,0 +1,55 @@ +// +// Copyright (C) 2022 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. + +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/json.h" + +namespace cuttlefish { + +Result ParseJson(std::string_view input) { + Json::Value root; + JSONCPP_STRING err; + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + auto begin = input.data(); + auto end = begin + input.length(); + CF_EXPECT(reader->parse(begin, end, &root, &err), err); + return root; +} + +Result LoadFromFile(SharedFD json_fd) { + CF_EXPECT(json_fd->IsOpen(), "json_fd is not open."); + std::string json_contents; + // on success, this must return a positive integer + CF_EXPECT_GE(ReadAll(json_fd, &json_contents), 0, + "ReadAll() failed and returned 0 or -1"); + Json::Value json_value = CF_EXPECTF( + ParseJson(json_contents), "Failed to parse json: \n{}", json_contents); + return json_value; +} + +Result LoadFromFile(const std::string& path_to_file) { + SharedFD json_fd = SharedFD::Open(path_to_file, O_RDONLY); + auto json_value = + CF_EXPECTF(LoadFromFile(json_fd), "Failed to open {}", path_to_file); + return json_value; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/json.h b/base/cvd/cuttlefish/common/libs/utils/json.h new file mode 100644 index 0000000000..bd5cf58825 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/json.h @@ -0,0 +1,86 @@ +// +// Copyright (C) 2022 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 + +#include +#include +#include + +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +Result ParseJson(std::string_view input); + +Result LoadFromFile(SharedFD json_fd); +Result LoadFromFile(const std::string& path_to_file); + +template +T As(const Json::Value& v); + +template <> +inline int As(const Json::Value& v) { + return v.asInt(); +} + +template <> +inline std::string As(const Json::Value& v) { + return v.asString(); +} + +template <> +inline bool As(const Json::Value& v) { + return v.asBool(); +} + +template +Result GetValue(const Json::Value& root, + const std::vector& selectors) { + const Json::Value* traversal = &root; + for (const auto& selector : selectors) { + CF_EXPECTF(traversal->isMember(selector), + "JSON selector \"{}\" does not exist", selector); + traversal = &(*traversal)[selector]; + } + return As(*traversal); +} + +template +Result> GetArrayValues( + const Json::Value& array, const std::vector& selectors) { + std::vector result; + for (const auto& element : array) { + result.emplace_back(CF_EXPECT(GetValue(element, selectors))); + } + return result; +} + +inline bool HasValue(const Json::Value& root, + const std::vector& selectors) { + const Json::Value* traversal = &root; + for (const auto& selector : selectors) { + if (!traversal->isMember(selector)) { + return false; + } + traversal = &(*traversal)[selector]; + } + return true; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/network.cpp b/base/cvd/cuttlefish/common/libs/utils/network.cpp new file mode 100644 index 0000000000..9c2a751666 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/network.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2019 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. + */ + +#include "common/libs/utils/network.h" + +#ifdef __linux__ +#include +// Kernel headers don't mix well with userspace headers, but there is no +// userspace header that provides the if_tun.h #defines. Include the kernel +// header, but move conflicting definitions out of the way using macros. +#define ethhdr __kernel_ethhdr +#include +#undef ethhdr +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common/libs/utils/subprocess.h" + +namespace cuttlefish { +namespace { + +#ifdef __linux__ +// This should be the size of virtio_net_hdr_v1, from linux/virtio_net.h, but +// the version of that header that ships with android in Pie does not include +// that struct (it was added in Q). +// This is what that struct looks like: +// struct virtio_net_hdr_v1 { +// u8 flags; +// u8 gso_type; +// u16 hdr_len; +// u16 gso_size; +// u16 csum_start; +// u16 csum_offset; +// u16 num_buffers; +// }; +static constexpr int SIZE_OF_VIRTIO_NET_HDR_V1 = 12; +#endif + +/** + * Generate mac address following: + * 00:1a:11:e0:cf:index + * ________ __ ______ + * | | | + * | type (e0, e1, etc) +*/ +void GenerateMacForInstance(int index, uint8_t type, std::uint8_t out[6]) { + // the first octet must be even + out[0] = 0x00; + out[1] = 0x1a; + out[2] = 0x11; + out[3] = type; + out[4] = 0xcf; + out[5] = static_cast(index); +} + +} // namespace + +bool NetworkInterfaceExists(const std::string& interface_name) { + struct ifaddrs *ifa_list{}, *ifa{}; + bool ret = false; + getifaddrs(&ifa_list); + for (ifa = ifa_list; ifa; ifa = ifa->ifa_next) { + if (strcmp(ifa->ifa_name, interface_name.c_str()) == 0) { + ret = true; + break; + } + } + freeifaddrs(ifa_list); + return ret; +} + +#ifdef __linux__ +SharedFD OpenTapInterface(const std::string& interface_name) { + constexpr auto TUNTAP_DEV = "/dev/net/tun"; + + auto tap_fd = SharedFD::Open(TUNTAP_DEV, O_RDWR | O_NONBLOCK); + if (!tap_fd->IsOpen()) { + LOG(ERROR) << "Unable to open tun device: " << tap_fd->StrError(); + return tap_fd; + } + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; + strncpy(ifr.ifr_name, interface_name.c_str(), IFNAMSIZ); + + int err = tap_fd->Ioctl(TUNSETIFF, &ifr); + if (err < 0) { + LOG(ERROR) << "Unable to connect to " << interface_name + << " tap interface: " << tap_fd->StrError(); + tap_fd->Close(); + return SharedFD(); + } + + // The interface's configuration may have been modified or just not set + // correctly on creation. While qemu checks this and enforces the right + // configuration, crosvm does not, so it needs to be set before it's passed to + // it. + tap_fd->Ioctl(TUNSETOFFLOAD, + reinterpret_cast(TUN_F_CSUM | TUN_F_UFO | TUN_F_TSO4 | + TUN_F_TSO6)); + int len = SIZE_OF_VIRTIO_NET_HDR_V1; + tap_fd->Ioctl(TUNSETVNETHDRSZ, &len); + return tap_fd; +} + +std::set TapInterfacesInUse() { + Command cmd("/bin/bash"); + cmd.AddParameter("-c"); + cmd.AddParameter("egrep -h -e \"^iff:.*\" /proc/*/fdinfo/*"); + std::string stdin_str, stdout_str, stderr_str; + RunWithManagedStdio(std::move(cmd), &stdin_str, &stdout_str, &stderr_str); + auto lines = android::base::Split(stdout_str, "\n"); + std::set tap_interfaces; + for (const auto& line : lines) { + if (line == "") { + continue; + } + if (!android::base::StartsWith(line, "iff:\t")) { + LOG(ERROR) << "Unexpected line \"" << line << "\""; + continue; + } + tap_interfaces.insert(line.substr(std::string("iff:\t").size())); + } + return tap_interfaces; +} +#endif + +std::string MacAddressToString(const std::uint8_t mac[6]) { + std::vector mac_vec(mac, mac + 6); + return fmt::format("{:0>2x}", fmt::join(mac_vec, ":")); +} + +std::string Ipv6ToString(const std::uint8_t ip[16]) { + char ipv6_str[INET6_ADDRSTRLEN + 1]; + inet_ntop(AF_INET6, ip, ipv6_str, sizeof(ipv6_str)); + return std::string(ipv6_str); +} + +void GenerateMobileMacForInstance(int index, std::uint8_t out[6]) { + GenerateMacForInstance(index, 0xe0, out); +} + +void GenerateEthMacForInstance(int index, std::uint8_t out[6]) { + GenerateMacForInstance(index, 0xe1, out); +} + +void GenerateWifiMacForInstance(int index, std::uint8_t out[6]) { + GenerateMacForInstance(index, 0xe2, out); +} + +/** + * Linux uses mac to generate link-local IPv6 address following: + * + * 1. Get mac address (for example 00:1a:11:ee:cf:01) + * 2. Throw ff:fe as a 3th and 4th octets (00:1a:11 :ff:fe: ee:cf:01) + * 3. Flip 2th bit in the first octet (02: 1a:11:ff:fe:ee:cf:01) + * 4. Use IPv6 format (021a:11ff:feee:cf01) + * 5. Add prefix fe80:: (fe80::021a:11ff:feee:cf01 or fe80:0000:0000:0000:021a:11ff:feee:cf00) +*/ +void GenerateCorrespondingIpv6ForMac(const std::uint8_t mac[6], std::uint8_t out[16]) { + out[0] = 0xfe; + out[1] = 0x80; + + // 2 - 7 octets are zero + + // need to invert 2th bit of the first octet + out[8] = mac[0] ^ (1 << 1); + out[9] = mac[1]; + + out[10] = mac[2]; + out[11] = 0xff; + + out[12] = 0xfe; + out[13] = mac[3]; + + out[14] = mac[4]; + out[15] = mac[5]; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/network.h b/base/cvd/cuttlefish/common/libs/utils/network.h new file mode 100644 index 0000000000..228df949a8 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/network.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 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 + +#include +#include +#include + +#include "common/libs/fs/shared_fd.h" + +namespace cuttlefish { +// Check network interface with given name exists, such as cvd-ebr. +bool NetworkInterfaceExists(const std::string& interface_name); + +#ifdef __linux__ +// Creates, or connects to if it already exists, a tap network interface. The +// user needs CAP_NET_ADMIN to create such interfaces or be the owner to connect +// to one. +SharedFD OpenTapInterface(const std::string& interface_name); + +// Returns a list of TAP devices that have open file descriptors +std::set TapInterfacesInUse(); +#endif + +void GenerateCorrespondingIpv6ForMac(const std::uint8_t mac[6], std::uint8_t out[16]); +void GenerateMobileMacForInstance(int index, std::uint8_t out[6]); +void GenerateEthMacForInstance(int index, std::uint8_t out[6]); +void GenerateWifiMacForInstance(int index, std::uint8_t out[6]); + +std::string MacAddressToString(const std::uint8_t mac[6]); +std::string Ipv6ToString(const std::uint8_t ip[16]); +} diff --git a/base/cvd/cuttlefish/common/libs/utils/proc_file_utils.cpp b/base/cvd/cuttlefish/common/libs/utils/proc_file_utils.cpp new file mode 100644 index 0000000000..1a3410c88d --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/proc_file_utils.cpp @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "common/libs/utils/proc_file_utils.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/files.h" + +namespace cuttlefish { + +// sometimes, files under /proc/ owned by a different user +// e.g. /proc//exe +static Result FileOwnerUid(const std::string& file_path) { + struct stat buf; + CF_EXPECT_EQ(::stat(file_path.data(), &buf), 0); + return buf.st_uid; +} + +struct ProcStatusUids { + uid_t real_; + uid_t effective_; + uid_t saved_set_; + uid_t filesystem_; +}; + +// /proc//status has Uid: line +// It normally is separated by a tab or more but that's not guaranteed forever +static Result OwnerUids(const pid_t pid) { + // parse from /proc//status + std::regex uid_pattern(R"(Uid:\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+))"); + std::string status_path = fmt::format("/proc/{}/status", pid); + std::string status_content; + CF_EXPECT(android::base::ReadFileToString(status_path, &status_content)); + std::vector uids; + for (const std::string& line : + android::base::Tokenize(status_content, "\n")) { + std::smatch matches; + if (!std::regex_match(line, matches, uid_pattern)) { + continue; + } + // the line, then 4 uids + CF_EXPECT_EQ(matches.size(), 5, + fmt::format("Error in the Uid line: \"{}\"", line)); + uids.reserve(4); + for (int i = 1; i < 5; i++) { + unsigned uid = 0; + CF_EXPECT(android::base::ParseUint(matches[i], &uid)); + uids.push_back(uid); + } + break; + } + CF_EXPECT(!uids.empty(), "The \"Uid:\" line was not found"); + return ProcStatusUids{ + .real_ = uids.at(0), + .effective_ = uids.at(1), + .saved_set_ = uids.at(2), + .filesystem_ = uids.at(3), + }; +} + +static std::string PidDirPath(const pid_t pid) { + return fmt::format("{}/{}", kProcDir, pid); +} + +/* ReadFile does not work for /proc// + * ReadFile requires the file size to be known in advance, + * which is not the case here. + */ +static Result ReadAll(const std::string& file_path) { + SharedFD fd = SharedFD::Open(file_path, O_RDONLY); + CF_EXPECT(fd->IsOpen()); + // should be good size to read all Envs or Args, + // whichever bigger + const int buf_size = 1024; + std::string output; + ssize_t nread = 0; + do { + std::vector buf(buf_size); + nread = ReadExact(fd, buf.data(), buf_size); + CF_EXPECT(nread >= 0, "ReadExact returns " << nread); + output.append(buf.begin(), buf.end()); + } while (nread > 0); + return output; +} + +/** + * Tokenizes the given string, using '\0' as a delimiter + * + * android::base::Tokenize works mostly except the delimiter can't be '\0'. + * The /proc//environ file has the list of environment variables, delimited + * by '\0'. Needs a dedicated tokenizer. + * + */ +static std::vector TokenizeByNullChar(const std::string& input) { + if (input.empty()) { + return {}; + } + std::vector tokens; + std::string token; + for (int i = 0; i < input.size(); i++) { + if (input.at(i) != '\0') { + token.append(1, input.at(i)); + } else { + if (token.empty()) { + break; + } + tokens.push_back(token); + token.clear(); + } + } + if (!token.empty()) { + tokens.push_back(token); + } + return tokens; +} + +Result> CollectPids(const uid_t uid) { + CF_EXPECT(DirectoryExists(kProcDir)); + auto subdirs = CF_EXPECT(DirectoryContents(kProcDir)); + std::regex pid_dir_pattern("[0-9]+"); + std::vector pids; + for (const auto& subdir : subdirs) { + if (!std::regex_match(subdir, pid_dir_pattern)) { + continue; + } + int pid; + // Shouldn't failed here. If failed, either regex or + // android::base::ParseInt needs serious fixes + CF_EXPECT(android::base::ParseInt(subdir, &pid)); + auto owner_uid_result = OwnerUids(pid); + if (owner_uid_result.ok() && owner_uid_result->real_ == uid) { + pids.push_back(pid); + } + } + return pids; +} + +Result> GetCmdArgs(const pid_t pid) { + std::string cmdline_file_path = PidDirPath(pid) + "/cmdline"; + auto owner = CF_EXPECT(FileOwnerUid(cmdline_file_path)); + CF_EXPECT(getuid() == owner); + std::string contents = CF_EXPECT(ReadAll(cmdline_file_path)); + return TokenizeByNullChar(contents); +} + +Result GetExecutablePath(const pid_t pid) { + std::string exec_target_path; + std::string proc_exe_path = fmt::format("/proc/{}/exe", pid); + CF_EXPECT( + android::base::Readlink(proc_exe_path, std::addressof(exec_target_path)), + proc_exe_path << " Should be a symbolic link but it is not."); + std::string suffix(" (deleted)"); + if (android::base::EndsWith(exec_target_path, suffix)) { + return exec_target_path.substr(0, exec_target_path.size() - suffix.size()); + } + return exec_target_path; +} + +static Result CheckExecNameFromStatus(const std::string& exec_name, + const pid_t pid) { + std::string status_path = fmt::format("/proc/{}/status", pid); + std::string status_content; + CF_EXPECT(android::base::ReadFileToString(status_path, &status_content)); + bool found = false; + for (const std::string& line : + android::base::Tokenize(status_content, "\n")) { + std::string_view line_view(line); + if (!android::base::ConsumePrefix(&line_view, "Name:")) { + continue; + } + auto trimmed_line = android::base::Trim(line_view); + if (trimmed_line == exec_name) { + found = true; + break; + } + } + CF_EXPECTF(found == true, + "\"Name: [name]\" line is not found in the status file: \"{}\"", + status_path); + return {}; +} + +Result> CollectPidsByExecName(const std::string& exec_name, + const uid_t uid) { + CF_EXPECT(cpp_basename(exec_name) == exec_name); + auto input_pids = CF_EXPECT(CollectPids(uid)); + std::vector output_pids; + for (const auto pid : input_pids) { + auto owner_uids_result = OwnerUids(pid); + if (!owner_uids_result.ok() || owner_uids_result->real_ != uid) { + LOG(VERBOSE) << "Process #" << pid << " does not belong to " << uid; + continue; + } + if (CheckExecNameFromStatus(exec_name, pid).ok()) { + output_pids.push_back(pid); + } + } + return output_pids; +} + +Result> CollectPidsByExecPath(const std::string& exec_path, + const uid_t uid) { + auto input_pids = CF_EXPECT(CollectPids(uid)); + std::vector output_pids; + for (const auto pid : input_pids) { + auto pid_exec_path = GetExecutablePath(pid); + if (!pid_exec_path.ok()) { + continue; + } + if (*pid_exec_path == exec_path) { + output_pids.push_back(pid); + } + } + return output_pids; +} + +Result> CollectPidsByArgv0(const std::string& expected_argv0, + const uid_t uid) { + auto input_pids = CF_EXPECT(CollectPids(uid)); + std::vector output_pids; + for (const auto pid : input_pids) { + auto argv_result = GetCmdArgs(pid); + if (!argv_result.ok()) { + continue; + } + if (argv_result->empty()) { + continue; + } + if (argv_result->front() == expected_argv0) { + output_pids.push_back(pid); + } + } + return output_pids; +} + +Result OwnerUid(const pid_t pid) { + // parse from /proc//status + auto uids_result = OwnerUids(pid); + if (!uids_result.ok()) { + LOG(DEBUG) << uids_result.error().Trace(); + LOG(DEBUG) << "Falling back to the old OwnerUid logic"; + return CF_EXPECT(FileOwnerUid(PidDirPath(pid))); + } + return uids_result->real_; +} + +Result> GetEnvs(const pid_t pid) { + std::string environ_file_path = PidDirPath(pid) + "/environ"; + auto owner = CF_EXPECT(FileOwnerUid(environ_file_path)); + CF_EXPECT(getuid() == owner, "Owned by another user of uid" << owner); + std::string environ = CF_EXPECT(ReadAll(environ_file_path)); + std::vector lines = TokenizeByNullChar(environ); + // now, each line looks like: HOME=/home/user + std::unordered_map envs; + for (const auto& line : lines) { + auto pos = line.find_first_of('='); + if (pos == std::string::npos) { + LOG(ERROR) << "Found an invalid env: " << line << " and ignored."; + continue; + } + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 1); + envs[key] = value; + } + return envs; +} + +Result ExtractProcInfo(const pid_t pid) { + auto owners = CF_EXPECT(OwnerUids(pid)); + return ProcInfo{.pid_ = pid, + .real_owner_ = owners.real_, + .effective_owner_ = owners.effective_, + .actual_exec_path_ = CF_EXPECT(GetExecutablePath(pid)), + .envs_ = CF_EXPECT(GetEnvs(pid)), + .args_ = CF_EXPECT(GetCmdArgs(pid))}; +} + +Result Ppid(const pid_t pid) { + // parse from /proc//status + std::regex uid_pattern(R"(PPid:\s*([0-9]+))"); + std::string status_path = fmt::format("/proc/{}/status", pid); + std::string status_content; + CF_EXPECT(android::base::ReadFileToString(status_path, &status_content)); + for (const auto& line : android::base::Tokenize(status_content, "\n")) { + std::smatch matches; + if (!std::regex_match(line, matches, uid_pattern)) { + continue; + } + unsigned ppid; + CF_EXPECT(android::base::ParseUint(matches[1], &ppid)); + return static_cast(ppid); + } + return CF_ERR("Status file does not have PPid: line in the right format"); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/proc_file_utils.h b/base/cvd/cuttlefish/common/libs/utils/proc_file_utils.h new file mode 100644 index 0000000000..e8a2e06032 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/proc_file_utils.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +#include +#include +#include + +#include "common/libs/utils/result.h" + +/** + * @file Utility functions to retrieve information from proc filesystem + * + * As of now, the major consumer is cvd. + */ +namespace cuttlefish { + +static constexpr char kProcDir[] = "/proc"; + +struct ProcInfo { + pid_t pid_; + uid_t real_owner_; + uid_t effective_owner_; + std::string actual_exec_path_; + std::unordered_map envs_; + std::vector args_; +}; +Result ExtractProcInfo(const pid_t pid); + +// collects all pids whose owner is uid +Result> CollectPids(const uid_t uid = getuid()); + +/* collects all pids that meet the following: + * + * 1. Belongs to the uid + * 2. cpp_basename(readlink(/proc//exe)) == exec_name + * + */ +Result> CollectPidsByExecName(const std::string& exec_name, + const uid_t uid = getuid()); + +/* collects all pids that meet the following: + * + * 1. Belongs to the uid + * 2. readlink(/proc//exe) == exec_name + * + */ +Result> CollectPidsByExecPath(const std::string& exec_path, + const uid_t uid = getuid()); + +/** + * When argv[0] != exec_path, collects PIDs based on argv[0] + * + */ +Result> CollectPidsByArgv0(const std::string& expected_argv0, + const uid_t uid = getuid()); + +Result OwnerUid(const pid_t pid); + +// retrieves command line args for the pid +Result> GetCmdArgs(const pid_t pid); + +// retrieves the path to the executable file used for the pid +// this does not work for the defunct processes +Result GetExecutablePath(const pid_t pid); + +// retrieves the environment variables of the process, pid +Result> GetEnvs(const pid_t pid); + +Result Ppid(const pid_t pid); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/proc_file_utils_test.cpp b/base/cvd/cuttlefish/common/libs/utils/proc_file_utils_test.cpp new file mode 100644 index 0000000000..94765ba3df --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/proc_file_utils_test.cpp @@ -0,0 +1,46 @@ +// +// Copyright (C) 2023 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. + +#include +#include + +#include +#include + +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/proc_file_utils.h" + +namespace cuttlefish { + +TEST(ProcFileUid, SelfUidTest) { + auto my_pid = getpid(); + auto login_uid_of_my_pid = OwnerUid(my_pid); + + ASSERT_TRUE(login_uid_of_my_pid.ok()) << login_uid_of_my_pid.error().Trace(); + ASSERT_EQ(getuid(), *login_uid_of_my_pid); +} + +TEST(ProcFilePid, CurrentPidCollected) { + auto pids_result = CollectPids(getuid()); + auto this_pid = getpid(); + + // verify if the pids returned are really owned by getuid() + ASSERT_TRUE(pids_result.ok()); + ASSERT_TRUE(Contains(*pids_result, this_pid)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/proto.h b/base/cvd/cuttlefish/common/libs/utils/proto.h new file mode 100644 index 0000000000..cf47c2e7d5 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/proto.h @@ -0,0 +1,57 @@ +// +// Copyright (C) 2023 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. + +#include +#include + +#include +#include +#include +#include + +/* Format proto messages as textproto with {} or {:t} and json with {:j}. */ +template +struct fmt::formatter< + T, + std::enable_if_t, char>> { + public: + constexpr format_parse_context::iterator parse(format_parse_context& ctx) { + auto it = ctx.begin(); + for (; it != ctx.end() && *it != '}'; it++) { + format_ = *it; + } + return it; + } + format_context::iterator format(const T& proto, format_context& ctx) const { + std::string text; + if (format_ == 't') { + if (!google::protobuf::TextFormat::PrintToString(proto, &text)) { + return fmt::format_to(ctx.out(), "(proto format error"); + } + } else if (format_ == 'j') { + auto result = google::protobuf::util::MessageToJsonString(proto, &text); + if (!result.ok()) { + return fmt::format_to(ctx.out(), "(json error: {})", + std::string(result.ToString())); + } + } else { + return fmt::format_to(ctx.out(), "(unknown format specifier)"); + } + return fmt::format_to(ctx.out(), "{}", text); + } + + private: + char format_ = 't'; +}; diff --git a/base/cvd/cuttlefish/common/libs/utils/result.cpp b/base/cvd/cuttlefish/common/libs/utils/result.cpp new file mode 100644 index 0000000000..2b9914ce0a --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/result.cpp @@ -0,0 +1,258 @@ +// +// Copyright (C) 2022 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. + +#include "common/libs/utils/result.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace cuttlefish { + +StackTraceEntry::StackTraceEntry(std::string file, size_t line, + std::string pretty_function, + std::string function) + : file_(std::move(file)), + line_(line), + pretty_function_(std::move(pretty_function)), + function_(std::move(function)) {} + +StackTraceEntry::StackTraceEntry(std::string file, size_t line, + std::string pretty_function, + std::string function, std::string expression) + : file_(std::move(file)), + line_(line), + pretty_function_(std::move(pretty_function)), + function_(std::move(function)), + expression_(std::move(expression)) {} + +StackTraceEntry::StackTraceEntry(const StackTraceEntry& other) + : file_(other.file_), + line_(other.line_), + pretty_function_(other.pretty_function_), + function_(other.function_), + expression_(other.expression_), + message_(other.message_.str()) {} + +StackTraceEntry& StackTraceEntry::operator=(const StackTraceEntry& other) { + file_ = other.file_; + line_ = other.line_; + pretty_function_ = other.pretty_function_; + function_ = other.function_; + expression_ = other.expression_; + message_.str(other.message_.str()); + return *this; +} + +bool StackTraceEntry::HasMessage() const { return !message_.str().empty(); } + +/* + * Print a single stack trace entry out of a list of format specifiers. + * Some format specifiers [a,c,n] cause changes that affect all lines, while + * the rest amount to printing a single line in the output. This code is + * reused by formatting code for both rendering individual stack trace + * entries, and rendering an entire stack trace with multiple entries. + */ +fmt::format_context::iterator StackTraceEntry::format( + fmt::format_context& ctx, const std::vector& specifiers, + std::optional index) const { + static constexpr char kTerminalBoldRed[] = "\033[0;1;31m"; + static constexpr char kTerminalCyan[] = "\033[0;36m"; + static constexpr char kTerminalRed[] = "\033[0;31m"; + static constexpr char kTerminalReset[] = "\033[0m"; + static constexpr char kTerminalUnderline[] = "\033[0;4m"; + static constexpr char kTerminalYellow[] = "\033[0;33m"; + auto out = ctx.out(); + std::vector filtered_specs; + bool arrow = false; + bool color = false; + bool numbers = false; + for (auto spec : specifiers) { + switch (spec) { + case FormatSpecifier::kArrow: + arrow = true; + continue; + case FormatSpecifier::kColor: + color = true; + continue; + case FormatSpecifier::kLongExpression: + case FormatSpecifier::kShortExpression: + if (expression_.empty()) { + continue; + } + break; + case FormatSpecifier::kMessage: + if (!HasMessage()) { + continue; + } + break; + case FormatSpecifier::kNumbers: + numbers = true; + continue; + default: // fall through + break; + } + filtered_specs.emplace_back(spec); + } + if (filtered_specs.empty()) { + filtered_specs.push_back(FormatSpecifier::kShort); + } + for (size_t i = 0; i < filtered_specs.size(); i++) { + if (index.has_value() && numbers) { + if (color) { + out = fmt::format_to(out, "{}{}{}. ", kTerminalYellow, *index, + kTerminalReset); + } else { + out = fmt::format_to(out, "{}. ", *index); + } + } + if (color) { + out = fmt::format_to(out, "{}", kTerminalRed); + } + if (numbers) { + if (arrow && (int)i < ((int)filtered_specs.size()) - 2) { + out = fmt::format_to(out, "| "); + } else if (arrow && i == filtered_specs.size() - 2) { + out = fmt::format_to(out, "v "); + } + } else { + if (arrow && (int)i < ((int)filtered_specs.size()) - 2) { + out = fmt::format_to(out, " | "); + } else if (arrow && i == filtered_specs.size() - 2) { + out = fmt::format_to(out, " v "); + } + } + if (color) { + out = fmt::format_to(out, "{}", kTerminalReset); + } + switch (filtered_specs[i]) { + case FormatSpecifier::kFunction: + if (color) { + out = fmt::format_to(out, "{}{}{}", kTerminalCyan, function_, + kTerminalReset); + } else { + out = fmt::format_to(out, "{}", function_); + } + break; + case FormatSpecifier::kLongExpression: + out = fmt::format_to(out, "CF_EXPECT({})", expression_); + break; + case FormatSpecifier::kLongLocation: + if (color) { + out = fmt::format_to(out, "{}{}{}:{}{}{}", kTerminalUnderline, file_, + kTerminalReset, kTerminalYellow, line_, + kTerminalYellow); + } else { + out = fmt::format_to(out, "{}:{}", file_, line_); + } + break; + case FormatSpecifier::kMessage: + if (color) { + out = fmt::format_to(out, "{}{}{}", kTerminalBoldRed, message_.str(), + kTerminalReset); + } else { + out = fmt::format_to(out, "{}", message_.str()); + } + break; + case FormatSpecifier::kPrettyFunction: + if (color) { + out = fmt::format_to(out, "{}{}{}", kTerminalCyan, pretty_function_, + kTerminalReset); + } else { + out = fmt::format_to(out, "{}", pretty_function_); + } + break; + case FormatSpecifier::kShort: { + auto last_slash = file_.rfind("/"); + auto short_file = + file_.substr(last_slash == std::string::npos ? 0 : last_slash + 1); + std::string last; + if (HasMessage()) { + last = color ? kTerminalBoldRed + message_.str() + kTerminalReset + : message_.str(); + } + if (color) { + out = fmt::format_to(out, "{}{}{}:{}{}{} | {}{}{} | {}", + kTerminalUnderline, short_file, kTerminalReset, + kTerminalYellow, line_, kTerminalReset, + kTerminalCyan, function_, kTerminalReset, last); + } else { + out = fmt::format_to(out, "{}:{} | {} | {}", short_file, line_, + function_, last); + } + break; + } + case FormatSpecifier::kShortExpression: + out = fmt::format_to(out, "{}", expression_); + break; + case FormatSpecifier::kShortLocation: { + auto last_slash = file_.rfind("/"); + auto short_file = + file_.substr(last_slash == std::string::npos ? 0 : last_slash + 1); + if (color) { + out = fmt::format_to(out, "{}{}{}:{}{}{}", kTerminalUnderline, + short_file, kTerminalReset, kTerminalYellow, + line_, kTerminalReset); + } else { + out = fmt::format_to(out, "{}:{}", short_file, line_); + } + break; + } + default: + fmt::format_to(out, "unknown specifier"); + } + if (i < filtered_specs.size() - 1) { + out = fmt::format_to(out, "\n"); + } + } + return out; +} + +std::string ResultErrorFormat(bool color) { + auto error_format = getenv("CF_ERROR_FORMAT"); + std::string default_error_format = (color ? "cns/acLFEm" : "ns/aLFEm"); + std::string fmt_str = + error_format == nullptr ? default_error_format : error_format; + if (fmt_str.find("}") != std::string::npos) { + fmt_str = "v"; + } + return "{:" + fmt_str + "}"; +} + +} // namespace cuttlefish + +fmt::format_context::iterator +fmt::formatter::format( + const cuttlefish::StackTraceError& error, format_context& ctx) const { + auto out = ctx.out(); + auto& stack = error.Stack(); + int begin = inner_to_outer_ ? 0 : stack.size() - 1; + int end = inner_to_outer_ ? stack.size() : -1; + int step = inner_to_outer_ ? 1 : -1; + for (int i = begin; i != end; i += step) { + auto& specs = has_inner_fmt_spec_ && i == 0 ? inner_fmt_specs_ : fmt_specs_; + out = stack[i].format(ctx, specs, i); + if (i != end - step) { + out = fmt::format_to(out, "\n"); + } + } + return out; +} diff --git a/allocd-port/include/cuttlefish/utils/result.h b/base/cvd/cuttlefish/common/libs/utils/result.h similarity index 56% rename from allocd-port/include/cuttlefish/utils/result.h rename to base/cvd/cuttlefish/common/libs/utils/result.h index 529276e44d..45b4ac488f 100644 --- a/allocd-port/include/cuttlefish/utils/result.h +++ b/base/cvd/cuttlefish/common/libs/utils/result.h @@ -16,57 +16,90 @@ #pragma once #include +#include #include #include #include +#include #include +#include // IWYU pragma: export +#include #include // IWYU pragma: export +#if FMT_VERSION < 80000 +namespace fmt { + // fmt::runtime was added in v8.0.0 + template + const T& runtime(const T& param) { + return param; + } +} +#endif + namespace cuttlefish { class StackTraceError; class StackTraceEntry { public: - StackTraceEntry(std::string file, size_t line, std::string pretty_function) - : file_(std::move(file)), - line_(line), - pretty_function_(std::move(pretty_function)) {} + enum class FormatSpecifier : char { + /** Prefix multi-line output with an arrow. */ + kArrow = 'a', + /** Use colors in all other output specifiers. */ + kColor = 'c', + /** The function name without namespace or arguments. */ + kFunction = 'f', + /** The CF_EXPECT(exp) expression. */ + kLongExpression = 'E', + /** The source file path relative to ANDROID_BUILD_TOP and line number. */ + kLongLocation = 'L', + /** The user-friendly string provided to CF_EXPECT. */ + kMessage = 'm', + /** Prefix output with the stack frame index. */ + kNumbers = 'n', + /** The function signature with fully-qualified types. */ + kPrettyFunction = 'F', + /** The short location and short filename. */ + kShort = 's', + /** The `exp` inside `CF_EXPECT(exp)` */ + kShortExpression = 'e', + /** The source file basename and line number. */ + kShortLocation = 'l', + }; + static constexpr auto kVerbose = { + FormatSpecifier::kArrow, + FormatSpecifier::kColor, + FormatSpecifier::kNumbers, + FormatSpecifier::kShort, + }; + static constexpr auto kVeryVerbose = { + FormatSpecifier::kArrow, FormatSpecifier::kColor, + FormatSpecifier::kNumbers, FormatSpecifier::kLongLocation, + FormatSpecifier::kPrettyFunction, FormatSpecifier::kLongExpression, + FormatSpecifier::kMessage, + }; StackTraceEntry(std::string file, size_t line, std::string pretty_function, - std::string expression) - : file_(std::move(file)), - line_(line), - pretty_function_(std::move(pretty_function)), - expression_(std::move(expression)) {} - - StackTraceEntry(const StackTraceEntry& other) - : file_(other.file_), - line_(other.line_), - pretty_function_(other.pretty_function_), - expression_(other.expression_), - message_(other.message_.str()) {} + std::string function); + + StackTraceEntry(std::string file, size_t line, std::string pretty_function, + std::string function, std::string expression); + + StackTraceEntry(const StackTraceEntry& other); StackTraceEntry(StackTraceEntry&&) = default; - StackTraceEntry& operator=(const StackTraceEntry& other) { - file_ = other.file_; - line_ = other.line_; - pretty_function_ = other.pretty_function_; - expression_ = other.expression_; - message_.str(other.message_.str()); - return *this; - } + StackTraceEntry& operator=(const StackTraceEntry& other); StackTraceEntry& operator=(StackTraceEntry&&) = default; template StackTraceEntry& operator<<(T&& message_ext) & { - message_ << message_ext; + message_ << std::forward(message_ext); return *this; } template StackTraceEntry operator<<(T&& message_ext) && { - message_ << message_ext; + message_ << std::forward(message_ext); return std::move(*this); } @@ -74,33 +107,78 @@ class StackTraceEntry { template operator android::base::expected() &&; - bool HasMessage() const { return !message_.str().empty(); } + bool HasMessage() const; - void Write(std::ostream& stream) const { stream << message_.str(); } - void WriteVerbose(std::ostream& stream) const { - auto str = message_.str(); - if (str.empty()) { - stream << "Failure\n"; - } else { - stream << message_.str() << "\n"; - } - stream << " at " << file_ << ":" << line_ << "\n"; - stream << " in " << pretty_function_; - if (!expression_.empty()) { - stream << " for CF_EXPECT(" << expression_ << ")\n"; - } - } + /* + * Print a single stack trace entry out of a list of format specifiers. + * Some format specifiers [a,c,n] cause changes that affect all lines, while + * the rest amount to printing a single line in the output. This code is + * reused by formatting code for both rendering individual stack trace + * entries, and rendering an entire stack trace with multiple entries. + */ + fmt::format_context::iterator format( + fmt::format_context& ctx, const std::vector& specifiers, + std::optional index) const; private: std::string file_; size_t line_; std::string pretty_function_; + std::string function_; std::string expression_; std::stringstream message_; }; +std::string ResultErrorFormat(bool color); + #define CF_STACK_TRACE_ENTRY(expression) \ - StackTraceEntry(__FILE__, __LINE__, __PRETTY_FUNCTION__, expression) + StackTraceEntry(__FILE__, __LINE__, __PRETTY_FUNCTION__, __func__, expression) + +} // namespace cuttlefish + +/** + * Specialized formatting for StackTraceEntry based on user-provided specifiers. + * + * A StackTraceEntry can be formatted with {:specifiers} in a `fmt::format` + * string, where `specifiers` is an ordered list of characters deciding on the + * format. `v` provides "verbose" output and `V` provides "very verbose" output. + * See `StackTraceEntry::FormatSpecifiers` for more fine-grained specifiers. + */ +template <> +struct fmt::formatter { + public: + constexpr auto parse(format_parse_context& ctx) + -> format_parse_context::iterator { + auto it = ctx.begin(); + while (it != ctx.end() && *it != '}') { + if (*it == 'v') { + for (const auto& specifier : cuttlefish::StackTraceEntry::kVerbose) { + fmt_specs_.push_back(specifier); + } + } else if (*it == 'V') { + for (const auto& specifier : + cuttlefish::StackTraceEntry::kVeryVerbose) { + fmt_specs_.push_back(specifier); + } + } else { + fmt_specs_.push_back( + static_cast(*it)); + } + it++; + } + return it; + } + + auto format(const cuttlefish::StackTraceEntry& entry, + format_context& ctx) const -> format_context::iterator { + return entry.format(ctx, fmt_specs_, std::nullopt); + } + + private: + std::vector fmt_specs_; +}; + +namespace cuttlefish { class StackTraceError { public: @@ -115,19 +193,13 @@ class StackTraceError { const std::vector& Stack() const { return stack_; } std::string Message() const { - std::stringstream writer; - for (const auto& entry : stack_) { - entry.Write(writer); - } - return writer.str(); + return fmt::format(fmt::runtime("{:m}"), *this); } - std::string Trace() const { - std::stringstream writer; - for (const auto& entry : stack_) { - entry.WriteVerbose(writer); - } - return writer.str(); + std::string Trace() const { return fmt::format(fmt::runtime("{:v}"), *this); } + + std::string FormatForEnv(bool color = (isatty(STDERR_FILENO) == 1)) const { + return fmt::format(fmt::runtime(ResultErrorFormat(color)), *this); } template @@ -149,6 +221,62 @@ inline StackTraceEntry::operator android::base::expected` for the format specifiers of + * individual entries. By default the specifier list is passed down to all + * indivudal entries, with the following additional rules. The `^` specifier + * will change the ordering from inner-to-outer instead of outer-to-inner, and + * using the `/` specifier like `/` will apply only to the + * innermost stack entry, and to all other stack entries. + */ +template <> +struct fmt::formatter { + public: + constexpr auto parse(format_parse_context& ctx) + -> format_parse_context::iterator { + auto it = ctx.begin(); + while (it != ctx.end() && *it != '}') { + if (*it == 'v') { + for (const auto& spec : StackTraceEntry::kVerbose) { + (has_inner_fmt_spec_ ? inner_fmt_specs_ : fmt_specs_).push_back(spec); + } + } else if (*it == 'V') { + for (const auto& spec : StackTraceEntry::kVeryVerbose) { + (has_inner_fmt_spec_ ? inner_fmt_specs_ : fmt_specs_).push_back(spec); + } + } else if (*it == '/') { + has_inner_fmt_spec_ = true; + } else if (*it == '^') { + inner_to_outer_ = true; + } else { + (has_inner_fmt_spec_ ? inner_fmt_specs_ : fmt_specs_) + .push_back(static_cast(*it)); + } + it++; + } + return it; + } + + format_context::iterator format(const cuttlefish::StackTraceError& error, + format_context& ctx) const; + + private: + using StackTraceEntry = cuttlefish::StackTraceEntry; + using StackTraceError = cuttlefish::StackTraceError; + + bool inner_to_outer_ = false; + bool has_inner_fmt_spec_ = false; + std::vector fmt_specs_; + std::vector inner_fmt_specs_; +}; + +namespace cuttlefish { + template using Result = android::base::expected; @@ -172,6 +300,8 @@ using Result = android::base::expected; */ #define CF_ERR(MSG) (CF_STACK_TRACE_ENTRY("") << MSG) #define CF_ERRNO(MSG) (CF_STACK_TRACE_ENTRY("") << MSG) +#define CF_ERRF(MSG, ...) \ + (CF_STACK_TRACE_ENTRY("") << fmt::format(FMT_STRING(MSG), __VA_ARGS__)) template T OutcomeDereference(std::optional&& value) { @@ -238,7 +368,7 @@ auto ErrorFromType(Result&& value) { current_entry << MSG; \ auto error = ErrorFromType(macro_intermediate_result); \ error.PushEntry(std::move(current_entry)); \ - return std::move(error); \ + return std::move(error); \ }; \ OutcomeDereference(std::move(macro_intermediate_result)); \ }) @@ -284,10 +414,13 @@ auto ErrorFromType(Result&& value) { #define CF_EXPECT(...) \ CF_EXPECT_OVERLOAD(__VA_ARGS__, CF_EXPECT2, CF_EXPECT1)(__VA_ARGS__) +#define CF_EXPECTF(RESULT, MSG, ...) \ + CF_EXPECT(RESULT, fmt::format(FMT_STRING(MSG), __VA_ARGS__)) + #define CF_COMPARE_EXPECT4(COMPARE_OP, LHS_RESULT, RHS_RESULT, MSG) \ ({ \ - decltype(LHS_RESULT)&& lhs_macro_intermediate_result = LHS_RESULT; \ - decltype(RHS_RESULT)&& rhs_macro_intermediate_result = RHS_RESULT; \ + auto&& lhs_macro_intermediate_result = LHS_RESULT; \ + auto&& rhs_macro_intermediate_result = RHS_RESULT; \ bool comparison_result = lhs_macro_intermediate_result COMPARE_OP \ rhs_macro_intermediate_result; \ if (!comparison_result) { \ @@ -295,11 +428,11 @@ auto ErrorFromType(Result&& value) { current_entry << "Expected \"" << #LHS_RESULT << "\" " << #COMPARE_OP \ << " \"" << #RHS_RESULT << "\" but was " \ << lhs_macro_intermediate_result << " vs " \ - << rhs_macro_intermediate_result << "."; \ + << rhs_macro_intermediate_result << ". "; \ current_entry << MSG; \ auto error = ErrorFromType(false); \ error.PushEntry(std::move(current_entry)); \ - return error; \ + return std::move(error); \ }; \ comparison_result; \ }) diff --git a/base/cvd/cuttlefish/common/libs/utils/result_matchers.h b/base/cvd/cuttlefish/common/libs/utils/result_matchers.h new file mode 100644 index 0000000000..a9e0f0935f --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/result_matchers.h @@ -0,0 +1,67 @@ +// +// Copyright (C) 2022 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 + +#include + +#include +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +MATCHER(IsOk, "an ok result") { + auto& result = arg; + if (!result.ok()) { + *result_listener << "which is an error result with trace: " + << result.error().Trace(); + return false; + } + return true; +} + +MATCHER(IsError, "an error result") { + auto& result = arg; + if (result.ok()) { + *result_listener << "which is an ok result"; + return false; + } + return true; +} + +MATCHER_P(IsOkAndValue, result_value_matcher, "") { + auto& result = arg; + using ResultType = std::decay_t; + return ExplainMatchResult( + ::testing::AllOf(IsOk(), ::testing::Property("value", &ResultType::value, + result_value_matcher)), + result, result_listener); +} + +MATCHER_P(IsErrorAndMessage, message_matcher, "") { + auto& result = arg; + using ResultType = std::decay_t; + return ExplainMatchResult( + ::testing::AllOf( + IsError(), + ::testing::Property( + "error", &ResultType::error, + ::testing::Property(&StackTraceError::Message, message_matcher))), + result, result_listener); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/result_test.cpp b/base/cvd/cuttlefish/common/libs/utils/result_test.cpp new file mode 100644 index 0000000000..2024212bc8 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/result_test.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2021 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. + */ + +#include "common/libs/utils/result.h" + +#include +#include + +#include +#include +#include + +#include "common/libs/utils/result_matchers.h" + +namespace cuttlefish { +namespace { + +using ::testing::HasSubstr; +using ::testing::StrEq; + +} // namespace + +TEST(ResultTest, ExpectBoolGoodNoMessage) { + const auto result = []() -> Result { + CF_EXPECT(true); + return "okay"; + }(); + EXPECT_THAT(result, IsOkAndValue(StrEq("okay"))); +} + +TEST(ResultTest, ExpectBoolGoodWithMessage) { + const auto result = []() -> Result { + CF_EXPECT(true, "Failed"); + return "okay"; + }(); + EXPECT_THAT(result, IsOkAndValue(StrEq("okay"))); +} + +TEST(ResultTest, ExpectBoolBadNoMessage) { + const auto result = []() -> Result { + CF_EXPECT(false); + return "okay"; + }(); + EXPECT_THAT(result, IsError()); +} + +TEST(ResultTest, ExpectBoolBadWithMessage) { + const auto result = []() -> Result { + CF_EXPECT(false, "ExpectBoolBadWithMessage message"); + return "okay"; + }(); + EXPECT_THAT(result, + IsErrorAndMessage(HasSubstr("ExpectBoolBadWithMessage message"))); +} + +TEST(ResultTest, ExpectWithResultGoodNoMessage) { + const auto result = []() -> Result { + const auto inner_result = []() -> Result { + CF_EXPECT(true); + return "inner okay"; + }; + CF_EXPECT(inner_result()); + return "outer okay"; + }(); + EXPECT_THAT(result, IsOkAndValue(StrEq("outer okay"))); +} + +TEST(ResultTest, ExpectWithResultGoodWithMessage) { + const auto result = []() -> Result { + const auto inner_result = []() -> Result { + CF_EXPECT(true); + return "inner okay"; + }; + CF_EXPECT(inner_result(), "Failed inner result."); + return "outer okay"; + }(); + EXPECT_THAT(result, IsOkAndValue(StrEq("outer okay"))); +} + +TEST(ResultTest, ExpectWithResultBadNoMessage) { + const auto result = []() -> Result { + const auto inner_result = []() -> Result { + CF_EXPECT(false, "inner bad"); + return "inner okay"; + }; + CF_EXPECT(inner_result()); + return "okay"; + }(); + EXPECT_THAT(result, IsError()); +} + +TEST(ResultTest, ExpectWithResultBadWithMessage) { + const auto result = []() -> Result { + const auto inner_result = []() -> Result { + CF_EXPECT(false, "inner bad"); + return "inner okay"; + }; + CF_EXPECT(inner_result(), "ExpectWithResultBadWithMessage message"); + return "okay"; + }(); + EXPECT_THAT(result, IsErrorAndMessage( + HasSubstr("ExpectWithResultBadWithMessage message"))); +} + +TEST(ResultTest, ExpectEqGoodNoMessage) { + const auto result = []() -> Result { + CF_EXPECT_EQ(1, 1); + return "okay"; + }(); + EXPECT_THAT(result, IsOkAndValue(StrEq("okay"))); +} + +TEST(ResultTest, ExpectEqGoodWithMessage) { + const auto result = []() -> Result { + CF_EXPECT_EQ(1, 1, "Failed comparison"); + return "okay"; + }(); + EXPECT_THAT(result, IsOkAndValue(StrEq("okay"))); +} + +TEST(ResultTest, ExpectEqBadNoMessage) { + const auto result = []() -> Result { + CF_EXPECT_EQ(1, 2); + return "okay"; + }(); + EXPECT_THAT(result, IsError()); +} + +TEST(ResultTest, ExpectEqBadWithMessage) { + const auto result = []() -> Result { + CF_EXPECT_EQ(1, 2, "ExpectEqBadWithMessage message"); + return "okay"; + }(); + EXPECT_THAT(result, + IsErrorAndMessage(HasSubstr("ExpectEqBadWithMessage message"))); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/shared_fd_flag.cpp b/base/cvd/cuttlefish/common/libs/utils/shared_fd_flag.cpp new file mode 100644 index 0000000000..e50d6d1527 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/shared_fd_flag.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 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. + */ +#include "common/libs/utils/shared_fd_flag.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/flag_parser.h" + +namespace cuttlefish { + +static Result Set(const FlagMatch& match, SharedFD& out) { + int raw_fd; + CF_EXPECTF(android::base::ParseInt(match.value.c_str(), &raw_fd), + "Failed to parse value \"{}\" for fd flag \"{}\"", match.value, + match.key); + out = SharedFD::Dup(raw_fd); + if (out->IsOpen()) { + close(raw_fd); + } + return {}; +} + +Flag SharedFDFlag(SharedFD& out) { + return Flag().Setter([&](const FlagMatch& mat) { return Set(mat, out); }); +} +Flag SharedFDFlag(const std::string& name, SharedFD& out) { + return GflagsCompatFlag(name).Setter( + [&out](const FlagMatch& mat) { return Set(mat, out); }); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/shared_fd_flag.h b/base/cvd/cuttlefish/common/libs/utils/shared_fd_flag.h new file mode 100644 index 0000000000..33b155578b --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/shared_fd_flag.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 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 + +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/flag_parser.h" + +namespace cuttlefish { + +Flag SharedFDFlag(SharedFD& out); +Flag SharedFDFlag(const std::string& name, SharedFD& out); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/size_utils.h b/base/cvd/cuttlefish/common/libs/utils/size_utils.h new file mode 100644 index 0000000000..71bfea637e --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/size_utils.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 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 + +#include + +namespace cuttlefish { + +// Keep the full disk size a multiple of 64k, for crosvm's virtio_blk driver +constexpr int DISK_SIZE_SHIFT = 16; + +// Keep all partitions 4k aligned, for host performance reasons +constexpr int PARTITION_SIZE_SHIFT = 12; + +// Returns the smallest multiple of 2^align_log greater than or equal to val. +constexpr uint64_t AlignToPowerOf2(uint64_t val, uint8_t align_log) { + uint64_t align = 1ULL << align_log; + return ((val + (align - 1)) / align) * align; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/socket2socket_proxy.cpp b/base/cvd/cuttlefish/common/libs/utils/socket2socket_proxy.cpp new file mode 100644 index 0000000000..63cc631ca2 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/socket2socket_proxy.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "common/libs/utils/socket2socket_proxy.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace cuttlefish { +namespace { + +class ProxyPair { + public: + ProxyPair() + : stop_fd_(SharedFD::Event()) { + if (!stop_fd_->IsOpen()) { + LOG(FATAL) << "Failed to open eventfd: " << stop_fd_->StrError(); + return; + } + } + + ProxyPair(ProxyPair&& other) + : started_(other.started_), + stop_fd_(std::move(other.stop_fd_)), + c2t_running_(other.c2t_running_.load()), + t2c_running_(other.t2c_running_.load()) { + if (other.started_) { + LOG(FATAL) << "ProxyPair cannot be moved after Start() being executed"; + } + } + + ~ProxyPair() { + if (stop_fd_->IsOpen() && stop_fd_->EventfdWrite(1) != 0) { + LOG(ERROR) << "Failed to stop proxy thread: " << stop_fd_->StrError(); + } + if (c2t_.joinable()) { + c2t_.join(); + } + if (t2c_.joinable()) { + t2c_.join(); + } + } + + void Start(SharedFD from, SharedFD to) { + if (started_) { + LOG(FATAL) << "ProxyPair cannot be started second time"; + } + started_ = true; + c2t_running_ = true; + t2c_running_ = true; + c2t_ = std::thread(&ProxyPair::Forward, this, "c2t", from, to, stop_fd_, + std::ref(c2t_running_)); + t2c_ = std::thread(&ProxyPair::Forward, this, "t2c", to, from, stop_fd_, + std::ref(t2c_running_)); + } + + bool Running() { + return c2t_running_ || t2c_running_; + } + + private: + void Forward(const std::string& label, SharedFD from, SharedFD to, + SharedFD stop, std::atomic& running) { + LOG(DEBUG) << label << ": Proxy thread started. Starting copying data"; + auto success = to->CopyAllFrom(*from, &(*stop)); + if (!success) { + if (from->GetErrno()) { + LOG(ERROR) << label << ": Error reading: " << from->StrError(); + } + if (to->GetErrno()) { + LOG(ERROR) << label << ": Error writing: " << to->StrError(); + } + } + to->Shutdown(SHUT_WR); + running = false; + LOG(DEBUG) << label << ": Proxy thread completed"; + } + + bool started_; + SharedFD stop_fd_; + std::atomic c2t_running_; + std::atomic t2c_running_; + std::thread c2t_; + std::thread t2c_; +}; + +} // namespace + +ProxyServer::ProxyServer(SharedFD server, std::function clients_factory) + : stop_fd_(SharedFD::Event()) { + if (!stop_fd_->IsOpen()) { + LOG(FATAL) << "Failed to open eventfd: " << stop_fd_->StrError(); + return; + } + server_ = std::thread([&, server_fd = std::move(server), + clients_factory = std::move(clients_factory)]() { + constexpr ssize_t SERVER = 0; + constexpr ssize_t STOP = 1; + std::list watched; + + std::vector server_poll = { + {.fd = server_fd, .events = POLLIN}, + {.fd = stop_fd_, .events = POLLIN} + }; + + while (server_fd->IsOpen()) { + server_poll[SERVER].revents = 0; + server_poll[STOP].revents = 0; + + const int poll_result = SharedFD::Poll(server_poll, -1); + if (poll_result < 0) { + LOG(ERROR) << "Failed to poll to wait for incoming connection"; + continue; + } + if (server_poll[STOP].revents & POLLIN) { + // Stop fd is available to read, so we received a stop event + // and must stop the thread + break; + } + if (!(server_poll[SERVER].revents & POLLIN)) { + continue; + } + + // Server fd is available to read, so we can accept the + // connection without blocking on that + auto client = SharedFD::Accept(*server_fd); + if (!client->IsOpen()) { + LOG(ERROR) << "Failed to accept incoming connection: " << client->StrError(); + continue; + } + auto target = clients_factory(); + if (target->IsOpen()) { + LOG(DEBUG) << "Launching proxy threads"; + watched.push_back(ProxyPair()); + watched.back().Start(client, target); + LOG(DEBUG) << "Proxy is launched. Amount of currently tracked proxy pairs: " + << watched.size(); + } else { + LOG(ERROR) << "Cannot connect to the target to setup proxying: " << target->StrError(); + } + // Unwatch completed proxy pairs + watched.remove_if([](ProxyPair& proxy) { return !proxy.Running(); }); + } + + // Making sure all launched proxy pairs are finished by triggering their destructor + LOG(DEBUG) << "Waiting for proxy threads to turn down"; + watched.clear(); + LOG(DEBUG) << "Proxy threads are successfully turned down"; + }); +} + +void ProxyServer::Join() { + if (server_.joinable()) { + server_.join(); + } +} + +ProxyServer::~ProxyServer() { + if (stop_fd_->EventfdWrite(1) != 0) { + LOG(ERROR) << "Failed to stop proxy thread: " << stop_fd_->StrError(); + } + Join(); +} + +void Proxy(SharedFD server, std::function conn_factory) { + ProxyServer proxy(std::move(server), std::move(conn_factory)); + proxy.Join(); +} + +std::unique_ptr ProxyAsync(SharedFD server, std::function conn_factory) { + return std::make_unique(std::move(server), std::move(conn_factory)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/socket2socket_proxy.h b/base/cvd/cuttlefish/common/libs/utils/socket2socket_proxy.h new file mode 100644 index 0000000000..f73bbb32d7 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/socket2socket_proxy.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include + +#include "common/libs/fs/shared_fd.h" + +namespace cuttlefish { + +class ProxyServer { + public: + ProxyServer(SharedFD server, std::function clients_factory); + void Join(); + ~ProxyServer(); + + private: + SharedFD stop_fd_; + std::thread server_; +}; + +// Executes a TCP proxy +// Accept() is called on the server in a loop, for every client connection a +// target connection is created through the conn_factory callback and data is +// forwarded between the two connections. +// This function is meant to execute forever, but will return if the server is +// closed in another thread. It's recommended the caller disables the default +// behavior for SIGPIPE before calling this function, otherwise it runs the risk +// or crashing the process when a connection breaks. +void Proxy(SharedFD server, std::function conn_factory); +std::unique_ptr ProxyAsync(SharedFD server, std::function conn_factory); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/subprocess.cpp b/base/cvd/cuttlefish/common/libs/utils/subprocess.cpp new file mode 100644 index 0000000000..6595cd5ed5 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/subprocess.cpp @@ -0,0 +1,692 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "common/libs/utils/subprocess.h" + +#ifdef __linux__ +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/files.h" + +extern char** environ; + +namespace cuttlefish { +namespace { + +// If a redirected-to file descriptor was already closed, it's possible that +// some inherited file descriptor duped to this file descriptor and the redirect +// would override that. This function makes sure that doesn't happen. +bool validate_redirects( + const std::map& redirects, + const std::map& inherited_fds) { + // Add the redirected IO channels to a set as integers. This allows converting + // the enum values into integers instead of the other way around. + std::set int_redirects; + for (const auto& entry : redirects) { + int_redirects.insert(static_cast(entry.first)); + } + for (const auto& entry : inherited_fds) { + auto dupped_fd = entry.second; + if (int_redirects.count(dupped_fd)) { + LOG(ERROR) << "Requested redirect of fd(" << dupped_fd + << ") conflicts with inherited FD."; + return false; + } + } + return true; +} + +void do_redirects(const std::map& redirects) { + for (const auto& entry : redirects) { + auto std_channel = static_cast(entry.first); + auto fd = entry.second; + TEMP_FAILURE_RETRY(dup2(fd, std_channel)); + } +} + +std::vector ToCharPointers(const std::vector& vect) { + std::vector ret = {}; + for (const auto& str : vect) { + ret.push_back(str.c_str()); + } + ret.push_back(NULL); + return ret; +} +} // namespace + +std::vector ArgsToVec(char** argv) { + std::vector args; + for (int i = 0; argv && argv[i]; i++) { + args.push_back(argv[i]); + } + return args; +} + +std::unordered_map EnvpToMap(char** envp) { + std::unordered_map env_map; + if (!envp) { + return env_map; + } + for (char** e = envp; *e != nullptr; e++) { + std::string env_var_val(*e); + auto tokens = android::base::Split(env_var_val, "="); + if (tokens.size() <= 1) { + LOG(WARNING) << "Environment var in unknown format: " << env_var_val; + continue; + } + const auto var = tokens.at(0); + tokens.erase(tokens.begin()); + env_map[var] = android::base::Join(tokens, "="); + } + return env_map; +} + +SubprocessOptions& SubprocessOptions::Verbose(bool verbose) & { + verbose_ = verbose; + return *this; +} +SubprocessOptions SubprocessOptions::Verbose(bool verbose) && { + verbose_ = verbose; + return std::move(*this); +} + +#ifdef __linux__ +SubprocessOptions& SubprocessOptions::ExitWithParent(bool v) & { + exit_with_parent_ = v; + return *this; +} +SubprocessOptions SubprocessOptions::ExitWithParent(bool v) && { + exit_with_parent_ = v; + return std::move(*this); +} +#endif + +SubprocessOptions& SubprocessOptions::SandboxArguments( + std::vector args) & { + sandbox_arguments_ = std::move(args); + return *this; +} + +SubprocessOptions SubprocessOptions::SandboxArguments( + std::vector args) && { + sandbox_arguments_ = std::move(args); + return *this; +} + +SubprocessOptions& SubprocessOptions::InGroup(bool in_group) & { + in_group_ = in_group; + return *this; +} +SubprocessOptions SubprocessOptions::InGroup(bool in_group) && { + in_group_ = in_group; + return std::move(*this); +} + +SubprocessOptions& SubprocessOptions::Strace(std::string s) & { + strace_ = std::move(s); + return *this; +} +SubprocessOptions SubprocessOptions::Strace(std::string s) && { + strace_ = std::move(s); + return std::move(*this); +} + +Subprocess::Subprocess(Subprocess&& subprocess) + : pid_(subprocess.pid_.load()), + started_(subprocess.started_), + stopper_(subprocess.stopper_) { + // Make sure the moved object no longer controls this subprocess + subprocess.pid_ = -1; + subprocess.started_ = false; +} + +Subprocess& Subprocess::operator=(Subprocess&& other) { + pid_ = other.pid_.load(); + started_ = other.started_; + stopper_ = other.stopper_; + + other.pid_ = -1; + other.started_ = false; + return *this; +} + +int Subprocess::Wait() { + if (pid_ < 0) { + LOG(ERROR) + << "Attempt to wait on invalid pid(has it been waited on already?): " + << pid_; + return -1; + } + int wstatus = 0; + auto pid = pid_.load(); // Wait will set pid_ to -1 after waiting + auto wait_ret = waitpid(pid, &wstatus, 0); + if (wait_ret < 0) { + auto error = errno; + LOG(ERROR) << "Error on call to waitpid: " << strerror(error); + return wait_ret; + } + int retval = 0; + if (WIFEXITED(wstatus)) { + pid_ = -1; + retval = WEXITSTATUS(wstatus); + if (retval) { + LOG(DEBUG) << "Subprocess " << pid + << " exited with error code: " << retval; + } + } else if (WIFSIGNALED(wstatus)) { + pid_ = -1; + int sig_num = WTERMSIG(wstatus); + LOG(ERROR) << "Subprocess " << pid << " was interrupted by a signal '" + << strsignal(sig_num) << "' (" << sig_num << ")"; + retval = -1; + } + return retval; +} +int Subprocess::Wait(siginfo_t* infop, int options) { + if (pid_ < 0) { + LOG(ERROR) + << "Attempt to wait on invalid pid(has it been waited on already?): " + << pid_; + return -1; + } + *infop = {}; + auto retval = waitid(P_PID, pid_, infop, options); + // We don't want to wait twice for the same process + bool exited = infop->si_code == CLD_EXITED || infop->si_code == CLD_DUMPED; + bool reaped = !(options & WNOWAIT); + if (exited && reaped) { + pid_ = -1; + } + return retval; +} + +static Result SendSignalImpl(const int signal, const pid_t pid, + bool to_group, const bool started) { + if (pid == -1) { + return CF_ERR(strerror(ESRCH)); + } + CF_EXPECTF(started == true, + "The Subprocess object lost the ownership" + "of the process {}.", + pid); + int ret_code = 0; + if (to_group) { + ret_code = killpg(getpgid(pid), signal); + } else { + ret_code = kill(pid, signal); + } + CF_EXPECTF(ret_code == 0, "kill/killpg returns {} with errno: {}", ret_code, + strerror(errno)); + return {}; +} + +Result Subprocess::SendSignal(const int signal) { + CF_EXPECT(SendSignalImpl(signal, pid_, /* to_group */ false, started_)); + return {}; +} + +Result Subprocess::SendSignalToGroup(const int signal) { + CF_EXPECT(SendSignalImpl(signal, pid_, /* to_group */ true, started_)); + return {}; +} + +StopperResult KillSubprocess(Subprocess* subprocess) { + auto pid = subprocess->pid(); + if (pid > 0) { + auto pgid = getpgid(pid); + if (pgid < 0) { + auto error = errno; + LOG(WARNING) << "Error obtaining process group id of process with pid=" + << pid << ": " << strerror(error); + // Send the kill signal anyways, because pgid will be -1 it will be sent + // to the process and not a (non-existent) group + } + bool is_group_head = pid == pgid; + auto kill_ret = (is_group_head ? killpg : kill)(pid, SIGKILL); + if (kill_ret == 0) { + return StopperResult::kStopSuccess; + } + auto kill_cmd = is_group_head ? "killpg(" : "kill("; + PLOG(ERROR) << kill_cmd << pid << ", SIGKILL) failed: "; + return StopperResult::kStopFailure; + } + return StopperResult::kStopSuccess; +} + +Command::Command(std::string executable, SubprocessStopper stopper) + : subprocess_stopper_(stopper) { + for (char** env = environ; *env; env++) { + env_.emplace_back(*env); + } + command_.emplace_back(std::move(executable)); +} + +Command::~Command() { + // Close all inherited file descriptors + for (const auto& entry : inherited_fds_) { + close(entry.second); + } + // Close all redirected file descriptors + for (const auto& entry : redirects_) { + close(entry.second); + } +} + +void Command::BuildParameter(std::stringstream* stream, SharedFD shared_fd) { + int fd; + if (inherited_fds_.count(shared_fd)) { + fd = inherited_fds_[shared_fd]; + } else { + fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3); + CHECK(fd >= 0) << "Could not acquire a new file descriptor: " + << shared_fd->StrError(); + inherited_fds_[shared_fd] = fd; + } + *stream << fd; +} + +Command& Command::RedirectStdIO(Subprocess::StdIOChannel channel, + SharedFD shared_fd) & { + CHECK(shared_fd->IsOpen()); + CHECK(redirects_.count(channel) == 0) + << "Attempted multiple redirections of fd: " << static_cast(channel); + auto dup_fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3); + CHECK(dup_fd >= 0) << "Could not acquire a new file descriptor: " + << shared_fd->StrError(); + redirects_[channel] = dup_fd; + return *this; +} +Command Command::RedirectStdIO(Subprocess::StdIOChannel channel, + SharedFD shared_fd) && { + RedirectStdIO(channel, shared_fd); + return std::move(*this); +} +Command& Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel, + Subprocess::StdIOChannel parent_channel) & { + return RedirectStdIO(subprocess_channel, + SharedFD::Dup(static_cast(parent_channel))); +} +Command Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel, + Subprocess::StdIOChannel parent_channel) && { + RedirectStdIO(subprocess_channel, parent_channel); + return std::move(*this); +} + +Command& Command::SetWorkingDirectory(const std::string& path) & { +#ifdef __linux__ + auto fd = SharedFD::Open(path, O_RDONLY | O_PATH | O_DIRECTORY); +#elif defined(__APPLE__) + auto fd = SharedFD::Open(path, O_RDONLY | O_DIRECTORY); +#else +#error "Unsupported operating system" +#endif + CHECK(fd->IsOpen()) << "Could not open \"" << path + << "\" dir fd: " << fd->StrError(); + return SetWorkingDirectory(fd); +} +Command Command::SetWorkingDirectory(const std::string& path) && { + return std::move(SetWorkingDirectory(path)); +} +Command& Command::SetWorkingDirectory(SharedFD dirfd) & { + CHECK(dirfd->IsOpen()) << "Dir fd invalid: " << dirfd->StrError(); + working_directory_ = std::move(dirfd); + return *this; +} +Command Command::SetWorkingDirectory(SharedFD dirfd) && { + return std::move(SetWorkingDirectory(std::move(dirfd))); +} + +Command& Command::AddPrerequisite( + const std::function()>& prerequisite) & { + prerequisites_.push_back(prerequisite); + return *this; +} + +Command Command::AddPrerequisite( + const std::function()>& prerequisite) && { + prerequisites_.push_back(prerequisite); + return std::move(*this); +} + +Subprocess Command::Start(SubprocessOptions options) const { + auto cmd = ToCharPointers(command_); + + if (!options.Strace().empty()) { + auto strace_args = { + "/usr/bin/strace", + "--daemonize", + "--output-separately", // Add .pid suffix + "--follow-forks", + "-o", // Write to a separate file. + options.Strace().c_str(), + }; + cmd.insert(cmd.begin(), strace_args); + } + + if (!validate_redirects(redirects_, inherited_fds_)) { + return Subprocess(-1, {}); + } + + std::string fds_arg; + if (!options.SandboxArguments().empty()) { + std::vector fds; + for (const auto& redirect : redirects_) { + fds.emplace_back(static_cast(redirect.first)); + } + for (const auto& inherited_fd : inherited_fds_) { + fds.emplace_back(inherited_fd.second); + } + fds_arg = "--inherited_fds=" + fmt::format("{}", fmt::join(fds, ",")); + + auto forwarding_args = {fds_arg.c_str(), "--"}; + cmd.insert(cmd.begin(), forwarding_args); + auto sbox_ptrs = ToCharPointers(options.SandboxArguments()); + sbox_ptrs.pop_back(); // Final null pointer will end argv early + cmd.insert(cmd.begin(), sbox_ptrs.begin(), sbox_ptrs.end()); + } + + pid_t pid = fork(); + if (!pid) { +#ifdef __linux__ + if (options.ExitWithParent()) { + prctl(PR_SET_PDEATHSIG, SIGHUP); // Die when parent dies + } +#endif + + do_redirects(redirects_); + + for (auto& prerequisite : prerequisites_) { + auto prerequisiteResult = prerequisite(); + + if (!prerequisiteResult.ok()) { + LOG(ERROR) << "Failed to check prerequisites: " + << prerequisiteResult.error().FormatForEnv(); + } + } + + if (options.InGroup()) { + // This call should never fail (see SETPGID(2)) + if (setpgid(0, 0) != 0) { + auto error = errno; + LOG(ERROR) << "setpgid failed (" << strerror(error) << ")"; + } + } + for (const auto& entry : inherited_fds_) { + if (fcntl(entry.second, F_SETFD, 0)) { + int error_num = errno; + LOG(ERROR) << "fcntl failed: " << strerror(error_num); + } + } + if (working_directory_->IsOpen()) { + if (SharedFD::Fchdir(working_directory_) != 0) { + LOG(ERROR) << "Fchdir failed: " << working_directory_->StrError(); + } + } + int rval; + auto envp = ToCharPointers(env_); + const char* executable = executable_ ? executable_->c_str() : cmd[0]; +#ifdef __linux__ + rval = execvpe(executable, const_cast(cmd.data()), + const_cast(envp.data())); +#elif defined(__APPLE__) + rval = execve(executable, const_cast(cmd.data()), + const_cast(envp.data())); +#else +#error "Unsupported architecture" +#endif + // No need for an if: if exec worked it wouldn't have returned + LOG(ERROR) << "exec of " << cmd[0] << " with path \"" << executable + << "\" failed (" << strerror(errno) << ")"; + exit(rval); + } + if (pid == -1) { + LOG(ERROR) << "fork failed (" << strerror(errno) << ")"; + } + if (options.Verbose()) { // "more verbose", and LOG(DEBUG) > LOG(VERBOSE) + LOG(DEBUG) << "Started (pid: " << pid << "): " << cmd[0]; + for (int i = 1; cmd[i]; i++) { + LOG(DEBUG) << cmd[i]; + } + } else { + LOG(VERBOSE) << "Started (pid: " << pid << "): " << cmd[0]; + for (int i = 1; cmd[i]; i++) { + LOG(VERBOSE) << cmd[i]; + } + } + return Subprocess(pid, subprocess_stopper_); +} + +std::string Command::AsBashScript( + const std::string& redirected_stdio_path) const { + CHECK(inherited_fds_.empty()) + << "Bash wrapper will not have inheritied file descriptors."; + CHECK(redirects_.empty()) << "Bash wrapper will not have redirected stdio."; + + std::string contents = + "#!/bin/bash\n\n" + android::base::Join(command_, " \\\n"); + if (!redirected_stdio_path.empty()) { + contents += " &> " + AbsolutePath(redirected_stdio_path); + } + return contents; +} + +// A class that waits for threads to exit in its destructor. +class ThreadJoiner { +std::vector threads_; +public: + ThreadJoiner(const std::vector threads) : threads_(threads) {} + ~ThreadJoiner() { + for (auto& thread : threads_) { + if (thread->joinable()) { + thread->join(); + } + } + } +}; + +int RunWithManagedStdio(Command&& cmd_tmp, const std::string* stdin_str, + std::string* stdout_str, std::string* stderr_str, + SubprocessOptions options) { + /* + * The order of these declarations is necessary for safety. If the function + * returns at any point, the Command will be destroyed first, closing all + * of its references to SharedFDs. This will cause the thread internals to fail + * their reads or writes. The ThreadJoiner then waits for the threads to + * complete, as running the destructor of an active std::thread crashes the + * program. + * + * C++ scoping rules dictate that objects are descoped in reverse order to + * construction, so this behavior is predictable. + */ + std::thread stdin_thread, stdout_thread, stderr_thread; + ThreadJoiner thread_joiner({&stdin_thread, &stdout_thread, &stderr_thread}); + Command cmd = std::move(cmd_tmp); + bool io_error = false; + if (stdin_str != nullptr) { + SharedFD pipe_read, pipe_write; + if (!SharedFD::Pipe(&pipe_read, &pipe_write)) { + LOG(ERROR) << "Could not create a pipe to write the stdin of \"" + << cmd.GetShortName() << "\""; + return -1; + } + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read); + stdin_thread = std::thread([pipe_write, stdin_str, &io_error]() { + int written = WriteAll(pipe_write, *stdin_str); + if (written < 0) { + io_error = true; + LOG(ERROR) << "Error in writing stdin to process"; + } + }); + } + if (stdout_str != nullptr) { + SharedFD pipe_read, pipe_write; + if (!SharedFD::Pipe(&pipe_read, &pipe_write)) { + LOG(ERROR) << "Could not create a pipe to read the stdout of \"" + << cmd.GetShortName() << "\""; + return -1; + } + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write); + stdout_thread = std::thread([pipe_read, stdout_str, &io_error]() { + int read = ReadAll(pipe_read, stdout_str); + if (read < 0) { + io_error = true; + LOG(ERROR) << "Error in reading stdout from process"; + } + }); + } + if (stderr_str != nullptr) { + SharedFD pipe_read, pipe_write; + if (!SharedFD::Pipe(&pipe_read, &pipe_write)) { + LOG(ERROR) << "Could not create a pipe to read the stderr of \"" + << cmd.GetShortName() << "\""; + return -1; + } + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write); + stderr_thread = std::thread([pipe_read, stderr_str, &io_error]() { + int read = ReadAll(pipe_read, stderr_str); + if (read < 0) { + io_error = true; + LOG(ERROR) << "Error in reading stderr from process"; + } + }); + } + + auto subprocess = cmd.Start(std::move(options)); + if (!subprocess.Started()) { + return -1; + } + auto cmd_short_name = cmd.GetShortName(); + { + // Force the destructor to run by moving it into a smaller scope. + // This is necessary to close the write end of the pipe. + Command forceDelete = std::move(cmd); + } + + int code = subprocess.Wait(); + { + auto join_threads = std::move(thread_joiner); + } + if (io_error) { + LOG(ERROR) << "IO error communicating with " << cmd_short_name; + return -1; + } + return code; +} + +namespace { + +struct ExtraParam { + // option for Subprocess::Start() + SubprocessOptions subprocess_options; + // options for Subprocess::Wait(...) + int wait_options; + siginfo_t* infop; +}; +Result ExecuteImpl(const std::vector& command, + const std::optional>& envs, + std::optional extra_param) { + Command cmd(command[0]); + for (size_t i = 1; i < command.size(); ++i) { + cmd.AddParameter(command[i]); + } + if (envs) { + cmd.SetEnvironment(*envs); + } + auto subprocess = + (!extra_param ? cmd.Start() + : cmd.Start(std::move(extra_param->subprocess_options))); + CF_EXPECT(subprocess.Started(), "Subprocess failed to start."); + + if (extra_param) { + CF_EXPECT(extra_param->infop != nullptr, + "When ExtraParam is given, the infop buffer address " + << "must not be nullptr."); + return subprocess.Wait(extra_param->infop, extra_param->wait_options); + } else { + return subprocess.Wait(); + } +} + +} // namespace + +int Execute(const std::vector& commands, + const std::vector& envs) { + auto result = ExecuteImpl(commands, envs, /* extra_param */ std::nullopt); + return (!result.ok() ? -1 : *result); +} + +int Execute(const std::vector& commands) { + std::vector envs; + auto result = ExecuteImpl(commands, /* envs */ std::nullopt, + /* extra_param */ std::nullopt); + return (!result.ok() ? -1 : *result); +} + +Result Execute(const std::vector& commands, + SubprocessOptions subprocess_options, + int wait_options) { + siginfo_t info; + auto ret_code = CF_EXPECT(ExecuteImpl( + commands, /* envs */ std::nullopt, + ExtraParam{.subprocess_options = std::move(subprocess_options), + .wait_options = wait_options, + .infop = &info})); + CF_EXPECT(ret_code == 0, "Subprocess::Wait() returned " << ret_code); + return info; +} + +Result Execute(const std::vector& commands, + const std::vector& envs, + SubprocessOptions subprocess_options, + int wait_options) { + siginfo_t info; + auto ret_code = CF_EXPECT(ExecuteImpl( + commands, envs, + ExtraParam{.subprocess_options = std::move(subprocess_options), + .wait_options = wait_options, + .infop = &info})); + CF_EXPECT(ret_code == 0, "Subprocess::Wait() returned " << ret_code); + return info; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/subprocess.h b/base/cvd/cuttlefish/common/libs/utils/subprocess.h new file mode 100644 index 0000000000..2f695304d3 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/subprocess.h @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2018 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 + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/fs/shared_fd.h" + +namespace cuttlefish { + +/* + * Does what ArgsToVec(int argc, char**) from flag_parser.h does + * without argc. + */ +std::vector ArgsToVec(char** argv); +std::unordered_map EnvpToMap(char** envp); + +enum class StopperResult { + kStopFailure, /* Failed to stop the subprocess. */ + kStopCrash, /* Attempted to stop the subprocess cleanly, but that failed. */ + kStopSuccess, /* The subprocess exited in the expected way. */ +}; + +class Subprocess; +using SubprocessStopper = std::function; +// Kills a process by sending it the SIGKILL signal. +StopperResult KillSubprocess(Subprocess* subprocess); + +// Keeps track of a running (sub)process. Allows to wait for its completion. +// It's an error to wait twice for the same subprocess. +class Subprocess { + public: + enum class StdIOChannel { + kStdIn = 0, + kStdOut = 1, + kStdErr = 2, + }; + + Subprocess(pid_t pid, SubprocessStopper stopper = KillSubprocess) + : pid_(pid), started_(pid > 0), stopper_(stopper) {} + // The default implementation won't do because we need to reset the pid of the + // moved object. + Subprocess(Subprocess&&); + ~Subprocess() = default; + Subprocess& operator=(Subprocess&&); + // Waits for the subprocess to complete. Returns zero if completed + // successfully, non-zero otherwise. + int Wait(); + // Same as waitid(2) + int Wait(siginfo_t* infop, int options); + // Whether the command started successfully. It only says whether the call to + // fork() succeeded or not, it says nothing about exec or successful + // completion of the command, that's what Wait is for. + bool Started() const { return started_; } + pid_t pid() const { return pid_; } + StopperResult Stop() { return stopper_(this); } + + Result SendSignal(const int signal); + Result SendSignalToGroup(const int signal); + + private: + // Copy is disabled to avoid waiting twice for the same pid (the first wait + // frees the pid, which allows the kernel to reuse it so we may end up waiting + // for the wrong process) + Subprocess(const Subprocess&) = delete; + Subprocess& operator=(const Subprocess&) = delete; + std::atomic pid_ = -1; + bool started_ = false; + SubprocessStopper stopper_; +}; + +class SubprocessOptions { + public: + SubprocessOptions() + : verbose_(true), exit_with_parent_(true), in_group_(false) {} + SubprocessOptions& Verbose(bool verbose) &; + SubprocessOptions Verbose(bool verbose) &&; + SubprocessOptions& ExitWithParent(bool exit_with_parent) &; + SubprocessOptions ExitWithParent(bool exit_with_parent) &&; + SubprocessOptions& SandboxArguments(std::vector) &; + SubprocessOptions SandboxArguments(std::vector) &&; + // The subprocess runs as head of its own process group. + SubprocessOptions& InGroup(bool in_group) &; + SubprocessOptions InGroup(bool in_group) &&; + + SubprocessOptions& Strace(std::string strace_output_path) &; + SubprocessOptions Strace(std::string strace_output_path) &&; + + bool Verbose() const { return verbose_; } + bool ExitWithParent() const { return exit_with_parent_; } + const std::vector& SandboxArguments() const { + return sandbox_arguments_; + } + bool InGroup() const { return in_group_; } + const std::string& Strace() const { return strace_; } + + private: + bool verbose_; + bool exit_with_parent_; + std::vector sandbox_arguments_; + bool in_group_; + std::string strace_; +}; + +// An executable command. Multiple subprocesses can be started from the same +// command object. This class owns any file descriptors that the subprocess +// should inherit. +class Command { + private: + template + // For every type other than SharedFD (for which there is a specialisation) + void BuildParameter(std::stringstream* stream, T t) { + *stream << t; + } + // Special treatment for SharedFD + void BuildParameter(std::stringstream* stream, SharedFD shared_fd); + template + void BuildParameter(std::stringstream* stream, T t, Args... args) { + BuildParameter(stream, t); + BuildParameter(stream, args...); + } + + public: + // Constructs a command object from the path to an executable binary and an + // optional subprocess stopper. When not provided, stopper defaults to sending + // SIGKILL to the subprocess. + Command(std::string executable, SubprocessStopper stopper = KillSubprocess); + Command(Command&&) = default; + // The default copy constructor is unsafe because it would mean multiple + // closing of the inherited file descriptors. If needed it can be implemented + // using dup(2) + Command(const Command&) = delete; + Command& operator=(const Command&) = delete; + ~Command(); + + const std::string& Executable() const { + return executable_ ? *executable_ : command_[0]; + } + + Command& SetExecutable(std::string executable) & { + executable_ = std::move(executable); + return *this; + } + Command SetExecutable(std::string executable) && { + return std::move(SetExecutable(executable)); + } + + Command& SetName(std::string name) & { + command_[0] = std::move(name); + return *this; + } + Command SetName(std::string name) && { + return std::move(SetName(std::move(name))); + } + + Command& SetExecutableAndName(std::string name) & { + return SetExecutable(name).SetName(std::move(name)); + } + + Command SetExecutableAndName(std::string name) && { + return std::move(SetExecutableAndName(std::move(name))); + } + + Command& SetStopper(SubprocessStopper stopper) & { + subprocess_stopper_ = std::move(stopper); + return *this; + } + Command SetStopper(SubprocessStopper stopper) && { + return std::move(SetStopper(std::move(stopper))); + } + + // Specify the environment for the subprocesses to be started. By default + // subprocesses inherit the parent's environment. + Command& SetEnvironment(std::vector env) & { + env_ = std::move(env); + return *this; + } + Command SetEnvironment(std::vector env) && { + return std::move(SetEnvironment(std::move(env))); + } + + Command& AddEnvironmentVariable(const std::string& env_var, + const std::string& value) & { + return AddEnvironmentVariable(env_var + "=" + value); + } + Command AddEnvironmentVariable(const std::string& env_var, + const std::string& value) && { + AddEnvironmentVariable(env_var, value); + return std::move(*this); + } + + Command& AddEnvironmentVariable(std::string env_var) & { + env_.emplace_back(std::move(env_var)); + return *this; + } + Command AddEnvironmentVariable(std::string env_var) && { + return std::move(AddEnvironmentVariable(std::move(env_var))); + } + + // Specify an environment variable to be unset from the parent's + // environment for the subprocesses to be started. + Command& UnsetFromEnvironment(const std::string& env_var) & { + auto it = env_.begin(); + while (it != env_.end()) { + if (android::base::StartsWith(*it, env_var + "=")) { + it = env_.erase(it); + } else { + ++it; + } + } + return *this; + } + Command UnsetFromEnvironment(const std::string& env_var) && { + return std::move(UnsetFromEnvironment(env_var)); + } + + // Adds a single parameter to the command. All arguments are concatenated into + // a single string to form a parameter. If one of those arguments is a + // SharedFD a duplicate of it will be used and won't be closed until the + // object is destroyed. To add multiple parameters to the command the function + // must be called multiple times, one per parameter. + template + Command& AddParameter(Args... args) & { + std::stringstream ss; + BuildParameter(&ss, args...); + command_.push_back(ss.str()); + return *this; + } + template + Command AddParameter(Args... args) && { + return std::move(AddParameter(std::forward(args)...)); + } + // Similar to AddParameter, except the args are appended to the last (most + // recently-added) parameter in the command. + template + Command& AppendToLastParameter(Args... args) & { + CHECK(!command_.empty()) << "There is no parameter to append to."; + std::stringstream ss; + BuildParameter(&ss, args...); + command_[command_.size() - 1] += ss.str(); + return *this; + } + template + Command AppendToLastParameter(Args... args) && { + return std::move(AppendToLastParameter(std::forward(args)...)); + } + + // Redirects the standard IO of the command. + Command& RedirectStdIO(Subprocess::StdIOChannel channel, + SharedFD shared_fd) &; + Command RedirectStdIO(Subprocess::StdIOChannel channel, + SharedFD shared_fd) &&; + Command& RedirectStdIO(Subprocess::StdIOChannel subprocess_channel, + Subprocess::StdIOChannel parent_channel) &; + Command RedirectStdIO(Subprocess::StdIOChannel subprocess_channel, + Subprocess::StdIOChannel parent_channel) &&; + + Command& SetWorkingDirectory(const std::string& path) &; + Command SetWorkingDirectory(const std::string& path) &&; + Command& SetWorkingDirectory(SharedFD dirfd) &; + Command SetWorkingDirectory(SharedFD dirfd) &&; + + Command& AddPrerequisite(const std::function()>& prerequisite) &; + Command AddPrerequisite(const std::function()>& prerequisite) &&; + + // Starts execution of the command. This method can be called multiple times, + // effectively staring multiple (possibly concurrent) instances. + Subprocess Start(SubprocessOptions options = SubprocessOptions()) const; + + std::string GetShortName() const { + // This is safe because the constructor guarantees the name of the binary to + // be at index 0 on the vector + return command_[0]; + } + + // Generates the contents for a bash script that can be used to run this + // command. Note that this command must not require any file descriptors + // or stdio redirects as those would not be available when the bash script + // is run. + std::string AsBashScript(const std::string& redirected_stdio_path = "") const; + + private: + std::optional executable_; // When unset, use command_[0] + std::vector command_; + std::vector()>> prerequisites_; + std::map inherited_fds_{}; + std::map redirects_{}; + std::vector env_{}; + SubprocessStopper subprocess_stopper_; + SharedFD working_directory_; +}; + +/* + * Consumes a Command and runs it, optionally managing the stdio channels. + * + * If `stdin` is set, the subprocess stdin will be pipe providing its contents. + * If `stdout` is set, the subprocess stdout will be captured and saved to it. + * If `stderr` is set, the subprocess stderr will be captured and saved to it. + * + * If `command` exits normally, the lower 8 bits of the return code will be + * returned in a value between 0 and 255. + * If some setup fails, `command` fails to start, or `command` exits due to a + * signal, the return value will be negative. + */ +int RunWithManagedStdio(Command&& command, const std::string* stdin, + std::string* stdout, std::string* stderr, + SubprocessOptions options = SubprocessOptions()); + +/** + * Returns the exit status on success, negative values on error + * + * If failed in fork() or exec(), returns -1. + * If the child exited from an unhandled signal, returns -1. + * Otherwise, returns the exit status. + * + * TODO: Changes return type to Result + * + * For now, too many callsites expects int, and needs quite a lot of changes + * if we change the return type. + */ +int Execute(const std::vector& commands); +int Execute(const std::vector& commands, + const std::vector& envs); + +/** + * Similar as the two above but returns CF_ERR instead of -1, and siginfo_t + * instead of the exit status. + */ +Result Execute(const std::vector& commands, + SubprocessOptions subprocess_options, + int wait_options); +Result Execute(const std::vector& commands, + const std::vector& envs, + SubprocessOptions subprocess_options, + int wait_options); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/tcp_socket.cpp b/base/cvd/cuttlefish/common/libs/utils/tcp_socket.cpp new file mode 100644 index 0000000000..4e68d11e1b --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/tcp_socket.cpp @@ -0,0 +1,138 @@ +/* + * 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. + */ + +#include "common/libs/utils/tcp_socket.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace cuttlefish { + +ClientSocket::ClientSocket(int port) + : fd_(SharedFD::SocketLocalClient(port, SOCK_STREAM)) {} + +Message ClientSocket::RecvAny(std::size_t length) { + Message buf(length); + auto read_count = fd_->Read(buf.data(), buf.size()); + if (read_count < 0) { + read_count = 0; + } + buf.resize(read_count); + return buf; +} + +bool ClientSocket::closed() const { + std::lock_guard guard(closed_lock_); + return other_side_closed_; +} + +Message ClientSocket::Recv(std::size_t length) { + Message buf(length); + ssize_t total_read = 0; + while (total_read < static_cast(length)) { + auto just_read = fd_->Read(&buf[total_read], buf.size() - total_read); + if (just_read <= 0) { + if (just_read < 0) { + LOG(ERROR) << "read() error: " << strerror(errno); + } + { + std::lock_guard guard(closed_lock_); + other_side_closed_ = true; + } + return Message{}; + } + total_read += just_read; + } + CHECK(total_read == static_cast(length)); + return buf; +} + +ssize_t ClientSocket::SendNoSignal(const uint8_t* data, std::size_t size) { + std::lock_guard lock(send_lock_); + ssize_t written{}; + while (written < static_cast(size)) { + if (!fd_->IsOpen()) { + LOG(ERROR) << "fd_ is closed"; + } + auto just_written = fd_->Send(data + written, size - written, MSG_NOSIGNAL); + if (just_written <= 0) { + LOG(INFO) << "Couldn't write to client: " << strerror(errno); + { + std::lock_guard guard(closed_lock_); + other_side_closed_ = true; + } + return just_written; + } + written += just_written; + } + return written; +} + +ssize_t ClientSocket::SendNoSignal(const Message& message) { + return SendNoSignal(&message[0], message.size()); +} + +ServerSocket::ServerSocket(int port) + : fd_{SharedFD::SocketLocalServer(port, SOCK_STREAM)} { + if (!fd_->IsOpen()) { + LOG(FATAL) << "Couldn't open streaming server on port " << port; + } +} + +ClientSocket ServerSocket::Accept() { + SharedFD client = SharedFD::Accept(*fd_); + if (!client->IsOpen()) { + LOG(FATAL) << "Error attemping to accept: " << strerror(errno); + } + return ClientSocket{client}; +} + +void AppendInNetworkByteOrder(Message* msg, const std::uint8_t b) { + msg->push_back(b); +} + +void AppendInNetworkByteOrder(Message* msg, const std::uint16_t s) { + const std::uint16_t n = htons(s); + auto p = reinterpret_cast(&n); + msg->insert(msg->end(), p, p + sizeof n); +} + +void AppendInNetworkByteOrder(Message* msg, const std::uint32_t w) { + const std::uint32_t n = htonl(w); + auto p = reinterpret_cast(&n); + msg->insert(msg->end(), p, p + sizeof n); +} + +void AppendInNetworkByteOrder(Message* msg, const std::int32_t w) { + std::uint32_t u{}; + std::memcpy(&u, &w, sizeof u); + AppendInNetworkByteOrder(msg, u); +} + +void AppendInNetworkByteOrder(Message* msg, const std::string& str) { + msg->insert(msg->end(), str.begin(), str.end()); +} + +} diff --git a/base/cvd/cuttlefish/common/libs/utils/tcp_socket.h b/base/cvd/cuttlefish/common/libs/utils/tcp_socket.h new file mode 100644 index 0000000000..22827ed2ea --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/tcp_socket.h @@ -0,0 +1,107 @@ +#pragma once + +/* + * 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. + */ + +#include "common/libs/fs/shared_fd.h" + +#include + +#include +#include +#include +#include +#include + +namespace cuttlefish { +using Message = std::vector; + +// Recv and Send wait until all data has been received or sent. +// Send is thread safe in this regard, Recv is not. +class ClientSocket { + public: + ClientSocket(ClientSocket&& other) : fd_{other.fd_} {} + + ClientSocket& operator=(ClientSocket&& other) { + fd_ = other.fd_; + return *this; + } + + ClientSocket(int port); + + ClientSocket(const ClientSocket&) = delete; + ClientSocket& operator=(const ClientSocket&) = delete; + + Message Recv(std::size_t length); + // RecvAny will receive whatever is available. + // An empty message returned indicates error or close. + Message RecvAny(std::size_t length); + // Sends are called with MSG_NOSIGNAL to suppress SIGPIPE + ssize_t SendNoSignal(const std::uint8_t* data, std::size_t size); + ssize_t SendNoSignal(const Message& message); + + template + ssize_t SendNoSignal(const std::uint8_t (&data)[N]) { + return SendNoSignal(data, N); + } + + bool closed() const; + + private: + friend class ServerSocket; + explicit ClientSocket(SharedFD fd) : fd_(fd) {} + + SharedFD fd_; + bool other_side_closed_{}; + mutable std::mutex closed_lock_; + std::mutex send_lock_; +}; + +class ServerSocket { + public: + explicit ServerSocket(int port); + + ServerSocket(const ServerSocket&) = delete; + ServerSocket& operator=(const ServerSocket&) = delete; + + ClientSocket Accept(); + + private: + SharedFD fd_; +}; + +void AppendInNetworkByteOrder(Message* msg, const std::uint8_t b); +void AppendInNetworkByteOrder(Message* msg, const std::uint16_t s); +void AppendInNetworkByteOrder(Message* msg, const std::uint32_t w); +void AppendInNetworkByteOrder(Message* msg, const std::int32_t w); +void AppendInNetworkByteOrder(Message* msg, const std::string& str); + +inline void AppendToMessage(Message*) {} + +template +void AppendToMessage(Message* msg, T v, Ts... vals) { + AppendInNetworkByteOrder(msg, v); + AppendToMessage(msg, vals...); +} + +template +Message CreateMessage(Ts... vals) { + Message m; + AppendToMessage(&m, vals...); + return m; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/tee_logging.cpp b/base/cvd/cuttlefish/common/libs/utils/tee_logging.cpp new file mode 100644 index 0000000000..a8cb52e956 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/tee_logging.cpp @@ -0,0 +1,291 @@ +// +// Copyright (C) 2020 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. + +#include "tee_logging.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/result.h" + +using android::base::GetThreadId; +using android::base::FATAL; +using android::base::LogSeverity; +using android::base::StringPrintf; + +namespace cuttlefish { +namespace { + +std::string ToUpper(const std::string& input) { + std::string output = input; + std::transform(output.begin(), output.end(), output.begin(), + [](unsigned char ch) { return std::toupper(ch); }); + return output; +} + +} // namespace + +std::string FromSeverity(const android::base::LogSeverity severity) { + switch (severity) { + case android::base::VERBOSE: + return "VERBOSE"; + case android::base::DEBUG: + return "DEBUG"; + case android::base::INFO: + return "INFO"; + case android::base::WARNING: + return "WARNING"; + case android::base::ERROR: + return "ERROR"; + case android::base::FATAL_WITHOUT_ABORT: + return "FATAL_WITHOUT_ABORT"; + case android::base::FATAL: + return "FATAL"; + } + return "Unexpected severity"; +} + +Result ToSeverity(const std::string& value) { + const std::unordered_map + string_to_severity{ + {"VERBOSE", android::base::VERBOSE}, + {"DEBUG", android::base::DEBUG}, + {"INFO", android::base::INFO}, + {"WARNING", android::base::WARNING}, + {"ERROR", android::base::ERROR}, + {"FATAL_WITHOUT_ABORT", android::base::FATAL_WITHOUT_ABORT}, + {"FATAL", android::base::FATAL}, + }; + + const auto upper_value = ToUpper(value); + if (Contains(string_to_severity, upper_value)) { + return string_to_severity.at(value); + } else { + int value_int; + CF_EXPECT(android::base::ParseInt(value, &value_int), + "Unable to determine severity from \"" << value << "\""); + const auto iter = std::find_if( + string_to_severity.begin(), string_to_severity.end(), + [&value_int]( + const std::pair& entry) { + return static_cast(entry.second) == value_int; + }); + CF_EXPECT(iter != string_to_severity.end(), + "Unable to determine severity from \"" << value << "\""); + return iter->second; + } +} + +static LogSeverity GuessSeverity(const std::string& env_var, + LogSeverity default_value) { + std::string env_value = StringFromEnv(env_var, ""); + auto severity_result = ToSeverity(env_value); + if (!severity_result.ok()) { + return default_value; + } + return severity_result.value(); +} + +LogSeverity ConsoleSeverity() { + return GuessSeverity("CF_CONSOLE_SEVERITY", android::base::INFO); +} + +LogSeverity LogFileSeverity() { + return GuessSeverity("CF_FILE_SEVERITY", android::base::DEBUG); +} + +TeeLogger::TeeLogger(const std::vector& destinations, + const std::string& prefix) + : destinations_(destinations), prefix_(prefix) {} + +// Copied from system/libbase/logging_splitters.h +static std::pair CountSizeAndNewLines(const char* message) { + int size = 0; + int new_lines = 0; + while (*message != '\0') { + size++; + if (*message == '\n') { + ++new_lines; + } + ++message; + } + return {size, new_lines}; +} + +// Copied from system/libbase/logging_splitters.h +// This splits the message up line by line, by calling log_function with a pointer to the start of +// each line and the size up to the newline character. It sends size = -1 for the final line. +template +static void SplitByLines(const char* msg, const F& log_function, Args&&... args) { + const char* newline = strchr(msg, '\n'); + while (newline != nullptr) { + log_function(msg, newline - msg, args...); + msg = newline + 1; + newline = strchr(msg, '\n'); + } + + log_function(msg, -1, args...); +} + +// Copied from system/libbase/logging_splitters.h +// This adds the log header to each line of message and returns it as a string intended to be +// written to stderr. +std::string StderrOutputGenerator(const struct tm& now, int pid, uint64_t tid, + LogSeverity severity, const char* tag, + const char* file, unsigned int line, + const char* message) { + char timestamp[32]; + strftime(timestamp, sizeof(timestamp), "%m-%d %H:%M:%S", &now); + + static const char log_characters[] = "VDIWEFF"; + static_assert(arraysize(log_characters) - 1 == FATAL + 1, + "Mismatch in size of log_characters and values in LogSeverity"); + char severity_char = log_characters[severity]; + std::string line_prefix; + if (file != nullptr) { + line_prefix = StringPrintf("%s %c %s %5d %5" PRIu64 " %s:%u] ", tag ? tag : "nullptr", + severity_char, timestamp, pid, tid, file, line); + } else { + line_prefix = StringPrintf("%s %c %s %5d %5" PRIu64 " ", tag ? tag : "nullptr", severity_char, + timestamp, pid, tid); + } + + auto [size, new_lines] = CountSizeAndNewLines(message); + std::string output_string; + output_string.reserve(size + new_lines * line_prefix.size() + 1); + + auto concat_lines = [&](const char* message, int size) { + output_string.append(line_prefix); + if (size == -1) { + output_string.append(message); + } else { + output_string.append(message, size); + } + output_string.append("\n"); + }; + SplitByLines(message, concat_lines); + return output_string; +} + +// TODO(schuffelen): Do something less primitive. +std::string StripColorCodes(const std::string& str) { + std::stringstream sstream; + bool in_color_code = false; + for (char c : str) { + if (c == '\033') { + in_color_code = true; + } + if (!in_color_code) { + sstream << c; + } + if (c == 'm') { + in_color_code = false; + } + } + return sstream.str(); +} + +void TeeLogger::operator()( + android::base::LogId, + android::base::LogSeverity severity, + const char* tag, + const char* file, + unsigned int line, + const char* message) { + for (const auto& destination : destinations_) { + std::string msg_with_prefix = prefix_ + message; + std::string output_string; + switch (destination.metadata_level) { + case MetadataLevel::ONLY_MESSAGE: + output_string = msg_with_prefix + std::string("\n"); + break; + case MetadataLevel::TAG_AND_MESSAGE: + output_string = fmt::format("{}] {}{}", tag, msg_with_prefix, "\n"); + break; + default: + struct tm now; + time_t t = time(nullptr); + localtime_r(&t, &now); + output_string = + StderrOutputGenerator(now, getpid(), GetThreadId(), severity, tag, + file, line, msg_with_prefix.c_str()); + break; + } + if (severity >= destination.severity) { + if (destination.target->IsATTY()) { + WriteAll(destination.target, output_string); + } else { + WriteAll(destination.target, StripColorCodes(output_string)); + } + } + } +} + +static std::vector SeverityTargetsForFiles( + const std::vector& files) { + std::vector log_severities; + for (const auto& file : files) { + auto log_file_fd = + SharedFD::Open(file, O_CREAT | O_WRONLY | O_APPEND, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + if (!log_file_fd->IsOpen()) { + LOG(FATAL) << "Failed to create log file: " << log_file_fd->StrError(); + } + log_severities.push_back( + SeverityTarget{LogFileSeverity(), log_file_fd, MetadataLevel::FULL}); + } + return log_severities; +} + +TeeLogger LogToFiles(const std::vector& files, + const std::string& prefix) { + return TeeLogger(SeverityTargetsForFiles(files), prefix); +} + +TeeLogger LogToStderrAndFiles(const std::vector& files, + const std::string& prefix, + MetadataLevel stderr_level) { + std::vector log_severities = SeverityTargetsForFiles(files); + log_severities.push_back(SeverityTarget{ConsoleSeverity(), + SharedFD::Dup(/* stderr */ 2), + stderr_level}); + return TeeLogger(log_severities, prefix); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/tee_logging.h b/base/cvd/cuttlefish/common/libs/utils/tee_logging.h new file mode 100644 index 0000000000..af86e596af --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/tee_logging.h @@ -0,0 +1,75 @@ +// +// Copyright (C) 2020 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 + +#include +#include + +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +std::string FromSeverity(const android::base::LogSeverity severity); +Result ToSeverity(const std::string& value); + +std::string StderrOutputGenerator(const struct tm& now, int pid, uint64_t tid, + android::base::LogSeverity severity, + const char* tag, const char* file, + unsigned int line, const char* message); + +android::base::LogSeverity ConsoleSeverity(); +android::base::LogSeverity LogFileSeverity(); + +enum class MetadataLevel { + FULL, + ONLY_MESSAGE, + TAG_AND_MESSAGE +}; + +struct SeverityTarget { + android::base::LogSeverity severity; + SharedFD target; + MetadataLevel metadata_level; +}; + +class TeeLogger { +private: + std::vector destinations_; +public: + TeeLogger(const std::vector& destinations, + const std::string& log_prefix = ""); + ~TeeLogger() = default; + + void operator()(android::base::LogId log_id, + android::base::LogSeverity severity, const char* tag, + const char* file, unsigned int line, const char* message); + +private: + std::string prefix_; +}; + +TeeLogger LogToFiles(const std::vector& files, + const std::string& log_prefix = ""); +TeeLogger LogToStderrAndFiles(const std::vector& files, + const std::string& log_prefix = "", + MetadataLevel stderr_level = MetadataLevel::ONLY_MESSAGE); + +std::string StripColorCodes(const std::string& str); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator.h b/base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator.h new file mode 100644 index 0000000000..a73a970783 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator.h @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/libs/utils/contains.h" + +namespace cuttlefish { + +/** + * Generic allocator that can provide RAII-aware resource reservations. + * + * See go/cf-resource-allocator-utils for more details. + */ +template +class UniqueResourceAllocator { + template + using RemoveCvref = + typename std::remove_cv_t>; + + public: + /* + * Returning the inner resource to the pool at destruction time + * + * The pool must live longer than the resources. Use this like you use + * std::unique_ptr. + */ + class Reservation { + friend class UniqueResourceAllocator; + friend class ReservationSet; + + public: + Reservation(const Reservation&) = delete; + Reservation(Reservation&& src) + : resource_pool_(src.resource_pool_), resource_(src.resource_) { + src.resource_pool_ = nullptr; + } + Reservation& operator=(const Reservation&) = delete; + Reservation& operator=(Reservation&& src) = delete; + + bool operator==(const Reservation& src) const { + return (resource_ == src.resource_ && + resource_pool_ == src.resource_pool_); + } + + ~Reservation() { + if (resource_pool_) { + resource_pool_->Reclaim(*resource_); + } + } + const T& Get() const { return *resource_; } + + private: + Reservation(UniqueResourceAllocator& resource_pool, const T& resource) + : resource_pool_(std::addressof(resource_pool)), + resource_(std::addressof(resource)) {} + /* + * Once this Reservation is std::move-ed out to other object, + * resource_pool_ should be invalidated, and resource_ shouldn't + * be tried to be returned to the invalid resource_pool_ + */ + UniqueResourceAllocator* resource_pool_; + const T* resource_; + }; + + struct ReservationHash { + std::size_t operator()(const Reservation& resource_wrapper) const { + return std::hash()(std::addressof(resource_wrapper.Get())); + } + }; + using ReservationSet = std::unordered_set; + /* + * Creates the singleton object. + * + * Call this function once during the entire program's life + */ + static UniqueResourceAllocator& Create(const std::vector& pool) { + static UniqueResourceAllocator singleton_allocator(pool); + return singleton_allocator; + } + + static std::unique_ptr New( + const std::vector& pool) { + UniqueResourceAllocator* new_allocator = new UniqueResourceAllocator(pool); + return std::unique_ptr(new_allocator); + } + + // Adds the elements from new pool that did not belong to and have not + // belonged to the current pool of the allocator. returns the leftover + std::vector ExpandPool(std::vector another_pool) { + std::lock_guard lock(mutex_); + std::vector not_selected; + for (auto& new_item : another_pool) { + if (Contains(available_resources_, new_item) || + Contains(allocated_resources_, new_item)) { + not_selected.emplace_back(std::move(new_item)); + continue; + } + available_resources_.insert(std::move(new_item)); + } + return not_selected; + } + + std::vector ExpandPool(T&& t) { + std::vector pool_to_add; + pool_to_add.emplace_back(std::move(t)); + return ExpandPool(std::move(pool_to_add)); + } + + std::vector ExpandPool(const T& t) { + std::vector pool_to_add; + pool_to_add.emplace_back(t); + return ExpandPool(std::move(pool_to_add)); + } + + std::optional UniqueItem() { + std::lock_guard lock(mutex_); + auto itr = available_resources_.begin(); + if (itr == available_resources_.end()) { + return std::nullopt; + } + Reservation r(*this, *(RemoveFromPool(itr))); + return {std::move(r)}; + } + + // gives n unique integers from the pool, and then remove them from the pool + std::optional UniqueItems(const int n) { + std::lock_guard lock(mutex_); + if (n <= 0 || available_resources_.size() < n) { + return std::nullopt; + } + ReservationSet result; + for (int i = 0; i < n; i++) { + auto itr = available_resources_.begin(); + result.insert(Reservation{*this, *(RemoveFromPool(itr))}); + } + return {std::move(result)}; + } + + template + std::enable_if_t::value, std::optional> + UniqueConsecutiveItems(const int n) { + static_assert(std::is_same::value); + std::lock_guard lock(mutex_); + if (n <= 0 || available_resources_.size() < n) { + return std::nullopt; + } + + for (const auto& available_resource : available_resources_) { + auto start_inclusive = available_resource; + auto resources_opt = + TakeRangeInternal(start_inclusive, start_inclusive + n); + if (!resources_opt) { + continue; + } + return resources_opt; + } + return std::nullopt; + } + + // takes t if available + // returns false if not available or not in the pool at all + std::optional Take(const T& t) { + std::lock_guard lock(mutex_); + auto itr = available_resources_.find(t); + if (itr == available_resources_.end()) { + return std::nullopt; + } + Reservation resource{*this, *(RemoveFromPool(itr))}; + return resource; + } + + template + std::optional TakeAll(const Container& ts) { + std::lock_guard lock(mutex_); + for (const auto& t : ts) { + if (!Contains(available_resources_, t)) { + return std::nullopt; + } + } + ReservationSet resources; + for (const auto& t : ts) { + auto itr = available_resources_.find(t); + resources.insert(Reservation{*this, *(RemoveFromPool(itr))}); + } + return resources; + } + + /* + * If the range is available, returns the resources from the pool + * + * Otherwise, makes no change in the internal data structure but + * returns false. + */ + template + std::enable_if_t::value, std::optional> + TakeRange(const T& start_inclusive, const T& end_exclusive) { + static_assert(std::is_same::value); + std::lock_guard lock(mutex_); + return TakeRangeInternal(start_inclusive, end_exclusive); + } + + private: + template + UniqueResourceAllocator(const Container& items) + : available_resources_{items.cbegin(), items.cend()} {} + + bool operator==(const UniqueResourceAllocator& other) const { + return std::addressof(*this) == std::addressof(other); + } + + // only called by the destructor of Reservation + // harder to use Result as this is called by destructors only + void Reclaim(const T& t) { + std::lock_guard lock(mutex_); + auto itr = allocated_resources_.find(t); + if (itr == allocated_resources_.end()) { + if (!Contains(available_resources_, t)) { + LOG(ERROR) << "The resource " << t << " does not belong to this pool"; + return; + } + // already reclaimed. + return; + } + T tmp = std::move(*itr); + allocated_resources_.erase(itr); + available_resources_.insert(std::move(tmp)); + } + + /* + * If the range is available, returns the resources from the pool + * + * Otherwise, makes no change in the internal data structure but + * returns false. + */ + template + std::enable_if_t::value, std::optional> + TakeRangeInternal(const T& start_inclusive, const T& end_exclusive) { + static_assert(std::is_same::value); + for (auto cursor = start_inclusive; cursor < end_exclusive; cursor++) { + if (!Contains(available_resources_, cursor)) { + return std::nullopt; + } + } + ReservationSet resources; + for (auto cursor = start_inclusive; cursor < end_exclusive; cursor++) { + auto itr = available_resources_.find(cursor); + resources.insert(Reservation{*this, *(RemoveFromPool(itr))}); + } + return resources; + } + + /* + * Moves *itr from available_resources_ to allocated_resources_, and returns + * the pointer of the object in the allocated_resources_. The pointer is never + * nullptr as it is std::addressof(an object in the unordered_set buffer). + * + * The itr must belong to available_resources_. + */ + const T* RemoveFromPool(const typename std::unordered_set::iterator itr) { + T tmp = std::move(*itr); + available_resources_.erase(itr); + const auto [new_itr, _] = allocated_resources_.insert(std::move(tmp)); + return std::addressof(*new_itr); + } + std::unordered_set available_resources_; + std::unordered_set allocated_resources_; + std::mutex mutex_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator_test.cpp b/base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator_test.cpp new file mode 100644 index 0000000000..88ab2a9357 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator_test.cpp @@ -0,0 +1,202 @@ +// +// Copyright (C) 2022 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. + +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/unique_resource_allocator.h" +#include "common/libs/utils/unique_resource_allocator_test.h" + +namespace cuttlefish { + +TEST_P(OneEachTest, GetAnyAvailableOne) { + const auto resources = GetParam(); + auto allocator = UniqueResourceAllocator::New(resources); + if (!allocator) { + GTEST_SKIP() << "Memory allocation failed but we aren't testing it."; + } + std::unordered_set expected_ids{resources.cbegin(), + resources.cend()}; + using Reservation = UniqueResourceAllocator::Reservation; + + std::vector allocated; + for (int i = 0; i < resources.size(); i++) { + auto id_opt = allocator->UniqueItem(); + ASSERT_TRUE(id_opt); + ASSERT_TRUE(Contains(expected_ids, id_opt->Get())); + allocated.emplace_back(std::move(*id_opt)); + } + ASSERT_FALSE(allocator->UniqueItem()); +} + +INSTANTIATE_TEST_SUITE_P( + CvdIdAllocator, OneEachTest, + testing::Values(std::vector{}, std::vector{1}, + std::vector{1, 22, 3, 43, 5})); + +TEST_F(CvdIdAllocatorTest, ClaimAll) { + std::vector inputs{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + auto allocator = UniqueResourceAllocator::New(inputs); + if (!allocator) { + GTEST_SKIP() << "Memory allocation failed but we aren't testing it."; + } + + // request inputs.size() items + auto allocated_items_opt = allocator->UniqueItems(inputs.size()); + ASSERT_TRUE(allocated_items_opt); + ASSERT_EQ(allocated_items_opt->size(), inputs.size()); + // did it claim all? + ASSERT_FALSE(allocator->UniqueItem()); +} + +TEST_F(CvdIdAllocatorTest, StrideBeyond) { + std::vector inputs{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + auto allocator = UniqueResourceAllocator::New(inputs); + if (!allocator) { + GTEST_SKIP() << "Memory allocation failed but we aren't testing it."; + } + + auto three_opt = allocator->UniqueItems(3); + auto four_opt = allocator->UniqueItems(4); + auto five_opt = allocator->UniqueItems(5); + auto two_opt = allocator->UniqueItems(2); + auto another_two_opt = allocator->UniqueItems(2); + + ASSERT_TRUE(three_opt); + ASSERT_TRUE(four_opt); + ASSERT_FALSE(five_opt); + ASSERT_TRUE(two_opt); + ASSERT_FALSE(another_two_opt); +} + +TEST_F(CvdIdAllocatorTest, Consecutive) { + std::vector inputs{1, 2, 4, 5, 6, 7, 9, 10, 11}; + auto allocator = UniqueResourceAllocator::New(inputs); + if (!allocator) { + GTEST_SKIP() << "Memory allocation failed but we aren't testing it."; + } + + auto four_consecutive = allocator->UniqueConsecutiveItems(4); + auto three_consecutive = allocator->UniqueConsecutiveItems(3); + auto another_three_consecutive = allocator->UniqueConsecutiveItems(3); + auto two_consecutive = allocator->UniqueConsecutiveItems(2); + + ASSERT_TRUE(four_consecutive); + ASSERT_TRUE(three_consecutive); + ASSERT_FALSE(another_three_consecutive); + ASSERT_TRUE(two_consecutive); + // it's empty + ASSERT_FALSE(allocator->UniqueItem()) << "one or more left"; +} + +TEST_F(CvdIdAllocatorTest, Take) { + std::vector inputs{4, 5, 9}; + auto allocator = UniqueResourceAllocator::New(inputs); + if (!allocator) { + GTEST_SKIP() << "Memory allocation failed but we aren't testing it."; + } + + auto four = allocator->Take(4); + auto nine = allocator->Take(9); + // wrong + auto twenty = allocator->Take(20); + + ASSERT_TRUE(four); + ASSERT_TRUE(nine); + ASSERT_FALSE(twenty); +} + +TEST_F(CvdIdAllocatorTest, TakeAll) { + std::vector inputs{4, 5, 9, 10}; + auto allocator = UniqueResourceAllocator::New(inputs); + if (!allocator) { + GTEST_SKIP() << "Memory allocation failed but we aren't testing it."; + } + + auto take_4_5_11 = allocator->TakeAll>({4, 5, 11}); + auto take_4_5_10 = allocator->TakeAll>({4, 5, 10}); + auto take_9_10 = allocator->TakeAll>({9, 10}); + auto take_9 = allocator->TakeAll>({9}); + + ASSERT_FALSE(take_4_5_11); + ASSERT_TRUE(take_4_5_10); + ASSERT_FALSE(take_9_10); + ASSERT_TRUE(take_9); +} + +TEST_F(CvdIdAllocatorTest, TakeRange) { + std::vector inputs{1, 2, 4, 5, 6, 7, 8, 9, 10, 11}; + auto allocator = UniqueResourceAllocator::New(inputs); + if (!allocator) { + GTEST_SKIP() << "Memory allocation failed but we aren't testing it."; + } + + auto take_range_5_12 = allocator->TakeRange(5, 12); + // shall fail as 3 is missing + auto take_range_2_4 = allocator->TakeRange(2, 4); + + ASSERT_TRUE(take_range_5_12); + ASSERT_FALSE(take_range_2_4); +} + +TEST_F(CvdIdAllocatorTest, Reclaim) { + std::vector inputs{1, 2, 4, 5, 6, 7, 8, 9, 10, 11}; + auto allocator = UniqueResourceAllocator::New(inputs); + if (!allocator) { + GTEST_SKIP() << "Memory allocation failed but we aren't testing it."; + } + unsigned one_resource = 0; + { + auto take_range_5_12 = allocator->TakeRange(5, 12); + auto any_single_item = allocator->UniqueItem(); + + ASSERT_TRUE(take_range_5_12); + ASSERT_TRUE(any_single_item); + one_resource = any_single_item->Get(); + + ASSERT_FALSE(allocator->TakeRange(5, 12)); + ASSERT_FALSE(allocator->Take(one_resource)); + } + // take_range_5_12 went out of scope, so resources were reclaimed + ASSERT_TRUE(allocator->TakeRange(5, 12)); + ASSERT_TRUE(allocator->Take(one_resource)); +} + +TEST(CvdIdAllocatorExpandTest, Expand) { + std::vector inputs{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + auto allocator = UniqueResourceAllocator::New(inputs); + if (!allocator) { + GTEST_SKIP() << "Memory allocation failed but we aren't testing it."; + } + auto hold_6_to_10 = allocator->TakeRange(6, 11); + if (!hold_6_to_10) { + GTEST_SKIP() << "TakeRange(6, 11) failed but it's not what is tested here"; + } + + auto expand = + allocator->ExpandPool(std::vector{2, 4, 6, 8, 12, 14}); + auto take_12 = allocator->Take(12); + auto take_14 = allocator->Take(14); + auto take_6 = allocator->Take(6); + + std::vector expected_return_from_expand{2, 4, 6, 8}; + ASSERT_EQ(expand, expected_return_from_expand); + ASSERT_TRUE(take_12); + ASSERT_TRUE(take_14); + ASSERT_FALSE(take_6); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator_test.h b/base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator_test.h new file mode 100644 index 0000000000..82cd1c0ff8 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator_test.h @@ -0,0 +1,34 @@ +// +// Copyright (C) 2022 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 + +#include + +#include + +namespace cuttlefish { + +// Get one unique item at a time +class OneEachTest : public testing::TestWithParam> {}; + +/* + * ClaimAll, StrideBeyond, Consecutive, Take, TakeAll, TakeRange, + * Reclaim + * + */ +class CvdIdAllocatorTest : public testing::Test {}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/unix_sockets.cpp b/base/cvd/cuttlefish/common/libs/utils/unix_sockets.cpp new file mode 100644 index 0000000000..bf907989d1 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/unix_sockets.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2021 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. + */ +#include "common/libs/utils/unix_sockets.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" + +// This would use android::base::ReceiveFileDescriptors, but it silently drops +// SCM_CREDENTIALS control messages. + +namespace cuttlefish { + +ControlMessage ControlMessage::FromRaw(const cmsghdr* cmsg) { + ControlMessage message; + message.data_ = + std::vector((char*)cmsg, ((char*)cmsg) + cmsg->cmsg_len); + if (message.IsFileDescriptors()) { + size_t fdcount = + static_cast(cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + for (int i = 0; i < fdcount; i++) { + // Use memcpy as CMSG_DATA may be unaligned + int fd = -1; + memcpy(&fd, CMSG_DATA(cmsg) + (i * sizeof(int)), sizeof(fd)); + message.fds_.push_back(fd); + } + } + return message; +} + +Result ControlMessage::FromFileDescriptors( + const std::vector& fds) { + ControlMessage message; + message.data_.resize(CMSG_SPACE(fds.size() * sizeof(int)), 0); + message.Raw()->cmsg_len = CMSG_LEN(fds.size() * sizeof(int)); + message.Raw()->cmsg_level = SOL_SOCKET; + message.Raw()->cmsg_type = SCM_RIGHTS; + for (int i = 0; i < fds.size(); i++) { + int fd_copy = fds[i]->Fcntl(F_DUPFD_CLOEXEC, 3); + CF_EXPECT(fd_copy >= 0, "Failed to duplicate fd: " << fds[i]->StrError()); + message.fds_.push_back(fd_copy); + // Following the CMSG_DATA spec, use memcpy to avoid alignment issues. + memcpy(CMSG_DATA(message.Raw()) + (i * sizeof(int)), &fd_copy, sizeof(int)); + } + return message; +} + +#ifdef __linux__ +ControlMessage ControlMessage::FromCredentials(const ucred& credentials) { + ControlMessage message; + message.data_.resize(CMSG_SPACE(sizeof(ucred)), 0); + message.Raw()->cmsg_len = CMSG_LEN(sizeof(ucred)); + message.Raw()->cmsg_level = SOL_SOCKET; + message.Raw()->cmsg_type = SCM_CREDENTIALS; + // Following the CMSG_DATA spec, use memcpy to avoid alignment issues. + memcpy(CMSG_DATA(message.Raw()), &credentials, sizeof(credentials)); + return message; +} +#endif + +ControlMessage::ControlMessage(ControlMessage&& existing) { + // Enforce that the old ControlMessage is left empty, so it doesn't try to + // close any file descriptors. https://stackoverflow.com/a/17735913 + data_ = std::move(existing.data_); + existing.data_.clear(); + fds_ = std::move(existing.fds_); + existing.fds_.clear(); +} + +ControlMessage& ControlMessage::operator=(ControlMessage&& existing) { + // Enforce that the old ControlMessage is left empty, so it doesn't try to + // close any file descriptors. https://stackoverflow.com/a/17735913 + data_ = std::move(existing.data_); + existing.data_.clear(); + fds_ = std::move(existing.fds_); + existing.fds_.clear(); + return *this; +} + +ControlMessage::~ControlMessage() { + for (const auto& fd : fds_) { + if (close(fd) != 0) { + PLOG(ERROR) << "Failed to close fd " << fd + << ", may have leaked or closed prematurely"; + } + } +} + +cmsghdr* ControlMessage::Raw() { + return reinterpret_cast(data_.data()); +} + +const cmsghdr* ControlMessage::Raw() const { + return reinterpret_cast(data_.data()); +} + +#ifdef __linux__ +bool ControlMessage::IsCredentials() const { + bool right_level = Raw()->cmsg_level == SOL_SOCKET; + bool right_type = Raw()->cmsg_type == SCM_CREDENTIALS; + bool enough_data = Raw()->cmsg_len >= sizeof(cmsghdr) + sizeof(ucred); + return right_level && right_type && enough_data; +} + +Result ControlMessage::AsCredentials() const { + CF_EXPECT(IsCredentials(), "Control message does not hold a credential"); + ucred credentials; + memcpy(&credentials, CMSG_DATA(Raw()), sizeof(ucred)); + return credentials; +} +#endif + +bool ControlMessage::IsFileDescriptors() const { + bool right_level = Raw()->cmsg_level == SOL_SOCKET; + bool right_type = Raw()->cmsg_type == SCM_RIGHTS; + return right_level && right_type; +} + +Result> ControlMessage::AsSharedFDs() const { + CF_EXPECT(IsFileDescriptors(), "Message does not contain file descriptors"); + size_t fdcount = + static_cast(Raw()->cmsg_len - CMSG_LEN(0)) / sizeof(int); + std::vector shared_fds; + for (int i = 0; i < fdcount; i++) { + // Use memcpy as CMSG_DATA may be unaligned + int fd = -1; + memcpy(&fd, CMSG_DATA(Raw()) + (i * sizeof(int)), sizeof(fd)); + SharedFD shared_fd = SharedFD::Dup(fd); + CF_EXPECT(shared_fd->IsOpen(), "Could not dup FD " << fd); + shared_fds.push_back(shared_fd); + } + return shared_fds; +} + +bool UnixSocketMessage::HasFileDescriptors() { + for (const auto& control_message : control) { + if (control_message.IsFileDescriptors()) { + return true; + } + } + return false; +} +Result> UnixSocketMessage::FileDescriptors() { + std::vector fds; + for (const auto& control_message : control) { + if (control_message.IsFileDescriptors()) { + auto additional_fds = CF_EXPECT(control_message.AsSharedFDs()); + fds.insert(fds.end(), additional_fds.begin(), additional_fds.end()); + } + } + return fds; +} +#ifdef __linux__ +bool UnixSocketMessage::HasCredentials() { + for (const auto& control_message : control) { + if (control_message.IsCredentials()) { + return true; + } + } + return false; +} +Result UnixSocketMessage::Credentials() { + std::vector credentials; + for (const auto& control_message : control) { + if (control_message.IsCredentials()) { + auto creds = CF_EXPECT(control_message.AsCredentials(), + "Message claims to have credentials but does not"); + credentials.push_back(creds); + } + } + if (credentials.size() == 0) { + return CF_ERR("No credentials present"); + } else if (credentials.size() == 1) { + return credentials[0]; + } else { + return CF_ERR("Excepted 1 credential, received " << credentials.size()); + } +} +#endif + +UnixMessageSocket::UnixMessageSocket(SharedFD socket) : socket_(socket) { + socklen_t ln = sizeof(max_message_size_); + CHECK(socket->GetSockOpt(SOL_SOCKET, SO_SNDBUF, &max_message_size_, &ln) == 0) + << "error: can't retrieve socket max message size: " + << socket->StrError(); +} + +#ifdef __linux__ +Result UnixMessageSocket::EnableCredentials(bool enable) { + int flag = enable ? 1 : 0; + if (socket_->SetSockOpt(SOL_SOCKET, SO_PASSCRED, &flag, sizeof(flag)) != 0) { + return CF_ERR("Could not set credential status to " << enable << ": " + << socket_->StrError()); + } + return {}; +} +#endif + +Result UnixMessageSocket::WriteMessage(const UnixSocketMessage& message) { + auto control_size = 0; + for (const auto& control : message.control) { + control_size += control.data_.size(); + } + std::vector message_control(control_size, 0); + msghdr message_header{}; + message_header.msg_control = message_control.data(); + message_header.msg_controllen = message_control.size(); + auto cmsg = CMSG_FIRSTHDR(&message_header); + for (const ControlMessage& control : message.control) { + CF_EXPECT(cmsg != nullptr, + "Control messages did not fit in control buffer"); + /* size() should match CMSG_SPACE */ + memcpy(cmsg, control.data_.data(), control.data_.size()); + cmsg = CMSG_NXTHDR(&message_header, cmsg); + } + + iovec message_iovec; + message_iovec.iov_base = (void*)message.data.data(); + message_iovec.iov_len = message.data.size(); + message_header.msg_name = nullptr; + message_header.msg_namelen = 0; + message_header.msg_iov = &message_iovec; + message_header.msg_iovlen = 1; + message_header.msg_flags = 0; + + auto bytes_sent = socket_->SendMsg(&message_header, MSG_NOSIGNAL); + CF_EXPECT(bytes_sent >= 0, "Failed to send message: " << socket_->StrError()); + CF_EXPECT(bytes_sent == message.data.size(), + "Failed to send entire message. Sent " + << bytes_sent << ", excepted to send " << message.data.size()); + return {}; +} + +Result UnixMessageSocket::ReadMessage() { + msghdr message_header{}; + std::vector message_control(max_message_size_, 0); + message_header.msg_control = message_control.data(); + message_header.msg_controllen = message_control.size(); + std::vector message_data(max_message_size_, 0); + iovec message_iovec; + message_iovec.iov_base = message_data.data(); + message_iovec.iov_len = message_data.size(); + message_header.msg_iov = &message_iovec; + message_header.msg_iovlen = 1; + message_header.msg_name = nullptr; + message_header.msg_namelen = 0; + message_header.msg_flags = 0; + +#ifdef __linux__ + auto bytes_read = socket_->RecvMsg(&message_header, MSG_CMSG_CLOEXEC); +#elif defined(__APPLE__) + auto bytes_read = socket_->RecvMsg(&message_header, 0); +#else +#error "Unsupported operating system" +#endif + CF_EXPECT(bytes_read >= 0, "Read error: " << socket_->StrError()); + CF_EXPECT(!(message_header.msg_flags & MSG_TRUNC), + "Message was truncated on read"); + CF_EXPECT(!(message_header.msg_flags & MSG_CTRUNC), + "Message control data was truncated on read"); +#ifdef __linux__ + CF_EXPECT(!(message_header.msg_flags & MSG_ERRQUEUE), "Error queue error"); +#endif + UnixSocketMessage managed_message; + for (auto cmsg = CMSG_FIRSTHDR(&message_header); cmsg != nullptr; + cmsg = CMSG_NXTHDR(&message_header, cmsg)) { + managed_message.control.emplace_back(ControlMessage::FromRaw(cmsg)); + } + message_data.resize(bytes_read); + managed_message.data = std::move(message_data); + + return managed_message; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/unix_sockets.h b/base/cvd/cuttlefish/common/libs/utils/unix_sockets.h new file mode 100644 index 0000000000..1cfc68da2f --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/unix_sockets.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 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 + +#include + +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +struct ControlMessage { + public: + static ControlMessage FromRaw(const cmsghdr*); + static Result FromFileDescriptors( + const std::vector&); +#ifdef __linux__ + static ControlMessage FromCredentials(const ucred&); +#endif + ControlMessage(const ControlMessage&) = delete; + ControlMessage(ControlMessage&&); + ~ControlMessage(); + ControlMessage& operator=(const ControlMessage&) = delete; + ControlMessage& operator=(ControlMessage&&); + + const cmsghdr* Raw() const; + +#ifdef __linux__ + bool IsCredentials() const; + Result AsCredentials() const; +#endif + + bool IsFileDescriptors() const; + Result> AsSharedFDs() const; + + private: + friend class UnixMessageSocket; + ControlMessage() = default; + cmsghdr* Raw(); + + std::vector data_; + std::vector fds_; +}; + +struct UnixSocketMessage { + std::vector data; + std::vector control; + + bool HasFileDescriptors(); + Result> FileDescriptors(); +#ifdef __linux__ + bool HasCredentials(); + Result Credentials(); +#endif +}; + +class UnixMessageSocket { + public: + UnixMessageSocket(SharedFD); + [[nodiscard]] Result WriteMessage(const UnixSocketMessage&); + Result ReadMessage(); + +#ifdef __linux__ + [[nodiscard]] Result EnableCredentials(bool); +#endif + + private: + SharedFD socket_; + std::uint32_t max_message_size_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/unix_sockets_test.cpp b/base/cvd/cuttlefish/common/libs/utils/unix_sockets_test.cpp new file mode 100644 index 0000000000..e063700247 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/unix_sockets_test.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2021 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. + */ + +#include "common/libs/utils/unix_sockets.h" + +#include +#include + +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" + +namespace cuttlefish { + +SharedFD CreateMemFDWithData(const std::string& data) { + auto memfd = SharedFD::MemfdCreate(""); + CHECK(WriteAll(memfd, data) == data.size()) << memfd->StrError(); + CHECK(memfd->LSeek(0, SEEK_SET) == 0); + return memfd; +} + +std::string ReadAllFDData(SharedFD fd) { + std::string data; + CHECK(ReadAll(fd, &data) > 0) << fd->StrError(); + return data; +} + +TEST(UnixSocketMessage, ExtractFileDescriptors) { + auto memfd1 = CreateMemFDWithData("abc"); + auto memfd2 = CreateMemFDWithData("def"); + + UnixSocketMessage message; + auto control1 = ControlMessage::FromFileDescriptors({memfd1}); + ASSERT_TRUE(control1.ok()) << control1.error().Trace(); + message.control.emplace_back(std::move(*control1)); + auto control2 = ControlMessage::FromFileDescriptors({memfd2}); + ASSERT_TRUE(control2.ok()) << control2.error().Trace(); + message.control.emplace_back(std::move(*control2)); + + ASSERT_TRUE(message.HasFileDescriptors()); + auto fds = message.FileDescriptors(); + ASSERT_TRUE(fds.ok()); + ASSERT_EQ("abc", ReadAllFDData((*fds)[0])); + ASSERT_EQ("def", ReadAllFDData((*fds)[1])); +} + +std::pair UnixMessageSocketPair() { + SharedFD sock1, sock2; + CHECK(SharedFD::SocketPair(AF_UNIX, SOCK_SEQPACKET, 0, &sock1, &sock2)); + return {UnixMessageSocket(sock1), UnixMessageSocket(sock2)}; +} + +TEST(UnixMessageSocket, SendPlainMessage) { + auto [writer, reader] = UnixMessageSocketPair(); + UnixSocketMessage message_in = {{1, 2, 3}, {}}; + auto write_result = writer.WriteMessage(message_in); + ASSERT_TRUE(write_result.ok()) << write_result.error().Trace(); + + auto message_out = reader.ReadMessage(); + ASSERT_TRUE(message_out.ok()) << message_out.error().Trace(); + ASSERT_EQ(message_in.data, message_out->data); + ASSERT_EQ(0, message_out->control.size()); +} + +TEST(UnixMessageSocket, SendFileDescriptor) { + auto [writer, reader] = UnixMessageSocketPair(); + + UnixSocketMessage message_in = {{4, 5, 6}, {}}; + auto control_in = + ControlMessage::FromFileDescriptors({CreateMemFDWithData("abc")}); + ASSERT_TRUE(control_in.ok()) << control_in.error().Trace(); + message_in.control.emplace_back(std::move(*control_in)); + auto write_result = writer.WriteMessage(message_in); + ASSERT_TRUE(write_result.ok()) << write_result.error().Trace(); + + auto message_out = reader.ReadMessage(); + ASSERT_TRUE(message_out.ok()) << message_out.error().Trace(); + ASSERT_EQ(message_in.data, message_out->data); + + ASSERT_EQ(1, message_out->control.size()); + auto fds_out = message_out->control[0].AsSharedFDs(); + ASSERT_TRUE(fds_out.ok()) << fds_out.error().Trace(); + ASSERT_EQ(1, fds_out->size()); + ASSERT_EQ("abc", ReadAllFDData((*fds_out)[0])); +} + +TEST(UnixMessageSocket, SendTwoFileDescriptors) { + auto memfd1 = CreateMemFDWithData("abc"); + auto memfd2 = CreateMemFDWithData("def"); + + auto [writer, reader] = UnixMessageSocketPair(); + UnixSocketMessage message_in = {{7, 8, 9}, {}}; + auto control_in = ControlMessage::FromFileDescriptors({memfd1, memfd2}); + ASSERT_TRUE(control_in.ok()) << control_in.error().Trace(); + message_in.control.emplace_back(std::move(*control_in)); + auto write_result = writer.WriteMessage(message_in); + ASSERT_TRUE(write_result.ok()) << write_result.error().Trace(); + + auto message_out = reader.ReadMessage(); + ASSERT_TRUE(message_out.ok()) << message_out.error().Trace(); + ASSERT_EQ(message_in.data, message_out->data); + + ASSERT_EQ(1, message_out->control.size()); + auto fds_out = message_out->control[0].AsSharedFDs(); + ASSERT_TRUE(fds_out.ok()) << fds_out.error().Trace(); + ASSERT_EQ(2, fds_out->size()); + + ASSERT_EQ("abc", ReadAllFDData((*fds_out)[0])); + ASSERT_EQ("def", ReadAllFDData((*fds_out)[1])); +} + +TEST(UnixMessageSocket, SendCredentials) { + auto [writer, reader] = UnixMessageSocketPair(); + auto writer_creds_status = writer.EnableCredentials(true); + ASSERT_TRUE(writer_creds_status.ok()) << writer_creds_status.error().Trace(); + auto reader_creds_status = reader.EnableCredentials(true); + ASSERT_TRUE(reader_creds_status.ok()) << reader_creds_status.error().Trace(); + + ucred credentials_in; + credentials_in.pid = getpid(); + credentials_in.uid = getuid(); + credentials_in.gid = getgid(); + UnixSocketMessage message_in = {{1, 5, 9}, {}}; + auto control_in = ControlMessage::FromCredentials(credentials_in); + message_in.control.emplace_back(std::move(control_in)); + auto write_result = writer.WriteMessage(message_in); + ASSERT_TRUE(write_result.ok()) << write_result.error().Trace(); + + auto message_out = reader.ReadMessage(); + ASSERT_TRUE(message_out.ok()) << message_out.error().Trace(); + ASSERT_EQ(message_in.data, message_out->data); + + ASSERT_EQ(1, message_out->control.size()); + auto credentials_out = message_out->control[0].AsCredentials(); + ASSERT_TRUE(credentials_out.ok()) << credentials_out.error().Trace(); + ASSERT_EQ(credentials_in.pid, credentials_out->pid); + ASSERT_EQ(credentials_in.uid, credentials_out->uid); + ASSERT_EQ(credentials_in.gid, credentials_out->gid); +} + +TEST(UnixMessageSocket, BadCredentialsBlocked) { + auto [writer, reader] = UnixMessageSocketPair(); + auto writer_creds_status = writer.EnableCredentials(true); + ASSERT_TRUE(writer_creds_status.ok()) << writer_creds_status.error().Trace(); + auto reader_creds_status = reader.EnableCredentials(true); + ASSERT_TRUE(reader_creds_status.ok()) << reader_creds_status.error().Trace(); + + ucred credentials_in; + // This assumes the test is running without root privileges + credentials_in.pid = getpid() + 1; + credentials_in.uid = getuid() + 1; + credentials_in.gid = getgid() + 1; + + UnixSocketMessage message_in = {{2, 4, 6}, {}}; + auto control_in = ControlMessage::FromCredentials(credentials_in); + message_in.control.emplace_back(std::move(control_in)); + auto write_result = writer.WriteMessage(message_in); + ASSERT_FALSE(write_result.ok()) << write_result.error().Trace(); +} + +TEST(UnixMessageSocket, AutoCredentials) { + auto [writer, reader] = UnixMessageSocketPair(); + auto writer_creds_status = writer.EnableCredentials(true); + ASSERT_TRUE(writer_creds_status.ok()) << writer_creds_status.error().Trace(); + auto reader_creds_status = reader.EnableCredentials(true); + ASSERT_TRUE(reader_creds_status.ok()) << reader_creds_status.error().Trace(); + + UnixSocketMessage message_in = {{3, 6, 9}, {}}; + auto write_result = writer.WriteMessage(message_in); + ASSERT_TRUE(write_result.ok()) << write_result.error().Trace(); + + auto message_out = reader.ReadMessage(); + ASSERT_TRUE(message_out.ok()) << message_out.error().Trace(); + ASSERT_EQ(message_in.data, message_out->data); + + ASSERT_EQ(1, message_out->control.size()); + auto credentials_out = message_out->control[0].AsCredentials(); + ASSERT_TRUE(credentials_out.ok()) << credentials_out.error().Trace(); + ASSERT_EQ(getpid(), credentials_out->pid); + ASSERT_EQ(getuid(), credentials_out->uid); + ASSERT_EQ(getgid(), credentials_out->gid); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/users.cpp b/base/cvd/cuttlefish/common/libs/utils/users.cpp new file mode 100644 index 0000000000..c6c82aa592 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/users.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "common/libs/utils/users.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/utils/contains.h" + +namespace cuttlefish { +namespace { +std::vector GetSuplementaryGroups() { + int num_groups = getgroups(0, nullptr); + if (num_groups < 0) { + LOG(ERROR) << "Unable to get number of suplementary groups: " + << std::strerror(errno); + return {}; + } + std::vector groups(num_groups + 1); + int retval = getgroups(groups.size(), groups.data()); + if (retval < 0) { + LOG(ERROR) << "Error obtaining list of suplementary groups (list size: " + << groups.size() << "): " << std::strerror(errno); + return {}; + } + return groups; +} +} // namespace + +gid_t GroupIdFromName(const std::string& group_name) { + struct group grp{}; + struct group* grp_p{}; + std::vector buffer(100); + int result = 0; + while(true) { + result = getgrnam_r(group_name.c_str(), &grp, buffer.data(), buffer.size(), + &grp_p); + if (result != ERANGE) { + break; + } + buffer.resize(2*buffer.size()); + } + if (result == 0) { + if (grp_p != nullptr) { + return grp.gr_gid; + } else { + // Caller may be checking with non-existent group name + return -1; + } + } else { + LOG(ERROR) << "Unable to get group id for group " << group_name << ": " + << std::strerror(result); + return -1; + } +} + +bool InGroup(const std::string& group) { + auto gid = GroupIdFromName(group); + if (gid == static_cast(-1)) { + return false; + } + + if (gid == getegid()) { + return true; + } + + auto groups = GetSuplementaryGroups(); + return Contains(groups, gid); +} + +Result SystemWideUserHome() { + auto uid = getuid(); + // getpwuid() is not thread-safe, so we need a lock across all calls + static std::mutex getpwuid_mutex; + std::string home_dir; + { + std::lock_guard lock(getpwuid_mutex); + const auto entry = getpwuid(uid); + if (entry) { + home_dir = entry->pw_dir; + } + endpwent(); + if (home_dir.empty()) { + return CF_ERRNO("Failed to find the home directory using " << uid); + } + } + std::string home_realpath; + if (!android::base::Realpath(home_dir, &home_realpath)) { + return CF_ERRNO("Failed to convert " << home_dir << " to its Realpath"); + } + return home_realpath; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/users.h b/base/cvd/cuttlefish/common/libs/utils/users.h new file mode 100644 index 0000000000..7952123253 --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/users.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 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 + +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +gid_t GroupIdFromName(const std::string& group_name); +bool InGroup(const std::string& group); + +/** + * returns the user's home defined by the system + * + * This is done not by using HOME but by calling getpwuid(getuid()) + */ +Result SystemWideUserHome(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/vsock_connection.cpp b/base/cvd/cuttlefish/common/libs/utils/vsock_connection.cpp new file mode 100644 index 0000000000..310869c3ac --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/vsock_connection.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2021 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. + */ + +#include "common/libs/utils/vsock_connection.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_select.h" + +namespace cuttlefish { + +VsockConnection::~VsockConnection() { Disconnect(); } + +std::future VsockConnection::ConnectAsync( + unsigned int port, unsigned int cid, + std::optional vhost_user_vsock_cid_) { + return std::async(std::launch::async, + [this, port, cid, vhost_user_vsock_cid_]() { + return Connect(port, cid, vhost_user_vsock_cid_); + }); +} + +void VsockConnection::Disconnect() { + // We need to serialize all accesses to the SharedFD. + std::lock_guard read_lock(read_mutex_); + std::lock_guard write_lock(write_mutex_); + + LOG(INFO) << "Disconnecting with fd status:" << fd_->StrError(); + fd_->Shutdown(SHUT_RDWR); + if (disconnect_callback_) { + disconnect_callback_(); + } + fd_->Close(); +} + +void VsockConnection::SetDisconnectCallback(std::function callback) { + disconnect_callback_ = callback; +} + +bool VsockConnection::IsConnected() { + // We need to serialize all accesses to the SharedFD. + std::lock_guard read_lock(read_mutex_); + std::lock_guard write_lock(write_mutex_); + + return fd_->IsOpen(); +} + +bool VsockConnection::DataAvailable() { + SharedFDSet read_set; + + // We need to serialize all accesses to the SharedFD. + std::lock_guard read_lock(read_mutex_); + std::lock_guard write_lock(write_mutex_); + + read_set.Set(fd_); + struct timeval timeout = {0, 0}; + return Select(&read_set, nullptr, nullptr, &timeout) > 0; +} + +int32_t VsockConnection::Read() { + std::lock_guard lock(read_mutex_); + int32_t result; + if (ReadExactBinary(fd_, &result) != sizeof(result)) { + Disconnect(); + return 0; + } + return result; +} + +bool VsockConnection::Read(std::vector& data) { + std::lock_guard lock(read_mutex_); + return ReadExact(fd_, &data) == data.size(); +} + +std::vector VsockConnection::Read(size_t size) { + if (size == 0) { + return {}; + } + std::lock_guard lock(read_mutex_); + std::vector result(size); + if (ReadExact(fd_, &result) != size) { + Disconnect(); + return {}; + } + return result; +} + +std::future> VsockConnection::ReadAsync(size_t size) { + return std::async(std::launch::async, [this, size]() { return Read(size); }); +} + +// Message format is buffer size followed by buffer data +std::vector VsockConnection::ReadMessage() { + std::lock_guard lock(read_mutex_); + auto size = Read(); + if (size < 0) { + Disconnect(); + return {}; + } + return Read(size); +} + +bool VsockConnection::ReadMessage(std::vector& data) { + std::lock_guard lock(read_mutex_); + auto size = Read(); + if (size < 0) { + Disconnect(); + return false; + } + data.resize(size); + return Read(data); +} + +std::future> VsockConnection::ReadMessageAsync() { + return std::async(std::launch::async, [this]() { return ReadMessage(); }); +} + +Json::Value VsockConnection::ReadJsonMessage() { + auto msg = ReadMessage(); + Json::CharReaderBuilder builder; + std::unique_ptr reader(builder.newCharReader()); + Json::Value json_msg; + std::string errors; + if (!reader->parse(msg.data(), msg.data() + msg.size(), &json_msg, &errors)) { + return {}; + } + return json_msg; +} + +std::future VsockConnection::ReadJsonMessageAsync() { + return std::async(std::launch::async, [this]() { return ReadJsonMessage(); }); +} + +bool VsockConnection::Write(int32_t data) { + std::lock_guard lock(write_mutex_); + if (WriteAllBinary(fd_, &data) != sizeof(data)) { + Disconnect(); + return false; + } + return true; +} + +bool VsockConnection::Write(const char* data, unsigned int size) { + std::lock_guard lock(write_mutex_); + if (WriteAll(fd_, data, size) != size) { + Disconnect(); + return false; + } + return true; +} + +bool VsockConnection::Write(const std::vector& data) { + return Write(data.data(), data.size()); +} + +// Message format is buffer size followed by buffer data +bool VsockConnection::WriteMessage(const std::string& data) { + return Write(data.size()) && Write(data.c_str(), data.length()); +} + +bool VsockConnection::WriteMessage(const std::vector& data) { + std::lock_guard lock(write_mutex_); + return Write(data.size()) && Write(data); +} + +bool VsockConnection::WriteMessage(const Json::Value& data) { + Json::StreamWriterBuilder factory; + std::string message_str = Json::writeString(factory, data); + return WriteMessage(message_str); +} + +bool VsockConnection::WriteStrides(const char* data, unsigned int size, + unsigned int num_strides, int stride_size) { + const char* src = data; + for (unsigned int i = 0; i < num_strides; ++i, src += stride_size) { + if (!Write(src, size)) { + return false; + } + } + return true; +} + +bool VsockClientConnection::Connect(unsigned int port, unsigned int cid, + std::optional vhost_user) { + fd_ = + SharedFD::VsockClient(cid, port, SOCK_STREAM, vhost_user ? true : false); + if (!fd_->IsOpen()) { + LOG(ERROR) << "Failed to connect:" << fd_->StrError(); + } + return fd_->IsOpen(); +} + +VsockServerConnection::~VsockServerConnection() { ServerShutdown(); } + +void VsockServerConnection::ServerShutdown() { + if (server_fd_->IsOpen()) { + LOG(INFO) << __FUNCTION__ + << ": server fd status:" << server_fd_->StrError(); + server_fd_->Shutdown(SHUT_RDWR); + server_fd_->Close(); + } +} + +bool VsockServerConnection::Connect(unsigned int port, unsigned int cid, + std::optional vhost_user_vsock_cid) { + if (!server_fd_->IsOpen()) { + server_fd_ = cuttlefish::SharedFD::VsockServer(port, SOCK_STREAM, + vhost_user_vsock_cid, cid); + } + if (server_fd_->IsOpen()) { + fd_ = SharedFD::Accept(*server_fd_); + return fd_->IsOpen(); + } else { + return false; + } +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/common/libs/utils/vsock_connection.h b/base/cvd/cuttlefish/common/libs/utils/vsock_connection.h new file mode 100644 index 0000000000..29e2c93bcf --- /dev/null +++ b/base/cvd/cuttlefish/common/libs/utils/vsock_connection.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 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 + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/libs/fs/shared_fd.h" + +namespace cuttlefish { + +class VsockConnection { + public: + virtual ~VsockConnection(); + virtual bool Connect(unsigned int port, unsigned int cid, + std::optional vhost_user_vsock_cid) = 0; + virtual void Disconnect(); + std::future ConnectAsync(unsigned int port, unsigned int cid, + std::optional vhost_user_vsock_cid); + void SetDisconnectCallback(std::function callback); + + bool IsConnected(); + bool DataAvailable(); + int32_t Read(); + bool Read(std::vector& data); + std::vector Read(size_t size); + std::future> ReadAsync(size_t size); + + bool ReadMessage(std::vector& data); + std::vector ReadMessage(); + std::future> ReadMessageAsync(); + Json::Value ReadJsonMessage(); + std::future ReadJsonMessageAsync(); + + bool Write(int32_t data); + bool Write(const char* data, unsigned int size); + bool Write(const std::vector& data); + bool WriteMessage(const std::string& data); + bool WriteMessage(const std::vector& data); + bool WriteMessage(const Json::Value& data); + bool WriteStrides(const char* data, unsigned int size, + unsigned int num_strides, int stride_size); + + protected: + std::recursive_mutex read_mutex_; + std::recursive_mutex write_mutex_; + std::function disconnect_callback_; + SharedFD fd_; +}; + +class VsockClientConnection : public VsockConnection { + public: + // the value of vhost_user_vsock_cid isn't actually used, it works like bool, + // so any value except nullopt means true + bool Connect(unsigned int port, unsigned int cid, + std::optional vhost_user) override; +}; + +class VsockServerConnection : public VsockConnection { + public: + virtual ~VsockServerConnection(); + void ServerShutdown(); + bool Connect(unsigned int port, unsigned int cid, + std::optional vhost_user_vsock_cid) override; + + private: + SharedFD server_fd_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/flags_defaults.h b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags_defaults.h new file mode 100644 index 0000000000..153007d04b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags_defaults.h @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2022 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 + +#include "host/libs/config/cuttlefish_config.h" + +#define CF_DEFAULTS_DYNAMIC_STRING "" +#define CF_DEFAULTS_DYNAMIC_INT 0 + +// Common configs paramneters +#define CF_DEFAULTS_NUM_INSTANCES 1 +#define CF_DEFAULTS_INSTANCE_NUMS CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_BASE_INSTANCE_NUM cuttlefish::GetInstance() +#define CF_DEFAULTS_ASSEMBLY_DIR \ + (StringFromEnv("HOME", ".") + "/cuttlefish_assembly") +#define CF_DEFAULTS_INSTANCE_DIR (StringFromEnv("HOME", ".") + "/cuttlefish") + +#define CF_DEFAULTS_SYSTEM_IMAGE_DIR CF_DEFAULTS_DYNAMIC_STRING + +// Instance specific parameters +// VM default parameters +#define CF_DEFAULTS_DISPLAY_DPI 320 +#define CF_DEFAULTS_DISPLAY_REFRESH_RATE 60 +#define CF_DEFAULTS_DISPLAY_WIDTH 720 +#define CF_DEFAULTS_DISPLAY_HEIGHT 1280 +#define CF_DEFAULTS_DISPLAYS_TEXTPROTO "" +#define CF_DEFAULTS_CPUS 2 +#define CF_DEFAULTS_RESUME true +#define CF_DEFAULTS_DAEMON false +#define CF_DEFAULTS_VM_MANAGER CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_VSOCK_GUEST_CID cuttlefish::GetDefaultVsockCid() +#define CF_DEFAULTS_VHOST_USER_VSOCK cuttlefish::kVhostUserVsockModeAuto +#define CF_DEFAULTS_ENABLE_MINIMAL_MODE false +#define CF_DEFAULTS_RESTART_SUBPROCESSES false +#define CF_DEFAULTS_SETUPWIZARD_MODE "DISABLED" +#define CF_DEFAULTS_SMT false +#define CF_DEFAULTS_USE_ALLOCD false +#define CF_DEFAULTS_USE_SDCARD true +#define CF_DEFAULTS_UUID \ + cuttlefish::ForCurrentInstance(cuttlefish::kDefaultUuidPrefix) +#define CF_DEFAULTS_FILE_VERBOSITY "DEBUG" +#define CF_DEFAULTS_VERBOSITY "INFO" +#define CF_DEFAULTS_RUN_FILE_DISCOVERY true +#define CF_DEFAULTS_MEMORY_MB CF_DEFAULTS_DYNAMIC_INT +#define CF_DEFAULTS_SHARE_SCHED_CORE false +#define CF_DEFAULTS_TRACK_HOST_TOOLS_CRC false +// TODO: defined twice, please remove redundant definitions +#define CF_DEFAULTS_USE_OVERLAY true +#define CF_DEFAULTS_DEVICE_EXTERNAL_NETWORK "tap" + +// crosvm default parameters +#define CF_DEFAULTS_CROSVM_BINARY HostBinaryPath("crosvm") +#define CF_DEFAULTS_SECCOMP_POLICY_DIR cuttlefish::GetSeccompPolicyDir() +#define CF_DEFAULTS_ENABLE_SANDBOX false +#define CF_DEFAULTS_ENABLE_VIRTIOFS false + +// Qemu default parameters +#define CF_DEFAULTS_QEMU_BINARY_DIR cuttlefish::DefaultQemuBinaryDir() + +// Gem5 default parameters +#define CF_DEFAULTS_GEM5_BINARY_DIR HostBinaryPath("gem5") +#define CF_DEFAULTS_GEM5_CHECKPOINT_DIR CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_GEM5_DEBUG_FILE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_GEM5_DEBUG_FLAGS CF_DEFAULTS_DYNAMIC_STRING + +// Boot default parameters +#define CF_DEFAULTS_BOOT_SLOT CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_BOOTLOADER CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_ENABLE_BOOTANIMATION true +#define CF_DEFAULTS_EXTRA_BOOTCONFIG_ARGS CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_PAUSE_IN_BOOTLOADER false +#define CF_DEFAULTS_REBOOT_NOTIFICATION_FD (-1) + +// Security default parameters +#define CF_DEFAULTS_GUEST_ENFORCE_SECURITY true +#define CF_DEFAULTS_USE_RANDOM_SERIAL false +#define CF_DEFAULTS_SERIAL_NUMBER \ + cuttlefish::ForCurrentInstance("CUTTLEFISHCVD") +#define CF_DEFAULTS_SECURE_HALS "keymint,gatekeeper,oemlock" +#define CF_DEFAULTS_PROTECTED_VM false +#define CF_DEFAULTS_MTE false + +// Kernel default parameters +#define CF_DEFAULTS_ENABLE_KERNEL_LOG true +#define CF_DEFAULTS_KGDB false +#define CF_DEFAULTS_GDB_PORT CF_DEFAULTS_DYNAMIC_INT +#define CF_DEFAULTS_CONSOLE false +#define CF_DEFAULTS_EXTRA_KERNEL_CMDLINE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_INITRAMFS_PATH CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_KERNEL_PATH CF_DEFAULTS_DYNAMIC_STRING + +// Disk default parameters +#define CF_DEFAULTS_BLANK_METADATA_IMAGE_MB "64" +#define CF_DEFAULTS_BLANK_SDCARD_IMAGE_MB "2048" +#define CF_DEFAULTS_BOOT_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_DATA_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_INIT_BOOT_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_ANDROID_EFI_LOADER CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_CHROMEOS_DISK "" +#define CF_DEFAULTS_CHROMEOS_KERNEL_PATH "" +#define CF_DEFAULTS_CHROMEOS_ROOT_IMAGE "" +#define CF_DEFAULTS_LINUX_INITRAMFS_PATH CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_LINUX_KERNEL_PATH CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_LINUX_ROOT_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_FUCHSIA_ZEDBOOT_PATH CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_FUCHSIA_MULTIBOOT_BIN_PATH CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_FUCHSIA_ROOT_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_CUSTOM_PARTITION_PATH CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_SUPER_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_VBMETA_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_VBMETA_SYSTEM_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_VBMETA_VENDOR_DLKM_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_VBMETA_SYSTEM_DLKM_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_VENDOR_BOOT_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_DEFAULT_TARGET_ZIP CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_SYSTEM_TARGET_ZIP CF_DEFAULTS_DYNAMIC_STRING + +// Policy default parameters +#define CF_DEFAULTS_DATA_POLICY "use_existing" +#define CF_DEFAULTS_USERDATA_FORMAT USERDATA_FILE_SYSTEM_TYPE +#define CF_DEFAULTS_BLANK_DATA_IMAGE_MB CF_DEFAULTS_DYNAMIC_INT + +// Graphics default parameters +#define CF_DEFAULTS_HWCOMPOSER cuttlefish::kHwComposerAuto +#define CF_DEFAULTS_GPU_MODE cuttlefish::kGpuModeAuto +#define CF_DEFAULTS_GPU_VHOST_USER_MODE cuttlefish::kGpuVhostUserModeAuto +#define CF_DEFAULTS_RECORD_SCREEN false +#define CF_DEFAULTS_GPU_CAPTURE_BINARY CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_ENABLE_GPU_UDMABUF false +#define CF_DEFAULTS_ENABLE_GPU_VHOST_USER false +#define CF_DEFAULTS_DISPLAY0 CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_DISPLAY1 CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_DISPLAY2 CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_DISPLAY3 CF_DEFAULTS_DYNAMIC_STRING + +// Touchpad default parameters +#define CF_DEFAULTS_TOUCHPAD CF_DEFAULTS_DYNAMIC_STRING + +// Camera default parameters +#define CF_DEFAULTS_CAMERA_SERVER_PORT CF_DEFAULTS_DYNAMIC_INT + +// Connectivity default parameters +#define CF_DEFAULTS_RIL_DNS "8.8.8.8" +// Default network handler +#define CF_DEFAULTS_NETSIM false +#define CF_DEFAULTS_NETSIM_BT true +#define CF_DEFAULTS_NETSIM_UWB false + +// Netsim default parameters +#define CF_DEFAULTS_NETSIM_ARGS "" + +// Wifi default parameters +#define CF_DEFAULTS_AP_KERNEL_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_AP_ROOTFS_IMAGE CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_VHOST_NET false +#define CF_DEFAULTS_VHOST_USER_MAC80211_HWSIM CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_WMEDIUMD_CONFIG CF_DEFAULTS_DYNAMIC_STRING + +// UWB default parameters +#define CF_DEFAULTS_ENABLE_HOST_UWB true +#define CF_DEFAULTS_ENABLE_PICA_INSTANCE_NUM 0 + +// Automotive Proxy default parameter +#define CF_DEFAULTS_ENABLE_AUTOMOTIVE_PROXY false + +// Bluetooth default parameters +#define CF_DEFAULTS_ENABLE_HOST_BLUETOOTH true +#define CF_DEFAULTS_ROOTCANAL_INSTANCE_NUM 0 +#define CF_DEFAULTS_ROOTCANAL_ARGS CF_DEFAULTS_DYNAMIC_STRING + +// NFC default parameters +#define CF_DEFAULTS_ENABLE_HOST_NFC true +#define CF_DEFAULTS_CASIMIR_INSTANCE_NUM 0 +#define CF_DEFAULTS_CASIMIR_ARGS CF_DEFAULTS_DYNAMIC_STRING + +// Modem Simulator default parameters +#define CF_DEFAULTS_ENABLE_MODEM_SIMULATOR true +#define CF_DEFAULTS_MODEM_SIMULATOR_SIM_TYPE 1 +#define CF_DEFAULTS_MODEM_SIMULATOR_COUNT 1 + +// Audio default parameters +#define CF_DEFAULTS_ENABLE_AUDIO true + +// Streaming default parameters +#define CF_DEFAULTS_START_WEBRTC false +#define CF_DEFAULTS_START_WEBRTC_SIG_SERVER true +#define CF_DEFAULTS_WEBRTC_DEVICE_ID "cvd-{num}" +#define CF_DEFAULTS_VERIFY_SIG_SERVER_CERTIFICATE false +#define CF_DEFAULTS_WEBRTC_ASSETS_DIR \ + DefaultHostArtifactsPath("usr/share/webrtc/assets") +#define CF_DEFAULTS_WEBRTC_CERTS_DIR \ + DefaultHostArtifactsPath("usr/share/webrtc/certs") +#define CF_DEFAULTS_WEBRTC_SIG_SERVER_ADDR CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_WEBRTC_SIG_SERVER_PATH "/register_device" +#define CF_DEFAULTS_WEBRTC_SIG_SERVER_PORT 443 +#define CF_DEFAULTS_WEBRTC_SIG_SERVER_SECURE true +#define CF_DEFAULTS_TCP_PORT_RANGE "15550:15599" +#define CF_DEFAULTS_UDP_PORT_RANGE "15550:15599" + +// Adb default parameters +// TODO : Replaceconstants with these flags, they're currently defined throug +// GflagsCompatFlag +#define CF_DEFAULTS_RUN_ADB_CONNECTOR true +#define CF_DEFAULTS_ADB_MODE "vsock_half_tunnel" + +// Location default parameters +#define CF_DEFAULTS_START_GNSS_PROXY true +#define CF_DEFAULTS_FIXED_LOCATION_FILE_PATH CF_DEFAULTS_DYNAMIC_STRING +#define CF_DEFAULTS_GNSS_FILE_PATH CF_DEFAULTS_DYNAMIC_STRING + +// Metrics default parameters +// TODO: Defined twice , please remove redundant definitions +#define CF_DEFAULTS_REPORT_ANONYMOUS_USAGE_STATS CF_DEFAULTS_DYNAMIC_STRING + +// MCU emulator default configuration path +#define CF_DEFAULTS_MCU_CONFIG_PATH CF_DEFAULTS_DYNAMIC_STRING + +// Which executables to run under strace by default +#define CF_DEFAULTS_STRACED_HOST_EXECUTABLES "" + +// Whether to use sandbox2 to lock down host processes where policies exist +#define CF_DEFAULTS_HOST_SANDBOX false diff --git a/base/cvd/cuttlefish/host/commands/cvd/README.md b/base/cvd/cuttlefish/host/commands/cvd/README.md new file mode 100644 index 0000000000..38db53bb36 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/README.md @@ -0,0 +1,14 @@ +Frontend command line interface for Cuttlefish command-line tools. + +Separated into a thin client and a persistent background server process that +tracks user state. + +Requests are handled through a handler classes, which may delegate into other +handler classes through `CommandSequenceExecutor`. + +[![Handler diagram](./doc/all_handlers.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/doc/all_handlers.svg) + +A specific example of handlers delegating into other handlers to implement some +functionality: + +[![Load config diagram](./doc/load_config.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/doc/load_config.svg) diff --git a/base/cvd/cuttlefish/host/commands/cvd/acloud/config.cpp b/base/cvd/cuttlefish/host/commands/cvd/acloud/config.cpp new file mode 100644 index 0000000000..0ef33d6344 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/acloud/config.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/acloud/config.h" + +#include + +#include + +#include "common/libs/utils/files.h" +#include "common/libs/utils/users.h" + +namespace cuttlefish { + +AcloudConfig::AcloudConfig(const acloud::UserConfig& usr_cfg) + : launch_args(usr_cfg.launch_args()), + project(usr_cfg.project()), + zone(usr_cfg.zone()), + use_legacy_acloud(usr_cfg.use_legacy_acloud()) { + // TODO(weihsu): Add back fields/variables (except of cheeps and emulator + // fields) in config files. Remove cheeps (Android on ChromeOS) and emulator + // fields. + + // TODO(weihsu): Verify validity of configurations. +} + +template +Result ParseTextProtoConfigHelper(const std::string& config_path) { + std::ifstream t(config_path); + std::stringstream buffer; + buffer << t.rdbuf(); + + ProtoType proto_result; + google::protobuf::TextFormat::Parser p; + CF_EXPECT(p.ParseFromString(buffer.str(), &proto_result), + "Failed to parse config: " << config_path); + return proto_result; +} + +/** + * Return path to default config file. + */ +Result GetDefaultConfigFile() { + const std::string home = CF_EXPECT(SystemWideUserHome()); + return (std::string(home) + "/.config/acloud/acloud.config"); +} + +Result LoadAcloudConfig(const std::string& user_config_path) { + acloud::UserConfig proto_result_user; + if (FileExists(user_config_path)) { + proto_result_user = CF_EXPECT( + ParseTextProtoConfigHelper(user_config_path)); + } else { + const std::string conf_path = CF_EXPECT(GetDefaultConfigFile()); + CF_EXPECT(user_config_path == conf_path, + "The specified config file does not exist."); + + // If the default config does not exist, acloud creates an empty object. + proto_result_user = acloud::UserConfig(); + } + return AcloudConfig(proto_result_user); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/acloud/config.h b/base/cvd/cuttlefish/host/commands/cvd/acloud/config.h new file mode 100644 index 0000000000..c11b85c7bb --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/acloud/config.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 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 + +#include +#include + +#include "internal_config.pb.h" +#include "user_config.pb.h" + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +class AcloudConfig { + public: + AcloudConfig(const acloud::UserConfig&); + ~AcloudConfig() = default; + + public: + // UserConfig/user_config.proto members + std::string launch_args; + + std::string project; + + std::string zone; + + bool use_legacy_acloud; + + // InternalConfig/internal_config.proto members + + // In both config +}; + +Result GetDefaultConfigFile(); +Result LoadAcloudConfig(const std::string& user_config_path); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/acloud/converter.cpp b/base/cvd/cuttlefish/host/commands/cvd/acloud/converter.cpp new file mode 100644 index 0000000000..5fe1c0c08d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/acloud/converter.cpp @@ -0,0 +1,723 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/acloud/converter.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/subprocess.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/acloud/config.h" +#include "host/commands/cvd/acloud/create_converter_parser.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/lock_file.h" +#include "host/commands/cvd/selector/instance_database_utils.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { +namespace { + +// Image names to search +const std::vector kKernelImageNames = {"kernel", "bzImage", + "Image"}; +const std::vector kInitRamFsImageName = {"initramfs.img"}; +const std::vector kBootImageName = {"boot.img"}; +const std::vector kVendorBootImageName = {"vendor_boot.img"}; +const std::string kMixedSuperImageName = "mixed_super.img"; + +struct BranchBuildTargetInfo { + std::string branch_str; + std::string build_target_str; +}; + +static Result GetDefaultBranchBuildTarget( + const std::string default_branch_str, SubprocessWaiter& waiter, + std::function(void)> callback_unlock, + std::function(void)> callback_lock) { + // get the default build branch and target from repo info and git remote + BranchBuildTargetInfo result_info; + result_info.branch_str = default_branch_str; + Command repo_cmd("repo"); + repo_cmd.AddParameter("info"); + repo_cmd.AddParameter("platform/tools/acloud"); + + auto cuttlefish_source = StringFromEnv("ANDROID_BUILD_TOP", "") + "/tools/acloud"; + auto fd_top = SharedFD::Open(cuttlefish_source, O_RDONLY | O_PATH | O_DIRECTORY); + if (!fd_top->IsOpen()) { + LOG(ERROR) << "Couldn't open \"" << cuttlefish_source + << "\": " << fd_top->StrError(); + } else { + repo_cmd.SetWorkingDirectory(fd_top); + } + RunWithManagedIoParam param_repo { + .cmd_ = std::move(repo_cmd), + .redirect_stdout_ = true, + .redirect_stderr_ = false, + .stdin_ = nullptr, + .callback_ = callback_unlock + }; + RunOutput output_repo = + CF_EXPECT(waiter.RunWithManagedStdioInterruptable(std::move(param_repo))); + + Command git_cmd("git"); + git_cmd.AddParameter("remote"); + if (fd_top->IsOpen()) { + git_cmd.SetWorkingDirectory(fd_top); + } + RunWithManagedIoParam param_git { + .cmd_ = std::move(git_cmd), + .redirect_stdout_ = true, + .redirect_stderr_ = false, + .stdin_ = nullptr, + .callback_ = callback_unlock + }; + CF_EXPECT(callback_lock()); + RunOutput output_git = + CF_EXPECT(waiter.RunWithManagedStdioInterruptable(std::move(param_git))); + + output_git.stdout_.erase(std::remove( + output_git.stdout_.begin(), output_git.stdout_.end(), '\n'), output_git.stdout_.cend()); + + static const std::regex repo_rgx("^Manifest branch: (.+)"); + std::smatch repo_matched; + CHECK(std::regex_search(output_repo.stdout_, repo_matched, repo_rgx)) + << "Manifest branch line is not found from: " << output_repo.stdout_; + // master or ... + std::string repo_matched_str = repo_matched[1].str(); + if (output_git.stdout_ == "aosp") { + result_info.branch_str = "aosp-"; + result_info.build_target_str = "aosp_"; + } + result_info.branch_str += repo_matched_str; + + // AVD_TYPES_MAPPING default is cf + // _DEFAULT_BUILD_BITNESS default is x86_64 + // flavor default is phone + // _DEFAULT_BUILD_TYPE default is userdebug + result_info.build_target_str += "cf_x86_64_phone-userdebug"; + return result_info; +} + +/** + * Split a string into arguments based on shell tokenization rules. + * + * This behaves like `shlex.split` from python where arguments are separated + * based on whitespace, but quoting and quote escaping is respected. This + * function effectively removes one level of quoting from its inputs while + * making the split. + */ +Result> BashTokenize( + const std::string& str, SubprocessWaiter& waiter, + std::function(void)> callback_unlock) { + Command command("bash"); + command.AddParameter("-c"); + command.AddParameter("printf '%s\n' ", str); + RunWithManagedIoParam param_bash { + .cmd_ = std::move(command), + .redirect_stdout_ = true, + .redirect_stderr_ = true, + .stdin_ = nullptr, + .callback_ = callback_unlock + }; + RunOutput output_bash = + CF_EXPECT(waiter.RunWithManagedStdioInterruptable(std::move(param_bash))); + return android::base::Split(output_bash.stdout_, "\n"); +} + +} // namespace + +namespace acloud_impl { + +Result ConvertAcloudCreate( + const RequestWithStdio& request, SubprocessWaiter& waiter, + std::function(void)> callback_unlock, + std::function(void)> callback_lock) { + auto arguments = ParseInvocation(request.Message()).arguments; + CF_EXPECT(arguments.size() > 0); + CF_EXPECT(arguments[0] == "create"); + arguments.erase(arguments.begin()); + + /* + * TODO(chadreynolds@): Move all the flag parsing eventually to the + * converter_parser.{h,cpp}. + * + * Note that the transfer should be done from the top through the bottom. + * ConsumeFlags() parses each flag in order. + */ + auto parsed_flags = CF_EXPECT(acloud_impl::ParseAcloudCreateFlags(arguments)); + + std::vector flags; + + std::optional boot_build_id; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--boot-build-id"}) + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--boot_build_id"}) + .Setter([&boot_build_id](const FlagMatch& m) -> Result { + boot_build_id = m.value; + return {}; + })); + std::optional boot_build_target; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--boot-build-target"}) + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--boot_build_target"}) + .Setter([&boot_build_target](const FlagMatch& m) -> Result { + boot_build_target = m.value; + return {}; + })); + std::optional boot_branch; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--boot-branch"}) + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--boot_branch"}) + .Setter([&boot_branch](const FlagMatch& m) -> Result { + boot_branch = m.value; + return {}; + })); + std::optional boot_artifact; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--boot-artifact"}) + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--boot_artifact"}) + .Setter([&boot_artifact](const FlagMatch& m) -> Result { + boot_artifact = m.value; + return {}; + })); + + std::optional ota_build_id; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--ota-build-id"}) + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--ota_build_id"}) + .Setter([&ota_build_id](const FlagMatch& m) -> Result { + ota_build_id = m.value; + return {}; + })); + std::optional ota_build_target; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--ota-build-target"}) + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--ota_build_target"}) + .Setter([&ota_build_target](const FlagMatch& m) -> Result { + ota_build_target = m.value; + return {}; + })); + std::optional ota_branch; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--ota-branch"}) + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--ota_branch"}) + .Setter([&ota_branch](const FlagMatch& m) -> Result { + ota_branch = m.value; + return {}; + })); + + std::optional launch_args; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--launch-args"}) + .Setter([&launch_args](const FlagMatch& m) -> Result { + launch_args = m.value; + return {}; + })); + + std::optional system_branch; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--system-branch"}) + .Setter([&system_branch](const FlagMatch& m) -> Result { + system_branch = m.value; + return {}; + })); + + std::optional system_build_target; + flags.emplace_back( + Flag() + .Alias( + {FlagAliasMode::kFlagConsumesFollowing, "--system-build-target"}) + .Setter([&system_build_target](const FlagMatch& m) -> Result { + system_build_target = m.value; + return {}; + })); + + std::optional system_build_id; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--system-build-id"}) + .Setter([&system_build_id](const FlagMatch& m) -> Result { + system_build_id = m.value; + return {}; + })); + + std::optional kernel_branch; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--kernel-branch"}) + .Setter([&kernel_branch](const FlagMatch& m) -> Result { + kernel_branch = m.value; + return {}; + })); + + std::optional kernel_build_target; + flags.emplace_back( + Flag() + .Alias( + {FlagAliasMode::kFlagConsumesFollowing, "--kernel-build-target"}) + .Setter([&kernel_build_target](const FlagMatch& m) -> Result { + kernel_build_target = m.value; + return {}; + })); + + std::optional kernel_build_id; + flags.emplace_back( + Flag() + .Alias({FlagAliasMode::kFlagConsumesFollowing, "--kernel-build-id"}) + .Setter([&kernel_build_id](const FlagMatch& m) -> Result { + kernel_build_id = m.value; + return {}; + })); + bool use_16k = false; + flags.emplace_back(Flag() + .Alias({FlagAliasMode::kFlagExact, "--16k"}) + .Alias({FlagAliasMode::kFlagExact, "--16K"}) + .Alias({FlagAliasMode::kFlagExact, "--use-16k"}) + .Alias({FlagAliasMode::kFlagExact, "--use-16K"}) + .Setter([&use_16k](const FlagMatch&) -> Result { + use_16k = true; + return {}; + })); + + std::optional pet_name; + Flag pet_name_gflag = GflagsCompatFlag("pet-name"); + flags.emplace_back( + GflagsCompatFlag("pet-name") + .Getter([&pet_name]() { return (pet_name ? *pet_name : ""); }) + .Setter([&pet_name](const FlagMatch& match) -> Result { + pet_name = match.value; + return {}; + })); + + CF_EXPECT(ConsumeFlags(flags, arguments)); + CF_EXPECT(arguments.size() == 0, "Unrecognized arguments:'" + << android::base::Join(arguments, "', '") + << "'"); + + CF_EXPECT_EQ(parsed_flags.local_instance.is_set, true, + "Only '--local-instance' is supported"); + auto host_dir = TempDir() + "/acloud_image_artifacts/"; + if (parsed_flags.image_download_dir) { + host_dir = + parsed_flags.image_download_dir.value() + "/acloud_image_artifacts/"; + } + + const auto& request_command = request.Message().command_request(); + auto host_artifacts_path = request_command.env().find(kAndroidHostOut); + CF_EXPECT(host_artifacts_path != request_command.env().end(), + "Missing " << kAndroidHostOut); + + std::vector request_protos; + const std::string user_config_path = + parsed_flags.config_file.value_or(CF_EXPECT(GetDefaultConfigFile())); + + AcloudConfig acloud_config = + CF_EXPECT(LoadAcloudConfig(user_config_path)); + + std::string fetch_command_str; + std::string fetch_cvd_args_file; + + if (parsed_flags.local_image.given) { + CF_EXPECT(!(system_branch || system_build_target || system_build_id), + "--local-image incompatible with --system-* flags"); + CF_EXPECT(!(parsed_flags.bootloader.branch || + parsed_flags.bootloader.build_target || + parsed_flags.bootloader.build_id), + "--local-image incompatible with --bootloader-* flags"); + CF_EXPECT( + !(boot_branch || boot_build_target || boot_build_id || boot_artifact), + "--local-image incompatible with --boot-* flags"); + CF_EXPECT(!(ota_branch || ota_build_target || ota_build_id), + "--local-image incompatible with --ota-* flags"); + } else { + if (!DirectoryExists(host_dir)) { + // fetch/download directory doesn't exist, create directory + cvd::Request& mkdir_request = request_protos.emplace_back(); + auto& mkdir_command = *mkdir_request.mutable_command_request(); + mkdir_command.add_args("cvd"); + mkdir_command.add_args("mkdir"); + mkdir_command.add_args("-p"); + mkdir_command.add_args(host_dir); + auto& mkdir_env = *mkdir_command.mutable_env(); + mkdir_env[kAndroidHostOut] = host_artifacts_path->second; + } + // used for default branch and target when there is no input + std::optional given_branch_target_info; + if (parsed_flags.branch || parsed_flags.build_id || + parsed_flags.build_target) { + auto target = parsed_flags.build_target ? *parsed_flags.build_target : ""; + auto build = parsed_flags.build_id.value_or( + parsed_flags.branch.value_or("aosp-main")); + host_dir += (build + target); + } else { + given_branch_target_info = CF_EXPECT(GetDefaultBranchBuildTarget( + "git_", waiter, callback_unlock, callback_lock)); + host_dir += (given_branch_target_info->branch_str + + given_branch_target_info->build_target_str); + } + // TODO(weihsu): The default branch and target value are the + // same as python acloud now. The only TODO item is default ID. + // Python acloud use Android build api to query build info, + // including the latest valid build ID. CVD acloud should follow + // the same method by using Android build api to get build ID, + // but it is not easy in C++. + + cvd::Request& fetch_request = request_protos.emplace_back(); + auto& fetch_command = *fetch_request.mutable_command_request(); + fetch_command.add_args("cvd"); + fetch_command.add_args("fetch"); + fetch_command.add_args("--directory"); + fetch_command.add_args(host_dir); + fetch_command.add_args("--default_build"); + fetch_command_str += "--default_build="; + if (given_branch_target_info) { + fetch_command.add_args(given_branch_target_info->branch_str + "/" + + given_branch_target_info->build_target_str); + fetch_command_str += (given_branch_target_info->branch_str + "/" + + given_branch_target_info->build_target_str); + } else { + auto target = + parsed_flags.build_target ? "/" + *parsed_flags.build_target : ""; + auto build = parsed_flags.build_id.value_or( + parsed_flags.branch.value_or("aosp-main")); + fetch_command.add_args(build + target); + fetch_command_str += (build + target); + } + if (system_branch || system_build_id || system_build_target) { + fetch_command.add_args("--system_build"); + fetch_command_str += " --system_build="; + auto target = + system_build_target.value_or(parsed_flags.build_target.value_or("")); + if (target != "") { + target = "/" + target; + } + auto build = + system_build_id.value_or(system_branch.value_or("aosp-main")); + fetch_command.add_args(build + target); + fetch_command_str += (build + target); + } + if (parsed_flags.bootloader.branch || parsed_flags.bootloader.build_id || + parsed_flags.bootloader.build_target) { + fetch_command.add_args("--bootloader_build"); + fetch_command_str += " --bootloader_build="; + auto target = parsed_flags.bootloader.build_target.value_or(""); + if (target != "") { + target = "/" + target; + } + auto build = parsed_flags.bootloader.build_id.value_or( + parsed_flags.bootloader.branch.value_or("aosp_u-boot-mainline")); + fetch_command.add_args(build + target); + fetch_command_str += (build + target); + } + if (boot_branch || boot_build_id || boot_build_target) { + fetch_command.add_args("--boot_build"); + fetch_command_str += " --boot_build="; + auto target = boot_build_target.value_or(""); + if (target != "") { + target = "/" + target; + } + auto build = boot_build_id.value_or(boot_branch.value_or("aosp-main")); + fetch_command.add_args(build + target); + fetch_command_str += (build + target); + } + if (boot_artifact) { + CF_EXPECT(boot_branch || boot_build_target || boot_build_id, + "--boot-artifact must combine with other --boot-* flags"); + fetch_command.add_args("--boot_artifact"); + fetch_command_str += " --boot_artifact="; + auto target = boot_artifact.value_or(""); + fetch_command.add_args(target); + fetch_command_str += (target); + } + if (ota_branch || ota_build_id || ota_build_target) { + fetch_command.add_args("--otatools_build"); + fetch_command_str += " --otatools_build="; + auto target = ota_build_target.value_or(""); + if (target != "") { + target = "/" + target; + } + auto build = ota_build_id.value_or(ota_branch.value_or("")); + fetch_command.add_args(build + target); + fetch_command_str += (build + target); + } + if (kernel_branch || kernel_build_id || kernel_build_target) { + fetch_command.add_args("--kernel_build"); + fetch_command_str += " --kernel_build="; + auto target = kernel_build_target.value_or("kernel_virt_x86_64"); + auto build = kernel_build_id.value_or( + kernel_branch.value_or("aosp_kernel-common-android-mainline")); + fetch_command.add_args(build + "/" + target); + fetch_command_str += (build + "/" + target); + } + auto& fetch_env = *fetch_command.mutable_env(); + fetch_env[kAndroidHostOut] = host_artifacts_path->second; + + fetch_cvd_args_file = host_dir + "/fetch-cvd-args.txt"; + if (FileExists(fetch_cvd_args_file)) { + // file exists + std::string read_str; + using android::base::ReadFileToString; + CF_EXPECT(ReadFileToString(fetch_cvd_args_file.c_str(), &read_str, + /* follow_symlinks */ true)); + if (read_str == fetch_command_str) { + // same fetch cvd command, reuse original dir + fetch_command_str = ""; + request_protos.pop_back(); + } + } + } + + std::string super_image_path; + if (parsed_flags.local_system_image) { + // in new cvd server design, at this point, + // we don't know which HOME is assigned by cvd start. + // create a temporary directory to store generated + // mix super image + TemporaryDir my_dir; + std::string required_paths; + my_dir.DoNotRemove(); + super_image_path = std::string(my_dir.path) + "/" + kMixedSuperImageName; + + // combine super_image path and local_system_image path + required_paths = super_image_path; + required_paths += ("," + parsed_flags.local_system_image.value()); + + cvd::Request& mixsuperimage_request = request_protos.emplace_back(); + auto& mixsuperimage_command = + *mixsuperimage_request.mutable_command_request(); + mixsuperimage_command.add_args("cvd"); + mixsuperimage_command.add_args("acloud"); + mixsuperimage_command.add_args("mix-super-image"); + mixsuperimage_command.add_args("--super_image"); + + auto& mixsuperimage_env = *mixsuperimage_command.mutable_env(); + if (parsed_flags.local_image.given) { + // added image_dir to required_paths for MixSuperImage use if there is + required_paths.append(",").append( + parsed_flags.local_image.path.value_or("")); + mixsuperimage_env[kAndroidHostOut] = host_artifacts_path->second; + + auto product_out = request_command.env().find(kAndroidProductOut); + CF_EXPECT(product_out != request_command.env().end(), + "Missing " << kAndroidProductOut); + mixsuperimage_env[kAndroidProductOut] = product_out->second; + } else { + mixsuperimage_env[kAndroidHostOut] = host_dir; + mixsuperimage_env[kAndroidProductOut] = host_dir; + } + + mixsuperimage_command.add_args(required_paths); + } + + cvd::Request start_request; + auto& start_command = *start_request.mutable_command_request(); + start_command.add_args("cvd"); + start_command.add_args("start"); + start_command.add_args("--daemon"); + start_command.add_args("--undefok"); + start_command.add_args("report_anonymous_usage_stats"); + start_command.add_args("--report_anonymous_usage_stats"); + start_command.add_args("y"); + if (parsed_flags.flavor) { + start_command.add_args("-config"); + start_command.add_args(parsed_flags.flavor.value()); + } + + if (parsed_flags.local_system_image) { + start_command.add_args("-super_image"); + start_command.add_args(super_image_path); + } + + if (parsed_flags.local_kernel_image) { + // kernel image has 1st priority than boot image + struct stat statbuf {}; + std::string local_boot_image; + std::string vendor_boot_image; + std::string kernel_image; + std::string initramfs_image; + if (stat(parsed_flags.local_kernel_image.value().c_str(), &statbuf) == 0) { + if (statbuf.st_mode & S_IFDIR) { + // it's a directory, deal with kernel image case first + kernel_image = FindImage(parsed_flags.local_kernel_image.value(), + kKernelImageNames); + initramfs_image = FindImage(parsed_flags.local_kernel_image.value(), + kInitRamFsImageName); + // This is the original python acloud behavior, it + // expects both kernel and initramfs files, however, + // there are some very old kernels that are built without + // an initramfs.img file, + // e.g. aosp_kernel-common-android-4.14-stable + if (kernel_image != "" && initramfs_image != "") { + start_command.add_args("-kernel_path"); + start_command.add_args(kernel_image); + start_command.add_args("-initramfs_path"); + start_command.add_args(initramfs_image); + } else { + // boot.img case + // adding boot.img and vendor_boot.img to the path + local_boot_image = FindImage(parsed_flags.local_kernel_image.value(), + kBootImageName); + vendor_boot_image = FindImage(parsed_flags.local_kernel_image.value(), + kVendorBootImageName); + start_command.add_args("-boot_image"); + start_command.add_args(local_boot_image); + // vendor boot image may not exist + if (vendor_boot_image != "") { + start_command.add_args("-vendor_boot_image"); + start_command.add_args(vendor_boot_image); + } + } + } else if (statbuf.st_mode & S_IFREG) { + // it's a file which directly points to boot.img + local_boot_image = parsed_flags.local_kernel_image.value(); + start_command.add_args("-boot_image"); + start_command.add_args(local_boot_image); + } + } + } else if (kernel_branch || kernel_build_id || kernel_build_target) { + // fetch remote kernel image files + std::string kernel_image = host_dir + "/kernel"; + + // even if initramfs doesn't exist, launch_cvd will still handle it + // correctly. We push the initramfs handler to launch_cvd stage. + std::string initramfs_image = host_dir + "/initramfs.img"; + start_command.add_args("-kernel_path"); + start_command.add_args(kernel_image); + start_command.add_args("-initramfs_path"); + start_command.add_args(initramfs_image); + } + + if (launch_args) { + CF_EXPECT(callback_lock()); + for (const auto& arg : CF_EXPECT(BashTokenize( + *launch_args, waiter, callback_unlock))) { + start_command.add_args(arg); + } + } + if (acloud_config.launch_args != "") { + CF_EXPECT(callback_lock()); + for (const auto& arg : CF_EXPECT(BashTokenize( + acloud_config.launch_args, waiter, callback_unlock))) { + start_command.add_args(arg); + } + } + if (pet_name) { + const auto [group_name, instance_name] = + CF_EXPECT(selector::BreakDeviceName(*pet_name), + *pet_name << " must be a group name followed by - " + << "followed by an instance name."); + std::string group_name_arg = "--"; + group_name_arg.append(selector::SelectorFlags::kGroupName) + .append("=") + .append(group_name); + std::string instance_name_arg = "--"; + instance_name_arg.append(selector::SelectorFlags::kInstanceName) + .append("=") + .append(instance_name); + start_command.mutable_selector_opts()->add_args(group_name_arg); + start_command.mutable_selector_opts()->add_args(instance_name_arg); + } + if (use_16k) { + start_command.add_args("--use_16k"); + } + + auto& start_env = *start_command.mutable_env(); + if (parsed_flags.local_image.given) { + if (parsed_flags.local_image.path) { + std::string local_image_path_str = parsed_flags.local_image.path.value(); + // Python acloud source: local_image_local_instance.py;l=81 + // this acloud flag is equal to launch_cvd flag system_image_dir + start_command.add_args("-system_image_dir"); + start_command.add_args(local_image_path_str); + } + + start_env[kAndroidHostOut] = host_artifacts_path->second; + + auto product_out = request_command.env().find(kAndroidProductOut); + CF_EXPECT(product_out != request_command.env().end(), + "Missing " << kAndroidProductOut); + start_env[kAndroidProductOut] = product_out->second; + } else { + start_env[kAndroidHostOut] = host_dir; + start_env[kAndroidProductOut] = host_dir; + } + if (Contains(start_env, kCuttlefishInstanceEnvVarName)) { + // Python acloud does not use this variable. + // this variable will confuse cvd start, though + start_env.erase(kCuttlefishInstanceEnvVarName); + } + if (parsed_flags.local_instance.id) { + start_env[kCuttlefishInstanceEnvVarName] = + std::to_string(*parsed_flags.local_instance.id); + } + // we don't know which HOME is assigned by cvd start. + // cvd server does not rely on the working directory for cvd start + *start_command.mutable_working_directory() = + request_command.working_directory(); + std::vector fds; + if (parsed_flags.verbose) { + fds = request.FileDescriptors(); + } else { + auto dev_null = SharedFD::Open("/dev/null", O_RDWR); + CF_EXPECT(dev_null->IsOpen(), dev_null->StrError()); + fds = {dev_null, dev_null, dev_null}; + } + + ConvertedAcloudCreateCommand ret{ + .start_request = RequestWithStdio(request.Client(), start_request, fds, + request.Credentials()), + .fetch_command_str = fetch_command_str, + .fetch_cvd_args_file = fetch_cvd_args_file, + .verbose = parsed_flags.verbose, + }; + for (auto& request_proto : request_protos) { + ret.prep_requests.emplace_back(request.Client(), request_proto, fds, + request.Credentials()); + } + return ret; +} + +} // namespace acloud_impl +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/acloud/converter.h b/base/cvd/cuttlefish/host/commands/cvd/acloud/converter.h new file mode 100644 index 0000000000..ab1dcccbd0 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/acloud/converter.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 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 + +#include +#include +#include + +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" + +namespace cuttlefish { + +struct ConvertedAcloudCreateCommand { + std::vector prep_requests; + RequestWithStdio start_request; + std::string fetch_command_str; + std::string fetch_cvd_args_file; + bool verbose; + bool interrupt_lock_released; +}; + +namespace acloud_impl { + +/* + * Converts the acloud create commands. + * + * Given that the lock is already acquired, it may start a subprocess + * using waiter. If it runs multiple subprocesses in turn using the same + * waiter, it acquire the lock before Start() and release the lock before + * Wait(). The interrupt_lock_released in the return value says whether + * the lock is released or not. + * The input parameters waiter, callback_unlock and callback_lock + * provide locking system to support interrupt. + * + */ +Result ConvertAcloudCreate( + const RequestWithStdio& request, SubprocessWaiter& waiter, + std::function(void)> callback_unlock, + std::function(void)> callback_lock); + +} // namespace acloud_impl +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/acloud/create_converter_parser.cpp b/base/cvd/cuttlefish/host/commands/cvd/acloud/create_converter_parser.cpp new file mode 100644 index 0000000000..36cce55bf5 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/acloud/create_converter_parser.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/acloud/create_converter_parser.h" + +#include +#include + +#include +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/flag_parser.h" + +namespace cuttlefish { +namespace { + +constexpr char kFlagConfig[] = "config"; +constexpr char kFlagBranch[] = "branch"; +constexpr char kFlagBuildId[] = "build_id"; +constexpr char kFlagBuildTarget[] = "build_target"; +constexpr char kFlagConfigFile[] = "config_file"; +constexpr char kFlagLocalKernelImage[] = "local-kernel-image"; +constexpr char kFlagLocalSystemImage[] = "local-system-image"; +constexpr char kFlagBootloaderBuildId[] = "bootloader_build_id"; +constexpr char kFlagBootloaderBuildTarget[] = "bootloader_build_target"; +constexpr char kFlagBootloaderBranch[] = "bootloader_branch"; +constexpr char kFlagImageDownloadDir[] = "image-download-dir"; +constexpr char kFlagLocalImage[] = "local-image"; +constexpr char kFlagLocalInstance[] = "local-instance"; + +constexpr char kAcloudCmdCreate[] = "create"; + +struct VerboseParser { + std::optional token; + + Flag Parser() { + return Flag() + .Alias({FlagAliasMode::kFlagExact, "-v"}) + .Alias({FlagAliasMode::kFlagExact, "-vv"}) + .Alias({FlagAliasMode::kFlagExact, "--verbose"}) + .Setter([this](const FlagMatch&) -> Result { + token = true; + return {}; + }); + } +}; + +struct StringParser { + StringParser(const char* orig) : StringParser(orig, "", false) {} + StringParser(const char* orig, bool allow_empty) + : StringParser(orig, "", allow_empty) {} + StringParser(const char* orig, const char* alias) + : StringParser(orig, alias, false) {} + StringParser(const char* orig, const char* alias, bool allow_empty) + : orig(orig), alias(alias), allow_empty(allow_empty), token({}) {} + std::string orig; + std::string alias; + bool allow_empty; + std::optional token; + + Flag Parser() { + Flag parser; + FlagAliasMode mode = allow_empty ? FlagAliasMode::kFlagConsumesArbitrary + : FlagAliasMode::kFlagConsumesFollowing; + parser.Alias({mode, "--" + orig}); + if (!alias.empty()) { + parser.Alias({mode, "--" + alias}); + } + parser.Setter([this](const FlagMatch& m) -> Result { + // Multiple matches could happen when kFlagConsumesArbitrary is used, the + // empty string match would be always the last one. + if (!token.has_value()) { + token = m.value; + } else if (!m.value.empty()) { + return CF_ERRF("\"{}\" already set, was \"{}\", now set to \"{}\"", + orig, token.value(), m.value); + } + return {}; + }); + return parser; + } +}; + +template +std::optional GetOptVal(const std::unordered_map& m, const K& key) { + auto it = m.find(key); + return it == m.end() ? std::optional() : it->second; +} + +struct Tokens { + std::unordered_map strings; + std::unordered_map booleans; + + std::optional StringVal(std::string name) { + return GetOptVal(strings, name); + } + + std::optional BoolVal(std::string name) { + return GetOptVal(booleans, name); + } +}; + +Result ParseForCvdCreate(cvd_common::Args& arguments) { + std::vector string_parsers = { + StringParser(kFlagBranch), + StringParser(kFlagLocalSystemImage), + StringParser(kFlagImageDownloadDir), + StringParser(kFlagConfig, "flavor"), + StringParser(kFlagBuildId, "build-id"), + StringParser(kFlagBuildTarget, "build-target"), + StringParser(kFlagConfigFile, "config-file"), + StringParser(kFlagLocalKernelImage, "local-boot-image"), + StringParser(kFlagBootloaderBuildId, "bootloader-build-id"), + StringParser(kFlagBootloaderBuildTarget, "bootloader-build-target"), + StringParser(kFlagBootloaderBranch, "bootloader-branch"), + StringParser(kFlagLocalImage, true), + StringParser(kFlagLocalInstance, true), + }; + VerboseParser verbose_parser = VerboseParser{}; + + std::vector parsers; + + for (auto& p : string_parsers) { + parsers.emplace_back(p.Parser()); + } + parsers.emplace_back(verbose_parser.Parser()); + + CF_EXPECT(ConsumeFlags(parsers, arguments)); + + auto result = Tokens{}; + for (auto& p : string_parsers) { + if (p.token.has_value()) { + result.strings[p.orig] = p.token.value(); + } + } + if (verbose_parser.token.has_value()) { + result.booleans["v"] = true; + } + return result; +} + +Result ParseForCvdRemoteCreate(cvd_common::Args& arguments) { + std::vector string_parsers = { + StringParser(kFlagBranch), + StringParser(kFlagBuildId, "build-id"), + StringParser(kFlagBuildTarget, "build-target"), + }; + + std::vector parsers; + + for (auto& p : string_parsers) { + parsers.emplace_back(p.Parser()); + } + + CF_EXPECT(ConsumeFlags(parsers, arguments)); + + auto result = Tokens{}; + for (auto& p : string_parsers) { + if (p.token.has_value()) { + result.strings[p.orig] = p.token.value(); + } + } + return result; +} + +} // namespace +namespace acloud_impl { + +Result ParseAcloudCreateFlags(cvd_common::Args& arguments) { + auto tokens = CF_EXPECT(ParseForCvdCreate(arguments)); + std::optional local_instance = + tokens.StringVal(kFlagLocalInstance); + std::optional local_instance_id; + if (local_instance.has_value() && !local_instance.value().empty()) { + int value = -1; + CF_EXPECTF(android::base::ParseInt(local_instance.value(), &value), + "Invalid integer value for flag \"{}\": \"{}\"", + kFlagLocalInstance, local_instance.value()); + local_instance_id = value; + } + std::optional local_image = tokens.StringVal(kFlagLocalImage); + std::optional local_image_path; + if (local_image.has_value() && !local_image.value().empty()) { + local_image_path = local_image.value(); + } + return ConverterParsed{ + .local_instance = {.is_set = local_instance.has_value(), + .id = local_instance_id}, + .flavor = tokens.StringVal(kFlagConfig), + .local_kernel_image = tokens.StringVal(kFlagLocalKernelImage), + .image_download_dir = tokens.StringVal(kFlagImageDownloadDir), + .local_system_image = tokens.StringVal(kFlagLocalSystemImage), + .verbose = tokens.BoolVal("v").has_value(), + .branch = tokens.StringVal(kFlagBranch), + .local_image = + { + .given = local_image.has_value(), + .path = local_image_path, + }, + .build_id = tokens.StringVal(kFlagBuildId), + .build_target = tokens.StringVal(kFlagBuildTarget), + .config_file = tokens.StringVal(kFlagConfigFile), + .bootloader = + { + .build_id = tokens.StringVal(kFlagBootloaderBuildId), + .build_target = tokens.StringVal(kFlagBootloaderBuildTarget), + .branch = tokens.StringVal(kFlagBootloaderBranch), + }, + }; +} + +Result CompileFromAcloudToCvdr(cvd_common::Args& arguments) { + CF_EXPECT(arguments.size() > 0); + CF_EXPECT(arguments[0] == kAcloudCmdCreate); + std::string main_cmd = arguments[0]; + arguments.erase(arguments.begin()); + auto tokens = CF_EXPECT(ParseForCvdRemoteCreate(arguments)); + CF_EXPECTF(arguments.empty(), "Unrecognized arguments: '{}'", + fmt::join(arguments, "', '")); + std::vector result{"create"}; + for (const auto& t : tokens.strings) { + result.emplace_back("--" + t.first); + result.emplace_back(t.second); + } + return result; +} + +} // namespace acloud_impl +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/acloud/create_converter_parser.h b/base/cvd/cuttlefish/host/commands/cvd/acloud/create_converter_parser.h new file mode 100644 index 0000000000..a1d1e8664b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/acloud/create_converter_parser.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace acloud_impl { + +struct ConverterParsed { + struct LocalInstance { + bool is_set; + std::optional id; + } local_instance; + std::optional flavor; + std::optional local_kernel_image; + std::optional image_download_dir; + std::optional local_system_image; + bool verbose; + std::optional branch; + struct LocalImage { + bool given; + std::optional path; + } local_image; + std::optional build_id; + std::optional build_target; + std::optional config_file; + struct Bootloader { + std::optional build_id; + std::optional build_target; + std::optional branch; + } bootloader; +}; + +Result ParseAcloudCreateFlags(cvd_common::Args& arguments); + +// Parse and generates a `cvdr` command given an `acloud` command. +Result CompileFromAcloudToCvdr(cvd_common::Args& arguments); + +} // namespace acloud_impl +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/acloud/data/default.config b/base/cvd/cuttlefish/host/commands/cvd/acloud/data/default.config new file mode 100644 index 0000000000..7fa77aa086 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/acloud/data/default.config @@ -0,0 +1,180 @@ +min_machine_size: "n1-standard-4" +disk_image_name: "avd-system.tar.gz" +disk_image_mime_type: "application/x-tar" +disk_image_extension: ".tar.gz" +disk_raw_image_name: "disk.raw" +disk_raw_image_extension: ".img" +default_extra_data_disk_device: "/dev/block/sdb" +creds_cache_file: ".acloud_oauth2.dat" +user_agent: "acloud" + +# [GOLDFISH only] The emulator build target: "emulator-linux_x64_internal". +# We use it to get build id if build id is not provided and It's very unlikely +# that this will ever change. +emulator_build_target: "emulator-linux_x64_internal" + +default_usr_cfg { + machine_type: "n1-standard-4" + network: "default" + extra_data_disk_size_gb: 0 + instance_name_pattern: "ins-{uuid}-{build_id}-{build_target}" + fetch_cvd_version: "9123511" + + metadata_variable { + key: "camera_front" + value: "1,32,24,checker-sliding" + } + + metadata_variable { + key: "camera_back" + value: "1,640,480,checker-fixed" + } + + metadata_variable { + key: "cfg_sta_ephemeral_cache_size_mb" + value: "512" + } + + metadata_variable { + key: "cfg_sta_ephemeral_data_size_mb" + value: "2048" + } + + metadata_variable { + key: "cfg_sta_persistent_data_device" + value: "default" + } + + metadata_variable { + key: "gps_coordinates" + value: "37.422,122.084,100,0,1,1" + } +} + +common_hw_property_map { + key: "local-auto" + value: "cpu:4,resolution:1280x800,dpi:160,memory:4g" +} + +common_hw_property_map { + key: "local-wear" + value: "cpu:4,resolution:320x320,dpi:240,memory:2g" +} + +common_hw_property_map { + key: "local-tablet" + value: "cpu:4,resolution:2560x1800,dpi:320,memory:4g" +} + +common_hw_property_map { + key: "local-foldable" + value: "cpu:4,resolution:1768x2208,dpi:386,memory:4g" +} + +common_hw_property_map { + key: "phone" + value: "cpu:4,resolution:720x1280,dpi:320,memory:2g" +} + +common_hw_property_map { + key: "auto" + value: "cpu:4,resolution:1280x800,dpi:160,memory:4g" +} + +common_hw_property_map { + key: "wear" + value: "cpu:4,resolution:320x320,dpi:240,memory:2g" +} + +common_hw_property_map { + key: "tablet" + value: "cpu:4,resolution:2560x1800,dpi:320,memory:4g" +} + +common_hw_property_map { + key: "tv" + value: "cpu:4,resolution:1920x1080,dpi:213,memory:2g" +} + +common_hw_property_map { + key: "foldable" + value: "cpu:4,resolution:1768x2208,dpi:386,memory:4g" +} + +# Device resolution +device_resolution_map { + key: "nexus5" + value: "1080x1920x32x480" +} + +device_resolution_map { + key: "nexus6" + value: "1440x2560x32x560" +} + +# nexus7 (2012) +device_resolution_map { + key: "nexus7_2012" + value: "800x1280x32x213" +} + +device_resolution_map { + key: "nexus7_2013" + value: "1200x1920x32x320" +} + +device_resolution_map { + key: "nexus9" + value: "1536x2048x32x320" +} + +device_resolution_map { + key: "nexus10" + value: "1600x2560x32x320" +} + +# Default orientation + +device_default_orientation_map { + key: "nexus5" + value: "portrait" +} + +device_default_orientation_map { + key: "nexus6" + value: "landscape" +} + +device_default_orientation_map { + key: "nexus7_2012" + value: "landscape" +} + +device_default_orientation_map { + key: "nexus7_2013" + value: "landscape" +} + +device_default_orientation_map { + key: "nexus9" + value: "landscape" +} + +device_default_orientation_map { + key: "nexus10" + value: "landscape" +} + +# Precreated data images. +precreated_data_image { + key: 4 + value: "extradisk-image-4gb" +} +precreated_data_image { + key: 10 + value: "extradisk-image-10gb" +} +precreated_data_image { + key: 100 + value: "extradisk-image-100gb" +} diff --git a/base/cvd/cuttlefish/host/commands/cvd/client.cpp b/base/cvd/cuttlefish/host/commands/cvd/client.cpp new file mode 100644 index 0000000000..a2eb1d7d1d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/client.cpp @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/client.h" + +#include + +#include +#include +#include + +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/proto.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/flag.h" +#include "host/commands/cvd/frontline_parser.h" +#include "host/commands/cvd/handle_reset.h" +#include "host/commands/cvd/run_server.h" +#include "host/libs/config/host_tools_version.h" + +namespace cuttlefish { +namespace { + +Result CvdFlags() { + FlagCollection cvd_flags; + CF_EXPECT(cvd_flags.EnrollFlag(CvdFlag("clean", false))); + CF_EXPECT(cvd_flags.EnrollFlag(CvdFlag("help", false))); + CF_EXPECT(cvd_flags.EnrollFlag(CvdFlag("verbosity"))); + return cvd_flags; +} + +Result FilterDriverHelpOptions(const FlagCollection& cvd_flags, + cvd_common::Args& cvd_args) { + auto help_flag = CF_EXPECT(cvd_flags.GetFlag("help")); + bool is_help = CF_EXPECT(help_flag.CalculateFlag(cvd_args)); + return is_help; +} + +[[noreturn]] void CallPythonAcloud(std::vector& args) { + auto android_top = StringFromEnv("ANDROID_BUILD_TOP", ""); + if (android_top == "") { + LOG(FATAL) << "Could not find android environment. Please run " + << "\"source build/envsetup.sh\"."; + abort(); + } + // TODO(b/206893146): Detect what the platform actually is. + auto py_acloud_path = + android_top + "/prebuilts/asuite/acloud/linux-x86/acloud"; + std::unique_ptr new_argv(new char*[args.size() + 1]); + for (size_t i = 0; i < args.size(); i++) { + new_argv[i] = args[i].data(); + } + new_argv[args.size()] = nullptr; + execv(py_acloud_path.data(), new_argv.get()); + PLOG(FATAL) << "execv(" << py_acloud_path << ", ...) failed"; + abort(); +} + +cvd_common::Args AllArgs(const std::string& prog_path, + const cvd_common::Args& cvd_args, + const std::optional& subcmd, + const cvd_common::Args& subcmd_args) { + std::vector all_args; + all_args.push_back(prog_path); + all_args.insert(all_args.end(), cvd_args.begin(), cvd_args.end()); + if (subcmd) { + all_args.push_back(*subcmd); + } + all_args.insert(all_args.end(), subcmd_args.begin(), subcmd_args.end()); + return all_args; +} + +enum class VersionCommandReport : std::uint32_t { + kNonVersion, + kVersion, +}; +Result HandleVersionCommand( + CvdClient& client, const cvd_common::Args& all_args) { + std::vector version_command{"version"}; + FlagCollection cvd_flags = CF_EXPECT(CvdFlags()); + FrontlineParser::ParserParam version_param{ + .server_supported_subcmds = std::vector{}, + .internal_cmds = version_command, + .all_args = all_args, + .cvd_flags = cvd_flags}; + auto version_parser_result = FrontlineParser::Parse(version_param); + if (!version_parser_result.ok()) { + return VersionCommandReport::kNonVersion; + } + + auto version_parser = std::move(*version_parser_result); + CF_EXPECT(version_parser != nullptr); + const auto subcmd = version_parser->SubCmd().value_or(""); + auto cvd_args = version_parser->CvdArgs(); + CF_EXPECT(subcmd == "version" || subcmd.empty(), + "subcmd is expected to be \"version\" or empty but is " << subcmd); + + if (subcmd == "version") { + auto version_msg = CF_EXPECT(client.HandleVersion()); + std::cout << version_msg; + return VersionCommandReport::kVersion; + } + return VersionCommandReport::kNonVersion; +} + +struct ClientCommandCheckResult { + bool was_client_command_; + cvd_common::Args new_all_args; +}; +Result HandleClientCommands( + CvdClient& client, const cvd_common::Args& all_args) { + ClientCommandCheckResult output; + std::vector client_internal_commands{"kill-server", + "server-kill", "reset"}; + FlagCollection cvd_flags = CF_EXPECT(CvdFlags()); + FrontlineParser::ParserParam client_param{ + .server_supported_subcmds = std::vector{}, + .internal_cmds = client_internal_commands, + .all_args = all_args, + .cvd_flags = cvd_flags}; + auto client_parser_result = FrontlineParser::Parse(client_param); + if (!client_parser_result.ok()) { + return ClientCommandCheckResult{.was_client_command_ = false, + .new_all_args = all_args}; + } + + auto client_parser = std::move(*client_parser_result); + CF_EXPECT(client_parser != nullptr); + auto cvd_args = client_parser->CvdArgs(); + auto is_help = CF_EXPECT(FilterDriverHelpOptions(cvd_flags, cvd_args)); + + output.new_all_args = + AllArgs(client_parser->ProgPath(), cvd_args, client_parser->SubCmd(), + client_parser->SubCmdArgs()); + output.was_client_command_ = (!is_help && client_parser->SubCmd()); + if (!output.was_client_command_) { + // could be simply "cvd" + output.new_all_args = cvd_common::Args{"cvd", "help"}; + return output; + } + + // Special case for `cvd kill-server`, handled by directly + // stopping the cvd_server. + std::vector kill_server_cmds{"kill-server", "server-kill"}; + std::string subcmd = client_parser->SubCmd().value_or(""); + if (Contains(kill_server_cmds, subcmd)) { + CF_EXPECT(client.StopCvdServer(/*clear=*/true)); + return output; + } + CF_EXPECT_EQ(subcmd, "reset", "unsupported subcmd: " << subcmd); + CF_EXPECT(HandleReset(client, client_parser->SubCmdArgs())); + return output; +} + +} // end of namespace + +Result CvdClient::ConnectToServer() { + auto connection = + SharedFD::SocketLocalClient(server_socket_path_, + /*is_abstract=*/true, SOCK_SEQPACKET); + if (!connection->IsOpen()) { + auto connection = + SharedFD::SocketLocalClient(server_socket_path_, + /*is_abstract=*/true, SOCK_STREAM); + } + if (!connection->IsOpen()) { + return CF_ERR("Failed to connect to server" << connection->StrError()); + } + return connection; +} + +static cvd::Version ClientVersion() { + cvd::Version client_version; + client_version.set_major(cvd::kVersionMajor); + client_version.set_minor(cvd::kVersionMinor); + client_version.set_build(android::build::GetBuildNumber()); + client_version.set_crc32(FileCrc(kServerExecPath)); + return client_version; +} + +Result CvdClient::GetServerVersion() { + cvd::Request request; + request.mutable_version_request(); + auto response = SendRequest(request); + + // If cvd_server is not running, start and wait before checking its version. + if (!response.ok()) { + CF_EXPECT(StartCvdServer()); + response = CF_EXPECT(SendRequest(request)); + } + CF_EXPECT(CheckStatus(response->status(), "GetVersion")); + CF_EXPECT(response->has_version_response(), + "GetVersion call missing VersionResponse."); + + return response->version_response().version(); +} + +static bool operator<(const cvd::Version& src, const cvd::Version& target) { + return (src.major() == target.major()) ? (src.minor() < target.minor()) + : (src.major() < target.major()); +} + +static std::ostream& operator<<(std::ostream& out, + const cvd::Version& version) { + out << "v" << version.major() << "." << version.minor(); + return out; +} + +Result CvdClient::RestartServer(const cvd::Version& server_version) { + cvd::Version reference; + reference.set_major(1); + reference.set_minor(4); + + if (server_version < reference) { + LOG(INFO) << "server version " << server_version << " does not support " + << "the restart-server operation, so will stop & start it."; + CF_EXPECT(StopCvdServer(/*clear=*/false)); + CF_EXPECT(StartCvdServer()); + return {}; + } + + LOG(INFO) << "server version v" << server_version + << " supports restart-server, so will restart the server" + << " in the same process."; + + const cvd_common::Args cvd_process_args{"cvd", "process"}; + CF_EXPECT(HandleCommand( + cvd_process_args, cvd_common::Envs{}, + cvd_common::Args{"cvd", "restart-server", "match-client"}, OverrideFd{})); + return {}; +} + +Result CvdClient::ValidateServerVersion(const int num_retries) { + auto server_version = CF_EXPECT(GetServerVersion()); + if (server_version.major() != cvd::kVersionMajor) { + return CF_ERR("Major version difference: cvd(" + << cvd::kVersionMajor << "." << cvd::kVersionMinor + << ") != cvd_server(" << server_version.major() << "." + << server_version.minor() + << "). Try `cvd kill-server` or `pkill cvd_server`."); + } + if (server_version.minor() < cvd::kVersionMinor) { + std::cerr << "Minor version of cvd_server is older than latest. " + << "Attempting to restart..." << std::endl; + CF_EXPECT(RestartServer(server_version)); + if (num_retries > 0) { + CF_EXPECT(ValidateServerVersion(num_retries - 1)); + return {}; + } else { + return CF_ERR("Unable to start the cvd_server with version " + << cvd::kVersionMajor << "." << cvd::kVersionMinor); + } + } + if (server_version.build() != android::build::GetBuildNumber()) { + LOG(VERBOSE) << "cvd_server client version (" + << android::build::GetBuildNumber() + << ") does not match server version (" + << server_version.build() << std::endl; + } + auto self_crc32 = FileCrc(kServerExecPath); + if (server_version.crc32() != self_crc32) { + LOG(VERBOSE) << "cvd_server client checksum (" << self_crc32 + << ") doesn't match server checksum (" + << server_version.crc32() << std::endl; + } + return {}; +} + +Result CvdClient::StopCvdServer(bool clear) { + if (!server_) { + // server_ may not represent a valid connection even while the server is + // running, if we haven't tried to connect. This establishes first whether + // the server is running. + auto connection_attempt = ConnectToServer(); + if (!connection_attempt.ok()) { + return {}; + } + } + + cvd::Request request; + auto shutdown_request = request.mutable_shutdown_request(); + if (clear) { + shutdown_request->set_clear(true); + } + + // Send the server a pipe with the Shutdown request that it + // will close when it fully exits. + SharedFD read_pipe, write_pipe; + CF_EXPECT(cuttlefish::SharedFD::Pipe(&read_pipe, &write_pipe), + "Unable to create shutdown pipe: " << strerror(errno)); + + auto response = + SendRequest(request, OverrideFd{/* override none of 0, 1, 2 */}, + /*extra_fd=*/write_pipe); + + // If the server is already not running then SendRequest will fail. + // We treat this as success. + if (!response.ok()) { + server_.reset(); + return {}; + } + + CF_EXPECT(CheckStatus(response->status(), "Shutdown")); + CF_EXPECT(response->has_shutdown_response(), + "Shutdown call missing ShutdownResponse."); + + // Clear out the server_ socket. + server_.reset(); + + // Close the write end of the pipe in this process. Now the only + // process that may have the write end still open is the cvd_server. + write_pipe->Close(); + + // Wait for the pipe to close by attempting to read from the pipe. + char buf[1]; // Any size >0 should work for read attempt. + CF_EXPECT(read_pipe->Read(buf, sizeof(buf)) <= 0, + "Unexpected read value from cvd_server shutdown pipe."); + return {}; +} + +Result CvdClient::HandleCommand( + const std::vector& cvd_process_args, + const std::unordered_map& env, + const std::vector& selector_args, + const OverrideFd& new_control_fd) { + std::optional exe_fd; + // actual commandline arguments are packed in selector_args + if (selector_args.size() > 2 && + android::base::Basename(selector_args[0]) == "cvd" && + selector_args[1] == "restart-server" && + selector_args[2] == "match-client") { + exe_fd = SharedFD::Open(kServerExecPath, O_RDONLY); + CF_EXPECT((*exe_fd)->IsOpen(), "Failed to open \"" + << kServerExecPath << "\": \"" + << (*exe_fd)->StrError() << "\""); + } + cvd::Request request = MakeRequest({.cmd_args = cvd_process_args, + .env = env, + .selector_args = selector_args}, + cvd::WAIT_BEHAVIOR_COMPLETE); + auto response = CF_EXPECT(SendRequest(request, new_control_fd, exe_fd)); + CF_EXPECT(CheckStatus(response.status(), "HandleCommand")); + CF_EXPECT(response.has_command_response(), + "HandleCommand call missing CommandResponse."); + return {response}; +} + +Result CvdClient::SetServer(const SharedFD& server) { + CF_EXPECT(!server_, "Already have a server"); + CF_EXPECT(server->IsOpen(), server->StrError()); + server_ = UnixMessageSocket(server); + CF_EXPECT(server_->EnableCredentials(true).ok(), + "Unable to enable UnixMessageSocket credentials."); + return {}; +} + +Result CvdClient::SendRequest(const cvd::Request& request_orig, + const OverrideFd& new_control_fds, + std::optional extra_fd) { + if (!server_) { + CF_EXPECT(SetServer(CF_EXPECT(ConnectToServer()))); + } + cvd::Request request(request_orig); + auto* verbosity = request.mutable_verbosity(); + *verbosity = CF_EXPECT(VerbosityToString(verbosity_)); + + // Serialize and send the request. + std::string serialized; + CF_EXPECT(request.SerializeToString(&serialized), + "Unable to serialize request proto."); + UnixSocketMessage request_message; + + std::vector control_fds = { + (new_control_fds.stdin_override_fd ? *new_control_fds.stdin_override_fd + : SharedFD::Dup(0)), + (new_control_fds.stdout_override_fd ? *new_control_fds.stdout_override_fd + : SharedFD::Dup(1)), + (new_control_fds.stderr_override_fd ? *new_control_fds.stderr_override_fd + : SharedFD::Dup(2))}; + if (extra_fd) { + control_fds.push_back(*extra_fd); + } + auto control = CF_EXPECT(ControlMessage::FromFileDescriptors(control_fds)); + request_message.control.emplace_back(std::move(control)); + + request_message.data = + std::vector(serialized.begin(), serialized.end()); + CF_EXPECT(server_->WriteMessage(request_message)); + + // Read and parse the response. + auto read_result = CF_EXPECT(server_->ReadMessage()); + serialized = std::string(read_result.data.begin(), read_result.data.end()); + cvd::Response response; + CF_EXPECT(response.ParseFromString(serialized), + "Unable to parse serialized response proto."); + return response; +} + +Result CvdClient::StartCvdServer() { + SharedFD server_fd = + SharedFD::SocketLocalServer(server_socket_path_, + /*is_abstract=*/true, SOCK_SEQPACKET, 0666); + CF_EXPECT(server_fd->IsOpen(), server_fd->StrError()); + + Command command(kServerExecPath); + command.AddParameter("-", kInternalServerFd, "=", server_fd); + command.Start(SubprocessOptions{}.ExitWithParent(false)); + + // Connect to the server_fd, which waits for startup. + CF_EXPECT(SetServer(SharedFD::SocketLocalClient(server_socket_path_, + /*is_abstract=*/true, + SOCK_SEQPACKET))); + return {}; +} + +Result CvdClient::CheckStatus(const cvd::Status& status, + const std::string& rpc) { + if (status.code() == cvd::Status::OK) { + return {}; + } + return CF_ERRF("Received error response for \"{}\"\n{}\n\n{}\n{}", rpc, + "*** End of Client Stack Trace ***", status.message(), + "*** End of Server Stack Trace/Error ***"); +} + +Result CvdClient::HandleAcloud( + const std::vector& args, + const std::unordered_map& env) { + auto server_running = ValidateServerVersion(); + + std::vector args_copy{args}; + + // TODO(b/206893146): Make this decision inside the server. + if (!server_running.ok()) { + CallPythonAcloud(args_copy); + // no return + } + + args_copy[0] = "try-acloud"; + auto attempt = HandleCommand(args_copy, env, {}); + if (!attempt.ok()) { + CallPythonAcloud(args_copy); + // no return + } + + args_copy[0] = "acloud"; + CF_EXPECT(HandleCommand(args_copy, env, {})); + return {}; +} + +Result CvdClient::HandleCvdCommand( + const std::vector& all_args, + const std::unordered_map& env) { + auto [was_client_command, new_all_args] = + CF_EXPECT(HandleClientCommands(*this, all_args)); + if (was_client_command) { + return {}; + } + CF_EXPECT(ValidateServerVersion(), "Unable to ensure cvd_server is running."); + + auto version_command_handle_report = + CF_EXPECT(HandleVersionCommand(*this, new_all_args)); + if (version_command_handle_report == VersionCommandReport::kVersion) { + return {}; + } + + const cvd_common::Args new_cmd_args{"cvd", "process"}; + CF_EXPECT(!new_all_args.empty()); + const cvd_common::Args new_selector_args{new_all_args.begin(), + new_all_args.end()}; + // TODO(schuffelen): Deduplicate when calls to setenv are removed. + CF_EXPECT(HandleCommand(new_cmd_args, env, new_selector_args)); + return {}; +} + +Result CvdClient::HandleVersion() { + return fmt::format("Server version:\n\n{}\nClient version:\n\n{}\n", + CF_EXPECT(GetServerVersion()), ClientVersion()); +} + +Result CvdClient::ListSubcommands(const cvd_common::Envs& envs) { + cvd_common::Args args{"cvd", "cmd-list"}; + SharedFD read_pipe, write_pipe; + CF_EXPECT(cuttlefish::SharedFD::Pipe(&read_pipe, &write_pipe), + "Unable to create shutdown pipe: " << strerror(errno)); + OverrideFd new_control_fd{.stdout_override_fd = write_pipe}; + CF_EXPECT( + HandleCommand(args, envs, std::vector{}, new_control_fd)); + + write_pipe->Close(); + const int kChunkSize = 512; + char buf[kChunkSize + 1] = {0}; + std::stringstream ss; + do { + auto n_read = ReadExact(read_pipe, buf, kChunkSize); + CF_EXPECT(n_read >= 0 && (n_read <= kChunkSize)); + if (n_read == 0) { + break; + } + buf[n_read] = 0; // null-terminate the C-style string + ss << buf; + if (n_read < sizeof(buf) - 1) { + break; + } + } while (true); + auto json_output = CF_EXPECT(ParseJson(ss.str())); + return json_output; +} + +Result CvdClient::ValidSubcmdsList( + const cvd_common::Envs& envs) { + auto valid_subcmd_json = CF_EXPECT(ListSubcommands(envs)); + CF_EXPECT(valid_subcmd_json.isMember("subcmd"), + "Server returned the list of subcommands in Json but it is missing " + << " \"subcmd\" field"); + std::string valid_subcmd_string = valid_subcmd_json["subcmd"].asString(); + auto valid_subcmds = android::base::Tokenize(valid_subcmd_string, ","); + return valid_subcmds; +} + +CvdClient::CvdClient(const android::base::LogSeverity verbosity, + const std::string& server_socket_path) + : server_socket_path_(server_socket_path), verbosity_(verbosity) {} + +} // end of namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/client.h b/base/cvd/cuttlefish/host/commands/cvd/client.h new file mode 100644 index 0000000000..ea5773c05f --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/client.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include +#include + +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/unix_sockets.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/server_constants.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +struct OverrideFd { + std::optional stdin_override_fd; + std::optional stdout_override_fd; + std::optional stderr_override_fd; +}; + +class CvdClient { + public: + CvdClient(const android::base::LogSeverity verbosity, + const std::string& server_socket_path = ServerSocketPath()); + Result ValidateServerVersion(const int num_retries = 1); + Result StopCvdServer(bool clear); + Result HandleAcloud( + const std::vector& args, + const std::unordered_map& env); + Result HandleCvdCommand( + const std::vector& args, + const std::unordered_map& env); + Result HandleCommand( + const std::vector& args, + const std::unordered_map& env, + const std::vector& selector_args, + const OverrideFd& control_fds); + Result HandleCommand( + const std::vector& args, + const std::unordered_map& env, + const std::vector& selector_args) { + auto response = CF_EXPECT( + HandleCommand(args, env, selector_args, + OverrideFd{std::nullopt, std::nullopt, std::nullopt})); + return response; + } + Result HandleVersion(); + Result ValidSubcmdsList(const cvd_common::Envs& envs); + + private: + std::optional server_; + + Result SetServer(const SharedFD& server); + Result SendRequest(const cvd::Request& request, + const OverrideFd& new_control_fds = {}, + std::optional extra_fd = {}); + Result StartCvdServer(); + Result CheckStatus(const cvd::Status& status, const std::string& rpc); + Result GetServerVersion(); + + Result ListSubcommands(const cvd_common::Envs& envs); + Result ConnectToServer(); + + Result RestartServer(const cvd::Version& server_version); + + std::string server_socket_path_; + android::base::LogSeverity verbosity_; +}; + +} // end of namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/command_sequence.cpp b/base/cvd/cuttlefish/host/commands/cvd/command_sequence.cpp new file mode 100644 index 0000000000..c754ec6f99 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/command_sequence.cpp @@ -0,0 +1,145 @@ +// +// Copyright (C) 2022 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. + +#include "host/commands/cvd/command_sequence.h" + +#include + +#include "common/libs/fs/shared_buf.h" +#include "host/commands/cvd/request_context.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace { + +std::string BashEscape(const std::string& input) { + bool safe = true; + for (const auto& c : input) { + if ('0' <= c && c <= '9') { + continue; + } + if ('a' <= c && c <= 'z') { + continue; + } + if ('A' <= c && c <= 'Z') { + continue; + } + if (c == '_' || c == '-' || c == '.' || c == ',' || c == '/') { + continue; + } + safe = false; + } + using android::base::StringReplace; + return safe ? input : "'" + StringReplace(input, "'", "\\'", true) + "'"; +} + +std::string FormattedCommand(const cvd::CommandRequest command) { + std::stringstream effective_command; + effective_command << "Executing `"; + for (const auto& [name, val] : command.env()) { + effective_command << BashEscape(name) << "=" << BashEscape(val) << " "; + } + auto args = cvd_common::ConvertToArgs(command.args()); + auto selector_args = + cvd_common::ConvertToArgs(command.selector_opts().args()); + if (args.empty()) { + return effective_command.str(); + } + const auto& cmd = args.front(); + cvd_common::Args cmd_args{args.begin() + 1, args.end()}; + effective_command << BashEscape(cmd) << " "; + for (const auto& selector_arg : selector_args) { + effective_command << BashEscape(selector_arg) << " "; + } + for (const auto& cmd_arg : cmd_args) { + effective_command << BashEscape(cmd_arg) << " "; + } + effective_command.seekp(-1, effective_command.cur); + effective_command << "`\n"; // Overwrite last space + return effective_command.str(); +} + +} // namespace + +CommandSequenceExecutor::CommandSequenceExecutor( + const std::vector>& server_handlers) + : server_handlers_(server_handlers) {} + +Result CommandSequenceExecutor::Interrupt() { + std::unique_lock interrupt_lock(interrupt_mutex_); + interrupted_ = true; + if (handler_stack_.empty()) { + return {}; + } + CF_EXPECT(handler_stack_.back()->Interrupt()); + return {}; +} + +Result> CommandSequenceExecutor::Execute( + const std::vector& requests, SharedFD report) { + std::unique_lock interrupt_lock(interrupt_mutex_); + CF_EXPECT(!interrupted_, "Interrupted"); + + std::vector responses; + for (const auto& request : requests) { + auto& inner_proto = request.Message(); + if (inner_proto.has_command_request()) { + auto& command = inner_proto.command_request(); + std::string str = FormattedCommand(command); + CF_EXPECT(WriteAll(report, str) == str.size(), report->StrError()); + } + + auto handler = CF_EXPECT(RequestHandler(request, server_handlers_)); + handler_stack_.push_back(handler); + interrupt_lock.unlock(); + auto response = CF_EXPECT(handler->Handle(request)); + interrupt_lock.lock(); + handler_stack_.pop_back(); + + CF_EXPECT(interrupted_ == false, "Interrupted"); + CF_EXPECT(response.status().code() == cvd::Status::OK, + "Reason: \"" << response.status().message() << "\""); + + responses.emplace_back(std::move(response)); + } + return {responses}; +} + +Result CommandSequenceExecutor::ExecuteOne( + const RequestWithStdio& request, SharedFD report) { + auto response_in_vector = CF_EXPECT(Execute({request}, report)); + CF_EXPECT_EQ(response_in_vector.size(), 1); + return response_in_vector.front(); +} + +std::vector CommandSequenceExecutor::CmdList() const { + std::unordered_set subcmds; + for (const auto& handler : server_handlers_) { + auto&& cmds_list = handler->CmdList(); + for (const auto& cmd : cmds_list) { + subcmds.insert(cmd); + } + } + // duplication removed + return std::vector{subcmds.begin(), subcmds.end()}; +} + +Result CommandSequenceExecutor::GetHandler( + const RequestWithStdio& request) { + return CF_EXPECT(RequestHandler(request, server_handlers_)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/command_sequence.h b/base/cvd/cuttlefish/host/commands/cvd/command_sequence.h new file mode 100644 index 0000000000..5ff948b4ed --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/command_sequence.h @@ -0,0 +1,48 @@ +// +// Copyright (C) 2022 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 + +#include +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +class CommandSequenceExecutor { + public: + CommandSequenceExecutor( + const std::vector>& server_handlers); + + Result Interrupt(); + Result> Execute( + const std::vector&, SharedFD report); + Result ExecuteOne(const RequestWithStdio&, SharedFD report); + + std::vector CmdList() const; + Result GetHandler(const RequestWithStdio& request); + + private: + const std::vector>& server_handlers_; + std::vector handler_stack_; + std::mutex interrupt_mutex_; + bool interrupted_ = false; +}; +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/common_utils.cpp b/base/cvd/cuttlefish/host/commands/cvd/common_utils.cpp new file mode 100644 index 0000000000..a2c032f58b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/common_utils.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/common_utils.h" + +#include +#include +#include + +#include +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/users.h" + +namespace cuttlefish { + +cvd::Request MakeRequest(const MakeRequestForm& request_form) { + return MakeRequest(request_form, cvd::WAIT_BEHAVIOR_COMPLETE); +} + +cvd::Request MakeRequest(const MakeRequestForm& request_form, + cvd::WaitBehavior wait_behavior) { + const auto& args = request_form.cmd_args; + const auto& env = request_form.env; + const auto& selector_args = request_form.selector_args; + cvd::Request request; + auto command_request = request.mutable_command_request(); + for (const std::string& arg : args) { + command_request->add_args(arg); + } + auto selector_opts = command_request->mutable_selector_opts(); + for (const std::string& selector_arg : selector_args) { + selector_opts->add_args(selector_arg); + } + + for (const auto& [key, value] : env) { + (*command_request->mutable_env())[key] = value; + } + + /* + * the client must set the kAndroidHostOut environment variable. There were, + * however, a few branches where kAndroidSoongHostOut replaced + * kAndroidHostOut. Cvd server eventually read kAndroidHostOut only and set + * both for the subtools. + * + * If none of the two are set, cvd server tries to use the parent directory of + * the client cvd executable as env[kAndroidHostOut]. + * + */ + if (!Contains(command_request->env(), kAndroidHostOut)) { + const std::string new_android_host_out = + Contains(command_request->env(), kAndroidSoongHostOut) + ? (*command_request->mutable_env())[kAndroidSoongHostOut] + : android::base::Dirname(android::base::GetExecutableDirectory()); + (*command_request->mutable_env())[kAndroidHostOut] = new_android_host_out; + } + + if (!request_form.working_dir) { + std::unique_ptr cwd(getcwd(nullptr, 0), &free); + command_request->set_working_directory(cwd.get()); + } else { + command_request->set_working_directory(request_form.working_dir.value()); + } + command_request->set_wait_behavior(wait_behavior); + + return request; +} + +cvd::Response CommandResponse(const cvd::Status_Code code, + const std::string message) { + cvd::Response response; + response.mutable_command_response(); // set oneof field + auto& status = *response.mutable_status(); + status.set_code(code); + status.set_message(message); + return response; +} + +template +std::ostream& operator<<(std::ostream& out, const std::vector& v) { + if (v.empty()) { + out << "{}"; + return out; + } + if (v.size() == 1) { + out << "{" << v.front() << "}"; + return out; + } + out << "{"; + for (size_t i = 0; i != v.size() - 1; i++) { + out << v.at(i) << ", "; + } + out << v.back() << "}"; + return out; +} + +Result EncodeVerbosity( + const std::string& verbosity) { + std::unordered_map + verbosity_encode_tab{ + {"VERBOSE", android::base::VERBOSE}, + {"DEBUG", android::base::DEBUG}, + {"INFO", android::base::INFO}, + {"WARNING", android::base::WARNING}, + {"ERROR", android::base::ERROR}, + {"FATAL_WITHOUT_ABORT", android::base::FATAL_WITHOUT_ABORT}, + {"FATAL", android::base::FATAL}, + }; + CF_EXPECT(Contains(verbosity_encode_tab, verbosity), + "Verbosity \"" << verbosity << "\" is unrecognized."); + return verbosity_encode_tab.at(verbosity); +} + +Result VerbosityToString( + const android::base::LogSeverity verbosity) { + std::unordered_map + verbosity_decode_tab{ + {android::base::VERBOSE, "VERBOSE"}, + {android::base::DEBUG, "DEBUG"}, + {android::base::INFO, "INFO"}, + {android::base::WARNING, "WARNING"}, + {android::base::ERROR, "ERROR"}, + {android::base::FATAL_WITHOUT_ABORT, "FATAL_WITHOUT_ABORT"}, + {android::base::FATAL, "FATAL"}, + }; + CF_EXPECT(Contains(verbosity_decode_tab, verbosity), + "Verbosity \"" << verbosity << "\" is unrecognized."); + return verbosity_decode_tab.at(verbosity); +} + +static std::mutex verbosity_mutex; + +android::base::LogSeverity SetMinimumVerbosity( + const android::base::LogSeverity severity) { + std::lock_guard lock(verbosity_mutex); + return android::base::SetMinimumLogSeverity(severity); +} + +Result SetMinimumVerbosity( + const std::string& severity) { + std::lock_guard lock(verbosity_mutex); + return SetMinimumVerbosity(CF_EXPECT(EncodeVerbosity(severity))); +} + +android::base::LogSeverity GetMinimumVerbosity() { + std::lock_guard lock(verbosity_mutex); + return android::base::GetMinimumLogSeverity(); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/common_utils.h b/base/cvd/cuttlefish/host/commands/cvd/common_utils.h new file mode 100644 index 0000000000..c676042dfc --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/common_utils.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 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 + +#include + +#include +#include +#include +#include + +#include + +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +// utility struct for std::variant uses +template +struct Overload : Ts... { + using Ts::operator()...; +}; + +template +Overload(Ts...) -> Overload; + +struct MakeRequestForm { + cvd_common::Args cmd_args; + cvd_common::Envs env; + cvd_common::Args selector_args; + std::optional working_dir; +}; + +cvd::Request MakeRequest(const MakeRequestForm& request_form, + const cvd::WaitBehavior wait_behavior); + +cvd::Request MakeRequest(const MakeRequestForm& request_form); + +cvd::Response CommandResponse(const cvd::Status_Code, + const std::string message = ""); + +// name of environment variable to mark the launch_cvd initiated by the cvd +// server +static constexpr char kCvdMarkEnv[] = "_STARTED_BY_CVD_SERVER_"; + +constexpr char kServerExecPath[] = "/proc/self/exe"; + +// The name of environment variable that points to the host out directory +constexpr char kAndroidHostOut[] = "ANDROID_HOST_OUT"; +// kAndroidHostOut for old branches +constexpr char kAndroidSoongHostOut[] = "ANDROID_SOONG_HOST_OUT"; +constexpr char kAndroidProductOut[] = "ANDROID_PRODUCT_OUT"; +constexpr char kLaunchedByAcloud[] = "LAUNCHED_BY_ACLOUD"; + +template +Ostream& ConcatToStream(Ostream& out, Args&&... args) { + (out << ... << std::forward(args)); + return out; +} + +template +std::string ConcatToString(Args&&... args) { + std::stringstream concatenator; + return ConcatToStream(concatenator, std::forward(args)...).str(); +} + +constexpr android::base::LogSeverity kCvdDefaultVerbosity = android::base::INFO; + +Result EncodeVerbosity( + const std::string& verbosity); + +Result VerbosityToString( + const android::base::LogSeverity verbosity); + +android::base::LogSeverity SetMinimumVerbosity( + const android::base::LogSeverity); +Result SetMinimumVerbosity(const std::string&); + +android::base::LogSeverity GetMinimumVerbosity(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/demo_multi_vd.cpp b/base/cvd/cuttlefish/host/commands/cvd/demo_multi_vd.cpp new file mode 100644 index 0000000000..f16bc732ea --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/demo_multi_vd.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 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. + */ +#include "host/commands/cvd/demo_multi_vd.h" + +#include "host/commands/cvd/server_command/serial_launch.h" +#include "host/commands/cvd/server_command/serial_preset.h" + +namespace cuttlefish { + +fruit::Component> +DemoMultiVdComponent() { + return fruit::createComponent() + .install(cvdSerialLaunchComponent) + .install(cvdSerialPresetComponent); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/demo_multi_vd.h b/base/cvd/cuttlefish/host/commands/cvd/demo_multi_vd.h new file mode 100644 index 0000000000..f0202eeb42 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/demo_multi_vd.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 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 + +#include + +#include "host/commands/cvd/command_sequence.h" + +namespace cuttlefish { + +fruit::Component> +DemoMultiVdComponent(); + +} diff --git a/base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.dot b/base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.dot new file mode 100644 index 0000000000..13d0419628 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.dot @@ -0,0 +1,92 @@ +digraph { + rankdir = "LR" + + subgraph cluster_cvd_client { + label = "cvd (client)" + CvdClient + } + subgraph cluster_cvd_server { + label = "cvd (server)" + CvdServer + CvdServerHandler [style = "dashed"] + CommandSequenceExecutor + + subgraph first_order_handlers { + rank = same; + + AcloudMixSuperImageCommand + CvdFetchCommandHandler + CvdFleetCommandHandler + CvdHelpHandler + CvdGenericCommandHandler + CvdResetCommandHandler + CvdShutdownHandler + CvdStartCommandHandler + CvdVersionHandler + TryAcloudCommand + } + subgraph second_order_handlers { + rank = same; + + AcloudCommand + AcloudTranslatorCommand + CvdServerHandlerProxy + CvdVmControlCommandHandler + LoadConfigsCommand + SerialLaunchCommand + SerialPreset + } + } + subgraph device_executables { + rank = same; + + launch_cvd + restart_cvd + powerwash_cvd + all_cvd [label = "*_cvd"] + cvd_status + stop_cvd + cvd_host_bugreport + acloud [label = "acloud (python)"] + } + + CvdClient -> CvdServer [label = "AF_UNIX"] + CvdClient -> acloud [label = "fork/exec"] + CvdServer -> CvdServerHandler + + CvdServerHandler -> TryAcloudCommand + CvdServerHandler -> AcloudTranslatorCommand + CvdServerHandler -> AcloudCommand + CvdServerHandler -> AcloudMixSuperImageCommand + CvdServerHandler -> CvdVmControlCommandHandler + CvdServerHandler -> CvdFetchCommandHandler + CvdServerHandler -> CvdFleetCommandHandler + CvdServerHandler -> CvdGenericCommandHandler [minlen = 2] + CvdServerHandler -> CvdServerHandlerProxy + CvdServerHandler -> CvdHelpHandler + CvdServerHandler -> LoadConfigsCommand + CvdServerHandler -> CvdResetCommandHandler + CvdServerHandler -> SerialLaunchCommand + CvdServerHandler -> SerialPreset + CvdServerHandler -> CvdShutdownHandler + CvdServerHandler -> CvdStartCommandHandler + CvdServerHandler -> CvdVersionHandler + + CommandSequenceExecutor -> AcloudTranslatorCommand [dir = "back"] + CommandSequenceExecutor -> AcloudCommand [dir = "back"] + CommandSequenceExecutor -> CvdServerHandlerProxy [dir = "back"] + CommandSequenceExecutor -> LoadConfigsCommand [dir = "back"] + CommandSequenceExecutor -> SerialPreset [dir = "back"] + CommandSequenceExecutor -> SerialLaunchCommand [dir = "back"] + CommandSequenceExecutor -> CvdVmControlCommandHandler [dir = "back"] + + CommandSequenceExecutor -> CvdServerHandler + + CvdStartCommandHandler -> launch_cvd [label = "fork/exec"] + CvdGenericCommandHandler -> restart_cvd [label = "fork/exec"] + CvdGenericCommandHandler -> powerwash_cvd [label = "fork/exec"] + CvdHelpHandler -> all_cvd [label = "fork/exec"] + CvdFleetCommandHandler -> cvd_status [label = "fork/exec"] + CvdGenericCommandHandler -> stop_cvd [label = "fork/exec"] + CvdGenericCommandHandler -> cvd_host_bugreport [label = "fork/exec"] +} diff --git a/base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.png b/base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.png new file mode 100644 index 0000000000..db5de632f4 Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.svg b/base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.svg new file mode 100644 index 0000000000..2544ed8c9d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.svg @@ -0,0 +1,416 @@ + + + + + + +%3 + + +cluster_cvd_client + +cvd (client) + + +cluster_cvd_server + +cvd (server) + + + +CvdClient + +CvdClient + + + +CvdServer + +CvdServer + + + +CvdClient->CvdServer + + +AF_UNIX + + + +acloud + +acloud (python) + + + +CvdClient->acloud + + +fork/exec + + + +CvdServerHandler + +CvdServerHandler + + + +CvdServer->CvdServerHandler + + + + + +AcloudMixSuperImageCommand + +AcloudMixSuperImageCommand + + + +CvdServerHandler->AcloudMixSuperImageCommand + + + + + +CvdFetchCommandHandler + +CvdFetchCommandHandler + + + +CvdServerHandler->CvdFetchCommandHandler + + + + + +CvdFleetCommandHandler + +CvdFleetCommandHandler + + + +CvdServerHandler->CvdFleetCommandHandler + + + + + +CvdHelpHandler + +CvdHelpHandler + + + +CvdServerHandler->CvdHelpHandler + + + + + +CvdGenericCommandHandler + +CvdGenericCommandHandler + + + +CvdServerHandler->CvdGenericCommandHandler + + + + + +CvdResetCommandHandler + +CvdResetCommandHandler + + + +CvdServerHandler->CvdResetCommandHandler + + + + + +CvdShutdownHandler + +CvdShutdownHandler + + + +CvdServerHandler->CvdShutdownHandler + + + + + +CvdStartCommandHandler + +CvdStartCommandHandler + + + +CvdServerHandler->CvdStartCommandHandler + + + + + +CvdVersionHandler + +CvdVersionHandler + + + +CvdServerHandler->CvdVersionHandler + + + + + +TryAcloudCommand + +TryAcloudCommand + + + +CvdServerHandler->TryAcloudCommand + + + + + +AcloudCommand + +AcloudCommand + + + +CvdServerHandler->AcloudCommand + + + + + +AcloudTranslatorCommand + +AcloudTranslatorCommand + + + +CvdServerHandler->AcloudTranslatorCommand + + + + + +CvdServerHandlerProxy + +CvdServerHandlerProxy + + + +CvdServerHandler->CvdServerHandlerProxy + + + + + +CvdVmControlCommandHandler + +CvdVmControlCommandHandler + + + +CvdServerHandler->CvdVmControlCommandHandler + + + + + +LoadConfigsCommand + +LoadConfigsCommand + + + +CvdServerHandler->LoadConfigsCommand + + + + + +SerialLaunchCommand + +SerialLaunchCommand + + + +CvdServerHandler->SerialLaunchCommand + + + + + +SerialPreset + +SerialPreset + + + +CvdServerHandler->SerialPreset + + + + + +CommandSequenceExecutor + +CommandSequenceExecutor + + + +CommandSequenceExecutor->CvdServerHandler + + + + + +CommandSequenceExecutor->AcloudCommand + + + + + +CommandSequenceExecutor->AcloudTranslatorCommand + + + + + +CommandSequenceExecutor->CvdServerHandlerProxy + + + + + +CommandSequenceExecutor->CvdVmControlCommandHandler + + + + + +CommandSequenceExecutor->LoadConfigsCommand + + + + + +CommandSequenceExecutor->SerialLaunchCommand + + + + + +CommandSequenceExecutor->SerialPreset + + + + + +cvd_status + +cvd_status + + + +CvdFleetCommandHandler->cvd_status + + +fork/exec + + + +all_cvd + +*_cvd + + + +CvdHelpHandler->all_cvd + + +fork/exec + + + +restart_cvd + +restart_cvd + + + +CvdGenericCommandHandler->restart_cvd + + +fork/exec + + + +powerwash_cvd + +powerwash_cvd + + + +CvdGenericCommandHandler->powerwash_cvd + + +fork/exec + + + +stop_cvd + +stop_cvd + + + +CvdGenericCommandHandler->stop_cvd + + +fork/exec + + + +cvd_host_bugreport + +cvd_host_bugreport + + + +CvdGenericCommandHandler->cvd_host_bugreport + + +fork/exec + + + +launch_cvd + +launch_cvd + + + +CvdStartCommandHandler->launch_cvd + + +fork/exec + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/doc/load_config.dot b/base/cvd/cuttlefish/host/commands/cvd/doc/load_config.dot new file mode 100644 index 0000000000..42791b5c56 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/doc/load_config.dot @@ -0,0 +1,22 @@ +digraph { + rankdir = "LR" + + subgraph cluster_cvd_client { + label = "cvd (client)" + CvdClient + } + subgraph cluster_cvd_server { + label = "cvd (server)" + + CvdServer + CvdServerHandler [style = "dashed"] + + Request [shape = "record", label = " CvdServerHandlerProxy | CommandSequenceExecutor | CvdLoadConfigsCommand | CommandSequenceExecutor | CvdStartCommandHandler"] + } + launch_cvd + + CvdClient -> CvdServer [label = "AF_UNIX"] + CvdServer -> CvdServerHandler + CvdServerHandler -> Request:proxy + Request:start -> launch_cvd [label = "fork/exec"] +} diff --git a/base/cvd/cuttlefish/host/commands/cvd/doc/load_config.png b/base/cvd/cuttlefish/host/commands/cvd/doc/load_config.png new file mode 100644 index 0000000000..e110db1062 Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/doc/load_config.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/doc/load_config.svg b/base/cvd/cuttlefish/host/commands/cvd/doc/load_config.svg new file mode 100644 index 0000000000..fecc02eba0 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/doc/load_config.svg @@ -0,0 +1,87 @@ + + + + + + +%3 + + +cluster_cvd_client + +cvd (client) + + +cluster_cvd_server + +cvd (server) + + + +CvdClient + +CvdClient + + + +CvdServer + +CvdServer + + + +CvdClient->CvdServer + + +AF_UNIX + + + +CvdServerHandler + +CvdServerHandler + + + +CvdServer->CvdServerHandler + + + + + +Request + +CvdServerHandlerProxy + +CommandSequenceExecutor + +CvdLoadConfigsCommand + +CommandSequenceExecutor + +CvdStartCommandHandler + + + +CvdServerHandler->Request:proxy + + + + + +launch_cvd + +launch_cvd + + + +Request:start->launch_cvd + + +fork/exec + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/driver_flags.cpp b/base/cvd/cuttlefish/host/commands/cvd/driver_flags.cpp new file mode 100644 index 0000000000..3b13f64ddf --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/driver_flags.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/driver_flags.h" + +#include + +namespace cuttlefish { + +CvdFlag DriverFlags::HelpFlag() { + const bool default_val = false; + CvdFlag help_flag(kHelp, default_val); + std::stringstream help; + help << "--" << kHelp << "to print this message."; + help_flag.SetHelpMessage(help.str()); + return help_flag; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/driver_flags.h b/base/cvd/cuttlefish/host/commands/cvd/driver_flags.h new file mode 100644 index 0000000000..a93191de33 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/driver_flags.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/flag.h" + +namespace cuttlefish { + +/** + * The authentic collection of cvd driver flags + * + */ +// names of the flags, which are also used for search + +class DriverFlags { + public: + static constexpr char kHelp[] = "help"; + static const DriverFlags& Get(); + + Result GetFlag(const std::string& search_key) const { + auto flag = CF_EXPECT(flags_.GetFlag(search_key)); + return flag; + } + + std::vector Flags() const { return flags_.Flags(); } + CvdFlag HelpFlag(); + + private: + DriverFlags() { flags_.EnrollFlag(HelpFlag()); } + + FlagCollection flags_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/epoll_loop.cpp b/base/cvd/cuttlefish/host/commands/cvd/epoll_loop.cpp new file mode 100644 index 0000000000..14fd6a7a6b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/epoll_loop.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/epoll_loop.h" + +#include + +#include "common/libs/fs/epoll.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +EpollPool::EpollPool() { + auto epoll = Epoll::Create(); + if (!epoll.ok()) { + LOG(ERROR) << epoll.error().FormatForEnv(); + abort(); + } + epoll_ = std::move(*epoll); +} + +Result EpollPool::Register(SharedFD fd, uint32_t events, + EpollCallback callback) { + std::lock_guard callbacks_lock(callbacks_mutex_); + CF_EXPECT(!Contains(callbacks_, fd), "Already have a callback created"); + CF_EXPECT(epoll_.AddOrModify(fd, events | EPOLLONESHOT)); + callbacks_[fd] = std::move(callback); + return {}; +} + +Result EpollPool::HandleEvent() { + auto event = CF_EXPECT(epoll_.Wait()); + if (!event) { + return {}; + } + EpollCallback callback; + { + std::lock_guard callbacks_lock(callbacks_mutex_); + auto it = callbacks_.find(event->fd); + CF_EXPECT(it != callbacks_.end(), "Could not find event callback"); + callback = std::move(it->second); + callbacks_.erase(it); + } + CF_EXPECT(callback(*event)); + return {}; +} + +Result EpollPool::Remove(SharedFD fd) { + std::lock_guard callbacks_lock(callbacks_mutex_); + CF_EXPECT(epoll_.Delete(fd), "No callback registered with epoll"); + callbacks_.erase(fd); + return {}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/epoll_loop.h b/base/cvd/cuttlefish/host/commands/cvd/epoll_loop.h new file mode 100644 index 0000000000..059b1020fc --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/epoll_loop.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 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. + */ + +#include +#include +#include + +#include "common/libs/fs/epoll.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" + +#pragma once + +namespace cuttlefish { + +using EpollCallback = std::function(EpollEvent)>; + +class EpollPool { + public: + EpollPool(); + + /** + * The `callback` function will be invoked with an EpollEvent containing `fd` + * and a subset of the bits in `events` matching which events were actually + * observed. The callback is invoked exactly once (enforced via EPOLLONESHOT) + * and must be re-`Register`ed to receive events again. This can be done + * in the callback implementation. Callbacks are invoked by callers of the + * `HandleEvent` function, and any errors produced by the callback function + * will manifest there. Callbacks that return errors will not be automatically + * re-registered. + */ + Result Register(SharedFD fd, uint32_t events, EpollCallback callback); + Result HandleEvent(); + Result Remove(SharedFD fd); + + private: + Epoll epoll_; + std::mutex callbacks_mutex_; + std::map callbacks_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd.cc b/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd.cc new file mode 100644 index 0000000000..0ff31e44d5 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd.cc @@ -0,0 +1,606 @@ +// +// Copyright (C) 2019 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. + +#include "host/commands/cvd/fetch/fetch_cvd.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/archive.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/tee_logging.h" +#include "host/commands/cvd/fetch/fetch_cvd_parser.h" +#include "host/libs/config/fetcher_config.h" +#include "host/libs/web/android_build_api.h" +#include "host/libs/web/android_build_string.h" +#include "host/libs/web/credential_source.h" +#include "host/libs/web/http_client/http_client.h" + +namespace cuttlefish { +namespace { + +constexpr mode_t kRwxAllMode = S_IRWXU | S_IRWXG | S_IRWXO; +constexpr bool kOverrideEntries = true; + +struct BuildStrings { + std::optional default_build; + std::optional system_build; + std::optional kernel_build; + std::optional boot_build; + std::optional bootloader_build; + std::optional android_efi_loader_build; + std::optional otatools_build; + std::optional host_package_build; +}; + +struct DownloadFlags { + bool download_img_zip; + bool download_target_files_zip; +}; + +struct TargetDirectories { + std::string root; + std::string otatools; + std::string default_target_files; + std::string system_target_files; +}; + +struct Builds { + std::optional default_build; + std::optional system; + std::optional kernel; + std::optional boot; + std::optional bootloader; + std::optional android_efi_loader; + std::optional otatools; +}; + +struct Target { + BuildStrings build_strings; + DownloadFlags download_flags; + TargetDirectories directories; + Builds builds; +}; + +struct HostToolsTarget { + std::optional build_string; + std::string host_tools_directory; +}; + +bool ShouldAppendSubdirectory(const FetchFlags& flags) { + return flags.number_of_builds > 1 || !flags.target_subdirectory.empty(); +} + +template +T AccessOrDefault(const std::vector& vector, const int i, + const T& default_value) { + if (i < vector.size()) { + return vector[i]; + } else { + return default_value; + } +} + +BuildStrings GetBuildStrings(const VectorFlags& flags, const int index) { + auto build_strings = BuildStrings{ + .default_build = AccessOrDefault>( + flags.default_build, index, std::nullopt), + .system_build = AccessOrDefault>( + flags.system_build, index, std::nullopt), + .kernel_build = AccessOrDefault>( + flags.kernel_build, index, std::nullopt), + .boot_build = AccessOrDefault>( + flags.boot_build, index, std::nullopt), + .bootloader_build = AccessOrDefault>( + flags.bootloader_build, index, std::nullopt), + .android_efi_loader_build = AccessOrDefault>( + flags.android_efi_loader_build, index, std::nullopt), + .otatools_build = AccessOrDefault>( + flags.otatools_build, index, std::nullopt), + }; + auto possible_boot_artifact = + AccessOrDefault(flags.boot_artifact, index, ""); + if (!possible_boot_artifact.empty() && build_strings.boot_build) { + SetFilepath(*build_strings.boot_build, possible_boot_artifact); + } + return build_strings; +} + +DownloadFlags GetDownloadFlags(const VectorFlags& flags, const int index) { + return DownloadFlags{ + .download_img_zip = AccessOrDefault(flags.download_img_zip, index, + kDefaultDownloadImgZip), + .download_target_files_zip = + AccessOrDefault(flags.download_target_files_zip, index, + kDefaultDownloadTargetFilesZip), + }; +} + +TargetDirectories GetTargetDirectories( + const std::string& target_directory, + const std::vector& target_subdirectories, const int index, + const bool append_subdirectory) { + std::string base_directory = target_directory; + if (append_subdirectory) { + base_directory += + "/" + AccessOrDefault(target_subdirectories, index, + "instance_" + std::to_string(index)); + } + return TargetDirectories{.root = base_directory, + .otatools = base_directory + "/otatools/", + .default_target_files = base_directory + "/default", + .system_target_files = base_directory + "/system"}; +} + +std::vector GetFetchTargets(const FetchFlags& flags, + const bool append_subdirectory) { + std::vector result(flags.number_of_builds); + for (int i = 0; i < result.size(); ++i) { + result[i] = Target{ + .build_strings = GetBuildStrings(flags.vector_flags, i), + .download_flags = GetDownloadFlags(flags.vector_flags, i), + .directories = GetTargetDirectories(flags.target_directory, + flags.target_subdirectory, i, + append_subdirectory), + }; + } + return result; +} + +HostToolsTarget GetHostToolsTarget(const FetchFlags& flags, + const bool append_subdirectory) { + std::string host_directory = flags.target_directory; + if (append_subdirectory) { + host_directory = host_directory + "/" + kHostToolsSubdirectory; + } + return HostToolsTarget{ + .build_string = flags.host_package_build, + .host_tools_directory = host_directory, + }; +} + +Result EnsureDirectoriesExist(const std::string& target_directory, + const std::string& host_tools_directory, + const std::vector& targets) { + CF_EXPECT(EnsureDirectoryExists(target_directory)); + CF_EXPECT(EnsureDirectoryExists(host_tools_directory)); + for (const auto& target : targets) { + CF_EXPECT(EnsureDirectoryExists(target.directories.root, kRwxAllMode)); + CF_EXPECT(EnsureDirectoryExists(target.directories.otatools, kRwxAllMode)); + CF_EXPECT(EnsureDirectoryExists(target.directories.default_target_files, + kRwxAllMode)); + CF_EXPECT(EnsureDirectoryExists(target.directories.system_target_files, + kRwxAllMode)); + } + return {}; +} + +Result FetchHostPackage(BuildApi& build_api, const Build& build, + const std::string& target_dir, + const bool keep_archives) { + std::string host_tools_filepath = CF_EXPECT( + build_api.DownloadFile(build, target_dir, "cvd-host_package.tar.gz")); + CF_EXPECT( + ExtractArchiveContents(host_tools_filepath, target_dir, keep_archives)); + return {}; +} + +Result GetBuildApi(const BuildApiFlags& flags) { + auto resolver = + flags.external_dns_resolver ? GetEntDnsResolve : NameResolver(); + const bool use_logging_debug_function = true; + std::unique_ptr curl = + HttpClient::CurlClient(resolver, use_logging_debug_function); + std::unique_ptr retrying_http_client = + HttpClient::ServerErrorRetryClient(*curl, 10, + std::chrono::milliseconds(5000)); + std::string oauth_filepath = + StringFromEnv("HOME", ".") + "/.acloud_oauth2.dat"; + std::unique_ptr credential_source = + CF_EXPECT(GetCredentialSource( + *retrying_http_client, flags.credential_source, oauth_filepath, + flags.credential_flags.use_gce_metadata, + flags.credential_flags.credential_filepath, + flags.credential_flags.service_account_filepath)); + + return BuildApi(std::move(retrying_http_client), std::move(curl), + std::move(credential_source), flags.api_key, + flags.wait_retry_period, flags.api_base_url); +} + +Result> GetBuildHelper( + BuildApi& build_api, const std::optional& build_source, + const std::string& fallback_target) { + if (!build_source) { + return std::nullopt; + } + return CF_EXPECT(build_api.GetBuild(*build_source, fallback_target), + "Unable to create build from (" + << *build_source << ") and target (" << fallback_target + << ")"); +} + +Result GetBuilds(BuildApi& build_api, + const BuildStrings& build_sources) { + Builds result = Builds{ + .default_build = CF_EXPECT(GetBuildHelper( + build_api, build_sources.default_build, kDefaultBuildTarget)), + .system = CF_EXPECT(GetBuildHelper(build_api, build_sources.system_build, + kDefaultBuildTarget)), + .kernel = CF_EXPECT( + GetBuildHelper(build_api, build_sources.kernel_build, "kernel")), + .boot = CF_EXPECT(GetBuildHelper(build_api, build_sources.boot_build, + "gki_x86_64-user")), + .bootloader = CF_EXPECT(GetBuildHelper( + build_api, build_sources.bootloader_build, "u-boot_crosvm_x86_64")), + .android_efi_loader = CF_EXPECT( + GetBuildHelper(build_api, build_sources.android_efi_loader_build, + "gbl_efi_dist_and_test")), + .otatools = CF_EXPECT(GetBuildHelper( + build_api, build_sources.otatools_build, kDefaultBuildTarget)), + }; + if (!result.otatools) { + if (result.system) { + result.otatools = result.system; + } else if (result.kernel) { + result.otatools = result.default_build; + } + } + return {result}; +} + +Result UpdateTargetsWithBuilds(BuildApi& build_api, + std::vector& targets) { + for (auto& target : targets) { + target.builds = CF_EXPECT(GetBuilds(build_api, target.build_strings)); + } + return {}; +} + +Result GetHostBuild(BuildApi& build_api, HostToolsTarget& host_target, + const std::optional& fallback_host_build) { + auto host_package_build = CF_EXPECT( + GetBuildHelper(build_api, host_target.build_string, kDefaultBuildTarget)); + CF_EXPECT(host_package_build.has_value() || fallback_host_build.has_value(), + "Either the host_package_build or default_build requires a value. " + "(previous default_build default was " + "aosp-master/aosp_cf_x86_64_phone-userdebug)"); + return host_package_build.value_or(*fallback_host_build); +} + +Result SaveConfig(FetcherConfig& config, + const std::string& target_directory) { + // Due to constraints of the build system, artifacts intentionally cannot + // determine their own build id. So it's unclear which build number fetch_cvd + // itself was built at. + // https://android.googlesource.com/platform/build/+/979c9f3/Changes.md#build_number + std::string fetcher_path = target_directory + "/fetcher_config.json"; + CF_EXPECT(config.AddFilesToConfig(FileSource::GENERATED, "", "", + {fetcher_path}, target_directory)); + config.SaveToFile(fetcher_path); + + for (const auto& file : config.get_cvd_files()) { + LOG(VERBOSE) << target_directory << "/" << file.second.file_path << "\n"; + } + return {}; +} + +Result FetchTarget(BuildApi& build_api, const Builds& builds, + const TargetDirectories& target_directories, + const DownloadFlags& flags, + const bool keep_downloaded_archives, + FetcherConfig& config) { + if (builds.default_build) { + const auto [default_build_id, default_build_target] = + GetBuildIdAndTarget(*builds.default_build); + + // Some older builds might not have misc_info.txt, so permit errors on + // fetching misc_info.txt + Result misc_info_result = build_api.DownloadFile( + *builds.default_build, target_directories.root, "misc_info.txt"); + if (misc_info_result.ok()) { + CF_EXPECT(config.AddFilesToConfig( + FileSource::DEFAULT_BUILD, default_build_id, default_build_target, + {misc_info_result.value()}, target_directories.root, + kOverrideEntries)); + } + + if (flags.download_img_zip) { + std::string img_zip_name = GetBuildZipName(*builds.default_build, "img"); + std::string default_img_zip_filepath = CF_EXPECT(build_api.DownloadFile( + *builds.default_build, target_directories.root, img_zip_name)); + std::vector image_files = CF_EXPECT(ExtractArchiveContents( + default_img_zip_filepath, target_directories.root, + keep_downloaded_archives)); + LOG(INFO) << "Adding img-zip files for default build"; + for (auto& file : image_files) { + LOG(VERBOSE) << file; + } + CF_EXPECT(config.AddFilesToConfig(FileSource::DEFAULT_BUILD, + default_build_id, default_build_target, + image_files, target_directories.root)); + } + + if (builds.system || flags.download_target_files_zip) { + std::string target_files_name = + GetBuildZipName(*builds.default_build, "target_files"); + std::string target_files = CF_EXPECT(build_api.DownloadFile( + *builds.default_build, target_directories.default_target_files, + target_files_name)); + LOG(INFO) << "Adding target files for default build"; + CF_EXPECT(config.AddFilesToConfig( + FileSource::DEFAULT_BUILD, default_build_id, default_build_target, + {target_files}, target_directories.root)); + } + } + + if (builds.system) { + std::string target_files_name = + GetBuildZipName(*builds.system, "target_files"); + std::string target_files = CF_EXPECT(build_api.DownloadFile( + *builds.system, target_directories.system_target_files, + target_files_name)); + const auto [system_id, system_target] = GetBuildIdAndTarget(*builds.system); + CF_EXPECT(config.AddFilesToConfig(FileSource::SYSTEM_BUILD, system_id, + system_target, {target_files}, + target_directories.root)); + + if (flags.download_img_zip) { + std::string system_img_zip_name = GetBuildZipName(*builds.system, "img"); + Result system_img_zip_result = build_api.DownloadFile( + *builds.system, target_directories.root, system_img_zip_name); + Result> extract_result; + if (system_img_zip_result.ok()) { + extract_result = ExtractImages( + system_img_zip_result.value(), target_directories.root, + {"system.img", "product.img"}, keep_downloaded_archives); + if (extract_result.ok()) { + CF_EXPECT(config.AddFilesToConfig( + FileSource::SYSTEM_BUILD, system_id, system_target, + extract_result.value(), target_directories.root, + kOverrideEntries)); + } + } + if (!system_img_zip_result.ok() || !extract_result.ok()) { + std::string extracted_system = CF_EXPECT(ExtractImage( + target_files, target_directories.root, "IMAGES/system.img")); + CF_EXPECT(RenameFile(extracted_system, + target_directories.root + "/system.img")); + + Result extracted_product_result = ExtractImage( + target_files, target_directories.root, "IMAGES/product.img"); + if (extracted_product_result.ok()) { + CF_EXPECT(RenameFile(extracted_product_result.value(), + target_directories.root + "/product.img")); + } + + Result extracted_system_ext_result = ExtractImage( + target_files, target_directories.root, "IMAGES/system_ext.img"); + if (extracted_system_ext_result.ok()) { + CF_EXPECT(RenameFile(extracted_system_ext_result.value(), + target_directories.root + "/system_ext.img")); + } + + Result extracted_vbmeta_system = ExtractImage( + target_files, target_directories.root, "IMAGES/vbmeta_system.img"); + if (extracted_vbmeta_system.ok()) { + CF_EXPECT(RenameFile(extracted_vbmeta_system.value(), + target_directories.root + "/vbmeta_system.img")); + } + Result extracted_init_boot = ExtractImage( + target_files, target_directories.root, "IMAGES/init_boot.img"); + if (extracted_init_boot.ok()) { + CF_EXPECT(RenameFile(extracted_init_boot.value(), + target_directories.root + "/init_boot.img")); + } + } + } + } + + if (builds.kernel) { + std::string kernel_filepath = target_directories.root + "/kernel"; + // If the kernel is from an arm/aarch64 build, the artifact will be called + // Image. + std::string downloaded_kernel_filepath = + CF_EXPECT(build_api.DownloadFileWithBackup( + *builds.kernel, target_directories.root, "bzImage", "Image")); + CF_EXPECT(RenameFile(downloaded_kernel_filepath, kernel_filepath)); + const auto [kernel_id, kernel_target] = GetBuildIdAndTarget(*builds.kernel); + CF_EXPECT(config.AddFilesToConfig(FileSource::KERNEL_BUILD, kernel_id, + kernel_target, {kernel_filepath}, + target_directories.root)); + + // Certain kernel builds do not have corresponding ramdisks. + Result initramfs_img_result = build_api.DownloadFile( + *builds.kernel, target_directories.root, "initramfs.img"); + if (initramfs_img_result.ok()) { + CF_EXPECT(config.AddFilesToConfig( + FileSource::KERNEL_BUILD, kernel_id, kernel_target, + {initramfs_img_result.value()}, target_directories.root)); + } + } + + if (builds.boot) { + std::string boot_img_zip_name = GetBuildZipName(*builds.boot, "img"); + std::string downloaded_boot_filepath; + std::optional boot_filepath = GetFilepath(*builds.boot); + if (boot_filepath) { + downloaded_boot_filepath = CF_EXPECT(build_api.DownloadFileWithBackup( + *builds.boot, target_directories.root, *boot_filepath, + boot_img_zip_name)); + } else { + downloaded_boot_filepath = CF_EXPECT(build_api.DownloadFile( + *builds.boot, target_directories.root, boot_img_zip_name)); + } + + std::vector boot_files; + // downloaded a zip that needs to be extracted + if (android::base::EndsWith(downloaded_boot_filepath, boot_img_zip_name)) { + std::string extract_target = boot_filepath.value_or("boot.img"); + std::string extracted_boot = CF_EXPECT(ExtractImage( + downloaded_boot_filepath, target_directories.root, extract_target)); + std::string target_boot = CF_EXPECT( + RenameFile(extracted_boot, target_directories.root + "/boot.img")); + boot_files.push_back(target_boot); + + // keep_downloaded_archives flag used because this is the last extract + // on this archive + Result extracted_vendor_boot_result = + ExtractImage(downloaded_boot_filepath, target_directories.root, + "vendor_boot.img", keep_downloaded_archives); + if (extracted_vendor_boot_result.ok()) { + boot_files.push_back(extracted_vendor_boot_result.value()); + } + } else { + boot_files.push_back(downloaded_boot_filepath); + } + const auto [boot_id, boot_target] = GetBuildIdAndTarget(*builds.boot); + CF_EXPECT(config.AddFilesToConfig( + FileSource::BOOT_BUILD, boot_id, boot_target, boot_files, + target_directories.root, kOverrideEntries)); + } + + if (builds.bootloader) { + std::string bootloader_filepath = target_directories.root + "/bootloader"; + // If the bootloader is from an arm/aarch64 build, the artifact will be of + // filetype bin. + std::string downloaded_bootloader_filepath = + CF_EXPECT(build_api.DownloadFileWithBackup(*builds.bootloader, + target_directories.root, + "u-boot.rom", "u-boot.bin")); + CF_EXPECT(RenameFile(downloaded_bootloader_filepath, bootloader_filepath)); + const auto [bootloader_id, bootloader_target] = + GetBuildIdAndTarget(*builds.bootloader); + CF_EXPECT(config.AddFilesToConfig( + FileSource::BOOTLOADER_BUILD, bootloader_id, bootloader_target, + {bootloader_filepath}, target_directories.root, kOverrideEntries)); + } + + if (builds.android_efi_loader) { + std::string android_efi_loader_target_filepath = + target_directories.root + "/android_efi_loader.efi"; + std::optional android_efi_loader_filepath = + GetFilepath(*builds.android_efi_loader); + + std::string downloaded_android_efi_loader_filepath = + CF_EXPECT(build_api.DownloadFile( + *builds.android_efi_loader, target_directories.root, + android_efi_loader_filepath.value_or("gbl_x86_64.efi"))); + CF_EXPECT(RenameFile(downloaded_android_efi_loader_filepath, + android_efi_loader_target_filepath)); + + const auto [android_efi_loader_id, android_efi_loader_target] = + GetBuildIdAndTarget(*builds.android_efi_loader); + CF_EXPECT(config.AddFilesToConfig( + FileSource::ANDROID_EFI_LOADER_BUILD, android_efi_loader_id, + android_efi_loader_target, {android_efi_loader_target_filepath}, + target_directories.root, kOverrideEntries)); + } + + if (builds.otatools) { + std::string otatools_filepath = CF_EXPECT(build_api.DownloadFile( + *builds.otatools, target_directories.root, "otatools.zip")); + std::vector ota_tools_files = CF_EXPECT( + ExtractArchiveContents(otatools_filepath, target_directories.otatools, + keep_downloaded_archives)); + const auto [otatools_build_id, otatools_build_target] = + GetBuildIdAndTarget(*builds.otatools); + CF_EXPECT(config.AddFilesToConfig( + FileSource::DEFAULT_BUILD, otatools_build_id, otatools_build_target, + ota_tools_files, target_directories.root)); + } + return {}; +} + +Result Fetch(const FetchFlags& flags, HostToolsTarget& host_target, + std::vector& targets) { +#ifdef __BIONIC__ + // TODO(schuffelen): Find a better way to deal with tzdata + setenv("ANDROID_TZDATA_ROOT", "/", /* overwrite */ 0); + setenv("ANDROID_ROOT", "/", /* overwrite */ 0); +#endif + + curl_global_init(CURL_GLOBAL_DEFAULT); + { + BuildApi build_api = CF_EXPECT(GetBuildApi(flags.build_api_flags)); + CF_EXPECT(UpdateTargetsWithBuilds(build_api, targets)); + std::optional fallback_host_build = std::nullopt; + if (!targets.empty()) { + fallback_host_build = targets[0].builds.default_build; + } + const auto host_target_build = + CF_EXPECT(GetHostBuild(build_api, host_target, fallback_host_build)); + + auto host_package_future = + std::async(std::launch::async, FetchHostPackage, std::ref(build_api), + std::cref(host_target_build), + std::cref(host_target.host_tools_directory), + std::cref(flags.keep_downloaded_archives)); + for (const auto& target : targets) { + LOG(INFO) << "Starting fetch to \"" << target.directories.root << "\""; + FetcherConfig config; + CF_EXPECT(FetchTarget(build_api, target.builds, target.directories, + target.download_flags, + flags.keep_downloaded_archives, config)); + CF_EXPECT(SaveConfig(config, target.directories.root)); + LOG(INFO) << "Completed fetch to \"" << target.directories.root << "\""; + } + CF_EXPECT(host_package_future.get()); + } + curl_global_cleanup(); + + LOG(INFO) << "Completed all fetches"; + return {}; +} + +} // namespace + +Result FetchCvdMain(int argc, char** argv) { + android::base::InitLogging(argv, android::base::StderrLogger); + const FetchFlags flags = CF_EXPECT(GetFlagValues(argc, argv)); + const bool append_subdirectory = ShouldAppendSubdirectory(flags); + std::vector targets = GetFetchTargets(flags, append_subdirectory); + HostToolsTarget host_target = GetHostToolsTarget(flags, append_subdirectory); + CF_EXPECT(EnsureDirectoriesExist(flags.target_directory, + host_target.host_tools_directory, targets)); + android::base::SetLogger( + LogToStderrAndFiles({flags.target_directory + "/fetch.log"})); + android::base::SetMinimumLogSeverity(flags.verbosity); + + auto result = Fetch(flags, host_target, targets); + if (!result.ok()) { + LOG(ERROR) << result.error().FormatForEnv(); + } + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd.h b/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd.h new file mode 100644 index 0000000000..ab5e4b5167 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd.h @@ -0,0 +1,26 @@ +// +// Copyright (C) 2019 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 + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +inline constexpr char kHostToolsSubdirectory[] = "host_tools"; + +Result FetchCvdMain(int argc, char** argv); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd_parser.cc b/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd_parser.cc new file mode 100644 index 0000000000..e02ed73b38 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd_parser.cc @@ -0,0 +1,236 @@ +// +// Copyright (C) 2023 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. + +#include "host/commands/cvd/fetch/fetch_cvd_parser.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common/libs/utils/files.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/result.h" +#include "host/libs/web/android_build_string.h" + +namespace cuttlefish { +namespace { + +constexpr char kUsageMessage[] = + "*_build flags accept values in the following format:\n" + "{ | }[/][{}]\n" + "For example: " + "\"aosp-main/aosp_cf_x86_64_phone-trunk_staging-userdebug{file.txt}\"" + " fetches artifacts from the latest build of the argument\n" + "{} is used for certain artifacts to specify the file to " + "download location in the build artifacts\n" + "if is not specified then the default build target is: "; + +Flag GflagsCompatFlagSeconds(const std::string& name, + std::chrono::seconds& value) { + return GflagsCompatFlag(name) + .Getter([&value]() { return std::to_string(value.count()); }) + .Setter([&value](const FlagMatch& match) -> Result { + int parsed_int; + CF_EXPECTF(android::base::ParseInt(match.value, &parsed_int), + "Failed to parse \"{}\" as an integer", match.value); + value = std::chrono::seconds(parsed_int); + return {}; + }); +} + +std::vector GetFlagsVector(FetchFlags& fetch_flags, + std::string& directory) { + std::vector flags; + flags.emplace_back( + GflagsCompatFlag("directory", directory) + .Help("Target directory to fetch files into. (deprecated)")); + flags.emplace_back( + GflagsCompatFlag("target_directory", fetch_flags.target_directory) + .Help("Target directory to fetch files into.")); + flags.emplace_back(GflagsCompatFlag("keep_downloaded_archives", + fetch_flags.keep_downloaded_archives) + .Help("Keep downloaded zip/tar.")); + flags.emplace_back(VerbosityFlag(fetch_flags.verbosity)); + flags.emplace_back( + GflagsCompatFlag("target_subdirectory", fetch_flags.target_subdirectory) + .Help("Target subdirectory to fetch files into. Specifically aimed " + "at organizing builds when there are multiple fetches. " + "**Note**: directory separator automatically prepended, only " + "give the subdirectory name.")); + flags.emplace_back( + GflagsCompatFlag("host_package_build", fetch_flags.host_package_build) + .Help("source for the host cvd tools")); + + BuildApiFlags& build_api_flags = fetch_flags.build_api_flags; + flags.emplace_back(GflagsCompatFlag("api_key", build_api_flags.api_key) + .Help("API key ofr the Android Build API")); + flags.emplace_back( + GflagsCompatFlag("credential_source", build_api_flags.credential_source) + .Help("Build API credential source")); + flags.emplace_back(GflagsCompatFlagSeconds("wait_retry_period", + build_api_flags.wait_retry_period) + .Help("Retry period for pending builds given in " + "seconds. Set to 0 to not wait.")); + flags.emplace_back( + GflagsCompatFlag("external_dns_resolver", + build_api_flags.external_dns_resolver) + .Help("Use an out-of-process mechanism to resolve DNS queries")); + flags.emplace_back( + GflagsCompatFlag("api_base_url", build_api_flags.api_base_url) + .Help("The base url for API requests to download artifacts from")); + + CredentialFlags& credential_flags = build_api_flags.credential_flags; + flags.emplace_back( + GflagsCompatFlag("use_gce_metadata", credential_flags.use_gce_metadata) + .Help("Enforce using GCE metadata credentials.")); + flags.emplace_back( + GflagsCompatFlag("credential_filepath", + credential_flags.credential_filepath) + .Help("Enforce reading credentials from the given filepath.")); + flags.emplace_back(GflagsCompatFlag("service_account_filepath", + credential_flags.service_account_filepath) + .Help("Enforce reading service account credentials " + "from the given filepath.")); + + VectorFlags& vector_flags = fetch_flags.vector_flags; + flags.emplace_back( + GflagsCompatFlag("default_build", vector_flags.default_build) + .Help("source for the cuttlefish build to use (vendor.img + host)")); + flags.emplace_back(GflagsCompatFlag("system_build", vector_flags.system_build) + .Help("source for system.img and product.img")); + flags.emplace_back(GflagsCompatFlag("kernel_build", vector_flags.kernel_build) + .Help("source for the kernel or gki target")); + flags.emplace_back(GflagsCompatFlag("boot_build", vector_flags.boot_build) + .Help("source for the boot or gki target")); + flags.emplace_back( + GflagsCompatFlag("bootloader_build", vector_flags.bootloader_build) + .Help("source for the bootloader target")); + flags.emplace_back(GflagsCompatFlag("android_efi_loader_build", + vector_flags.android_efi_loader_build) + .Help("source for the uefi app target")); + flags.emplace_back( + GflagsCompatFlag("otatools_build", vector_flags.otatools_build) + .Help("source for the host ota tools")); + + flags.emplace_back( + GflagsCompatFlag("boot_artifact", vector_flags.boot_artifact) + .Help("name of the boot image in boot_build")); + flags.emplace_back(GflagsCompatFlag("download_img_zip", + vector_flags.download_img_zip, + kDefaultDownloadImgZip) + .Help("Whether to fetch the -img-*.zip file.")); + flags.emplace_back( + GflagsCompatFlag("download_target_files_zip", + vector_flags.download_target_files_zip, + kDefaultDownloadTargetFilesZip) + .Help("Whether to fetch the -target_files-*.zip file.")); + + std::stringstream help_message; + help_message << kUsageMessage << kDefaultBuildTarget; + flags.emplace_back(HelpFlag(flags, help_message.str())); + flags.emplace_back( + HelpXmlFlag(flags, std::cout, fetch_flags.helpxml, help_message.str())); + + flags.emplace_back(UnexpectedArgumentGuard()); + return flags; +} + +Result GetNumberOfBuilds( + const VectorFlags& flags, + const std::vector& subdirectory_flag) { + std::optional number_of_builds; + for (const auto& flag_size : + {flags.default_build.size(), flags.system_build.size(), + flags.kernel_build.size(), flags.boot_build.size(), + flags.bootloader_build.size(), flags.android_efi_loader_build.size(), + flags.otatools_build.size(), flags.boot_artifact.size(), + flags.download_img_zip.size(), flags.download_target_files_zip.size(), + subdirectory_flag.size()}) { + if (flag_size == 0) { + // a size zero flag vector means the flag was not given + continue; + } + if (number_of_builds) { + CF_EXPECT( + flag_size == *number_of_builds, + "Mismatched flag lengths: " << *number_of_builds << "," << flag_size); + } + number_of_builds = flag_size; + } + // if no flags had values there is 1 all-default build + return number_of_builds.value_or(1); +} + +} // namespace + +Result GetFlagValues(int argc, char** argv) { + FetchFlags fetch_flags; + std::string directory; + std::vector flags = GetFlagsVector(fetch_flags, directory); + std::vector args = ArgsToVec(argc - 1, argv + 1); + CF_EXPECT(ConsumeFlags(flags, args), "Could not process command line flags."); + + if (!directory.empty()) { + LOG(ERROR) << "Please use --target_directory instead of --directory"; + if (fetch_flags.target_directory.empty()) { + fetch_flags.target_directory = directory; + } + } else { + if (fetch_flags.target_directory.empty()) { + fetch_flags.target_directory = CurrentDirectory(); + } + } + fetch_flags.target_directory = AbsolutePath(fetch_flags.target_directory); + + if (!fetch_flags.vector_flags.boot_artifact.empty()) { + LOG(ERROR) << "Please use the build string filepath syntax instead of " + "deprecated --boot_artifact"; + for (const auto& build_string : fetch_flags.vector_flags.boot_build) { + if (build_string) { + CF_EXPECT(!GetFilepath(*build_string), + "Cannot use both the --boot_artifact flag and set the " + "filepath in the boot build string. Please use only the " + "build string filepath"); + } + } + } + + if (!fetch_flags.build_api_flags.credential_source.empty()) { + LOG(ERROR) << "Please use the new, specific credential flags instead of " + "the deprecated --credential_source"; + } + CredentialFlags& credential_flags = + fetch_flags.build_api_flags.credential_flags; + const int number_of_set_credential_flags = + !fetch_flags.build_api_flags.credential_source.empty() + + credential_flags.use_gce_metadata + + !credential_flags.credential_filepath.empty() + + !credential_flags.service_account_filepath.empty(); + CF_EXPECT_LE(number_of_set_credential_flags, 1, + "At most a single credential flag may be set."); + + fetch_flags.number_of_builds = CF_EXPECT(GetNumberOfBuilds( + fetch_flags.vector_flags, fetch_flags.target_subdirectory)); + return {fetch_flags}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd_parser.h b/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd_parser.h new file mode 100644 index 0000000000..552955bf6e --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd_parser.h @@ -0,0 +1,95 @@ +// +// Copyright (C) 2023 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 + +#include +#include +#include +#include + +#include + +#include "common/libs/utils/result.h" +#include "host/libs/web/android_build_api.h" +#include "host/libs/web/android_build_string.h" + +namespace cuttlefish { + +inline constexpr bool kDefaultUseGceMetadata = false; +inline constexpr char kDefaultCredentialFilepath[] = ""; +inline constexpr char kDefaultServiceAccountFilepath[] = ""; +inline constexpr char kDefaultApiKey[] = ""; +inline constexpr char kDefaultCredentialSource[] = ""; +inline constexpr std::chrono::seconds kDefaultWaitRetryPeriod = + std::chrono::seconds(20); +inline constexpr bool kDefaultExternalDnsResolver = +#ifdef __BIONIC__ + true; +#else + false; +#endif +inline constexpr char kDefaultBuildString[] = ""; +inline constexpr bool kDefaultDownloadImgZip = true; +inline constexpr bool kDefaultDownloadTargetFilesZip = false; +inline constexpr char kDefaultTargetDirectory[] = ""; +inline constexpr bool kDefaultKeepDownloadedArchives = false; + +inline constexpr char kDefaultBuildTarget[] = + "aosp_cf_x86_64_phone-trunk_staging-userdebug"; + +struct CredentialFlags { + bool use_gce_metadata = kDefaultUseGceMetadata; + std::string credential_filepath = kDefaultCredentialFilepath; + std::string service_account_filepath = kDefaultServiceAccountFilepath; +}; + +struct BuildApiFlags { + std::string api_key = kDefaultApiKey; + CredentialFlags credential_flags; + std::string credential_source = kDefaultCredentialSource; + std::chrono::seconds wait_retry_period = kDefaultWaitRetryPeriod; + bool external_dns_resolver = kDefaultExternalDnsResolver; + std::string api_base_url = kAndroidBuildServiceUrl; +}; + +struct VectorFlags { + std::vector> default_build; + std::vector> system_build; + std::vector> kernel_build; + std::vector> boot_build; + std::vector> bootloader_build; + std::vector> android_efi_loader_build; + std::vector> otatools_build; + std::vector download_img_zip; + std::vector download_target_files_zip; + std::vector boot_artifact; +}; + +struct FetchFlags { + std::string target_directory = kDefaultTargetDirectory; + std::vector target_subdirectory; + std::optional host_package_build; + bool keep_downloaded_archives = kDefaultKeepDownloadedArchives; + android::base::LogSeverity verbosity = android::base::INFO; + bool helpxml = false; + BuildApiFlags build_api_flags; + VectorFlags vector_flags; + int number_of_builds = 0; +}; + +Result GetFlagValues(int argc, char** argv); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/flag.cpp b/base/cvd/cuttlefish/host/commands/cvd/flag.cpp new file mode 100644 index 0000000000..cbd32f5ed6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/flag.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/flag.h" + +#include "host/commands/cvd/common_utils.h" + +namespace cuttlefish { + +Result CvdFlagProxy::Name() const { + CF_EXPECT(GetType() != FlagType::kUnknown, "Unsupported flag type"); + auto decode_name = Overload{ + [](auto&& param) -> std::string { return param.Name(); }, + }; + return std::visit(decode_name, flag_); +} + +CvdFlagProxy::FlagType CvdFlagProxy::GetType() const { + auto decode_type = Overload{ + [](const CvdFlag&) -> FlagType { return FlagType::kBool; }, + [](const CvdFlag&) -> FlagType { return FlagType::kInt32; }, + [](const CvdFlag&) -> FlagType { return FlagType::kString; }, + [](auto) -> FlagType { return FlagType::kUnknown; }, + }; + return std::visit(decode_type, flag_); +} + +Result CvdFlagProxy::HasDefaultValue() const { + CF_EXPECT(GetType() != FlagType::kUnknown, "Unsupported flag type of typeid"); + auto decode_default_value = Overload{ + [](auto&& flag) -> bool { return flag.HasDefaultValue(); }, + }; + return std::visit(decode_default_value, flag_); +} + +std::vector FlagCollection::Flags() const { + std::vector flags; + flags.reserve(name_flag_map_.size()); + for (const auto& [name, flag] : name_flag_map_) { + flags.push_back(flag); + } + return flags; +} + +template +static Result> FilterKnownTypeFlag( + const CvdFlag& flag, cvd_common::Args& args) { + std::optional opt = CF_EXPECT(flag.FilterFlag(args)); + if (!opt) { + return std::nullopt; + } + CvdFlagProxy::ValueVariant value_variant = *opt; + return value_variant; +} + +Result> CvdFlagProxy::FilterFlag( + cvd_common::Args& args) const { + CF_EXPECT(GetType() != FlagType::kUnknown, "Unsupported flag type of typeid"); + std::optional output; + auto filter_flag = Overload{ + [&args](const CvdFlag& int32_t_flag) + -> Result> { + return FilterKnownTypeFlag(int32_t_flag, args); + }, + [&args](const CvdFlag& bool_flag) + -> Result> { + return FilterKnownTypeFlag(bool_flag, args); + }, + [&args](const CvdFlag& string_flag) + -> Result> { + return FilterKnownTypeFlag(string_flag, args); + }, + [](auto) -> Result> { + return CF_ERR("Invalid type is passed to FlagCollection::FilterFlags"); + }, + }; + output = CF_EXPECT(std::visit(filter_flag, flag_)); + return output; +} + +Result> +FlagCollection::FilterFlags(cvd_common::Args& args) const { + std::unordered_map output; + for (const auto& [name, flag_proxy] : name_flag_map_) { + auto value_opt = CF_EXPECT(flag_proxy.FilterFlag(args)); + if (!value_opt) { + continue; + } + output.emplace(name, + FlagValuePair{.flag = flag_proxy, .value = *value_opt}); + } + return output; +} + +Result> +FlagCollection::CalculateFlags(cvd_common::Args& args) const { + auto output = CF_EXPECT(FilterFlags(args)); + for (const auto& [name, flag_proxy] : name_flag_map_) { + if (Contains(output, name)) { + // the flag was given with a value, there is no need for update it + continue; + } + if (!CF_EXPECT(flag_proxy.HasDefaultValue())) { + continue; + } + switch (flag_proxy.GetType()) { + case CvdFlagProxy::FlagType::kBool: + output.emplace( + name, + FlagValuePair{.flag = flag_proxy, + .value = CF_EXPECT(flag_proxy.DefaultValue())}); + break; + case CvdFlagProxy::FlagType::kInt32: + output.emplace( + name, FlagValuePair{.flag = flag_proxy, + .value = CF_EXPECT( + flag_proxy.DefaultValue())}); + break; + case CvdFlagProxy::FlagType::kString: + output.emplace( + name, FlagValuePair{.flag = flag_proxy, + .value = CF_EXPECT( + flag_proxy.DefaultValue())}); + break; + default: + return CF_ERR("Unsupported FlagType in " + << "--" << name); + } + } + return output; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/flag.h b/base/cvd/cuttlefish/host/commands/cvd/flag.h new file mode 100644 index 0000000000..520816eaee --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/flag.h @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2023 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +/** + * Data structure to represent cvd user-facing flags + * + * Flag in flag_parser.h is more on parsing. gflags library would be + * slowly depreicated. The cvd driver and selector flags are a specification for + * a user-facing flag. + */ +template +class CvdFlag { + public: + using GflagFactoryCallback = + std::function; + CvdFlag(const std::string& name) + : name_(name), + gflag_factory_cb([](const std::string& name, T& value_out) { + return GflagsCompatFlag(name, value_out); + }) {} + + CvdFlag(const std::string& name, const T& default_value) + : name_(name), + default_value_(default_value), + gflag_factory_cb([](const std::string& name, T& value_out) { + return GflagsCompatFlag(name, value_out); + }) {} + + std::string Name() const { return name_; } + std::string HelpMessage() const { return help_msg_; } + CvdFlag& SetHelpMessage(const std::string& help_msg) & { + help_msg_ = help_msg; + return *this; + } + CvdFlag SetHelpMessage(const std::string& help_msg) && { + help_msg_ = help_msg; + return *this; + } + bool HasDefaultValue() const { return default_value_ != std::nullopt; } + Result DefaultValue() const { + CF_EXPECT(HasDefaultValue()); + return *default_value_; + } + + CvdFlag& SetGflagFactory(GflagFactoryCallback factory) & { + gflag_factory_cb = std::move(factory); + return *this; + } + CvdFlag SetGflagFactory(GflagFactoryCallback factory) && { + gflag_factory_cb = std::move(factory); + return *this; + } + + // returns CF_ERR if parsing error, + // returns std::nullopt if parsing was okay but the flag wasn't given + Result> FilterFlag(cvd_common::Args& args) const { + const int args_initial_size = args.size(); + if (args_initial_size == 0) { + return std::nullopt; + } + T value; + CF_EXPECT(ConsumeFlags({gflag_factory_cb(name_, value)}, args), + "Failed to parse --" << name_); + if (args.size() == args_initial_size) { + // not consumed + return std::nullopt; + } + return value; + } + + // Parses the arguments. If flag is given, returns the parsed value. If not, + // returns the default value if any. If no default value, it returns CF_ERR. + Result CalculateFlag(cvd_common::Args& args) const { + auto value_opt = CF_EXPECT(FilterFlag(args)); + if (!value_opt) { + CF_EXPECT(default_value_ != std::nullopt); + value_opt = default_value_; + } + return *value_opt; + } + + private: + const std::string name_; + std::string help_msg_; + std::optional default_value_; + /** + * A callback function to generate Flag defined in + * common/libs/utils/flag_parser.h. The name is this CvdFlag's name. + * The value is a buffer that is kept in this object + */ + GflagFactoryCallback gflag_factory_cb; +}; + +class CvdFlagProxy { + friend class FlagCollection; + + public: + enum class FlagType : std::uint32_t { + kUnknown = 0, + kBool, + kInt32, + kString, + }; + + static std::string ToString(const FlagType flag_type) { + switch (flag_type) { + case FlagType::kUnknown: + return "kUnknown"; + case FlagType::kBool: + return "bool"; + case FlagType::kInt32: + return "std::int32_t"; + case FlagType::kString: + return "std::string"; + } + } + + template + CvdFlagProxy(CvdFlag&& flag) : flag_{std::move(flag)} {} + + template + const CvdFlag* GetFlag() const { + return std::get_if>(&flag_); + } + + template + CvdFlag* GetFlag() { + return std::get_if>(&flag_); + } + + /* + * If the actual type of flag_ is not handled by SelectorFlagProxy, it is a + * developer error, and the Name() and HasDefaultValue() will returns + * CF_ERR + */ + Result Name() const; + Result HasDefaultValue() const; + + FlagType GetType() const; + + template + Result DefaultValue() const { + const bool has_default_value = CF_EXPECT(HasDefaultValue()); + CF_EXPECT(has_default_value == true); + const auto* ptr = CF_EXPECT(std::get_if>(&flag_)); + CF_EXPECT(ptr != nullptr); + return ptr->DefaultValue(); + } + + // returns CF_ERR if parsing error, + // returns std::nullopt if parsing was okay but the flag wasn't given + template + Result> FilterFlag(cvd_common::Args& args) const { + std::optional output; + const auto* ptr = CF_EXPECT(std::get_if>(&flag_)); + CF_EXPECT(ptr != nullptr); + output = CF_EXPECT(ptr->FilterFlag(args)); + return output; + } + + // Parses the arguments. If flag is given, returns the parsed value. If not, + // returns the default value if any. If no default value, it returns CF_ERR. + template + Result CalculateFlag(cvd_common::Args& args) const { + bool has_default_value = CF_EXPECT(HasDefaultValue()); + CF_EXPECT(has_default_value == true); + const auto* ptr = CF_EXPECT(std::get_if>(&flag_)); + CF_EXPECT(ptr != nullptr); + T output = CF_EXPECT(ptr->CalculateFlag(args)); + return output; + } + + using ValueVariant = std::variant; + + // Returns std::nullopt when the parsing goes okay but the flag wasn't given + // Returns ValueVariant when the flag was given in args + // Returns CF_ERR when the parsing failed or the type is not supported + Result> FilterFlag(cvd_common::Args& args) const; + + private: + std::variant, CvdFlag, CvdFlag> + flag_; +}; + +class FlagCollection { + public: + using ValueVariant = CvdFlagProxy::ValueVariant; + + Result EnrollFlag(CvdFlagProxy&& flag) { + auto name = CF_EXPECT(flag.Name()); + CF_EXPECT(!Contains(name_flag_map_, name), + name << " is already registered."); + name_flag_map_.emplace(name, std::move(flag)); + return {}; + } + + template + Result EnrollFlag(CvdFlag&& flag) { + CF_EXPECT(EnrollFlag(CvdFlagProxy(std::move(flag)))); + return {}; + } + + Result GetFlag(const std::string& name) const { + const auto itr = name_flag_map_.find(name); + CF_EXPECT(itr != name_flag_map_.end(), + "Flag \"" << name << "\" is not found."); + const CvdFlagProxy& flag_proxy = itr->second; + return flag_proxy; + } + + std::vector Flags() const; + + struct FlagValuePair { + CvdFlagProxy flag; + ValueVariant value; + }; + + /* does not consider default values + * so, if not default value and the flag wasn't given, it won't be found + * in the returned map + */ + Result> FilterFlags( + cvd_common::Args& args) const; + + /* considers default values + * so, if the flag wasn't given, the default value will be used to fill + * out the returned map. If a default value isn't available and the flag + * isn't given either, the entry won't be in the returned map + */ + Result> CalculateFlags( + cvd_common::Args& args) const; + + template + static Result GetValue(const ValueVariant& value_variant) { + auto* value_ptr = std::get_if(std::addressof(value_variant)); + CF_EXPECT(value_ptr != nullptr, + "GetValue template function was instantiated with a wrong type."); + return *value_ptr; + } + + template + static Result GetValue(const FlagValuePair& flag_and_value) { + std::string flag_type_string = + CvdFlagProxy::ToString(flag_and_value.flag.GetType()); + auto* value_ptr = std::get_if(std::addressof(flag_and_value.value)); + CF_EXPECT(value_ptr != nullptr, + "The actual flag type is " << flag_type_string); + return *value_ptr; + } + + private: + std::unordered_map name_flag_map_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/frontline_parser.cpp b/base/cvd/cuttlefish/host/commands/cvd/frontline_parser.cpp new file mode 100644 index 0000000000..6c333090c5 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/frontline_parser.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/frontline_parser.h" + +#include +#include +#include + +#include +#include + +#include "common/libs/utils/flag_parser.h" +#include "host/commands/cvd/selector/selector_constants.h" + +namespace cuttlefish { + +Result> FrontlineParser::Parse( + ParserParam param) { + CF_EXPECT(!param.all_args.empty()); + FrontlineParser* frontline_parser = new FrontlineParser(param); + CF_EXPECT(frontline_parser != nullptr, + "Memory allocation for FrontlineParser failed."); + CF_EXPECT(frontline_parser->Separate()); + return std::unique_ptr(frontline_parser); +} + +FrontlineParser::FrontlineParser(const ParserParam& param) + : server_supported_subcmds_{param.server_supported_subcmds}, + all_args_(param.all_args), + internal_cmds_(param.internal_cmds), + cvd_flags_(param.cvd_flags) {} + +Result FrontlineParser::Separate() { + arguments_separator_ = CF_EXPECT(CallSeparator()); + return {}; +} + +Result FrontlineParser::ValidSubcmdsList() { + cvd_common::Args valid_subcmds(server_supported_subcmds_); + std::copy(internal_cmds_.cbegin(), internal_cmds_.cend(), + std::back_inserter(valid_subcmds)); + return valid_subcmds; +} + +static Result> BoolFlagNames( + const std::vector& flags) { + std::unordered_set output; + for (const auto& flag : flags) { + if (flag.GetType() == CvdFlagProxy::FlagType::kBool) { + output.insert(CF_EXPECT(flag.Name())); + } + } + return output; +} + +static Result> ValueFlagNames( + const std::vector& flags) { + std::unordered_set output; + for (const auto& flag : flags) { + if (flag.GetType() == CvdFlagProxy::FlagType::kInt32 || + flag.GetType() == CvdFlagProxy::FlagType::kString) { + output.insert(CF_EXPECT(flag.Name())); + } + } + return output; +} + +Result> +FrontlineParser::CallSeparator() { + auto valid_subcmds_vector = CF_EXPECT(ValidSubcmdsList()); + std::unordered_set valid_subcmds{valid_subcmds_vector.begin(), + valid_subcmds_vector.end()}; + auto cvd_flags = cvd_flags_.Flags(); + + auto known_bool_flags = CF_EXPECT(BoolFlagNames(cvd_flags)); + auto known_value_flags = CF_EXPECT(ValueFlagNames(cvd_flags)); + + ArgumentsSeparator::FlagsRegistration flag_registration{ + .known_boolean_flags = known_bool_flags, + .known_value_flags = known_value_flags, + .valid_subcommands = valid_subcmds}; + auto arguments_separator = + CF_EXPECT(ArgumentsSeparator::Parse(flag_registration, all_args_)); + CF_EXPECT(arguments_separator != nullptr); + return arguments_separator; +} + +const std::string& FrontlineParser::ProgPath() const { + return arguments_separator_->ProgPath(); +} + +std::optional FrontlineParser::SubCmd() const { + return arguments_separator_->SubCmd(); +} + +const cvd_common::Args& FrontlineParser::SubCmdArgs() const { + return arguments_separator_->SubCmdArgs(); +} + +const cvd_common::Args& FrontlineParser::CvdArgs() const { + return arguments_separator_->CvdArgs(); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/frontline_parser.h b/base/cvd/cuttlefish/host/commands/cvd/frontline_parser.h new file mode 100644 index 0000000000..5becead4b8 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/frontline_parser.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/client.h" +#include "host/commands/cvd/flag.h" +#include "host/commands/cvd/selector/arguments_separator.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +/* the very first command line parser + * + * Being aware of valid subcommands and cvd-specific commands, it will + * separate the command line arguments into: + * + * 1. program path/name + * 2. cvd-specific arguments + * a) selector flags + * b) non-selector flags + * 3. subcommand + * 4. subcommand arguments + * + * This is currently on the client side but will be moved to the server + * side. + */ +class FrontlineParser { + using ArgumentsSeparator = selector::ArgumentsSeparator; + + public: + struct ParserParam { + // commands supported by the server + std::vector server_supported_subcmds; + // commands supported by the client itself + std::vector internal_cmds; + cvd_common::Args all_args; + FlagCollection cvd_flags; + }; + + // This call must guarantee all public methods will be valid + static Result> Parse(ParserParam param); + + const std::string& ProgPath() const; + std::optional SubCmd() const; + const cvd_common::Args& SubCmdArgs() const; + const cvd_common::Args& CvdArgs() const; + + private: + FrontlineParser(const ParserParam& parser); + + // internal workers in order + Result Separate(); + Result ValidSubcmdsList(); + Result> CallSeparator(); + struct FilterOutput { + bool clean; + bool help; + cvd_common::Args selector_args; + }; + Result FilterNonSelectorArgs(); + + cvd_common::Args server_supported_subcmds_; + const cvd_common::Args all_args_; + const std::vector internal_cmds_; + FlagCollection cvd_flags_; + std::unique_ptr arguments_separator_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/handle_reset.cpp b/base/cvd/cuttlefish/host/commands/cvd/handle_reset.cpp new file mode 100644 index 0000000000..7d974fc6da --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/handle_reset.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/handle_reset.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/reset_client_utils.h" + +namespace cuttlefish { + +struct ParsedFlags { + bool is_help; + bool clean_runtime_dir; + bool device_by_cvd_only; + bool is_confirmed_by_flag; + std::optional log_level; +}; + +static Result ParseResetFlags(cvd_common::Args subcmd_args) { + if (subcmd_args.size() > 2 && subcmd_args.at(2) == "help") { + // unfortunately, {FlagAliasMode::kFlagExact, "help"} is not allowed + subcmd_args[2] = "--help"; + } + + bool is_help = false; + bool clean_runtime_dir = true; + bool device_by_cvd_only = false; + bool is_confirmed_by_flag = false; + std::string verbosity_flag_value; + + Flag y_flag = + Flag() + .Alias({FlagAliasMode::kFlagExact, "-y"}) + .Alias({FlagAliasMode::kFlagExact, "--yes"}) + .Setter([&is_confirmed_by_flag](const FlagMatch&) -> Result { + is_confirmed_by_flag = true; + return {}; + }); + Flag help_flag = Flag() + .Alias({FlagAliasMode::kFlagExact, "-h"}) + .Alias({FlagAliasMode::kFlagExact, "--help"}) + .Setter([&is_help](const FlagMatch&) -> Result { + is_help = true; + return {}; + }); + std::vector flags{ + GflagsCompatFlag("device-by-cvd-only", device_by_cvd_only), + y_flag, + GflagsCompatFlag("clean-runtime-dir", clean_runtime_dir), + help_flag, + GflagsCompatFlag("verbosity", verbosity_flag_value), + UnexpectedArgumentGuard()}; + CF_EXPECT(ConsumeFlags(flags, subcmd_args)); + + std::optional verbosity; + if (!verbosity_flag_value.empty()) { + verbosity = CF_EXPECT(EncodeVerbosity(verbosity_flag_value), + "invalid verbosity level"); + } + return ParsedFlags{.is_help = is_help, + .clean_runtime_dir = clean_runtime_dir, + .device_by_cvd_only = device_by_cvd_only, + .is_confirmed_by_flag = is_confirmed_by_flag, + .log_level = verbosity}; +} + +static bool GetUserConfirm() { + std::cout << "Are you sure to reset all the devices, runtime files, " + << "and the cvd server if any [y/n]? "; + std::string user_confirm; + std::getline(std::cin, user_confirm); + std::transform(user_confirm.begin(), user_confirm.end(), user_confirm.begin(), + ::tolower); + return (user_confirm == "y" || user_confirm == "yes"); +} + +/* + * Try client.StopCvdServer(), and wait for a while. + * + * There should be two threads or processes. One is to call + * "StopCvdServer()," which could hang forever. The other is waiting + * for the thread/process, and should kill it after timeout. + * + * In that sense, a process is easy to kill in the middle (kill -9). + * + */ +static Result TimedKillCvdServer(CvdClient& client, const int timeout) { + sem_t* binary_sem = (sem_t*)mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_SHARED, 0, 0); + CF_EXPECT(binary_sem != nullptr, + "Failed to allocated shm for inter-process semaphore." + << "(errno: " << errno << ")"); + CF_EXPECT_EQ(sem_init(binary_sem, 1, 0), 0, + "Failed to initialized inter-process semaphore" + << "(errno: " << errno << ")"); + pid_t pid = fork(); + CF_EXPECT(pid >= 0, "fork() failed in TimedKillCvdServer"); + if (pid == 0) { + LOG(ERROR) << "Stopping the cvd server..."; + constexpr bool clear_running_devices_first = true; + auto stop_server_result = client.StopCvdServer(clear_running_devices_first); + if (!stop_server_result.ok()) { + LOG(ERROR) << "cvd kill-server returned error" + << stop_server_result.error().FormatForEnv(); + LOG(ERROR) << "However, cvd reset will continue cleaning up."; + } + sem_post(binary_sem); + // exit 0. This is a short-living worker process + exit(0); + } + + Subprocess worker_process(pid); + struct timespec waiting_time; + if (clock_gettime(CLOCK_MONOTONIC, &waiting_time) == -1) { + // cannot set up an alarm clock. Not sure how long it should wait + // for the worker process. Thus, we wait for a certain amount of time, + // and send SIGKILL to the cvd server process and the worker process. + LOG(ERROR) << "Could not get the CLOCK_REALTIME."; + LOG(ERROR) << "Sleeping " << timeout << " seconds, and " + << "will send sigkill to the server."; + using namespace std::chrono_literals; + std::this_thread::sleep_for(std::chrono::seconds(timeout)); + auto result_kill = KillCvdServerProcess(); + worker_process.Stop(); + // TODO(kwstephenkim): Compose error messages, and propagate + CF_EXPECT(result_kill.ok(), "KillCvdServerProcess() failed."); + return {}; + } + + // timed wait for the binary semaphore + waiting_time.tv_sec += timeout; + auto ret_code = sem_timedwait(binary_sem, &waiting_time); + + // ret_code == 0 means sem_wait succeeded before timeout. + if (ret_code == 0) { + worker_process.Wait(); + CF_EXPECT(KillCvdServerProcess()); + return {}; + } + + // worker process is still running. + worker_process.Stop(); + CF_EXPECT(KillCvdServerProcess()); + return {}; +} + +Result HandleReset(CvdClient& client, + const cvd_common::Args& subcmd_args) { + auto options = CF_EXPECT(ParseResetFlags(subcmd_args)); + if (options.log_level) { + SetMinimumVerbosity(options.log_level.value()); + } + if (options.is_help) { + std::cout << kHelpMessage << std::endl; + return {}; + } + + // cvd reset. Give one more opportunity + if (!options.is_confirmed_by_flag && !GetUserConfirm()) { + std::cout << "For more details: " + << " cvd reset --help" << std::endl; + return {}; + } + + auto result = TimedKillCvdServer(client, 50); + if (!result.ok()) { + LOG(ERROR) << result.error().FormatForEnv(); + LOG(ERROR) << "Cvd reset continues cleaning up devices."; + } + // cvd reset handler placeholder. identical to cvd kill-server for now. + CF_EXPECT(KillAllCuttlefishInstances( + {.cvd_server_children_only = options.device_by_cvd_only, + .clear_instance_dirs = options.clean_runtime_dir})); + return {}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/handle_reset.h b/base/cvd/cuttlefish/host/commands/cvd/handle_reset.h new file mode 100644 index 0000000000..56428f1a77 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/handle_reset.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 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 + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/client.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +Result HandleReset(CvdClient& client, + const cvd_common::Args& subcmd_args); + +static constexpr char kHelpMessage[] = R"(usage: cvd reset + +* Warning: Cvd reset is an experimental implementation. When you are in panic, +cvd reset is the last resort. + +args: + --help Prints this message. + help + + --device-by-cvd-only Terminates devices that a cvd server started + This excludes the devices launched by "launch_cvd" + or "cvd_internal_start" directly (default: false) + + --clean-runtime-dir Cleans up the runtime directory for the devices + Yet to be implemented. For now, if true, only if + stop_cvd supports --clear_instance_dirs and the + device could be stopped by stop_cvd, the flag takes + effects. (default: true) + + --yes Resets without asking the user confirmation. + -y + +description: + + 1. Gracefully stops all devices that the cvd client can reach. + 2. Forcefully stops all run_cvd processes and their subprocesses. + 3. Kill the cvd server itself if unresponsive. + 4. Reset the states of the involved instance lock files + -- If cvd reset stops a device, it resets the corresponding lock file. + 5. Optionally, cleans up the runtime files of the stopped devices.)"; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/instance_lock.cpp b/base/cvd/cuttlefish/host/commands/cvd/instance_lock.cpp new file mode 100644 index 0000000000..346ca1b0cc --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/instance_lock.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/instance_lock.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +InstanceLockFile::InstanceLockFile(LockFile&& lock_file, const int instance_num) + : lock_file_(std::move(lock_file)), instance_num_(instance_num) {} + +int InstanceLockFile::Instance() const { return instance_num_; } + +Result InstanceLockFile::Status() const { + auto in_use_state = CF_EXPECT(lock_file_.Status()); + return in_use_state; +} + +Result InstanceLockFile::Status(InUseState state) { + CF_EXPECT(lock_file_.Status(state)); + return {}; +} + +bool InstanceLockFile::operator<(const InstanceLockFile& other) const { + if (instance_num_ != other.instance_num_) { + return instance_num_ < other.instance_num_; + } + return lock_file_ < other.lock_file_; +} + +InstanceLockFileManager::InstanceLockFileManager() {} + +Result InstanceLockFileManager::LockFilePath(int instance_num) { + std::stringstream path; + path << TempDir() << "/acloud_cvd_temp/"; + CF_EXPECT(EnsureDirectoryExists(path.str())); + path << "local-instance-" << instance_num << ".lock"; + return path.str(); +} + +Result InstanceLockFileManager::AcquireLock( + int instance_num) { + const auto lock_file_path = CF_EXPECT(LockFilePath(instance_num)); + LockFile lock_file = + CF_EXPECT(lock_file_manager_.AcquireLock(lock_file_path)); + return InstanceLockFile(std::move(lock_file), instance_num); +} + +Result> InstanceLockFileManager::AcquireLocks( + const std::set& instance_nums) { + std::set locks; + for (const auto& num : instance_nums) { + locks.emplace(CF_EXPECT(AcquireLock(num))); + } + return locks; +} + +Result> InstanceLockFileManager::TryAcquireLock( + int instance_num) { + const auto lock_file_path = CF_EXPECT(LockFilePath(instance_num)); + std::optional lock_file_opt = + CF_EXPECT(lock_file_manager_.TryAcquireLock(lock_file_path)); + if (!lock_file_opt) { + return std::nullopt; + } + return InstanceLockFile(std::move(*lock_file_opt), instance_num); +} + +Result> InstanceLockFileManager::TryAcquireLocks( + const std::set& instance_nums) { + std::set locks; + for (const auto& num : instance_nums) { + auto lock = CF_EXPECT(TryAcquireLock(num)); + if (lock) { + locks.emplace(std::move(*lock)); + } + } + return locks; +} + +Result> +InstanceLockFileManager::LockAllAvailable() { + if (!all_instance_nums_) { + all_instance_nums_ = CF_EXPECT(FindPotentialInstanceNumsFromNetDevices()); + } + + std::vector acquired_lock_files; + for (const auto num : *all_instance_nums_) { + auto lock_result = TryAcquireLock(num); + if (!lock_result.ok()) { + LOG(DEBUG) << "Unable to open lock file for ID #" << num << " but " + << "moving on to the next one as it's not a critical failure."; + continue; + } + auto lock = std::move(*lock_result); + if (!lock) { + continue; + } + auto status = CF_EXPECT(lock->Status()); + if (status != InUseState::kNotInUse) { + continue; + } + acquired_lock_files.emplace_back(std::move(*lock)); + } + return acquired_lock_files; +} + +static std::string DevicePatternString( + const std::unordered_map>& device_to_ids_map) { + std::string device_pattern_str("^[[:space:]]*cvd-("); + for (const auto& [key, _] : device_to_ids_map) { + device_pattern_str.append(key).append("|"); + } + if (!device_to_ids_map.empty()) { + *device_pattern_str.rbegin() = ')'; + } + device_pattern_str.append("-[0-9]+"); + return device_pattern_str; +} + +struct TypeAndId { + std::string device_type; + int id; +}; +// call this if the line is a network device line +static Result ParseMatchedLine( + const std::smatch& device_string_match) { + std::string device_string = *device_string_match.begin(); + auto tokens = android::base::Tokenize(device_string, "-"); + CF_EXPECT_GE(tokens.size(), 3); + const auto cvd = tokens.front(); + int id = 0; + CF_EXPECT(android::base::ParseInt(tokens.back(), &id)); + // '-'.join(tokens[1:-1]) + tokens.pop_back(); + tokens.erase(tokens.begin()); + const auto device_type = android::base::Join(tokens, "-"); + return TypeAndId{.device_type = device_type, .id = id}; +} + +Result> +InstanceLockFileManager::FindPotentialInstanceNumsFromNetDevices() { + // Estimate this by looking at available tap devices + // clang-format off + /** Sample format: +Inter-| Receive | Transmit + face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed +cvd-wtap-02: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + */ + // clang-format on + static constexpr char kPath[] = "/proc/net/dev"; + std::string proc_net_dev; + using android::base::ReadFileToString; + CF_EXPECT(ReadFileToString(kPath, &proc_net_dev, /* follow_symlinks */ true)); + + auto lines = android::base::Split(proc_net_dev, "\n"); + std::unordered_map> device_to_ids_map{ + {"etap", std::set{}}, + {"mtap", std::set{}}, + {"wtap", std::set{}}, + {"wifiap", std::set{}}, + }; + // "^[[:space:]]*cvd-(etap|mtap|wtap|wifiap)-[0-9]+" + std::string device_pattern_str = DevicePatternString(device_to_ids_map); + + std::regex device_pattern(device_pattern_str); + for (const auto& line : lines) { + std::smatch device_string_match; + if (!std::regex_search(line, device_string_match, device_pattern)) { + continue; + } + const auto [device_type, id] = + CF_EXPECT(ParseMatchedLine(device_string_match)); + CF_EXPECT(Contains(device_to_ids_map, device_type)); + device_to_ids_map[device_type].insert(id); + } + + std::set result{device_to_ids_map["etap"]}; // any set except "wifiap" + for (const auto& [device_type, id_set] : device_to_ids_map) { + /* + * b/2457509 + * + * Until the debian host packages are sufficiently up-to-date, the wifiap + * devices wouldn't show up in /proc/net/dev. + */ + if (device_type == "wifiap" && id_set.empty()) { + continue; + } + std::set tmp; + std::set_intersection(result.begin(), result.end(), id_set.begin(), + id_set.end(), std::inserter(tmp, tmp.begin())); + result = std::move(tmp); + } + return result; +} + +Result> +InstanceLockFileManager::TryAcquireUnusedLock() { + if (!all_instance_nums_) { + all_instance_nums_ = CF_EXPECT(FindPotentialInstanceNumsFromNetDevices()); + } + + for (const auto num : *all_instance_nums_) { + auto lock = CF_EXPECT(TryAcquireLock(num)); + if (lock && CF_EXPECT(lock->Status()) == InUseState::kNotInUse) { + return std::move(*lock); + } + } + return {}; +} + +Result InstanceLockFileManager::RemoveLockFile(int instance_num) { + const auto lock_file_path = CF_EXPECT(LockFilePath(instance_num)); + CF_EXPECT(RemoveFile(lock_file_path), std::strerror(errno)); + return {}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/instance_lock.h b/base/cvd/cuttlefish/host/commands/cvd/instance_lock.h new file mode 100644 index 0000000000..6c1581853d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/instance_lock.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 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 + +#include +#include + +#include "host/commands/cvd/lock_file.h" + +namespace cuttlefish { + +// This class is not thread safe. +class InstanceLockFile { + friend class InstanceLockFileManager; + using LockFile = cvd_impl::LockFile; + + public: + int Instance() const; + Result Status() const; + Result Status(InUseState); + + bool operator<(const InstanceLockFile&) const; + + private: + InstanceLockFile(LockFile&& lock_file, const int instance_num); + LockFile lock_file_; + const int instance_num_; +}; + +class InstanceLockFileManager { + using LockFile = cvd_impl::LockFile; + using LockFileManager = cvd_impl::LockFileManager; + + public: + InstanceLockFileManager(); + + Result AcquireLock(int instance_num); + Result> AcquireLocks(const std::set& nums); + + Result> TryAcquireLock(int instance_num); + Result> TryAcquireLocks(const std::set& nums); + + // Best-effort attempt to find a free instance id. + Result> TryAcquireUnusedLock(); + + Result> LockAllAvailable(); + + // TODO: This routine should be removed and replaced with allocd + // The caller must check if the instance_num belongs to the user, before + // calling this. It is a quick fix for b/316824572 + Result RemoveLockFile(int instance_num); + + private: + /* + * Generate value to initialize + */ + Result> FindPotentialInstanceNumsFromNetDevices(); + static Result LockFilePath(int instance_num); + std::optional> all_instance_nums_; + LockFileManager lock_file_manager_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/instance_manager.cpp b/base/cvd/cuttlefish/host/commands/cvd/instance_manager.cpp new file mode 100644 index 0000000000..8e50722579 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/instance_manager.cpp @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/instance_manager.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/subprocess.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/selector/instance_database_types.h" +#include "host/commands/cvd/selector/instance_database_utils.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_constants.h" +#include "host/libs/config/config_constants.h" +#include "host/libs/config/config_utils.h" +#include "host/libs/config/known_paths.h" + +namespace cuttlefish { +namespace { + +// Returns true only if command terminated normally, and returns 0 +Result RunCommand(Command&& command) { + auto subprocess = command.Start(); + siginfo_t infop{}; + // This blocks until the process exits, but doesn't reap it. + auto result = subprocess.Wait(&infop, WEXITED); + CF_EXPECT(result != -1, "Lost track of subprocess pid"); + CF_EXPECT(infop.si_code == CLD_EXITED && infop.si_status == 0); + return {}; +} + +} // namespace + +Result InstanceManager::GetCuttlefishConfigPath( + const std::string& home) { + return selector::GetCuttlefishConfigPath(home); +} + +InstanceManager::InstanceManager( + InstanceLockFileManager& lock_manager, + HostToolTargetManager& host_tool_target_manager) + : lock_manager_(lock_manager), + host_tool_target_manager_(host_tool_target_manager) {} + +Result InstanceManager::Serialize() { + std::lock_guard lock(instance_db_mutex_); + return instance_db_.Serialize(); +} + +Result InstanceManager::LoadFromJson(const Json::Value& db_json) { + std::lock_guard lock(instance_db_mutex_); + CF_EXPECT(instance_db_.LoadFromJson(db_json)); + return {}; +} + +Result InstanceManager::Analyze( + const std::string& sub_cmd, const CreationAnalyzerParam& param, + const ucred& credential) { + std::lock_guard lock(instance_db_mutex_); + auto group_creation_info = CF_EXPECT(CreationAnalyzer::Analyze( + sub_cmd, param, credential, instance_db_, lock_manager_)); + return {group_creation_info}; +} + +Result InstanceManager::SelectGroup( + const cvd_common::Args& selector_args, const cvd_common::Envs& envs) { + return SelectGroup(selector_args, {}, envs); +} + +Result InstanceManager::SelectGroup( + const cvd_common::Args& selector_args, const Queries& extra_queries, + const cvd_common::Envs& envs) { + std::unique_lock lock(instance_db_mutex_); + auto group_selector = + CF_EXPECT(GroupSelector::GetSelector(selector_args, extra_queries, envs)); + auto group = CF_EXPECT(group_selector.FindGroup(instance_db_)); + return group; +} + +Result InstanceManager::SelectInstance( + const cvd_common::Args& selector_args, const cvd_common::Envs& envs) { + return SelectInstance(selector_args, {}, envs); +} + +Result InstanceManager::SelectInstance( + const cvd_common::Args& selector_args, const Queries& extra_queries, + const cvd_common::Envs& envs) { + std::unique_lock lock(instance_db_mutex_); + auto instance_selector = CF_EXPECT( + InstanceSelector::GetSelector(selector_args, extra_queries, envs)); + auto instance_copy = CF_EXPECT(instance_selector.FindInstance(instance_db_)); + return instance_copy; +} + +bool InstanceManager::HasInstanceGroups() { + std::lock_guard lock(instance_db_mutex_); + return !instance_db_.IsEmpty(); +} + +Result InstanceManager::SetInstanceGroup( + const selector::GroupCreationInfo& group_info) { + std::lock_guard assemblies_lock(instance_db_mutex_); + + const auto group_name = group_info.group_name; + const auto home_dir = group_info.home; + const auto host_artifacts_path = group_info.host_artifacts_path; + const auto product_out_path = group_info.product_out_path; + const auto& per_instance_info = group_info.instances; + auto new_group = CF_EXPECT(instance_db_.AddInstanceGroup( + {.group_name = group_name, + .home_dir = home_dir, + .host_artifacts_path = host_artifacts_path, + .product_out_path = product_out_path, + .start_time = selector::CvdServerClock::now()})); + + using InstanceInfo = selector::InstanceDatabase::InstanceInfo; + std::vector instances_info; + for (const auto& instance : per_instance_info) { + InstanceInfo info{.id = instance.instance_id_, + .name = instance.per_instance_name_}; + instances_info.push_back(info); + } + android::base::ScopeGuard action_on_failure([this, &new_group]() { + /* + * The way InstanceManager uses the database is that it adds an empty + * group, gets an handle, and add instances to it. Thus, failing to adding + * an instance to the group does not always mean that the instance group + * addition fails. It is up to the caller. In this case, however, failing + * to add an instance to a new group means failing to create an instance + * group itself. Thus, we should remove the new instance group from the + * database. + * + */ + instance_db_.RemoveInstanceGroup(new_group.Get()); + }); + CF_EXPECTF(instance_db_.AddInstances(group_name, instances_info), + "Failed to add instances to the group \"{}\" so the group " + "is not added", + group_name); + action_on_failure.Disable(); + return {}; +} + +void InstanceManager::RemoveInstanceGroup(const std::string& dir) { + std::lock_guard assemblies_lock(instance_db_mutex_); + auto result = instance_db_.FindGroup({selector::kHomeField, dir}); + if (!result.ok()) return; + auto group = *result; + instance_db_.RemoveInstanceGroup(group); +} + +Result InstanceManager::StopBin( + const std::string& host_android_out) { + const auto stop_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({ + .artifacts_path = host_android_out, + .op = "stop", + })); + return stop_bin; +} + +Result InstanceManager::IssueStopCommand( + const SharedFD& out, const SharedFD& err, + const std::string& config_file_path, + const selector::LocalInstanceGroup& group) { + const auto stop_bin = CF_EXPECT(StopBin(group.HostArtifactsPath())); + Command command(group.HostArtifactsPath() + "/bin/" + stop_bin); + command.AddParameter("--clear_instance_dirs"); + command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, out); + command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, err); + command.AddEnvironmentVariable(kCuttlefishConfigEnvVarName, config_file_path); + auto wait_result = RunCommand(std::move(command)); + /** + * --clear_instance_dirs may not be available for old branches. This causes + * the stop_cvd to terminates with a non-zero exit code due to the parsing + * error. Then, we will try to re-run it without the flag. + */ + if (!wait_result.ok()) { + std::stringstream error_msg; + error_msg << stop_bin << " was executed internally, and failed. It might " + << "be failing to parse the new --clear_instance_dirs. Will try " + << "without the flag.\n"; + WriteAll(err, error_msg.str()); + Command no_clear_instance_dir_command(group.HostArtifactsPath() + "/bin/" + + stop_bin); + no_clear_instance_dir_command.RedirectStdIO( + Subprocess::StdIOChannel::kStdOut, out); + no_clear_instance_dir_command.RedirectStdIO( + Subprocess::StdIOChannel::kStdErr, err); + no_clear_instance_dir_command.AddEnvironmentVariable( + kCuttlefishConfigEnvVarName, config_file_path); + wait_result = RunCommand(std::move(no_clear_instance_dir_command)); + } + + if (!wait_result.ok()) { + WriteAll(err, + "Warning: error stopping instances for dir \"" + group.HomeDir() + + "\".\nThis can happen if instances are already stopped.\n"); + } + for (const auto& instance : group.Instances()) { + auto lock = lock_manager_.TryAcquireLock(instance->InstanceId()); + if (lock.ok() && (*lock)) { + (*lock)->Status(InUseState::kNotInUse); + continue; + } + WriteAll(err, "InstanceLockFileManager failed to acquire lock"); + } + return {}; +} + +cvd::Status InstanceManager::CvdClear(const SharedFD& out, + const SharedFD& err) { + std::lock_guard lock(instance_db_mutex_); + cvd::Status status; + const std::string config_json_name = cpp_basename(GetGlobalConfigFileLink()); + auto&& instance_groups = instance_db_.InstanceGroups(); + for (const auto& group : instance_groups) { + auto config_path = group->GetCuttlefishConfigPath(); + if (config_path.ok()) { + auto stop_result = IssueStopCommand(out, err, *config_path, *group); + if (!stop_result.ok()) { + LOG(ERROR) << stop_result.error().FormatForEnv(); + } + } + RemoveFile(group->HomeDir() + "/cuttlefish_runtime"); + RemoveFile(group->HomeDir() + config_json_name); + } + instance_db_.Clear(); + // TODO(kwstephenkim): we need a better mechanism to make sure that + // we clear all run_cvd processes. + WriteAll(err, "Stopped all known instances\n"); + status.set_code(cvd::Status::OK); + return status; +} + +Result> InstanceManager::TryAcquireLock( + int instance_num) { + std::lock_guard lock(instance_db_mutex_); + return CF_EXPECT(lock_manager_.TryAcquireLock(instance_num)); +} + +Result> +InstanceManager::FindGroups(const Query& query) const { + return CF_EXPECT(FindGroups(Queries{query})); +} + +Result> +InstanceManager::FindGroups(const Queries& queries) const { + std::lock_guard lock(instance_db_mutex_); + auto groups = CF_EXPECT(instance_db_.FindGroups(queries)); + // create a copy as we are escaping the critical section + std::vector output; + for (const auto& group_ref : groups) { + output.push_back(group_ref.Get()); + } + return output; +} + +Result> +InstanceManager::FindInstances(const Query& query) const { + return CF_EXPECT(FindInstances(Queries{query})); +} + +Result> +InstanceManager::FindInstances(const Queries& queries) const { + std::lock_guard lock(instance_db_mutex_); + auto instances = CF_EXPECT(instance_db_.FindInstances(queries)); + // create a copy as we are escaping the critical section + std::vector output; + for (const auto& instance : instances) { + output.push_back(instance.Get().GetCopy()); + } + return output; +} + +Result InstanceManager::FindGroup( + const Query& query) const { + return CF_EXPECT(FindGroup(Queries{query})); +} + +Result InstanceManager::FindGroup( + const Queries& queries) const { + std::lock_guard lock(instance_db_mutex_); + auto output = CF_EXPECT(instance_db_.FindGroups(queries)); + CF_EXPECT_EQ(output.size(), 1); + return *(output.begin()); +} + +std::vector InstanceManager::AllGroupNames() const { + std::lock_guard lock(instance_db_mutex_); + auto& local_instance_groups = instance_db_.InstanceGroups(); + std::vector group_names; + group_names.reserve(local_instance_groups.size()); + for (const auto& group : local_instance_groups) { + group_names.push_back(group->GroupName()); + } + return group_names; +} + +Result +InstanceManager::GroupSummaryMenu() const { + std::lock_guard lock(instance_db_mutex_); + + UserGroupSelectionSummary summary; + + // List of Cuttlefish Instance Groups: + // [i] : group_name (created: TIME) + // instance0.device_name() (id: instance_id) + // instance1.device_name() (id: instance_id) + std::stringstream ss; + ss << "List of Cuttlefish Instance Groups:" << std::endl; + int group_idx = 0; + for (const auto& group : instance_db_.InstanceGroups()) { + fmt::print(ss, " [{}] : {} (created: {})\n", group_idx, group->GroupName(), + selector::Format(group->StartTime())); + summary.idx_to_group_name[group_idx] = group->GroupName(); + char instance_idx = 'a'; + for (const auto& instance : CF_EXPECT(group->FindAllInstances())) { + fmt::print(ss, " <{}> {} (id : {})\n", instance_idx++, + instance.Get().DeviceName(), instance.Get().InstanceId()); + } + group_idx++; + } + summary.menu = ss.str(); + return summary; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/instance_manager.h b/base/cvd/cuttlefish/host/commands/cvd/instance_manager.h new file mode 100644 index 0000000000..d95f7fe670 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/instance_manager.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022 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 + +#include + +#include +#include +#include +#include +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/instance_lock.h" +#include "host/commands/cvd/selector/creation_analyzer.h" +#include "host/commands/cvd/selector/group_selector.h" +#include "host/commands/cvd/selector/instance_database.h" +#include "host/commands/cvd/selector/instance_database_types.h" +#include "host/commands/cvd/selector/instance_selector.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" + +namespace cuttlefish { + +class InstanceManager { + public: + using CreationAnalyzer = selector::CreationAnalyzer; + using CreationAnalyzerParam = CreationAnalyzer::CreationAnalyzerParam; + using GroupCreationInfo = selector::GroupCreationInfo; + using LocalInstanceGroup = selector::LocalInstanceGroup; + using LocalInstance = selector::LocalInstance; + using GroupSelector = selector::GroupSelector; + using InstanceSelector = selector::InstanceSelector; + using Queries = selector::Queries; + using Query = selector::Query; + template + using Set = selector::Set; + + InstanceManager(InstanceLockFileManager&, HostToolTargetManager&); + + // For cvd start + Result Analyze(const std::string& sub_cmd, + const CreationAnalyzerParam& param, + const ucred& credential); + + Result SelectGroup(const cvd_common::Args& selector_args, + const cvd_common::Envs& envsd); + + Result SelectGroup(const cvd_common::Args& selector_args, + const Queries& extra_queries, + const cvd_common::Envs& envs); + + Result SelectInstance( + const cvd_common::Args& selector_args, const Queries& extra_queries, + const cvd_common::Envs& envs); + + Result SelectInstance( + const cvd_common::Args& selector_args, const cvd_common::Envs& envs); + + bool HasInstanceGroups(); + Result SetInstanceGroup(const selector::GroupCreationInfo& group_info); + void RemoveInstanceGroup(const std::string&); + + cvd::Status CvdClear(const SharedFD& out, const SharedFD& err); + static Result GetCuttlefishConfigPath(const std::string& home); + + Result> TryAcquireLock(int instance_num); + + Result> FindGroups(const Query& query) const; + Result> FindGroups( + const Queries& queries) const; + Result> FindInstances( + const Query& query) const; + Result> FindInstances( + const Queries& queries) const; + + Result FindGroup(const Query& query) const; + Result FindGroup(const Queries& queries) const; + Result Serialize(); + Result LoadFromJson(const Json::Value&); + std::vector AllGroupNames() const; + + struct UserGroupSelectionSummary { + // Index to group name. This is the index printed in the menu + // This field offers mapping between the number/index the user + // selects and the group that is to be chosen + std::unordered_map idx_to_group_name; + std::string menu; + }; + Result GroupSummaryMenu() const; + + private: + Result IssueStopCommand(const SharedFD& out, const SharedFD& err, + const std::string& config_file_path, + const selector::LocalInstanceGroup& group); + Result StopBin(const std::string& host_android_out); + + InstanceLockFileManager& lock_manager_; + HostToolTargetManager& host_tool_target_manager_; + mutable std::mutex instance_db_mutex_; + selector::InstanceDatabase instance_db_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/interruptible_terminal.cpp b/base/cvd/cuttlefish/host/commands/cvd/interruptible_terminal.cpp new file mode 100644 index 0000000000..8795eb7642 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/interruptible_terminal.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/interruptible_terminal.h" + +#include + +#include + +#include "common/libs/fs/shared_select.h" + +namespace cuttlefish { + +InterruptibleTerminal::InterruptibleTerminal(SharedFD stdin_fd) + : stdin_fd_(std::move(stdin_fd)), interrupt_event_fd_(SharedFD::Event()) {} + +Result InterruptibleTerminal::Interrupt() { + std::unique_lock lock(terminal_mutex_); + interrupted_ = true; + if (owner_tid_) { + CF_EXPECT_EQ(interrupt_event_fd_->EventfdWrite(1), 0, + interrupt_event_fd_->StrError()); + } + readline_done_.wait(lock, [this]() { return owner_tid_ == std::nullopt; }); + { SharedFD discard = std::move(stdin_fd_); } + return {}; +} + +// only up to one thread can call this function +Result InterruptibleTerminal::ReadLine() { + { + std::lock_guard lock(terminal_mutex_); + CF_EXPECT(interrupted_ == false, "Interrupted"); + CF_EXPECT(owner_tid_ == std::nullopt, + "This InterruptibleTerminal is already owned by " + << owner_tid_.value()); + CF_EXPECT(stdin_fd_->IsOpen(), + "The copy of client stdin fd has been already closed."); + owner_tid_ = std::this_thread::get_id(); + } + SharedFDSet read_set; + std::string line_buf; + while (true) { + read_set.Set(interrupt_event_fd_); + read_set.Set(stdin_fd_); + int num_fds = Select(&read_set, nullptr, nullptr, nullptr); + + std::lock_guard lock(terminal_mutex_); + auto return_action = android::base::ScopeGuard([this]() { + owner_tid_ = std::nullopt; + readline_done_.notify_one(); + }); + CF_EXPECT(interrupted_ == false, "Interrupted"); + CF_EXPECTF(num_fds >= 0, + "Select call to read the user input returned error: {}", + strerror(errno)); + + if (read_set.IsSet(interrupt_event_fd_)) { + eventfd_t val; + CF_EXPECT_EQ(interrupt_event_fd_->EventfdRead(&val), 0); + return CF_ERR("Terminal input interrupted."); + } + CF_EXPECT(read_set.IsSet(stdin_fd_)); + char c = 0; + const char end_of_transmission = 4; + auto n_read = stdin_fd_->Read(&c, sizeof(c)); + CF_EXPECT(n_read >= 0, + "Read from stdin returned an error: " << stdin_fd_->StrError()); + if (n_read > 0) { + CF_EXPECTF(n_read == 1, "Expected to read 1 byte but read: {} bytes", + n_read); + if (c == '\n' || c == 0 || c == end_of_transmission) { + return line_buf; + } + line_buf.append(1, c); + continue; + } + return line_buf; + } +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/interruptible_terminal.h b/base/cvd/cuttlefish/host/commands/cvd/interruptible_terminal.h new file mode 100644 index 0000000000..a2b1167f56 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/interruptible_terminal.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 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 + +#include +#include +#include +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +class InterruptibleTerminal { + public: + InterruptibleTerminal(SharedFD stdin_fd); + /** + * Interrupts ReadLine() and reset the stdin_fd_ + * + * It is guaranteed to return after the on-going ReadLine() returns + * either normally or abnormally. It is guaranteed to block the new + * ReadLine() call. + */ + Result Interrupt(); + + /* + * Returns a line from the stdin_fd, which is the client stdin + * + * Notes: + * 1. Only up to one thread can call this function, so each handler + * should have its own copy + * 2. Each handler release the interrupt_lock before calling + * ReadLine(), get the interrupt lock again afterwards, and + * check the interrupted_ flag + * + */ + Result ReadLine(); + + private: + SharedFD stdin_fd_; + SharedFD interrupt_event_fd_; + bool interrupted_ = false; + // one owner per InterruptibleTerminal + // also protecting interrupted_ + std::mutex terminal_mutex_; + std::optional owner_tid_; + std::condition_variable readline_done_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/lock_file.cpp b/base/cvd/cuttlefish/host/commands/cvd/lock_file.cpp new file mode 100644 index 0000000000..7b6c9c9ebe --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/lock_file.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/lock_file.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { +namespace cvd_impl { + +static Result SetStatus(SharedFD& fd, const InUseState state) { + CF_EXPECT(fd->LSeek(0, SEEK_SET) == 0, fd->StrError()); + char state_char = static_cast(state); + CF_EXPECT(fd->Write(&state_char, 1) == 1, fd->StrError()); + return {}; +} + +LockFile::LockFileReleaser::LockFileReleaser(const SharedFD& fd, + const std::string& lock_file_path) + : flocked_file_fd_(fd), lock_file_path_(lock_file_path) {} + +LockFile::LockFileReleaser::~LockFileReleaser() { + if (!flocked_file_fd_->IsOpen()) { + LOG(ERROR) << "SharedFD to " << lock_file_path_ + << " is closed and unable to un-flock()"; + return; + } + auto funlock_result = flocked_file_fd_->Flock(LOCK_UN | LOCK_NB); + if (!funlock_result.ok()) { + LOG(ERROR) << "Unlock the \"" << lock_file_path_ + << "\" failed: " << funlock_result.error().Trace(); + } +} + +LockFile::LockFile(SharedFD fd, const std::string& lock_file_path) + : fd_(std::move(fd)), + lock_file_path_(lock_file_path), + lock_file_lock_releaser_{ + std::make_shared(fd_, lock_file_path_)} {} + +Result LockFile::Status() const { + CF_EXPECT(fd_->LSeek(0, SEEK_SET) == 0, fd_->StrError()); + char state_char = static_cast(InUseState::kNotInUse); + CF_EXPECT(fd_->Read(&state_char, 1) >= 0, fd_->StrError()); + switch (state_char) { + case static_cast(InUseState::kInUse): + return InUseState::kInUse; + case static_cast(InUseState::kNotInUse): + return InUseState::kNotInUse; + default: + return CF_ERRF("Unexpected state value \"{}\"", state_char); + } +} + +Result LockFile::Status(InUseState state) { + CF_EXPECT(SetStatus(fd_, state)); + return {}; +} + +bool LockFile::operator<(const LockFile& other) const { + if (this == std::addressof(other)) { + return false; + } + if (LockFilePath() == other.LockFilePath()) { + return fd_ < other.fd_; + } + // operator< for std::string will be gone as of C++20 + return (strncmp(lock_file_path_.data(), other.LockFilePath().data(), + std::max(lock_file_path_.size(), + other.LockFilePath().size())) < 0); +} + +Result LockFileManager::OpenLockFile(const std::string& file_path) { + auto parent_dir = android::base::Dirname(file_path); + CF_EXPECT(EnsureDirectoryExists(parent_dir)); + auto fd = SharedFD::Open(file_path.data(), O_CREAT | O_RDWR, 0666); + int result = chmod(file_path.c_str(), 0666); + if (result) { + LOG(DEBUG) << "failed: chmod 666 " << file_path; + } + result = chmod(parent_dir.c_str(), 0755); + if (result) { + LOG(DEBUG) << "failed: chmod 755 " << parent_dir; + } + CF_EXPECTF(fd->IsOpen(), "open(\"{}\"): {}", file_path, fd->StrError()); + return fd; +} + +Result LockFileManager::AcquireLock( + const std::string& lock_file_path) { + auto fd = CF_EXPECT(OpenLockFile(lock_file_path)); + CF_EXPECT(fd->Flock(LOCK_EX)); + return LockFile(fd, lock_file_path); +} + +Result> LockFileManager::AcquireLocks( + const std::set& lock_file_paths) { + std::set locks; + for (const auto& lock_file_path : lock_file_paths) { + locks.emplace(CF_EXPECT(AcquireLock(lock_file_path))); + } + return locks; +} + +Result> LockFileManager::TryAcquireLock( + const std::string& lock_file_path) { + auto fd = CF_EXPECT(OpenLockFile(lock_file_path)); + auto flock_result = fd->Flock(LOCK_EX | LOCK_NB); + if (flock_result.ok()) { + return std::optional(LockFile(fd, lock_file_path)); + // TODO(schuffelen): Include the error code in the Result + } else if (!flock_result.ok() && fd->GetErrno() == EWOULDBLOCK) { + return {}; + } + CF_EXPECT(std::move(flock_result)); + return {}; +} + +Result> LockFileManager::TryAcquireLocks( + const std::set& lock_file_paths) { + std::set locks; + for (const auto& lock_file_path : lock_file_paths) { + auto lock = CF_EXPECT(TryAcquireLock(lock_file_path)); + if (lock) { + locks.emplace(std::move(*lock)); + } + } + return locks; +} + +} // namespace cvd_impl + +// Replicates tempfile.gettempdir() in Python +std::string TempDir() { + std::vector try_dirs = { + StringFromEnv("TMPDIR", ""), + StringFromEnv("TEMP", ""), + StringFromEnv("TMP", ""), + "/tmp", + "/var/tmp", + "/usr/tmp", + }; + for (const auto& try_dir : try_dirs) { + if (DirectoryExists(try_dir)) { + return try_dir; + } + } + return CurrentDirectory(); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/lock_file.h b/base/cvd/cuttlefish/host/commands/cvd/lock_file.h new file mode 100644 index 0000000000..a723c7dc07 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/lock_file.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2023 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 + +#include +#include +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +enum class InUseState : char { + kInUse = 'I', + kNotInUse = 'N', +}; + +// Replicates tempfile.gettempdir() in Python +std::string TempDir(); + +namespace cvd_impl { + +// This class is not thread safe. +class LockFile { + friend class LockFileManager; + + /* b/303724170 + * + * Unfortunately, the LockFile implementation forgot to release + * the Flock() it got on destruction. Also, unfortunately, the + * LockFile is being copied as we have used std::optional + * here and there, which means LockFile objects are copied. + * + * The goal is to disable LockFile(const LockFile&) and the + * copy assignment operator. For now, however, we add to LockFile + * a shared_ptr of this class, so that at the very last LockFile + * object for a given lock is destroyed, the LockFileReleaser will + * releases the lock. + * + * Must be created only in the LockFile constructors + */ + class LockFileReleaser { + public: + LockFileReleaser(const SharedFD& fd, const std::string& lock_file_path); + ~LockFileReleaser(); + + private: + SharedFD flocked_file_fd_; + const std::string lock_file_path_; // for error message + }; + + public: + const auto& LockFilePath() const { return lock_file_path_; } + Result Status() const; + Result Status(InUseState); + + // to put this into a set + bool operator<(const LockFile& other) const; + + private: + LockFile(SharedFD fd, const std::string& lock_file_path); + + SharedFD fd_; + const std::string lock_file_path_; + std::shared_ptr lock_file_lock_releaser_; +}; + +class LockFileManager { + public: + LockFileManager() = default; + + Result AcquireLock(const std::string& lock_file_path); + Result> AcquireLocks( + const std::set& lock_file_paths); + + Result> TryAcquireLock( + const std::string& lock_file_path); + Result> TryAcquireLocks( + const std::set& lock_file_paths); + + // Best-effort attempt to find a free instance id. + Result> TryAcquireUnusedLock(); + + static Result OpenLockFile(const std::string& file_path); +}; + +} // namespace cvd_impl +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/logger.cpp b/base/cvd/cuttlefish/host/commands/cvd/logger.cpp new file mode 100644 index 0000000000..ceea878922 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/logger.cpp @@ -0,0 +1,129 @@ +// +// Copyright (C) 2022 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. + +#include "host/commands/cvd/logger.h" + +#include +#include +#include +#include + +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/tee_logging.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/server_client.h" + +namespace cuttlefish { + +ServerLogger::ServerLogger() { + auto log_callback = [this](android::base::LogId log_buffer_id, + android::base::LogSeverity severity, + const char* tag, const char* file, + unsigned int line, const char* message) { + auto thread_id = std::this_thread::get_id(); + std::shared_lock lock(thread_loggers_lock_); + auto logger_it = thread_loggers_.find(thread_id); + if (logger_it == thread_loggers_.end()) { + return; + } + logger_it->second->LogMessage(log_buffer_id, severity, tag, file, line, + message); + }; + android::base::SetLogger(log_callback); +} + +ServerLogger::~ServerLogger() { + android::base::SetLogger(android::base::StderrLogger); +} + +ServerLogger::ScopedLogger ServerLogger::LogThreadToFd( + SharedFD target, const android::base::LogSeverity verbosity) { + return ScopedLogger(*this, std::move(target), verbosity); +} + +ServerLogger::ScopedLogger ServerLogger::LogThreadToFd(SharedFD target) { + return ScopedLogger(*this, std::move(target), kCvdDefaultVerbosity); +} + +ServerLogger::ScopedLogger::ScopedLogger( + ServerLogger& server_logger, SharedFD target, + const android::base::LogSeverity verbosity) + : server_logger_(server_logger), + target_(std::move(target)), + verbosity_(verbosity) { + auto thread_id = std::this_thread::get_id(); + std::unique_lock lock(server_logger_.thread_loggers_lock_); + server_logger_.thread_loggers_[thread_id] = this; +} + +ServerLogger::ScopedLogger::ScopedLogger( + ServerLogger::ScopedLogger&& other) noexcept + : server_logger_(other.server_logger_), + target_(std::move(other.target_)), + verbosity_(std::move(other.verbosity_)) { + auto thread_id = std::this_thread::get_id(); + std::unique_lock lock(server_logger_.thread_loggers_lock_); + server_logger_.thread_loggers_[thread_id] = this; +} + +ServerLogger::ScopedLogger::~ScopedLogger() { + auto thread_id = std::this_thread::get_id(); + std::unique_lock lock(server_logger_.thread_loggers_lock_); + auto logger_it = server_logger_.thread_loggers_.find(thread_id); + if (logger_it == server_logger_.thread_loggers_.end()) { + return; + } + if (logger_it->second == this) { + server_logger_.thread_loggers_.erase(logger_it); + } +} + +void ServerLogger::SetSeverity(const LogSeverity severity) { + std::lock_guard lock(thread_loggers_lock_); + const auto tid = std::this_thread::get_id(); + if (!Contains(thread_loggers_, tid)) { + LOG(ERROR) << "Thread logger is not registered for thread #" << tid; + return; + } + thread_loggers_[tid]->SetSeverity(severity); +} + +void ServerLogger::ScopedLogger::LogMessage( + android::base::LogId /* log_buffer_id */, + android::base::LogSeverity severity, const char* tag, const char* file, + unsigned int line, const char* message) { + if (severity < verbosity_) { + return; + } + + time_t t = time(nullptr); + struct tm now; + localtime_r(&t, &now); + auto output_string = + StderrOutputGenerator(now, getpid(), android::base::GetThreadId(), + severity, tag, file, line, message); + const bool color = target_->IsOpen() && target_->IsATTY(); + WriteAll(target_, (color ? output_string : StripColorCodes(output_string))); +} + +void ServerLogger::ScopedLogger::SetSeverity(const LogSeverity severity) { + verbosity_ = severity; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/logger.h b/base/cvd/cuttlefish/host/commands/cvd/logger.h new file mode 100644 index 0000000000..91310638c1 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/logger.h @@ -0,0 +1,80 @@ +// +// Copyright (C) 2022 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 + +#include +#include +#include +#include + +#include +#include "common/libs/fs/shared_fd.h" + +namespace cuttlefish { + +/** Per-thread logging state manager class. */ +class ServerLogger { + friend class CvdServer; + + public: + /** + * Thread-specific logger instance. + * + * When a `LOG(severity)` message is written on the same thread where this + * object was created, the message will be sent to the file descriptor stored + * in this object. + */ + class ScopedLogger { + public: + friend ServerLogger; + using LogSeverity = android::base::LogSeverity; + + ScopedLogger(ScopedLogger&&) noexcept; + ~ScopedLogger(); + + private: + ScopedLogger(ServerLogger&, SharedFD target, + const android::base::LogSeverity verbosity); + + /** Callback for `LOG(severity)` messages */ + void LogMessage(android::base::LogId log_buffer_id, + android::base::LogSeverity severity, const char* tag, + const char* file, unsigned int line, const char* message); + void SetSeverity(const LogSeverity); + + ServerLogger& server_logger_; + SharedFD target_; + android::base::LogSeverity verbosity_; + }; + ServerLogger(); + ~ServerLogger(); + + /** + * Configure `LOG(severity)` messages to write to the given file descriptor + * for the lifetime of the returned object. + */ + ScopedLogger LogThreadToFd(SharedFD, const android::base::LogSeverity); + ScopedLogger LogThreadToFd(SharedFD); + + private: + using LogSeverity = android::base::LogSeverity; + void SetSeverity(const LogSeverity); + + std::shared_mutex thread_loggers_lock_; + std::unordered_map thread_loggers_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/main.cc b/base/cvd/cuttlefish/host/commands/cvd/main.cc new file mode 100644 index 0000000000..84ea98bdf6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/main.cc @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2021 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. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/client.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/fetch/fetch_cvd.h" +#include "host/commands/cvd/flag.h" +#include "host/commands/cvd/frontline_parser.h" +// TODO(315772518) Re-enable once metrics send is reenabled +// #include "host/commands/cvd/metrics/cvd_metrics_api.h" +#include "host/commands/cvd/run_server.h" +#include "host/commands/cvd/server_constants.h" +#include "host/libs/config/host_tools_version.h" + +namespace cuttlefish { +namespace { + +/** + * Returns --verbosity value if ever exist in the entire commandline args + * + * Note that this will also pick up from the subtool arguments: + * e.g. cvd start --verbosity=DEBUG + * + * This may be incorrect as the verbosity should be ideally applied to the + * launch_cvd/cvd_internal_start only. + * + * However, parsing the --verbosity flag only from the driver is quite + * complicated as we do not know the full list of the subcommands, + * the subcommands flags, and even the selector/driver flags. + * + * Thus, we live with the corner case for now. + */ +android::base::LogSeverity CvdVerbosityOption(const int argc, char** argv) { + cvd_common::Args all_args = ArgsToVec(argc, argv); + std::string verbosity_flag_value; + std::vector verbosity_flag{ + GflagsCompatFlag("verbosity", verbosity_flag_value)}; + if (!ConsumeFlags(verbosity_flag, all_args).ok()) { + LOG(ERROR) << "Verbosity flag parsing failed, so use the default value."; + return GetMinimumVerbosity(); + } + if (verbosity_flag_value.empty()) { + return GetMinimumVerbosity(); + } + auto encoded_verbosity = EncodeVerbosity(verbosity_flag_value); + return (encoded_verbosity.ok() ? *encoded_verbosity : GetMinimumVerbosity()); +} + +/** + * Terminates a cvd server listening on "cvd_server" + * + * So far, the server processes across users were listing on the "cvd_server" + * socket. And, so far, we had one user. Now, we have multiple users. Each + * server listens to cvd_server_. The thing is if there is a server process + * started out of an old executable it will be listening to "cvd_server," and + * thus we should kill the server process first. + */ +Result KillOldServer() { + CvdClient client_to_old_server(kCvdDefaultVerbosity, "cvd_server"); + auto result = client_to_old_server.StopCvdServer(/*clear=*/true); + if (!result.ok()) { + LOG(ERROR) << "Old server listening on \"cvd_server\" socket " + << "must be killed first but failed to terminate it."; + LOG(ERROR) << "Perhaps, try cvd reset -y"; + CF_EXPECT(std::move(result)); + } + return {}; +} + +Result CvdMain(int argc, char** argv, char** envp, + const android::base::LogSeverity verbosity) { + CF_EXPECT(KillOldServer()); + + cvd_common::Args all_args = ArgsToVec(argc, argv); + CF_EXPECT(!all_args.empty()); + + if (IsServerModeExpected(all_args[0])) { + auto parsed = CF_EXPECT(ParseIfServer(all_args)); + return RunServer( + {.internal_server_fd = std::move(parsed.internal_server_fd), + .carryover_client_fd = std::move(parsed.carryover_client_fd), + .memory_carryover_fd = std::move(parsed.memory_carryover_fd), + .verbosity_level = parsed.verbosity_level, + .acloud_translator_optout = parsed.acloud_translator_optout, + .restarted_in_process = parsed.restarted_in_process}); + } + + auto env = EnvpToMap(envp); + // TODO(315772518) Re-enable once metrics send is skipped in a env + // without network support + // CvdMetrics::SendCvdMetrics(all_args); + + if (android::base::Basename(all_args[0]) == "fetch_cvd") { + CF_EXPECT(FetchCvdMain(argc, argv)); + return {}; + } + + CvdClient client(verbosity); + + // TODO(b/206893146): Make this decision inside the server. + if (android::base::Basename(all_args[0]) == "acloud") { + return client.HandleAcloud(all_args, env); + } + + if (android::base::Basename(all_args[0]) == "cvd") { + CF_EXPECT(client.HandleCvdCommand(all_args, env)); + return {}; + } + + CF_EXPECT(client.ValidateServerVersion(), + "Unable to ensure cvd_server is running."); + CF_EXPECT(client.HandleCommand(all_args, env, {})); + + return {}; +} + +/** + * Returns the URL as a colored string + * + * If stderr is not terminal, no color. + * If stderr is a tty, tries to use ".deb" file color + * If .deb is not available in LS_COLORS, uses .zip + * color. If none are available, use a default color that + * is red. + */ +std::string ColoredUrl(const std::string& url) { + if (!isatty(STDERR_FILENO)) { + return url; + } + std::string coloring_prefix = "\033[01;31m"; + std::string output; + auto ls_colors = StringFromEnv("LS_COLORS", ""); + std::vector colors_vec = android::base::Tokenize(ls_colors, ":"); + std::unordered_map colors; + for (const auto& color_entry : colors_vec) { + std::vector tokenized = + android::base::Tokenize(color_entry, "="); + if (tokenized.size() != 2) { + continue; + } + colors[tokenized.front()] = tokenized.back(); + } + + android::base::ScopeGuard return_action([&coloring_prefix, url, &output]() { + static constexpr char kRestoreColor[] = "\033[0m"; + output = fmt::format("{}{}{}", coloring_prefix, url, kRestoreColor); + }); + auto deb_color_itr = colors.find("*.deb"); + auto zip_color_itr = colors.find("*.zip"); + if (deb_color_itr == colors.end() && zip_color_itr == colors.end()) { + return output; + } + coloring_prefix = fmt::format( + "{}{}m", "\033[", + (deb_color_itr == colors.end() ? colors["*.zip"] : colors["*.deb"])); + return output; +} + +} // namespace +} // namespace cuttlefish + +int main(int argc, char** argv, char** envp) { + android::base::LogSeverity verbosity = + cuttlefish::CvdVerbosityOption(argc, argv); + android::base::InitLogging(argv, android::base::StderrLogger); + // set verbosity for this process + cuttlefish::SetMinimumVerbosity(verbosity); + + auto result = cuttlefish::CvdMain(argc, argv, envp, verbosity); + if (result.ok()) { + return 0; + } else { + // TODO: we should not print the stack trace, instead, we should rely on + // each handler to print the error message directly in the client's + // std::cerr. We print the stack trace only in the verbose mode. + std::cerr << result.error().FormatForEnv(isatty(STDERR_FILENO)) + << std::endl; + // TODO(kwstephenkim): better coloring + constexpr char kUserReminder[] = + R"( If the error above is unclear, please copy the text into an issue at:)"; + constexpr char kCuttlefishBugUrl[] = "http://go/cuttlefish-bug"; + std::cerr << std::endl << kUserReminder << std::endl; + std::cerr << " " << cuttlefish::ColoredUrl(kCuttlefishBugUrl) + << std::endl + << std::endl; + return -1; + } +} diff --git a/base/cvd/cuttlefish/host/commands/cvd/metrics/cvd_metrics_api.cpp b/base/cvd/cuttlefish/host/commands/cvd/metrics/cvd_metrics_api.cpp new file mode 100644 index 0000000000..24fca78cdc --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/metrics/cvd_metrics_api.cpp @@ -0,0 +1,172 @@ +// +// Copyright (C) 2023 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. + +#include + +#include + +#include "common/libs/utils/files.h" +#include "common/libs/utils/flag_parser.h" +#include "host/commands/cvd/metrics/cvd_metrics_api.h" +#include "host/commands/cvd/metrics/utils.h" +#include "host/commands/metrics/metrics_defs.h" + +namespace cuttlefish { + +namespace { + +// 971 for atest internal events, while 934 for external events +constexpr char kToolName[] = "cvd"; + +constexpr char kLogSourceStr[] = "CUTTLEFISH_METRICS"; +constexpr int kCppClientType = + 19; // C++ native client type (clientanalytics.proto) + +const std::string kInternalEmail = "@google.com"; +const std::vector kInternalHostname = {"google"}; + +std::string GenerateUUID() { + uuid_t uuid; + uuid_generate_random(uuid); + std::string uuid_str = "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx"; + uuid_unparse(uuid, uuid_str.data()); + return uuid_str; +} + +std::unique_ptr BuildAtestLogEvent( + const std::string& command_line) { + std::unique_ptr event = + std::make_unique(); + + // Set common fields + std::string user_key = GenerateUUID(); + std::string run_id = GenerateUUID(); + std::string os_name = metrics::GetOsName(); + std::string dir = CurrentDirectory(); + event->set_user_key(user_key); + event->set_run_id(run_id); + event->set_tool_name(kToolName); + event->set_user_type(UserType::GOOGLE); + + // Create and populate AtestStartEvent + AtestLogEventInternal::AtestStartEvent* start_event = + event->mutable_atest_start_event(); + start_event->set_command_line(command_line); + start_event->set_cwd(dir); + start_event->set_os(os_name); + + return event; +} + +std::unique_ptr BuildAtestLogRequest( + uint64_t now_ms, AtestLogEventInternal* cfEvent) { + // "log_request" is the top level LogRequest + auto log_request = std::make_unique(); + log_request->set_request_time_ms(now_ms); + log_request->set_log_source_name(kLogSourceStr); + + ClientInfo* client_info = log_request->mutable_client_info(); + client_info->set_client_type(kCppClientType); + + std::string atest_log_event; + if (!cfEvent->SerializeToString(&atest_log_event)) { + LOG(ERROR) << "Serialization failed for atest event"; + return nullptr; + } + + LogEvent* logEvent = log_request->add_log_event(); + logEvent->set_event_time_ms(now_ms); + logEvent->set_source_extension(atest_log_event); + + return log_request; +} + +std::string createCommandLine(const std::vector& args) { + std::string commandLine; + for (const auto& arg : args) { + commandLine += arg + " "; + } + // Remove the trailing space + if (!commandLine.empty()) { + commandLine.pop_back(); + } + return commandLine; +} + +std::string GetUserEmail() { + std::string email; + FILE* pipe = popen("git config --get user.email 2>/dev/null", "r"); + if (!pipe) { + LOG(ERROR) << "popen() failed!"; + return ""; + } + char buffer[128]; + while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { + email += buffer; + } + pclose(pipe); + email.erase(std::remove(email.begin(), email.end(), '\n'), email.end()); + return email; +} + +UserType GetUserType() { + std::string email = GetUserEmail(); + if (email.find(kInternalEmail) != std::string::npos) { + return UserType::GOOGLE; + } + + char hostname[1024]; + hostname[1023] = '\0'; + gethostname(hostname, sizeof(hostname) - 1); + std::string host_str(hostname); + + for (const auto& internal_host : kInternalHostname) { + if (host_str.find(internal_host) != std::string::npos) { + return UserType::GOOGLE; + } + } + + return UserType::EXTERNAL; +} + +} // namespace + +int CvdMetrics::SendLaunchCommand(const std::string& command_line) { + uint64_t now_ms = metrics::GetEpochTimeMs(); + auto cfEvent = BuildAtestLogEvent(command_line); + + auto logRequest = BuildAtestLogRequest(now_ms, cfEvent.get()); + if (!logRequest) { + LOG(ERROR) << "Failed to build atest LogRequest"; + return MetricsExitCodes::kMetricsError; + } + + std::string logRequestStr; + if (!logRequest->SerializeToString(&logRequestStr)) { + LOG(ERROR) << "Serialization failed for atest LogRequest"; + return MetricsExitCodes::kMetricsError; + } + return metrics::PostRequest(logRequestStr, metrics::kProd); +} + +int CvdMetrics::SendCvdMetrics(const std::vector& args) { + if (GetUserType() != UserType::GOOGLE) { + return 0; + } + std::string command_line = createCommandLine(args); + return CvdMetrics::SendLaunchCommand(command_line); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/metrics/cvd_metrics_api.h b/base/cvd/cuttlefish/host/commands/cvd/metrics/cvd_metrics_api.h new file mode 100644 index 0000000000..292fd8cfcc --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/metrics/cvd_metrics_api.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 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 + +namespace cuttlefish { + +class CvdMetrics { + public: + CvdMetrics() = default; + ~CvdMetrics() = default; + static int SendLaunchCommand(const std::string& command_line); + static int SendCvdMetrics(const std::vector& args); +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/metrics/metrics_notice.cpp b/base/cvd/cuttlefish/host/commands/cvd/metrics/metrics_notice.cpp new file mode 100644 index 0000000000..9a09c7736a --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/metrics/metrics_notice.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 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. + */ + +#include +#include +#include + +namespace cuttlefish { + +namespace { + +const std::string kContentLicensesUrl = + "https://source.android.com/setup/start/licenses"; +const std::map kContributorAgreementUrl = { + {"INTERNAL", "https://cla.developers.google.com/"}, + {"EXTERNAL", "https://opensource.google.com/docs/cla/"}, +}; +const std::string kPrivacyPolicyUrl = "https://policies.google.com/privacy"; +const std::string kTermsServiceUrl = "https://policies.google.com/terms"; + +} // namespace + +// TODO (moelsherif@): Extend the function after supporting internal and +// external users. +std::string GetUserType() { return "INTERNAL"; } + +void PrintDataCollectionNotice(bool colorful = true) { + const std::string red = "31m"; + const std::string green = "32m"; + const std::string start = "\033[1;"; + const std::string end = "\033[0m"; + const std::string delimiter(18, '='); + std::string anonymous; + std::string user_type = "INTERNAL"; + + if (GetUserType() == "EXTERNAL") { + anonymous = " anonymous"; + user_type = "EXTERNAL"; + } + + std::string notice = + " We collect" + anonymous + + " usage statistics in accordance with our Content " + "Licenses (" + + kContentLicensesUrl + "), Contributor License Agreement (" + + kContributorAgreementUrl.at(user_type) + + "), Privacy " + "Policy (" + + kPrivacyPolicyUrl + ") and Terms of Service (" + kTermsServiceUrl + ")."; + + if (colorful) { + std::cerr << "\n" + << delimiter << "\n" + << start << red << "Notice:" << end << std::endl; + std::cerr << start << green << " " << notice << end << "\n" + << delimiter << "\n"; + } else { + std::cerr << "\n" << delimiter << "\nNotice:" << std::endl; + std::cerr << " " << notice << "\n" << delimiter << "\n"; + } +} + +} // namespace cuttlefish diff --git a/allocd-port/include/android-base/result.h b/base/cvd/cuttlefish/host/commands/cvd/metrics/metrics_notice.h similarity index 78% rename from allocd-port/include/android-base/result.h rename to base/cvd/cuttlefish/host/commands/cvd/metrics/metrics_notice.h index 5223b9dd7a..61e805350e 100644 --- a/allocd-port/include/android-base/result.h +++ b/base/cvd/cuttlefish/host/commands/cvd/metrics/metrics_notice.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2023 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. @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #pragma once -#include "android-base/errors.h" -#include "android-base/expected.h" -#include "android-base/format.h" +namespace cuttlefish { + +void PrintDataCollectionNotice(bool colorful = true); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/metrics/proto/cvd_metrics_protos.h b/base/cvd/cuttlefish/host/commands/cvd/metrics/proto/cvd_metrics_protos.h new file mode 100644 index 0000000000..ec63915eb9 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/metrics/proto/cvd_metrics_protos.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 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 GCC system_header + +#include +#include diff --git a/base/cvd/cuttlefish/host/commands/cvd/metrics/utils.cpp b/base/cvd/cuttlefish/host/commands/cvd/metrics/utils.cpp new file mode 100644 index 0000000000..89b90c89b6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/metrics/utils.cpp @@ -0,0 +1,206 @@ +// +// Copyright (C) 2023 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. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/libs/utils/tee_logging.h" +#include "host/commands/cvd/metrics/utils.h" +#include "host/commands/metrics/metrics_defs.h" + +using cuttlefish::MetricsExitCodes; + +namespace metrics { + +static std::string Hashing(const std::string& input) { + const std::hash hasher; + return std::to_string(hasher(input)); +} + +std::string GetOsName() { + struct utsname buf; + if (uname(&buf) != 0) { + LOG(ERROR) << "failed to retrieve system information"; + return "Error"; + } + return std::string(buf.sysname); +} + +std::string GenerateSessionId(uint64_t now_ms) { + uint64_t now_day = now_ms / 1000 / 60 / 60 / 24; + return Hashing(GetMacAddress() + std::to_string(now_day)); +} + +std::string GetCfVersion() { + // TODO: per ellisr@ leave empty for now + return ""; +} + +std::string GetOsVersion() { + struct utsname buf; + if (uname(&buf) != 0) { + LOG(ERROR) << "failed to retrieve system information"; + } + std::string version = buf.release; + return version; +} + +std::string GetMacAddress() { + int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sock == -1) { + LOG(ERROR) << "couldn't connect to socket"; + return ""; + } + + char buf2[1024]; + struct ifconf ifc; + ifc.ifc_len = sizeof(buf2); + ifc.ifc_buf = buf2; + if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) { + LOG(ERROR) << "couldn't connect to socket"; + return ""; + } + + struct ifreq* it = ifc.ifc_req; + const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq)); + + unsigned char mac_address[6] = {0}; + struct ifreq ifr; + for (; it != end; ++it) { + strcpy(ifr.ifr_name, it->ifr_name); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) != 0) { + LOG(ERROR) << "couldn't connect to socket"; + return ""; + } + if (ifr.ifr_flags & IFF_LOOPBACK) { + continue; + } + if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) { + memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6); + break; + } + } + + char mac[100]; + sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", mac_address[0], mac_address[1], + mac_address[2], mac_address[3], mac_address[4], mac_address[5]); + return mac; +} + +std::string GetCompany() { + // TODO: per ellisr@ leave hard-coded for now + return "GOOGLE"; +} + +std::string GetVmmVersion() { + // TODO: per ellisr@ leave empty for now + return ""; +} + +uint64_t GetEpochTimeMs() { + auto now = std::chrono::system_clock::now().time_since_epoch(); + uint64_t milliseconds_since_epoch = + std::chrono::duration_cast(now).count(); + return milliseconds_since_epoch; +} + +size_t curl_out_writer([[maybe_unused]] char* response, size_t size, + size_t nmemb, [[maybe_unused]] void* userdata) { + return size * nmemb; +} + +CURLUcode SetCurlUrlPart(CURLU* url, CURLUPart part, const char* value) { + CURLUcode urc = curl_url_set(url, part, value, 0); + if (urc != 0) { + LOG(ERROR) << "Failed to set url part '" << part << "' to '" << value + << "': Error '" << curl_url_strerror(urc) << "'"; + } + return urc; +} + +std::string ClearcutServerUrl(metrics::ClearcutServer server) { + switch (server) { + case metrics::kLocal: + return "http://localhost:27910/log"; + + case metrics::kStaging: + return "https://play.googleapis.com:443/staging/log"; + + case metrics::kProd: + return "https://play.googleapis.com:443/log"; + + default: + LOG(FATAL) << "Invalid host configuration"; + return ""; + } +} + +MetricsExitCodes PostRequest(const std::string& output, + metrics::ClearcutServer server) { + std::string clearcut_url = ClearcutServerUrl(server); + + std::unique_ptr url(curl_url(), curl_url_cleanup); + if (!url) { + LOG(ERROR) << "Failed to initialize CURLU."; + return cuttlefish::kMetricsError; + } + + CURLUcode urc = + curl_url_set(url.get(), CURLUPART_URL, clearcut_url.c_str(), 0); + if (urc != 0) { + LOG(ERROR) << "Failed to set url to " << url.get() << clearcut_url + << "': " << curl_url_strerror(urc) << "'"; + return cuttlefish::kMetricsError; + } + curl_global_init(CURL_GLOBAL_ALL); + + std::unique_ptr curl(curl_easy_init(), + curl_easy_cleanup); + + if (!curl) { + LOG(ERROR) << "Failed to initialize CURL."; + return cuttlefish::kMetricsError; + } + + curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, &curl_out_writer); + curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl.get(), CURLOPT_CURLU, url.get()); + curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, output.data()); + curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE, output.size()); + CURLcode rc = curl_easy_perform(curl.get()); + long http_code = 0; + curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code); + + if (rc == CURLE_ABORTED_BY_CALLBACK || http_code != 200) { + LOG(ERROR) << "Metrics message failed: [" << output << "]"; + LOG(ERROR) << "http error code: " << http_code; + LOG(ERROR) << "curl error code: " << rc << " | " << curl_easy_strerror(rc); + return cuttlefish::kMetricsError; + } + curl_global_cleanup(); + return cuttlefish::kSuccess; +} +} // namespace metrics diff --git a/base/cvd/cuttlefish/host/commands/cvd/metrics/utils.h b/base/cvd/cuttlefish/host/commands/cvd/metrics/utils.h new file mode 100644 index 0000000000..283d6ffdaf --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/metrics/utils.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include +#include "host/commands/metrics/metrics_defs.h" + +namespace metrics { +enum ClearcutServer : int { + kLocal = 0, + kStaging = 1, + kProd = 2, +}; + +std::string GetOsName(); +std::string GetOsVersion(); +std::string GenerateSessionId(uint64_t now); +std::string GetCfVersion(); +std::string GetMacAddress(); +std::string GetCompany(); +std::string GetVmmVersion(); +uint64_t GetEpochTimeMs(); +std::string ProtoToString(cuttlefish::LogEvent* event); +cuttlefish::MetricsExitCodes PostRequest(const std::string& output, + ClearcutServer server); + +} // namespace metrics diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/README.md b/base/cvd/cuttlefish/host/commands/cvd/parser/README.md new file mode 100644 index 0000000000..c4b305fbb2 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/README.md @@ -0,0 +1,33 @@ +Canonical configs user interface groups and classes + +# UI structure and categories +[![UI structure diagram](./doc/linkage.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/parser/doc/linkage.svg) + +## vm category +[![vm category diagram](./doc/vm.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/parser/doc/vm.svg) + +## graphics category +[![graphics category diagram](./doc/graphics.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/parser/doc/graphics.svg) + +## adb category +[![adb category diagram](./doc/adb.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/parser/doc/adb.svg) + +## streaming category +[![streaming category diagram](./doc/streaming.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/parser/doc/streaming.svg) + +## disk category +[![disk category diagram](./doc/disk.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/parser/doc/disk.svg) + +## connectivity category +[![connectivity category diagram](./doc/connectivity.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/parser/doc/connectivity.svg) + +## camera category +[![camera category diagram](./doc/camera.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/parser/doc/camera.svg) +## audio category +[![audio category diagram](./doc/audio.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/parser/doc/audio.svg) + +## location category +[![location category diagram](./doc/location.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/parser/doc/location.svg) + +## vehicle category +[![streaming category diagram](./doc/vehicle.png)](https://cs.android.com/android/platform/superproject/+/master:device/google/cuttlefish/host/commands/cvd/parser/doc/vehicle.svg) diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_common.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_common.cpp new file mode 100644 index 0000000000..e698179bb6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_common.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2022 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. + */ +#include "host/commands/cvd/parser/cf_configs_common.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "common/libs/utils/base64.h" +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { +namespace { + +std::string ToString(const Json::ValueType value_type) { + switch (value_type) { + case Json::ValueType::nullValue: + return "null"; + case Json::ValueType::intValue: + return "int"; + case Json::ValueType::uintValue: + return "uint"; + case Json::ValueType::realValue: + return "real"; + case Json::ValueType::stringValue: + return "string"; + case Json::ValueType::booleanValue: + return "boolean"; + case Json::ValueType::arrayValue: + return "array"; + case Json::ValueType::objectValue: + return "object"; + } +} + +} // namespace + +void InitIntConfigSubGroupVector(Json::Value& instances, + const std::string& group, + const std::string& subgroup, + const std::string& json_flag, + int default_value) { + // Allocate and initialize with default values + for (auto& instance : instances) { + if (!instance.isMember(group) || (!instance[group].isMember(subgroup)) || + (instance[group][subgroup].size() == 0)) { + instance[group][subgroup][0][json_flag] = default_value; + + } else { + // Check the whole array + for (auto& subgroup_member : instance[group][subgroup]) { + if (!subgroup_member.isMember(json_flag)) { + subgroup_member[json_flag] = default_value; + } + } + } + } +} + +std::string GenerateGflag(const std::string& gflag_name, + const std::vector& values) { + std::stringstream buff; + buff << "--" << gflag_name << "="; + buff << android::base::Join(values, ','); + return buff.str(); +} + +Result GenerateGflag(const Json::Value& instances, + const std::string& gflag_name, + const std::vector& selectors) { + auto values = CF_EXPECTF(GetArrayValues(instances, selectors), + "Unable to get values for gflag \"{}\"", gflag_name); + return GenerateGflag(gflag_name, values); +} + +Result Base64EncodeGflag( + const Json::Value& instances, const std::string& gflag_name, + const std::vector& selectors) { + auto values = + CF_EXPECTF(GetArrayValues(instances, selectors), + "Unable to produce values for gflag \"{}\"", gflag_name); + for (int i = 0; i < values.size(); i++) { + std::string out; + CF_EXPECT(EncodeBase64(values[i].c_str(), values[i].size(), &out)); + values[i] = out; + } + return GenerateGflag(gflag_name, values); +} + +std::vector MergeResults(std::vector first_list, + std::vector scond_list) { + std::vector result; + result.reserve(first_list.size() + scond_list.size()); + result.insert(result.begin(), first_list.begin(), first_list.end()); + result.insert(result.end(), scond_list.begin(), scond_list.end()); + return result; +} + +/** + * @brief This function merges two json objects and override json tree in dst + * with src json keys + * + * @param dst : destination json object tree(modified in place) + * @param src : input json object tree to be merged + */ +void MergeTwoJsonObjs(Json::Value& dst, const Json::Value& src) { + for (const auto& key : src.getMemberNames()) { + if (src[key].type() == Json::arrayValue) { + for (int i = 0; i < src[key].size(); i++) { + MergeTwoJsonObjs(dst[key][i], src[key][i]); + } + } else if (src[key].type() == Json::objectValue) { + MergeTwoJsonObjs(dst[key], src[key]); + } else { + dst[key] = src[key]; + } + } +} + +// TODO(chadreynolds): collect all Result values under object and array cases to +// help user make fixes in less runs +Result Validate(const Json::Value& value, const ConfigNode& node) { + if (node.type == Json::ValueType::objectValue) { + for (const std::string& member : value.getMemberNames()) { + const auto lookup_pair = node.children.find(member); + CF_EXPECTF(lookup_pair != node.children.end(), "Unexpected node name: {}", + member); + CF_EXPECTF(Validate(value[member], lookup_pair->second), "\"{}\" ->", + member); + } + } else if (node.type == Json::ValueType::arrayValue) { + const auto lookup_pair = node.children.find(kArrayValidationSentinel); + CF_EXPECTF(lookup_pair != node.children.end(), + "Developer error in validation structure definition. A \"{}\" " + "node is expected under any array to determine element types.", + kArrayValidationSentinel); + for (const auto& element : value) { + CF_EXPECT(Validate(element, lookup_pair->second), "[array element] ->"); + } + } else { // is a leaf node + CF_EXPECTF(value.isConvertibleTo(node.type), + "Failure to convert value \"{}\" to expected JSON type: {}", + value.asString(), ToString(node.type)); + } + return {}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_common.h b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_common.h new file mode 100644 index 0000000000..6c21676a79 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_common.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include +#include +#include +#include + +#include + +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +// sentinel lookup value for validating arrays to retrieve type of the elements +inline constexpr char kArrayValidationSentinel[] = "kArrayValidationSentinel"; + +struct ConfigNode { + Json::ValueType type; + std::map children; +}; + +template +Result ValidateConfig(const Json::Value& instance, + std::function(const T&)> validator, + const std::vector& selectors) { + const int size = selectors.size(); + CF_EXPECT(size > 0, "No keys given for initializing config"); + auto result = GetValue(instance, selectors); + if (!result.ok()) { + // Field isn't present, nothing to validate + return {}; + } + auto flag_value = *result; + CF_EXPECTF(validator(flag_value), "Invalid flag value \"{}\"", flag_value); + return {}; +} + +template +Result InitConfig(Json::Value& root, const T& default_value, + const std::vector& selectors) { + const int size = selectors.size(); + CF_EXPECT(size > 0, "No keys given for initializing config"); + int i = 0; + Json::Value* traverse = &root; + for (const auto& selector : selectors) { + if (!traverse->isMember(selector)) { + if (i == size - 1) { + (*traverse)[selector] = default_value; + } else { + (*traverse)[selector] = Json::Value(); + } + } + traverse = &(*traverse)[selector]; + ++i; + } + return {}; +} + +void InitIntConfigSubGroupVector(Json::Value& instances, + const std::string& group, + const std::string& subgroup, + const std::string& json_flag, + int default_value); + +std::string GenerateGflag(const std::string& gflag_name, + const std::vector& values); +Result GenerateGflag(const Json::Value& instances, + const std::string& gflag_name, + const std::vector& selectors); +Result Base64EncodeGflag( + const Json::Value& instances, const std::string& gflag_name, + const std::vector& selectors); + +std::vector MergeResults(std::vector first_list, + std::vector scond_list); + +void MergeTwoJsonObjs(Json::Value& dst, const Json::Value& src); + +Result Validate(const Json::Value& value, const ConfigNode& node); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_instances.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_instances.cpp new file mode 100644 index 0000000000..5542132c6f --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_instances.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/parser/cf_configs_instances.h" + +#include +#include +#include + +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/parser/cf_configs_common.h" +#include "host/commands/cvd/parser/instance/cf_boot_configs.h" +#include "host/commands/cvd/parser/instance/cf_disk_configs.h" +#include "host/commands/cvd/parser/instance/cf_graphics_configs.h" +#include "host/commands/cvd/parser/instance/cf_security_configs.h" +#include "host/commands/cvd/parser/instance/cf_streaming_configs.h" +#include "host/commands/cvd/parser/instance/cf_vm_configs.h" + +namespace cuttlefish { + +Result InitInstancesConfigs(Json::Value& instances) { + for (auto& instance : instances) { + CF_EXPECT(InitConfig(instance, "", {"name"})); + } + CF_EXPECT(InitBootConfigs(instances)); + CF_EXPECT(InitDiskConfigs(instances)); + CF_EXPECT(InitGraphicsConfigs(instances)); + CF_EXPECT(InitSecurityConfigs(instances)); + CF_EXPECT(InitStreamingConfigs(instances)); + CF_EXPECT(InitVmConfigs(instances)); + return {}; +} + +Result> GenerateInstancesFlags( + const Json::Value& instances) { + std::vector result = CF_EXPECT(GenerateBootFlags(instances)); + result = MergeResults(result, CF_EXPECT(GenerateDiskFlags(instances))); + result = MergeResults(result, CF_EXPECT(GenerateGraphicsFlags(instances))); + result = MergeResults(result, CF_EXPECT(GenerateSecurityFlags(instances))); + result = MergeResults(result, CF_EXPECT(GenerateStreamingFlags(instances))); + result = MergeResults(result, CF_EXPECT(GenerateVmFlags(instances))); + + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_instances.h b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_instances.h new file mode 100644 index 0000000000..05351ef412 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_instances.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + std::string * 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 + std::string * 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +Result InitInstancesConfigs(Json::Value& root); +Result> GenerateInstancesFlags( + const Json::Value& root); + +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/cf_flags_validator.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_flags_validator.cpp new file mode 100644 index 0000000000..d85a744758 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_flags_validator.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/parser/cf_flags_validator.h" + +#include +#include +#include +#include + +#include + +#include "common/libs/utils/files.h" +#include "common/libs/utils/flags_validator.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/parser/cf_configs_common.h" + +namespace cuttlefish { +namespace { + +using Json::ValueType::arrayValue; +using Json::ValueType::booleanValue; +using Json::ValueType::intValue; +using Json::ValueType::objectValue; +using Json::ValueType::stringValue; +using Json::ValueType::uintValue; + +const auto& kRoot = *new ConfigNode{.type = objectValue, .children = { + {"netsim_bt", ConfigNode{.type = booleanValue}}, + {"netsim_uwb", ConfigNode{.type = booleanValue}}, + {"instances", ConfigNode{.type = arrayValue, .children = { + {kArrayValidationSentinel, ConfigNode{.type = objectValue, .children = { + {"@import", ConfigNode{.type = stringValue}}, + {"name", ConfigNode{.type = stringValue}}, + {"vm", ConfigNode{.type = objectValue, .children = { + {"cpus", ConfigNode{.type = uintValue}}, + {"memory_mb", ConfigNode{.type = uintValue}}, + {"use_sdcard", ConfigNode{.type = booleanValue}}, + {"setupwizard_mode", ConfigNode{.type = stringValue}}, + {"uuid", ConfigNode{.type = stringValue}}, + {"crosvm", ConfigNode{.type = objectValue, .children = { + {"enable_sandbox", ConfigNode{.type = booleanValue}}, + }}}, + {"custom_actions", ConfigNode{.type = arrayValue, .children = { + {kArrayValidationSentinel, ConfigNode{.type = objectValue, .children = { + {"shell_command", ConfigNode{.type = stringValue}}, + {"button", ConfigNode{.type = objectValue, .children = { + {"command", ConfigNode{.type = stringValue}}, + {"title", ConfigNode{.type = stringValue}}, + {"icon_name", ConfigNode{.type = stringValue}}, + }}}, + {"server", ConfigNode{.type = stringValue}}, + {"buttons", ConfigNode{.type = arrayValue, .children = { + {kArrayValidationSentinel, ConfigNode{.type = objectValue, .children = { + {"command", ConfigNode{.type = stringValue}}, + {"title", ConfigNode{.type = stringValue}}, + {"icon_name", ConfigNode{.type = stringValue}}, + }}}, + }}}, + {"device_states", ConfigNode{.type = arrayValue, .children = { + {kArrayValidationSentinel, ConfigNode{.type = objectValue, .children = { + {"lid_switch_open", ConfigNode{.type = booleanValue}}, + {"hinge_angle_value", ConfigNode{.type = intValue}}, + }}}, + }}}, + }}}, + }}}, + }}}, + {"boot", ConfigNode{.type = objectValue, .children = { + {"kernel", ConfigNode{.type = objectValue, .children = { + {"build", ConfigNode{.type = stringValue}}, + }}}, + {"enable_bootanimation", ConfigNode{.type = booleanValue}}, + {"extra_bootconfig_args", ConfigNode{.type = stringValue}}, + {"build", ConfigNode{.type = stringValue}}, + {"bootloader", ConfigNode{.type = objectValue, .children = { + {"build", ConfigNode{.type = stringValue}}, + }}}, + }}}, + {"security", ConfigNode{.type = objectValue, .children = { + {"serial_number", ConfigNode{.type = stringValue}}, + {"use_random_serial", ConfigNode{.type = stringValue}}, + {"guest_enforce_security", ConfigNode{.type = booleanValue}}, + }}}, + {"disk", ConfigNode{.type = objectValue, .children = { + {"default_build", ConfigNode{.type = stringValue}}, + {"super", ConfigNode{.type = objectValue, .children = { + {"system", ConfigNode{.type = stringValue}}, + }}}, + {"download_img_zip", ConfigNode{.type = booleanValue}}, + {"download_target_zip_files", ConfigNode{.type = booleanValue}}, + {"blank_data_image_mb", ConfigNode{.type = uintValue}}, + {"otatools", ConfigNode{.type = stringValue}}, + }}}, + {"graphics", ConfigNode{.type = objectValue, .children = { + {"displays", ConfigNode{.type = arrayValue, .children = { + {kArrayValidationSentinel, ConfigNode{.type = objectValue, .children { + {"width", ConfigNode{.type = uintValue}}, + {"height", ConfigNode{.type = uintValue}}, + {"dpi", ConfigNode{.type = uintValue}}, + {"refresh_rate_hertz", ConfigNode{.type = uintValue}}, + }}}, + }}}, + {"record_screen", ConfigNode{.type = booleanValue}}, + }}}, + {"streaming", ConfigNode{.type = objectValue, .children = { + {"device_id", ConfigNode{.type = stringValue}}, + }}}, + }}}, + }}}, + {"fetch", ConfigNode{.type = objectValue, .children = { + {"api_key", ConfigNode{.type = stringValue}}, + {"credential_source", ConfigNode{.type = stringValue}}, + {"wait_retry_period", ConfigNode{.type = uintValue}}, + {"external_dns_resolver", ConfigNode{.type = booleanValue}}, + {"keep_downloaded_archives", ConfigNode{.type = booleanValue}}, + {"api_base_url", ConfigNode{.type = stringValue}}, + }}}, + {"metrics", ConfigNode{.type = objectValue, .children = { + {"enable", ConfigNode{.type = booleanValue}}, + }}}, + {"common", ConfigNode{.type = objectValue, .children = { + {"group_name", ConfigNode{.type = stringValue}}, + {"host_package", ConfigNode{.type = stringValue}}, + }}}, +}, +}; + +} // namespace + +Result ValidateCfConfigs(const Json::Value& root) { + static const auto& kSupportedImportValues = + *new std::unordered_set{"phone", "tablet", "tv", "wearable", + "auto", "slim", "go", "foldable"}; + + CF_EXPECT(Validate(root, kRoot), "Validation failure in [root object] ->"); + for (const auto& instance : root["instances"]) { + // TODO(chadreynolds): update `ExtractLaunchTemplates` to return a Result + // and check import values there, then remove this check + if (instance.isMember("@import")) { + const std::string import_value = instance["@import"].asString(); + CF_EXPECTF(kSupportedImportValues.find(import_value) != + kSupportedImportValues.end(), + "import value of \"{}\" is not supported", import_value); + } + CF_EXPECT(ValidateConfig(instance, ValidateSetupWizardMode, + {"vm", "setupwizard_mode"}), + "Invalid value for setupwizard_mode flag"); + } + return {}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/cf_flags_validator.h b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_flags_validator.h new file mode 100644 index 0000000000..c393f8d325 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_flags_validator.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +Result ValidateCfConfigs(const Json::Value& root); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/cf_metrics_configs.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_metrics_configs.cpp new file mode 100644 index 0000000000..ff1909ee5b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_metrics_configs.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 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. + */ +#include "host/commands/cvd/parser/cf_metrics_configs.h" + +#include +#include +#include + +#include + +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/parser/cf_configs_common.h" + +namespace cuttlefish { +namespace { + +constexpr bool kEnableMetricsDefault = false; + +std::string EnabledToReportAnonUsageStats(const bool enabled) { + return enabled ? "y" : "n"; +} + +} // namespace + +Result InitMetricsConfigs(Json::Value& root) { + CF_EXPECT(InitConfig(root, kEnableMetricsDefault, {"metrics", "enable"})); + return {}; +} + +Result> GenerateMetricsFlags(const Json::Value& root) { + std::vector result; + auto report_flag_value = EnabledToReportAnonUsageStats( + CF_EXPECT(GetValue(root, {"metrics", "enable"}))); + result.emplace_back( + GenerateGflag("report_anonymous_usage_stats", {report_flag_value})); + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/cf_metrics_configs.h b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_metrics_configs.h new file mode 100644 index 0000000000..715912558c --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/cf_metrics_configs.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + std::string * 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 + std::string * 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +Result InitMetricsConfigs(Json::Value& root); +Result> GenerateMetricsFlags(const Json::Value& root); + +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.dot b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.dot new file mode 100644 index 0000000000..06deeba6a4 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.dot @@ -0,0 +1,10 @@ +graph { + +adb +run_adb_connector +mode + +adb--run_adb_connector +adb--mode + +} \ No newline at end of file diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.png b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.png new file mode 100644 index 0000000000..4a0d68eaa2 Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.svg b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.svg new file mode 100644 index 0000000000..9e32683e0b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.svg @@ -0,0 +1,41 @@ + + + + + + +%3 + + + +adb + +adb + + + +run_adb_connector + +run_adb_connector + + + +adb--run_adb_connector + + + + +mode + +mode + + + +adb--mode + + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.dot b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.dot new file mode 100644 index 0000000000..eeafd8e601 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.dot @@ -0,0 +1,6 @@ +graph { + rankdir=LR + +audio--enable + +} \ No newline at end of file diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.png b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.png new file mode 100644 index 0000000000..7a7f549342 Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.svg b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.svg new file mode 100644 index 0000000000..b265f3f696 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.svg @@ -0,0 +1,30 @@ + + + + + + +%3 + + + +audio + +audio + + + +enable + +enable + + + +audio--enable + + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.dot b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.dot new file mode 100644 index 0000000000..78bb75faa2 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.dot @@ -0,0 +1,4 @@ +graph { + rankdir=LR +camera--camera_server_port +} \ No newline at end of file diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.png b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.png new file mode 100644 index 0000000000..83c0a633d2 Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.svg b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.svg new file mode 100644 index 0000000000..1a9e45ef7d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.svg @@ -0,0 +1,30 @@ + + + + + + +%3 + + + +camera + +camera + + + +camera_server_port + +camera_server_port + + + +camera--camera_server_port + + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.dot b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.dot new file mode 100644 index 0000000000..723eb14511 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.dot @@ -0,0 +1,14 @@ +graph { + rankdir=LR + +connectivity--ril_dns +connectivity--wifi + wifi--ap_kernel_image + wifi--ap_rootfs_image + wifi--vhost_net +connectivity--bluetooth +connectivity--modem_simulator + modem_simulator--enable + modem_simulator--sim_type + modem_simulator--count +} \ No newline at end of file diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.png b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.png new file mode 100644 index 0000000000..3c73606abd Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.svg b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.svg new file mode 100644 index 0000000000..921565d7b2 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.svg @@ -0,0 +1,129 @@ + + + + + + +%3 + + + +connectivity + +connectivity + + + +ril_dns + +ril_dns + + + +connectivity--ril_dns + + + + +wifi + +wifi + + + +connectivity--wifi + + + + +bluetooth + +bluetooth + + + +connectivity--bluetooth + + + + +modem_simulator + +modem_simulator + + + +connectivity--modem_simulator + + + + +ap_kernel_image + +ap_kernel_image + + + +wifi--ap_kernel_image + + + + +ap_rootfs_image + +ap_rootfs_image + + + +wifi--ap_rootfs_image + + + + +vhost_net + +vhost_net + + + +wifi--vhost_net + + + + +enable + +enable + + + +modem_simulator--enable + + + + +sim_type + +sim_type + + + +modem_simulator--sim_type + + + + +count + +count + + + +modem_simulator--count + + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.dot b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.dot new file mode 100644 index 0000000000..3682004b94 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.dot @@ -0,0 +1,33 @@ +graph { + rankdir=LR + +disk--bootloader + bootloader--boot_slot + bootloader--build + bootloader--pause +disk--boot + boot_build [label = "build"] + boot--boot_build + boot--kernel + boot--initramfs + boot--extra_cmdline + boot--extra_bootconfig +disk--data + data--format + data--size +disk--metadata + metadata_size [label = "size"] + metadata--metadata_size +disk--misc + misc_size [label = "size"] + misc--misc_size +disk--otheros + otheros--esp_image + otheros--initramfs_path + otheros--kernel_path + otheros--root_image +disk--super + super_build [label = "build"] + super--system + super--super_build +} \ No newline at end of file diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.png b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.png new file mode 100644 index 0000000000..9979454b7e Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.svg b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.svg new file mode 100644 index 0000000000..016744d243 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.svg @@ -0,0 +1,294 @@ + + + + + + +%3 + + + +disk + +disk + + + +bootloader + +bootloader + + + +disk--bootloader + + + + +boot + +boot + + + +disk--boot + + + + +data + +data + + + +disk--data + + + + +metadata + +metadata + + + +disk--metadata + + + + +misc + +misc + + + +disk--misc + + + + +otheros + +otheros + + + +disk--otheros + + + + +super + +super + + + +disk--super + + + + +boot_slot + +boot_slot + + + +bootloader--boot_slot + + + + +build + +build + + + +bootloader--build + + + + +pause + +pause + + + +bootloader--pause + + + + +boot_build + +build + + + +boot--boot_build + + + + +kernel + +kernel + + + +boot--kernel + + + + +initramfs + +initramfs + + + +boot--initramfs + + + + +extra_cmdline + +extra_cmdline + + + +boot--extra_cmdline + + + + +extra_bootconfig + +extra_bootconfig + + + +boot--extra_bootconfig + + + + +format + +format + + + +data--format + + + + +size + +size + + + +data--size + + + + +metadata_size + +size + + + +metadata--metadata_size + + + + +misc_size + +size + + + +misc--misc_size + + + + +esp_image + +esp_image + + + +otheros--esp_image + + + + +initramfs_path + +initramfs_path + + + +otheros--initramfs_path + + + + +kernel_path + +kernel_path + + + +otheros--kernel_path + + + + +root_image + +root_image + + + +otheros--root_image + + + + +super_build + +build + + + +super--super_build + + + + +system + +system + + + +super--system + + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.dot b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.dot new file mode 100644 index 0000000000..abc9dbe92a --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.dot @@ -0,0 +1,14 @@ +graph { + rankdir=LR + graphics--hwcomposer + graphics--gpu_mode + graphics--record_screen + graphics--gpu_capture_binary + graphics--enable_gpu_udmabuf + graphics--enable_gpu_angle + graphics--displays + displays--dpi + displays--refresh_rate_hz + displays--x_res + displays--y_res +} \ No newline at end of file diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.png b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.png new file mode 100644 index 0000000000..bd8bd8735f Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.svg b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.svg new file mode 100644 index 0000000000..21a5032290 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.svg @@ -0,0 +1,140 @@ + + + + + + +%3 + + + +graphics + +graphics + + + +hwcomposer + +hwcomposer + + + +graphics--hwcomposer + + + + +gpu_mode + +gpu_mode + + + +graphics--gpu_mode + + + + +record_screen + +record_screen + + + +graphics--record_screen + + + + +gpu_capture_binary + +gpu_capture_binary + + + +graphics--gpu_capture_binary + + + + +enable_gpu_udmabuf + +enable_gpu_udmabuf + + + +graphics--enable_gpu_udmabuf + + + + +enable_gpu_angle + +enable_gpu_angle + + + +graphics--enable_gpu_angle + + + + +displays + +displays + + + +graphics--displays + + + + +dpi + +dpi + + + +displays--dpi + + + + +refresh_rate_hz + +refresh_rate_hz + + + +displays--refresh_rate_hz + + + + +x_res + +x_res + + + +displays--x_res + + + + +y_res + +y_res + + + +displays--y_res + + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.dot b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.dot new file mode 100644 index 0000000000..a54deb4bf6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.dot @@ -0,0 +1,21 @@ +graph { + rankdir=LR +node [shape=record,width=.1,height=.1]; +root--instances +root--common +common--vhost_user_mac80211_hwsim +common--wmediumd_config +common--enable_host_bluetooth +common--netsim +common--netsim_bt +instances--vm +instances--disk +instances--graphics +instances--camera +instances--connectivity +instances--audio +instances--streaming +instances--adb +instances--vehicle +instances--location +} diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.png b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.png new file mode 100644 index 0000000000..4ef1c9d22f Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.svg b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.svg new file mode 100644 index 0000000000..127d70a6b0 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.svg @@ -0,0 +1,228 @@ + + + + + + +%3 + + + +root + +root + + + +instances + +instances + + + +root--instances + + + + +common + +common + + + +root--common + + + + +vm + +vm + + + +instances--vm + + + + +disk + +disk + + + +instances--disk + + + + +graphics + +graphics + + + +instances--graphics + + + + +camera + +camera + + + +instances--camera + + + + +connectivity + +connectivity + + + +instances--connectivity + + + + +audio + +audio + + + +instances--audio + + + + +streaming + +streaming + + + +instances--streaming + + + + +adb + +adb + + + +instances--adb + + + + +vehicle + +vehicle + + + +instances--vehicle + + + + +location + +location + + + +instances--location + + + + +vhost_user_mac80211_hwsim + +vhost_user_mac80211_hwsim + + + +common--vhost_user_mac80211_hwsim + + + + +wmediumd_config + +wmediumd_config + + + +common--wmediumd_config + + + + +bluetooth_controller_properties_file + +bluetooth_controller_properties_file + + + +common--bluetooth_controller_properties_file + + + + +bluetooth_default_commands_file + +bluetooth_default_commands_file + + + +common--bluetooth_default_commands_file + + + + +enable_host_bluetooth + +enable_host_bluetooth + + + +common--enable_host_bluetooth + + + + +netsim + +netsim + + + +common--netsim + + + + +netsim_bt + +netsim_bt + + + +common--netsim_bt + + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.dot b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.dot new file mode 100644 index 0000000000..ed8a3de9c3 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.dot @@ -0,0 +1,6 @@ +graph { + rankdir=LR + location--start_gnss_proxy + location--fixed_location_file_path + location--gnss_file_path +} \ No newline at end of file diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.png b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.png new file mode 100644 index 0000000000..f90e1ce0a6 Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.svg b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.svg new file mode 100644 index 0000000000..cad95d8e75 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.svg @@ -0,0 +1,52 @@ + + + + + + +%3 + + + +location + +location + + + +start_gnss_proxy + +start_gnss_proxy + + + +location--start_gnss_proxy + + + + +fixed_location_file_path + +fixed_location_file_path + + + +location--fixed_location_file_path + + + + +gnss_file_path + +gnss_file_path + + + +location--gnss_file_path + + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.dot b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.dot new file mode 100644 index 0000000000..42f521d88f --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.dot @@ -0,0 +1,14 @@ +graph { + rankdir=LR +streaming--start +streaming--start_sig_server +streaming--verify_sig_server_certificate +streaming--assets_dir +streaming--certs_dir +streaming--sig_server_addr +streaming--sig_server_path +streaming--sig_server_port +streaming--sig_server_secure +streaming--tcp_port_range +streaming--udp_port_range +} \ No newline at end of file diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.png b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.png new file mode 100644 index 0000000000..798484e5b6 Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.svg b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.svg new file mode 100644 index 0000000000..efbd42fd7d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.svg @@ -0,0 +1,140 @@ + + + + + + +%3 + + + +streaming + +streaming + + + +start + +start + + + +streaming--start + + + + +start_sig_server + +start_sig_server + + + +streaming--start_sig_server + + + + +verify_sig_server_certificate + +verify_sig_server_certificate + + + +streaming--verify_sig_server_certificate + + + + +assets_dir + +assets_dir + + + +streaming--assets_dir + + + + +certs_dir + +certs_dir + + + +streaming--certs_dir + + + + +sig_server_addr + +sig_server_addr + + + +streaming--sig_server_addr + + + + +sig_server_path + +sig_server_path + + + +streaming--sig_server_path + + + + +sig_server_port + +sig_server_port + + + +streaming--sig_server_port + + + + +sig_server_secure + +sig_server_secure + + + +streaming--sig_server_secure + + + + +tcp_port_range + +tcp_port_range + + + +streaming--tcp_port_range + + + + +udp_port_range + +udp_port_range + + + +streaming--udp_port_range + + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.dot b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.dot new file mode 100644 index 0000000000..6931be5d5d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.dot @@ -0,0 +1,5 @@ +graph { + rankdir=LR + + vehicle--enable_vehicle_hal_grpc_server +} \ No newline at end of file diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.png b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.png new file mode 100644 index 0000000000..fe745036ee Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.svg b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.svg new file mode 100644 index 0000000000..ee45abccb7 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.svg @@ -0,0 +1,30 @@ + + + + + + +%3 + + + +vehicle + +vehicle + + + +enable_vehicle_hal_grpc_server + +enable_vehicle_hal_grpc_server + + + +vehicle--enable_vehicle_hal_grpc_server + + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.dot b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.dot new file mode 100644 index 0000000000..b0d40b9ef1 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.dot @@ -0,0 +1,47 @@ +graph { + rankdir=LR + vm--cpus + vm--vm_manager + vm--vsock_guest_cid + vm--enable_minimal_mode + vm--restart_subprocesses + vm--setupwizard_mode + vm--smt + vm--use_allocd + vm--use_sdcard + vm--uuid + vm--file_verbosity + vm--verbosity + vm--Run_file_discovery + vm--config + vm--memory_mb + vm--custom_actions + vm--vm_manager + crosvm_binary_dir [label = "binary_dir"] + qemu_binary_dir [label = "binary_dir"] + gem5_binary_dir [label = "binary_dir"] + + vm_manager--crosvm + crosvm--crosvm_binary_dir + crosvm--seccomp_policy_dir + crosvm--enable_sandbox + vm_manager--qemu + qemu--qemu_binary_dir + vm_manager--gem5 + gem5--gem5_binary_dir + gem5--checkpoint_dir + gem5--debug_file + gem5--debug_flags + vm--security + security--guest_enforce_security + security--serial_number + security--secure_hals + vm--kernel + kernel--enable_kernel_log + kernel--kgdb + kernel--gdb_port + kernel--console + kernel--extra_kernel_cmdline + kernel--initramfs_path + kernel--path +} \ No newline at end of file diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.png b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.png new file mode 100644 index 0000000000..3b3ff8d38e Binary files /dev/null and b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.png differ diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.svg b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.svg new file mode 100644 index 0000000000..a86496e5f5 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.svg @@ -0,0 +1,453 @@ + + + + + + +%3 + + + +vm + +vm + + + +cpus + +cpus + + + +vm--cpus + + + + +vm_manager + +vm_manager + + + +vm--vm_manager + + + + +vm--vm_manager + + + + +vsock_guest_cid + +vsock_guest_cid + + + +vm--vsock_guest_cid + + + + +enable_minimal_mode + +enable_minimal_mode + + + +vm--enable_minimal_mode + + + + +restart_subprocesses + +restart_subprocesses + + + +vm--restart_subprocesses + + + + +setupwizard_mode + +setupwizard_mode + + + +vm--setupwizard_mode + + + + +smt + +smt + + + +vm--smt + + + + +use_allocd + +use_allocd + + + +vm--use_allocd + + + + +use_sdcard + +use_sdcard + + + +vm--use_sdcard + + + + +uuid + +uuid + + + +vm--uuid + + + + +file_verbosity + +file_verbosity + + + +vm--file_verbosity + + + + +verbosity + +verbosity + + + +vm--verbosity + + + + +Run_file_discovery + +Run_file_discovery + + + +vm--Run_file_discovery + + + + +config + +config + + + +vm--config + + + + +memory_mb + +memory_mb + + + +vm--memory_mb + + + + +custom_actions + +custom_actions + + + +vm--custom_actions + + + + +security + +security + + + +vm--security + + + + +kernel + +kernel + + + +vm--kernel + + + + +crosvm + +crosvm + + + +vm_manager--crosvm + + + + +qemu + +qemu + + + +vm_manager--qemu + + + + +gem5 + +gem5 + + + +vm_manager--gem5 + + + + +crosvm_binary_dir + +binary_dir + + + +qemu_binary_dir + +binary_dir + + + +gem5_binary_dir + +binary_dir + + + +crosvm--crosvm_binary_dir + + + + +seccomp_policy_dir + +seccomp_policy_dir + + + +crosvm--seccomp_policy_dir + + + + +enable_sandbox + +enable_sandbox + + + +crosvm--enable_sandbox + + + + +qemu--qemu_binary_dir + + + + +gem5--gem5_binary_dir + + + + +checkpoint_dir + +checkpoint_dir + + + +gem5--checkpoint_dir + + + + +debug_file + +debug_file + + + +gem5--debug_file + + + + +debug_flags + +debug_flags + + + +gem5--debug_flags + + + + +guest_enforce_security + +guest_enforce_security + + + +security--guest_enforce_security + + + + +serial_number + +serial_number + + + +security--serial_number + + + + +secure_hals + +secure_hals + + + +security--secure_hals + + + + +enable_kernel_log + +enable_kernel_log + + + +kernel--enable_kernel_log + + + + +kgdb + +kgdb + + + +kernel--kgdb + + + + +gdb_port + +gdb_port + + + +kernel--gdb_port + + + + +console + +console + + + +kernel--console + + + + +extra_kernel_cmdline + +extra_kernel_cmdline + + + +kernel--extra_kernel_cmdline + + + + +initramfs_path + +initramfs_path + + + +kernel--initramfs_path + + + + +path + +path + + + +kernel--path + + + + diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_config_parser.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_config_parser.cpp new file mode 100644 index 0000000000..9077496de3 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_config_parser.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/parser/fetch_config_parser.h" + +#include +#include +#include + +#include +#include + +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/fetch/fetch_cvd_parser.h" +#include "host/commands/cvd/parser/cf_configs_common.h" +#include "host/libs/web/android_build_api.h" + +namespace cuttlefish { +namespace { + +constexpr std::string_view kFetchPrefix = "@ab/"; + +Result InitFetchInstanceConfigs(Json::Value& instance) { + CF_EXPECT( + InitConfig(instance, kDefaultBuildString, {"disk", "default_build"})); + CF_EXPECT( + InitConfig(instance, kDefaultBuildString, {"disk", "super", "system"})); + CF_EXPECT( + InitConfig(instance, kDefaultBuildString, {"boot", "kernel", "build"})); + CF_EXPECT(InitConfig(instance, kDefaultBuildString, {"boot", "build"})); + CF_EXPECT(InitConfig(instance, kDefaultBuildString, + {"boot", "bootloader", "build"})); + CF_EXPECT(InitConfig(instance, kDefaultBuildString, {"disk", "otatools"})); + CF_EXPECT(InitConfig(instance, kDefaultDownloadImgZip, + {"disk", "download_img_zip"})); + CF_EXPECT(InitConfig(instance, kDefaultDownloadTargetFilesZip, + {"disk", "download_target_files_zip"})); + return {}; +} + +Result InitFetchCvdConfigs(Json::Value& root) { + CF_EXPECT(InitConfig(root, kDefaultApiKey, {"fetch", "api_key"})); + CF_EXPECT(InitConfig(root, kDefaultCredentialSource, + {"fetch", "credential_source"})); + CF_EXPECT(InitConfig(root, static_cast(kDefaultWaitRetryPeriod.count()), + {"fetch", "wait_retry_period"})); + CF_EXPECT(InitConfig(root, kDefaultExternalDnsResolver, + {"fetch", "external_dns_resolver"})); + CF_EXPECT(InitConfig(root, kDefaultKeepDownloadedArchives, + {"fetch", "keep_downloaded_archives"})); + CF_EXPECT( + InitConfig(root, kAndroidBuildServiceUrl, {"fetch", "api_base_url"})); + CF_EXPECT(InitConfig(root, kDefaultBuildString, {"common", "host_package"})); + for (auto& instance : root["instances"]) { + CF_EXPECT(InitFetchInstanceConfigs(instance)); + } + return {}; +} + +bool ShouldFetch(const Json::Value& instance) { + for (const auto& value : + {instance["disk"]["default_build"], instance["disk"]["super"]["system"], + instance["boot"]["kernel"]["build"], instance["boot"]["build"], + instance["boot"]["bootloader"]["build"], + instance["disk"]["otatools"]}) { + // expects non-prefixed build strings already converted to empty strings + if (!value.asString().empty()) { + return true; + } + } + return false; +} + +Result GetFetchBuildString(const Json::Value& value) { + std::string strVal = value.asString(); + std::string_view view = strVal; + if (!android::base::ConsumePrefix(&view, kFetchPrefix)) { + // intentionally return an empty string when there are local, non-prefixed + // paths. Fetch does not process the local paths + return ""; + } + CF_EXPECTF(!view.empty(), + "\"{}\" prefixed build string was not followed by a value", + kFetchPrefix); + return std::string(view); +} + +Result RemoveNonPrefixedBuildStrings(const Json::Value& instance) { + auto result = Json::Value(instance); + result["disk"]["default_build"] = + CF_EXPECT(GetFetchBuildString(result["disk"]["default_build"])); + result["disk"]["super"]["system"] = + CF_EXPECT(GetFetchBuildString(result["disk"]["super"]["system"])); + result["boot"]["kernel"]["build"] = + CF_EXPECT(GetFetchBuildString(result["boot"]["kernel"]["build"])); + result["boot"]["build"] = + CF_EXPECT(GetFetchBuildString(result["boot"]["build"])); + result["boot"]["bootloader"]["build"] = + CF_EXPECT(GetFetchBuildString(result["boot"]["bootloader"]["build"])); + result["disk"]["otatools"] = + CF_EXPECT(GetFetchBuildString(result["disk"]["otatools"])); + return result; +} + +Result> GenerateFetchFlags( + const Json::Value& root, const std::string& target_directory, + const std::vector& target_subdirectories) { + Json::Value fetch_instances = Json::Value(Json::ValueType::arrayValue); + std::vector fetch_subdirectories; + const auto& instances = root["instances"]; + CF_EXPECT_EQ(instances.size(), target_subdirectories.size(), + "Mismatched sizes between number of subdirectories and number " + "of instances"); + for (int i = 0; i < instances.size(); i++) { + const auto prefix_filtered = + CF_EXPECT(RemoveNonPrefixedBuildStrings(instances[i])); + if (ShouldFetch(prefix_filtered)) { + fetch_instances.append(prefix_filtered); + fetch_subdirectories.emplace_back(target_subdirectories[i]); + } + } + + const std::string host_package_build = + CF_EXPECT(GetFetchBuildString(root["common"]["host_package"])); + std::vector result; + if (fetch_subdirectories.empty() && host_package_build.empty()) { + return result; + } + + result.emplace_back(GenerateGflag("target_directory", {target_directory})); + result.emplace_back(GenerateGflag( + "api_key", + {CF_EXPECT(GetValue(root, {"fetch", "api_key"}))})); + result.emplace_back(GenerateGflag( + "credential_source", {CF_EXPECT(GetValue( + root, {"fetch", "credential_source"}))})); + result.emplace_back(GenerateGflag( + "wait_retry_period", {CF_EXPECT(GetValue( + root, {"fetch", "wait_retry_period"}))})); + result.emplace_back( + GenerateGflag("external_dns_resolver", + {CF_EXPECT(GetValue( + root, {"fetch", "external_dns_resolver"}))})); + result.emplace_back( + GenerateGflag("keep_downloaded_archives", + {CF_EXPECT(GetValue( + root, {"fetch", "keep_downloaded_archives"}))})); + result.emplace_back(GenerateGflag( + "api_base_url", + {CF_EXPECT(GetValue(root, {"fetch", "api_base_url"}))})); + result.emplace_back( + GenerateGflag("host_package_build", {host_package_build})); + + result.emplace_back( + GenerateGflag("target_subdirectory", fetch_subdirectories)); + result.emplace_back(CF_EXPECT(GenerateGflag(fetch_instances, "default_build", + {"disk", "default_build"}))); + result.emplace_back(CF_EXPECT(GenerateGflag(fetch_instances, "system_build", + {"disk", "super", "system"}))); + result.emplace_back(CF_EXPECT(GenerateGflag(fetch_instances, "kernel_build", + {"boot", "kernel", "build"}))); + result.emplace_back(CF_EXPECT( + GenerateGflag(fetch_instances, "boot_build", {"boot", "build"}))); + result.emplace_back(CF_EXPECT(GenerateGflag( + fetch_instances, "bootloader_build", {"boot", "bootloader", "build"}))); + result.emplace_back( + CF_EXPECT(GenerateGflag(fetch_instances, "android_efi_loader_build", + {"boot", "bootloader", "build"}))); + result.emplace_back(CF_EXPECT( + GenerateGflag(fetch_instances, "otatools_build", {"disk", "otatools"}))); + result.emplace_back(CF_EXPECT(GenerateGflag( + fetch_instances, "download_img_zip", {"disk", "download_img_zip"}))); + result.emplace_back( + CF_EXPECT(GenerateGflag(fetch_instances, "download_target_files_zip", + {"disk", "download_target_files_zip"}))); + return result; +} + +} // namespace + +Result> ParseFetchCvdConfigs( + Json::Value& root, const std::string& target_directory, + const std::vector& target_subdirectories) { + CF_EXPECT(InitFetchCvdConfigs(root)); + return CF_EXPECT( + GenerateFetchFlags(root, target_directory, target_subdirectories)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_config_parser.h b/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_config_parser.h new file mode 100644 index 0000000000..f2258b3185 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_config_parser.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +Result> ParseFetchCvdConfigs( + Json::Value& root, const std::string& target_directory, + const std::vector& target_subdirectories); + +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_cvd_parser.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_cvd_parser.cpp new file mode 100644 index 0000000000..af06858d7f --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_cvd_parser.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/parser/fetch_cvd_parser.h" + +#include +#include +#include + +#include +#include + +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/fetch/fetch_cvd.h" +#include "host/commands/cvd/parser/cf_configs_common.h" +#include "host/libs/web/build_api.h" + +namespace cuttlefish { +namespace { + +constexpr std::string_view kFetchPrefix = "@ab/"; + +Result InitFetchInstanceConfigs(Json::Value& instance) { + CF_EXPECT( + InitConfig(instance, kDefaultBuildString, {"disk", "default_build"})); + CF_EXPECT( + InitConfig(instance, kDefaultBuildString, {"disk", "super", "system"})); + CF_EXPECT( + InitConfig(instance, kDefaultBuildString, {"boot", "kernel", "build"})); + CF_EXPECT(InitConfig(instance, kDefaultBuildString, {"boot", "build"})); + CF_EXPECT(InitConfig(instance, kDefaultBuildString, + {"boot", "bootloader", "build"})); + CF_EXPECT(InitConfig(instance, kDefaultBuildString, {"disk", "otatools"})); + CF_EXPECT(InitConfig(instance, kDefaultDownloadImgZip, + {"disk", "download_img_zip"})); + CF_EXPECT(InitConfig(instance, kDefaultDownloadTargetFilesZip, + {"disk", "download_target_files_zip"})); + return {}; +} + +Result InitFetchCvdConfigs(Json::Value& root) { + CF_EXPECT(InitConfig(root, kDefaultApiKey, {"fetch", "api_key"})); + CF_EXPECT(InitConfig(root, kDefaultCredentialSource, + {"fetch", "credential_source"})); + CF_EXPECT(InitConfig(root, static_cast(kDefaultWaitRetryPeriod.count()), + {"fetch", "wait_retry_period"})); + CF_EXPECT(InitConfig(root, kDefaultExternalDnsResolver, + {"fetch", "external_dns_resolver"})); + CF_EXPECT(InitConfig(root, kDefaultKeepDownloadedArchives, + {"fetch", "keep_downloaded_archives"})); + CF_EXPECT( + InitConfig(root, kAndroidBuildServiceUrl, {"fetch", "api_base_url"})); + CF_EXPECT(InitConfig(root, kDefaultBuildString, {"common", "host_package"})); + for (auto& instance : root["instances"]) { + CF_EXPECT(InitFetchInstanceConfigs(instance)); + } + return {}; +} + +bool ShouldFetch(const Json::Value& instance) { + for (const auto& value : + {instance["disk"]["default_build"], instance["disk"]["super"]["system"], + instance["boot"]["kernel"]["build"], instance["boot"]["build"], + instance["boot"]["bootloader"]["build"], + instance["disk"]["otatools"]}) { + // expects non-prefixed build strings already converted to empty strings + if (!value.asString().empty()) { + return true; + } + } + return false; +} + +Result GetFetchBuildString(const Json::Value& value) { + std::string strVal = value.asString(); + std::string_view view = strVal; + if (!android::base::ConsumePrefix(&view, kFetchPrefix)) { + // intentionally return an empty string when there are local, non-prefixed + // paths. Fetch does not process the local paths + return ""; + } + CF_EXPECTF(!view.empty(), + "\"{}\" prefixed build string was not followed by a value", + kFetchPrefix); + return std::string(view); +} + +Result RemoveNonPrefixedBuildStrings(const Json::Value& instance) { + auto result = Json::Value(instance); + result["disk"]["default_build"] = + CF_EXPECT(GetFetchBuildString(result["disk"]["default_build"])); + result["disk"]["super"]["system"] = + CF_EXPECT(GetFetchBuildString(result["disk"]["super"]["system"])); + result["boot"]["kernel"]["build"] = + CF_EXPECT(GetFetchBuildString(result["boot"]["kernel"]["build"])); + result["boot"]["build"] = + CF_EXPECT(GetFetchBuildString(result["boot"]["build"])); + result["boot"]["bootloader"]["build"] = + CF_EXPECT(GetFetchBuildString(result["boot"]["bootloader"]["build"])); + result["disk"]["otatools"] = + CF_EXPECT(GetFetchBuildString(result["disk"]["otatools"])); + return result; +} + +Result> GenerateFetchFlags( + const Json::Value& root, const std::string& target_directory, + const std::vector& target_subdirectories) { + Json::Value fetch_instances = Json::Value(Json::ValueType::arrayValue); + std::vector fetch_subdirectories; + const auto& instances = root["instances"]; + CF_EXPECT_EQ(instances.size(), target_subdirectories.size(), + "Mismatched sizes between number of subdirectories and number " + "of instances"); + for (int i = 0; i < instances.size(); i++) { + const auto prefix_filtered = + CF_EXPECT(RemoveNonPrefixedBuildStrings(instances[i])); + if (ShouldFetch(prefix_filtered)) { + fetch_instances.append(prefix_filtered); + fetch_subdirectories.emplace_back(target_subdirectories[i]); + } + } + + const std::string host_package_build = + CF_EXPECT(GetFetchBuildString(root["common"]["host_package"])); + std::vector result; + if (fetch_subdirectories.empty() && host_package_build.empty()) { + return result; + } + + result.emplace_back(GenerateGflag("target_directory", {target_directory})); + result.emplace_back(GenerateGflag( + "api_key", + {CF_EXPECT(GetValue(root, {"fetch", "api_key"}))})); + result.emplace_back(GenerateGflag( + "credential_source", {CF_EXPECT(GetValue( + root, {"fetch", "credential_source"}))})); + result.emplace_back(GenerateGflag( + "wait_retry_period", {CF_EXPECT(GetValue( + root, {"fetch", "wait_retry_period"}))})); + result.emplace_back( + GenerateGflag("external_dns_resolver", + {CF_EXPECT(GetValue( + root, {"fetch", "external_dns_resolver"}))})); + result.emplace_back( + GenerateGflag("keep_downloaded_archives", + {CF_EXPECT(GetValue( + root, {"fetch", "keep_downloaded_archives"}))})); + result.emplace_back(GenerateGflag( + "api_base_url", + {CF_EXPECT(GetValue(root, {"fetch", "api_base_url"}))})); + result.emplace_back( + GenerateGflag("host_package_build", {host_package_build})); + + result.emplace_back( + GenerateGflag("target_subdirectory", fetch_subdirectories)); + result.emplace_back(CF_EXPECT(GenerateGflag(fetch_instances, "default_build", + {"disk", "default_build"}))); + result.emplace_back(CF_EXPECT(GenerateGflag(fetch_instances, "system_build", + {"disk", "super", "system"}))); + result.emplace_back(CF_EXPECT(GenerateGflag(fetch_instances, "kernel_build", + {"boot", "kernel", "build"}))); + result.emplace_back(CF_EXPECT( + GenerateGflag(fetch_instances, "boot_build", {"boot", "build"}))); + result.emplace_back(CF_EXPECT(GenerateGflag( + fetch_instances, "bootloader_build", {"boot", "bootloader", "build"}))); + result.emplace_back(CF_EXPECT( + GenerateGflag(fetch_instances, "otatools_build", {"disk", "otatools"}))); + result.emplace_back(CF_EXPECT(GenerateGflag( + fetch_instances, "download_img_zip", {"disk", "download_img_zip"}))); + result.emplace_back( + CF_EXPECT(GenerateGflag(fetch_instances, "download_target_files_zip", + {"disk", "download_target_files_zip"}))); + return result; +} + +} // namespace + +Result> ParseFetchCvdConfigs( + Json::Value& root, const std::string& target_directory, + const std::vector& target_subdirectories) { + CF_EXPECT(InitFetchCvdConfigs(root)); + return CF_EXPECT( + GenerateFetchFlags(root, target_directory, target_subdirectories)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_cvd_parser.h b/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_cvd_parser.h new file mode 100644 index 0000000000..703ff50364 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/fetch_cvd_parser.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +Result> ParseLaunchCvdConfigs(Json::Value& root); + +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_boot_configs.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_boot_configs.cpp new file mode 100644 index 0000000000..0955b0aba1 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_boot_configs.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 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. + */ +#include "host/commands/cvd/parser/instance/cf_boot_configs.h" + +#include +#include + +#include + +#include "common/libs/utils/result.h" +#include "host/commands/assemble_cvd/flags_defaults.h" +#include "host/commands/cvd/parser/cf_configs_common.h" + +namespace cuttlefish { + +Result InitBootConfigs(Json::Value& instances) { + for (auto& instance : instances) { + CF_EXPECT(InitConfig(instance, CF_DEFAULTS_ENABLE_BOOTANIMATION, + {"boot", "enable_bootanimation"})); + CF_EXPECT(InitConfig(instance, CF_DEFAULTS_EXTRA_BOOTCONFIG_ARGS, + {"boot", "extra_bootconfig_args"})); + } + return {}; +} + +Result> GenerateBootFlags( + const Json::Value& instances) { + std::vector result; + result.emplace_back( + CF_EXPECT(Base64EncodeGflag(instances, "extra_bootconfig_args_base64", + {"boot", "extra_bootconfig_args"}))); + result.emplace_back(CF_EXPECT(GenerateGflag( + instances, "enable_bootanimation", {"boot", "enable_bootanimation"}))); + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_boot_configs.h b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_boot_configs.h new file mode 100644 index 0000000000..7e18bf0540 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_boot_configs.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + std::string * 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 + std::string * 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { +Result InitBootConfigs(Json::Value& root); +Result> GenerateBootFlags(const Json::Value& root); +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_disk_configs.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_disk_configs.cpp new file mode 100644 index 0000000000..aad71e6bda --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_disk_configs.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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. + */ +#include "host/commands/cvd/parser/instance/cf_disk_configs.h" + +#include +#include + +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/parser/cf_configs_common.h" + +#define DEFAULT_BLANK_DATA_IMAGE_SIZE "unset" + +namespace cuttlefish { + +Result InitDiskConfigs(Json::Value& instances) { + for (auto& instance : instances) { + CF_EXPECT(InitConfig(instance, DEFAULT_BLANK_DATA_IMAGE_SIZE, + {"disk", "blank_data_image_mb"})); + } + return {}; +} + +Result> GenerateDiskFlags( + const Json::Value& instances) { + std::vector result; + result.emplace_back(CF_EXPECT(GenerateGflag( + instances, "blank_data_image_mb", {"disk", "blank_data_image_mb"}))); + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_disk_configs.h b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_disk_configs.h new file mode 100644 index 0000000000..bdbc5840ec --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_disk_configs.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + std::string * 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 + std::string * 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { +Result InitDiskConfigs(Json::Value& root); +Result> GenerateDiskFlags(const Json::Value& root); + +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_graphics_configs.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_graphics_configs.cpp new file mode 100644 index 0000000000..e636e3d8b4 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_graphics_configs.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022 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. + */ +#include "host/commands/cvd/parser/instance/cf_boot_configs.h" + +#include +#include +#include + +#include "launch_cvd.pb.h" + +#include "common/libs/utils/base64.h" +#include "host/commands/assemble_cvd/flags_defaults.h" +#include "host/commands/cvd/parser/cf_configs_common.h" +#include "host/libs/config/config_utils.h" + +namespace cuttlefish { + +Result InitGraphicsConfigs(Json::Value& instances) { + InitIntConfigSubGroupVector(instances, "graphics", "displays", "width", + CF_DEFAULTS_DISPLAY_WIDTH); + InitIntConfigSubGroupVector(instances, "graphics", "displays", "height", + CF_DEFAULTS_DISPLAY_HEIGHT); + InitIntConfigSubGroupVector(instances, "graphics", "displays", "dpi", + CF_DEFAULTS_DISPLAY_DPI); + InitIntConfigSubGroupVector(instances, "graphics", "displays", + "refresh_rate_hertz", + CF_DEFAULTS_DISPLAY_REFRESH_RATE); + for (auto& instance : instances) { + CF_EXPECT(InitConfig(instance, CF_DEFAULTS_RECORD_SCREEN, + {"graphics", "record_screen"})); + } + return {}; +} + +std::string GenerateDisplayFlag(const Json::Value& instances_json) { + using google::protobuf::TextFormat; + cuttlefish::InstancesDisplays all_instances_displays; + + for (const auto& instance_json : instances_json) { + auto* instance = all_instances_displays.add_instances(); + for (const auto& display_json : instance_json["graphics"]["displays"]) { + auto* display = instance->add_displays(); + display->set_width(display_json["width"].asInt()); + display->set_height(display_json["height"].asInt()); + display->set_dpi(display_json["dpi"].asInt()); + display->set_refresh_rate_hertz( + display_json["refresh_rate_hertz"].asInt()); + } + } + + std::string bin_output; + if (!all_instances_displays.SerializeToString(&bin_output)) { + LOG(ERROR) << "Failed to convert display proto to binary string "; + return std::string(); + } + + std::string base64_output; + if (!cuttlefish::EncodeBase64((void*)bin_output.c_str(), bin_output.size(), + &base64_output)) { + LOG(ERROR) << "Failed to apply EncodeBase64 to binary string "; + return std::string(); + } + return "--displays_binproto=" + base64_output; +} + +Result> GenerateGraphicsFlags( + const Json::Value& instances) { + std::vector result; + result.emplace_back(GenerateDisplayFlag(instances)); + result.emplace_back(CF_EXPECT(GenerateGflag(instances, "record_screen", + {"graphics", "record_screen"}))); + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_graphics_configs.h b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_graphics_configs.h new file mode 100644 index 0000000000..f577fed6a3 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_graphics_configs.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + std::string * 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 + std::string * 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 +#include +#include +#include + +namespace cuttlefish { +Result InitGraphicsConfigs(Json::Value& root); +Result> GenerateGraphicsFlags(const Json::Value& root); +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_security_configs.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_security_configs.cpp new file mode 100644 index 0000000000..7e2a78ac02 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_security_configs.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 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. + */ +#include "host/commands/cvd/parser/instance/cf_security_configs.h" + +#include +#include + +#include + +#include "common/libs/utils/result.h" +#include "host/commands/assemble_cvd/flags_defaults.h" +#include "host/commands/cvd/parser/cf_configs_common.h" + +namespace cuttlefish { + +Result InitSecurityConfigs(Json::Value& instances) { + for (auto& instance : instances) { + CF_EXPECT(InitConfig(instance, CF_DEFAULTS_SERIAL_NUMBER, + {"security", "serial_number"})); + CF_EXPECT(InitConfig(instance, CF_DEFAULTS_USE_RANDOM_SERIAL, + {"security", "use_random_serial"})); + CF_EXPECT(InitConfig(instance, CF_DEFAULTS_GUEST_ENFORCE_SECURITY, + {"security", "guest_enforce_security"})); + } + return {}; +} + +Result> GenerateSecurityFlags( + const Json::Value& instances) { + std::vector result; + result.emplace_back(CF_EXPECT(GenerateGflag(instances, "serial_number", + {"security", "serial_number"}))); + result.emplace_back(CF_EXPECT(GenerateGflag( + instances, "use_random_serial", {"security", "use_random_serial"}))); + result.emplace_back( + CF_EXPECT(GenerateGflag(instances, "guest_enforce_security", + {"security", "guest_enforce_security"}))); + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_security_configs.h b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_security_configs.h new file mode 100644 index 0000000000..c12aeff363 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_security_configs.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + std::string * 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 + std::string * 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { +Result InitSecurityConfigs(Json::Value& root); +Result> GenerateSecurityFlags(const Json::Value& root); +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_streaming_configs.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_streaming_configs.cpp new file mode 100644 index 0000000000..51b5e85f51 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_streaming_configs.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + std::string * 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 + std::string * 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. + */ +#include "host/commands/cvd/parser/instance/cf_streaming_configs.h" + +#include +#include + +#include + +#include "common/libs/utils/result.h" +#include "host/commands/assemble_cvd/flags_defaults.h" +#include "host/commands/cvd/parser/cf_configs_common.h" + +namespace cuttlefish { + +Result InitStreamingConfigs(Json::Value& instances) { + for (auto& instance : instances) { + CF_EXPECT(InitConfig(instance, CF_DEFAULTS_WEBRTC_DEVICE_ID, + {"streaming", "device_id"})); + } + return {}; +} + +Result> GenerateStreamingFlags( + const Json::Value& root) { + std::vector result; + result.emplace_back(CF_EXPECT( + GenerateGflag(root, "webrtc_device_id", {"streaming", "device_id"}))); + return result; +} + +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_streaming_configs.h b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_streaming_configs.h new file mode 100644 index 0000000000..9c784eb1cb --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_streaming_configs.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + std::string * 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 + std::string * 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +Result InitStreamingConfigs(Json::Value& root); +Result> GenerateStreamingFlags( + const Json::Value& root); + +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_vm_configs.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_vm_configs.cpp new file mode 100644 index 0000000000..44bc990ba7 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_vm_configs.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2022 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. + */ +#include "host/commands/cvd/parser/instance/cf_vm_configs.h" + +#include +#include + +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/assemble_cvd/flags_defaults.h" +#include "host/commands/cvd/parser/cf_configs_common.h" + +#define UI_DEFAULTS_MEMORY_MB 2048 + +namespace cuttlefish { +namespace { + +std::string GetVmManagerDefault(Json::Value& instance_vm) { + if (instance_vm.isNull()) { + return "crosvm"; + } + if (instance_vm.isMember("crosvm")) { + return "crosvm"; + } else if (instance_vm.isMember("qemu")) { + return "qemu_cli"; + } else if (instance_vm.isMember("gem5")) { + return "gem5"; + } else { + return "crosvm"; + } +} + +} // namespace + +Result InitVmConfigs(Json::Value& instances) { + for (auto& instance : instances) { + CF_EXPECT(InitConfig(instance, CF_DEFAULTS_CPUS, {"vm", "cpus"})); + CF_EXPECT(InitConfig(instance, UI_DEFAULTS_MEMORY_MB, {"vm", "memory_mb"})); + CF_EXPECT( + InitConfig(instance, CF_DEFAULTS_USE_SDCARD, {"vm", "use_sdcard"})); + CF_EXPECT(InitConfig(instance, CF_DEFAULTS_SETUPWIZARD_MODE, + {"vm", "setupwizard_mode"})); + CF_EXPECT(InitConfig(instance, CF_DEFAULTS_UUID, {"vm", "uuid"})); + CF_EXPECT(InitConfig(instance, GetVmManagerDefault(instance["vm"]), + {"vm", "vm_manager"})); + CF_EXPECT(InitConfig(instance, CF_DEFAULTS_ENABLE_SANDBOX, + {"vm", "crosvm", "enable_sandbox"})); + } + return {}; +} + +std::vector GenerateCustomConfigsFlags( + const Json::Value& instances) { + std::vector result; + for (auto& instance : instances) { + if (instance.isMember("vm") && instance["vm"].isMember("custom_actions")) { + Json::StreamWriterBuilder factory; + std::string mapped_text = + Json::writeString(factory, instance["vm"]["custom_actions"]); + // format json string string to match aosp/2374890 input format + mapped_text = android::base::StringReplace(mapped_text, "\n", "", true); + mapped_text = android::base::StringReplace(mapped_text, "\r", "", true); + mapped_text = + android::base::StringReplace(mapped_text, "\"", "\\\"", true); + std::stringstream buff; + buff << "--custom_actions=" << mapped_text; + result.emplace_back(buff.str()); + } else { + // custom_actions parameter doesn't exist in the configuration file + result.emplace_back("--custom_actions=unset"); + } + } + return result; +} + +Result> GenerateVmFlags(const Json::Value& instances) { + std::vector result; + result.emplace_back( + CF_EXPECT(GenerateGflag(instances, "cpus", {"vm", "cpus"}))); + result.emplace_back( + CF_EXPECT(GenerateGflag(instances, "memory_mb", {"vm", "memory_mb"}))); + result.emplace_back( + CF_EXPECT(GenerateGflag(instances, "use_sdcard", {"vm", "use_sdcard"}))); + result.emplace_back( + CF_EXPECT(GenerateGflag(instances, "vm_manager", {"vm", "vm_manager"}))); + result.emplace_back(CF_EXPECT(GenerateGflag(instances, "setupwizard_mode", + {"vm", "setupwizard_mode"}))); + result.emplace_back( + CF_EXPECT(GenerateGflag(instances, "uuid", {"vm", "uuid"}))); + result.emplace_back(CF_EXPECT(GenerateGflag( + instances, "enable_sandbox", {"vm", "crosvm", "enable_sandbox"}))); + + result = MergeResults(result, GenerateCustomConfigsFlags(instances)); + + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_vm_configs.h b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_vm_configs.h new file mode 100644 index 0000000000..9a626ca345 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_vm_configs.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + std::string * 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 + std::string * 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { +Result InitVmConfigs(Json::Value& root); +Result> GenerateVmFlags(const Json::Value& root); + +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_parser.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_parser.cpp new file mode 100644 index 0000000000..1688190715 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_parser.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 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. + */ +#include "host/commands/cvd/parser/launch_cvd_parser.h" + +#include +#include + +#include + +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "host/commands/assemble_cvd/flags_defaults.h" +#include "host/commands/cvd/parser/cf_configs_common.h" +#include "host/commands/cvd/parser/cf_configs_instances.h" +#include "host/commands/cvd/parser/cf_metrics_configs.h" +#include "host/commands/cvd/parser/launch_cvd_templates.h" + +namespace cuttlefish { +namespace { + +Result> GenerateCfFlags(const Json::Value& root) { + std::vector result; + result.emplace_back(GenerateGflag( + "num_instances", {std::to_string(root["instances"].size())})); + result.emplace_back(GenerateGflag( + "netsim_bt", {CF_EXPECT(GetValue(root, {"netsim_bt"}))})); + result.emplace_back(GenerateGflag( + "netsim_uwb", {CF_EXPECT(GetValue(root, {"netsim_uwb"}))})); + result = MergeResults(result, CF_EXPECT(GenerateMetricsFlags(root))); + result = MergeResults(result, + CF_EXPECT(GenerateInstancesFlags(root["instances"]))); + return result; +} + +Result InitCvdConfigs(Json::Value& root) { + CF_EXPECT(InitConfig(root, CF_DEFAULTS_NETSIM_BT, {"netsim_bt"})); + CF_EXPECT(InitConfig(root, CF_DEFAULTS_NETSIM_UWB, {"netsim_uwb"})); + CF_EXPECT(InitMetricsConfigs(root)); + CF_EXPECT(InitInstancesConfigs(root["instances"])); + return {}; +} + +} // namespace + +Result> ParseLaunchCvdConfigs(Json::Value& root) { + ExtractLaunchTemplates(root["instances"]); + CF_EXPECT(InitCvdConfigs(root)); + return GenerateCfFlags(root); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_parser.h b/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_parser.h new file mode 100644 index 0000000000..703ff50364 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_parser.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +Result> ParseLaunchCvdConfigs(Json::Value& root); + +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_templates.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_templates.cpp new file mode 100644 index 0000000000..7aa6adde3a --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_templates.cpp @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2023 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. + */ +#include + +#include +#include + +#include "common/libs/utils/json.h" +#include "host/commands/cvd/parser/cf_configs_common.h" + +namespace cuttlefish { + +enum class ConfigTemplate { + PHONE, + TABLET, + TV, + WEARABLE, + AUTO, + SLIM, + GO, + FOLDABLE, + UNKNOWN, +}; + +static std::map kSupportedTemplatesKeyMap = { + {"phone", ConfigTemplate::PHONE}, {"tablet", ConfigTemplate::TABLET}, + {"tv", ConfigTemplate::TV}, {"wearable", ConfigTemplate::WEARABLE}, + {"auto", ConfigTemplate::AUTO}, {"slim", ConfigTemplate::SLIM}, + {"go", ConfigTemplate::GO}, {"foldable", ConfigTemplate::FOLDABLE}}; + +// Definition of phone instance template in Json format +static const char* kPhoneInstanceTemplate = R""""( +{ + "vm": { + "memory_mb": 4096 + }, + "graphics":{ + "displays":[ + { + "width": 720, + "height": 1280, + "dpi": 320 + } + ] + } +} + )""""; + +// Definition of tablet instance template in Json format +static const char* kTabletInstanceTemplate = R""""( +{ + "vm": { + "memory_mb": 4096 + }, + "graphics":{ + "displays":[ + { + "width": 2560, + "height": 1800, + "dpi": 320 + } + ] + } +} + )""""; + +// Definition of tablet instance template in Json format +static const char* kTvInstanceTemplate = R""""( +{ + "vm": { + "memory_mb": 2048 + }, + "graphics":{ + "displays":[ + { + "width": 1920, + "height": 1080, + "dpi": 213 + } + ] + } +} + )""""; + +// Definition of tablet instance template in Json format +static const char* kWearableInstanceTemplate = R""""( +{ + "vm": { + "memory_mb": 1536, + "use_sdcard" : false + }, + "graphics":{ + "displays":[ + { + "width": 450, + "height": 450, + "dpi": 320 + } + ] + } +} + )""""; + +// Definition of auto instance template in Json format +static const char* kAutoInstanceTemplate = R""""( +{ + "vm": { + "memory_mb": 4096 + }, + "graphics":{ + "displays":[ + { + "width": 1080, + "height": 600, + "dpi": 120 + }, + { + "width": 400, + "height": 600, + "dpi": 120 + } + ] + } +} + )""""; + +// Definition of auto instance template in Json format +static const char* kSlimInstanceTemplate = R""""( +{ + "vm": { + "memory_mb": 2048, + "use_sdcard" : false + }, + "graphics":{ + "displays":[ + { + "width": 720, + "height": 1280, + "dpi": 320 + } + ] + } +} + )""""; + +// Definition of go instance template in Json format +static const char* kGoInstanceTemplate = R""""( +{ + "vm": { + "memory_mb": 2048 + }, + "graphics":{ + "displays":[ + { + "width": 720, + "height": 1280, + "dpi": 320 + } + ] + } +} + )""""; + +static const char* kFoldableInstanceTemplate = R""""( +{ + "vm": { + "memory_mb": 4096, + "custom_actions" : [ + { + "device_states": [ + { + "lid_switch_open": false, + "hinge_angle_value": 0 + } + ], + "button":{ + "command":"device_state_closed", + "title":"Device State Closed", + "icon_name":"smartphone" + } + }, + { + "device_states": [ + { + "lid_switch_open": true, + "hinge_angle_value": 90 + } + ], + "button":{ + "command":"device_state_half_opened", + "title":"Device State Half-Opened", + "icon_name":"laptop" + } + }, + { + "device_states": [ + { + "lid_switch_open": true, + "hinge_angle_value": 180 + } + ], + "button":{ + "command":"device_state_opened", + "title":"Device State Opened", + "icon_name":"tablet" + } + } + ] + }, + "graphics":{ + "displays":[ + { + "width": 1768, + "height": 2208, + "dpi": 374 + }, + { + "width": 832, + "height": 2268, + "dpi": 387 + } + ] + } +} + )""""; + +Json::Value ExtractJsonTemplate(const Json::Value& instance, + const char* template_string) { + std::string json_text(template_string); + Json::Value result; + + Json::Reader reader; + reader.parse(json_text, result); + MergeTwoJsonObjs(result, instance); + return result; +} + +Json::Value ExtractInstaneTemplate(const Json::Value& instance) { + std::string instance_template = instance["@import"].asString(); + ConfigTemplate selected_template = + kSupportedTemplatesKeyMap.at(instance_template); + + Json::Value result; + + switch (selected_template) { + case ConfigTemplate::PHONE: + // Extract phone instance configs from input template + result = ExtractJsonTemplate(instance, kPhoneInstanceTemplate); + break; + case ConfigTemplate::TABLET: + // Extract tablet instance configs from input template + result = ExtractJsonTemplate(instance, kTabletInstanceTemplate); + break; + case ConfigTemplate::TV: + // Extract tv instance configs from input template + result = ExtractJsonTemplate(instance, kTvInstanceTemplate); + break; + case ConfigTemplate::WEARABLE: + // Extract wearable instance configs from input template + result = ExtractJsonTemplate(instance, kWearableInstanceTemplate); + break; + case ConfigTemplate::AUTO: + // Extract auto instance configs from input template + result = ExtractJsonTemplate(instance, kAutoInstanceTemplate); + break; + case ConfigTemplate::SLIM: + // Extract slim instance configs from input template + result = ExtractJsonTemplate(instance, kSlimInstanceTemplate); + break; + case ConfigTemplate::GO: + // Extract go instance configs from input template + result = ExtractJsonTemplate(instance, kGoInstanceTemplate); + break; + case ConfigTemplate::FOLDABLE: + // Extract foldable instance configs from input template + result = ExtractJsonTemplate(instance, kFoldableInstanceTemplate); + break; + + default: + // handle unsupported @import flag values + result = instance; + break; + } + + return result; +} + +void ExtractLaunchTemplates(Json::Value& root) { + int num_instances = root.size(); + for (unsigned int i = 0; i < num_instances; i++) { + // Validate @import flag values are supported or not + if (root[i].isMember("@import")) { + // Extract instance configs from input template and override current + // instance + root[i] = ExtractInstaneTemplate(root[i]); + } + } +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_templates.h b/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_templates.h new file mode 100644 index 0000000000..718667bae9 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_templates.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 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 +#include + +namespace cuttlefish { + +void ExtractLaunchTemplates(Json::Value& root); + +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/load_configs_parser.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/load_configs_parser.cpp new file mode 100644 index 0000000000..f40ea105c5 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/load_configs_parser.cpp @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/parser/load_configs_parser.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common/libs/utils/files.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/fetch/fetch_cvd.h" +#include "host/commands/cvd/parser/cf_configs_common.h" +#include "host/commands/cvd/parser/cf_flags_validator.h" +#include "host/commands/cvd/parser/fetch_config_parser.h" +#include "host/commands/cvd/parser/launch_cvd_parser.h" +#include "host/commands/cvd/parser/selector_parser.h" + +namespace cuttlefish { +namespace { + +constexpr std::string_view kOverrideSeparator = ":"; +constexpr std::string_view kCredentialSourceOverride = + "fetch.credential_source"; + +bool IsLocalBuild(std::string path) { + return android::base::StartsWith(path, "/"); +} + +Flag GflagsCompatFlagOverride(const std::string& name, + std::vector& values) { + return GflagsCompatFlag(name) + .Getter([&values]() { return android::base::Join(values, ','); }) + .Setter([&values](const FlagMatch& match) -> Result { + std::size_t separator_index = match.value.find(kOverrideSeparator); + CF_EXPECTF(separator_index != std::string::npos, + "Unable to find separator \"{}\" in input \"{}\"", + kOverrideSeparator, match.value); + auto result = + Override{.config_path = match.value.substr(0, separator_index), + .new_value = match.value.substr(separator_index + 1)}; + CF_EXPECTF(!result.config_path.empty(), + "Config path before the separator \"{}\" cannot be empty in " + "input \"{}\"", + kOverrideSeparator, match.value); + CF_EXPECTF(!result.new_value.empty(), + "New value after the separator \"{}\" cannot be empty in " + "input \"{}\"", + kOverrideSeparator, match.value); + CF_EXPECTF(result.config_path.front() != '.' && + result.config_path.back() != '.', + "Config path \"{}\" must not start or end with dot", + result.config_path); + CF_EXPECTF(result.config_path.find("..") == std::string::npos, + "Config path \"{}\" cannot contain two consecutive dots", + result.config_path); + values.emplace_back(result); + return {}; + }); +} + +// TODO(moelsherif): expand this enum in the future to support more types ( +// double , float , etc) if neeeded +enum ArgValueType { UINTEGER, BOOLEAN, TEXT }; + +bool IsUnsignedInteger(const std::string& str) { + return !str.empty() && std::all_of(str.begin(), str.end(), + [](char c) { return std::isdigit(c); }); +} + +ArgValueType GetArgValueType(const std::string& str) { + if (IsUnsignedInteger(str)) { + return UINTEGER; + } + + if (str == "true" || str == "false") { + return BOOLEAN; + } + + // Otherwise, treat the string as text + return TEXT; +} + +Json::Value OverrideToJson(const std::string& key, + const std::string& leafValue) { + std::stack levels; + std::stringstream ks(key); + std::string token; + while (std::getline(ks, token, '.')) { + levels.push(token); + } + + // assign the leaf value based on the type of input value + Json::Value leaf; + if (GetArgValueType(leafValue) == UINTEGER) { + std::uint32_t leaf_val{}; + if (!android::base::ParseUint(leafValue, &leaf_val)) { + LOG(ERROR) << "Failed to parse unsigned integer " << leafValue; + return Json::Value::null; + }; + leaf = leaf_val; + } else if (GetArgValueType(leafValue) == BOOLEAN) { + leaf = (leafValue == "true"); + } else { + leaf = leafValue; + } + + while (!levels.empty()) { + Json::Value curr; + std::string index = levels.top(); + + if (GetArgValueType(index) == UINTEGER) { + std::uint32_t index_val{}; + if (!android::base::ParseUint(index, &index_val)) { + LOG(ERROR) << "Failed to parse unsigned integer " << index; + return Json::Value::null; + } + curr[index_val] = leaf; + } else { + curr[index] = leaf; + } + + leaf = curr; + levels.pop(); + } + + return leaf; +} + +std::vector GetFlagsVector(LoadFlags& load_flags) { + std::vector flags; + flags.emplace_back( + GflagsCompatFlag("credential_source", load_flags.credential_source)); + flags.emplace_back( + GflagsCompatFlag("base_directory", load_flags.base_dir) + .Help("Parent directory for artifacts and runtime files. Defaults to " + "/tmp/cvd//.")); + flags.emplace_back(GflagsCompatFlagOverride("override", load_flags.overrides) + .Help("Use --override=: " + "to override config values")); + return flags; +} + +std::string DefaultBaseDir() { + auto time = std::chrono::system_clock::now().time_since_epoch().count(); + std::stringstream ss; + ss << "/tmp/cvd/" << getuid() << "/" << time; + return ss.str(); +} + +void MakeAbsolute(std::string& path, const std::string& working_dir) { + if (path.size() > 0 && path[0] == '/') { + return; + } + path.insert(0, working_dir + "/"); +} + +Result ParseJsonFile(const std::string& file_path) { + CF_EXPECTF(FileExists(file_path), + "Provided file \"{}\" to cvd command does not exist", file_path); + + std::string file_content; + using android::base::ReadFileToString; + CF_EXPECTF(ReadFileToString(file_path.c_str(), &file_content, + /* follow_symlinks */ true), + "Failed to read file \"{}\"", file_path); + auto root = CF_EXPECTF(ParseJson(file_content), + "Failed parsing file \"{}\" as JSON", file_path); + return root; +} + +Result> GetConfiguredSystemImagePaths( + Json::Value& root) { + return CF_EXPECTF( + GetArrayValues(root["instances"], {"disk", "default_build"}), + "Instance is missing required Image path", ""); +} + +std::optional GetConfiguredSystemHostPath(Json::Value& root) { + auto result = GetValue(root, {"common", "host_package"}); + if (result.ok()) { + return std::optional{*result}; + } + return std::nullopt; +} + +Result GetOverriddenConfig( + const std::string& config_path, + const std::vector& override_flags) { + Json::Value result = CF_EXPECT(ParseJsonFile(config_path)); + + if (override_flags.size() > 0) { + for (const auto& flag : override_flags) { + MergeTwoJsonObjs(result, + OverrideToJson(flag.config_path, flag.new_value)); + } + } + + return result; +} + +Result GenerateLoadDirectories( + const std::string& parent_directory, + std::vector& system_image_path_configs, + std::optional system_host_path, const int num_instances) { + CF_EXPECT_GT(num_instances, 0, "No instances in config to load"); + auto result = LoadDirectories{ + .target_directory = parent_directory + "/artifacts", + .launch_home_directory = parent_directory + "/home", + }; + + std::vector system_image_directories; + int num_remote = 0; + for (int i = 0; i < num_instances; i++) { + const std::string instance_build_path = system_image_path_configs[i]; + CF_EXPECT_EQ(system_image_path_configs.size(), num_instances, + "Number of instances is inconsistent"); + + auto target_subdirectory = std::to_string(i); + result.target_subdirectories.emplace_back(target_subdirectory); + if (IsLocalBuild(instance_build_path)) { + system_image_directories.emplace_back(instance_build_path); + } else { + const std::string dir = + result.target_directory + "/" + target_subdirectory; + system_image_directories.emplace_back(dir); + num_remote++; + } + LOG(INFO) << "Instance " << i << " directory is " + << system_image_directories.back(); + } + + CF_EXPECT(system_host_path || num_remote > 0, + "Host tools path must be provided when using only local artifacts"); + + if (system_host_path && IsLocalBuild(system_host_path.value())) { + // If config specifies a host tools path, we use this. + result.host_package_directory = system_host_path.value(); + } else { + result.host_package_directory = + result.target_directory + "/" + kHostToolsSubdirectory; + } + + result.system_image_directory_flag = + "--system_image_dir=" + + android::base::Join(system_image_directories, ','); + return result; +} + +Result ParseCvdConfigs(Json::Value& root, + const LoadDirectories& load_directories) { + CF_EXPECT(ValidateCfConfigs(root), "Loaded Json validation failed"); + return CvdFlags{.launch_cvd_flags = CF_EXPECT(ParseLaunchCvdConfigs(root)), + .selector_flags = CF_EXPECT(ParseSelectorConfigs(root)), + .fetch_cvd_flags = CF_EXPECT(ParseFetchCvdConfigs( + root, load_directories.target_directory, + load_directories.target_subdirectories)), + .load_directories = load_directories}; +} + +} // namespace + +std::ostream& operator<<(std::ostream& out, const Override& override) { + fmt::print(out, "(config_path=\"{}\", new_value=\"{}\")", + override.config_path, override.new_value); + return out; +} + +Result GetFlags(std::vector& args, + const std::string& working_directory) { + LoadFlags load_flags; + auto flags = GetFlagsVector(load_flags); + CF_EXPECT(ConsumeFlags(flags, args)); + CF_EXPECT( + args.size() > 0, + "No arguments provided to cvd command, please provide path to json file"); + + if (load_flags.base_dir.empty()) { + load_flags.base_dir = DefaultBaseDir(); + } + MakeAbsolute(load_flags.base_dir, working_directory); + + load_flags.config_path = args.front(); + MakeAbsolute(load_flags.config_path, working_directory); + + if (!load_flags.credential_source.empty()) { + for (const auto& flag : load_flags.overrides) { + CF_EXPECT(!android::base::StartsWith(flag.config_path, + kCredentialSourceOverride), + "Specifying both --override=fetch.credential_source and the " + "--credential_source flag is not allowed."); + } + load_flags.overrides.emplace_back( + Override{.config_path = std::string(kCredentialSourceOverride), + .new_value = load_flags.credential_source}); + } + return load_flags; +} + +Result GetCvdFlags(const LoadFlags& flags) { + Json::Value json_configs = + CF_EXPECT(GetOverriddenConfig(flags.config_path, flags.overrides)); + + std::vector system_image_path_configs = + CF_EXPECT(GetConfiguredSystemImagePaths(json_configs)); + std::optional host_package_dir = + GetConfiguredSystemHostPath(json_configs); + + const auto load_directories = CF_EXPECT(GenerateLoadDirectories( + flags.base_dir, system_image_path_configs, host_package_dir, + json_configs["instances"].size())); + return CF_EXPECT(ParseCvdConfigs(json_configs, load_directories), + "Parsing json configs failed"); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/load_configs_parser.h b/base/cvd/cuttlefish/host/commands/cvd/parser/load_configs_parser.h new file mode 100644 index 0000000000..15d1f573cf --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/load_configs_parser.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/parser/fetch_config_parser.h" + +namespace cuttlefish { + +struct LoadDirectories { + std::string target_directory; + std::vector target_subdirectories; + std::string launch_home_directory; + std::string host_package_directory; + std::string system_image_directory_flag; +}; + +struct CvdFlags { + std::vector launch_cvd_flags; + std::vector selector_flags; + std::vector fetch_cvd_flags; + LoadDirectories load_directories; +}; + +struct Override { + std::string config_path; + std::string new_value; +}; + +std::ostream& operator<<(std::ostream& out, const Override& override); + +struct LoadFlags { + std::vector overrides; + std::string config_path; + std::string credential_source; + std::string base_dir; +}; + +Result GetFlags(std::vector& args, + const std::string& working_directory); + +Result GetCvdFlags(const LoadFlags& flags); + +}; // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/selector_parser.cpp b/base/cvd/cuttlefish/host/commands/cvd/parser/selector_parser.cpp new file mode 100644 index 0000000000..12a2e384eb --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/selector_parser.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 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. + */ +#include "host/commands/cvd/parser/selector_parser.h" + +#include +#include + +#include + +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/parser/cf_configs_common.h" +#include "host/commands/cvd/selector/selector_constants.h" + +namespace cuttlefish { + +Result> ParseSelectorConfigs(Json::Value& root) { + std::string instance_name_flag = + CF_EXPECT(GenerateGflag(root["instances"], "instance_name", {"name"})); + + if (!HasValue(root, {"common", "group_name"})) { + return {{instance_name_flag}}; + } + + return {{instance_name_flag, + GenerateGflag("group_name", {CF_EXPECT(GetValue( + root, {"common", "group_name"}))})}}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/parser/selector_parser.h b/base/cvd/cuttlefish/host/commands/cvd/parser/selector_parser.h new file mode 100644 index 0000000000..7da8685c83 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/parser/selector_parser.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +Result> ParseSelectorConfigs(Json::Value& root); + +}; // namespace cuttlefish + diff --git a/base/cvd/cuttlefish/host/commands/cvd/request_context.cpp b/base/cvd/cuttlefish/host/commands/cvd/request_context.cpp new file mode 100644 index 0000000000..6f6ab41465 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/request_context.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/request_context.h" + +#include +#include + +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/instance_lock.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/acloud_command.h" +#include "host/commands/cvd/server_command/acloud_mixsuperimage.h" +#include "host/commands/cvd/server_command/acloud_translator.h" +#include "host/commands/cvd/server_command/cmd_list.h" +#include "host/commands/cvd/server_command/display.h" +#include "host/commands/cvd/server_command/env.h" +#include "host/commands/cvd/server_command/fetch.h" +#include "host/commands/cvd/server_command/fleet.h" +#include "host/commands/cvd/server_command/generic.h" +#include "host/commands/cvd/server_command/handler_proxy.h" +#include "host/commands/cvd/server_command/help.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" +#include "host/commands/cvd/server_command/lint.h" +#include "host/commands/cvd/server_command/load_configs.h" +#include "host/commands/cvd/server_command/power.h" +#include "host/commands/cvd/server_command/reset.h" +#include "host/commands/cvd/server_command/restart.h" +#include "host/commands/cvd/server_command/serial_launch.h" +#include "host/commands/cvd/server_command/serial_preset.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/shutdown.h" +#include "host/commands/cvd/server_command/snapshot.h" +#include "host/commands/cvd/server_command/start.h" +#include "host/commands/cvd/server_command/status.h" +#include "host/commands/cvd/server_command/subcmd.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" +#include "host/commands/cvd/server_command/try_acloud.h" +#include "host/commands/cvd/server_command/version.h" +#include "host/libs/web/android_build_api.h" + +namespace cuttlefish { + +RequestContext::RequestContext( + CvdServer& cvd_server, InstanceLockFileManager& instance_lockfile_manager, + InstanceManager& instance_manager, BuildApi& build_api, + HostToolTargetManager& host_tool_target_manager, + std::atomic& acloud_translator_optout) + : cvd_server_(cvd_server), + instance_lockfile_manager_(instance_lockfile_manager), + instance_manager_(instance_manager), + build_api_(build_api), + host_tool_target_manager_(host_tool_target_manager), + command_sequence_executor_(this->request_handlers_), + acloud_translator_optout_(acloud_translator_optout) { + request_handlers_.emplace_back(NewAcloudCommand(command_sequence_executor_)); + request_handlers_.emplace_back(NewAcloudMixSuperImageCommand()); + request_handlers_.emplace_back( + NewAcloudTranslatorCommand(acloud_translator_optout_)); + request_handlers_.emplace_back( + NewCvdCmdlistHandler(command_sequence_executor_)); + request_handlers_.emplace_back( + NewCvdDisplayCommandHandler(instance_manager_, subprocess_waiter_)); + request_handlers_.emplace_back( + NewCvdEnvCommandHandler(instance_manager_, subprocess_waiter_)); + request_handlers_.emplace_back(NewCvdFetchCommandHandler(subprocess_waiter_)); + request_handlers_.emplace_back(NewCvdFleetCommandHandler( + instance_manager_, subprocess_waiter_, host_tool_target_manager_)); + request_handlers_.emplace_back(NewCvdGenericCommandHandler( + instance_lockfile_manager_, instance_manager_, subprocess_waiter_, + host_tool_target_manager_)); + request_handlers_.emplace_back( + NewCvdServerHandlerProxy(command_sequence_executor_)); + request_handlers_.emplace_back(NewCvdHelpHandler(this->request_handlers_)); + request_handlers_.emplace_back(NewLintCommand()); + request_handlers_.emplace_back( + NewLoadConfigsCommand(command_sequence_executor_)); + request_handlers_.emplace_back(NewCvdDevicePowerCommandHandler( + host_tool_target_manager_, instance_manager_, subprocess_waiter_)); + request_handlers_.emplace_back(NewCvdResetCommandHandler()); + request_handlers_.emplace_back( + NewCvdRestartHandler(build_api_, cvd_server_, instance_manager_)); + request_handlers_.emplace_back( + NewSerialLaunchCommand(command_sequence_executor_, lock_file_manager_)); + request_handlers_.emplace_back(NewSerialPreset(command_sequence_executor_)); + request_handlers_.emplace_back( + NewCvdShutdownHandler(cvd_server_, instance_manager_)); + request_handlers_.emplace_back(NewCvdSnapshotCommandHandler( + instance_manager_, subprocess_waiter_, host_tool_target_manager_)); + request_handlers_.emplace_back( + NewCvdStartCommandHandler(instance_manager_, host_tool_target_manager_, + command_sequence_executor_)); + request_handlers_.emplace_back( + NewCvdStatusCommandHandler(instance_manager_, host_tool_target_manager_)); + request_handlers_.emplace_back( + NewTryAcloudCommand(acloud_translator_optout_)); + request_handlers_.emplace_back(NewCvdVersionHandler()); +} + +Result RequestContext::Handler( + const RequestWithStdio& request) { + return RequestHandler(request, request_handlers_); +} + +Result RequestHandler( + const RequestWithStdio& request, + const std::vector>& handlers) { + Result response; + std::vector compatible_handlers; + for (auto& handler : handlers) { + if (CF_EXPECT(handler->CanHandle(request))) { + compatible_handlers.push_back(handler.get()); + } + } + CF_EXPECT(compatible_handlers.size() == 1, + "Expected exactly one handler for message, found " + << compatible_handlers.size()); + return compatible_handlers[0]; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/request_context.h b/base/cvd/cuttlefish/host/commands/cvd/request_context.h new file mode 100644 index 0000000000..cbc2200990 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/request_context.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/instance_lock.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" +#include "host/libs/web/android_build_api.h" + +namespace cuttlefish { + +class CvdServer; + +class RequestContext { + public: + RequestContext(CvdServer& cvd_server, + InstanceLockFileManager& instance_lockfile_manager, + InstanceManager& instance_manager, BuildApi& build_api, + HostToolTargetManager& host_tool_target_manager, + std::atomic& acloud_translator_optout); + + Result Handler(const RequestWithStdio& request); + + private: + void InstantiateHandlers(); + + CvdServer& cvd_server_; + std::vector> request_handlers_; + InstanceLockFileManager& instance_lockfile_manager_; + InstanceManager& instance_manager_; + BuildApi& build_api_; + SubprocessWaiter subprocess_waiter_; + InstanceLockFileManager lock_file_manager_; + HostToolTargetManager& host_tool_target_manager_; + CommandSequenceExecutor command_sequence_executor_; + std::atomic& acloud_translator_optout_; +}; + +Result RequestHandler( + const RequestWithStdio& request, + const std::vector>& handlers); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/reset_client_utils.cpp b/base/cvd/cuttlefish/host/commands/cvd/reset_client_utils.cpp new file mode 100644 index 0000000000..34f4a620a4 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/reset_client_utils.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/reset_client_utils.h" + +#include + +#include +#include +#include // std::setw +#include // std::endl +#include +#include +#include + +#include +#include +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/proc_file_utils.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/reset_client_utils.h" +#include "host/commands/cvd/run_cvd_proc_collector.h" +#include "host/commands/cvd/run_server.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { + +Result RunCvdProcessManager::Get() { + RunCvdProcessCollector run_cvd_collector = + CF_EXPECT(RunCvdProcessCollector::Get()); + RunCvdProcessManager run_cvd_processes_manager(std::move(run_cvd_collector)); + return run_cvd_processes_manager; +} + +RunCvdProcessManager::RunCvdProcessManager(RunCvdProcessCollector&& collector) + : run_cvd_process_collector_(std::move(collector)) {} + +static Command CreateStopCvdCommand(const std::string& stopper_path, + const cvd_common::Envs& envs, + const cvd_common::Args& args) { + Command command(cpp_basename(stopper_path)); + command.SetExecutable(stopper_path); + for (const auto& arg : args) { + command.AddParameter(arg); + } + for (const auto& [key, value] : envs) { + command.UnsetFromEnvironment(key); + command.AddEnvironmentVariable(key, value); + } + return command; +} + +Result RunCvdProcessManager::RunStopCvd(const GroupProcInfo& group_info, + bool clear_runtime_dirs) { + const auto& stopper_path = group_info.stop_cvd_path_; + int ret_code = 0; + cvd_common::Envs stop_cvd_envs; + stop_cvd_envs["HOME"] = group_info.home_; + if (group_info.android_host_out_) { + stop_cvd_envs[kAndroidHostOut] = group_info.android_host_out_.value(); + stop_cvd_envs[kAndroidSoongHostOut] = group_info.android_host_out_.value(); + } else { + auto android_host_out = StringFromEnv( + kAndroidHostOut, + android::base::Dirname(android::base::GetExecutableDirectory())); + stop_cvd_envs[kAndroidHostOut] = android_host_out; + stop_cvd_envs[kAndroidSoongHostOut] = android_host_out; + } + + if (clear_runtime_dirs) { + Command first_stop_cvd = CreateStopCvdCommand( + stopper_path, stop_cvd_envs, {"--clear_instance_dirs=true"}); + LOG(ERROR) << "Running HOME=" << stop_cvd_envs.at("HOME") << " " + << stopper_path << " --clear_instance_dirs=true"; + std::string stdout_str; + std::string stderr_str; + ret_code = RunWithManagedStdio(std::move(first_stop_cvd), nullptr, + std::addressof(stdout_str), + std::addressof(stderr_str)); + // TODO(kwstephenkim): deletes manually if `stop_cvd --clear_instance_dirs` + // failed. + } + if (!clear_runtime_dirs || ret_code != 0) { + if (clear_runtime_dirs) { + LOG(ERROR) << "Failed to run " << stopper_path + << " --clear_instance_dirs=true"; + LOG(ERROR) << "Perhaps --clear_instance_dirs is not taken."; + LOG(ERROR) << "Trying again without it"; + } + Command second_stop_cvd = + CreateStopCvdCommand(stopper_path, stop_cvd_envs, {}); + LOG(ERROR) << "Running HOME=" << stop_cvd_envs.at("HOME") << " " + << stopper_path; + std::string stdout_str; + std::string stderr_str; + ret_code = RunWithManagedStdio(std::move(second_stop_cvd), nullptr, + std::addressof(stdout_str), + std::addressof(stderr_str)); + } + if (ret_code != 0) { + std::stringstream error; + error << "HOME=" << group_info.home_ + << group_info.stop_cvd_path_ + " Failed."; + return CF_ERR(error.str()); + } + LOG(ERROR) << "\"" << stopper_path << " successfully " + << "\" stopped instances at HOME=" << group_info.home_; + return {}; +} + +Result RunCvdProcessManager::RunStopCvdAll(bool cvd_server_children_only, + bool clear_instance_dirs) { + for (const auto& group_info : run_cvd_process_collector_.CfGroups()) { + if (cvd_server_children_only && !group_info.is_cvd_server_started_) { + continue; + } + auto stop_cvd_result = RunStopCvd(group_info, clear_instance_dirs); + if (!stop_cvd_result.ok()) { + LOG(ERROR) << stop_cvd_result.error().FormatForEnv(); + continue; + } + } + return {}; +} + +static bool IsStillRunCvd(const pid_t pid) { + std::string pid_dir = fmt::format("/proc/{}", pid); + if (!FileExists(pid_dir)) { + return false; + } + auto owner_result = OwnerUid(pid); + if (!owner_result.ok() || (getuid() != *owner_result)) { + return false; + } + auto extract_proc_info_result = ExtractProcInfo(pid); + if (!extract_proc_info_result.ok()) { + return false; + } + return (cpp_basename(extract_proc_info_result->actual_exec_path_) == + "run_cvd"); +} + +Result RunCvdProcessManager::SendSignal(bool cvd_server_children_only, + const GroupProcInfo& group_info) { + std::vector failed_pids; + if (cvd_server_children_only && !group_info.is_cvd_server_started_) { + return {}; + } + for (const auto& [unused, instance] : group_info.instances_) { + for (const auto parent_run_cvd_pid : instance.parent_run_cvd_pids_) { + if (!IsStillRunCvd(parent_run_cvd_pid)) { + continue; + } + if (kill(parent_run_cvd_pid, SIGKILL) == 0) { + LOG(VERBOSE) << "Successfully SIGKILL'ed " << parent_run_cvd_pid; + } else { + failed_pids.push_back(parent_run_cvd_pid); + } + } + } + CF_EXPECTF(failed_pids.empty(), + "Some run_cvd processes were not killed: [{}]", + fmt::join(failed_pids, ", ")); + return {}; +} + +Result RunCvdProcessManager::DeleteLockFile( + bool cvd_server_children_only, const GroupProcInfo& group_info) { + const std::string lock_dir = "/tmp/acloud_cvd_temp"; + std::string lock_file_prefix = lock_dir; + lock_file_prefix.append("/local-instance-"); + + if (cvd_server_children_only && !group_info.is_cvd_server_started_) { + return {}; + } + bool all_success = true; + const auto& instances = group_info.instances_; + for (const auto& [id, _] : instances) { + std::stringstream lock_file_path_stream; + lock_file_path_stream << lock_file_prefix << id << ".lock"; + auto lock_file_path = lock_file_path_stream.str(); + if (FileExists(lock_file_path) && !DirectoryExists(lock_file_path)) { + if (RemoveFile(lock_file_path)) { + LOG(DEBUG) << "Reset the lock file: " << lock_file_path; + } else { + all_success = false; + LOG(ERROR) << "Failed to remove the lock file: " << lock_file_path; + } + } + } + CF_EXPECT(all_success == true); + return {}; +} + +Result KillAllCuttlefishInstances(const DeviceClearOptions& options) { + RunCvdProcessManager manager = CF_EXPECT(RunCvdProcessManager::Get()); + CF_EXPECT(manager.KillAllCuttlefishInstances(options.cvd_server_children_only, + options.clear_instance_dirs)); + return {}; +} + +Result KillCvdServerProcess() { + std::vector self_exe_pids = + CF_EXPECT(CollectPidsByArgv0(kServerExecPath)); + if (self_exe_pids.empty()) { + LOG(ERROR) << "cvd server is not running."; + return {}; + } + std::vector cvd_server_pids; + /** + * Finds processes whose executable path is kServerExecPath, and + * that is owned by getuid(), and that has the "INTERNAL_server_fd" + * in the arguments list. + */ + for (const auto pid : self_exe_pids) { + auto proc_info_result = ExtractProcInfo(pid); + if (!proc_info_result.ok()) { + LOG(ERROR) << "Failed to extract process info for pid " << pid; + continue; + } + auto owner_uid_result = OwnerUid(pid); + if (!owner_uid_result.ok()) { + LOG(ERROR) << "Failed to find the uid for pid " << pid; + continue; + } + if (getuid() != *owner_uid_result) { + continue; + } + for (const auto& arg : proc_info_result->args_) { + if (Contains(arg, kInternalServerFd)) { + cvd_server_pids.push_back(pid); + break; + } + } + } + if (cvd_server_pids.empty()) { + LOG(ERROR) + << "Cvd server process is not found. Perhaps, it is not running."; + return {}; + } + if (cvd_server_pids.size() > 1) { + LOG(ERROR) << "There are " << cvd_server_pids.size() << " server processes " + << "running while it should be up to 1."; + } + for (const auto pid : cvd_server_pids) { + auto kill_ret = kill(pid, SIGKILL); + if (kill_ret == 0) { + LOG(ERROR) << "Cvd server process #" << pid << " is killed."; + } else { + LOG(ERROR) << "kill(" << pid << ", SIGKILL) failed."; + } + } + return {}; +} + +Result RunCvdProcessManager::KillAllCuttlefishInstances( + bool cvd_server_children_only, bool clear_runtime_dirs) { + auto stop_cvd_result = + RunStopCvdAll(cvd_server_children_only, clear_runtime_dirs); + if (!stop_cvd_result.ok()) { + LOG(ERROR) << stop_cvd_result.error().FormatForEnv(); + } + for (const auto& group_info : run_cvd_process_collector_.CfGroups()) { + auto result = ForcefullyStopGroup(cvd_server_children_only, group_info); + if (!result.ok()) { + LOG(ERROR) << result.error().FormatForEnv(); + } + } + return {}; +} + +Result RunCvdProcessManager::ForcefullyStopGroup( + bool cvd_server_children_only, const uid_t id) { + auto groups_info = run_cvd_process_collector_.CfGroups(); + for (const auto& group_info : groups_info) { + if (!Contains(group_info.instances_, static_cast(id))) { + continue; + } + CF_EXPECT(ForcefullyStopGroup(cvd_server_children_only, group_info)); + } + // run_cvd is not created yet as.. ctrl+C was in assembly phase, etc + return {}; +} + +Result RunCvdProcessManager::ForcefullyStopGroup( + bool cvd_server_children_only, const GroupProcInfo& group) { + if (cvd_server_children_only && !group.is_cvd_server_started_) { + return {}; + } + CF_EXPECTF(SendSignal(cvd_server_children_only, group), + "Tried SIGKILL to a group of run_cvd processes rooted at " + "HOME={} but failed", + group.home_); + CF_EXPECTF(DeleteLockFile(cvd_server_children_only, group), + "Tried to delete instance lock file for the group rooted at " + "HOME={} but failed.", + group.home_); + return {}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/reset_client_utils.h b/base/cvd/cuttlefish/host/commands/cvd/reset_client_utils.h new file mode 100644 index 0000000000..b2e96a7559 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/reset_client_utils.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/run_cvd_proc_collector.h" + +namespace cuttlefish { + +class RunCvdProcessManager { + public: + using GroupProcInfo = RunCvdProcessCollector::GroupProcInfo; + + static Result Get(); + // called by cvd reset handler + Result KillAllCuttlefishInstances(bool cvd_server_children_only, + bool clear_runtime_dirs); + // called by cvd start + Result ForcefullyStopGroup(bool cvd_server_children_only, + uid_t any_id_in_group); + + private: + RunCvdProcessManager() = delete; + RunCvdProcessManager(RunCvdProcessCollector&&); + static Result RunStopCvd(const GroupProcInfo& run_cvd_info, + bool clear_runtime_dirs); + Result RunStopCvdAll(bool cvd_server_children_only, + bool clear_runtime_dirs); + Result SendSignal(bool cvd_server_children_only, const GroupProcInfo&); + Result DeleteLockFile(bool cvd_server_children_only, + const GroupProcInfo&); + Result ForcefullyStopGroup(bool cvd_server_children_only, + const GroupProcInfo& group); + + RunCvdProcessCollector run_cvd_process_collector_; +}; + +struct DeviceClearOptions { + bool cvd_server_children_only; + bool clear_instance_dirs; +}; + +/* + * Runs stop_cvd for all cuttlefish instances found based on run_cvd processes, + * and send SIGKILL to the run_cvd processes. + * + * If cvd_server_children_only is set, it kills the run_cvd processes that were + * started by a cvd server process. + */ +Result KillAllCuttlefishInstances(const DeviceClearOptions& options); + +Result KillCvdServerProcess(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/run_cvd_proc_collector.cpp b/base/cvd/cuttlefish/host/commands/cvd/run_cvd_proc_collector.cpp new file mode 100644 index 0000000000..6bee03e8e1 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/run_cvd_proc_collector.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/run_cvd_proc_collector.h" + +#include +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/proc_file_utils.h" +#include "host/commands/cvd/common_utils.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { +namespace { + +struct RunCvdProcInfo { + pid_t pid_; + std::string home_; + std::string exec_path_; + std::unordered_map envs_; + std::vector cmd_args_; + std::string stop_cvd_path_; + bool is_cvd_server_started_; + std::optional android_host_out_; + unsigned id_; + uid_t real_owner_uid_; +}; + +bool IsTrue(std::string value) { + std::unordered_set true_strings = {"y", "yes", "true"}; + for (auto& c : value) { + c = std::tolower(static_cast(c)); + } + return Contains(true_strings, value); +} + +static Result SearchFilesInPath( + const std::string& dir_path, const std::vector& files) { + for (const auto& bin : files) { + std::string file_path = ConcatToString(dir_path, "/", bin); + if (!FileExists(file_path)) { + continue; + } + return file_path; + } + std::stringstream error; + error << "cvd_internal_stop/stop_cvd does not exist in " + << "the host tools path: " << dir_path << "."; + return CF_ERR(error.str()); +} + +static Result StopCvdPath(const RunCvdProcInfo& info) { + std::vector stop_bins{"cvd_internal_stop", "stop_cvd"}; + if (info.android_host_out_) { + auto result = + SearchFilesInPath(info.android_host_out_.value() + "/bin", stop_bins); + if (result.ok()) { + return *result; + } + LOG(ERROR) << result.error().FormatForEnv(); + } else { + LOG(ERROR) << "run_cvd host tool directory was not able to be guessed."; + } + LOG(ERROR) << "Falling back to use the cvd executable path"; + const std::string cvd_dir = + android::base::Dirname(android::base::GetExecutableDirectory()); + return SearchFilesInPath(cvd_dir, stop_bins); +} + +static std::optional HostOut(const cvd_common::Envs& envs) { + if (Contains(envs, kAndroidHostOut)) { + return envs.at(kAndroidHostOut); + } + if (Contains(envs, kAndroidSoongHostOut)) { + return envs.at(kAndroidSoongHostOut); + } + return std::nullopt; +} + +Result ExtractRunCvdInfo(const pid_t pid) { + auto proc_info = CF_EXPECT(ExtractProcInfo(pid)); + RunCvdProcInfo info; + info.pid_ = proc_info.pid_; + info.real_owner_uid_ = proc_info.real_owner_; + info.exec_path_ = proc_info.actual_exec_path_; + info.cmd_args_ = std::move(proc_info.args_); + info.envs_ = std::move(proc_info.envs_); + CF_EXPECT(Contains(info.envs_, "HOME")); + info.home_ = info.envs_.at("HOME"); + info.android_host_out_ = HostOut(info.envs_); + + CF_EXPECT(Contains(info.envs_, kCuttlefishInstanceEnvVarName)); + + CF_EXPECT(android::base::ParseUint( + info.envs_.at(kCuttlefishInstanceEnvVarName), &info.id_)); + + if (Contains(info.envs_, kCvdMarkEnv) && IsTrue(info.envs_.at(kCvdMarkEnv))) { + info.is_cvd_server_started_ = true; + } + + info.stop_cvd_path_ = + CF_EXPECTF(StopCvdPath(info), + "cvd_internal_stop or stop_cvd cannot be found for " + "pid #{}", + pid); + return info; +} + +Result> ExtractAllRunCvdInfo( + std::optional uid) { + std::vector run_cvd_procs_of_uid; + auto run_cvd_pids = CF_EXPECT(CollectPidsByExecName("run_cvd"), getuid()); + std::vector run_cvd_proc_infos; + run_cvd_proc_infos.reserve(run_cvd_pids.size()); + for (const auto run_cvd_pid : run_cvd_pids) { + auto proc_info_result = ExtractRunCvdInfo(run_cvd_pid); + if (!proc_info_result.ok()) { + LOG(DEBUG) << "Failed to fetch run_cvd process info for " << run_cvd_pid; + // perhaps, not my process + continue; + } + if (uid && uid.value() != proc_info_result->real_owner_uid_) { + LOG(DEBUG) << "run_cvd process " << run_cvd_pid << " does not belong to " + << uid.value() << " so skipped."; + continue; + } + run_cvd_procs_of_uid.emplace_back(std::move(*proc_info_result)); + } + return run_cvd_procs_of_uid; +} + +} // namespace + +Result RunCvdProcessCollector::Get() { + RunCvdProcessCollector run_cvd_processes_collector; + run_cvd_processes_collector.cf_groups_ = + CF_EXPECT(run_cvd_processes_collector.CollectInfo()); + return run_cvd_processes_collector; +} + +Result> + +RunCvdProcessCollector::CollectInfo() { + auto run_cvd_pids = CF_EXPECT(CollectPidsByExecName("run_cvd")); + std::vector run_cvd_infos = + CF_EXPECT(ExtractAllRunCvdInfo(getuid())); + + // home --> group map + std::unordered_map groups; + for (auto& run_cvd_info : run_cvd_infos) { + const auto home = run_cvd_info.home_; + if (!Contains(groups, home)) { + // create using a default constructor + auto& group = (groups[home] = GroupProcInfo()); + group.home_ = home; + group.exec_path_ = run_cvd_info.exec_path_; + group.stop_cvd_path_ = run_cvd_info.stop_cvd_path_; + group.is_cvd_server_started_ = run_cvd_info.is_cvd_server_started_; + group.android_host_out_ = run_cvd_info.android_host_out_; + } + auto& id_instance_map = groups[home].instances_; + if (!Contains(id_instance_map, run_cvd_info.id_)) { + id_instance_map[run_cvd_info.id_] = GroupProcInfo::InstanceInfo{ + .pids_ = std::set{run_cvd_info.pid_}, + .envs_ = std::move(run_cvd_info.envs_), + .cmd_args_ = std::move(run_cvd_info.cmd_args_), + .id_ = run_cvd_info.id_}; + continue; + } + // this is the other run_cvd process under the same instance i + id_instance_map[run_cvd_info.id_].pids_.insert(run_cvd_info.pid_); + } + + for (auto& [_, group] : groups) { + auto& id_instance_map = group.instances_; + for (auto& [id, instance] : id_instance_map) { + const auto& instance_run_cvd_pids = instance.pids_; + for (const auto run_cvd_pid : instance_run_cvd_pids) { + auto ppid_result = Ppid(run_cvd_pid); + if (!ppid_result.ok()) { + LOG(VERBOSE) << "Failed to fetch the parent id of " << run_cvd_pid; + continue; + } + if (Contains(instance_run_cvd_pids, *ppid_result) && + run_cvd_pid != *ppid_result) { + continue; + } + instance.parent_run_cvd_pids_.insert(run_cvd_pid); + } + } + } + + std::vector output; + output.reserve(groups.size()); + for (auto& [_, group] : groups) { + output.emplace_back(std::move(group)); + } + + return output; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/run_cvd_proc_collector.h b/base/cvd/cuttlefish/host/commands/cvd/run_cvd_proc_collector.h new file mode 100644 index 0000000000..077f06330c --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/run_cvd_proc_collector.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +class RunCvdProcessCollector { + public: + struct GroupProcInfo { + std::string home_; + std::string exec_path_; + std::string stop_cvd_path_; + bool is_cvd_server_started_; + std::optional android_host_out_; + struct InstanceInfo { + std::set pids_; + std::set parent_run_cvd_pids_; + cvd_common::Envs envs_; + cvd_common::Args cmd_args_; + unsigned id_; + }; + // instance id to instance info mapping + std::unordered_map instances_; + }; + const std::vector& CfGroups() const { return cf_groups_; } + + static Result Get(); + RunCvdProcessCollector(const RunCvdProcessCollector&) = delete; + RunCvdProcessCollector(RunCvdProcessCollector&&) = default; + + private: + RunCvdProcessCollector() = default; + static Result> CollectInfo(); + std::vector cf_groups_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/run_server.cpp b/base/cvd/cuttlefish/host/commands/cvd/run_server.cpp new file mode 100644 index 0000000000..ad51ac4292 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/run_server.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/run_server.h" + +#include +#include + +#include + +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/shared_fd_flag.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/logger.h" +#include "host/commands/cvd/metrics/metrics_notice.h" +#include "host/commands/cvd/server.h" +#include "host/commands/cvd/server_constants.h" + +namespace cuttlefish { + +SharedFD ServerMainLog() { + std::string log_path = "/tmp/cvd_server" + std::to_string(getuid()) + ".log"; + return SharedFD::Open(log_path, O_CREAT | O_APPEND | O_RDWR, 0644); +} + +bool IsServerModeExpected(const std::string& exec_file) { + return exec_file == kServerExecPath; +} + +Result RunServer(RunServerParam&& params) { + CF_EXPECTF(params.internal_server_fd->IsOpen(), + "Expected to be in server mode, but didn't get a server fd: {}", + params.internal_server_fd->StrError()); + + struct rlimit old_lim; + // Get old limits + if (getrlimit(RLIMIT_NOFILE, &old_lim) == 0) { + LOG(INFO) << "Old limits -> soft limit= " << old_lim.rlim_cur << "\t" + << " hard limit= " << old_lim.rlim_max; + } else { + PLOG(FATAL) << "CVD Server getrlimit error"; + } + // Set new value + old_lim.rlim_cur = old_lim.rlim_max; + // Set limits + CF_EXPECTF(setrlimit(RLIMIT_NOFILE, &old_lim) == 0, + "CVD Server setrlimit error, {}", strerror(errno)); + + std::unique_ptr server_logger = + std::make_unique(); + CF_EXPECT(server_logger != nullptr, "ServerLogger memory allocation failed."); + + const auto verbosity_level = params.verbosity_level; + // TODO(kwstephenkim): for cvd restart-server, it should print the LOG(ERROR) + // of the server codes outside handlers into the file descriptor eventually + // passed from the cvd restart client. However, the testing frameworks are + // waiting for the client's stderr to be closed. Thus, it should not be the + // client's stderr. See b/293191537. + SharedFD stderr_fd = ServerMainLog(); + std::unique_ptr run_server_logger; + if (stderr_fd->IsOpen()) { + ServerLogger::ScopedLogger tmp_logger = + (verbosity_level ? server_logger->LogThreadToFd(std::move(stderr_fd), + *verbosity_level) + : server_logger->LogThreadToFd(std::move(stderr_fd))); + run_server_logger = + std::make_unique(std::move(tmp_logger)); + } + + // run_server_logger will be destroyed only if CvdServerMain terminates, which + // normally does not. And, CvdServerMain does not refer its .scoped_logger. + if (params.memory_carryover_fd && !(*params.memory_carryover_fd)->IsOpen()) { + LOG(ERROR) << "Memory carryover file is supposed to be open but is not."; + } + CF_EXPECT(CvdServerMain( + {.internal_server_fd = std::move(params.internal_server_fd), + .carryover_client_fd = std::move(params.carryover_client_fd), + .memory_carryover_fd = std::move(params.memory_carryover_fd), + .acloud_translator_optout = params.acloud_translator_optout, + .server_logger = std::move(server_logger), + .scoped_logger = std::move(run_server_logger), + .restarted_in_process = params.restarted_in_process})); + return {}; +} + +Result ParseIfServer(std::vector& all_args) { + ParseResult result; + std::vector flags; + flags.emplace_back( + SharedFDFlag(kInternalServerFd, result.internal_server_fd)); + flags.emplace_back( + SharedFDFlag(kInternalCarryoverClientFd, result.carryover_client_fd)); + SharedFD memory_carryover_fd; + flags.emplace_back( + SharedFDFlag("INTERNAL_memory_carryover_fd", memory_carryover_fd)); + // the server's default verbosity must be VERBOSE, the least LogSeverity + // the LogSeverity control will be done later on by the server by masking + std::string verbosity = "VERBOSE"; + flags.emplace_back(GflagsCompatFlag("verbosity", verbosity)); + result.restarted_in_process = false; + flags.emplace_back(GflagsCompatFlag(kInternalRestartedInProcess, + result.restarted_in_process)); + CF_EXPECT(ConsumeFlags(flags, all_args)); + + // now the flags above consumed their lexical tokens from all_args + // For now, the default value of acloud_translator_optout is false + // In the future, it might be determined by the server if not given. + const auto all_args_size_before = all_args.size(); + bool acloud_translator_optout_value = false; + PrintDataCollectionNotice(); + flags.emplace_back(GflagsCompatFlag("INTERNAL_acloud_translator_optout", + acloud_translator_optout_value)); + CF_EXPECT(ConsumeFlags({GflagsCompatFlag("INTERNAL_acloud_translator_optout", + acloud_translator_optout_value)}, + all_args)); + if (all_args.size() != all_args_size_before) { + result.acloud_translator_optout = acloud_translator_optout_value; + } + + if (memory_carryover_fd->IsOpen()) { + result.memory_carryover_fd = std::move(memory_carryover_fd); + } + + if (!verbosity.empty()) { + result.verbosity_level = CF_EXPECT(EncodeVerbosity(verbosity)); + } + + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/run_server.h b/base/cvd/cuttlefish/host/commands/cvd/run_server.h new file mode 100644 index 0000000000..5fd4e03f6f --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/run_server.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 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 + +#include +#include +#include + +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +// names of the flags to start cvd server +inline constexpr char kInternalServerFd[] = "INTERNAL_server_fd"; +inline constexpr char kInternalCarryoverClientFd[] = + "INTERNAL_carryover_client_fd"; +inline constexpr char kInternalMemoryCarryoverFd[] = + "INTERNAL_memory_carryover_fd"; +inline constexpr char kInternalAcloudTranslatorOptOut[] = + "INTERNAL_acloud_translator_optout"; +inline constexpr char kInternalRestartedInProcess[] = + "INTERNAL_restarted_in_process"; + +bool IsServerModeExpected(const std::string& exec_file); + +struct RunServerParam { + SharedFD internal_server_fd; + SharedFD carryover_client_fd; + std::optional memory_carryover_fd; + std::optional verbosity_level; + std::optional acloud_translator_optout; + bool restarted_in_process; +}; +// must move to ensure the clarity of the ownership +Result RunServer(RunServerParam&& params); + +struct ParseResult { + SharedFD internal_server_fd; + SharedFD carryover_client_fd; + std::optional memory_carryover_fd; + std::optional acloud_translator_optout; + std::optional verbosity_level; + bool restarted_in_process; +}; +Result ParseIfServer(cvd_common::Args& all_args); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_lexer.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_lexer.cpp new file mode 100644 index 0000000000..5a12f07ddf --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_lexer.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/arguments_lexer.h" + +#include +#include +#include + +#include + +#include "host/commands/cvd/selector/instance_database_utils.h" + +namespace cuttlefish { +namespace selector { +namespace { + +template +bool Included(const std::string& item, Sets&&... containers) { + return ((Contains(std::forward(containers), item)) || ... || false); +} + +} // namespace + +/* + * Eventually, we get two sets, each include strings start with "-" or "--". + * + * Say, the two sets are BaseSet and NoPrependedSet. + * + * Given a boolean flag --foo, these will happen: + * BaseSet = BaseSet U {"--foo", "-foo"} + * NoPrependedSet = NoPrependedSet U {"--nofoo", "-nofoo"} + * Given a non boolean flag --bar, these will happen: + * BaseSet = BaseSet U {"--bar", "-bar"} + * + * Later on, when the parser reads a token, the parser will look up the + * two sets to see if the token that is supposedly a flag is a known + * flag. + */ +Result +ArgumentsLexerBuilder::GenerateFlagPatterns( + const LexerFlagsSpecification& known_flags) { + FlagPatterns flag_patterns; + for (const auto& non_bool_flag : known_flags.known_value_flags) { + const auto one_dash = "-" + non_bool_flag; + const auto two_dashes = "--" + non_bool_flag; + CF_EXPECT(!ArgumentsLexer::Registered(one_dash, flag_patterns)); + CF_EXPECT(!ArgumentsLexer::Registered(two_dashes, flag_patterns)); + flag_patterns.value_patterns.insert(one_dash); + flag_patterns.value_patterns.insert(two_dashes); + } + for (const auto& bool_flag : known_flags.known_boolean_flags) { + const auto one_dash = "-" + bool_flag; + const auto two_dashes = "--" + bool_flag; + const auto one_dash_with_no = "-no" + bool_flag; + const auto two_dashes_with_no = "--no" + bool_flag; + CF_EXPECT(!ArgumentsLexer::Registered(one_dash, flag_patterns)); + CF_EXPECT(!ArgumentsLexer::Registered(two_dashes, flag_patterns)); + CF_EXPECT(!ArgumentsLexer::Registered(one_dash_with_no, flag_patterns)); + CF_EXPECT(!ArgumentsLexer::Registered(two_dashes_with_no, flag_patterns)); + flag_patterns.bool_patterns.insert(one_dash); + flag_patterns.bool_patterns.insert(two_dashes); + flag_patterns.bool_no_patterns.insert(one_dash_with_no); + flag_patterns.bool_no_patterns.insert(two_dashes_with_no); + } + return flag_patterns; +} + +Result> ArgumentsLexerBuilder::Build( + const LexerFlagsSpecification& known_flags) { + auto flag_patterns = CF_EXPECT(GenerateFlagPatterns(known_flags)); + ArgumentsLexer* new_lexer = new ArgumentsLexer(std::move(flag_patterns)); + CF_EXPECT(new_lexer != nullptr, + "Memory allocation for ArgumentsLexer failed."); + return std::unique_ptr{new_lexer}; +} + +ArgumentsLexer::ArgumentsLexer(FlagPatterns&& flag_patterns) + : flag_patterns_{std::move(flag_patterns)} { + valid_bool_values_in_lower_cases_ = + std::unordered_set{"true", "false", "yes", "no", "y", "n"}; +} + +bool ArgumentsLexer::Registered(const std::string& flag_string, + const FlagPatterns& flag_patterns) { + return Included(flag_string, flag_patterns.value_patterns, + flag_patterns.bool_patterns, flag_patterns.bool_no_patterns); +} + +Result ArgumentsLexer::Process(const std::string& token) const { + if (token == "--") { + return ArgToken{ArgType::kDoubleDash, token}; + } + std::regex flag_and_value_pattern("[\\-][\\-]?[^\\-]+.*=.*"); + std::regex flag_pattern("[\\-][\\-]?[^\\-]+.*"); + std::regex base_pattern("[^\\-]+.*"); + if (std::regex_match(token, base_pattern)) { + return ArgToken{ArgType::kPositional, token}; + } + if (!std::regex_match(token, flag_pattern)) { + return ArgToken{ArgType::kError, token}; + } + // --flag=value + if (std::regex_match(token, flag_and_value_pattern)) { + auto [flag_string, value] = CF_EXPECT(Separate(token)); + // is --flag registered? + if (Contains(flag_patterns_.value_patterns, flag_string)) { + return ArgToken{ArgType::kKnownFlagAndValue, token}; + } + return ArgToken{ArgType::kUnknownFlag, token}; + } + if (Contains(flag_patterns_.value_patterns, token)) { + return ArgToken{ArgType::kKnownValueFlag, token}; + } + if (Contains(flag_patterns_.bool_patterns, token)) { + return ArgToken{ArgType::kKnownBoolFlag, token}; + } + if (Contains(flag_patterns_.bool_no_patterns, token)) { + return ArgToken{ArgType::kKnownBoolNoFlag, token}; + } + return ArgToken{ArgType::kUnknownFlag, token}; +} + +Result> ArgumentsLexer::Tokenize( + const std::vector& args) const { + std::vector tokenized; + auto intersection = + Intersection(flag_patterns_.value_patterns, flag_patterns_.bool_patterns); + CF_EXPECT(intersection.empty()); + auto preprocessed_args = CF_EXPECT(Preprocess(args)); + for (const auto& arg : preprocessed_args) { + auto arg_token = CF_EXPECT(Process(arg)); + tokenized.emplace_back(arg_token); + } + return tokenized; +} + +static std::string ToLower(const std::string& src) { + std::string lower_cased_value; + lower_cased_value.resize(src.size()); + std::transform(src.begin(), src.end(), lower_cased_value.begin(), ::tolower); + return lower_cased_value; +} + +Result ArgumentsLexer::Separate( + const std::string& equal_included_string) const { + CF_EXPECT(Contains(equal_included_string, "=")); + auto equal_sign_pos = equal_included_string.find_first_of('='); + auto first_token = equal_included_string.substr(0, equal_sign_pos); + auto second_token = equal_included_string.substr(equal_sign_pos + 1); + return FlagValuePair{.flag_string = first_token, .value = second_token}; +} + +Result> ArgumentsLexer::Preprocess( + const std::vector& args) const { + std::vector new_args; + std::regex pattern("[\\-][\\-]?[^\\-]+.*=.*"); + for (const auto& arg : args) { + if (!std::regex_match(arg, pattern)) { + new_args.emplace_back(arg); + continue; + } + // needs to split based on the first '=' + // --something=another_thing or + // -something=another_thing + const auto [flag_string, value] = CF_EXPECT(Separate(arg)); + + if (Contains(flag_patterns_.bool_patterns, flag_string)) { + const auto low_cased_value = ToLower(value); + CF_EXPECT(Contains(valid_bool_values_in_lower_cases_, low_cased_value), + "The value for the boolean flag " << flag_string << ", " + << value << " is not valid"); + if (low_cased_value == "true" || low_cased_value == "yes") { + new_args.emplace_back(flag_string); + continue; + } + auto base_pos = flag_string.find_first_not_of('-'); + auto base = flag_string.substr(base_pos); + new_args.emplace_back("--no" + base); + continue; + } + + if (Contains(flag_patterns_.bool_no_patterns, flag_string)) { + CF_EXPECT(android::base::StartsWith(flag_string, "-no") || + android::base::StartsWith(flag_string, "--no")); + // if --nohelp=XYZ, the "=XYZ" is ignored. + new_args.emplace_back(flag_string); + continue; + } + + new_args.emplace_back(arg); + } + return new_args; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_lexer.h b/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_lexer.h new file mode 100644 index 0000000000..3bb4f137c3 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_lexer.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { +namespace selector { + +/** + * A "token" is each piece of command line argument that is mostly + * separated by " ". + * + * Each token has a type. The type is a useful information for the + * grammar parser, which will use this lexer. + * + * Before going into the details, we assume that a set of flags are + * pre-registered, and the user may still give unregisterred flags. + * + * Note that the purpose of this lexer/parser is to separate cvd + * client specific arguments and the "subcmd" from the rest. So, + * "registered" arguments would be the cvd client specific arguments. + * The unregisterred arguments would be for the sub tool. + * + * Also, in terms of lexing, boolean flags are different from other + * value-taking flags. A boolean flag --foo could be --nofoo. + * + * 1. kKnownValueFlag + * --foo, -foo that may take a non-boolean value + * 2. kKnownFlagAndValue + * --foo=value, -foo=value, which does not take more values + * 3. kKnownBoolFlag + * --daemon, -daemon, etc, which may take a boolean arg + * 4. kKnownBoolNoFlag + * --nodaemon, -nodaemon, etc, which does not take another argument. + * 5. kUnknownFlag + * -anything_else or --anything_else + * --anything_else=any_value, etc + * Note that if we don't know the type of the flag, we will have to forward + * the entire thing to the subcmd as is. + * 6. kPositional + * mostly without leading "-" or "--" + * 7. kDoubleDash + * A literally "--" + * cvd and its subtools as of not are not really using that. + * However, it might be useful in the future for any subtool of cvd, so + * we allow "--" in the subcmd arguments only in the parser level. + * In the lexer level, we simply returns kDoubleDash token. + * 8. kError + * The rest. + * + */ +enum class ArgType : int { + kKnownValueFlag, + kKnownFlagAndValue, + kKnownBoolFlag, + kKnownBoolNoFlag, + kUnknownFlag, + kPositional, + kDoubleDash, + kError +}; + +class ArgToken { + public: + ArgToken() = delete; + ArgToken(const ArgType arg_type, const std::string& token) + : type_(arg_type), token_(token) {} + ArgToken(const ArgToken& src) = default; + ArgToken(ArgToken&& src) = default; + ArgToken& operator=(const ArgToken& src) { + type_ = src.type_; + token_ = src.token_; + return *this; + } + ArgToken& operator=(ArgToken&& src) { + type_ = std::move(src.type_); + token_ = std::move(src.token_); + return *this; + } + + auto Type() const { return type_; } + const auto& Token() const { return token_; } + auto& Token() { return token_; } + bool operator==(const ArgToken& dst) const { + return Type() == dst.Type() && Token() == dst.Token(); + } + + private: + ArgType type_; + std::string token_; +}; + +class ArgumentsLexer { + friend class ArgumentsLexerBuilder; + using CvdProtobufArg = google::protobuf::RepeatedPtrField; + + public: + Result> Tokenize( + const std::vector& args) const; + + private: + // Lexer factory function will internally generate this, + // and give it to ArgumentsLexer. + struct FlagPatterns { + /* represents flags that takes values + * e.g. -group_name, --group_name (which may take an additional + * positional arg, or use its default value.) + * + * With the given example, this set shall be: + * {"-group_name", "--group_name"} + */ + std::unordered_set value_patterns; + /* boolean flags + * e.g. --daemon, --nodaemon + * + * With the given example, this set shall be: + * {"-daemon", "--daemon"} + */ + std::unordered_set bool_patterns; + // e.g. {"-nodaemon", "--nodaemon"} + std::unordered_set bool_no_patterns; + }; + ArgumentsLexer(FlagPatterns&& flag_patterns); + + // preprocess boolean flags: + // e.g. --help=yes --> --help + // --help=faLSe --> --nohelp + Result> Preprocess( + const std::vector& args) const; + Result Process(const std::string& token) const; + + struct FlagValuePair { + std::string flag_string; + std::string value; + }; + Result Separate( + const std::string& equal_included_string) const; + // flag_string starts with "-" or "--" + static bool Registered(const std::string& flag_string, + const FlagPatterns& flag_patterns); + bool Registered(const std::string& flag_string) const { + return Registered(flag_string, flag_patterns_); + } + std::unordered_set valid_bool_values_in_lower_cases_; + FlagPatterns flag_patterns_; +}; + +// input to the lexer factory function +struct LexerFlagsSpecification { + std::unordered_set known_boolean_flags; + std::unordered_set known_value_flags; +}; + +/* + * At the top level, there are only two tokens: flag and positional tokens. + * + * A flag token starts with "-" or "--" followed by one or more non "-" letters. + * A positional token starts with any character other than "-". + * + * Between flag tokens, there are "known" and "unknown" flag tokens. + * + */ +class ArgumentsLexerBuilder { + using FlagPatterns = ArgumentsLexer::FlagPatterns; + + public: + static Result> Build( + const LexerFlagsSpecification& known_flags); + + private: + static Result GenerateFlagPatterns( + const LexerFlagsSpecification& known_flags); +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_separator.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_separator.cpp new file mode 100644 index 0000000000..9b25f36f1c --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_separator.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/arguments_separator.h" + +#include + +#include + +#include "common/libs/utils/contains.h" + +namespace cuttlefish { +namespace selector { + +Result> ArgumentsSeparator::Parse( + const FlagsRegistration& flag_registration, + const std::vector& input_args) { + LexerFlagsSpecification lexer_flag_spec{ + .known_boolean_flags = flag_registration.known_boolean_flags, + .known_value_flags = flag_registration.known_value_flags, + }; + auto lexer = CF_EXPECT(ArgumentsLexerBuilder::Build(lexer_flag_spec)); + CF_EXPECT(lexer != nullptr); + ArgumentsSeparator* new_arg_separator = + new ArgumentsSeparator(std::move(lexer), input_args, flag_registration); + CF_EXPECT(new_arg_separator != nullptr, + "Memory allocation failed for ArgumentSeparator"); + std::unique_ptr arg_separator{new_arg_separator}; + CF_EXPECT(arg_separator->Parse()); + return std::move(arg_separator); +} + +Result> ArgumentsSeparator::Parse( + const FlagsRegistration& flag_registration, + const CvdProtobufArg& input_args) { + std::vector input_args_vec; + input_args_vec.reserve(input_args.size()); + for (const auto& input_arg : input_args) { + input_args_vec.emplace_back(input_arg); + } + auto arg_separator = CF_EXPECT(Parse(flag_registration, input_args_vec)); + return std::move(arg_separator); +} + +Result> ArgumentsSeparator::Parse( + const FlagsRegistration& flag_registration, const std::string& input_args, + const std::string delim) { + std::vector input_args_vec = + android::base::Tokenize(input_args, delim); + auto arg_separator = CF_EXPECT(Parse(flag_registration, input_args_vec)); + return std::move(arg_separator); +} + +ArgumentsSeparator::ArgumentsSeparator( + std::unique_ptr&& lexer, + const std::vector& input_args, + const FlagsRegistration& flag_registration) + : lexer_(std::move(lexer)), + input_args_(input_args), + known_boolean_flags_(flag_registration.known_boolean_flags), + known_value_flags_(flag_registration.known_value_flags), + valid_subcmds_(flag_registration.valid_subcommands), + match_any_subcmd_(Contains(valid_subcmds_, "*")) {} + +Result ArgumentsSeparator::Parse() { + auto output = CF_EXPECT(ParseInternal()); + prog_path_ = std::move(output.prog_path); + cvd_args_ = std::move(output.cvd_args); + sub_cmd_ = std::move(output.sub_cmd); + sub_cmd_args_ = std::move(output.sub_cmd_args); + return {}; +} + +/* + * prog_name, , sub_cmd, + * + * -- could be included, which makes things complicated. However, if -- is + * part of cvd flags, it's ill-formatted. If -- is among sub_cmd flags, + * we will just forward it. + * + * If something like this is really needed, use the suggested alternative: + * original: cvd --some_flag -- --this-is-value start --subcmd_args + * alternative: cvd --some_flag="--this-is-value" start --subcmd_args + * + */ +Result ArgumentsSeparator::ParseInternal() { + CF_EXPECT(lexer_ != nullptr); + CF_EXPECT(!input_args_.empty()); + Output output; + + auto tokenized = CF_EXPECT(lexer_->Tokenize(input_args_)); + std::deque tokens_queue{tokenized.begin(), tokenized.end()}; + + // take program path/name + CF_EXPECT(!tokens_queue.empty() && + tokens_queue.front().Type() == ArgType::kPositional); + output.prog_path = std::move(tokens_queue.front().Token()); + tokens_queue.pop_front(); + + // break loop either if there is no token or + // the subcommand token is consumed + bool cvd_flags_mode = true; + while (!tokens_queue.empty() && cvd_flags_mode) { + const auto current = std::move(tokens_queue.front()); + const auto current_type = current.Type(); + const auto& current_token = current.Token(); + tokens_queue.pop_front(); + + // look up next if any + std::optional next; + if (!tokens_queue.empty()) { + next = tokens_queue.front(); + } + + switch (current_type) { + case ArgType::kKnownValueFlag: { + output.cvd_args.emplace_back(current_token); + if (next && next->Type() == ArgType::kPositional) { + output.cvd_args.emplace_back(next->Token()); + tokens_queue.pop_front(); + } + } break; + case ArgType::kKnownFlagAndValue: + case ArgType::kKnownBoolFlag: + case ArgType::kKnownBoolNoFlag: { + output.cvd_args.emplace_back(current_token); + } break; + case ArgType::kPositional: { + output.sub_cmd = current.Token(); + CF_EXPECT(output.sub_cmd != std::nullopt); + CF_EXPECT(match_any_subcmd_ || Contains(valid_subcmds_, output.sub_cmd), + "Subcommand " << *(output.sub_cmd) << " is not valid"); + cvd_flags_mode = false; + } break; + case ArgType::kDoubleDash: { + return CF_ERR("--" + << " is not allowed within cvd specific flags."); + } + case ArgType::kUnknownFlag: + case ArgType::kError: { + return CF_ERR(current.Token() + << " in cvd-specific flags is disallowed."); + } + } + } + while (!tokens_queue.empty()) { + auto token = std::move(tokens_queue.front().Token()); + output.sub_cmd_args.emplace_back(std::move(token)); + tokens_queue.pop_front(); + } + return output; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_separator.h b/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_separator.h new file mode 100644 index 0000000000..d6d4304f08 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/arguments_separator.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include +#include +#include + +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/selector/arguments_lexer.h" + +namespace cuttlefish { +namespace selector { + +/** + * The very first parser for cmdline that separates: + * + * 1. program name/path + * 2. cvd specific options such as --clean, selector options, etc + * 3. subcmd + * 4. subcmd arguments + * + * Note that the user's command line arguments are in this order: + * $ program_path/name \ + * subcmd + * + * For the parser's sake, there are a few more rules. + * + * 1. All the optional cvd-specific flags should be pre-registered. Usually, + * the subcmd arguments do not have to be registered. However, cvd-specific + * flags must be. + * + * E.g. "--clean" is the only registered cvd-specific flag, which happened + * to be bool. + * These are okay: + * cvd --clean start --never-exist-flag + * cvd --noclean stop + * cvd start + * + * However, this is not okay: + * cvd --daemon start + * + * 2. -- + * E.g. cvd --clean start --have --some --args -- a b c d e + * -- is basically for subcommands. cvd itself does not use it. + * If -- is within cvd arguments, it is ill-formatted. If it is within + * subcommands arguments, we simply forward it to the subtool as is. + * + */ +class ArgumentsSeparator { + using CvdProtobufArg = google::protobuf::RepeatedPtrField; + + public: + struct FlagsRegistration { + std::unordered_set known_boolean_flags; + std::unordered_set known_value_flags; + std::unordered_set valid_subcommands; + }; + static Result> Parse( + const FlagsRegistration& flag_registration, + const std::vector& input_args); + static Result> Parse( + const FlagsRegistration& flag_registration, + const CvdProtobufArg& input_args); + static Result> Parse( + const FlagsRegistration& flag_registration, const std::string& input_args, + const std::string delim = " "); + + const std::string& ProgPath() const { return prog_path_; } + const std::vector& CvdArgs() const { return cvd_args_; } + std::optional SubCmd() const { return sub_cmd_; } + const std::vector& SubCmdArgs() const { return sub_cmd_args_; } + + private: + ArgumentsSeparator(std::unique_ptr&& lexer, + const std::vector& input_args, + const FlagsRegistration& flag_registration); + + bool IsFlag(const ArgType arg_type) const; + struct Output { + std::string prog_path; + std::vector cvd_args; + std::optional sub_cmd; + std::vector sub_cmd_args; + }; + Result Parse(); + Result ParseInternal(); + + // internals + std::unique_ptr lexer_; + + // inputs + std::vector input_args_; + std::unordered_set known_boolean_flags_; + std::unordered_set known_value_flags_; + std::unordered_set valid_subcmds_; + bool match_any_subcmd_; + + // outputs + std::string prog_path_; + std::vector cvd_args_; + std::optional sub_cmd_; + std::vector sub_cmd_args_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/constant_reference.h b/base/cvd/cuttlefish/host/commands/cvd/selector/constant_reference.h new file mode 100644 index 0000000000..199cad8b85 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/constant_reference.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 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 + +#include + +namespace cuttlefish { +namespace selector { + +class LocalInstanceGroup; +class LocalInstance; + +template +class ConstRef { + static_assert(std::is_same::value || + std::is_same::value); + + public: + ConstRef(ConstRef& ref) = default; + ConstRef(const ConstRef& ref) = default; + ConstRef(ConstRef&& ref) = default; + + ConstRef(const T& t) : inner_wrapper_(t) {} + ConstRef(T&&) = delete; + + ConstRef& operator=(const ConstRef& other) { + inner_wrapper_ = other.inner_wrapper_; + return *this; + } + + operator const T&() const noexcept { return inner_wrapper_.get(); } + + const T& Get() const noexcept { return inner_wrapper_.get(); } + + /** + * comparison based on the address of underlying object + * + * Note that, per instance (group), there is only one LocalInstance(Group) + * object is created during the program's life time. Besides, they don't + * offer operator==, either. ConstRef has to be in + * a set. + */ + bool operator==(const ConstRef& rhs) const noexcept { + return std::addressof(Get()) == std::addressof(rhs.Get()); + } + + private: + std::reference_wrapper inner_wrapper_; +}; + +template +ConstRef Cref(const T& t) noexcept { + return ConstRef(t); +} + +} // namespace selector +} // namespace cuttlefish + +/** + * the assumption is, if std::addressof(lhs) != std::addressof(rhs), + * the two LocalInstance objects are actually different. There is only + * on LocalInstance(Group) object per a given cuttlefish instance (group). + */ +template +struct std::hash> { + std::size_t operator()( + const cuttlefish::selector::ConstRef& ref) const noexcept { + const auto ptr = std::addressof(ref.Get()); + return std::hash()(ptr); + } +}; diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/creation_analyzer.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/creation_analyzer.cpp new file mode 100644 index 0000000000..0c7c6ef80d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/creation_analyzer.cpp @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/creation_analyzer.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/users.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/selector/instance_database_utils.h" +#include "host/commands/cvd/selector/selector_constants.h" + +namespace cuttlefish { +namespace selector { + +static bool IsCvdStart(const std::string& cmd) { + if (cmd.empty()) { + return false; + } + return cmd == "start" || cmd == "launch_cvd"; +} + +Result CreationAnalyzer::Analyze( + const std::string& cmd, const CreationAnalyzerParam& param, + const ucred& credential, const InstanceDatabase& instance_database, + InstanceLockFileManager& instance_lock_file_manager) { + CF_EXPECT(IsCvdStart(cmd), + "CreationAnalyzer::Analyze() is for cvd start only."); + auto selector_options_parser = + CF_EXPECT(StartSelectorParser::ConductSelectFlagsParser( + param.selector_args, param.cmd_args, param.envs)); + CreationAnalyzer analyzer(param, credential, + std::move(selector_options_parser), + instance_database, instance_lock_file_manager); + auto result = CF_EXPECT(analyzer.Analyze()); + return result; +} + +CreationAnalyzer::CreationAnalyzer( + const CreationAnalyzerParam& param, const ucred& credential, + StartSelectorParser&& selector_options_parser, + const InstanceDatabase& instance_database, + InstanceLockFileManager& instance_file_lock_manager) + : cmd_args_(param.cmd_args), + envs_(param.envs), + selector_args_(param.selector_args), + credential_(credential), + selector_options_parser_{std::move(selector_options_parser)}, + instance_database_{instance_database}, + instance_file_lock_manager_{instance_file_lock_manager} {} + +static std::unordered_map ConstructIdLockFileMap( + std::vector&& lock_files) { + std::unordered_map mapping; + for (auto& lock_file : lock_files) { + const unsigned id = static_cast(lock_file.Instance()); + mapping.insert({id, std::move(lock_file)}); + } + lock_files.clear(); + return mapping; +} + +static Result IsIdAvailable(const InstanceDatabase& instance_database, + const unsigned id) { + auto subset = + CF_EXPECT(instance_database.FindInstances(Query{kInstanceIdField, id})); + CF_EXPECT(subset.empty()); + return {}; +} + +Result> +CreationAnalyzer::AnalyzeInstanceIdsInternal( + const std::vector& requested_instance_ids) { + CF_EXPECT(!requested_instance_ids.empty(), + "Instance IDs were specified, so should be one or more."); + for (const auto id : requested_instance_ids) { + CF_EXPECT(IsIdAvailable(instance_database_, id), + "instance ID #" << id << " is requested but not available."); + } + + std::vector per_instance_names; + if (selector_options_parser_.PerInstanceNames()) { + per_instance_names = *selector_options_parser_.PerInstanceNames(); + CF_EXPECT_EQ(per_instance_names.size(), requested_instance_ids.size()); + } else { + for (const auto id : requested_instance_ids) { + per_instance_names.push_back(std::to_string(id)); + } + } + + std::map id_name_pairs; + for (size_t i = 0; i != requested_instance_ids.size(); i++) { + id_name_pairs[requested_instance_ids.at(i)] = per_instance_names.at(i); + } + + std::vector instance_info; + bool must_acquire_file_locks = selector_options_parser_.MustAcquireFileLock(); + if (!must_acquire_file_locks) { + for (const auto& [id, name] : id_name_pairs) { + instance_info.emplace_back(id, name); + } + return instance_info; + } + auto acquired_all_file_locks = + CF_EXPECT(instance_file_lock_manager_.LockAllAvailable()); + auto id_to_lockfile_map = + ConstructIdLockFileMap(std::move(acquired_all_file_locks)); + for (const auto& [id, instance_name] : id_name_pairs) { + CF_EXPECT(Contains(id_to_lockfile_map, id), + "Instance ID " << id << " lock file can't be locked."); + auto& lock_file = id_to_lockfile_map.at(id); + instance_info.emplace_back(id, instance_name, std::move(lock_file)); + } + return instance_info; +} + +/* + * Filters out the ids in id_pool that already exist in instance_database + */ +static Result> CollectUnusedIds( + const InstanceDatabase& instance_database, + std::vector&& id_pool) { + std::vector collected_ids; + for (const auto id : id_pool) { + if (IsIdAvailable(instance_database, id).ok()) { + collected_ids.push_back(id); + } + } + return collected_ids; +} + +struct NameLockFilePair { + std::string name; + InstanceLockFile lock_file; +}; +Result> +CreationAnalyzer::AnalyzeInstanceIdsInternal() { + CF_EXPECT(selector_options_parser_.MustAcquireFileLock(), + "For now, cvd server always acquire the file locks " + << "when IDs are automatically allocated."); + + // As this test was done earlier, this line must not fail + const auto n_instances = selector_options_parser_.RequestedNumInstances(); + auto acquired_all_file_locks = + CF_EXPECT(instance_file_lock_manager_.LockAllAvailable()); + auto id_to_lockfile_map = + ConstructIdLockFileMap(std::move(acquired_all_file_locks)); + + /* generate n_instances consecutive ids. For backward compatibility, + * we prefer n consecutive ids for now. + */ + std::vector id_pool; + id_pool.reserve(id_to_lockfile_map.size()); + for (const auto& [id, _] : id_to_lockfile_map) { + id_pool.push_back(id); + } + auto unused_id_pool = + CF_EXPECT(CollectUnusedIds(instance_database_, std::move(id_pool))); + auto unique_id_allocator = IdAllocator::New(unused_id_pool); + CF_EXPECT(unique_id_allocator != nullptr, + "Memory allocation for UniqueResourceAllocator failed."); + + // auto-generation means the user did not specify much: e.g. "cvd start" + // In this case, the user may expect the instance id to be 1+ + auto allocated_ids_opt = + unique_id_allocator->UniqueConsecutiveItems(n_instances); + CF_EXPECT(allocated_ids_opt != std::nullopt, "Unique ID allocation failed."); + + std::vector allocated_ids; + allocated_ids.reserve(allocated_ids_opt->size()); + for (const auto& reservation : *allocated_ids_opt) { + allocated_ids.push_back(reservation.Get()); + } + std::sort(allocated_ids.begin(), allocated_ids.end()); + + const auto per_instance_names_opt = + selector_options_parser_.PerInstanceNames(); + if (per_instance_names_opt) { + CF_EXPECT(per_instance_names_opt->size() == allocated_ids.size()); + } + std::vector instance_info; + for (size_t i = 0; i != allocated_ids.size(); i++) { + const auto id = allocated_ids.at(i); + + std::string name = std::to_string(id); + // Use the user provided instance name only if it's not empty. + if (per_instance_names_opt && !(*per_instance_names_opt)[i].empty()) { + name = (*per_instance_names_opt)[i]; + } + instance_info.emplace_back(id, name, std::move(id_to_lockfile_map.at(id))); + } + return instance_info; +} + +Result> CreationAnalyzer::AnalyzeInstanceIds() { + auto requested_instance_ids = selector_options_parser_.InstanceIds(); + return requested_instance_ids + ? CF_EXPECT(AnalyzeInstanceIdsInternal(*requested_instance_ids)) + : CF_EXPECT(AnalyzeInstanceIdsInternal()); +} + +/* + * 1. Remove --num_instances, --instance_nums, --base_instance_num if any. + * 2. If the ids are consecutive and ordered, add: + * --base_instance_num=min --num_instances=ids.size() + * 3. If not, --instance_nums= + * + */ +static Result> UpdateInstanceArgs( + std::vector&& args, const std::vector& ids) { + CF_EXPECT(ids.empty() == false); + + std::vector new_args{std::move(args)}; + std::string old_instance_nums; + std::string old_num_instances; + std::string old_base_instance_num; + + std::vector instance_id_flags{ + GflagsCompatFlag("instance_nums", old_instance_nums), + GflagsCompatFlag("num_instances", old_num_instances), + GflagsCompatFlag("base_instance_num", old_base_instance_num)}; + // discard old ones + CF_EXPECT(ConsumeFlags(instance_id_flags, new_args)); + + auto max = *(std::max_element(ids.cbegin(), ids.cend())); + auto min = *(std::min_element(ids.cbegin(), ids.cend())); + + const bool is_consecutive = ((max - min) == (ids.size() - 1)); + const bool is_sorted = std::is_sorted(ids.begin(), ids.end()); + + if (!is_consecutive || !is_sorted) { + std::string flag_value = android::base::Join(ids, ","); + new_args.push_back("--instance_nums=" + flag_value); + return new_args; + } + + // sorted and consecutive, so let's use old flags + // like --num_instances and --base_instance_num + new_args.push_back("--num_instances=" + std::to_string(ids.size())); + new_args.push_back("--base_instance_num=" + std::to_string(min)); + return new_args; +} + +Result> CreationAnalyzer::UpdateWebrtcDeviceId( + std::vector&& args, + const std::vector& per_instance_info) { + std::vector new_args{std::move(args)}; + std::string flag_value; + std::vector webrtc_device_id_flag{ + GflagsCompatFlag("webrtc_device_id", flag_value)}; + std::vector copied_args{new_args}; + CF_EXPECT(ConsumeFlags(webrtc_device_id_flag, copied_args)); + + if (!flag_value.empty()) { + return new_args; + } + + CF_EXPECT(!group_name_.empty()); + std::vector device_name_list; + for (const auto& instance : per_instance_info) { + const auto& per_instance_name = instance.per_instance_name_; + std::string device_name = group_name_ + "-" + per_instance_name; + device_name_list.push_back(device_name); + } + // take --webrtc_device_id flag away + new_args = std::move(copied_args); + new_args.push_back("--webrtc_device_id=" + + android::base::Join(device_name_list, ",")); + return new_args; +} + +Result CreationAnalyzer::Analyze() { + auto instance_info = CF_EXPECT(AnalyzeInstanceIds()); + std::vector ids; + ids.reserve(instance_info.size()); + for (const auto& instance : instance_info) { + ids.push_back(instance.instance_id_); + } + cmd_args_ = CF_EXPECT(UpdateInstanceArgs(std::move(cmd_args_), ids)); + + auto group_info = CF_EXPECT(ExtractGroup(instance_info)); + group_name_ = group_info.group_name; + cmd_args_ = + CF_EXPECT(UpdateWebrtcDeviceId(std::move(cmd_args_), instance_info)); + + home_ = CF_EXPECT(AnalyzeHome()); + envs_["HOME"] = home_; + + CF_EXPECT(Contains(envs_, kAndroidHostOut)); + std::string android_product_out_path = Contains(envs_, kAndroidProductOut) + ? envs_.at(kAndroidProductOut) + : envs_.at(kAndroidHostOut); + GroupCreationInfo report = {.home = home_, + .host_artifacts_path = envs_.at(kAndroidHostOut), + .product_out_path = android_product_out_path, + .group_name = group_name_, + .instances = std::move(instance_info), + .args = cmd_args_, + .envs = envs_, + .is_default_group = group_info.default_group}; + return report; +} + +Result CreationAnalyzer::ExtractGroup( + const std::vector& per_instance_infos) const { + if (selector_options_parser_.GroupName()) { + CreationAnalyzer::GroupInfo group_name_info = { + .group_name = selector_options_parser_.GroupName().value(), + .default_group = false + }; + return group_name_info; + } + // auto-generate group name + std::vector ids; + ids.reserve(per_instance_infos.size()); + for (const auto& per_instance_info : per_instance_infos) { + ids.push_back(per_instance_info.instance_id_); + } + std::string base_name = GenDefaultGroupName(); + + /* We cannot return simply "cvd" as we do not want duplication in the group + * name across the instance groups owned by the user. Note that the set of ids + * are expected to be unique to the user, so we use the ids. If ever the end + * user happened to have already used the generated name, we did our best, and + * cvd start will fail with a proper error message. + */ + auto unique_suffix = + std::to_string(*std::min_element(ids.begin(), ids.end())); + CreationAnalyzer::GroupInfo group_name_info = { + .group_name = base_name + "_" + unique_suffix, + .default_group = selector_options_parser_.IsMaybeDefaultGroup() + }; + return group_name_info; +} + +Result CreationAnalyzer::AnalyzeHome() const { + auto system_wide_home = CF_EXPECT(SystemWideUserHome()); + if (Contains(envs_, "HOME") && envs_.at("HOME") != system_wide_home) { + return envs_.at("HOME"); + } + + CF_EXPECT(!group_name_.empty(), + "To auto-generate HOME, the group name is a must."); + const auto client_uid = credential_.uid; + const auto client_gid = credential_.gid; + std::string auto_generated_home = + CF_EXPECT(ParentOfAutogeneratedHomes(client_uid, client_gid)); + auto_generated_home.append("/" + std::to_string(client_uid)); + auto_generated_home.append("/" + group_name_); + CF_EXPECT(EnsureDirectoryExists(auto_generated_home)); + return auto_generated_home; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/creation_analyzer.h b/base/cvd/cuttlefish/host/commands/cvd/selector/creation_analyzer.h new file mode 100644 index 0000000000..d9e62094ef --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/creation_analyzer.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2022 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 + +#include +#include // for ucred + +#include +#include +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "common/libs/utils/unique_resource_allocator.h" +#include "host/commands/cvd/instance_lock.h" +#include "host/commands/cvd/selector/instance_database.h" +#include "host/commands/cvd/selector/start_selector_parser.h" + +namespace cuttlefish { +namespace selector { + +struct PerInstanceInfo { + // for the sake of std::vector::emplace_back + PerInstanceInfo(const unsigned id, const std::string& per_instance_name, + InstanceLockFile&& instance_file_lock) + : instance_id_(id), + per_instance_name_(per_instance_name), + instance_file_lock_(std::move(instance_file_lock)) {} + + PerInstanceInfo(const unsigned id, const std::string& per_instance_name) + : instance_id_(id), per_instance_name_(per_instance_name) {} + + const unsigned instance_id_; + const std::string per_instance_name_; + std::optional instance_file_lock_; +}; + +/** + * Creation is currently group by group + * + * If you want one instance, you should create a group with one instance. + */ +struct GroupCreationInfo { + std::string home; + std::string host_artifacts_path; ///< e.g. out/host/linux-x86 + // set to host_artifacts_path if no ANDROID_PRODUCT_OUT + std::string product_out_path; + std::string group_name; + std::vector instances; + std::vector args; + std::unordered_map envs; + bool is_default_group { false }; +}; + +/** + * Instance IDs: + * Use the InstanceNumCalculator's logic + * + * HOME directory: + * If given in envs and is different from the system-wide home, use it + * If not, try ParentOfAutogeneratedHomes()/uid/${group_name} + * + * host_artifacts_path: + * ANDROID_HOST_OUT must be given. + * + * Group name: + * if a group name is not given, automatically generate: + * default_prefix + "_" + one_of_ids + * + * Per-instance name: + * When not given, use std::string(id) as the per instance name of each + * + * Number of instances: + * Controlled by --instance_nums, --num_instances, etc. + * Also controlled by --instance_name + * + * p.s. + * dependency: (a-->b means b depends on a) + * group_name --> HOME + * instance ids --> per_instance_name + * + */ +class CreationAnalyzer { + public: + struct CreationAnalyzerParam { + const std::vector& cmd_args; + const std::unordered_map& envs; + const std::vector& selector_args; + }; + + struct GroupInfo { + std::string group_name; + const bool default_group; + }; + + static Result Analyze( + const std::string& cmd, const CreationAnalyzerParam& param, + const ucred& credential, const InstanceDatabase& instance_database, + InstanceLockFileManager& instance_lock_file_manager); + + private: + using IdAllocator = UniqueResourceAllocator; + + CreationAnalyzer(const CreationAnalyzerParam& param, const ucred& credential, + StartSelectorParser&& selector_options_parser, + const InstanceDatabase& instance_database, + InstanceLockFileManager& instance_lock_file_manager); + + Result Analyze(); + + /** + * calculate n_instances_ and instance_ids_ + */ + Result> AnalyzeInstanceIds(); + + /* + * When group name is nil, it is auto-generated using instance ids + * + * If the instanc group is the default one, the group name is cvd. Otherwise, + * for given instance ids, {i}, the group name will be cvd_i. + */ + Result ExtractGroup( + const std::vector&) const; + + /** + * Figures out the HOME directory + * + * The issue is that many times, HOME is anyway implicitly given. Thus, only + * if the HOME value is not equal to the HOME directory recognized by the + * system, it can be safely regarded as overridden by the user. + * + * If that is not the case, we use a automatically generated value as HOME. + * If the group instance is the default one, we still use the user's system- + * widely recognized home. If not, we populate them user /tmp/.cf// + * + */ + Result AnalyzeHome() const; + + Result> AnalyzeInstanceIdsInternal(); + Result> AnalyzeInstanceIdsInternal( + const std::vector& requested_instance_ids); + + /* + * Adds --webrtc_device_id when necessary to cmd_args_ + */ + Result> UpdateWebrtcDeviceId( + std::vector&& args, + const std::vector& per_instance_info); + + // inputs + std::vector cmd_args_; + std::unordered_map envs_; + std::vector selector_args_; + const ucred credential_; + + // information to return later + std::string home_; + std::string group_name_; + + // internal, temporary + StartSelectorParser selector_options_parser_; + const InstanceDatabase& instance_database_; + InstanceLockFileManager& instance_file_lock_manager_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/device_selector_utils.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/device_selector_utils.cpp new file mode 100644 index 0000000000..fa42e971cf --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/device_selector_utils.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/selector/device_selector_utils.h" + +#include "common/libs/utils/users.h" +#include "host/commands/cvd/selector/selector_constants.h" + +namespace cuttlefish { +namespace selector { + +Result GetDefaultGroup( + const InstanceDatabase& instance_database) { + const auto& all_groups = instance_database.InstanceGroups(); + if (all_groups.size() == 1) { + return *(all_groups.front()); + } + std::string system_wide_home = CF_EXPECT(SystemWideUserHome()); + auto group = + CF_EXPECT(instance_database.FindGroup({kHomeField, system_wide_home})); + return group; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/device_selector_utils.h b/base/cvd/cuttlefish/host/commands/cvd/selector/device_selector_utils.h new file mode 100644 index 0000000000..c2edca90bf --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/device_selector_utils.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 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 + +/** + * @file Utils shared by device selectors for non-start operations + * + */ + +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/selector/instance_database.h" + +namespace cuttlefish { +namespace selector { + +Result GetDefaultGroup( + const InstanceDatabase& instance_database); + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/group_selector.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/group_selector.cpp new file mode 100644 index 0000000000..df2d815af2 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/group_selector.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/selector/group_selector.h" + +#include + +#include "common/libs/utils/contains.h" +#include "host/commands/cvd/selector/device_selector_utils.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { +namespace selector { + +Result GroupSelector::GetSelector( + const cvd_common::Args& selector_args, const Queries& extra_queries, + const cvd_common::Envs& envs) { + cvd_common::Args selector_args_copied{selector_args}; + SelectorCommonParser common_parser = + CF_EXPECT(SelectorCommonParser::Parse(selector_args_copied, envs)); + std::stringstream unused_args; + unused_args << "{"; + for (const auto& arg : selector_args_copied) { + unused_args << arg << ", "; + } + std::string unused_arg_list = unused_args.str(); + if (!selector_args_copied.empty()) { + unused_arg_list.pop_back(); + unused_arg_list.pop_back(); + } + unused_arg_list.append("}"); + if (!selector_args_copied.empty()) { + LOG(ERROR) << "Warning: there are unused selector options. " + << unused_arg_list; + } + + // search by group and instances + // search by HOME if overridden + Queries queries; + if (IsHomeOverridden(common_parser)) { + CF_EXPECT(common_parser.Home()); + queries.emplace_back(kHomeField, common_parser.Home().value()); + } + if (common_parser.GroupName()) { + queries.emplace_back(kGroupNameField, common_parser.GroupName().value()); + } + if (common_parser.PerInstanceNames()) { + const auto per_instance_names = common_parser.PerInstanceNames().value(); + for (const auto& per_instance_name : per_instance_names) { + queries.emplace_back(kInstanceNameField, per_instance_name); + } + } + // if CUTTLEFISH_INSTANCE is set, cvd start should ignore if there's + // --base_instance_num, etc. cvd start has its own custom logic. Thus, + // non-start operations cannot share the SelectorCommonParser to parse + // the environment variable. It should be here. + if (Contains(envs, kCuttlefishInstanceEnvVarName)) { + int id; + auto cuttlefish_instance = envs.at(kCuttlefishInstanceEnvVarName); + CF_EXPECT(android::base::ParseInt(cuttlefish_instance, &id)); + queries.emplace_back(kInstanceIdField, cuttlefish_instance); + } + + for (const auto& extra_query : extra_queries) { + queries.push_back(extra_query); + } + + GroupSelector group_selector(queries); + return group_selector; +} + +bool GroupSelector::IsHomeOverridden( + const SelectorCommonParser& common_parser) { + auto home_overridden_result = common_parser.HomeOverridden(); + if (!home_overridden_result.ok()) { + return false; + } + return *home_overridden_result; +} + +Result GroupSelector::FindGroup( + const InstanceDatabase& instance_database) { + if (queries_.empty()) { + auto default_group = CF_EXPECT(FindDefaultGroup(instance_database)); + return default_group; + } + auto groups = CF_EXPECT(instance_database.FindGroups(queries_)); + CF_EXPECT(groups.size() == 1, "groups.size() = " << groups.size()); + return *(groups.cbegin()); +} + +Result GroupSelector::FindDefaultGroup( + const InstanceDatabase& instance_database) { + auto group = CF_EXPECT(GetDefaultGroup(instance_database)); + return group; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/group_selector.h b/base/cvd/cuttlefish/host/commands/cvd/selector/group_selector.h new file mode 100644 index 0000000000..07db518868 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/group_selector.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "common/libs/utils/result.h" +#include "common/libs/utils/users.h" +#include "host/commands/cvd/selector/instance_database.h" +#include "host/commands/cvd/selector/instance_database_types.h" +#include "host/commands/cvd/selector/selector_common_parser.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace selector { + +class GroupSelector { + public: + static Result GetSelector( + const cvd_common::Args& selector_args, const Queries& extra_queries, + const cvd_common::Envs& envs); + /* + * If default, try running single instance group. If multiple, try to find + * HOME == SystemWideUserHome. If not exists, give up. + * + * If group given, find group, and check if all instance names are included + * + * If group not given, not yet supported. Will be in next CLs + */ + Result FindGroup( + const InstanceDatabase& instance_database); + + private: + GroupSelector(const Queries& queries) + : queries_{queries} {} + + // used by Select() + static bool IsHomeOverridden(const SelectorCommonParser& common_parser); + + Result FindDefaultGroup( + const InstanceDatabase& instance_database); + + const Queries queries_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database.cpp new file mode 100644 index 0000000000..75d69536e0 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/instance_database.h" + +#include // std::iota + +#include "host/commands/cvd/selector/instance_database_utils.h" +#include "host/commands/cvd/selector/selector_constants.h" + +namespace cuttlefish { +namespace selector { + +InstanceDatabase::InstanceDatabase() { + group_handlers_[kHomeField] = [this](const Value& field_value) { + return FindGroupsByHome(field_value); + }; + group_handlers_[kInstanceIdField] = [this](const Value& field_value) { + return FindGroupsById(field_value); + }; + group_handlers_[kGroupNameField] = [this](const Value& field_value) { + return FindGroupsByGroupName(field_value); + }; + group_handlers_[kInstanceNameField] = [this](const Value& field_value) { + return FindGroupsByInstanceName(field_value); + }; + instance_handlers_[kHomeField] = [this](const Value& field_value) { + return FindInstancesByHome(field_value); + }; + instance_handlers_[kInstanceIdField] = [this](const Value& field_value) { + return FindInstancesById(field_value); + }; + instance_handlers_[kGroupNameField] = [this](const Value& field_value) { + return FindInstancesByGroupName(field_value); + }; + instance_handlers_[kInstanceNameField] = [this](const Value& field_value) { + return FindInstancesByInstanceName(field_value); + }; +} + +bool InstanceDatabase::IsEmpty() const { + return local_instance_groups_.empty(); +} + +Result>> InstanceDatabase::FindGroups( + const Query& query) const { + return Find(query, group_handlers_); +} + +Result>> InstanceDatabase::FindGroups( + const Queries& queries) const { + return Find(queries, group_handlers_); +} + +Result>> InstanceDatabase::FindInstances( + const Query& query) const { + return Find(query, instance_handlers_); +} + +Result>> InstanceDatabase::FindInstances( + const Queries& queries) const { + return Find(queries, instance_handlers_); +} + +Result> InstanceDatabase::FindGroup( + const Query& query) const { + return FindOne(query, group_handlers_); +} + +Result> InstanceDatabase::FindGroup( + const Queries& queries) const { + return FindOne(queries, group_handlers_); +} + +Result> InstanceDatabase::FindInstance( + const Query& query) const { + return FindOne(query, instance_handlers_); +} + +Result> InstanceDatabase::FindInstance( + const Queries& queries) const { + return FindOne(queries, instance_handlers_); +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database.h b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database.h new file mode 100644 index 0000000000..2fcab8996b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include + +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/selector/constant_reference.h" +#include "host/commands/cvd/selector/instance_database_types.h" +#include "host/commands/cvd/selector/instance_group_record.h" +#include "host/commands/cvd/selector/instance_record.h" + +namespace cuttlefish { +namespace selector { + +// TODO(kwstephenkim): make this per-user instance database +class InstanceDatabase { + template + using ConstHandler = std::function>>(const Value&)>; + + using ConstGroupHandler = ConstHandler; + using ConstInstanceHandler = ConstHandler; + + public: + static constexpr const char kJsonGroups[] = "Groups"; + + InstanceDatabase(); + bool IsEmpty() const; + + struct AddInstanceGroupParam { + std::string group_name; + std::string home_dir; + std::string host_artifacts_path; + std::string product_out_path; + TimeStamp start_time; + }; + /** Adds instance group. + * + * If group_name or home_dir is already taken or host_artifacts_path is + * not likely an artifacts path, CF_ERR is returned. + */ + Result> AddInstanceGroup( + const AddInstanceGroupParam& param); + + Json::Value Serialize() const; + Result LoadFromJson(const Json::Value&); + + /** + * Adds instance to the group. + * + * If id is duplicated in the scope of the InstanceDatabase or instance_name + * is not unique within the group, CF_ERR is returned. + */ + Result AddInstance(const std::string& group_name, const unsigned id, + const std::string& instance_name); + + struct InstanceInfo { + const unsigned id; + const std::string name; + }; + Result AddInstances(const std::string& group_name, + const std::vector& instances); + + /* + * auto group = CF_EXPEC(FindGroups(...)); + * RemoveInstanceGroup(group) + */ + bool RemoveInstanceGroup(const LocalInstanceGroup& group); + bool RemoveInstanceGroup(const std::string& group_name); + void Clear(); + + Result>> FindGroups( + const Query& query) const; + Result>> FindGroups( + const Queries& queries) const; + Result>> FindInstances(const Query& query) const; + Result>> FindInstances( + const Queries& queries) const; + const auto& InstanceGroups() const { return local_instance_groups_; } + + /* + * FindGroup/Instance method must be used when exactly one instance/group + * is expected to match the query + */ + Result> FindGroup(const Query& query) const; + Result> FindGroup(const Queries& queries) const; + Result> FindInstance(const Query& query) const; + Result> FindInstance(const Queries& queries) const; + + private: + template + Result>> Find( + const Query& query, + const Map>& handler_map) const { + static_assert(std::is_same::value || + std::is_same::value); + const auto& [key, value] = query; + auto itr = handler_map.find(key); + if (itr == handler_map.end()) { + return CF_ERR("Handler does not exist for query " << key); + } + return (itr->second)(value); +} + + template + Result>> Find( + const Queries& queries, + const Map>& handler_map) const { + static_assert(std::is_same::value || + std::is_same::value); + if (queries.empty()) { + return CF_ERR("Queries must not be empty"); + } + auto first_set = CF_EXPECT(Find(queries[0], handler_map)); + for (int i = 1; i < queries.size(); i++) { + auto subset = CF_EXPECT(Find(queries[i], handler_map)); + first_set = Intersection(first_set, subset); + } + return {first_set}; +} + + template + Result> FindOne( + const Query& query, + const Map>& handler_map) const { + auto set = CF_EXPECT(Find(query, handler_map)); + CF_EXPECT_EQ(set.size(), 1, "Only one Instance (Group) is allowed."); + return {*set.cbegin()}; +} + + template + Result> FindOne( + const Queries& queries, + const Map>& handler_map) const { + auto set = CF_EXPECT(Find(queries, handler_map)); + CF_EXPECT_EQ(set.size(), 1, "Only one Instance (Group) is allowed."); + return {*set.cbegin()}; +} + + std::vector>::iterator FindIterator( + const LocalInstanceGroup& group); + + // actual Find implementations + Result>> FindGroupsByHome( + const Value& home) const; + Result>> FindGroupsById( + const Value& id) const; + Result>> FindGroupsByGroupName( + const Value& group_name) const; + Result>> FindGroupsByInstanceName( + const Value& instance_name) const; + Result>> FindInstancesByHome( + const Value& home) const; + Result>> FindInstancesById(const Value& id) const; + Result>> FindInstancesByGroupName( + const Value& instance_specific_name) const; + Result>> FindInstancesByInstanceName( + const Value& group_name) const; + + Result FindMutableGroup(const std::string& group_name); + + Result LoadGroupFromJson(const Json::Value& group_json); + + std::vector> local_instance_groups_; + Map group_handlers_; + Map instance_handlers_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_impl.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_impl.cpp new file mode 100644 index 0000000000..73dc561fcd --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_impl.cpp @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/instance_database.h" + +#include +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/files.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/selector/instance_database_utils.h" +#include "host/commands/cvd/selector/selector_constants.h" + +namespace cuttlefish { +namespace selector { + +std::vector>::iterator +InstanceDatabase::FindIterator(const LocalInstanceGroup& group) { + for (auto itr = local_instance_groups_.begin(); + itr != local_instance_groups_.end(); itr++) { + if (itr->get() == std::addressof(group)) { + return itr; + } + } + // must not reach here + return local_instance_groups_.end(); +} + +void InstanceDatabase::Clear() { local_instance_groups_.clear(); } + +Result> InstanceDatabase::AddInstanceGroup( + const AddInstanceGroupParam& param) { + CF_EXPECTF(IsValidGroupName(param.group_name), + "GroupName \"{}\" is ill-formed.", param.group_name); + CF_EXPECTF(EnsureDirectoryExists(param.home_dir), + "HOME dir, \"{}\" neither exists nor can be created.", + param.home_dir); + CF_EXPECTF(PotentiallyHostArtifactsPath(param.host_artifacts_path), + "ANDROID_HOST_OUT, \"{}\" is not a tool directory", + param.host_artifacts_path); + std::vector queries = {{kHomeField, param.home_dir}, + {kGroupNameField, param.group_name}}; + for (const auto& query : queries) { + auto instance_groups = + CF_EXPECT(Find(query, group_handlers_)); + CF_EXPECTF(instance_groups.empty(), "[\"{}\" : \"{}\"] is already taken", + query.field_name_, query.field_value_); + } + auto new_group = + new LocalInstanceGroup({.group_name = param.group_name, + .home_dir = param.home_dir, + .host_artifacts_path = param.host_artifacts_path, + .product_out_path = param.product_out_path, + .start_time = param.start_time}); + CF_EXPECT(new_group != nullptr); + local_instance_groups_.emplace_back(new_group); + const auto raw_ptr = local_instance_groups_.back().get(); + ConstRef const_ref = *raw_ptr; + return {const_ref}; +} + +Result InstanceDatabase::AddInstance(const std::string& group_name, + const unsigned id, + const std::string& instance_name) { + LocalInstanceGroup* group_ptr = CF_EXPECT(FindMutableGroup(group_name)); + LocalInstanceGroup& group = *group_ptr; + + CF_EXPECTF(IsValidInstanceName(instance_name), + "instance_name \"{}\" is invalid", instance_name); + auto itr = FindIterator(group); + CF_EXPECTF(itr != local_instance_groups_.end() && *itr != nullptr, + "Adding instances to non-existing group \"{}\"", + group.InternalGroupName()); + + auto instances = + CF_EXPECT(FindInstances({kInstanceIdField, std::to_string(id)})); + CF_EXPECTF(instances.empty(), "instance id \"{}\" is taken.", id); + + auto instances_by_name = CF_EXPECT((*itr)->FindByInstanceName(instance_name)); + CF_EXPECTF(instances_by_name.empty(), + "instance name \"{}\" is already taken.", instance_name); + return (*itr)->AddInstance(id, instance_name); +} + +Result InstanceDatabase::AddInstances( + const std::string& group_name, const std::vector& instances) { + for (const auto& instance_info : instances) { + CF_EXPECT(AddInstance(group_name, instance_info.id, instance_info.name)); + } + return {}; +} + +Result InstanceDatabase::FindMutableGroup( + const std::string& group_name) { + LocalInstanceGroup* group_ptr = nullptr; + for (auto& group_uniq_ptr : local_instance_groups_) { + if (group_uniq_ptr && group_uniq_ptr->GroupName() == group_name) { + group_ptr = group_uniq_ptr.get(); + break; + } + } + CF_EXPECTF(group_ptr != nullptr, + "Instance Group named as \"{}\" is not found.", group_name); + return group_ptr; +} + +bool InstanceDatabase::RemoveInstanceGroup(const std::string& group_name) { + auto group_result = FindGroup({kGroupNameField, group_name}); + if (!group_result.ok()) { + return false; + } + const LocalInstanceGroup& group = group_result->Get(); + return RemoveInstanceGroup(group); +} + +bool InstanceDatabase::RemoveInstanceGroup(const LocalInstanceGroup& group) { + auto itr = FindIterator(group); + // *itr is the reference to the unique pointer object + if (itr == local_instance_groups_.end() || !(*itr)) { + return false; + } + local_instance_groups_.erase(itr); + return true; +} + +Result>> InstanceDatabase::FindGroupsByHome( + const std::string& home) const { + auto subset = CollectToSet( + local_instance_groups_, + [&home](const std::unique_ptr& group) { + if (!group) { + return false; + } + if (group->HomeDir() == home) { + return true; + } + if (group->HomeDir().empty() || home.empty()) { + return false; + } + // The two paths must be an absolute path. + // this is guaranteed by the CreationAnalyzer + std::string home_realpath; + std::string group_home_realpath; + if (!android::base::Realpath(home, std::addressof(home_realpath))) { + return false; + } + if (!android::base::Realpath(group->HomeDir(), + std::addressof(group_home_realpath))) { + return false; + } + return home_realpath == group_home_realpath; + }); + return AtMostOne(subset, GenerateTooManyInstancesErrorMsg(1, kHomeField)); +} + +Result>> +InstanceDatabase::FindGroupsByGroupName(const std::string& group_name) const { + auto subset = CollectToSet( + local_instance_groups_, + [&group_name](const std::unique_ptr& group) { + return (group && group->GroupName() == group_name); + }); + return AtMostOne(subset, + GenerateTooManyInstancesErrorMsg(1, kGroupNameField)); +} + +Result>> InstanceDatabase::FindGroupsById( + const std::string& id_str) const { + auto subset = CollectToSet( + local_instance_groups_, + [&id_str](const std::unique_ptr& group) { + if (!group) { + return false; + } + int id; + if (!android::base::ParseInt(id_str, &id)) { + return false; + } + auto group_set_result = group->FindById(static_cast(id)); + return group_set_result.ok() && (group_set_result->size() == 1); + }); + return subset; +} + +Result>> +InstanceDatabase::FindGroupsByInstanceName( + const std::string& instance_name) const { + auto subset = CollectToSet( + local_instance_groups_, + [&instance_name](const std::unique_ptr& group) { + if (!group) { + return false; + } + auto instance_set_result = group->FindByInstanceName(instance_name); + return instance_set_result.ok() && (instance_set_result->size() == 1); + }); + return subset; +} + +Result>> InstanceDatabase::FindInstancesByHome( + const std::string& home) const { + auto collector = + [&home](const std::unique_ptr& group) + -> Result>> { + CF_EXPECT(group != nullptr); + if (group->HomeDir() != home) { + return Set>{}; + } + return (group->FindAllInstances()); + }; + return CollectAllElements( + collector, local_instance_groups_); +} + +Result>> InstanceDatabase::FindInstancesById( + const std::string& id) const { + int parsed_int = 0; + CF_EXPECTF(android::base::ParseInt(id, &parsed_int), + "\"{}\" cannot be converted to an integer.", id); + auto collector = + [parsed_int](const std::unique_ptr& group) + -> Result>> { + CF_EXPECT(group != nullptr); + return group->FindById(parsed_int); + }; + auto subset = CollectAllElements( + collector, local_instance_groups_); + CF_EXPECT(subset.ok()); + return AtMostOne(*subset, + GenerateTooManyInstancesErrorMsg(1, kInstanceIdField)); +} + +Result>> +InstanceDatabase::FindInstancesByInstanceName( + const Value& instance_specific_name) const { + auto collector = [&instance_specific_name]( + const std::unique_ptr& group) + -> Result>> { + CF_EXPECT(group != nullptr); + return (group->FindByInstanceName(instance_specific_name)); + }; + return CollectAllElements( + collector, local_instance_groups_); +} + +Result>> InstanceDatabase::FindInstancesByGroupName( + const Value& group_name) const { + auto collector = + [&group_name](const std::unique_ptr& group) + -> Result>> { + CF_EXPECT(group != nullptr); + if (group->GroupName() != group_name) { + Set> empty_set; + return empty_set; + } + return (group->FindAllInstances()); + }; + return CollectAllElements( + collector, local_instance_groups_); +} + +Json::Value InstanceDatabase::Serialize() const { + Json::Value instance_db_json; + int i = 0; + Json::Value group_array; + for (const auto& local_instance_group : local_instance_groups_) { + group_array[i] = local_instance_group->Serialize(); + ++i; + } + instance_db_json[kJsonGroups] = group_array; + return instance_db_json; +} + +Result InstanceDatabase::LoadGroupFromJson( + const Json::Value& group_json) { + const std::string group_name = + group_json[LocalInstanceGroup::kJsonGroupName].asString(); + const std::string home_dir = + group_json[LocalInstanceGroup::kJsonHomeDir].asString(); + const std::string host_artifacts_path = + group_json[LocalInstanceGroup::kJsonHostArtifactPath].asString(); + const std::string product_out_path = + group_json[LocalInstanceGroup::kJsonProductOutPath].asString(); + TimeStamp start_time = CvdServerClock::now(); + + // test if the field is available as the field has been added + // recently as of b/315855286 + if (group_json.isMember(LocalInstanceGroup::kJsonStartTime)) { + auto restored_start_time_result = DeserializeTimePoint(group_json); + if (restored_start_time_result.ok()) { + start_time = std::move(*restored_start_time_result); + } else { + LOG(ERROR) << "Start time restoration from json failed, so we use " + << " the current system time. Reasons: " + << restored_start_time_result.error().FormatForEnv(); + } + } + const auto new_group_ref = + CF_EXPECT(AddInstanceGroup({.group_name = group_name, + .home_dir = home_dir, + .host_artifacts_path = host_artifacts_path, + .product_out_path = product_out_path, + .start_time = std::move(start_time)})); + android::base::ScopeGuard remove_already_added_new_group( + [&new_group_ref, this]() { + this->RemoveInstanceGroup(new_group_ref.Get()); + }); + const Json::Value& instances_json_array = + group_json[LocalInstanceGroup::kJsonInstances]; + for (int i = 0; i < instances_json_array.size(); i++) { + const Json::Value& instance_json = instances_json_array[i]; + const std::string instance_name = + instance_json[LocalInstance::kJsonInstanceName].asString(); + const std::string instance_id = + instance_json[LocalInstance::kJsonInstanceId].asString(); + + int id; + auto parse_result = + android::base::ParseInt(instance_id, std::addressof(id)); + CF_EXPECTF(parse_result == true, "Invalid instance ID in instance json: {}", + instance_id); + CF_EXPECTF(AddInstance(group_name, id, instance_name), + "Adding instance [{} : \"{}\"] to the group \"{}\" failed.", + instance_name, id, group_name); + } + remove_already_added_new_group.Disable(); + return {}; +} + +Result InstanceDatabase::LoadFromJson(const Json::Value& db_json) { + const Json::Value& group_array = db_json[kJsonGroups]; + int n_groups = group_array.size(); + for (int i = 0; i < n_groups; i++) { + CF_EXPECT(LoadGroupFromJson(group_array[i])); + } + return {}; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_types.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_types.cpp new file mode 100644 index 0000000000..1ab81c965a --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_types.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/instance_database_types.h" + +#include +#include +#include + +#include +#include + +#include "host/commands/cvd/selector/instance_group_record.h" + +namespace cuttlefish { +namespace selector { + +Query::Query(const std::string& field_name, const std::string& field_value) + : field_name_(field_name), field_value_(field_value) {} + +std::string SerializeTimePoint(const TimeStamp& present) { + const auto duration = + std::chrono::duration_cast(present.time_since_epoch()); + return fmt::format("{}", duration.count()); +} + +Result DeserializeTimePoint(const Json::Value& group_json) { + std::string group_name = "unknown"; + if (group_json.isMember(LocalInstanceGroup::kJsonGroupName)) { + group_name = group_json[LocalInstanceGroup::kJsonGroupName].asString(); + } + CF_EXPECTF(group_json.isMember(LocalInstanceGroup::kJsonStartTime), + "The serialized instance database in json file for group \"{}\"" + " is missing the start time field: {}", + group_name, LocalInstanceGroup::kJsonStartTime); + std::string serialized( + group_json[LocalInstanceGroup::kJsonStartTime].asString()); + + using CountType = decltype(((const CvdTimeDuration*)nullptr)->count()); + CountType count = 0; + CF_EXPECTF(android::base::ParseInt(serialized, &count), + "Failed to serialize: {}", serialized); + CvdTimeDuration duration(count); + TimeStamp restored_time(duration); + LOG(VERBOSE) << "The start time of the group \"" << group_name + << "\" is restored as: " << Format(restored_time); + return restored_time; +} + +std::string Format(const TimeStamp& time_point) { + auto tc = CvdServerClock::to_time_t(time_point); + std::stringstream ss; + ss << std::put_time(std::localtime(&tc), "%F %T"); + return ss.str(); +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_types.h b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_types.h new file mode 100644 index 0000000000..7b69b769be --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_types.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include +#include +#include +#include +#include + +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { +namespace selector { +namespace selector_impl { + +template +using ToStringTypeReturnType = + decltype(void(std::to_string(std::declval()))); + +template +struct IsToStringOk : std::false_type {}; + +template +struct IsToStringOk> : std::true_type {}; + +} // namespace selector_impl + +using FieldName = std::string; +using Value = std::string; +// e.g. if intended to search by --home=/home/vsoc-01, +// field_name_ is "home" and the field_value_ is "/home/vsoc-01" +struct Query { + template ::value, void>> + Query(const std::string& field_name, ValueType&& field_value) + : field_name_(field_name), + field_value_(std::to_string(std::forward(field_value))) {} + Query(const std::string& field_name, const std::string& field_value); + + FieldName field_name_; + Value field_value_; +}; +using Queries = std::vector; + +template +using Set = std::unordered_set; + +template +using Map = std::unordered_map; + +using CvdServerClock = std::chrono::system_clock; +using TimeStamp = std::chrono::time_point; +using CvdTimeDuration = std::chrono::milliseconds; + +std::string SerializeTimePoint(const TimeStamp&); +Result DeserializeTimePoint(const Json::Value& group_json); +std::string Format(const TimeStamp&); + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_utils.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_utils.cpp new file mode 100644 index 0000000000..14f663f286 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_utils.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/instance_database_utils.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/utils/files.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { +namespace selector { + +Result GetCuttlefishConfigPath(const std::string& home) { + std::string home_realpath; + CF_EXPECT(DirectoryExists(home), "Invalid Home Directory"); + CF_EXPECT(android::base::Realpath(home, &home_realpath)); + static const char kSuffix[] = "/cuttlefish_assembly/cuttlefish_config.json"; + std::string config_path = AbsolutePath(home_realpath + kSuffix); + CF_EXPECT(FileExists(config_path), "No config file exists"); + return {config_path}; +} + +std::string GenInternalGroupName() { + std::string_view internal_name{kCvdNamePrefix}; // "cvd-" + internal_name.remove_suffix(1); // "cvd" + return std::string(internal_name); +} + +std::string GenDefaultGroupName() { return GenInternalGroupName(); } + +std::string LocalDeviceNameRule(const std::string& group_name, + const std::string& instance_name) { + return group_name + "-" + instance_name; +} + +bool IsValidGroupName(const std::string& token) { + std::regex regular_expr("[A-Za-z_][A-Za-z_0-9]*"); + return std::regex_match(token, regular_expr); +} + +bool IsValidInstanceName(const std::string& token) { + if (token.empty()) { + return true; + } + std::regex base_regular_expr("[A-Za-z_0-9]+"); + auto pieces = android::base::Split(token, "-"); + for (const auto& piece : pieces) { + if (!std::regex_match(piece, base_regular_expr)) { + return false; + } + } + return true; +} + +Result BreakDeviceName(const std::string& device_name) { + CF_EXPECT(!device_name.empty()); + CF_EXPECT(Contains(device_name, '-')); + auto dash_pos = device_name.find_first_of('-'); + // - must be neither the first nor the last character + CF_EXPECT(dash_pos != 0 && dash_pos != (device_name.size() - 1)); + const auto group_name = device_name.substr(0, dash_pos); + const auto instance_name = device_name.substr(dash_pos + 1); + return DeviceName{.group_name = group_name, + .per_instance_name = instance_name}; +} + +bool IsValidDeviceName(const std::string& token) { + if (token.empty()) { + return false; + } + auto result = BreakDeviceName(token); + if (!result.ok()) { + return false; + } + const auto [group_name, instance_name] = *result; + return IsValidGroupName(group_name) && IsValidInstanceName(instance_name); +} + +bool PotentiallyHostArtifactsPath(const std::string& host_artifacts_path) { + if (host_artifacts_path.empty() || !DirectoryExists(host_artifacts_path)) { + return false; + } + const auto host_bin_path = host_artifacts_path + "/bin"; + auto contents_result = DirectoryContents(host_bin_path); + if (!contents_result.ok()) { + return false; + } + std::vector contents = std::move(*contents_result); + std::set contents_set{std::move_iterator(contents.begin()), + std::move_iterator(contents.end())}; + std::set launchers = {"cvd", "launch_cvd"}; + std::vector result; + std::set_intersection(launchers.cbegin(), launchers.cend(), + contents_set.cbegin(), contents_set.cend(), + std::back_inserter(result)); + return !result.empty(); +} + +std::string GenerateTooManyInstancesErrorMsg(const int n, + const std::string& field_name) { + std::stringstream s; + s << "Only up to " << n << " must match"; + if (!field_name.empty()) { + s << " by the field " << field_name; + } + return s.str(); +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_utils.h b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_utils.h new file mode 100644 index 0000000000..198064afd0 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_utils.h @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include + +#include "common/libs/utils/collect.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/selector/constant_reference.h" +#include "host/commands/cvd/selector/instance_database_types.h" + +namespace cuttlefish { +namespace selector { + +Result GetCuttlefishConfigPath(const std::string& home); + +std::string GenInternalGroupName(); +std::string GenDefaultGroupName(); +std::string LocalDeviceNameRule(const std::string& group_name, + const std::string& instance_name); + +// [A-Za-z0-9_]+, e.g. 0, tv, my_phone07, etc +// Or, it can include "-" in the middle +// ([A-Za-z0-9_]+[-])*[A-Za-z0-9_] +bool IsValidInstanceName(const std::string& token); + +// [A-Za-z_][A-Za-z0-9_]*, e.g. cool_group, cv0_d, cf, etc +// but can't start with [0-9] +bool IsValidGroupName(const std::string& token); + +// - +bool IsValidDeviceName(const std::string& token); + +struct DeviceName { + std::string group_name; + std::string per_instance_name; +}; +Result BreakDeviceName(const std::string& device_name); + +/** + * Runs simple tests to see if it could potentially be a host artifacts dir + * + */ +bool PotentiallyHostArtifactsPath(const std::string& host_binaries_dir); + +/** + * simply returns: + * + * "Only up to n must match" or + * "Only up to n must match by field " + FieldName + * + */ +std::string GenerateTooManyInstancesErrorMsg(const int n, + const std::string& field_name); + +/** + * return all the elements in container that satisfies predicate. + * + * Container has Wrappers, where each Wrapper is typically, + * std::unique/shared_ptr of T, or some wrapper of T, etc. Set is a set of T. + * + * This method returns the Set of T, as long as its corresponding Wrapper in + * Container meets the predicate. + */ +template +Set Collect(const Container& container, + std::function predicate, + std::function convert) { + Set output; + for (const auto& t : container) { + if (!predicate(t)) { + continue; + } + output.insert(convert(t)); + } + return output; +} + +/* + * Returns a Set of ConstRef, which essentially satisfies "predicate" + * + * Container has a list/set of std::unique_ptr. We collect all the + * const references of each object owned by Container, which meets the + * condition defined by predicate. + * + */ +template +Set> CollectToSet( + Container&& container, + std::function&)> predicate) { + auto convert = [](const std::unique_ptr& uniq_ptr) { + return Cref(*uniq_ptr); + }; + return Collect, std::unique_ptr, Set>>( + std::forward(container), std::move(predicate), + std::move(convert)); +} + +/** + * Given: + * Containers have a list of n `Container`s. Each Container may have + * m Element. Each is stored as a unique_ptr. + * + * Goal: + * To collect Elements from each Container with Container's APIs. The + * collected Elements meet the condition implicitly defined in collector. + * + * E.g. InstanceDatabase has InstanceGroups, each has Instances. We want + * all the Instances its build-target was TV. Then, collector will look + * like this: + * [&build_target](const std::unique_ptr& group) { + * return group->FindByBuildTarget(build_target); + * } + * + * We take the union of all the returned subsets from each collector call. + */ +template +Result>> CollectAllElements( + std::function< + Result>>(const std::unique_ptr&)> + collector, + const Containers& outermost_container) { + Set> output; + for (const auto& container_ptr : outermost_container) { + auto subset = CF_EXPECT(collector(container_ptr)); + output.insert(subset.cbegin(), subset.cend()); + } + return {output}; +} + +template +Result::type> AtMostOne( + S&& s, const std::string& err_msg) { + CF_EXPECT(AtMostN(std::forward(s), 1), err_msg); + return {std::forward(s)}; +} + +template +RetSet Intersection(const RetSet& u, AnyContainer&& v) { + RetSet result; + if (u.empty() || v.empty()) { + return result; + } + for (auto const& e : v) { + if (Contains(u, e)) { + result.insert(e); + } + } + return result; +} + +template +RetSet Intersection(const RetSet& u, AnyContainer&& v, Containers&&... s) { + RetSet first = Intersection(u, std::forward(v)); + if (first.empty()) { + return first; + } + return Intersection(first, std::forward(s)...); +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_group_record.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_group_record.cpp new file mode 100644 index 0000000000..d07b586eab --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_group_record.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/instance_group_record.h" + +#include "host/commands/cvd/selector/instance_database_types.h" +#include "host/commands/cvd/selector/instance_database_utils.h" +#include "host/commands/cvd/selector/selector_constants.h" + +namespace cuttlefish { +namespace selector { + +LocalInstanceGroup::LocalInstanceGroup(const InstanceGroupParam& param) + : home_dir_{param.home_dir}, + host_artifacts_path_{param.host_artifacts_path}, + product_out_path_{param.product_out_path}, + internal_group_name_(GenInternalGroupName()), + group_name_(param.group_name), + start_time_(param.start_time) { + LOG(VERBOSE) << "Creating a group \"" << group_name_ << "\" (" + << Format(start_time_) << ")"; +} + +LocalInstanceGroup::LocalInstanceGroup(const LocalInstanceGroup& src) + : home_dir_{src.home_dir_}, + host_artifacts_path_{src.host_artifacts_path_}, + product_out_path_{src.product_out_path_}, + internal_group_name_{src.internal_group_name_}, + group_name_{src.group_name_}, + start_time_{src.start_time_}, + instances_{CopyInstances(src.instances_)} {} + +LocalInstanceGroup& LocalInstanceGroup::operator=( + const LocalInstanceGroup& src) { + if (this == std::addressof(src)) { + return *this; + } + home_dir_ = src.home_dir_; + host_artifacts_path_ = src.host_artifacts_path_; + product_out_path_ = src.product_out_path_; + internal_group_name_ = src.internal_group_name_; + group_name_ = src.group_name_; + instances_ = CopyInstances(src.instances_); + return *this; +} + +Set> LocalInstanceGroup::CopyInstances( + const Set>& src_instances) { + Set> copied; + // Due to the const reference to the parent, LocalInstanceGroup, + // the LocalInstance class does not have a copy constructor + for (const auto& src_instance : src_instances) { + LocalInstance* new_instance = new LocalInstance( + *this, src_instance->InstanceId(), src_instance->PerInstanceName()); + copied.emplace(new_instance); + } + return copied; +} + +Result LocalInstanceGroup::GetCuttlefishConfigPath() const { + return ::cuttlefish::selector::GetCuttlefishConfigPath(HomeDir()); +} + +Result LocalInstanceGroup::AddInstance(const unsigned instance_id, + const std::string& instance_name) { + if (HasInstance(instance_id)) { + return CF_ERR("Instance Id " << instance_id << " is taken"); + } + LocalInstance* instance = + new LocalInstance(*this, instance_id, instance_name); + instances_.emplace(std::unique_ptr(instance)); + return {}; +} + +Result>> LocalInstanceGroup::FindById( + const unsigned id) const { + auto subset = CollectToSet( + instances_, [&id](const std::unique_ptr& instance) { + return instance && (instance->InstanceId() == id); + }); + return AtMostOne(subset, + GenerateTooManyInstancesErrorMsg(1, kInstanceIdField)); +} + +Result>> LocalInstanceGroup::FindByInstanceName( + const std::string& instance_name) const { + auto subset = CollectToSet( + instances_, + [&instance_name](const std::unique_ptr& instance) { + return instance && (instance->PerInstanceName() == instance_name); + }); + + // note that inside a group, the instance name is unique. However, + // across groups, they can be multiple + return AtMostOne(subset, + GenerateTooManyInstancesErrorMsg(1, kInstanceNameField)); +} + +Result>> LocalInstanceGroup::FindAllInstances() + const { + auto subset = CollectToSet( + instances_, [](const std::unique_ptr& instance) { + if (instance) { + return true; + } + return false; + }); + return subset; +} + +bool LocalInstanceGroup::HasInstance(const unsigned instance_id) const { + for (const auto& instance : instances_) { + if (!instance) { + continue; + } + if (instance_id == instance->InstanceId()) { + return true; + } + } + return false; +} + +Json::Value LocalInstanceGroup::Serialize() const { + Json::Value group_json; + group_json[kJsonGroupName] = group_name_; + group_json[kJsonHomeDir] = home_dir_; + group_json[kJsonHostArtifactPath] = host_artifacts_path_; + group_json[kJsonProductOutPath] = product_out_path_; + group_json[kJsonStartTime] = SerializeTimePoint(start_time_); + + int i = 0; + Json::Value instances_array_json; + for (const auto& instance : instances_) { + Json::Value instance_json = Serialize(instance); + instance_json[kJsonParent] = group_name_; + instances_array_json[i] = instance_json; + i++; + } + group_json[kJsonInstances] = instances_array_json; + return group_json; +} + +Json::Value LocalInstanceGroup::Serialize( + const std::unique_ptr& instance) const { + Json::Value instance_json; + if (!instance) { + return instance_json; + } + instance_json[LocalInstance::kJsonInstanceName] = instance->PerInstanceName(); + instance_json[LocalInstance::kJsonInstanceId] = + std::to_string(instance->InstanceId()); + return instance_json; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_group_record.h b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_group_record.h new file mode 100644 index 0000000000..9099fb8309 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_group_record.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2022 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 + +#include +#include + +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/selector/constant_reference.h" +#include "host/commands/cvd/selector/instance_database_types.h" +#include "host/commands/cvd/selector/instance_record.h" + +namespace cuttlefish { +namespace selector { + +class InstanceDatabase; + +/** + * TODO(kwstephenkim): add more methods, fields, and abstract out Instance + * + * Needs design changes to support both Remote Instances + */ +class LocalInstanceGroup { + public: + struct InstanceGroupParam { + std::string group_name; + std::string home_dir; + std::string host_artifacts_path; + std::string product_out_path; + TimeStamp start_time; + }; + LocalInstanceGroup(const InstanceGroupParam& param); + LocalInstanceGroup(const LocalInstanceGroup& src); + LocalInstanceGroup& operator=(const LocalInstanceGroup& src); + + const std::string& InternalGroupName() const { return internal_group_name_; } + const std::string& GroupName() const { return group_name_; } + const std::string& HomeDir() const { return home_dir_; } + const std::string& HostArtifactsPath() const { return host_artifacts_path_; } + const std::string& ProductOutPath() const { return product_out_path_; } + Result GetCuttlefishConfigPath() const; + const Set>& Instances() const { + return instances_; + } + Json::Value Serialize() const; + auto StartTime() const { return start_time_; } + + /** + * return error if instance id of instance is taken AND that taken id + * belongs to this group + */ + Result AddInstance(const unsigned instance_id, + const std::string& instance_name); + bool HasInstance(const unsigned instance_id) const; + Result>> FindById(const unsigned id) const; + /** + * Find by per-instance name. + * + * If the device name is cvd-foo or cvd-4, "cvd" is the group name, + * "foo" or "4" is the per-instance names, and "cvd-foo" or "cvd-4" is + * the device name. + */ + Result>> FindByInstanceName( + const std::string& instance_name) const; + + // returns all instances in the dedicated data type + Result>> FindAllInstances() const; + + static constexpr const char kJsonGroupName[] = "Group Name"; + static constexpr const char kJsonHomeDir[] = "Runtime/Home Dir"; + static constexpr const char kJsonHostArtifactPath[] = "Host Tools Dir"; + static constexpr const char kJsonProductOutPath[] = "Product Out Dir"; + static constexpr const char kJsonStartTime[] = "Start Time"; + static constexpr const char kJsonInstances[] = "Instances"; + static constexpr const char kJsonParent[] = "Parent Group"; + + private: + // Eventually copies the instances of a src to *this + Set> CopyInstances( + const Set>& src_instances); + Json::Value Serialize(const std::unique_ptr& instance) const; + + std::string home_dir_; + std::string host_artifacts_path_; + std::string product_out_path_; + + // for now, "cvd", which is "cvd-".remove_suffix(1) + std::string internal_group_name_; + std::string group_name_; + TimeStamp start_time_; + Set> instances_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_record.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_record.cpp new file mode 100644 index 0000000000..debc31175f --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_record.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/instance_record.h" + +#include "host/commands/cvd/selector/instance_database_utils.h" +#include "host/commands/cvd/selector/instance_group_record.h" + +namespace cuttlefish { +namespace selector { + +LocalInstance::LocalInstance(const LocalInstanceGroup& parent_group, + const unsigned instance_id, + const std::string& instance_name) + : parent_group_(parent_group), + instance_id_(instance_id), + internal_name_(std::to_string(instance_id_)), + per_instance_name_(instance_name) {} + +unsigned LocalInstance::InstanceId() const { return instance_id_; } + +std::string LocalInstance::InternalDeviceName() const { + return LocalDeviceNameRule(parent_group_.InternalGroupName(), internal_name_); +} + +const std::string& LocalInstance::InternalName() const { + return internal_name_; +} + +std::string LocalInstance::DeviceName() const { + return LocalDeviceNameRule(parent_group_.GroupName(), per_instance_name_); +} + +const std::string& LocalInstance::PerInstanceName() const { + return per_instance_name_; +} + +const LocalInstanceGroup& LocalInstance::ParentGroup() const { + return parent_group_; +} + +LocalInstance::Copy LocalInstance::GetCopy() const { + Copy copy(*this); + return copy; +} + +LocalInstance::Copy::Copy(const LocalInstance& src) + : internal_name_{src.InternalName()}, + internal_device_name_{src.InternalDeviceName()}, + instance_id_{src.InstanceId()}, + per_instance_name_{src.PerInstanceName()}, + device_name_{src.DeviceName()}, + mock_group_{MockParentParam{ + .home_dir = src.ParentGroup().HomeDir(), + .host_artifacts_path = src.ParentGroup().HostArtifactsPath(), + .internal_group_name = src.ParentGroup().InternalGroupName(), + .group_name = src.ParentGroup().GroupName(), + .start_time = src.ParentGroup().StartTime()}} {} + +LocalInstance::Copy::MockParent::MockParent(const MockParentParam& params) + : home_dir_{params.home_dir}, + host_artifacts_path_{params.host_artifacts_path}, + internal_group_name_{params.internal_group_name}, + group_name_{params.group_name}, + start_time_{params.start_time} {} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_record.h b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_record.h new file mode 100644 index 0000000000..db7e69044c --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_record.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2022 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 + +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/selector/instance_database_types.h" + +namespace cuttlefish { +namespace selector { + +class LocalInstanceGroup; + +/** + * TODO(kwstephenkim): add more methods, fields, and abstract out Instance + * + * Needs design changes to support both Remote and Local Instances + */ +class LocalInstance { + friend class LocalInstanceGroup; + friend class InstanceDatabase; + + public: + static constexpr const char kJsonInstanceId[] = "Instance Id"; + static constexpr const char kJsonInstanceName[] = "Per-Instance Name"; + + /* names: + * + * Many components in Cuttlefish traditionally expect the name to be "cvd-N," + * and rely on "N" to avoid conflicts in the global resource uses. + * + * Thus, we will eventually maintain the internal device name for those + * existing cuttlefish implementation, and the user-given name. + * + */ + const std::string& InternalName() const; + std::string InternalDeviceName() const; + + unsigned InstanceId() const; + const std::string& PerInstanceName() const; + std::string DeviceName() const; + + const LocalInstanceGroup& ParentGroup() const; + + class Copy { + friend class LocalInstance; + struct MockParentParam { + std::string home_dir; + std::string host_artifacts_path; + std::string internal_group_name; + std::string group_name; + TimeStamp start_time; + }; + + public: + /* when Copy is used, it is already disconnected from the original parent + * group. Thus, it should carry the snapshot of needed information about + * the parent group + */ + class MockParent { + public: + MockParent(const MockParentParam&); + const std::string& InternalGroupName() const { + return internal_group_name_; + } + const std::string& GroupName() const { return group_name_; } + const std::string& HomeDir() const { return home_dir_; } + const std::string& HostArtifactsPath() const { + return host_artifacts_path_; + } + auto StartTime() const { return start_time_; } + + private: + std::string home_dir_; + std::string host_artifacts_path_; + std::string internal_group_name_; + std::string group_name_; + TimeStamp start_time_; + }; + Copy(const LocalInstance& src); + const std::string& InternalName() const { return internal_name_; } + const std::string& InternalDeviceName() const { + return internal_device_name_; + } + unsigned InstanceId() const { return instance_id_; } + const std::string& PerInstanceName() const { return per_instance_name_; } + const std::string& DeviceName() const { return device_name_; } + const MockParent& ParentGroup() const { return mock_group_; } + + private: + std::string internal_name_; + std::string internal_device_name_; + unsigned instance_id_; + std::string per_instance_name_; + std::string device_name_; + MockParent mock_group_; + }; + Copy GetCopy() const; + + private: + LocalInstance(const LocalInstanceGroup& parent_group, + const unsigned instance_id, const std::string& instance_name); + + const LocalInstanceGroup& parent_group_; + unsigned instance_id_; + std::string internal_name_; ///< for now, it is to_string(instance_id_) + /** the instance specific name to be appended to the group name + * + * by default, to_string(instance_id_). The default value is decided by + * InstanceGroupRecord, as that's the only class that will create this + * instance + */ + std::string per_instance_name_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_selector.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_selector.cpp new file mode 100644 index 0000000000..b6cbc36bbd --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_selector.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/selector/instance_selector.h" + +#include + +#include "host/commands/cvd/selector/device_selector_utils.h" +#include "host/commands/cvd/selector/instance_database_types.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { +namespace selector { + +Result InstanceSelector::GetSelector( + const cvd_common::Args& selector_args, const Queries& extra_queries, + const cvd_common::Envs& envs) { + cvd_common::Args selector_args_copied{selector_args}; + SelectorCommonParser common_parser = + CF_EXPECT(SelectorCommonParser::Parse(selector_args_copied, envs)); + std::stringstream unused_args; + unused_args << "{"; + for (const auto& arg : selector_args_copied) { + unused_args << arg << ", "; + } + std::string unused_arg_list = unused_args.str(); + if (!selector_args_copied.empty()) { + unused_arg_list.pop_back(); + unused_arg_list.pop_back(); + } + unused_arg_list.append("}"); + if (!selector_args_copied.empty()) { + LOG(ERROR) << "Warning: there are unused selector options. " + << unused_arg_list; + } + + // search by instance and instances + // search by HOME if overridden + Queries queries; + if (IsHomeOverridden(common_parser)) { + CF_EXPECT(common_parser.Home()); + queries.emplace_back(kHomeField, common_parser.Home().value()); + } + if (common_parser.GroupName()) { + queries.emplace_back(kGroupNameField, common_parser.GroupName().value()); + } + if (common_parser.PerInstanceNames()) { + const auto per_instance_names = common_parser.PerInstanceNames().value(); + CF_EXPECT_LE(per_instance_names.size(), 1, + "Instance Selector only picks up to 1 instance and thus " + "only take up to 1 instance_name"); + if (!per_instance_names.empty()) { + queries.emplace_back(kInstanceNameField, per_instance_names.front()); + } + } + if (Contains(envs, kCuttlefishInstanceEnvVarName)) { + int id; + const std::string instance_id_str = envs.at(kCuttlefishInstanceEnvVarName); + if (android::base::ParseInt(instance_id_str, std::addressof(id))) { + queries.emplace_back(kInstanceIdField, std::to_string(id)); + } else { + LOG(ERROR) << kCuttlefishInstanceEnvVarName << "=" << id + << " was given but it must have one valid instance ID."; + } + } + + for (const auto& extra_query : extra_queries) { + queries.push_back(extra_query); + } + + InstanceSelector instance_selector(queries); + return instance_selector; +} + +bool InstanceSelector::IsHomeOverridden( + const SelectorCommonParser& common_parser) { + auto home_overridden_result = common_parser.HomeOverridden(); + if (!home_overridden_result.ok()) { + return false; + } + return *home_overridden_result; +} + +Result InstanceSelector::FindInstance( + const InstanceDatabase& instance_database) { + if (queries_.empty()) { + auto default_instance = CF_EXPECT(FindDefaultInstance(instance_database)); + return default_instance; + } + + auto instances = CF_EXPECT(instance_database.FindInstances(queries_)); + CF_EXPECT(instances.size() == 1, "instances.size() = " << instances.size()); + auto& instance = *(instances.cbegin()); + return instance.Get().GetCopy(); +} + +Result InstanceSelector::FindDefaultInstance( + const InstanceDatabase& instance_database) { + auto group = CF_EXPECT(GetDefaultGroup(instance_database)); + const auto instances = CF_EXPECT(group.FindAllInstances()); + CF_EXPECT_EQ(instances.size(), 1, + "Default instance is the single instance in the default group."); + return instances.cbegin()->Get().GetCopy(); +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/instance_selector.h b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_selector.h new file mode 100644 index 0000000000..7419c7bdc7 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/instance_selector.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "common/libs/utils/result.h" +#include "common/libs/utils/users.h" +#include "host/commands/cvd/selector/instance_database.h" +#include "host/commands/cvd/selector/selector_common_parser.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace selector { + +class InstanceSelector { + public: + static Result GetSelector( + const cvd_common::Args& selector_args, const Queries& extra_queries, + const cvd_common::Envs& envs); + /* + * If default, try running single instance group. If multiple, try to find + * HOME == SystemWideUserHome. If not exists, give up. + * + * If group given, find group, and check if all instance names are included + * + * If group not given, not yet supported. Will be in next CLs + */ + Result FindInstance( + const InstanceDatabase& instance_database); + + private: + InstanceSelector( Queries& queries) + : queries_(queries) {} + static bool IsHomeOverridden(const SelectorCommonParser& common_parser); + + Result FindDefaultInstance( + const InstanceDatabase& instance_database); + + const Queries queries_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/selector_common_parser.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_common_parser.cpp new file mode 100644 index 0000000000..715c118ec4 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_common_parser.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/selector/selector_common_parser.h" + +#include + +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/users.h" +#include "host/commands/cvd/selector/instance_database_utils.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/selector/selector_option_parser_utils.h" + +namespace cuttlefish { +namespace selector { + +Result SelectorCommonParser::Parse( + cvd_common::Args& selector_args, const cvd_common::Envs& envs) { + std::string system_wide_home = CF_EXPECT(SystemWideUserHome()); + SelectorCommonParser parser(system_wide_home, envs); + CF_EXPECT(parser.ParseOptions(selector_args)); + return std::move(parser); +} + +SelectorCommonParser::SelectorCommonParser(const std::string& client_user_home, + const cvd_common::Envs& envs) + : client_user_home_(client_user_home), envs_{envs} {} + +Result SelectorCommonParser::HomeOverridden() const { + return Contains(envs_, "HOME") && (client_user_home_ != envs_.at("HOME")); +} + +std::optional SelectorCommonParser::Home() const { + if (Contains(envs_, "HOME")) { + return envs_.at("HOME"); + } + return std::nullopt; +} + +Result SelectorCommonParser::ParseOptions( + cvd_common::Args& selector_args) { + // Handling name-related options + auto group_name_flag = + CF_EXPECT(SelectorFlags::Get().GetFlag(SelectorFlags::kGroupName)); + auto instance_name_flag = + CF_EXPECT(SelectorFlags::Get().GetFlag(SelectorFlags::kInstanceName)); + std::optional group_name_opt = + CF_EXPECT(group_name_flag.FilterFlag(selector_args)); + std::optional instance_name_opt = + CF_EXPECT(instance_name_flag.FilterFlag(selector_args)); + + NameFlagsParam name_flags_param{.group_name = group_name_opt, + .instance_names = instance_name_opt}; + auto parsed_name_flags = CF_EXPECT(HandleNameOpts(name_flags_param)); + group_name_ = parsed_name_flags.group_name; + instance_names_ = parsed_name_flags.instance_names; + return {}; +} + +Result +SelectorCommonParser::HandleNameOpts(const NameFlagsParam& name_flags) const { + std::optional group_name_output; + std::optional> instance_names_output; + if (name_flags.group_name) { + group_name_output = CF_EXPECT(HandleGroupName(name_flags.group_name)); + } + + if (name_flags.instance_names) { + instance_names_output = + CF_EXPECT(HandleInstanceNames(name_flags.instance_names)); + } + return {ParsedNameFlags{.group_name = std::move(group_name_output), + .instance_names = std::move(instance_names_output)}}; +} + +Result> SelectorCommonParser::HandleInstanceNames( + const std::optional& per_instance_names) const { + CF_EXPECT(per_instance_names.has_value()); + + auto instance_names = android::base::Split(per_instance_names.value(), ","); + std::unordered_set duplication_check; + for (const auto& instance_name : instance_names) { + CF_EXPECT(IsValidInstanceName(instance_name)); + // Check that provided non-empty instance names are unique. Empty names will + // be replaced later with defaults guaranteed to be unique. + CF_EXPECT(instance_name.empty() || !Contains(duplication_check, instance_name)); + duplication_check.insert(instance_name); + } + return instance_names; +} + +Result SelectorCommonParser::HandleGroupName( + const std::optional& group_name) const { + CF_EXPECT(group_name && !group_name.value().empty()); + CF_EXPECT(IsValidGroupName(group_name.value()), group_name.value() + << " failed"); + return {group_name.value()}; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/selector_common_parser.h b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_common_parser.h new file mode 100644 index 0000000000..6178157e18 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_common_parser.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include +#include +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace selector { + +class SelectorCommonParser { + public: + // parses common selector options, and drop the used selector_args + static Result Parse(cvd_common::Args& selector_args, + const cvd_common::Envs& envs); + + std::optional GroupName() const { return group_name_; } + + std::optional> PerInstanceNames() const { + return instance_names_; + } + + // CF_ERR --> unknown, true --> overridden, false --> not overridden. + Result HomeOverridden() const; + std::optional Home() const; + + /* + * returns if selector flags has device select options: e.g. --group_name + * + * this is mainly to see if cvd start is about the default instance. + */ + bool HasDeviceSelectOption() const { return group_name_ || instance_names_; } + + private: + SelectorCommonParser(const std::string& client_user_home, + const cvd_common::Envs& envs); + + Result ParseOptions(cvd_common::Args& selector_args); + struct ParsedNameFlags { + std::optional group_name; + std::optional> instance_names; + }; + struct NameFlagsParam { + std::optional group_name; + std::optional instance_names; + }; + Result HandleNameOpts( + const NameFlagsParam& name_flags) const; + Result HandleGroupName( + const std::optional& group_name) const; + Result> HandleInstanceNames( + const std::optional& per_instance_names) const; + + // temporarily keeps the leftover of the input cmd_args + // Will be never used after parsing is done + std::string client_user_home_; + const cvd_common::Envs& envs_; + + // processed result + std::optional group_name_; + std::optional> instance_names_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/selector_constants.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_constants.cpp new file mode 100644 index 0000000000..6ec7cd69dd --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_constants.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/selector_constants.h" + +#include +#include + +#include +#include + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/users.h" + +namespace cuttlefish { +namespace selector { + +enum class OwnershipType { kUser, kGroup, kOthers }; + +static OwnershipType GetOwnershipType(const struct stat& file_stat, + const uid_t uid, const gid_t gid) { + if (file_stat.st_uid == uid) { + return OwnershipType::kUser; + } + if (file_stat.st_gid == gid) { + return OwnershipType::kGroup; + } + return OwnershipType::kOthers; +} + +struct RequirePermission { + const bool needs_read_permission; + const bool needs_write_permission; + const bool needs_exec_permission; +}; + +static Result CheckPermission(const OwnershipType ownership_type, + const struct stat& file_stat, + const RequirePermission& perm) { + const auto perm_bits = file_stat.st_mode; + + switch (ownership_type) { + case OwnershipType::kUser: { + CF_EXPECT(!perm.needs_read_permission || (perm_bits & S_IRUSR)); + CF_EXPECT(!perm.needs_write_permission || (perm_bits & S_IWUSR)); + CF_EXPECT(!perm.needs_exec_permission || (perm_bits & S_IXUSR)); + return {}; + } + case OwnershipType::kGroup: { + CF_EXPECT(!perm.needs_read_permission || (perm_bits & S_IRGRP)); + CF_EXPECT(!perm.needs_write_permission || (perm_bits & S_IWGRP)); + CF_EXPECT(!perm.needs_exec_permission || (perm_bits & S_IXGRP)); + return {}; + } + case OwnershipType::kOthers: + break; + } + CF_EXPECT(!perm.needs_read_permission || (perm_bits & S_IROTH)); + CF_EXPECT(!perm.needs_write_permission || (perm_bits & S_IWOTH)); + CF_EXPECT(!perm.needs_exec_permission || (perm_bits & S_IXOTH)); + return {}; +} + +static Result CheckPermission(const std::string& dir, + const uid_t client_uid, + const gid_t client_gid) { + CF_EXPECT(!dir.empty() && DirectoryExists(dir)); + struct stat dir_stat; + CF_EXPECT_EQ(stat(dir.c_str(), std::addressof(dir_stat)), 0); + + const auto server_ownership = GetOwnershipType(dir_stat, getuid(), getgid()); + CF_EXPECT(CheckPermission(server_ownership, dir_stat, + RequirePermission{.needs_read_permission = true, + .needs_write_permission = true, + .needs_exec_permission = true})); + const auto client_ownership = + GetOwnershipType(dir_stat, client_uid, client_gid); + CF_EXPECT(CheckPermission(client_ownership, dir_stat, + RequirePermission{.needs_read_permission = true, + .needs_write_permission = true, + .needs_exec_permission = true})); + return {}; +} + +Result ParentOfAutogeneratedHomes(const uid_t client_uid, + const gid_t client_gid) { + std::deque try_dirs = { + StringFromEnv("TMPDIR", ""), + StringFromEnv("TEMP", ""), + StringFromEnv("TMP", ""), + "/tmp", + "/var/tmp", + "/usr/tmp", + }; + + auto system_wide_home = SystemWideUserHome(); + if (system_wide_home.ok()) { + try_dirs.emplace_back(*system_wide_home); + } + try_dirs.emplace_back(AbsolutePath(".")); + while (!try_dirs.empty()) { + const auto candidate = std::move(try_dirs.front()); + try_dirs.pop_front(); + if (candidate.empty() || !EnsureDirectoryExists(candidate).ok()) { + continue; + } + CF_EXPECT(CheckPermission(candidate, client_uid, client_gid)); + return AbsolutePath(candidate); + } + return CF_ERR("Tried all candidate directories but none was read-writable."); +} + +CvdFlag SelectorFlags::GroupNameFlag(const std::string& name) { + CvdFlag group_name{name}; + std::stringstream group_name_help; + group_name_help << "--" << name << "=<" + << "name of the instance group>"; + group_name.SetHelpMessage(group_name_help.str()); + return group_name; +} + +CvdFlag SelectorFlags::InstanceNameFlag(const std::string& name) { + CvdFlag instance_name{name}; + std::stringstream instance_name_help; + instance_name_help << "--" << name << "=<" + << "comma-separated names of the instances>"; + instance_name.SetHelpMessage(instance_name_help.str()); + return instance_name; +} + +CvdFlag SelectorFlags::AcquireFileLockFlag(const std::string& name, + const bool default_val) { + CvdFlag acquire_file_lock(name, default_val); + std::stringstream help; + help << "--" << name + << "=false for cvd server not to acquire lock file locks."; + acquire_file_lock.SetHelpMessage(help.str()); + return acquire_file_lock; +} + +const SelectorFlags& SelectorFlags::Get() { + static Result singleton = New(); + CHECK(singleton.ok()) << singleton.error().FormatForEnv(); + return *singleton; +} + +Result SelectorFlags::New() { + SelectorFlags selector_flags; + CF_EXPECT(selector_flags.flags_.EnrollFlag(GroupNameFlag(kGroupName))); + CF_EXPECT(selector_flags.flags_.EnrollFlag(InstanceNameFlag(kInstanceName))); + CF_EXPECT(selector_flags.flags_.EnrollFlag( + AcquireFileLockFlag(kAcquireFileLock, true))); + CF_EXPECT(selector_flags.flags_.EnrollFlag(VerbosityFlag(kVerbosity))); + return selector_flags; +} + +CvdFlag SelectorFlags::VerbosityFlag(const std::string& name) { + CvdFlag verbosity_level(name); + std::stringstream help; + help << "--" << name << "=Severity for LOG(Severity) in the server."; + verbosity_level.SetHelpMessage(help.str()); + return verbosity_level; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/selector_constants.h b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_constants.h new file mode 100644 index 0000000000..c94d857047 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_constants.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 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 + +#include + +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/flag.h" + +namespace cuttlefish { +namespace selector { + +/** The direct parent of auto-generated runtime directories, which + * is recommended to be short. + * + * Try these one by one in order, and append /.cf + * + * 1. $TMPDIR + * 2. $TEMP + * 3. $TMP + * 4. /tmp + * 5. /var/tmp + * 6. /usr/tmp + * 7. HOME of uid + * + */ +Result ParentOfAutogeneratedHomes(const uid_t client_uid, + const gid_t client_gid); + +/* + * These are fields in instance database + * + */ +constexpr char kGroupNameField[] = "group_name"; +constexpr char kHomeField[] = "home"; +constexpr char kInstanceIdField[] = "instance_id"; +/* per_instance_name + * + * by default, to_string(instance_id), and users can override it + */ +constexpr char kInstanceNameField[] = "instance_name"; + +/** + * The authentic collection of selector flags + * + */ +// names of the flags, which are also used for search + +class SelectorFlags { + public: + static constexpr char kGroupName[] = "group_name"; + static constexpr char kInstanceName[] = "instance_name"; + static constexpr char kAcquireFileLock[] = "acquire_file_lock"; + static constexpr char kAcquireFileLockEnv[] = "CVD_ACQUIRE_FILE_LOCK"; + static constexpr char kVerbosity[] = "verbosity"; + static const SelectorFlags& Get(); + static Result New(); + + Result GetFlag(const std::string& search_key) const { + auto flag = CF_EXPECT(flags_.GetFlag(search_key)); + return flag; + } + + std::vector Flags() const { return flags_.Flags(); } + const auto& FlagsAsCollection() const { return flags_; } + + private: + SelectorFlags() = default; + + static CvdFlag GroupNameFlag(const std::string& name); + static CvdFlag InstanceNameFlag(const std::string& name); + static CvdFlag AcquireFileLockFlag(const std::string& name, + const bool default_val); + static CvdFlag VerbosityFlag(const std::string& name); + + FlagCollection flags_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/selector_option_parser_utils.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_option_parser_utils.cpp new file mode 100644 index 0000000000..7eda2905a5 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_option_parser_utils.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/selector_option_parser_utils.h" + +#include + +namespace cuttlefish { +namespace selector { + +Result> SeparateButWithNoEmptyToken( + const std::string& input, const std::string& delimiter) { + auto tokens = android::base::Split(input, delimiter); + for (const auto& t : tokens) { + CF_EXPECT(!t.empty()); + } + return tokens; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/selector_option_parser_utils.h b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_option_parser_utils.h new file mode 100644 index 0000000000..fb34ba014b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/selector_option_parser_utils.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { +namespace selector { + +/* + * @return any parsing successfully and actually happened + */ +template +Result FilterSelectorFlag(std::vector& args, + const std::string& flag_name, + std::optional& value_opt) { + value_opt = std::nullopt; + const int args_initial_size = args.size(); + if (args_initial_size == 0) { + return {}; + } + + T value; + CF_EXPECT(ConsumeFlags({GflagsCompatFlag(flag_name, value)}, args), + "Failed to parse --" << flag_name); + if (args.size() == args_initial_size) { + // not consumed + return {}; + } + value_opt = value; + return {}; +} + +/* + * android::base::Split by delimeter but returns CF_ERR if any split token is + * empty + */ +Result> SeparateButWithNoEmptyToken( + const std::string& input, const std::string& delimiter); + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/start_selector_parser.cpp b/base/cvd/cuttlefish/host/commands/cvd/selector/start_selector_parser.cpp new file mode 100644 index 0000000000..6f0ec66b3a --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/start_selector_parser.cpp @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/selector/start_selector_parser.h" + +#include + +#include +#include +#include + +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/users.h" +#include "host/commands/cvd/selector/instance_database_utils.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/selector/selector_option_parser_utils.h" +#include "host/commands/cvd/types.h" +#include "host/libs/config/config_constants.h" +#include "host/libs/config/instance_nums.h" + +namespace cuttlefish { +namespace selector { + +static bool Unique(const std::vector& v) { + std::unordered_set hash_set(v.begin(), v.end()); + return v.size() == hash_set.size(); +} + +static Result ParseNaturalNumber(const std::string& token) { + std::int32_t value; + CF_EXPECT(android::base::ParseInt(token, &value)); + CF_EXPECT(value > 0); + return static_cast(value); +} + +Result StartSelectorParser::ConductSelectFlagsParser( + const cvd_common::Args& selector_args, + const cvd_common::Args& cmd_args, const cvd_common::Envs& envs) { + const std::string system_wide_home = CF_EXPECT(SystemWideUserHome()); + cvd_common::Args selector_args_copied{selector_args}; + StartSelectorParser parser( + system_wide_home, selector_args_copied, cmd_args, envs, + CF_EXPECT(SelectorCommonParser::Parse(selector_args_copied, envs))); + CF_EXPECT(parser.ParseOptions(), "selector option flag parsing failed."); + return {std::move(parser)}; +} + +StartSelectorParser::StartSelectorParser( + const std::string& system_wide_user_home, + const cvd_common::Args& selector_args, const cvd_common::Args& cmd_args, + const cvd_common::Envs& envs, SelectorCommonParser&& common_parser) + : client_user_home_{system_wide_user_home}, + selector_args_(selector_args), + cmd_args_(cmd_args), + envs_(envs), + common_parser_(std::move(common_parser)) {} + +std::optional StartSelectorParser::GroupName() const { + return group_name_; +} + +std::optional> StartSelectorParser::PerInstanceNames() + const { + return per_instance_names_; +} + +namespace { + +std::optional TryFromCuttlefishInstance( + const cvd_common::Envs& envs) { + if (!Contains(envs, kCuttlefishInstanceEnvVarName)) { + return std::nullopt; + } + const auto cuttlefish_instance = envs.at(kCuttlefishInstanceEnvVarName); + if (cuttlefish_instance.empty()) { + return std::nullopt; + } + auto parsed = ParseNaturalNumber(cuttlefish_instance); + return parsed.ok() ? std::optional(*parsed) : std::nullopt; +} + +} // namespace + +std::optional> +StartSelectorParser::InstanceFromEnvironment( + const InstanceFromEnvParam& params) { + const auto& cuttlefish_instance_env = params.cuttlefish_instance_env; + const auto& vsoc_suffix = params.vsoc_suffix; + const auto& num_instances = params.num_instances; + + // see the logic in cuttlefish::InstanceFromEnvironment() + // defined in host/libs/config/cuttlefish_config.cpp + std::vector nums; + std::optional base; + if (cuttlefish_instance_env) { + base = *cuttlefish_instance_env; + } + if (!base && vsoc_suffix) { + base = *vsoc_suffix; + } + if (!base) { + return std::nullopt; + } + // this is guaranteed by the caller + // assert(num_instances != std::nullopt); + for (unsigned i = 0; i != *num_instances; i++) { + nums.emplace_back(base.value() + i); + } + return nums; +} + +Result StartSelectorParser::VerifyNumOfInstances( + const VerifyNumOfInstancesParam& params, + const unsigned default_n_instances) const { + const auto& num_instances_flag = params.num_instances_flag; + const auto& instance_names = params.instance_names; + const auto& instance_nums_flag = params.instance_nums_flag; + + std::optional num_instances; + if (num_instances_flag) { + num_instances = CF_EXPECT(ParseNaturalNumber(*num_instances_flag)); + } + if (instance_names && !instance_names->empty()) { + auto implied_n_instances = instance_names->size(); + if (num_instances) { + CF_EXPECT_EQ(*num_instances, static_cast(implied_n_instances), + "The number of instances requested by --num_instances " + << " are not the same as what is implied by " + << " --instance_name."); + } + num_instances = implied_n_instances; + } + if (instance_nums_flag) { + std::vector tokens = + android::base::Split(*instance_nums_flag, ","); + for (const auto& t : tokens) { + CF_EXPECT(ParseNaturalNumber(t), t << " must be a natural number"); + } + if (!num_instances) { + num_instances = tokens.size(); + } + CF_EXPECT_EQ(*num_instances, tokens.size(), + "All information for the number of instances must match."); + } + return num_instances.value_or(default_n_instances); +} + +static Result> ParseInstanceNums( + const std::string& instance_nums_flag) { + std::vector nums; + std::vector tokens = + android::base::Split(instance_nums_flag, ","); + for (const auto& t : tokens) { + unsigned num = + CF_EXPECT(ParseNaturalNumber(t), t << " must be a natural number"); + nums.emplace_back(num); + } + CF_EXPECT(Unique(nums), "--instance_nums include duplicated numbers"); + return nums; +} + +Result +StartSelectorParser::HandleInstanceIds( + const InstanceIdsParams& instance_id_params) { + const auto& instance_nums = instance_id_params.instance_nums; + const auto& base_instance_num = instance_id_params.base_instance_num; + const auto& cuttlefish_instance_env = + instance_id_params.cuttlefish_instance_env; + const auto& vsoc_suffix = instance_id_params.vsoc_suffix; + + // calculate and/or verify the number of instances + unsigned num_instances = + CF_EXPECT(VerifyNumOfInstances(VerifyNumOfInstancesParam{ + .num_instances_flag = instance_id_params.num_instances, + .instance_names = PerInstanceNames(), + .instance_nums_flag = instance_nums})); + + if (!instance_nums && !base_instance_num) { + // num_instances is given. if non-std::nullopt is returned, + // the base is also figured out. If base can't be figured out, + // std::nullopt is returned. + auto instance_ids = InstanceFromEnvironment( + {.cuttlefish_instance_env = cuttlefish_instance_env, + .vsoc_suffix = vsoc_suffix, + .num_instances = num_instances}); + if (instance_ids) { + return ParsedInstanceIdsOpt(*instance_ids); + } + // the return value, n_instances is the "desired/requested" instances + // When instance_ids set isn't figured out, n_instances is not meant to + // be always zero; it could be any natural number. + return ParsedInstanceIdsOpt(num_instances); + } + + InstanceNumsCalculator calculator; + calculator.NumInstances(static_cast(num_instances)); + if (instance_nums) { + CF_EXPECT(base_instance_num == std::nullopt, + "-base_instance_num and -instance_nums are mutually exclusive."); + std::vector parsed_nums = + CF_EXPECT(ParseInstanceNums(*instance_nums)); + return ParsedInstanceIdsOpt(parsed_nums); + } + if (base_instance_num) { + unsigned base = CF_EXPECT(ParseNaturalNumber(*base_instance_num)); + calculator.BaseInstanceNum(static_cast(base)); + } + auto instance_ids = CF_EXPECT(calculator.CalculateFromFlags()); + CF_EXPECT(!instance_ids.empty(), + "CalculateFromFlags() must be called when --num_instances or " + << "--base_instance_num is given, and must not return an " + << "empty set"); + auto instance_ids_vector = + std::vector{instance_ids.begin(), instance_ids.end()}; + return ParsedInstanceIdsOpt{instance_ids_vector}; +} + +Result StartSelectorParser::CalcMayBeDefaultGroup() { + /* + * the logic to determine whether this group is the default one or not: + * If HOME is not overridden and no selector options, then + * the default group + * Or, not a default group + * + */ + if (CF_EXPECT(common_parser_.HomeOverridden())) { + return false; + } + return !common_parser_.HasDeviceSelectOption(); +} + +static bool IsTrue(const std::string& value) { + std::unordered_set true_strings = {"y", "yes", "true"}; + std::string value_in_lower_case = value; + /* + * https://en.cppreference.com/w/cpp/string/byte/tolower + * + * char should be converted to unsigned char first. + */ + std::transform(value_in_lower_case.begin(), value_in_lower_case.end(), + value_in_lower_case.begin(), + [](unsigned char c) { return std::tolower(c); }); + return Contains(true_strings, value_in_lower_case); +} + +static bool IsFalse(const std::string& value) { + std::unordered_set false_strings = {"n", "no", "false"}; + std::string value_in_lower_case = value; + /* + * https://en.cppreference.com/w/cpp/string/byte/tolower + * + * char should be converted to unsigned char first. + */ + std::transform(value_in_lower_case.begin(), value_in_lower_case.end(), + value_in_lower_case.begin(), + [](unsigned char c) { return std::tolower(c); }); + return Contains(false_strings, value_in_lower_case); +} + +static std::optional GetAcquireFileLockEnvValue( + const cvd_common::Envs& envs) { + if (!Contains(envs, SelectorFlags::kAcquireFileLockEnv)) { + return std::nullopt; + } + auto env_value = envs.at(SelectorFlags::kAcquireFileLockEnv); + if (env_value.empty()) { + return std::nullopt; + } + return env_value; +} + +Result StartSelectorParser::CalcAcquireFileLock() { + // if the flag is set, flag has the highest priority + auto must_acquire_file_lock_flag = + CF_EXPECT(SelectorFlags::Get().GetFlag(SelectorFlags::kAcquireFileLock)); + std::optional value_opt = + CF_EXPECT(must_acquire_file_lock_flag.FilterFlag(selector_args_)); + if (value_opt) { + return *value_opt; + } + // flag is not set. see if there is the environment variable set + auto env_value_opt = GetAcquireFileLockEnvValue(envs_); + if (env_value_opt) { + auto value_string = *env_value_opt; + if (IsTrue(value_string)) { + return true; + } + if (IsFalse(value_string)) { + return false; + } + return CF_ERR("In \"" << SelectorFlags::kAcquireFileLockEnv << "=" + << value_string << ",\" \"" << value_string + << "\" is an invalid value. Try true or false."); + } + // nothing set, falls back to the default value of the flag + auto default_value = + CF_EXPECT(must_acquire_file_lock_flag.DefaultValue()); + return default_value; +} + +Result StartSelectorParser::ParseOptions() { + may_be_default_group_ = CF_EXPECT(CalcMayBeDefaultGroup()); + must_acquire_file_lock_ = CF_EXPECT(CalcAcquireFileLock()); + + group_name_ = common_parser_.GroupName(); + per_instance_names_ = common_parser_.PerInstanceNames(); + + std::optional num_instances; + std::optional instance_nums; + std::optional base_instance_num; + // set num_instances as std::nullptr or the value of --num_instances + CF_EXPECT(FilterSelectorFlag(cmd_args_, "num_instances", num_instances)); + CF_EXPECT(FilterSelectorFlag(cmd_args_, "instance_nums", instance_nums)); + CF_EXPECT( + FilterSelectorFlag(cmd_args_, "base_instance_num", base_instance_num)); + + InstanceIdsParams instance_nums_param{ + .num_instances = std::move(num_instances), + .instance_nums = std::move(instance_nums), + .base_instance_num = std::move(base_instance_num), + .cuttlefish_instance_env = TryFromCuttlefishInstance(envs_)}; + auto parsed_ids = CF_EXPECT(HandleInstanceIds(instance_nums_param)); + requested_num_instances_ = parsed_ids.GetNumOfInstances(); + instance_ids_ = parsed_ids.GetInstanceIds(); + + return {}; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/selector/start_selector_parser.h b/base/cvd/cuttlefish/host/commands/cvd/selector/start_selector_parser.h new file mode 100644 index 0000000000..b4b8cacd7c --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/selector/start_selector_parser.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2022 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 + +#include + +#include +#include +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/selector/selector_common_parser.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace selector { + +/** + * This class parses the separated SelectorOptions defined in + * cvd_server.proto. + * + * Note that the parsing is from the perspective of syntax. + * + * In other words, this does not check the following, for example: + * 1. If the numeric instance id is duplicated + * 2. If the group name is already taken + * + * How it works is, it parses the selector options that are common + * across operations with SelectorCommonParser first. Following that, + * StartSelectorParser parses start-specific selector options. + */ +class StartSelectorParser { + public: + static Result ConductSelectFlagsParser( + const cvd_common::Args& selector_args, const cvd_common::Args& cmd_args, + const cvd_common::Envs& envs); + std::optional GroupName() const; + std::optional> PerInstanceNames() const; + const std::optional>& InstanceIds() const { + return instance_ids_; + } + unsigned RequestedNumInstances() const { return requested_num_instances_; } + bool IsMaybeDefaultGroup() const { return may_be_default_group_; } + bool MustAcquireFileLock() const { return must_acquire_file_lock_; } + + private: + StartSelectorParser(const std::string& system_wide_user_home, + const cvd_common::Args& selector_args, + const cvd_common::Args& cmd_args, + const cvd_common::Envs& envs, + SelectorCommonParser&& common_parser); + + Result ParseOptions(); + + struct InstanceIdsParams { + std::optional num_instances; + std::optional instance_nums; + std::optional base_instance_num; + std::optional cuttlefish_instance_env; + std::optional vsoc_suffix; + }; + + class ParsedInstanceIdsOpt { + friend class StartSelectorParser; + + private: + ParsedInstanceIdsOpt(const std::vector& instance_ids) + : instance_ids_{instance_ids}, + n_instances_{static_cast(instance_ids.size())} {} + ParsedInstanceIdsOpt(const unsigned n_instances) + : instance_ids_{std::nullopt}, n_instances_{n_instances} {} + auto GetInstanceIds() { return std::move(instance_ids_); } + unsigned GetNumOfInstances() const { return n_instances_; } + std::optional> instance_ids_; + const unsigned n_instances_; + }; + + /* + * CF_ERR is meant to be an error: + * For example, --num_instances != |--instance_nums|. + * + * On the contrary, std::nullopt inside Result is not necessary one. + * std::nullopt inside Result means that with the given information, + * the instance_ids_ cannot be yet figured out, so the task is deferred + * to CreationAnaylizer or so, which has more contexts. For example, + * if no option at all is given, it is not an error; however, the + * StartSelectorParser alone cannot figure out the list of instance ids. The + * InstanceDatabase, UniqueResourceAllocator, InstanceLockFileManager will be + * involved to automatically generate the valid, numeric instance ids. + * If that's the case, Result{std::nullopt} could be returned. + * + */ + Result HandleInstanceIds( + const InstanceIdsParams& instance_id_params); + + struct InstanceFromEnvParam { + std::optional cuttlefish_instance_env; + std::optional vsoc_suffix; + std::optional num_instances; + }; + std::optional> InstanceFromEnvironment( + const InstanceFromEnvParam& params); + + struct VerifyNumOfInstancesParam { + std::optional num_instances_flag; + std::optional> instance_names; + std::optional instance_nums_flag; + }; + + Result VerifyNumOfInstances( + const VerifyNumOfInstancesParam& params, + const unsigned default_n_instances = 1) const; + Result CalcMayBeDefaultGroup(); + Result CalcAcquireFileLock(); + + /** + * The following are considered, and left empty if can't be figured out. + * + * --base_instance_num, --instance_nums, --num_instances, + * instance_names_.size(), CUTTLEFISH_INSTANCE, and vsoc-suffix if + * it is the user name. + * + * instance_names_.size() is effectively another --num_instances. + * CUTTLEFISH_INSTANCE and the suffix in order are considered as + * --base_instance_num if --base_instance_num is not given and + * --instance_nums is not given. + * + */ + std::optional> instance_ids_; + unsigned requested_num_instances_; + bool may_be_default_group_; + bool must_acquire_file_lock_; + std::optional group_name_; + std::optional> per_instance_names_; + + // temporarily keeps the leftover of the input cmd_args + const std::string client_user_home_; + cvd_common::Args selector_args_; + cvd_common::Args cmd_args_; + cvd_common::Envs envs_; + SelectorCommonParser common_parser_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server.cc b/base/cvd/cuttlefish/host/commands/cvd/server.cc new file mode 100644 index 0000000000..7fc5a8bc07 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server.cc @@ -0,0 +1,521 @@ +/* + * Copyright (C) 2021 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. + */ + +#include "host/commands/cvd/server.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/fs/shared_select.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/shared_fd_flag.h" +#include "common/libs/utils/subprocess.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/epoll_loop.h" +#include "host/commands/cvd/logger.h" +#include "host/commands/cvd/request_context.h" +#include "host/commands/cvd/run_server.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_command/cmd_list.h" +#include "host/commands/cvd/server_command/display.h" +#include "host/commands/cvd/server_command/env.h" +#include "host/commands/cvd/server_command/generic.h" +#include "host/commands/cvd/server_command/handler_proxy.h" +#include "host/commands/cvd/server_command/load_configs.h" +#include "host/commands/cvd/server_command/power.h" +#include "host/commands/cvd/server_command/reset.h" +#include "host/commands/cvd/server_command/snapshot.h" +#include "host/commands/cvd/server_command/start.h" +#include "host/commands/cvd/server_command/status.h" +#include "host/commands/cvd/server_command/subcmd.h" +#include "host/commands/cvd/server_constants.h" +#include "host/libs/config/known_paths.h" + +using android::base::ScopeGuard; + +namespace cuttlefish { + +static constexpr int kNumThreads = 10; + +CvdServer::CvdServer(BuildApi& build_api, EpollPool& epoll_pool, + InstanceLockFileManager& lock_manager, + InstanceManager& instance_manager, + HostToolTargetManager& host_tool_target_manager, + ServerLogger& server_logger) + : build_api_(build_api), + epoll_pool_(epoll_pool), + instance_lockfile_manager_(lock_manager), + instance_manager_(instance_manager), + host_tool_target_manager_(host_tool_target_manager), + server_logger_(server_logger), + running_(true), + optout_(false) { + std::scoped_lock lock(threads_mutex_); + for (auto i = 0; i < kNumThreads; i++) { + threads_.emplace_back([this]() { + while (running_) { + auto result = epoll_pool_.HandleEvent(); + if (!result.ok()) { + LOG(ERROR) << "Epoll worker error:\n" + << result.error().FormatForEnv(); + } + } + auto wakeup = BestEffortWakeup(); + CHECK(wakeup.ok()) << wakeup.error().FormatForEnv(); + }); + } +} + +CvdServer::~CvdServer() { + running_ = false; + auto wakeup = BestEffortWakeup(); + CHECK(wakeup.ok()) << wakeup.error().FormatForEnv(); + Join(); +} + +Result CvdServer::BestEffortWakeup() { + // This attempts to cascade through the responder threads, forcing them + // to wake up and see that running_ is false, then exit and wake up + // further threads. + auto eventfd = SharedFD::Event(); + CF_EXPECT(eventfd->IsOpen(), eventfd->StrError()); + CF_EXPECT(eventfd->EventfdWrite(1) == 0, eventfd->StrError()); + + auto cb = [](EpollEvent) -> Result { return {}; }; + CF_EXPECT(epoll_pool_.Register(eventfd, EPOLLIN, cb)); + return {}; +} + +void CvdServer::Stop() { + { + std::lock_guard lock(ongoing_requests_mutex_); + running_ = false; + } + while (true) { + std::shared_ptr request; + { + std::lock_guard lock(ongoing_requests_mutex_); + if (ongoing_requests_.empty()) { + break; + } + auto it = ongoing_requests_.begin(); + request = *it; + ongoing_requests_.erase(it); + } + { + std::lock_guard lock(request->mutex); + if (request->handler == nullptr) { + continue; + } + request->handler->Interrupt(); + } + auto wakeup = BestEffortWakeup(); + CHECK(wakeup.ok()) << wakeup.error().FormatForEnv(); + std::scoped_lock lock(threads_mutex_); + for (auto& thread : threads_) { + auto current_thread = thread.get_id() == std::this_thread::get_id(); + auto matching_thread = thread.get_id() == request->thread_id; + if (!current_thread && matching_thread && thread.joinable()) { + thread.join(); + } + } + } +} + +void CvdServer::Join() { + for (auto& thread : threads_) { + if (thread.joinable()) { + thread.join(); + } + } +} + +Result CvdServer::Exec(ExecParam&& exec_param) { + CF_EXPECT(server_fd_->IsOpen(), "Server not running"); + Stop(); + android::base::unique_fd server_dup{server_fd_->UNMANAGED_Dup()}; + CF_EXPECT(server_dup.get() >= 0, "dup: \"" << server_fd_->StrError() << "\""); + android::base::unique_fd client_dup{ + exec_param.carryover_client_fd->UNMANAGED_Dup()}; + CF_EXPECT(client_dup.get() >= 0, "dup: \"" << server_fd_->StrError() << "\""); + cvd_common::Args argv_str = { + kServerExecPath, + fmt::format("-{}={}", kInternalServerFd, server_dup.get()), + fmt::format("-{}={}", kInternalCarryoverClientFd, client_dup.get()), + fmt::format("-{}={}", kInternalAcloudTranslatorOptOut, optout_), + fmt::format("-{}={}", kInternalRestartedInProcess, true), + }; + + int in_memory_dup = -1; + ScopeGuard exit_action([&in_memory_dup]() { + if (in_memory_dup >= 0) { + if (close(in_memory_dup) != 0) { + LOG(ERROR) << "Failed to close file " << in_memory_dup; + } + } + }); + if (exec_param.in_memory_data_fd) { + in_memory_dup = exec_param.in_memory_data_fd.value()->UNMANAGED_Dup(); + CF_EXPECTF(in_memory_dup >= 0, "dup: \"{}\"", + exec_param.in_memory_data_fd.value()->StrError()); + argv_str.push_back( + ConcatToString("-", kInternalMemoryCarryoverFd, "=", in_memory_dup)); + } + + std::vector argv_cstr; + for (const auto& argv : argv_str) { + argv_cstr.emplace_back(strdup(argv.c_str())); + } + argv_cstr.emplace_back(nullptr); + android::base::unique_fd new_exe_dup{exec_param.new_exe->UNMANAGED_Dup()}; + CF_EXPECT(new_exe_dup.get() >= 0, + "dup: \"" << exec_param.new_exe->StrError() << "\""); + if (fcntl(new_exe_dup.get(), F_SETFD, FD_CLOEXEC) != 0) { + LOG(WARNING) << "Failed to set FD_CLOEXEC on the exec file descriptor: " + << std::strerror(errno) + << ". As it's not fatal, so the operation continues"; + } + + if (exec_param.verbose) { + LOG(ERROR) << "Server Exec'ing: " << android::base::Join(argv_str, " "); + } + + server_fd_->Close(); + exec_param.carryover_client_fd->Close(); + exec_param.new_exe->Close(); + fexecve(new_exe_dup.get(), argv_cstr.data(), environ); + for (const auto& argv : argv_cstr) { + free(argv); + } + return CF_ERR("fexecve failed: \"" << strerror(errno) << "\""); +} + +Result CvdServer::StartServer(SharedFD server_fd) { + server_fd_ = server_fd; + auto cb = [this](EpollEvent ev) -> Result { + CF_EXPECT(AcceptClient(ev)); + return {}; + }; + CF_EXPECT(epoll_pool_.Register(server_fd, EPOLLIN, cb)); + return {}; +} + +Result CvdServer::AcceptCarryoverClient(SharedFD client) { + auto self_cb = [this](EpollEvent ev) -> Result { + CF_EXPECT(HandleMessage(ev)); + return {}; + }; + CF_EXPECT(epoll_pool_.Register(client, EPOLLIN, self_cb)); + + cvd::Response success_message; + success_message.mutable_status()->set_code(cvd::Status::OK); + success_message.mutable_command_response(); + CF_EXPECT(SendResponse(client, success_message)); + return {}; +} + +Result CvdServer::AcceptClient(EpollEvent event) { + ScopeGuard stop_on_failure([this] { Stop(); }); + + CF_EXPECT(event.events & EPOLLIN); + auto client_fd = SharedFD::Accept(*event.fd); + CF_EXPECT(client_fd->IsOpen(), client_fd->StrError()); + auto client_cb = [this](EpollEvent ev) -> Result { + CF_EXPECT(HandleMessage(ev)); + return {}; + }; + CF_EXPECT(epoll_pool_.Register(client_fd, EPOLLIN, client_cb)); + + auto self_cb = [this](EpollEvent ev) -> Result { + CF_EXPECT(AcceptClient(ev)); + return {}; + }; + CF_EXPECT(epoll_pool_.Register(event.fd, EPOLLIN, self_cb)); + + stop_on_failure.Disable(); + return {}; +} + +Result CvdServer::HandleMessage(EpollEvent event) { + ScopeGuard abandon_client([this, event] { epoll_pool_.Remove(event.fd); }); + + if (event.events & EPOLLHUP) { // Client went away. + epoll_pool_.Remove(event.fd); + return {}; + } + + CF_EXPECT(event.events & EPOLLIN); + auto request = CF_EXPECT(GetRequest(event.fd)); + if (!request) { // End-of-file / client went away. + epoll_pool_.Remove(event.fd); + return {}; + } + const auto verbosity = request->Message().verbosity(); + const auto encoded_verbosity = EncodeVerbosity(verbosity); + auto logger = + encoded_verbosity.ok() + ? server_logger_.LogThreadToFd(request->Err(), *encoded_verbosity) + : server_logger_.LogThreadToFd(request->Err()); + auto response = HandleRequest(*request, event.fd); + if (!response.ok()) { + cvd::Response failure_message; + failure_message.mutable_status()->set_code(cvd::Status::INTERNAL); + const bool color = request->Err()->IsOpen() && request->Err()->IsATTY(); + failure_message.mutable_status()->set_message( + response.error().FormatForEnv(color)); + CF_EXPECT(SendResponse(event.fd, failure_message)); + return {}; // Error already sent to the client, don't repeat on the server + } + CF_EXPECT(SendResponse(event.fd, *response)); + + auto self_cb = [this, err = request->Err()](EpollEvent ev) -> Result { + CF_EXPECT(HandleMessage(ev)); + return {}; + }; + CF_EXPECT(epoll_pool_.Register(event.fd, EPOLLIN, self_cb)); + + abandon_client.Disable(); + return {}; +} + +static Result Verbosity( + const RequestWithStdio& request, const std::string& default_val) { + if (request.Message().contents_case() != + cvd::Request::ContentsCase::kCommandRequest) { + return default_val.empty() ? kCvdDefaultVerbosity + : CF_EXPECT(EncodeVerbosity(default_val)); + } + const auto& selector_opts = + request.Message().command_request().selector_opts(); + auto selector_args = cvd_common::ConvertToArgs(selector_opts.args()); + auto verbosity_flag = CF_EXPECT(selector::SelectorFlags::New()) + .FlagsAsCollection() + .GetFlag(selector::SelectorFlags::kVerbosity); + auto verbosity_opt = + CF_EXPECT(verbosity_flag->FilterFlag(selector_args)); + auto ret_val = verbosity_opt.value_or(default_val); + if (ret_val.empty()) { + const auto severity_in_string = + CF_EXPECT(VerbosityToString(kCvdDefaultVerbosity)); + LOG(DEBUG) << "Verbosity level is not given, so using the default value: " + << severity_in_string << " is used"; + return kCvdDefaultVerbosity; + } + return CF_EXPECT(EncodeVerbosity(ret_val), + "Invalid verbosity level : \"" << ret_val << "\""); +} + +// convert HOME, ANDROID_HOST_OUT, ANDROID_SOONG_HOST_OUT +// and ANDROID_PRODUCT_OUT into absolute paths if any. +static Result ConvertDirPathToAbsolute( + const RequestWithStdio& request) { + if (request.Message().contents_case() != + cvd::Request::ContentsCase::kCommandRequest) { + return request; + } + if (request.Message().command_request().env().empty()) { + return request; + } + auto envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + std::unordered_set interested_envs{ + kAndroidHostOut, kAndroidSoongHostOut, "HOME", kAndroidProductOut}; + const auto& current_dir = + request.Message().command_request().working_directory(); + + // make sure that "~" is not included + for (const auto& key : interested_envs) { + if (!Contains(envs, key)) { + continue; + } + const auto& dir = envs.at(key); + CF_EXPECT(dir != "~" && !android::base::StartsWith(dir, "~/"), + "The " << key << " directory should not start with ~"); + } + + for (const auto& key : interested_envs) { + if (!Contains(envs, key)) { + continue; + } + const auto dir = envs.at(key); + envs[key] = + CF_EXPECT(EmulateAbsolutePath({.current_working_dir = current_dir, + .home_dir = std::nullopt, // unused + .path_to_convert = dir, + .follow_symlink = false})); + } + + auto cmd_args = + cvd_common::ConvertToArgs(request.Message().command_request().args()); + auto selector_args = cvd_common::ConvertToArgs( + request.Message().command_request().selector_opts().args()); + RequestWithStdio new_request( + request.Client(), + MakeRequest({.cmd_args = std::move(cmd_args), + .env = std::move(envs), + .selector_args = std::move(selector_args), + .working_dir = current_dir}, + request.Message().command_request().wait_behavior()), + request.FileDescriptors(), request.Credentials()); + return new_request; +} + +static Result VerifyUser(const RequestWithStdio& request) { + CF_EXPECT(request.Credentials(), + "ucred is not available while it is necessary."); + const uid_t client_uid = request.Credentials()->uid; + CF_EXPECT_EQ(client_uid, getuid(), "Cvd server process is one per user."); + return {}; +} + +Result CvdServer::HandleRequest(RequestWithStdio orig_request, + SharedFD client) { + CF_EXPECT(VerifyUser(orig_request)); + auto request = CF_EXPECT(ConvertDirPathToAbsolute(orig_request)); + const auto verbosity = + CF_EXPECT(Verbosity(request, request.Message().verbosity())); + server_logger_.SetSeverity(verbosity); + + RequestContext context(*this, instance_lockfile_manager_, instance_manager_, + build_api_, host_tool_target_manager_, optout_); + + // Even if the interrupt callback outlives the request handler, it'll only + // hold on to this struct which will be cleaned out when the request handler + // exits. + auto shared = std::make_shared(); + shared->handler = CF_EXPECT(context.Handler(request)); + shared->thread_id = std::this_thread::get_id(); + + { + std::lock_guard lock(ongoing_requests_mutex_); + if (running_) { + ongoing_requests_.insert(shared); + } else { + // We're executing concurrently with a Stop() call. + return {}; + } + } + ScopeGuard remove_ongoing_request([this, shared] { + std::lock_guard lock(ongoing_requests_mutex_); + ongoing_requests_.erase(shared); + }); + + auto interrupt_cb = [this, shared, verbosity, + err = request.Err()](EpollEvent) -> Result { + auto logger = server_logger_.LogThreadToFd(err, verbosity); + std::lock_guard lock(shared->mutex); + CF_EXPECT(shared->handler != nullptr); + CF_EXPECT(shared->handler->Interrupt()); + return {}; + }; + CF_EXPECT(epoll_pool_.Register(client, EPOLLHUP, interrupt_cb)); + + auto response = CF_EXPECT(shared->handler->Handle(request)); + { + std::lock_guard lock(shared->mutex); + shared->handler = nullptr; + } + CF_EXPECT(epoll_pool_.Remove(client)); // Delete interrupt handler + + return response; +} + +Result CvdServer::InstanceDbFromJson(const std::string& json_string) { + auto json = CF_EXPECT(ParseJson(json_string)); + CF_EXPECT(instance_manager_.LoadFromJson(json)); + return {}; +} + +Result CvdServerMain(ServerMainParam&& param) { + SetMinimumVerbosity(android::base::VERBOSE); + + LOG(INFO) << "Starting server"; + + if (!param.restarted_in_process) { + LOG(INFO) << "Server is being daemonized..."; + CF_EXPECT(daemon(0, 0) != -1, strerror(errno)); + } + + signal(SIGPIPE, SIG_IGN); + + SharedFD server_fd = std::move(param.internal_server_fd); + CF_EXPECT(server_fd->IsOpen(), "Did not receive a valid cvd_server fd"); + + std::unique_ptr server_logger = std::move(param.server_logger); + BuildApi build_api; + EpollPool epoll_pool; + auto host_tool_target_manager = NewHostToolTargetManager(); + InstanceLockFileManager lock_manager; + InstanceManager instance_manager(lock_manager, *host_tool_target_manager); + CvdServer server(build_api, epoll_pool, lock_manager, instance_manager, + *host_tool_target_manager, *server_logger); + + if (param.memory_carryover_fd) { + SharedFD memory_carryover_fd = std::move(*param.memory_carryover_fd); + const std::string json_string = + CF_EXPECT(ReadAllFromMemFd(memory_carryover_fd)); + CF_EXPECT(server.InstanceDbFromJson(json_string), + "Failed to load from: " << json_string); + } + if (param.acloud_translator_optout) { + server.optout_ = param.acloud_translator_optout.value(); + } + server.StartServer(server_fd); + + // The carryover_client wouldn't be available after AcceptCarryoverClient() + if (param.carryover_client_fd->IsOpen()) { + // release scoped_logger for this thread inside AcceptCarryoverClient() + CF_EXPECT( + server.AcceptCarryoverClient(std::move(param.carryover_client_fd))); + } + server.Join(); + + return 0; +} + +Result ReadAllFromMemFd(const SharedFD& mem_fd) { + const auto n_message_size = mem_fd->LSeek(0, SEEK_END); + CF_EXPECT_NE(n_message_size, -1, "LSeek on the memory file failed."); + std::vector buffer(n_message_size); + CF_EXPECT_EQ(mem_fd->LSeek(0, SEEK_SET), 0, mem_fd->StrError()); + auto n_read = ReadExact(mem_fd, buffer.data(), n_message_size); + CF_EXPECT(n_read == n_message_size, + "Expected to read " << n_message_size << " bytes but actually read " + << n_read << " bytes."); + std::string message(buffer.begin(), buffer.end()); + return message; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server.h b/base/cvd/cuttlefish/host/commands/cvd/server.h new file mode 100644 index 0000000000..ffd357a70e --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include +#include +#include +#include + +#include "cvd_server.pb.h" + +#include "common/libs/fs/epoll.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/subprocess.h" +#include "common/libs/utils/unix_sockets.h" +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/epoll_loop.h" +#include "host/commands/cvd/instance_lock.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/logger.h" +// including "server_command/subcmd.h" causes cyclic dependency +#include "host/commands/cvd/server_command/host_tool_target_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/libs/web/android_build_api.h" + +namespace cuttlefish { + +struct ServerMainParam { + SharedFD internal_server_fd; + SharedFD carryover_client_fd; + std::optional memory_carryover_fd; + std::optional acloud_translator_optout; + std::unique_ptr server_logger; + /* scoped logger that carries the stderr of the carried-over + * client. The client may have called "cvd restart-server." + * + * The scoped_logger should expire just after AcceptCarryoverClient() + */ + std::unique_ptr scoped_logger; + // If true, the server restarted in the previous server process + // This is common for restart-server operations. + bool restarted_in_process; +}; +Result CvdServerMain(ServerMainParam&& fds); + +class CvdServer { + // for server_logger_. + // server_logger_ shouldn't be exposed to anything but CvdServerMain() + friend Result CvdServerMain(ServerMainParam&& fds); + + public: + CvdServer(BuildApi&, EpollPool&, InstanceLockFileManager&, InstanceManager&, + HostToolTargetManager&, ServerLogger&); + ~CvdServer(); + + Result StartServer(SharedFD server); + struct ExecParam { + SharedFD new_exe; + SharedFD carryover_client_fd; // the client that called cvd restart-server + std::optional + in_memory_data_fd; // fd to carry over in-memory data + bool verbose; + }; + Result Exec(ExecParam&&); + Result AcceptCarryoverClient(SharedFD client); + void Stop(); + void Join(); + Result InstanceDbFromJson(const std::string& json_string); + + private: + struct OngoingRequest { + CvdServerHandler* handler; + std::mutex mutex; + std::thread::id thread_id; + }; + + Result AcceptClient(EpollEvent); + Result HandleMessage(EpollEvent); + Result HandleRequest(RequestWithStdio, SharedFD client); + Result BestEffortWakeup(); + + SharedFD server_fd_; + BuildApi& build_api_; + EpollPool& epoll_pool_; + InstanceLockFileManager& instance_lockfile_manager_; + InstanceManager& instance_manager_; + HostToolTargetManager& host_tool_target_manager_; + ServerLogger& server_logger_; + std::atomic_bool running_ = true; + + std::mutex ongoing_requests_mutex_; + std::set> ongoing_requests_; + // TODO(schuffelen): Move this thread pool to another class. + std::mutex threads_mutex_; + std::vector threads_; + + // translator optout + std::atomic optout_; +}; + +// Read all contents from the file +Result ReadAllFromMemFd(const SharedFD& mem_fd); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_client.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_client.cpp new file mode 100644 index 0000000000..7020d6897c --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_client.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_client.h" + +#include +#include +#include +#include +#include +#include + +#include "cvd_server.pb.h" + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/fs/shared_select.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/unix_sockets.h" + +namespace cuttlefish { + +Result GetClient(const SharedFD& client) { + UnixMessageSocket result(client); + CF_EXPECT(result.EnableCredentials(true), + "Unable to enable UnixMessageSocket credentials."); + return result; +} + +Result> GetRequest(const SharedFD& client) { + UnixMessageSocket reader = + CF_EXPECT(GetClient(client), "Couldn't get client"); + auto read_result = CF_EXPECT(reader.ReadMessage(), "Couldn't read message"); + + if (read_result.data.empty()) { + LOG(VERBOSE) << "Read empty packet, so the client has probably closed the " + "connection."; + return {}; + }; + + std::string serialized(read_result.data.begin(), read_result.data.end()); + cvd::Request request; + CF_EXPECT(request.ParseFromString(serialized), + "Unable to parse serialized request proto."); + + CF_EXPECT(read_result.HasFileDescriptors(), + "Missing stdio fds from request."); + auto fds = CF_EXPECT(read_result.FileDescriptors(), + "Error reading stdio fds from request"); + CF_EXPECT(fds.size() == 3 || fds.size() == 4, "Wrong number of FDs, received " + << fds.size() + << ", wanted 3 or 4"); + + std::optional creds; + if (read_result.HasCredentials()) { + // TODO(b/198453477): Use Credentials to control command access. + creds = CF_EXPECT(read_result.Credentials(), "Failed to get credentials"); + LOG(DEBUG) << "Has credentials, uid=" << creds->uid; + } + + return RequestWithStdio(client, std::move(request), std::move(fds), + std::move(creds)); +} + +Result SendResponse(const SharedFD& client, + const cvd::Response& response) { + std::string serialized; + CF_EXPECT(response.SerializeToString(&serialized), + "Unable to serialize response proto."); + UnixSocketMessage message; + message.data = std::vector(serialized.begin(), serialized.end()); + + UnixMessageSocket writer = + CF_EXPECT(GetClient(client), "Couldn't get client"); + CF_EXPECT(writer.WriteMessage(message)); + return {}; +} + +RequestWithStdio::RequestWithStdio(SharedFD client_fd, cvd::Request message, + std::vector fds, + std::optional creds) + : client_fd_(client_fd), + message_(message), + fds_(std::move(fds)), + creds_(creds) {} + +SharedFD RequestWithStdio::Client() const { return client_fd_; } + +const cvd::Request& RequestWithStdio::Message() const { return message_; } + +const std::vector& RequestWithStdio::FileDescriptors() const { + return fds_; +} + +SharedFD RequestWithStdio::In() const { + return fds_.size() > 0 ? fds_[0] : SharedFD(); +} + +SharedFD RequestWithStdio::Out() const { + return fds_.size() > 1 ? fds_[1] : SharedFD(); +} + +SharedFD RequestWithStdio::Err() const { + return fds_.size() > 2 ? fds_[2] : SharedFD(); +} + +std::optional RequestWithStdio::Extra() const { + return fds_.size() > 3 ? fds_[3] : std::optional{}; +} + +std::optional RequestWithStdio::Credentials() const { return creds_; } + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_client.h b/base/cvd/cuttlefish/host/commands/cvd/server_client.h new file mode 100644 index 0000000000..e7f568d967 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_client.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 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 + +#include +#include + +#include +#include +#include + +#include "cvd_server.pb.h" + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/unix_sockets.h" + +namespace cuttlefish { + +class RequestWithStdio { + public: + RequestWithStdio(SharedFD, cvd::Request, std::vector, + std::optional); + + SharedFD Client() const; + const cvd::Request& Message() const; + const std::vector& FileDescriptors() const; + SharedFD In() const; + SharedFD Out() const; + SharedFD Err() const; + std::optional Extra() const; + std::optional Credentials() const; + + private: + SharedFD client_fd_; + cvd::Request message_; + std::vector fds_; + std::optional creds_; +}; + +Result GetClient(const SharedFD& client); +Result> GetRequest(const SharedFD& client); +Result SendResponse(const SharedFD& client, + const cvd::Response& response); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud.cpp new file mode 100644 index 0000000000..713c8843a2 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/acloud.h" + +#include + +#include + +#include "host/commands/cvd/server_command/acloud.h" +#include "host/commands/cvd/server_command/acloud_command.h" +#include "host/commands/cvd/server_command/acloud_translator.h" +#include "host/commands/cvd/server_command/acloud_mixsuperimage.h" +#include "host/commands/cvd/server_command/try_acloud.h" + +namespace cuttlefish { + +fruit::Component>>> +CvdAcloudComponent() { + return fruit::createComponent() + .install(AcloudCommandComponent) + .install(TryAcloudCommandComponent) + .install(AcloudTranslatorCommandComponent) + .install(AcloudMixSuperImageCommandComponent); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_command.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_command.cpp new file mode 100644 index 0000000000..20a2af4e6e --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_command.cpp @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/acloud_command.h" + +#include +#include +#include + +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/acloud/converter.h" +#include "host/commands/cvd/acloud/create_converter_parser.h" +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/instance_lock.h" +#include "host/commands/cvd/server_command/acloud_common.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace { + +constexpr char kSummaryHelpText[] = + R"(Toggles translation of acloud commands to run through cvd if supported)"; + +constexpr char kDetailedHelpText[] = R"( +Usage: +cvd acloud translator (--opt-out|--opt-in) +Any acloud command will by default (and if supported by cvd) be translated to the appropriate cvd command and executed. +If not supported by cvd, acloud will be used. + +To opt out or opt back in, run this translation toggle. +)"; + +} // namespace + +class AcloudCommand : public CvdServerHandler { + public: + AcloudCommand(CommandSequenceExecutor& executor) : executor_(executor) {} + ~AcloudCommand() = default; + + Result CanHandle(const RequestWithStdio& request) const override { + auto invocation = ParseInvocation(request.Message()); + if (invocation.arguments.size() >= 2) { + if (invocation.command == "acloud" && + (invocation.arguments[0] == "translator" || + invocation.arguments[0] == "mix-super-image")) { + return false; + } + } + return invocation.command == "acloud"; + } + + cvd_common::Args CmdList() const override { return {"acloud"}; } + + Result SummaryHelp() const override { return kSummaryHelpText; } + + bool ShouldInterceptHelp() const override { return true; } + + Result DetailedHelp(std::vector&) const override { + return kDetailedHelpText; + } + + /** + * The `acloud` command satisfies the original `acloud CLI` command using + * either: + * + * 1. `cvd` for local instance management + * + * 2. Or `cvdr` for remote instance management. + * + */ + Result Handle(const RequestWithStdio& request) override { + auto result = ValidateLocal(request); + if (result.ok()) { + return CF_EXPECT(HandleLocal(*result, request)); + } else if (ValidateRemoteArgs(request)) { + return CF_EXPECT(HandleRemote(request)); + } + CF_EXPECT(std::move(result)); + return {}; + } + + Result Interrupt() override { + std::scoped_lock interrupt_lock(interrupt_mutex_); + interrupted_ = true; + CF_EXPECT(waiter_.Interrupt()); + CF_EXPECT(executor_.Interrupt()); + return {}; + } + + private: + Result HandleStartResponse( + const cvd::Response& start_response); + Result PrintBriefSummary(const cvd::InstanceGroupInfo& group_info, + std::optional stream_fd) const; + Result ValidateLocal( + const RequestWithStdio& request); + bool ValidateRemoteArgs(const RequestWithStdio& request); + Result HandleLocal(const ConvertedAcloudCreateCommand& command, + const RequestWithStdio& request); + Result HandleRemote(const RequestWithStdio& request); + Result RunAcloudConnect(const RequestWithStdio& request, + const std::string& hostname); + + CommandSequenceExecutor& executor_; + std::mutex interrupt_mutex_; + bool interrupted_ = false; + SubprocessWaiter waiter_; +}; + +Result AcloudCommand::HandleStartResponse( + const cvd::Response& start_response) { + CF_EXPECT(start_response.has_command_response(), + "cvd start did not return a command response."); + const auto& start_command_response = start_response.command_response(); + CF_EXPECT(start_command_response.has_instance_group_info(), + "cvd start command response did not return instance_group_info."); + cvd::InstanceGroupInfo group_info = + start_command_response.instance_group_info(); + return group_info; +} + +Result AcloudCommand::PrintBriefSummary( + const cvd::InstanceGroupInfo& group_info, + std::optional stream_fd) const { + if (!stream_fd) { + return {}; + } + SharedFD fd = *stream_fd; + std::stringstream ss; + const std::string& group_name = group_info.group_name(); + CF_EXPECT_EQ(group_info.home_directories().size(), 1); + const std::string home_dir = (group_info.home_directories())[0]; + std::vector instance_names; + std::vector instance_ids; + instance_names.reserve(group_info.instances().size()); + instance_ids.reserve(group_info.instances().size()); + for (const auto& instance : group_info.instances()) { + instance_names.push_back(instance.name()); + instance_ids.push_back(instance.instance_id()); + } + ss << std::endl << "Created instance group: " << group_name << std::endl; + for (size_t i = 0; i != instance_ids.size(); i++) { + std::string device_name = group_name + "-" + instance_names[i]; + ss << " " << device_name << " (local-instance-" << instance_ids[i] << ")" + << std::endl; + } + ss << std::endl + << "acloud list or cvd fleet for more information." << std::endl; + auto n_write = WriteAll(*stream_fd, ss.str()); + CF_EXPECT_EQ(n_write, ss.str().size()); + return {}; +} + +Result AcloudCommand::ValidateLocal( + const RequestWithStdio& request) { + std::unique_lock interrupt_lock(interrupt_mutex_); + bool lock_released = false; + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CanHandle(request)); + CF_EXPECT(IsSubOperationSupported(request)); + // ConvertAcloudCreate may lock and unlock the lock + auto cb_unlock = [&lock_released, &interrupt_lock](void) -> Result { + if (!lock_released) { + interrupt_lock.unlock(); + lock_released = true; + } + return {}; + }; + auto cb_lock = [&lock_released, &interrupt_lock](void) -> Result { + if (lock_released) { + interrupt_lock.lock(); + lock_released = true; + } + return {}; + }; + // ConvertAcloudCreate converts acloud to cvd commands. + // The input parameters waiter_, cb_unlock, cb_lock are.used to + // support interrupt which have locking and unlocking functions + return acloud_impl::ConvertAcloudCreate(request, waiter_, cb_unlock, cb_lock); +} + +bool AcloudCommand::ValidateRemoteArgs(const RequestWithStdio& request) { + auto args = ParseInvocation(request.Message()).arguments; + return acloud_impl::CompileFromAcloudToCvdr(args).ok(); +} + +Result AcloudCommand::HandleLocal( + const ConvertedAcloudCreateCommand& command, + const RequestWithStdio& request) { + CF_EXPECT(executor_.Execute(command.prep_requests, request.Err())); + auto start_response = + CF_EXPECT(executor_.ExecuteOne(command.start_request, request.Err())); + + if (!command.fetch_command_str.empty()) { + // has cvd fetch command, update the fetch cvd command file + using android::base::WriteStringToFile; + CF_EXPECT(WriteStringToFile(command.fetch_command_str, + command.fetch_cvd_args_file), + true); + } + + auto handle_response_result = HandleStartResponse(start_response); + if (handle_response_result.ok()) { + // print + std::optional fd_opt; + if (command.verbose) { + fd_opt = request.Err(); + } + auto write_result = PrintBriefSummary(*handle_response_result, fd_opt); + if (!write_result.ok()) { + LOG(ERROR) << "Failed to write the start response report."; + } + } else { + LOG(ERROR) << "Failed to analyze the cvd start response."; + } + cvd::Response response; + response.mutable_command_response(); + return response; +} + +Result AcloudCommand::HandleRemote( + const RequestWithStdio& request) { + auto args = ParseInvocation(request.Message()).arguments; + args = CF_EXPECT(acloud_impl::CompileFromAcloudToCvdr(args)); + Command cmd = Command("cvdr"); + for (auto a : args) { + cmd.AddParameter(a); + } + // Do not perform ADB connection with `cvdr` until acloud CLI is fully + // deprecated. + if (args[0] == "create") { + cmd.AddParameter("--auto_connect=false"); + } + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, request.In()); + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, request.Err()); + std::string stdout_; + SharedFD stdout_pipe_read, stdout_pipe_write; + CF_EXPECT(SharedFD::Pipe(&stdout_pipe_read, &stdout_pipe_write), + "Could not create a pipe"); + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, stdout_pipe_write); + std::thread stdout_thread([stdout_pipe_read, &stdout_]() { + int read = ReadAll(stdout_pipe_read, &stdout_); + if (read < 0) { + LOG(ERROR) << "Error in reading stdout from process"; + } + }); + WriteAll(request.Err(), + "UPDATE! Try the new `cvdr` tool directly. Run `cvdr --help` to get " + "started.\n"); + { + std::unique_lock interrupt_lock(interrupt_mutex_); + CF_EXPECT(!interrupted_, "Interrupted"); + auto subprocess = cmd.Start(); + CF_EXPECT(subprocess.Started()); + CF_EXPECT(waiter_.Setup(std::move(subprocess))); + } + siginfo_t siginfo = CF_EXPECT(waiter_.Wait()); + { + // Force the destructor to run by moving it into a smaller scope. + // This is necessary to close the write end of the pipe. + Command forceDelete = std::move(cmd); + } + stdout_pipe_write->Close(); + stdout_thread.join(); + WriteAll(request.Out(), stdout_); + if (args[0] == "create" && siginfo.si_status == EXIT_SUCCESS) { + std::string hostname = stdout_.substr(0, stdout_.find(" ")); + CF_EXPECT(RunAcloudConnect(request, hostname)); + } + cvd::Response response; + response.mutable_command_response(); + return response; +} + +Result AcloudCommand::RunAcloudConnect(const RequestWithStdio& request, + const std::string& hostname) { + auto build_top = StringFromEnv("ANDROID_BUILD_TOP", ""); + CF_EXPECT( + build_top != "", + "Missing ANDROID_BUILD_TOP environment variable. Please run `source " + "build/envsetup.sh`"); + Command cmd = + Command(build_top + "/prebuilts/asuite/acloud/linux-x86/acloud"); + cmd.AddParameter("reconnect"); + cmd.AddParameter("--instance-names"); + cmd.AddParameter(hostname); + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, request.In()); + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, request.Out()); + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, request.Err()); + { + std::unique_lock interrupt_lock(interrupt_mutex_); + CF_EXPECT(!interrupted_, "Interrupted"); + auto subprocess = cmd.Start(); + CF_EXPECT(subprocess.Started()); + CF_EXPECT(waiter_.Setup(std::move(subprocess))); + } + CF_EXPECT(waiter_.Wait()); + return {}; +} + +std::unique_ptr NewAcloudCommand( + CommandSequenceExecutor& executor) { + return std::unique_ptr(new AcloudCommand(executor)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_command.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_command.h new file mode 100644 index 0000000000..9944a3abe4 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_command.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/command_sequence.h" + +namespace cuttlefish { + +std::unique_ptr NewAcloudCommand( + CommandSequenceExecutor& executor); +} diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_common.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_common.cpp new file mode 100644 index 0000000000..4c343bb680 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_common.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/acloud_common.h" + +#include "host/commands/cvd/server_command/utils.h" + +namespace cuttlefish { + +bool IsSubOperationSupported(const RequestWithStdio& request) { + auto invocation = ParseInvocation(request.Message()); + if (invocation.arguments.empty()) { + return false; + } + return invocation.arguments[0] == "create"; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_common.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_common.h new file mode 100644 index 0000000000..820973f1c2 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_common.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 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 + +#include "host/commands/cvd/server_client.h" + +namespace cuttlefish { + +struct AcloudTranslatorOptOut {}; + +bool IsSubOperationSupported(const RequestWithStdio& request); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_mixsuperimage.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_mixsuperimage.cpp new file mode 100644 index 0000000000..854b05034f --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_mixsuperimage.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2023 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. + */ + +#include +#include +#include + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/acloud_mixsuperimage.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" +#include "host/libs/config/config_utils.h" + +namespace cuttlefish { + +static constexpr char kMixSuperImageHelpMessage[] = + R"(Cuttlefish Virtual Device (CVD) CLI. + +usage: cvd acloud mix-super-image + +Args: + --super_image Super image path. +)"; + +const std::string _MISC_INFO_FILE_NAME = "misc_info.txt"; +const std::string _TARGET_FILES_META_DIR_NAME = "META"; +const std::string _TARGET_FILES_IMAGES_DIR_NAME = "IMAGES"; +const std::string _SYSTEM_IMAGE_NAME_PATTERN = "system.img"; +const std::string _SYSTEM_EXT_IMAGE_NAME_PATTERN = "system_ext.img"; +const std::string _PRODUCT_IMAGE_NAME_PATTERN = "product.img"; + +/* + * Find misc info in build output dir or extracted target files. + */ +Result FindMiscInfo(const std::string& image_dir) { + std::string misc_info_path = image_dir + _MISC_INFO_FILE_NAME; + + if (FileExists(misc_info_path)) { + return misc_info_path; + } + misc_info_path = image_dir + _TARGET_FILES_META_DIR_NAME + + "/" + _MISC_INFO_FILE_NAME; + + if (FileExists(misc_info_path)) { + return misc_info_path; + } + return CF_ERR("Cannot find " << _MISC_INFO_FILE_NAME + << " in " << image_dir); +} + +/* + * Find images in build output dir or extracted target files. + */ +Result FindImageDir(const std::string& image_dir) { + for (const auto & file: CF_EXPECT(DirectoryContents(image_dir))) { + if (android::base::EndsWith(file, ".img")) { + return image_dir; + } + } + + std::string subdir = image_dir + _TARGET_FILES_IMAGES_DIR_NAME; + for (const auto & file: CF_EXPECT(DirectoryContents(subdir))) { + if (android::base::EndsWith(file, ".img")) { + return subdir; + } + } + return CF_ERR("Cannot find images in " << image_dir); +} + +/* + * Map a partition name to an image path. + * + * This function is used with BuildSuperImage to mix + * image_dir and image_paths into the output file. + */ +Result GetImageForPartition( + std::string const &partition_name, std::string const &image_dir, + const std::map& image_paths) { + std::string result_path = ""; + if (auto search = image_paths.find(partition_name); + search != image_paths.end()) { + result_path = search->second; + } + if (result_path == "") { + result_path = image_dir + partition_name + ".img"; + } + + CF_EXPECT(FileExists(result_path), + "Cannot find image for partition " << partition_name); + return result_path; +} + +/* + * Rewrite lpmake and image paths in misc_info.txt. + */ +Result _RewriteMiscInfo( + const std::string& output_file, const std::string& input_file, + const std::string& lpmake_path, + const std::function(const std::string&)>& get_image) { + std::vector partition_names; + std::ifstream input_fs; + std::ofstream output_fs; + input_fs.open(input_file); + output_fs.open(output_file); + CF_EXPECT(output_fs.is_open(), "Failed to open file: " << output_file); + std::string line; + while (getline(input_fs, line)) { + std::vector split_line = android::base::Split(line, "="); + if (split_line.size() < 2) { + split_line = { split_line[0], "" }; + } + if (split_line[0] == "dynamic_partition_list") { + partition_names = android::base::Tokenize(split_line[1], " "); + } else if (split_line[0] == "lpmake") { + output_fs << "lpmake=" << lpmake_path << "\n"; + continue; + } else if (android::base::EndsWith(split_line[0], "_image")) { + continue; + } + output_fs << line << "\n"; + } + input_fs.close(); + + if (partition_names.size() == 0) { + LOG(INFO) << "No dynamic partition list in misc info."; + } + + for (const auto & partition_name : partition_names) { + output_fs << partition_name << "_image=" << + CF_EXPECT(get_image(partition_name)) << "\n"; + } + + output_fs.close(); + return {}; +} + +class AcloudMixSuperImageCommand : public CvdServerHandler { + public: + AcloudMixSuperImageCommand() {} + ~AcloudMixSuperImageCommand() = default; + + Result CanHandle(const RequestWithStdio& request) const override { + auto invocation = ParseInvocation(request.Message()); + if (invocation.arguments.size() >= 2) { + if (invocation.command == "acloud" && + invocation.arguments[0] == "mix-super-image") { + return true; + } + } + return false; + } + + cvd_common::Args CmdList() const override { return {}; } + + Result Handle(const RequestWithStdio& request) override { + std::unique_lock interrupt_lock(interrupt_mutex_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CanHandle(request)); + auto invocation = ParseInvocation(request.Message()); + if (invocation.arguments.empty() || invocation.arguments.size() < 2) { + return CF_ERR("Acloud mix-super-image command not support"); + } + + // cvd acloud mix-super-image --super_image path + cvd::Response response; + response.mutable_command_response(); + bool help = false; + std::string flag_paths = ""; + std::vector mixsuperimage_flags = { + GflagsCompatFlag("help", help), + GflagsCompatFlag("super_image", flag_paths), + }; + CF_EXPECT(ConsumeFlags(mixsuperimage_flags, invocation.arguments), + "Failed to process mix-super-image flag."); + if (help) { + WriteAll(request.Out(), kMixSuperImageHelpMessage); + return response; + } + + auto cb_unlock = [&interrupt_lock](void) -> Result { + interrupt_lock.unlock(); + return {}; + }; + CF_EXPECT(MixSuperImage(flag_paths , cb_unlock), + "Build mixed super image failed"); + return response; + } + Result Interrupt() override { + std::scoped_lock interrupt_lock(interrupt_mutex_); + interrupted_ = true; + CF_EXPECT(waiter_.Interrupt()); + return {}; + } + + private: + /* + * Use build_super_image to create a super image. + */ + Result BuildSuperImage( + const std::string& output_path, const std::string& misc_info_path, + const std::function(void)>& callback_unlock, + const std::function(const std::string&)>& get_image) { + std::string build_super_image_binary; + std::string lpmake_binary; + std::string otatools_path; + if (FileExists(DefaultHostArtifactsPath("otatools/bin/build_super_image"))) { + build_super_image_binary = + DefaultHostArtifactsPath("otatools/bin/build_super_image"); + lpmake_binary = + DefaultHostArtifactsPath("otatools/bin/lpmake"); + otatools_path = DefaultHostArtifactsPath("otatools"); + } else if (FileExists(HostBinaryPath("build_super_image"))) { + build_super_image_binary = + HostBinaryPath("build_super_image"); + lpmake_binary = + HostBinaryPath("lpmake"); + otatools_path = DefaultHostArtifactsPath(""); + } else { + return CF_ERR("Could not find otatools"); + } + + TemporaryFile new_misc_info; + std::string new_misc_info_path = new_misc_info.path; + CF_EXPECT(_RewriteMiscInfo(new_misc_info_path, misc_info_path, + lpmake_binary, get_image)); + + Command command(build_super_image_binary); + command.AddParameter(new_misc_info_path); + command.AddParameter(output_path); + auto subprocess = command.Start(); + CF_EXPECT(subprocess.Started()); + CF_EXPECT(waiter_.Setup(std::move(subprocess))); + CF_EXPECT(callback_unlock()); + CF_EXPECT(waiter_.Wait()); + return {}; + } + + Result MixSuperImage( + const std::string& paths, + const std::function(void)>& callback_unlock) { + std::string super_image = ""; + std::string local_system_image = ""; + std::string system_image_path = ""; + std::string image_dir = ""; + std::string misc_info = ""; + + int index = 0; + std::vector paths_vec = android::base::Split(paths, ","); + for (const auto & each_path :paths_vec) { + if (index == 0) { + super_image = each_path; + } else if (index == 1) { + local_system_image = each_path; + } else if (index == 2) { + image_dir = each_path; + } + index++; + } + // no specific image directory, use $ANDROID_PRODUCT_OUT + if (image_dir == "") { + image_dir = DefaultGuestImagePath("/"); + } + if (!android::base::EndsWith(image_dir, "/")) { + image_dir += "/"; + } + misc_info = CF_EXPECT(FindMiscInfo(image_dir)); + image_dir = CF_EXPECT(FindImageDir(image_dir)); + system_image_path = FindImage(local_system_image, {_SYSTEM_IMAGE_NAME_PATTERN}); + CF_EXPECT(system_image_path != "", + "Cannot find system.img in " << local_system_image); + std::string system_ext_image_path = + FindImage(local_system_image, {_SYSTEM_EXT_IMAGE_NAME_PATTERN}); + std::string product_image_path = + FindImage(local_system_image, {_PRODUCT_IMAGE_NAME_PATTERN}); + + return BuildSuperImage( + super_image, misc_info, callback_unlock, + [&](const std::string& partition) -> Result { + return GetImageForPartition(partition, image_dir, + { + {"system", system_image_path}, + {"system_ext", system_ext_image_path}, + {"product", product_image_path}, + }); + }); + } + + std::mutex interrupt_mutex_; + bool interrupted_ = false; + SubprocessWaiter waiter_; +}; + +std::unique_ptr NewAcloudMixSuperImageCommand() { + return std::unique_ptr(new AcloudMixSuperImageCommand()); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_mixsuperimage.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_mixsuperimage.h new file mode 100644 index 0000000000..17193f18e3 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_mixsuperimage.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/server_command/acloud_common.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" + +namespace cuttlefish { + +std::unique_ptr NewAcloudMixSuperImageCommand(); +} diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_translator.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_translator.cpp new file mode 100644 index 0000000000..9dd442358f --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_translator.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/acloud_translator.h" + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +static constexpr char kTranslatorHelpMessage[] = + R"(Cuttlefish Virtual Device (CVD) CLI. + +usage: cvd acloud translator + +Args: + --opt-out Opt-out CVD Acloud and choose to run original Python Acloud. + --opt-in Opt-in and run CVD Acloud as default. +Both -opt-out and --opt-in are mutually exclusive. +)"; + +class AcloudTranslatorCommand : public CvdServerHandler { + public: + AcloudTranslatorCommand(std::atomic& optout) : optout_(optout) {} + ~AcloudTranslatorCommand() = default; + + Result CanHandle(const RequestWithStdio& request) const override { + auto invocation = ParseInvocation(request.Message()); + if (invocation.arguments.size() >= 2) { + if (invocation.command == "acloud" && + invocation.arguments[0] == "translator") { + return true; + } + } + return false; + } + + cvd_common::Args CmdList() const override { return {}; } + + Result Handle(const RequestWithStdio& request) override { + CF_EXPECT(CanHandle(request)); + auto invocation = ParseInvocation(request.Message()); + if (invocation.arguments.empty() || invocation.arguments.size() < 2) { + return CF_ERR("Translator command not support"); + } + + // cvd acloud translator --opt-out + // cvd acloud translator --opt-in + cvd::Response response; + response.mutable_command_response(); + bool help = false; + bool flag_optout = false; + bool flag_optin = false; + std::vector translator_flags = { + GflagsCompatFlag("help", help), + GflagsCompatFlag("opt-out", flag_optout), + GflagsCompatFlag("opt-in", flag_optin), + }; + CF_EXPECT(ConsumeFlags(translator_flags, invocation.arguments), + "Failed to process translator flag."); + if (help) { + WriteAll(request.Out(), kTranslatorHelpMessage); + return response; + } + CF_EXPECT(flag_optout != flag_optin, + "Only one of --opt-out or --opt-in should be given."); + optout_ = flag_optout; + return response; + } + Result Interrupt() override { return CF_ERR("Can't be interrupted."); } + + private: + std::atomic& optout_; +}; + +std::unique_ptr NewAcloudTranslatorCommand( + std::atomic& optout) { + return std::unique_ptr(new AcloudTranslatorCommand(optout)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_translator.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_translator.h new file mode 100644 index 0000000000..799d6c693b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_translator.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +#include "host/commands/cvd/server_command/acloud_common.h" +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewAcloudTranslatorCommand( + std::atomic& optout); +} diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/cmd_list.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/cmd_list.cpp new file mode 100644 index 0000000000..97e15891d0 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/cmd_list.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/cmd_list.h" + +#include +#include + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/json.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +class CvdCmdlistHandler : public CvdServerHandler { + public: + CvdCmdlistHandler(CommandSequenceExecutor& executor) : executor_(executor) {} + + Result CanHandle(const RequestWithStdio& request) const override { + auto invocation = ParseInvocation(request.Message()); + return (invocation.command == "cmd-list"); + } + + Result Handle(const RequestWithStdio& request) override { + std::lock_guard interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + + cvd::Response response; + response.mutable_command_response(); // Sets oneof member + response.mutable_status()->set_code(cvd::Status::OK); + + CF_EXPECT(CanHandle(request)); + + auto [subcmd, subcmd_args] = ParseInvocation(request.Message()); + const auto subcmds = executor_.CmdList(); + + std::vector subcmds_vec{subcmds.begin(), subcmds.end()}; + const auto subcmds_str = android::base::Join(subcmds_vec, ","); + Json::Value subcmd_info; + subcmd_info["subcmd"] = subcmds_str; + WriteAll(request.Out(), subcmd_info.toStyledString()); + return response; + } + + Result Interrupt() override { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + CF_EXPECT(executor_.Interrupt()); + return {}; + } + + // not intended to be used by the user + cvd_common::Args CmdList() const override { return {}; } + + private: + std::mutex interruptible_; + bool interrupted_ = false; + CommandSequenceExecutor& executor_; +}; + +std::unique_ptr NewCvdCmdlistHandler( + CommandSequenceExecutor& executor) { + return std::unique_ptr(new CvdCmdlistHandler(executor)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/cmd_list.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/cmd_list.h new file mode 100644 index 0000000000..f060883621 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/cmd_list.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdCmdlistHandler( + CommandSequenceExecutor& executor); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/components.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/components.cpp new file mode 100644 index 0000000000..10a90d5311 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/components.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/components.h" + +#include "host/commands/cvd/server_command/fetch.h" +#include "host/commands/cvd/server_command/fleet.h" + +namespace cuttlefish { + +fruit::Component> +cvdCommandComponent() { + return fruit::createComponent() + .install(cvdFleetCommandComponent) + .install(cvdFetchCommandComponent); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/components.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/components.h new file mode 100644 index 0000000000..5d91d67f0e --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/components.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 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 + +#include + +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server.h" +#include "host/commands/cvd/server_command/generic.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" +#include "host/libs/web/build_api.h" + +namespace cuttlefish { + +fruit::Component> CvdHelpComponent(); + +fruit::Component> +cvdCommandComponent(); + +fruit::Component> +CvdRestartComponent(); + +fruit::Component> +cvdShutdownComponent(); + +fruit::Component<> cvdVersionComponent(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/display.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/display.cpp new file mode 100644 index 0000000000..169c3f70f8 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/display.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/display.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/flag.h" +#include "host/commands/cvd/selector/instance_database_types.h" +#include "host/commands/cvd/selector/instance_group_record.h" +#include "host/commands/cvd/selector/instance_record.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace { +constexpr char kSummaryHelpText[] = + R"(Enables hotplug/unplug of displays from running cuttlefish virtual devices)"; + +constexpr char kDetailedHelpText[] = + R"(Cuttlefish Virtual Device (CVD) Display CLI. + +usage: cvd display + +Commands: + help Print help for a command. + add Adds a new display to a given device. + list Prints the currently connected displays. + remove Removes a display from a given device. +)"; +} + +class CvdDisplayCommandHandler : public CvdServerHandler { + public: + CvdDisplayCommandHandler(InstanceManager& instance_manager, + SubprocessWaiter& subprocess_waiter) + : instance_manager_{instance_manager}, + subprocess_waiter_(subprocess_waiter), + cvd_display_operations_{"display"} {} + + Result CanHandle(const RequestWithStdio& request) const { + auto invocation = ParseInvocation(request.Message()); + return Contains(cvd_display_operations_, invocation.command); + } + + Result Handle(const RequestWithStdio& request) override { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CanHandle(request)); + CF_EXPECT(VerifyPrecondition(request)); + cvd_common::Envs envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + + auto [_, subcmd_args] = ParseInvocation(request.Message()); + + bool is_help = CF_EXPECT(IsHelp(subcmd_args)); + // may modify subcmd_args by consuming in parsing + Command command = + is_help ? CF_EXPECT(HelpCommand(request, subcmd_args, envs)) + : CF_EXPECT(NonHelpCommand(request, subcmd_args, envs)); + CF_EXPECT(subprocess_waiter_.Setup(command.Start())); + interrupt_lock.unlock(); + + auto infop = CF_EXPECT(subprocess_waiter_.Wait()); + return ResponseFromSiginfo(infop); + } + + Result Interrupt() override { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + CF_EXPECT(subprocess_waiter_.Interrupt()); + return {}; + } + + cvd_common::Args CmdList() const override { + return cvd_common::Args(cvd_display_operations_.begin(), + cvd_display_operations_.end()); + } + + Result SummaryHelp() const override { return kSummaryHelpText; } + + bool ShouldInterceptHelp() const override { return true; } + + Result DetailedHelp(std::vector&) const override { + return kDetailedHelpText; + } + + private: + Result HelpCommand(const RequestWithStdio& request, + const cvd_common::Args& subcmd_args, + cvd_common::Envs envs) { + CF_EXPECT(Contains(envs, kAndroidHostOut)); + auto cvd_display_bin_path = + ConcatToString(envs.at(kAndroidHostOut), "/bin/", kDisplayBin); + std::string home = Contains(envs, "HOME") + ? envs.at("HOME") + : CF_EXPECT(SystemWideUserHome()); + envs["HOME"] = home; + envs[kAndroidSoongHostOut] = envs.at(kAndroidHostOut); + ConstructCommandParam construct_cmd_param{ + .bin_path = cvd_display_bin_path, + .home = home, + .args = subcmd_args, + .envs = envs, + .working_dir = request.Message().command_request().working_directory(), + .command_name = kDisplayBin, + .in = request.In(), + .out = request.Out(), + .err = request.Err()}; + Command command = CF_EXPECT(ConstructCommand(construct_cmd_param)); + return command; + } + + Result NonHelpCommand(const RequestWithStdio& request, + cvd_common::Args& subcmd_args, + cvd_common::Envs envs) { + // test if there is --instance_num flag + CvdFlag instance_num_flag("instance_num"); + auto instance_num_opt = + CF_EXPECT(instance_num_flag.FilterFlag(subcmd_args)); + selector::Queries extra_queries; + if (instance_num_opt) { + extra_queries.emplace_back(selector::kInstanceIdField, *instance_num_opt); + } + + const auto& selector_opts = + request.Message().command_request().selector_opts(); + const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args()); + + auto instance = CF_EXPECT( + instance_manager_.SelectInstance(selector_args, extra_queries, envs)); + const auto& instance_group = instance.ParentGroup(); + const auto& home = instance_group.HomeDir(); + + const auto& android_host_out = instance_group.HostArtifactsPath(); + auto cvd_display_bin_path = + ConcatToString(android_host_out, "/bin/", kDisplayBin); + + cvd_common::Args cvd_env_args{subcmd_args}; + cvd_env_args.push_back( + ConcatToString("--instance_num=", instance.InstanceId())); + envs["HOME"] = home; + envs[kAndroidHostOut] = android_host_out; + envs[kAndroidSoongHostOut] = android_host_out; + + std::stringstream command_to_issue; + command_to_issue << "HOME=" << home << " " << kAndroidHostOut << "=" + << android_host_out << " " << kAndroidSoongHostOut << "=" + << android_host_out << " " << cvd_display_bin_path << " "; + for (const auto& arg : cvd_env_args) { + command_to_issue << arg << " "; + } + WriteAll(request.Err(), command_to_issue.str()); + + ConstructCommandParam construct_cmd_param{ + .bin_path = cvd_display_bin_path, + .home = home, + .args = cvd_env_args, + .envs = envs, + .working_dir = request.Message().command_request().working_directory(), + .command_name = kDisplayBin, + .in = request.In(), + .out = request.Out(), + .err = request.Err()}; + Command command = CF_EXPECT(ConstructCommand(construct_cmd_param)); + return command; + } + + Result IsHelp(const cvd_common::Args& cmd_args) const { + // cvd display --help, --helpxml, etc or simply cvd display + if (cmd_args.empty() || CF_EXPECT(IsHelpSubcmd(cmd_args))) { + return true; + } + // cvd display help format + return (cmd_args.front() == "help"); + } + + InstanceManager& instance_manager_; + SubprocessWaiter& subprocess_waiter_; + std::mutex interruptible_; + bool interrupted_ = false; + std::vector cvd_display_operations_; + static constexpr char kDisplayBin[] = "cvd_internal_display"; +}; + +std::unique_ptr NewCvdDisplayCommandHandler( + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter) { + return std::unique_ptr( + new CvdDisplayCommandHandler(instance_manager, subprocess_waiter)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/display.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/display.h new file mode 100644 index 0000000000..07f0ff4811 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/display.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdDisplayCommandHandler( + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/env.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/env.cpp new file mode 100644 index 0000000000..c1ad4119b3 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/env.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/env.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/flag.h" +#include "host/commands/cvd/selector/instance_group_record.h" +#include "host/commands/cvd/selector/instance_record.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +class CvdEnvCommandHandler : public CvdServerHandler { + public: + CvdEnvCommandHandler(InstanceManager& instance_manager, + SubprocessWaiter& subprocess_waiter) + : instance_manager_{instance_manager}, + subprocess_waiter_(subprocess_waiter), + cvd_env_operations_{"env"} {} + + Result CanHandle(const RequestWithStdio& request) const { + auto invocation = ParseInvocation(request.Message()); + return Contains(cvd_env_operations_, invocation.command); + } + + Result Handle(const RequestWithStdio& request) override { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CanHandle(request)); + CF_EXPECT(VerifyPrecondition(request)); + cvd_common::Envs envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + + auto [_, subcmd_args] = ParseInvocation(request.Message()); + + /* + * cvd_env --help only. Not --helpxml, etc. + * + * Otherwise, IsHelpSubcmd() should be used here instead. + */ + auto help_flag = CvdFlag("help", false); + cvd_common::Args subcmd_args_copy{subcmd_args}; + auto help_parse_result = help_flag.CalculateFlag(subcmd_args_copy); + bool is_help = help_parse_result.ok() && (*help_parse_result); + + Command command = + is_help ? CF_EXPECT(HelpCommand(request, subcmd_args, envs)) + : CF_EXPECT(NonHelpCommand(request, subcmd_args, envs)); + CF_EXPECT(subprocess_waiter_.Setup(command.Start())); + interrupt_lock.unlock(); + + auto infop = CF_EXPECT(subprocess_waiter_.Wait()); + return ResponseFromSiginfo(infop); + } + + Result Interrupt() override { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + CF_EXPECT(subprocess_waiter_.Interrupt()); + return {}; + } + + cvd_common::Args CmdList() const override { + return cvd_common::Args(cvd_env_operations_.begin(), + cvd_env_operations_.end()); + } + + private: + Result HelpCommand(const RequestWithStdio& request, + const cvd_common::Args& subcmd_args, + const cvd_common::Envs& envs) { + CF_EXPECT(Contains(envs, kAndroidHostOut)); + return CF_EXPECT( + ConstructCvdHelpCommand(kCvdEnvBin, envs, subcmd_args, request)); + } + + Result NonHelpCommand(const RequestWithStdio& request, + const cvd_common::Args& subcmd_args, + const cvd_common::Envs& envs) { + const auto& selector_opts = + request.Message().command_request().selector_opts(); + const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args()); + + auto instance = + CF_EXPECT(instance_manager_.SelectInstance(selector_args, envs)); + const auto& instance_group = instance.ParentGroup(); + const auto& home = instance_group.HomeDir(); + + const auto& android_host_out = instance_group.HostArtifactsPath(); + auto cvd_env_bin_path = + ConcatToString(android_host_out, "/bin/", kCvdEnvBin); + const auto& internal_device_name = instance.InternalDeviceName(); + + cvd_common::Args cvd_env_args{internal_device_name}; + cvd_env_args.insert(cvd_env_args.end(), subcmd_args.begin(), + subcmd_args.end()); + + return CF_EXPECT( + ConstructCvdGenericNonHelpCommand({.bin_file = kCvdEnvBin, + .envs = envs, + .cmd_args = cvd_env_args, + .android_host_out = android_host_out, + .home = home, + .verbose = true}, + request)); + } + + InstanceManager& instance_manager_; + SubprocessWaiter& subprocess_waiter_; + std::mutex interruptible_; + bool interrupted_ = false; + std::vector cvd_env_operations_; + + static constexpr char kCvdEnvBin[] = "cvd_internal_env"; +}; + +std::unique_ptr NewCvdEnvCommandHandler( + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter) { + return std::unique_ptr( + new CvdEnvCommandHandler(instance_manager, subprocess_waiter)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/env.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/env.h new file mode 100644 index 0000000000..686a97bf81 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/env.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdEnvCommandHandler( + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/fetch.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/fetch.cpp new file mode 100644 index 0000000000..574e28984d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/fetch.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/fetch.h" + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +class CvdFetchCommandHandler : public CvdServerHandler { + public: + CvdFetchCommandHandler(SubprocessWaiter& subprocess_waiter) + : subprocess_waiter_(subprocess_waiter), + fetch_cmd_list_{std::vector{"fetch", "fetch_cvd"}} {} + + Result CanHandle(const RequestWithStdio& request) const override; + Result Handle(const RequestWithStdio& request) override; + Result Interrupt() override; + cvd_common::Args CmdList() const override { return fetch_cmd_list_; } + Result SummaryHelp() const override; + virtual bool ShouldInterceptHelp() const { return true; } + Result DetailedHelp(std::vector&) const override; + + private: + SubprocessWaiter& subprocess_waiter_; + std::mutex interruptible_; + bool interrupted_ = false; + std::vector fetch_cmd_list_; +}; + +Result CvdFetchCommandHandler::CanHandle( + const RequestWithStdio& request) const { + auto invocation = ParseInvocation(request.Message()); + return Contains(fetch_cmd_list_, invocation.command); +} + +Result CvdFetchCommandHandler::Handle( + const RequestWithStdio& request) { + std::unique_lock interrupt_lock(interruptible_); + if (interrupted_) { + return CF_ERR("Interrupted"); + } + CF_EXPECT(CanHandle(request)); + + Command command("/proc/self/exe"); + command.SetName("fetch_cvd"); + command.SetExecutable("/proc/self/exe"); + + for (const auto& argument : ParseInvocation(request.Message()).arguments) { + command.AddParameter(argument); + } + + command.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, request.In()); + command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, request.Out()); + command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, request.Err()); + SubprocessOptions options; + + const auto& command_request = request.Message().command_request(); + if (command_request.wait_behavior() == cvd::WAIT_BEHAVIOR_START) { + options.ExitWithParent(false); + } + + const auto& working_dir = command_request.working_directory(); + if (!working_dir.empty()) { + auto fd = SharedFD::Open(working_dir, O_RDONLY | O_PATH | O_DIRECTORY); + if (fd->IsOpen()) { + command.SetWorkingDirectory(fd); + } + } + + CF_EXPECT(subprocess_waiter_.Setup(command.Start(std::move(options)))); + + if (command_request.wait_behavior() == cvd::WAIT_BEHAVIOR_START) { + cvd::Response response; + response.mutable_command_response(); + response.mutable_status()->set_code(cvd::Status::OK); + return response; + } + + interrupt_lock.unlock(); + + auto infop = CF_EXPECT(subprocess_waiter_.Wait()); + + return ResponseFromSiginfo(infop); +} + +Result CvdFetchCommandHandler::Interrupt() { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + CF_EXPECT(subprocess_waiter_.Interrupt()); + return {}; +} + +Result CvdFetchCommandHandler::SummaryHelp() const { + return "Retrieve build artifacts based on branch and target names"; +} + +Result CvdFetchCommandHandler::DetailedHelp( + std::vector&) const { + Command fetch_command("/proc/self/exe"); + fetch_command.SetName("fetch_cvd"); + fetch_command.SetExecutable("/proc/self/exe"); + fetch_command.AddParameter("--help"); + + std::string output; + RunWithManagedStdio(std::move(fetch_command), nullptr, nullptr, &output); + return output; +} + +std::unique_ptr NewCvdFetchCommandHandler( + SubprocessWaiter& subprocess_waiter) { + return std::unique_ptr( + new CvdFetchCommandHandler(subprocess_waiter)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/fetch.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/fetch.h new file mode 100644 index 0000000000..d606ea2483 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/fetch.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdFetchCommandHandler( + SubprocessWaiter& subprocess_waiter); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/flags_collector.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/flags_collector.cpp new file mode 100644 index 0000000000..fe235e47d7 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/flags_collector.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/flags_collector.h" + +#include +#include + +#include "common/libs/utils/contains.h" + +namespace cuttlefish { +namespace { + +struct XmlDocDeleter { + void operator()(struct _xmlDoc* doc); +}; + +using XmlDocPtr = std::unique_ptr; + +void XmlDocDeleter::operator()(struct _xmlDoc* doc) { + if (!doc) { + return; + } + xmlFree(doc); +} + +/* + * Each "flag" xmlNode has child nodes such as file, name, meaning, + * type, default, current, etc. Each child xmlNode is a leaf XML node, + * which means that each child xmlNode has a child, and that child + * keeps the value: e.g. the value of "name" xmlNode is the name + * of the flag such as "daemon", "restart_subprocesses", etc. + * + * For the grandchild xmlNode of a flag xmlNode, we could do this + * to retrieve the string value: + * xmlNodeListGetString(doc, grandchild, 1); + */ +FlagInfoPtr ParseFlagNode(struct _xmlDoc* doc, xmlNode& flag) { + std::unordered_map field_value_map; + for (xmlNode* child = flag.xmlChildrenNode; child != nullptr; + child = child->next) { + if (!child->name) { + continue; + } + std::string field_name = reinterpret_cast(child->name); + if (!child->xmlChildrenNode) { + field_value_map[field_name] = ""; + continue; + } + auto* xml_node_text = xmlNodeListGetString(doc, child->xmlChildrenNode, 1); + if (!xml_node_text) { + field_value_map[field_name] = ""; + continue; + } + field_value_map[field_name] = reinterpret_cast(xml_node_text); + } + if (field_value_map.empty()) { + return nullptr; + } + return FlagInfo::Create(field_value_map); +} + +std::vector ParseXml(struct _xmlDoc* doc, xmlNode* node) { + if (!node) { + return {}; + } + + std::vector flags; + // if it is node + if (node->name && + xmlStrcmp(node->name, reinterpret_cast("flag")) == 0) { + auto flag_info = ParseFlagNode(doc, *node); + // we don't assume that a flag node is nested. + if (flag_info) { + flags.push_back(std::move(flag_info)); + return flags; + } + return {}; + } + + if (!node->xmlChildrenNode) { + return {}; + } + + for (xmlNode* child_node = node->xmlChildrenNode; child_node != nullptr; + child_node = child_node->next) { + auto child_flags = ParseXml(doc, child_node); + if (child_flags.empty()) { + continue; + } + for (auto& child_flag : child_flags) { + flags.push_back(std::move(child_flag)); + } + } + return flags; +} + +XmlDocPtr BuildXmlDocFromString(const std::string& xml_str) { + struct _xmlDoc* doc = + xmlReadMemory(xml_str.data(), xml_str.size(), NULL, NULL, 0); + XmlDocPtr doc_uniq_ptr = XmlDocPtr(doc, XmlDocDeleter()); + if (!doc) { + LOG(ERROR) << "helpxml parsing failed: " << xml_str; + return nullptr; + } + return doc_uniq_ptr; +} + +std::optional> LoadFromXml(XmlDocPtr&& doc) { + std::vector flags; + XmlDocPtr moved_doc = std::move(doc); + xmlNode* root = xmlDocGetRootElement(moved_doc.get()); + if (!root) { + LOG(ERROR) << "Failed to get the root element from XML doc."; + return std::nullopt; + } + flags = ParseXml(moved_doc.get(), root); + return flags; +} + +} // namespace + +std::unique_ptr FlagInfo::Create( + const FlagInfoFieldMap& field_value_map) { + if (!Contains(field_value_map, "name") || + field_value_map.at("name").empty()) { + return nullptr; + } + if (!Contains(field_value_map, "type") || + field_value_map.at("type").empty()) { + return nullptr; + } + FlagInfo* new_flag_info = new FlagInfo(field_value_map); + return std::unique_ptr(new_flag_info); +} + +std::optional> CollectFlagsFromHelpxml( + const std::string& xml_str) { + auto helpxml_doc = BuildXmlDocFromString(xml_str); + if (!helpxml_doc) { + return std::nullopt; + } + return LoadFromXml(std::move(helpxml_doc)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/flags_collector.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/flags_collector.h new file mode 100644 index 0000000000..3423d3efe1 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/flags_collector.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 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 + +#include +#include +#include +#include +#include + +namespace cuttlefish { + +class FlagInfo { + public: + using FlagInfoFieldMap = std::unordered_map; + static std::unique_ptr Create( + const FlagInfoFieldMap& field_value_map); + const std::string& Name() const { return name_; } + const std::string& Type() const { return type_; } + + private: + // field_value_map must have needed fields; guaranteed by the factory + // function, static Create(). + FlagInfo(const FlagInfoFieldMap& field_value_map) + : name_(field_value_map.at("name")), type_(field_value_map.at("type")) {} + + // TODO(kwstephenkim): add more fields + std::string name_; + std::string type_; +}; + +using FlagInfoPtr = std::unique_ptr; + +std::optional> CollectFlagsFromHelpxml( + const std::string& xml_str); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/fleet.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/fleet.cpp new file mode 100644 index 0000000000..5514fc9eb6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/fleet.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/fleet.h" + +#include + +#include + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/selector/instance_database_types.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/status_fetcher.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { + +static constexpr char kHelpMessage[] = R"( +usage: cvd fleet [--help] + + cvd fleet will list the active devices with information. +)"; + +class CvdFleetCommandHandler : public CvdServerHandler { + public: + CvdFleetCommandHandler(InstanceManager& instance_manager, + SubprocessWaiter& subprocess_waiter, + HostToolTargetManager& host_tool_target_manager) + : instance_manager_(instance_manager), + subprocess_waiter_(subprocess_waiter), + status_fetcher_(instance_manager_, host_tool_target_manager) {} + + Result CanHandle(const RequestWithStdio& request) const; + Result Handle(const RequestWithStdio& request) override; + Result Interrupt() override; + cvd_common::Args CmdList() const override { return {kFleetSubcmd}; } + + private: + InstanceManager& instance_manager_; + SubprocessWaiter& subprocess_waiter_; + StatusFetcher status_fetcher_; + std::mutex interruptible_; + bool interrupted_ = false; + + static constexpr char kFleetSubcmd[] = "fleet"; + Result CvdFleetHelp(const SharedFD& out) const; + bool IsHelp(const cvd_common::Args& cmd_args) const; +}; + +Result CvdFleetCommandHandler::CanHandle( + const RequestWithStdio& request) const { + auto invocation = ParseInvocation(request.Message()); + return invocation.command == kFleetSubcmd; +} + +Result CvdFleetCommandHandler::Interrupt() { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + CF_EXPECT(subprocess_waiter_.Interrupt()); + return {}; +} + +Result CvdFleetCommandHandler::Handle( + const RequestWithStdio& request) { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CanHandle(request)); + + cvd::Response ok_response; + ok_response.mutable_command_response(); + auto& status = *ok_response.mutable_status(); + status.set_code(cvd::Status::OK); + + auto [sub_cmd, args] = ParseInvocation(request.Message()); + auto envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + interrupt_lock.unlock(); + + if (IsHelp(args)) { + CF_EXPECT(CvdFleetHelp(request.Out())); + return ok_response; + } + + CF_EXPECT(Contains(envs, "ANDROID_HOST_OUT") && + DirectoryExists(envs.at("ANDROID_HOST_OUT"))); + Json::Value groups_json(Json::arrayValue); + auto all_group_names = instance_manager_.AllGroupNames(); + envs.erase(kCuttlefishInstanceEnvVarName); + for (const auto& group_name : all_group_names) { + auto group_obj_copy_result = instance_manager_.SelectGroup( + {}, InstanceManager::Queries{{selector::kGroupNameField, group_name}}, + {}); + if (!group_obj_copy_result.ok()) { + LOG(DEBUG) << "Group \"" << group_name + << "\" has already been removed. Skipped."; + continue; + } + + Json::Value group_json(Json::objectValue); + group_json["group_name"] = group_name; + group_json["start_time"] = + selector::Format(group_obj_copy_result->StartTime()); + + auto request_message = MakeRequest( + {.cmd_args = {"cvd", "status", "--print", "--all_instances"}, + .env = envs, + .selector_args = {"--group_name", group_name}, + .working_dir = + request.Message().command_request().working_directory()}); + RequestWithStdio group_request{request.Client(), request_message, + request.FileDescriptors(), + request.Credentials()}; + auto [_, instances_json, group_response] = + CF_EXPECT(status_fetcher_.FetchStatus(group_request)); + CF_EXPECT_EQ( + group_response.status().code(), cvd::Status::OK, + fmt::format( + "Running cvd status --all_instances for group \"{}\" failed", + group_name)); + group_json["instances"] = instances_json; + groups_json.append(group_json); + } + Json::Value output_json(Json::objectValue); + output_json["groups"] = groups_json; + auto serialized_json = output_json.toStyledString(); + CF_EXPECT_EQ(WriteAll(request.Out(), serialized_json), + serialized_json.size()); + return ok_response; +} + +bool CvdFleetCommandHandler::IsHelp(const cvd_common::Args& args) const { + for (const auto& arg : args) { + if (arg == "--help" || arg == "-help") { + return true; + } + } + return false; +} + +Result CvdFleetCommandHandler::CvdFleetHelp( + const SharedFD& out) const { + const std::string help_message(kHelpMessage); + CF_EXPECT_EQ(WriteAll(out, help_message), help_message.size()); + cvd::Status status; + status.set_code(cvd::Status::OK); + return status; +} + +std::unique_ptr NewCvdFleetCommandHandler( + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter, + HostToolTargetManager& host_tool_target_manager) { + return std::unique_ptr(new CvdFleetCommandHandler( + instance_manager, subprocess_waiter, host_tool_target_manager)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/fleet.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/fleet.h new file mode 100644 index 0000000000..02682aa1f4 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/fleet.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 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 + +#include + +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdFleetCommandHandler( + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter, + HostToolTargetManager& host_tool_target_manager); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/generic.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/generic.cpp new file mode 100644 index 0000000000..f14d4e1e84 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/generic.cpp @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/generic.h" + +#include + +#include +#include + +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/subprocess.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/interruptible_terminal.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" +#include "host/libs/config/instance_nums.h" + +namespace cuttlefish { + +class CvdGenericCommandHandler : public CvdServerHandler { + public: + CvdGenericCommandHandler(InstanceLockFileManager& instance_lockfile_manager, + InstanceManager& instance_manager, + SubprocessWaiter& subprocess_waiter, + HostToolTargetManager& host_tool_target_manager); + + Result CanHandle(const RequestWithStdio& request) const; + Result Handle(const RequestWithStdio& request) override; + Result Interrupt() override; + cvd_common::Args CmdList() const override; + + private: + struct CommandInvocationInfo { + std::string command; + std::string bin; + std::string bin_path; + std::string home; + std::string host_artifacts_path; + uid_t uid; + std::vector args; + cvd_common::Envs envs; + }; + enum class UiResponseType : int { + kNoGroup = 1, // no group is active + kNoTTY = 2, // there are groups to select but no tty for user input + kUserSelection = 3, // selector couldn't pick so asked the user + kCvdServerPick = 4, // selector picks based on selector flags, env, etc + }; + struct ExtractedInfo { + CommandInvocationInfo invocation_info; + std::optional group; + bool is_non_help_cvd; + UiResponseType ui_response_type; + }; + Result ExtractInfo(const RequestWithStdio& request); + Result GetBin(const std::string& subcmd) const; + Result GetBin(const std::string& subcmd, + const std::string& host_artifacts_path) const; + bool IsStopCommand(const std::string& subcmd) const { + return subcmd == "stop" || subcmd == "stop_cvd"; + } + // whether the "bin" is cvd bins like stop_cvd or not (e.g. ln, ls, mkdir) + // The information to fire the command might be different. This information + // is about what the executable binary is and how to find it. + struct BinPathInfo { + std::string bin_; + std::string bin_path_; + std::string host_artifacts_path_; + }; + Result NonCvdBinPath(const std::string& subcmd, + const cvd_common::Envs& envs) const; + Result CvdHelpBinPath(const std::string& subcmd, + const cvd_common::Envs& envs) const; + + InstanceLockFileManager& instance_lockfile_manager_; + InstanceManager& instance_manager_; + SubprocessWaiter& subprocess_waiter_; + HostToolTargetManager& host_tool_target_manager_; + std::mutex interruptible_; + bool interrupted_ = false; + using BinGeneratorType = std::function( + const std::string& host_artifacts_path)>; + std::map command_to_binary_map_; + std::unique_ptr terminal_ = nullptr; + + static constexpr char kHostBugreportBin[] = "cvd_internal_host_bugreport"; + static constexpr char kLnBin[] = "ln"; + static constexpr char kMkdirBin[] = "mkdir"; + static constexpr char kClearBin[] = + "clear_placeholder"; // Unused, runs CvdClear() + // Only indicates that host_tool_target_manager_ should generate at runtime + static constexpr char kBinGeneratedAtRuntime[] = + "host_tool_manager_generates_at_runtime_placeholder"; +}; + +CvdGenericCommandHandler::CvdGenericCommandHandler( + InstanceLockFileManager& instance_lockfile_manager, + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter, + HostToolTargetManager& host_tool_target_manager) + : instance_lockfile_manager_(instance_lockfile_manager), + instance_manager_(instance_manager), + subprocess_waiter_(subprocess_waiter), + host_tool_target_manager_(host_tool_target_manager), + command_to_binary_map_{{"host_bugreport", kHostBugreportBin}, + {"cvd_host_bugreport", kHostBugreportBin}, + {"stop", kBinGeneratedAtRuntime}, + {"stop_cvd", kBinGeneratedAtRuntime}, + {"clear", kClearBin}, + {"mkdir", kMkdirBin}, + {"ln", kLnBin}} {} + +Result CvdGenericCommandHandler::CanHandle( + const RequestWithStdio& request) const { + auto invocation = ParseInvocation(request.Message()); + return Contains(command_to_binary_map_, invocation.command); +} + +Result CvdGenericCommandHandler::Interrupt() { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + if (terminal_) { + auto terminal_interrupt_result = terminal_->Interrupt(); + // TODO(b/316202887): utilize the multi-CF_EXPECT feature + if (!terminal_interrupt_result.ok()) { + LOG(ERROR) << "Failed to interrupt terminal"; + } + } + CF_EXPECT(subprocess_waiter_.Interrupt()); + return {}; +} + +Result CvdGenericCommandHandler::Handle( + const RequestWithStdio& request) { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CanHandle(request)); + CF_EXPECT(request.Credentials() != std::nullopt); + + cvd::Response response; + response.mutable_command_response(); + + auto precondition_verified = VerifyPrecondition(request); + if (!precondition_verified.ok()) { + response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION); + response.mutable_status()->set_message( + precondition_verified.error().Message()); + return response; + } + + interrupt_lock.unlock(); + auto [invocation_info, group_opt, is_non_help_cvd, ui_response_type] = + CF_EXPECT(ExtractInfo(request)); + + interrupt_lock.lock(); + CF_EXPECT(!interrupted_, "Interrupted"); + if (invocation_info.bin == kClearBin) { + *response.mutable_status() = + instance_manager_.CvdClear(request.Out(), request.Err()); + return response; + } + + // besides the two cases, the rest will be handled by running subprocesses + if (is_non_help_cvd && ui_response_type == UiResponseType::kNoGroup) { + return CF_EXPECT(NoGroupResponse(request)); + } + if (is_non_help_cvd && ui_response_type == UiResponseType::kNoTTY) { + return CF_EXPECT(NoTTYResponse(request)); + } + + ConstructCommandParam construct_cmd_param{ + .bin_path = invocation_info.bin_path, + .home = invocation_info.home, + .args = invocation_info.args, + .envs = invocation_info.envs, + .working_dir = request.Message().command_request().working_directory(), + .command_name = invocation_info.bin, + .in = request.In(), + .out = request.Out(), + .err = request.Err()}; + Command command = CF_EXPECT(ConstructCommand(construct_cmd_param)); + + SubprocessOptions options; + if (request.Message().command_request().wait_behavior() == + cvd::WAIT_BEHAVIOR_START) { + options.ExitWithParent(false); + } + CF_EXPECT(subprocess_waiter_.Setup(command.Start(std::move(options)))); + + bool is_stop = IsStopCommand(invocation_info.command); + + // captured structured bindings are a C++20 extension + // so we need [group_ptr] instead of [&group_opt] + auto* group_ptr = (group_opt ? std::addressof(*group_opt) : nullptr); + android::base::ScopeGuard exit_action([this, is_stop, group_ptr]() { + if (!is_stop) { + return; + } + if (!group_ptr) { + return; + } + for (const auto& instance : group_ptr->Instances()) { + auto lock = + instance_lockfile_manager_.RemoveLockFile(instance->InstanceId()); + if (!lock.ok()) { + LOG(ERROR) << "Deleting instance Lock file for ID #" + << instance->InstanceId() + << " failed: " << lock.error().Message(); + } + } + }); + + if (request.Message().command_request().wait_behavior() == + cvd::WAIT_BEHAVIOR_START) { + response.mutable_status()->set_code(cvd::Status::OK); + return response; + } + + interrupt_lock.unlock(); + + auto infop = CF_EXPECT(subprocess_waiter_.Wait()); + + if (infop.si_code == CLD_EXITED && IsStopCommand(invocation_info.command)) { + instance_manager_.RemoveInstanceGroup(invocation_info.home); + } + + return ResponseFromSiginfo(infop); +} + +std::vector CvdGenericCommandHandler::CmdList() const { + std::vector subcmd_list; + subcmd_list.reserve(command_to_binary_map_.size()); + for (const auto& [cmd, _] : command_to_binary_map_) { + subcmd_list.emplace_back(cmd); + } + return subcmd_list; +} + +Result +CvdGenericCommandHandler::NonCvdBinPath(const std::string& subcmd, + const cvd_common::Envs& envs) const { + auto bin_path_base = CF_EXPECT(GetBin(subcmd)); + // no need of executable directory. Will look up by PATH + // bin_path_base is like ln, mkdir, etc. + return BinPathInfo{.bin_ = bin_path_base, + .bin_path_ = bin_path_base, + .host_artifacts_path_ = envs.at(kAndroidHostOut)}; +} + +Result +CvdGenericCommandHandler::CvdHelpBinPath(const std::string& subcmd, + const cvd_common::Envs& envs) const { + auto tool_dir_path = envs.at(kAndroidHostOut); + if (!DirectoryExists(tool_dir_path + "/bin")) { + tool_dir_path = + android::base::Dirname(android::base::GetExecutableDirectory()); + } + auto bin_path_base = CF_EXPECT(GetBin(subcmd, tool_dir_path)); + // no need of executable directory. Will look up by PATH + // bin_path_base is like ln, mkdir, etc. + return BinPathInfo{ + .bin_ = bin_path_base, + .bin_path_ = tool_dir_path.append("/bin/").append(bin_path_base), + .host_artifacts_path_ = envs.at(kAndroidHostOut)}; +} + +/* + * commands like ln, mkdir, clear + * -> bin, bin, system_wide_home, N/A, cmd_args, envs + * + * help command + * -> android_out/bin, bin, system_wide_home, android_out, cmd_args, envs + * + * non-help command + * -> group->a/o/bin, bin, group->home, group->android_out, cmd_args, envs + * + */ +Result +CvdGenericCommandHandler::ExtractInfo(const RequestWithStdio& request) { + auto result_opt = request.Credentials(); + CF_EXPECT(result_opt != std::nullopt); + const uid_t uid = result_opt->uid; + + auto [subcmd, cmd_args] = ParseInvocation(request.Message()); + CF_EXPECT(Contains(command_to_binary_map_, subcmd)); + + cvd_common::Envs envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + const auto& selector_opts = + request.Message().command_request().selector_opts(); + const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args()); + CF_EXPECT(Contains(envs, kAndroidHostOut) && + DirectoryExists(envs.at(kAndroidHostOut))); + + std::unordered_set non_cvd_op{"clear", "mkdir", "ln"}; + if (Contains(non_cvd_op, subcmd) || CF_EXPECT(IsHelpSubcmd(cmd_args))) { + const auto [bin, bin_path, host_artifacts_path] = + Contains(non_cvd_op, subcmd) ? CF_EXPECT(NonCvdBinPath(subcmd, envs)) + : CF_EXPECT(CvdHelpBinPath(subcmd, envs)); + return ExtractedInfo{ + .invocation_info = + CommandInvocationInfo{ + .command = subcmd, + .bin = bin, + .bin_path = bin_path, + .home = CF_EXPECT(SystemWideUserHome()), + .host_artifacts_path = envs.at(kAndroidHostOut), + .uid = uid, + .args = cmd_args, + .envs = envs}, + .group = std::nullopt, + .is_non_help_cvd = false, + .ui_response_type = UiResponseType::kCvdServerPick, + }; + } + + auto instance_group_result = + instance_manager_.SelectGroup(selector_args, envs); + ExtractedInfo extracted_info{ + .invocation_info = CommandInvocationInfo(), + .group = std::nullopt, + .is_non_help_cvd = true, + .ui_response_type = UiResponseType::kCvdServerPick, + }; + std::string chosen_group_name; + if (!instance_group_result.ok()) { + if (!instance_manager_.HasInstanceGroups()) { + extracted_info.ui_response_type = UiResponseType::kNoGroup; + return extracted_info; + } + + if (!request.In()->IsOpen() || !request.In()->IsATTY()) { + // can't take the user input + extracted_info.ui_response_type = UiResponseType::kNoTTY; + return extracted_info; + } + + extracted_info.ui_response_type = UiResponseType::kUserSelection; + std::unique_lock lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + // show the menu and let the user choose + auto group_summaries = CF_EXPECT(instance_manager_.GroupSummaryMenu()); + auto& group_summary_menu = group_summaries.menu; + CF_EXPECT_EQ(WriteAll(request.Out(), group_summary_menu + "\n"), + group_summary_menu.size() + 1); + terminal_ = std::make_unique(request.In()); + lock.unlock(); + + const bool is_tty = request.Err()->IsOpen() && request.Err()->IsATTY(); + while (true) { + lock.lock(); + std::string question = fmt::format( + "For which instance group would you like to run {}? ", subcmd); + CF_EXPECT_EQ(WriteAll(request.Out(), question), question.size()); + lock.unlock(); + + std::string input_line = CF_EXPECT(terminal_->ReadLine()); + int selection = -1; + if (android::base::ParseInt(input_line, &selection)) { + const auto n_groups = group_summaries.idx_to_group_name.size(); + if (n_groups <= selection || selection < 0) { + std::string out_of_range = fmt::format( + "\n Selection {}{}{} is beyond the range {}[0, {}]{}\n\n", + TerminalColor(is_tty, TerminalColors::kBoldRed), selection, + TerminalColor(is_tty, TerminalColors::kReset), + TerminalColor(is_tty, TerminalColors::kCyan), n_groups - 1, + TerminalColor(is_tty, TerminalColors::kReset)); + CF_EXPECT_EQ(WriteAll(request.Err(), out_of_range), + out_of_range.size()); + continue; + } + chosen_group_name = group_summaries.idx_to_group_name[selection]; + } else { + chosen_group_name = android::base::Trim(input_line); + } + + InstanceManager::Queries extra_queries{ + {selector::kGroupNameField, chosen_group_name}}; + instance_group_result = instance_manager_.SelectGroup( + selector_args, extra_queries, envs); + if (instance_group_result.ok()) { + break; + } + std::string cannot_find_group_name = fmt::format( + "\n Failed to find a group whose name is {}\"{}\"{}\n\n", + TerminalColor(is_tty, TerminalColors::kBoldRed), chosen_group_name, + TerminalColor(is_tty, TerminalColors::kReset)); + CF_EXPECT_EQ(WriteAll(request.Err(), cannot_find_group_name), + cannot_find_group_name.size()); + } + } + + auto& instance_group = *instance_group_result; + auto android_host_out = instance_group.HostArtifactsPath(); + auto home = instance_group.HomeDir(); + auto bin = CF_EXPECT(GetBin(subcmd, android_host_out)); + auto bin_path = ConcatToString(android_host_out, "/bin/", bin); + CommandInvocationInfo result = {.command = subcmd, + .bin = bin, + .bin_path = bin_path, + .home = home, + .host_artifacts_path = android_host_out, + .uid = uid, + .args = cmd_args, + .envs = envs}; + result.envs["HOME"] = home; + extracted_info.invocation_info = result; + extracted_info.group = instance_group; + return extracted_info; +} + +Result CvdGenericCommandHandler::GetBin( + const std::string& subcmd) const { + CF_EXPECT(Contains(command_to_binary_map_, subcmd)); + const auto& bin_name = command_to_binary_map_.at(subcmd); + CF_EXPECT(bin_name != kBinGeneratedAtRuntime); + return bin_name; +} + +Result CvdGenericCommandHandler::GetBin( + const std::string& subcmd, const std::string& host_artifacts_path) const { + CF_EXPECT(Contains(command_to_binary_map_, subcmd)); + const auto& bin_name = command_to_binary_map_.at(subcmd); + if (bin_name != kBinGeneratedAtRuntime) { + return GetBin(subcmd); + } + std::string calculated_bin_name = + CF_EXPECT(host_tool_target_manager_.ExecBaseName( + {.artifacts_path = host_artifacts_path, .op = subcmd})); + return calculated_bin_name; +} + +std::unique_ptr NewCvdGenericCommandHandler( + InstanceLockFileManager& instance_lockfile_manager, + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter, + HostToolTargetManager& host_tool_target_manager) { + return std::unique_ptr(new CvdGenericCommandHandler( + instance_lockfile_manager, instance_manager, subprocess_waiter, + host_tool_target_manager)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/generic.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/generic.h new file mode 100644 index 0000000000..c8ea8e0b38 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/generic.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/instance_lock.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdGenericCommandHandler( + InstanceLockFileManager& instance_lockfile_manager, + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter, + HostToolTargetManager& host_tool_target_manager); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/handler_proxy.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/handler_proxy.cpp new file mode 100644 index 0000000000..7747ac6a76 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/handler_proxy.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/handler_proxy.h" + +#include +#include + +#include + +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/frontline_parser.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +class CvdServerHandlerProxy : public CvdServerHandler { + public: + CvdServerHandlerProxy(CommandSequenceExecutor& executor) + : executor_(executor) {} + + Result CanHandle(const RequestWithStdio& request) const override { + auto invocation = ParseInvocation(request.Message()); + return (invocation.command == "process"); + } + + // the input format is: + // cmd_args: cvd cmdline-parser + // selector_args: [command args to parse] + Result Handle(const RequestWithStdio& request) override { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CanHandle(request)); + + const auto& selector_opts = + request.Message().command_request().selector_opts(); + auto all_args = cvd_common::ConvertToArgs(selector_opts.args()); + CF_EXPECT_GE(all_args.size(), 1); + if (all_args.size() == 1) { + CF_EXPECT_EQ(all_args.front(), "cvd"); + all_args = cvd_common::Args{"cvd", "help"}; + } + + cvd_common::Envs envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + + auto subcmds = executor_.CmdList(); + auto selector_flag_collection = + CF_EXPECT(selector::SelectorFlags::New()).FlagsAsCollection(); + + FrontlineParser::ParserParam server_param{ + .server_supported_subcmds = subcmds, + .internal_cmds = std::vector{}, + .all_args = all_args, + .cvd_flags = std::move(selector_flag_collection)}; + auto frontline_parser = CF_EXPECT(FrontlineParser::Parse(server_param)); + CF_EXPECT(frontline_parser != nullptr); + + const auto prog_path = frontline_parser->ProgPath(); + const auto new_sub_cmd = frontline_parser->SubCmd(); + cvd_common::Args cmd_args{frontline_parser->SubCmdArgs()}; + cvd_common::Args selector_args{frontline_parser->CvdArgs()}; + + cvd_common::Args new_exec_args{prog_path}; + if (new_sub_cmd) { + new_exec_args.push_back(*new_sub_cmd); + } + new_exec_args.insert(new_exec_args.end(), cmd_args.begin(), cmd_args.end()); + + cvd::Request exec_request = MakeRequest( + {.cmd_args = new_exec_args, + .env = envs, + .selector_args = selector_args, + .working_dir = + request.Message().command_request().working_directory()}, + request.Message().command_request().wait_behavior()); + + RequestWithStdio forwarded_request( + request.Client(), std::move(exec_request), request.FileDescriptors(), + request.Credentials()); + interrupt_lock.unlock(); + SharedFD dev_null = SharedFD::Open("/dev/null", O_RDWR); + CF_EXPECT(dev_null->IsOpen(), "Failed to open /dev/null"); + + cvd::Response response; + auto invocation_args = + ParseInvocation(forwarded_request.Message()).arguments; + auto handler = CF_EXPECT(executor_.GetHandler(forwarded_request)); + if (CF_EXPECT(IsHelpSubcmd(invocation_args)) && + handler->ShouldInterceptHelp()) { + std::string output = + CF_EXPECT(handler->DetailedHelp(invocation_args)) + "\n"; + response = CF_EXPECT(WriteToFd(forwarded_request.Out(), output)); + } else { + response = CF_EXPECT(executor_.ExecuteOne(forwarded_request, dev_null)); + } + return response; + } + + Result Interrupt() override { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + CF_EXPECT(executor_.Interrupt()); + return {}; + } + + // not intended to be used by the user + cvd_common::Args CmdList() const override { return {}; } + + private: + std::mutex interruptible_; + bool interrupted_ = false; + CommandSequenceExecutor& executor_; +}; + +std::unique_ptr NewCvdServerHandlerProxy( + CommandSequenceExecutor& executor) { + return std::unique_ptr(new CvdServerHandlerProxy(executor)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/handler_proxy.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/handler_proxy.h new file mode 100644 index 0000000000..66e9f3a299 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/handler_proxy.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdServerHandlerProxy( + CommandSequenceExecutor& executor); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/help.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/help.cpp new file mode 100644 index 0000000000..dad5b32331 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/help.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/help.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/request_context.h" +#include "host/commands/cvd/server.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace { + +constexpr char kHelpIntroText[] = R"(Cuttlefish Virtual Device (CVD) CLI. + +usage: cvd + +Selector Options: + -group_name Specify the name of the instance group created + or selected. + -instance_name Selects the device of the given name to perform the + commands for. + -instance_name Takes the names of the devices to create within an + instance group. The 'names' is comma-separated. + +Driver Options: + -help Print this message + -verbosity= Adjust Cvd verbosity level. LEVEL is Android log + severity. (Required: cvd >= v1.3) + -acquire_file_lock If the flag is given, the cvd server attempts to + acquire the instance lock file lock. (default: true) + +Commands (cvd help for more information):)"; + +constexpr char kSummaryHelpText[] = + "Used to display help information for other commands"; + +constexpr char kDetailedHelpText[] = + R"(cvd help - used to display help text for cvd and its commands + +Example usage: + cvd help - displays summary help for available commands + + cvd help - displays more detailed help for the specific command +)"; + +} // namespace + +class CvdHelpHandler : public CvdServerHandler { + public: + CvdHelpHandler( + const std::vector>& request_handlers) + : request_handlers_(request_handlers) {} + + Result CanHandle(const RequestWithStdio& request) const override { + auto invocation = ParseInvocation(request.Message()); + return (invocation.command == "help"); + } + + Result Handle(const RequestWithStdio& request) override { + std::unique_lock interrupt_lock(interruptible_); + if (interrupted_) { + return CF_ERR("Interrupted"); + } + CF_EXPECT(CanHandle(request)); + + std::string output; + auto args = ParseInvocation(request.Message()).arguments; + if (args.empty()) { + output = CF_EXPECT(TopLevelHelp()); + } else { + output = CF_EXPECT(SubCommandHelp(args)); + } + auto response = CF_EXPECT(WriteToFd(request.Out(), output)); + interrupt_lock.unlock(); + return response; + } + + Result Interrupt() override { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + return {}; + } + + cvd_common::Args CmdList() const override { return {"help"}; } + + Result SummaryHelp() const override { return kSummaryHelpText; } + + bool ShouldInterceptHelp() const override { return true; } + + Result DetailedHelp(std::vector&) const override { + return kDetailedHelpText; + } + + private: + Result GetLookupRequest(const std::string& arg) { + cvd::Request lookup; + auto& lookup_cmd = *lookup.mutable_command_request(); + lookup_cmd.add_args("cvd"); + lookup_cmd.add_args(arg); + auto dev_null = SharedFD::Open("/dev/null", O_RDWR); + CF_EXPECT(dev_null->IsOpen(), dev_null->StrError()); + return RequestWithStdio(dev_null, lookup, {dev_null, dev_null, dev_null}, + {}); + } + + Result TopLevelHelp() { + std::stringstream help_message; + help_message << kHelpIntroText << std::endl; + for (const auto& handler : request_handlers_) { + std::string command_list = android::base::Join(handler->CmdList(), ", "); + // exclude commands without any command list values as not intended for + // use by users or sub-subcommands + if (!command_list.empty()) { + help_message << "\t" << command_list << " - "; + help_message << CF_EXPECT(handler->SummaryHelp()) << std::endl + << std::endl; + } + } + return help_message.str(); + } + + Result SubCommandHelp(std::vector& args) { + CF_EXPECT( + !args.empty(), + "Cannot process subcommand help without valid subcommand argument"); + auto lookup_request = CF_EXPECT(GetLookupRequest(args.front())); + auto handler = CF_EXPECT(RequestHandler(lookup_request, request_handlers_)); + + std::stringstream help_message; + help_message << CF_EXPECT(handler->DetailedHelp(args)) << std::endl; + return help_message.str(); + } + + std::mutex interruptible_; + bool interrupted_ = false; + const std::vector>& request_handlers_; +}; + +std::unique_ptr NewCvdHelpHandler( + const std::vector>& request_handlers) { + return std::unique_ptr( + new CvdHelpHandler(request_handlers)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/help.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/help.h new file mode 100644 index 0000000000..38085b3270 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/help.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 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 + +#include +#include + +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdHelpHandler( + const std::vector>& server_handlers); +} diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target.cpp new file mode 100644 index 0000000000..316544aff0 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/host_tool_target.h" + +#include + +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/server_command/flags_collector.h" + +namespace cuttlefish { +namespace { + +const std::map>& OpToBinsMap() { + static const auto& map = *new std::map>{ + {"stop", {"cvd_internal_stop", "stop_cvd"}}, + {"stop_cvd", {"cvd_internal_stop", "stop_cvd"}}, + {"start", {"cvd_internal_start", "launch_cvd"}}, + {"launch_cvd", {"cvd_internal_start", "launch_cvd"}}, + {"status", {"cvd_internal_status", "cvd_status"}}, + {"cvd_status", {"cvd_internal_status", "cvd_status"}}, + {"restart", {"restart_cvd"}}, + {"powerwash", {"powerwash_cvd"}}, + {"suspend", {"snapshot_util_cvd"}}, + {"resume", {"snapshot_util_cvd"}}, + {"snapshot_take", {"snapshot_util_cvd"}}, + }; + return map; +} + +} // namespace + +Result HostToolTarget::Create( + const std::string& artifacts_path) { + std::string bin_dir_path = ConcatToString(artifacts_path, "/bin"); + std::unordered_map op_to_impl_map; + for (const auto& [op, candidates] : OpToBinsMap()) { + for (const auto& bin_name : candidates) { + const auto bin_path = ConcatToString(bin_dir_path, "/", bin_name); + if (!FileExists(bin_path)) { + continue; + } + op_to_impl_map[op] = OperationImplementation{.bin_name_ = bin_name}; + break; + } + } + + for (auto& [op, op_impl] : op_to_impl_map) { + const std::string bin_path = + ConcatToString(artifacts_path, "/bin/", op_impl.bin_name_); + Command command(bin_path); + command.AddParameter("--helpxml"); + // b/276497044 + command.UnsetFromEnvironment(kAndroidHostOut); + command.AddEnvironmentVariable(kAndroidHostOut, artifacts_path); + command.UnsetFromEnvironment(kAndroidSoongHostOut); + command.AddEnvironmentVariable(kAndroidSoongHostOut, artifacts_path); + + std::string xml_str; + std::string err_out; + RunWithManagedStdio(std::move(command), nullptr, std::addressof(xml_str), + std::addressof(err_out)); + auto flags_opt = CollectFlagsFromHelpxml(xml_str); + if (!flags_opt) { + LOG(ERROR) << bin_path << " --helpxml failed."; + LOG(ERROR) << err_out; + continue; + } + auto flags = std::move(*flags_opt); + for (auto& flag : flags) { + op_impl.supported_flags_[flag->Name()] = std::move(flag); + } + } + + struct stat for_dir_time_stamp; + time_t dir_time_stamp = 0; + // we get dir time stamp, as the runtime libraries might be updated + if (::stat(bin_dir_path.data(), &for_dir_time_stamp) == 0) { + // if stat failed, use the smallest possible value, which is 0 + // in that way, the HostTool entry will be always updated on read request. + dir_time_stamp = for_dir_time_stamp.st_mtime; + } + return HostToolTarget(artifacts_path, dir_time_stamp, + std::move(op_to_impl_map)); +} + +HostToolTarget::HostToolTarget( + const std::string& artifacts_path, const time_t dir_time_stamp, + std::unordered_map&& op_to_impl_map) + : artifacts_path_(artifacts_path), + dir_time_stamp_(dir_time_stamp), + op_to_impl_map_(std::move(op_to_impl_map)) {} + +bool HostToolTarget::IsDirty() const { + std::string bin_path = ConcatToString(artifacts_path_, "/bin"); + if (!DirectoryExists(bin_path)) { + return true; + } + struct stat for_dir_time_stamp; + if (::stat(bin_path.data(), &for_dir_time_stamp) != 0) { + return true; + } + return dir_time_stamp_ != for_dir_time_stamp.st_mtime; +} + +Result HostToolTarget::GetFlagInfo( + const FlagInfoRequest& request) const { + CF_EXPECT(Contains(op_to_impl_map_, request.operation_), + "Operation \"" << request.operation_ << "\" is not supported."); + auto& supported_flags = + op_to_impl_map_.at(request.operation_).supported_flags_; + CF_EXPECT(Contains(supported_flags, request.flag_name_)); + const auto& flag_uniq_ptr = supported_flags.at(request.flag_name_); + FlagInfo copied(*flag_uniq_ptr); + return copied; +} + +Result HostToolTarget::GetBinName( + const std::string& operation) const { + CF_EXPECT(Contains(op_to_impl_map_, operation), + "Operation \"" << operation << "\" is not supported by " + << "the host tool target object at " + << artifacts_path_); + return op_to_impl_map_.at(operation).bin_name_; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target.h new file mode 100644 index 0000000000..3642304419 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/server_command/flags_collector.h" + +namespace cuttlefish { + +class HostToolTarget { + public: + struct FlagInfoRequest { + std::string operation_; + std::string flag_name_; + }; + // artifacts_path: ANDROID_HOST_OUT, or so + static Result Create(const std::string& artifacts_path); + + bool IsDirty() const; + Result GetFlagInfo(const FlagInfoRequest& request) const; + bool HasField(const FlagInfoRequest& request) const { + return GetFlagInfo(request).ok(); + } + Result GetBinName(const std::string& operation) const; + + private: + using SupportedFlagMap = std::unordered_map; + struct OperationImplementation { + std::string bin_name_; + SupportedFlagMap supported_flags_; + }; + HostToolTarget(const std::string& artifacts_path, const time_t dir_time_stamp, + std::unordered_map&& + op_to_impl_map); + + // time stamp on creation + const std::string artifacts_path_; + const time_t dir_time_stamp_; + // map from "start", "stop", etc, to "cvd_internal_start", "stop_cvd", etc + // with the supported flags if those implementation offers --helpxml. + std::unordered_map op_to_impl_map_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target_manager.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target_manager.cpp new file mode 100644 index 0000000000..b8ceb6b66e --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target_manager.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/host_tool_target_manager.h" + +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/files.h" +#include "host/commands/cvd/common_utils.h" + +namespace cuttlefish { + +class HostToolTargetManagerImpl : public HostToolTargetManager { + public: + HostToolTargetManagerImpl() = default; + + Result ReadFlag(const HostToolFlagRequestForm& request) override; + Result ExecBaseName( + const HostToolExecNameRequestForm& request) override; + + private: + Result EnsureExistence(const std::string& artifacts_path); + Result UpdateOutdated(const std::string& artifacts_path); + + using HostToolTargetMap = std::unordered_map; + + // map from artifact dir to host tool target information object + HostToolTargetMap host_target_table_; + // predefined mapping from an operation to potential executable binary names + // e.g. "start" -> {"cvd_internal_start", "launch_cvd"} + std::mutex table_mutex_; +}; + +// use this only after acquiring the table_mutex_ +Result HostToolTargetManagerImpl::EnsureExistence( + const std::string& artifacts_path) { + if (!Contains(host_target_table_, artifacts_path)) { + HostToolTarget new_host_tool_target = + CF_EXPECT(HostToolTarget::Create(artifacts_path)); + host_target_table_.emplace(artifacts_path, std::move(new_host_tool_target)); + } + return {}; +} + +Result HostToolTargetManagerImpl::UpdateOutdated( + const std::string& artifacts_path) { + CF_EXPECT(Contains(host_target_table_, artifacts_path)); + auto& host_target = host_target_table_.at(artifacts_path); + if (!host_target.IsDirty()) { + return {}; + } + LOG(INFO) << artifacts_path << " is new, so updating HostToolTarget"; + host_target_table_.erase(artifacts_path); + HostToolTarget new_host_tool_target = + CF_EXPECT(HostToolTarget::Create(artifacts_path)); + host_target_table_.emplace(artifacts_path, std::move(new_host_tool_target)); + return {}; +} + +Result HostToolTargetManagerImpl::ReadFlag( + const HostToolFlagRequestForm& request) { + std::lock_guard lock(table_mutex_); + CF_EXPECT( + EnsureExistence(request.artifacts_path), + "Could not create HostToolTarget object for " << request.artifacts_path); + CF_EXPECT(UpdateOutdated(request.artifacts_path)); + auto& host_target = host_target_table_.at(request.artifacts_path); + auto flag_info = + CF_EXPECT(host_target.GetFlagInfo(HostToolTarget::FlagInfoRequest{ + .operation_ = request.op, + .flag_name_ = request.flag_name, + })); + return flag_info; +} + +Result HostToolTargetManagerImpl::ExecBaseName( + const HostToolExecNameRequestForm& request) { + std::lock_guard lock(table_mutex_); + CF_EXPECT( + EnsureExistence(request.artifacts_path), + "Could not create HostToolTarget object for " << request.artifacts_path); + auto& host_target = host_target_table_.at(request.artifacts_path); + auto base_name = CF_EXPECT(host_target.GetBinName(request.op)); + return base_name; +} + +std::unique_ptr NewHostToolTargetManager() { + return std::unique_ptr( + new HostToolTargetManagerImpl()); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target_manager.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target_manager.h new file mode 100644 index 0000000000..851c6ebda6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target_manager.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 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 + +#include +#include +#include +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/server_command/flags_collector.h" +#include "host/commands/cvd/server_command/host_tool_target.h" + +namespace cuttlefish { + +struct HostToolFlagRequestForm { + std::string artifacts_path; + // operations like stop, start, status, etc + std::string op; + std::string flag_name; +}; + +struct HostToolExecNameRequestForm { + std::string artifacts_path; + // operations like stop, start, status, etc + std::string op; +}; + +class HostToolTargetManager { + public: + virtual ~HostToolTargetManager() = default; + virtual Result ReadFlag(const HostToolFlagRequestForm& request) = 0; + virtual Result ExecBaseName( + const HostToolExecNameRequestForm& request) = 0; +}; + +std::unique_ptr NewHostToolTargetManager(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/lint.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/lint.cpp new file mode 100644 index 0000000000..716f0caf30 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/lint.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/lint.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/parser/load_configs_parser.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +class LintCommandHandler : public CvdServerHandler { + public: + LintCommandHandler() {} + + Result CanHandle(const RequestWithStdio& request) const { + auto invocation = ParseInvocation(request.Message()); + return invocation.command == kLintSubCmd; + } + + Result Handle(const RequestWithStdio& request) override { + CF_EXPECT(CanHandle(request)); + + auto args = ParseInvocation(request.Message()).arguments; + auto working_directory = + request.Message().command_request().working_directory(); + const auto config_path = CF_EXPECT(ValidateConfig(args, working_directory)); + + std::stringstream message_stream; + message_stream << "Lint of flags and config \"" << config_path + << "\" succeeded\n"; + const auto message = message_stream.str(); + CF_EXPECT_EQ(WriteAll(request.Out(), message), message.size(), + "Error writing message"); + cvd::Response response; + response.mutable_command_response(); + response.mutable_status()->set_code(cvd::Status::OK); + return response; + } + + Result Interrupt() override { return CF_ERR("Can't interrupt"); } + + cvd_common::Args CmdList() const override { return {kLintSubCmd}; } + + private: + Result ValidateConfig(std::vector& args, + const std::string& working_directory) { + const LoadFlags flags = CF_EXPECT(GetFlags(args, working_directory)); + CF_EXPECT(GetCvdFlags(flags)); + return flags.config_path; + } + + static constexpr char kLintSubCmd[] = "lint"; +}; + +std::unique_ptr NewLintCommand() { + return std::unique_ptr(new LintCommandHandler()); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/lint.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/lint.h new file mode 100644 index 0000000000..a860e49374 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/lint.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewLintCommand(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/load_configs.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/load_configs.cpp new file mode 100644 index 0000000000..9ad004187d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/load_configs.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2022 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. + */ +#include "host/commands/cvd/server_command/load_configs.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/parser/load_configs_parser.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace { + +constexpr char kSummaryHelpText[] = + R"(Loads the given JSON configuration file and launches devices based on the options provided)"; + +constexpr char kDetailedHelpText[] = R"( +Warning: This command is deprecated, use cvd start --config_file instead. + +Usage: +cvd load [--override=:] + +Reads the fields in the JSON configuration file and translates them to corresponding start command and flags. + +Optionally fetches remote artifacts prior to launching the cuttlefish environment. + +The --override flag can be used to give new values for properties in the config file without needing to edit the file directly. Convenient for one-off invocations. +)"; + +} // namespace + +class LoadConfigsCommand : public CvdServerHandler { + public: + LoadConfigsCommand(CommandSequenceExecutor& executor) : executor_(executor) {} + ~LoadConfigsCommand() = default; + + Result CanHandle(const RequestWithStdio& request) const override { + auto invocation = ParseInvocation(request.Message()); + return invocation.command == kLoadSubCmd; + } + + Result Handle(const RequestWithStdio& request) override { + std::unique_lock interrupt_lock(interrupt_mutex_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CF_EXPECT(CanHandle(request))); + + auto commands = CF_EXPECT(CreateCommandSequence(request)); + interrupt_lock.unlock(); + CF_EXPECT(executor_.Execute(commands, request.Err())); + + cvd::Response response; + response.mutable_command_response(); + return response; + } + + Result Interrupt() override { + std::scoped_lock interrupt_lock(interrupt_mutex_); + interrupted_ = true; + CF_EXPECT(executor_.Interrupt()); + return {}; + } + + cvd_common::Args CmdList() const override { return {kLoadSubCmd}; } + + Result SummaryHelp() const override { return kSummaryHelpText; } + + bool ShouldInterceptHelp() const override { return true; } + + Result DetailedHelp(std::vector&) const override { + return kDetailedHelpText; + } + + Result> CreateCommandSequence( + const RequestWithStdio& request) { + auto args = ParseInvocation(request.Message()).arguments; + auto working_directory = + request.Message().command_request().working_directory(); + const LoadFlags flags = CF_EXPECT(GetFlags(args, working_directory)); + auto cvd_flags = CF_EXPECT(GetCvdFlags(flags)); + + std::vector req_protos; + const auto& client_env = request.Message().command_request().env(); + + if (!cvd_flags.fetch_cvd_flags.empty()) { + auto& fetch_cmd = *req_protos.emplace_back().mutable_command_request(); + *fetch_cmd.mutable_env() = client_env; + fetch_cmd.add_args("cvd"); + fetch_cmd.add_args("fetch"); + for (const auto& flag : cvd_flags.fetch_cvd_flags) { + fetch_cmd.add_args(flag); + } + } + + auto& mkdir_cmd = *req_protos.emplace_back().mutable_command_request(); + *mkdir_cmd.mutable_env() = client_env; + mkdir_cmd.add_args("cvd"); + mkdir_cmd.add_args("mkdir"); + mkdir_cmd.add_args("-p"); + mkdir_cmd.add_args(cvd_flags.load_directories.launch_home_directory); + + auto& launch_cmd = *req_protos.emplace_back().mutable_command_request(); + launch_cmd.set_working_directory( + cvd_flags.load_directories.host_package_directory); + *launch_cmd.mutable_env() = client_env; + (*launch_cmd.mutable_env())["HOME"] = + cvd_flags.load_directories.launch_home_directory; + (*launch_cmd.mutable_env())[kAndroidHostOut] = + cvd_flags.load_directories.host_package_directory; + (*launch_cmd.mutable_env())[kAndroidSoongHostOut] = + cvd_flags.load_directories.host_package_directory; + if (Contains(*launch_cmd.mutable_env(), kAndroidProductOut)) { + (*launch_cmd.mutable_env()).erase(kAndroidProductOut); + } + + /* cvd load will always create instances in daemon mode (to be independent + of terminal) and will enable reporting automatically (to run automatically + without question during launch) + */ + launch_cmd.add_args("cvd"); + launch_cmd.add_args("start"); + launch_cmd.add_args("--daemon"); + + for (const auto& parsed_flag : cvd_flags.launch_cvd_flags) { + launch_cmd.add_args(parsed_flag); + } + // Add system flag for multi-build scenario + launch_cmd.add_args(cvd_flags.load_directories.system_image_directory_flag); + + auto selector_opts = launch_cmd.mutable_selector_opts(); + + for (const auto& flag : cvd_flags.selector_flags) { + selector_opts->add_args(flag); + } + + /*Verbose is disabled by default*/ + auto dev_null = SharedFD::Open("/dev/null", O_RDWR); + CF_EXPECT(dev_null->IsOpen(), dev_null->StrError()); + std::vector fds = {dev_null, dev_null, dev_null}; + std::vector ret; + + for (auto& request_proto : req_protos) { + ret.emplace_back(RequestWithStdio(request.Client(), request_proto, fds, + request.Credentials())); + } + + return ret; + } + + private: + static constexpr char kLoadSubCmd[] = "load"; + + CommandSequenceExecutor& executor_; + + std::mutex interrupt_mutex_; + bool interrupted_ = false; +}; + +std::unique_ptr NewLoadConfigsCommand( + CommandSequenceExecutor& executor) { + return std::unique_ptr(new LoadConfigsCommand(executor)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/load_configs.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/load_configs.h new file mode 100644 index 0000000000..49ada915f1 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/load_configs.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 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 + +#include + +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +/* +cvd load component is responsible of loading, translation and creation of +cuttlefish instances based on input json configuration file +*/ +std::unique_ptr NewLoadConfigsCommand( + CommandSequenceExecutor& executor); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/power.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/power.cpp new file mode 100644 index 0000000000..bb17ca5010 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/power.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/power.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/flag.h" +#include "host/commands/cvd/selector/instance_database_types.h" +#include "host/commands/cvd/selector/instance_group_record.h" +#include "host/commands/cvd/selector/instance_record.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +class CvdDevicePowerCommandHandler : public CvdServerHandler { + public: + CvdDevicePowerCommandHandler(HostToolTargetManager& host_tool_target_manager, + InstanceManager& instance_manager, + SubprocessWaiter& subprocess_waiter) + : host_tool_target_manager_(host_tool_target_manager), + instance_manager_{instance_manager}, + subprocess_waiter_(subprocess_waiter) { + cvd_power_operations_["restart"] = + [this](const std::string& android_host_out) -> Result { + return CF_EXPECT(RestartDeviceBin(android_host_out)); + }; + cvd_power_operations_["powerwash"] = + [this](const std::string& android_host_out) -> Result { + return CF_EXPECT(PowerwashBin(android_host_out)); + }; + } + + Result CanHandle(const RequestWithStdio& request) const { + auto invocation = ParseInvocation(request.Message()); + return Contains(cvd_power_operations_, invocation.command); + } + + Result Handle(const RequestWithStdio& request) override { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CanHandle(request)); + CF_EXPECT(VerifyPrecondition(request)); + cvd_common::Envs envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + + auto [op, subcmd_args] = ParseInvocation(request.Message()); + bool is_help = CF_EXPECT(IsHelp(subcmd_args)); + + // may modify subcmd_args by consuming in parsing + Command command = + is_help + ? CF_EXPECT(HelpCommand(request, op, subcmd_args, envs)) + : CF_EXPECT(NonHelpCommand(request, op, subcmd_args, envs)); + CF_EXPECT(subprocess_waiter_.Setup(command.Start())); + interrupt_lock.unlock(); + + auto infop = CF_EXPECT(subprocess_waiter_.Wait()); + return ResponseFromSiginfo(infop); + } + + Result Interrupt() override { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + CF_EXPECT(subprocess_waiter_.Interrupt()); + return {}; + } + + cvd_common::Args CmdList() const override { + cvd_common::Args valid_ops; + valid_ops.reserve(cvd_power_operations_.size()); + for (const auto& [op, _] : cvd_power_operations_) { + valid_ops.push_back(op); + } + return valid_ops; + } + + private: + Result RestartDeviceBin( + const std::string& android_host_out) const { + auto restart_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({ + .artifacts_path = android_host_out, + .op = "restart", + })); + return restart_bin; + } + + Result PowerwashBin(const std::string& android_host_out) const { + auto powerwash_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({ + .artifacts_path = android_host_out, + .op = "powerwash", + })); + return powerwash_bin; + } + + Result HelpCommand(const RequestWithStdio& request, + const std::string& op, + const cvd_common::Args& subcmd_args, + cvd_common::Envs envs) { + CF_EXPECT(Contains(envs, kAndroidHostOut)); + const auto bin_base = CF_EXPECT(GetBin(op, envs.at(kAndroidHostOut))); + auto cvd_power_bin_path = + ConcatToString(envs.at(kAndroidHostOut), "/bin/", bin_base); + std::string home = Contains(envs, "HOME") + ? envs.at("HOME") + : CF_EXPECT(SystemWideUserHome()); + envs["HOME"] = home; + envs[kAndroidSoongHostOut] = envs.at(kAndroidHostOut); + ConstructCommandParam construct_cmd_param{ + .bin_path = cvd_power_bin_path, + .home = home, + .args = subcmd_args, + .envs = envs, + .working_dir = request.Message().command_request().working_directory(), + .command_name = bin_base, + .in = request.In(), + .out = request.Out(), + .err = request.Err()}; + Command command = CF_EXPECT(ConstructCommand(construct_cmd_param)); + return command; + } + + Result NonHelpCommand(const RequestWithStdio& request, + const std::string& op, + cvd_common::Args& subcmd_args, + cvd_common::Envs envs) { + // test if there is --instance_num flag + CvdFlag instance_num_flag("instance_num"); + auto instance_num_opt = + CF_EXPECT(instance_num_flag.FilterFlag(subcmd_args)); + selector::Queries extra_queries; + if (instance_num_opt) { + extra_queries.emplace_back(selector::kInstanceIdField, *instance_num_opt); + } + const auto& selector_opts = + request.Message().command_request().selector_opts(); + const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args()); + + auto instance = CF_EXPECT(instance_manager_.SelectInstance( + selector_args, extra_queries, envs)); + const auto& instance_group = instance.ParentGroup(); + const auto& home = instance_group.HomeDir(); + + const auto& android_host_out = instance_group.HostArtifactsPath(); + const auto bin_base = CF_EXPECT(GetBin(op, android_host_out)); + auto cvd_power_bin_path = + ConcatToString(android_host_out, "/bin/", bin_base); + + cvd_common::Args cvd_env_args{subcmd_args}; + cvd_env_args.push_back( + ConcatToString("--instance_num=", instance.InstanceId())); + envs["HOME"] = home; + envs[kAndroidHostOut] = android_host_out; + envs[kAndroidSoongHostOut] = android_host_out; + + std::stringstream command_to_issue; + command_to_issue << "HOME=" << home << " " << kAndroidHostOut << "=" + << android_host_out << " " << kAndroidSoongHostOut << "=" + << android_host_out << " " << cvd_power_bin_path << " "; + for (const auto& arg : cvd_env_args) { + command_to_issue << arg << " "; + } + WriteAll(request.Err(), command_to_issue.str()); + + ConstructCommandParam construct_cmd_param{ + .bin_path = cvd_power_bin_path, + .home = home, + .args = cvd_env_args, + .envs = envs, + .working_dir = request.Message().command_request().working_directory(), + .command_name = bin_base, + .in = request.In(), + .out = request.Out(), + .err = request.Err()}; + Command command = CF_EXPECT(ConstructCommand(construct_cmd_param)); + return command; + } + + Result IsHelp(const cvd_common::Args& cmd_args) const { + if (cmd_args.empty()) { + return false; + } + // cvd restart/powerwash --help, --helpxml, etc or simply cvd restart + if (CF_EXPECT(IsHelpSubcmd(cmd_args))) { + return true; + } + // cvd restart/powerwash help format + return (cmd_args.front() == "help"); + } + + Result GetBin(const std::string& subcmd, + const std::string& android_host_out) const { + CF_EXPECT(Contains(cvd_power_operations_, subcmd), + subcmd << " is not supported."); + return CF_EXPECT((cvd_power_operations_.at(subcmd))(android_host_out)); + } + + HostToolTargetManager& host_tool_target_manager_; + InstanceManager& instance_manager_; + SubprocessWaiter& subprocess_waiter_; + std::mutex interruptible_; + bool interrupted_ = false; + using BinGetter = std::function(const std::string&)>; + std::unordered_map cvd_power_operations_; +}; + +std::unique_ptr NewCvdDevicePowerCommandHandler( + HostToolTargetManager& host_tool_target_manager, + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter) { + return std::unique_ptr(new CvdDevicePowerCommandHandler( + host_tool_target_manager, instance_manager, subprocess_waiter)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/power.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/power.h new file mode 100644 index 0000000000..03e90586f2 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/power.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" + +namespace cuttlefish { + +// restart, powerwash +std::unique_ptr NewCvdDevicePowerCommandHandler( + HostToolTargetManager& host_tool_target_manager, + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/reset.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/reset.cpp new file mode 100644 index 0000000000..df36356b5d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/reset.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/reset.h" + +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" + +namespace cuttlefish { + +/* + * Prints out help message for cvd reset + * + * cvd reset is a feature implemented by the client. However, the user may run + * cvd help reset. The cvd help parsing will be done on the server side, and + * forwarded to the cvd help handler. The cvd help handler again will forward + * it to supposedly cvd reset handler. The cvd reset handler will only receive + * "cvd reset --help." + * + * For, say, "cvd reset" or even "cvd reset --help"," the parsing will be done + * on the client side, and handled by the client. + * + */ +class CvdResetCommandHandler : public CvdServerHandler { + public: + CvdResetCommandHandler() {} + + Result CanHandle(const RequestWithStdio& request) const { + auto invocation = ParseInvocation(request.Message()); + return invocation.command == kResetSubcmd; + } + Result Handle(const RequestWithStdio& request) override { + CF_EXPECT(CanHandle(request)); + cvd::Response response; + response.mutable_command_response(); + response.mutable_status()->set_code(cvd::Status::OK); + std::stringstream guide_message; + guide_message << "\"cvd reset\" is implemented on the client side." + << " Try:" << std::endl; + guide_message << " cvd reset --help" << std::endl; + const auto guide_message_str = guide_message.str(); + CF_EXPECT_EQ(WriteAll(request.Err(), guide_message_str), + guide_message_str.size()); + return response; + } + Result Interrupt() override { return CF_ERR("Can't interrupt"); } + cvd_common::Args CmdList() const override { return {kResetSubcmd}; } + + private: + static constexpr char kResetSubcmd[] = "reset"; +}; + +std::unique_ptr NewCvdResetCommandHandler() { + return std::unique_ptr(new CvdResetCommandHandler()); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/reset.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/reset.h new file mode 100644 index 0000000000..40fdfc0bcd --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/reset.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdResetCommandHandler(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/restart.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/restart.cpp new file mode 100644 index 0000000000..e1028c9ad4 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/restart.cpp @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/restart.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/epoll_loop.h" +#include "host/commands/cvd/frontline_parser.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" +#include "host/libs/web/android_build_api.h" +#include "host/libs/web/android_build_string.h" + +namespace cuttlefish { +namespace { + +constexpr char kRestartServerHelpMessage[] = + R"(Cuttlefish Virtual Device (CVD) CLI. + +usage: cvd restart-server + +Common Args: + --help Print out this message + --verbose Control verbose mode + +Modes: + match-client Use the client executable. + latest Download the latest executable + reuse-server Use the server executable. +)"; + +Result LatestCvdAsFd(BuildApi& build_api) { + static constexpr char kTarget[] = + "aosp_cf_x86_64_phone-trunk_staging-userdebug"; + const auto build_string = + DeviceBuildString{.branch_or_id = "aosp-main", .target = kTarget}; + Build build = CF_EXPECT(build_api.GetBuild(build_string, kTarget)); + CF_EXPECT(std::holds_alternative(build), + "Unable to process non-DeviceBuild. Something has gone wrong."); + DeviceBuild device_build = *std::get_if(&build); + + auto fd = SharedFD::MemfdCreate("cvd"); + CF_EXPECT(fd->IsOpen(), "MemfdCreate failed: " << fd->StrError()); + + auto write = [fd](char* data, size_t size) -> bool { + if (size == 0) { + return true; + } + auto written = WriteAll(fd, data, size); + if (written != size) { + LOG(ERROR) << "Failed to persist data: " << fd->StrError(); + return false; + } + return true; + }; + CF_EXPECT(build_api.ArtifactToCallback(device_build, "cvd", write)); + + return fd; +} + +class CvdRestartHandler : public CvdServerHandler { + public: + CvdRestartHandler(BuildApi& build_api, CvdServer& server, + InstanceManager& instance_manager) + : build_api_(build_api), + supported_modes_({"match-client", "latest", "reuse-server"}), + server_(server), + instance_manager_(instance_manager) { + flags_.EnrollFlag(CvdFlag("help", false)); + flags_.EnrollFlag(CvdFlag("verbose", false)); + // If the fla is false, the request will fail if there's on-going requests + // If true, calls Stop() + flags_.EnrollFlag(CvdFlag("force", true)); + } + + Result CanHandle(const RequestWithStdio& request) const override { + auto invocation = ParseInvocation(request.Message()); + return android::base::Basename(invocation.command) == kRestartServer; + } + + Result Handle(const RequestWithStdio& request) override { + /* + * TODO(weihsu@): change the code accordingly per verbosity level control. + * + * Now, the server can start with a verbosity level. Change the code + * accordingly. + */ + CF_EXPECT(CanHandle(request)); + cvd::Response response; + if (request.Message().has_shutdown_request()) { + response.mutable_shutdown_response(); + } else { + CF_EXPECT( + request.Message().has_command_request(), + "cvd restart request must be either command or shutdown request."); + response.mutable_command_response(); + } + // all_args[0] = "cvd", all_args[1] = "restart_server" + cvd_common::Args all_args = + cvd_common::ConvertToArgs(request.Message().command_request().args()); + CF_EXPECT_GE(all_args.size(), 2); + CF_EXPECT_EQ(all_args.at(0), "cvd"); + CF_EXPECT_EQ(all_args.at(1), kRestartServer); + // erase the first item, "cvd" + all_args.erase(all_args.begin()); + + auto parsed = CF_EXPECT(Parse(all_args)); + if (parsed.help) { + const std::string help_message(kRestartServerHelpMessage); + WriteAll(request.Out(), help_message); + return response; + } + + // On CF_ERR, the locks will be released automatically + WriteAll(request.Out(), "Stopping the cvd_server.\n"); + server_.Stop(); + + CF_EXPECT(request.Credentials() != std::nullopt); + auto json_string = CF_EXPECT(SerializedInstanceDatabaseToString()); + std::optional mem_fd; + if (instance_manager_.HasInstanceGroups()) { + mem_fd = CF_EXPECT(CreateMemFileWithSerializedDb(json_string)); + CF_EXPECT(mem_fd != std::nullopt && (*mem_fd)->IsOpen(), + "mem file not open?"); + } + + if (parsed.verbose && mem_fd) { + PrintFileLink(request.Err(), *mem_fd); + } + + const std::string subcmd = parsed.subcmd.value_or("reuse-server"); + SharedFD new_exe; + CF_EXPECT(Contains(supported_modes_, subcmd), + "unsupported subcommand :" << subcmd); + if (subcmd == "match-client") { + CF_EXPECT(request.Extra(), "match-client requires the file descriptor."); + new_exe = *request.Extra(); + } else if (subcmd == "latest") { + new_exe = CF_EXPECT(LatestCvdAsFd(build_api_)); + } else if (subcmd == "reuse-server") { + new_exe = CF_EXPECT(NewExecFromPath(request, kServerExecPath)); + } else { + return CF_ERR("unsupported subcommand"); + } + + CF_EXPECT(server_.Exec({.new_exe = std::move(new_exe), + .carryover_client_fd = request.Client(), + .in_memory_data_fd = mem_fd, + .verbose = parsed.verbose})); + return CF_ERR("Should be unreachable"); + } + + Result Interrupt() override { return CF_ERR("Can't interrupt"); } + cvd_common::Args CmdList() const override { return {kRestartServer}; } + constexpr static char kRestartServer[] = "restart-server"; + + private: + struct Parsed { + bool help; + bool verbose; + std::optional subcmd; + std::optional exec_path; + }; + Result Parse(const cvd_common::Args& args) { + // it's ugly but let's reuse the frontline parser + auto parser_with_result = + CF_EXPECT(FrontlineParser::Parse({.internal_cmds = supported_modes_, + .all_args = args, + .cvd_flags = flags_})); + CF_EXPECT(parser_with_result != nullptr, + "FrontlineParser::Parse() returned nullptr"); + // If there was a subcmd, the flags for the subcmd is in SubCmdArgs(). + // If not, the flags for restart-server would be in CvdArgs() + std::optional subcmd_opt = parser_with_result->SubCmd(); + cvd_common::Args subcmd_args = + (subcmd_opt ? parser_with_result->SubCmdArgs() + : parser_with_result->CvdArgs()); + auto name_flag_map = CF_EXPECT(flags_.CalculateFlags(subcmd_args)); + CF_EXPECT(Contains(name_flag_map, "help")); + CF_EXPECT(Contains(name_flag_map, "verbose")); + + bool help = + CF_EXPECT(FlagCollection::GetValue(name_flag_map.at("help"))); + bool verbose = + CF_EXPECT(FlagCollection::GetValue(name_flag_map.at("verbose"))); + std::optional exec_path; + if (Contains(name_flag_map, "exec-path")) { + exec_path = CF_EXPECT( + FlagCollection::GetValue(name_flag_map.at("exec-path"))); + } + return Parsed{.help = help, + .verbose = verbose, + .subcmd = subcmd_opt, + .exec_path = exec_path}; + } + + Result NewExecFromPath(const RequestWithStdio& request, + const std::string& exec_path) { + std::string emulated_absolute_path; + const std::string client_pwd = + request.Message().command_request().working_directory(); + // ~ that means $HOME is not supported + CF_EXPECT(!android::base::StartsWith(exec_path, "~/"), + "Path starting with ~/ is not supported."); + CF_EXPECT_NE( + exec_path, "~", + "~ is not supported as a executable path, and likely is not a file."); + emulated_absolute_path = + CF_EXPECT(EmulateAbsolutePath({.current_working_dir = client_pwd, + .path_to_convert = exec_path, + .follow_symlink = false}), + "Failed to change exec_path to an absolute path."); + auto new_exe = SharedFD::Open(emulated_absolute_path, O_RDONLY); + CF_EXPECT(new_exe->IsOpen(), "Failed to open \"" + << exec_path << " that is " + << emulated_absolute_path + << "\": " << new_exe->StrError()); + return new_exe; + } + + Result SerializedInstanceDatabaseToString() { + auto db_json = CF_EXPECT(instance_manager_.Serialize(), + "Failed to serialized instance database"); + return db_json.toStyledString(); + } + + Result CreateMemFileWithSerializedDb( + const std::string& json_string) { + const std::string mem_file_name = "cvd_server_" + std::to_string(getpid()); + auto mem_fd = SharedFD::MemfdCreateWithData(mem_file_name, json_string); + CF_EXPECT(mem_fd->IsOpen(), + "MemfdCreateWithData failed: " << mem_fd->StrError()); + return mem_fd; + } + + void PrintFileLink(const SharedFD& fd_stream, const SharedFD& mem_fd) const { + auto link_target_result = mem_fd->ProcFdLinkTarget(); + if (!link_target_result.ok()) { + WriteAll(fd_stream, + "Failed to resolve the link target for the memory file.\n"); + return; + } + std::string message("The link target for the memory file is "); + message.append(*link_target_result).append("\n"); + WriteAll(fd_stream, message); + return; + } + + BuildApi& build_api_; + std::vector supported_modes_; + FlagCollection flags_; + CvdServer& server_; + InstanceManager& instance_manager_; +}; + +} // namespace + +std::unique_ptr NewCvdRestartHandler( + BuildApi& build_api, CvdServer& server, InstanceManager& instance_manager) { + return std::unique_ptr( + new CvdRestartHandler(build_api, server, instance_manager)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/restart.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/restart.h new file mode 100644 index 0000000000..761b6aa37b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/restart.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/libs/web/android_build_api.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdRestartHandler( + BuildApi& build_api, CvdServer& server, InstanceManager& instance_manager); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_launch.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_launch.cpp new file mode 100644 index 0000000000..b5b64430a5 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_launch.cpp @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/serial_launch.h" + +#include + +#include +#include +#include +#include +#include + +#include "android-base/parseint.h" +#include "android-base/strings.h" + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/instance_lock.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +// copied from "../demo_multi_vd.cpp" +namespace cuttlefish { +namespace { + +template +cvd::Request CreateCommandRequest( + const google::protobuf::Map& envs, + Args&&... args) { + cvd::Request request; + auto& cmd_request = *request.mutable_command_request(); + (cmd_request.add_args(std::forward(args)), ...); + *cmd_request.mutable_env() = envs; + return request; +} + +std::vector AppendRequestVectors( + std::vector&& dest, std::vector&& src) { + auto merged = std::move(dest); + for (auto& request : src) { + merged.emplace_back(std::move(request)); + } + return merged; +} + +struct DemoCommandSequence { + std::vector instance_locks; + std::vector requests; +}; + +/** Returns a `Flag` object that accepts comma-separated unsigned integers. */ +template +Flag DeviceSpecificUintFlag(const std::string& name, std::vector& values) { + return GflagsCompatFlag(name).Setter( + [&values](const FlagMatch& match) -> Result { + auto parsed_values = android::base::Tokenize(match.value, ", "); + for (auto& parsed_value : parsed_values) { + std::uint32_t num = 0; + CF_EXPECTF(android::base::ParseUint(parsed_value, &num), + "Failed to parse {} as an integer", parsed_value); + values.push_back(num); + } + return {}; + }); +} + +/** Returns a `Flag` object that accepts comma-separated strings. */ +Flag DeviceSpecificStringFlag(const std::string& name, + std::vector& values) { + return GflagsCompatFlag(name).Setter( + [&values](const FlagMatch& match) -> Result { + auto parsed_values = android::base::Tokenize(match.value, ", "); + for (auto& parsed_value : parsed_values) { + values.push_back(parsed_value); + } + return {}; + }); +} + +std::string ParentDir(const uid_t uid) { + constexpr char kParentDirPrefix[] = "/tmp/cvd/"; + std::stringstream ss; + ss << kParentDirPrefix << uid << "/"; + return ss.str(); +} + +} // namespace + +class SerialLaunchCommand : public CvdServerHandler { + public: + SerialLaunchCommand(CommandSequenceExecutor& executor, + InstanceLockFileManager& lock_file_manager) + : executor_(executor), lock_file_manager_(lock_file_manager) {} + ~SerialLaunchCommand() = default; + + Result CanHandle(const RequestWithStdio& request) const override { + auto invocation = ParseInvocation(request.Message()); + return invocation.command == "experimental" && + invocation.arguments.size() >= 1 && + invocation.arguments[0] == "serial_launch"; + } + Result Handle(const RequestWithStdio& request) override { + std::unique_lock interrupt_lock(interrupt_mutex_); + if (interrupted_) { + return CF_ERR("Interrupted"); + } + CF_EXPECT(CF_EXPECT(CanHandle(request))); + + auto commands = CF_EXPECT(CreateCommandSequence(request)); + interrupt_lock.unlock(); + CF_EXPECT(executor_.Execute(commands.requests, request.Err())); + + for (auto& lock : commands.instance_locks) { + CF_EXPECT(lock.Status(InUseState::kInUse)); + } + + cvd::Response response; + response.mutable_command_response(); + return response; + } + + Result Interrupt() override { + std::scoped_lock interrupt_lock(interrupt_mutex_); + interrupted_ = true; + CF_EXPECT(executor_.Interrupt()); + return {}; + } + + cvd_common::Args CmdList() const override { return {"experimental"}; } + + Result CreateCommandSequence( + const RequestWithStdio& request) { + const auto& client_env = request.Message().command_request().env(); + const auto client_uid = CF_EXPECT(request.Credentials()).uid; + + std::vector flags; + + bool help = false; + flags.emplace_back(GflagsCompatFlag("help", help)); + + std::string credentials; + flags.emplace_back(GflagsCompatFlag("credentials", credentials)); + + bool verbose = false; + flags.emplace_back(GflagsCompatFlag("verbose", verbose)); + + std::vector x_res; + flags.emplace_back(DeviceSpecificUintFlag("x_res", x_res)); + + std::vector y_res; + flags.emplace_back(DeviceSpecificUintFlag("y_res", y_res)); + + std::vector dpi; + flags.emplace_back(DeviceSpecificUintFlag("dpi", dpi)); + + std::vector cpus; + flags.emplace_back(DeviceSpecificUintFlag("cpus", cpus)); + + std::vector memory_mb; + flags.emplace_back(DeviceSpecificUintFlag("memory_mb", memory_mb)); + + std::vector setupwizard_mode; + flags.emplace_back( + DeviceSpecificStringFlag("setupwizard_mode", setupwizard_mode)); + + std::vector report_anonymous_usage_stats; + flags.emplace_back(DeviceSpecificStringFlag("report_anonymous_usage_stats", + report_anonymous_usage_stats)); + + std::vector webrtc_device_id; + flags.emplace_back( + DeviceSpecificStringFlag("webrtc_device_id", webrtc_device_id)); + + bool daemon = true; + flags.emplace_back(GflagsCompatFlag("daemon", daemon)); + + struct Device { + std::string build; + std::string home_dir; + InstanceLockFile ins_lock; + }; + + auto time = std::chrono::system_clock::now().time_since_epoch().count(); + std::vector devices; + auto& device_flag = flags.emplace_back(); + device_flag.Alias({FlagAliasMode::kFlagPrefix, "--device="}); + device_flag.Alias({FlagAliasMode::kFlagConsumesFollowing, "--device"}); + device_flag.Setter([this, time, client_uid, + &devices](const FlagMatch& mat) -> Result { + auto lock = CF_EXPECT(lock_file_manager_.TryAcquireUnusedLock()); + CF_EXPECT(lock.has_value(), "could not acquire instance lock"); + int num = lock->Instance(); + std::string home_dir = ParentDir(client_uid) + std::to_string(time) + + "_" + std::to_string(num) + "/"; + devices.emplace_back(Device{ + .build = mat.value, + .home_dir = std::move(home_dir), + .ins_lock = std::move(*lock), + }); + return {}; + }); + + auto args = ParseInvocation(request.Message()).arguments; + for (const auto& arg : args) { + std::string message = "argument: \"" + arg + "\"\n"; + CF_EXPECT(WriteAll(request.Err(), message) == message.size()); + } + + CF_EXPECT(ConsumeFlags(flags, args)); + + if (help) { + static constexpr char kHelp[] = + "Usage: cvd experimental serial_launch [--verbose] --credentials=XYZ " + "--device=build/target --device=build/target"; + CF_EXPECT(WriteAll(request.Out(), kHelp, sizeof(kHelp)) == sizeof(kHelp)); + return {}; + } + + CF_EXPECT(devices.size() < 2 || daemon, + "--daemon=true required for more than 1 device"); + + std::vector*> int_device_args = { + &x_res, &y_res, &dpi, &cpus, &memory_mb, + }; + for (const auto& int_device_arg : int_device_args) { + CF_EXPECT(int_device_arg->size() == 0 || + int_device_arg->size() == devices.size(), + "If given, device-specific flags should have as many values as " + "there are `--device` arguments"); + } + std::vector*> string_device_args = { + &setupwizard_mode, + &report_anonymous_usage_stats, + &webrtc_device_id, + }; + for (const auto& string_device_arg : string_device_args) { + CF_EXPECT(string_device_arg->size() == 0 || + string_device_arg->size() == devices.size(), + "If given, device-specific flags should have as many values as " + "there are `--device` arguments"); + } + + std::vector req_protos; + + auto mkdir_ancestors_requests = + CF_EXPECT(CreateMkdirCommandRequestRecursively(client_env, + ParentDir(client_uid))); + req_protos = AppendRequestVectors(std::move(req_protos), + std::move(mkdir_ancestors_requests)); + + bool is_first = true; + + int index = 0; + for (const auto& device : devices) { + auto& mkdir_cmd = *req_protos.emplace_back().mutable_command_request(); + *mkdir_cmd.mutable_env() = client_env; + mkdir_cmd.add_args("cvd"); + mkdir_cmd.add_args("mkdir"); + mkdir_cmd.add_args(device.home_dir); + + auto& fetch_cmd = *req_protos.emplace_back().mutable_command_request(); + *fetch_cmd.mutable_env() = client_env; + fetch_cmd.set_working_directory(device.home_dir); + fetch_cmd.add_args("cvd"); + fetch_cmd.add_args("fetch"); + fetch_cmd.add_args("--directory=" + device.home_dir); + fetch_cmd.add_args("-default_build=" + device.build); + fetch_cmd.add_args("-credential_source=" + credentials); + + auto& launch_cmd = *req_protos.emplace_back().mutable_command_request(); + *launch_cmd.mutable_env() = client_env; + launch_cmd.set_working_directory(device.home_dir); + (*launch_cmd.mutable_env())["HOME"] = device.home_dir; + (*launch_cmd.mutable_env())["ANDROID_HOST_OUT"] = device.home_dir; + (*launch_cmd.mutable_env())["ANDROID_PRODUCT_OUT"] = device.home_dir; + launch_cmd.add_args("cvd"); + /* TODO(kwstephenkim): remove kAcquireFileLockOpt flag when + * SerialLaunchCommand is re-implemented so that it does not have to + * acquire a file lock. + */ + launch_cmd.mutable_selector_opts()->add_args( + std::string("--") + selector::SelectorFlags::kAcquireFileLock + + "=false"); + launch_cmd.add_args("start"); + launch_cmd.add_args( + "--undefok=daemon,base_instance_num,x_res,y_res,dpi,cpus,memory_mb," + "setupwizard_mode,report_anonymous_usage_stats,webrtc_device_id"); + launch_cmd.add_args("--daemon"); + launch_cmd.add_args("--base_instance_num=" + + std::to_string(device.ins_lock.Instance())); + if (index < x_res.size()) { + launch_cmd.add_args("--x_res=" + std::to_string(x_res[index])); + } + if (index < y_res.size()) { + launch_cmd.add_args("--y_res=" + std::to_string(y_res[index])); + } + if (index < dpi.size()) { + launch_cmd.add_args("--dpi=" + std::to_string(dpi[index])); + } + if (index < cpus.size()) { + launch_cmd.add_args("--cpus=" + std::to_string(cpus[index])); + } + if (index < memory_mb.size()) { + launch_cmd.add_args("--memory_mb=" + std::to_string(memory_mb[index])); + } + if (index < setupwizard_mode.size()) { + launch_cmd.add_args("--setupwizard_mode=" + setupwizard_mode[index]); + } + if (index < report_anonymous_usage_stats.size()) { + launch_cmd.add_args("--report_anonymous_usage_stats=" + + report_anonymous_usage_stats[index]); + } + if (index < webrtc_device_id.size()) { + launch_cmd.add_args("--webrtc_device_id=" + webrtc_device_id[index]); + } + + index++; + if (is_first) { + is_first = false; + continue; + } + const auto& first = devices[0]; + const auto& first_instance_num = + std::to_string(first.ins_lock.Instance()); + auto hwsim_path = first.home_dir + "cuttlefish_runtime." + + first_instance_num + "/internal/vhost_user_mac80211"; + launch_cmd.add_args("--vhost_user_mac80211_hwsim=" + hwsim_path); + launch_cmd.add_args("--rootcanal_instance_num=" + first_instance_num); + } + + std::vector fds; + if (verbose) { + fds = request.FileDescriptors(); + } else { + auto dev_null = SharedFD::Open("/dev/null", O_RDWR); + CF_EXPECT(dev_null->IsOpen(), dev_null->StrError()); + fds = {dev_null, dev_null, dev_null}; + } + + DemoCommandSequence ret; + for (auto& device : devices) { + ret.instance_locks.emplace_back(std::move(device.ins_lock)); + } + for (auto& request_proto : req_protos) { + ret.requests.emplace_back(request.Client(), request_proto, fds, + request.Credentials()); + } + + return ret; + } + + private: + Result> CreateMkdirCommandRequestRecursively( + const google::protobuf::Map& client_env, + const std::string& path) { + std::vector output; + CF_EXPECT(!path.empty() && path.at(0) == '/', + "Only absolute path is supported."); + if (path == "/") { + return output; + } + std::string path_exclude_root = path.substr(1); + std::vector tokens = + android::base::Tokenize(path_exclude_root, "/"); + std::string current_dir = "/"; + for (int i = 0; i < tokens.size(); i++) { + current_dir.append(tokens[i]); + if (!DirectoryExists(current_dir)) { + output.emplace_back( + CreateCommandRequest(client_env, "cvd", "mkdir", current_dir)); + } + current_dir.append("/"); + } + return output; + } + + CommandSequenceExecutor& executor_; + InstanceLockFileManager& lock_file_manager_; + + std::mutex interrupt_mutex_; + bool interrupted_ = false; +}; + +std::unique_ptr NewSerialLaunchCommand( + CommandSequenceExecutor& executor, + InstanceLockFileManager& lock_file_manager) { + return std::unique_ptr( + new SerialLaunchCommand(executor, lock_file_manager)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_launch.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_launch.h new file mode 100644 index 0000000000..396e31af7a --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_launch.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/instance_lock.h" +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewSerialLaunchCommand( + CommandSequenceExecutor& executor, + InstanceLockFileManager& lock_file_manager); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_preset.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_preset.cpp new file mode 100644 index 0000000000..6916d95c25 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_preset.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2022 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. + */ +#include "host/commands/cvd/server_command/serial_preset.h" + +#include +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +// file copied from "../demo_multi_vd.cpp" +namespace cuttlefish { + +class SerialPreset : public CvdServerHandler { + public: + SerialPreset(CommandSequenceExecutor& executor) : executor_(executor) {} + ~SerialPreset() = default; + + Result CanHandle(const RequestWithStdio& request) const override { + auto invocation = ParseInvocation(request.Message()); + return invocation.command == "experimental" && + invocation.arguments.size() >= 1 && + Presets().count(invocation.arguments[0]) > 0; + } + + Result Handle(const RequestWithStdio& request) override { + std::unique_lock interrupt_lock(interrupt_mutex_); + if (interrupted_) { + return CF_ERR("Interrupted"); + } + CF_EXPECT(CF_EXPECT(CanHandle(request))); + + auto invocation = ParseInvocation(request.Message()); + CF_EXPECT(invocation.arguments.size() >= 1); + const auto& presets = Presets(); + auto devices = presets.find(invocation.arguments[0]); + CF_EXPECT(devices != presets.end(), "could not find preset"); + + cvd::Request inner_req_proto = request.Message(); + auto& cmd = *inner_req_proto.mutable_command_request(); + cmd.clear_args(); + cmd.add_args("cvd"); + cmd.add_args("experimental"); + cmd.add_args("serial_launch"); + for (const auto& device : devices->second) { + cmd.add_args("--device=" + device); + } + for (int i = 1; i < invocation.arguments.size(); i++) { + cmd.add_args(invocation.arguments[i]); + } + + RequestWithStdio inner_request(request.Client(), std::move(inner_req_proto), + request.FileDescriptors(), + request.Credentials()); + + CF_EXPECT(executor_.Execute({std::move(inner_request)}, request.Err())); + interrupt_lock.unlock(); + + cvd::Response response; + response.mutable_command_response(); + return response; + } + + Result Interrupt() override { + std::scoped_lock interrupt_lock(interrupt_mutex_); + interrupted_ = true; + CF_EXPECT(executor_.Interrupt()); + return {}; + } + + cvd_common::Args CmdList() const override { return {"experimental"}; } + + private: + CommandSequenceExecutor& executor_; + + static std::unordered_map> Presets() { + return { + {"create_phone_tablet", + {"git_master/cf_x86_64_phone-userdebug", + "git_master/cf_x86_64_tablet-userdebug"}}, + {"create_phone_wear", + {"git_master/cf_x86_64_phone-userdebug", "git_master/cf_gwear_x86"}}, + }; + } + + std::mutex interrupt_mutex_; + bool interrupted_ = false; +}; + +std::unique_ptr NewSerialPreset( + CommandSequenceExecutor& executor) { + return std::unique_ptr(new SerialPreset(executor)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_preset.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_preset.h new file mode 100644 index 0000000000..d49460b1d9 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/serial_preset.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewSerialPreset( + CommandSequenceExecutor& executor); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/server_handler.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/server_handler.h new file mode 100644 index 0000000000..ec523faf7f --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/server_handler.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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 + +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +class CvdServerHandler { + public: + virtual ~CvdServerHandler() = default; + + virtual Result CanHandle(const RequestWithStdio&) const = 0; + virtual Result Handle(const RequestWithStdio&) = 0; + virtual Result Interrupt() = 0; + // returns the list of subcommand it can handle + virtual cvd_common::Args CmdList() const = 0; + // TODO make pure virtual once every implementation has overrides + virtual Result SummaryHelp() const { + return "Consider contributing a CL with help text if you read this :)"; + } + virtual bool ShouldInterceptHelp() const { return false; } + virtual Result DetailedHelp(std::vector&) const { + return "Consider contributing a CL with help text if you read this :)"; + } +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/shutdown.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/shutdown.cpp new file mode 100644 index 0000000000..477b0a92a1 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/shutdown.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/shutdown.h" + +#include + +#include "cvd_server.pb.h" + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace { + +class CvdShutdownHandler : public CvdServerHandler { + public: + CvdShutdownHandler(CvdServer& server, InstanceManager& instance_manager) + : server_(server), instance_manager_(instance_manager) {} + + Result CanHandle(const RequestWithStdio& request) const override { + return request.Message().contents_case() == + cvd::Request::ContentsCase::kShutdownRequest; + } + + Result Handle(const RequestWithStdio& request) override { + CF_EXPECT(CanHandle(request)); + CF_EXPECT(request.Credentials() != std::nullopt); + + cvd::Response response; + response.mutable_shutdown_response(); + + if (!request.Extra()) { + response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION); + response.mutable_status()->set_message( + "Missing extra SharedFD for shutdown"); + return response; + } + + if (request.Message().shutdown_request().clear()) { + *response.mutable_status() = + instance_manager_.CvdClear(request.Out(), request.Err()); + if (response.status().code() != cvd::Status::OK) { + return response; + } + } + + if (instance_manager_.HasInstanceGroups()) { + response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION); + response.mutable_status()->set_message( + "Cannot shut down cvd_server while devices are being tracked. " + "Try `cvd kill-server`."); + return response; + } + + // Intentionally leak the write_pipe fd so that it only closes + // when this process fully exits. + (*request.Extra())->UNMANAGED_Dup(); + + WriteAll(request.Out(), "Stopping the cvd_server.\n"); + server_.Stop(); + + response.mutable_status()->set_code(cvd::Status::OK); + return response; + } + + Result Interrupt() override { return CF_ERR("Can't interrupt"); } + + // For now, shutdown isn't done by cvd shutdown. + cvd_common::Args CmdList() const override { return {}; } + + private: + CvdServer& server_; + InstanceManager& instance_manager_; +}; + +} // namespace + +std::unique_ptr NewCvdShutdownHandler( + CvdServer& server, InstanceManager& instance_manager) { + return std::unique_ptr( + new CvdShutdownHandler(server, instance_manager)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/shutdown.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/shutdown.h new file mode 100644 index 0000000000..15ac624da6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/shutdown.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdShutdownHandler( + CvdServer& server, InstanceManager& instance_manager); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/snapshot.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/snapshot.cpp new file mode 100644 index 0000000000..58e69f6f75 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/snapshot.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/snapshot.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/flag.h" +#include "host/commands/cvd/selector/instance_database_types.h" +#include "host/commands/cvd/selector/instance_group_record.h" +#include "host/commands/cvd/selector/instance_record.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +static constexpr char kSnapshot[] = + R"(Cuttlefish Virtual Device (CVD) CLI. + +Suspend/resume the cuttlefish device, or take snapshot of the device + +usage: cvd [selector flags] suspend/resume/snapshot_take [--help] + +Common: + Selector Flags: + --group_name= The name of the instance group + --snapshot_path=> Directory that contains saved snapshot files + + Args: + --help print this message + +Crosvm: + --snapshot_compat Tells the device to be snapshot-compatible + The device to be created is checked if it is + compatible with snapshot operations + +QEMU: + No QEMU-specific arguments at the moment + +)"; + +class CvdSnapshotCommandHandler : public CvdServerHandler { + public: + CvdSnapshotCommandHandler(InstanceManager& instance_manager, + SubprocessWaiter& subprocess_waiter, + HostToolTargetManager& host_tool_target_manager) + : instance_manager_{instance_manager}, + subprocess_waiter_(subprocess_waiter), + host_tool_target_manager_(host_tool_target_manager), + cvd_snapshot_operations_{"suspend", "resume", "snapshot_take"} {} + + Result CanHandle(const RequestWithStdio& request) const { + auto invocation = ParseInvocation(request.Message()); + return Contains(cvd_snapshot_operations_, invocation.command); + } + + Result Handle(const RequestWithStdio& request) override { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CanHandle(request)); + CF_EXPECT(VerifyPrecondition(request)); + cvd_common::Envs envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + + auto [subcmd, subcmd_args] = ParseInvocation(request.Message()); + + std::stringstream ss; + for (const auto& arg : subcmd_args) { + ss << arg << " "; + } + LOG(DEBUG) << "Calling new handler with " << subcmd << ": " << ss.str(); + + auto help_flag = CvdFlag("help", false); + cvd_common::Args subcmd_args_copy{subcmd_args}; + auto help_parse_result = help_flag.CalculateFlag(subcmd_args_copy); + bool is_help = help_parse_result.ok() && (*help_parse_result); + + if (is_help) { + auto help_response = CF_EXPECT(HandleHelp(request.Err())); + return help_response; + } + + // may modify subcmd_args by consuming in parsing + Command command = + CF_EXPECT(NonHelpCommand(request, subcmd, subcmd_args, envs)); + CF_EXPECT(subprocess_waiter_.Setup(command.Start())); + interrupt_lock.unlock(); + + auto infop = CF_EXPECT(subprocess_waiter_.Wait()); + return ResponseFromSiginfo(infop); + } + + Result Interrupt() override { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + CF_EXPECT(subprocess_waiter_.Interrupt()); + return {}; + } + + cvd_common::Args CmdList() const override { + return cvd_common::Args(cvd_snapshot_operations_.begin(), + cvd_snapshot_operations_.end()); + } + + private: + Result HandleHelp(const SharedFD& client_stderr) { + std::string help_message(kSnapshot); + help_message.append("\n"); + CF_EXPECT(WriteAll(client_stderr, help_message) == help_message.size(), + "Failed to write the help message"); + cvd::Response response; + response.mutable_command_response(); + response.mutable_status()->set_code(cvd::Status::OK); + return response; + } + + Result NonHelpCommand(const RequestWithStdio& request, + const std::string& subcmd, + cvd_common::Args& subcmd_args, + cvd_common::Envs envs) { + const auto& selector_opts = + request.Message().command_request().selector_opts(); + const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args()); + + // create a string that is comma-separated instance IDs + auto instance_group = + CF_EXPECT(instance_manager_.SelectGroup(selector_args, envs)); + + const auto& home = instance_group.HomeDir(); + const auto& android_host_out = instance_group.HostArtifactsPath(); + auto cvd_snapshot_bin_path = android_host_out + "/bin/" + + CF_EXPECT(GetBin(android_host_out, subcmd)); + const std::string& snapshot_util_cmd = subcmd; + cvd_common::Args cvd_snapshot_args{"--subcmd=" + snapshot_util_cmd}; + cvd_snapshot_args.insert(cvd_snapshot_args.end(), subcmd_args.begin(), + subcmd_args.end()); + // This helps snapshot_util find CuttlefishConfig and figure out + // the instance ids + envs["HOME"] = home; + envs[kAndroidHostOut] = android_host_out; + envs[kAndroidSoongHostOut] = android_host_out; + + std::stringstream command_to_issue; + command_to_issue << "HOME=" << home << " " << kAndroidHostOut << "=" + << android_host_out << " " << kAndroidSoongHostOut << "=" + << android_host_out << " " << cvd_snapshot_bin_path << " "; + for (const auto& arg : cvd_snapshot_args) { + command_to_issue << arg << " "; + } + WriteAll(request.Err(), command_to_issue.str()); + + ConstructCommandParam construct_cmd_param{ + .bin_path = cvd_snapshot_bin_path, + .home = home, + .args = cvd_snapshot_args, + .envs = envs, + .working_dir = request.Message().command_request().working_directory(), + .command_name = android::base::Basename(cvd_snapshot_bin_path), + .in = request.In(), + .out = request.Out(), + .err = request.Err()}; + Command command = CF_EXPECT(ConstructCommand(construct_cmd_param)); + return command; + } + + Result GetBin(const std::string& host_artifacts_path, + const std::string& op) const { + auto snapshot_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({ + .artifacts_path = host_artifacts_path, + .op = op, + })); + return snapshot_bin; + } + + InstanceManager& instance_manager_; + SubprocessWaiter& subprocess_waiter_; + HostToolTargetManager& host_tool_target_manager_; + std::mutex interruptible_; + bool interrupted_ = false; + std::vector cvd_snapshot_operations_; +}; + +std::unique_ptr NewCvdSnapshotCommandHandler( + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter, + HostToolTargetManager& host_tool_target_manager) { + return std::unique_ptr(new CvdSnapshotCommandHandler( + instance_manager, subprocess_waiter, host_tool_target_manager)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/snapshot.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/snapshot.h new file mode 100644 index 0000000000..d79f660d4d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/snapshot.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdSnapshotCommandHandler( + InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter, + HostToolTargetManager& host_tool_target_manager); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/start.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/start.cpp new file mode 100644 index 0000000000..e1f27115c1 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/start.cpp @@ -0,0 +1,838 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/start.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/reset_client_utils.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { +namespace { + +std::optional GetConfigPath(cvd_common::Args& args) { + int initial_size = args.size(); + std::string config_file; + std::vector config_flags = { + GflagsCompatFlag("config_file", config_file)}; + auto result = ConsumeFlags(config_flags, args); + if (!result.ok() || initial_size == args.size()) { + return std::nullopt; + } + return config_file; +} + +RequestWithStdio CreateLoadCommand(const RequestWithStdio& request, + cvd_common::Args& args, + const std::string& config_file) { + cvd::Request request_proto; + auto& load_command = *request_proto.mutable_command_request(); + *load_command.mutable_env() = request.Message().command_request().env(); + load_command.set_working_directory( + request.Message().command_request().working_directory()); + load_command.add_args("cvd"); + load_command.add_args("load"); + for (const auto& arg : args) { + load_command.add_args(arg); + } + load_command.add_args(config_file); + return RequestWithStdio(request.Client(), request_proto, + request.FileDescriptors(), request.Credentials()); +} + +// link might be a directory, so we clean that up, and create a link from +// target to link +Result EnsureSymlink(const std::string& target, const std::string link) { + if (DirectoryExists(link, /* follow_symlinks */ false)) { + CF_EXPECTF(RecursivelyRemoveDirectory(link), + "Failed to remove legacy directory \"{}\"", link); + } + if (FileExists(link, /* follow_symlinks */ false)) { + CF_EXPECTF(RemoveFile(link), "Failed to remove file \"{}\": {}", link, + std::strerror(errno)); + } + CF_EXPECTF(symlink(target.c_str(), link.c_str()) == 0, + "symlink(\"{}\", \"{}\") failed: {}", target, link, + std::strerror(errno)); + return {}; +} + +} // namespace + +class CvdStartCommandHandler : public CvdServerHandler { + public: + CvdStartCommandHandler(InstanceManager& instance_manager, + HostToolTargetManager& host_tool_target_manager, + CommandSequenceExecutor& command_executor) + : instance_manager_(instance_manager), + host_tool_target_manager_(host_tool_target_manager), + // TODO: b/300476262 - Migrate to using local instances rather than + // constructor-injected ones + command_executor_(command_executor), + sub_action_ended_(false) {} + + Result CanHandle(const RequestWithStdio& request) const; + Result Handle(const RequestWithStdio& request) override; + Result Interrupt() override; + std::vector CmdList() const override; + + private: + Result UpdateInstanceDatabase( + const selector::GroupCreationInfo& group_creation_info); + Result FireCommand(Command&& command, const bool wait); + + Result ConstructCvdNonHelpCommand( + const std::string& bin_file, + const selector::GroupCreationInfo& group_info, + const RequestWithStdio& request); + + // call this only if !is_help + Result GetGroupCreationInfo( + const std::string& start_bin, const std::string& subcmd, + const cvd_common::Args& subcmd_args, const cvd_common::Envs& envs, + const RequestWithStdio& request); + + Result FillOutNewInstanceInfo( + cvd::Response&& response, + const selector::GroupCreationInfo& group_creation_info); + + struct UpdatedArgsAndEnvs { + cvd_common::Args args; + cvd_common::Envs envs; + }; + Result UpdateInstanceArgsAndEnvs( + cvd_common::Args&& args, cvd_common::Envs&& envs, + const std::vector& instances, + const std::string& artifacts_path, const std::string& start_bin); + + Result UpdateArgsAndEnvs( + selector::GroupCreationInfo&& old_group_info, + const std::string& start_bin); + + Result FindStartBin(const std::string& android_host_out); + + static void MarkLockfiles(selector::GroupCreationInfo& group_info, + const InUseState state); + static void MarkLockfilesInUse(selector::GroupCreationInfo& group_info) { + MarkLockfiles(group_info, InUseState::kInUse); + } + + /* + * wait, remove the instance group if start failed, filling out the + * response. + */ + Result PostStartExecutionActions( + selector::GroupCreationInfo& group_creation_info); + Result AcloudCompatActions( + const selector::GroupCreationInfo& group_creation_info, + const RequestWithStdio& request); + Result CreateSymlinks( + const selector::GroupCreationInfo& group_creation_info); + + InstanceManager& instance_manager_; + SubprocessWaiter subprocess_waiter_; + HostToolTargetManager& host_tool_target_manager_; + CommandSequenceExecutor& command_executor_; + std::mutex interruptible_; + bool interrupted_ = false; + /* + * Used by Interrupt() not to call command_executor_.Interrupt() + * + * If true, it is guaranteed that the command_executor_ ended the execution. + * If false, it may or may not be after the command_executor_.Execute() + */ + std::atomic sub_action_ended_; + static const std::array supported_commands_; +}; + +Result CvdStartCommandHandler::AcloudCompatActions( + const selector::GroupCreationInfo& group_creation_info, + const RequestWithStdio& request) { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + // rm -fr "TempDir()/acloud_cvd_temp/local-instance-" + std::string acloud_compat_home_prefix = + TempDir() + "/acloud_cvd_temp/local-instance-"; + std::vector acloud_compat_homes; + acloud_compat_homes.reserve(group_creation_info.instances.size()); + for (const auto& instance : group_creation_info.instances) { + acloud_compat_homes.push_back( + ConcatToString(acloud_compat_home_prefix, instance.instance_id_)); + } + for (const auto& acloud_compat_home : acloud_compat_homes) { + bool result_deleted = true; + std::stringstream acloud_compat_home_stream; + if (!FileExists(acloud_compat_home)) { + continue; + } + if (!Contains(group_creation_info.envs, kLaunchedByAcloud) || + group_creation_info.envs.at(kLaunchedByAcloud) != "true") { + if (!DirectoryExists(acloud_compat_home, /*follow_symlinks=*/false)) { + // cvd created a symbolic link + result_deleted = RemoveFile(acloud_compat_home); + } else { + // acloud created a directory + // rm -fr isn't supporetd by TreeHugger, so if we fork-and-exec to + // literally run "rm -fr", the presubmit testing may fail if ever this + // code is tested in the future. + result_deleted = RecursivelyRemoveDirectory(acloud_compat_home); + } + } + if (!result_deleted) { + LOG(ERROR) << "Removing " << acloud_compat_home << " failed."; + continue; + } + } + + // ln -f -s [target] [symlink] + // 1. mkdir -p home + // 2. ln -f -s android_host_out home/host_bins + // 3. for each i in ids, + // ln -f -s home /tmp/acloud_cvd_temp/local-instance- + std::vector request_forms; + const cvd_common::Envs& common_envs = group_creation_info.envs; + + const std::string& home_dir = group_creation_info.home; + const std::string client_pwd = + request.Message().command_request().working_directory(); + request_forms.push_back( + {.cmd_args = cvd_common::Args{"mkdir", "-p", home_dir}, + .env = common_envs, + .selector_args = cvd_common::Args{}, + .working_dir = client_pwd}); + const std::string& android_host_out = group_creation_info.host_artifacts_path; + request_forms.push_back( + {.cmd_args = cvd_common::Args{"ln", "-T", "-f", "-s", android_host_out, + home_dir + "/host_bins"}, + .env = common_envs, + .selector_args = cvd_common::Args{}, + .working_dir = client_pwd}); + /* TODO(weihsu@): cvd acloud delete/list must handle multi-tenancy gracefully + * + * acloud delete just calls, for all instances in a group, + * /tmp/acloud_cvd_temp/local-instance-/host_bins/stop_cvd + * + * That isn't necessary. Not desirable. Cvd acloud should read the instance + * manager's in-memory data structure, and call stop_cvd once for the entire + * group. + * + * Likewise, acloud list simply shows all instances in a flattened way. The + * user has no clue about an instance group. Cvd acloud should show the + * hierarchy. + * + * For now, we create the symbolic links so that it is compatible with acloud + * in Python. + */ + for (const auto& acloud_compat_home : acloud_compat_homes) { + if (acloud_compat_home == home_dir) { + LOG(ERROR) << "The \"HOME\" directory is acloud workspace, which will " + << "be deleted by next cvd start or acloud command with the" + << " same directory being \"HOME\""; + continue; + } + request_forms.push_back({ + .cmd_args = cvd_common::Args{"ln", "-T", "-f", "-s", home_dir, + acloud_compat_home}, + .env = common_envs, + .selector_args = cvd_common::Args{}, + .working_dir = client_pwd, + }); + } + std::vector request_protos; + for (const auto& request_form : request_forms) { + request_protos.emplace_back(MakeRequest(request_form)); + } + std::vector new_requests; + auto dev_null = SharedFD::Open("/dev/null", O_RDWR); + CF_EXPECT(dev_null->IsOpen(), dev_null->StrError()); + std::vector dev_null_fds = {dev_null, dev_null, dev_null}; + for (auto& request_proto : request_protos) { + new_requests.emplace_back(request.Client(), request_proto, dev_null_fds, + request.Credentials()); + } + interrupt_lock.unlock(); + CF_EXPECT(command_executor_.Execute(new_requests, dev_null)); + return {}; +} + +void CvdStartCommandHandler::MarkLockfiles( + selector::GroupCreationInfo& group_info, const InUseState state) { + auto& instances = group_info.instances; + for (auto& instance : instances) { + if (!instance.instance_file_lock_) { + continue; + } + auto result = instance.instance_file_lock_->Status(state); + if (!result.ok()) { + LOG(ERROR) << result.error().FormatForEnv(); + } + } +} + +Result CvdStartCommandHandler::CanHandle( + const RequestWithStdio& request) const { + auto invocation = ParseInvocation(request.Message()); + return Contains(supported_commands_, invocation.command); +} + +Result +CvdStartCommandHandler::UpdateInstanceArgsAndEnvs( + cvd_common::Args&& args, cvd_common::Envs&& envs, + const std::vector& instances, + const std::string& artifacts_path, const std::string& start_bin) { + std::vector ids; + ids.reserve(instances.size()); + for (const auto& instance : instances) { + ids.emplace_back(instance.instance_id_); + } + + cvd_common::Args new_args{std::move(args)}; + std::string old_instance_nums; + std::string old_num_instances; + std::string old_base_instance_num; + + std::vector instance_id_flags{ + GflagsCompatFlag("instance_nums", old_instance_nums), + GflagsCompatFlag("num_instances", old_num_instances), + GflagsCompatFlag("base_instance_num", old_base_instance_num)}; + // discard old ones + CF_EXPECT(ConsumeFlags(instance_id_flags, new_args)); + + auto check_flag = [artifacts_path, start_bin, + this](const std::string& flag_name) -> Result { + CF_EXPECT( + host_tool_target_manager_.ReadFlag({.artifacts_path = artifacts_path, + .op = "start", + .flag_name = flag_name})); + return {}; + }; + auto max = *(std::max_element(ids.cbegin(), ids.cend())); + auto min = *(std::min_element(ids.cbegin(), ids.cend())); + + const bool is_consecutive = ((max - min) == (ids.size() - 1)); + const bool is_sorted = std::is_sorted(ids.begin(), ids.end()); + + if (!is_consecutive || !is_sorted) { + std::string flag_value = android::base::Join(ids, ","); + CF_EXPECT(check_flag("instance_nums")); + new_args.emplace_back("--instance_nums=" + flag_value); + return UpdatedArgsAndEnvs{.args = std::move(new_args), + .envs = std::move(envs)}; + } + + // sorted and consecutive, so let's use old flags + // like --num_instances and --base_instance_num + if (ids.size() > 1) { + CF_EXPECT(check_flag("num_instances"), + "--num_instances is not supported but multi-tenancy requested."); + new_args.emplace_back("--num_instances=" + std::to_string(ids.size())); + } + cvd_common::Envs new_envs{std::move(envs)}; + if (check_flag("base_instance_num").ok()) { + new_args.emplace_back("--base_instance_num=" + std::to_string(min)); + } + new_envs[kCuttlefishInstanceEnvVarName] = std::to_string(min); + return UpdatedArgsAndEnvs{.args = std::move(new_args), + .envs = std::move(new_envs)}; +} + +Result CvdStartCommandHandler::ConstructCvdNonHelpCommand( + const std::string& bin_file, const selector::GroupCreationInfo& group_info, + const RequestWithStdio& request) { + auto bin_path = group_info.host_artifacts_path; + bin_path.append("/bin/").append(bin_file); + CF_EXPECT(!group_info.home.empty()); + ConstructCommandParam construct_cmd_param{ + .bin_path = bin_path, + .home = group_info.home, + .args = group_info.args, + .envs = group_info.envs, + .working_dir = request.Message().command_request().working_directory(), + .command_name = bin_file, + .in = request.In(), + .out = request.Out(), + .err = request.Err()}; + Command non_help_command = CF_EXPECT(ConstructCommand(construct_cmd_param)); + return non_help_command; +} + +// call this only if !is_help +Result +CvdStartCommandHandler::GetGroupCreationInfo( + const std::string& start_bin, const std::string& subcmd, + const std::vector& subcmd_args, const cvd_common::Envs& envs, + const RequestWithStdio& request) { + using CreationAnalyzerParam = + selector::CreationAnalyzer::CreationAnalyzerParam; + const auto& selector_opts = + request.Message().command_request().selector_opts(); + const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args()); + CreationAnalyzerParam analyzer_param{ + .cmd_args = subcmd_args, .envs = envs, .selector_args = selector_args}; + auto cred = CF_EXPECT(request.Credentials()); + auto group_creation_info = + CF_EXPECT(instance_manager_.Analyze(subcmd, analyzer_param, cred)); + auto final_group_creation_info = + CF_EXPECT(UpdateArgsAndEnvs(std::move(group_creation_info), start_bin)); + return final_group_creation_info; +} + +Result CvdStartCommandHandler::UpdateArgsAndEnvs( + selector::GroupCreationInfo&& old_group_info, + const std::string& start_bin) { + selector::GroupCreationInfo group_creation_info = std::move(old_group_info); + // update instance related-flags, envs + const auto& instances = group_creation_info.instances; + const auto& host_artifacts_path = group_creation_info.host_artifacts_path; + auto [new_args, new_envs] = CF_EXPECT(UpdateInstanceArgsAndEnvs( + std::move(group_creation_info.args), std::move(group_creation_info.envs), + instances, host_artifacts_path, start_bin)); + group_creation_info.args = std::move(new_args); + group_creation_info.envs = std::move(new_envs); + + // for backward compatibility, older cvd host tools don't accept group_id + auto has_group_id_flag = + host_tool_target_manager_ + .ReadFlag({.artifacts_path = group_creation_info.host_artifacts_path, + .op = "start", + .flag_name = "group_id"}) + .ok(); + if (has_group_id_flag) { + group_creation_info.args.emplace_back("--group_id=" + + group_creation_info.group_name); + } + + group_creation_info.envs["HOME"] = group_creation_info.home; + group_creation_info.envs[kAndroidHostOut] = + group_creation_info.host_artifacts_path; + group_creation_info.envs[kAndroidProductOut] = + group_creation_info.product_out_path; + /* b/253644566 + * + * Old branches used kAndroidSoongHostOut instead of kAndroidHostOut + */ + group_creation_info.envs[kAndroidSoongHostOut] = + group_creation_info.host_artifacts_path; + group_creation_info.envs[kCvdMarkEnv] = "true"; + return group_creation_info; +} + +static std::ostream& operator<<(std::ostream& out, const cvd_common::Args& v) { + if (v.empty()) { + return out; + } + for (int i = 0; i < v.size() - 1; i++) { + out << v.at(i) << " "; + } + out << v.back(); + return out; +} + +static void ShowLaunchCommand(const std::string& bin, + const cvd_common::Args& args, + const cvd_common::Envs& envs) { + std::stringstream ss; + std::vector interesting_env_names{"HOME", + kAndroidHostOut, + kAndroidSoongHostOut, + "ANDROID_PRODUCT_OUT", + kCuttlefishInstanceEnvVarName, + kCuttlefishConfigEnvVarName}; + for (const auto& interesting_env_name : interesting_env_names) { + if (Contains(envs, interesting_env_name)) { + ss << interesting_env_name << "=\"" << envs.at(interesting_env_name) + << "\" "; + } + } + ss << " " << bin << " " << args; + LOG(INFO) << "launcher command: " << ss.str(); +} + +static void ShowLaunchCommand(const std::string& bin, + selector::GroupCreationInfo& group_info) { + ShowLaunchCommand(bin, group_info.args, group_info.envs); +} + +Result CvdStartCommandHandler::FindStartBin( + const std::string& android_host_out) { + auto start_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({ + .artifacts_path = android_host_out, + .op = "start", + })); + return start_bin; +} + +static Result ConsumeDaemonModeFlag(cvd_common::Args& args) { + Flag flag = + Flag() + .Alias({FlagAliasMode::kFlagPrefix, "-daemon="}) + .Alias({FlagAliasMode::kFlagPrefix, "--daemon="}) + .Alias({FlagAliasMode::kFlagExact, "-daemon"}) + .Alias({FlagAliasMode::kFlagExact, "--daemon"}) + .Alias({FlagAliasMode::kFlagExact, "-nodaemon"}) + .Alias({FlagAliasMode::kFlagExact, "--nodaemon"}) + .Setter([](const FlagMatch& match) -> Result { + static constexpr char kPossibleCmds[] = + "\"cvd start\" or \"launch_cvd\""; + if (match.key == match.value) { + CF_EXPECTF(match.key.find("no") == std::string::npos, + "--nodaemon is not supported by {}", kPossibleCmds); + return {}; + } + CF_EXPECTF(match.value.find(",") == std::string::npos, + "{} had a comma that is not allowed", match.value); + static constexpr std::string_view kValidFalseStrings[] = {"n", "no", + "false"}; + static constexpr std::string_view kValidTrueStrings[] = {"y", "yes", + "true"}; + for (const auto& true_string : kValidTrueStrings) { + if (android::base::EqualsIgnoreCase(true_string, match.value)) { + return {}; + } + } + for (const auto& false_string : kValidFalseStrings) { + CF_EXPECTF( + !android::base::EqualsIgnoreCase(false_string, match.value), + "\"{}{} was given and is not supported by {}", match.key, + match.value, kPossibleCmds); + } + return CF_ERRF( + "Invalid --daemon option: {}{}. {} supports only " + "\"--daemon=true\"", + match.key, match.value, kPossibleCmds); + }); + CF_EXPECT(ConsumeFlags({flag}, args)); + return {}; +} + +// For backward compatibility, we add extra symlink in system wide home +// when HOME is NOT overridden and selector flags are NOT given. +Result CvdStartCommandHandler::CreateSymlinks( + const selector::GroupCreationInfo& group_creation_info) { + CF_EXPECT(EnsureDirectoryExists(group_creation_info.home)); + auto system_wide_home = CF_EXPECT(SystemWideUserHome()); + auto smallest_id = std::numeric_limits::max(); + for (const auto& instance : group_creation_info.instances) { + // later on, we link cuttlefish_runtime to cuttlefish_runtime.smallest_id + smallest_id = std::min(smallest_id, instance.instance_id_); + const std::string instance_home_dir = + fmt::format("{}/cuttlefish/instances/cvd-{}", group_creation_info.home, + instance.instance_id_); + CF_EXPECT( + EnsureSymlink(instance_home_dir, + fmt::format("{}/cuttlefish_runtime.{}", system_wide_home, + instance.instance_id_))); + CF_EXPECT(EnsureSymlink(group_creation_info.home + "/cuttlefish", + system_wide_home + "/cuttlefish")); + CF_EXPECT(EnsureSymlink(group_creation_info.home + + "/cuttlefish/assembly/cuttlefish_config.json", + system_wide_home + "/.cuttlefish_config.json")); + } + + // create cuttlefish_runtime to cuttlefish_runtime.id + CF_EXPECT_NE(std::numeric_limits::max(), smallest_id, + "The group did not have any instance, which is not expected."); + const std::string instance_runtime_dir = + fmt::format("{}/cuttlefish_runtime.{}", system_wide_home, smallest_id); + const std::string runtime_dir_link = system_wide_home + "/cuttlefish_runtime"; + CF_EXPECT(EnsureSymlink(instance_runtime_dir, runtime_dir_link)); + return {}; +} + +Result CvdStartCommandHandler::Handle( + const RequestWithStdio& request) { + std::unique_lock interrupt_lock(interruptible_); + if (interrupted_) { + return CF_ERR("Interrupted"); + } + CF_EXPECT(CanHandle(request)); + + cvd::Response response; + response.mutable_command_response(); + + auto [subcmd, subcmd_args] = ParseInvocation(request.Message()); + std::optional config_file = GetConfigPath(subcmd_args); + if (config_file) { + auto subrequest = CreateLoadCommand(request, subcmd_args, *config_file); + interrupt_lock.unlock(); + response = + CF_EXPECT(command_executor_.ExecuteOne(subrequest, request.Err())); + sub_action_ended_ = true; + return response; + } + + auto precondition_verified = VerifyPrecondition(request); + if (!precondition_verified.ok()) { + response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION); + response.mutable_status()->set_message( + precondition_verified.error().Message()); + return response; + } + + cvd_common::Envs envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + if (Contains(envs, "HOME")) { + if (envs.at("HOME").empty()) { + envs.erase("HOME"); + } else { + // As the end-user may override HOME, this could be a relative path + // to client's pwd, or may include "~" which is the client's actual + // home directory. + auto client_pwd = request.Message().command_request().working_directory(); + const auto given_home_dir = envs.at("HOME"); + /* + * Imagine this scenario: + * client$ export HOME=/tmp/new/dir + * client$ HOME="~/subdir" cvd start + * + * The value of ~ isn't sent to the server. The server can't figure that + * out as it might be overridden before the cvd start command. + */ + CF_EXPECT(!android::base::StartsWith(given_home_dir, "~") && + !android::base::StartsWith(given_home_dir, "~/"), + "The HOME directory should not start with ~"); + envs["HOME"] = CF_EXPECT( + EmulateAbsolutePath({.current_working_dir = client_pwd, + .home_dir = CF_EXPECT(SystemWideUserHome()), + .path_to_convert = given_home_dir, + .follow_symlink = false})); + } + } + CF_EXPECT(Contains(envs, kAndroidHostOut)); + const auto bin = CF_EXPECT(FindStartBin(envs.at(kAndroidHostOut))); + + // update DB if not help + // collect group creation infos + CF_EXPECT(Contains(supported_commands_, subcmd), + "subcmd should be start but is " << subcmd); + const bool is_help = CF_EXPECT(IsHelpSubcmd(subcmd_args)); + CF_EXPECT(ConsumeDaemonModeFlag(subcmd_args)); + subcmd_args.push_back("--daemon=true"); + + std::optional group_creation_info; + if (!is_help) { + group_creation_info = CF_EXPECT( + GetGroupCreationInfo(bin, subcmd, subcmd_args, envs, request)); + CF_EXPECT(UpdateInstanceDatabase(*group_creation_info)); + response = CF_EXPECT( + FillOutNewInstanceInfo(std::move(response), *group_creation_info)); + } + + Command command = + is_help + ? CF_EXPECT(ConstructCvdHelpCommand(bin, envs, subcmd_args, request)) + : CF_EXPECT( + ConstructCvdNonHelpCommand(bin, *group_creation_info, request)); + + if (!is_help) { + CF_EXPECT( + group_creation_info != std::nullopt, + "group_creation_info should be nullopt only when --help is given."); + } + + if (is_help) { + ShowLaunchCommand(command.Executable(), subcmd_args, envs); + } else { + ShowLaunchCommand(command.Executable(), *group_creation_info); + CF_EXPECT(request.Message().command_request().wait_behavior() != + cvd::WAIT_BEHAVIOR_START); + } + + CF_EXPECT(FireCommand(std::move(command), /*should_wait*/ true)); + interrupt_lock.unlock(); + + if (is_help) { + auto infop = CF_EXPECT(subprocess_waiter_.Wait()); + return ResponseFromSiginfo(infop); + } + + // make acquire interrupt_lock inside. + auto acloud_compat_action_result = + AcloudCompatActions(*group_creation_info, request); + sub_action_ended_ = true; + if (!acloud_compat_action_result.ok()) { + LOG(ERROR) << acloud_compat_action_result.error().FormatForEnv(); + LOG(ERROR) << "AcloudCompatActions() failed" + << " but continue as they are minor errors."; + } + return PostStartExecutionActions(*group_creation_info); +} + +static constexpr char kCollectorFailure[] = R"( + Consider running: + cvd reset -y + + cvd start failed. While we should collect run_cvd processes to manually + clean them up, collecting run_cvd failed. +)"; +static constexpr char kStopFailure[] = R"( + Consider running: + cvd reset -y + + cvd start failed, and stopping run_cvd processes failed. +)"; +static Result CvdResetGroup( + const selector::GroupCreationInfo& group_creation_info) { + auto run_cvd_process_manager = RunCvdProcessManager::Get(); + if (!run_cvd_process_manager.ok()) { + return CommandResponse(cvd::Status::INTERNAL, kCollectorFailure); + } + // We can't run stop_cvd here. It may hang forever, and doesn't make sense + // to interrupt it. + const auto& instances = group_creation_info.instances; + CF_EXPECT(!instances.empty()); + const auto& first_instance = instances.front(); + auto stop_result = run_cvd_process_manager->ForcefullyStopGroup( + /* cvd_server_children_only */ true, first_instance.instance_id_); + if (!stop_result.ok()) { + return CommandResponse(cvd::Status::INTERNAL, kStopFailure); + } + return CommandResponse(cvd::Status::OK, ""); +} + +Result CvdStartCommandHandler::PostStartExecutionActions( + selector::GroupCreationInfo& group_creation_info) { + auto infop = CF_EXPECT(subprocess_waiter_.Wait()); + if (infop.si_code != CLD_EXITED || infop.si_status != EXIT_SUCCESS) { + // run_cvd processes may be still running in background + // the order of the following operations should be kept + auto reset_response = CF_EXPECT(CvdResetGroup(group_creation_info)); + instance_manager_.RemoveInstanceGroup(group_creation_info.home); + if (reset_response.status().code() != cvd::Status::OK) { + return reset_response; + } + } + auto final_response = ResponseFromSiginfo(infop); + if (!final_response.has_status() || + final_response.status().code() != cvd::Status::OK) { + return final_response; + } + // If not daemonized, reaching here means the instance group terminated. + // Thus, it's enough to release the file lock in the destructor. + // If daemonized, reaching here means the group started successfully + // As the destructor will release the file lock, the instance lock + // files must be marked as used + MarkLockfilesInUse(group_creation_info); + + // For backward compatibility, we add extra symlink in system wide home + // when HOME is NOT overridden and selector flags are NOT given. + if (group_creation_info.is_default_group) { + CF_EXPECT(CreateSymlinks(group_creation_info)); + } + + // group_creation_info is nullopt only if is_help is false + return FillOutNewInstanceInfo(std::move(final_response), group_creation_info); +} + +Result CvdStartCommandHandler::Interrupt() { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + if (!sub_action_ended_) { + auto result = command_executor_.Interrupt(); + if (!result.ok()) { + LOG(ERROR) << "Failed to interrupt CommandExecutor" + << result.error().FormatForEnv(); + } + } + CF_EXPECT(subprocess_waiter_.Interrupt()); + return {}; +} + +Result CvdStartCommandHandler::FillOutNewInstanceInfo( + cvd::Response&& response, + const selector::GroupCreationInfo& group_creation_info) { + auto new_response = std::move(response); + auto& command_response = *(new_response.mutable_command_response()); + auto& instance_group_info = + *(CF_EXPECT(command_response.mutable_instance_group_info())); + instance_group_info.set_group_name(group_creation_info.group_name); + instance_group_info.add_home_directories(group_creation_info.home); + for (const auto& per_instance_info : group_creation_info.instances) { + auto* new_entry = CF_EXPECT(instance_group_info.add_instances()); + new_entry->set_name(per_instance_info.per_instance_name_); + new_entry->set_instance_id(per_instance_info.instance_id_); + } + return new_response; +} + +Result CvdStartCommandHandler::UpdateInstanceDatabase( + const selector::GroupCreationInfo& group_creation_info) { + CF_EXPECT(instance_manager_.SetInstanceGroup(group_creation_info), + group_creation_info.home + << " is already taken so can't create new instance."); + return {}; +} + +Result CvdStartCommandHandler::FireCommand(Command&& command, + const bool wait) { + SubprocessOptions options; + if (!wait) { + options.ExitWithParent(false); + } + CF_EXPECT(subprocess_waiter_.Setup(command.Start(std::move(options)))); + return {}; +} + +std::vector CvdStartCommandHandler::CmdList() const { + std::vector subcmd_list; + subcmd_list.reserve(supported_commands_.size()); + for (const auto& cmd : supported_commands_) { + subcmd_list.emplace_back(cmd); + } + return subcmd_list; +} + +const std::array CvdStartCommandHandler::supported_commands_{ + "start", "launch_cvd"}; + +std::unique_ptr NewCvdStartCommandHandler( + InstanceManager& instance_manager, + HostToolTargetManager& host_tool_target_manager, + CommandSequenceExecutor& executor) { + return std::unique_ptr(new CvdStartCommandHandler( + instance_manager, host_tool_target_manager, executor)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/start.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/start.h new file mode 100644 index 0000000000..58d18ccd3d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/start.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/command_sequence.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdStartCommandHandler( + InstanceManager& instance_manager, + HostToolTargetManager& host_tool_target_manager, + CommandSequenceExecutor& executor); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/start_impl.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/start_impl.cpp new file mode 100644 index 0000000000..a9ff4563fd --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/start_impl.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/start_impl.h" + +#include +#include + +#include + +#include "common/libs/utils/files.h" +#include "host/commands/cvd/common_utils.h" + +namespace cuttlefish { +namespace cvd_start_impl { + +/* Picks up the line starting with [*] GUEST_BUILD_FINGERPRINT:, + * and removes the "[*] GUEST_BUILD_FINGERPRINT:" part. + * + */ +static Result ExtractBuildIdLineValue( + const std::string& home_dir) { + std::string kernel_log_path = + ConcatToString(home_dir, "/cuttlefish_runtime/kernel.log"); + if (!FileExists(kernel_log_path)) { + kernel_log_path = + ConcatToString(home_dir, "/cuttlefish_runtime_runtime/kernel.log"); + } + std::ifstream kernel_log_file(kernel_log_path); + CF_EXPECT(kernel_log_file.is_open(), + "The " << kernel_log_path << " is not open."); + std::regex pattern("\\[\\s*[0-9]*\\.[0-9]+\\]\\s*GUEST_BUILD_FINGERPRINT:"); + for (std::string line; std::getline(kernel_log_file, line);) { + std::smatch matched; + if (!std::regex_search(line, matched, pattern)) { + continue; + } + return matched.suffix().str(); + } + auto err_message = + ConcatToString("The GUEST_BUILD_FINGERPRINT line is not found in the", + kernel_log_path, " file"); + return CF_ERR(err_message); +} + +Result ExtractBuildId(const std::string& home_dir) { + auto fingerprint_line_value = CF_EXPECT(ExtractBuildIdLineValue(home_dir)); + /* format: + * /target/build year/branch.id/who built it/when:target/?? + * + * We need the branch followed by . followed by sort of Id part + */ + std::vector tokens = + android::base::Tokenize(fingerprint_line_value, "/"); + CF_EXPECT(tokens.size() > 2); + return tokens.at(3); +} + +} // namespace cvd_start_impl +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/start_impl.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/start_impl.h new file mode 100644 index 0000000000..ce41f317f6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/start_impl.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { +namespace cvd_start_impl { + +Result ExtractBuildId(const std::string& home_dir); + +} // namespace cvd_start_impl +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/status.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/status.cpp new file mode 100644 index 0000000000..e972a4334c --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/status.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/status.h" + +#include + +#include + +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/flag.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/status_fetcher.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { + +static constexpr char kHelpMessage[] = R"( + +usage: cvd + +Selector Options: + -group_name Specify the name of the instance group created + or selected. + -instance_name Selects the device of the given name to perform the + commands for. + -instance_name Takes the names of the devices to create within an + instance group. The 'names' is comma-separated. + +Driver Options: + -verbosity= Adjust Cvd verbosity level. LEVEL is Android log + severity. (Required: cvd >= v1.3) + +Args: + --wait_for_launcher How many seconds to wait for the launcher to respond + to the status command. A value of zero means wait + indefinitely + (Current value: "5") + + --instance_name Either instance id (e.g. 1) or internal name (e.g. + cvd-1) If not provided, the smallest id in the given + instance group is selected. + (Current value: "", Required: Android > 12) + + --print If provided, prints status and instance config + information to stdout instead of CHECK. + (Current value: "false", Required: Android > 12) + + --all_instances List, within the given instance group, all instances + status and instance config information. + (Current value: "false", Required: Android > 12) + + --help List this message + + * Only the flags in `-help` are supported. Positional + arguments are not supported. + +)"; + +class CvdStatusCommandHandler : public CvdServerHandler { + public: + CvdStatusCommandHandler(InstanceManager& instance_manager, + HostToolTargetManager& host_tool_target_manager); + + Result CanHandle(const RequestWithStdio& request) const; + Result Handle(const RequestWithStdio& request) override; + Result Interrupt() override; + cvd_common::Args CmdList() const override; + + private: + Result HandleHelp(const RequestWithStdio&); + + InstanceManager& instance_manager_; + HostToolTargetManager& host_tool_target_manager_; + StatusFetcher status_fetcher_; + std::mutex interruptible_; + bool interrupted_ = false; + std::vector supported_subcmds_; +}; + +CvdStatusCommandHandler::CvdStatusCommandHandler( + InstanceManager& instance_manager, + HostToolTargetManager& host_tool_target_manager) + : instance_manager_(instance_manager), + host_tool_target_manager_(host_tool_target_manager), + status_fetcher_(instance_manager_, host_tool_target_manager_), + supported_subcmds_{"status", "cvd_status"} {} + +Result CvdStatusCommandHandler::CanHandle( + const RequestWithStdio& request) const { + auto invocation = ParseInvocation(request.Message()); + return Contains(supported_subcmds_, invocation.command); +} + +Result CvdStatusCommandHandler::Interrupt() { + std::scoped_lock interrupt_lock(interruptible_); + interrupted_ = true; + CF_EXPECT(status_fetcher_.Interrupt()); + return {}; +} + +static Result ProcessInstanceNameFlag( + const RequestWithStdio& request) { + cvd_common::Envs envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + auto [subcmd, cmd_args] = ParseInvocation(request.Message()); + + CvdFlag instance_name_flag("instance_name"); + auto instance_name_flag_opt = + CF_EXPECT(instance_name_flag.FilterFlag(cmd_args)); + + if (!instance_name_flag_opt) { + return request; + } + + std::string internal_name_or_id = *instance_name_flag_opt; + int id; + if (android::base::ParseInt(internal_name_or_id, &id)) { + envs[kCuttlefishInstanceEnvVarName] = std::to_string(id); + } else { + CF_EXPECT(android::base::StartsWith(internal_name_or_id, kCvdNamePrefix), + "--instance_name should be either cvd- or id"); + std::string id_part = + internal_name_or_id.substr(std::string(kCvdNamePrefix).size()); + CF_EXPECT(android::base::ParseInt(id_part, &id), + "--instance_name should be either cvd- or id"); + envs[kCuttlefishInstanceEnvVarName] = std::to_string(id); + } + + cvd_common::Args new_cmd_args{"cvd", "status"}; + new_cmd_args.insert(new_cmd_args.end(), cmd_args.begin(), cmd_args.end()); + const auto& selector_opts = + request.Message().command_request().selector_opts(); + cvd::Request new_message = MakeRequest({ + .cmd_args = new_cmd_args, + .env = envs, + .selector_args = cvd_common::ConvertToArgs(selector_opts.args()), + .working_dir = request.Message().command_request().working_directory(), + }); + return RequestWithStdio(request.Client(), new_message, + request.FileDescriptors(), request.Credentials()); +} + +static Result HasPrint(cvd_common::Args cmd_args) { + CvdFlag print_flag("print"); + auto print_flag_opt = CF_EXPECT(print_flag.FilterFlag(cmd_args)); + return print_flag_opt.has_value() && *print_flag_opt; +} + +Result CvdStatusCommandHandler::Handle( + const RequestWithStdio& request) { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CanHandle(request)); + CF_EXPECT(request.Credentials()); + + auto precondition_verified = VerifyPrecondition(request); + if (!precondition_verified.ok()) { + cvd::Response response; + response.mutable_command_response(); + response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION); + response.mutable_status()->set_message( + precondition_verified.error().Message()); + return response; + } + + CF_EXPECT_NE(request.Message().command_request().wait_behavior(), + cvd::WAIT_BEHAVIOR_START, + "cvd status shouldn't be cvd::WAIT_BEHAVIOR_START"); + interrupt_lock.unlock(); + + auto [subcmd, cmd_args] = ParseInvocation(request.Message()); + CF_EXPECT(Contains(supported_subcmds_, subcmd)); + const bool has_print = CF_EXPECT(HasPrint(cmd_args)); + + if (CF_EXPECT(IsHelpSubcmd(cmd_args))) { + return HandleHelp(request); + } + + if (instance_manager_.AllGroupNames().empty()) { + return CF_EXPECT(NoGroupResponse(request)); + } + RequestWithStdio new_request = CF_EXPECT(ProcessInstanceNameFlag(request)); + + auto [entire_stderr_msg, instances_json, response] = + CF_EXPECT(status_fetcher_.FetchStatus(new_request)); + if (response.status().code() != cvd::Status::OK) { + return response; + } + + std::string serialized_group_json = instances_json.toStyledString(); + CF_EXPECT_EQ(WriteAll(request.Err(), entire_stderr_msg), + entire_stderr_msg.size()); + if (has_print) { + CF_EXPECT_EQ(WriteAll(request.Out(), serialized_group_json), + serialized_group_json.size()); + } + return response; +} + +std::vector CvdStatusCommandHandler::CmdList() const { + return supported_subcmds_; +} + +Result CvdStatusCommandHandler::HandleHelp( + const RequestWithStdio& request) { + cvd::Response response; + response.mutable_command_response(); // Sets oneof member + response.mutable_status()->set_code(cvd::Status::OK); + CF_EXPECT_EQ(WriteAll(request.Out(), kHelpMessage), + strnlen(kHelpMessage, sizeof(kHelpMessage) - 1)); + return response; +} + +std::unique_ptr NewCvdStatusCommandHandler( + InstanceManager& instance_manager, + HostToolTargetManager& host_tool_target_manager) { + return std::unique_ptr( + new CvdStatusCommandHandler(instance_manager, host_tool_target_manager)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/status.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/status.h new file mode 100644 index 0000000000..4e2010d1ea --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/status.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdStatusCommandHandler( + InstanceManager& instance_manager, + HostToolTargetManager& host_tool_target_manager); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/status_fetcher.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/status_fetcher.cpp new file mode 100644 index 0000000000..2833edb664 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/status_fetcher.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/status_fetcher.h" + +#include +#include +#include +#include + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/files.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/flag.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { +namespace { + +struct IdAndPerInstanceName { + IdAndPerInstanceName(std::string name, const unsigned id) + : per_instance_name(name), id(id) {} + IdAndPerInstanceName() = default; + std::string per_instance_name; + unsigned id; +}; + +} // namespace + +Result StatusFetcher::Interrupt() { + std::lock_guard interrupt_lock(interruptible_); + interrupted_ = true; + CF_EXPECT(subprocess_waiter_.Interrupt()); + return {}; +} + +static Result CreateFileToRedirect( + const std::string& stderr_or_stdout) { + auto thread_id = std::this_thread::get_id(); + std::stringstream ss; + ss << "cvd.status." << stderr_or_stdout << "." << thread_id; + auto mem_fd_name = ss.str(); + SharedFD fd = SharedFD::MemfdCreate(mem_fd_name); + CF_EXPECT(fd->IsOpen()); + return fd; +} + +Result StatusFetcher::FetchOneInstanceStatus( + const RequestWithStdio& request, + const InstanceManager::LocalInstanceGroup& instance_group, + const std::string& per_instance_name, const unsigned id) { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + auto [subcmd, cmd_args] = ParseInvocation(request.Message()); + + // remove --all_instances if there is + bool all_instances = false; + CF_EXPECT(ConsumeFlags({GflagsCompatFlag("all_instances", all_instances)}, + cmd_args)); + + const auto working_dir = + request.Message().command_request().working_directory(); + + auto android_host_out = instance_group.HostArtifactsPath(); + auto home = instance_group.HomeDir(); + auto bin = CF_EXPECT(GetBin(android_host_out)); + auto bin_path = fmt::format("{}/bin/{}", android_host_out, bin); + + cvd_common::Envs envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + envs["HOME"] = home; + // old cvd_internal_status expects CUTTLEFISH_INSTANCE= + envs[kCuttlefishInstanceEnvVarName] = std::to_string(id); + + SharedFD redirect_stdout_fd = CF_EXPECT(CreateFileToRedirect("stdout")); + SharedFD redirect_stderr_fd = CF_EXPECT(CreateFileToRedirect("stderr")); + ConstructCommandParam construct_cmd_param{.bin_path = bin_path, + .home = home, + .args = cmd_args, + .envs = envs, + .working_dir = working_dir, + .command_name = bin, + .in = request.In(), + .out = redirect_stdout_fd, + .err = redirect_stderr_fd}; + Command command = CF_EXPECT(ConstructCommand(construct_cmd_param)); + + CF_EXPECT(subprocess_waiter_.Setup(command.Start())); + + interrupt_lock.unlock(); + auto infop = CF_EXPECT(subprocess_waiter_.Wait()); + + CF_EXPECT_EQ(redirect_stdout_fd->LSeek(0, SEEK_SET), 0); + CF_EXPECT_EQ(redirect_stderr_fd->LSeek(0, SEEK_SET), 0); + + std::string serialized_json; + CF_EXPECT_GE(ReadAll(redirect_stdout_fd, &serialized_json), 0); + + // old branches will print nothing + if (serialized_json.empty()) { + serialized_json = "[{\"warning\" : \"cvd-status-unsupported device\"}]"; + } + + std::string status_stderr; + CF_EXPECT_GE(ReadAll(redirect_stderr_fd, &status_stderr), 0); + + auto instance_status_json = CF_EXPECT(ParseJson(serialized_json)); + CF_EXPECT_EQ(instance_status_json.size(), 1); + instance_status_json = instance_status_json[0]; + static constexpr auto kWebrtcProp = "webrtc_device_id"; + static constexpr auto kNameProp = "instance_name"; + + // Check for isObject first, calling isMember on anything else causes a + // runtime error + if (instance_status_json.isObject() && + !instance_status_json.isMember(kWebrtcProp) && + instance_status_json.isMember(kNameProp)) { + // b/296644913 some cuttlefish versions printed the webrtc device id as + // the instance name. + instance_status_json[kWebrtcProp] = instance_status_json[kNameProp]; + } + instance_status_json[kNameProp] = per_instance_name; + + return StatusFetcherOutput{ + .stderr_buf = status_stderr, + .json_from_stdout = instance_status_json, + .response = ResponseFromSiginfo(infop), + }; +} + +Result StatusFetcher::FetchStatus( + const RequestWithStdio& request) { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + cvd_common::Envs envs = + cvd_common::ConvertToEnvs(request.Message().command_request().env()); + auto [subcmd, cmd_args] = ParseInvocation(request.Message()); + + // find group + const auto& selector_opts = + request.Message().command_request().selector_opts(); + const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args()); + CF_EXPECT(Contains(envs, kAndroidHostOut) && + DirectoryExists(envs.at(kAndroidHostOut))); + + CvdFlag all_instances_flag("all_instances"); + auto all_instances_opt = CF_EXPECT(all_instances_flag.FilterFlag(cmd_args)); + + auto instance_group = + CF_EXPECT(instance_manager_.SelectGroup(selector_args, envs)); + + std::vector instance_infos; + auto instance_record_result = + instance_manager_.SelectInstance(selector_args, envs); + + bool status_the_group_flag = all_instances_opt && *all_instances_opt; + if (instance_record_result.ok() && !status_the_group_flag) { + instance_infos.emplace_back( + instance_record_result->PerInstanceName(), + static_cast(instance_record_result->InstanceId())); + } else { + auto instances = CF_EXPECT(instance_manager_.FindInstances( + {selector::kGroupNameField, instance_group.GroupName()})); + if (status_the_group_flag) { + instance_infos.reserve(instances.size()); + for (const auto& instance : instances) { + instance_infos.emplace_back( + instance.PerInstanceName(), + static_cast(instance.InstanceId())); + } + } else { + std::map sorted_id_name_map; + for (const auto& instance : instances) { + sorted_id_name_map[instance.InstanceId()] = instance.PerInstanceName(); + } + auto first_itr = sorted_id_name_map.begin(); + instance_infos.emplace_back(first_itr->second, first_itr->first); + } + } + interrupt_lock.unlock(); + + std::string entire_stderr_msg; + Json::Value instances_json(Json::arrayValue); + for (const auto& instance_info : instance_infos) { + auto [status_stderr, instance_status_json, instance_response] = + CF_EXPECT(FetchOneInstanceStatus(request, instance_group, + instance_info.per_instance_name, + instance_info.id)); + CF_EXPECTF(instance_response.status().code() == cvd::Status::OK, + "cvd status for {}-{} failed", instance_group.GroupName(), + instance_info.per_instance_name); + instances_json.append(instance_status_json); + entire_stderr_msg.append(status_stderr); + } + + cvd::Response response; + response.mutable_command_response(); + response.mutable_status()->set_code(cvd::Status::OK); + return StatusFetcherOutput{ + .stderr_buf = entire_stderr_msg, + .json_from_stdout = instances_json, + .response = response, + }; +} + +Result StatusFetcher::GetBin( + const std::string& host_artifacts_path) const { + return CF_EXPECT(host_tool_target_manager_.ExecBaseName({ + .artifacts_path = host_artifacts_path, + .op = "status", + })); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/status_fetcher.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/status_fetcher.h new file mode 100644 index 0000000000..83a2f893c2 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/status_fetcher.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include +#include + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/json.h" +#include "common/libs/utils/result.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +struct StatusFetcherOutput { + std::string stderr_buf; + Json::Value json_from_stdout; + cvd::Response response; +}; + +class StatusFetcher { + public: + StatusFetcher(InstanceManager& instance_manager, + HostToolTargetManager& host_tool_target_manager) + : instance_manager_(instance_manager), + host_tool_target_manager_(host_tool_target_manager) {} + Result Interrupt(); + Result FetchStatus(const RequestWithStdio&); + + private: + Result GetBin(const std::string& host_artifacts_path) const; + Result FetchOneInstanceStatus( + const RequestWithStdio&, const InstanceManager::LocalInstanceGroup&, + const std::string&, const unsigned); + + std::mutex interruptible_; + bool interrupted_ = false; + + InstanceManager& instance_manager_; + HostToolTargetManager& host_tool_target_manager_; + // needs to be exclusively owned by StatusFetcher + SubprocessWaiter subprocess_waiter_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/subcmd.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/subcmd.h new file mode 100644 index 0000000000..91fd788089 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/subcmd.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 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 + +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/utils.h" diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/subprocess_waiter.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/subprocess_waiter.cpp new file mode 100644 index 0000000000..064d7d6dd8 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/subprocess_waiter.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "common/libs/fs/shared_buf.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" + +namespace cuttlefish { + +Result SubprocessWaiter::Setup(Subprocess subprocess) { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(!subprocess_, "Already running"); + + subprocess_ = std::move(subprocess); + return {}; +} + +Result SubprocessWaiter::Wait() { + std::unique_lock interrupt_lock(interruptible_); + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(subprocess_.has_value()); + + siginfo_t infop{}; + + interrupt_lock.unlock(); + + // This blocks until the process exits, but doesn't reap it. + auto result = subprocess_->Wait(&infop, WEXITED | WNOWAIT); + CF_EXPECT(result != -1, "Lost track of subprocess pid"); + interrupt_lock.lock(); + // Perform a reaping wait on the process (which should already have exited). + result = subprocess_->Wait(&infop, WEXITED); + CF_EXPECT(result != -1, "Lost track of subprocess pid"); + // The double wait avoids a race around the kernel reusing pids. Waiting + // with WNOWAIT won't cause the child process to be reaped, so the kernel + // won't reuse the pid until the Wait call below, and any kill signals won't + // reach unexpected processes. + + subprocess_ = {}; + + return infop; +} + +Result SubprocessWaiter::Interrupt() { + std::scoped_lock interrupt_lock(interruptible_); + if (subprocess_) { + auto stop_result = subprocess_->Stop(); + switch (stop_result) { + case StopperResult::kStopFailure: + return CF_ERR("Failed to stop subprocess"); + case StopperResult::kStopCrash: + return CF_ERR("Stopper caused process to crash"); + case StopperResult::kStopSuccess: + return {}; + default: + return CF_ERR("Unknown stop result: " << (uint64_t)stop_result); + } + } + return {}; +} + +Result SubprocessWaiter::RunWithManagedStdioInterruptable( + RunWithManagedIoParam param) { + RunOutput output; + std::thread stdin_thread, stdout_thread, stderr_thread; + std::vector threads_({&stdin_thread, &stdout_thread, &stderr_thread}); + Command cmd = std::move(param.cmd_); + bool io_error = false; + if (param.stdin_) { + SharedFD pipe_read, pipe_write; + CF_EXPECT(SharedFD::Pipe(&pipe_read, &pipe_write), + "Could not create a pipe to write the stdin of \"" + << cmd.GetShortName() << "\""); + + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read); + stdin_thread = std::thread([pipe_write, ¶m, &io_error]() { + int written = WriteAll(pipe_write, *param.stdin_); + if (written < 0) { + io_error = true; + LOG(ERROR) << "Error in writing stdin to process"; + } + }); + } + if (param.redirect_stdout_) { + SharedFD pipe_read, pipe_write; + CF_EXPECT(SharedFD::Pipe(&pipe_read, &pipe_write), + "Could not create a pipe to read the stdout of \"" + << cmd.GetShortName() << "\""); + + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write); + stdout_thread = std::thread([pipe_read, &output, &io_error]() { + int read = ReadAll(pipe_read, &output.stdout_); + if (read < 0) { + io_error = true; + LOG(ERROR) << "Error in reading stdout from process"; + } + }); + } + if (param.redirect_stderr_ == true) { + SharedFD pipe_read, pipe_write; + CF_EXPECT(SharedFD::Pipe(&pipe_read, &pipe_write), + "Could not create a pipe to read the stderr of \"" + << cmd.GetShortName() << "\""); + + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write); + stderr_thread = std::thread([pipe_read, &output, &io_error]() { + int read = ReadAll(pipe_read, &output.stderr_); + if (read < 0) { + io_error = true; + LOG(ERROR) << "Error in reading stderr from process"; + } + }); + } + + // lower half + auto subprocess = cmd.Start(std::move(param.options_)); + CF_EXPECT(subprocess.Started()); + + auto cmd_short_name = cmd.GetShortName(); + CF_EXPECT(this->Setup(std::move(subprocess))); + { + // Force the destructor to run by moving it into a smaller scope. + // This is necessary to close the write end of the pipe. + Command forceDelete = std::move(cmd); + } + + param.callback_(); + CF_EXPECT(this->Wait()); + for (auto& thread : threads_) { + if (thread->joinable()) { + thread->join(); + } + } + CF_EXPECT(!io_error, + "IO error communicating with " << cmd_short_name); + return output; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/subprocess_waiter.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/subprocess_waiter.h new file mode 100644 index 0000000000..1b16e45c78 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/subprocess_waiter.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 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 + +#include +#include + +#include "common/libs/utils/result.h" +#include "common/libs/utils/subprocess.h" + +namespace cuttlefish { + +struct RunWithManagedIoParam { + Command cmd_; + const bool redirect_stdout_ = false; + const bool redirect_stderr_ = false; + const std::string* stdin_; + std::function(void)> callback_; + SubprocessOptions options_ = SubprocessOptions(); +}; + +struct RunOutput { // a better name please + std::string stdout_; + std::string stderr_; +}; + +class SubprocessWaiter { + public: + SubprocessWaiter() {} + + Result Setup(Subprocess subprocess); + Result Wait(); + Result Interrupt(); + + Result RunWithManagedStdioInterruptable( + RunWithManagedIoParam param); + + private: + std::optional subprocess_; + std::mutex interruptible_; + bool interrupted_ = false; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/try_acloud.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/try_acloud.cpp new file mode 100644 index 0000000000..e5afb008c9 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/try_acloud.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_command/try_acloud.h" + +#include +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/utils/result.h" +#include "cvd_server.pb.h" +#include "host/commands/cvd/acloud/config.h" +#include "host/commands/cvd/acloud/converter.h" +#include "host/commands/cvd/acloud/create_converter_parser.h" +#include "host/commands/cvd/server_command/server_handler.h" +#include "host/commands/cvd/server_command/subprocess_waiter.h" +#include "host/commands/cvd/server_command/utils.h" +#include "host/commands/cvd/types.h" + +#define ENABLE_CVDR_TRANSLATION 1 + +namespace cuttlefish { +namespace { + +constexpr char kCvdrBinName[] = "cvdr"; + +bool CheckIfCvdrExist() { + auto cmd = Command("which").AddParameter(kCvdrBinName); + int ret = RunWithManagedStdio(std::move(cmd), nullptr, nullptr, nullptr, + SubprocessOptions()); + return ret == 0; +} + +} // namespace + +class TryAcloudCommand : public CvdServerHandler { + public: + TryAcloudCommand(const std::atomic& optout) : optout_(optout) {} + ~TryAcloudCommand() = default; + + Result CanHandle(const RequestWithStdio& request) const override { + auto invocation = ParseInvocation(request.Message()); + return invocation.command == "try-acloud"; + } + + cvd_common::Args CmdList() const override { return {"try-acloud"}; } + + /** + * The `try-acloud` command verifies whether an original `acloud CLI` command + * could be satisfied using either: + * + * - `cvd` for local instance management, determined by flag + * `--local-instance`. + * + * - Or `cvdr` for remote instance management (#if ENABLE_CVDR_TRANSLATION). + * + * If the test fails, the command will be handed to the `python acloud CLI`. + * + */ + Result Handle(const RequestWithStdio& request) override { +#if ENABLE_CVDR_TRANSLATION + auto res = VerifyWithCvdRemote(request); + return res.ok() ? res : VerifyWithCvd(request); +#endif + return VerifyWithCvd(request); + } + + Result Interrupt() override { + std::lock_guard interrupt_lock(interrupt_mutex_); + interrupted_ = true; + CF_EXPECT(waiter_.Interrupt()); + return {}; + } + + private: + Result VerifyWithCvd(const RequestWithStdio& request); + Result VerifyWithCvdRemote(const RequestWithStdio& request); + Result RunCvdRemoteGetConfig(const std::string& name); + + std::mutex interrupt_mutex_; + bool interrupted_ = false; + SubprocessWaiter waiter_; + const std::atomic& optout_; +}; + +Result TryAcloudCommand::VerifyWithCvd( + const RequestWithStdio& request) { + std::unique_lock interrupt_lock(interrupt_mutex_); + bool lock_released = false; + CF_EXPECT(!interrupted_, "Interrupted"); + CF_EXPECT(CanHandle(request)); + CF_EXPECT(IsSubOperationSupported(request)); + auto cb_unlock = [&lock_released, &interrupt_lock](void) -> Result { + if (!lock_released) { + interrupt_lock.unlock(); + lock_released = true; + } + return {}; + }; + auto cb_lock = [&lock_released, &interrupt_lock](void) -> Result { + if (lock_released) { + interrupt_lock.lock(); + lock_released = true; + } + return {}; + }; + // ConvertAcloudCreate converts acloud to cvd commands. + // The input parameters waiter_, cb_unlock, cb_lock are.used to + // support interrupt which have locking and unlocking functions + auto converted = CF_EXPECT( + acloud_impl::ConvertAcloudCreate(request, waiter_, cb_unlock, cb_lock)); + if (lock_released) { + interrupt_lock.lock(); + } + // currently, optout/optin feature only works in local instance + // remote instance would continue to be done either through `python acloud` or + // `cvdr` (if enabled). + CF_EXPECT(!optout_); + cvd::Response response; + response.mutable_command_response(); + return response; +} + +Result TryAcloudCommand::VerifyWithCvdRemote( + const RequestWithStdio& request) { + auto filename = CF_EXPECT(GetDefaultConfigFile()); + auto config = CF_EXPECT(LoadAcloudConfig(filename)); + CF_EXPECT(config.use_legacy_acloud == false); + CF_EXPECT(CheckIfCvdrExist()); + auto args = ParseInvocation(request.Message()).arguments; + CF_EXPECT(acloud_impl::CompileFromAcloudToCvdr(args)); + std::string cvdr_service_url = + CF_EXPECT(RunCvdRemoteGetConfig("service_url")); + CF_EXPECT(config.project == "google.com:android-treehugger-developer" && + cvdr_service_url == + "http://android-treehugger-developer.googleplex.com"); + std::string cvdr_zone = CF_EXPECT(RunCvdRemoteGetConfig("zone")); + CF_EXPECT(config.zone == cvdr_zone); + cvd::Response response; + response.mutable_command_response(); + return response; +} + +Result TryAcloudCommand::RunCvdRemoteGetConfig( + const std::string& name) { + Command cmd = Command("cvdr"); + cmd.AddParameter("get_config"); + cmd.AddParameter(name); + std::string stdout_; + SharedFD stdout_pipe_read, stdout_pipe_write; + CF_EXPECT(SharedFD::Pipe(&stdout_pipe_read, &stdout_pipe_write), + "Could not create a pipe"); + cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, stdout_pipe_write); + std::thread stdout_thread([stdout_pipe_read, &stdout_]() { + int read = ReadAll(stdout_pipe_read, &stdout_); + if (read < 0) { + LOG(ERROR) << "Error in reading stdout from process"; + } + }); + { + std::unique_lock interrupt_lock(interrupt_mutex_); + CF_EXPECT(!interrupted_, "Interrupted"); + auto subprocess = cmd.Start(); + CF_EXPECT(subprocess.Started()); + CF_EXPECT(waiter_.Setup(std::move(subprocess))); + } + siginfo_t siginfo = CF_EXPECT(waiter_.Wait()); + { + // Force the destructor to run by moving it into a smaller scope. + // This is necessary to close the write end of the pipe. + Command forceDelete = std::move(cmd); + } + stdout_pipe_write->Close(); + stdout_thread.join(); + CF_EXPECT(siginfo.si_status == EXIT_SUCCESS); + stdout_.erase(stdout_.find('\n')); + return stdout_; +} + +std::unique_ptr NewTryAcloudCommand( + std::atomic& optout) { + return std::unique_ptr(new TryAcloudCommand(optout)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/try_acloud.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/try_acloud.h new file mode 100644 index 0000000000..5e98edbabb --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/try_acloud.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/server_command/acloud_common.h" +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewTryAcloudCommand( + std::atomic& optout); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/utils.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/utils.cpp new file mode 100644 index 0000000000..9e02986467 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/utils.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server_command/utils.h" + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/contains.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/users.h" +#include "host/commands/cvd/instance_manager.h" +#include "host/commands/cvd/server.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { + +CommandInvocation ParseInvocation(const cvd::Request& request) { + CommandInvocation invocation; + if (request.contents_case() != cvd::Request::ContentsCase::kCommandRequest) { + return invocation; + } + if (request.command_request().args_size() == 0) { + return invocation; + } + for (const std::string& arg : request.command_request().args()) { + invocation.arguments.push_back(arg); + } + invocation.arguments[0] = cpp_basename(invocation.arguments[0]); + if (invocation.arguments[0] == "cvd") { + invocation.command = invocation.arguments[1]; + invocation.arguments.erase(invocation.arguments.begin()); + invocation.arguments.erase(invocation.arguments.begin()); + } else { + invocation.command = invocation.arguments[0]; + invocation.arguments.erase(invocation.arguments.begin()); + } + return invocation; +} + +Result VerifyPrecondition(const RequestWithStdio& request) { + CF_EXPECT( + Contains(request.Message().command_request().env(), kAndroidHostOut), + "ANDROID_HOST_OUT in client environment is invalid."); + return {}; +} + +cuttlefish::cvd::Response ResponseFromSiginfo(siginfo_t infop) { + cvd::Response response; + response.mutable_command_response(); // set oneof field + auto& status = *response.mutable_status(); + if (infop.si_code == CLD_EXITED && infop.si_status == 0) { + status.set_code(cvd::Status::OK); + return response; + } + + status.set_code(cvd::Status::INTERNAL); + std::string status_code_str = std::to_string(infop.si_status); + if (infop.si_code == CLD_EXITED) { + status.set_message("Exited with code " + status_code_str); + } else if (infop.si_code == CLD_KILLED) { + status.set_message("Exited with signal " + status_code_str); + } else { + status.set_message("Quit with code " + status_code_str); + } + return response; +} + +Result ConstructCommand(const ConstructCommandParam& param) { + Command command(param.command_name); + command.SetExecutable(param.bin_path); + for (const std::string& arg : param.args) { + command.AddParameter(arg); + } + // Set CuttlefishConfig path based on assembly dir, + // used by subcommands when locating the CuttlefishConfig. + if (param.envs.count(cuttlefish::kCuttlefishConfigEnvVarName) == 0) { + auto config_path = InstanceManager::GetCuttlefishConfigPath(param.home); + if (config_path.ok()) { + command.AddEnvironmentVariable(cuttlefish::kCuttlefishConfigEnvVarName, + *config_path); + } + } + for (auto& it : param.envs) { + command.UnsetFromEnvironment(it.first); + command.AddEnvironmentVariable(it.first, it.second); + } + // Redirect stdin, stdout, stderr back to the cvd client + command.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, param.in); + command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, param.out); + command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, param.err); + + if (!param.working_dir.empty()) { + auto fd = + SharedFD::Open(param.working_dir, O_RDONLY | O_PATH | O_DIRECTORY); + CF_EXPECT(fd->IsOpen(), "Couldn't open \"" << param.working_dir + << "\": " << fd->StrError()); + command.SetWorkingDirectory(fd); + } + return {std::move(command)}; +} + +Result ConstructCvdHelpCommand( + const std::string& bin_file, cvd_common::Envs envs, + const std::vector& subcmd_args, + const RequestWithStdio& request) { + const auto host_artifacts_path = envs.at("ANDROID_HOST_OUT"); + const auto bin_path = host_artifacts_path + "/bin/" + bin_file; + auto client_pwd = request.Message().command_request().working_directory(); + const auto home = (Contains(envs, "HOME") ? envs.at("HOME") : client_pwd); + cvd_common::Envs envs_copy{envs}; + envs_copy["HOME"] = AbsolutePath(home); + envs[kAndroidSoongHostOut] = envs.at(kAndroidHostOut); + ConstructCommandParam construct_cmd_param{.bin_path = bin_path, + .home = home, + .args = subcmd_args, + .envs = std::move(envs_copy), + .working_dir = client_pwd, + .command_name = bin_file, + .in = request.In(), + .out = request.Out(), + .err = request.Err()}; + Command help_command = CF_EXPECT(ConstructCommand(construct_cmd_param)); + return help_command; +} + +Result ConstructCvdGenericNonHelpCommand( + const ConstructNonHelpForm& request_form, const RequestWithStdio& request) { + cvd_common::Envs envs{request_form.envs}; + envs["HOME"] = request_form.home; + envs[kAndroidHostOut] = request_form.android_host_out; + envs[kAndroidSoongHostOut] = request_form.android_host_out; + const auto bin_path = ConcatToString(request_form.android_host_out, "/bin/", + request_form.bin_file); + + if (request_form.verbose) { + std::stringstream verbose_stream; + verbose_stream << "HOME=" << request_form.home << " "; + verbose_stream << kAndroidHostOut << "=" << envs.at(kAndroidHostOut) << " " + << kAndroidSoongHostOut << "=" + << envs.at(kAndroidSoongHostOut) << " "; + verbose_stream << bin_path << "\\" << std::endl; + for (const auto& cmd_arg : request_form.cmd_args) { + verbose_stream << cmd_arg << " "; + } + if (!request_form.cmd_args.empty()) { + // remove trailing " ", and add a new line + verbose_stream.seekp(-1, std::ios_base::end); + verbose_stream << std::endl; + } + WriteAll(request.Err(), verbose_stream.str()); + } + ConstructCommandParam construct_cmd_param{ + .bin_path = bin_path, + .home = request_form.home, + .args = request_form.cmd_args, + .envs = envs, + .working_dir = request.Message().command_request().working_directory(), + .command_name = request_form.bin_file, + .in = request.In(), + .out = request.Out(), + .err = request.Err()}; + return CF_EXPECT(ConstructCommand(construct_cmd_param)); +} + +/* + * From external/gflags/src, commit: + * 061f68cd158fa658ec0b9b2b989ed55764870047 + * + */ +constexpr static std::array help_bool_opts{ + "help", "helpfull", "helpshort", "helppackage", "helpxml", "version"}; +constexpr static std::array help_str_opts{ + "helpon", + "helpmatch", +}; + +Result IsHelpSubcmd(const std::vector& args) { + std::vector copied_args(args); + std::vector flags; + flags.reserve(help_bool_opts.size() + help_str_opts.size()); + bool bool_value_placeholder = false; + std::string str_value_placeholder; + for (const auto bool_opt : help_bool_opts) { + flags.emplace_back(GflagsCompatFlag(bool_opt, bool_value_placeholder)); + } + for (const auto str_opt : help_str_opts) { + flags.emplace_back(GflagsCompatFlag(str_opt, str_value_placeholder)); + } + CF_EXPECT(ConsumeFlags(flags, copied_args)); + // if there was any match, some in copied_args were consumed. + return (args.size() != copied_args.size()); +} + +static constexpr char kTerminalBoldRed[] = "\033[0;1;31m"; +static constexpr char kTerminalCyan[] = "\033[0;36m"; +static constexpr char kTerminalRed[] = "\033[0;31m"; +static constexpr char kTerminalReset[] = "\033[0m"; + +std::string TerminalColor(const bool is_tty, TerminalColors color) { + if (!is_tty) { + return ""; + } + switch (color) { + case TerminalColors::kReset: { + return kTerminalReset; + } + case TerminalColors::kBoldRed: { + return kTerminalBoldRed; + } + case TerminalColors::kCyan: { + return kTerminalCyan; + } + case TerminalColors::kRed: { + return kTerminalRed; + } + default: + return kTerminalReset; + } +} + +Result NoGroupResponse(const RequestWithStdio& request) { + cvd::Response response; + response.mutable_command_response(); + response.mutable_status()->set_code(cvd::Status::OK); + const uid_t uid = CF_EXPECT(request.Credentials()).uid; + const bool is_tty = request.Out()->IsOpen() && request.Out()->IsATTY(); + auto notice = fmt::format( + "Command `{}{}{}` is not applicable:\n {}{}{} (uid: '{}{}{}')", + TerminalColor(is_tty, TerminalColors::kRed), + fmt::join(request.Message().command_request().args(), " "), + TerminalColor(is_tty, TerminalColors::kReset), + TerminalColor(is_tty, TerminalColors::kBoldRed), "no device", + TerminalColor(is_tty, TerminalColors::kReset), + TerminalColor(is_tty, TerminalColors::kCyan), uid, + TerminalColor(is_tty, TerminalColors::kReset)); + CF_EXPECT_EQ(WriteAll(request.Out(), notice + "\n"), notice.size() + 1); + + response.mutable_status()->set_message(notice); + return response; +} + +Result NoTTYResponse(const RequestWithStdio& request) { + cvd::Response response; + response.mutable_command_response(); + response.mutable_status()->set_code(cvd::Status::OK); + const uid_t uid = CF_EXPECT(request.Credentials()).uid; + const bool is_tty = request.Out()->IsOpen() && request.Out()->IsATTY(); + auto notice = fmt::format( + "Command `{}{}{}` is not applicable:\n {}{}{} (uid: '{}{}{}')", + TerminalColor(is_tty, TerminalColors::kRed), + fmt::join(request.Message().command_request().args(), " "), + TerminalColor(is_tty, TerminalColors::kReset), + TerminalColor(is_tty, TerminalColors::kBoldRed), + "No terminal/tty for selecting one of multiple Cuttlefish groups", + TerminalColor(is_tty, TerminalColors::kReset), + TerminalColor(is_tty, TerminalColors::kCyan), uid, + TerminalColor(is_tty, TerminalColors::kReset)); + CF_EXPECT_EQ(WriteAll(request.Out(), notice + "\n"), notice.size() + 1); + response.mutable_status()->set_message(notice); + return response; +} + +Result WriteToFd(SharedFD fd, const std::string& output) { + cvd::Response response; + auto written_size = WriteAll(fd, output); + CF_EXPECT_EQ(output.size(), written_size, fd->StrError()); + response.mutable_command_response(); // Sets oneof member + response.mutable_status()->set_code(cvd::Status::OK); + return response; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/utils.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/utils.h new file mode 100644 index 0000000000..8da9a43505 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/utils.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 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 + +#include + +#include +#include +#include +#include + +#include "cvd_server.pb.h" + +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/result.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/server_client.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +struct CommandInvocation { + std::string command; + std::vector arguments; +}; + +CommandInvocation ParseInvocation(const cvd::Request& request); + +cuttlefish::cvd::Response ResponseFromSiginfo(siginfo_t infop); + +Result VerifyPrecondition(const RequestWithStdio& request); + +struct ConstructCommandParam { + const std::string& bin_path; + const std::string& home; + const std::vector& args; + const cvd_common::Envs& envs; + const std::string& working_dir; + const std::string& command_name; + SharedFD in; + SharedFD out; + SharedFD err; +}; +Result ConstructCommand(const ConstructCommandParam& cmd_param); + +// Constructs a command for cvd whatever --help or --help-related-option +Result ConstructCvdHelpCommand(const std::string& bin_file, + cvd_common::Envs envs, + const cvd_common::Args& _args, + const RequestWithStdio& request); + +// Constructs a command for cvd non-start-op +struct ConstructNonHelpForm { + std::string bin_file; + cvd_common::Envs envs; + cvd_common::Args cmd_args; + std::string android_host_out; + std::string home; + bool verbose; +}; +Result ConstructCvdGenericNonHelpCommand( + const ConstructNonHelpForm& request_form, const RequestWithStdio& request); + +// e.g. cvd start --help, cvd stop --help +Result IsHelpSubcmd(const std::vector& args); + +// Call this when there is no instance group is running +// The function does not verify that. +Result NoGroupResponse(const RequestWithStdio& request); + +// Call this when there is more than one group, which the selector flags are +// not sufficients to choose one from. The function does not verify that. +Result NoTTYResponse(const RequestWithStdio& request); + +enum class TerminalColors : int { + kReset = 0, + kBoldRed = 1, + kCyan = 2, + kRed = 3, +}; + +std::string TerminalColor(const bool is_tty, TerminalColors color); + +Result WriteToFd(SharedFD fd, const std::string& output); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/version.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_command/version.cpp new file mode 100644 index 0000000000..b9d3c4ace3 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/version.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/server.h" + +#include +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/common_utils.h" +#include "host/commands/cvd/server_constants.h" +#include "host/commands/cvd/types.h" +#include "host/libs/config/host_tools_version.h" + +namespace cuttlefish { +namespace { + +class CvdVersionHandler : public CvdServerHandler { + public: + CvdVersionHandler() = default; + + Result CanHandle(const RequestWithStdio& request) const override { + return request.Message().contents_case() == + cvd::Request::ContentsCase::kVersionRequest; + } + + Result Handle(const RequestWithStdio& request) override { + CF_EXPECT(CanHandle(request)); + cvd::Response response; + auto& version = *response.mutable_version_response()->mutable_version(); + version.set_major(cvd::kVersionMajor); + version.set_minor(cvd::kVersionMinor); + version.set_build(android::build::GetBuildNumber()); + version.set_crc32(FileCrc(kServerExecPath)); + response.mutable_status()->set_code(cvd::Status::OK); + return response; + } + + Result Interrupt() override { return CF_ERR("Can't interrupt"); } + + cvd_common::Args CmdList() const override { return {"version"}; } +}; + +} // namespace + +std::unique_ptr NewCvdVersionHandler() { + return std::unique_ptr(new CvdVersionHandler()); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_command/version.h b/base/cvd/cuttlefish/host/commands/cvd/server_command/version.h new file mode 100644 index 0000000000..a28809c472 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_command/version.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 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 + +#include + +#include "host/commands/cvd/server_command/server_handler.h" + +namespace cuttlefish { + +std::unique_ptr NewCvdVersionHandler(); + +} diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_constants.cpp b/base/cvd/cuttlefish/host/commands/cvd/server_constants.cpp new file mode 100644 index 0000000000..7182f09122 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_constants.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/server_constants.h" + +#include + +#include + +namespace cuttlefish { + +std::string ServerSocketPath() { + std::stringstream socket_path; + socket_path << "cvd_server" + << "_" << getuid(); + return socket_path.str(); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/server_constants.h b/base/cvd/cuttlefish/host/commands/cvd/server_constants.h new file mode 100644 index 0000000000..96bd57b759 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/server_constants.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 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 + +#include + +namespace cuttlefish { +namespace cvd { + +// Major version uprevs are backwards incompatible. +// Minor version uprevs are backwards compatible within major version. +constexpr int kVersionMajor = 1; +constexpr int kVersionMinor = 7; + +} // namespace cvd + +// Pathname of the abstract cvd_server socket. +std::string ServerSocketPath(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/types.cpp b/base/cvd/cuttlefish/host/commands/cvd/types.cpp new file mode 100644 index 0000000000..651cb14334 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/types.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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. + */ + +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace cvd_common { + +Args ConvertToArgs( + const google::protobuf::RepeatedPtrField& proto_args) { + Args args; + args.reserve(proto_args.size()); + for (const auto& proto_arg : proto_args) { + args.emplace_back(proto_arg); + } + return args; +} + +Envs ConvertToEnvs( + const google::protobuf::Map& proto_map) { + cvd_common::Envs envs; + for (const auto& entry : proto_map) { + envs[entry.first] = entry.second; + } + return envs; +} + +} // namespace cvd_common +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/types.h b/base/cvd/cuttlefish/host/commands/cvd/types.h new file mode 100644 index 0000000000..8c5c112b43 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/types.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 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 + +#include +#include +#include + +#include + +namespace cuttlefish { +namespace cvd_common { + +using Args = std::vector; +using Envs = std::unordered_map; + +Envs ConvertToEnvs( + const google::protobuf::Map& proto_map); + +Args ConvertToArgs( + const google::protobuf::RepeatedPtrField& proto_args); + +} // namespace cvd_common +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/cf_configs_common_tests.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/cf_configs_common_tests.cpp new file mode 100644 index 0000000000..ce6b5062ee --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/cf_configs_common_tests.cpp @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/parser/cf_configs_common.h" + +#include + +#include +#include + +#include "common/libs/utils/result.h" +#include "common/libs/utils/result_matchers.h" +#include "host/commands/cvd/unittests/parser/test_common.h" + +namespace cuttlefish { + +TEST(CfConfigsCommonTests, ValidateConfigValidationSuccess) { + const char* raw_json = R""""( +{ + "instances" : [ + { + "vm" : { + "memory_mb" : 8192, + "setupwizard_mode" : "OPTIONAL", + "cpus" : 4 + } + } + ] +} + )""""; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + ASSERT_TRUE(json_config.isMember("instances")); + ASSERT_TRUE(json_config["instances"].isValidIndex(0)); + ASSERT_TRUE(json_config["instances"][0].isMember("vm")); + + auto success_validator = std::function(const std::string&)>( + [](const std::string&) -> Result { return {}; }); + auto result = ValidateConfig(json_config["instances"][0], success_validator, + {"vm", "cpus"}); + + EXPECT_THAT(result, IsOk()); +} + +TEST(CfConfigsCommonTests, ValidateConfigValidationFailure) { + const char* raw_json = R""""( +{ + "instances" : [ + { + "vm" : { + "memory_mb" : 8192, + "setupwizard_mode" : "OPTIONAL", + "cpus" : 4 + } + } + ] +} + )""""; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + ASSERT_TRUE(json_config.isMember("instances")); + ASSERT_TRUE(json_config["instances"].isValidIndex(0)); + ASSERT_TRUE(json_config["instances"][0].isMember("vm")); + + auto error_validator = std::function(const std::string&)>( + [](const std::string&) -> Result { return CF_ERR("placeholder"); }); + auto result = ValidateConfig(json_config["instances"][0], error_validator, + {"vm", "cpus"}); + + EXPECT_THAT(result, IsError()); +} + +TEST(CfConfigsCommonTests, ValidateConfigFieldDoesNotExist) { + const char* raw_json = R""""( +{ + "instances" : [ + { + "vm" : { + "memory_mb" : 8192, + "setupwizard_mode" : "OPTIONAL", + "cpus" : 4 + } + } + ] +} + )""""; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + ASSERT_TRUE(json_config.isMember("instances")); + ASSERT_TRUE(json_config["instances"].isValidIndex(0)); + + auto success_validator = std::function(const std::string&)>( + [](const std::string&) -> Result { return {}; }); + auto result = ValidateConfig(json_config["instances"][0], success_validator, + {"disk", "cpus"}); + + EXPECT_THAT(result, IsOk()); +} + +TEST(CfConfigsCommonTests, InitConfigTopLevel) { + const char* raw_json = R""""( +{ + "instances" : [ + { + "@import" : "phone", + "vm" : { + "memory_mb" : 8192, + "setupwizard_mode" : "OPTIONAL", + "cpus" : 4 + }, + "disk" : { + "default_build" : "git_master/cf_x86_64_phone-userdebug", + "download_img_zip" : true + } + } + ], + "wait_retry_period" : 20, + "keep_downloaded_archives" : false +} + )""""; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + EXPECT_FALSE(json_config.isMember("api_key")); + + auto result = + InitConfig(json_config, Json::Value::nullSingleton(), {"api_key"}); + + EXPECT_THAT(result, IsOk()); + EXPECT_TRUE(json_config.isMember("api_key")); + EXPECT_TRUE(json_config["api_key"].isNull()); +} + +TEST(CfConfigsCommonTests, InitConfigInstanceLevel) { + const char* raw_json = R""""( +{ + "instances" : [ + { + "@import" : "phone", + "vm" : { + "memory_mb" : 8192, + "setupwizard_mode" : "OPTIONAL", + "cpus" : 4 + }, + "disk" : { + "default_build" : "git_master/cf_x86_64_phone-userdebug", + "download_img_zip" : true + } + } + ], + "wait_retry_period" : 20, + "keep_downloaded_archives" : false +} + )""""; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + ASSERT_TRUE(json_config.isMember("instances")); + ASSERT_TRUE(json_config["instances"].isValidIndex(0)); + ASSERT_TRUE(json_config["instances"][0].isMember("disk")); + EXPECT_FALSE(json_config["instances"][0]["disk"].isMember( + "download_target_files_zip")); + + auto result = + InitConfig(json_config["instances"][0], Json::Value::nullSingleton(), + {"disk", "download_target_files_zip"}); + + EXPECT_THAT(result, IsOk()); + EXPECT_TRUE(json_config["instances"][0]["disk"].isMember( + "download_target_files_zip")); + EXPECT_TRUE(json_config["instances"][0]["disk"]["download_target_files_zip"] + .isNull()); +} + +TEST(CfConfigsCommonTests, InitConfigInstanceLevelMissingLevel) { + const char* raw_json = R""""( +{ + "instances" : [ + { + "@import" : "phone", + "vm" : { + "memory_mb" : 8192, + "setupwizard_mode" : "OPTIONAL", + "cpus" : 4 + } + } + ], + "wait_retry_period" : 20, + "keep_downloaded_archives" : false +} + )""""; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + ASSERT_TRUE(json_config.isMember("instances")); + ASSERT_TRUE(json_config["instances"].isValidIndex(0)); + EXPECT_FALSE(json_config["instances"][0].isMember("disk")); + + auto result = + InitConfig(json_config["instances"][0], Json::Value::nullSingleton(), + {"disk", "download_target_files_zip"}); + + EXPECT_THAT(result, IsOk()); + ASSERT_TRUE(json_config["instances"][0].isMember("disk")); + EXPECT_TRUE(json_config["instances"][0]["disk"].isMember( + "download_target_files_zip")); + EXPECT_TRUE(json_config["instances"][0]["disk"]["download_target_files_zip"] + .isNull()); +} + +TEST(CfConfigsCommonTests, GenerateGflagSingleInstance) { + const char* raw_json = R""""( +{ + "instances" : [ + { + "@import" : "phone", + "vm" : { + "memory_mb" : 8192, + "setupwizard_mode" : "OPTIONAL", + "cpus" : 4 + } + } + ], + "wait_retry_period" : 20, + "keep_downloaded_archives" : false +} + )""""; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + ASSERT_TRUE(json_config.isMember("instances")); + ASSERT_TRUE(json_config["instances"].isValidIndex(0)); + ASSERT_TRUE(json_config["instances"][0].isMember("vm")); + ASSERT_TRUE(json_config["instances"][0]["vm"].isMember("cpus")); + auto result = GenerateGflag(json_config["instances"], "cpus", {"vm", "cpus"}); + + EXPECT_THAT(result, IsOkAndValue("--cpus=4")); +} + +TEST(CfConfigsCommonTests, GenerateGflagMultiInstance) { + const char* raw_json = R""""( +{ + "instances" : [ + { + "@import" : "phone", + "vm" : { + "memory_mb" : 8192, + "setupwizard_mode" : "OPTIONAL", + "cpus" : 4 + } + }, + { + "@import" : "phone", + "vm" : { + "memory_mb" : 4096, + "setupwizard_mode" : "OPTIONAL", + "cpus" : 2 + } + } + ], + "wait_retry_period" : 20, + "keep_downloaded_archives" : false +} + )""""; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + ASSERT_TRUE(json_config.isMember("instances")); + ASSERT_TRUE(json_config["instances"].isValidIndex(0)); + ASSERT_TRUE(json_config["instances"][0].isMember("vm")); + ASSERT_TRUE(json_config["instances"][0]["vm"].isMember("cpus")); + ASSERT_TRUE(json_config["instances"].isValidIndex(1)); + ASSERT_TRUE(json_config["instances"][1].isMember("vm")); + ASSERT_TRUE(json_config["instances"][1]["vm"].isMember("cpus")); + auto result = GenerateGflag(json_config["instances"], "cpus", {"vm", "cpus"}); + + EXPECT_THAT(result, IsOkAndValue("--cpus=4,2")); +} + +TEST(CfConfigsCommonTests, GenerateGflagMissingValue) { + const char* raw_json = R""""( +{ + "instances" : [ + { + "@import" : "phone", + "vm" : { + "memory_mb" : 8192, + "cpus" : 4 + } + } + ], + "wait_retry_period" : 20, + "keep_downloaded_archives" : false +} + )""""; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + ASSERT_TRUE(json_config.isMember("instances")); + ASSERT_TRUE(json_config["instances"].isValidIndex(0)); + ASSERT_TRUE(json_config["instances"][0].isMember("vm")); + auto result = GenerateGflag(json_config["instances"], "setupwizard_mode", + {"vm", "setupwizard_mode"}); + + EXPECT_THAT(result, IsError()); +} + +TEST(ValidateTests, ValidateArrayTypeSuccess) { + const char* raw_json = R""""( + [ + "value1", + "value2", + "value3" + ] + )""""; + const auto validation_definition = + ConfigNode{.type = Json::ValueType::arrayValue, + .children = { + {kArrayValidationSentinel, + ConfigNode{.type = Json::ValueType::stringValue}}, + }}; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + auto result = Validate(json_config, validation_definition); + EXPECT_THAT(result, IsOk()); +} + +TEST(ValidateTests, ValidateArrayTypeFailure) { + const char* raw_json = R""""( + [ + "value1", + "value2", + "value3" + ] + )""""; + const auto validation_definition = + ConfigNode{.type = Json::ValueType::arrayValue, + .children = { + {"foo", ConfigNode{.type = Json::ValueType::stringValue}}, + }}; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + auto result = Validate(json_config, validation_definition); + EXPECT_THAT(result, IsError()); +} + +TEST(ValidateTests, ValidateObjectTypeSuccess) { + const char* raw_json = R""""( + { + "key" : "value", + "key2" : 1234, + "key3" : { + "key4" : true + } + } + )""""; + const auto validation_definition = ConfigNode{ + .type = Json::ValueType::objectValue, + .children = { + {"key", ConfigNode{.type = Json::ValueType::stringValue}}, + {"key2", ConfigNode{.type = Json::ValueType::uintValue}}, + {"key3", + ConfigNode{ + .type = Json::ValueType::objectValue, + .children = + { + {"key4", + ConfigNode{.type = Json::ValueType::booleanValue}}, + }}}, + }}; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + auto result = Validate(json_config, validation_definition); + EXPECT_THAT(result, IsOk()); +} + +TEST(ValidateTests, ValidateObjectTypeFailure) { + const char* raw_json = R""""( + { + "key" : "value", + "key2" : 1234, + "key3" : { + "key4" : true + } + } + )""""; + const auto validation_definition = ConfigNode{ + .type = Json::ValueType::objectValue, + .children = { + {"key", ConfigNode{.type = Json::ValueType::booleanValue}}, + {"key2", ConfigNode{.type = Json::ValueType::uintValue}}, + {"key3", + ConfigNode{ + .type = Json::ValueType::objectValue, + .children = + { + {"key4", + ConfigNode{.type = Json::ValueType::stringValue}}, + }}}, + }}; + + Json::Value json_config; + std::string json_text(raw_json); + ASSERT_TRUE(ParseJsonString(json_text, json_config)) + << "Invalid JSON string for test"; + + auto result = Validate(json_config, validation_definition); + EXPECT_THAT(result, IsError()); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/configs_inheritance_test.cc b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/configs_inheritance_test.cc new file mode 100644 index 0000000000..979929ff51 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/configs_inheritance_test.cc @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023 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. + */ + +#include +#include +#include + +#include + +#include + +#include "host/commands/cvd/parser/cf_configs_common.h" +#include "host/commands/cvd/unittests/parser/test_common.h" + +namespace cuttlefish { + +TEST(FlagsInheritanceTest, MergeTwoIndependentJson) { + const char* dst_string = R""""( +{ + "instances" : + [ + { + "vm": { + "memory_mb": 2048 + } + } + ] +} + )""""; + + const char* src_string = R""""( +{ + "instances" : + [ + { + "graphics":{ + "displays":[ + { + "width": 720, + "height": 1280, + "dpi": 320 + } + ] + } + } + ] +} + )""""; + + Json::Value src_object, dst_object; + std::string src_text(src_string); + std::string dst_text(dst_string); + EXPECT_TRUE(ParseJsonString(dst_text, dst_object)) << "Invalid Json string"; + EXPECT_TRUE(ParseJsonString(src_text, src_object)) << "Invalid Json string"; + + cuttlefish::MergeTwoJsonObjs(dst_object, src_object); + EXPECT_TRUE(dst_object["instances"][0].isMember("graphics")); + EXPECT_TRUE(dst_object["instances"][0]["graphics"].isMember("displays")); + EXPECT_TRUE( + dst_object["instances"][0]["graphics"]["displays"][0].isMember("width")); + EXPECT_TRUE( + dst_object["instances"][0]["graphics"]["displays"][0].isMember("height")); + EXPECT_TRUE( + dst_object["instances"][0]["graphics"]["displays"][0].isMember("dpi")); + + EXPECT_EQ(dst_object["instances"][0]["graphics"]["displays"][0]["width"], + 720); + EXPECT_EQ(dst_object["instances"][0]["graphics"]["displays"][0]["height"], + 1280); + EXPECT_EQ(dst_object["instances"][0]["graphics"]["displays"][0]["dpi"], 320); +} + +TEST(FlagsInheritanceTest, MergeTwoOverlappedJson) { + const char* dst_string = R""""( +{ + "instances" : + [ + { + "vm": { + "memory_mb": 1024 + } + } + ] +} + )""""; + + const char* src_string = R""""( +{ + "instances" : + [ + { + "vm": { + "memory_mb": 2048 + }, + "graphics":{ + "displays":[ + { + "width": 720, + "height": 1280, + "dpi": 320 + } + ] + } + } + ] +} + )""""; + + Json::Value src_object, dst_object; + std::string src_text(src_string); + std::string dst_text(dst_string); + EXPECT_TRUE(ParseJsonString(dst_text, dst_object)) << "Invalid Json string"; + EXPECT_TRUE(ParseJsonString(src_text, src_object)) << "Invalid Json string"; + + cuttlefish::MergeTwoJsonObjs(dst_object, src_object); + EXPECT_TRUE(dst_object["instances"][0].isMember("graphics")); + EXPECT_TRUE(dst_object["instances"][0]["graphics"].isMember("displays")); + EXPECT_TRUE( + dst_object["instances"][0]["graphics"]["displays"][0].isMember("width")); + EXPECT_TRUE( + dst_object["instances"][0]["graphics"]["displays"][0].isMember("height")); + EXPECT_TRUE( + dst_object["instances"][0]["graphics"]["displays"][0].isMember("dpi")); + + EXPECT_EQ(dst_object["instances"][0]["graphics"]["displays"][0]["width"], + 720); + EXPECT_EQ(dst_object["instances"][0]["graphics"]["displays"][0]["height"], + 1280); + EXPECT_EQ(dst_object["instances"][0]["graphics"]["displays"][0]["dpi"], 320); + // Check for overlapped values + EXPECT_EQ(dst_object["instances"][0]["vm"]["memory_mb"], 2048); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/fetch_cvd_parser_tests.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/fetch_cvd_parser_tests.cpp new file mode 100644 index 0000000000..2201007e63 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/fetch_cvd_parser_tests.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/parser/fetch_cvd_parser.h" + +#include +#include + +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "common/libs/utils/result_matchers.h" +#include "host/commands/cvd/parser/cf_flags_validator.h" +#include "host/commands/cvd/unittests/parser/test_common.h" + +using ::testing::Contains; +using ::testing::Eq; +using ::testing::Not; + +namespace cuttlefish { +namespace { + +Json::Value GetTestJson(const char* raw_json) { + Json::Value json_config; + std::string json_text(raw_json); + ParseJsonString(json_text, json_config); + return json_config; +} + +Result> FetchCvdParserTestHelper( + Json::Value& root, const std::string& target_directory, + const std::vector& target_subdirectories) { + CF_EXPECT(ValidateCfConfigs(root), "Loaded Json validation failed"); + return CF_EXPECT( + ParseFetchCvdConfigs(root, target_directory, target_subdirectories)); +} + +} // namespace + +TEST(FetchCvdParserTests, SingleFetch) { + const char* raw_json = R""""( +{ + "common" : { + "host_package" : "@ab/git_master/cf_x86_64_phone-userdebug" + }, + "instances" : [ + { + "@import" : "phone", + "vm" : { + "memory_mb" : 8192, + "setupwizard_mode" : "OPTIONAL", + "cpus" : 4 + }, + "disk" : { + "default_build" : "@ab/git_master/cf_x86_64_phone-userdebug", + "download_img_zip" : true, + "otatools" : "@ab/git_master/cf_x86_64_phone-userdebug" + }, + "boot" : { + "build" : "@ab/git_master/cf_x86_64_phone-userdebug", + "kernel" : { + "build" : "@ab/git_master/cf_x86_64_phone-userdebug" + }, + "bootloader" : { + "build" : "@ab/git_master/cf_x86_64_phone-userdebug" + } + } + } + ], + "fetch":{ + "wait_retry_period" : 20, + "keep_downloaded_archives" : false + } +} + )""""; + Json::Value json_config = GetTestJson(raw_json); + + auto result_flags = FetchCvdParserTestHelper(json_config, "/target", {"0"}); + ASSERT_THAT(result_flags, IsOk()); + + const auto flags = result_flags.value(); + EXPECT_THAT(flags, Contains("--wait_retry_period=20")); + EXPECT_THAT(flags, Contains("--keep_downloaded_archives=false")); + EXPECT_THAT(flags, Contains("--target_directory=/target")); + EXPECT_THAT(flags, Contains("--target_subdirectory=0")); + EXPECT_THAT(flags, + Contains("--default_build=git_master/cf_x86_64_phone-userdebug")); + EXPECT_THAT(flags, Contains("--download_img_zip=true")); + EXPECT_THAT( + flags, Contains("--otatools_build=git_master/cf_x86_64_phone-userdebug")); + EXPECT_THAT( + flags, + Contains("--host_package_build=git_master/cf_x86_64_phone-userdebug")); + EXPECT_THAT(flags, + Contains("--boot_build=git_master/cf_x86_64_phone-userdebug")); + EXPECT_THAT(flags, + Contains("--kernel_build=git_master/cf_x86_64_phone-userdebug")); + EXPECT_THAT( + flags, + Contains("--bootloader_build=git_master/cf_x86_64_phone-userdebug")); +} + +TEST(FetchCvdParserTests, SingleFetchNoPrefix) { + const char* raw_json = R""""( +{ + "instances" : [ + { + "@import" : "phone", + "disk" : { + "default_build" : "git_master/cf_x86_64_phone-userdebug" + "otatools" : "git_master/cf_x86_64_phone-userdebug" + }, + "boot" : { + "build" : "git_master/cf_x86_64_phone-userdebug", + "kernel" : { + "build" : "git_master/cf_x86_64_phone-userdebug" + }, + "bootloader" : { + "build" : "git_master/cf_x86_64_phone-userdebug" + } + } + } + ] +} + )""""; + Json::Value json_config = GetTestJson(raw_json); + + auto result_flags = FetchCvdParserTestHelper(json_config, "/target", {"0"}); + ASSERT_THAT(result_flags, IsOk()); +} + +TEST(FetchCvdParserTests, MultiFetch) { + const char* raw_json = R""""( +{ + "common" : { + "host_package" : "@ab/git_master/cf_x86_64_phone-userdebug" + }, + "instances" : [ + { + "@import" : "phone", + "vm" : { + "memory_mb" : 8192, + "setupwizard_mode" : "OPTIONAL", + "cpus" : 4 + }, + "disk" : { + "default_build" : "@ab/git_master/cf_x86_64_phone-userdebug", + "download_img_zip" : true, + "otatools" : "@ab/git_master/cf_x86_64_phone-userdebug" + }, + "boot" : { + "build" : "@ab/git_master/cf_x86_64_phone-userdebug", + "kernel" : { + "build" : "@ab/git_master/cf_x86_64_phone-userdebug" + }, + "bootloader" : { + "build" : "@ab/git_master/cf_x86_64_phone-userdebug" + } + } + }, + { + "@import" : "wearable", + "vm" : { + "memory_mb" : 8192, + "setupwizard_mode" : "REQUIRED", + "cpus" : 4 + }, + "disk" : { + "default_build" : "@ab/git_master/cf_gwear_x86-userdebug", + "download_img_zip" : true + } + } + ], + "fetch":{ + "wait_retry_period" : 20, + "keep_downloaded_archives" : false + } +} + )""""; + Json::Value json_config = GetTestJson(raw_json); + + auto result_flags = + FetchCvdParserTestHelper(json_config, "/target", {"0", "1"}); + ASSERT_THAT(result_flags, IsOk()); + + const auto flags = result_flags.value(); + EXPECT_THAT(flags, Contains("--wait_retry_period=20")); + EXPECT_THAT(flags, Contains("--keep_downloaded_archives=false")); + EXPECT_THAT(flags, Contains("--target_directory=/target")); + EXPECT_THAT(flags, Contains("--target_subdirectory=0,1")); + EXPECT_THAT( + flags, + Contains("--default_build=git_master/" + "cf_x86_64_phone-userdebug,git_master/cf_gwear_x86-userdebug")); + EXPECT_THAT(flags, Contains("--download_img_zip=true,true")); + EXPECT_THAT( + flags, + Contains("--otatools_build=git_master/cf_x86_64_phone-userdebug,")); + EXPECT_THAT( + flags, + Contains("--host_package_build=git_master/cf_x86_64_phone-userdebug")); + EXPECT_THAT(flags, + Contains("--boot_build=git_master/cf_x86_64_phone-userdebug,")); + EXPECT_THAT(flags, + Contains("--kernel_build=git_master/cf_x86_64_phone-userdebug,")); + EXPECT_THAT( + flags, + Contains("--bootloader_build=git_master/cf_x86_64_phone-userdebug,")); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/flags_parser_test.cc b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/flags_parser_test.cc new file mode 100644 index 0000000000..deda50be45 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/flags_parser_test.cc @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2022 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. + */ + +#include +#include +#include + +#include +#include + +#include "host/commands/cvd/parser/launch_cvd_parser.h" +#include "host/commands/cvd/unittests/parser/test_common.h" +namespace cuttlefish { +TEST(FlagsParserTest, ParseInvalidJson) { + const char* test_string = R""""( + instances=50; + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_FALSE(ParseJsonString(json_text, json_configs)); +} + +TEST(FlagsParserTest, ParseJsonWithSpellingError) { + const char* test_string = R""""( +{ + "Insta" : + [ + { + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_FALSE(serialized_data.ok()); +} + +TEST(FlagsParserTest, ParseBasicJsonSingleInstances) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, "--num_instances=1")) + << "num_instances flag is missing or wrongly formatted"; +} + +TEST(FlagsParserTest, ParseBasicJsonTwoInstances) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, "--num_instances=2")) + << "num_instances flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseNetSimFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, R"(--netsim_bt=true)")) + << "netsim_bt flag is missing or wrongly formatted"; + EXPECT_TRUE(FindConfig(*serialized_data, R"(--netsim_uwb=false)")) + << "netsim_uwb flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseNetSimFlagEnabled) { + const char* test_string = R""""( +{ + "netsim_bt": false, + "netsim_uwb": true, + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, R"(--netsim_bt=false)")) + << "netsim_bt flag is missing or wrongly formatted"; + EXPECT_TRUE(FindConfig(*serialized_data, R"(--netsim_uwb=true)")) + << "netsim_uwb flag is missing or wrongly formatted"; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/boot_configs_test.cc b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/boot_configs_test.cc new file mode 100644 index 0000000000..eeee17e224 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/boot_configs_test.cc @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2015-2022 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. + */ + +#include +#include "host/commands/cvd/parser/launch_cvd_parser.h" +#include "host/commands/cvd/unittests/parser/test_common.h" + +namespace cuttlefish { + +TEST(BootFlagsParserTest, ParseTwoInstancesBootAnimationFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--enable_bootanimation=true,true)")) + << "enable_bootanimation flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesBootAnimationFlagPartialJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + }, + "boot": { + } + }, + { + "vm": { + "crosvm":{ + } + }, + "boot": { + "enable_bootanimation": false + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--enable_bootanimation=true,false)")) + << "enable_bootanimation flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesBootAnimationFlagFullJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + }, + "boot": { + "enable_bootanimation": false + } + }, + { + "vm": { + "crosvm":{ + } + }, + "boot": { + "enable_bootanimation": false + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--enable_bootanimation=false,false)")) + << "enable_bootanimation flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesSerialNumberFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, + R"(--serial_number=CUTTLEFISHCVD01,CUTTLEFISHCVD01)")) + << "serial_number flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesSerialNumberFlagPartialJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + }, + "security": { + } + }, + { + "vm": { + "crosvm":{ + } + }, + "security": { + "serial_number": "CUTTLEFISHCVD101" + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, + R"(--serial_number=CUTTLEFISHCVD01,CUTTLEFISHCVD101)")) + << "serial_number flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesSerialNumberFlagFullJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + }, + "security": { + "serial_number": "CUTTLEFISHCVD101" + } + }, + { + "vm": { + "crosvm":{ + } + }, + "security": { + "serial_number": "CUTTLEFISHCVD102" + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig( + *serialized_data, R"(--serial_number=CUTTLEFISHCVD101,CUTTLEFISHCVD102)")) + << "serial_number flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesEnforceSecurityFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--guest_enforce_security=true,true)")) + << "guest_enforce_security flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesEnforceSecurityFlagPartialJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + }, + "security": { + } + }, + { + "vm": { + "crosvm":{ + } + }, + "security": { + "guest_enforce_security": false + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--guest_enforce_security=true,false)")) + << "guest_enforce_security flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesEnforceSecurityFlagFullJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + }, + "security": { + "guest_enforce_security": false + } + }, + { + "vm": { + "crosvm":{ + } + }, + "security": { + "guest_enforce_security": false + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--guest_enforce_security=false,false)")) + << "guest_enforce_security flag is missing or wrongly formatted"; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/disk_configs_test.cc b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/disk_configs_test.cc new file mode 100644 index 0000000000..3bcc3003e8 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/disk_configs_test.cc @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2023 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. + */ + +#include +#include "host/commands/cvd/parser/launch_cvd_parser.h" +#include "host/commands/cvd/unittests/parser/test_common.h" + +namespace cuttlefish { + +TEST(BootFlagsParserTest, ParseTwoInstancesBlankDataImageEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + }, + { + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--blank_data_image_mb=unset,unset)")) + << "blank_data_image_mb flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesBlankDataImagePartialJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "disk": { + } + }, + { + "disk": { + "blank_data_image_mb": 2048 + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--blank_data_image_mb=unset,2048)")) + << "blank_data_image_mb flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesBlankDataImageFullJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "disk": { + "blank_data_image_mb": 2048 + } + }, + { + "disk": { + "blank_data_image_mb": 4096 + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--blank_data_image_mb=2048,4096)")) + << "blank_data_image_mb flag is missing or wrongly formatted"; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/graphics_configs_test.cc b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/graphics_configs_test.cc new file mode 100644 index 0000000000..57956fefc0 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/graphics_configs_test.cc @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2015-2022 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. + */ + +#include +#include "host/commands/cvd/parser/launch_cvd_parser.h" +#include "host/commands/cvd/unittests/parser/test_common.h" + +namespace cuttlefish { + +TEST(BootFlagsParserTest, ParseTwoInstancesDisplaysFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + }, + { + } + ] +} +)""""; + + const char* expected_string = + R""""(--displays_binproto=Cg0KCwjQBRCAChjAAiA8Cg0KCwjQBRCAChjAAiA8)""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, expected_string)) + << "extra_bootconfig_args flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesDisplaysFlagEmptyGraphics) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "graphics": { + } + }, + { + "graphics": { + } + } + ] +} + )""""; + + const char* expected_string = + R""""(--displays_binproto=Cg0KCwjQBRCAChjAAiA8Cg0KCwjQBRCAChjAAiA8)""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, expected_string)) + << "extra_bootconfig_args flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesDisplaysFlagEmptyDisplays) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "graphics":{ + "displays":[ + { + } + ] + } + }, + { + "graphics":{ + "displays":[ + { + }, + { + } + ] + } + } + ] +} +)""""; + + const char* expected_string = + R""""(--displays_binproto=Cg0KCwjQBRCAChjAAiA8ChoKCwjQBRCAChjAAiA8CgsI0AUQgAoYwAIgPA==)""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, expected_string)) + << "extra_bootconfig_args flag is missing or wrongly formatted"; +} + +TEST(BootFlagsParserTest, ParseTwoInstancesAutoTabletDisplaysFlag) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "graphics":{ + "displays":[ + { + "width": 1080, + "height": 600, + "dpi": 120, + "refresh_rate_hertz": 60 + }, + { + "width": 400, + "height": 600, + "dpi": 120, + "refresh_rate_hertz": 60 + } + ] + } + }, + { + "graphics":{ + "displays":[ + { + "width": 2560, + "height": 1800, + "dpi": 320, + "refresh_rate_hertz": 60 + } + ] + } + } + ] +} + )""""; + + const char* expected_string = + R""""(--displays_binproto=ChgKCgi4CBDYBBh4IDwKCgiQAxDYBBh4IDwKDQoLCIAUEIgOGMACIDw=)""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, expected_string)) + << "extra_bootconfig_args flag is missing or wrongly formatted"; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/vm_configs_test.cc b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/vm_configs_test.cc new file mode 100644 index 0000000000..a26ce54548 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/vm_configs_test.cc @@ -0,0 +1,761 @@ +/* + * Copyright (C) 2022 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. + */ + +#include +#include +#include + +#include + +#include + +#include "host/commands/cvd/parser/launch_cvd_parser.h" +#include "host/commands/cvd/unittests/parser/test_common.h" + +namespace cuttlefish { + +TEST(VmFlagsParserTest, ParseTwoInstancesCpuFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, "--cpus=2,2")) + << "cpus flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesCpuFlagPartialJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + }, + "cpus": 4 + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, "--cpus=2,4")) + << "cpus flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesCpuFlagFullJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + }, + "cpus": 4 + } + }, + { + "vm": { + "crosvm":{ + }, + "cpus": 6 + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, "--cpus=4,6")) + << "cpus flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesMemoryFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, "--memory_mb=2048,2048")) + << "memory_mb flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesMemoryFlagPartialJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + }, + "memory_mb": 4096 + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, "--memory_mb=2048,4096")) + << "memory_mb flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesMemoryFlagFullJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + }, + "memory_mb": 4096 + } + }, + { + "vm": { + "crosvm":{ + }, + "memory_mb": 8192 + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, "--memory_mb=4096,8192")) + << "memory_mb flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesSdCardFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + }, + { + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, "--use_sdcard=true,true")) + << "use_sdcard flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesSdCardFlagPartialJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + }, + { + "vm": { + "use_sdcard": false + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, "--use_sdcard=true,false")) + << "use_sdcard flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesSdCardFlagFullJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "use_sdcard": false + } + }, + { + "vm": { + "use_sdcard": false + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, "--use_sdcard=false,false")) + << "use_sdcard flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesVmManagerFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, R"(--vm_manager=crosvm,crosvm)")) + << "vm_manager flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesVmManagerFlagDefault) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + } + }, + { + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, R"(--vm_manager=crosvm,crosvm)")) + << "vm_manager flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseOneInstanceSetupWizardInvalidValue) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + }, + "setupwizard_mode": "ENABLED" + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)); + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_FALSE(serialized_data.ok()); +} + +TEST(VmFlagsParserTest, ParseTwoInstancesSetupWizardFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--setupwizard_mode=DISABLED,DISABLED)")) + << "setupwizard_mode flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesSetupWizardFlagPartialJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + }, + "setupwizard_mode": "REQUIRED" + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--setupwizard_mode=DISABLED,REQUIRED)")) + << "setupwizard_mode flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesSetupWizardFlagFullJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + }, + "setupwizard_mode": "OPTIONAL" + } + }, + { + "vm": { + "crosvm":{ + }, + "setupwizard_mode": "REQUIRED" + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfig(*serialized_data, R"(--setupwizard_mode=OPTIONAL,REQUIRED)")) + << "setupwizard_mode flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesUuidFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig( + *serialized_data, + R"(--uuid=699acfc4-c8c4-11e7-882b-5065f31dc101,699acfc4-c8c4-11e7-882b-5065f31dc101)")) + << "uuid flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesUuidFlagPartialJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + }, + "uuid": "870acfc4-c8c4-11e7-99ac-5065f31dc250" + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig( + *serialized_data, + R"(--uuid=699acfc4-c8c4-11e7-882b-5065f31dc101,870acfc4-c8c4-11e7-99ac-5065f31dc250)")) + << "uuid flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesUuidFlagFullJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + }, + "uuid": "870acfc4-c8c4-11e7-99ac-5065f31dc250" + } + }, + { + "vm": { + "crosvm":{ + }, + "uuid": "870acfc4-c8c4-11e7-99ac-5065f31dc251" + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig( + *serialized_data, + R"(--uuid=870acfc4-c8c4-11e7-99ac-5065f31dc250,870acfc4-c8c4-11e7-99ac-5065f31dc251)")) + << "uuid flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesSandboxFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, R"(--enable_sandbox=false,false)")) + << "enable_sandbox flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesSandboxFlagPartialJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + } + } + }, + { + "vm": { + "crosvm":{ + "enable_sandbox": true + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, R"(--enable_sandbox=false,true)")) + << "enable_sandbox flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesSandboxFlagFullJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + "vm": { + "crosvm":{ + "enable_sandbox": true + } + } + }, + { + "vm": { + "crosvm":{ + "enable_sandbox": true + } + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, R"(--enable_sandbox=true,true)")) + << "enable_sandbox flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesCustomActionsFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE(FindConfig(*serialized_data, R"(--custom_actions=unset)")) + << "custom_actions flag is missing or wrongly formatted"; +} + +TEST(VmFlagsParserTest, ParseTwoInstancesCustomActionsFlagPartialJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + }, + { + "vm": { + "custom_actions" : [ + { + "device_states": [ + { + "lid_switch_open": false, + "hinge_angle_value": 0 + } + ] + } + ] + } + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + std::string expected_custom_actions = + R"""(--custom_actions=[{\"device_states\":[{\"hinge_angle_value\":0,\"lid_switch_open\":false}]}])"""; + + EXPECT_TRUE(ParseJsonString(json_text, json_configs)) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_TRUE(serialized_data.ok()) << serialized_data.error().Trace(); + EXPECT_TRUE( + FindConfigIgnoreSpaces(*serialized_data, R"(--custom_actions=unset)")) + << "custom_actions flag is missing or wrongly formatted"; + + EXPECT_TRUE(FindConfigIgnoreSpaces(*serialized_data, expected_custom_actions)) + << "custom_actions flag is missing or wrongly formatted"; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/metrics_configs_test.cc b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/metrics_configs_test.cc new file mode 100644 index 0000000000..56aedb87b7 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/metrics_configs_test.cc @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2015-2022 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. + */ + +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "common/libs/utils/result_matchers.h" +#include "host/commands/cvd/parser/launch_cvd_parser.h" +#include "host/commands/cvd/unittests/parser/test_common.h" + +namespace cuttlefish { + +using ::testing::IsTrue; +using ::testing::Not; + +TEST(MetricsFlagsParserTest, ParseOneInstanceMetricsReportValidValue) { + const char* test_string = R""""( +{ + "instances" : + [ + { + } + ], + "metrics": { + "enable": true + } +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_THAT(ParseJsonString(json_text, json_configs), IsTrue()) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_THAT(serialized_data, IsOk()) << serialized_data.error().Trace(); +} + +TEST(MetricsFlagsParserTest, ParseOneInstanceMetricsReportInvalidValue) { + const char* test_string = R""""( +{ + "instances" : + [ + { + } + ], + "metrics": { + "enable": "foo" + } +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_THAT(ParseJsonString(json_text, json_configs), IsTrue()) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_THAT(serialized_data, Not(IsOk())); +} + +TEST(MetricsFlagsParserTest, ParseOneInstancesMetricsReportFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_THAT(ParseJsonString(json_text, json_configs), IsTrue()) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_THAT(serialized_data, IsOk()) << serialized_data.error().Trace(); + EXPECT_THAT( + FindConfig(*serialized_data, R"(--report_anonymous_usage_stats=n)"), + IsTrue()) + << "report_anonymous_usage_stats flag is missing or wrongly formatted"; +} + +TEST(MetricsFlagsParserTest, ParseTwoInstancesMetricsReportFlagEmptyJson) { + const char* test_string = R""""( +{ + "instances" : + [ + { + }, + { + } + ] +} + )""""; + + Json::Value json_configs; + std::string json_text(test_string); + + EXPECT_THAT(ParseJsonString(json_text, json_configs), IsTrue()) + << "Invalid Json string"; + auto serialized_data = LaunchCvdParserTester(json_configs); + EXPECT_THAT(serialized_data, IsOk()) << serialized_data.error().Trace(); + EXPECT_THAT( + FindConfig(*serialized_data, R"(--report_anonymous_usage_stats=n)"), + IsTrue()) + << "report_anonymous_usage_stats flag is missing or wrongly formatted"; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/test_common.cc b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/test_common.cc new file mode 100644 index 0000000000..c7026031b2 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/test_common.cc @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 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. + */ + +#include + +#include +#include + +#include "host/commands/cvd/parser/cf_flags_validator.h" +#include "host/commands/cvd/parser/launch_cvd_parser.h" +#include "host/commands/cvd/unittests/parser/test_common.h" + +namespace cuttlefish { + +bool ParseJsonString(std::string& json_text, Json::Value& root) { + Json::Reader reader; // Reader + return reader.parse(json_text, root); +} + +bool FindConfig(const std::vector& vec, + const std::string& element) { + auto it = find(vec.begin(), vec.end(), element); + return it != vec.end(); +} +bool FindConfigIgnoreSpaces(const std::vector& vec, + const std::string& str) { + std::string target = str; + target.erase(std::remove(target.begin(), target.end(), ' '), target.end()); + target.erase(std::remove(target.begin(), target.end(), '\t'), target.end()); + + for (const auto& s : vec) { + std::string current = s; + current.erase(std::remove(current.begin(), current.end(), ' '), + current.end()); + current.erase(std::remove(current.begin(), current.end(), '\t'), + current.end()); + if (current == target) { + return true; + } + } + return false; +} + +Result> LaunchCvdParserTester(Json::Value& root) { + CF_EXPECT(ValidateCfConfigs(root), "Loaded Json validation failed"); + return ParseLaunchCvdConfigs(root); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/test_common.h b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/test_common.h new file mode 100644 index 0000000000..2d1dcc0a28 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/parser/test_common.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 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. + */ + +#include + +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +bool ParseJsonString(std::string& json_text, Json::Value& root); + +bool FindConfig(const std::vector& vec, + const std::string& element); + +bool FindConfigIgnoreSpaces(const std::vector& vec, + const std::string& str); +Result> LaunchCvdParserTester(Json::Value& root); + +} // namespace cuttlefish \ No newline at end of file diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_helper.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_helper.cpp new file mode 100644 index 0000000000..2fbce42d85 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_helper.cpp @@ -0,0 +1,31 @@ +// +// Copyright (C) 2022 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. + +#include "host/commands/cvd/unittests/selector/client_lexer_helper.h" + +namespace cuttlefish { +namespace selector { + +LexerTestBase::LexerTestBase() { Init(); } + +void LexerTestBase::Init() { + auto param = GetParam(); + known_flags_ = param.known_flags_; + lex_input_ = param.lex_input_; + expected_tokens_ = param.expected_tokens_; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_helper.h b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_helper.h new file mode 100644 index 0000000000..de1800de16 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_helper.h @@ -0,0 +1,55 @@ +// +// Copyright (C) 2022 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 + +#include +#include +#include + +#include + +#include "host/commands/cvd/selector/arguments_lexer.h" + +namespace cuttlefish { +namespace selector { + +using Tokens = std::vector; + +struct LexerInputOutput { + LexerFlagsSpecification known_flags_; + std::string lex_input_; + std::optional expected_tokens_; +}; + +class LexerTestBase : public testing::TestWithParam { + protected: + LexerTestBase(); + void Init(); + + LexerFlagsSpecification known_flags_; + std::string lex_input_; + std::optional expected_tokens_; +}; + +class EmptyArgsLexTest : public LexerTestBase {}; +class NonBooleanArgsTest : public LexerTestBase {}; +class BooleanArgsTest : public LexerTestBase {}; +class BothArgsTest : public LexerTestBase {}; + +class BooleanBadArgsTest : public LexerTestBase {}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_test.cpp new file mode 100644 index 0000000000..37472c34f6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_test.cpp @@ -0,0 +1,217 @@ +// +// Copyright (C) 2022 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. + +#include + +#include +#include + +#include "common/libs/utils/result.h" +#include "common/libs/utils/result_matchers.h" +#include "host/commands/cvd/selector/arguments_lexer.h" +#include "host/commands/cvd/unittests/selector/client_lexer_helper.h" + +namespace cuttlefish { +namespace selector { +namespace { + +const LexerFlagsSpecification empty_known_flags; +const LexerFlagsSpecification boolean_known_flags{ + .known_boolean_flags = {"clean"}}; +const LexerFlagsSpecification non_boolean_known_flags{ + .known_value_flags = {"group_name"}}; +const LexerFlagsSpecification both_known_flags{ + .known_boolean_flags = {"clean"}, .known_value_flags = {"group_name"}}; + +Result> Tokenize(const ArgumentsLexer& lexer, + const std::string& args) { + auto args_vec = android::base::Tokenize(args, " "); + return CF_EXPECT(lexer.Tokenize(args_vec)); +} + +} // namespace + +TEST_P(EmptyArgsLexTest, SuccessExpectedTest) { + auto lexer_gen_result = ArgumentsLexerBuilder::Build(known_flags_); + std::unique_ptr lexer = + lexer_gen_result.ok() ? std::move(*lexer_gen_result) : nullptr; + if (!lexer) { + GTEST_SKIP() << "Memory allocation failed but it is not in the test scope."; + } + EXPECT_THAT(Tokenize(*lexer, lex_input_), IsOkAndValue(*expected_tokens_)); +} + +INSTANTIATE_TEST_SUITE_P( + ClientSpecificOptionParser, EmptyArgsLexTest, + testing::Values(LexerInputOutput{.known_flags_ = empty_known_flags, + .lex_input_ = "", + .expected_tokens_ = Tokens{}}, + LexerInputOutput{.known_flags_ = boolean_known_flags, + .lex_input_ = "", + .expected_tokens_ = Tokens{}}, + LexerInputOutput{.known_flags_ = non_boolean_known_flags, + .lex_input_ = "", + .expected_tokens_ = Tokens{}}, + LexerInputOutput{.known_flags_ = both_known_flags, + .lex_input_ = "", + .expected_tokens_ = Tokens{}})); + +TEST_P(NonBooleanArgsTest, SuccessExpectedTest) { + auto lexer_gen_result = ArgumentsLexerBuilder::Build(known_flags_); + std::unique_ptr lexer = + lexer_gen_result.ok() ? std::move(*lexer_gen_result) : nullptr; + if (!lexer) { + GTEST_SKIP() << "Memory allocation failed but it is not in the test scope."; + } + EXPECT_THAT(Tokenize(*lexer, lex_input_), IsOkAndValue(*expected_tokens_)); +} + +INSTANTIATE_TEST_SUITE_P( + ClientSpecificOptionParser, NonBooleanArgsTest, + testing::Values( + LexerInputOutput{ + .known_flags_ = non_boolean_known_flags, + .lex_input_ = "cvd --group_name=yumi", + .expected_tokens_ = Tokens{ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kKnownFlagAndValue, + "--group_name=yumi"}}}, + LexerInputOutput{ + .known_flags_ = non_boolean_known_flags, + .lex_input_ = "cvd --group_name yumi", + .expected_tokens_ = Tokens{ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kKnownValueFlag, + "--group_name"}, + ArgToken{ArgType::kPositional, "yumi"}}}, + LexerInputOutput{.known_flags_ = non_boolean_known_flags, + .lex_input_ = "cvd --group_name yumi start --daemon", + .expected_tokens_ = Tokens{ + ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kKnownValueFlag, "--group_name"}, + ArgToken{ArgType::kPositional, "yumi"}, + ArgToken{ArgType::kPositional, "start"}, + ArgToken{ArgType::kUnknownFlag, "--daemon"}}})); + +TEST_P(BooleanArgsTest, SuccessExpectedTest) { + auto lexer_gen_result = ArgumentsLexerBuilder::Build(known_flags_); + std::unique_ptr lexer = + lexer_gen_result.ok() ? std::move(*lexer_gen_result) : nullptr; + if (!lexer) { + GTEST_SKIP() << "Memory allocation failed but it is not in the test scope."; + } + EXPECT_THAT(Tokenize(*lexer, lex_input_), IsOkAndValue(*expected_tokens_)); +} + +INSTANTIATE_TEST_SUITE_P( + ClientSpecificOptionParser, BooleanArgsTest, + testing::Values( + LexerInputOutput{ + .known_flags_ = boolean_known_flags, + .lex_input_ = "cvd --clean", + .expected_tokens_ = Tokens{ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kKnownBoolFlag, + "--clean"}}}, + LexerInputOutput{ + .known_flags_ = boolean_known_flags, + .lex_input_ = "cvd --clean=TrUe", + .expected_tokens_ = Tokens{ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kKnownBoolFlag, + "--clean"}}}, + LexerInputOutput{ + .known_flags_ = boolean_known_flags, + .lex_input_ = "cvd --noclean", + .expected_tokens_ = Tokens{ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kKnownBoolNoFlag, + "--noclean"}}}, + LexerInputOutput{ + .known_flags_ = boolean_known_flags, + .lex_input_ = "cvd --noclean=redundant", + .expected_tokens_ = Tokens{ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kKnownBoolNoFlag, + "--noclean"}}}, + LexerInputOutput{ + .known_flags_ = boolean_known_flags, + .lex_input_ = "cvd --clean=no --norandom=y", + .expected_tokens_ = Tokens{ + ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kKnownBoolNoFlag, "--noclean"}, + ArgToken{ArgType::kUnknownFlag, "--norandom=y"}}})); + +TEST_P(BothArgsTest, SuccessExpectedTest) { + auto lexer_gen_result = ArgumentsLexerBuilder::Build(known_flags_); + std::unique_ptr lexer = + lexer_gen_result.ok() ? std::move(*lexer_gen_result) : nullptr; + if (!lexer) { + GTEST_SKIP() << "Memory allocation failed but it is not in the test scope."; + } + EXPECT_THAT(Tokenize(*lexer, lex_input_), IsOkAndValue(*expected_tokens_)); +} + +INSTANTIATE_TEST_SUITE_P( + ClientSpecificOptionParser, BothArgsTest, + testing::Values( + LexerInputOutput{ + .known_flags_ = both_known_flags, + .lex_input_ = "cvd --clean -group_name=yumi", + .expected_tokens_ = Tokens{ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kKnownBoolFlag, + "--clean"}, + ArgToken{ArgType::kKnownFlagAndValue, + "-group_name=yumi"}}}, + LexerInputOutput{ + .known_flags_ = both_known_flags, + .lex_input_ = "cvd --group_name -noclean", + .expected_tokens_ = Tokens{ + ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kKnownValueFlag, "--group_name"}, + ArgToken{ArgType::kKnownBoolNoFlag, "-noclean"}}})); + +TEST_P(BooleanBadArgsTest, FailureExpectedTest) { + auto lexer_gen_result = ArgumentsLexerBuilder::Build(known_flags_); + std::unique_ptr lexer = + lexer_gen_result.ok() ? std::move(*lexer_gen_result) : nullptr; + if (!lexer) { + GTEST_SKIP() << "Memory allocation failed but it is not in the test scope."; + } + auto tokenized_result = Tokenize(*lexer, lex_input_); + + if (!expected_tokens_) { + ASSERT_FALSE(tokenized_result.ok()) + << "Lexing " << lex_input_ << " should have failed."; + return; + } + EXPECT_THAT(tokenized_result, IsOkAndValue(*expected_tokens_)); +} + +INSTANTIATE_TEST_SUITE_P( + ClientSpecificOptionParser, BooleanBadArgsTest, + testing::Values( + LexerInputOutput{ + .known_flags_ = boolean_known_flags, + .lex_input_ = "cvd --yesclean", + .expected_tokens_ = Tokens{ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kUnknownFlag, + "--yesclean"}}}, + LexerInputOutput{.known_flags_ = boolean_known_flags, + .lex_input_ = "cvd --clean=Hello", + .expected_tokens_ = std::nullopt}, + LexerInputOutput{.known_flags_ = boolean_known_flags, + .lex_input_ = "cvd --clean false", + .expected_tokens_ = Tokens{ + ArgToken{ArgType::kPositional, "cvd"}, + ArgToken{ArgType::kKnownBoolFlag, "--clean"}, + ArgToken{ArgType::kPositional, "false"}}})); + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_helper.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_helper.cpp new file mode 100644 index 0000000000..1a1d206edb --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_helper.cpp @@ -0,0 +1,70 @@ +// +// Copyright (C) 2022 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. + +#include "host/commands/cvd/unittests/selector/creation_analyzer_helper.h" + +#include + +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace selector { +namespace { + +// copied from server.h +struct CommandInvocation { + std::string command; + std::vector arguments; +}; + +CommandInvocation MockParseInvocation(const std::vector& args) { + if (args.empty()) { + return CommandInvocation{}; + } + if (args[0] != "cvd") { + return CommandInvocation{.command = args[0], + .arguments = cvd_common::Args{}}; + } + if (args.size() == 1) { + return CommandInvocation{.command = "help", + .arguments = cvd_common::Args{}}; + } + cvd_common::Args program_args{args.begin() + 2, args.end()}; + return CommandInvocation{.command = args[1], .arguments = program_args}; +} + +} // namespace + +CreationInfoGenTest::CreationInfoGenTest() { Init(); } +void CreationInfoGenTest::Init() { + const auto& input_param = GetParam(); + selector_args_ = android::base::Tokenize(input_param.selector_args, " "); + auto cmd_invocation = + MockParseInvocation(android::base::Tokenize(input_param.cmd_args, " ")); + sub_cmd_ = cmd_invocation.command; + cmd_args_ = std::move(cmd_invocation.arguments); + if (!input_param.home.empty()) { + envs_["HOME"] = input_param.home; + } + if (!input_param.android_host_out.empty()) { + envs_["ANDROID_HOST_OUT"] = input_param.android_host_out; + } + expected_output_ = input_param.expected_output.output; + expected_success_ = input_param.expected_output.is_success; + credential_ = ucred{.pid = getpid(), .uid = getuid(), .gid = getgid()}; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_helper.h b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_helper.h new file mode 100644 index 0000000000..cc48625c4b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_helper.h @@ -0,0 +1,80 @@ +// +// Copyright (C) 2022 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 + +#include +#include + +#include +#include +#include +#include + +#include + +#include "host/commands/cvd/selector/creation_analyzer.h" + +namespace cuttlefish { +namespace selector { + +struct OutputInfo { + std::string home; + std::string host_artifacts_path; ///< e.g. out/host/linux-x86 + std::string group_name; + std::vector instances; + std::vector args; + std::unordered_map envs; +}; + +struct Expected { + OutputInfo output; + bool is_success; +}; + +struct InputOutput { + // inputs + std::string selector_args; + std::string cmd_args; + std::string home; + std::string android_host_out; + + // output + Expected expected_output; +}; + +class CreationInfoGenTest : public testing::TestWithParam { + protected: + CreationInfoGenTest(); + void Init(); + + std::vector selector_args_; + std::string sub_cmd_; + std::vector cmd_args_; + std::unordered_map envs_; + ucred credential_; + OutputInfo expected_output_; + bool expected_success_; + InstanceDatabase instance_db_; + InstanceLockFileManager instance_lock_file_manager_; +}; + +class HomeTest : public CreationInfoGenTest {}; +class HostArtifactsTest : public CreationInfoGenTest {}; +class InvalidSubCmdTest : public CreationInfoGenTest {}; +class ValidSubCmdTest : public CreationInfoGenTest {}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_test.cpp new file mode 100644 index 0000000000..a6d30e9450 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_test.cpp @@ -0,0 +1,289 @@ +// +// Copyright (C) 2022 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. + +#include +#include + +#include "host/commands/cvd/unittests/selector/creation_analyzer_helper.h" + +#include "common/libs/utils/environment.h" +#include "common/libs/utils/users.h" +#include "host/commands/cvd/selector/instance_database_utils.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace selector { +namespace { + +std::string TestUserHome() { + static const std::string home = StringFromEnv("HOME", ""); + if (!home.empty()) { + return home; + } + auto result = SystemWideUserHome(); + return (result.ok() ? *result : ""); +} + +std::string AutoGeneratedHome(const std::string& subdir) { + auto parent_result = ParentOfAutogeneratedHomes(getuid(), getgid()); + if (!parent_result.ok()) { + return ""; + } + std::string parent(*parent_result); + return parent + "/" + std::to_string(getuid()) + "/" + subdir; +} + +} // namespace + +static auto home_test_inputs = testing::Values( + InputOutput{ + .cmd_args = "cvd start --daemon", + .selector_args = "--group_name=cf --instance_name=1", + .android_host_out = "/home/user/download", + .home = "/usr/local/home/_fake_user", + .expected_output = + Expected{.output = OutputInfo{.home = "/usr/local/home/_fake_user", + .host_artifacts_path = + "/home/user/download"}, + .is_success = true}}, + InputOutput{.cmd_args = "cvd start --daemon", + /* no selector_args */ + .android_host_out = "/home/user/download", + .home = TestUserHome(), + .expected_output = + Expected{.output = OutputInfo{.home = TestUserHome(), + .host_artifacts_path = + "/home/user/download"}, + .is_success = true}}, + InputOutput{ + .cmd_args = "cvd start --daemon", + /* no selector_args */ + .android_host_out = "/home/user/download", + /* undefined HOME */ + .expected_output = Expected{ + .output = OutputInfo{.home = TestUserHome(), + .host_artifacts_path = "/home/user/download"}, + .is_success = true}}); + +TEST_P(HomeTest, HomeTest) { + if (TestUserHome().empty()) { + /* + * If $HOME is the same as the real home directory (i.e. HOME is not + * overridden), cvd uses an automatically generated path in place of + * HOME when the operation is "start". + * + * Otherwise, for backward compatibility, cvd respects the overridden + * HOME. + * + * In testing that feature, if we cannot get the real home directory, + * the testing is not possible. + */ + GTEST_SKIP() << "$HOME should be available for this set of tests."; + } + auto param = CreationAnalyzer::CreationAnalyzerParam{ + .cmd_args = cmd_args_, .envs = envs_, .selector_args = selector_args_}; + + auto result = CreationAnalyzer::Analyze( + sub_cmd_, param, credential_, instance_db_, instance_lock_file_manager_); + + ASSERT_EQ(result.ok(), expected_success_) << result.error().Trace(); + if (!expected_success_) { + return; + } + ASSERT_EQ(result->home, expected_output_.home); +} + +INSTANTIATE_TEST_SUITE_P(CvdCreationInfo, HomeTest, home_test_inputs); + +static auto host_out_test_inputs = testing::Values( + InputOutput{.cmd_args = "cvd start --daemon", + .selector_args = "--group_name=cf --instance_name=1", + .android_host_out = "/home/user/download", + .home = "/home/fake_user", + .expected_output = + Expected{.output = OutputInfo{.home = "/home/fake_user", + .host_artifacts_path = + "/home/user/download"}, + .is_success = true}}, + InputOutput{.cmd_args = "cvd start --daemon", + .selector_args = "--group_name=cf --instance_name=1", + /* missing ANDROID_HOST_OUT */ + .home = "/home/fake_user", + .expected_output = + Expected{.output = OutputInfo{.home = "/home/fake_user"}, + .is_success = false}}); + +TEST_P(HostArtifactsTest, HostArtifactsTest) { + auto param = CreationAnalyzer::CreationAnalyzerParam{ + .cmd_args = cmd_args_, .envs = envs_, .selector_args = selector_args_}; + + auto result = CreationAnalyzer::Analyze( + sub_cmd_, param, credential_, instance_db_, instance_lock_file_manager_); + + ASSERT_EQ(result.ok(), expected_success_) << result.error().Trace(); + if (!expected_success_) { + return; + } + ASSERT_EQ(result->host_artifacts_path, expected_output_.host_artifacts_path); +} + +INSTANTIATE_TEST_SUITE_P(CvdCreationInfo, HostArtifactsTest, + host_out_test_inputs); + +static auto invalid_sub_cmd_test_inputs = + testing::Values(InputOutput{.cmd_args = "cvd stop --daemon", + .android_host_out = "/home/user/download", + .home = "/home/fake_user"}, + InputOutput{.cmd_args = "cvd", + .android_host_out = "/home/user/download", + .home = "/home/fake_user"}, + InputOutput{.cmd_args = "cvd help --daemon", + .android_host_out = "/home/user/download", + .home = "/home/fake_user"}); + +TEST_P(InvalidSubCmdTest, InvalidSubCmdTest) { + auto param = CreationAnalyzer::CreationAnalyzerParam{ + .cmd_args = cmd_args_, .envs = envs_, .selector_args = selector_args_}; + + auto result = CreationAnalyzer::Analyze( + sub_cmd_, param, credential_, instance_db_, instance_lock_file_manager_); + + ASSERT_FALSE(result.ok()) + << "Analyze() had to fail with the subcmd in " << GetParam().cmd_args; +} + +INSTANTIATE_TEST_SUITE_P(CvdCreationInfo, InvalidSubCmdTest, + invalid_sub_cmd_test_inputs); + +static auto& valid_sub_cmd_test_inputs = home_test_inputs; + +TEST_P(ValidSubCmdTest, ValidSubCmdTest) { + auto param = CreationAnalyzer::CreationAnalyzerParam{ + .cmd_args = cmd_args_, .envs = envs_, .selector_args = selector_args_}; + + auto result = CreationAnalyzer::Analyze( + sub_cmd_, param, credential_, instance_db_, instance_lock_file_manager_); + + ASSERT_TRUE(result.ok()) << result.error().Trace(); +} + +INSTANTIATE_TEST_SUITE_P(CvdCreationInfo, ValidSubCmdTest, + valid_sub_cmd_test_inputs); + +/* + * Tries to run Cuttlefish with default group two times, so the second + * run should fail as the default group_name is registed in the Instance- + * Database. + */ +TEST(AutoHomeTest, DefaultFailAtSecondTrialTest) { + auto android_host_out = StringFromEnv("ANDROID_HOST_OUT", "."); + if (android_host_out.empty()) { + GTEST_SKIP() << "This test requires ANDROID_HOST_OUT to be set"; + } + auto credential = ucred{.pid = getpid(), .uid = getuid(), .gid = getgid()}; + InstanceLockFileManager lock_manager; + InstanceDatabase instance_db; + cvd_common::Envs envs = {{"ANDROID_HOST_OUT", android_host_out}}; + cvd_common::Args empty_args; + std::vector cmd_args_list{ + cvd_common::Args{"--daemon", "--instance_nums=7"}, + cvd_common::Args{"--daemon", "--instance_nums=3"}}; + auto param0 = CreationAnalyzer::CreationAnalyzerParam{ + .cmd_args = cmd_args_list[0], .envs = envs, .selector_args = empty_args}; + auto param1 = CreationAnalyzer::CreationAnalyzerParam{ + .cmd_args = cmd_args_list[1], .envs = envs, .selector_args = empty_args}; + + auto result_1st_exec = CreationAnalyzer::Analyze("start", param0, credential, + instance_db, lock_manager); + auto result_db_addition = + instance_db.AddInstanceGroup({.group_name = "cvd", + .home_dir = TestUserHome(), + .host_artifacts_path = android_host_out, + .product_out_path = android_host_out}); + if (!result_db_addition.ok()) { + GTEST_SKIP() << "This test requires mock group addition to work."; + } + auto result_2nd_exec = CreationAnalyzer::Analyze("start", param1, credential, + instance_db, lock_manager); + + ASSERT_TRUE(result_1st_exec.ok()) << result_1st_exec.error().Trace(); + ASSERT_EQ(result_1st_exec->home, TestUserHome()); + ASSERT_FALSE(result_2nd_exec.ok()) + << "Meant to be fail but returned home : " << result_2nd_exec->home; +} + +TEST(AutoHomeTest, DefaultFollowedByNonDefaultTest) { + auto android_host_out = StringFromEnv("ANDROID_HOST_OUT", "."); + if (android_host_out.empty()) { + GTEST_SKIP() << "This test requires ANDROID_HOST_OUT to be set"; + } + if (AutoGeneratedHome("goog").empty()) { + GTEST_SKIP() << "This test requires read-writable temp directory"; + } + auto credential = ucred{.pid = getpid(), .uid = getuid(), .gid = getgid()}; + InstanceLockFileManager lock_manager; + InstanceDatabase instance_db; + cvd_common::Envs envs = {{"ANDROID_HOST_OUT", android_host_out}}; + // needs this as the CreationAnalyzerParam takes references, not copies. + // i.e. .cmd_args = cvd_common::Args{} won't work. + cvd_common::Args empty_args; + cvd_common::Args cmd_arg_for_default{"--daemon", "--instance_nums=7"}; + cvd_common::Args cmd_args_1st_non_default{"--daemon", "--instance_nums=3"}; + cvd_common::Args cmd_args_2nd_non_default{"--daemon", "--instance_nums=5"}; + cvd_common::Args selector_device_name_args{"--group_name=goog", + "--instance_name=tv"}; + auto param_default = + CreationAnalyzer::CreationAnalyzerParam{.cmd_args = cmd_arg_for_default, + .envs = envs, + .selector_args = empty_args}; + auto param_1st_non_default = CreationAnalyzer::CreationAnalyzerParam{ + .cmd_args = cmd_args_1st_non_default, + .envs = envs, + .selector_args = selector_device_name_args}; + // To make second non-default run non-default. + cvd_common::Envs envs_with_home{envs}; + envs_with_home["HOME"] = "/home/mocktester"; + auto param_2nd_non_default = CreationAnalyzer::CreationAnalyzerParam{ + .cmd_args = cmd_args_2nd_non_default, + .selector_args = empty_args, + .envs = envs_with_home}; + + auto result_default = CreationAnalyzer::Analyze( + "start", param_default, credential, instance_db, lock_manager); + auto result_db_addition = + instance_db.AddInstanceGroup({.group_name = "cvd", + .home_dir = TestUserHome(), + .host_artifacts_path = android_host_out, + .product_out_path = android_host_out}); + if (!result_db_addition.ok()) { + GTEST_SKIP() << "This test requires mock group addition to work."; + } + auto result_1st_non_default = CreationAnalyzer::Analyze( + "start", param_1st_non_default, credential, instance_db, lock_manager); + auto result_2nd_non_default = CreationAnalyzer::Analyze( + "start", param_2nd_non_default, credential, instance_db, lock_manager); + + ASSERT_TRUE(result_default.ok()) << result_default.error().Trace(); + ASSERT_EQ(result_default->home, TestUserHome()); + ASSERT_TRUE(result_1st_non_default.ok()) + << result_1st_non_default.error().Trace(); + ASSERT_EQ(result_1st_non_default->home, AutoGeneratedHome("goog")); + ASSERT_TRUE(result_2nd_non_default.ok()) + << result_2nd_non_default.error().Trace(); +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_helper.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_helper.cpp new file mode 100644 index 0000000000..e5f249ecd5 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_helper.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/unittests/selector/cvd_flags_helper.h" + +#include + +#include + +namespace cuttlefish { + +CvdFlagCollectionTest::CvdFlagCollectionTest() { + std::string in_str = "--name=foo --not_consumed --enable_vnc --id 9"; + input_ = android::base::Tokenize(in_str, " "); + CvdFlag Help("help", false); + CvdFlag Name("name"); + CvdFlag EnableVnc("enable_vnc", true); + CvdFlag Id("id"); + CvdFlag NotGiven("not-given"); + + flag_collection_.EnrollFlag(std::move(Help)); + flag_collection_.EnrollFlag(std::move(Name)); + flag_collection_.EnrollFlag(std::move(EnableVnc)); + flag_collection_.EnrollFlag(std::move(Id)); + flag_collection_.EnrollFlag(std::move(NotGiven)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_helper.h b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_helper.h new file mode 100644 index 0000000000..1bb5fcb038 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_helper.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 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 + +#include +#include +#include + +#include + +#include "common/libs/utils/result.h" +#include "host/commands/cvd/flag.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +// use this only when std::optional is not nullopt +template +static Result Get( + const std::optional& opt_var) { + CF_EXPECT(opt_var != std::nullopt); + std::variant var = *opt_var; + auto* ptr = std::get_if(&var); + CF_EXPECT(ptr != nullptr); + return *ptr; +} + +class CvdFlagCollectionTest : public testing::Test { + protected: + CvdFlagCollectionTest(); + + cvd_common::Args input_; + FlagCollection flag_collection_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_test.cpp new file mode 100644 index 0000000000..a04866de69 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_test.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/commands/cvd/unittests/selector/cvd_flags_helper.h" + +namespace cuttlefish { + +TEST_F(CvdFlagCollectionTest, Init) { + auto output_result = flag_collection_.FilterFlags(input_); + ASSERT_TRUE(output_result.ok()) << output_result.error().Trace(); +} + +TEST_F(CvdFlagCollectionTest, Leftover) { + auto output_result = flag_collection_.FilterFlags(input_); + ASSERT_TRUE(output_result.ok()) << output_result.error().Trace(); + ASSERT_EQ(input_, cvd_common::Args{"--not_consumed"}); +} + +TEST_F(CvdFlagCollectionTest, AllGivenFlagsListed) { + auto output_result = flag_collection_.FilterFlags(input_); + ASSERT_TRUE(output_result.ok()) << output_result.error().Trace(); + ASSERT_EQ(input_, cvd_common::Args{"--not_consumed"}); + auto output = std::move(*output_result); + + ASSERT_FALSE(Contains(output, "help")); + ASSERT_TRUE(Contains(output, "name")); + ASSERT_TRUE(Contains(output, "enable_vnc")); + ASSERT_TRUE(Contains(output, "id")); + ASSERT_FALSE(Contains(output, "not-given")); + ASSERT_FALSE(Contains(output, "not-consumed")); +} + +TEST_F(CvdFlagCollectionTest, DefaultValueFlagsAlsoListed) { + auto output_result = flag_collection_.CalculateFlags(input_); + ASSERT_TRUE(output_result.ok()) << output_result.error().Trace(); + ASSERT_EQ(input_, cvd_common::Args{"--not_consumed"}); + auto output = std::move(*output_result); + + ASSERT_TRUE(Contains(output, "help")); + ASSERT_TRUE(Contains(output, "name")); + ASSERT_TRUE(Contains(output, "enable_vnc")); + ASSERT_TRUE(Contains(output, "id")); + ASSERT_FALSE(Contains(output, "not-given")); + ASSERT_FALSE(Contains(output, "not-consumed")); +} + +TEST_F(CvdFlagCollectionTest, ValueTest) { + auto output_result = flag_collection_.CalculateFlags(input_); + ASSERT_TRUE(output_result.ok()) << output_result.error().Trace(); + auto output = std::move(*output_result); + // without these verified, the code below will crash + ASSERT_TRUE(Contains(output, "help")); + ASSERT_TRUE(Contains(output, "name")); + ASSERT_TRUE(Contains(output, "enable_vnc")); + ASSERT_TRUE(Contains(output, "id")); + const auto help_output = output.at("help"); + const auto name_output = output.at("name"); + const auto enable_vnc_output = output.at("enable_vnc"); + const auto id_output = output.at("id"); + + auto help_value_result = FlagCollection::GetValue(help_output.value); + auto name_value_result = + FlagCollection::GetValue(name_output.value); + auto enable_vnc_value_result = + FlagCollection::GetValue(enable_vnc_output.value); + auto id_value_result = + FlagCollection::GetValue(id_output.value); + + ASSERT_TRUE(help_value_result.ok()); + ASSERT_TRUE(name_value_result.ok()); + ASSERT_TRUE(enable_vnc_value_result.ok()); + ASSERT_TRUE(id_value_result.ok()); + ASSERT_EQ(*help_value_result, false); + ASSERT_EQ(*name_value_result, "foo"); + ASSERT_EQ(*enable_vnc_value_result, true); + ASSERT_EQ(*id_value_result, 9); +} + +TEST(CvdFlagTest, FlagProxyFilter) { + CvdFlag no_default("no_default"); + cvd_common::Args has_flag_args{"--no_default=Hello"}; + cvd_common::Args not_has_flag_args{"--bar --foo=name --enable_vnc"}; + cvd_common::Args empty_args{""}; + + CvdFlagProxy no_default_proxy(std::move(no_default)); + auto expected_hello_opt_result = no_default_proxy.FilterFlag(has_flag_args); + auto expected_null_result = no_default_proxy.FilterFlag(not_has_flag_args); + auto expected_null_result2 = no_default_proxy.FilterFlag(empty_args); + + ASSERT_TRUE(expected_hello_opt_result.ok()); + ASSERT_TRUE(expected_null_result.ok()); + ASSERT_TRUE(expected_null_result2.ok()); + + ASSERT_TRUE(*expected_hello_opt_result); + auto value_result = Get(**expected_hello_opt_result); + ASSERT_TRUE(value_result.ok()); + ASSERT_EQ(*value_result, "Hello"); + ASSERT_FALSE(*expected_null_result); + ASSERT_FALSE(*expected_null_result2); + + ASSERT_TRUE(has_flag_args.empty()); + ASSERT_EQ(not_has_flag_args, + cvd_common::Args{"--bar --foo=name --enable_vnc"}); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/group_record_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/group_record_test.cpp new file mode 100644 index 0000000000..30168e3f30 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/group_record_test.cpp @@ -0,0 +1,134 @@ +// +// Copyright (C) 2022 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. + +#include +#include + +#include + +#include "host/commands/cvd/selector/instance_group_record.h" +#include "host/commands/cvd/selector/instance_record.h" + +namespace cuttlefish { +namespace selector { + +static std::string GroupName() { return "yah_ong"; } +static std::string HomeDir() { return "/home/user"; } +static std::string TestBinDir() { return "/opt/android11"; } + +class CvdInstanceGroupUnitTest : public testing::Test { + protected: + CvdInstanceGroupUnitTest() + : group_({.group_name = GroupName(), + .home_dir = HomeDir(), + .host_artifacts_path = TestBinDir(), + .product_out_path = TestBinDir()}) {} + LocalInstanceGroup& Get() { return group_; } + LocalInstanceGroup group_; +}; + +// CvdInstanceGroupUnitTest + add 4 instances +class CvdInstanceGroupSearchUnitTest : public testing::Test { + protected: + CvdInstanceGroupSearchUnitTest() + : group_({.group_name = GroupName(), + .home_dir = HomeDir(), + .host_artifacts_path = TestBinDir(), + .product_out_path = TestBinDir()}) { + is_setup_ = + (Get().AddInstance(1, "tv_instance").ok() && + Get().AddInstance(2, "2").ok() && Get().AddInstance(3, "phone").ok() && + Get().AddInstance(7, "tv_instance").ok()); + is_setup_ = is_setup_ && (Get().Instances().size() == 4); + } + LocalInstanceGroup& Get() { return group_; } + bool IsSetup() const { return is_setup_; } + + private: + bool is_setup_; + LocalInstanceGroup group_; +}; + +TEST_F(CvdInstanceGroupUnitTest, Fields) { + auto& group = Get(); + + ASSERT_EQ(group.InternalGroupName(), "cvd"); + ASSERT_EQ(group.GroupName(), "yah_ong"); + ASSERT_EQ(group.HomeDir(), HomeDir()); + ASSERT_EQ(group.HostArtifactsPath(), TestBinDir()); +} + +TEST_F(CvdInstanceGroupUnitTest, AddInstances) { + auto& group = Get(); + + ASSERT_TRUE(group.AddInstance(1, "tv_instance").ok()); + ASSERT_TRUE(group.AddInstance(2, "2").ok()); + ASSERT_TRUE(group.AddInstance(3, "phone").ok()); + ASSERT_EQ(group.Instances().size(), 3); +} + +TEST_F(CvdInstanceGroupUnitTest, AddInstancesAndListAll) { + auto& group = Get(); + group.AddInstance(1, "tv_instance"); + group.AddInstance(2, "2"); + group.AddInstance(3, "phone"); + if (group.Instances().size() != 3) { + GTEST_SKIP() << "AddInstance failed but is verified in other testing."; + } + + auto set_result = group.FindAllInstances(); + + ASSERT_TRUE(set_result.ok()) << set_result.error().Trace(); + ASSERT_EQ(set_result->size(), 3); +} + +TEST_F(CvdInstanceGroupSearchUnitTest, SearchById) { + auto& group = Get(); + if (!IsSetup()) { + /* + * Here's why we skip the test rather than see it as a failure. + * + * 1. The test is specifically designed for searcy-by-id operations. + * 2. Adding instance to a group is tested in AddInstances test designed + * specifically for it. It's a failure there but not here. + */ + GTEST_SKIP() << "Failed to add instances to the group."; + } + // valid_ids were added in the CvdInstanceGroupSearchUnitTest_SearchById + // constructor. + std::vector valid_ids{1, 2, 7}; + std::vector invalid_ids{20, 0, 5}; + + // valid search + for (auto const& valid_id : valid_ids) { + auto result = group.FindById(valid_id); + ASSERT_TRUE(result.ok()); + auto set = *result; + ASSERT_EQ(set.size(), 1); + const LocalInstance& instance = *set.cbegin(); + ASSERT_EQ(instance.InstanceId(), valid_id); + } + + // invalid search + for (auto const& invalid_id : invalid_ids) { + auto result = group.FindById(invalid_id); + // it's okay not to be found + ASSERT_TRUE(result.ok()); + ASSERT_TRUE(result->empty()); + } +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/host_tool_target_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/host_tool_target_test.cpp new file mode 100644 index 0000000000..5bb87ed8c3 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/host_tool_target_test.cpp @@ -0,0 +1,99 @@ +// +// Copyright (C) 2023 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. + +#include +#include +#include + +#include + +#include "common/libs/utils/environment.h" +#include "common/libs/utils/result_matchers.h" +#include "host/commands/cvd/server_command/host_tool_target_manager.h" + +namespace cuttlefish { + +TEST(HostToolTarget, KnownFlags) { + std::string android_host_out = StringFromEnv("ANDROID_HOST_OUT", ""); + if (android_host_out.empty()) { + GTEST_SKIP() << "Set ANDROID_HOST_OUT"; + } + + auto host_tool_target = HostToolTarget::Create(android_host_out); + EXPECT_THAT(host_tool_target, IsOk()); + + auto daemon_flag = + host_tool_target->GetFlagInfo(HostToolTarget::FlagInfoRequest{ + .operation_ = "start", + .flag_name_ = "daemon", + }); + + auto bad_flag = host_tool_target->GetFlagInfo(HostToolTarget::FlagInfoRequest{ + .operation_ = "start", + .flag_name_ = "@never_exist@", + }); + + EXPECT_THAT(daemon_flag, IsOk()); + ASSERT_EQ(daemon_flag->Name(), "daemon"); + ASSERT_TRUE(daemon_flag->Type() == "string" || daemon_flag->Type() == "bool"); + EXPECT_THAT(bad_flag, IsError()); +} + +TEST(HostToolManager, KnownFlags) { + std::string android_host_out = StringFromEnv("ANDROID_HOST_OUT", ""); + if (android_host_out.empty()) { + GTEST_SKIP() << "Set ANDROID_HOST_OUT"; + } + auto host_tool_manager = NewHostToolTargetManager(); + + auto daemon_flag = + host_tool_manager->ReadFlag({.artifacts_path = android_host_out, + .op = "start", + .flag_name = "daemon"}); + auto bad_flag = + host_tool_manager->ReadFlag({.artifacts_path = android_host_out, + .op = "start", + .flag_name = "@never_exist@"}); + + EXPECT_THAT(daemon_flag, IsOk()); + ASSERT_EQ(daemon_flag->Name(), "daemon"); + ASSERT_TRUE(daemon_flag->Type() == "string" || daemon_flag->Type() == "bool"); + EXPECT_THAT(bad_flag, IsError()); +} + +TEST(HostToolManager, KnownBins) { + std::string android_host_out = StringFromEnv("ANDROID_HOST_OUT", ""); + if (android_host_out.empty()) { + GTEST_SKIP() << "Set ANDROID_HOST_OUT"; + } + auto host_tool_manager = NewHostToolTargetManager(); + + auto start_bin = host_tool_manager->ExecBaseName( + {.artifacts_path = android_host_out, .op = "start"}); + auto stop_bin = host_tool_manager->ExecBaseName( + {.artifacts_path = android_host_out, .op = "stop"}); + auto bad_bin = host_tool_manager->ExecBaseName( + {.artifacts_path = android_host_out, .op = "bad"}); + + EXPECT_THAT(start_bin, IsOk()); + EXPECT_THAT(stop_bin, IsOk()); + EXPECT_THAT(bad_bin, IsError()); + ASSERT_TRUE(*start_bin == "cvd_internal_start" || *start_bin == "launch_cvd") + << "start_bin was " << *start_bin; + ASSERT_TRUE(*stop_bin == "cvd_internal_stop" || *stop_bin == "stop_cvd") + << "stop_bin was " << *stop_bin; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.cpp new file mode 100644 index 0000000000..2c6ac684d7 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.cpp @@ -0,0 +1,149 @@ +// +// Copyright (C) 2022 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. + +#include "host/commands/cvd/unittests/selector/instance_database_helper.h" + +#include +#include +#include + +#include + +#include "common/libs/fs/shared_buf.h" +#include "common/libs/fs/shared_fd.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/files.h" +#include "host/commands/cvd/selector/selector_constants.h" + +namespace cuttlefish { +namespace selector { +namespace { + +// mktemp with /tmp/.XXXXXX, and if failed, +// mkdir -p /tmp/. +std::optional CreateTempDirectory( + const std::string& subdir, const std::string& default_suffix) { + std::string path_pattern = "/tmp/" + subdir + ".XXXXXX"; + auto ptr = mkdtemp(path_pattern.data()); + if (ptr) { + return {std::string(ptr)}; + } + std::string default_path = "/tmp/" + subdir + "." + default_suffix; + return (EnsureDirectoryExists(default_path).ok() ? std::optional(default_path) + : std::nullopt); +} + +// Linux "touch" a(n empty) file +bool Touch(const std::string& full_path) { + // this file is required only to make FileExists() true. + SharedFD new_file = SharedFD::Creat(full_path, S_IRUSR | S_IWUSR); + return new_file->IsOpen(); +} + +} // namespace + +CvdInstanceDatabaseTest::CvdInstanceDatabaseTest() + : error_{.error_code = ErrorCode::kOk, .msg = ""} { + InitWorkspace() && InitMockAndroidHostOut(); +} + +CvdInstanceDatabaseTest::~CvdInstanceDatabaseTest() { ClearWorkspace(); } + +void CvdInstanceDatabaseTest::ClearWorkspace() { + if (!workspace_dir_.empty()) { + RecursivelyRemoveDirectory(workspace_dir_); + } +} + +void CvdInstanceDatabaseTest::SetErrorCode(const ErrorCode error_code, + const std::string& msg) { + error_.error_code = error_code; + error_.msg = msg; +} + +bool CvdInstanceDatabaseTest::InitWorkspace() { + // creating a parent dir of the mock home directories for each fake group + auto result_opt = CreateTempDirectory("cf_unittest", "default_location"); + if (!result_opt) { + SetErrorCode(ErrorCode::kFileError, "Failed to create workspace"); + return false; + } + workspace_dir_ = std::move(result_opt.value()); + return true; +} + +bool CvdInstanceDatabaseTest::InitMockAndroidHostOut() { + /* creating a fake host out directory + * + * As the automated testing system does not guarantee that there is either + * ANDROID_HOST_OUT or ".", where we can find host tools, we create a fake + * host tool directory just enough to deceive InstanceDatabase APIs. + * + */ + std::string android_host_out = workspace_dir_ + "/android_host_out"; + if (!EnsureDirectoryExists(android_host_out).ok()) { + SetErrorCode(ErrorCode::kFileError, "Failed to create " + android_host_out); + return false; + } + android_artifacts_path_ = android_host_out; + if (!EnsureDirectoryExists(android_artifacts_path_ + "/bin").ok()) { + SetErrorCode(ErrorCode::kFileError, + "Failed to create " + android_artifacts_path_ + "/bin"); + return false; + } + if (!Touch(android_artifacts_path_ + "/bin" + "/launch_cvd")) { + SetErrorCode(ErrorCode::kFileError, "Failed to create mock launch_cvd"); + return false; + } + return true; +} + +// Add an InstanceGroups with each home directory and android_host_out_ +bool CvdInstanceDatabaseTest::AddGroups( + const std::unordered_set& base_names) { + for (const auto& base_name : base_names) { + const std::string home(Workspace() + "/" + base_name); + if (!EnsureDirectoryExists(home).ok()) { + SetErrorCode(ErrorCode::kFileError, home + " directory is not found."); + return false; + } + InstanceDatabase::AddInstanceGroupParam param{ + .group_name = base_name, + .home_dir = home, + .host_artifacts_path = android_artifacts_path_, + .product_out_path = android_artifacts_path_}; + if (!db_.AddInstanceGroup(param).ok()) { + SetErrorCode(ErrorCode::kInstanceDabaseError, "Failed to add group"); + return false; + } + } + return true; +} + +bool CvdInstanceDatabaseTest::AddInstances( + const std::string& group_name, + const std::vector& instances_info) { + for (const auto& [id, per_instance_name] : instances_info) { + if (!db_.AddInstance(group_name, id, per_instance_name).ok()) { + SetErrorCode(ErrorCode::kInstanceDabaseError, + "Failed to add instance " + per_instance_name); + return false; + } + } + return true; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.h b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.h new file mode 100644 index 0000000000..6e7b862ae6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.h @@ -0,0 +1,105 @@ +// +// Copyright (C) 2022 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 + +#include +#include +#include +#include +#include +#include + +#include + +#include "host/commands/cvd/selector/constant_reference.h" +#include "host/commands/cvd/selector/instance_database.h" + +namespace cuttlefish { +namespace selector { + +/** + * Creates n mock HOME directories, one per group. Also, creates + * 1 mock ANDROID_HOST_OUT with a mock launcher file. + * + * The test suite is to assess InstanceDatabase APIs such as + * adding groups, adding instances to the groups, etc. The thing + * is that the InstanceDatabase APIs will check if HOME and/or + * ANDROID_HOST_OUT are directories. Also, for ANDROID_HOST_OUT, + * as a bare minimum validity check, it will see if there is a launcher + * file under the bin directory of it. + * + * Thus, the mock environment should prepare an actual directories with + * a mock launcher file(s). In case the test runs/tests in the suite run + * in parallel, we give each test run a unique directory, and that's why + * all mock homes are under a temp directory created by mkdtemp() + * + */ +class CvdInstanceDatabaseTest : public ::testing::Test { + protected: + enum class ErrorCode : std::int32_t { + kOk, + kFileError, + kInstanceDabaseError, + }; + + struct SetupError { + ErrorCode error_code; + std::string msg; + }; + + CvdInstanceDatabaseTest(); + ~CvdInstanceDatabaseTest(); + + bool SetUpOk() const { return error_.error_code == ErrorCode::kOk; } + const std::string& Workspace() const { return workspace_dir_; } + /* + * Returns a valid host artifacts dir, which is a prerequisite for + * InstanceDatabase APIs. + */ + const std::string& HostArtifactsPath() const { + return android_artifacts_path_; + } + + // Adds InstanceGroups, each by: + // "mkdir" : Workspace() + "/" + base_name, HostArtifactsPath() + // db_.AddInstanceGroup() + bool AddGroups(const std::unordered_set& base_names); + struct InstanceInfo { + unsigned id; + std::string per_instance_name; + }; + bool AddInstances(const std::string& group_name, + const std::vector& instances_info); + InstanceDatabase& GetDb() { return db_; } + const SetupError& Error() const { return error_; } + + private: + void ClearWorkspace(); + bool InitWorkspace(); + bool InitMockAndroidHostOut(); + // set error_ when there is an error + void SetErrorCode(const ErrorCode error_code, const std::string& msg); + + std::string android_artifacts_path_; + std::string workspace_dir_; + SetupError error_; + InstanceDatabase db_; +}; + +using CvdInstanceDatabaseJsonTest = CvdInstanceDatabaseTest; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_test.cpp new file mode 100644 index 0000000000..c83ae18632 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_test.cpp @@ -0,0 +1,463 @@ +// +// Copyright (C) 2022 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. + +#include +#include +#include + +#include + +#include "common/libs/utils/files.h" +#include "common/libs/utils/json.h" +#include "host/commands/cvd/selector/instance_database.h" +#include "host/commands/cvd/selector/selector_constants.h" +#include "host/commands/cvd/unittests/selector/instance_database_helper.h" + +/* + * SetUp creates a mock ANDROID_HOST_OUT directory where there is + * bin/launch_cvd, and a "Workspace" directory where supposedly HOME + * directories for each LocalInstanceGroup will be populated. + * + * InstanceDatabase APIs conduct validity checks: e.g. if the host tool + * directory actually has host tools such as launch_cvd, if the "HOME" + * directory for the LocalInstanceGroup is actually an existing directory, + * and so on. + * + * With TEST_F(Suite, Test), the following is the class declaration: + * class Suite : public testing::Test; + * class Suite_Test : public Suite; + * + * Thus, the set up is done inside the constructur of the Suite base class. + * Also, cleaning up the directories and files are done inside the destructor. + * If creating files/directories fails, the "Test" is skipped. + * + */ + +namespace cuttlefish { +namespace selector { + +TEST_F(CvdInstanceDatabaseTest, Empty) { + if (!SetUpOk()) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + ASSERT_TRUE(db.IsEmpty()); + ASSERT_TRUE(db.InstanceGroups().empty()); +} + +TEST_F(CvdInstanceDatabaseTest, AddWithInvalidGroupInfo) { + if (!SetUpOk()) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + // populate home directories under Workspace() + const std::string home{Workspace() + "/" + "meow"}; + if (!EnsureDirectoryExists(home).ok()) { + // if ever failed, skip + GTEST_SKIP() << "Failed to find/create " << home; + } + const std::string invalid_host_artifacts_path{Workspace() + "/" + "host_out"}; + if (!EnsureDirectoryExists(invalid_host_artifacts_path).ok() || + !EnsureDirectoryExists(invalid_host_artifacts_path + "/bin").ok()) { + GTEST_SKIP() << "Failed to find/create " + << invalid_host_artifacts_path + "/bin"; + } + + // group_name : "meow" + auto result_bad_host_bin_dir = + db.AddInstanceGroup({.group_name = "meow", + .home_dir = home, + .host_artifacts_path = "/path/to/never/exists", + .product_out_path = "/path/to/never/exists"}); + auto result_bad_group_name = + db.AddInstanceGroup({.group_name = "0invalid_group_name", + .home_dir = home, + .host_artifacts_path = HostArtifactsPath(), + .product_out_path = HostArtifactsPath()}); + // Everything is correct but one thing: the host artifacts directory does not + // have host tool files such as launch_cvd + auto result_non_qualifying_host_tool_dir = + db.AddInstanceGroup({.group_name = "meow", + .home_dir = home, + .host_artifacts_path = invalid_host_artifacts_path, + .product_out_path = invalid_host_artifacts_path}); + + ASSERT_FALSE(result_bad_host_bin_dir.ok()); + ASSERT_FALSE(result_bad_group_name.ok()); + ASSERT_FALSE(result_non_qualifying_host_tool_dir.ok()); +} + +TEST_F(CvdInstanceDatabaseTest, AddWithValidGroupInfo) { + if (!SetUpOk()) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + const std::string home0{Workspace() + "/" + "home0"}; + if (!EnsureDirectoryExists(home0).ok()) { + GTEST_SKIP() << "Failed to find/create " << home0; + } + const std::string home1{Workspace() + "/" + "home1"}; + if (!EnsureDirectoryExists(home1).ok()) { + GTEST_SKIP() << "Failed to find/create " << home1; + } + + ASSERT_TRUE(db.AddInstanceGroup({.group_name = "meow", + .home_dir = home0, + .host_artifacts_path = HostArtifactsPath(), + .product_out_path = HostArtifactsPath()}) + .ok()); + ASSERT_TRUE(db.AddInstanceGroup({.group_name = "miaou", + .home_dir = home1, + .host_artifacts_path = HostArtifactsPath(), + .product_out_path = HostArtifactsPath()}) + .ok()); +} + +TEST_F(CvdInstanceDatabaseTest, AddToTakenHome) { + if (!SetUpOk()) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + const std::string home{Workspace() + "/" + "my_home"}; + if (!EnsureDirectoryExists(home).ok()) { + GTEST_SKIP() << "Failed to find/create " << home; + } + + ASSERT_TRUE(db.AddInstanceGroup({.group_name = "meow", + .home_dir = home, + .host_artifacts_path = HostArtifactsPath(), + .product_out_path = HostArtifactsPath()}) + .ok()); + ASSERT_FALSE(db.AddInstanceGroup({.group_name = "meow", + .home_dir = home, + .host_artifacts_path = HostArtifactsPath(), + .product_out_path = HostArtifactsPath()}) + .ok()); +} + +TEST_F(CvdInstanceDatabaseTest, Clear) { + /* AddGroups(name): + * HOME: Workspace() + "/" + name + * HostArtifactsPath: Workspace() + "/" + "android_host_out" + * group_ := LocalInstanceGroup(name, HOME, HostArtifactsPath) + */ + if (!SetUpOk() || !AddGroups({"nyah", "yah_ong"})) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + + // test Clear() + ASSERT_FALSE(db.IsEmpty()); + db.Clear(); + ASSERT_TRUE(db.IsEmpty()); +} + +TEST_F(CvdInstanceDatabaseTest, SearchGroups) { + if (!SetUpOk() || !AddGroups({"myau", "miau"})) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + const std::string valid_home_search_key{Workspace() + "/" + "myau"}; + const std::string invalid_home_search_key{"/no/such/path"}; + + auto valid_groups = db.FindGroups({kHomeField, valid_home_search_key}); + auto valid_group = db.FindGroup({kHomeField, valid_home_search_key}); + auto invalid_groups = db.FindGroups({kHomeField, invalid_home_search_key}); + auto invalid_group = db.FindGroup({kHomeField, invalid_home_search_key}); + + ASSERT_TRUE(valid_groups.ok()); + ASSERT_EQ(valid_groups->size(), 1); + ASSERT_TRUE(valid_group.ok()); + + ASSERT_TRUE(invalid_groups.ok()); + ASSERT_EQ(invalid_groups->size(), 0); + ASSERT_FALSE(invalid_group.ok()); +} + +TEST_F(CvdInstanceDatabaseTest, RemoveGroup) { + if (!SetUpOk()) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + if (!AddGroups({"miaaaw", "meow", "mjau"})) { + GTEST_SKIP() << Error().msg; + } + auto eng_group = db.FindGroup({kHomeField, Workspace() + "/" + "meow"}); + if (!eng_group.ok()) { + GTEST_SKIP() << "meow" + << " group was not found."; + } + + ASSERT_TRUE(db.RemoveInstanceGroup(*eng_group)); + ASSERT_FALSE(db.RemoveInstanceGroup(*eng_group)); +} + +TEST_F(CvdInstanceDatabaseTest, AddInstances) { + if (!SetUpOk() || !AddGroups({"yah_ong"})) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + auto kitty_group = db.FindGroup({kHomeField, Workspace() + "/" + "yah_ong"}); + if (!kitty_group.ok()) { + GTEST_SKIP() << "yah_ong" + << " group was not found"; + } + const auto& instances = kitty_group->Get().Instances(); + + ASSERT_TRUE(db.AddInstance("yah_ong", 1, "yumi").ok()); + ASSERT_FALSE(db.AddInstance("yah_ong", 3, "yumi").ok()); + ASSERT_FALSE(db.AddInstance("yah_ong", 1, "tiger").ok()); + ASSERT_TRUE(db.AddInstance("yah_ong", 3, "tiger").ok()); + for (auto const& instance_unique_ptr : instances) { + ASSERT_TRUE(instance_unique_ptr->PerInstanceName() == "yumi" || + instance_unique_ptr->PerInstanceName() == "tiger"); + } +} + +TEST_F(CvdInstanceDatabaseTest, AddInstancesInvalid) { + if (!SetUpOk() || !AddGroups({"yah_ong"})) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + auto kitty_group = db.FindGroup({kHomeField, Workspace() + "/" + "yah_ong"}); + if (!kitty_group.ok()) { + GTEST_SKIP() << "yah_ong" + << " group was not found"; + } + + ASSERT_FALSE(db.AddInstance("yah_ong", 1, "!yumi").ok()); + ASSERT_FALSE(db.AddInstance("yah_ong", 7, "ti ger").ok()); +} + +TEST_F(CvdInstanceDatabaseTest, FindByInstanceId) { + // The start of set up + if (!SetUpOk()) { + GTEST_SKIP() << Error().msg; + } + if (!AddGroups({"miau", "nyah"})) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + // per_instance_name could be the same if the parent groups are different. + std::vector miau_group_instance_id_name_pairs{ + {1, "8"}, {10, "tv-instance"}}; + std::vector nyah_group_instance_id_name_pairs{ + {7, "my_favorite_phone"}, {11, "tv-instance"}, {3, "3_"}}; + auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"}); + auto nyah_group = db.FindGroup({kHomeField, Workspace() + "/" + "nyah"}); + if (!miau_group.ok() || !nyah_group.ok()) { + GTEST_SKIP() << "miau or nyah group" + << " group was not found"; + } + if (!AddInstances("miau", miau_group_instance_id_name_pairs) || + !AddInstances("nyah", nyah_group_instance_id_name_pairs)) { + GTEST_SKIP() << Error().msg; + } + // The end of set up + + auto result1 = db.FindInstance(Query(kInstanceIdField, std::to_string(1))); + auto result10 = db.FindInstance(Query(kInstanceIdField, std::to_string(10))); + auto result7 = db.FindInstance(Query(kInstanceIdField, std::to_string(7))); + auto result11 = db.FindInstance(Query(kInstanceIdField, std::to_string(11))); + auto result3 = db.FindInstance(Query(kInstanceIdField, std::to_string(3))); + auto result_invalid = db.FindInstance(Query(kInstanceIdField, std::to_string(20))); + + ASSERT_TRUE(result1.ok()); + ASSERT_TRUE(result10.ok()); + ASSERT_TRUE(result7.ok()); + ASSERT_TRUE(result11.ok()); + ASSERT_TRUE(result3.ok()); + ASSERT_EQ(result1->Get().PerInstanceName(), "8"); + ASSERT_EQ(result10->Get().PerInstanceName(), "tv-instance"); + ASSERT_EQ(result7->Get().PerInstanceName(), "my_favorite_phone"); + ASSERT_EQ(result11->Get().PerInstanceName(), "tv-instance"); + ASSERT_EQ(result3->Get().PerInstanceName(), "3_"); + ASSERT_FALSE(result_invalid.ok()); +} + +TEST_F(CvdInstanceDatabaseTest, FindByPerInstanceName) { + // starting set up + if (!SetUpOk() || !AddGroups({"miau", "nyah"})) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + std::vector miau_group_instance_id_name_pairs{ + {1, "8"}, {10, "tv_instance"}}; + std::vector nyah_group_instance_id_name_pairs{ + {7, "my_favorite_phone"}, {11, "tv_instance"}}; + auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"}); + auto nyah_group = db.FindGroup({kHomeField, Workspace() + "/" + "nyah"}); + if (!miau_group.ok() || !nyah_group.ok()) { + GTEST_SKIP() << "miau or nyah " + << " group was not found"; + } + if (!AddInstances("miau", miau_group_instance_id_name_pairs) || + !AddInstances("nyah", nyah_group_instance_id_name_pairs)) { + GTEST_SKIP() << Error().msg; + } + // end of set up + + auto result1 = db.FindInstance(Query(kInstanceNameField, "8")); + auto result10_and_11 = db.FindInstances(Query(kInstanceNameField, "tv_instance")); + auto result7 = db.FindInstance(Query(kInstanceNameField, "my_favorite_phone")); + auto result_invalid = + db.FindInstance(Query(kInstanceNameField, "name_never_seen")); + + ASSERT_TRUE(result1.ok()); + ASSERT_TRUE(result10_and_11.ok()); + ASSERT_TRUE(result7.ok()); + ASSERT_EQ(result10_and_11->size(), 2); + ASSERT_EQ(result1->Get().InstanceId(), 1); + ASSERT_EQ(result7->Get().InstanceId(), 7); + ASSERT_FALSE(result_invalid.ok()); +} + +TEST_F(CvdInstanceDatabaseTest, FindInstancesByGroupName) { + // starting set up + if (!SetUpOk() || !AddGroups({"miau", "nyah"})) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + std::vector nyah_group_instance_id_name_pairs{ + {7, "my_favorite_phone"}, {11, "tv_instance"}}; + auto nyah_group = db.FindGroup({kHomeField, Workspace() + "/" + "nyah"}); + if (!nyah_group.ok()) { + GTEST_SKIP() << "nyah group was not found"; + } + if (!AddInstances("nyah", nyah_group_instance_id_name_pairs)) { + GTEST_SKIP() << Error().msg; + } + // end of set up + + auto result_nyah = db.FindInstances(Query(kGroupNameField, "nyah")); + auto result_invalid = db.FindInstance(Query(kGroupNameField, "name_never_seen")); + + ASSERT_TRUE(result_nyah.ok()); + std::set nyah_instance_names; + for (const auto& instance : *result_nyah) { + nyah_instance_names.insert(instance.Get().PerInstanceName()); + } + std::set expected{"my_favorite_phone", "tv_instance"}; + ASSERT_EQ(nyah_instance_names, expected); + ASSERT_FALSE(result_invalid.ok()); +} + +TEST_F(CvdInstanceDatabaseTest, FindGroupByPerInstanceName) { + // starting set up + if (!SetUpOk() || !AddGroups({"miau", "nyah"})) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + std::vector miau_group_instance_id_name_pairs{ + {1, "8"}, {10, "tv_instance"}}; + std::vector nyah_group_instance_id_name_pairs{ + {7, "my_favorite_phone"}, {11, "tv_instance"}}; + auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"}); + auto nyah_group = db.FindGroup({kHomeField, Workspace() + "/" + "nyah"}); + if (!miau_group.ok() || !nyah_group.ok()) { + GTEST_SKIP() << "miau or nyah " + << " group was not found"; + } + if (!AddInstances("miau", miau_group_instance_id_name_pairs) || + !AddInstances("nyah", nyah_group_instance_id_name_pairs)) { + GTEST_SKIP() << Error().msg; + } + // end of set up + + auto result_miau = db.FindGroups(Query(kInstanceNameField, "8")); + auto result_both = db.FindGroups(Query(kInstanceNameField, "tv_instance")); + auto result_nyah = db.FindGroups(Query(kInstanceNameField, "my_favorite_phone")); + auto result_invalid = db.FindGroups(Query(kInstanceNameField, "name_never_seen")); + + ASSERT_TRUE(result_miau.ok()); + ASSERT_TRUE(result_both.ok()); + ASSERT_TRUE(result_nyah.ok()); + ASSERT_TRUE(result_invalid.ok()); + ASSERT_EQ(result_miau->size(), 1); + ASSERT_EQ(result_both->size(), 2); + ASSERT_EQ(result_nyah->size(), 1); + ASSERT_TRUE(result_invalid->empty()) + << "result_invalid should be empty but with size: " + << result_invalid->size(); +} + +TEST_F(CvdInstanceDatabaseTest, AddInstancesTogether) { + // starting set up + if (!SetUpOk() || !AddGroups({"miau"})) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + std::vector miau_group_instance_id_name_pairs{ + {1, "8"}, {10, "tv_instance"}}; + auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"}); + if (!miau_group.ok()) { + GTEST_SKIP() << "miau group was not found"; + } + + auto add_result = db.AddInstances("miau", miau_group_instance_id_name_pairs); + ASSERT_TRUE(add_result.ok()) << add_result.error().Trace(); + + auto result_8 = db.FindInstance(Query(kInstanceNameField, "8")); + auto result_tv = db.FindInstance(Query(kInstanceNameField, "tv_instance")); + + ASSERT_TRUE(result_8.ok()) << result_8.error().Trace(); + ASSERT_TRUE(result_tv.ok()) << result_tv.error().Trace(); +} + +TEST_F(CvdInstanceDatabaseJsonTest, DumpLoadDumpCompare) { + // starting set up + if (!SetUpOk() || !AddGroups({"miau"})) { + GTEST_SKIP() << Error().msg; + } + auto& db = GetDb(); + std::vector miau_group_instance_id_name_pairs{ + {1, "8"}, {10, "tv_instance"}}; + auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"}); + if (!miau_group.ok()) { + GTEST_SKIP() << "miau group was not found"; + } + auto add_result = db.AddInstances("miau", miau_group_instance_id_name_pairs); + if (!add_result.ok()) { + GTEST_SKIP() << "Adding instances are not being tested in this test case."; + } + + /* + * Dumping to json, clearing up the DB, loading from the json, + * + */ + auto serialized_db = db.Serialize(); + if (!db.RemoveInstanceGroup("miau")) { + // not testing RemoveInstanceGroup + GTEST_SKIP() << "miau had to be added."; + } + auto json_parsing = ParseJson(serialized_db.toStyledString()); + ASSERT_TRUE(json_parsing.ok()) << serialized_db << std::endl + << " is not a valid json."; + auto load_result = db.LoadFromJson(serialized_db); + ASSERT_TRUE(load_result.ok()) << load_result.error().Trace(); + { + // re-look up the group and the instances + auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"}); + ASSERT_TRUE(miau_group.ok()) << miau_group.error().Trace(); + auto result_8 = db.FindInstance(Query(kInstanceNameField, "8")); + auto result_tv = db.FindInstance(Query(kInstanceNameField, "tv_instance")); + + ASSERT_TRUE(result_8.ok()) << result_8.error().Trace(); + ASSERT_TRUE(result_tv.ok()) << result_tv.error().Trace(); + } +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_record_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_record_test.cpp new file mode 100644 index 0000000000..d9485a8444 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_record_test.cpp @@ -0,0 +1,90 @@ +// +// Copyright (C) 2022 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. + +#include + +#include "host/commands/cvd/selector/instance_group_record.h" +#include "host/commands/cvd/selector/instance_record.h" + +namespace cuttlefish { +namespace selector { + +/** + * Note that invalid inputs must be tested at the InstanceDatabase level + */ +TEST(CvdInstanceRecordUnitTest, Fields) { + LocalInstanceGroup parent_group( + {.group_name = "super", + .home_dir = "/home/user", + .host_artifacts_path = "/home/user/download/bin", + .product_out_path = "/home/user/download/bin"}); + if (!parent_group.AddInstance(3, "phone").ok()) { + /* + * Here's why we skip the test rather than see it as a failure. + * + * 1. The test is specifically designed for operations in + * LocalInstanceRecord. + * 2. Adding instance to a group is tested in another test suites designed + * for LocalInstanceGroup. It's a failure there but not here. + * + */ + GTEST_SKIP() << "Failed to add instance group. Set up failed."; + } + auto& instances = parent_group.Instances(); + auto& instance = *instances.cbegin(); + + ASSERT_EQ(instance->InstanceId(), 3); + ASSERT_EQ(instance->InternalName(), "3"); + ASSERT_EQ(instance->PerInstanceName(), "phone"); + ASSERT_EQ(instance->InternalDeviceName(), "cvd-3"); + ASSERT_EQ(instance->DeviceName(), "super-phone"); + ASSERT_EQ(std::addressof(instance->ParentGroup()), + std::addressof(parent_group)); +} + +/** + * Note that invalid inputs must be tested at the InstanceDatabase level + */ +TEST(CvdInstanceRecordUnitTest, Copy) { + LocalInstanceGroup parent_group( + {.group_name = "super", + .home_dir = "/home/user", + .host_artifacts_path = "/home/user/download/bin", + .product_out_path = "/home/user/download/bin"}); + if (!parent_group.AddInstance(3, "phone").ok()) { + /* + * Here's why we skip the test rather than see it as a failure. + * + * 1. The test is specifically designed for operations in + * LocalInstanceRecord. + * 2. Adding instance to a group is tested in another test suites designed + * for LocalInstanceGroup. It's a failure there but not here. + * + */ + GTEST_SKIP() << "Failed to add instance group. Set up failed."; + } + auto& instances = parent_group.Instances(); + auto& instance = *instances.cbegin(); + auto copy = instance->GetCopy(); + + ASSERT_EQ(instance->InstanceId(), copy.InstanceId()); + ASSERT_EQ(instance->InternalName(), copy.InternalName()); + ASSERT_EQ(instance->PerInstanceName(), copy.PerInstanceName()); + ASSERT_EQ(instance->InternalDeviceName(), copy.InternalDeviceName()); + ASSERT_EQ(instance->DeviceName(), copy.DeviceName()); +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_helper.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_helper.cpp new file mode 100644 index 0000000000..45a0f889db --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_helper.cpp @@ -0,0 +1,41 @@ +// +// Copyright (C) 2022 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. + +#include "host/commands/cvd/unittests/selector/parser_ids_helper.h" + +#include +#include + +#include + +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { +namespace selector { + +InstanceIdTest::InstanceIdTest() { + auto cuttlefish_instance = GetParam().cuttlefish_instance; + if (cuttlefish_instance) { + envs_[kCuttlefishInstanceEnvVarName] = cuttlefish_instance.value(); + } + cmd_args_ = android::base::Tokenize(GetParam().cmd_args, " "); + selector_args_ = android::base::Tokenize(GetParam().selector_args, " "); + expected_ids_ = GetParam().expected_ids; + expected_result_ = GetParam().expected_result; + requested_num_instances_ = GetParam().requested_num_instances; +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_helper.h b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_helper.h new file mode 100644 index 0000000000..db017085b2 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_helper.h @@ -0,0 +1,52 @@ +// +// Copyright (C) 2022 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 + +#include +#include +#include +#include + +#include + +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace selector { + +struct InstanceIdTestInput { + std::string cmd_args; + std::string selector_args; + std::optional cuttlefish_instance; + std::optional> expected_ids; + unsigned requested_num_instances; + bool expected_result; +}; + +class InstanceIdTest : public testing::TestWithParam { + protected: + InstanceIdTest(); + + bool expected_result_; + unsigned requested_num_instances_; + std::optional> expected_ids_; + cvd_common::Args cmd_args_; + cvd_common::Args selector_args_; + cvd_common::Envs envs_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_test.cpp new file mode 100644 index 0000000000..9eb2618b4b --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_test.cpp @@ -0,0 +1,122 @@ +// +// Copyright (C) 2022 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. + +#include +#include + +#include "host/commands/cvd/selector/start_selector_parser.h" +#include "host/commands/cvd/unittests/selector/parser_ids_helper.h" + +namespace cuttlefish { +namespace selector { + +TEST_P(InstanceIdTest, InstanceIdCalculation) { + auto parser = StartSelectorParser::ConductSelectFlagsParser(selector_args_, + cmd_args_, envs_); + + ASSERT_EQ(parser.ok(), expected_result_); + if (!expected_result_) { + return; + } + ASSERT_EQ(parser->InstanceIds(), expected_ids_); + ASSERT_EQ(parser->RequestedNumInstances(), requested_num_instances_); +} + +INSTANTIATE_TEST_SUITE_P( + CvdParser, InstanceIdTest, + testing::Values( + InstanceIdTestInput{.cuttlefish_instance = std::nullopt, + .expected_ids = std::nullopt, + .requested_num_instances = 1, + .expected_result = true}, + InstanceIdTestInput{.cuttlefish_instance = "8", + .expected_ids = std::vector{8}, + .requested_num_instances = 1, + .expected_result = true}, + InstanceIdTestInput{.cmd_args = "--num_instances=2", + .expected_ids = std::nullopt, + .requested_num_instances = 2, + .expected_result = true}, + InstanceIdTestInput{.cmd_args = "--num_instances=2", + .cuttlefish_instance = "8", + .expected_ids = std::vector{8, 9}, + .requested_num_instances = 2, + .expected_result = true}, + InstanceIdTestInput{ + .cmd_args = "--base_instance_num=10 --num_instances=2", + .cuttlefish_instance = "8", + .expected_ids = std::vector{10, 11}, + .requested_num_instances = 2, + .expected_result = true}, + InstanceIdTestInput{.cmd_args = "--instance_nums 2", + .cuttlefish_instance = std::nullopt, + .expected_ids = std::vector{2}, + .requested_num_instances = 1, + .expected_result = true}, + InstanceIdTestInput{.cmd_args = "--instance_nums 2,5,6", + .cuttlefish_instance = std::nullopt, + .expected_ids = std::vector{2, 5, 6}, + .requested_num_instances = 3, + .expected_result = true}, + InstanceIdTestInput{ + .cmd_args = "--instance_nums 2,5,6 --num_instances=3", + .cuttlefish_instance = std::nullopt, + .expected_ids = std::vector{2, 5, 6}, + .requested_num_instances = 3, + .expected_result = true}, + InstanceIdTestInput{ + .cmd_args = "--instance_nums 2,5,6 --num_instances=3", + .selector_args = "--instance_name=c-1,c-3,c-5", + .cuttlefish_instance = std::nullopt, + .expected_ids = std::vector{2, 5, 6}, + .requested_num_instances = 3, + .expected_result = true}, + InstanceIdTestInput{.selector_args = "--instance_name=c-1,c-3,c-5", + .cuttlefish_instance = std::nullopt, + .expected_ids = std::nullopt, + .requested_num_instances = 3, + .expected_result = true}, + // CUTTLEFISH_INSTANCE should be ignored + InstanceIdTestInput{ + .cmd_args = "--instance_nums 2,5,6 --num_instances=3", + .cuttlefish_instance = "8", + .expected_ids = std::vector{2, 5, 6}, + .requested_num_instances = 3, + .expected_result = true}, + // instance_nums and num_instances mismatch + InstanceIdTestInput{ + .cmd_args = "--instance_nums 2,5,6 --num_instances=7", + .cuttlefish_instance = std::nullopt, + .expected_ids = std::vector{2, 5, 6}, + .requested_num_instances = 3, + .expected_result = false}, + // --instance_name requested 2 instances while instance_nums 3. + InstanceIdTestInput{ + .cmd_args = "--num_instances=3 --instance_nums 2,5,6", + .selector_args = "--instance_name=c-1,c-3", + .cuttlefish_instance = std::nullopt, + .expected_ids = std::vector{2, 5, 6}, + .requested_num_instances = 3, + .expected_result = false}, + // --base_instance_num is not allowed with --instance_nums + InstanceIdTestInput{ + .cmd_args = "--instance_nums 2,5,6 --base_instance_num=7", + .cuttlefish_instance = std::nullopt, + .expected_ids = std::vector{2, 5, 6}, + .requested_num_instances = 3, + .expected_result = false})); + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_helper.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_helper.cpp new file mode 100644 index 0000000000..812ef2570d --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_helper.cpp @@ -0,0 +1,39 @@ +// +// Copyright (C) 2022 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. + +#include "host/commands/cvd/unittests/selector/parser_names_helper.h" + +#include +#include + +#include "host/commands/cvd/selector/selector_option_parser_utils.h" + +namespace cuttlefish { +namespace selector { + +ValidNamesTest::ValidNamesTest() { Init(); } + +void ValidNamesTest::Init() { + auto [input, expected_output] = GetParam(); + selector_args_ = android::base::Tokenize(input, " "); + expected_output_ = std::move(expected_output); +} + +InvalidNamesTest::InvalidNamesTest() { + selector_args_ = android::base::Tokenize(GetParam(), " "); +} + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_helper.h b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_helper.h new file mode 100644 index 0000000000..ee06838ff6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_helper.h @@ -0,0 +1,60 @@ +// +// Copyright (C) 2022 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 + +#include +#include +#include +#include +#include + +#include + +#include "host/commands/cvd/selector/start_selector_parser.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace selector { + +struct ExpectedOutput { + std::optional> names; + std::optional group_name; + std::optional> per_instance_names; +}; + +struct InputOutput { + std::string input; + ExpectedOutput expected; +}; + +class ValidNamesTest : public testing::TestWithParam { + protected: + ValidNamesTest(); + void Init(); + + cvd_common::Args selector_args_; + ExpectedOutput expected_output_; +}; + +class InvalidNamesTest : public testing::TestWithParam { + protected: + InvalidNamesTest(); + + cvd_common::Args selector_args_; +}; + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_test.cpp new file mode 100644 index 0000000000..c22eab3996 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_test.cpp @@ -0,0 +1,86 @@ +// +// Copyright (C) 2022 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. + +#include +#include + +#include "host/commands/cvd/unittests/selector/parser_names_helper.h" + +namespace cuttlefish { +namespace selector { + +TEST_P(ValidNamesTest, ValidInputs) { + auto parser = StartSelectorParser::ConductSelectFlagsParser( + selector_args_, cvd_common::Args{}, cvd_common::Envs{}); + + ASSERT_TRUE(parser.ok()); +} + +/** + * Note that invalid inputs must be tested at the InstanceDatabase level + */ +TEST_P(ValidNamesTest, FieldsNoSubstring) { + auto parser = StartSelectorParser::ConductSelectFlagsParser( + selector_args_, cvd_common::Args{}, cvd_common::Envs{}); + + ASSERT_TRUE(parser.ok()); + ASSERT_EQ(parser->GroupName(), expected_output_.group_name); + ASSERT_EQ(parser->PerInstanceNames(), expected_output_.per_instance_names); +} + +INSTANTIATE_TEST_SUITE_P( + CvdParser, ValidNamesTest, + testing::Values( + InputOutput{.input = "--group_name=cf", + .expected = ExpectedOutput{.group_name = "cf"}}, + InputOutput{.input = "--instance_name=cvd,cf", + .expected = ExpectedOutput{.per_instance_names = + std::vector{ + "cvd", "cf"}}}, + InputOutput{.input = "--instance_name=09-1,tv-2 --group_name cf", + .expected = ExpectedOutput{.group_name = "cf", + .per_instance_names = + std::vector{ + "09-1", "tv-2"}}}, + InputOutput{ + .input = "--group_name=cf --instance_name 09", + .expected = ExpectedOutput{.group_name = "cf", + .per_instance_names = + std::vector{"09"}}}, + InputOutput{.input = "--group_name=my_cool --instance_name=phone-1,tv", + .expected = ExpectedOutput{.group_name = "my_cool", + .per_instance_names = + std::vector{ + "phone-1", "tv"}}}, + InputOutput{ + .input = "--instance_name=my-cool", + .expected = ExpectedOutput{ + .per_instance_names = std::vector{"my-cool"}}})); + +TEST_P(InvalidNamesTest, InvalidInputs) { + auto parser = StartSelectorParser::ConductSelectFlagsParser( + selector_args_, cvd_common::Args{}, cvd_common::Envs{}); + + ASSERT_FALSE(parser.ok()); +} + +INSTANTIATE_TEST_SUITE_P(CvdParser, InvalidNamesTest, + testing::Values("--group_name", "--group_name=?34", + "--group_name=ab-cd", + "--group_name=3a", "--instance_name", + "--instance_name=*7a")); + +} // namespace selector +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/autogen_ids_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/autogen_ids_test.cpp new file mode 100644 index 0000000000..a0e3f8c0b6 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/autogen_ids_test.cpp @@ -0,0 +1,72 @@ +// +// Copyright (C) 2023 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. + +#include +#include + +#include + +#include "common/libs/utils/contains.h" +#include "host/commands/cvd/types.h" +#include "host/commands/cvd/unittests/server/cmd_runner.h" +#include "host/commands/cvd/unittests/server/utils.h" + +namespace cuttlefish { + +TEST(CvdAutoGenId, CvdTwoFollowedByFive) { + cvd_common::Envs envs; + envs["HOME"] = StringFromEnv("HOME", ""); + CmdRunner::Run("cvd reset -y", envs); + + cvd_common::Args start_two_instances_args{ + "cvd", + "start", + "--report_anonymous_usage_stats=yes", + "--daemon", + "--norestart_subprocesses", + "--num_instances=2"}; + cvd_common::Args start_three_instances_args{ + "cvd", + "start", + "--report_anonymous_usage_stats=yes", + "--daemon", + "--norestart_subprocesses", + "--num_instances=3"}; + + auto cmd_start_two = CmdRunner::Run(start_two_instances_args, envs); + ASSERT_TRUE(cmd_start_two.Success()) << cmd_start_two.Stderr(); + auto cmd_fleet = CmdRunner::Run("cvd fleet", envs); + ASSERT_TRUE(cmd_fleet.Success()) << cmd_fleet.Stderr(); + ASSERT_EQ(NumberOfOccurrences(cmd_fleet.Stdout(), "instance_name"), 2) + << cmd_fleet.Stdout(); + + auto cmd_start_three = CmdRunner::Run(start_three_instances_args, envs); + ASSERT_TRUE(cmd_start_three.Success()) << cmd_start_three.Stderr(); + cmd_fleet = CmdRunner::Run("cvd fleet", envs); + ASSERT_TRUE(cmd_fleet.Success()) << cmd_fleet.Stderr(); + ASSERT_EQ(NumberOfOccurrences(cmd_fleet.Stdout(), "instance_name"), 5) + << cmd_fleet.Stdout(); + + auto cmd_stop = CmdRunner::Run("cvd reset -y", envs); + ASSERT_TRUE(cmd_stop.Success()) << cmd_stop.Stderr(); + + cmd_fleet = CmdRunner::Run("cvd fleet", envs); + ASSERT_FALSE(Contains(cmd_fleet.Stdout(), "instance_name")); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/basic_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/basic_test.cpp new file mode 100644 index 0000000000..ad83ce4eb4 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/basic_test.cpp @@ -0,0 +1,53 @@ +// +// Copyright (C) 2023 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. + +#include +#include + +#include + +#include "common/libs/utils/contains.h" +#include "host/commands/cvd/types.h" +#include "host/commands/cvd/unittests/server/cmd_runner.h" + +namespace cuttlefish { + +TEST(CvdBasic, CvdDefaultStart) { + cvd_common::Envs envs; + const auto home_dir = StringFromEnv("HOME", ""); + envs["HOME"] = home_dir; + CmdRunner::Run("cvd reset -y", envs); + + cvd_common::Args start_args{"cvd", "start", + "--report_anonymous_usage_stats=yes", "--daemon"}; + + auto cmd_start = CmdRunner::Run(start_args, envs); + ASSERT_TRUE(cmd_start.Success()) << cmd_start.Stderr(); + + auto cmd_fleet = CmdRunner::Run("cvd fleet", envs); + ASSERT_TRUE(cmd_fleet.Success()) << cmd_fleet.Stderr(); + ASSERT_TRUE(Contains(cmd_fleet.Stdout(), home_dir)); + + auto cmd_stop = CmdRunner::Run("cvd stop", envs); + ASSERT_TRUE(cmd_stop.Success()) << cmd_stop.Stderr(); + + cmd_fleet = CmdRunner::Run("cvd fleet", envs); + ASSERT_FALSE(Contains(cmd_fleet.Stdout(), home_dir)); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/clear_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/clear_test.cpp new file mode 100644 index 0000000000..5693499be1 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/clear_test.cpp @@ -0,0 +1,77 @@ +// +// Copyright (C) 2023 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. + +#include +#include + +#include + +#include "common/libs/utils/contains.h" +#include "host/commands/cvd/types.h" +#include "host/commands/cvd/unittests/server/cmd_runner.h" +#include "host/commands/cvd/unittests/server/utils.h" + +namespace cuttlefish { + +TEST(CvdClear, ClearAfterThreeStarts) { + cvd_common::Envs envs; + envs["HOME"] = StringFromEnv("HOME", ""); + CmdRunner::Run("cvd reset -y", envs); + + cvd_common::Args start_two_instances_args{ + "cvd", + "start", + "--report_anonymous_usage_stats=yes", + "--daemon", + "--norestart_subprocesses", + "--num_instances=2"}; + cvd_common::Args start_three_instances_args{ + "cvd", + "start", + "--report_anonymous_usage_stats=yes", + "--daemon", + "--norestart_subprocesses", + "--num_instances=3"}; + cvd_common::Args start_one_instances_args{ + "cvd", + "start", + "--report_anonymous_usage_stats=yes", + "--daemon", + "--norestart_subprocesses", + "--num_instances=1"}; + + auto cmd_start_two = CmdRunner::Run(start_two_instances_args, envs); + ASSERT_TRUE(cmd_start_two.Success()) << cmd_start_two.Stderr(); + auto cmd_start_three = CmdRunner::Run(start_three_instances_args, envs); + ASSERT_TRUE(cmd_start_three.Success()) << cmd_start_three.Stderr(); + auto cmd_start_one = CmdRunner::Run(start_one_instances_args, envs); + ASSERT_TRUE(cmd_start_one.Success()) << cmd_start_one.Stderr(); + + auto cmd_fleet = CmdRunner::Run("cvd fleet", envs); + ASSERT_TRUE(cmd_fleet.Success()) << cmd_fleet.Stderr(); + ASSERT_EQ(NumberOfOccurrences(cmd_fleet.Stdout(), "instance_name"), 2 + 3 + 1) + << cmd_fleet.Stdout(); + + auto cmd_stop = CmdRunner::Run("cvd clear", envs); + ASSERT_TRUE(cmd_stop.Success()) << cmd_stop.Stderr(); + + cmd_fleet = CmdRunner::Run("cvd fleet", envs); + ASSERT_FALSE(Contains(cmd_fleet.Stdout(), "instance_name")); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/cmd_runner.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/cmd_runner.cpp new file mode 100644 index 0000000000..07e4e877b1 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/cmd_runner.cpp @@ -0,0 +1,62 @@ +// +// Copyright (C) 2023 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. + +#include "host/commands/cvd/unittests/server/cmd_runner.h" + +#include + +namespace cuttlefish { + +CmdResult::CmdResult(const std::string& stdout_str, + const std::string& stderr_str, const int ret_code) + : stdout_{stdout_str}, stderr_{stderr_str}, code_{ret_code} {} + +CmdResult CmdRunner::Run(const cvd_common::Args& args, + const cvd_common::Envs& envs) { + if (args.empty() || args.front().empty()) { + return CmdResult("", "Empty or invalid command", -1); + } + const auto& cmd = args.front(); + cvd_common::Args cmd_args{args.begin() + 1, args.end()}; + CmdRunner cmd_runner(Command(cmd), cmd_args, envs); + return cmd_runner.Run(); +} + +CmdResult CmdRunner::Run(const std::string& args, + const cvd_common::Envs& envs) { + return CmdRunner::Run(android::base::Tokenize(args, " "), envs); +} + +CmdRunner::CmdRunner(Command&& cmd, const cvd_common::Args& args, + const cvd_common::Envs& envs) + : cmd_(std::move(cmd)) { + for (const auto& arg : args) { + cmd_.AddParameter(arg); + } + for (const auto& [key, value] : envs) { + cmd_.AddEnvironmentVariable(key, value); + } +} + +CmdResult CmdRunner::Run() { + std::string stdout_str; + std::string stderr_str; + auto ret_code = + RunWithManagedStdio(std::move(cmd_), nullptr, std::addressof(stdout_str), + std::addressof(stderr_str)); + return CmdResult(stdout_str, stderr_str, ret_code); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/cmd_runner.h b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/cmd_runner.h new file mode 100644 index 0000000000..13eb943e41 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/cmd_runner.h @@ -0,0 +1,72 @@ +// +// Copyright (C) 2023 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 + +#include +#include +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/subprocess.h" +#include "host/commands/cvd/types.h" + +namespace cuttlefish { + +class CmdResult { + public: + CmdResult(const std::string& stdout, const std::string& stderr, + const int ret_code); + const std::string& Stdout() const { return stdout_; } + const std::string& Stderr() const { return stderr_; } + int Code() const { return code_; } + bool Success() const { return code_ == 0; } + + private: + std::string stdout_; + std::string stderr_; + int code_; +}; + +class CmdRunner { + public: + template < + typename... CmdArgs, + typename std::enable_if<(sizeof...(CmdArgs) >= 1), bool>::type = true> + static CmdResult Run(const std::string& exec, const cvd_common::Envs& envs, + CmdArgs&&... cmd_args) { + cvd_common::Args args; + args.reserve(sizeof...(CmdArgs)); + (args.emplace_back(std::forward(cmd_args)), ...); + CmdRunner cmd_runner(Command(exec), args, envs); + return cmd_runner.Run(); + } + static CmdResult Run(const cvd_common::Args& args, + const cvd_common::Envs& envs); + static CmdResult Run(const std::string& args, const cvd_common::Envs& envs); + + private: + CmdRunner(Command&& cmd, const cvd_common::Args& args, + const cvd_common::Envs& envs); + + CmdResult Run(); + + Command cmd_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/collect_flags_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/collect_flags_test.cpp new file mode 100644 index 0000000000..7d283a1960 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/collect_flags_test.cpp @@ -0,0 +1,62 @@ +// +// Copyright (C) 2023 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. + +#include +#include +#include + +#include + +#include +#include + +#include "host/commands/cvd/server_command/flags_collector.h" +#include "host/commands/cvd/types.h" +#include "host/commands/cvd/unittests/server/cmd_runner.h" + +namespace cuttlefish { + +TEST(CvdHelpFlagCollect, LauncCvd) { + cvd_common::Envs envs; + const auto home_dir = StringFromEnv("HOME", ""); + envs["HOME"] = home_dir; + const auto android_host_out = StringFromEnv( + "ANDROID_HOST_OUT", + android::base::Dirname(android::base::GetExecutableDirectory())); + envs["ANDROID_HOST_OUT"] = android_host_out; + const auto launch_cvd_path = android_host_out + "/bin/launch_cvd"; + if (!FileExists(launch_cvd_path)) { + GTEST_SKIP() << "Can't find " << launch_cvd_path << " for testing."; + } + CmdRunner::Run("cvd kill-server", envs); + cvd_common::Args helpxml_args{launch_cvd_path, "--helpxml"}; + + auto cmd_help_xml = CmdRunner::Run(helpxml_args, envs); + auto flags_opt = CollectFlagsFromHelpxml(cmd_help_xml.Stdout()); + + ASSERT_FALSE(cmd_help_xml.Stdout().empty()) << "output shouldn't be empty."; + ASSERT_TRUE(flags_opt); + auto& flags = *flags_opt; + auto daemon_flag_itr = std::find_if( + flags.cbegin(), flags.cend(), + [](const FlagInfoPtr& ptr) { return (ptr && ptr->Name() == "daemon"); }); + auto bad_flag_itr = std::find_if( + flags.cbegin(), flags.cend(), + [](const FlagInfoPtr& ptr) { return (ptr && ptr->Name() == "@bad@"); }); + ASSERT_NE(daemon_flag_itr, flags.cend()); + ASSERT_EQ(bad_flag_itr, flags.cend()); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/frontline_parser_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/frontline_parser_test.cpp new file mode 100644 index 0000000000..4509e17e88 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/frontline_parser_test.cpp @@ -0,0 +1,69 @@ +// +// Copyright (C) 2023 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. + +#include +#include +#include + +#include + +#include "host/commands/cvd/frontline_parser.h" + +namespace std { + +template +static std::ostream& operator<<(std::ostream& out, const std::vector& v) { + if (v.empty()) { + out << "{}"; + return out; + } + if (v.size() == 1) { + out << "{" << v.front() << "}"; + return out; + } + out << "{"; + for (size_t i = 0; i != v.size() - 1; i++) { + out << v.at(i) << ", "; + } + out << v.back() << "}"; + return out; +} + +} // namespace std + +namespace cuttlefish { + +TEST(FrontlineParserTest, CvdOnly) { + cvd_common::Args input{"cvd"}; + FlagCollection empty_flags; + FrontlineParser::ParserParam parser_param{.server_supported_subcmds = {}, + .internal_cmds = {}, + .all_args = {"cvd"}, + .cvd_flags = empty_flags}; + + auto result = FrontlineParser::Parse(parser_param); + + ASSERT_TRUE(result.ok()) << result.error().Trace(); + auto& parser_ptr = *result; + ASSERT_TRUE(parser_ptr); + ASSERT_EQ("cvd", parser_ptr->ProgPath()); + ASSERT_EQ(std::nullopt, parser_ptr->SubCmd()) + << (parser_ptr->SubCmd() ? std::string("nullopt") + : *parser_ptr->SubCmd()); + ASSERT_EQ(cvd_common::Args{}, parser_ptr->SubCmdArgs()); + ASSERT_EQ(cvd_common::Args{}, parser_ptr->CvdArgs()); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/help_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/help_test.cpp new file mode 100644 index 0000000000..a5de7052d2 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/help_test.cpp @@ -0,0 +1,140 @@ +// +// Copyright (C) 2023 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. + +#include +#include + +#include + +#include "common/libs/utils/contains.h" +#include "host/commands/cvd/types.h" +#include "host/commands/cvd/unittests/server/cmd_runner.h" + +namespace cuttlefish { +namespace { + +bool ContainsAll(const std::string& stream, + const std::vector& tokens) { + for (const auto& token : tokens) { + if (!Contains(stream, token)) { + return false; + } + } + return true; +} + +/* + * Sees if this might be cvd --help output. + * + * Not very accurate. + */ +bool MaybeCvdHelp(const CmdResult& result) { + const auto& stdout = result.Stdout(); + return ContainsAll(stdout, {"help", "start", "stop", "fleet"}); +} + +bool MaybeCvdStop(const CmdResult& result) { + const auto& stderr = result.Stderr(); + const auto& stdout = result.Stdout(); + return Contains(stderr, "cvd_internal_stop") || + Contains(stdout, "cvd_internal_stop") || + Contains(stderr, "stop_cvd") || Contains(stdout, "stop_cvd"); +} + +bool MaybeCvdStart(const CmdResult& result) { + const auto& stdout = result.Stdout(); + return ContainsAll(stdout, {"vhost", "modem", "daemon", "adb"}); +} + +} // namespace + +TEST(CvdDriver, CvdHelp) { + cvd_common::Envs envs; + CmdRunner::Run("cvd reset -y", envs); + + auto cmd_help = CmdRunner::Run("cvd help", envs); + auto cmd_dash_help = CmdRunner::Run("cvd --help", envs); + + ASSERT_TRUE(cmd_help.Success()) << cmd_help.Stderr(); + ASSERT_TRUE(MaybeCvdHelp(cmd_help)); + ASSERT_TRUE(cmd_dash_help.Success()) << cmd_dash_help.Stderr(); + ASSERT_TRUE(MaybeCvdHelp(cmd_dash_help)); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); +} + +TEST(CvdDriver, CvdOnly) { + cvd_common::Envs envs; + CmdRunner::Run("cvd reset -y", envs); + + auto cmd_help = CmdRunner::Run("cvd help", envs); + auto cmd_only = CmdRunner::Run("cvd", envs); + + ASSERT_TRUE(cmd_help.Success()) << cmd_help.Stderr(); + ASSERT_TRUE(cmd_only.Success()) << cmd_only.Stderr(); + ASSERT_EQ(cmd_help.Stdout(), cmd_only.Stdout()); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); +} + +// this test is expected to fail. included proactively. +TEST(CvdDriver, CvdHelpWrong) { + cvd_common::Envs envs; + CmdRunner::Run("cvd reset -y", envs); + + auto cmd_help_ref = CmdRunner::Run("cvd help", envs); + auto cmd_help_wrong = CmdRunner::Run("cvd help not_exist", envs); + + EXPECT_TRUE(cmd_help_ref.Success()) << cmd_help_ref.Stderr(); + EXPECT_TRUE(cmd_help_wrong.Success()) << cmd_help_wrong.Stderr(); + EXPECT_EQ(cmd_help_ref.Stdout(), cmd_help_wrong.Stdout()); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); +} + +TEST(CvdSubtool, CvdStopHelp) { + cvd_common::Envs envs; + CmdRunner::Run("cvd reset -y", envs); + + auto cmd_stop_help = CmdRunner::Run("cvd help stop", envs); + + ASSERT_TRUE(cmd_stop_help.Success()) << cmd_stop_help.Stderr(); + ASSERT_TRUE(MaybeCvdStop(cmd_stop_help)) + << "stderr: " << cmd_stop_help.Stderr() + << "stdout: " << cmd_stop_help.Stdout(); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); +} + +TEST(CvdSubtool, CvdStartHelp) { + cvd_common::Envs envs; + CmdRunner::Run("cvd reset -y", envs); + + auto cmd_start_help = CmdRunner::Run("cvd help start", envs); + + ASSERT_TRUE(cmd_start_help.Success()) << cmd_start_help.Stderr(); + ASSERT_TRUE(MaybeCvdStart(cmd_start_help)) + << "stderr: " << cmd_start_help.Stderr() + << "stdout: " << cmd_start_help.Stdout(); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/instance_ids_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/instance_ids_test.cpp new file mode 100644 index 0000000000..78b53dadad --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/instance_ids_test.cpp @@ -0,0 +1,79 @@ +// +// Copyright (C) 2023 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. + +#include +#include + +#include + +#include "common/libs/utils/contains.h" +#include "host/commands/cvd/types.h" +#include "host/commands/cvd/unittests/server/cmd_runner.h" +#include "host/commands/cvd/unittests/server/utils.h" + +namespace cuttlefish { + +TEST(CvdInstanceIds, CvdTakenInstanceIds) { + cvd_common::Envs envs; + envs["HOME"] = StringFromEnv("HOME", ""); + CmdRunner::Run("cvd reset -y", envs); + + cvd_common::Args start_1_2_args{"cvd", + "start", + "--report_anonymous_usage_stats=yes", + "--daemon", + "--norestart_subprocesses", + "--instance_nums=1,2"}; + cvd_common::Args start_3_args{"cvd", + "start", + "--report_anonymous_usage_stats=yes", + "--daemon", + "--norestart_subprocesses", + "--instance_nums=3"}; + cvd_common::Args start_4_5_6_args{"cvd", + "start", + "--report_anonymous_usage_stats=yes", + "--daemon", + "--norestart_subprocesses", + "--instance_nums=4,5,6"}; + cvd_common::Args start_5_7_args{"cvd", + "start", + "--report_anonymous_usage_stats=yes", + "--daemon", + "--norestart_subprocesses", + "--instance_nums=4,5,6"}; + + auto cmd_start_1_2 = CmdRunner::Run(start_1_2_args, envs); + auto cmd_start_3 = CmdRunner::Run(start_3_args, envs); + auto cmd_start_4_5_6 = CmdRunner::Run(start_4_5_6_args, envs); + ASSERT_TRUE(cmd_start_1_2.Success()) << cmd_start_1_2.Stderr(); + ASSERT_TRUE(cmd_start_3.Success()) << cmd_start_3.Stderr(); + ASSERT_TRUE(cmd_start_4_5_6.Success()) << cmd_start_4_5_6.Stderr(); + + auto cmd_fleet = CmdRunner::Run("cvd fleet", envs); + ASSERT_TRUE(cmd_fleet.Success()) << cmd_fleet.Stderr(); + ASSERT_EQ(NumberOfOccurrences(cmd_fleet.Stdout(), "instance_name"), 6) + << cmd_fleet.Stdout(); + + auto cmd_3_to_fail = CmdRunner::Run(start_3_args, envs); + auto cmd_5_7_to_fail = CmdRunner::Run(start_5_7_args, envs); + ASSERT_TRUE(cmd_start_3.Success()) << cmd_start_3.Stderr(); + ASSERT_TRUE(cmd_start_4_5_6.Success()) << cmd_start_4_5_6.Stderr(); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_helper.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_helper.cpp new file mode 100644 index 0000000000..4ef5bad57f --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_helper.cpp @@ -0,0 +1,42 @@ +// +// Copyright (C) 2023 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. + +#include "host/commands/cvd/unittests/server/local_instance_helper.h" + +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace acloud { + +CvdInstanceLocalTest::CvdInstanceLocalTest() { InitCmd(); } + +CmdResult CvdInstanceLocalTest::Execute(const std::string& cmd_) { + cvd_common::Envs envs; + CmdResult result = CmdRunner::Run(cmd_, envs); + + auto cmd_stop = CmdRunner::Run("cvd stop", envs); + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); + + return result; +} + +void CvdInstanceLocalTest::InitCmd() { + cvd_common::Envs envs; + CmdRunner::Run("cvd reset -y", envs); +} + +} // namespace acloud +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_helper.h b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_helper.h new file mode 100644 index 0000000000..0ec969400f --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_helper.h @@ -0,0 +1,41 @@ +// +// Copyright (C) 2023 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 + +#include + +#include + +#include "host/commands/cvd/unittests/server/cmd_runner.h" + +namespace cuttlefish { +namespace acloud { + +/** + * Creates a mock cmd_ command line, and execute the test flow + * + */ +class CvdInstanceLocalTest : public ::testing::Test { + protected: + CvdInstanceLocalTest(); + CmdResult Execute(const std::string& cmd_); + + private: + void InitCmd(); +}; + +} // namespace acloud +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_test.cpp new file mode 100644 index 0000000000..af8137cbed --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_test.cpp @@ -0,0 +1,142 @@ +// +// Copyright (C) 2023 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. + +#include +#include +#include + +#include + +#include "common/libs/utils/contains.h" +#include "host/commands/cvd/types.h" +#include "host/commands/cvd/unittests/server/local_instance_helper.h" + +namespace cuttlefish { +namespace acloud { + +TEST(CvdDriver, CvdLocalInstance) { + cvd_common::Envs envs; + CmdRunner::Run("cvd reset -y", envs); + + // 1st test normal case + auto cmd_local_instance_local_image = + CmdRunner::Run("cvd acloud create --local-instance --local-image", envs); + ASSERT_TRUE(cmd_local_instance_local_image.Success()) + << cmd_local_instance_local_image.Stderr(); + auto cmd_stop = CmdRunner::Run("cvd stop", envs); + ASSERT_TRUE(cmd_stop.Success()) << cmd_stop.Stderr(); + + // 2nd test random id input + std::random_device rd; + std::default_random_engine mt(rd()); + std::uniform_int_distribution dist(1, 10); + + // randomly generate instance id within 1-10, id 0 has been used + std::string id = std::to_string(dist(mt)); + std::string cmd_str = "cvd acloud create --local-instance " + id; + cmd_str += " --local-image"; + auto cmd_id = CmdRunner::Run(cmd_str, envs); + ASSERT_TRUE(cmd_id.Success()) << cmd_id.Stderr(); + + auto cmd_fleet = CmdRunner::Run("cvd fleet", envs); + ASSERT_TRUE(cmd_fleet.Success()) << cmd_fleet.Stderr(); + ASSERT_TRUE(Contains(cmd_fleet.Stdout(), "cvd-" + id)); + + cmd_stop = CmdRunner::Run("cvd stop", envs); + ASSERT_TRUE(cmd_stop.Success()) << cmd_stop.Stderr(); + + cmd_fleet = CmdRunner::Run("cvd fleet", envs); + ASSERT_TRUE(cmd_fleet.Success()) << cmd_fleet.Stderr(); + ASSERT_FALSE(Contains(cmd_fleet.Stdout(), "cvd-" + id)); + + // 3rd test local instance --local-boot-image + const auto product_out_dir = StringFromEnv("ANDROID_PRODUCT_OUT", ""); + cmd_str = + "cvd acloud create --local-instance --local-image --local-boot-image " + + product_out_dir; + cmd_str += "/boot.img"; + auto cmd_local_boot_image = CmdRunner::Run(cmd_str, envs); + ASSERT_TRUE(cmd_local_boot_image.Success()) << cmd_local_boot_image.Stderr(); + cmd_stop = CmdRunner::Run("cvd stop", envs); + ASSERT_TRUE(cmd_stop.Success()) << cmd_stop.Stderr(); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); +} + +TEST_F(CvdInstanceLocalTest, CvdLocalInstanceRemoteImage) { + // 4th test local instance, remote image, --branch, --build-id flags + auto cmd_result = Execute("cvd acloud create --local-instance --build-id " + "9759836 --branch git_master --build-target cf_x86_64_phone-userdebug " + "--bootloader-branch aosp_u-boot-mainline --bootloader-build-id " + "9602025 --bootloader-build-target u-boot_crosvm_x86_64"); + ASSERT_TRUE(cmd_result.Success()) << cmd_result.Stderr(); +} + +TEST(CvdDriver, CvdLocalInstanceRemoteImageKernelImage) { + cvd_common::Envs envs; + CmdRunner::Run("cvd reset -y", envs); + + // 5th test local instance, remote image, --kernel-branch, --kernel-build-id, + // --kernel-build-target, --image-download-dir --build-target flags + auto cmd_kernel_build = CmdRunner::Run( + "cvd acloud create --local-instance --branch " + "git_master --build-target cf_x86_64_phone-userdebug --kernel-branch " + "aosp_kernel-common-android13-5.10 --kernel-build-id 9600402 " + "--kernel-build-target kernel_virt_x86_64 --image-download-dir " + "/tmp/acloud_cvd_temp/test123", + envs); + ASSERT_TRUE(cmd_kernel_build.Success()) << cmd_kernel_build.Stderr(); + auto cmd_stop = CmdRunner::Run("cvd stop", envs); + // after this command, the 5.10 kernel image should be downloaded at + // /tmp/acloud_cvd_temp/test123/acloud_image_artifacts/9594220cf_x86_64_phone-userdebug + // I will re-use this pre-built kernel image for later testing + + // 6th test local instance, local-kernel-image, --branch + auto cmd_local_kernel_image = CmdRunner::Run( + "cvd acloud create --local-instance --branch git_master --build-target " + "cf_x86_64_phone-userdebug --local-kernel-image " + "/tmp/acloud_cvd_temp/test123/acloud_image_artifacts/" + "9695745cf_x86_64_phone-userdebug", + envs); + ASSERT_TRUE(cmd_local_kernel_image.Success()) + << cmd_local_kernel_image.Stderr(); + cmd_stop = CmdRunner::Run("cvd stop", envs); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); +} + +// CvdInstanceLocalTest is testing different flags with "cvd acloud create --local-instance" +TEST_F(CvdInstanceLocalTest, CvdLocalInstanceRemoteImageBootloader) { + // 7th test --bootloader-branch --bootloader-build-id + // --bootloader-build-target + auto cmd_result = Execute("cvd acloud create --local-instance " + "--branch git_master --build-target cf_x86_64_phone-userdebug " + "--bootloader-branch aosp_u-boot-mainline --bootloader-build-id 9602025 " + "--bootloader-build-target u-boot_crosvm_x86_64"); + ASSERT_TRUE(cmd_result.Success()) << cmd_result.Stderr(); +} + +TEST_F(CvdInstanceLocalTest, CvdLocalInstanceRemoteImageSystem) { + // 8th --system-branch, --system-build-id, --system-build-target + auto cmd_result = Execute("cvd acloud create --local-instance --branch git_master " + "--build-target cf_x86_64_phone-userdebug --system-branch git_master " + "--system-build-id 9684420 --system-build-target aosp_x86_64-userdebug"); + ASSERT_TRUE(cmd_result.Success()) << cmd_result.Stderr(); +} + +} // namespace acloud +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/snapshot_test.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/snapshot_test.cpp new file mode 100644 index 0000000000..ac6796448c --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/snapshot_test.cpp @@ -0,0 +1,87 @@ +// +// Copyright (C) 2023 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. + +#include +#include + +#include + +#include "common/libs/utils/contains.h" +#include "host/commands/cvd/types.h" +#include "host/commands/cvd/unittests/server/cmd_runner.h" +#include "host/commands/cvd/unittests/server/snapshot_test_helper.h" + +namespace cuttlefish { +namespace cvdsnapshot { + +TEST_F(CvdSnapshotTest, CvdSuspendResume) { + auto cmd_suspend = CmdRunner::Run("cvd suspend", envs); + ASSERT_TRUE(cmd_suspend.Success()) << cmd_suspend.Stderr(); + + auto cmd_resume = CmdRunner::Run("cvd resume", envs); + ASSERT_TRUE(cmd_resume.Success()) << cmd_resume.Stderr(); + + auto cmd_stop = CmdRunner::Run("cvd stop", envs); + ASSERT_TRUE(cmd_stop.Success()) << cmd_stop.Stderr(); +} + +TEST_F(CvdSnapshotTest, CvdSuspendSnapshotResume) { + auto cmd_suspend = CmdRunner::Run("cvd suspend", envs); + ASSERT_TRUE(cmd_suspend.Success()) << cmd_suspend.Stderr(); + + auto cmd_snapshot = CmdRunner::Run( + "cvd snapshot_take --snapshot_path=/tmp/snapshots/snapshot", envs); + ASSERT_TRUE(cmd_snapshot.Success()) << cmd_snapshot.Stderr(); + + auto cmd_resume = CmdRunner::Run("cvd resume", envs); + ASSERT_TRUE(cmd_resume.Success()) << cmd_resume.Stderr(); + + auto cmd_stop = CmdRunner::Run("cvd stop", envs); + ASSERT_TRUE(cmd_stop.Success()) << cmd_stop.Stderr(); + + auto cmd_rm = CmdRunner::Run("rm -rf /tmp/snapshots/snapshot", envs); + ASSERT_TRUE(cmd_rm.Success()) << cmd_rm.Stderr(); +} + +TEST_F(CvdSnapshotTest, CvdSuspendSnapshotResumeRestore) { + auto cmd_suspend = CmdRunner::Run("cvd suspend", envs); + ASSERT_TRUE(cmd_suspend.Success()) << cmd_suspend.Stderr(); + + auto cmd_snapshot = CmdRunner::Run( + "cvd snapshot_take --snapshot_path=/tmp/snapshots/snapshot", envs); + ASSERT_TRUE(cmd_snapshot.Success()) << cmd_snapshot.Stderr(); + + auto cmd_stop = CmdRunner::Run("cvd stop", envs); + ASSERT_TRUE(cmd_stop.Success()) << cmd_stop.Stderr(); + + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); + + cvd_common::Args start_args{"cvd", "start", + "--report_anonymous_usage_stats=yes", "--daemon", + "--snapshot_path=/tmp/snapshots/snapshot"}; + + auto cmd_start_2 = CmdRunner::Run(start_args, envs); + ASSERT_TRUE(cmd_start_2.Success()) << cmd_start_2.Stderr(); + + auto cmd_stop_2 = CmdRunner::Run("cvd stop", envs); + ASSERT_TRUE(cmd_stop_2.Success()) << cmd_stop_2.Stderr(); + + auto cmd_rm = CmdRunner::Run("rm -rf /tmp/snapshots/snapshot", envs); + ASSERT_TRUE(cmd_rm.Success()) << cmd_rm.Stderr(); +} + +} // namespace cvdsnapshot +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/snapshot_test_helper.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/snapshot_test_helper.cpp new file mode 100644 index 0000000000..b9fac0bc26 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/snapshot_test_helper.cpp @@ -0,0 +1,46 @@ +// +// Copyright (C) 2023 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. + +#include + +#include + +#include "host/commands/cvd/unittests/server/cmd_runner.h" + +#include "host/commands/cvd/types.h" + +namespace cuttlefish { +namespace cvdsnapshot { + +class CvdSnapshotTest : public ::testing::Test { + protected: + void SetUp() override { + CmdRunner::Run("cvd reset -y", envs); + + cvd_common::Args start_args{ + "cvd", "start", "--report_anonymous_usage_stats=yes", "--daemon"}; + + auto cmd_start = CmdRunner::Run(start_args, envs); + ASSERT_TRUE(cmd_start.Success()) << cmd_start.Stderr(); + }; + void TearDown() override { + // clean up for the next test + CmdRunner::Run("cvd reset -y", envs); + } + cvd_common::Envs envs; +}; + +} // namespace cvdsnapshot +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/utils.cpp b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/utils.cpp new file mode 100644 index 0000000000..2f211c1038 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/utils.cpp @@ -0,0 +1,30 @@ +// +// Copyright (C) 2023 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. + +#include "host/commands/cvd/unittests/server/utils.h" + +namespace cuttlefish { + +int NumberOfOccurrences(const std::string& str, const std::string& substr) { + int cnt = 0; + int pos = str.find(substr, 0); + while (pos != std::string::npos) { + ++cnt; + pos = str.find(substr, pos + 1); + } + return cnt; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/unittests/server/utils.h b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/utils.h new file mode 100644 index 0000000000..4bddab06e4 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/unittests/server/utils.h @@ -0,0 +1,24 @@ +// +// Copyright (C) 2023 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 + +#include + +namespace cuttlefish { + +int NumberOfOccurrences(const std::string& str, const std::string& substr); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/metrics/metrics_defs.h b/base/cvd/cuttlefish/host/commands/metrics/metrics_defs.h new file mode 100644 index 0000000000..f36b3a0b82 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/metrics/metrics_defs.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 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 + +namespace cuttlefish { + +enum MetricsExitCodes : int { + kSuccess=0, + kMetricsError=1, + kInvalidHostConfiguration=2, +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/config_constants.h b/base/cvd/cuttlefish/host/libs/config/config_constants.h new file mode 100644 index 0000000000..eaa71cab81 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/config_constants.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 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 + +namespace cuttlefish { + +inline constexpr char kLogcatSerialMode[] = "serial"; +inline constexpr char kLogcatVsockMode[] = "vsock"; + +inline constexpr char kDefaultUuidPrefix[] = + "699acfc4-c8c4-11e7-882b-5065f31dc1"; +inline constexpr char kCuttlefishConfigEnvVarName[] = "CUTTLEFISH_CONFIG_FILE"; +inline constexpr char kCuttlefishInstanceEnvVarName[] = "CUTTLEFISH_INSTANCE"; +inline constexpr char kVsocUserPrefix[] = "vsoc-"; +inline constexpr char kCvdNamePrefix[] = "cvd-"; +inline constexpr char kBootStartedMessage[] = "VIRTUAL_DEVICE_BOOT_STARTED"; +inline constexpr char kBootCompletedMessage[] = "VIRTUAL_DEVICE_BOOT_COMPLETED"; +inline constexpr char kBootFailedMessage[] = "VIRTUAL_DEVICE_BOOT_FAILED"; +inline constexpr char kMobileNetworkConnectedMessage[] = + "VIRTUAL_DEVICE_NETWORK_MOBILE_CONNECTED"; +inline constexpr char kWifiConnectedMessage[] = + "VIRTUAL_DEVICE_NETWORK_WIFI_CONNECTED"; +inline constexpr char kEthernetConnectedMessage[] = + "VIRTUAL_DEVICE_NETWORK_ETHERNET_CONNECTED"; +// TODO(b/131864854): Replace this with a string less likely to change +inline constexpr char kAdbdStartedMessage[] = + "init: starting service 'adbd'..."; +inline constexpr char kFastbootdStartedMessage[] = + "init: starting service 'fastbootd'..."; +inline constexpr char kFastbootStartedMessage[] = + "Listening for fastboot command on tcp"; +inline constexpr char kScreenChangedMessage[] = "VIRTUAL_DEVICE_SCREEN_CHANGED"; +inline constexpr char kDisplayPowerModeChangedMessage[] = + "VIRTUAL_DEVICE_DISPLAY_POWER_MODE_CHANGED"; +inline constexpr char kInternalDirName[] = "internal"; +inline constexpr char kGrpcSocketDirName[] = "grpc_socket"; +inline constexpr char kSharedDirName[] = "shared"; +inline constexpr char kLogDirName[] = "logs"; +inline constexpr char kCrosvmVarEmptyDir[] = "/var/empty"; +inline constexpr char kKernelLoadedMessage[] = "] Linux version"; +inline constexpr char kBootloaderLoadedMessage[] = "U-Boot 20"; +inline constexpr char kApName[] = "crosvm_openwrt"; + +inline constexpr int kDefaultInstance = 1; + +} diff --git a/base/cvd/cuttlefish/host/libs/config/config_fragment.h b/base/cvd/cuttlefish/host/libs/config/config_fragment.h new file mode 100644 index 0000000000..cc1bfc1cfd --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/config_fragment.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 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 + +#include +#include +#include + +namespace cuttlefish { + +class ConfigFragment { + public: + virtual ~ConfigFragment(); + + virtual std::string Name() const = 0; + virtual Json::Value Serialize() const = 0; + virtual bool Deserialize(const Json::Value&) = 0; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/config_utils.cpp b/base/cvd/cuttlefish/host/libs/config/config_utils.cpp new file mode 100644 index 0000000000..0ee99118fd --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/config_utils.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "host/libs/config/config_utils.h" + +#include + +#include +#include + +#include +#include + +#include "common/libs/utils/environment.h" +#include "host/libs/config/config_constants.h" + +namespace cuttlefish { + +int InstanceFromString(std::string instance_str) { + if (android::base::StartsWith(instance_str, kVsocUserPrefix)) { + instance_str = instance_str.substr(std::string(kVsocUserPrefix).size()); + } else if (android::base::StartsWith(instance_str, kCvdNamePrefix)) { + instance_str = instance_str.substr(std::string(kCvdNamePrefix).size()); + } + + int instance = std::stoi(instance_str); + if (instance <= 0) { + LOG(INFO) << "Failed to interpret \"" << instance_str << "\" as an id, " + << "using instance id " << kDefaultInstance; + return kDefaultInstance; + } + return instance; +} + +int InstanceFromEnvironment() { + std::string instance_str = StringFromEnv(kCuttlefishInstanceEnvVarName, ""); + if (instance_str.empty()) { + // Try to get it from the user instead + instance_str = StringFromEnv("USER", ""); + + if (instance_str.empty()) { + LOG(DEBUG) << kCuttlefishInstanceEnvVarName + << " and USER unset, using instance id " << kDefaultInstance; + return kDefaultInstance; + } + if (!android::base::StartsWith(instance_str, kVsocUserPrefix)) { + // No user or we don't recognize this user + LOG(DEBUG) << "Non-vsoc user, using instance id " << kDefaultInstance; + return kDefaultInstance; + } + } + return InstanceFromString(instance_str); +} + +int GetInstance() { + static int instance_id = InstanceFromEnvironment(); + return instance_id; +} + +int GetDefaultVsockCid() { + // we assume that this function is used to configure CuttlefishConfig once + static const int default_vsock_cid = 3 + GetInstance() - 1; + return default_vsock_cid; +} + +int GetVsockServerPort(const int base, + const int vsock_guest_cid /**< per instance guest cid */) { + return base + (vsock_guest_cid - 3); +} + +std::string GetGlobalConfigFileLink() { + return StringFromEnv("HOME", ".") + "/.cuttlefish_config.json"; +} + +std::string ForCurrentInstance(const char* prefix) { + std::ostringstream stream; + stream << prefix << std::setfill('0') << std::setw(2) << GetInstance(); + return stream.str(); +} +int ForCurrentInstance(int base) { return base + GetInstance() - 1; } + +std::string RandomSerialNumber(const std::string& prefix) { + const char hex_characters[] = "0123456789ABCDEF"; + std::srand(time(0)); + char str[10]; + for(int i=0; i<10; i++){ + str[i] = hex_characters[rand() % strlen(hex_characters)]; + } + return prefix + str; +} + +std::string DefaultHostArtifactsPath(const std::string& file_name) { + return (StringFromEnv("ANDROID_HOST_OUT", StringFromEnv("HOME", ".")) + "/") + + file_name; +} + +std::string HostBinaryDir() { + return DefaultHostArtifactsPath("bin"); +} + +std::string DefaultQemuBinaryDir() { + const std::string target_prod_str = StringFromEnv("TARGET_PRODUCT", ""); + if (HostArch() == Arch::X86_64 && + target_prod_str.find("arm") == std::string::npos) { + return HostBinaryDir(); + } + return "/usr/bin"; +} + +std::string HostBinaryPath(const std::string& binary_name) { +#ifdef __ANDROID__ + return binary_name; +#else + return HostBinaryDir() + "/" + binary_name; +#endif +} + +std::string HostUsrSharePath(const std::string& binary_name) { + return DefaultHostArtifactsPath("usr/share/" + binary_name); +} + +std::string DefaultGuestImagePath(const std::string& file_name) { + return (StringFromEnv("ANDROID_PRODUCT_OUT", StringFromEnv("HOME", "."))) + + file_name; +} + +bool HostSupportsQemuCli() { + static bool supported = +#ifdef __linux__ + std::system( + "/usr/lib/cuttlefish-common/bin/capability_query.py qemu_cli") == 0; +#else + true; +#endif + return supported; +} + +} diff --git a/base/cvd/cuttlefish/host/libs/config/config_utils.h b/base/cvd/cuttlefish/host/libs/config/config_utils.h new file mode 100644 index 0000000000..1f9ca28438 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/config_utils.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 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 + +#include + +namespace cuttlefish { + +// Returns the instance number as obtained from the +// *kCuttlefishInstanceEnvVarName environment variable or the username. +int GetInstance(); + +// Returns default Vsock CID, which is +// GetInstance() + 2 +int GetDefaultVsockCid(); + +// Calculates vsock server port number +// return base + (vsock_guest_cid - 3) +int GetVsockServerPort(const int base, + const int vsock_guest_cid); + +// Returns a path where the launcher puts a link to the config file which makes +// it easily discoverable regardless of what vm manager is in use +std::string GetGlobalConfigFileLink(); + +// These functions modify a given base value to make it different across +// different instances by appending the instance id in case of strings or adding +// it in case of integers. +std::string ForCurrentInstance(const char* prefix); +int ForCurrentInstance(int base); + +int InstanceFromString(std::string instance_str); + +// Returns a random serial number appeneded to a given prefix. +std::string RandomSerialNumber(const std::string& prefix); + +std::string DefaultHostArtifactsPath(const std::string& file); +std::string DefaultQemuBinaryDir(); +std::string HostBinaryPath(const std::string& file); +std::string HostUsrSharePath(const std::string& file); +std::string DefaultGuestImagePath(const std::string& file); +std::string DefaultEnvironmentPath(const char* environment_key, + const char* default_value, + const char* path); + +// Whether the host supports qemu +bool HostSupportsQemuCli(); + +} diff --git a/base/cvd/cuttlefish/host/libs/config/cuttlefish_config.cpp b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config.cpp new file mode 100644 index 0000000000..2bb66aa4b5 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config.cpp @@ -0,0 +1,801 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "host/libs/config/cuttlefish_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common/libs/utils/environment.h" +#include "common/libs/utils/files.h" +#include "host/libs/vm_manager/crosvm_manager.h" +#include "host/libs/vm_manager/gem5_manager.h" +#include "host/libs/vm_manager/qemu_manager.h" + +namespace cuttlefish { +namespace { + +const char* kInstances = "instances"; + +} // namespace + +const char* const kVhostUserVsockModeAuto = "auto"; +const char* const kVhostUserVsockModeTrue = "true"; +const char* const kVhostUserVsockModeFalse = "false"; + +const char* const kGpuModeAuto = "auto"; +const char* const kGpuModeDrmVirgl = "drm_virgl"; +const char* const kGpuModeGfxstream = "gfxstream"; +const char* const kGpuModeGfxstreamGuestAngle = "gfxstream_guest_angle"; +const char* const kGpuModeGfxstreamGuestAngleHostSwiftShader = + "gfxstream_guest_angle_host_swiftshader"; +const char* const kGpuModeGuestSwiftshader = "guest_swiftshader"; +const char* const kGpuModeNone = "none"; + +const char* const kGpuVhostUserModeAuto = "auto"; +const char* const kGpuVhostUserModeOn = "on"; +const char* const kGpuVhostUserModeOff = "off"; + +const char* const kHwComposerAuto = "auto"; +const char* const kHwComposerDrm = "drm"; +const char* const kHwComposerRanchu = "ranchu"; +const char* const kHwComposerNone = "none"; + +std::string DefaultEnvironmentPath(const char* environment_key, + const char* default_value, + const char* subpath) { + return StringFromEnv(environment_key, default_value) + "/" + subpath; +} + +ConfigFragment::~ConfigFragment() = default; + +static constexpr char kFragments[] = "fragments"; +bool CuttlefishConfig::LoadFragment(ConfigFragment& fragment) const { + if (!dictionary_->isMember(kFragments)) { + LOG(ERROR) << "Fragments member was missing"; + return false; + } + const Json::Value& json_fragments = (*dictionary_)[kFragments]; + if (!json_fragments.isMember(fragment.Name())) { + LOG(ERROR) << "Could not find a fragment called " << fragment.Name(); + return false; + } + return fragment.Deserialize(json_fragments[fragment.Name()]); +} +bool CuttlefishConfig::SaveFragment(const ConfigFragment& fragment) { + Json::Value& json_fragments = (*dictionary_)[kFragments]; + if (json_fragments.isMember(fragment.Name())) { + LOG(ERROR) << "Already have a fragment called " << fragment.Name(); + return false; + } + json_fragments[fragment.Name()] = fragment.Serialize(); + return true; +} + +static constexpr char kRootDir[] = "root_dir"; +std::string CuttlefishConfig::root_dir() const { + return (*dictionary_)[kRootDir].asString(); +} +void CuttlefishConfig::set_root_dir(const std::string& root_dir) { + (*dictionary_)[kRootDir] = root_dir; +} + +static constexpr char kVmManager[] = "vm_manager"; +std::string CuttlefishConfig::vm_manager() const { + return (*dictionary_)[kVmManager].asString(); +} +void CuttlefishConfig::set_vm_manager(const std::string& name) { + (*dictionary_)[kVmManager] = name; +} + +static constexpr char kApVmManager[] = "ap_vm_manager"; +std::string CuttlefishConfig::ap_vm_manager() const { + return (*dictionary_)[kApVmManager].asString(); +} +void CuttlefishConfig::set_ap_vm_manager(const std::string& name) { + (*dictionary_)[kApVmManager] = name; +} + +static SecureHal StringToSecureHal(std::string mode) { + std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower); + if (mode == "keymint") { + return SecureHal::Keymint; + } else if (mode == "gatekeeper") { + return SecureHal::Gatekeeper; + } else if (mode == "oemlock") { + return SecureHal::Oemlock; + } else { + return SecureHal::Unknown; + } +} + +static constexpr char kSecureHals[] = "secure_hals"; +std::set CuttlefishConfig::secure_hals() const { + std::set args_set; + for (auto& hal : (*dictionary_)[kSecureHals]) { + args_set.insert(StringToSecureHal(hal.asString())); + } + return args_set; +} +void CuttlefishConfig::set_secure_hals(const std::set& hals) { + Json::Value hals_json_obj(Json::arrayValue); + for (const auto& hal : hals) { + hals_json_obj.append(hal); + } + (*dictionary_)[kSecureHals] = hals_json_obj; +} + +static constexpr char kCrosvmBinary[] = "crosvm_binary"; +std::string CuttlefishConfig::crosvm_binary() const { + return (*dictionary_)[kCrosvmBinary].asString(); +} +void CuttlefishConfig::set_crosvm_binary(const std::string& crosvm_binary) { + (*dictionary_)[kCrosvmBinary] = crosvm_binary; +} + +bool CuttlefishConfig::IsCrosvm() const { return vm_manager() == "crosvm"; } + +static constexpr char kGem5DebugFlags[] = "gem5_debug_flags"; +std::string CuttlefishConfig::gem5_debug_flags() const { + return (*dictionary_)[kGem5DebugFlags].asString(); +} +void CuttlefishConfig::set_gem5_debug_flags(const std::string& gem5_debug_flags) { + (*dictionary_)[kGem5DebugFlags] = gem5_debug_flags; +} + +static constexpr char kWebRTCCertsDir[] = "webrtc_certs_dir"; +void CuttlefishConfig::set_webrtc_certs_dir(const std::string& certs_dir) { + (*dictionary_)[kWebRTCCertsDir] = certs_dir; +} +std::string CuttlefishConfig::webrtc_certs_dir() const { + return (*dictionary_)[kWebRTCCertsDir].asString(); +} + +static constexpr char kSigServerPort[] = "webrtc_sig_server_port"; +void CuttlefishConfig::set_sig_server_port(int port) { + (*dictionary_)[kSigServerPort] = port; +} +int CuttlefishConfig::sig_server_port() const { + return (*dictionary_)[kSigServerPort].asInt(); +} + +static constexpr char kSigServerAddress[] = "webrtc_sig_server_addr"; +void CuttlefishConfig::set_sig_server_address(const std::string& addr) { + (*dictionary_)[kSigServerAddress] = addr; +} +std::string CuttlefishConfig::sig_server_address() const { + return (*dictionary_)[kSigServerAddress].asString(); +} + +static constexpr char kSigServerPath[] = "webrtc_sig_server_path"; +void CuttlefishConfig::set_sig_server_path(const std::string& path) { + // Don't use SetPath here, it's a URL path not a file system path + (*dictionary_)[kSigServerPath] = path; +} +std::string CuttlefishConfig::sig_server_path() const { + return (*dictionary_)[kSigServerPath].asString(); +} + +static constexpr char kSigServerSecure[] = "webrtc_sig_server_secure"; +void CuttlefishConfig::set_sig_server_secure(bool secure) { + (*dictionary_)[kSigServerSecure] = secure; +} +bool CuttlefishConfig::sig_server_secure() const { + return (*dictionary_)[kSigServerSecure].asBool(); +} + +static constexpr char kSigServerStrict[] = "webrtc_sig_server_strict"; +void CuttlefishConfig::set_sig_server_strict(bool strict) { + (*dictionary_)[kSigServerStrict] = strict; +} +bool CuttlefishConfig::sig_server_strict() const { + return (*dictionary_)[kSigServerStrict].asBool(); +} + +static constexpr char kHostToolsVersion[] = "host_tools_version"; +void CuttlefishConfig::set_host_tools_version( + const std::map& versions) { + Json::Value json(Json::objectValue); + for (const auto& [key, value] : versions) { + json[key] = value; + } + (*dictionary_)[kHostToolsVersion] = json; +} +std::map CuttlefishConfig::host_tools_version() const { + if (!dictionary_->isMember(kHostToolsVersion)) { + return {}; + } + std::map versions; + const auto& elem = (*dictionary_)[kHostToolsVersion]; + for (auto it = elem.begin(); it != elem.end(); it++) { + versions[it.key().asString()] = it->asUInt(); + } + return versions; +} + +static constexpr char kEnableHostUwb[] = "enable_host_uwb"; +void CuttlefishConfig::set_enable_host_uwb(bool enable_host_uwb) { + (*dictionary_)[kEnableHostUwb] = enable_host_uwb; +} +bool CuttlefishConfig::enable_host_uwb() const { + return (*dictionary_)[kEnableHostUwb].asBool(); +} + +static constexpr char kEnableHostUwbConnector[] = "enable_host_uwb_connector"; +void CuttlefishConfig::set_enable_host_uwb_connector(bool enable_host_uwb) { + (*dictionary_)[kEnableHostUwbConnector] = enable_host_uwb; +} +bool CuttlefishConfig::enable_host_uwb_connector() const { + return (*dictionary_)[kEnableHostUwbConnector].asBool(); +} + +static constexpr char kPicaUciPort[] = "pica_uci_port"; +int CuttlefishConfig::pica_uci_port() const { + return (*dictionary_)[kPicaUciPort].asInt(); +} +void CuttlefishConfig::set_pica_uci_port(int pica_uci_port) { + (*dictionary_)[kPicaUciPort] = pica_uci_port; +} + +static constexpr char kEnableHostBluetooth[] = "enable_host_bluetooth"; +void CuttlefishConfig::set_enable_host_bluetooth(bool enable_host_bluetooth) { + (*dictionary_)[kEnableHostBluetooth] = enable_host_bluetooth; +} +bool CuttlefishConfig::enable_host_bluetooth() const { + return (*dictionary_)[kEnableHostBluetooth].asBool(); +} + +static constexpr char kEnableHostBluetoothConnector[] = + "enable_host_bluetooth_connector"; +void CuttlefishConfig::set_enable_host_bluetooth_connector(bool enable_host_bluetooth) { + (*dictionary_)[kEnableHostBluetoothConnector] = enable_host_bluetooth; +} +bool CuttlefishConfig::enable_host_bluetooth_connector() const { + return (*dictionary_)[kEnableHostBluetoothConnector].asBool(); +} + +static constexpr char kEnableAutomotiveProxy[] = "enable_automotive_proxy"; +void CuttlefishConfig::set_enable_automotive_proxy( + bool enable_automotive_proxy) { + (*dictionary_)[kEnableAutomotiveProxy] = enable_automotive_proxy; +} +bool CuttlefishConfig::enable_automotive_proxy() const { + return (*dictionary_)[kEnableAutomotiveProxy].asBool(); +} + +static constexpr char kEnableHostNfc[] = "enable_host_nfc"; +void CuttlefishConfig::set_enable_host_nfc(bool enable_host_nfc) { + (*dictionary_)[kEnableHostNfc] = enable_host_nfc; +} +bool CuttlefishConfig::enable_host_nfc() const { + return (*dictionary_)[kEnableHostNfc].asBool(); +} + +static constexpr char kEnableHostNfcConnector[] = "enable_host_nfc_connector"; +void CuttlefishConfig::set_enable_host_nfc_connector(bool enable_host_nfc) { + (*dictionary_)[kEnableHostNfcConnector] = enable_host_nfc; +} +bool CuttlefishConfig::enable_host_nfc_connector() const { + return (*dictionary_)[kEnableHostNfcConnector].asBool(); +} + +static constexpr char kCasimirInstanceNum[] = "casimir_instance_num"; +void CuttlefishConfig::set_casimir_instance_num(int casimir_instance_num) { + (*dictionary_)[kCasimirInstanceNum] = casimir_instance_num; +} +int CuttlefishConfig::casimir_instance_num() const { + return (*dictionary_)[kCasimirInstanceNum].asInt(); +} + +static constexpr char kCasimirArgs[] = "casimir_args"; +void CuttlefishConfig::set_casimir_args(const std::string& casimir_args) { + Json::Value args_json_obj(Json::arrayValue); + for (const auto& arg : android::base::Split(casimir_args, " ")) { + if (!arg.empty()) { + args_json_obj.append(arg); + } + } + (*dictionary_)[kCasimirArgs] = args_json_obj; +} +std::vector CuttlefishConfig::casimir_args() const { + std::vector casimir_args; + for (const Json::Value& arg : (*dictionary_)[kCasimirArgs]) { + casimir_args.push_back(arg.asString()); + } + return casimir_args; +} + +static constexpr char kCasimirNciPort[] = "casimir_nci_port"; +void CuttlefishConfig::set_casimir_nci_port(int port) { + (*dictionary_)[kCasimirNciPort] = port; +} +int CuttlefishConfig::casimir_nci_port() const { + return (*dictionary_)[kCasimirNciPort].asInt(); +} + +static constexpr char kCasimirRfPort[] = "casimir_rf_port"; +void CuttlefishConfig::set_casimir_rf_port(int port) { + (*dictionary_)[kCasimirRfPort] = port; +} +int CuttlefishConfig::casimir_rf_port() const { + return (*dictionary_)[kCasimirRfPort].asInt(); +} + +static constexpr char kEnableWifi[] = "enable_wifi"; +void CuttlefishConfig::set_enable_wifi(bool enable_wifi) { + (*dictionary_)[kEnableWifi] = enable_wifi; +} +bool CuttlefishConfig::enable_wifi() const { + return (*dictionary_)[kEnableWifi].asBool(); +} + +static constexpr char kNetsimRadios[] = "netsim_radios"; + +void CuttlefishConfig::netsim_radio_enable(NetsimRadio flag) { + if (dictionary_->isMember(kNetsimRadios)) { + // OR the radio to current set of radios + (*dictionary_)[kNetsimRadios] = (*dictionary_)[kNetsimRadios].asInt() | flag; + } else { + (*dictionary_)[kNetsimRadios] = flag; + } +} + +bool CuttlefishConfig::netsim_radio_enabled(NetsimRadio flag) const { + return (*dictionary_)[kNetsimRadios].asInt() & flag; +} + +static constexpr char kNetsimInstanceNum[] = "netsim_instance_num"; +int CuttlefishConfig::netsim_instance_num() const { + return (*dictionary_)[kNetsimInstanceNum].asInt(); +} +void CuttlefishConfig::set_netsim_instance_num(int netsim_instance_num) { + (*dictionary_)[kNetsimInstanceNum] = netsim_instance_num; +} + +static constexpr char kNetsimConnectorInstanceNum[] = + "netsim_connector_instance_num"; +int CuttlefishConfig::netsim_connector_instance_num() const { + return (*dictionary_)[kNetsimConnectorInstanceNum].asInt(); +} +void CuttlefishConfig::set_netsim_connector_instance_num( + int netsim_instance_num) { + (*dictionary_)[kNetsimConnectorInstanceNum] = netsim_instance_num; +} + +static constexpr char kNetsimArgs[] = "netsim_args"; +void CuttlefishConfig::set_netsim_args(const std::string& netsim_args) { + Json::Value args_json_obj(Json::arrayValue); + for (const auto& arg : android::base::Tokenize(netsim_args, " ")) { + args_json_obj.append(arg); + } + (*dictionary_)[kNetsimArgs] = args_json_obj; +} +std::vector CuttlefishConfig::netsim_args() const { + std::vector netsim_args; + for (const Json::Value& arg : (*dictionary_)[kNetsimArgs]) { + netsim_args.push_back(arg.asString()); + } + return netsim_args; +} + +static constexpr char kEnableMetrics[] = "enable_metrics"; +void CuttlefishConfig::set_enable_metrics(std::string enable_metrics) { + (*dictionary_)[kEnableMetrics] = + static_cast(cuttlefish::CuttlefishConfig::Answer::kUnknown); + if (!enable_metrics.empty()) { + switch (enable_metrics.at(0)) { + case 'y': + case 'Y': + (*dictionary_)[kEnableMetrics] = + static_cast(cuttlefish::CuttlefishConfig::Answer::kYes); + break; + case 'n': + case 'N': + (*dictionary_)[kEnableMetrics] = + static_cast(cuttlefish::CuttlefishConfig::Answer::kNo); + break; + } + } +} + +// validate the casting and conversion from json configs to +// CuttlefishConfig::Answer class +bool IsValidMetricsConfigs(int value) { + return value == static_cast(CuttlefishConfig::Answer::kUnknown) || + value == static_cast(CuttlefishConfig::Answer::kNo) || + value == static_cast(CuttlefishConfig::Answer::kYes); +} + +CuttlefishConfig::Answer CuttlefishConfig::enable_metrics() const { + int value = (*dictionary_)[kEnableMetrics].asInt(); + if (!IsValidMetricsConfigs(value)) { + LOG(ERROR) << "Invalid integer value for Answer enum"; + return static_cast(CuttlefishConfig::Answer::kUnknown); + } + return static_cast(value); +} + +static constexpr char kMetricsBinary[] = "metrics_binary"; +void CuttlefishConfig::set_metrics_binary(const std::string& metrics_binary) { + (*dictionary_)[kMetricsBinary] = metrics_binary; +} +std::string CuttlefishConfig::metrics_binary() const { + return (*dictionary_)[kMetricsBinary].asString(); +} + +static constexpr char kExtraKernelCmdline[] = "extra_kernel_cmdline"; +void CuttlefishConfig::set_extra_kernel_cmdline( + const std::string& extra_cmdline) { + Json::Value args_json_obj(Json::arrayValue); + for (const auto& arg : android::base::Split(extra_cmdline, " ")) { + args_json_obj.append(arg); + } + (*dictionary_)[kExtraKernelCmdline] = args_json_obj; +} +std::vector CuttlefishConfig::extra_kernel_cmdline() const { + std::vector cmdline; + for (const Json::Value& arg : (*dictionary_)[kExtraKernelCmdline]) { + cmdline.push_back(arg.asString()); + } + return cmdline; +} + +static constexpr char kVirtioMac80211Hwsim[] = "virtio_mac80211_hwsim"; +void CuttlefishConfig::set_virtio_mac80211_hwsim(bool virtio_mac80211_hwsim) { + (*dictionary_)[kVirtioMac80211Hwsim] = virtio_mac80211_hwsim; +} +bool CuttlefishConfig::virtio_mac80211_hwsim() const { + return (*dictionary_)[kVirtioMac80211Hwsim].asBool(); +} + +static constexpr char kApRootfsImage[] = "ap_rootfs_image"; +std::string CuttlefishConfig::ap_rootfs_image() const { + return (*dictionary_)[kApRootfsImage].asString(); +} +void CuttlefishConfig::set_ap_rootfs_image(const std::string& ap_rootfs_image) { + (*dictionary_)[kApRootfsImage] = ap_rootfs_image; +} + +static constexpr char kApKernelImage[] = "ap_kernel_image"; +std::string CuttlefishConfig::ap_kernel_image() const { + return (*dictionary_)[kApKernelImage].asString(); +} +void CuttlefishConfig::set_ap_kernel_image(const std::string& ap_kernel_image) { + (*dictionary_)[kApKernelImage] = ap_kernel_image; +} + +static constexpr char kRootcanalArgs[] = "rootcanal_args"; +void CuttlefishConfig::set_rootcanal_args(const std::string& rootcanal_args) { + Json::Value args_json_obj(Json::arrayValue); + for (const auto& arg : android::base::Split(rootcanal_args, " ")) { + args_json_obj.append(arg); + } + (*dictionary_)[kRootcanalArgs] = args_json_obj; +} +std::vector CuttlefishConfig::rootcanal_args() const { + std::vector rootcanal_args; + for (const Json::Value& arg : (*dictionary_)[kRootcanalArgs]) { + rootcanal_args.push_back(arg.asString()); + } + return rootcanal_args; +} + +static constexpr char kRootcanalHciPort[] = "rootcanal_hci_port"; +int CuttlefishConfig::rootcanal_hci_port() const { + return (*dictionary_)[kRootcanalHciPort].asInt(); +} +void CuttlefishConfig::set_rootcanal_hci_port(int rootcanal_hci_port) { + (*dictionary_)[kRootcanalHciPort] = rootcanal_hci_port; +} + +static constexpr char kRootcanalLinkPort[] = "rootcanal_link_port"; +int CuttlefishConfig::rootcanal_link_port() const { + return (*dictionary_)[kRootcanalLinkPort].asInt(); +} +void CuttlefishConfig::set_rootcanal_link_port(int rootcanal_link_port) { + (*dictionary_)[kRootcanalLinkPort] = rootcanal_link_port; +} + +static constexpr char kRootcanalLinkBlePort[] = "rootcanal_link_ble_port"; +int CuttlefishConfig::rootcanal_link_ble_port() const { + return (*dictionary_)[kRootcanalLinkBlePort].asInt(); +} +void CuttlefishConfig::set_rootcanal_link_ble_port( + int rootcanal_link_ble_port) { + (*dictionary_)[kRootcanalLinkBlePort] = rootcanal_link_ble_port; +} + +static constexpr char kRootcanalTestPort[] = "rootcanal_test_port"; +int CuttlefishConfig::rootcanal_test_port() const { + return (*dictionary_)[kRootcanalTestPort].asInt(); +} +void CuttlefishConfig::set_rootcanal_test_port(int rootcanal_test_port) { + (*dictionary_)[kRootcanalTestPort] = rootcanal_test_port; +} + +static constexpr char kSnapshotPath[] = "snapshot_path"; +std::string CuttlefishConfig::snapshot_path() const { + return (*dictionary_)[kSnapshotPath].asString(); +} +void CuttlefishConfig::set_snapshot_path(const std::string& snapshot_path) { + (*dictionary_)[kSnapshotPath] = snapshot_path; +} + +static constexpr char kStracedExecutables[] = "straced_host_executables"; +void CuttlefishConfig::set_straced_host_executables( + const std::set& straced_host_executables) { + Json::Value args_json_obj(Json::arrayValue); + for (const auto& arg : straced_host_executables) { + args_json_obj.append(arg); + } + (*dictionary_)[kStracedExecutables] = args_json_obj; +} +std::set CuttlefishConfig::straced_host_executables() const { + std::set straced_host_executables; + for (const Json::Value& arg : (*dictionary_)[kStracedExecutables]) { + straced_host_executables.insert(arg.asString()); + } + return straced_host_executables; +} + +static constexpr char kHostSandbox[] = "host_sandbox"; +bool CuttlefishConfig::host_sandbox() const { + return (*dictionary_)[kHostSandbox].asBool(); +} +void CuttlefishConfig::set_host_sandbox(bool host_sandbox) { + (*dictionary_)[kHostSandbox] = host_sandbox; +} + +/*static*/ CuttlefishConfig* CuttlefishConfig::BuildConfigImpl( + const std::string& path) { + auto ret = new CuttlefishConfig(); + if (ret) { + auto loaded = ret->LoadFromFile(path.c_str()); + if (!loaded) { + delete ret; + return nullptr; + } + } + return ret; +} + +/*static*/ std::unique_ptr +CuttlefishConfig::GetFromFile(const std::string& path) { + return std::unique_ptr(BuildConfigImpl(path)); +} + +// Creates the (initially empty) config object and populates it with values from +// the config file if the CUTTLEFISH_CONFIG_FILE env variable is present. +// Returns nullptr if there was an error loading from file +/*static*/ const CuttlefishConfig* CuttlefishConfig::Get() { + auto config_file_path = + StringFromEnv(kCuttlefishConfigEnvVarName, GetGlobalConfigFileLink()); + static std::shared_ptr config( + BuildConfigImpl(config_file_path)); + return config.get(); +} + +/*static*/ bool CuttlefishConfig::ConfigExists() { + auto config_file_path = StringFromEnv(kCuttlefishConfigEnvVarName, + GetGlobalConfigFileLink()); + auto real_file_path = AbsolutePath(config_file_path.c_str()); + return FileExists(real_file_path); +} + +CuttlefishConfig::CuttlefishConfig() : dictionary_(new Json::Value()) {} +// Can't use '= default' on the header because the compiler complains of +// Json::Value being an incomplete type +CuttlefishConfig::~CuttlefishConfig() = default; + +CuttlefishConfig::CuttlefishConfig(CuttlefishConfig&&) = default; +CuttlefishConfig& CuttlefishConfig::operator=(CuttlefishConfig&&) = default; + +bool CuttlefishConfig::LoadFromFile(const char* file) { + auto real_file_path = AbsolutePath(file); + if (real_file_path.empty()) { + LOG(ERROR) << "Could not get real path for file " << file; + return false; + } + Json::CharReaderBuilder builder; + std::ifstream ifs(real_file_path); + std::string errorMessage; + if (!Json::parseFromStream(builder, ifs, dictionary_.get(), &errorMessage)) { + LOG(ERROR) << "Could not read config file " << file << ": " << errorMessage; + return false; + } + return true; +} +bool CuttlefishConfig::SaveToFile(const std::string& file) const { + std::ofstream ofs(file); + if (!ofs.is_open()) { + LOG(ERROR) << "Unable to write to file " << file; + return false; + } + ofs << *dictionary_; + return !ofs.fail(); +} + +std::string CuttlefishConfig::instances_dir() const { + return AbsolutePath(root_dir() + "/instances"); +} + +std::string CuttlefishConfig::InstancesPath( + const std::string& file_name) const { + return AbsolutePath(instances_dir() + "/" + file_name); +} + +std::string CuttlefishConfig::assembly_dir() const { + return AbsolutePath(root_dir() + "/assembly"); +} + +std::string CuttlefishConfig::AssemblyPath( + const std::string& file_name) const { + return AbsolutePath(assembly_dir() + "/" + file_name); +} + +std::string CuttlefishConfig::instances_uds_dir() const { + // Try to use /tmp/cf_avd_{uid}/ for UDS directory. + // If it fails, use HOME directory(legacy) instead. + + auto defaultPath = AbsolutePath("/tmp/cf_avd_" + std::to_string(getuid())); + + if (!DirectoryExists(defaultPath) || + CanAccess(defaultPath, R_OK | W_OK | X_OK)) { + return defaultPath; + } + + return instances_dir(); +} + +std::string CuttlefishConfig::InstancesUdsPath( + const std::string& file_name) const { + return AbsolutePath(instances_uds_dir() + "/" + file_name); +} + +std::string CuttlefishConfig::environments_dir() const { + return AbsolutePath(root_dir() + "/environments"); +} + +std::string CuttlefishConfig::EnvironmentsPath( + const std::string& file_name) const { + return AbsolutePath(environments_dir() + "/" + file_name); +} + +std::string CuttlefishConfig::environments_uds_dir() const { + // Try to use /tmp/cf_env_{uid}/ for UDS directory. + // If it fails, use HOME directory instead. + + auto defaultPath = AbsolutePath("/tmp/cf_env_" + std::to_string(getuid())); + + if (!DirectoryExists(defaultPath) || + CanAccess(defaultPath, R_OK | W_OK | X_OK)) { + return defaultPath; + } + + return environments_dir(); +} + +std::string CuttlefishConfig::EnvironmentsUdsPath( + const std::string& file_name) const { + return AbsolutePath(environments_uds_dir() + "/" + file_name); +} + +CuttlefishConfig::MutableInstanceSpecific CuttlefishConfig::ForInstance(int num) { + return MutableInstanceSpecific(this, std::to_string(num)); +} + +CuttlefishConfig::InstanceSpecific CuttlefishConfig::ForInstance(int num) const { + return InstanceSpecific(this, std::to_string(num)); +} + +CuttlefishConfig::InstanceSpecific CuttlefishConfig::ForInstanceName( + const std::string& name) const { + return ForInstance(InstanceFromString(name)); +} + +CuttlefishConfig::InstanceSpecific CuttlefishConfig::ForDefaultInstance() const { + return ForInstance(GetInstance()); +} + +std::vector CuttlefishConfig::Instances() const { + const auto& json = (*dictionary_)[kInstances]; + std::vector instances; + for (const auto& name : json.getMemberNames()) { + instances.push_back(CuttlefishConfig::InstanceSpecific(this, name)); + } + return instances; +} + +std::vector CuttlefishConfig::instance_dirs() const { + std::vector result; + for (const auto& instance : Instances()) { + result.push_back(instance.instance_dir()); + result.push_back(instance.instance_uds_dir()); + } + return result; +} + +static constexpr char kInstanceNames[] = "instance_names"; +void CuttlefishConfig::set_instance_names( + const std::vector& instance_names) { + Json::Value args_json_obj(Json::arrayValue); + for (const auto& name : instance_names) { + args_json_obj.append(name); + } + (*dictionary_)[kInstanceNames] = args_json_obj; +} +std::vector CuttlefishConfig::instance_names() const { + // NOTE: The structure of this field needs to remain stable, since + // cvd_server may call this on config JSON files from various builds. + // + // This info is duplicated into its own field here so it is simpler + // to keep stable, rather than parsing from Instances()::instance_name. + // + // Any non-stable changes must be accompanied by an uprev to the + // cvd_server major version. + std::vector names; + for (const Json::Value& name : (*dictionary_)[kInstanceNames]) { + names.push_back(name.asString()); + } + return names; +} + +CuttlefishConfig::MutableEnvironmentSpecific CuttlefishConfig::ForEnvironment( + const std::string& envName) { + return MutableEnvironmentSpecific(this, envName); +} + +CuttlefishConfig::EnvironmentSpecific CuttlefishConfig::ForEnvironment( + const std::string& envName) const { + return EnvironmentSpecific(this, envName); +} + +CuttlefishConfig::MutableEnvironmentSpecific +CuttlefishConfig::ForDefaultEnvironment() { + return MutableEnvironmentSpecific(this, + ForDefaultInstance().environment_name()); +} + +CuttlefishConfig::EnvironmentSpecific CuttlefishConfig::ForDefaultEnvironment() + const { + return EnvironmentSpecific(this, ForDefaultInstance().environment_name()); +} + +std::vector CuttlefishConfig::environment_dirs() const { + auto environment = ForDefaultEnvironment(); + + std::vector result; + + result.push_back(environment.environment_dir()); + result.push_back(environment.environment_uds_dir()); + + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/cuttlefish_config.h b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config.h new file mode 100644 index 0000000000..3f9aee2990 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config.h @@ -0,0 +1,982 @@ +/* + * Copyright (C) 2018 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/libs/utils/environment.h" +#include "common/libs/utils/result.h" +#include "host/libs/config/config_constants.h" +#include "host/libs/config/config_fragment.h" +#include "host/libs/config/config_utils.h" + +namespace Json { +class Value; +} + +namespace cuttlefish { + +enum class SecureHal { + Unknown, + Keymint, + Gatekeeper, + Oemlock, +}; + +enum class ExternalNetworkMode { + kUnknown, + kTap, + kSlirp, +}; + +std::ostream& operator<<(std::ostream&, ExternalNetworkMode); +Result ParseExternalNetworkMode(std::string_view); + +// Holds the configuration of the cuttlefish instances. +class CuttlefishConfig { + public: + static const CuttlefishConfig* Get(); + static std::unique_ptr GetFromFile( + const std::string& path); + static bool ConfigExists(); + + CuttlefishConfig(); + CuttlefishConfig(CuttlefishConfig&&); + ~CuttlefishConfig(); + CuttlefishConfig& operator=(CuttlefishConfig&&); + + // Saves the configuration object in a file, it can then be read in other + // processes by passing the --config_file option. + bool SaveToFile(const std::string& file) const; + bool LoadFromFile(const char* file); + + bool SaveFragment(const ConfigFragment&); + bool LoadFragment(ConfigFragment&) const; + + std::string root_dir() const; + void set_root_dir(const std::string& root_dir); + + std::string instances_dir() const; + std::string InstancesPath(const std::string&) const; + + std::string assembly_dir() const; + std::string AssemblyPath(const std::string&) const; + + std::string instances_uds_dir() const; + std::string InstancesUdsPath(const std::string&) const; + + std::string environments_dir() const; + std::string EnvironmentsPath(const std::string&) const; + + std::string environments_uds_dir() const; + std::string EnvironmentsUdsPath(const std::string&) const; + + std::string vm_manager() const; + void set_vm_manager(const std::string& name); + + std::string ap_vm_manager() const; + void set_ap_vm_manager(const std::string& name); + + struct DisplayConfig { + int width; + int height; + int dpi; + int refresh_rate_hz; + }; + + struct TouchpadConfig { + int width; + int height; + + static Json::Value Serialize( + const CuttlefishConfig::TouchpadConfig& config); + static TouchpadConfig Deserialize(const Json::Value& config_json); + }; + + void set_secure_hals(const std::set& hals); + std::set secure_hals() const; + + void set_crosvm_binary(const std::string& crosvm_binary); + std::string crosvm_binary() const; + + void set_gem5_debug_flags(const std::string& gem5_debug_flags); + std::string gem5_debug_flags() const; + + void set_enable_host_uwb(bool enable_host_uwb); + bool enable_host_uwb() const; + + void set_enable_host_uwb_connector(bool enable_host_uwb); + bool enable_host_uwb_connector() const; + + void set_enable_host_bluetooth(bool enable_host_bluetooth); + bool enable_host_bluetooth() const; + + void set_enable_automotive_proxy(bool enable_automotive_proxy); + bool enable_automotive_proxy() const; + + // Bluetooth is enabled by bt_connector and rootcanal + void set_enable_host_bluetooth_connector(bool enable_host_bluetooth); + bool enable_host_bluetooth_connector() const; + + void set_enable_host_nfc(bool enable_host_nfc); + bool enable_host_nfc() const; + + void set_enable_host_nfc_connector(bool enable_host_nfc_connector); + bool enable_host_nfc_connector() const; + + void set_casimir_args(const std::string& casimir_args); + std::vector casimir_args() const; + void set_casimir_instance_num(int casimir_instance_num); + int casimir_instance_num() const; + void set_casimir_nci_port(int port); + int casimir_nci_port() const; + void set_casimir_rf_port(int port); + int casimir_rf_port() const; + + void set_enable_wifi(const bool enable_wifi); + bool enable_wifi() const; + + // Flags for the set of radios that are connected to netsim + enum NetsimRadio { + Bluetooth = 0b00000001, + Wifi = 0b00000010, + Uwb = 0b00000100, + }; + + void netsim_radio_enable(NetsimRadio flag); + bool netsim_radio_enabled(NetsimRadio flag) const; + void set_netsim_instance_num(int netsim_instance_num); + int netsim_instance_num() const; + // Netsim has a built-in connector to forward packets to another daemon based + // on instance number. This is set in the serial launch case when FLAGS + // rootcanal_instance_num is specified. The non-netsim case uses + // bluetooth_connector and rootcanal_hci_port for the same purpose. Purposely + // restricted to legacy bluetooth serial invocation because new cases should + // use cvd. + int netsim_connector_instance_num() const; + void set_netsim_connector_instance_num(int netsim_instance_num); + void set_netsim_args(const std::string& netsim_args); + std::vector netsim_args() const; + + enum class Answer { + kUnknown = 0, + kYes, + kNo, + }; + + void set_enable_metrics(std::string enable_metrics); + CuttlefishConfig::Answer enable_metrics() const; + + void set_metrics_binary(const std::string& metrics_binary); + std::string metrics_binary() const; + + void set_extra_kernel_cmdline(const std::string& extra_cmdline); + std::vector extra_kernel_cmdline() const; + + // A directory containing the SSL certificates for the signaling server + void set_webrtc_certs_dir(const std::string& certs_dir); + std::string webrtc_certs_dir() const; + + // The port for the webrtc signaling server. It's used by the signaling server + // to bind to it and by the webrtc process to connect to and register itself + void set_sig_server_port(int port); + int sig_server_port() const; + + // The address of the signaling server + void set_sig_server_address(const std::string& addr); + std::string sig_server_address() const; + + // The path section of the url where the webrtc process registers itself with + // the signaling server + void set_sig_server_path(const std::string& path); + std::string sig_server_path() const; + + // Whether the webrtc process should use a secure connection (WSS) to the + // signaling server. + void set_sig_server_secure(bool secure); + bool sig_server_secure() const; + + // Whether the webrtc process should attempt to verify the authenticity of the + // signaling server (reject self signed certificates) + void set_sig_server_strict(bool strict); + bool sig_server_strict() const; + + void set_host_tools_version(const std::map&); + std::map host_tools_version() const; + + void set_virtio_mac80211_hwsim(bool virtio_mac80211_hwsim); + bool virtio_mac80211_hwsim() const; + + void set_ap_rootfs_image(const std::string& path); + std::string ap_rootfs_image() const; + + void set_ap_kernel_image(const std::string& path); + std::string ap_kernel_image() const; + + void set_pica_uci_port(int pica_uci_port); + int pica_uci_port() const; + + void set_rootcanal_args(const std::string& rootcanal_args); + std::vector rootcanal_args() const; + + void set_rootcanal_hci_port(int rootcanal_hci_port); + int rootcanal_hci_port() const; + + void set_rootcanal_link_port(int rootcanal_link_port); + int rootcanal_link_port() const; + + void set_rootcanal_link_ble_port(int rootcanal_link_ble_port); + int rootcanal_link_ble_port() const; + + void set_rootcanal_test_port(int rootcanal_test_port); + int rootcanal_test_port() const; + + // The path of an AP image in composite disk + std::string ap_image_dev_path() const; + void set_ap_image_dev_path(const std::string& dev_path); + + // path to the saved snapshot file(s) + std::string snapshot_path() const; + void set_snapshot_path(const std::string& snapshot_path); + + std::set straced_host_executables() const; + void set_straced_host_executables(const std::set& executables); + + bool host_sandbox() const; + void set_host_sandbox(bool host_sandbox); + + bool IsCrosvm() const; + + class InstanceSpecific; + class MutableInstanceSpecific; + + MutableInstanceSpecific ForInstance(int instance_num); + InstanceSpecific ForInstance(int instance_num) const; + InstanceSpecific ForInstanceName(const std::string& name) const; + InstanceSpecific ForDefaultInstance() const; + + std::vector Instances() const; + std::vector instance_dirs() const; + + void set_instance_names(const std::vector& instance_names); + std::vector instance_names() const; + + // A view into an existing CuttlefishConfig object for a particular instance. + class InstanceSpecific { + const CuttlefishConfig* config_; + std::string id_; + friend InstanceSpecific CuttlefishConfig::ForInstance(int num) const; + friend std::vector CuttlefishConfig::Instances() const; + + InstanceSpecific(const CuttlefishConfig* config, const std::string& id) + : config_(config), id_(id) {} + + Json::Value* Dictionary(); + const Json::Value* Dictionary() const; + public: + std::string serial_number() const; + // If any of the following port numbers is 0, the relevant service is not + // running on the guest. + + // Port number for qemu to run a vnc server on the host + int qemu_vnc_server_port() const; + // Port number to connect to the tombstone receiver on the host + int tombstone_receiver_port() const; + // Port number to connect to the config server on the host + int config_server_port() const; + // Port number to connect to the audiocontrol server on the guest + int audiocontrol_server_port() const; + // Port number to connect to the adb server on the host + int adb_host_port() const; + // Port number to connect to the fastboot server on the host + int fastboot_host_port() const; + // Device-specific ID to distinguish modem simulators. Must be 4 digits. + int modem_simulator_host_id() const; + // Port number to connect to the gnss grpc proxy server on the host + int gnss_grpc_proxy_server_port() const; + std::string adb_ip_and_port() const; + // Port number to connect to the camera hal on the guest + int camera_server_port() const; + // Port number to connect to the lights hal on the guest + int lights_server_port() const; + + std::string adb_device_name() const; + std::string gnss_file_path() const; + std::string fixed_location_file_path() const; + std::string mobile_bridge_name() const; + std::string mobile_tap_name() const; + std::string mobile_mac() const; + std::string wifi_bridge_name() const; + std::string wifi_tap_name() const; + std::string wifi_mac() const; + bool use_bridged_wifi_tap() const; + std::string ethernet_tap_name() const; + std::string ethernet_bridge_name() const; + std::string ethernet_mac() const; + std::string ethernet_ipv6() const; + uint32_t session_id() const; + bool use_allocd() const; + int vsock_guest_cid() const; + std::string uuid() const; + std::string instance_name() const; + std::string environment_name() const; + std::vector virtual_disk_paths() const; + + // Returns the path to a file with the given name in the instance + // directory.. + std::string PerInstancePath(const std::string& file_name) const; + std::string PerInstanceInternalPath(const std::string& file_name) const; + std::string PerInstanceLogPath(const std::string& file_name) const; + + std::string CrosvmSocketPath() const; + std::string OpenwrtCrosvmSocketPath() const; + std::string instance_dir() const; + + std::string instance_internal_dir() const; + + // Return the Unix domain socket path with given name. Because the + // length limitation of Unix domain socket name, it needs to be in + // the another directory than normal Instance path. + std::string PerInstanceUdsPath(const std::string& file_name) const; + std::string PerInstanceInternalUdsPath(const std::string& file_name) const; + std::string PerInstanceGrpcSocketPath(const std::string& socket_name) const; + + std::string instance_uds_dir() const; + + std::string instance_internal_uds_dir() const; + + std::string touch_socket_path(int touch_dev_idx) const; + std::string rotary_socket_path() const; + std::string keyboard_socket_path() const; + std::string switches_socket_path() const; + std::string frames_socket_path() const; + + std::string access_kregistry_path() const; + + std::string hwcomposer_pmem_path() const; + + std::string pstore_path() const; + + std::string console_path() const; + + std::string logcat_path() const; + + std::string kernel_log_pipe_name() const; + + std::string console_pipe_prefix() const; + std::string console_in_pipe_name() const; + std::string console_out_pipe_name() const; + + std::string gnss_pipe_prefix() const; + std::string gnss_in_pipe_name() const; + std::string gnss_out_pipe_name() const; + + std::string logcat_pipe_name() const; + std::string restore_pipe_name() const; + + std::string launcher_log_path() const; + + std::string launcher_monitor_socket_path() const; + + std::string sdcard_path() const; + std::string sdcard_overlay_path() const; + + std::string persistent_composite_disk_path() const; + std::string persistent_composite_overlay_path() const; + std::string persistent_ap_composite_disk_path() const; + std::string persistent_ap_composite_overlay_path() const; + + std::string os_composite_disk_path() const; + + std::string ap_composite_disk_path() const; + + std::string uboot_env_image_path() const; + + std::string ap_uboot_env_image_path() const; + + std::string ap_esp_image_path() const; + + std::string esp_image_path() const; + + std::string chromeos_state_image() const; + + std::string otheros_esp_grub_config() const; + + std::string ap_esp_grub_config() const; + + std::string audio_server_path() const; + + enum class BootFlow { + Android, + AndroidEfiLoader, + ChromeOs, + ChromeOsDisk, + Linux, + Fuchsia + }; + + BootFlow boot_flow() const; + + // modem simulator related + std::string modem_simulator_ports() const; + + // The device id the webrtc process should use to register with the + // signaling server + std::string webrtc_device_id() const; + + // The group id the webrtc process should use to register with the + // signaling server + std::string group_id() const; + + // Whether this instance should start the webrtc signaling server + bool start_webrtc_sig_server() const; + + // Whether to start a reverse proxy to the webrtc signaling server already + // running in the host + bool start_webrtc_sig_server_proxy() const; + + // Whether this instance should start a rootcanal instance + bool start_rootcanal() const; + + // Whether this instance should start a casimir instance + bool start_casimir() const; + + // Whether this instance should start a pica instance + bool start_pica() const; + + // Whether this instance should start a netsim instance + bool start_netsim() const; + + // TODO(b/288987294) Remove this when separating environment is done + // Whether this instance should start a wmediumd instance + bool start_wmediumd_instance() const; + + const Json::Value& mcu() const; + + enum class APBootFlow { + // Not starting AP at all (for example not the 1st instance) + None, + // Generating ESP and using U-BOOT to boot AP + Grub, + // Using legacy way to boot AP in case we cannot generate ESP image. + // Currently we have only one case when we cannot do it. When users + // have ubuntu bionic which doesn't have monolith binaris in the + // grub-efi-arm64-bin (for arm64) and grub-efi-ia32-bin (x86) deb packages. + // TODO(b/260337906): check is it possible to add grub binaries into the AOSP + // to deliver the proper grub environment + // TODO(b/260338443): use grub-mkimage from grub-common in case we cannot overcome + // legal issues + LegacyDirect + }; + APBootFlow ap_boot_flow() const; + + bool crosvm_use_balloon() const; + bool crosvm_use_rng() const; + bool use_pmem() const; + /* fmayle@ found out that when cuttlefish starts from the saved snapshot + * that was saved after ADBD start event, the socket_vsock_proxy must not + * wait for the AdbdStarted event. + * + * This instance-specific configuration tells the host sock_vsock_proxy + * not to wait for the adbd start event. + */ + bool sock_vsock_proxy_wait_adbd_start() const; + + // Wifi MAC address inside the guest + int wifi_mac_prefix() const; + + std::string factory_reset_protected_path() const; + + std::string persistent_bootconfig_path() const; + + std::string vbmeta_path() const; + + std::string ap_vbmeta_path() const; + + std::string id() const; + + std::string gem5_binary_dir() const; + + std::string gem5_checkpoint_dir() const; + + // Serial console + bool console() const; + std::string console_dev() const; + bool enable_sandbox() const; + bool enable_virtiofs() const; + + // KGDB configuration for kernel debugging + bool kgdb() const; + + // TODO (b/163575714) add virtio console support to the bootloader so the + // virtio console path for the console device can be taken again. When that + // happens, this function can be deleted along with all the code paths it + // forces. + bool use_bootloader() const; + + Arch target_arch() const; + + int cpus() const; + + std::string data_policy() const; + + int blank_data_image_mb() const; + + int gdb_port() const; + + std::vector display_configs() const; + std::vector touchpad_configs() const; + + std::string grpc_socket_path() const; + int memory_mb() const; + int ddr_mem_mb() const; + std::string setupwizard_mode() const; + std::string userdata_format() const; + bool guest_enforce_security() const; + bool use_sdcard() const; + bool pause_in_bootloader() const; + bool run_as_daemon() const; + bool enable_audio() const; + bool enable_gnss_grpc_proxy() const; + bool enable_bootanimation() const; + std::vector extra_bootconfig_args() const; + bool record_screen() const; + std::string gem5_debug_file() const; + bool protected_vm() const; + bool mte() const; + std::string boot_slot() const; + + // Kernel and bootloader logging + bool enable_kernel_log() const; + bool vhost_net() const; + bool vhost_user_vsock() const; + + // The dns address of mobile network (RIL) + std::string ril_dns() const; + + bool enable_webrtc() const; + std::string webrtc_assets_dir() const; + + // The range of TCP ports available for webrtc sessions. + std::pair webrtc_tcp_port_range() const; + + // The range of UDP ports available for webrtc sessions. + std::pair webrtc_udp_port_range() const; + + bool smt() const; + std::string crosvm_binary() const; + std::string seccomp_policy_dir() const; + std::string qemu_binary_dir() const; + + // Configuration flags for a minimal device + bool enable_minimal_mode() const; + bool enable_modem_simulator() const; + int modem_simulator_instance_number() const; + int modem_simulator_sim_type() const; + + std::string gpu_mode() const; + std::string gpu_angle_feature_overrides_enabled() const; + std::string gpu_angle_feature_overrides_disabled() const; + std::string gpu_capture_binary() const; + std::string gpu_gfxstream_transport() const; + + std::string gpu_vhost_user_mode() const; + + bool enable_gpu_udmabuf() const; + bool enable_gpu_vhost_user() const; + bool enable_gpu_external_blob() const; + bool enable_gpu_system_blob() const; + + std::string hwcomposer() const; + + bool restart_subprocesses() const; + + // android artifacts + std::string boot_image() const; + std::string new_boot_image() const; + std::string init_boot_image() const; + std::string data_image() const; + std::string new_data_image() const; + std::string super_image() const; + std::string new_super_image() const; + std::string misc_image() const; + std::string misc_info_txt() const; + std::string metadata_image() const; + std::string vendor_boot_image() const; + std::string new_vendor_boot_image() const; + std::string vbmeta_image() const; + std::string vbmeta_system_image() const; + std::string vbmeta_vendor_dlkm_image() const; + std::string new_vbmeta_vendor_dlkm_image() const; + std::string vbmeta_system_dlkm_image() const; + std::string new_vbmeta_system_dlkm_image() const; + std::string default_target_zip() const; + std::string system_target_zip() const; + + // otheros artifacts + std::string otheros_esp_image() const; + + // android efi loader flow + std::string android_efi_loader() const; + + // chromeos artifacts for otheros flow + std::string chromeos_disk() const; + std::string chromeos_kernel_path() const; + std::string chromeos_root_image() const; + + // linux artifacts for otheros flow + std::string linux_kernel_path() const; + std::string linux_initramfs_path() const; + std::string linux_root_image() const; + + std::string fuchsia_zedboot_path() const; + std::string fuchsia_multiboot_bin_path() const; + std::string fuchsia_root_image() const; + + std::string custom_partition_path() const; + + int blank_metadata_image_mb() const; + int blank_sdcard_image_mb() const; + std::string bootloader() const; + std::string initramfs_path() const; + std::string kernel_path() const; + std::string guest_android_version() const; + bool bootconfig_supported() const; + std::string filename_encryption_mode() const; + ExternalNetworkMode external_network_mode() const; + }; + + // A view into an existing CuttlefishConfig object for a particular instance. + class MutableInstanceSpecific { + CuttlefishConfig* config_; + std::string id_; + friend MutableInstanceSpecific CuttlefishConfig::ForInstance(int num); + + MutableInstanceSpecific(CuttlefishConfig* config, const std::string& id); + + Json::Value* Dictionary(); + public: + void set_serial_number(const std::string& serial_number); + void set_qemu_vnc_server_port(int qemu_vnc_server_port); + void set_tombstone_receiver_port(int tombstone_receiver_port); + void set_config_server_port(int config_server_port); + void set_frames_server_port(int config_server_port); + void set_touch_server_port(int config_server_port); + void set_keyboard_server_port(int config_server_port); + void set_gatekeeper_vsock_port(int gatekeeper_vsock_port); + void set_keymaster_vsock_port(int keymaster_vsock_port); + void set_audiocontrol_server_port(int audiocontrol_server_port); + void set_lights_server_port(int lights_server_port); + void set_adb_host_port(int adb_host_port); + void set_modem_simulator_host_id(int modem_simulator_id); + void set_adb_ip_and_port(const std::string& ip_port); + void set_fastboot_host_port(int fastboot_host_port); + void set_camera_server_port(int camera_server_port); + void set_mobile_bridge_name(const std::string& mobile_bridge_name); + void set_mobile_tap_name(const std::string& mobile_tap_name); + void set_mobile_mac(const std::string& mac); + void set_wifi_bridge_name(const std::string& wifi_bridge_name); + void set_wifi_tap_name(const std::string& wifi_tap_name); + void set_wifi_mac(const std::string& mac); + void set_use_bridged_wifi_tap(bool use_bridged_wifi_tap); + void set_ethernet_tap_name(const std::string& ethernet_tap_name); + void set_ethernet_bridge_name(const std::string& set_ethernet_bridge_name); + void set_ethernet_mac(const std::string& mac); + void set_ethernet_ipv6(const std::string& ip); + void set_session_id(uint32_t session_id); + void set_use_allocd(bool use_allocd); + void set_vsock_guest_cid(int vsock_guest_cid); + void set_uuid(const std::string& uuid); + void set_environment_name(const std::string& env_name); + // modem simulator related + void set_modem_simulator_ports(const std::string& modem_simulator_ports); + void set_virtual_disk_paths(const std::vector& disk_paths); + void set_webrtc_device_id(const std::string& id); + void set_group_id(const std::string& id); + void set_start_webrtc_signaling_server(bool start); + void set_start_webrtc_sig_server_proxy(bool start); + void set_start_rootcanal(bool start); + void set_start_casimir(bool start); + void set_start_pica(bool start); + void set_start_netsim(bool start); + // TODO(b/288987294) Remove this when separating environment is done + void set_start_wmediumd_instance(bool start); + void set_mcu(const Json::Value &v); + void set_ap_boot_flow(InstanceSpecific::APBootFlow flow); + void set_crosvm_use_balloon(const bool use_balloon); + void set_crosvm_use_rng(const bool use_rng); + void set_use_pmem(const bool use_pmem); + void set_sock_vsock_proxy_wait_adbd_start(const bool); + // Wifi MAC address inside the guest + void set_wifi_mac_prefix(const int wifi_mac_prefix); + // Gnss grpc proxy server port inside the host + void set_gnss_grpc_proxy_server_port(int gnss_grpc_proxy_server_port); + // Gnss grpc proxy local file path + void set_gnss_file_path(const std::string &gnss_file_path); + void set_fixed_location_file_path( + const std::string& fixed_location_file_path); + void set_gem5_binary_dir(const std::string& gem5_binary_dir); + void set_gem5_checkpoint_dir(const std::string& gem5_checkpoint_dir); + // Serial console + void set_console(bool console); + void set_enable_sandbox(const bool enable_sandbox); + void set_enable_virtiofs(const bool enable_virtiofs); + void set_kgdb(bool kgdb); + void set_target_arch(Arch target_arch); + void set_cpus(int cpus); + void set_data_policy(const std::string& data_policy); + void set_blank_data_image_mb(int blank_data_image_mb); + void set_gdb_port(int gdb_port); + void set_display_configs(const std::vector& display_configs); + void set_touchpad_configs( + const std::vector& touchpad_configs); + void set_memory_mb(int memory_mb); + void set_ddr_mem_mb(int ddr_mem_mb); + Result set_setupwizard_mode(const std::string& title); + void set_userdata_format(const std::string& userdata_format); + void set_guest_enforce_security(bool guest_enforce_security); + void set_use_sdcard(bool use_sdcard); + void set_pause_in_bootloader(bool pause_in_bootloader); + void set_run_as_daemon(bool run_as_daemon); + void set_enable_audio(bool enable); + void set_enable_gnss_grpc_proxy(const bool enable_gnss_grpc_proxy); + void set_enable_bootanimation(const bool enable_bootanimation); + void set_extra_bootconfig_args(const std::string& extra_bootconfig_args); + void set_record_screen(bool record_screen); + void set_gem5_debug_file(const std::string& gem5_debug_file); + void set_protected_vm(bool protected_vm); + void set_mte(bool mte); + void set_boot_slot(const std::string& boot_slot); + void set_grpc_socket_path(const std::string& sockets); + + // Kernel and bootloader logging + void set_enable_kernel_log(bool enable_kernel_log); + + void set_enable_webrtc(bool enable_webrtc); + void set_webrtc_assets_dir(const std::string& webrtc_assets_dir); + + // The range of TCP ports available for webrtc sessions. + void set_webrtc_tcp_port_range(std::pair range); + + // The range of UDP ports available for webrtc sessions. + void set_webrtc_udp_port_range(std::pair range); + + void set_smt(bool smt); + void set_crosvm_binary(const std::string& crosvm_binary); + void set_seccomp_policy_dir(const std::string& seccomp_policy_dir); + void set_qemu_binary_dir(const std::string& qemu_binary_dir); + + void set_vhost_net(bool vhost_net); + void set_vhost_user_vsock(bool vhost_user_vsock); + + // The dns address of mobile network (RIL) + void set_ril_dns(const std::string& ril_dns); + + // Configuration flags for a minimal device + void set_enable_minimal_mode(bool enable_minimal_mode); + void set_enable_modem_simulator(bool enable_modem_simulator); + void set_modem_simulator_instance_number(int instance_numbers); + void set_modem_simulator_sim_type(int sim_type); + + void set_gpu_mode(const std::string& name); + void set_gpu_angle_feature_overrides_enabled(const std::string& overrides); + void set_gpu_angle_feature_overrides_disabled(const std::string& overrides); + void set_gpu_capture_binary(const std::string&); + void set_gpu_gfxstream_transport(const std::string& transport); + void set_enable_gpu_udmabuf(const bool enable_gpu_udmabuf); + void set_enable_gpu_vhost_user(const bool enable_gpu_vhost_user); + void set_enable_gpu_external_blob(const bool enable_gpu_external_blob); + void set_enable_gpu_system_blob(const bool enable_gpu_system_blob); + + void set_hwcomposer(const std::string&); + + void set_restart_subprocesses(bool restart_subprocesses); + + // system image files + void set_boot_image(const std::string& boot_image); + void set_new_boot_image(const std::string& new_boot_image); + void set_init_boot_image(const std::string& init_boot_image); + void set_data_image(const std::string& data_image); + void set_new_data_image(const std::string& new_data_image); + void set_super_image(const std::string& super_image); + void set_new_super_image(const std::string& super_image); + void set_misc_info_txt(const std::string& misc_image); + void set_vendor_boot_image(const std::string& vendor_boot_image); + void set_new_vendor_boot_image(const std::string& new_vendor_boot_image); + void set_vbmeta_image(const std::string& vbmeta_image); + void set_vbmeta_system_image(const std::string& vbmeta_system_image); + void set_vbmeta_vendor_dlkm_image( + const std::string& vbmeta_vendor_dlkm_image); + void set_new_vbmeta_vendor_dlkm_image( + const std::string& vbmeta_vendor_dlkm_image); + void set_vbmeta_system_dlkm_image( + const std::string& vbmeta_system_dlkm_image); + void set_new_vbmeta_system_dlkm_image( + const std::string& vbmeta_system_dlkm_image); + void set_default_target_zip(const std::string& default_target_zip); + void set_system_target_zip(const std::string& system_target_zip); + void set_otheros_esp_image(const std::string& otheros_esp_image); + void set_android_efi_loader(const std::string& android_efi_loader); + void set_chromeos_disk(const std::string& chromeos_disk); + void set_chromeos_kernel_path(const std::string& linux_kernel_path); + void set_chromeos_root_image(const std::string& linux_root_image); + void set_linux_kernel_path(const std::string& linux_kernel_path); + void set_linux_initramfs_path(const std::string& linux_initramfs_path); + void set_linux_root_image(const std::string& linux_root_image); + void set_fuchsia_zedboot_path(const std::string& fuchsia_zedboot_path); + void set_fuchsia_multiboot_bin_path(const std::string& fuchsia_multiboot_bin_path); + void set_fuchsia_root_image(const std::string& fuchsia_root_image); + void set_custom_partition_path(const std::string& custom_partition_path); + void set_blank_metadata_image_mb(int blank_metadata_image_mb); + void set_blank_sdcard_image_mb(int blank_sdcard_image_mb); + void set_bootloader(const std::string& bootloader); + void set_initramfs_path(const std::string& initramfs_path); + void set_kernel_path(const std::string& kernel_path); + void set_guest_android_version(const std::string& guest_android_version); + void set_bootconfig_supported(bool bootconfig_supported); + void set_filename_encryption_mode(const std::string& userdata_format); + void set_external_network_mode(ExternalNetworkMode network_mode); + + private: + void SetPath(const std::string& key, const std::string& path); + }; + + class EnvironmentSpecific; + class MutableEnvironmentSpecific; + + MutableEnvironmentSpecific ForEnvironment(const std::string& envName); + EnvironmentSpecific ForEnvironment(const std::string& envName) const; + + MutableEnvironmentSpecific ForDefaultEnvironment(); + EnvironmentSpecific ForDefaultEnvironment() const; + + std::vector environment_dirs() const; + + class EnvironmentSpecific { + friend EnvironmentSpecific CuttlefishConfig::ForEnvironment( + const std::string&) const; + friend EnvironmentSpecific CuttlefishConfig::ForDefaultEnvironment() const; + + const CuttlefishConfig* config_; + std::string envName_; + + EnvironmentSpecific(const CuttlefishConfig* config, + const std::string& envName) + : config_(config), envName_(envName) {} + + Json::Value* Dictionary(); + const Json::Value* Dictionary() const; + + public: + std::string environment_name() const; + + std::string environment_uds_dir() const; + std::string PerEnvironmentUdsPath(const std::string& file_name) const; + + std::string environment_dir() const; + std::string PerEnvironmentPath(const std::string& file_name) const; + + std::string PerEnvironmentLogPath(const std::string& file_name) const; + + std::string PerEnvironmentGrpcSocketPath( + const std::string& file_name) const; + + std::string control_socket_path() const; + std::string launcher_log_path() const; + + // wmediumd related configs + bool enable_wifi() const; + bool start_wmediumd() const; + std::string vhost_user_mac80211_hwsim() const; + std::string wmediumd_api_server_socket() const; + std::string wmediumd_config() const; + int wmediumd_mac_prefix() const; + }; + + class MutableEnvironmentSpecific { + friend MutableEnvironmentSpecific CuttlefishConfig::ForEnvironment( + const std::string&); + friend MutableEnvironmentSpecific CuttlefishConfig::ForDefaultEnvironment(); + + CuttlefishConfig* config_; + std::string envName_; + + MutableEnvironmentSpecific(CuttlefishConfig* config, + const std::string& envName) + : config_(config), envName_(envName) {} + + Json::Value* Dictionary(); + + public: + // wmediumd related configs + void set_enable_wifi(const bool enable_wifi); + void set_start_wmediumd(bool start); + void set_vhost_user_mac80211_hwsim(const std::string& path); + void set_wmediumd_api_server_socket(const std::string& path); + void set_wmediumd_config(const std::string& path); + void set_wmediumd_mac_prefix(int mac_prefix); + }; + + private: + std::unique_ptr dictionary_; + + static CuttlefishConfig* BuildConfigImpl(const std::string& path); + + CuttlefishConfig(const CuttlefishConfig&) = delete; + CuttlefishConfig& operator=(const CuttlefishConfig&) = delete; +}; + +// Vhost-user-vsock modes +extern const char* const kVhostUserVsockModeAuto; +extern const char* const kVhostUserVsockModeTrue; +extern const char* const kVhostUserVsockModeFalse; + +// GPU modes +extern const char* const kGpuModeAuto; +extern const char* const kGpuModeDrmVirgl; +extern const char* const kGpuModeGfxstream; +extern const char* const kGpuModeGfxstreamGuestAngle; +extern const char* const kGpuModeGfxstreamGuestAngleHostSwiftShader; +extern const char* const kGpuModeGuestSwiftshader; +extern const char* const kGpuModeNone; + +// GPU vhost user modes +extern const char* const kGpuVhostUserModeAuto; +extern const char* const kGpuVhostUserModeOn; +extern const char* const kGpuVhostUserModeOff; + +// HwComposer modes +extern const char* const kHwComposerAuto; +extern const char* const kHwComposerDrm; +extern const char* const kHwComposerRanchu; +extern const char* const kHwComposerNone; +} // namespace cuttlefish + +#if FMT_VERSION >= 90000 +template <> +struct fmt::formatter : ostream_formatter {}; +#endif diff --git a/base/cvd/cuttlefish/host/libs/config/cuttlefish_config_environment.cpp b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config_environment.cpp new file mode 100644 index 0000000000..aa798827af --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config_environment.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2023 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. + */ + +#include "host/libs/config/cuttlefish_config.h" + +#include "common/libs/utils/files.h" + +const char* kEnvironments = "environments"; + +namespace cuttlefish { + +Json::Value* CuttlefishConfig::EnvironmentSpecific::Dictionary() { + return &(*config_->dictionary_)[kEnvironments][envName_]; +} + +const Json::Value* CuttlefishConfig::EnvironmentSpecific::Dictionary() const { + return &(*config_->dictionary_)[kEnvironments][envName_]; +} + +Json::Value* CuttlefishConfig::MutableEnvironmentSpecific::Dictionary() { + return &(*config_->dictionary_)[kEnvironments][envName_]; +} + +std::string CuttlefishConfig::EnvironmentSpecific::environment_name() const { + return envName_; +} + +std::string CuttlefishConfig::EnvironmentSpecific::environment_uds_dir() const { + return config_->EnvironmentsUdsPath(envName_); +} + +std::string CuttlefishConfig::EnvironmentSpecific::PerEnvironmentUdsPath( + const std::string& file_name) const { + return (environment_uds_dir() + "/") + file_name; +} + +std::string CuttlefishConfig::EnvironmentSpecific::environment_dir() const { + return config_->EnvironmentsPath(envName_); +} + +std::string CuttlefishConfig::EnvironmentSpecific::PerEnvironmentPath( + const std::string& file_name) const { + return (environment_dir() + "/") + file_name; +} + +std::string CuttlefishConfig::EnvironmentSpecific::PerEnvironmentLogPath( + const std::string& file_name) const { + if (file_name.size() == 0) { + // Don't append a / if file_name is empty. + return PerEnvironmentPath(kLogDirName); + } + auto relative_path = (std::string(kLogDirName) + "/") + file_name; + return PerEnvironmentPath(relative_path.c_str()); +} + +std::string CuttlefishConfig::EnvironmentSpecific::PerEnvironmentGrpcSocketPath( + const std::string& file_name) const { + if (file_name.size() == 0) { + // Don't append a / if file_name is empty. + return PerEnvironmentPath(kGrpcSocketDirName); + } + auto relative_path = (std::string(kGrpcSocketDirName) + "/") + file_name; + return PerEnvironmentPath(relative_path.c_str()); +} + +std::string CuttlefishConfig::EnvironmentSpecific::control_socket_path() const { + return PerEnvironmentUdsPath("env_control.sock"); +} + +std::string CuttlefishConfig::EnvironmentSpecific::launcher_log_path() const { + return AbsolutePath(PerEnvironmentLogPath("launcher.log")); +} + +static constexpr char kEnableWifi[] = "enable_wifi"; +void CuttlefishConfig::MutableEnvironmentSpecific::set_enable_wifi( + bool enable_wifi) { + (*Dictionary())[kEnableWifi] = enable_wifi; +} +bool CuttlefishConfig::EnvironmentSpecific::enable_wifi() const { + return (*Dictionary())[kEnableWifi].asBool(); +} + +static constexpr char kStartWmediumd[] = "start_wmediumd"; +void CuttlefishConfig::MutableEnvironmentSpecific::set_start_wmediumd( + bool start) { + (*Dictionary())[kStartWmediumd] = start; +} +bool CuttlefishConfig::EnvironmentSpecific::start_wmediumd() const { + return (*Dictionary())[kStartWmediumd].asBool(); +} + +static constexpr char kVhostUserMac80211Hwsim[] = "vhost_user_mac80211_hwsim"; +void CuttlefishConfig::MutableEnvironmentSpecific:: + set_vhost_user_mac80211_hwsim(const std::string& path) { + (*Dictionary())[kVhostUserMac80211Hwsim] = path; +} +std::string CuttlefishConfig::EnvironmentSpecific::vhost_user_mac80211_hwsim() + const { + return (*Dictionary())[kVhostUserMac80211Hwsim].asString(); +} + +static constexpr char kWmediumdApiServerSocket[] = "wmediumd_api_server_socket"; +void CuttlefishConfig::MutableEnvironmentSpecific:: + set_wmediumd_api_server_socket(const std::string& path) { + (*Dictionary())[kWmediumdApiServerSocket] = path; +} +std::string CuttlefishConfig::EnvironmentSpecific::wmediumd_api_server_socket() + const { + return (*Dictionary())[kWmediumdApiServerSocket].asString(); +} + +static constexpr char kWmediumdConfig[] = "wmediumd_config"; +void CuttlefishConfig::MutableEnvironmentSpecific::set_wmediumd_config( + const std::string& config) { + (*Dictionary())[kWmediumdConfig] = config; +} +std::string CuttlefishConfig::EnvironmentSpecific::wmediumd_config() const { + return (*Dictionary())[kWmediumdConfig].asString(); +} + +static constexpr char kWmediumdMacPrefix[] = "wmediumd_mac_prefix"; +void CuttlefishConfig::MutableEnvironmentSpecific::set_wmediumd_mac_prefix( + int mac_prefix) { + (*Dictionary())[kWmediumdMacPrefix] = mac_prefix; +} +int CuttlefishConfig::EnvironmentSpecific::wmediumd_mac_prefix() const { + return (*Dictionary())[kWmediumdMacPrefix].asInt(); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/cuttlefish_config_instance.cpp b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config_instance.cpp new file mode 100644 index 0000000000..17fe9c48cb --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/cuttlefish_config_instance.cpp @@ -0,0 +1,1775 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "cuttlefish_config.h" +#include "host/libs/config/cuttlefish_config.h" + +#include + +#include +#include +#include + +#include "common/libs/utils/files.h" +#include "common/libs/utils/flags_validator.h" +#include "host/libs/vm_manager/crosvm_manager.h" +#include "host/libs/vm_manager/gem5_manager.h" + +namespace cuttlefish { +namespace { + +using APBootFlow = CuttlefishConfig::InstanceSpecific::APBootFlow; + +const char* kInstances = "instances"; + +std::string IdToName(const std::string& id) { return kCvdNamePrefix + id; } + +} // namespace + +std::ostream& operator<<(std::ostream& out, ExternalNetworkMode net) { + switch (net) { + case ExternalNetworkMode::kUnknown: + return out << "unknown"; + case ExternalNetworkMode::kTap: + return out << "tap"; + case ExternalNetworkMode::kSlirp: + return out << "slirp"; + } +} +Result ParseExternalNetworkMode(std::string_view str) { + if (android::base::EqualsIgnoreCase(str, "tap")) { + return ExternalNetworkMode::kTap; + } else if (android::base::EqualsIgnoreCase(str, "slirp")) { + return ExternalNetworkMode::kSlirp; + } else { + return CF_ERRF( + "\"{}\" is not a valid ExternalNetworkMode. Valid values are \"tap\" " + "and \"slirp\"", + str); + } +} + +static constexpr char kInstanceDir[] = "instance_dir"; +CuttlefishConfig::MutableInstanceSpecific::MutableInstanceSpecific( + CuttlefishConfig* config, const std::string& id) + : config_(config), id_(id) { + // Legacy for acloud + (*Dictionary())[kInstanceDir] = config_->InstancesPath(IdToName(id)); +} + +Json::Value* CuttlefishConfig::MutableInstanceSpecific::Dictionary() { + return &(*config_->dictionary_)[kInstances][id_]; +} + +const Json::Value* CuttlefishConfig::InstanceSpecific::Dictionary() const { + return &(*config_->dictionary_)[kInstances][id_]; +} + +std::string CuttlefishConfig::InstanceSpecific::instance_dir() const { + return config_->InstancesPath(IdToName(id_)); +} + +std::string CuttlefishConfig::InstanceSpecific::instance_internal_dir() const { + return PerInstancePath(kInternalDirName); +} + +std::string CuttlefishConfig::InstanceSpecific::instance_uds_dir() const { + return config_->InstancesUdsPath(IdToName(id_)); +} + +std::string CuttlefishConfig::InstanceSpecific::instance_internal_uds_dir() + const { + return PerInstanceUdsPath(kInternalDirName); +} + +// TODO (b/163575714) add virtio console support to the bootloader so the +// virtio console path for the console device can be taken again. When that +// happens, this function can be deleted along with all the code paths it +// forces. +bool CuttlefishConfig::InstanceSpecific::use_bootloader() const { + return true; +}; + +// vectorized and moved system image files into instance specific +static constexpr char kBootImage[] = "boot_image"; +std::string CuttlefishConfig::InstanceSpecific::boot_image() const { + return (*Dictionary())[kBootImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_boot_image( + const std::string& boot_image) { + (*Dictionary())[kBootImage] = boot_image; +} +static constexpr char kNewBootImage[] = "new_boot_image"; +std::string CuttlefishConfig::InstanceSpecific::new_boot_image() const { + return (*Dictionary())[kNewBootImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_new_boot_image( + const std::string& new_boot_image) { + (*Dictionary())[kNewBootImage] = new_boot_image; +} +static constexpr char kInitBootImage[] = "init_boot_image"; +std::string CuttlefishConfig::InstanceSpecific::init_boot_image() const { + return (*Dictionary())[kInitBootImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_init_boot_image( + const std::string& init_boot_image) { + (*Dictionary())[kInitBootImage] = init_boot_image; +} +static constexpr char kDataImage[] = "data_image"; +std::string CuttlefishConfig::InstanceSpecific::data_image() const { + return (*Dictionary())[kDataImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_data_image( + const std::string& data_image) { + (*Dictionary())[kDataImage] = data_image; +} +static constexpr char kNewDataImage[] = "new_data_image"; +std::string CuttlefishConfig::InstanceSpecific::new_data_image() const { + return (*Dictionary())[kNewDataImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_new_data_image( + const std::string& new_data_image) { + (*Dictionary())[kNewDataImage] = new_data_image; +} +static constexpr char kSuperImage[] = "super_image"; +std::string CuttlefishConfig::InstanceSpecific::super_image() const { + return (*Dictionary())[kSuperImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_super_image( + const std::string& super_image) { + (*Dictionary())[kSuperImage] = super_image; +} +static constexpr char kNewSuperImage[] = "new_super_image"; +std::string CuttlefishConfig::InstanceSpecific::new_super_image() const { + return (*Dictionary())[kNewSuperImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_new_super_image( + const std::string& super_image) { + (*Dictionary())[kNewSuperImage] = super_image; +} +static constexpr char kMiscInfoTxt[] = "misc_info_txt"; +std::string CuttlefishConfig::InstanceSpecific::misc_info_txt() const { + return (*Dictionary())[kMiscInfoTxt].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_misc_info_txt( + const std::string& misc_info) { + (*Dictionary())[kMiscInfoTxt] = misc_info; +} +static constexpr char kVendorBootImage[] = "vendor_boot_image"; +std::string CuttlefishConfig::InstanceSpecific::vendor_boot_image() const { + return (*Dictionary())[kVendorBootImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_vendor_boot_image( + const std::string& vendor_boot_image) { + (*Dictionary())[kVendorBootImage] = vendor_boot_image; +} +static constexpr char kNewVendorBootImage[] = "new_vendor_boot_image"; +std::string CuttlefishConfig::InstanceSpecific::new_vendor_boot_image() const { + return (*Dictionary())[kNewVendorBootImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_new_vendor_boot_image( + const std::string& new_vendor_boot_image) { + (*Dictionary())[kNewVendorBootImage] = new_vendor_boot_image; +} +static constexpr char kVbmetaImage[] = "vbmeta_image"; +std::string CuttlefishConfig::InstanceSpecific::vbmeta_image() const { + return (*Dictionary())[kVbmetaImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_vbmeta_image( + const std::string& vbmeta_image) { + (*Dictionary())[kVbmetaImage] = vbmeta_image; +} +static constexpr char kVbmetaSystemImage[] = "vbmeta_system_image"; +std::string CuttlefishConfig::InstanceSpecific::vbmeta_system_image() const { + return (*Dictionary())[kVbmetaSystemImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_vbmeta_system_image( + const std::string& vbmeta_system_image) { + (*Dictionary())[kVbmetaSystemImage] = vbmeta_system_image; +} +static constexpr char kVbmetaVendorDlkmImage[] = "vbmeta_vendor_dlkm_image"; +std::string CuttlefishConfig::InstanceSpecific::vbmeta_vendor_dlkm_image() + const { + return (*Dictionary())[kVbmetaVendorDlkmImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_vbmeta_vendor_dlkm_image( + const std::string& image) { + (*Dictionary())[kVbmetaVendorDlkmImage] = image; +} +static constexpr char kNewVbmetaVendorDlkmImage[] = + "new_vbmeta_vendor_dlkm_image"; +std::string CuttlefishConfig::InstanceSpecific::new_vbmeta_vendor_dlkm_image() + const { + return (*Dictionary())[kNewVbmetaVendorDlkmImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific:: + set_new_vbmeta_vendor_dlkm_image(const std::string& image) { + (*Dictionary())[kNewVbmetaVendorDlkmImage] = image; +} +static constexpr char kVbmetaSystemDlkmImage[] = "vbmeta_system_dlkm_image"; +std::string CuttlefishConfig::InstanceSpecific::vbmeta_system_dlkm_image() + const { + return (*Dictionary())[kVbmetaSystemDlkmImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_vbmeta_system_dlkm_image( + const std::string& image) { + (*Dictionary())[kVbmetaSystemDlkmImage] = image; +} +static constexpr char kNewVbmetaSystemDlkmImage[] = + "new_vbmeta_system_dlkm_image"; +std::string CuttlefishConfig::InstanceSpecific::new_vbmeta_system_dlkm_image() + const { + return (*Dictionary())[kNewVbmetaSystemDlkmImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific:: + set_new_vbmeta_system_dlkm_image(const std::string& image) { + (*Dictionary())[kNewVbmetaSystemDlkmImage] = image; +} +static constexpr char kOtherosEspImage[] = "otheros_esp_image"; +std::string CuttlefishConfig::InstanceSpecific::otheros_esp_image() const { + return (*Dictionary())[kOtherosEspImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_otheros_esp_image( + const std::string& otheros_esp_image) { + (*Dictionary())[kOtherosEspImage] = otheros_esp_image; +} +static constexpr char kAndroidEfiLoader[] = "android_efi_loader"; +std::string CuttlefishConfig::InstanceSpecific::android_efi_loader() const { + return (*Dictionary())[kAndroidEfiLoader].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_android_efi_loader( + const std::string& android_efi_loader) { + (*Dictionary())[kAndroidEfiLoader] = android_efi_loader; +} +static constexpr char kChromeOsDisk[] = "chromeos_disk"; +std::string CuttlefishConfig::InstanceSpecific::chromeos_disk() const { + return (*Dictionary())[kChromeOsDisk].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_chromeos_disk( + const std::string& chromeos_disk) { + (*Dictionary())[kChromeOsDisk] = chromeos_disk; +} +static constexpr char kChromeOsKernelPath[] = "chromeos_kernel_path"; +std::string CuttlefishConfig::InstanceSpecific::chromeos_kernel_path() const { + return (*Dictionary())[kChromeOsKernelPath].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_chromeos_kernel_path( + const std::string& chromeos_kernel_path) { + (*Dictionary())[kChromeOsKernelPath] = chromeos_kernel_path; +} +static constexpr char kChromeOsRootImage[] = "chromeos_root_image"; +std::string CuttlefishConfig::InstanceSpecific::chromeos_root_image() const { + return (*Dictionary())[kChromeOsRootImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_chromeos_root_image( + const std::string& chromeos_root_image) { + (*Dictionary())[kChromeOsRootImage] = chromeos_root_image; +} +static constexpr char kLinuxKernelPath[] = "linux_kernel_path"; +std::string CuttlefishConfig::InstanceSpecific::linux_kernel_path() const { + return (*Dictionary())[kLinuxKernelPath].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_linux_kernel_path( + const std::string& linux_kernel_path) { + (*Dictionary())[kLinuxKernelPath] = linux_kernel_path; +} +static constexpr char kLinuxInitramfsPath[] = "linux_initramfs_path"; +std::string CuttlefishConfig::InstanceSpecific::linux_initramfs_path() const { + return (*Dictionary())[kLinuxInitramfsPath].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_linux_initramfs_path( + const std::string& linux_initramfs_path) { + (*Dictionary())[kLinuxInitramfsPath] = linux_initramfs_path; +} +static constexpr char kLinuxRootImage[] = "linux_root_image"; +std::string CuttlefishConfig::InstanceSpecific::linux_root_image() const { + return (*Dictionary())[kLinuxRootImage].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_linux_root_image( + const std::string& linux_root_image) { + (*Dictionary())[kLinuxRootImage] = linux_root_image; +} +static constexpr char kFuchsiaZedbootPath[] = "fuchsia_zedboot_path"; +void CuttlefishConfig::MutableInstanceSpecific::set_fuchsia_zedboot_path( + const std::string& fuchsia_zedboot_path) { + (*Dictionary())[kFuchsiaZedbootPath] = fuchsia_zedboot_path; +} +std::string CuttlefishConfig::InstanceSpecific::fuchsia_zedboot_path() const { + return (*Dictionary())[kFuchsiaZedbootPath].asString(); +} +static constexpr char kFuchsiaMultibootBinPath[] = "multiboot_bin_path"; +void CuttlefishConfig::MutableInstanceSpecific::set_fuchsia_multiboot_bin_path( + const std::string& fuchsia_multiboot_bin_path) { + (*Dictionary())[kFuchsiaMultibootBinPath] = fuchsia_multiboot_bin_path; +} +std::string CuttlefishConfig::InstanceSpecific::fuchsia_multiboot_bin_path() const { + return (*Dictionary())[kFuchsiaMultibootBinPath].asString(); +} +static constexpr char kFuchsiaRootImage[] = "fuchsia_root_image"; +void CuttlefishConfig::MutableInstanceSpecific::set_fuchsia_root_image( + const std::string& fuchsia_root_image) { + (*Dictionary())[kFuchsiaRootImage] = fuchsia_root_image; +} +std::string CuttlefishConfig::InstanceSpecific::fuchsia_root_image() const { + return (*Dictionary())[kFuchsiaRootImage].asString(); +} +static constexpr char kCustomPartitionPath[] = "custom_partition_path"; +void CuttlefishConfig::MutableInstanceSpecific::set_custom_partition_path( + const std::string& custom_partition_path) { + (*Dictionary())[kCustomPartitionPath] = custom_partition_path; +} +std::string CuttlefishConfig::InstanceSpecific::custom_partition_path() const { + return (*Dictionary())[kCustomPartitionPath].asString(); +} +static constexpr char kBlankMetadataImageMb[] = "blank_metadata_image_mb"; +int CuttlefishConfig::InstanceSpecific::blank_metadata_image_mb() const { + return (*Dictionary())[kBlankMetadataImageMb].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_blank_metadata_image_mb( + int blank_metadata_image_mb) { + (*Dictionary())[kBlankMetadataImageMb] = blank_metadata_image_mb; +} +static constexpr char kBlankSdcardImageMb[] = "blank_sdcard_image_mb"; +int CuttlefishConfig::InstanceSpecific::blank_sdcard_image_mb() const { + return (*Dictionary())[kBlankSdcardImageMb].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_blank_sdcard_image_mb( + int blank_sdcard_image_mb) { + (*Dictionary())[kBlankSdcardImageMb] = blank_sdcard_image_mb; +} +static constexpr char kBootloader[] = "bootloader"; +std::string CuttlefishConfig::InstanceSpecific::bootloader() const { + return (*Dictionary())[kBootloader].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_bootloader( + const std::string& bootloader) { + (*Dictionary())[kBootloader] = bootloader; +} +static constexpr char kInitramfsPath[] = "initramfs_path"; +std::string CuttlefishConfig::InstanceSpecific::initramfs_path() const { + return (*Dictionary())[kInitramfsPath].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_initramfs_path( + const std::string& initramfs_path) { + (*Dictionary())[kInitramfsPath] = initramfs_path; +} +static constexpr char kKernelPath[] = "kernel_path"; +std::string CuttlefishConfig::InstanceSpecific::kernel_path() const { + return (*Dictionary())[kKernelPath].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_kernel_path( + const std::string& kernel_path) { + (*Dictionary())[kKernelPath] = kernel_path; +} +// end of system image files + +static constexpr char kDefaultTargetZip[] = "default_target_zip"; +std::string CuttlefishConfig::InstanceSpecific::default_target_zip() const { + return (*Dictionary())[kDefaultTargetZip].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_default_target_zip( + const std::string& default_target_zip) { + (*Dictionary())[kDefaultTargetZip] = default_target_zip; +} +static constexpr char kSystemTargetZip[] = "system_target_zip"; +std::string CuttlefishConfig::InstanceSpecific::system_target_zip() const { + return (*Dictionary())[kSystemTargetZip].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_system_target_zip( + const std::string& system_target_zip) { + (*Dictionary())[kSystemTargetZip] = system_target_zip; +} + +static constexpr char kSerialNumber[] = "serial_number"; +std::string CuttlefishConfig::InstanceSpecific::serial_number() const { + return (*Dictionary())[kSerialNumber].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_serial_number( + const std::string& serial_number) { + (*Dictionary())[kSerialNumber] = serial_number; +} + +static constexpr char kVirtualDiskPaths[] = "virtual_disk_paths"; +std::vector CuttlefishConfig::InstanceSpecific::virtual_disk_paths() const { + std::vector virtual_disks; + auto virtual_disks_json_obj = (*Dictionary())[kVirtualDiskPaths]; + for (const auto& disk : virtual_disks_json_obj) { + virtual_disks.push_back(disk.asString()); + } + return virtual_disks; +} +void CuttlefishConfig::MutableInstanceSpecific::set_virtual_disk_paths( + const std::vector& virtual_disk_paths) { + Json::Value virtual_disks_json_obj(Json::arrayValue); + for (const auto& arg : virtual_disk_paths) { + virtual_disks_json_obj.append(arg); + } + (*Dictionary())[kVirtualDiskPaths] = virtual_disks_json_obj; +} + +static constexpr char kGuestAndroidVersion[] = "guest_android_version"; +std::string CuttlefishConfig::InstanceSpecific::guest_android_version() const { + return (*Dictionary())[kGuestAndroidVersion].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_guest_android_version( + const std::string& guest_android_version) { + (*Dictionary())[kGuestAndroidVersion] = guest_android_version; +} + +static constexpr char kBootconfigSupported[] = "bootconfig_supported"; +bool CuttlefishConfig::InstanceSpecific::bootconfig_supported() const { + return (*Dictionary())[kBootconfigSupported].asBool(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_bootconfig_supported( + bool bootconfig_supported) { + (*Dictionary())[kBootconfigSupported] = bootconfig_supported; +} + +static constexpr char kFilenameEncryptionMode[] = "filename_encryption_mode"; +std::string CuttlefishConfig::InstanceSpecific::filename_encryption_mode() const { + return (*Dictionary())[kFilenameEncryptionMode].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_filename_encryption_mode( + const std::string& filename_encryption_mode) { + auto fmt = filename_encryption_mode; + std::transform(fmt.begin(), fmt.end(), fmt.begin(), ::tolower); + (*Dictionary())[kFilenameEncryptionMode] = fmt; +} + +static constexpr char kExternalNetworkMode[] = "external_network_mode"; +ExternalNetworkMode CuttlefishConfig::InstanceSpecific::external_network_mode() + const { + auto str = (*Dictionary())[kExternalNetworkMode].asString(); + return ParseExternalNetworkMode(str).value_or(ExternalNetworkMode::kUnknown); +} +void CuttlefishConfig::MutableInstanceSpecific::set_external_network_mode( + ExternalNetworkMode mode) { + (*Dictionary())[kExternalNetworkMode] = fmt::format("{}", mode); +} + +std::string CuttlefishConfig::InstanceSpecific::kernel_log_pipe_name() const { + return AbsolutePath(PerInstanceInternalPath("kernel-log-pipe")); +} + +std::string CuttlefishConfig::InstanceSpecific::console_pipe_prefix() const { + return AbsolutePath(PerInstanceInternalPath("console")); +} + +std::string CuttlefishConfig::InstanceSpecific::console_in_pipe_name() const { + return console_pipe_prefix() + ".in"; +} + +std::string CuttlefishConfig::InstanceSpecific::console_out_pipe_name() const { + return console_pipe_prefix() + ".out"; +} + +std::string CuttlefishConfig::InstanceSpecific::gnss_pipe_prefix() const { + return AbsolutePath(PerInstanceInternalPath("gnss")); +} + +std::string CuttlefishConfig::InstanceSpecific::gnss_in_pipe_name() const { + return gnss_pipe_prefix() + ".in"; +} + +std::string CuttlefishConfig::InstanceSpecific::gnss_out_pipe_name() const { + return gnss_pipe_prefix() + ".out"; +} + +static constexpr char kGnssGrpcProxyServerPort[] = + "gnss_grpc_proxy_server_port"; +int CuttlefishConfig::InstanceSpecific::gnss_grpc_proxy_server_port() const { + return (*Dictionary())[kGnssGrpcProxyServerPort].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_gnss_grpc_proxy_server_port( + int gnss_grpc_proxy_server_port) { + (*Dictionary())[kGnssGrpcProxyServerPort] = gnss_grpc_proxy_server_port; +} + +static constexpr char kGnssFilePath[] = "gnss_file_path"; +std::string CuttlefishConfig::InstanceSpecific::gnss_file_path() const { + return (*Dictionary())[kGnssFilePath].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_gnss_file_path( + const std::string& gnss_file_path) { + (*Dictionary())[kGnssFilePath] = gnss_file_path; +} + +static constexpr char kFixedLocationFilePath[] = "fixed_location_file_path"; +std::string CuttlefishConfig::InstanceSpecific::fixed_location_file_path() + const { + return (*Dictionary())[kFixedLocationFilePath].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_fixed_location_file_path( + const std::string& fixed_location_file_path) { + (*Dictionary())[kFixedLocationFilePath] = fixed_location_file_path; +} + +static constexpr char kGem5BinaryDir[] = "gem5_binary_dir"; +std::string CuttlefishConfig::InstanceSpecific::gem5_binary_dir() const { + return (*Dictionary())[kGem5BinaryDir].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_gem5_binary_dir( + const std::string& gem5_binary_dir) { + (*Dictionary())[kGem5BinaryDir] = gem5_binary_dir; +} + +static constexpr char kGem5CheckpointDir[] = "gem5_checkpoint_dir"; +std::string CuttlefishConfig::InstanceSpecific::gem5_checkpoint_dir() const { + return (*Dictionary())[kGem5CheckpointDir].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_gem5_checkpoint_dir( + const std::string& gem5_checkpoint_dir) { + (*Dictionary())[kGem5CheckpointDir] = gem5_checkpoint_dir; +} + +static constexpr char kKgdb[] = "kgdb"; +void CuttlefishConfig::MutableInstanceSpecific::set_kgdb(bool kgdb) { + (*Dictionary())[kKgdb] = kgdb; +} +bool CuttlefishConfig::InstanceSpecific::kgdb() const { + return (*Dictionary())[kKgdb].asBool(); +} + +static constexpr char kCpus[] = "cpus"; +void CuttlefishConfig::MutableInstanceSpecific::set_cpus(int cpus) { (*Dictionary())[kCpus] = cpus; } +int CuttlefishConfig::InstanceSpecific::cpus() const { return (*Dictionary())[kCpus].asInt(); } + +static constexpr char kDataPolicy[] = "data_policy"; +void CuttlefishConfig::MutableInstanceSpecific::set_data_policy( + const std::string& data_policy) { + (*Dictionary())[kDataPolicy] = data_policy; +} +std::string CuttlefishConfig::InstanceSpecific::data_policy() const { + return (*Dictionary())[kDataPolicy].asString(); +} + +static constexpr char kBlankDataImageMb[] = "blank_data_image_mb"; +void CuttlefishConfig::MutableInstanceSpecific::set_blank_data_image_mb( + int blank_data_image_mb) { + (*Dictionary())[kBlankDataImageMb] = blank_data_image_mb; +} +int CuttlefishConfig::InstanceSpecific::blank_data_image_mb() const { + return (*Dictionary())[kBlankDataImageMb].asInt(); +} + +static constexpr char kGdbPort[] = "gdb_port"; +void CuttlefishConfig::MutableInstanceSpecific::set_gdb_port(int port) { + (*Dictionary())[kGdbPort] = port; +} +int CuttlefishConfig::InstanceSpecific::gdb_port() const { + return (*Dictionary())[kGdbPort].asInt(); +} + +static constexpr char kMemoryMb[] = "memory_mb"; +int CuttlefishConfig::InstanceSpecific::memory_mb() const { + return (*Dictionary())[kMemoryMb].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_memory_mb(int memory_mb) { + (*Dictionary())[kMemoryMb] = memory_mb; +} + +static constexpr char kDdrMemMb[] = "ddr_mem_mb"; +int CuttlefishConfig::InstanceSpecific::ddr_mem_mb() const { + return (*Dictionary())[kDdrMemMb].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_ddr_mem_mb(int ddr_mem_mb) { + (*Dictionary())[kDdrMemMb] = ddr_mem_mb; +} + +static constexpr char kSetupWizardMode[] = "setupwizard_mode"; +std::string CuttlefishConfig::InstanceSpecific::setupwizard_mode() const { + return (*Dictionary())[kSetupWizardMode].asString(); +} +Result CuttlefishConfig::MutableInstanceSpecific::set_setupwizard_mode( + const std::string& mode) { + CF_EXPECT(ValidateSetupWizardMode(mode), + "setupwizard_mode flag has invalid value: " << mode); + (*Dictionary())[kSetupWizardMode] = mode; + return {}; +} + +static constexpr char kUserdataFormat[] = "userdata_format"; +std::string CuttlefishConfig::InstanceSpecific::userdata_format() const { + return (*Dictionary())[kUserdataFormat].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_userdata_format(const std::string& userdata_format) { + auto fmt = userdata_format; + std::transform(fmt.begin(), fmt.end(), fmt.begin(), ::tolower); + (*Dictionary())[kUserdataFormat] = fmt; +} + +static constexpr char kGuestEnforceSecurity[] = "guest_enforce_security"; +void CuttlefishConfig::MutableInstanceSpecific::set_guest_enforce_security(bool guest_enforce_security) { + (*Dictionary())[kGuestEnforceSecurity] = guest_enforce_security; +} +bool CuttlefishConfig::InstanceSpecific::guest_enforce_security() const { + return (*Dictionary())[kGuestEnforceSecurity].asBool(); +} + +static constexpr char kUseSdcard[] = "use_sdcard"; +void CuttlefishConfig::MutableInstanceSpecific::set_use_sdcard(bool use_sdcard) { + (*Dictionary())[kUseSdcard] = use_sdcard; +} +bool CuttlefishConfig::InstanceSpecific::use_sdcard() const { + return (*Dictionary())[kUseSdcard].asBool(); +} + +static constexpr char kPauseInBootloader[] = "pause_in_bootloader"; +void CuttlefishConfig::MutableInstanceSpecific::set_pause_in_bootloader(bool pause_in_bootloader) { + (*Dictionary())[kPauseInBootloader] = pause_in_bootloader; +} +bool CuttlefishConfig::InstanceSpecific::pause_in_bootloader() const { + return (*Dictionary())[kPauseInBootloader].asBool(); +} + +static constexpr char kRunAsDaemon[] = "run_as_daemon"; +bool CuttlefishConfig::InstanceSpecific::run_as_daemon() const { + return (*Dictionary())[kRunAsDaemon].asBool(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_run_as_daemon(bool run_as_daemon) { + (*Dictionary())[kRunAsDaemon] = run_as_daemon; +} + +static constexpr char kEnableMinimalMode[] = "enable_minimal_mode"; +bool CuttlefishConfig::InstanceSpecific::enable_minimal_mode() const { + return (*Dictionary())[kEnableMinimalMode].asBool(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_enable_minimal_mode( + bool enable_minimal_mode) { + (*Dictionary())[kEnableMinimalMode] = enable_minimal_mode; +} + +static constexpr char kRunModemSimulator[] = "enable_modem_simulator"; +bool CuttlefishConfig::InstanceSpecific::enable_modem_simulator() const { + return (*Dictionary())[kRunModemSimulator].asBool(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_enable_modem_simulator( + bool enable_modem_simulator) { + (*Dictionary())[kRunModemSimulator] = enable_modem_simulator; +} + +static constexpr char kModemSimulatorInstanceNumber[] = + "modem_simulator_instance_number"; +void CuttlefishConfig::MutableInstanceSpecific:: + set_modem_simulator_instance_number(int instance_number) { + (*Dictionary())[kModemSimulatorInstanceNumber] = instance_number; +} +int CuttlefishConfig::InstanceSpecific::modem_simulator_instance_number() + const { + return (*Dictionary())[kModemSimulatorInstanceNumber].asInt(); +} + +static constexpr char kModemSimulatorSimType[] = "modem_simulator_sim_type"; +void CuttlefishConfig::MutableInstanceSpecific::set_modem_simulator_sim_type( + int sim_type) { + (*Dictionary())[kModemSimulatorSimType] = sim_type; +} +int CuttlefishConfig::InstanceSpecific::modem_simulator_sim_type() const { + return (*Dictionary())[kModemSimulatorSimType].asInt(); +} + +static constexpr char kGpuMode[] = "gpu_mode"; +std::string CuttlefishConfig::InstanceSpecific::gpu_mode() const { + return (*Dictionary())[kGpuMode].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_gpu_mode(const std::string& name) { + (*Dictionary())[kGpuMode] = name; +} + +static constexpr char kGpuAngleFeatureOverridesEnabled[] = + "gpu_angle_feature_overrides_enabled"; +std::string +CuttlefishConfig::InstanceSpecific::gpu_angle_feature_overrides_enabled() + const { + return (*Dictionary())[kGpuAngleFeatureOverridesEnabled].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific:: + set_gpu_angle_feature_overrides_enabled(const std::string& overrides) { + (*Dictionary())[kGpuAngleFeatureOverridesEnabled] = overrides; +} + +static constexpr char kGpuAngleFeatureOverridesDisabled[] = + "gpu_angle_feature_overrides_disabled"; +std::string +CuttlefishConfig::InstanceSpecific::gpu_angle_feature_overrides_disabled() + const { + return (*Dictionary())[kGpuAngleFeatureOverridesDisabled].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific:: + set_gpu_angle_feature_overrides_disabled(const std::string& overrides) { + (*Dictionary())[kGpuAngleFeatureOverridesDisabled] = overrides; +} + +static constexpr char kGpuCaptureBinary[] = "gpu_capture_binary"; +std::string CuttlefishConfig::InstanceSpecific::gpu_capture_binary() const { + return (*Dictionary())[kGpuCaptureBinary].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_gpu_capture_binary(const std::string& name) { + (*Dictionary())[kGpuCaptureBinary] = name; +} + +static constexpr char kGpuGfxstreamTransport[] = "gpu_gfxstream_transport"; +std::string CuttlefishConfig::InstanceSpecific::gpu_gfxstream_transport() + const { + return (*Dictionary())[kGpuGfxstreamTransport].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_gpu_gfxstream_transport( + const std::string& transport) { + (*Dictionary())[kGpuGfxstreamTransport] = transport; +} + +static constexpr char kRestartSubprocesses[] = "restart_subprocesses"; +bool CuttlefishConfig::InstanceSpecific::restart_subprocesses() const { + return (*Dictionary())[kRestartSubprocesses].asBool(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_restart_subprocesses(bool restart_subprocesses) { + (*Dictionary())[kRestartSubprocesses] = restart_subprocesses; +} + +static constexpr char kHWComposer[] = "hwcomposer"; +std::string CuttlefishConfig::InstanceSpecific::hwcomposer() const { + return (*Dictionary())[kHWComposer].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_hwcomposer(const std::string& name) { + (*Dictionary())[kHWComposer] = name; +} + +static constexpr char kEnableGpuUdmabuf[] = "enable_gpu_udmabuf"; +void CuttlefishConfig::MutableInstanceSpecific::set_enable_gpu_udmabuf(const bool enable_gpu_udmabuf) { + (*Dictionary())[kEnableGpuUdmabuf] = enable_gpu_udmabuf; +} +bool CuttlefishConfig::InstanceSpecific::enable_gpu_udmabuf() const { + return (*Dictionary())[kEnableGpuUdmabuf].asBool(); +} + +static constexpr char kEnableGpuVhostUser[] = "enable_gpu_vhost_user"; +void CuttlefishConfig::MutableInstanceSpecific::set_enable_gpu_vhost_user( + const bool enable_gpu_vhost_user) { + (*Dictionary())[kEnableGpuVhostUser] = enable_gpu_vhost_user; +} +bool CuttlefishConfig::InstanceSpecific::enable_gpu_vhost_user() const { + return (*Dictionary())[kEnableGpuVhostUser].asBool(); +} + +static constexpr char kEnableGpuExternalBlob[] = "enable_gpu_external_blob"; +void CuttlefishConfig::MutableInstanceSpecific::set_enable_gpu_external_blob( + const bool enable_gpu_external_blob) { + (*Dictionary())[kEnableGpuExternalBlob] = enable_gpu_external_blob; +} +bool CuttlefishConfig::InstanceSpecific::enable_gpu_external_blob() const { + return (*Dictionary())[kEnableGpuExternalBlob].asBool(); +} + +static constexpr char kEnableGpuSystemBlob[] = "enable_gpu_system_blob"; +void CuttlefishConfig::MutableInstanceSpecific::set_enable_gpu_system_blob( + const bool enable_gpu_system_blob) { + (*Dictionary())[kEnableGpuSystemBlob] = enable_gpu_system_blob; +} +bool CuttlefishConfig::InstanceSpecific::enable_gpu_system_blob() const { + return (*Dictionary())[kEnableGpuSystemBlob].asBool(); +} + +static constexpr char kEnableAudio[] = "enable_audio"; +void CuttlefishConfig::MutableInstanceSpecific::set_enable_audio(bool enable) { + (*Dictionary())[kEnableAudio] = enable; +} +bool CuttlefishConfig::InstanceSpecific::enable_audio() const { + return (*Dictionary())[kEnableAudio].asBool(); +} + +static constexpr char kEnableGnssGrpcProxy[] = "enable_gnss_grpc_proxy"; +void CuttlefishConfig::MutableInstanceSpecific::set_enable_gnss_grpc_proxy(const bool enable_gnss_grpc_proxy) { + (*Dictionary())[kEnableGnssGrpcProxy] = enable_gnss_grpc_proxy; +} +bool CuttlefishConfig::InstanceSpecific::enable_gnss_grpc_proxy() const { + return (*Dictionary())[kEnableGnssGrpcProxy].asBool(); +} + +static constexpr char kEnableBootAnimation[] = "enable_bootanimation"; +bool CuttlefishConfig::InstanceSpecific::enable_bootanimation() const { + return (*Dictionary())[kEnableBootAnimation].asBool(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_enable_bootanimation( + bool enable_bootanimation) { + (*Dictionary())[kEnableBootAnimation] = enable_bootanimation; +} + +static constexpr char kExtraBootconfigArgsInstanced[] = "extra_bootconfig_args"; +std::vector +CuttlefishConfig::InstanceSpecific::extra_bootconfig_args() const { + std::string extra_bootconfig_args_str = + (*Dictionary())[kExtraBootconfigArgsInstanced].asString(); + std::vector bootconfig; + if (!extra_bootconfig_args_str.empty()) { + for (const auto& arg : + android::base::Split(extra_bootconfig_args_str, " ")) { + bootconfig.push_back(arg); + } + } + return bootconfig; +} + +void CuttlefishConfig::MutableInstanceSpecific::set_extra_bootconfig_args( + const std::string& transport) { + (*Dictionary())[kExtraBootconfigArgsInstanced] = transport; +} + +static constexpr char kRecordScreen[] = "record_screen"; +void CuttlefishConfig::MutableInstanceSpecific::set_record_screen( + bool record_screen) { + (*Dictionary())[kRecordScreen] = record_screen; +} +bool CuttlefishConfig::InstanceSpecific::record_screen() const { + return (*Dictionary())[kRecordScreen].asBool(); +} + +static constexpr char kGem5DebugFile[] = "gem5_debug_file"; +std::string CuttlefishConfig::InstanceSpecific::gem5_debug_file() const { + return (*Dictionary())[kGem5DebugFile].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_gem5_debug_file(const std::string& gem5_debug_file) { + (*Dictionary())[kGem5DebugFile] = gem5_debug_file; +} + +static constexpr char kProtectedVm[] = "protected_vm"; +void CuttlefishConfig::MutableInstanceSpecific::set_protected_vm(bool protected_vm) { + (*Dictionary())[kProtectedVm] = protected_vm; +} +bool CuttlefishConfig::InstanceSpecific::protected_vm() const { + return (*Dictionary())[kProtectedVm].asBool(); +} + +static constexpr char kMte[] = "mte"; +void CuttlefishConfig::MutableInstanceSpecific::set_mte(bool mte) { + (*Dictionary())[kMte] = mte; +} +bool CuttlefishConfig::InstanceSpecific::mte() const { + return (*Dictionary())[kMte].asBool(); +} + +static constexpr char kEnableKernelLog[] = "enable_kernel_log"; +void CuttlefishConfig::MutableInstanceSpecific::set_enable_kernel_log(bool enable_kernel_log) { + (*Dictionary())[kEnableKernelLog] = enable_kernel_log; +} +bool CuttlefishConfig::InstanceSpecific::enable_kernel_log() const { + return (*Dictionary())[kEnableKernelLog].asBool(); +} + +static constexpr char kBootSlot[] = "boot_slot"; +void CuttlefishConfig::MutableInstanceSpecific::set_boot_slot(const std::string& boot_slot) { + (*Dictionary())[kBootSlot] = boot_slot; +} +std::string CuttlefishConfig::InstanceSpecific::boot_slot() const { + return (*Dictionary())[kBootSlot].asString(); +} + +static constexpr char kEnableWebRTC[] = "enable_webrtc"; +void CuttlefishConfig::MutableInstanceSpecific::set_enable_webrtc(bool enable_webrtc) { + (*Dictionary())[kEnableWebRTC] = enable_webrtc; +} +bool CuttlefishConfig::InstanceSpecific::enable_webrtc() const { + return (*Dictionary())[kEnableWebRTC].asBool(); +} + +static constexpr char kWebRTCAssetsDir[] = "webrtc_assets_dir"; +void CuttlefishConfig::MutableInstanceSpecific::set_webrtc_assets_dir(const std::string& webrtc_assets_dir) { + (*Dictionary())[kWebRTCAssetsDir] = webrtc_assets_dir; +} +std::string CuttlefishConfig::InstanceSpecific::webrtc_assets_dir() const { + return (*Dictionary())[kWebRTCAssetsDir].asString(); +} + +static constexpr char kWebrtcTcpPortRange[] = "webrtc_tcp_port_range"; +void CuttlefishConfig::MutableInstanceSpecific::set_webrtc_tcp_port_range( + std::pair range) { + Json::Value arr(Json::ValueType::arrayValue); + arr[0] = range.first; + arr[1] = range.second; + (*Dictionary())[kWebrtcTcpPortRange] = arr; +} +std::pair CuttlefishConfig::InstanceSpecific::webrtc_tcp_port_range() const { + std::pair ret; + ret.first = (*Dictionary())[kWebrtcTcpPortRange][0].asInt(); + ret.second = (*Dictionary())[kWebrtcTcpPortRange][1].asInt(); + return ret; +} + +static constexpr char kWebrtcUdpPortRange[] = "webrtc_udp_port_range"; +void CuttlefishConfig::MutableInstanceSpecific::set_webrtc_udp_port_range( + std::pair range) { + Json::Value arr(Json::ValueType::arrayValue); + arr[0] = range.first; + arr[1] = range.second; + (*Dictionary())[kWebrtcUdpPortRange] = arr; +} +std::pair CuttlefishConfig::InstanceSpecific::webrtc_udp_port_range() const { + std::pair ret; + ret.first = (*Dictionary())[kWebrtcUdpPortRange][0].asInt(); + ret.second = (*Dictionary())[kWebrtcUdpPortRange][1].asInt(); + return ret; +} + +static constexpr char kGrpcConfig[] = "grpc_config"; +std::string CuttlefishConfig::InstanceSpecific::grpc_socket_path() const { + return (*Dictionary())[kGrpcConfig].asString(); +} + +void CuttlefishConfig::MutableInstanceSpecific::set_grpc_socket_path( + const std::string& socket_path) { + (*Dictionary())[kGrpcConfig] = socket_path; +} + +static constexpr char kSmt[] = "smt"; +void CuttlefishConfig::MutableInstanceSpecific::set_smt(bool smt) { + (*Dictionary())[kSmt] = smt; +} +bool CuttlefishConfig::InstanceSpecific::smt() const { + return (*Dictionary())[kSmt].asBool(); +} + +static constexpr char kCrosvmBinary[] = "crosvm_binary"; +std::string CuttlefishConfig::InstanceSpecific::crosvm_binary() const { + return (*Dictionary())[kCrosvmBinary].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_crosvm_binary( + const std::string& crosvm_binary) { + (*Dictionary())[kCrosvmBinary] = crosvm_binary; +} + +void CuttlefishConfig::MutableInstanceSpecific::SetPath( + const std::string& key, const std::string& path) { + if (!path.empty()) { + (*Dictionary())[key] = AbsolutePath(path); + } +} + +static constexpr char kSeccompPolicyDir[] = "seccomp_policy_dir"; +void CuttlefishConfig::MutableInstanceSpecific::set_seccomp_policy_dir( + const std::string& seccomp_policy_dir) { + if (seccomp_policy_dir.empty()) { + (*Dictionary())[kSeccompPolicyDir] = seccomp_policy_dir; + return; + } + SetPath(kSeccompPolicyDir, seccomp_policy_dir); +} +std::string CuttlefishConfig::InstanceSpecific::seccomp_policy_dir() const { + return (*Dictionary())[kSeccompPolicyDir].asString(); +} + +static constexpr char kQemuBinaryDir[] = "qemu_binary_dir"; +std::string CuttlefishConfig::InstanceSpecific::qemu_binary_dir() const { + return (*Dictionary())[kQemuBinaryDir].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_qemu_binary_dir( + const std::string& qemu_binary_dir) { + (*Dictionary())[kQemuBinaryDir] = qemu_binary_dir; +} + +static constexpr char kVhostNet[] = "vhost_net"; +void CuttlefishConfig::MutableInstanceSpecific::set_vhost_net(bool vhost_net) { + (*Dictionary())[kVhostNet] = vhost_net; +} +bool CuttlefishConfig::InstanceSpecific::vhost_net() const { + return (*Dictionary())[kVhostNet].asBool(); +} + +static constexpr char kVhostUserVsock[] = "vhost_user_vsock"; +void CuttlefishConfig::MutableInstanceSpecific::set_vhost_user_vsock( + bool vhost_user_vsock) { + (*Dictionary())[kVhostUserVsock] = vhost_user_vsock; +} +bool CuttlefishConfig::InstanceSpecific::vhost_user_vsock() const { + return (*Dictionary())[kVhostUserVsock].asBool(); +} + +static constexpr char kRilDns[] = "ril_dns"; +void CuttlefishConfig::MutableInstanceSpecific::set_ril_dns(const std::string& ril_dns) { + (*Dictionary())[kRilDns] = ril_dns; +} +std::string CuttlefishConfig::InstanceSpecific::ril_dns() const { + return (*Dictionary())[kRilDns].asString(); +} + +static constexpr char kDisplayConfigs[] = "display_configs"; +static constexpr char kXRes[] = "x_res"; +static constexpr char kYRes[] = "y_res"; +static constexpr char kDpi[] = "dpi"; +static constexpr char kRefreshRateHz[] = "refresh_rate_hz"; +std::vector +CuttlefishConfig::InstanceSpecific::display_configs() const { + std::vector display_configs; + for (auto& display_config_json : (*Dictionary())[kDisplayConfigs]) { + DisplayConfig display_config = {}; + display_config.width = display_config_json[kXRes].asInt(); + display_config.height = display_config_json[kYRes].asInt(); + display_config.dpi = display_config_json[kDpi].asInt(); + display_config.refresh_rate_hz = + display_config_json[kRefreshRateHz].asInt(); + display_configs.emplace_back(display_config); + } + return display_configs; +} +void CuttlefishConfig::MutableInstanceSpecific::set_display_configs( + const std::vector& display_configs) { + Json::Value display_configs_json(Json::arrayValue); + + for (const DisplayConfig& display_configs : display_configs) { + Json::Value display_config_json(Json::objectValue); + display_config_json[kXRes] = display_configs.width; + display_config_json[kYRes] = display_configs.height; + display_config_json[kDpi] = display_configs.dpi; + display_config_json[kRefreshRateHz] = display_configs.refresh_rate_hz; + display_configs_json.append(display_config_json); + } + + (*Dictionary())[kDisplayConfigs] = display_configs_json; +} + +static constexpr char kTouchpadConfigs[] = "touchpad_configs"; + +Json::Value CuttlefishConfig::TouchpadConfig::Serialize( + const CuttlefishConfig::TouchpadConfig& config) { + Json::Value config_json(Json::objectValue); + config_json[kXRes] = config.width; + config_json[kYRes] = config.height; + + return config_json; +} + +CuttlefishConfig::TouchpadConfig CuttlefishConfig::TouchpadConfig::Deserialize( + const Json::Value& config_json) { + TouchpadConfig touchpad_config = {}; + touchpad_config.width = config_json[kXRes].asInt(); + touchpad_config.height = config_json[kYRes].asInt(); + + return touchpad_config; +} + +std::vector +CuttlefishConfig::InstanceSpecific::touchpad_configs() const { + std::vector touchpad_configs; + for (auto& touchpad_config_json : (*Dictionary())[kTouchpadConfigs]) { + auto touchpad_config = TouchpadConfig::Deserialize(touchpad_config_json); + touchpad_configs.emplace_back(touchpad_config); + } + return touchpad_configs; +} +void CuttlefishConfig::MutableInstanceSpecific::set_touchpad_configs( + const std::vector& touchpad_configs) { + Json::Value touchpad_configs_json(Json::arrayValue); + + for (const TouchpadConfig& touchpad_config : touchpad_configs) { + touchpad_configs_json.append(TouchpadConfig::Serialize(touchpad_config)); + } + + (*Dictionary())[kTouchpadConfigs] = touchpad_configs_json; +} + +static constexpr char kTargetArch[] = "target_arch"; +void CuttlefishConfig::MutableInstanceSpecific::set_target_arch( + Arch target_arch) { + (*Dictionary())[kTargetArch] = static_cast(target_arch); +} +Arch CuttlefishConfig::InstanceSpecific::target_arch() const { + return static_cast((*Dictionary())[kTargetArch].asInt()); +} + +static constexpr char kEnableSandbox[] = "enable_sandbox"; +void CuttlefishConfig::MutableInstanceSpecific::set_enable_sandbox(const bool enable_sandbox) { + (*Dictionary())[kEnableSandbox] = enable_sandbox; +} +bool CuttlefishConfig::InstanceSpecific::enable_sandbox() const { + return (*Dictionary())[kEnableSandbox].asBool(); +} +static constexpr char kEnableVirtiofs[] = "enable_virtiofs"; +void CuttlefishConfig::MutableInstanceSpecific::set_enable_virtiofs( + const bool enable_virtiofs) { + (*Dictionary())[kEnableVirtiofs] = enable_virtiofs; +} +bool CuttlefishConfig::InstanceSpecific::enable_virtiofs() const { + return (*Dictionary())[kEnableVirtiofs].asBool(); +} +static constexpr char kConsole[] = "console"; +void CuttlefishConfig::MutableInstanceSpecific::set_console(bool console) { + (*Dictionary())[kConsole] = console; +} +bool CuttlefishConfig::InstanceSpecific::console() const { + return (*Dictionary())[kConsole].asBool(); +} +std::string CuttlefishConfig::InstanceSpecific::console_dev() const { + auto can_use_virtio_console = !kgdb() && !use_bootloader(); + std::string console_dev; + if (can_use_virtio_console || + config_->vm_manager() == vm_manager::Gem5Manager::name()) { + // If kgdb and the bootloader are disabled, the Android serial console + // spawns on a virtio-console port. If the bootloader is enabled, virtio + // console can't be used since uboot doesn't support it. + console_dev = "hvc1"; + } else { + // QEMU and Gem5 emulate pl011 on ARM/ARM64, but QEMU and crosvm on other + // architectures emulate ns16550a/uart8250 instead. + Arch target = target_arch(); + if ((target == Arch::Arm64 || target == Arch::Arm) && + config_->vm_manager() != vm_manager::CrosvmManager::name()) { + console_dev = "ttyAMA0"; + } else { + console_dev = "ttyS0"; + } + } + return console_dev; +} + +std::string CuttlefishConfig::InstanceSpecific::logcat_pipe_name() const { + return AbsolutePath(PerInstanceInternalPath("logcat-pipe")); +} + +std::string CuttlefishConfig::InstanceSpecific::restore_pipe_name() const { + return AbsolutePath(PerInstanceInternalPath("restore-pipe")); +} + +std::string CuttlefishConfig::InstanceSpecific::access_kregistry_path() const { + return AbsolutePath(PerInstancePath("access-kregistry")); +} + +std::string CuttlefishConfig::InstanceSpecific::hwcomposer_pmem_path() const { + return AbsolutePath(PerInstancePath("hwcomposer-pmem")); +} + +std::string CuttlefishConfig::InstanceSpecific::pstore_path() const { + return AbsolutePath(PerInstancePath("pstore")); +} + +std::string CuttlefishConfig::InstanceSpecific::console_path() const { + return AbsolutePath(PerInstancePath("console")); +} + +std::string CuttlefishConfig::InstanceSpecific::logcat_path() const { + return AbsolutePath(PerInstanceLogPath("logcat")); +} + +std::string CuttlefishConfig::InstanceSpecific::launcher_monitor_socket_path() + const { + return AbsolutePath(PerInstanceUdsPath("launcher_monitor.sock")); +} + +static constexpr char kModemSimulatorPorts[] = "modem_simulator_ports"; +std::string CuttlefishConfig::InstanceSpecific::modem_simulator_ports() const { + return (*Dictionary())[kModemSimulatorPorts].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_modem_simulator_ports( + const std::string& modem_simulator_ports) { + (*Dictionary())[kModemSimulatorPorts] = modem_simulator_ports; +} + +std::string CuttlefishConfig::InstanceSpecific::launcher_log_path() const { + return AbsolutePath(PerInstanceLogPath("launcher.log")); +} + +std::string CuttlefishConfig::InstanceSpecific::metadata_image() const { + return AbsolutePath(PerInstancePath("metadata.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::misc_image() const { + return AbsolutePath(PerInstancePath("misc.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::sdcard_path() const { + return AbsolutePath(PerInstancePath("sdcard.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::sdcard_overlay_path() const { + return AbsolutePath(PerInstancePath("sdcard_overlay.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::persistent_composite_disk_path() + const { + return AbsolutePath(PerInstancePath("persistent_composite.img")); +} + +std::string +CuttlefishConfig::InstanceSpecific::persistent_composite_overlay_path() const { + return AbsolutePath(PerInstancePath("persistent_composite_overlay.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::persistent_ap_composite_disk_path() + const { + return AbsolutePath(PerInstancePath("ap_persistent_composite.img")); +} + +std::string +CuttlefishConfig::InstanceSpecific::persistent_ap_composite_overlay_path() + const { + return AbsolutePath(PerInstancePath("ap_persistent_composite_overlay.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::os_composite_disk_path() + const { + return AbsolutePath(PerInstancePath("os_composite.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::ap_composite_disk_path() + const { + return AbsolutePath(PerInstancePath("ap_composite.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::vbmeta_path() const { + return AbsolutePath(PerInstancePath("vbmeta.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::ap_vbmeta_path() const { + return AbsolutePath(PerInstancePath("ap_vbmeta.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::uboot_env_image_path() const { + return AbsolutePath(PerInstancePath("uboot_env.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::ap_uboot_env_image_path() const { + return AbsolutePath(PerInstancePath("ap_uboot_env.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::chromeos_state_image() const { + return AbsolutePath(PerInstancePath("chromeos_state.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::esp_image_path() const { + return AbsolutePath(PerInstancePath("esp.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::ap_esp_image_path() const { + return AbsolutePath(PerInstancePath("ap_esp.img")); +} + +std::string CuttlefishConfig::InstanceSpecific::otheros_esp_grub_config() const { + return AbsolutePath(PerInstancePath("grub.cfg")); +} + +std::string CuttlefishConfig::InstanceSpecific::ap_esp_grub_config() const { + return AbsolutePath(PerInstancePath("ap_grub.cfg")); +} + +static constexpr char kMobileBridgeName[] = "mobile_bridge_name"; + +std::string CuttlefishConfig::InstanceSpecific::audio_server_path() const { + return AbsolutePath(PerInstanceInternalUdsPath("audio_server.sock")); +} + +CuttlefishConfig::InstanceSpecific::BootFlow CuttlefishConfig::InstanceSpecific::boot_flow() const { + const bool android_efi_loader_flow_used = !android_efi_loader().empty(); + + const bool chromeos_disk_flow_used = !chromeos_disk().empty(); + + const bool chromeos_flow_used = + !chromeos_kernel_path().empty() || !chromeos_root_image().empty(); + + const bool linux_flow_used = !linux_kernel_path().empty() + || !linux_initramfs_path().empty() + || !linux_root_image().empty(); + + const bool fuchsia_flow_used = !fuchsia_zedboot_path().empty() + || !fuchsia_root_image().empty() + || !fuchsia_multiboot_bin_path().empty(); + + if (android_efi_loader_flow_used) { + return BootFlow::AndroidEfiLoader; + } else if (chromeos_flow_used) { + return BootFlow::ChromeOs; + } else if (chromeos_disk_flow_used) { + return BootFlow::ChromeOsDisk; + } else if (linux_flow_used) { + return BootFlow::Linux; + } else if (fuchsia_flow_used) { + return BootFlow::Fuchsia; + } else { + return BootFlow::Android; + } + } + +std::string CuttlefishConfig::InstanceSpecific::mobile_bridge_name() const { + return (*Dictionary())[kMobileBridgeName].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_mobile_bridge_name( + const std::string& mobile_bridge_name) { + (*Dictionary())[kMobileBridgeName] = mobile_bridge_name; +} + +static constexpr char kMobileTapName[] = "mobile_tap_name"; +std::string CuttlefishConfig::InstanceSpecific::mobile_tap_name() const { + return (*Dictionary())[kMobileTapName].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_mobile_tap_name( + const std::string& mobile_tap_name) { + (*Dictionary())[kMobileTapName] = mobile_tap_name; +} + +static constexpr char kMobileMac[] = "mobile_mac"; +std::string CuttlefishConfig::InstanceSpecific::mobile_mac() const { + return (*Dictionary())[kMobileMac].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_mobile_mac( + const std::string& mac) { + (*Dictionary())[kMobileMac] = mac; +} + +// TODO(b/199103204): remove this as well when +// PRODUCT_ENFORCE_MAC80211_HWSIM is removed +static constexpr char kWifiTapName[] = "wifi_tap_name"; +std::string CuttlefishConfig::InstanceSpecific::wifi_tap_name() const { + return (*Dictionary())[kWifiTapName].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_wifi_tap_name( + const std::string& wifi_tap_name) { + (*Dictionary())[kWifiTapName] = wifi_tap_name; +} + +static constexpr char kWifiBridgeName[] = "wifi_bridge_name"; +std::string CuttlefishConfig::InstanceSpecific::wifi_bridge_name() const { + return (*Dictionary())[kWifiBridgeName].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_wifi_bridge_name( + const std::string& wifi_bridge_name) { + (*Dictionary())[kWifiBridgeName] = wifi_bridge_name; +} + +static constexpr char kWifiMac[] = "wifi_mac"; +std::string CuttlefishConfig::InstanceSpecific::wifi_mac() const { + return (*Dictionary())[kWifiMac].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_wifi_mac( + const std::string& mac) { + (*Dictionary())[kWifiMac] = mac; +} + +static constexpr char kUseBridgedWifiTap[] = "use_bridged_wifi_tap"; +bool CuttlefishConfig::InstanceSpecific::use_bridged_wifi_tap() const { + return (*Dictionary())[kUseBridgedWifiTap].asBool(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_use_bridged_wifi_tap( + bool use_bridged_wifi_tap) { + (*Dictionary())[kUseBridgedWifiTap] = use_bridged_wifi_tap; +} + +static constexpr char kEthernetTapName[] = "ethernet_tap_name"; +std::string CuttlefishConfig::InstanceSpecific::ethernet_tap_name() const { + return (*Dictionary())[kEthernetTapName].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_ethernet_tap_name( + const std::string& ethernet_tap_name) { + (*Dictionary())[kEthernetTapName] = ethernet_tap_name; +} + +static constexpr char kEthernetBridgeName[] = "ethernet_bridge_name"; +std::string CuttlefishConfig::InstanceSpecific::ethernet_bridge_name() const { + return (*Dictionary())[kEthernetBridgeName].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_ethernet_bridge_name( + const std::string& ethernet_bridge_name) { + (*Dictionary())[kEthernetBridgeName] = ethernet_bridge_name; +} + +static constexpr char kEthernetMac[] = "ethernet_mac"; +std::string CuttlefishConfig::InstanceSpecific::ethernet_mac() const { + return (*Dictionary())[kEthernetMac].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_ethernet_mac( + const std::string& mac) { + (*Dictionary())[kEthernetMac] = mac; +} + +static constexpr char kEthernetIPV6[] = "ethernet_ipv6"; +std::string CuttlefishConfig::InstanceSpecific::ethernet_ipv6() const { + return (*Dictionary())[kEthernetIPV6].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_ethernet_ipv6( + const std::string& ip) { + (*Dictionary())[kEthernetIPV6] = ip; +} + +static constexpr char kUseAllocd[] = "use_allocd"; +bool CuttlefishConfig::InstanceSpecific::use_allocd() const { + return (*Dictionary())[kUseAllocd].asBool(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_use_allocd( + bool use_allocd) { + (*Dictionary())[kUseAllocd] = use_allocd; +} + +static constexpr char kSessionId[] = "session_id"; +uint32_t CuttlefishConfig::InstanceSpecific::session_id() const { + return (*Dictionary())[kSessionId].asUInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_session_id( + uint32_t session_id) { + (*Dictionary())[kSessionId] = session_id; +} + +static constexpr char kVsockGuestCid[] = "vsock_guest_cid"; +int CuttlefishConfig::InstanceSpecific::vsock_guest_cid() const { + return (*Dictionary())[kVsockGuestCid].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_vsock_guest_cid( + int vsock_guest_cid) { + (*Dictionary())[kVsockGuestCid] = vsock_guest_cid; +} + +static constexpr char kUuid[] = "uuid"; +std::string CuttlefishConfig::InstanceSpecific::uuid() const { + return (*Dictionary())[kUuid].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_uuid(const std::string& uuid) { + (*Dictionary())[kUuid] = uuid; +} + +static constexpr char kEnvironmentName[] = "environment_name"; +std::string CuttlefishConfig::InstanceSpecific::environment_name() const { + return (*Dictionary())[kEnvironmentName].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_environment_name( + const std::string& environment_name) { + (*Dictionary())[kEnvironmentName] = environment_name; +} + +std::string CuttlefishConfig::InstanceSpecific::CrosvmSocketPath() const { + return PerInstanceInternalUdsPath("crosvm_control.sock"); +} + +std::string CuttlefishConfig::InstanceSpecific::OpenwrtCrosvmSocketPath() + const { + return PerInstanceInternalUdsPath("ap_control.sock"); +} + +static constexpr char kHostPort[] = "adb_host_port"; +int CuttlefishConfig::InstanceSpecific::adb_host_port() const { + return (*Dictionary())[kHostPort].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_adb_host_port(int port) { + (*Dictionary())[kHostPort] = port; +} + +static constexpr char kFastbootHostPort[] = "fastboot_host_port"; +int CuttlefishConfig::InstanceSpecific::fastboot_host_port() const { + return (*Dictionary())[kFastbootHostPort].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_fastboot_host_port(int port) { + (*Dictionary())[kFastbootHostPort] = port; +} + +static constexpr char kModemSimulatorId[] = "modem_simulator_host_id"; +int CuttlefishConfig::InstanceSpecific::modem_simulator_host_id() const { + return (*Dictionary())[kModemSimulatorId].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_modem_simulator_host_id( + int id) { + (*Dictionary())[kModemSimulatorId] = id; +} + +static constexpr char kAdbIPAndPort[] = "adb_ip_and_port"; +std::string CuttlefishConfig::InstanceSpecific::adb_ip_and_port() const { + return (*Dictionary())[kAdbIPAndPort].asString(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_adb_ip_and_port( + const std::string& ip_port) { + (*Dictionary())[kAdbIPAndPort] = ip_port; +} + +std::string CuttlefishConfig::InstanceSpecific::adb_device_name() const { + if (adb_ip_and_port() != "") { + return adb_ip_and_port(); + } + LOG(ERROR) << "no adb_mode found, returning bad device name"; + return "NO_ADB_MODE_SET_NO_VALID_DEVICE_NAME"; +} + +static constexpr char kQemuVncServerPort[] = "qemu_vnc_server_port"; +int CuttlefishConfig::InstanceSpecific::qemu_vnc_server_port() const { + return (*Dictionary())[kQemuVncServerPort].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_qemu_vnc_server_port( + int qemu_vnc_server_port) { + (*Dictionary())[kQemuVncServerPort] = qemu_vnc_server_port; +} + +static constexpr char kTombstoneReceiverPort[] = "tombstone_receiver_port"; +int CuttlefishConfig::InstanceSpecific::tombstone_receiver_port() const { + return (*Dictionary())[kTombstoneReceiverPort].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_tombstone_receiver_port(int tombstone_receiver_port) { + (*Dictionary())[kTombstoneReceiverPort] = tombstone_receiver_port; +} + +static constexpr char kAudioControlServerPort[] = "audiocontrol_server_port"; +int CuttlefishConfig::InstanceSpecific::audiocontrol_server_port() const { + return (*Dictionary())[kAudioControlServerPort].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_audiocontrol_server_port(int audiocontrol_server_port) { + (*Dictionary())[kAudioControlServerPort] = audiocontrol_server_port; +} + +static constexpr char kConfigServerPort[] = "config_server_port"; +int CuttlefishConfig::InstanceSpecific::config_server_port() const { + return (*Dictionary())[kConfigServerPort].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_config_server_port(int config_server_port) { + (*Dictionary())[kConfigServerPort] = config_server_port; +} + +static constexpr char kLightsServerPort[] = "lights_server_port"; +int CuttlefishConfig::InstanceSpecific::lights_server_port() const { + return (*Dictionary())[kLightsServerPort].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_lights_server_port(int lights_server_port) { + (*Dictionary())[kLightsServerPort] = lights_server_port; +} + +static constexpr char kCameraServerPort[] = "camera_server_port"; +int CuttlefishConfig::InstanceSpecific::camera_server_port() const { + return (*Dictionary())[kCameraServerPort].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_camera_server_port( + int camera_server_port) { + (*Dictionary())[kCameraServerPort] = camera_server_port; +} + +static constexpr char kWebrtcDeviceId[] = "webrtc_device_id"; +void CuttlefishConfig::MutableInstanceSpecific::set_webrtc_device_id( + const std::string& id) { + (*Dictionary())[kWebrtcDeviceId] = id; +} +std::string CuttlefishConfig::InstanceSpecific::webrtc_device_id() const { + return (*Dictionary())[kWebrtcDeviceId].asString(); +} + +static constexpr char kGroupId[] = "group_id"; +void CuttlefishConfig::MutableInstanceSpecific::set_group_id( + const std::string& id) { + (*Dictionary())[kGroupId] = id; +} +std::string CuttlefishConfig::InstanceSpecific::group_id() const { + return (*Dictionary())[kGroupId].asString(); +} + +static constexpr char kStartSigServer[] = "webrtc_start_sig_server"; +void CuttlefishConfig::MutableInstanceSpecific::set_start_webrtc_signaling_server(bool start) { + (*Dictionary())[kStartSigServer] = start; +} +bool CuttlefishConfig::InstanceSpecific::start_webrtc_sig_server() const { + return (*Dictionary())[kStartSigServer].asBool(); +} + +static constexpr char kStartSigServerProxy[] = "webrtc_start_sig_server_proxy"; +void CuttlefishConfig::MutableInstanceSpecific:: + set_start_webrtc_sig_server_proxy(bool start) { + (*Dictionary())[kStartSigServerProxy] = start; +} +bool CuttlefishConfig::InstanceSpecific::start_webrtc_sig_server_proxy() const { + return (*Dictionary())[kStartSigServerProxy].asBool(); +} + +static constexpr char kStartRootcanal[] = "start_rootcanal"; +void CuttlefishConfig::MutableInstanceSpecific::set_start_rootcanal( + bool start) { + (*Dictionary())[kStartRootcanal] = start; +} +bool CuttlefishConfig::InstanceSpecific::start_rootcanal() const { + return (*Dictionary())[kStartRootcanal].asBool(); +} + +static constexpr char kStartCasimir[] = "start_casimir"; +void CuttlefishConfig::MutableInstanceSpecific::set_start_casimir(bool start) { + (*Dictionary())[kStartCasimir] = start; +} +bool CuttlefishConfig::InstanceSpecific::start_casimir() const { + return (*Dictionary())[kStartCasimir].asBool(); +} + +static constexpr char kStartPica[] = "start_pica"; +void CuttlefishConfig::MutableInstanceSpecific::set_start_pica( + bool start) { + (*Dictionary())[kStartPica] = start; +} +bool CuttlefishConfig::InstanceSpecific::start_pica() const { + return (*Dictionary())[kStartPica].asBool(); +} + +static constexpr char kStartNetsim[] = "start_netsim"; +void CuttlefishConfig::MutableInstanceSpecific::set_start_netsim(bool start) { + (*Dictionary())[kStartNetsim] = start; +} +bool CuttlefishConfig::InstanceSpecific::start_netsim() const { + return (*Dictionary())[kStartNetsim].asBool(); +} + +// TODO(b/288987294) Remove this when separating environment is done +static constexpr char kStartWmediumdInstance[] = "start_wmediumd_instance"; +void CuttlefishConfig::MutableInstanceSpecific::set_start_wmediumd_instance( + bool start) { + (*Dictionary())[kStartWmediumdInstance] = start; +} +bool CuttlefishConfig::InstanceSpecific::start_wmediumd_instance() const { + return (*Dictionary())[kStartWmediumdInstance].asBool(); +} + +static constexpr char kMcu[] = "mcu"; +void CuttlefishConfig::MutableInstanceSpecific::set_mcu(const Json::Value& cfg) { + (*Dictionary())[kMcu] = cfg; +} +const Json::Value& CuttlefishConfig::InstanceSpecific::mcu() const { + return (*Dictionary())[kMcu]; +} + +static constexpr char kApBootFlow[] = "ap_boot_flow"; +void CuttlefishConfig::MutableInstanceSpecific::set_ap_boot_flow(APBootFlow flow) { + (*Dictionary())[kApBootFlow] = static_cast(flow); +} +APBootFlow CuttlefishConfig::InstanceSpecific::ap_boot_flow() const { + return static_cast((*Dictionary())[kApBootFlow].asInt()); +} + +static constexpr char kCrosvmUseBalloon[] = "crosvm_use_balloon"; +void CuttlefishConfig::MutableInstanceSpecific::set_crosvm_use_balloon( + const bool use_balloon) { + (*Dictionary())[kCrosvmUseBalloon] = use_balloon; +} +bool CuttlefishConfig::InstanceSpecific::crosvm_use_balloon() const { + return (*Dictionary())[kCrosvmUseBalloon].asBool(); +} + +static constexpr char kCrosvmUseRng[] = "crosvm_use_rng"; +void CuttlefishConfig::MutableInstanceSpecific::set_crosvm_use_rng( + const bool use_rng) { + (*Dictionary())[kCrosvmUseRng] = use_rng; +} +bool CuttlefishConfig::InstanceSpecific::crosvm_use_rng() const { + return (*Dictionary())[kCrosvmUseRng].asBool(); +} + +static constexpr char kCrosvmUsePmem[] = "use_pmem"; +void CuttlefishConfig::MutableInstanceSpecific::set_use_pmem( + const bool use_pmem) { + (*Dictionary())[kCrosvmUsePmem] = use_pmem; +} +bool CuttlefishConfig::InstanceSpecific::use_pmem() const { + return (*Dictionary())[kCrosvmUsePmem].asBool(); +} + +static constexpr char kSockVsockWaitAdbdStart[] = + "sock_vsock_proxy_wait_adbd_start"; +void CuttlefishConfig::MutableInstanceSpecific:: + set_sock_vsock_proxy_wait_adbd_start(const bool wait_adbd_start) { + (*Dictionary())[kSockVsockWaitAdbdStart] = wait_adbd_start; +} +bool CuttlefishConfig::InstanceSpecific::sock_vsock_proxy_wait_adbd_start() + const { + return (*Dictionary())[kSockVsockWaitAdbdStart].asBool(); +} + +std::string CuttlefishConfig::InstanceSpecific::touch_socket_path( + int touch_dev_idx) const { + return PerInstanceInternalUdsPath( + ("touch_" + std::to_string(touch_dev_idx) + ".sock").c_str()); +} + +std::string CuttlefishConfig::InstanceSpecific::rotary_socket_path() const { + return PerInstanceInternalPath("rotary.sock"); +} + +std::string CuttlefishConfig::InstanceSpecific::keyboard_socket_path() const { + return PerInstanceInternalUdsPath("keyboard.sock"); +} + +std::string CuttlefishConfig::InstanceSpecific::switches_socket_path() const { + return PerInstanceInternalUdsPath("switches.sock"); +} + +std::string CuttlefishConfig::InstanceSpecific::frames_socket_path() const { + return PerInstanceInternalUdsPath("frames.sock"); +} + +static constexpr char kWifiMacPrefix[] = "wifi_mac_prefix"; +int CuttlefishConfig::InstanceSpecific::wifi_mac_prefix() const { + return (*Dictionary())[kWifiMacPrefix].asInt(); +} +void CuttlefishConfig::MutableInstanceSpecific::set_wifi_mac_prefix( + int wifi_mac_prefix) { + (*Dictionary())[kWifiMacPrefix] = wifi_mac_prefix; +} + +std::string CuttlefishConfig::InstanceSpecific::factory_reset_protected_path() const { + return PerInstanceInternalPath("factory_reset_protected.img"); +} + +std::string CuttlefishConfig::InstanceSpecific::persistent_bootconfig_path() + const { + return PerInstanceInternalPath("bootconfig"); +} + +std::string CuttlefishConfig::InstanceSpecific::PerInstancePath( + const std::string& file_name) const { + return (instance_dir() + "/") + file_name; +} + +std::string CuttlefishConfig::InstanceSpecific::PerInstanceInternalPath( + const std::string& file_name) const { + if (file_name[0] == '\0') { + // Don't append a / if file_name is empty. + return PerInstancePath(kInternalDirName); + } + auto relative_path = (std::string(kInternalDirName) + "/") + file_name; + return PerInstancePath(relative_path.c_str()); +} + +std::string CuttlefishConfig::InstanceSpecific::PerInstanceUdsPath( + const std::string& file_name) const { + return (instance_uds_dir() + "/") + file_name; +} + +std::string CuttlefishConfig::InstanceSpecific::PerInstanceInternalUdsPath( + const std::string& file_name) const { + if (file_name[0] == '\0') { + // Don't append a / if file_name is empty. + return PerInstanceUdsPath(kInternalDirName); + } + auto relative_path = (std::string(kInternalDirName) + "/") + file_name; + return PerInstanceUdsPath(relative_path.c_str()); +} + +std::string CuttlefishConfig::InstanceSpecific::PerInstanceGrpcSocketPath( + const std::string& socket_name) const { + if (socket_name.size() == 0) { + // Don't append a / if file_name is empty. + return PerInstanceUdsPath(kGrpcSocketDirName); + } + auto relative_path = (std::string(kGrpcSocketDirName) + "/") + socket_name; + return PerInstanceUdsPath(relative_path.c_str()); +} + +std::string CuttlefishConfig::InstanceSpecific::PerInstanceLogPath( + const std::string& file_name) const { + if (file_name.size() == 0) { + // Don't append a / if file_name is empty. + return PerInstancePath(kLogDirName); + } + auto relative_path = (std::string(kLogDirName) + "/") + file_name; + return PerInstancePath(relative_path.c_str()); +} + +std::string CuttlefishConfig::InstanceSpecific::instance_name() const { + return IdToName(id_); +} + +std::string CuttlefishConfig::InstanceSpecific::id() const { return id_; } + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/fetcher_config.cpp b/base/cvd/cuttlefish/host/libs/config/fetcher_config.cpp new file mode 100644 index 0000000000..b20c0276ea --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/fetcher_config.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2019 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. + */ + +#include "host/libs/config/fetcher_config.h" + +#include +#include +#include +#include + +#include "android-base/logging.h" +#include "android-base/strings.h" +#include "gflags/gflags.h" +#include "json/json.h" + +#include "common/libs/utils/files.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +namespace { + +const char* kFlags = "flags"; +const char* kCvdFiles = "cvd_files"; +const char* kCvdFileSource = "source"; +const char* kCvdFileBuildId = "build_id"; +const char* kCvdFileBuildTarget = "build_target"; + +FileSource SourceStringToEnum(std::string source) { + for (auto& c : source) { + c = std::tolower(c); + } + if (source == "default_build") { + return FileSource::DEFAULT_BUILD; + } else if (source == "system_build") { + return FileSource::SYSTEM_BUILD; + } else if (source == "kernel_build") { + return FileSource::KERNEL_BUILD; + } else if (source == "local_file") { + return FileSource::LOCAL_FILE; + } else if (source == "generated") { + return FileSource::GENERATED; + } else if (source == "bootloader_build") { + return FileSource::BOOTLOADER_BUILD; + } else if (source == "android_efi_loader_build") { + return FileSource::ANDROID_EFI_LOADER_BUILD; + } else if (source == "boot_build") { + return FileSource::BOOT_BUILD; + } else if (source == "host_package_build") { + return FileSource::HOST_PACKAGE_BUILD; + } else { + return FileSource::UNKNOWN_PURPOSE; + } +} + +std::string SourceEnumToString(const FileSource& source) { + if (source == FileSource::DEFAULT_BUILD) { + return "default_build"; + } else if (source == FileSource::SYSTEM_BUILD) { + return "system_build"; + } else if (source == FileSource::KERNEL_BUILD) { + return "kernel_build"; + } else if (source == FileSource::LOCAL_FILE) { + return "local_file"; + } else if (source == FileSource::GENERATED) { + return "generated"; + } else if (source == FileSource::BOOTLOADER_BUILD) { + return "bootloader_build"; + } else if (source == FileSource::ANDROID_EFI_LOADER_BUILD) { + return "android_efi_loader_build"; + } else if (source == FileSource::BOOT_BUILD) { + return "boot_build"; + } else if (source == FileSource::HOST_PACKAGE_BUILD) { + return "host_package_build"; + } else { + return "unknown"; + } +} + +} // namespace + +CvdFile::CvdFile() { +} + +CvdFile::CvdFile(const FileSource& source, const std::string& build_id, + const std::string& build_target, const std::string& file_path) + : source(source), build_id(build_id), build_target(build_target), file_path(file_path) { +} + +std::ostream& operator<<(std::ostream& os, const CvdFile& cvd_file) { + os << "CvdFile("; + os << "source = " << SourceEnumToString(cvd_file.source) << ", "; + os << "build_id = " << cvd_file.build_id << ", "; + os << "build_target = " << cvd_file.build_target << ", "; + os << "file_path = " << cvd_file.file_path << ")"; + return os; +} + +FetcherConfig::FetcherConfig() : dictionary_(new Json::Value()) { +} + +FetcherConfig::FetcherConfig(FetcherConfig&&) = default; + +FetcherConfig::~FetcherConfig() { +} + +bool FetcherConfig::SaveToFile(const std::string& file) const { + std::ofstream ofs(file); + if (!ofs.is_open()) { + LOG(ERROR) << "Unable to write to file " << file; + return false; + } + ofs << *dictionary_; + return !ofs.fail(); +} + +bool FetcherConfig::LoadFromFile(const std::string& file) { + auto real_file_path = AbsolutePath(file); + if (real_file_path.empty()) { + LOG(ERROR) << "Could not get real path for file " << file; + return false; + } + Json::CharReaderBuilder builder; + std::ifstream ifs(real_file_path); + std::string errorMessage; + if (!Json::parseFromStream(builder, ifs, dictionary_.get(), &errorMessage)) { + LOG(ERROR) << "Could not read config file " << file << ": " << errorMessage; + return false; + } + + auto base_dir = cpp_dirname(file); + if (base_dir != "." && dictionary_->isMember(kCvdFiles)) { + LOG(INFO) << "Adjusting cvd_file paths to directory: " << base_dir; + for (const auto& member_name : (*dictionary_)[kCvdFiles].getMemberNames()) { + (*dictionary_)[kCvdFiles][base_dir + "/" + member_name] = + (*dictionary_)[kCvdFiles][member_name]; + (*dictionary_)[kCvdFiles].removeMember(member_name); + } + } + + return true; +} + +void FetcherConfig::RecordFlags() { + std::vector all_flags; + GetAllFlags(&all_flags); + Json::Value flags_json(Json::arrayValue); + for (const auto& flag : all_flags) { + Json::Value flag_json; + flag_json["name"] = flag.name; + flag_json["type"] = flag.type; + flag_json["description"] = flag.description; + flag_json["current_value"] = flag.current_value; + flag_json["default_value"] = flag.default_value; + flag_json["filename"] = flag.filename; + flag_json["has_validator_fn"] = flag.has_validator_fn; + flag_json["is_default"] = flag.is_default; + flags_json.append(flag_json); + } + (*dictionary_)[kFlags] = flags_json; +} + +namespace { + +CvdFile JsonToCvdFile(const std::string& file_path, const Json::Value& json) { + CvdFile cvd_file; + cvd_file.file_path = file_path; + if (json.isMember(kCvdFileSource)) { + cvd_file.source = SourceStringToEnum(json[kCvdFileSource].asString()); + } else { + cvd_file.source = FileSource::UNKNOWN_PURPOSE; + } + if (json.isMember(kCvdFileBuildId)) { + cvd_file.build_id = json[kCvdFileBuildId].asString(); + } + if (json.isMember(kCvdFileBuildTarget)) { + cvd_file.build_target = json[kCvdFileBuildTarget].asString(); + } + return cvd_file; +} + +Json::Value CvdFileToJson(const CvdFile& cvd_file) { + Json::Value json; + json[kCvdFileSource] = SourceEnumToString(cvd_file.source); + json[kCvdFileBuildId] = cvd_file.build_id; + json[kCvdFileBuildTarget] = cvd_file.build_target; + return json; +} + +} // namespace + +bool FetcherConfig::add_cvd_file(const CvdFile& file, bool override_entry) { + if (!dictionary_->isMember(kCvdFiles)) { + Json::Value files_json(Json::objectValue); + (*dictionary_)[kCvdFiles] = files_json; + } + if ((*dictionary_)[kCvdFiles].isMember(file.file_path) && !override_entry) { + return false; + } + (*dictionary_)[kCvdFiles][file.file_path] = CvdFileToJson(file); + return true; +} + +std::map FetcherConfig::get_cvd_files() const { + if (!dictionary_->isMember(kCvdFiles)) { + return {}; + } + std::map files; + const auto& json_files = (*dictionary_)[kCvdFiles]; + for (auto it = json_files.begin(); it != json_files.end(); it++) { + files[it.key().asString()] = JsonToCvdFile(it.key().asString(), *it); + } + return files; +} + +std::string FetcherConfig::FindCvdFileWithSuffix(const std::string& suffix) const { + if (!dictionary_->isMember(kCvdFiles)) { + return {}; + } + const auto& json_files = (*dictionary_)[kCvdFiles]; + for (auto it = json_files.begin(); it != json_files.end(); it++) { + const auto& file = it.key().asString(); + if (android::base::EndsWith(file, suffix)) { + return file; + } + } + LOG(DEBUG) << "Could not find file ending in " << suffix; + return ""; +} + +Result FetcherConfig::AddFilesToConfig( + FileSource purpose, const std::string& build_id, + const std::string& build_target, const std::vector& paths, + const std::string& directory_prefix, bool override_entry) { + for (const std::string& path : paths) { + std::string_view local_path(path); + if (!android::base::ConsumePrefix(&local_path, directory_prefix)) { + LOG(ERROR) << "Failed to remove prefix " << directory_prefix << " from " + << local_path; + } + while (android::base::StartsWith(local_path, "/")) { + android::base::ConsumePrefix(&local_path, "/"); + } + // TODO(schuffelen): Do better for local builds here. + CvdFile file(purpose, build_id, build_target, std::string(local_path)); + CF_EXPECT(add_cvd_file(file, override_entry), + "Duplicate file \"" + << file << "\", Existing file: \"" << get_cvd_files()[path] + << "\". Failed to add path \"" << path << "\""); + } + return {}; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/fetcher_config.h b/base/cvd/cuttlefish/host/libs/config/fetcher_config.h new file mode 100644 index 0000000000..bad5b5f1fb --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/fetcher_config.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 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 + +#include +#include +#include +#include + +#include "common/libs/utils/result.h" + +namespace Json { +class Value; +} + +namespace cuttlefish { + +// Order in enum is not guaranteed to be stable, serialized as a string. +enum class FileSource { + UNKNOWN_PURPOSE = 0, + DEFAULT_BUILD, + SYSTEM_BUILD, + KERNEL_BUILD, + LOCAL_FILE, + GENERATED, + BOOTLOADER_BUILD, + ANDROID_EFI_LOADER_BUILD, + BOOT_BUILD, + HOST_PACKAGE_BUILD, +}; + +/* + * Attempts to answer the general question "where did this file come from, and + * what purpose is it serving? + */ +struct CvdFile { + FileSource source; + std::string build_id; + std::string build_target; + std::string file_path; + + CvdFile(); + CvdFile(const FileSource& source, const std::string& build_id, + const std::string& build_target, const std::string& file_path); +}; + +std::ostream& operator<<(std::ostream&, const CvdFile&); + +/** + * A report of state to transfer from fetch_cvd to downstream consumers. + * + * This includes data intended for programmatic access by other tools such as + * assemble_cvd. assemble_cvd can use signals like that multiple build IDs are + * present to judge that it needs to do super image remixing or rebuilding the + * boot image for a new kernel. + * + * The output json also includes data relevant for human debugging, like which + * flags fetch_cvd was invoked with. + */ +class FetcherConfig { + std::unique_ptr dictionary_; + + public: + FetcherConfig(); + FetcherConfig(FetcherConfig&&); + ~FetcherConfig(); + + bool SaveToFile(const std::string& file) const; + bool LoadFromFile(const std::string& file); + + // For debugging only, not intended for programmatic access. + void RecordFlags(); + + bool add_cvd_file(const CvdFile& file, bool override_entry = false); + std::map get_cvd_files() const; + + std::string FindCvdFileWithSuffix(const std::string& suffix) const; + + Result AddFilesToConfig(FileSource purpose, const std::string& build_id, + const std::string& build_target, + const std::vector& paths, + const std::string& directory_prefix, + bool override_entry = false); +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/host_tools_version.cpp b/base/cvd/cuttlefish/host/libs/config/host_tools_version.cpp new file mode 100644 index 0000000000..9ef895d0ba --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/host_tools_version.cpp @@ -0,0 +1,85 @@ +// +// Copyright (C) 2020 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. + +#include "host/libs/config/host_tools_version.h" + +#include +#include +#include +#include + +#include + +#include "common/libs/utils/files.h" +#include "host/libs/config/config_utils.h" + +using std::uint32_t; + +namespace cuttlefish { + +uint32_t FileCrc(const std::string& path) { + uint32_t crc = crc32(0, (unsigned char*) path.c_str(), path.size()); + std::ifstream file_stream(path, std::ifstream::binary); + std::vector data(1024, 0); + while (file_stream) { + file_stream.read(data.data(), data.size()); + crc = crc32(crc, (unsigned char*) data.data(), file_stream.gcount()); + } + return crc; +} + +static std::map DirectoryCrc(const std::string& path) { + auto full_path = DefaultHostArtifactsPath(path); + if (!DirectoryExists(full_path)) { + return {}; + } + auto files_result = DirectoryContents(full_path); + CHECK(files_result.ok()) << files_result.error().FormatForEnv(); + std::vector files = std::move(*files_result); + for (auto it = files.begin(); it != files.end();) { + if (*it == "." || *it == "..") { + it = files.erase(it); + } else { + it++; + } + } + std::vector> calculations; + calculations.reserve(files.size()); + for (auto& file : files) { + file = path + "/" + file; // mutate in place in files vector + calculations.emplace_back( + std::async(FileCrc, DefaultHostArtifactsPath(file))); + } + std::map crcs; + for (int i = 0; i < files.size(); i++) { + crcs[files[i]] = calculations[i].get(); + } + return crcs; +} + +std::map HostToolsCrc() { + auto bin_future = std::async(DirectoryCrc, "bin"); + auto lib_future = std::async(DirectoryCrc, "lib64"); + std::map all_crcs; + for (auto const& [file, crc] : bin_future.get()) { + all_crcs[file] = crc; + } + for (auto const& [file, crc] : lib_future.get()) { + all_crcs[file] = crc; + } + return all_crcs; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/host_tools_version.h b/base/cvd/cuttlefish/host/libs/config/host_tools_version.h new file mode 100644 index 0000000000..c8516926dc --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/host_tools_version.h @@ -0,0 +1,27 @@ +// +// Copyright (C) 2020 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 + +#include +#include +#include + +namespace cuttlefish { + +uint32_t FileCrc(const std::string& path); +std::map HostToolsCrc(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/inject.h b/base/cvd/cuttlefish/host/libs/config/inject.h new file mode 100644 index 0000000000..10664b1e4d --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/inject.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 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 + +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +class LateInjected { + public: + virtual ~LateInjected() = default; + virtual Result LateInject(fruit::Injector<>& injector) = 0; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/instance_nums.cpp b/base/cvd/cuttlefish/host/libs/config/instance_nums.cpp new file mode 100644 index 0000000000..5cf2a7c901 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/instance_nums.cpp @@ -0,0 +1,255 @@ +// +// Copyright (C) 2022 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. + +#include "host/libs/config/instance_nums.h" + +#include +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/flag_parser.h" +#include "host/libs/config/config_utils.h" + +namespace cuttlefish { + +// Failed result: The flag was specified in an invalid way +// Empty optional: The flag was not specified +// Present optional: The flag was specified with a valid value +static Result> ParseBaseInstanceFlag( + std::vector& flags) { + int value = -1; + auto flag = GflagsCompatFlag("base_instance_num", value); + CF_EXPECT(flag.Parse(flags), "Flag parsing error"); + return value > 0 ? value : std::optional(); +} + +// Failed result: The flag was specified in an invalid way +// Empty optional: The flag was not specified +// Present optional: The flag was specified with a valid value +static Result> ParseNumInstancesFlag( + std::vector& flags) { + int value = -1; + auto flag = GflagsCompatFlag("num_instances", value); + CF_EXPECT(flag.Parse(flags), "Flag parsing error"); + return value > 0 ? value : std::optional(); +} + +// Failed result: The flag was specified in an invalid way +// Empty set: The flag was not specified +// Set with members: The flag was specified with a valid value +static Result> ParseInstanceNums( + const std::string& instance_nums_str) { + if (instance_nums_str == "") { + return {}; + } + std::vector instance_nums; + std::vector split_str = + android::base::Split(instance_nums_str, ","); + std::set duplication_check_set; + for (const auto& instance_num_str : split_str) { + std::int32_t instance_num; + CF_EXPECT(android::base::ParseInt(instance_num_str.c_str(), &instance_num), + "Unable to parse \"" << instance_num_str << "\" in " + << "`--instance_nums=\"" << instance_nums_str + << "\"`"); + CF_EXPECT(!Contains(duplication_check_set, instance_num), + instance_num << " is duplicated in -instance_nums flag."); + duplication_check_set.insert(instance_num); + instance_nums.push_back(instance_num); + } + return instance_nums; +} + +// Failed result: The flag was specified in an invalid way +// Empty set: The flag was not specified +// Set with members: The flag was specified with a valid value +static Result> ParseInstanceNumsFlag( + std::vector& flags) { + std::string value; + auto flag = GflagsCompatFlag("instance_nums", value); + CF_EXPECT(flag.Parse(flags), "Flag parsing error"); + if (!value.empty()) { + return CF_EXPECT(ParseInstanceNums(value)); + } else { + return {}; + } +} + +InstanceNumsCalculator& InstanceNumsCalculator::FromFlags( + const std::vector& flags) & { + std::vector flags_copy = flags; + TrySet(base_instance_num_, ParseBaseInstanceFlag(flags_copy)); + TrySet(num_instances_, ParseNumInstancesFlag(flags_copy)); + TrySet(instance_nums_, ParseInstanceNumsFlag(flags_copy)); + return *this; +} + +InstanceNumsCalculator InstanceNumsCalculator::FromFlags( + const std::vector& flags) && { + return FromFlags(flags); +} + +// Failed result: The flag was specified in an invalid way +// Empty optional: The flag was not specified +// Present optional: The flag was specified with a valid value +static Result> GflagsBaseInstanceFlag() { + gflags::CommandLineFlagInfo info; + if (!gflags::GetCommandLineFlagInfo("base_instance_num", &info)) { + return {}; + } + if (info.is_default) { + return {}; + } + CF_EXPECT(info.type == "int32"); + return std::atoi(info.current_value.c_str()); +} + +// Failed result: The flag was specified in an invalid way +// Empty optional: The flag was not specified +// Present optional: The flag was specified with a valid value +static Result> GflagsNumInstancesFlag() { + gflags::CommandLineFlagInfo info; + if (!gflags::GetCommandLineFlagInfo("num_instances", &info)) { + return {}; + } + if (info.is_default) { + return {}; + } + CF_EXPECT(info.type == "int32"); + return std::atoi(info.current_value.c_str()); +} + +// Failed result: The flag was specified in an invalid way +// Empty set: The flag was not specified +// Set with members: The flag was specified with a valid value +static Result> GflagsInstanceNumsFlag() { + gflags::CommandLineFlagInfo info; + if (!gflags::GetCommandLineFlagInfo("instance_nums", &info)) { + return {}; + } + if (info.is_default) { + return {}; + } + CF_EXPECT(info.type == "string"); + auto contents = info.current_value; + return CF_EXPECT(ParseInstanceNums(contents)); +} + +InstanceNumsCalculator& InstanceNumsCalculator::FromGlobalGflags() & { + TrySet(base_instance_num_, GflagsBaseInstanceFlag()); + TrySet(num_instances_, GflagsNumInstancesFlag()); + TrySet(instance_nums_, GflagsInstanceNumsFlag()); + return *this; +} + +InstanceNumsCalculator InstanceNumsCalculator::FromGlobalGflags() && { + return FromGlobalGflags(); +} + +InstanceNumsCalculator& InstanceNumsCalculator::BaseInstanceNum( + std::int32_t num) & { + base_instance_num_ = num; + return *this; +} +InstanceNumsCalculator InstanceNumsCalculator::BaseInstanceNum( + std::int32_t num) && { + return BaseInstanceNum(num); +} + +InstanceNumsCalculator& InstanceNumsCalculator::NumInstances( + std::int32_t num) & { + num_instances_ = num; + return *this; +} +InstanceNumsCalculator InstanceNumsCalculator::NumInstances( + std::int32_t num) && { + return NumInstances(num); +} + +InstanceNumsCalculator& InstanceNumsCalculator::InstanceNums( + const std::string& nums) & { + TrySet(instance_nums_, ParseInstanceNums(nums)); + return *this; +} +InstanceNumsCalculator InstanceNumsCalculator::InstanceNums( + const std::string& nums) && { + return InstanceNums(nums); +} + +InstanceNumsCalculator& InstanceNumsCalculator::InstanceNums( + std::vector set) & { + instance_nums_ = std::move(set); + return *this; +} +InstanceNumsCalculator InstanceNumsCalculator::InstanceNums( + std::vector set) && { + return InstanceNums(std::move(set)); +} + +template +void InstanceNumsCalculator::TrySet(T& field, Result result) { + if (result.ok()) { + field = std::move(*result); + } else { + // TODO(schuffelen): Combine both errors into one + setter_result_.error() = result.error(); + } +} + +Result> InstanceNumsCalculator::CalculateFromFlags() { + CF_EXPECT(Result(setter_result_)); + std::optional> instance_nums_opt; + if (!instance_nums_.empty()) { + instance_nums_opt = instance_nums_; + } + // exactly one of these two should be given + CF_EXPECT(!instance_nums_opt || !base_instance_num_, + "At least one of --instance_nums or --base_instance_num" + << "should be given to call CalculateFromFlags()"); + CF_EXPECT(instance_nums_opt || base_instance_num_, + "InstanceNums and BaseInstanceNum are mutually exclusive"); + + if (instance_nums_opt) { + if (num_instances_) { + CF_EXPECT(instance_nums_.size() == *num_instances_); + } + CF_EXPECT(instance_nums_.size() > 0, "no instance nums"); + return instance_nums_; + } + + std::vector instance_nums; + for (int i = 0; i < num_instances_.value_or(1); i++) { + instance_nums.push_back(i + *base_instance_num_); + } + return instance_nums; +} + +Result> InstanceNumsCalculator::Calculate() { + CF_EXPECT(Result(setter_result_)); + + if (!instance_nums_.empty() || base_instance_num_) { + return CalculateFromFlags(); + } + + std::vector instance_nums; + for (int i = 0; i < num_instances_.value_or(1); i++) { + instance_nums.push_back(i + GetInstance()); + } + CF_EXPECT(instance_nums.size() > 0, "no instance nums"); + return instance_nums; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/instance_nums.h b/base/cvd/cuttlefish/host/libs/config/instance_nums.h new file mode 100644 index 0000000000..548292d35f --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/instance_nums.h @@ -0,0 +1,77 @@ +// +// Copyright (C) 2022 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 + +#include +#include +#include +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +class InstanceNumsCalculator { + public: + InstanceNumsCalculator& FromFlags(const std::vector&) &; + InstanceNumsCalculator FromFlags(const std::vector&) &&; + + InstanceNumsCalculator& FromGlobalGflags() &; + InstanceNumsCalculator FromGlobalGflags() &&; + + InstanceNumsCalculator& BaseInstanceNum(std::int32_t) &; + InstanceNumsCalculator BaseInstanceNum(std::int32_t) &&; + + InstanceNumsCalculator& NumInstances(std::int32_t) &; + InstanceNumsCalculator NumInstances(std::int32_t) &&; + + InstanceNumsCalculator& InstanceNums(const std::string&) &; + InstanceNumsCalculator InstanceNums(const std::string&) &&; + + // if any element is duplicated, only the first one of them is taken. + // E.g. InstanceNums({1, 2, 3, 2}) == InstanceNums({1, 2, 3}) + // That is how the code was implemented in Android 14 + InstanceNumsCalculator& InstanceNums(std::vector) &; + InstanceNumsCalculator InstanceNums(std::vector) &&; + + /** + * Finds set of ids using the flags only. + * + * Especially, this calculates the base from --instance_nums and + * --base_instance_num only + * + * Processes such as cvd clients may see different user accounts, + * CUTTLEFISH_INSTANCE environment variable, etc, than the launcher + * effectively sees. This util method is still helpful for that. + */ + Result> CalculateFromFlags(); + + // Calculates the base from the --instance_nums, --base_instance_num, + // CUTTLEFISH_INSTANCE, suffix of the user account, and the default value. + // Then, figures out the set if ids. + Result> Calculate(); + + private: + template + void TrySet(T& field, Result result); + + Result setter_result_; + std::optional base_instance_num_; + std::optional num_instances_; + std::vector instance_nums_; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/known_paths.h b/base/cvd/cuttlefish/host/libs/config/known_paths.h new file mode 100644 index 0000000000..4952227adc --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/known_paths.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 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 + +#include + +namespace cuttlefish { + +std::string AdbConnectorBinary(); +std::string CasimirControlServerBinary(); +std::string ConfigServerBinary(); +std::string ConsoleForwarderBinary(); +std::string ControlEnvProxyServerBinary(); +std::string EchoServerBinary(); +std::string GnssGrpcProxyBinary(); +std::string KernelLogMonitorBinary(); +std::string LogcatReceiverBinary(); +std::string MetricsBinary(); +std::string ModemSimulatorBinary(); +std::string NetsimdBinary(); +std::string OpenwrtControlServerBinary(); +std::string PicaBinary(); +std::string ProcessRestarterBinary(); +std::string RootCanalBinary(); +std::string CasimirBinary(); +std::string ScreenRecordingServerBinary(); +std::string SecureEnvBinary(); +std::string SocketVsockProxyBinary(); +std::string StopCvdBinary(); +std::string TcpConnectorBinary(); +std::string TombstoneReceiverBinary(); +std::string WebRtcBinary(); +std::string WebRtcSigServerBinary(); +std::string WebRtcSigServerProxyBinary(); +std::string WmediumdBinary(); +std::string WmediumdGenConfigBinary(); +std::string AutomotiveProxyBinary(); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/logging.cpp b/base/cvd/cuttlefish/host/libs/config/logging.cpp new file mode 100644 index 0000000000..cb2af81a47 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/logging.cpp @@ -0,0 +1,47 @@ +// +// Copyright (C) 2020 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. + +#include "logging.h" + +#include + +#include "host/libs/config/cuttlefish_config.h" + +using android::base::SetLogger; + +namespace cuttlefish { + +void DefaultSubprocessLogging(char* argv[], MetadataLevel stderr_level) { + ::android::base::InitLogging(argv, android::base::StderrLogger); + + auto config = CuttlefishConfig::Get(); + + CHECK(config) << "Could not open cuttlefish config"; + + auto instance = config->ForDefaultInstance(); + + std::string prefix = ""; + if (config->Instances().size() > 1) { + prefix = instance.instance_name() + ": "; + } + + if (instance.run_as_daemon()) { + SetLogger(LogToFiles({instance.launcher_log_path()})); + } else { + SetLogger(LogToStderrAndFiles({instance.launcher_log_path()}, prefix, stderr_level)); + } +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/config/logging.h b/base/cvd/cuttlefish/host/libs/config/logging.h new file mode 100644 index 0000000000..84725392c8 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/config/logging.h @@ -0,0 +1,25 @@ +// +// Copyright (C) 2020 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 + +#include "common/libs/utils/tee_logging.h" + +namespace cuttlefish { + +void DefaultSubprocessLogging(char* argv[], + MetadataLevel stderr_level = MetadataLevel::ONLY_MESSAGE); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/vm_manager/crosvm_manager.h b/base/cvd/cuttlefish/host/libs/vm_manager/crosvm_manager.h new file mode 100644 index 0000000000..698ec514fe --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/vm_manager/crosvm_manager.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace cuttlefish { +namespace vm_manager { + +struct CrosvmManager { + static std::string name() { + return "crosvm"; + } +}; + +} +} diff --git a/base/cvd/cuttlefish/host/libs/vm_manager/gem5_manager.h b/base/cvd/cuttlefish/host/libs/vm_manager/gem5_manager.h new file mode 100644 index 0000000000..19e0145c7f --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/vm_manager/gem5_manager.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace cuttlefish { +namespace vm_manager { + +struct Gem5Manager { + static std::string name() { + return "gem5"; + } +}; + +} +} diff --git a/base/cvd/cuttlefish/host/libs/vm_manager/qemu_manager.h b/base/cvd/cuttlefish/host/libs/vm_manager/qemu_manager.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/base/cvd/cuttlefish/host/libs/web/android_build_api.cpp b/base/cvd/cuttlefish/host/libs/web/android_build_api.cpp new file mode 100644 index 0000000000..bf4444456a --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/android_build_api.cpp @@ -0,0 +1,469 @@ +// +// Copyright (C) 2019 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. + +#include "host/libs/web/android_build_api.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/utils/contains.h" +#include "common/libs/utils/environment.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/result.h" +#include "host/libs/web/android_build_string.h" +#include "host/libs/web/credential_source.h" + +namespace cuttlefish { +namespace { + +bool StatusIsTerminal(const std::string& status) { + const static std::set terminal_statuses = { + "abandoned", "complete", "error", "ABANDONED", "COMPLETE", "ERROR", + }; + return terminal_statuses.count(status) > 0; +} + +std::string BuildNameRegexp( + const std::vector& artifact_filenames) { + // surrounding with \Q and \E treats the text literally to avoid + // characters being treated as regex + auto it = artifact_filenames.begin(); + std::string name_regex = "^\\Q" + *it + "\\E$"; + std::string result = name_regex; + ++it; + for (const auto end = artifact_filenames.end(); it != end; ++it) { + name_regex = "^\\Q" + *it + "\\E$"; + result += "|" + name_regex; + } + return result; +} + +struct CloseDir { + void operator()(DIR* dir) { closedir(dir); } +}; + +} // namespace + +DeviceBuild::DeviceBuild(std::string id, std::string target, + std::optional filepath) + : id(std::move(id)), + target(std::move(target)), + filepath(std::move(filepath)) {} + +std::ostream& operator<<(std::ostream& out, const DeviceBuild& build) { + return out << "(id=\"" << build.id << "\", target=\"" << build.target + << "\", filepath=\"" << build.filepath.value_or("") << "\")"; +} + +DirectoryBuild::DirectoryBuild(std::vector paths, + std::string target, + std::optional filepath) + : paths(std::move(paths)), + target(std::move(target)), + id("eng"), + filepath(std::move(filepath)) { + product = StringFromEnv("TARGET_PRODUCT", ""); +} + +std::ostream& operator<<(std::ostream& out, const DirectoryBuild& build) { + auto paths = android::base::Join(build.paths, ":"); + return out << "(paths=\"" << paths << "\", target=\"" << build.target + << "\", filepath=\"" << build.filepath.value_or("") << "\")"; +} + +std::ostream& operator<<(std::ostream& out, const Build& build) { + std::visit([&out](auto&& arg) { out << arg; }, build); + return out; +} + +BuildApi::BuildApi() + : BuildApi(HttpClient::CurlClient(), nullptr, kAndroidBuildServiceUrl) {} + +BuildApi::BuildApi(std::unique_ptr http_client, + std::unique_ptr credential_source, + std::string api_base_url) + : BuildApi(std::move(http_client), nullptr, std::move(credential_source), + "", std::chrono::seconds(0), std::move(api_base_url)) {} + +BuildApi::BuildApi(std::unique_ptr http_client, + std::unique_ptr inner_http_client, + std::unique_ptr credential_source, + std::string api_key, const std::chrono::seconds retry_period, + std::string api_base_url) + : http_client(std::move(http_client)), + inner_http_client(std::move(inner_http_client)), + credential_source(std::move(credential_source)), + api_key_(std::move(api_key)), + retry_period_(retry_period), + api_base_url_(std::move(api_base_url)) {} + +Result BuildApi::ArtifactToCallback(const DeviceBuild& build, + const std::string& artifact, + HttpClient::DataCallback callback) { + std::string download_url_endpoint = + api_base_url_ + "/builds/" + http_client->UrlEscape(build.id) + "/" + + http_client->UrlEscape(build.target) + "/attempts/latest/artifacts/" + + http_client->UrlEscape(artifact) + "/url"; + if (!api_key_.empty()) { + download_url_endpoint += "?key=" + http_client->UrlEscape(api_key_); + } + auto response = CF_EXPECT( + http_client->DownloadToJson(download_url_endpoint, CF_EXPECT(Headers()))); + const auto& json = response.data; + CF_EXPECT(response.HttpSuccess() || response.HttpRedirect(), + "Error fetching the url of \"" << artifact << "\" for \"" << build + << "\". The server response was \"" + << json << "\", and code was " + << response.http_code); + CF_EXPECT(!json.isMember("error"), + "Response had \"error\" but had http success status. " + << "Received \"" << json << "\""); + CF_EXPECT(json.isMember("signedUrl"), + "URL endpoint did not have json path: " << json); + std::string url = json["signedUrl"].asString(); + auto callback_response = + CF_EXPECT(http_client->DownloadToCallback(callback, url)); + CF_EXPECT(IsHttpSuccess(callback_response.http_code)); + return {}; +} + +Result BuildApi::GetBuild(const DeviceBuildString& build_string, + const std::string& fallback_target) { + auto proposed_build = DeviceBuild( + build_string.branch_or_id, build_string.target.value_or(fallback_target), + build_string.filepath); + auto latest_build_id = + CF_EXPECT(LatestBuildId(build_string.branch_or_id, + build_string.target.value_or(fallback_target))); + if (latest_build_id) { + proposed_build.id = *latest_build_id; + LOG(INFO) << "Latest build id for branch" << build_string.branch_or_id + << " and target " << proposed_build.target << " is " + << proposed_build.id; + } + + std::string status = CF_EXPECT(BuildStatus(proposed_build)); + CF_EXPECT(status != "", + proposed_build << " is not a valid branch or build id."); + LOG(INFO) << "Status for build " << proposed_build << " is " << status; + while (retry_period_ != std::chrono::seconds::zero() && + !StatusIsTerminal(status)) { + LOG(INFO) << "Status is \"" << status << "\". Waiting for " + << retry_period_.count() << " seconds."; + std::this_thread::sleep_for(retry_period_); + status = CF_EXPECT(BuildStatus(proposed_build)); + } + LOG(INFO) << "Status for build " << proposed_build << " is " << status; + proposed_build.product = CF_EXPECT(ProductName(proposed_build)); + return proposed_build; +} + +Result BuildApi::GetBuild(const DirectoryBuildString& build_string, + const std::string&) { + return DirectoryBuild(build_string.paths, build_string.target, + build_string.filepath); +} + +Result BuildApi::GetBuild(const BuildString& build_string, + const std::string& fallback_target) { + auto result = + std::visit([this, &fallback_target]( + auto&& arg) { return GetBuild(arg, fallback_target); }, + build_string); + return CF_EXPECT(std::move(result)); +} + +Result BuildApi::DownloadFile(const Build& build, + const std::string& target_directory, + const std::string& artifact_name) { + std::unordered_set artifacts = + CF_EXPECT(Artifacts(build, {artifact_name})); + CF_EXPECT(Contains(artifacts, artifact_name), + "Target " << build << " did not contain " << artifact_name); + return DownloadTargetFile(build, target_directory, artifact_name); +} + +Result BuildApi::DownloadFileWithBackup( + const Build& build, const std::string& target_directory, + const std::string& artifact_name, const std::string& backup_artifact_name) { + std::unordered_set artifacts = + CF_EXPECT(Artifacts(build, {artifact_name, backup_artifact_name})); + std::string selected_artifact = artifact_name; + if (!Contains(artifacts, artifact_name)) { + selected_artifact = backup_artifact_name; + } + return DownloadTargetFile(build, target_directory, selected_artifact); +} + +Result> BuildApi::Headers() { + std::vector headers; + if (credential_source) { + headers.push_back("Authorization: Bearer " + + CF_EXPECT(credential_source->Credential())); + } + return headers; +} + +Result> BuildApi::LatestBuildId( + const std::string& branch, const std::string& target) { + std::string url = + api_base_url_ + "/builds?branch=" + http_client->UrlEscape(branch) + + "&buildAttemptStatus=complete" + + "&buildType=submitted&maxResults=1&successful=true&target=" + + http_client->UrlEscape(target); + if (!api_key_.empty()) { + url += "&key=" + http_client->UrlEscape(api_key_); + } + auto response = + CF_EXPECT(http_client->DownloadToJson(url, CF_EXPECT(Headers()))); + const auto& json = response.data; + CF_EXPECT(response.HttpSuccess(), "Error fetching the latest build of \"" + << target << "\" on \"" << branch + << "\". The server response was \"" + << json << "\", and code was " + << response.http_code); + CF_EXPECT(!json.isMember("error"), + "Response had \"error\" but had http success status. Received \"" + << json << "\""); + + if (!json.isMember("builds")) { + return std::nullopt; + } + CF_EXPECT(json["builds"].size() == 1, + "Expected to receive 1 build for \"" + << target << "\" on \"" << branch << "\", but received " + << json["builds"].size() << ". Full response:\n" + << json); + CF_EXPECT(json["builds"][0].isMember("buildId"), + "\"buildId\" member missing from response. Full response:\n" + << json); + return json["builds"][0]["buildId"].asString(); +} + +Result BuildApi::BuildStatus(const DeviceBuild& build) { + std::string url = api_base_url_ + "/builds/" + + http_client->UrlEscape(build.id) + "/" + + http_client->UrlEscape(build.target); + if (!api_key_.empty()) { + url += "?key=" + http_client->UrlEscape(api_key_); + } + auto response = + CF_EXPECT(http_client->DownloadToJson(url, CF_EXPECT(Headers()))); + const auto& json = response.data; + CF_EXPECT(response.HttpSuccess(), + "Error fetching the status of \"" + << build << "\". The server response was \"" << json + << "\", and code was " << response.http_code); + CF_EXPECT(!json.isMember("error"), + "Response had \"error\" but had http success status. Received \"" + << json << "\""); + + return json["buildAttemptStatus"].asString(); +} + +Result BuildApi::ProductName(const DeviceBuild& build) { + std::string url = api_base_url_ + "/builds/" + + http_client->UrlEscape(build.id) + "/" + + http_client->UrlEscape(build.target); + if (!api_key_.empty()) { + url += "?key=" + http_client->UrlEscape(api_key_); + } + auto response = + CF_EXPECT(http_client->DownloadToJson(url, CF_EXPECT(Headers()))); + const auto& json = response.data; + CF_EXPECT(response.HttpSuccess(), + "Error fetching the product name of \"" + << build << "\". The server response was \"" << json + << "\", and code was " << response.http_code); + CF_EXPECT(!json.isMember("error"), + "Response had \"error\" but had http success status. Received \"" + << json << "\""); + + CF_EXPECT(json.isMember("target"), "Build was missing target field."); + return json["target"]["product"].asString(); +} + +Result> BuildApi::Artifacts( + const DeviceBuild& build, + const std::vector& artifact_filenames) { + std::string page_token = ""; + std::unordered_set artifacts; + do { + std::string url = api_base_url_ + "/builds/" + + http_client->UrlEscape(build.id) + "/" + + http_client->UrlEscape(build.target) + + "/attempts/latest/artifacts?maxResults=100"; + if (!artifact_filenames.empty()) { + url += "&nameRegexp=" + + http_client->UrlEscape(BuildNameRegexp(artifact_filenames)); + } + if (page_token != "") { + url += "&pageToken=" + http_client->UrlEscape(page_token); + } + if (!api_key_.empty()) { + url += "&key=" + http_client->UrlEscape(api_key_); + } + auto response = + CF_EXPECT(http_client->DownloadToJson(url, CF_EXPECT(Headers()))); + const auto& json = response.data; + CF_EXPECT(response.HttpSuccess(), + "Error fetching the artifacts of \"" + << build << "\". The server response was \"" << json + << "\", and code was " << response.http_code); + CF_EXPECT(!json.isMember("error"), + "Response had \"error\" but had http success status. Received \"" + << json << "\""); + if (json.isMember("nextPageToken")) { + page_token = json["nextPageToken"].asString(); + } else { + page_token = ""; + } + for (const auto& artifact_json : json["artifacts"]) { + artifacts.emplace(artifact_json["name"].asString()); + } + } while (page_token != ""); + return artifacts; +} + +Result> BuildApi::Artifacts( + const DirectoryBuild& build, const std::vector&) { + std::unordered_set artifacts; + for (const auto& path : build.paths) { + auto dir = std::unique_ptr(opendir(path.c_str())); + CF_EXPECT(dir != nullptr, "Could not read files from \"" << path << "\""); + for (auto entity = readdir(dir.get()); entity != nullptr; + entity = readdir(dir.get())) { + artifacts.emplace(std::string(entity->d_name)); + } + } + return artifacts; +} + +Result> BuildApi::Artifacts( + const Build& build, const std::vector& artifact_filenames) { + auto res = + std::visit([this, &artifact_filenames]( + auto&& arg) { return Artifacts(arg, artifact_filenames); }, + build); + return CF_EXPECT(std::move(res)); +} + +Result BuildApi::ArtifactToFile(const DeviceBuild& build, + const std::string& artifact, + const std::string& path) { + std::string download_url_endpoint = + api_base_url_ + "/builds/" + http_client->UrlEscape(build.id) + "/" + + http_client->UrlEscape(build.target) + "/attempts/latest/artifacts/" + + http_client->UrlEscape(artifact) + "/url"; + if (!api_key_.empty()) { + download_url_endpoint += "?key=" + http_client->UrlEscape(api_key_); + } + auto response = CF_EXPECT( + http_client->DownloadToJson(download_url_endpoint, CF_EXPECT(Headers()))); + const auto& json = response.data; + CF_EXPECT(response.HttpSuccess() || response.HttpRedirect(), + "Error fetching the url of \"" << artifact << "\" for \"" << build + << "\". The server response was \"" + << json << "\", and code was " + << response.http_code); + CF_EXPECT(!json.isMember("error"), + "Response had \"error\" but had http success status. " + << "Received \"" << json << "\""); + CF_EXPECT(json.isMember("signedUrl"), + "URL endpoint did not have json path: " << json); + std::string url = json["signedUrl"].asString(); + CF_EXPECT(CF_EXPECT(http_client->DownloadToFile(url, path)).HttpSuccess()); + return {}; +} + +Result BuildApi::ArtifactToFile(const DirectoryBuild& build, + const std::string& artifact, + const std::string& destination) { + for (const auto& path : build.paths) { + auto source = path + "/" + artifact; + if (!FileExists(source)) { + continue; + } + unlink(destination.c_str()); + CF_EXPECT(symlink(source.c_str(), destination.c_str()) == 0, + "Could not create symlink from " << source << " to " + << destination << ": " + << strerror(errno)); + return {}; + } + return CF_ERR("Could not find artifact \"" << artifact << "\" in build \"" + << build << "\""); +} + +Result BuildApi::ArtifactToFile(const Build& build, + const std::string& artifact, + const std::string& path) { + auto res = std::visit( + [this, &artifact, &path](auto&& arg) { + return ArtifactToFile(arg, artifact, path); + }, + build); + CF_EXPECT(std::move(res)); + return {}; +} + +Result BuildApi::DownloadTargetFile( + const Build& build, const std::string& target_directory, + const std::string& artifact_name) { + std::string target_filepath = target_directory + "/" + artifact_name; + CF_EXPECT(ArtifactToFile(build, artifact_name, target_filepath), + "Unable to download " << build << ":" << artifact_name << " to " + << target_filepath); + return {target_filepath}; +} + +/** Returns the name of one of the artifact target zip files. + * + * For example, for a target "aosp_cf_x86_phone-userdebug" at a build "5824130", + * the image zip file would be "aosp_cf_x86_phone-img-5824130.zip" + */ +std::string GetBuildZipName(const Build& build, const std::string& name) { + std::string product = + std::visit([](auto&& arg) { return arg.product; }, build); + auto id = std::visit([](auto&& arg) { return arg.id; }, build); + return product + "-" + name + "-" + id + ".zip"; +} + +std::tuple GetBuildIdAndTarget(const Build& build) { + auto id = std::visit([](auto&& arg) { return arg.id; }, build); + auto target = std::visit([](auto&& arg) { return arg.target; }, build); + return {id, target}; +} + +std::optional GetFilepath(const Build& build) { + return std::visit([](auto&& arg) { return arg.filepath; }, build); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/android_build_api.h b/base/cvd/cuttlefish/host/libs/web/android_build_api.h new file mode 100644 index 0000000000..a968493128 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/android_build_api.h @@ -0,0 +1,155 @@ +// +// Copyright (C) 2019 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/libs/utils/result.h" +#include "host/libs/web/android_build_string.h" +#include "host/libs/web/credential_source.h" +#include "host/libs/web/http_client/http_client.h" + +namespace cuttlefish { + +inline constexpr char kAndroidBuildServiceUrl[] = + "https://www.googleapis.com/android/internal/build/v3"; + +struct DeviceBuild { + DeviceBuild(std::string id, std::string target, + std::optional filepath); + + std::string id; + std::string target; + std::string product; + std::optional filepath; +}; + +std::ostream& operator<<(std::ostream&, const DeviceBuild&); + +struct DirectoryBuild { + // TODO(schuffelen): Support local builds other than "eng" + DirectoryBuild(std::vector paths, std::string target, + std::optional filepath); + + std::vector paths; + std::string target; + std::string id; + std::string product; + std::optional filepath; +}; + +std::ostream& operator<<(std::ostream&, const DirectoryBuild&); + +using Build = std::variant; + +std::ostream& operator<<(std::ostream&, const Build&); + +class BuildApi { + public: + BuildApi(); + BuildApi(BuildApi&&) = default; + BuildApi(std::unique_ptr http_client, + std::unique_ptr credential_source, + std::string api_base_url); + BuildApi(std::unique_ptr http_client, + std::unique_ptr inner_http_client, + std::unique_ptr credential_source, + std::string api_key, const std::chrono::seconds retry_period, + std::string api_base_url); + ~BuildApi() = default; + + // download the artifact from the build and apply the callback + Result ArtifactToCallback(const DeviceBuild& build, + const std::string& artifact, + HttpClient::DataCallback callback); + + Result GetBuild(const DeviceBuildString& build_string, + const std::string& fallback_target); + Result GetBuild(const DirectoryBuildString& build_string, + const std::string& fallback_target); + Result GetBuild(const BuildString& build_string, + const std::string& fallback_target); + + Result DownloadFile(const Build& build, + const std::string& target_directory, + const std::string& artifact_name); + + Result DownloadFileWithBackup( + const Build& build, const std::string& target_directory, + const std::string& artifact_name, + const std::string& backup_artifact_name); + + private: + Result> Headers(); + + Result> LatestBuildId(const std::string& branch, + const std::string& target); + + Result BuildStatus(const DeviceBuild&); + + Result ProductName(const DeviceBuild&); + + Result> Artifacts( + const DeviceBuild& build, + const std::vector& artifact_filenames); + + Result> Artifacts( + const DirectoryBuild& build, + const std::vector& artifact_filenames); + + Result> Artifacts( + const Build& build, const std::vector& artifact_filenames); + + Result ArtifactToFile(const DeviceBuild& build, + const std::string& artifact, + const std::string& path); + + Result ArtifactToFile(const DirectoryBuild& build, + const std::string& artifact, + const std::string& path); + + Result ArtifactToFile(const Build& build, const std::string& artifact, + const std::string& path); + + Result DownloadTargetFile(const Build& build, + const std::string& target_directory, + const std::string& artifact_name); + + std::unique_ptr http_client; + std::unique_ptr inner_http_client; + std::unique_ptr credential_source; + std::string api_key_; + std::chrono::seconds retry_period_; + std::string api_base_url_; +}; + +std::string GetBuildZipName(const Build& build, const std::string& name); + +std::tuple GetBuildIdAndTarget(const Build& build); + +std::optional GetFilepath(const Build& build); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/android_build_string.cpp b/base/cvd/cuttlefish/host/libs/web/android_build_string.cpp new file mode 100644 index 0000000000..33151f283b --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/android_build_string.cpp @@ -0,0 +1,211 @@ +// +// Copyright (C) 2023 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. + +#include "host/libs/web/android_build_string.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +namespace { + +Result>> ParseFilepath( + const std::string& build_string) { + std::string remaining_build_string = build_string; + std::optional filepath; + std::size_t open_bracket = build_string.find('{'); + std::size_t close_bracket = build_string.find('}'); + + bool has_open = open_bracket != std::string::npos; + bool has_close = close_bracket != std::string::npos; + CF_EXPECTF( + has_open == has_close, + "Open or close curly bracket exists without its complement in \"{}\"", + build_string); + if (has_open && has_close) { + std::string remaining_substring = build_string.substr(0, open_bracket); + CF_EXPECTF( + !remaining_substring.empty(), + "The build string excluding filepath cannot be empty. Input: {}", + build_string); + std::size_t filepath_start = open_bracket + 1; + std::string filepath_substring = + build_string.substr(filepath_start, close_bracket - filepath_start); + CF_EXPECTF( + !filepath_substring.empty(), + "The filepath between positions {},{} cannot be empty. Input: {}", + filepath_start, close_bracket, build_string); + remaining_build_string = remaining_substring; + filepath = filepath_substring; + } + return {{remaining_build_string, filepath}}; +} + +Result ParseDeviceBuildString( + const std::string& build_string, + const std::optional& filepath) { + std::size_t slash_pos = build_string.find('/'); + std::string branch_or_id = build_string.substr(0, slash_pos); + auto result = DeviceBuildString{ + .branch_or_id = build_string.substr(0, slash_pos), .filepath = filepath}; + if (slash_pos != std::string::npos) { + std::size_t next_slash_pos = build_string.find('/', slash_pos + 1); + CF_EXPECTF(next_slash_pos == std::string::npos, + "Build string argument cannot have more than one '/'. Found at " + "positions {},{}.", + slash_pos, next_slash_pos); + result.target = build_string.substr(slash_pos + 1); + } + return result; +} + +Result ParseDirectoryBuildString( + const std::string& build_string, + const std::optional& filepath) { + auto result = DirectoryBuildString{.filepath = filepath}; + std::vector split = android::base::Split(build_string, ":"); + result.target = split.back(); + split.pop_back(); + result.paths = std::move(split); + return result; +} + +} // namespace + +std::ostream& operator<<(std::ostream& out, + const DeviceBuildString& build_string) { + fmt::print(out, "(branch_or_id=\"{}\", target=\"{}\", filepath=\"{}\")", + build_string.branch_or_id, build_string.target.value_or(""), + build_string.filepath.value_or("")); + return out; +} + +bool operator==(const DeviceBuildString& lhs, const DeviceBuildString& rhs) { + return lhs.branch_or_id == rhs.branch_or_id && lhs.target == rhs.target && + lhs.filepath == rhs.filepath; +} + +bool operator!=(const DeviceBuildString& lhs, const DeviceBuildString& rhs) { + return !(lhs == rhs); +} + +std::ostream& operator<<(std::ostream& out, + const DirectoryBuildString& build_string) { + fmt::print(out, "(paths=\"{}\", target=\"{}\", filepath=\"{}\")", + fmt::join(build_string.paths, ":"), build_string.target, + build_string.filepath.value_or("")); + return out; +} + +bool operator==(const DirectoryBuildString& lhs, + const DirectoryBuildString& rhs) { + return lhs.paths == rhs.paths && lhs.target == rhs.target && + lhs.filepath == rhs.filepath; +} + +bool operator!=(const DirectoryBuildString& lhs, + const DirectoryBuildString& rhs) { + return !(lhs == rhs); +} + +std::ostream& operator<<(std::ostream& out, const BuildString& build_string) { + std::visit([&out](auto&& arg) { out << arg; }, build_string); + return out; +} + +std::ostream& operator<<(std::ostream& out, + const std::optional& build_string) { + if (build_string) { + out << "has_value(" << *build_string << ")"; + } else { + out << "no_value()"; + } + return out; +} + +std::optional GetFilepath(const BuildString& build_string) { + return std::visit([](auto&& arg) { return arg.filepath; }, build_string); +} + +void SetFilepath(BuildString& build_string, const std::string& value) { + std::visit([&value](auto&& arg) { arg.filepath = value; }, build_string); +} + +Result ParseBuildString(const std::string& build_string) { + CF_EXPECT(!build_string.empty(), "The given build string cannot be empty"); + auto [remaining_build_string, filepath] = + CF_EXPECT(ParseFilepath(build_string)); + if (remaining_build_string.find(':') != std::string::npos) { + return CF_EXPECT( + ParseDirectoryBuildString(remaining_build_string, filepath)); + } else { + return CF_EXPECT(ParseDeviceBuildString(remaining_build_string, filepath)); + } +} + +Flag GflagsCompatFlag(const std::string& name, + std::optional& value) { + return GflagsCompatFlag(name) + .Getter([&value]() { + std::stringstream result; + result << value; + return result.str(); + }) + .Setter([&value](const FlagMatch& match) -> Result { + value = std::nullopt; + if (!match.value.empty()) { + value = CF_EXPECT(ParseBuildString(match.value)); + } + return {}; + }); +} + +Flag GflagsCompatFlag(const std::string& name, + std::vector>& value) { + return GflagsCompatFlag(name) + .Getter([&value]() { return android::base::Join(value, ','); }) + .Setter([&value](const FlagMatch& match) -> Result { + if (match.value.empty()) { + value.clear(); + return {}; + } + std::vector str_vals = + android::base::Split(match.value, ","); + value.clear(); + for (const auto& str_val : str_vals) { + if (str_val.empty()) { + value.emplace_back(std::nullopt); + } else { + value.emplace_back(CF_EXPECT(ParseBuildString(str_val))); + } + } + return {}; + }); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/android_build_string.h b/base/cvd/cuttlefish/host/libs/web/android_build_string.h new file mode 100644 index 0000000000..15b1013772 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/android_build_string.h @@ -0,0 +1,70 @@ +// +// Copyright (C) 2023 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 + +#include +#include +#include +#include + +#include "common/libs/utils/flag_parser.h" +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +struct DeviceBuildString { + std::string branch_or_id; + std::optional target; + std::optional filepath; +}; + +std::ostream& operator<<(std::ostream& out, + const DeviceBuildString& build_string); +bool operator==(const DeviceBuildString& lhs, const DeviceBuildString& rhs); +bool operator!=(const DeviceBuildString& lhs, const DeviceBuildString& rhs); + +struct DirectoryBuildString { + std::vector paths; + std::string target; + std::optional filepath; +}; + +std::ostream& operator<<(std::ostream& out, + const DirectoryBuildString& build_string); +bool operator==(const DirectoryBuildString& lhs, + const DirectoryBuildString& rhs); +bool operator!=(const DirectoryBuildString& lhs, + const DirectoryBuildString& rhs); + +using BuildString = std::variant; + +std::ostream& operator<<(std::ostream& out, const BuildString& build_string); + +std::ostream& operator<<(std::ostream& out, + const std::optional& build_string); + +std::optional GetFilepath(const BuildString& build_string); + +void SetFilepath(BuildString& build_string, const std::string& value); + +Result ParseBuildString(const std::string& build_string); + +Flag GflagsCompatFlag(const std::string& name, + std::optional& value); +Flag GflagsCompatFlag(const std::string& name, + std::vector>& value); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/credential_source.cc b/base/cvd/cuttlefish/host/libs/web/credential_source.cc new file mode 100644 index 0000000000..5873f906cb --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/credential_source.cc @@ -0,0 +1,425 @@ +// +// Copyright (C) 2019 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. + +#include "host/libs/web/credential_source.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common/libs/utils/base64.h" +#include "common/libs/utils/files.h" +#include "common/libs/utils/result.h" +#include "host/libs/web/http_client/http_client.h" + +namespace cuttlefish { +namespace { + +constexpr auto kRefreshWindow = std::chrono::minutes(2); + +std::unique_ptr TryParseServiceAccount( + HttpClient& http_client, const std::string& file_content) { + Json::Reader reader; + Json::Value content; + if (!reader.parse(file_content, content)) { + // Don't log the actual content of the file since it could be the actual + // access token. + LOG(DEBUG) << "Could not parse credential file as Service Account"; + return {}; + } + auto result = ServiceAccountOauthCredentialSource::FromJson( + http_client, content, kBuildScope); + if (!result.ok()) { + LOG(DEBUG) << "Failed to load service account json file: \n" + << result.error().FormatForEnv(); + return {}; + } + return std::unique_ptr( + new ServiceAccountOauthCredentialSource(std::move(*result))); +} + +Result> GetCredentialSourceLegacy( + HttpClient& http_client, const std::string& credential_source, + const std::string& oauth_filepath) { + std::unique_ptr result; + if (credential_source == "gce") { + result = GceMetadataCredentialSource::Make(http_client); + } else if (credential_source == "") { + LOG(VERBOSE) << "Probing acloud credentials at " << oauth_filepath; + if (FileExists(oauth_filepath)) { + std::ifstream stream(oauth_filepath); + auto attempt_load = + RefreshCredentialSource::FromOauth2ClientFile(http_client, stream); + if (attempt_load.ok()) { + result.reset(new RefreshCredentialSource(std::move(*attempt_load))); + } else { + LOG(DEBUG) << "Failed to load acloud credentials: " + << attempt_load.error().FormatForEnv(); + } + } else { + LOG(INFO) << "\"" << oauth_filepath + << "\" missing, running without credentials"; + } + } else if (!FileExists(credential_source)) { + // If the parameter doesn't point to an existing file it must be the + // credentials. + result = FixedCredentialSource::Make(credential_source); + } else { + // Read the file only once in case it's a pipe. + LOG(DEBUG) << "Attempting to open credentials file \"" << credential_source + << "\""; + auto file_content = + CF_EXPECTF(ReadFileContents(credential_source), + "Failure getting credential file contents from file \"{}\"", + credential_source); + if (auto crds = TryParseServiceAccount(http_client, file_content)) { + result = std::move(crds); + } else { + result = FixedCredentialSource::Make(file_content); + } + } + return result; +} + +} // namespace + +GceMetadataCredentialSource::GceMetadataCredentialSource( + HttpClient& http_client) + : http_client(http_client) { + latest_credential = ""; + expiration = std::chrono::steady_clock::now(); +} + +Result GceMetadataCredentialSource::Credential() { + if (expiration - std::chrono::steady_clock::now() < kRefreshWindow) { + CF_EXPECT(RefreshCredential()); + } + return latest_credential; +} + +Result GceMetadataCredentialSource::RefreshCredential() { + static constexpr char kRefreshUrl[] = + "http://metadata.google.internal/computeMetadata/v1/instance/" + "service-accounts/default/token"; + auto response = CF_EXPECT( + http_client.DownloadToJson(kRefreshUrl, {"Metadata-Flavor: Google"})); + const auto& json = response.data; + CF_EXPECT(response.HttpSuccess(), + "Error fetching credentials. The server response was \"" + << json << "\", and code was " << response.http_code); + CF_EXPECT(!json.isMember("error"), + "Response had \"error\" but had http success status. Received \"" + << json << "\""); + + CF_EXPECT(json.isMember("access_token") && json.isMember("expires_in"), + "GCE credential was missing access_token or expires_in. " + << "Full response was " << json << ""); + + expiration = std::chrono::steady_clock::now() + + std::chrono::seconds(json["expires_in"].asInt()); + latest_credential = json["access_token"].asString(); + return {}; +} + +std::unique_ptr GceMetadataCredentialSource::Make( + HttpClient& http_client) { + return std::unique_ptr( + new GceMetadataCredentialSource(http_client)); +} + +FixedCredentialSource::FixedCredentialSource(const std::string& credential) { + this->credential = credential; +} + +Result FixedCredentialSource::Credential() { return credential; } + +std::unique_ptr FixedCredentialSource::Make( + const std::string& credential) { + return std::unique_ptr(new FixedCredentialSource(credential)); +} + +Result RefreshCredentialSource::FromOauth2ClientFile( + HttpClient& http_client, std::istream& stream) { + Json::CharReaderBuilder builder; + std::unique_ptr reader(builder.newCharReader()); + Json::Value json; + std::string errorMessage; + CF_EXPECT(Json::parseFromStream(builder, stream, &json, &errorMessage), + "Failed to parse json: " << errorMessage); + CF_EXPECT(json.isMember("data")); + auto& data = json["data"]; + CF_EXPECT(data.type() == Json::ValueType::arrayValue); + + CF_EXPECT(data.size() == 1); + auto& data_first = data[0]; + CF_EXPECT(data_first.type() == Json::ValueType::objectValue); + + CF_EXPECT(data_first.isMember("credential")); + auto& credential = data_first["credential"]; + CF_EXPECT(credential.type() == Json::ValueType::objectValue); + + CF_EXPECT(credential.isMember("client_id")); + auto& client_id = credential["client_id"]; + CF_EXPECT(client_id.type() == Json::ValueType::stringValue); + + CF_EXPECT(credential.isMember("client_secret")); + auto& client_secret = credential["client_secret"]; + CF_EXPECT(client_secret.type() == Json::ValueType::stringValue); + + CF_EXPECT(credential.isMember("refresh_token")); + auto& refresh_token = credential["refresh_token"]; + CF_EXPECT(refresh_token.type() == Json::ValueType::stringValue); + + return RefreshCredentialSource(http_client, client_id.asString(), + client_secret.asString(), + refresh_token.asString()); +} + +RefreshCredentialSource::RefreshCredentialSource( + HttpClient& http_client, const std::string& client_id, + const std::string& client_secret, const std::string& refresh_token) + : http_client_(http_client), + client_id_(client_id), + client_secret_(client_secret), + refresh_token_(refresh_token) {} + +Result RefreshCredentialSource::Credential() { + if (expiration_ - std::chrono::steady_clock::now() < kRefreshWindow) { + CF_EXPECT(UpdateLatestCredential()); + } + return latest_credential_; +} + +Result RefreshCredentialSource::UpdateLatestCredential() { + std::vector headers = { + "Content-Type: application/x-www-form-urlencoded"}; + std::stringstream data; + data << "client_id=" << http_client_.UrlEscape(client_id_) << "&"; + data << "client_secret=" << http_client_.UrlEscape(client_secret_) << "&"; + data << "refresh_token=" << http_client_.UrlEscape(refresh_token_) << "&"; + data << "grant_type=refresh_token"; + + static constexpr char kUrl[] = "https://oauth2.googleapis.com/token"; + auto response = CF_EXPECT(http_client_.PostToJson(kUrl, data.str(), headers)); + CF_EXPECT(response.HttpSuccess(), response.data); + auto& json = response.data; + + CF_EXPECT(!json.isMember("error"), + "Response had \"error\" but had http success status. Received \"" + << json << "\""); + + CF_EXPECT(json.isMember("access_token") && json.isMember("expires_in"), + "Refresh credential was missing access_token or expires_in." + << " Full response was " << json << ""); + + expiration_ = std::chrono::steady_clock::now() + + std::chrono::seconds(json["expires_in"].asInt()); + latest_credential_ = json["access_token"].asString(); + return {}; +} + +static std::string CollectSslErrors() { + std::stringstream errors; + auto callback = [](const char* str, size_t len, void* stream) { + ((std::stringstream*)stream)->write(str, len); + return 1; // success + }; + ERR_print_errors_cb(callback, &errors); + return errors.str(); +} + +Result +ServiceAccountOauthCredentialSource::FromJson(HttpClient& http_client, + const Json::Value& json, + const std::string& scope) { + ServiceAccountOauthCredentialSource source(http_client); + source.scope_ = scope; + + CF_EXPECT(json.isMember("client_email")); + CF_EXPECT(json["client_email"].type() == Json::ValueType::stringValue); + source.email_ = json["client_email"].asString(); + + CF_EXPECT(json.isMember("private_key")); + CF_EXPECT(json["private_key"].type() == Json::ValueType::stringValue); + std::string key_str = json["private_key"].asString(); + + std::unique_ptr bo(CF_EXPECT(BIO_new(BIO_s_mem())), + BIO_free); + CF_EXPECT(BIO_write(bo.get(), key_str.c_str(), key_str.size()) == + key_str.size()); + + auto pkey = CF_EXPECT(PEM_read_bio_PrivateKey(bo.get(), nullptr, 0, 0), + CollectSslErrors()); + source.private_key_.reset(pkey); + + return source; +} + +ServiceAccountOauthCredentialSource::ServiceAccountOauthCredentialSource( + HttpClient& http_client) + : http_client_(http_client), private_key_(nullptr, EVP_PKEY_free) {} + +static Result Base64Url(const char* data, std::size_t size) { + std::string base64; + CF_EXPECT(EncodeBase64(data, size, &base64)); + base64 = android::base::StringReplace(base64, "+", "-", /* all */ true); + base64 = android::base::StringReplace(base64, "/", "_", /* all */ true); + return base64; +} + +static Result JsonToBase64Url(const Json::Value& json) { + Json::StreamWriterBuilder factory; + auto serialized = Json::writeString(factory, json); + return CF_EXPECT(Base64Url(serialized.c_str(), serialized.size())); +} + +static Result CreateJwt(const std::string& email, + const std::string& scope, + EVP_PKEY* private_key) { + using std::chrono::duration_cast; + using std::chrono::minutes; + using std::chrono::seconds; + using std::chrono::system_clock; + // https://developers.google.com/identity/protocols/oauth2/service-account + Json::Value header_json; + header_json["alg"] = "RS256"; + header_json["typ"] = "JWT"; + std::string header_str = CF_EXPECT(JsonToBase64Url(header_json)); + + Json::Value claim_set_json; + claim_set_json["iss"] = email; + claim_set_json["scope"] = scope; + claim_set_json["aud"] = "https://oauth2.googleapis.com/token"; + auto time = system_clock::now(); + claim_set_json["iat"] = + (Json::Value::UInt64)duration_cast(time.time_since_epoch()) + .count(); + auto exp = time + minutes(30); + claim_set_json["exp"] = + (Json::Value::UInt64)duration_cast(exp.time_since_epoch()) + .count(); + std::string claim_set_str = CF_EXPECT(JsonToBase64Url(claim_set_json)); + + std::string jwt_to_sign = header_str + "." + claim_set_str; + + std::unique_ptr sign_ctx( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + CF_EXPECT(EVP_DigestSignInit(sign_ctx.get(), nullptr, EVP_sha256(), nullptr, + private_key)); + CF_EXPECT(EVP_DigestSignUpdate(sign_ctx.get(), jwt_to_sign.c_str(), + jwt_to_sign.size())); + size_t length; + CF_EXPECT(EVP_DigestSignFinal(sign_ctx.get(), nullptr, &length)); + std::vector sig_raw(length); + CF_EXPECT(EVP_DigestSignFinal(sign_ctx.get(), sig_raw.data(), &length)); + + auto signature = CF_EXPECT(Base64Url((const char*)sig_raw.data(), length)); + return jwt_to_sign + "." + signature; +} + +Result ServiceAccountOauthCredentialSource::RefreshCredential() { + static constexpr char URL[] = "https://oauth2.googleapis.com/token"; + static constexpr char GRANT[] = "urn:ietf:params:oauth:grant-type:jwt-bearer"; + std::stringstream content; + content << "grant_type=" << http_client_.UrlEscape(GRANT) << "&"; + auto jwt = CF_EXPECT(CreateJwt(email_, scope_, private_key_.get())); + content << "assertion=" << http_client_.UrlEscape(jwt); + std::vector headers = { + "Content-Type: application/x-www-form-urlencoded"}; + auto response = + CF_EXPECT(http_client_.PostToJson(URL, content.str(), headers)); + CF_EXPECT(response.HttpSuccess(), + "Error fetching credentials. The server response was \"" + << response.data << "\", and code was " << response.http_code); + Json::Value json = response.data; + + CF_EXPECT(!json.isMember("error"), + "Response had \"error\" but had http success status. Received \"" + << json << "\""); + + CF_EXPECT(json.isMember("access_token") && json.isMember("expires_in"), + "Service account credential was missing access_token or expires_in." + << " Full response was " << json << ""); + + expiration_ = std::chrono::steady_clock::now() + + std::chrono::seconds(json["expires_in"].asInt()); + latest_credential_ = json["access_token"].asString(); + return {}; +} + +Result ServiceAccountOauthCredentialSource::Credential() { + if (expiration_ - std::chrono::steady_clock::now() < kRefreshWindow) { + CF_EXPECT(RefreshCredential()); + } + return latest_credential_; +} + +Result> GetCredentialSource( + HttpClient& http_client, const std::string& credential_source, + const std::string& oauth_filepath, const bool use_gce_metadata, + const std::string& credential_filepath, + const std::string& service_account_filepath) { + const int number_of_set_credentials = + !credential_source.empty() + use_gce_metadata + + !credential_filepath.empty() + !service_account_filepath.empty(); + CF_EXPECT_LE(number_of_set_credentials, 1, + "At most a single credential option may be used."); + + if (use_gce_metadata) { + return GceMetadataCredentialSource::Make(http_client); + } + if (!credential_filepath.empty()) { + std::string contents = + CF_EXPECTF(ReadFileContents(credential_filepath), + "Failure getting credential file contents from file \"{}\".", + credential_filepath); + return FixedCredentialSource::Make(contents); + } + if (!service_account_filepath.empty()) { + std::string contents = + CF_EXPECTF(ReadFileContents(service_account_filepath), + "Failure getting service account credential file contents " + "from file \"{}\".", + service_account_filepath); + auto service_account_credentials = + TryParseServiceAccount(http_client, contents); + CF_EXPECTF(service_account_credentials != nullptr, + "Unable to parse service account credentials in file \"{}\". " + "File contents: {}", + service_account_filepath, contents); + return std::move(service_account_credentials); + } + // use the deprecated credential_source or no value + // when this helper is removed its `.acloud2_oauth2.dat` processing should be + // moved here + return GetCredentialSourceLegacy(http_client, credential_source, + oauth_filepath); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/credential_source.h b/base/cvd/cuttlefish/host/libs/web/credential_source.h new file mode 100644 index 0000000000..e7cc2752c2 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/credential_source.h @@ -0,0 +1,117 @@ +// +// Copyright (C) 2019 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 + +#include +#include +#include +#include + +#include +#include + +#include "common/libs/utils/result.h" +#include "host/libs/web/http_client/http_client.h" + +namespace cuttlefish { + +inline constexpr char kBuildScope[] = + "https://www.googleapis.com/auth/androidbuild.internal"; + +class CredentialSource { +public: + virtual ~CredentialSource() = default; + virtual Result Credential() = 0; +}; + +class GceMetadataCredentialSource : public CredentialSource { + HttpClient& http_client; + std::string latest_credential; + std::chrono::steady_clock::time_point expiration; + + Result RefreshCredential(); + + public: + GceMetadataCredentialSource(HttpClient&); + GceMetadataCredentialSource(GceMetadataCredentialSource&&) = default; + + Result Credential() override; + + static std::unique_ptr Make(HttpClient&); +}; + +class FixedCredentialSource : public CredentialSource { + std::string credential; +public: + FixedCredentialSource(const std::string& credential); + + Result Credential() override; + + static std::unique_ptr Make(const std::string& credential); +}; + +class RefreshCredentialSource : public CredentialSource { + public: + static Result FromOauth2ClientFile( + HttpClient& http_client, std::istream& stream); + + RefreshCredentialSource(HttpClient& http_client, const std::string& client_id, + const std::string& client_secret, + const std::string& refresh_token); + + Result Credential() override; + + private: + Result UpdateLatestCredential(); + + HttpClient& http_client_; + std::string client_id_; + std::string client_secret_; + std::string refresh_token_; + + std::string latest_credential_; + std::chrono::steady_clock::time_point expiration_; +}; + +class ServiceAccountOauthCredentialSource : public CredentialSource { + public: + static Result FromJson( + HttpClient& http_client, const Json::Value& service_account_json, + const std::string& scope); + ServiceAccountOauthCredentialSource(ServiceAccountOauthCredentialSource&&) = + default; + + Result Credential() override; + + private: + ServiceAccountOauthCredentialSource(HttpClient& http_client); + Result RefreshCredential(); + + HttpClient& http_client_; + std::string email_; + std::string scope_; + std::unique_ptr private_key_; + + std::string latest_credential_; + std::chrono::steady_clock::time_point expiration_; +}; + +Result> GetCredentialSource( + HttpClient& http_client, const std::string& credential_source, + const std::string& oauth_filepath, const bool use_gce_metadata, + const std::string& credential_filepath, + const std::string& service_account_filepath); +} diff --git a/base/cvd/cuttlefish/host/libs/web/http_client/http_client.cc b/base/cvd/cuttlefish/host/libs/web/http_client/http_client.cc new file mode 100644 index 0000000000..6f497c4fa1 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/http_client/http_client.cc @@ -0,0 +1,456 @@ +// +// Copyright (C) 2019 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. + +#include "host/libs/web/http_client/http_client.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "common/libs/utils/json.h" +#include "common/libs/utils/subprocess.h" +#include "host/libs/web/http_client/http_client_util.h" + +namespace cuttlefish { +namespace { + +std::string TrimWhitespace(const char* data, const size_t size) { + std::string converted(data, size); + return android::base::Trim(converted); +} + +int LoggingCurlDebugFunction(CURL*, curl_infotype type, char* data, size_t size, + void*) { + switch (type) { + case CURLINFO_TEXT: + LOG(VERBOSE) << "CURLINFO_TEXT "; + LOG(INFO) << TrimWhitespace(data, size); + break; + case CURLINFO_HEADER_IN: + LOG(VERBOSE) << "CURLINFO_HEADER_IN "; + LOG(DEBUG) << TrimWhitespace(data, size); + break; + case CURLINFO_HEADER_OUT: + LOG(VERBOSE) << "CURLINFO_HEADER_OUT "; + LOG(DEBUG) << ScrubSecrets(TrimWhitespace(data, size)); + break; + case CURLINFO_DATA_IN: + break; + case CURLINFO_DATA_OUT: + break; + case CURLINFO_SSL_DATA_IN: + break; + case CURLINFO_SSL_DATA_OUT: + break; + case CURLINFO_END: + break; + default: + LOG(ERROR) << "Unexpected cURL output type: " << type; + break; + } + return 0; +} + +enum class HttpMethod { + kGet, + kPost, + kDelete, +}; + +size_t curl_to_function_cb(char* ptr, size_t, size_t nmemb, void* userdata) { + HttpClient::DataCallback* callback = (HttpClient::DataCallback*)userdata; + if (!(*callback)(ptr, nmemb)) { + return 0; // Signals error to curl + } + return nmemb; +} + +Result CurlUrlGet(CURLU* url, CURLUPart what, unsigned int flags) { + char* str_ptr = nullptr; + CF_EXPECT(curl_url_get(url, what, &str_ptr, flags) == CURLUE_OK); + std::string str(str_ptr); + curl_free(str_ptr); + return str; +} + +using ManagedCurlSlist = + std::unique_ptr; + +Result SlistFromStrings( + const std::vector& strings) { + ManagedCurlSlist curl_headers(nullptr, curl_slist_free_all); + for (const auto& str : strings) { + curl_slist* temp = curl_slist_append(curl_headers.get(), str.c_str()); + CF_EXPECT(temp != nullptr, + "curl_slist_append failed to add \"" << str << "\""); + (void)curl_headers.release(); // Memory is now owned by `temp` + curl_headers.reset(temp); + } + return curl_headers; +} + +class CurlClient : public HttpClient { + public: + CurlClient(NameResolver resolver, const bool use_logging_debug_function) + : resolver_(std::move(resolver)), + use_logging_debug_function_(use_logging_debug_function) { + curl_ = curl_easy_init(); + if (!curl_) { + LOG(ERROR) << "failed to initialize curl"; + return; + } + } + ~CurlClient() { curl_easy_cleanup(curl_); } + + Result> GetToString( + const std::string& url, + const std::vector& headers) override { + return DownloadToString(HttpMethod::kGet, url, headers); + } + + Result> PostToString( + const std::string& url, const std::string& data_to_write, + const std::vector& headers) override { + return DownloadToString(HttpMethod::kPost, url, headers, data_to_write); + } + + Result> DeleteToString( + const std::string& url, + const std::vector& headers) override { + return DownloadToString(HttpMethod::kDelete, url, headers); + } + + Result> PostToJson( + const std::string& url, const std::string& data_to_write, + const std::vector& headers) override { + return DownloadToJson(HttpMethod::kPost, url, headers, data_to_write); + } + + Result> PostToJson( + const std::string& url, const Json::Value& data_to_write, + const std::vector& headers) override { + std::stringstream json_str; + json_str << data_to_write; + return DownloadToJson(HttpMethod::kPost, url, headers, json_str.str()); + } + + Result> DownloadToCallback( + DataCallback callback, const std::string& url, + const std::vector& headers) { + return DownloadToCallback(HttpMethod::kGet, callback, url, headers); + } + + Result> DownloadToFile( + const std::string& url, const std::string& path, + const std::vector& headers) { + LOG(INFO) << "Attempting to save \"" << url << "\" to \"" << path << "\""; + std::fstream stream; + auto callback = [&stream, path](char* data, size_t size) -> bool { + if (data == nullptr) { + stream.open(path, std::ios::out | std::ios::binary | std::ios::trunc); + return !stream.fail(); + } + stream.write(data, size); + return !stream.fail(); + }; + auto http_response = CF_EXPECT(DownloadToCallback(callback, url, headers)); + return HttpResponse{path, http_response.http_code}; + } + + Result> DownloadToJson( + const std::string& url, const std::vector& headers) { + return DownloadToJson(HttpMethod::kGet, url, headers); + } + + Result> DeleteToJson( + const std::string& url, + const std::vector& headers) override { + return DownloadToJson(HttpMethod::kDelete, url, headers); + } + + std::string UrlEscape(const std::string& text) override { + char* escaped_str = curl_easy_escape(curl_, text.c_str(), text.size()); + std::string ret{escaped_str}; + curl_free(escaped_str); + return ret; + } + + private: + Result ManuallyResolveUrl(const std::string& url_str) { + if (!resolver_) { + return ManagedCurlSlist(nullptr, curl_slist_free_all); + } + LOG(INFO) << "Manually resolving \"" << url_str << "\""; + std::stringstream resolve_line; + std::unique_ptr url(curl_url(), + curl_url_cleanup); + CF_EXPECT(curl_url_set(url.get(), CURLUPART_URL, url_str.c_str(), 0) == + CURLUE_OK); + auto hostname = CF_EXPECT(CurlUrlGet(url.get(), CURLUPART_HOST, 0)); + resolve_line << "+" << hostname; + auto port = + CF_EXPECT(CurlUrlGet(url.get(), CURLUPART_PORT, CURLU_DEFAULT_PORT)); + resolve_line << ":" << port << ":"; + resolve_line << android::base::Join(CF_EXPECT(resolver_(hostname)), ","); + auto slist = CF_EXPECT(SlistFromStrings({resolve_line.str()})); + return slist; + } + + Result> DownloadToJson( + HttpMethod method, const std::string& url, + const std::vector& headers, + const std::string& data_to_write = "") { + auto response = + CF_EXPECT(DownloadToString(method, url, headers, data_to_write)); + auto result = ParseJson(response.data); + if (!result.ok()) { + Json::Value error_json; + LOG(ERROR) << "Could not parse json: " << result.error().FormatForEnv(); + error_json["error"] = "Failed to parse json: " + result.error().Message(); + error_json["response"] = response.data; + return HttpResponse{error_json, response.http_code}; + } + return HttpResponse{*result, response.http_code}; + } + + Result> DownloadToString( + HttpMethod method, const std::string& url, + const std::vector& headers, + const std::string& data_to_write = "") { + std::stringstream stream; + auto callback = [&stream](char* data, size_t size) -> bool { + if (data == nullptr) { + stream = std::stringstream(); + return true; + } + stream.write(data, size); + return true; + }; + auto http_response = CF_EXPECT( + DownloadToCallback(method, callback, url, headers, data_to_write)); + return HttpResponse{stream.str(), http_response.http_code}; + } + + Result> DownloadToCallback( + HttpMethod method, DataCallback callback, const std::string& url, + const std::vector& headers, + const std::string& data_to_write = "") { + std::lock_guard lock(mutex_); + auto extra_cache_entries = CF_EXPECT(ManuallyResolveUrl(url)); + curl_easy_setopt(curl_, CURLOPT_RESOLVE, extra_cache_entries.get()); + LOG(INFO) << "Attempting to download \"" << url << "\""; + CF_EXPECT(data_to_write.empty() || method == HttpMethod::kPost, + "data must be empty for non POST requests"); + CF_EXPECT(curl_ != nullptr, "curl was not initialized"); + CF_EXPECT(callback(nullptr, 0) /* Signal start of data */, + "callback failure"); + auto curl_headers = CF_EXPECT(SlistFromStrings(headers)); + curl_easy_reset(curl_); + if (method == HttpMethod::kDelete) { + curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "DELETE"); + } + curl_easy_setopt(curl_, CURLOPT_CAINFO, + "/etc/ssl/certs/ca-certificates.crt"); + curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, curl_headers.get()); + curl_easy_setopt(curl_, CURLOPT_URL, url.c_str()); + if (method == HttpMethod::kPost) { + curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, data_to_write.size()); + curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, data_to_write.c_str()); + } + curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, curl_to_function_cb); + curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &callback); + char error_buf[CURL_ERROR_SIZE]; + curl_easy_setopt(curl_, CURLOPT_ERRORBUFFER, error_buf); + curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L); + // CURLOPT_VERBOSE must be set for CURLOPT_DEBUGFUNCTION be utilized + if (use_logging_debug_function_) { + curl_easy_setopt(curl_, CURLOPT_DEBUGFUNCTION, LoggingCurlDebugFunction); + } + CURLcode res = curl_easy_perform(curl_); + CF_EXPECT(res == CURLE_OK, + "curl_easy_perform() failed. " + << "Code was \"" << res << "\". " + << "Strerror was \"" << curl_easy_strerror(res) << "\". " + << "Error buffer was \"" << error_buf << "\"."); + long http_code = 0; + curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &http_code); + return HttpResponse{{}, http_code}; + } + + CURL* curl_; + NameResolver resolver_; + std::mutex mutex_; + bool use_logging_debug_function_; +}; + +class ServerErrorRetryClient : public HttpClient { + public: + ServerErrorRetryClient(HttpClient& inner, int retry_attempts, + std::chrono::milliseconds retry_delay) + : inner_client_(inner), + retry_attempts_(retry_attempts), + retry_delay_(retry_delay) {} + + Result> GetToString( + const std::string& url, const std::vector& headers) { + auto fn = [&, this]() { return inner_client_.GetToString(url, headers); }; + return CF_EXPECT(RetryImpl(fn)); + } + + Result> PostToString( + const std::string& url, const std::string& data, + const std::vector& headers) override { + auto fn = [&, this]() { + return inner_client_.PostToString(url, data, headers); + }; + return CF_EXPECT(RetryImpl(fn)); + } + + Result> DeleteToString( + const std::string& url, const std::vector& headers) { + auto fn = [&, this]() { + return inner_client_.DeleteToString(url, headers); + }; + return CF_EXPECT(RetryImpl(fn)); + } + + Result> PostToJson( + const std::string& url, const Json::Value& data, + const std::vector& headers) override { + auto fn = [&, this]() { + return inner_client_.PostToJson(url, data, headers); + }; + return CF_EXPECT(RetryImpl(fn)); + } + + Result> PostToJson( + const std::string& url, const std::string& data, + const std::vector& headers) override { + auto fn = [&, this]() { + return inner_client_.PostToJson(url, data, headers); + }; + return CF_EXPECT(RetryImpl(fn)); + } + + Result> DownloadToFile( + const std::string& url, const std::string& path, + const std::vector& headers) { + auto fn = [&, this]() { + return inner_client_.DownloadToFile(url, path, headers); + }; + return CF_EXPECT(RetryImpl(fn)); + } + + Result> DownloadToJson( + const std::string& url, const std::vector& headers) { + auto fn = [&, this]() { + return inner_client_.DownloadToJson(url, headers); + }; + return CF_EXPECT(RetryImpl(fn)); + } + + Result> DownloadToCallback( + DataCallback cb, const std::string& url, + const std::vector& hdrs) override { + auto fn = [&, this]() { + return inner_client_.DownloadToCallback(cb, url, hdrs); + }; + return CF_EXPECT(RetryImpl(fn)); + } + + Result> DeleteToJson( + const std::string& url, + const std::vector& headers) override { + auto fn = [&, this]() { return inner_client_.DeleteToJson(url, headers); }; + return CF_EXPECT(RetryImpl(fn)); + } + + std::string UrlEscape(const std::string& text) override { + return inner_client_.UrlEscape(text); + } + + private: + template + Result> RetryImpl( + std::function>()> attempt_fn) { + HttpResponse response; + for (int attempt = 0; attempt != retry_attempts_; ++attempt) { + if (attempt != 0) { + std::this_thread::sleep_for(retry_delay_); + } + response = CF_EXPECT(attempt_fn()); + if (!response.HttpServerError()) { + return response; + } + } + return response; + } + + private: + HttpClient& inner_client_; + int retry_attempts_; + std::chrono::milliseconds retry_delay_; +}; + +} // namespace + +Result> GetEntDnsResolve(const std::string& host) { + Command command("/bin/getent"); + command.AddParameter("hosts"); + command.AddParameter(host); + + std::string out; + std::string err; + CF_EXPECT(RunWithManagedStdio(std::move(command), nullptr, &out, &err) == 0, + "`getent hosts " << host << "` failed: out = \"" << out + << "\", err = \"" << err << "\""); + auto lines = android::base::Tokenize(out, "\n"); + for (auto& line : lines) { + auto line_split = android::base::Tokenize(line, " \t"); + CF_EXPECT(line_split.size() == 2, + "unexpected line format: \"" << line << "\""); + line = line_split[0]; + } + return lines; +} + +/* static */ std::unique_ptr HttpClient::CurlClient( + NameResolver resolver, bool use_logging_debug_function) { + return std::unique_ptr( + new class CurlClient(std::move(resolver), use_logging_debug_function)); +} + +/* static */ std::unique_ptr HttpClient::ServerErrorRetryClient( + HttpClient& inner, int retry_attempts, + std::chrono::milliseconds retry_delay) { + return std::unique_ptr( + new class ServerErrorRetryClient(inner, retry_attempts, retry_delay)); +} + +HttpClient::~HttpClient() = default; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/http_client/http_client.h b/base/cvd/cuttlefish/host/libs/web/http_client/http_client.h new file mode 100644 index 0000000000..67a54ac9b0 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/http_client/http_client.h @@ -0,0 +1,105 @@ +// +// Copyright (C) 2019 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 + +#include +#include +#include +#include +#include +#include + +#include + +#include "common/libs/utils/result.h" + +namespace cuttlefish { + +static inline bool IsHttpSuccess(int http_code) { + return http_code >= 200 && http_code <= 299; +}; + +struct HttpVoidResponse {}; + +template +struct HttpResponse { + bool HttpInfo() { return http_code >= 100 && http_code <= 199; } + bool HttpSuccess() { return IsHttpSuccess(http_code); } + bool HttpRedirect() { return http_code >= 300 && http_code <= 399; } + bool HttpClientError() { return http_code >= 400 && http_code <= 499; } + bool HttpServerError() { return http_code >= 500 && http_code <= 599; } + + typename std::conditional, HttpVoidResponse, T>::type data; + long http_code; +}; + +using NameResolver = + std::function>(const std::string&)>; + +Result> GetEntDnsResolve(const std::string& host); + +class HttpClient { + public: + typedef std::function DataCallback; + + static std::unique_ptr CurlClient( + NameResolver resolver = NameResolver(), + const bool use_logging_debug_function = false); + static std::unique_ptr ServerErrorRetryClient( + HttpClient&, int retry_attempts, std::chrono::milliseconds retry_delay); + + virtual ~HttpClient(); + + virtual Result> GetToString( + const std::string& url, const std::vector& headers = {}) = 0; + virtual Result> PostToString( + const std::string& url, const std::string& data, + const std::vector& headers = {}) = 0; + virtual Result> DeleteToString( + const std::string& url, const std::vector& headers = {}) = 0; + + // Returns the json object contained in the response's body. + // + // NOTE: In case of a parsing error a successful `result` will be returned + // with the relevant http status code and a json object with the next format: + // { + // "error": "Failed to parse json", + // "response: "" + // } + virtual Result> PostToJson( + const std::string& url, const std::string& data, + const std::vector& headers = {}) = 0; + virtual Result> PostToJson( + const std::string& url, const Json::Value& data, + const std::vector& headers = {}) = 0; + virtual Result> DownloadToJson( + const std::string& url, const std::vector& headers = {}) = 0; + virtual Result> DeleteToJson( + const std::string& url, const std::vector& headers = {}) = 0; + + virtual Result> DownloadToFile( + const std::string& url, const std::string& path, + const std::vector& headers = {}) = 0; + + // Returns response's status code. + virtual Result> DownloadToCallback( + DataCallback callback, const std::string& url, + const std::vector& headers = {}) = 0; + + virtual std::string UrlEscape(const std::string&) = 0; +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/http_client/http_client_util.cc b/base/cvd/cuttlefish/host/libs/web/http_client/http_client_util.cc new file mode 100644 index 0000000000..5fc432495e --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/http_client/http_client_util.cc @@ -0,0 +1,37 @@ +// +// Copyright (C) 2023 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. + +#include "host/libs/web/http_client/http_client_util.h" + +#include +#include + +namespace cuttlefish { + +std::string ScrubSecrets(const std::string& data) { + std::string result = data; + // eg []Authorization: Bearer token_text[] -> + // []Authorization: Bearer token_...[] + result = std::regex_replace( + result, std::regex("(Authorization:[ ]+\\S+[ ]+)(\\S{6})\\S*"), + "$1$2..."); + // eg []client_secret=token_text[] -> + // []client_secret=token_...[] + result = std::regex_replace( + result, std::regex("(client_secret=)(\\S{6})[^\\&\\s]*"), "$1$2..."); + return result; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/http_client/http_client_util.h b/base/cvd/cuttlefish/host/libs/web/http_client/http_client_util.h new file mode 100644 index 0000000000..89881a75fc --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/http_client/http_client_util.h @@ -0,0 +1,24 @@ +// +// Copyright (C) 2023 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 + +#include + +namespace cuttlefish { + +std::string ScrubSecrets(const std::string& data); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/http_client/sso_client.cc b/base/cvd/cuttlefish/host/libs/web/http_client/sso_client.cc new file mode 100644 index 0000000000..04bc616057 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/http_client/sso_client.cc @@ -0,0 +1,142 @@ +// +// Copyright (C) 2022 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. + +#include "host/libs/web/http_client/sso_client.h" + +#include +#include + +#include "common/libs/utils/subprocess.h" + +namespace cuttlefish { +namespace http_client { +namespace { + +constexpr char kSsoClientBin[] = "/usr/bin/sso_client"; + +// Matches the sso_client's standard output when it succeeds expecting a valid +// http response. +const std::regex kStdoutRegex( + "HTTP/\\d+\\.\\d+\\s(\\d+)\\s.+\r\n" /* status */ + "(?:.+\r\n)+\r\n" /* headers */ + "(.+)?" /* body */ + "\n?" /* new line added by the sso_client if a body exists */); + +enum class HttpMethod { + kGet, + kPost, + kDelete, +}; + +const char* kHttpMethodStrings[] = {"GET", "POST", "DELETE"}; + +Result> MakeRequest( + ExecCmdFunc exec_cmd_func_, const std::string& url, + HttpMethod method = HttpMethod::kGet, const std::string& data = "") { + Command sso_client_cmd(kSsoClientBin); + sso_client_cmd.AddParameter("--use_master_cookie"); + sso_client_cmd.AddParameter("--request_timeout=300"); // 5 minutes + sso_client_cmd.AddParameter("--dump_header"); + sso_client_cmd.AddParameter("--url=" + url); + sso_client_cmd.AddParameter("--method=" + + std::string(kHttpMethodStrings[(int)method])); + if (method == HttpMethod::kPost) { + if (!data.empty()) { + sso_client_cmd.AddParameter("--data=" + data); + } + } + std::string stdout_, stderr_; + int ret = exec_cmd_func_(std::move(sso_client_cmd), nullptr, &stdout_, + &stderr_, SubprocessOptions()); + CF_EXPECT(ret == 0, + "`sso_client` execution failed with combined stdout and stderr: " + << stdout_ << stderr_); + CF_EXPECT(std::regex_match(stdout_, kStdoutRegex), + "Failed parsing `sso_client` output. Output:\n" + << stdout_); + std::smatch match; + std::regex_search(stdout_, match, kStdoutRegex); + long status_code = std::atol(match[1].str().data()); + std::string body = ""; + if (match.size() == 3) { + body = match[2]; + } + return HttpResponse{body, status_code}; +} +} // namespace + +SsoClient::SsoClient() : exec_cmd_func_(&RunWithManagedStdio) {} + +SsoClient::SsoClient(ExecCmdFunc exec_cmd_func) + : exec_cmd_func_(exec_cmd_func) {} + +SsoClient::~SsoClient() {} + +Result> SsoClient::GetToString( + const std::string& url, const std::vector& headers) { + // TODO(b/250670329): Handle request headers. + CF_EXPECT(headers.empty(), "headers are not handled yet"); + return MakeRequest(exec_cmd_func_, url); +} + +Result> SsoClient::PostToString( + const std::string& url, const std::string& data, + const std::vector& headers) { + // TODO(b/250670329): Handle request headers. + CF_EXPECT(headers.empty(), "headers are not handled yet"); + return MakeRequest(exec_cmd_func_, url, HttpMethod::kPost, data); +}; + +Result> SsoClient::DeleteToString( + const std::string& url, const std::vector& headers) { + // TODO(b/250670329): Handle request headers. + CF_EXPECT(headers.empty(), "headers are not handled yet"); + return MakeRequest(exec_cmd_func_, url, HttpMethod::kDelete); +} + +Result> SsoClient::PostToJson( + const std::string&, const std::string&, const std::vector&) { + return CF_ERR("Not implemented"); +} + +Result> SsoClient::PostToJson( + const std::string&, const Json::Value&, const std::vector&) { + return CF_ERR("Not implemented"); +} + +Result> SsoClient::DownloadToFile( + const std::string&, const std::string&, const std::vector&) { + return CF_ERR("Not implemented"); +} + +Result> SsoClient::DownloadToJson( + const std::string&, const std::vector&) { + return CF_ERR("Not implemented"); +} + +Result> SsoClient::DownloadToCallback( + DataCallback, const std::string&, const std::vector&) { + return CF_ERR("Not implemented"); +} + +Result> SsoClient::DeleteToJson( + const std::string&, const std::vector&) { + return CF_ERR("Not implemented"); +} + +std::string SsoClient::UrlEscape(const std::string&) { return ""; } + +} // namespace http_client +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/http_client/sso_client.h b/base/cvd/cuttlefish/host/libs/web/http_client/sso_client.h new file mode 100644 index 0000000000..846823b928 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/http_client/sso_client.h @@ -0,0 +1,79 @@ +// +// Copyright (C) 2022 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 + +#include "common/libs/utils/subprocess.h" +#include "host/libs/web/http_client/http_client.h" + +namespace cuttlefish { +namespace http_client { + +typedef std::function + ExecCmdFunc; + +class SsoClient : public HttpClient { + public: + SsoClient(); + + SsoClient(ExecCmdFunc); + + ~SsoClient(); + + Result> GetToString( + const std::string& url, + const std::vector& headers = {}) override; + + Result> PostToString( + const std::string&, const std::string&, + const std::vector& headers = {}) override; + + Result> DeleteToString( + const std::string& url, + const std::vector& headers = {}) override; + + Result> PostToJson( + const std::string&, const std::string&, + const std::vector& headers = {}) override; + + Result> PostToJson( + const std::string&, const Json::Value&, + const std::vector& headers = {}) override; + + Result> DownloadToFile( + const std::string&, const std::string&, + const std::vector& headers = {}) override; + + Result> DownloadToJson( + const std::string&, + const std::vector& headers = {}) override; + + Result> DownloadToCallback( + DataCallback, const std::string&, + const std::vector& headers = {}) override; + + Result> DeleteToJson( + const std::string&, + const std::vector& headers = {}) override; + + std::string UrlEscape(const std::string&) override; + + private: + ExecCmdFunc exec_cmd_func_; +}; + +} // namespace http_client +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/http_client/unittest/http_client_util_test.cc b/base/cvd/cuttlefish/host/libs/web/http_client/unittest/http_client_util_test.cc new file mode 100644 index 0000000000..d0af2e5d47 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/http_client/unittest/http_client_util_test.cc @@ -0,0 +1,75 @@ +// +// Copyright (C) 2023 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. + +#include "host/libs/web/http_client/http_client_util.h" + +#include + +namespace cuttlefish { +namespace http_client { + +TEST(HttpClientUtilTest, ScrubSecretsAuthorizationMatch) { + EXPECT_EQ(ScrubSecrets("Authorization: Bearer 123456"), + "Authorization: Bearer 123456..."); + EXPECT_EQ(ScrubSecrets("Authorization: Bearer 1234567890"), + "Authorization: Bearer 123456..."); + EXPECT_EQ(ScrubSecrets("Authorization: Basic 1234567890"), + "Authorization: Basic 123456..."); + EXPECT_EQ(ScrubSecrets("text\nAuthorization: Bearer 1234567890"), + "text\nAuthorization: Bearer 123456..."); + EXPECT_EQ(ScrubSecrets("Authorization: Bearer 1234567890\nnext_line"), + "Authorization: Bearer 123456...\nnext_line"); + EXPECT_EQ(ScrubSecrets("Authorization: Bearer 1234567890 \nnext_line"), + "Authorization: Bearer 123456... \nnext_line"); + EXPECT_EQ(ScrubSecrets("Authorization: Bearer 1234567890 \nnext_line"), + "Authorization: Bearer 123456... \nnext_line"); +} + +TEST(HttpClientUtilTest, ScrubSecretsAuthorizationNoMatch) { + EXPECT_EQ(ScrubSecrets("hello world"), "hello world"); + EXPECT_EQ(ScrubSecrets("Authorization: Bearer 12345"), + "Authorization: Bearer 12345"); + EXPECT_EQ(ScrubSecrets("Authorization Bearer 1234567890"), + "Authorization Bearer 1234567890"); + EXPECT_EQ(ScrubSecrets("Authorization: 1234567890"), + "Authorization: 1234567890"); +} + +TEST(HttpClientUtilTest, ScrubSecretsClientSecretMatch) { + EXPECT_EQ(ScrubSecrets("client_secret=123456"), "client_secret=123456..."); + EXPECT_EQ(ScrubSecrets("client_secret=1234567890"), + "client_secret=123456..."); + EXPECT_EQ(ScrubSecrets("text\nclient_secret=1234567890"), + "text\nclient_secret=123456..."); + EXPECT_EQ(ScrubSecrets("client_id=abc&client_secret=1234567890"), + "client_id=abc&client_secret=123456..."); + EXPECT_EQ(ScrubSecrets("client_secret=1234567890\nnext_line"), + "client_secret=123456...\nnext_line"); + EXPECT_EQ(ScrubSecrets("client_secret=1234567890 \nnext_line"), + "client_secret=123456... \nnext_line"); + EXPECT_EQ(ScrubSecrets("client_secret=1234567890 \nnext_line"), + "client_secret=123456... \nnext_line"); + EXPECT_EQ(ScrubSecrets("client_secret=1234567890&client_id=abc"), + "client_secret=123456...&client_id=abc"); +} + +TEST(HttpClientUtilTest, ScrubSecretsClientSecretNoMatch) { + EXPECT_EQ(ScrubSecrets("hello world"), "hello world"); + EXPECT_EQ(ScrubSecrets("client_secret=12345"), "client_secret=12345"); + EXPECT_EQ(ScrubSecrets("client_id=1234567890"), "client_id=1234567890"); +} + +} // namespace http_client +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/http_client/unittest/main_test.cc b/base/cvd/cuttlefish/host/libs/web/http_client/unittest/main_test.cc new file mode 100644 index 0000000000..d2ceeb7887 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/http_client/unittest/main_test.cc @@ -0,0 +1,21 @@ +// +// Copyright (C) 2022 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. + +#include + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/base/cvd/cuttlefish/host/libs/web/http_client/unittest/sso_client_test.cc b/base/cvd/cuttlefish/host/libs/web/http_client/unittest/sso_client_test.cc new file mode 100644 index 0000000000..84e98c2d0d --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/http_client/unittest/sso_client_test.cc @@ -0,0 +1,209 @@ +// +// Copyright (C) 2022 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. + +#include "host/libs/web/http_client/sso_client.h" + +#include + +#include + +namespace cuttlefish { +namespace http_client { + +TEST(SsoClientTest, GetToStringSucceeds) { + std::string stdout_ = + "HTTP/1.1 222 Bad Request\r\n" + "Content-Type: application/json\r\n" + "Vary: Accept-Encoding\r\n" + "Date: Tue, 19 Jul 2022 00:00:54 GMT\r\n" + "Pragma: no-cache\r\n" + "Expires: Fri, 01 Jan 1990 00:00:00 GMT\r\n" + "Cache-Control: no-cache, must-revalidate\r\n" + "\r\n" + "foo" + "\n"; + auto exec = [&](Command&&, const std::string*, std::string* out, std::string*, + SubprocessOptions) { + *out = stdout_; + return 0; + }; + SsoClient client(exec); + + auto result = client.GetToString("https://some.url"); + + EXPECT_TRUE(result.ok()) << result.error().Trace(); + EXPECT_EQ(result->data, "foo"); + EXPECT_EQ(result->http_code, 222); +} + +TEST(SsoClientTest, GetToStringSucceedsEmptyBody) { + std::string stdout_ = + "HTTP/1.1 222 OK\r\n" + "Content-Type: application/json\r\n" + "\r\n" + "\n"; + auto exec = [&](Command&&, const std::string*, std::string* out, std::string*, + SubprocessOptions) { + *out = stdout_; + return 0; + }; + SsoClient client(exec); + + auto result = client.GetToString("https://some.url"); + + EXPECT_TRUE(result.ok()) << result.error().Trace(); + EXPECT_EQ(result->data, ""); + EXPECT_EQ(result->http_code, 222); +} + +TEST(SsoClientTest, GetToStringNoBody) { + std::string stdout_ = + "HTTP/1.1 502 Bad Gateway\r\n" + "Content-Type: application/json\r\n" + "\r\n"; + auto exec = [&](Command&&, const std::string*, std::string* out, std::string*, + SubprocessOptions) { + *out = stdout_; + return 0; + }; + SsoClient client(exec); + + auto result = client.GetToString("https://some.url"); + + EXPECT_TRUE(result.ok()) << result.error().Trace(); + EXPECT_EQ(result->data, ""); + EXPECT_EQ(result->http_code, 502); +} + +constexpr char kBashScriptPrefix[] = R"(#!/bin/bash + +/usr/bin/sso_client \ +--use_master_cookie \ +--request_timeout=300 \ +--dump_header \)"; + +TEST(SsoClientTest, GetToStringVerifyCommandArgs) { + std::string cmd_as_bash_script; + auto exec = [&](Command&& cmd, const std::string*, std::string*, std::string*, + SubprocessOptions) { + cmd_as_bash_script = cmd.AsBashScript(); + return 0; + }; + SsoClient client(exec); + + client.GetToString("https://some.url"); + + std::string expected = std::string(kBashScriptPrefix) + R"( +--url=https://some.url \ +--method=GET)"; + EXPECT_EQ(cmd_as_bash_script, expected); +} + +TEST(SsoClientTest, PostToStringVerifyCommandArgs) { + std::string cmd_as_bash_script; + auto exec = [&](Command&& cmd, const std::string*, std::string*, std::string*, + SubprocessOptions) { + cmd_as_bash_script = cmd.AsBashScript(); + return 0; + }; + SsoClient client(exec); + + client.PostToString("https://some.url", "foo"); + + std::string expected = std::string(kBashScriptPrefix) + R"( +--url=https://some.url \ +--method=POST \ +--data=foo)"; + EXPECT_EQ(cmd_as_bash_script, expected); +} + +TEST(SsoClientTest, PostToStringEmptyDataVerifyCommandArgs) { + std::string cmd_as_bash_script; + auto exec = [&](Command&& cmd, const std::string*, std::string*, std::string*, + SubprocessOptions) { + cmd_as_bash_script = cmd.AsBashScript(); + return 0; + }; + SsoClient client(exec); + + client.PostToString("https://some.url", ""); + + std::string expected = std::string(kBashScriptPrefix) + R"( +--url=https://some.url \ +--method=POST)"; + EXPECT_EQ(cmd_as_bash_script, expected); +} + +TEST(SsoClientTest, DeleteToStringVerifyCommandArgs) { + std::string cmd_as_bash_script; + auto exec = [&](Command&& cmd, const std::string*, std::string*, std::string*, + SubprocessOptions) { + cmd_as_bash_script = cmd.AsBashScript(); + return 0; + }; + SsoClient client(exec); + + client.DeleteToString("https://some.url"); + + std::string expected = std::string(kBashScriptPrefix) + R"( +--url=https://some.url \ +--method=DELETE)"; + EXPECT_EQ(cmd_as_bash_script, expected); +} + +TEST(SsoClientTest, GetToStringFailsInvalidResponseFormat) { + std::string stdout_ = "E0719 13:45:32.891177 2702210 foo failed"; + auto exec = [&](Command&&, const std::string*, std::string* out, std::string*, + SubprocessOptions) { + *out = stdout_; + return 0; + }; + SsoClient client(exec); + + auto result = client.GetToString("https://some.url"); + + EXPECT_FALSE(result.ok()); +} + +TEST(SsoClientTest, GetToStringFailsEmptyStdout) { + auto exec = [&](Command&&, const std::string*, std::string*, std::string*, + SubprocessOptions) { return 0; }; + SsoClient client(exec); + + auto result = client.GetToString("https://some.url"); + + EXPECT_FALSE(result.ok()); +} + +TEST(SsoClientTest, GetToStringFailsExecutionFails) { + std::string stdout_ = "foo"; + std::string stderr_ = "bar"; + auto exec = [&](Command&&, const std::string*, std::string* out, + std::string* err, SubprocessOptions) { + *out = stdout_; + *err = stderr_; + return -1; + }; + SsoClient client(exec); + + auto result = client.GetToString("https://some.url"); + + EXPECT_FALSE(result.ok()); + EXPECT_TRUE(result.error().Message().find(stdout_) != std::string::npos); + EXPECT_TRUE(result.error().Message().find(stderr_) != std::string::npos); +} + +} // namespace http_client +} // namespace cuttlefish diff --git a/base/cvd/cvd_server.proto b/base/cvd/cvd_server.proto new file mode 100644 index 0000000000..299050c6ac --- /dev/null +++ b/base/cvd/cvd_server.proto @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2021 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. + */ + +syntax = "proto3"; + +package cuttlefish.cvd; + +message Status { + // Subset of status codes from gRPC. + enum Code { + OK = 0; + FAILED_PRECONDITION = 9; + INTERNAL = 13; + } + + Code code = 1; + string message = 2; +} + +message Request { + oneof contents { + // Returns the version of the CvdServer. + VersionRequest version_request = 1; + // Requests the CvdServer to shutdown. + ShutdownRequest shutdown_request = 2; + // Requests the CvdServer to execute a command on behalf of the client. + CommandRequest command_request = 3; + } + string verbosity = 4; +} + +message Response { + Status status = 1; + oneof contents { + VersionResponse version_response = 2; + ShutdownResponse shutdown_response = 3; + CommandResponse command_response = 4; + } +} + +message Version { + int32 major = 1; + int32 minor = 2; + string build = 3; + uint32 crc32 = 4; +} + +message VersionRequest {} +message VersionResponse { + Version version = 1; +} + +message ShutdownRequest { + // If true, clears instance and assembly state before shutting down. + bool clear = 1; +} +message ShutdownResponse {} + +enum WaitBehavior { + WAIT_BEHAVIOR_UNKNOWN = 0; + WAIT_BEHAVIOR_START = 1; + WAIT_BEHAVIOR_COMPLETE = 2; +} + +// the arguments that are used by selector inside the server +message SelectorOption { + repeated string args = 1; +} + +message CommandRequest { + // The args that should be executed, including the subcommand. + repeated string args = 1; + // Environment variables that will be used by the subcommand. + map env = 2; + string working_directory = 3; + WaitBehavior wait_behavior = 4; + SelectorOption selector_opts = 5; +} + +/* + * The fields are required to be filled only for a successful "cvd start" cmd + */ +message InstanceGroupInfo { + string group_name = 1; + message PerInstanceInfo { + string name = 1; + uint32 instance_id = 2; + } + repeated PerInstanceInfo instances = 2; + repeated string home_directories = 3; +} + +message CommandResponse { + oneof response_report { + InstanceGroupInfo instance_group_info = 1; + } +} diff --git a/base/cvd/internal_config.proto b/base/cvd/internal_config.proto new file mode 100644 index 0000000000..782e2ef116 --- /dev/null +++ b/base/cvd/internal_config.proto @@ -0,0 +1,112 @@ +// Copyright 2016 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. + +syntax = "proto2"; + +package cuttlefish.acloud; + +// Default values to use when user did not provide a value. +// Each field should match a field in user_config.UserConfig. +message DefaultUserConfig { + optional string machine_type = 1; + optional string network = 2; + // Default extra data disk size. + optional int32 extra_data_disk_size_gb = 3; + // Metadata for creating Compute Engine instance + // The map will be updated with values from user config. + map metadata_variable = 4; + // [CVD only] The name of the stable host image + optional string stable_host_image_name = 5; + // [CVD only] The project where stable host image is + optional string stable_host_image_project = 6; + // [GOLDFISH only] The name of the stable host image + optional string stable_goldfish_host_image_name = 7; + // [GOLDFISH only] The project where stable host image is + optional string stable_goldfish_host_image_project = 8; + // [CHEEPS only] The name of the stable host image + optional string stable_cheeps_host_image_name = 9; + // [CHEEPS only] The project where stable host image is + optional string stable_cheeps_host_image_project = 10; + // The pattern of the instance name, e.g. ins-{uuid}-{build_id}-{build_target} + // the parts in {} will be automatically replaced with the actual value if + // you specify them in the pattern, uuid will be automatically generated. + optional string instance_name_pattern = 11; + // [CVD only] Version of fetch_cvd to use. + optional string fetch_cvd_version = 12; +} + +// Internal configuration +// TODO: Currently we specify resolutions and orientations +// for all device types in the same config. And all branches are +// using the same config at data/default.config. However, +// each branch only supports a subset of devices. So ideally, +// we should have one config per branch, and only specify supported +// devices for that branch in the config. +message InternalConfig { + optional DefaultUserConfig default_usr_cfg = 1; + // Device resolution + map device_resolution_map = 2; + // Device default orientation + map device_default_orientation_map = 3; + // Minimum gce instance size, e.g. n1-standard-1 + optional string min_machine_size = 4; + // The name of the default disk image, e.g. avd-system.tar.gz + optional string disk_image_name = 5; + // The mime type of the disk image, e.g. 'application/x-tar' + optional string disk_image_mime_type = 6; + // The file extension of disk image, e.g. ".tar.gz" + optional string disk_image_extension = 7; + // The name of the raw image name that should apper in the tar gz file. + // e.g. "disk.raw" + optional string disk_raw_image_name = 8; + // The file extension of a raw linux image file, e.g. "img" + // If file is of this type, it will be compressed to avd-system.tar.gz + optional string disk_raw_image_extension = 9; + // Default data disk device to use when extra_data_disk_size_gb + // is greater than 0 + optional string default_extra_data_disk_device = 10; + // A map from size_gb to the name of a precreated_data_image + map precreated_data_image = 11; + // Branches and corresponding minimum build_ids for which + // this config is valid for. + map valid_branch_and_min_build_id = 12; + + // Path of a file where Oauth2 credential data will be cached. + // For example, ".acloud_oauth2.dat". This file will be created under + // the home directory if the user is authenticated via Oauth2 method. + // The file name by convention usually starts with a dot noting it is + // a hidden file. + optional string creds_cache_file = 13; + // user_agent is a string noting which software it is. + // It is used during the Oauth2 authentication flow. It is okay to + // make up a value, e.g. "acloud". + optional string user_agent = 14; + + // Error messages to be displayed to user when the user + // does not have access to the cloud project. + // Key is the name of the project. + // Value is the error message to show. + map no_project_access_msg_map = 15; + + // [CVD only] The kernel build target: "kernel". This is unlikely to change. + optional string kernel_build_target = 16; + + // [GOLDFISH only] The emulator build target: + // "emulator-linux_x64_nolocationui". It's very unlikely that this will ever + // change. + optional string emulator_build_target = 17; + + // Common hw property + map common_hw_property_map = 18; +} diff --git a/base/cvd/internal_user_log.proto b/base/cvd/internal_user_log.proto new file mode 100644 index 0000000000..2b804e487e --- /dev/null +++ b/base/cvd/internal_user_log.proto @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2023 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. + */ + + syntax = "proto2"; + +package cuttlefish; + +message Duration { + required int64 seconds = 1; + required int32 nanos = 2; +} + +//TODO(moelsherif) : This message is not used yet +enum UserType { + GOOGLE = 0; + EXTERNAL = 1; +} + +// Proto used by Atest CLI Tool for Internal PII Users +message AtestLogEventInternal { + // ------------------------ + // EVENT DEFINITIONS + // ------------------------ + // Occurs immediately upon execution of atest + message AtestStartEvent { + optional string command_line = 1; + repeated string test_references = 2; + optional string cwd = 3; + optional string os = 4; + } + + // Occurs when atest exits for any reason + message AtestExitEvent { + optional Duration duration = 1; + optional int32 exit_code = 2; + optional string stacktrace = 3; + optional string logs = 4; + } + + // Occurs after a SINGLE test reference has been resolved to a test or + // not found + message FindTestFinishEvent { + optional Duration duration = 1; + optional bool success = 2; + optional string test_reference = 3 + ; + repeated string test_finders = 4; + optional string test_info = 5; + } + + // Occurs after the build finishes, either successfully or not. + message BuildFinishEvent { + optional Duration duration = 1; + optional bool success = 2; + repeated string targets = 3; + } + + // Occurs when a single test runner has completed + message RunnerFinishEvent { + optional Duration duration = 1; + optional bool success = 2; + optional string runner_name = 3; + message Test { + optional string name = 1; + optional int32 result = 2; + optional string stacktrace = 3; + } + repeated Test test = 4; + } + + // Occurs after all test runners and tests have finished + message RunTestsFinishEvent { + optional Duration duration = 1; + } + // Occurs after detection of catching bug by atest have finished + message LocalDetectEvent { + optional int32 detect_type = 1; + optional int32 result = 2; + } + + // ------------------------ + // FIELDS FOR ATESTLOGEVENT + // ------------------------ + // user_key and run_id are both python uuid.uuid4() completely anonymous + // and random hexadecimal strings. We cannot use normal google ids + // (zwieback, gaia, etc) because we are an open source command line tool. + // uuid4 provides us a unique key for grouping, which is all we need. + optional string user_key = 1 ; + optional string run_id = 2 ; + optional UserType user_type = 3; + optional string tool_name = 10; + optional string sub_tool_name = 12; + oneof event { + AtestStartEvent atest_start_event = 4; + AtestExitEvent atest_exit_event = 5; + FindTestFinishEvent find_test_finish_event = 6; + BuildFinishEvent build_finish_event = 7; + RunnerFinishEvent runner_finish_event = 8; + RunTestsFinishEvent run_tests_finish_event = 9; + LocalDetectEvent local_detect_event = 11; + } +} diff --git a/base/cvd/launch_cvd.proto b/base/cvd/launch_cvd.proto new file mode 100644 index 0000000000..87855e8022 --- /dev/null +++ b/base/cvd/launch_cvd.proto @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 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. + */ + +syntax = "proto3"; + +package cuttlefish; + +message InstanceDisplay { + int32 width = 1; + int32 height = 2; + int32 dpi = 3; + int32 refresh_rate_hertz = 4; +} +message InstanceDisplays { + repeated InstanceDisplay displays = 1; +} +message InstancesDisplays { + repeated InstanceDisplays instances = 1; +} \ No newline at end of file diff --git a/base/cvd/logging_splitters.h b/base/cvd/logging_splitters.h new file mode 100644 index 0000000000..f602e32fd9 --- /dev/null +++ b/base/cvd/logging_splitters.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2020 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 + +#include +#include + +#include +#include + +#define LOGGER_ENTRY_MAX_PAYLOAD 4068 // This constant is not in the NDK. + +namespace android { +namespace base { + +// This splits the message up line by line, by calling log_function with a pointer to the start of +// each line and the size up to the newline character. It sends size = -1 for the final line. +template +static void SplitByLines(const char* msg, const F& log_function, Args&&... args) { + const char* newline = strchr(msg, '\n'); + while (newline != nullptr) { + log_function(msg, newline - msg, args...); + msg = newline + 1; + newline = strchr(msg, '\n'); + } + + log_function(msg, -1, args...); +} + +// This splits the message up into chunks that logs can process delimited by new lines. It calls +// log_function with the exact null terminated message that should be sent to logd. +// Note, despite the loops and snprintf's, if severity is not fatal and there are no new lines, +// this function simply calls log_function with msg without any extra overhead. +template +static void SplitByLogdChunks(LogId log_id, LogSeverity severity, const char* tag, const char* file, + unsigned int line, const char* msg, const F& log_function) { + // The maximum size of a payload, after the log header that logd will accept is + // LOGGER_ENTRY_MAX_PAYLOAD, so subtract the other elements in the payload to find the size of + // the string that we can log in each pass. + // The protocol is documented in liblog/README.protocol.md. + // Specifically we subtract a byte for the priority, the length of the tag + its null terminator, + // and an additional byte for the null terminator on the payload. We subtract an additional 32 + // bytes for slack, similar to java/android/util/Log.java. + ptrdiff_t max_size = LOGGER_ENTRY_MAX_PAYLOAD - strlen(tag) - 35; + if (max_size <= 0) { + abort(); + } + // If we're logging a fatal message, we'll append the file and line numbers. + bool add_file = file != nullptr && (severity == FATAL || severity == FATAL_WITHOUT_ABORT); + + std::string file_header; + if (add_file) { + file_header = StringPrintf("%s:%u] ", file, line); + } + int file_header_size = file_header.size(); + + __attribute__((uninitialized)) std::vector logd_chunk(max_size + 1); + ptrdiff_t chunk_position = 0; + + auto call_log_function = [&]() { + log_function(log_id, severity, tag, logd_chunk.data()); + chunk_position = 0; + }; + + auto write_to_logd_chunk = [&](const char* message, int length) { + int size_written = 0; + const char* new_line = chunk_position > 0 ? "\n" : ""; + if (add_file) { + size_written = snprintf(logd_chunk.data() + chunk_position, logd_chunk.size() - chunk_position, + "%s%s%.*s", new_line, file_header.c_str(), length, message); + } else { + size_written = snprintf(logd_chunk.data() + chunk_position, logd_chunk.size() - chunk_position, + "%s%.*s", new_line, length, message); + } + + // This should never fail, if it does and we set size_written to 0, which will skip this line + // and move to the next one. + if (size_written < 0) { + size_written = 0; + } + chunk_position += size_written; + }; + + const char* newline = strchr(msg, '\n'); + while (newline != nullptr) { + // If we have data in the buffer and this next line doesn't fit, write the buffer. + if (chunk_position != 0 && chunk_position + (newline - msg) + 1 + file_header_size > max_size) { + call_log_function(); + } + + // Otherwise, either the next line fits or we have any empty buffer and too large of a line to + // ever fit, in both cases, we add it to the buffer and continue. + write_to_logd_chunk(msg, newline - msg); + + msg = newline + 1; + newline = strchr(msg, '\n'); + } + + // If we have left over data in the buffer and we can fit the rest of msg, add it to the buffer + // then write the buffer. + if (chunk_position != 0 && + chunk_position + static_cast(strlen(msg)) + 1 + file_header_size <= max_size) { + write_to_logd_chunk(msg, -1); + call_log_function(); + } else { + // If the buffer is not empty and we can't fit the rest of msg into it, write its contents. + if (chunk_position != 0) { + call_log_function(); + } + // Then write the rest of the msg. + if (add_file) { + snprintf(logd_chunk.data(), logd_chunk.size(), "%s%s", file_header.c_str(), msg); + log_function(log_id, severity, tag, logd_chunk.data()); + } else { + log_function(log_id, severity, tag, msg); + } + } +} + +static std::pair CountSizeAndNewLines(const char* message) { + int size = 0; + int new_lines = 0; + while (*message != '\0') { + size++; + if (*message == '\n') { + ++new_lines; + } + ++message; + } + return {size, new_lines}; +} + +// This adds the log header to each line of message and returns it as a string intended to be +// written to stderr. +static std::string StderrOutputGenerator(const struct timespec& ts, int pid, uint64_t tid, + LogSeverity severity, const char* tag, const char* file, + unsigned int line, const char* message) { + struct tm now; + localtime_r(&ts.tv_sec, &now); + char timestamp[sizeof("mm-DD HH:MM:SS.mmm\0")]; + size_t n = strftime(timestamp, sizeof(timestamp), "%m-%d %H:%M:%S", &now); + snprintf(timestamp + n, sizeof(timestamp) - n, ".%03ld", ts.tv_nsec / (1000 * 1000)); + + static const char log_characters[] = "VDIWEFF"; + static_assert(arraysize(log_characters) - 1 == FATAL + 1, + "Mismatch in size of log_characters and values in LogSeverity"); + char severity_char = log_characters[severity]; + std::string line_prefix; + const char* real_tag = tag ? tag : "nullptr"; + if (file != nullptr) { + line_prefix = StringPrintf("%s %5d %5" PRIu64 " %c %-8s: %s:%u ", timestamp, pid, tid, + severity_char, real_tag, file, line); + } else { + line_prefix = + StringPrintf("%s %5d %5" PRIu64 " %c %-8s: ", timestamp, pid, tid, severity_char, real_tag); + } + + auto [size, new_lines] = CountSizeAndNewLines(message); + std::string output_string; + output_string.reserve(size + new_lines * line_prefix.size() + 1); + + auto concat_lines = [&](const char* message, int size) { + output_string.append(line_prefix); + if (size == -1) { + output_string.append(message); + } else { + output_string.append(message, size); + } + output_string.append("\n"); + }; + SplitByLines(message, concat_lines); + return output_string; +} + +} // namespace base +} // namespace android diff --git a/base/cvd/meson.build b/base/cvd/meson.build new file mode 100644 index 0000000000..fe3d598eb4 --- /dev/null +++ b/base/cvd/meson.build @@ -0,0 +1,288 @@ +project('cvd', 'cpp', default_options: ['cpp_std=gnu++17']) + +common_sources = [ + 'android-base/file.cpp', + 'android-base/parsebool.cpp', + 'android-base/posix_strerror_r.cpp', + 'android-base/stringprintf.cpp', + 'android-base/strings.cpp', + 'android-base/threads.cpp', + 'android-base/logging.cpp', + 'cuttlefish/common/libs/fs/epoll.cpp', + 'cuttlefish/common/libs/fs/shared_buf.cc', + 'cuttlefish/common/libs/fs/shared_fd.cpp', + 'cuttlefish/common/libs/utils/archive.cpp', + 'cuttlefish/common/libs/utils/base64.cpp', + 'cuttlefish/common/libs/utils/environment.cpp', + 'cuttlefish/common/libs/utils/files.cpp', + 'cuttlefish/common/libs/utils/flag_parser.cpp', + 'cuttlefish/common/libs/utils/flags_validator.cpp', + 'cuttlefish/common/libs/utils/inotify.cpp', + 'cuttlefish/common/libs/utils/json.cpp', + 'cuttlefish/common/libs/utils/proc_file_utils.cpp', + 'cuttlefish/common/libs/utils/result.cpp', + 'cuttlefish/common/libs/utils/shared_fd_flag.cpp', + 'cuttlefish/common/libs/utils/subprocess.cpp', + 'cuttlefish/common/libs/utils/tee_logging.cpp', + 'cuttlefish/common/libs/utils/unix_sockets.cpp', + 'cuttlefish/common/libs/utils/users.cpp', + 'cuttlefish/host/libs/config/config_utils.cpp', + 'cuttlefish/host/libs/config/fetcher_config.cpp', + 'cuttlefish/host/libs/config/host_tools_version.cpp', + 'cuttlefish/host/libs/config/instance_nums.cpp', + 'cuttlefish/host/libs/web/android_build_api.cpp', + 'cuttlefish/host/libs/web/android_build_string.cpp', + 'cuttlefish/host/libs/web/credential_source.cc', + 'cuttlefish/host/libs/web/http_client/http_client.cc', + 'cuttlefish/host/libs/web/http_client/http_client_util.cc', + 'cuttlefish/host/libs/web/http_client/sso_client.cc', +] + +cvd_sources = [ + 'cuttlefish/host/commands/cvd/acloud/config.cpp', + 'cuttlefish/host/commands/cvd/acloud/converter.cpp', + 'cuttlefish/host/commands/cvd/acloud/create_converter_parser.cpp', + 'cuttlefish/host/commands/cvd/client.cpp', + 'cuttlefish/host/commands/cvd/command_sequence.cpp', + 'cuttlefish/host/commands/cvd/common_utils.cpp', + 'cuttlefish/host/commands/cvd/driver_flags.cpp', + 'cuttlefish/host/commands/cvd/epoll_loop.cpp', + 'cuttlefish/host/commands/cvd/fetch/fetch_cvd.cc', + 'cuttlefish/host/commands/cvd/fetch/fetch_cvd_parser.cc', + 'cuttlefish/host/commands/cvd/flag.cpp', + 'cuttlefish/host/commands/cvd/frontline_parser.cpp', + 'cuttlefish/host/commands/cvd/handle_reset.cpp', + 'cuttlefish/host/commands/cvd/instance_lock.cpp', + 'cuttlefish/host/commands/cvd/instance_manager.cpp', + 'cuttlefish/host/commands/cvd/interruptible_terminal.cpp', + 'cuttlefish/host/commands/cvd/lock_file.cpp', + 'cuttlefish/host/commands/cvd/logger.cpp', + 'cuttlefish/host/commands/cvd/metrics/metrics_notice.cpp', + 'cuttlefish/host/commands/cvd/parser/cf_configs_common.cpp', + 'cuttlefish/host/commands/cvd/parser/cf_configs_instances.cpp', + 'cuttlefish/host/commands/cvd/parser/cf_flags_validator.cpp', + 'cuttlefish/host/commands/cvd/parser/cf_metrics_configs.cpp', + 'cuttlefish/host/commands/cvd/parser/fetch_config_parser.cpp', + 'cuttlefish/host/commands/cvd/parser/instance/cf_boot_configs.cpp', + 'cuttlefish/host/commands/cvd/parser/instance/cf_disk_configs.cpp', + 'cuttlefish/host/commands/cvd/parser/instance/cf_graphics_configs.cpp', + 'cuttlefish/host/commands/cvd/parser/instance/cf_security_configs.cpp', + 'cuttlefish/host/commands/cvd/parser/instance/cf_streaming_configs.cpp', + 'cuttlefish/host/commands/cvd/parser/instance/cf_vm_configs.cpp', + 'cuttlefish/host/commands/cvd/parser/launch_cvd_parser.cpp', + 'cuttlefish/host/commands/cvd/parser/launch_cvd_templates.cpp', + 'cuttlefish/host/commands/cvd/parser/load_configs_parser.cpp', + 'cuttlefish/host/commands/cvd/parser/selector_parser.cpp', + 'cuttlefish/host/commands/cvd/request_context.cpp', + 'cuttlefish/host/commands/cvd/reset_client_utils.cpp', + 'cuttlefish/host/commands/cvd/run_cvd_proc_collector.cpp', + 'cuttlefish/host/commands/cvd/run_server.cpp', + 'cuttlefish/host/commands/cvd/selector/arguments_lexer.cpp', + 'cuttlefish/host/commands/cvd/selector/arguments_separator.cpp', + 'cuttlefish/host/commands/cvd/selector/creation_analyzer.cpp', + 'cuttlefish/host/commands/cvd/selector/device_selector_utils.cpp', + 'cuttlefish/host/commands/cvd/selector/group_selector.cpp', + 'cuttlefish/host/commands/cvd/selector/instance_database.cpp', + 'cuttlefish/host/commands/cvd/selector/instance_database_impl.cpp', + 'cuttlefish/host/commands/cvd/selector/instance_database_types.cpp', + 'cuttlefish/host/commands/cvd/selector/instance_database_utils.cpp', + 'cuttlefish/host/commands/cvd/selector/instance_group_record.cpp', + 'cuttlefish/host/commands/cvd/selector/instance_record.cpp', + 'cuttlefish/host/commands/cvd/selector/instance_selector.cpp', + 'cuttlefish/host/commands/cvd/selector/selector_common_parser.cpp', + 'cuttlefish/host/commands/cvd/selector/selector_constants.cpp', + 'cuttlefish/host/commands/cvd/selector/start_selector_parser.cpp', + 'cuttlefish/host/commands/cvd/server.cc', + 'cuttlefish/host/commands/cvd/server_client.cpp', + 'cuttlefish/host/commands/cvd/server_command/acloud_command.cpp', + 'cuttlefish/host/commands/cvd/server_command/acloud_common.cpp', + 'cuttlefish/host/commands/cvd/server_command/acloud_mixsuperimage.cpp', + 'cuttlefish/host/commands/cvd/server_command/acloud_translator.cpp', + 'cuttlefish/host/commands/cvd/server_command/cmd_list.cpp', + 'cuttlefish/host/commands/cvd/server_command/display.cpp', + 'cuttlefish/host/commands/cvd/server_command/env.cpp', + 'cuttlefish/host/commands/cvd/server_command/fetch.cpp', + 'cuttlefish/host/commands/cvd/server_command/flags_collector.cpp', + 'cuttlefish/host/commands/cvd/server_command/fleet.cpp', + 'cuttlefish/host/commands/cvd/server_command/generic.cpp', + 'cuttlefish/host/commands/cvd/server_command/handler_proxy.cpp', + 'cuttlefish/host/commands/cvd/server_command/help.cpp', + 'cuttlefish/host/commands/cvd/server_command/host_tool_target.cpp', + 'cuttlefish/host/commands/cvd/server_command/host_tool_target_manager.cpp', + 'cuttlefish/host/commands/cvd/server_command/lint.cpp', + 'cuttlefish/host/commands/cvd/server_command/load_configs.cpp', + 'cuttlefish/host/commands/cvd/server_command/power.cpp', + 'cuttlefish/host/commands/cvd/server_command/reset.cpp', + 'cuttlefish/host/commands/cvd/server_command/restart.cpp', + 'cuttlefish/host/commands/cvd/server_command/serial_launch.cpp', + 'cuttlefish/host/commands/cvd/server_command/serial_preset.cpp', + 'cuttlefish/host/commands/cvd/server_command/shutdown.cpp', + 'cuttlefish/host/commands/cvd/server_command/snapshot.cpp', + 'cuttlefish/host/commands/cvd/server_command/start.cpp', + 'cuttlefish/host/commands/cvd/server_command/status.cpp', + 'cuttlefish/host/commands/cvd/server_command/status_fetcher.cpp', + 'cuttlefish/host/commands/cvd/server_command/subprocess_waiter.cpp', + 'cuttlefish/host/commands/cvd/server_command/try_acloud.cpp', + 'cuttlefish/host/commands/cvd/server_command/utils.cpp', + 'cuttlefish/host/commands/cvd/server_command/version.cpp', + 'cuttlefish/host/commands/cvd/server_constants.cpp', + 'cuttlefish/host/commands/cvd/types.cpp', +] + +allocd_sources = [ + 'allocd/alloc_utils.cpp', + 'allocd/allocd.cpp', + 'allocd/resource.cpp', + 'allocd/resource_manager.cpp', + 'allocd/utils.cpp', +] + +cc = meson.get_compiler('cpp') + +protoc = find_program('protoc', required : true) +deps = dependency('protobuf', required : true) +protoc_generator = generator(protoc, + output : ['@BASENAME@.pb.cc', '@BASENAME@.pb.h'], + arguments : ['--proto_path=@CURRENT_SOURCE_DIR@', '--cpp_out=@BUILD_DIR@', '@INPUT@']) + +server = protoc_generator.process('cvd_server.proto') +server_dependency = declare_dependency(sources: server) + +internal_config = protoc_generator.process('internal_config.proto') +internal_config_dependency = declare_dependency(sources: internal_config) + +launch_cvd_proto = protoc_generator.process('launch_cvd.proto') +launch_cvd_proto_dependency = declare_dependency(sources: launch_cvd_proto) + +user_config = protoc_generator.process('user_config.proto') +user_config_dependency = declare_dependency(sources: user_config) + +client_analytics_proto = protoc_generator.process('clientanalytics.proto') +client_analytics_proto_dependency = declare_dependency(sources: client_analytics_proto) + +internal_user_log_proto = protoc_generator.process('internal_user_log.proto') +internal_user_log_proto_dependency = declare_dependency(sources: internal_user_log_proto) + +git_version_h = vcs_tag( + command : ['git', 'describe'], + fallback : 'unknown', + input : 'build_version.h.in', + output :'version.h', +) +git_version_h_dependency = declare_dependency(sources: git_version_h) + +dependencies = [ + client_analytics_proto_dependency, + dependency('fmt'), + dependency('gflags'), + dependency('jsoncpp'), + dependency('libcurl'), + dependency('libglog'), + dependency('libxml-2.0'), + dependency('openssl'), + dependency('protobuf'), + dependency('uuid'), + dependency('zlib'), + git_version_h_dependency, + internal_config_dependency, + internal_user_log_proto_dependency, + launch_cvd_proto_dependency, + server_dependency, + user_config_dependency, +] + +inc_dirs = [ + '/usr/local/include', + 'cuttlefish', +] + +libcvd = static_library( + 'libcvd', + cpp_args: ['-Wno-reorder', '-Wno-unknown-pragmas', '-Wno-attributes', '-Wno-sign-compare', '-Wno-write-strings', '-DNODISCARD_EXPECTED=true'], + sources: common_sources + cvd_sources, + dependencies: dependencies, + include_directories: inc_dirs, +) +libcvd_dep = declare_dependency( + link_with : libcvd, + include_directories: inc_dirs, +) + +executable( + 'cvd', + cpp_args: ['-Wno-reorder', '-Wno-unknown-pragmas', '-Wno-attributes', '-Wno-sign-compare', '-Wno-write-strings', '-DNODISCARD_EXPECTED=true'], + link_args: ['-pthread'], + sources: [ + 'cuttlefish/host/commands/cvd/main.cc', + ], + dependencies: dependencies + [libcvd_dep], + include_directories: inc_dirs, +) + +test_dependencies = [ + dependency('gtest'), + dependency('gmock'), + dependency('gtest_main'), +] + +cvd_test = executable( + 'cvd_tests', + cpp_args: ['-Wno-reorder', '-Wno-unknown-pragmas', '-Wno-attributes', '-Wno-sign-compare', '-Wno-write-strings', '-DNODISCARD_EXPECTED=true'], + link_args: ['-pthread'], + sources: [ + 'cuttlefish/common/libs/fs/shared_fd_test.cpp', + 'cuttlefish/common/libs/utils/flag_parser_test.cpp', + 'cuttlefish/common/libs/utils/proc_file_utils_test.cpp', + 'cuttlefish/common/libs/utils/result_matchers.h', + 'cuttlefish/common/libs/utils/result_test.cpp', + 'cuttlefish/common/libs/utils/unique_resource_allocator_test.cpp', + 'cuttlefish/common/libs/utils/unique_resource_allocator_test.h', + 'cuttlefish/common/libs/utils/unix_sockets_test.cpp', + 'cuttlefish/host/commands/cvd/unittests/parser/configs_inheritance_test.cc', + 'cuttlefish/host/commands/cvd/unittests/parser/flags_parser_test.cc', + 'cuttlefish/host/commands/cvd/unittests/parser/instance/boot_configs_test.cc', + 'cuttlefish/host/commands/cvd/unittests/parser/instance/disk_configs_test.cc', + 'cuttlefish/host/commands/cvd/unittests/parser/instance/graphics_configs_test.cc', + 'cuttlefish/host/commands/cvd/unittests/parser/instance/vm_configs_test.cc', + 'cuttlefish/host/commands/cvd/unittests/parser/metrics_configs_test.cc', + 'cuttlefish/host/commands/cvd/unittests/parser/test_common.h', + 'cuttlefish/host/commands/cvd/unittests/parser/test_common.cc', + 'cuttlefish/host/commands/cvd/unittests/selector/client_lexer_helper.h', + 'cuttlefish/host/commands/cvd/unittests/selector/client_lexer_helper.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/client_lexer_test.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_helper.h', + 'cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_helper.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_test.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/group_record_test.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/host_tool_target_test.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.h', + 'cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/instance_database_test.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/instance_record_test.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/parser_ids_helper.h', + 'cuttlefish/host/commands/cvd/unittests/selector/parser_ids_helper.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/parser_ids_test.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/parser_names_helper.h', + 'cuttlefish/host/commands/cvd/unittests/selector/parser_names_helper.cpp', + 'cuttlefish/host/commands/cvd/unittests/selector/parser_names_test.cpp', + 'cuttlefish/host/commands/cvd/unittests/server/frontline_parser_test.cpp', + 'cuttlefish/host/commands/cvd/unittests/server/utils.h', + 'cuttlefish/host/libs/web/http_client/unittest/http_client_util_test.cc', + 'cuttlefish/host/libs/web/http_client/unittest/main_test.cc', + 'cuttlefish/host/libs/web/http_client/unittest/sso_client_test.cc', + ], + dependencies: dependencies + [libcvd_dep] + test_dependencies, + include_directories: inc_dirs, +) +test('cvd_test', cvd_test) + + +executable( + 'allocd', + cpp_args: ['-Wno-reorder', '-Wno-unknown-pragmas', '-Wno-attributes', '-Wno-sign-compare', '-Wno-write-strings', '-DNODISCARD_EXPECTED=true'], + link_args: ['-pthread'], + sources: common_sources + allocd_sources, + dependencies: dependencies, + include_directories: inc_dirs, +) + diff --git a/base/cvd/user_config.proto b/base/cvd/user_config.proto new file mode 100644 index 0000000000..c1a18ddcde --- /dev/null +++ b/base/cvd/user_config.proto @@ -0,0 +1,139 @@ +// Copyright 2016 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. + +syntax = "proto2"; + +package cuttlefish.acloud; + +// Hold configurations from user. +message UserConfig { + // Account information for accessing Cloud API + optional string service_account_name = 1; + optional string service_account_private_key_path = 2; + + // Compute Engine project name + optional string project = 3; + // Compute Engine zone name, e.g. "us-central1-f" + optional string zone = 4; + optional string machine_type = 5; + // Compute Engine network name, e.g. "default" + optional string network = 6; + + // SSH key configuration + optional string ssh_private_key_path = 7; + optional string ssh_public_key_path = 8; + + // Storage configuration + optional string storage_bucket_name = 9; + + // Desired orientation, e.g. 'portrait' or 'landscape' + optional string orientation = 10; + // Desired resolution + optional string resolution = 11; + // Size of extra data disk. + optional int32 extra_data_disk_size_gb = 12; + // Metadata for creating Compute Engine instance + map metadata_variable = 13; + + // client_id and client secret are required when user authenticates via + // Oauth2 flow with their user account (not service account). + // * They are created in the cloud project console -> API manager. + // * They are used to authorize the app to talk to the cloud project + // on behalf of the user. + // * They by themselves do not authenticate the user. + // * They are stored as plain text in the configuration file so they are + // not that secret. Generally, we should not share it with people we + // don't trust. + // * All users talking to the same cloud project can share the same + // client_id and client_secret. + optional string client_id = 14; + optional string client_secret = 15; + + // [CVD only] The name of the stable host image released by Cloud Android team + optional string stable_host_image_name = 16; + // [CVD only] The name of the host image family released by Cloud Android team + optional string stable_host_image_family = 17; + // [CVD only] The project that the stable host image is released to + optional string stable_host_image_project = 18; + + // [GOLDFISH only] The name of the stable host image released by Android + // Emulator (emu-dev) team + optional string stable_goldfish_host_image_name = 19; + // [GOLDFISH only] The project that the stable goldfish host image is + // released to (emu-dev-cts) + + optional string stable_goldfish_host_image_project = 20; + + // Account information for accessing Cloud API + // This is the new way to provide service account auth. + optional string service_account_json_private_key_path = 21; + + // Desired hw_property + optional string hw_property = 22; + + // [CHEEPS only] The name of the stable host image released by the ARC + // (arc-eng) team + optional string stable_cheeps_host_image_name = 23; + // [CHEEPS only] The project that the stable host image is released to + optional string stable_cheeps_host_image_project = 24; + + // [CVD only] It will get passed into the launch_cvd command if not empty. + // In version 0.7.2 and later. + optional string launch_args = 25; + + // The pattern of the instance name, e.g. ins-{uuid}-{build_id}-{build_target} + // the parts in {} will be automatically replaced with the actual value if + // you specify them in the pattern, uuid will be automatically generated. + optional string instance_name_pattern = 26; + + // List of scopes that will be given to the instance + // https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#changeserviceaccountandscopes + repeated string extra_scopes = 27; + + // Provide some additional parameters to build the ssh tunnel. + optional string extra_args_ssh_tunnel = 28; + + // [CVD only] Version of fetch_cvd to use. + optional string fetch_cvd_version = 29; + + // [CVD only] Enable multi stage function. + optional bool enable_multi_stage = 30; + + // [CHEEPS only] The name of the L1 betty image (used with Cheeps controller) + optional string betty_image = 31; + + // [Oxygen only] The OAuth Credentials of API key. + optional string api_key = 32; + + // [Oxygen only] The API service url. + optional string api_url = 33; + + // [Oxygen only] The client to call oxygen api. + optional string oxygen_client = 34; + + // [Oxygen only] The args append to lease command. + optional string oxygen_lease_args = 35; + + // Storage options of created GCP instance, e.g. pd-standard, pd-ssd. + optional string disk_type = 36; + + // [CVD only] Ssh connect with hostname. + optional bool connect_hostname = 37; + + // [CVD only] [DEPRECATED]. + optional bool use_cvdr = 38; + + // [CVD only] Opt out from using cvdr. + optional bool use_legacy_acloud = 39; +} diff --git a/base/debian/changelog b/base/debian/changelog index eb97d42849..1405f91e29 100644 --- a/base/debian/changelog +++ b/base/debian/changelog @@ -1,10 +1,18 @@ -cuttlefish-common (0.9.27) UNRELEASED; urgency=medium +cuttlefish-common (0.9.28) UNRELEASED; urgency=medium + + * Include the cvd command in the base package + + * Explicitly specify dependency on iproute2 + + -- Jorge E. Moreira Wed, 31 Jan 2024 11:40:30 -0700 + +cuttlefish-common (0.9.27) unstable; urgency=medium * Increase nofile soft limit to support passthrough GPU modes -- Jason Macnak Tue, 23 May 2023 08:58:58 -0700 -cuttlefish-common (0.9.26) UNRELEASED; urgency=medium +cuttlefish-common (0.9.26) stable; urgency=medium * Change operator's web UI to tile UI diff --git a/base/debian/control b/base/debian/control index 2387477c19..730220d481 100644 --- a/base/debian/control +++ b/base/debian/control @@ -4,6 +4,22 @@ Section: misc Priority: optional Build-Depends: config-package-dev, debhelper-compat (= 12), + clang, + pkg-config, + meson, + libfmt-dev, + libgflags-dev, + libjsoncpp-dev, + libcurl4-openssl-dev, + libgoogle-glog-dev, + libgtest-dev, + libssl-dev, + libxml2-dev, + uuid-dev, + libprotobuf-dev, + libprotobuf-c-dev, + protobuf-compiler, + libz3-dev Standards-Version: 4.5.0 Package: cuttlefish-base @@ -11,27 +27,38 @@ Architecture: any Depends: adduser, binfmt-support [arm64], bridge-utils, + curl, dnsmasq-base, e2fsprogs, + ebtables-legacy | ebtables, f2fs-tools, grub-efi-arm64-bin [arm64], grub-efi-ia32-bin [!arm64], + iproute2, iptables, libarchive-tools | bsdtar, + libcurl4, libdrm2, libfdt1, + libfmt-dev, + libgflags-dev, libgl1, + libjsoncpp-dev, + libprotobuf-dev, + libssl-dev, libusb-1.0-0, libwayland-client0, libwayland-server0, libx11-6, libxext6, + libxml2-dev, + libz3-4, lsb-base, net-tools, + openssl, python3, - util-linux, qemu-user-static [arm64], - ebtables-legacy | ebtables, + util-linux, ${misc:Depends}, ${shlibs:Depends} Pre-Depends: ${misc:Pre-Depends} diff --git a/base/debian/cuttlefish-base.install b/base/debian/cuttlefish-base.install index 11fa0dec06..334f02783d 100644 --- a/base/debian/cuttlefish-base.install +++ b/base/debian/cuttlefish-base.install @@ -2,3 +2,4 @@ host/deploy/install_zip.sh /usr/bin/ host/deploy/unpack_boot_image.py /usr/lib/cuttlefish-common/bin/ host/deploy/capability_query.py /usr/lib/cuttlefish-common/bin/ host/packages/cuttlefish-base/* / +cvd/build/cvd /usr/lib/cuttlefish-common/bin/ diff --git a/base/debian/cuttlefish-base.links b/base/debian/cuttlefish-base.links new file mode 100644 index 0000000000..5899e7d17a --- /dev/null +++ b/base/debian/cuttlefish-base.links @@ -0,0 +1 @@ +/usr/lib/cuttlefish-common/bin/cvd /usr/bin/cvd diff --git a/base/debian/rules b/base/debian/rules index b7e1cf6626..c9849e4766 100755 --- a/base/debian/rules +++ b/base/debian/rules @@ -19,3 +19,7 @@ include /usr/share/dpkg/buildflags.mk override_dh_installinit: dh_installinit --name=cuttlefish-host-resources dh_installinit + +override_dh_auto_build: + mkdir -p cvd/build && rm -rf cvd/build/* && cd cvd/build && meson setup && meson compile + dh_auto_build diff --git a/build_debs.sh b/build_debs.sh new file mode 100644 index 0000000000..d4b69742b6 --- /dev/null +++ b/build_debs.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Copyright (C) 2024 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. + +# Build the debian packages in this repository. + +set -e + +eval $(grep VERSION_CODENAME /etc/os-release) + +[[ ${VERSION_CODENAME} == "bullseye" ]] || { echo "Invalid distribution '${VERSION_CODENAME}'. Use Debian 11 (bullseye)." >&2; exit 1; } + +sudo apt install -y debconf-utils debhelper ubuntu-dev-tools equivs + +dpkg-source -b base +dpkg-source -b frontend + +for dsc in *.dsc; do + yes | sudo mk-build-deps -i "${dsc}" -t apt-get +done + +# Cleanup the `*build-deps_*_all.deb` packages created when installing the build +# dependencies. +yes | rm -f *.deb + +for dsc in *.dsc; do + # Unpack the source and build it + dpkg-source -x "${dsc}" + dir="$(basename "${dsc}" .dsc)" + dir="${dir/_/-}" + pushd "${dir}/" + debuild -uc -us + popd +done + +ls -la diff --git a/docker/Dockerfile b/docker/Dockerfile index 30aa80e5dc..67e8f3f4d8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,6 @@ # This file is based on https://hub.docker.com/r/jrei/systemd-debian/. -FROM debian:buster-slim AS cuttlefish-softgpu +FROM debian:stable-20211011 AS cuttlefish-softgpu ENV container docker ENV LC_ALL C @@ -39,7 +39,7 @@ RUN apt-get update \ && apt-get install --no-install-recommends -y apt-utils sudo vim gawk coreutils \ openssh-server openssh-client psmisc iptables iproute2 dnsmasq \ net-tools rsyslog equivs equivs devscripts dpkg-dev dialog \ - protobuf-compiler # qemu-system-x86 + protobuf-compiler libegl1 libegl-dev # qemu-system-x86 SHELL ["/bin/bash", "-c"] @@ -47,7 +47,7 @@ RUN if test $(uname -m) == aarch64; then \ dpkg --add-architecture amd64 \ && apt-get update \ && apt-get install --no-install-recommends -y libc6:amd64 \ - && apt-get install --no-install-recommends -y qemu qemu-user qemu-user-static binfmt-support; \ + && apt-get install --no-install-recommends -y qemu-system-arm qemu-user qemu-user-static binfmt-support; \ fi # host packages and google-chrome (google-chrome*.deb) @@ -57,9 +57,11 @@ RUN cd /root/android-cuttlefish/out \ && apt-get install --no-install-recommends -y -f ./cuttlefish-base_*.deb \ && apt-get install --no-install-recommends -y -f ./cuttlefish-user_*.deb \ && apt-get install --no-install-recommends -y -f ./cuttlefish-common_*.deb \ + && apt-get install --no-install-recommends -y -f ./cuttlefish-orchestration_*.deb \ && rm -rvf ./cuttlefish-base_*.deb \ && rm -rvf ./cuttlefish-user_*.deb \ && rm -rvf ./cuttlefish-common_*.deb \ + && rm -rvf ./cuttlefish-orchestration_*.deb \ && cd /root # to share X with the local docker host diff --git a/docker/arm-server/Dockerfile b/docker/arm-server/Dockerfile new file mode 100644 index 0000000000..7df19cfeb1 --- /dev/null +++ b/docker/arm-server/Dockerfile @@ -0,0 +1,49 @@ +# Docker image for running CF instances in ARM64 server. +# Docker image includes HO(Host Orchestrator) inside, +# so it could execute CF instance with API in HO. +FROM debian:stable-20211011 AS cuttlefish-arm64 + +# Expose Operator Port (HTTP:1080, HTTPS:1443) +EXPOSE 1080 1443 +# Expose HO(Host Orchestrator) Port (HTTP:2080, HTTPS:2443) +EXPOSE 2080 2443 +# Expose WebRTC Port +EXPOSE 15550-15560 +# Expose ADB Port +# Corresponding ADB port for CF instance is, 6520+instance_num-1. +EXPOSE 6520-6620 + +USER root + +RUN set -x + +# Install dependants of CF debian packages. +RUN apt update +RUN apt install -y --no-install-recommends \ + ca-certificates \ + curl \ + sudo +RUN update-ca-certificates + +# ADD CF debian packages built by +# docker/debs-builder-docker/build-debs-with-docker.sh. +COPY ./out/cuttlefish-base_*.deb /root/ +COPY ./out/cuttlefish-user_*.deb /root/ +COPY ./out/cuttlefish-orchestration_*.deb /root/ + +# Install CF debian packages. +WORKDIR /root +RUN apt install -y -f \ + /root/cuttlefish-base_*.deb \ + /root/cuttlefish-user_*.deb \ + /root/cuttlefish-orchestration_*.deb + +RUN echo "num_cvd_accounts=100" >> /etc/default/cuttlefish-host-resources + +RUN usermod -aG kvm root +RUN usermod -aG cvdnetwork root + +COPY ./run_services.sh /run_services.sh +RUN chmod +x /run_services.sh + +ENTRYPOINT ["/run_services.sh"] diff --git a/docker/arm-server/build.sh b/docker/arm-server/build.sh new file mode 100755 index 0000000000..b1bbaef00f --- /dev/null +++ b/docker/arm-server/build.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# This shell script exists for building ARM64 docker image. +# Docker image includes HO(Host Orchestrator) inside, +# so it could execute CF instance with API in HO. + +# Build debian packages(e.g. cuttlefish-base) with docker. +pushd .. +./debs-builder-docker/build-debs-with-docker.sh +popd +mkdir out +mv ../out/cuttlefish-*.deb ./out + +# Build docker image +docker build --no-cache -t cuttlefish-arm64 $PWD + +# Cleanup out directory +rm -rf out diff --git a/docker/arm-server/run.sh b/docker/arm-server/run.sh new file mode 100755 index 0000000000..99488dcbbc --- /dev/null +++ b/docker/arm-server/run.sh @@ -0,0 +1,2 @@ +# Shell script to run docker image. +docker run --privileged -d -P -it cuttlefish-arm64 diff --git a/docker/arm-server/run_services.sh b/docker/arm-server/run_services.sh new file mode 100644 index 0000000000..402120d023 --- /dev/null +++ b/docker/arm-server/run_services.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +service cuttlefish-host-resources start +service cuttlefish-operator start +service cuttlefish-host_orchestrator start + +if [ -f /root/cvd_home/bin/cvd ]; then + /root/cvd_home/bin/cvd start \ + --vhost-user-vsock=true \ + --report_anonymous_usage_stats=y \ + $@ +fi +# To keep it running +tail -f /dev/null diff --git a/docker/debs-builder-docker/build-debs-with-docker.sh b/docker/debs-builder-docker/build-debs-with-docker.sh index 049ab8c1a9..975384f77d 100755 --- a/docker/debs-builder-docker/build-debs-with-docker.sh +++ b/docker/debs-builder-docker/build-debs-with-docker.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# at /path/to/android-cuttlefish, run like this: +# at /path/to/android-cuttlefish/docker, run like this: # ./debs-builder-docker/build-debs-docker.sh # diff --git a/docker/setup.sh b/docker/setup.sh index 5e5d5b658b..1087222045 100755 --- a/docker/setup.sh +++ b/docker/setup.sh @@ -52,7 +52,7 @@ function cf_allocate_instance_id { done local sorted; IFS=$'\n' sorted=($(sort -n <<<"${ids[*]}")); unset IFS - local prev=0 + local prev=1 for id in ${sorted[@]}; do if [[ "${prev}" -lt "${id}" ]]; then break; @@ -349,7 +349,7 @@ function cf_docker_create { echo "Container ${name} does not exist."; local cf_instance=$(cf_allocate_instance_id) - if [ "${cf_instance}" -gt 7 ]; then + if [ "${cf_instance}" -gt 8 ]; then echo "Limit is maximum 8 Cuttlefish instances." return fi @@ -362,15 +362,15 @@ function cf_docker_create { if [[ -d "${android}" ]]; then echo "Setting up Android images from ${android} in ${home}." if [[ $(compgen -G "${android}"/*.img) != "${android}/*.img" ]]; then - for f in "${android}"/*.img; do - volumes+=("-v ${f}:/home/vsoc-01/$(basename ${f}):rw") - done + for f in "${android}"/*.img; do + cp "${f}" "${home}" + done else echo "WARNING: No Android images in ${android}." fi - if [ -f "${android}/bootloader" ]; then - volumes+=("-v ${android}/bootloader:/home/vsoc-01/bootloader:rw") - fi + if [ -f "${android}/bootloader" ]; then + cp ${android}/bootloader ${home} + fi fi if [[ -f "${cuttlefish}" || -d "${android}" ]]; then volumes+=("-v ${home}:/home/vsoc-01:rw") @@ -472,7 +472,7 @@ function cf_docker_rm { local ip_addr_var_name="ip_${name}" unset ${ip_addr_var_name} - if [ -n "$(docker ps -q -a -f name=${name})" ]; then + if [ -n "$(docker ps -a -f name=${name})" ]; then homedir=$(cf_gethome_${name}) echo "Deleting container ${name}." docker rm -f ${name} @@ -528,7 +528,7 @@ function __gen_gethome_func_name { function __gen_funcs { local name=$1 local instance_id=$(cf_get_instance_id ${name}) - local vcid_opt="--base_instance_num=${instance_id}" + local vcid_opt local login_func local start_func local stop_func @@ -673,7 +673,7 @@ function cf_clean_autogens() { cf_clean_autogens unset -f cf_clean_autogens -for cf in $(docker ps -q -a --filter="ancestor=cuttlefish" --format "table {{.Names}}" | tail -n+2); do +for cf in $(docker ps -a --filter="ancestor=cuttlefish" --format "table {{.Names}}" | tail -n+2); do __gen_funcs "${cf}" if [ -z "$cf_script" ]; then help_on_container "${cf}" diff --git a/frontend/debian/changelog b/frontend/debian/changelog index 2d0e339b40..c83c673e30 100644 --- a/frontend/debian/changelog +++ b/frontend/debian/changelog @@ -1,10 +1,26 @@ -cuttlefish-frontend (0.9.27) UNRELEASED; urgency=medium +cuttlefish-frontend (0.9.29) UNRELEASED; urgency=medium + + * Make cuttlefish-orchestration depend on cuttlefish-user + + -- Jorge Moreira Thu, 12 Oct 2023 18:12:35 -0700 + +cuttlefish-frontend (0.9.28) stable; urgency=medium + + * Replace operator functionality with reverse proxy in host operator + + * Make operator and host_orchestrator configurable via CLI flags + + * Make the host_orchestrator available as a command in cuttlefish-user + + -- Jorge Moreira Thu, 12 Oct 2023 18:12:35 -0700 + +cuttlefish-frontend (0.9.27) unstable; urgency=medium * Increase nofile soft limit to support passthrough GPU modes -- Jason Macnak Tue, 23 May 2023 08:58:58 -0700 -cuttlefish-frontend (0.9.26) UNRELEASED; urgency=medium +cuttlefish-frontend (0.9.26) unstable; urgency=medium * Change operator's web UI to tile UI diff --git a/frontend/debian/control b/frontend/debian/control index 209f607de8..3f6a421869 100644 --- a/frontend/debian/control +++ b/frontend/debian/control @@ -10,7 +10,7 @@ Build-Depends: config-package-dev, protobuf-compiler, Standards-Version: 4.5.0 -Package: cuttlefish-orchestration +Package: cuttlefish-user Architecture: any Depends: cuttlefish-base, adduser, @@ -18,19 +18,18 @@ Depends: cuttlefish-base, ${misc:Depends}, ${shlibs:Depends} Pre-Depends: ${misc:Pre-Depends} -Breaks: cuttlefish-user Description: Cuttlefish Android Virtual Device companion package - Contains the host orchestrator. + Contains the host signaling server supporting multi-device flows + over WebRTC. -Package: cuttlefish-user +Package: cuttlefish-orchestration Architecture: any -Depends: cuttlefish-base, +Depends: cuttlefish-user, adduser, + bash, openssl, ${misc:Depends}, ${shlibs:Depends} Pre-Depends: ${misc:Pre-Depends} -Breaks: cuttlefish-orchestration Description: Cuttlefish Android Virtual Device companion package - Contains the host signaling server supporting multi-device flows - over WebRTC. + Contains the host orchestrator. diff --git a/frontend/debian/cuttlefish-orchestration.cuttlefish-host_orchestrator.default b/frontend/debian/cuttlefish-orchestration.cuttlefish-host_orchestrator.default index 96cc0d0c19..edaa2da07b 100644 --- a/frontend/debian/cuttlefish-orchestration.cuttlefish-host_orchestrator.default +++ b/frontend/debian/cuttlefish-orchestration.cuttlefish-host_orchestrator.default @@ -1,12 +1,16 @@ # defaults for cuttlefish-host_orchestrator +# The network address to listen on for both HTTP and HTTPS. +# Defaults to localhost only. +orchestrator_listen_address=0.0.0.0 +# # The port on which the orchestrator server should listen on for plain HTTP. -# Defaults to 1080. +# Defaults to 2080. # orchestrator_http_port= # # The port on which the orchestrator server should listen on for HTTPS. # The orchestrator won't server over HTTPS if the port is not set. -orchestrator_https_port=1443 +orchestrator_https_port=2443 # # The directory where the TLS certificate needed to establish the HTTPS # communication lives. @@ -23,11 +27,15 @@ orchestrator_https_port=1443 # orchestrator_cvdbin_android_build_target= # # Directory used to store the CVD artifacts. -# Defaults to "/var/lib/cuttlefish-common" -# orchestrator_cvd_artifacts_dir= +# Defaults to "/tmp//cvd_artifacts" +orchestrator_cvd_artifacts_dir=/var/lib/cuttlefish-common # # Where web UI in the application is from # If it isn't empty, the application uses web UI from the url, # else, it uses web pages which is generated during the build # Defaults to "" -# orchestrator_webui_url= +# operator_webui_url= +# +# The port where the operator is to listen on. +# Defaults to 1080. +# operator_http_port= diff --git a/frontend/debian/cuttlefish-orchestration.cuttlefish-host_orchestrator.init b/frontend/debian/cuttlefish-orchestration.cuttlefish-host_orchestrator.init index e74e90097c..e31ab62c51 100755 --- a/frontend/debian/cuttlefish-orchestration.cuttlefish-host_orchestrator.init +++ b/frontend/debian/cuttlefish-orchestration.cuttlefish-host_orchestrator.init @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # ### BEGIN INIT INFO # Provides: cuttlefish-host_orchestrator @@ -39,9 +39,9 @@ orchestrator_tls_cert_dir=${orchestrator_tls_cert_dir:-"/etc/cuttlefish-common/h orchestrator_cvd_artifacts_dir=${orchestrator_cvd_artifacts_dir:-"/var/lib/cuttlefish-common"} RUN_DIR="/run/cuttlefish" -ASSET_DIR="/usr/share/cuttlefish-common/host_orchestrator" -DAEMON="/usr/lib/cuttlefish-common/bin/host_orchestrator" -PIDFILE="${RUN_DIR}"/host_orchestrator.pid +ORCHESTRATOR_BIN="/usr/lib/cuttlefish-common/bin/host_orchestrator" +ORCHESTRATOR_PIDFILE="${RUN_DIR}"/host_orchestrator.pid +ASSET_DIR="/usr/share/cuttlefish-common/operator" gen_cert() { CERT_FILE="${orchestrator_tls_cert_dir}/cert.pem" @@ -68,44 +68,69 @@ set_config_expr() { echo "\${$2+"export $1=\$$2"}" } -start() { - gen_cert +prepare_run_dir() { mkdir -p "${RUN_DIR}" chown _cutf-operator:cvdnetwork "${RUN_DIR}" chmod 775 "${RUN_DIR}" +} +start_orchestrator() { mkdir -p "${orchestrator_cvd_artifacts_dir}" chown _cutf-operator:cvdnetwork "${orchestrator_cvd_artifacts_dir}" - eval $(set_config_expr ORCHESTRATOR_HTTP_PORT orchestrator_http_port) - eval $(set_config_expr ORCHESTRATOR_HTTPS_PORT orchestrator_https_port) - eval $(set_config_expr ORCHESTRATOR_TLS_CERT_DIR orchestrator_tls_cert_dir) - eval $(set_config_expr ORCHESTRATOR_ANDROID_BUILD_URL orchestrator_android_build_url) - eval $(set_config_expr ORCHESTRATOR_CVDBIN_ANDROID_BUILD_ID orchestrator_cvdbin_android_build_id) - eval $(set_config_expr ORCHESTRATOR_CVDBIN_ANDROID_BUILD_TARGET orchestrator_cvdbin_android_build_target) - eval $(set_config_expr ORCHESTRATOR_CVD_ARTIFACTS_DIR orchestrator_cvd_artifacts_dir) - eval $(set_config_expr ORCHESTRATOR_WEBUI_URL orchestrator_webui_url) - - ORCHESTRATOR_SOCKET_PATH="${RUN_DIR}"/operator \ - ORCHESTRATOR_CVD_USER="_cvd-executor" \ + args=() + + if [[ -n "${orchestrator_http_port}" ]]; then + args+=("--http_port=${orchestrator_http_port}") + fi + if [[ -n "${orchestrator_https_port}" ]]; then + args+=("--https_port=${orchestrator_https_port}") + fi + if [[ -n "${orchestrator_tls_cert_dir}" ]]; then + args+=("--tls_cert_dir=${orchestrator_tls_cert_dir}") + fi + if [[ -n "${orchestrator_android_build_url}" ]]; then + args+=("--android_build_url=${orchestrator_android_build_url}") + fi + if [[ -n "${orchestrator_cvdbin_android_build_id}" ]]; then + args+=("--cvd_build_id=${orchestrator_cvdbin_android_build_id}") + fi + if [[ -n "${orchestrator_cvdbin_android_build_target}" ]]; then + args+=("--cvd_build_target=${orchestrator_cvdbin_android_build_target}") + fi + if [[ -n "${orchestrator_cvd_artifacts_dir}" ]]; then + args+=("--cvd_artifacts_dir=${orchestrator_cvd_artifacts_dir}") + fi + if [[ -n "${operator_http_port}" ]]; then + args+=("--operator_http_port=${operator_http_port}") + fi + if [[ -n "${orchestrator_listen_address}" ]]; then + args+=("--listen_addr=${orchestrator_listen_address}") + fi + args+=("--cvd_user=_cvd-executor") + start-stop-daemon --start \ - --pidfile "${PIDFILE}" \ + --pidfile "${ORCHESTRATOR_PIDFILE}" \ --chuid _cutf-operator:cvdnetwork \ --chdir "${ASSET_DIR}" \ --background --no-close \ --make-pidfile \ - --exec "${DAEMON}" + --exec "${ORCHESTRATOR_BIN}" -- "${args[@]}" +} + +start() { + gen_cert + + prepare_run_dir + + start_orchestrator } stop() { start-stop-daemon --stop \ - --pidfile "${PIDFILE}" \ + --pidfile "${ORCHESTRATOR_PIDFILE}" \ --remove-pidfile \ - --exec "${DAEMON}" - # The presence of the socket will cause devices to try to connect to it - # instead of starting their own signaling servers, so it needs to be removed - # once the service isn't running. - unlink "${RUN_DIR}"/operator + --exec "${ORCHESTRATOR_BIN}" } status() { @@ -114,9 +139,9 @@ status() { # 1 if daemon is dead and pid file exists # 3 if daemon is not running # 4 if daemon status is unknown - start-stop-daemon --start --quiet --pidfile "${PIDFILE}" --exec ${DAEMON} --test > /dev/null + start-stop-daemon --start --quiet --pidfile "${ORCHESTRATOR_PIDFILE}" --exec ${ORCHESTRATOR_BIN} --test > /dev/null case "${?}" in - 0) [ -e "${PIDFILE}" ] && return 1 ; return 3 ;; + 0) [ -e "${ORCHESTRATOR_PIDFILE}" ] && return 1 ; return 3 ;; 1) return 0 ;; *) return 4 ;; esac diff --git a/frontend/debian/cuttlefish-orchestration.install b/frontend/debian/cuttlefish-orchestration.install index 5ea13767b7..6dbf6b3f51 100644 --- a/frontend/debian/cuttlefish-orchestration.install +++ b/frontend/debian/cuttlefish-orchestration.install @@ -1,4 +1 @@ host/packages/cuttlefish-orchestration/* / -src/host_orchestrator/host_orchestrator /usr/lib/cuttlefish-common/bin/ -src/operator/webui/dist/static /usr/share/cuttlefish-common/host_orchestrator/ -src/operator/intercept /usr/share/cuttlefish-common/host_orchestrator/ diff --git a/frontend/debian/cuttlefish-orchestration.postinst b/frontend/debian/cuttlefish-orchestration.postinst index e95bdc7de2..124526e5a2 100755 --- a/frontend/debian/cuttlefish-orchestration.postinst +++ b/frontend/debian/cuttlefish-orchestration.postinst @@ -4,16 +4,11 @@ set -e case "$1" in configure) - if ! getent passwd _cutf-operator > /dev/null 2>&1 - then - # The cvdnetwork group is created by cuttlefish-base - adduser --system --disabled-password --disabled-login --home /var/empty \ - --no-create-home --quiet --force-badname --ingroup cvdnetwork _cutf-operator - fi if ! getent passwd _cvd-executor > /dev/null 2>&1 then adduser --system --disabled-password --disabled-login --home /var/empty \ --no-create-home --quiet --force-badname --group _cvd-executor + # The cvdnetwork group is created by cuttlefish-base usermod -a -G cvdnetwork,kvm _cvd-executor fi esac diff --git a/frontend/debian/cuttlefish-user.cuttlefish-operator.default b/frontend/debian/cuttlefish-user.cuttlefish-operator.default index 706165d036..e093c8b3c2 100644 --- a/frontend/debian/cuttlefish-user.cuttlefish-operator.default +++ b/frontend/debian/cuttlefish-user.cuttlefish-operator.default @@ -1,12 +1,16 @@ # defaults for cuttlefish-operator +# The network address to listen on for both HTTP and HTTPS. +# Defaults to all ip addresses. +operator_listen_address=0.0.0.0 +# # The port on which the operator server should listen on for plain HTTP. # Defaults to 1080. # operator_http_port= # # The port on which the operator server should listen on for HTTPS. -# Defaults to 1443. -# operator_https_port= +# Serve over http only when not provided. +operator_https_port=1443 # # The directory where the TLS certificate needed to establish the HTTPS # communication lives. @@ -17,4 +21,4 @@ # If it isn't empty, the application uses web UI from the url, # else, it uses web pages which is generated during the build # Defaults to "" -# operator_webui_url= \ No newline at end of file +# operator_webui_url= diff --git a/frontend/debian/cuttlefish-user.cuttlefish-operator.init b/frontend/debian/cuttlefish-user.cuttlefish-operator.init index a57a9e4040..d9fe656af2 100755 --- a/frontend/debian/cuttlefish-user.cuttlefish-operator.init +++ b/frontend/debian/cuttlefish-user.cuttlefish-operator.init @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # ### BEGIN INIT INFO # Provides: cuttlefish-operator @@ -34,10 +34,6 @@ if [ -f /etc/default/cuttlefish-operator ]; then . /etc/default/cuttlefish-operator fi -operator_http_port=${operator_http_port:-1080} -operator_https_port=${operator_https_port:-1443} -operator_tls_cert_dir=${operator_tls_cert_dir:-"/etc/cuttlefish-common/operator/cert"} -operator_webui_url=${operator_webui_url:-""} RUN_DIR="/run/cuttlefish" ASSET_DIR="/usr/share/cuttlefish-common/operator" @@ -45,6 +41,7 @@ DAEMON="/usr/lib/cuttlefish-common/bin/operator" PIDFILE="${RUN_DIR}"/operator.pid gen_cert() { + operator_tls_cert_dir=${operator_tls_cert_dir:-/etc/cuttlefish-common/operator/cert} CERT_FILE="${operator_tls_cert_dir}/cert.pem" KEY_FILE="${operator_tls_cert_dir}/key.pem" if [ -f "$CERT_FILE" ] && [ -f "$KEY_FILE" ]; then @@ -71,18 +68,31 @@ start() { chown _cutf-operator:cvdnetwork "${RUN_DIR}" chmod 775 "${RUN_DIR}" - OPERATOR_HTTP_PORT="${operator_http_port}" \ - OPERATOR_HTTPS_PORT="${operator_https_port}" \ - OPERATOR_TLS_CERT_DIR="${operator_tls_cert_dir}" \ - OPERATOR_SOCKET_PATH="${RUN_DIR}"/operator \ - OPERATOR_WEBUI_URL="${operator_webui_url}" \ + args=() + if [[ -n "${operator_http_port}" ]]; then + args+=(--http_port="${operator_http_port}") + fi + if [[ -n "${operator_https_port}" ]]; then + args+=(--https_port="${operator_https_port}") + fi + if [[ -n "${operator_tls_cert_dir}" ]]; then + args+=(--tls_cert_dir="${operator_tls_cert_dir}") + fi + args+=(--socket_path="${RUN_DIR}"/operator) + if [[ -n "${operator_webui_url}" ]]; then + args+=(--webui_url="${operator_webui_url}") + fi + if [[ -n "${operator_listen_address}" ]]; then + args+=(--listen_addr="${operator_listen_address}") + fi + start-stop-daemon --start \ --pidfile "${PIDFILE}" \ --chuid _cutf-operator:cvdnetwork \ --chdir "${ASSET_DIR}" \ --background --no-close \ --make-pidfile \ - --exec "${DAEMON}" + --exec "${DAEMON}" -- "${args[@]}" } stop() { diff --git a/frontend/debian/cuttlefish-user.install b/frontend/debian/cuttlefish-user.install index 68b63e8848..49e5e2180d 100644 --- a/frontend/debian/cuttlefish-user.install +++ b/frontend/debian/cuttlefish-user.install @@ -1,3 +1,4 @@ src/operator/operator /usr/lib/cuttlefish-common/bin/ +src/host_orchestrator/host_orchestrator /usr/lib/cuttlefish-common/bin/ src/operator/webui/dist/static /usr/share/cuttlefish-common/operator/ src/operator/intercept /usr/share/cuttlefish-common/operator/ diff --git a/frontend/debian/cuttlefish-user.links b/frontend/debian/cuttlefish-user.links new file mode 100644 index 0000000000..479faa664e --- /dev/null +++ b/frontend/debian/cuttlefish-user.links @@ -0,0 +1 @@ +/usr/lib/cuttlefish-common/bin/host_orchestrator /usr/bin/cvd_host_orchestrator diff --git a/frontend/debian/rules b/frontend/debian/rules index d047c49cd3..6f9a065b4b 100755 --- a/frontend/debian/rules +++ b/frontend/debian/rules @@ -40,4 +40,5 @@ override_dh_installinit: override_dh_auto_clean: rm -f $(ORCHESTRATOR_SOURCE_DIR)/host_orchestrator + rm -f $(OPERATOR_SOURCE_DIR)/operator dh_auto_clean diff --git a/frontend/src/host_orchestrator/constants_arm64.go b/frontend/src/host_orchestrator/constants_arm64.go new file mode 100644 index 0000000000..14eed6bfcd --- /dev/null +++ b/frontend/src/host_orchestrator/constants_arm64.go @@ -0,0 +1,24 @@ +// Copyright 2023 Google LLC +// +// 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 +// +// https://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. + +//go:build arm64 +// +build arm64 + +package main + +const ( + // Build ID relies on aosp-main-throttled + defaultCVDBinAndroidBuildID = "11220041" + defaultCVDBinAndroidBuildTarget = "aosp_cf_arm64_only_phone-trunk_staging-userdebug" +) diff --git a/frontend/src/host_orchestrator/constants_x86_64.go b/frontend/src/host_orchestrator/constants_x86_64.go new file mode 100644 index 0000000000..91fc6b5c3d --- /dev/null +++ b/frontend/src/host_orchestrator/constants_x86_64.go @@ -0,0 +1,24 @@ +// Copyright 2023 Google LLC +// +// 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 +// +// https://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. + +//go:build amd64 +// +build amd64 + +package main + +const ( + // Build ID relies on aosp-main + defaultCVDBinAndroidBuildID = "11219993" + defaultCVDBinAndroidBuildTarget = "aosp_cf_x86_64_phone-trunk_staging-userdebug" +) diff --git a/frontend/src/host_orchestrator/main.go b/frontend/src/host_orchestrator/main.go index 6e86ff4a49..c024b3f738 100644 --- a/frontend/src/host_orchestrator/main.go +++ b/frontend/src/host_orchestrator/main.go @@ -12,51 +12,53 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package exists for running host orchestrator(HO) server. package main import ( + "flag" "fmt" "log" "net/http" "net/http/httputil" "net/url" "os" + "os/user" "path/filepath" "sync" "time" "github.com/google/android-cuttlefish/frontend/src/host_orchestrator/orchestrator" "github.com/google/android-cuttlefish/frontend/src/host_orchestrator/orchestrator/debug" - apiv1 "github.com/google/android-cuttlefish/frontend/src/liboperator/api/v1" - "github.com/google/android-cuttlefish/frontend/src/liboperator/operator" + "github.com/gorilla/mux" "github.com/google/uuid" ) const ( - DefaultSocketPath = "/run/cuttlefish/operator" - DefaultHttpPort = "1080" - DefaultTLSCertDir = "/etc/cuttlefish-common/host_orchestrator/cert" - DefaultStaticFilesDir = "static" // relative path - DefaultInterceptDir = "intercept" // relative path - DefaultWebUIUrl = "" - - defaultAndroidBuildURL = "https://androidbuildinternal.googleapis.com" - defaultCVDBinAndroidBuildID = "10712190" - defaultCVDBinAndroidBuildTarget = "aosp_cf_x86_64_phone-trunk_staging-userdebug" - defaultCVDArtifactsDir = "/var/lib/cuttlefish-common" + defaultTLSCertDir = "/etc/cuttlefish-common/host_orchestrator/cert" + defaultAndroidBuildURL = "https://androidbuildinternal.googleapis.com" + DefaultListenAddress = "127.0.0.1" ) -func startHttpServer(port string) error { - log.Println(fmt.Sprint("Host Orchestrator is listening at http://localhost:", port)) +func defaultCVDArtifactsDir() string { + u, err := user.Current() + if err != nil { + log.Fatalf("Unable to get current user uid: %v", err) + } + return fmt.Sprintf("/tmp/cvd/%s/artifacts", u.Uid) +} + +func startHttpServer(addr string, port int) error { + log.Printf("Host Orchestrator is listening at http://%s:%d", addr, port) // handler is nil, so DefaultServeMux is used. - return http.ListenAndServe(fmt.Sprint(":", port), nil) + return http.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), nil) } -func startHttpsServer(port string, certPath string, keyPath string) error { - log.Println(fmt.Sprint("Host Orchestrator is listening at https://localhost:", port)) - return http.ListenAndServeTLS(fmt.Sprint(":", port), +func startHttpsServer(addr string, port int, certPath string, keyPath string) error { + log.Printf("Host Orchestrator is listening at https://%s:%d", addr, port) + return http.ListenAndServeTLS(fmt.Sprintf("%s:%d", addr, port), certPath, keyPath, // handler is nil, so DefaultServeMux is used. @@ -73,15 +75,6 @@ func fromEnvOrDefault(key string, def string) string { return def } -// Whether a device file request should be intercepted and served from the signaling server instead -func maybeIntercept(path string) *string { - if path == "/js/server_connector.js" { - alt := fmt.Sprintf("%s%s", DefaultInterceptDir, path) - return &alt - } - return nil -} - func start(starters []func() error) { wg := new(sync.WaitGroup) wg.Add(len(starters)) @@ -96,86 +89,87 @@ func start(starters []func() error) { wg.Wait() } +func newOperatorProxy(port int) *httputil.ReverseProxy { + if port <= 0 { + log.Fatal("The host orchestrator requires access to the operator") + } + operatorURL, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", port)) + if err != nil { + log.Fatalf("Invalid operator port (%d): %v", port, err) + } + return httputil.NewSingleHostReverseProxy(operatorURL) +} + func main() { - socketPath := fromEnvOrDefault("ORCHESTRATOR_SOCKET_PATH", DefaultSocketPath) - httpPort := fromEnvOrDefault("ORCHESTRATOR_HTTP_PORT", DefaultHttpPort) - httpsPort := fromEnvOrDefault("ORCHESTRATOR_HTTPS_PORT", "") - tlsCertDir := fromEnvOrDefault("ORCHESTRATOR_TLS_CERT_DIR", DefaultTLSCertDir) - webUIUrlStr := fromEnvOrDefault("ORCHESTRATOR_WEBUI_URL", DefaultWebUIUrl) - certPath := filepath.Join(tlsCertDir, "cert.pem") - keyPath := filepath.Join(tlsCertDir, "key.pem") - cvdUser := fromEnvOrDefault("ORCHESTRATOR_CVD_USER", "") - - pool := operator.NewDevicePool() - polledSet := operator.NewPolledSet() - config := apiv1.InfraConfig{ - Type: "config", - IceServers: []apiv1.IceServer{ - {URLs: []string{"stun:stun.l.google.com:19302"}}, - }, + httpPort := flag.Int("http_port", 2080, "Port to listen on for HTTP requests.") + httpsPort := flag.Int("https_port", -1, "Port to listen on for HTTPS requests.") + tlsCertDir := flag.String("tls_cert_dir", defaultTLSCertDir, "Directory with the TLS certificate.") + cvdUser := flag.String("cvd_user", "", "User to execute cvd as.") + operatorPort := flag.Int("operator_http_port", 1080, "Port where the operator is listening.") + abURL := flag.String("android_build_url", defaultAndroidBuildURL, "URL to an Android Build API.") + cvdBinAndroidBuildID := flag.String("cvd_build_id", defaultCVDBinAndroidBuildID, "Build ID to fetch the cvd binary from.") + cvdBinAndroidBuildTarget := flag.String("cvd_build_target", defaultCVDBinAndroidBuildTarget, "Build target to fetch the cvd binary from.") + imRootDir := flag.String("cvd_artifacts_dir", defaultCVDArtifactsDir(), "Directory where cvd will download android build artifacts to.") + address := flag.String("listen_addr", DefaultListenAddress, "IP address to listen for requests.") + + flag.Parse() + + certPath := filepath.Join(*tlsCertDir, "cert.pem") + keyPath := filepath.Join(*tlsCertDir, "key.pem") + + if err := os.MkdirAll(*imRootDir, 0774); err != nil { + log.Fatalf("Unable to create artifacts directory: %v", err) } - abURL := fromEnvOrDefault("ORCHESTRATOR_ANDROID_BUILD_URL", defaultAndroidBuildURL) - cvdBinAndroidBuildID := fromEnvOrDefault("ORCHESTRATOR_CVDBIN_ANDROID_BUILD_ID", defaultCVDBinAndroidBuildID) - cvdBinAndroidBuildTarget := fromEnvOrDefault("ORCHESTRATOR_CVDBIN_ANDROID_BUILD_TARGET", defaultCVDBinAndroidBuildTarget) - imRootDir := fromEnvOrDefault("ORCHESTRATOR_CVD_ARTIFACTS_DIR", defaultCVDArtifactsDir) + imPaths := orchestrator.IMPaths{ - RootDir: imRootDir, - CVDToolsDir: imRootDir, - ArtifactsRootDir: filepath.Join(imRootDir, "artifacts"), - RuntimesRootDir: filepath.Join(imRootDir, "runtimes"), + RootDir: *imRootDir, + CVDToolsDir: *imRootDir, + ArtifactsRootDir: filepath.Join(*imRootDir, "artifacts"), } om := orchestrator.NewMapOM() uamOpts := orchestrator.UserArtifactsManagerOpts{ - RootDir: filepath.Join(imRootDir, "user_artifacs"), + RootDir: filepath.Join(*imRootDir, "user_artifacts"), NameFactory: func() string { return uuid.New().String() }, } uam := orchestrator.NewUserArtifactsManagerImpl(uamOpts) cvdToolsVersion := orchestrator.AndroidBuild{ - ID: cvdBinAndroidBuildID, - Target: cvdBinAndroidBuildTarget, + ID: *cvdBinAndroidBuildID, + Target: *cvdBinAndroidBuildTarget, } debugStaticVars := debug.StaticVariables{ InitialCVDBinAndroidBuildID: cvdToolsVersion.ID, InitialCVDBinAndroidBuildTarget: cvdToolsVersion.Target, } debugVarsManager := debug.NewVariablesManager(debugStaticVars) - deviceServerLoop := operator.SetupDeviceEndpoint(pool, config, socketPath) - go func() { - err := deviceServerLoop() - log.Fatal("Error with device endpoint: ", err) - }() - r := operator.CreateHttpHandlers(pool, polledSet, config, maybeIntercept) imController := orchestrator.Controller{ Config: orchestrator.Config{ Paths: imPaths, CVDToolsVersion: cvdToolsVersion, - AndroidBuildServiceURL: abURL, - CVDUser: cvdUser, + AndroidBuildServiceURL: *abURL, + CVDUser: *cvdUser, }, OperationManager: om, WaitOperationDuration: 2 * time.Minute, UserArtifactsManager: uam, DebugVariablesManager: debugVarsManager, } + proxy := newOperatorProxy(*operatorPort) + + r := mux.NewRouter() imController.AddRoutes(r) - // The host orchestrator currently has no use for this, since clients won't connect - // to it directly, however they probably will once the multi-device feature matures. - if len(webUIUrlStr) > 0 { - webUIUrl, _ := url.Parse(webUIUrlStr) - proxy := httputil.NewSingleHostReverseProxy(webUIUrl) - r.PathPrefix("/").Handler(proxy) - } else { - fs := http.FileServer(http.Dir(DefaultStaticFilesDir)) - r.PathPrefix("/").Handler(fs) - } + // Defer to the operator for every route not covered by the orchestrator. That + // includes web UI and other static files. + r.PathPrefix("/").Handler(proxy) + http.Handle("/", r) starters := []func() error{ - func() error { return operator.SetupDeviceEndpoint(pool, config, socketPath)() }, - func() error { return startHttpServer(httpPort) }, + func() error { return startHttpServer(*address, *httpPort) }, } - if httpsPort != "" { - starters = append(starters, func() error { return startHttpsServer(httpsPort, certPath, keyPath) }) + if *httpsPort > 0 { + starters = append(starters, func() error { + return startHttpsServer(*address, *httpsPort, certPath, keyPath) + }) } start(starters) } diff --git a/frontend/src/host_orchestrator/orchestrator/controller.go b/frontend/src/host_orchestrator/orchestrator/controller.go index d63e7d421e..0a3b411e6f 100644 --- a/frontend/src/host_orchestrator/orchestrator/controller.go +++ b/frontend/src/host_orchestrator/orchestrator/controller.go @@ -59,6 +59,8 @@ func (c *Controller) AddRoutes(router *mux.Router) { httpHandler(newCreateCVDHandler(c.Config, c.OperationManager, c.UserArtifactsManager))).Methods("POST") router.Handle("/cvds", httpHandler(&listCVDsHandler{Config: c.Config})).Methods("GET") router.PathPrefix("/cvds/{name}/logs").Handler(&getCVDLogsHandler{Config: c.Config}).Methods("GET") + router.Handle("/cvds/{group}", httpHandler(newStopCVDHandler(c.Config, c.OperationManager))).Methods("DELETE") + router.Handle("/cvds/{group}/{name}", httpHandler(newStopCVDHandler(c.Config, c.OperationManager))).Methods("DELETE") router.Handle("/operations/{name}", httpHandler(&getOperationHandler{om: c.OperationManager})).Methods("GET") // The expected response of the operation in case of success. If the original method returns no data on // success, such as `Delete`, response will be empty. If the original method is standard @@ -109,7 +111,7 @@ func replyJSONOK(w http.ResponseWriter, obj interface{}) error { func replyJSONErr(w http.ResponseWriter, err error) error { appErr, ok := err.(*operator.AppError) if !ok { - return replyJSON(w, apiv1.ErrorMsg{Error: "Internal Server Error"}, http.StatusInternalServerError) + appErr, _ = (operator.NewInternalError("Internal server error", err)).(*operator.AppError) } return replyJSON(w, appErr.JSONResponse(), appErr.StatusCode) } @@ -176,13 +178,18 @@ func (h *createCVDHandler) Handle(r *http.Request) (interface{}, error) { } cvdDwnl := NewAndroidCICVDDownloader( artifacts.NewAndroidCIBuildAPI(http.DefaultClient, h.Config.AndroidBuildServiceURL)) - creds := ExtractCredentials(req) + creds := r.Header.Get(HeaderBuildAPICreds) buildAPIOpts := artifacts.AndroidCIBuildAPIOpts{Credentials: creds} buildAPI := artifacts.NewAndroidCIBuildAPIWithOpts( http.DefaultClient, h.Config.AndroidBuildServiceURL, buildAPIOpts) artifactsFetcher := newBuildAPIArtifactsFetcher(buildAPI) cvdBundleFetcher := newFetchCVDCommandArtifactsFetcher( exec.CommandContext, h.Config.Paths.FetchCVDBin(), creds) + cvdStartTimeout := 3 * time.Minute + if req.EnvConfig != nil { + // Use a lengthier timeout when using canonical configs as this operation downloads artifacts as well. + cvdStartTimeout = 7 * time.Minute + } opts := CreateCVDActionOpts{ Request: req, HostValidator: &HostValidator{ExecContext: exec.CommandContext}, @@ -195,9 +202,10 @@ func (h *createCVDHandler) Handle(r *http.Request) (interface{}, error) { ArtifactsFetcher: artifactsFetcher, CVDBundleFetcher: cvdBundleFetcher, UUIDGen: func() string { return uuid.New().String() }, - CVDStartTimeout: 3 * time.Minute, + CVDStartTimeout: cvdStartTimeout, CVDUser: h.Config.CVDUser, UserArtifactsDirResolver: h.UADirResolver, + BuildAPICredentials: creds, } return NewCreateCVDAction(opts).Run() } @@ -219,6 +227,36 @@ func (h *listCVDsHandler) Handle(r *http.Request) (interface{}, error) { return NewListCVDsAction(opts).Run() } +type stopCVDHandler struct { + Config Config + OM OperationManager +} + +func newStopCVDHandler(c Config, om OperationManager) *stopCVDHandler { + return &stopCVDHandler{ + Config: c, + OM: om, + } +} + +func (h *stopCVDHandler) Handle(r *http.Request) (interface{}, error) { + vars := mux.Vars(r) + group := vars["group"] + name := vars["name"] + buildAPI := artifacts.NewAndroidCIBuildAPI(http.DefaultClient, h.Config.AndroidBuildServiceURL) + cvdDwnl := NewAndroidCICVDDownloader(buildAPI) + opts := StopCVDActionOpts{ + Selector: CVDSelector{Group: group, Name: name}, + Paths: h.Config.Paths, + OperationManager: h.OM, + ExecContext: exec.CommandContext, + CVDToolsVersion: h.Config.CVDToolsVersion, + CVDDownloader: cvdDwnl, + CVDUser: h.Config.CVDUser, + } + return NewStopCVDAction(opts).Run() +} + type getCVDLogsHandler struct { Config Config } diff --git a/frontend/src/host_orchestrator/orchestrator/createcvdaction.go b/frontend/src/host_orchestrator/orchestrator/createcvdaction.go index 5932d7a7d7..7bc328c7c6 100644 --- a/frontend/src/host_orchestrator/orchestrator/createcvdaction.go +++ b/frontend/src/host_orchestrator/orchestrator/createcvdaction.go @@ -15,12 +15,15 @@ package orchestrator import ( - "errors" - "fmt" + "encoding/json" + "io/ioutil" "log" + "os" + "path/filepath" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/google/android-cuttlefish/frontend/src/host_orchestrator/orchestrator/artifacts" @@ -46,6 +49,7 @@ type CreateCVDActionOpts struct { CVDUser string CVDStartTimeout time.Duration UserArtifactsDirResolver UserArtifactsDirResolver + BuildAPICredentials string } type CreateCVDAction struct { @@ -62,6 +66,9 @@ type CreateCVDAction struct { userArtifactsDirResolver UserArtifactsDirResolver artifactsMngr *artifacts.Manager startCVDHandler *startCVDHandler + cvdUser string + cvdStartTimeout time.Duration + buildAPICredentials string instanceCounter uint32 } @@ -79,6 +86,9 @@ func NewCreateCVDAction(opts CreateCVDActionOpts) *CreateCVDAction { artifactsFetcher: opts.ArtifactsFetcher, cvdBundleFetcher: opts.CVDBundleFetcher, userArtifactsDirResolver: opts.UserArtifactsDirResolver, + cvdUser: opts.CVDUser, + cvdStartTimeout: opts.CVDStartTimeout, + buildAPICredentials: opts.BuildAPICredentials, artifactsMngr: artifacts.NewManager( opts.Paths.ArtifactsRootDir, @@ -103,9 +113,6 @@ func (a *CreateCVDAction) Run() (apiv1.Operation, error) { if err := createDir(a.paths.ArtifactsRootDir); err != nil { return apiv1.Operation{}, err } - if err := createRuntimesRootDir(a.paths.RuntimesRootDir); err != nil { - return apiv1.Operation{}, fmt.Errorf("failed creating cuttlefish runtime directory: %w", err) - } if err := a.cvdDownloader.Download(a.cvdToolsVersion, a.paths.CVDBin(), a.paths.FetchCVDBin()); err != nil { return apiv1.Operation{}, err } @@ -115,12 +122,56 @@ func (a *CreateCVDAction) Run() (apiv1.Operation, error) { } func (a *CreateCVDAction) launchCVD(op apiv1.Operation) { - result := a.launchCVDResult(op) + result := &OperationResult{} + if a.req.EnvConfig != nil { + result.Value, result.Error = a.launchWithCanonicalConfig(op) + } else { + result = a.launchCVDResult(op) + } if err := a.om.Complete(op.Name, result); err != nil { log.Printf("error completing launch cvd operation %q: %v\n", op.Name, err) } } +func (a *CreateCVDAction) launchWithCanonicalConfig(op apiv1.Operation) (*apiv1.CreateCVDResponse, error) { + data, err := json.MarshalIndent(a.req.EnvConfig, "", " ") + if err != nil { + return nil, err + } + configFile, err := createTempFile("cvdload*.json", data, 0640) + if err != nil { + return nil, err + } + args := []string{"load", configFile.Name()} + if a.buildAPICredentials != "" { + filename, err := createCredsFile(a.execContext) + if err != nil { + return nil, err + } + if err := writeCredsFile(a.execContext, filename, []byte(a.buildAPICredentials)); err != nil { + return nil, err + } + defer func() { + if err := removeCredsFile(a.execContext, filename); err != nil { + log.Println("failed to remove credentials file: ", err) + } + }() + args = append(args, "--credential_source="+filename) + } + opts := cvd.CommandOpts{ + Timeout: a.cvdStartTimeout, + } + cmd := cvd.NewCommand(a.execContext, a.paths.CVDBin(), args, opts) + if err := cmd.Run(); err != nil { + return nil, operator.NewInternalError(ErrMsgLaunchCVDFailed, err) + } + group, err := cvdFleetFirstGroup(a.execContext, a.paths.CVDBin()) + if err != nil { + return nil, err + } + return &apiv1.CreateCVDResponse{CVDs: group.toAPIObject()}, nil +} + func (a *CreateCVDAction) launchCVDResult(op apiv1.Operation) *OperationResult { instancesCount := 1 + a.req.AdditionalInstancesNum var instanceNumbers []uint32 @@ -137,25 +188,14 @@ func (a *CreateCVDAction) launchCVDResult(op apiv1.Operation) *OperationResult { } } if err != nil { - var details string - var execError *cvd.CommandExecErr - var timeoutErr *cvd.CommandTimeoutErr - if errors.As(err, &execError) { - details = execError.Error() - // Overwrite err with the unwrapped error as execution errors were already logged. - err = execError.Unwrap() - } else if errors.As(err, &timeoutErr) { - details = timeoutErr.Error() - } - log.Printf("failed to launch cvd with error: %v", err) - return &OperationResult{Error: operator.NewInternalErrorD(ErrMsgLaunchCVDFailed, details, err)} + return &OperationResult{Error: operator.NewInternalError(ErrMsgLaunchCVDFailed, err)} } - fleet, err := cvdFleet(a.execContext, a.paths.CVDBin()) + group, err := cvdFleetFirstGroup(a.execContext, a.paths.CVDBin()) if err != nil { return &OperationResult{Error: operator.NewInternalError(ErrMsgLaunchCVDFailed, err)} } relevant := []*cvdInstance{} - for _, item := range fleet { + for _, item := range group.Instances { n, err := strconv.Atoi(item.InstanceName) if err != nil { return &OperationResult{Error: operator.NewInternalError(ErrMsgLaunchCVDFailed, err)} @@ -164,9 +204,8 @@ func (a *CreateCVDAction) launchCVDResult(op apiv1.Operation) *OperationResult { relevant = append(relevant, item) } } - res := &apiv1.CreateCVDResponse{ - CVDs: fleetToCVDs(relevant), - } + group.Instances = relevant + res := &apiv1.CreateCVDResponse{CVDs: group.toAPIObject()} return &OperationResult{Value: res} } @@ -242,7 +281,6 @@ func (a *CreateCVDAction) launchFromAndroidCI( startParams := startCVDParams{ InstanceNumbers: a.newInstanceNumbers(instancesCount), MainArtifactsDir: mainBuildDir, - RuntimeDir: a.paths.RuntimesRootDir, KernelDir: kernelBuildDir, BootloaderDir: bootloaderBuildDir, } @@ -251,7 +289,7 @@ func (a *CreateCVDAction) launchFromAndroidCI( } // TODO: Remove once `acloud CLI` gets deprecated. if contains(startParams.InstanceNumbers, 1) { - go runAcloudSetup(a.execContext, a.paths.ArtifactsRootDir, mainBuildDir, a.paths.RuntimesRootDir) + go runAcloudSetup(a.execContext, a.paths.ArtifactsRootDir, mainBuildDir) } return startParams.InstanceNumbers, nil } @@ -262,17 +300,23 @@ func (a *CreateCVDAction) launchFromUserBuild( if err := untarCVDHostPackage(artifactsDir); err != nil { return nil, err } + // assemble_cvd needs writer permission over vbmeta images + // https://cs.android.com/android/platform/superproject/main/+/main:device/google/cuttlefish/host/commands/assemble_cvd/disk_flags.cc;l=639;drc=b0ec6e4df1126fd4045ce32bbfcedb79f25bd5bc + for _, name := range []string{"vbmeta.img", "vbmeta_system.img"} { + if err := os.Chmod(filepath.Join(artifactsDir, name), 0664); err != nil { + return nil, err + } + } startParams := startCVDParams{ InstanceNumbers: a.newInstanceNumbers(instancesCount), MainArtifactsDir: artifactsDir, - RuntimeDir: a.paths.RuntimesRootDir, } if err := a.startCVDHandler.Start(startParams); err != nil { return nil, err } // TODO: Remove once `acloud CLI` gets deprecated. if contains(startParams.InstanceNumbers, 1) { - go runAcloudSetup(a.execContext, a.paths.ArtifactsRootDir, artifactsDir, a.paths.RuntimesRootDir) + go runAcloudSetup(a.execContext, a.paths.ArtifactsRootDir, artifactsDir) } return startParams.InstanceNumbers, nil } @@ -287,6 +331,9 @@ func (a *CreateCVDAction) newInstanceNumbers(n uint32) []uint32 { } func validateRequest(r *apiv1.CreateCVDRequest) error { + if r.EnvConfig != nil { + return nil + } if r.CVD == nil { return EmptyFieldError("CVD") } @@ -304,12 +351,97 @@ func validateRequest(r *apiv1.CreateCVDRequest) error { return nil } -func ExtractCredentials(r *apiv1.CreateCVDRequest) string { - if r == nil || - r.CVD == nil || - r.CVD.BuildSource == nil || - r.CVD.BuildSource.AndroidCIBuildSource == nil { - return "" - } - return r.CVD.BuildSource.AndroidCIBuildSource.Credentials +// See https://pkg.go.dev/io/ioutil@go1.13.15#TempFile +func createTempFile(pattern string, data []byte, mode os.FileMode) (*os.File, error) { + file, err := ioutil.TempFile("", pattern) + if err != nil { + return nil, err + } + if err := file.Chmod(mode); err != nil { + return nil, err + } + _, err = file.Write(data) + if closeErr := file.Close(); closeErr != nil && err == nil { + err = closeErr + } + if err != nil { + return nil, err + } + return file, nil +} + +// Create the credential file so it's owned by the configured `cvd user`, e.g: `_cvd-executor`. +func createCredsFile(ctx cvd.CVDExecContext) (string, error) { + name, err := tempFilename("cvdload*.creds") + if err != nil { + return "", err + } + if err := cvd.Exec(ctx, "touch", name); err != nil { + return "", err + } + if err := cvd.Exec(ctx, "chmod", "0600", name); err != nil { + return "", err + } + return name, nil +} + +// Write into credential files by granting temporary write permission to `cvdnetwork` group. +func writeCredsFile(ctx cvd.CVDExecContext, name string, data []byte) error { + info, err := os.Stat(name) + if err != nil { + return err + } + infoSys := info.Sys() + var gid uint32 + if stat, ok := infoSys.(*syscall.Stat_t); ok { + gid = stat.Gid + } else { + panic("unexpected stat syscall type") + } + defer func() { + // Reverts the write permission. + if err := cvd.Exec(ctx, "chgrp", strconv.Itoa(int(gid)), name); err != nil { + log.Println(err) + } + if err := cvd.Exec(ctx, "chmod", "0600", name); err != nil { + log.Println(err) + } + }() + // Grants temporal write permission to `cvdnetwork`, so this process can write the file. + if err := cvd.Exec(ctx, "chgrp", "cvdnetwork", name); err != nil { + return err + } + if err := cvd.Exec(ctx, "chmod", "0620", name); err != nil { + return err + } + file, err := os.OpenFile(name, os.O_WRONLY, 0) + if err != nil { + return err + } + _, err = file.Write(data) + if closeErr := file.Close(); closeErr != nil && err == nil { + err = closeErr + } + if err != nil { + return err + } + return nil +} + +func removeCredsFile(ctx cvd.CVDExecContext, name string) error { + return cvd.Exec(ctx, "rm", name) +} + +// Returns a random name for a file in the /tmp directory given a pattern. +// See https://pkg.go.dev/io/ioutil@go1.13.15#TempFile +func tempFilename(pattern string) (string, error) { + file, err := ioutil.TempFile("", pattern) + if err != nil { + return "", err + } + name := file.Name() + if os.Remove(name); err != nil { + return "", err + } + return name, nil } diff --git a/frontend/src/host_orchestrator/orchestrator/createcvdaction_test.go b/frontend/src/host_orchestrator/orchestrator/createcvdaction_test.go index 755c227b55..e6a97aa45c 100644 --- a/frontend/src/host_orchestrator/orchestrator/createcvdaction_test.go +++ b/frontend/src/host_orchestrator/orchestrator/createcvdaction_test.go @@ -96,7 +96,6 @@ func TestCreateCVDSameTargetArtifactsIsDownloadedOnce(t *testing.T) { paths := IMPaths{ CVDToolsDir: dir, ArtifactsRootDir: dir + "/artifacts", - RuntimesRootDir: dir + "/runtimes", } om := NewMapOM() buildAPI := &fakeBuildAPI{} @@ -138,7 +137,6 @@ func TestCreateCVDVerifyRootDirectoriesAreCreated(t *testing.T) { paths := IMPaths{ CVDToolsDir: dir, ArtifactsRootDir: dir + "/artifacts", - RuntimesRootDir: dir + "/runtimes", } om := NewMapOM() buildAPI := &fakeBuildAPI{} @@ -166,16 +164,12 @@ func TestCreateCVDVerifyRootDirectoriesAreCreated(t *testing.T) { if diff := cmp.Diff("drwxrwxr--", stats.Mode().String()); diff != "" { t.Errorf("mode mismatch (-want +got):\n%s", diff) } - stats, _ = os.Stat(paths.RuntimesRootDir) - if diff := cmp.Diff("dgrwxrwxr--", stats.Mode().String()); diff != "" { - t.Errorf("mode mismatch (-want +got):\n%s", diff) - } } func TestCreateCVDVerifyStartCVDCmdArgs(t *testing.T) { dir := orchtesting.TempDir(t) defer orchtesting.RemoveDir(t, dir) - goldenPrefixFmt := fmt.Sprintf("sudo -u fakecvduser HOME=%[1]s/runtimes "+ + goldenPrefixFmt := fmt.Sprintf("sudo -u fakecvduser "+ "ANDROID_HOST_OUT=%[1]s/artifacts/%%[1]s "+"%[1]s/cvd --group_name=cvd start --daemon --report_anonymous_usage_stats=y"+ " --base_instance_num=1 --system_image_dir=%[1]s/artifacts/%%[1]s", dir) tests := []struct { @@ -289,7 +283,6 @@ func TestCreateCVDVerifyStartCVDCmdArgs(t *testing.T) { paths := IMPaths{ CVDToolsDir: dir, ArtifactsRootDir: dir + "/artifacts", - RuntimesRootDir: dir + "/runtimes", } om := NewMapOM() buildAPI := &fakeBuildAPI{} @@ -338,8 +331,10 @@ func TestCreateCVDFromUserBuildVerifyStartCVDCmdArgs(t *testing.T) { dir := orchtesting.TempDir(t) defer orchtesting.RemoveDir(t, dir) tarContent, _ := ioutil.ReadFile(getTestTarFilename()) + ioutil.WriteFile(dir+"/vbmeta.img", []byte{}, 0755) + ioutil.WriteFile(dir+"/vbmeta_system.img", []byte{}, 0755) ioutil.WriteFile(dir+"/"+CVDHostPackageName, tarContent, 0755) - expected := fmt.Sprintf("sudo -u fakecvduser HOME=%[1]s/runtimes "+ + expected := fmt.Sprintf("sudo -u fakecvduser "+ "ANDROID_HOST_OUT=%[1]s "+"%[1]s/cvd --group_name=cvd start --daemon --report_anonymous_usage_stats=y"+ " --base_instance_num=1 --system_image_dir=%[1]s", dir) var usedCmdName string @@ -354,7 +349,6 @@ func TestCreateCVDFromUserBuildVerifyStartCVDCmdArgs(t *testing.T) { paths := IMPaths{ CVDToolsDir: dir, ArtifactsRootDir: dir + "/artifacts", - RuntimesRootDir: dir + "/runtimes", } om := NewMapOM() buildAPI := &fakeBuildAPI{} @@ -407,7 +401,6 @@ func TestCreateCVDFailsDueCVDSubCommandExecution(t *testing.T) { paths := IMPaths{ CVDToolsDir: dir, ArtifactsRootDir: dir + "/artifacts", - RuntimesRootDir: dir + "/runtimes", } om := NewMapOM() buildAPI := &fakeBuildAPI{} @@ -444,7 +437,6 @@ func TestCreateCVDFailsDueTimeout(t *testing.T) { paths := IMPaths{ CVDToolsDir: dir, ArtifactsRootDir: dir + "/artifacts", - RuntimesRootDir: dir + "/runtimes", } om := NewMapOM() buildAPI := &fakeBuildAPI{} @@ -488,7 +480,6 @@ func TestCreateCVDFailsDueInvalidHost(t *testing.T) { paths := IMPaths{ CVDToolsDir: dir, ArtifactsRootDir: dir + "/artifacts", - RuntimesRootDir: dir + "/runtimes", } om := NewMapOM() opts := CreateCVDActionOpts{ @@ -508,47 +499,3 @@ func TestCreateCVDFailsDueInvalidHost(t *testing.T) { t.Error("expected error") } } - -func TestExtractCredentials(t *testing.T) { - var tests = []struct { - req *apiv1.CreateCVDRequest - exp string - }{ - { - req: nil, - exp: "", - }, - { - req: &apiv1.CreateCVDRequest{}, - exp: "", - }, - { - req: &apiv1.CreateCVDRequest{CVD: &apiv1.CVD{}}, - exp: "", - }, - { - req: &apiv1.CreateCVDRequest{CVD: &apiv1.CVD{BuildSource: &apiv1.BuildSource{}}}, - exp: "", - }, - { - req: &apiv1.CreateCVDRequest{ - CVD: &apiv1.CVD{ - BuildSource: &apiv1.BuildSource{ - AndroidCIBuildSource: &apiv1.AndroidCIBuildSource{ - Credentials: "foo", - }, - }, - }, - }, - exp: "foo", - }, - } - - for _, test := range tests { - creds := ExtractCredentials(test.req) - - if diff := cmp.Diff(test.exp, creds); diff != "" { - t.Errorf("cred mismatch (-want +got):\n%s", diff) - } - } -} diff --git a/frontend/src/host_orchestrator/orchestrator/cvd/cvd.go b/frontend/src/host_orchestrator/orchestrator/cvd/cvd.go index cabb38c82d..fb628f756d 100644 --- a/frontend/src/host_orchestrator/orchestrator/cvd/cvd.go +++ b/frontend/src/host_orchestrator/orchestrator/cvd/cvd.go @@ -33,7 +33,6 @@ const CVDCommandDefaultTimeout = 30 * time.Second const ( envVarAndroidHostOut = "ANDROID_HOST_OUT" - envVarHome = "HOME" ) type CommandOpts struct { @@ -88,7 +87,7 @@ func (c *Command) Run() error { } // TODO: Use `context.WithTimeout` if upgrading to go 1.19 as `exec.Cmd` adds the `Cancel` function field, // so the cancel logic could be customized to continue sending the SIGINT signal. - cmd := c.execContext(context.TODO(), cvdEnv(c.opts.AndroidHostOut, c.opts.Home), c.cvdBin, c.args...) + cmd := c.execContext(context.TODO(), cvdEnv(c.opts.AndroidHostOut), c.cvdBin, c.args...) stderr := &bytes.Buffer{} cmd.Stdout = c.opts.Stdout cmd.Stderr = stderr @@ -123,7 +122,7 @@ func (c *Command) Run() error { } func (c *Command) startCVDServer() error { - cmd := c.execContext(context.TODO(), cvdEnv("", ""), c.cvdBin) + cmd := c.execContext(context.TODO(), cvdEnv(""), c.cvdBin) // NOTE: Stdout and Stderr should be nil so Run connects the corresponding // file descriptor to the null device (os.DevNull). // Otherwhise, `Run` will never complete. Why? a pipe will be created to handle @@ -135,8 +134,8 @@ func (c *Command) startCVDServer() error { return cmd.Run() } -func cvdEnv(androidHostOut, home string) []string { - env := []string{envVarHome + "=" + home} +func cvdEnv(androidHostOut string) []string { + env := []string{} if androidHostOut != "" { env = append(env, envVarAndroidHostOut+"="+androidHostOut) } @@ -163,3 +162,15 @@ func LogCombinedStdoutStderr(cmd *exec.Cmd, val string) { msg := "`%s`, combined stdout and stderr :\n%s" log.Printf(msg, strings.Join(cmd.Args, " "), OutputLogMessage(val)) } + +func Exec(ctx CVDExecContext, name string, args ...string) error { + cmd := ctx(context.TODO(), nil, name, args...) + var b bytes.Buffer + cmd.Stdout = nil + cmd.Stderr = &b + err := cmd.Run() + if err != nil { + return &CommandExecErr{args, b.String(), err} + } + return nil +} diff --git a/frontend/src/host_orchestrator/orchestrator/instancemanager.go b/frontend/src/host_orchestrator/orchestrator/instancemanager.go index bd4471b0c4..8ecce186a2 100644 --- a/frontend/src/host_orchestrator/orchestrator/instancemanager.go +++ b/frontend/src/host_orchestrator/orchestrator/instancemanager.go @@ -56,7 +56,6 @@ type IMPaths struct { RootDir string CVDToolsDir string ArtifactsRootDir string - RuntimesRootDir string } func (p *IMPaths) CVDBin() string { @@ -67,6 +66,22 @@ func (p *IMPaths) FetchCVDBin() string { return filepath.Join(p.CVDToolsDir, "fetch_cvd") } +type CVDSelector struct { + Group string + Name string +} + +func (s *CVDSelector) ToCVDCLI() []string { + res := []string{} + if s.Group != "" { + res = append(res, "--group_name="+s.Group) + } + if s.Name != "" { + res = append(res, "--instance_name="+s.Name) + } + return res +} + // Creates a CVD execution context from a regular execution context. // If a non-empty user name is provided the returned execution context executes commands as that user. func newCVDExecContext(execContext ExecContext, user string) cvd.CVDExecContext { @@ -107,14 +122,35 @@ type cvdGroup struct { Instances []*cvdInstance `json:"instances"` } +func (g *cvdGroup) toAPIObject() []*apiv1.CVD { + result := make([]*apiv1.CVD, len(g.Instances)) + for i, item := range g.Instances { + result[i] = item.toAPIObject(g.Name) + } + return result +} + type cvdInstance struct { - InstanceName string `json:"instance_name"` - Status string `json:"status"` - Displays []string `json:"displays"` - InstanceDir string `json:"instance_dir"` + InstanceName string `json:"instance_name"` + Status string `json:"status"` + Displays []string `json:"displays"` + InstanceDir string `json:"instance_dir"` + WebRTCDeviceID string `json:"webrtc_device_id"` +} + +func (i *cvdInstance) toAPIObject(group string) *apiv1.CVD { + return &apiv1.CVD{ + Group: group, + Name: i.InstanceName, + // TODO(b/259725479): Update when `cvd fleet` prints out build information. + BuildSource: &apiv1.BuildSource{}, + Status: i.Status, + Displays: i.Displays, + WebRTCDeviceID: i.WebRTCDeviceID, + } } -func cvdFleet(ctx cvd.CVDExecContext, cvdBin string) ([]*cvdInstance, error) { +func cvdFleet(ctx cvd.CVDExecContext, cvdBin string) (*cvdFleetOutput, error) { stdout := &bytes.Buffer{} cvdCmd := cvd.NewCommand(ctx, cvdBin, []string{"fleet"}, cvd.CommandOpts{Stdout: stdout}) err := cvdCmd.Run() @@ -126,33 +162,28 @@ func cvdFleet(ctx cvd.CVDExecContext, cvdBin string) ([]*cvdInstance, error) { log.Printf("Failed parsing `cvd fleet` ouput. Output: \n\n%s\n", cvd.OutputLogMessage(stdout.String())) return nil, fmt.Errorf("failed parsing `cvd fleet` output: %w", err) } - if len(output.Groups) == 0 { - return []*cvdInstance{}, nil - } - // Host orchestrator only works with one instances group. - return output.Groups[0].Instances, nil + return output, nil } -func fleetToCVDs(val []*cvdInstance) []*apiv1.CVD { - result := make([]*apiv1.CVD, len(val)) - for i, item := range val { - result[i] = &apiv1.CVD{ - Name: item.InstanceName, - // TODO(b/259725479): Update when `cvd fleet` prints out build information. - BuildSource: &apiv1.BuildSource{}, - Status: item.Status, - Displays: item.Displays, - } +// Helper for listing first group instances only. Legacy flows didn't have a multi-group environment hence unsing +// the first group only. +func cvdFleetFirstGroup(ctx cvd.CVDExecContext, cvdBin string) (*cvdGroup, error) { + fleet, err := cvdFleet(ctx, cvdBin) + if err != nil { + return nil, err } - return result + if len(fleet.Groups) == 0 { + return &cvdGroup{}, nil + } + return fleet.Groups[0], nil } func CVDLogsDir(ctx cvd.CVDExecContext, cvdBin, name string) (string, error) { - instances, err := cvdFleet(ctx, cvdBin) + group, err := cvdFleetFirstGroup(ctx, cvdBin) if err != nil { return "", err } - ok, ins := cvdInstances(instances).findByName(name) + ok, ins := cvdInstances(group.Instances).findByName(name) if !ok { return "", operator.NewNotFoundError(fmt.Sprintf("Instance %q not found", name), nil) } @@ -160,17 +191,20 @@ func CVDLogsDir(ctx cvd.CVDExecContext, cvdBin, name string) (string, error) { } func HostBugReport(ctx cvd.CVDExecContext, paths IMPaths, out string) error { - fleet, err := cvdFleet(ctx, paths.CVDBin()) + group, err := cvdFleetFirstGroup(ctx, paths.CVDBin()) if err != nil { return err } - if len(fleet) == 0 { + if len(group.Instances) == 0 { return operator.NewNotFoundError("no artifacts found", nil) } - opts := cvd.CommandOpts{ - Home: paths.RuntimesRootDir, - } - return cvd.NewCommand(ctx, paths.CVDBin(), []string{"host_bugreport", "--output=" + out}, opts).Run() + cmd := cvd.NewCommand( + ctx, + paths.CVDBin(), + []string{"host_bugreport", "--output=" + out}, + cvd.CommandOpts{}, + ) + return cmd.Run() } const ( @@ -292,7 +326,7 @@ func (f *fetchCVDCommandArtifactsFetcher) Fetch(outDir, buildID, target string, out, err := fetchCmd.CombinedOutput() if err != nil { cvd.LogCombinedStdoutStderr(fetchCmd, string(out)) - return err + return fmt.Errorf("`fetch_cvd` failed: %w. \n\nCombined Output:\n%s", err, string(out)) } // TODO(b/286466643): Remove this hack once cuttlefish is capable of booting from read-only artifacts again. chmodCmd := f.execContext(context.TODO(), "chmod", "-R", "g+rw", outDir) @@ -319,17 +353,17 @@ func createCredentialsFile(content string) (*os.File, error) { return p1, nil } -type buildAPIArtifacstFetcher struct { +type buildAPIArtifactsFetcher struct { buildAPI artifacts.BuildAPI } -func newBuildAPIArtifactsFetcher(buildAPI artifacts.BuildAPI) *buildAPIArtifacstFetcher { - return &buildAPIArtifacstFetcher{ +func newBuildAPIArtifactsFetcher(buildAPI artifacts.BuildAPI) *buildAPIArtifactsFetcher { + return &buildAPIArtifactsFetcher{ buildAPI: buildAPI, } } -func (f *buildAPIArtifacstFetcher) Fetch(outDir, buildID, target string, artifactNames ...string) error { +func (f *buildAPIArtifactsFetcher) Fetch(outDir, buildID, target string, artifactNames ...string) error { var chans []chan error for _, name := range artifactNames { ch := make(chan error) @@ -522,7 +556,7 @@ func (s cvdInstances) findByName(name string) (bool, *cvdInstance) { return false, &cvdInstance{} } -func runAcloudSetup(execContext cvd.CVDExecContext, artifactsRootDir, artifactsDir, runtimeDir string) { +func runAcloudSetup(execContext cvd.CVDExecContext, artifactsRootDir, artifactsDir string) { run := func(cmd *exec.Cmd) { var b bytes.Buffer cmd.Stdout = &b diff --git a/frontend/src/host_orchestrator/orchestrator/listcvdsaction.go b/frontend/src/host_orchestrator/orchestrator/listcvdsaction.go index 12c85f03a9..7d6c14553f 100644 --- a/frontend/src/host_orchestrator/orchestrator/listcvdsaction.go +++ b/frontend/src/host_orchestrator/orchestrator/listcvdsaction.go @@ -48,9 +48,9 @@ func (a *ListCVDsAction) Run() (*apiv1.ListCVDsResponse, error) { if err := a.cvdDownloader.Download(a.cvdToolsVersion, a.paths.CVDBin(), a.paths.FetchCVDBin()); err != nil { return nil, err } - fleet, err := cvdFleet(a.execContext, a.paths.CVDBin()) + group, err := cvdFleetFirstGroup(a.execContext, a.paths.CVDBin()) if err != nil { return nil, err } - return &apiv1.ListCVDsResponse{CVDs: fleetToCVDs(fleet)}, nil + return &apiv1.ListCVDsResponse{CVDs: group.toAPIObject()}, nil } diff --git a/frontend/src/host_orchestrator/orchestrator/listcvdsaction_test.go b/frontend/src/host_orchestrator/orchestrator/listcvdsaction_test.go index edfe1e9bc7..e62c8285fb 100644 --- a/frontend/src/host_orchestrator/orchestrator/listcvdsaction_test.go +++ b/frontend/src/host_orchestrator/orchestrator/listcvdsaction_test.go @@ -34,7 +34,25 @@ func TestListCVDsSucceeds(t *testing.T) { `{ "groups": [ { - "group_name": "cvd", + "group_name": "foo", + "instances": [ + { + "adb_serial": "0.0.0.0:6520", + "assembly_dir": "/var/lib/cuttlefish-common/runtimes/cuttlefish/assembly", + "displays": [ + "720 x 1280 ( 320 )" + ], + "instance_dir": "/var/lib/cuttlefish-common/runtimes/cuttlefish/instances/cvd-1", + "instance_name": "1", + "status": "Running", + "web_access": "https://localhost:1443/devices/cvd-1/files/client.html", + "webrtc_device_id": "cvd-1", + "webrtc_port": "8443" + } + ] + }, + { + "group_name": "bar", "instances": [ { "adb_serial": "0.0.0.0:6520", @@ -66,7 +84,6 @@ func TestListCVDsSucceeds(t *testing.T) { paths := IMPaths{ CVDToolsDir: dir, ArtifactsRootDir: dir + "/artifacts", - RuntimesRootDir: dir + "/runtimes", } opts := ListCVDsActionOpts{ Paths: paths, @@ -81,10 +98,12 @@ func TestListCVDsSucceeds(t *testing.T) { want := &apiv1.ListCVDsResponse{CVDs: []*apiv1.CVD{ { - Name: "1", - BuildSource: &apiv1.BuildSource{}, - Status: "Running", - Displays: []string{"720 x 1280 ( 320 )"}, + Group: "foo", + Name: "1", + BuildSource: &apiv1.BuildSource{}, + Status: "Running", + Displays: []string{"720 x 1280 ( 320 )"}, + WebRTCDeviceID: "cvd-1", }, }} if diff := cmp.Diff(want, res); diff != "" { diff --git a/frontend/src/host_orchestrator/orchestrator/stopcvdaction.go b/frontend/src/host_orchestrator/orchestrator/stopcvdaction.go new file mode 100644 index 0000000000..4fda4e2075 --- /dev/null +++ b/frontend/src/host_orchestrator/orchestrator/stopcvdaction.go @@ -0,0 +1,78 @@ +// Copyright 2023 Google LLC +// +// 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 +// +// https://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. + +package orchestrator + +import ( + "log" + + "github.com/google/android-cuttlefish/frontend/src/host_orchestrator/orchestrator/cvd" + apiv1 "github.com/google/android-cuttlefish/frontend/src/liboperator/api/v1" + "github.com/google/android-cuttlefish/frontend/src/liboperator/operator" +) + +type StopCVDActionOpts struct { + Selector CVDSelector + Paths IMPaths + OperationManager OperationManager + ExecContext ExecContext + CVDToolsVersion AndroidBuild + CVDDownloader CVDDownloader + CVDUser string +} + +type StopCVDAction struct { + selector CVDSelector + paths IMPaths + om OperationManager + cvdToolsVersion AndroidBuild + cvdDownloader CVDDownloader + execContext cvd.CVDExecContext +} + +func NewStopCVDAction(opts StopCVDActionOpts) *StopCVDAction { + return &StopCVDAction{ + selector: opts.Selector, + paths: opts.Paths, + om: opts.OperationManager, + cvdToolsVersion: opts.CVDToolsVersion, + cvdDownloader: opts.CVDDownloader, + execContext: newCVDExecContext(opts.ExecContext, opts.CVDUser), + } +} + +func (a *StopCVDAction) Run() (apiv1.Operation, error) { + op := a.om.New() + go func(op apiv1.Operation) { + result := &OperationResult{} + result.Value, result.Error = a.exec(op) + if err := a.om.Complete(op.Name, result); err != nil { + log.Printf("error completing operation %q: %v\n", op.Name, err) + } + }(op) + return op, nil +} + +func (a *StopCVDAction) exec(op apiv1.Operation) (*apiv1.StopCVDResponse, error) { + if err := a.cvdDownloader.Download(a.cvdToolsVersion, a.paths.CVDBin(), a.paths.FetchCVDBin()); err != nil { + return nil, operator.NewInternalError("failed downloading the cvd binary", err) + } + args := a.selector.ToCVDCLI() + args = append(args, "stop") + cmd := cvd.NewCommand(a.execContext, a.paths.CVDBin(), args, cvd.CommandOpts{}) + if err := cmd.Run(); err != nil { + return nil, operator.NewInternalError("cvd stop failed", err) + } + return &apiv1.StopCVDResponse{}, nil +} diff --git a/frontend/src/host_orchestrator/orchestrator/userartifacts.go b/frontend/src/host_orchestrator/orchestrator/userartifacts.go index cb5b1e9615..15efd122ea 100644 --- a/frontend/src/host_orchestrator/orchestrator/userartifacts.go +++ b/frontend/src/host_orchestrator/orchestrator/userartifacts.go @@ -47,7 +47,7 @@ type UserArtifactsManager interface { NewDir() (*apiv1.UploadDirectory, error) // List existing directories ListDirs() (*apiv1.ListUploadDirectoriesResponse, error) - // Upldate artifact with the passed chunk. + // Update artifact with the passed chunk. UpdateArtifact(dir string, chunk UserArtifactChunk) error } @@ -126,17 +126,19 @@ func (m *UserArtifactsManagerImpl) UpdateArtifact(dir string, chunk UserArtifact func writeChunk(dir string, chunk UserArtifactChunk) error { filename := dir + "/" + chunk.Name - f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0644) + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0664) if err != nil { return err } + defer f.Close() if _, err := f.Seek(int64(chunk.ChunkNumber-1)*chunk.ChunkSizeBytes, 0); err != nil { return err } if _, err = io.Copy(f, chunk.File); err != nil { return err } - return nil + // Sets permission regardless of umask. + return os.Chmod(filename, 0664) } func Untar(dst string, src string) error { diff --git a/frontend/src/liboperator/api/v1/messages.go b/frontend/src/liboperator/api/v1/messages.go index 2447b167d0..b8e3f90de3 100644 --- a/frontend/src/liboperator/api/v1/messages.go +++ b/frontend/src/liboperator/api/v1/messages.go @@ -14,20 +14,44 @@ package v1 +// Control messages + +type ControlMsgHeader struct { + Type string `json:"message_type"` +} + +type PreRegisterMsg struct { + // Type must be set to "pre-register" + ControlMsgHeader + GroupName string `json:"group_name"` + Owner string `json:"owner"` + Devices []struct { + Id string `json:"id"` + Name string `json:"name"` + } `json:"devices"` +} + +type RegistrationStatusReport struct { + Id string `json:"id"` + Status string `json:"status"` + Msg string `json:"message"` +} + +type PreRegistrationResponse []RegistrationStatusReport + +// Device and client messages + type RegisterMsg struct { - Type string `json:"message_type"` DeviceId string `json:"device_id"` Port int `json:"device_port"` Info interface{} `json:"device_info"` } type ConnectMsg struct { - Type string `json:"message_type"` DeviceId string `json:"device_id"` } type ForwardMsg struct { - Type string `json:"message_type"` Payload interface{} `json:"payload"` // This is used by the device message and ignored by the client ClientId int `json:"client_id"` @@ -86,9 +110,18 @@ type AndroidCIBundle struct { Type ArtifactsBundleType `json:"type"` } +// Use `X-Cutf-Host-Orchestrator-BuildAPI-Creds` http header to pass the Build API credentials. type CreateCVDRequest struct { - // REQUIRED. + // Environment canonical configuration. + // Structure: https://android.googlesource.com/device/google/cuttlefish/+/8bbd3b9cd815f756f332791d45c4f492b663e493/host/commands/cvd/parser/README.md + // Example: https://cs.android.com/android/platform/superproject/main/+/main:device/google/cuttlefish/host/cvd_test_configs/main_phone-main_watch.json;drc=b2e8f4f014abb7f9cb56c0ae199334aacb04542d + // NOTE: Using this as a black box for now as its content is unstable. Use the test configs pointed + // above as reference to build your config object. + EnvConfig map[string]interface{} `json:"env_config"` + + // [DEPRECATED]. Use `EnvConfig` field. CVD *CVD `json:"cvd"` + // [DEPRECATED]. Use `EnvConfig` field. // Use to create multiple homogeneous instances. AdditionalInstancesNum uint32 `json:"additional_instances_num,omitempty"` } @@ -117,8 +150,6 @@ type AndroidCIBuildSource struct { BootloaderBuild *AndroidCIBuild `json:"bootloader_build,omitempty"` // Uses this specific system image build target if set. SystemImageBuild *AndroidCIBuild `json:"system_image_build,omitempty"` - // Credentials to use when connecting to the build API - Credentials string `json:"credentials,omitempty"` } // Represents a user build. @@ -144,7 +175,9 @@ type Operation struct { } type CVD struct { - // [Output Only] + // [Output Only] The group name the instance belongs to. + Group string `json:"group"` + // [Output Only] Identifier within a group. Name string `json:"name"` // [REQUIRED] BuildSource *BuildSource `json:"build_source"` @@ -152,10 +185,22 @@ type CVD struct { Status string `json:"status"` // [Output Only] Displays []string `json:"displays"` + // [Output Only] + WebRTCDeviceID string `json:"webrtc_device_id"` +} + +// Identifier within the whole fleet. Format: "{group}/{name}". +func (c *CVD) ID() string { return c.Group + "/" + c.Name } + +type DeviceDescriptor struct { + DeviceId string `json:"device_id"` + GroupId string `json:"group_id"` + Owner string `json:"owner,omitempty"` + Name string `json:"name,omitempty"` } type DeviceInfoReply struct { - DeviceId string `json:"device_id"` + DeviceDescriptor RegistrationInfo interface{} `json:"registration_info"` } @@ -171,3 +216,5 @@ type UploadDirectory struct { type ListUploadDirectoriesResponse struct { Items []*UploadDirectory `json:"items"` } + +type StopCVDResponse struct{} diff --git a/frontend/src/liboperator/operator/clients_test.go b/frontend/src/liboperator/operator/clients_test.go index 4075f02686..a3b62b1b9d 100644 --- a/frontend/src/liboperator/operator/clients_test.go +++ b/frontend/src/liboperator/operator/clients_test.go @@ -21,11 +21,11 @@ import ( func TestNewPolledConnection(t *testing.T) { ps := NewPolledSet() - c1 := ps.NewConnection(NewDevice(nil, 0, "")) + c1 := ps.NewConnection(newDevice("d1", nil, 0, "")) if c1 == nil { t.Error("Failed to create polled connection") } - c2 := ps.NewConnection(NewDevice(nil, 0, "")) + c2 := ps.NewConnection(newDevice("d1", nil, 0, "")) if c2 == nil { t.Error("Failed to create polled connection") } @@ -36,14 +36,14 @@ func TestNewPolledConnection(t *testing.T) { func TestGetConnection(t *testing.T) { ps := NewPolledSet() - c1 := ps.NewConnection(NewDevice(nil, 0, "")) + c1 := ps.NewConnection(newDevice("d1", nil, 0, "")) if ps.GetConnection(c1.Id()) != c1 { t.Error("Failed to get connection by id") } if ps.GetConnection(c1.Id()+"some suffix") == c1 { t.Error("Returned connection with wrong id") } - c2 := ps.NewConnection(NewDevice(nil, 0, "")) + c2 := ps.NewConnection(newDevice("d1", nil, 0, "")) if ps.GetConnection(c1.Id()) == c2 || ps.GetConnection(c2.Id()) == c1 { t.Error("Returned the wrong connection") } @@ -60,7 +60,7 @@ func TestGetConnection(t *testing.T) { func TestMessages(t *testing.T) { ps := NewPolledSet() - c := ps.NewConnection(NewDevice(nil, 0, "")) + c := ps.NewConnection(newDevice("d1", nil, 0, "")) msgs := c.GetMessages(0, -1) if len(msgs) != 0 { t.Error("Returned messages") @@ -80,7 +80,7 @@ func TestMessages(t *testing.T) { func TestDestroy(t *testing.T) { ps := NewPolledSet() - c := ps.NewConnection(NewDevice(nil, 0, "")) + c := ps.NewConnection(newDevice("d1", nil, 0, "")) c.OnDeviceDisconnected() if ps.GetConnection(c.Id()) != nil { t.Error("connection was not destroyed after device disconnect") diff --git a/frontend/src/liboperator/operator/devices.go b/frontend/src/liboperator/operator/devices.go index acbe3db528..be5a5d3b92 100644 --- a/frontend/src/liboperator/operator/devices.go +++ b/frontend/src/liboperator/operator/devices.go @@ -21,12 +21,16 @@ import ( "net/http/httputil" "net/url" "sync" + + apiv1 "github.com/google/android-cuttlefish/frontend/src/liboperator/api/v1" ) type Device struct { - info interface{} - groupId string - conn *JSONUnix + // Provided by the device at registration time + privateData interface{} + // Provided at pre-registration time + Descriptor apiv1.DeviceDescriptor + conn *JSONUnix // Reverse proxy to the client files Proxy *httputil.ReverseProxy // Synchronizes access to the client list and the client count @@ -36,18 +40,13 @@ type Device struct { clientCount int } -type DeviceInfo struct { - DeviceId string `json:"device_id"` - GroupId string `json:"group_id"` -} - type Group struct { deviceIds []string } const DEFAULT_GROUP_ID = "default" -func NewDevice(conn *JSONUnix, port int, info interface{}) *Device { +func newDevice(id string, conn *JSONUnix, port int, privateData interface{}) *Device { url, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", port)) if err != nil { // This should not happen @@ -56,8 +55,18 @@ func NewDevice(conn *JSONUnix, port int, info interface{}) *Device { } proxy := httputil.NewSingleHostReverseProxy(url) - groupId := GetGroupId(info) - return &Device{conn: conn, Proxy: proxy, info: info, groupId: groupId, clients: make(map[int]Client), clientCount: 0} + groupId := groupIdFromPrivateData(privateData) + return &Device{ + conn: conn, + Proxy: proxy, + privateData: privateData, + Descriptor: apiv1.DeviceDescriptor{ + DeviceId: id, + GroupId: groupId, + }, + clients: make(map[int]Client), + clientCount: 0, + } } // Sends a message to the device @@ -103,22 +112,31 @@ func (d *Device) ToClient(id int, msg interface{}) error { return client.Send(msg) } +// preDevice holds information about a device between pre-registration and registration. +type preDevice struct { + Desc apiv1.DeviceDescriptor + RegCh chan bool +} + // Keeps track of the registered devices. type DevicePool struct { + // preDevicesMtx must always be locked before devicesMtx + preDevicesMtx sync.Mutex + // Pre-registered devices by id + preDevices map[string]*preDevice devicesMtx sync.Mutex devices map[string]*Device - groups map[string]*Group } func NewDevicePool() *DevicePool { return &DevicePool{ - devices: make(map[string]*Device), - groups: make(map[string]*Group), + preDevices: make(map[string]*preDevice), + devices: make(map[string]*Device), } } -func GetGroupId(info interface{}) string { - deviceInfo, ok := info.(map[string]interface{}) +func groupIdFromPrivateData(privateData interface{}) string { + deviceInfo, ok := privateData.(map[string]interface{}) if !ok { return DEFAULT_GROUP_ID } @@ -131,50 +149,61 @@ func GetGroupId(info interface{}) string { return groupId } -func CreateOrGetGroup(p *DevicePool, groupId string) *Group { - _, ok := p.groups[groupId] - if !ok { - p.groups[groupId] = &Group{} +// PreRegister accepts a channel of boolean which it closes if the pre-registration is cancelled or +// sends the output of registering the device as a boolean. +func (p *DevicePool) PreRegister(Descriptor *apiv1.DeviceDescriptor, regCh chan bool) error { + d := &preDevice{ + Desc: *Descriptor, + RegCh: regCh, } + p.preDevicesMtx.Lock() + defer p.preDevicesMtx.Unlock() + if _, found := p.preDevices[d.Desc.DeviceId]; found { + return fmt.Errorf("Device Id already pre-registered") + } + // This locks devicesMtx after preDevicesMtx, which is the right order + if d := p.GetDevice(d.Desc.DeviceId); d != nil { + return fmt.Errorf("Device Id already registered") + } + p.preDevices[d.Desc.DeviceId] = d + return nil +} - return p.groups[groupId] +func (p *DevicePool) CancelPreRegistration(id string) { + p.preDevicesMtx.Lock() + defer p.preDevicesMtx.Unlock() + if d, ok := p.preDevices[id]; ok { + close(d.RegCh) + } + delete(p.preDevices, id) } // Register a new device, returns false if a device with that id already exists -func (p *DevicePool) Register(d *Device, id string) bool { +func (p *DevicePool) Register(id string, conn *JSONUnix, port int, privateData interface{}) *Device { + // acquire locks in this order to avoid deadlock + p.preDevicesMtx.Lock() + defer p.preDevicesMtx.Unlock() p.devicesMtx.Lock() defer p.devicesMtx.Unlock() - _, ok := p.devices[id] - if ok { - return false + if _, ok := p.devices[id]; ok { + // already registered + return nil + } + d := newDevice(id, conn, port, privateData) + if preDevice, ok := p.preDevices[id]; ok { + d.Descriptor = preDevice.Desc + delete(p.preDevices, id) + preDevice.RegCh <- true } p.devices[id] = d - groupId := GetGroupId(d.info) - group := CreateOrGetGroup(p, groupId) - group.deviceIds = append(group.deviceIds, id) - - return true + return d } func (p *DevicePool) Unregister(id string) { p.devicesMtx.Lock() defer p.devicesMtx.Unlock() if d, ok := p.devices[id]; ok { - groupId := GetGroupId(d.info) - group := CreateOrGetGroup(p, groupId) - - for i, deviceId := range group.deviceIds { - if id == deviceId { - group.deviceIds = append(group.deviceIds[:i], group.deviceIds[(i+1):]...) - break - } - } - - if len(group.deviceIds) == 0 { - delete(p.groups, groupId) - } - d.DisconnectClients() delete(p.devices, id) } @@ -188,11 +217,13 @@ func (p *DevicePool) GetDevice(id string) *Device { // List the registered groups' ids func (p *DevicePool) GroupIds() []string { - p.devicesMtx.Lock() - defer p.devicesMtx.Unlock() - ret := make([]string, 0, len(p.groups)) - for key := range p.groups { - ret = append(ret, key) + set := make(map[string]bool) + for _, d := range p.GetDeviceDescList() { + set[d.GroupId] = true + } + ret := make([]string, 0, len(set)) + for k := range set { + ret = append(ret, k) } return ret } @@ -208,27 +239,23 @@ func (p *DevicePool) DeviceIds() []string { return ret } -func (p *DevicePool) GetDeviceInfoList() []*DeviceInfo { +func (p *DevicePool) GetDeviceDescList() []*apiv1.DeviceDescriptor { p.devicesMtx.Lock() defer p.devicesMtx.Unlock() - ret := make([]*DeviceInfo, 0) - for id, device := range p.devices { - ret = append(ret, &DeviceInfo{DeviceId: id, GroupId: device.groupId}) + ret := make([]*apiv1.DeviceDescriptor, 0) + for _, device := range p.devices { + ret = append(ret, &device.Descriptor) } return ret } -func (p *DevicePool) GetDeviceInfoListByGroupId(groupId string) []*DeviceInfo { - p.devicesMtx.Lock() - defer p.devicesMtx.Unlock() - ret := make([]*DeviceInfo, 0) - group, ok := p.groups[groupId] - if !ok { - return ret - } - - for _, deviceId := range group.deviceIds { - ret = append(ret, &DeviceInfo{DeviceId: deviceId, GroupId: groupId}) +func (p *DevicePool) GetDeviceDescByGroupId(groupId string) []*apiv1.DeviceDescriptor { + ret := make([]*apiv1.DeviceDescriptor, 0) + devs := p.GetDeviceDescList() + for _, d := range devs { + if d.GroupId == groupId { + ret = append(ret, d) + } } return ret } diff --git a/frontend/src/liboperator/operator/devices_test.go b/frontend/src/liboperator/operator/devices_test.go index e1b5a6f872..0fdfd4ef2d 100644 --- a/frontend/src/liboperator/operator/devices_test.go +++ b/frontend/src/liboperator/operator/devices_test.go @@ -16,10 +16,12 @@ package operator import ( "testing" + + apiv1 "github.com/google/android-cuttlefish/frontend/src/liboperator/api/v1" ) func TestNewDevice(t *testing.T) { - d := NewDevice(nil, 0, "info") + d := newDevice("d1", nil, 0, "info") if d == nil { t.Error("null device") } @@ -40,7 +42,7 @@ func (c *MockClient) OnDeviceDisconnected() { } func TestDeviceRegister(t *testing.T) { - d := NewDevice(nil, 0, "info") + d := newDevice("d1", nil, 0, "info") // Unregistering unexisting client has no effect d.Unregister(9) // Register a client @@ -92,11 +94,11 @@ func TestPoolRegister(t *testing.T) { p := NewDevicePool() // Unregistering wrong device has no effect p.Unregister("id1") - d := NewDevice(nil, 0, "info1") if p.GetDevice("id1") != nil { t.Error("Empty pool returned device") } - if !p.Register(d, "id1") { + d := p.Register("id1", nil, 0, "info1") + if d == nil { t.Error("Failed to register new device") } if p.GetDevice("id1") != d { @@ -106,10 +108,10 @@ func TestPoolRegister(t *testing.T) { t.Error("Returned wrong device") } // Register with the same id - if p.Register(d, "id1") { + if p.Register("id1", nil, 0, "info1") != nil { t.Error("Didn't fail with same device id") } - if !p.Register(d, "id2") { + if p.Register("id2", nil, 0, "info1") == nil { t.Error("Failed to register with different id") } // Try to get device after unregistering another @@ -123,19 +125,59 @@ func TestPoolRegister(t *testing.T) { } } +func TestPoolPreRegister(t *testing.T) { + p := NewDevicePool() + p.CancelPreRegistration("inexistent_device") + dd1 := &apiv1.DeviceDescriptor{ + DeviceId: "d1", + GroupId: "g1", + Owner: "o1", + Name: "n1", + } + if err := p.PreRegister(dd1, make(chan bool, 1)); err != nil { + t.Fatal("Failed to pre-register: ", err) + } + + dd2 := &apiv1.DeviceDescriptor{ + DeviceId: "d2", + GroupId: "g2", + Owner: "o2", + Name: "n2", + } + if err := p.PreRegister(dd2, make(chan bool, 1)); err != nil { + t.Fatal("Failed to pre-register: ", err) + } + + p.CancelPreRegistration("d2") + + d1 := p.Register("d1", nil, 0, "foo") + if d1 == nil { + t.Fatal("Failed to register pre-registered device") + } + if d1.Descriptor.GroupId != "g1" || d1.Descriptor.Name != "n1" || d1.Descriptor.Owner != "o1" { + t.Fatal("Registered device doesn't match pre-registration: ", d1) + } + + d2 := p.Register("d2", nil, 0, "bar") + if d2 == nil { + t.Fatal("Failed to register un-pre-registered device") + } + if d2.Descriptor.GroupId == "g2" || d2.Descriptor.Name == "n2" || d2.Descriptor.Owner == "o2" { + t.Fatal("Device with cancelled pre-registration contains pre-registration data: ", d2) + } +} + func TestListDevices(t *testing.T) { p := NewDevicePool() - d1 := NewDevice(nil, 0, "info1") - d2 := NewDevice(nil, 0, "info2") if len(p.DeviceIds()) != 0 { t.Error("Empty pool listed devices") } - p.Register(d1, "1") + p.Register("1", nil, 0, "info1") l := p.DeviceIds() if len(l) != 1 || l[0] != "1" { t.Error("Error listing after 1 device registration: ", l) } - p.Register(d2, "2") + p.Register("2", nil, 0, "info2") l = p.DeviceIds() if len(l) != 2 || l[0] != "1" && l[0] != "2" || l[1] != "1" && l[1] != "2" || l[0] == l[1] { t.Error("Error listing after 2 device restrations: ", l) @@ -166,45 +208,38 @@ func MakeInfo(groupId string) map[string]interface{} { func TestListDevicesByGroup(t *testing.T) { p := NewDevicePool() - foo_d1 := NewDevice(nil, 0, MakeInfo("foo")) - foo_d2 := NewDevice(nil, 0, MakeInfo("foo")) - bar_d1 := NewDevice(nil, 0, MakeInfo("bar")) - bar_d2 := NewDevice(nil, 0, MakeInfo("bar")) - bar_d3 := NewDevice(nil, 0, MakeInfo("bar")) + p.Register("1", nil, 0, MakeInfo("foo")) + p.Register("2", nil, 0, MakeInfo("foo")) + p.Register("3", nil, 0, MakeInfo("bar")) + p.Register("4", nil, 0, MakeInfo("bar")) + p.Register("5", nil, 0, MakeInfo("bar")) - p.Register(foo_d1, "1") - p.Register(foo_d2, "2") - p.Register(bar_d1, "3") - p.Register(bar_d2, "4") - p.Register(bar_d3, "5") - - if deviceCnt := len(p.GetDeviceInfoListByGroupId("foo")); deviceCnt != 2 { + if deviceCnt := len(p.GetDeviceDescByGroupId("foo")); deviceCnt != 2 { t.Error("List of devices in group foo should have size of 2, but have ", deviceCnt) } - if deviceCnt := len(p.GetDeviceInfoListByGroupId("bar")); deviceCnt != 3 { + if deviceCnt := len(p.GetDeviceDescByGroupId("bar")); deviceCnt != 3 { t.Error("List of devices in group bar should have size of 3, but have ", deviceCnt) } } func TestListDevicesEmpty(t *testing.T) { p := NewDevicePool() - d := NewDevice(nil, 0, MakeInfo("foo")) - p.Register(d, "d") + p.Register("d", nil, 0, MakeInfo("foo")) p.Unregister("d") - if deviceCnt := len(p.GetDeviceInfoList()); deviceCnt != 0 { + if deviceCnt := len(p.GetDeviceDescList()); deviceCnt != 0 { t.Error("List of all devices should have size of 0, but have ", deviceCnt) } - if deviceCnt := len(p.GetDeviceInfoListByGroupId("foo")); deviceCnt != 0 { + if deviceCnt := len(p.GetDeviceDescByGroupId("foo")); deviceCnt != 0 { t.Error("List of devices in group foo should have size of 0, but have ", deviceCnt) } } -func TestGetGroupId(t *testing.T) { +func TestGroupIdFromPrivateData(t *testing.T) { info := MakeInfo("foo") - groupId := GetGroupId(info) + groupId := groupIdFromPrivateData(info) if groupId != "foo" { t.Error("info should have group_id as foo") @@ -214,58 +249,50 @@ func TestGetGroupId(t *testing.T) { func TestListGroups(t *testing.T) { p := NewDevicePool() - d1 := NewDevice(nil, 0, MakeInfo("group1")) - d2 := NewDevice(nil, 0, MakeInfo("group2")) - d3 := NewDevice(nil, 0, MakeInfo("group2")) if len(p.GroupIds()) != 0 { t.Error("Empty pool listed groups") } - p.Register(d1, "d1") - p.Register(d2, "d2") - p.Register(d3, "d3") + p.Register("d1", nil, 0, MakeInfo("group1")) + p.Register("d2", nil, 0, MakeInfo("group2")) + p.Register("d3", nil, 0, MakeInfo("group2")) if len(p.devices) != 3 { t.Error("Error listing after 3 device registrations - expected 3 but ", len(p.devices)) } - if len(p.groups) != 2 { - t.Error("Error listing after 3 device registrations - expected 2 but ", len(p.groups)) + if len(p.GroupIds()) != 2 { + t.Error("Error listing after 3 device registrations - expected 2 but ", len(p.GroupIds())) } p.Unregister("d1") - if len(p.groups) != 1 { - t.Error("Error listing after 3 device registrations, 1 unregistration - expected 1 but ", len(p.groups)) + if len(p.GroupIds()) != 1 { + t.Error("Error listing after 3 device registrations, 1 unregistration - expected 1 but ", len(p.GroupIds())) } p.Unregister(("d2")) - if len(p.groups) != 1 { - t.Error("Error listing after 3 device registrations, 2 unregistrations - expected 1 but ", len(p.groups)) + if len(p.GroupIds()) != 1 { + t.Error("Error listing after 3 device registrations, 2 unregistrations - expected 1 but ", len(p.GroupIds())) } } func TestDefaultGroup(t *testing.T) { p := NewDevicePool() - d1 := NewDevice(nil, 0, MakeInfo("foo")) - d2 := NewDevice(nil, 0, map[string]interface{}{}) - d3 := NewDevice(nil, 0, "") - d4 := NewDevice(nil, 0, MakeInfo("")) - - p.Register(d1, "d1") - p.Register(d2, "d2") - p.Register(d3, "d3") - p.Register(d4, "d4") + p.Register("d1", nil, 0, MakeInfo("foo")) + p.Register("d2", nil, 0, map[string]interface{}{}) + p.Register("d3", nil, 0, "") + p.Register("d4", nil, 0, MakeInfo("")) if len(p.devices) != 4 { t.Error("Error listing after 4 device registrations - expected 4 but ", len(p.devices)) } - if defaultDeviceCount := len(p.groups[DEFAULT_GROUP_ID].deviceIds); defaultDeviceCount != 3 { + if defaultDeviceCount := len(p.GetDeviceDescByGroupId(DEFAULT_GROUP_ID)); defaultDeviceCount != 3 { t.Error("Error after 3 device in default group - expected 3 but ", defaultDeviceCount) } - if defaultDeviceCount := len(p.GetDeviceInfoListByGroupId(DEFAULT_GROUP_ID)); defaultDeviceCount != 3 { + if defaultDeviceCount := len(p.GetDeviceDescByGroupId(DEFAULT_GROUP_ID)); defaultDeviceCount != 3 { t.Error("Error after 3 device in default group - expected 3 but ", defaultDeviceCount) } diff --git a/frontend/src/liboperator/operator/errors.go b/frontend/src/liboperator/operator/errors.go index 0f29e4a138..cdb2df85a0 100644 --- a/frontend/src/liboperator/operator/errors.go +++ b/frontend/src/liboperator/operator/errors.go @@ -22,7 +22,6 @@ import ( type AppError struct { Msg string - Details string StatusCode int Err error } @@ -39,9 +38,7 @@ func (e *AppError) Unwrap() error { } func (e *AppError) JSONResponse() apiv1.ErrorMsg { - // Include only the high level error message in the error response, the - // lower level errors are just for logging - return apiv1.ErrorMsg{Error: e.Msg, Details: e.Details} + return apiv1.ErrorMsg{Error: e.Msg, Details: e.Error()} } func NewBadRequestError(msg string, e error) error { @@ -52,10 +49,6 @@ func NewInternalError(msg string, e error) error { return &AppError{Msg: msg, StatusCode: http.StatusInternalServerError, Err: e} } -func NewInternalErrorD(msg string, details string, e error) error { - return &AppError{Msg: msg, Details: details, StatusCode: http.StatusInternalServerError, Err: e} -} - func NewNotFoundError(msg string, e error) error { return &AppError{Msg: msg, StatusCode: http.StatusNotFound, Err: e} } diff --git a/frontend/src/liboperator/operator/operator.go b/frontend/src/liboperator/operator/operator.go index 9c09515ff2..8794dd556d 100644 --- a/frontend/src/liboperator/operator/operator.go +++ b/frontend/src/liboperator/operator/operator.go @@ -35,26 +35,34 @@ import ( emptypb "google.golang.org/protobuf/types/known/emptypb" ) -// Sets up a unix socket for devices to connect to and returns a function that listens on the -// socket until an error occurrs. -func SetupDeviceEndpoint(pool *DevicePool, config apiv1.InfraConfig, path string) func() error { +func createUnixSocketEndpoint(path string) (*net.UnixListener, error) { if err := os.RemoveAll(path); err != nil { - return func() error { return fmt.Errorf("Failed to clean previous socket: %w", err) } + return nil, fmt.Errorf("Failed to clean previous socket: %w", err) } addr, err := net.ResolveUnixAddr("unixpacket", path) if err != nil { // Returns a loop function that will immediately return an error when invoked - return func() error { return fmt.Errorf("Failed to create unix address from path: %w", err) } + return nil, fmt.Errorf("Failed to create unix address from path: %w", err) } sock, err := net.ListenUnix("unixpacket", addr) if err != nil { - return func() error { return fmt.Errorf("Failed to create unix socket: %w", err) } + return nil, fmt.Errorf("Failed to create unix socket: %w", err) } // Make sure the socket is only accessible by owner and group if err := os.Chmod(path, 0770); err != nil { // This shouldn't happen since the creation of the socket just succeeded log.Println("Failed to change permissions on socket file: ", err) } + return sock, err +} + +// Sets up a unix socket for devices to connect to and returns a function that listens on the +// socket until an error occurrs. +func SetupDeviceEndpoint(pool *DevicePool, config apiv1.InfraConfig, path string) (func() error, error) { + sock, err := createUnixSocketEndpoint(path) + if err != nil { + return nil, err + } log.Println("Device endpoint created") // Serve the register_device endpoint in a background thread return func() error { @@ -66,7 +74,28 @@ func SetupDeviceEndpoint(pool *DevicePool, config apiv1.InfraConfig, path string } go deviceEndpoint(NewJSONUnix(c), pool, config) } + }, nil +} + +// Sets up a unix socket for server control and returns a function that listens on the socket +// until an error occurrs. +func SetupControlEndpoint(pool *DevicePool, path string) (func() error, error) { + sock, err := createUnixSocketEndpoint(path) + if err != nil { + return nil, err } + log.Println("Control endpoint created") + // Serve the register_device endpoint in a background thread + return func() error { + defer sock.Close() + for { + c, err := sock.AcceptUnix() + if err != nil { + return err + } + go controlEndpoint(NewJSONUnix(c), pool) + } + }, nil } // Creates a router with handlers for the following endpoints: @@ -141,6 +170,112 @@ func CreateHttpHandlers( return router } +// Control endpoint +func controlEndpoint(c *JSONUnix, pool *DevicePool) { + log.Println("Controller connected") + defer c.Close() + + msg := make(map[string]interface{}) + if err := c.Recv(&msg); err != nil { + log.Println("Error reading control message: ", err) + return + } + typeRaw, ok := msg["message_type"] + if !ok { + log.Println("Control message doesn't include message_type: ", msg) + return + } + typeStr, ok := typeRaw.(string) + if !ok { + log.Println("Control message's message_type field is not a string: ", msg) + return + } + switch typeStr { + case "pre-register": + var preRegisterMsg apiv1.PreRegisterMsg + // Ignore errors, msg was parsed from json so this can't fail + j, _ := json.Marshal(msg) + json.Unmarshal(j, &preRegisterMsg) + PreRegister(c, pool, &preRegisterMsg) + default: + log.Println("Invalid control type: ", typeStr) + return + } +} + +func PreRegister(c *JSONUnix, pool *DevicePool, msg *apiv1.PreRegisterMsg) { + var ret apiv1.PreRegistrationResponse + idSet := make(map[string]bool) + // Lenght ensures no writers will block on this channel + devCh := make(chan string, len(msg.Devices)) + for _, d := range msg.Devices { + regCh := make(chan bool, 1) + err := pool.PreRegister( + &apiv1.DeviceDescriptor{ + DeviceId: d.Id, + GroupId: msg.GroupName, + Owner: msg.Owner, + Name: d.Name, + }, + regCh, + ) + status := apiv1.RegistrationStatusReport{ + Id: d.Id, + Status: "accepted", + } + if err != nil { + status.Status = "failed" + status.Msg = err.Error() + } else { + idSet[d.Id] = true + go func(regCh chan bool, devCh chan string, id string) { + registered, ok := <-regCh + // The channel is closed if cancelled, true is sent when registration succeeds. + if registered && ok { + devCh <- id + } + }(regCh, devCh, d.Id) + } + ret = append(ret, status) + } + + if err := c.Send(ret); err != nil { + log.Println("Error sending pre-registration response: ", err) + return + } + + closeCh := make(chan bool) + go func(c *JSONUnix, ch chan bool) { + // Wait until socket closes or produces another error + var err error + for err == nil { + var ignored interface{} + err = c.Recv(ignored) + } + ch <- true + }(c, closeCh) + + for { + select { + case id := <-devCh: + evt := apiv1.RegistrationStatusReport{ + Id: id, + Status: "registered", + } + if err := c.Send(&evt); err != nil { + log.Println("Error sending registration event: ", err) + } + delete(idSet, id) + case <-closeCh: + // Cancel all pending pre-registrations when this function returns + for id := range idSet { + pool.CancelPreRegistration(id) + } + return + } + } +} + // Device endpoint func deviceEndpoint(c *JSONUnix, pool *DevicePool, config apiv1.InfraConfig) { log.Println("Device connected") @@ -150,10 +285,6 @@ func deviceEndpoint(c *JSONUnix, pool *DevicePool, config apiv1.InfraConfig) { log.Println("Error reading from device: ", err) return } - if msg.Type != "register" { - ReplyError(c, "First device message must be the registration") - return - } id := msg.DeviceId if id == "" { ReplyError(c, "Missing device_id") @@ -165,8 +296,8 @@ func deviceEndpoint(c *JSONUnix, pool *DevicePool, config apiv1.InfraConfig) { info = make(map[string]interface{}) } port := msg.Port - device := NewDevice(c, port, info) - if !pool.Register(device, id) { + device := pool.Register(id, c, port, info) + if device == nil { ReplyError(c, fmt.Sprintln("Device id already taken: ", id)) return } @@ -181,10 +312,6 @@ func deviceEndpoint(c *JSONUnix, pool *DevicePool, config apiv1.InfraConfig) { log.Println("Error reading from device: ", err) return } - if msg.Type != "forward" { - ReplyError(c, fmt.Sprintln("Unrecognized message type: ", msg.Type)) - return - } clientId := msg.ClientId if clientId == 0 { ReplyError(c, "Device forward message missing client id") @@ -333,7 +460,7 @@ func openwrt(w http.ResponseWriter, r *http.Request, pool *DevicePool) { return } - devInfo := dev.info.(map[string]interface{}) + devInfo := dev.privateData.(map[string]interface{}) openwrtDevId, ok := devInfo["openwrt_device_id"].(string) if !ok { http.Error(w, "Device obtaining Openwrt not found", http.StatusNotFound) @@ -364,13 +491,13 @@ func listDevices(w http.ResponseWriter, r *http.Request, pool *DevicePool) { groupId := r.URL.Query().Get("groupId") if len(groupId) == 0 { - if err := ReplyJSONOK(w, pool.GetDeviceInfoList()); err != nil { + if err := ReplyJSONOK(w, pool.GetDeviceDescList()); err != nil { log.Println(err) } return } - if err := ReplyJSONOK(w, pool.GetDeviceInfoListByGroupId(groupId)); err != nil { + if err := ReplyJSONOK(w, pool.GetDeviceDescByGroupId(groupId)); err != nil { log.Println(err) } } @@ -385,7 +512,7 @@ func deviceInfo(w http.ResponseWriter, r *http.Request, pool *DevicePool) { http.NotFound(w, r) return } - ReplyJSONOK(w, apiv1.DeviceInfoReply{DeviceId: devId, RegistrationInfo: dev.info}) + ReplyJSONOK(w, apiv1.DeviceInfoReply{DeviceDescriptor: dev.Descriptor, RegistrationInfo: dev.privateData}) } func deviceFiles(w http.ResponseWriter, r *http.Request, pool *DevicePool, maybeIntercept func(string) *string) { @@ -422,7 +549,7 @@ func createPolledConnection(w http.ResponseWriter, r *http.Request, pool *Device return } conn := polledSet.NewConnection(device) - reply := apiv1.NewConnReply{ConnId: conn.Id(), DeviceInfo: device.info} + reply := apiv1.NewConnReply{ConnId: conn.Id(), DeviceInfo: device.privateData} ReplyJSONOK(w, reply) } diff --git a/frontend/src/liboperator/operator/utils.go b/frontend/src/liboperator/operator/utils.go index 020a6ca492..c6bc2d14bd 100644 --- a/frontend/src/liboperator/operator/utils.go +++ b/frontend/src/liboperator/operator/utils.go @@ -61,7 +61,6 @@ func ReplyJSON(w http.ResponseWriter, obj interface{}, statusCode int) error { return encoder.Encode(obj) } - // Connect ControlEnvProxyServer func ConnectControlEnvProxyServer(devId string, pool *DevicePool) (*grpc.ClientConn, error) { dev := pool.GetDevice(devId) @@ -69,10 +68,10 @@ func ConnectControlEnvProxyServer(devId string, pool *DevicePool) (*grpc.ClientC return nil, errors.New("Device not found") } - devInfo := dev.info.(map[string]interface{}) + devInfo := dev.privateData.(map[string]interface{}) serverPath, ok := devInfo["control_env_proxy_server_path"].(string) if !ok { return nil, errors.New("ControlEnvProxyServer path not found") } - return grpc.Dial("unix://" + serverPath, grpc.WithInsecure()) + return grpc.Dial("unix://"+serverPath, grpc.WithInsecure()) } diff --git a/frontend/src/operator/intercept/js/server_connector.js b/frontend/src/operator/intercept/js/server_connector.js index c33ba600a6..d70a2bac0e 100644 --- a/frontend/src/operator/intercept/js/server_connector.js +++ b/frontend/src/operator/intercept/js/server_connector.js @@ -90,6 +90,13 @@ class Connector { async sendToDevice(msg) { throw 'Not implemented!'; } + + // Provides a hint to this controller that it should expect messages from the + // signaling server soon. This is useful for a connector which polls for + // example which might want to poll more quickly for a period of time. + expectMessagesSoon(durationMilliseconds) { + throw 'Not implemented!'; + } } // Returns real implementation for ParentController. @@ -160,6 +167,8 @@ async function ajaxPostJson(url, data) { return response.json(); } +const SHORT_POLL_DELAY = 1000; + // Implementation of the Connector interface using HTTP polling class PollingConnector extends Connector { #configUrl = httpUrl('infra_config'); @@ -169,6 +178,7 @@ class PollingConnector extends Connector { #config = undefined; #messagesReceived = 0; #pollerSchedule; + #pollQuicklyUntil = Date.now(); #onDeviceMsgCb = msg => console.error('Received device message without registered listener'); @@ -222,27 +232,46 @@ class PollingConnector extends Connector { return arr; } + #calcNextPollDelay(previousPollDelay) { + if (Date.now() < this.#pollQuicklyUntil) { + return SHORT_POLL_DELAY; + } else { + // Do exponential backoff on the polling up to 60 seconds + return Math.min(60000, 2 * previousPollDelay); + } + } + #startPolling() { if (this.#pollerSchedule !== undefined) { return; } - let currentPollDelay = 1000; + let currentPollDelay = SHORT_POLL_DELAY; let pollerRoutine = async () => { let messages = await this.#pollMessages(); - // Do exponential backoff on the polling up to 60 seconds - currentPollDelay = Math.min(60000, 2 * currentPollDelay); + currentPollDelay = this.#calcNextPollDelay(currentPollDelay); + for (const message of messages) { this.#onDeviceMsgCb(message.payload); // There is at least one message, poll sooner - currentPollDelay = 1000; + currentPollDelay = SHORT_POLL_DELAY; } this.#pollerSchedule = setTimeout(pollerRoutine, currentPollDelay); }; this.#pollerSchedule = setTimeout(pollerRoutine, currentPollDelay); } + + expectMessagesSoon(durationMilliseconds) { + console.debug("Polling frequently for ", durationMilliseconds, " ms."); + + clearTimeout(this.#pollerSchedule); + this.#pollerSchedule = undefined; + + this.#pollQuicklyUntil = Date.now() + durationMilliseconds; + this.#startPolling(); + } } export class DisplayInfo { diff --git a/frontend/src/operator/main.go b/frontend/src/operator/main.go index e039954fb2..7fab437f51 100644 --- a/frontend/src/operator/main.go +++ b/frontend/src/operator/main.go @@ -15,6 +15,7 @@ package main import ( + "flag" "fmt" "log" "net/http" @@ -28,25 +29,26 @@ import ( ) const ( - DefaultSocketPath = "/run/cuttlefish/operator" - DefaultHttpPort = "1080" - DefaultHttpsPort = "1443" - DefaultTLSCertDir = "/etc/cuttlefish-common/operator/cert" - DefaultStaticFilesDir = "static" // relative path - DefaultInterceptDir = "intercept" // relative path - DefaultWebUIUrl = "" + DefaultSocketPath = "/run/cuttlefish/operator" + DefaultControlSocketPath = "/run/cuttlefish/operator_control" + DefaultHttpPort = 1080 + DefaultTLSCertDir = "/etc/cuttlefish-common/operator/cert" + DefaultStaticFilesDir = "static" // relative path + DefaultInterceptDir = "intercept" // relative path + DefaultWebUIUrl = "" + DefaultListenAddress = "127.0.0.1" ) -func startHttpServer(port string) error { +func startHttpServer(address string, port int) error { log.Println(fmt.Sprint("Operator is listening at http://localhost:", port)) // handler is nil, so DefaultServeMux is used. - return http.ListenAndServe(fmt.Sprint(":", port), nil) + return http.ListenAndServe(fmt.Sprintf("%s:%d", address, port), nil) } -func startHttpsServer(port string, certPath string, keyPath string) error { +func startHttpsServer(address string, port int, certPath string, keyPath string) error { log.Println(fmt.Sprint("Operator is listening at https://localhost:", port)) - return http.ListenAndServeTLS(fmt.Sprint(":", port), + return http.ListenAndServeTLS(fmt.Sprintf("%s:%d", address, port), certPath, keyPath, // handler is nil, so DefaultServeMux is used. @@ -88,14 +90,18 @@ func start(starters []func() error) { } func main() { - socketPath := fromEnvOrDefault("OPERATOR_SOCKET_PATH", DefaultSocketPath) - httpPort := fromEnvOrDefault("OPERATOR_HTTP_PORT", DefaultHttpPort) - httpsPort := fromEnvOrDefault("OPERATOR_HTTPS_PORT", DefaultHttpsPort) - tlsCertDir := fromEnvOrDefault("OPERATOR_TLS_CERT_DIR", DefaultTLSCertDir) - webUiUrlStr := fromEnvOrDefault("OPERATOR_WEBUI_URL", DefaultWebUIUrl) + socketPath := flag.String("socket_path", DefaultSocketPath, "Path to the device endpoint unix socket.") + controlSocketPath := flag.String("control_socket_path", DefaultControlSocketPath, "Path to the control endpoint unix socket.") + httpPort := flag.Int("http_port", DefaultHttpPort, "Port to serve HTTP requests on.") + httpsPort := flag.Int("https_port", -1, "Port to serve HTTPS requests on.") + tlsCertDir := flag.String("tls_cert_dir", DefaultTLSCertDir, "Directory where the TLS certificates are located.") + webUiUrlStr := flag.String("webui_url", DefaultWebUIUrl, "WebUI URL.") + address := flag.String("listen_addr", DefaultListenAddress, "IP address to listen for requests.") - certPath := tlsCertDir + "/cert.pem" - keyPath := tlsCertDir + "/key.pem" + flag.Parse() + + certPath := *tlsCertDir + "/cert.pem" + keyPath := *tlsCertDir + "/key.pem" pool := operator.NewDevicePool() polledSet := operator.NewPolledSet() @@ -107,8 +113,8 @@ func main() { } r := operator.CreateHttpHandlers(pool, polledSet, config, maybeIntercept) - if len(webUiUrlStr) > 0 { - webUiUrl, _ := url.Parse(webUiUrlStr) + if *webUiUrlStr != "" { + webUiUrl, _ := url.Parse(*webUiUrlStr) proxy := httputil.NewSingleHostReverseProxy(webUiUrl) r.PathPrefix("/").Handler(proxy) } else { @@ -118,9 +124,26 @@ func main() { http.Handle("/", r) starters := []func() error{ - func() error { return operator.SetupDeviceEndpoint(pool, config, socketPath)() }, - func() error { return startHttpsServer(httpsPort, certPath, keyPath) }, - func() error { return startHttpServer(httpPort) }, + func() error { + st, err := operator.SetupControlEndpoint(pool, *controlSocketPath) + if err != nil { + return err + } + return st() + }, + func() error { + st, err := operator.SetupDeviceEndpoint(pool, config, *socketPath) + if err != nil { + return err + } + return st() + }, + func() error { return startHttpServer(*address, *httpPort) }, + } + if *httpsPort > 0 { + starters = append(starters, func() error { + return startHttpsServer(*address, *httpsPort, certPath, keyPath) + }) } start(starters) } diff --git a/frontend/src/operator/webui/package-lock.json b/frontend/src/operator/webui/package-lock.json index d71f0fdadf..e823e07000 100644 --- a/frontend/src/operator/webui/package-lock.json +++ b/frontend/src/operator/webui/package-lock.json @@ -721,12 +721,13 @@ "dev": true }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -926,9 +927,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" @@ -947,25 +948,39 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name/node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1107,18 +1122,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -1163,13 +1178,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -1177,9 +1192,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", - "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -2344,19 +2359,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", - "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -2365,12 +2380,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", - "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.21.4", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -2379,6 +2394,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/traverse/node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -2394,13 +2421,13 @@ } }, "node_modules/@babel/types": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", - "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -7551,9 +7578,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true, "funding": [ { @@ -14483,12 +14510,13 @@ "dev": true }, "@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -14641,9 +14669,9 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-explode-assignable-expression": { @@ -14656,22 +14684,35 @@ } }, "@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "dependencies": { + "@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + } + } } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -14777,15 +14818,15 @@ } }, "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -14818,20 +14859,20 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", - "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -15616,35 +15657,44 @@ } }, "@babel/traverse": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", - "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { "@babel/generator": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", - "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.21.4", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" } }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, "@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -15659,13 +15709,13 @@ } }, "@babel/types": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", - "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -19654,9 +19704,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true }, "forwarded": { diff --git a/frontend/src/operator/webui/src/app/view-pane/view-pane.component.ts b/frontend/src/operator/webui/src/app/view-pane/view-pane.component.ts index c403e5bd28..c14b45a3fb 100644 --- a/frontend/src/operator/webui/src/app/view-pane/view-pane.component.ts +++ b/frontend/src/operator/webui/src/app/view-pane/view-pane.component.ts @@ -9,6 +9,7 @@ import { } from '@angular/core'; import {DisplaysService} from '../displays.service'; import { + asyncScheduler, BehaviorSubject, merge, fromEvent, @@ -237,6 +238,10 @@ export class ViewPaneComponent implements OnInit, OnDestroy, AfterViewInit { item.placed = true; + asyncScheduler.schedule(() => { + this.forceShowDevice(item.id); + }, 10000); + return item; } @@ -259,6 +264,13 @@ export class ViewPaneComponent implements OnInit, OnDestroy, AfterViewInit { : updateItem.values; updateItem.overwrites.forEach(prop => { + // Ignore force show display message when display size is already set + if ((prop === 'display_width' || prop === 'display_height') + && item[prop] !== null && item[prop] !== this.freeScale + && updateItem.values[prop] === this.freeScale) { + return; + } + item[prop] = updateItem.values[prop]!; }); diff --git a/tools/buildimage/increase_disk_size.sh b/tools/buildimage/increase_disk_size.sh new file mode 100644 index 0000000000..b84a69b6bb --- /dev/null +++ b/tools/buildimage/increase_disk_size.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Copyright (C) 2024 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. + +# Increases image's disk size increasing the partition `1` size. + +set -e + +usage() { + echo "usage: $0 -r /path/to/disk.raw" +} + +diskraw= + +# Defaults to a final image size of 20.00GiB. Original image has a size of 2.00GiB. +# TODO: Make the increase size value an option. +inc_in_gib=18 + +while getopts ":hr:i:" opt; do + case "${opt}" in + h) + usage + exit 0 + ;; + r) + diskraw="${OPTARG}" + ;; + \?) + echo "Invalid option: ${OPTARG}" >&2 + usage + exit 1 + ;; + :) + echo "Invalid option: ${OPTARG} requires an argument" >&2 + usage + exit 1 + ;; + esac +done + +echo -e "Original partition table:" +sudo parted ${diskraw} unit GiB print free +echo "Appending ${inc_in_gib}GiB of null characters making the required space ..." +dd if=/dev/zero bs=1G count=${inc_in_gib} >> ${diskraw} +# Answer "Fix" to the next prompt when using `parted`. +# Warning: Not all of the space available to ${diskraw} appears to be used, you can fix the GPT to use all of the space (an extra 2014 blocks) or continue with the current setting? +# Fix/Ignore? +printf "fix\n" | sudo parted ---pretend-input-tty ${diskraw} unit B print free +new_end=$(sudo parted -s ${diskraw} unit B print free 2>/dev/null | awk '/Free Space$/ {print $2}' | tail -n 1) +echo "Resizing partition ..." +sudo parted ${diskraw} resizepart 1 ${new_end} 1> /dev/null +sudo parted ${diskraw} unit GiB print free +# Update the filesystem table catching up with new size +sudo kpartx -a ${diskraw} +loopdev=$(sudo losetup -l | awk -v pat="${diskraw}" '$0~pat {print $1}') +mapperdev=/dev/mapper/$(basename -- "$loopdev")p1 +sudo e2fsck -f -y -v -C 0 ${mapperdev} +sudo resize2fs -p ${mapperdev} +sudo kpartx -d ${diskraw} + diff --git a/tools/buildimage/install_cf_packages.sh b/tools/buildimage/install_cf_packages.sh new file mode 100644 index 0000000000..67c216359c --- /dev/null +++ b/tools/buildimage/install_cf_packages.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +# Copyright (C) 2024 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. + +# Install relevant cuttlefish packages from https://github.com/google/android-cuttlefish. + +# IMPORTANT!!! This script expects an `image_bullseye_gce_amd64.raw` file +# from https://salsa.debian.org/cloud-team/debian-cloud-images + +set -e + +usage() { + echo "usage: $0 -r /path/to/disk.raw -p /path/to/packages_dir" + exit 1 +} + +diskraw= +packages_dir= + +while getopts ":hr:p:" opt; do + case "${opt}" in + h) + usage + ;; + r) + diskraw="${OPTARG}" + ;; + p) + packages_dir="${OPTARG}" + ;; + \?) + echo "Invalid option: ${OPTARG}" >&2 + usage + ;; + :) + echo "Invalid option: ${OPTARG} requires an argument" >&2 + usage + ;; + esac +done + +mount_point="/mnt/image" +sudo mkdir ${mount_point} +# offset value is 262144 * 512, the `262144`th is the sector where the `Linux filesystem` partition +# starts and `512` bytes is the sectors size. See `sudo fdisk -l disk.raw`. +sudo mount -o loop,offset=$((262144 * 512)) ${diskraw} ${mount_point} + +cp ${packages_dir}/cuttlefish-base_0.9.28_amd64.deb ${mount_point}/tmp/ +cp ${packages_dir}/cuttlefish-user_0.9.29_amd64.deb ${mount_point}/tmp/ +cp ${packages_dir}/cuttlefish-orchestration_0.9.29_amd64.deb ${mount_point}/tmp/ + +sudo chroot /mnt/image mkdir /run/resolvconf +sudo cp /etc/resolv.conf /mnt/image/run/resolvconf/resolv.conf + +sudo chroot ${mount_point} apt update +sudo chroot ${mount_point} apt install -y /tmp/cuttlefish-base_0.9.28_amd64.deb +sudo chroot ${mount_point} apt install -y /tmp/cuttlefish-user_0.9.29_amd64.deb +sudo chroot ${mount_point} apt install -y /tmp/cuttlefish-orchestration_0.9.29_amd64.deb + +sudo rm -r ${mount_point}/run/resolvconf + +sudo umount ${mount_point} +sudo rm -r ${mount_point} diff --git a/tools/buildimage/install_gce_guest_environment.sh b/tools/buildimage/install_gce_guest_environment.sh new file mode 100644 index 0000000000..020214270e --- /dev/null +++ b/tools/buildimage/install_gce_guest_environment.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# Copyright (C) 2024 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. + +# Install GCP guest environment: https://cloud.google.com/compute/docs/images/install-guest-environment + +# IMPORTANT!!! This script expects debian-11-genericcloud-amd64-20240104-1616.raw image. +# https://cloud.debian.org/images/cloud/bullseye/20240104-1616/debian-11-genericcloud-amd64-20240104-1616.raw + +usage() { + echo "usage: $0 -r /path/to/disk.raw" + exit 1 +} + +diskraw= + +while getopts ":hr:" opt; do + case "${opt}" in + h) + usage + ;; + r) + diskraw="${OPTARG}" + ;; + \?) + echo "Invalid option: ${OPTARG}" >&2 + usage + ;; + :) + echo "Invalid option: ${OPTARG} requires an argument" >&2 + usage + ;; + esac +done + +mount_point="/mnt/image" +sudo mkdir ${mount_point} + +# offset value is 262144 * 512, the `262144`th is the sector where the `Linux filesystem` partition +# starts and `512` bytes is the sectors size. See `sudo fdisk -l disk.raw`. +sudo mount -o loop,offset=$((262144 * 512)) ${diskraw} ${mount_point} + +sudo chroot /mnt/image mkdir /run/resolvconf +sudo cp /etc/resolv.conf /mnt/image/run/resolvconf/resolv.conf + +cat <<'EOF' >${mount_point}/tmp/install.sh +#!/bin/bash +echo "== Installing Google guest environment for Debian ==" +export DEBIAN_FRONTEND=noninteractive +echo "Determining Debian version..." +eval $(grep VERSION_CODENAME /etc/os-release) +if [[ -z $VERSION_CODENAME ]]; then + echo "ERROR: Could not determine Debian version." + exit 1 +fi +echo "Running apt update..." +apt update +echo "Installing gnupg..." +apt install -y gnupg +echo "Adding GPG key for Google cloud repo." +curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - +echo "Updating repo file..." +tee "/etc/apt/sources.list.d/google-cloud.list" << EOM +deb http://packages.cloud.google.com/apt google-compute-engine-${VERSION_CODENAME}-stable main +deb http://packages.cloud.google.com/apt google-cloud-packages-archive-keyring-${VERSION_CODENAME} main +EOM +echo "Running apt update..." +apt update +echo "Installing packages..." +for pkg in google-cloud-packages-archive-keyring google-compute-engine; do + echo "Running apt install ${pkg}..." + apt install -y ${pkg} + if [[ $? -ne 0 ]]; then + echo "ERROR: Failed to install ${pkg}." + fi +done +EOF +sudo chroot /mnt/image bash /tmp/install.sh + +sudo rm -r ${mount_point}/run/resolvconf + +sudo umount ${mount_point} +sudo rm -r ${mount_point} diff --git a/tools/buildimage/main.sh b/tools/buildimage/main.sh new file mode 100644 index 0000000000..a8dd993cfe --- /dev/null +++ b/tools/buildimage/main.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Copyright (C) 2024 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. + +# Build custom image to be used in GCE VMs. + +set -e + +script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) + +pushd /tmp +wget https://cloud.debian.org/images/cloud/bullseye/20240104-1616/debian-11-genericcloud-amd64-20240104-1616.raw +popd + +out_dir=$(pwd) +cp /tmp/debian-11-genericcloud-amd64-20240104-1616.raw ${out_dir}/disk.raw + +bash ${script_dir}/install_gce_guest_environment.sh -r ${out_dir}/disk.raw +bash ${script_dir}/install_cf_packages.sh -r ${out_dir}/disk.raw -p $(pwd) +bash ${script_dir}/increase_disk_size.sh -r ${out_dir}/disk.raw + +tar -czvf image.tar.gz -C ${out_dir} disk.raw diff --git a/tools/imagetogce/main.sh b/tools/imagetogce/main.sh new file mode 100644 index 0000000000..60d734eefa --- /dev/null +++ b/tools/imagetogce/main.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +# Copyright (C) 2024 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. + +# Export the host image to GCP. +# +# Image Name Format: cf-debian11-amd64-YYYYMMDD-commit +# +# IMPORTANT!!! Artifact download URL only work for registered users (404 for guests) +# https://github.com/actions/upload-artifact/issues/51 + +set -e + +usage() { + echo "usage: $0 -s head-commit-sha -t /path/to/github_auth_token.txt -b bucket-name -p project-name -f image-family-name" +} + +commit_sha= +github_auth_token_filename= +gce_bucket= +image_dest_project= +image_family_name= + +while getopts ":hs:t:p:b:f:" opt; do + case "${opt}" in + h) + usage + exit 0 + ;; + s) + commit_sha="${OPTARG}" + ;; + t) + github_auth_token_filename="${OPTARG}" + ;; + b) + gce_bucket="${OPTARG}" + ;; + p) + image_dest_project="${OPTARG}" + ;; + f) + image_family_name="${OPTARG}" + ;; + \?) + echo "Invalid option: ${OPTARG}" >&2 + usage + exit 1 + ;; + :) + echo "Invalid option: ${OPTARG} requires an argument" >&2 + usage + exit 1 + ;; + esac +done + +jq_select=$(echo ".workflow_run.head_sha == \"${commit_sha}\" and .name == \"image_gce_debian11_amd64\"") + +artifact=$(curl -L \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $(cat ${github_auth_token_filename})" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/google/android-cuttlefish/actions/artifacts 2> /dev/null \ + | jq ".artifacts[] | select(${jq_select})") + +updated_at=$(echo $artifact | jq -r ".updated_at") +date_suffix=$(date -u -d ${updated_at} +"%Y%m%d") +name=cf-debian11-amd64-${date_suffix}-${commit_sha:0:7} + +function cleanup { + rm "image.zip" 2> /dev/null + rm "image.tar.gz" 2> /dev/null + gcloud storage rm gs://${gce_bucket}/${name}.tar.gz 2> /dev/null +} + +trap cleanup EXIT + +download_url=$(echo $artifact | jq -r ".archive_download_url") +curl -L \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $(cat ${github_auth_token_filename})" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + --output image.zip \ + ${download_url} + +unzip image.zip + +gcloud storage cp image.tar.gz gs://${gce_bucket}/${name}.tar.gz + +echo "Creating image ..." +gcloud compute images create ${name} \ + --source-uri gs://${gce_bucket}/${name}.tar.gz \ + --project ${image_dest_project} \ + --family ${image_family_name} diff --git a/tools/minimerge/.gitignore b/tools/minimerge/.gitignore new file mode 100644 index 0000000000..846f9618f5 --- /dev/null +++ b/tools/minimerge/.gitignore @@ -0,0 +1 @@ +minimerge_tool diff --git a/tools/minimerge/cuttlefish_args.txt b/tools/minimerge/cuttlefish_args.txt new file mode 100644 index 0000000000..db83d252b2 --- /dev/null +++ b/tools/minimerge/cuttlefish_args.txt @@ -0,0 +1,472 @@ +--map=common/libs/fs/epoll.cpp:base/cvd/cuttlefish/common/libs/fs/epoll.cpp +--map=common/libs/fs/epoll.h:base/cvd/cuttlefish/common/libs/fs/epoll.h +--map=common/libs/fs/shared_buf.cc:base/cvd/cuttlefish/common/libs/fs/shared_buf.cc +--map=common/libs/fs/shared_buf.h:base/cvd/cuttlefish/common/libs/fs/shared_buf.h +--map=common/libs/fs/shared_fd.cpp:base/cvd/cuttlefish/common/libs/fs/shared_fd.cpp +--map=common/libs/fs/shared_fd.h:base/cvd/cuttlefish/common/libs/fs/shared_fd.h +--map=common/libs/fs/shared_fd_stream.cpp:base/cvd/cuttlefish/common/libs/fs/shared_fd_stream.cpp +--map=common/libs/fs/shared_fd_stream.h:base/cvd/cuttlefish/common/libs/fs/shared_fd_stream.h +--map=common/libs/fs/shared_fd_test.cpp:base/cvd/cuttlefish/common/libs/fs/shared_fd_test.cpp +--map=common/libs/fs/shared_select.h:base/cvd/cuttlefish/common/libs/fs/shared_select.h +--map=common/libs/fs/vm_sockets.h:base/cvd/cuttlefish/common/libs/fs/vm_sockets.h +--map=common/libs/tcp_socket/tcp_socket.cpp:base/cvd/cuttlefish/common/libs/utils/tcp_socket.cpp +--map=common/libs/tcp_socket/tcp_socket.h:base/cvd/cuttlefish/common/libs/utils/tcp_socket.h +--map=common/libs/utils/archive.cpp:base/cvd/cuttlefish/common/libs/utils/archive.cpp +--map=common/libs/utils/archive.h:base/cvd/cuttlefish/common/libs/utils/archive.h +--map=common/libs/utils/base64.cpp:base/cvd/cuttlefish/common/libs/utils/base64.cpp +--map=common/libs/utils/base64.h:base/cvd/cuttlefish/common/libs/utils/base64.h +--map=common/libs/utils/cf_endian.h:base/cvd/cuttlefish/common/libs/utils/cf_endian.h +--map=common/libs/utils/collect.h:base/cvd/cuttlefish/common/libs/utils/collect.h +--map=common/libs/utils/contains.h:base/cvd/cuttlefish/common/libs/utils/contains.h +--map=common/libs/utils/environment.cpp:base/cvd/cuttlefish/common/libs/utils/environment.cpp +--map=common/libs/utils/environment.h:base/cvd/cuttlefish/common/libs/utils/environment.h +--map=common/libs/utils/files.cpp:base/cvd/cuttlefish/common/libs/utils/files.cpp +--map=common/libs/utils/files.h:base/cvd/cuttlefish/common/libs/utils/files.h +--map=common/libs/utils/files_test.cpp:base/cvd/cuttlefish/common/libs/utils/files_test.cpp +--map=common/libs/utils/files_test_helper.cpp:base/cvd/cuttlefish/common/libs/utils/files_test_helper.cpp +--map=common/libs/utils/files_test_helper.h:base/cvd/cuttlefish/common/libs/utils/files_test_helper.h +--map=common/libs/utils/flag_parser.cpp:base/cvd/cuttlefish/common/libs/utils/flag_parser.cpp +--map=common/libs/utils/flag_parser.h:base/cvd/cuttlefish/common/libs/utils/flag_parser.h +--map=common/libs/utils/flag_parser_test.cpp:base/cvd/cuttlefish/common/libs/utils/flag_parser_test.cpp +--map=common/libs/utils/flags_validator.cpp:base/cvd/cuttlefish/common/libs/utils/flags_validator.cpp +--map=common/libs/utils/flags_validator.h:base/cvd/cuttlefish/common/libs/utils/flags_validator.h +--map=common/libs/utils/inotify.cpp:base/cvd/cuttlefish/common/libs/utils/inotify.cpp +--map=common/libs/utils/inotify.h:base/cvd/cuttlefish/common/libs/utils/inotify.h +--map=common/libs/utils/json.cpp:base/cvd/cuttlefish/common/libs/utils/json.cpp +--map=common/libs/utils/json.h:base/cvd/cuttlefish/common/libs/utils/json.h +--map=common/libs/utils/network.cpp:base/cvd/cuttlefish/common/libs/utils/network.cpp +--map=common/libs/utils/network.h:base/cvd/cuttlefish/common/libs/utils/network.h +--map=common/libs/utils/proc_file_utils.cpp:base/cvd/cuttlefish/common/libs/utils/proc_file_utils.cpp +--map=common/libs/utils/proc_file_utils.h:base/cvd/cuttlefish/common/libs/utils/proc_file_utils.h +--map=common/libs/utils/proc_file_utils_test.cpp:base/cvd/cuttlefish/common/libs/utils/proc_file_utils_test.cpp +--map=common/libs/utils/proto.h:base/cvd/cuttlefish/common/libs/utils/proto.h +--map=common/libs/utils/result.h:base/cvd/cuttlefish/common/libs/utils/result.h +--map=common/libs/utils/result.cpp:base/cvd/cuttlefish/common/libs/utils/result.cpp +--map=common/libs/utils/result_matchers.h:base/cvd/cuttlefish/common/libs/utils/result_matchers.h +--map=common/libs/utils/result_test.cpp:base/cvd/cuttlefish/common/libs/utils/result_test.cpp +--map=common/libs/utils/shared_fd_flag.cpp:base/cvd/cuttlefish/common/libs/utils/shared_fd_flag.cpp +--map=common/libs/utils/shared_fd_flag.h:base/cvd/cuttlefish/common/libs/utils/shared_fd_flag.h +--map=common/libs/utils/size_utils.h:base/cvd/cuttlefish/common/libs/utils/size_utils.h +--map=common/libs/utils/socket2socket_proxy.cpp:base/cvd/cuttlefish/common/libs/utils/socket2socket_proxy.cpp +--map=common/libs/utils/socket2socket_proxy.h:base/cvd/cuttlefish/common/libs/utils/socket2socket_proxy.h +--map=common/libs/utils/subprocess.cpp:base/cvd/cuttlefish/common/libs/utils/subprocess.cpp +--map=common/libs/utils/subprocess.h:base/cvd/cuttlefish/common/libs/utils/subprocess.h +--map=common/libs/utils/tcp_socket.cpp:base/cvd/cuttlefish/common/libs/utils/tcp_socket.cpp +--map=common/libs/utils/tcp_socket.h:base/cvd/cuttlefish/common/libs/utils/tcp_socket.h +--map=common/libs/utils/tee_logging.cpp:base/cvd/cuttlefish/common/libs/utils/tee_logging.cpp +--map=common/libs/utils/tee_logging.h:base/cvd/cuttlefish/common/libs/utils/tee_logging.h +--map=common/libs/utils/unique_resource_allocator.h:base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator.h +--map=common/libs/utils/unique_resource_allocator_test.cpp:base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator_test.cpp +--map=common/libs/utils/unique_resource_allocator_test.h:base/cvd/cuttlefish/common/libs/utils/unique_resource_allocator_test.h +--map=common/libs/utils/unix_sockets.cpp:base/cvd/cuttlefish/common/libs/utils/unix_sockets.cpp +--map=common/libs/utils/unix_sockets.h:base/cvd/cuttlefish/common/libs/utils/unix_sockets.h +--map=common/libs/utils/unix_sockets_test.cpp:base/cvd/cuttlefish/common/libs/utils/unix_sockets_test.cpp +--map=common/libs/utils/users.cpp:base/cvd/cuttlefish/common/libs/utils/users.cpp +--map=common/libs/utils/users.h:base/cvd/cuttlefish/common/libs/utils/users.h +--map=common/libs/utils/vsock_connection.cpp:base/cvd/cuttlefish/common/libs/utils/vsock_connection.cpp +--map=common/libs/utils/vsock_connection.h:base/cvd/cuttlefish/common/libs/utils/vsock_connection.h +--map=host/commands/assemble_cvd/flags_defaults.h:base/cvd/cuttlefish/host/commands/assemble_cvd/flags_defaults.h +--map=host/commands/cvd/README.md:base/cvd/cuttlefish/host/commands/cvd/README.md +--map=host/commands/cvd/acloud/config.cpp:base/cvd/cuttlefish/host/commands/cvd/acloud/config.cpp +--map=host/commands/cvd/acloud/config.h:base/cvd/cuttlefish/host/commands/cvd/acloud/config.h +--map=host/commands/cvd/acloud/converter.cpp:base/cvd/cuttlefish/host/commands/cvd/acloud/converter.cpp +--map=host/commands/cvd/acloud/converter.h:base/cvd/cuttlefish/host/commands/cvd/acloud/converter.h +--map=host/commands/cvd/acloud/create_converter_parser.cpp:base/cvd/cuttlefish/host/commands/cvd/acloud/create_converter_parser.cpp +--map=host/commands/cvd/acloud/create_converter_parser.h:base/cvd/cuttlefish/host/commands/cvd/acloud/create_converter_parser.h +--map=host/commands/cvd/acloud/data/default.config:base/cvd/cuttlefish/host/commands/cvd/acloud/data/default.config +--map=host/commands/cvd/acloud_config.cpp:base/cvd/cuttlefish/host/commands/cvd/acloud/config.cpp +--map=host/commands/cvd/acloud_config.h:base/cvd/cuttlefish/host/commands/cvd/acloud/config.h +--map=host/commands/cvd/client.cpp:base/cvd/cuttlefish/host/commands/cvd/client.cpp +--map=host/commands/cvd/client.h:base/cvd/cuttlefish/host/commands/cvd/client.h +--map=host/commands/cvd/command_sequence.cpp:base/cvd/cuttlefish/host/commands/cvd/command_sequence.cpp +--map=host/commands/cvd/command_sequence.h:base/cvd/cuttlefish/host/commands/cvd/command_sequence.h +--map=host/commands/cvd/common_utils.cpp:base/cvd/cuttlefish/host/commands/cvd/common_utils.cpp +--map=host/commands/cvd/common_utils.h:base/cvd/cuttlefish/host/commands/cvd/common_utils.h +--map=host/commands/cvd/constant_reference.h:base/cvd/cuttlefish/host/commands/cvd/selector/constant_reference.h +--map=host/commands/cvd/data/default.config:base/cvd/cuttlefish/host/commands/cvd/acloud/data/default.config +--map=host/commands/cvd/demo_multi_vd.cpp:base/cvd/cuttlefish/host/commands/cvd/demo_multi_vd.cpp +--map=host/commands/cvd/demo_multi_vd.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/serial_launch.cpp +--map=host/commands/cvd/demo_multi_vd.h:base/cvd/cuttlefish/host/commands/cvd/demo_multi_vd.h +--map=host/commands/cvd/demo_multi_vd.h:base/cvd/cuttlefish/host/commands/cvd/server_command/cmd_list.h +--map=host/commands/cvd/demo_multi_vd.h:base/cvd/cuttlefish/host/commands/cvd/server_command/handler_proxy.h +--map=host/commands/cvd/demo_multi_vd.h:base/cvd/cuttlefish/host/commands/cvd/server_command/serial_launch.h +--map=host/commands/cvd/demo_multi_vd.h:base/cvd/cuttlefish/host/commands/cvd/server_command/serial_preset.h +--map=host/commands/cvd/doc/all_handlers.dot:base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.dot +--map=host/commands/cvd/doc/all_handlers.png:base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.png +--map=host/commands/cvd/doc/all_handlers.svg:base/cvd/cuttlefish/host/commands/cvd/doc/all_handlers.svg +--map=host/commands/cvd/doc/load_config.dot:base/cvd/cuttlefish/host/commands/cvd/doc/load_config.dot +--map=host/commands/cvd/doc/load_config.png:base/cvd/cuttlefish/host/commands/cvd/doc/load_config.png +--map=host/commands/cvd/doc/load_config.svg:base/cvd/cuttlefish/host/commands/cvd/doc/load_config.svg +--map=host/commands/cvd/driver_flags.cpp:base/cvd/cuttlefish/host/commands/cvd/driver_flags.cpp +--map=host/commands/cvd/driver_flags.h:base/cvd/cuttlefish/host/commands/cvd/driver_flags.h +--map=host/commands/cvd/epoll_loop.cpp:base/cvd/cuttlefish/host/commands/cvd/epoll_loop.cpp +--map=host/commands/cvd/epoll_loop.h:base/cvd/cuttlefish/host/commands/cvd/epoll_loop.h +--map=host/commands/cvd/fetch/fetch_cvd.cc:base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd.cc +--map=host/commands/cvd/fetch/fetch_cvd.h:base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd.h +--map=host/commands/cvd/fetch/fetch_cvd_parser.cc:base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd_parser.cc +--map=host/commands/cvd/fetch/fetch_cvd_parser.h:base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd_parser.h +--map=host/commands/cvd/fetch_cvd.cc:base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd.cc +--map=host/commands/cvd/fetch_cvd.h:base/cvd/cuttlefish/host/commands/cvd/fetch/fetch_cvd.h +--map=host/commands/cvd/flag.cpp:base/cvd/cuttlefish/host/commands/cvd/flag.cpp +--map=host/commands/cvd/flag.h:base/cvd/cuttlefish/host/commands/cvd/flag.h +--map=host/commands/cvd/frontline_parser.cpp:base/cvd/cuttlefish/host/commands/cvd/frontline_parser.cpp +--map=host/commands/cvd/frontline_parser.h:base/cvd/cuttlefish/host/commands/cvd/frontline_parser.h +--map=host/commands/cvd/handle_reset.cpp:base/cvd/cuttlefish/host/commands/cvd/handle_reset.cpp +--map=host/commands/cvd/handle_reset.h:base/cvd/cuttlefish/host/commands/cvd/handle_reset.h +--map=host/commands/cvd/help_command.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/help.cpp +--map=host/commands/cvd/instance_database.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database.cpp +--map=host/commands/cvd/instance_database.h:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database.h +--map=host/commands/cvd/instance_database_impl.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_impl.cpp +--map=host/commands/cvd/instance_database_types.h:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_types.h +--map=host/commands/cvd/instance_database_utils.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_utils.cpp +--map=host/commands/cvd/instance_database_utils.h:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_utils.h +--map=host/commands/cvd/instance_group_record.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_group_record.cpp +--map=host/commands/cvd/instance_group_record.h:base/cvd/cuttlefish/host/commands/cvd/selector/instance_group_record.h +--map=host/commands/cvd/instance_lock.cpp:base/cvd/cuttlefish/host/commands/cvd/instance_lock.cpp +--map=host/commands/cvd/instance_lock.h:base/cvd/cuttlefish/host/commands/cvd/instance_lock.h +--map=host/commands/cvd/instance_manager.cpp:base/cvd/cuttlefish/host/commands/cvd/instance_manager.cpp +--map=host/commands/cvd/instance_manager.h:base/cvd/cuttlefish/host/commands/cvd/instance_manager.h +--map=host/commands/cvd/instance_record.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_record.cpp +--map=host/commands/cvd/instance_record.h:base/cvd/cuttlefish/host/commands/cvd/selector/instance_record.h +--map=host/commands/cvd/interruptible_terminal.cpp:base/cvd/cuttlefish/host/commands/cvd/interruptible_terminal.cpp +--map=host/commands/cvd/interruptible_terminal.h:base/cvd/cuttlefish/host/commands/cvd/interruptible_terminal.h +--map=host/commands/cvd/load_configs.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/load_configs.cpp +--map=host/commands/cvd/load_configs.h:base/cvd/cuttlefish/host/commands/cvd/server_command/load_configs.h +--map=host/commands/cvd/lock_file.cpp:base/cvd/cuttlefish/host/commands/cvd/lock_file.cpp +--map=host/commands/cvd/lock_file.h:base/cvd/cuttlefish/host/commands/cvd/lock_file.h +--map=host/commands/cvd/logger.cpp:base/cvd/cuttlefish/host/commands/cvd/logger.cpp +--map=host/commands/cvd/logger.h:base/cvd/cuttlefish/host/commands/cvd/logger.h +--map=host/commands/cvd/main.cc:base/cvd/cuttlefish/host/commands/cvd/main.cc +--map=host/commands/cvd/metrics/cvd_metrics_api.cpp:base/cvd/cuttlefish/host/commands/cvd/metrics/cvd_metrics_api.cpp +--map=host/commands/cvd/metrics/cvd_metrics_api.h:base/cvd/cuttlefish/host/commands/cvd/metrics/cvd_metrics_api.h +--map=host/commands/cvd/metrics/metrics_notice.cpp:base/cvd/cuttlefish/host/commands/cvd/metrics/metrics_notice.cpp +--map=host/commands/cvd/metrics/metrics_notice.h:base/cvd/cuttlefish/host/commands/cvd/metrics/metrics_notice.h +--map=host/commands/cvd/metrics/proto/cvd_metrics_protos.h:base/cvd/cuttlefish/host/commands/cvd/metrics/proto/cvd_metrics_protos.h +--map=host/commands/cvd/metrics/utils.cpp:base/cvd/cuttlefish/host/commands/cvd/metrics/utils.cpp +--map=host/commands/cvd/metrics/utils.h:base/cvd/cuttlefish/host/commands/cvd/metrics/utils.h +--map=host/commands/cvd/parser/README.md:base/cvd/cuttlefish/host/commands/cvd/parser/README.md +--map=host/commands/cvd/parser/cf_configs_common.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_common.cpp +--map=host/commands/cvd/parser/cf_configs_common.h:base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_common.h +--map=host/commands/cvd/parser/cf_configs_instances.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_instances.cpp +--map=host/commands/cvd/parser/cf_configs_instances.h:base/cvd/cuttlefish/host/commands/cvd/parser/cf_configs_instances.h +--map=host/commands/cvd/parser/cf_flags_validator.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/cf_flags_validator.cpp +--map=host/commands/cvd/parser/cf_flags_validator.h:base/cvd/cuttlefish/host/commands/cvd/parser/cf_flags_validator.h +--map=host/commands/cvd/parser/cf_metrics_configs.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/cf_metrics_configs.cpp +--map=host/commands/cvd/parser/cf_metrics_configs.h:base/cvd/cuttlefish/host/commands/cvd/parser/cf_metrics_configs.h +--map=host/commands/cvd/parser/doc/adb.dot:base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.dot +--map=host/commands/cvd/parser/doc/adb.png:base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.png +--map=host/commands/cvd/parser/doc/adb.svg:base/cvd/cuttlefish/host/commands/cvd/parser/doc/adb.svg +--map=host/commands/cvd/parser/doc/audio.dot:base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.dot +--map=host/commands/cvd/parser/doc/audio.png:base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.png +--map=host/commands/cvd/parser/doc/audio.svg:base/cvd/cuttlefish/host/commands/cvd/parser/doc/audio.svg +--map=host/commands/cvd/parser/doc/camera.dot:base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.dot +--map=host/commands/cvd/parser/doc/camera.png:base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.png +--map=host/commands/cvd/parser/doc/camera.svg:base/cvd/cuttlefish/host/commands/cvd/parser/doc/camera.svg +--map=host/commands/cvd/parser/doc/connectivity.dot:base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.dot +--map=host/commands/cvd/parser/doc/connectivity.png:base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.png +--map=host/commands/cvd/parser/doc/connectivity.svg:base/cvd/cuttlefish/host/commands/cvd/parser/doc/connectivity.svg +--map=host/commands/cvd/parser/doc/disk.dot:base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.dot +--map=host/commands/cvd/parser/doc/disk.png:base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.png +--map=host/commands/cvd/parser/doc/disk.svg:base/cvd/cuttlefish/host/commands/cvd/parser/doc/disk.svg +--map=host/commands/cvd/parser/doc/graphics.dot:base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.dot +--map=host/commands/cvd/parser/doc/graphics.png:base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.png +--map=host/commands/cvd/parser/doc/graphics.svg:base/cvd/cuttlefish/host/commands/cvd/parser/doc/graphics.svg +--map=host/commands/cvd/parser/doc/linkage.dot:base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.dot +--map=host/commands/cvd/parser/doc/linkage.png:base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.png +--map=host/commands/cvd/parser/doc/linkage.svg:base/cvd/cuttlefish/host/commands/cvd/parser/doc/linkage.svg +--map=host/commands/cvd/parser/doc/location.dot:base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.dot +--map=host/commands/cvd/parser/doc/location.png:base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.png +--map=host/commands/cvd/parser/doc/location.svg:base/cvd/cuttlefish/host/commands/cvd/parser/doc/location.svg +--map=host/commands/cvd/parser/doc/streaming.dot:base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.dot +--map=host/commands/cvd/parser/doc/streaming.png:base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.png +--map=host/commands/cvd/parser/doc/streaming.svg:base/cvd/cuttlefish/host/commands/cvd/parser/doc/streaming.svg +--map=host/commands/cvd/parser/doc/vehicle.dot:base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.dot +--map=host/commands/cvd/parser/doc/vehicle.png:base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.png +--map=host/commands/cvd/parser/doc/vehicle.svg:base/cvd/cuttlefish/host/commands/cvd/parser/doc/vehicle.svg +--map=host/commands/cvd/parser/doc/vm.dot:base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.dot +--map=host/commands/cvd/parser/doc/vm.png:base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.png +--map=host/commands/cvd/parser/doc/vm.svg:base/cvd/cuttlefish/host/commands/cvd/parser/doc/vm.svg +--map=host/commands/cvd/parser/fetch_config_parser.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/fetch_config_parser.cpp +--map=host/commands/cvd/parser/fetch_config_parser.h:base/cvd/cuttlefish/host/commands/cvd/parser/fetch_config_parser.h +--map=host/commands/cvd/parser/fetch_cvd_parser.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/fetch_cvd_parser.cpp +--map=host/commands/cvd/parser/fetch_cvd_parser.h:base/cvd/cuttlefish/host/commands/cvd/parser/fetch_cvd_parser.h +--map=host/commands/cvd/parser/instance/cf_boot_configs.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_boot_configs.cpp +--map=host/commands/cvd/parser/instance/cf_boot_configs.h:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_boot_configs.h +--map=host/commands/cvd/parser/instance/cf_disk_configs.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_disk_configs.cpp +--map=host/commands/cvd/parser/instance/cf_disk_configs.h:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_disk_configs.h +--map=host/commands/cvd/parser/instance/cf_graphics_configs.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_graphics_configs.cpp +--map=host/commands/cvd/parser/instance/cf_graphics_configs.h:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_graphics_configs.h +--map=host/commands/cvd/parser/instance/cf_metrics_configs.h:base/cvd/cuttlefish/host/commands/cvd/parser/cf_metrics_configs.h +--map=host/commands/cvd/parser/instance/cf_security_configs.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_security_configs.cpp +--map=host/commands/cvd/parser/instance/cf_security_configs.h:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_security_configs.h +--map=host/commands/cvd/parser/instance/cf_streaming_configs.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_streaming_configs.cpp +--map=host/commands/cvd/parser/instance/cf_streaming_configs.h:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_streaming_configs.h +--map=host/commands/cvd/parser/instance/cf_vm_configs.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_vm_configs.cpp +--map=host/commands/cvd/parser/instance/cf_vm_configs.h:base/cvd/cuttlefish/host/commands/cvd/parser/instance/cf_vm_configs.h +--map=host/commands/cvd/parser/launch_cvd_parser.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_parser.cpp +--map=host/commands/cvd/parser/launch_cvd_parser.h:base/cvd/cuttlefish/host/commands/cvd/parser/fetch_cvd_parser.h +--map=host/commands/cvd/parser/launch_cvd_parser.h:base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_parser.h +--map=host/commands/cvd/parser/launch_cvd_parser.h:base/cvd/cuttlefish/host/commands/cvd/parser/selector_parser.h +--map=host/commands/cvd/parser/launch_cvd_templates.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_templates.cpp +--map=host/commands/cvd/parser/launch_cvd_templates.h:base/cvd/cuttlefish/host/commands/cvd/parser/launch_cvd_templates.h +--map=host/commands/cvd/parser/load_configs_parser.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/load_configs_parser.cpp +--map=host/commands/cvd/parser/load_configs_parser.h:base/cvd/cuttlefish/host/commands/cvd/parser/load_configs_parser.h +--map=host/commands/cvd/parser/selector_parser.cpp:base/cvd/cuttlefish/host/commands/cvd/parser/selector_parser.cpp +--map=host/commands/cvd/parser/selector_parser.h:base/cvd/cuttlefish/host/commands/cvd/parser/selector_parser.h +--map=host/commands/cvd/request_context.cpp:base/cvd/cuttlefish/host/commands/cvd/request_context.cpp +--map=host/commands/cvd/request_context.h:base/cvd/cuttlefish/host/commands/cvd/request_context.h +--map=host/commands/cvd/reset_client_utils.cpp:base/cvd/cuttlefish/host/commands/cvd/reset_client_utils.cpp +--map=host/commands/cvd/reset_client_utils.h:base/cvd/cuttlefish/host/commands/cvd/reset_client_utils.h +--map=host/commands/cvd/run_cvd_proc_collector.cpp:base/cvd/cuttlefish/host/commands/cvd/run_cvd_proc_collector.cpp +--map=host/commands/cvd/run_cvd_proc_collector.h:base/cvd/cuttlefish/host/commands/cvd/run_cvd_proc_collector.h +--map=host/commands/cvd/run_server.cpp:base/cvd/cuttlefish/host/commands/cvd/run_server.cpp +--map=host/commands/cvd/run_server.h:base/cvd/cuttlefish/host/commands/cvd/run_server.h +--map=host/commands/cvd/selector/arguments_lexer.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/arguments_lexer.cpp +--map=host/commands/cvd/selector/arguments_lexer.h:base/cvd/cuttlefish/host/commands/cvd/selector/arguments_lexer.h +--map=host/commands/cvd/selector/arguments_separator.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/arguments_separator.cpp +--map=host/commands/cvd/selector/arguments_separator.h:base/cvd/cuttlefish/host/commands/cvd/selector/arguments_separator.h +--map=host/commands/cvd/selector/constant_reference.h:base/cvd/cuttlefish/host/commands/cvd/selector/constant_reference.h +--map=host/commands/cvd/selector/creation_analyzer.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/creation_analyzer.cpp +--map=host/commands/cvd/selector/creation_analyzer.h:base/cvd/cuttlefish/host/commands/cvd/selector/creation_analyzer.h +--map=host/commands/cvd/selector/device_selector_utils.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/device_selector_utils.cpp +--map=host/commands/cvd/selector/device_selector_utils.h:base/cvd/cuttlefish/host/commands/cvd/selector/device_selector_utils.h +--map=host/commands/cvd/selector/group_selector.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/group_selector.cpp +--map=host/commands/cvd/selector/group_selector.h:base/cvd/cuttlefish/host/commands/cvd/selector/group_selector.h +--map=host/commands/cvd/selector/instance_database.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database.cpp +--map=host/commands/cvd/selector/instance_database.h:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database.h +--map=host/commands/cvd/selector/instance_database_impl.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_impl.cpp +--map=host/commands/cvd/selector/instance_database_types.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_types.cpp +--map=host/commands/cvd/selector/instance_database_types.h:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_types.h +--map=host/commands/cvd/selector/instance_database_utils.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_utils.cpp +--map=host/commands/cvd/selector/instance_database_utils.h:base/cvd/cuttlefish/host/commands/cvd/selector/instance_database_utils.h +--map=host/commands/cvd/selector/instance_group_record.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_group_record.cpp +--map=host/commands/cvd/selector/instance_group_record.h:base/cvd/cuttlefish/host/commands/cvd/selector/instance_group_record.h +--map=host/commands/cvd/selector/instance_record.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_record.cpp +--map=host/commands/cvd/selector/instance_record.h:base/cvd/cuttlefish/host/commands/cvd/selector/instance_record.h +--map=host/commands/cvd/selector/instance_selector.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/instance_selector.cpp +--map=host/commands/cvd/selector/instance_selector.h:base/cvd/cuttlefish/host/commands/cvd/selector/instance_selector.h +--map=host/commands/cvd/selector/selector_cmdline_parser.h:base/cvd/cuttlefish/host/commands/cvd/selector/start_selector_parser.h +--map=host/commands/cvd/selector/selector_common_parser.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/selector_common_parser.cpp +--map=host/commands/cvd/selector/selector_common_parser.h:base/cvd/cuttlefish/host/commands/cvd/selector/selector_common_parser.h +--map=host/commands/cvd/selector/selector_constants.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/selector_constants.cpp +--map=host/commands/cvd/selector/selector_constants.h:base/cvd/cuttlefish/host/commands/cvd/selector/selector_constants.h +--map=host/commands/cvd/selector/selector_flags_parser.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/start_selector_parser.cpp +--map=host/commands/cvd/selector/selector_option_parser_utils.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/selector_option_parser_utils.cpp +--map=host/commands/cvd/selector/selector_option_parser_utils.h:base/cvd/cuttlefish/host/commands/cvd/selector/selector_option_parser_utils.h +--map=host/commands/cvd/selector/start_selector_parser.cpp:base/cvd/cuttlefish/host/commands/cvd/selector/start_selector_parser.cpp +--map=host/commands/cvd/selector/start_selector_parser.h:base/cvd/cuttlefish/host/commands/cvd/selector/start_selector_parser.h +--map=host/commands/cvd/selector_constants.h:base/cvd/cuttlefish/host/commands/cvd/selector/selector_constants.h +--map=host/commands/cvd/server.cc:base/cvd/cuttlefish/host/commands/cvd/server.cc +--map=host/commands/cvd/server.h:base/cvd/cuttlefish/host/commands/cvd/server.h +--map=host/commands/cvd/server.h:base/cvd/cuttlefish/host/commands/cvd/server_constants.h +--map=host/commands/cvd/server_client.cpp:base/cvd/cuttlefish/host/commands/cvd/server_client.cpp +--map=host/commands/cvd/server_client.h:base/cvd/cuttlefish/host/commands/cvd/server_client.h +--map=host/commands/cvd/server_command/acloud.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/acloud.cpp +--map=host/commands/cvd/server_command/acloud_command.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_command.cpp +--map=host/commands/cvd/server_command/acloud_command.h:base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_command.h +--map=host/commands/cvd/server_command/acloud_common.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_common.cpp +--map=host/commands/cvd/server_command/acloud_common.h:base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_common.h +--map=host/commands/cvd/server_command/acloud_mixsuperimage.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_mixsuperimage.cpp +--map=host/commands/cvd/server_command/acloud_mixsuperimage.h:base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_mixsuperimage.h +--map=host/commands/cvd/server_command/acloud_translator.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_translator.cpp +--map=host/commands/cvd/server_command/acloud_translator.h:base/cvd/cuttlefish/host/commands/cvd/server_command/acloud_translator.h +--map=host/commands/cvd/server_command/cmd_list.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/cmd_list.cpp +--map=host/commands/cvd/server_command/cmd_list.h:base/cvd/cuttlefish/host/commands/cvd/server_command/cmd_list.h +--map=host/commands/cvd/server_command/components.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/components.cpp +--map=host/commands/cvd/server_command/components.h:base/cvd/cuttlefish/host/commands/cvd/server_command/components.h +--map=host/commands/cvd/server_command/crosvm.h:base/cvd/cuttlefish/host/commands/cvd/server_command/display.h +--map=host/commands/cvd/server_command/crosvm.h:base/cvd/cuttlefish/host/commands/cvd/server_command/env.h +--map=host/commands/cvd/server_command/display.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/display.cpp +--map=host/commands/cvd/server_command/display.h:base/cvd/cuttlefish/host/commands/cvd/server_command/display.h +--map=host/commands/cvd/server_command/env.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/env.cpp +--map=host/commands/cvd/server_command/env.h:base/cvd/cuttlefish/host/commands/cvd/server_command/display.h +--map=host/commands/cvd/server_command/env.h:base/cvd/cuttlefish/host/commands/cvd/server_command/env.h +--map=host/commands/cvd/server_command/fetch.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/fetch.cpp +--map=host/commands/cvd/server_command/fetch.h:base/cvd/cuttlefish/host/commands/cvd/server_command/display.h +--map=host/commands/cvd/server_command/fetch.h:base/cvd/cuttlefish/host/commands/cvd/server_command/env.h +--map=host/commands/cvd/server_command/fetch.h:base/cvd/cuttlefish/host/commands/cvd/server_command/fetch.h +--map=host/commands/cvd/server_command/flags_collector.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/flags_collector.cpp +--map=host/commands/cvd/server_command/flags_collector.h:base/cvd/cuttlefish/host/commands/cvd/server_command/flags_collector.h +--map=host/commands/cvd/server_command/fleet.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/fleet.cpp +--map=host/commands/cvd/server_command/fleet.h:base/cvd/cuttlefish/host/commands/cvd/server_command/fleet.h +--map=host/commands/cvd/server_command/generic.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/generic.cpp +--map=host/commands/cvd/server_command/generic.h:base/cvd/cuttlefish/host/commands/cvd/server_command/generic.h +--map=host/commands/cvd/server_command/generic.h:base/cvd/cuttlefish/host/commands/cvd/server_command/snapshot.h +--map=host/commands/cvd/server_command/handler_proxy.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/handler_proxy.cpp +--map=host/commands/cvd/server_command/handler_proxy.h:base/cvd/cuttlefish/host/commands/cvd/server_command/handler_proxy.h +--map=host/commands/cvd/server_command/help.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/help.cpp +--map=host/commands/cvd/server_command/help.h:base/cvd/cuttlefish/host/commands/cvd/server_command/help.h +--map=host/commands/cvd/server_command/host_tool_target.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target.cpp +--map=host/commands/cvd/server_command/host_tool_target.h:base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target.h +--map=host/commands/cvd/server_command/host_tool_target_manager.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target_manager.cpp +--map=host/commands/cvd/server_command/host_tool_target_manager.h:base/cvd/cuttlefish/host/commands/cvd/server_command/host_tool_target_manager.h +--map=host/commands/cvd/server_command/lint.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/lint.cpp +--map=host/commands/cvd/server_command/lint.h:base/cvd/cuttlefish/host/commands/cvd/server_command/lint.h +--map=host/commands/cvd/server_command/load_configs.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/load_configs.cpp +--map=host/commands/cvd/server_command/load_configs.h:base/cvd/cuttlefish/host/commands/cvd/server_command/load_configs.h +--map=host/commands/cvd/server_command/power.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/power.cpp +--map=host/commands/cvd/server_command/power.h:base/cvd/cuttlefish/host/commands/cvd/server_command/power.h +--map=host/commands/cvd/server_command/reset.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/reset.cpp +--map=host/commands/cvd/server_command/reset.h:base/cvd/cuttlefish/common/libs/utils/inotify.h +--map=host/commands/cvd/server_command/reset.h:base/cvd/cuttlefish/host/commands/cvd/server_command/reset.h +--map=host/commands/cvd/server_command/restart.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/restart.cpp +--map=host/commands/cvd/server_command/restart.h:base/cvd/cuttlefish/host/commands/cvd/server_command/restart.h +--map=host/commands/cvd/server_command/serial_launch.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/serial_launch.cpp +--map=host/commands/cvd/server_command/serial_launch.h:base/cvd/cuttlefish/host/commands/cvd/server_command/cmd_list.h +--map=host/commands/cvd/server_command/serial_launch.h:base/cvd/cuttlefish/host/commands/cvd/server_command/handler_proxy.h +--map=host/commands/cvd/server_command/serial_launch.h:base/cvd/cuttlefish/host/commands/cvd/server_command/serial_launch.h +--map=host/commands/cvd/server_command/serial_preset.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/serial_preset.cpp +--map=host/commands/cvd/server_command/serial_preset.h:base/cvd/cuttlefish/host/commands/cvd/server_command/cmd_list.h +--map=host/commands/cvd/server_command/serial_preset.h:base/cvd/cuttlefish/host/commands/cvd/server_command/handler_proxy.h +--map=host/commands/cvd/server_command/serial_preset.h:base/cvd/cuttlefish/host/commands/cvd/server_command/serial_launch.h +--map=host/commands/cvd/server_command/serial_preset.h:base/cvd/cuttlefish/host/commands/cvd/server_command/serial_preset.h +--map=host/commands/cvd/server_command/server_handler.h:base/cvd/cuttlefish/host/commands/cvd/server_command/server_handler.h +--map=host/commands/cvd/server_command/shutdown.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/shutdown.cpp +--map=host/commands/cvd/server_command/shutdown.h:base/cvd/cuttlefish/host/commands/cvd/server_command/shutdown.h +--map=host/commands/cvd/server_command/snapshot.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/snapshot.cpp +--map=host/commands/cvd/server_command/snapshot.h:base/cvd/cuttlefish/host/commands/cvd/server_command/snapshot.h +--map=host/commands/cvd/server_command/start.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/start.cpp +--map=host/commands/cvd/server_command/start.h:base/cvd/cuttlefish/host/commands/cvd/server_command/start.h +--map=host/commands/cvd/server_command/start_impl.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/start_impl.cpp +--map=host/commands/cvd/server_command/start_impl.h:base/cvd/cuttlefish/host/commands/cvd/server_command/start_impl.h +--map=host/commands/cvd/server_command/status.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/status.cpp +--map=host/commands/cvd/server_command/status.h:base/cvd/cuttlefish/host/commands/cvd/server_command/status.h +--map=host/commands/cvd/server_command/status_fetcher.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/status_fetcher.cpp +--map=host/commands/cvd/server_command/status_fetcher.h:base/cvd/cuttlefish/host/commands/cvd/server_command/status_fetcher.h +--map=host/commands/cvd/server_command/subcmd.h:base/cvd/cuttlefish/host/commands/cvd/server_command/subcmd.h +--map=host/commands/cvd/server_command/subprocess_waiter.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/subprocess_waiter.cpp +--map=host/commands/cvd/server_command/subprocess_waiter.h:base/cvd/cuttlefish/host/commands/cvd/server_command/subprocess_waiter.h +--map=host/commands/cvd/server_command/suspend_resume.h:base/cvd/cuttlefish/host/commands/cvd/server_command/snapshot.h +--map=host/commands/cvd/server_command/try_acloud.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/try_acloud.cpp +--map=host/commands/cvd/server_command/try_acloud.h:base/cvd/cuttlefish/host/commands/cvd/server_command/try_acloud.h +--map=host/commands/cvd/server_command/utils.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/utils.cpp +--map=host/commands/cvd/server_command/utils.h:base/cvd/cuttlefish/host/commands/cvd/server_command/utils.h +--map=host/commands/cvd/server_command/version.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/version.cpp +--map=host/commands/cvd/server_command/version.h:base/cvd/cuttlefish/host/commands/cvd/server_command/version.h +--map=host/commands/cvd/server_command_impl.h:base/cvd/cuttlefish/host/commands/cvd/server_command/subprocess_waiter.h +--map=host/commands/cvd/server_command_subprocess_waiter.h:base/cvd/cuttlefish/host/commands/cvd/server_command/subprocess_waiter.h +--map=host/commands/cvd/server_constants.cpp:base/cvd/cuttlefish/host/commands/cvd/server_constants.cpp +--map=host/commands/cvd/server_constants.h:base/cvd/cuttlefish/host/commands/cvd/server_constants.h +--map=host/commands/cvd/server_restart.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/restart.cpp +--map=host/commands/cvd/server_shutdown.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/shutdown.cpp +--map=host/commands/cvd/server_version.cpp:base/cvd/cuttlefish/host/commands/cvd/server_command/version.cpp +--map=host/commands/cvd/types.cpp:base/cvd/cuttlefish/host/commands/cvd/types.cpp +--map=host/commands/cvd/types.h:base/cvd/cuttlefish/host/commands/cvd/types.h +--map=host/commands/cvd/unittests/instance_database_test_helper.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.cpp +--map=host/commands/cvd/unittests/instance_database_test_helper.h:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.h +--map=host/commands/cvd/unittests/parser/cf_configs_common_tests.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/cf_configs_common_tests.cpp +--map=host/commands/cvd/unittests/parser/configs_inheritance_test.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/configs_inheritance_test.cc +--map=host/commands/cvd/unittests/parser/fetch_cvd_parser_tests.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/fetch_cvd_parser_tests.cpp +--map=host/commands/cvd/unittests/parser/flags_parser_test.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/flags_parser_test.cc +--map=host/commands/cvd/unittests/parser/instance/boot_configs_test.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/boot_configs_test.cc +--map=host/commands/cvd/unittests/parser/instance/disk_configs_test.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/disk_configs_test.cc +--map=host/commands/cvd/unittests/parser/instance/graphics_configs_test.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/graphics_configs_test.cc +--map=host/commands/cvd/unittests/parser/instance/vm_configs_test.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/vm_configs_test.cc +--map=host/commands/cvd/unittests/parser/metrics_configs_test.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/metrics_configs_test.cc +--map=host/commands/cvd/unittests/parser/test_common.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/test_common.cc +--map=host/commands/cvd/unittests/parser/test_common.h:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/test_common.h +--map=host/commands/cvd/unittests/selector/client_lexer_helper.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_helper.cpp +--map=host/commands/cvd/unittests/selector/client_lexer_helper.h:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_helper.h +--map=host/commands/cvd/unittests/selector/client_lexer_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/client_lexer_test.cpp +--map=host/commands/cvd/unittests/selector/creation_analyzer_helper.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_helper.cpp +--map=host/commands/cvd/unittests/selector/creation_analyzer_helper.h:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_helper.h +--map=host/commands/cvd/unittests/selector/creation_analyzer_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_test.cpp +--map=host/commands/cvd/unittests/selector/creation_analyzer_test_helper.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_helper.cpp +--map=host/commands/cvd/unittests/selector/creation_analyzer_test_helper.h:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_helper.h +--map=host/commands/cvd/unittests/selector/cvd_flags_helper.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_helper.cpp +--map=host/commands/cvd/unittests/selector/cvd_flags_helper.h:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_helper.h +--map=host/commands/cvd/unittests/selector/cvd_flags_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/cvd_flags_test.cpp +--map=host/commands/cvd/unittests/selector/group_record_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/group_record_test.cpp +--map=host/commands/cvd/unittests/selector/host_tool_target_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/host_tool_target_test.cpp +--map=host/commands/cvd/unittests/selector/instance_database_helper.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.cpp +--map=host/commands/cvd/unittests/selector/instance_database_helper.h:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.h +--map=host/commands/cvd/unittests/selector/instance_database_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_test.cpp +--map=host/commands/cvd/unittests/selector/instance_database_test_helper.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.cpp +--map=host/commands/cvd/unittests/selector/instance_database_test_helper.h:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_helper.h +--map=host/commands/cvd/unittests/selector/instance_record_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_record_test.cpp +--map=host/commands/cvd/unittests/selector/parser_ids_helper.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_helper.cpp +--map=host/commands/cvd/unittests/selector/parser_ids_helper.h:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_helper.h +--map=host/commands/cvd/unittests/selector/parser_ids_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_ids_test.cpp +--map=host/commands/cvd/unittests/selector/parser_names_helper.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_helper.cpp +--map=host/commands/cvd/unittests/selector/parser_names_helper.h:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_helper.h +--map=host/commands/cvd/unittests/selector/parser_names_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/parser_names_test.cpp +--map=host/commands/cvd/unittests/selector/test_creation_analyzer.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/creation_analyzer_test.cpp +--map=host/commands/cvd/unittests/selector/test_group_record.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/group_record_test.cpp +--map=host/commands/cvd/unittests/selector/test_instance_database.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_test.cpp +--map=host/commands/cvd/unittests/selector/test_instance_record.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_record_test.cpp +--map=host/commands/cvd/unittests/server/autogen_ids_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/autogen_ids_test.cpp +--map=host/commands/cvd/unittests/server/basic_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/basic_test.cpp +--map=host/commands/cvd/unittests/server/clear_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/clear_test.cpp +--map=host/commands/cvd/unittests/server/cmd_runner.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/cmd_runner.cpp +--map=host/commands/cvd/unittests/server/cmd_runner.h:base/cvd/cuttlefish/host/commands/cvd/unittests/server/cmd_runner.h +--map=host/commands/cvd/unittests/server/collect_flags_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/collect_flags_test.cpp +--map=host/commands/cvd/unittests/server/common_utils_helper.cpp:base/cvd/cuttlefish/common/libs/utils/files_test_helper.cpp +--map=host/commands/cvd/unittests/server/common_utils_helper.h:base/cvd/cuttlefish/common/libs/utils/files_test_helper.h +--map=host/commands/cvd/unittests/server/common_utils_test.cpp:base/cvd/cuttlefish/common/libs/utils/files_test.cpp +--map=host/commands/cvd/unittests/server/frontline_parser_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/frontline_parser_test.cpp +--map=host/commands/cvd/unittests/server/help_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/help_test.cpp +--map=host/commands/cvd/unittests/server/instance_ids_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/instance_ids_test.cpp +--map=host/commands/cvd/unittests/server/local_instance_helper.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_helper.cpp +--map=host/commands/cvd/unittests/server/local_instance_helper.h:base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_helper.h +--map=host/commands/cvd/unittests/server/local_instance_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/local_instance_test.cpp +--map=host/commands/cvd/unittests/server/snapshot_test.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/snapshot_test.cpp +--map=host/commands/cvd/unittests/server/snapshot_test_helper.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/snapshot_test_helper.cpp +--map=host/commands/cvd/unittests/server/utils.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/server/utils.cpp +--map=host/commands/cvd/unittests/server/utils.h:base/cvd/cuttlefish/host/commands/cvd/unittests/server/utils.h +--map=host/commands/cvd/unittests/server/utils.h:base/cvd/cuttlefish/host/libs/web/http_client/http_client_util.h +--map=host/commands/cvd/unittests/test_group_record.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/group_record_test.cpp +--map=host/commands/cvd/unittests/test_instance_database.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_database_test.cpp +--map=host/commands/cvd/unittests/test_instance_record.cpp:base/cvd/cuttlefish/host/commands/cvd/unittests/selector/instance_record_test.cpp +--map=host/commands/cvd_load/unittest/boot_configs_test.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/boot_configs_test.cc +--map=host/commands/cvd_load/unittest/flags_parser_test.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/flags_parser_test.cc +--map=host/commands/cvd_load/unittest/test_common.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/test_common.cc +--map=host/commands/cvd_load/unittest/test_common.h:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/test_common.h +--map=host/commands/cvd_load/unittest/vm_configs_test.cc:base/cvd/cuttlefish/host/commands/cvd/unittests/parser/instance/vm_configs_test.cc +--map=host/commands/cvd_send_sms/unittest/main_test.cc:base/cvd/cuttlefish/host/libs/web/http_client/unittest/main_test.cc +--map=host/commands/metrics/metrics_defs.h:base/cvd/cuttlefish/host/commands/metrics/metrics_defs.h +--map=host/frontend/vnc_server/tcp_socket.h:base/cvd/cuttlefish/common/libs/utils/tcp_socket.h +--map=host/libs/adb_connection_maintainer/adb_connection_maintainer.h:base/cvd/cuttlefish/common/libs/utils/users.h +--map=host/libs/config/config_constants.h:base/cvd/cuttlefish/host/libs/config/config_constants.h +--map=host/libs/config/config_fragment.h:base/cvd/cuttlefish/host/libs/config/config_fragment.h +--map=host/libs/config/config_utils.cpp:base/cvd/cuttlefish/host/libs/config/config_utils.cpp +--map=host/libs/config/config_utils.h:base/cvd/cuttlefish/host/libs/config/config_utils.h +--map=host/libs/config/cuttlefish_config.cpp:base/cvd/cuttlefish/host/libs/config/cuttlefish_config.cpp +--map=host/libs/config/cuttlefish_config.h:base/cvd/cuttlefish/host/libs/config/cuttlefish_config.h +--map=host/libs/config/cuttlefish_config_environment.cpp:base/cvd/cuttlefish/host/libs/config/cuttlefish_config_environment.cpp +--map=host/libs/config/cuttlefish_config_instance.cpp:base/cvd/cuttlefish/host/libs/config/cuttlefish_config_instance.cpp +--map=host/libs/config/fetcher_config.cpp:base/cvd/cuttlefish/host/libs/config/fetcher_config.cpp +--map=host/libs/config/fetcher_config.h:base/cvd/cuttlefish/host/libs/config/fetcher_config.h +--map=host/libs/config/host_tools_version.cpp:base/cvd/cuttlefish/host/libs/config/host_tools_version.cpp +--map=host/libs/config/host_tools_version.h:base/cvd/cuttlefish/host/libs/config/host_tools_version.h +--map=host/libs/config/inject.h:base/cvd/cuttlefish/host/libs/config/inject.h +--map=host/libs/config/instance_nums.cpp:base/cvd/cuttlefish/host/libs/config/instance_nums.cpp +--map=host/libs/config/instance_nums.h:base/cvd/cuttlefish/host/libs/config/instance_nums.h +--map=host/libs/config/known_paths.h:base/cvd/cuttlefish/host/libs/config/known_paths.h +--map=host/libs/config/logging.cpp:base/cvd/cuttlefish/host/libs/config/logging.cpp +--map=host/libs/config/logging.h:base/cvd/cuttlefish/host/libs/config/logging.h +--map=host/libs/web/android_build_api.cpp:base/cvd/cuttlefish/host/libs/web/android_build_api.cpp +--map=host/libs/web/android_build_api.h:base/cvd/cuttlefish/host/libs/web/android_build_api.h +--map=host/libs/web/android_build_string.cpp:base/cvd/cuttlefish/host/libs/web/android_build_string.cpp +--map=host/libs/web/android_build_string.h:base/cvd/cuttlefish/host/libs/web/android_build_string.h +--map=host/libs/web/credential_source.cc:base/cvd/cuttlefish/host/libs/web/credential_source.cc +--map=host/libs/web/credential_source.h:base/cvd/cuttlefish/host/libs/web/credential_source.h +--map=host/libs/web/http_client.cc:base/cvd/cuttlefish/host/libs/web/http_client/http_client.cc +--map=host/libs/web/http_client.h:base/cvd/cuttlefish/host/libs/web/http_client/http_client.h +--map=host/libs/web/http_client/http_client.cc:base/cvd/cuttlefish/host/libs/web/http_client/http_client.cc +--map=host/libs/web/http_client/http_client.h:base/cvd/cuttlefish/host/libs/web/http_client/http_client.h +--map=host/libs/web/http_client/http_client_util.cc:base/cvd/cuttlefish/host/libs/web/http_client/http_client_util.cc +--map=host/libs/web/http_client/http_client_util.h:base/cvd/cuttlefish/host/libs/web/http_client/http_client_util.h +--map=host/libs/web/http_client/sso_client.cc:base/cvd/cuttlefish/host/libs/web/http_client/sso_client.cc +--map=host/libs/web/http_client/sso_client.h:base/cvd/cuttlefish/host/libs/web/http_client/sso_client.h +--map=host/libs/web/http_client/unittest/http_client_util_test.cc:base/cvd/cuttlefish/host/libs/web/http_client/unittest/http_client_util_test.cc +--map=host/libs/web/http_client/unittest/main_test.cc:base/cvd/cuttlefish/host/libs/web/http_client/unittest/main_test.cc +--map=host/libs/web/http_client/unittest/sso_client_test.cc:base/cvd/cuttlefish/host/libs/web/http_client/unittest/sso_client_test.cc + +# Protos go to the top directory +--map=host/commands/assemble_cvd/proto/launch_cvd.proto:base/cvd/launch_cvd.proto +--map=host/commands/cvd/metrics/proto/clientanalytics.proto:base/cvd/clientanalytics.proto +--map=host/commands/cvd/metrics/proto/internal_user_log.proto:base/cvd/internal_user_log.proto +--map=host/commands/cvd/proto/cvd_server.proto:base/cvd/cvd_server.proto +--map=host/commands/cvd/proto/internal_config.proto:base/cvd/internal_config.proto +--map=host/commands/cvd/proto/user_config.proto:base/cvd/user_config.proto + diff --git a/tools/minimerge/cuttlefish_import_metadata.txt b/tools/minimerge/cuttlefish_import_metadata.txt new file mode 100644 index 0000000000..6e74148d84 --- /dev/null +++ b/tools/minimerge/cuttlefish_import_metadata.txt @@ -0,0 +1 @@ +last_import: 9f4e903feb1e24d2973c9f90b6a77fd8bd2b0a92 diff --git a/tools/minimerge/import.sh b/tools/minimerge/import.sh new file mode 100755 index 0000000000..39fcfd56e2 --- /dev/null +++ b/tools/minimerge/import.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +set -e + +DIR="$(realpath "$(dirname $0)")" +REPO_DIR="$(git rev-parse --show-toplevel)" +TOOL="${DIR}/minimerge_tool" +METADATA_FILE="${DIR}/cuttlefish_import_metadata.txt" +CUTTLEFISH_REPO_URL="https://android.googlesource.com/device/google/cuttlefish" + +echo "Importing AOSP's device/google/cuttlefish project" + +# Do these early to avoid doing unnecessary work when these fail +# Find last imported commit +stat "${METADATA_FILE}" >/dev/null +last_imported_commit="$(grep '^[[:space:]]*last_import[[:space:]]*:' "${METADATA_FILE}" | cut -d: -f2 | grep -o -E '[^[:space:]]+' )" +# Find current commit +pushd "${DIR}" >/dev/null +[[ `git status --porcelain` ]] >/dev/null && (echo "Repository is not in clean state"; exit 1) +current_commit="$(git rev-parse HEAD)" +popd >/dev/null + + +echo "Compiling minimerge tool..." +pushd ${DIR} >/dev/null +g++ main.cpp -o "${TOOL}" -lgit2 -O2 +popd >/dev/null + +echo "Cloning cuttlefish repo..." +CUTTLEFISH_DIR="$(mktemp -t -d cuttlefish.XXX)" +trap "rm -rf '${CUTTLEFISH_DIR}'" EXIT +git clone "${CUTTLEFISH_REPO_URL}" "${CUTTLEFISH_DIR}" + +# Find commit to import +pushd "${CUTTLEFISH_DIR}" >/dev/null +current_import="$(git rev-parse HEAD)" +popd >/dev/null + +if [[ "${last_imported_commit}" == "${current_import}" ]]; then + echo "Nothing to import" + exit 0 +fi + +function import() { + "${TOOL}" \ + --source="${CUTTLEFISH_DIR}" \ + --destination="${REPO_DIR}" \ + --rev_range="${last_imported_commit}..HEAD" \ + "@${DIR}/cuttlefish_args.txt" +} + +import || (git reset --hard "${current_commit}"; exit 1) + +pushd "${DIR}" >/dev/null +cat >"${METADATA_FILE}" </dev/null diff --git a/tools/minimerge/libbase_args.txt b/tools/minimerge/libbase_args.txt new file mode 100644 index 0000000000..74be8af960 --- /dev/null +++ b/tools/minimerge/libbase_args.txt @@ -0,0 +1,28 @@ +--map=file.cpp:base/cvd/android-base/file.cpp +--map=logging.cpp:base/cvd/android-base/logging.cpp +--map=parsebool.cpp:base/cvd/android-base/parsebool.cpp +--map=posix_strerror_r.cpp:base/cvd/android-base/posix_strerror_r.cpp +--map=strings.cpp:base/cvd/android-base/strings.cpp +--map=stringprintf.cpp:base/cvd/android-base/stringprintf.cpp +--map=threads.cpp:base/cvd/android-base/threads.cpp +--map=include/android-base/cmsg.h:base/cvd/android-base/cmsg.h +--map=include/android-base/collections.h:base/cvd/android-base/collections.h +--map=include/android-base/endian.h:base/cvd/android-base/endian.h +--map=include/android-base/errno_restorer.h:base/cvd/android-base/errno_restorer.h +--map=include/android-base/errors.h:base/cvd/android-base/errors.h +--map=include/android-base/expected.h:base/cvd/android-base/expected.h +--map=include/android-base/file.h:base/cvd/android-base/file.h +--map=include/android-base/format.h:base/cvd/android-base/format.h +--map=include/android-base/log.h:base/cvd/android-base/log.h +--map=include/android-base/logging.h:base/cvd/android-base/logging.h +--map=include/android-base/macros.h:base/cvd/android-base/macros.h +--map=include/android-base/off64_t.h:base/cvd/android-base/off64_t.h +--map=include/android-base/parsebool.h:base/cvd/android-base/parsebool.h +--map=include/android-base/parseint.h:base/cvd/android-base/parseint.h +--map=include/android-base/result.h:base/cvd/android-base/result.h +--map=include/android-base/scopeguard.h:base/cvd/android-base/scopeguard.h +--map=include/android-base/stringprintf.h:base/cvd/android-base/stringprintf.h +--map=include/android-base/strings.h:base/cvd/android-base/strings.h +--map=include/android-base/threads.h:base/cvd/android-base/threads.h +--map=include/android-base/unique_fd.h:base/cvd/android-base/unique_fd.h +--map=include/android-base/utf8.h:base/cvd/android-base/utf8.h diff --git a/tools/minimerge/main.cpp b/tools/minimerge/main.cpp new file mode 100644 index 0000000000..1508680709 --- /dev/null +++ b/tools/minimerge/main.cpp @@ -0,0 +1,369 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +int handle_git2_error(int code) { + if (code >= 0) { + return code; + } + std::cerr << "error code " << code << "\n"; + auto error = git_error_last(); + if (error != nullptr) { + std::cerr << "error klass \"" << error->klass << "\"\n"; + std::cerr << "error \"" << error->message << "\"\n"; + } + std::abort(); +} + +struct FreeGitType { + void operator()(git_commit* commit) { git_commit_free(commit); } + void operator()(git_diff* commit) { git_diff_free(commit); } + void operator()(git_diff_stats* stats) { git_diff_stats_free(stats); } + void operator()(git_index* index) { git_index_free(index); } + void operator()(git_object* obj) { git_object_free(obj); } + void operator()(git_reference* ref) { git_reference_free(ref); } + void operator()(git_repository* repo) { git_repository_free(repo); } + void operator()(git_revwalk* walk) { git_revwalk_free(walk); } + void operator()(git_tree* tree) { git_tree_free(tree); } + void operator()(git_tree_entry* tree) { git_tree_entry_free(tree); } +}; + +template +using ManagedGitType = std::unique_ptr; + +template +ManagedGitType ManagedGitErr(F function, Args... args) { + T* output = nullptr; + handle_git2_error(function(&output, args...)); + return ManagedGitType(output); +} + +struct GitBuf { + GitBuf() : buffer({0}) {} + GitBuf(GitBuf&& other) { + buffer = std::move(other.buffer); + other.buffer = {}; + } + ~GitBuf() { git_buf_dispose(&buffer); } + GitBuf& operator=(GitBuf&& other) { + git_buf_dispose(&buffer); + buffer = std::move(other.buffer); + other.buffer = {0}; + return *this; + } + + git_buf buffer; +}; + +struct GitSignature { + GitSignature(const git_signature* signature) { + if (signature) { + if (signature->name) { name = signature->name; } + if (signature->email) { email = signature->email; } + when = signature->when; + } + } + + std::string name; + std::string email; + git_time when; + + operator git_signature() { + return git_signature { + name.data(), + email.data(), + when, + }; + } +}; + +struct UnanchoredCommit { + GitSignature author; + GitSignature committer; + std::string message_encoding; + std::string message; + std::unordered_map updated_file_contents; +}; + +std::optional FilterCommit(const git_commit& commit, const std::vector>& mappings) { + auto commit_tree = ManagedGitErr(git_commit_tree, &commit); + + std::unordered_map updated_file_contents; + for (size_t i = 0; i < git_commit_parentcount(&commit); i++) { + auto parent = ManagedGitErr(git_commit_parent, &commit, i); + auto parent_tree = ManagedGitErr(git_commit_tree, parent.get()); + auto repo = git_commit_owner(&commit); + auto diff = ManagedGitErr(git_diff_tree_to_tree, repo, parent_tree.get(), commit_tree.get(), nullptr); + for (size_t i = 0; i < git_diff_num_deltas(diff.get()); i++) { + auto delta = git_diff_get_delta(diff.get(), i); + std::string old_file = delta->old_file.path; + std::string new_file = delta->new_file.path; + for (const auto& [mapping_key, mapping_value] : mappings) { + if (mapping_key == old_file || mapping_key == new_file) { + git_tree_entry* entry = nullptr; + auto ret = git_tree_entry_bypath(&entry, commit_tree.get(), new_file.c_str()); + if (ret == GIT_ENOTFOUND) { + //updated_file_contents[std::string(mapping_value)] = GitBuf(); + continue; + } else { + handle_git2_error(ret); + } + ManagedGitType managed_entry(entry); + auto object = ManagedGitErr(git_tree_entry_to_object, git_commit_owner(&commit), entry); + if (git_object_type(object.get()) != GIT_OBJECT_BLOB) { + std::cerr << "object was not blob\n"; + std::abort(); + } + git_blob* blob = reinterpret_cast(object.get()); + GitBuf buf; + git_buf_set(&buf.buffer, git_blob_rawcontent(blob), git_blob_rawsize(blob)); + updated_file_contents[std::string(mapping_value)] = std::move(buf); + } + } + } + } + if (updated_file_contents.empty()) { + return std::nullopt; + } + UnanchoredCommit ret = { + GitSignature(git_commit_author(&commit)), + GitSignature(git_commit_committer(&commit)), + }; + if (auto encoding = git_commit_message_encoding(&commit); encoding != nullptr) { + ret.message_encoding = encoding; + }; + if (auto message = git_commit_message(&commit); message != nullptr) { + ret.message = message; + } + ret.updated_file_contents = std::move(updated_file_contents); + return ret; +} + +std::optional FixupCommit(git_repository& repo, const std::vector>& mappings) { + struct Payload { + git_repository* repo; + std::unordered_map file_contents; + const std::vector>* mappings; + }; + Payload payload; + payload.repo = &repo; + payload.mappings = &mappings; + auto obj = ManagedGitErr(git_revparse_single, &repo, "HEAD^{tree}"); + auto tree = reinterpret_cast(obj.get()); + handle_git2_error(git_tree_walk(tree, GIT_TREEWALK_PRE, [](const char* root, const git_tree_entry* entry, void* void_payload) { + Payload* payload = reinterpret_cast(void_payload); + auto name = std::string(root) + std::string(git_tree_entry_name(entry)); + for (const auto& [mapping_key, mapping_value] : *(payload->mappings)) { + if (name == mapping_key) { + std::cerr << "Ensuring '" << name << "' is correct\n"; + auto object = ManagedGitErr(git_tree_entry_to_object, payload->repo, entry); + if (git_object_type(object.get()) != GIT_OBJECT_BLOB) { + std::cerr << "object was not blob\n"; + std::abort(); + } + git_blob* blob = reinterpret_cast(object.get()); + GitBuf buf; + git_buf_set(&buf.buffer, git_blob_rawcontent(blob), git_blob_rawsize(blob)); + (payload->file_contents)[std::string(mapping_value)] = std::move(buf); + break; + } + } + return 0; + }, &payload)); + GitSignature signature(nullptr); + signature.name = "No one"; + signature.email = "No-one@google.com"; + signature.when.time = 0; + signature.when.offset = 0; + signature.when.sign = 0; + UnanchoredCommit ret = { signature, signature }; + ret.updated_file_contents = std::move(payload.file_contents); + return ret; +} + +void ApplyCommit(git_repository& repo, UnanchoredCommit& commit) { + auto head = ManagedGitErr(git_revparse_single, &repo, "HEAD"); + if (git_object_type(head.get()) != GIT_OBJECT_COMMIT) { + std::cerr << "HEAD was not a commit\n"; + std::abort(); + } + const git_commit* head_commit = reinterpret_cast(head.get()); + auto head_tree = ManagedGitErr(git_commit_tree, head_commit); + std::vector tree_updates; + for (const auto& [path, contents] : commit.updated_file_contents) { + git_oid blob_id; + handle_git2_error(git_blob_create_from_buffer(&blob_id, &repo, contents.buffer.ptr, contents.buffer.size)); + tree_updates.emplace_back(git_tree_update { + GIT_TREE_UPDATE_UPSERT, + blob_id, + GIT_FILEMODE_BLOB, + path.c_str(), + }); + } + git_oid tree_id; + handle_git2_error( + git_tree_create_updated( + &tree_id, + &repo, + head_tree.get(), + tree_updates.size(), + tree_updates.data())); + auto tree_obj = ManagedGitErr(git_object_lookup, &repo, &tree_id, GIT_OBJECT_TREE); + git_tree* tree = reinterpret_cast(tree_obj.get()); + + auto diff = ManagedGitErr(git_diff_tree_to_tree, &repo, head_tree.get(), tree, nullptr); + auto stats = ManagedGitErr(git_diff_get_stats, diff.get()); + if (git_diff_stats_insertions(stats.get()) == 0 && git_diff_stats_deletions(stats.get()) == 0) { + return; + } + + git_oid new_commit_id; + git_signature author = commit.author; + git_signature committer = commit.committer; + handle_git2_error( + git_commit_create( + &new_commit_id, + &repo, + "HEAD", + &author, + &committer, + commit.message_encoding.empty() ? nullptr : commit.message_encoding.c_str(), + commit.message.c_str(), + tree, + 1, + &head_commit)); + + std::cerr << "Applied " << commit.author.email << " " << commit.message.substr(0, commit.message.find("\n")) << "\n"; +} + +static constexpr char kUsage[] = R"raw( +"mini merge" tool. + +Creates commits in a destination repository matching commits in the source repository +filtered down to a smaller set of files. + +`--help`: Print this message +`--source_repo=/path/to/git/repo`: Where to pull commits from +`--dest_repo=/path/to/git/repo`: Where to push commits to +`--map=/source/path:/dest/path`: Relative path mapping within the repository +)raw"; + +std::optional ArgValue(std::string_view name, std::string_view arg) { + if (arg.substr(0, name.size()) != name) { + return std::nullopt; + } + arg.remove_prefix(name.size()); + return arg; +} + +int RunMiniMerge(int argc, char** argv) { + argc--; + argv++; + std::vector args(argc); + std::transform(argv, argv + argc, args.begin(), [](auto arg) { return std::string_view(arg); }); + + for (size_t i = 0; i < args.size(); i++) { + if (args[i].size() > 0 && args[i][0] == '@') { + std::ifstream arg_file(std::string(args[i].substr(1))); + if (!arg_file.is_open()) { + std::cerr << "Failed to open \"" << args[i] << "\n"; + std::abort(); + } + args.erase(args.begin() + i); + for (std::string line; std::getline(arg_file, line);) { + if (line.size() > 0 && line[0] != '#') { + args.insert(args.begin() + i, line); + } + } + } + } + for (const auto& arg : args) { + std::cerr << "Argument \"" << arg << "\"\n"; + } + + if (std::find(args.begin(), args.end(), "--help") != args.end()) { + std::cerr << kUsage; + } + + std::string source_path; + std::string destination_path; + std::string revision_range; + std::vector> mappings; + for (const auto& arg : args) { + if (auto path = ArgValue("--destination=", arg)) { destination_path = *path; } + else if (auto path = ArgValue("--source=", arg)) { source_path = *path; } + else if (auto path = ArgValue("--rev_range=", arg)) { revision_range = *path; } + else if (auto mapping = ArgValue("--map=", arg)) { + auto separator_index = mapping->find(":"); + if (separator_index == std::string_view::npos) { + std::cerr << "Error in arg `--map=" << *mapping << "`: no separator\n"; + std::abort(); + } + mappings.emplace_back(mapping->substr(0, separator_index), mapping->substr(separator_index + 1)); + } + } + auto source = ManagedGitErr(git_repository_open, source_path.c_str()); + auto dest = ManagedGitErr(git_repository_open, destination_path.c_str()); + + std::vector input_files; + for (const auto& mapping : mappings) { + input_files.emplace_back(mapping.first); + } + auto walk = ManagedGitErr(git_revwalk_new, source.get()); + handle_git2_error(git_revwalk_sorting(walk.get(), GIT_SORT_TOPOLOGICAL)); + if (revision_range.empty()) { + handle_git2_error(git_revwalk_push_head(walk.get())); + } else { + handle_git2_error(git_revwalk_push_range(walk.get(), revision_range.c_str())); + } + git_oid oid; + std::vector commits; + while (!git_revwalk_next(&oid, walk.get())) { + auto commit = ManagedGitErr(git_commit_lookup, source.get(), &oid); + auto filtered_commit = FilterCommit(*commit, mappings); + if (!filtered_commit) { + continue; + } + commits.emplace_back(std::move(*filtered_commit)); + std::string summary = git_commit_summary(commit.get()); + std::cerr << "Discovered " << git_commit_author(commit.get())->email << " " << summary << "\n"; + } + for (auto it = commits.rbegin(); it != commits.rend(); it++) { + ApplyCommit(*dest, *it); + } + + auto fixup = FixupCommit(*source, mappings); + if (fixup.has_value()) { + ApplyCommit(*dest, *fixup); + } + + auto tree_obj = ManagedGitErr(git_revparse_single, dest.get(), "HEAD^{tree}"); + auto tree = reinterpret_cast(tree_obj.get()); + + auto index = ManagedGitErr(git_repository_index, dest.get()); + handle_git2_error(git_index_read_tree(index.get(), tree)); + handle_git2_error(git_index_write(index.get())); + + git_checkout_options checkout_options; + handle_git2_error(git_checkout_options_init(&checkout_options, GIT_CHECKOUT_OPTIONS_VERSION)); + checkout_options.checkout_strategy = GIT_CHECKOUT_FORCE; + handle_git2_error(git_checkout_tree(dest.get(), tree_obj.get(), &checkout_options)); + + return 0; +} + +int main(int argc, char** argv) { + handle_git2_error(git_libgit2_init()); + int ret = RunMiniMerge(argc, argv); + handle_git2_error(git_libgit2_shutdown()); + return ret; +} +