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 }