Loading commands/system/losetup.cpp +190 −39 Changes for commands/system/losetup.cpp: 190 added lines, 39 removed lines. Original line number Diff line number Diff line Loading @@ -29,93 +29,244 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <string> #include <cstring> #include <cerrno> #include <cstdlib> #include <unistd.h> #include <fcntl.h> #include <dirent.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/sysmacros.h> #include <linux/loop.h> #include "shell.h" namespace fresh { namespace cmd { // "losetup [-r] LOOPDEV FILE" (attach) and "losetup -d LOOPDEV" (detach) // -- just the two forms the initramfs squashfs-mount path uses. No -f // auto-find (the caller always names an explicit /dev/loopN, having // mknod'd it itself if devtmpfs hadn't already provided it). // losetup [-r] [-o OFFSET] LOOPDEV FILE attach FILE to LOOPDEV // losetup [-r] [-o OFFSET] -f [--show] FILE // attach FILE to a free device // (the kernel makes one if none // is free); --show prints it // losetup -f print a free device // losetup -d LOOPDEV detach // losetup -a list the attached devices // // -f asks /dev/loop-control (LOOP_CTL_GET_FREE): trying /dev/loop0, // /dev/loop1, ... in turn fails as soon as those are taken and the next // ones don't exist yet, which is what an initramfs that loop-mounts an // ISO file and then the squashfs in it runs into. class LosetupCommand : public ShellCommand { public: LosetupCommand():ShellCommand("losetup"){} int runCommand(int argc,const char **argv){ bool readOnly=false; bool detach=false; bool readOnly=false,detach=false,find=false,show=false,list=false; unsigned long long offset=0; std::string loopdev,file; for(int i=1;i<argc;++i){ std::string a=argv[i]; if(a=="-r") readOnly=true; else if(a=="-d") detach=true; else if(loopdev.empty()) loopdev=a; if(a=="-r"||a=="--read-only") readOnly=true; else if(a=="-d"||a=="--detach") detach=true; else if(a=="-f"||a=="--find") find=true; else if(a=="--show") show=true; else if(a=="-a"||a=="--all") list=true; else if(a=="-fr"||a=="-rf") find=readOnly=true; else if(a=="-o"||a=="--offset"){ if(++i>=argc){ std::cerr << "losetup: -o needs an offset" << std::endl; return 1; } char *end=nullptr; offset=std::strtoull(argv[i],&end,0); if(!*argv[i]||*end){ std::cerr << "losetup: bad offset " << argv[i] << std::endl; return 1; } } else if(!a.empty()&&a[0]=='-'){ usage(); return 1; } else if(loopdev.empty()&&!find) loopdev=a; else if(file.empty()) file=a; else { usage(); return 1; } } if(loopdev.empty()){ std::cerr << "losetup: usage: losetup [-r] LOOPDEV FILE | losetup -d LOOPDEV" << std::endl; if(list) return listAll(); if(find){ if(!loopdev.empty()){ usage(); return 1; } if(file.empty()){ // just the name of a free one std::string dev; if(!freeDevice(dev)) return 1; std::cout << dev << std::endl; return 0; } // another losetup may take the same device between // GET_FREE and attaching: then ask again for(int attempt=0;attempt<8;++attempt){ std::string dev; if(!freeDevice(dev)) return 1; int rc=attach(dev,file,readOnly,offset,true); if(rc==0){ if(show) std::cout << dev << std::endl; return 0; } if(rc!=EBUSY) return 1; } std::cerr << "losetup: no free loop device" << std::endl; return 1; } int loopFd=open(loopdev.c_str(),O_RDWR); if(loopFd<0 && readOnly) loopFd=open(loopdev.c_str(),O_RDONLY); if(loopFd<0){ std::cerr << "losetup: " << loopdev << ": " << strerror(errno) << std::endl; return 1; if(loopdev.empty()){ usage(); return 1; } if(detach) return detachDevice(loopdev); if(file.empty()){ std::cerr << "losetup: missing FILE" << std::endl; return 1; } return attach(loopdev,file,readOnly,offset,false)==0 ? 0 : 1; } if(detach){ if(ioctl(loopFd,LOOP_CLR_FD,0)!=0){ std::cerr << "losetup: detach " << loopdev << ": " << strerror(errno) << std::endl; close(loopFd); return 1; private: static void usage(){ std::cerr << "losetup: usage: losetup [-r] [-o OFFSET] LOOPDEV FILE | losetup [-r] [-o OFFSET] -f [--show] FILE\n" " losetup -f | losetup -d LOOPDEV | losetup -a" << std::endl; } close(loopFd); return 0; // the node of loop device n, made if devtmpfs hasn't (yet) static std::string node(long n){ std::string dev="/dev/loop"+std::to_string(n); struct stat st; if(stat(dev.c_str(),&st)!=0) mknod(dev.c_str(),S_IFBLK|0660,makedev(7,(unsigned)n)); return dev; } if(file.empty()){ std::cerr << "losetup: missing FILE" << std::endl; close(loopFd); return 1; static bool freeDevice(std::string &dev){ int ctl=open("/dev/loop-control",O_RDWR|O_CLOEXEC); if(ctl<0){ mknod("/dev/loop-control",S_IFCHR|0660,makedev(10,237)); ctl=open("/dev/loop-control",O_RDWR|O_CLOEXEC); } if(ctl<0){ std::cerr << "losetup: /dev/loop-control: " << strerror(errno) << std::endl; return false; } long n=ioctl(ctl,LOOP_CTL_GET_FREE); int err=errno; close(ctl); if(n<0){ std::cerr << "losetup: no free loop device: " << strerror(err) << std::endl; return false; } dev=node(n); return true; } int backingFd=open(file.c_str(),readOnly ? O_RDONLY : O_RDWR); // 0, or the errno (EBUSY: the device was taken meanwhile) static int attach(const std::string &loopdev,const std::string &file,bool readOnly, unsigned long long offset,bool quietBusy){ int backingFd=open(file.c_str(),(readOnly ? O_RDONLY : O_RDWR)|O_CLOEXEC); if(backingFd<0){ std::cerr << "losetup: " << file << ": " << strerror(errno) << std::endl; close(loopFd); return 1; int err=errno; std::cerr << "losetup: " << file << ": " << strerror(err) << std::endl; return err ? err : EINVAL; } int loopFd=open(loopdev.c_str(),O_RDWR|O_CLOEXEC); if(loopFd<0 && readOnly) loopFd=open(loopdev.c_str(),O_RDONLY|O_CLOEXEC); if(loopFd<0){ int err=errno; std::cerr << "losetup: " << loopdev << ": " << strerror(err) << std::endl; close(backingFd); return err ? err : EINVAL; } int rc=0; #ifdef LOOP_CONFIGURE // file, offset and read-only in one step (Linux 5.8) struct loop_config cfg; std::memset(&cfg,0,sizeof(cfg)); cfg.fd=(unsigned)backingFd; cfg.info.lo_offset=offset; if(readOnly) cfg.info.lo_flags|=LO_FLAGS_READ_ONLY; if(ioctl(loopFd,LOOP_CONFIGURE,&cfg)==0){ close(backingFd); close(loopFd); return 0; } rc=errno; if(rc!=EINVAL&&rc!=ENOTTY&&rc!=ENOSYS){ if(!(rc==EBUSY&&quietBusy)) std::cerr << "losetup: " << loopdev << ": " << strerror(rc) << std::endl; close(backingFd); close(loopFd); return rc; } #endif // older kernels: attach, then offset and flags if(ioctl(loopFd,LOOP_SET_FD,backingFd)!=0){ std::cerr << "losetup: " << loopdev << ": " << strerror(errno) << std::endl; rc=errno; if(!(rc==EBUSY&&quietBusy)) std::cerr << "losetup: " << loopdev << ": " << strerror(rc) << std::endl; close(backingFd); close(loopFd); return 1; return rc; } close(backingFd); // kernel keeps its own reference once LOOP_SET_FD succeeds if(readOnly){ close(backingFd); // the kernel keeps its own reference once LOOP_SET_FD succeeds if(readOnly||offset){ struct loop_info64 info; std::memset(&info,0,sizeof(info)); if(ioctl(loopFd,LOOP_GET_STATUS64,&info)==0){ info.lo_flags|=LO_FLAGS_READ_ONLY; ioctl(loopFd,LOOP_SET_STATUS64,&info); // best-effort; mount -o ro is the real enforcement if(readOnly) info.lo_flags|=LO_FLAGS_READ_ONLY; info.lo_offset=offset; if(ioctl(loopFd,LOOP_SET_STATUS64,&info)!=0 && offset){ rc=errno; std::cerr << "losetup: " << loopdev << ": offset: " << strerror(rc) << std::endl; ioctl(loopFd,LOOP_CLR_FD,0); close(loopFd); return rc; } } } close(loopFd); return 0; } static int detachDevice(const std::string &loopdev){ int loopFd=open(loopdev.c_str(),O_RDONLY|O_CLOEXEC); if(loopFd<0){ std::cerr << "losetup: " << loopdev << ": " << strerror(errno) << std::endl; return 1; } if(ioctl(loopFd,LOOP_CLR_FD,0)!=0){ std::cerr << "losetup: detach " << loopdev << ": " << strerror(errno) << std::endl; close(loopFd); return 1; } close(loopFd); return 0; } // "/dev/loopN: [offset] backing file", from sysfs static int listAll(){ DIR *d=opendir("/sys/block"); if(!d){ std::cerr << "losetup: /sys/block: " << strerror(errno) << std::endl; return 1; } struct dirent *e; while((e=readdir(d))){ std::string name=e->d_name; if(name.compare(0,4,"loop")!=0) continue; std::string base="/sys/block/"+name+"/loop/"; std::string backing=readLine(base+"backing_file"); if(backing.empty()) continue; std::string off=readLine(base+"offset"); std::cout << "/dev/" << name << ": " << (off.empty() ? "0" : off) << " " << backing << std::endl; } closedir(d); return 0; } static std::string readLine(const std::string &path){ int fd=open(path.c_str(),O_RDONLY|O_CLOEXEC); if(fd<0) return ""; char buf[4096]; ssize_t n=read(fd,buf,sizeof(buf)-1); close(fd); if(n<=0) return ""; buf[n]=0; std::string s=buf; while(!s.empty()&&(s.back()=='\n'||s.back()=='\r')) s.pop_back(); return s; } }; FRESH_REGISTER_COMMAND(LosetupCommand); Loading Loading
commands/system/losetup.cpp +190 −39 Changes for commands/system/losetup.cpp: 190 added lines, 39 removed lines. Original line number Diff line number Diff line Loading @@ -29,93 +29,244 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <string> #include <cstring> #include <cerrno> #include <cstdlib> #include <unistd.h> #include <fcntl.h> #include <dirent.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/sysmacros.h> #include <linux/loop.h> #include "shell.h" namespace fresh { namespace cmd { // "losetup [-r] LOOPDEV FILE" (attach) and "losetup -d LOOPDEV" (detach) // -- just the two forms the initramfs squashfs-mount path uses. No -f // auto-find (the caller always names an explicit /dev/loopN, having // mknod'd it itself if devtmpfs hadn't already provided it). // losetup [-r] [-o OFFSET] LOOPDEV FILE attach FILE to LOOPDEV // losetup [-r] [-o OFFSET] -f [--show] FILE // attach FILE to a free device // (the kernel makes one if none // is free); --show prints it // losetup -f print a free device // losetup -d LOOPDEV detach // losetup -a list the attached devices // // -f asks /dev/loop-control (LOOP_CTL_GET_FREE): trying /dev/loop0, // /dev/loop1, ... in turn fails as soon as those are taken and the next // ones don't exist yet, which is what an initramfs that loop-mounts an // ISO file and then the squashfs in it runs into. class LosetupCommand : public ShellCommand { public: LosetupCommand():ShellCommand("losetup"){} int runCommand(int argc,const char **argv){ bool readOnly=false; bool detach=false; bool readOnly=false,detach=false,find=false,show=false,list=false; unsigned long long offset=0; std::string loopdev,file; for(int i=1;i<argc;++i){ std::string a=argv[i]; if(a=="-r") readOnly=true; else if(a=="-d") detach=true; else if(loopdev.empty()) loopdev=a; if(a=="-r"||a=="--read-only") readOnly=true; else if(a=="-d"||a=="--detach") detach=true; else if(a=="-f"||a=="--find") find=true; else if(a=="--show") show=true; else if(a=="-a"||a=="--all") list=true; else if(a=="-fr"||a=="-rf") find=readOnly=true; else if(a=="-o"||a=="--offset"){ if(++i>=argc){ std::cerr << "losetup: -o needs an offset" << std::endl; return 1; } char *end=nullptr; offset=std::strtoull(argv[i],&end,0); if(!*argv[i]||*end){ std::cerr << "losetup: bad offset " << argv[i] << std::endl; return 1; } } else if(!a.empty()&&a[0]=='-'){ usage(); return 1; } else if(loopdev.empty()&&!find) loopdev=a; else if(file.empty()) file=a; else { usage(); return 1; } } if(loopdev.empty()){ std::cerr << "losetup: usage: losetup [-r] LOOPDEV FILE | losetup -d LOOPDEV" << std::endl; if(list) return listAll(); if(find){ if(!loopdev.empty()){ usage(); return 1; } if(file.empty()){ // just the name of a free one std::string dev; if(!freeDevice(dev)) return 1; std::cout << dev << std::endl; return 0; } // another losetup may take the same device between // GET_FREE and attaching: then ask again for(int attempt=0;attempt<8;++attempt){ std::string dev; if(!freeDevice(dev)) return 1; int rc=attach(dev,file,readOnly,offset,true); if(rc==0){ if(show) std::cout << dev << std::endl; return 0; } if(rc!=EBUSY) return 1; } std::cerr << "losetup: no free loop device" << std::endl; return 1; } int loopFd=open(loopdev.c_str(),O_RDWR); if(loopFd<0 && readOnly) loopFd=open(loopdev.c_str(),O_RDONLY); if(loopFd<0){ std::cerr << "losetup: " << loopdev << ": " << strerror(errno) << std::endl; return 1; if(loopdev.empty()){ usage(); return 1; } if(detach) return detachDevice(loopdev); if(file.empty()){ std::cerr << "losetup: missing FILE" << std::endl; return 1; } return attach(loopdev,file,readOnly,offset,false)==0 ? 0 : 1; } if(detach){ if(ioctl(loopFd,LOOP_CLR_FD,0)!=0){ std::cerr << "losetup: detach " << loopdev << ": " << strerror(errno) << std::endl; close(loopFd); return 1; private: static void usage(){ std::cerr << "losetup: usage: losetup [-r] [-o OFFSET] LOOPDEV FILE | losetup [-r] [-o OFFSET] -f [--show] FILE\n" " losetup -f | losetup -d LOOPDEV | losetup -a" << std::endl; } close(loopFd); return 0; // the node of loop device n, made if devtmpfs hasn't (yet) static std::string node(long n){ std::string dev="/dev/loop"+std::to_string(n); struct stat st; if(stat(dev.c_str(),&st)!=0) mknod(dev.c_str(),S_IFBLK|0660,makedev(7,(unsigned)n)); return dev; } if(file.empty()){ std::cerr << "losetup: missing FILE" << std::endl; close(loopFd); return 1; static bool freeDevice(std::string &dev){ int ctl=open("/dev/loop-control",O_RDWR|O_CLOEXEC); if(ctl<0){ mknod("/dev/loop-control",S_IFCHR|0660,makedev(10,237)); ctl=open("/dev/loop-control",O_RDWR|O_CLOEXEC); } if(ctl<0){ std::cerr << "losetup: /dev/loop-control: " << strerror(errno) << std::endl; return false; } long n=ioctl(ctl,LOOP_CTL_GET_FREE); int err=errno; close(ctl); if(n<0){ std::cerr << "losetup: no free loop device: " << strerror(err) << std::endl; return false; } dev=node(n); return true; } int backingFd=open(file.c_str(),readOnly ? O_RDONLY : O_RDWR); // 0, or the errno (EBUSY: the device was taken meanwhile) static int attach(const std::string &loopdev,const std::string &file,bool readOnly, unsigned long long offset,bool quietBusy){ int backingFd=open(file.c_str(),(readOnly ? O_RDONLY : O_RDWR)|O_CLOEXEC); if(backingFd<0){ std::cerr << "losetup: " << file << ": " << strerror(errno) << std::endl; close(loopFd); return 1; int err=errno; std::cerr << "losetup: " << file << ": " << strerror(err) << std::endl; return err ? err : EINVAL; } int loopFd=open(loopdev.c_str(),O_RDWR|O_CLOEXEC); if(loopFd<0 && readOnly) loopFd=open(loopdev.c_str(),O_RDONLY|O_CLOEXEC); if(loopFd<0){ int err=errno; std::cerr << "losetup: " << loopdev << ": " << strerror(err) << std::endl; close(backingFd); return err ? err : EINVAL; } int rc=0; #ifdef LOOP_CONFIGURE // file, offset and read-only in one step (Linux 5.8) struct loop_config cfg; std::memset(&cfg,0,sizeof(cfg)); cfg.fd=(unsigned)backingFd; cfg.info.lo_offset=offset; if(readOnly) cfg.info.lo_flags|=LO_FLAGS_READ_ONLY; if(ioctl(loopFd,LOOP_CONFIGURE,&cfg)==0){ close(backingFd); close(loopFd); return 0; } rc=errno; if(rc!=EINVAL&&rc!=ENOTTY&&rc!=ENOSYS){ if(!(rc==EBUSY&&quietBusy)) std::cerr << "losetup: " << loopdev << ": " << strerror(rc) << std::endl; close(backingFd); close(loopFd); return rc; } #endif // older kernels: attach, then offset and flags if(ioctl(loopFd,LOOP_SET_FD,backingFd)!=0){ std::cerr << "losetup: " << loopdev << ": " << strerror(errno) << std::endl; rc=errno; if(!(rc==EBUSY&&quietBusy)) std::cerr << "losetup: " << loopdev << ": " << strerror(rc) << std::endl; close(backingFd); close(loopFd); return 1; return rc; } close(backingFd); // kernel keeps its own reference once LOOP_SET_FD succeeds if(readOnly){ close(backingFd); // the kernel keeps its own reference once LOOP_SET_FD succeeds if(readOnly||offset){ struct loop_info64 info; std::memset(&info,0,sizeof(info)); if(ioctl(loopFd,LOOP_GET_STATUS64,&info)==0){ info.lo_flags|=LO_FLAGS_READ_ONLY; ioctl(loopFd,LOOP_SET_STATUS64,&info); // best-effort; mount -o ro is the real enforcement if(readOnly) info.lo_flags|=LO_FLAGS_READ_ONLY; info.lo_offset=offset; if(ioctl(loopFd,LOOP_SET_STATUS64,&info)!=0 && offset){ rc=errno; std::cerr << "losetup: " << loopdev << ": offset: " << strerror(rc) << std::endl; ioctl(loopFd,LOOP_CLR_FD,0); close(loopFd); return rc; } } } close(loopFd); return 0; } static int detachDevice(const std::string &loopdev){ int loopFd=open(loopdev.c_str(),O_RDONLY|O_CLOEXEC); if(loopFd<0){ std::cerr << "losetup: " << loopdev << ": " << strerror(errno) << std::endl; return 1; } if(ioctl(loopFd,LOOP_CLR_FD,0)!=0){ std::cerr << "losetup: detach " << loopdev << ": " << strerror(errno) << std::endl; close(loopFd); return 1; } close(loopFd); return 0; } // "/dev/loopN: [offset] backing file", from sysfs static int listAll(){ DIR *d=opendir("/sys/block"); if(!d){ std::cerr << "losetup: /sys/block: " << strerror(errno) << std::endl; return 1; } struct dirent *e; while((e=readdir(d))){ std::string name=e->d_name; if(name.compare(0,4,"loop")!=0) continue; std::string base="/sys/block/"+name+"/loop/"; std::string backing=readLine(base+"backing_file"); if(backing.empty()) continue; std::string off=readLine(base+"offset"); std::cout << "/dev/" << name << ": " << (off.empty() ? "0" : off) << " " << backing << std::endl; } closedir(d); return 0; } static std::string readLine(const std::string &path){ int fd=open(path.c_str(),O_RDONLY|O_CLOEXEC); if(fd<0) return ""; char buf[4096]; ssize_t n=read(fd,buf,sizeof(buf)-1); close(fd); if(n<=0) return ""; buf[n]=0; std::string s=buf; while(!s.empty()&&(s.back()=='\n'||s.back()=='\r')) s.pop_back(); return s; } }; FRESH_REGISTER_COMMAND(LosetupCommand); Loading