blob: 4e467a9c8d4435e295cc40aeae6ea95e417288af [file] [log] [blame]
/* 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, 0x4C82))) // 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))) goto done;
if (errno != ENXIO || !file) {
perror_msg_raw(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)) {
// The constant is LOOP_SET_CAPACITY
if (ioctl(lfd, (flags & FLAG_c) ? 0x4C07 : LOOP_CLR_FD, 0)) {
perror_msg_raw(device);
goto done;
}
// Associate file with this device?
} else if (file) {
char *s = xabspath(file, 1);
if (!s) perror_exit("file"); // already opened, but if deleted since...
if (ioctl(lfd, LOOP_SET_FD, ffd)) perror_exit("%s=%s", device, file);
loop->lo_offset = TT.offset;
loop->lo_sizelimit = TT.size;
xstrncpy((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 != 2))
help_exit("needs %d arg%s", 1+!!file, file ? "s" : "");
for (s = toys.optargs; *s; s++) {
loopback_setup(*s, file);
if (file) break;
}
}
}