!SUBMIT
Ttask
+ Ppart <-- optional part ID
Ssize
+ Xextension
(after the request is ACK-ed, the client sends raw data and then server sends a 2nd reply)
Submit reply:
Submit reply after data:
(only status)
+
+
+Task status objects
+~~~~~~~~~~~~~~~~~~~
+(T <-- task
+ Tname
+ (P <-- task part
+ Pname
+ Vid <-- which version is current
+ (V <-- submitted version
+ Vid
+ Tsubmit_time
+ Sstatus
+ Hhash
+ Xextension
+ )
+ )
+)
#include "lib/lib.h"
#include "lib/mempool.h"
+#include "lib/simple-lists.h"
+#include "lib/stkstring.h"
#include "sherlock/object.h"
#include "sherlock/objread.h"
+#include <time.h>
+
#include "submitd.h"
/*** REQUESTS AND REPLIES ***/
obj_set_attr(c->reply, '-', msg);
}
+/*** STATUS ***/
+
+static void
+copy_attrs(struct odes *dest, struct odes *src)
+{
+ for (struct oattr *a = src->attrs ; a; a=a->next)
+ for (struct oattr *aa = a; aa; aa=aa->same)
+ obj_add_attr(dest, aa->attr, aa->val);
+}
+
+static void
+cmd_status(struct conn *c)
+{
+ uns verbose = obj_find_anum(c->request, 'V', 0);
+ task_load_status(c);
+
+ CLIST_FOR_EACH(struct task *, t, task_list)
+ {
+ struct odes *to = task_status_find_task(c, t, 1);
+ struct odes *tr = obj_add_son(c->reply, 'T' + OBJ_ATTR_SON);
+ copy_attrs(tr, to);
+ CLIST_FOR_EACH(simp_node *, p, t->parts)
+ {
+ struct odes *po = task_status_find_part(to, p->s, 1);
+ struct odes *pr = obj_add_son(tr, 'P' + OBJ_ATTR_SON);
+ copy_attrs(pr, po);
+ uns current_ver = obj_find_anum(po, 'V', 0);
+ for (struct oattr *v = obj_find_attr(po, 'V' + OBJ_ATTR_SON); v; v=v->same)
+ {
+ struct odes *vo = v->son;
+ uns ver = obj_find_anum(vo, 'V', 0);
+ if (ver == current_ver || verbose)
+ obj_add_son_ref(pr, 'V' + OBJ_ATTR_SON, vo);
+ }
+ }
+ }
+}
+
/*** SUBMIT ***/
static struct fastbuf *
err(c, "No such task");
return;
}
+
+ byte *pname = obj_find_aval(c->request, 'P');
+ if (!pname)
+ {
+ simp_node *s = clist_head(&task->parts);
+ ASSERT(s);
+ pname = s->s;
+ }
+ else if (!part_exists_p(task, pname))
+ {
+ err(c, "No such task part");
+ return;
+ }
+
+ byte *ext = obj_find_aval(c->request, 'X');
+ if (!ext || !ext_exists_p(task, ext))
+ {
+ err(c, "Missing or invalid extension");
+ return;
+ }
+
struct fastbuf *fb = read_attachment(c);
if (!fb)
return;
// FIXME: Check contest time
// FIXME: Keep history of submitted tasks
- // FIXME: File names
task_lock_status(c);
- struct odes *o = task_status_find_task(c, task);
- task_submit(c, task, fb, task->name);
- log(L_INFO, "User %s submitted task %s", c->user, task->name);
+ struct odes *tasko = task_status_find_task(c, task, 1);
+ struct odes *parto = task_status_find_part(tasko, pname, 1);
+ uns current_ver = obj_find_anum(parto, 'V', 0);
+ uns last_ver = 0;
+ uns replaced_ver = 0;
+ for (struct oattr *a = obj_find_attr(parto, 'V' + OBJ_ATTR_SON); a; a=a->same)
+ {
+ uns ver = obj_find_anum(a->son, 'V', 0);
+ byte *ext = obj_find_aval(a->son, 'X');
+ ASSERT(ver && ext);
+ last_ver = MAX(last_ver, ver);
+ if (ver == current_ver)
+ {
+ task_delete_part(c->user, tname, pname, ext, ver);
+ obj_set_attr(a->son, 'S', "replaced");
+ replaced_ver = current_ver;
+ }
+ }
+ struct odes *vero = obj_add_son(parto, 'V' + OBJ_ATTR_SON);
+ obj_set_attr_num(vero, 'V', ++last_ver);
+ obj_set_attr_num(vero, 'T', time(NULL));
+ obj_set_attr(vero, 'S', "submitted");
+ obj_set_attr(vero, 'X', ext);
+ // FIXME: hash
+ // FIXME: remove old versions from the status file?
+ task_submit_part(c->user, tname, pname, ext, last_ver, fb);
+ obj_set_attr_num(parto, 'V', last_ver);
task_unlock_status(c, 1);
+
+ log(L_INFO, "User %s submitted task %s%s (version %d%s)",
+ c->user, tname,
+ (strcmp(tname, pname) ? stk_printf("/%s", pname) : ""),
+ last_ver,
+ (replaced_ver ? stk_printf(", replaced %d", replaced_ver) : ""));
}
/*** COMMAND MUX ***/
log(L_DEBUG, "<< %s", cmd);
if (!strcasecmp(cmd, "SUBMIT"))
cmd_submit(c);
+ else if (!strcasecmp(cmd, "STATUS"))
+ cmd_status(c);
else
err(c, "Unknown command");
}
}
};
-static byte *
-config_init(void)
-{
- clist_init(&access_rules);
- return NULL;
-}
-
static struct cf_section submitd_conf = {
- CF_INIT(config_init),
CF_ITEMS {
CF_UNS("Port", &port),
CF_UNS("DHBits", &dh_bits),
struct task {
cnode n;
byte *name;
+ uns open_data; // Number of parts for open-data tasks
+ clist parts; // List of parts of this task (simp_nodes)
+ clist *extensions; // List of allowed extensions for this task (simp_nodes)
};
extern clist task_list;
extern struct cf_section tasks_conf;
struct task *task_find(byte *name);
+int part_exists_p(struct task *t, byte *name);
int user_exists_p(byte *user);
+int ext_exists_p(struct task *t, byte *ext);
+
void task_lock_status(struct conn *c);
void task_unlock_status(struct conn *c, uns write_back);
-void task_submit(struct conn *c, struct task *t, struct fastbuf *fb, byte *filename);
-struct odes *task_status_find_task(struct conn *c, struct task *t);
+void task_load_status(struct conn *c);
+
+struct odes *task_status_find_task(struct conn *c, struct task *t, uns create);
+struct odes *task_status_find_part(struct odes *t, byte *part, uns create);
+
+void task_submit_part(byte *user, byte *task, byte *part, byte *ext, uns version, struct fastbuf *fb);
+void task_delete_part(byte *user, byte *task, byte *part, byte *ext, uns version);
#endif
#include "lib/conf.h"
#include "lib/fastbuf.h"
#include "lib/stkstring.h"
+#include "lib/simple-lists.h"
+#include "lib/mempool.h"
#include "sherlock/object.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
+#include <errno.h>
#include "submitd.h"
clist task_list;
+static clist extensions;
+static clist open_data_extensions;
static byte *
-tasks_conf_init(void)
+tasks_conf_commit(void *p UNUSED)
{
- clist_init(&task_list);
+ // We do not do any journaling here as we do not switch config files on the fly
+ CLIST_FOR_EACH(struct task *, t, task_list)
+ {
+ clist_init(&t->parts);
+ if (t->open_data)
+ {
+ for (uns i=1; i<=t->open_data; i++)
+ simp_append(cf_pool, &t->parts)->s = mp_printf(cf_pool, "%d", i);
+ t->extensions = &open_data_extensions;
+ }
+ else
+ {
+ simp_append(cf_pool, &t->parts)->s = t->name;
+ t->extensions = &extensions;
+ }
+ }
return NULL;
}
CF_TYPE(struct task),
CF_ITEMS {
CF_STRING("Name", PTR_TO(struct task, name)),
+ CF_UNS("OpenData", PTR_TO(struct task, open_data)),
CF_END
}
};
struct cf_section tasks_conf = {
- CF_INIT(tasks_conf_init),
+ CF_COMMIT(tasks_conf_commit),
CF_ITEMS {
CF_LIST("Task", &task_list, &task_conf),
+ CF_LIST("Extension", &extensions, &cf_string_list_config),
+ CF_LIST("OpenDataExt", &open_data_extensions, &cf_string_list_config),
CF_END
}
};
return NULL;
}
+int
+part_exists_p(struct task *t, byte *name)
+{
+ CLIST_FOR_EACH(simp_node *, p, t->parts)
+ if (!strcmp(p->s, name))
+ return 1;
+ return 0;
+}
+
+int
+ext_exists_p(struct task *t, byte *ext)
+{
+ CLIST_FOR_EACH(simp_node *, x, *t->extensions)
+ if (!strcmp(x->s, ext))
+ return 1;
+ return 0;
+}
+
int
user_exists_p(byte *user)
{
- byte *fn = stk_printf("solutions/%s/status", user);
+ byte *fn = stk_printf("solutions/%s", user);
struct stat st;
- return !stat(fn, &st) && S_ISREG(st.st_mode);
+ return !stat(fn, &st) && S_ISDIR(st.st_mode);
+}
+
+void
+task_load_status(struct conn *c)
+{
+ struct fastbuf *fb = bopen_try(stk_printf("solutions/%s/status", c->user), O_RDONLY, 4096);
+ c->task_status = obj_new(c->pool);
+ if (fb)
+ {
+ obj_read(fb, c->task_status);
+ bclose(fb);
+ }
}
void
};
if (fcntl(c->task_lock_fd, F_SETLKW, &fl) < 0)
die("Cannot lock status file: %m");
-
- struct fastbuf *fb = bopen_try(stk_printf("solutions/%s/status", c->user), O_RDONLY, 4096);
- c->task_status = obj_new(c->pool);
- if (fb)
- {
- obj_read(fb, c->task_status);
- bclose(fb);
- }
+ task_load_status(c);
}
void
task_unlock_status(struct conn *c, uns write_back)
{
ASSERT(c->task_lock_fd);
- ASSERT(c->task_status);
if (write_back)
{
if (fcntl(c->task_lock_fd, F_SETLKW, &fl) < 0)
die("Cannot unlock status file: %m");
c->task_lock_fd = 0;
- c->task_status = NULL;
}
struct odes *
-task_status_find_task(struct conn *c, struct task *t)
+task_status_find_task(struct conn *c, struct task *t, uns create)
{
for (struct oattr *a = obj_find_attr(c->task_status, 'T' + OBJ_ATTR_SON); a; a=a->same)
{
struct odes *o = a->son;
byte *name = obj_find_aval(o, 'T');
+ ASSERT(name);
if (!strcmp(name, t->name))
return o;
}
+ if (!create)
+ return NULL;
struct odes *o = obj_add_son(c->task_status, 'T' + OBJ_ATTR_SON);
obj_set_attr(o, 'T', t->name);
return o;
}
-void
-task_submit(struct conn *c, struct task *t, struct fastbuf *fb, byte *filename)
+struct odes *
+task_status_find_part(struct odes *to, byte *part, uns create)
+{
+ for (struct oattr *a = obj_find_attr(to, 'P' + OBJ_ATTR_SON); a; a=a->same)
+ {
+ struct odes *o = a->son;
+ byte *name = obj_find_aval(o, 'P');
+ ASSERT(name);
+ if (!strcmp(name, part))
+ return o;
+ }
+ if (!create)
+ return NULL;
+ struct odes *o = obj_add_son(to, 'P' + OBJ_ATTR_SON);
+ obj_set_attr(o, 'P', part);
+ return o;
+}
+
+void task_submit_part(byte *user, byte *task, byte *part, byte *ext, uns version UNUSED, struct fastbuf *fb)
+{
+ byte *dir = stk_printf("solutions/%s/%s", user, task);
+ byte *name = stk_printf("%s/%s.%s", dir, part, ext);
+
+ struct stat st;
+ if (stat(dir, &st) < 0 && errno == ENOENT && mkdir(dir, 0777) < 0)
+ die("Cannot create %s: %m", dir);
+
+ bconfig(fb, BCONFIG_IS_TEMP_FILE, 0);
+ if (rename(fb->name, name) < 0)
+ die("Cannot rename %s to %s: %m", fb->name, name);
+}
+
+void task_delete_part(byte *user, byte *task, byte *part, byte *ext, uns version UNUSED)
{
- byte *dir = stk_printf("solutions/%s/%s", c->user, t->name);
- byte *name = stk_printf("%s/%s", dir, filename);
+ byte *dir = stk_printf("solutions/%s/%s", user, task);
+ byte *name = stk_printf("%s/%s.%s", dir, part, ext);
+ if (unlink(name) < 0)
+ log(L_ERROR, "Cannot delete %s: %m", name);
}
) or die "Cannot establish TLS connection: " . IO::Socket::SSL::errstr() . "\n";
}
-print $sk "Hello, world!\n";
-my $y = <$sk>;
-print $y;
+sub sendobj($) {
+ my ($h) = @_;
+ foreach my $x (keys %{$h}) {
+ print $sk $x, $h->{$x}, "\n";
+ }
+ print $sk "\n";
+ # FIXME: flush
+};
+
+sub recvobj() {
+ my $h = {};
+ while (<$sk>) {
+ chomp;
+ /^(.)(.*)$/ || last;
+ $h->{$1} = $2;
+ }
+ if (defined $h->{'-'}) { die "-" . $h->{'-'} . "\n"; }
+ return $h;
+}
+
+sub printobj($) {
+ my ($h) = @_;
+ foreach my $x (keys %{$h}) {
+ print $x, $h->{$x}, "\n";
+ }
+}
+
+sendobj({ 'U' => 'testuser' });
+recvobj();
+
+#sendobj({ '!' => 'SUBMIT', 'T' => 'plans', 'S' => 100, 'X' => 'c' });
+#recvobj();
+#print $sk "<";
+#foreach my $x (1..98) { print $sk "."; }
+#print $sk ">";
+#recvobj();
+
+sendobj({ '!' => 'STATUS' });
+printobj(recvobj());
+
close $sk;