#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);
}
/*
#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
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) {
}
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;
}
*
* * 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)
*
* 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;
* @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
*/
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.
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)));
}
/*
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));
/*
* @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.
*/
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;
}
/*
}
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;
/*
* @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);
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
*/
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);
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
*/
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);
*/
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;
}
*/
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;
}
*/
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;
}
*/
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)) {
uint64_t sizelimit,
int flags)
{
- ino_t ino;
- dev_t dev;
+ ino_t ino = 0;
+ dev_t dev = 0;
if (!lc)
return 0;
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;
}
{
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;
{
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
{
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)
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;
}
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)) {
} 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) {
"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;
return 0;
}
+
/*
- * @cl: context
+ * @lc: context
*
* Associate the current device (see loopcxt_{set,get}_device()) with
* a file (see loopcxt_set_backing_file()).
*/
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;
/*
* 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"));
}
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)
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;
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;
}
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;
}
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"));
* 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)
{
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);
}
if (rc < 0) {
+ DBG(CXT, ul_debugobj(lc, "using loop scan"));
rc = loopcxt_init_iterator(lc, LOOPITER_FL_FREE);
if (rc)
return rc;
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;
}
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
*/
return rc;
}
+/*
+ * Returns: 0 = success, < 0 error
+ */
int loopdev_delete(const char *device)
{
struct loopdev_cxt lc;
if (!filename)
return -EINVAL;
+ DBG(CXT, ul_debugobj(lc, "find_overlap requested"));
hasst = !stat(filename, &st);
rc = loopcxt_init_iterator(lc, LOOPITER_FL_USED);
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));
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 */
rc = 0; /* not found */
found:
loopcxt_deinit_iterator(lc);
+ DBG(CXT, ul_debugobj(lc, "find_overlap done [rc=%d]", rc));
return rc;
}
/*
* 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)
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;
}
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
+