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 }