Просмотр исходного кода

Merge pull request #46 from chimera-linux/setenv

command to set a variable in activation environment
Davin McCall 2 лет назад
Родитель
Сommit
c76a688694

+ 1 - 0
CONTRIBUTORS

@@ -1,5 +1,6 @@
 Dinit is primarily written by Davin McCall.
 
 The following people have contributed:
+ * Daniel Kolesa - Code, testing, documentation.
  * Edd Barrett - Testing, documentation.
  * Fabien Poussin - Code, services, documentation.

+ 9 - 0
doc/manpages/dinitctl.8.m4

@@ -46,6 +46,9 @@ dinitctl \- control services supervised by Dinit
 .br
 .B dinitctl
 [\fIoptions\fR] \fBdisable\fR [\fB\-\-from\fR \fIfrom-service\fR] \fIto-service\fR
+.br
+.B dinitctl
+[\fIoptions\fR] \fBsetenv\fR [\fIname\fR[=\fIvalue\fR] \fI...\fR]
 .\"
 .SH DESCRIPTION
 .\"
@@ -216,6 +219,12 @@ Permanently disable a \fBwaits-for\fR dependency between two services. This is t
 Note that the \fBdisable\fR command affects only the dependency specified (or implied). It has no
 other effect, and a service that is "disabled" may still be started if it is a dependency of
 another started service.
+.TP
+\fBsetenv\fR
+Export one or more variables into the activation environment. The value can be provided on the command line
+or retrieved from the environment \fBdinitctl\fR is called in. Any subsequently started or restarted
+service will have these environment variables available. This is particularly useful for user services
+that need access to session information.
 .\"
 .SH SERVICE OPERATION
 .\"

+ 62 - 0
src/control.cc

@@ -101,6 +101,9 @@ bool control_conn_t::process_packet()
     if (pktType == DINIT_CP_QUERYSERVICENAME) {
         return process_query_name();
     }
+    if (pktType == DINIT_CP_SETENV) {
+        return process_setenv();
+    }
 
     // Unrecognized: give error response
     char outbuf[] = { DINIT_RP_BADREQ };
@@ -789,6 +792,65 @@ bool control_conn_t::process_query_name()
     return queue_packet(std::move(reply));
 }
 
+bool control_conn_t::process_setenv()
+{
+    using std::string;
+
+    string envVar;
+    typename string::size_type eq;
+
+    constexpr int pkt_size = 4;
+    char badreqRep[] = { DINIT_RP_BADREQ };
+    char okRep[] = { DINIT_RP_ACK };
+
+    if (rbuf.get_length() < pkt_size) {
+        chklen = pkt_size;
+        return true;
+    }
+
+    uint16_t envSize;
+    rbuf.extract((char *)&envSize, 1, 2);
+    if (envSize <= 0 || envSize > (1024 - 3)) {
+        goto badreq;
+    }
+    chklen = envSize + 3; // packet type + (2 byte) length + envvar
+
+    if (rbuf.get_length() < chklen) {
+        // packet not complete yet; read more
+        return true;
+    }
+
+    envVar = rbuf.extract_string(3, envSize);
+
+    eq = envVar.find('=');
+    if (!eq || eq == envVar.npos) {
+        // not found or at the beginning of the string
+        goto badreq;
+    }
+
+    envVar[eq] = '\0';
+
+    if (setenv(envVar.c_str(), &envVar[eq + 1], 1) != 0) {
+        // failed to set the var
+        goto badreq;
+    }
+
+    // success response
+    if (! queue_packet(okRep, 1)) return false;
+
+    // Clear the packet from the buffer
+    rbuf.consume(chklen);
+    chklen = 0;
+    return true;
+
+badreq:
+    // Queue error response / mark connection bad
+    if (! queue_packet(badreqRep, 1)) return false;
+    bad_conn_close = true;
+    iob.set_watches(OUT_EVENTS);
+    return true;
+}
+
 bool control_conn_t::query_load_mech()
 {
     rbuf.consume(1);

+ 68 - 2
src/dinitctl.cc

@@ -49,6 +49,7 @@ static int add_remove_dependency(int socknum, cpbuffer_t &rbuffer, bool add, con
         const char *service_to, dependency_type dep_type, bool verbose);
 static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *from, const char *to,
         bool enable, bool verbose);
