github.com/vmware/govmomi@v0.51.0/toolbox/hgfs/server_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 hgfs
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"path"
    11  	"runtime"
    12  	"testing"
    13  )
    14  
    15  type Client struct {
    16  	s         *Server
    17  	SessionID uint64
    18  }
    19  
    20  func NewClient() *Client {
    21  	s := NewServer()
    22  
    23  	return &Client{
    24  		s: s,
    25  	}
    26  }
    27  
    28  func (c *Client) Dispatch(op int32, req any, res any) *Packet {
    29  	var err error
    30  	p := new(Packet)
    31  	p.Payload, err = MarshalBinary(req)
    32  	if err != nil {
    33  		panic(err)
    34  	}
    35  
    36  	p.Header.Version = 0x1
    37  	p.Header.Dummy = OpNewHeader
    38  	p.Header.HeaderSize = headerSize
    39  	p.Header.PacketSize = headerSize + uint32(len(p.Payload))
    40  	p.Header.SessionID = c.SessionID
    41  	p.Header.Op = op
    42  
    43  	data, err := p.MarshalBinary()
    44  	if err != nil {
    45  		panic(err)
    46  	}
    47  
    48  	data, err = c.s.Dispatch(data)
    49  	if err != nil {
    50  		panic(err)
    51  	}
    52  
    53  	p = new(Packet)
    54  	err = p.UnmarshalBinary(data)
    55  	if err != nil {
    56  		panic(err)
    57  	}
    58  
    59  	if p.Status == StatusSuccess {
    60  		err = UnmarshalBinary(p.Payload, res)
    61  		if err != nil {
    62  			panic(err)
    63  		}
    64  	}
    65  
    66  	return p
    67  }
    68  
    69  func (c *Client) CreateSession() uint32 {
    70  	req := new(RequestCreateSessionV4)
    71  	res := new(ReplyCreateSessionV4)
    72  
    73  	p := c.Dispatch(OpCreateSessionV4, req, res)
    74  
    75  	if p.Status == StatusSuccess {
    76  		c.SessionID = res.SessionID
    77  	}
    78  
    79  	return p.Status
    80  }
    81  
    82  func (c *Client) DestroySession() uint32 {
    83  	req := new(RequestDestroySessionV4)
    84  	res := new(ReplyDestroySessionV4)
    85  
    86  	return c.Dispatch(OpDestroySessionV4, req, res).Status
    87  }
    88  
    89  func (c *Client) GetAttr(name string) (*AttrV2, uint32) {
    90  	req := new(RequestGetattrV2)
    91  	res := new(ReplyGetattrV2)
    92  
    93  	req.FileName.FromString(name)
    94  
    95  	p := c.Dispatch(OpGetattrV2, req, res)
    96  
    97  	if p.Status != StatusSuccess {
    98  		return nil, p.Status
    99  	}
   100  
   101  	return &res.Attr, p.Status
   102  }
   103  
   104  func (c *Client) SetAttr(name string, attr AttrV2) uint32 {
   105  	req := new(RequestSetattrV2)
   106  	res := new(ReplySetattrV2)
   107  
   108  	req.FileName.FromString(name)
   109  
   110  	req.Attr = attr
   111  
   112  	p := c.Dispatch(OpSetattrV2, req, res)
   113  
   114  	if p.Status != StatusSuccess {
   115  		return p.Status
   116  	}
   117  
   118  	return p.Status
   119  }
   120  
   121  func (c *Client) Open(name string, write ...bool) (uint32, uint32) {
   122  	req := new(RequestOpen)
   123  	res := new(ReplyOpen)
   124  
   125  	if len(write) == 1 && write[0] {
   126  		req.OpenMode = OpenModeWriteOnly
   127  	}
   128  
   129  	req.FileName.FromString(name)
   130  
   131  	p := c.Dispatch(OpOpen, req, res)
   132  	if p.Status != StatusSuccess {
   133  		return 0, p.Status
   134  	}
   135  
   136  	return res.Handle, p.Status
   137  }
   138  
   139  func (c *Client) OpenWrite(name string) (uint32, uint32) {
   140  	req := new(RequestOpenV3)
   141  	res := new(ReplyOpenV3)
   142  
   143  	req.OpenMode = OpenModeWriteOnly
   144  	req.OpenFlags = OpenCreateEmpty
   145  	req.FileName.FromString(name)
   146  
   147  	p := c.Dispatch(OpOpenV3, req, res)
   148  	if p.Status != StatusSuccess {
   149  		return 0, p.Status
   150  	}
   151  
   152  	// cover the unsupported lock type path
   153  	req.DesiredLock = LockOpportunistic
   154  	status := c.Dispatch(OpOpenV3, req, res).Status
   155  	if status != StatusOperationNotSupported {
   156  		return 0, status
   157  	}
   158  
   159  	// cover the unsupported open mode path
   160  	req.DesiredLock = LockNone
   161  	req.OpenMode = OpenCreateSafe
   162  	status = c.Dispatch(OpOpenV3, req, res).Status
   163  	if status != StatusAccessDenied {
   164  		return 0, status
   165  	}
   166  
   167  	return res.Handle, p.Status
   168  }
   169  
   170  func (c *Client) Close(handle uint32) uint32 {
   171  	req := new(RequestClose)
   172  	res := new(ReplyClose)
   173  
   174  	req.Handle = handle
   175  
   176  	return c.Dispatch(OpClose, req, res).Status
   177  }
   178  
   179  func TestStaleSession(t *testing.T) {
   180  	c := NewClient()
   181  
   182  	// list of methods that can return StatusStaleSession
   183  	invalid := []func() uint32{
   184  		func() uint32 { _, status := c.Open("enoent"); return status },
   185  		func() uint32 { return c.Dispatch(OpReadV3, new(RequestReadV3), new(ReplyReadV3)).Status },
   186  		func() uint32 { return c.Dispatch(OpWriteV3, new(RequestWriteV3), new(ReplyWriteV3)).Status },
   187  		func() uint32 { return c.Close(0) },
   188  		c.DestroySession,
   189  	}
   190  
   191  	for i, f := range invalid {
   192  		status := f()
   193  		if status != StatusStaleSession {
   194  			t.Errorf("%d: status=%d", i, status)
   195  		}
   196  	}
   197  }
   198  
   199  func TestSessionMax(t *testing.T) {
   200  	c := NewClient()
   201  	var status uint32
   202  
   203  	for i := 0; i <= maxSessions+1; i++ {
   204  		status = c.CreateSession()
   205  	}
   206  
   207  	if status != StatusTooManySessions {
   208  		t.Errorf("status=%d", status)
   209  	}
   210  }
   211  
   212  func TestSessionDestroy(t *testing.T) {
   213  	Trace = true
   214  	c := NewClient()
   215  	c.CreateSession()
   216  	_, status := c.Open("/etc/resolv.conf")
   217  	if status != StatusSuccess {
   218  		t.Errorf("status=%d", status)
   219  	}
   220  	c.DestroySession()
   221  
   222  	if c.s.removeSession(c.SessionID) {
   223  		t.Error("session was not removed")
   224  	}
   225  }
   226  
   227  func TestInvalidOp(t *testing.T) {
   228  	c := NewClient()
   229  	status := c.Dispatch(1024, new(RequestClose), new(ReplyClose)).Status
   230  	if status != StatusOperationNotSupported {
   231  		t.Errorf("status=%d", status)
   232  	}
   233  }
   234  
   235  func TestReadV3(t *testing.T) {
   236  	Trace = testing.Verbose()
   237  
   238  	c := NewClient()
   239  	c.CreateSession()
   240  
   241  	_, status := c.GetAttr("enoent")
   242  
   243  	if status != StatusNoSuchFileOrDir {
   244  		t.Errorf("status=%d", status)
   245  	}
   246  
   247  	_, status = c.Open("enoent")
   248  	if status != StatusNoSuchFileOrDir {
   249  		t.Errorf("status=%d", status)
   250  	}
   251  
   252  	fname := "/etc/resolv.conf"
   253  
   254  	attr, _ := c.GetAttr(path.Dir(fname))
   255  	if attr.Type != FileTypeDirectory {
   256  		t.Errorf("type=%d", attr.Type)
   257  	}
   258  
   259  	attr, _ = c.GetAttr(fname)
   260  	if attr.Type != FileTypeRegular {
   261  		t.Errorf("type=%d", attr.Type)
   262  	}
   263  
   264  	if attr.Size == 0 {
   265  		t.Errorf("size=%d", attr.Size)
   266  	}
   267  
   268  	handle, status := c.Open(fname)
   269  	if status != StatusSuccess {
   270  		t.Fatalf("status=%d", status)
   271  	}
   272  
   273  	var req *RequestReadV3
   274  	var offset uint64
   275  	size := uint32(attr.Size / 2)
   276  
   277  	for offset = 0; offset < attr.Size; {
   278  		req = &RequestReadV3{
   279  			Offset:       offset,
   280  			Handle:       handle,
   281  			RequiredSize: size,
   282  		}
   283  
   284  		res := new(ReplyReadV3)
   285  
   286  		status = c.Dispatch(OpReadV3, req, res).Status
   287  
   288  		if status != StatusSuccess {
   289  			t.Fatalf("status=%d", status)
   290  		}
   291  
   292  		if Trace {
   293  			fmt.Fprintf(os.Stderr, "read %d: %q\n", res.ActualSize, string(res.Payload))
   294  		}
   295  
   296  		offset += uint64(res.ActualSize)
   297  	}
   298  
   299  	if uint64(offset) != attr.Size {
   300  		t.Errorf("size %d vs %d", offset, attr.Size)
   301  	}
   302  
   303  	status = c.Dispatch(OpReadV3, new(RequestReadV3), new(ReplyReadV3)).Status
   304  	if status != StatusInvalidHandle {
   305  		t.Fatalf("status=%d", status)
   306  	}
   307  
   308  	status = c.Close(0)
   309  	if status != StatusInvalidHandle {
   310  		t.Fatalf("status=%d", status)
   311  	}
   312  
   313  	status = c.Close(handle)
   314  	if status != StatusSuccess {
   315  		t.Fatalf("status=%d", status)
   316  	}
   317  
   318  	status = c.DestroySession()
   319  	if status != StatusSuccess {
   320  		t.Fatalf("status=%d", status)
   321  	}
   322  }
   323  
   324  func TestWriteV3(t *testing.T) {
   325  	if runtime.GOOS != "linux" {
   326  		t.Skip("requires Linux")
   327  	}
   328  
   329  	Trace = testing.Verbose()
   330  
   331  	f, err := os.CreateTemp("", "toolbox")
   332  	if err != nil {
   333  		t.Fatal(err)
   334  	}
   335  	_ = f.Close()
   336  
   337  	name := f.Name()
   338  
   339  	c := NewClient()
   340  	c.CreateSession()
   341  
   342  	_, status := c.Open("enoent", true)
   343  	// write not supported yet
   344  	if status != StatusAccessDenied {
   345  		t.Errorf("status=%d", status)
   346  	}
   347  
   348  	handle, status := c.OpenWrite(name)
   349  	if status != StatusSuccess {
   350  		t.Fatalf("status=%d", status)
   351  	}
   352  
   353  	payload := []byte("one two three\n")
   354  	size := uint32(len(payload))
   355  
   356  	req := &RequestWriteV3{
   357  		Handle:       handle,
   358  		WriteFlags:   WriteAppend,
   359  		Offset:       0,
   360  		RequiredSize: size,
   361  		Payload:      payload,
   362  	}
   363  
   364  	res := new(ReplyReadV3)
   365  
   366  	status = c.Dispatch(OpWriteV3, req, res).Status
   367  
   368  	if status != StatusSuccess {
   369  		t.Errorf("status=%d", status)
   370  	}
   371  
   372  	if size != res.ActualSize {
   373  		t.Errorf("%d vs %d", size, res.ActualSize)
   374  	}
   375  
   376  	status = c.Dispatch(OpWriteV3, new(RequestWriteV3), new(ReplyWriteV3)).Status
   377  	if status != StatusInvalidHandle {
   378  		t.Fatalf("status=%d", status)
   379  	}
   380  
   381  	status = c.Close(handle)
   382  	if status != StatusSuccess {
   383  		t.Errorf("status=%d", status)
   384  	}
   385  
   386  	attr, _ := c.GetAttr(name)
   387  	if attr.Size != uint64(size) {
   388  		t.Errorf("%d vs %d", size, attr.Size)
   389  	}
   390  
   391  	attr.OwnerPerms |= PermExec
   392  
   393  	errors := []struct {
   394  		err    error
   395  		status uint32
   396  	}{
   397  		{os.ErrPermission, StatusOperationNotPermitted},
   398  		{os.ErrNotExist, StatusNoSuchFileOrDir},
   399  		{os.ErrExist, StatusFileExists},
   400  		{nil, StatusSuccess},
   401  	}
   402  
   403  	for _, e := range errors {
   404  		c.s.chown = func(_ string, _ int, _ int) error {
   405  			return e.err
   406  		}
   407  
   408  		status = c.SetAttr(name, *attr)
   409  		if status != e.status {
   410  			t.Errorf("status=%d", status)
   411  		}
   412  	}
   413  
   414  	c.s.chown = func(_ string, _ int, _ int) error {
   415  		return nil
   416  	}
   417  
   418  	for _, e := range errors {
   419  		c.s.chmod = func(_ string, _ os.FileMode) error {
   420  			return e.err
   421  		}
   422  
   423  		status = c.SetAttr(name, *attr)
   424  		if status != e.status {
   425  			t.Errorf("status=%d", status)
   426  		}
   427  	}
   428  
   429  	status = c.DestroySession()
   430  	if status != StatusSuccess {
   431  		t.Errorf("status=%d", status)
   432  	}
   433  
   434  	_ = os.Remove(name)
   435  }