/* losetup.c - Loopback setup * * Copyright 2012 Rob Landley * * No standard. (Sigh.) USE_LOSETUP(NEWTOY(losetup, ">2S(sizelimit)#s(show)ro#j:fdcaD[!afj]", TOYFLAG_SBIN)) config LOSETUP bool "losetup" default y help usage: losetup [-cdrs] [-o OFFSET] [-S SIZE] {-d DEVICE...|-j FILE|-af|{DEVICE FILE}} Associate a loopback device with a file, or show current file (if any) associated with a loop device. Instead of a device: -a Iterate through all loopback devices -f Find first unused loop device (may create one) -j FILE Iterate through all loopback devices associated with FILE existing: -c Check capacity (file size changed) -d DEV Detach loopback device -D Detach all loopback devices new: -s Show device name (alias --show) -o OFF Start association at offset OFF into FILE -r Read only -S SIZE Limit SIZE of loopback association (alias --sizelimit) */ #define FOR_losetup #include "toys.h" #include GLOBALS( char *j; long o, S; int openflags; dev_t jdev; ino_t jino; char *dir; ) // -f: *device is NULL // Perform requested operation on one device. Returns 1 if handled, 0 if error static int loopback_setup(char *device, char *file) { struct loop_info64 *loop = (void *)(toybuf+32); int lfd = -1, ffd = -1; int racy = !device; // Open file (ffd) and loop device (lfd) if (file) ffd = xopen(file, TT.openflags); if (!device) { int i, cfd = open("/dev/loop-control", O_RDWR); // We assume /dev is devtmpfs so device creation has no lag. Otherwise // just preallocate loop devices and stay within them. // mount -o loop depends on found device being at the start of toybuf. if (cfd != -1) { if (0 <= (i = ioctl(cfd, LOOP_CTL_GET_FREE))) { sprintf(device = toybuf, "%s/loop%d", TT.dir, i); } close(cfd); } } if (device) lfd = open(device, TT.openflags); // Stat the loop device to see if there's a current association. memset(loop, 0, sizeof(struct loop_info64)); if (-1 == lfd || ioctl(lfd, LOOP_GET_STATUS64, loop)) { if (errno == ENXIO && (FLAG(a) || FLAG(j))) goto done; // ENXIO expected if we're just trying to print the first unused device. if (errno == ENXIO && FLAG(f) && !file) { puts(device); goto done; } if (errno != ENXIO || !file) { perror_msg_raw(device ? device : "-f"); goto done; } } // Skip -j filtered devices if (TT.j && (loop->lo_device != TT.jdev || loop->lo_inode != TT.jino)) goto done; // Check size of file or delete existing association if (FLAG(c) || FLAG(d)) { // The constant is LOOP_SET_CAPACITY if (ioctl(lfd, FLAG(c) ? 0x4C07 : LOOP_CLR_FD, 0)) { perror_msg_raw(device); goto done; } // Associate file with this device? } else if (file) { char *f_path = xabspath(file, 1); if (!f_path) perror_exit("file"); // already opened, but if deleted since... if (ioctl(lfd, LOOP_SET_FD, ffd)) { free(f_path); if (racy && errno == EBUSY) return 1; perror_exit("%s=%s", device, file); } xstrncpy((char *)loop->lo_file_name, f_path, LO_NAME_SIZE); free(f_path); loop->lo_offset = TT.o; loop->lo_sizelimit = TT.S; if (ioctl(lfd, LOOP_SET_STATUS64, loop)) perror_exit("%s=%s", device, file); if (FLAG(s)) puts(device); } else { xprintf("%s: [%lld]:%llu (%s)", device, (long long)loop->lo_device, (long long)loop->lo_inode, loop->lo_file_name); if (loop->lo_offset) xprintf(", offset %llu", (unsigned long long)loop->lo_offset); if (loop->lo_sizelimit) xprintf(", sizelimit %llu", (unsigned long long)loop->lo_sizelimit); xputc('\n'); } done: xclose(ffd); xclose(lfd); return 0; } // Perform an action on all currently existing loop devices static int dash_a(struct dirtree *node) { char *s = node->name; // Initial /dev node needs to recurse down one level, then only loop[0-9]* if (!node->parent) return DIRTREE_RECURSE; if (strncmp(s, "loop", 4) || !isdigit(s[4])) return 0; s = dirtree_path(node, 0); loopback_setup(s, 0); free(s); return 0; } void losetup_main(void) { char **s; TT.dir = CFG_TOYBOX_ON_ANDROID ? "/dev/block" : "/dev"; TT.openflags = FLAG(r) ? O_RDONLY : O_RDWR; if (TT.j) { struct stat st; xstat(TT.j, &st); TT.jdev = st.st_dev; TT.jino = st.st_ino; } // With just device, display current association // -a, -f substitute for device // -j substitute for device // new association: S size o offset rs - need a file // existing association: cd // -f(dc FILE) if (FLAG(D)) toys.optflags |= FLAG_a | FLAG_d; if (FLAG(f)) { if (toys.optc > 1) perror_exit("max 1 arg"); while (loopback_setup(NULL, *toys.optargs)); } else if (FLAG(a) || FLAG(j)) { if (toys.optc) error_exit("bad args"); dirtree_read(TT.dir, dash_a); // Do we need one DEVICE argument? } else { char *file = (FLAG(c) || FLAG(d)) ? NULL : toys.optargs[1]; if (!toys.optc || (file && toys.optc != 2)) help_exit("needs %d arg%s", 1+!!file, file ? "s" : ""); for (s = toys.optargs; *s; s++) { loopback_setup(*s, file); if (file) break; } } }