diff options
author | Xin Li <delphij@google.com> | 2019-09-04 13:35:27 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2019-09-04 13:35:27 -0700 |
commit | 99c42afb19204ffd6e525b28e57989e0dbcf2b64 (patch) | |
tree | a5c3e70d0c965d5b0cd9dc2e7a9f881fcd4ac971 | |
parent | 23cc552f8685522d75e41bd41539f300b89056f8 (diff) | |
parent | 4275a31fdc1814baa1f1443c056c4f36a98da84b (diff) | |
download | platform_system_iorap-ndk-sysroot-r21.tar.gz platform_system_iorap-ndk-sysroot-r21.tar.bz2 platform_system_iorap-ndk-sysroot-r21.zip |
DO NOT MERGE - Merge Android 10 into masterndk-sysroot-r21
Bug: 139893257
Change-Id: Iade42e1a78a3f2fdd0fe86e274028138114ba6aa
24 files changed, 7623 insertions, 46 deletions
@@ -48,6 +48,18 @@ cc_defaults { "src", ], + /* + TODO: Header refactoring cleanup: + + Option 1): Move src/$component/file_name.h to src/$component/include/$component/file_name.h + Option 2): Symlink src/$component/include/$component to src/$component + + Set export_include_dirs to '$component/include' for that component. + + Also delete the 'include' directory unless we have code other non-iorap + targets are allowed to reference. + */ + clang: true, shared_libs: ["libbase"], } @@ -57,31 +69,29 @@ cc_defaults { static_libs: [ "libiorap-binder", + "libplatformprotos", // android framework C++ protos. ], shared_libs: [ - "libiorap", "libbinder", "libutils", "libcutils", // tracing. + "libfruit", // dependency injection. // TODO: remove these annoying dependencies by hiding them in the main library code. - ], -} -cc_library_shared { - name: "libiorap", - defaults: [ - "iorap-default-flags", - ], - srcs: [ + // dependency for libplatformprotos + // "libprotobuf-cpp-lite", + + // libplatformprotos has an indirect dependency on full, causing compilation/linking + // errors if we use lite + "libprotobuf-cpp-full", ], - //export_include_dirs: ["include"], - //local_include_dirs: ["src/native"], + // srcs: [":libprotobuf-internal-protos"], + // commented out because it causes compilation errors + // TODO: can we use the lite library somehow? - static_libs: [ - "libprotobuf-cpp-lite", - ], + header_libs: ["librxcpp"], } cc_library_static { @@ -102,6 +112,34 @@ cc_library_static { include_dirs: ["frameworks/native/aidl/binder"], export_aidl_headers: true, }, + static_libs: [ + "libplatformprotos", // android framework C++ protos. + ], +} + +cc_defaults { + name: "libiorap-manager-default-dependencies", + static_libs: [ + "libiorap-perfetto", + ], + defaults: [ + "libiorap-perfetto-default-dependencies", + ], + // Users of 'libiorap-manager' also need to include these defaults to avoid + // linking errors. +} + +cc_library_static { + name: "libiorap-manager", + defaults: [ + "iorap-default-flags", + "iorap-default-dependencies", + "libiorap-manager-default-dependencies", + ], + + srcs: [ + "src/manager/**/*.cc", + ], } cc_binary { @@ -109,18 +147,61 @@ cc_binary { defaults: [ "iorap-default-flags", "iorap-default-dependencies", + "libiorap-manager-default-dependencies", ], srcs: [ "src/iorapd/main.cc", ], + static_libs: [ + "libiorap-manager", + ], init_rc: [ "iorapd.rc", ], } +cc_library_static { + name: "libiorap-inode2filename", + defaults: [ + "iorap-default-flags", + "iorap-default-dependencies", + ], + + srcs: [ + "src/inode2filename/**/*.cc", + ], +} + +cc_binary { + name: "iorap.inode2filename", + defaults: [ + "iorap-default-flags", + "iorap-default-dependencies", + ], + srcs: [ + "src/inode2filename/**/*.cc", + ], + // Easier debugging. TODO: make a separate debug config. + // XX: Using -O0 seems to completely hide some errors. + cflags: ["-O2", "-UNDEBUG", "-DIORAP_INODE2FILENAME_MAIN=1"], + sanitize: { + undefined: true, + all_undefined: true, + // Pretty print when ubsan detects a problem. + // Otherwise it just calls abort(). + +/* + diag: { + undefined: true, + }, + */ // don't use the diag when you want it to crash. + }, +} + cc_test { name: "iorapd-tests", test_suites: ["device-tests"], + gtest: false, // we use gtest *and* gmock. defaults: [ "iorap-default-flags", "iorap-default-dependencies", @@ -128,4 +209,88 @@ cc_test { srcs: [ "tests/src/**/*.cc", ], + cflags: ["-O2", "-UNDEBUG"], + + // TODO: we should probably have per-component tests. + static_libs: ["libgmock_main", "libgmock", "libgtest", "libiorap-inode2filename"], +} + +filegroup { + name: "libiorap-perfetto-protos", + srcs: [ + ], +} + +// Static libraries cannot export their dependencies, +// the current convention is to use an extra 'defaults' rule for statics +// to bring in all the dependencies. +cc_defaults { + name: "libiorap-perfetto-default-dependencies", + + // Some of the libperfetto header typedefs leak out into iorap. + // Avoids compilation #include errors. + // TODO: clean this up, the headers should not leak out (maybe all we need is a PerfettoConsumer + // forward declaration?). + include_dirs: ["external/perfetto/include"], + // Various perfetto protos are used directly by iorap. + // + // Furthermore, we need this regardless to avoid linking errors when linking + // libiorap-perfetto.a into the main cc_binary rule. + static_libs: [ + "perfetto_trace_protos", + ], + + shared_libs: [ + // Not part of true dependencies: Users of 'libiorap-perfetto' do not link against + // libperfetto. + // We only put this to avoid linking errors when building iorapd. + // TODO: can we split iorapd into libiorapd-main that doesn't link against libperfetto? + // only the last cc_binary should need the full transitive closure of the dependency graph. + "libperfetto", + ] +} + +cc_library_static { + name: "libiorap-perfetto", + defaults: [ + "iorap-default-flags", + "iorap-default-dependencies", + "libiorap-perfetto-default-dependencies", + ], + + srcs: [ + "src/perfetto/**/*.cc", + ], +} + +cc_binary { + name: "iorap.cmd.perfetto", + defaults: [ + "iorap-default-flags", + "iorap-default-dependencies", + ], + shared_libs: ["libperfetto"], + include_dirs: ["external/perfetto/include"], + srcs: [ + "src/perfetto/**/*.cc", + ], + // Easier debugging. TODO: make a separate debug config. + // XX: Using -O0 seems to completely hide some errors. + cflags: ["-O2", "-UNDEBUG", "-DIORAP_PERFETTO_MAIN=1"], + sanitize: { + undefined: true, + all_undefined: true, + // Pretty print when ubsan detects a problem. + // Otherwise it just calls abort(). + +/* + diag: { + undefined: true, + }, + */ // don't use the diag when you want it to crash. + }, + + static_libs: [ + "perfetto_trace_protos", + ], } diff --git a/binder/com/google/android/startop/iorap/AppLaunchEvent.aidl b/binder/com/google/android/startop/iorap/AppLaunchEvent.aidl new file mode 100644 index 0000000..ea3f9f1 --- /dev/null +++ b/binder/com/google/android/startop/iorap/AppLaunchEvent.aidl @@ -0,0 +1,20 @@ +/* + * 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. + */ + +package com.google.android.startop.iorap; + +/** @hide */ +parcelable AppLaunchEvent cpp_header "binder/app_launch_event.h"; diff --git a/binder/com/google/android/startop/iorap/IIorap.aidl b/binder/com/google/android/startop/iorap/IIorap.aidl index 2b58905..fe91d15 100644 --- a/binder/com/google/android/startop/iorap/IIorap.aidl +++ b/binder/com/google/android/startop/iorap/IIorap.aidl @@ -19,6 +19,7 @@ package com.google.android.startop.iorap; import com.google.android.startop.iorap.ITaskListener; import com.google.android.startop.iorap.PackageEvent; +import com.google.android.startop.iorap.AppLaunchEvent; import com.google.android.startop.iorap.AppIntentEvent; import com.google.android.startop.iorap.SystemServiceEvent; import com.google.android.startop.iorap.SystemServiceUserEvent; @@ -107,6 +108,7 @@ oneway interface IIorap { // in frameworks/base/startop/src/com/google/android/startop/iorap/${Type}Event.java // void onActivityHintEvent(in RequestId request, in ActivityHintEvent event); + void onAppLaunchEvent(in RequestId request, in AppLaunchEvent event); void onPackageEvent(in RequestId request, in PackageEvent event); void onAppIntentEvent(in RequestId request, in AppIntentEvent event); void onSystemServiceEvent(in RequestId request, in SystemServiceEvent event); diff --git a/include/binder/app_launch_event.h b/include/binder/app_launch_event.h new file mode 100644 index 0000000..784db16 --- /dev/null +++ b/include/binder/app_launch_event.h @@ -0,0 +1,400 @@ +/* + * 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 IORAP_BINDER_APP_LAUNCH_EVENT_H_ +#define IORAP_BINDER_APP_LAUNCH_EVENT_H_ + +#include "binder/common.h" +#include "common/introspection.h" +#include "common/expected.h" + +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <frameworks/base/core/proto/android/content/intent.pb.h> // IntentProto +#include <frameworks/base/core/proto/android/server/activitymanagerservice.pb.h> // ActivityRecord + +namespace iorap { +namespace binder { + +// These protos are part of the iorapd binder ABI, alias them for easier usage. +using IntentProto = ::android::content::IntentProto; +using ActivityRecordProto = ::com::android::server::am::ActivityRecordProto; + +struct AppLaunchEvent : public ::android::Parcelable { + // Index position matters: Keep up-to-date with AppLaunchEvent.java sTypes field. + enum class Type : int32_t { + kUninitialized = -1, + kIntentStarted = 0, + kIntentFailed = 1, + kActivityLaunched = 2, + kActivityLaunchFinished = 3, + kActivityLaunchCancelled = 4, + }; + + enum class Temperature : int32_t { + kUninitialized = -1, + kCold = 1, + kWarm = 2, + kHot = 3, + }; + + Type type{Type::kUninitialized}; + int64_t sequence_id{-1}; + // kIntentStarted only. + std::unique_ptr<IntentProto> intent_proto; + // kActivityLaunched only. + Temperature temperature{Temperature::kUninitialized}; + // kActivityLaunch*. Can be null in kActivityLaunchCancelled. + std::unique_ptr<ActivityRecordProto> activity_record_proto; + + AppLaunchEvent() = default; + AppLaunchEvent(Type type, + int64_t sequence_id, + std::unique_ptr<IntentProto> intent_proto = nullptr, + Temperature temperature = Temperature::kUninitialized, + std::unique_ptr<ActivityRecordProto> activity_record_proto = nullptr) + : type(type), + sequence_id(sequence_id), + intent_proto(std::move(intent_proto)), + temperature(temperature), + activity_record_proto(std::move(activity_record_proto)) { + } + + ::android::status_t readFromParcel(const android::Parcel* parcel) override { + +# define PARCEL_READ_OR_RETURN(function, ...) \ + if (::android::status_t res = function(__VA_ARGS__); res != ::android::NO_ERROR) { \ + LOG(ERROR) << "AppLaunchEvent::readFromParcel failed"; \ + return res; \ + } + + int32_t type_int; + PARCEL_READ_OR_RETURN(parcel->readInt32, &type_int); + type = static_cast<Type>(type_int); + + LOG(VERBOSE) << "AppLaunchEvent::readFromParcel (type=" << type_int << ")"; + + PARCEL_READ_OR_RETURN(parcel->readInt64, &sequence_id); + + switch (type) { + case Type::kIntentStarted: + PARCEL_READ_OR_RETURN(readIntent, parcel); + break; + case Type::kIntentFailed: + // No extra arguments. + break; + case Type::kActivityLaunched: { + PARCEL_READ_OR_RETURN(readActivityRecordProto, parcel); + int32_t temperature_int; + PARCEL_READ_OR_RETURN(parcel->readInt32, &temperature_int); + temperature = static_cast<Temperature>(temperature_int); + break; + } + case Type::kActivityLaunchFinished: + PARCEL_READ_OR_RETURN(readActivityRecordProto, parcel); + break; + case Type::kActivityLaunchCancelled: + PARCEL_READ_OR_RETURN(readActivityRecordProtoNullable, parcel); + break; + default: + return android::BAD_VALUE; + } +# undef PARCEL_READ_OR_RETURN + + return ::android::NO_ERROR; + + // TODO: std::variant + protobuf implementation in AutoParcelable. + } + +#define PARCEL_WRITE_OR_RETURN(function, ...) \ + if (::android::status_t res = function(__VA_ARGS__); res != ::android::NO_ERROR) { \ + return res; \ + } + + ::android::status_t writeToParcel(android::Parcel* parcel) const override { + PARCEL_WRITE_OR_RETURN(parcel->writeInt32, static_cast<int32_t>(type)); + PARCEL_WRITE_OR_RETURN(parcel->writeInt64, sequence_id); + + switch (type) { + case Type::kIntentStarted: + PARCEL_WRITE_OR_RETURN(writeIntent, parcel); + break; + case Type::kIntentFailed: + // No extra arguments. + break; + case Type::kActivityLaunched: + PARCEL_WRITE_OR_RETURN(writeActivityRecordProto, parcel); + PARCEL_WRITE_OR_RETURN(parcel->writeInt32, static_cast<int32_t>(temperature)); + break; + case Type::kActivityLaunchFinished: + PARCEL_WRITE_OR_RETURN(writeActivityRecordProto, parcel); + break; + case Type::kActivityLaunchCancelled: + PARCEL_WRITE_OR_RETURN(writeActivityRecordProtoNullable, parcel); + break; + default: + DCHECK(false) << "attempted to write an uninitialized AppLaunchEvent to Parcel"; + return android::BAD_VALUE; + } + +#undef PARCEL_WRITE_OR_RETURN + + return android::NO_ERROR; + } + + private: + // Using 'unique_ptr' here because protobufs don't have a move constructor. Is there + // a better way that is cheap to pass them around? + template <typename T> + static expected<std::unique_ptr<T>, ::android::status_t> + ReadProto(const android::Parcel* parcel) { + DCHECK(parcel != nullptr); + + ::android::status_t res; + + std::vector<uint8_t> byte_vector; + if ((res = parcel->readByteVector(/*out*/&byte_vector)) != ::android::NO_ERROR) { + return unexpected(res); + } + // TODO: we may want to do this without an extra copy, by parsing + // the protobuf directly out of the parcel. + + const uint8_t* data = byte_vector.data(); + const size_t size = byte_vector.size(); + + std::unique_ptr<T> proto_ptr{new T{}}; + + if (!proto_ptr) { + return unexpected(::android::NO_MEMORY); + } + + if (!proto_ptr->ParseFromArray(data, size)) { + return unexpected(::android::BAD_VALUE); + } + + return proto_ptr; + } + + template <typename T> + static expected<std::unique_ptr<T>, ::android::status_t> + ReadNullableProto(const android::Parcel* parcel) { + DCHECK(parcel != nullptr); + + bool value; + + ::android::status_t res; + res = parcel->readBool(/*out*/&value); + + if (res != ::android::NO_ERROR) { + return unexpected(res); + } + + if (!value) { + return std::unique_ptr<T>{nullptr}; + } + + return ReadProto<T>(parcel); + } + + template <typename T> + static ::android::status_t + WriteProto(android::Parcel* parcel, const std::unique_ptr<T>& proto) { + DCHECK(parcel != nullptr); + DCHECK(proto != nullptr); + + std::vector<uint8_t> byte_vector; + { + const int serialized_size = proto->ByteSize(); + byte_vector.resize(serialized_size); + if (!proto->SerializeToArray(byte_vector.data(), serialized_size)) { + return ::android::BAD_VALUE; + } + } + + ::android::status_t res; + if ((res = parcel->writeByteVector(/*in*/byte_vector)) != ::android::NO_ERROR) { + return res; + } + + return ::android::NO_ERROR; + } + + template <typename T> + static ::android::status_t + WriteNullableProto(android::Parcel* parcel, const std::unique_ptr<T>& maybe_proto) { + bool value = (maybe_proto != nullptr); + + ::android::status_t res; + res = parcel->writeBool(value); + + if (res != ::android::NO_ERROR) { + return res; + } + + if (!value) { + return ::android::NO_ERROR; + } + + return WriteProto<T>(parcel, maybe_proto); + } + + android::status_t readIntent(const android::Parcel* parcel) { + expected<std::unique_ptr<IntentProto>, ::android::status_t> maybe_intent = + ReadProto<IntentProto>(parcel); + + if (maybe_intent) { + intent_proto = std::move(maybe_intent.value()); + return ::android::NO_ERROR; + } else { + return maybe_intent.error(); + } + } + + android::status_t readActivityRecordProto(const android::Parcel* parcel) { + expected<std::unique_ptr<ActivityRecordProto>, ::android::status_t> maybe_record = + ReadProto<ActivityRecordProto>(parcel); + + if (maybe_record) { + activity_record_proto = std::move(maybe_record.value()); + return ::android::NO_ERROR; + } else { + return maybe_record.error(); + } + } + + android::status_t readActivityRecordProtoNullable(const android::Parcel* parcel) { + expected<std::unique_ptr<ActivityRecordProto>, ::android::status_t> maybe_record = + ReadNullableProto<ActivityRecordProto>(parcel); + + if (maybe_record) { + activity_record_proto = std::move(maybe_record.value()); + return ::android::NO_ERROR; + } else { + return maybe_record.error(); + } + } + + android::status_t writeIntent(android::Parcel* parcel) const { + return WriteProto<IntentProto>(parcel, intent_proto); + } + + android::status_t writeActivityRecordProto(android::Parcel* parcel) const { + return WriteProto<ActivityRecordProto>(parcel, activity_record_proto); + } + + android::status_t writeActivityRecordProtoNullable(android::Parcel* parcel) const { + return WriteNullableProto<ActivityRecordProto>(parcel, activity_record_proto); + } +}; + +inline std::ostream& operator<<(std::ostream& os, const AppLaunchEvent::Type& type) { + switch (type) { + case AppLaunchEvent::Type::kUninitialized: + os << "kUninitialized"; + break; + case AppLaunchEvent::Type::kIntentStarted: + os << "kIntentStarted"; + break; + case AppLaunchEvent::Type::kIntentFailed: + os << "kIntentFailed"; + break; + case AppLaunchEvent::Type::kActivityLaunched: + os << "kActivityLaunched"; + break; + case AppLaunchEvent::Type::kActivityLaunchCancelled: + os << "kActivityLaunchCancelled"; + break; + case AppLaunchEvent::Type::kActivityLaunchFinished: + os << "kActivityLaunchFinished"; + break; + default: + os << "(unknown)"; + } + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const AppLaunchEvent::Temperature& type) { + switch (type) { + case AppLaunchEvent::Temperature::kUninitialized: + os << "kUninitialized"; + break; + case AppLaunchEvent::Temperature::kCold: + os << "kCold"; + break; + case AppLaunchEvent::Temperature::kWarm: + os << "kWarm"; + break; + case AppLaunchEvent::Temperature::kHot: + os << "kHot"; + break; + default: + os << "(unknown)"; + } + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const AppLaunchEvent& e) { + os << "AppLaunchEvent{"; + os << "type=" << e.type << ","; + os << "sequence_id=" << e.sequence_id << ","; + + os << "intent_proto="; + if (e.intent_proto == nullptr) { + os << "(nullptr)"; + } else { + os << "(action=" << e.intent_proto->action() << ","; + os << "component="; + if (e.intent_proto->has_component()) { + // $package/$class_name + os << e.intent_proto->component().package_name() << "/" + << e.intent_proto->component().class_name(); + } else { + os << "(no component)"; + } + os << ")"; + } + os << ","; + + os << "temperature=" << e.temperature << ","; + os << ","; + + os << "activity_record_proto="; + if (e.activity_record_proto == nullptr) { + os << "(nullptr)"; + } else { + // title or component name. + os << "'" << e.activity_record_proto->identifier().title() << "'"; + } + os << "}"; + + return os; +} + +/* +IORAP_INTROSPECT_ADAPT_STRUCT(AppLaunchEvent, + type, + sequence_id, + intent_proto, + temperature, + activity_record_proto); +*/ + +} // namespace binder +} // namespace iorap + +IORAP_JAVA_NAMESPACE_BINDER_TYPEDEF(AppLaunchEvent) + +#endif // IORAP_BINDER_APP_LAUNCH_EVENT_H_ diff --git a/src/binder/iiorap_def.h b/src/binder/iiorap_def.h index b88f873..0c1eac8 100644 --- a/src/binder/iiorap_def.h +++ b/src/binder/iiorap_def.h @@ -27,6 +27,8 @@ FN_BEGIN(::com::google::android::startup::iorap::,IIorap) \ /* name <see IORAP_BINDER_PARAM_JOIN> */ \ FN(setTaskListener, (const ::android::sp<::com::google::android::startop::iorap::,ITaskListener,>&,listener)) /*NOLINT*/ \ +FN(onAppLaunchEvent,(const ::com::google::android::startop::iorap::,RequestId,&,request), \ + (const ::com::google::android::startop::iorap::,AppLaunchEvent,&,event)) \ FN(onPackageEvent, (const ::com::google::android::startop::iorap::,RequestId,&,request), \ (const ::com::google::android::startop::iorap::,PackageEvent,&,event)) \ FN(onAppIntentEvent,(const ::com::google::android::startop::iorap::,RequestId,&,request), \ diff --git a/src/binder/iiorap_impl.cc b/src/binder/iiorap_impl.cc index 52ef9fa..a6f6409 100644 --- a/src/binder/iiorap_impl.cc +++ b/src/binder/iiorap_impl.cc @@ -17,8 +17,10 @@ #include "binder/iiorap_impl.h" #include "binder/iiorap_def.h" #include "common/macros.h" +#include "manager/event_manager.h" #include <android-base/logging.h> +#include <android-base/properties.h> #include <binder/BinderService.h> #include <binder/IPCThreadState.h> #include <include/binder/request_id.h> @@ -57,6 +59,25 @@ IIORAP_IFACE_DEF(/*begin*/IORAP_PP_NOP, IIORAP_IMPL_BODY, /*end*/IORAP_PP_NOP); #undef IIORAP_IMPL_ARG_NAMES #undef IIORAP_IMPL_ARGS +namespace { + +struct ServiceParams { + bool fake_{false}; + std::shared_ptr<manager::EventManager> event_manager_; +}; + +static std::atomic<bool> s_service_started_{false}; +static std::atomic<bool> s_service_params_ready_{false}; + +// TODO: BinderService constructs IIorapImpl, +// but how do I get a pointer to it afterwards? +// +// This is a workaround for that, by using a global. +static ServiceParams s_service_params_; +static std::atomic<ServiceParams*> s_service_params_atomic_; + +} // namespace anonymous + class IIorapImpl::Impl { public: void SetTaskListener(const ::android::sp<ITaskListener>& listener) { @@ -90,28 +111,88 @@ class IIorapImpl::Impl { } } + bool OnAppLaunchEvent(const RequestId& request_id, + const AppLaunchEvent& event) { + if (MaybeHandleFakeBehavior(request_id)) { + return true; + } + + return service_params_.event_manager_->OnAppLaunchEvent(request_id, event); + } + + void HandleFakeBehavior(const RequestId& request_id) { + DCHECK(service_params_.fake_); + + // Send these dummy callbacks for testing only. + ReplyWithResult(request_id, TaskResult::State::kBegan); + ReplyWithResult(request_id, TaskResult::State::kOngoing); + ReplyWithResult(request_id, TaskResult::State::kCompleted); + } + + // TODO: Subclass IIorap with a separate fake implementation. + bool MaybeHandleFakeBehavior(const RequestId& request_id) { + if (service_params_.fake_) { + HandleFakeBehavior(request_id); + return true; + } + + return false; + } + ::android::sp<ITaskListener> listener_; + + Impl(ServiceParams p) : service_params_{std::move(p)} { + CHECK(service_params_.event_manager_ != nullptr); + } + + ServiceParams service_params_; }; using Impl = IIorapImpl::Impl; -IIorapImpl::IIorapImpl() : impl_(new Impl()) {} +IIorapImpl::IIorapImpl() { + // Acquire edge of synchronizes-with IIorapImpl::Start(). + CHECK(s_service_params_ready_.load()); + // Do not turn this into a DCHECK, the above atomic load + // must happen-before the read of s_service_params_ready_. + impl_.reset(new Impl(std::move(s_service_params_))); +} namespace { static bool started_ = false; } -bool IIorapImpl::Start() { - if (started_) { +bool IIorapImpl::Start(std::shared_ptr<manager::EventManager> event_manager) { + if (s_service_started_.load()) { // Acquire-edge (see bottom of function). + // Note: Not meant to be idempotent. Two threads could race, and the second + // one would likely fail the publish. + LOG(ERROR) << "service was already started"; return false; // Already started } + CHECK(event_manager != nullptr); + + { + // This block of code needs to happen-before IIorapImpl::IIorapImpl. + + // TODO: There should be a simpler way of passing down + // this data which doesn't involve globals and memory synchronization. + ServiceParams* p = &s_service_params_; + // TODO: move all property reads to a dedicated Config class. + p->fake_ = ::android::base::GetBoolProperty("iorapd.binder.fake", /*default*/false); + p->event_manager_ = std::move(event_manager); + + // Release edge of synchronizes-with IIorapImpl::IIorapImpl. + s_service_params_ready_.store(true); + } + ::android::IPCThreadState::self()->disableBackgroundScheduling(/*disable*/true); ::android::status_t ret = android::BinderService<IIorapImpl>::publish(); if (ret != android::OK) { LOG(ERROR) << "BinderService::publish failed with error code: " << ret; return false; } + android::sp<android::ProcessState> ps = android::ProcessState::self(); // Reduce thread consumption by only using 1 thread. // We should also be able to leverage this by avoiding locks, etc. @@ -119,45 +200,72 @@ bool IIorapImpl::Start() { ps->startThreadPool(); ps->giveThreadPoolName(); - started_ = true; + // Release edge synchronizes-with the top of this function. + s_service_started_.store(true); return true; } namespace { + +#define MAYBE_HAVE_FAKE_BEHAVIOR(self, request_id) \ + if (self->MaybeHandleFakeBehavior(request_id)) { return ::android::binder::Status::ok(); } + template <typename ... Args> -void SendArgs(const char* function_name, - Impl* self, - const RequestId& request_id, - Args&&... /*rest*/) { - // TODO: verbose, not INFO +Status SendArgs(const char* function_name, + Impl* self, + const RequestId& request_id, + Args&&... /*rest*/) { LOG(VERBOSE) << "IIorap::" << function_name << " (request_id = " << request_id.request_id << ")"; - // TODO: implementation. - // Send these dummy callbacks for testing only. - // TODO: these should only be sent back when the client connects in a special 'test' mode. - self->ReplyWithResult(request_id, TaskResult::State::kBegan); - self->ReplyWithResult(request_id, TaskResult::State::kOngoing); - self->ReplyWithResult(request_id, TaskResult::State::kCompleted); + MAYBE_HAVE_FAKE_BEHAVIOR(self, request_id); + + // TODO: implementation. + LOG(ERROR) << "IIorap::" << function_name << " -- not implemented for real code"; + return Status::fromStatusT(::android::INVALID_OPERATION); } template <typename ... Args> -void SendArgs(const char* /*function_name*/, Impl* self, Args&&... rest) { - // TODO: may want an assert here for readability. +Status SendArgs(const char* function_name, Impl* self, Args&&... rest) { + DCHECK_EQ(std::string(function_name), "setTaskListener"); LOG(VERBOSE) << "IIorap::setTaskListener"; self->SetTaskListener(std::forward<Args&&>(rest)...); + + return Status::ok(); } template <typename ... Args> -Status Send(const char* function_name, Args&&... args) { - LOG(VERBOSE) << "IIorap::Send(" << function_name << ")"; +Status SendArgs(const char* function_name, + Impl* self, + const RequestId& request_id, + const AppLaunchEvent& app_launch_event) { + DCHECK_EQ(std::string(function_name), "onAppLaunchEvent"); + LOG(VERBOSE) << "IIorap::onAppLaunchEvent"; - SendArgs(function_name, std::forward<Args>(args)...); + MAYBE_HAVE_FAKE_BEHAVIOR(self, request_id); - // Note: The exact return code doesn't matter: all the AIDL methods are oneway. - return Status::ok(); -} + if (self->OnAppLaunchEvent(request_id, app_launch_event)) { + return Status::ok(); + } else { + // TODO: I suppose this should write out an exception back, + // like a service-specific error or something. + // + // It depends on whether or not we even have any synchronous + // errors. + // + // Most of the work here is done async, so it should handle + // async callbacks. + return Status::fromStatusT(::android::BAD_VALUE); + } } +template <typename ... Args> +Status Send(const char* function_name, Args&&... args) { + LOG(VERBOSE) << "IIorap::Send(" << function_name << ")"; + + return SendArgs(function_name, std::forward<Args>(args)...); } -} +} // namespace <anonymous> + +} // namespace binder +} // namespace iorap diff --git a/src/binder/iiorap_impl.h b/src/binder/iiorap_impl.h index 753967d..811497f 100644 --- a/src/binder/iiorap_impl.h +++ b/src/binder/iiorap_impl.h @@ -22,11 +22,17 @@ #include "com/google/android/startop/iorap/BnIorap.h" +#include <memory> + namespace android { template <typename Service> class BinderService; } +namespace iorap::manager { + class EventManager; +}; + namespace iorap { namespace binder { @@ -36,7 +42,7 @@ namespace binder { // See also IIorap.aidl. class IIorapImpl : public ::com::google::android::startop::iorap::BnIorap { public: - static bool Start(); + static bool Start(std::shared_ptr<iorap::manager::EventManager> event_manager); static constexpr const char* getServiceName() { return "iorapd"; }; // Join all parameter declarations by splitting each parameter with a comma. diff --git a/src/common/debug.h b/src/common/debug.h new file mode 100644 index 0000000..bc21c74 --- /dev/null +++ b/src/common/debug.h @@ -0,0 +1,98 @@ +// 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 <ostream> + +namespace iorap { + +// kIsDebugBuild is special. +// It gets to be in the 'iorap' namespace +// so that different modules don't need to qualify it. +#ifndef NDEBUG +static constexpr bool kIsDebugBuild = true; +#else +static constexpr bool kIsDebugBuild = false; +#endif + +namespace common { + +// TODO: move below code to helpers. +template <typename T, bool> +struct base_if_condition {}; + +template <typename T> +struct base_if_condition<T, true> : public T {}; + +template <typename T> +using base_if_debug = base_if_condition<T, kIsDebugBuild>; + +namespace detail { +// "if constexpr" doesn't allow us to exclude fields from a struct/class, +// and also "if constexpr" doesn't allow us to reference a field that does not +// exist. +// so we must move everything into a separate base class. +template <bool kIsDebug = kIsDebugBuild> +struct DebugCounterBase { + constexpr size_t value() const { + return counter; + } + + constexpr void set_value(size_t value) { + counter = value; + } + + size_t counter{1}; // Don't start with 0. +}; + +template <> +struct DebugCounterBase<false /*kIsDebug*/> { + constexpr size_t value() const { + return 0; + } + + constexpr void set_value(size_t value) { + } +}; +} // namespace detail + +// This counter does absolutely nothing, the code compiles to no-ops +// when debugging is disabled. +struct DebugCounter : detail::DebugCounterBase<> { + constexpr DebugCounter& operator++() { + set_value(value() + 1); + return *this; + } + + constexpr DebugCounter operator++(int) { + DebugCounter now = *this; + set_value(value() + 1); + return now; + } + + constexpr operator size_t() const { + return value(); + } + + friend std::ostream& operator<<(std::ostream& os, DebugCounter counter); +}; + +inline std::ostream& operator<<(std::ostream& os, DebugCounter counter) { + os << counter.value(); + return os; +} + +// TODO: refactor DebugCounter and base traits into their own files? + +} // namespace common +} // namespace iorap diff --git a/src/common/expected.h b/src/common/expected.h new file mode 100644 index 0000000..3d8eef7 --- /dev/null +++ b/src/common/expected.h @@ -0,0 +1,404 @@ +// 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 IORAP_SRC_COMMON_EXPECTED_H_ +#define IORAP_SRC_COMMON_EXPECTED_H_ + +#include <type_traits> +#include <utility> + +#include <android-base/logging.h> // CHECK/DCHECK. + +// Ignore the tautological-undefined-compare warning. +// We obviously want to do this to protect against undefined behavior +// that sets a reference to a null value. +#define DCHECK_UB_NOT_NULL(x) \ + DCHECK(reinterpret_cast<volatile decltype(x)>(x) != nullptr) + +/** + * Result<Value, Error>-like interface. + * + * Subset of the experimental standard C++ proposal (p0323r3) + * + * Example: + * + * expected<std::string, status_t> x = function_which_might_fail(); + * if (x) { + * std::string str = x.value(); + * } else { + * status_t err = x.error(); + * } + */ + +namespace iorap { +namespace detail { + // Use perfect forwarding for expected_data constructors with overloading. + struct expected_tag{}; + struct expected_tag_right : public expected_tag { + static constexpr bool is_right_v = true; + }; + struct expected_tag_error : public expected_tag { + static constexpr bool is_right_v = false; + }; + + template <typename T, typename E, bool DefineDestructor> + struct expected_data; + + // This doesn't always work because this code could be instantiated with a non-trivial T/E, + // and then the union becomes invalid. + template <typename T, typename E> + struct expected_data<T, E, /*DefineDestructor*/true> { + // Mark everything 'constexpr' to keep the code the same as the other partial specialization. + + template <typename U> + constexpr expected_data(U&& either, expected_tag_right) + : right_{std::forward<U>(either)}, is_right_{true} {} + + template <typename U> + constexpr expected_data(U&& either, expected_tag_error) + : error_{std::forward<U>(either)}, is_right_{false} {} + + constexpr bool has_value() const { + return is_right_; + } + + constexpr const T& value() const { + return right_; + } + + constexpr T& value() { + return right_; + } + + constexpr const E& error() const { + return error_; + } + + constexpr E& error() { + return error_; + } + + // Using an "anonymous union" here allows non-trivial types to be stored. + union { + T right_; + E error_; + }; + + bool is_right_; + + // Below code differs slightly by handling non-trivial constructors/destructors. + bool moved_from_{false}; + + // Note: Destructors cannot be templated, so it is illegal to use SFINAE to try to + // conditionalize this destructor somehow. + ~expected_data() { + if (moved_from_) { return; } + if (is_right_) { + right_.~T(); + } else { + error_.~E(); + } + } + + expected_data(expected_data&& other) + noexcept( + noexcept(T(std::move(other.right_))) && + noexcept(E(std::move(other.error_))) + ) { + DCHECK_UB_NOT_NULL(&other) << __PRETTY_FUNCTION__; + DCHECK_EQ(other.moved_from_, false) << __PRETTY_FUNCTION__; + if (other.is_right_) { + new (&right_) T(std::move(other.right_)); + } else { + new (&error_) E(std::move(other.error_)); + } + other.moved_from_ = true; + is_right_ = other.is_right_; + } + + expected_data(const expected_data& other) { + DCHECK_UB_NOT_NULL(&other) << __PRETTY_FUNCTION__; + DCHECK_EQ(other.moved_from_, false) << __PRETTY_FUNCTION__; + if (other.is_right_) { + new (&right_) T(other.right_); + } else { + new (&error_) E(other.error_); + } + is_right_ = other.is_right_; + } + + expected_data& operator=(const expected_data& other) { + DCHECK_UB_NOT_NULL(&other) << __PRETTY_FUNCTION__; + DCHECK_EQ(other.moved_from_, false) << __PRETTY_FUNCTION__; + + if (this == &other) { + return *this; + } + + if (other.is_right_) { + if (!is_right_) { + error_.~E(); + new (&right_) T(other.right_); + } else { + right_ = other.right_; + } + } else { + if (is_right_) { + right_.~T(); + new (&error_) E(other.error_); + } else { + error_ = other.error_; + } + } + is_right_ = other.is_right_; + + return *this; + } + + expected_data& operator=(expected_data&& other) { + DCHECK_UB_NOT_NULL(&other) << __PRETTY_FUNCTION__; + DCHECK_EQ(other.moved_from_, false) << __PRETTY_FUNCTION__; + + if (this == &other) { + return *this; + } + + if (other.is_right_) { + if (!is_right_) { + error_.~E(); + new (&right_) T(std::move(other.right_)); + } else { + right_ = std::move(other.right_); + } + } else { + if (is_right_) { + right_.~T(); + new (&error_) E(std::move(other.error_)); + } else { + error_ = std::move(other.error_); + } + } + + other.moved_from_ = true; + is_right_ = other.is_right_; + + return *this; + } + }; + + // Trivial-destructor copy of the above struct. + // + // A separate copy is required because otherwise compilation fails with an error about + // the union having an implicitly deleted constructor. + // + // Having this implementation gives us the property that + // + // (is_trivially_destructible<T> && is_trivially_destructible<E> + // ==> is_trivially_destructible<expected<T, E>>) + template <typename T, typename E> + struct expected_data<T, E, /*DefineDestructor*/false> { + template <typename U> + constexpr expected_data(U&& either, expected_tag_right) + : right_{std::forward<U>(either)}, is_right_{true} {} + + template <typename U> + constexpr expected_data(U&& either, expected_tag_error) + : error_{std::forward<U>(either)}, is_right_{false} {} + + constexpr bool has_value() const { + return is_right_; + } + + constexpr const T& value() const { + return right_; + } + + constexpr T& value() { + return right_; + } + + constexpr const E& error() const { + return error_; + } + + constexpr E& error() { + return error_; + } + + // Using an "anonymous union" here allows non-trivial types to be stored. + union { + T right_; + E error_; + }; + + bool is_right_; + + ~expected_data() = default; + }; + + // Select between trivial and non-trivial implementations. Trivial implementations + // are more optimized and constexpr-compatible. + template <typename T, typename E> + using expected_pick_data_t = + expected_data<T, E, + !(std::is_trivially_destructible_v<T> && std::is_trivially_destructible_v<E>) >; +} // namespace detail + +template <typename E> +struct unexpected; + +// Subset of std::experimental::expected proposal (p0323r3). +template <typename T, typename E> +struct expected { + // Never-empty: expected<T,E> values have either 'T' or 'E' in them. + template <typename U = T, typename _ = std::enable_if_t<std::is_default_constructible_v<U>>> + constexpr expected() noexcept(noexcept(T{})) : expected(T{}) {} + + constexpr expected(const T& value) : data_{value, detail::expected_tag_right{}} {} + constexpr expected(T&& value) : data_{std::move(value), detail::expected_tag_right{}} {} + constexpr expected(const E& error) : data_{error, detail::expected_tag_error{}} {} + constexpr expected(E&& error) : data_{std::move(error), detail::expected_tag_error{}} {} + + template <typename G = E> + constexpr expected(unexpected<G> const& u) : expected{u.value()} {} + + template <typename G = E> + constexpr expected(unexpected<G>&& u) : expected{std::move(u.value())} {} + + explicit constexpr operator bool() const { + return has_value(); + } + + constexpr bool has_value() const { + return data_.has_value(); + } + + constexpr const T& operator*() const { + return data_.value(); + } + + constexpr T& operator*() { + return data_.value(); + } + + // TODO: arrow operator? + + constexpr T& value() & { + CHECK(has_value()); + return data_.value(); + } + + constexpr const T& value() const & { + CHECK(has_value()); + return data_.value(); + } + + constexpr T&& value() && { + CHECK(has_value()); + return std::move(data_.value()); + } + + constexpr const T& value() const && { + CHECK(has_value()); + return std::move(data_.value()); + } + + constexpr E& error() { + DCHECK(!has_value()); + return data_.error(); + } + + constexpr const E& error() const { + DCHECK(!has_value()); + return data_.error(); + } + + // TODO: other functions such as operator=, unexpected, etc. + private: + detail::expected_pick_data_t<T, E> data_; +}; + +// TODO: move to tests file +namespace { + struct TestType { + TestType() {} + ~TestType() {} + }; + struct TestType2 : TestType {}; + + static_assert(std::is_trivially_destructible_v<expected<int, /*error*/double> >); + static_assert(!std::is_trivially_destructible_v<expected<TestType, /*error*/double> >); + static_assert(!std::is_trivially_destructible_v<expected<int, /*error*/TestType> >); + static_assert(!std::is_trivially_destructible_v<expected<TestType, /*error*/TestType2> >); + + // Ensure expected is constexpr-compatible. + struct TestCase { + static constexpr auto t1 = expected<int, double>{}; + }; +} // namespace <anonymous> + +template <typename E> +struct unexpected { + unexpected() = delete; + constexpr explicit unexpected(const E& error) : error_{error} {} + constexpr explicit unexpected(E&& error) : error_{std::move(error)} {} + constexpr const E& value() const& { return error_; } + constexpr E& value() & { return error_; } + constexpr E&& value() && { return std::move(error_); } + constexpr E const&& value() const&& { return std::move(error_); } + private: + E error_; +}; + +template <class E> +constexpr bool operator==(const unexpected<E>& x, const unexpected<E>& y) { + return x.value() == y.value(); +} + +template <class E> +constexpr bool operator!=(const unexpected<E>& x, const unexpected<E>& y) { + return !(x == y); +} + +// TODO: move below codes to separate utils file +// +// future C++20 implementation of std::identity +struct identity { + template <typename U> + constexpr auto operator()(U&& v) const noexcept { + return std::forward<U>(v); + } +}; + +// Given a lambda [...](auto&& var) {...} +// apply std::forward to 'var' to achieve perfect forwarding. +// +// Note that this doesn't work when var is a template type, i.e. +// template <typename T> +// void func(T&& tvar) {...} +// +// It would be invalid to use this macro with 'tvar' in that context. +#define IORAP_FORWARD_LAMBDA(var) std::forward<decltype(var)>(var) + +// Borrowed non-null pointer, i.e. we do not own the lifetime. +// +// Function calls: This pointer is not used past the call. +// Struct fields: This pointer is not used past the lifetime of the struct. +template <class T, class = std::enable_if_t<std::is_pointer<T>::value>> +using borrowed = T _Nonnull; +// TODO: need a DCHECK or high warning levels, since null is technically well-defined. + +} // namespace iorap + +#endif // IORAP_SRC_COMMON_EXPECTED_H_ diff --git a/src/inode2filename/inode.cc b/src/inode2filename/inode.cc new file mode 100644 index 0000000..cdb2325 --- /dev/null +++ b/src/inode2filename/inode.cc @@ -0,0 +1,81 @@ +// 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 "inode2filename/inode.h" + +#include <android-base/logging.h> +#include <android-base/parseint.h> +#include <android-base/strings.h> + +#include <string> +#include <vector> + +#include <sys/sysmacros.h> + +using android::base::ParseUint; + +namespace iorap::inode2filename { + +// TODO: refactor to return expected<Inode, string> +bool Inode::Parse(const std::string& str, Inode* out, std::string* error_msg) { + DCHECK(out != nullptr); + DCHECK(error_msg != nullptr); + + // Major:minor:inode OR dev_t@inode + std::vector<std::string> lst_pair = android::base::Split(str, "@"); + if (lst_pair.size() == 2) { + size_t dev_whole = 0; + if (!ParseUint(lst_pair[0], &dev_whole)) { + *error_msg = "Failed to parse the whole device id as uint."; + return false; + } + + dev_t dev_w = static_cast<dev_t>(dev_whole); + out->device_major = major(dev_w); + out->device_minor = minor(dev_w); + + if (!ParseUint(lst_pair[1], &out->inode)) { + *error_msg = "Failed to parse inode as uint."; + return false; + } + + return true; + } + + std::vector<std::string> lst = android::base::Split(str, ":"); + + if (lst.size() != 3) { + *error_msg = "Too few : separated items"; + return false; + } + + if (!ParseUint(lst[0], &out->device_major)) { + *error_msg = "Failed to parse 0th element as a uint"; + return false; + } + + if (!ParseUint(lst[1], &out->device_minor)) { + *error_msg = "Failed to parse 1st element as a uint"; + return false; + } + + if (!ParseUint(lst[2], &out->inode)) { + *error_msg = "Failed to parse 2nd element as a uint"; + return false; + } + + return true; +} + +} // namespace iorap::inode2filename diff --git a/src/inode2filename/inode.h b/src/inode2filename/inode.h new file mode 100644 index 0000000..2dd5611 --- /dev/null +++ b/src/inode2filename/inode.h @@ -0,0 +1,65 @@ +// 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 IORAP_SRC_INODE2FILENAME_INODE_H_ +#define IORAP_SRC_INODE2FILENAME_INODE_H_ + +#include <functional> +#include <ostream> +#include <string> + +#include <stddef.h> + +namespace iorap::inode2filename { + +struct Inode { + size_t device_major; // dev_t = makedev(major, minor) + size_t device_minor; + size_t inode; // ino_t = inode + + static bool Parse(const std::string& str, /*out*/Inode* out, /*out*/std::string* error_msg); + + bool operator==(const Inode& rhs) const { + return device_major == rhs.device_major && + device_minor == rhs.device_minor && + inode == rhs.inode; + } + + bool operator!=(const Inode& rhs) const { + return !(*this == rhs); + } +}; + +inline std::ostream& operator<<(std::ostream& os, const Inode& inode) { + os << inode.device_major << ":" << inode.device_minor << ":" << inode.inode; + return os; +} + +} // namespace iorap::inode2filename + +namespace std { + template <> + struct hash<iorap::inode2filename::Inode> { + using argument_type = iorap::inode2filename::Inode; + using result_type = size_t; + result_type operator()(argument_type const& s) const noexcept { + // Hash the inode by using only the inode#. Ignore devices, we are extremely unlikely + // to ever collide on the devices. + result_type const h1 = std::hash<size_t>{}(s.inode); + return h1; + } + }; +} // namespace std + +#endif // IORAP_SRC_INODE2FILENAME_INODE_H_ diff --git a/src/inode2filename/main.cc b/src/inode2filename/main.cc new file mode 100644 index 0000000..2da364c --- /dev/null +++ b/src/inode2filename/main.cc @@ -0,0 +1,152 @@ +// 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/debug.h" +#include "common/expected.h" +#include "inode2filename/search_directories.h" + +using namespace iorap::inode2filename; // NOLINT + +#if defined(IORAP_INODE2FILENAME_MAIN) + +void Usage(char** argv) { + std::cerr << "Usage: " << argv[0] << " <options> <<inode_syntax>> [inode_syntax1 inode_syntax2 ...]" << std::endl; + std::cerr << "" << std::endl; + std::cerr << " Block until all inodes have been read in, then begin searching for filenames for those inodes." << std::endl; + std::cerr << " Results are written immediately as they are available, and once all inodes are found, " << std::endl; + std::cerr << " the program will terminate." << std::endl; + std::cerr << "" << std::endl; + std::cerr << " Inode syntax: ('dev_t@inode' | 'major:minor:inode')" << std::endl; + std::cerr << " --help,-h Print this Usage." << std::endl; + std::cerr << " --root,-r Add root directory (default '.'). Repeatable." << std::endl; + std::cerr << " --verbose,-v Set verbosity (default off)." << std::endl; + std::cerr << " --wait,-w Wait for key stroke before continuing (default off)." << std::endl; + exit(1); +} + +static fruit::Component<SearchDirectories> GetSearchDirectoriesComponent() { + return fruit::createComponent().bind<SystemCall, SystemCallImpl>(); +} + +int main(int argc, char** argv) { + android::base::InitLogging(argv); + android::base::SetLogger(android::base::StderrLogger); + + bool wait_for_keystroke = false; + bool enable_verbose = false; + std::vector<std::string> root_directories; + std::vector<Inode> inode_list; + + if (argc == 1) { + Usage(argv); + } + + for (int arg = 1; arg < argc; ++arg) { + std::string argstr = argv[arg]; + bool has_arg_next = (arg+1)<argc; + std::string arg_next = has_arg_next ? argv[arg+1] : ""; + + if (argstr == "--help" || argstr == "-h") { + Usage(argv); + } else if (argstr == "--root" || argstr == "-r") { + if (!has_arg_next) { + std::cerr << "Missing --root <value>" << std::endl; + return 1; + } + root_directories.push_back(arg_next); + ++arg; + } else if (argstr == "--verbose" || argstr == "-v") { + enable_verbose = true; + } else if (argstr == "--wait" || argstr == "-w") { + wait_for_keystroke = true; + } else { + Inode maybe_inode{}; + + std::string error_msg; + if (Inode::Parse(argstr, /*out*/&maybe_inode, /*out*/&error_msg)) { + inode_list.push_back(maybe_inode); + } else { + if (argstr.size() >= 1) { + if (argstr[0] == '-') { + std::cerr << "Unrecognized flag: " << argstr << std::endl; + return 1; + } + } + + std::cerr << "Failed to parse inode (" << argstr << ") because: " << error_msg << std::endl; + return 1; + } + } + } + + if (root_directories.size() == 0) { + root_directories.push_back("."); + } + + if (inode_list.size() == 0) { + DCHECK_EQ(true, false); + std::cerr << "Provide at least one inode." << std::endl; + return 1; + } + + if (enable_verbose) { + android::base::SetMinimumLogSeverity(android::base::VERBOSE); + + LOG(VERBOSE) << "Verbose check"; + LOG(VERBOSE) << "Debug check: " << ::iorap::kIsDebugBuild; + + for (auto& inode_num : inode_list) { + LOG(VERBOSE) << "Searching for inode " << inode_num; + } + } + + // Useful to attach a debugger... + // 1) $> inode2filename -w <args> + // 2) $> gdbclient <pid> + if (wait_for_keystroke) { + LOG(INFO) << "Self pid: " << getpid(); + LOG(INFO) << "Press any key to continue..."; + std::cin >> wait_for_keystroke; + } + + fruit::Injector<SearchDirectories> injector(GetSearchDirectoriesComponent); + SearchDirectories* search_directories = injector.get<SearchDirectories*>(); + + auto/*observable[2]*/ [inode_results, connectable] = + search_directories->FindFilenamesFromInodesPair( + std::move(root_directories), + std::move(inode_list), + SearchMode::kInProcessDirect); + + int return_code = 1; + inode_results.subscribe([&return_code](const InodeResult& result) { + if (result) { + LOG(DEBUG) << "Inode match: " << result.inode << ", " << result.data.value(); + std::cout << "Inode match: " << result.inode << ", " << result.data.value() << std::endl; + return_code = 0; + } else { + LOG(WARNING) << "Failed to match inode: " << result.inode; + } + }); + + // Normally #subscribe would start emitting items immediately, but this does nothing yet + // because one of the nodes in the flow graph was published. Published streams make the entire + // downstream inert until #connect is called. + connectable->connect(); + + // 0 -> found at least a single match, 1 -> could not find any matches. + return return_code; +} + +#endif diff --git a/src/inode2filename/search_directories.cc b/src/inode2filename/search_directories.cc new file mode 100644 index 0000000..2ec09d9 --- /dev/null +++ b/src/inode2filename/search_directories.cc @@ -0,0 +1,915 @@ +// 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/debug.h" +#include "inode2filename/search_directories.h" +#include "inode2filename/system_call.h" + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/scopeguard.h> +#include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> + +#include "rxcpp/rx.hpp" + +#include <iostream> +#include <stdio.h> +#include <fstream> +#include <vector> +#include <optional> + +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sys/types.h> + +#ifdef __ANDROID__ +#include <sys/sysmacros.h> +#endif + +#include <sys/stat.h> +#include <fcntl.h> +#include <poll.h> +#include <dirent.h> + +#include <unordered_map> + +namespace rx = rxcpp; +using android::base::unique_fd; // NOLINT +using android::base::StringPrintf; // NOLINT + +namespace iorap::inode2filename { + +// A multimap of 'ino_t -> List[Inode]' (where the value Inodes have the same ino_t as the key). +// +// A flat list of Inodes is turned into the above map, then keys can be removed one at a time +// until the InodeSet eventually becomes empty. +struct InodeSet { + struct ValueRange { + auto/*Iterable<Inode>*/ begin() { + return begin_; + } + + auto/*Iterable<Inode>*/ end() { + return end_; + } + + bool empty() const { + return begin_ == end_; + } + + explicit operator bool() const { + return !empty(); + } + + std::unordered_multimap<ino_t, Inode>::iterator begin_, end_; + + friend std::ostream& operator<<(std::ostream& os, const ValueRange& s); + }; + + // Create an observable that emits the remaining inodes in the map. + // + // Mutation functions must not be called until this observable + // has been finished emitting all values (e.g. with on_completed) since that + // would cause the underlying iterators to go into an undefined state. + auto/*observable<Inode>*/ IterateValues() const { + return rxcpp::observable<>::iterate(set_).map( // XX: should we use identity_immediate here? + [](const std::pair<const ino_t, Inode>& pair) { + return pair.second; + } + ); + // TODO: this would be more efficient as a range-v3 view. + } + + constexpr bool Empty() const { + return set_.empty(); + } + + static InodeSet OfList(const std::vector<Inode>& list) { + InodeSet new_inode_set; + std::unordered_multimap<ino_t, Inode>* map = &new_inode_set.set_; + + for (const Inode& inode : list) { + map->insert({inode.inode, inode}); + } + + return new_inode_set; + } + + // Return an optional list of 'Inode' structs whose 'inode' field matches the 'inode' parameter. + // Returns an empty range if there was nothing found. + ValueRange FindInodeList(ino_t inode) { + auto range = set_.equal_range(inode); + return ValueRange{range.first, range.second}; + } + + // Match all fields of an Inode against a 'struct stat' stat_buf. + // + // The returned Inode (if any) is removed from the InodeSet; it will not be returned by + // FindInodeList in future calls. + std::optional<Inode> FindAndRemoveInodeInList(ValueRange inode_list, + const struct stat& stat_buf) { + LOG(VERBOSE) << "FindAndRemoveInodeInList " << inode_list << ", " + << "stat_buf{st_dev=" << stat_buf.st_dev << ",st_ino=" << stat_buf.st_ino << "}"; + + auto /*iterator*/ found = std::find_if(inode_list.begin(), + inode_list.end(), + [&](const std::pair<ino_t, Inode>& pair) { + const Inode& inode = pair.second; + if (inode.inode != stat_buf.st_ino) { + return false; + } + + dev_t inode_dev = + makedev(static_cast<int>(inode.device_major), static_cast<int>(inode.device_minor)); + + // Inodes could be the same across different devices. + // Also match the device id. + if (inode_dev != stat_buf.st_dev) { + LOG(VERBOSE) << "InodeSet:FindAndRemoveInodeInList matched ino: " << inode.inode + << " but not device" + << ", expected dev: " << stat_buf.st_dev + << ", actual dev: " << inode_dev; + return false; + } + return true; + }); + + if (found != inode_list.end()) { + const Inode& inode = found->second; + LOG(VERBOSE) << "InodeSet:FindAndRemoveInodeInList *success* inode+device " << inode; + DCHECK(found->second.inode == stat_buf.st_ino); + // Erase the inode from the list. This is important. + set_.erase(found); + return inode; + } + + return std::nullopt; + } + + // TODO: equality and string operators for testing/logging. + private: + // Explanation: readdir returns a 'file' -> 'ino_t inode' mapping. + // + // However inodes can be reused on different partitions (but they have a different device number). + // To handle this edge case, and to avoid calling stat whenever the inode definitely doesn't match + // store the inodes into a single-key,multi-value container. + // + // This enables fast scanning of readdir results by matching just the 'inode' portion, + // then calling stat only when the inode portion definitely matches to confirm the device. + + // There are no single-key multi-value containers in standard C++, so pretend + // we have one by writing this simple facade around an unordered set. + // + // We expect that the vector size is usually size=1 (or 2 or 3) since the # of devices + // is fixed by however many partitions there are on the system, AND the same inode # + // would have to be reused across a different file. + std::unordered_multimap<ino_t, Inode> set_; // TODO: Rename to map_. + + friend std::ostream& operator<<(std::ostream& os, const InodeSet& s); +}; + +std::ostream& operator<<(std::ostream& os, const InodeSet& s) { + os << "InodeSet{"; + for (const auto& kv : s.set_) { + // e.g. "123=>(1:2:123)" ... its expected for the 'ino_t' portion to be repeated. + os << "" << kv.first << "=>(" << kv.second << "),"; + } + os << "}"; + return os; +} + +std::ostream& operator<<(std::ostream& os, const InodeSet::ValueRange& v) { + // Don't want to make a const and non const version of ValueRange. + InodeSet::ValueRange& s = const_cast<InodeSet::ValueRange&>(v); + + os << "InodeSet::ValueRange{"; + for (const auto& kv : s) { + // e.g. "123=>(1:2:123)" ... its expected for the 'ino_t' portion to be repeated. + os << "" << kv.first << "=>(" << kv.second << "),"; + } + os << "}"; + return os; +} + +void search_for_inodes_in(std::vector<Inode>& inode_list, const std::string& dirpath); + +enum DirectoryEntryErrorCode { + kInvalid, // not a real error code. to detect bad initialization. + kOpenDir, // opendir failed. + kReadDir, // readdir failed. + kDtUnknown, // d_type was DT_UNKNOWN error. +}; + +struct DirectoryEntryError { + DirectoryEntryErrorCode code; + int err_no; + std::string filename; +}; + +std::ostream& operator<<(std::ostream& os, const DirectoryEntryError& e) { + os << "DirectoryEntryError{" + << static_cast<int>(e.code) << "," << e.err_no << "," << e.filename << "}"; + return os; + // TODO: pretty-print code and err-no +} + +static common::DebugCounter gDebugDirectoryEntryCounter{}; +static constexpr bool kDebugDirectoryEntry = false; + +#define DIRECTORY_ENTRY_MOVE_DCHECK() \ + DCHECK_EQ(other.moved_from_, false) << __PRETTY_FUNCTION__ << "CNT:" << other.debug_counter_; +#define DIRECTORY_ENTRY_TRACE_CTOR() \ + if (kDebugDirectoryEntry) LOG(VERBOSE) << __PRETTY_FUNCTION__ << "@CNT:" << debug_counter_ + +struct DirectoryEntry { + using ResultT = iorap::expected<DirectoryEntry, DirectoryEntryError>; + using ObservableT = rx::observable<ResultT>; + + static constexpr ino_t kInvalidIno = std::numeric_limits<ino_t>::max(); + static constexpr auto kInvalidFileName = ""; + + // Path to file, the prefix is one of the root directories. + std::string filename{kInvalidFileName}; + // Inode number of the file. Not unique across different devices. + ino_t d_ino{kInvalidIno}; + // File type (DT_LNK, DT_REG, DT_DIR, or DT_UNKNOWN) + unsigned char d_type{DT_UNKNOWN}; // Note: not seen outside of sentinel roots. + // TODO: Consider invariant checks for valid combinations of above fields? + + // Debug-only flags. + bool moved_from_{false}; + size_t debug_counter_{0}; + + private: + // TODO: remove default constructor? + // + // SEEMS TO BE USED by std::vector etc. FIX DAT. + DirectoryEntry() noexcept { + debug_counter_ = gDebugDirectoryEntryCounter++; + DIRECTORY_ENTRY_TRACE_CTOR(); + } + public: + DirectoryEntry(std::string filename, ino_t d_ino, unsigned char d_type) noexcept + : filename{std::move(filename)}, + d_ino{d_ino}, + d_type{d_type} { + debug_counter_ = gDebugDirectoryEntryCounter++; + DIRECTORY_ENTRY_TRACE_CTOR(); + } + + DirectoryEntry(const DirectoryEntry& other) noexcept { + // Do not use member-initialization syntax so that this DCHECK can execute first. + DIRECTORY_ENTRY_MOVE_DCHECK(); + + filename = other.filename; + d_ino = other.d_ino; + d_type = other.d_type; + children_paths_ = other.children_paths_; + children_initialized_ = other.children_initialized_; + debug_counter_ = other.debug_counter_; + DIRECTORY_ENTRY_TRACE_CTOR(); + } + + DirectoryEntry& operator=(const DirectoryEntry& other) noexcept { + if (this == &other) { + return *this; + } + + DIRECTORY_ENTRY_MOVE_DCHECK(); + + filename = other.filename; + d_ino = other.d_ino; + d_type = other.d_type; + children_paths_ = other.children_paths_; + children_initialized_ = other.children_initialized_; + debug_counter_ = other.debug_counter_; + DIRECTORY_ENTRY_TRACE_CTOR(); + + return *this; + } + + DirectoryEntry& operator=(DirectoryEntry&& other) noexcept { + if (this == &other) { + return *this; + } + + DIRECTORY_ENTRY_MOVE_DCHECK(); + + filename = std::move(other.filename); + d_ino = other.d_ino; + d_type = other.d_type; + children_paths_ = std::move(other.children_paths_); + children_initialized_ = other.children_initialized_; + debug_counter_ = other.debug_counter_; + DIRECTORY_ENTRY_TRACE_CTOR(); + + return *this; + } + + DirectoryEntry(DirectoryEntry&& other) noexcept { + DIRECTORY_ENTRY_MOVE_DCHECK(); + other.moved_from_ = true; + + filename = std::move(other.filename); + d_ino = other.d_ino; + d_type = other.d_type; + children_paths_ = std::move(other.children_paths_); + children_initialized_ = other.children_initialized_; + debug_counter_ = other.debug_counter_; + DIRECTORY_ENTRY_TRACE_CTOR(); + } + + // Create a sentinel (root of roots) whose children entries are those specified by + // children_paths. + static DirectoryEntry CreateSentinel(std::vector<std::string> children_paths) { + DirectoryEntry e; + e.d_type = DT_DIR; + ++gDebugDirectoryEntryCounter; + + for (std::string& child_path : children_paths) { + // TODO: Should we call Stat on the child path here to reconstitute the ino_t for a root dir? + // Otherwise it can look a little strange (i.e. the root dir itself will never match + // the searched inode). + // + // Probably not too big of a problem in practice. + DirectoryEntry child_entry{std::move(child_path), kInvalidIno, DT_DIR}; + ResultT child_entry_as_result{std::move(child_entry)}; + e.children_paths_.push_back(std::move(child_entry_as_result)); + } + + e.children_initialized_ = true; + + return e; + } + + // Return an observable which emits the direct children only. + // The children entries are now read from disk (with readdir) if they weren't read previously. + std::vector<ResultT> GetChildrenEntries(borrowed<SystemCall*> system_call) const& { + BuildChildrenPaths(system_call); + return children_paths_; + } + + // Return an observable which emits the direct children only. + // The children entries are now read from disk (with readdir) if they weren't read previously. + // Movable overload. + std::vector<ResultT> GetChildrenEntries(borrowed<SystemCall*> system_call) && { + BuildChildrenPaths(system_call); + return std::move(children_paths_); + } + + // Returns a (lazy) observable that emits every single node, in pre-order, + // rooted at this tree. + // + // New entries are only read from disk (with e.g. readdir) when more values are pulled + // from the observable. Only the direct children of any entry are read at any time. + // + // The emission can be stopped prematurely by unsubscribing from the observable. + // This means the maximum amount of 'redundant' IO reads is bounded by the children count + // of all entries emitted thus far minus entries actually emitted. + ObservableT GetSubTreePreOrderEntries(borrowed<SystemCall*> system_call) const; + + private: + // Out-of-line definition to avoid circular type dependency. + void BuildChildrenPaths(borrowed<SystemCall*> system_call) const; + + // We need to lazily initialize children_paths_ only when we try to read them. + // + // Assuming the underlying file system doesn't change (which isn't strictly true), + // the directory children are referentially transparent. + // + // In practice we do not need to distinguish between the file contents changing out + // from under us in this code, so we don't need the more strict requirements. + mutable std::vector<ResultT> children_paths_; + mutable bool children_initialized_{false}; + + friend std::ostream& operator<<(std::ostream& os, const DirectoryEntry& d); +}; + +std::ostream& operator<<(std::ostream& os, const DirectoryEntry& d) { + os << "DirectoryEntry{" << d.filename << ",ino:" << d.d_ino << ",type:" << d.d_type << "}"; + return os; +} + +using DirectoryEntryResult = DirectoryEntry::ResultT; + +// Read all directory entries and return it as a vector. This must be an eager operation, +// as readdir is not re-entrant. +// +// This could be considered as a limitation from the 'observable' perspective since +// one can end up reading unnecessary extra directory entries that are then never consumed. +// +// The following entries are skipped: +// - '.' self +// - ".." parent +// +// All DT types except the following are removed: +// * DT_LNK - symbolic link (empty children) +// * DT_REG - regular file (empty children) +// * DT_DIR - directory (has children) +static std::vector<DirectoryEntryResult> + ReadDirectoryEntriesFromDirectoryPath(std::string dirpath, borrowed<SystemCall*> system_call) { + DIR *dirp; + struct dirent *dp; + + LOG(VERBOSE) << "ReadDirectoryEntriesFromDirectoryPath(" << dirpath << ")"; + + if ((dirp = system_call->opendir(dirpath.c_str())) == nullptr) { + PLOG(ERROR) << "Couldn't open directory: " << dirpath; + return {DirectoryEntryError{kOpenDir, errno, dirpath}}; + } + + // Read all the results up front because readdir is not re-entrant. + std::vector<DirectoryEntryResult> results; + + // Get full path + the directory entry path. + auto child_path = [&] { return dirpath + "/" + dp->d_name; }; + + do { + errno = 0; + if ((dp = system_call->readdir(dirp)) != nullptr) { + if (dp->d_type == DT_DIR) { + if (strcmp(".", dp->d_name) == 0 || strcmp("..", dp->d_name) == 0) { + LOG(VERBOSE) << "Skip self/parent: " << dp->d_name; + continue; + } + + LOG(VERBOSE) << "Find entry " << child_path() + << ", ino: " << dp->d_ino << ", type: " << dp->d_type; + results.push_back(DirectoryEntry{child_path(), + static_cast<ino_t>(dp->d_ino), + dp->d_type}); + } else if (dp->d_type == DT_UNKNOWN) { + // This seems bad if it happens. We should probably do something about this. + LOG(WARNING) << "Found unknown DT entry: " << child_path(); + + results.push_back(DirectoryEntryError{kDtUnknown, /*errno*/0, child_path()}); + } else if (dp->d_type == DT_LNK || dp->d_type == DT_REG) { + // Regular non-directory file entry. + results.push_back(DirectoryEntry{child_path(), + static_cast<ino_t>(dp->d_ino), + dp->d_type}); + } else { + // Block device, character device, socket, etc... + LOG(VERBOSE) << "Skip DT entry of type: " << dp->d_type << " " << child_path(); + } + } else if (errno != 0) { + PLOG(ERROR) << "Error reading directory entry in " << dirpath; + + results.push_back(DirectoryEntryError{kReadDir, errno, dirpath}); + } + } while (dp != nullptr); + + if (system_call->closedir(dirp) < 0) { + PLOG(ERROR) << "Failed to close directory " << dirpath; + } + + return results; +} + +void DirectoryEntry::BuildChildrenPaths(borrowed<SystemCall*> system_call) const { + if (children_initialized_) { + return; + } + + if (d_type == DT_DIR) { + children_paths_ = ReadDirectoryEntriesFromDirectoryPath(filename, system_call); + // TODO: consider using dependency injection here to substitute this function during testing? + } +} + +struct InodeSearchParameters { + std::vector<Inode> inode_list; + std::vector<std::string> root_dirs; +}; + +// [IN] +// observable: expected<Value, Error>, ... +// [OUT] +// observable: Value, ... +// +// Any encountered 'Error' items are dropped after logging. +template <typename T> +auto MapExpectedOrLog(T&& observable, + ::android::base::LogSeverity log_level) { + return observable.filter([log_level](const auto& result) { + if (result) { + return true; + } else { + LOG(log_level) << result.error(); + return false; + } + }).map([](auto&& result) { + return IORAP_FORWARD_LAMBDA(result).value(); + }); +} + +template <typename T> +auto MapExpectedOrLogError(T&& observable) { + return MapExpectedOrLog(std::forward<T>(observable), ::android::base::ERROR); +} + +template <typename T> +auto MapOptionalOrDrop(T&& observable) { + return observable.filter([](const auto& result) { + return result.has_value(); + }).map([](auto&& result) { + return IORAP_FORWARD_LAMBDA(result).value(); + }); + // TODO: static_assert this isn't used with an unexpected. +} + +template <typename T, typename F> +auto VisitValueOrLogError(T&& expected, F&& visit_func, const char* error_prefix = "") { + if (!expected) { + LOG(ERROR) << error_prefix << " " << expected.error(); + } else { + visit_func(std::forward<T>(expected).value()); + } + // TODO: Could be good to make this more monadic by returning an optional. +} + +template <typename TSimple, typename T, typename F> +void TreeTraversalPreOrderObservableImpl(rx::subscriber<TSimple> dest, T&& node, F&& fn) { + LOG(VERBOSE) << "TreeTraversalPreOrderObservableImpl (begin) " << __PRETTY_FUNCTION__; + + if (!dest.is_subscribed()) { + LOG(VERBOSE) << "TreeTraversalPreOrderObservableImpl (unsubscribed)"; + return; + } else { + LOG(VERBOSE) << "TreeTraversalPreOrderObservableImpl (on_next node)"; + + // Copy the node here. This is less bad than it seems since we haven't yet + // calculated its children (except in the root), so its just doing a shallow memcpy (sizeof(T)). + // + // This assumes the children are calculated lazily, otherwise we'd need to have a separate + // NodeBody class which only holds the non-children elements. + + TSimple copy = std::forward<T>(node); + dest.on_next(std::move(copy)); + + if (!node.has_value()) { + return; + } + + // Whenever we call 'on_next' also check if we end up unsubscribing. + // This avoids the expensive call into the children. + if (!dest.is_subscribed()) { + LOG(VERBOSE) << "TreeTraversalPreOrderObservableImpl (post-self unsubscribe)"; + return; + } + + // Eagerly get the childrem, moving them instead of copying them. + auto&& children = fn(std::forward<T>(node)); + for (auto&& child : children) { + TreeTraversalPreOrderObservableImpl(dest, IORAP_FORWARD_LAMBDA(child), fn); + // TODO: double check this is doing the std::move properly for rvalues. + + if (!dest.is_subscribed()) { + LOG(VERBOSE) << "TreeTraversalPreOrderObservableImpl (unsubscribed in children)"; + break; + } + }; + } +} + +// Creates an observable over all the nodes in the tree rooted at node. +// fn is a function that returns the children of that node. +// +// The items are emitted left-to-right pre-order, and stop early if the +// observable is unsubscribed from. +// +// Implementation requirement: +// typeof(node) -> expected<V, E> or optional<V> or similar. +// fn(node) -> iterable<typeof(node)> +// +// preorder(self): +// visit(self) +// for child in fn(self): +// preorder(child) +template <typename T, typename F> +auto/*observable<T>*/ TreeTraversalPreOrderObservable(T&& node, F&& fn) { + LOG(VERBOSE) << "TreeTraversalPreOrderObservable: " << __PRETTY_FUNCTION__; + + using T_simple = std::decay_t<T>; + return rx::observable<>::create<T_simple>( + // Copy node to avoid lifetime issues. + [node=node,fn=std::forward<F>(fn)](rx::subscriber<T_simple> dest) { + LOG(VERBOSE) << "TreeTraversalPreOrderObservable (lambda)"; + TreeTraversalPreOrderObservableImpl<T_simple>(dest, + std::move(node), + std::move(fn)); + dest.on_completed(); + } + ); +} + +DirectoryEntry::ObservableT + DirectoryEntry::GetSubTreePreOrderEntries(borrowed<SystemCall*> system_call) const { + return TreeTraversalPreOrderObservable( + DirectoryEntryResult{*this}, + [system_call=system_call](auto/*DirectoryEntryResult*/&& result) + -> std::vector<DirectoryEntryResult> { + if (!result) { + LOG(VERBOSE) << "GetSubTreePreOrderEntries (no value return)"; + // Cannot have children when it was an error. + return {}; + } + return + IORAP_FORWARD_LAMBDA(result) + .value() + .GetChildrenEntries(system_call); + }); +} + +struct StatError { + int err_no; + std::string path_name; +}; + +std::ostream& operator<<(std::ostream& os, const StatError& e) { + os << "StatError{" << e.err_no << "," << e.path_name << "}"; + return os; +} + +template <typename U = void> // suppress unused warning. +static iorap::expected<struct stat, StatError> Stat(const std::string& path_name, + borrowed<SystemCall*> system_call) { + struct stat statbuf{}; + + // Call stat(2) in live code. Overridden in test code. + if (system_call->stat(path_name.c_str(), /*out*/&statbuf) == 0) { + return statbuf; + } else { + return iorap::unexpected(StatError{errno, path_name}); + } +} + +using StatResult = iorap::expected<struct stat, StatError>; + +// An inode's corresponding filename on the system. +struct SearchMatch { + Inode inode; + // Relative path joined with a root directory. + // + // Use absolute path root dirs to get back absolute path filenames. + // If relative, this is relative to the current working directory. + std::string filename; +}; + +std::ostream& operator<<(std::ostream& os, const SearchMatch& s) { + os << "SearchMatch{" << s.inode << ", " << s.filename << "}"; + return os; +} + +struct SearchState { + // Emit 'match' Inodes corresponding to the ones here. + InodeSet inode_set; + + // An inode matching one of the ones in inode_set was discovered in the most-recently + // emitted SearchState. + // + // The InodeSet removes any matching 'Inode'. + std::optional<SearchMatch> match; + + // TODO: make sure this doesn't copy [inodes], as that would be unnecessarily expensive. +}; + +std::ostream& operator<<(std::ostream& os, const SearchState& s) { + os << "SearchState{match:"; + // Print the 'match' first. The InodeSet could be very large so it could be truncated in logs. + if (s.match) { + os << s.match.value(); + } else { + os << "(none)"; + } + os << ", inode_set:" << s.inode_set << "}"; + return os; +} + +// TODO: write operator<< etc. + +// Return a lazy observable that will search for all filenames whose inodes +// match the inodes in inode_search_list. +// +// Every unmatched inode will be emitted as an unexpected at the end of the stream. +auto/*[observable<InodeResult>, connectable]*/ SearchDirectoriesForMatchingInodes( + std::vector<std::string> root_dirs, + std::vector<Inode> inode_search_list, + borrowed<SystemCall*> system_call) { + + // Create a (lazy) observable that will emit each DirectoryEntry that is a recursive subchild + // of root_dirs. Emission will be stopped when its unsubscribed from. + // + // This is done by calling readdir(3) lazily. + auto/*obs<DirectoryEntry>*/ find_all_subdir_entries = ([&]() { + DirectoryEntry sentinel = DirectoryEntry::CreateSentinel(std::move(root_dirs)); + auto/*obs<DirectoryEntryResult*/ results = sentinel.GetSubTreePreOrderEntries(system_call); + + // Drop any errors by logging them to logcat. "Unwrap" the expected into the underlying data. + auto/*obs<DirectoryEntry*>*/ expected_drop_errors = MapExpectedOrLogError(std::move(results)); + return expected_drop_errors; + })(); + + // DirectoryEntry is missing the dev_t portion, so we may need to call scan(2) again + // to confirm the dev_t. We skip calling scan(2) when the ino_t does not match. + // InodeSet lets us optimally avoid calling scan(2). + SearchState initial; + initial.inode_set = InodeSet::OfList(inode_search_list); + + auto/*[observable<SearchState>,Connectable]*/ search_state_results = find_all_subdir_entries.scan( + std::move(initial), + [system_call=system_call](SearchState search_state, const DirectoryEntry& dir_entry) { + LOG(VERBOSE) << "SearchDirectoriesForMatchingInodes#Scan " + << dir_entry << ", state: " << search_state; + + search_state.match = std::nullopt; + + InodeSet* inodes = &search_state.inode_set; + + // Find all the possible inodes across different devices. + InodeSet::ValueRange inode_list = inodes->FindInodeList(dir_entry.d_ino); + + // This directory doesn't correspond to any inodes we are searching for. + if (!inode_list) { + return search_state; + } + + StatResult maybe_stat = Stat(dir_entry.filename, system_call); + VisitValueOrLogError(maybe_stat, [&](const struct stat& stat_buf) { + // Try to match the specific inode. Usually this will not result in a match (nullopt). + std::optional<Inode> inode = inodes->FindAndRemoveInodeInList(inode_list, stat_buf); + + if (inode) { + search_state.match = SearchMatch{inode.value(), dir_entry.filename}; + } + }); + + return search_state; // implicit move. + } + // Avoid exhausting a potentially 'infinite' stream of files by terminating as soon + // as we find every single inode we care about. + ).take_while([](const SearchState& state) { + // Also emit the last item that caused the search set to go empty. + bool cond = !state.inode_set.Empty() || state.match; + + if (WOULD_LOG(VERBOSE)) { + static int kCounter = 0; + LOG(VERBOSE) << "SearchDirectoriesForMatchingInodes#take_while (" << kCounter++ << + ",is_empty:" + << state.inode_set.Empty() << ", match:" << state.match.has_value(); + } + // Minor O(1) implementation inefficiency: + // (Too minor to fix but it can be strange if looking at the logs or readdir traces). + // + // Note, because we return 'true' after the search set went empty, + // the overall stream graph still pulls from search_state_results exactly once more: + // + // This means that for cond to go to false, we would've read one extra item and then discarded + // it. If that item was the first child of a directory, that means we essentially did + // one redundant pass of doing a readdir. + // + // In other words if the search set goes to empty while the current item is a directory, + // it will definitely readdir on it at least once as we try to get the first child in + // OnTreeTraversal. + // + // This could be fixed with a 'take_until(Predicate)' operator which doesn't discard + // the last item when the condition becomes false. However rxcpp seems to lack this operator, + // whereas RxJava has it. + + if (!cond) { + LOG(VERBOSE) << "SearchDirectoriesForMatchingInodes#take_while " + << "should now terminate for " << state; + } + + return cond; + }).publish(); + // The publish here is mandatory. The stream is consumed twice (once by matched and once by + // unmatched streams). Without the publish, once all items from 'matched' were consumed it would + // start another instance of 'search_state_results' (i.e. it appears as if the search + // is restarted). + // + // By using 'publish', the search_state_results is effectively shared by both downstream nodes. + // Note that this also requires the subscriber to additionally call #connect on the above stream, + // otherwise no work will happen. + + // Lifetime notes: + // + // The the 'SearchState' is emitted into both below streams simultaneously. + // The 'unmatched_inode_values' only touches the inode_set. + // The 'matched_inode_values' only touches the match. + // Either stream can 'std::move' from those fields because they don't move each other's fields. + auto/*observable<InodeResult>*/ matched_inode_values = search_state_results + .filter([](const SearchState& search_state) { return search_state.match.has_value(); }) + .map([](SearchState& search_state) { return std::move(search_state.match.value()); }) + // observable<SearchMatch> + .map([](SearchMatch search_match) { + return InodeResult::makeSuccess(search_match.inode, std::move(search_match.filename)); + }); // observable<InodeResult> + + auto/*observable<?>*/ unmatched_inode_values = search_state_results + // The 'last' SearchState is the one that contains all the remaining inodes. + .take_last(1) // observable<SearchState> + .flat_map([](const SearchState& search_state) { + LOG(VERBOSE) << "SearchDirectoriesForMatchingInodes#unmatched -- flat_map"; + // Aside: Could've used a move here if the inodes weren't so lightweight already. + return search_state.inode_set.IterateValues(); }) + // observable<Inode> + .map([](const Inode& inode) { + LOG(VERBOSE) << "SearchDirectoriesForMatchingInodes#unmatched -- map"; + return InodeResult::makeFailure(inode, InodeResult::kCouldNotFindFilename); + }); + // observable<InodeResult> + + // The matched and unmatched InodeResults are emitted together. + // Use merge, not concat, because we need both observables to be subscribed to simultaneously. + + auto/*observable<InodeResult*/ all_inode_results = + matched_inode_values.merge(unmatched_inode_values); + + // Now that all mid-stream observables have been connected, turn the Connectable observable + // into a regular observable. + + // The caller has to call 'connect' on the search_state_results after subscribing + // and before any work can actually start. + return std::make_pair(all_inode_results, search_state_results); +} + + +rxcpp::observable<InodeResult> SearchDirectories::FindFilenamesFromInodes( + std::vector<std::string> root_directories, + std::vector<Inode> inode_list, + SearchMode mode) { + DCHECK(mode == SearchMode::kInProcessDirect) << " other modes not implemented yet"; + + auto/*observable[2]*/ [inode_results, connectable] = SearchDirectoriesForMatchingInodes( + std::move(root_directories), + std::move(inode_list), + system_call_); + + return inode_results; +} + +// I think we could avoid this with auto_connect, which rxcpp doesn't seem to have. +// +// I can't figure out any other way to avoid this, or at least to allow connecting +// on the primary observable (instead of a secondary side-observable). +// +// If using the obvious publish+ref_count then the unmerged stream gets no items emitted into it. +// If tried to ref_count later, everything turns into no-op. +// If trying to call connect too early, the subscribe is missed. +template <typename T> +struct RxAnyConnectableFromObservable : public SearchDirectories::RxAnyConnectable { + virtual void connect() override { + observable.connect(); + } + + virtual ~RxAnyConnectableFromObservable() {} + + RxAnyConnectableFromObservable(rxcpp::connectable_observable<T> observable) + : observable(observable) { + } + + rxcpp::connectable_observable<T> observable; +}; + +// Type deduction helper. +template <typename T> +std::unique_ptr<SearchDirectories::RxAnyConnectable> + MakeRxAnyConnectableFromObservable(rxcpp::connectable_observable<T> observable) { + SearchDirectories::RxAnyConnectable* ptr = new RxAnyConnectableFromObservable<T>{observable}; + return std::unique_ptr<SearchDirectories::RxAnyConnectable>{ptr}; +} + +std::pair<rxcpp::observable<InodeResult>, std::unique_ptr<SearchDirectories::RxAnyConnectable>> + SearchDirectories::FindFilenamesFromInodesPair( + std::vector<std::string> root_directories, + std::vector<Inode> inode_list, + SearchMode mode) { + DCHECK(mode == SearchMode::kInProcessDirect) << " other modes not implemented yet"; + + auto/*observable[2]*/ [inode_results, connectable] = SearchDirectoriesForMatchingInodes( + std::move(root_directories), + std::move(inode_list), + system_call_); + + std::unique_ptr<SearchDirectories::RxAnyConnectable> connectable_ptr = + MakeRxAnyConnectableFromObservable(connectable.as_dynamic()); + + return {inode_results, std::move(connectable_ptr)}; +} + +} // namespace iorap::inode2filename diff --git a/src/inode2filename/search_directories.h b/src/inode2filename/search_directories.h new file mode 100644 index 0000000..8156574 --- /dev/null +++ b/src/inode2filename/search_directories.h @@ -0,0 +1,147 @@ +// 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 IORAP_SRC_INODE2FILENAME_SEARCH_DIRECTORIES_H_ +#define IORAP_SRC_INODE2FILENAME_SEARCH_DIRECTORIES_H_ + +#include "common/expected.h" +#include "inode2filename/inode.h" +#include "inode2filename/system_call.h" + +#include <fruit/fruit.h> + +#include <rxcpp/rx.hpp> +namespace iorap::inode2filename { + +// Tuple of (Inode -> (Filename|Errno)) +struct InodeResult { + // We set this error when all root directories have been searched and + // yet we still could not find a corresponding filename for the inode under search. + static constexpr int kCouldNotFindFilename = -ENOENT; + + Inode inode; + // Value: Contains the filename (with a root directory as a prefix). + // Error: Contains the errno, usually -ENOENT or perhaps a security error. + iorap::expected<std::string /*filename*/, int /*errno*/> data; + + static InodeResult makeSuccess(Inode inode, std::string filename) { + return InodeResult{inode, std::move(filename)}; + } + + static InodeResult makeFailure(Inode inode, int err_no) { + return InodeResult{inode, iorap::unexpected{err_no}}; + } + + constexpr operator bool() const { + return data.has_value(); + } +}; + +enum class SearchMode { + // Test modes: + kInProcessDirect, // Execute the code directly. + kInProcessIpc, // Execute code via IPC layer using multiple threads. + // Shipping mode: + kOutOfProcessIpc, // Execute code via fork+exec with IPC. + + // Note: in-process system-wide stat(2)/readdir/etc is blocked by selinux. + // Attempting to call the test modes will fail with -EPERM. + // + // Use fork+exec mode in shipping configurations, which spawns inode2filename + // as a separate command. +}; + +struct SearchDirectories { + // Type-erased subset of rxcpp::connectable_observable<?> + struct RxAnyConnectable { + // Connects to the underlying observable. + // + // This kicks off the graph, streams begin emitting items. + // This method will block until all items have been fully emitted + // and processed by any subscribers. + virtual void connect() = 0; + + virtual ~RxAnyConnectable(){} + }; + + + // Create a cold observable of inode results (a lazy stream) corresponding + // to the inode search list. + // + // A depth-first search is done on each of the root directories (in order), + // until all inodes have been found (or until all directories have been exhausted). + // + // Some internal errors may occur during emission that aren't part of an InodeResult; + // these will be sent to the error logcat and dropped. + // + // Calling this function does not begin the search. + // The returned observable will begin the search after subscribing to it. + // + // The emitted InodeResult stream has these guarantees: + // - All inodes in inode_list will eventually be emitted exactly once in an InodeResult + // - When all inodes are found, directory traversal is halted. + // - The order of emission can be considered arbitrary. + // + // Lifetime rules: + // - The observable must be fully consumed before deleting any of the SearchDirectory's + // borrowed constructor parameters (e.g. the SystemCall). + // - SearchDirectory itself can be deleted at any time after creating an observable. + rxcpp::observable<InodeResult> + FindFilenamesFromInodes(std::vector<std::string> root_directories, + std::vector<Inode> inode_list, + SearchMode mode); + + // Create a cold observable of inode results (a lazy stream) corresponding + // to the inode search list. + // + // A depth-first search is done on each of the root directories (in order), + // until all inodes have been found (or until all directories have been exhausted). + // + // Some internal errors may occur during emission that aren't part of an InodeResult; + // these will be sent to the error logcat and dropped. + // + // Calling this function does not begin the search. + // The returned observable will begin the search after subscribing to it. + // + // The emitted InodeResult stream has these guarantees: + // - All inodes in inode_list will eventually be emitted exactly once in an InodeResult + // - When all inodes are found, directory traversal is halted. + // - The order of emission can be considered arbitrary. + // + // Lifetime rules: + // - The observable must be fully consumed before deleting any of the SearchDirectory's + // borrowed constructor parameters (e.g. the SystemCall). + // - SearchDirectory itself can be deleted at any time after creating an observable. + std::pair<rxcpp::observable<InodeResult>, std::unique_ptr<RxAnyConnectable>> + FindFilenamesFromInodesPair(std::vector<std::string> root_directories, + std::vector<Inode> inode_list, + SearchMode mode); + + // Any borrowed parameters here can also be borrowed by the observables returned by the above + // member functions. + // + // The observables must be fully consumed within the lifetime of the borrowed parameters. + INJECT(SearchDirectories(borrowed<SystemCall*> system_call)) + : system_call_(system_call) {} + + // TODO: is there a way to get rid of this second RxAnyConnectable parameter? + private: + // This gets passed around to lazy lambdas, so we must finish consuming any observables + // before the injected system call is deleted. + borrowed<SystemCall*> system_call_; +}; + +} // namespace iorap::inode2filename + +#endif // IORAP_SRC_INODE2FILENAME_SEARCH_DIRECTORIES_H_ diff --git a/src/inode2filename/system_call.h b/src/inode2filename/system_call.h new file mode 100644 index 0000000..43c371f --- /dev/null +++ b/src/inode2filename/system_call.h @@ -0,0 +1,73 @@ +// 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 IORAP_SRC_INODE2FILENAME_SYSTEM_CALL_H_ +#define IORAP_SRC_INODE2FILENAME_SYSTEM_CALL_H_ + +#include <fruit/fruit.h> + +#include <dirent.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +// Abstract out the system calls behind a virtual interface: +// This enables us to use dependency injection to provide mock implementations +// during tests. +struct SystemCall { + // stat(2) + virtual int stat(const char *pathname, struct stat *statbuf) = 0; + + // opendir(3) + virtual DIR *opendir(const char *name) = 0; + + // readdir(3) + virtual struct dirent *readdir(DIR *dirp) = 0; + + // closedir(3) + virtual int closedir(DIR *dirp) = 0; + + virtual ~SystemCall() {} +}; + +// "Live" implementation that calls down to libc. +struct SystemCallImpl : public SystemCall { + // Marks this constructor as the one to use for injection. + INJECT(SystemCallImpl()) = default; + + // stat(2) + virtual int stat(const char *pathname, struct stat *statbuf) override { + return ::stat(pathname, statbuf); + } + + // opendir(3) + virtual DIR *opendir(const char *name) override { + return ::opendir(name); + } + + // readdir(3) + virtual struct dirent *readdir(DIR *dirp) override { + return ::readdir(dirp); + } + + // closedir(3) + virtual int closedir(DIR *dirp) override { + return ::closedir(dirp); + } + + virtual ~SystemCallImpl() {} +}; + +#endif // IORAP_SRC_INODE2FILENAME_SYSTEM_CALL_H_ + diff --git a/src/iorapd/main.cc b/src/iorapd/main.cc index 074984d..ab73fe5 100644 --- a/src/iorapd/main.cc +++ b/src/iorapd/main.cc @@ -15,8 +15,11 @@ */ #include "binder/iiorap_impl.h" +#include "common/debug.h" +#include "manager/event_manager.h" #include <android-base/logging.h> +#include <android-base/properties.h> #include <binder/IPCThreadState.h> #include <utils/Trace.h> @@ -46,22 +49,24 @@ class StderrAndLogdLogger { }; int main(int /*argc*/, char** argv) { - // Log everything!! TODO: less aggressive logging once this is closer to being shipped. - setenv("ANDROID_LOG_TAGS", "*:v", /*overwrite*/ 1); + if (android::base::GetBoolProperty("iorapd.log.verbose", iorap::kIsDebugBuild)) { + // Show verbose logs if the property is enabled or if we are a debug build. + setenv("ANDROID_LOG_TAGS", "*:v", /*overwrite*/ 1); + } // Logs go to system logcat. android::base::InitLogging(argv, StderrAndLogdLogger{android::base::SYSTEM}); - // TODO: an selinux context is required, otherwise clients are rejected when trying to - // find this service. - - // Testing workaround: use 'adb shell setenforce 0' { android::ScopedTrace trace_main{ATRACE_TAG_PACKAGE_MANAGER, "main"}; LOG(INFO) << kServiceName << " (the prefetchening) firing up"; android::ScopedTrace trace_start{ATRACE_TAG_PACKAGE_MANAGER, "IorapNativeService::start"}; - if (!iorap::binder::IIorapImpl::Start()) { + + // TODO: use fruit for this DI. + auto /*std::shared_ptr<EventManager>*/ event_manager = + iorap::manager::EventManager::Create(); + if (!iorap::binder::IIorapImpl::Start(std::move(event_manager))) { LOG(ERROR) << "Unable to start IorapNativeService"; exit(1); } diff --git a/src/manager/event_manager.cc b/src/manager/event_manager.cc new file mode 100644 index 0000000..3b75b89 --- /dev/null +++ b/src/manager/event_manager.cc @@ -0,0 +1,496 @@ +/* + * 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/debug.h" +#include "common/expected.h" +#include "manager/event_manager.h" +#include "perfetto/rx_producer.h" + +#include <android-base/properties.h> +#include <rxcpp/rx.hpp> + +#include <atomic> +#include <functional> + +using rxcpp::observe_on_one_worker; + +namespace iorap::manager { + +using binder::RequestId; +using binder::AppLaunchEvent; +using perfetto::PerfettoStreamCommand; +using perfetto::PerfettoTraceProto; + +struct AppComponentName { + std::string package; + std::string activity_name; + + static bool HasAppComponentName(const std::string& s) { + return s.find('/') != std::string::npos; + } + + // "com.foo.bar/.A" -> {"com.foo.bar", ".A"} + static AppComponentName FromString(const std::string& s) { + constexpr const char delimiter = '/'; + std::string package = s.substr(0, delimiter); + + std::string activity_name = s; + activity_name.erase(0, s.find(delimiter) + sizeof(delimiter)); + + return {std::move(package), std::move(activity_name)}; + } + + // {"com.foo.bar", ".A"} -> "com.foo.bar/.A" + std::string ToString() const { + return package + "/" + activity_name; + } + + /* + * '/' is encoded into %2F + * '%' is encoded into %25 + * + * This allows the component name to be be used as a file name + * ('/' is illegal due to being a path separator) with minimal + * munging. + */ + + // "com.foo.bar%2F.A%25" -> {"com.foo.bar", ".A%"} + static AppComponentName FromUrlEncodedString(const std::string& s) { + std::string cpy = s; + Replace(cpy, "%2F", "/"); + Replace(cpy, "%25", "%"); + + return FromString(cpy); + } + + // {"com.foo.bar", ".A%"} -> "com.foo.bar%2F.A%25" + std::string ToUrlEncodedString() const { + std::string s = ToString(); + Replace(s, "%", "%25"); + Replace(s, "/", "%2F"); + return s; + } + + private: + static bool Replace(std::string& str, const std::string& from, const std::string& to) { + // TODO: call in a loop to replace all occurrences, not just the first one. + const size_t start_pos = str.find(from); + if (start_pos == std::string::npos) { + return false; + } + + str.replace(start_pos, from.length(), to); + + return true; +} +}; + +std::ostream& operator<<(std::ostream& os, const AppComponentName& name) { + os << name.ToString(); + return os; +} + +// Main logic of the #OnAppLaunchEvent scan method. +// +// All functions are called from the same thread as the event manager +// functions. +// +// This is a data type, it's moved (std::move) around from one iteration +// of #scan to another. +struct AppLaunchEventState { + std::optional<AppComponentName> component_name_; + + bool is_tracing_{false}; + std::optional<rxcpp::composite_subscription> rx_lifetime_; + std::vector<rxcpp::composite_subscription> rx_in_flight_; + + borrowed<perfetto::RxProducerFactory*> perfetto_factory_; // not null + borrowed<observe_on_one_worker*> thread_; // not null + borrowed<observe_on_one_worker*> io_thread_; // not null + + explicit AppLaunchEventState(borrowed<perfetto::RxProducerFactory*> perfetto_factory, + borrowed<observe_on_one_worker*> thread, + borrowed<observe_on_one_worker*> io_thread) { + perfetto_factory_ = perfetto_factory; + DCHECK(perfetto_factory_ != nullptr); + + thread_ = thread; + DCHECK(thread_ != nullptr); + + io_thread_ = io_thread; + DCHECK(io_thread_ != nullptr); + } + + // Updates the values in this struct only as a side effect. + // + // May create and fire a new rx chain on the same threads as passed + // in by the constructors. + void OnNewEvent(const AppLaunchEvent& event) { + LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent: " << event; + + using Type = AppLaunchEvent::Type; + + switch (event.type) { + case Type::kIntentStarted: { + DCHECK(!IsTracing()); + // Optimistically start tracing if we have the activity in the intent. + if (!event.intent_proto->has_component()) { + // Can't do anything if there is no component in the proto. + LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent: no component, can't trace"; + break; + } + + const std::string& package_name = event.intent_proto->component().package_name(); + const std::string& class_name = event.intent_proto->component().class_name(); + AppComponentName component_name{package_name, class_name}; + + component_name_ = component_name; + rx_lifetime_ = StartTracing(std::move(component_name)); + + break; + } + case Type::kIntentFailed: + AbortTrace(); + break; + case Type::kActivityLaunched: { + // Cancel tracing for warm/hot. + // Restart tracing if the activity was unexpected. + + AppLaunchEvent::Temperature temperature = event.temperature; + if (temperature != AppLaunchEvent::Temperature::kCold) { + LOG(DEBUG) << "AppLaunchEventState#OnNewEvent aborting trace due to non-cold temperature"; + AbortTrace(); + } else if (!IsTracing()) { // and the temperature is Cold. + // Start late trace when intent didn't have a component name + LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent need to start new trace"; + + const std::string& title = event.activity_record_proto->identifier().title(); + if (!AppComponentName::HasAppComponentName(title)) { + // Proto comment claim this is sometimes a window title. + // We need the actual 'package/component' here, so just ignore it if it's a title. + LOG(WARNING) << "App launched without a component name: " << event; + break; + } + + AppComponentName component_name = AppComponentName::FromString(title); + + component_name_ = component_name; + rx_lifetime_ = StartTracing(std::move(component_name)); + } else { + // FIXME: match actual component name against intent component name. + // abort traces if they don't match. + + LOG(VERBOSE) << "AppLaunchEventState#OnNewEvent already tracing"; + } + break; + } + case Type::kActivityLaunchFinished: + // Finish tracing and collect trace buffer. + // + // TODO: this happens automatically when perfetto finishes its + // trace duration. + if (IsTracing()) { + MarkPendingTrace(); + } + break; + case Type::kActivityLaunchCancelled: + // Abort tracing. + AbortTrace(); + break; + default: + DCHECK(false) << "invalid type: " << event; // binder layer should've rejected this. + LOG(ERROR) << "invalid type: " << event; // binder layer should've rejected this. + } + } + + bool IsTracing() const { + return is_tracing_; + } + + rxcpp::composite_subscription StartTracing(AppComponentName component_name) { + DCHECK(!IsTracing()); + + auto /*observable<PerfettoStreamCommand>*/ perfetto_commands = + rxcpp::observable<>::just(PerfettoStreamCommand::kStartTracing) + // wait 1x + .concat( + // Pick a value longer than the perfetto config delay_ms, so that we send + // 'kShutdown' after tracing has already finished. + rxcpp::observable<>::interval(std::chrono::milliseconds(10000)) + .take(2) // kStopTracing, kShutdown. + .map([](int value) { + // value is 1,2,3,... + return static_cast<PerfettoStreamCommand>(value); // 1,2, ... + }) + ); + + auto /*observable<PerfettoTraceProto>*/ trace_proto_stream = + perfetto_factory_->CreateTraceStream(perfetto_commands); + // This immediately connects to perfetto asynchronously. + // + // TODO: create a perfetto handle earlier, to minimize perfetto startup latency. + + rxcpp::composite_subscription lifetime; + + trace_proto_stream + .tap([](const PerfettoTraceProto& trace_proto) { + LOG(VERBOSE) << "StartTracing -- PerfettoTraceProto received (1)"; + }) + .observe_on(*thread_) // All work prior to 'observe_on' is handled on thread_. + .subscribe_on(*thread_) // All work prior to 'observe_on' is handled on thread_. + .observe_on(*io_thread_) // Write data on an idle-class-priority thread. + .tap([](const PerfettoTraceProto& trace_proto) { + LOG(VERBOSE) << "StartTracing -- PerfettoTraceProto received (2)"; + }) + .as_blocking() // TODO: remove. + .subscribe(/*out*/lifetime, + /*on_next*/[component_name] + (PerfettoTraceProto trace_proto) { + std::string file_path = "/data/misc/iorapd/"; + file_path += component_name.ToUrlEncodedString(); + file_path += ".perfetto_trace.pb"; + + // TODO: timestamp each file into a subdirectory. + + if (!trace_proto.WriteFullyToFile(file_path)) { + LOG(ERROR) << "Failed to save TraceBuffer to " << file_path; + } else { + LOG(INFO) << "Perfetto TraceBuffer saved to file: " << file_path; + } + }, + /*on_error*/[](rxcpp::util::error_ptr err) { + LOG(ERROR) << "Perfetto trace proto collection error: " << rxcpp::util::what(err); + }); + + is_tracing_ = true; + + return lifetime; + } + + void AbortTrace() { + LOG(VERBOSE) << "AppLaunchEventState - AbortTrace"; + is_tracing_ = false; + if (rx_lifetime_) { + // TODO: it would be good to call perfetto Destroy. + + LOG(VERBOSE) << "AppLaunchEventState - AbortTrace - Unsubscribe"; + rx_lifetime_->unsubscribe(); + rx_lifetime_.reset(); + } + } + + void MarkPendingTrace() { + LOG(VERBOSE) << "AppLaunchEventState - MarkPendingTrace"; + DCHECK(is_tracing_); + DCHECK(rx_lifetime_.has_value()); + + if (rx_lifetime_) { + LOG(VERBOSE) << "AppLaunchEventState - MarkPendingTrace - lifetime moved"; + // Don't unsubscribe because that would cause the perfetto TraceBuffer + // to get dropped on the floor. + // + // Instead, we want to let it finish and write it out to a file. + rx_in_flight_.push_back(*std::move(rx_lifetime_)); + rx_lifetime_.reset(); + } else { + LOG(VERBOSE) << "AppLaunchEventState - MarkPendingTrace - lifetime was empty"; + } + + // FIXME: how do we clear this vector? + } +}; + +// Convert callback pattern into reactive pattern. +struct AppLaunchEventSubject { + using RefWrapper = + std::reference_wrapper<const AppLaunchEvent>; + + AppLaunchEventSubject() {} + + void Subscribe(rxcpp::subscriber<RefWrapper> subscriber) { + DCHECK(ready_ != true) << "Cannot Subscribe twice"; + + subscriber_ = std::move(subscriber); + + // Release edge of synchronizes-with AcquireIsReady. + ready_.store(true); + } + + void OnNext(const AppLaunchEvent& e) { + if (!AcquireIsReady()) { + return; + } + + if (!subscriber_->is_subscribed()) { + return; + } + + /* + * TODO: fix upstream. + * + * Rx workaround: this fails to compile when + * the observable is a reference type: + * + * external/Reactive-Extensions/RxCpp/Rx/v2/src/rxcpp/rx-observer.hpp:354:18: error: multiple overloads of 'on_next' instantiate to the same signature 'void (const iorap::binder::AppLaunchEvent &) const' + * virtual void on_next(T&&) const {}; + * + * external/Reactive-Extensions/RxCpp/Rx/v2/src/rxcpp/rx-observer.hpp:353:18: note: previous declaration is here + * virtual void on_next(T&) const {}; + * + * (The workaround is to use reference_wrapper instead + * of const AppLaunchEvent&) + */ + subscriber_->on_next(std::cref(e)); + + } + + void OnCompleted() { + if (!AcquireIsReady()) { + return; + } + + subscriber_->on_completed(); + } + + private: + bool AcquireIsReady() { + // Synchronizes-with the release-edge in Subscribe. + // This can happen much later, only once the subscription actually happens. + + // However, as far as I know, 'rxcpp::subscriber' is not thread safe, + // (but the observable chain itself can be made thread-safe via #observe_on, etc). + // so we must avoid reading it until it has been fully synchronized. + // + // TODO: investigate rxcpp subscribers and see if we can get rid of this atomics, + // to make it simpler. + return ready_.load(); + } + + // TODO: also track the RequestId ? + + std::atomic<bool> ready_{false}; + + + std::optional<rxcpp::subscriber<RefWrapper>> subscriber_; +}; + +class EventManager::Impl { + public: + Impl(/*borrow*/perfetto::RxProducerFactory& perfetto_factory) + : perfetto_factory_(perfetto_factory), + worker_thread_(rxcpp::observe_on_new_thread()), + worker_thread2_(rxcpp::observe_on_new_thread()), + io_thread_(perfetto::ObserveOnNewIoThread()) { + + // TODO: read all properties from one config class. + tracing_allowed_ = ::android::base::GetBoolProperty("iorapd.perfetto.enable", /*default*/false); + + if (tracing_allowed_) { + rx_lifetime_ = InitializeRxGraph(); + } else { + LOG(WARNING) << "Tracing disabled by iorapd.perfetto.enable=false"; + } + } + + bool OnAppLaunchEvent(RequestId request_id, + const AppLaunchEvent& event) { + LOG(VERBOSE) << "EventManager::OnAppLaunchEvent(" + << "request_id=" << request_id.request_id << "," + << event; + + app_launch_event_subject_.OnNext(event); + + return true; + } + + rxcpp::composite_subscription InitializeRxGraph() { + LOG(VERBOSE) << "EventManager::InitializeRxGraph"; + + app_launch_events_ = rxcpp::observable<>::create<AppLaunchEventRefWrapper>( + [&](rxcpp::subscriber<AppLaunchEventRefWrapper> subscriber) { + app_launch_event_subject_.Subscribe(std::move(subscriber)); + }); + + rxcpp::composite_subscription lifetime; + + AppLaunchEventState initial_state{&perfetto_factory_, &worker_thread2_, &io_thread_}; + app_launch_events_ + .subscribe_on(worker_thread_) + .scan(std::move(initial_state), + [](AppLaunchEventState state, AppLaunchEventRefWrapper event) { + state.OnNewEvent(event.get()); + return state; + }) + .subscribe(/*out*/lifetime, [](const AppLaunchEventState& state) { + // Intentionally left blank. + (void)state; + }); + + return lifetime; + } + + perfetto::RxProducerFactory& perfetto_factory_; + bool tracing_allowed_{true}; + + using AppLaunchEventRefWrapper = AppLaunchEventSubject::RefWrapper; + rxcpp::observable<AppLaunchEventRefWrapper> app_launch_events_; + AppLaunchEventSubject app_launch_event_subject_; + + rxcpp::observable<RequestId> completed_requests_; + + // regular-priority thread to handle binder callbacks. + observe_on_one_worker worker_thread_; + observe_on_one_worker worker_thread2_; + // low priority idle-class thread for IO operations. + observe_on_one_worker io_thread_; + + rxcpp::composite_subscription rx_lifetime_; + +//INTENTIONAL_COMPILER_ERROR_HERE: + // FIXME: + // ok so we want to expose a 'BlockingSubscribe' or a 'Subscribe' or some kind of function + // that the main thread can call. This would subscribe on all the observables we internally + // have here (probably on an event-manager-dedicated thread for simplicity). + // + // ideally we'd just reuse the binder thread to handle the events but I'm not super sure, + // maybe this already works with the identity_current_thread coordination? +}; +using Impl = EventManager::Impl; + +EventManager::EventManager(perfetto::RxProducerFactory& perfetto_factory) + : impl_(new Impl(perfetto_factory)) {} + +std::shared_ptr<EventManager> EventManager::Create() { + static perfetto::PerfettoDependencies::Injector injector{ + perfetto::PerfettoDependencies::CreateComponent + }; + static perfetto::RxProducerFactory producer_factory{ + /*borrow*/injector + }; + return EventManager::Create(/*borrow*/producer_factory); +} + +std::shared_ptr<EventManager> EventManager::Create(perfetto::RxProducerFactory& perfetto_factory) { + std::shared_ptr<EventManager> p{new EventManager{/*borrow*/perfetto_factory}}; + return p; +} + +bool EventManager::OnAppLaunchEvent(RequestId request_id, + const AppLaunchEvent& event) { + return impl_->OnAppLaunchEvent(request_id, event); +} + +} // namespace iorap::manager diff --git a/src/manager/event_manager.h b/src/manager/event_manager.h new file mode 100644 index 0000000..5355296 --- /dev/null +++ b/src/manager/event_manager.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#ifndef IORAP_MANAGER_EVENT_MANAGER_H_ +#define IORAP_MANAGER_EVENT_MANAGER_H_ + +#include "binder/app_launch_event.h" +#include "binder/request_id.h" + +#include <memory> + +namespace iorap::perfetto { + struct RxProducerFactory; +} // namespace iorap::perfetto + +namespace iorap::manager { + +class EventManager { + public: + static std::shared_ptr<EventManager> Create(); + static std::shared_ptr<EventManager> Create( + /*borrow*/perfetto::RxProducerFactory& perfetto_factory); + + // Handles an AppLaunchEvent: + // + // * Intent starts and app launch starts are treated critically + // and will be handled immediately. This means the caller + // (e.g. the binder pool thread) could be starved in the name + // of low latency. + // + // * Other types are handled in a separate thread. + bool OnAppLaunchEvent(binder::RequestId request_id, + const binder::AppLaunchEvent& event); + + class Impl; + private: + std::unique_ptr<Impl> impl_; + + EventManager(perfetto::RxProducerFactory& perfetto_factory); +}; + +} // namespace iorap::manager + +#endif // IORAP_MANAGER_EVENT_MANAGER_H_ diff --git a/src/perfetto/main.cc b/src/perfetto/main.cc new file mode 100644 index 0000000..0b1056e --- /dev/null +++ b/src/perfetto/main.cc @@ -0,0 +1,244 @@ +// 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. + +//#undef NDEBUG // get DCHECK etc. + + +#include "common/debug.h" +#include "common/expected.h" +#include "perfetto/rx_producer.h" + +#include <android-base/unique_fd.h> +#include <android-base/parseint.h> +#include <android-base/file.h> + +#include "rxcpp/rx.hpp" +#include <iostream> +#include <optional> + +#include <sched.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <syscall.h> +#include <fcntl.h> +#include <unistd.h> + +using namespace iorap::perfetto; // NOLINT + +#if defined(IORAP_PERFETTO_MAIN) + +void Usage(char** argv) { + std::cerr << "Usage: " << argv[0] << " [--config-proto=config.pb] [--duration-ms=5000] [--output-proto=output.pb]" << std::endl; + std::cerr << "" << std::endl; + std::cerr << " Request a perfetto trace, blocking until it's complete. The resulting trace proto" << std::endl; + std::cerr << " is output to stdout as text, or to --output-proto as a binary." << std::endl; + std::cerr << "" << std::endl; + std::cerr << " Optional flags:" << std::endl; + std::cerr << " --help,-h Print this Usage." << std::endl; + std::cerr << " --output-proto $,-op $ Perfetto tracebuffer output file (default stdout)." << std::endl; + std::cerr << " --config-proto $,-cp $ Path to binary protobuf config." << std::endl; + std::cerr << " --duration-ms $,-dm $ How long to run trace for in milliseconds." << std::endl; + std::cerr << " --simple Simplest possible perfetto state transitions (default off)." << std::endl; + std::cerr << " --verbose,-v Set verbosity (default off)." << std::endl; + std::cerr << " --wait,-w Wait for key stroke before continuing (default off)." << std::endl; + exit(1); +} + +PerfettoDependencies::Component CreateCommandLinePerfettoDependenciesComponent( + uint32_t duration_ms) { + // TODO: read from command line. + static const uint32_t kBufferSize = 4096; + + // TODO: remove this hack. + static const uint32_t kTraceDurationMs = duration_ms; + + // fruit: using 'bindInstance' causes a segfault every time. +#if 0 + + // fruit: Can't use a stateful lambda, so use bindInstance instead of registerProvider. + auto config = PerfettoDependencies::CreateConfig(duration_ms, + /*deferred_start*/true, + kBufferSize); + + .... bindInstance(config); +#endif + + return fruit::createComponent() + .bind<PerfettoConsumer, PerfettoConsumerImpl>() + .registerProvider([]() /* -> TraceConfig */ { + return PerfettoDependencies::CreateConfig(kTraceDurationMs, + /*deferred_start*/true, + kBufferSize); + }); +} + +static void CollectPerfettoTraceBufferViaAbstractions( + RxProducerFactory& producer_factory, + const std::string& arg_output_proto, + const int arg_duration_ms) { + LOG(VERBOSE) << "CollectPerfettoTraceBufferViaAbstractions"; + + // Don't create a subscriber to emit the PerfettoStreamCommand. + // RxCpp is "greedy" and consumes every possible item emitted (it doesn't support 'pull'). We want + // to operate on a (command,state) iteration every time, just like in a real scenario. + // Adding the 'interval' turns into a non-greedy version (i.e. push). + + // Immediately emit 'kStartTracing', wait and emit kStopTracing, wait and emit kShutdown. + // In reality, there would be a delay between all these events. + auto /*observable<PerfettoStreamCommand>*/ commands = + rxcpp::observable<>::just(PerfettoStreamCommand::kStartTracing) + // wait 1x + .concat( + // Pick a value longer than the perfetto config delay_ms, so that we send + // 'kShutdown' after tracing has already finished. + rxcpp::observable<>::interval(std::chrono::milliseconds(arg_duration_ms * 2)) + .take(2) // kStopTracing, kShutdown. + .map([](int value) { + // value is 1,2,3,... + return static_cast<PerfettoStreamCommand>(value); // 1,2, ... + }) + ); + + auto /*observable<PerfettoTraceProto>*/ trace_proto_stream = + producer_factory.CreateTraceStream(commands); + + trace_proto_stream + .observe_on(ObserveOnNewIoThread()) // Write data on an idle-class-priority thread. + .as_blocking() // Wait for observable to terminate with on_completed or on_error. + .subscribe(/*on_next*/[arg_output_proto] + (PerfettoTraceProto trace_proto) { + if (!trace_proto.WriteFullyToFile(arg_output_proto)) { + LOG(ERROR) << "Failed to save TraceBuffer to " << arg_output_proto; + } else { + LOG(INFO) << "TraceBuffer saved to file: " << arg_output_proto; + LOG(INFO); + LOG(INFO) << "To print this in a human readable form, execute these commands:"; + LOG(INFO) << "$> adb pull '" << arg_output_proto << "'"; + LOG(INFO) << "$> trace_to_text systrace <filename.pb>"; + } + }, + /*on_error*/[](rxcpp::util::error_ptr err) { + LOG(ERROR) << "Perfetto trace proto collection error: " << rxcpp::util::what(err); + }); +} + +namespace iorap::perfetto { +// Reach inside rx_producer.cc +// Not part of any headers because it's internal. +void CollectPerfettoTraceBufferImmediately( + RxProducerFactory& producer_factory, + const std::string& arg_output_proto); +} + +int main(int argc, char** argv) { + android::base::InitLogging(argv); + android::base::SetLogger(android::base::StderrLogger); + + bool wait_for_keystroke = false; + bool enable_verbose = false; + + std::string arg_output_proto; + std::string arg_config_proto; + uint32_t arg_duration_ms = 1000; + bool arg_simple = false; + + if (argc == 1) { + Usage(argv); + } + + for (int arg = 1; arg < argc; ++arg) { + std::string argstr = argv[arg]; + bool has_arg_next = (arg+1)<argc; + std::string arg_next = has_arg_next ? argv[arg+1] : ""; + + if (argstr == "--help" || argstr == "-h") { + Usage(argv); + } else if (argstr == "--output-proto" || argstr == "-op") { + if (!has_arg_next) { + std::cerr << "Missing --output-proto <value>" << std::endl; + return 1; + } + arg_output_proto = arg_next; + ++arg; + } else if (argstr == "--config-proto" || argstr == "-cp") { + if (!has_arg_next) { + std::cerr << "Missing --config-proto <value>" << std::endl; + return 1; + } + arg_config_proto = arg_next; + LOG(WARNING) << "TODO: parse configs from a file, not implemented yet."; + ++arg; + } else if (argstr == "--duration-ms" || argstr == "-dm") { + if (!has_arg_next) { + std::cerr << "Missing --duration-ms <value>" << std::endl; + return 1; + } + if (!android::base::ParseUint(arg_next.c_str(), /*out*/&arg_duration_ms)) { + std::cerr << "Invalid --duration-ms " << arg_next << ", reason: " << strerror(errno); + return 1; + } + ++arg; + } else if (argstr == "--simple") { + arg_simple = true; + } else if (argstr == "--verbose" || argstr == "-v") { + enable_verbose = true; + } else if (argstr == "--wait" || argstr == "-w") { + wait_for_keystroke = true; + } + } + + if (enable_verbose) { + android::base::SetMinimumLogSeverity(android::base::VERBOSE); + + LOG(VERBOSE) << "Verbose check"; + LOG(VERBOSE) << "Debug check: " << ::iorap::kIsDebugBuild; + } + + // Useful to attach a debugger... + // 1) $> iorap-cmd-perfetto -w <args> + // 2) $> gdbclient <pid> + if (wait_for_keystroke) { + LOG(INFO) << "Self pid: " << getpid(); + LOG(INFO) << "Press any key to continue..."; + std::cin >> wait_for_keystroke; + } + + int return_code = 0; + // TODO: convert #on-error into a non-0 return code. + + PerfettoDependencies::Injector injector{ + CreateCommandLinePerfettoDependenciesComponent, + arg_duration_ms + }; + RxProducerFactory rx_producer_factory{/*borrow*/injector}; + + if (arg_simple) { + // To debug any kind of low-level perfetto issues. + CollectPerfettoTraceBufferImmediately(/*inout*/rx_producer_factory, arg_output_proto); + } else { + // To debug our own iorap internal abstractions. + CollectPerfettoTraceBufferViaAbstractions(/*inout*/rx_producer_factory, + arg_output_proto, + arg_duration_ms); + } + + // Uncomment this if we want to leave the process around to inspect it from adb shell. + // sleep(100000); + + // 0 -> successfully wrote the TraceProto out to file. + // 1 -> failed along the way (#on_error and also see the error logs). + return return_code; +} + +#endif diff --git a/src/perfetto/perfetto_consumer.h b/src/perfetto/perfetto_consumer.h new file mode 100644 index 0000000..ce04ffd --- /dev/null +++ b/src/perfetto/perfetto_consumer.h @@ -0,0 +1,86 @@ +// 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 IORAP_SRC_PERFETTO_PERFETTO_CONSUMER_H_ +#define IORAP_SRC_PERFETTO_PERFETTO_CONSUMER_H_ + +#include <fruit/fruit.h> +#include <perfetto/public/consumer_api.h> // libperfetto + +namespace iorap::perfetto { + +// Abstract out the Perfetto C API behind a virtual interface: +// This enables us to use dependency injection to provide mock implementations +// during tests. +struct PerfettoConsumer { + // 1:1 aliasing of type definitions and constants in perfetto/public/consumer_api.h + // Refer to the documentation there. + using State = ::perfetto::consumer::State; + using Handle = ::perfetto::consumer::Handle; + static constexpr Handle kInvalidHandle = ::perfetto::consumer::kInvalidHandle; + using OnStateChangedCb = ::perfetto::consumer::OnStateChangedCb; + using TraceBuffer = ::perfetto::consumer::TraceBuffer; + + // 1:1 forwarding of C-style functions in perfetto/public/consumer_api.h + // Refer to the documentation there. + + virtual Handle Create(const void* config_proto, + size_t config_len, + OnStateChangedCb callback, + void* callback_arg) = 0; + virtual void StartTracing(Handle) = 0; + virtual TraceBuffer ReadTrace(Handle) = 0; + virtual void Destroy(Handle) = 0; + virtual State PollState(Handle) = 0; + + virtual ~PerfettoConsumer() {} +}; + +// "Live" implementation that calls down to libperfetto. +struct PerfettoConsumerImpl : public PerfettoConsumer { + // Marks this constructor as the one to use for injection. + INJECT(PerfettoConsumerImpl()) = default; + + virtual Handle Create(const void* config_proto, + size_t config_len, + OnStateChangedCb callback, + void* callback_arg) override { + return ::perfetto::consumer::Create(config_proto, + config_len, + callback, + callback_arg); + } + + virtual void StartTracing(Handle handle) override { + ::perfetto::consumer::StartTracing(handle); + } + + virtual TraceBuffer ReadTrace(Handle handle) override { + return ::perfetto::consumer::ReadTrace(handle); + } + + virtual void Destroy(Handle handle) override { + ::perfetto::consumer::Destroy(handle); + } + virtual State PollState(Handle handle) override { + return ::perfetto::consumer::PollState(handle); + } + + virtual ~PerfettoConsumerImpl() {} +}; + +} // namespace iorap::perfetto + +#endif // IORAP_SRC_PERFETTO_PERFETTO_CONSUMER_H_ + diff --git a/src/perfetto/rx_producer.cc b/src/perfetto/rx_producer.cc new file mode 100644 index 0000000..14311a1 --- /dev/null +++ b/src/perfetto/rx_producer.cc @@ -0,0 +1,865 @@ +// 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/debug.h" +#include "common/expected.h" +#include "perfetto/rx_producer.h" + +#include <android-base/file.h> +#include <android-base/properties.h> +#include <android-base/unique_fd.h> + +#include <iostream> + +#include <sched.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <syscall.h> +#include <fcntl.h> +#include <unistd.h> + +// TODO: move to perfetto code +namespace perfetto { +namespace consumer { + +std::ostream& operator<<(std::ostream& os, State state) { + switch (state) { + case State::kTraceFailed: + os << "kTraceFailed"; + break; + case State::kConnectionError: + os << "kConnectionError"; + break; + case State::kSessionNotFound: + os << "kSessionNotFound"; + break; + case State::kIdle: + os << "kIdle"; + break; + case State::kConnecting: + os << "kConnecting"; + break; + case State::kConfigured: + os << "kConfigured"; + break; + case State::kTracing: + os << "kTracing"; + break; + case State::kTraceEnded: + os << "kTraceEnded"; + break; + default: + os << "(unknown)"; // did someone forget to update this code? + break; + } + return os; +} + +} // namespace consumer +} // namespace perfetto + +namespace iorap::perfetto { + +PerfettoDependencies::Component PerfettoDependencies::CreateComponent() { + // TODO: read from config. + static const uint32_t kTraceDurationMs = + ::android::base::GetUintProperty("iorapd.perfetto.trace_duration_ms", /*default*/5000U); + + static const uint32_t kBufferSize = + ::android::base::GetUintProperty("iorapd.perfetto.buffer_size", /*default*/4096U); + + return fruit::createComponent() + .bind<PerfettoConsumer, PerfettoConsumerImpl>() + .registerProvider([]() /* -> TraceConfig */ { + return CreateConfig(kTraceDurationMs, + /*deferred_start*/true, + kBufferSize); + }); +} + +::perfetto::protos::TraceConfig PerfettoDependencies::CreateConfig(uint32_t duration_ms, + bool deferred_start, + uint32_t buffer_size) { + ::perfetto::protos::TraceConfig trace_config; + + trace_config.set_duration_ms(duration_ms); + trace_config.add_buffers()->set_size_kb(buffer_size); + trace_config.set_deferred_start(deferred_start); + + auto* ds_config = trace_config.add_data_sources()->mutable_config(); + ds_config->set_name("linux.ftrace"); + ds_config->mutable_ftrace_config()->add_ftrace_events( + "mm_filemap_add_to_page_cache"); + ds_config->mutable_ftrace_config()->add_ftrace_events( + "mm_filemap_delete_from_page_cache"); + ds_config->set_target_buffer(0); + + return trace_config; +} + +// RAII-style wrapper around a perfetto handle that calls Destroy +// in a thread-safe manner. +struct PerfettoConsumerHandle { + private: + std::shared_ptr<PerfettoConsumer> consumer_; + PerfettoConsumer::Handle handle_; + + public: + // Takes over ownership of the 'handle'. + // + // Consumer must not be null. + PerfettoConsumerHandle(std::shared_ptr<PerfettoConsumer> consumer, + PerfettoConsumer::Handle handle) + : consumer_{std::move(consumer)}, + handle_{std::move(handle)} { + DCHECK(consumer_ != nullptr); + } + + std::shared_ptr<PerfettoConsumer> GetConsumer() const { + return consumer_; + } + + PerfettoConsumer::Handle GetHandle() const { + return handle_; + } + + ~PerfettoConsumerHandle() { + LOG(VERBOSE) << "PerfettoConsumerHandle::Destroy(" << handle_ << ")"; + consumer_->Destroy(handle_); + } + + bool operator==(const PerfettoConsumerHandle& other) const { + return handle_ == other.handle_ && consumer_ == other.consumer_; + } + + bool operator!=(const PerfettoConsumerHandle& other) const { + return !(*this == other); + } +}; + + +// Snapshot of a single perfetto OnStateChanged callback. +// +// Operate on the PerfettoConsumer to further change the state. +// +// The Handle is kept 'valid' until all references to the PerfettoConsumerHandle +// are dropped to 0. This ensures the Handle is not destroyed too early. All +// direct usages of 'Handle' must be scoped by the PerfettoConsumerHandle. +struct PerfettoStateChange { + public: + using State = ::perfetto::consumer::State; + using Handle = ::perfetto::consumer::Handle; + + State state; // Never invalid. + std::shared_ptr<PerfettoConsumerHandle> perfetto_consumer_and_handle; // Never null. + + // Safety: Use only within scope of the PerfettoStateChange. + Handle GetHandle() const { + // TODO: it would be even safer to wrap all the calls to the handle inside a class, + // instead of exposing this raw Handle. + return perfetto_consumer_and_handle->GetHandle(); + } + + std::shared_ptr<PerfettoConsumer> GetConsumer() const { + return perfetto_consumer_and_handle->GetConsumer(); + } +}; + +std::ostream& operator<<(std::ostream& os, const PerfettoStateChange& state_change) { + os << "PerfettoStateChange{" << state_change.state << "," + << state_change.GetHandle() << "," + << state_change.GetConsumer().get() << "}"; + return os; +} + +// Once created, this acts as a hot observable, emitting 'PerfettoStateChange' transition items. +// Only the 'state' will vary, the handle and perfetto_consumer are always the same value. +// +// Clients only need to handle the success states in #on_next, all failure states will go to +// #on_error. +// +// Upon reaching the appropriate terminal states, either #on_completed or #on_error is called. +// No future callbacks will then occur, so this object should be subsequently deleted. +// +// The Handle is destroyed automatically after the last item is emitted, so it must only be +// manipulated from the #on_next callbacks. Do not save the Handle and use it at other times. +class StateChangedSubject { + public: + using State = ::perfetto::consumer::State; + using Handle = ::perfetto::consumer::Handle; + + StateChangedSubject(const ::perfetto::protos::TraceConfig& trace_config, + rxcpp::subscriber<PerfettoStateChange> destination, + std::shared_ptr<PerfettoConsumer> perfetto_consumer) + : deferred_start(trace_config.deferred_start()), + dest(std::move(destination)), + perfetto_consumer_(std::move(perfetto_consumer)) { + DCHECK(perfetto_consumer_ != nullptr); + } + + private: + struct StateChangedError : public std::runtime_error { + explicit StateChangedError(const std::string& what_arg) : std::runtime_error(what_arg) {} + }; + + std::shared_ptr<PerfettoConsumerHandle> handle_; // non-null after bound_ == true. + std::atomic<bool> bound_{false}; // synchronize-with for BindHandle -> OnStateChanged. + + State last_state{State::kIdle}; + bool deferred_start{false}; + + rxcpp::subscriber<PerfettoStateChange> dest; + std::shared_ptr<PerfettoConsumer> perfetto_consumer_; // This is never null. + + void DcheckBadStateTransition(State state, bool fail_unless = false) const { + DCHECK(fail_unless) << "Invalid state transition to " << state << " from " << last_state; + } + + void DcheckValidStateTransition(State state) { + // State must not be out of range. + DCHECK_GE(state, State::kTraceFailed); + DCHECK_LE(state, State::kTraceEnded); + + // Internal state that should never leak out into public perfetto API: + DCHECK_NE(state, State::kIdle); + // These can only be returned by PollState: + DCHECK_NE(state, State::kSessionNotFound); + + // Validate state transitions as per the perfetto API contract. + // See the 'state diagram' in consumer_api.h + switch (last_state) { + case State::kTraceFailed: // Final and unrecoverable. + // b/122548195: this can transition to 'kConnectionError' if selinux is disabled. + if (state == State::kConnectionError) { + LOG(WARNING) << "b/122548195: kTraceFailed is non-terminal, ignoring."; + // This is a bit awkward: rxcpp will drop the #on_error calls if its more than once. + break; + } + DcheckBadStateTransition(state); + break; + case State::kConnectionError: // Final and unrecoverable. + DcheckBadStateTransition(state); + break; + case State::kSessionNotFound: + DcheckBadStateTransition(state); + break; + case State::kIdle: + // OK: we initialized our own state to idle prior to the first callback. + break; + case State::kConnecting: + switch (state) { + case State::kConfigured: + // kConfigured, if |deferred_start| == true in the trace config. + DcheckBadStateTransition(state, deferred_start); + break; + case State::kTracing: + // kTracing, if |deferred_start| == false. + DcheckBadStateTransition(state, !deferred_start); + break; + case State::kConnectionError: + // An error state, e.g. if cannot reach the traced daemon. + break; + default: + // Unconditionally invalid state transitions from kConnecting to anything else. + DcheckBadStateTransition(state); + } + break; + case State::kConfigured: + DCHECK(deferred_start); + if (state != State::kTracing // OK: this is documented. + && state != State::kTraceFailed) { // Undocumented selinux failure. + // Undocumented, but it appears to go directly from Configured->TraceEnded + // it can also go to kTraceFailed if e.g. there's an selinux violation + // however this appears to be underdocumented. + // b/122607276 #2 + + if (state != State::kTraceEnded) { // b/122607276 #1 + DcheckBadStateTransition(state); + } + } + break; + case State::kTracing: + switch (state) { + case State::kTraceEnded: + break; + case State::kTraceFailed: + break; + default: + DcheckBadStateTransition(state); + } + break; + case State::kTraceEnded: + // Cannot transition from terminal state to another state. + DcheckBadStateTransition(state); + break; + + // default: This list is exhaustive + } + } + + constexpr bool IsTerminalState() const { + switch (last_state) { + case State::kTraceFailed: + case State::kConnectionError: + case State::kTraceEnded: + return true; + default: + return false; + } + } + + // Returns true for non-terminal states (i.e. this callback will be invoked again). + // Returns false otherwise. + bool OnStateChanged(Handle handle, State state) { + using namespace ::perfetto::consumer; + + // Block until 'BoundHandle' is called by the other thread. + while (!bound_.load()) {} // seq_cst acquire. + + std::shared_ptr<PerfettoConsumerHandle> handle_ptr = handle_; + DCHECK(handle_ptr != nullptr); + + DCHECK_EQ(handle_ptr->GetHandle(), handle); + DcheckValidStateTransition(state); + + switch (state) { + // Error states (terminal). + case State::kTraceFailed: + EmitError("kTraceFailed"); + break; + case State::kConnectionError: + EmitError("kConnectionError"); + break; + + // Regular transitions (non-terminal). + case State::kConnecting: + case State::kConfigured: + case State::kTracing: + EmitNext(state); + break; + // Regular transitions (terminal). + case State::kTraceEnded: // XX: do we even need to emit the 'TraceEnded' state? + EmitNext(state); + dest.on_completed(); + break; + default: + DcheckBadStateTransition(state); + } + + bool force_non_terminal = false; + + if (last_state == State::kConfigured && state == State::kConnectionError) { + // b/122548195: this can transition to 'kConnectionError' if selinux is disabled. + force_non_terminal = true; + // This function must 'return true' in this buggy case, otherwise we will + // call the destructor too early and subsequent callbacks will crash. + } + + // Remember the state to validate prior state transitions. + last_state = state; + + // The owner of this class should avoid leaking memory once we reach a terminal state. + return !IsTerminalState() || force_non_terminal; + } + + public: + // Thread safety: Called by main thread, terminates the rx stream. + // When this function is invoked, no calls to this class from other threads can occur. + void OnCreateFailed() { + // returned when an invalid handle is passed to PollState(). + last_state = State::kSessionNotFound; + EmitError("Create returned kInvalidHandle"); + } + + // Thread safety: Called by main thread, this could be concurrent to + // 'CallbackOnStateChanged'. + void BindHandle(const std::shared_ptr<PerfettoConsumerHandle>& handle) { + handle_ = handle; + + // Unblock OnStateChanged. + bound_.store(true); // seq_cst release. + } + + // Thread safety: Called by libperfetto background thread (same one every time). + static void CallbackOnStateChanged(Handle handle, State state, void* callback_arg) { + LOG(VERBOSE) << "CallbackOnStateChanged(handle=" << handle << ",state=" << state + << ",callback_arg=" << callback_arg << ")"; + + // Validate OnStateChanged callback invariants, guaranteed by libperfetto. + DCHECK_NE(handle, ::perfetto::consumer::kInvalidHandle); + + // Note: Perfetto guarantees this callback always occurs on the same thread, + // so we don't need to do any extra thread synchronization here since we are only mutating + // StateChangedSubject from within this function. + + // TODO: the memory ordering guarantees should be explicitly specified in consumer_api.h: + // This isn't specific enough: + // "The callback will be invoked on an internal thread and must not block." + // However looking at the implementation it posts onto a single-thread task runner, + // so this must be the case. + + StateChangedSubject* state_subject = reinterpret_cast<StateChangedSubject*>(callback_arg); + // This current thread owns 'StateChangedSubject', no other threads must access it. + // Explicit synchronization is not necessary. + + if (!state_subject->OnStateChanged(handle, state)) { + // Clean up the state tracker when we reach a terminal state. + // This means that no future callbacks will occur anymore. + delete state_subject; + } + } + + private: + void EmitError(const std::string& msg) { + // Sidenote: Exact error class does not matter, rxcpp only lets us access the error + // as a string (rxcpp::util::what). + // + // Either way, the recovery strategy is identical (log then try and restart). + dest.on_error(rxcpp::util::make_error_ptr(StateChangedError{msg})); + } + + void EmitNext(State state) { + if (WOULD_LOG(VERBOSE) && !dest.is_subscribed()) { + // This is purely for logging: #on_next already filters out items after unsubscription. + LOG(VERBOSE) << "StateChangedSubject#EmitNext(" << state << ") - drop due to unsubscribe"; + } + + auto handle_ptr = handle_; + DCHECK(handle_ptr != nullptr); + + // Non-null guarantee for the items emitted into this stream. + PerfettoStateChange state_change{state, handle_ptr}; + dest.on_next(std::move(state_change)); + } + + // TODO: inherit from rx subject and handle #unsubscribe explicitly, instead + // of just being subject-like? +}; + +// Note: The states will be emitted on a separate thread, so e.g. #as_blocking() +// needs to be used to avoid dropping everything on the floor. +// +// Important: The #on_error case must be handled explicitly by the observable, +// because the default behavior is to 'throw' which will cause an std::terminate with -fno-except. +static auto /*[observable<State>, shared_ptr<PerfettoConsumerHandle>]*/ + CreatePerfettoStateStream(::perfetto::protos::TraceConfig perfetto_config, + std::shared_ptr<PerfettoConsumer> perfetto_consumer) { + auto obs = rxcpp::observable<>::create<PerfettoStateChange>( + [perfetto_config = std::move(perfetto_config), perfetto_consumer = std::move(perfetto_consumer)] + (rxcpp::subscriber<PerfettoStateChange> subscriber) { + std::unique_ptr<StateChangedSubject> state_subject{ + new StateChangedSubject{perfetto_config, subscriber, perfetto_consumer}}; + + // Perfetto API requires a pointer to a serialized protobuf, it doesn't accept + // the code-generated object. + std::string perfetto_config_str = perfetto_config.SerializeAsString(); + + ::perfetto::consumer::Handle handle = + perfetto_consumer->Create(perfetto_config_str.data(), + perfetto_config_str.size(), + // executes on the same background thread repeatedly. + &StateChangedSubject::CallbackOnStateChanged, + // inter-thread-move + reinterpret_cast<void*>(state_subject.get())); + // perfetto::consumer::Create synchronizes-with OnStateChanged callback, this means + // we don't need to explicitly synchronize state_subject here so long as we don't access + // it on this thread again. + LOG(DEBUG) << "Create Perfetto handle " << handle; + + if (handle == ::perfetto::consumer::kInvalidHandle) { + LOG(ERROR) << "Failed to create Perfetto handle"; + // No callbacks will occur, so our thread still owns the state subject. + state_subject->OnCreateFailed(); + return; + } + + std::shared_ptr<PerfettoConsumerHandle> safe_handle{ + new PerfettoConsumerHandle{perfetto_consumer, handle}}; + + // Share ownership of the Handle with the StateSubject. + // This way we defer calling 'Destroy' until the callback reaches a terminal state + // *and* all users of the stream are done with the handle. + state_subject->BindHandle(safe_handle); + + // state_subject ownership is taken over by OnStateChanged. + // It will also be touched in a separate thread, so we must never access it here again. + state_subject.release(); + + // 'subscriber#add' is actually a call to register an on_unsubscribe listener. + subscriber.add([safe_handle]() { + LOG(VERBOSE) << "PerfettoStateChange#unsubscribe"; + + // Release our ref-count to the handle. + // safe_handle.reset(); // This happens implicitly. + + // TODO: I think this won't handle the case where we need to shut down early. + // Need to use the explicit kShutdown for that? + }); + + // TODO: this would be an excellent place to shuffle the perfetto config protobuf + // into a global debug state for dumpsys. + }); + + return obs; +} + +template <typename T> +bool BinaryWireProtobuf<T>::WriteFullyToFile(const std::string& path, + bool follow_symlinks) const { + // TODO: it would be great if android::base had a string_view overload to avoid copying + // data into an std::string. + + // u g o + // rw-rw---- + // + // Protobufs can be read/written but not executed. + static constexpr const mode_t kMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + + 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, kMode))); + + if (fd == -1) { + PLOG(ERROR) << "BinaryWireProtobuf::WriteFullyToFile open failed"; + return false; + } + + if (!::android::base::WriteFully(fd, data_.data(), size())) { + PLOG(ERROR) << "BinaryWireProtobuf::WriteFullyToFile write failed"; + return CleanUpAfterFailedWrite(path); + } + + return true; +} + +template <typename T> +bool BinaryWireProtobuf<T>::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; +} + +template <typename T> +bool BinaryWireProtobuf<T>::WriteStringToFd(int fd) const { + const char* p = reinterpret_cast<const char*>(data_.data()); + size_t left = size(); + while (left > 0) { + ssize_t n = TEMP_FAILURE_RETRY(write(fd, p, left)); + if (n == -1) { + return false; + } + p += n; + left -= n; + } + return true; +} + +// explicit template instantiation. +template struct BinaryWireProtobuf<::google::protobuf::MessageLite>; +// TODO: refactor this not to need the template instantiation. + +#if defined(__ANDROID__) +// Copy of the 2.6.18 kernel header (linux/ioprio.h) + +#define IOPRIO_WHO_PROCESS (1) +#define IOPRIO_CLASS_IDLE (3) + +#define IOPRIO_BITS (16) +#define IOPRIO_CLASS_SHIFT (13) +#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) + +#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) +#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) +#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) +#endif + +static int ioprio_get(int which, int who) { + return syscall(SYS_ioprio_get, which, who); +} + +static int ioprio_set(int which, int who, int ioprio) { + return syscall(SYS_ioprio_set, which, who, ioprio); +} + +// An rx Coordination, which will cause a new thread to spawn for each new Worker. +// +// Idle-class priority is set for the CPU and IO priorities on the new thread. +rxcpp::observe_on_one_worker ObserveOnNewIoThread() { + // IO thread factory for idle-priority threads. + // Both the CPU scheduler and the IO scheduler are set to idle. + // + // Use this when needing to schedule disk access from a normal-priority thread onto a + // very low priority thread, but not so low that we need to use a BackgroundJobScheduler. + struct io_thread_factory { + std::thread operator()(std::function<void()> start) const { + return std::thread{ + [start=std::move(start)]() { + // Set IO priority to idle. + do { + int value = ioprio_get(IOPRIO_WHO_PROCESS, /*pid*/0); + if (value == -1) { + PLOG(ERROR) << "io_thread_factory failed ioprio_get"; + break; // Can't set the ioprio, we don't know what data to use. + } + + int data = IOPRIO_PRIO_DATA(value); // priority level + // This appears to be '4' in practice. We may want to raise to + // be the highest-priority within the idle class. + + // idle scheduling class. only access disk when nobody else needs disk. + int res = ioprio_set(IOPRIO_WHO_PROCESS, + /*pid*/0, + IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, data)); + if (res < 0) { + PLOG(ERROR) << "io_thread_factory failed ioprio_set"; + break; + } + + // Changing the IO priority only has any effect with cfq scheduler: + // $> cat /sys/block/sda/queue/scheduler + LOG(VERBOSE) << "ioprio_set(WHO_PROCESS, class=IDLE, data=" << data << ")"; + } while (false); + + // Set CPU priority to idle. + do { + struct sched_param param{}; + param.sched_priority = 0; // Required to be statically 0 when used with SCHED_IDLE. + + if (sched_setscheduler(/*pid*/0, // current thread, + SCHED_IDLE, + /*in*/¶m) != 0) { + PLOG(ERROR) << "io_thread_factory failed sched_setscheduler"; + break; + } + + LOG(VERBOSE) << "sched_setscheduler(self, IDLE)"; + } while (false); + + // XX: if changing the scheduling is too aggressive (i.e. it causes starvation), + // we may want to stick with the default class and change the nice (priority) levels + // to the minimum. + + // TODO: future work, maybe use cgroups configuration file instead? + + // Call the rxcpp-supplied code. + start(); + } + }; + } + }; + + static rxcpp::schedulers::scheduler thread_scheduler = + rxcpp::schedulers::make_new_thread(io_thread_factory{}); + + static rxcpp::observe_on_one_worker observe_on_io_thread{thread_scheduler}; + + return observe_on_io_thread; +} + +static auto/*observable<PerfettoTraceProto>*/ + CreatePerfettoStream(rxcpp::observable<PerfettoStreamCommand> input, + std::shared_ptr<PerfettoConsumer> perfetto_consumer, + const ::perfetto::protos::TraceConfig& trace_config) { + // XX: should I also take a scheduler for input here??? + + auto /*observable<PerfettoStateChange>*/ perfetto_states = + CreatePerfettoStateStream(trace_config, perfetto_consumer); + + using State = ::perfetto::consumer::State; + + auto/*coordinator*/ serialize_coordinator = rxcpp::observe_on_new_thread(); + // Rx note: + // The optimal thing to do would be to have a lock/unlock for an entire subset of a chain. + // This would avoid creating new threads, and could also be used to intentionally block + // the regular C-callback perfetto thread. + // + // It seems possible to create a coordinator to lock a single operator in a chain, but this + // appears to be unsound. In particular, it doesn't even make life any simpler below because + // it would only apply the synchronization to 'zip' but not 'flat_map' which is unsound. + // + // There is also the built-in 'serialize_new_thread' which seems to create a new thread but + // then never actually uses it, that seems unfortunate and wasteful. + // + // Instead, do the simple thing which is create a new thread and always queue on there. + // Execution an action on that worker is itself unsynchronized, but this doesn't matter since + // the worker is only backed by 1 thread (no 2 schedulables can be executed concurrently + // on the 'observe_new_thread' worker). + return input + .tap([](PerfettoStreamCommand command) { + LOG(VERBOSE) << "CreatePerfettoStreamCommand#tap(command=" << command << ")"; + }) + // Input A, thread tA. Input B, thread tB. Continue execution with (A,B) on thread tC. + .zip(serialize_coordinator, // rest of chain is also executed on the same thread. + perfetto_states) + // Note: zip terminates when either of the streams complete. + .flat_map( + [](std::tuple<PerfettoStreamCommand, PerfettoStateChange> p) { + auto& [command, state_change] = p; + LOG(VERBOSE) << "CreatePerfettoStream#combine(" + << command << "," << state_change << ")"; + if (command == PerfettoStreamCommand::kShutdown) { + // Perfetto: Always safe to call ::perfetto::consumer::Destroy + // at any time. + // + // XX: How do we clean up the StateChangedSubject without racing + // against the callback? It strikes me that we may need a 'kDestroyed' + // state that perfetto can transition to from kConfigured. + LOG(VERBOSE) << "Call Perfetto_Consumer->Destroy"; + state_change.GetConsumer()->Destroy(state_change.GetHandle()); + + // XX: Do we even have any guarantees about not getting more callbacks? + // We could just say 'there can still be spurious output after Shutdown' + // and just ignore it (e.g. Shutdown and immediately unsubscribe). + } else if (command == PerfettoStreamCommand::kStartTracing + && state_change.state == State::kConfigured) { + LOG(VERBOSE) << "Call Perfetto_Consumer->StartTracing"; + state_change.GetConsumer()->StartTracing(state_change.GetHandle()); + } else if (command == PerfettoStreamCommand::kStopTracing && + state_change.state == State::kTraceEnded) { + // TODO: if perfetto actually had a 'StopTracing' we could call that here. + // right now we just pretend it exists, but rely on the config timer instead. + ::perfetto::consumer::TraceBuffer trace_buffer = + state_change.GetConsumer()->ReadTrace(state_change.GetHandle()); + + LOG(VERBOSE) << "Perfetto Trace ended" + << ", addr=" << reinterpret_cast<void*>(trace_buffer.begin) + << ",size= " << trace_buffer.size; + + PerfettoTraceProto wire_proto{trace_buffer.begin, trace_buffer.size}; + return rxcpp::observable<>::just(std::move(wire_proto)).as_dynamic(); + } + return rxcpp::observable<>::empty<PerfettoTraceProto>().as_dynamic(); + } + ); +} + +std::ostream& operator<<(std::ostream& os, PerfettoStreamCommand c) { + switch (c) { + case PerfettoStreamCommand::kStartTracing: + os << "kStartTracing"; + break; + case PerfettoStreamCommand::kStopTracing: + os << "kStopTracing"; + break; + case PerfettoStreamCommand::kShutdown: + os << "kShutdown"; + break; + default: + os << "(unknown)"; + break; + } + return os; +} + +RxProducerFactory::RxProducerFactory(PerfettoDependencies::Injector& injector) + : injector_(injector) { +} + +// TODO: (fruit) maybe this could be streamlined further by avoiding this boilerplate? +rxcpp::observable<PerfettoTraceProto> RxProducerFactory::CreateTraceStream( + rxcpp::observable<PerfettoStreamCommand> commands) { + std::shared_ptr<PerfettoConsumer> perfetto_consumer = + injector_.get<std::shared_ptr<PerfettoConsumer>>(); + const ::perfetto::protos::TraceConfig& trace_config = + injector_.get<::perfetto::protos::TraceConfig>(); + + DCHECK(perfetto_consumer != nullptr); + DCHECK(reinterpret_cast<volatile const void*>(&trace_config) != nullptr); + + return CreatePerfettoStream(commands, + perfetto_consumer, + trace_config); +} + +// For testing/debugging only. +// +// Saves protobuf results in file name specified by 'arg_output_proto'. +void CollectPerfettoTraceBufferImmediately( + RxProducerFactory& producer_factory, + const std::string& arg_output_proto) { + LOG(VERBOSE) << "CollectPerfettoTraceBufferImmediately"; + + std::shared_ptr<PerfettoConsumer> perfetto_consumer = + producer_factory.injector_.get<std::shared_ptr<PerfettoConsumer>>(); + const ::perfetto::protos::TraceConfig& trace_config = + producer_factory.injector_.get<const ::perfetto::protos::TraceConfig&>(); + + auto /*observable<PerfettoStateChange>*/ perfetto_states = + CreatePerfettoStateStream(trace_config, perfetto_consumer); + + perfetto_states + .as_blocking() // Wait for observable to terminate with on_completed or on_error. + .subscribe(/*on_next*/[&](auto state_change) { + LOG(VERBOSE) << "Perfetto post-processed State change: " << state_change; + + using State = ::perfetto::consumer::State; + switch (state_change.state) { + case State::kConnecting: + LOG(VERBOSE) << "Perfetto Tracing is Connecting"; + // Transitional state. No-op. + break; + case State::kConfigured: + state_change.GetConsumer()->StartTracing(state_change.GetHandle()); + break; + case State::kTracing: + LOG(VERBOSE) << "Perfetto Tracing started"; + // Transitional state. No-op. + break; + case State::kTraceEnded: { + ::perfetto::consumer::TraceBuffer trace_buffer = + state_change.GetConsumer()->ReadTrace(state_change.GetHandle()); + + LOG(VERBOSE) << "Perfetto Trace ended" + << ", addr=" << reinterpret_cast<void*>(trace_buffer.begin) + << ",size= " << trace_buffer.size; + + if (!arg_output_proto.empty()) { + std::string trace_buffer_str; + trace_buffer_str.resize(trace_buffer.size); + std::copy(trace_buffer.begin, + trace_buffer.begin + trace_buffer.size, + trace_buffer_str.data()); + if (!android::base::WriteStringToFile(trace_buffer_str, arg_output_proto)) { + LOG(ERROR) << "Failed to save TraceBuffer to " << arg_output_proto; + } else { + LOG(INFO) << "TraceBuffer saved to file: " << arg_output_proto; + LOG(INFO); + LOG(INFO) << "To print this in a human readable form, execute these commands:"; + LOG(INFO) << "$> adb pull '" << arg_output_proto << "'"; + LOG(INFO) << "$> trace_to_text systrace <filename.pb>"; + } + } + + // TODO: something more useful with this TraceBuffer, such as saving it to a file + // and printing the output. + break; + } + default: + // No other states are possible, because they go to #on_error or cause a dcheck. + DCHECK(false) << "Invalid state: " << state_change; + } + + //INTENTIONAL_COMPILER_ERROR_HERE // lets make sure this code actually does a trace. + + }, /*on_error*/[](rxcpp::util::error_ptr err) { + LOG(ERROR) << "Perfetto post-processed state change failed: " << rxcpp::util::what(err); + }, /*on_completed*/[]() { + LOG(VERBOSE) << "Perfetto post-processed State #on_completed"; + }); +} + + +} // namespace iorap::perfetto diff --git a/src/perfetto/rx_producer.h b/src/perfetto/rx_producer.h new file mode 100644 index 0000000..f4c40b4 --- /dev/null +++ b/src/perfetto/rx_producer.h @@ -0,0 +1,165 @@ +// 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. + +#ifndef IORAP_SRC_PERFETTO_RX_PRODUCER_H_ +#define IORAP_SRC_PERFETTO_RX_PRODUCER_H_ + +#include "perfetto/perfetto_consumer.h" // libiorap + +#include <perfetto/config/trace_config.pb.h> // libperfetto +#include <rxcpp/rx.hpp> + +#include <iosfwd> +#include <functional> +#include <optional> +#include <vector> + +namespace iorap::perfetto { + +struct PerfettoDependencies { + using Component = + fruit::Component<PerfettoConsumer, ::perfetto::protos::TraceConfig>; + using Injector = + fruit::Injector<PerfettoConsumer, ::perfetto::protos::TraceConfig>; + using NormalizedComponent = + fruit::NormalizedComponent<PerfettoConsumer, ::perfetto::protos::TraceConfig>; + + // Create a 'live' component that will talk to perfetto via traced. + static Component CreateComponent(/*TODO: config params*/); + + // Create perfetto.protos.TraceConfig , serialized as a (machine-readable) string. + // + // The following ftrace events are enabled: + // * mm_filemap_add_to_page_cache + // * mm_filemap_delete_from_page_cache + // + // If deferred starting is also enabled, no tracing will begin until + // ::perfetto::consumer::StartTracing is invoked. + static ::perfetto::protos::TraceConfig CreateConfig(uint32_t duration_ms, + bool deferred_start = true, + uint32_t buffer_size = 4096); +}; + +// This acts as a lightweight type marker so that we know what data has actually +// encoded under the hood. +template <typename T> +struct BinaryWireProtobuf { + std::vector<std::byte>& data() { + return data_; + } + + const std::vector<std::byte>& data() const { + return data_; + } + + size_t size() const { + return data_.size(); + } + + explicit BinaryWireProtobuf(char* data, size_t size) + : BinaryWireProtobuf(reinterpret_cast<std::byte*>(data), size) { + } + + explicit BinaryWireProtobuf(std::byte* data, size_t size) { + data_.resize(size); + std::copy(data, + data + size, + data_.data()); + } + + // Important: Deserialization could fail, for example data is truncated or + // some minor disc corruption occurred. + template <typename U> + std::optional<U> MaybeUnserialize() { + U unencoded; + + if (!unencoded.ParseFromArray(data_.data(), data_.size())) { + return std::nullopt; + } + + return {std::move(unencoded)}; + } + + bool WriteFullyToFile(const std::string& path, + bool follow_symlinks = false) const; + + private: + static bool CleanUpAfterFailedWrite(const std::string& path); + bool WriteStringToFd(int fd) const; + + std::vector<std::byte> data_; +}; + +//using PerfettoTraceProto = BinaryWireProtobuf<::perfetto::protos::Trace>; +using PerfettoTraceProto = BinaryWireProtobuf<::google::protobuf::MessageLite>; + +enum class PerfettoStreamCommand { + kStartTracing, // -> () | on_error + kStopTracing, // -> on_next(PerfettoTraceProto) | on_error + kShutdown, // -> on_completed | on_error + // XX: should shutdown be converted to use the rx suscriber#unsubscribe instead? +}; + +std::ostream& operator<<(std::ostream& os, PerfettoStreamCommand c); + +struct RxProducerFactory { + // Passing anything by value leads to a lot of pain and headache. + // Pass in the injector by reference because nothing else seems to work. + explicit RxProducerFactory(PerfettoDependencies::Injector& injector); + + // Create a one-shot perfetto observable that will begin + // asynchronously producing a PerfettoTraceProto after the 'kStartTracing' + // command is observed. + // + // libperfetto is immediately primed (i.e. connected in a deferred state) + // upon calling this function, to reduce the latency of 'kStartTracing'. + // + // To finish the trace, push 'kStopTracing'. To cancel or tear down at any + // time, push 'kShutdown'. + // + // The TraceProto may come out at any time after 'kStartTracing', + // this is controlled by duration_ms in the TraceConfig. + // + // TODO: libperfetto should actually stop tracing when we ask it to, + // instead of using a hardcoded time. + // + // The observable may go into #on_error at any time, if the underlying + // libperfetto states transition to a failing state. + // This usually means the OS is not configured correctly. + rxcpp::observable<PerfettoTraceProto> CreateTraceStream( + rxcpp::observable<PerfettoStreamCommand> commands); + + // TODO: is this refactor-able into a subscriber factory that takes + // the commands-observable as a parameter? + + // TODO: infinite perfetto stream. + + private: + // XX: why doesn't this just let me pass in a regular Component? + PerfettoDependencies::Injector& injector_; + + friend void CollectPerfettoTraceBufferImmediately( + RxProducerFactory& producer_factory, + const std::string& arg_output_proto); +}; + +// An rx Coordination, which will cause a new thread to spawn for each new Worker. +// +// Idle-class priority is set for the CPU and IO priorities on the new thread. +// +// TODO: move to separate file +rxcpp::observe_on_one_worker ObserveOnNewIoThread(); + +} // namespace iorap::perfetto +#endif // IORAP_SRC_PERFETTO_RX_PRODUCER_H_ diff --git a/tests/src/binder/app_launch_event_test.cc b/tests/src/binder/app_launch_event_test.cc new file mode 100644 index 0000000..1d19bb1 --- /dev/null +++ b/tests/src/binder/app_launch_event_test.cc @@ -0,0 +1,202 @@ +/* + * 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 <binder/app_launch_event.h> + +#include <gtest/gtest.h> + +// TODO: move to app_launch_event.h +#include <google/protobuf/util/message_differencer.h> + +using namespace iorap::binder; // NOLINT + +using android::Parcel; + +using Type = AppLaunchEvent::Type; +using Temperature = AppLaunchEvent::Temperature; + +namespace iorap::binder { + +inline bool ProtosEqual(const ::google::protobuf::Message& lhs, + const ::google::protobuf::Message& rhs) { + return ::google::protobuf::util::MessageDifferencer::Equals(lhs, rhs); +} + +inline bool ProtosEqual(const ::google::protobuf::MessageLite& lhs, + const ::google::protobuf::MessageLite& rhs) { + // MessageLite does not support 'MessageDifferencer' which requires protobuf-full + // because it uses reflection. + // + // Serialize as a string and compare. This may lead to false inequality when protobufs + // are actually the same but their encodings are slightly different. + return lhs.GetTypeName() == rhs.GetTypeName() + && lhs.SerializeAsString() == rhs.SerializeAsString(); +} + +template <typename T> +inline bool ProtoPointersEqual(const T& lhs_ptr, const T& rhs_ptr) { + if (lhs_ptr == nullptr && rhs_ptr == nullptr) { + return true; + } + else if (lhs_ptr != nullptr && rhs_ptr != nullptr) { + return ProtosEqual(*lhs_ptr, *rhs_ptr); + } + return false; +} + +// Field-by-field equality. +// Protos are compared according by checking that their serialized encodings are the same. +inline bool operator==(const AppLaunchEvent& lhs, const AppLaunchEvent& rhs) { +# define EQ_OR_RETURN(l, r, val) if (!(l.val == r.val)) { return false; } +# define PROTO_EQ_OR_RETURN(l, r, val) if (!ProtoPointersEqual(l.val, r.val)) { return false; } + + EQ_OR_RETURN(lhs, rhs, type); + EQ_OR_RETURN(lhs, rhs, sequence_id); + PROTO_EQ_OR_RETURN(lhs, rhs, intent_proto); + EQ_OR_RETURN(lhs, rhs, temperature); + PROTO_EQ_OR_RETURN(lhs, rhs, activity_record_proto); + +# undef EQ_OR_RETURN +# undef PROTO_EQ_OR_RETURN + + return true; +} + +inline bool operator!=(const AppLaunchEvent& lhs, const AppLaunchEvent& rhs) { + return !(lhs == rhs); +} + +static AppLaunchEvent MakeIntentStarted(int64_t sequence_id, + // non-null + std::unique_ptr<IntentProto> intent_proto) { + DCHECK(intent_proto != nullptr); + + AppLaunchEvent e{Type::kIntentStarted, sequence_id, std::move(intent_proto)}; + return e; +} + +static AppLaunchEvent MakeIntentFailed(int64_t sequence_id) { + AppLaunchEvent e{Type::kIntentFailed, sequence_id}; + return e; +} + +static AppLaunchEvent +MakeActivityLaunched(int64_t sequence_id, + Temperature temperature, + // non-null + std::unique_ptr<ActivityRecordProto> activity_record_proto) { + DCHECK(activity_record_proto != nullptr); + + AppLaunchEvent e{Type::kActivityLaunched, + sequence_id, + /*intent_proto*/nullptr, + temperature, + std::move(activity_record_proto)}; + return e; +} + +static AppLaunchEvent +MakeActivityLaunchCancelled(int64_t sequence_id, + // nullable + std::unique_ptr<ActivityRecordProto> activity_record_proto = nullptr) { + AppLaunchEvent e{Type::kActivityLaunchCancelled, + sequence_id, + /*intent_proto*/nullptr, + Temperature::kUninitialized, + std::move(activity_record_proto)}; + return e; +} + +static AppLaunchEvent +MakeActivityLaunchFinished(int64_t sequence_id, + // non-null + std::unique_ptr<ActivityRecordProto> activity_record_proto) { + DCHECK(activity_record_proto != nullptr); + AppLaunchEvent e{Type::kActivityLaunchFinished, + sequence_id, + /*intent_proto*/nullptr, + Temperature::kUninitialized, + std::move(activity_record_proto)}; + return e; +} + +} // namespace iorap::binder + +auto MakeDummyIntent(std::string action = "package_name/.ClassName") { + std::unique_ptr<IntentProto> dummy_intent{new IntentProto{}}; + dummy_intent->set_action(action); + return dummy_intent; +} + +auto MakeDummyActivityRecord(std::string title = "package_name/.ClassName") { + std::unique_ptr<ActivityRecordProto> dummy{new ActivityRecordProto{}}; + + dummy->mutable_identifier()->set_title(title); + + return dummy; +} + +TEST(AppLaunchEventTest, Equals) { + EXPECT_EQ(MakeIntentStarted(456, MakeDummyIntent()), MakeIntentStarted(456, MakeDummyIntent())); + EXPECT_NE(MakeIntentStarted(45, MakeDummyIntent()), MakeIntentStarted(45, MakeDummyIntent("a"))); + + EXPECT_EQ(MakeIntentFailed(123), MakeIntentFailed(123)); + EXPECT_NE(MakeIntentFailed(0), MakeIntentFailed(123)); + + EXPECT_EQ((MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord())), + (MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord()))); + EXPECT_NE((MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord())), + (MakeActivityLaunched(3, Temperature::kCold, MakeDummyActivityRecord()))); + EXPECT_NE((MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord())), + (MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord("other title")))); + + EXPECT_EQ((MakeActivityLaunchCancelled(4)), + (MakeActivityLaunchCancelled(4))); + EXPECT_EQ((MakeActivityLaunchCancelled(4, MakeDummyActivityRecord())), + (MakeActivityLaunchCancelled(4, MakeDummyActivityRecord()))); + EXPECT_NE((MakeActivityLaunchCancelled(4, MakeDummyActivityRecord())), + (MakeActivityLaunchCancelled(4, MakeDummyActivityRecord("other")))); + EXPECT_NE((MakeActivityLaunchCancelled(4, MakeDummyActivityRecord())), + (MakeActivityLaunchCancelled(4))); + + EXPECT_EQ((MakeActivityLaunchFinished(5, MakeDummyActivityRecord())), + (MakeActivityLaunchFinished(5, MakeDummyActivityRecord()))); + EXPECT_NE((MakeActivityLaunchFinished(5, MakeDummyActivityRecord())), + (MakeActivityLaunchFinished(5, MakeDummyActivityRecord("other title")))); +} + +template <typename T> +T ValueParcelRoundTrip(const T& value) { + ::android::Parcel p; + CHECK_EQ(value.writeToParcel(&p), ::android::NO_ERROR); + + T new_value; + p.setDataPosition(0); + CHECK_EQ(new_value.readFromParcel(&p), ::android::NO_ERROR); + + return new_value; +} + +#define EXPECT_PARCELING_ROUND_TRIP(a) EXPECT_EQ((a), ValueParcelRoundTrip((a))) + +TEST(AppLaunchEventTest, ParcelingRoundTrip) { + EXPECT_PARCELING_ROUND_TRIP(MakeIntentStarted(456, MakeDummyIntent())); + EXPECT_PARCELING_ROUND_TRIP(MakeIntentFailed(123)); + EXPECT_PARCELING_ROUND_TRIP(MakeActivityLaunched(3, Temperature::kHot, MakeDummyActivityRecord())); + EXPECT_PARCELING_ROUND_TRIP(MakeActivityLaunchCancelled(4)); + EXPECT_PARCELING_ROUND_TRIP(MakeActivityLaunchCancelled(4, MakeDummyActivityRecord())); + EXPECT_PARCELING_ROUND_TRIP(MakeActivityLaunchFinished(5, MakeDummyActivityRecord())); +} diff --git a/tests/src/inode2filename/search_directories_test.cc b/tests/src/inode2filename/search_directories_test.cc new file mode 100644 index 0000000..d9903d6 --- /dev/null +++ b/tests/src/inode2filename/search_directories_test.cc @@ -0,0 +1,2819 @@ +/* + * 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 "inode2filename/search_directories.h" +#include "inode2filename/system_call.h" + +#include <android-base/logging.h> +#include <android-base/strings.h> + +#include <fruit/fruit.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <optional> + +#include <sys/sysmacros.h> + + +// Set this to 1 when debugging by hand to get more output. +// Otherwise the spam might be too much when most tests are failing. +#define LOG_WITH_VERBOSE 0 +// Set this to 1 when debugging by hand to have the logging output go to stderr. +// TODO: I think the automated test bots have problems capturing non-logcat output. +#define LOG_TO_STDERR 1 + +// TODO: Might be nice to have these controlled by command line. + +using namespace std::literals::string_literals; // NOLINT +using namespace std::literals::string_view_literals; // NOLINT +using namespace iorap::inode2filename; // NOLINT +using namespace testing; // NOLINT + +static void ConfigureLogging() { + if (LOG_TO_STDERR) { + ::android::base::SetLogger(::android::base::StderrLogger); + } + if (LOG_WITH_VERBOSE) { + ::android::base::SetMinimumLogSeverity(::android::base::VERBOSE); + } else { + ::android::base::SetMinimumLogSeverity(::android::base::DEBUG); + } +} + +// Iterate substrings in 'what' that are separated by 'separator'. +// Should be similar to the python 'str.split' behavior. +// +// Empty separators will have 0 iterations. +// +// NOTE: this could end up returning empty strings, e.g. '/'.split('/') -> ('', '') +// Think of it more like splitting on "$/^" except the $ and ^ become empty strings in the end. +// +// Zero-copy guarantee (and no dynamic allocation). +struct StringSplit { + struct SplitIterable; + + // Return a 0-length substring whose address range is one past the end of 'what'. + // Logically equivalent to a "", but its real address will be within 'what'. + // + // Repeatedly applying this function on itself will return the same value. + // + // Do not use operator[] on the returned substring, as that would cause undefined + // behavior. + // + // To safely access the pointer, use #data(). The pointer must not be dereferenced, + // which would cause undefined behavior. + static constexpr std::string_view EmptySubstringAtEnd(std::string_view what) { + return what.substr(/*pos*/what.size(), /*count*/0); + } + + // Create an Iterable that will iterate over substrings in 'what' separated by 'separator'. + // + // Each such 'value' emitted is guaranteed to be: + // - a substring of 'what' + // - not have any 'separator' substrings + // - the address range of 'value' is within the address range of 'what' (or one-past-the-end) + // + // For example: + // + // for (std::string_view substr : StringSplit("hello/world"sv, "/"sv)) { + // ... // loop 0: substr == "hello" + // ... // loop 1: substr == "world" + // } + static constexpr SplitIterable Iterable(std::string_view what, + std::string_view separator) { + return SplitIterable{what, separator}; + } + + // Implement LegacyForwardIterator concept. + struct SplitIterator { + using value_type = std::string_view; + using reference = value_type&; + using pointer = value_type*; + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; // required by concept, but its meaningless. + + constexpr bool operator==(const SplitIterator& other) const { + if (state != other.state) { + return false; + } + switch (state) { + case kNormal: + case kNearEnd: + return current_split.data() == other.current_split.data(); + case kAtEnd: + return true; + } + } + + constexpr bool operator!=(const SplitIterator& other) const { + return !(*this == other); + } + + constexpr std::string_view& operator*() { + DCHECK(state != kAtEnd) << "Undefined behavior to dereference end() iterators"; + return current_split; + } + + constexpr std::string_view* operator->() { + DCHECK(state != kAtEnd) << "Undefined behavior to dereference end() iterators"; + return ¤t_split; + } + + /* + constexpr const std::string_view& operator*() const { + return current_split; + } + + constexpr const std::string_view* operator->() const { + return ¤t_split; + } + */ + + constexpr SplitIterator& operator++() { + UpdateValues(); + return *this; + } + + constexpr SplitIterator operator++(int) { + SplitIterator copy{*this}; + ++(*this); + return copy; + } + + private: + // Avoid defining constructors etc. We get the default constructors and operator= then. + + friend struct SplitIterable; // Use below Make functions. + + constexpr static SplitIterator MakeBegin(std::string_view whole, std::string_view separator) { + SplitIterator it; + it.state = kNormal; + + if (separator == "") { + it.rest_of_string = StringSplit::EmptySubstringAtEnd(whole); + // point to one past-the end (which is legal), also equivalent to "" + // the difference being that the address range is guaranteed to be within 'whole' + // actually any 0-length subrange would be appropriate here, but just go with the 'end' + // because dereferencing it would be obviously bad. + it.state = kAtEnd; + // Empty separator -> empty # of visits. This seems the most composable. + // Note: Need to handle this case especially since find_first_of("") would return the + // entire string. + } else { + it.rest_of_string = whole; + it.separator = separator; + it.UpdateValues(); + } + return it; + } + + constexpr static SplitIterator MakeEnd() { + SplitIterator it; + it.state = kAtEnd; + return it; + } + + constexpr void UpdateValues() { + switch (state) { + case kNormal: + break; + case kNearEnd: + // Address of emitted value is always within subrange of 'whole'. + current_split = StringSplit::EmptySubstringAtEnd(rest_of_string); + state = kAtEnd; + return; + case kAtEnd: + // Incrementing the 'end()' operator is undefined behavior. + DCHECK(false) << "Undefined behavior: Cannot increment end() iterator."; + return; + } + + DCHECK(state == kNormal); + + size_t pos = rest_of_string.find_first_of(separator); + if (std::string_view::npos == pos) { + // Always visit at least once for non-empty separators, even if the string is empty. + + current_split = rest_of_string; + // Address of emitted value is always within subrange of 'whole'. + rest_of_string = rest_of_string.substr(/*pos*/0, /*count*/0); // = "" + state = kNearEnd; + } else { + // includes the starting position of the needle + // e.g. "+x-".find_first_of('x') -> 1 + + // current_split = rest_of_string[0..pos) + current_split = rest_of_string.substr(/*pos*/0, pos); + + // strip '${left}${separator}' from the left hand side. + // continue iterating. + rest_of_string = rest_of_string.substr(pos + separator.size()); + } + } + + public: + + void PrintToStream(std::ostream& os) const { + os << "SplitIterator{"; + os << "current_split:\"" << current_split << "\","; + os << "rest_of_string:\"" << rest_of_string << "\","; + os << "separator:\"" << separator << "\","; + os << "state:"; + switch (state) { + case kNormal: + os << "kNormal"; + break; + case kNearEnd: + os << "kNearEnd"; + break; + case kAtEnd: + os << "kAtEnd"; + break; + } + os << "}"; + } + private: + // Not intended to be used directly. + // Public visibility to avoid making extra constructors. + std::string_view current_split; + std::string_view rest_of_string; + std::string_view separator; + + enum State { + kNormal, + kNearEnd, + kAtEnd + }; + State state{kNormal}; + // This cannot have a field initializer due to a clang bug, + // https://bugs.llvm.org/show_bug.cgi?id=36684 + // So define an explicit constructor below. + + // This needs to go last: + // undefined constructor 'SplitIterator' cannot be used in a constant expression + // constexpr SplitIterator() : state{kNormal} {} + // constexpr SplitIterator() {} + }; + + friend struct SplitIterable; + + struct SplitIterable { + std::string_view whole; + std::string_view separator; + + constexpr SplitIterator begin() { + return SplitIterator::MakeBegin(whole, separator); + } + + constexpr SplitIterator end() { + return SplitIterator::MakeEnd(); + } + }; +}; + +std::ostream& operator<<(std::ostream& os, const StringSplit::SplitIterator& it) { + it.PrintToStream(os); + return os; +} + +static constexpr const StringSplit::SplitIterator kBlankSplit; + +// Visit substrings in 'what' that are separated by 'separator'. +// Should be similar to the python 'str.split' behavior. +// +// Empty separators will have 0 visits. +// +// 'f' is called back for each visit of a substring, this means there's 0 allocations here. +// +// NOTE: this could end up returning empty strings, e.g. '/'.split('/') -> ('', '') +// Think of it more like splitting on "$/^" except the $ and ^ become empty strings in the end. +// +// (Dynamic allocation free) +template <typename Fn> +static constexpr void VisitSplitStringView(std::string_view what, + std::string_view separator, + Fn f) { + // Empty separator -> empty # of visits. This seems the most composable. + if (separator == "") { + // Note: Need to handle this case especially since find_first_of("") would return the + // entire string. + return; + } + + size_t sep_length = separator.size(); + + do { + size_t pos = what.find_first_of(separator); + if (std::string_view::npos == pos) { + // Always visit at least once for non-empty separators, even if the string is empty. + f(what); + break; + } else { + // includes the starting position of the needle + // e.g. "+x-".find_first_of('x') -> 1 + + // left = what[0..pos) + std::string_view left_split = what.substr(/*pos*/0, pos); + f(left_split); + + // strip '${left}${separator}' from the left hand side. + // continue iterating. + what = what.substr(pos + sep_length); + } + } + while (true); +} + +std::vector<std::string> VisitSplitStringViewVec(std::string_view what, + std::string_view separator) { + std::vector<std::string> vec; + VisitSplitStringView(what, separator, [&vec](auto&& part) { + vec.push_back(std::string{part}); + }); + return vec; +} + +std::vector<std::string> IterableSplitStringViewVec(std::string_view what, + std::string_view separator) { + auto iterable = StringSplit::Iterable(what, separator); + std::vector<std::string> vec{iterable.begin(), iterable.end()}; + + return vec; +} + +TEST(SplitStringView, Tests) { + EXPECT_THAT(VisitSplitStringViewVec("", ""), IsEmpty()); + EXPECT_THAT(VisitSplitStringViewVec("abcdef", ""), IsEmpty()); + EXPECT_THAT(VisitSplitStringViewVec("", "/"), ElementsAre(""s)); + EXPECT_THAT(VisitSplitStringViewVec("/", "/"), ElementsAre(""s, ""s)); + EXPECT_THAT(VisitSplitStringViewVec("//", "/"), ElementsAre(""s, ""s, ""s)); + EXPECT_THAT(VisitSplitStringViewVec("/hello", "/"), ElementsAre(""s, "hello"s)); + EXPECT_THAT(VisitSplitStringViewVec("/hello/world", "/"), ElementsAre(""s, "hello"s, "world"s)); + EXPECT_THAT(VisitSplitStringViewVec("bar", "/"), ElementsAre("bar"s)); + EXPECT_THAT(VisitSplitStringViewVec("bar/baz", "/"), ElementsAre("bar"s, "baz"s)); + + EXPECT_THAT(IterableSplitStringViewVec("", ""), IsEmpty()); + EXPECT_THAT(IterableSplitStringViewVec("abcdef", ""), IsEmpty()); + EXPECT_THAT(IterableSplitStringViewVec("", "/"), ElementsAre(""s)); + EXPECT_THAT(IterableSplitStringViewVec("/", "/"), ElementsAre(""s, ""s)); + EXPECT_THAT(IterableSplitStringViewVec("//", "/"), ElementsAre(""s, ""s, ""s)); + EXPECT_THAT(IterableSplitStringViewVec("/hello", "/"), ElementsAre(""s, "hello"s)); + EXPECT_THAT(IterableSplitStringViewVec("/hello/world", "/"), ElementsAre(""s, "hello"s, "world"s)); + EXPECT_THAT(IterableSplitStringViewVec("bar", "/"), ElementsAre("bar"s)); + EXPECT_THAT(IterableSplitStringViewVec("bar/baz", "/"), ElementsAre("bar"s, "baz"s)); + + EXPECT_THAT(IterableSplitStringViewVec("/hello", "/"), ElementsAre(""sv, "hello"sv)); + EXPECT_THAT(IterableSplitStringViewVec("/hello///", "/"), ElementsAre(""sv, "hello"sv, ""sv, ""sv, ""sv)); + +} + + +// Allocation-free immutable path representation and manipulation. +// +// A PurePath is logically represented by its 'parts', which is a view of each component. +// +// Examples: +// parts('foo/bar') -> ['foo', 'bar'] +// parts('/bar') -> ['/', 'bar'] +// parts('') -> [] +// parts('.') -> [] +// parts('baz//') -> ['baz'] +// parts('hello/././world') -> ['hello', 'world'] +// parts('../down/../down2') -> ['..', 'down', '..', 'down2'] +// +// See also #VisitParts which allows an allocation-free traversal of the parts. +// +// Memory allocation/ownership guarantees: +// * Functions marked as 'constexpr' are guaranteed never to allocate (zero-copy). +// * Functions not marked as 'constexpr' and returning a PurePath will always return an object +// with its own internal copy of the underlying data (i.e. the memory is not borrowed). +struct PurePath { + using part_type = std::string_view; + + struct PartIterable; + + // Create an empty PurePath. + // + // Empty paths are considered to have 0 parts, i.e. + // PurePath{}.VisitParts() -> [] + constexpr PurePath() : path_(".") { + } + + // Create a PurePath from a string view. + // + // This borrows memory ownership of the string view. If you wish to make a copy, + // use the PurePath(std::string) constructor. + // + // Paths are non-normalized (i.e. redundant up-references, "..", are not stripped), + // you may wish to call 'NormalizePath' if this is important. + constexpr PurePath(std::string_view path) : path_(path) { + /// : owner_(std::string(path)), path_(owner_.value()) { + // TODO: no copy + } + + constexpr PurePath(const char* path) : PurePath(std::string_view(path)) {} + + // Creates a PurePath from a string. + // + // The PurePath owns the memory of the string path. + // + // Only accepts movable strings, so that the cheaper borrowing (string_view) + // constructor is used by default. + PurePath(std::string&& path) : owner_(std::move(path)), path_(owner_.value()) { + } + + // Return an Iterable, which upon traversing would + // return each part as an std::string_view. + // + // Empty and '.' path components are not visited, + // effectively ignoring redundant // and intermediate '.' components. + // + // To also ignore redundant up-references, see #NormalizePath. + // + // Example: + // for (std::string_view part : PurePath("hello//world/./").IterateParts()) { + // // loop 0, part == "hello"sv + // // loop 1, part == "world"sv + // } + constexpr PartIterable IterateParts() const { + return PartIterable::FromPath(*this); + } + + // f is a function<void(std::string_view part)> + // + // Invoke 'f' repeatedly on each logical part of this path. + // + // Empty and '.' path components are not visited, + // effectively ignoring redundant // and intermediate '.' components. + // + // To also ignore redundant up-references, see #NormalizePath. + template <typename Fn> + constexpr void VisitParts(Fn f) const { + // Note: Near the top to avoid -Wundefined-inline warnings. + if (IsAbsolute()) { + f(kRoot); // When we split, we no longer visit the '/' tokens. Handle root explicitly. + } + VisitSplitStringView(path_, + kRoot, + [&f](auto&& substr) { + // Ignore duplicate /// and also . + // + // e.g. + // '//foo' -> ['/', 'foo'] + // './foo' -> ['foo'] + // + // This is consistent with PurePath.parts implementation. + // + // Note that redundant .. are not removed, e.g. + // '../foo/..' is not rewritten to ['..'] + // + // Use 'NormalizePath' to do this explicitly. + if (!substr.empty() && substr != ".") { + f(substr); + } + }); + } + + + // A path is considered equal to another path if all of the parts are identical. + /*constexpr*/ bool operator==(const PurePath& other) const { + /*if (path_ == other.path_) { + return true; + } else*/ { + auto this_range = IterateParts(); + auto other_range = other.IterateParts(); + + return std::equal(this_range.begin(), + this_range.end(), + other_range.begin(), + other_range.end()); + } + } + + // Returns the name component (if any). + // + // Logically equivalent to returning the last part unless: + // - the last part is the root '/' + // - there are no parts + // + // If the above conditions do not hold, return the empty string. + constexpr std::string_view Name() const { + std::string_view component = StringSplit::EmptySubstringAtEnd(path_); + + size_t count = 0; + for (auto&& part : IterateParts()) { + if (count++ == 0 && part == kRoot) { + continue; // '/' does not count as a name. + } else { + DCHECK_NE(part, kRoot); + } + + component = part; + } + + return component; + } + + // Find the parent of this path. + // + // This is usually the path with the last part stripped off, with some special cases: + // - The parent of '/' is always '/' (recursive). + // - The parent of '' is always '..'. + // - The parent of '..[/..]*' is an additional '/..' appended. + // + // The parent is always distinct (i.e. not equal to this) except for '/', whose parent + // is itself. + /*constexpr*/ PurePath Parent() const { + size_t parts_count = 0; + size_t upreference_count = 0; + // TODO: this should be constexpr, but it complains about PurePath not being a literal type. + + for (auto&& part : IterateParts()) { + ++parts_count; + + if (part == "..") { + ++upreference_count; + } + } + + if (upreference_count == parts_count) { // Could also have 0 parts. + // "../../../" etc. No other parts are there. + // We need to add another '..' + + // Explicitly handle a few iterations to remain constexpr. + switch (upreference_count) { + case 0: + return {".."}; + case 1: + return {"../.."}; + case 2: + return {"../../.."}; + case 3: + return {"../../../.."}; + default: + break; + } + + // As a special case, this part of the function is not constexpr. + std::string built_parent_string = ".."; + for (size_t i = 0; i < upreference_count; ++i) { + built_parent_string += kRoot; + built_parent_string += ".."; + } + + return PurePath{std::move(built_parent_string)}; + + } else if (parts_count == 1) { + if (IsAbsolute()) { + // "/" + ".." is still "/" + return {kRoot}; + } else { + // <NOT-ROOT-OR-UP-REFERENCE> + ".." is just "." + return {}; + } + } else { + DCHECK_GE(parts_count, 2u); + + // Find the last iterator before we hit the end. + std::optional<std::string_view> last; + std::optional<std::string_view> prev_last; + for (auto&& part : IterateParts()) { + prev_last = last; + last = part; + } + + DCHECK(last.has_value()); + DCHECK(prev_last.has_value()); + + std::string_view& prev_last_view = *prev_last; + // prev_last_view must be within address of subrange_. + DCHECK_GE(prev_last_view.data(), path_.data()); + DCHECK_LE(prev_last_view.data() + prev_last_view.size(), path_.data() + path_.size()); + + // take advantage of the address subrange property by calculating a new substring + // for the parent. + size_t length = prev_last_view.data() + prev_last_view.size() - path_.data(); + std::string_view parent = std::string_view{path_.data(), length} ; + + if ((false)) { + LOG(DEBUG) << "PurePath::Parent of \"" << path_ << "\" returns \"" << parent << "\""; + } + return { parent }; + } + } + + // A path is considered non-equal to another path if one or more of the parts differ. + constexpr bool operator!=(const PurePath& other) const { + return !(*this == other); + } + + // Return the string view, i.e. to pass to other classes that need a string-like type. + // + // This passes in the original string as was passed into the constructor. + // The exact char-by-char representation may be different than concatenating all the parts + // together. + // + // See also #NormalizePath if you want to get a 1:1 mapping between a PurePath + // and a string. + constexpr std::string_view AsStringView() const { + // This is slightly inconsistent with PurePath#bytes because it actually collapses the string + // to the equivalent of concatenating the parts together. But we prefer not to do that, + // since it just causes more work and more allocations unnecessarily. + // + // This is generally not-noticeable when operating with the path at the logical layer. + return path_; + } + + constexpr bool IsAbsolute() const { + return !path_.empty() && path_[0] == '/'; // left-whitespace is considered significant. + } + + // Join one or more paths together. + // + // Logically equivalent to calling JoinPath(other) repeatedly. + template <typename It> + PurePath JoinPath(It begin, It end) const { + std::vector<std::string_view> parts_stack = PartsList(); + + while (begin != end) { + const PurePath& path = *begin; + + if (path.IsAbsolute()) { + parts_stack = path.PartsList(); + } else { + path.VisitParts([&parts_stack](auto&& part) { + parts_stack.push_back(part); + }); + } + + ++begin; + } + + return {JoinPartsList(parts_stack)}; + } + + // Join two paths together: + // + // If 'other' is an absolute path, it is returned. + // + // Otherwise, return the concatenation of the parts (this and other) as a new path. + // (The returned path is always owned by the caller -- this is triggering an allocation every + // time). + PurePath JoinPath(const PurePath& other) const { + if (other.IsAbsolute()) { + return other.OwningCopy(); + } else { + std::vector<std::string_view> parts_stack = PartsList(); + other.VisitParts([&parts_stack](auto&& part) { + parts_stack.push_back(part); + }); + return {JoinPartsList(parts_stack)}; + } + } + + constexpr PurePath(const PurePath& other) { + if (this == &other) { + return; + } + if (other.owner_) { // stay constexpr for non-owning paths. + owner_ = other.owner_; + path_ = *owner_; // path_ always points to owner if possible. + } else { + path_ = other.path_; + } + } + + constexpr PurePath(PurePath&& other) { + if (this == &other) { + return; + } + if (other.owner_) { // stay constexpr for non-owning paths. + owner_ = std::move(other.owner_); + path_ = *owner_; // path_ always points to owner if possible. + } else { + path_ = std::move(other.path_); + } + } + + // "/.." -> "/" + // "../foo/.." -> ".." + // etc. + // + // PurePath returned always owns its own memory (this always triggers an allocation). + PurePath NormalizePath() const { + if (IsNormalized()) { + return OwningCopy(); // Don't call this function if you want to avoid copies! + } else { + // Invariant: [/]? <UP-REFERENCE>* <NOT-AN-UP-REFERENCE>* + std::vector<std::string_view> parts_stack; + size_t not_an_up_reference = 0; + + // Special handling of absolute paths: + // '/' '..'* -> '/' + // + // Otherwise, remove the last part when encountering redundant up-references: + // e.g. '../foo/bar/baz/..' -> '../foo/bar' + VisitParts([&](auto&& part) { + if (part == "..") { + // <UP-REFERENCE> + if (not_an_up_reference > 0) { + // Remove redundant up-references. + DCHECK(!parts_stack.empty()); + + // Could trigger de-normalization, remove redundant part from stack. + + if (parts_stack.back() != kRoot) { // '/' '..'* -> '/' + parts_stack.pop_back(); + --not_an_up_reference; // '../foo/..' -> '..' + } + } else { + // Did not trigger a denormalization. + parts_stack.push_back(part); + } + } else { + // <NOT-AN-UP-REFERENCE> or '/' (note: / is only visited the first time). + parts_stack.push_back(part); + ++not_an_up_reference; + } + }); + + // join again with empty delimiter. + std::string concat = JoinPartsList(std::move(parts_stack)); + + return PurePath(std::move(concat)); + } + } + + // Returns true if 'NormalizePath' would return a Path with a different parts representation. + // + // (This is not as strict as normalizing the underlying string, i.e. redundant '.' and "//" + // in AsStringView() could still be seen). + // + // A path is considered non-normalized unless all up-references are at the start. + // + // NormalizedString := <UP-REFERENCE>* <NOT-AN-UP-REFERENCE>* + // + // where each token is a 'part' returned by VisitParts. + // + // Returning false here means that 'NormalizePath' will also trigger an extra allocation. + constexpr bool IsNormalized() const { + size_t not_an_up_reference = 0; + bool is_normalized = true; + + // Note that this also handles '/' [..]* because '/' is treated identically to non-up-refs. + VisitParts([&](auto&& part) { + // Remove redundant up-references. + if (part != "..") { + ++not_an_up_reference; + } else { // part == ".." + if (not_an_up_reference > 0) { // <not-an-up-reference> <up-reference> + is_normalized = false; + } + } + }); + + return is_normalized; + } + + // Implement LegacyForwardIterator concept. + struct PartIterator { + using value_type = std::string_view; + using reference = value_type&; + using pointer = value_type*; + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; // required by concept, but its meaningless. + + private: + enum State { + kUninitialized, + kAtRoot, + kInitialized, + kAtEnd + }; + + using SplitIterable = StringSplit::SplitIterable; + using SplitIterator = StringSplit::SplitIterator; + + State state{kUninitialized}; + value_type cur_value; + SplitIterator cur; + SplitIterator end; + + friend std::ostream& operator<<(std::ostream& os, const PartIterator& it); + + // Print out extra debugging information when looping through the iterator. + static constexpr bool kLogDebug = false; + + public: + void PrintToStream(std::ostream& os) const { + os << "PartIterator{"; + os << "state:"; + switch (state) { + case kUninitialized: + os << "kUninitialized"; + break; + case kAtRoot: + os << "kAtRoot"; + break; + case kInitialized: + os << "kInitialized"; + break; + case kAtEnd: + os << "kAtEnd"; + break; + } + os << ","; + os << "cur_value:\"" << cur_value << "\","; + os << "cur:" << cur << ","; + os << "end:" << end << ","; + os << "}"; + } + + /*constexpr*/ bool operator==(const PartIterator& other) const { + DCHECK(state != kUninitialized) << "State must be initialized"; + DCHECK(other.state != kUninitialized) << "Other state must be initialized"; + + if (kLogDebug) { + LOG(DEBUG) << "PartIterator =="; + } + + if (state != other.state) { + if (kLogDebug) { + LOG(DEBUG) << "State: " << static_cast<int>(state); + LOG(DEBUG) << "Other State: " << static_cast<int>(other.state); + + LOG(DEBUG) << "== states differ (&self=" << this << ",&other=" << &other << ")"; + LOG(DEBUG) << "Self=" << *this; + LOG(DEBUG) << "Other=" << other; + } + return false; + } + + switch (state) { + case kAtRoot: + DCHECK(cur != end); + return cur == other.cur; + case kInitialized: + DCHECK(cur != end); + return cur == other.cur; + case kAtEnd: + DCHECK(cur == end); + DCHECK(cur == other.cur); + return true; + default: + DCHECK(false); // -Werror -Wswitch + return true; + } + } + + constexpr bool operator!=(const PartIterator& other) const { + return !(*this == other); + } + + constexpr reference operator*() { + DCHECK(state != kAtEnd) << "Undefined behavior to dereference end() iterators"; + return cur_value; // Can't use *cur because we could yield a '/'. + } + + constexpr pointer operator->() { + DCHECK(state != kAtEnd) << "Undefined behavior to dereference end() iterators"; + return &cur_value; // Can't use &*cur because we could yield a '/'. + } + + /* + constexpr const reference operator*() const { + return *cur; + } + + constexpr const pointer operator->() const { + return &*cur; + }*/ + + constexpr PartIterator& operator++() { + DCHECK(state != kAtEnd) << "Undefined behavior to increment end() iterators"; + UpdateValues(); + return *this; + } + + constexpr PartIterator operator++(int) { + PartIterator copy{*this}; + ++(*this); + return copy; + } + + constexpr static PartIterator MakeBegin(SplitIterable& split_iterable, + std::string_view whole_path) { + SplitIterator begin = split_iterable.begin(); + SplitIterator end = split_iterable.end(); + + PartIterator it; + it.end = end; + + const bool is_absolute = !whole_path.empty() && whole_path[0] == '/'; + + if (begin == end) { + it.cur = end; + it.state = kAtEnd; + // I'm not sure this path is actually possible due to the nature of how StringSplit + // works, but it's better to cover this case just to be safe. + DCHECK(false) << "unreachable code, splitting by '/' always returns at least 1 split"; + } else { + it.cur = begin; + + if (is_absolute) { + // When we split, we no longer visit the '/' tokens. Handle root explicitly. + // + // All emitted values must be within the address range of the whole path. + it.cur_value = whole_path.substr(0, /*count*/1); // '/' + DCHECK_EQ(it.cur_value, "/"sv); + it.state = kAtRoot; + } else { + it.state = kUninitialized; + it.UpdateValues(); + } + } + + return it; + } + + constexpr static PartIterator MakeEnd(SplitIterable& split_iterable) { + SplitIterator end = split_iterable.end(); + + PartIterator it; + it.cur = end; + it.end = end; + it.state = kAtEnd; + + return it; + } + + private: + void UpdateValues() { + State previous_state = state; + + if (kLogDebug) { + LOG(DEBUG) << "operator ++ // UpdateValues (&this=" << this << ")"; + } + + if (state == kAtEnd) { + return; + } + + if (state == kInitialized) { + DCHECK(IsValidCurrent()); + } + + // '/' has no corresponding split, so it's handled as a special case. + // Furthermore, any splits that are empty or "." are skipped since they aren't + // considered to be a valid path component. + // + // The below code handles these special cases. + + if (state == kAtRoot) { + state = kUninitialized; + } + + if (state == kUninitialized) { + // If we are already at a valid value stop. + if (cur != end && IsValidCurrent()) { + state = kInitialized; + cur_value = *cur; + return; + } + + // Otherwise we are either at the end, or + // the current value is invalid (e.g. empty or '.'). + state = kInitialized; + } + + DCHECK(state == kInitialized) << static_cast<int>(state); + if (previous_state == kInitialized) { + // If we fell-through from kAtRoot or kUninitialized + // then there's no guarantee that the current value is valid. + DCHECK(IsValidCurrent()); + } + + auto old_cur_value = *cur; + + // Already at the end. Switch to end state. + if (cur == end) { + state = kAtEnd; + LOG(DEBUG) << "Updated state is: kAtEnd (1)"; + return; + } + + // Skip ahead. + // We may or may not be at a valid value now. + ++cur; + + // If we aren't at a valid value yet, then keep going forward + // until we hit a valid value (or we exhaust the iterator). + while (cur != end && !IsValidCurrent()) { + ++cur; + } + + if (cur == end) { + state = kAtEnd; + } else { + // We reached a valid value before exhausting the iterator. + + // Stay in the 'Initialized' state. + DCHECK(IsValidCurrent()) << *cur; + cur_value = *cur; + + // After we go forward, the old and current value cannot match. + DCHECK_NE(&cur_value[0], &old_cur_value[0]); + } + + if (kLogDebug) { + LOG(DEBUG) << "Updated state is: " << state; + } + } + + constexpr bool IsValidCurrent() { + if (cur->empty()) { + return false; + } else if (*cur == ".") { + return false; + } + + return true; + } + }; + + friend struct PartIterable; + + struct PartIterable { + constexpr static PartIterable FromPath(const PurePath& path) { + return PartIterable{ + path.AsStringView(), + StringSplit::Iterable(path.AsStringView(), PurePath::kRoot), + }; + } + + constexpr PartIterator begin() { + return PartIterator::MakeBegin(split_iterable, whole_path); + } + + constexpr PartIterator end() { + return PartIterator::MakeEnd(split_iterable); + } + + std::string_view whole_path; + StringSplit::SplitIterable split_iterable; + }; + + // This isn't performance-efficient, but it might be needed by some functions + // that have to allocate anyway such as JoinPaths. + // + // Intended only for testing. + std::vector<std::string_view> PartsList() const { + PartIterable iterable = IterateParts(); + + std::vector<std::string_view> parts{iterable.begin(), iterable.end()}; + return parts; + } + + // Does this PurePath own the underlying memory? + // + // true = borrowing memory from someone else (might not be safe to retain this object) + // false = owns its own memory (can keep this object indefinitely long) + // + // Currently intended only for testing. + constexpr bool IsBorrowed() const { + return !owner_.has_value(); + } + + private: + // Return a PurePath that owns its own memory. + // + // This way functions which 'may' allocate memory turn into functions + // that always allocate memory, and avoid a dangling reference. + const PurePath OwningCopy() const { + std::string make_copy{path_}; + return PurePath{std::move(make_copy)}; + } + + constexpr size_t PartsCount() const { + size_t count = 0; + VisitParts([&count](auto&& /*part*/) { + ++count; + }); + return count; + } + + // Basically a string join with an empty delimiter. + template <typename Container> + static std::string JoinPartsList(Container&& c) { + std::string build; + for (auto begin = c.begin(), end = c.end(); begin != end; ++begin) { + build += *begin; + + // TODO: use forward_dependent here. + } + + return build; + } + + // This might be empty, in which case path_ is just a temporary borrow of path_. + std::optional<std::string> owner_; + std::string_view path_; // points to owner_ if there's a value there. + + // TODO: this is a bit error-prone, so we might want to refactor into a + // never-owning PathView and an always-owning PurePath. + + static constexpr std::string_view kRoot = "/"; +}; + +std::ostream& operator<<(std::ostream& os, const PurePath::PartIterator& it) { + it.PrintToStream(os); + return os; +} + +static constexpr const PurePath::PartIterator kMakeMeABlank; + +std::ostream& operator<<(std::ostream& os, const PurePath& path) { + os << path.AsStringView(); + return os; +} + +TEST(PurePathTest, Ctor) { + ConfigureLogging(); + + EXPECT_EQ(PurePath{}.AsStringView(), "."sv); + EXPECT_EQ(PurePath{""}.AsStringView(), ""sv); + EXPECT_EQ(PurePath{""sv}.AsStringView(), ""sv); + EXPECT_EQ(PurePath{""s}.AsStringView(), ""sv); + EXPECT_EQ(PurePath{"/hello/world"}.AsStringView(), "/hello/world"sv); + EXPECT_EQ(PurePath{"/hello/world"s}.AsStringView(), "/hello/world"sv); + EXPECT_EQ(PurePath{"/hello/world"sv}.AsStringView(), "/hello/world"sv); + EXPECT_EQ(PurePath{"hello/world"}.AsStringView(), "hello/world"sv); + EXPECT_EQ(PurePath{"hello/world"s}.AsStringView(), "hello/world"sv); + EXPECT_EQ(PurePath{"hello/world"sv}.AsStringView(), "hello/world"sv); + + // Ensure that std::string is only owning memory when we move a string into it. + // Otherwise, always take the string_view constructor. + EXPECT_FALSE(PurePath{std::string{"hello"}}.IsBorrowed()); + std::string hello{"hello"}; + EXPECT_TRUE(PurePath{hello}.IsBorrowed()); + EXPECT_FALSE(PurePath{std::move(hello)}.IsBorrowed()); +} + +TEST(PurePathTest, Parts) { + ConfigureLogging(); + + EXPECT_THAT(PurePath{}.PartsList(), IsEmpty()); + EXPECT_THAT(PurePath{"."}.PartsList(), IsEmpty()); + EXPECT_THAT(PurePath{"./"}.PartsList(), IsEmpty()); + EXPECT_THAT(PurePath{"./."}.PartsList(), IsEmpty()); + EXPECT_THAT(PurePath{".///"}.PartsList(), IsEmpty()); + EXPECT_THAT(PurePath{"./././."}.PartsList(), IsEmpty()); + EXPECT_THAT(PurePath{"/"s}.PartsList(), ElementsAre("/"sv)); + EXPECT_THAT(PurePath{"///"s}.PartsList(), ElementsAre("/"sv)); + EXPECT_THAT(PurePath{"/hello"s}.PartsList(), ElementsAre("/"sv, "hello"sv)); + EXPECT_THAT(PurePath{"/hello///"s}.PartsList(), ElementsAre("/"sv, "hello"sv)); + EXPECT_THAT(PurePath{"/hello/world"s}.PartsList(), ElementsAre("/"sv, "hello"sv, "world"sv)); + EXPECT_THAT(PurePath{"hello/world"sv}.PartsList(), ElementsAre("hello"sv, "world"sv)); + EXPECT_THAT(PurePath{"hello/world"sv}.PartsList(), ElementsAre("hello"sv, "world"sv)); + EXPECT_THAT(PurePath{"hello//world"sv}.PartsList(), ElementsAre("hello"sv, "world"sv)); + EXPECT_THAT(PurePath{"hello/./world"sv}.PartsList(), ElementsAre("hello"sv, "world"sv)); + EXPECT_THAT(PurePath{"hello/./world/././"sv}.PartsList(), ElementsAre("hello"sv, "world"sv)); +} + +#define EXPECT_PATH_EQ(lhs, rhs) EXPECT_EQ(PurePath{lhs}, PurePath{rhs}) +#define EXPECT_PATH_NE(lhs, rhs) EXPECT_NE(PurePath{lhs}, PurePath{rhs}) + +TEST(PurePathTest, Equals) { + ConfigureLogging(); + + EXPECT_PATH_EQ("", ""); + EXPECT_PATH_EQ(".", "."); + EXPECT_PATH_EQ("", "."); + EXPECT_PATH_EQ("./", "."); + EXPECT_PATH_EQ(".////", "."); + EXPECT_PATH_EQ(".//././", "."); + EXPECT_PATH_EQ("hello/world//", "hello/world"); + EXPECT_PATH_EQ("hello/world//", "./hello/world"); + EXPECT_PATH_EQ("//hello/world//", "/hello/world"); + EXPECT_PATH_EQ("/./hello/world//", "/hello/world/./"); + EXPECT_PATH_EQ("..", ".././."); + EXPECT_PATH_EQ("../..//", "../.."); + + // Also make sure that the path is not equal to its parent [which is a substring]. + EXPECT_PATH_NE("/data", "/data/baz"); + EXPECT_PATH_NE("/data/././baz", "/data/baz/bar"); + + // Also make sure its not equal when the other path shares the same underlying starting data(). + { + std::string_view view = "/data/bar"; + EXPECT_PATH_NE(PurePath{view}, PurePath{view.substr(/*pos*/0, /*count*/5)}); + } +} + +// A parent is always different than its child (except for '/'). +#define EXPECT_PATH_PARENT_EQ(actual, expected) \ + EXPECT_EQ(PurePath{actual}.Parent(), PurePath{expected}); \ + { auto act = PurePath{actual}; \ + EXPECT_NE(act, act.Parent()); \ + } +TEST(PurePathTest, Parent) { + ConfigureLogging(); + + // Special recursive case: parent of '/' is still '/'. + EXPECT_EQ(PurePath{"/"}, PurePath{"/"}.Parent()); + EXPECT_NE(PurePath{""}, PurePath{"/"}.Parent()); + + // All other cases are non-recursive. + EXPECT_PATH_PARENT_EQ("", ".."); + EXPECT_PATH_PARENT_EQ("..", "../.."); + EXPECT_PATH_PARENT_EQ("../..", "../../.."); + EXPECT_PATH_PARENT_EQ("../../../../../../../../..", "../../../../../../../../../.."); + + EXPECT_PATH_PARENT_EQ("/abc", "/"); + EXPECT_PATH_PARENT_EQ("abc", ""); + + EXPECT_PATH_PARENT_EQ("/foo/bar", "/foo"); + EXPECT_PATH_PARENT_EQ("/foo/bar/b", "/foo/bar"); + EXPECT_PATH_PARENT_EQ("/foo/bar///baz///././/nay", "/foo/bar/baz"); + + EXPECT_PATH_PARENT_EQ("foo/bar", "foo"); + EXPECT_PATH_PARENT_EQ("foo/bar/b", "foo/bar"); + EXPECT_PATH_PARENT_EQ("foo/bar///baz///././/nay", "foo/bar/baz"); + + EXPECT_PATH_PARENT_EQ("../foo/bar", "../foo"); + EXPECT_PATH_PARENT_EQ("../foo/bar/b", "../foo/bar"); + EXPECT_PATH_PARENT_EQ("../foo/bar///baz///././/nay", "../foo/bar/baz"); +} + +#define EXPECT_PATH_NAME_EQ(expected, actual) EXPECT_EQ(PurePath{actual}, PurePath{expected}.Name()) +TEST(PurePathTest, Name) { + ConfigureLogging(); + + EXPECT_PATH_NAME_EQ("", ""); + EXPECT_PATH_NAME_EQ("..", ".."); + EXPECT_PATH_NAME_EQ("../..", ".."); + EXPECT_PATH_NAME_EQ("../../../../../../../../..", ".."); + + EXPECT_PATH_NAME_EQ("/", ""); + EXPECT_PATH_NAME_EQ("/abc", "abc"); + EXPECT_PATH_NAME_EQ("abc", "abc"); + + EXPECT_PATH_NAME_EQ("/foo/bar", "bar"); + EXPECT_PATH_NAME_EQ("/foo/bar/b", "b"); + EXPECT_PATH_NAME_EQ("/foo/bar///baz///././/nay", "nay"); + EXPECT_PATH_NAME_EQ("/foo/bar///baz///././/nay//./.", "nay"); + + EXPECT_PATH_NAME_EQ("foo/bar", "bar"); + EXPECT_PATH_NAME_EQ("foo/bar/b", "b"); + EXPECT_PATH_NAME_EQ("foo/bar///baz///././/nay", "nay"); + + EXPECT_PATH_NAME_EQ("../foo/bar", "bar"); + EXPECT_PATH_NAME_EQ("../foo/bar/b", "b"); + EXPECT_PATH_NAME_EQ("../foo/bar///baz///././/nay", "nay"); +} + + +struct PathEntry { + Inode inode; + PurePath path; // full path + + static std::vector<PathEntry> Zip(std::vector<Inode>& inodes, std::vector<std::string>& paths) { + CHECK_EQ(inodes.size(), paths.size()); + + std::vector<PathEntry> entries; + + static bool debug = true; // Print only once. + + if (debug) { + LOG(DEBUG) << "PathEntry::Zip (begin)"; + } + + for (size_t i = 0; i < inodes.size(); ++i) { + entries.push_back(PathEntry{inodes[i], PurePath{std::string{paths[i]}}}); + + // TODO: this seems awkward, maybe refactor into PurePath + PurePathView ? + DCHECK(entries[i].path.IsBorrowed() == false); + + if (debug) { + LOG(DEBUG) << "PathEntry - add " << inodes[i] << " at '" << paths[i] << "'"; + } + } + + debug = false; + + return entries; + } +}; + +std::ostream& operator<<(std::ostream& os, const PathEntry& path_entry) { + os << "PathEntry{inode=" << path_entry.inode << ",path=\"" << path_entry.path << "\"}"; + return os; +} + +// This super-inefficient class models a Tree to a list of absolute path names. +// Obviously intended only for testing, since its algorithmically suboptimal. +struct PathEntryTree { + std::vector<PathEntry> entries; + + static constexpr bool debug{false}; +#define PET_LOG_DEBUG if (debug) LOG(DEBUG) + + std::optional<PathEntry> GetEntryFor(const std::string& path_name) { + PurePath path{path_name}; + for (auto&& entry : entries) { + if (entry.path == path) { + return entry; + } + } + return {}; + } + + bool HasDirectory(const std::string& path_name) { + PurePath path{path_name}; + for (auto&& entry : entries) { + if (entry.path == path) { + return true; + } + } + return false; + } + + std::vector<PathEntry> OpenDirectory(const std::string& path_name) { + PurePath path{path_name}; + return OpenDirectory(path); + } + + std::vector<PathEntry> OpenDirectory(const PurePath& path) { + std::vector<PathEntry> children; + + PET_LOG_DEBUG << "OpenDirectory(" << path << ")"; + + for (auto&& entry : entries) { + // Only find the immediate children, don't find any other offspring. + PurePath parent = entry.path.Parent(); + if (parent == path) { + if (parent == entry.path) { + // Ignore recursive parents, e.g. '/' + PET_LOG_DEBUG << "OpenDirectory - Ignore recursive parent " << parent; + continue; + } + + children.push_back(entry); + + DCHECK(!children.back().path.IsBorrowed()); + + PET_LOG_DEBUG << "OpenDirectory - Child added = " << entry; + } + } + + + return children; + } + + size_t size() const { + return entries.size(); + } +}; + + +static std::vector<std::string> ParseLines(const char* what) { + std::vector<std::string> do_split = android::base::Split(what, "\n"); + + std::vector<std::string> output; + for (std::string& s : do_split) { + if (s.size() != 0) { + output.push_back(s); + } + } + + return output; +} + +static std::vector<Inode> ParseInodes(std::vector<std::string> inode_strings) { + std::vector<Inode> results; + + for (std::string& s : inode_strings) { + Inode inode; + + std::string error_msg; + bool inode_parse_succeeded = Inode::Parse(s, /*out*/&inode, /*out*/&error_msg); + CHECK(inode_parse_succeeded) << s << ", error: " << error_msg; + + results.push_back(inode); + } + + return results; +} + + + +static PathEntryTree CreateFakePathEntries() { +#if 1 + // adb shell 'find /data/data/com.google.android.googlequicksearchbox/ | xargs stat -c "%d@%i"' + static const char* kInodeValues = R"1N0D3( +66323@1117133 +66323@1127133 +66323@1137133 +66323@1327133 +66323@1336383 +66323@1376559 +66323@1376448 +66323@1376446 +66323@1376596 +66323@1376638 +66323@1376438 +66323@1376444 +66323@1376563 +66323@1376434 +66323@1376439 +66323@1336384 +66323@1335704 +66323@1336031 +66323@1335751 +66323@1337692 +66323@1336090 +66323@1336385 +66323@1376543 +66323@1376449 +66323@1376544 +66323@1376547 +66323@1376436 +66323@1336619 +66323@1336070 +66323@1336681 +66323@1336064 +66323@1336088 +66323@1336470 +66323@1335570 +66323@1335668 +66323@1336471 +66323@1335514 +66323@1376475 +66323@1376462 +66323@1376435 +66323@1376476 +66323@1376632 +66323@1351934 +66323@1351948 +66323@1351949 +66323@1351950 +66323@1351939 +66323@1376479 +66323@1376437 +66323@1376450 +66323@1376480 +66323@1376442 +66323@1376451 +66323@1376454 +66323@1376457 +66323@1376452 +66323@1376546 +66323@1335629 +66323@1343800 +66323@1343801 +66323@1336890 +66323@1336616 +66323@1336921 +66323@1327135 +66323@1335862 +66323@1336547 +66323@1351681 +66323@1351684 +66323@1351744 +66323@1351705 +66323@1351699 +66323@1351711 +66323@1351748 +66323@1351734 +66323@1351682 +66323@1351683 +66323@1351719 +66323@1351739 +66323@1351689 +66323@1351724 +66323@1351690 +66323@1351745 +66323@1351686 +66323@1351691 +66323@1351741 +66323@1351687 +66323@1351747 +66323@1351736 +66323@1351698 +66323@1351697 +66323@1351730 +66323@1351712 +66323@1351703 +66323@1351721 +66323@1351701 +66323@1351717 +66323@1351716 +66323@1351695 +66323@1351720 +66323@1351688 +66323@1351685 +66323@1351727 +66323@1351738 +66323@1351729 +66323@1351704 +66323@1351743 +66323@1351723 +66323@1351700 +66323@1351713 +66323@1351707 +66323@1351709 +66323@1351731 +66323@1351732 +66323@1351693 +66323@1351726 +66323@1351708 +66323@1351714 +66323@1351728 +66323@1351694 +66323@1351706 +66323@1351722 +66323@1351696 +66323@1351715 +66323@1351740 +66323@1351725 +66323@1351702 +66323@1351710 +66323@1351737 +66323@1351742 +66323@1351746 +66323@1351735 +66323@1351733 +66323@1351692 +66323@1351718 +66323@1336864 +66323@1335446 +66323@1337584 +66323@1335740 +66323@1335854 +66323@1336644 +66323@1376553 +66323@1376554 +66323@1376469 +66323@1376637 +66323@1376555 +66323@1376556 +66323@1376570 +66323@1376565 +66323@1376557 +66323@1376558 +66323@1376432 +66323@1376567 +66323@1376440 +66323@1343805 +66323@1336646 +66323@1336947 +66323@1336393 +66323@1336394 +66323@1335920 +66323@1336041 +66323@1335650 +66323@1336667 +66323@1336665 +66323@1335760 +66323@1343802 +66323@1343803 +66323@1344013 +66323@1344134 +66323@1376276 +66323@1336598 +66323@1336634 +66323@1336652 +66323@1336656 +66323@1336446 +66323@1336863 +66323@1337682 +66323@1336866 +66323@1336867 +66323@1335678 +66323@1336865 +66323@1327631 +66323@1327664 +66323@1327660 +66323@1327134 +66323@1336825 +66323@1337969 +66323@1335938 +66323@1337849 +66323@1337839 +66323@1337866 +66323@1337122 +66323@1337756 +66323@1336966 +66323@1337982 +66323@1337097 +66323@1336683 +66323@1337824 +66323@1337460 +66323@1337775 +66323@1337810 +66323@1337847 +66323@1335853 +66323@1337594 +66323@1337808 +66323@1337817 +66323@1337092 +66323@1337699 +66323@1337593 +66323@1337089 +66323@1335959 +66323@1337788 +66323@1337181 +66323@1337610 +66323@1336980 +66323@1337972 +66323@1337554 +66323@1337661 +66323@1337770 +66323@1335951 +66323@1337984 +66323@1336061 +66323@1337497 +66323@1337835 +66323@1337805 +66323@1336557 +66323@1336780 +66323@1337816 +66323@1337732 +66323@1337983 +66323@1337954 +66323@1337713 +66323@1337687 +66323@1337597 +66323@1337466 +66323@1337814 +66323@1337603 +66323@1337031 +66323@1336784 +66323@1337534 +66323@1337727 +66323@1337693 +66323@1337791 +66323@1337567 +66323@1337748 +66323@1337777 +66323@1336194 +66323@1337843 +66323@1336971 +66323@1337974 +66323@1336785 +66323@1337871 +66323@1337815 +66323@1337709 +66323@1337551 +66323@1337088 +66323@1337776 +66323@1337672 +66323@1335979 +66323@1337823 +66323@1336028 +66323@1337526 +66323@1337971 +66323@1337853 +66323@1337596 +66323@1337901 +66323@1337572 +66323@1335921 +66323@1336954 +66323@1337820 +66323@1335492 +66323@1337809 +66323@1337696 +66323@1335636 +66323@1337608 +66323@1335746 +66323@1337731 +66323@1337967 +66323@1337769 +66323@1337751 +66323@1337973 +66323@1337697 +66323@1335939 +66323@1336001 +66323@1337598 +66323@1336713 +66323@1337702 +66323@1337844 +66323@1337862 +66323@1336978 +66323@1337975 +66323@1336798 +66323@1337858 +66323@1337605 +66323@1337510 +66323@1337914 +66323@1376548 +66323@1376549 +66323@1376550 +66323@1376564 +66323@1376571 +66323@1376683 +66323@1376681 +66323@1376652 +66323@1376682 +66323@1376684 +66323@1376649 +66323@1376568 +66323@1376569 +66323@1376576 +66323@1376578 +66323@1376579 +66323@1376581 +66323@1376582 +66323@1376577 +66323@1376580 +66323@1376597 +66323@1376598 +66323@1376602 +66323@1376599 +66323@1376600 +66323@1376601 +66323@1376583 +66323@1376551 +66323@1376552 +66323@1376560 +66323@1376561 +66323@1376562 +66323@1376591 +66323@1376497 +66323@1376482 +66323@1376536 +66323@1376533 +66323@1376532 +66323@1336380 +66323@1336425 +66323@1337738 +66323@1337978 +66323@1337796 +66323@1337819 +66323@1337781 +66323@1337857 +66323@1337963 +66323@1335777 +66323@1337569 +66323@1337818 +66323@1337758 +66323@1337742 +66323@1336950 +66323@1337730 +66323@1337021 +66323@1335774 +66323@1337813 +66323@1337755 +66323@1337964 +66323@1337860 +66323@1338005 +66323@1336592 +66323@1336428 +66323@1335779 +66323@1337976 +66323@1337461 +66323@1337789 +66323@1337745 +66323@1337602 +66323@1337698 +66323@1336813 +66323@1337606 +66323@1337896 +66323@1337712 +66323@1337970 +66323@1337981 +66323@1335435 +66323@1337587 +66323@1337821 +66323@1337716 +66323@1337754 +66323@1337786 +66323@1337778 +66323@1336032 +66323@1338029 +66323@1337550 +66323@1337783 +66323@1337609 +66323@1337107 +66323@1337841 +66323@1337557 +66323@1337700 +66323@1337604 +66323@1337920 +66323@1337469 +66323@1337811 +66323@1337715 +66323@1337980 +66323@1336949 +66323@1337812 +66323@1337806 +66323@1337779 +66323@1337600 +66323@1336080 +66323@1337601 +66323@1336920 +66323@1337703 +66323@1337033 +66323@1336824 +66323@1337104 +66323@1337854 +66323@1336078 +66323@1336970 +66323@1337917 +66323@1337671 +66323@1337926 +66323@1336802 +66323@1337797 +66323@1338031 +66323@1337095 +66323@1337676 +66323@1337708 +66323@1335905 +66323@1336124 +66323@1337859 +66323@1337784 +66323@1337795 +66323@1337724 +66323@1337822 +66323@1336426 +66323@1337852 +66323@1337856 +66323@1337855 +66323@1337780 +66323@1337607 +66323@1336956 +66323@1337038 +66323@1336513 +66323@1336918 +66323@1336739 +66323@1337924 +66323@1337530 +66323@1337757 +66323@1337850 +66323@1337701 +66323@1336613 +66323@1337737 +66323@1336817 +66323@1337977 +66323@1336314 +66323@1337465 +66323@1336991 +66323@1337279 +66323@1337922 +66323@1337710 +66323@1337599 +66323@1337861 +66323@1336388 +66323@1336389 +66323@1336084 +66323@1335615 +66323@1336375 +66323@1335759 +66323@1336036 +66323@1336433 +66323@1335649 +66323@1337744 +66323@1336008 +66323@1336004 +66323@1336026 +66323@1335834 +66323@1336376 +66323@1336377 +66323@1336505 +66323@1336378 +66323@1335382 +66323@1337015 +66323@1336108 +66323@1337103 +66323@1335413 +66323@1335935 +66323@1335429 +66323@1337733 +66323@1336382 +66323@1336381 +66323@1336633 +66323@1337522 +66323@1336694 +66323@1335428 +)1N0D3"; + + const char* kPathNames = R"F1L3N4M3( +/ +/data/ +/data/data/ +/data/data/com.google.android.googlequicksearchbox/ +/data/data/com.google.android.googlequicksearchbox/app_si +/data/data/com.google.android.googlequicksearchbox/app_si/searchbox_stats_content_store +/data/data/com.google.android.googlequicksearchbox/app_si/searchbox_stats_content_store/content_store.db-shm +/data/data/com.google.android.googlequicksearchbox/app_si/searchbox_stats_content_store/content_store.db-wal +/data/data/com.google.android.googlequicksearchbox/app_si/searchbox_stats_content_store/content_store.db +/data/data/com.google.android.googlequicksearchbox/app_si/searchbox_stats_content_store/content_store.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/app_si/shortcuts_content_store +/data/data/com.google.android.googlequicksearchbox/app_si/shortcuts_content_store/content_store.db-shm +/data/data/com.google.android.googlequicksearchbox/app_si/shortcuts_content_store/content_store.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/app_si/shortcuts_content_store/content_store.db-wal +/data/data/com.google.android.googlequicksearchbox/app_si/shortcuts_content_store/content_store.db +/data/data/com.google.android.googlequicksearchbox/app_si/now_content_store +/data/data/com.google.android.googlequicksearchbox/app_si/now_content_store/content_store.db-wal +/data/data/com.google.android.googlequicksearchbox/app_si/now_content_store/content_store.db-shm +/data/data/com.google.android.googlequicksearchbox/app_si/now_content_store/now_content_store_blob_9060309284749123123.bin +/data/data/com.google.android.googlequicksearchbox/app_si/now_content_store/content_store.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/app_si/now_content_store/now_content_store_blob_9184734810098631032.bin +/data/data/com.google.android.googlequicksearchbox/app_si/now_content_store/content_store.db +/data/data/com.google.android.googlequicksearchbox/app_si/proactive_key_value_content_store +/data/data/com.google.android.googlequicksearchbox/app_si/proactive_key_value_content_store/content_store.db-shm +/data/data/com.google.android.googlequicksearchbox/app_si/proactive_key_value_content_store/content_store.db +/data/data/com.google.android.googlequicksearchbox/app_si/proactive_key_value_content_store/content_store.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/app_si/proactive_key_value_content_store/content_store.db-wal +/data/data/com.google.android.googlequicksearchbox/app_si/srp_content_store +/data/data/com.google.android.googlequicksearchbox/app_si/srp_content_store/content_store.db-wal +/data/data/com.google.android.googlequicksearchbox/app_si/srp_content_store/content_store.db +/data/data/com.google.android.googlequicksearchbox/app_si/srp_content_store/content_store.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/app_si/srp_content_store/content_store.db-shm +/data/data/com.google.android.googlequicksearchbox/app_si/state_dump_event_content_store +/data/data/com.google.android.googlequicksearchbox/app_si/state_dump_event_content_store/content_store.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/app_si/state_dump_event_content_store/content_store.db-shm +/data/data/com.google.android.googlequicksearchbox/app_si/state_dump_event_content_store/content_store.db +/data/data/com.google.android.googlequicksearchbox/app_si/state_dump_event_content_store/content_store.db-wal +/data/data/com.google.android.googlequicksearchbox/app_si/homescreen_shortcut_content_store +/data/data/com.google.android.googlequicksearchbox/app_si/homescreen_shortcut_content_store/content_store.db-shm +/data/data/com.google.android.googlequicksearchbox/app_si/homescreen_shortcut_content_store/content_store.db-wal +/data/data/com.google.android.googlequicksearchbox/app_si/homescreen_shortcut_content_store/content_store.db +/data/data/com.google.android.googlequicksearchbox/app_si/homescreen_shortcut_content_store/content_store.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/app_si/search_widget_overlay_content_store +/data/data/com.google.android.googlequicksearchbox/app_si/search_widget_overlay_content_store/content_store.db-wal +/data/data/com.google.android.googlequicksearchbox/app_si/search_widget_overlay_content_store/content_store.db-shm +/data/data/com.google.android.googlequicksearchbox/app_si/search_widget_overlay_content_store/content_store.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/app_si/search_widget_overlay_content_store/content_store.db +/data/data/com.google.android.googlequicksearchbox/app_si/opa_content_store +/data/data/com.google.android.googlequicksearchbox/app_si/opa_content_store/content_store.db-wal +/data/data/com.google.android.googlequicksearchbox/app_si/opa_content_store/content_store.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/app_si/opa_content_store/content_store.db +/data/data/com.google.android.googlequicksearchbox/app_si/opa_content_store/content_store.db-shm +/data/data/com.google.android.googlequicksearchbox/app_si/accl_conv_client_content_store +/data/data/com.google.android.googlequicksearchbox/app_si/accl_conv_client_content_store/content_store.db-shm +/data/data/com.google.android.googlequicksearchbox/app_si/accl_conv_client_content_store/content_store.db +/data/data/com.google.android.googlequicksearchbox/app_si/accl_conv_client_content_store/content_store.db-wal +/data/data/com.google.android.googlequicksearchbox/app_si/accl_conv_client_content_store/content_store.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/app_session +/data/data/com.google.android.googlequicksearchbox/app_monet_init_data +/data/data/com.google.android.googlequicksearchbox/app_monet_init_data/search.TYPE_SEARCHNOW.binarypb +/data/data/com.google.android.googlequicksearchbox/no_backup +/data/data/com.google.android.googlequicksearchbox/no_backup/com.google.InstanceId.properties +/data/data/com.google.android.googlequicksearchbox/no_backup/com.google.android.gms.appid-no-backup +/data/data/com.google.android.googlequicksearchbox/code_cache +/data/data/com.google.android.googlequicksearchbox/app_sid +/data/data/com.google.android.googlequicksearchbox/app_g3_models +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/CLG.prewalk.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/verbalizer_terse.mfar +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/en-US_app-actions_prompted-app-name_TWIDDLER_FST.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/contacts.abnf +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/en-US_monastery_contact-disambig-static_TWIDDLER_FST.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/wordlist.syms +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/norm_fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/APP_NAME.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/APP_NAME.syms +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/ep_portable_mean_stddev +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/portable_meanstddev +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/SONG_NAME.syms +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/g2p_phonemes.syms +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/TERSE_LSTM_LM.lstm_lm.main_model.uint8.data +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/voice_actions.config +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/CONTACT_NAME.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/TERSE_LSTM_LM.lstm_lm.self_normalized_model.uint8.data +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/pumpkin.mmap +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/CONTACT_NAME.syms +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/word_confidence_classifier +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/offline_action_data.pb +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/config.pumpkin +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/compile_grammar.config +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/lstm_model.uint8.data +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/en-US_read-items_SearchMessageAction-Prompted-Read_TWIDDLER_FST.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/embedded_class_denorm.mfar +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/g2p.data +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/dictation.config +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/endpointer_model.mmap +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/endpointer_model +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/c_fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/ep_portable_model.uint8.mmap +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/SONG_NAME.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/CONTACT.transform.mfar +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/hmmlist +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/portable_lstm +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/lexicon.U.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/embedded_normalizer.mfar +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/semantics.pumpkin +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/g2p_graphemes.syms +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/dict +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/en-US_read-items_SearchMessageAction-Prompted-Skip_TWIDDLER_FST.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/en-US_confirmation_confirmation-cancellation_TWIDDLER_FST.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/en-US_media-actions_music-service-controllable_TWIDDLER_FST.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/magic_mic.config +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/metadata +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/am_phonemes.syms +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/hmm_symbols +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/en-US_gmm-actions_gmm-nav-actions_TWIDDLER_FST.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/en-US_time-actions_time-context_TWIDDLER_FST.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/input_mean_std_dev +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/benchmark.volvo.txt +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/en-US_calendar-actions_AddCalendarEvent-Prompted-FieldToChange_TWIDDLER_FST.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/g2p_fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/commands.abnf +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/endpointer_dictation.config +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/prons_exception_dictionary_file.txt +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/grammar.config +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/dnn +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/en-US_monastery_GenericAction-Prompted-ContactName_TWIDDLER_FST.fst +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/phonelist +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/rescoring.fst.compact +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/voice_actions_compiler.config +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/offensive_word_normalizer.mfar +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/monastery_config.pumpkin +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/TERSE_LSTM_LM.lstm_lm.syms +/data/data/com.google.android.googlequicksearchbox/app_g3_models/en-US/endpointer_voicesearch.config +/data/data/com.google.android.googlequicksearchbox/app_textures +/data/data/com.google.android.googlequicksearchbox/files +/data/data/com.google.android.googlequicksearchbox/files/current_experiments.bin +/data/data/com.google.android.googlequicksearchbox/files/training_question_data +/data/data/com.google.android.googlequicksearchbox/files/now_request_queue +/data/data/com.google.android.googlequicksearchbox/files/velour +/data/data/com.google.android.googlequicksearchbox/files/velour/preferences +/data/data/com.google.android.googlequicksearchbox/files/velour/preferences/ipa +/data/data/com.google.android.googlequicksearchbox/files/velour/preferences/ipa/IpaBgTask +/data/data/com.google.android.googlequicksearchbox/files/velour/preferences/wernicke_player +/data/data/com.google.android.googlequicksearchbox/files/velour/feature_data +/data/data/com.google.android.googlequicksearchbox/files/velour/feature_data/ipa +/data/data/com.google.android.googlequicksearchbox/files/velour/feature_data/ipa/ipa_0p_instant_cache +/data/data/com.google.android.googlequicksearchbox/files/velour/feature_data/ipa/ZeroPrefixContacts +/data/data/com.google.android.googlequicksearchbox/files/velour/feature_data/ipa/ipa_content_store +/data/data/com.google.android.googlequicksearchbox/files/velour/feature_data/ipa/ipa_content_store/content_store.db +/data/data/com.google.android.googlequicksearchbox/files/velour/feature_data/ipa/ipa_content_store/content_store.db-wal +/data/data/com.google.android.googlequicksearchbox/files/velour/feature_data/ipa/ipa_content_store/content_store.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/files/velour/feature_data/ipa/ipa_content_store/content_store.db-shm +/data/data/com.google.android.googlequicksearchbox/files/velour/jar_data +/data/data/com.google.android.googlequicksearchbox/files/velour/verified_jars +/data/data/com.google.android.googlequicksearchbox/files/velour/dex_cache +/data/data/com.google.android.googlequicksearchbox/files/native_crash_dir +/data/data/com.google.android.googlequicksearchbox/files/native_crash_dir/com.google.android.googlequicksearchbox:search +/data/data/com.google.android.googlequicksearchbox/files/current_configuration.bin +/data/data/com.google.android.googlequicksearchbox/files/dynamic_update_config_log +/data/data/com.google.android.googlequicksearchbox/files/brainsuggest +/data/data/com.google.android.googlequicksearchbox/files/brainsuggest/libbrainsuggest.so +/data/data/com.google.android.googlequicksearchbox/files/brainsuggest/tensors.bin +/data/data/com.google.android.googlequicksearchbox/files/persisted_profiling_statistics +/data/data/com.google.android.googlequicksearchbox/files/en-US +/data/data/com.google.android.googlequicksearchbox/files/en-US/x_hotword.data +/data/data/com.google.android.googlequicksearchbox/files/recently +/data/data/com.google.android.googlequicksearchbox/files/recently/libcore.test@gmail.com +/data/data/com.google.android.googlequicksearchbox/files/dump +/data/data/com.google.android.googlequicksearchbox/files/bloblobber +/data/data/com.google.android.googlequicksearchbox/files/bloblobber/pending +/data/data/com.google.android.googlequicksearchbox/files/web_suggest_model +/data/data/com.google.android.googlequicksearchbox/files/web_suggest_model/index.bin +/data/data/com.google.android.googlequicksearchbox/files/client_data_request_log +/data/data/com.google.android.googlequicksearchbox/app_webview +/data/data/com.google.android.googlequicksearchbox/app_webview/variations_seed_new +/data/data/com.google.android.googlequicksearchbox/app_webview/Cookies-journal +/data/data/com.google.android.googlequicksearchbox/app_webview/variations_stamp +/data/data/com.google.android.googlequicksearchbox/app_webview/variations_seed +/data/data/com.google.android.googlequicksearchbox/app_webview/Cookies +/data/data/com.google.android.googlequicksearchbox/app_shared_prefs +/data/data/com.google.android.googlequicksearchbox/app_shared_prefs/StartupSettings.bin +/data/data/com.google.android.googlequicksearchbox/app_shared_prefs/SearchSettings.bin +/data/data/com.google.android.googlequicksearchbox/cache +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/fd12e9a1ba593cbe075c925a95626534054861f9dd82fa27f656ac0088648fd9.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/4a19d733c917d730443eaff509ee0496e116f79c69d0d2fa54a594f5accd19d1.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/76f6a9848373162cd602a03e00e363ad8455e62293e9218d57da728f7382ee34.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/666d7a8c0d257a9a9f1457a1bb04b8eda821966283d466db872d5693de42d29b.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/0f714cd570228ce48e2741fd6ff959bcbbab49e40427b6eb5c4b1ff3aae4ad40.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/458acc9b996dc815a7259a2c9dbf5b94ae549da3d66f3649d1e0a1e239214390.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/13ec5eaf61460a0be11467ba2e0efad6602142e45fd1c97988bc42aa54832407.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/99b468ff1549d8e260ce274f7afdaaf32fb70064c31596173b812ea2d11c8097.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/40c737d31b2d843c5772648820deeb4c8d5bef9426b025f75bdc72ba7913e0fd.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/41186543405abfea16b57da566ce9e509826f9b1b6337473d05d9421f53912aa.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/cda361ffde7db6bedfeb9f01a63dd51ebbe4b733d3c6be69cede7a681d20b583.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/108adf201cd9b479777504c8e7fb74299bbc2b51082d2872a34616abe6c743fb.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/47a3358d7989bf06c4ce1ecb350149b1669bf16073ea1d321371a02ad3507d63.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/0c25c0bd4df514cdd4531872553e053b74a3b9a60476871184b7e8c2a1b67048.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/936a9280b8b33ffaf98e9497927d7d84cfc87757bf235569fa950c55922ab76c.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/4cb136284aa9e19be2492e23d421297736f888ddf03cd7eebdb690d4f3b929c1.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/43f3dcad386912254bc1b2a6cd8414c3373f465665655584a0cf31b9c2f6ce6f.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/73c62366e6dda1ee960692d71e4ff9ba92d193b966986866793e64dec10fdc9b.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/d361cbbc4c1c8a5eeda8dda6173926c112f28f0bc49efc7946a0c218b4195fa3.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/eb0939417658eea85bdccd5b4c1c460f836649e442cad3100393e039f8f82fe6.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/5979b3d43ade0ddbf8aa86e3ef5e2951fb677bcc0a39d133433bd58989328130.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/82f2aaeedc6ddb9a8086912bd8064c5aac85437814d7ef6e5a6fb5e22bd71491.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/cf4ed99e5d88c5bbf18d812c6eb7a5b90f12e47f346b33061f6ad6c073d81be7.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/f5f95fe8ca532f13faeb1209981138d786f0df2e061d151fe655a571a8ddd88c.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/5a5f20fcb280a697e2f22c460d796fbd291481760480a764d6fe6a6c83e6380a.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/dba86bd8a4f75d2148b80fb04d525193b29b060fbf8a4743fe1b41e39c4fb25d.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/d7017ff3c9fbda9a376ff72d8a95eb9e0a5858cf50ee82a5b92d6e995550867d.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/a6bf758115a73beafa9124803667e93729442e7cad367a86608ad9ad8655b08b.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/24ae44003669f9f9640832b4c9cf9acb8ac3c2adf5ab5a2444a7715b090b3f67.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/b39aac6d9d0b5ea2ce78831602e48e0a48f7f2c792697e9c58de1d45b27a792a.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/9194df1b37d6a7da9ee8fd03abebdc3e81ec6ea366224eecb0cd7d3d66024062.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/f64b3e72b098f53f10ca3f3031b93df60c8ef407510bab8a003c9747e82f6043.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/6ff297c691797ab5fd5222b0c1fa13abc750fc031685a29589eace7748864318.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/8704bbe8a29455b6034d773c57246b1633da5393fd102f87fcb6eca819b82753.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/ef4146d94b8e32988b3cb0eba7e967cfa9627a683a1359cf00a1d76aa5022680.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/ebf974f9350e2f784145463f0afcccac69f265af0e8b233813617829684a290f.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/06a6aaede02e9527e1eb6dad977a7889e22d2dfcc098f9342eacc134c31363df.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/d915555e83e27c4d5f6dcd1badaaed666fa80e5ae11d6d2382e666efe606bd1f.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/a189554aa8b3185799fdb5bfb89cc42698c544a1041e65709b0e79d267cabdc5.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/0f310a87cae45484d5da35274ff89463eb966a1aeb32d53a2fb8350cf9d836b2.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/df7598066371b7da446954feb42a1febdb8921cacf436285e85606cae9de4bb5.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/ff8d8869f35a9a4243fac1ce8ab5deff7ee161dfc8c2ea1107099ec1cf74e100.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/75d36a907297689726ca96ef721c091c04a879f1f096f503076dd172834a27dc.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/5e6fee09c93c6f1d493bd04dd18cc8043c0b40093d85ed94c2df28ab129b226b.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/d7ddd06a2070b76dc05fe12741d7882df5a4312b174a11ce3d98d059fcc17173.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/7bcac404ee6981364837ac1a89033184ff65939507a81caa7c43ea52a195e215.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/392ac948386e63063f941449eeb9199c3f1a05959934c47c5987bbf6aa0721a3.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/df13e0aac1211176a1939bf2198f9e0e7dedd1f043875a4093ce0265cd02744e.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/052649971efbb0e18631044219b92ab68f12dc244041042b24203c88a62377b6.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/1e86ba82b4061a7ef799089ce29826fcba0ba07c77aec6638f20e850d1864144.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/7fe25319e6b2049b96c6659264568defe6a7e21bb3817685970b3a3aa511d8c8.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/3a66049907f84778aa548c747d9a52c2e67ce880c19fa4b0a8b4e58ae96fd9ef.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/54f622cb04525cc1953cc1ffbe12646be3290de6ca378ce5508869860df761e5.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/6fa117d5fcf115733a154f5d0911ce05b103ea33d5eee65d2b08a83605cf6e80.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/cc1d2b8828dc4dba9f0ea7d980eb8b24d2b0792c3282725552a56f7c8929459f.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/9b59194c025f9e6fc5c1d60ee444be69f14546b7efa4389a30ee6db88fbc207f.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/8686d00f5e82bb2343d8154fad3d66afca1420c45e4f63fe5decb6b9f5b84d2b.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/6b7ac224bb235b03798c358125eadc5d805445744543c368ebc0a4f7bb7a4328.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/dfb89354e0b433989eab276004296575f5b5e3ecb8c700cfcee620765ef0e74d.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/ef0656c2dce6c462c2e2591b5c43b76c1fe83ebebbaa778c1194706577c46d5c.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/7bfb6c4c45d0dcfbd66456456c5300b3ba83d52f37c5434e3e78baf0b54e5c07.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/c61ee02616c8d1c87be89499cb1ebdcd7267e47e51fa53e10578e8935a8b7aa9.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/f32062ec857d3d40d3a82359511167113065019d09d10755764aec91ba37bb32.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/d678ca3b06ea1872ebb20236ed1099e1b6e1451c51d78ab98d914abead7e4651.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/ee4c2e6785e3062be57305190a480ee437f6569471534533dd3524794b125ace.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/9a3ee31c30754a89da9280cbff44440e8e974bfac4c815376a0df768ef926590.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/334ae045a86998d77ed3fca093d7c43dfd5be53f939d156d9e71a885263739c1.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/65b4c081cd081eb8716e435c51abf7882f9aacb47005d77aa336ddf8190f2249.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/e2d38a33de7d378bf8a2989ae6db7b20d9168dd9b4c078225ced7aef9154a370.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/988ece4e5c191b30ff71dd2ce3fa3dc16f22dd3076702c17e9f3373612765c9c.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/272e01e003b0a4ed9b8ebb908d88cf1fbc841f65cf3c5994e99c7e5b10332209.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/eb1fd91a2d9b5feba4504c7ee8182ec680121e83a1571a292d52bf7cac12c396.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/ea4967bd3dec3fdcf48ba9682777ae09b1be48ae861a5fb55f8650fccb24aeb5.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/23653a7282567de2cc678619554153f1aff5409061b2a08ffedf208b10c7fd9f.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/6e4b9df052f0c4c0b726301c66e4aedc596a01416268857f3742c1eaf6760c64.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/7f82912066c187783d04de0b189314fcde9d33208335827a6d0fa8755637a136.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/f07293e9a6dc9a826363cb52fbc0db1f75fbba49814cb626db63affa74dbc9bc.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/46df84d2163240ae59efe0c91b94b3d23018daa816de1d44405275e17b5f4e77.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/1101fd494d1a226a790d5091f04fae9bcab5543eec8c80a0d3dd8b83a8d31c14.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/2f405ef994ff2c7470116d092ec7e9a8833400354479760742e616f800e19831.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/5f1efddc7e6c6bb7984813794cd275f0cf46d2bf598ac16cc7adc05c9878eb22.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/c641b68789fcc05feb518d6ea7dd5ffa1344c124263e67573c86786766547fa8.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/f9b35c5e0adbd9f0084559cd909d57a1dc8928b5c48887a1119d226226664270.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/3ab2ef4b9b5f1900ff51f892d531b2abc539f9acfb728c841f35dab93bde160f.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/c92c58cb868b0c698c9e24ea9dfb63f1f4587a04fbc0cc6d495058ebb7534f69.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/46cb9725c59d409c7e7603f9eab6d2dbd3b29e5d4aef2fb154d5ceda40a33f85.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/b4fd15d151bbcf2f299a24ccd2c7e94afae7d6eccf7208f65b06febc5958d95d.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/191e06e43ac8a9618a5c1f10178e7cdf6e609f14dc7ec56be2ea89ac19b5e253.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/69191aa596bfd2633279e0488152f67565ae47bf3e9e728b9c57376bfd2abdc3.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/222870114e13280aa20077b14588e6d2fa8e7a7b347cde4a01553e395fb40a22.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/ba3636d73e375a6f16a878752464adaa57a03614dbb3e2d68e26d08d686262c3.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/b9b7fb42891473d4906c7acb11f8680565eb02eabce71a8645f917bb1375c0d2.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/7d62853edb09b73996a0d4bb369067e45fc229926a8961207596a3162941dccd.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/c0bcad354275f905e235af359dd789db4110201a4ef1fd7f8d4aae3563ff06a5.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/66397bf25b427e4d910ca117e3ce8afb8e19012403c7a1716696eca2da261884.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/487b35586c4591772393a2d2f430d00f97c3d36ad8ee7be784f130249cac7c31.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/6b664a4b20fde7754d5448f129532fded9103284aa101b50b79f810246f75a3b.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/2752173075a4ef17451a0a3db546956389eff82db209711e4a1fff47e90b6065.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/265252b3db2c2501483502b6aadab3ea891f32cae539b5ccc7ef8295b63f4018.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/b5cc6b97c28bd853ebf7d853a7b19e4b5018c01eae8823ac537800c2cbb06011.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/c9b094709f2b2773b5e5258716df0663b2aee98a6ea47c3cb4040322123cb99c.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/7f158d1bfbafe42c7b3118d2b9ea701bda16df10dbdf9c4f2779ea15595a331f.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/518548841153bce3488afa3a36ad6e6cbddb4f1689f5e9366ee80e206b6a1ffe.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/629e460d17bf23cce5d75bbe8672e037db86d8d757cc4efd9a1a0f53d435425b.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/667ddeda218b4f95a47792345550d546e00fe2a52a505437d30608cefd0fc4bd.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/20d9f1018284a761162108e9a82d6a73b1fa8a9fd6866a506db77ed07ec5e578.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/1f35f42d565dd11860e30c41241c78bc5f06d724117bfc83b3784c66d52e332f.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/58cdc6432a1cd95f7f6427744019b59d164384edfc54aa51537d4685f847ba39.0 +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/journal +/data/data/com.google.android.googlequicksearchbox/cache/image_manager_disk_cache/61f774ab3a005d56366b267b08b994e5b035ff8eab1e454f00fb2cf7b356a46f.0 +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contacts +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contacts/prons.cache +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contacts/v1539635905559 +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contacts/v1539635905559/digest +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contacts/v1539635905559/semantic_fst +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contacts/v1539635905559/grammar_symbols +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contacts/v1539635905559/semantic_symbols +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contacts/v1539635905559/metadata +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contacts/v1539635905559/grammar_clg +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/hands_free_commands +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/hands_free_commands/v1536705472984 +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/hands_free_commands/v1536705472984/grammar_clg +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/hands_free_commands/v1536705472984/semantic_fst +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/hands_free_commands/v1536705472984/semantic_symbols +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/hands_free_commands/v1536705472984/digest +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/hands_free_commands/v1536705472984/metadata +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/hands_free_commands/v1536705472984/grammar_symbols +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/hands_free_commands/prons.cache +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/music_names +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/music_names/v1536705480879 +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/music_names/v1536705480879/metadata +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/music_names/v1536705480879/SONG_NAME.fst +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/music_names/v1536705480879/SONG_NAME.syms +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/music_names/v1536705480879/digest +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contact_names +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contact_names/v1539635914600 +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contact_names/v1539635914600/CONTACT_NAME.fst +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contact_names/v1539635914600/CONTACT_NAME.syms +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contact_names/v1539635914600/digest +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/contact_names/v1539635914600/metadata +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/app_names +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/app_names/v1543480552712 +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/app_names/v1543480552712/APP_NAME.fst +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/app_names/v1543480552712/digest +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/app_names/v1543480552712/metadata +/data/data/com.google.android.googlequicksearchbox/cache/g3_grammars/en-US/app_names/v1543480552712/APP_NAME.syms +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/79b5269c206115a4_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/aa9db037f918da1f_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/aebae57f6f7dcdb9_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/e446f170a9c613a1_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/2f06680d22ff6fbf_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/ffa3f495612db016_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/69c20684e88c955b_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/ef65bf506ba3e339_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/014821f96953c508_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/1180e087d9bd1160_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/3b36cd7b2f6df416_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/e5701f55e9ce22c8_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/9ec568d6b3dc0762_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/88f57d1088993219_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/83fd8318538fbe29_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/22930ed83887868c_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/333bf7ac47cc9770_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/adfd903f6a8ce876_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/2a1237e13688c120_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/3f3f18bf8e704931_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/b8262fc8c9591057_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/59829c5897cb9d93_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/index-dir +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/index-dir/the-real-index +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/feb5af6bca039e09_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/0050e1bcb6d6546c_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/3a3d22ec4fc7ad21_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/5f269f49d811cd82_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/e1cf52389fbebceb_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/359582e09cf26c7a_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/964347070fc23ea0_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/fb7d48b4e068afda_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/a5c586e8f0aeb850_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/37fadb6203e4e379_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/a7c25e80d95ef15d_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/a11922fc39ec0249_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/bebb870e573c852c_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/89c95cdfc9b59f48_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/fcb3fff3117a2d12_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/5c48229cf8e35d0b_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/55a3abf82b2a626c_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/f79ff1a77d9e9492_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/ca9ccf019443fb16_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/986246894e9084ec_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/e546ff051d5dbafc_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/23b6960b741da560_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/8c9509f47aa07ed8_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/81be3f3a1ebb3222_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/4edb09d9737acff2_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/e89950485ea68183_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/e40df7fec15afae9_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/cc338158aa28d723_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/4797a2fb8c7eac6b_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/411f64d386b7c4fe_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/29f6d5d8b27eb0c5_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/e99ae68f3e468751_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/502dafda143b5a74_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/d1783d0a170fdb8e_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/ac27a389f7bf6b67_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/40300177b5c0050d_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/4a8de756f1428237_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/e029cf1b0932611f_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/c69ff5c7e450ab22_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/bdfd0aa008d40005_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/40dd89dd968602d3_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/d04eb6456f31d2f7_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/ff4e7b79b6327627_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/b136f3771ffb9958_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/207bb56723cc5c3a_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/95035b9448e65cf2_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/73afbe7f6b7a496c_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/84b41c998e542199_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/d68d127f97a27059_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/037a0f2e4460355f_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/da584b3cb202e078_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/2d6c5245e29028b2_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/ca9b25d228896196_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/0e2708cf50936235_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/ab39283d30a39dd1_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/f721910d7c288b54_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/e1cd683779c2ea08_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/d1a8c9a323296d5b_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/4821c08320e603ae_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/01a2afcf422b3b4f_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/d41f0a4d475402fd_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/fcacc70d27c27f8b_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/f76d072b8c546a89_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/aff6b5b6e20cc2fa_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/8037b4d4c7774071_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/index +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/1e6d9e3ecd002bc9_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/3cfe648fbdd026a7_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/ab5d3ea4f0904068_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/9a785469b604c8af_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/472d78242cce2d22_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/503a7645e7f2d973_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/593a42d396c32634_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/b6ee82fb12843073_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/3561efa2281c73ed_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/318755c427839e86_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/3ef890a79ddb7e0c_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/d9772c6ee701ad39_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/5b3eff799688e021_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/30bb71565ae0cc27_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/54afe61c6fcf0e3f_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/8c9d078e6dbc501d_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/32a4f6fc17306385_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/0111bfd7286ca658_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/55ccaf33bd76fb46_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/65b80b5a552aaeaf_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/30fffcc41f7846bf_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/5377846224f95fc9_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/f9c93b74a177706b_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/94239554b50b59b5_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/14ced047ba93cdd3_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/2ceea49fe8c9e2fe_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/disk_cache/3ec7cdbc127eae35_0 +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/version +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/prefs +/data/data/com.google.android.googlequicksearchbox/cache/cronet-async/prefs/local_prefs.json +/data/data/com.google.android.googlequicksearchbox/cache/si +/data/data/com.google.android.googlequicksearchbox/databases +/data/data/com.google.android.googlequicksearchbox/databases/icing-mdh.db-wal +/data/data/com.google.android.googlequicksearchbox/databases/google_app_measurement.db-shm +/data/data/com.google.android.googlequicksearchbox/databases/icing-mdh.db-shm +/data/data/com.google.android.googlequicksearchbox/databases/google_app_measurement_local.db +/data/data/com.google.android.googlequicksearchbox/databases/google_app_measurement_local.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/databases/google_app_measurement.db-wal +/data/data/com.google.android.googlequicksearchbox/databases/google_app_measurement.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/databases/google_app_measurement.db +/data/data/com.google.android.googlequicksearchbox/databases/icing-mdh.db-wipecheck +/data/data/com.google.android.googlequicksearchbox/databases/launcher.db +/data/data/com.google.android.googlequicksearchbox/databases/launcher.db-wal +/data/data/com.google.android.googlequicksearchbox/databases/icing-mdh.db +/data/data/com.google.android.googlequicksearchbox/databases/launcher.db-shm +/data/data/com.google.android.googlequicksearchbox/shared_prefs +/data/data/com.google.android.googlequicksearchbox/shared_prefs/AccountSwitcherDrawerPresenter.Prefs.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/com.google.android.gms.appid.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/consecutive_crash_stats.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/uncaught_exception_handler_stats.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/VoiceInteractionService.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/interactor_process_uncaught_exception_handler_stats.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/WebViewChromiumPrefs.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/com.android.launcher3.managedusers.prefs.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/com.android.launcher3.prefs.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/GEL.GSAPrefs.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/com.google.android.gms.measurement.prefs.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/ThrottlingLogger.xml +/data/data/com.google.android.googlequicksearchbox/shared_prefs/default_process_uncaught_exception_handler_stats.xml +)F1L3N4M3"; + +#else + + static const char* kInodeValues = R"1N0D3( +66323@1117133 +66323@1127134 +66323@1137135 +66323@1137136 +66323@1137137 +)1N0D3"; + + const char* kPathNames = R"F1L3N4M3( +/ +/data/ +/data/data/ +/data/data/file +/data/data/last_file +)F1L3N4M3"; + +#endif + + + std::vector<std::string> inode_values = ParseLines(kInodeValues); + std::vector<std::string> path_names = ParseLines(kPathNames); + + std::vector<Inode> inodes = ParseInodes(inode_values); + + return PathEntryTree{ PathEntry::Zip(inodes, path_names) }; +} + +class FakeSystemCall : public SystemCall { + public: + // stat(2) + virtual int stat(const char *pathname, struct stat *statbuf) override { + if (pathname == nullptr || statbuf == nullptr) { + errno = EINVAL; + return -1; + } + + std::optional<PathEntry> maybe_path_entry = path_entries_.GetEntryFor(pathname); + + if (!maybe_path_entry) { + errno = ENOENT; + return -1; + } + + memset(statbuf, 0, sizeof(*statbuf)); + + Inode inode = maybe_path_entry->inode; + statbuf->st_dev = makedev(static_cast<int>(inode.device_major), + static_cast<int>(inode.device_minor)); + statbuf->st_ino = static_cast<ino_t>(inode.inode); + + return 0; + } + + static constexpr bool debug{false}; + +#define FS_LOG_DEBUG if (debug) LOG(DEBUG) + + // opendir(3) + virtual DIR *opendir(const char *name) override { + + FS_LOG_DEBUG << "opendir(" << name << ")"; + + std::string name_str{name}; + if (path_entries_.HasDirectory(name_str)) { + CHECK(!state_.open_); + + std::vector<PathEntry> children = path_entries_.OpenDirectory(name_str); + + state_ = State::Open(name_str, std::move(children)); + + FS_LOG_DEBUG << "opendir - success, state address: " << &state_; + + return get_state_as_dir(); + } + + FS_LOG_DEBUG << "opendir - no matching entry, scanned " << path_entries_.size(); + + // TODO. errno. + errno = EINVAL; + return nullptr; + } + + // readdir(3) + virtual struct dirent *readdir(DIR *dirp) override { + DCHECK(dirp != nullptr); + // We could also errno=EBADF but this seems more apropro to test. + + State* state = dir_to_state(dirp); + (void) state; + DCHECK(state != nullptr); + + std::optional<PathEntry> path_entry_opt = state->ReadDir(); + + if (!path_entry_opt) { + FS_LOG_DEBUG << "readdir(" << &state << ") - no children left "; + + // No more children left. We have exhausted them all. + return nullptr; + } + + PathEntry path_entry = *path_entry_opt; + + FS_LOG_DEBUG << "readdir(" << &state << ") - called for " << path_entry.path; + + // TODO. impelment this. + static struct dirent dir_ent{}; + + // Clear it again. + memset(&dir_ent, 0, sizeof(dir_ent)); + + dir_ent.d_ino = path_entry.inode.inode; + + FS_LOG_DEBUG << "readdir(" << &state << ") - children check" << path_entry.path; + + // Is this a file (no children) or a directory (some children)? + // + // In reality some directories might be empty too, but lets not worry about it yet. + std::vector<PathEntry> children = path_entries_.OpenDirectory(path_entry.path); + + if (children.empty()) { + dir_ent.d_type = DT_REG; + } else { + dir_ent.d_type = DT_DIR; + } + + // the d_name must be just the final name component of a path. + // Do not include the full path. + + std::string_view name_view = path_entry.path.Name(); + DCHECK_LT(name_view.size(), sizeof(dir_ent.d_name)); + + std::copy(name_view.begin(), + name_view.end(), + &dir_ent.d_name[0]); + dir_ent.d_name[name_view.size()] = '\0'; + + FS_LOG_DEBUG << "readdir(" << &state << ") - return , d_name=\"" << dir_ent.d_name << "\"" + << ", d_type=" << (dir_ent.d_type == DT_REG ? "DT_REG" : "DT_DIR"); + + return &dir_ent; + } + + // closedir(3) + virtual int closedir(DIR *dirp) override { + CHECK(dirp != nullptr); + State* state = dir_to_state(dirp); + state->Close(); + + return 0; + } + + FakeSystemCall() { + path_entries_ = CreateFakePathEntries(); + } + + private: + struct State { + std::string name_; + bool open_{false}; + std::vector<PathEntry> children; + + static State Open(std::string name, std::vector<PathEntry> children) { + return State{name, /*open*/true, std::move(children)}; + } + + std::optional<PathEntry> ReadDir() { + if (children.empty()) { + return {}; + } + + PathEntry last = children.back(); + children.pop_back(); + + return { std::move(last) }; + } + + void Close() { + CHECK(open_); + open_ = false; + } + }; + + DIR* get_state_as_dir() { + return reinterpret_cast<DIR*>(reinterpret_cast<void*>(&state_)); + } + + State* dir_to_state(DIR* dirp) { + return reinterpret_cast<State*>(reinterpret_cast<void*>(dirp)); + } + + State state_; + + PathEntryTree path_entries_; +}; + +class MockSystemCall : public SystemCall { + public: + INJECT(MockSystemCall()) { + // Delegate calls to a fake (see the googlemock CookBook for more details). + // https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md#delegating-calls-to-a-fake + DelegateToFake(); + + WorkAroundForNiceMock(); + } + + ~MockSystemCall() { + } + + MOCK_METHOD2(stat, int(const char *, struct stat *)); + MOCK_METHOD1(opendir, DIR*(const char *)); + MOCK_METHOD1(readdir, struct dirent*(DIR*)); + MOCK_METHOD1(closedir, int(DIR*)); + + // Delegates the default actions of the methods to a FakeSystemCall object. + // This must be called *before* the custom ON_CALL() statements. + void DelegateToFake() { + ON_CALL(*this, stat(_,_)) + .WillByDefault(Invoke(&fake_, &FakeSystemCall::stat)); + ON_CALL(*this, opendir(_)) + .WillByDefault(Invoke(&fake_, &FakeSystemCall::opendir)); + ON_CALL(*this, readdir(_)) + .WillByDefault(Invoke(&fake_, &FakeSystemCall::readdir)); + ON_CALL(*this, closedir(_)) + .WillByDefault(Invoke(&fake_, &FakeSystemCall::closedir)); + } + + void WorkAroundForNiceMock(); + + private: + FakeSystemCall fake_; +}; + +// Don't print any warnings when methods are executed without EXPECT_CALL. +//using NiceMockSystemCall = NiceMock<MockSystemCall>; + +// Can't use NiceMock<MockSystemCall> here, fails with this compilation error +// +// external/google-fruit/include/fruit/impl/injection_errors.h:107:3: error: static_assert failed due to requirement 'AlwaysFalse<NiceMock<MockSystemCall> >::value' "C::Inject is a signature, but does not return a C. Maybe the class C has no Inject typedef and inherited the base class' one? If that's not the case, make sure it returns just C, not C* or other types." +using NiceMockSystemCall = MockSystemCall; + +void MockSystemCall::WorkAroundForNiceMock() { + // Should be able to use NiceMock instead, but fruit is having problems compiling. + EXPECT_CALL(*this, stat).Times(AtLeast(0)); + EXPECT_CALL(*this, opendir).Times(AtLeast(0)); + EXPECT_CALL(*this, readdir).Times(AtLeast(0)); + EXPECT_CALL(*this, closedir).Times(AtLeast(0)); +} + +fruit::Component<SearchDirectories, NiceMockSystemCall> getTestComponents() { + return fruit::createComponent() + .bind<SystemCall, NiceMockSystemCall>(); +} + +// TODO: there might be a helper or similar to do this instead. +template <typename T> +static std::vector<T> subscribe_drain(std::pair<rxcpp::observable<T>, + std::unique_ptr<SearchDirectories::RxAnyConnectable>> pair) { + rxcpp::observable<T>& obs = pair.first; + std::unique_ptr<SearchDirectories::RxAnyConnectable>& connectable_ptr = pair.second; + + std::vector<T> vec; + + obs.subscribe([&vec](auto&& x) { + vec.push_back(IORAP_FORWARD_LAMBDA(x)); + }); + + CHECK(connectable_ptr != nullptr); + + // Execute above lambda, blocking until all values are drained. + connectable_ptr->connect(); + + return vec; +} + +struct SearchDirectoriesParam { + std::vector<std::string> root_directories; + std::vector<Inode> search_inodes; + std::vector<InodeResult> expected_results; +}; + +template <typename It> +std::ostream& iterator_to_stream(std::ostream& os, It begin, It end) { + os << "{"; + while (begin != end) { + os << *begin; + os << ","; + + ++begin; + } + os << "}"; + + return os; +} + +template <typename T> +std::ostream& container_to_stream(std::ostream& os, T&& c) { + return iterator_to_stream(os, c.begin(), c.end()); +} + +std::ostream& operator<<(std::ostream& os, const SearchDirectoriesParam& p) { + os << "{"; + os << "root_directories:"; + container_to_stream(os, p.root_directories); + os << ", "; + os << "search_inodes:"; + container_to_stream(os, p.search_inodes) << ", "; + os << "expected_results:"; + container_to_stream(os, p.expected_results); + os << "}"; + return os; +} + +struct SearchDirectoriesTest : + public ::testing::TestWithParam<SearchDirectoriesParam> { + + static void SetUpTestCase() { + ConfigureLogging(); + } + + virtual void SetUp() override { + auto pair = + search.FindFilenamesFromInodesPair(GetParam().root_directories, + GetParam().search_inodes, + SearchMode::kInProcessDirect); + + actual = subscribe_drain(std::move(pair)); + expected = GetParam().expected_results; + } + + virtual void TearDown() override { + // TODO. + } + + protected: + fruit::Injector<SearchDirectories, NiceMockSystemCall> injector{getTestComponents}; + + SearchDirectories& search = injector.get<SearchDirectories&>(); + MockSystemCall& mock_syscall = injector.get<NiceMockSystemCall&>(); + + std::vector<InodeResult> actual; + std::vector<InodeResult> expected; +}; + +TEST_P(SearchDirectoriesTest, ElementsAreArrayMatcher) { + EXPECT_THAT(actual, ElementsAreArray(expected)); +} + +auto MakeEmptyInodes(std::vector<std::string> root_dirs) { + return SearchDirectoriesParam{root_dirs, /*inodes*/{}, /*actual*/{}}; +} + +// When are are 0 inodes to search for, the results will be empty. +INSTANTIATE_TEST_CASE_P(EmptyResults, + SearchDirectoriesTest, + ::testing::Values( + MakeEmptyInodes(/*root_dirs*/{}), + MakeEmptyInodes(/*root_dirs*/{""}), + MakeEmptyInodes(/*root_dirs*/{"/"}), + MakeEmptyInodes(/*root_dirs*/{"/abc"}) + )); + + +auto MakeAllFailInodes(std::vector<std::string> root_dirs, std::vector<Inode> inodes) { + std::vector<InodeResult> results; + for (const Inode& inode : inodes) { + results.push_back(InodeResult::makeFailure(inode, InodeResult::kCouldNotFindFilename)); + } + + return SearchDirectoriesParam{root_dirs, inodes, results}; +} + +// TODO: fixme + +#if 1 + +// When none of the inodes can be found, all results will be failing results. +INSTANTIATE_TEST_CASE_P(AllResultsAreErrorCouldNotFindFilename, + SearchDirectoriesTest, + ::testing::Values( + // TODO: why is empty root dir failing? + // MakeAllFailInodes(/*root_dirs*/{}, {Inode{1,2,3}}), + MakeAllFailInodes(/*root_dirs*/{"/"}, {Inode{1,2,3}}), + MakeAllFailInodes(/*root_dirs*/{"/data"}, {Inode{1,2,3}}), + MakeAllFailInodes(/*root_dirs*/{"/data/data"}, {Inode{1,2,3}}) + )); + +auto MakeAllPassInodes(std::vector<std::string> root_dirs, std::vector<std::string> inodes, std::vector<std::string> paths) { + std::vector<InodeResult> results; + + std::vector<Inode> inodes_actual; + + size_t i = 0; + for (const std::string& inode_str : inodes) { + Inode inode; + std::string error_msg; + + CHECK(Inode::Parse(inode_str, &inode, &error_msg)); + + inodes_actual.push_back(inode); + + std::string& path = paths[i]; + results.push_back(InodeResult::makeSuccess(inode, path)); + + ++i; + } + + return SearchDirectoriesParam{root_dirs, inodes_actual, results}; +} + +// Find all the inodes. Yay. +INSTANTIATE_TEST_CASE_P(AllResultsAreSuccess, + SearchDirectoriesTest, + ::testing::Values( + MakeAllPassInodes(/*root_dirs*/{"/"}, {"66323@1127133"}, {"/data"}) + )); + +#endif |