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  }