github.com/vmware/govmomi@v0.51.0/toolbox/command_test.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package toolbox
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding"
    11  	"encoding/binary"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"os"
    16  	"path/filepath"
    17  	"runtime"
    18  	"strconv"
    19  	"strings"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/vmware/govmomi/toolbox/hgfs"
    24  	"github.com/vmware/govmomi/toolbox/process"
    25  	"github.com/vmware/govmomi/toolbox/vix"
    26  )
    27  
    28  type CommandClient struct {
    29  	Service *Service
    30  	Header  *vix.CommandRequestHeader
    31  	creds   []byte
    32  }
    33  
    34  func NewCommandClient() *CommandClient {
    35  	Trace = testing.Verbose()
    36  	hgfs.Trace = Trace
    37  
    38  	creds, _ := (&vix.UserCredentialNamePassword{
    39  		Name:     "user",
    40  		Password: "pass",
    41  	}).MarshalBinary()
    42  
    43  	header := new(vix.CommandRequestHeader)
    44  	header.Magic = vix.CommandMagicWord
    45  
    46  	header.UserCredentialType = vix.UserCredentialTypeNamePassword
    47  	header.CredentialLength = uint32(len(creds))
    48  
    49  	in := new(mockChannelIn)
    50  	out := new(mockChannelOut)
    51  
    52  	return &CommandClient{
    53  		creds:   creds,
    54  		Header:  header,
    55  		Service: NewService(in, out),
    56  	}
    57  }
    58  
    59  func (c *CommandClient) Request(op uint32, m encoding.BinaryMarshaler) []byte {
    60  	b, err := m.MarshalBinary()
    61  	if err != nil {
    62  		panic(err)
    63  	}
    64  
    65  	c.Header.OpCode = op
    66  	c.Header.BodyLength = uint32(len(b))
    67  
    68  	var buf bytes.Buffer
    69  	_, _ = buf.Write([]byte("\"reqname\"\x00"))
    70  	_ = binary.Write(&buf, binary.LittleEndian, c.Header)
    71  
    72  	_, _ = buf.Write(b)
    73  
    74  	data := append(buf.Bytes(), c.creds...)
    75  	reply, err := c.Service.Command.Dispatch(data)
    76  	if err != nil {
    77  		panic(err)
    78  	}
    79  
    80  	return reply
    81  }
    82  
    83  func vixRC(buf []byte) int {
    84  	args := bytes.SplitN(buf, []byte{' '}, 2)
    85  	rc, err := strconv.Atoi(string(args[0]))
    86  	if err != nil {
    87  		panic(err)
    88  	}
    89  	return rc
    90  }
    91  
    92  func TestVixRelayedCommandHandler(t *testing.T) {
    93  	Trace = true
    94  	if !testing.Verbose() {
    95  		// cover Trace paths but discard output
    96  		traceLog = io.Discard
    97  	}
    98  
    99  	in := new(mockChannelIn)
   100  	out := new(mockChannelOut)
   101  
   102  	service := NewService(in, out)
   103  
   104  	cmd := service.Command
   105  
   106  	msg := []byte("\"reqname\"\x00")
   107  
   108  	_, err := cmd.Dispatch(msg) // io.EOF
   109  	if err == nil {
   110  		t.Fatal("expected error")
   111  	}
   112  
   113  	header := new(vix.CommandRequestHeader)
   114  
   115  	marshal := func(m ...encoding.BinaryMarshaler) []byte {
   116  		var buf bytes.Buffer
   117  		_, _ = buf.Write(msg)
   118  		_ = binary.Write(&buf, binary.LittleEndian, header)
   119  
   120  		for _, e := range m {
   121  			b, err := e.MarshalBinary()
   122  			if err != nil {
   123  				panic(err)
   124  			}
   125  			_, _ = buf.Write(b)
   126  		}
   127  
   128  		return buf.Bytes()
   129  	}
   130  
   131  	// header.Magic not set
   132  	reply, _ := cmd.Dispatch(marshal())
   133  	rc := vixRC(reply)
   134  	if rc != vix.InvalidMessageHeader {
   135  		t.Fatalf("%q", reply)
   136  	}
   137  
   138  	// header.OpCode not set
   139  	header.Magic = vix.CommandMagicWord
   140  	reply, _ = cmd.Dispatch(marshal())
   141  	rc = vixRC(reply)
   142  	if rc != vix.UnrecognizedCommandInGuest {
   143  		t.Fatalf("%q", reply)
   144  	}
   145  
   146  	// valid request for GetToolsState
   147  	header.OpCode = vix.CommandGetToolsState
   148  	reply, _ = cmd.Dispatch(marshal())
   149  	rc = vixRC(reply)
   150  	if rc != vix.OK {
   151  		t.Fatalf("%q", reply)
   152  	}
   153  
   154  	// header.UserCredentialType not set
   155  	header.OpCode = vix.CommandStartProgram
   156  	request := new(vix.StartProgramRequest)
   157  	reply, _ = cmd.Dispatch(marshal())
   158  	rc = vixRC(reply)
   159  	if rc != vix.AuthenticationFail {
   160  		t.Fatalf("%q", reply)
   161  	}
   162  
   163  	creds, _ := (&vix.UserCredentialNamePassword{
   164  		Name:     "user",
   165  		Password: "pass",
   166  	}).MarshalBinary()
   167  
   168  	header.BodyLength = uint32(binary.Size(request.Body))
   169  	header.UserCredentialType = vix.UserCredentialTypeNamePassword
   170  	header.CredentialLength = uint32(len(creds))
   171  
   172  	// ProgramPath not set
   173  	buf := append(marshal(request), creds...)
   174  	reply, _ = cmd.Dispatch(buf)
   175  	rc = vixRC(reply)
   176  	if rc != vix.FileNotFound {
   177  		t.Fatalf("%q", reply)
   178  	}
   179  
   180  	cmd.ProcessStartCommand = func(pm *process.Manager, r *vix.StartProgramRequest) (int64, error) {
   181  		return -1, nil
   182  	}
   183  
   184  	// valid request for StartProgram
   185  	buf = append(marshal(request), creds...)
   186  	reply, _ = cmd.Dispatch(buf)
   187  	rc = vixRC(reply)
   188  	if rc != vix.OK {
   189  		t.Fatalf("%q", reply)
   190  	}
   191  
   192  	cmd.Authenticate = func(_ vix.CommandRequestHeader, data []byte) error {
   193  		var c vix.UserCredentialNamePassword
   194  		if err := c.UnmarshalBinary(data); err != nil {
   195  			panic(err)
   196  		}
   197  
   198  		return errors.New("you shall not pass")
   199  	}
   200  
   201  	// fail auth with our own handler
   202  	buf = append(marshal(request), creds...)
   203  	reply, _ = cmd.Dispatch(buf)
   204  	rc = vixRC(reply)
   205  	if rc != vix.AuthenticationFail {
   206  		t.Fatalf("%q", reply)
   207  	}
   208  
   209  	cmd.Authenticate = nil
   210  
   211  	// cause Vix.UserCredentialNamePassword.UnmarshalBinary to error
   212  	// first by EOF reading header, second in base64 decode
   213  	for _, l := range []uint32{1, 10} {
   214  		header.CredentialLength = l
   215  		buf = append(marshal(request), creds...)
   216  		reply, _ = cmd.Dispatch(buf)
   217  		rc = vixRC(reply)
   218  		if rc != vix.AuthenticationFail {
   219  			t.Fatalf("%q", reply)
   220  		}
   221  	}
   222  }
   223  
   224  // cover misc error paths
   225  func TestVixCommandErrors(t *testing.T) {
   226  	r := new(vix.StartProgramRequest)
   227  	err := r.UnmarshalBinary(nil)
   228  	if err == nil {
   229  		t.Error("expected error")
   230  	}
   231  
   232  	r.Body.NumEnvVars = 1
   233  	buf, _ := r.MarshalBinary()
   234  	err = r.UnmarshalBinary(buf)
   235  	if err == nil {
   236  		t.Error("expected error")
   237  	}
   238  
   239  	c := new(CommandServer)
   240  	_, err = c.StartCommand(r.CommandRequestHeader, nil)
   241  	if err == nil {
   242  		t.Error("expected error")
   243  	}
   244  }
   245  
   246  func TestVixInitiateDirTransfer(t *testing.T) {
   247  	c := NewCommandClient()
   248  
   249  	dir := os.TempDir()
   250  
   251  	for _, enable := range []bool{true, false} {
   252  		expect := vix.NotAFile
   253  		if enable {
   254  			expect = vix.OK
   255  		} else {
   256  			// validate we behave as open-vm-tools does when the directory archive feature is disabled
   257  			c.Service.Command.FileServer.RegisterFileHandler(hgfs.ArchiveScheme, nil)
   258  		}
   259  
   260  		fromGuest := &vix.ListFilesRequest{GuestPathName: dir}
   261  		toGuest := &vix.InitiateFileTransferToGuestRequest{GuestPathName: dir}
   262  		toGuest.Body.Overwrite = true
   263  
   264  		tests := []struct {
   265  			op      uint32
   266  			request encoding.BinaryMarshaler
   267  		}{
   268  			{vix.CommandInitiateFileTransferFromGuest, fromGuest},
   269  			{vix.CommandInitiateFileTransferToGuest, toGuest},
   270  		}
   271  
   272  		for _, test := range tests {
   273  			reply := c.Request(test.op, test.request)
   274  
   275  			rc := vixRC(reply)
   276  
   277  			if rc != expect {
   278  				t.Errorf("rc=%d", rc)
   279  			}
   280  		}
   281  	}
   282  }
   283  
   284  func TestVixInitiateFileTransfer(t *testing.T) {
   285  	c := NewCommandClient()
   286  
   287  	request := new(vix.ListFilesRequest)
   288  
   289  	f, err := os.CreateTemp("", "toolbox")
   290  	if err != nil {
   291  		t.Fatal(err)
   292  	}
   293  
   294  	for _, s := range []string{"a", "b", "c", "d", "e"} {
   295  		_, _ = f.WriteString(strings.Repeat(s, 40))
   296  	}
   297  
   298  	_ = f.Close()
   299  
   300  	name := f.Name()
   301  
   302  	// 1st pass file exists == OK, 2nd pass does not exist == FAIL
   303  	for _, fail := range []bool{false, true} {
   304  		request.GuestPathName = name
   305  
   306  		reply := c.Request(vix.CommandInitiateFileTransferFromGuest, request)
   307  
   308  		rc := vixRC(reply)
   309  
   310  		if Trace {
   311  			fmt.Fprintf(os.Stderr, "%s: %s\n", name, string(reply))
   312  		}
   313  
   314  		if fail {
   315  			if rc == vix.OK {
   316  				t.Errorf("%s: %d", name, rc)
   317  			}
   318  		} else {
   319  			if rc != vix.OK {
   320  				t.Errorf("%s: %d", name, rc)
   321  			}
   322  
   323  			err = os.Remove(name)
   324  			if err != nil {
   325  				t.Error(err)
   326  			}
   327  		}
   328  	}
   329  }
   330  
   331  func TestVixInitiateFileTransferWrite(t *testing.T) {
   332  	c := NewCommandClient()
   333  
   334  	request := new(vix.InitiateFileTransferToGuestRequest)
   335  
   336  	f, err := os.CreateTemp("", "toolbox")
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  
   341  	_ = f.Close()
   342  
   343  	name := f.Name()
   344  
   345  	tests := []struct {
   346  		force bool
   347  		fail  bool
   348  	}{
   349  		{false, true},  // exists == FAIL
   350  		{true, false},  // exists, but overwrite == OK
   351  		{false, false}, // does not exist == OK
   352  	}
   353  
   354  	for i, test := range tests {
   355  		request.GuestPathName = name
   356  		request.Body.Overwrite = test.force
   357  
   358  		reply := c.Request(vix.CommandInitiateFileTransferToGuest, request)
   359  
   360  		rc := vixRC(reply)
   361  
   362  		if Trace {
   363  			fmt.Fprintf(os.Stderr, "%s: %s\n", name, string(reply))
   364  		}
   365  
   366  		if test.fail {
   367  			if rc == vix.OK {
   368  				t.Errorf("%d: %d", i, rc)
   369  			}
   370  		} else {
   371  			if rc != vix.OK {
   372  				t.Errorf("%d: %d", i, rc)
   373  			}
   374  			if test.force {
   375  				_ = os.Remove(name)
   376  			}
   377  		}
   378  	}
   379  }
   380  
   381  func TestVixProcessHgfsPacket(t *testing.T) {
   382  	c := NewCommandClient()
   383  
   384  	c.Header.CommonFlags = vix.CommandGuestReturnsBinary
   385  
   386  	request := new(vix.CommandHgfsSendPacket)
   387  
   388  	op := new(hgfs.RequestCreateSessionV4)
   389  	packet := new(hgfs.Packet)
   390  	packet.Payload, _ = op.MarshalBinary()
   391  	packet.Header.Version = hgfs.HeaderVersion
   392  	packet.Header.Dummy = hgfs.OpNewHeader
   393  	packet.Header.HeaderSize = uint32(binary.Size(&packet.Header))
   394  	packet.Header.PacketSize = packet.Header.HeaderSize + uint32(len(packet.Payload))
   395  	packet.Header.Op = hgfs.OpCreateSessionV4
   396  
   397  	request.Packet, _ = packet.MarshalBinary()
   398  	request.Body.PacketSize = uint32(len(request.Packet))
   399  
   400  	reply := c.Request(vix.HgfsSendPacketCommand, request)
   401  
   402  	rc := vixRC(reply)
   403  	if rc != vix.OK {
   404  		t.Fatalf("rc: %d", rc)
   405  	}
   406  
   407  	ix := bytes.IndexByte(reply, '#')
   408  	reply = reply[ix+1:]
   409  	err := packet.UnmarshalBinary(reply)
   410  	if err != nil {
   411  		t.Fatal(err)
   412  	}
   413  
   414  	if packet.Status != hgfs.StatusSuccess {
   415  		t.Errorf("status=%d", packet.Status)
   416  	}
   417  
   418  	if packet.Dummy != hgfs.OpNewHeader {
   419  		t.Errorf("dummy=%d", packet.Dummy)
   420  	}
   421  
   422  	session := new(hgfs.ReplyCreateSessionV4)
   423  	err = session.UnmarshalBinary(packet.Payload)
   424  	if err != nil {
   425  		t.Fatal(err)
   426  	}
   427  
   428  	if session.NumCapabilities == 0 || int(session.NumCapabilities) != len(session.Capabilities) {
   429  		t.Errorf("NumCapabilities=%d", session.NumCapabilities)
   430  	}
   431  }
   432  
   433  func TestVixListProcessesEx(t *testing.T) {
   434  	c := NewCommandClient()
   435  
   436  	c.Service.Command.ProcessStartCommand = func(pm *process.Manager, r *vix.StartProgramRequest) (int64, error) {
   437  		var p *process.Process
   438  		switch r.ProgramPath {
   439  		case "foo":
   440  			p = process.NewFunc(func(ctx context.Context, arg string) error {
   441  				return nil
   442  			})
   443  		default:
   444  			return -1, os.ErrNotExist
   445  		}
   446  
   447  		return pm.Start(r, p)
   448  	}
   449  
   450  	exec := &vix.StartProgramRequest{
   451  		ProgramPath: "foo",
   452  	}
   453  
   454  	reply := c.Request(vix.CommandStartProgram, exec)
   455  	rc := vixRC(reply)
   456  	if rc != vix.OK {
   457  		t.Fatalf("rc: %d", rc)
   458  	}
   459  
   460  	r := bytes.Trim(bytes.Split(reply, []byte{' '})[2], "\x00")
   461  	pid, _ := strconv.Atoi(string(r))
   462  
   463  	exec.ProgramPath = "bar"
   464  	reply = c.Request(vix.CommandStartProgram, exec)
   465  	rc = vixRC(reply)
   466  	t.Log(vix.Error(rc).Error())
   467  	if rc != vix.FileNotFound {
   468  		t.Fatalf("rc: %d", rc)
   469  	}
   470  	if vix.ErrorCode(os.ErrNotExist) != rc {
   471  		t.Fatalf("rc: %d", rc)
   472  	}
   473  
   474  	<-time.Tick(time.Millisecond * 100)
   475  
   476  	ps := new(vix.ListProcessesRequest)
   477  
   478  	ps.Pids = []int64{int64(pid)}
   479  
   480  	reply = c.Request(vix.CommandListProcessesEx, ps)
   481  	rc = vixRC(reply)
   482  	if rc != vix.OK {
   483  		t.Fatalf("rc: %d", rc)
   484  	}
   485  
   486  	n := bytes.Count(reply, []byte("<proc>"))
   487  	if n != len(ps.Pids) {
   488  		t.Errorf("ps -p %d=%d", pid, n)
   489  	}
   490  
   491  	kill := new(vix.KillProcessRequest)
   492  	kill.Body.Pid = ps.Pids[0]
   493  
   494  	reply = c.Request(vix.CommandTerminateProcess, kill)
   495  	rc = vixRC(reply)
   496  	if rc != vix.OK {
   497  		t.Fatalf("rc: %d", rc)
   498  	}
   499  
   500  	kill.Body.Pid = 33333
   501  	reply = c.Request(vix.CommandTerminateProcess, kill)
   502  	rc = vixRC(reply)
   503  	if rc != vix.NoSuchProcess {
   504  		t.Fatalf("rc: %d", rc)
   505  	}
   506  }
   507  
   508  func TestVixGetenv(t *testing.T) {
   509  	c := NewCommandClient()
   510  
   511  	env := os.Environ()
   512  	key := strings.SplitN(env[0], "=", 2)[0]
   513  
   514  	tests := []struct {
   515  		names  []string
   516  		expect int
   517  	}{
   518  		{nil, len(env)},              // all env
   519  		{[]string{key, "ENOENT"}, 1}, // specific vars, 1 exists 1 does not
   520  	}
   521  
   522  	for i, test := range tests {
   523  		env := &vix.ReadEnvironmentVariablesRequest{
   524  			Names: test.names,
   525  		}
   526  		reply := c.Request(vix.CommandReadEnvVariables, env)
   527  		rc := vixRC(reply)
   528  		if rc != vix.OK {
   529  			t.Fatalf("%d) rc: %d", i, rc)
   530  		}
   531  
   532  		num := bytes.Count(reply, []byte("<ev>"))
   533  		if num != test.expect {
   534  			t.Errorf("%d) getenv(%v): %d", i, test.names, num)
   535  		}
   536  	}
   537  }
   538  
   539  func TestVixDirectories(t *testing.T) {
   540  	c := NewCommandClient()
   541  
   542  	mktemp := &vix.CreateTempFileRequest{
   543  		FilePrefix: "toolbox-",
   544  	}
   545  
   546  	// mktemp -d
   547  	reply := c.Request(vix.CommandCreateTemporaryDirectory, mktemp)
   548  	rc := vixRC(reply)
   549  	if rc != vix.OK {
   550  		t.Fatalf("rc: %d", rc)
   551  	}
   552  
   553  	dir := strings.TrimSuffix(string(reply[4:]), "\x00")
   554  
   555  	mkdir := &vix.DirRequest{
   556  		GuestPathName: dir,
   557  	}
   558  
   559  	// mkdir $dir == EEXIST
   560  	reply = c.Request(vix.CommandCreateDirectoryEx, mkdir)
   561  	rc = vixRC(reply)
   562  	if rc != vix.FileAlreadyExists {
   563  		t.Fatalf("rc: %d", rc)
   564  	}
   565  
   566  	// mkdir $dir/ok == OK
   567  	mkdir.GuestPathName = dir + "/ok"
   568  	reply = c.Request(vix.CommandCreateDirectoryEx, mkdir)
   569  	rc = vixRC(reply)
   570  	if rc != vix.OK {
   571  		t.Fatalf("rc: %d", rc)
   572  	}
   573  
   574  	// rm of a dir should fail, regardless if empty or not
   575  	reply = c.Request(vix.CommandDeleteGuestFileEx, &vix.FileRequest{
   576  		GuestPathName: mkdir.GuestPathName,
   577  	})
   578  	rc = vixRC(reply)
   579  	if rc != vix.NotAFile {
   580  		t.Errorf("rc: %d", rc)
   581  	}
   582  
   583  	// rmdir $dir/ok == OK
   584  	reply = c.Request(vix.CommandDeleteGuestDirectoryEx, mkdir)
   585  	rc = vixRC(reply)
   586  	if rc != vix.OK {
   587  		t.Fatalf("rc: %d", rc)
   588  	}
   589  
   590  	// rmdir $dir/ok == ENOENT
   591  	reply = c.Request(vix.CommandDeleteGuestDirectoryEx, mkdir)
   592  	rc = vixRC(reply)
   593  	if rc != vix.FileNotFound {
   594  		t.Fatalf("rc: %d", rc)
   595  	}
   596  
   597  	// mkdir $dir/1/2 == ENOENT (parent directory does not exist)
   598  	mkdir.GuestPathName = dir + "/1/2"
   599  	reply = c.Request(vix.CommandCreateDirectoryEx, mkdir)
   600  	rc = vixRC(reply)
   601  	if rc != vix.FileNotFound {
   602  		t.Fatalf("rc: %d", rc)
   603  	}
   604  
   605  	// mkdir -p $dir/1/2 == OK
   606  	mkdir.Body.Recursive = true
   607  	reply = c.Request(vix.CommandCreateDirectoryEx, mkdir)
   608  	rc = vixRC(reply)
   609  	if rc != vix.OK {
   610  		t.Fatalf("rc: %d", rc)
   611  	}
   612  
   613  	// rmdir $dir == ENOTEMPTY
   614  	mkdir.GuestPathName = dir
   615  	mkdir.Body.Recursive = false
   616  	reply = c.Request(vix.CommandDeleteGuestDirectoryEx, mkdir)
   617  	rc = vixRC(reply)
   618  	if rc != vix.DirectoryNotEmpty {
   619  		t.Fatalf("rc: %d", rc)
   620  	}
   621  
   622  	// rm -rf $dir == OK
   623  	mkdir.Body.Recursive = true
   624  	reply = c.Request(vix.CommandDeleteGuestDirectoryEx, mkdir)
   625  	rc = vixRC(reply)
   626  	if rc != vix.OK {
   627  		t.Fatalf("rc: %d", rc)
   628  	}
   629  }
   630  
   631  func TestVixFiles(t *testing.T) {
   632  	if runtime.GOOS != "linux" {
   633  		t.Skip("requires Linux")
   634  	}
   635  
   636  	c := NewCommandClient()
   637  
   638  	mktemp := &vix.CreateTempFileRequest{
   639  		FilePrefix: "toolbox-",
   640  	}
   641  
   642  	// mktemp -d
   643  	reply := c.Request(vix.CommandCreateTemporaryDirectory, mktemp)
   644  	rc := vixRC(reply)
   645  	if rc != vix.OK {
   646  		t.Fatalf("rc: %d", rc)
   647  	}
   648  
   649  	dir := strings.TrimSuffix(string(reply[4:]), "\x00")
   650  
   651  	max := 12
   652  	var total int
   653  
   654  	// mktemp
   655  	for i := 0; i <= max; i++ {
   656  		mktemp = &vix.CreateTempFileRequest{
   657  			DirectoryPath: dir,
   658  		}
   659  
   660  		reply = c.Request(vix.CommandCreateTemporaryFileEx, mktemp)
   661  		rc = vixRC(reply)
   662  		if rc != vix.OK {
   663  			t.Fatalf("rc: %d", rc)
   664  		}
   665  	}
   666  
   667  	// name of the last file temp file we created, we'll mess around with it then delete it
   668  	name := strings.TrimSuffix(string(reply[4:]), "\x00")
   669  	// for testing symlinks
   670  	link := filepath.Join(dir, "a-link")
   671  	err := os.Symlink(name, link)
   672  	if err != nil {
   673  		t.Fatal(err)
   674  	}
   675  
   676  	for _, fpath := range []string{name, link} {
   677  		// test ls of a single file
   678  		ls := &vix.ListFilesRequest{
   679  			GuestPathName: fpath,
   680  		}
   681  
   682  		reply = c.Request(vix.CommandListFiles, ls)
   683  
   684  		rc = vixRC(reply)
   685  		if rc != vix.OK {
   686  			t.Fatalf("rc: %d", rc)
   687  		}
   688  
   689  		num := bytes.Count(reply, []byte("<fxi>"))
   690  		if num != 1 {
   691  			t.Errorf("ls %s: %d", name, num)
   692  		}
   693  
   694  		num = bytes.Count(reply, []byte("<rem>0</rem>"))
   695  		if num != 1 {
   696  			t.Errorf("ls %s: %d", name, num)
   697  		}
   698  
   699  		ft := 0
   700  		target := ""
   701  		if fpath == link {
   702  			target = name
   703  			ft = vix.FileAttributesSymlink
   704  		}
   705  
   706  		num = bytes.Count(reply, []byte(fmt.Sprintf("<slt>%s</slt>", target)))
   707  		if num != 1 {
   708  			t.Errorf("ls %s: %d", name, num)
   709  		}
   710  
   711  		num = bytes.Count(reply, []byte(fmt.Sprintf("<ft>%d</ft>", ft)))
   712  		if num != 1 {
   713  			t.Errorf("ls %s: %d", name, num)
   714  		}
   715  	}
   716  
   717  	mv := &vix.RenameFileRequest{
   718  		OldPathName: name,
   719  		NewPathName: name + "-new",
   720  	}
   721  
   722  	for _, expect := range []int{vix.OK, vix.FileNotFound} {
   723  		reply = c.Request(vix.CommandMoveGuestFileEx, mv)
   724  		rc = vixRC(reply)
   725  		if rc != expect {
   726  			t.Errorf("rc: %d", rc)
   727  		}
   728  
   729  		if expect == vix.OK {
   730  			// test file type is properly checked
   731  			reply = c.Request(vix.CommandMoveGuestDirectory, &vix.RenameFileRequest{
   732  				OldPathName: mv.NewPathName,
   733  				NewPathName: name,
   734  			})
   735  			rc = vixRC(reply)
   736  			if rc != vix.NotADirectory {
   737  				t.Errorf("rc: %d", rc)
   738  			}
   739  
   740  			// test Overwrite flag is properly checked
   741  			reply = c.Request(vix.CommandMoveGuestFileEx, &vix.RenameFileRequest{
   742  				OldPathName: mv.NewPathName,
   743  				NewPathName: mv.NewPathName,
   744  			})
   745  			rc = vixRC(reply)
   746  			if rc != vix.FileAlreadyExists {
   747  				t.Errorf("rc: %d", rc)
   748  			}
   749  		}
   750  	}
   751  
   752  	// rmdir of a file should fail
   753  	reply = c.Request(vix.CommandDeleteGuestDirectoryEx, &vix.DirRequest{
   754  		GuestPathName: mv.NewPathName,
   755  	})
   756  
   757  	rc = vixRC(reply)
   758  	if rc != vix.NotADirectory {
   759  		t.Errorf("rc: %d", rc)
   760  	}
   761  
   762  	file := &vix.FileRequest{
   763  		GuestPathName: mv.NewPathName,
   764  	}
   765  
   766  	for _, expect := range []int{vix.OK, vix.FileNotFound} {
   767  		reply = c.Request(vix.CommandDeleteGuestFileEx, file)
   768  		rc = vixRC(reply)
   769  		if rc != expect {
   770  			t.Errorf("rc: %d", rc)
   771  		}
   772  	}
   773  
   774  	// ls again now that file is gone
   775  	reply = c.Request(vix.CommandListFiles, &vix.ListFilesRequest{
   776  		GuestPathName: name,
   777  	})
   778  
   779  	rc = vixRC(reply)
   780  	if rc != vix.FileNotFound {
   781  		t.Errorf("rc: %d", rc)
   782  	}
   783  
   784  	// ls
   785  	ls := &vix.ListFilesRequest{
   786  		GuestPathName: dir,
   787  	}
   788  	ls.Body.MaxResults = 5 // default is 50
   789  
   790  	for i := 0; i < 5; i++ {
   791  		reply = c.Request(vix.CommandListFiles, ls)
   792  
   793  		if Trace {
   794  			fmt.Fprintf(os.Stderr, "%s: %q\n", dir, string(reply[4:]))
   795  		}
   796  
   797  		var rem int
   798  		_, err := fmt.Fscanf(bytes.NewReader(reply[4:]), "<rem>%d</rem>", &rem)
   799  		if err != nil {
   800  			t.Fatal(err)
   801  		}
   802  
   803  		num := bytes.Count(reply, []byte("<fxi>"))
   804  		total += num
   805  		ls.Body.Offset += uint64(num)
   806  
   807  		if rem == 0 {
   808  			break
   809  		}
   810  	}
   811  
   812  	if total != max+1 {
   813  		t.Errorf("expected %d, got %d", max, total)
   814  	}
   815  
   816  	// Test invalid offset, making sure it doesn't cause panic (issue #934)
   817  	ls.Body.Offset += 10
   818  	_ = c.Request(vix.CommandListFiles, ls)
   819  
   820  	// mv $dir ${dir}-old
   821  	mv = &vix.RenameFileRequest{
   822  		OldPathName: dir,
   823  		NewPathName: dir + "-old",
   824  	}
   825  
   826  	for _, expect := range []int{vix.OK, vix.FileNotFound} {
   827  		reply = c.Request(vix.CommandMoveGuestDirectory, mv)
   828  		rc = vixRC(reply)
   829  		if rc != expect {
   830  			t.Errorf("rc: %d", rc)
   831  		}
   832  
   833  		if expect == vix.OK {
   834  			// test file type is properly checked
   835  			reply = c.Request(vix.CommandMoveGuestFileEx, &vix.RenameFileRequest{
   836  				OldPathName: mv.NewPathName,
   837  				NewPathName: dir,
   838  			})
   839  			rc = vixRC(reply)
   840  			if rc != vix.NotAFile {
   841  				t.Errorf("rc: %d", rc)
   842  			}
   843  
   844  			// test Overwrite flag is properly checked
   845  			reply = c.Request(vix.CommandMoveGuestDirectory, &vix.RenameFileRequest{
   846  				OldPathName: mv.NewPathName,
   847  				NewPathName: mv.NewPathName,
   848  			})
   849  			rc = vixRC(reply)
   850  			if rc != vix.FileAlreadyExists {
   851  				t.Errorf("rc: %d", rc)
   852  			}
   853  		}
   854  	}
   855  
   856  	rmdir := &vix.DirRequest{
   857  		GuestPathName: mv.NewPathName,
   858  	}
   859  
   860  	// rm -rm $dir
   861  	for _, rmr := range []bool{false, true} {
   862  		rmdir.Body.Recursive = rmr
   863  
   864  		reply = c.Request(vix.CommandDeleteGuestDirectoryEx, rmdir)
   865  		rc = vixRC(reply)
   866  		if rmr {
   867  			if rc != vix.OK {
   868  				t.Fatalf("rc: %d", rc)
   869  			}
   870  		} else {
   871  			if rc != vix.DirectoryNotEmpty {
   872  				t.Fatalf("rc: %d", rc)
   873  			}
   874  		}
   875  	}
   876  }
   877  
   878  func TestVixFileChangeAttributes(t *testing.T) {
   879  	if os.Getuid() == 0 {
   880  		t.Skip("running as root")
   881  	}
   882  
   883  	c := NewCommandClient()
   884  
   885  	f, err := os.CreateTemp("", "toolbox-")
   886  	if err != nil {
   887  		t.Fatal(err)
   888  	}
   889  	_ = f.Close()
   890  	name := f.Name()
   891  
   892  	// touch,chown,chmod
   893  	chattr := &vix.SetGuestFileAttributesRequest{
   894  		GuestPathName: name,
   895  	}
   896  
   897  	h := &chattr.Body
   898  
   899  	tests := []struct {
   900  		expect int
   901  		f      func()
   902  	}{
   903  		{
   904  			vix.OK, func() {},
   905  		},
   906  		{
   907  			vix.OK, func() {
   908  				h.FileOptions = vix.FileAttributeSetModifyDate
   909  				h.ModificationTime = time.Now().Unix()
   910  			},
   911  		},
   912  		{
   913  			vix.OK, func() {
   914  				h.FileOptions = vix.FileAttributeSetAccessDate
   915  				h.AccessTime = time.Now().Unix()
   916  			},
   917  		},
   918  		{
   919  			vix.FileAccessError, func() {
   920  				h.FileOptions = vix.FileAttributeSetUnixOwnerid
   921  				h.OwnerID = 0 // fails as we are not root
   922  			},
   923  		},
   924  		{
   925  			vix.FileAccessError, func() {
   926  				h.FileOptions = vix.FileAttributeSetUnixGroupid
   927  				h.GroupID = 0 // fails as we are not root
   928  			},
   929  		},
   930  		{
   931  			vix.OK, func() {
   932  				h.FileOptions = vix.FileAttributeSetUnixOwnerid
   933  				h.OwnerID = int32(os.Getuid())
   934  			},
   935  		},
   936  		{
   937  			vix.OK, func() {
   938  				h.FileOptions = vix.FileAttributeSetUnixGroupid
   939  				h.GroupID = int32(os.Getgid())
   940  			},
   941  		},
   942  		{
   943  			vix.OK, func() {
   944  				h.FileOptions = vix.FileAttributeSetUnixPermissions
   945  				h.Permissions = int32(os.FileMode(0755).Perm())
   946  			},
   947  		},
   948  		{
   949  			vix.FileNotFound, func() {
   950  				_ = os.Remove(name)
   951  			},
   952  		},
   953  	}
   954  
   955  	for i, test := range tests {
   956  		test.f()
   957  		reply := c.Request(vix.CommandSetGuestFileAttributes, chattr)
   958  		rc := vixRC(reply)
   959  
   960  		if rc != test.expect {
   961  			t.Errorf("%d: rc=%d", i, rc)
   962  		}
   963  	}
   964  }