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