Created
June 21, 2018 17:28
-
-
Save stapelberg/2264acd713fed3ae58594ce4d4476c50 to your computer and use it in GitHub Desktop.
Squashfs correctness verification for https://github.com/gokrazy/internal/pull/1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Binary squashvrfy creates squashfs file system images using the gokrazy | |
// squashfs writer, then verifies that the resulting image yields the same | |
// result when mounted. | |
package main | |
import ( | |
"bufio" | |
"bytes" | |
"compress/gzip" | |
"flag" | |
"fmt" | |
"io" | |
"io/ioutil" | |
"log" | |
"net/http" | |
"os" | |
"os/exec" | |
"path" | |
"path/filepath" | |
"strings" | |
"syscall" | |
"time" | |
"pault.ag/go/debian/control" | |
"github.com/gokrazy/internal/squashfs" | |
) | |
func copyDir(dir *squashfs.Directory, fn string) error { | |
fis, err := ioutil.ReadDir(fn) | |
if err != nil { | |
return err | |
} | |
for _, fi := range fis { | |
if fi.IsDir() { | |
subdir := dir.Directory(fi.Name(), fi.ModTime()) | |
if err := copyDir(subdir, filepath.Join(fn, fi.Name())); err != nil { | |
return err | |
} | |
if err := subdir.Flush(); err != nil { | |
return err | |
} | |
continue | |
} | |
if !fi.Mode().IsRegular() { | |
if err := os.Remove(filepath.Join(fn, fi.Name())); err != nil { | |
return err | |
} | |
continue | |
} | |
wc, err := dir.File(fi.Name(), fi.ModTime(), fi.Mode()) | |
b, err := ioutil.ReadFile(filepath.Join(fn, fi.Name())) | |
if err != nil { | |
return err | |
} | |
if _, err := wc.Write(b); err != nil { | |
return err | |
} | |
if err := wc.Close(); err != nil { | |
return err | |
} | |
} | |
return nil | |
} | |
func logic(src string) error { | |
mountpoint, err := ioutil.TempDir("", "mnt") | |
if err != nil { | |
return err | |
} | |
defer os.RemoveAll(mountpoint) | |
img, err := os.Create("/tmp/broken.squashfs") | |
if err != nil { | |
return err | |
} | |
defer img.Close() | |
now := time.Now() | |
w, err := squashfs.NewWriter(img, now) | |
if err != nil { | |
return err | |
} | |
if err := copyDir(w.Root, src); err != nil { | |
return err | |
} | |
if err := w.Root.Flush(); err != nil { | |
return err | |
} | |
if err := w.Flush(); err != nil { | |
return err | |
} | |
if err := img.Close(); err != nil { | |
return err | |
} | |
if err := exec.Command("unsquashfs", "-l", img.Name()).Run(); err != nil { | |
return err | |
} | |
loopb, err := exec.Command("losetup", "-f").Output() | |
if err != nil { | |
return fmt.Errorf("losetup -f: %v", err) | |
} | |
loop := strings.TrimSpace(string(loopb)) | |
if err := exec.Command("losetup", loop, img.Name()).Run(); err != nil { | |
return fmt.Errorf("losetup: %v", err) | |
} | |
defer func() { | |
exec.Command("losetup", "-d", loop).Run() | |
}() | |
// Mount the image | |
if err := syscall.Mount(loop, mountpoint, "squashfs", 0, ""); err != nil { | |
return fmt.Errorf("mount: %v", err) | |
} | |
defer func() { | |
syscall.Unmount(mountpoint, 0) | |
}() | |
ls := exec.Command("diff", "-ur", src, mountpoint) | |
ls.Stdout = os.Stdout | |
ls.Stderr = os.Stderr | |
if err := ls.Run(); err != nil { | |
return err | |
} | |
if err := syscall.Unmount(mountpoint, 0); err != nil { | |
return fmt.Errorf("unmount: %v", err) | |
} | |
return nil | |
} | |
func vrfyOrig(url string) error { | |
destdir, err := ioutil.TempDir("", "deb") | |
if err != nil { | |
return err | |
} | |
defer os.RemoveAll(destdir) | |
base := path.Base(url) | |
dest, err := os.Create(filepath.Join(destdir, base)) | |
if err != nil { | |
return err | |
} | |
defer dest.Close() | |
log.Printf("downloading %s", url) | |
resp, err := http.Get(url) | |
if err != nil { | |
return err | |
} | |
defer resp.Body.Close() | |
if _, err := io.Copy(dest, resp.Body); err != nil { | |
return err | |
} | |
if err := dest.Close(); err != nil { | |
return err | |
} | |
if err := os.MkdirAll(filepath.Join(destdir, "extracted"), 0755); err != nil { | |
return err | |
} | |
log.Printf("extracting %s", base) | |
ex := filepath.Join(destdir, "extracted") | |
tar := exec.Command("tar", "xf", filepath.Join(destdir, base), "-C", ex) | |
tar.Stdout = os.Stdout | |
tar.Stderr = os.Stderr | |
if err := tar.Run(); err != nil { | |
return err | |
} | |
if err := logic(ex); err != nil { | |
log.Printf("FAIL: %s: %v", url, err) | |
} else { | |
log.Printf("PASS: %s", url) | |
} | |
return nil | |
} | |
func debian() error { | |
resp, err := http.Get("http://ftp.ch.debian.org/debian/dists/sid/main/source/Sources.gz") | |
if err != nil { | |
return err | |
} | |
b, err := ioutil.ReadAll(resp.Body) | |
resp.Body.Close() | |
rd, err := gzip.NewReader(bytes.NewReader(b)) | |
if err != nil { | |
return err | |
} | |
idx, err := control.ParseSourceIndex(bufio.NewReader(rd)) | |
if err != nil { | |
return err | |
} | |
seen := make(map[string]bool) | |
for _, x := range idx { | |
for _, f := range x.Files { | |
parts := strings.Split(f, " ") | |
if len(parts) != 3 { | |
continue | |
} | |
if !strings.Contains(parts[2], ".orig.") || | |
strings.HasSuffix(parts[2], ".asc") { | |
continue | |
} | |
if seen[x.Directory+"/"+parts[2]] { | |
continue | |
} | |
seen[x.Directory+"/"+parts[2]] = true | |
if err := vrfyOrig("http://ftp.ch.debian.org/debian/" + x.Directory + "/" + parts[2]); err != nil { | |
return err | |
} | |
} | |
} | |
return nil | |
} | |
func main() { | |
flag.Parse() | |
if flag.NArg() > 0 { | |
if strings.HasPrefix(flag.Arg(0), "http://") { | |
if err := vrfyOrig(flag.Arg(0)); err != nil { | |
log.Fatal(err) | |
} | |
} else { | |
if err := logic(flag.Arg(0)); err != nil { | |
log.Fatal(err) | |
} | |
} | |
} else { | |
if err := debian(); err != nil { | |
log.Fatal(err) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment