osbase/gpt: sync device after writing each GPT

This makes it safer to update the GPT. Without the first sync call, it 
could for example happen that both GPTs are partially written to disk 
when the system loses power, resulting in both GPTs being corrupted. The 
second sync call ensures that the GPT update is committed to disk and 
will not be rolled back, and also that both GPTs are valid after the 
call to Write returns.

Change-Id: I50b3cabee4ee8a3162766812e945e129358dd875
Reviewed-on: https://review.monogon.dev/c/monogon/+/3360
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/osbase/gpt/gpt.go b/osbase/gpt/gpt.go
index 033a34d..1c66816 100644
--- a/osbase/gpt/gpt.go
+++ b/osbase/gpt/gpt.go
@@ -533,6 +533,12 @@
 		return fmt.Errorf("failed to write alternate header: %v", err)
 	}
 
+	// Sync device after writing each GPT, to ensure there is at least one valid
+	// GPT at all times.
+	if err := gpt.b.Sync(); err != nil {
+		return fmt.Errorf("failed to sync device after writing alternate GPT: %w", err)
+	}
+
 	// Primary header
 	hdr.HeaderBlock = 1
 	hdr.AlternateHeaderBlock = uint64(blockCount - 1)
@@ -565,6 +571,9 @@
 	if _, err := gpt.b.WriteAt(hdrRaw.Bytes(), 0); err != nil {
 		return fmt.Errorf("failed to write primary GPT: %w", err)
 	}
+	if err := gpt.b.Sync(); err != nil {
+		return fmt.Errorf("failed to sync device after writing primary GPT: %w", err)
+	}
 	return nil
 }