vold: skip first disk change when converting MBR to GPT

When converting a public (MBR) partition to private (GPT) partition,
'sgdisk --zap-all <path>' triggers a disk change netlink event
when converting from MBR to GPT.  Then, 'sgdisk --new=....' triggers
another disk change netlink event.

vold informs clients a new volume is created after the first disk
change event occurs.  system server reacts by requesting to mount
the volume.  If this request is honored before the second disk change
event, the volume will be unmounted immediately after system server's
request to mount is honored.  The next time system server performs
an operation (createnewuser) on this volume, it will fail due to
the volume being unmounted.

This is reproduced by running the following commands in a loop:

adb shell sm partition <disk> private
adb shell sm partition <disk> public
adb shell sm forget all

OR

run cts -c com.android.cts.appsecurity.AdoptableHostTest -m testPackageInstaller

This change causes vold to delay notifying clients that the volume is
ready until after it's actually partitioned.

CYNGNOS-2283
Change-Id: I457cc1508573d73ef2be2f0cfdc5c2237bfabad7
diff --git a/model/Disk.cpp b/model/Disk.cpp
index 4df4e9d..3da0f14 100644
--- a/model/Disk.cpp
+++ b/model/Disk.cpp
@@ -97,7 +97,8 @@
       mNickname(nickname),
       mFlags(flags),
       mCreated(false),
-      mJustPartitioned(false) {
+      mJustPartitioned(false),
+      mSkipChange(false) {
     mId = StringPrintf("disk:%u,%u", major(device), minor(device));
     mEventPath = eventPath;
     mSysPath = StringPrintf("/sys/%s", eventPath.c_str());
@@ -235,6 +236,11 @@
 }
 
 status_t Disk::readMetadata() {
+
+    if (mSkipChange) {
+        return OK;
+    }
+
     mSize = -1;
     mLabel.clear();
 
@@ -326,6 +332,12 @@
         return -ENOTSUP;
     }
 
+    if (mSkipChange) {
+        mSkipChange = false;
+        LOG(INFO) << "Skip first change";
+        return OK;
+    }
+
     destroyAllVolumes();
 
     // Parse partition table
@@ -446,9 +458,46 @@
     destroyAllVolumes();
     mJustPartitioned = true;
 
-    // First nuke any existing partition table
+    // Determine if we're coming from MBR
     std::vector<std::string> cmd;
     cmd.push_back(kSgdiskPath);
+    cmd.push_back("--android-dump");
+    cmd.push_back(mDevPath);
+
+    std::vector<std::string> output;
+    res = ForkExecvp(cmd, &output);
+    if (res != OK) {
+        LOG(WARNING) << "sgdisk failed to scan " << mDevPath;
+        mJustPartitioned = false;
+        return res;
+    }
+
+    Table table = Table::kUnknown;
+    for (auto line : output) {
+        char* cline = (char*) line.c_str();
+        char* token = strtok(cline, kSgdiskToken);
+        if (token == nullptr) continue;
+
+        if (!strcmp(token, "DISK")) {
+            const char* type = strtok(nullptr, kSgdiskToken);
+            if (!strcmp(type, "mbr")) {
+                table = Table::kMbr;
+                break;
+            } else if (!strcmp(type, "gpt")) {
+                table = Table::kGpt;
+                break;
+            }
+        }
+    }
+
+    if (table == Table::kMbr) {
+        LOG(INFO) << "skip first disk change event due to MBR -> GPT switch";
+        mSkipChange = true;
+    }
+
+    // First nuke any existing partition table
+    cmd.clear();
+    cmd.push_back(kSgdiskPath);
     cmd.push_back("--zap-all");
     cmd.push_back(mDevPath);
 
diff --git a/model/Disk.h b/model/Disk.h
index 8c75f59..cc1d66d 100644
--- a/model/Disk.h
+++ b/model/Disk.h
@@ -116,6 +116,8 @@
     bool mCreated;
     /* Flag that we just partitioned and should format all volumes */
     bool mJustPartitioned;
+    /* Flag that we need to skip first disk change events after partitioning*/
+    bool mSkipChange;
 
     void createPublicVolume(dev_t device);
     void createPrivateVolume(dev_t device, const std::string& partGuid);