github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/exp/tcz/tcz.go (about)

     1  // Copyright 2012 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"log"
    13  	"net/http"
    14  	"os"
    15  	"path/filepath"
    16  	"strings"
    17  	"syscall"
    18  )
    19  
    20  const (
    21  	cmd = "tcz [options] package-names"
    22  	/*
    23  	 * IOCTL commands --- we will commandeer 0x4C ('L')
    24  	 */
    25  	LOOP_SET_CAPACITY = 0x4C07
    26  	LOOP_CHANGE_FD    = 0x4C06
    27  	LOOP_GET_STATUS64 = 0x4C05
    28  	LOOP_SET_STATUS64 = 0x4C04
    29  	LOOP_GET_STATUS   = 0x4C03
    30  	LOOP_SET_STATUS   = 0x4C02
    31  	LOOP_CLR_FD       = 0x4C01
    32  	LOOP_SET_FD       = 0x4C00
    33  	LO_NAME_SIZE      = 64
    34  	LO_KEY_SIZE       = 32
    35  	/* /dev/loop-control interface */
    36  	LOOP_CTL_ADD      = 0x4C80
    37  	LOOP_CTL_REMOVE   = 0x4C81
    38  	LOOP_CTL_GET_FREE = 0x4C82
    39  	SYS_ioctl         = 16
    40  	dirMode           = 0755
    41  	tinyCoreRoot      = "/TinyCorePackages/tcloop"
    42  )
    43  
    44  //http://distro.ibiblio.org/tinycorelinux/5.x/x86_64/tcz/
    45  //The .dep is the name + .dep
    46  
    47  var (
    48  	l                  = log.New(os.Stdout, "tcz: ", 0)
    49  	host               = flag.String("h", "tinycorelinux.net", "Host name for packages")
    50  	version            = flag.String("v", "8.x", "tinycore version")
    51  	arch               = flag.String("a", "x86_64", "tinycore architecture")
    52  	port               = flag.String("p", "80", "Host port")
    53  	install            = flag.Bool("i", true, "Install the packages, i.e. mount and create symlinks")
    54  	tczRoot            = flag.String("r", "/tcz", "tcz root directory")
    55  	debugPrint         = flag.Bool("d", false, "Enable debug prints")
    56  	skip               = flag.String("skip", "", "Packages to skip")
    57  	debug              = func(f string, s ...interface{}) {}
    58  	tczServerDir       string
    59  	tczLocalPackageDir string
    60  	ignorePackage      = make(map[string]struct{})
    61  )
    62  
    63  // consider making this a goroutine which pushes the string down the channel.
    64  func findloop() (name string, err error) {
    65  	cfd, err := syscall.Open("/dev/loop-control", syscall.O_RDWR, 0)
    66  	if err != nil {
    67  		log.Fatalf("/dev/loop-control: %v", err)
    68  	}
    69  	defer syscall.Close(cfd)
    70  	a, b, errno := syscall.Syscall(SYS_ioctl, uintptr(cfd), LOOP_CTL_GET_FREE, 0)
    71  	if errno != 0 {
    72  		log.Fatalf("ioctl: %v\n", err)
    73  	}
    74  	debug("a %v b %v err %v\n", a, b, err)
    75  	name = fmt.Sprintf("/dev/loop%d", a)
    76  	return name, nil
    77  }
    78  
    79  func clonetree(tree string) error {
    80  	debug("Clone tree %v", tree)
    81  	lt := len(tree)
    82  	err := filepath.Walk(tree, func(path string, fi os.FileInfo, err error) error {
    83  		if err != nil {
    84  			return err
    85  		}
    86  
    87  		debug("Clone tree with path %s fi %v", path, fi)
    88  		if fi.IsDir() {
    89  			debug("Walking, dir %v\n", path)
    90  			if path[lt:] == "" {
    91  				return nil
    92  			}
    93  			if err := os.MkdirAll(path[lt:], dirMode); err != nil {
    94  				debug("Mkdir of %s failed: %v", path[lt:], err)
    95  				// TODO: EEXIST should not be an error. Ignore
    96  				// err for now. FIXME.
    97  				//return err
    98  			}
    99  			return nil
   100  		}
   101  		// all else gets a symlink.
   102  
   103  		// If the link exists
   104  		if target, err := os.Readlink(path[lt:]); err == nil {
   105  			// Confirm that it points to the same path to be symlinked
   106  			if target == path {
   107  				return nil
   108  			}
   109  
   110  			// If it does not, return error because tcz packages are inconsistent
   111  			return fmt.Errorf("symlink: need %q -> %q, but %q -> %q is already there", path, path[lt:], path, target)
   112  		}
   113  
   114  		debug("Need to symlink %v to %v\n", path, path[lt:])
   115  
   116  		if err := os.Symlink(path, path[lt:]); err != nil {
   117  			return fmt.Errorf("Symlink: %v", err)
   118  		}
   119  
   120  		return nil
   121  	})
   122  	if err != nil {
   123  		l.Fatalf("Clone tree: %v", err)
   124  	}
   125  	return nil
   126  }
   127  
   128  func fetch(p string) error {
   129  	fullpath := filepath.Join(tczLocalPackageDir, p)
   130  	packageName := filepath.Join(tczServerDir, p)
   131  
   132  	if _, err := os.Stat(fullpath); !os.IsNotExist(err) {
   133  		debug("package %s is downloaded\n", fullpath)
   134  		return nil
   135  	}
   136  
   137  	if _, err := os.Stat(fullpath); err != nil {
   138  		cmd := fmt.Sprintf("http://%s:%s/%s", *host, *port, packageName)
   139  		debug("Fetch %v\n", cmd)
   140  
   141  		resp, err := http.Get(cmd)
   142  		if err != nil {
   143  			l.Fatalf("Get of %v failed: %v\n", cmd, err)
   144  		}
   145  		defer resp.Body.Close()
   146  
   147  		if resp.Status != "200 OK" {
   148  			debug("%v Not OK! %v\n", cmd, resp.Status)
   149  			return syscall.ENOENT
   150  		}
   151  
   152  		debug("resp %v err %v\n", resp, err)
   153  		// we have the whole tcz in resp.Body.
   154  		// First, save it to /tczRoot/name
   155  		f, err := os.Create(fullpath)
   156  		if err != nil {
   157  			l.Fatalf("Create of :%v: failed: %v\n", fullpath, err)
   158  		} else {
   159  			debug("created %v f %v\n", fullpath, f)
   160  		}
   161  
   162  		if c, err := io.Copy(f, resp.Body); err != nil {
   163  			l.Fatal(err)
   164  		} else {
   165  			/* OK, these are compressed tars ... */
   166  			debug("c %v err %v\n", c, err)
   167  		}
   168  		f.Close()
   169  	}
   170  	return nil
   171  }
   172  
   173  // deps is ALL the packages we need fetched or not
   174  // this may even let us work with parallel tcz, ALMOST
   175  func installPackage(tczName string, deps map[string]bool) error {
   176  	debug("installPackage: %v %v\n", tczName, deps)
   177  	depName := tczName + ".dep"
   178  	if err := fetch(tczName); err != nil {
   179  		l.Fatal(err)
   180  	}
   181  	deps[tczName] = true
   182  	debug("Fetched %v\n", tczName)
   183  	// now fetch dependencies if any.
   184  	if err := fetch(depName); err == nil {
   185  		debug("Fetched dep ok!\n")
   186  	} else {
   187  		debug("No dep file found\n")
   188  		if err := ioutil.WriteFile(filepath.Join(tczLocalPackageDir, depName), []byte{}, os.FileMode(0444)); err != nil {
   189  			debug("Tried to write Blank file %v, failed %v\n", depName, err)
   190  		}
   191  		return nil
   192  	}
   193  	// read deps file
   194  	depFullPath := filepath.Join(tczLocalPackageDir, depName)
   195  	deplist, err := ioutil.ReadFile(depFullPath)
   196  	if err != nil {
   197  		l.Fatalf("Fetched dep file %v but can't read it? %v", depName, err)
   198  	}
   199  	debug("deplist for %v is :%v:\n", depName, deplist)
   200  	realDepList := ""
   201  	for _, v := range strings.Split(string(deplist), "\n") {
   202  		// split("name\n") gets you a 2-element array with second
   203  		// element the empty string
   204  		if len(v) == 0 {
   205  			break
   206  		}
   207  		if _, ok := ignorePackage[v]; ok {
   208  			debug("%v is ignored", v)
   209  			continue
   210  		}
   211  		realDepList = realDepList + v + "\n"
   212  		debug("FOR %v get package %v\n", tczName, v)
   213  		if deps[v] {
   214  			continue
   215  		}
   216  		if err := installPackage(v, deps); err != nil {
   217  			return err
   218  		}
   219  	}
   220  	if string(deplist) == realDepList {
   221  		return nil
   222  	}
   223  	if err := ioutil.WriteFile(depFullPath, []byte(realDepList), os.FileMode(0444)); err != nil {
   224  		debug("Tried to write deplist file %v, failed %v\n", depName, err)
   225  		return err
   226  	}
   227  	return nil
   228  
   229  }
   230  
   231  func setupPackages(tczName string, deps map[string]bool) error {
   232  	debug("setupPackages: @ %v deps %v\n", tczName, deps)
   233  	for v := range deps {
   234  		cmdName := strings.Split(v, filepath.Ext(v))[0]
   235  		packagePath := filepath.Join(tinyCoreRoot, cmdName)
   236  
   237  		if _, err := os.Stat(packagePath); err == nil {
   238  			debug("PackagePath %s exists, skipping mount", packagePath)
   239  			continue
   240  		}
   241  
   242  		if err := os.MkdirAll(packagePath, dirMode); err != nil {
   243  			l.Fatalf("Package directory %s at %s, can not be created: %v", tczName, packagePath, err)
   244  		}
   245  
   246  		loopname, err := findloop()
   247  		if err != nil {
   248  			l.Fatal(err)
   249  		}
   250  		debug("findloop gets %v err %v\n", loopname, err)
   251  		pkgpath := filepath.Join(tczLocalPackageDir, v)
   252  		ffd, err := syscall.Open(pkgpath, syscall.O_RDONLY, 0)
   253  		if err != nil {
   254  			l.Fatalf("%v: %v\n", pkgpath, err)
   255  		}
   256  		lfd, err := syscall.Open(loopname, syscall.O_RDONLY, 0)
   257  		if err != nil {
   258  			l.Fatalf("%v: %v\n", loopname, err)
   259  		}
   260  		debug("ffd %v lfd %v\n", ffd, lfd)
   261  
   262  		a, b, errno := syscall.Syscall(SYS_ioctl, uintptr(lfd), LOOP_SET_FD, uintptr(ffd))
   263  		if errno != 0 {
   264  			l.Fatalf("loop set fd ioctl: pkgpath :%v:, loop :%v:, %v, %v, %v\n", pkgpath, loopname, a, b, errno)
   265  		}
   266  
   267  		/* now mount it. The convention is the mount is in /tinyCoreRoot/packagename */
   268  		if err := syscall.Mount(loopname, packagePath, "squashfs", syscall.MS_MGC_VAL|syscall.MS_RDONLY, ""); err != nil {
   269  			// how I hate Linux.
   270  			// Since all you ever get back is a USELESS ERRNO
   271  			// note: the open succeeded for both things, the mkdir worked,
   272  			// the loop device open worked. And we can get ENODEV. So,
   273  			// just what went wrong? "We're not telling. Here's an errno."
   274  			li, lerr := os.Stat(loopname)
   275  			pi, perr := os.Stat(pkgpath)
   276  			di, derr := os.Stat(packagePath)
   277  			e := fmt.Sprintf("%v: loop is (%v, %v); package is (%v, %v); dir is (%v, %v). Is squashfs built into your kernel?", err, li, lerr, pi, perr, di, derr)
   278  			l.Fatalf("Mount :%s: on :%s: %v\n", loopname, packagePath, e)
   279  		}
   280  		err = clonetree(packagePath)
   281  		if err != nil {
   282  			l.Fatalf("clonetree:  %v\n", err)
   283  		}
   284  	}
   285  	return nil
   286  
   287  }
   288  
   289  func usage() string {
   290  	return "tcz [-v version] [-a architecture] [-h hostname] [-p host port] [-d debug prints] PROGRAM..."
   291  }
   292  
   293  func init() {
   294  	defUsage := flag.Usage
   295  	flag.Usage = func() {
   296  		os.Args[0] = cmd
   297  		defUsage()
   298  	}
   299  }
   300  
   301  func main() {
   302  	flag.Parse()
   303  	if *debugPrint {
   304  		debug = l.Printf
   305  	}
   306  
   307  	ip := strings.Fields(*skip)
   308  	debug("ignored packages: %v", ip)
   309  	for _, p := range ip {
   310  		ignorePackage[p+".tcz"] = struct{}{}
   311  	}
   312  	needPackages := make(map[string]bool)
   313  	tczServerDir = filepath.Join("/", *version, *arch, "/tcz")
   314  	tczLocalPackageDir = filepath.Join(*tczRoot, tczServerDir)
   315  
   316  	packages := flag.Args()
   317  	debug("tcz: packages %v", packages)
   318  
   319  	if len(packages) == 0 {
   320  		flag.Usage()
   321  		os.Exit(1)
   322  	}
   323  
   324  	if err := os.MkdirAll(tczLocalPackageDir, dirMode); err != nil {
   325  		l.Fatal(err)
   326  	}
   327  
   328  	if *install {
   329  		if err := os.MkdirAll(tinyCoreRoot, dirMode); err != nil {
   330  			l.Fatal(err)
   331  		}
   332  	}
   333  
   334  	for _, cmdName := range packages {
   335  
   336  		tczName := cmdName + ".tcz"
   337  
   338  		if err := installPackage(tczName, needPackages); err != nil {
   339  			l.Fatal(err)
   340  		}
   341  
   342  		debug("After installpackages: needPackages %v\n", needPackages)
   343  
   344  		if *install {
   345  			if err := setupPackages(tczName, needPackages); err != nil {
   346  				l.Fatal(err)
   347  			}
   348  		}
   349  	}
   350  }