github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/parent/dynidtools/dynidtools.go (about) 1 package dynidtools 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "os" 9 "os/exec" 10 "strconv" 11 "strings" 12 13 "github.com/rootless-containers/rootlesskit/v2/pkg/parent/idtools" 14 "github.com/sirupsen/logrus" 15 ) 16 17 func withoutDuplicates(sliceList []idtools.SubIDRange) []idtools.SubIDRange { 18 seenKeys := make(map[idtools.SubIDRange]bool) 19 var list []idtools.SubIDRange 20 for _, item := range sliceList { 21 if _, value := seenKeys[item]; !value { 22 seenKeys[item] = true 23 list = append(list, item) 24 } 25 } 26 return list 27 } 28 29 func GetSubIDRanges(uid int, username string) ([]idtools.SubIDRange, []idtools.SubIDRange, error) { 30 getsubidsExeName := "getsubids" 31 if v := os.Getenv("GETSUBIDS"); v != "" { 32 getsubidsExeName = v 33 } 34 getsubidsExe, err := exec.LookPath(getsubidsExeName) 35 if err != nil { 36 return nil, nil, fmt.Errorf("subid-source:dynamic: %w", err) 37 } 38 39 uByUsername, uByUsernameErr := execGetsubids(getsubidsExe, false, username) 40 uByUID, uByUIDErr := execGetsubids(getsubidsExe, false, strconv.Itoa(uid)) 41 // Typically, uByUsernameErr == nil, uByUIDErr == "Error fetching ranges" (exit code 1) 42 if uByUsernameErr != nil { 43 logrus.WithError(uByUsernameErr).Debugf("subid-source:dynamic: failed to get subuids by the username %q", username) 44 } 45 if uByUIDErr != nil { 46 logrus.WithError(uByUIDErr).Debugf("subid-source:dynamic: failed to get subuids by the UID %d", uid) 47 if uByUsernameErr != nil { 48 return nil, nil, fmt.Errorf("subid-source:dynamic: failed to get subuids by the username %q: %w; also failed to get subuids by the UID %d: %v", 49 username, uByUsernameErr, uid, uByUIDErr) 50 } 51 } 52 53 gByUsername, gByUsernameErr := execGetsubids(getsubidsExe, true, username) 54 gByUID, gByUIDErr := execGetsubids(getsubidsExe, true, strconv.Itoa(uid)) 55 // Typically, gByUsernameErr == nil, gByUIDErr == "Error fetching ranges" (exit code 1) 56 if gByUsernameErr != nil { 57 logrus.WithError(gByUsernameErr).Debugf("subid-source:dynamic: failed to get subgids by the username %q", username) 58 } 59 if gByUIDErr != nil { 60 logrus.WithError(gByUIDErr).Debugf("subid-source:dynamic: failed to get subgids by the UID %d", uid) 61 if gByUsernameErr != nil { 62 return nil, nil, fmt.Errorf("subid-source:dynamic: failed to get subgids by the username %q: %w; also failed to get subuids by the UID %d: %v", 63 username, gByUsernameErr, uid, gByUIDErr) 64 } 65 } 66 67 u := withoutDuplicates(append(uByUsername, uByUID...)) 68 g := withoutDuplicates(append(gByUsername, gByUID...)) 69 return u, g, nil 70 } 71 72 // execGetsubids executes `getsubids [-g] user` 73 func execGetsubids(exe string, g bool, s string) ([]idtools.SubIDRange, error) { 74 var args []string 75 if g { 76 args = append(args, "-g") 77 } 78 var stderr bytes.Buffer 79 args = append(args, s) 80 cmd := exec.Command(exe, args...) 81 cmd.Stderr = &stderr 82 logrus.Debugf("Executing %v", cmd.Args) 83 out, err := cmd.Output() 84 if err != nil { 85 return nil, fmt.Errorf("failed to exec %v: %w (stdout=%q, stderr=%q)", cmd.Args, err, string(out), stderr.String()) 86 } 87 r := bytes.NewReader(out) 88 ranges, warns, err := parseGetsubidsOutput(r) 89 for _, warn := range warns { 90 logrus.Warnf("Error while parsing the result of %v: %s (stdout=%q, stderr=%q)", cmd.Args, warn, string(out), stderr.String()) 91 } 92 return ranges, err 93 } 94 95 func parseGetsubidsOutput(r io.Reader) (res []idtools.SubIDRange, warns []string, err error) { 96 sc := bufio.NewScanner(r) 97 for i := 0; sc.Scan(); i++ { 98 line := strings.TrimSpace(sc.Text()) 99 // line is like "0: foo 100000 655360" 100 if line == "" || strings.HasPrefix(line, "#") { 101 continue 102 } 103 splitByColon := strings.Split(line, ":") 104 switch len(splitByColon) { 105 case 0, 1: 106 return res, warns, fmt.Errorf("line %d: unparsable line %q", i+1, line) 107 case 2: 108 // NOP 109 default: 110 warns = append(warns, fmt.Sprintf("line %d: line %q contains unknown fields", i+1, line)) 111 } 112 triplet := strings.Fields(strings.TrimSpace(splitByColon[1])) 113 switch len(triplet) { 114 case 0, 1, 2: 115 return res, warns, fmt.Errorf("line %d: unparsable line %q", i+1, line) 116 case 3: 117 // NOP 118 default: 119 warns = append(warns, fmt.Sprintf("line %d: line %q contains unknown fields", i+1, line)) 120 } 121 var entry idtools.SubIDRange 122 entry.Start, err = strconv.Atoi(triplet[1]) 123 if err != nil { 124 return res, warns, fmt.Errorf("line %d: unparsable line %q: failed to Atoi(%q): %w", i+1, line, triplet[1], err) 125 } 126 entry.Length, err = strconv.Atoi(triplet[2]) 127 if err != nil { 128 return res, warns, fmt.Errorf("line %d: unparsable line %q: failed to Atoi(%q): %w", i+1, line, triplet[2], err) 129 } 130 res = append(res, entry) 131 } 132 err = sc.Err() 133 return 134 }