blob: 0e68b0608f0e891ffcf8017452743716a1d62b03 [file] [log] [blame]
Serge Bazanski581b0bd2020-03-12 13:36:43 +01001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package main
18
19import (
20 "fmt"
21 "io"
22 "os"
23 "path/filepath"
24 "strings"
25 "syscall"
26
27 "go.uber.org/zap"
28 "golang.org/x/sys/unix"
29)
30
31// switchRoot moves the root from initramfs into a tmpfs
32// This is necessary because you cannot pivot_root from a initramfs (and runsc wants to do that).
33// In the future, we should instead use something like squashfs instead of an initramfs and just nuke this.
34func switchRoot(log *zap.Logger) error {
35 // We detect the need to remount to tmpfs over env vars.
36 // The first run of /init (from initramfs) will not have this var, and will be re-exec'd from a new tmpfs root with
37 // that variable set.
38 witness := "SIGNOS_REMOUNTED"
39
40 // If the witness env var is found in the environment, it means we are ready to go.
41 environ := os.Environ()
42 for _, env := range environ {
43 if strings.HasPrefix(env, witness+"=") {
44 log.Info("Smalltown running in tmpfs root")
45 return nil
46 }
47 }
48
49 // Otherwise, we need to remount to a tmpfs.
50 environ = append(environ, witness+"=yes")
51 log.Info("Smalltown running in initramfs, remounting to tmpfs...")
52
53 // Make note of all directories we have to make and files that we have to copy.
54 paths := []string{}
55 dirs := []string{}
56 err := filepath.Walk("/", func(path string, info os.FileInfo, err error) error {
57 if err != nil {
58 return err
59 }
60 if path == "/" {
61 return nil
62 }
63 // /dev is prepopulated by the initramfs, skip that. The target root uses devtmpfs.
64 if path == "/dev" || strings.HasPrefix(path, "/dev/") {
65 return nil
66 }
67
68 if info.IsDir() {
69 dirs = append(dirs, path)
70 } else {
71 paths = append(paths, path)
72 }
73
74 return nil
75 })
76 if err != nil {
77 return fmt.Errorf("could not list root files: %w", err)
78 }
79
80 log.Info("Copying to tmpfs", zap.Strings("paths", paths), zap.Strings("dirs", dirs))
81
82 // Make new root at /mnt
83 if err := os.Mkdir("/mnt", 0755); err != nil {
84 return fmt.Errorf("could not make /mnt: %w", err)
85 }
86 // And mount a tmpfs on it
87 if err := unix.Mount("tmpfs", "/mnt", "tmpfs", 0, ""); err != nil {
88 return fmt.Errorf("could not mount tmpfs on /mnt: %w", err)
89 }
90
91 // Make all directories. Since filepath.Walk is lexicographically ordered, we don't need to ensure that the parent
92 // exists.
93 for _, src := range dirs {
94 stat, err := os.Stat(src)
95 if err != nil {
96 return fmt.Errorf("Stat(%q): %w", src, err)
97 }
98 dst := "/mnt" + src
99 err = os.Mkdir(dst, stat.Mode())
100 if err != nil {
101 return fmt.Errorf("Mkdir(%q): %w", dst, err)
102 }
103 }
104
105 // Move all files over. Parent directories will exist by now.
106 for _, src := range paths {
107 stat, err := os.Stat(src)
108 if err != nil {
109 return fmt.Errorf("Stat(%q): %w", src, err)
110 }
111 dst := "/mnt" + src
112
113 // Copy file.
114 sfd, err := os.Open(src)
115 if err != nil {
116 return fmt.Errorf("Open(%q): %w", src, err)
117 }
118 dfd, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, stat.Mode())
119 if err != nil {
120 sfd.Close()
121 return fmt.Errorf("OpenFile(%q): %w", dst, err)
122 }
123 _, err = io.Copy(dfd, sfd)
124
125 sfd.Close()
126 dfd.Close()
127 if err != nil {
128 return fmt.Errorf("Copying %q failed: %w", src, err)
129 }
130
131 // Remove the old file.
132 err = unix.Unlink(src)
133 if err != nil {
134 return fmt.Errorf("Unlink(%q): %w", src, err)
135 }
136 }
137
138 // Set up target filesystems.
139 for _, el := range []struct {
140 dir string
141 fs string
142 flags uintptr
143 }{
144 {"/sys", "sysfs", unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV},
145 {"/proc", "proc", unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV},
146 {"/dev", "devtmpfs", unix.MS_NOEXEC | unix.MS_NOSUID},
147 {"/dev/pts", "devpts", unix.MS_NOEXEC | unix.MS_NOSUID},
148 } {
149 if err := os.Mkdir("/mnt"+el.dir, 0755); err != nil {
150 return fmt.Errorf("could not make /mnt%s: %w", el.dir, err)
151 }
152 if err := unix.Mount(el.fs, "/mnt"+el.dir, el.fs, el.flags, ""); err != nil {
153 return fmt.Errorf("could not mount %s on /mnt%s: %w", el.fs, el.dir, err)
154 }
155 }
156
157 // Chroot to new root.
158 // This is adapted from util-linux's switch_root.
159 err = os.Chdir("/mnt")
160 if err != nil {
161 return fmt.Errorf("could not chdir to /mnt: %w", err)
162 }
163 err = syscall.Mount("/mnt", "/", "", syscall.MS_MOVE, "")
164 if err != nil {
165 return fmt.Errorf("could not remount /mnt to /: %w", err)
166 }
167 err = syscall.Chroot(".")
168 if err != nil {
169 return fmt.Errorf("could not chroot to new root: %w", err)
170 }
171
172 // Re-exec into new init with new environment
173 return unix.Exec("/init", os.Args, environ)
174}