a geicko-2 based round robin ranking system designed to test c++ battleship submissions
battleship.dunkirk.sh
1package sftp
2
3// This serves as an example of how to implement the request server handler as
4// well as a dummy backend for testing. It implements an in-memory backend that
5// works as a very simple filesystem with simple flat key-value lookup system.
6
7import (
8 "errors"
9 "io"
10 "os"
11 "path"
12 "sort"
13 "strings"
14 "sync"
15 "syscall"
16 "time"
17)
18
19const maxSymlinkFollows = 5
20
21var errTooManySymlinks = errors.New("too many symbolic links")
22
23// InMemHandler returns a Handlers object with the test handlers.
24func InMemHandler() Handlers {
25 root := &root{
26 rootFile: &memFile{name: "/", modtime: time.Now(), isdir: true},
27 files: make(map[string]*memFile),
28 }
29 return Handlers{root, root, root, root}
30}
31
32// Example Handlers
33func (fs *root) Fileread(r *Request) (io.ReaderAt, error) {
34 flags := r.Pflags()
35 if !flags.Read {
36 // sanity check
37 return nil, os.ErrInvalid
38 }
39
40 return fs.OpenFile(r)
41}
42
43func (fs *root) Filewrite(r *Request) (io.WriterAt, error) {
44 flags := r.Pflags()
45 if !flags.Write {
46 // sanity check
47 return nil, os.ErrInvalid
48 }
49
50 return fs.OpenFile(r)
51}
52
53func (fs *root) OpenFile(r *Request) (WriterAtReaderAt, error) {
54 if fs.mockErr != nil {
55 return nil, fs.mockErr
56 }
57 _ = r.WithContext(r.Context()) // initialize context for deadlock testing
58
59 fs.mu.Lock()
60 defer fs.mu.Unlock()
61
62 return fs.openfile(r.Filepath, r.Flags)
63}
64
65func (fs *root) putfile(pathname string, file *memFile) error {
66 pathname, err := fs.canonName(pathname)
67 if err != nil {
68 return err
69 }
70
71 if !strings.HasPrefix(pathname, "/") {
72 return os.ErrInvalid
73 }
74
75 if _, err := fs.lfetch(pathname); err != os.ErrNotExist {
76 return os.ErrExist
77 }
78
79 file.name = pathname
80 fs.files[pathname] = file
81
82 return nil
83}
84
85func (fs *root) openfile(pathname string, flags uint32) (*memFile, error) {
86 pflags := newFileOpenFlags(flags)
87
88 file, err := fs.fetch(pathname)
89 if err == os.ErrNotExist {
90 if !pflags.Creat {
91 return nil, os.ErrNotExist
92 }
93
94 var count int
95 // You can create files through dangling symlinks.
96 link, err := fs.lfetch(pathname)
97 for err == nil && link.symlink != "" {
98 if pflags.Excl {
99 // unless you also passed in O_EXCL
100 return nil, os.ErrInvalid
101 }
102
103 if count++; count > maxSymlinkFollows {
104 return nil, errTooManySymlinks
105 }
106
107 pathname = link.symlink
108 link, err = fs.lfetch(pathname)
109 }
110
111 file := &memFile{
112 modtime: time.Now(),
113 }
114
115 if err := fs.putfile(pathname, file); err != nil {
116 return nil, err
117 }
118
119 return file, nil
120 }
121
122 if err != nil {
123 return nil, err
124 }
125
126 if pflags.Creat && pflags.Excl {
127 return nil, os.ErrExist
128 }
129
130 if file.IsDir() {
131 return nil, os.ErrInvalid
132 }
133
134 if pflags.Trunc {
135 if err := file.Truncate(0); err != nil {
136 return nil, err
137 }
138 }
139
140 return file, nil
141}
142
143func (fs *root) Filecmd(r *Request) error {
144 if fs.mockErr != nil {
145 return fs.mockErr
146 }
147 _ = r.WithContext(r.Context()) // initialize context for deadlock testing
148
149 fs.mu.Lock()
150 defer fs.mu.Unlock()
151
152 switch r.Method {
153 case "Setstat":
154 file, err := fs.openfile(r.Filepath, sshFxfWrite)
155 if err != nil {
156 return err
157 }
158
159 if r.AttrFlags().Size {
160 return file.Truncate(int64(r.Attributes().Size))
161 }
162
163 return nil
164
165 case "Rename":
166 // SFTP-v2: "It is an error if there already exists a file with the name specified by newpath."
167 // This varies from the POSIX specification, which allows limited replacement of target files.
168 if fs.exists(r.Target) {
169 return os.ErrExist
170 }
171
172 return fs.rename(r.Filepath, r.Target)
173
174 case "Rmdir":
175 return fs.rmdir(r.Filepath)
176
177 case "Remove":
178 // IEEE 1003.1 remove explicitly can unlink files and remove empty directories.
179 // We use instead here the semantics of unlink, which is allowed to be restricted against directories.
180 return fs.unlink(r.Filepath)
181
182 case "Mkdir":
183 return fs.mkdir(r.Filepath)
184
185 case "Link":
186 return fs.link(r.Filepath, r.Target)
187
188 case "Symlink":
189 // NOTE: r.Filepath is the target, and r.Target is the linkpath.
190 return fs.symlink(r.Filepath, r.Target)
191 }
192
193 return errors.New("unsupported")
194}
195
196func (fs *root) rename(oldpath, newpath string) error {
197 file, err := fs.lfetch(oldpath)
198 if err != nil {
199 return err
200 }
201
202 newpath, err = fs.canonName(newpath)
203 if err != nil {
204 return err
205 }
206
207 if !strings.HasPrefix(newpath, "/") {
208 return os.ErrInvalid
209 }
210
211 target, err := fs.lfetch(newpath)
212 if err != os.ErrNotExist {
213 if target == file {
214 // IEEE 1003.1: if oldpath and newpath are the same directory entry,
215 // then return no error, and perform no further action.
216 return nil
217 }
218
219 switch {
220 case file.IsDir():
221 // IEEE 1003.1: if oldpath is a directory, and newpath exists,
222 // then newpath must be a directory, and empty.
223 // It is to be removed prior to rename.
224 if err := fs.rmdir(newpath); err != nil {
225 return err
226 }
227
228 case target.IsDir():
229 // IEEE 1003.1: if oldpath is not a directory, and newpath exists,
230 // then newpath may not be a directory.
231 return syscall.EISDIR
232 }
233 }
234
235 fs.files[newpath] = file
236
237 if file.IsDir() {
238 dirprefix := file.name + "/"
239
240 for name, file := range fs.files {
241 if strings.HasPrefix(name, dirprefix) {
242 newname := path.Join(newpath, strings.TrimPrefix(name, dirprefix))
243
244 fs.files[newname] = file
245 file.name = newname
246 delete(fs.files, name)
247 }
248 }
249 }
250
251 file.name = newpath
252 delete(fs.files, oldpath)
253
254 return nil
255}
256
257func (fs *root) PosixRename(r *Request) error {
258 if fs.mockErr != nil {
259 return fs.mockErr
260 }
261 _ = r.WithContext(r.Context()) // initialize context for deadlock testing
262
263 fs.mu.Lock()
264 defer fs.mu.Unlock()
265
266 return fs.rename(r.Filepath, r.Target)
267}
268
269func (fs *root) StatVFS(r *Request) (*StatVFS, error) {
270 if fs.mockErr != nil {
271 return nil, fs.mockErr
272 }
273
274 return getStatVFSForPath(r.Filepath)
275}
276
277func (fs *root) mkdir(pathname string) error {
278 dir := &memFile{
279 modtime: time.Now(),
280 isdir: true,
281 }
282
283 return fs.putfile(pathname, dir)
284}
285
286func (fs *root) rmdir(pathname string) error {
287 // IEEE 1003.1: If pathname is a symlink, then rmdir should fail with ENOTDIR.
288 dir, err := fs.lfetch(pathname)
289 if err != nil {
290 return err
291 }
292
293 if !dir.IsDir() {
294 return syscall.ENOTDIR
295 }
296
297 // use the dir‘s internal name not the pathname we passed in.
298 // the dir.name is always the canonical name of a directory.
299 pathname = dir.name
300
301 for name := range fs.files {
302 if path.Dir(name) == pathname {
303 return errors.New("directory not empty")
304 }
305 }
306
307 delete(fs.files, pathname)
308
309 return nil
310}
311
312func (fs *root) link(oldpath, newpath string) error {
313 file, err := fs.lfetch(oldpath)
314 if err != nil {
315 return err
316 }
317
318 if file.IsDir() {
319 return errors.New("hard link not allowed for directory")
320 }
321
322 return fs.putfile(newpath, file)
323}
324
325// symlink() creates a symbolic link named `linkpath` which contains the string `target`.
326// NOTE! This would be called with `symlink(req.Filepath, req.Target)` due to different semantics.
327func (fs *root) symlink(target, linkpath string) error {
328 link := &memFile{
329 modtime: time.Now(),
330 symlink: target,
331 }
332
333 return fs.putfile(linkpath, link)
334}
335
336func (fs *root) unlink(pathname string) error {
337 // does not follow symlinks!
338 file, err := fs.lfetch(pathname)
339 if err != nil {
340 return err
341 }
342
343 if file.IsDir() {
344 // IEEE 1003.1: implementations may opt out of allowing the unlinking of directories.
345 // SFTP-v2: SSH_FXP_REMOVE may not remove directories.
346 return os.ErrInvalid
347 }
348
349 // DO NOT use the file’s internal name.
350 // because of hard-links files cannot have a single canonical name.
351 delete(fs.files, pathname)
352
353 return nil
354}
355
356type listerat []os.FileInfo
357
358// Modeled after strings.Reader's ReadAt() implementation
359func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) {
360 var n int
361 if offset >= int64(len(f)) {
362 return 0, io.EOF
363 }
364 n = copy(ls, f[offset:])
365 if n < len(ls) {
366 return n, io.EOF
367 }
368 return n, nil
369}
370
371func (fs *root) Filelist(r *Request) (ListerAt, error) {
372 if fs.mockErr != nil {
373 return nil, fs.mockErr
374 }
375 _ = r.WithContext(r.Context()) // initialize context for deadlock testing
376
377 fs.mu.Lock()
378 defer fs.mu.Unlock()
379
380 switch r.Method {
381 case "List":
382 files, err := fs.readdir(r.Filepath)
383 if err != nil {
384 return nil, err
385 }
386 return listerat(files), nil
387
388 case "Stat":
389 file, err := fs.fetch(r.Filepath)
390 if err != nil {
391 return nil, err
392 }
393 return listerat{file}, nil
394 }
395
396 return nil, errors.New("unsupported")
397}
398
399func (fs *root) readdir(pathname string) ([]os.FileInfo, error) {
400 dir, err := fs.fetch(pathname)
401 if err != nil {
402 return nil, err
403 }
404
405 if !dir.IsDir() {
406 return nil, syscall.ENOTDIR
407 }
408
409 var files []os.FileInfo
410
411 for name, file := range fs.files {
412 if path.Dir(name) == dir.name {
413 files = append(files, file)
414 }
415 }
416
417 sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
418
419 return files, nil
420}
421
422func (fs *root) Readlink(pathname string) (string, error) {
423 file, err := fs.lfetch(pathname)
424 if err != nil {
425 return "", err
426 }
427
428 if file.symlink == "" {
429 return "", os.ErrInvalid
430 }
431
432 return file.symlink, nil
433}
434
435// implements LstatFileLister interface
436func (fs *root) Lstat(r *Request) (ListerAt, error) {
437 if fs.mockErr != nil {
438 return nil, fs.mockErr
439 }
440 _ = r.WithContext(r.Context()) // initialize context for deadlock testing
441
442 fs.mu.Lock()
443 defer fs.mu.Unlock()
444
445 file, err := fs.lfetch(r.Filepath)
446 if err != nil {
447 return nil, err
448 }
449 return listerat{file}, nil
450}
451
452// In memory file-system-y thing that the Handlers live on
453type root struct {
454 rootFile *memFile
455 mockErr error
456
457 mu sync.Mutex
458 files map[string]*memFile
459}
460
461// Set a mocked error that the next handler call will return.
462// Set to nil to reset for no error.
463func (fs *root) returnErr(err error) {
464 fs.mockErr = err
465}
466
467func (fs *root) lfetch(path string) (*memFile, error) {
468 if path == "/" {
469 return fs.rootFile, nil
470 }
471
472 file, ok := fs.files[path]
473 if file == nil {
474 if ok {
475 delete(fs.files, path)
476 }
477
478 return nil, os.ErrNotExist
479 }
480
481 return file, nil
482}
483
484// canonName returns the “canonical” name of a file, that is:
485// if the directory of the pathname is a symlink, it follows that symlink to the valid directory name.
486// this is relatively easy, since `dir.name` will be the only valid canonical path for a directory.
487func (fs *root) canonName(pathname string) (string, error) {
488 dirname, filename := path.Dir(pathname), path.Base(pathname)
489
490 dir, err := fs.fetch(dirname)
491 if err != nil {
492 return "", err
493 }
494
495 if !dir.IsDir() {
496 return "", syscall.ENOTDIR
497 }
498
499 return path.Join(dir.name, filename), nil
500}
501
502func (fs *root) exists(path string) bool {
503 path, err := fs.canonName(path)
504 if err != nil {
505 return false
506 }
507
508 _, err = fs.lfetch(path)
509
510 return err != os.ErrNotExist
511}
512
513func (fs *root) fetch(pathname string) (*memFile, error) {
514 file, err := fs.lfetch(pathname)
515 if err != nil {
516 return nil, err
517 }
518
519 var count int
520 for file.symlink != "" {
521 if count++; count > maxSymlinkFollows {
522 return nil, errTooManySymlinks
523 }
524
525 linkTarget := file.symlink
526 if !path.IsAbs(linkTarget) {
527 linkTarget = path.Join(path.Dir(file.name), linkTarget)
528 }
529
530 file, err = fs.lfetch(linkTarget)
531 if err != nil {
532 return nil, err
533 }
534 }
535
536 return file, nil
537}
538
539// Implements os.FileInfo, io.ReaderAt and io.WriterAt interfaces.
540// These are the 3 interfaces necessary for the Handlers.
541// Implements the optional interface TransferError.
542type memFile struct {
543 name string
544 modtime time.Time
545 symlink string
546 isdir bool
547
548 mu sync.RWMutex
549 content []byte
550 err error
551}
552
553// These are helper functions, they must be called while holding the memFile.mu mutex
554func (f *memFile) size() int64 { return int64(len(f.content)) }
555func (f *memFile) grow(n int64) { f.content = append(f.content, make([]byte, n)...) }
556
557// Have memFile fulfill os.FileInfo interface
558func (f *memFile) Name() string { return path.Base(f.name) }
559func (f *memFile) Size() int64 {
560 f.mu.Lock()
561 defer f.mu.Unlock()
562
563 return f.size()
564}
565func (f *memFile) Mode() os.FileMode {
566 if f.isdir {
567 return os.FileMode(0755) | os.ModeDir
568 }
569 if f.symlink != "" {
570 return os.FileMode(0777) | os.ModeSymlink
571 }
572 return os.FileMode(0644)
573}
574func (f *memFile) ModTime() time.Time { return f.modtime }
575func (f *memFile) IsDir() bool { return f.isdir }
576func (f *memFile) Sys() interface{} {
577 return fakeFileInfoSys()
578}
579
580func (f *memFile) ReadAt(b []byte, off int64) (int, error) {
581 f.mu.Lock()
582 defer f.mu.Unlock()
583
584 if f.err != nil {
585 return 0, f.err
586 }
587
588 if off < 0 {
589 return 0, errors.New("memFile.ReadAt: negative offset")
590 }
591
592 if off >= f.size() {
593 return 0, io.EOF
594 }
595
596 n := copy(b, f.content[off:])
597 if n < len(b) {
598 return n, io.EOF
599 }
600
601 return n, nil
602}
603
604func (f *memFile) WriteAt(b []byte, off int64) (int, error) {
605 // fmt.Println(string(p), off)
606 // mimic write delays, should be optional
607 time.Sleep(time.Microsecond * time.Duration(len(b)))
608
609 f.mu.Lock()
610 defer f.mu.Unlock()
611
612 if f.err != nil {
613 return 0, f.err
614 }
615
616 grow := int64(len(b)) + off - f.size()
617 if grow > 0 {
618 f.grow(grow)
619 }
620
621 return copy(f.content[off:], b), nil
622}
623
624func (f *memFile) Truncate(size int64) error {
625 f.mu.Lock()
626 defer f.mu.Unlock()
627
628 if f.err != nil {
629 return f.err
630 }
631
632 grow := size - f.size()
633 if grow <= 0 {
634 f.content = f.content[:size]
635 } else {
636 f.grow(grow)
637 }
638
639 return nil
640}
641
642func (f *memFile) TransferError(err error) {
643 f.mu.Lock()
644 defer f.mu.Unlock()
645
646 f.err = err
647}