| /* |
| * Copyright (C) 2011 Karel Zak <kzak@redhat.com> |
| * |
| * This file may be redistributed under the terms of the |
| * GNU Lesser General Public License. |
| */ |
| |
| /* |
| * DOCS: - "lo@" prefix for fstype is unsupported |
| */ |
| |
| #include <blkid.h> |
| |
| #include "mountP.h" |
| #include "loopdev.h" |
| #include "linux_version.h" |
| |
| |
| int mnt_context_is_loopdev(struct libmnt_context *cxt) |
| { |
| const char *type, *src; |
| |
| assert(cxt); |
| |
| /* The mount flags have to be merged, otherwise we have to use |
| * expensive mnt_context_get_user_mflags() instead of cxt->user_mountflags. */ |
| assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); |
| |
| if (!cxt->fs) |
| return 0; |
| src = mnt_fs_get_srcpath(cxt->fs); |
| if (!src) |
| return 0; /* backing file not set */ |
| |
| if (cxt->user_mountflags & (MNT_MS_LOOP | |
| MNT_MS_OFFSET | |
| MNT_MS_SIZELIMIT)) { |
| |
| DBG(CXT, ul_debugobj(cxt, "loopdev specific options detected")); |
| return 1; |
| } |
| |
| if ((cxt->mountflags & (MS_BIND | MS_MOVE)) |
| || mnt_context_propagation_only(cxt)) |
| return 0; |
| |
| /* Automatically create a loop device from a regular file if a |
| * filesystem is not specified or the filesystem is known for libblkid |
| * (these filesystems work with block devices only). The file size |
| * should be at least 1KiB, otherwise we will create an empty loopdev with |
| * no mountable filesystem... |
| * |
| * Note that there is no restriction (on kernel side) that would prevent a regular |
| * file as a mount(2) source argument. A filesystem that is able to mount |
| * regular files could be implemented. |
| */ |
| type = mnt_fs_get_fstype(cxt->fs); |
| |
| if (mnt_fs_is_regular(cxt->fs) && |
| (!type || strcmp(type, "auto") == 0 || blkid_known_fstype(type))) { |
| struct stat st; |
| |
| if (stat(src, &st) == 0 && S_ISREG(st.st_mode) && |
| st.st_size > 1024) { |
| DBG(CXT, ul_debugobj(cxt, "automatically enabling loop= option")); |
| cxt->user_mountflags |= MNT_MS_LOOP; |
| mnt_optstr_append_option(&cxt->fs->user_optstr, "loop", NULL); |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| /* Check if there already exists a mounted loop device on the mountpoint node |
| * with the same parameters. |
| */ |
| static int __attribute__((nonnull)) |
| is_mounted_same_loopfile(struct libmnt_context *cxt, |
| const char *target, |
| const char *backing_file, |
| uint64_t offset) |
| { |
| struct libmnt_table *tb; |
| struct libmnt_iter itr; |
| struct libmnt_fs *fs; |
| struct libmnt_cache *cache; |
| const char *bf; |
| int rc = 0; |
| |
| assert(cxt); |
| assert(cxt->fs); |
| assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); |
| |
| if (!target || !backing_file || mnt_context_get_mtab(cxt, &tb)) |
| return 0; |
| |
| DBG(CXT, ul_debugobj(cxt, "checking if %s mounted on %s", |
| backing_file, target)); |
| |
| cache = mnt_context_get_cache(cxt); |
| mnt_reset_iter(&itr, MNT_ITER_BACKWARD); |
| |
| bf = cache ? mnt_resolve_path(backing_file, cache) : backing_file; |
| |
| /* Search for a mountpoint node in mtab, proceed if any of these have the |
| * loop option set or the device is a loop device |
| */ |
| while (rc == 0 && mnt_table_next_fs(tb, &itr, &fs) == 0) { |
| const char *src = mnt_fs_get_source(fs); |
| const char *opts = mnt_fs_get_user_options(fs); |
| char *val; |
| size_t len; |
| |
| if (!src || !mnt_fs_match_target(fs, target, cache)) |
| continue; |
| |
| rc = 0; |
| |
| if (strncmp(src, "/dev/loop", 9) == 0) { |
| rc = loopdev_is_used((char *) src, bf, offset, LOOPDEV_FL_OFFSET); |
| |
| } else if (opts && (cxt->user_mountflags & MNT_MS_LOOP) && |
| mnt_optstr_get_option(opts, "loop", &val, &len) == 0 && val) { |
| |
| val = strndup(val, len); |
| rc = loopdev_is_used((char *) val, bf, offset, LOOPDEV_FL_OFFSET); |
| free(val); |
| } |
| } |
| if (rc) |
| DBG(CXT, ul_debugobj(cxt, "%s already mounted", backing_file)); |
| return rc; |
| } |
| |
| int mnt_context_setup_loopdev(struct libmnt_context *cxt) |
| { |
| const char *backing_file, *optstr, *loopdev = NULL; |
| char *val = NULL; |
| size_t len; |
| struct loopdev_cxt lc; |
| int rc = 0, lo_flags = 0; |
| uint64_t offset = 0, sizelimit = 0; |
| |
| assert(cxt); |
| assert(cxt->fs); |
| assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); |
| |
| if (!cxt) |
| return -EINVAL; |
| |
| backing_file = mnt_fs_get_srcpath(cxt->fs); |
| if (!backing_file) |
| return -EINVAL; |
| |
| DBG(CXT, ul_debugobj(cxt, "trying to setup loopdev for %s", backing_file)); |
| |
| if (cxt->mountflags & MS_RDONLY) { |
| DBG(CXT, ul_debugobj(cxt, "enabling READ-ONLY flag")); |
| lo_flags |= LO_FLAGS_READ_ONLY; |
| } |
| |
| rc = loopcxt_init(&lc, 0); |
| if (rc) |
| return rc; |
| |
| optstr = mnt_fs_get_user_options(cxt->fs); |
| |
| /* |
| * loop= |
| */ |
| if (rc == 0 && (cxt->user_mountflags & MNT_MS_LOOP) && |
| mnt_optstr_get_option(optstr, "loop", &val, &len) == 0 && val) { |
| |
| val = strndup(val, len); |
| rc = val ? loopcxt_set_device(&lc, val) : -ENOMEM; |
| free(val); |
| |
| if (rc == 0) |
| loopdev = loopcxt_get_device(&lc); |
| } |
| |
| /* |
| * offset= |
| */ |
| if (rc == 0 && (cxt->user_mountflags & MNT_MS_OFFSET) && |
| mnt_optstr_get_option(optstr, "offset", &val, &len) == 0) { |
| rc = mnt_parse_offset(val, len, &offset); |
| if (rc) { |
| DBG(CXT, ul_debugobj(cxt, "failed to parse offset=")); |
| rc = -MNT_ERR_MOUNTOPT; |
| } |
| } |
| |
| /* |
| * sizelimit= |
| */ |
| if (rc == 0 && (cxt->user_mountflags & MNT_MS_SIZELIMIT) && |
| mnt_optstr_get_option(optstr, "sizelimit", &val, &len) == 0) { |
| rc = mnt_parse_offset(val, len, &sizelimit); |
| if (rc) { |
| DBG(CXT, ul_debugobj(cxt, "failed to parse sizelimit=")); |
| rc = -MNT_ERR_MOUNTOPT; |
| } |
| } |
| |
| /* |
| * encryption= |
| */ |
| if (rc == 0 && (cxt->user_mountflags & MNT_MS_ENCRYPTION) && |
| mnt_optstr_get_option(optstr, "encryption", &val, &len) == 0) { |
| DBG(CXT, ul_debugobj(cxt, "encryption no longer supported")); |
| rc = -MNT_ERR_MOUNTOPT; |
| } |
| |
| if (rc == 0 && is_mounted_same_loopfile(cxt, |
| mnt_context_get_target(cxt), |
| backing_file, offset)) |
| rc = -EBUSY; |
| |
| if (rc) |
| goto done; |
| |
| /* since 2.6.37 we don't have to store backing filename to mtab |
| * because kernel provides the name in /sys. |
| */ |
| if (get_linux_version() >= KERNEL_VERSION(2, 6, 37) || |
| !mnt_context_mtab_writable(cxt)) { |
| DBG(CXT, ul_debugobj(cxt, "enabling AUTOCLEAR flag")); |
| lo_flags |= LO_FLAGS_AUTOCLEAR; |
| } |
| |
| do { |
| /* found free device */ |
| if (!loopdev) { |
| rc = loopcxt_find_unused(&lc); |
| if (rc) |
| goto done; |
| DBG(CXT, ul_debugobj(cxt, "trying to use %s", |
| loopcxt_get_device(&lc))); |
| } |
| |
| /* set device attributes |
| * -- note that loopcxt_find_unused() resets "lc" |
| */ |
| rc = loopcxt_set_backing_file(&lc, backing_file); |
| |
| if (!rc && offset) |
| rc = loopcxt_set_offset(&lc, offset); |
| if (!rc && sizelimit) |
| rc = loopcxt_set_sizelimit(&lc, sizelimit); |
| if (!rc) |
| loopcxt_set_flags(&lc, lo_flags); |
| if (rc) { |
| DBG(CXT, ul_debugobj(cxt, "failed to set loopdev attributes")); |
| goto done; |
| } |
| |
| /* setup the device */ |
| rc = loopcxt_setup_device(&lc); |
| if (!rc) |
| break; /* success */ |
| |
| if (loopdev || rc != -EBUSY) { |
| DBG(CXT, ul_debugobj(cxt, "failed to setup device")); |
| rc = -MNT_ERR_LOOPDEV; |
| goto done; |
| } |
| DBG(CXT, ul_debugobj(cxt, "loopdev stolen...trying again")); |
| } while (1); |
| |
| if (!rc) |
| rc = mnt_fs_set_source(cxt->fs, loopcxt_get_device(&lc)); |
| |
| if (!rc) { |
| /* success */ |
| cxt->flags |= MNT_FL_LOOPDEV_READY; |
| |
| if ((cxt->user_mountflags & MNT_MS_LOOP) && |
| loopcxt_is_autoclear(&lc)) { |
| /* |
| * autoclear flag accepted by the kernel, don't store |
| * the "loop=" option to mtab. |
| */ |
| cxt->user_mountflags &= ~MNT_MS_LOOP; |
| mnt_optstr_remove_option(&cxt->fs->user_optstr, "loop"); |
| } |
| |
| if (!(cxt->mountflags & MS_RDONLY) && |
| loopcxt_is_readonly(&lc)) |
| /* |
| * mount planned read-write, but loopdev is read-only, |
| * let's fix mount options... |
| */ |
| mnt_context_set_mflags(cxt, cxt->mountflags | MS_RDONLY); |
| |
| /* we have to keep the device open until mount(1), |
| * otherwise it will be auto-cleared by kernel |
| */ |
| cxt->loopdev_fd = loopcxt_get_fd(&lc); |
| loopcxt_set_fd(&lc, -1, 0); |
| } |
| done: |
| loopcxt_deinit(&lc); |
| return rc; |
| } |
| |
| /* |
| * Deletes loop device |
| */ |
| int mnt_context_delete_loopdev(struct libmnt_context *cxt) |
| { |
| const char *src; |
| int rc; |
| |
| assert(cxt); |
| assert(cxt->fs); |
| |
| if (!cxt) |
| return -EINVAL; |
| |
| src = mnt_fs_get_srcpath(cxt->fs); |
| if (!src) |
| return -EINVAL; |
| |
| if (cxt->loopdev_fd > -1) |
| close(cxt->loopdev_fd); |
| |
| rc = loopdev_delete(src); |
| cxt->flags &= ~MNT_FL_LOOPDEV_READY; |
| cxt->loopdev_fd = -1; |
| |
| DBG(CXT, ul_debugobj(cxt, "loopdev deleted [rc=%d]", rc)); |
| return rc; |
| } |
| |
| /* |
| * Clears loopdev stuff in context, should be called after |
| * failed or successful mount(2). |
| */ |
| int mnt_context_clear_loopdev(struct libmnt_context *cxt) |
| { |
| assert(cxt); |
| |
| if (!cxt) |
| return -EINVAL; |
| |
| if (mnt_context_get_status(cxt) == 0 && |
| (cxt->flags & MNT_FL_LOOPDEV_READY)) { |
| /* |
| * mount(2) failed, delete loopdev |
| */ |
| mnt_context_delete_loopdev(cxt); |
| |
| } else if (cxt->loopdev_fd > -1) { |
| /* |
| * mount(2) success, close the device |
| */ |
| DBG(CXT, ul_debugobj(cxt, "closing loopdev FD")); |
| close(cxt->loopdev_fd); |
| } |
| cxt->loopdev_fd = -1; |
| return 0; |
| } |
| |