Read environment from file at startup.
authorDavin McCall <davmac@davmac.org>
Mon, 26 Feb 2018 22:53:40 +0000 (22:53 +0000)
committerDavin McCall <davmac@davmac.org>
Mon, 26 Feb 2018 23:59:56 +0000 (23:59 +0000)
TODO
doc/manpages/dinit.8
src/dinit.cc

diff --git a/TODO b/TODO
index 6fb10735f7ebbdada8e6fc6a3fcad4288b31e202..5af26747f79670a9e78bd660c8836f78d007e5ee 100644 (file)
--- a/TODO
+++ b/TODO
@@ -9,8 +9,6 @@ For version 1.0:
 * Write wtmp entry on startup (see simpleinit)
 * "triggered" service type: external process notifies Dinit when the service
   has started.
-* Some way to specify environment (perhaps a common environment for all
-  processes)
 * Load services from several different directories, with an order of precedence,
   to allow for packaged service descriptions and user-modified service
   descriptions.
index 54ca9fe4c239fe12818ea589d820df89c60dcbb0..b24a89ebacf71255f8a9e3f8aac73902b817ccb5 100644 (file)
@@ -30,6 +30,12 @@ DESCRIPTION FILES\fR for details.
 \fB\-d\fR \fIdir\fP, \fB\-\-services\-dir\fR \fIdir\fP
 Specifies \fIdir\fP as the directory containing service definition files.
 .TP
+\fB\-e\fR \fIfile\fP, \fB\-\-env\-file\fR \fIfile\fP
+Read initial environment from \fIfile\fP. For the system init process, the
+default is \fI/etc/dinit/environment\fR; for a user instance there is no
+default. Values add to and replace variables present in the environment when
+Dinit started.
+.TP
 \fB\-p\fR \fIpath\fP, \fB\-\-socket-path\fR \fIpath\fP
 Species \fIpath\fP as the path to the control socket used to listen for
 commands from the \fBdinitctl\fR program.
index 247937fd0b2470221e2c0b1f2c505e467105758e..1d6ac2230f09baa730918a221aec37b881bff7a3 100644 (file)
@@ -1,4 +1,5 @@
 #include <iostream>
+#include <fstream>
 #include <list>
 #include <cstring>
 #include <csignal>
@@ -50,6 +51,7 @@ static void sigquit_cb(eventloop_t &eloop) noexcept;
 static void sigterm_cb(eventloop_t &eloop) noexcept;
 static void close_control_socket() noexcept;
 static void wait_for_user_input() noexcept;
+static void read_env_file(const char *);
 
 static void control_socket_cb(eventloop_t *loop, int fd);
 
@@ -69,6 +71,8 @@ int active_control_conns = 0;
 static const char *control_socket_path = "/dev/dinitctl";
 static std::string control_socket_str;
 
+static const char *env_file_path = "/etc/dinit/environment";
+
 static const char *log_socket_path = "/dev/log";
 
 static const char *user_home_path = nullptr;
@@ -160,24 +164,33 @@ int dinit_main(int argc, char **argv)
     
     am_system_init = (getpid() == 1);
     const char * service_dir = nullptr;
+    const char * env_file = nullptr;
     string service_dir_str; // to hold storage for above if necessary
     bool control_socket_path_set = false;
+    bool env_file_set = false;
 
     // list of services to start
     list<const char *> services_to_start;
     
     // Arguments, if given, specify a list of services to start.
