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