+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(STDIN_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;
+}
+
+void nss_disable_nscd(void) {
+
+ void (*func)(void);
+
+ /* This is an internal glibc function call. We are not
+ * supposed to call this, because we are not nscd. However
+ * sometimes we feel really dangerous and do it
+ * nonetheless. Muahahah! But at least we protect this with a
+ * dlsym() just in case glibc takes this away from us. */
+
+ if ((func = dlsym(RTLD_DEFAULT, "__nss_disable_nscd"))) {
+ log_debug("Disabling nscd.");
+ func();
+ } else
+ log_debug("Cannot disable nscd.");
+}
+
+