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