github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/common/common.go (about) 1 // Copyright 2014 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package common defines values shared by different parts 16 // of rkt (e.g. stage0 and stage1) 17 package common 18 19 import ( 20 "bufio" 21 "errors" 22 "fmt" 23 "net" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "runtime" 28 "strconv" 29 "strings" 30 "syscall" 31 "unsafe" 32 33 "github.com/appc/spec/aci" 34 "github.com/appc/spec/schema/types" 35 "github.com/hashicorp/errwrap" 36 37 "github.com/rkt/rkt/pkg/fileutil" 38 ) 39 40 const ( 41 sharedVolumesDir = "/sharedVolumes" 42 SharedVolumePerm = os.FileMode(0755) 43 stage1Dir = "/stage1" 44 stage2Dir = "/opt/stage2" 45 AppsInfoDir = "/appsinfo" 46 47 EnvLockFd = "RKT_LOCK_FD" 48 EnvSELinuxContext = "RKT_SELINUX_CONTEXT" 49 EnvSELinuxMountContext = "RKT_SELINUX_MOUNT_CONTEXT" 50 Stage1TreeStoreIDFilename = "stage1TreeStoreID" 51 AppTreeStoreIDFilename = "treeStoreID" 52 OverlayPreparedFilename = "overlay-prepared" 53 PrivateUsersPreparedFilename = "private-users-prepared" 54 55 PrepareLock = "prepareLock" 56 57 MetadataServicePort = 18112 58 MetadataServiceRegSock = "/run/rkt/metadata-svc.sock" 59 60 APIServiceListenAddr = "localhost:15441" 61 62 DefaultLocalConfigDir = "/etc/rkt" 63 DefaultSystemConfigDir = "/usr/lib/rkt" 64 65 // Default perm bits for the regular files 66 // within the stage1 directory. (e.g. image manifest, 67 // pod manifest, stage1ID, etc). 68 DefaultRegularFilePerm = os.FileMode(0640) 69 70 // Default perm bits for the regular directories 71 // within the stage1 directory. 72 DefaultRegularDirPerm = os.FileMode(0750) 73 74 // Enter command for crossing entrypoints. 75 CrossingEnterCmd = "RKT_STAGE1_ENTERCMD" 76 // Stage1 (PID) to enter, used by crossing entrypoints. 77 CrossingEnterPID = "RKT_STAGE1_ENTERPID" 78 // Stage2 (application name) to enter, optionally used by crossing entrypoints. 79 CrossingEnterApp = "RKT_STAGE1_ENTERAPP" 80 ) 81 82 const ( 83 FsMagicAUFS = 0x61756673 // https://goo.gl/CBwx43 84 FsMagicZFS = 0x2FC12FC1 // https://goo.gl/xTvzO5 85 ) 86 87 // ErrOverlayUnsupported is the error determining whether OverlayFS is supported. 88 type ErrOverlayUnsupported string 89 90 func (e ErrOverlayUnsupported) Error() string { 91 return string(e) 92 } 93 94 // Stage1ImagePath returns the path where the stage1 app image (unpacked ACI) is rooted, 95 // (i.e. where its contents are extracted during stage0). 96 func Stage1ImagePath(root string) string { 97 return filepath.Join(root, stage1Dir) 98 } 99 100 // Stage1RootfsPath returns the path to the stage1 rootfs 101 func Stage1RootfsPath(root string) string { 102 return filepath.Join(Stage1ImagePath(root), aci.RootfsDir) 103 } 104 105 // Stage1ManifestPath returns the path to the stage1's manifest file inside the expanded ACI. 106 func Stage1ManifestPath(root string) string { 107 return filepath.Join(Stage1ImagePath(root), aci.ManifestFile) 108 } 109 110 // PodManifestPath returns the path in root to the Pod Manifest 111 func PodManifestPath(root string) string { 112 return filepath.Join(root, "pod") 113 } 114 115 // PodCreatedPath returns the path in root to the Pod Created file used to 116 // denote the time of creation. 117 func PodCreatedPath(root string) string { 118 return filepath.Join(root, "pod-created") 119 } 120 121 // PodManifestLockPath returns the path in root to the Pod Manifest lock file. 122 // This must be different from the PodManifestPath since mutations on the pod manifest file 123 // happen by overwriting the original file. 124 func PodManifestLockPath(root string) string { 125 return filepath.Join(root, "pod.lck") 126 } 127 128 // AppsStatusesPathFromStage1Rootfs returns the path of the status dir for all apps. 129 // It receives the stage1 rootfs as parameter instead of the pod root. 130 func AppsStatusesPathFromStage1Rootfs(rootfs string) string { 131 return filepath.Join(rootfs, "/rkt/status") 132 } 133 134 // AppsStatusesPath returns the path of the status dir for all apps. 135 func AppsStatusesPath(root string) string { 136 return AppsStatusesPathFromStage1Rootfs(Stage1RootfsPath(root)) 137 } 138 139 // AppStatusPath returns the path of the status file of an app. 140 func AppStatusPath(root, appName string) string { 141 return filepath.Join(AppsStatusesPath(root), appName) 142 } 143 144 // AppStatusPathFromStage1Rootfs returns the path of the status file of an app. 145 // It receives the stage1 rootfs as parameter instead of the pod root. 146 func AppStatusPathFromStage1Rootfs(rootfs, appName string) string { 147 return filepath.Join(AppsStatusesPathFromStage1Rootfs(rootfs), appName) 148 } 149 150 // AppCreatedPath returns the path of the ${appname}-created file, which is used to record 151 // the creation timestamp of the app. 152 func AppCreatedPath(root, appName string) string { 153 return filepath.Join(AppsStatusesPath(root), fmt.Sprintf("%s-created", appName)) 154 } 155 156 // AppCreatedPathFromStage1Rootfs returns the path of the ${appname}-created file, 157 // which is used to record the creation timestamp of the app. 158 // It receives the stage1 rootfs as parameter instead of the pod root. 159 func AppCreatedPathFromStage1Rootfs(rootfs, appName string) string { 160 return filepath.Join(AppsStatusesPathFromStage1Rootfs(rootfs), fmt.Sprintf("%s-created", appName)) 161 } 162 163 // AppStartedPath returns the path of the ${appname}-started file, which is used to record 164 // the start timestamp of the app. 165 func AppStartedPath(root, appName string) string { 166 return filepath.Join(AppsStatusesPath(root), fmt.Sprintf("%s-started", appName)) 167 } 168 169 // AppStartedPathFromStage1Rootfs returns the path of the ${appname}-started file, which is used to record 170 // the start timestamp of the app. 171 // It receives the stage1 rootfs as parameter instead of the pod root. 172 func AppStartedPathFromStage1Rootfs(rootfs, appName string) string { 173 return filepath.Join(AppsStatusesPathFromStage1Rootfs(rootfs), fmt.Sprintf("%s-started", appName)) 174 } 175 176 // AppsPath returns the path where the apps within a pod live. 177 func AppsPath(root string) string { 178 return filepath.Join(Stage1RootfsPath(root), stage2Dir) 179 } 180 181 // AppPath returns the path to an app's rootfs. 182 func AppPath(root string, appName types.ACName) string { 183 return filepath.Join(AppsPath(root), appName.String()) 184 } 185 186 // AppRootfsPath returns the path to an app's rootfs. 187 func AppRootfsPath(root string, appName types.ACName) string { 188 return filepath.Join(AppPath(root, appName), aci.RootfsDir) 189 } 190 191 // RelAppPath returns the path of an app relative to the stage1 chroot. 192 func RelAppPath(appName types.ACName) string { 193 return filepath.Join(stage2Dir, appName.String()) 194 } 195 196 // RelAppRootfsPath returns the path of an app's rootfs relative to the stage1 chroot. 197 func RelAppRootfsPath(appName types.ACName) string { 198 return filepath.Join(RelAppPath(appName), aci.RootfsDir) 199 } 200 201 // ImageManifestPath returns the path to the app's manifest file of a pod. 202 func ImageManifestPath(root string, appName types.ACName) string { 203 return filepath.Join(AppPath(root, appName), aci.ManifestFile) 204 } 205 206 // AppsInfoPath returns the path to the appsinfo directory of a pod. 207 func AppsInfoPath(root string) string { 208 return filepath.Join(root, AppsInfoDir) 209 } 210 211 // AppInfoPath returns the path to the app's appsinfo directory of a pod. 212 func AppInfoPath(root string, appName types.ACName) string { 213 return filepath.Join(AppsInfoPath(root), appName.String()) 214 } 215 216 // AppTreeStoreIDPath returns the path to the app's treeStoreID file of a pod. 217 func AppTreeStoreIDPath(root string, appName types.ACName) string { 218 return filepath.Join(AppInfoPath(root, appName), AppTreeStoreIDFilename) 219 } 220 221 // AppImageManifestPath returns the path to the app's ImageManifest file 222 func AppImageManifestPath(root string, appName types.ACName) string { 223 return filepath.Join(AppInfoPath(root, appName), aci.ManifestFile) 224 } 225 226 // SharedVolumesPath returns the path to the shared (empty) volumes of a pod. 227 func SharedVolumesPath(root string) string { 228 return filepath.Join(root, sharedVolumesDir) 229 } 230 231 // CreateSharedVolumesPath ensures the sharedVolumePath for the pod root passed 232 // in exists. It returns the shared volume path or an error. 233 func CreateSharedVolumesPath(root string) (string, error) { 234 sharedVolPath := SharedVolumesPath(root) 235 236 if err := os.MkdirAll(sharedVolPath, SharedVolumePerm); err != nil { 237 return "", errwrap.Wrap(errors.New("could not create shared volumes directory"), err) 238 } 239 // In case it already existed and we didn't make it, ensure permissions are 240 // what the caller expects them to be. 241 if err := os.Chmod(sharedVolPath, SharedVolumePerm); err != nil { 242 return "", errwrap.Wrap(fmt.Errorf("could not change permissions of %q", sharedVolPath), err) 243 } 244 245 return sharedVolPath, nil 246 } 247 248 // MetadataServicePublicURL returns the public URL used to host the metadata service 249 func MetadataServicePublicURL(ip net.IP, token string) string { 250 return fmt.Sprintf("http://%v:%v/%v", ip, MetadataServicePort, token) 251 } 252 253 func GetRktLockFD() (int, error) { 254 if v := os.Getenv(EnvLockFd); v != "" { 255 fd, err := strconv.ParseUint(v, 10, 32) 256 if err != nil { 257 return -1, err 258 } 259 return int(fd), nil 260 } 261 return -1, fmt.Errorf("%v env var is not set", EnvLockFd) 262 } 263 264 // SupportsUserNS returns whether the kernel has CONFIG_USER_NS set 265 func SupportsUserNS() bool { 266 if _, err := os.Stat("/proc/self/uid_map"); err == nil { 267 return true 268 } 269 270 return false 271 } 272 273 // NetList implements the flag.Value interface to allow specification of --net with and without values 274 // Example: --net="all,net1:k1=v1;k2=v2,net2:l1=w1" 275 type NetList struct { 276 mapping map[string]string 277 } 278 279 func (l *NetList) String() string { 280 return strings.Join(l.Strings(), ",") 281 } 282 283 func (l *NetList) Set(value string) error { 284 if l.mapping == nil { 285 l.mapping = make(map[string]string) 286 } 287 for _, s := range strings.Split(value, ",") { 288 netArgsPair := strings.Split(s, ":") 289 netName := netArgsPair[0] 290 291 if netName == "" { 292 return fmt.Errorf("netname must not be empty") 293 } 294 295 if _, duplicate := l.mapping[netName]; duplicate { 296 return fmt.Errorf("found duplicate netname %q", netName) 297 } 298 299 switch { 300 case len(netArgsPair) == 1: 301 l.mapping[netName] = "" 302 case len(netArgsPair) == 2: 303 if netName == "all" || 304 netName == "host" { 305 return fmt.Errorf("arguments are not supported by special netname %q", netName) 306 } 307 l.mapping[netName] = netArgsPair[1] 308 case len(netArgsPair) > 2: 309 return fmt.Errorf("network %q provided with invalid arguments: %v", netName, netArgsPair[1:]) 310 default: 311 return fmt.Errorf("unexpected case when processing network %q", s) 312 } 313 } 314 return nil 315 } 316 317 func (l *NetList) Type() string { 318 return "netList" 319 } 320 321 func (l *NetList) Strings() []string { 322 if len(l.mapping) == 0 { 323 return []string{"default"} 324 } 325 326 var list []string 327 for k, v := range l.mapping { 328 if v == "" { 329 list = append(list, k) 330 } else { 331 list = append(list, fmt.Sprintf("%s:%s", k, v)) 332 } 333 } 334 return list 335 } 336 337 func (l *NetList) StringsOnlyNames() (list []string) { 338 for k := range l.mapping { 339 list = append(list, k) 340 } 341 342 return 343 } 344 345 // Check if host networking has been requested 346 func (l *NetList) Host() bool { 347 return l.Specific("host") 348 } 349 350 // Check if 'none' (loopback only) networking has been requested 351 func (l *NetList) None() bool { 352 return l.Specific("none") 353 } 354 355 // Check if the container needs to be put in a separate network namespace 356 func (l *NetList) Contained() bool { 357 return !l.Host() && len(l.mapping) > 0 358 } 359 360 func (l *NetList) Specific(net string) bool { 361 _, exists := l.mapping[net] 362 return exists 363 } 364 365 func (l *NetList) SpecificArgs(net string) string { 366 return l.mapping[net] 367 } 368 369 func (l *NetList) All() bool { 370 return l.Specific("all") 371 } 372 373 // LookupPath search for bin in paths. If found, it returns its absolute path, 374 // if not, an error 375 func LookupPath(bin string, paths string) (string, error) { 376 pathsArr := filepath.SplitList(paths) 377 for _, path := range pathsArr { 378 binPath := filepath.Join(path, bin) 379 binAbsPath, err := filepath.Abs(binPath) 380 if err != nil { 381 return "", fmt.Errorf("unable to find absolute path for %s", binPath) 382 } 383 if fileutil.IsExecutable(binAbsPath) { 384 return binAbsPath, nil 385 } 386 } 387 return "", fmt.Errorf("unable to find %q in %q", bin, paths) 388 } 389 390 // SystemdVersion parses and returns the version of a given systemd binary 391 func SystemdVersion(systemdBinaryPath string) (int, error) { 392 versionBytes, err := exec.Command(systemdBinaryPath, "--version").CombinedOutput() 393 if err != nil { 394 return -1, errwrap.Wrap(fmt.Errorf("unable to probe %s version", systemdBinaryPath), err) 395 } 396 versionStr := strings.SplitN(string(versionBytes), "\n", 2)[0] 397 var version int 398 n, err := fmt.Sscanf(versionStr, "systemd %d", &version) 399 if err != nil || n != 1 { 400 return -1, fmt.Errorf("cannot parse version: %q", versionStr) 401 } 402 403 return version, nil 404 } 405 406 // SupportsOverlay returns whether the operating system generally supports OverlayFS, 407 // returning an instance of ErrOverlayUnsupported which encodes the reason. 408 // It is sufficient to check for nil if the reason is not of interest. 409 func SupportsOverlay() error { 410 // ignore exec.Command error, modprobe may not be present on the system, 411 // or the kernel module will fail to load. 412 // we'll find out by reading the side effect in /proc/filesystems 413 _ = exec.Command("modprobe", "overlay").Run() 414 415 f, err := os.Open("/proc/filesystems") 416 if err != nil { 417 // don't use errwrap so consumers can type-check on ErrOverlayUnsupported 418 return ErrOverlayUnsupported(fmt.Sprintf("cannot open /proc/filesystems: %v", err)) 419 } 420 defer f.Close() 421 422 s := bufio.NewScanner(f) 423 for s.Scan() { 424 if s.Text() == "nodev\toverlay" { 425 return nil 426 } 427 } 428 429 return ErrOverlayUnsupported("overlay entry not present in /proc/filesystems") 430 } 431 432 // PathSupportsOverlay checks whether the given path is compatible with OverlayFS. 433 // This method also calls SupportsOverlay(). 434 // 435 // It returns an instance of ErrOverlayUnsupported if OverlayFS is not supported 436 // or any other error if determining overlay support failed. 437 func PathSupportsOverlay(path string) error { 438 if err := SupportsOverlay(); err != nil { 439 // don't wrap since SupportsOverlay already returns ErrOverlayUnsupported 440 return err 441 } 442 443 var data syscall.Statfs_t 444 if err := syscall.Statfs(path, &data); err != nil { 445 return errwrap.Wrap(fmt.Errorf("cannot statfs %q", path), err) 446 } 447 448 switch data.Type { 449 case FsMagicAUFS: 450 return ErrOverlayUnsupported("unsupported filesystem: aufs") 451 case FsMagicZFS: 452 return ErrOverlayUnsupported("unsupported filesystem: zfs") 453 } 454 455 dir, err := os.OpenFile(path, syscall.O_RDONLY|syscall.O_DIRECTORY, 0755) 456 if err != nil { 457 return errwrap.Wrap(fmt.Errorf("cannot open %q", path), err) 458 } 459 defer dir.Close() 460 461 buf := make([]byte, 4096) 462 // ReadDirent forwards to the raw syscall getdents(3), 463 // passing the buffer size. 464 n, err := syscall.ReadDirent(int(dir.Fd()), buf) 465 if err != nil { 466 return errwrap.Wrap(fmt.Errorf("cannot read directory %q", path), err) 467 } 468 469 offset := 0 470 for offset < n { 471 // offset overflow cannot happen, because Reclen 472 // is being maintained by getdents(3), considering the buffer size. 473 dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[offset])) 474 offset += int(dirent.Reclen) 475 476 if dirent.Ino == 0 { // File absent in directory. 477 continue 478 } 479 480 if dirent.Type == syscall.DT_UNKNOWN { 481 return ErrOverlayUnsupported("unsupported filesystem: missing d_type support") 482 } 483 } 484 485 return nil 486 } 487 488 // RemoveEmptyLines removes empty lines from the given string 489 // and breaks it up into a list of strings at newline characters 490 func RemoveEmptyLines(str string) []string { 491 lines := make([]string, 0) 492 493 for _, v := range strings.Split(str, "\n") { 494 if len(v) > 0 { 495 lines = append(lines, v) 496 } 497 } 498 499 return lines 500 } 501 502 // GetExitStatus converts an error to an exit status. If it wasn't an exit 503 // status != 0 it returns the same error that it was called with 504 func GetExitStatus(err error) (int, error) { 505 if err == nil { 506 return 0, nil 507 } 508 if exiterr, ok := err.(*exec.ExitError); ok { 509 // the program has exited with an exit code != 0 510 if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { 511 return status.ExitStatus(), nil 512 } 513 } 514 return -1, err 515 } 516 517 // GetOS returns the current ACI operating system (linux, windows etc...) 518 func GetOS() string { 519 os, _ := GetOSArch() 520 return os 521 } 522 523 // GetArch returns the current ACI architecture. 524 func GetArch() string { 525 _, arch := GetOSArch() 526 return arch 527 } 528 529 // reference to GOARM, needs to be globally, if in GetArch() function it resolves to zero 530 //go:linkname goarm runtime.goarm 531 var goarm uint8 532 533 func GetOSArch() (os string, arch string) { 534 arch = runtime.GOARCH 535 flavor := "" 536 if arch == "arm" { 537 flavor = strconv.Itoa(int(goarm)) 538 } 539 os, arch, _ = types.ToAppcOSArch(runtime.GOOS, arch, flavor) 540 return os, arch 541 } 542 543 // ImageNameToAppName converts the full name of image to an app name without special 544 // characters - we use it as a default app name when specyfing it is optional 545 func ImageNameToAppName(name types.ACIdentifier) (*types.ACName, error) { 546 parts := strings.Split(name.String(), "/") 547 last := parts[len(parts)-1] 548 549 sn, err := types.SanitizeACName(last) 550 if err != nil { 551 return nil, err 552 } 553 554 return types.MustACName(sn), nil 555 }