Merge "Looper: Fixed compile error when enabling DEBUG_CALLBACKS" into main
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index faea5eb..35c8c63 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -1388,6 +1388,8 @@
         return {FS_MGR_MNTALL_FAIL, userdata_mounted};
     }
 
+    bool scratch_can_be_mounted = true;
+
     // Keep i int to prevent unsigned integer overflow from (i = top_idx - 1),
     // where top_idx is 0. It will give SIGABRT
     for (int i = 0; i < static_cast<int>(fstab->size()); i++) {
@@ -1520,6 +1522,9 @@
             if (current_entry.mount_point == "/data") {
                 userdata_mounted = true;
             }
+
+            MountOverlayfs(attempted_entry, &scratch_can_be_mounted);
+
             // Success!  Go get the next one.
             continue;
         }
@@ -1604,10 +1609,6 @@
 
     set_type_property(encryptable);
 
-#if ALLOW_ADBD_DISABLE_VERITY == 1  // "userdebug" build
-    fs_mgr_overlayfs_mount_all(fstab);
-#endif
-
     if (error_count) {
         return {FS_MGR_MNTALL_FAIL, userdata_mounted};
     } else {
diff --git a/fs_mgr/fs_mgr_overlayfs_control.cpp b/fs_mgr/fs_mgr_overlayfs_control.cpp
index 50d8280..9be236d 100644
--- a/fs_mgr/fs_mgr_overlayfs_control.cpp
+++ b/fs_mgr/fs_mgr_overlayfs_control.cpp
@@ -347,33 +347,6 @@
     return "";
 }
 
-// This returns the scratch device that was detected during early boot (first-
-// stage init). If the device was created later, for example during setup for
-// the adb remount command, it can return an empty string since it does not
-// query ImageManager. (Note that ImageManager in first-stage init will always
-// use device-mapper, since /data is not available to use loop devices.)
-static std::string GetBootScratchDevice() {
-    // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
-    if (fs_mgr_is_dsu_running()) {
-        return GetDsuScratchDevice();
-    }
-
-    auto& dm = DeviceMapper::Instance();
-
-    // If there is a scratch partition allocated in /data or on super, we
-    // automatically prioritize that over super_other or system_other.
-    // Some devices, for example, have a write-protected eMMC and the
-    // super partition cannot be used even if it exists.
-    std::string device;
-    auto partition_name = android::base::Basename(kScratchMountPoint);
-    if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
-        dm.GetDmDevicePathByName(partition_name, &device)) {
-        return device;
-    }
-
-    return "";
-}
-
 bool MakeScratchFilesystem(const std::string& scratch_device) {
     // Force mkfs by design for overlay support of adb remount, simplify and
     // thus do not rely on fsck to correct problems that could creep in.
@@ -922,6 +895,33 @@
     }
 }
 
