编译运行模块主要负责用户代码的编译和运行,在此过程中我们会定义五个文件,他们分别是:源代码、程序、编译错误文件、运行错误文件、标准输出
在编译和运行方法中,我们会进行程序替换,并使用重定向分别将编译结果和运行结果写入文件然后返回给前台。最后删除临时文件。
我们还运用了json数据格式,需要安装一个文件
yum install jsoncpp-devel
简单介绍一下json数据格式:具体参考博文
- 表示名称 / 值对 { “firstName”: “Brett” }
- 表示数组
{ “people”: [
{ “firstName”: “Brett”, “lastName”:“McLaughlin”, “email”: “aaaa” },
{ “firstName”: “Jason”, “lastName”:“Hunter”, “email”: “bbbb”},
{ “firstName”: “Elliotte”, “lastName”:“Harold”, “email”: “cccc” }
]}
重点介绍几个接口
1.int stat(const char *restrict pathname, struct stat *restrict buf); 获取文件信息
2.int unlink(const char* pathname); //删除某个文件
#pragma once
#include <sys/resource.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include "tools.hpp"
#include <string>
#include <jsoncpp/json/json.h>
#include <iostream>
#include "oj_log.hpp"
enum ErrorNo
{
OK = 0,
COMPILE_ERROR,
RUN_ERROR,
PRAM_ERROR,
INTER_ERROR
};
class Compiler
{
public:
//有可能浏览器对不同的题目提交的数据不同
//0:编译错误 2:运行错误 3:参数错误 4:内部错误
static void CompilerAndRun(Json::Value req, Json::Value* resp)
{
if (req["code"].empty()) {
(*resp)["errorno"] = PRAM_ERROR;
(*resp)["reason"] = "Pram error";
LOG(ERROR, "Request code id empty") << std::endl;
return;
}
//2.将代码写到文件中去
std::string code = req["code"].asString();
std::string tmp_filename = WriteTmpFile(code);
if (tmp_filename == "") {
(*resp)["errorno"] = INTER_ERROR;
(*resp)["reason"] = "Create file failed";
LOG(ERROR,"Write source failed");
return;
}
//编译
if (!Compile(tmp_filename)) {
(*resp)["errorno"] = COMPILE_ERROR;
std::string reason;
FileOpen::ReadDataFromFile(ErrorPath(tmp_filename), &reason);
(*resp)["reason"] = reason;
LOG(ERROR, "Compile Error") << std::endl;
return;
}
//运行
int sig = Run(tmp_filename);
if (sig != 0) {
(*resp)["errorno"] = RUN_ERROR;
(*resp)["reason"] = "Program exit by sig" + std::to_string(sig);
LOG(ERROR, "run error") << std::endl;
return;
}
//构造响应
(*resp)["errorno"] = OK;
(*resp)["reason"] = "Compile and run is ok!";
//标准输出
std::string stdout_reason;
FileOpen::ReadDataFromFile(StdoutPath(tmp_filename), &stdout_reason);
(*resp)["stdout"] = stdout_reason;
//标准错误
std::string stderr_reason;
FileOpen::ReadDataFromFile(StderrPath(tmp_filename), &stderr_reason);
(*resp)["stdout"] = stderr_reason;
//清理临时文件
Clean(tmp_filename);
return;
}
private:
static std::string WriteTmpFile(const std::string& code)
{
//组织文件名称。组织文件的前缀名称,用来区分源码文件,可知性文件时一组数据
std::string tmp_filename = "/tmp_" + std::to_string(LogTime::GetTimeStamp());
//写文件
int ret = FileOpen::WriteDataToFile(SrcPath(tmp_filename), code);
if (ret < 0) {
LOG(ERROR, "Write code to source failed");
return "";
}
return tmp_filename;
}
//源代码文件
static std::string SrcPath(const std::string& filename)
{
return "./tmp_files" + filename + ".cpp";
}
//可执行文件
static std::string ExePath(const std::string& filename)
{
return "./tmp_files" + filename + ".executable";
}
//编译文件,用于保存编译信息
static std::string ErrorPath(const std::string& filename)
{
return "./tmp_files" + filename + ".error";
}
//运行成功文件,保存运行成功信息
static std::string StdoutPath(const std::string& filename)
{
return "./tmp_files" + filename + ".stdout";
}
//运行失败文件,保存运行失败信息
static std::string StderrPath(const std::string& filename)
{
return "./tmp_files" + filename + ".stderr";
}
static bool Compile(const std::string& filename)
{
//1.构造编译命令
const int command_count = 20;
char buf[command_count][50] = {{0}};
char* command[command_count] = {0};
for(int i = 0; i < command_count; ++i) {
command[i] = buf[i];
}
snprintf(command[0], 49, "%s", "g++");
snprintf(command[1], 49, "%s", SrcPath(filename).c_str());
snprintf(command[2], 49, "%s", "-o");
snprintf(command[3], 49, "%s", ExePath(filename).c_str());
snprintf(command[4], 49, "%s", "-std=c++11");
snprintf(command[5], 49, "%s", "-D");
snprintf(command[6], 49, "%s", "CompileOnline");
command[7] = NULL;
//2.创建子进程
int pid = fork();
if (pid < 0) {
LOG(ERROR, "fork failed");
}
else if (pid == 0) {
//2.2 子进程 程序替换
int fd = open(ErrorPath(filename).c_str(), O_CREAT | O_RDWR, 0664);
if (fd < 0) {
LOG(ERROR, "Open Compile failed") << std::endl;
exit(1);
}
//重定向
dup2(fd, 2);
//程序替换
execvp(command[0], command);
exit(0);
}
else {
//2.1 父进程
waitpid(pid, NULL, 0);
}
//3.验证是否生成可执行程序
struct stat st;
int ret = stat(ExePath(filename).c_str(), &st);
if (ret < 0) {
LOG(ERROR, "Compile ERROR! exe filename is ") << ExePath(filename) << std::endl;
return false;
}
return true;
}
static int Run(const std::string& filename)
{
//可执行程序
//1.创建子进程
int pid = fork();
if (pid < 0) {
LOG(ERROR, "Exec pragma failed") << std::endl;
return -1;
}
else if (pid == 0) {
//对于子进程执行的限制
//1.时间限制
alarm(1);
//2.内存限制
struct rlimit rl;
rl.rlim_cur = 1024 * 30000;
rl.rlim_max = RLIM_INFINITY; //无限制
setrlimit(RLIMIT_AS, &rl);
//标准输出重定义到文件
int stdout_fd = open(StdoutPath(filename).c_str(), O_CREAT | O_RDWR, 0664);
if (stdout_fd < 0) {
LOG(ERROR, "Open stdout file failed") << StdoutPath(filename) << std::endl;
return -1;
}
dup2(stdout_fd, 1);
//标准错误重定向到文件
int stderr_fd = open(StdoutPath(filename).c_str(), O_CREAT | O_RDWR, 0664);
if (stderr_fd < 0) {
LOG(ERROR, "Open stderr file failed") << StdoutPath(filename) << std::endl;
return -1;
}
dup2(stdout_fd, 2);
execl(ExePath(filename).c_str(), ExePath(filename).c_str(), NULL);
exit(1);
}
int status = -1;
waitpid(pid, &status, 0);
//将是否收到信号的信息返回给调用者,如果调用者判断为0,
return status & 0x7f;
}
//运行完成后删除临时文件
static void Clean(std::string filename)
{
unlink(SrcPath(filename).c_str());
unlink(ExePath(filename).c_str());
unlink(ErrorPath(filename).c_str());
unlink(StdoutPath(filename).c_str());
unlink(StderrPath(filename).c_str());
}
};