tree-wide: rewrite ioutil functions to their replacements

The ioutil package has been deprecated in Go 1.16 [1]. This CL removes
all our own users of that package and rewrites them to use their
replacements in the os package. I initially wanted to do this with a
gofix but because all replacements were signature-compatible I just
did it with a few string replaces and then ran goimports to fix up the
imports.

I intentionally didn't rewrite the patches as that would require a
different process and is IMO of less value.

[1] https://github.com/golang/go/issues/42026

Change-Id: Iac6663a1f1ee49f9b1c6e4b3d97e73f2c3b54a13
Reviewed-on: https://review.monogon.dev/c/monogon/+/449
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/build/bazel_cc_fix/main.go b/build/bazel_cc_fix/main.go
index 1be47f7..611f1eb 100644
--- a/build/bazel_cc_fix/main.go
+++ b/build/bazel_cc_fix/main.go
@@ -34,7 +34,6 @@
 	"encoding/json"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -179,7 +178,7 @@
 func (m rewriteMetadata) fixIncludesAndGetRefs(filePath string, quoteIncludes, systemIncludes []string, spec *ccfixspec.CCFixSpec, isGeneratedFile map[string]bool) []string {
 	meta, ok := m[filePath]
 	if !ok {
-		cSourceRaw, err := ioutil.ReadFile(filePath)
+		cSourceRaw, err := os.ReadFile(filePath)
 		if err != nil {
 			log.Printf("failed to open source file: %v", err)
 			return nil
@@ -328,7 +327,7 @@
 	if err := json.NewDecoder(compilationDBFile).Decode(&compilationDB); err != nil {
 		log.Fatalf("failed to read compilation db: %v", err)
 	}
-	specRaw, err := ioutil.ReadFile(*specPath)
+	specRaw, err := os.ReadFile(*specPath)
 	var spec ccfixspec.CCFixSpec
 	if err := proto.UnmarshalText(string(specRaw), &spec); err != nil {
 		log.Fatalf("failed to load spec: %v", err)
diff --git a/build/fietsje/deps_monogon.go b/build/fietsje/deps_monogon.go
index 54e7d94..0abcd66 100644
--- a/build/fietsje/deps_monogon.go
+++ b/build/fietsje/deps_monogon.go
@@ -7,7 +7,7 @@
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
+	"os"
 )
 
 // Monogon runs fietsje for all Monogon transitive dependencies.
@@ -134,7 +134,7 @@
 		return fmt.Errorf("could not render deps: %w", err)
 	}
 
-	err = ioutil.WriteFile(repositoriesBzlPath, buf.Bytes(), 0666)
+	err = os.WriteFile(repositoriesBzlPath, buf.Bytes(), 0666)
 	if err != nil {
 		return fmt.Errorf("could not write deps: %w", err)
 	}
diff --git a/build/fietsje/shelf.go b/build/fietsje/shelf.go
index 807ee44..c377186 100644
--- a/build/fietsje/shelf.go
+++ b/build/fietsje/shelf.go
@@ -19,7 +19,6 @@
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"sort"
@@ -69,7 +68,7 @@
 	if _, err := os.Stat(path); os.IsNotExist(err) {
 		log.Printf("Creating new shelf file at %q, this run will be slow.", path)
 	} else {
-		data, err = ioutil.ReadFile(path)
+		data, err = os.ReadFile(path)
 		if err != nil {
 			return nil, fmt.Errorf("could not read shelf: %v", err)
 		}
@@ -154,7 +153,7 @@
 	}
 
 	// And write it out.
-	err = ioutil.WriteFile(s.path, buf.Bytes(), 0644)
+	err = os.WriteFile(s.path, buf.Bytes(), 0644)
 	if err != nil {
 		return fmt.Errorf("could not write shelf: %v", err)
 	}
diff --git a/build/fietsje/transitive.go b/build/fietsje/transitive.go
index 2e2a9f7..6ffd594 100644
--- a/build/fietsje/transitive.go
+++ b/build/fietsje/transitive.go
@@ -18,7 +18,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"strings"
@@ -61,7 +60,7 @@
 
 	read := func(p string) []byte {
 		full := fmt.Sprintf("%s/%s", path, p)
-		data, err := ioutil.ReadFile(full)
+		data, err := os.ReadFile(full)
 		if err != nil {
 			panic(fmt.Sprintf("reading file %q: %v", full, err))
 		}
diff --git a/build/static_binary_tarball/main.go b/build/static_binary_tarball/main.go
index 72ee5d0..b7b3612 100644
--- a/build/static_binary_tarball/main.go
+++ b/build/static_binary_tarball/main.go
@@ -20,7 +20,6 @@
 	"archive/tar"
 	"flag"
 	"io"
-	"io/ioutil"
 	"log"
 	"os"
 	"path"
@@ -39,7 +38,7 @@
 func main() {
 	flag.Parse()
 	var spec spec.Spec
-	specRaw, err := ioutil.ReadFile(*specPath)
+	specRaw, err := os.ReadFile(*specPath)
 	if err != nil {
 		log.Fatalf("failed to open spec file: %v", err)
 	}
diff --git a/intellij/localconfig/watchers/filewatchers.go b/intellij/localconfig/watchers/filewatchers.go
index dd1810c..4a56161 100644
--- a/intellij/localconfig/watchers/filewatchers.go
+++ b/intellij/localconfig/watchers/filewatchers.go
@@ -19,7 +19,6 @@
 import (
 	"encoding/xml"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 )
@@ -67,7 +66,7 @@
 }
 
 func readConfig(filename string) (cfg *config, err error) {
-	b, err := ioutil.ReadFile(filename)
+	b, err := os.ReadFile(filename)
 	if err != nil {
 		return nil, fmt.Errorf("failed reading file: %w", err)
 	}
@@ -90,7 +89,7 @@
 	// (but not applies) instantly.
 	tmpPath := filename + ".tmp"
 	defer os.Remove(tmpPath)
-	if err := ioutil.WriteFile(tmpPath, []byte(xml.Header+string(b)), 0664); err != nil {
+	if err := os.WriteFile(tmpPath, []byte(xml.Header+string(b)), 0664); err != nil {
 		return fmt.Errorf("failed to write: %w", err)
 	}
 	if err := os.Rename(tmpPath, filename); err != nil {
diff --git a/metropolis/build/gotoolwrap/main.go b/metropolis/build/gotoolwrap/main.go
index eb70884..86dc3a1 100644
--- a/metropolis/build/gotoolwrap/main.go
+++ b/metropolis/build/gotoolwrap/main.go
@@ -36,7 +36,6 @@
 import (
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
@@ -81,7 +80,7 @@
 	// We list all files inside so that we can print them to the user for
 	// debugging purposes if that's not the case.
 	binFiles := make(map[string]bool)
-	files, err := ioutil.ReadDir(gorootBin)
+	files, err := os.ReadDir(gorootBin)
 	if err != nil {
 		log.Fatalf("Could not read dir $GOTOOLWRAP_GOROOT/bin (%q): %v", gorootBin, err)
 	}
diff --git a/metropolis/cli/dbg/main.go b/metropolis/cli/dbg/main.go
index 84df60d..99aff6c 100644
--- a/metropolis/cli/dbg/main.go
+++ b/metropolis/cli/dbg/main.go
@@ -21,7 +21,6 @@
 	"flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math/rand"
 	"os"
 	"strings"
@@ -142,7 +141,7 @@
 	case "kubectl":
 		// Always get a kubeconfig with cluster-admin (group system:masters),
 		// kubectl itself can impersonate
-		kubeconfigFile, err := ioutil.TempFile("", "dbg_kubeconfig")
+		kubeconfigFile, err := os.CreateTemp("", "dbg_kubeconfig")
 		if err != nil {
 			fmt.Fprintf(os.Stderr, "Failed to create kubeconfig temp file: %v\n", err)
 			os.Exit(1)
diff --git a/metropolis/cli/metroctl/install.go b/metropolis/cli/metroctl/install.go
index ab31610..3970e78 100644
--- a/metropolis/cli/metroctl/install.go
+++ b/metropolis/cli/metroctl/install.go
@@ -4,7 +4,6 @@
 	"crypto/ed25519"
 	"crypto/rand"
 	"encoding/pem"
-	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -63,14 +62,14 @@
 		log.Fatalf("Failed to create config directory: %v", err)
 	}
 	var ownerPublicKey ed25519.PublicKey
-	ownerPrivateKeyPEM, err := ioutil.ReadFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem"))
+	ownerPrivateKeyPEM, err := os.ReadFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem"))
 	if os.IsNotExist(err) {
 		pub, priv, err := ed25519.GenerateKey(rand.Reader)
 		if err != nil {
 			log.Fatalf("Failed to generate owner private key: %v", err)
 		}
 		pemPriv := pem.EncodeToMemory(&pem.Block{Type: ownerKeyType, Bytes: priv})
-		if err := ioutil.WriteFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem"), pemPriv, 0600); err != nil {
+		if err := os.WriteFile(filepath.Join(xdg.ConfigHome, "metroctl/owner-key.pem"), pemPriv, 0600); err != nil {
 			log.Fatalf("Failed to store owner private key: %v", err)
 		}
 		ownerPublicKey = pub
diff --git a/metropolis/node/build/genosrelease/main.go b/metropolis/node/build/genosrelease/main.go
index ad6e3e2..adb8202 100644
--- a/metropolis/node/build/genosrelease/main.go
+++ b/metropolis/node/build/genosrelease/main.go
@@ -23,7 +23,6 @@
 import (
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"strings"
 
@@ -40,7 +39,7 @@
 
 func main() {
 	flag.Parse()
-	statusFileContent, err := ioutil.ReadFile(*flagStatusFile)
+	statusFileContent, err := os.ReadFile(*flagStatusFile)
 	if err != nil {
 		fmt.Printf("Failed to open bazel workspace status file: %v\n", err)
 		os.Exit(1)
@@ -73,7 +72,7 @@
 		fmt.Printf("Failed to encode os-release file: %v\n", err)
 		os.Exit(1)
 	}
-	if err := ioutil.WriteFile(*flagOutFile, []byte(osReleaseContent), 0644); err != nil {
+	if err := os.WriteFile(*flagOutFile, []byte(osReleaseContent), 0644); err != nil {
 		fmt.Printf("Failed to write os-release file: %v\n", err)
 		os.Exit(1)
 	}
diff --git a/metropolis/node/build/mkerofs/main.go b/metropolis/node/build/mkerofs/main.go
index ea89d67..d4e9d4d 100644
--- a/metropolis/node/build/mkerofs/main.go
+++ b/metropolis/node/build/mkerofs/main.go
@@ -22,7 +22,6 @@
 import (
 	"flag"
 	"io"
-	"io/ioutil"
 	"log"
 	"os"
 	"path"
@@ -132,7 +131,7 @@
 
 func main() {
 	flag.Parse()
-	specRaw, err := ioutil.ReadFile(*specPath)
+	specRaw, err := os.ReadFile(*specPath)
 	if err != nil {
 		log.Fatalf("failed to open spec: %v", err)
 	}
diff --git a/metropolis/node/build/mkimage/osimage/osimage.go b/metropolis/node/build/mkimage/osimage/osimage.go
index 4ad2d5f..2f000a6 100644
--- a/metropolis/node/build/mkimage/osimage/osimage.go
+++ b/metropolis/node/build/mkimage/osimage/osimage.go
@@ -21,7 +21,6 @@
 import (
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 
 	diskfs "github.com/diskfs/go-diskfs"
@@ -58,7 +57,7 @@
 	// If this is streamed (e.g. using io.Copy) it exposes a bug in diskfs, so
 	// do it in one go.
 	// TODO(mateusz@monogon.tech): Investigate the bug.
-	data, err := ioutil.ReadAll(src)
+	data, err := io.ReadAll(src)
 	if err != nil {
 		return fmt.Errorf("while reading %q: %w", src, err)
 	}
diff --git a/metropolis/node/core/cluster/cluster.go b/metropolis/node/core/cluster/cluster.go
index aa293b0..60d7e15 100644
--- a/metropolis/node/core/cluster/cluster.go
+++ b/metropolis/node/core/cluster/cluster.go
@@ -29,7 +29,7 @@
 	"context"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"os"
 	"sync"
 
 	"google.golang.org/protobuf/proto"
@@ -129,7 +129,7 @@
 }
 
 func (m *Manager) nodeParamsFWCFG(ctx context.Context) (*apb.NodeParameters, error) {
-	bytes, err := ioutil.ReadFile("/sys/firmware/qemu_fw_cfg/by_name/dev.monogon.metropolis/parameters.pb/raw")
+	bytes, err := os.ReadFile("/sys/firmware/qemu_fw_cfg/by_name/dev.monogon.metropolis/parameters.pb/raw")
 	if err != nil {
 		return nil, fmt.Errorf("could not read firmware enrolment file: %w", err)
 	}
diff --git a/metropolis/node/core/consensus/consensus_test.go b/metropolis/node/core/consensus/consensus_test.go
index 105b8eb..16d6f76 100644
--- a/metropolis/node/core/consensus/consensus_test.go
+++ b/metropolis/node/core/consensus/consensus_test.go
@@ -20,7 +20,6 @@
 	"bytes"
 	"context"
 	"crypto/x509"
-	"io/ioutil"
 	"os"
 	"testing"
 	"time"
@@ -44,7 +43,7 @@
 	// Force usage of /tmp as temp directory root, otherwsie TMPDIR from Bazel
 	// returns a path long enough that socket binds in the localstorage fail
 	// (as socket names are limited to 108 characters).
-	tmp, err := ioutil.TempDir("/tmp", "metropolis-consensus-test")
+	tmp, err := os.MkdirTemp("/tmp", "metropolis-consensus-test")
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/metropolis/node/core/curator/curator_test.go b/metropolis/node/core/curator/curator_test.go
index 5ce0077..5529e5d 100644
--- a/metropolis/node/core/curator/curator_test.go
+++ b/metropolis/node/core/curator/curator_test.go
@@ -3,7 +3,6 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"testing"
 	"time"
@@ -79,7 +78,7 @@
 
 	// Create ephemeral directory for curator and place it into /tmp.
 	dir := localstorage.EphemeralCuratorDirectory{}
-	tmp, err := ioutil.TempDir("/tmp", "curator-test-*")
+	tmp, err := os.MkdirTemp("/tmp", "curator-test-*")
 	if err != nil {
 		t.Fatalf("TempDir: %v", err)
 	}
diff --git a/metropolis/node/core/curator/listener_test.go b/metropolis/node/core/curator/listener_test.go
index fad7e92..7c1744e 100644
--- a/metropolis/node/core/curator/listener_test.go
+++ b/metropolis/node/core/curator/listener_test.go
@@ -3,7 +3,7 @@
 import (
 	"context"
 	"errors"
-	"io/ioutil"
+	"os"
 	"testing"
 
 	"google.golang.org/grpc/codes"
@@ -29,7 +29,7 @@
 	// Force usage of /tmp as temp directory root, otherwsie TMPDIR from Bazel
 	// returns a path long enough that socket binds in the localstorage fail
 	// (as socket names are limited to 108 characters).
-	tmp, err := ioutil.TempDir("/tmp", "curator-test-*")
+	tmp, err := os.MkdirTemp("/tmp", "curator-test-*")
 	if err != nil {
 		t.Fatalf("TempDir: %v", err)
 	}
diff --git a/metropolis/node/core/debug_service.go b/metropolis/node/core/debug_service.go
index 814da9a..48a4c9b 100644
--- a/metropolis/node/core/debug_service.go
+++ b/metropolis/node/core/debug_service.go
@@ -20,7 +20,6 @@
 	"bufio"
 	"context"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"regexp"
 	"strings"
@@ -215,7 +214,7 @@
 	if !safeTracingPropertyNamesRe.MatchString(name) {
 		return fmt.Errorf("disallowed tracing property name received: \"%v\"", name)
 	}
-	return ioutil.WriteFile("/sys/kernel/tracing/"+name, []byte(value+"\n"), 0)
+	return os.WriteFile("/sys/kernel/tracing/"+name, []byte(value+"\n"), 0)
 }
 
 func (s *debugService) Trace(req *apb.TraceRequest, srv apb.NodeDebugService_TraceServer) error {
diff --git a/metropolis/node/core/localstorage/crypt/blockdev.go b/metropolis/node/core/localstorage/crypt/blockdev.go
index 6ea1b49..dde2fd9 100644
--- a/metropolis/node/core/localstorage/crypt/blockdev.go
+++ b/metropolis/node/core/localstorage/crypt/blockdev.go
@@ -19,7 +19,6 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -51,7 +50,7 @@
 // to ESPDevicePath and NodeDataCryptPath respectively. This doesn't fail if it
 // doesn't find the partitions, only if something goes catastrophically wrong.
 func MakeBlockDevices(ctx context.Context) error {
-	blockdevNames, err := ioutil.ReadDir("/sys/class/block")
+	blockdevNames, err := os.ReadDir("/sys/class/block")
 	if err != nil {
 		return fmt.Errorf("failed to read sysfs block class: %w", err)
 	}
diff --git a/metropolis/node/core/localstorage/declarative/placement_local.go b/metropolis/node/core/localstorage/declarative/placement_local.go
index 43921cd..f12f654 100644
--- a/metropolis/node/core/localstorage/declarative/placement_local.go
+++ b/metropolis/node/core/localstorage/declarative/placement_local.go
@@ -18,7 +18,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"sync"
 
@@ -58,7 +57,7 @@
 }
 
 func (f *FSPlacement) Read() ([]byte, error) {
-	return ioutil.ReadFile(f.FullPath())
+	return os.ReadFile(f.FullPath())
 }
 
 // Write performs an atomic file write, via a temporary file.
@@ -69,7 +68,7 @@
 	// TODO(q3k): ensure that these do not collide with an existing sibling file, or generate this suffix randomly.
 	tmp := f.FullPath() + ".__metropolis_tmp"
 	defer os.Remove(tmp)
-	if err := ioutil.WriteFile(tmp, d, mode); err != nil {
+	if err := os.WriteFile(tmp, d, mode); err != nil {
 		return fmt.Errorf("temporary file write failed: %w", err)
 	}
 
diff --git a/metropolis/node/core/localstorage/storage.go b/metropolis/node/core/localstorage/storage.go
index 1e06967..d067438 100644
--- a/metropolis/node/core/localstorage/storage.go
+++ b/metropolis/node/core/localstorage/storage.go
@@ -53,7 +53,7 @@
 	// Ephemeral data, used by runtime, stored in tmpfs. Things like sockets,
 	// temporary config files, etc.
 	Ephemeral EphemeralDirectory `dir:"ephemeral"`
-	// FHS-standard /tmp directory, used by ioutil.TempFile.
+	// FHS-standard /tmp directory, used by os.MkdirTemp.
 	Tmp TmpDirectory `dir:"tmp"`
 	// FHS-standard /run directory. Used by various services.
 	Run RunDirectory `dir:"run"`
diff --git a/metropolis/node/core/mounts.go b/metropolis/node/core/mounts.go
index bb98462..a54331d 100644
--- a/metropolis/node/core/mounts.go
+++ b/metropolis/node/core/mounts.go
@@ -18,7 +18,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"strings"
 
@@ -57,7 +56,7 @@
 	if err := unix.Mount("tmpfs", "/sys/fs/cgroup", "tmpfs", unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV, ""); err != nil {
 		panic(err)
 	}
-	cgroupsRaw, err := ioutil.ReadFile("/proc/cgroups")
+	cgroupsRaw, err := os.ReadFile("/proc/cgroups")
 	if err != nil {
 		panic(err)
 	}
diff --git a/metropolis/node/kubernetes/containerd/main.go b/metropolis/node/kubernetes/containerd/main.go
index c3dd4a0..b5e2cf0 100644
--- a/metropolis/node/kubernetes/containerd/main.go
+++ b/metropolis/node/kubernetes/containerd/main.go
@@ -20,7 +20,6 @@
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -108,7 +107,7 @@
 		return fmt.Errorf("failed to connect to containerd: %w", err)
 	}
 	logger := supervisor.Logger(ctx)
-	preseedNamespaceDirs, err := ioutil.ReadDir(preseedNamespacesDir)
+	preseedNamespaceDirs, err := os.ReadDir(preseedNamespacesDir)
 	if err != nil {
 		return fmt.Errorf("failed to open preseed dir: %w", err)
 	}
@@ -118,7 +117,7 @@
 			continue
 		}
 		namespace := dir.Name()
-		images, err := ioutil.ReadDir(filepath.Join(preseedNamespacesDir, namespace))
+		images, err := os.ReadDir(filepath.Join(preseedNamespacesDir, namespace))
 		if err != nil {
 			return fmt.Errorf("failed to list namespace preseed directory for ns \"%v\": %w", namespace, err)
 		}
diff --git a/metropolis/node/kubernetes/plugins/kvmdevice/kvmdevice.go b/metropolis/node/kubernetes/plugins/kvmdevice/kvmdevice.go
index ed47f74..c9a6a79 100644
--- a/metropolis/node/kubernetes/plugins/kvmdevice/kvmdevice.go
+++ b/metropolis/node/kubernetes/plugins/kvmdevice/kvmdevice.go
@@ -27,7 +27,6 @@
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
 	"net"
 	"os"
 	"strconv"
@@ -136,7 +135,7 @@
 func (k *Plugin) Run(ctx context.Context) error {
 	k.logger = supervisor.Logger(ctx)
 
-	l1tfStatus, err := ioutil.ReadFile("/sys/devices/system/cpu/vulnerabilities/l1tf")
+	l1tfStatus, err := os.ReadFile("/sys/devices/system/cpu/vulnerabilities/l1tf")
 	if err != nil && !os.IsNotExist(err) {
 		return fmt.Errorf("failed to query for CPU vulnerabilities: %v", err)
 	}
@@ -148,7 +147,7 @@
 		return nil
 	}
 
-	kvmDevRaw, err := ioutil.ReadFile("/sys/devices/virtual/misc/kvm/dev")
+	kvmDevRaw, err := os.ReadFile("/sys/devices/virtual/misc/kvm/dev")
 	if err != nil {
 		k.logger.Warning("KVM is not available. Check firmware settings and CPU.")
 		supervisor.Signal(ctx, supervisor.SignalHealthy)
diff --git a/metropolis/node/kubernetes/provisioner.go b/metropolis/node/kubernetes/provisioner.go
index 42edf77..7288c84 100644
--- a/metropolis/node/kubernetes/provisioner.go
+++ b/metropolis/node/kubernetes/provisioner.go
@@ -20,7 +20,6 @@
 	"context"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 
@@ -276,7 +275,7 @@
 		if err := os.Mkdir(volumePath, 0644); err != nil && !os.IsExist(err) {
 			return fmt.Errorf("failed to create volume directory: %w", err)
 		}
-		files, err := ioutil.ReadDir(volumePath)
+		files, err := os.ReadDir(volumePath)
 		if err != nil {
 			return fmt.Errorf("failed to list files in newly-created volume: %w", err)
 		}
diff --git a/metropolis/pkg/efivarfs/efivarfs.go b/metropolis/pkg/efivarfs/efivarfs.go
index fc6100e..e731069 100644
--- a/metropolis/pkg/efivarfs/efivarfs.go
+++ b/metropolis/pkg/efivarfs/efivarfs.go
@@ -22,7 +22,6 @@
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -64,7 +63,7 @@
 func ReadLoaderDevicePartUUID() (string, error) {
 	// Read the EFI variable file containing the ESP UUID.
 	espUuidPath := filepath.Join(Path, "LoaderDevicePartUUID-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f")
-	efiVar, err := ioutil.ReadFile(espUuidPath)
+	efiVar, err := os.ReadFile(espUuidPath)
 	if err != nil {
 		return "", fmt.Errorf("couldn't read the LoaderDevicePartUUID file at %q: %w", espUuidPath, err)
 	}
@@ -98,7 +97,7 @@
 	if err != nil {
 		return -1, fmt.Errorf("while marshaling the EFI boot entry: %w", err)
 	}
-	if err := ioutil.WriteFile(ep, bem, 0644); err != nil {
+	if err := os.WriteFile(ep, bem, 0644); err != nil {
 		return -1, fmt.Errorf("while creating a boot entry variable: %w", err)
 	}
 	return n, nil
@@ -108,7 +107,7 @@
 // specified in ord. It may return an io error.
 func SetBootOrder(ord *BootOrder) error {
 	op := filepath.Join(Path, fmt.Sprintf("BootOrder-%s", GlobalGuid))
-	if err := ioutil.WriteFile(op, ord.Marshal(), 0644); err != nil {
+	if err := os.WriteFile(op, ord.Marshal(), 0644); err != nil {
 		return fmt.Errorf("while creating a boot order variable: %w", err)
 	}
 	return nil
diff --git a/metropolis/pkg/erofs/erofs_test.go b/metropolis/pkg/erofs/erofs_test.go
index d02c2dd..fbd1af1 100644
--- a/metropolis/pkg/erofs/erofs_test.go
+++ b/metropolis/pkg/erofs/erofs_test.go
@@ -18,7 +18,6 @@
 
 import (
 	"io"
-	"io/ioutil"
 	"log"
 	"math/rand"
 	"os"
@@ -78,14 +77,14 @@
 				return nil
 			},
 			validate: func(t *testing.T) error {
-				dirInfo, err := ioutil.ReadDir("/test")
+				dirInfo, err := os.ReadDir("/test")
 				if err != nil {
 					t.Fatalf("Failed to read top-level directory: %v", err)
 				}
 				require.Len(t, dirInfo, 1, "more subdirs than expected")
 				require.Equal(t, "subdir", dirInfo[0].Name(), "unexpected subdir")
 				require.True(t, dirInfo[0].IsDir(), "subdir not a directory")
-				subdirInfo, err := ioutil.ReadDir("/test/subdir")
+				subdirInfo, err := os.ReadDir("/test/subdir")
 				assert.NoError(t, err, "cannot read empty subdir")
 				require.Len(t, subdirInfo, 0, "unexpected subdirs in empty directory")
 				return nil
@@ -123,8 +122,8 @@
 				assert.NoError(t, err, "failed to open test file")
 				defer file.Close()
 				r := io.LimitReader(rand.New(rand.NewSource(0)), 128) // Random but deterministic data
-				expected, _ := ioutil.ReadAll(r)
-				actual, err := ioutil.ReadAll(file)
+				expected, _ := io.ReadAll(r)
+				actual, err := io.ReadAll(file)
 				assert.NoError(t, err, "failed to read test file")
 				assert.Equal(t, expected, actual, "content not identical")
 				return nil
@@ -153,7 +152,7 @@
 			},
 			validate: func(t *testing.T) error {
 				var stat unix.Stat_t
-				rawContents, err := ioutil.ReadFile("/dev/ram0")
+				rawContents, err := os.ReadFile("/dev/ram0")
 				assert.NoError(t, err, "failed to read test data")
 				log.Printf("%x", rawContents)
 				err = unix.Stat("/test/test.bin", &stat)
@@ -166,8 +165,8 @@
 				assert.NoError(t, err, "failed to open test file")
 				defer file.Close()
 				r := io.LimitReader(rand.New(rand.NewSource(1)), 6500) // Random but deterministic data
-				expected, _ := ioutil.ReadAll(r)
-				actual, err := ioutil.ReadAll(file)
+				expected, _ := io.ReadAll(r)
+				actual, err := io.ReadAll(file)
 				assert.NoError(t, err, "failed to read test file")
 				assert.Equal(t, expected, actual, "content not identical")
 				return nil
@@ -204,8 +203,8 @@
 					assert.NoError(t, err, "failed to open test file")
 					defer file.Close()
 					r := io.LimitReader(rand.New(rand.NewSource(int64(i))), 2053) // Random but deterministic data
-					expected, _ := ioutil.ReadAll(r)
-					actual, err := ioutil.ReadAll(file)
+					expected, _ := io.ReadAll(r)
+					actual, err := io.ReadAll(file)
 					assert.NoError(t, err, "failed to read test file")
 					require.Equal(t, expected, actual, "content not identical")
 				}
diff --git a/metropolis/pkg/fileargs/fileargs.go b/metropolis/pkg/fileargs/fileargs.go
index bec8fca..88863da 100644
--- a/metropolis/pkg/fileargs/fileargs.go
+++ b/metropolis/pkg/fileargs/fileargs.go
@@ -21,7 +21,6 @@
 	"encoding/hex"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 
@@ -78,7 +77,7 @@
 
 	path := filepath.Join(f.path, name)
 
-	if err := ioutil.WriteFile(path, content, 0600); err != nil {
+	if err := os.WriteFile(path, content, 0600); err != nil {
 		f.lastError = err
 		return ""
 	}
diff --git a/metropolis/pkg/fsquota/fsquota_test.go b/metropolis/pkg/fsquota/fsquota_test.go
index 392a0e9..c842b63 100644
--- a/metropolis/pkg/fsquota/fsquota_test.go
+++ b/metropolis/pkg/fsquota/fsquota_test.go
@@ -18,7 +18,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"math"
 	"os"
 	"os/exec"
@@ -120,7 +119,7 @@
 			t.Fatal(err)
 		}
 		sizeFileData := make([]byte, 512*1024)
-		if err := ioutil.WriteFile("/test/readback/512kfile", sizeFileData, 0644); err != nil {
+		if err := os.WriteFile("/test/readback/512kfile", sizeFileData, 0644); err != nil {
 			t.Fatal(err)
 		}
 
@@ -138,7 +137,7 @@
 
 		// Write 50 inodes for a total of 51 (with the 512K file)
 		for i := 0; i < 50; i++ {
-			if err := ioutil.WriteFile(fmt.Sprintf("/test/readback/ifile%v", i), []byte("test"), 0644); err != nil {
+			if err := os.WriteFile(fmt.Sprintf("/test/readback/ifile%v", i), []byte("test"), 0644); err != nil {
 				t.Fatal(err)
 			}
 		}
diff --git a/metropolis/pkg/logtree/unraw/unraw_test.go b/metropolis/pkg/logtree/unraw/unraw_test.go
index afd1da9..5526feb 100644
--- a/metropolis/pkg/logtree/unraw/unraw_test.go
+++ b/metropolis/pkg/logtree/unraw/unraw_test.go
@@ -4,7 +4,6 @@
 	"context"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"syscall"
 	"testing"
@@ -21,7 +20,7 @@
 }
 
 func TestNamedPipeReader(t *testing.T) {
-	dir, err := ioutil.TempDir("/tmp", "metropolis-test-named-pipe-reader")
+	dir, err := os.MkdirTemp("/tmp", "metropolis-test-named-pipe-reader")
 	if err != nil {
 		t.Fatalf("could not create tempdir: %v", err)
 	}
diff --git a/metropolis/pkg/loop/loop.go b/metropolis/pkg/loop/loop.go
index c338f04..a4974a8 100644
--- a/metropolis/pkg/loop/loop.go
+++ b/metropolis/pkg/loop/loop.go
@@ -28,7 +28,6 @@
 import (
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"math/bits"
 	"os"
 	"sync"
@@ -223,7 +222,7 @@
 
 // BackingFilePath returns the path of the backing file
 func (d *Device) BackingFilePath() (string, error) {
-	backingFile, err := ioutil.ReadFile(fmt.Sprintf("/sys/block/loop%d/loop/backing_file", d.num))
+	backingFile, err := os.ReadFile(fmt.Sprintf("/sys/block/loop%d/loop/backing_file", d.num))
 	if err != nil {
 		return "", fmt.Errorf("failed to get backing file path: %w", err)
 	}
diff --git a/metropolis/pkg/loop/loop_test.go b/metropolis/pkg/loop/loop_test.go
index 16ead64..7f23f3e 100644
--- a/metropolis/pkg/loop/loop_test.go
+++ b/metropolis/pkg/loop/loop_test.go
@@ -19,7 +19,6 @@
 import (
 	"encoding/binary"
 	"io"
-	"io/ioutil"
 	"math"
 	"os"
 	"runtime"
@@ -36,7 +35,7 @@
 // bit unsigned integers) to detect offset correctness. File is always 128KiB
 // large (2^16 * 2 bytes).
 func makeTestFile() *os.File {
-	f, err := ioutil.TempFile("/tmp", "")
+	f, err := os.CreateTemp("/tmp", "")
 	if err != nil {
 		panic(err)
 	}
@@ -183,7 +182,7 @@
 	if os.Getenv("IN_KTEST") != "true" {
 		t.Skip("Not in ktest")
 	}
-	f, err := ioutil.TempFile("/tmp", "")
+	f, err := os.CreateTemp("/tmp", "")
 	assert.NoError(t, err)
 	empty1K := make([]byte, 1024)
 	for i := 0; i < 64; i++ {
diff --git a/metropolis/pkg/tpm/tpm.go b/metropolis/pkg/tpm/tpm.go
index ab02dd3..f84aac6 100644
--- a/metropolis/pkg/tpm/tpm.go
+++ b/metropolis/pkg/tpm/tpm.go
@@ -24,7 +24,6 @@
 	"crypto/x509"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -584,5 +583,5 @@
 // result can be parsed by eventlog.  As this library currently doesn't support
 // extending PCRs it just returns the log as supplied by the EFI interface.
 func GetMeasurementLog() ([]byte, error) {
-	return ioutil.ReadFile("/sys/kernel/security/tpm0/binary_bios_measurements")
+	return os.ReadFile("/sys/kernel/security/tpm0/binary_bios_measurements")
 }
diff --git a/metropolis/test/launch/cluster/cluster.go b/metropolis/test/launch/cluster/cluster.go
index a4f11a2..7af1f55 100644
--- a/metropolis/test/launch/cluster/cluster.go
+++ b/metropolis/test/launch/cluster/cluster.go
@@ -12,7 +12,6 @@
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
 	"os"
@@ -86,7 +85,7 @@
 	// that we open and pass the name of it to QEMU. Not pinning this crashes both
 	// swtpm and qemu because we run into UNIX socket length limitations (for legacy
 	// reasons 108 chars).
-	tempDir, err := ioutil.TempDir("/tmp", "launch*")
+	tempDir, err := os.MkdirTemp("/tmp", "launch*")
 	if err != nil {
 		return fmt.Errorf("failed to create temporary directory: %w", err)
 	}
@@ -99,7 +98,7 @@
 	if err := os.Mkdir(tpmTargetDir, 0755); err != nil {
 		return fmt.Errorf("failed to create TPM state directory: %w", err)
 	}
-	tpmFiles, err := ioutil.ReadDir(tpmSrcDir)
+	tpmFiles, err := os.ReadDir(tpmSrcDir)
 	if err != nil {
 		return fmt.Errorf("failed to read TPM directory: %w", err)
 	}
@@ -160,7 +159,7 @@
 		if err != nil {
 			return fmt.Errorf("failed to encode node paraeters: %w", err)
 		}
-		if err := ioutil.WriteFile(parametersPath, parametersRaw, 0644); err != nil {
+		if err := os.WriteFile(parametersPath, parametersRaw, 0644); err != nil {
 			return fmt.Errorf("failed to write node parameters: %w", err)
 		}
 		qemuArgs = append(qemuArgs, "-fw_cfg", "name=dev.monogon.metropolis/parameters.pb,file="+parametersPath)
diff --git a/metropolis/test/nanoswitch/nanoswitch.go b/metropolis/test/nanoswitch/nanoswitch.go
index 212696d..e44fc80 100644
--- a/metropolis/test/nanoswitch/nanoswitch.go
+++ b/metropolis/test/nanoswitch/nanoswitch.go
@@ -28,7 +28,6 @@
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"os"
 	"time"
@@ -302,7 +301,7 @@
 			dhcpClient.RequestedOptions = []dhcpv4.OptionCode{dhcpv4.OptionRouter}
 			dhcpClient.LeaseCallback = dhcpcb.Compose(dhcpcb.ManageIP(externalLink), dhcpcb.ManageDefaultRoute(externalLink))
 			supervisor.Run(ctx, "dhcp-client", dhcpClient.Run)
-			if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
+			if err := os.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
 				logger.Fatalf("Failed to write ip forwards: %v", err)
 			}
 		} else {
diff --git a/metropolis/vm/smoketest/main.go b/metropolis/vm/smoketest/main.go
index a1bd3a2..c1eeae6 100644
--- a/metropolis/vm/smoketest/main.go
+++ b/metropolis/vm/smoketest/main.go
@@ -20,7 +20,7 @@
 
 import (
 	"bytes"
-	"io/ioutil"
+	"io"
 	"log"
 	"net"
 	"os"
@@ -39,7 +39,7 @@
 		if err != nil {
 			panic(err)
 		}
-		testValue, _ := ioutil.ReadAll(conn)
+		testValue, _ := io.ReadAll(conn)
 		if bytes.Equal(testValue, []byte("test123")) {
 			testResultChan <- true
 		} else {