chiark / gitweb /
util: ignore lost+found, too
[elogind.git] / util.c
diff --git a/util.c b/util.c
index 43826990cd702244cb67e5881da6a98e74f2c4b6..eed9aa7f84e598a5d92ed8fac733fd030ec3b695 100644 (file)
--- a/util.c
+++ b/util.c
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <dirent.h>
+#include <sys/ioctl.h>
+#include <linux/vt.h>
+#include <linux/tiocl.h>
+#include <termios.h>
+#include <stdarg.h>
+#include <sys/inotify.h>
+#include <sys/poll.h>
+#include <libgen.h>
 
 #include "macro.h"
 #include "util.h"
 #include "log.h"
 #include "strv.h"
 
+bool streq_ptr(const char *a, const char *b) {
+
+        /* Like streq(), but tries to make sense of NULL pointers */
+
+        if (a && b)
+                return streq(a, b);
+
+        if (!a && !b)
+                return true;
+
+        return false;
+}
+
 usec_t now(clockid_t clock_id) {
         struct timespec ts;
 
@@ -93,6 +114,9 @@ bool endswith(const char *s, const char *postfix) {
         sl = strlen(s);
         pl = strlen(postfix);
 
+        if (pl == 0)
+                return true;
+
         if (sl < pl)
                 return false;
 
@@ -108,6 +132,9 @@ bool startswith(const char *s, const char *prefix) {
         sl = strlen(s);
         pl = strlen(prefix);
 
+        if (pl == 0)
+                return true;
+
         if (sl < pl)
                 return false;
 
@@ -126,11 +153,14 @@ bool first_word(const char *s, const char *word) {
         if (sl < wl)
                 return false;
 
+        if (wl == 0)
+                return true;
+
         if (memcmp(s, word, wl) != 0)
                 return false;
 
-        return (s[wl] == 0 ||
-                strchr(WHITESPACE, s[wl]));
+        return s[wl] == 0 ||
+                strchr(WHITESPACE, s[wl]);
 }
 
 int close_nointr(int fd) {
@@ -148,11 +178,14 @@ int close_nointr(int fd) {
 }
 
 void close_nointr_nofail(int fd) {
+        int saved_errno = errno;
 
         /* like close_nointr() but cannot fail, and guarantees errno
          * is unchanged */
 
         assert_se(close_nointr(fd) == 0);
+
+        errno = saved_errno;
 }
 
 int parse_boolean(const char *v) {
@@ -412,7 +445,7 @@ finish:
 int read_one_line_file(const char *fn, char **line) {
         FILE *f;
         int r;
-        char t[64], *c;
+        char t[2048], *c;
 
         assert(fn);
         assert(line);
@@ -438,6 +471,33 @@ finish:
         return r;
 }
 
+char *truncate_nl(char *s) {
+        assert(s);
+
+        s[strcspn(s, NEWLINE)] = 0;
+        return s;
+}
+
+int get_process_name(pid_t pid, char **name) {
+        char *p;
+        int r;
+
+        assert(pid >= 1);
+        assert(name);
+
+        if (asprintf(&p, "/proc/%llu/comm", (unsigned long long) pid) < 0)
+                return -ENOMEM;
+
+        r = read_one_line_file(p, name);
+        free(p);
+
+        if (r < 0)
+                return r;
+
+        truncate_nl(*name);
+        return 0;
+}
+
 char *strappend(const char *s, const char *suffix) {
         size_t a, b;
         char *r;
@@ -608,7 +668,23 @@ char *strstrip(char *s) {
                 *s = 0;
 
         return s;
+}
+
+char *delete_chars(char *s, const char *bad) {
+        char *f, *t;
+
+        /* Drops all whitespace, regardless where in the string */
 
+        for (f = s, t = s; *f; f++) {
+                if (strchr(bad, *f))
+                        continue;
+
+                *(t++) = *f;
+        }
+
+        *t = 0;
+
+        return s;
 }
 
 char *file_in_same_dir(const char *path, const char *filename) {
@@ -671,6 +747,20 @@ int mkdir_parents(const char *path, mode_t mode) {
         }
 }
 
+int mkdir_p(const char *path, mode_t mode) {
+        int r;
+
+        /* Like mkdir -p */
+
+        if ((r = mkdir_parents(path, mode)) < 0)
+                return r;
+
+        if (mkdir(path, mode) < 0)
+                return -errno;
+
+        return 0;
+}
+
 char hexchar(int x) {
         static const char table[16] = "0123456789abcdef";
 
@@ -961,16 +1051,15 @@ char *bus_path_escape(const char *s) {
         return r;
 }
 
-char *bus_path_unescape(const char *s) {
+char *bus_path_unescape(const char *f) {
         char *r, *t;
-        const char *f;
 
-        assert(s);
+        assert(f);
 
-        if (!(r = new(char, strlen(s)+1)))
+        if (!(r = strdup(f)))
                 return NULL;
 
-        for (f = s, t = r; *f; f++) {
+        for (t = r; *f; f++) {
 
                 if (*f == '_') {
                         int a, b;
@@ -1060,6 +1149,39 @@ bool path_startswith(const char *path, const char *prefix) {
         }
 }
 
+bool path_equal(const char *a, const char *b) {
+        assert(a);
+        assert(b);
+
+        if ((a[0] == '/') != (b[0] == '/'))
+                return false;
+
+        for (;;) {
+                size_t j, k;
+
+                a += strspn(a, "/");
+                b += strspn(b, "/");
+
+                if (*a == 0 && *b == 0)
+                        return true;
+
+                if (*a == 0 || *b == 0)
+                        return false;
+
+                j = strcspn(a, "/");
+                k = strcspn(b, "/");
+
+                if (j != k)
+                        return false;
+
+                if (memcmp(a, b, j) != 0)
+                        return false;
+
+                a += j;
+                b += k;
+        }
+}
+
 char *ascii_strlower(char *t) {
         char *p;
 
@@ -1077,6 +1199,7 @@ bool ignore_file(const char *filename) {
 
         return
                 filename[0] == '.' ||
+                streq(filename, "lost+found") ||
                 endswith(filename, "~") ||
                 endswith(filename, ".rpmnew") ||
                 endswith(filename, ".rpmsave") ||
@@ -1135,7 +1258,7 @@ int close_all_fds(const int except[], unsigned n_except) {
         while ((de = readdir(d))) {
                 int fd = -1;
 
-                if (de->d_name[0] == '.')
+                if (ignore_file(de->d_name))
                         continue;
 
                 if ((r = safe_atoi(de->d_name, &fd)) < 0)
@@ -1176,6 +1299,571 @@ finish:
         return r;
 }
 
+bool chars_intersect(const char *a, const char *b) {
+        const char *p;
+
+        /* Returns true if any of the chars in a are in b. */
+        for (p = a; *p; p++)
+                if (strchr(b, *p))
+                        return true;
+
+        return false;
+}
+
+char *format_timestamp(char *buf, size_t l, usec_t t) {
+        struct tm tm;
+        time_t sec;
+
+        assert(buf);
+        assert(l > 0);
+
+        if (t <= 0)
+                return NULL;
+
+        sec = (time_t) t / USEC_PER_SEC;
+
+        if (strftime(buf, l, "%a, %d %b %Y %H:%M:%S %z", localtime_r(&sec, &tm)) <= 0)
+                return NULL;
+
+        return buf;
+}
+
+bool fstype_is_network(const char *fstype) {
+        static const char * const table[] = {
+                "cifs",
+                "smbfs",
+                "ncpfs",
+                "nfs",
+                "nfs4"
+        };
+
+        unsigned i;
+
+        for (i = 0; i < ELEMENTSOF(table); i++)
+                if (streq(table[i], fstype))
+                        return true;
+
+        return false;
+}
+
+int chvt(int vt) {
+        int fd, r = 0;
+
+        if ((fd = open("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0)
+                return -errno;
+
+        if (vt < 0) {
+                int tiocl[2] = {
+                        TIOCL_GETKMSGREDIRECT,
+                        0
+                };
+
+                if (ioctl(fd, TIOCLINUX, tiocl) < 0)
+                        return -errno;
+
+                vt = tiocl[0] <= 0 ? 1 : tiocl[0];
+        }
+
+        if (ioctl(fd, VT_ACTIVATE, vt) < 0)
+                r = -errno;
+
+        close_nointr_nofail(r);
+        return r;
+}
+
+int read_one_char(FILE *f, char *ret, bool *need_nl) {
+        struct termios old_termios, new_termios;
+        char c;
+        char line[1024];
+
+        assert(f);
+        assert(ret);
+
+        if (tcgetattr(fileno(f), &old_termios) >= 0) {
+                new_termios = old_termios;
+
+                new_termios.c_lflag &= ~ICANON;
+                new_termios.c_cc[VMIN] = 1;
+                new_termios.c_cc[VTIME] = 0;
+
+                if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) {
+                        size_t k;
+
+                        k = fread(&c, 1, 1, f);
+
+                        tcsetattr(fileno(f), TCSADRAIN, &old_termios);
+
+                        if (k <= 0)
+                                return -EIO;
+
+                        if (need_nl)
+                                *need_nl = c != '\n';
+
+                        *ret = c;
+                        return 0;
+                }
+        }
+
+        if (!(fgets(line, sizeof(line), f)))
+                return -EIO;
+
+        truncate_nl(line);
+
+        if (strlen(line) != 1)
+                return -EBADMSG;
+
+        if (need_nl)
+                *need_nl = false;
+
+        *ret = line[0];
+        return 0;
+}
+
+int ask(char *ret, const char *replies, const char *text, ...) {
+        assert(ret);
+        assert(replies);
+        assert(text);
+
+        for (;;) {
+                va_list ap;
+                char c;
+                int r;
+                bool need_nl = true;
+
+                fputs("\x1B[1m", stdout);
+
+                va_start(ap, text);
+                vprintf(text, ap);
+                va_end(ap);
+
+                fputs("\x1B[0m", stdout);
+
+                fflush(stdout);
+
+                if ((r = read_one_char(stdin, &c, &need_nl)) < 0) {
+
+                        if (r == -EBADMSG) {
+                                puts("Bad input, please try again.");
+                                continue;
+                        }
+
+                        putchar('\n');
+                        return r;
+                }
+
+                if (need_nl)
+                        putchar('\n');
+
+                if (strchr(replies, c)) {
+                        *ret = c;
+                        return 0;
+                }
+
+                puts("Read unexpected character, please try again.");
+        }
+}
+
+int reset_terminal(int fd) {
+        struct termios termios;
+        int r = 0;
+
+        assert(fd >= 0);
+
+        /* Set terminal to some sane defaults */
+
+        if (tcgetattr(fd, &termios) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        /* We only reset the stuff that matters to the software. How
+         * hardware is set up we don't touch assuming that somebody
+         * else will do that for us */
+
+        termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC);
+        termios.c_iflag |= ICRNL | IMAXBEL | IUTF8;
+        termios.c_oflag |= ONLCR;
+        termios.c_cflag |= CREAD;
+        termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE;
+
+        termios.c_cc[VINTR]    =   03;  /* ^C */
+        termios.c_cc[VQUIT]    =  034;  /* ^\ */
+        termios.c_cc[VERASE]   = 0177;
+        termios.c_cc[VKILL]    =  025;  /* ^X */
+        termios.c_cc[VEOF]     =   04;  /* ^D */
+        termios.c_cc[VSTART]   =  021;  /* ^Q */
+        termios.c_cc[VSTOP]    =  023;  /* ^S */
+        termios.c_cc[VSUSP]    =  032;  /* ^Z */
+        termios.c_cc[VLNEXT]   =  026;  /* ^V */
+        termios.c_cc[VWERASE]  =  027;  /* ^W */
+        termios.c_cc[VREPRINT] =  022;  /* ^R */
+        termios.c_cc[VEOL]     =    0;
+        termios.c_cc[VEOL2]    =    0;
+
+        termios.c_cc[VTIME]  = 0;
+        termios.c_cc[VMIN]   = 1;
+
+        if (tcsetattr(fd, TCSANOW, &termios) < 0)
+                r = -errno;
+
+finish:
+        /* Just in case, flush all crap out */
+        tcflush(fd, TCIOFLUSH);
+
+        return r;
+}
+
+int open_terminal(const char *name, int mode) {
+        int fd, r;
+
+        if ((fd = open(name, mode)) < 0)
+                return -errno;
+
+        if ((r = isatty(fd)) < 0) {
+                close_nointr_nofail(fd);
+                return -errno;
+        }
+
+        if (!r) {
+                close_nointr_nofail(fd);
+                return -ENOTTY;
+        }
+
+        return fd;
+}
+
+int flush_fd(int fd) {
+        struct pollfd pollfd;
+
+        zero(pollfd);
+        pollfd.fd = fd;
+        pollfd.events = POLLIN;
+
+        for (;;) {
+                char buf[1024];
+                ssize_t l;
+                int r;
+
+                if ((r = poll(&pollfd, 1, 0)) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        return -errno;
+                }
+
+                if (r == 0)
+                        return 0;
+
+                if ((l = read(fd, buf, sizeof(buf))) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        if (errno == EAGAIN)
+                                return 0;
+
+                        return -errno;
+                }
+
+                if (l <= 0)
+                        return 0;
+        }
+}
+
+int acquire_terminal(const char *name, bool fail, bool force) {
+        int fd = -1, notify = -1, r, wd;
+
+        assert(name);
+
+        /* We use inotify to be notified when the tty is closed. We
+         * create the watch before checking if we can actually acquire
+         * it, so that we don't lose any event.
+         *
+         * Note: strictly speaking this actually watches for the
+         * device being closed, it does *not* really watch whether a
+         * tty loses its controlling process. However, unless some
+         * rogue process uses TIOCNOTTY on /dev/tty *after* closing
+         * its tty otherwise this will not become a problem. As long
+         * as the administrator makes sure not configure any service
+         * on the same tty as an untrusted user this should not be a
+         * problem. (Which he probably should not do anyway.) */
+
+        if (!fail && !force) {
+                if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+
+                if ((wd = inotify_add_watch(notify, name, IN_CLOSE)) < 0) {
+                        r = -errno;
+                        goto fail;
+                }
+        }
+
+        for (;;) {
+                if ((r = flush_fd(notify)) < 0)
+                        goto fail;
+
+                /* We pass here O_NOCTTY only so that we can check the return
+                 * value TIOCSCTTY and have a reliable way to figure out if we
+                 * successfully became the controlling process of the tty */
+                if ((fd = open_terminal(name, O_RDWR|O_NOCTTY)) < 0)
+                        return -errno;
+
+                /* First, try to get the tty */
+                if ((r = ioctl(fd, TIOCSCTTY, force)) < 0 &&
+                    (force || fail || errno != EPERM)) {
+                        r = -errno;
+                        goto fail;
+                }
+
+                if (r >= 0)
+                        break;
+
+                assert(!fail);
+                assert(!force);
+                assert(notify >= 0);
+
+                for (;;) {
+                        struct inotify_event e;
+                        ssize_t l;
+
+                        if ((l = read(notify, &e, sizeof(e))) != sizeof(e)) {
+
+                                if (l < 0) {
+
+                                        if (errno == EINTR)
+                                                continue;
+
+                                        r = -errno;
+                                } else
+                                        r = -EIO;
+
+                                goto fail;
+                        }
+
+                        if (e.wd != wd || !(e.mask & IN_CLOSE)) {
+                                r = -errno;
+                                goto fail;
+                        }
+
+                        break;
+                }
+
+                /* We close the tty fd here since if the old session
+                 * ended our handle will be dead. It's important that
+                 * we do this after sleeping, so that we don't enter
+                 * an endless loop. */
+                close_nointr_nofail(fd);
+        }
+
+        if (notify >= 0)
+                close_nointr_nofail(notify);
+
+        if ((r = reset_terminal(fd)) < 0)
+                log_warning("Failed to reset terminal: %s", strerror(-r));
+
+        return fd;
+
+fail:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        if (notify >= 0)
+                close_nointr_nofail(notify);
+
+        return r;
+}
+
+int release_terminal(void) {
+        int r = 0, fd;
+        struct sigaction sa_old, sa_new;
+
+        if ((fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_NDELAY)) < 0)
+                return -errno;
+
+        /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed
+         * by our own TIOCNOTTY */
+
+        zero(sa_new);
+        sa_new.sa_handler = SIG_IGN;
+        sa_new.sa_flags = SA_RESTART;
+        assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0);
+
+        if (ioctl(fd, TIOCNOTTY) < 0)
+                r = -errno;
+
+        assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0);
+
+        close_nointr_nofail(fd);
+        return r;
+}
+
+int ignore_signal(int sig) {
+        struct sigaction sa;
+
+        zero(sa);
+        sa.sa_handler = SIG_IGN;
+        sa.sa_flags = SA_RESTART;
+
+        return sigaction(sig, &sa, NULL);
+}
+
+int close_pipe(int p[]) {
+        int a = 0, b = 0;
+
+        assert(p);
+
+        if (p[0] >= 0) {
+                a = close_nointr(p[0]);
+                p[0] = -1;
+        }
+
+        if (p[1] >= 0) {
+                b = close_nointr(p[1]);
+                p[1] = -1;
+        }
+
+        return a < 0 ? a : b;
+}
+
+ssize_t loop_read(int fd, void *buf, size_t nbytes) {
+        uint8_t *p;
+        ssize_t n = 0;
+
+        assert(fd >= 0);
+        assert(buf);
+
+        p = buf;
+
+        while (nbytes > 0) {
+                ssize_t k;
+
+                if ((k = read(fd, p, nbytes)) <= 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        if (errno == EAGAIN) {
+                                struct pollfd pollfd;
+
+                                zero(pollfd);
+                                pollfd.fd = fd;
+                                pollfd.events = POLLIN;
+
+                                if (poll(&pollfd, 1, -1) < 0) {
+                                        if (errno == EINTR)
+                                                continue;
+
+                                        return n > 0 ? n : -errno;
+                                }
+
+                                if (pollfd.revents != POLLIN)
+                                        return n > 0 ? n : -EIO;
+
+                                continue;
+                        }
+
+                        return n > 0 ? n : (k < 0 ? -errno : 0);
+                }
+
+                p += k;
+                nbytes -= k;
+                n += k;
+        }
+
+        return n;
+}
+
+int path_is_mount_point(const char *t) {
+        struct stat a, b;
+        char *copy;
+
+        if (lstat(t, &a) < 0) {
+
+                if (errno == ENOENT)
+                        return 0;
+
+                return -errno;
+        }
+
+        if (!(copy = strdup(t)))
+                return -ENOMEM;
+
+        if (lstat(dirname(copy), &b) < 0) {
+                free(copy);
+                return -errno;
+        }
+
+        free(copy);
+
+        return a.st_dev != b.st_dev;
+}
+
+int parse_usec(const char *t, usec_t *usec) {
+        static const struct {
+                const char *suffix;
+                usec_t usec;
+        } table[] = {
+                { "sec", USEC_PER_SEC },
+                { "s", USEC_PER_SEC },
+                { "min", USEC_PER_MINUTE },
+                { "hr", USEC_PER_HOUR },
+                { "h", USEC_PER_HOUR },
+                { "d", USEC_PER_DAY },
+                { "w", USEC_PER_WEEK },
+                { "msec", USEC_PER_MSEC },
+                { "ms", USEC_PER_MSEC },
+                { "m", USEC_PER_MINUTE },
+                { "usec", 1ULL },
+                { "us", 1ULL },
+                { "", USEC_PER_SEC },
+        };
+
+        const char *p;
+        usec_t r = 0;
+
+        assert(t);
+        assert(usec);
+
+        p = t;
+        do {
+                long long l;
+                char *e;
+                unsigned i;
+
+                errno = 0;
+                l = strtoll(p, &e, 10);
+
+                if (errno != 0)
+                        return -errno;
+
+                if (l < 0)
+                        return -ERANGE;
+
+                if (e == p)
+                        return -EINVAL;
+
+                e += strspn(e, WHITESPACE);
+
+                for (i = 0; i < ELEMENTSOF(table); i++)
+                        if (startswith(e, table[i].suffix)) {
+                                r += (usec_t) l * table[i].usec;
+                                p = e + strlen(table[i].suffix);
+                                break;
+                        }
+
+                if (i >= ELEMENTSOF(table))
+                        return -EINVAL;
+
+        } while (*p != 0);
+
+        *usec = r;
+
+        return 0;
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",