在線oj之編譯運行模塊

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

我們還運用了json數據格式,需要安裝一個文件

yum install jsoncpp-devel

簡單介紹一下json數據格式:具體參考博文

  1. 表示名稱 / 值對 { “firstName”: “Brett” }
  2. 表示數組

{ “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());
        }
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章