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