github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/cmd/snap-bootstrap/initramfs_systemd_mount.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "strings" 29 "time" 30 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/osutil" 33 "github.com/snapcore/snapd/systemd" 34 ) 35 36 var ( 37 timeNow = time.Now 38 39 // default 1:30, as that is how long systemd will wait for services by 40 // default so seems a sensible default 41 defaultMountUnitWaitTimeout = time.Minute + 30*time.Second 42 43 unitFileDependOverride = `[Unit] 44 Requires=%[1]s 45 After=%[1]s 46 ` 47 48 doSystemdMount = doSystemdMountImpl 49 ) 50 51 // systemdMountOptions reflects the set of options for mounting something using 52 // systemd-mount(1) 53 type systemdMountOptions struct { 54 // Tmpfs indicates that "what" should be ignored and a new tmpfs should be 55 // mounted at the location. 56 Tmpfs bool 57 // Ephemeral indicates that the mount should not persist from the initramfs 58 // to after the pivot_root to normal userspace. The default value, false, 59 // means that the mount will persist across the transition, this is done by 60 // creating systemd unit overrides for various initrd targets in /run that 61 // systemd understands when it isolates to the initrd-cleanup.target when 62 // the pivot_root is performed. 63 Ephemeral bool 64 // NeedsFsck indicates that before returning to the caller, an fsck check 65 // should be performed on the thing being mounted. 66 NeedsFsck bool 67 // NoWait will not wait until the systemd unit is active and running, which 68 // is the default behavior. 69 NoWait bool 70 // NoSuid indicates that the partition should be mounted with nosuid set on 71 // it to prevent suid execution. 72 NoSuid bool 73 // Bind indicates a bind mount 74 Bind bool 75 } 76 77 // doSystemdMount will mount "what" at "where" using systemd-mount(1) with 78 // various options. Note that in some error cases, the mount unit may have 79 // already been created and it will not be deleted here, if that is the case 80 // callers should check manually if the unit needs to be removed on error 81 // conditions. 82 func doSystemdMountImpl(what, where string, opts *systemdMountOptions) error { 83 if opts == nil { 84 opts = &systemdMountOptions{} 85 } 86 87 // doesn't make sense to fsck a tmpfs 88 if opts.NeedsFsck && opts.Tmpfs { 89 return fmt.Errorf("cannot mount %q at %q: impossible to fsck a tmpfs", what, where) 90 } 91 92 whereEscaped := systemd.EscapeUnitNamePath(where) 93 unitName := whereEscaped + ".mount" 94 95 args := []string{what, where, "--no-pager", "--no-ask-password"} 96 if opts.Tmpfs { 97 args = append(args, "--type=tmpfs") 98 } 99 100 if opts.NeedsFsck { 101 // note that with the --fsck=yes argument, systemd will block starting 102 // the mount unit on a new systemd-fsck@<what> unit that will run the 103 // fsck, so we don't need to worry about waiting for that to finish in 104 // the case where we are supposed to wait (which is the default for this 105 // function) 106 args = append(args, "--fsck=yes") 107 } else { 108 // the default is to use fsck=yes, so if it doesn't need fsck we need to 109 // explicitly turn it off 110 args = append(args, "--fsck=no") 111 } 112 113 // Under all circumstances that we use systemd-mount here from 114 // snap-bootstrap, it is expected to be okay to block waiting for the unit 115 // to be started and become active, because snap-bootstrap is, by design, 116 // expected to run as late as possible in the initramfs, and so any 117 // dependencies there might be in systemd creating and starting these mount 118 // units should already be ready and so we will not block forever. If 119 // however there was something going on in systemd at the same time that the 120 // mount unit depended on, we could hit a deadlock blocking as systemd will 121 // not enqueue this job until it's dependencies are ready, and so if those 122 // things depend on this mount unit we are stuck. The solution to this 123 // situation is to make snap-bootstrap run as late as possible before 124 // mounting things. 125 // However, we leave in the option to not block if there is ever a reason 126 // we need to do so. 127 if opts.NoWait { 128 args = append(args, "--no-block") 129 } 130 131 var options []string 132 if opts.NoSuid { 133 options = append(options, "nosuid") 134 } 135 if opts.Bind { 136 options = append(options, "bind") 137 } 138 if len(options) > 0 { 139 args = append(args, "--options=" + strings.Join(options, ",")) 140 } 141 142 // note that we do not currently parse any output from systemd-mount, but if 143 // we ever do, take special care surrounding the debug output that systemd 144 // outputs with the "debug" kernel command line present (or equivalently the 145 // SYSTEMD_LOG_LEVEL=debug env var) which will add lots of additional output 146 // to stderr from systemd commands 147 out, err := exec.Command("systemd-mount", args...).CombinedOutput() 148 if err != nil { 149 return osutil.OutputErr(out, err) 150 } 151 152 // if it should survive pivot_root() then we need to add overrides for this 153 // unit to /run/systemd units 154 if !opts.Ephemeral { 155 // to survive the pivot_root, we need to make the mount units depend on 156 // all of the various initrd special targets by adding runtime conf 157 // files there 158 // note we could do this statically in the initramfs main filesystem 159 // layout, but that means that changes to snap-bootstrap would block on 160 // waiting for those files to be added before things works here, this is 161 // a more flexible strategy that puts snap-bootstrap in control 162 overrideContent := []byte(fmt.Sprintf(unitFileDependOverride, unitName)) 163 for _, initrdUnit := range []string{ 164 "initrd.target", 165 "initrd-fs.target", 166 "initrd-switch-root.target", 167 "local-fs.target", 168 } { 169 targetDir := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d") 170 err := os.MkdirAll(targetDir, 0755) 171 if err != nil { 172 return err 173 } 174 175 // add an override file for the initrd unit to depend on this mount 176 // unit so that when we isolate to the initrd unit, it does not get 177 // unmounted 178 fname := fmt.Sprintf("snap_bootstrap_%s.conf", whereEscaped) 179 err = ioutil.WriteFile(filepath.Join(targetDir, fname), overrideContent, 0644) 180 if err != nil { 181 return err 182 } 183 } 184 } 185 186 if !opts.NoWait { 187 // TODO: is this necessary, systemd-mount seems to only return when the 188 // unit is active and the mount is there, but perhaps we should be a bit 189 // paranoid here and wait anyways? 190 // see systemd-mount(1) 191 192 // wait for the mount to exist 193 start := timeNow() 194 var now time.Time 195 for now = timeNow(); now.Sub(start) < defaultMountUnitWaitTimeout; now = timeNow() { 196 mounted, err := osutilIsMounted(where) 197 if mounted { 198 break 199 } 200 if err != nil { 201 return err 202 } 203 } 204 205 if now.Sub(start) > defaultMountUnitWaitTimeout { 206 return fmt.Errorf("timed out after %s waiting for mount %s on %s", defaultMountUnitWaitTimeout, what, where) 207 } 208 } 209 210 return nil 211 }