-
Notifications
You must be signed in to change notification settings - Fork 280
refactor drivers util methods #2645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
rawahars
wants to merge
1
commit into
microsoft:main
Choose a base branch
from
rawahars:refactor-drivers
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+251
−196
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| //go:build windows | ||
|
|
||
| // Package drivers provides utility methods for installing drivers into | ||
| // Linux or Windows utility VMs (UVMs). | ||
| // | ||
| // These utility methods are used by 'containerd-shim-runhcs-v1' as well as | ||
| // V2 shims to install the drivers. | ||
| // | ||
| // For LCOW guests, driver installation is performed by executing the | ||
| // 'install-drivers' binary inside the guest via [ExecGCSInstallDriver]. | ||
| // | ||
| // For WCOW guests, driver installation is performed by invoking 'pnputil' | ||
| // inside the UVM via [ExecPnPInstallDriver]. | ||
| package drivers |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| //go:build windows | ||
|
|
||
| package drivers | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "io" | ||
| "net" | ||
|
|
||
| "github.com/Microsoft/go-winio/pkg/guid" | ||
|
|
||
| "github.com/Microsoft/hcsshim/internal/cmd" | ||
| "github.com/Microsoft/hcsshim/internal/guestpath" | ||
| ) | ||
|
|
||
| var errNoExecOutput = errors.New("failed to get any pipe output") | ||
|
|
||
| // guest is the UVM instance in which the driver will be installed. | ||
| type guest interface { | ||
| ExecInUVM(ctx context.Context, request *cmd.CmdProcessRequest) (int, error) | ||
| } | ||
|
|
||
| // ExecGCSInstallDriver installs a driver into the UVM by running 'install-drivers' | ||
| // inside the guest. hostPath is the host VHD path and guestPath is the | ||
| // SCSI-mounted location inside the UVM. Returns an error if installation fails, | ||
| // along with any stderr output from the guest process. | ||
| func ExecGCSInstallDriver(ctx context.Context, guest guest, hostPath string, guestPath string) error { | ||
| driverReadWriteDir, err := getDriverWorkDir(hostPath) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to create a guid path for driver %+v: %w", hostPath, err) | ||
| } | ||
|
|
||
| p, l, err := cmd.CreateNamedPipeListener() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer l.Close() | ||
|
|
||
| var stderrOutput string | ||
| errChan := make(chan error) | ||
|
|
||
| go readAllPipeOutput(l, errChan, &stderrOutput) | ||
|
|
||
| args := []string{ | ||
| "/bin/install-drivers", | ||
| driverReadWriteDir, | ||
| guestPath, | ||
| } | ||
| req := &cmd.CmdProcessRequest{ | ||
| Args: args, | ||
| Stderr: p, | ||
| } | ||
|
|
||
| // A call to `ExecInUvm` may fail in the following ways: | ||
| // - The process runs and exits with a non-zero exit code. In this case we need to wait on the output | ||
| // from stderr so we can log it for debugging. | ||
| // - There's an error trying to run the process. No need to wait for stderr logs. | ||
| // - There's an error copying IO. No need to wait for stderr logs. | ||
| // | ||
| // Since we cannot distinguish between the cases above, we should always wait to read the stderr output. | ||
| exitCode, execErr := guest.ExecInUVM(ctx, req) | ||
|
|
||
| // wait to finish parsing stdout results | ||
| select { | ||
| case err := <-errChan: | ||
| if err != nil && !errors.Is(err, errNoExecOutput) { | ||
| return fmt.Errorf("failed to get stderr output from command %s: %w", guestPath, err) | ||
| } | ||
| case <-ctx.Done(): | ||
| return fmt.Errorf("timed out waiting for the console output from installing driver %s: %w", guestPath, ctx.Err()) | ||
| } | ||
|
|
||
| if execErr != nil { | ||
| return fmt.Errorf("%w: failed to install driver %s in uvm with exit code %d: %v", execErr, guestPath, exitCode, stderrOutput) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // getDriverWorkDir returns the deterministic guest path used as the overlayfs | ||
| // root for a driver installation. 'install-drivers' uses the read-only SCSI VHD | ||
| // as the lower layer and uses this directory for the upper, work, and content | ||
| // directories, giving depmod/modprobe a writable view. | ||
| // | ||
| // If the directory already exists, 'install-drivers' skips reinstallation. | ||
| // The path is derived from a v5 UUID seeded with the host VHD path, | ||
| // ensuring a stable mapping across reboots. | ||
| func getDriverWorkDir(hostPath string) (string, error) { | ||
| // 914aadc8-f700-4365-8016-ddad0a9d406d. Random GUID chosen for namespace. | ||
| ns := guid.GUID{ | ||
| Data1: 0x914aadc8, | ||
| Data2: 0xf700, | ||
| Data3: 0x4365, | ||
| Data4: [8]byte{0x80, 0x16, 0xdd, 0xad, 0x0a, 0x9d, 0x40, 0x6d}, | ||
| } | ||
|
|
||
| driverGUID, err := guid.NewV5(ns, []byte(hostPath)) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| return fmt.Sprintf(guestpath.LCOWGlobalDriverPrefixFmt, driverGUID.String()), nil | ||
| } | ||
|
|
||
| // readAllPipeOutput is a helper function that connects to a listener and attempts to | ||
| // read the connection's entire output. Resulting output is returned as a string | ||
| // in the `result` param. The `errChan` param is used to propagate an errors to | ||
| // the calling function. | ||
| func readAllPipeOutput(l net.Listener, errChan chan<- error, result *string) { | ||
| defer close(errChan) | ||
| c, err := l.Accept() | ||
| if err != nil { | ||
| errChan <- fmt.Errorf("failed to accept named pipe: %w", err) | ||
| return | ||
| } | ||
| bytes, err := io.ReadAll(c) | ||
| if err != nil { | ||
| errChan <- err | ||
| return | ||
| } | ||
|
|
||
| *result = string(bytes) | ||
|
|
||
| if len(*result) == 0 { | ||
| errChan <- errNoExecOutput | ||
| return | ||
| } | ||
|
|
||
| errChan <- nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| //go:build windows | ||
|
|
||
| package drivers | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| "github.com/Microsoft/hcsshim/internal/cmd" | ||
| "github.com/Microsoft/hcsshim/internal/log" | ||
| "github.com/Microsoft/hcsshim/internal/logfields" | ||
| "github.com/Microsoft/hcsshim/internal/uvm" | ||
| "github.com/Microsoft/hcsshim/internal/winapi" | ||
|
|
||
| "github.com/sirupsen/logrus" | ||
| ) | ||
|
|
||
| const ( | ||
| uvmPnpExePath = "C:\\Windows\\System32\\pnputil.exe" | ||
| pnputilNoMoreItemsErrorMessage = `driver not ranked higher than existing driver in UVM. | ||
| if drivers were not previously present in the UVM, this | ||
| is an expected race and can be ignored.` | ||
| ) | ||
|
|
||
| // createPnPInstallDriverCommand creates a pnputil command to add and install drivers | ||
| // present in `driverUVMPath` and all subdirectories. | ||
| func createPnPInstallDriverCommand(driverUVMPath string) []string { | ||
| dirFormatted := fmt.Sprintf("%s/*.inf", driverUVMPath) | ||
| args := []string{ | ||
| "cmd", | ||
| "/c", | ||
| uvmPnpExePath, | ||
| "/add-driver", | ||
| dirFormatted, | ||
| "/subdirs", | ||
| "/install", | ||
| } | ||
| return args | ||
| } | ||
|
|
||
| // ExecPnPInstallDriver makes the calls to exec in the uvm the pnp command | ||
| // that installs a driver previously mounted into the uvm. | ||
| func ExecPnPInstallDriver(ctx context.Context, vm *uvm.UtilityVM, driverDir string) error { | ||
| args := createPnPInstallDriverCommand(driverDir) | ||
| cmdReq := &cmd.CmdProcessRequest{ | ||
| Args: args, | ||
| } | ||
| exitCode, err := vm.ExecInUVM(ctx, cmdReq) | ||
| if err != nil && exitCode != winapi.ERROR_NO_MORE_ITEMS { | ||
| return fmt.Errorf("failed to install driver %s in uvm with exit code %d: %w", driverDir, exitCode, err) | ||
| } else if exitCode == winapi.ERROR_NO_MORE_ITEMS { | ||
| // As mentioned in `pnputilNoMoreItemsErrorMessage`, this exit code comes from pnputil | ||
| // but is not necessarily an error | ||
| log.G(ctx).WithFields(logrus.Fields{ | ||
| logfields.UVMID: vm.ID(), | ||
| "driver": driverDir, | ||
| "error": pnputilNoMoreItemsErrorMessage, | ||
| }).Warn("expected version of driver may not have been installed") | ||
| } | ||
|
|
||
| log.G(ctx).WithField("added drivers", driverDir).Debug("installed drivers") | ||
| return nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we already have a
guestmanager.Managerinterface, why not use that directly?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We would be getting rid of
guestmanager.Managerinterface. In accordance withAccept interfaces, return structsapproach, the suggestion was to declare the narrow-scoped interfaces on caller side.