github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/util/utils.go (about) 1 package util 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/fs" 7 "math" 8 "os" 9 "os/user" 10 "path/filepath" 11 "regexp" 12 "strconv" 13 "strings" 14 "sync" 15 "syscall" 16 "time" 17 18 "github.com/BurntSushi/toml" 19 "github.com/containers/common/pkg/config" 20 "github.com/containers/common/pkg/util" 21 "github.com/containers/image/v5/types" 22 "github.com/hanks177/podman/v4/pkg/errorhandling" 23 "github.com/hanks177/podman/v4/pkg/namespaces" 24 "github.com/hanks177/podman/v4/pkg/rootless" 25 "github.com/hanks177/podman/v4/pkg/signal" 26 "github.com/containers/storage/pkg/idtools" 27 stypes "github.com/containers/storage/types" 28 v1 "github.com/opencontainers/image-spec/specs-go/v1" 29 "github.com/opencontainers/runtime-spec/specs-go" 30 "github.com/pkg/errors" 31 "github.com/sirupsen/logrus" 32 "golang.org/x/term" 33 ) 34 35 var containerConfig *config.Config 36 37 func init() { 38 var err error 39 containerConfig, err = config.Default() 40 if err != nil { 41 logrus.Error(err) 42 os.Exit(1) 43 } 44 } 45 46 // Helper function to determine the username/password passed 47 // in the creds string. It could be either or both. 48 func parseCreds(creds string) (string, string) { 49 if creds == "" { 50 return "", "" 51 } 52 up := strings.SplitN(creds, ":", 2) 53 if len(up) == 1 { 54 return up[0], "" 55 } 56 return up[0], up[1] 57 } 58 59 // ParseRegistryCreds takes a credentials string in the form USERNAME:PASSWORD 60 // and returns a DockerAuthConfig 61 func ParseRegistryCreds(creds string) (*types.DockerAuthConfig, error) { 62 username, password := parseCreds(creds) 63 if username == "" { 64 fmt.Print("Username: ") 65 fmt.Scanln(&username) 66 } 67 if password == "" { 68 fmt.Print("Password: ") 69 termPassword, err := term.ReadPassword(0) 70 if err != nil { 71 return nil, errors.Wrapf(err, "could not read password from terminal") 72 } 73 password = string(termPassword) 74 } 75 76 return &types.DockerAuthConfig{ 77 Username: username, 78 Password: password, 79 }, nil 80 } 81 82 // StringInSlice is deprecated, use containers/common/pkg/util/StringInSlice 83 func StringInSlice(s string, sl []string) bool { 84 return util.StringInSlice(s, sl) 85 } 86 87 // StringMatchRegexSlice determines if a given string matches one of the given regexes, returns bool 88 func StringMatchRegexSlice(s string, re []string) bool { 89 for _, r := range re { 90 m, err := regexp.MatchString(r, s) 91 if err == nil && m { 92 return true 93 } 94 } 95 return false 96 } 97 98 // ImageConfig is a wrapper around the OCIv1 Image Configuration struct exported 99 // by containers/image, but containing additional fields that are not supported 100 // by OCIv1 (but are by Docker v2) - notably OnBuild. 101 type ImageConfig struct { 102 v1.ImageConfig 103 OnBuild []string 104 } 105 106 // GetImageConfig produces a v1.ImageConfig from the --change flag that is 107 // accepted by several Podman commands. It accepts a (limited subset) of 108 // Dockerfile instructions. 109 func GetImageConfig(changes []string) (ImageConfig, error) { 110 // Valid changes: 111 // USER 112 // EXPOSE 113 // ENV 114 // ENTRYPOINT 115 // CMD 116 // VOLUME 117 // WORKDIR 118 // LABEL 119 // STOPSIGNAL 120 // ONBUILD 121 122 config := ImageConfig{} 123 124 for _, change := range changes { 125 // First, let's assume proper Dockerfile format - space 126 // separator between instruction and value 127 split := strings.SplitN(change, " ", 2) 128 129 if len(split) != 2 { 130 split = strings.SplitN(change, "=", 2) 131 if len(split) != 2 { 132 return ImageConfig{}, errors.Errorf("invalid change %q - must be formatted as KEY VALUE", change) 133 } 134 } 135 136 outerKey := strings.ToUpper(strings.TrimSpace(split[0])) 137 value := strings.TrimSpace(split[1]) 138 switch outerKey { 139 case "USER": 140 // Assume literal contents are the user. 141 if value == "" { 142 return ImageConfig{}, errors.Errorf("invalid change %q - must provide a value to USER", change) 143 } 144 config.User = value 145 case "EXPOSE": 146 // EXPOSE is either [portnum] or 147 // [portnum]/[proto] 148 // Protocol must be "tcp" or "udp" 149 splitPort := strings.Split(value, "/") 150 if len(splitPort) > 2 { 151 return ImageConfig{}, errors.Errorf("invalid change %q - EXPOSE port must be formatted as PORT[/PROTO]", change) 152 } 153 portNum, err := strconv.Atoi(splitPort[0]) 154 if err != nil { 155 return ImageConfig{}, errors.Wrapf(err, "invalid change %q - EXPOSE port must be an integer", change) 156 } 157 if portNum > 65535 || portNum <= 0 { 158 return ImageConfig{}, errors.Errorf("invalid change %q - EXPOSE port must be a valid port number", change) 159 } 160 proto := "tcp" 161 if len(splitPort) > 1 { 162 testProto := strings.ToLower(splitPort[1]) 163 switch testProto { 164 case "tcp", "udp": 165 proto = testProto 166 default: 167 return ImageConfig{}, errors.Errorf("invalid change %q - EXPOSE protocol must be TCP or UDP", change) 168 } 169 } 170 if config.ExposedPorts == nil { 171 config.ExposedPorts = make(map[string]struct{}) 172 } 173 config.ExposedPorts[fmt.Sprintf("%d/%s", portNum, proto)] = struct{}{} 174 case "ENV": 175 // Format is either: 176 // ENV key=value 177 // ENV key=value key=value ... 178 // ENV key value 179 // Both keys and values can be surrounded by quotes to group them. 180 // For now: we only support key=value 181 // We will attempt to strip quotation marks if present. 182 183 var ( 184 key, val string 185 ) 186 187 splitEnv := strings.SplitN(value, "=", 2) 188 key = splitEnv[0] 189 // We do need a key 190 if key == "" { 191 return ImageConfig{}, errors.Errorf("invalid change %q - ENV must have at least one argument", change) 192 } 193 // Perfectly valid to not have a value 194 if len(splitEnv) == 2 { 195 val = splitEnv[1] 196 } 197 198 if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) { 199 key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`) 200 } 201 if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) { 202 val = strings.TrimPrefix(strings.TrimSuffix(val, `"`), `"`) 203 } 204 config.Env = append(config.Env, fmt.Sprintf("%s=%s", key, val)) 205 case "ENTRYPOINT": 206 // Two valid forms. 207 // First, JSON array. 208 // Second, not a JSON array - we interpret this as an 209 // argument to `sh -c`, unless empty, in which case we 210 // just use a blank entrypoint. 211 testUnmarshal := []string{} 212 if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil { 213 // It ain't valid JSON, so assume it's an 214 // argument to sh -c if not empty. 215 if value != "" { 216 config.Entrypoint = []string{"/bin/sh", "-c", value} 217 } else { 218 config.Entrypoint = []string{} 219 } 220 } else { 221 // Valid JSON 222 config.Entrypoint = testUnmarshal 223 } 224 case "CMD": 225 // Same valid forms as entrypoint. 226 // However, where ENTRYPOINT assumes that 'ENTRYPOINT ' 227 // means no entrypoint, CMD assumes it is 'sh -c' with 228 // no third argument. 229 testUnmarshal := []string{} 230 if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil { 231 // It ain't valid JSON, so assume it's an 232 // argument to sh -c. 233 // Only include volume if it's not "" 234 config.Cmd = []string{"/bin/sh", "-c"} 235 if value != "" { 236 config.Cmd = append(config.Cmd, value) 237 } 238 } else { 239 // Valid JSON 240 config.Cmd = testUnmarshal 241 } 242 case "VOLUME": 243 // Either a JSON array or a set of space-separated 244 // paths. 245 // Acts rather similar to ENTRYPOINT and CMD, but always 246 // appends rather than replacing, and no sh -c prepend. 247 testUnmarshal := []string{} 248 if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil { 249 // Not valid JSON, so split on spaces 250 testUnmarshal = strings.Split(value, " ") 251 } 252 if len(testUnmarshal) == 0 { 253 return ImageConfig{}, errors.Errorf("invalid change %q - must provide at least one argument to VOLUME", change) 254 } 255 for _, vol := range testUnmarshal { 256 if vol == "" { 257 return ImageConfig{}, errors.Errorf("invalid change %q - VOLUME paths must not be empty", change) 258 } 259 if config.Volumes == nil { 260 config.Volumes = make(map[string]struct{}) 261 } 262 config.Volumes[vol] = struct{}{} 263 } 264 case "WORKDIR": 265 // This can be passed multiple times. 266 // Each successive invocation is treated as relative to 267 // the previous one - so WORKDIR /A, WORKDIR b, 268 // WORKDIR c results in /A/b/c 269 // Just need to check it's not empty... 270 if value == "" { 271 return ImageConfig{}, errors.Errorf("invalid change %q - must provide a non-empty WORKDIR", change) 272 } 273 config.WorkingDir = filepath.Join(config.WorkingDir, value) 274 case "LABEL": 275 // Same general idea as ENV, but we no longer allow " " 276 // as a separator. 277 // We didn't do that for ENV either, so nice and easy. 278 // Potentially problematic: LABEL might theoretically 279 // allow an = in the key? If people really do this, we 280 // may need to investigate more advanced parsing. 281 var ( 282 key, val string 283 ) 284 285 splitLabel := strings.SplitN(value, "=", 2) 286 // Unlike ENV, LABEL must have a value 287 if len(splitLabel) != 2 { 288 return ImageConfig{}, errors.Errorf("invalid change %q - LABEL must be formatted key=value", change) 289 } 290 key = splitLabel[0] 291 val = splitLabel[1] 292 293 if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) { 294 key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`) 295 } 296 if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) { 297 val = strings.TrimPrefix(strings.TrimSuffix(val, `"`), `"`) 298 } 299 // Check key after we strip quotations 300 if key == "" { 301 return ImageConfig{}, errors.Errorf("invalid change %q - LABEL must have a non-empty key", change) 302 } 303 if config.Labels == nil { 304 config.Labels = make(map[string]string) 305 } 306 config.Labels[key] = val 307 case "STOPSIGNAL": 308 // Check the provided signal for validity. 309 killSignal, err := ParseSignal(value) 310 if err != nil { 311 return ImageConfig{}, errors.Wrapf(err, "invalid change %q - KILLSIGNAL must be given a valid signal", change) 312 } 313 config.StopSignal = fmt.Sprintf("%d", killSignal) 314 case "ONBUILD": 315 // Onbuild always appends. 316 if value == "" { 317 return ImageConfig{}, errors.Errorf("invalid change %q - ONBUILD must be given an argument", change) 318 } 319 config.OnBuild = append(config.OnBuild, value) 320 default: 321 return ImageConfig{}, errors.Errorf("invalid change %q - invalid instruction %s", change, outerKey) 322 } 323 } 324 325 return config, nil 326 } 327 328 // ParseSignal parses and validates a signal name or number. 329 func ParseSignal(rawSignal string) (syscall.Signal, error) { 330 // Strip off leading dash, to allow -1 or -HUP 331 basename := strings.TrimPrefix(rawSignal, "-") 332 333 sig, err := signal.ParseSignal(basename) 334 if err != nil { 335 return -1, err 336 } 337 // 64 is SIGRTMAX; wish we could get this from a standard Go library 338 if sig < 1 || sig > 64 { 339 return -1, errors.Errorf("valid signals are 1 through 64") 340 } 341 return sig, nil 342 } 343 344 // GetKeepIDMapping returns the mappings and the user to use when keep-id is used 345 func GetKeepIDMapping() (*stypes.IDMappingOptions, int, int, error) { 346 if !rootless.IsRootless() { 347 return nil, -1, -1, errors.New("keep-id is only supported in rootless mode") 348 } 349 options := stypes.IDMappingOptions{ 350 HostUIDMapping: false, 351 HostGIDMapping: false, 352 } 353 min := func(a, b int) int { 354 if a < b { 355 return a 356 } 357 return b 358 } 359 360 uid := rootless.GetRootlessUID() 361 gid := rootless.GetRootlessGID() 362 363 uids, gids, err := rootless.GetConfiguredMappings() 364 if err != nil { 365 return nil, -1, -1, errors.Wrapf(err, "cannot read mappings") 366 } 367 if len(uids) == 0 || len(gids) == 0 { 368 return nil, -1, -1, errors.Wrapf(err, "keep-id requires additional UIDs or GIDs defined in /etc/subuid and /etc/subgid to function correctly") 369 } 370 maxUID, maxGID := 0, 0 371 for _, u := range uids { 372 maxUID += u.Size 373 } 374 for _, g := range gids { 375 maxGID += g.Size 376 } 377 378 options.UIDMap, options.GIDMap = nil, nil 379 380 options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) 381 options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) 382 if maxUID > uid { 383 options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) 384 } 385 386 options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) 387 options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) 388 if maxGID > gid { 389 options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) 390 } 391 392 return &options, uid, gid, nil 393 } 394 395 // GetNoMapMapping returns the mappings and the user to use when nomap is used 396 func GetNoMapMapping() (*stypes.IDMappingOptions, int, int, error) { 397 if !rootless.IsRootless() { 398 return nil, -1, -1, errors.New("nomap is only supported in rootless mode") 399 } 400 options := stypes.IDMappingOptions{ 401 HostUIDMapping: false, 402 HostGIDMapping: false, 403 } 404 uids, gids, err := rootless.GetConfiguredMappings() 405 if err != nil { 406 return nil, -1, -1, errors.Wrapf(err, "cannot read mappings") 407 } 408 if len(uids) == 0 || len(gids) == 0 { 409 return nil, -1, -1, errors.Wrapf(err, "nomap requires additional UIDs or GIDs defined in /etc/subuid and /etc/subgid to function correctly") 410 } 411 options.UIDMap, options.GIDMap = nil, nil 412 uid, gid := 0, 0 413 for _, u := range uids { 414 options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: uid + 1, Size: u.Size}) 415 uid += u.Size 416 } 417 for _, g := range gids { 418 options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: gid + 1, Size: g.Size}) 419 gid += g.Size 420 } 421 return &options, 0, 0, nil 422 } 423 424 // ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping 425 func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*stypes.IDMappingOptions, error) { 426 options := stypes.IDMappingOptions{ 427 HostUIDMapping: true, 428 HostGIDMapping: true, 429 } 430 431 if mode.IsAuto() { 432 var err error 433 options.HostUIDMapping = false 434 options.HostGIDMapping = false 435 options.AutoUserNs = true 436 opts, err := mode.GetAutoOptions() 437 if err != nil { 438 return nil, err 439 } 440 options.AutoUserNsOpts = *opts 441 return &options, nil 442 } 443 if mode.IsKeepID() || mode.IsNoMap() { 444 options.HostUIDMapping = false 445 options.HostGIDMapping = false 446 return &options, nil 447 } 448 449 if subGIDMap == "" && subUIDMap != "" { 450 subGIDMap = subUIDMap 451 } 452 if subUIDMap == "" && subGIDMap != "" { 453 subUIDMap = subGIDMap 454 } 455 if len(gidMapSlice) == 0 && len(uidMapSlice) != 0 { 456 gidMapSlice = uidMapSlice 457 } 458 if len(uidMapSlice) == 0 && len(gidMapSlice) != 0 { 459 uidMapSlice = gidMapSlice 460 } 461 462 if subUIDMap != "" && subGIDMap != "" { 463 mappings, err := idtools.NewIDMappings(subUIDMap, subGIDMap) 464 if err != nil { 465 return nil, err 466 } 467 options.UIDMap = mappings.UIDs() 468 options.GIDMap = mappings.GIDs() 469 } 470 parsedUIDMap, err := idtools.ParseIDMap(uidMapSlice, "UID") 471 if err != nil { 472 return nil, err 473 } 474 parsedGIDMap, err := idtools.ParseIDMap(gidMapSlice, "GID") 475 if err != nil { 476 return nil, err 477 } 478 options.UIDMap = append(options.UIDMap, parsedUIDMap...) 479 options.GIDMap = append(options.GIDMap, parsedGIDMap...) 480 if len(options.UIDMap) > 0 { 481 options.HostUIDMapping = false 482 } 483 if len(options.GIDMap) > 0 { 484 options.HostGIDMapping = false 485 } 486 return &options, nil 487 } 488 489 var ( 490 rootlessConfigHomeDirOnce sync.Once 491 rootlessConfigHomeDir string 492 rootlessRuntimeDirOnce sync.Once 493 rootlessRuntimeDir string 494 ) 495 496 type tomlOptionsConfig struct { 497 MountProgram string `toml:"mount_program"` 498 } 499 500 type tomlConfig struct { 501 Storage struct { 502 Driver string `toml:"driver"` 503 RunRoot string `toml:"runroot"` 504 GraphRoot string `toml:"graphroot"` 505 Options struct{ tomlOptionsConfig } `toml:"options"` 506 } `toml:"storage"` 507 } 508 509 func getTomlStorage(storeOptions *stypes.StoreOptions) *tomlConfig { 510 config := new(tomlConfig) 511 512 config.Storage.Driver = storeOptions.GraphDriverName 513 config.Storage.RunRoot = storeOptions.RunRoot 514 config.Storage.GraphRoot = storeOptions.GraphRoot 515 for _, i := range storeOptions.GraphDriverOptions { 516 s := strings.SplitN(i, "=", 2) 517 if s[0] == "overlay.mount_program" && len(s) == 2 { 518 config.Storage.Options.MountProgram = s[1] 519 } 520 } 521 522 return config 523 } 524 525 // WriteStorageConfigFile writes the configuration to a file 526 func WriteStorageConfigFile(storageOpts *stypes.StoreOptions, storageConf string) error { 527 if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil { 528 return err 529 } 530 storageFile, err := os.OpenFile(storageConf, os.O_RDWR|os.O_TRUNC, 0600) 531 if err != nil { 532 return err 533 } 534 tomlConfiguration := getTomlStorage(storageOpts) 535 defer errorhandling.CloseQuiet(storageFile) 536 enc := toml.NewEncoder(storageFile) 537 if err := enc.Encode(tomlConfiguration); err != nil { 538 if err := os.Remove(storageConf); err != nil { 539 logrus.Error(err) 540 } 541 return err 542 } 543 return nil 544 } 545 546 // ParseInputTime takes the users input and to determine if it is valid and 547 // returns a time format and error. The input is compared to known time formats 548 // or a duration which implies no-duration 549 func ParseInputTime(inputTime string, since bool) (time.Time, error) { 550 timeFormats := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05", "2006-01-02T15:04:05.999999999", 551 "2006-01-02Z07:00", "2006-01-02"} 552 // iterate the supported time formats 553 for _, tf := range timeFormats { 554 t, err := time.Parse(tf, inputTime) 555 if err == nil { 556 return t, nil 557 } 558 } 559 560 unixTimestamp, err := strconv.ParseFloat(inputTime, 64) 561 if err == nil { 562 iPart, fPart := math.Modf(unixTimestamp) 563 return time.Unix(int64(iPart), int64(fPart*1_000_000_000)).UTC(), nil 564 } 565 566 // input might be a duration 567 duration, err := time.ParseDuration(inputTime) 568 if err != nil { 569 return time.Time{}, errors.Errorf("unable to interpret time value") 570 } 571 if since { 572 return time.Now().Add(-duration), nil 573 } 574 return time.Now().Add(duration), nil 575 } 576 577 // OpenExclusiveFile opens a file for writing and ensure it doesn't already exist 578 func OpenExclusiveFile(path string) (*os.File, error) { 579 baseDir := filepath.Dir(path) 580 if baseDir != "" { 581 if _, err := os.Stat(baseDir); err != nil { 582 return nil, err 583 } 584 } 585 return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) 586 } 587 588 // ExitCode reads the error message when failing to executing container process 589 // and then returns 0 if no error, 126 if command does not exist, or 127 for 590 // all other errors 591 func ExitCode(err error) int { 592 if err == nil { 593 return 0 594 } 595 e := strings.ToLower(err.Error()) 596 if strings.Contains(e, "file not found") || 597 strings.Contains(e, "no such file or directory") { 598 return 127 599 } 600 601 return 126 602 } 603 604 // HomeDir returns the home directory for the current user. 605 func HomeDir() (string, error) { 606 home := os.Getenv("HOME") 607 if home == "" { 608 usr, err := user.LookupId(fmt.Sprintf("%d", rootless.GetRootlessUID())) 609 if err != nil { 610 return "", errors.Wrapf(err, "unable to resolve HOME directory") 611 } 612 home = usr.HomeDir 613 } 614 return home, nil 615 } 616 617 func Tmpdir() string { 618 tmpdir := os.Getenv("TMPDIR") 619 if tmpdir == "" { 620 tmpdir = "/var/tmp" 621 } 622 623 return tmpdir 624 } 625 626 // ValidateSysctls validates a list of sysctl and returns it. 627 func ValidateSysctls(strSlice []string) (map[string]string, error) { 628 sysctl := make(map[string]string) 629 validSysctlMap := map[string]bool{ 630 "kernel.msgmax": true, 631 "kernel.msgmnb": true, 632 "kernel.msgmni": true, 633 "kernel.sem": true, 634 "kernel.shmall": true, 635 "kernel.shmmax": true, 636 "kernel.shmmni": true, 637 "kernel.shm_rmid_forced": true, 638 } 639 validSysctlPrefixes := []string{ 640 "net.", 641 "fs.mqueue.", 642 } 643 644 for _, val := range strSlice { 645 foundMatch := false 646 arr := strings.Split(val, "=") 647 if len(arr) < 2 { 648 return nil, errors.Errorf("%s is invalid, sysctl values must be in the form of KEY=VALUE", val) 649 } 650 651 trimmed := fmt.Sprintf("%s=%s", strings.TrimSpace(arr[0]), strings.TrimSpace(arr[1])) 652 if trimmed != val { 653 return nil, errors.Errorf("'%s' is invalid, extra spaces found", val) 654 } 655 656 if validSysctlMap[arr[0]] { 657 sysctl[arr[0]] = arr[1] 658 continue 659 } 660 661 for _, prefix := range validSysctlPrefixes { 662 if strings.HasPrefix(arr[0], prefix) { 663 sysctl[arr[0]] = arr[1] 664 foundMatch = true 665 break 666 } 667 } 668 if !foundMatch { 669 return nil, errors.Errorf("sysctl '%s' is not allowed", arr[0]) 670 } 671 } 672 return sysctl, nil 673 } 674 675 func DefaultContainerConfig() *config.Config { 676 return containerConfig 677 } 678 679 func CreateCidFile(cidfile string, id string) error { 680 cidFile, err := OpenExclusiveFile(cidfile) 681 if err != nil { 682 if os.IsExist(err) { 683 return errors.Errorf("container id file exists. Ensure another container is not using it or delete %s", cidfile) 684 } 685 return errors.Errorf("opening cidfile %s", cidfile) 686 } 687 if _, err = cidFile.WriteString(id); err != nil { 688 logrus.Error(err) 689 } 690 cidFile.Close() 691 return nil 692 } 693 694 // DefaultCPUPeriod is the default CPU period (100ms) in microseconds, which is 695 // the same default as Kubernetes. 696 const DefaultCPUPeriod uint64 = 100000 697 698 // CoresToPeriodAndQuota converts a fraction of cores to the equivalent 699 // Completely Fair Scheduler (CFS) parameters period and quota. 700 // 701 // Cores is a fraction of the CFS period that a container may use. Period and 702 // Quota are in microseconds. 703 func CoresToPeriodAndQuota(cores float64) (uint64, int64) { 704 return DefaultCPUPeriod, int64(cores * float64(DefaultCPUPeriod)) 705 } 706 707 // PeriodAndQuotaToCores takes the CFS parameters period and quota and returns 708 // a fraction that represents the limit to the number of cores that can be 709 // utilized over the scheduling period. 710 // 711 // Cores is a fraction of the CFS period that a container may use. Period and 712 // Quota are in microseconds. 713 func PeriodAndQuotaToCores(period uint64, quota int64) float64 { 714 return float64(quota) / float64(period) 715 } 716 717 // IDtoolsToRuntimeSpec converts idtools ID mapping to the one of the runtime spec. 718 func IDtoolsToRuntimeSpec(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) { 719 for _, idmap := range idMaps { 720 tempIDMap := specs.LinuxIDMapping{ 721 ContainerID: uint32(idmap.ContainerID), 722 HostID: uint32(idmap.HostID), 723 Size: uint32(idmap.Size), 724 } 725 convertedIDMap = append(convertedIDMap, tempIDMap) 726 } 727 return convertedIDMap 728 } 729 730 func LookupUser(name string) (*user.User, error) { 731 // Assume UID look up first, if it fails lookup by username 732 if u, err := user.LookupId(name); err == nil { 733 return u, nil 734 } 735 return user.Lookup(name) 736 } 737 738 // SizeOfPath determines the file usage of a given path. it was called volumeSize in v1 739 // and now is made to be generic and take a path instead of a libpod volume 740 func SizeOfPath(path string) (uint64, error) { 741 var size uint64 742 err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error { 743 if err == nil && !d.IsDir() { 744 info, err := d.Info() 745 if err != nil { 746 return err 747 } 748 size += uint64(info.Size()) 749 } 750 return err 751 }) 752 return size, err 753 }