Add functions to handle idle maintenance

runIdleMaint is equivalent with:

1. echo 1 > /sys/fs/f2fs/sdX/gc_urgent
2. wait until /sys/fs/f2fs/sdX/dirty_segments
     <= threshold or timeout
3. echo 0 > /sys/fs/f2fs/sdX/gc_urgent
4. fstrim

abortIdleMaint forces the wait loop above to exit and
skips fstrim. However, if fstrim is already running,
abortIdleMaint will just leave it run to completion.

Test: adb shell sm idle-maint [run|abort]
Bug: 67776637
Change-Id: I4adff8d9b6bbd63bce41368cea55dc9e9b117eb6
diff --git a/IdleMaint.cpp b/IdleMaint.cpp
index ed6374f..62086cd 100644
--- a/IdleMaint.cpp
+++ b/IdleMaint.cpp
@@ -17,7 +17,12 @@
 #include "IdleMaint.h"
 #include "Utils.h"
 #include "VolumeManager.h"
+#include "model/PrivateVolume.h"
 
+#include <thread>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <android-base/logging.h>
 #include <fs_mgr.h>
@@ -31,26 +36,60 @@
 #include <sys/wait.h>
 #include <fcntl.h>
 
+using android::base::Basename;
+using android::base::ReadFileToString;
+using android::base::Realpath;
 using android::base::StringPrintf;
