using std::locale;
using std::isspace;
+ using std::list;
+ using std::pair;
+
// First try and find an existing record...
ServiceRecord * rval = findService(string(name));
if (rval != 0) {
service_filename += name;
string command;
- std::list<std::pair<unsigned,unsigned>> command_offsets;
+ list<pair<unsigned,unsigned>> command_offsets;
+ string stop_command;
+ list<pair<unsigned,unsigned>> stop_command_offsets;
ServiceType service_type = ServiceType::PROCESS;
std::list<ServiceRecord *> depends_on;
if (setting == "command") {
command = read_setting_value(i, end, &command_offsets);
}
+ else if (setting == "stop-command") {
+ stop_command = read_setting_value(i, end, &stop_command_offsets);
+ }
else if (setting == "depends-on") {
string dependency_name = read_setting_value(i, end);
depends_on.push_back(loadServiceRecord(dependency_name.c_str()));
delete rval;
rval = new ServiceRecord(this, string(name), service_type, std::move(command), command_offsets,
& depends_on, & depends_soft);
+ rval->setStopCommand(stop_command, stop_command_offsets);
rval->setLogfile(logfile);
rval->setAutoRestart(auto_restart);
rval->setOnstartFlags(onstart_flags);
}
else if (service_type == ServiceType::SCRIPTED) {
// Script-controlled service
- bool start_success = start_ps_process(std::vector<std::string>(1, "start"));
+ bool start_success = start_ps_process();
if (! start_success) {
failed_to_start();
}
bool ServiceRecord::start_ps_process() noexcept
{
try {
- return start_ps_process(std::vector<std::string>());
+ return start_ps_process(exec_arg_parts);
}
catch (std::bad_alloc & bad_alloc_exc) {
// TODO log error
}
}
-bool ServiceRecord::start_ps_process(const std::vector<std::string> &pargs) noexcept
+bool ServiceRecord::start_ps_process(const std::vector<const char *> &cmd) noexcept
{
// In general, you can't tell whether fork/exec is successful. We use a pipe to communicate
// success/failure from the child to the parent. The pipe is set CLOEXEC so a successful
}
// Set up the argument array and other data now (before fork), in case memory allocation fails.
+
+ auto args = cmd.data();
+
+ const char * logfile = this->logfile.c_str();
+ if (*logfile == 0) {
+ logfile = "/dev/null";
+ }
- try {
- //auto argsv = std::vector<const char *>(num_args + pargs.size() + 1);
- auto argsv = std::vector<const char *>(num_args + pargs.size() + 1);
- auto args = argsv.data();
- int i;
- for (i = 0; i < num_args; i++) {
- args[i] = exec_arg_parts[i];
- }
- for (auto progarg : pargs) {
- args[i] = progarg.c_str();
- i++;
- }
- args[i] = nullptr;
-
- string logfile = this->logfile;
- if (logfile.length() == 0) {
- logfile = "/dev/null";
- }
+ // TODO make sure pipefd's are not 0/1/2 (STDIN/OUT/ERR) - if they are, dup them
+ // until they are not.
- // TODO make sure pipefd's are not 0/1/2 (STDIN/OUT/ERR) - if they are, dup them
- // until they are not.
+ pid_t forkpid = fork();
+ if (forkpid == -1) {
+ // TODO log error
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return false;
+ }
- pid_t forkpid = fork();
- if (forkpid == -1) {
- // TODO log error
- close(pipefd[0]);
- close(pipefd[1]);
- return false;
+ if (forkpid == 0) {
+ // Child process. Must not allocate memory (or otherwise risk throwing any exception)
+ // from here until exit().
+ ev_default_destroy(); // won't need that on this side, free up fds.
+
+ // Re-set stdin, stdout, stderr
+ close(0); close(1); close(2);
+
+ // TODO rethink this logic. If we open it at not-0, shouldn't we just dup it to 0?:
+ if (open("/dev/null", O_RDONLY) == 0) {
+ // stdin = 0. That's what we should have; proceed with opening
+ // stdout and stderr.
+ open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
+ dup2(1, 2);
}
- if (forkpid == 0) {
- // Child process. Must not allocate memory (or otherwise risk throwing any exception)
- // from here until exit().
- ev_default_destroy(); // won't need that on this side, free up fds.
+ execvp(exec_arg_parts[0], const_cast<char **>(args));
- // Re-set stdin, stdout, stderr
- close(0); close(1); close(2);
+ // If we got here, the exec failed:
+ int exec_status = errno;
+ write(pipefd[1], &exec_status, sizeof(int));
+ exit(0);
+ }
+ else {
+ // Parent process
+ close(pipefd[1]); // close the 'other end' fd
- // TODO rethink this logic. If we open it at not-0, shouldn't we just dup it to 0?:
- if (open("/dev/null", O_RDONLY) == 0) {
- // stdin = 0. That's what we should have; proceed with opening
- // stdout and stderr.
- open(logfile.c_str(), O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
- dup2(1, 2);
- }
+ int exec_status;
+ if (read(pipefd[0], &exec_status, sizeof(int)) == 0) {
+ // pipe closed; success
+ pid = forkpid;
- execvp(exec_arg_parts[0], const_cast<char **>(args));
+ // Add a process listener so we can detect when the
+ // service stops
+ ev_child_init(&child_listener, process_child_callback, pid, 0);
+ child_listener.data = this;
+ ev_child_start(ev_default_loop(EVFLAG_AUTO), &child_listener);
- // If we got here, the exec failed:
- int exec_status = errno;
- write(pipefd[1], &exec_status, sizeof(int));
- exit(0);
+ close(pipefd[0]);
+ return true;
}
else {
- // Parent process
- close(pipefd[1]); // close the 'other end' fd
-
- int exec_status;
- if (read(pipefd[0], &exec_status, sizeof(int)) == 0) {
- // pipe closed; success
- pid = forkpid;
-
- // Add a process listener so we can detect when the
- // service stops
- ev_child_init(&child_listener, process_child_callback, pid, 0);
- child_listener.data = this;
- ev_child_start(ev_default_loop(EVFLAG_AUTO), &child_listener);
-
- close(pipefd[0]);
- return true;
- }
- else {
- // TODO log error
- close(pipefd[0]);
- return false;
- }
+ // TODO log error
+ close(pipefd[0]);
+ return false;
}
}
- catch (std::bad_alloc &bad_alloc_exc) {
- log(LogLevel::ERROR, "Out of memory");
- return false;
- }
}
// Mark this and all dependent services as force-stopped.
}
else if (service_type == ServiceType::SCRIPTED) {
// Scripted service.
- if (! start_ps_process(std::vector<string>(1, "stop"))) {
+ if (! start_ps_process(stop_arg_parts)) {
// stop script failed, but there's not much we can do:
stopped();
}
};
// Given a string and a list of pairs of (start,end) indices for each argument in that string,
-// store a null terminator for the argument. Return a `char *` array pointing at the beginning
-// of each argument. (The returned array is invalidated if the string is later modified).
-static const char ** separate_args(std::string &s, std::list<std::pair<unsigned,unsigned>> &arg_indices)
+// store a null terminator for the argument. Return a `char *` vector containing the beginning
+// of each argument and a trailing nullptr. (The returned array is invalidated if the string is later modified).
+static std::vector<const char *> separate_args(std::string &s, std::list<std::pair<unsigned,unsigned>> &arg_indices)
{
- const char ** r = new const char *[arg_indices.size()];
- int i = 0;
+ std::vector<const char *> r;
+ r.reserve(arg_indices.size() + 1);
// First store nul terminator for each part:
for (auto index_pair : arg_indices) {
// Now we can get the C string (c_str) and store offsets into it:
const char * cstr = s.c_str();
for (auto index_pair : arg_indices) {
- r[i] = cstr + index_pair.first;
- i++;
+ r.push_back(cstr + index_pair.first);
}
+ r.push_back(nullptr);
return r;
}
ServiceState desired_state = ServiceState::STOPPED; /* ServiceState::STOPPED / STARTED */
string program_name; /* storage for program/script and arguments */
- const char **exec_arg_parts; /* pointer to each argument/part of the program_name */
- int num_args; /* number of argumrnets (including program) */
+ std::vector<const char *> exec_arg_parts; /* pointer to each argument/part of the program_name */
+
+ string stop_command; /* storage for stop program/script and arguments */
+ std::vector<const char *> stop_arg_parts; /* pointer to each argument/part of the stop_command */
+
OnstartFlags onstart_flags;
string logfile; /* log file name, empty string specifies /dev/null */
// For process services, start the process, return true on success
bool start_ps_process() noexcept;
- bool start_ps_process(const std::vector<std::string> &args) noexcept;
+ bool start_ps_process(const std::vector<const char *> &args) noexcept;
// Callback from libev when a child process dies
static void process_child_callback(struct ev_loop *loop, struct ev_child *w,
program_name = command;
exec_arg_parts = separate_args(program_name, command_offsets);
- num_args = command_offsets.size();
for (sr_iter i = depends_on.begin(); i != depends_on.end(); ++i) {
(*i)->dependents.push_back(this);
// TODO write a destructor
+ // Set the stop command and arguments (may throw std::bad_alloc)
+ void setStopCommand(std::string command, std::list<std::pair<unsigned,unsigned>> &stop_command_offsets)
+ {
+ stop_command = command;
+ stop_arg_parts = separate_args(stop_command, stop_command_offsets);
+ }
+
// Get the current service state.
ServiceState getState() noexcept
{