fastbootd: add support to reset fd by handle

There are cases where the flags the file descriptor was opened with need
to be modified. This CL adds functionality to reset the file descriptor
held by a PartitionHandle, reopening with new flags and repositioning
file offset to previous.

Bug: 225108941
Signed-off-by: Konstantin Vyshetsky <vkon@google.com>
Change-Id: I9adb0e7696bc6af74e14dd61a6cb0ef10b4c98c8
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index 97b5ad4..3302c43 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -90,14 +90,7 @@
         return false;
     }
 
-    flags |= (O_EXCL | O_CLOEXEC | O_BINARY);
-    unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags)));
-    if (fd < 0) {
-        PLOG(ERROR) << "Failed to open block device: " << handle->path();
-        return false;
-    }
-    handle->set_fd(std::move(fd));
-    return true;
+    return handle->Open(flags);
 }
 
 std::optional<std::string> FindPhysicalPartition(const std::string& name) {
diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h
index 1d81b7a..6e1453f 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -18,6 +18,8 @@
 #include <optional>
 #include <string>
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <android/hardware/boot/1.0/IBootControl.h>
 #include <fstab/fstab.h>
@@ -44,11 +46,51 @@
     }
     const std::string& path() const { return path_; }
     int fd() const { return fd_.get(); }
-    void set_fd(android::base::unique_fd&& fd) { fd_ = std::move(fd); }
+    bool Open(int flags) {
+        flags |= (O_EXCL | O_CLOEXEC | O_BINARY);
 
+        // Attempts to open a second device can fail with EBUSY if the device is already open.
+        // Explicitly close any previously opened devices as unique_fd won't close them until
+        // after the attempt to open.
+        fd_.reset();
+
+        fd_ = android::base::unique_fd(TEMP_FAILURE_RETRY(open(path_.c_str(), flags)));
+        if (fd_ < 0) {
+            PLOG(ERROR) << "Failed to open block device: " << path_;
+            return false;
+        }
+        flags_ = flags;
+
+        return true;
+    }
+    bool Reset(int flags) {
+        if (fd_.ok() && (flags | O_EXCL | O_CLOEXEC | O_BINARY) == flags_) {
+            return true;
+        }
+
+        off_t offset = fd_.ok() ? lseek(fd_.get(), 0, SEEK_CUR) : 0;
+        if (offset < 0) {
+            PLOG(ERROR) << "Failed lseek on block device: " << path_;
+            return false;
+        }
+
+        sync();
+
+        if (Open(flags) == false) {
+            return false;
+        }
+
+        if (lseek(fd_.get(), offset, SEEK_SET) != offset) {
+            PLOG(ERROR) << "Failed lseek on block device: " << path_;
+            return false;
+        }
+
+        return true;
+    }
   private:
     std::string path_;
     android::base::unique_fd fd_;
+    int flags_;
     std::function<void()> closer_;
 };