| /* losetup.c - Loopback setup |
| * |
| * Copyright 2012 Rob Landley <rob@landley.net> |
| * |
| * No standard. (Sigh.) |
| |
| USE_LOSETUP(NEWTOY(losetup, ">2S(sizelimit)#s(show)ro#j:fdca[!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 Iterate through all loopback devices associated with FILE |
| |
| existing: |
| -c Check capacity (file size changed) |
| -d Detach loopback device |
| |
| new: |
| -s Show device name (alias --show) |
| -o Start assocation at OFFSET into FILE |
| -r Read only |
| -S Limit SIZE of loopback association (alias --sizelimit) |
| */ |
| |
| #define FOR_losetup |
| #include "toys.h" |
| #include <linux/loop.h> |
| |
| GLOBALS( |
| char *jfile; |
| long offset; |
| long size; |
| |
| int openflags; |
| dev_t jdev; |
| ino_t jino; |
| ) |
| |
| /* |
| todo: basic /dev file association |
| associate DEV FILE |
| #-a |
| cdfjosS |
| allocate new loop device: |
| /dev/loop-control |
| https://lkml.org/lkml/2011/7/26/148 |
| */ |
| |
| // -f: *device is NULL |
| |
| // Perform requested operation on one device. Returns 1 if handled, 0 if error |
| static void loopback_setup(char *device, char *file) |
| { |
| struct loop_info64 *loop = (void *)(toybuf+32); |
| int lfd = -1, ffd = ffd; |
| unsigned flags = toys.optflags; |
| |
| // 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, 0x4C82))) // LOOP_CTL_GET_FREE |
| sprintf(device = toybuf, "/dev/loop%d", 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 && (flags & (FLAG_a|FLAG_j))) goto done; |
| if (errno != ENXIO || !file) { |
| perror_msg_raw(device ? device : "-f"); |
| goto done; |
| } |
| } |
| |
| // Skip -j filtered devices |
| if (TT.jfile && (loop->lo_device != TT.jdev || loop->lo_inode != TT.jino)) |
| goto done; |
| |
| // Check size of file or delete existing association |
| if (flags & (FLAG_c|FLAG_d)) { |
| // The constant is LOOP_SET_CAPACITY |
| if (ioctl(lfd, (flags & FLAG_c) ? 0x4C07 : LOOP_CLR_FD, 0)) { |
| perror_msg_raw(device); |
| goto done; |
| } |
| // Associate file with this device? |
| } else if (file) { |
| char *s = xabspath(file, 1); |
| |
| if (!s) perror_exit("file"); // already opened, but if deleted since... |
| if (ioctl(lfd, LOOP_SET_FD, ffd)) perror_exit("%s=%s", device, file); |
| loop->lo_offset = TT.offset; |
| loop->lo_sizelimit = TT.size; |
| xstrncpy((char *)loop->lo_file_name, s, LO_NAME_SIZE); |
| s[LO_NAME_SIZE-1] = 0; |
| if (ioctl(lfd, LOOP_SET_STATUS64, loop)) perror_exit("%s=%s", device, file); |
| if (flags & FLAG_s) printf("%s", device); |
| free(s); |
| } else if (flags & FLAG_f) printf("%s", device); |
| else { |
| xprintf("%s: [%04llx]:%llu (%s)", device, loop->lo_device, loop->lo_inode, |
| loop->lo_file_name); |
| if (loop->lo_offset) xprintf(", offset %llu", loop->lo_offset); |
| if (loop->lo_sizelimit) xprintf(", sizelimit %llu", loop->lo_sizelimit); |
| xputc('\n'); |
| } |
| |
| done: |
| if (file) close(ffd); |
| if (lfd != -1) close(lfd); |
| } |
| |
| // 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 (*s == '/') 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.openflags = (toys.optflags & FLAG_r) ? O_RDONLY : O_RDWR; |
| |
| if (TT.jfile) { |
| struct stat st; |
| |
| xstat(TT.jfile, &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 (toys.optflags & FLAG_f) { |
| if (toys.optc > 1) perror_exit("max 1 arg"); |
| loopback_setup(NULL, *toys.optargs); |
| } else if (toys.optflags & (FLAG_a|FLAG_j)) { |
| if (toys.optc) error_exit("bad args"); |
| dirtree_read("/dev", dash_a); |
| // Do we need one DEVICE argument? |
| } else { |
| char *file = (toys.optflags & (FLAG_d|FLAG_c)) ? 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; |
| } |
| } |
| } |