Home | History | Annotate | Download | only in other
      1 /* losetup.c - Loopback setup
      2  *
      3  * Copyright 2012 Rob Landley <rob (at) landley.net>
      4  *
      5  * No standard. (Sigh.)
      6 
      7 USE_LOSETUP(NEWTOY(losetup, ">2S(sizelimit)#s(show)ro#j:fdca[!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	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 
     38 GLOBALS(
     39   char *jfile;
     40   long offset;
     41   long size;
     42 
     43   int openflags;
     44   dev_t jdev;
     45   ino_t jino;
     46 )
     47 
     48 /*
     49 todo: 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
     61 static 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 
    129 done:
    130   if (file) close(ffd);
    131   if (lfd != -1) close(lfd);
    132 }
    133 
    134 // Perform an action on all currently existing loop devices
    135 static 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 
    150 void 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