]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - lib/loopdev.c
lib/timeutils: print error if timestamp can't be parsed
[thirdparty/util-linux.git] / lib / loopdev.c
index 21c8f4366ae4473845b06329992c9c894a4c634c..323f7bd50d39b92cebf9a51477621e68e4705fb4 100644 (file)
@@ -32,7 +32,6 @@
 #include <sys/mman.h>
 #include <inttypes.h>
 #include <dirent.h>
-#include <linux/posix_types.h>
 
 #include "linux_version.h"
 #include "c.h"
 #include "canonicalize.h"
 #include "blkdev.h"
 #include "debug.h"
+#include "fileutils.h"
+
+#define LOOPDEV_MAX_TRIES      10
 
 /*
  * Debug stuff (based on include/debug.h)
  */
-UL_DEBUG_DEFINE_MASK(loopdev);
+static UL_DEBUG_DEFINE_MASK(loopdev);
 UL_DEBUG_DEFINE_MASKNAMES(loopdev) = UL_DEBUG_EMPTY_MASKNAMES;
 
 #define LOOPDEV_DEBUG_INIT     (1 << 1)
 #define LOOPDEV_DEBUG_CXT      (1 << 2)
 #define LOOPDEV_DEBUG_ITER     (1 << 3)
 #define LOOPDEV_DEBUG_SETUP    (1 << 4)
-#define SFDISKPROG_DEBUG_ALL   0xFFFF
 
 #define DBG(m, x)       __UL_DBG(loopdev, LOOPDEV_DEBUG_, m, x)
 #define ON_DBG(m, x)    __UL_DBG_CALL(loopdev, LOOPDEV_DEBUG_, m, x)
 
+#define UL_DEBUG_CURRENT_MASK  UL_DEBUG_MASK(loopdev)
+#include "debugobj.h"
+
 static void loopdev_init_debug(void)
 {
        if (loopdev_debug_mask)
                return;
-       __UL_INIT_DEBUG(loopdev, LOOPDEV_DEBUG_, 0, LOOPDEV_DEBUG);
+       __UL_INIT_DEBUG_FROM_ENV(loopdev, LOOPDEV_DEBUG_, 0, LOOPDEV_DEBUG);
 }
 
 /*
@@ -72,6 +76,24 @@ static void loopdev_init_debug(void)
 #define loopcxt_sysfs_available(_lc)   (!((_lc)->flags & LOOPDEV_FL_NOSYSFS)) \
                                         && !loopcxt_ioctl_enabled(_lc)
 
+/*
+ * Calls @x and repeat on EAGAIN
+ */
+#define repeat_on_eagain(x) __extension__ ({                   \
+               int _c = 0, _e;                                 \
+               do {                                            \
+                       errno = 0;                              \
+                       _e = x;                                 \
+                       if (_e == 0 || errno != EAGAIN)         \
+                               break;                          \
+                       if (_c >= LOOPDEV_MAX_TRIES)            \
+                               break;                          \
+                       xusleep(250000);                        \
+                       _c++;                                   \
+               } while (1);                                    \
+               _e == 0 ? 0 : errno ? -errno : -1;              \
+       })
+
 /*
  * @lc: context
  * @device: device name, absolute device path or NULL to reset the current setting
@@ -94,11 +116,12 @@ int loopcxt_set_device(struct loopdev_cxt *lc, const char *device)
                DBG(CXT, ul_debugobj(lc, "closing old open fd"));
        }
        lc->fd = -1;
-       lc->mode = 0;
+       lc->mode = O_RDONLY;
+       lc->blocksize = 0;
        lc->has_info = 0;
        lc->info_failed = 0;
        *lc->device = '\0';
-       memset(&lc->info, 0, sizeof(lc->info));
+       memset(&lc->config, 0, sizeof(lc->config));
 
        /* set new */
        if (device) {
@@ -114,14 +137,14 @@ int loopcxt_set_device(struct loopdev_cxt *lc, const char *device)
                        }
                        snprintf(lc->device, sizeof(lc->device), "%s%s",
                                dir, device);
-               } else {
-                       strncpy(lc->device, device, sizeof(lc->device));
-                       lc->device[sizeof(lc->device) - 1] = '\0';
-               }
+               } else
+                       xstrncpy(lc->device, device, sizeof(lc->device));
+
                DBG(CXT, ul_debugobj(lc, "%s name assigned", device));
        }
 
-       sysfs_deinit(&lc->sysfs);
+       ul_unref_path(lc->sysfs);
+       lc->sysfs = NULL;
        return 0;
 }
 
@@ -142,12 +165,6 @@ int loopcxt_has_device(struct loopdev_cxt *lc)
  *
  *     * LO_FLAGS_* are kernel flags used for LOOP_{SET,GET}_STAT64 ioctls
  *
- * Note about LOOPDEV_FL_{RDONLY,RDWR} flags. These flags are used for open(2)
- * syscall to open loop device. By default is the device open read-only.
- *
- * The expection is loopcxt_setup_device(), where the device is open read-write
- * if LO_FLAGS_READ_ONLY flags is not set (see loopcxt_set_flags()).
- *
  * Returns: <0 on error, 0 on success.
  */
 int loopcxt_init(struct loopdev_cxt *lc, int flags)
@@ -242,48 +259,70 @@ const char *loopcxt_get_device(struct loopdev_cxt *lc)
  *
  * Returns pointer to the sysfs context (see lib/sysfs.c)
  */
