osbase/fat32: write file creation time

Add file creation times to directory entries. This was already partially
implemented, and there even was a test. But the test was conditional on
the kernel version, and the version comparison was broken. The test
timestamps were also wrong, as the last component of time.Date is
nanoseconds, not milliseconds.

Change-Id: Ic00d6bb27aced918df018c9ff3148d12ecb07753
Reviewed-on: https://review.monogon.dev/c/monogon/+/3608
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/build/bazel/go.MODULE.bazel b/build/bazel/go.MODULE.bazel
index c3ccadc..7cbe759 100644
--- a/build/bazel/go.MODULE.bazel
+++ b/build/bazel/go.MODULE.bazel
@@ -94,7 +94,6 @@
     "org_golang_google_grpc",
     "org_golang_google_protobuf",
     "org_golang_x_crypto",
-    "org_golang_x_mod",
     "org_golang_x_net",
     "org_golang_x_sync",
     "org_golang_x_sys",
diff --git a/go.mod b/go.mod
index 7ec7f08..c9f0a4c 100644
--- a/go.mod
+++ b/go.mod
@@ -136,7 +136,6 @@
 	go.uber.org/multierr v1.11.0
 	go.uber.org/zap v1.26.0
 	golang.org/x/crypto v0.24.0
-	golang.org/x/mod v0.21.0
 	golang.org/x/net v0.26.0
 	golang.org/x/sync v0.8.0
 	golang.org/x/sys v0.25.0
@@ -415,6 +414,7 @@
 	golang.org/x/arch v0.3.0 // indirect
 	golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
 	golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
+	golang.org/x/mod v0.21.0 // indirect
 	golang.org/x/oauth2 v0.21.0 // indirect
 	golang.org/x/term v0.22.0
 	golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178 // indirect
diff --git a/osbase/fat32/BUILD.bazel b/osbase/fat32/BUILD.bazel
index 80a6e03..e329179 100644
--- a/osbase/fat32/BUILD.bazel
+++ b/osbase/fat32/BUILD.bazel
@@ -31,7 +31,6 @@
         "@com_github_stretchr_testify//assert",
         "@com_github_stretchr_testify//require",
         "@io_bazel_rules_go//go/runfiles:go_default_library",
-        "@org_golang_x_mod//semver",
         "@org_golang_x_sys//unix",
     ],
 )
diff --git a/osbase/fat32/fat32.go b/osbase/fat32/fat32.go
index 65f03e5..c8d4ec9 100644
--- a/osbase/fat32/fat32.go
+++ b/osbase/fat32/fat32.go
@@ -161,9 +161,13 @@
 		selfSize = 0 // Directories don't have an explicit size
 	}
 	date, t, _ := timeToMsDosTime(i.ModTime)
+	cdate, ctime, ctens := timeToMsDosTime(i.CreateTime)
 	if err := binary.Write(w, binary.LittleEndian, &dirEntry{
 		DOSName:           i.dosName,
 		Attributes:        uint8(i.Attrs),
+		CreationTenMilli:  ctens,
+		CreationTime:      ctime,
+		CreationDate:      cdate,
 		FirstClusterHigh:  uint16(i.startCluster >> 16),
 		LastWrittenToTime: t,
 		LastWrittenToDate: date,
@@ -212,6 +216,9 @@
 			// Time is intentionally taken from this directory, not the parent
 			if err := binary.Write(w, binary.LittleEndian, &dirEntry{
 				DOSName:           [11]byte{'.', '.', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '},
+				CreationDate:      cdate,
+				CreationTime:      ctime,
+				CreationTenMilli:  ctens,
 				LastWrittenToTime: t,
 				LastWrittenToDate: date,
 				Attributes:        uint8(AttrDirectory),
diff --git a/osbase/fat32/linux_test.go b/osbase/fat32/linux_test.go
index ca62b76..2237f38 100644
--- a/osbase/fat32/linux_test.go
+++ b/osbase/fat32/linux_test.go
@@ -12,7 +12,6 @@
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
-	"golang.org/x/mod/semver"
 	"golang.org/x/sys/unix"
 )
 
@@ -21,14 +20,6 @@
 		t.Skip("Not in ktest")
 	}
 
-	// ONCHANGE(//third_party/linux): Drop this once we move to a Kernel version
-	// newer than 5.19 which will have FAT btime support.
-	kernelVersion, err := os.ReadFile("/proc/sys/kernel/osrelease")
-	if err != nil {
-		t.Fatalf("unable to determine kernel version: %v", err)
-	}
-	haveBtime := semver.Compare("v"+string(kernelVersion), "v5.19.0") >= 0
-
 	type testCase struct {
 		name     string
 		setup    func(root *Inode) error
@@ -36,11 +27,11 @@
 	}
 
 	// Random timestamp in UTC, divisible by 10ms
-	testTimestamp1 := time.Date(2022, 03, 04, 5, 6, 7, 10, time.UTC)
+	testTimestamp1 := time.Date(2022, 03, 04, 5, 6, 7, 10_000_000, time.UTC)
 	// Random timestamp in UTC, divisible by 2s
 	testTimestamp2 := time.Date(2022, 03, 04, 5, 6, 8, 0, time.UTC)
 	// Random timestamp in UTC, divisible by 10ms
-	testTimestamp3 := time.Date(2052, 03, 02, 5, 6, 7, 10, time.UTC)
+	testTimestamp3 := time.Date(2052, 03, 02, 5, 6, 7, 10_000_000, time.UTC)
 	// Random timestamp in UTC, divisible by 2s
 	testTimestamp4 := time.Date(2052, 10, 04, 5, 3, 4, 0, time.UTC)
 
@@ -75,7 +66,7 @@
 					t.Errorf("testdir is expected to be a directory, but has mode %v", stat.Mode)
 				}
 				btime := time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec))
-				if !btime.Equal(testTimestamp1) && haveBtime {
+				if !btime.Equal(testTimestamp1) {
 					t.Errorf("testdir btime expected %v, got %v", testTimestamp1, btime)
 				}
 				mtime := time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec))
@@ -105,7 +96,7 @@
 					t.Errorf("testfile is expected to be a file, but has mode %v", stat.Mode)
 				}
 				btime := time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec))
-				if !btime.Equal(testTimestamp3) && haveBtime {
+				if !btime.Equal(testTimestamp3) {
 					t.Errorf("testfile ctime expected %v, got %v", testTimestamp3, btime)
 				}
 				mtime := time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec))