/* * class Executable * Base class for Server and Proxy * Handles common stuff for setting up and running a process */ #include <pwd.h> #include <sys/types.h> #include <errno.h> #include <libgen.h> #include <unistd.h> #include <sys/wait.h> #include <signal.h> #include <assert.h> #include <cstdlib> #include <cstring> #include <iostream> #include <fstream> #include <vector> #include "Executable.h" #include "configuration.h" #include "stringtools.h" Executable::cmd_map Executable::CommandMap; Executable::Executable(const std::string& name, const std::string& type, Configuration * config) : _name(name), _type(type), _configuration(config), _variables(config->beginEnvironment(), config->endEnvironment()), _verbose(config->verbose()) { } bool Executable::initializeCommandMap() { CommandMap["start"] = &Executable::startServer; CommandMap["stop"] = &Executable::stopServer; CommandMap["restart"] = &Executable::restartServer; CommandMap["status"] = &Executable::statusServer; CommandMap["checkpoint"] = &Executable::checkpointServer; CommandMap["journal"] = &Executable::rotateJournalServer; return true; } Executable::~Executable() { } void Executable::addVariable(const std::string& name, const std::string& value) { _variables[name] = value; } void Executable::addUserEnvironment(const std::string& name, const std::string& value) { env_map::iterator iter = _variables.find(name); if (iter == _variables.end()) { _variables[name] = value; } } int Executable::executeCommand(const std::string& command) { cmd_func cmd = parseCommand(command); pid_t pid = fork(); switch (pid) { case -1 : perror("Fork failed, terminating program"); return 1; case 0 : // child (this->*cmd)(); exit(0); default : // reap the result int result; pid_t childPid = wait(&result); if (childPid < 0) { perror("Wait failed"); } return result; } return 0; } Executable::cmd_func Executable::parseCommand(const std::string& command) { static bool initialized = initializeCommandMap(); assert(initialized); cmd_map::iterator iter = CommandMap.find(command); if (iter != CommandMap.end()) { return iter->second; } else { std::string err = "Unknown command: " + command; throw VerificationException(err); } } void Executable::prepareEnv(arg_vec& vec) { env_map::iterator iter; for (iter = _variables.begin(); iter != _variables.end(); ++iter) { std::string env = iter->first + "=" + iter->second; vec.push_back(env); } } // possible error conditions: // - user does not exist // - current user is not root and not identical to the user // - other errors void Executable::changeUser() { struct passwd * userInfo = getpwnam(_user.c_str()); if (!userInfo) { std::string err = "Unknown user " + _user; throw VerificationException(err); } const uid_t userId = userInfo->pw_uid; uid_t currentId = geteuid(); if (0 == userId) { std::cerr << "User cannot be root. Daemons should not run under root" << std::endl; _exit(1); } if (userId != currentId) { if (currentId != 0) { std::cerr << "No permission to set user id to "; std::cerr << userId << " [" << _user << "]. " << std::endl; std::cerr << "This process must run as root"; std::cerr << " or as the user \"" << _user << "\""; std::cerr << std::endl; _exit(EPERM); } if (setuid(userId)) { switch (errno) { case EINVAL: std::cerr << "Illegal user id. Should not happen" << std::endl; break; case EPERM: std::cerr << "No permission to set user id to "; std::cerr << userId << " [" << _user << "]. " << std::endl; std::cerr << "This process must run as root"; std::cerr << " or as the user \"" << _user << "\""; std::cerr << std::endl; break; default: perror("Unknown error : "); break; } _exit(errno); } } if (chdir(userInfo->pw_dir)) { std::string err("Could not change directory to "); err += userInfo->pw_dir; perror(err.c_str()); } // adjust the environment if not overwritten by the config file: addUserEnvironment("USER", _user); addUserEnvironment("P4USER", _user); addUserEnvironment("LOGNAME", _user); addUserEnvironment("HOME", userInfo->pw_dir); addUserEnvironment("SHELL", userInfo->pw_shell); } void Executable::addOption(const char * name, const char * option, arg_vec& args) { env_map::iterator envp; envp = _variables.find(name); if (envp != _variables.end()) { if (option) args.push_back(option); std::vector<std::string> options; tokenize(envp->second, options); for (std::vector<std::string>::const_iterator iter = options.begin(); iter != options.end(); ++iter) { args.push_back(*iter); } } } void Executable::storePid(pid_t pid) { std::string pidname = createRunName(); setEuidRoot(); std::ofstream pidfile(pidname.c_str()); if (!pidfile) { // we cannot create the pid file because we do not have permission. // minor problem, log, but ignore std::cerr << "Cannot create file " << pidname << std::endl; } else { pidfile << pid << std::endl; } setSafeEuid(); } void Executable::removePid() { setEuidRoot(); std::string pidname = createRunName(); if (unlink(pidname.c_str())) { std::string err("Could not remove "); err += pidname; perror(err.c_str()); } setSafeEuid(); } std::string Executable::createRunName() const { return std::string("/var/run/") + basename(const_cast<char *>(_executable.c_str())) + "." + _port; } char ** Executable::makeCharArray(Executable::arg_vec& vec) { char ** argsArray = new char * [vec.size() + 1]; char ** argsPos = argsArray; for (arg_vec::iterator iter = vec.begin(); iter != vec.end(); ++iter) { *argsPos++ = const_cast<char *> (iter->c_str()); } *argsPos = (char *) 0; return argsArray; } void Executable::startServer() { // turn process into a daemon if (!isServerRunning()) { setsid(); _pid = fork(); if (-1 == _pid) { std::cerr << "Fork failed, terminating program" << std::endl; _exit(1); } else if (0 == _pid) { changeUser(); // child process arg_vec envs; prepareEnv(envs); arg_vec args; prepareArgs(args); char ** argsArray = makeCharArray(args); char ** envsArray = makeCharArray(envs); if (!_verbose) { freopen( "/dev/null", "r", stdin ); freopen( "/dev/null", "w", stdout ); freopen( "/dev/null", "w", stderr ); } else { std::cout << "Starting " << _executable; for (arg_vec::const_iterator iter = args.begin(); iter != args.end(); ++iter) { std::cout << " " << *iter; } std::cout << std::endl; std::cout << "Environment : " << std::endl; for (arg_vec::const_iterator iter = envs.begin(); iter != envs.end(); ++iter) { std::cout << *iter << std::endl; } } if (execve(_executable.c_str(), argsArray, envsArray)) { std::cerr << "Calling " << _executable << " failed with " << strerror(errno) << " ["<< errno << "]" << std::endl; exit(errno); } } else { storePid(_pid); _exit(0); } } } void Executable::stopServer() { // Strategy : find pidname in /var/run and kill process. Fails if // - file not found // - wrong user std::ifstream fin(createRunName().c_str()); if (fin) { fin >> _pid; kill(_pid, SIGQUIT); } // attempt to remove in any case removePid(); } void Executable::restartServer() { stopServer(); startServer(); } void Executable::checkpointServer() { std::cerr << "Checkpoint not available for this server type" << std::endl; } void Executable::rotateJournalServer() { std::cerr << "Rotating the journal not available for this server type" << std::endl; } bool Executable::isServerRunning() { // find pid file // if exists, try to ping the process // if that works, all is well, otherwise server is down std::string pidname = createRunName(); std::ifstream pidfile(pidname.c_str()); if (pidfile) { pidfile >> _pid; if (kill(_pid, 0)) { switch (errno) { case EPERM: std::cerr << "Could not find out the status, not the right permission" << std::endl; break; case ESRCH: // process does not exist, status = down break; default: perror("Unknown error, kill"); } } else { return true; } } return false; } void Executable::statusServer() { std::cout << "Process " << _executable << " [name = " << _name << "] on port " << _port; if (isServerRunning()) { std::cout << " is running [pid = " << _pid << "]" << std::endl; } else { std::cout << " is down" << std::endl; } } void Executable::print(std::ostream& str) const { str << _type << " " << _name; } std::ostream& operator<<(std::ostream& str, const Executable& exec) { exec.print(str); return str; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#4 | 8039 | Sven Erik Knop |
P4FTPD is allowed to run as root. This also fixes an overloaded method that could cause confusion in the code. |
||
#3 | 8038 | Sven Erik Knop | Updated imports and link libraries for Linux to deal with new compiler. | ||
#2 | 5987 | Sven Erik Knop |
Few minor changes: All commands now accept variable called "Options". This can be used to collect all options for which Perforce has not defined an environment variable - for example the undocumented "-C1" for p4d to start a case insensitive server. p4d also accepts the variables P4USER and P4PASSWD. These are only used for stopping the service, which is done first via "p4 admin stop". Note that if the stop via the admin command fails, p4dcfg will stop the service via a kill |
||
#1 | 5882 | Sven Erik Knop | initial publish |