chiark / gitweb /
resolved: read the system /etc/resolv.conf unless we wrote it ourselves
[elogind.git] / src / shared / util.c
index a1c8baf237f12999d65268e6e6973283def34783..e2c955bc0700594620a7c517d2a368c7593f296e 100644 (file)
@@ -140,26 +140,38 @@ char* endswith(const char *s, const char *postfix) {
         return (char*) s + sl - pl;
 }
 
-bool first_word(const char *s, const char *word) {
+char* first_word(const char *s, const char *word) {
         size_t sl, wl;
+        const char *p;
 
         assert(s);
         assert(word);
 
+        /* Checks if the string starts with the specified word, either
+         * followed by NUL or by whitespace. Returns a pointer to the
+         * NUL or the first character after the whitespace. */
+
         sl = strlen(s);
         wl = strlen(word);
 
         if (sl < wl)
-                return false;
+                return NULL;
 
         if (wl == 0)
-                return true;
+                return (char*) s;
 
         if (memcmp(s, word, wl) != 0)
-                return false;
+                return NULL;
 
-        return s[wl] == 0 ||
-                strchr(WHITESPACE, s[wl]);
+        p = s + wl;
+        if (*p == 0)
+                return (char*) p;
+
+        if (!strchr(WHITESPACE, *p))
+                return NULL;
+
+        p += strspn(p, WHITESPACE);
+        return (char*) p;
 }
 
 int close_nointr(int fd) {
@@ -231,9 +243,9 @@ int unlink_noerrno(const char *path) {
 int parse_boolean(const char *v) {
         assert(v);
 
-        if (streq(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || strcaseeq(v, "on"))
+        if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on"))
                 return 1;
-        else if (streq(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || strcaseeq(v, "off"))
+        else if (streq(v, "0") || strcaseeq(v, "no") || strcaseeq(v, "n") || strcaseeq(v, "false") || strcaseeq(v, "f") || strcaseeq(v, "off"))
                 return 0;
 
         return -EINVAL;
@@ -332,6 +344,26 @@ int safe_atoi(const char *s, int *ret_i) {
         return 0;
 }
 
+int safe_atou8(const char *s, uint8_t *ret) {
+        char *x = NULL;
+        unsigned long l;
+
+        assert(s);
+        assert(ret);
+
+        errno = 0;
+        l = strtoul(s, &x, 0);
+
+        if (!x || x == s || *x || errno)
+                return errno > 0 ? -errno : -EINVAL;
+
+        if ((unsigned long) (uint8_t) l != l)
+                return -ERANGE;
+
+        *ret = (uint8_t) l;
+        return 0;
+}
+
 int safe_atollu(const char *s, long long unsigned *ret_llu) {
         char *x = NULL;
         unsigned long long l;
@@ -395,37 +427,50 @@ static size_t strcspn_escaped(const char *s, const char *reject) {
                 else if (s[n] == '\\')
                         escaped = true;
                 else if (strchr(reject, s[n]))
-                        return n;
+                        break;
         }
-        return n;
+        /* if s ends in \, return index of previous char */
+        return n - escaped;
 }
 
 /* Split a string into words. */
-char *split(const char *c, size_t *l, const char *separator, bool quoted, char **state) {
-        char *current;
+const char* split(const char **state, size_t *l, const char *separator, bool quoted) {
+        const char *current;
 
-        current = *state ? *state : (char*) c;
+        current = *state;
 
-        if (!*current || *c == 0)
+        if (!*current) {
+                assert(**state == '\0');
                 return NULL;
+        }
 
         current += strspn(current, separator);
-        if (!*current)
+        if (!*current) {
+                *state = current;
                 return NULL;
+        }
 
         if (quoted && strchr("\'\"", *current)) {
-                char quotechar = *(current++);
-                *l = strcspn_escaped(current, (char[]){quotechar, '\0'});
-                *state = current+*l+1;
+                char quotechars[2] = {*current, '\0'};
+
+                *l = strcspn_escaped(current + 1, quotechars);
+                if (current[*l + 1] == '\0' ||
+                    (current[*l + 2] && !strchr(separator, current[*l + 2]))) {
+                        /* right quote missing or garbage at the end*/
+                        *state = current;
+                        return NULL;
+                }
+                assert(current[*l + 1] == quotechars[0]);
+                *state = current++ + *l + 2;
         } else if (quoted) {
                 *l = strcspn_escaped(current, separator);
-                *state = current+*l;
+                *state = current + *l;
         } else {
                 *l = strcspn(current, separator);
-                *state = current+*l;
+                *state = current + *l;
         }
 
-        return (char*) current;
+        return current;
 }
 
 int get_parent_of_pid(pid_t pid, pid_t *_ppid) {
@@ -1197,7 +1242,7 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre
 
         r = new(char, pl+length+1);
         if (!r)
-                return r;
+                return NULL;
 
         if (prefix)
                 memcpy(r, prefix, pl);
@@ -1256,7 +1301,7 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre
                         a = unhexchar(f[1]);
                         b = unhexchar(f[2]);
 
-                        if (a < 0 || b < 0) {
+                        if (a < 0 || b < 0 || (a == 0 && b == 0)) {
                                 /* Invalid escape code, let's take it literal then */
                                 *(t++) = '\\';
                                 *(t++) = 'x';
@@ -1283,7 +1328,7 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre
                         b = unoctchar(f[1]);
                         c = unoctchar(f[2]);
 
-                        if (a < 0 || b < 0 || c < 0) {
+                        if (a < 0 || b < 0 || c < 0 || (a == 0 && b == 0 && c == 0)) {
                                 /* Invalid escape code, let's take it literal then */
                                 *(t++) = '\\';
                                 *(t++) = f[0];
@@ -1566,8 +1611,7 @@ int chvt(int vt) {
 
 int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
         struct termios old_termios, new_termios;
-        char c;
-        char line[LINE_MAX];
+        char c, line[LINE_MAX];
 
         assert(f);
         assert(ret);
@@ -1582,7 +1626,7 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
                 if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) {
                         size_t k;
 
-                        if (t != (usec_t) -1) {
+                        if (t != USEC_INFINITY) {
                                 if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) {
                                         tcsetattr(fileno(f), TCSADRAIN, &old_termios);
                                         return -ETIMEDOUT;
@@ -1604,12 +1648,14 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
                 }
         }
 
-        if (t != (usec_t) -1)
+        if (t != USEC_INFINITY) {
                 if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0)
                         return -ETIMEDOUT;
+        }
 
+        errno = 0;
         if (!fgets(line, sizeof(line), f))
-                return -EIO;
+                return errno ? -errno : -EIO;
 
         truncate_nl(line);
 
@@ -1623,7 +1669,8 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) {
         return 0;
 }
 
-int ask(char *ret, const char *replies, const char *text, ...) {
+int ask_char(char *ret, const char *replies, const char *text, ...) {
+        int r;
 
         assert(ret);
         assert(replies);
@@ -1632,7 +1679,6 @@ int ask(char *ret, const char *replies, const char *text, ...) {
         for (;;) {
                 va_list ap;
                 char c;
-                int r;
                 bool need_nl = true;
 
                 if (on_tty())
@@ -1647,7 +1693,7 @@ int ask(char *ret, const char *replies, const char *text, ...) {
 
                 fflush(stdout);
 
-                r = read_one_char(stdin, &c, (usec_t) -1, &need_nl);
+                r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl);
                 if (r < 0) {
 
                         if (r == -EBADMSG) {
@@ -1671,6 +1717,49 @@ int ask(char *ret, const char *replies, const char *text, ...) {
         }
 }
 
+int ask_string(char **ret, const char *text, ...) {
+        assert(ret);
+        assert(text);
+
+        for (;;) {
+                char line[LINE_MAX];
+                va_list ap;
+
+                if (on_tty())
+                        fputs(ANSI_HIGHLIGHT_ON, stdout);
+
+                va_start(ap, text);
+                vprintf(text, ap);
+                va_end(ap);
+
+                if (on_tty())
+                        fputs(ANSI_HIGHLIGHT_OFF, stdout);
+
+                fflush(stdout);
+
+                errno = 0;
+                if (!fgets(line, sizeof(line), stdin))
+                        return errno ? -errno : -EIO;
+
+                if (!endswith(line, "\n"))
+                        putchar('\n');
+                else {
+                        char *s;
+
+                        if (isempty(line))
+                                continue;
+
+                        truncate_nl(line);
+                        s = strdup(line);
+                        if (!s)
+                                return -ENOMEM;
+
+                        *ret = s;
+                        return 0;
+                }
+        }
+}
+
 int reset_terminal_fd(int fd, bool switch_to_text) {
         struct termios termios;
         int r = 0;
@@ -1854,11 +1943,11 @@ int acquire_terminal(
          * on the same tty as an untrusted user this should not be a
          * problem. (Which he probably should not do anyway.) */
 
-        if (timeout != (usec_t) -1)
+        if (timeout != USEC_INFINITY)
                 ts = now(CLOCK_MONOTONIC);
 
         if (!fail && !force) {
-                notify = inotify_init1(IN_CLOEXEC | (timeout != (usec_t) -1 ? IN_NONBLOCK : 0));
+                notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0));
                 if (notify < 0) {
                         r = -errno;
                         goto fail;
@@ -1922,7 +2011,7 @@ int acquire_terminal(
                         ssize_t l;
                         struct inotify_event *e;
 
-                        if (timeout != (usec_t) -1) {
+                        if (timeout != USEC_INFINITY) {
                                 usec_t n;
 
                                 n = now(CLOCK_MONOTONIC);
@@ -2104,7 +2193,7 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) {
                          * and expect that any error/EOF is reported
                          * via read() */
 
-                        fd_wait_for_event(fd, POLLIN, (usec_t) -1);
+                        fd_wait_for_event(fd, POLLIN, USEC_INFINITY);
                         continue;
                 }
 
@@ -2139,7 +2228,7 @@ ssize_t loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
                          * and expect that any error/EOF is reported
                          * via write() */
 
-                        fd_wait_for_event(fd, POLLOUT, (usec_t) -1);
+                        fd_wait_for_event(fd, POLLOUT, USEC_INFINITY);
                         continue;
                 }
 
@@ -3086,12 +3175,13 @@ fail:
 }
 
 char **replace_env_argv(char **argv, char **env) {
-        char **r, **i;
+        char **ret, **i;
         unsigned k = 0, l = 0;
 
         l = strv_length(argv);
 
-        if (!(r = new(char*, l+1)))
+        ret = new(char*, l+1);
+        if (!ret)
                 return NULL;
 
         STRV_FOREACH(i, argv) {
@@ -3104,10 +3194,12 @@ char **replace_env_argv(char **argv, char **env) {
 
                         e = strv_env_get(env, *i+1);
                         if (e) {
+                                int r;
 
-                                if (!(m = strv_split_quoted(e))) {
-                                        r[k] = NULL;
-                                        strv_free(r);
+                                r = strv_split_quoted(&m, e);
+                                if (r < 0) {
+                                        ret[k] = NULL;
+                                        strv_free(ret);
                                         return NULL;
                                 }
                         } else
@@ -3116,16 +3208,17 @@ char **replace_env_argv(char **argv, char **env) {
                         q = strv_length(m);
                         l = l + q - 1;
 
-                        if (!(w = realloc(r, sizeof(char*) * (l+1)))) {
-                                r[k] = NULL;
-                                strv_free(r);
+                        w = realloc(ret, sizeof(char*) * (l+1));
+                        if (!w) {
+                                ret[k] = NULL;
+                                strv_free(ret);
                                 strv_free(m);
                                 return NULL;
                         }
 
-                        r = w;
+                        ret = w;
                         if (m) {
-                                memcpy(r + k, m, q * sizeof(char*));
+                                memcpy(ret + k, m, q * sizeof(char*));
                                 free(m);
                         }
 
@@ -3134,14 +3227,16 @@ char **replace_env_argv(char **argv, char **env) {
                 }
 
                 /* If ${FOO} appears as part of a word, replace it by the variable as-is */
-                if (!(r[k++] = replace_env(*i, env))) {
-                        strv_free(r);
+                ret[k] = replace_env(*i, env);
+                if (!ret[k]) {
+                        strv_free(ret);
                         return NULL;
                 }
+                k++;
         }
 
-        r[k] = NULL;
-        return r;
+        ret[k] = NULL;
+        return ret;
 }
 
 int fd_columns(int fd) {
@@ -3384,7 +3479,7 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi
                         return -errno;
         }
 
-        if (stamp != (usec_t) -1) {
+        if (stamp != USEC_INFINITY) {
                 struct timespec ts[2];
 
                 timespec_store(&ts[0], stamp);
@@ -3399,7 +3494,7 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi
 }
 
 int touch(const char *path) {
-        return touch_file(path, false, (usec_t) -1, (uid_t) -1, (gid_t) -1, 0);
+        return touch_file(path, false, USEC_INFINITY, (uid_t) -1, (gid_t) -1, 0);
 }
 
 char *unquote(const char *s, const char* quotes) {
@@ -3560,6 +3655,17 @@ int null_or_empty_path(const char *fn) {
         return null_or_empty(&st);
 }
 
+int null_or_empty_fd(int fd) {
+        struct stat st;
+
+        assert(fd >= 0);
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        return null_or_empty(&st);
+}
+
 DIR *xopendirat(int fd, const char *name, int flags) {
         int nfd;
         DIR *d;
@@ -3806,7 +3912,8 @@ void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv
                         if (!dirent_is_file(de))
                                 continue;
 
-                        if (asprintf(&path, "%s/%s", directory, de->d_name) < 0) {
+                        path = strjoin(directory, "/", de->d_name, NULL);
+                        if (!path) {
                                 log_oom();
                                 _exit(EXIT_FAILURE);
                         }
@@ -3848,7 +3955,7 @@ void execute_directory(const char *directory, DIR *d, usec_t timeout, char *argv
                  * timout. We simply rely on SIGALRM as default action
                  * terminating the process, and turn on alarm(). */
 
-                if (timeout != (usec_t) -1)
+                if (timeout != USEC_INFINITY)
                         alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC);
 
                 while (!hashmap_isempty(pids)) {
@@ -3975,6 +4082,21 @@ char* hostname_cleanup(char *s, bool lowercase) {
         return s;
 }
 
+bool machine_name_is_valid(const char *s) {
+
+        if (!hostname_is_valid(s))
+                return false;
+
+        /* Machine names should be useful hostnames, but also be
+         * useful in unit names, hence we enforce a stricter length
+         * limitation. */
+
+        if (strlen(s) > 64)
+                return false;
+
+        return true;
+}
+
 int pipe_eof(int fd) {
         struct pollfd pollfd = {
                 .fd = fd,
@@ -4003,7 +4125,7 @@ int fd_wait_for_event(int fd, int event, usec_t t) {
         struct timespec ts;
         int r;
 
-        r = ppoll(&pollfd, 1, t == (usec_t) -1 ? NULL : timespec_store(&ts, t), NULL);
+        r = ppoll(&pollfd, 1, t == USEC_INFINITY ? NULL : timespec_store(&ts, t), NULL);
         if (r < 0)
                 return -errno;
 
@@ -5202,7 +5324,7 @@ int make_console_stdio(void) {
 
         /* Make /dev/console the controlling terminal and stdin/stdout/stderr */
 
-        fd = acquire_terminal("/dev/console", false, true, true, (usec_t) -1);
+        fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY);
         if (fd < 0) {
                 log_error("Failed to acquire terminal: %s", strerror(-fd));
                 return fd;
@@ -5334,13 +5456,14 @@ bool filename_is_safe(const char *p) {
 bool string_is_safe(const char *p) {
         const char *t;
 
-        assert(p);
+        if (!p)
+                return false;
 
         for (t = p; *t; t++) {
                 if (*t > 0 && *t < ' ')
                         return false;
 
-                if (strchr("\\\"\'", *t))
+                if (strchr("\\\"\'\0x7f", *t))
                         return false;
         }
 
@@ -5348,18 +5471,25 @@ bool string_is_safe(const char *p) {
 }
 
 /**
- * Check if a string contains control characters.
- * Spaces and tabs are not considered control characters.
+ * Check if a string contains control characters. If 'ok' is non-NULL
+ * it may be a string containing additional CCs to be considered OK.
  */
-bool string_has_cc(const char *p) {
+bool string_has_cc(const char *p, const char *ok) {
         const char *t;
 
         assert(p);
 
-        for (t = p; *t; t++)
-                if (*t > 0 && *t < ' ' && *t != '\t')
+        for (t = p; *t; t++) {
+                if (ok && strchr(ok, *t))
+                        continue;
+
+                if (*t > 0 && *t < ' ')
                         return true;
 
+                if (*t == 127)
+                        return true;
+        }
+
         return false;
 }
 
@@ -5960,7 +6090,7 @@ int split_pair(const char *s, const char *sep, char **l, char **r) {
 
 int shall_restore_state(void) {
         _cleanup_free_ char *line = NULL;
-        char *w, *state;
+        const char *word, *state;
         size_t l;
         int r;
 
@@ -5972,12 +6102,12 @@ int shall_restore_state(void) {
 
         r = 1;
 
-        FOREACH_WORD_QUOTED(w, l, line, state) {
+        FOREACH_WORD_QUOTED(word, l, line, state) {
                 const char *e;
                 char n[l+1];
                 int k;
 
-                memcpy(n, w, l);
+                memcpy(n, word, l);
                 n[l] = 0;
 
                 e = startswith(n, "systemd.restore_state=");
@@ -6021,7 +6151,7 @@ int proc_cmdline(char **ret) {
 
 int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) {
         _cleanup_free_ char *line = NULL;
-        char *w, *state;
+        const char *w, *state;
         size_t l;
         int r;
 
@@ -6732,3 +6862,58 @@ char *tempfn_random(const char *p) {
 
         return t;
 }
+
+/* make sure the hostname is not "localhost" */
+bool is_localhost(const char *hostname) {
+        assert(hostname);
+
+        /* This tries to identify local hostnames described in RFC6761
+         * plus the redhatism of .localdomain */
+
+        return streq(hostname, "localhost") ||
+               streq(hostname, "localhost.") ||
+               endswith(hostname, ".localhost") ||
+               endswith(hostname, ".localhost.") ||
+               endswith(hostname, ".localdomain") ||
+               endswith(hostname, ".localdomain.");
+}
+
+int take_password_lock(const char *root) {
+
+        struct flock flock = {
+                .l_type = F_WRLCK,
+                .l_whence = SEEK_SET,
+                .l_start = 0,
+                .l_len = 0,
+        };
+
+        const char *path;
+        int fd, r;
+
+        /* This is roughly the same as lckpwdf(), but not as awful. We
+         * don't want to use alarm() and signals, hence we implement
+         * our own trivial version of this.
+         *
+         * Note that shadow-utils also takes per-database locks in
+         * addition to lckpwdf(). However, we don't given that they
+         * are redundant as they they invoke lckpwdf() first and keep
+         * it during everything they do. The per-database locks are
+         * awfully racy, and thus we just won't do them. */
+
+        if (root)
+                path = strappenda(root, "/etc/.pwd.lock");
+        else
+                path = "/etc/.pwd.lock";
+
+        fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
+        if (fd < 0)
+                return -errno;
+
+        r = fcntl(fd, F_SETLKW, &flock);
+        if (r < 0) {
+                safe_close(fd);
+                return -errno;
+        }
+
+        return fd;
+}