-struct sysfs_cxt *loopcxt_get_sysfs(struct loopdev_cxt *lc)
+static struct path_cxt *loopcxt_get_sysfs(struct loopdev_cxt *lc)
 {
        if (!lc || !*lc->device || (lc->flags & LOOPDEV_FL_NOSYSFS))
                return NULL;
 
-       if (!lc->sysfs.devno) {
-               dev_t devno = sysfs_devname_to_devno(lc->device, NULL);
+       if (!lc->sysfs) {
+               dev_t devno = sysfs_devname_to_devno(lc->device);
                if (!devno) {
                        DBG(CXT, ul_debugobj(lc, "sysfs: failed devname to devno"));
                        return NULL;
                }
-               if (sysfs_init(&lc->sysfs, devno, NULL)) {
+
+               lc->sysfs = ul_new_sysfs_path(devno, NULL, NULL);
+               if (!lc->sysfs)
                        DBG(CXT, ul_debugobj(lc, "sysfs: init failed"));
-                       return NULL;
-               }
        }
 
-       return &lc->sysfs;
+       return lc->sysfs;
 }
 
-/*
- * @lc: context
- *
- * Returns: file descriptor to the open loop device or <0 on error. The mode
- *          depends on LOOPDEV_FL_{RDWR,RDONLY} context flags. Default is
- *          read-only.
- */
-int loopcxt_get_fd(struct loopdev_cxt *lc)
+static int __loopcxt_get_fd(struct loopdev_cxt *lc, mode_t mode)
 {
+       int old = -1;
+
        if (!lc || !*lc->device)
                return -EINVAL;
 
+       /* It's okay to return a FD with read-write permissions if someone
+        * asked for read-only, but you shouldn't do the opposite.
+        *
+        * (O_RDONLY is a widely usable default.)
+        */
+       if (lc->fd >= 0 && mode == O_RDWR && lc->mode == O_RDONLY) {
+               DBG(CXT, ul_debugobj(lc, "closing already open device (mode mismatch)"));
+               old = lc->fd;
+               lc->fd = -1;
+       }
+
        if (lc->fd < 0) {
-               lc->mode = lc->flags & LOOPDEV_FL_RDWR ? O_RDWR : O_RDONLY;
+               lc->mode = mode;
                lc->fd = open(lc->device, lc->mode | O_CLOEXEC);
                DBG(CXT, ul_debugobj(lc, "open %s [%s]: %m", lc->device,
-                               lc->flags & LOOPDEV_FL_RDWR ? "rw" : "ro"));
+                               mode == O_RDONLY ? "ro" :
+                               mode == O_RDWR ? "rw" : "??"));
+
+               if (lc->fd < 0 && old >= 0) {
+                       /* restore original on error */
+                       lc->fd = old;
+                       old = -1;
+               }
        }
+
+       if (old >= 0)
+               close(old);
        return lc->fd;
 }
 
-int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, int mode)
+/* default is read-only file descriptor, it's enough for all ioctls */
+int loopcxt_get_fd(struct loopdev_cxt *lc)
+{
+       return __loopcxt_get_fd(lc, O_RDONLY);
+}
+
+int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, mode_t mode)
 {
        if (!lc)
                return -EINVAL;
@@ -297,7 +336,7 @@ int loopcxt_set_fd(struct loopdev_cxt *lc, int fd, int mode)
  * @lc: context
  * @flags: LOOPITER_FL_* flags
  *
- * Iterator allows to scan list of the free or used loop devices.
+ * Iterator can be used to scan list of the free or used loop devices.
  *
  * Returns: <0 on error, 0 on success
  */
@@ -353,16 +392,14 @@ int loopcxt_deinit_iterator(struct loopdev_cxt *lc)
                fclose(iter->proc);
        if (iter->sysblock)
                closedir(iter->sysblock);
-       iter->minors = NULL;
-       iter->proc = NULL;
-       iter->sysblock = NULL;
-       iter->done = 1;
+
+       memset(iter, 0, sizeof(*iter));
        return 0;
 }
 
 /*
  * Same as loopcxt_set_device, but also checks if the device is
- * associeted with any file.
+ * associated with any file.
  *
  * Returns: <0 on error, 0 on success, 1 device does not match with
  *         LOOPITER_FL_{USED,FREE} flags.
@@ -402,8 +439,8 @@ static int loopiter_set_device(struct loopdev_cxt *lc, const char *device)
 
 static int cmpnum(const void *p1, const void *p2)
 {
-       return (((* (int *) p1) > (* (int *) p2)) -
-                       ((* (int *) p1) < (* (int *) p2)));
+       return (((* (const int *) p1) > (* (const int *) p2)) -
+                       ((* (const int *) p1) < (* (const int *) p2)));
 }
 
 /*
@@ -535,7 +572,7 @@ static int loopcxt_next_from_sysfs(struct loopdev_cxt *lc)
        fd = dirfd(iter->sysblock);
 
        while ((d = readdir(iter->sysblock))) {
-               char name[256];
+               char name[NAME_MAX + 18 + 1];
                struct stat st;
 
                DBG(ITER, ul_debugobj(iter, "check %s", d->d_name));
@@ -559,7 +596,7 @@ static int loopcxt_next_from_sysfs(struct loopdev_cxt *lc)
 /*
  * @lc: context, has to initialized by loopcxt_init_iterator()
  *
- * Returns: 0 on success, -1 on error, 1 at the end of scanning. The details
+ * Returns: 0 on success, < 0 on error, 1 at the end of scanning. The details
  *          about the current loop device are available by
  *          loopcxt_get_{fd,backing_file,device,offset, ...} functions.
  */
