/* vi: set sw=4 ts=4: */ /* * Utility routines. * * Copyright (C) 1999-2004 by Erik Andersen * Copyright (C) 2005 by Rob Landley * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ #include "libbb.h" #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) /* For 2.6, use the cleaned up header to get the 64 bit API. */ // Commented out per Rob's request //# include "fix_u32.h" /* some old toolchains need __u64 for linux/loop.h */ # include typedef struct loop_info64 bb_loop_info; # define BB_LOOP_SET_STATUS LOOP_SET_STATUS64 # define BB_LOOP_GET_STATUS LOOP_GET_STATUS64 #else /* For 2.4 and earlier, use the 32 bit API (and don't trust the headers) */ /* Stuff stolen from linux/loop.h for 2.4 and earlier kernels */ # include # define LO_NAME_SIZE 64 # define LO_KEY_SIZE 32 # define LOOP_SET_FD 0x4C00 # define LOOP_CLR_FD 0x4C01 # define BB_LOOP_SET_STATUS 0x4C02 # define BB_LOOP_GET_STATUS 0x4C03 typedef struct { int lo_number; __kernel_dev_t lo_device; unsigned long lo_inode; __kernel_dev_t lo_rdevice; int lo_offset; int lo_encrypt_type; int lo_encrypt_key_size; int lo_flags; char lo_file_name[LO_NAME_SIZE]; unsigned char lo_encrypt_key[LO_KEY_SIZE]; unsigned long lo_init[2]; char reserved[4]; } bb_loop_info; #endif char* FAST_FUNC query_loop(const char *device) { int fd; bb_loop_info loopinfo; char *dev = NULL; fd = open(device, O_RDONLY); if (fd >= 0) { if (ioctl(fd, BB_LOOP_GET_STATUS, &loopinfo) == 0) { dev = xasprintf("%"OFF_FMT"u %s", (off_t) loopinfo.lo_offset, (char *)loopinfo.lo_file_name); } close(fd); } return dev; } int FAST_FUNC del_loop(const char *device) { int fd, rc; fd = open(device, O_RDONLY); if (fd < 0) return 1; rc = ioctl(fd, LOOP_CLR_FD, 0); close(fd); return rc; } /* Obtain an unused loop device number */ int FAST_FUNC get_free_loop(void) { int fd; int loopdevno; fd = open("/dev/loop-control", O_RDWR | O_CLOEXEC); if (fd == -1) return fd - 1; /* -2: "no /dev/loop-control" */ #ifndef LOOP_CTL_GET_FREE # define LOOP_CTL_GET_FREE 0x4C82 #endif loopdevno = ioctl(fd, LOOP_CTL_GET_FREE); close(fd); return loopdevno; /* can be -1 if error */ } /* Returns opened fd to the loop device, <0 on error. * *device is loop device to use, or if *device==NULL finds a loop device to * mount it on and sets *device to a strdup of that loop device name. */ int FAST_FUNC set_loop(char **device, const char *file, unsigned long long offset, unsigned long long sizelimit, unsigned flags) { char dev[LOOP_NAMESIZE]; char *try; bb_loop_info loopinfo; struct stat statbuf; int i, lfd, ffd, mode, rc; /* Open the file. Barf if this doesn't work. */ mode = (flags & BB_LO_FLAGS_READ_ONLY) ? O_RDONLY : O_RDWR; open_ffd: ffd = open(file, mode); if (ffd < 0) { if (mode != O_RDONLY) { mode = O_RDONLY; goto open_ffd; } return -errno; } try = *device; if (!try) { get_free_loopN: i = get_free_loop(); if (i == -1) { close(ffd); return -1; /* no free loop devices */ } if (i >= 0) { try = xasprintf(LOOP_FORMAT, i); goto open_lfd; } /* i == -2: no /dev/loop-control. Do an old-style search for a free device */ try = dev; } /* Find a loop device */ /* 0xfffff is a max possible minor number in Linux circa 2010 */ for (i = 0; i <= 0xfffff; i++) { sprintf(dev, LOOP_FORMAT, i); IF_FEATURE_MOUNT_LOOP_CREATE(errno = 0;) if (stat(try, &statbuf) != 0 || !S_ISBLK(statbuf.st_mode)) { if (ENABLE_FEATURE_MOUNT_LOOP_CREATE && errno == ENOENT && try == dev ) { /* Node doesn't exist, try to create it */ if (mknod(dev, S_IFBLK|0644, makedev(7, i)) == 0) goto open_lfd; } /* Ran out of block devices, return failure */ rc = -1; break; } open_lfd: /* Open the sucker and check its loopiness */ lfd = rc = open(try, mode); if (lfd < 0 && errno == EROFS) { mode = O_RDONLY; lfd = rc = open(try, mode); } if (lfd < 0) { if (errno == ENXIO) { /* Happens if loop module is not loaded */ /* rc is -1; */ break; } goto try_next_loopN; } rc = ioctl(lfd, BB_LOOP_GET_STATUS, &loopinfo); /* If device is free, try to claim it */ if (rc && errno == ENXIO) { /* Associate free loop device with file */ if (ioctl(lfd, LOOP_SET_FD, ffd)) { /* Ouch. Are we racing with other mount? */ if (!*device /* yes */ && try != dev /* tried a _kernel-offered_ loopN? */ ) { free(try); close(lfd); //TODO: add "if (--failcount != 0) ..."? goto get_free_loopN; } goto close_and_try_next_loopN; } memset(&loopinfo, 0, sizeof(loopinfo)); safe_strncpy((char *)loopinfo.lo_file_name, file, LO_NAME_SIZE); loopinfo.lo_offset = offset; loopinfo.lo_sizelimit = sizelimit; /* * Used by mount to set LO_FLAGS_AUTOCLEAR. * LO_FLAGS_READ_ONLY is not set because RO is controlled by open type of the file. * Note that closing LO_FLAGS_AUTOCLEARed lfd before mount * is wrong (would free the loop device!) */ loopinfo.lo_flags = (flags & ~BB_LO_FLAGS_READ_ONLY); rc = ioctl(lfd, BB_LOOP_SET_STATUS, &loopinfo); if (rc != 0 && (loopinfo.lo_flags & BB_LO_FLAGS_AUTOCLEAR)) { /* Old kernel, does not support LO_FLAGS_AUTOCLEAR? */ /* (this code path is not tested) */ loopinfo.lo_flags -= BB_LO_FLAGS_AUTOCLEAR; rc = ioctl(lfd, BB_LOOP_SET_STATUS, &loopinfo); } if (rc == 0) { /* SUCCESS! */ if (try != dev) /* tried a kernel-offered free loopN? */ *device = try; /* malloced */ if (!*device) /* was looping in search of free "/dev/loopN"? */ *device = xstrdup(dev); rc = lfd; /* return this */ break; } /* failure, undo LOOP_SET_FD */ ioctl(lfd, LOOP_CLR_FD, 0); // actually, 0 param is unnecessary } /* else: device is not free (rc == 0) or error other than ENXIO */ close_and_try_next_loopN: close(lfd); try_next_loopN: rc = -1; if (*device) /* was looking for a particular "/dev/loopN"? */ break; /* yes, do not try other names */ } /* for() */ close(ffd); return rc; }