...

Source file src/github.com/Microsoft/hcsshim/internal/devices/pnp.go

Documentation: github.com/Microsoft/hcsshim/internal/devices

     1  //go:build windows
     2  // +build windows
     3  
     4  package devices
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  	"net"
    11  	"strings"
    12  
    13  	"github.com/Microsoft/hcsshim/internal/cmd"
    14  	"github.com/Microsoft/hcsshim/internal/log"
    15  	"github.com/Microsoft/hcsshim/internal/logfields"
    16  	"github.com/Microsoft/hcsshim/internal/uvm"
    17  	"github.com/Microsoft/hcsshim/internal/winapi"
    18  	"github.com/pkg/errors"
    19  	"github.com/sirupsen/logrus"
    20  )
    21  
    22  const (
    23  	uvmPnpExePath                  = "C:\\Windows\\System32\\pnputil.exe"
    24  	pnputilNoMoreItemsErrorMessage = `driver not ranked higher than existing driver in UVM.
    25  										if drivers were not previously present in the UVM, this
    26  										is an expected race and can be ignored.`
    27  )
    28  
    29  var noExecOutputErr = errors.New("failed to get any pipe output")
    30  
    31  // createPnPInstallDriverCommand creates a pnputil command to add and install drivers
    32  // present in `driverUVMPath` and all subdirectories.
    33  func createPnPInstallDriverCommand(driverUVMPath string) []string {
    34  	dirFormatted := fmt.Sprintf("%s/*.inf", driverUVMPath)
    35  	args := []string{
    36  		"cmd",
    37  		"/c",
    38  		uvmPnpExePath,
    39  		"/add-driver",
    40  		dirFormatted,
    41  		"/subdirs",
    42  		"/install",
    43  	}
    44  	return args
    45  }
    46  
    47  // execPnPInstallDriver makes the calls to exec in the uvm the pnp command
    48  // that installs a driver previously mounted into the uvm.
    49  func execPnPInstallDriver(ctx context.Context, vm *uvm.UtilityVM, driverDir string) error {
    50  	args := createPnPInstallDriverCommand(driverDir)
    51  	cmdReq := &cmd.CmdProcessRequest{
    52  		Args: args,
    53  	}
    54  	exitCode, err := cmd.ExecInUvm(ctx, vm, cmdReq)
    55  	if err != nil && exitCode != winapi.ERROR_NO_MORE_ITEMS {
    56  		return errors.Wrapf(err, "failed to install driver %s in uvm with exit code %d", driverDir, exitCode)
    57  	} else if exitCode == winapi.ERROR_NO_MORE_ITEMS {
    58  		// As mentioned in `pnputilNoMoreItemsErrorMessage`, this exit code comes from pnputil
    59  		// but is not necessarily an error
    60  		log.G(ctx).WithFields(logrus.Fields{
    61  			logfields.UVMID: vm.ID(),
    62  			"driver":        driverDir,
    63  			"error":         pnputilNoMoreItemsErrorMessage,
    64  		}).Warn("expected version of driver may not have been installed")
    65  	}
    66  
    67  	log.G(ctx).WithField("added drivers", driverDir).Debug("installed drivers")
    68  	return nil
    69  }
    70  
    71  // readCsPipeOutput is a helper function that connects to a listener and reads
    72  // the connection's comma separated output until done. resulting comma separated
    73  // values are returned in the `result` param. The `errChan` param is used to
    74  // propagate an errors to the calling function.
    75  func readCsPipeOutput(l net.Listener, errChan chan<- error, result *[]string) {
    76  	defer close(errChan)
    77  	c, err := l.Accept()
    78  	if err != nil {
    79  		errChan <- errors.Wrapf(err, "failed to accept named pipe")
    80  		return
    81  	}
    82  	bytes, err := io.ReadAll(c)
    83  	if err != nil {
    84  		errChan <- err
    85  		return
    86  	}
    87  
    88  	elementsAsString := strings.TrimSuffix(string(bytes), "\n")
    89  	elements := strings.Split(elementsAsString, ",")
    90  	*result = append(*result, elements...)
    91  
    92  	if len(*result) == 0 {
    93  		errChan <- noExecOutputErr
    94  		return
    95  	}
    96  
    97  	errChan <- nil
    98  }
    99  
   100  // readAllPipeOutput is a helper function that connects to a listener and attempts to
   101  // read the connection's entire output. Resulting output is returned as a string
   102  // in the `result` param. The `errChan` param is used to propagate an errors to
   103  // the calling function.
   104  func readAllPipeOutput(l net.Listener, errChan chan<- error, result *string) {
   105  	defer close(errChan)
   106  	c, err := l.Accept()
   107  	if err != nil {
   108  		errChan <- errors.Wrapf(err, "failed to accept named pipe")
   109  		return
   110  	}
   111  	bytes, err := io.ReadAll(c)
   112  	if err != nil {
   113  		errChan <- err
   114  		return
   115  	}
   116  
   117  	*result = string(bytes)
   118  
   119  	if len(*result) == 0 {
   120  		errChan <- noExecOutputErr
   121  		return
   122  	}
   123  
   124  	errChan <- nil
   125  }
   126  

View as plain text