@@ -634,13 +671,30 @@ done:
 int is_loopdev(const char *device)
 {
        struct stat st;
+       int rc = 0;
 
-       if (!device)
-               return 0;
+       if (!device || stat(device, &st) != 0 || !S_ISBLK(st.st_mode))
+               rc = 0;
+       else if (major(st.st_rdev) == LOOPDEV_MAJOR)
+               rc = 1;
+       else if (sysfs_devno_is_wholedisk(st.st_rdev)) {
+               /* It's possible that kernel creates a device with a different
+                * major number ... check by /sys it's really loop device.
+                */
+               char name[PATH_MAX], *cn, *p = NULL;
+
+               snprintf(name, sizeof(name), _PATH_SYS_DEVBLOCK "/%d:%d",
+                               major(st.st_rdev), minor(st.st_rdev));
+               cn = canonicalize_path(name);
+               if (cn)
+                       p = stripoff_last_component(cn);
+               rc = p && startswith(p, "loop");
+               free(cn);
+       }
 
-       return (stat(device, &st) == 0 &&
-               S_ISBLK(st.st_mode) &&
-               major(st.st_rdev) == LOOPDEV_MAJOR);
+       if (rc == 0)
+               errno = ENODEV;
+       return rc;
 }
 
 /*
@@ -658,17 +712,17 @@ struct loop_info64 *loopcxt_get_info(struct loopdev_cxt *lc)
        }
        errno = 0;
        if (lc->has_info)
-               return &lc->info;
+               return &lc->config.info;
 
        fd = loopcxt_get_fd(lc);
        if (fd < 0)
                return NULL;
 
-       if (ioctl(fd, LOOP_GET_STATUS64, &lc->info) == 0) {
+       if (ioctl(fd, LOOP_GET_STATUS64, &lc->config.info) == 0) {
                lc->has_info = 1;
                lc->info_failed = 0;
                DBG(CXT, ul_debugobj(lc, "reading loop_info64 OK"));
-               return &lc->info;
+               return &lc->config.info;
        }
 
        lc->info_failed = 1;
@@ -680,20 +734,20 @@ struct loop_info64 *loopcxt_get_info(struct loopdev_cxt *lc)
 /*
  * @lc: context
  *
- * Returns (allocated) string with path to the file assicieted
+ * Returns (allocated) string with path to the file associated
  * with the current loop device.
  */
 char *loopcxt_get_backing_file(struct loopdev_cxt *lc)
 {
-       struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+       struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
        char *res = NULL;
 
        if (sysfs)
                /*
-                * This is always preffered, the loop_info64
+                * This is always preferred, the loop_info64
                 * has too small buffer for the filename.
                 */
-               res = sysfs_strdup(sysfs, "loop/backing_file");
+               ul_path_read_string(sysfs, &res, "loop/backing_file");
 
        if (!res && loopcxt_ioctl_enabled(lc)) {
                struct loop_info64 *lo = loopcxt_get_info(lc);
@@ -709,6 +763,26 @@ char *loopcxt_get_backing_file(struct loopdev_cxt *lc)
        return res;
 }
 
+/*
+ * @lc: context
+ *
+ * Returns (allocated) string with loop reference. The same as backing file by
+ * default.
+ */
+char *loopcxt_get_refname(struct loopdev_cxt *lc)
+{
+       char *res = NULL;
+       struct loop_info64 *lo = loopcxt_get_info(lc);
+
+       if (lo) {
+               lo->lo_file_name[LO_NAME_SIZE - 1] = '\0';
+               res = strdup((char *) lo->lo_file_name);
+       }
+
+       DBG(CXT, ul_debugobj(lc, "get_refname [%s]", res));
+       return res;
+}
+
 /*
  * @lc: context
  * @offset: returns offset number for the given device
@@ -717,11 +791,12 @@ char *loopcxt_get_backing_file(struct loopdev_cxt *lc)
  */
 int loopcxt_get_offset(struct loopdev_cxt *lc, uint64_t *offset)
 {
-       struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+       struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
        int rc = -EINVAL;
 
        if (sysfs)
-               rc = sysfs_read_u64(sysfs, "loop/offset", offset);
+               if (ul_path_read_u64(sysfs, offset, "loop/offset") == 0)
+                       rc = 0;
 
        if (rc && loopcxt_ioctl_enabled(lc)) {
                struct loop_info64 *lo = loopcxt_get_info(lc);
@@ -737,6 +812,39 @@ int loopcxt_get_offset(struct loopdev_cxt *lc, uint64_t *offset)
        return rc;
 }
 
+/*
+ * @lc: context
+ * @blocksize: returns logical blocksize for the given device
+ *
+ * Returns: <0 on error, 0 on success
+ */
+int loopcxt_get_blocksize(struct loopdev_cxt *lc, uint64_t *blocksize)
+{
+       struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
+       int rc = -EINVAL;
+
+       if (sysfs)
+               if (ul_path_read_u64(sysfs, blocksize, "queue/logical_block_size") == 0)
+                       rc = 0;
+
+       /* Fallback based on BLKSSZGET ioctl */
+       if (rc) {
+               int fd = loopcxt_get_fd(lc);
+               int sz = 0;
+
+               if (fd < 0)
+                       return -EINVAL;
+               rc = blkdev_get_sector_size(fd, &sz);
+               if (rc)
+                       return rc;
+
+               *blocksize = sz;
+       }
+
+       DBG(CXT, ul_debugobj(lc, "get_blocksize [rc=%d]", rc));
+       return rc;
+}
+
 /*
  * @lc: context
  * @sizelimit: returns size limit for the given device
@@ -745,11 +853,12 @@ int loopcxt_get_offset(struct loopdev_cxt *lc, uint64_t *offset)
  */
 int loopcxt_get_sizelimit(struct loopdev_cxt *lc, uint64_t *size)
 {
-       struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+       struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
        int rc = -EINVAL;
 
        if (sysfs)
-               rc = sysfs_read_u64(sysfs, "loop/sizelimit", size);
+               if (ul_path_read_u64(sysfs, size, "loop/sizelimit") == 0)
+                       rc = 0;
 
        if (rc && loopcxt_ioctl_enabled(lc)) {
                struct loop_info64 *lo = loopcxt_get_info(lc);
@@ -894,12 +1003,12 @@ int loopmod_supports_partscan(void)
  */
 int loopcxt_is_partscan(struct loopdev_cxt *lc)
 {
-       struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+       struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
 
        if (sysfs) {
                /* kernel >= 3.2 */
                int fl;
-               if (sysfs_read_int(sysfs, "loop/partscan", &fl) == 0)
+               if (ul_path_read_s32(sysfs, &fl, "loop/partscan") == 0)
                        return fl;
        }
 
@@ -914,11 +1023,11 @@ int loopcxt_is_partscan(struct loopdev_cxt *lc)
  */
 int loopcxt_is_autoclear(struct loopdev_cxt *lc)
 {
-       struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+       struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
 
        if (sysfs) {
                int fl;
-               if (sysfs_read_int(sysfs, "loop/autoclear", &fl) == 0)
+               if (ul_path_read_s32(sysfs, &fl, "loop/autoclear") == 0)
                        return fl;
        }
 
@@ -937,11 +1046,11 @@ int loopcxt_is_autoclear(struct loopdev_cxt *lc)
  */
 int loopcxt_is_readonly(struct loopdev_cxt *lc)
 {
-       struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+       struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
 
        if (sysfs) {
                int fl;
-               if (sysfs_read_int(sysfs, "ro", &fl) == 0)
+               if (ul_path_read_s32(sysfs, &fl, "ro") == 0)
                        return fl;
        }
 
@@ -960,11 +1069,11 @@ int loopcxt_is_readonly(struct loopdev_cxt *lc)
  */
 int loopcxt_is_dio(struct loopdev_cxt *lc)
 {
-       struct sysfs_cxt *sysfs = loopcxt_get_sysfs(lc);
+       struct path_cxt *sysfs = loopcxt_get_sysfs(lc);
 
        if (sysfs) {
                int fl;
-               if (sysfs_read_int(sysfs, "loop/dio", &fl) == 0)
+               if (ul_path_read_s32(sysfs, &fl, "loop/dio") == 0)
                        return fl;
        }
        if (loopcxt_ioctl_enabled(lc)) {
@@ -1000,8 +1109,8 @@ int loopcxt_is_used(struct loopdev_cxt *lc,
                    uint64_t sizelimit,
                    int flags)
 {
-       ino_t ino;
-       dev_t dev;
+       ino_t ino = 0;
+       dev_t dev = 0;
 
        if (!lc)
                return 0;
@@ -1033,16 +1142,16 @@ int loopcxt_is_used(struct loopdev_cxt *lc,
        return 0;
 found:
        if (flags & LOOPDEV_FL_OFFSET) {
-               uint64_t off;
+               uint64_t off = 0;
 
                int rc = loopcxt_get_offset(lc, &off) == 0 && off == offset;
 
                if (rc && flags & LOOPDEV_FL_SIZELIMIT) {
-                       uint64_t sz;
+                       uint64_t sz = 0;
 
                        return loopcxt_get_sizelimit(lc, &sz) == 0 && sz == sizelimit;
-               } else
-                       return rc;
+               }
+               return rc;
        }
        return 1;
 }
@@ -1054,7 +1163,7 @@ int loopcxt_set_offset(struct loopdev_cxt *lc, uint64_t offset)
 {
        if (!lc)
                return -EINVAL;
-       lc->info.lo_offset = offset;
+       lc->config.info.lo_offset = offset;
 
        DBG(CXT, ul_debugobj(lc, "set offset=%jd", offset));
        return 0;
@@ -1067,12 +1176,28 @@ int loopcxt_set_sizelimit(struct loopdev_cxt *lc, uint64_t sizelimit)
 {
        if (!lc)
                return -EINVAL;
-       lc->info.lo_sizelimit = sizelimit;
+       lc->config.info.lo_sizelimit = sizelimit;
 
        DBG(CXT, ul_debugobj(lc, "set sizelimit=%jd", sizelimit));
        return 0;
 }
 
+/*
+ * The blocksize will be used by loopcxt_set_device(). For already exiting
+ * devices use  loopcxt_ioctl_blocksize().
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ */
+int loopcxt_set_blocksize(struct loopdev_cxt *lc, uint64_t blocksize)
+{
+       if (!lc)
+               return -EINVAL;
+       lc->blocksize = blocksize;
+
+       DBG(CXT, ul_debugobj(lc, "set blocksize=%jd", blocksize));
+       return 0;
+}
+
 /*
  * @lc: context
  * @flags: kernel LO_FLAGS_{READ_ONLY,USE_AOPS,AUTOCLEAR} flags
@@ -1085,12 +1210,34 @@ int loopcxt_set_flags(struct loopdev_cxt *lc, uint32_t flags)
 {
        if (!lc)
                return -EINVAL;
-       lc->info.lo_flags = flags;
+       lc->config.info.lo_flags = flags;
 
        DBG(CXT, ul_debugobj(lc, "set flags=%u", (unsigned) flags));
        return 0;
 }
 
+/*
+ * @lc: context
+ * @refname: reference name (used to overwrite lo_file_name where is backing
+ *           file by default)
+ *
+ * The setting is removed by loopcxt_set_device() loopcxt_next()!
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int loopcxt_set_refname(struct loopdev_cxt *lc, const char *refname)
+{
+       if (!lc)
+               return -EINVAL;
+
+       memset(lc->config.info.lo_file_name, 0, sizeof(lc->config.info.lo_file_name));
+       if (refname)
+               xstrncpy((char *)lc->config.info.lo_file_name, refname, LO_NAME_SIZE);
+
+       DBG(CXT, ul_debugobj(lc, "set refname=%s", (char *)lc->config.info.lo_file_name));
+       return 0;
+}
+
 /*
  * @lc: context
  * @filename: backing file path (the path will be canonicalized)
@@ -1108,10 +1255,10 @@ int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename)
        if (!lc->filename)
                return -errno;
 
-       strncpy((char *)lc->info.lo_file_name, lc->filename, LO_NAME_SIZE);
-       lc->info.lo_file_name[LO_NAME_SIZE- 1] = '\0';
+       if (!lc->config.info.lo_file_name[0])
+               loopcxt_set_refname(lc, lc->filename);
 
-       DBG(CXT, ul_debugobj(lc, "set backing file=%s", lc->info.lo_file_name));
+       DBG(CXT, ul_debugobj(lc, "set backing file=%s", lc->filename));
        return 0;
 }
 
@@ -1134,7 +1281,7 @@ static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd)
        int dev_fd;
        struct stat st;
 
-       if (!lc->info.lo_offset && !lc->info.lo_sizelimit)
+       if (!lc->config.info.lo_offset && !lc->config.info.lo_sizelimit)
                return 0;
 
        if (fstat(file_fd, &st)) {
@@ -1150,16 +1297,16 @@ static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd)
        } else
                expected_size = st.st_size;
 
-       if (expected_size == 0 || expected_size <= lc->info.lo_offset) {
+       if (expected_size == 0 || expected_size <= lc->config.info.lo_offset) {
                DBG(CXT, ul_debugobj(lc, "failed to determine expected size"));
                return 0;       /* ignore this error */
        }
 
-       if (lc->info.lo_offset > 0)
-               expected_size -= lc->info.lo_offset;
+       if (lc->config.info.lo_offset > 0)
+               expected_size -= lc->config.info.lo_offset;
 
-       if (lc->info.lo_sizelimit > 0 && lc->info.lo_sizelimit < expected_size)
-               expected_size = lc->info.lo_sizelimit;
+       if (lc->config.info.lo_sizelimit > 0 && lc->config.info.lo_sizelimit < expected_size)
+               expected_size = lc->config.info.lo_sizelimit;
 
        dev_fd = loopcxt_get_fd(lc);
        if (dev_fd < 0) {
@@ -1183,7 +1330,7 @@ static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd)
                                      "size mismatch (%ju/%ju)",
                                      size, expected_size));
 
-               if (loopcxt_set_capacity(lc)) {
+               if (loopcxt_ioctl_capacity(lc)) {
                        /* ioctl not available */
                        if (errno == ENOTTY || errno == EINVAL)
                                errno = ERANGE;
@@ -1205,8 +1352,9 @@ static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd)
        return 0;
 }
 
+
 /*
- * @cl: context
+ * @lc: context
  *
  * Associate the current device (see loopcxt_{set,get}_device()) with
  * a file (see loopcxt_set_backing_file()).
@@ -1225,8 +1373,11 @@ static int loopcxt_check_size(struct loopdev_cxt *lc, int file_fd)
  */
 int loopcxt_setup_device(struct loopdev_cxt *lc)
 {
-       int file_fd, dev_fd, mode = O_RDWR, rc = -1, cnt = 0;
+       int file_fd, dev_fd;
+       mode_t flags = O_CLOEXEC, mode = O_RDWR;
+       int rc = -1, cnt = 0;
        int errsv = 0;
+       int fallback = 0;
 
        if (!lc || !*lc->device || !lc->filename)
                return -EINVAL;
@@ -1236,12 +1387,15 @@ int loopcxt_setup_device(struct loopdev_cxt *lc)
        /*
         * Open backing file and device
         */
-       if (lc->info.lo_flags & LO_FLAGS_READ_ONLY)
+       if (lc->config.info.lo_flags & LO_FLAGS_READ_ONLY)
                mode = O_RDONLY;
 
-       if ((file_fd = open(lc->filename, mode | O_CLOEXEC)) < 0) {
+       if (lc->config.info.lo_flags & LO_FLAGS_DIRECT_IO)
+               flags |= O_DIRECT;
+
+       if ((file_fd = open(lc->filename, mode | flags)) < 0) {
                if (mode != O_RDONLY && (errno == EROFS || errno == EACCES))
-                       file_fd = open(lc->filename, mode = O_RDONLY);
+                       file_fd = open(lc->filename, (mode = O_RDONLY) | flags);
 
                if (file_fd < 0) {
                        DBG(SETUP, ul_debugobj(lc, "open backing file failed: %m"));
@@ -1250,25 +1404,22 @@ int loopcxt_setup_device(struct loopdev_cxt *lc)
        }
        DBG(SETUP, ul_debugobj(lc, "backing file open: OK"));
 
-       if (lc->fd != -1 && lc->mode != mode) {
-               DBG(SETUP, ul_debugobj(lc, "closing already open device (mode mismatch)"));
-               close(lc->fd);
-               lc->fd = -1;
-               lc->mode = 0;
-       }
-
-       if (mode == O_RDONLY) {
-               lc->flags |= LOOPDEV_FL_RDONLY;                 /* open() mode */
-               lc->info.lo_flags |= LO_FLAGS_READ_ONLY;        /* kernel loopdev mode */
-       } else {
-               lc->flags |= LOOPDEV_FL_RDWR;                   /* open() mode */
-               lc->info.lo_flags &= ~LO_FLAGS_READ_ONLY;
-               lc->flags &= ~LOOPDEV_FL_RDONLY;
-       }
+       if (mode == O_RDONLY)
+               lc->config.info.lo_flags |= LO_FLAGS_READ_ONLY; /* kernel loopdev mode */
+       else
+               lc->config.info.lo_flags &= ~LO_FLAGS_READ_ONLY;
 
        do {
                errno = 0;
-               dev_fd = loopcxt_get_fd(lc);
+
+               /* For the ioctls, it's enough to use O_RDONLY, but udevd
+                * monitor devices by inotify, and udevd needs IN_CLOSE_WRITE
+                * event to trigger probing of the new device.
+                *
+                * The mode used for the device does not have to match the mode
+                * used for the backing file.
+                */
+               dev_fd = __loopcxt_get_fd(lc, O_RDWR);
                if (dev_fd >= 0 || lc->control_ok == 0)
                        break;
                if (errno != EACCES && errno != ENOENT)
@@ -1287,32 +1438,57 @@ int loopcxt_setup_device(struct loopdev_cxt *lc)
        DBG(SETUP, ul_debugobj(lc, "device open: OK"));
 
        /*
-        * Set FD
+        * Atomic way to configure all by one ioctl call
+        * -- since Linux v5.8-rc1, commit 3448914e8cc550ba792d4ccc74471d1ca4293aae
         */
-       if (ioctl(dev_fd, LOOP_SET_FD, file_fd) < 0) {
-               rc = -errno;
+       lc->config.fd = file_fd;
+       if (lc->blocksize > 0)
+               lc->config.block_size = lc->blocksize;
+
+       rc = repeat_on_eagain( ioctl(dev_fd, LOOP_CONFIGURE, &lc->config) );
+       if (rc != 0) {
                errsv = errno;
-               DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD failed: %m"));
-               goto err;
+               if (errno != EINVAL && errno != ENOTTY && errno != ENOSYS) {
+                       DBG(SETUP, ul_debugobj(lc, "LOOP_CONFIGURE failed: %m"));
+                       goto err;
+               }
+               fallback = 1;
+       } else {
+               DBG(SETUP, ul_debugobj(lc, "LOOP_CONFIGURE: OK"));
        }
 
-       DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD: OK"));
+       /*
+        * Old deprecated way; first assign backing file FD and then in the
+        * second step set loop device properties.
+        */
+       if (fallback) {
+               if (ioctl(dev_fd, LOOP_SET_FD, file_fd) < 0) {
+                       rc = -errno;
+                       errsv = errno;
+                       DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD failed: %m"));
+                       goto err;
+               }
 
-       if (ioctl(dev_fd, LOOP_SET_STATUS64, &lc->info)) {
-               rc = -errno;
-               errsv = errno;
-               DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64 failed: %m"));
-               goto err;
-       }
+               DBG(SETUP, ul_debugobj(lc, "LOOP_SET_FD: OK"));
 
-       DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64: OK"));
+               if (lc->blocksize > 0
+                       && (rc = loopcxt_ioctl_blocksize(lc, lc->blocksize)) < 0) {
+                       errsv = -rc;
+                       goto err;
+               }
+
+               if ((rc = loopcxt_ioctl_status(lc)) < 0) {
+                       errsv = -rc;
+                       goto err;
+               }
+       }
 
        if ((rc = loopcxt_check_size(lc, file_fd)))
                goto err;
 
        close(file_fd);
 
-       memset(&lc->info, 0, sizeof(lc->info));
+       memset(&lc->config, 0, sizeof(lc->config));
        lc->has_info = 0;
        lc->info_failed = 0;
 
@@ -1330,16 +1506,52 @@ err:
        return rc;
 }
 
-int loopcxt_set_capacity(struct loopdev_cxt *lc)
+
+/*
+ * @lc: context
+ *
+ * Update status of the current device (see loopcxt_{set,get}_device()).
+ *
+ * Note that once initialized, kernel accepts only selected changes:
+ * LO_FLAGS_AUTOCLEAR and LO_FLAGS_PARTSCAN
+ * For more see linux/drivers/block/loop.c:loop_set_status()
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int loopcxt_ioctl_status(struct loopdev_cxt *lc)
 {
-       int fd = loopcxt_get_fd(lc);
+       int dev_fd, rc;
+
+       errno = 0;
+       dev_fd = loopcxt_get_fd(lc);
+
+       if (dev_fd < 0)
+               return -errno;
+
+       DBG(SETUP, ul_debugobj(lc, "calling LOOP_SET_STATUS64"));
+
+       rc = repeat_on_eagain( ioctl(dev_fd, LOOP_SET_STATUS64, &lc->config.info) );
+       if (rc != 0) {
+               DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64 failed: %m"));
+               return rc;
+       }
+
+       DBG(SETUP, ul_debugobj(lc, "LOOP_SET_STATUS64: OK"));
+       return 0;
+}
+
+int loopcxt_ioctl_capacity(struct loopdev_cxt *lc)
+{
+       int rc, fd = loopcxt_get_fd(lc);
 
        if (fd < 0)
                return -EINVAL;
 
+       DBG(SETUP, ul_debugobj(lc, "calling LOOP_SET_CAPACITY"));
+
        /* Kernels prior to v2.6.30 don't support this ioctl */
-       if (ioctl(fd, LOOP_SET_CAPACITY, 0) < 0) {
-               int rc = -errno;
+       rc = repeat_on_eagain( ioctl(fd, LOOP_SET_CAPACITY, 0) );
+       if (rc != 0) {
                DBG(CXT, ul_debugobj(lc, "LOOP_SET_CAPACITY failed: %m"));
                return rc;
        }
@@ -1348,16 +1560,18 @@ int loopcxt_set_capacity(struct loopdev_cxt *lc)
        return 0;
 }
 
-int loopcxt_set_dio(struct loopdev_cxt *lc, unsigned long use_dio)
+int loopcxt_ioctl_dio(struct loopdev_cxt *lc, unsigned long use_dio)
 {
-       int fd = loopcxt_get_fd(lc);
+       int rc, fd = loopcxt_get_fd(lc);
 
        if (fd < 0)
                return -EINVAL;
 
+       DBG(SETUP, ul_debugobj(lc, "calling LOOP_SET_DIRECT_IO"));
+
        /* Kernels prior to v4.4 don't support this ioctl */
-       if (ioctl(fd, LOOP_SET_DIRECT_IO, use_dio) < 0) {
-               int rc = -errno;
+       rc = repeat_on_eagain( ioctl(fd, LOOP_SET_DIRECT_IO, use_dio) );
+       if (rc != 0) {
                DBG(CXT, ul_debugobj(lc, "LOOP_SET_DIRECT_IO failed: %m"));
                return rc;
        }
@@ -1366,16 +1580,43 @@ int loopcxt_set_dio(struct loopdev_cxt *lc, unsigned long use_dio)
        return 0;
 }
 
+/*
+ * Kernel uses "unsigned long" as ioctl arg, but we use u64 for all sizes to
+ * keep loopdev internal API simple.
+ */
+int loopcxt_ioctl_blocksize(struct loopdev_cxt *lc, uint64_t blocksize)
+{
+       int rc, fd = loopcxt_get_fd(lc);
+
+       if (fd < 0)
+               return -EINVAL;
+
+       DBG(SETUP, ul_debugobj(lc, "calling LOOP_SET_BLOCK_SIZE"));
+
+       rc = repeat_on_eagain(
+               ioctl(fd, LOOP_SET_BLOCK_SIZE, (unsigned long) blocksize) );
+       if (rc != 0) {
+               DBG(CXT, ul_debugobj(lc, "LOOP_SET_BLOCK_SIZE failed: %m"));
+               return rc;
+       }
+
+       DBG(CXT, ul_debugobj(lc, "logical block size set"));
+       return 0;
+}
+
 int loopcxt_delete_device(struct loopdev_cxt *lc)
 {
-       int fd = loopcxt_get_fd(lc);
+       int rc, fd = loopcxt_get_fd(lc);
 
        if (fd < 0)
                return -EINVAL;
 
-       if (ioctl(fd, LOOP_CLR_FD, 0) < 0) {
+       DBG(SETUP, ul_debugobj(lc, "calling LOOP_SET_CLR_FD"));
+
+       rc = repeat_on_eagain( ioctl(fd, LOOP_CLR_FD, 0) );
+       if (rc != 0) {
                DBG(CXT, ul_debugobj(lc, "LOOP_CLR_FD failed: %m"));
-               return -errno;
+               return rc;
        }
 
        DBG(CXT, ul_debugobj(lc, "device removed"));
@@ -1418,6 +1659,8 @@ done:
  * kernels we have to check all loop devices to found unused one.
  *
  * See kernel commit 770fe30a46a12b6fb6b63fbe1737654d28e8484.
+ *
+ * Returns: 0 = success, < 0 error
  */
 int loopcxt_find_unused(struct loopdev_cxt *lc)
 {
@@ -1426,10 +1669,15 @@ int loopcxt_find_unused(struct loopdev_cxt *lc)
        DBG(CXT, ul_debugobj(lc, "find_unused requested"));
 
        if (lc->flags & LOOPDEV_FL_CONTROL) {
-               int ctl = open(_PATH_DEV_LOOPCTL, O_RDWR|O_CLOEXEC);
+               int ctl;
+
+               DBG(CXT, ul_debugobj(lc, "using loop-control"));
 
+               ctl = open(_PATH_DEV_LOOPCTL, O_RDWR|O_CLOEXEC);
                if (ctl >= 0)
                        rc = ioctl(ctl, LOOP_CTL_GET_FREE);
+               else
+                       rc = -errno;
                if (rc >= 0) {
                        char name[16];
                        snprintf(name, sizeof(name), "loop%d", rc);
@@ -1443,6 +1691,7 @@ int loopcxt_find_unused(struct loopdev_cxt *lc)
        }
 
        if (rc < 0) {
+               DBG(CXT, ul_debugobj(lc, "using loop scan"));
                rc = loopcxt_init_iterator(lc, LOOPITER_FL_FREE);
                if (rc)
                        return rc;
@@ -1450,6 +1699,8 @@ int loopcxt_find_unused(struct loopdev_cxt *lc)
                rc = loopcxt_next(lc);
                loopcxt_deinit_iterator(lc);
                DBG(CXT, ul_debugobj(lc, "find_unused by scan [rc=%d]", rc));
+               if (rc)
+                       return -ENOENT;
        }
        return rc;
 }
@@ -1493,6 +1744,20 @@ char *loopdev_get_backing_file(const char *device)
        return res;
 }
 
+/*
+ * Returns: TRUE/FALSE
+ */
+int loopdev_has_backing_file(const char *device)
+{
+       char *tmp = loopdev_get_backing_file(device);
+
+       if (tmp) {
+               free(tmp);
+               return 1;
+       }
+       return 0;
+}
+
 /*
  * Returns: TRUE/FALSE
  */
@@ -1519,6 +1784,9 @@ int loopdev_is_used(const char *device, const char *filename,
        return rc;
 }
 
+/*
+ * Returns: 0 = success, < 0 error
+ */
 int loopdev_delete(const char *device)
 {
        struct loopdev_cxt lc;
@@ -1577,6 +1845,7 @@ int loopcxt_find_overlap(struct loopdev_cxt *lc, const char *filename,
        if (!filename)
                return -EINVAL;
 
+       DBG(CXT, ul_debugobj(lc, "find_overlap requested"));
        hasst = !stat(filename, &st);
 
        rc = loopcxt_init_iterator(lc, LOOPITER_FL_USED);
@@ -1588,10 +1857,13 @@ int loopcxt_find_overlap(struct loopdev_cxt *lc, const char *filename,
 
                rc = loopcxt_is_used(lc, hasst ? &st : NULL,
                                     filename, offset, sizelimit, 0);
-               if (!rc)
-                       continue;       /* unused */
-               if (rc < 0)
-                       break;          /* error */
+               /*
+                * Either the loopdev is unused or we've got an error which can
+                * happen when we are racing with device autoclear. Just ignore
+                * this loopdev...
+                */
+               if (rc <= 0)
+                       continue;
 
                DBG(CXT, ul_debugobj(lc, "found %s backed by %s",
                        loopcxt_get_device(lc), filename));
@@ -1600,13 +1872,13 @@ int loopcxt_find_overlap(struct loopdev_cxt *lc, const char *filename,
                if (rc) {
                        DBG(CXT, ul_debugobj(lc, "failed to get offset for device %s",
                                loopcxt_get_device(lc)));
-                       break;
+                       continue;
                }
                rc = loopcxt_get_sizelimit(lc, &lc_sizelimit);
                if (rc) {
                        DBG(CXT, ul_debugobj(lc, "failed to get sizelimit for device %s",
                                loopcxt_get_device(lc)));
-                       break;
+                       continue;
                }
 
                /* full match */
@@ -1633,6 +1905,7 @@ int loopcxt_find_overlap(struct loopdev_cxt *lc, const char *filename,
                rc = 0; /* not found */
 found:
        loopcxt_deinit_iterator(lc);
+       DBG(CXT, ul_debugobj(lc, "find_overlap done [rc=%d]", rc));
        return rc;
 }
 
@@ -1658,7 +1931,7 @@ char *loopdev_find_by_backing_file(const char *filename, uint64_t offset, uint64
 
 /*
  * Returns number of loop devices associated with @file, if only one loop
- * device is associeted with the given @filename and @loopdev is not NULL then
+ * device is associated with the given @filename and @loopdev is not NULL then
  * @loopdev returns name of the device.
  */
 int loopdev_count_by_backing_file(const char *filename, char **loopdev)
@@ -1678,7 +1951,7 @@ int loopdev_count_by_backing_file(const char *filename, char **loopdev)
        while(loopcxt_next(&lc) == 0) {
                char *backing = loopcxt_get_backing_file(&lc);
 
-               if (!backing || strcmp(backing, filename)) {
+               if (!backing || strcmp(backing, filename) != 0) {
                        free(backing);
                        continue;
                }
@@ -1698,3 +1971,22 @@ int loopdev_count_by_backing_file(const char *filename, char **loopdev)
        return count;
 }
 
+#ifdef TEST_PROGRAM_LOOPDEV
+int main(int argc, char *argv[])
+{
+       if (argc < 2)
+               goto usage;
+
+       if (strcmp(argv[1], "--is-loopdev") == 0 && argc == 3)
+               printf("%s: %s\n", argv[2], is_loopdev(argv[2]) ? "OK" : "FAIL");
+       else
+               goto usage;
+
+       return EXIT_SUCCESS;
+usage:
+       fprintf(stderr, "usage: %1$s --is-loopdev <dev>\n",
+                       program_invocation_short_name);
+       return EXIT_FAILURE;
+}
+#endif
+