chiark / gitweb /
util: add rename_noreplace
[elogind.git] / src / shared / util.c
index 4a970af3b1972f3a28abe28d878c50b2966d1875..55de83023d53c1e6c34226e532df505418473561 100644 (file)
@@ -2326,6 +2326,17 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) {
         return n;
 }
 
+int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) {
+        ssize_t n;
+
+        n = loop_read(fd, buf, nbytes, do_poll);
+        if (n < 0)
+                return n;
+        if ((size_t) n != nbytes)
+                return -EIO;
+        return 0;
+}
+
 int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) {
         const uint8_t *p = buf;
 
@@ -2580,8 +2591,9 @@ char* dirname_malloc(const char *path) {
 
 int dev_urandom(void *p, size_t n) {
         static int have_syscall = -1;
-        int r, fd;
-        ssize_t k;
+
+        _cleanup_close_ int fd = -1;
+        int r;
 
         /* Gathers some randomness from the kernel. This call will
          * never block, and will always return some data from the
@@ -2616,22 +2628,14 @@ int dev_urandom(void *p, size_t n) {
                                 return -errno;
                 } else
                         /* too short read? */
-                        return -EIO;
+                        return -ENODATA;
         }
 
         fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
         if (fd < 0)
                 return errno == ENOENT ? -ENOSYS : -errno;
 
-        k = loop_read(fd, p, n, true);
-        safe_close(fd);
-
-        if (k < 0)
-                return (int) k;
-        if ((size_t) k != n)
-                return -EIO;
-
-        return 0;
+        return loop_read_exact(fd, p, n, true);
 }
 
 void initialize_srand(void) {
@@ -5999,7 +6003,7 @@ int on_ac_power(void) {
 
         d = opendir("/sys/class/power_supply");
         if (!d)
-                return -errno;
+                return errno == ENOENT ? true : -errno;
 
         for (;;) {
                 struct dirent *de;
@@ -8118,3 +8122,44 @@ void cmsg_close_all(struct msghdr *mh) {
                 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
                         close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int));
 }
+
+int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
+        struct stat buf;
+        int ret;
+
+        ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE);
+        if (ret >= 0)
+                return 0;
+
+        /* Even though renameat2() exists since Linux 3.15, btrfs added
+         * support for it later. If it is not implemented, fallback to another
+         * method. */
+        if (errno != EINVAL)
+                return -errno;
+
+        /* The link()/unlink() fallback does not work on directories. But
+         * renameat() without RENAME_NOREPLACE gives the same semantics on
+         * directories, except when newpath is an *empty* directory. This is
+         * good enough. */
+        ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW);
+        if (ret >= 0 && S_ISDIR(buf.st_mode)) {
+                ret = renameat(olddirfd, oldpath, newdirfd, newpath);
+                return ret >= 0 ? 0 : -errno;
+        }
+
+        /* If it is not a directory, use the link()/unlink() fallback. */
+        ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0);
+        if (ret < 0)
+                return -errno;
+
+        ret = unlinkat(olddirfd, oldpath, 0);
+        if (ret < 0) {
+                /* backup errno before the following unlinkat() alters it */
+                ret = errno;
+                (void) unlinkat(newdirfd, newpath, 0);
+                errno = ret;
+                return -errno;
+        }
+
+        return 0;
+}