--- /dev/null
+Revision history for Perl extension Gruntmaster::Daemon.
+
+0.001 Tue Oct 15 00:15:35 2013
+ - original version; created by h2xs 1.23 with options
+ -AX -v 0.001 -b 5.14.0 --skip-exporter Gruntmaster::Daemon
+
--- /dev/null
+Changes
+gruntmasterd
+lib/Gruntmaster/Daemon/Base.pm
+lib/Gruntmaster/Daemon/Constants.pm
+lib/Gruntmaster/Daemon/Format/CPP.pm
+lib/Gruntmaster/Daemon/Generator/File.pm
+lib/Gruntmaster/Daemon/Generator/Run.pm
+lib/Gruntmaster/Daemon/Generator/Undef.pm
+lib/Gruntmaster/Daemon/Judge/Absolute.pm
+lib/Gruntmaster/Daemon/Judge/Points.pm
+lib/Gruntmaster/Daemon.pm
+lib/Gruntmaster/Daemon/Runner/File.pm
+lib/Gruntmaster/Daemon/Runner/Interactive.pm
+lib/Gruntmaster/Daemon/Runner/Verifier.pm
+log.conf
+Makefile.PL
+MANIFEST
+README
+selinux/gruntmasterd.te
+t/00-compile.t
+t/01-jobs.t
+t/problems/aminusb/1.in
+t/problems/aminusb/2.in
+t/problems/aminusb/3.in
+t/problems/aminusb/4.in
+t/problems/aminusb/5.in
+t/problems/aminusb/meta.yml
+t/problems/aminusb/tests/ac/meta.yml
+t/problems/aminusb/tests/ac/prog.cpp
+t/problems/aminusb/tests/wa/meta.yml
+t/problems/aminusb/tests/wa/prog.cpp
+t/problems/aminusb/ver.cpp
+t/problems/aplusb/1.in
+t/problems/aplusb/1.ok
+t/problems/aplusb/2.in
+t/problems/aplusb/2.ok
+t/problems/aplusb/3.in
+t/problems/aplusb/3.ok
+t/problems/aplusb/4.in
+t/problems/aplusb/4.ok
+t/problems/aplusb/5.in
+t/problems/aplusb/5.ok
+t/problems/aplusb/6.in
+t/problems/aplusb/6.ok
+t/problems/aplusb/meta.yml
+t/problems/aplusb/tests/40/meta.yml
+t/problems/aplusb/tests/40/prog.cpp
+t/problems/aplusb/tests/ac/meta.yml
+t/problems/aplusb/tests/ac/prog.cpp
+t/problems/aplusb/tests/compile-error/meta.yml
+t/problems/aplusb/tests/compile-error/prog.cpp
+t/problems/aplusb/tests/mle/meta.yml
+t/problems/aplusb/tests/mle/prog.cpp
+t/problems/aplusb/tests/nzx/meta.yml
+t/problems/aplusb/tests/nzx/prog.cpp
+t/problems/aplusb/tests/ole/meta.yml
+t/problems/aplusb/tests/ole/prog.cpp
+t/problems/aplusb/tests/tle/meta.yml
+t/problems/aplusb/tests/tle/prog.cpp
+t/problems/double/10.in
+t/problems/double/10.ok
+t/problems/double/1.in
+t/problems/double/1.ok
+t/problems/double/2.in
+t/problems/double/2.ok
+t/problems/double/3.in
+t/problems/double/3.ok
+t/problems/double/4.in
+t/problems/double/4.ok
+t/problems/double/5.in
+t/problems/double/5.ok
+t/problems/double/6.in
+t/problems/double/6.ok
+t/problems/double/7.in
+t/problems/double/7.ok
+t/problems/double/8.in
+t/problems/double/8.ok
+t/problems/double/9.in
+t/problems/double/9.ok
+t/problems/double/meta.yml
+t/problems/double/tests/wa/meta.yml
+t/problems/double/tests/wa/prog.cpp
+t/problems/increment/int.cpp
+t/problems/increment/meta.yml
+t/problems/increment/tests/ac/meta.yml
+t/problems/increment/tests/ac/prog.cpp
+t/problems/square/gen.cpp
+t/problems/square/meta.yml
+t/problems/square/tests/ac/meta.yml
+t/problems/square/tests/ac/prog.cpp
+t/problems/square/ver.cpp
--- /dev/null
+use 5.014000;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+ NAME => 'Gruntmaster::Daemon',
+ VERSION_FROM => 'lib/Gruntmaster/Daemon.pm',
+ EXE_FILES => [ 'gruntmasterd' ],
+ ABSTRACT_FROM => 'lib/Gruntmaster/Daemon.pm',
+ AUTHOR => 'Marius Gavrilescu <marius@ieval.ro>',
+ MIN_PERL_VERSION => '5.14.0',
+ LICENSE => 'perl',
+ BUILD_REQUIRES => {
+ qw/Cwd 0
+ File::Copy 0
+ File::Copy::Recursive 0
+ File::Path 0
+ File::Temp 0
+ List::Util 0
+ Log::Log4perl 0
+ Test::More 0
+ YAML::Any 0/,
+ },
+ SIGN => 1,
+ PREREQ_PM => {
+ qw/Exporter 0
+ Fcntl 0
+ File::Basename 0
+ File::Copy 0
+ File::Spec::Functions 0
+ IO::File 0
+ IPC::Open3 0
+ List::Util 0
+ POSIX 0
+ Time::HiRes 0
+
+ BSD::Resource 0
+ File::Slurp 0
+ IPC::Signal 0
+ Linux::Inotify2 0
+ List::MoreUtils 0
+ Log::Log4perl 0
+ Try::Tiny 0
+ YAML::Any 0/,
+ },
+ META_MERGE => {
+ dynamic_config => 0,
+ }
+);
--- /dev/null
+Gruntmaster-Daemon version 0.001
+================================
+
+The README is used to introduce the module and provide instructions on
+how to install the module, any machine dependencies it may have (for
+example C compilers and installed libraries) and any other information
+that should be provided before the module is installed.
+
+A README file is required for CPAN modules since CPAN extracts the
+README file from a module distribution so that people browsing the
+archive can use it get an idea of the modules uses. It is usually a
+good idea to provide version information here so that people can
+decide whether fixes for the module are worth downloading.
+
+INSTALLATION
+
+To install this module type the following:
+
+ perl Makefile.PL
+ make
+ make test
+ make install
+
+DEPENDENCIES
+
+This module requires these other modules and libraries:
+
+ blah blah blah
+
+COPYRIGHT AND LICENCE
+
+Put the correct copyright and licence information here.
+
+Copyright (C) 2013 by Marius Gavrilescu
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.18.1 or,
+at your option, any later version of Perl 5 you may have available.
+
+
--- /dev/null
+#!/usr/bin/perl -w
+use v5.14;
+use Gruntmaster::Daemon;
+
+Gruntmaster::Daemon->run;
--- /dev/null
+package Gruntmaster::Daemon;
+
+use 5.014000;
+use strict;
+use warnings;
+
+our $VERSION = '0.001';
+
+use Gruntmaster::Daemon::Constants qw/ERR/;
+use Gruntmaster::Daemon::Base qw/watch/;
+use Gruntmaster::Page qw/generate/;
+
+use Fcntl qw/:flock/;
+use File::Copy qw/cp/;
+use IO::File;
+use Time::HiRes qw/time/;
+use Try::Tiny;
+use YAML::Any qw/LoadFile DumpFile/;
+use Log::Log4perl qw/get_logger/;
+
+##################################################
+
+sub safe_can_nodie {
+ my ($type, $sub, $name) = @_;
+
+ return unless $name =~ /^\w+$/;
+ no strict 'refs';
+ my $pkg = __PACKAGE__ . "::${type}::${name}";
+ eval "require $pkg" or get_logger->warn("Error while requiring $pkg: $@");
+ $pkg->can($sub);
+}
+
+sub safe_can {
+ my ($type, $sub, $name) = @_;
+
+ safe_can_nodie @_ or get_logger->logdie("No such \l$type: '$name'");
+}
+
+sub prepare_files{
+ my ($dir, $meta) = @_;
+
+ for my $file (values $meta->{files}) {
+ my ($format, $name) = @{$file}{qw/format name/};
+
+ my $prepare = safe_can Format => prepare => $format;
+ $file->{run} = safe_can Format => run => $format;
+ die "No such file: '$name'" unless -e $name;
+ $prepare->($name);
+ }
+}
+
+sub process{
+ my $dir = $_[0];
+
+ chdir $dir;
+ mkdir "in";
+ for (<*>) {
+ cp $_, "in" unless $_ eq 'in' || $_ eq 'pidfile';
+ }
+
+ my @results;
+ my @full_results = ();
+ my $meta = {};
+ try {
+ $meta = LoadFile "meta.yml";
+ if (exists $meta->{problem}) {
+ my $problem = $meta->{problem};
+ die "No such problem: $problem" unless -d "../../pb/$problem";
+ for (<../../pb/$problem/*>) {
+ cp $_, '.' unless $_ eq "../../pb/$problem/meta.yml"
+ }
+
+ my $pbmeta = LoadFile "../../pb/$problem/meta.yml";
+ $meta = {%$meta, %$pbmeta};
+ }
+
+ my ($files, $generator, $runner, $judge, $testcnt) = map { $meta->{$_} or die "Required parameter missing: $_"} qw/files generator runner judge testcnt/;
+
+ $generator = safe_can Generator => generate => $generator;
+ $runner = safe_can Runner => run => $runner;
+ $judge = safe_can Judge => judge => $judge;
+
+ prepare_files $dir, $meta;
+ for my $test (1 .. $testcnt) {
+ my $start_time = time;
+ my $result;
+ try {
+ $generator->($test, $meta);
+ $result = $runner->($test, $meta);
+ } catch {
+ $result = $_;
+ unless (ref $result) {
+ chomp $result;
+ $result = [ERR, $result];
+ }
+ };
+
+ if (ref $result) {
+ get_logger->trace("Test $test result is " . $result->[1]);
+ push @full_results, {id => $test, result => $result->[0], result_text => $result->[1], time => time - $start_time}
+ } else {
+ get_logger->trace("Test $test result is $result");
+ push @full_results, {id => $test, result => 0, result_text => $result, time => time - $start_time}
+ }
+ push @results, $result;
+ last if $meta->{judge} eq 'Absolute' && ref $result
+ }
+
+ my %results = $judge->(@results);
+ $meta->{$_} = $results{$_} for keys %results;
+ } catch {
+ s,(.*) at .*,$1,;
+ chomp;
+ $meta->{result} = -1;
+ $meta->{result_text} = $_;
+ };
+
+ # Clean up
+ get_logger->info("Job result: " . $meta->{result_text});
+ delete $meta->{files}{$_}{run} for keys $meta->{files};
+ $meta->{date} = time;
+ $meta->{results} = \@full_results if scalar @full_results;
+ DumpFile "meta.yml", $meta;
+ for (<*>) {
+ unlink $_ unless $_ eq 'in' || $_ eq 'meta.yml';
+ }
+ chdir '../..';
+ mkdir 'log' unless -d 'log';
+ IO::File->new('>log/meta.yml')->close unless -f 'log/meta.yml';
+ flock my $logmetafh = IO::File->new('<log/meta.yml'), LOCK_EX;
+ my $logmeta = LoadFile 'log/meta.yml';
+ $logmeta->{last}++;
+ rename $dir, 'log/' . $logmeta->{last};
+ generate 'log/' . $logmeta->{last} . '/index.htm';
+ DumpFile 'log/meta.yml', $logmeta;
+ undef $logmetafh;
+ generate 'log/index.html'
+}
+
+sub run{
+ Log::Log4perl->init('log.conf');
+ watch 'jobs', \&process;
+}
+
+1;
+__END__
+# Below is stub documentation for your module. You'd better edit it!
+
+=head1 NAME
+
+Gruntmaster::Daemon - Perl extension for blah blah blah
+
+=head1 SYNOPSIS
+
+ use Gruntmaster::Daemon;
+ blah blah blah
+
+=head1 DESCRIPTION
+
+Stub documentation for Gruntmaster::Daemon, created by h2xs. It looks like the
+author of the extension was negligent enough to leave the stub
+unedited.
+
+Blah blah blah.
+
+
+=head1 SEE ALSO
+
+Mention other useful documentation such as the documentation of
+related modules or operating system documentation (such as man pages
+in UNIX), or any relevant external documentation such as RFCs or
+standards.
+
+If you have a mailing list set up for your module, mention it here.
+
+If you have a web site set up for your module, mention it here.
+
+=head1 AUTHOR
+
+Marius Gavrilescu, E<lt>marius@E<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2013 by Marius Gavrilescu
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.18.1 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
--- /dev/null
+package Gruntmaster::Daemon::Base;
+
+use 5.014000;
+use strict;
+use warnings;
+use parent qw/Exporter/;
+our @EXPORT_OK = qw/watch/;
+our $VERSION = '0.001';
+
+use Fcntl qw/O_WRONLY O_EXCL O_CREAT/;
+use Linux::Inotify2;
+use Log::Log4perl qw/get_logger/;
+
+##################################################
+
+sub process{
+ my ($name, $dir, $cb) = @_;
+ my $logger = get_logger;
+ $logger->debug("Taking job $name...");
+ if (sysopen my $file, "$dir/$name/pidfile", O_WRONLY | O_EXCL | O_CREAT){
+ $logger->debug("Successfully taken job $name, executing callback");
+ $cb->("$dir/$name");
+ } else {
+ $logger->debug("Job $name already taken");
+ }
+}
+
+sub watch{
+ my ($dir, $cb) = @_;
+ for (<$dir/*>) {
+ s,$dir/,,;
+ process $_, $dir, $cb;
+ }
+
+ my $logger = Log::Log4perl->get_logger(__PACKAGE__);
+ my $inotify = Linux::Inotify2->new or $logger->logdie("Unable to create Linux::Inotify2 object: $!");
+ $inotify->watch($dir, IN_MOVED_TO, sub { process $_[0]->name, $dir, $cb }) or $logger->logdie("Error watching $dir: $!");
+ 1 while $inotify->poll;
+ $logger->logdie("Inotify polling stopped: $!");
+}
+
+1
--- /dev/null
+package Gruntmaster::Daemon::Constants;
+
+use 5.014000;
+use strict;
+use warnings;
+use parent qw/Exporter/;
+
+our $VERSION = '0.001';
+
+use constant +{
+ # Accepted
+ AC => 0,
+
+ # Internal server error
+ ERR => -1,
+
+ # All other errors
+ WA => 1,
+ NZX => 2,
+ TLE => 3,
+ OLE => 4,
+ DIED => 5,
+ REJ => 10,
+};
+
+our @EXPORT_OK = qw/AC ERR WA NZX TLE OLE DIED REJ/;
--- /dev/null
+package Gruntmaster::Daemon::Format::CPP;
+
+use 5.014000;
+use strict;
+use warnings;
+
+use BSD::Resource qw/setrlimit RLIMIT_AS RLIMIT_FSIZE/;
+use POSIX qw//;
+use File::Basename qw/fileparse/;
+use Gruntmaster::Daemon::Constants qw/TLE OLE DIED NZX/;
+use Log::Log4perl qw/get_logger/;
+use Time::HiRes qw/alarm/;
+use List::MoreUtils qw/natatime/;
+use IPC::Signal qw/sig_name sig_num/;
+use IPC::Open3 qw/open3/;
+use File::Spec::Functions qw/devnull/;
+use Fcntl qw/F_GETFD F_SETFD FD_CLOEXEC/;
+
+our $VERSION = '0.001';
+
+##################################################
+
+sub prepare{
+ my $name = $_[0];
+ my $basename = fileparse $name, qr/\.[^.]*/;
+ get_logger->trace("Preparing file $name...");
+
+ open my $devnull, devnull;
+ open my $errors, '>compile-error';
+ my $ret = open3 $devnull, $errors, $errors, 'g++', '-o', $basename, $name;
+ local $SIG{ALRM} = sub {kill KILL => $ret};
+ alarm 5;
+ wait;
+ close $devnull;
+ close $errors;
+ die 'Compile error' if $?
+}
+
+sub run{
+ my ($name, %args) = @_;
+ my $basename = fileparse $name, qr/\.[^.]*/;
+ my $ret = fork // die 'Cannot fork';
+ if ($ret) {
+ my $tle;
+ local $SIG{ALRM} = sub { kill KILL => $ret; $tle = 1};
+ alarm $args{timeout} if exists $args{timeout};
+ wait;
+ alarm 0;
+ my $sig = $? & 127;
+ my $signame = sig_name $sig;
+ die [TLE, "Time Limit Exceeded"] if $tle;
+ die [OLE, 'Output Limit Exceeded'] if $sig && $signame eq 'XFSZ';
+ die [DIED, "Crash (SIG$signame)"] if $sig;
+ die [NZX, "Non-zero exit status: " . ($? >> 8)] if $?;
+ } else {
+ $^F = 50;
+ POSIX::close $_ for 0, 1, 3 .. $^F;
+ my @fds = exists $args{fds} ? @{$args{fds}} : ();
+ get_logger->trace("Running $basename with fds ". join ' ', @fds);
+ my $it = natatime 2, @fds;
+ while (my ($fd, $file) = $it->()) {
+ open my $fh, $file or die $!;
+ my $oldfd = fileno $fh;
+ if ($oldfd != $fd) {
+ POSIX::dup2 $oldfd, $fd or die $!;
+ POSIX::close $oldfd or die $!;
+ }
+ }
+# POSIX::close 2;
+ setrlimit RLIMIT_AS, $args{mlimit}, $args{mlimit} if exists $args{mlimit};
+ setrlimit RLIMIT_FSIZE, $args{olimit}, $args{olimit} if exists $args{olimit};
+ exec "./$basename", exists $args{args} ? @{$args{args}} : ();
+ }
+}
+
+1
--- /dev/null
+package Gruntmaster::Daemon::Generator::File;
+
+use 5.014000;
+use strict;
+use warnings;
+
+use File::Copy qw/cp/;
+use Log::Log4perl qw/get_logger/;
+
+our $VERSION = '0.001';
+
+##################################################
+
+sub generate{
+ my ($test, $meta) = @_;
+ get_logger->trace("Generating test $test ...");
+ cp "$test.in", 'input' or die "Cannot copy input for test $test: $!";
+}
+
+1
--- /dev/null
+package Gruntmaster::Daemon::Generator::Run;
+
+use 5.014000;
+use strict;
+use warnings;
+
+use Log::Log4perl qw/get_logger/;
+
+our $VERSION = '0.001';
+
+##################################################
+
+sub generate{
+ my ($test, $meta) = @_;
+ my $gen = $meta->{files}{gen};
+ get_logger->trace("Generating test $test...");
+ $gen->{run}->($gen->{name}, args => [ $test ], fds => [qw/1 >input/]);
+}
--- /dev/null
+package Gruntmaster::Daemon::Generator::Undef;
+
+use 5.014000;
+use strict;
+use warnings;
+
+use Log::Log4perl qw/get_logger/;
+
+our $VERSION = '0.001';
+
+##################################################
+
+sub generate{
+ get_logger->trace("Pretending to generate test $_[0]...");
+}
+
+1
--- /dev/null
+package Gruntmaster::Daemon::Judge::Absolute;
+
+use 5.014000;
+use strict;
+use warnings;
+
+use Gruntmaster::Daemon::Constants qw/AC/;
+
+our $VERSION = '0.001';
+
+##################################################
+
+sub judge{
+ $_ = pop;
+ ref $_ ? (result => $_->[0], result_text => $_->[1]) : (result => AC, result_text => 'Accepted')
+}
+
+1;
+__END__
--- /dev/null
+package Gruntmaster::Daemon::Judge::Points;
+
+use 5.014000;
+use strict;
+use warnings;
+
+use Gruntmaster::Daemon::Constants qw/AC REJ/;
+use List::Util qw/sum/;
+use Log::Log4perl qw/get_logger/;
+our $VERSION = '0.001';
+
+##################################################
+
+sub judge{
+ no warnings qw/numeric/;
+ get_logger->trace("Judging results: @_");
+ my $points = sum 0, grep { $_+0 eq "$_" } @_;
+ $points == 100 ? (result => AC, result_text => 'Accepted') : (result => REJ, result_text => "$points points", points => $points)
+}
+
+1
--- /dev/null
+package Gruntmaster::Daemon::Runner::File;
+
+use 5.014000;
+use strict;
+use warnings;
+
+use Gruntmaster::Daemon::Constants qw/WA/;
+use File::Slurp qw/slurp/;
+use Log::Log4perl qw/get_logger/;
+
+##################################################
+
+sub run{
+ my ($test, $meta) = @_;
+ get_logger->trace("Running on test $test...");
+ $meta->{files}{prog}{run}->($meta->{files}{prog}{name}, fds => [qw/0 input 1 >output/], map {defined $meta->{$_} ? ($_ => $meta->{$_}) : () } qw/timeout olimit mlimit/);
+ my $out = slurp 'output';
+ my $ok = slurp "$test.ok";
+
+ $out =~ s/^\s+//;
+ $ok =~ s/^\s+//;
+ $out =~ s/\s+/ /;
+ $ok =~ s/\s+/ /;
+ $out =~ s/\s+$//;
+ $ok =~ s/\s+$//;
+
+ die [WA, "Wrong answer"] if $out ne $ok;
+ $meta->{tests}[$test - 1] // 0
+}
+
+1
--- /dev/null
+package Gruntmaster::Daemon::Runner::Interactive;
+
+use 5.014000;
+use strict;
+use warnings;
+
+use File::Slurp qw/slurp/;
+use Gruntmaster::Daemon::Constants qw/WA/;
+use Log::Log4perl qw/get_logger/;
+use POSIX qw/mkfifo/;
+use Try::Tiny;
+
+##################################################
+
+sub run{
+ my ($test, $meta) = @_;
+ get_logger->trace("Running on test $test...");
+ my $ret = fork // get_logger->logdie("Fork failed: $!");
+
+ mkfifo 'fifo1', 0600 or die $! unless -e 'fifo1';
+ mkfifo 'fifo2', 0600 or die $! unless -e 'fifo2';
+ if ($ret) {
+ $meta->{files}{prog}{run}->($meta->{files}{prog}{name}, fds => [qw/0 fifo1 1 >fifo2/], map {defined $meta->{$_} ? ($_ => $meta->{$_}) : () } qw/timeout mlimit/);
+ wait;
+ die [WA, "Wrong Answer"] if $?;
+ } else {
+ try {
+ $meta->{files}{int}{run}->($meta->{files}{int}{name}, fds => [qw/1 >fifo1 0 fifo2 4 >result/]);
+ } catch {
+ exit 1;
+ };
+ exit
+ }
+
+ scalar slurp 'result'
+}
+
+1;
+__END__
--- /dev/null
+package Gruntmaster::Daemon::Runner::Verifier;
+
+use 5.014000;
+use strict;
+use warnings;
+
+use Gruntmaster::Daemon::Constants qw/WA/;
+use File::Slurp qw/slurp/;
+use Log::Log4perl qw/get_logger/;
+use Try::Tiny;
+
+##################################################
+
+sub run{
+ my ($test, $meta) = @_;
+ get_logger->trace("Running on test $test...");
+ $meta->{files}{prog}{run}->($meta->{files}{prog}{name}, fds => [qw/0 input 1 >output/], map {defined $meta->{$_} ? ($_ => $meta->{$_}) : () } qw/timeout olimit mlimit/);
+
+ try {
+ $meta->{files}{ver}{run}->($meta->{files}{ver}{name}, fds => [qw/0 input 3 output 1 >result/]);
+ } catch {
+ die [WA, "Wrong answer"]
+ };
+ scalar slurp 'result';
+}
+
+1
--- /dev/null
+log4perl.category.Gruntmaster.Daemon = TRACE, stderr
+
+log4perl.appender.stderr = Log::Log4perl::Appender::Screen
+log4perl.appender.stderr.layout = Log::Log4perl::Layout::PatternLayout
+log4perl.appender.stderr.layout.ConversionPattern = [%d] [%F{1}:%M{1}:%L] [%p] %m%n
--- /dev/null
+policy_mdule(gruntmasterd, 1.0)
+
+type gruntmasterd_t;
+type gruntmasterd_exec_t;
+init_daemon_domain(gruntmasterd_t, gruntmasterd_exec_t);
+
+type gruntmasterd_log_t;
+logging_log_file(gruntmasterd_log_t);
+
+type gruntmaster_job_t;
+type gruntmaster_job_exec_t;
+init_daemon_domain(gruntmaster_job_t, gruntmaster_job_exec_t);
+
+allow gruntmasterd_t self : process fork;
+
+allow gruntmasterd_t gruntmasterd_job_t : dir ra_dir_perms;
+allow gruntmasterd_t gruntmasterd_job_t : file { create ra_file_perms };
+logging_log_filetrans(gruntmasterd_t, gruntmasterd_log_t, file)
+logging_search_logs(gruntmasterd_t)
+
+libs_use_ld_so(gruntmasterd_t)
+libs_use_ld_so(gruntmasterd_job_t)
+libs_use_shared_libs(gruntmasterd_t)
+libs_use_shared_libs(gruntmasterd_job_t)
\ No newline at end of file
--- /dev/null
+#!/usr/bin/perl -w
+use v5.14;
+use strict;
+use warnings;
+
+use Test::More tests => 1;
+BEGIN { use_ok('Gruntmaster::Daemon') };
--- /dev/null
+#!/usr/bin/perl -w
+use v5.14;
+use strict;
+use warnings;
+
+use Gruntmaster::Daemon;
+
+use Cwd qw/cwd/;
+use File::Copy qw/copy/;
+use File::Copy::Recursive qw/dircopy/;
+use File::Path qw/remove_tree/;
+use File::Temp qw/tempdir/;
+use List::Util qw/sum/;
+use Log::Log4perl;
+use Test::More;
+use YAML::Any qw/LoadFile/;
+
+##################################################
+
+my $loglevel = $ENV{TEST_LOG_LEVEL} // 'OFF';
+my $log_conf = <<CONF;
+log4perl.category.Gruntmaster.Daemon = $loglevel, stderr
+
+log4perl.appender.stderr = Log::Log4perl::Appender::Screen
+log4perl.appender.stderr.layout = Log::Log4perl::Layout::PatternLayout
+log4perl.appender.stderr.layout.ConversionPattern = [\%d] [\%F{1}:\%M{1}:\%L] [\%p] \%m\%n
+CONF
+Log::Log4perl->init(\$log_conf);
+
+sub check_job{
+ my $job = shift;
+ my $meta = LoadFile "$job/meta.yml";
+ if (exists $meta->{results}) {
+ delete $meta->{results}[$_]{time} for keys $meta->{results};
+ }
+ is $meta->{result}, $meta->{expected_result}, "Result is correct";
+ is $meta->{result_text}, $meta->{expected_result_text}, "Result text is correct";
+ is_deeply $meta->{results}, $meta->{expected_results}, "Results are correct";
+}
+
+my @problems = exists $ENV{TEST_PROBLEMS} ? map {"t/problems/$_"} split ' ', $ENV{TEST_PROBLEMS} : <t/problems/*>;
+plan tests => 3 * sum map { my @temp = <$_/tests/*>; scalar @temp } @problems;
+note "Problems to be tested: " . join ', ', @problems;
+
+my $tempdir = tempdir CLEANUP => 1;
+mkdir "$tempdir/jobs";
+dircopy 't/problems' => "$tempdir/pb";
+
+for my $problem (@problems) {
+ my $meta = LoadFile "$problem/meta.yml";
+ TODO: {
+ local $TODO = $meta->{todo} if exists $meta->{todo};
+ note "Now testing problem $meta->{name} ($meta->{description})";
+ for my $source (<$problem/tests/*>) {
+ my $meta = LoadFile "$source/meta.yml";
+ note "Running $meta->{test_name} ($meta->{test_description})...";
+ dircopy $source => "$tempdir/jobs/job";
+ my $savedcwd = cwd;
+ Gruntmaster::Daemon::process "$tempdir/jobs/job";
+ check_job "$tempdir/log/1";
+ chdir $savedcwd;
+ remove_tree "$tempdir/log", "$tempdir/jobs", {keep_root => 1};
+ }
+ }
+}
--- /dev/null
+25926 30994
--- /dev/null
+18404 28903
--- /dev/null
+28523 27674
--- /dev/null
+14479 15757
--- /dev/null
+---
+name: A-B
+generator: File
+judge: Points
+runner: Verifier
+testcnt: 5
+timeout: 1
+mlimit: 20971520
+olimit: 100
+description: The A-B problem, with a verifier
+files:
+ ver:
+ format: CPP
+ name: ver.cpp
+ prog:
+ format: CPP
+ name: prog.cpp
--- /dev/null
+test_name: Accepted
+test_description: A program which works correctly
+problem: aminusb
+expected_result: 0
+expected_result_text: Accepted
+expected_results:
+- id: 1
+ result: 0
+ result_text: 20
+- id: 2
+ result: 0
+ result_text: 20
+- id: 3
+ result: 0
+ result_text: 20
+- id: 4
+ result: 0
+ result_text: 20
+- id: 5
+ result: 0
+ result_text: 20
--- /dev/null
+#include<cstdio>
+int main()
+{
+ int a,b;
+ scanf ("%d%d",&a,&b);
+ printf ("%d",a-b);
+ return 0;
+}
--- /dev/null
+test_name: Wrong answer
+test_description: A program which does nothing
+problem: aminusb
+expected_result: 10
+expected_result_text: 0 points
+expected_results:
+- id: 1
+ result: 1
+ result_text: Wrong answer
+- id: 2
+ result: 1
+ result_text: Wrong answer
+- id: 3
+ result: 1
+ result_text: Wrong answer
+- id: 4
+ result: 1
+ result_text: Wrong answer
+- id: 5
+ result: 1
+ result_text: Wrong answer
--- /dev/null
+int main() { return 0; }
--- /dev/null
+#include <cstdio>
+
+int main(void){
+ FILE *output = fdopen(3, "r");
+ int a, b, ret;
+ scanf("%d%d", &a, &b);
+ fscanf(output, "%d", &ret);
+ if(a-b == ret){
+ printf("20");
+ return 0;
+ } else
+ return 1;
+}
+
--- /dev/null
+25926 30994
--- /dev/null
+18404 28903
--- /dev/null
+28523 27674
--- /dev/null
+14479 15757
--- /dev/null
+2253 23718
--- /dev/null
+---
+name: A+B
+generator: File
+judge: Points
+runner: File
+testcnt: 6
+timeout: 1
+mlimit: 20971520
+olimit: 100
+description: The simple A+B problem
+files:
+ prog:
+ format: CPP
+ name: prog.cpp
+tests:
+- 20
+- 20
+- 20
+- 20
+- 10
+- 10
--- /dev/null
+test_name: 40
+test_description: A program which should get 40 points
+problem: aplusb
+expected_result: 10
+expected_result_text: 40 points
+expected_results:
+- id: 1
+ result: 0
+ result_text: 20
+- id: 2
+ result: 1
+ result_text: Wrong answer
+- id: 3
+ result: 1
+ result_text: Wrong answer
+- id: 4
+ result: 1
+ result_text: Wrong answer
+- id: 5
+ result: 0
+ result_text: 10
+- id: 6
+ result: 0
+ result_text: 10
+
--- /dev/null
+#include<stdio.h>
+int main(void){
+ short a,b;
+ scanf("%hd%hd", &a, &b);
+ printf("%hd", a+b);
+ return 0;
+}
--- /dev/null
+test_name: Accepted
+test_description: A program which works correctly
+problem: aplusb
+expected_result: 0
+expected_result_text: Accepted
+expected_results:
+- id: 1
+ result: 0
+ result_text: 20
+- id: 2
+ result: 0
+ result_text: 20
+- id: 3
+ result: 0
+ result_text: 20
+- id: 4
+ result: 0
+ result_text: 20
+- id: 5
+ result: 0
+ result_text: 10
+- id: 6
+ result: 0
+ result_text: 10
--- /dev/null
+#include<cstdio>
+int main()
+{
+ int a,b;
+ scanf ("%d%d",&a,&b);
+ printf ("%d",a+b);
+ return 0;
+}
--- /dev/null
+test_name: Compile Error
+test_description: A program which does not compile
+problem: aplusb
+expected_result: -1
+expected_result_text: Compile error
\ No newline at end of file
--- /dev/null
+test_name: Memory Limit Exceeded
+test_description: A program which uses up a lot of memory
+problem: aplusb
+expected_result: 10
+expected_result_text: 0 points
+expected_results:
+- id: 1
+ result: 5
+ result_text: Crash (SIGKILL)
+- id: 2
+ result: 5
+ result_text: Crash (SIGKILL)
+- id: 3
+ result: 5
+ result_text: Crash (SIGKILL)
+- id: 4
+ result: 5
+ result_text: Crash (SIGKILL)
+- id: 5
+ result: 5
+ result_text: Crash (SIGKILL)
+- id: 6
+ result: 5
+ result_text: Crash (SIGKILL)
--- /dev/null
+int v[1005][1005][20];
+int main(void){
+}
--- /dev/null
+test_name: Non-Zero Exit status
+test_description: A program which returns 42
+problem: aplusb
+expected_result: 10
+expected_result_text: 0 points
+expected_results:
+- id: 1
+ result: 2
+ result_text: 'Non-zero exit status: 42'
+- id: 2
+ result: 2
+ result_text: 'Non-zero exit status: 42'
+- id: 3
+ result: 2
+ result_text: 'Non-zero exit status: 42'
+- id: 4
+ result: 2
+ result_text: 'Non-zero exit status: 42'
+- id: 5
+ result: 2
+ result_text: 'Non-zero exit status: 42'
+- id: 6
+ result: 2
+ result_text: 'Non-zero exit status: 42'
--- /dev/null
+int main(void){
+ return 42;
+}
--- /dev/null
+test_name: Output Limit Exceeded
+test_description: A program which outputs a lot of nonsense
+problem: aplusb
+expected_result: 10
+expected_result_text: 0 points
+expected_results:
+- id: 1
+ result: 4
+ result_text: Output Limit Exceeded
+- id: 2
+ result: 4
+ result_text: Output Limit Exceeded
+- id: 3
+ result: 4
+ result_text: Output Limit Exceeded
+- id: 4
+ result: 4
+ result_text: Output Limit Exceeded
+- id: 5
+ result: 4
+ result_text: Output Limit Exceeded
+- id: 6
+ result: 4
+ result_text: Output Limit Exceeded
--- /dev/null
+#include<cstdio>
+
+int main(void){
+ for(int i=0;i<100;i++)
+ puts("asdasdasd");
+}
--- /dev/null
+test_name: Time Limit Exceeded
+test_description: A program which loops forever
+problem: aplusb
+expected_result: 10
+expected_result_text: 0 points
+expected_results:
+- id: 1
+ result: 3
+ result_text: Time Limit Exceeded
+- id: 2
+ result: 3
+ result_text: Time Limit Exceeded
+- id: 3
+ result: 3
+ result_text: Time Limit Exceeded
+- id: 4
+ result: 3
+ result_text: Time Limit Exceeded
+- id: 5
+ result: 3
+ result_text: Time Limit Exceeded
+- id: 6
+ result: 3
+ result_text: Time Limit Exceeded
--- /dev/null
+int main(void){
+ while(1);
+}
--- /dev/null
+---
+name: A-B
+generator: File
+judge: Absolute
+runner: File
+testcnt: 10
+timeout: 1
+mlimit: 20971520
+olimit: 100
+description: The A * 2 problem
+files:
+ prog:
+ format: CPP
+ name: prog.cpp
--- /dev/null
+test_name: Wrong answer
+test_description: A program which fails on test 4
+problem: double
+expected_result: 1
+expected_result_text: Wrong answer
+expected_results:
+- id: 1
+ result: 0
+ result_text: 0
+- id: 2
+ result: 0
+ result_text: 0
+- id: 3
+ result: 0
+ result_text: 0
+- id: 4
+ result: 1
+ result_text: Wrong answer
--- /dev/null
+#include<cstdio>
+
+int main(void){
+ char x;
+ scanf("%d", &x);
+ printf("%d", 2*x);
+ return 0;
+}
--- /dev/null
+#include<cstdio>
+#include<cstdlib>
+#include<ctime>
+#include<unistd.h>
+
+int main(void){
+ srand(time(NULL));
+ for(int i=0;i<100;i++){
+ int nr = rand() % 100;
+ printf("%d\n", nr);
+ fflush(stdout);
+ int ret;
+ scanf("%d", &ret);
+ if(ret != nr + 1){
+ fprintf(stderr, "bad ret: %d instead of %d", ret, nr + 1);
+ return 1;
+ }
+ }
+
+ if(write(4, "20", 2) == -1)
+ perror("write");
+ return 0;
+}
--- /dev/null
+---
+name: Increment
+generator: Undef
+judge: Points
+runner: Interactive
+testcnt: 5
+timeout: 1
+mlimit: 20971520
+description: A trivial interactive problem
+files:
+ int:
+ format: CPP
+ name: int.cpp
+ prog:
+ format: CPP
+ name: prog.cpp
--- /dev/null
+test_name: Accepted
+test_description: A program which works correctly
+problem: increment
+expected_result: 0
+expected_result_text: Accepted
+expected_results:
+- id: 1
+ result: 0
+ result_text: 20
+- id: 2
+ result: 0
+ result_text: 20
+- id: 3
+ result: 0
+ result_text: 20
+- id: 4
+ result: 0
+ result_text: 20
+- id: 5
+ result: 0
+ result_text: 20
--- /dev/null
+#include<cstdio>
+
+int main(void){
+ for(int i=0;i<100;i++){
+ int x;
+ scanf("%d", &x);
+ printf("%d\n", x + 1);
+ fflush(stdout);
+ }
+
+ return 0;
+}
--- /dev/null
+#include<cstdio>
+#include<cstdlib>
+
+int main(int argc, char **argv){
+ int testnum = atoi(argv[0]);
+ printf("%d", testnum);
+ return 0;
+}
--- /dev/null
+---
+name: A-B
+generator: Run
+judge: Points
+runner: Verifier
+testcnt: 10
+timeout: 1
+mlimit: 20971520
+olimit: 100
+description: The X^2 problem
+files:
+ ver:
+ format: CPP
+ name: ver.cpp
+ gen:
+ format: CPP
+ name: gen.cpp
+ prog:
+ format: CPP
+ name: prog.cpp
--- /dev/null
+test_name: Accepted
+test_description: A program which works correctly
+problem: square
+expected_result: 0
+expected_result_text: Accepted
+expected_results:
+- id: 1
+ result: 0
+ result_text: 10
+- id: 2
+ result: 0
+ result_text: 10
+- id: 3
+ result: 0
+ result_text: 10
+- id: 4
+ result: 0
+ result_text: 10
+- id: 5
+ result: 0
+ result_text: 10
+- id: 6
+ result: 0
+ result_text: 10
+- id: 7
+ result: 0
+ result_text: 10
+- id: 8
+ result: 0
+ result_text: 10
+- id: 9
+ result: 0
+ result_text: 10
+- id: 10
+ result: 0
+ result_text: 10
--- /dev/null
+#include<cstdio>
+
+int main(void){
+ int x;
+ scanf("%d", &x);
+ printf("%d", x*x);
+ return 0;
+}
--- /dev/null
+#include <cstdio>
+
+int main(void){
+ FILE *output = fdopen(3, "r");
+ int x, ret;
+ scanf("%d", &x);
+ fscanf(output, "%d", &ret);
+ if(x*x == ret){
+ printf("10");
+ return 0;
+ } else
+ return 1;
+}
+