+// This returns the scratch device that was detected during early boot (first-
+// stage init). If the device was created later, for example during setup for
+// the adb remount command, it can return an empty string since it does not
+// query ImageManager. (Note that ImageManager in first-stage init will always
+// use device-mapper, since /data is not available to use loop devices.)
+std::string GetBootScratchDevice() {
+    // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
+    if (fs_mgr_is_dsu_running()) {
+        return GetDsuScratchDevice();
+    }
+
+    auto& dm = DeviceMapper::Instance();
+
+    // If there is a scratch partition allocated in /data or on super, we
+    // automatically prioritize that over super_other or system_other.
+    // Some devices, for example, have a write-protected eMMC and the
+    // super partition cannot be used even if it exists.
+    std::string device;
+    auto partition_name = android::base::Basename(kScratchMountPoint);
+    if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
+        dm.GetDmDevicePathByName(partition_name, &device)) {
+        return device;
+    }
+
+    return "";
+}
+
 void TeardownAllOverlayForMountPoint(const std::string& mount_point) {
     if (!OverlayfsTeardownAllowed()) {
         return;
diff --git a/fs_mgr/fs_mgr_overlayfs_control.h b/fs_mgr/fs_mgr_overlayfs_control.h
index b175101..3516c46 100644
--- a/fs_mgr/fs_mgr_overlayfs_control.h
+++ b/fs_mgr/fs_mgr_overlayfs_control.h
@@ -38,5 +38,7 @@
 
 void CleanupOldScratchFiles();
 
+std::string GetBootScratchDevice();
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/fs_mgr_overlayfs_mount.cpp b/fs_mgr/fs_mgr_overlayfs_mount.cpp
index cdbac00..e168436 100644
--- a/fs_mgr/fs_mgr_overlayfs_mount.cpp
+++ b/fs_mgr/fs_mgr_overlayfs_mount.cpp
@@ -42,15 +42,14 @@
 #include <fs_mgr/file_wait.h>
 #include <fs_mgr_overlayfs.h>
 #include <fstab/fstab.h>
-#include <libdm/dm.h>
 #include <libgsi/libgsi.h>
 #include <storage_literals/storage_literals.h>
 
+#include "fs_mgr_overlayfs_control.h"
 #include "fs_mgr_overlayfs_mount.h"
 #include "fs_mgr_priv.h"
 
 using namespace std::literals;
-using namespace android::dm;
 using namespace android::fs_mgr;
 using namespace android::storage_literals;
 
@@ -302,6 +301,25 @@
     return true;
 }
 
+static bool fs_mgr_overlayfs_mount(const std::string& mount_point, const std::string& options) {
+    auto report = "__mount(source=overlay,target="s + mount_point + ",type=overlay";
+    for (const auto& opt : android::base::Split(options, ",")) {
+        if (android::base::StartsWith(opt, kUpperdirOption)) {
+            report = report + "," + opt;
+            break;
+        }
+    }
+    report = report + ")=";
+    auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
+                     options.c_str());
+    if (ret) {
+        PERROR << report << ret;
+    } else {
+        LINFO << report << ret;
+    }
+    return !ret;
+}
+
 struct mount_info {
     std::string mount_point;
     bool shared_flag;
@@ -488,25 +506,7 @@
         moved_mounts.push_back(std::move(new_entry));
     }
 
-    // hijack __mount() report format to help triage
-    auto report = "__mount(source=overlay,target="s + mount_point + ",type=overlay";
-    const auto opt_list = android::base::Split(options, ",");
-    for (const auto& opt : opt_list) {
-        if (android::base::StartsWith(opt, kUpperdirOption)) {
-            report = report + "," + opt;
-            break;
-        }
-    }
-    report = report + ")=";
-
-    auto ret = mount("overlay", mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
-                     options.c_str());
-    if (ret) {
-        retval = false;
-        PERROR << report << ret;
-    } else {
-        LINFO << report << ret;
-    }
+    retval &= fs_mgr_overlayfs_mount(mount_point, options);
 
     // Move submounts back.
     for (const auto& entry : moved_mounts) {
@@ -592,45 +592,6 @@
     return true;
 }
 
-// Note: The scratch partition of DSU is managed by gsid, and should be initialized during
-// first-stage-mount. Just check if the DM device for DSU scratch partition is created or not.
-static std::string GetDsuScratchDevice() {
-    auto& dm = DeviceMapper::Instance();
-    std::string device;
-    if (dm.GetState(android::gsi::kDsuScratch) != DmDeviceState::INVALID &&
-        dm.GetDmDevicePathByName(android::gsi::kDsuScratch, &device)) {
-        return device;
-    }
-    return "";
-}
-
-// This returns the scratch device that was detected during early boot (first-
-// stage init). If the device was created later, for example during setup for
-// the adb remount command, it can return an empty string since it does not
-// query ImageManager. (Note that ImageManager in first-stage init will always
-// use device-mapper, since /data is not available to use loop devices.)
-static std::string GetBootScratchDevice() {
-    // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
-    if (fs_mgr_is_dsu_running()) {
-        return GetDsuScratchDevice();
-    }
-
-    auto& dm = DeviceMapper::Instance();
-
-    // If there is a scratch partition allocated in /data or on super, we
-    // automatically prioritize that over super_other or system_other.
-    // Some devices, for example, have a write-protected eMMC and the
-    // super partition cannot be used even if it exists.
-    std::string device;
-    auto partition_name = android::base::Basename(kScratchMountPoint);
-    if (dm.GetState(partition_name) != DmDeviceState::INVALID &&
-        dm.GetDmDevicePathByName(partition_name, &device)) {
-        return device;
-    }
-
-    return "";
-}
-
 // NOTE: OverlayfsSetupAllowed() must be "stricter" than OverlayfsTeardownAllowed().
 // Setup is allowed only if teardown is also allowed.
 bool OverlayfsSetupAllowed(bool verbose) {
@@ -814,3 +775,38 @@
     }
     return false;
 }
