xref: /aosp_15_r20/external/toybox/toys/other/losetup.c (revision cf5a6c84e2b8763fc1a7db14496fd4742913b199)
1 /* losetup.c - Loopback setup
2  *
3  * Copyright 2012 Rob Landley <[email protected]>
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 = -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     if (CFG_TOYBOX_ON_ANDROID && device) {
75       // ANDROID SPECIFIC: /dev is not devtmpfs, instead an userspace daemon
76       // ueventd is responsible for creating the loop devices under /dev.
77       // Wait for the uevent to be processed to avoid race.
78       long long timeout = millitime() + 5000;
79       do {
80         if (!access(device, F_OK) || errno != ENOENT) break;
81         msleep(20);
82       } while (millitime() < timeout);
83     }
84   }
85 
86   if (device) lfd = open(device, TT.openflags);
87 
88   // Stat the loop device to see if there's a current association.
89   memset(loop, 0, sizeof(struct loop_info64));
90   if (-1 == lfd || ioctl(lfd, LOOP_GET_STATUS64, loop)) {
91     if (errno == ENXIO && (FLAG(a) || FLAG(j))) goto done;
92     // ENXIO expected if we're just trying to print the first unused device.
93     if (errno == ENXIO && FLAG(f) && !file) {
94       puts(device);
95       goto done;
96     }
97     if (errno != ENXIO || !file) {
98       perror_msg_raw(device ? device : "-f");
99       goto done;
100     }
101   }
102 
103   // Skip -j filtered devices
104   if (TT.j && (loop->lo_device != TT.jdev || loop->lo_inode != TT.jino))
105     goto done;
106 
107   // Check size of file or delete existing association
108   if (FLAG(c) || FLAG(d)) {
109     // The constant is LOOP_SET_CAPACITY
110     if (ioctl(lfd, FLAG(c) ? 0x4C07 : LOOP_CLR_FD, 0)) {
111       perror_msg_raw(device);
112       goto done;
113     }
114   // Associate file with this device?
115   } else if (file) {
116     char *f_path = xabspath(file, ABS_PATH);
117 
118     if (!f_path) perror_exit("%s", file); // already opened but if deleted since
119     if (ioctl(lfd, LOOP_SET_FD, ffd)) {
120       free(f_path);
121       if (racy && errno == EBUSY) return 1;
122       perror_exit("%s=%s", device, file);
123     }
124     xstrncpy((char *)loop->lo_file_name, f_path, LO_NAME_SIZE);
125     free(f_path);
126     loop->lo_offset = TT.o;
127     loop->lo_sizelimit = TT.S;
128     if (ioctl(lfd, LOOP_SET_STATUS64, loop)) perror_exit("%s=%s", device, file);
129     if (FLAG(s)) puts(device);
130   }
131   else {
132     xprintf("%s: [%lld]:%llu (%s)", device, (long long)loop->lo_device,
133       (long long)loop->lo_inode, loop->lo_file_name);
134     if (loop->lo_offset) xprintf(", offset %llu",
135       (unsigned long long)loop->lo_offset);
136     if (loop->lo_sizelimit) xprintf(", sizelimit %llu",
137       (unsigned long long)loop->lo_sizelimit);
138     xputc('\n');
139   }
140 
141 done:
142   xclose(ffd);
143   xclose(lfd);
144   return 0;
145 }
146 
147 // Perform an action on all currently existing loop devices
dash_a(struct dirtree * node)148 static int dash_a(struct dirtree *node)
149 {
150   char *s = node->name;
151 
152   // Initial /dev node needs to recurse down one level, then only loop[0-9]*
153   if (!node->parent) return DIRTREE_RECURSE;
154   if (strncmp(s, "loop", 4) || !isdigit(s[4])) return 0;
155 
156   s = dirtree_path(node, 0);
157   loopback_setup(s, 0);
158   free(s);
159 
160   return 0;
161 }
162 
losetup_main(void)163 void losetup_main(void)
164 {
165   char **s;
166 
167   TT.dir = CFG_TOYBOX_ON_ANDROID ? "/dev/block" : "/dev";
168   TT.openflags = FLAG(r) ? O_RDONLY : O_RDWR;
169 
170   if (TT.j) {
171     struct stat st;
172 
173     xstat(TT.j, &st);
174     TT.jdev = st.st_dev;
175     TT.jino = st.st_ino;
176   }
177 
178   // With just device, display current association
179   // -a, -f substitute for device
180   // -j substitute for device
181 
182   // new association: S size o offset rs - need a file
183   // existing association: cd
184 
185   // -f(dc FILE)
186 
187   if (FLAG(D)) toys.optflags |= FLAG_a | FLAG_d;
188 
189   if (FLAG(f)) {
190     if (toys.optc > 1) perror_exit("max 1 arg");
191     while (loopback_setup(NULL, *toys.optargs));
192   } else if (FLAG(a) || FLAG(j)) {
193     if (toys.optc) error_exit("bad args");
194     dirtree_read(TT.dir, dash_a);
195   // Do we need one DEVICE argument?
196   } else {
197     char *file = (FLAG(c) || FLAG(d)) ? NULL : toys.optargs[1];
198 
199     if (!toys.optc || (file && toys.optc != 2))
200       help_exit("needs %d arg%s", 1+!!file, file ? "s" : "");
201     for (s = toys.optargs; *s; s++) {
202       loopback_setup(*s, file);
203       if (file) break;
204     }
205   }
206 }
207