github.com/moby/docker@v26.1.3+incompatible/daemon/daemon_linux.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "io" 8 "net" 9 "os" 10 "regexp" 11 "strings" 12 "sync" 13 14 "github.com/containerd/log" 15 "github.com/docker/docker/daemon/config" 16 "github.com/docker/docker/libnetwork/ns" 17 "github.com/docker/docker/libnetwork/resolvconf" 18 "github.com/moby/sys/mount" 19 "github.com/moby/sys/mountinfo" 20 "github.com/pkg/errors" 21 "github.com/vishvananda/netlink" 22 "golang.org/x/sys/unix" 23 ) 24 25 // On Linux, plugins use a static path for storing execution state, 26 // instead of deriving path from daemon's exec-root. This is because 27 // plugin socket files are created here and they cannot exceed max 28 // path length of 108 bytes. 29 func getPluginExecRoot(_ *config.Config) string { 30 return "/run/docker/plugins" 31 } 32 33 func (daemon *Daemon) cleanupMountsByID(id string) error { 34 log.G(context.TODO()).Debugf("Cleaning up old mountid %s: start.", id) 35 f, err := os.Open("/proc/self/mountinfo") 36 if err != nil { 37 return err 38 } 39 defer f.Close() 40 41 return daemon.cleanupMountsFromReaderByID(f, id, mount.Unmount) 42 } 43 44 func (daemon *Daemon) cleanupMountsFromReaderByID(reader io.Reader, id string, unmount func(target string) error) error { 45 if daemon.root == "" { 46 return nil 47 } 48 var errs []string 49 50 regexps := getCleanPatterns(id) 51 sc := bufio.NewScanner(reader) 52 for sc.Scan() { 53 if fields := strings.Fields(sc.Text()); len(fields) > 4 { 54 if mnt := fields[4]; strings.HasPrefix(mnt, daemon.root) { 55 for _, p := range regexps { 56 if p.MatchString(mnt) { 57 if err := unmount(mnt); err != nil { 58 log.G(context.TODO()).Error(err) 59 errs = append(errs, err.Error()) 60 } 61 } 62 } 63 } 64 } 65 } 66 67 if err := sc.Err(); err != nil { 68 return err 69 } 70 71 if len(errs) > 0 { 72 return fmt.Errorf("Error cleaning up mounts:\n%v", strings.Join(errs, "\n")) 73 } 74 75 log.G(context.TODO()).Debugf("Cleaning up old mountid %v: done.", id) 76 return nil 77 } 78 79 // cleanupMounts umounts used by container resources and the daemon root mount 80 func (daemon *Daemon) cleanupMounts(cfg *config.Config) error { 81 if err := daemon.cleanupMountsByID(""); err != nil { 82 return err 83 } 84 85 info, err := mountinfo.GetMounts(mountinfo.SingleEntryFilter(daemon.root)) 86 if err != nil { 87 return errors.Wrap(err, "error reading mount table for cleanup") 88 } 89 90 if len(info) < 1 { 91 // no mount found, we're done here 92 return nil 93 } 94 95 // `info.Root` here is the root mountpoint of the passed in path (`daemon.root`). 96 // The ony cases that need to be cleaned up is when the daemon has performed a 97 // `mount --bind /daemon/root /daemon/root && mount --make-shared /daemon/root` 98 // This is only done when the daemon is started up and `/daemon/root` is not 99 // already on a shared mountpoint. 100 if !shouldUnmountRoot(daemon.root, info[0]) { 101 return nil 102 } 103 104 unmountFile := getUnmountOnShutdownPath(cfg) 105 if _, err := os.Stat(unmountFile); err != nil { 106 return nil 107 } 108 109 log.G(context.TODO()).WithField("mountpoint", daemon.root).Debug("unmounting daemon root") 110 if err := mount.Unmount(daemon.root); err != nil { 111 return err 112 } 113 return os.Remove(unmountFile) 114 } 115 116 func getCleanPatterns(id string) (regexps []*regexp.Regexp) { 117 var patterns []string 118 if id == "" { 119 id = "[0-9a-f]{64}" 120 patterns = append(patterns, "containers/"+id+"/mounts/shm", "containers/"+id+"/shm") 121 } 122 patterns = append(patterns, "overlay2/"+id+"/merged$", "zfs/graph/"+id+"$") 123 for _, p := range patterns { 124 r, err := regexp.Compile(p) 125 if err == nil { 126 regexps = append(regexps, r) 127 } 128 } 129 return 130 } 131 132 func shouldUnmountRoot(root string, info *mountinfo.Info) bool { 133 if !strings.HasSuffix(root, info.Root) { 134 return false 135 } 136 return hasMountInfoOption(info.Optional, sharedPropagationOption) 137 } 138 139 // setupResolvConf sets the appropriate resolv.conf file if not specified 140 // When systemd-resolved is running the default /etc/resolv.conf points to 141 // localhost. In this case fetch the alternative config file that is in a 142 // different path so that containers can use it 143 // In all the other cases fallback to the default one 144 func setupResolvConf(config *config.Config) { 145 if config.ResolvConf != "" { 146 return 147 } 148 config.ResolvConf = resolvconf.Path() 149 } 150 151 // ifaceAddrs returns the IPv4 and IPv6 addresses assigned to the network 152 // interface with name linkName. 153 // 154 // No error is returned if the named interface does not exist. 155 func ifaceAddrs(linkName string) (v4, v6 []*net.IPNet, err error) { 156 nl := ns.NlHandle() 157 link, err := nl.LinkByName(linkName) 158 if err != nil { 159 if !errors.As(err, new(netlink.LinkNotFoundError)) { 160 return nil, nil, err 161 } 162 return nil, nil, nil 163 } 164 165 get := func(family int) ([]*net.IPNet, error) { 166 addrs, err := nl.AddrList(link, family) 167 if err != nil { 168 return nil, err 169 } 170 171 ipnets := make([]*net.IPNet, len(addrs)) 172 for i := range addrs { 173 ipnets[i] = addrs[i].IPNet 174 } 175 return ipnets, nil 176 } 177 178 v4, err = get(netlink.FAMILY_V4) 179 if err != nil { 180 return nil, nil, err 181 } 182 v6, err = get(netlink.FAMILY_V6) 183 if err != nil { 184 return nil, nil, err 185 } 186 return v4, v6, nil 187 } 188 189 var ( 190 kernelSupportsRROOnce sync.Once 191 kernelSupportsRROErr error 192 ) 193 194 func kernelSupportsRecursivelyReadOnly() error { 195 fn := func() error { 196 tmpMnt, err := os.MkdirTemp("", "moby-detect-rro") 197 if err != nil { 198 return fmt.Errorf("failed to create a temp directory: %w", err) 199 } 200 for { 201 err = unix.Mount("", tmpMnt, "tmpfs", 0, "") 202 if !errors.Is(err, unix.EINTR) { 203 break 204 } 205 } 206 if err != nil { 207 return fmt.Errorf("failed to mount tmpfs on %q: %w", tmpMnt, err) 208 } 209 defer func() { 210 var umErr error 211 for { 212 umErr = unix.Unmount(tmpMnt, 0) 213 if !errors.Is(umErr, unix.EINTR) { 214 break 215 } 216 } 217 if umErr != nil { 218 log.G(context.TODO()).WithError(umErr).Warnf("Failed to unmount %q", tmpMnt) 219 } 220 }() 221 attr := &unix.MountAttr{ 222 Attr_set: unix.MOUNT_ATTR_RDONLY, 223 } 224 for { 225 err = unix.MountSetattr(-1, tmpMnt, unix.AT_RECURSIVE, attr) 226 if !errors.Is(err, unix.EINTR) { 227 break 228 } 229 } 230 // ENOSYS on kernel < 5.12 231 if err != nil { 232 return fmt.Errorf("failed to call mount_setattr: %w", err) 233 } 234 return nil 235 } 236 237 kernelSupportsRROOnce.Do(func() { 238 kernelSupportsRROErr = fn() 239 }) 240 return kernelSupportsRROErr 241 } 242 243 func supportsRecursivelyReadOnly(cfg *configStore, runtime string) error { 244 if err := kernelSupportsRecursivelyReadOnly(); err != nil { 245 return fmt.Errorf("rro is not supported: %w (kernel is older than 5.12?)", err) 246 } 247 if runtime == "" { 248 runtime = cfg.Runtimes.Default 249 } 250 features := cfg.Runtimes.Features(runtime) 251 if features == nil { 252 return fmt.Errorf("rro is not supported by runtime %q: OCI features struct is not available", runtime) 253 } 254 for _, s := range features.MountOptions { 255 if s == "rro" { 256 return nil 257 } 258 } 259 return fmt.Errorf("rro is not supported by runtime %q", runtime) 260 }