github.com/vmware/govmomi@v0.43.0/toolbox/hgfs/server.go (about) 1 /* 2 Copyright (c) 2017-2023 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 "errors" 21 "flag" 22 "fmt" 23 "io" 24 "log" 25 "math/rand" 26 "net/url" 27 "os" 28 "path/filepath" 29 "strconv" 30 "strings" 31 "sync" 32 "sync/atomic" 33 ) 34 35 // See: https://github.com/vmware/open-vm-tools/blob/master/open-vm-tools/lib/hgfsServer/hgfsServer.c 36 37 var ( 38 // Trace enables hgfs packet tracing 39 Trace = false 40 ) 41 42 // Server provides an HGFS protocol implementation to support guest tools VmxiHgfsSendPacketCommand 43 type Server struct { 44 Capabilities []Capability 45 46 handlers map[int32]func(*Packet) (interface{}, error) 47 schemes map[string]FileHandler 48 sessions map[uint64]*session 49 mu sync.Mutex 50 handle uint32 51 52 chmod func(string, os.FileMode) error 53 chown func(string, int, int) error 54 } 55 56 // NewServer creates a new Server instance with the default handlers 57 func NewServer() *Server { 58 if f := flag.Lookup("toolbox.trace"); f != nil { 59 Trace, _ = strconv.ParseBool(f.Value.String()) 60 } 61 62 s := &Server{ 63 sessions: make(map[uint64]*session), 64 schemes: make(map[string]FileHandler), 65 chmod: os.Chmod, 66 chown: os.Chown, 67 } 68 69 s.handlers = map[int32]func(*Packet) (interface{}, error){ 70 OpCreateSessionV4: s.CreateSessionV4, 71 OpDestroySessionV4: s.DestroySessionV4, 72 OpGetattrV2: s.GetattrV2, 73 OpSetattrV2: s.SetattrV2, 74 OpOpen: s.Open, 75 OpClose: s.Close, 76 OpOpenV3: s.OpenV3, 77 OpReadV3: s.ReadV3, 78 OpWriteV3: s.WriteV3, 79 } 80 81 for op := range s.handlers { 82 s.Capabilities = append(s.Capabilities, Capability{Op: op, Flags: 0x1}) 83 } 84 85 return s 86 } 87 88 // RegisterFileHandler enables dispatch to handler for the given scheme. 89 func (s *Server) RegisterFileHandler(scheme string, handler FileHandler) { 90 if handler == nil { 91 delete(s.schemes, scheme) 92 return 93 } 94 s.schemes[scheme] = handler 95 } 96 97 // Dispatch unpacks the given request packet and dispatches to the appropriate handler 98 func (s *Server) Dispatch(packet []byte) ([]byte, error) { 99 req := &Packet{} 100 101 err := req.UnmarshalBinary(packet) 102 if err != nil { 103 return nil, err 104 } 105 106 if Trace { 107 fmt.Fprintf(os.Stderr, "[hgfs] request %#v\n", req.Header) 108 } 109 110 var res interface{} 111 112 handler, ok := s.handlers[req.Op] 113 if ok { 114 res, err = handler(req) 115 } else { 116 err = &Status{ 117 Code: StatusOperationNotSupported, 118 Err: fmt.Errorf("unsupported Op(%d)", req.Op), 119 } 120 } 121 122 return req.Reply(res, err) 123 } 124 125 // File interface abstracts standard i/o methods to support transfer 126 // of regular files and archives of directories. 127 type File interface { 128 io.Reader 129 io.Writer 130 io.Closer 131 132 Name() string 133 } 134 135 // FileHandler is the plugin interface for hgfs file transport. 136 type FileHandler interface { 137 Stat(*url.URL) (os.FileInfo, error) 138 Open(*url.URL, int32) (File, error) 139 } 140 141 // urlParse attempts to convert the given name to a URL with scheme for use as FileHandler dispatch. 142 func urlParse(name string) *url.URL { 143 var info os.FileInfo 144 145 u, err := url.Parse(name) 146 if err == nil && u.Scheme == "" { 147 info, err = os.Stat(u.Path) 148 if err == nil && info.IsDir() { 149 u.Scheme = ArchiveScheme // special case for IsDir() 150 return u 151 } 152 } 153 154 u, err = url.Parse(strings.TrimPrefix(name, "/")) // must appear to be an absolute path or hgfs errors 155 if err != nil { 156 u = &url.URL{Path: name} 157 } 158 159 if u.Scheme == "" { 160 ix := strings.Index(u.Path, "/") 161 if ix > 0 { 162 u.Scheme = u.Path[:ix] 163 u.Path = u.Path[ix:] 164 } 165 } 166 167 return u 168 } 169 170 // OpenFile selects the File implementation based on file type and mode. 171 func (s *Server) OpenFile(name string, mode int32) (File, error) { 172 u := urlParse(name) 173 174 if h, ok := s.schemes[u.Scheme]; ok { 175 f, serr := h.Open(u, mode) 176 if serr != os.ErrNotExist { 177 return f, serr 178 } 179 } 180 181 switch mode { 182 case OpenModeReadOnly: 183 return os.Open(filepath.Clean(name)) 184 case OpenModeWriteOnly: 185 flag := os.O_WRONLY | os.O_CREATE | os.O_TRUNC 186 return os.OpenFile(name, flag, 0600) 187 default: 188 return nil, &Status{ 189 Err: fmt.Errorf("open mode(%d) not supported for file %q", mode, name), 190 Code: StatusAccessDenied, 191 } 192 } 193 } 194 195 // Stat wraps os.Stat such that we can report directory types as regular files to support archive streaming. 196 // In the case of standard vmware-tools, attempts to transfer directories result 197 // with a VIX_E_NOT_A_FILE (see InitiateFileTransfer{To,From}Guest). 198 // Note that callers on the VMX side that reach this path are only concerned with: 199 // - does the file exist? 200 // - size: 201 // + used for UI progress with desktop Drag-N-Drop operations, which toolbox does not support. 202 // + sent to as Content-Length header in response to GET of FileTransferInformation.Url, 203 // if the first ReadV3 size is > HGFS_LARGE_PACKET_MAX 204 func (s *Server) Stat(name string) (os.FileInfo, error) { 205 u := urlParse(name) 206 207 if h, ok := s.schemes[u.Scheme]; ok { 208 sinfo, serr := h.Stat(u) 209 if serr != os.ErrNotExist { 210 return sinfo, serr 211 } 212 } 213 214 return os.Stat(name) 215 } 216 217 type session struct { 218 files map[uint32]File 219 mu sync.Mutex 220 } 221 222 // TODO: we currently depend on the VMX to close files and remove sessions, 223 // which it does provided it can communicate with the toolbox. Let's look at 224 // adding session expiration when implementing OpenModeWriteOnly support. 225 func newSession() *session { 226 return &session{ 227 files: make(map[uint32]File), 228 } 229 } 230 231 func (s *Server) getSession(p *Packet) (*session, error) { 232 s.mu.Lock() 233 session, ok := s.sessions[p.SessionID] 234 s.mu.Unlock() 235 236 if !ok { 237 return nil, &Status{ 238 Code: StatusStaleSession, 239 Err: errors.New("session not found"), 240 } 241 } 242 243 return session, nil 244 } 245 246 func (s *Server) removeSession(id uint64) bool { 247 s.mu.Lock() 248 session, ok := s.sessions[id] 249 delete(s.sessions, id) 250 s.mu.Unlock() 251 252 if !ok { 253 return false 254 } 255 256 session.mu.Lock() 257 defer session.mu.Unlock() 258 259 for _, f := range session.files { 260 log.Printf("[hgfs] session %X removed with open file: %s", id, f.Name()) 261 _ = f.Close() 262 } 263 264 return true 265 } 266 267 // open-vm-tools' session max is 1024, there shouldn't be more than a handful at a given time in our use cases 268 const maxSessions = 24 269 270 // CreateSessionV4 handls OpCreateSessionV4 requests 271 func (s *Server) CreateSessionV4(p *Packet) (interface{}, error) { 272 const SessionMaxPacketSizeValid = 0x1 273 274 req := new(RequestCreateSessionV4) 275 err := UnmarshalBinary(p.Payload, req) 276 if err != nil { 277 return nil, err 278 } 279 280 res := &ReplyCreateSessionV4{ 281 SessionID: uint64(rand.Int63()), 282 NumCapabilities: uint32(len(s.Capabilities)), 283 MaxPacketSize: LargePacketMax, 284 Flags: SessionMaxPacketSizeValid, 285 Capabilities: s.Capabilities, 286 } 287 288 s.mu.Lock() 289 defer s.mu.Unlock() 290 if len(s.sessions) > maxSessions { 291 return nil, &Status{Code: StatusTooManySessions} 292 } 293 294 s.sessions[res.SessionID] = newSession() 295 296 return res, nil 297 } 298 299 // DestroySessionV4 handls OpDestroySessionV4 requests 300 func (s *Server) DestroySessionV4(p *Packet) (interface{}, error) { 301 if s.removeSession(p.SessionID) { 302 return &ReplyDestroySessionV4{}, nil 303 } 304 305 return nil, &Status{Code: StatusStaleSession} 306 } 307 308 // Stat maps os.FileInfo to AttrV2 309 func (a *AttrV2) Stat(info os.FileInfo) { 310 switch { 311 case info.IsDir(): 312 a.Type = FileTypeDirectory 313 case info.Mode()&os.ModeSymlink == os.ModeSymlink: 314 a.Type = FileTypeSymlink 315 default: 316 a.Type = FileTypeRegular 317 } 318 319 a.Size = uint64(info.Size()) 320 321 a.Mask = AttrValidType | AttrValidSize 322 323 a.sysStat(info) 324 } 325 326 // GetattrV2 handles OpGetattrV2 requests 327 func (s *Server) GetattrV2(p *Packet) (interface{}, error) { 328 res := &ReplyGetattrV2{} 329 330 req := new(RequestGetattrV2) 331 err := UnmarshalBinary(p.Payload, req) 332 if err != nil { 333 return nil, err 334 } 335 336 name := req.FileName.Path() 337 info, err := s.Stat(name) 338 if err != nil { 339 return nil, err 340 } 341 342 res.Attr.Stat(info) 343 344 return res, nil 345 } 346 347 // SetattrV2 handles OpSetattrV2 requests 348 func (s *Server) SetattrV2(p *Packet) (interface{}, error) { 349 res := &ReplySetattrV2{} 350 351 req := new(RequestSetattrV2) 352 err := UnmarshalBinary(p.Payload, req) 353 if err != nil { 354 return nil, err 355 } 356 357 name := req.FileName.Path() 358 359 _, err = os.Stat(name) 360 if err != nil && os.IsNotExist(err) { 361 // assuming this is a virtual file 362 return res, nil 363 } 364 365 uid := -1 366 if req.Attr.Mask&AttrValidUserID == AttrValidUserID { 367 uid = int(req.Attr.UserID) 368 } 369 370 gid := -1 371 if req.Attr.Mask&AttrValidGroupID == AttrValidGroupID { 372 gid = int(req.Attr.GroupID) 373 } 374 375 err = s.chown(name, uid, gid) 376 if err != nil { 377 return nil, err 378 } 379 380 var perm os.FileMode 381 382 if req.Attr.Mask&AttrValidOwnerPerms == AttrValidOwnerPerms { 383 perm |= os.FileMode(req.Attr.OwnerPerms) << 6 384 } 385 386 if req.Attr.Mask&AttrValidGroupPerms == AttrValidGroupPerms { 387 perm |= os.FileMode(req.Attr.GroupPerms) << 3 388 } 389 390 if req.Attr.Mask&AttrValidOtherPerms == AttrValidOtherPerms { 391 perm |= os.FileMode(req.Attr.OtherPerms) 392 } 393 394 if perm != 0 { 395 err = s.chmod(name, perm) 396 if err != nil { 397 return nil, err 398 } 399 } 400 401 return res, nil 402 } 403 404 func (s *Server) newHandle() uint32 { 405 return atomic.AddUint32(&s.handle, 1) 406 } 407 408 // Open handles OpOpen requests 409 func (s *Server) Open(p *Packet) (interface{}, error) { 410 req := new(RequestOpen) 411 err := UnmarshalBinary(p.Payload, req) 412 if err != nil { 413 return nil, err 414 } 415 416 session, err := s.getSession(p) 417 if err != nil { 418 return nil, err 419 } 420 421 name := req.FileName.Path() 422 mode := req.OpenMode 423 424 if mode != OpenModeReadOnly { 425 return nil, &Status{ 426 Err: fmt.Errorf("open mode(%d) not supported for file %q", mode, name), 427 Code: StatusAccessDenied, 428 } 429 } 430 431 file, err := s.OpenFile(name, mode) 432 if err != nil { 433 return nil, err 434 } 435 436 res := &ReplyOpen{ 437 Handle: s.newHandle(), 438 } 439 440 session.mu.Lock() 441 session.files[res.Handle] = file 442 session.mu.Unlock() 443 444 return res, nil 445 } 446 447 // Close handles OpClose requests 448 func (s *Server) Close(p *Packet) (interface{}, error) { 449 req := new(RequestClose) 450 err := UnmarshalBinary(p.Payload, req) 451 if err != nil { 452 return nil, err 453 } 454 455 session, err := s.getSession(p) 456 if err != nil { 457 return nil, err 458 } 459 460 session.mu.Lock() 461 file, ok := session.files[req.Handle] 462 if ok { 463 delete(session.files, req.Handle) 464 } 465 session.mu.Unlock() 466 467 if ok { 468 err = file.Close() 469 } else { 470 return nil, &Status{Code: StatusInvalidHandle} 471 } 472 473 return &ReplyClose{}, err 474 } 475 476 // OpenV3 handles OpOpenV3 requests 477 func (s *Server) OpenV3(p *Packet) (interface{}, error) { 478 req := new(RequestOpenV3) 479 err := UnmarshalBinary(p.Payload, req) 480 if err != nil { 481 return nil, err 482 } 483 484 session, err := s.getSession(p) 485 if err != nil { 486 return nil, err 487 } 488 489 name := req.FileName.Path() 490 491 if req.DesiredLock != LockNone { 492 return nil, &Status{ 493 Err: fmt.Errorf("open lock type=%d not supported for file %q", req.DesiredLock, name), 494 Code: StatusOperationNotSupported, 495 } 496 } 497 498 file, err := s.OpenFile(name, req.OpenMode) 499 if err != nil { 500 return nil, err 501 } 502 503 res := &ReplyOpenV3{ 504 Handle: s.newHandle(), 505 } 506 507 session.mu.Lock() 508 session.files[res.Handle] = file 509 session.mu.Unlock() 510 511 return res, nil 512 } 513 514 // ReadV3 handles OpReadV3 requests 515 func (s *Server) ReadV3(p *Packet) (interface{}, error) { 516 req := new(RequestReadV3) 517 err := UnmarshalBinary(p.Payload, req) 518 if err != nil { 519 return nil, err 520 } 521 522 session, err := s.getSession(p) 523 if err != nil { 524 return nil, err 525 } 526 527 session.mu.Lock() 528 file, ok := session.files[req.Handle] 529 session.mu.Unlock() 530 531 if !ok { 532 return nil, &Status{Code: StatusInvalidHandle} 533 } 534 535 buf := make([]byte, req.RequiredSize) 536 537 // Use ReadFull as Read() of an archive io.Pipe may return much smaller chunks, 538 // such as when we've read a tar header. 539 n, err := io.ReadFull(file, buf) 540 if err != nil && n == 0 { 541 if err != io.EOF { 542 return nil, err 543 } 544 } 545 546 res := &ReplyReadV3{ 547 ActualSize: uint32(n), 548 Payload: buf[:n], 549 } 550 551 return res, nil 552 } 553 554 // WriteV3 handles OpWriteV3 requests 555 func (s *Server) WriteV3(p *Packet) (interface{}, error) { 556 req := new(RequestWriteV3) 557 err := UnmarshalBinary(p.Payload, req) 558 if err != nil { 559 return nil, err 560 } 561 562 session, err := s.getSession(p) 563 if err != nil { 564 return nil, err 565 } 566 567 session.mu.Lock() 568 file, ok := session.files[req.Handle] 569 session.mu.Unlock() 570 571 if !ok { 572 return nil, &Status{Code: StatusInvalidHandle} 573 } 574 575 n, err := file.Write(req.Payload) 576 if err != nil { 577 return nil, err 578 } 579 580 res := &ReplyWriteV3{ 581 ActualSize: uint32(n), 582 } 583 584 return res, nil 585 }