+
+namespace android {
+namespace fs_mgr {
+
+void MountOverlayfs(const FstabEntry& fstab_entry, bool* scratch_can_be_mounted) {
+    if (!OverlayfsSetupAllowed()) {
+        return;
+    }
+    const auto candidates = fs_mgr_overlayfs_candidate_list({fstab_entry});
+    if (candidates.empty()) {
+        return;
+    }
+    const auto& entry = candidates.front();
+    if (fs_mgr_is_verity_enabled(entry)) {
+        return;
+    }
+    const auto mount_point = fs_mgr_mount_point(entry.mount_point);
+    if (fs_mgr_overlayfs_already_mounted(mount_point)) {
+        return;
+    }
+    if (*scratch_can_be_mounted) {
+        *scratch_can_be_mounted = false;
+        if (!fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
+            TryMountScratch();
+        }
+    }
+    const auto options = fs_mgr_get_overlayfs_options(entry);
+    if (options.empty()) {
+        return;
+    }
+    fs_mgr_overlayfs_mount(mount_point, options);
+}
+
+}  // namespace fs_mgr
+}  // namespace android
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index bdaabbf..bf68b2c 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -30,6 +30,9 @@
 namespace android {
 namespace fs_mgr {
 
+// Mount the overlayfs override for |fstab_entry|.
+void MountOverlayfs(const FstabEntry& fstab_entry, bool* scratch_can_be_mounted);
+
 void MapScratchPartitionIfNeeded(Fstab* fstab,
                                  const std::function<bool(const std::set<std::string>&)>& init);
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 5e5546d..debe87e 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -16,6 +16,7 @@
 
 #include <stdint.h>
 
+#include <limits>
 #include <optional>
 #include <string_view>
 
@@ -119,10 +120,30 @@
     uint32_t compression_algorithm;
 } __attribute__((packed));
 
+enum class CowOperationType : uint8_t {
+    kCowCopyOp = 1,
+    kCowReplaceOp = 2,
+    kCowZeroOp = 3,
+    kCowLabelOp = 4,
+    kCowClusterOp = 5,
+    kCowXorOp = 6,
+    kCowSequenceOp = 7,
+    kCowFooterOp = std::numeric_limits<uint8_t>::max(),
+};
+
+static constexpr CowOperationType kCowCopyOp = CowOperationType::kCowCopyOp;
+static constexpr CowOperationType kCowReplaceOp = CowOperationType::kCowReplaceOp;
+static constexpr CowOperationType kCowZeroOp = CowOperationType::kCowZeroOp;
+static constexpr CowOperationType kCowLabelOp = CowOperationType::kCowLabelOp;
+static constexpr CowOperationType kCowClusterOp = CowOperationType::kCowClusterOp;
+static constexpr CowOperationType kCowXorOp = CowOperationType::kCowXorOp;
+static constexpr CowOperationType kCowSequenceOp = CowOperationType::kCowSequenceOp;
+static constexpr CowOperationType kCowFooterOp = CowOperationType::kCowFooterOp;
+
 // This structure is the same size of a normal Operation, but is repurposed for the footer.
 struct CowFooterOperation {
     // The operation code (always kCowFooterOp).
-    uint8_t type;
+    CowOperationType type;
 
     // If this operation reads from the data section of the COW, this contains
     // the compression type of that data (see constants below).
@@ -141,7 +162,7 @@
 // V2 version of COW. On disk format for older devices
 struct CowOperationV2 {
     // The operation code (see the constants and structures below).
-    uint8_t type;
+    CowOperationType type;
 
     // If this operation reads from the data section of the COW, this contains
     // the compression type of that data (see constants below).
@@ -176,7 +197,7 @@
 // The on disk format of cow (currently ==  CowOperation)
 struct CowOperationV3 {
     // The operation code (see the constants and structures below).
-    uint8_t type;
+    CowOperationType type;
 
     // If this operation reads from the data section of the COW, this contains
     // the length.
@@ -201,15 +222,6 @@
 
 static_assert(sizeof(CowOperationV2) == sizeof(CowFooterOperation));
 
-static constexpr uint8_t kCowCopyOp = 1;
-static constexpr uint8_t kCowReplaceOp = 2;
-static constexpr uint8_t kCowZeroOp = 3;
-static constexpr uint8_t kCowLabelOp = 4;
-static constexpr uint8_t kCowClusterOp = 5;
-static constexpr uint8_t kCowXorOp = 6;
-static constexpr uint8_t kCowSequenceOp = 7;
-static constexpr uint8_t kCowFooterOp = -1;
-
 enum CowCompressionAlgorithm : uint8_t {
     kCowCompressNone = 0,
     kCowCompressGz = 1,
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
index 937065d..4afd026 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
@@ -30,7 +30,7 @@
 
 using android::base::unique_fd;
 
-std::ostream& EmitCowTypeString(std::ostream& os, uint8_t cow_type) {
+std::ostream& EmitCowTypeString(std::ostream& os, CowOperationType cow_type) {
     switch (cow_type) {
         case kCowCopyOp:
             return os << "kCowCopyOp";
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
index 37324c7..f9a4e47 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
@@ -369,7 +369,7 @@
 }
 
 bool CowWriterV2::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
-                             uint64_t old_block, uint16_t offset, uint8_t type) {
+                             uint64_t old_block, uint16_t offset, CowOperationType type) {
     CHECK(!merge_in_progress_);
     const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
index 24170eb..50e635f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
@@ -42,7 +42,7 @@
     bool EmitCluster();
     bool EmitClusterIfNeeded();
     bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
-                    uint16_t offset, uint8_t type);
+                    uint16_t offset, CowOperationType type);
     void SetupHeaders();
     void SetupWriteOptions();
     bool ParseOptions();
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index b36c6f3..767f3d5 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -230,7 +230,7 @@
 }
 
 bool CowWriterV3::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
-                             uint64_t old_block, uint16_t offset, uint8_t type) {
+                             uint64_t old_block, uint16_t offset, CowOperationType type) {
     const size_t num_blocks = (size / header_.block_size);
     for (size_t i = 0; i < num_blocks; i++) {
         const uint8_t* const iter =
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index 3dfc33c..340218f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -46,7 +46,7 @@
     bool OpenForAppend(uint64_t label);
     bool WriteOperation(const CowOperationV3& op, const void* data = nullptr, size_t size = 0);
     bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
-                    uint16_t offset, uint8_t type);
+                    uint16_t offset, CowOperationType type);
     bool CompressBlocks(size_t num_blocks, const void* data);
 
   private:
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index ebaca2d..0396a55 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -227,8 +227,12 @@
         if (file_offset >= dev_sz) {
             break;
         }
+
+        if (fsync(cfd.get()) < 0) {
+            PLOG(ERROR) << "Fsync failed at offset: " << file_offset << " size: " << to_read;
+            return false;
+        }
     }
-    fsync(cfd.get());
     return true;
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index 571b352..b24844d 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -191,7 +191,8 @@
         }
 
         default: {
-            SNAP_LOG(ERROR) << "Unsupported operation-type found: " << cow_op->type;
+            SNAP_LOG(ERROR) << "Unsupported operation-type found: "
+                            << static_cast<uint8_t>(cow_op->type);
         }
     }
     return false;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