+using android::base::Timer;
+using android::base::WriteStringToFile;
 
 namespace android {
 namespace vold {
 
-static const char* kWakeLock = "IdleMaint";
+enum class PathTypes {
+    kMountPoint = 1,
+    kBlkDevice,
+};
 
-static void addFromVolumeManager(std::list<std::string>* paths) {
+enum class IdleMaintStats {
+    kStopped = 1,
+    kRunning,
+    kAbort,
+};
+
+static const char* kWakeLock = "IdleMaint";
+static const int DIRTY_SEGMENTS_THRESHOLD = 100;
+static const int GC_TIMEOUT_SEC = 480;
+
+static IdleMaintStats idle_maint_stat(IdleMaintStats::kStopped);
+static std::condition_variable cv_abort, cv_stop;
+static std::mutex cv_m;
+
+static void addFromVolumeManager(std::list<std::string>* paths,
+                                 PathTypes path_type) {
     VolumeManager* vm = VolumeManager::Instance();
     std::list<std::string> privateIds;
     vm->listVolumes(VolumeBase::Type::kPrivate, privateIds);
     for (const auto& id : privateIds) {
-        auto vol = vm->findVolume(id);
+        PrivateVolume* vol = static_cast<PrivateVolume*>(vm->findVolume(id).get());
         if (vol != nullptr && vol->getState() == VolumeBase::State::kMounted) {
-            paths->push_back(vol->getPath());
+            if (path_type == PathTypes::kMountPoint) {
+                paths->push_back(vol->getPath());
+            } else if (path_type == PathTypes::kBlkDevice) {
+                std::string gc_path;
+                const std::string& fs_type = vol->getFsType();
+                if (fs_type == "f2fs" &&
+                    Realpath(vol->getRawDevPath(), &gc_path)) {
+                    paths->push_back(std::string("/sys/fs/") + fs_type +
+                                     "/" + Basename(gc_path));
+                }
+            }
+
         }
     }
 }
 
-static void addFromFstab(std::list<std::string>* paths) {
+static void addFromFstab(std::list<std::string>* paths, PathTypes path_type) {
     std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(),
                                                                fs_mgr_free_fstab);
     struct fstab_rec *prev_rec = NULL;
@@ -79,7 +118,17 @@
             continue;
         }
 
-        paths->push_back(fstab->recs[i].mount_point);
+        if (path_type == PathTypes::kMountPoint) {
+            paths->push_back(fstab->recs[i].mount_point);
+        } else if (path_type == PathTypes::kBlkDevice) {
+            std::string gc_path;
+            if (std::string(fstab->recs[i].fs_type) == "f2fs" &&
+                Realpath(fstab->recs[i].blk_device, &gc_path)) {
+                paths->push_back(std::string("/sys/fs/") + fstab->recs[i].fs_type +
+                                 "/" + Basename(gc_path));
+            }
+        }
+
         prev_rec = &fstab->recs[i];
     }
 }
@@ -89,8 +138,8 @@
 
     // Collect both fstab and vold volumes
     std::list<std::string> paths;
-    addFromFstab(&paths);
-    addFromVolumeManager(&paths);
+    addFromFstab(&paths, PathTypes::kMountPoint);
+    addFromVolumeManager(&paths, PathTypes::kMountPoint);
 
     for (const auto& path : paths) {
         LOG(DEBUG) << "Starting trim of " << path;
@@ -138,5 +187,136 @@
     release_wake_lock(kWakeLock);
 }
 
+static bool waitForGc(const std::list<std::string>& paths) {
+    std::unique_lock<std::mutex> lk(cv_m, std::defer_lock);
+    bool stop = false, aborted = false;
+    Timer timer;
+
+    while (!stop && !aborted) {
+        stop = true;
+        for (const auto& path : paths) {
+            std::string dirty_segments;
+            if (!ReadFileToString(path + "/dirty_segments", &dirty_segments)) {
+                PLOG(WARNING) << "Reading dirty_segments failed in " << path;
+                continue;
+            }
+            if (std::stoi(dirty_segments) > DIRTY_SEGMENTS_THRESHOLD) {
+                stop = false;
+                break;
+            }
+        }
+
+        if (stop) break;
+
+        if (timer.duration() >= std::chrono::seconds(GC_TIMEOUT_SEC)) {
+            LOG(WARNING) << "GC timeout";
+            break;
+        }
+
+        lk.lock();
+        aborted = cv_abort.wait_for(lk, 10s, []{
+            return idle_maint_stat == IdleMaintStats::kAbort;});
+        lk.unlock();
+    }
+
+    return aborted;
+}
+
+static int startGc(const std::list<std::string>& paths) {
+    for (const auto& path : paths) {
+        LOG(DEBUG) << "Start GC on " << path;
+        if (!WriteStringToFile("1", path + "/gc_urgent")) {
+            PLOG(WARNING) << "Start GC failed on " << path;
+        }
+    }
+    return android::OK;
+}
+
+static int stopGc(const std::list<std::string>& paths) {
+    for (const auto& path : paths) {
+        LOG(DEBUG) << "Stop GC on " << path;
+        if (!WriteStringToFile("0", path + "/gc_urgent")) {
+            PLOG(WARNING) << "Stop GC failed on " << path;
+        }
+    }
+    return android::OK;
+}
+
+int RunIdleMaint(const android::sp<android::os::IVoldTaskListener>& listener) {
+    std::unique_lock<std::mutex> lk(cv_m);
+    if (idle_maint_stat != IdleMaintStats::kStopped) {
+        LOG(DEBUG) << "idle maintenance is already running";
+        if (listener) {
+            android::os::PersistableBundle extras;
+            listener->onFinished(0, extras);
+        }
+        return android::OK;
+    }
+    idle_maint_stat = IdleMaintStats::kRunning;
+    lk.unlock();
+
+    LOG(DEBUG) << "idle maintenance started";
+
+    acquire_wake_lock(PARTIAL_WAKE_LOCK, kWakeLock);
+
+    std::list<std::string> paths;
+    addFromFstab(&paths, PathTypes::kBlkDevice);
+    addFromVolumeManager(&paths, PathTypes::kBlkDevice);
+
+    startGc(paths);
+
+    bool gc_aborted = waitForGc(paths);
+
+    stopGc(paths);
+
+    lk.lock();
+    idle_maint_stat = IdleMaintStats::kStopped;
+    lk.unlock();
+
+    cv_stop.notify_one();
+
+    if (!gc_aborted) {
+        Trim(nullptr);
+    }
+
+    if (listener) {
+        android::os::PersistableBundle extras;
+        listener->onFinished(0, extras);
+    }
+
+    LOG(DEBUG) << "idle maintenance completed";
+
+    release_wake_lock(kWakeLock);
+
+    return android::OK;
+}
+
+int AbortIdleMaint(const android::sp<android::os::IVoldTaskListener>& listener) {
+    acquire_wake_lock(PARTIAL_WAKE_LOCK, kWakeLock);
+
+    std::unique_lock<std::mutex> lk(cv_m);
+    if (idle_maint_stat != IdleMaintStats::kStopped) {
+        idle_maint_stat = IdleMaintStats::kAbort;
+        lk.unlock();
+        cv_abort.notify_one();
+        lk.lock();
+        LOG(DEBUG) << "aborting idle maintenance";
+        cv_stop.wait(lk, []{
+            return idle_maint_stat == IdleMaintStats::kStopped;});
+    }
+    lk.unlock();
+
+    if (listener) {
+        android::os::PersistableBundle extras;
+        listener->onFinished(0, extras);
+    }
+
+    release_wake_lock(kWakeLock);
+
+    LOG(DEBUG) << "idle maintenance stopped";
+
+    return android::OK;
+}
+
 }  // namespace vold
 }  // namespace android