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