a geicko-2 based round robin ranking system designed to test c++ battleship submissions battleship.dunkirk.sh
at main 16 kB view raw
1package sftp 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "os" 9 "strings" 10 "sync" 11 "syscall" 12) 13 14// MaxFilelist is the max number of files to return in a readdir batch. 15var MaxFilelist int64 = 100 16 17// state encapsulates the reader/writer/readdir from handlers. 18type state struct { 19 mu sync.RWMutex 20 21 writerAt io.WriterAt 22 readerAt io.ReaderAt 23 writerAtReaderAt WriterAtReaderAt 24 listerAt ListerAt 25 lsoffset int64 26} 27 28// copy returns a shallow copy the state. 29// This is broken out to specific fields, 30// because we have to copy around the mutex in state. 31func (s *state) copy() state { 32 s.mu.RLock() 33 defer s.mu.RUnlock() 34 35 return state{ 36 writerAt: s.writerAt, 37 readerAt: s.readerAt, 38 writerAtReaderAt: s.writerAtReaderAt, 39 listerAt: s.listerAt, 40 lsoffset: s.lsoffset, 41 } 42} 43 44func (s *state) setReaderAt(rd io.ReaderAt) { 45 s.mu.Lock() 46 defer s.mu.Unlock() 47 48 s.readerAt = rd 49} 50 51func (s *state) getReaderAt() io.ReaderAt { 52 s.mu.RLock() 53 defer s.mu.RUnlock() 54 55 return s.readerAt 56} 57 58func (s *state) setWriterAt(rd io.WriterAt) { 59 s.mu.Lock() 60 defer s.mu.Unlock() 61 62 s.writerAt = rd 63} 64 65func (s *state) getWriterAt() io.WriterAt { 66 s.mu.RLock() 67 defer s.mu.RUnlock() 68 69 return s.writerAt 70} 71 72func (s *state) setWriterAtReaderAt(rw WriterAtReaderAt) { 73 s.mu.Lock() 74 defer s.mu.Unlock() 75 76 s.writerAtReaderAt = rw 77} 78 79func (s *state) getWriterAtReaderAt() WriterAtReaderAt { 80 s.mu.RLock() 81 defer s.mu.RUnlock() 82 83 return s.writerAtReaderAt 84} 85 86func (s *state) getAllReaderWriters() (io.ReaderAt, io.WriterAt, WriterAtReaderAt) { 87 s.mu.RLock() 88 defer s.mu.RUnlock() 89 90 return s.readerAt, s.writerAt, s.writerAtReaderAt 91} 92 93// Returns current offset for file list 94func (s *state) lsNext() int64 { 95 s.mu.RLock() 96 defer s.mu.RUnlock() 97 98 return s.lsoffset 99} 100 101// Increases next offset 102func (s *state) lsInc(offset int64) { 103 s.mu.Lock() 104 defer s.mu.Unlock() 105 106 s.lsoffset += offset 107} 108 109// manage file read/write state 110func (s *state) setListerAt(la ListerAt) { 111 s.mu.Lock() 112 defer s.mu.Unlock() 113 114 s.listerAt = la 115} 116 117func (s *state) getListerAt() ListerAt { 118 s.mu.RLock() 119 defer s.mu.RUnlock() 120 121 return s.listerAt 122} 123 124func (s *state) closeListerAt() error { 125 s.mu.Lock() 126 defer s.mu.Unlock() 127 128 var err error 129 130 if s.listerAt != nil { 131 if c, ok := s.listerAt.(io.Closer); ok { 132 err = c.Close() 133 } 134 s.listerAt = nil 135 } 136 137 return err 138} 139 140// Request contains the data and state for the incoming service request. 141type Request struct { 142 // Get, Put, Setstat, Stat, Rename, Remove 143 // Rmdir, Mkdir, List, Readlink, Link, Symlink 144 Method string 145 Filepath string 146 Flags uint32 147 Attrs []byte // convert to sub-struct 148 Target string // for renames and sym-links 149 handle string 150 151 // reader/writer/readdir from handlers 152 state 153 154 // context lasts duration of request 155 ctx context.Context 156 cancelCtx context.CancelFunc 157} 158 159// NewRequest creates a new Request object. 160func NewRequest(method, path string) *Request { 161 return &Request{ 162 Method: method, 163 Filepath: cleanPath(path), 164 } 165} 166 167// copy returns a shallow copy of existing request. 168// This is broken out to specific fields, 169// because we have to copy around the mutex in state. 170func (r *Request) copy() *Request { 171 return &Request{ 172 Method: r.Method, 173 Filepath: r.Filepath, 174 Flags: r.Flags, 175 Attrs: r.Attrs, 176 Target: r.Target, 177 handle: r.handle, 178 179 state: r.state.copy(), 180 181 ctx: r.ctx, 182 cancelCtx: r.cancelCtx, 183 } 184} 185 186// New Request initialized based on packet data 187func requestFromPacket(ctx context.Context, pkt hasPath, baseDir string) *Request { 188 request := &Request{ 189 Method: requestMethod(pkt), 190 Filepath: cleanPathWithBase(baseDir, pkt.getPath()), 191 } 192 request.ctx, request.cancelCtx = context.WithCancel(ctx) 193 194 switch p := pkt.(type) { 195 case *sshFxpOpenPacket: 196 request.Flags = p.Pflags 197 request.Attrs = p.Attrs.([]byte) 198 case *sshFxpSetstatPacket: 199 request.Flags = p.Flags 200 request.Attrs = p.Attrs.([]byte) 201 case *sshFxpRenamePacket: 202 request.Target = cleanPathWithBase(baseDir, p.Newpath) 203 case *sshFxpSymlinkPacket: 204 // NOTE: given a POSIX compliant signature: symlink(target, linkpath string) 205 // this makes Request.Target the linkpath, and Request.Filepath the target. 206 request.Target = cleanPathWithBase(baseDir, p.Linkpath) 207 request.Filepath = p.Targetpath 208 case *sshFxpExtendedPacketHardlink: 209 request.Target = cleanPathWithBase(baseDir, p.Newpath) 210 } 211 return request 212} 213 214// Context returns the request's context. To change the context, 215// use WithContext. 216// 217// The returned context is always non-nil; it defaults to the 218// background context. 219// 220// For incoming server requests, the context is canceled when the 221// request is complete or the client's connection closes. 222func (r *Request) Context() context.Context { 223 if r.ctx != nil { 224 return r.ctx 225 } 226 return context.Background() 227} 228 229// WithContext returns a copy of r with its context changed to ctx. 230// The provided ctx must be non-nil. 231func (r *Request) WithContext(ctx context.Context) *Request { 232 if ctx == nil { 233 panic("nil context") 234 } 235 r2 := r.copy() 236 r2.ctx = ctx 237 r2.cancelCtx = nil 238 return r2 239} 240 241// Close reader/writer if possible 242func (r *Request) close() error { 243 defer func() { 244 if r.cancelCtx != nil { 245 r.cancelCtx() 246 } 247 }() 248 249 err := r.state.closeListerAt() 250 251 rd, wr, rw := r.getAllReaderWriters() 252 253 // Close errors on a Writer are far more likely to be the important one. 254 // As they can be information that there was a loss of data. 255 if c, ok := wr.(io.Closer); ok { 256 if err2 := c.Close(); err == nil { 257 // update error if it is still nil 258 err = err2 259 } 260 } 261 262 if c, ok := rw.(io.Closer); ok { 263 if err2 := c.Close(); err == nil { 264 // update error if it is still nil 265 err = err2 266 267 r.setWriterAtReaderAt(nil) 268 } 269 } 270 271 if c, ok := rd.(io.Closer); ok { 272 if err2 := c.Close(); err == nil { 273 // update error if it is still nil 274 err = err2 275 } 276 } 277 278 return err 279} 280 281// Notify transfer error if any 282func (r *Request) transferError(err error) { 283 if err == nil { 284 return 285 } 286 287 rd, wr, rw := r.getAllReaderWriters() 288 289 if t, ok := wr.(TransferError); ok { 290 t.TransferError(err) 291 } 292 293 if t, ok := rw.(TransferError); ok { 294 t.TransferError(err) 295 } 296 297 if t, ok := rd.(TransferError); ok { 298 t.TransferError(err) 299 } 300} 301 302// called from worker to handle packet/request 303func (r *Request) call(handlers Handlers, pkt requestPacket, alloc *allocator, orderID uint32, maxTxPacket uint32) responsePacket { 304 switch r.Method { 305 case "Get": 306 return fileget(handlers.FileGet, r, pkt, alloc, orderID, maxTxPacket) 307 case "Put": 308 return fileput(handlers.FilePut, r, pkt, alloc, orderID, maxTxPacket) 309 case "Open": 310 return fileputget(handlers.FilePut, r, pkt, alloc, orderID, maxTxPacket) 311 case "Setstat", "Rename", "Rmdir", "Mkdir", "Link", "Symlink", "Remove", "PosixRename", "StatVFS": 312 return filecmd(handlers.FileCmd, r, pkt) 313 case "List": 314 return filelist(handlers.FileList, r, pkt) 315 case "Stat", "Lstat": 316 return filestat(handlers.FileList, r, pkt) 317 case "Readlink": 318 if readlinkFileLister, ok := handlers.FileList.(ReadlinkFileLister); ok { 319 return readlink(readlinkFileLister, r, pkt) 320 } 321 return filestat(handlers.FileList, r, pkt) 322 default: 323 return statusFromError(pkt.id(), fmt.Errorf("unexpected method: %s", r.Method)) 324 } 325} 326 327// Additional initialization for Open packets 328func (r *Request) open(h Handlers, pkt requestPacket) responsePacket { 329 flags := r.Pflags() 330 331 id := pkt.id() 332 333 switch { 334 case flags.Write, flags.Append, flags.Creat, flags.Trunc: 335 if flags.Read { 336 if openFileWriter, ok := h.FilePut.(OpenFileWriter); ok { 337 r.Method = "Open" 338 rw, err := openFileWriter.OpenFile(r) 339 if err != nil { 340 return statusFromError(id, err) 341 } 342 343 r.setWriterAtReaderAt(rw) 344 345 return &sshFxpHandlePacket{ 346 ID: id, 347 Handle: r.handle, 348 } 349 } 350 } 351 352 r.Method = "Put" 353 wr, err := h.FilePut.Filewrite(r) 354 if err != nil { 355 return statusFromError(id, err) 356 } 357 358 r.setWriterAt(wr) 359 360 case flags.Read: 361 r.Method = "Get" 362 rd, err := h.FileGet.Fileread(r) 363 if err != nil { 364 return statusFromError(id, err) 365 } 366 367 r.setReaderAt(rd) 368 369 default: 370 return statusFromError(id, errors.New("bad file flags")) 371 } 372 373 return &sshFxpHandlePacket{ 374 ID: id, 375 Handle: r.handle, 376 } 377} 378 379func (r *Request) opendir(h Handlers, pkt requestPacket) responsePacket { 380 r.Method = "List" 381 la, err := h.FileList.Filelist(r) 382 if err != nil { 383 return statusFromError(pkt.id(), wrapPathError(r.Filepath, err)) 384 } 385 386 r.setListerAt(la) 387 388 return &sshFxpHandlePacket{ 389 ID: pkt.id(), 390 Handle: r.handle, 391 } 392} 393 394// wrap FileReader handler 395func fileget(h FileReader, r *Request, pkt requestPacket, alloc *allocator, orderID uint32, maxTxPacket uint32) responsePacket { 396 rd := r.getReaderAt() 397 if rd == nil { 398 return statusFromError(pkt.id(), errors.New("unexpected read packet")) 399 } 400 401 data, offset, _ := packetData(pkt, alloc, orderID, maxTxPacket) 402 403 n, err := rd.ReadAt(data, offset) 404 // only return EOF error if no data left to read 405 if err != nil && (err != io.EOF || n == 0) { 406 return statusFromError(pkt.id(), err) 407 } 408 409 return &sshFxpDataPacket{ 410 ID: pkt.id(), 411 Length: uint32(n), 412 Data: data[:n], 413 } 414} 415 416// wrap FileWriter handler 417func fileput(h FileWriter, r *Request, pkt requestPacket, alloc *allocator, orderID uint32, maxTxPacket uint32) responsePacket { 418 wr := r.getWriterAt() 419 if wr == nil { 420 return statusFromError(pkt.id(), errors.New("unexpected write packet")) 421 } 422 423 data, offset, _ := packetData(pkt, alloc, orderID, maxTxPacket) 424 425 _, err := wr.WriteAt(data, offset) 426 return statusFromError(pkt.id(), err) 427} 428 429// wrap OpenFileWriter handler 430func fileputget(h FileWriter, r *Request, pkt requestPacket, alloc *allocator, orderID uint32, maxTxPacket uint32) responsePacket { 431 rw := r.getWriterAtReaderAt() 432 if rw == nil { 433 return statusFromError(pkt.id(), errors.New("unexpected write and read packet")) 434 } 435 436 switch p := pkt.(type) { 437 case *sshFxpReadPacket: 438 data, offset := p.getDataSlice(alloc, orderID, maxTxPacket), int64(p.Offset) 439 440 n, err := rw.ReadAt(data, offset) 441 // only return EOF error if no data left to read 442 if err != nil && (err != io.EOF || n == 0) { 443 return statusFromError(pkt.id(), err) 444 } 445 446 return &sshFxpDataPacket{ 447 ID: pkt.id(), 448 Length: uint32(n), 449 Data: data[:n], 450 } 451 452 case *sshFxpWritePacket: 453 data, offset := p.Data, int64(p.Offset) 454 455 _, err := rw.WriteAt(data, offset) 456 return statusFromError(pkt.id(), err) 457 458 default: 459 return statusFromError(pkt.id(), errors.New("unexpected packet type for read or write")) 460 } 461} 462 463// file data for additional read/write packets 464func packetData(p requestPacket, alloc *allocator, orderID uint32, maxTxPacket uint32) (data []byte, offset int64, length uint32) { 465 switch p := p.(type) { 466 case *sshFxpReadPacket: 467 return p.getDataSlice(alloc, orderID, maxTxPacket), int64(p.Offset), p.Len 468 case *sshFxpWritePacket: 469 return p.Data, int64(p.Offset), p.Length 470 } 471 return 472} 473 474// wrap FileCmder handler 475func filecmd(h FileCmder, r *Request, pkt requestPacket) responsePacket { 476 switch p := pkt.(type) { 477 case *sshFxpFsetstatPacket: 478 r.Flags = p.Flags 479 r.Attrs = p.Attrs.([]byte) 480 } 481 482 switch r.Method { 483 case "PosixRename": 484 if posixRenamer, ok := h.(PosixRenameFileCmder); ok { 485 err := posixRenamer.PosixRename(r) 486 return statusFromError(pkt.id(), err) 487 } 488 489 // PosixRenameFileCmder not implemented handle this request as a Rename 490 r.Method = "Rename" 491 err := h.Filecmd(r) 492 return statusFromError(pkt.id(), err) 493 494 case "StatVFS": 495 if statVFSCmdr, ok := h.(StatVFSFileCmder); ok { 496 stat, err := statVFSCmdr.StatVFS(r) 497 if err != nil { 498 return statusFromError(pkt.id(), err) 499 } 500 stat.ID = pkt.id() 501 return stat 502 } 503 504 return statusFromError(pkt.id(), ErrSSHFxOpUnsupported) 505 } 506 507 err := h.Filecmd(r) 508 return statusFromError(pkt.id(), err) 509} 510 511// wrap FileLister handler 512func filelist(h FileLister, r *Request, pkt requestPacket) responsePacket { 513 lister := r.getListerAt() 514 if lister == nil { 515 return statusFromError(pkt.id(), errors.New("unexpected dir packet")) 516 } 517 518 offset := r.lsNext() 519 finfo := make([]os.FileInfo, MaxFilelist) 520 n, err := lister.ListAt(finfo, offset) 521 r.lsInc(int64(n)) 522 // ignore EOF as we only return it when there are no results 523 finfo = finfo[:n] // avoid need for nil tests below 524 525 switch r.Method { 526 case "List": 527 if err != nil && (err != io.EOF || n == 0) { 528 return statusFromError(pkt.id(), err) 529 } 530 531 nameAttrs := make([]*sshFxpNameAttr, 0, len(finfo)) 532 533 // If the type conversion fails, we get untyped `nil`, 534 // which is handled by not looking up any names. 535 idLookup, _ := h.(NameLookupFileLister) 536 537 for _, fi := range finfo { 538 nameAttrs = append(nameAttrs, &sshFxpNameAttr{ 539 Name: fi.Name(), 540 LongName: runLs(idLookup, fi), 541 Attrs: []interface{}{fi}, 542 }) 543 } 544 545 return &sshFxpNamePacket{ 546 ID: pkt.id(), 547 NameAttrs: nameAttrs, 548 } 549 550 default: 551 err = fmt.Errorf("unexpected method: %s", r.Method) 552 return statusFromError(pkt.id(), err) 553 } 554} 555 556func filestat(h FileLister, r *Request, pkt requestPacket) responsePacket { 557 var lister ListerAt 558 var err error 559 560 if r.Method == "Lstat" { 561 if lstatFileLister, ok := h.(LstatFileLister); ok { 562 lister, err = lstatFileLister.Lstat(r) 563 } else { 564 // LstatFileLister not implemented handle this request as a Stat 565 r.Method = "Stat" 566 lister, err = h.Filelist(r) 567 } 568 } else { 569 lister, err = h.Filelist(r) 570 } 571 if err != nil { 572 return statusFromError(pkt.id(), err) 573 } 574 finfo := make([]os.FileInfo, 1) 575 n, err := lister.ListAt(finfo, 0) 576 finfo = finfo[:n] // avoid need for nil tests below 577 578 switch r.Method { 579 case "Stat", "Lstat": 580 if err != nil && err != io.EOF { 581 return statusFromError(pkt.id(), err) 582 } 583 if n == 0 { 584 err = &os.PathError{ 585 Op: strings.ToLower(r.Method), 586 Path: r.Filepath, 587 Err: syscall.ENOENT, 588 } 589 return statusFromError(pkt.id(), err) 590 } 591 return &sshFxpStatResponse{ 592 ID: pkt.id(), 593 info: finfo[0], 594 } 595 case "Readlink": 596 if err != nil && err != io.EOF { 597 return statusFromError(pkt.id(), err) 598 } 599 if n == 0 { 600 err = &os.PathError{ 601 Op: "readlink", 602 Path: r.Filepath, 603 Err: syscall.ENOENT, 604 } 605 return statusFromError(pkt.id(), err) 606 } 607 filename := finfo[0].Name() 608 return &sshFxpNamePacket{ 609 ID: pkt.id(), 610 NameAttrs: []*sshFxpNameAttr{ 611 { 612 Name: filename, 613 LongName: filename, 614 Attrs: emptyFileStat, 615 }, 616 }, 617 } 618 default: 619 err = fmt.Errorf("unexpected method: %s", r.Method) 620 return statusFromError(pkt.id(), err) 621 } 622} 623 624func readlink(readlinkFileLister ReadlinkFileLister, r *Request, pkt requestPacket) responsePacket { 625 resolved, err := readlinkFileLister.Readlink(r.Filepath) 626 if err != nil { 627 return statusFromError(pkt.id(), err) 628 } 629 return &sshFxpNamePacket{ 630 ID: pkt.id(), 631 NameAttrs: []*sshFxpNameAttr{ 632 { 633 Name: resolved, 634 LongName: resolved, 635 Attrs: emptyFileStat, 636 }, 637 }, 638 } 639} 640 641// init attributes of request object from packet data 642func requestMethod(p requestPacket) (method string) { 643 switch p.(type) { 644 case *sshFxpReadPacket, *sshFxpWritePacket, *sshFxpOpenPacket: 645 // set in open() above 646 case *sshFxpOpendirPacket, *sshFxpReaddirPacket: 647 // set in opendir() above 648 case *sshFxpSetstatPacket, *sshFxpFsetstatPacket: 649 method = "Setstat" 650 case *sshFxpRenamePacket: 651 method = "Rename" 652 case *sshFxpSymlinkPacket: 653 method = "Symlink" 654 case *sshFxpRemovePacket: 655 method = "Remove" 656 case *sshFxpStatPacket, *sshFxpFstatPacket: 657 method = "Stat" 658 case *sshFxpLstatPacket: 659 method = "Lstat" 660 case *sshFxpRmdirPacket: 661 method = "Rmdir" 662 case *sshFxpReadlinkPacket: 663 method = "Readlink" 664 case *sshFxpMkdirPacket: 665 method = "Mkdir" 666 case *sshFxpExtendedPacketHardlink: 667 method = "Link" 668 } 669 return method 670}