index 5cb13e8..906316e 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
@@ -183,7 +183,8 @@
         }
 
         default: {
-            SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+            SNAP_LOG(ERROR) << "Unknown operation-type found: "
+                            << static_cast<uint8_t>(cow_op->type);
         }
     }
     return false;
diff --git a/init/service_test.cpp b/init/service_test.cpp
index 87a2ce5..c81b007 100644
--- a/init/service_test.cpp
+++ b/init/service_test.cpp
@@ -17,18 +17,45 @@
 #include "service.h"
 
 #include <algorithm>
+#include <fstream>
 #include <memory>
 #include <type_traits>
 #include <vector>
 
 #include <gtest/gtest.h>
 
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <selinux/selinux.h>
+#include <sys/signalfd.h>
 #include "lmkd_service.h"
+#include "reboot.h"
+#include "service.h"
+#include "service_list.h"
+#include "service_parser.h"
 #include "util.h"
 
+using ::android::base::ReadFileToString;
+using ::android::base::StringPrintf;
+using ::android::base::StringReplace;
+using ::android::base::unique_fd;
+using ::android::base::WriteStringToFd;
+using ::android::base::WriteStringToFile;
+
 namespace android {
 namespace init {
 
+static std::string GetSecurityContext() {
+    char* ctx;
+    if (getcon(&ctx) == -1) {
+        ADD_FAILURE() << "Failed to call getcon : " << strerror(errno);
+    }
+    std::string result{ctx};
+    freecon(ctx);
+    return result;
+}
+
 TEST(service, pod_initialized) {
     constexpr auto memory_size = sizeof(Service);
     alignas(alignof(Service)) unsigned char old_memory[memory_size];
@@ -190,5 +217,64 @@
     Test_make_temporary_oneshot_service(false, false, false, false, false);
 }
 
+// Returns the path in the v2 cgroup hierarchy for a given process in the format /uid_%d/pid_%d.
+static std::string CgroupPath(pid_t pid) {
+    std::string cgroup_path = StringPrintf("/proc/%d/cgroup", pid);
+    std::ifstream is(cgroup_path, std::ios::in);
+    std::string line;
+    while (std::getline(is, line)) {
+        if (line.substr(0, 3) == "0::") {
+            return line.substr(3);
+        }
+    }
+    return {};
+}
+
+class ServiceStopTest : public testing::TestWithParam<bool> {};
+
+// Before November 2023, processes that were migrated to another v2 cgroup were ignored by
+// Service::Stop() if their uid_%d/pid_%d cgroup directory got removed. This test, if run with the
+// parameter set to 'true', verifies that such services are stopped.
+TEST_P(ServiceStopTest, stop) {
+    static constexpr std::string_view kServiceName = "ServiceA";
+    static constexpr std::string_view kScriptTemplate = R"init(
+service $name /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+)init";
+
+    std::string script = StringReplace(StringReplace(kScriptTemplate, "$name", kServiceName, false),
+                                       "$selabel", GetSecurityContext(), false);
+    ServiceList& service_list = ServiceList::GetInstance();
+    Parser parser;
+    parser.AddSectionParser("service",
+                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+
+    TemporaryFile tf;
+    ASSERT_GE(tf.fd, 0);
+    ASSERT_TRUE(WriteStringToFd(script, tf.fd));
+    ASSERT_TRUE(parser.ParseConfig(tf.path));
+
+    Service* const service = ServiceList::GetInstance().FindService(kServiceName);
+    ASSERT_NE(service, nullptr);
+    ASSERT_RESULT_OK(service->Start());
+    ASSERT_TRUE(service->IsRunning());
+    if (GetParam()) {
+        const pid_t pid = service->pid();
+        const std::string cgroup_path = CgroupPath(pid);
+        EXPECT_NE(cgroup_path, "");
+        EXPECT_NE(cgroup_path, "/");
+        const std::string pid_str = std::to_string(pid);
+        EXPECT_TRUE(WriteStringToFile(pid_str, "/sys/fs/cgroup/cgroup.procs"));
+        EXPECT_EQ(CgroupPath(pid), "/");
+        EXPECT_EQ(rmdir(("/sys/fs/cgroup" + cgroup_path).c_str()), 0);
+    }
+    EXPECT_EQ(0, StopServicesAndLogViolations({service->name()}, 10s, /*terminate=*/true));
+    ServiceList::GetInstance().RemoveService(*service);
+}
+
+INSTANTIATE_TEST_SUITE_P(service, ServiceStopTest, testing::Values(false, true));
+
 }  // namespace init
 }  // namespace android
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 76868bb..f594f7f 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -378,8 +378,11 @@
         fd.reset(fopen(path.c_str(), "re"));
         if (!fd) {
             if (errno == ENOENT) {
-                // This happens when process is already dead
-                return 0;
+                // This happens when the process is already dead or if, as the result of a bug, it
+                // has been migrated to another cgroup. An example of a bug that can cause migration
+                // to another cgroup is using the JoinCgroup action with a cgroup controller that
+                // has been activated in the v2 cgroup hierarchy.
+                goto kill;
             }
             PLOG(WARNING) << __func__ << " failed to open process cgroup uid " << uid << " pid "
                           << initialPid;
@@ -418,6 +421,7 @@
         }
     }
 
+kill:
     // Kill all process groups.
     for (const auto pgid : pgids) {
         LOG(VERBOSE) << "Killing process group " << -pgid << " in uid " << uid