go-hep.org/x/hep@v0.38.1/xrootd/fshandler.go (about)

     1  // Copyright ©2018 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package xrootd // import "go-hep.org/x/hep/xrootd"
     6  
     7  import (
     8  	"crypto/rand"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path"
    13  	"sync"
    14  
    15  	"go-hep.org/x/hep/xrootd/xrdfs"
    16  	"go-hep.org/x/hep/xrootd/xrdproto"
    17  	"go-hep.org/x/hep/xrootd/xrdproto/dirlist"
    18  	"go-hep.org/x/hep/xrootd/xrdproto/mkdir"
    19  	"go-hep.org/x/hep/xrootd/xrdproto/mv"
    20  	"go-hep.org/x/hep/xrootd/xrdproto/open"
    21  	"go-hep.org/x/hep/xrootd/xrdproto/read"
    22  	"go-hep.org/x/hep/xrootd/xrdproto/rm"
    23  	"go-hep.org/x/hep/xrootd/xrdproto/rmdir"
    24  	"go-hep.org/x/hep/xrootd/xrdproto/stat"
    25  	xrdsync "go-hep.org/x/hep/xrootd/xrdproto/sync"
    26  	"go-hep.org/x/hep/xrootd/xrdproto/truncate"
    27  	"go-hep.org/x/hep/xrootd/xrdproto/write"
    28  	"go-hep.org/x/hep/xrootd/xrdproto/xrdclose"
    29  )
    30  
    31  // fshandler implements server.Handler API by making request to the backing filesystem at basePath.
    32  type fshandler struct {
    33  	Handler
    34  	basePath string
    35  
    36  	// map + RWMutex works a bit faster and with significant lower memory usage under Linux
    37  	// than sync.Map for given scenarios (write to map once per session and a lot of reads per session).
    38  	mu       sync.RWMutex
    39  	sessions map[[16]byte]*srvSession
    40  }
    41  
    42  type srvSession struct {
    43  	mu      sync.Mutex
    44  	handles map[xrdfs.FileHandle]*os.File
    45  }
    46  
    47  // NewFSHandler creates a Handler that passes requests to the backing filesystem at basePath.
    48  func NewFSHandler(basePath string) Handler {
    49  	return &fshandler{
    50  		Handler:  Default(),
    51  		basePath: basePath,
    52  		sessions: make(map[[16]byte]*srvSession),
    53  	}
    54  }
    55  
    56  // Dirlist implements server.Handler.Dirlist.
    57  func (h *fshandler) Dirlist(sessionID [16]byte, request *dirlist.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
    58  	files, err := os.ReadDir(path.Join(h.basePath, request.Path))
    59  	if err != nil {
    60  		return xrdproto.ServerError{
    61  			Code:    xrdproto.IOError,
    62  			Message: fmt.Sprintf("An IO error occurred: %v", err),
    63  		}, xrdproto.Error
    64  	}
    65  
    66  	resp := &dirlist.Response{
    67  		WithStatInfo: request.Options&dirlist.WithStatInfo != 0,
    68  		Entries:      make([]xrdfs.EntryStat, 0, len(files)),
    69  	}
    70  
    71  	for _, file := range files {
    72  		info, err := file.Info()
    73  		if err != nil {
    74  			return xrdproto.ServerError{
    75  				Code:    xrdproto.IOError,
    76  				Message: fmt.Sprintf("An IO error occurred: %+v", err),
    77  			}, xrdproto.Error
    78  		}
    79  		entry := xrdfs.EntryStatFrom(info)
    80  		entry.HasStatInfo = resp.WithStatInfo
    81  		resp.Entries = append(resp.Entries, entry)
    82  	}
    83  
    84  	return resp, xrdproto.Ok
    85  }
    86  
    87  // Open implements server.Handler.Open.
    88  func (h *fshandler) Open(sessionID [16]byte, request *open.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
    89  	var flag int
    90  	if request.Options&xrdfs.OpenOptionsOpenRead != 0 {
    91  		flag |= os.O_RDONLY
    92  	}
    93  	if request.Options&xrdfs.OpenOptionsOpenUpdate != 0 {
    94  		flag |= os.O_RDWR
    95  	}
    96  	if request.Options&xrdfs.OpenOptionsOpenAppend != 0 {
    97  		flag |= os.O_APPEND
    98  	}
    99  	if request.Options&xrdfs.OpenOptionsNew != 0 || request.Options&xrdfs.OpenOptionsDelete != 0 {
   100  		flag |= os.O_CREATE
   101  		if request.Options&xrdfs.OpenOptionsDelete == 0 {
   102  			flag |= os.O_EXCL
   103  		} else {
   104  			flag |= os.O_TRUNC
   105  		}
   106  	}
   107  
   108  	filePath := path.Join(h.basePath, request.Path)
   109  	if request.Options&xrdfs.OpenOptionsMkPath != 0 {
   110  		if err := os.MkdirAll(path.Dir(filePath), os.FileMode(request.Mode)); err != nil {
   111  			return xrdproto.ServerError{
   112  				Code:    xrdproto.IOError,
   113  				Message: fmt.Sprintf("An IO error occurred: %v", err),
   114  			}, xrdproto.Error
   115  		}
   116  	}
   117  
   118  	file, err := os.OpenFile(filePath, flag, os.FileMode(request.Mode))
   119  	if err != nil {
   120  		return xrdproto.ServerError{
   121  			Code:    xrdproto.IOError,
   122  			Message: fmt.Sprintf("An IO error occurred: %v", err),
   123  		}, xrdproto.Error
   124  	}
   125  
   126  	h.mu.RLock()
   127  	sess, ok := h.sessions[sessionID]
   128  	h.mu.RUnlock()
   129  	if !ok {
   130  		h.mu.Lock()
   131  		// Check that there was no change in state during h.mu.RUnlock and h.mu.Lock.
   132  		sess, ok = h.sessions[sessionID]
   133  		if !ok {
   134  			sess = &srvSession{handles: make(map[xrdfs.FileHandle]*os.File)}
   135  			h.sessions[sessionID] = sess
   136  		}
   137  		h.mu.Unlock()
   138  	}
   139  
   140  	sess.mu.Lock()
   141  	defer sess.mu.Unlock()
   142  	var handle xrdfs.FileHandle
   143  
   144  	// TODO: make handle obtain more deterministic.
   145  	// Right now, we hope that even if 1000000000 of 256*256*256*256 handles are obtained by single user,
   146  	// we have appr. 0.7 probability to find a free handle by the random guess.
   147  	// Then, probability that no free handle is found by 100 tries is something near pow(0.3,100) = 1e-53.
   148  	for range 100 {
   149  		rand.Read(handle[:])
   150  		if _, dup := sess.handles[handle]; !dup {
   151  			resp := open.Response{FileHandle: handle}
   152  			if request.Options&xrdfs.OpenOptionsReturnStatus != 0 {
   153  				st, err := file.Stat()
   154  				if err != nil {
   155  					return xrdproto.ServerError{
   156  						Code:    xrdproto.IOError,
   157  						Message: fmt.Sprintf("An IO error occurred: %v", err),
   158  					}, xrdproto.Error
   159  				}
   160  				es := xrdfs.EntryStatFrom(st)
   161  				resp.Stat = &es
   162  				if request.Options&xrdfs.OpenOptionsCompress == 0 {
   163  					resp.Compression = &xrdfs.FileCompression{}
   164  				}
   165  			}
   166  			// TODO: return compression info if requested.
   167  			sess.handles[handle] = file
   168  
   169  			return resp, xrdproto.Ok
   170  		}
   171  	}
   172  
   173  	return xrdproto.ServerError{
   174  		Code:    xrdproto.InvalidRequest,
   175  		Message: "handle limit exceeded",
   176  	}, xrdproto.Error
   177  }
   178  
   179  // Close implements server.Handler.Close.
   180  func (h *fshandler) Close(sessionID [16]byte, request *xrdclose.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
   181  	h.mu.RLock()
   182  	sess, ok := h.sessions[sessionID]
   183  	h.mu.RUnlock()
   184  	if !ok {
   185  		// This situation can appear if user tries to close without opening any file at all.
   186  		return xrdproto.ServerError{
   187  			Code:    xrdproto.InvalidRequest,
   188  			Message: fmt.Sprintf("Invalid file handle: %v", request.Handle),
   189  		}, xrdproto.Error
   190  	}
   191  	sess.mu.Lock()
   192  	defer sess.mu.Unlock()
   193  	file, ok := sess.handles[request.Handle]
   194  	if !ok {
   195  		return xrdproto.ServerError{
   196  			Code:    xrdproto.InvalidRequest,
   197  			Message: fmt.Sprintf("Invalid file handle: %v", request.Handle),
   198  		}, xrdproto.Error
   199  	}
   200  	delete(sess.handles, request.Handle)
   201  	err := file.Close()
   202  	if err != nil {
   203  		return xrdproto.ServerError{
   204  			Code:    xrdproto.IOError,
   205  			Message: fmt.Sprintf("An IO error occurred: %v", err),
   206  		}, xrdproto.Error
   207  	}
   208  	return nil, xrdproto.Ok
   209  }
   210  
   211  // Read implements server.Handler.Read.
   212  func (h *fshandler) Read(sessionID [16]byte, request *read.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
   213  	file := h.getFile(sessionID, request.Handle)
   214  	if file == nil {
   215  		return xrdproto.ServerError{
   216  			Code:    xrdproto.InvalidRequest,
   217  			Message: fmt.Sprintf("Invalid file handle: %v", request.Handle),
   218  		}, xrdproto.Error
   219  	}
   220  
   221  	buf := make([]byte, request.Length)
   222  	n, err := file.ReadAt(buf, request.Offset)
   223  	if err != nil && err != io.EOF {
   224  		return xrdproto.ServerError{
   225  			Code:    xrdproto.IOError,
   226  			Message: fmt.Sprintf("An IO error occurred: %v", err),
   227  		}, xrdproto.Error
   228  	}
   229  
   230  	return read.Response{Data: buf[:n]}, xrdproto.Ok
   231  }
   232  
   233  // Write implements server.Handler.Write.
   234  func (h *fshandler) Write(sessionID [16]byte, request *write.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
   235  	file := h.getFile(sessionID, request.Handle)
   236  	if file == nil {
   237  		return xrdproto.ServerError{
   238  			Code:    xrdproto.InvalidRequest,
   239  			Message: fmt.Sprintf("Invalid file handle: %v", request.Handle),
   240  		}, xrdproto.Error
   241  	}
   242  
   243  	_, err := file.WriteAt(request.Data, request.Offset)
   244  	if err != nil {
   245  		return xrdproto.ServerError{
   246  			Code:    xrdproto.IOError,
   247  			Message: fmt.Sprintf("An IO error occurred: %v", err),
   248  		}, xrdproto.Error
   249  	}
   250  
   251  	return nil, xrdproto.Ok
   252  }
   253  
   254  func (h *fshandler) getFile(sessionID [16]byte, handle xrdfs.FileHandle) *os.File {
   255  	h.mu.RLock()
   256  	sess, ok := h.sessions[sessionID]
   257  	h.mu.RUnlock()
   258  	if !ok {
   259  		return nil
   260  	}
   261  	sess.mu.Lock()
   262  	defer sess.mu.Unlock()
   263  	file, ok := sess.handles[handle]
   264  	if !ok {
   265  		return nil
   266  	}
   267  	return file
   268  }
   269  
   270  // Stat implements server.Handler.Stat.
   271  func (h *fshandler) Stat(sessionID [16]byte, request *stat.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
   272  	if request.Options&stat.OptionsVFS != 0 {
   273  		// TODO: handle virtual stat info.
   274  		return xrdproto.ServerError{
   275  			Code:    xrdproto.InvalidRequest,
   276  			Message: "Stat request with OptionsVFS is not implemented",
   277  		}, xrdproto.Error
   278  	}
   279  
   280  	var fi os.FileInfo
   281  	var err error
   282  	if len(request.Path) == 0 {
   283  		file := h.getFile(sessionID, request.FileHandle)
   284  		if file == nil {
   285  			return xrdproto.ServerError{
   286  				Code:    xrdproto.InvalidRequest,
   287  				Message: fmt.Sprintf("Invalid file handle: %v", request.FileHandle),
   288  			}, xrdproto.Error
   289  		}
   290  		fi, err = file.Stat()
   291  	} else {
   292  		fi, err = os.Stat(path.Join(h.basePath, request.Path))
   293  	}
   294  
   295  	if err != nil {
   296  		return xrdproto.ServerError{
   297  			Code:    xrdproto.IOError,
   298  			Message: fmt.Sprintf("An IO error occurred: %v", err),
   299  		}, xrdproto.Error
   300  	}
   301  
   302  	return stat.DefaultResponse{EntryStat: xrdfs.EntryStatFrom(fi)}, xrdproto.Ok
   303  }
   304  
   305  // Truncate implements server.Handler.Truncate.
   306  func (h *fshandler) Truncate(sessionID [16]byte, request *truncate.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
   307  	var err error
   308  	if len(request.Path) == 0 {
   309  		file := h.getFile(sessionID, request.Handle)
   310  		if file == nil {
   311  			return xrdproto.ServerError{
   312  				Code:    xrdproto.InvalidRequest,
   313  				Message: fmt.Sprintf("Invalid file handle: %v", request.Handle),
   314  			}, xrdproto.Error
   315  		}
   316  		err = file.Truncate(request.Size)
   317  	} else {
   318  		err = os.Truncate(path.Join(h.basePath, request.Path), request.Size)
   319  	}
   320  
   321  	if err != nil {
   322  		return xrdproto.ServerError{
   323  			Code:    xrdproto.IOError,
   324  			Message: fmt.Sprintf("An IO error occurred: %v", err),
   325  		}, xrdproto.Error
   326  	}
   327  
   328  	return nil, xrdproto.Ok
   329  }
   330  
   331  // Sync implements server.Handler.Sync.
   332  func (h *fshandler) Sync(sessionID [16]byte, request *xrdsync.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
   333  	file := h.getFile(sessionID, request.Handle)
   334  	if file == nil {
   335  		return xrdproto.ServerError{
   336  			Code:    xrdproto.InvalidRequest,
   337  			Message: fmt.Sprintf("Invalid file handle: %v", request.Handle),
   338  		}, xrdproto.Error
   339  	}
   340  
   341  	if err := file.Sync(); err != nil {
   342  		return xrdproto.ServerError{
   343  			Code:    xrdproto.IOError,
   344  			Message: fmt.Sprintf("An IO error occurred: %v", err),
   345  		}, xrdproto.Error
   346  	}
   347  
   348  	return nil, xrdproto.Ok
   349  }
   350  
   351  // Rename implements server.Handler.Rename.
   352  func (h *fshandler) Rename(sessionID [16]byte, request *mv.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
   353  	if err := os.Rename(path.Join(h.basePath, request.OldPath), path.Join(h.basePath, request.NewPath)); err != nil {
   354  		return xrdproto.ServerError{
   355  			Code:    xrdproto.IOError,
   356  			Message: fmt.Sprintf("An IO error occurred: %v", err),
   357  		}, xrdproto.Error
   358  	}
   359  
   360  	return nil, xrdproto.Ok
   361  }
   362  
   363  // Mkdir implements server.Handler.Mkdir.
   364  func (h *fshandler) Mkdir(sessionID [16]byte, request *mkdir.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
   365  	mkdirFunc := os.Mkdir
   366  	if request.Options&mkdir.OptionsMakePath != 0 {
   367  		mkdirFunc = os.MkdirAll
   368  	}
   369  
   370  	if err := mkdirFunc(path.Join(h.basePath, request.Path), os.FileMode(request.Mode)); err != nil {
   371  		return xrdproto.ServerError{
   372  			Code:    xrdproto.IOError,
   373  			Message: fmt.Sprintf("An IO error occurred: %v", err),
   374  		}, xrdproto.Error
   375  	}
   376  	return nil, xrdproto.Ok
   377  }
   378  
   379  // Remove implements server.Handler.Remove.
   380  func (h *fshandler) Remove(sessionID [16]byte, request *rm.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
   381  	if err := os.Remove(path.Join(h.basePath, request.Path)); err != nil {
   382  		return xrdproto.ServerError{
   383  			Code:    xrdproto.IOError,
   384  			Message: fmt.Sprintf("An IO error occurred: %v", err),
   385  		}, xrdproto.Error
   386  	}
   387  	return nil, xrdproto.Ok
   388  }
   389  
   390  // RemoveDir implements server.Handler.RemoveDir.
   391  func (h *fshandler) RemoveDir(sessionID [16]byte, request *rmdir.Request) (xrdproto.Marshaler, xrdproto.ResponseStatus) {
   392  	if err := os.Remove(path.Join(h.basePath, request.Path)); err != nil {
   393  		return xrdproto.ServerError{
   394  			Code:    xrdproto.IOError,
   395  			Message: fmt.Sprintf("An IO error occurred: %v", err),
   396  		}, xrdproto.Error
   397  	}
   398  	return nil, xrdproto.Ok
   399  }
   400  
   401  // CloseSession implements server.Handler.CloseSession.
   402  func (h *fshandler) CloseSession(sessionID [16]byte) error {
   403  	h.mu.Lock()
   404  	sess, ok := h.sessions[sessionID]
   405  	if !ok {
   406  		// That means that no files were opened in that session and we have nothing to clear.
   407  		h.mu.Unlock()
   408  		return nil
   409  	}
   410  	delete(h.sessions, sessionID)
   411  	h.mu.Unlock()
   412  	sess.mu.Lock()
   413  	defer sess.mu.Unlock()
   414  
   415  	var err error
   416  	for _, f := range sess.handles {
   417  		if cerr := f.Close(); cerr != nil && err == nil {
   418  			err = cerr
   419  		}
   420  	}
   421  	return err
   422  }