github.com/jlowellwofford/u-root@v1.0.0/cmds/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  
    84  		debug("Clone tree with path %s fi %v", path, fi)
    85  		if fi.IsDir() {
    86  			debug("Walking, dir %v\n", path)
    87  			if path[lt:] == "" {
    88  				return nil
    89  			}
    90  			if err := os.MkdirAll(path[lt:], dirMode); err != nil {
    91  				debug("Mkdir of %s failed: %v", path[lt:], err)
    92  				// TODO: EEXIST should not be an error. Ignore
    93  				// err for now. FIXME.
    94  				//return err
    95  			}
    96  			return nil
    97  		}
    98  		// all else gets a symlink.
    99  
   100  		// If the link exists
   101  		if target, err := os.Readlink(path[lt:]); err == nil {
   102  			// Confirm that it points to the same path to be symlinked
   103  			if target == path {
   104  				return nil
   105  			}
   106  
   107  			// If it does not, return error because tcz packages are inconsistent
   108  			return fmt.Errorf("symlink: need %q -> %q, but %q -> %q is already there", path, path[lt:], path, target)
   109  		}
   110  
   111  		debug("Need to symlink %v to %v\n", path, path[lt:])
   112  
   113  		if err := os.Symlink(path, path[lt:]); err != nil {
   114  			return fmt.Errorf("Symlink: %v", err)
   115  		}
   116  
   117  		return nil
   118  	})
   119  	if err != nil {
   120  		l.Fatalf("Clone tree: %v", err)
   121  	}
   122  	return nil
   123  }
   124  
   125  func fetch(p string) error {
   126  	fullpath := filepath.Join(tczLocalPackageDir, p)
   127  	packageName := filepath.Join(tczServerDir, p)
   128  
   129  	if _, err := os.Stat(fullpath); !os.IsNotExist(err) {
   130  		debug("package %s is downloaded\n", fullpath)
   131  		return nil
   132  	}
   133  
   134  	if _, err := os.Stat(fullpath); err != nil {
   135  		cmd := fmt.Sprintf("http://%s:%s/%s", *host, *port, packageName)
   136  		debug("Fetch %v\n", cmd)
   137  
   138  		resp, err := http.Get(cmd)
   139  		if err != nil {
   140  			l.Fatalf("Get of %v failed: %v\n", cmd, err)
   141  		}
   142  		defer resp.Body.Close()
   143  
   144  		if resp.Status != "200 OK" {
   145  			debug("%v Not OK! %v\n", cmd, resp.Status)
   146  			return syscall.ENOENT
   147  		}
   148  
   149  		debug("resp %v err %v\n", resp, err)
   150  		// we have the whole tcz in resp.Body.
   151  		// First, save it to /tczRoot/name
   152  		f, err := os.Create(fullpath)
   153  		if err != nil {
   154  			l.Fatalf("Create of :%v: failed: %v\n", fullpath, err)
   155  		} else {
   156  			debug("created %v f %v\n", fullpath, f)
   157  		}
   158  
   159  		if c, err := io.Copy(f, resp.Body); err != nil {
   160  			l.Fatal(err)
   161  		} else {
   162  			/* OK, these are compressed tars ... */
   163  			debug("c %v err %v\n", c, err)
   164  		}
   165  		f.Close()
   166  	}
   167  	return nil
   168  }
   169  
   170  // deps is ALL the packages we need fetched or not
   171  // this may even let us work with parallel tcz, ALMOST
   172  func installPackage(tczName string, deps map[string]bool) error {
   173  	debug("installPackage: %v %v\n", tczName, deps)
   174  	depName := tczName + ".dep"
   175  	if err := fetch(tczName); err != nil {
   176  		l.Fatal(err)
   177  	}
   178  	deps[tczName] = true
   179  	debug("Fetched %v\n", tczName)
   180  	// now fetch dependencies if any.
   181  	if err := fetch(depName); err == nil {
   182  		debug("Fetched dep ok!\n")
   183  	} else {
   184  		debug("No dep file found\n")
   185  		if err := ioutil.WriteFile(filepath.Join(tczLocalPackageDir, depName), []byte{}, os.FileMode(0444)); err != nil {
   186  			debug("Tried to write Blank file %v, failed %v\n", depName, err)
   187  		}
   188  		return nil
   189  	}
   190  	// read deps file
   191  	depFullPath := filepath.Join(tczLocalPackageDir, depName)
   192  	deplist, err := ioutil.ReadFile(depFullPath)
   193  	if err != nil {
   194  		l.Fatalf("Fetched dep file %v but can't read it? %v", depName, err)
   195  	}
   196  	debug("deplist for %v is :%v:\n", depName, deplist)
   197  	realDepList := ""
   198  	for _, v := range strings.Split(string(deplist), "\n") {
   199  		// split("name\n") gets you a 2-element array with second
   200  		// element the empty string
   201  		if len(v) == 0 {
   202  			break
   203  		}
   204  		if _, ok := ignorePackage[v]; ok {
   205  			debug("%v is ignored", v)
   206  			continue
   207  		}
   208  		realDepList = realDepList + v + "\n"
   209  		debug("FOR %v get package %v\n", tczName, v)
   210  		if deps[v] {
   211  			continue
   212  		}
   213  		if err := installPackage(v, deps); err != nil {
   214  			return err
   215  		}
   216  	}
   217  	if string(deplist) == realDepList {
   218  		return nil
   219  	}
   220  	if err := ioutil.WriteFile(depFullPath, []byte(realDepList), os.FileMode(0444)); err != nil {
   221  		debug("Tried to write deplist file %v, failed %v\n", depName, err)
   222  		return err
   223  	}
   224  	return nil
   225  
   226  }
   227  
   228  func setupPackages(tczName string, deps map[string]bool) error {
   229  	debug("setupPackages: @ %v deps %v\n", tczName, deps)
   230  	for v := range deps {
   231  		cmdName := strings.Split(v, filepath.Ext(v))[0]
   232  		packagePath := filepath.Join(tinyCoreRoot, cmdName)
   233  
   234  		if _, err := os.Stat(packagePath); err == nil {
   235  			debug("PackagePath %s exists, skipping mount", packagePath)
   236  			continue
   237  		}
   238  
   239  		if err := os.MkdirAll(packagePath, dirMode); err != nil {
   240  			l.Fatalf("Package directory %s at %s, can not be created: %v", tczName, packagePath, err)
   241  		}
   242  
   243  		loopname, err := findloop()
   244  		if err != nil {
   245  			l.Fatal(err)
   246  		}
   247  		debug("findloop gets %v err %v\n", loopname, err)
   248  		pkgpath := filepath.Join(tczLocalPackageDir, v)
   249  		ffd, err := syscall.Open(pkgpath, syscall.O_RDONLY, 0)
   250  		if err != nil {
   251  			l.Fatalf("%v: %v\n", pkgpath, err)
   252  		}
   253  		lfd, err := syscall.Open(loopname, syscall.O_RDONLY, 0)
   254  		if err != nil {
   255  			l.Fatalf("%v: %v\n", loopname, err)
   256  		}
   257  		debug("ffd %v lfd %v\n", ffd, lfd)
   258  
   259  		a, b, errno := syscall.Syscall(SYS_ioctl, uintptr(lfd), LOOP_SET_FD, uintptr(ffd))
   260  		if errno != 0 {
   261  			l.Fatalf("loop set fd ioctl: pkgpath :%v:, loop :%v:, %v, %v, %v\n", pkgpath, loopname, a, b, errno)
   262  		}
   263  
   264  		/* now mount it. The convention is the mount is in /tinyCoreRoot/packagename */
   265  		if err := syscall.Mount(loopname, packagePath, "squashfs", syscall.MS_MGC_VAL|syscall.MS_RDONLY, ""); err != nil {
   266  			l.Fatalf("Mount :%s: on :%s: %v\n", loopname, packagePath, err)
   267  		}
   268  		err = clonetree(packagePath)
   269  		if err != nil {
   270  			l.Fatalf("clonetree:  %v\n", err)
   271  		}
   272  	}
   273  	return nil
   274  
   275  }
   276  
   277  func usage() string {
   278  	return "tcz [-v version] [-a architecture] [-h hostname] [-p host port] [-d debug prints] PROGRAM..."
   279  }
   280  
   281  func init() {
   282  	defUsage := flag.Usage
   283  	flag.Usage = func() {
   284  		os.Args[0] = cmd
   285  		defUsage()
   286  	}
   287  }
   288  
   289  func main() {
   290  	flag.Parse()
   291  	if *debugPrint {
   292  		debug = l.Printf
   293  	}
   294  
   295  	ip := strings.Fields(*skip)
   296  	debug("ignored packages: %v", ip)
   297  	for _, p := range ip {
   298  		ignorePackage[p+".tcz"] = struct{}{}
   299  	}
   300  	needPackages := make(map[string]bool)
   301  	tczServerDir = filepath.Join("/", *version, *arch, "/tcz")
   302  	tczLocalPackageDir = filepath.Join(*tczRoot, tczServerDir)
   303  
   304  	packages := flag.Args()
   305  
   306  	if len(packages) == 0 {
   307  		flag.Usage()
   308  		os.Exit(1)
   309  	}
   310  
   311  	if err := os.MkdirAll(tczLocalPackageDir, dirMode); err != nil {
   312  		l.Fatal(err)
   313  	}
   314  
   315  	if *install {
   316  		if err := os.MkdirAll(tinyCoreRoot, dirMode); err != nil {
   317  			l.Fatal(err)
   318  		}
   319  	}
   320  
   321  	for _, cmdName := range packages {
   322  
   323  		tczName := cmdName + ".tcz"
   324  
   325  		if err := installPackage(tczName, needPackages); err != nil {
   326  			l.Fatal(err)
   327  		}
   328  
   329  		debug("After installpackages: needPackages %v\n", needPackages)
   330  
   331  		if *install {
   332  			if err := setupPackages(tczName, needPackages); err != nil {
   333  				l.Fatal(err)
   334  			}
   335  		}
   336  	}
   337  }