chiark / gitweb /
util: replace RUN_WITH_LOCALE with extended locale functions
[elogind.git] / src / shared / util.c
index 06bd1b9f04b4fdc4714d6c5d0ae7a872b5ecf93b..8f6d5e660c657356a429d115c17823f3412b2945 100644 (file)
@@ -62,6 +62,7 @@
 #include <sys/xattr.h>
 #include <libgen.h>
 #include <sys/statvfs.h>
+#include <sys/file.h>
 #include <linux/fs.h>
 #undef basename
 
@@ -506,18 +507,24 @@ int safe_atolli(const char *s, long long int *ret_lli) {
 int safe_atod(const char *s, double *ret_d) {
         char *x = NULL;
         double d = 0;
+        locale_t loc;
 
         assert(s);
         assert(ret_d);
 
-        RUN_WITH_LOCALE(LC_NUMERIC_MASK, "C") {
-                errno = 0;
-                d = strtod(s, &x);
-        }
+        loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0);
+        if (loc == (locale_t) 0)
+                return -errno;
 
-        if (!x || x == s || *x || errno)
+        errno = 0;
+        d = strtod_l(s, &x, loc);
+
+        if (!x || x == s || *x || errno) {
+                freelocale(loc);
                 return errno ? -errno : -EINVAL;
+        }
 
+        freelocale(loc);
         *ret_d = (double) d;
         return 0;
 }
