+char *replace_env(const char *format, char **env) {
+ enum {
+ WORD,
+ CURLY,
+ VARIABLE
+ } state = WORD;
+
+ const char *e, *word = format;
+ char *r = NULL, *k;
+
+ assert(format);
+
+ for (e = format; *e; e ++) {
+
+ switch (state) {
+
+ case WORD:
+ if (*e == '$')
+ state = CURLY;
+ break;
+
+ case CURLY:
+ if (*e == '{') {
+ if (!(k = strnappend(r, word, e-word-1)))
+ goto fail;
+
+ free(r);
+ r = k;
+
+ word = e-1;
+ state = VARIABLE;
+
+ } else if (*e == '$') {
+ if (!(k = strnappend(r, word, e-word)))
+ goto fail;
+
+ free(r);
+ r = k;
+
+ word = e+1;
+ state = WORD;
+ } else
+ state = WORD;
+ break;
+
+ case VARIABLE:
+ if (*e == '}') {
+ const char *t;
+
+ if (!(t = strv_env_get_with_length(env, word+2, e-word-2)))
+ t = "";
+
+ if (!(k = strappend(r, t)))
+ goto fail;
+
+ free(r);
+ r = k;
+
+ word = e+1;
+ state = WORD;
+ }
+ break;
+ }
+ }
+
+ if (!(k = strnappend(r, word, e-word)))
+ goto fail;
+
+ free(r);
+ return k;
+
+fail:
+ free(r);
+ return NULL;
+}
+
+char **replace_env_argv(char **argv, char **env) {
+ char **r, **i;
+ unsigned k = 0, l = 0;
+
+ l = strv_length(argv);
+
+ if (!(r = new(char*, l+1)))
+ return NULL;
+
+ STRV_FOREACH(i, argv) {
+
+ /* If $FOO appears as single word, replace it by the split up variable */
+ if ((*i)[0] == '$' && (*i)[1] != '{') {
+ char *e;
+ char **w, **m;
+ unsigned q;
+
+ if ((e = strv_env_get(env, *i+1))) {
+
+ if (!(m = strv_split_quoted(e))) {
+ r[k] = NULL;
+ strv_free(r);
+ return NULL;
+ }
+ } else
+ m = NULL;
+
+ q = strv_length(m);
+ l = l + q - 1;
+
+ if (!(w = realloc(r, sizeof(char*) * (l+1)))) {
+ r[k] = NULL;
+ strv_free(r);
+ strv_free(m);
+ return NULL;
+ }
+
+ r = w;
+ if (m) {
+ memcpy(r + k, m, q * sizeof(char*));
+ free(m);
+ }
+
+ k += q;
+ continue;
+ }
+
+ /* 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);
+ return NULL;
+ }
+ }
+
+ r[k] = NULL;
+ return r;
+}
+
+int columns(void) {
+ static __thread int parsed_columns = 0;
+ const char *e;
+
+ if (parsed_columns > 0)
+ return parsed_columns;
+
+ if ((e = getenv("COLUMNS")))
+ parsed_columns = atoi(e);
+
+ if (parsed_columns <= 0) {
+ struct winsize ws;
+ zero(ws);
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
+ parsed_columns = ws.ws_col;
+ }
+
+ if (parsed_columns <= 0)
+ parsed_columns = 80;
+
+ return parsed_columns;
+}
+
+int running_in_chroot(void) {
+ struct stat a, b;
+
+ zero(a);
+ zero(b);
+
+ /* Only works as root */
+
+ if (stat("/proc/1/root", &a) < 0)
+ return -errno;
+
+ if (stat("/", &b) < 0)
+ return -errno;
+
+ return
+ a.st_dev != b.st_dev ||
+ a.st_ino != b.st_ino;
+}
+
+char *ellipsize(const char *s, unsigned length, unsigned percent) {
+ size_t l, x;
+ char *r;
+
+ assert(s);
+ assert(percent <= 100);
+ assert(length >= 3);
+
+ l = strlen(s);
+
+ if (l <= 3 || l <= length)
+ return strdup(s);
+
+ if (!(r = new0(char, length+1)))
+ return r;
+
+ x = (length * percent) / 100;
+
+ if (x > length - 3)
+ x = length - 3;
+
+ memcpy(r, s, x);
+ r[x] = '.';
+ r[x+1] = '.';
+ r[x+2] = '.';
+ memcpy(r + x + 3,
+ s + l - (length - x - 3),
+ length - x - 3);
+
+ return r;
+}
+
+int touch(const char *path) {
+ int fd;
+
+ assert(path);
+
+ if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0666)) < 0)
+ return -errno;
+
+ close_nointr_nofail(fd);
+ return 0;
+}
+
+char *unquote(const char *s, const char* quotes) {
+ size_t l;
+ assert(s);
+
+ if ((l = strlen(s)) < 2)
+ return strdup(s);
+
+ if (strchr(quotes, s[0]) && s[l-1] == s[0])
+ return strndup(s+1, l-2);
+
+ return strdup(s);
+}
+
+int wait_for_terminate(pid_t pid, siginfo_t *status) {
+ assert(pid >= 1);
+ assert(status);
+
+ for (;;) {
+ zero(*status);
+
+ if (waitid(P_PID, pid, status, WEXITED) < 0) {
+
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ }
+
+ return 0;
+ }
+}
+
+int wait_for_terminate_and_warn(const char *name, pid_t pid) {
+ int r;
+ siginfo_t status;
+
+ assert(name);
+ assert(pid > 1);
+
+ if ((r = wait_for_terminate(pid, &status)) < 0) {
+ log_warning("Failed to wait for %s: %s", name, strerror(-r));
+ return r;
+ }
+
+ if (status.si_code == CLD_EXITED) {
+ if (status.si_status != 0) {
+ log_warning("%s failed with error code %i.", name, status.si_status);
+ return -EPROTO;
+ }
+
+ log_debug("%s succeeded.", name);
+ return 0;
+
+ } else if (status.si_code == CLD_KILLED ||
+ status.si_code == CLD_DUMPED) {
+
+ log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status));
+ return -EPROTO;
+ }
+
+ log_warning("%s failed due to unknown reason.", name);
+ return -EPROTO;
+
+}
+