-    // If we are running as init (PID=1), the kernel gives us any command line
-    // arguments it was given but didn't recognize, including "single" (usually
-    // for "boot to single user mode" aka just start the shell). We can treat
-    // them as service names. In the worst case we can't find any of the named
+    // If we are running as init (PID=1), the Linux kernel gives us any command line arguments it was given
+    // but didn't recognize, including "single" (usually for "boot to single user mode" aka just start the
+    // shell). We can treat them as service names. In the worst case we can't find any of the named
     // services, and so we'll start the "boot" service by default.
     if (argc > 1) {
       for (int i = 1; i < argc; i++) {
         if (argv[i][0] == '-') {
             // An option...
-            if (strcmp(argv[i], "--services-dir") == 0 ||
-                    strcmp(argv[i], "-d") == 0) {
+            if (strcmp(argv[i], "--env-file") == 0 || strcmp(argv[i], "-e") == 0) {
+                if (++i < argc) {
+                    env_file_set = true;
+                    env_file = argv[i];
+                }
+                else {
+                    cerr << "dinit: '--env-file' (-e) requires an argument" << endl;
+                }
+            }
+            else if (strcmp(argv[i], "--services-dir") == 0 || strcmp(argv[i], "-d") == 0) {
                 if (++i < argc) {
                     service_dir = argv[i];
                 }
@@ -186,12 +199,10 @@ int dinit_main(int argc, char **argv)
                     return 1;
                 }
             }
-            else if (strcmp(argv[i], "--system") == 0 ||
-                    strcmp(argv[i], "-s") == 0) {
+            else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
                 am_system_init = true;
             }
-            else if (strcmp(argv[i], "--socket-path") == 0 ||
-                    strcmp(argv[i], "-p") == 0) {
+            else if (strcmp(argv[i], "--socket-path") == 0 || strcmp(argv[i], "-p") == 0) {
                 if (++i < argc) {
                     control_socket_path = argv[i];
                     control_socket_path_set = true;
@@ -204,6 +215,8 @@ int dinit_main(int argc, char **argv)
             else if (strcmp(argv[i], "--help") == 0) {
                 cout << "dinit, an init with dependency management" << endl;
                 cout << " --help                       display help" << endl;
+                cout << " --env-file <file>, -e <file>" << endl;
+                cout << "                              environment variable initialisation file" << endl;
                 cout << " --services-dir <dir>, -d <dir>" << endl;
                 cout << "                              set base directory for service description" << endl;
                 cout << "                              files (-d <dir>)" << endl;
@@ -244,6 +257,10 @@ int dinit_main(int argc, char **argv)
         
         if (onefd > 2) close(onefd);
         if (twofd > 2) close(twofd);
+
+        if (! env_file_set) {
+            env_file = env_file_path;
+        }
     }
 
     /* Set up signal handlers etc */
@@ -343,6 +360,10 @@ int dinit_main(int argc, char **argv)
     
     init_log(services);
     
+    if (env_file != nullptr) {
+        read_env_file(env_file);
+    }
+
     for (auto svc : services_to_start) {
         try {
             services->start_service(svc);
@@ -446,6 +467,65 @@ int dinit_main(int argc, char **argv)
     return 0;
 }
 
+static void log_bad_env(int linenum)
+{
+    log(loglevel_t::ERROR, "invalid environment variable setting in environment file (line ", linenum, ")");
+}
+
+// Read and set environment variables from a file.
+static void read_env_file(const char *env_file_path)
+{
+    // Note that we can't use the log in this function; it hasn't been initialised yet.
+
+    std::ifstream env_file(env_file_path);
+    if (! env_file) return;
+
+    env_file.exceptions(std::ios::badbit);
+
+    auto &clocale = std::locale::classic();
+    std::string line;
+    int linenum = 0;
+
+    while (std::getline(env_file, line)) {
+        linenum++;
+        auto lpos = line.begin();
+        auto lend = line.end();
+        while (lpos != lend && std::isspace(*lpos, clocale)) {
+            ++lpos;
+        }
+
+        if (lpos != lend) {
+            if (*lpos != '#') {
+                if (*lpos == '=') {
+                    log_bad_env(linenum);
+                    continue;
+                }
+                auto name_begin = lpos++;
+                // skip until '=' or whitespace:
+                while (lpos != lend && *lpos != '=' && ! std::isspace(*lpos, clocale)) ++lpos;
+                auto name_end = lpos;
+                //  skip whitespace:
+                while (lpos != lend && std::isspace(*lpos, clocale)) ++lpos;
+                if (lpos == lend) {
+                    log_bad_env(linenum);
+                    continue;
+                }
+
+                ++lpos;
+                auto val_begin = lpos;
+                while (lpos != lend && *lpos != '\n') ++lpos;
+                auto val_end = lpos;
+
+                std::string name = line.substr(name_begin - line.begin(), name_end - name_begin);
+                std::string value = line.substr(val_begin - line.begin(), val_end - val_begin);
+                if (setenv(name.c_str(), value.c_str(), true) == -1) {
+                    throw std::system_error(errno, std::system_category());
+                }
+            }
+        }
+    }
+}
+
 // In exception situations we want user confirmation before proceeding (eg on critical boot failure
 // we wait before rebooting to avoid a reboot loop).
 static void wait_for_user_input() noexcept