1 /* losetup.c - Loopback setup
2 *
3 * Copyright 2012 Rob Landley <rob@landley.net>
4 *
5 * No standard. (Sigh.)
6
7 USE_LOSETUP(NEWTOY(losetup, ">2S(sizelimit)#s(show)ro#j:fdcaD[!afj]", TOYFLAG_SBIN))
8
9 config 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
GLOBALS(char * j;long o,S;int openflags;dev_t jdev;ino_t jino;char * dir;)39 GLOBALS(
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
52 static int loopback_setup(char *device, char *file)
53 {
54 struct loop_info64 *loop = (void *)(toybuf+32);
55 int lfd = -1, ffd = ffd;
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 *s = xabspath(file, 1);
107
108 if (!s) perror_exit("file"); // already opened, but if deleted since...
109 if (ioctl(lfd, LOOP_SET_FD, ffd)) {
110 if (racy && errno == EBUSY) return 1;
111 perror_exit("%s=%s", device, file);
112 }
113 loop->lo_offset = TT.o;
114 loop->lo_sizelimit = TT.S;
115 xstrncpy((char *)loop->lo_file_name, s, LO_NAME_SIZE);
116 if (ioctl(lfd, LOOP_SET_STATUS64, loop)) perror_exit("%s=%s", device, file);
117 if (FLAG(s)) puts(device);
118 free(s);
119 }
120 else {
121 xprintf("%s: [%lld]:%llu (%s)", device, (long long)loop->lo_device,
122 (long long)loop->lo_inode, loop->lo_file_name);
123 if (loop->lo_offset) xprintf(", offset %llu",
124 (unsigned long long)loop->lo_offset);
125 if (loop->lo_sizelimit) xprintf(", sizelimit %llu",
126 (unsigned long long)loop->lo_sizelimit);
127 xputc('\n');
128 }
129
130 done:
131 if (file) close(ffd);
132 if (lfd != -1) close(lfd);
133 return 0;
134 }
135
136 // Perform an action on all currently existing loop devices
dash_a(struct dirtree * node)137 static int dash_a(struct dirtree *node)
138 {
139 char *s = node->name;
140
141 // Initial /dev node needs to recurse down one level, then only loop[0-9]*
142 if (!node->parent) return DIRTREE_RECURSE;
143 if (strncmp(s, "loop", 4) || !isdigit(s[4])) return 0;
144
145 s = dirtree_path(node, 0);
146 loopback_setup(s, 0);
147 free(s);
148
149 return 0;
150 }
151
losetup_main(void)152 void losetup_main(void)
153 {
154 char **s;
155
156 TT.dir = CFG_TOYBOX_ON_ANDROID ? "/dev/block" : "/dev";
157 TT.openflags = FLAG(r) ? O_RDONLY : O_RDWR;
158
159 if (TT.j) {
160 struct stat st;
161
162 xstat(TT.j, &st);
163 TT.jdev = st.st_dev;
164 TT.jino = st.st_ino;
165 }
166
167 // With just device, display current association
168 // -a, -f substitute for device
169 // -j substitute for device
170
171 // new association: S size o offset rs - need a file
172 // existing association: cd
173
174 // -f(dc FILE)
175
176 if (FLAG(D)) toys.optflags |= FLAG_a | FLAG_d;
177
178 if (FLAG(f)) {
179 if (toys.optc > 1) perror_exit("max 1 arg");
180 while (loopback_setup(NULL, *toys.optargs));
181 } else if (FLAG(a) || FLAG(j)) {
182 if (toys.optc) error_exit("bad args");
183 dirtree_read(TT.dir, dash_a);
184 // Do we need one DEVICE argument?
185 } else {
186 char *file = (FLAG(c) || FLAG(d)) ? NULL : toys.optargs[1];
187
188 if (!toys.optc || (file && toys.optc != 2))
189 help_exit("needs %d arg%s", 1+!!file, file ? "s" : "");
190 for (s = toys.optargs; *s; s++) {
191 loopback_setup(*s, file);
192 if (file) break;
193 }
194 }
195 }
196