編譯運行模塊主要負責用戶代碼的編譯和運行,在此過程中我們會定義五個文件,他們分別是:源代碼、程序、編譯錯誤文件、運行錯誤文件、標準輸出
在編譯和運行方法中,我們會進行程序替換,並使用重定向分別將編譯結果和運行結果寫入文件然後返回給前臺。最後刪除臨時文件。
我們還運用了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());
}
};