github.com/vmware/govmomi@v0.37.2/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  }