aboutsummaryrefslogtreecommitdiff
path: root/toys/other/losetup.c
blob: d279c2ad69916ab70d835f5f1a6964e70f631309 (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
/* losetup.c - Loopback setup
 *
 * Copyright 2012 Rob Landley <rob@landley.net>
 *
 * No standard. (Sigh.)

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

    existing:
    -c	Check capacity (file size changed)
    -d	Detach loopback device

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

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

GLOBALS(
  char *jfile;
  long offset;
  long size;

  int openflags;
  dev_t jdev;
  ino_t jino;
)

/*
todo: basic /dev file association
  associate DEV FILE
  #-a
  cdfjosS
  allocate new loop device:
    /dev/loop-control
    https://lkml.org/lkml/2011/7/26/148
*/

// -f: *device is NULL

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

  // 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, "/dev/loop%d", 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 && (flags & (FLAG_a|FLAG_j))) return;
    if (errno != ENXIO || !file) {
      perror_msg("%s", device ? device : "-f");
      goto done;
    }
  }

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

  // Check size of file or delete existing association
  if (flags & (FLAG_c|FLAG_d)) {
    if (ioctl(lfd, (flags & FLAG_c) ? LOOP_SET_CAPACITY : LOOP_CLR_FD, 0)) {
      perror_msg("%s", device);
      goto done;
    }
  // Associate file with this device?
  } else if (file) {
    char *s = xrealpath(file);

    if (ioctl(lfd, LOOP_SET_FD, ffd)) perror_exit("%s=%s", device, file);
    loop->lo_offset = TT.offset;
    loop->lo_sizelimit = TT.size;
    strncpy((char *)loop->lo_file_name, s, LO_NAME_SIZE);
    s[LO_NAME_SIZE-1] = 0;
    if (ioctl(lfd, LOOP_SET_STATUS64, loop)) perror_exit("%s=%s", device, file);
    if (flags & FLAG_s) printf("%s", device);
    free(s);
  } else if (flags & FLAG_f) printf("%s", device);
  else {
    xprintf("%s: [%04llx]:%llu (%s)", device, loop->lo_device, loop->lo_inode,
      loop->lo_file_name);
    if (loop->lo_offset) xprintf(", offset %llu", loop->lo_offset);
    if (loop->lo_sizelimit) xprintf(", sizelimit %llu", loop->lo_sizelimit);
    xputc('\n');
  }

done:
  if (file) close(ffd);
  if (lfd != -1) close(lfd);
}

// 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 (*s == '/') 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.openflags = (toys.optflags & FLAG_r) ? O_RDONLY : O_RDWR;

  if (TT.jfile) {
    struct stat st;

    xstat(TT.jfile, &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 (toys.optflags & FLAG_f) {
    if (toys.optc > 1) perror_exit("max 1 arg");
    loopback_setup(NULL, *toys.optargs);
  } else if (toys.optflags & (FLAG_a|FLAG_j)) {
    if (toys.optc) error_exit("bad args");
    dirtree_read("/dev", dash_a);
  // Do we need one DEVICE argument?
  } else {
    char *file = (toys.optflags & (FLAG_d|FLAG_c)) ? NULL : toys.optargs[1];

    if (!toys.optc || (file && toys.optc>1)) {
      if (CFG_HELP) toys.exithelp++;
      perror_exit("needs 1 arg");
    }
    for (s = toys.optargs; *s; s++) loopback_setup(*s, file);
  }
}