1/* losetup.c - Loopback setup 2 * 3 * Copyright 2012 Rob Landley <rob@landley.net> 4 * 5 * No standard. (Sigh.) 6 7USE_LOSETUP(NEWTOY(losetup, ">2S(sizelimit)#s(show)ro#j:fdcaD[!afj]", TOYFLAG_SBIN)) 8 9config LOSETUP 10 bool "losetup" 11 default y 12 help 13 usage: losetup [-cdrs] [-o OFFSET] [-S SIZE] {-d DEVICE...|-j FILE|-af|{DEVICE FILE}} 14 15 Associate a loopback device with a file, or show current file (if any) 16 associated with a loop device. 17 18 Instead of a device: 19 -a Iterate through all loopback devices 20 -f Find first unused loop device (may create one) 21 -j FILE Iterate through all loopback devices associated with FILE 22 23 existing: 24 -c Check capacity (file size changed) 25 -d DEV Detach loopback device 26 -D Detach all loopback devices 27 28 new: 29 -s Show device name (alias --show) 30 -o OFF Start association at offset OFF into FILE 31 -r Read only 32 -S SIZE Limit SIZE of loopback association (alias --sizelimit) 33*/ 34 35#define FOR_losetup 36#include "toys.h" 37#include <linux/loop.h> 38 39GLOBALS( 40 char *j; 41 long o, S; 42 43 int openflags; 44 dev_t jdev; 45 ino_t jino; 46 char *dir; 47) 48 49// -f: *device is NULL 50 51// Perform requested operation on one device. Returns 1 if handled, 0 if error 52static int loopback_setup(char *device, char *file) 53{ 54 struct loop_info64 *loop = (void *)(toybuf+32); 55 int lfd = -1, ffd = -1; 56 int racy = !device; 57 58 // Open file (ffd) and loop device (lfd) 59 60 if (file) ffd = xopen(file, TT.openflags); 61 if (!device) { 62 int i, cfd = open("/dev/loop-control", O_RDWR); 63 64 // We assume /dev is devtmpfs so device creation has no lag. Otherwise 65 // just preallocate loop devices and stay within them. 66 67 // mount -o loop depends on found device being at the start of toybuf. 68 if (cfd != -1) { 69 if (0 <= (i = ioctl(cfd, LOOP_CTL_GET_FREE))) { 70 sprintf(device = toybuf, "%s/loop%d", TT.dir, i); 71 } 72 close(cfd); 73 } 74 } 75 76 if (device) lfd = open(device, TT.openflags); 77 78 // Stat the loop device to see if there's a current association. 79 memset(loop, 0, sizeof(struct loop_info64)); 80 if (-1 == lfd || ioctl(lfd, LOOP_GET_STATUS64, loop)) { 81 if (errno == ENXIO && (FLAG(a) || FLAG(j))) goto done; 82 // ENXIO expected if we're just trying to print the first unused device. 83 if (errno == ENXIO && FLAG(f) && !file) { 84 puts(device); 85 goto done; 86 } 87 if (errno != ENXIO || !file) { 88 perror_msg_raw(device ? device : "-f"); 89 goto done; 90 } 91 } 92 93 // Skip -j filtered devices 94 if (TT.j && (loop->lo_device != TT.jdev || loop->lo_inode != TT.jino)) 95 goto done; 96 97 // Check size of file or delete existing association 98 if (FLAG(c) || FLAG(d)) { 99 // The constant is LOOP_SET_CAPACITY 100 if (ioctl(lfd, FLAG(c) ? 0x4C07 : LOOP_CLR_FD, 0)) { 101 perror_msg_raw(device); 102 goto done; 103 } 104 // Associate file with this device? 105 } else if (file) { 106 char *f_path = xabspath(file, ABS_PATH); 107 108 if (!f_path) perror_exit("%s", file); // already opened but if deleted since 109 if (ioctl(lfd, LOOP_SET_FD, ffd)) { 110 free(f_path); 111 if (racy && errno == EBUSY) return 1; 112 perror_exit("%s=%s", device, file); 113 } 114 xstrncpy((char *)loop->lo_file_name, f_path, LO_NAME_SIZE); 115 free(f_path); 116 loop->lo_offset = TT.o; 117 loop->lo_sizelimit = TT.S; 118 if (ioctl(lfd, LOOP_SET_STATUS64, loop)) perror_exit("%s=%s", device, file); 119 if (FLAG(s)) puts(device); 120 } 121 else { 122 xprintf("%s: [%lld]:%llu (%s)", device, (long long)loop->lo_device, 123 (long long)loop->lo_inode, loop->lo_file_name); 124 if (loop->lo_offset) xprintf(", offset %llu", 125 (unsigned long long)loop->lo_offset); 126 if (loop->lo_sizelimit) xprintf(", sizelimit %llu", 127 (unsigned long long)loop->lo_sizelimit); 128 xputc('\n'); 129 } 130 131done: 132 xclose(ffd); 133 xclose(lfd); 134 return 0; 135} 136 137// Perform an action on all currently existing loop devices 138static int dash_a(struct dirtree *node) 139{ 140 char *s = node->name; 141 142 // Initial /dev node needs to recurse down one level, then only loop[0-9]* 143 if (!node->parent) return DIRTREE_RECURSE; 144 if (strncmp(s, "loop", 4) || !isdigit(s[4])) return 0; 145 146 s = dirtree_path(node, 0); 147 loopback_setup(s, 0); 148 free(s); 149 150 return 0; 151} 152 153void losetup_main(void) 154{ 155 char **s; 156 157 TT.dir = CFG_TOYBOX_ON_ANDROID ? "/dev/block" : "/dev"; 158 TT.openflags = FLAG(r) ? O_RDONLY : O_RDWR; 159 160 if (TT.j) { 161 struct stat st; 162 163 xstat(TT.j, &st); 164 TT.jdev = st.st_dev; 165 TT.jino = st.st_ino; 166 } 167 168 // With just device, display current association 169 // -a, -f substitute for device 170 // -j substitute for device 171 172 // new association: S size o offset rs - need a file 173 // existing association: cd 174 175 // -f(dc FILE) 176 177 if (FLAG(D)) toys.optflags |= FLAG_a | FLAG_d; 178 179 if (FLAG(f)) { 180 if (toys.optc > 1) perror_exit("max 1 arg"); 181 while (loopback_setup(NULL, *toys.optargs)); 182 } else if (FLAG(a) || FLAG(j)) { 183 if (toys.optc) error_exit("bad args"); 184 dirtree_read(TT.dir, dash_a); 185 // Do we need one DEVICE argument? 186 } else { 187 char *file = (FLAG(c) || FLAG(d)) ? NULL : toys.optargs[1]; 188 189 if (!toys.optc || (file && toys.optc != 2)) 190 help_exit("needs %d arg%s", 1+!!file, file ? "s" : ""); 191 for (s = toys.optargs; *s; s++) { 192 loopback_setup(*s, file); 193 if (file) break; 194 } 195 } 196} 197