+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);
+}
+
+char *normalize_env_assignment(const char *s) {
+ char *name, *value, *p, *r;
+
+ p = strchr(s, '=');
+
+ if (!p) {
+ if (!(r = strdup(s)))
+ return NULL;
+
+ return strstrip(r);
+ }
+
+ if (!(name = strndup(s, p - s)))
+ return NULL;
+
+ if (!(p = strdup(p+1))) {
+ free(name);
+ return NULL;
+ }
+
+ value = unquote(strstrip(p), QUOTES);
+ free(p);
+
+ if (!value) {
+ free(p);
+ free(name);
+ return NULL;
+ }
+
+ if (asprintf(&r, "%s=%s", name, value) < 0)
+ r = NULL;
+
+ free(value);
+ free(name);
+
+ return r;
+}
+
+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;
+
+}
+
+void freeze(void) {
+ sync();
+
+ for (;;)
+ pause();
+}
+
+bool null_or_empty(struct stat *st) {
+ assert(st);
+
+ if (S_ISREG(st->st_mode) && st->st_size <= 0)
+ return true;
+
+ if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
+ return true;
+
+ return false;
+}
+
+DIR *xopendirat(int fd, const char *name, int flags) {
+ int nfd;
+ DIR *d;
+
+ if ((nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags)) < 0)
+ return NULL;
+
+ if (!(d = fdopendir(nfd))) {
+ close_nointr_nofail(nfd);
+ return NULL;
+ }
+
+ return d;
+}
+
+int signal_from_string_try_harder(const char *s) {
+ int signo;
+ assert(s);
+
+ if ((signo = signal_from_string(s)) <= 0)
+ if (startswith(s, "SIG"))
+ return signal_from_string(s+3);
+
+ return signo;
+}
+
+void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
+
+ assert(f);
+ assert(name);
+ assert(t);
+
+ if (!dual_timestamp_is_set(t))
+ return;
+
+ fprintf(f, "%s=%llu %llu\n",
+ name,
+ (unsigned long long) t->realtime,
+ (unsigned long long) t->monotonic);
+}
+
+void dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
+ unsigned long long a, b;
+
+ assert(value);
+ assert(t);
+
+ if (sscanf(value, "%lli %llu", &a, &b) != 2)
+ log_debug("Failed to parse finish timestamp value %s", value);
+ else {
+ t->realtime = a;
+ t->monotonic = b;
+ }
+}
+
+char *fstab_node_to_udev_node(const char *p) {
+ char *dn, *t, *u;
+ int r;
+
+ /* FIXME: to follow udev's logic 100% we need to leave valid
+ * UTF8 chars unescaped */
+
+ if (startswith(p, "LABEL=")) {
+
+ if (!(u = unquote(p+6, "\"\'")))
+ return NULL;
+
+ t = xescape(u, "/ ");
+ free(u);
+
+ if (!t)
+ return NULL;
+
+ r = asprintf(&dn, "/dev/disk/by-label/%s", t);
+ free(t);
+
+ if (r < 0)
+ return NULL;
+
+ return dn;
+ }
+
+ if (startswith(p, "UUID=")) {
+
+ if (!(u = unquote(p+5, "\"\'")))
+ return NULL;
+
+ t = xescape(u, "/ ");
+ free(u);
+
+ if (!t)
+ return NULL;
+
+ r = asprintf(&dn, "/dev/disk/by-uuid/%s", t);
+ free(t);
+
+ if (r < 0)
+ return NULL;
+
+ return dn;
+ }
+
+ return strdup(p);
+}
+
+void filter_environ(const char *prefix) {
+ int i, j;
+ assert(prefix);
+
+ if (!environ)
+ return;
+
+ for (i = 0, j = 0; environ[i]; i++) {
+
+ if (startswith(environ[i], prefix))
+ continue;
+
+ environ[j++] = environ[i];
+ }
+
+ environ[j] = NULL;
+}
+
+bool tty_is_vc(const char *tty) {
+ assert(tty);
+
+ if (startswith(tty, "/dev/"))
+ tty += 5;
+
+ return startswith(tty, "tty") &&
+ tty[3] >= '0' && tty[3] <= '9';
+}
+
+const char *default_term_for_tty(const char *tty) {
+ char *active = NULL;
+ const char *term;
+
+ assert(tty);
+
+ if (startswith(tty, "/dev/"))
+ tty += 5;
+
+ /* Resolve where /dev/console is pointing when determining
+ * TERM */
+ if (streq(tty, "console"))
+ if (read_one_line_file("/sys/class/tty/console/active", &active) >= 0) {
+ truncate_nl(active);
+
+ /* If multiple log outputs are configured the
+ * last one is what /dev/console points to */
+ if ((tty = strrchr(active, ' ')))
+ tty++;
+ else
+ tty = active;
+ }
+
+ term = tty_is_vc(tty) ? "TERM=linux" : "TERM=vt100";
+ free(active);
+
+ return term;
+}
+
+bool running_in_vm(void) {
+
+#if defined(__i386__) || defined(__x86_64__)
+
+ /* Both CPUID and DMI are x86 specific interfaces... */
+
+ const char *const dmi_vendors[] = {
+ "/sys/class/dmi/id/sys_vendor",
+ "/sys/class/dmi/id/board_vendor",
+ "/sys/class/dmi/id/bios_vendor"
+ };
+
+ uint32_t eax = 0x40000000;
+ union {
+ uint32_t sig32[3];
+ char text[13];
+ } sig;
+
+ unsigned i;
+
+ for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) {
+ char *s;
+ bool b;
+
+ if (read_one_line_file(dmi_vendors[i], &s) < 0)
+ continue;
+
+ b = startswith(s, "QEMU") ||
+ /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
+ startswith(s, "VMware") ||
+ startswith(s, "VMW") ||
+ startswith(s, "Microsoft Corporation") ||
+ startswith(s, "innotek GmbH") ||
+ startswith(s, "Xen");
+
+ free(s);
+
+ if (b)
+ return true;
+ }
+
+ /* http://lwn.net/Articles/301888/ */
+ zero(sig);
+
+
+#if defined (__i386__)
+#define REG_a "eax"
+#define REG_b "ebx"
+#elif defined (__amd64__)
+#define REG_a "rax"
+#define REG_b "rbx"
+#endif
+
+ __asm__ __volatile__ (
+ /* ebx/rbx is being used for PIC! */
+ " push %%"REG_b" \n\t"
+ " cpuid \n\t"
+ " mov %%ebx, %1 \n\t"
+ " pop %%"REG_b" \n\t"
+
+ : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2])
+ : "0" (eax)
+ );
+
+ if (streq(sig.text, "XenVMMXenVMM") ||
+ streq(sig.text, "KVMKVMKVM") ||
+ /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */
+ streq(sig.text, "VMwareVMware") ||
+ /* http://msdn.microsoft.com/en-us/library/bb969719.aspx */
+ streq(sig.text, "Microsoft Hv"))
+ return true;
+#endif
+
+ return false;
+}
+