]> mj.ucw.cz Git - moe.git/blob - submit/commands.c
Added checking of contest time.
[moe.git] / submit / commands.c
1 /*
2  *  The Submit Daemon: High-Level Part of the Protocol
3  *
4  *  (c) 2007 Martin Mares <mj@ucw.cz>
5  */
6
7 #include "lib/lib.h"
8 #include "lib/mempool.h"
9 #include "lib/simple-lists.h"
10 #include "lib/stkstring.h"
11 #include "lib/fastbuf.h"
12 #include "sherlock/object.h"
13 #include "sherlock/objread.h"
14
15 #include <fcntl.h>
16 #include <time.h>
17
18 #include "submitd.h"
19
20 /*** REQUESTS AND REPLIES ***/
21
22 static void NONRET
23 read_error_cb(struct obj_read_state *st UNUSED, char *msg)
24 {
25   client_error("Request parse error: %s", msg);
26 }
27
28 static int
29 read_request(struct conn *c)
30 {
31   if (c->pool)
32     mp_flush(c->pool);
33   else
34     c->pool = mp_new(1024);
35   c->request = obj_new(c->pool);
36   c->reply = obj_new(c->pool);
37
38   struct obj_read_state st;
39   obj_read_start(&st, c->request);
40   st.error_callback = read_error_cb;
41   byte line[1024];
42   uns size = 0;
43   for (;;)
44     {
45       int l = bgets_nodie(&c->rx_fb, line, sizeof(line));
46       if (l < 0)
47         client_error("Request line too long");
48       if (!l)
49         {
50           if (!size)
51             return 0;
52           else
53             client_error("Truncated request");
54         }
55       if (l == 1)
56         break;
57       size += l;
58       if (size >= max_request_size)
59         client_error("Request too long");
60       obj_read_attr(&st, line[0], line+1);
61     }
62   obj_read_end(&st);
63   return 1;
64 }
65
66 static void
67 write_reply(struct conn *c)
68 {
69   if (!obj_find_attr(c->reply, '-') && !obj_find_attr(c->reply, '+'))
70     obj_set_attr(c->reply, '+', "OK");
71   if (trace_commands)
72     {
73       byte *m;
74       if (m = obj_find_aval(c->reply, '-'))
75         msg(L_DEBUG, ">> -%s", m);
76       else if (m = obj_find_aval(c->reply, '+'))
77         msg(L_DEBUG, ">> +%s", m);
78       else
79         msg(L_DEBUG, ">> ???");
80     }
81   obj_write(&c->tx_fb, c->reply, BUCKET_TYPE_PLAIN);
82   bputc(&c->tx_fb, '\n');
83   bflush(&c->tx_fb);
84 }
85
86 static void
87 err(struct conn *c, byte *msg)
88 {
89   obj_set_attr(c->reply, '-', msg);
90 }
91
92 /*** STATUS ***/
93
94 static void
95 copy_attrs(struct odes *dest, struct odes *src)
96 {
97   for (struct oattr *a = src->attrs ; a; a=a->next)
98     if (a->attr < OBJ_ATTR_SON)
99       for (struct oattr *aa = a; aa; aa=aa->same)
100         obj_add_attr(dest, aa->attr, aa->val);
101 }
102
103 static void
104 cmd_status(struct conn *c)
105 {
106   uns verbose = obj_find_anum(c->request, 'V', 0);
107   task_load_status(c);
108
109   CLIST_FOR_EACH(struct task *, t, task_list)
110     {
111       struct odes *to = task_status_find_task(c, t, 1);
112       struct odes *tr = obj_add_son(c->reply, 'T' + OBJ_ATTR_SON);
113       copy_attrs(tr, to);
114       CLIST_FOR_EACH(simp_node *, x, *t->extensions)
115         obj_add_attr(tr, 'A', x->s);
116       CLIST_FOR_EACH(simp_node *, p, t->parts)
117         {
118           struct odes *po = task_status_find_part(to, p->s, 1);
119           struct odes *pr = obj_add_son(tr, 'P' + OBJ_ATTR_SON);
120           copy_attrs(pr, po);
121           uns current_ver = obj_find_anum(po, 'V', 0);
122           for (struct oattr *v = obj_find_attr(po, 'V' + OBJ_ATTR_SON); v; v=v->same)
123             {
124               struct odes *vo = v->son;
125               uns ver = obj_find_anum(vo, 'V', 0);
126               if (ver == current_ver || verbose)
127                 obj_add_son_ref(pr, 'V' + OBJ_ATTR_SON, vo);
128             }
129         }
130     }
131 }
132
133 /*** Contest timeout checks ***/
134
135 static int
136 load_time_limit(char *name)
137 {
138   struct fastbuf *f = bopen_try(name, O_RDONLY, 1024);
139   if (!f)
140     return -1;
141   char buf[256];
142   int h, m;
143   if (bgets_nodie(f, buf, sizeof(buf)) < 0 ||
144       sscanf(buf, "%d:%d", &h, &m) != 2 ||
145       h < 0 || h > 23 || m < 0 || m > 59)
146     {
147       msg(L_ERROR, "Invalid timeout in %s", name);
148       bclose(f);
149       return -1;
150     }
151   bclose(f);
152   return 100*h + m;
153 }
154
155 static int
156 contest_over(struct conn *c)
157 {
158   time_t now = time(NULL);
159   struct tm *tm = localtime(&now);
160   int tstamp = tm->tm_hour*100 + tm->tm_min;
161   int local_limit = load_time_limit(stk_printf("solutions/%s/TIMEOUT", c->user));
162   int global_limit = load_time_limit("solutions/TIMEOUT");
163   if (trace_commands > 1)
164     msg(L_DEBUG, "Time check: current %d, global limit %d, user limit %d", tstamp, global_limit, local_limit);
165   if (local_limit >= 0)
166     return (tstamp >= local_limit);
167   return (global_limit >= 0 && tstamp >= global_limit);
168 }
169
170 /*** SUBMIT ***/
171
172 static struct fastbuf *
173 read_attachment(struct conn *c, uns max_size)
174 {
175   uns size = obj_find_anum(c->request, 'S', 0);
176   if (size > max_size)
177     {
178       err(c, "Submission too large");
179       return NULL;
180     }
181   obj_set_attr(c->reply, '+', "Go on");
182   write_reply(c);
183   obj_set_attr(c->reply, '+', NULL);
184
185   // This is less efficient than bbcopy(), but we want our own error handling.
186   struct fastbuf *fb = bopen_tmp(4096);
187   byte buf[4096];
188   uns remains = size;
189   while (remains)
190     {
191       uns cnt = bread(&c->rx_fb, buf, MIN(remains, (uns)sizeof(buf)));
192       if (!cnt)
193         {
194           bclose(fb);
195           client_error("Truncated attachment");
196         }
197       bwrite(fb, buf, cnt);
198       remains -= cnt;
199     }
200   brewind(fb);
201   return fb;
202 }
203
204 static void
205 cmd_submit(struct conn *c)
206 {
207   if (contest_over(c))
208     {
209       err(c, "The contest is over, no more submits allowed");
210       return;
211     }
212
213   byte *tname = obj_find_aval(c->request, 'T');
214   if (!tname)
215     {
216       err(c, "No task specified");
217       return;
218     }
219   struct task *task = task_find(tname);
220   if (!task)
221     {
222       err(c, "No such task");
223       return;
224     }
225
226   byte *pname = obj_find_aval(c->request, 'P');
227   if (!pname)
228     {
229       simp_node *s = clist_head(&task->parts);
230       ASSERT(s);
231       pname = s->s;
232     }
233   else if (!part_exists_p(task, pname))
234     {
235       err(c, "No such task part");
236       return;
237     }
238
239   byte *ext = obj_find_aval(c->request, 'X');
240   if (!ext || !ext_exists_p(task, ext))
241     {
242       err(c, "Missing or invalid extension");
243       return;
244     }
245
246   uns max_size = task->max_size ? : max_attachment_size;
247   struct fastbuf *fb = read_attachment(c, max_size);
248   if (!fb)
249     return;
250
251   task_lock_status(c);
252   struct odes *tasko = task_status_find_task(c, task, 1);
253   struct odes *parto = task_status_find_part(tasko, pname, 1);
254   uns current_ver = obj_find_anum(parto, 'V', 0);
255   if (current_ver >= max_versions)
256     {
257       err(c, "Maximum number of submits of this task exceeded");
258       bclose(fb);
259       task_unlock_status(c, 0);
260       return;
261     }
262   uns last_ver = 0;
263   uns replaced_ver = 0;
264   for (struct oattr *a = obj_find_attr(parto, 'V' + OBJ_ATTR_SON); a; a=a->same)
265     {
266       uns ver = obj_find_anum(a->son, 'V', 0);
267       byte *ext = obj_find_aval(a->son, 'X');
268       ASSERT(ver && ext);
269       last_ver = MAX(last_ver, ver);
270       if (ver == current_ver)
271         {
272           task_delete_part(c->user, tname, pname, ext, ver);
273           obj_set_attr(a->son, 'S', "replaced");
274           replaced_ver = current_ver;
275         }
276     }
277   struct odes *vero = obj_add_son(parto, 'V' + OBJ_ATTR_SON);
278   obj_set_attr_num(vero, 'V', ++last_ver);
279   obj_set_attr_num(vero, 'T', time(NULL));
280   obj_set_attr_num(vero, 'L', obj_find_anum(c->request, 'S', 0));
281   obj_set_attr(vero, 'S', "submitted");
282   obj_set_attr(vero, 'X', ext);
283   task_submit_part(c->user, tname, pname, ext, last_ver, fb);
284   obj_set_attr_num(parto, 'V', last_ver);
285   task_unlock_status(c, 1);
286
287   msg(L_INFO, "User %s submitted task %s%s (version %d%s)",
288         c->user, tname,
289         (strcmp(tname, pname) ? stk_printf("/%s", pname) : ""),
290         last_ver,
291         (replaced_ver ? stk_printf(", replaced %d", replaced_ver) : ""));
292 }
293
294 /*** COMMAND MUX ***/
295
296 static void
297 execute_command(struct conn *c)
298 {
299   byte *cmd = obj_find_aval(c->request, '!');
300   if (!cmd)
301     {
302       err(c, "Missing command");
303       return;
304     }
305   if (trace_commands)
306     msg(L_DEBUG, "<< %s", cmd);
307   if (!strcasecmp(cmd, "SUBMIT"))
308     cmd_submit(c);
309   else if (!strcasecmp(cmd, "STATUS"))
310     cmd_status(c);
311   else
312     err(c, "Unknown command");
313 }
314
315 int
316 process_command(struct conn *c)
317 {
318   if (!read_request(c))
319     return 0;
320   execute_command(c);
321   write_reply(c);
322   return 1;
323 }
324
325 /*** INITIAL HANDSHAKE ***/
326
327 static void
328 execute_init(struct conn *c)
329 {
330   byte *user = obj_find_aval(c->request, 'U');
331   if (!user)
332     {
333       err(c, "Missing user");
334       return;
335     }
336   if (!c->cert_name ||
337       !strcmp(user, c->cert_name) ||
338       c->rule->allow_admin && !strcmp(c->cert_name, "admin"))
339     {
340       if (!user_exists_p(user))
341         {
342           err(c, "Unknown user");
343           return;
344         }
345       msg(L_INFO, "Logged in %s", user);
346     }
347   else
348     {
349       err(c, "Permission denied");
350       msg(L_ERROR, "Unauthorized attempt to log in as %s", user);
351       return;
352     }
353   c->user = xstrdup(user);
354 }
355
356 int
357 process_init(struct conn *c)
358 {
359   if (!read_request(c))
360     return 0;
361   execute_init(c);
362   write_reply(c);
363   return !obj_find_attr(c->reply, '-');
364 }