aboutsummaryrefslogtreecommitdiff
path: root/toys/other/losetup.c
blob: 7b8aed4f7a91ca4c764a8c64a8c7ec3d812e38b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/* losetup.c - Loopback setup
 *
 * Copyright 2012 Rob Landley <rob@landley.net>
 *
 * No standard. (Sigh.)

USE_LOSETUP(NEWTOY(losetup, ">2S(sizelimit)#s(show)ro#j:fdcaD[!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 FILE	Iterate through all loopback devices associated with FILE

    existing:
    -c	Check capacity (file size changed)
    -d DEV	Detach loopback device
    -D	Detach all loopback devices

    new:
    -s	Show device name (alias --show)
    -o OFF	Start association at offset OFF into FILE
    -r	Read only
    -S SIZE	Limit SIZE of loopback association (alias --sizelimit)
*/

#define FOR_losetup
#include "toys.h"
#include <linux/loop.h>

GLOBALS(
  char *j;
  long o, S;

  int openflags;
  dev_t jdev;
  ino_t jino;
  char *dir;
)

// -f: *device is NULL

// Perform requested operation on one device. Returns 1 if handled, 0 if error
static int loopback_setup(char *device, char *file)
{
  struct loop_info64 *loop = (void *)(toybuf+32);
  int lfd = -1, ffd = -1;
  int racy = !device;

  // 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, LOOP_CTL_GET_FREE))) {
        sprintf(device = toybuf, "%s/loop%d", TT.dir, 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 && (FLAG(a) || FLAG(j))) goto done;
    // ENXIO expected if we're just trying to print the first unused device.
    if (errno == ENXIO && FLAG(f) && !file) {
      puts(device);
      goto done;
    }
    if (errno != ENXIO || !file) {
      perror_msg_raw(device ? device : "-f");
      goto done;
    }
  }

  // Skip -j filtered devices
  if (TT.j && (loop->lo_device != TT.jdev || loop->lo_inode != TT.jino))
    goto done;

  // Check size of file or delete existing association
  if (FLAG(c) || FLAG(d)) {
    // The constant is LOOP_SET_CAPACITY
    if (ioctl(lfd, FLAG(c) ? 0x4C07 : LOOP_CLR_FD, 0)) {
      perror_msg_raw(device);
      goto done;
    }
  // Associate file with this device?
  } else if (file) {
    char *f_path = xabspath(file, 1);

    if (!f_path) perror_exit("file"); // already opened, but if deleted since...
    if (ioctl(lfd, LOOP_SET_FD, ffd)) {
      free(f_path);
      if (racy && errno == EBUSY) return 1;
      perror_exit("%s=%s", device, file);
    }
    xstrncpy((char *)loop->lo_file_name, f_path, LO_NAME_SIZE);
    free(f_path);
    loop->lo_offset = TT.o;
    loop->lo_sizelimit = TT.S;
    if (ioctl(lfd, LOOP_SET_STATUS64, loop)) perror_exit("%s=%s", device, file);
    if (FLAG(s)) puts(device);
  }
  else {
    xprintf("%s: [%lld]:%llu (%s)", device, (long long)loop->lo_device,
      (long long)loop->lo_inode, loop->lo_file_name);
    if (loop->lo_offset) xprintf(", offset %llu",
      (unsigned long long)loop->lo_offset);
    if (loop->lo_sizelimit) xprintf(", sizelimit %llu",
      (unsigned long long)loop->lo_sizelimit);
    xputc('\n');
  }

done:
  xclose(ffd);
  xclose(lfd);
  return 0;
}

// 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 (!node->parent) 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.dir = CFG_TOYBOX_ON_ANDROID ? "/dev/block" : "/dev";
  TT.openflags = FLAG(r) ? O_RDONLY : O_RDWR;

  if (TT.j) {
    struct stat st;

    xstat(TT.j, &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 (FLAG(D)) toys.optflags |= FLAG_a | FLAG_d;

  if (FLAG(f)) {
    if (toys.optc > 1) perror_exit("max 1 arg");
    while (loopback_setup(NULL, *toys.optargs));
  } else if (FLAG(a) || FLAG(j)) {
    if (toys.optc) error_exit("bad args");
    dirtree_read(TT.dir, dash_a);
  // Do we need one DEVICE argument?
  } else {
    char *file = (FLAG(c) || FLAG(d)) ? 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;
    }
  }
}