VMware vSphere Integrated Containers provider (#206)
* Add Virtual Kubelet provider for VIC Initial virtual kubelet provider for VMware VIC. This provider currently handles creating and starting of a pod VM via the VIC portlayer and persona server. Image store handling via the VIC persona server. This provider currently requires the feature/wolfpack branch of VIC. * Added pod stop and delete. Also added node capacity. Added the ability to stop and delete pod VMs via VIC. Also retrieve node capacity information from the VCH. * Cleanup and readme file Some file clean up and added a Readme.md markdown file for the VIC provider. * Cleaned up errors, added function comments, moved operation code 1. Cleaned up error handling. Set standard for creating errors. 2. Added method prototype comments for all interface functions. 3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder. * Add mocking code and unit tests for podcache, podcreator, and podstarter Used the unit test framework used in VIC to handle assertions in the provider's unit test. Mocking code generated using OSS project mockery, which is compatible with the testify assertion framework. * Vendored packages for the VIC provider Requires feature/wolfpack branch of VIC and a few specific commit sha of projects used within VIC. * Implementation of POD Stopper and Deleter unit tests (#4) * Updated files for initial PR
This commit is contained in:
1
vendor/github.com/vmware/govmomi/toolbox/.gitignore
generated
vendored
Normal file
1
vendor/github.com/vmware/govmomi/toolbox/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.iso
|
||||
158
vendor/github.com/vmware/govmomi/toolbox/README.md
generated
vendored
Normal file
158
vendor/github.com/vmware/govmomi/toolbox/README.md
generated
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
# toolbox - VMware guest tools library for Go #
|
||||
|
||||
## Overview
|
||||
|
||||
The toolbox library is a lightweight, extensible framework for implementing VMware guest tools functionality.
|
||||
The primary focus of the library is the implementation of VM guest RPC protocols, transport and dispatch.
|
||||
These protocols are undocumented for the most part, but [open-vm-tools](https://github.com/vmware/open-vm-tools) serves
|
||||
as a reference implementation. The toolbox provides default implementations of the supported RPCs, which can be
|
||||
overridden and/or extended by consumers.
|
||||
|
||||
## Supported features
|
||||
|
||||
Feature list from the perspective of vSphere public API interaction. The properties, objects and methods listed are
|
||||
relative to
|
||||
the [VirtualMachine](http://pubs.vmware.com/vsphere-60/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.VirtualMachine.html)
|
||||
managed object type.
|
||||
|
||||
### guest.toolsVersionStatus property
|
||||
|
||||
The toolbox reports version as `guestToolsUnmanaged`.
|
||||
|
||||
See [ToolsVersionStatus](http://pubs.vmware.com/vsphere-60/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.vm.GuestInfo.ToolsVersionStatus.html)
|
||||
|
||||
### guest.toolsRunningStatus and guest.guestState properties
|
||||
|
||||
The VMX determines these values based on the toolbox's response to the `ping` RPC.
|
||||
|
||||
### guest.ipAddress property
|
||||
|
||||
The VMX requests this value via the `Set_Option broadcastIP` RPC.
|
||||
|
||||
The default value can be overridden by setting the `Service.PrimaryIP` function.
|
||||
|
||||
See [vim.vm.GuestInfo](http://pubs.vmware.com/vsphere-60/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.vm.GuestInfo.html)
|
||||
|
||||
### guest.net property
|
||||
|
||||
This data is pushed to the VMX using the `SendGuestInfo(INFO_IPADDRESS_V3)` RPC.
|
||||
|
||||
See [GuestNicInfo](http://pubs.vmware.com/vsphere-60/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.vm.GuestInfo.NicInfo.html).
|
||||
|
||||
### ShutdownGuest and RebootGuest methods
|
||||
|
||||
The [PowerCommandHandler](power.go) provides power hooks for customized guest shutdown and reboot.
|
||||
|
||||
### GuestAuthManager object
|
||||
|
||||
Not supported, but authentication can be customized.
|
||||
|
||||
See [vim.vm.guest.AuthManager](http://pubs.vmware.com/vsphere-60/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.vm.guest.AuthManager.html)
|
||||
|
||||
### GuestFileManager object
|
||||
|
||||
| Method | Supported | Client Examples |
|
||||
|---------------------------------|-----------|-------------------------------------------------------------------------------------|
|
||||
| ChangeFileAttributesInGuest | Yes | [chmod](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/chmod.go) |
|
||||
| | | [chown](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/chown.go) |
|
||||
| | | [touch](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/touch.go) |
|
||||
| CreateTemporaryDirectoryInGuest | Yes | [mktemp](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/mktemp.go) |
|
||||
| CreateTemporaryFileInGuest | Yes | [mktemp](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/mktemp.go) |
|
||||
| DeleteDirectoryInGuest | Yes | [rmdir](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/rmdir.go) |
|
||||
| DeleteFileInGuest | Yes | [rm](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/rm.go) |
|
||||
| InitiateFileTransferFromGuest | Yes | [download](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/download.go) |
|
||||
| InitiateFileTransferToGuest | Yes | [upload](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/upload.go) |
|
||||
| ListFilesInGuest | Yes | [ls](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/ls.go) |
|
||||
| MakeDirectoryInGuest | Yes | [mkdir](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/mkdir.go) |
|
||||
| MoveDirectoryInGuest | Yes | [mv](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/mv.go) |
|
||||
| MoveFileInGuest | Yes | [mv](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/mv.go) |
|
||||
|
||||
See [vim.vm.guest.FileManager](http://pubs.vmware.com/vsphere-60/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.vm.guest.FileManager.html)
|
||||
|
||||
### GuestProcessManager
|
||||
|
||||
Currently, the `ListProcessesInGuest` and `TerminateProcessInGuest` methods only apply those processes and goroutines
|
||||
started by `StartProgramInGuest`.
|
||||
|
||||
| Method | Supported | Client Examples |
|
||||
|--------------------------------|-----------|-------------------------------------------------------------------------------------|
|
||||
| ListProcessesInGuest | Yes | [ps](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/ps.go) |
|
||||
| ReadEnvironmentVariableInGuest | Yes | [getenv](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/getenv.go) |
|
||||
| StartProgramInGuest | Yes | [start](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/start.go) |
|
||||
| TerminateProcessInGuest | Yes | [kill](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/kill.go) |
|
||||
|
||||
See [vim.vm.guest.ProcessManager](http://pubs.vmware.com/vsphere-60/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.vm.guest.ProcessManager.html)
|
||||
|
||||
## Extensions
|
||||
|
||||
### Authentication
|
||||
|
||||
Guest operations can be authenticated using the `toolbox.CommandServer.Authenticate` hook.
|
||||
|
||||
### Go functions
|
||||
|
||||
The toolbox [ProcessManager](process.go) can manage both OS processes and Go functions running as go routines.
|
||||
|
||||
### File handlers
|
||||
|
||||
The `hgfs.FileHandler` interface can be used to customize file transfer.
|
||||
|
||||
### Process I/O
|
||||
|
||||
The toolbox provides support for I/O redirection without the use of disk files within the guest.
|
||||
Access to *stdin*, *stdout* and *stderr* streams is implemented as an `hgfs.FileHandler` within the `ProcessManager`.
|
||||
|
||||
See [toolbox.Client](https://github.com/vmware/govmomi/blob/master/guest/toolbox/client.go) and
|
||||
[govc guest.run](https://github.com/vmware/govmomi/blob/master/govc/vm/guest/run.go)
|
||||
|
||||
### http.RoundTripper
|
||||
|
||||
Building on top of the process I/O functionality, `toolbox.NewProcessRoundTrip` can be used to start a Go function to
|
||||
implement the [http.RoundTripper](https://golang.org/pkg/net/http/#RoundTripper) interface over vmx guest RPC. This
|
||||
makes it possible to use the Go [http.Client](https://golang.org/pkg/net/http/#Client) without network access to the VM
|
||||
or to a port that is bound to the guest's loopback address. It is intended for use with bootstrap configuration for
|
||||
example.
|
||||
|
||||
### Directory archives
|
||||
|
||||
The toolbox provides support for transferring directories to and from guests as gzip'd tar streams, without writing the
|
||||
tar file itself to the guest file system. Archive supports is implemented as an `hgfs.FileHandler` within the `hgfs`
|
||||
package. See [hgfs.NewArchiveHandler](https://github.com/vmware/govmomi/blob/master/toolbox/hgfs/archive.go)
|
||||
|
||||
### Linux /proc file access
|
||||
|
||||
With standard vmware-tools, the file size is reported as returned by `stat()` and hence a `Content-Length` header of
|
||||
size `0`. The toolbox reports /proc file size as `hgfs.LargePacketMax` to enable transfer of these files. Note that if
|
||||
the file data fits within in `hgfs.LargePacketMax`, the `Content-Length` header will be correct as it is sent after the
|
||||
first read by the vmx. However, if the file data exceeds `hgfs.LargePacketMax`, the `Content-Length` will be
|
||||
`hgfs.LargePacketMax`, and client side will truncate to that size.
|
||||
|
||||
## Testing
|
||||
|
||||
The Go tests cover most of the toolbox code and can be run on any Linux or MacOSX machine, virtual or otherwise.
|
||||
|
||||
To test the toolbox with vSphere API interaction, it must be run inside a VM managed by vSphere without the standard
|
||||
vmtoolsd running.
|
||||
|
||||
The [toolbox-test.sh](toolbox-test.sh) can be used to run the full suite of toolbox tests with vSphere API interaction.
|
||||
Use the `-s` flag to start the standalone version of the toolbox and leave it running, to test vSphere interaction
|
||||
without running the test suite.
|
||||
|
||||
## Consumers of the toolbox library
|
||||
|
||||
* [Toolbox example main](https://github.com/vmware/govmomi/blob/master/toolbox/toolbox/main.go)
|
||||
|
||||
* [VIC tether toolbox extension](https://github.com/vmware/vic/blob/master/lib/tether/toolbox.go)
|
||||
|
||||
* [VIC container VM tether](https://github.com/vmware/vic/blob/main/cmd/tether/main_linux.go)
|
||||
|
||||
* [VIC container host tether](https://github.com/vmware/vic/blob/master/cmd/vic-init/main_linux.go)
|
||||
|
||||
## Supported guests
|
||||
|
||||
The toolbox guest RPC implementations tend to be simple and portable thanks to the Go standard library, but are only
|
||||
supported on Linux currently. Support for other guests, such as Windows, has been kept in mind but not yet tested.
|
||||
|
||||
## Supported vSphere Versions
|
||||
|
||||
The toolbox is supported with vSphere 6.0 and 6.5, but may function with older versions.
|
||||
80
vendor/github.com/vmware/govmomi/toolbox/backdoor.go
generated
vendored
Normal file
80
vendor/github.com/vmware/govmomi/toolbox/backdoor.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/vmware/vmw-guestinfo/message"
|
||||
"github.com/vmware/vmw-guestinfo/vmcheck"
|
||||
)
|
||||
|
||||
const (
|
||||
rpciProtocol uint32 = 0x49435052
|
||||
tcloProtocol uint32 = 0x4f4c4354
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotVirtualWorld = errors.New("not in a virtual world")
|
||||
)
|
||||
|
||||
type backdoorChannel struct {
|
||||
protocol uint32
|
||||
|
||||
*message.Channel
|
||||
}
|
||||
|
||||
func (b *backdoorChannel) Start() error {
|
||||
if !vmcheck.IsVirtualCPU() {
|
||||
return ErrNotVirtualWorld
|
||||
}
|
||||
|
||||
channel, err := message.NewChannel(b.protocol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Channel = channel
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backdoorChannel) Stop() error {
|
||||
if b.Channel == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := b.Channel.Close()
|
||||
|
||||
b.Channel = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewBackdoorChannelOut creates a Channel for use with the RPCI protocol
|
||||
func NewBackdoorChannelOut() Channel {
|
||||
return &backdoorChannel{
|
||||
protocol: rpciProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
// NewBackdoorChannelIn creates a Channel for use with the TCLO protocol
|
||||
func NewBackdoorChannelIn() Channel {
|
||||
return &backdoorChannel{
|
||||
protocol: tcloProtocol,
|
||||
}
|
||||
}
|
||||
50
vendor/github.com/vmware/govmomi/toolbox/backdoor_test.go
generated
vendored
Normal file
50
vendor/github.com/vmware/govmomi/toolbox/backdoor_test.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import "testing"
|
||||
|
||||
var _ Channel = new(backdoorChannel)
|
||||
|
||||
func TestBackdoorChannel(t *testing.T) {
|
||||
in := NewBackdoorChannelIn()
|
||||
out := NewBackdoorChannelOut()
|
||||
|
||||
funcs := []func() error{
|
||||
in.Start,
|
||||
out.Start,
|
||||
in.Stop,
|
||||
out.Stop,
|
||||
}
|
||||
|
||||
for _, f := range funcs {
|
||||
err := f()
|
||||
|
||||
if err != nil {
|
||||
if err == ErrNotVirtualWorld {
|
||||
t.SkipNow()
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// expect an error if we don't specify the protocol
|
||||
err := new(backdoorChannel).Start()
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
}
|
||||
58
vendor/github.com/vmware/govmomi/toolbox/channel.go
generated
vendored
Normal file
58
vendor/github.com/vmware/govmomi/toolbox/channel.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Channel abstracts the guest<->vmx RPC transport
|
||||
type Channel interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
Send([]byte) error
|
||||
Receive() ([]byte, error)
|
||||
}
|
||||
|
||||
var (
|
||||
rpciOK = []byte{'1', ' '}
|
||||
rpciERR = []byte{'0', ' '}
|
||||
)
|
||||
|
||||
// ChannelOut extends Channel to provide RPCI protocol helpers
|
||||
type ChannelOut struct {
|
||||
Channel
|
||||
}
|
||||
|
||||
// Request sends an RPC command to the vmx and checks the return code for success or error
|
||||
func (c *ChannelOut) Request(request []byte) ([]byte, error) {
|
||||
if err := c.Send(request); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply, err := c.Receive()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(reply, rpciOK) {
|
||||
return reply[2:], nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("request %q: %q", request, reply)
|
||||
}
|
||||
711
vendor/github.com/vmware/govmomi/toolbox/command.go
generated
vendored
Normal file
711
vendor/github.com/vmware/govmomi/toolbox/command.go
generated
vendored
Normal file
@@ -0,0 +1,711 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox/hgfs"
|
||||
"github.com/vmware/govmomi/toolbox/vix"
|
||||
)
|
||||
|
||||
type CommandHandler func(vix.CommandRequestHeader, []byte) ([]byte, error)
|
||||
|
||||
type CommandServer struct {
|
||||
Out *ChannelOut
|
||||
|
||||
ProcessManager *ProcessManager
|
||||
|
||||
Authenticate func(vix.CommandRequestHeader, []byte) error
|
||||
|
||||
ProcessStartCommand func(*ProcessManager, *vix.StartProgramRequest) (int64, error)
|
||||
|
||||
handlers map[uint32]CommandHandler
|
||||
|
||||
FileServer *hgfs.Server
|
||||
}
|
||||
|
||||
func registerCommandServer(service *Service) *CommandServer {
|
||||
server := &CommandServer{
|
||||
Out: service.out,
|
||||
ProcessManager: NewProcessManager(),
|
||||
}
|
||||
|
||||
server.handlers = map[uint32]CommandHandler{
|
||||
vix.CommandGetToolsState: server.GetToolsState,
|
||||
vix.CommandStartProgram: server.StartCommand,
|
||||
vix.CommandTerminateProcess: server.KillProcess,
|
||||
vix.CommandListProcessesEx: server.ListProcesses,
|
||||
vix.CommandReadEnvVariables: server.ReadEnvironmentVariables,
|
||||
vix.CommandCreateTemporaryFileEx: server.CreateTemporaryFile,
|
||||
vix.CommandCreateTemporaryDirectory: server.CreateTemporaryDirectory,
|
||||
vix.CommandDeleteGuestFileEx: server.DeleteFile,
|
||||
vix.CommandCreateDirectoryEx: server.CreateDirectory,
|
||||
vix.CommandDeleteGuestDirectoryEx: server.DeleteDirectory,
|
||||
vix.CommandMoveGuestFileEx: server.MoveFile,
|
||||
vix.CommandMoveGuestDirectory: server.MoveDirectory,
|
||||
vix.CommandListFiles: server.ListFiles,
|
||||
vix.CommandSetGuestFileAttributes: server.SetGuestFileAttributes,
|
||||
vix.CommandInitiateFileTransferFromGuest: server.InitiateFileTransferFromGuest,
|
||||
vix.CommandInitiateFileTransferToGuest: server.InitiateFileTransferToGuest,
|
||||
vix.HgfsSendPacketCommand: server.ProcessHgfsPacket,
|
||||
}
|
||||
|
||||
server.ProcessStartCommand = DefaultStartCommand
|
||||
|
||||
service.RegisterHandler("Vix_1_Relayed_Command", server.Dispatch)
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
func commandResult(header vix.CommandRequestHeader, rc int, err error, response []byte) []byte {
|
||||
// All Foundry tools commands return results that start with a foundry error
|
||||
// and a guest-OS-specific error (e.g. errno)
|
||||
errno := 0
|
||||
|
||||
if err != nil {
|
||||
// TODO: inspect err for system error, setting errno
|
||||
|
||||
response = []byte(err.Error())
|
||||
|
||||
log.Printf("[vix] op=%d error: %s", header.OpCode, err)
|
||||
}
|
||||
|
||||
buf := bytes.NewBufferString(fmt.Sprintf("%d %d ", rc, errno))
|
||||
|
||||
if header.CommonFlags&vix.CommandGuestReturnsBinary != 0 {
|
||||
// '#' delimits end of ascii and the start of the binary data (see ToolsDaemonTcloReceiveVixCommand)
|
||||
_ = buf.WriteByte('#')
|
||||
}
|
||||
|
||||
_, _ = buf.Write(response)
|
||||
|
||||
if header.CommonFlags&vix.CommandGuestReturnsBinary == 0 {
|
||||
// this is not binary data, so it should be a NULL terminated string (see ToolsDaemonTcloReceiveVixCommand)
|
||||
_ = buf.WriteByte(0)
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (c *CommandServer) Dispatch(data []byte) ([]byte, error) {
|
||||
// See ToolsDaemonTcloGetQuotedString
|
||||
if data[0] == '"' {
|
||||
data = data[1:]
|
||||
}
|
||||
|
||||
var name string
|
||||
|
||||
ix := bytes.IndexByte(data, '"')
|
||||
if ix > 0 {
|
||||
name = string(data[:ix])
|
||||
data = data[ix+1:]
|
||||
}
|
||||
// skip the NULL
|
||||
if data[0] == 0 {
|
||||
data = data[1:]
|
||||
}
|
||||
|
||||
if Trace {
|
||||
fmt.Fprintf(os.Stderr, "vix dispatch %q...\n%s\n", name, hex.Dump(data))
|
||||
}
|
||||
|
||||
var header vix.CommandRequestHeader
|
||||
buf := bytes.NewBuffer(data)
|
||||
err := binary.Read(buf, binary.LittleEndian, &header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if header.Magic != vix.CommandMagicWord {
|
||||
return commandResult(header, vix.InvalidMessageHeader, nil, nil), nil
|
||||
}
|
||||
|
||||
handler, ok := c.handlers[header.OpCode]
|
||||
if !ok {
|
||||
return commandResult(header, vix.UnrecognizedCommandInGuest, nil, nil), nil
|
||||
}
|
||||
|
||||
if header.OpCode != vix.CommandGetToolsState {
|
||||
// Every command expect GetToolsState requires authentication
|
||||
creds := buf.Bytes()[header.BodyLength:]
|
||||
|
||||
err = c.authenticate(header, creds[:header.CredentialLength])
|
||||
if err != nil {
|
||||
return commandResult(header, vix.AuthenticationFail, err, nil), nil
|
||||
}
|
||||
}
|
||||
|
||||
rc := vix.OK
|
||||
|
||||
response, err := handler(header, buf.Bytes())
|
||||
if err != nil {
|
||||
rc = vix.ErrorCode(err)
|
||||
}
|
||||
|
||||
return commandResult(header, rc, err, response), nil
|
||||
}
|
||||
|
||||
func (c *CommandServer) RegisterHandler(op uint32, handler CommandHandler) {
|
||||
c.handlers[op] = handler
|
||||
}
|
||||
|
||||
func (c *CommandServer) GetToolsState(_ vix.CommandRequestHeader, _ []byte) ([]byte, error) {
|
||||
hostname, _ := os.Hostname()
|
||||
osname := fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
// Note that vmtoolsd sends back 40 or so of these properties, sticking with the minimal set for now.
|
||||
props := vix.PropertyList{
|
||||
vix.NewStringProperty(vix.PropertyGuestOsVersion, osname),
|
||||
vix.NewStringProperty(vix.PropertyGuestOsVersionShort, osname),
|
||||
vix.NewStringProperty(vix.PropertyGuestToolsProductNam, "VMware Tools (Go)"),
|
||||
vix.NewStringProperty(vix.PropertyGuestToolsVersion, "10.0.5 build-3227872 (Compatible)"),
|
||||
vix.NewStringProperty(vix.PropertyGuestName, hostname),
|
||||
vix.NewInt32Property(vix.PropertyGuestToolsAPIOptions, 0x0001), // TODO: const VIX_TOOLSFEATURE_SUPPORT_GET_HANDLE_STATE
|
||||
vix.NewInt32Property(vix.PropertyGuestOsFamily, 1), // TODO: const GUEST_OS_FAMILY_*
|
||||
vix.NewBoolProperty(vix.PropertyGuestStartProgramEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestTerminateProcessEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestListProcessesEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestReadEnvironmentVariableEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestMakeDirectoryEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestDeleteFileEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestDeleteDirectoryEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestMoveDirectoryEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestMoveFileEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestCreateTempFileEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestCreateTempDirectoryEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestListFilesEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestChangeFileAttributesEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestInitiateFileTransferFromGuestEnabled, true),
|
||||
vix.NewBoolProperty(vix.PropertyGuestInitiateFileTransferToGuestEnabled, true),
|
||||
}
|
||||
|
||||
src, _ := props.MarshalBinary()
|
||||
enc := base64.StdEncoding
|
||||
buf := make([]byte, enc.EncodedLen(len(src)))
|
||||
enc.Encode(buf, src)
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (c *CommandServer) StartCommand(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.StartProgramRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pid, err := c.ProcessStartCommand(c.ProcessManager, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append([]byte(fmt.Sprintf("%d", pid)), 0), nil
|
||||
}
|
||||
|
||||
func DefaultStartCommand(m *ProcessManager, r *vix.StartProgramRequest) (int64, error) {
|
||||
p := NewProcess()
|
||||
|
||||
switch r.ProgramPath {
|
||||
case "http.RoundTrip":
|
||||
p = NewProcessRoundTrip()
|
||||
default:
|
||||
// Standard vmware-tools requires an absolute path,
|
||||
// we'll enable IO redirection by default without an absolute path.
|
||||
if !strings.Contains(r.ProgramPath, "/") {
|
||||
p = p.WithIO()
|
||||
}
|
||||
}
|
||||
|
||||
return m.Start(r, p)
|
||||
}
|
||||
|
||||
func (c *CommandServer) KillProcess(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.KillProcessRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.ProcessManager.Kill(r.Body.Pid) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: could kill process started outside of toolbox
|
||||
|
||||
return nil, vix.Error(vix.NoSuchProcess)
|
||||
}
|
||||
|
||||
func (c *CommandServer) ListProcesses(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.ListProcessesRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
state := c.ProcessManager.ListProcesses(r.Pids)
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (c *CommandServer) ReadEnvironmentVariables(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.ReadEnvironmentVariablesRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if len(r.Names) == 0 {
|
||||
for _, e := range os.Environ() {
|
||||
_, _ = buf.WriteString(fmt.Sprintf("<ev>%s</ev>", xmlEscape.Replace(e)))
|
||||
}
|
||||
} else {
|
||||
for _, key := range r.Names {
|
||||
val := os.Getenv(key)
|
||||
if val == "" {
|
||||
continue
|
||||
}
|
||||
_, _ = buf.WriteString(fmt.Sprintf("<ev>%s=%s</ev>", xmlEscape.Replace(key), xmlEscape.Replace(val)))
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *CommandServer) CreateTemporaryFile(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.CreateTempFileRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := ioutil.TempFile(r.DirectoryPath, r.FilePrefix+"vmware")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = f.Close()
|
||||
|
||||
return []byte(f.Name()), nil
|
||||
}
|
||||
|
||||
func (c *CommandServer) CreateTemporaryDirectory(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.CreateTempFileRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name, err := ioutil.TempDir(r.DirectoryPath, r.FilePrefix+"vmware")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(name), nil
|
||||
}
|
||||
|
||||
func (c *CommandServer) DeleteFile(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.FileRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := os.Stat(r.GuestPathName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil, vix.Error(vix.NotAFile)
|
||||
}
|
||||
|
||||
err = os.Remove(r.GuestPathName)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (c *CommandServer) DeleteDirectory(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.DirRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := os.Stat(r.GuestPathName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return nil, vix.Error(vix.NotADirectory)
|
||||
}
|
||||
|
||||
if r.Body.Recursive {
|
||||
err = os.RemoveAll(r.GuestPathName)
|
||||
} else {
|
||||
err = os.Remove(r.GuestPathName)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (c *CommandServer) CreateDirectory(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.DirRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mkdir := os.Mkdir
|
||||
|
||||
if r.Body.Recursive {
|
||||
mkdir = os.MkdirAll
|
||||
}
|
||||
|
||||
err = mkdir(r.GuestPathName, 0700)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (c *CommandServer) MoveDirectory(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.RenameFileRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := os.Stat(r.OldPathName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return nil, vix.Error(vix.NotADirectory)
|
||||
}
|
||||
|
||||
if !r.Body.Overwrite {
|
||||
info, err = os.Stat(r.NewPathName)
|
||||
if err == nil {
|
||||
return nil, vix.Error(vix.FileAlreadyExists)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, os.Rename(r.OldPathName, r.NewPathName)
|
||||
}
|
||||
|
||||
func (c *CommandServer) MoveFile(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.RenameFileRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := os.Stat(r.OldPathName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil, vix.Error(vix.NotAFile)
|
||||
}
|
||||
|
||||
if !r.Body.Overwrite {
|
||||
info, err = os.Stat(r.NewPathName)
|
||||
if err == nil {
|
||||
return nil, vix.Error(vix.FileAlreadyExists)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, os.Rename(r.OldPathName, r.NewPathName)
|
||||
}
|
||||
|
||||
func (c *CommandServer) ListFiles(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.ListFilesRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := os.Lstat(r.GuestPathName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dir string
|
||||
var files []os.FileInfo
|
||||
|
||||
if info.IsDir() {
|
||||
dir = r.GuestPathName
|
||||
files, err = ioutil.ReadDir(r.GuestPathName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
dir = filepath.Dir(r.GuestPathName)
|
||||
files = append(files, info)
|
||||
}
|
||||
|
||||
offset := r.Body.Offset + uint64(r.Body.Index)
|
||||
total := uint64(len(files)) - offset
|
||||
if int(offset) < len(files) {
|
||||
files = files[offset:]
|
||||
} else {
|
||||
total = 0 // offset is not valid (open-vm-tools behaves the same in this case)
|
||||
}
|
||||
|
||||
var remaining uint64
|
||||
|
||||
if r.Body.MaxResults > 0 && total > uint64(r.Body.MaxResults) {
|
||||
remaining = total - uint64(r.Body.MaxResults)
|
||||
files = files[:r.Body.MaxResults]
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString(fmt.Sprintf("<rem>%d</rem>", remaining))
|
||||
|
||||
for _, info = range files {
|
||||
buf.WriteString(fileExtendedInfoFormat(dir, info))
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func chtimes(r *vix.SetGuestFileAttributesRequest) error {
|
||||
var mtime, atime *time.Time
|
||||
|
||||
if r.IsSet(vix.FileAttributeSetModifyDate) {
|
||||
t := time.Unix(r.Body.ModificationTime, 0)
|
||||
mtime = &t
|
||||
}
|
||||
|
||||
if r.IsSet(vix.FileAttributeSetAccessDate) {
|
||||
t := time.Unix(r.Body.AccessTime, 0)
|
||||
atime = &t
|
||||
}
|
||||
|
||||
if mtime == nil && atime == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
info, err := os.Stat(r.GuestPathName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if mtime == nil {
|
||||
t := info.ModTime()
|
||||
mtime = &t
|
||||
}
|
||||
|
||||
if atime == nil {
|
||||
t := info.ModTime()
|
||||
atime = &t
|
||||
}
|
||||
|
||||
return os.Chtimes(r.GuestPathName, *atime, *mtime)
|
||||
}
|
||||
|
||||
func chown(r *vix.SetGuestFileAttributesRequest) error {
|
||||
uid := -1
|
||||
gid := -1
|
||||
|
||||
if r.IsSet(vix.FileAttributeSetUnixOwnerid) {
|
||||
uid = int(r.Body.OwnerID)
|
||||
}
|
||||
|
||||
if r.IsSet(vix.FileAttributeSetUnixGroupid) {
|
||||
gid = int(r.Body.GroupID)
|
||||
}
|
||||
|
||||
if uid == -1 && gid == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return os.Chown(r.GuestPathName, uid, gid)
|
||||
}
|
||||
|
||||
func chmod(r *vix.SetGuestFileAttributesRequest) error {
|
||||
if r.IsSet(vix.FileAttributeSetUnixPermissions) {
|
||||
return os.Chmod(r.GuestPathName, os.FileMode(r.Body.Permissions).Perm())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CommandServer) SetGuestFileAttributes(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.SetGuestFileAttributesRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, set := range []func(*vix.SetGuestFileAttributesRequest) error{chtimes, chown, chmod} {
|
||||
err = set(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *CommandServer) InitiateFileTransferFromGuest(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.ListFilesRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := c.FileServer.Stat(r.GuestPathName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
return nil, vix.Error(vix.InvalidArg)
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil, vix.Error(vix.NotAFile)
|
||||
}
|
||||
|
||||
return []byte(fileExtendedInfoFormat("", info)), nil
|
||||
}
|
||||
|
||||
func (c *CommandServer) InitiateFileTransferToGuest(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.InitiateFileTransferToGuestRequest{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := c.FileServer.Stat(r.GuestPathName)
|
||||
if err == nil {
|
||||
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
return nil, vix.Error(vix.InvalidArg)
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil, vix.Error(vix.NotAFile)
|
||||
}
|
||||
|
||||
if !r.Body.Overwrite {
|
||||
return nil, vix.Error(vix.FileAlreadyExists)
|
||||
}
|
||||
} else {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *CommandServer) ProcessHgfsPacket(header vix.CommandRequestHeader, data []byte) ([]byte, error) {
|
||||
r := &vix.CommandHgfsSendPacket{
|
||||
CommandRequestHeader: header,
|
||||
}
|
||||
|
||||
err := r.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.FileServer.Dispatch(r.Packet)
|
||||
}
|
||||
|
||||
func (c *CommandServer) authenticate(r vix.CommandRequestHeader, data []byte) error {
|
||||
if c.Authenticate != nil {
|
||||
return c.Authenticate(r, data)
|
||||
}
|
||||
|
||||
switch r.UserCredentialType {
|
||||
case vix.UserCredentialTypeNamePassword:
|
||||
var c vix.UserCredentialNamePassword
|
||||
|
||||
if err := c.UnmarshalBinary(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if Trace {
|
||||
fmt.Fprintf(traceLog, "ignoring credentials: %q:%q\n", c.Name, c.Password)
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unsupported UserCredentialType=%d", r.UserCredentialType)
|
||||
}
|
||||
}
|
||||
972
vendor/github.com/vmware/govmomi/toolbox/command_test.go
generated
vendored
Normal file
972
vendor/github.com/vmware/govmomi/toolbox/command_test.go
generated
vendored
Normal file
@@ -0,0 +1,972 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox/hgfs"
|
||||
"github.com/vmware/govmomi/toolbox/vix"
|
||||
)
|
||||
|
||||
type CommandClient struct {
|
||||
Service *Service
|
||||
Header *vix.CommandRequestHeader
|
||||
creds []byte
|
||||
}
|
||||
|
||||
func NewCommandClient() *CommandClient {
|
||||
Trace = testing.Verbose()
|
||||
hgfs.Trace = Trace
|
||||
|
||||
creds, _ := (&vix.UserCredentialNamePassword{
|
||||
Name: "user",
|
||||
Password: "pass",
|
||||
}).MarshalBinary()
|
||||
|
||||
header := new(vix.CommandRequestHeader)
|
||||
header.Magic = vix.CommandMagicWord
|
||||
|
||||
header.UserCredentialType = vix.UserCredentialTypeNamePassword
|
||||
header.CredentialLength = uint32(len(creds))
|
||||
|
||||
in := new(mockChannelIn)
|
||||
out := new(mockChannelOut)
|
||||
|
||||
return &CommandClient{
|
||||
creds: creds,
|
||||
Header: header,
|
||||
Service: NewService(in, out),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) Request(op uint32, m encoding.BinaryMarshaler) []byte {
|
||||
b, err := m.MarshalBinary()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Header.OpCode = op
|
||||
c.Header.BodyLength = uint32(len(b))
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.Write([]byte("\"reqname\"\x00"))
|
||||
_ = binary.Write(&buf, binary.LittleEndian, c.Header)
|
||||
|
||||
_, _ = buf.Write(b)
|
||||
|
||||
data := append(buf.Bytes(), c.creds...)
|
||||
reply, err := c.Service.Command.Dispatch(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return reply
|
||||
}
|
||||
|
||||
func vixRC(buf []byte) int {
|
||||
args := bytes.SplitN(buf, []byte{' '}, 2)
|
||||
rc, err := strconv.Atoi(string(args[0]))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return rc
|
||||
}
|
||||
|
||||
func TestVixRelayedCommandHandler(t *testing.T) {
|
||||
Trace = true
|
||||
if !testing.Verbose() {
|
||||
// cover Trace paths but discard output
|
||||
traceLog = ioutil.Discard
|
||||
}
|
||||
|
||||
in := new(mockChannelIn)
|
||||
out := new(mockChannelOut)
|
||||
|
||||
service := NewService(in, out)
|
||||
|
||||
cmd := service.Command
|
||||
|
||||
msg := []byte("\"reqname\"\x00")
|
||||
|
||||
_, err := cmd.Dispatch(msg) // io.EOF
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
header := new(vix.CommandRequestHeader)
|
||||
|
||||
marshal := func(m ...encoding.BinaryMarshaler) []byte {
|
||||
var buf bytes.Buffer
|
||||
_, _ = buf.Write(msg)
|
||||
_ = binary.Write(&buf, binary.LittleEndian, header)
|
||||
|
||||
for _, e := range m {
|
||||
b, err := e.MarshalBinary()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, _ = buf.Write(b)
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// header.Magic not set
|
||||
reply, _ := cmd.Dispatch(marshal())
|
||||
rc := vixRC(reply)
|
||||
if rc != vix.InvalidMessageHeader {
|
||||
t.Fatalf("%q", reply)
|
||||
}
|
||||
|
||||
// header.OpCode not set
|
||||
header.Magic = vix.CommandMagicWord
|
||||
reply, _ = cmd.Dispatch(marshal())
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.UnrecognizedCommandInGuest {
|
||||
t.Fatalf("%q", reply)
|
||||
}
|
||||
|
||||
// valid request for GetToolsState
|
||||
header.OpCode = vix.CommandGetToolsState
|
||||
reply, _ = cmd.Dispatch(marshal())
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("%q", reply)
|
||||
}
|
||||
|
||||
// header.UserCredentialType not set
|
||||
header.OpCode = vix.CommandStartProgram
|
||||
request := new(vix.StartProgramRequest)
|
||||
buf := marshal(request)
|
||||
reply, _ = cmd.Dispatch(marshal())
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.AuthenticationFail {
|
||||
t.Fatalf("%q", reply)
|
||||
}
|
||||
|
||||
creds, _ := (&vix.UserCredentialNamePassword{
|
||||
Name: "user",
|
||||
Password: "pass",
|
||||
}).MarshalBinary()
|
||||
|
||||
header.BodyLength = uint32(binary.Size(request.Body))
|
||||
header.UserCredentialType = vix.UserCredentialTypeNamePassword
|
||||
header.CredentialLength = uint32(len(creds))
|
||||
|
||||
// ProgramPath not set
|
||||
buf = append(marshal(request), creds...)
|
||||
reply, _ = cmd.Dispatch(buf)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.FileNotFound {
|
||||
t.Fatalf("%q", reply)
|
||||
}
|
||||
|
||||
cmd.ProcessStartCommand = func(pm *ProcessManager, r *vix.StartProgramRequest) (int64, error) {
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// valid request for StartProgram
|
||||
buf = append(marshal(request), creds...)
|
||||
reply, _ = cmd.Dispatch(buf)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("%q", reply)
|
||||
}
|
||||
|
||||
cmd.Authenticate = func(_ vix.CommandRequestHeader, data []byte) error {
|
||||
var c vix.UserCredentialNamePassword
|
||||
if err := c.UnmarshalBinary(data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return errors.New("you shall not pass")
|
||||
}
|
||||
|
||||
// fail auth with our own handler
|
||||
buf = append(marshal(request), creds...)
|
||||
reply, _ = cmd.Dispatch(buf)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.AuthenticationFail {
|
||||
t.Fatalf("%q", reply)
|
||||
}
|
||||
|
||||
cmd.Authenticate = nil
|
||||
|
||||
// cause Vix.UserCredentialNamePassword.UnmarshalBinary to error
|
||||
// first by EOF reading header, second in base64 decode
|
||||
for _, l := range []uint32{1, 10} {
|
||||
header.CredentialLength = l
|
||||
buf = append(marshal(request), creds...)
|
||||
reply, _ = cmd.Dispatch(buf)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.AuthenticationFail {
|
||||
t.Fatalf("%q", reply)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cover misc error paths
|
||||
func TestVixCommandErrors(t *testing.T) {
|
||||
r := new(vix.StartProgramRequest)
|
||||
err := r.UnmarshalBinary(nil)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
|
||||
r.Body.NumEnvVars = 1
|
||||
buf, _ := r.MarshalBinary()
|
||||
err = r.UnmarshalBinary(buf)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
|
||||
c := new(CommandServer)
|
||||
_, err = c.StartCommand(r.CommandRequestHeader, nil)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVixInitiateDirTransfer(t *testing.T) {
|
||||
c := NewCommandClient()
|
||||
|
||||
dir := os.TempDir()
|
||||
|
||||
for _, enable := range []bool{true, false} {
|
||||
expect := vix.NotAFile
|
||||
if enable {
|
||||
expect = vix.OK
|
||||
} else {
|
||||
// validate we behave as open-vm-tools does when the directory archive feature is disabled
|
||||
c.Service.Command.FileServer.RegisterFileHandler(hgfs.ArchiveScheme, nil)
|
||||
}
|
||||
|
||||
fromGuest := &vix.ListFilesRequest{GuestPathName: dir}
|
||||
toGuest := &vix.InitiateFileTransferToGuestRequest{GuestPathName: dir}
|
||||
toGuest.Body.Overwrite = true
|
||||
|
||||
tests := []struct {
|
||||
op uint32
|
||||
request encoding.BinaryMarshaler
|
||||
}{
|
||||
{vix.CommandInitiateFileTransferFromGuest, fromGuest},
|
||||
{vix.CommandInitiateFileTransferToGuest, toGuest},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
reply := c.Request(test.op, test.request)
|
||||
|
||||
rc := vixRC(reply)
|
||||
|
||||
if rc != expect {
|
||||
t.Errorf("rc=%d", rc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVixInitiateFileTransfer(t *testing.T) {
|
||||
c := NewCommandClient()
|
||||
|
||||
request := new(vix.ListFilesRequest)
|
||||
|
||||
f, err := ioutil.TempFile("", "toolbox")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, s := range []string{"a", "b", "c", "d", "e"} {
|
||||
_, _ = f.WriteString(strings.Repeat(s, 40))
|
||||
}
|
||||
|
||||
_ = f.Close()
|
||||
|
||||
name := f.Name()
|
||||
|
||||
// 1st pass file exists == OK, 2nd pass does not exist == FAIL
|
||||
for _, fail := range []bool{false, true} {
|
||||
request.GuestPathName = name
|
||||
|
||||
reply := c.Request(vix.CommandInitiateFileTransferFromGuest, request)
|
||||
|
||||
rc := vixRC(reply)
|
||||
|
||||
if Trace {
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", name, string(reply))
|
||||
}
|
||||
|
||||
if fail {
|
||||
if rc == vix.OK {
|
||||
t.Errorf("%s: %d", name, rc)
|
||||
}
|
||||
} else {
|
||||
if rc != vix.OK {
|
||||
t.Errorf("%s: %d", name, rc)
|
||||
}
|
||||
|
||||
err = os.Remove(name)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVixInitiateFileTransferWrite(t *testing.T) {
|
||||
c := NewCommandClient()
|
||||
|
||||
request := new(vix.InitiateFileTransferToGuestRequest)
|
||||
|
||||
f, err := ioutil.TempFile("", "toolbox")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_ = f.Close()
|
||||
|
||||
name := f.Name()
|
||||
|
||||
tests := []struct {
|
||||
force bool
|
||||
fail bool
|
||||
}{
|
||||
{false, true}, // exists == FAIL
|
||||
{true, false}, // exists, but overwrite == OK
|
||||
{false, false}, // does not exist == OK
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
request.GuestPathName = name
|
||||
request.Body.Overwrite = test.force
|
||||
|
||||
reply := c.Request(vix.CommandInitiateFileTransferToGuest, request)
|
||||
|
||||
rc := vixRC(reply)
|
||||
|
||||
if Trace {
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", name, string(reply))
|
||||
}
|
||||
|
||||
if test.fail {
|
||||
if rc == vix.OK {
|
||||
t.Errorf("%d: %d", i, rc)
|
||||
}
|
||||
} else {
|
||||
if rc != vix.OK {
|
||||
t.Errorf("%d: %d", i, rc)
|
||||
}
|
||||
if test.force {
|
||||
_ = os.Remove(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVixProcessHgfsPacket(t *testing.T) {
|
||||
c := NewCommandClient()
|
||||
|
||||
c.Header.CommonFlags = vix.CommandGuestReturnsBinary
|
||||
|
||||
request := new(vix.CommandHgfsSendPacket)
|
||||
|
||||
op := new(hgfs.RequestCreateSessionV4)
|
||||
packet := new(hgfs.Packet)
|
||||
packet.Payload, _ = op.MarshalBinary()
|
||||
packet.Header.Version = hgfs.HeaderVersion
|
||||
packet.Header.Dummy = hgfs.OpNewHeader
|
||||
packet.Header.HeaderSize = uint32(binary.Size(&packet.Header))
|
||||
packet.Header.PacketSize = packet.Header.HeaderSize + uint32(len(packet.Payload))
|
||||
packet.Header.Op = hgfs.OpCreateSessionV4
|
||||
|
||||
request.Packet, _ = packet.MarshalBinary()
|
||||
request.Body.PacketSize = uint32(len(request.Packet))
|
||||
|
||||
reply := c.Request(vix.HgfsSendPacketCommand, request)
|
||||
|
||||
rc := vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
ix := bytes.IndexByte(reply, '#')
|
||||
reply = reply[ix+1:]
|
||||
err := packet.UnmarshalBinary(reply)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if packet.Status != hgfs.StatusSuccess {
|
||||
t.Errorf("status=%d", packet.Status)
|
||||
}
|
||||
|
||||
if packet.Dummy != hgfs.OpNewHeader {
|
||||
t.Errorf("dummy=%d", packet.Dummy)
|
||||
}
|
||||
|
||||
session := new(hgfs.ReplyCreateSessionV4)
|
||||
err = session.UnmarshalBinary(packet.Payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if session.NumCapabilities == 0 || int(session.NumCapabilities) != len(session.Capabilities) {
|
||||
t.Errorf("NumCapabilities=%d", session.NumCapabilities)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVixListProcessesEx(t *testing.T) {
|
||||
c := NewCommandClient()
|
||||
pm := c.Service.Command.ProcessManager
|
||||
|
||||
c.Service.Command.ProcessStartCommand = func(pm *ProcessManager, r *vix.StartProgramRequest) (int64, error) {
|
||||
var p *Process
|
||||
switch r.ProgramPath {
|
||||
case "foo":
|
||||
p = NewProcessFunc(func(ctx context.Context, arg string) error {
|
||||
return nil
|
||||
})
|
||||
default:
|
||||
return -1, os.ErrNotExist
|
||||
}
|
||||
|
||||
return pm.Start(r, p)
|
||||
}
|
||||
|
||||
exec := &vix.StartProgramRequest{
|
||||
ProgramPath: "foo",
|
||||
}
|
||||
|
||||
reply := c.Request(vix.CommandStartProgram, exec)
|
||||
rc := vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
r := bytes.Trim(bytes.Split(reply, []byte{' '})[2], "\x00")
|
||||
pid, _ := strconv.Atoi(string(r))
|
||||
|
||||
exec.ProgramPath = "bar"
|
||||
reply = c.Request(vix.CommandStartProgram, exec)
|
||||
rc = vixRC(reply)
|
||||
t.Log(vix.Error(rc).Error())
|
||||
if rc != vix.FileNotFound {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
if vix.ErrorCode(os.ErrNotExist) != rc {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
pm.wg.Wait()
|
||||
|
||||
ps := new(vix.ListProcessesRequest)
|
||||
|
||||
ps.Pids = []int64{int64(pid)}
|
||||
|
||||
reply = c.Request(vix.CommandListProcessesEx, ps)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
n := bytes.Count(reply, []byte("<proc>"))
|
||||
if n != len(ps.Pids) {
|
||||
t.Errorf("ps -p %d=%d", pid, n)
|
||||
}
|
||||
|
||||
kill := new(vix.KillProcessRequest)
|
||||
kill.Body.Pid = ps.Pids[0]
|
||||
|
||||
reply = c.Request(vix.CommandTerminateProcess, kill)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
kill.Body.Pid = 33333
|
||||
reply = c.Request(vix.CommandTerminateProcess, kill)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.NoSuchProcess {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVixGetenv(t *testing.T) {
|
||||
c := NewCommandClient()
|
||||
|
||||
env := os.Environ()
|
||||
key := strings.SplitN(env[0], "=", 2)[0]
|
||||
|
||||
tests := []struct {
|
||||
names []string
|
||||
expect int
|
||||
}{
|
||||
{nil, len(env)}, // all env
|
||||
{[]string{key, "ENOENT"}, 1}, // specific vars, 1 exists 1 does not
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
env := &vix.ReadEnvironmentVariablesRequest{
|
||||
Names: test.names,
|
||||
}
|
||||
reply := c.Request(vix.CommandReadEnvVariables, env)
|
||||
rc := vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("%d) rc: %d", i, rc)
|
||||
}
|
||||
|
||||
num := bytes.Count(reply, []byte("<ev>"))
|
||||
if num != test.expect {
|
||||
t.Errorf("%d) getenv(%v): %d", i, test.names, num)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVixDirectories(t *testing.T) {
|
||||
c := NewCommandClient()
|
||||
|
||||
mktemp := &vix.CreateTempFileRequest{
|
||||
FilePrefix: "toolbox-",
|
||||
}
|
||||
|
||||
// mktemp -d
|
||||
reply := c.Request(vix.CommandCreateTemporaryDirectory, mktemp)
|
||||
rc := vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
dir := strings.TrimSuffix(string(reply[4:]), "\x00")
|
||||
|
||||
mkdir := &vix.DirRequest{
|
||||
GuestPathName: dir,
|
||||
}
|
||||
|
||||
// mkdir $dir == EEXIST
|
||||
reply = c.Request(vix.CommandCreateDirectoryEx, mkdir)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.FileAlreadyExists {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
// mkdir $dir/ok == OK
|
||||
mkdir.GuestPathName = dir + "/ok"
|
||||
reply = c.Request(vix.CommandCreateDirectoryEx, mkdir)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
// rm of a dir should fail, regardless if empty or not
|
||||
reply = c.Request(vix.CommandDeleteGuestFileEx, &vix.FileRequest{
|
||||
GuestPathName: mkdir.GuestPathName,
|
||||
})
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.NotAFile {
|
||||
t.Errorf("rc: %d", rc)
|
||||
}
|
||||
|
||||
// rmdir $dir/ok == OK
|
||||
reply = c.Request(vix.CommandDeleteGuestDirectoryEx, mkdir)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
// rmdir $dir/ok == ENOENT
|
||||
reply = c.Request(vix.CommandDeleteGuestDirectoryEx, mkdir)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.FileNotFound {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
// mkdir $dir/1/2 == ENOENT (parent directory does not exist)
|
||||
mkdir.GuestPathName = dir + "/1/2"
|
||||
reply = c.Request(vix.CommandCreateDirectoryEx, mkdir)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.FileNotFound {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
// mkdir -p $dir/1/2 == OK
|
||||
mkdir.Body.Recursive = true
|
||||
reply = c.Request(vix.CommandCreateDirectoryEx, mkdir)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
// rmdir $dir == ENOTEMPTY
|
||||
mkdir.GuestPathName = dir
|
||||
mkdir.Body.Recursive = false
|
||||
reply = c.Request(vix.CommandDeleteGuestDirectoryEx, mkdir)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.DirectoryNotEmpty {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
// rm -rf $dir == OK
|
||||
mkdir.Body.Recursive = true
|
||||
reply = c.Request(vix.CommandDeleteGuestDirectoryEx, mkdir)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVixFiles(t *testing.T) {
|
||||
c := NewCommandClient()
|
||||
|
||||
mktemp := &vix.CreateTempFileRequest{
|
||||
FilePrefix: "toolbox-",
|
||||
}
|
||||
|
||||
// mktemp -d
|
||||
reply := c.Request(vix.CommandCreateTemporaryDirectory, mktemp)
|
||||
rc := vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
dir := strings.TrimSuffix(string(reply[4:]), "\x00")
|
||||
|
||||
max := 12
|
||||
var total int
|
||||
|
||||
// mktemp
|
||||
for i := 0; i <= max; i++ {
|
||||
mktemp = &vix.CreateTempFileRequest{
|
||||
DirectoryPath: dir,
|
||||
}
|
||||
|
||||
reply = c.Request(vix.CommandCreateTemporaryFileEx, mktemp)
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
}
|
||||
|
||||
// name of the last file temp file we created, we'll mess around with it then delete it
|
||||
name := strings.TrimSuffix(string(reply[4:]), "\x00")
|
||||
// for testing symlinks
|
||||
link := filepath.Join(dir, "a-link")
|
||||
err := os.Symlink(name, link)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, fpath := range []string{name, link} {
|
||||
// test ls of a single file
|
||||
ls := &vix.ListFilesRequest{
|
||||
GuestPathName: fpath,
|
||||
}
|
||||
|
||||
reply = c.Request(vix.CommandListFiles, ls)
|
||||
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
|
||||
num := bytes.Count(reply, []byte("<fxi>"))
|
||||
if num != 1 {
|
||||
t.Errorf("ls %s: %d", name, num)
|
||||
}
|
||||
|
||||
num = bytes.Count(reply, []byte("<rem>0</rem>"))
|
||||
if num != 1 {
|
||||
t.Errorf("ls %s: %d", name, num)
|
||||
}
|
||||
|
||||
ft := 0
|
||||
target := ""
|
||||
if fpath == link {
|
||||
target = name
|
||||
ft = vix.FileAttributesSymlink
|
||||
}
|
||||
|
||||
num = bytes.Count(reply, []byte(fmt.Sprintf("<slt>%s</slt>", target)))
|
||||
if num != 1 {
|
||||
t.Errorf("ls %s: %d", name, num)
|
||||
}
|
||||
|
||||
num = bytes.Count(reply, []byte(fmt.Sprintf("<ft>%d</ft>", ft)))
|
||||
if num != 1 {
|
||||
t.Errorf("ls %s: %d", name, num)
|
||||
}
|
||||
}
|
||||
|
||||
mv := &vix.RenameFileRequest{
|
||||
OldPathName: name,
|
||||
NewPathName: name + "-new",
|
||||
}
|
||||
|
||||
for _, expect := range []int{vix.OK, vix.FileNotFound} {
|
||||
reply = c.Request(vix.CommandMoveGuestFileEx, mv)
|
||||
rc = vixRC(reply)
|
||||
if rc != expect {
|
||||
t.Errorf("rc: %d", rc)
|
||||
}
|
||||
|
||||
if expect == vix.OK {
|
||||
// test file type is properly checked
|
||||
reply = c.Request(vix.CommandMoveGuestDirectory, &vix.RenameFileRequest{
|
||||
OldPathName: mv.NewPathName,
|
||||
NewPathName: name,
|
||||
})
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.NotADirectory {
|
||||
t.Errorf("rc: %d", rc)
|
||||
}
|
||||
|
||||
// test Overwrite flag is properly checked
|
||||
reply = c.Request(vix.CommandMoveGuestFileEx, &vix.RenameFileRequest{
|
||||
OldPathName: mv.NewPathName,
|
||||
NewPathName: mv.NewPathName,
|
||||
})
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.FileAlreadyExists {
|
||||
t.Errorf("rc: %d", rc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rmdir of a file should fail
|
||||
reply = c.Request(vix.CommandDeleteGuestDirectoryEx, &vix.DirRequest{
|
||||
GuestPathName: mv.NewPathName,
|
||||
})
|
||||
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.NotADirectory {
|
||||
t.Errorf("rc: %d", rc)
|
||||
}
|
||||
|
||||
file := &vix.FileRequest{
|
||||
GuestPathName: mv.NewPathName,
|
||||
}
|
||||
|
||||
for _, expect := range []int{vix.OK, vix.FileNotFound} {
|
||||
reply = c.Request(vix.CommandDeleteGuestFileEx, file)
|
||||
rc = vixRC(reply)
|
||||
if rc != expect {
|
||||
t.Errorf("rc: %d", rc)
|
||||
}
|
||||
}
|
||||
|
||||
// ls again now that file is gone
|
||||
reply = c.Request(vix.CommandListFiles, &vix.ListFilesRequest{
|
||||
GuestPathName: name,
|
||||
})
|
||||
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.FileNotFound {
|
||||
t.Errorf("rc: %d", rc)
|
||||
}
|
||||
|
||||
// ls
|
||||
ls := &vix.ListFilesRequest{
|
||||
GuestPathName: dir,
|
||||
}
|
||||
ls.Body.MaxResults = 5 // default is 50
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
reply = c.Request(vix.CommandListFiles, ls)
|
||||
|
||||
if Trace {
|
||||
fmt.Fprintf(os.Stderr, "%s: %q\n", dir, string(reply[4:]))
|
||||
}
|
||||
|
||||
var rem int
|
||||
_, err := fmt.Fscanf(bytes.NewReader(reply[4:]), "<rem>%d</rem>", &rem)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
num := bytes.Count(reply, []byte("<fxi>"))
|
||||
total += num
|
||||
ls.Body.Offset += uint64(num)
|
||||
|
||||
if rem == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if total != max+1 {
|
||||
t.Errorf("expected %d, got %d", max, total)
|
||||
}
|
||||
|
||||
// Test invalid offset, making sure it doesn't cause panic (issue #934)
|
||||
ls.Body.Offset += 10
|
||||
_ = c.Request(vix.CommandListFiles, ls)
|
||||
|
||||
// mv $dir ${dir}-old
|
||||
mv = &vix.RenameFileRequest{
|
||||
OldPathName: dir,
|
||||
NewPathName: dir + "-old",
|
||||
}
|
||||
|
||||
for _, expect := range []int{vix.OK, vix.FileNotFound} {
|
||||
reply = c.Request(vix.CommandMoveGuestDirectory, mv)
|
||||
rc = vixRC(reply)
|
||||
if rc != expect {
|
||||
t.Errorf("rc: %d", rc)
|
||||
}
|
||||
|
||||
if expect == vix.OK {
|
||||
// test file type is properly checked
|
||||
reply = c.Request(vix.CommandMoveGuestFileEx, &vix.RenameFileRequest{
|
||||
OldPathName: mv.NewPathName,
|
||||
NewPathName: dir,
|
||||
})
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.NotAFile {
|
||||
t.Errorf("rc: %d", rc)
|
||||
}
|
||||
|
||||
// test Overwrite flag is properly checked
|
||||
reply = c.Request(vix.CommandMoveGuestDirectory, &vix.RenameFileRequest{
|
||||
OldPathName: mv.NewPathName,
|
||||
NewPathName: mv.NewPathName,
|
||||
})
|
||||
rc = vixRC(reply)
|
||||
if rc != vix.FileAlreadyExists {
|
||||
t.Errorf("rc: %d", rc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rmdir := &vix.DirRequest{
|
||||
GuestPathName: mv.NewPathName,
|
||||
}
|
||||
|
||||
// rm -rm $dir
|
||||
for _, rmr := range []bool{false, true} {
|
||||
rmdir.Body.Recursive = rmr
|
||||
|
||||
reply = c.Request(vix.CommandDeleteGuestDirectoryEx, rmdir)
|
||||
rc = vixRC(reply)
|
||||
if rmr {
|
||||
if rc != vix.OK {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
} else {
|
||||
if rc != vix.DirectoryNotEmpty {
|
||||
t.Fatalf("rc: %d", rc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVixFileChangeAttributes(t *testing.T) {
|
||||
if os.Getuid() == 0 {
|
||||
t.Skip("running as root")
|
||||
}
|
||||
|
||||
c := NewCommandClient()
|
||||
|
||||
f, err := ioutil.TempFile("", "toolbox-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = f.Close()
|
||||
name := f.Name()
|
||||
|
||||
// touch,chown,chmod
|
||||
chattr := &vix.SetGuestFileAttributesRequest{
|
||||
GuestPathName: name,
|
||||
}
|
||||
|
||||
h := &chattr.Body
|
||||
|
||||
tests := []struct {
|
||||
expect int
|
||||
f func()
|
||||
}{
|
||||
{
|
||||
vix.OK, func() {},
|
||||
},
|
||||
{
|
||||
vix.OK, func() {
|
||||
h.FileOptions = vix.FileAttributeSetModifyDate
|
||||
h.ModificationTime = time.Now().Unix()
|
||||
},
|
||||
},
|
||||
{
|
||||
vix.OK, func() {
|
||||
h.FileOptions = vix.FileAttributeSetAccessDate
|
||||
h.AccessTime = time.Now().Unix()
|
||||
},
|
||||
},
|
||||
{
|
||||
vix.FileAccessError, func() {
|
||||
h.FileOptions = vix.FileAttributeSetUnixOwnerid
|
||||
h.OwnerID = 0 // fails as we are not root
|
||||
},
|
||||
},
|
||||
{
|
||||
vix.FileAccessError, func() {
|
||||
h.FileOptions = vix.FileAttributeSetUnixGroupid
|
||||
h.GroupID = 0 // fails as we are not root
|
||||
},
|
||||
},
|
||||
{
|
||||
vix.OK, func() {
|
||||
h.FileOptions = vix.FileAttributeSetUnixOwnerid
|
||||
h.OwnerID = int32(os.Getuid())
|
||||
},
|
||||
},
|
||||
{
|
||||
vix.OK, func() {
|
||||
h.FileOptions = vix.FileAttributeSetUnixGroupid
|
||||
h.GroupID = int32(os.Getgid())
|
||||
},
|
||||
},
|
||||
{
|
||||
vix.OK, func() {
|
||||
h.FileOptions = vix.FileAttributeSetUnixPermissions
|
||||
h.Permissions = int32(os.FileMode(0755).Perm())
|
||||
},
|
||||
},
|
||||
{
|
||||
vix.FileNotFound, func() {
|
||||
_ = os.Remove(name)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
test.f()
|
||||
reply := c.Request(vix.CommandSetGuestFileAttributes, chattr)
|
||||
rc := vixRC(reply)
|
||||
|
||||
if rc != test.expect {
|
||||
t.Errorf("%d: rc=%d", i, rc)
|
||||
}
|
||||
}
|
||||
}
|
||||
202
vendor/github.com/vmware/govmomi/toolbox/guest_info.go
generated
vendored
Normal file
202
vendor/github.com/vmware/govmomi/toolbox/guest_info.go
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/davecgh/go-xdr/xdr2"
|
||||
)
|
||||
|
||||
// Defs from: open-vm-tools/lib/guestRpc/nicinfo.x
|
||||
|
||||
type TypedIPAddress struct {
|
||||
Type int32
|
||||
Address []byte
|
||||
}
|
||||
|
||||
type IPAddressEntry struct {
|
||||
Address TypedIPAddress
|
||||
PrefixLength uint32
|
||||
Origin *int32 `xdr:"optional"`
|
||||
Status *int32 `xdr:"optional"`
|
||||
}
|
||||
|
||||
type InetCidrRouteEntry struct {
|
||||
Dest TypedIPAddress
|
||||
PrefixLength uint32
|
||||
NextHop *TypedIPAddress `xdr:"optional"`
|
||||
IfIndex uint32
|
||||
Type int32
|
||||
Metric uint32
|
||||
}
|
||||
|
||||
type DNSConfigInfo struct {
|
||||
HostName *string `xdr:"optional"`
|
||||
DomainName *string `xdr:"optional"`
|
||||
Servers []TypedIPAddress
|
||||
Search *string `xdr:"optional"`
|
||||
}
|
||||
|
||||
type WinsConfigInfo struct {
|
||||
Primary TypedIPAddress
|
||||
Secondary TypedIPAddress
|
||||
}
|
||||
|
||||
type DhcpConfigInfo struct {
|
||||
Enabled bool
|
||||
Settings string
|
||||
}
|
||||
|
||||
type GuestNicV3 struct {
|
||||
MacAddress string
|
||||
IPs []IPAddressEntry
|
||||
DNSConfigInfo *DNSConfigInfo `xdr:"optional"`
|
||||
WinsConfigInfo *WinsConfigInfo `xdr:"optional"`
|
||||
DhcpConfigInfov4 *DhcpConfigInfo `xdr:"optional"`
|
||||
DhcpConfigInfov6 *DhcpConfigInfo `xdr:"optional"`
|
||||
}
|
||||
|
||||
type NicInfoV3 struct {
|
||||
Nics []GuestNicV3
|
||||
Routes []InetCidrRouteEntry
|
||||
DNSConfigInfo *DNSConfigInfo `xdr:"optional"`
|
||||
WinsConfigInfo *WinsConfigInfo `xdr:"optional"`
|
||||
DhcpConfigInfov4 *DhcpConfigInfo `xdr:"optional"`
|
||||
DhcpConfigInfov6 *DhcpConfigInfo `xdr:"optional"`
|
||||
}
|
||||
|
||||
type GuestNicInfo struct {
|
||||
Version int32
|
||||
V3 *NicInfoV3 `xdr:"optional"`
|
||||
}
|
||||
|
||||
func EncodeXDR(val interface{}) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
_, err := xdr.Marshal(&buf, val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func DecodeXDR(buf []byte, val interface{}) error {
|
||||
r := bytes.NewReader(buf)
|
||||
_, err := xdr.Unmarshal(r, val)
|
||||
return err
|
||||
}
|
||||
|
||||
func NewGuestNicInfo() *GuestNicInfo {
|
||||
return &GuestNicInfo{
|
||||
Version: 3,
|
||||
V3: &NicInfoV3{},
|
||||
}
|
||||
}
|
||||
|
||||
func (nic *GuestNicV3) AddIP(addr net.Addr) {
|
||||
ip, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
kind := int32(1) // IAT_IPV4
|
||||
if ip.IP.To4() == nil {
|
||||
kind = 2 // IAT_IPV6
|
||||
} else {
|
||||
ip.IP = ip.IP.To4() // convert to 4-byte representation
|
||||
}
|
||||
|
||||
size, _ := ip.Mask.Size()
|
||||
|
||||
// nicinfo.x defines enum IpAddressStatus, but vmtoolsd only uses IAS_PREFERRED
|
||||
var status int32 = 1 // IAS_PREFERRED
|
||||
|
||||
e := IPAddressEntry{
|
||||
Address: TypedIPAddress{
|
||||
Type: kind,
|
||||
Address: []byte(ip.IP),
|
||||
},
|
||||
PrefixLength: uint32(size),
|
||||
Status: &status,
|
||||
}
|
||||
|
||||
nic.IPs = append(nic.IPs, e)
|
||||
}
|
||||
|
||||
func GuestInfoCommand(kind int, req []byte) []byte {
|
||||
request := fmt.Sprintf("SetGuestInfo %d ", kind)
|
||||
return append([]byte(request), req...)
|
||||
}
|
||||
|
||||
var (
|
||||
netInterfaces = net.Interfaces
|
||||
maxNics = 16 // guestRpc/nicinfo.x:NICINFO_MAX_NICS
|
||||
)
|
||||
|
||||
//
|
||||
func DefaultGuestNicInfo() *GuestNicInfo {
|
||||
proto := NewGuestNicInfo()
|
||||
info := proto.V3
|
||||
// #nosec: Errors unhandled
|
||||
ifs, _ := netInterfaces()
|
||||
|
||||
for _, i := range ifs {
|
||||
if i.Flags&net.FlagLoopback == net.FlagLoopback {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(i.HardwareAddr) == 0 {
|
||||
continue // Not useful from outside the guest without a MAC
|
||||
}
|
||||
|
||||
// #nosec: Errors unhandled
|
||||
addrs, _ := i.Addrs()
|
||||
|
||||
if len(addrs) == 0 {
|
||||
continue // Not useful from outside the guest without an IP
|
||||
}
|
||||
|
||||
nic := GuestNicV3{
|
||||
MacAddress: i.HardwareAddr.String(),
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
nic.AddIP(addr)
|
||||
}
|
||||
|
||||
info.Nics = append(info.Nics, nic)
|
||||
|
||||
if len(info.Nics) >= maxNics {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return proto
|
||||
}
|
||||
|
||||
func GuestInfoNicInfoRequest() ([]byte, error) {
|
||||
r, err := EncodeXDR(DefaultGuestNicInfo())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GuestInfoCommand(9 /*INFO_IPADDRESS_V3*/, r), nil
|
||||
}
|
||||
73
vendor/github.com/vmware/govmomi/toolbox/guest_info_test.go
generated
vendored
Normal file
73
vendor/github.com/vmware/govmomi/toolbox/guest_info_test.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultGuestNicProto(t *testing.T) {
|
||||
p := DefaultGuestNicInfo()
|
||||
|
||||
info := p.V3
|
||||
|
||||
for _, nic := range info.Nics {
|
||||
if len(nic.MacAddress) == 0 {
|
||||
continue
|
||||
}
|
||||
_, err := net.ParseMAC(nic.MacAddress)
|
||||
if err != nil {
|
||||
t.Errorf("invalid MAC %s: %s", nic.MacAddress, err)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := EncodeXDR(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var dp GuestNicInfo
|
||||
err = DecodeXDR(b, &dp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(p, &dp) {
|
||||
t.Error("decode mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxGuestNic(t *testing.T) {
|
||||
p := DefaultGuestNicInfo()
|
||||
|
||||
maxNics = len(p.V3.Nics)
|
||||
|
||||
a, _ := net.Interfaces()
|
||||
a = append(a, a...) // double the number of interfaces returned
|
||||
netInterfaces = func() ([]net.Interface, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
p = DefaultGuestNicInfo()
|
||||
|
||||
l := len(p.V3.Nics)
|
||||
if l != maxNics {
|
||||
t.Errorf("Nics=%d", l)
|
||||
}
|
||||
}
|
||||
342
vendor/github.com/vmware/govmomi/toolbox/hgfs/archive.go
generated
vendored
Normal file
342
vendor/github.com/vmware/govmomi/toolbox/hgfs/archive.go
generated
vendored
Normal file
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hgfs
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox/vix"
|
||||
)
|
||||
|
||||
// ArchiveScheme is the default scheme used to register the archive FileHandler
|
||||
var ArchiveScheme = "archive"
|
||||
|
||||
// ArchiveHandler implements a FileHandler for transferring directories.
|
||||
type ArchiveHandler struct {
|
||||
Read func(*url.URL, *tar.Reader) error
|
||||
Write func(*url.URL, *tar.Writer) error
|
||||
}
|
||||
|
||||
// NewArchiveHandler returns a FileHandler implementation for transferring directories using gzip'd tar files.
|
||||
func NewArchiveHandler() FileHandler {
|
||||
return &ArchiveHandler{
|
||||
Read: archiveRead,
|
||||
Write: archiveWrite,
|
||||
}
|
||||
}
|
||||
|
||||
// Stat implements FileHandler.Stat
|
||||
func (*ArchiveHandler) Stat(u *url.URL) (os.FileInfo, error) {
|
||||
switch u.Query().Get("format") {
|
||||
case "", "tar", "tgz":
|
||||
// ok
|
||||
default:
|
||||
log.Printf("unknown archive format: %q", u)
|
||||
return nil, vix.Error(vix.InvalidArg)
|
||||
}
|
||||
|
||||
return &archive{
|
||||
name: u.Path,
|
||||
size: math.MaxInt64,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Open implements FileHandler.Open
|
||||
func (h *ArchiveHandler) Open(u *url.URL, mode int32) (File, error) {
|
||||
switch mode {
|
||||
case OpenModeReadOnly:
|
||||
return h.newArchiveFromGuest(u)
|
||||
case OpenModeWriteOnly:
|
||||
return h.newArchiveToGuest(u)
|
||||
default:
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
}
|
||||
|
||||
// archive implements the hgfs.File and os.FileInfo interfaces.
|
||||
type archive struct {
|
||||
name string
|
||||
size int64
|
||||
done func() error
|
||||
|
||||
io.Reader
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// Name implementation of the os.FileInfo interface method.
|
||||
func (a *archive) Name() string {
|
||||
return a.name
|
||||
}
|
||||
|
||||
// Size implementation of the os.FileInfo interface method.
|
||||
func (a *archive) Size() int64 {
|
||||
return a.size
|
||||
}
|
||||
|
||||
// Mode implementation of the os.FileInfo interface method.
|
||||
func (a *archive) Mode() os.FileMode {
|
||||
return 0600
|
||||
}
|
||||
|
||||
// ModTime implementation of the os.FileInfo interface method.
|
||||
func (a *archive) ModTime() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// IsDir implementation of the os.FileInfo interface method.
|
||||
func (a *archive) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Sys implementation of the os.FileInfo interface method.
|
||||
func (a *archive) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The trailer is required since TransferFromGuest requires a Content-Length,
|
||||
// which toolbox doesn't know ahead of time as the gzip'd tarball never touches the disk.
|
||||
// HTTP clients need to be aware of this and stop reading when they see the 2nd gzip header.
|
||||
var gzipHeader = []byte{0x1f, 0x8b, 0x08} // rfc1952 {ID1, ID2, CM}
|
||||
|
||||
var gzipTrailer = true
|
||||
|
||||
// newArchiveFromGuest returns an hgfs.File implementation to read a directory as a gzip'd tar.
|
||||
func (h *ArchiveHandler) newArchiveFromGuest(u *url.URL) (File, error) {
|
||||
r, w := io.Pipe()
|
||||
|
||||
a := &archive{
|
||||
name: u.Path,
|
||||
done: r.Close,
|
||||
Reader: r,
|
||||
Writer: w,
|
||||
}
|
||||
|
||||
var z io.Writer = w
|
||||
var c io.Closer = ioutil.NopCloser(nil)
|
||||
|
||||
switch u.Query().Get("format") {
|
||||
case "tgz":
|
||||
gz := gzip.NewWriter(w)
|
||||
z = gz
|
||||
c = gz
|
||||
}
|
||||
|
||||
tw := tar.NewWriter(z)
|
||||
|
||||
go func() {
|
||||
err := h.Write(u, tw)
|
||||
|
||||
_ = tw.Close()
|
||||
_ = c.Close()
|
||||
if gzipTrailer {
|
||||
_, _ = w.Write(gzipHeader)
|
||||
}
|
||||
_ = w.CloseWithError(err)
|
||||
}()
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// newArchiveToGuest returns an hgfs.File implementation to expand a gzip'd tar into a directory.
|
||||
func (h *ArchiveHandler) newArchiveToGuest(u *url.URL) (File, error) {
|
||||
r, w := io.Pipe()
|
||||
|
||||
buf := bufio.NewReader(r)
|
||||
|
||||
a := &archive{
|
||||
name: u.Path,
|
||||
Reader: buf,
|
||||
Writer: w,
|
||||
}
|
||||
|
||||
var cerr error
|
||||
var wg sync.WaitGroup
|
||||
|
||||
a.done = func() error {
|
||||
_ = w.Close()
|
||||
// We need to wait for unpack to finish to complete its work
|
||||
// and to propagate the error if any to Close.
|
||||
wg.Wait()
|
||||
return cerr
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
c := func() error {
|
||||
// Drain the pipe of tar trailer data (two null blocks)
|
||||
if cerr == nil {
|
||||
_, _ = io.Copy(ioutil.Discard, a.Reader)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
header, _ := buf.Peek(len(gzipHeader))
|
||||
|
||||
if bytes.Equal(header, gzipHeader) {
|
||||
gz, err := gzip.NewReader(a.Reader)
|
||||
if err != nil {
|
||||
_ = r.CloseWithError(err)
|
||||
cerr = err
|
||||
return
|
||||
}
|
||||
|
||||
c = gz.Close
|
||||
a.Reader = gz
|
||||
}
|
||||
|
||||
tr := tar.NewReader(a.Reader)
|
||||
|
||||
cerr = h.Read(u, tr)
|
||||
|
||||
_ = c()
|
||||
_ = r.CloseWithError(cerr)
|
||||
}()
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *archive) Close() error {
|
||||
return a.done()
|
||||
}
|
||||
|
||||
// archiveRead writes the contents of the given tar.Reader to the given directory.
|
||||
func archiveRead(u *url.URL, tr *tar.Reader) error {
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
name := filepath.Join(u.Path, header.Name)
|
||||
mode := os.FileMode(header.Mode)
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
err = os.MkdirAll(name, mode)
|
||||
case tar.TypeReg:
|
||||
_ = os.MkdirAll(filepath.Dir(name), 0755)
|
||||
|
||||
var f *os.File
|
||||
|
||||
f, err = os.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_TRUNC, mode)
|
||||
if err == nil {
|
||||
_, cerr := io.Copy(f, tr)
|
||||
err = f.Close()
|
||||
if cerr != nil {
|
||||
err = cerr
|
||||
}
|
||||
}
|
||||
case tar.TypeSymlink:
|
||||
err = os.Symlink(header.Linkname, name)
|
||||
}
|
||||
|
||||
// TODO: Uid/Gid may not be meaningful here without some mapping.
|
||||
// The other option to consider would be making use of the guest auth user ID.
|
||||
// os.Lchown(name, header.Uid, header.Gid)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// archiveWrite writes the contents of the given source directory to the given tar.Writer.
|
||||
func archiveWrite(u *url.URL, tw *tar.Writer) error {
|
||||
info, err := os.Stat(u.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Note that the VMX will trim any trailing slash. For example:
|
||||
// "/foo/bar/?prefix=bar/" will end up here as "/foo/bar/?prefix=bar"
|
||||
// Escape to avoid this: "/for/bar/?prefix=bar%2F"
|
||||
prefix := u.Query().Get("prefix")
|
||||
|
||||
dir := u.Path
|
||||
|
||||
f := func(file string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
name := strings.TrimPrefix(file, dir)
|
||||
name = strings.TrimPrefix(name, "/")
|
||||
|
||||
if name == "" {
|
||||
return nil // this is u.Path itself (which may or may not have a trailing "/")
|
||||
}
|
||||
|
||||
if prefix != "" {
|
||||
name = prefix + name
|
||||
}
|
||||
|
||||
header, _ := tar.FileInfoHeader(fi, name)
|
||||
|
||||
header.Name = name
|
||||
|
||||
if header.Typeflag == tar.TypeDir {
|
||||
header.Name += "/"
|
||||
}
|
||||
|
||||
var f *os.File
|
||||
|
||||
if header.Typeflag == tar.TypeReg && fi.Size() != 0 {
|
||||
f, err = os.Open(file)
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_ = tw.WriteHeader(header)
|
||||
|
||||
if f != nil {
|
||||
_, err = io.Copy(tw, f)
|
||||
_ = f.Close()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return filepath.Walk(u.Path, f)
|
||||
}
|
||||
|
||||
dir = filepath.Dir(dir)
|
||||
|
||||
return f(u.Path, info, nil)
|
||||
}
|
||||
437
vendor/github.com/vmware/govmomi/toolbox/hgfs/archive_test.go
generated
vendored
Normal file
437
vendor/github.com/vmware/govmomi/toolbox/hgfs/archive_test.go
generated
vendored
Normal file
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hgfs
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestReadArchive(t *testing.T) {
|
||||
Trace = testing.Verbose()
|
||||
|
||||
dir, err := ioutil.TempDir("", "toolbox-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nfiles := 5
|
||||
subdir := path.Join(dir, "hangar-18")
|
||||
_ = os.MkdirAll(subdir, 0755)
|
||||
dirs := []string{dir, subdir}
|
||||
|
||||
for i := 0; i < nfiles; i++ {
|
||||
for _, p := range dirs {
|
||||
data := bytes.NewBufferString(strings.Repeat("X", i+1024))
|
||||
|
||||
f, ferr := ioutil.TempFile(p, fmt.Sprintf("file-%d-", i))
|
||||
if ferr != nil {
|
||||
t.Fatal(ferr)
|
||||
}
|
||||
_, err = io.Copy(f, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
err = os.Symlink(f.Name(), path.Join(p, "first-file"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c := NewClient()
|
||||
c.s.RegisterFileHandler(ArchiveScheme, NewArchiveHandler())
|
||||
|
||||
status := c.CreateSession()
|
||||
if status != StatusSuccess {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
_, status = c.GetAttr(dir)
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
handle, status := c.Open(dir + "?format=tgz")
|
||||
if status != StatusSuccess {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
var req *RequestReadV3
|
||||
var offset uint64
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
for {
|
||||
req = &RequestReadV3{
|
||||
Offset: offset,
|
||||
Handle: handle,
|
||||
RequiredSize: 4096,
|
||||
}
|
||||
|
||||
res := new(ReplyReadV3)
|
||||
|
||||
status = c.Dispatch(OpReadV3, req, res).Status
|
||||
if status != StatusSuccess {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
if Trace {
|
||||
fmt.Fprintf(os.Stderr, "read %d: %q\n", res.ActualSize, string(res.Payload))
|
||||
}
|
||||
|
||||
offset += uint64(res.ActualSize)
|
||||
_, _ = buf.Write(res.Payload)
|
||||
|
||||
if res.ActualSize == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
status = c.Close(handle)
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
status = c.DestroySession()
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
var files []string
|
||||
gz, _ := gzip.NewReader(&buf)
|
||||
tr := tar.NewReader(gz)
|
||||
|
||||
for {
|
||||
header, terr := tr.Next()
|
||||
if terr != nil {
|
||||
if terr == io.EOF {
|
||||
break
|
||||
}
|
||||
t.Fatal(terr)
|
||||
}
|
||||
|
||||
files = append(files, header.Name)
|
||||
|
||||
if header.Typeflag == tar.TypeReg {
|
||||
_, err = io.Copy(ioutil.Discard, tr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nfiles++ // symlink
|
||||
expect := nfiles*len(dirs) + len(dirs) - 1
|
||||
if len(files) != expect {
|
||||
t.Errorf("expected %d, files=%d", expect, len(files))
|
||||
}
|
||||
|
||||
err = os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteArchive(t *testing.T) {
|
||||
Trace = testing.Verbose()
|
||||
|
||||
dir, err := ioutil.TempDir("", "toolbox-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nfiles := 5
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
gz := gzip.NewWriter(&buf)
|
||||
tw := tar.NewWriter(gz)
|
||||
|
||||
for i := 0; i < nfiles; i++ {
|
||||
data := bytes.NewBufferString(strings.Repeat("X", i+1024))
|
||||
|
||||
header := &tar.Header{
|
||||
Name: fmt.Sprintf("toolbox-file-%d", i),
|
||||
ModTime: time.Now(),
|
||||
Mode: 0644,
|
||||
Typeflag: tar.TypeReg,
|
||||
Size: int64(data.Len()),
|
||||
}
|
||||
|
||||
err = tw.WriteHeader(header)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, _ = tw.Write(data.Bytes())
|
||||
|
||||
if i == 0 {
|
||||
err = tw.WriteHeader(&tar.Header{
|
||||
Linkname: header.Name,
|
||||
Name: "first-file",
|
||||
ModTime: time.Now(),
|
||||
Mode: 0644,
|
||||
Typeflag: tar.TypeSymlink,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = tw.WriteHeader(&tar.Header{
|
||||
Name: "subdir",
|
||||
ModTime: time.Now(),
|
||||
Mode: 0755,
|
||||
Typeflag: tar.TypeDir,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = tw.Close()
|
||||
_ = gz.Close()
|
||||
|
||||
c := NewClient()
|
||||
c.s.RegisterFileHandler(ArchiveScheme, NewArchiveHandler())
|
||||
|
||||
status := c.CreateSession()
|
||||
if status != StatusSuccess {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
_, status = c.GetAttr(dir)
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
handle, status := c.OpenWrite(dir)
|
||||
if status != StatusSuccess {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
payload := buf.Bytes()
|
||||
size := uint32(buf.Len())
|
||||
|
||||
req := &RequestWriteV3{
|
||||
Handle: handle,
|
||||
WriteFlags: WriteAppend,
|
||||
Offset: 0,
|
||||
RequiredSize: size,
|
||||
Payload: payload,
|
||||
}
|
||||
|
||||
res := new(ReplyReadV3)
|
||||
|
||||
status = c.Dispatch(OpWriteV3, req, res).Status
|
||||
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
var attr AttrV2
|
||||
info, _ := os.Stat(dir)
|
||||
attr.Stat(info)
|
||||
|
||||
status = c.SetAttr(dir, attr)
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
status = c.Close(handle)
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
status = c.DestroySession()
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(files) != nfiles+2 {
|
||||
t.Errorf("files=%d", len(files))
|
||||
}
|
||||
|
||||
err = os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func cpTar(tr *tar.Reader, tw *tar.Writer) error {
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if header.Typeflag != tar.TypeReg {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = tw.WriteHeader(header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(tw, tr)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArchiveFormat(t *testing.T) {
|
||||
gzipTrailer = false
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
h := &ArchiveHandler{
|
||||
Read: func(_ *url.URL, tr *tar.Reader) error {
|
||||
tw := tar.NewWriter(&buf)
|
||||
if err := cpTar(tr, tw); err != nil {
|
||||
return err
|
||||
}
|
||||
return tw.Close()
|
||||
},
|
||||
Write: func(u *url.URL, tw *tar.Writer) error {
|
||||
tr := tar.NewReader(&buf)
|
||||
|
||||
return cpTar(tr, tw)
|
||||
},
|
||||
}
|
||||
|
||||
for _, format := range []string{"tar", "tgz"} {
|
||||
u := &url.URL{
|
||||
Scheme: ArchiveScheme,
|
||||
Path: ".",
|
||||
RawQuery: "format=" + format,
|
||||
}
|
||||
|
||||
_, err := h.Stat(u)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// ToGuest: git archive | h.Read (to buf)
|
||||
f, err := h.Open(u, OpenModeWriteOnly)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("git", "archive", "--format", format, "HEAD")
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := io.Copy(f, stdout)
|
||||
if err != nil {
|
||||
t.Errorf("copy %d: %s", n, err)
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// FromGuest: h.Write (from buf) | tar -tvf-
|
||||
f, err = h.Open(u, OpenModeReadOnly)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd = exec.Command("tar", "-tvf-")
|
||||
|
||||
if format == "tgz" {
|
||||
cmd.Args = append(cmd.Args, "-z")
|
||||
}
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if testing.Verbose() {
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stderr
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err = io.Copy(stdin, f)
|
||||
if err != nil {
|
||||
t.Errorf("copy %d: %s", n, err)
|
||||
}
|
||||
|
||||
_ = stdin.Close()
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
73
vendor/github.com/vmware/govmomi/toolbox/hgfs/encoding.go
generated
vendored
Normal file
73
vendor/github.com/vmware/govmomi/toolbox/hgfs/encoding.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hgfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// MarshalBinary is a wrapper around binary.Write
|
||||
func MarshalBinary(fields ...interface{}) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
for _, p := range fields {
|
||||
switch m := p.(type) {
|
||||
case encoding.BinaryMarshaler:
|
||||
data, err := m.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, ProtocolError(err)
|
||||
}
|
||||
|
||||
_, _ = buf.Write(data)
|
||||
case []byte:
|
||||
_, _ = buf.Write(m)
|
||||
case string:
|
||||
_, _ = buf.WriteString(m)
|
||||
default:
|
||||
err := binary.Write(buf, binary.LittleEndian, p)
|
||||
if err != nil {
|
||||
return nil, ProtocolError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary is a wrapper around binary.Read
|
||||
func UnmarshalBinary(data []byte, fields ...interface{}) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
for _, p := range fields {
|
||||
switch m := p.(type) {
|
||||
case encoding.BinaryUnmarshaler:
|
||||
return m.UnmarshalBinary(buf.Bytes())
|
||||
case *[]byte:
|
||||
*m = buf.Bytes()
|
||||
return nil
|
||||
default:
|
||||
err := binary.Read(buf, binary.LittleEndian, p)
|
||||
if err != nil {
|
||||
return ProtocolError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
24
vendor/github.com/vmware/govmomi/toolbox/hgfs/hgfs_darwin.go
generated
vendored
Normal file
24
vendor/github.com/vmware/govmomi/toolbox/hgfs/hgfs_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hgfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func (a *AttrV2) sysStat(info os.FileInfo) {
|
||||
}
|
||||
58
vendor/github.com/vmware/govmomi/toolbox/hgfs/hgfs_linux.go
generated
vendored
Normal file
58
vendor/github.com/vmware/govmomi/toolbox/hgfs/hgfs_linux.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hgfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const attrMask = AttrValidAllocationSize |
|
||||
AttrValidAccessTime | AttrValidWriteTime | AttrValidCreateTime | AttrValidChangeTime |
|
||||
AttrValidSpecialPerms | AttrValidOwnerPerms | AttrValidGroupPerms | AttrValidOtherPerms | AttrValidEffectivePerms |
|
||||
AttrValidUserID | AttrValidGroupID | AttrValidFileID | AttrValidVolID
|
||||
|
||||
func (a *AttrV2) sysStat(info os.FileInfo) {
|
||||
sys, ok := info.Sys().(*syscall.Stat_t)
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
a.AllocationSize = uint64(sys.Blocks * 512)
|
||||
|
||||
nt := func(t syscall.Timespec) uint64 {
|
||||
return uint64(t.Nano()) // TODO: this is supposed to be Windows NT system time, not needed atm
|
||||
}
|
||||
|
||||
a.AccessTime = nt(sys.Atim)
|
||||
a.WriteTime = nt(sys.Mtim)
|
||||
a.CreationTime = a.WriteTime // see HgfsGetCreationTime
|
||||
a.AttrChangeTime = nt(sys.Ctim)
|
||||
|
||||
a.SpecialPerms = uint8((sys.Mode & (syscall.S_ISUID | syscall.S_ISGID | syscall.S_ISVTX)) >> 9)
|
||||
a.OwnerPerms = uint8((sys.Mode & syscall.S_IRWXU) >> 6)
|
||||
a.GroupPerms = uint8((sys.Mode & syscall.S_IRWXG) >> 3)
|
||||
a.OtherPerms = uint8(sys.Mode & syscall.S_IRWXO)
|
||||
|
||||
a.UserID = sys.Uid
|
||||
a.GroupID = sys.Gid
|
||||
a.HostFileID = sys.Ino
|
||||
a.VolumeID = uint32(sys.Dev)
|
||||
|
||||
a.Mask |= attrMask
|
||||
}
|
||||
24
vendor/github.com/vmware/govmomi/toolbox/hgfs/hgfs_windows.go
generated
vendored
Normal file
24
vendor/github.com/vmware/govmomi/toolbox/hgfs/hgfs_windows.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hgfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func (a *AttrV2) sysStat(info os.FileInfo) {
|
||||
}
|
||||
847
vendor/github.com/vmware/govmomi/toolbox/hgfs/protocol.go
generated
vendored
Normal file
847
vendor/github.com/vmware/govmomi/toolbox/hgfs/protocol.go
generated
vendored
Normal file
@@ -0,0 +1,847 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hgfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// See: https://github.com/vmware/open-vm-tools/blob/master/open-vm-tools/lib/include/hgfsProto.h
|
||||
|
||||
// Opcodes for server operations as defined in hgfsProto.h
|
||||
const (
|
||||
OpOpen = iota /* Open file */
|
||||
OpRead /* Read from file */
|
||||
OpWrite /* Write to file */
|
||||
OpClose /* Close file */
|
||||
OpSearchOpen /* Start new search */
|
||||
OpSearchRead /* Get next search response */
|
||||
OpSearchClose /* End a search */
|
||||
OpGetattr /* Get file attributes */
|
||||
OpSetattr /* Set file attributes */
|
||||
OpCreateDir /* Create new directory */
|
||||
OpDeleteFile /* Delete a file */
|
||||
OpDeleteDir /* Delete a directory */
|
||||
OpRename /* Rename a file or directory */
|
||||
OpQueryVolumeInfo /* Query volume information */
|
||||
OpOpenV2 /* Open file */
|
||||
OpGetattrV2 /* Get file attributes */
|
||||
OpSetattrV2 /* Set file attributes */
|
||||
OpSearchReadV2 /* Get next search response */
|
||||
OpCreateSymlink /* Create a symlink */
|
||||
OpServerLockChange /* Change the oplock on a file */
|
||||
OpCreateDirV2 /* Create a directory */
|
||||
OpDeleteFileV2 /* Delete a file */
|
||||
OpDeleteDirV2 /* Delete a directory */
|
||||
OpRenameV2 /* Rename a file or directory */
|
||||
OpOpenV3 /* Open file */
|
||||
OpReadV3 /* Read from file */
|
||||
OpWriteV3 /* Write to file */
|
||||
OpCloseV3 /* Close file */
|
||||
OpSearchOpenV3 /* Start new search */
|
||||
OpSearchReadV3 /* Read V3 directory entries */
|
||||
OpSearchCloseV3 /* End a search */
|
||||
OpGetattrV3 /* Get file attributes */
|
||||
OpSetattrV3 /* Set file attributes */
|
||||
OpCreateDirV3 /* Create new directory */
|
||||
OpDeleteFileV3 /* Delete a file */
|
||||
OpDeleteDirV3 /* Delete a directory */
|
||||
OpRenameV3 /* Rename a file or directory */
|
||||
OpQueryVolumeInfoV3 /* Query volume information */
|
||||
OpCreateSymlinkV3 /* Create a symlink */
|
||||
OpServerLockChangeV3 /* Change the oplock on a file */
|
||||
OpWriteWin32StreamV3 /* Write WIN32_STREAM_ID format data to file */
|
||||
OpCreateSessionV4 /* Create a session and return host capabilities. */
|
||||
OpDestroySessionV4 /* Destroy/close session. */
|
||||
OpReadFastV4 /* Read */
|
||||
OpWriteFastV4 /* Write */
|
||||
OpSetWatchV4 /* Start monitoring directory changes. */
|
||||
OpRemoveWatchV4 /* Stop monitoring directory changes. */
|
||||
OpNotifyV4 /* Notification for a directory change event. */
|
||||
OpSearchReadV4 /* Read V4 directory entries. */
|
||||
OpOpenV4 /* Open file */
|
||||
OpEnumerateStreamsV4 /* Enumerate alternative named streams for a file. */
|
||||
OpGetattrV4 /* Get file attributes */
|
||||
OpSetattrV4 /* Set file attributes */
|
||||
OpDeleteV4 /* Delete a file or a directory */
|
||||
OpLinkmoveV4 /* Rename/move/create hard link. */
|
||||
OpFsctlV4 /* Sending FS control requests. */
|
||||
OpAccessCheckV4 /* Access check. */
|
||||
OpFsyncV4 /* Flush all cached data to the disk. */
|
||||
OpQueryVolumeInfoV4 /* Query volume information. */
|
||||
OpOplockAcquireV4 /* Acquire OPLOCK. */
|
||||
OpOplockBreakV4 /* Break or downgrade OPLOCK. */
|
||||
OpLockByteRangeV4 /* Acquire byte range lock. */
|
||||
OpUnlockByteRangeV4 /* Release byte range lock. */
|
||||
OpQueryEasV4 /* Query extended attributes. */
|
||||
OpSetEasV4 /* Add or modify extended attributes. */
|
||||
OpNewHeader = 0xff /* Header op, must be unique, distinguishes packet headers. */
|
||||
)
|
||||
|
||||
// Status codes
|
||||
const (
|
||||
StatusSuccess = iota
|
||||
StatusNoSuchFileOrDir
|
||||
StatusInvalidHandle
|
||||
StatusOperationNotPermitted
|
||||
StatusFileExists
|
||||
StatusNotDirectory
|
||||
StatusDirNotEmpty
|
||||
StatusProtocolError
|
||||
StatusAccessDenied
|
||||
StatusInvalidName
|
||||
StatusGenericError
|
||||
StatusSharingViolation
|
||||
StatusNoSpace
|
||||
StatusOperationNotSupported
|
||||
StatusNameTooLong
|
||||
StatusInvalidParameter
|
||||
StatusNotSameDevice
|
||||
StatusStaleSession
|
||||
StatusTooManySessions
|
||||
StatusTransportError
|
||||
)
|
||||
|
||||
// Flags for attr mask
|
||||
const (
|
||||
AttrValidType = 1 << iota
|
||||
AttrValidSize
|
||||
AttrValidCreateTime
|
||||
AttrValidAccessTime
|
||||
AttrValidWriteTime
|
||||
AttrValidChangeTime
|
||||
AttrValidSpecialPerms
|
||||
AttrValidOwnerPerms
|
||||
AttrValidGroupPerms
|
||||
AttrValidOtherPerms
|
||||
AttrValidFlags
|
||||
AttrValidAllocationSize
|
||||
AttrValidUserID
|
||||
AttrValidGroupID
|
||||
AttrValidFileID
|
||||
AttrValidVolID
|
||||
AttrValidNonStaticFileID
|
||||
AttrValidEffectivePerms
|
||||
AttrValidExtendAttrSize
|
||||
AttrValidReparsePoint
|
||||
AttrValidShortName
|
||||
)
|
||||
|
||||
// HeaderVersion for HGFS protocol version 4
|
||||
const HeaderVersion = 0x1
|
||||
|
||||
// LargePacketMax is maximum size of an hgfs packet
|
||||
const LargePacketMax = 0xf800 // HGFS_LARGE_PACKET_MAX
|
||||
|
||||
// Packet flags
|
||||
const (
|
||||
PacketFlagRequest = 1 << iota
|
||||
PacketFlagReply
|
||||
PacketFlagInfoExterror
|
||||
PacketFlagValidFlags = 0x7
|
||||
)
|
||||
|
||||
// Status is an error type that encapsulates an error status code and the cause
|
||||
type Status struct {
|
||||
Err error
|
||||
Code uint32
|
||||
}
|
||||
|
||||
func (s *Status) Error() string {
|
||||
if s.Err != nil {
|
||||
return s.Err.Error()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("hgfs.Status=%d", s.Code)
|
||||
}
|
||||
|
||||
// errorStatus maps the given error type to a status code
|
||||
func errorStatus(err error) uint32 {
|
||||
if x, ok := err.(*Status); ok {
|
||||
return x.Code
|
||||
}
|
||||
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
return StatusNoSuchFileOrDir
|
||||
case os.IsExist(err):
|
||||
return StatusFileExists
|
||||
case os.IsPermission(err):
|
||||
return StatusOperationNotPermitted
|
||||
}
|
||||
|
||||
return StatusGenericError
|
||||
}
|
||||
|
||||
// ProtocolError wraps the given error as a Status type
|
||||
func ProtocolError(err error) error {
|
||||
return &Status{
|
||||
Err: err,
|
||||
Code: StatusProtocolError,
|
||||
}
|
||||
}
|
||||
|
||||
// Request as defined in hgfsProto.h:HgfsRequest
|
||||
type Request struct {
|
||||
Handle uint32
|
||||
Op int32
|
||||
}
|
||||
|
||||
// Reply as defined in hgfsProto.h:HgfsReply
|
||||
type Reply struct {
|
||||
Handle uint32
|
||||
Status uint32
|
||||
}
|
||||
|
||||
// Header as defined in hgfsProto.h:HgfsHeader
|
||||
type Header struct {
|
||||
Version uint8
|
||||
Reserved1 [3]uint8
|
||||
Dummy int32
|
||||
PacketSize uint32
|
||||
HeaderSize uint32
|
||||
RequestID uint32
|
||||
Op int32
|
||||
Status uint32
|
||||
Flags uint32
|
||||
Information uint32
|
||||
SessionID uint64
|
||||
Reserved uint64
|
||||
}
|
||||
|
||||
var (
|
||||
headerSize = uint32(binary.Size(new(Header)))
|
||||
|
||||
packetSize = func(r *Packet) uint32 {
|
||||
return headerSize + uint32(len(r.Payload))
|
||||
}
|
||||
)
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (h *Header) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, h)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading hgfs header: %s", err)
|
||||
}
|
||||
|
||||
if h.Dummy != OpNewHeader {
|
||||
return fmt.Errorf("expected hgfs header with OpNewHeader (%#x), got: %#x", OpNewHeader, h.Dummy)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Packet encapsulates an hgfs Header and Payload
|
||||
type Packet struct {
|
||||
Header
|
||||
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
// Reply composes a new Packet with the given payload or error
|
||||
func (r *Packet) Reply(payload interface{}, err error) ([]byte, error) {
|
||||
p := new(Packet)
|
||||
|
||||
status := uint32(StatusSuccess)
|
||||
|
||||
if err != nil {
|
||||
status = errorStatus(err)
|
||||
} else {
|
||||
p.Payload, err = MarshalBinary(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
p.Header = Header{
|
||||
Version: HeaderVersion,
|
||||
Dummy: OpNewHeader,
|
||||
PacketSize: headerSize + uint32(len(p.Payload)),
|
||||
HeaderSize: headerSize,
|
||||
RequestID: r.RequestID,
|
||||
Op: r.Op,
|
||||
Status: status,
|
||||
Flags: PacketFlagReply,
|
||||
Information: 0,
|
||||
SessionID: r.SessionID,
|
||||
}
|
||||
|
||||
if Trace {
|
||||
rc := "OK"
|
||||
if err != nil {
|
||||
rc = err.Error()
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "[hgfs] response %#v [%s]\n", p.Header, rc)
|
||||
} else if err != nil {
|
||||
log.Printf("[hgfs] op=%d error: %s", r.Op, err)
|
||||
}
|
||||
|
||||
return p.MarshalBinary()
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *Packet) MarshalBinary() ([]byte, error) {
|
||||
r.Header.PacketSize = packetSize(r)
|
||||
|
||||
buf, _ := MarshalBinary(r.Header)
|
||||
|
||||
return append(buf, r.Payload...), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *Packet) UnmarshalBinary(data []byte) error {
|
||||
err := r.Header.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Payload = data[r.HeaderSize:r.PacketSize]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Capability as defined in hgfsProto.h:HgfsCapability
|
||||
type Capability struct {
|
||||
Op int32
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// RequestCreateSessionV4 as defined in hgfsProto.h:HgfsRequestCreateSessionV4
|
||||
type RequestCreateSessionV4 struct {
|
||||
NumCapabilities uint32
|
||||
MaxPacketSize uint32
|
||||
Flags uint32
|
||||
Reserved uint32
|
||||
Capabilities []Capability
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *RequestCreateSessionV4) MarshalBinary() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
r.NumCapabilities = uint32(len(r.Capabilities))
|
||||
|
||||
fields := []*uint32{
|
||||
&r.NumCapabilities,
|
||||
&r.MaxPacketSize,
|
||||
&r.Flags,
|
||||
&r.Reserved,
|
||||
}
|
||||
|
||||
for _, p := range fields {
|
||||
err := binary.Write(buf, binary.LittleEndian, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for i := uint32(0); i < r.NumCapabilities; i++ {
|
||||
err := binary.Write(buf, binary.LittleEndian, &r.Capabilities[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *RequestCreateSessionV4) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
fields := []*uint32{
|
||||
&r.NumCapabilities,
|
||||
&r.MaxPacketSize,
|
||||
&r.Flags,
|
||||
&r.Reserved,
|
||||
}
|
||||
|
||||
for _, p := range fields {
|
||||
err := binary.Read(buf, binary.LittleEndian, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i := uint32(0); i < r.NumCapabilities; i++ {
|
||||
var cap Capability
|
||||
err := binary.Read(buf, binary.LittleEndian, &cap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Capabilities = append(r.Capabilities, cap)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplyCreateSessionV4 as defined in hgfsProto.h:HgfsReplyCreateSessionV4
|
||||
type ReplyCreateSessionV4 struct {
|
||||
SessionID uint64
|
||||
NumCapabilities uint32
|
||||
MaxPacketSize uint32
|
||||
IdentityOffset uint32
|
||||
Flags uint32
|
||||
Reserved uint32
|
||||
Capabilities []Capability
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *ReplyCreateSessionV4) MarshalBinary() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
fields := []interface{}{
|
||||
&r.SessionID,
|
||||
&r.NumCapabilities,
|
||||
&r.MaxPacketSize,
|
||||
&r.IdentityOffset,
|
||||
&r.Flags,
|
||||
&r.Reserved,
|
||||
}
|
||||
|
||||
for _, p := range fields {
|
||||
err := binary.Write(buf, binary.LittleEndian, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for i := uint32(0); i < r.NumCapabilities; i++ {
|
||||
err := binary.Write(buf, binary.LittleEndian, &r.Capabilities[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *ReplyCreateSessionV4) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
fields := []interface{}{
|
||||
&r.SessionID,
|
||||
&r.NumCapabilities,
|
||||
&r.MaxPacketSize,
|
||||
&r.IdentityOffset,
|
||||
&r.Flags,
|
||||
&r.Reserved,
|
||||
}
|
||||
|
||||
for _, p := range fields {
|
||||
err := binary.Read(buf, binary.LittleEndian, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i := uint32(0); i < r.NumCapabilities; i++ {
|
||||
var cap Capability
|
||||
err := binary.Read(buf, binary.LittleEndian, &cap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Capabilities = append(r.Capabilities, cap)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequestDestroySessionV4 as defined in hgfsProto.h:HgfsRequestDestroySessionV4
|
||||
type RequestDestroySessionV4 struct {
|
||||
Reserved uint64
|
||||
}
|
||||
|
||||
// ReplyDestroySessionV4 as defined in hgfsProto.h:HgfsReplyDestroySessionV4
|
||||
type ReplyDestroySessionV4 struct {
|
||||
Reserved uint64
|
||||
}
|
||||
|
||||
// FileName as defined in hgfsProto.h:HgfsFileName
|
||||
type FileName struct {
|
||||
Length uint32
|
||||
Name string
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (f *FileName) MarshalBinary() ([]byte, error) {
|
||||
name := f.Name
|
||||
f.Length = uint32(len(f.Name))
|
||||
if f.Length == 0 {
|
||||
// field is defined as 'char name[1];', this byte is required for min sizeof() validation
|
||||
name = "\x00"
|
||||
}
|
||||
return MarshalBinary(&f.Length, name)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (f *FileName) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
_ = binary.Read(buf, binary.LittleEndian, &f.Length)
|
||||
|
||||
f.Name = string(buf.Next(int(f.Length)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const serverPolicyRootShareName = "root"
|
||||
|
||||
// FromString converts name to a FileName
|
||||
func (f *FileName) FromString(name string) {
|
||||
name = strings.TrimPrefix(name, "/")
|
||||
|
||||
cp := strings.Split(name, "/")
|
||||
|
||||
cp = append([]string{serverPolicyRootShareName}, cp...)
|
||||
|
||||
f.Name = strings.Join(cp, "\x00")
|
||||
f.Length = uint32(len(f.Name))
|
||||
}
|
||||
|
||||
// Path converts FileName to a string
|
||||
func (f *FileName) Path() string {
|
||||
cp := strings.Split(f.Name, "\x00")
|
||||
|
||||
if len(cp) == 0 || cp[0] != serverPolicyRootShareName {
|
||||
return "" // TODO: not happening until if/when we handle Windows shares
|
||||
}
|
||||
|
||||
cp[0] = ""
|
||||
|
||||
return strings.Join(cp, "/")
|
||||
}
|
||||
|
||||
// FileNameV3 as defined in hgfsProto.h:HgfsFileNameV3
|
||||
type FileNameV3 struct {
|
||||
Length uint32
|
||||
Flags uint32
|
||||
CaseType int32
|
||||
ID uint32
|
||||
Name string
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (f *FileNameV3) MarshalBinary() ([]byte, error) {
|
||||
name := f.Name
|
||||
f.Length = uint32(len(f.Name))
|
||||
if f.Length == 0 {
|
||||
// field is defined as 'char name[1];', this byte is required for min sizeof() validation
|
||||
name = "\x00"
|
||||
}
|
||||
return MarshalBinary(&f.Length, &f.Flags, &f.CaseType, &f.ID, name)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (f *FileNameV3) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
fields := []interface{}{
|
||||
&f.Length, &f.Flags, &f.CaseType, &f.ID,
|
||||
}
|
||||
|
||||
for _, p := range fields {
|
||||
if err := binary.Read(buf, binary.LittleEndian, p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
f.Name = string(buf.Next(int(f.Length)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FromString converts name to a FileNameV3
|
||||
func (f *FileNameV3) FromString(name string) {
|
||||
p := new(FileName)
|
||||
p.FromString(name)
|
||||
f.Name = p.Name
|
||||
f.Length = p.Length
|
||||
}
|
||||
|
||||
// Path converts FileNameV3 to a string
|
||||
func (f *FileNameV3) Path() string {
|
||||
return (&FileName{Name: f.Name, Length: f.Length}).Path()
|
||||
}
|
||||
|
||||
// FileType
|
||||
const (
|
||||
FileTypeRegular = iota
|
||||
FileTypeDirectory
|
||||
FileTypeSymlink
|
||||
)
|
||||
|
||||
// AttrV2 as defined in hgfsProto.h:HgfsAttrV2
|
||||
type AttrV2 struct {
|
||||
Mask uint64
|
||||
Type int32
|
||||
Size uint64
|
||||
CreationTime uint64
|
||||
AccessTime uint64
|
||||
WriteTime uint64
|
||||
AttrChangeTime uint64
|
||||
SpecialPerms uint8
|
||||
OwnerPerms uint8
|
||||
GroupPerms uint8
|
||||
OtherPerms uint8
|
||||
AttrFlags uint64
|
||||
AllocationSize uint64
|
||||
UserID uint32
|
||||
GroupID uint32
|
||||
HostFileID uint64
|
||||
VolumeID uint32
|
||||
EffectivePerms uint32
|
||||
Reserved2 uint64
|
||||
}
|
||||
|
||||
// RequestGetattrV2 as defined in hgfsProto.h:HgfsRequestGetattrV2
|
||||
type RequestGetattrV2 struct {
|
||||
Request
|
||||
AttrHint uint64
|
||||
Handle uint32
|
||||
FileName FileName
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *RequestGetattrV2) MarshalBinary() ([]byte, error) {
|
||||
return MarshalBinary(&r.Request, &r.AttrHint, &r.Handle, &r.FileName)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *RequestGetattrV2) UnmarshalBinary(data []byte) error {
|
||||
return UnmarshalBinary(data, &r.Request, &r.AttrHint, &r.Handle, &r.FileName)
|
||||
}
|
||||
|
||||
// ReplyGetattrV2 as defined in hgfsProto.h:HgfsReplyGetattrV2
|
||||
type ReplyGetattrV2 struct {
|
||||
Reply
|
||||
Attr AttrV2
|
||||
SymlinkTarget FileName
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *ReplyGetattrV2) MarshalBinary() ([]byte, error) {
|
||||
return MarshalBinary(&r.Reply, &r.Attr, &r.SymlinkTarget)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *ReplyGetattrV2) UnmarshalBinary(data []byte) error {
|
||||
return UnmarshalBinary(data, &r.Reply, &r.Attr, &r.SymlinkTarget)
|
||||
}
|
||||
|
||||
// RequestSetattrV2 as defined in hgfsProto.h:HgfsRequestSetattrV2
|
||||
type RequestSetattrV2 struct {
|
||||
Request
|
||||
Hints uint64
|
||||
Attr AttrV2
|
||||
Handle uint32
|
||||
FileName FileName
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *RequestSetattrV2) MarshalBinary() ([]byte, error) {
|
||||
return MarshalBinary(&r.Request, &r.Hints, &r.Attr, &r.Handle, &r.FileName)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *RequestSetattrV2) UnmarshalBinary(data []byte) error {
|
||||
return UnmarshalBinary(data, &r.Request, &r.Hints, &r.Attr, &r.Handle, &r.FileName)
|
||||
}
|
||||
|
||||
// ReplySetattrV2 as defined in hgfsProto.h:HgfsReplySetattrV2
|
||||
type ReplySetattrV2 struct {
|
||||
Header Reply
|
||||
}
|
||||
|
||||
// OpenMode
|
||||
const (
|
||||
OpenModeReadOnly = iota
|
||||
OpenModeWriteOnly
|
||||
OpenModeReadWrite
|
||||
OpenModeAccmodes
|
||||
)
|
||||
|
||||
// OpenFlags
|
||||
const (
|
||||
Open = iota
|
||||
OpenEmpty
|
||||
OpenCreate
|
||||
OpenCreateSafe
|
||||
OpenCreateEmpty
|
||||
)
|
||||
|
||||
// Permissions
|
||||
const (
|
||||
PermRead = 4
|
||||
PermWrite = 2
|
||||
PermExec = 1
|
||||
)
|
||||
|
||||
// RequestOpen as defined in hgfsProto.h:HgfsRequestOpen
|
||||
type RequestOpen struct {
|
||||
Request
|
||||
OpenMode int32
|
||||
OpenFlags int32
|
||||
Permissions uint8
|
||||
FileName FileName
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *RequestOpen) MarshalBinary() ([]byte, error) {
|
||||
return MarshalBinary(&r.Request, &r.OpenMode, &r.OpenFlags, r.Permissions, &r.FileName)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *RequestOpen) UnmarshalBinary(data []byte) error {
|
||||
return UnmarshalBinary(data, &r.Request, &r.OpenMode, &r.OpenFlags, &r.Permissions, &r.FileName)
|
||||
}
|
||||
|
||||
// ReplyOpen as defined in hgfsProto.h:HgfsReplyOpen
|
||||
type ReplyOpen struct {
|
||||
Reply
|
||||
Handle uint32
|
||||
}
|
||||
|
||||
// RequestClose as defined in hgfsProto.h:HgfsRequestClose
|
||||
type RequestClose struct {
|
||||
Request
|
||||
Handle uint32
|
||||
}
|
||||
|
||||
// ReplyClose as defined in hgfsProto.h:HgfsReplyClose
|
||||
type ReplyClose struct {
|
||||
Reply
|
||||
}
|
||||
|
||||
// Lock type
|
||||
const (
|
||||
LockNone = iota
|
||||
LockOpportunistic
|
||||
LockExclusive
|
||||
LockShared
|
||||
LockBatch
|
||||
LockLease
|
||||
)
|
||||
|
||||
// RequestOpenV3 as defined in hgfsProto.h:HgfsRequestOpenV3
|
||||
type RequestOpenV3 struct {
|
||||
Mask uint64
|
||||
OpenMode int32
|
||||
OpenFlags int32
|
||||
SpecialPerms uint8
|
||||
OwnerPerms uint8
|
||||
GroupPerms uint8
|
||||
OtherPerms uint8
|
||||
AttrFlags uint64
|
||||
AllocationSize uint64
|
||||
DesiredAccess uint32
|
||||
ShareAccess uint32
|
||||
DesiredLock int32
|
||||
Reserved1 uint64
|
||||
Reserved2 uint64
|
||||
FileName FileNameV3
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *RequestOpenV3) MarshalBinary() ([]byte, error) {
|
||||
return MarshalBinary(&r.Mask, &r.OpenMode, &r.OpenFlags,
|
||||
&r.SpecialPerms, &r.OwnerPerms, &r.GroupPerms, &r.OtherPerms,
|
||||
&r.AttrFlags, &r.AllocationSize, &r.DesiredAccess, &r.ShareAccess,
|
||||
&r.DesiredLock, &r.Reserved1, &r.Reserved2, &r.FileName)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *RequestOpenV3) UnmarshalBinary(data []byte) error {
|
||||
return UnmarshalBinary(data, &r.Mask, &r.OpenMode, &r.OpenFlags,
|
||||
&r.SpecialPerms, &r.OwnerPerms, &r.GroupPerms, &r.OtherPerms,
|
||||
&r.AttrFlags, &r.AllocationSize, &r.DesiredAccess, &r.ShareAccess,
|
||||
&r.DesiredLock, &r.Reserved1, &r.Reserved2, &r.FileName)
|
||||
}
|
||||
|
||||
// ReplyOpenV3 as defined in hgfsProto.h:HgfsReplyOpenV3
|
||||
type ReplyOpenV3 struct {
|
||||
Handle uint32
|
||||
AcquiredLock int32
|
||||
Flags int32
|
||||
Reserved uint32
|
||||
}
|
||||
|
||||
// RequestReadV3 as defined in hgfsProto.h:HgfsRequestReadV3
|
||||
type RequestReadV3 struct {
|
||||
Handle uint32
|
||||
Offset uint64
|
||||
RequiredSize uint32
|
||||
Reserved uint64
|
||||
}
|
||||
|
||||
// ReplyReadV3 as defined in hgfsProto.h:HgfsReplyReadV3
|
||||
type ReplyReadV3 struct {
|
||||
ActualSize uint32
|
||||
Reserved uint64
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *ReplyReadV3) MarshalBinary() ([]byte, error) {
|
||||
return MarshalBinary(&r.ActualSize, &r.Reserved, r.Payload)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *ReplyReadV3) UnmarshalBinary(data []byte) error {
|
||||
return UnmarshalBinary(data, &r.ActualSize, &r.Reserved, &r.Payload)
|
||||
}
|
||||
|
||||
// Write flags
|
||||
const (
|
||||
WriteAppend = 1
|
||||
)
|
||||
|
||||
// RequestWriteV3 as defined in hgfsProto.h:HgfsRequestWriteV3
|
||||
type RequestWriteV3 struct {
|
||||
Handle uint32
|
||||
WriteFlags uint8
|
||||
Offset uint64
|
||||
RequiredSize uint32
|
||||
Reserved uint64
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *RequestWriteV3) MarshalBinary() ([]byte, error) {
|
||||
return MarshalBinary(&r.Handle, &r.WriteFlags, &r.Offset, &r.RequiredSize, &r.Reserved, r.Payload)
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *RequestWriteV3) UnmarshalBinary(data []byte) error {
|
||||
return UnmarshalBinary(data, &r.Handle, &r.WriteFlags, &r.Offset, &r.RequiredSize, &r.Reserved, &r.Payload)
|
||||
}
|
||||
|
||||
// ReplyWriteV3 as defined in hgfsProto.h:HgfsReplyWriteV3
|
||||
type ReplyWriteV3 struct {
|
||||
ActualSize uint32
|
||||
Reserved uint64
|
||||
}
|
||||
198
vendor/github.com/vmware/govmomi/toolbox/hgfs/protocol_test.go
generated
vendored
Normal file
198
vendor/github.com/vmware/govmomi/toolbox/hgfs/protocol_test.go
generated
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hgfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProtocolEncoding(t *testing.T) {
|
||||
ps := packetSize
|
||||
defer func() {
|
||||
packetSize = ps
|
||||
}()
|
||||
|
||||
// a few structs have pading of some sort, leave PacketSize as-is for now with these tests
|
||||
packetSize = func(r *Packet) uint32 {
|
||||
return r.PacketSize
|
||||
}
|
||||
|
||||
decode := func(s string) []byte {
|
||||
b, _ := base64.StdEncoding.DecodeString(s)
|
||||
return b
|
||||
}
|
||||
|
||||
// base64 encoded packets below were captured from vmtoolsd during:
|
||||
// govc guest.download /etc/hosts -
|
||||
// govc guest.upload /etc/hosts /tmp/hosts
|
||||
tests := []struct {
|
||||
pkt string
|
||||
dec interface{}
|
||||
}{
|
||||
{
|
||||
"AQAAAP8AAABMAAAANAAAAAAAAIApAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
new(RequestCreateSessionV4),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAABYAgAANAAAAAAAAIApAAAAAAAAAAIAAAAAAAAA//////////8AAAAAAAAAAF/NcjwZ1DYAQQAAAAD4AAAAAAAAAQAAAAAAAAAAAAAAAQAAAAEAAAABAAAAAgAAAAEAAAADAAAAAQAAAAQAAAABAAAABQAAAAEAAAAGAAAAAQAAAAcAAAABAAAACAAAAAEAAAAJAAAAAQAAAAoAAAABAAAACwAAAAEAAAAMAAAAAQAAAA0AAAABAAAADgAAAAEAAAAPAAAAAQAAABAAAAABAAAAEQAAAAEAAAASAAAAAQAAABMAAAAAAAAAFAAAAAEAAAAVAAAAAQAAABYAAAABAAAAFwAAAAEAAAAYAAAAAQAAABkAAAABAAAAGgAAAAEAAAAbAAAAAQAAABwAAAABAAAAHQAAAAEAAAAeAAAAAQAAAB8AAAABAAAAIAAAAAEAAAAhAAAAAQAAACIAAAABAAAAIwAAAAEAAAAkAAAAAQAAACUAAAABAAAAJgAAAAEAAAAnAAAAAAAAACgAAAAAAAAAKQAAAAEAAAAqAAAAAQAAACsAAAAAAAAALAAAAAAAAAAtAAAAAAAAAC4AAAAAAAAALwAAAAAAAAAwAAAAAAAAADEAAAAAAAAAMgAAAAAAAAAzAAAAAAAAADQAAAAAAAAANQAAAAAAAAA2AAAAAAAAADcAAAAAAAAAOAAAAAAAAAA5AAAAAAAAADoAAAAAAAAAOwAAAAAAAAA8AAAAAAAAAD0AAAAAAAAAPgAAAAAAAAA/AAAAAAAAAEAAAAAAAAAA",
|
||||
new(ReplyCreateSessionV4),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAABbAAAANAAAAAAAAIAPAAAAAAAAAAEAAAAAAAAAX81yPBnUNgAAAAAAAAAAAAAAAIAPAAAAAAAAAAAAAAAAAACADgAAAHJvb3QAZXRjAGhvc3RzAA==",
|
||||
new(RequestGetattrV2),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAACpAAAANAAAAAAAAIAPAAAAAAAAAAIAAAAAAAAAX81yPBnUNgAAAAAAAAAAAAAAAAAAAAAA//sCAAAAAAAAAAAAxgAAAAAAAABkP/0QmUzSAaAHo3qfz9IBZD/9EJlM0gFkP/0QmUzSAQAGBAQAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAABY9xMAAAAAAAD8AAAEAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
new(ReplyGetattrV2),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAABYAAAANAAAAAAAAIAAAAAAAAAAAAEAAAAAAAAAX81yPBnUNgAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAGDgAAAHJvb3QAZXRjAGhvc3Rzcw==",
|
||||
new(RequestOpen),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAABAAAAANAAAAAAAAIAAAAAAAAAAAAIAAAAAAAAAX81yPBnUNgAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
new(ReplyOpen),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAABMAAAANAAAAAAAAIAZAAAAAAAAAAEAAAAAAAAAX81yPBnUNgAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAAAAAAAAA==",
|
||||
new(RequestReadV3),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAAAHAQAANAAAAAAAAIAZAAAAAAAAAAIAAAAAAAAAX81yPBnUNgAAAAAAAAAAAMYAAAAAAAAAAAAAADEyNy4wLjAuMQlsb2NhbGhvc3QKMTI3LjAuMS4xCXZhZ3JhbnQudm0JdmFncmFudAoKIyBUaGUgZm9sbG93aW5nIGxpbmVzIGFyZSBkZXNpcmFibGUgZm9yIElQdjYgY2FwYWJsZSBob3N0cwo6OjEgICAgIGxvY2FsaG9zdCBpcDYtbG9jYWxob3N0IGlwNi1sb29wYmFjawpmZjAyOjoxIGlwNi1hbGxub2RlcwpmZjAyOjoyIGlwNi1hbGxyb3V0ZXJzCgA=",
|
||||
new(ReplyReadV3),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAABAAAAANAAAAAAAAIADAAAAAAAAAAEAAAAAAAAAX81yPBnUNgAAAAAAAAAAAAAAAIADAAAAAAAAAA==",
|
||||
new(RequestClose),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAAA8AAAANAAAAAAAAIADAAAAAAAAAAIAAAAAAAAAX81yPBnUNgAAAAAAAAAAAAAAAAAAAAAA",
|
||||
new(ReplyClose),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAAA8AAAANAAAAAAAAIAqAAAAAAAAAAEAAAAAAAAAX81yPBnUNgAAAAAAAAAAAAAAAAAAAAAA",
|
||||
new(RequestDestroySessionV4),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAAA8AAAANAAAAAAAAIAqAAAAAAAAAAIAAAAAAAAAX81yPBnUNgAAAAAAAAAAAAAAAAAAAAAA",
|
||||
new(ReplyDestroySessionV4),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAACZAAAANAAAAAAAAIAYAAAAAAAAAAEAAAAAAAAAyXxnMKrzOwAAAAAAAAAAAAsIAAAAAAAAAQAAAAQAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAcm9vdAB0bXAAcmVzb2x2LmNvbmYA",
|
||||
new(RequestOpenV3),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAABEAAAANAAAAAAAAIAYAAAAAAAAAAIAAAAAAAAAyXxnMKrzOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
new(ReplyOpenV3),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAADJAAAANAAAAAAAAIAQAAAAAAAAAAEAAAAAAAAAyXxnMKrzOwAAAAAAAAAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAcm9vdAB0bXAAcmVzb2x2LmNvbmYA",
|
||||
new(RequestSetattrV2),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAAA8AAAANAAAAAAAAIAQAAAAAAAAAAIAAAAAAAAAyXxnMKrzOwAAAAAAAAAAAAAAAAAAAAAA",
|
||||
new(ReplySetattrV2),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAACTAAAANAAAAAAAAIAaAAAAAAAAAAEAAAAAAAAAyXxnMKrzOwAAAAAAAAAAAAAAAAABAAAAAAAAAABGAAAAAAAAAAAAAABuYW1lc2VydmVyIDEwLjExOC42NS4xCm5hbWVzZXJ2ZXIgMTAuMTE4LjY1LjIKc2VhcmNoIGVuZy52bXdhcmUuY29tIAoK",
|
||||
new(RequestWriteV3),
|
||||
},
|
||||
{
|
||||
"AQAAAP8AAABAAAAANAAAAAAAAIAaAAAAAAAAAAIAAAAAAAAAyXxnMKrzOwAAAAAAAAAAAEYAAAAAAAAAAAAAAA==",
|
||||
new(ReplyWriteV3),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
dec := decode(test.pkt)
|
||||
pkt := new(Packet)
|
||||
err := pkt.UnmarshalBinary(dec)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = UnmarshalBinary(pkt.Payload, test.dec)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pkt.Payload, err = MarshalBinary(test.dec)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
enc, err := pkt.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.HasPrefix(dec, enc) {
|
||||
t.Errorf("%d: %T != %s\n", i, test.dec, test.pkt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileName(t *testing.T) {
|
||||
tests := []struct {
|
||||
raw string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
"root\x00etc\x00hosts",
|
||||
"/etc/hosts",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
fn := FileName{
|
||||
Name: test.raw,
|
||||
Length: uint32(len(test.raw)),
|
||||
}
|
||||
|
||||
if fn.Path() != test.name {
|
||||
t.Errorf("%d: %q != %q", i, fn.Path(), test.name)
|
||||
}
|
||||
|
||||
var fs FileName
|
||||
fs.FromString(test.name)
|
||||
if fs != fn {
|
||||
t.Errorf("%d: %v != %v", i, fn, fs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtocolMarshal(t *testing.T) {
|
||||
var buf []byte
|
||||
var x uint64
|
||||
err := UnmarshalBinary(buf, x)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
status := err.(*Status)
|
||||
if status.Error() != io.EOF.Error() {
|
||||
t.Errorf("err=%s", status.Err)
|
||||
}
|
||||
|
||||
status.Err = nil
|
||||
if status.Error() == "" {
|
||||
t.Error("status")
|
||||
}
|
||||
}
|
||||
584
vendor/github.com/vmware/govmomi/toolbox/hgfs/server.go
generated
vendored
Normal file
584
vendor/github.com/vmware/govmomi/toolbox/hgfs/server.go
generated
vendored
Normal file
@@ -0,0 +1,584 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hgfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// See: https://github.com/vmware/open-vm-tools/blob/master/open-vm-tools/lib/hgfsServer/hgfsServer.c
|
||||
|
||||
var (
|
||||
// Trace enables hgfs packet tracing
|
||||
Trace = false
|
||||
)
|
||||
|
||||
// Server provides an HGFS protocol implementation to support guest tools VmxiHgfsSendPacketCommand
|
||||
type Server struct {
|
||||
Capabilities []Capability
|
||||
|
||||
handlers map[int32]func(*Packet) (interface{}, error)
|
||||
schemes map[string]FileHandler
|
||||
sessions map[uint64]*session
|
||||
mu sync.Mutex
|
||||
handle uint32
|
||||
|
||||
chmod func(string, os.FileMode) error
|
||||
chown func(string, int, int) error
|
||||
}
|
||||
|
||||
// NewServer creates a new Server instance with the default handlers
|
||||
func NewServer() *Server {
|
||||
if f := flag.Lookup("toolbox.trace"); f != nil {
|
||||
Trace, _ = strconv.ParseBool(f.Value.String())
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
sessions: make(map[uint64]*session),
|
||||
schemes: make(map[string]FileHandler),
|
||||
chmod: os.Chmod,
|
||||
chown: os.Chown,
|
||||
}
|
||||
|
||||
s.handlers = map[int32]func(*Packet) (interface{}, error){
|
||||
OpCreateSessionV4: s.CreateSessionV4,
|
||||
OpDestroySessionV4: s.DestroySessionV4,
|
||||
OpGetattrV2: s.GetattrV2,
|
||||
OpSetattrV2: s.SetattrV2,
|
||||
OpOpen: s.Open,
|
||||
OpClose: s.Close,
|
||||
OpOpenV3: s.OpenV3,
|
||||
OpReadV3: s.ReadV3,
|
||||
OpWriteV3: s.WriteV3,
|
||||
}
|
||||
|
||||
for op := range s.handlers {
|
||||
s.Capabilities = append(s.Capabilities, Capability{Op: op, Flags: 0x1})
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// RegisterFileHandler enables dispatch to handler for the given scheme.
|
||||
func (s *Server) RegisterFileHandler(scheme string, handler FileHandler) {
|
||||
if handler == nil {
|
||||
delete(s.schemes, scheme)
|
||||
return
|
||||
}
|
||||
s.schemes[scheme] = handler
|
||||
}
|
||||
|
||||
// Dispatch unpacks the given request packet and dispatches to the appropriate handler
|
||||
func (s *Server) Dispatch(packet []byte) ([]byte, error) {
|
||||
req := &Packet{}
|
||||
|
||||
err := req.UnmarshalBinary(packet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if Trace {
|
||||
fmt.Fprintf(os.Stderr, "[hgfs] request %#v\n", req.Header)
|
||||
}
|
||||
|
||||
var res interface{}
|
||||
|
||||
handler, ok := s.handlers[req.Op]
|
||||
if ok {
|
||||
res, err = handler(req)
|
||||
} else {
|
||||
err = &Status{
|
||||
Code: StatusOperationNotSupported,
|
||||
Err: fmt.Errorf("unsupported Op(%d)", req.Op),
|
||||
}
|
||||
}
|
||||
|
||||
return req.Reply(res, err)
|
||||
}
|
||||
|
||||
// File interface abstracts standard i/o methods to support transfer
|
||||
// of regular files and archives of directories.
|
||||
type File interface {
|
||||
io.Reader
|
||||
io.Writer
|
||||
io.Closer
|
||||
|
||||
Name() string
|
||||
}
|
||||
|
||||
// FileHandler is the plugin interface for hgfs file transport.
|
||||
type FileHandler interface {
|
||||
Stat(*url.URL) (os.FileInfo, error)
|
||||
Open(*url.URL, int32) (File, error)
|
||||
}
|
||||
|
||||
// urlParse attempts to convert the given name to a URL with scheme for use as FileHandler dispatch.
|
||||
func urlParse(name string) *url.URL {
|
||||
var info os.FileInfo
|
||||
|
||||
u, err := url.Parse(name)
|
||||
if err == nil && u.Scheme == "" {
|
||||
info, err = os.Stat(u.Path)
|
||||
if err == nil && info.IsDir() {
|
||||
u.Scheme = ArchiveScheme // special case for IsDir()
|
||||
return u
|
||||
}
|
||||
}
|
||||
|
||||
u, err = url.Parse(strings.TrimPrefix(name, "/")) // must appear to be an absolute path or hgfs errors
|
||||
if err != nil {
|
||||
u = &url.URL{Path: name}
|
||||
}
|
||||
|
||||
if u.Scheme == "" {
|
||||
ix := strings.Index(u.Path, "/")
|
||||
if ix > 0 {
|
||||
u.Scheme = u.Path[:ix]
|
||||
u.Path = u.Path[ix:]
|
||||
}
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// OpenFile selects the File implementation based on file type and mode.
|
||||
func (s *Server) OpenFile(name string, mode int32) (File, error) {
|
||||
u := urlParse(name)
|
||||
|
||||
if h, ok := s.schemes[u.Scheme]; ok {
|
||||
f, serr := h.Open(u, mode)
|
||||
if serr != os.ErrNotExist {
|
||||
return f, serr
|
||||
}
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case OpenModeReadOnly:
|
||||
return os.Open(name)
|
||||
case OpenModeWriteOnly:
|
||||
flag := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
|
||||
return os.OpenFile(name, flag, 0600)
|
||||
default:
|
||||
return nil, &Status{
|
||||
Err: fmt.Errorf("open mode(%d) not supported for file %q", mode, name),
|
||||
Code: StatusAccessDenied,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stat wraps os.Stat such that we can report directory types as regular files to support archive streaming.
|
||||
// In the case of standard vmware-tools, attempts to transfer directories result
|
||||
// with a VIX_E_NOT_A_FILE (see InitiateFileTransfer{To,From}Guest).
|
||||
// Note that callers on the VMX side that reach this path are only concerned with:
|
||||
// - does the file exist?
|
||||
// - size:
|
||||
// + used for UI progress with desktop Drag-N-Drop operations, which toolbox does not support.
|
||||
// + sent to as Content-Length header in response to GET of FileTransferInformation.Url,
|
||||
// if the first ReadV3 size is > HGFS_LARGE_PACKET_MAX
|
||||
func (s *Server) Stat(name string) (os.FileInfo, error) {
|
||||
u := urlParse(name)
|
||||
|
||||
if h, ok := s.schemes[u.Scheme]; ok {
|
||||
sinfo, serr := h.Stat(u)
|
||||
if serr != os.ErrNotExist {
|
||||
return sinfo, serr
|
||||
}
|
||||
}
|
||||
|
||||
return os.Stat(name)
|
||||
}
|
||||
|
||||
type session struct {
|
||||
files map[uint32]File
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// TODO: we currently depend on the VMX to close files and remove sessions,
|
||||
// which it does provided it can communicate with the toolbox. Let's look at
|
||||
// adding session expiration when implementing OpenModeWriteOnly support.
|
||||
func newSession() *session {
|
||||
return &session{
|
||||
files: make(map[uint32]File),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) getSession(p *Packet) (*session, error) {
|
||||
s.mu.Lock()
|
||||
session, ok := s.sessions[p.SessionID]
|
||||
s.mu.Unlock()
|
||||
|
||||
if !ok {
|
||||
return nil, &Status{
|
||||
Code: StatusStaleSession,
|
||||
Err: errors.New("session not found"),
|
||||
}
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (s *Server) removeSession(id uint64) bool {
|
||||
s.mu.Lock()
|
||||
session, ok := s.sessions[id]
|
||||
delete(s.sessions, id)
|
||||
s.mu.Unlock()
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
session.mu.Lock()
|
||||
defer session.mu.Unlock()
|
||||
|
||||
for _, f := range session.files {
|
||||
log.Printf("[hgfs] session %X removed with open file: %s", id, f.Name())
|
||||
_ = f.Close()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// open-vm-tools' session max is 1024, there shouldn't be more than a handful at a given time in our use cases
|
||||
const maxSessions = 24
|
||||
|
||||
// CreateSessionV4 handls OpCreateSessionV4 requests
|
||||
func (s *Server) CreateSessionV4(p *Packet) (interface{}, error) {
|
||||
const SessionMaxPacketSizeValid = 0x1
|
||||
|
||||
req := new(RequestCreateSessionV4)
|
||||
err := UnmarshalBinary(p.Payload, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &ReplyCreateSessionV4{
|
||||
SessionID: uint64(rand.Int63()),
|
||||
NumCapabilities: uint32(len(s.Capabilities)),
|
||||
MaxPacketSize: LargePacketMax,
|
||||
Flags: SessionMaxPacketSizeValid,
|
||||
Capabilities: s.Capabilities,
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if len(s.sessions) > maxSessions {
|
||||
return nil, &Status{Code: StatusTooManySessions}
|
||||
}
|
||||
|
||||
s.sessions[res.SessionID] = newSession()
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// DestroySessionV4 handls OpDestroySessionV4 requests
|
||||
func (s *Server) DestroySessionV4(p *Packet) (interface{}, error) {
|
||||
if s.removeSession(p.SessionID) {
|
||||
return &ReplyDestroySessionV4{}, nil
|
||||
}
|
||||
|
||||
return nil, &Status{Code: StatusStaleSession}
|
||||
}
|
||||
|
||||
// Stat maps os.FileInfo to AttrV2
|
||||
func (a *AttrV2) Stat(info os.FileInfo) {
|
||||
switch {
|
||||
case info.IsDir():
|
||||
a.Type = FileTypeDirectory
|
||||
case info.Mode()&os.ModeSymlink == os.ModeSymlink:
|
||||
a.Type = FileTypeSymlink
|
||||
default:
|
||||
a.Type = FileTypeRegular
|
||||
}
|
||||
|
||||
a.Size = uint64(info.Size())
|
||||
|
||||
a.Mask = AttrValidType | AttrValidSize
|
||||
|
||||
a.sysStat(info)
|
||||
}
|
||||
|
||||
// GetattrV2 handles OpGetattrV2 requests
|
||||
func (s *Server) GetattrV2(p *Packet) (interface{}, error) {
|
||||
res := &ReplyGetattrV2{}
|
||||
|
||||
req := new(RequestGetattrV2)
|
||||
err := UnmarshalBinary(p.Payload, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := req.FileName.Path()
|
||||
info, err := s.Stat(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.Attr.Stat(info)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SetattrV2 handles OpSetattrV2 requests
|
||||
func (s *Server) SetattrV2(p *Packet) (interface{}, error) {
|
||||
res := &ReplySetattrV2{}
|
||||
|
||||
req := new(RequestSetattrV2)
|
||||
err := UnmarshalBinary(p.Payload, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := req.FileName.Path()
|
||||
|
||||
_, err = os.Stat(name)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
// assuming this is a virtual file
|
||||
return res, nil
|
||||
}
|
||||
|
||||
uid := -1
|
||||
if req.Attr.Mask&AttrValidUserID == AttrValidUserID {
|
||||
uid = int(req.Attr.UserID)
|
||||
}
|
||||
|
||||
gid := -1
|
||||
if req.Attr.Mask&AttrValidGroupID == AttrValidGroupID {
|
||||
gid = int(req.Attr.GroupID)
|
||||
}
|
||||
|
||||
err = s.chown(name, uid, gid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var perm os.FileMode
|
||||
|
||||
if req.Attr.Mask&AttrValidOwnerPerms == AttrValidOwnerPerms {
|
||||
perm |= os.FileMode(req.Attr.OwnerPerms) << 6
|
||||
}
|
||||
|
||||
if req.Attr.Mask&AttrValidGroupPerms == AttrValidGroupPerms {
|
||||
perm |= os.FileMode(req.Attr.GroupPerms) << 3
|
||||
}
|
||||
|
||||
if req.Attr.Mask&AttrValidOtherPerms == AttrValidOtherPerms {
|
||||
perm |= os.FileMode(req.Attr.OtherPerms)
|
||||
}
|
||||
|
||||
if perm != 0 {
|
||||
err = s.chmod(name, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Server) newHandle() uint32 {
|
||||
return atomic.AddUint32(&s.handle, 1)
|
||||
}
|
||||
|
||||
// Open handles OpOpen requests
|
||||
func (s *Server) Open(p *Packet) (interface{}, error) {
|
||||
req := new(RequestOpen)
|
||||
err := UnmarshalBinary(p.Payload, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := s.getSession(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := req.FileName.Path()
|
||||
mode := req.OpenMode
|
||||
|
||||
if mode != OpenModeReadOnly {
|
||||
return nil, &Status{
|
||||
Err: fmt.Errorf("open mode(%d) not supported for file %q", mode, name),
|
||||
Code: StatusAccessDenied,
|
||||
}
|
||||
}
|
||||
|
||||
file, err := s.OpenFile(name, mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &ReplyOpen{
|
||||
Handle: s.newHandle(),
|
||||
}
|
||||
|
||||
session.mu.Lock()
|
||||
session.files[res.Handle] = file
|
||||
session.mu.Unlock()
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Close handles OpClose requests
|
||||
func (s *Server) Close(p *Packet) (interface{}, error) {
|
||||
req := new(RequestClose)
|
||||
err := UnmarshalBinary(p.Payload, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := s.getSession(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session.mu.Lock()
|
||||
file, ok := session.files[req.Handle]
|
||||
if ok {
|
||||
delete(session.files, req.Handle)
|
||||
}
|
||||
session.mu.Unlock()
|
||||
|
||||
if ok {
|
||||
err = file.Close()
|
||||
} else {
|
||||
return nil, &Status{Code: StatusInvalidHandle}
|
||||
}
|
||||
|
||||
return &ReplyClose{}, err
|
||||
}
|
||||
|
||||
// OpenV3 handles OpOpenV3 requests
|
||||
func (s *Server) OpenV3(p *Packet) (interface{}, error) {
|
||||
req := new(RequestOpenV3)
|
||||
err := UnmarshalBinary(p.Payload, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := s.getSession(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := req.FileName.Path()
|
||||
|
||||
if req.DesiredLock != LockNone {
|
||||
return nil, &Status{
|
||||
Err: fmt.Errorf("open lock type=%d not supported for file %q", req.DesiredLock, name),
|
||||
Code: StatusOperationNotSupported,
|
||||
}
|
||||
}
|
||||
|
||||
file, err := s.OpenFile(name, req.OpenMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &ReplyOpenV3{
|
||||
Handle: s.newHandle(),
|
||||
}
|
||||
|
||||
session.mu.Lock()
|
||||
session.files[res.Handle] = file
|
||||
session.mu.Unlock()
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ReadV3 handles OpReadV3 requests
|
||||
func (s *Server) ReadV3(p *Packet) (interface{}, error) {
|
||||
req := new(RequestReadV3)
|
||||
err := UnmarshalBinary(p.Payload, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := s.getSession(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session.mu.Lock()
|
||||
file, ok := session.files[req.Handle]
|
||||
session.mu.Unlock()
|
||||
|
||||
if !ok {
|
||||
return nil, &Status{Code: StatusInvalidHandle}
|
||||
}
|
||||
|
||||
buf := make([]byte, req.RequiredSize)
|
||||
|
||||
// Use ReadFull as Read() of an archive io.Pipe may return much smaller chunks,
|
||||
// such as when we've read a tar header.
|
||||
n, err := io.ReadFull(file, buf)
|
||||
if err != nil && n == 0 {
|
||||
if err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
res := &ReplyReadV3{
|
||||
ActualSize: uint32(n),
|
||||
Payload: buf[:n],
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// WriteV3 handles OpWriteV3 requests
|
||||
func (s *Server) WriteV3(p *Packet) (interface{}, error) {
|
||||
req := new(RequestWriteV3)
|
||||
err := UnmarshalBinary(p.Payload, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := s.getSession(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session.mu.Lock()
|
||||
file, ok := session.files[req.Handle]
|
||||
session.mu.Unlock()
|
||||
|
||||
if !ok {
|
||||
return nil, &Status{Code: StatusInvalidHandle}
|
||||
}
|
||||
|
||||
n, err := file.Write(req.Payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := &ReplyWriteV3{
|
||||
ActualSize: uint32(n),
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
443
vendor/github.com/vmware/govmomi/toolbox/hgfs/server_test.go
generated
vendored
Normal file
443
vendor/github.com/vmware/govmomi/toolbox/hgfs/server_test.go
generated
vendored
Normal file
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hgfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
s *Server
|
||||
SessionID uint64
|
||||
}
|
||||
|
||||
func NewClient() *Client {
|
||||
s := NewServer()
|
||||
|
||||
return &Client{
|
||||
s: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Dispatch(op int32, req interface{}, res interface{}) *Packet {
|
||||
var err error
|
||||
p := new(Packet)
|
||||
p.Payload, err = MarshalBinary(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p.Header.Version = 0x1
|
||||
p.Header.Dummy = OpNewHeader
|
||||
p.Header.HeaderSize = headerSize
|
||||
p.Header.PacketSize = headerSize + uint32(len(p.Payload))
|
||||
p.Header.SessionID = c.SessionID
|
||||
p.Header.Op = op
|
||||
|
||||
data, err := p.MarshalBinary()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
data, err = c.s.Dispatch(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p = new(Packet)
|
||||
err = p.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if p.Status == StatusSuccess {
|
||||
err = UnmarshalBinary(p.Payload, res)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (c *Client) CreateSession() uint32 {
|
||||
req := new(RequestCreateSessionV4)
|
||||
res := new(ReplyCreateSessionV4)
|
||||
|
||||
p := c.Dispatch(OpCreateSessionV4, req, res)
|
||||
|
||||
if p.Status == StatusSuccess {
|
||||
c.SessionID = res.SessionID
|
||||
}
|
||||
|
||||
return p.Status
|
||||
}
|
||||
|
||||
func (c *Client) DestroySession() uint32 {
|
||||
req := new(RequestDestroySessionV4)
|
||||
res := new(ReplyDestroySessionV4)
|
||||
|
||||
return c.Dispatch(OpDestroySessionV4, req, res).Status
|
||||
}
|
||||
|
||||
func (c *Client) GetAttr(name string) (*AttrV2, uint32) {
|
||||
req := new(RequestGetattrV2)
|
||||
res := new(ReplyGetattrV2)
|
||||
|
||||
req.FileName.FromString(name)
|
||||
|
||||
p := c.Dispatch(OpGetattrV2, req, res)
|
||||
|
||||
if p.Status != StatusSuccess {
|
||||
return nil, p.Status
|
||||
}
|
||||
|
||||
return &res.Attr, p.Status
|
||||
}
|
||||
|
||||
func (c *Client) SetAttr(name string, attr AttrV2) uint32 {
|
||||
req := new(RequestSetattrV2)
|
||||
res := new(ReplySetattrV2)
|
||||
|
||||
req.FileName.FromString(name)
|
||||
|
||||
req.Attr = attr
|
||||
|
||||
p := c.Dispatch(OpSetattrV2, req, res)
|
||||
|
||||
if p.Status != StatusSuccess {
|
||||
return p.Status
|
||||
}
|
||||
|
||||
return p.Status
|
||||
}
|
||||
|
||||
func (c *Client) Open(name string, write ...bool) (uint32, uint32) {
|
||||
req := new(RequestOpen)
|
||||
res := new(ReplyOpen)
|
||||
|
||||
if len(write) == 1 && write[0] {
|
||||
req.OpenMode = OpenModeWriteOnly
|
||||
}
|
||||
|
||||
req.FileName.FromString(name)
|
||||
|
||||
p := c.Dispatch(OpOpen, req, res)
|
||||
if p.Status != StatusSuccess {
|
||||
return 0, p.Status
|
||||
}
|
||||
|
||||
return res.Handle, p.Status
|
||||
}
|
||||
|
||||
func (c *Client) OpenWrite(name string) (uint32, uint32) {
|
||||
req := new(RequestOpenV3)
|
||||
res := new(ReplyOpenV3)
|
||||
|
||||
req.OpenMode = OpenModeWriteOnly
|
||||
req.OpenFlags = OpenCreateEmpty
|
||||
req.FileName.FromString(name)
|
||||
|
||||
p := c.Dispatch(OpOpenV3, req, res)
|
||||
if p.Status != StatusSuccess {
|
||||
return 0, p.Status
|
||||
}
|
||||
|
||||
// cover the unsupported lock type path
|
||||
req.DesiredLock = LockOpportunistic
|
||||
status := c.Dispatch(OpOpenV3, req, res).Status
|
||||
if status != StatusOperationNotSupported {
|
||||
return 0, status
|
||||
}
|
||||
|
||||
// cover the unsupported open mode path
|
||||
req.DesiredLock = LockNone
|
||||
req.OpenMode = OpenCreateSafe
|
||||
status = c.Dispatch(OpOpenV3, req, res).Status
|
||||
if status != StatusAccessDenied {
|
||||
return 0, status
|
||||
}
|
||||
|
||||
return res.Handle, p.Status
|
||||
}
|
||||
|
||||
func (c *Client) Close(handle uint32) uint32 {
|
||||
req := new(RequestClose)
|
||||
res := new(ReplyClose)
|
||||
|
||||
req.Handle = handle
|
||||
|
||||
return c.Dispatch(OpClose, req, res).Status
|
||||
}
|
||||
|
||||
func TestStaleSession(t *testing.T) {
|
||||
c := NewClient()
|
||||
|
||||
// list of methods that can return StatusStaleSession
|
||||
invalid := []func() uint32{
|
||||
func() uint32 { _, status := c.Open("enoent"); return status },
|
||||
func() uint32 { return c.Dispatch(OpReadV3, new(RequestReadV3), new(ReplyReadV3)).Status },
|
||||
func() uint32 { return c.Dispatch(OpWriteV3, new(RequestWriteV3), new(ReplyWriteV3)).Status },
|
||||
func() uint32 { return c.Close(0) },
|
||||
c.DestroySession,
|
||||
}
|
||||
|
||||
for i, f := range invalid {
|
||||
status := f()
|
||||
if status != StatusStaleSession {
|
||||
t.Errorf("%d: status=%d", i, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionMax(t *testing.T) {
|
||||
c := NewClient()
|
||||
var status uint32
|
||||
|
||||
for i := 0; i <= maxSessions+1; i++ {
|
||||
status = c.CreateSession()
|
||||
}
|
||||
|
||||
if status != StatusTooManySessions {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionDestroy(t *testing.T) {
|
||||
Trace = true
|
||||
c := NewClient()
|
||||
c.CreateSession()
|
||||
_, status := c.Open("/etc/resolv.conf")
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
c.DestroySession()
|
||||
|
||||
if c.s.removeSession(c.SessionID) {
|
||||
t.Error("session was not removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidOp(t *testing.T) {
|
||||
c := NewClient()
|
||||
status := c.Dispatch(1024, new(RequestClose), new(ReplyClose)).Status
|
||||
if status != StatusOperationNotSupported {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadV3(t *testing.T) {
|
||||
Trace = testing.Verbose()
|
||||
|
||||
c := NewClient()
|
||||
c.CreateSession()
|
||||
|
||||
_, status := c.GetAttr("enoent")
|
||||
|
||||
if status != StatusNoSuchFileOrDir {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
_, status = c.Open("enoent")
|
||||
if status != StatusNoSuchFileOrDir {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
fname := "/etc/resolv.conf"
|
||||
|
||||
attr, _ := c.GetAttr(path.Dir(fname))
|
||||
if attr.Type != FileTypeDirectory {
|
||||
t.Errorf("type=%d", attr.Type)
|
||||
}
|
||||
|
||||
attr, _ = c.GetAttr(fname)
|
||||
if attr.Type != FileTypeRegular {
|
||||
t.Errorf("type=%d", attr.Type)
|
||||
}
|
||||
|
||||
if attr.Size <= 0 {
|
||||
t.Errorf("size=%d", attr.Size)
|
||||
}
|
||||
|
||||
handle, status := c.Open(fname)
|
||||
if status != StatusSuccess {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
var req *RequestReadV3
|
||||
var offset uint64
|
||||
size := uint32(attr.Size / 2)
|
||||
|
||||
for offset = 0; offset < attr.Size; {
|
||||
req = &RequestReadV3{
|
||||
Offset: offset,
|
||||
Handle: handle,
|
||||
RequiredSize: size,
|
||||
}
|
||||
|
||||
res := new(ReplyReadV3)
|
||||
|
||||
status = c.Dispatch(OpReadV3, req, res).Status
|
||||
|
||||
if status != StatusSuccess {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
if Trace {
|
||||
fmt.Fprintf(os.Stderr, "read %d: %q\n", res.ActualSize, string(res.Payload))
|
||||
}
|
||||
|
||||
offset += uint64(res.ActualSize)
|
||||
}
|
||||
|
||||
if uint64(offset) != attr.Size {
|
||||
t.Errorf("size %d vs %d", offset, attr.Size)
|
||||
}
|
||||
|
||||
status = c.Dispatch(OpReadV3, new(RequestReadV3), new(ReplyReadV3)).Status
|
||||
if status != StatusInvalidHandle {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
status = c.Close(0)
|
||||
if status != StatusInvalidHandle {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
status = c.Close(handle)
|
||||
if status != StatusSuccess {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
status = c.DestroySession()
|
||||
if status != StatusSuccess {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteV3(t *testing.T) {
|
||||
Trace = testing.Verbose()
|
||||
|
||||
f, err := ioutil.TempFile("", "toolbox")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
name := f.Name()
|
||||
|
||||
c := NewClient()
|
||||
c.CreateSession()
|
||||
|
||||
_, status := c.Open("enoent", true)
|
||||
// write not supported yet
|
||||
if status != StatusAccessDenied {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
handle, status := c.OpenWrite(name)
|
||||
if status != StatusSuccess {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
payload := []byte("one two three\n")
|
||||
size := uint32(len(payload))
|
||||
|
||||
req := &RequestWriteV3{
|
||||
Handle: handle,
|
||||
WriteFlags: WriteAppend,
|
||||
Offset: 0,
|
||||
RequiredSize: size,
|
||||
Payload: payload,
|
||||
}
|
||||
|
||||
res := new(ReplyReadV3)
|
||||
|
||||
status = c.Dispatch(OpWriteV3, req, res).Status
|
||||
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
if size != res.ActualSize {
|
||||
t.Errorf("%d vs %d", size, res.ActualSize)
|
||||
}
|
||||
|
||||
status = c.Dispatch(OpWriteV3, new(RequestWriteV3), new(ReplyWriteV3)).Status
|
||||
if status != StatusInvalidHandle {
|
||||
t.Fatalf("status=%d", status)
|
||||
}
|
||||
|
||||
status = c.Close(handle)
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
attr, _ := c.GetAttr(name)
|
||||
if attr.Size != uint64(size) {
|
||||
t.Errorf("%d vs %d", size, attr.Size)
|
||||
}
|
||||
|
||||
attr.OwnerPerms |= PermExec
|
||||
|
||||
errors := []struct {
|
||||
err error
|
||||
status uint32
|
||||
}{
|
||||
{os.ErrPermission, StatusOperationNotPermitted},
|
||||
{os.ErrNotExist, StatusNoSuchFileOrDir},
|
||||
{os.ErrExist, StatusFileExists},
|
||||
{nil, StatusSuccess},
|
||||
}
|
||||
|
||||
for _, e := range errors {
|
||||
c.s.chown = func(_ string, _ int, _ int) error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
status = c.SetAttr(name, *attr)
|
||||
if status != e.status {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
}
|
||||
|
||||
c.s.chown = func(_ string, _ int, _ int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, e := range errors {
|
||||
c.s.chmod = func(_ string, _ os.FileMode) error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
status = c.SetAttr(name, *attr)
|
||||
if status != e.status {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
}
|
||||
|
||||
status = c.DestroySession()
|
||||
if status != StatusSuccess {
|
||||
t.Errorf("status=%d", status)
|
||||
}
|
||||
|
||||
_ = os.Remove(name)
|
||||
}
|
||||
118
vendor/github.com/vmware/govmomi/toolbox/power.go
generated
vendored
Normal file
118
vendor/github.com/vmware/govmomi/toolbox/power.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// GuestOsState enum as defined in open-vm-tools/lib/include/vmware/guestrpc/powerops.h
|
||||
const (
|
||||
_ = iota
|
||||
powerStateHalt
|
||||
powerStateReboot
|
||||
powerStatePowerOn
|
||||
powerStateResume
|
||||
powerStateSuspend
|
||||
)
|
||||
|
||||
var (
|
||||
shutdown = "/sbin/shutdown"
|
||||
)
|
||||
|
||||
type PowerCommand struct {
|
||||
Handler func() error
|
||||
|
||||
out *ChannelOut
|
||||
state int
|
||||
name string
|
||||
}
|
||||
|
||||
type PowerCommandHandler struct {
|
||||
Halt PowerCommand
|
||||
Reboot PowerCommand
|
||||
PowerOn PowerCommand
|
||||
Resume PowerCommand
|
||||
Suspend PowerCommand
|
||||
}
|
||||
|
||||
func registerPowerCommandHandler(service *Service) *PowerCommandHandler {
|
||||
handler := new(PowerCommandHandler)
|
||||
|
||||
handlers := map[string]struct {
|
||||
cmd *PowerCommand
|
||||
state int
|
||||
}{
|
||||
"OS_Halt": {&handler.Halt, powerStateHalt},
|
||||
"OS_Reboot": {&handler.Reboot, powerStateReboot},
|
||||
"OS_PowerOn": {&handler.PowerOn, powerStatePowerOn},
|
||||
"OS_Resume": {&handler.Resume, powerStateResume},
|
||||
"OS_Suspend": {&handler.Suspend, powerStateSuspend},
|
||||
}
|
||||
|
||||
for name, h := range handlers {
|
||||
*h.cmd = PowerCommand{
|
||||
name: name,
|
||||
state: h.state,
|
||||
out: service.out,
|
||||
}
|
||||
|
||||
service.RegisterHandler(name, h.cmd.Dispatch)
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func (c *PowerCommand) Dispatch([]byte) ([]byte, error) {
|
||||
rc := rpciOK
|
||||
|
||||
log.Printf("dispatching power op %q", c.name)
|
||||
|
||||
if c.Handler == nil {
|
||||
if c.state == powerStateHalt || c.state == powerStateReboot {
|
||||
rc = rpciERR
|
||||
}
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("tools.os.statechange.status %s%d\x00", rc, c.state)
|
||||
|
||||
if _, err := c.out.Request([]byte(msg)); err != nil {
|
||||
log.Printf("unable to send %q: %q", msg, err)
|
||||
}
|
||||
|
||||
if c.Handler != nil {
|
||||
if err := c.Handler(); err != nil {
|
||||
log.Printf("%s: %s", c.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func Halt() error {
|
||||
log.Printf("Halting system...")
|
||||
// #nosec: Subprocess launching with variable
|
||||
return exec.Command(shutdown, "-h", "now").Run()
|
||||
}
|
||||
|
||||
func Reboot() error {
|
||||
log.Printf("Rebooting system...")
|
||||
// #nosec: Subprocess launching with variable
|
||||
return exec.Command(shutdown, "-r", "now").Run()
|
||||
}
|
||||
53
vendor/github.com/vmware/govmomi/toolbox/power_test.go
generated
vendored
Normal file
53
vendor/github.com/vmware/govmomi/toolbox/power_test.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPowerCommandHandler(t *testing.T) {
|
||||
shutdown = "/bin/echo"
|
||||
|
||||
in := new(mockChannelIn)
|
||||
out := new(mockChannelOut)
|
||||
|
||||
service := NewService(in, out)
|
||||
power := service.Power
|
||||
|
||||
// cover nil Handler and out.Receive paths
|
||||
_, _ = power.Halt.Dispatch(nil)
|
||||
|
||||
out.reply = append(out.reply, rpciOK, rpciOK)
|
||||
|
||||
power.Halt.Handler = Halt
|
||||
power.Reboot.Handler = Reboot
|
||||
power.Suspend.Handler = func() error {
|
||||
return errors.New("an error")
|
||||
}
|
||||
|
||||
commands := []PowerCommand{
|
||||
power.Halt,
|
||||
power.Reboot,
|
||||
power.Suspend,
|
||||
}
|
||||
|
||||
for _, cmd := range commands {
|
||||
_, _ = cmd.Dispatch(nil)
|
||||
}
|
||||
}
|
||||
630
vendor/github.com/vmware/govmomi/toolbox/process.go
generated
vendored
Normal file
630
vendor/github.com/vmware/govmomi/toolbox/process.go
generated
vendored
Normal file
@@ -0,0 +1,630 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox/hgfs"
|
||||
"github.com/vmware/govmomi/toolbox/vix"
|
||||
)
|
||||
|
||||
var (
|
||||
xmlEscape *strings.Replacer
|
||||
|
||||
shell = "/bin/sh"
|
||||
|
||||
defaultOwner = os.Getenv("USER")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// See: VixToolsEscapeXMLString
|
||||
chars := []string{
|
||||
`"`,
|
||||
"%",
|
||||
"&",
|
||||
"'",
|
||||
"<",
|
||||
">",
|
||||
}
|
||||
|
||||
replace := make([]string, 0, len(chars)*2)
|
||||
|
||||
for _, c := range chars {
|
||||
replace = append(replace, c)
|
||||
replace = append(replace, url.QueryEscape(c))
|
||||
}
|
||||
|
||||
xmlEscape = strings.NewReplacer(replace...)
|
||||
|
||||
// See procMgrPosix.c:ProcMgrStartProcess:
|
||||
// Prefer bash -c as is uses exec() to replace itself,
|
||||
// whereas bourne shell does a fork & exec, so two processes are started.
|
||||
if sh, err := exec.LookPath("bash"); err != nil {
|
||||
shell = sh
|
||||
}
|
||||
|
||||
if defaultOwner == "" {
|
||||
defaultOwner = "toolbox"
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessIO encapsulates IO for Go functions and OS commands such that they can interact via the OperationsManager
|
||||
// without file system disk IO.
|
||||
type ProcessIO struct {
|
||||
In struct {
|
||||
io.Writer
|
||||
io.Reader
|
||||
io.Closer // Closer for the write side of the pipe, can be closed via hgfs ops (FileTranfserToGuest)
|
||||
}
|
||||
|
||||
Out *bytes.Buffer
|
||||
Err *bytes.Buffer
|
||||
}
|
||||
|
||||
// ProcessState is the toolbox representation of the GuestProcessInfo type
|
||||
type ProcessState struct {
|
||||
Name string
|
||||
Args string
|
||||
Owner string
|
||||
Pid int64
|
||||
ExitCode int32
|
||||
StartTime int64
|
||||
EndTime int64
|
||||
|
||||
IO *ProcessIO
|
||||
}
|
||||
|
||||
// WithIO enables toolbox Process IO without file system disk IO.
|
||||
func (p *Process) WithIO() *Process {
|
||||
p.IO = &ProcessIO{
|
||||
Out: new(bytes.Buffer),
|
||||
Err: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// ProcessFile implements the os.FileInfo interface to enable toolbox interaction with virtual files.
|
||||
type ProcessFile struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
io.Closer
|
||||
|
||||
name string
|
||||
size int
|
||||
}
|
||||
|
||||
// Name implementation of the os.FileInfo interface method.
|
||||
func (a *ProcessFile) Name() string {
|
||||
return a.name
|
||||
}
|
||||
|
||||
// Size implementation of the os.FileInfo interface method.
|
||||
func (a *ProcessFile) Size() int64 {
|
||||
return int64(a.size)
|
||||
}
|
||||
|
||||
// Mode implementation of the os.FileInfo interface method.
|
||||
func (a *ProcessFile) Mode() os.FileMode {
|
||||
if strings.HasSuffix(a.name, "stdin") {
|
||||
return 0200
|
||||
}
|
||||
return 0400
|
||||
}
|
||||
|
||||
// ModTime implementation of the os.FileInfo interface method.
|
||||
func (a *ProcessFile) ModTime() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// IsDir implementation of the os.FileInfo interface method.
|
||||
func (a *ProcessFile) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Sys implementation of the os.FileInfo interface method.
|
||||
func (a *ProcessFile) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ProcessState) toXML() string {
|
||||
const format = "<proc>" +
|
||||
"<cmd>%s</cmd>" +
|
||||
"<name>%s</name>" +
|
||||
"<pid>%d</pid>" +
|
||||
"<user>%s</user>" +
|
||||
"<start>%d</start>" +
|
||||
"<eCode>%d</eCode>" +
|
||||
"<eTime>%d</eTime>" +
|
||||
"</proc>"
|
||||
|
||||
name := filepath.Base(s.Name)
|
||||
|
||||
argv := []string{s.Name}
|
||||
|
||||
if len(s.Args) != 0 {
|
||||
argv = append(argv, xmlEscape.Replace(s.Args))
|
||||
}
|
||||
|
||||
args := strings.Join(argv, " ")
|
||||
|
||||
exit := atomic.LoadInt32(&s.ExitCode)
|
||||
end := atomic.LoadInt64(&s.EndTime)
|
||||
|
||||
return fmt.Sprintf(format, name, args, s.Pid, s.Owner, s.StartTime, exit, end)
|
||||
}
|
||||
|
||||
// Process managed by the ProcessManager.
|
||||
type Process struct {
|
||||
ProcessState
|
||||
|
||||
Start func(*Process, *vix.StartProgramRequest) (int64, error)
|
||||
Wait func() error
|
||||
Kill context.CancelFunc
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// ProcessError can be returned by the Process.Wait function to propagate ExitCode to ProcessState.
|
||||
type ProcessError struct {
|
||||
Err error
|
||||
ExitCode int32
|
||||
}
|
||||
|
||||
func (e *ProcessError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// ProcessManager manages processes within the guest.
|
||||
// See: http://pubs.vmware.com/vsphere-60/topic/com.vmware.wssdk.apiref.doc/vim.vm.guest.ProcessManager.html
|
||||
type ProcessManager struct {
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
expire time.Duration
|
||||
entries map[int64]*Process
|
||||
pids sync.Pool
|
||||
}
|
||||
|
||||
// NewProcessManager creates a new ProcessManager instance.
|
||||
func NewProcessManager() *ProcessManager {
|
||||
// We use pseudo PIDs that don't conflict with OS PIDs, so they can live in the same table.
|
||||
// For the pseudo PIDs, we use a sync.Pool rather than a plain old counter to avoid the unlikely,
|
||||
// but possible wrapping should such a counter exceed MaxInt64.
|
||||
pid := int64(32768) // TODO: /proc/sys/kernel/pid_max
|
||||
|
||||
return &ProcessManager{
|
||||
expire: time.Minute * 5,
|
||||
entries: make(map[int64]*Process),
|
||||
pids: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return atomic.AddInt64(&pid, 1)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start calls the Process.Start function, returning the pid on success or an error.
|
||||
// A goroutine is started that calls the Process.Wait function. After Process.Wait has
|
||||
// returned, the ProcessState EndTime and ExitCode fields are set. The process state can be
|
||||
// queried via ListProcessesInGuest until it is removed, 5 minutes after Wait returns.
|
||||
func (m *ProcessManager) Start(r *vix.StartProgramRequest, p *Process) (int64, error) {
|
||||
p.Name = r.ProgramPath
|
||||
p.Args = r.Arguments
|
||||
|
||||
// Owner is cosmetic, but useful for example with: govc guest.ps -U $uid
|
||||
if p.Owner == "" {
|
||||
p.Owner = defaultOwner
|
||||
}
|
||||
|
||||
p.StartTime = time.Now().Unix()
|
||||
|
||||
p.ctx, p.Kill = context.WithCancel(context.Background())
|
||||
|
||||
pid, err := p.Start(p, r)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if pid == 0 {
|
||||
p.Pid = m.pids.Get().(int64) // pseudo pid for funcs
|
||||
} else {
|
||||
p.Pid = pid
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.entries[p.Pid] = p
|
||||
m.mu.Unlock()
|
||||
|
||||
m.wg.Add(1)
|
||||
go func() {
|
||||
werr := p.Wait()
|
||||
|
||||
atomic.StoreInt64(&p.EndTime, time.Now().Unix())
|
||||
|
||||
if werr != nil {
|
||||
rc := int32(1)
|
||||
if xerr, ok := werr.(*ProcessError); ok {
|
||||
rc = xerr.ExitCode
|
||||
}
|
||||
|
||||
atomic.StoreInt32(&p.ExitCode, rc)
|
||||
}
|
||||
|
||||
m.wg.Done()
|
||||
p.Kill() // cancel context for those waiting on p.ctx.Done()
|
||||
|
||||
// See: http://pubs.vmware.com/vsphere-65/topic/com.vmware.wssdk.apiref.doc/vim.vm.guest.ProcessManager.ProcessInfo.html
|
||||
// "If the process was started using StartProgramInGuest then the process completion time
|
||||
// will be available if queried within 5 minutes after it completes."
|
||||
<-time.After(m.expire)
|
||||
|
||||
m.mu.Lock()
|
||||
delete(m.entries, p.Pid)
|
||||
m.mu.Unlock()
|
||||
|
||||
if pid == 0 {
|
||||
m.pids.Put(p.Pid) // pseudo pid can be reused now
|
||||
}
|
||||
}()
|
||||
|
||||
return p.Pid, nil
|
||||
}
|
||||
|
||||
// Kill cancels the Process Context.
|
||||
// Returns true if pid exists in the process table, false otherwise.
|
||||
func (m *ProcessManager) Kill(pid int64) bool {
|
||||
m.mu.Lock()
|
||||
entry, ok := m.entries[pid]
|
||||
m.mu.Unlock()
|
||||
|
||||
if ok {
|
||||
entry.Kill()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ListProcesses marshals the ProcessState for the given pids.
|
||||
// If no pids are specified, all current processes are included.
|
||||
// The return value can be used for responding to a VixMsgListProcessesExRequest.
|
||||
func (m *ProcessManager) ListProcesses(pids []int64) []byte {
|
||||
w := new(bytes.Buffer)
|
||||
|
||||
m.mu.Lock()
|
||||
|
||||
if len(pids) == 0 {
|
||||
for _, p := range m.entries {
|
||||
_, _ = w.WriteString(p.toXML())
|
||||
}
|
||||
} else {
|
||||
for _, id := range pids {
|
||||
p, ok := m.entries[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
_, _ = w.WriteString(p.toXML())
|
||||
}
|
||||
}
|
||||
|
||||
m.mu.Unlock()
|
||||
|
||||
return w.Bytes()
|
||||
}
|
||||
|
||||
type procFileInfo struct {
|
||||
os.FileInfo
|
||||
}
|
||||
|
||||
// Size returns hgfs.LargePacketMax such that InitiateFileTransferFromGuest can download a /proc/ file from the guest.
|
||||
// If we were to return the size '0' here, then a 'Content-Length: 0' header is returned by VC/ESX.
|
||||
func (p procFileInfo) Size() int64 {
|
||||
return hgfs.LargePacketMax // Remember, Sully, when I promised to kill you last? I lied.
|
||||
}
|
||||
|
||||
// Stat implements hgfs.FileHandler.Stat
|
||||
func (m *ProcessManager) Stat(u *url.URL) (os.FileInfo, error) {
|
||||
name := path.Join("/proc", u.Path)
|
||||
|
||||
info, err := os.Stat(name)
|
||||
if err == nil && info.Size() == 0 {
|
||||
// This is a real /proc file
|
||||
return &procFileInfo{info}, nil
|
||||
}
|
||||
|
||||
dir, file := path.Split(u.Path)
|
||||
|
||||
pid, err := strconv.ParseInt(path.Base(dir), 10, 64)
|
||||
if err != nil {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
p := m.entries[pid]
|
||||
m.mu.Unlock()
|
||||
|
||||
if p == nil || p.IO == nil {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
pf := &ProcessFile{
|
||||
name: name,
|
||||
Closer: ioutil.NopCloser(nil), // via hgfs, nop for stdout and stderr
|
||||
}
|
||||
|
||||
var r *bytes.Buffer
|
||||
|
||||
switch file {
|
||||
case "stdin":
|
||||
pf.Writer = p.IO.In.Writer
|
||||
pf.Closer = p.IO.In.Closer
|
||||
return pf, nil
|
||||
case "stdout":
|
||||
r = p.IO.Out
|
||||
case "stderr":
|
||||
r = p.IO.Err
|
||||
default:
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
case <-time.After(time.Second):
|
||||
// The vmx guest RPC calls are queue based, serialized on the vmx side.
|
||||
// There are 5 seconds between "ping" RPC calls and after a few misses,
|
||||
// the vmx considers tools as not running. In this case, the vmx would timeout
|
||||
// a file transfer after 60 seconds.
|
||||
//
|
||||
// vix.FileAccessError is converted to a CannotAccessFile fault,
|
||||
// so the client can choose to retry the transfer in this case.
|
||||
// Would have preferred vix.ObjectIsBusy (EBUSY), but VC/ESX converts that
|
||||
// to a general SystemErrorFault with nothing but a localized string message
|
||||
// to check against: "<reason>vix error codes = (5, 0).</reason>"
|
||||
// Is standard vmware-tools, EACCES is converted to a CannotAccessFile fault.
|
||||
return nil, vix.Error(vix.FileAccessError)
|
||||
}
|
||||
|
||||
pf.Reader = r
|
||||
pf.size = r.Len()
|
||||
|
||||
return pf, nil
|
||||
}
|
||||
|
||||
// Open implements hgfs.FileHandler.Open
|
||||
func (m *ProcessManager) Open(u *url.URL, mode int32) (hgfs.File, error) {
|
||||
info, err := m.Stat(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pinfo, ok := info.(*ProcessFile)
|
||||
|
||||
if !ok {
|
||||
return nil, os.ErrNotExist // fall through to default os.Open
|
||||
}
|
||||
|
||||
switch path.Base(u.Path) {
|
||||
case "stdin":
|
||||
if mode != hgfs.OpenModeWriteOnly {
|
||||
return nil, vix.Error(vix.InvalidArg)
|
||||
}
|
||||
case "stdout", "stderr":
|
||||
if mode != hgfs.OpenModeReadOnly {
|
||||
return nil, vix.Error(vix.InvalidArg)
|
||||
}
|
||||
}
|
||||
|
||||
return pinfo, nil
|
||||
}
|
||||
|
||||
type processFunc struct {
|
||||
wg sync.WaitGroup
|
||||
|
||||
run func(context.Context, string) error
|
||||
|
||||
err error
|
||||
}
|
||||
|
||||
// NewProcessFunc creates a new Process, where the Start function calls the given run function within a goroutine.
|
||||
// The Wait function waits for the goroutine to finish and returns the error returned by run.
|
||||
// The run ctx param may be used to return early via the ProcessManager.Kill method.
|
||||
// The run args command is that of the VixMsgStartProgramRequest.Arguments field.
|
||||
func NewProcessFunc(run func(ctx context.Context, args string) error) *Process {
|
||||
f := &processFunc{run: run}
|
||||
|
||||
return &Process{
|
||||
Start: f.start,
|
||||
Wait: f.wait,
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessFuncIO is the Context key to access optional ProcessIO
|
||||
var ProcessFuncIO = struct {
|
||||
key int
|
||||
}{vix.CommandMagicWord}
|
||||
|
||||
func (f *processFunc) start(p *Process, r *vix.StartProgramRequest) (int64, error) {
|
||||
f.wg.Add(1)
|
||||
|
||||
var c io.Closer
|
||||
|
||||
if p.IO != nil {
|
||||
pr, pw := io.Pipe()
|
||||
|
||||
p.IO.In.Reader, p.IO.In.Writer = pr, pw
|
||||
c, p.IO.In.Closer = pr, pw
|
||||
|
||||
p.ctx = context.WithValue(p.ctx, ProcessFuncIO, p.IO)
|
||||
}
|
||||
|
||||
go func() {
|
||||
f.err = f.run(p.ctx, r.Arguments)
|
||||
|
||||
if p.IO != nil {
|
||||
_ = c.Close()
|
||||
|
||||
if f.err != nil && p.IO.Err.Len() == 0 {
|
||||
p.IO.Err.WriteString(f.err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
f.wg.Done()
|
||||
}()
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (f *processFunc) wait() error {
|
||||
f.wg.Wait()
|
||||
return f.err
|
||||
}
|
||||
|
||||
type processCmd struct {
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
// NewProcess creates a new Process, where the Start function use exec.CommandContext to create and start the process.
|
||||
// The Wait function waits for the process to finish and returns the error returned by exec.Cmd.Wait().
|
||||
// Prior to Wait returning, the exec.Cmd.Wait() error is used to set the ProcessState.ExitCode, if error is of type exec.ExitError.
|
||||
// The ctx param may be used to kill the process via the ProcessManager.Kill method.
|
||||
// The VixMsgStartProgramRequest param fields are mapped to the exec.Cmd counterpart fields.
|
||||
// Processes are started within a sub-shell, allowing for i/o redirection, just as with the C version of vmware-tools.
|
||||
func NewProcess() *Process {
|
||||
c := new(processCmd)
|
||||
|
||||
return &Process{
|
||||
Start: c.start,
|
||||
Wait: c.wait,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *processCmd) start(p *Process, r *vix.StartProgramRequest) (int64, error) {
|
||||
name, err := exec.LookPath(r.ProgramPath)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
// #nosec: Subprocess launching with variable
|
||||
// Note that processCmd is currently used only for testing.
|
||||
c.cmd = exec.CommandContext(p.ctx, shell, "-c", fmt.Sprintf("%s %s", name, r.Arguments))
|
||||
c.cmd.Dir = r.WorkingDir
|
||||
c.cmd.Env = r.EnvVars
|
||||
|
||||
if p.IO != nil {
|
||||
in, perr := c.cmd.StdinPipe()
|
||||
if perr != nil {
|
||||
return -1, perr
|
||||
}
|
||||
|
||||
p.IO.In.Writer = in
|
||||
p.IO.In.Closer = in
|
||||
|
||||
// Note we currently use a Buffer in addition to the os.Pipe so that:
|
||||
// - Stat() can provide a size
|
||||
// - FileTransferFromGuest won't block
|
||||
// - Can't use the exec.Cmd.Std{out,err}Pipe methods since Wait() closes the pipes.
|
||||
// We could use os.Pipe directly, but toolbox needs to take care of closing both ends,
|
||||
// but also need to prevent FileTransferFromGuest from blocking.
|
||||
c.cmd.Stdout = p.IO.Out
|
||||
c.cmd.Stderr = p.IO.Err
|
||||
}
|
||||
|
||||
err = c.cmd.Start()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return int64(c.cmd.Process.Pid), nil
|
||||
}
|
||||
|
||||
func (c *processCmd) wait() error {
|
||||
err := c.cmd.Wait()
|
||||
if err != nil {
|
||||
xerr := &ProcessError{
|
||||
Err: err,
|
||||
ExitCode: 1,
|
||||
}
|
||||
|
||||
if x, ok := err.(*exec.ExitError); ok {
|
||||
if status, ok := x.Sys().(syscall.WaitStatus); ok {
|
||||
xerr.ExitCode = int32(status.ExitStatus())
|
||||
}
|
||||
}
|
||||
|
||||
return xerr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewProcessRoundTrip starts a Go function to implement a toolbox backed http.RoundTripper
|
||||
func NewProcessRoundTrip() *Process {
|
||||
return NewProcessFunc(func(ctx context.Context, host string) error {
|
||||
p, _ := ctx.Value(ProcessFuncIO).(*ProcessIO)
|
||||
|
||||
closers := []io.Closer{p.In.Closer}
|
||||
|
||||
defer func() {
|
||||
for _, c := range closers {
|
||||
_ = c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
c, err := new(net.Dialer).DialContext(ctx, "tcp", host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
closers = append(closers, c)
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
_ = c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = io.Copy(c, p.In.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(p.Out, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}).WithIO()
|
||||
}
|
||||
312
vendor/github.com/vmware/govmomi/toolbox/process_test.go
generated
vendored
Normal file
312
vendor/github.com/vmware/govmomi/toolbox/process_test.go
generated
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox/vix"
|
||||
)
|
||||
|
||||
func TestProcessFunction(t *testing.T) {
|
||||
m := NewProcessManager()
|
||||
var pids []int64
|
||||
|
||||
for i := 0; i <= 2; i++ {
|
||||
r := &vix.StartProgramRequest{
|
||||
ProgramPath: "test",
|
||||
Arguments: strconv.Itoa(i),
|
||||
}
|
||||
|
||||
pid, _ := m.Start(r, NewProcessFunc(func(_ context.Context, arg string) error {
|
||||
rc, _ := strconv.Atoi(arg)
|
||||
if rc == 0 {
|
||||
return nil
|
||||
|
||||
}
|
||||
return &ProcessError{Err: errors.New("fail"), ExitCode: int32(rc)}
|
||||
}))
|
||||
|
||||
if pid == 0 {
|
||||
t.Fatalf("no pid")
|
||||
}
|
||||
|
||||
pids = append(pids, pid)
|
||||
}
|
||||
|
||||
m.wg.Wait()
|
||||
|
||||
_ = m.ListProcesses(pids)
|
||||
|
||||
for i, pid := range pids {
|
||||
p := m.entries[pid]
|
||||
if p.ExitCode != int32(i) {
|
||||
t.Errorf("%d: %d != %d", pid, p.ExitCode, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessCommand(t *testing.T) {
|
||||
m := NewProcessManager()
|
||||
var pids []int64
|
||||
|
||||
for i := 0; i <= 2; i++ {
|
||||
r := &vix.StartProgramRequest{
|
||||
ProgramPath: "/bin/bash",
|
||||
Arguments: fmt.Sprintf(`-c "exit %d"`, i),
|
||||
}
|
||||
|
||||
pid, _ := m.Start(r, NewProcess())
|
||||
pids = append(pids, pid)
|
||||
}
|
||||
|
||||
m.wg.Wait()
|
||||
|
||||
_ = m.ListProcesses(nil)
|
||||
|
||||
for i, pid := range pids {
|
||||
p := m.entries[pid]
|
||||
if p.ExitCode != int32(i) {
|
||||
t.Errorf("%d: %d != %d", pid, p.ExitCode, i)
|
||||
}
|
||||
}
|
||||
|
||||
r := &vix.StartProgramRequest{
|
||||
ProgramPath: shell,
|
||||
}
|
||||
|
||||
shell = "/enoent/enoent"
|
||||
_, err := m.Start(r, NewProcess())
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
shell = r.ProgramPath
|
||||
|
||||
r.ProgramPath = "/enoent/enoent"
|
||||
_, err = m.Start(r, NewProcess())
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessKill(t *testing.T) {
|
||||
m := NewProcessManager()
|
||||
var pids []int64
|
||||
|
||||
procs := []struct {
|
||||
r *vix.StartProgramRequest
|
||||
p *Process
|
||||
}{
|
||||
{
|
||||
&vix.StartProgramRequest{
|
||||
ProgramPath: "test",
|
||||
Arguments: "none",
|
||||
},
|
||||
NewProcessFunc(func(ctx context.Context, _ string) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &ProcessError{Err: ctx.Err(), ExitCode: 42}
|
||||
case <-time.After(time.Minute):
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
{
|
||||
&vix.StartProgramRequest{
|
||||
ProgramPath: "/bin/bash",
|
||||
Arguments: fmt.Sprintf(`-c "while true; do sleep 1; done"`),
|
||||
},
|
||||
NewProcess(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range procs {
|
||||
pid, err := m.Start(test.r, test.p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pids = append(pids, pid)
|
||||
}
|
||||
|
||||
for {
|
||||
b := m.ListProcesses(pids)
|
||||
if bytes.Count(b, []byte("<proc>")) == len(pids) {
|
||||
break
|
||||
}
|
||||
|
||||
<-time.After(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
for _, pid := range pids {
|
||||
if !m.Kill(pid) {
|
||||
t.Errorf("kill %d", pid)
|
||||
}
|
||||
}
|
||||
|
||||
m.wg.Wait()
|
||||
|
||||
for _, pid := range pids {
|
||||
p := m.entries[pid]
|
||||
|
||||
if p.ExitCode == 0 {
|
||||
t.Errorf("%s: exit=%d", p.Name, p.ExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
if m.Kill(-1) {
|
||||
t.Error("kill -1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessRemove(t *testing.T) {
|
||||
m := NewProcessManager()
|
||||
|
||||
m.expire = time.Millisecond
|
||||
|
||||
r := &vix.StartProgramRequest{
|
||||
ProgramPath: "test",
|
||||
}
|
||||
|
||||
pid, _ := m.Start(r, NewProcessFunc(func(_ context.Context, arg string) error {
|
||||
return nil
|
||||
}))
|
||||
|
||||
m.wg.Wait()
|
||||
|
||||
<-time.After(m.expire * 20)
|
||||
// pid should be removed by now
|
||||
b := m.ListProcesses([]int64{pid})
|
||||
if len(b) != 0 {
|
||||
t.Error("expected 0 processes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscapeXML(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{`echo "foo bar" > /dev/null`, "echo %22foo bar%22 %3E /dev/null"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
e := xmlEscape.Replace(test.in)
|
||||
if e != test.out {
|
||||
t.Errorf("%d: %s != %s", i, e, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessError(t *testing.T) {
|
||||
fault := errors.New("fail")
|
||||
var err error
|
||||
|
||||
err = &ProcessError{Err: fault}
|
||||
|
||||
if err.Error() != fault.Error() {
|
||||
t.Fatal()
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessIO(t *testing.T) {
|
||||
m := NewProcessManager()
|
||||
|
||||
r := &vix.StartProgramRequest{
|
||||
ProgramPath: "/bin/date",
|
||||
}
|
||||
|
||||
p := NewProcess().WithIO()
|
||||
|
||||
_, err := m.Start(r, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m.wg.Wait()
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
_, _ = io.Copy(&buf, p.IO.Out)
|
||||
|
||||
if buf.Len() == 0 {
|
||||
t.Error("no data")
|
||||
}
|
||||
}
|
||||
|
||||
type testRoundTripper struct {
|
||||
*Process
|
||||
}
|
||||
|
||||
func (c *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Set("Connection", "close") // we need the server to close the connection after 1 request
|
||||
|
||||
err := req.Write(c.IO.In.Writer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = c.IO.In.Close()
|
||||
|
||||
<-c.ctx.Done()
|
||||
|
||||
return http.ReadResponse(bufio.NewReader(c.IO.Out), req)
|
||||
}
|
||||
|
||||
func TestProcessRoundTripper(t *testing.T) {
|
||||
echo := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.Write(w)
|
||||
}))
|
||||
|
||||
u, _ := url.Parse(echo.URL)
|
||||
|
||||
m := NewProcessManager()
|
||||
|
||||
r := &vix.StartProgramRequest{
|
||||
ProgramPath: "http.RoundTrip",
|
||||
Arguments: u.Host,
|
||||
}
|
||||
|
||||
p := NewProcessRoundTrip()
|
||||
|
||||
_, err := m.Start(r, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := (&http.Client{Transport: &testRoundTripper{p}}).Get(echo.URL)
|
||||
if err != nil {
|
||||
t.Logf("Err: %s", p.IO.Err.String())
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.ContentLength == 0 {
|
||||
t.Errorf("len=%d", res.ContentLength)
|
||||
}
|
||||
}
|
||||
336
vendor/github.com/vmware/govmomi/toolbox/service.go
generated
vendored
Normal file
336
vendor/github.com/vmware/govmomi/toolbox/service.go
generated
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox/hgfs"
|
||||
)
|
||||
|
||||
const (
|
||||
// TOOLS_VERSION_UNMANAGED as defined in open-vm-tools/lib/include/vm_tools_version.h
|
||||
toolsVersionUnmanaged = 0x7fffffff
|
||||
|
||||
// RPCIN_MAX_DELAY as defined in rpcChannelInt.h:
|
||||
maxDelay = 10
|
||||
)
|
||||
|
||||
var (
|
||||
capabilities = []string{
|
||||
// Without tools.set.version, the UI reports Tools are "running", but "not installed"
|
||||
fmt.Sprintf("tools.set.version %d", toolsVersionUnmanaged),
|
||||
|
||||
// Required to invoke guest power operations (shutdown, reboot)
|
||||
"tools.capability.statechange",
|
||||
|
||||
"tools.capability.hgfs_server toolbox 1",
|
||||
}
|
||||
|
||||
netInterfaceAddrs = net.InterfaceAddrs
|
||||
|
||||
// If we have an RPCI send error, the channels will be reset.
|
||||
// open-vm-tools/lib/rpcChannel/rpcChannel.c:RpcChannelCheckReset also backs off in this case
|
||||
resetDelay = time.Duration(500) // 500 * 10ms == 5s
|
||||
)
|
||||
|
||||
// Service receives and dispatches incoming RPC requests from the vmx
|
||||
type Service struct {
|
||||
name string
|
||||
in Channel
|
||||
out *ChannelOut
|
||||
handlers map[string]Handler
|
||||
stop chan struct{}
|
||||
wg *sync.WaitGroup
|
||||
delay time.Duration
|
||||
rpcError bool
|
||||
|
||||
Command *CommandServer
|
||||
Power *PowerCommandHandler
|
||||
|
||||
PrimaryIP func() string
|
||||
}
|
||||
|
||||
// NewService initializes a Service instance
|
||||
func NewService(rpcIn Channel, rpcOut Channel) *Service {
|
||||
s := &Service{
|
||||
name: "toolbox", // Same name used by vmtoolsd
|
||||
in: NewTraceChannel(rpcIn),
|
||||
out: &ChannelOut{NewTraceChannel(rpcOut)},
|
||||
handlers: make(map[string]Handler),
|
||||
wg: new(sync.WaitGroup),
|
||||
stop: make(chan struct{}),
|
||||
|
||||
PrimaryIP: DefaultIP,
|
||||
}
|
||||
|
||||
s.RegisterHandler("reset", s.Reset)
|
||||
s.RegisterHandler("ping", s.Ping)
|
||||
s.RegisterHandler("Set_Option", s.SetOption)
|
||||
s.RegisterHandler("Capabilities_Register", s.CapabilitiesRegister)
|
||||
|
||||
s.Command = registerCommandServer(s)
|
||||
s.Command.FileServer = hgfs.NewServer()
|
||||
s.Command.FileServer.RegisterFileHandler("proc", s.Command.ProcessManager)
|
||||
s.Command.FileServer.RegisterFileHandler(hgfs.ArchiveScheme, hgfs.NewArchiveHandler())
|
||||
|
||||
s.Power = registerPowerCommandHandler(s)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// backoff exponentially increases the RPC poll delay up to maxDelay
|
||||
func (s *Service) backoff() {
|
||||
if s.delay < maxDelay {
|
||||
if s.delay > 0 {
|
||||
d := s.delay * 2
|
||||
if d > s.delay && d < maxDelay {
|
||||
s.delay = d
|
||||
} else {
|
||||
s.delay = maxDelay
|
||||
}
|
||||
} else {
|
||||
s.delay = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) stopChannel() {
|
||||
_ = s.in.Stop()
|
||||
_ = s.out.Stop()
|
||||
}
|
||||
|
||||
func (s *Service) startChannel() error {
|
||||
err := s.in.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.out.Start()
|
||||
}
|
||||
|
||||
func (s *Service) checkReset() error {
|
||||
if s.rpcError {
|
||||
s.stopChannel()
|
||||
err := s.startChannel()
|
||||
if err != nil {
|
||||
s.delay = resetDelay
|
||||
return err
|
||||
}
|
||||
s.rpcError = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start initializes the RPC channels and starts a goroutine to listen for incoming RPC requests
|
||||
func (s *Service) Start() error {
|
||||
err := s.startChannel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
|
||||
// Same polling interval and backoff logic as vmtoolsd.
|
||||
// Required in our case at startup at least, otherwise it is possible
|
||||
// we miss the 1 Capabilities_Register call for example.
|
||||
|
||||
// Note we Send(response) even when nil, to let the VMX know we are here
|
||||
var response []byte
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.stop:
|
||||
s.stopChannel()
|
||||
return
|
||||
case <-time.After(time.Millisecond * 10 * s.delay):
|
||||
if err = s.checkReset(); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
err = s.in.Send(response)
|
||||
response = nil
|
||||
if err != nil {
|
||||
s.delay = resetDelay
|
||||
s.rpcError = true
|
||||
continue
|
||||
}
|
||||
|
||||
request, _ := s.in.Receive()
|
||||
|
||||
if len(request) > 0 {
|
||||
response = s.Dispatch(request)
|
||||
|
||||
s.delay = 0
|
||||
} else {
|
||||
s.backoff()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop cancels the RPC listener routine created via Start
|
||||
func (s *Service) Stop() {
|
||||
close(s.stop)
|
||||
}
|
||||
|
||||
// Wait blocks until Start returns, allowing any current RPC in progress to complete.
|
||||
func (s *Service) Wait() {
|
||||
s.wg.Wait()
|
||||
}
|
||||
|
||||
// Handler is given the raw argument portion of an RPC request and returns a response
|
||||
type Handler func([]byte) ([]byte, error)
|
||||
|
||||
// RegisterHandler for the given RPC name
|
||||
func (s *Service) RegisterHandler(name string, handler Handler) {
|
||||
s.handlers[name] = handler
|
||||
}
|
||||
|
||||
// Dispatch an incoming RPC request to a Handler
|
||||
func (s *Service) Dispatch(request []byte) []byte {
|
||||
msg := bytes.SplitN(request, []byte{' '}, 2)
|
||||
name := msg[0]
|
||||
|
||||
// Trim NULL byte terminator
|
||||
name = bytes.TrimRight(name, "\x00")
|
||||
|
||||
handler, ok := s.handlers[string(name)]
|
||||
|
||||
if !ok {
|
||||
log.Printf("unknown command: %q\n", name)
|
||||
return []byte("Unknown Command")
|
||||
}
|
||||
|
||||
var args []byte
|
||||
if len(msg) == 2 {
|
||||
args = msg[1]
|
||||
}
|
||||
|
||||
response, err := handler(args)
|
||||
if err == nil {
|
||||
response = append([]byte("OK "), response...)
|
||||
} else {
|
||||
log.Printf("error calling %s: %s\n", name, err)
|
||||
response = append([]byte("ERR "), response...)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// Reset is the default Handler for reset requests
|
||||
func (s *Service) Reset([]byte) ([]byte, error) {
|
||||
s.SendGuestInfo() // Send the IP info ASAP
|
||||
|
||||
return []byte("ATR " + s.name), nil
|
||||
}
|
||||
|
||||
// Ping is the default Handler for ping requests
|
||||
func (s *Service) Ping([]byte) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SetOption is the default Handler for Set_Option requests
|
||||
func (s *Service) SetOption(args []byte) ([]byte, error) {
|
||||
opts := bytes.SplitN(args, []byte{' '}, 2)
|
||||
key := string(opts[0])
|
||||
val := string(opts[1])
|
||||
|
||||
if Trace {
|
||||
fmt.Fprintf(os.Stderr, "set option %q=%q\n", key, val)
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "broadcastIP": // TODO: const-ify
|
||||
if val == "1" {
|
||||
ip := s.PrimaryIP()
|
||||
if ip == "" {
|
||||
log.Printf("failed to find primary IP")
|
||||
return nil, nil
|
||||
}
|
||||
msg := fmt.Sprintf("info-set guestinfo.ip %s", ip)
|
||||
_, err := s.out.Request([]byte(msg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.SendGuestInfo()
|
||||
}
|
||||
default:
|
||||
// TODO: handle other options...
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DefaultIP is used by default when responding to a Set_Option broadcastIP request
|
||||
// It can be overridden with the Service.PrimaryIP field
|
||||
func DefaultIP() string {
|
||||
addrs, err := netInterfaceAddrs()
|
||||
if err == nil {
|
||||
for _, addr := range addrs {
|
||||
if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() {
|
||||
if ip.IP.To4() != nil {
|
||||
return ip.IP.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Service) CapabilitiesRegister([]byte) ([]byte, error) {
|
||||
for _, cap := range capabilities {
|
||||
_, err := s.out.Request([]byte(cap))
|
||||
if err != nil {
|
||||
log.Printf("send %q: %s", cap, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *Service) SendGuestInfo() {
|
||||
info := []func() ([]byte, error){
|
||||
GuestInfoNicInfoRequest,
|
||||
}
|
||||
|
||||
for i, r := range info {
|
||||
b, err := r()
|
||||
|
||||
if err == nil {
|
||||
_, err = s.out.Request(b)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("SendGuestInfo %d: %s", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
465
vendor/github.com/vmware/govmomi/toolbox/service_test.go
generated
vendored
Normal file
465
vendor/github.com/vmware/govmomi/toolbox/service_test.go
generated
vendored
Normal file
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox/hgfs"
|
||||
"github.com/vmware/govmomi/toolbox/vix"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
func TestDefaultIP(t *testing.T) {
|
||||
ip := DefaultIP()
|
||||
if ip == "" {
|
||||
t.Error("failed to get a default IP address")
|
||||
}
|
||||
}
|
||||
|
||||
type testRPC struct {
|
||||
cmd string
|
||||
expect string
|
||||
}
|
||||
|
||||
type mockChannelIn struct {
|
||||
t *testing.T
|
||||
service *Service
|
||||
rpc []*testRPC
|
||||
wg sync.WaitGroup
|
||||
start error
|
||||
sendErr int
|
||||
count struct {
|
||||
send int
|
||||
stop int
|
||||
start int
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mockChannelIn) Start() error {
|
||||
c.count.start++
|
||||
return c.start
|
||||
}
|
||||
|
||||
func (c *mockChannelIn) Stop() error {
|
||||
c.count.stop++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockChannelIn) Receive() ([]byte, error) {
|
||||
if len(c.rpc) == 0 {
|
||||
if c.rpc != nil {
|
||||
// All test RPC requests have been consumed
|
||||
c.wg.Done()
|
||||
c.rpc = nil
|
||||
}
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
return []byte(c.rpc[0].cmd), nil
|
||||
}
|
||||
|
||||
func (c *mockChannelIn) Send(buf []byte) error {
|
||||
if c.sendErr > 0 {
|
||||
c.count.send++
|
||||
if c.count.send%c.sendErr == 0 {
|
||||
c.wg.Done()
|
||||
return errors.New("rpci send error")
|
||||
}
|
||||
}
|
||||
|
||||
if buf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
expect := c.rpc[0].expect
|
||||
if string(buf) != expect {
|
||||
c.t.Errorf("expected %q reply for request %q, got: %q", expect, c.rpc[0].cmd, buf)
|
||||
}
|
||||
|
||||
c.rpc = c.rpc[1:]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// discard rpc out for now
|
||||
type mockChannelOut struct {
|
||||
reply [][]byte
|
||||
start error
|
||||
}
|
||||
|
||||
func (c *mockChannelOut) Start() error {
|
||||
return c.start
|
||||
}
|
||||
|
||||
func (c *mockChannelOut) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *mockChannelOut) Receive() ([]byte, error) {
|
||||
if len(c.reply) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
reply := c.reply[0]
|
||||
c.reply = c.reply[1:]
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
func (c *mockChannelOut) Send(buf []byte) error {
|
||||
if len(buf) == 0 {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestServiceRun(t *testing.T) {
|
||||
in := new(mockChannelIn)
|
||||
out := new(mockChannelOut)
|
||||
|
||||
service := NewService(in, out)
|
||||
|
||||
in.rpc = []*testRPC{
|
||||
{"reset", "OK ATR toolbox"},
|
||||
{"ping", "OK "},
|
||||
{"Set_Option synctime 0", "OK "},
|
||||
{"Capabilities_Register", "OK "},
|
||||
{"Set_Option broadcastIP 1", "OK "},
|
||||
}
|
||||
|
||||
in.wg.Add(1)
|
||||
|
||||
// replies to register capabilities
|
||||
for i := 0; i < len(capabilities); i++ {
|
||||
out.reply = append(out.reply, rpciOK)
|
||||
}
|
||||
|
||||
out.reply = append(out.reply,
|
||||
rpciOK, // reply to SendGuestInfo call in Reset()
|
||||
rpciOK, // reply to IP broadcast
|
||||
)
|
||||
|
||||
in.service = service
|
||||
|
||||
in.t = t
|
||||
|
||||
err := service.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
in.wg.Wait()
|
||||
|
||||
service.Stop()
|
||||
service.Wait()
|
||||
|
||||
// verify we don't set delay > maxDelay
|
||||
for i := 0; i <= maxDelay+1; i++ {
|
||||
service.backoff()
|
||||
}
|
||||
|
||||
if service.delay != maxDelay {
|
||||
t.Errorf("delay=%d", service.delay)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceErrors(t *testing.T) {
|
||||
Trace = true
|
||||
if !testing.Verbose() {
|
||||
// cover TraceChannel but discard output
|
||||
traceLog = ioutil.Discard
|
||||
}
|
||||
|
||||
netInterfaceAddrs = func() ([]net.Addr, error) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
in := new(mockChannelIn)
|
||||
out := new(mockChannelOut)
|
||||
|
||||
service := NewService(in, out)
|
||||
|
||||
service.RegisterHandler("Sorry", func([]byte) ([]byte, error) {
|
||||
return nil, errors.New("i am so sorry")
|
||||
})
|
||||
|
||||
ip := ""
|
||||
service.PrimaryIP = func() string {
|
||||
if ip == "" {
|
||||
ip = "127"
|
||||
} else if ip == "127" {
|
||||
ip = "127.0.0.1"
|
||||
} else if ip == "127.0.0.1" {
|
||||
ip = ""
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
in.rpc = []*testRPC{
|
||||
{"Capabilities_Register", "OK "},
|
||||
{"Set_Option broadcastIP 1", "ERR "},
|
||||
{"Set_Option broadcastIP 1", "OK "},
|
||||
{"Set_Option broadcastIP 1", "OK "},
|
||||
{"NOPE", "Unknown Command"},
|
||||
{"Sorry", "ERR "},
|
||||
}
|
||||
|
||||
in.wg.Add(1)
|
||||
|
||||
// replies to register capabilities
|
||||
for i := 0; i < len(capabilities); i++ {
|
||||
out.reply = append(out.reply, rpciERR)
|
||||
}
|
||||
|
||||
foo := []byte("foo")
|
||||
out.reply = append(
|
||||
out.reply,
|
||||
rpciERR,
|
||||
rpciOK,
|
||||
rpciOK,
|
||||
append(rpciOK, foo...),
|
||||
rpciERR,
|
||||
)
|
||||
|
||||
in.service = service
|
||||
|
||||
in.t = t
|
||||
|
||||
err := service.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
in.wg.Wait()
|
||||
|
||||
// Done serving RPCs, test ChannelOut errors
|
||||
reply, err := service.out.Request(rpciOK)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(reply, foo) {
|
||||
t.Errorf("reply=%q", foo)
|
||||
}
|
||||
|
||||
_, err = service.out.Request(rpciOK)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
|
||||
_, err = service.out.Request(nil)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
|
||||
service.Stop()
|
||||
service.Wait()
|
||||
|
||||
// cover service start error paths
|
||||
start := errors.New("fail")
|
||||
|
||||
in.start = start
|
||||
err = service.Start()
|
||||
if err != start {
|
||||
t.Error("expected error")
|
||||
}
|
||||
|
||||
in.start = nil
|
||||
out.start = start
|
||||
err = service.Start()
|
||||
if err != start {
|
||||
t.Error("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceResetChannel(t *testing.T) {
|
||||
in := new(mockChannelIn)
|
||||
out := new(mockChannelOut)
|
||||
|
||||
service := NewService(in, out)
|
||||
|
||||
resetDelay = maxDelay
|
||||
|
||||
fails := 2
|
||||
in.wg.Add(fails)
|
||||
in.sendErr = 10
|
||||
|
||||
err := service.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
in.wg.Wait()
|
||||
|
||||
service.Stop()
|
||||
service.Wait()
|
||||
|
||||
expect := fails
|
||||
if in.count.start != expect || in.count.stop != expect {
|
||||
t.Errorf("count=%#v", in.count)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testESX = flag.Bool("toolbox.testesx", false, "Test toolbox service against ESX (vmtoolsd must not be running)")
|
||||
testPID = flag.Int64("toolbox.testpid", 0, "PID to return from toolbox start command")
|
||||
testOn = flag.String("toolbox.powerState", "", "Power state of VM prior to starting the test")
|
||||
)
|
||||
|
||||
// echoHandler for testing hgfs.FileHandler
|
||||
type echoHandler struct{}
|
||||
|
||||
func (e *echoHandler) Stat(u *url.URL) (os.FileInfo, error) {
|
||||
if u.RawQuery == "" {
|
||||
return nil, errors.New("no query")
|
||||
}
|
||||
|
||||
if u.Query().Get("foo") != "bar" {
|
||||
return nil, errors.New("invalid query")
|
||||
}
|
||||
|
||||
return os.Stat(u.Path)
|
||||
}
|
||||
|
||||
func (e *echoHandler) Open(u *url.URL, mode int32) (hgfs.File, error) {
|
||||
_, err := e.Stat(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return os.Open(u.Path)
|
||||
}
|
||||
|
||||
func TestServiceRunESX(t *testing.T) {
|
||||
if *testESX == false {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
Trace = testing.Verbose()
|
||||
|
||||
// A server that echos HTTP requests, for testing toolbox's http.RoundTripper
|
||||
echo := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.Write(w)
|
||||
}))
|
||||
// Client side can use 'govc guest.getenv' to get the URL w/ random port
|
||||
_ = os.Setenv("TOOLBOX_ECHO_SERVER", echo.URL)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
in := NewBackdoorChannelIn()
|
||||
out := NewBackdoorChannelOut()
|
||||
|
||||
service := NewService(in, out)
|
||||
service.Command.FileServer.RegisterFileHandler("echo", new(echoHandler))
|
||||
|
||||
ping := sync.NewCond(new(sync.Mutex))
|
||||
|
||||
service.RegisterHandler("ping", func(b []byte) ([]byte, error) {
|
||||
ping.Broadcast()
|
||||
return service.Ping(b)
|
||||
})
|
||||
|
||||
// assert that reset, ping, Set_Option and Capabilities_Register are called at least once
|
||||
for _, name := range []string{"reset", "ping", "Set_Option", "Capabilities_Register"} {
|
||||
n := name
|
||||
h := service.handlers[name]
|
||||
wg.Add(1)
|
||||
|
||||
service.handlers[name] = func(b []byte) ([]byte, error) {
|
||||
defer wg.Done()
|
||||
|
||||
service.handlers[n] = h // reset
|
||||
|
||||
return h(b)
|
||||
}
|
||||
}
|
||||
|
||||
if *testOn == string(types.VirtualMachinePowerStatePoweredOff) {
|
||||
wg.Add(1)
|
||||
service.Power.PowerOn.Handler = func() error {
|
||||
defer wg.Done()
|
||||
log.Print("power on event")
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
log.Print("skipping power on test")
|
||||
}
|
||||
|
||||
if *testPID != 0 {
|
||||
service.Command.ProcessStartCommand = func(m *ProcessManager, r *vix.StartProgramRequest) (int64, error) {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
|
||||
switch r.ProgramPath {
|
||||
case "/bin/date":
|
||||
return *testPID, nil
|
||||
case "sleep":
|
||||
p := NewProcessFunc(func(ctx context.Context, arg string) error {
|
||||
d, err := time.ParseDuration(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return &ProcessError{Err: ctx.Err(), ExitCode: 42}
|
||||
case <-time.After(d):
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return m.Start(r, p)
|
||||
default:
|
||||
return DefaultStartCommand(m, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
service.PrimaryIP = func() string {
|
||||
log.Print("broadcasting IP")
|
||||
return DefaultIP()
|
||||
}
|
||||
|
||||
log.Print("starting toolbox service")
|
||||
err := service.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// wait for 1 last ping to make sure the final response has reached the client before stopping
|
||||
ping.L.Lock()
|
||||
ping.Wait()
|
||||
ping.L.Unlock()
|
||||
|
||||
service.Stop()
|
||||
service.Wait()
|
||||
}
|
||||
259
vendor/github.com/vmware/govmomi/toolbox/toolbox-test.sh
generated
vendored
Executable file
259
vendor/github.com/vmware/govmomi/toolbox/toolbox-test.sh
generated
vendored
Executable file
@@ -0,0 +1,259 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Create (or reuse) a VM to run toolbox and/or toolbox.test
|
||||
# Requires ESX to be configured with:
|
||||
# govc host.esxcli system settings advanced set -o /Net/GuestIPHack -i 1
|
||||
|
||||
set -o pipefail
|
||||
|
||||
vm="toolbox-test-$(uuidgen)"
|
||||
destroy=true
|
||||
verbose=true
|
||||
|
||||
while getopts n:qstv flag
|
||||
do
|
||||
case $flag in
|
||||
n)
|
||||
vm=$OPTARG
|
||||
unset destroy
|
||||
;;
|
||||
q)
|
||||
verbose=false # you want this if generating lots of traffic, such as large file transfers
|
||||
;;
|
||||
s)
|
||||
start=true
|
||||
;;
|
||||
t)
|
||||
test=true
|
||||
;;
|
||||
*)
|
||||
echo "unknown option" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "Building toolbox binaries..."
|
||||
pushd "$(git rev-parse --show-toplevel)" >/dev/null
|
||||
GOOS=linux GOARCH=amd64 go build -o "$GOPATH/bin/toolbox" -v ./toolbox/toolbox
|
||||
GOOS=linux GOARCH=amd64 go test -race -i -c ./toolbox -o "$GOPATH/bin/toolbox.test"
|
||||
popd >/dev/null
|
||||
|
||||
iso=coreos_production_iso_image.iso
|
||||
|
||||
govc datastore.mkdir -p images
|
||||
|
||||
if ! govc datastore.ls images | grep -q $iso ; then
|
||||
echo "Downloading ${iso}..."
|
||||
if [ ! -e $iso ] ; then
|
||||
wget http://beta.release.core-os.net/amd64-usr/current/$iso
|
||||
fi
|
||||
|
||||
echo "Uploading ${iso}..."
|
||||
govc datastore.upload $iso images/$iso
|
||||
fi
|
||||
|
||||
if [ ! -e config.iso ] ; then
|
||||
echo "Generating config.iso..."
|
||||
keys=$(cat ~/.ssh/id_[rd]sa.pub)
|
||||
|
||||
dir=$(mktemp -d toolbox.XXXXXX)
|
||||
pushd "${dir}" >/dev/null
|
||||
|
||||
mkdir -p drive/openstack/latest
|
||||
|
||||
cat > drive/openstack/latest/user_data <<EOF
|
||||
#!/bin/bash
|
||||
|
||||
# Add ${USER}'s public key(s) to .ssh/authorized_keys
|
||||
echo "$keys" | update-ssh-keys -u core -a coreos-cloudinit
|
||||
EOF
|
||||
genisoimage=$(type -p genisoimage mkisofs | head -1)
|
||||
$genisoimage -R -V config-2 -o config.iso ./drive
|
||||
|
||||
popd >/dev/null
|
||||
|
||||
mv -f "$dir/config.iso" .
|
||||
rm -rf "$dir"
|
||||
fi
|
||||
|
||||
govc datastore.mkdir -p "$vm"
|
||||
|
||||
vm_path="$(govc find / -type m -name "$vm")"
|
||||
|
||||
if [ -z "$vm_path" ] ; then
|
||||
echo "Creating VM ${vm}..."
|
||||
govc vm.create -g otherGuest64 -m 1024 -on=false "$vm"
|
||||
|
||||
device=$(govc device.cdrom.add -vm "$vm")
|
||||
govc device.cdrom.insert -vm "$vm" -device "$device" images/$iso
|
||||
|
||||
govc datastore.upload config.iso "$vm/config.iso" >/dev/null
|
||||
device=$(govc device.cdrom.add -vm "$vm")
|
||||
govc device.cdrom.insert -vm "$vm" -device "$device" "$vm/config.iso"
|
||||
|
||||
vm_path="$(govc find / -type m -name "$vm")"
|
||||
else
|
||||
govc object.collect -s "$vm_path" -guest.toolsStatus toolsNot* # wait for previous toolbox to unregister
|
||||
fi
|
||||
|
||||
state=$(govc object.collect -s "$vm_path" runtime.powerState)
|
||||
|
||||
if [ "$state" != "poweredOn" ] ; then
|
||||
govc vm.power -on "$vm"
|
||||
fi
|
||||
|
||||
echo -n "Waiting for ${vm} ip..."
|
||||
ip=$(govc vm.ip -esxcli "$vm")
|
||||
|
||||
opts=(-o "UserKnownHostsFile /dev/null" -o "StrictHostKeyChecking no" -o "LogLevel error" -o "BatchMode yes")
|
||||
|
||||
destroy() {
|
||||
if [ -n "$test" ] ; then
|
||||
rm -f "$GOVC_TLS_KNOWN_HOSTS"
|
||||
fi
|
||||
|
||||
if [ -n "$destroy" ] ; then
|
||||
echo "Destroying VM ${vm}..."
|
||||
govc vm.destroy "$vm"
|
||||
govc datastore.rm -f "$vm"
|
||||
else
|
||||
ssh "${opts[@]}" "core@${ip}" pkill toolbox || true
|
||||
fi
|
||||
}
|
||||
|
||||
trap destroy EXIT
|
||||
|
||||
scp "${opts[@]}" "$GOPATH"/bin/toolbox{,.test} "core@${ip}:"
|
||||
|
||||
if [ -n "$test" ] ; then
|
||||
# validate guest.FileManager adds the host thumbprint when transferring files
|
||||
unset GOVC_INSECURE
|
||||
GOVC_TLS_KNOWN_HOSTS=$(mktemp --tmpdir toolbox.XXXXXX)
|
||||
govc about.cert -k -thumbprint > "$GOVC_TLS_KNOWN_HOSTS"
|
||||
|
||||
export GOVC_TLS_KNOWN_HOSTS GOVC_GUEST_LOGIN=user:pass
|
||||
|
||||
echo "Running toolbox tests..."
|
||||
ssh "${opts[@]}" "core@${ip}" ./toolbox.test -test.v=$verbose -test.run TestServiceRunESX -toolbox.testesx \
|
||||
-toolbox.testpid="$$" -toolbox.powerState="$state" &
|
||||
|
||||
echo "Waiting for VM ip from toolbox..."
|
||||
ip=$(govc vm.ip "$vm")
|
||||
echo "toolbox vm.ip=$ip"
|
||||
|
||||
echo "Testing guest.{start,kill,ps} operations via govc..."
|
||||
export GOVC_VM="$vm"
|
||||
|
||||
# should be 0 procs as toolbox only lists processes it started, for now
|
||||
test -z "$(govc guest.ps -e | grep -v STIME)"
|
||||
|
||||
out=$(govc guest.start /bin/date)
|
||||
|
||||
if [ "$out" != "$$" ] ; then
|
||||
echo "'$out' != '$$'" 1>&2
|
||||
fi
|
||||
|
||||
# These processes would run for 1h if we didn't kill them.
|
||||
pid=$(govc guest.start sleep 1h)
|
||||
|
||||
echo "Killing func $pid..."
|
||||
govc guest.kill -p "$pid"
|
||||
govc guest.ps -e -p "$pid" -X | grep "$pid"
|
||||
govc guest.ps -e -p "$pid" -json | jq -r .ProcessInfo[].ExitCode | grep -q 42
|
||||
|
||||
pid=$(govc guest.start /bin/sh -c "sleep 3600")
|
||||
echo "Killing proc $pid..."
|
||||
govc guest.kill -p "$pid"
|
||||
govc guest.ps -e -p "$pid" -X | grep "$pid"
|
||||
|
||||
echo "Testing file copy to and from guest via govc..."
|
||||
base="$(basename "$0")"
|
||||
dest="/tmp/$base"
|
||||
|
||||
govc guest.upload -f -perm 0640 -gid 10 "$0" "$dest"
|
||||
govc guest.download "$dest" - | md5sum --quiet -c <(<"$0" md5sum)
|
||||
govc guest.chmod 0755 "$dest"
|
||||
govc guest.ls "$dest" | grep rwxr-xr-x
|
||||
|
||||
echo "Testing custom hgfs.FileHandler..."
|
||||
if ! govc guest.download "/echo:$dest" - 2>/dev/null ; then
|
||||
govc guest.download "/echo:$dest?foo=bar" - >/dev/null
|
||||
fi
|
||||
|
||||
home=$(govc guest.getenv HOME | cut -d= -f2)
|
||||
|
||||
if date | govc guest.upload -f - /tmp 2>/dev/null ; then
|
||||
echo "guest.upload to directory should fail without .tgz source" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$verbose" = "false" ] ; then # else you don't want to see this noise
|
||||
# Download the $HOME directory, includes toolbox binaries (~30M total)
|
||||
# Note: trailing slash is required
|
||||
prefix=$(basename "$home")
|
||||
govc guest.download "$home/?prefix=$prefix/&format=tgz" - | tar -tzvf - | grep "$prefix"/toolbox
|
||||
|
||||
govc guest.mkdir -p /tmp/toolbox-src
|
||||
# Upload source files from this directory
|
||||
# and validate that query string is not used as the file/dir name (see hgfs.ArchiveHandler)
|
||||
git archive --format tgz HEAD | govc guest.upload -f - /tmp/toolbox-src?skip=stuff
|
||||
# Upload .tar
|
||||
git archive --format tar HEAD | govc guest.upload -f - /tmp/toolbox-src
|
||||
# Download .tar
|
||||
govc guest.download "/tmp/toolbox-src/" - | tar -tvf - | grep "$base"
|
||||
# Download a single file as a .tar.gz (note: /archive: prefix is required)
|
||||
govc guest.download "/archive:/tmp/toolbox-src/$base?format=tgz" - | tar -tvzf - | grep -v README.md
|
||||
# Download a single file as a .tar (note: /archive: prefix is required)
|
||||
govc guest.download "/archive:/tmp/toolbox-src/$base" - | tar -tvf - | grep -v README.md
|
||||
govc guest.rmdir -r /tmp/toolbox-src
|
||||
fi
|
||||
|
||||
echo "Testing we can download /proc files..."
|
||||
for name in uptime diskstats net/dev ; do
|
||||
test -n "$(govc guest.download /proc/$name -)"
|
||||
done
|
||||
|
||||
addr=$(govc guest.getenv TOOLBOX_ECHO_SERVER | cut -d= -f2)
|
||||
|
||||
echo "Testing http.RoundTrip via $addr..."
|
||||
govc guest.run GET "$addr/$vm" | grep "$vm"
|
||||
echo "$vm" | govc guest.run -e Content-Type:text/plain -d - POST "$addr" | grep "$vm"
|
||||
|
||||
echo "Testing commands with IO..."
|
||||
# Note that we don't use a pipe here, as guest.ps transitions the vm to VM_STATE_GUEST_OPERATION,
|
||||
# which prevents guest.run from invoking guest operations. By letting guest.ps complete before
|
||||
# guest.run, the vm will have transitioned back to VM_STATE_POWERED_ON.
|
||||
data=$(govc guest.ps -json)
|
||||
govc guest.run -d "$data" jq .
|
||||
|
||||
# test ProcessIO download retries
|
||||
govc version | govc guest.run -d - bash -c "'sleep 2 && cat'"
|
||||
|
||||
echo "Waiting for tests to complete..."
|
||||
wait
|
||||
fi
|
||||
|
||||
if [ -n "$start" ] ; then
|
||||
(
|
||||
govc object.collect -s "$vm_path" -guest.toolsStatus toolsOk # wait for toolbox to register
|
||||
/usr/bin/time -f "Waiting for VM ip from toolbox...%e" govc vm.ip -v4 -n ethernet-0 "$vm"
|
||||
) &
|
||||
|
||||
echo "Starting toolbox..."
|
||||
ssh "${opts[@]}" "core@${ip}" ./toolbox -toolbox.trace=$verbose
|
||||
fi
|
||||
71
vendor/github.com/vmware/govmomi/toolbox/toolbox/main.go
generated
vendored
Normal file
71
vendor/github.com/vmware/govmomi/toolbox/toolbox/main.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox"
|
||||
)
|
||||
|
||||
// This example can be run on a VM hosted by ESX, Fusion or Workstation
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
in := toolbox.NewBackdoorChannelIn()
|
||||
out := toolbox.NewBackdoorChannelOut()
|
||||
|
||||
service := toolbox.NewService(in, out)
|
||||
|
||||
if os.Getuid() == 0 {
|
||||
service.Power.Halt.Handler = toolbox.Halt
|
||||
service.Power.Reboot.Handler = toolbox.Reboot
|
||||
}
|
||||
|
||||
err := service.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// handle the signals and gracefully shutdown the service
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
log.Printf("signal %s received", <-sig)
|
||||
service.Stop()
|
||||
}()
|
||||
|
||||
service.Wait()
|
||||
}
|
||||
25
vendor/github.com/vmware/govmomi/toolbox/toolbox_darwin.go
generated
vendored
Normal file
25
vendor/github.com/vmware/govmomi/toolbox/toolbox_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func fileExtendedInfoFormat(dir string, info os.FileInfo) string {
|
||||
return ""
|
||||
}
|
||||
69
vendor/github.com/vmware/govmomi/toolbox/toolbox_linux.go
generated
vendored
Normal file
69
vendor/github.com/vmware/govmomi/toolbox/toolbox_linux.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/toolbox/vix"
|
||||
)
|
||||
|
||||
func fileExtendedInfoFormat(dir string, info os.FileInfo) string {
|
||||
const format = "<fxi>" +
|
||||
"<Name>%s</Name>" +
|
||||
"<ft>%d</ft>" +
|
||||
"<fs>%d</fs>" +
|
||||
"<mt>%d</mt>" +
|
||||
"<at>%d</at>" +
|
||||
"<uid>%d</uid>" +
|
||||
"<gid>%d</gid>" +
|
||||
"<perm>%d</perm>" +
|
||||
"<slt>%s</slt>" +
|
||||
"</fxi>"
|
||||
|
||||
props := 0
|
||||
targ := ""
|
||||
|
||||
if info.IsDir() {
|
||||
props |= vix.FileAttributesDirectory
|
||||
}
|
||||
|
||||
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
props |= vix.FileAttributesSymlink
|
||||
targ, _ = os.Readlink(filepath.Join(dir, info.Name()))
|
||||
}
|
||||
|
||||
size := info.Size()
|
||||
mtime := info.ModTime().Unix()
|
||||
perm := info.Mode().Perm()
|
||||
|
||||
atime := mtime
|
||||
uid := os.Getuid()
|
||||
gid := os.Getgid()
|
||||
|
||||
if sys, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
atime = time.Unix(sys.Atim.Unix()).Unix()
|
||||
uid = int(sys.Uid)
|
||||
gid = int(sys.Gid)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(format, info.Name(), props, size, mtime, atime, uid, gid, perm, targ)
|
||||
}
|
||||
41
vendor/github.com/vmware/govmomi/toolbox/toolbox_windows.go
generated
vendored
Normal file
41
vendor/github.com/vmware/govmomi/toolbox/toolbox_windows.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func fileExtendedInfoFormat(dir string, info os.FileInfo) string {
|
||||
const format = "<fxi>" +
|
||||
"<Name>%s</Name>" +
|
||||
"<ft>%d</ft>" +
|
||||
"<fs>%d</fs>" +
|
||||
"<mt>%d</mt>" +
|
||||
"<ct>%d</ct>" +
|
||||
"<at>%d</at>" +
|
||||
"</fxi>"
|
||||
|
||||
props := 0
|
||||
size := info.Size()
|
||||
mtime := info.ModTime().Unix()
|
||||
ctime := 0
|
||||
atime := 0
|
||||
|
||||
return fmt.Sprintf(format, info.Name(), props, size, mtime, ctime, atime)
|
||||
}
|
||||
83
vendor/github.com/vmware/govmomi/toolbox/trace_channel.go
generated
vendored
Normal file
83
vendor/github.com/vmware/govmomi/toolbox/trace_channel.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
Trace = false
|
||||
|
||||
traceLog io.Writer = os.Stderr
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&Trace, "toolbox.trace", Trace, "Enable toolbox trace")
|
||||
}
|
||||
|
||||
type TraceChannel struct {
|
||||
Channel
|
||||
log io.Writer
|
||||
}
|
||||
|
||||
func NewTraceChannel(c Channel) Channel {
|
||||
if !Trace {
|
||||
return c
|
||||
}
|
||||
|
||||
return &TraceChannel{
|
||||
Channel: c,
|
||||
log: traceLog,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *TraceChannel) Start() error {
|
||||
err := d.Channel.Start()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *TraceChannel) Stop() error {
|
||||
err := d.Channel.Stop()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *TraceChannel) Send(buf []byte) error {
|
||||
if len(buf) > 0 {
|
||||
fmt.Fprintf(d.log, "SEND %d...\n%s\n", len(buf), hex.Dump(buf))
|
||||
}
|
||||
|
||||
err := d.Channel.Send(buf)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *TraceChannel) Receive() ([]byte, error) {
|
||||
buf, err := d.Channel.Receive()
|
||||
|
||||
if err == nil && len(buf) > 0 {
|
||||
fmt.Fprintf(d.log, "RECV %d...\n%s\n", len(buf), hex.Dump(buf))
|
||||
}
|
||||
|
||||
return buf, err
|
||||
}
|
||||
236
vendor/github.com/vmware/govmomi/toolbox/vix/property.go
generated
vendored
Normal file
236
vendor/github.com/vmware/govmomi/toolbox/vix/property.go
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package vix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Property type enum as defined in open-vm-tools/lib/include/vix.h
|
||||
const (
|
||||
_ = iota // ANY type not supported
|
||||
vixPropertyTypeInt32
|
||||
vixPropertyTypeString
|
||||
vixPropertyTypeBool
|
||||
_ // HANDLE type not supported
|
||||
vixPropertyTypeInt64
|
||||
vixPropertyTypeBlob
|
||||
)
|
||||
|
||||
// Property ID enum as defined in open-vm-tools/lib/include/vixOpenSource.h
|
||||
const (
|
||||
PropertyGuestToolsAPIOptions = 4501
|
||||
PropertyGuestOsFamily = 4502
|
||||
PropertyGuestOsVersion = 4503
|
||||
PropertyGuestToolsProductNam = 4511
|
||||
PropertyGuestToolsVersion = 4500
|
||||
PropertyGuestName = 4505
|
||||
PropertyGuestOsVersionShort = 4520
|
||||
|
||||
PropertyGuestStartProgramEnabled = 4540
|
||||
PropertyGuestListProcessesEnabled = 4541
|
||||
PropertyGuestTerminateProcessEnabled = 4542
|
||||
PropertyGuestReadEnvironmentVariableEnabled = 4543
|
||||
|
||||
PropertyGuestMakeDirectoryEnabled = 4547
|
||||
PropertyGuestDeleteFileEnabled = 4548
|
||||
PropertyGuestDeleteDirectoryEnabled = 4549
|
||||
PropertyGuestMoveDirectoryEnabled = 4550
|
||||
PropertyGuestMoveFileEnabled = 4551
|
||||
PropertyGuestCreateTempFileEnabled = 4552
|
||||
PropertyGuestCreateTempDirectoryEnabled = 4553
|
||||
PropertyGuestListFilesEnabled = 4554
|
||||
PropertyGuestChangeFileAttributesEnabled = 4555
|
||||
PropertyGuestInitiateFileTransferFromGuestEnabled = 4556
|
||||
PropertyGuestInitiateFileTransferToGuestEnabled = 4557
|
||||
)
|
||||
|
||||
type Property struct {
|
||||
header struct {
|
||||
ID int32
|
||||
Kind int32
|
||||
Length int32
|
||||
}
|
||||
|
||||
data struct {
|
||||
Int32 int32
|
||||
String string
|
||||
Bool uint8
|
||||
Int64 int64
|
||||
Blob []byte
|
||||
}
|
||||
}
|
||||
|
||||
var int32Size int32
|
||||
|
||||
func init() {
|
||||
var i int32
|
||||
int32Size = int32(binary.Size(&i))
|
||||
}
|
||||
|
||||
type PropertyList []*Property
|
||||
|
||||
func NewInt32Property(ID int32, val int32) *Property {
|
||||
p := new(Property)
|
||||
p.header.ID = ID
|
||||
p.header.Kind = vixPropertyTypeInt32
|
||||
p.header.Length = int32Size
|
||||
p.data.Int32 = val
|
||||
return p
|
||||
}
|
||||
|
||||
func NewStringProperty(ID int32, val string) *Property {
|
||||
p := new(Property)
|
||||
p.header.ID = ID
|
||||
p.header.Kind = vixPropertyTypeString
|
||||
p.header.Length = int32(len(val) + 1)
|
||||
p.data.String = val
|
||||
return p
|
||||
}
|
||||
|
||||
func NewBoolProperty(ID int32, val bool) *Property {
|
||||
p := new(Property)
|
||||
p.header.ID = ID
|
||||
p.header.Kind = vixPropertyTypeBool
|
||||
p.header.Length = 1
|
||||
if val {
|
||||
p.data.Bool = 1
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func NewInt64Property(ID int32, val int64) *Property {
|
||||
p := new(Property)
|
||||
p.header.ID = ID
|
||||
p.header.Kind = vixPropertyTypeInt64
|
||||
p.header.Length = int32Size * 2
|
||||
p.data.Int64 = val
|
||||
return p
|
||||
}
|
||||
|
||||
func NewBlobProperty(ID int32, val []byte) *Property {
|
||||
p := new(Property)
|
||||
p.header.ID = ID
|
||||
p.header.Kind = vixPropertyTypeBlob
|
||||
p.header.Length = int32(len(val))
|
||||
p.data.Blob = val
|
||||
return p
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (p *Property) MarshalBinary() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// #nosec: Errors unhandled
|
||||
_ = binary.Write(buf, binary.LittleEndian, &p.header)
|
||||
|
||||
switch p.header.Kind {
|
||||
case vixPropertyTypeBool:
|
||||
// #nosec: Errors unhandled
|
||||
_ = binary.Write(buf, binary.LittleEndian, p.data.Bool)
|
||||
case vixPropertyTypeInt32:
|
||||
// #nosec: Errors unhandled
|
||||
_ = binary.Write(buf, binary.LittleEndian, p.data.Int32)
|
||||
case vixPropertyTypeInt64:
|
||||
// #nosec: Errors unhandled
|
||||
_ = binary.Write(buf, binary.LittleEndian, p.data.Int64)
|
||||
case vixPropertyTypeString:
|
||||
// #nosec: Errors unhandled
|
||||
_, _ = buf.WriteString(p.data.String)
|
||||
// #nosec: Errors unhandled
|
||||
_ = buf.WriteByte(0)
|
||||
case vixPropertyTypeBlob:
|
||||
// #nosec: Errors unhandled
|
||||
_, _ = buf.Write(p.data.Blob)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (p *Property) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &p.header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch p.header.Kind {
|
||||
case vixPropertyTypeBool:
|
||||
return binary.Read(buf, binary.LittleEndian, &p.data.Bool)
|
||||
case vixPropertyTypeInt32:
|
||||
return binary.Read(buf, binary.LittleEndian, &p.data.Int32)
|
||||
case vixPropertyTypeInt64:
|
||||
return binary.Read(buf, binary.LittleEndian, &p.data.Int64)
|
||||
case vixPropertyTypeString:
|
||||
s := make([]byte, p.header.Length)
|
||||
if _, err := buf.Read(s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.data.String = string(bytes.TrimRight(s, "\x00"))
|
||||
case vixPropertyTypeBlob:
|
||||
p.data.Blob = make([]byte, p.header.Length)
|
||||
if _, err := buf.Read(p.data.Blob); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("VIX_E_UNRECOGNIZED_PROPERTY")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (l *PropertyList) UnmarshalBinary(data []byte) error {
|
||||
headerSize := int32Size * 3
|
||||
|
||||
for {
|
||||
p := new(Property)
|
||||
|
||||
err := p.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*l = append(*l, p)
|
||||
|
||||
offset := headerSize + p.header.Length
|
||||
data = data[offset:]
|
||||
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (l *PropertyList) MarshalBinary() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
for _, p := range *l {
|
||||
// #nosec: Errors unhandled
|
||||
b, _ := p.MarshalBinary()
|
||||
// #nosec: Errors unhandled
|
||||
_, _ = buf.Write(b)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
107
vendor/github.com/vmware/govmomi/toolbox/vix/property_test.go
generated
vendored
Normal file
107
vendor/github.com/vmware/govmomi/toolbox/vix/property_test.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package vix
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestToolsStateProperties(t *testing.T) {
|
||||
// captured from vmtoolsd
|
||||
str := `lxEAAAIAAAAoAAAATGludXggNC40LjAtMjEtZ2VuZXJpYyBVYnVudHUgMTYuMDQgTFRTAKgRAAACAAAACgAAAHVidW50dS02NACfEQAAAgAAAA0AAABWTXdhcmUgVG9vbHMAlBEAAAIAAAAVAAAAMTAuMC41IGJ1aWxkLTMyMjc4NzIAmREAAAIAAAATAAAAdWJ1bnR1LTE2MDQtdm13YXJlAJURAAABAAAABAAAAAEAAACWEQAAAQAAAAQAAAABAAAAmBEAAAIAAAABAAAAAMsAAAACAAAAEQAAAC90bXAvdm13YXJlLXJvb3QApxEAAAEAAAAEAAAAQAAAAK0RAAACAAAACgAAAC9tbnQvaGdmcwC8EQAAAwAAAAEAAAAAvREAAAMAAAABAAAAAL4RAAADAAAAAQAAAAC/EQAAAwAAAAEAAAAAwBEAAAMAAAABAAAAAMERAAADAAAAAQAAAADCEQAAAwAAAAEAAAAAwxEAAAMAAAABAAAAAMQRAAADAAAAAQAAAADFEQAAAwAAAAEAAAAAxhEAAAMAAAABAAAAAMcRAAADAAAAAQAAAADIEQAAAwAAAAEAAAAAyREAAAMAAAABAAAAAMoRAAADAAAAAQAAAADLEQAAAwAAAAEAAAAAzBEAAAMAAAABAAAAAM0RAAADAAAAAQAAAADOEQAAAwAAAAEAAAAAzxEAAAMAAAABAAAAANARAAADAAAAAQAAAADREQAAAwAAAAEAAAAA0hEAAAMAAAABAAAAANMRAAADAAAAAQAAAADUEQAAAwAAAAEAAAAA1REAAAMAAAABAAAAANYRAAADAAAAAQAAAADXEQAAAwAAAAEAAAAA2BEAAAMAAAABAAAAAA==`
|
||||
|
||||
data, err := base64.StdEncoding.DecodeString(str)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var props PropertyList
|
||||
|
||||
err = props.UnmarshalBinary(data)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
data2, err := props.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
str2 := base64.StdEncoding.EncodeToString(data2)
|
||||
if str != str2 {
|
||||
t.Error("encoding mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalProperties(t *testing.T) {
|
||||
in := PropertyList{
|
||||
NewInt32Property(1, math.MaxInt32),
|
||||
NewStringProperty(2, "foo"),
|
||||
NewBoolProperty(3, true),
|
||||
NewInt64Property(4, math.MaxInt64),
|
||||
NewBlobProperty(5, []byte("deadbeef")),
|
||||
}
|
||||
|
||||
buf, err := in.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var out PropertyList
|
||||
|
||||
err = out.UnmarshalBinary(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, out) {
|
||||
t.Error("marshal mismatch")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// hit unmarshal error paths
|
||||
func TestUnmarshalPropertiesErrors(t *testing.T) {
|
||||
props := PropertyList{
|
||||
NewBoolProperty(1, true),
|
||||
NewStringProperty(1, "foo"),
|
||||
NewBlobProperty(2, []byte("deadbeef")),
|
||||
}
|
||||
|
||||
props[0].header.Kind = 0xff
|
||||
|
||||
for _, prop := range props {
|
||||
buf, _ := prop.MarshalBinary()
|
||||
|
||||
for i, l := range []int{1, binary.Size(prop.header)} {
|
||||
err := prop.UnmarshalBinary(buf[:l])
|
||||
if err == nil {
|
||||
t.Errorf("test %d (len=%d) expected error", i, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf, _ := props.MarshalBinary()
|
||||
err := props.UnmarshalBinary(buf)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
}
|
||||
847
vendor/github.com/vmware/govmomi/toolbox/vix/protocol.go
generated
vendored
Normal file
847
vendor/github.com/vmware/govmomi/toolbox/vix/protocol.go
generated
vendored
Normal file
@@ -0,0 +1,847 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package vix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
CommandMagicWord = 0xd00d0001
|
||||
|
||||
CommandGetToolsState = 62
|
||||
|
||||
CommandStartProgram = 185
|
||||
CommandListProcessesEx = 186
|
||||
CommandReadEnvVariables = 187
|
||||
CommandTerminateProcess = 193
|
||||
|
||||
CommandCreateDirectoryEx = 178
|
||||
CommandMoveGuestFileEx = 179
|
||||
CommandMoveGuestDirectory = 180
|
||||
CommandCreateTemporaryFileEx = 181
|
||||
CommandCreateTemporaryDirectory = 182
|
||||
CommandSetGuestFileAttributes = 183
|
||||
CommandDeleteGuestFileEx = 194
|
||||
CommandDeleteGuestDirectoryEx = 195
|
||||
|
||||
CommandListFiles = 177
|
||||
HgfsSendPacketCommand = 84
|
||||
CommandInitiateFileTransferFromGuest = 188
|
||||
CommandInitiateFileTransferToGuest = 189
|
||||
|
||||
// VIX_USER_CREDENTIAL_NAME_PASSWORD
|
||||
UserCredentialTypeNamePassword = 1
|
||||
|
||||
// VIX_E_* constants from vix.h
|
||||
OK = 0
|
||||
Fail = 1
|
||||
InvalidArg = 3
|
||||
FileNotFound = 4
|
||||
FileAlreadyExists = 12
|
||||
FileAccessError = 13
|
||||
AuthenticationFail = 35
|
||||
|
||||
UnrecognizedCommandInGuest = 3025
|
||||
InvalidMessageHeader = 10000
|
||||
InvalidMessageBody = 10001
|
||||
NotAFile = 20001
|
||||
NotADirectory = 20002
|
||||
NoSuchProcess = 20003
|
||||
DirectoryNotEmpty = 20006
|
||||
|
||||
// VIX_COMMAND_* constants from Commands.h
|
||||
CommandGuestReturnsBinary = 0x80
|
||||
|
||||
// VIX_FILE_ATTRIBUTES_ constants from vix.h
|
||||
FileAttributesDirectory = 0x0001
|
||||
FileAttributesSymlink = 0x0002
|
||||
)
|
||||
|
||||
// SetGuestFileAttributes flags as defined in vixOpenSource.h
|
||||
const (
|
||||
FileAttributeSetAccessDate = 0x0001
|
||||
FileAttributeSetModifyDate = 0x0002
|
||||
FileAttributeSetReadonly = 0x0004
|
||||
FileAttributeSetHidden = 0x0008
|
||||
FileAttributeSetUnixOwnerid = 0x0010
|
||||
FileAttributeSetUnixGroupid = 0x0020
|
||||
FileAttributeSetUnixPermissions = 0x0040
|
||||
)
|
||||
|
||||
type Error int
|
||||
|
||||
func (err Error) Error() string {
|
||||
return fmt.Sprintf("vix error=%d", err)
|
||||
}
|
||||
|
||||
// ErrorCode does its best to map the given error to a VIX error code.
|
||||
// See also: Vix_TranslateErrno
|
||||
func ErrorCode(err error) int {
|
||||
switch t := err.(type) {
|
||||
case Error:
|
||||
return int(t)
|
||||
case *os.PathError:
|
||||
if errno, ok := t.Err.(syscall.Errno); ok {
|
||||
switch errno {
|
||||
case syscall.ENOTEMPTY:
|
||||
return DirectoryNotEmpty
|
||||
}
|
||||
}
|
||||
case *exec.Error:
|
||||
if t.Err == exec.ErrNotFound {
|
||||
return FileNotFound
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
return FileNotFound
|
||||
case os.IsExist(err):
|
||||
return FileAlreadyExists
|
||||
case os.IsPermission(err):
|
||||
return FileAccessError
|
||||
default:
|
||||
return Fail
|
||||
}
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
Magic uint32
|
||||
MessageVersion uint16
|
||||
|
||||
TotalMessageLength uint32
|
||||
HeaderLength uint32
|
||||
BodyLength uint32
|
||||
CredentialLength uint32
|
||||
|
||||
CommonFlags uint8
|
||||
}
|
||||
|
||||
type CommandRequestHeader struct {
|
||||
Header
|
||||
|
||||
OpCode uint32
|
||||
RequestFlags uint32
|
||||
|
||||
TimeOut uint32
|
||||
|
||||
Cookie uint64
|
||||
ClientHandleID uint32
|
||||
|
||||
UserCredentialType uint32
|
||||
}
|
||||
|
||||
type StartProgramRequest struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
StartMinimized uint8
|
||||
ProgramPathLength uint32
|
||||
ArgumentsLength uint32
|
||||
WorkingDirLength uint32
|
||||
NumEnvVars uint32
|
||||
EnvVarLength uint32
|
||||
}
|
||||
|
||||
ProgramPath string
|
||||
Arguments string
|
||||
WorkingDir string
|
||||
EnvVars []string
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *StartProgramRequest) MarshalBinary() ([]byte, error) {
|
||||
var env bytes.Buffer
|
||||
|
||||
if n := len(r.EnvVars); n != 0 {
|
||||
for _, e := range r.EnvVars {
|
||||
_, _ = env.Write([]byte(e))
|
||||
_ = env.WriteByte(0)
|
||||
}
|
||||
r.Body.NumEnvVars = uint32(n)
|
||||
r.Body.EnvVarLength = uint32(env.Len())
|
||||
}
|
||||
|
||||
var fields []string
|
||||
|
||||
add := func(s string, l *uint32) {
|
||||
if n := len(s); n != 0 {
|
||||
*l = uint32(n) + 1
|
||||
fields = append(fields, s)
|
||||
}
|
||||
}
|
||||
|
||||
add(r.ProgramPath, &r.Body.ProgramPathLength)
|
||||
add(r.Arguments, &r.Body.ArgumentsLength)
|
||||
add(r.WorkingDir, &r.Body.WorkingDirLength)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
for _, val := range fields {
|
||||
_, _ = buf.Write([]byte(val))
|
||||
_ = buf.WriteByte(0)
|
||||
}
|
||||
|
||||
if r.Body.EnvVarLength != 0 {
|
||||
_, _ = buf.Write(env.Bytes())
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *StartProgramRequest) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := []struct {
|
||||
len uint32
|
||||
val *string
|
||||
}{
|
||||
{r.Body.ProgramPathLength, &r.ProgramPath},
|
||||
{r.Body.ArgumentsLength, &r.Arguments},
|
||||
{r.Body.WorkingDirLength, &r.WorkingDir},
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
if field.len == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
x := buf.Next(int(field.len))
|
||||
*field.val = string(bytes.TrimRight(x, "\x00"))
|
||||
}
|
||||
|
||||
for i := 0; i < int(r.Body.NumEnvVars); i++ {
|
||||
env, rerr := buf.ReadString(0)
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
|
||||
env = env[:len(env)-1] // discard NULL terminator
|
||||
r.EnvVars = append(r.EnvVars, env)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type KillProcessRequest struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
Pid int64
|
||||
Options uint32
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *KillProcessRequest) MarshalBinary() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *KillProcessRequest) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
return binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
}
|
||||
|
||||
type ListProcessesRequest struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
Key uint32
|
||||
Offset uint32
|
||||
NumPids uint32
|
||||
}
|
||||
|
||||
Pids []int64
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *ListProcessesRequest) MarshalBinary() ([]byte, error) {
|
||||
r.Body.NumPids = uint32(len(r.Pids))
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
for _, pid := range r.Pids {
|
||||
_ = binary.Write(buf, binary.LittleEndian, &pid)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *ListProcessesRequest) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Pids = make([]int64, r.Body.NumPids)
|
||||
|
||||
for i := uint32(0); i < r.Body.NumPids; i++ {
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Pids[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ReadEnvironmentVariablesRequest struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
NumNames uint32
|
||||
NamesLength uint32
|
||||
}
|
||||
|
||||
Names []string
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *ReadEnvironmentVariablesRequest) MarshalBinary() ([]byte, error) {
|
||||
var env bytes.Buffer
|
||||
|
||||
if n := len(r.Names); n != 0 {
|
||||
for _, e := range r.Names {
|
||||
_, _ = env.Write([]byte(e))
|
||||
_ = env.WriteByte(0)
|
||||
}
|
||||
r.Body.NumNames = uint32(n)
|
||||
r.Body.NamesLength = uint32(env.Len())
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
if r.Body.NamesLength != 0 {
|
||||
_, _ = buf.Write(env.Bytes())
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *ReadEnvironmentVariablesRequest) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < int(r.Body.NumNames); i++ {
|
||||
env, rerr := buf.ReadString(0)
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
|
||||
env = env[:len(env)-1] // discard NULL terminator
|
||||
r.Names = append(r.Names, env)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type CreateTempFileRequest struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
Options int32
|
||||
FilePrefixLength uint32
|
||||
FileSuffixLength uint32
|
||||
DirectoryPathLength uint32
|
||||
PropertyListLength uint32
|
||||
}
|
||||
|
||||
FilePrefix string
|
||||
FileSuffix string
|
||||
DirectoryPath string
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *CreateTempFileRequest) MarshalBinary() ([]byte, error) {
|
||||
var fields []string
|
||||
|
||||
add := func(s string, l *uint32) {
|
||||
*l = uint32(len(s)) // NOTE: NULL byte is not included in the length fields on the wire
|
||||
fields = append(fields, s)
|
||||
}
|
||||
|
||||
add(r.FilePrefix, &r.Body.FilePrefixLength)
|
||||
add(r.FileSuffix, &r.Body.FileSuffixLength)
|
||||
add(r.DirectoryPath, &r.Body.DirectoryPathLength)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
for _, val := range fields {
|
||||
_, _ = buf.Write([]byte(val))
|
||||
_ = buf.WriteByte(0)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *CreateTempFileRequest) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := []struct {
|
||||
len uint32
|
||||
val *string
|
||||
}{
|
||||
{r.Body.FilePrefixLength, &r.FilePrefix},
|
||||
{r.Body.FileSuffixLength, &r.FileSuffix},
|
||||
{r.Body.DirectoryPathLength, &r.DirectoryPath},
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
field.len++ // NOTE: NULL byte is not included in the length fields on the wire
|
||||
|
||||
x := buf.Next(int(field.len))
|
||||
*field.val = string(bytes.TrimRight(x, "\x00"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type FileRequest struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
FileOptions int32
|
||||
GuestPathNameLength uint32
|
||||
}
|
||||
|
||||
GuestPathName string
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *FileRequest) MarshalBinary() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
r.Body.GuestPathNameLength = uint32(len(r.GuestPathName))
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
_, _ = buf.WriteString(r.GuestPathName)
|
||||
_ = buf.WriteByte(0)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *FileRequest) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := buf.Next(int(r.Body.GuestPathNameLength))
|
||||
r.GuestPathName = string(bytes.TrimRight(name, "\x00"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type DirRequest struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
FileOptions int32
|
||||
GuestPathNameLength uint32
|
||||
FilePropertiesLength uint32
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
GuestPathName string
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *DirRequest) MarshalBinary() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
r.Body.GuestPathNameLength = uint32(len(r.GuestPathName))
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
_, _ = buf.WriteString(r.GuestPathName)
|
||||
_ = buf.WriteByte(0)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *DirRequest) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := buf.Next(int(r.Body.GuestPathNameLength))
|
||||
r.GuestPathName = string(bytes.TrimRight(name, "\x00"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RenameFileRequest struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
CopyFileOptions int32
|
||||
OldPathNameLength uint32
|
||||
NewPathNameLength uint32
|
||||
FilePropertiesLength uint32
|
||||
Overwrite bool
|
||||
}
|
||||
|
||||
OldPathName string
|
||||
NewPathName string
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *RenameFileRequest) MarshalBinary() ([]byte, error) {
|
||||
var fields []string
|
||||
|
||||
add := func(s string, l *uint32) {
|
||||
*l = uint32(len(s)) // NOTE: NULL byte is not included in the length fields on the wire
|
||||
fields = append(fields, s)
|
||||
}
|
||||
|
||||
add(r.OldPathName, &r.Body.OldPathNameLength)
|
||||
add(r.NewPathName, &r.Body.NewPathNameLength)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
for _, val := range fields {
|
||||
_, _ = buf.Write([]byte(val))
|
||||
_ = buf.WriteByte(0)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *RenameFileRequest) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := []struct {
|
||||
len uint32
|
||||
val *string
|
||||
}{
|
||||
{r.Body.OldPathNameLength, &r.OldPathName},
|
||||
{r.Body.NewPathNameLength, &r.NewPathName},
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
field.len++ // NOTE: NULL byte is not included in the length fields on the wire
|
||||
|
||||
x := buf.Next(int(field.len))
|
||||
*field.val = string(bytes.TrimRight(x, "\x00"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ListFilesRequest struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
FileOptions int32
|
||||
GuestPathNameLength uint32
|
||||
PatternLength uint32
|
||||
Index int32
|
||||
MaxResults int32
|
||||
Offset uint64
|
||||
}
|
||||
|
||||
GuestPathName string
|
||||
Pattern string
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *ListFilesRequest) MarshalBinary() ([]byte, error) {
|
||||
var fields []string
|
||||
|
||||
add := func(s string, l *uint32) {
|
||||
if n := len(s); n != 0 {
|
||||
*l = uint32(n) + 1
|
||||
fields = append(fields, s)
|
||||
}
|
||||
}
|
||||
|
||||
add(r.GuestPathName, &r.Body.GuestPathNameLength)
|
||||
add(r.Pattern, &r.Body.PatternLength)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
for _, val := range fields {
|
||||
_, _ = buf.Write([]byte(val))
|
||||
_ = buf.WriteByte(0)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *ListFilesRequest) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fields := []struct {
|
||||
len uint32
|
||||
val *string
|
||||
}{
|
||||
{r.Body.GuestPathNameLength, &r.GuestPathName},
|
||||
{r.Body.PatternLength, &r.Pattern},
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
if field.len == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
x := buf.Next(int(field.len))
|
||||
*field.val = string(bytes.TrimRight(x, "\x00"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type SetGuestFileAttributesRequest struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
FileOptions int32
|
||||
AccessTime int64
|
||||
ModificationTime int64
|
||||
OwnerID int32
|
||||
GroupID int32
|
||||
Permissions int32
|
||||
Hidden bool
|
||||
ReadOnly bool
|
||||
GuestPathNameLength uint32
|
||||
}
|
||||
|
||||
GuestPathName string
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *SetGuestFileAttributesRequest) MarshalBinary() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
r.Body.GuestPathNameLength = uint32(len(r.GuestPathName))
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
_, _ = buf.WriteString(r.GuestPathName)
|
||||
_ = buf.WriteByte(0)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *SetGuestFileAttributesRequest) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := buf.Next(int(r.Body.GuestPathNameLength))
|
||||
r.GuestPathName = string(bytes.TrimRight(name, "\x00"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SetGuestFileAttributesRequest) IsSet(opt int32) bool {
|
||||
return r.Body.FileOptions&opt == opt
|
||||
}
|
||||
|
||||
type CommandHgfsSendPacket struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
PacketSize uint32
|
||||
Timeout int32
|
||||
}
|
||||
|
||||
Packet []byte
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *CommandHgfsSendPacket) MarshalBinary() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
_, _ = buf.Write(r.Packet)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *CommandHgfsSendPacket) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Packet = buf.Next(int(r.Body.PacketSize))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type InitiateFileTransferToGuestRequest struct {
|
||||
CommandRequestHeader
|
||||
|
||||
Body struct {
|
||||
Options int32
|
||||
GuestPathNameLength uint32
|
||||
Overwrite bool
|
||||
}
|
||||
|
||||
GuestPathName string
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface
|
||||
func (r *InitiateFileTransferToGuestRequest) MarshalBinary() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
r.Body.GuestPathNameLength = uint32(len(r.GuestPathName))
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &r.Body)
|
||||
|
||||
_, _ = buf.WriteString(r.GuestPathName)
|
||||
_ = buf.WriteByte(0)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
|
||||
func (r *InitiateFileTransferToGuestRequest) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := buf.Next(int(r.Body.GuestPathNameLength))
|
||||
r.GuestPathName = string(bytes.TrimRight(name, "\x00"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type UserCredentialNamePassword struct {
|
||||
Body struct {
|
||||
NameLength uint32
|
||||
PasswordLength uint32
|
||||
}
|
||||
|
||||
Name string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (c *UserCredentialNamePassword) UnmarshalBinary(data []byte) error {
|
||||
buf := bytes.NewBuffer(bytes.TrimRight(data, "\x00"))
|
||||
|
||||
err := binary.Read(buf, binary.LittleEndian, &c.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str, err := base64.StdEncoding.DecodeString(string(buf.Bytes()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Name = string(str[0:c.Body.NameLength])
|
||||
c.Password = string(str[c.Body.NameLength+1 : len(str)-1])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *UserCredentialNamePassword) MarshalBinary() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
c.Body.NameLength = uint32(len(c.Name))
|
||||
c.Body.PasswordLength = uint32(len(c.Password))
|
||||
|
||||
_ = binary.Write(buf, binary.LittleEndian, &c.Body)
|
||||
|
||||
src := append([]byte(c.Name+"\x00"), []byte(c.Password+"\x00")...)
|
||||
|
||||
enc := base64.StdEncoding
|
||||
pwd := make([]byte, enc.EncodedLen(len(src)))
|
||||
enc.Encode(pwd, src)
|
||||
_, _ = buf.Write(pwd)
|
||||
_ = buf.WriteByte(0)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
67
vendor/github.com/vmware/govmomi/toolbox/vix/protocol_test.go
generated
vendored
Normal file
67
vendor/github.com/vmware/govmomi/toolbox/vix/protocol_test.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package vix
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMarshalVixMsgStartProgramRequest(t *testing.T) {
|
||||
requests := []*StartProgramRequest{
|
||||
{},
|
||||
{
|
||||
ProgramPath: "/bin/date",
|
||||
},
|
||||
{
|
||||
ProgramPath: "/bin/date",
|
||||
Arguments: "--date=@2147483647",
|
||||
},
|
||||
{
|
||||
ProgramPath: "/bin/date",
|
||||
WorkingDir: "/tmp",
|
||||
},
|
||||
{
|
||||
ProgramPath: "/bin/date",
|
||||
WorkingDir: "/tmp",
|
||||
EnvVars: []string{"FOO=bar"},
|
||||
},
|
||||
{
|
||||
ProgramPath: "/bin/date",
|
||||
WorkingDir: "/tmp",
|
||||
EnvVars: []string{"FOO=bar", "BAR=foo"},
|
||||
},
|
||||
}
|
||||
|
||||
for i, in := range requests {
|
||||
buf, err := in.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
out := new(StartProgramRequest)
|
||||
|
||||
err = out.UnmarshalBinary(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(in, out) {
|
||||
t.Errorf("%d marshal mismatch", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user