a geicko-2 based round robin ranking system designed to test c++ battleship submissions
battleship.dunkirk.sh
1package main
2
3import (
4 "fmt"
5 "log"
6 "os"
7 "path/filepath"
8 "strings"
9
10 "github.com/charmbracelet/ssh"
11 "github.com/charmbracelet/wish/scp"
12)
13
14func newSCPHandlers() (scp.CopyToClientHandler, scp.CopyFromClientHandler) {
15 // Use FileSystemHandler as base
16 baseHandler := scp.NewFileSystemHandler(uploadDir)
17
18 // Wrap it to add validation and user namespacing
19 uploadHandler := &validatingHandler{
20 baseHandler: baseHandler,
21 }
22
23 // Return nil for downloads (disabled), wrapped handler for uploads
24 return nil, uploadHandler
25}
26
27type validatingHandler struct {
28 baseHandler scp.CopyFromClientHandler
29}
30
31func (h *validatingHandler) Write(s ssh.Session, entry *scp.FileEntry) (int64, error) {
32 filename := filepath.Base(entry.Name)
33 log.Printf("SCP Write called: entry.Name=%s, filename=%s, size=%d", entry.Name, filename, entry.Size)
34
35 // Skip validation for directory markers (SCP protocol negotiation)
36 if filename == "~" || filename == "." || filename == ".." {
37 log.Printf("Skipping directory marker: %s", filename)
38 return 0, nil
39 }
40
41 // Validate filename (must be memory_functions_*.cpp)
42 if !strings.HasPrefix(filename, "memory_functions_") || !strings.HasSuffix(filename, ".cpp") {
43 log.Printf("Invalid filename from %s: %s", s.User(), filename)
44 return 0, fmt.Errorf("only memory_functions_*.cpp files are accepted")
45 }
46
47 // Create user-specific subdirectory
48 userDir := filepath.Join(uploadDir, s.User())
49 if err := os.MkdirAll(userDir, 0755); err != nil {
50 log.Printf("Failed to create user directory: %v", err)
51 return 0, err
52 }
53
54 // Remove old file if it exists to ensure clean overwrite
55 targetPath := filepath.Join(userDir, filename)
56 if _, err := os.Stat(targetPath); err == nil {
57 log.Printf("Removing old file: %s", targetPath)
58 os.Remove(targetPath)
59 }
60
61 // Modify the entry to write to user's subdirectory
62 userEntry := &scp.FileEntry{
63 Name: filepath.Join(s.User(), filename),
64 Mode: entry.Mode,
65 Size: entry.Size,
66 Reader: entry.Reader,
67 }
68
69 log.Printf("Writing to: %s", filepath.Join(uploadDir, userEntry.Name))
70
71 n, err := h.baseHandler.Write(s, userEntry)
72 if err != nil {
73 log.Printf("Write error: %v", err)
74 return n, err
75 }
76
77 log.Printf("Uploaded %s from %s (%d bytes)", filename, s.User(), n)
78
79 // Add submission and trigger testing
80 submissionID, err := addSubmission(s.User(), filename)
81 if err != nil {
82 log.Printf("Failed to add submission: %v", err)
83 } else {
84 log.Printf("Queued submission %d for testing", submissionID)
85 // The worker will pick it up automatically
86 }
87
88 return n, nil
89}
90
91func (h *validatingHandler) Mkdir(s ssh.Session, entry *scp.DirEntry) error {
92 // Allow mkdir but namespace it to user directory
93 userEntry := &scp.DirEntry{
94 Name: filepath.Join(s.User(), entry.Name),
95 Mode: entry.Mode,
96 }
97 return h.baseHandler.Mkdir(s, userEntry)
98}