+static int do_setenv(int socknum, cpbuffer_t &rbuffer, char **env_names);
 
 static const char * describeState(bool stopped)
 {
@@ -75,7 +76,8 @@ enum class command_t {
     ADD_DEPENDENCY,
     RM_DEPENDENCY,
     ENABLE_SERVICE,
-    DISABLE_SERVICE
+    DISABLE_SERVICE,
+    SETENV,
 };
 
 class dinit_protocol_error
@@ -91,6 +93,7 @@ int main(int argc, char **argv)
     bool show_help = argc < 2;
     const char *service_name = nullptr;
     const char *to_service_name = nullptr;
+    char **env_names = nullptr;
     dependency_type dep_type;
     bool dep_type_set = false;
     
@@ -203,6 +206,12 @@ int main(int argc, char **argv)
             else if (strcmp(argv[i], "disable") == 0) {
                 command = command_t::DISABLE_SERVICE;
             }
+            else if (strcmp(argv[i], "setenv") == 0) {
+                command = command_t::SETENV;
+                if ((i + 1) < argc) {
+                    env_names = &argv[i + 1];
+                }
+            }
             else {
                 cerr << "dinitctl: unrecognized command: " << argv[i] << " (use --help for help)\n";
                 return 1;
@@ -246,7 +255,7 @@ int main(int argc, char **argv)
                 to_service_name = argv[i];
             }
             else {
-                if (service_name != nullptr) {
+                if (service_name != nullptr && (command != command_t::SETENV)) {
                     show_help = true;
                     break;
                 }
@@ -274,6 +283,10 @@ int main(int argc, char **argv)
         show_help = true;
     }
 
+    if ((command == command_t::SETENV) && ! env_names) {
+        show_help = true;
+    }
+
     if (show_help) {
         cout << "dinitctl:   control Dinit services\n"
           "\n"
@@ -292,6 +305,7 @@ int main(int argc, char **argv)
           "    dinitctl [options] rm-dep <type> <from-service> <to-service>\n"
           "    dinitctl [options] enable [--from <from-service>] <to-service>\n"
           "    dinitctl [options] disable [--from <from-service>] <to-service>\n"
+          "    dinitctl [options] setenv [name[=value] ...]\n"
           "\n"
           "Note: An activated service continues running when its dependents stop.\n"
           "\n"
@@ -395,6 +409,9 @@ int main(int argc, char **argv)
             return enable_disable_service(socknum, rbuffer, service_name, to_service_name,
                     command == command_t::ENABLE_SERVICE, verbose);
         }
+        else if (command == command_t::SETENV) {
+            return do_setenv(socknum, rbuffer, env_names);
+        }
         else {
             return start_stop_service(socknum, rbuffer, service_name, command, do_pin, do_force,
                     wait_for_service, ignore_unstarted, verbose);
@@ -1287,3 +1304,52 @@ static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *
 
     return 0;
 }
+
+static int do_setenv(int socknum, cpbuffer_t &rbuffer, char **env_names)
+{
+    using namespace std;
+
+    string buf;
+
+    while (*env_names) {
+        const char *envp = *env_names++;
+        buf.clear();
+        buf.reserve(6);
+        // protocol message and size space
+        buf.push_back(DINIT_CP_SETENV);
+        buf.append(2, 0);
+        // either full var or name
+        auto elen = strlen(envp);
+        buf.append(envp, elen);
+        // = not found, get value from environment
+        if (!memchr(envp, '=', elen)) {
+            buf.push_back('=');
+            auto *envv = getenv(envp);
+            if (envv) {
+                buf.append(envv);
+            }
+        }
+        uint16_t bufs = buf.size() - 3;
+        // sanitize length early on
+        if (bufs > (1024 - 3)) {
+            auto eq = buf.find('=', 3);
+            auto name = buf.substr(3, eq - 3);
+            cerr << "dinitctl: environment variable '" << name << "' too long." << endl;
+            return 1;
+        }
+        // set size in protocol message
+        memcpy(&buf[1], &bufs, 2);
+        // send
+        write_all_x(socknum, buf.data(), bufs + 3);
+        wait_for_reply(rbuffer, socknum);
+        if (rbuffer[0] == DINIT_RP_BADREQ) {
+            cerr << "dinitctl: failed to export environment." << endl;
+            return 1;
+        } else if (rbuffer[0] != DINIT_RP_ACK) {
+            throw dinit_protocol_error();
+        }
+        rbuffer.consume(1);
+    }
+
+    return 0;
+}

+ 4 - 1
src/igr-tests/environ/run-test.sh

@@ -10,9 +10,12 @@ rm -f ./env-record
         -e environment2 \
 	checkenv
 
+../../dinit -d sd -u -p socket -q \
+	setenv1
+
 STATUS=FAIL
 if [ -e env-record ]; then
-   if [ "$(cat env-record)" = "$(echo hello; echo goodbye)" ]; then
+   if [ "$(cat env-record)" = "$(echo hello; echo goodbye; echo 3; echo 2; echo 1)" ]; then
        STATUS=PASS
    fi
 fi

+ 3 - 0
src/igr-tests/environ/sd/setenv1

@@ -0,0 +1,3 @@
+type = process
+command = ./setenv.sh setenv1
+depends-on = setenv2

+ 3 - 0
src/igr-tests/environ/sd/setenv2

@@ -0,0 +1,3 @@
+type = scripted
+command = ./setenv.sh setenv2
+depends-on = setenv3

+ 2 - 0
src/igr-tests/environ/sd/setenv3

@@ -0,0 +1,2 @@
+type = scripted
+command = ./setenv.sh setenv3

+ 21 - 0
src/igr-tests/environ/setenv.sh

@@ -0,0 +1,21 @@
+#!/bin/sh
+
+case "$1" in
+    setenv1)
+        if [ "$FOO" = "foo" -a "$BAR" = "bar" -a "$BAZ" = "baz" ]; then
+            echo 1 >> ./env-record
+        fi
+        ;;
+    setenv2)
+        if [ "$FOO" = "foo" ]; then
+            echo 2 >> ./env-record
+            export BAR=bar
+            dinitctl -p socket setenv BAR BAZ=baz
+        fi
+        ;;
+    setenv3)
+        dinitctl -p socket setenv FOO=foo
+        echo 3 >> ./env-record
+        ;;
+    *) ;;
+esac

+ 3 - 0
src/includes/control-cmds.h

@@ -45,6 +45,9 @@ constexpr static int DINIT_CP_QUERYSERVICENAME = 15;
 // Reload a service:
 constexpr static int DINIT_CP_RELOADSERVICE = 16;
 
+// Export a set of environment variables into activation environment:
+constexpr static int DINIT_CP_SETENV = 17;
+
 // Replies:
 
 // Reply: ACK/NAK to request

+ 3 - 0
src/includes/control.h

@@ -152,6 +152,9 @@ class control_conn_t : private service_listener
     // Process a QUERYSERVICENAME packet.
     bool process_query_name();
 
+    // Process a SETENV packet.
+    bool process_setenv();
+
     // List all loaded services and their state.
     bool list_services();