@@ -1352,12 +1359,19 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre
                 memcpy(r, prefix, pl);
 
         for (f = s, t = r + pl; f < s + length; f++) {
+                size_t remaining = s + length - f;
+                assert(remaining > 0);
 
-                if (*f != '\\') {
+                if (*f != '\\') {        /* a literal literal */
                         *(t++) = *f;
                         continue;
                 }
 
+                if (--remaining == 0) {  /* copy trailing backslash verbatim */
+                        *(t++) = *f;
+                        break;
+                }
+
                 f++;
 
                 switch (*f) {
@@ -1400,10 +1414,12 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre
 
                 case 'x': {
                         /* hexadecimal encoding */
-                        int a, b;
+                        int a = -1, b = -1;
 
-                        a = unhexchar(f[1]);
-                        b = unhexchar(f[2]);
+                        if (remaining >= 2) {
+                                a = unhexchar(f[1]);
+                                b = unhexchar(f[2]);
+                        }
 
                         if (a < 0 || b < 0 || (a == 0 && b == 0)) {
                                 /* Invalid escape code, let's take it literal then */
@@ -1426,11 +1442,13 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre
                 case '6':
                 case '7': {
                         /* octal encoding */
-                        int a, b, c;
+                        int a = -1, b = -1, c = -1;
 
-                        a = unoctchar(f[0]);
-                        b = unoctchar(f[1]);
-                        c = unoctchar(f[2]);
+                        if (remaining >= 3) {
+                                a = unoctchar(f[0]);
+                                b = unoctchar(f[1]);
+                                c = unoctchar(f[2]);
+                        }
 
                         if (a < 0 || b < 0 || c < 0 || (a == 0 && b == 0 && c == 0)) {
                                 /* Invalid escape code, let's take it literal then */
@@ -1444,11 +1462,6 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre
                         break;
                 }
 
-                case 0:
-                        /* premature end of string. */
-                        *(t++) = '\\';
-                        goto finish;
-
                 default:
                         /* Invalid escape code, let's take it literal then */
                         *(t++) = '\\';
@@ -1457,7 +1470,6 @@ char *cunescape_length_with_prefix(const char *s, size_t length, const char *pre
                 }
         }
 
-finish:
         *t = 0;
         return r;
 }
@@ -4037,10 +4049,10 @@ bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) {
         return endswith(de->d_name, suffix);
 }
 
-static int do_execute(const char *directory, usec_t timeout, char *argv[]) {
+static int do_execute(char **directories, usec_t timeout, char *argv[]) {
         _cleanup_hashmap_free_free_ Hashmap *pids = NULL;
-        _cleanup_closedir_ DIR *d;
-        struct dirent *de;
+        _cleanup_set_free_free_ Set *seen = NULL;
+        char **directory;
 
         /* We fork this all off from a child process so that we can
          * somewhat cleanly make use of SIGALRM to set a time limit */
@@ -4050,57 +4062,80 @@ static int do_execute(const char *directory, usec_t timeout, char *argv[]) {
 
         assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
 
-        d = opendir(directory);
-        if (!d) {
-                if (errno == ENOENT)
-                        return 0;
-
-                return log_error_errno(errno, "Failed to open directory %s: %m", directory);
-        }
-
         pids = hashmap_new(NULL);
         if (!pids)
                 return log_oom();
 
-        FOREACH_DIRENT(de, d, break) {
-                _cleanup_free_ char *path = NULL;
-                pid_t pid;
-                int r;
+        seen = set_new(&string_hash_ops);
+        if (!seen)
+                return log_oom();
 
-                if (!dirent_is_file(de))
-                        continue;
+        STRV_FOREACH(directory, directories) {
+                _cleanup_closedir_ DIR *d;
+                struct dirent *de;
 
-                path = strjoin(directory, "/", de->d_name, NULL);
-                if (!path)
-                        return log_oom();
+                d = opendir(*directory);
+                if (!d) {
+                        if (errno == ENOENT)
+                                continue;
 
-                pid = fork();
-                if (pid < 0) {
-                        log_error_errno(errno, "Failed to fork: %m");
-                        continue;
-                } else if (pid == 0) {
-                        char *_argv[2];
+                        return log_error_errno(errno, "Failed to open directory %s: %m", *directory);
+                }
+
+                FOREACH_DIRENT(de, d, break) {
+                        _cleanup_free_ char *path = NULL;
+                        pid_t pid;
+                        int r;
 
-                        assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+                        if (!dirent_is_file(de))
+                                continue;
 
-                        if (!argv) {
-                                _argv[0] = path;
-                                _argv[1] = NULL;
-                                argv = _argv;
+                        if (set_contains(seen, de->d_name)) {
+                                log_debug("%1$s/%2$s skipped (%2$s was already seen).", *directory, de->d_name);
+                                continue;
+                        }
+
+                        r = set_put_strdup(seen, de->d_name);
+                        if (r < 0)
+                                return log_oom();
+
+                        path = strjoin(*directory, "/", de->d_name, NULL);
+                        if (!path)
+                                return log_oom();
+
+                        if (null_or_empty_path(path)) {
+                                log_debug("%s is empty (a mask).", path);
+                                continue;
                         } else
-                                argv[0] = path;
+                                log_debug("%s will be executed.", path);
 
-                        execv(path, argv);
-                        return log_error_errno(errno, "Failed to execute %s: %m", path);
-                }
+                        pid = fork();
+                        if (pid < 0) {
+                                log_error_errno(errno, "Failed to fork: %m");
+                                continue;
+                        } else if (pid == 0) {
+                                char *_argv[2];
 
-                log_debug("Spawned %s as " PID_FMT ".", path, pid);
+                                assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
 
-                r = hashmap_put(pids, UINT_TO_PTR(pid), path);
-                if (r < 0)
-                        return log_oom();
+                                if (!argv) {
+                                        _argv[0] = path;
+                                        _argv[1] = NULL;
+                                        argv = _argv;
+                                } else
+                                        argv[0] = path;
+
+                                execv(path, argv);
+                                return log_error_errno(errno, "Failed to execute %s: %m", path);
+                        }
 
-                path = NULL;
+                        log_debug("Spawned %s as " PID_FMT ".", path, pid);
+
+                        r = hashmap_put(pids, UINT_TO_PTR(pid), path);
+                        if (r < 0)
+                                return log_oom();
+                        path = NULL;
+                }
         }
 
         /* Abort execution of this process after the timout. We simply
@@ -4126,14 +4161,21 @@ static int do_execute(const char *directory, usec_t timeout, char *argv[]) {
         return 0;
 }
 
-void execute_directory(const char *directory, usec_t timeout, char *argv[]) {
+void execute_directories(const char* const* directories, usec_t timeout, char *argv[]) {
         pid_t executor_pid;
         int r;
+        char *name;
+        char **dirs = (char**) directories;
+
+        assert(!strv_isempty(dirs));
 
-        assert(directory);
+        name = basename(dirs[0]);
+        assert(!isempty(name));
 
-        /* Executes all binaries in the directory in parallel and waits
-         * for them to finish. Optionally a timeout is applied. */
+        /* Executes all binaries in the directories in parallel and waits
+         * for them to finish. Optionally a timeout is applied. If a file
+         * with the same name exists in more than one directory, the
+         * earliest one wins. */
 
         executor_pid = fork();
         if (executor_pid < 0) {
@@ -4141,11 +4183,11 @@ void execute_directory(const char *directory, usec_t timeout, char *argv[]) {
                 return;
 
         } else if (executor_pid == 0) {
-                r = do_execute(directory, timeout, argv);
+                r = do_execute(dirs, timeout, argv);
                 _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
         }
 
-        wait_for_terminate_and_warn(directory, executor_pid, true);
+        wait_for_terminate_and_warn(name, executor_pid, true);
 }
 
 int kill_and_sigcont(pid_t pid, int sig) {
@@ -4268,23 +4310,6 @@ bool machine_name_is_valid(const char *s) {
         return true;
 }
 
-bool image_name_is_valid(const char *s) {
-        if (!filename_is_valid(s))
-                return false;
-
-        if (string_has_cc(s, NULL))
-                return false;
-
-        if (!utf8_is_valid(s))
-                return false;
-
-        /* Temporary files for atomically creating new files */
-        if (startswith(s, ".#"))
-                return false;
-
-        return true;
-}
-
 int pipe_eof(int fd) {
         struct pollfd pollfd = {
                 .fd = fd,
@@ -6714,23 +6739,6 @@ uint64_t physical_memory(void) {
         return (uint64_t) mem * (uint64_t) page_size();
 }
 
-char* mount_test_option(const char *haystack, const char *needle) {
-
-        struct mntent me = {
-                .mnt_opts = (char*) haystack
-        };
-
-        assert(needle);
-
-        /* Like glibc's hasmntopt(), but works on a string, not a
-         * struct mntent */
-
-        if (!haystack)
-                return NULL;
-
-        return hasmntopt(&me, needle);
-}
-
 void hexdump(FILE *f, const void *p, size_t s) {
         const uint8_t *b = p;
         unsigned n = 0;
@@ -7740,11 +7748,14 @@ int same_fd(int a, int b) {
         return fa == fb;
 }
 
-int chattr_fd(int fd, bool b, int mask) {
-        int old_attr, new_attr;
+int chattr_fd(int fd, bool b, unsigned mask) {
+        unsigned old_attr, new_attr;
 
         assert(fd >= 0);
 
+        if (mask == 0)
+                return 0;
+
         if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0)
                 return -errno;
 
@@ -7762,12 +7773,161 @@ int chattr_fd(int fd, bool b, int mask) {
         return 0;
 }
 
-int chattr_path(const char *p, bool b, int mask) {
+int chattr_path(const char *p, bool b, unsigned mask) {
         _cleanup_close_ int fd = -1;
 
-        fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+        assert(p);
+
+        if (mask == 0)
+                return 0;
+
+        fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
         if (fd < 0)
                 return -errno;
 
         return chattr_fd(fd, b, mask);
 }
+
+int read_attr_fd(int fd, unsigned *ret) {
+        assert(fd >= 0);
+
+        if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0)
+                return -errno;
+
+        return 0;
+}
+
+int read_attr_path(const char *p, unsigned *ret) {
+        _cleanup_close_ int fd = -1;
+
+        assert(p);
+        assert(ret);
+
+        fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
+        if (fd < 0)
+                return -errno;
+
+        return read_attr_fd(fd, ret);
+}
+
+int make_lock_file(const char *p, int operation, LockFile *ret) {
+        _cleanup_close_ int fd = -1;
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        /*
+         * We use UNPOSIX locks if they are available. They have nice
+         * semantics, and are mostly compatible with NFS. However,
+         * they are only available on new kernels. When we detect we
+         * are running on an older kernel, then we fall back to good
+         * old BSD locks. They also have nice semantics, but are
+         * slightly problematic on NFS, where they are upgraded to
+         * POSIX locks, even though locally they are orthogonal to
+         * POSIX locks.
+         */
+
+        t = strdup(p);
+        if (!t)
+                return -ENOMEM;
+
+        for (;;) {
+                struct flock fl = {
+                        .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK,
+                        .l_whence = SEEK_SET,
+                };
+                struct stat st;
+
+                fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600);
+                if (fd < 0)
+                        return -errno;
+
+                r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl);
+                if (r < 0) {
+
+                        /* If the kernel is too old, use good old BSD locks */
+                        if (errno == EINVAL)
+                                r = flock(fd, operation);
+
+                        if (r < 0)
+                                return errno == EAGAIN ? -EBUSY : -errno;
+                }
+
+                /* If we acquired the lock, let's check if the file
+                 * still exists in the file system. If not, then the
+                 * previous exclusive owner removed it and then closed
+                 * it. In such a case our acquired lock is worthless,
+                 * hence try again. */
+
+                r = fstat(fd, &st);
+                if (r < 0)
+                        return -errno;
+                if (st.st_nlink > 0)
+                        break;
+
+                fd = safe_close(fd);
+        }
+
+        ret->path = t;
+        ret->fd = fd;
+        ret->operation = operation;
+
+        fd = -1;
+        t = NULL;
+
+        return r;
+}
+
+int make_lock_file_for(const char *p, int operation, LockFile *ret) {
+        const char *fn;
+        char *t;
+
+        assert(p);
+        assert(ret);
+
+        fn = basename(p);
+        if (!filename_is_valid(fn))
+                return -EINVAL;
+
+        t = newa(char, strlen(p) + 2 + 4 + 1);
+        stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), ".lck");
+
+        return make_lock_file(t, operation, ret);
+}
+
+void release_lock_file(LockFile *f) {
+        int r;
+
+        if (!f)
+                return;
+
+        if (f->path) {
+
+                /* If we are the exclusive owner we can safely delete
+                 * the lock file itself. If we are not the exclusive
+                 * owner, we can try becoming it. */
+
+                if (f->fd >= 0 &&
+                    (f->operation & ~LOCK_NB) == LOCK_SH) {
+                        static const struct flock fl = {
+                                .l_type = F_WRLCK,
+                                .l_whence = SEEK_SET,
+                        };
+
+                        r = fcntl(f->fd, F_OFD_SETLK, &fl);
+                        if (r < 0 && errno == EINVAL)
+                                r = flock(f->fd, LOCK_EX|LOCK_NB);
+
+                        if (r >= 0)
+                                f->operation = LOCK_EX|LOCK_NB;
+                }
+
+                if ((f->operation & ~LOCK_NB) == LOCK_EX)
+                        unlink_noerrno(f->path);
+
+                free(f->path);
+                f->path = NULL;
+        }
+
+        f->fd = safe_close(f->fd);
+        f->operation = 0;
+}