a geicko-2 based round robin ranking system designed to test c++ battleship submissions
battleship.dunkirk.sh
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}