#include <memory>
#include <sys/types.h>
+#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include "control-cmds.h"
#include "service-constants.h"
#include "cpbuffer.h"
+#include "dinit-client.h"
// dinitctl: utility to control the Dinit daemon, including starting and stopping of services.
// This utility communicates with the dinit daemon via a unix stream socket (/dev/initctl, or $HOME/.dinitctl).
-using handle_t = uint32_t;
+static constexpr uint16_t min_cp_version = 1;
+static constexpr uint16_t max_cp_version = 1;
+enum class command_t;
-class ReadCPException
-{
- public:
- int errcode;
- ReadCPException(int err) : errcode(err) { }
-};
-
-enum class Command;
-
-static int issueLoadService(int socknum, const char *service_name);
-static int checkLoadReply(int socknum, cpbuffer<1024> &rbuffer, handle_t *handle_p, service_state_t *state_p);
-static int startStopService(int socknum, const char *service_name, Command command, bool do_pin, bool wait_for_service, bool verbose);
-static int unpinService(int socknum, const char *service_name, bool verbose);
-static int listServices(int socknum);
-
+static int issue_load_service(int socknum, const char *service_name, bool find_only = false);
+static int check_load_reply(int socknum, cpbuffer_t &, handle_t *handle_p, service_state_t *state_p);
+static int start_stop_service(int socknum, cpbuffer_t &, const char *service_name, command_t command,
+ bool do_pin, bool wait_for_service, bool verbose);
+static int unpin_service(int socknum, cpbuffer_t &, const char *service_name, bool verbose);
+static int unload_service(int socknum, cpbuffer_t &, const char *service_name);
+static int list_services(int socknum, cpbuffer_t &);
+static int shutdown_dinit(int soclknum, cpbuffer_t &);
+static int add_remove_dependency(int socknum, cpbuffer_t &rbuffer, bool add, char *service_from,
+ char *service_to, dependency_type dep_type);
-// Fill a circular buffer from a file descriptor, reading at least _rlength_ bytes.
-// Throws ReadException if the requested number of bytes cannot be read, with:
-// errcode = 0 if end of stream (remote end closed)
-// errcode = errno if another error occurred
-// Note that EINTR is ignored (i.e. the read will be re-tried).
-static void fillBufferTo(cpbuffer<1024> *buf, int fd, int rlength)
-{
- do {
- int r = buf->fill_to(fd, rlength);
- if (r == -1) {
- if (errno != EINTR) {
- throw ReadCPException(errno);
- }
- }
- else if (r == 0) {
- throw ReadCPException(0);
- }
- else {
- return;
- }
- }
- while (true);
-}
static const char * describeState(bool stopped)
{
return stop ? "stop" : "start";
}
-// Wait for a reply packet, skipping over any information packets
-// that are received in the meantime.
-static void wait_for_reply(cpbuffer<1024> &rbuffer, int fd)
-{
- fillBufferTo(&rbuffer, fd, 1);
-
- while (rbuffer[0] >= 100) {
- // Information packet; discard.
- fillBufferTo(&rbuffer, fd, 1);
- int pktlen = (unsigned char) rbuffer[1];
-
- rbuffer.consume(1); // Consume one byte so we'll read one byte of the next packet
- fillBufferTo(&rbuffer, fd, pktlen);
- rbuffer.consume(pktlen - 1);
- }
-}
-
-
-// Write *all* the requested buffer and re-try if necessary until
-// the buffer is written or an unrecoverable error occurs.
-static int write_all(int fd, const void *buf, size_t count)
-{
- const char *cbuf = static_cast<const char *>(buf);
- int w = 0;
- while (count > 0) {
- int r = write(fd, cbuf, count);
- if (r == -1) {
- if (errno == EINTR) continue;
- return r;
- }
- w += r;
- cbuf += r;
- count -= r;
- }
- return w;
-}
-
-
-enum class Command {
+enum class command_t {
NONE,
START_SERVICE,
WAKE_SERVICE,
STOP_SERVICE,
RELEASE_SERVICE,
UNPIN_SERVICE,
- LIST_SERVICES
+ UNLOAD_SERVICE,
+ LIST_SERVICES,
+ SHUTDOWN,
+ ADD_DEPENDENCY,
+ RM_DEPENDENCY
};
+
// Entry point.
int main(int argc, char **argv)
{
bool show_help = argc < 2;
char *service_name = nullptr;
+ char *to_service_name = nullptr;
+ dependency_type dep_type;
+ bool dep_type_set = false;
std::string control_socket_str;
const char * control_socket_path = nullptr;
bool wait_for_service = true;
bool do_pin = false;
- Command command = Command::NONE;
+ command_t command = command_t::NONE;
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
do_pin = true;
}
else {
+ cerr << "dinitctl: unrecognized option: " << argv[i] << " (use --help for help)\n";
return 1;
}
}
- else if (command == Command::NONE) {
+ else if (command == command_t::NONE) {
if (strcmp(argv[i], "start") == 0) {
- command = Command::START_SERVICE;
+ command = command_t::START_SERVICE;
}
else if (strcmp(argv[i], "wake") == 0) {
- command = Command::WAKE_SERVICE;
+ command = command_t::WAKE_SERVICE;
}
else if (strcmp(argv[i], "stop") == 0) {
- command = Command::STOP_SERVICE;
+ command = command_t::STOP_SERVICE;
}
else if (strcmp(argv[i], "release") == 0) {
- command = Command::RELEASE_SERVICE;
+ command = command_t::RELEASE_SERVICE;
}
else if (strcmp(argv[i], "unpin") == 0) {
- command = Command::UNPIN_SERVICE;
+ command = command_t::UNPIN_SERVICE;
+ }
+ else if (strcmp(argv[i], "unload") == 0) {
+ command = command_t::UNLOAD_SERVICE;
}
else if (strcmp(argv[i], "list") == 0) {
- command = Command::LIST_SERVICES;
+ command = command_t::LIST_SERVICES;
+ }
+ else if (strcmp(argv[i], "shutdown") == 0) {
+ command = command_t::SHUTDOWN;
+ }
+ else if (strcmp(argv[i], "add-dep") == 0) {
+ command = command_t::ADD_DEPENDENCY;
+ }
+ else if (strcmp(argv[i], "rm-dep") == 0) {
+ command = command_t::RM_DEPENDENCY;
}
else {
- show_help = true;
- break;
+ cerr << "dinitctl: unrecognized command: " << argv[i] << " (use --help for help)\n";
+ return 1;
}
}
else {
- // service name
- if (service_name != nullptr) {
- show_help = true;
- break;
+ // service name / other non-option
+ if (command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY) {
+ if (! dep_type_set) {
+ if (strcmp(argv[i], "regular") == 0) {
+ dep_type = dependency_type::REGULAR;
+ }
+ else if (strcmp(argv[i], "milestone") == 0) {
+ dep_type = dependency_type::MILESTONE;
+ }
+ else if (strcmp(argv[i], "waits-for") == 0) {
+ dep_type = dependency_type::WAITS_FOR;
+ }
+ else {
+ show_help = true;
+ break;
+ }
+ dep_type_set = true;
+ }
+ else if (service_name == nullptr) {
+ service_name = argv[i];
+ }
+ else if (to_service_name == nullptr) {
+ to_service_name = argv[i];
+ }
+ else {
+ show_help = true;
+ break;
+ }
+ }
+ else {
+ if (service_name != nullptr) {
+ show_help = true;
+ break;
+ }
+ service_name = argv[i];
+ // TODO support multiple services
}
- service_name = argv[i];
- // TODO support multiple services
}
}
- if (service_name != nullptr && command == Command::LIST_SERVICES) {
+ bool no_service_cmd = (command == command_t::LIST_SERVICES || command == command_t::SHUTDOWN);
+
+ if (service_name != nullptr && no_service_cmd) {
show_help = true;
}
- if ((service_name == nullptr && command != Command::LIST_SERVICES) || command == Command::NONE) {
+ if ((service_name == nullptr && ! no_service_cmd) || command == command_t::NONE) {
+ show_help = true;
+ }
+
+ if ((command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY)
+ && (! dep_type_set || service_name == nullptr || to_service_name == nullptr)) {
show_help = true;
}
if (show_help) {
- cout << "dinitctl: control Dinit services" << endl;
-
- cout << "\nUsage:" << endl;
- cout << " dinitctl [options] start [options] <service-name> : start and activate service" << endl;
- cout << " dinitctl [options] stop [options] <service-name> : stop service and cancel explicit activation" << endl;
- cout << " dinitctl [options] wake [options] <service-name> : start but do not mark activated" << endl;
- cout << " dinitctl [options] release [options] <service-name> : release activation, stop if no dependents" << endl;
- cout << " dinitctl [options] unpin <service-name> : un-pin the service (after a previous pin)" << endl;
- cout << " dinitctl list : list loaded services" << endl;
-
- cout << "\nNote: An activated service continues running when its dependents stop." << endl;
-
- cout << "\nGeneral options:" << endl;
- cout << " -s, --system : control system daemon instead of user daemon" << endl;
- cout << " --quiet : suppress output (except errors)" << endl;
-
- cout << "\nCommand options:" << endl;
- cout << " --help : show this help" << endl;
- cout << " --no-wait : don't wait for service startup/shutdown to complete" << endl;
- cout << " --pin : pin the service in the requested (started/stopped) state" << endl;
+ cout << "dinitctl: control Dinit services\n"
+ "\n"
+ "Usage:\n"
+ " dinitctl [options] start [options] <service-name> : start and activate service\n"
+ " dinitctl [options] stop [options] <service-name> : stop service and cancel explicit activation\n"
+ " dinitctl [options] wake [options] <service-name> : start but do not mark activated\n"
+ " dinitctl [options] release [options] <service-name> : release activation, stop if no dependents\n"
+ " dinitctl [options] unpin <service-name> : un-pin the service (after a previous pin)\n"
+ " dinitctl unload <service-name> : unload the service\n"
+ " dinitctl list : list loaded services\n"
+ " dinitctl shutdown : stop all services and terminate dinit\n"
+ " dinitctl add-dep <type> <from-service> <to-service> : add a dependency between services\n"
+ " dinitctl rm-dep <type> <from-service> <to-service> : remove a dependency between services\n"
+ "\n"
+ "Note: An activated service continues running when its dependents stop.\n"
+ "\n"
+ "General options:\n"
+ " -s, --system : control system daemon instead of user daemon\n"
+ " --quiet : suppress output (except errors)\n"
+ "\n"
+ "Command options:\n"
+ " --help : show this help\n"
+ " --no-wait : don't wait for service startup/shutdown to complete\n"
+ " --pin : pin the service in the requested (started/stopped) state\n";
return 1;
}
return 1;
}
- // TODO should start by querying protocol version
-
- if (command == Command::UNPIN_SERVICE) {
- return unpinService(socknum, service_name, verbose);
+ try {
+ // Start by querying protocol version:
+ cpbuffer_t rbuffer;
+ check_protocol_version(min_cp_version, max_cp_version, rbuffer, socknum);
+
+ if (command == command_t::UNPIN_SERVICE) {
+ return unpin_service(socknum, rbuffer, service_name, verbose);
+ }
+ else if (command == command_t::UNLOAD_SERVICE) {
+ return unload_service(socknum, rbuffer, service_name);
+ }
+ else if (command == command_t::LIST_SERVICES) {
+ return list_services(socknum, rbuffer);
+ }
+ else if (command == command_t::SHUTDOWN) {
+ return shutdown_dinit(socknum, rbuffer);
+ }
+ else if (command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY) {
+ return add_remove_dependency(socknum, rbuffer, command == command_t::ADD_DEPENDENCY,
+ service_name, to_service_name, dep_type);
+ }
+ else {
+ return start_stop_service(socknum, rbuffer, service_name, command, do_pin,
+ wait_for_service, verbose);
+ }
}
- else if (command == Command::LIST_SERVICES) {
- return listServices(socknum);
+ catch (cp_old_client_exception &e) {
+ std::cerr << "dinitctl: too old (server reports newer protocol version)" << std::endl;
+ return 1;
+ }
+ catch (cp_old_server_exception &e) {
+ std::cerr << "dinitctl: server too old or protocol error" << std::endl;
+ return 1;
+ }
+ catch (cp_read_exception &e) {
+ cerr << "dinitctl: control socket read failure or protocol error" << endl;
+ return 1;
+ }
+ catch (cp_write_exception &e) {
+ cerr << "dinitctl: control socket write error: " << std::strerror(e.errcode) << endl;
+ return 1;
}
-
- return startStopService(socknum, service_name, command, do_pin, wait_for_service, verbose);
}
// Start/stop a service
-static int startStopService(int socknum, const char *service_name, Command command, bool do_pin, bool wait_for_service, bool verbose)
+static int start_stop_service(int socknum, cpbuffer_t &rbuffer, const char *service_name,
+ command_t command, bool do_pin, bool wait_for_service, bool verbose)
{
using namespace std;
- bool do_stop = (command == Command::STOP_SERVICE || command == Command::RELEASE_SERVICE);
+ bool do_stop = (command == command_t::STOP_SERVICE || command == command_t::RELEASE_SERVICE);
- if (issueLoadService(socknum, service_name)) {
+ if (issue_load_service(socknum, service_name)) {
return 1;
}
// Now we expect a reply:
- try {
- cpbuffer<1024> rbuffer;
- wait_for_reply(rbuffer, socknum);
-
- service_state_t state;
- //service_state_t target_state;
- handle_t handle;
-
- if (checkLoadReply(socknum, rbuffer, &handle, &state) != 0) {
- return 0;
- }
-
- service_state_t wanted_state = do_stop ? service_state_t::STOPPED : service_state_t::STARTED;
- int pcommand = 0;
- switch (command) {
- case Command::STOP_SERVICE:
+ wait_for_reply(rbuffer, socknum);
+
+ service_state_t state;
+ //service_state_t target_state;
+ handle_t handle;
+
+ if (check_load_reply(socknum, rbuffer, &handle, &state) != 0) {
+ return 1;
+ }
+
+ service_state_t wanted_state = do_stop ? service_state_t::STOPPED : service_state_t::STARTED;
+ int pcommand = 0;
+ switch (command) {
+ case command_t::STOP_SERVICE:
pcommand = DINIT_CP_STOPSERVICE;
break;
- case Command::RELEASE_SERVICE:
+ case command_t::RELEASE_SERVICE:
pcommand = DINIT_CP_RELEASESERVICE;
break;
- case Command::START_SERVICE:
+ case command_t::START_SERVICE:
pcommand = DINIT_CP_STARTSERVICE;
break;
- case Command::WAKE_SERVICE:
+ case command_t::WAKE_SERVICE:
pcommand = DINIT_CP_WAKESERVICE;
break;
default: ;
- }
-
- // Need to issue STOPSERVICE/STARTSERVICE
- // We'll do this regardless of the current service state / target state, since issuing
- // start/stop also sets or clears the "explicitly started" flag on the service.
- {
- int r;
-
- {
- auto buf = new char[2 + sizeof(handle)];
- unique_ptr<char[]> ubuf(buf);
-
- buf[0] = pcommand;
- buf[1] = do_pin ? 1 : 0;
- memcpy(buf + 2, &handle, sizeof(handle));
- r = write_all(socknum, buf, 2 + sizeof(handle));
- }
-
- if (r == -1) {
- perror("dinitctl: write");
- return 1;
- }
-
- wait_for_reply(rbuffer, socknum);
- if (rbuffer[0] == DINIT_RP_ALREADYSS) {
- bool already = (state == wanted_state);
- if (verbose) {
- cout << "Service " << (already ? "(already) " : "") << describeState(do_stop) << "." << endl;
- }
- return 0; // success!
- }
- if (rbuffer[0] != DINIT_RP_ACK) {
- cerr << "dinitctl: Protocol error." << endl;
- return 1;
- }
- rbuffer.consume(1);
- }
+ }
+
+ // Need to issue STOPSERVICE/STARTSERVICE
+ // We'll do this regardless of the current service state / target state, since issuing
+ // start/stop also sets or clears the "explicitly started" flag on the service.
+ {
+ char buf[2 + sizeof(handle)];
+ buf[0] = pcommand;
+ buf[1] = do_pin ? 1 : 0;
+ memcpy(buf + 2, &handle, sizeof(handle));
+ write_all_x(socknum, buf, 2 + sizeof(handle));
- if (! wait_for_service) {
+ wait_for_reply(rbuffer, socknum);
+ if (rbuffer[0] == DINIT_RP_ALREADYSS) {
+ bool already = (state == wanted_state);
if (verbose) {
- cout << "Issued " << describeVerb(do_stop) << " command successfully." << endl;
+ cout << "Service " << (already ? "(already) " : "") << describeState(do_stop) << "." << endl;
}
- return 0;
+ return 0; // success!
}
-
- service_event_t completionEvent;
- service_event_t cancelledEvent;
-
- if (do_stop) {
- completionEvent = service_event_t::STOPPED;
- cancelledEvent = service_event_t::STOPCANCELLED;
+ if (rbuffer[0] != DINIT_RP_ACK) {
+ cerr << "dinitctl: Protocol error." << endl;
+ return 1;
}
- else {
- completionEvent = service_event_t::STARTED;
- cancelledEvent = service_event_t::STARTCANCELLED;
+ rbuffer.consume(1);
+ }
+
+ if (! wait_for_service) {
+ if (verbose) {
+ cout << "Issued " << describeVerb(do_stop) << " command successfully." << endl;
}
-
- // Wait until service started:
- int r = rbuffer.fill_to(socknum, 2);
- while (r > 0) {
- if (rbuffer[0] >= 100) {
- int pktlen = (unsigned char) rbuffer[1];
- fillBufferTo(&rbuffer, socknum, pktlen);
-
- if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
- handle_t ev_handle;
- rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
- service_event_t event = static_cast<service_event_t>(rbuffer[2 + sizeof(ev_handle)]);
- if (ev_handle == handle) {
- if (event == completionEvent) {
- if (verbose) {
- cout << "Service " << describeState(do_stop) << "." << endl;
- }
- return 0;
+ return 0;
+ }
+
+ service_event_t completionEvent;
+ service_event_t cancelledEvent;
+
+ if (do_stop) {
+ completionEvent = service_event_t::STOPPED;
+ cancelledEvent = service_event_t::STOPCANCELLED;
+ }
+ else {
+ completionEvent = service_event_t::STARTED;
+ cancelledEvent = service_event_t::STARTCANCELLED;
+ }
+
+ // Wait until service started:
+ int r = rbuffer.fill_to(socknum, 2);
+ while (r > 0) {
+ if (rbuffer[0] >= 100) {
+ int pktlen = (unsigned char) rbuffer[1];
+ fill_buffer_to(rbuffer, socknum, pktlen);
+
+ if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
+ handle_t ev_handle;
+ rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
+ service_event_t event = static_cast<service_event_t>(rbuffer[2 + sizeof(ev_handle)]);
+ if (ev_handle == handle) {
+ if (event == completionEvent) {
+ if (verbose) {
+ cout << "Service " << describeState(do_stop) << "." << endl;
}
- else if (event == cancelledEvent) {
- if (verbose) {
- cout << "Service " << describeVerb(do_stop) << " cancelled." << endl;
- }
- return 1;
+ return 0;
+ }
+ else if (event == cancelledEvent) {
+ if (verbose) {
+ cout << "Service " << describeVerb(do_stop) << " cancelled." << endl;
}
- else if (! do_stop && event == service_event_t::FAILEDSTART) {
- if (verbose) {
- cout << "Service failed to start." << endl;
- }
- return 1;
+ return 1;
+ }
+ else if (! do_stop && event == service_event_t::FAILEDSTART) {
+ if (verbose) {
+ cout << "Service failed to start." << endl;
}
+ return 1;
}
}
-
- rbuffer.consume(pktlen);
- r = rbuffer.fill_to(socknum, 2);
}
- else {
- // Not an information packet?
- cerr << "dinitctl: protocol error" << endl;
- return 1;
- }
- }
-
- if (r == -1) {
- perror("dinitctl: read");
+
+ rbuffer.consume(pktlen);
+ r = rbuffer.fill_to(socknum, 2);
}
else {
- cerr << "protocol error (connection closed by server)" << endl;
+ // Not an information packet?
+ cerr << "dinitctl: protocol error" << endl;
+ return 1;
}
- return 1;
}
- catch (ReadCPException &exc) {
- cerr << "dinitctl: control socket read failure or protocol error" << endl;
- return 1;
+
+ if (r == -1) {
+ perror("dinitctl: read");
}
- catch (std::bad_alloc &exc) {
- cerr << "dinitctl: out of memory" << endl;
- return 1;
+ else {
+ cerr << "protocol error (connection closed by server)" << endl;
}
-
- return 0;
+ return 1;
}
// Issue a "load service" command (DINIT_CP_LOADSERVICE), without waiting for
// a response. Returns 1 on failure (with error logged), 0 on success.
-static int issueLoadService(int socknum, const char *service_name)
+static int issue_load_service(int socknum, const char *service_name, bool find_only)
{
// Build buffer;
uint16_t sname_len = strlen(service_name);
int bufsize = 3 + sname_len;
- int r;
- try {
- std::unique_ptr<char[]> ubuf(new char[bufsize]);
- auto buf = ubuf.get();
-
- buf[0] = DINIT_CP_LOADSERVICE;
- memcpy(buf + 1, &sname_len, 2);
- memcpy(buf + 3, service_name, sname_len);
-
- r = write_all(socknum, buf, bufsize);
- }
- catch (std::bad_alloc &badalloc) {
- std::cerr << "dinitctl: " << badalloc.what() << std::endl;
- return 1;
- }
-
- if (r == -1) {
- perror("dinitctl: write");
- return 1;
- }
+ std::unique_ptr<char[]> ubuf(new char[bufsize]);
+ auto buf = ubuf.get();
+
+ buf[0] = find_only ? DINIT_CP_FINDSERVICE : DINIT_CP_LOADSERVICE;
+ memcpy(buf + 1, &sname_len, 2);
+ memcpy(buf + 3, service_name, sname_len);
+
+ write_all_x(socknum, buf, bufsize);
return 0;
}
// Check that a "load service" reply was received, and that the requested service was found.
-static int checkLoadReply(int socknum, cpbuffer<1024> &rbuffer, handle_t *handle_p, service_state_t *state_p)
+static int check_load_reply(int socknum, cpbuffer_t &rbuffer, handle_t *handle_p, service_state_t *state_p)
{
using namespace std;
if (rbuffer[0] == DINIT_RP_SERVICERECORD) {
- fillBufferTo(&rbuffer, socknum, 2 + sizeof(*handle_p));
+ fill_buffer_to(rbuffer, socknum, 2 + sizeof(*handle_p));
rbuffer.extract((char *) handle_p, 2, sizeof(*handle_p));
if (state_p) *state_p = static_cast<service_state_t>(rbuffer[1]);
//target_state = static_cast<service_state_t>(rbuffer[2 + sizeof(handle)]);
return 0;
}
else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
- cerr << "dinitctl: Failed to find/load service." << endl;
+ cerr << "dinitctl: failed to find/load service." << endl;
return 1;
}
else {
- cerr << "dinitctl: Protocol error." << endl;
+ cerr << "dinitctl: protocol error." << endl;
return 1;
}
}
-static int unpinService(int socknum, const char *service_name, bool verbose)
+static int unpin_service(int socknum, cpbuffer_t &rbuffer, const char *service_name, bool verbose)
{
using namespace std;
// Build buffer;
- if (issueLoadService(socknum, service_name) == 1) {
+ if (issue_load_service(socknum, service_name) == 1) {
return 1;
}
// Now we expect a reply:
- try {
- cpbuffer<1024> rbuffer;
- wait_for_reply(rbuffer, socknum);
-
- handle_t handle;
+ wait_for_reply(rbuffer, socknum);
+
+ handle_t handle;
+
+ if (check_load_reply(socknum, rbuffer, &handle, nullptr) != 0) {
+ return 1;
+ }
+
+ // Issue UNPIN command.
+ {
+ char buf[1 + sizeof(handle)];
+ buf[0] = DINIT_CP_UNPINSERVICE;
+ memcpy(buf + 1, &handle, sizeof(handle));
+ write_all_x(socknum, buf, 2 + sizeof(handle));
- if (checkLoadReply(socknum, rbuffer, &handle, nullptr) != 0) {
+ wait_for_reply(rbuffer, socknum);
+ if (rbuffer[0] != DINIT_RP_ACK) {
+ cerr << "dinitctl: protocol error." << endl;
return 1;
}
-
- // Issue UNPIN command.
- {
- int r;
-
- {
- char *buf = new char[1 + sizeof(handle)];
- unique_ptr<char[]> ubuf(buf);
- buf[0] = DINIT_CP_UNPINSERVICE;
- memcpy(buf + 1, &handle, sizeof(handle));
- r = write_all(socknum, buf, 2 + sizeof(handle));
- }
-
- if (r == -1) {
- perror("dinitctl: write");
- return 1;
- }
-
- wait_for_reply(rbuffer, socknum);
- if (rbuffer[0] != DINIT_RP_ACK) {
- cerr << "dinitctl: Protocol error." << endl;
- return 1;
- }
- rbuffer.consume(1);
- }
+ rbuffer.consume(1);
}
- catch (ReadCPException &exc) {
- cerr << "dinitctl: Control socket read failure or protocol error" << endl;
+
+ if (verbose) {
+ cout << "Service unpinned." << endl;
+ }
+ return 0;
+}
+
+static int unload_service(int socknum, cpbuffer_t &rbuffer, const char *service_name)
+{
+ using namespace std;
+
+ // Build buffer;
+ if (issue_load_service(socknum, service_name, true) == 1) {
return 1;
}
- catch (std::bad_alloc &exc) {
- cerr << "dinitctl: Out of memory" << endl;
+
+ // Now we expect a reply:
+ wait_for_reply(rbuffer, socknum);
+
+ handle_t handle;
+
+ if (rbuffer[0] == DINIT_RP_NOSERVICE) {
+ cerr << "dinitctl: service not loaded." << endl;
return 1;
}
-
- if (verbose) {
- cout << "Service unpinned." << endl;
+
+ if (check_load_reply(socknum, rbuffer, &handle, nullptr) != 0) {
+ return 1;
}
+
+ // Issue UNLOAD command.
+ {
+ char buf[1 + sizeof(handle)];
+ buf[0] = DINIT_CP_UNLOADSERVICE;
+ memcpy(buf + 1, &handle, sizeof(handle));
+ write_all_x(socknum, buf, 2 + sizeof(handle));
+
+ wait_for_reply(rbuffer, socknum);
+ if (rbuffer[0] == DINIT_RP_NAK) {
+ cerr << "dinitctl: Could not unload service; service not stopped, or is a dependency of "
+ "other service." << endl;
+ return 1;
+ }
+ if (rbuffer[0] != DINIT_RP_ACK) {
+ cerr << "dinitctl: Protocol error." << endl;
+ return 1;
+ }
+ rbuffer.consume(1);
+ }
+
+ cout << "Service unloaded." << endl;
return 0;
}
-static int listServices(int socknum)
+static int list_services(int socknum, cpbuffer_t &rbuffer)
{
using namespace std;
- try {
- char cmdbuf[] = { (char)DINIT_CP_LISTSERVICES };
- int r = write_all(socknum, cmdbuf, 1);
+ char cmdbuf[] = { (char)DINIT_CP_LISTSERVICES };
+ write_all_x(socknum, cmdbuf, 1);
+
+ wait_for_reply(rbuffer, socknum);
+ while (rbuffer[0] == DINIT_RP_SVCINFO) {
+ int hdrsize = 8 + std::max(sizeof(int), sizeof(pid_t));
+ fill_buffer_to(rbuffer, socknum, hdrsize);
+ int nameLen = rbuffer[1];
+ service_state_t current = static_cast<service_state_t>(rbuffer[2]);
+ service_state_t target = static_cast<service_state_t>(rbuffer[3]);
+
+ int console_flags = rbuffer[4];
+ bool has_console = (console_flags & 2) != 0;
+ bool waiting_console = (console_flags & 1) != 0;
+ bool was_skipped = (console_flags & 4) != 0;
+
+ stopped_reason_t stop_reason = static_cast<stopped_reason_t>(rbuffer[5]);
+
+ pid_t service_pid;
+ int exit_status;
+ if (current != service_state_t::STOPPED) {
+ rbuffer.extract((char *)&service_pid, 8, sizeof(service_pid));
+ }
+ else {
+ rbuffer.extract((char *)&exit_status, 8, sizeof(exit_status));
+ }
+
+ fill_buffer_to(rbuffer, socknum, nameLen + hdrsize);
+
+ char *name_ptr = rbuffer.get_ptr(hdrsize);
+ int clength = std::min(rbuffer.get_contiguous_length(name_ptr), nameLen);
+
+ string name = string(name_ptr, clength);
+ name.append(rbuffer.get_buf_base(), nameLen - clength);
+
+ cout << "[";
+
+ cout << (target == service_state_t::STARTED ? "{" : " ");
+ if (current == service_state_t::STARTED) {
+ cout << (was_skipped ? "s" : "+");
+ }
+ else {
+ cout << " ";
+ }
+ cout << (target == service_state_t::STARTED ? "}" : " ");
- if (r == -1) {
- perror("dinitctl: write");
- return 1;
+ if (current == service_state_t::STARTING) {
+ cout << "<<";
+ }
+ else if (current == service_state_t::STOPPING) {
+ cout << ">>";
+ }
+ else {
+ cout << " ";
}
- cpbuffer<1024> rbuffer;
- wait_for_reply(rbuffer, socknum);
- while (rbuffer[0] == DINIT_RP_SVCINFO) {
- fillBufferTo(&rbuffer, socknum, 8);
- int nameLen = rbuffer[1];
- service_state_t current = static_cast<service_state_t>(rbuffer[2]);
- service_state_t target = static_cast<service_state_t>(rbuffer[3]);
-
- fillBufferTo(&rbuffer, socknum, nameLen + 8);
-
- char *name_ptr = rbuffer.get_ptr(8);
- int clength = std::min(rbuffer.get_contiguous_length(name_ptr), nameLen);
-
- string name = string(name_ptr, clength);
- name.append(rbuffer.get_buf_base(), nameLen - clength);
-
- cout << "[";
-
- cout << (target == service_state_t::STARTED ? "{" : " ");
- cout << (current == service_state_t::STARTED ? "+" : " ");
- cout << (target == service_state_t::STARTED ? "}" : " ");
-
- if (current == service_state_t::STARTING) {
- cout << "<<";
+ cout << (target == service_state_t::STOPPED ? "{" : " ");
+ if (current == service_state_t::STOPPED) {
+ bool did_fail = false;
+ if (stop_reason == stopped_reason_t::TERMINATED) {
+ if (!WIFEXITED(exit_status) || WEXITSTATUS(exit_status) != 0) {
+ did_fail = true;
+ }
}
- else if (current == service_state_t::STOPPING) {
- cout << ">>";
+ else did_fail = (stop_reason != stopped_reason_t::NORMAL);
+
+ cout << (did_fail ? "X" : "-");
+ }
+ else {
+ cout << " ";
+ }
+ cout << (target == service_state_t::STOPPED ? "}" : " ");
+
+ cout << "] " << name;
+
+ if (current != service_state_t::STOPPED && service_pid != -1) {
+ cout << " (pid: " << service_pid << ")";
+ }
+
+ if (current == service_state_t::STOPPED && stop_reason == stopped_reason_t::TERMINATED) {
+ if (WIFEXITED(exit_status)) {
+ cout << " (exit status: " << WEXITSTATUS(exit_status) << ")";
}
- else {
- cout << " ";
+ else if (WIFSIGNALED(exit_status)) {
+ cout << " (signal: " << WSTOPSIG(exit_status) << ")";
}
-
- cout << (target == service_state_t::STOPPED ? "{" : " ");
- cout << (current == service_state_t::STOPPED ? "-" : " ");
- cout << (target == service_state_t::STOPPED ? "}" : " ");
-
- cout << "] " << name << endl;
-
- rbuffer.consume(8 + nameLen);
- wait_for_reply(rbuffer, socknum);
}
-
- if (rbuffer[0] != DINIT_RP_LISTDONE) {
- cerr << "dinitctl: Control socket protocol error" << endl;
- return 1;
+
+ if (has_console) {
+ cout << " (has console)";
+ }
+ else if (waiting_console) {
+ cout << " (waiting for console)";
}
+
+ cout << endl;
+
+ rbuffer.consume(hdrsize + nameLen);
+ wait_for_reply(rbuffer, socknum);
}
- catch (ReadCPException &exc) {
- cerr << "dinitctl: Control socket read failure or protocol error" << endl;
+
+ if (rbuffer[0] != DINIT_RP_LISTDONE) {
+ cerr << "dinitctl: Control socket protocol error" << endl;
return 1;
}
- catch (std::bad_alloc &exc) {
- cerr << "dinitctl: Out of memory" << endl;
+
+ return 0;
+}
+
+static int add_remove_dependency(int socknum, cpbuffer_t &rbuffer, bool add, char *service_from,
+ char *service_to, dependency_type dep_type)
+{
+ using namespace std;
+
+ // First find the "from" service:
+ if (issue_load_service(socknum, service_from, false) == 1) {
return 1;
}
-
+
+ wait_for_reply(rbuffer, socknum);
+
+ handle_t from_handle;
+
+ if (check_load_reply(socknum, rbuffer, &from_handle, nullptr) != 0) {
+ return 1;
+ }
+
+ // Then find or load the "to" service:
+ if (issue_load_service(socknum, service_to, false) == 1) {
+ return 1;
+ }
+
+ wait_for_reply(rbuffer, socknum);
+
+ handle_t to_handle;
+
+ if (check_load_reply(socknum, rbuffer, &to_handle, nullptr) != 0) {
+ return 1;
+ }
+
+ constexpr int pktsize = 2 + sizeof(handle_t) * 2;
+ char cmdbuf[pktsize] = { add ? (char)DINIT_CP_ADD_DEP : (char)DINIT_CP_REM_DEP, (char)dep_type};
+ memcpy(cmdbuf + 2, &from_handle, sizeof(from_handle));
+ memcpy(cmdbuf + 2 + sizeof(from_handle), &to_handle, sizeof(to_handle));
+ write_all_x(socknum, cmdbuf, pktsize);
+
+ wait_for_reply(rbuffer, socknum);
+
+ // check reply
+ if (rbuffer[0] == DINIT_RP_NAK) {
+ cerr << "dinitctl: Could not add dependency: circular dependency or wrong state" << endl;
+ return 1;
+ }
+ if (rbuffer[0] != DINIT_RP_ACK) {
+ cerr << "dinitctl: Control socket protocol error" << endl;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int shutdown_dinit(int socknum, cpbuffer_t &rbuffer)
+{
+ // TODO support no-wait option.
+ using namespace std;
+
+ // Build buffer;
+ constexpr int bufsize = 2;
+ char buf[bufsize];
+
+ buf[0] = DINIT_CP_SHUTDOWN;
+ buf[1] = static_cast<char>(shutdown_type_t::HALT);
+
+ write_all_x(socknum, buf, bufsize);
+
+ wait_for_reply(rbuffer, socknum);
+
+ if (rbuffer[0] != DINIT_RP_ACK) {
+ cerr << "dinitctl: Control socket protocol error" << endl;
+ return 1;
+ }
+
+ // Now wait for rollback complete:
+ try {
+ while (true) {
+ wait_for_info(rbuffer, socknum);
+ if (rbuffer[0] == DINIT_ROLLBACK_COMPLETED) {
+ break;
+ }
+ }
+ }
+ catch (cp_read_exception &exc) {
+ // Dinit can terminate before replying: let's assume that happened.
+ // TODO: better check, possibly ensure that dinit actually sends rollback complete before
+ // termination.
+ }
+
return 0;
}