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 }