Browse Source

Merge pull request #46 from chimera-linux/setenv

command to set a variable in activation environment
Davin McCall 2 years ago
parent
commit
c76a688694

+ 1 - 0
CONTRIBUTORS

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

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

@@ -46,6 +46,9 @@ dinitctl \- control services supervised by Dinit
 .br
 .br
 .B dinitctl
 .B dinitctl
 [\fIoptions\fR] \fBdisable\fR [\fB\-\-from\fR \fIfrom-service\fR] \fIto-service\fR
 [\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
 .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
 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
 other effect, and a service that is "disabled" may still be started if it is a dependency of
 another started service.
 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
 .SH SERVICE OPERATION
 .\"
 .\"

+ 62 - 0
src/control.cc

@@ -101,6 +101,9 @@ bool control_conn_t::process_packet()
     if (pktType == DINIT_CP_QUERYSERVICENAME) {
     if (pktType == DINIT_CP_QUERYSERVICENAME) {
         return process_query_name();
         return process_query_name();
     }
     }
+    if (pktType == DINIT_CP_SETENV) {
+        return process_setenv();
+    }
 
 
     // Unrecognized: give error response
     // Unrecognized: give error response
     char outbuf[] = { DINIT_RP_BADREQ };
     char outbuf[] = { DINIT_RP_BADREQ };
@@ -789,6 +792,65 @@ bool control_conn_t::process_query_name()
     return queue_packet(std::move(reply));
     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()
 bool control_conn_t::query_load_mech()
 {
 {
     rbuf.consume(1);
     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);
         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,
 static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *from, const char *to,
         bool enable, bool verbose);
         bool enable, bool verbose);
+static int do_setenv(int socknum, cpbuffer_t &rbuffer, char **env_names);
 
 
 static const char * describeState(bool stopped)
 static const char * describeState(bool stopped)
 {
 {
@@ -75,7 +76,8 @@ enum class command_t {
     ADD_DEPENDENCY,
     ADD_DEPENDENCY,
     RM_DEPENDENCY,
     RM_DEPENDENCY,
     ENABLE_SERVICE,
     ENABLE_SERVICE,
-    DISABLE_SERVICE
+    DISABLE_SERVICE,
+    SETENV,
 };
 };
 
 
 class dinit_protocol_error
 class dinit_protocol_error
@@ -91,6 +93,7 @@ int main(int argc, char **argv)
     bool show_help = argc < 2;
     bool show_help = argc < 2;
     const char *service_name = nullptr;
     const char *service_name = nullptr;
     const char *to_service_name = nullptr;
     const char *to_service_name = nullptr;
+    char **env_names = nullptr;
     dependency_type dep_type;
     dependency_type dep_type;
     bool dep_type_set = false;
     bool dep_type_set = false;
     
     
@@ -203,6 +206,12 @@ int main(int argc, char **argv)
             else if (strcmp(argv[i], "disable") == 0) {
             else if (strcmp(argv[i], "disable") == 0) {
                 command = command_t::DISABLE_SERVICE;
                 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 {
             else {
                 cerr << "dinitctl: unrecognized command: " << argv[i] << " (use --help for help)\n";
                 cerr << "dinitctl: unrecognized command: " << argv[i] << " (use --help for help)\n";
                 return 1;
                 return 1;
@@ -246,7 +255,7 @@ int main(int argc, char **argv)
                 to_service_name = argv[i];
                 to_service_name = argv[i];
             }
             }
             else {
             else {
-                if (service_name != nullptr) {
+                if (service_name != nullptr && (command != command_t::SETENV)) {
                     show_help = true;
                     show_help = true;
                     break;
                     break;
                 }
                 }
@@ -274,6 +283,10 @@ int main(int argc, char **argv)
         show_help = true;
         show_help = true;
     }
     }
 
 
+    if ((command == command_t::SETENV) && ! env_names) {
+        show_help = true;
+    }
+
     if (show_help) {
     if (show_help) {
         cout << "dinitctl:   control Dinit services\n"
         cout << "dinitctl:   control Dinit services\n"
           "\n"
           "\n"
@@ -292,6 +305,7 @@ int main(int argc, char **argv)
           "    dinitctl [options] rm-dep <type> <from-service> <to-service>\n"
           "    dinitctl [options] rm-dep <type> <from-service> <to-service>\n"
           "    dinitctl [options] enable [--from <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] disable [--from <from-service>] <to-service>\n"
+          "    dinitctl [options] setenv [name[=value] ...]\n"
           "\n"
           "\n"
           "Note: An activated service continues running when its dependents stop.\n"
           "Note: An activated service continues running when its dependents stop.\n"
           "\n"
           "\n"
@@ -395,6 +409,9 @@ int main(int argc, char **argv)
             return enable_disable_service(socknum, rbuffer, service_name, to_service_name,
             return enable_disable_service(socknum, rbuffer, service_name, to_service_name,
                     command == command_t::ENABLE_SERVICE, verbose);
                     command == command_t::ENABLE_SERVICE, verbose);
         }
         }
+        else if (command == command_t::SETENV) {
+            return do_setenv(socknum, rbuffer, env_names);
+        }
         else {
         else {
             return start_stop_service(socknum, rbuffer, service_name, command, do_pin, do_force,
             return start_stop_service(socknum, rbuffer, service_name, command, do_pin, do_force,
                     wait_for_service, ignore_unstarted, verbose);
                     wait_for_service, ignore_unstarted, verbose);
@@ -1287,3 +1304,52 @@ static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *
 
 
     return 0;
     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 \
         -e environment2 \
 	checkenv
 	checkenv
 
 
+../../dinit -d sd -u -p socket -q \
+	setenv1
+
 STATUS=FAIL
 STATUS=FAIL
 if [ -e env-record ]; then
 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
        STATUS=PASS
    fi
    fi
 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:
 // Reload a service:
 constexpr static int DINIT_CP_RELOADSERVICE = 16;
 constexpr static int DINIT_CP_RELOADSERVICE = 16;
 
 
+// Export a set of environment variables into activation environment:
+constexpr static int DINIT_CP_SETENV = 17;
+
 // Replies:
 // Replies:
 
 
 // Reply: ACK/NAK to request
 // 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.
     // Process a QUERYSERVICENAME packet.
     bool process_query_name();
     bool process_query_name();
 
 
+    // Process a SETENV packet.
+    bool process_setenv();
+
     // List all loaded services and their state.
     // List all loaded services and their state.
     bool list_services();
     bool list_services();