Commit 418d788d authored by Jan Koester's avatar Jan Koester
Browse files

test

parent 2fe29ff8
Loading
Loading
Loading
Loading
+190 −39
Changes for commands/system/losetup.cpp: 190 added lines, 39 removed lines.
Original line number Diff line number Diff line
@@ -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);