/* The MIT License (MIT) Copyright (c) 2013-2017 SRS(ossrs) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include // for srs-librtmp, @see https://github.com/ossrs/srs/issues/213 #ifndef _WIN32 #include #endif using namespace std; #include #include #include #include #include #include #include SrsProcess::SrsProcess() { is_started = false; fast_stopped = false; pid = -1; } SrsProcess::~SrsProcess() { } int SrsProcess::get_pid() { return pid; } bool SrsProcess::started() { return is_started; } int SrsProcess::initialize(string binary, vector argv) { int ret = ERROR_SUCCESS; bin = binary; cli = ""; actual_cli = ""; params.clear(); for (int i = 0; i < (int)argv.size(); i++) { std::string ffp = argv[i]; std::string nffp = (i < (int)argv.size() - 1)? argv[i + 1] : ""; std::string nnffp = (i < (int)argv.size() - 2)? argv[i + 2] : ""; // >file if (srs_string_starts_with(ffp, ">")) { stdout_file = ffp.substr(1); continue; } // 1>file if (srs_string_starts_with(ffp, "1>")) { stdout_file = ffp.substr(2); continue; } // 2>file if (srs_string_starts_with(ffp, "2>")) { stderr_file = ffp.substr(2); continue; } // 1 >X if (ffp == "1" && srs_string_starts_with(nffp, ">")) { if (nffp == ">") { // 1 > file if (!nnffp.empty()) { stdout_file = nnffp; i++; } } else { // 1 >file stdout_file = srs_string_trim_start(nffp, ">"); } // skip the > i++; continue; } // 2 >X if (ffp == "2" && srs_string_starts_with(nffp, ">")) { if (nffp == ">") { // 2 > file if (!nnffp.empty()) { stderr_file = nnffp; i++; } } else { // 2 >file stderr_file = srs_string_trim_start(nffp, ">"); } // skip the > i++; continue; } params.push_back(ffp); } actual_cli = srs_join_vector_string(params, " "); cli = srs_join_vector_string(argv, " "); return ret; } int srs_redirect_output(string from_file, int to_fd) { int ret = ERROR_SUCCESS; // use default output. if (from_file.empty()) { return ret; } // redirect the fd to file. int fd = -1; int flags = O_CREAT|O_WRONLY|O_APPEND; mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH; if ((fd = ::open(from_file.c_str(), flags, mode)) < 0) { ret = ERROR_FORK_OPEN_LOG; fprintf(stderr, "open process %d %s failed. ret=%d", to_fd, from_file.c_str(), ret); exit(ret); } if (dup2(fd, to_fd) < 0) { ret = ERROR_FORK_DUP2_LOG; srs_error("dup2 process %d failed. ret=%d", to_fd, ret); exit(ret); } ::close(fd); return ret; } int SrsProcess::start() { int ret = ERROR_SUCCESS; if (is_started) { return ret; } // generate the argv of process. srs_info("fork process: %s", cli.c_str()); // for log int cid = _srs_context->get_id(); int ppid = getpid(); // TODO: fork or vfork? if ((pid = fork()) < 0) { ret = ERROR_ENCODER_FORK; srs_error("vfork process failed, cli=%s. ret=%d", cli.c_str(), ret); return ret; } // for osx(lldb) to debug the child process. // user can use "lldb -p " to resume the parent or child process. //kill(0, SIGSTOP); // child process: ffmpeg encoder engine. if (pid == 0) { // ignore the SIGINT and SIGTERM signal(SIGINT, SIG_IGN); signal(SIGTERM, SIG_IGN); // for the stdin, // should never close it or ffmpeg will error. // for the stdout, ignore when not specified. // redirect stdout to file if possible. if ((ret = srs_redirect_output(stdout_file, STDOUT_FILENO)) != ERROR_SUCCESS) { return ret; } // for the stderr, ignore when not specified. // redirect stderr to file if possible. if ((ret = srs_redirect_output(stderr_file, STDERR_FILENO)) != ERROR_SUCCESS) { return ret; } // should never close the fd 3+, for it myabe used. // for fd should close at exec, use fnctl to set it. // log basic info to stderr. if (true) { fprintf(stderr, "\n"); fprintf(stderr, "process ppid=%d, cid=%d, pid=%d\n", ppid, cid, getpid()); fprintf(stderr, "process binary=%s, cli: %s\n", bin.c_str(), cli.c_str()); fprintf(stderr, "process actual cli: %s\n", actual_cli.c_str()); } // memory leak in child process, it's ok. char** argv = new char*[params.size() + 1]; for (int i = 0; i < (int)params.size(); i++) { std::string& p = params[i]; // memory leak in child process, it's ok. char* v = new char[p.length() + 1]; argv[i] = strcpy(v, p.data()); } argv[params.size()] = NULL; // use execv to start the program. ret = execv(bin.c_str(), argv); if (ret < 0) { fprintf(stderr, "fork process failed, errno=%d(%s)", errno, strerror(errno)); } exit(ret); } // parent. if (pid > 0) { is_started = true; srs_trace("fored process, pid=%d, bin=%s, stdout=%s, stderr=%s, argv=%s", pid, bin.c_str(), stdout_file.c_str(), stderr_file.c_str(), actual_cli.c_str()); return ret; } return ret; } int SrsProcess::cycle() { int ret = ERROR_SUCCESS; if (!is_started) { return ret; } // ffmpeg is prepare to stop, donot cycle. if (fast_stopped) { return ret; } int status = 0; pid_t p = waitpid(pid, &status, WNOHANG); if (p < 0) { ret = ERROR_SYSTEM_WAITPID; srs_error("process waitpid failed, pid=%d, ret=%d", pid, ret); return ret; } if (p == 0) { srs_info("process process pid=%d is running.", pid); return ret; } srs_trace("process pid=%d terminate, please restart it.", pid); is_started = false; return ret; } void SrsProcess::stop() { if (!is_started) { return; } // kill the ffmpeg, // when rewind, upstream will stop publish(unpublish), // unpublish event will stop all ffmpeg encoders, // then publish will start all ffmpeg encoders. int ret = srs_kill_forced(pid); if (ret != ERROR_SUCCESS) { srs_warn("ignore kill the process failed, pid=%d. ret=%d", pid, ret); return; } // terminated, set started to false to stop the cycle. is_started = false; } void SrsProcess::fast_stop() { int ret = ERROR_SUCCESS; if (!is_started) { return; } if (pid <= 0) { return; } if (kill(pid, SIGTERM) < 0) { ret = ERROR_SYSTEM_KILL; srs_warn("ignore fast stop process failed, pid=%d. ret=%d", pid, ret); return; } return; }