1 package squashfs 2 3 import ( 4 "archive/tar" 5 "io" 6 "io/fs" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "testing" 11 "time" 12 ) 13 14 var checkSQFSTar = func(_ *testing.T) {} 15 16 func TestMain(m *testing.M) { 17 _, err := exec.LookPath("sqfstar") 18 if err != nil { 19 checkSQFSTar = (*testing.T).SkipNow 20 } 21 22 os.Exit(m.Run()) 23 } 24 25 type option func(*tar.Header) 26 27 func modtime(t time.Time) option { 28 return func(h *tar.Header) { 29 h.ModTime = t 30 } 31 } 32 33 func chmod(perms fs.FileMode) option { 34 return func(h *tar.Header) { 35 h.Mode = int64(perms) 36 } 37 } 38 39 type directory struct { 40 tar.Header 41 children []child 42 } 43 44 func (d *directory) add(parent *directory) { 45 parent.children = append(parent.children, d) 46 } 47 48 func (d *directory) writeTo(w *tar.Writer, path string) error { 49 d.Header.Name = filepath.Join(path, d.Header.Name) 50 51 if err := w.WriteHeader(&d.Header); err != nil { 52 return err 53 } 54 55 for _, child := range d.children { 56 if err := child.writeTo(w, d.Header.Name); err != nil { 57 return err 58 } 59 } 60 61 return nil 62 } 63 64 type data struct { 65 tar.Header 66 contents string 67 } 68 69 func (d *data) add(parent *directory) { 70 parent.children = append(parent.children, d) 71 } 72 73 func (d *data) writeTo(w *tar.Writer, path string) error { 74 d.Header.Name = filepath.Join(path, d.Header.Name) 75 76 if err := w.WriteHeader(&d.Header); err != nil { 77 return err 78 } 79 80 _, err := io.WriteString(w, d.contents) 81 82 return err 83 } 84 85 type link struct { 86 tar.Header 87 } 88 89 func (l *link) add(parent *directory) { 90 parent.children = append(parent.children, l) 91 } 92 93 func (l *link) writeTo(w *tar.Writer, path string) error { 94 l.Header.Name = filepath.Join(path, l.Header.Name) 95 96 return w.WriteHeader(&l.Header) 97 } 98 99 type child interface { 100 add(*directory) 101 writeTo(*tar.Writer, string) error 102 } 103 104 const ( 105 requiredFile = ".required" 106 requiredContents = "some contents" 107 ) 108 109 func buildSquashFS(t *testing.T, children ...child) (string, error) { 110 t.Helper() 111 112 checkSQFSTar(t) 113 114 pr, pw := io.Pipe() 115 ch := make(chan error, 1) 116 117 go func() { 118 w := tar.NewWriter(pw) 119 120 if err := fileData(requiredFile, requiredContents).writeTo(w, "/"); err != nil { 121 ch <- err 122 } else { 123 for _, child := range children { 124 if err := child.writeTo(w, "/"); err != nil { 125 ch <- err 126 127 break 128 } 129 } 130 } 131 132 w.Close() 133 pw.Close() 134 135 close(ch) 136 }() 137 138 tmp := t.TempDir() 139 140 sqfs := filepath.Join(tmp, "out.sqfs") 141 142 cmd := exec.Command("sqfstar", sqfs) 143 cmd.Stdin = pr 144 145 if err := cmd.Run(); err != nil { 146 return "", err 147 } 148 149 pr.Close() 150 151 if err := <-ch; err != nil { 152 return "", err 153 } 154 155 return sqfs, nil 156 } 157 158 func dirData(name string, children []child, opts ...option) *directory { 159 dir := &directory{ 160 Header: tar.Header{ 161 Name: name, 162 Typeflag: tar.TypeDir, 163 Mode: 0o555, 164 ModTime: time.Now(), 165 Format: tar.FormatGNU, 166 }, 167 children: children, 168 } 169 170 for _, opt := range opts { 171 opt(&dir.Header) 172 } 173 174 return dir 175 } 176 177 func fileData(name string, contents string, opts ...option) *data { 178 file := &data{ 179 Header: tar.Header{ 180 Name: name, 181 Typeflag: tar.TypeReg, 182 Mode: 0o555, 183 ModTime: time.Now(), 184 Size: int64(len(contents)), 185 Format: tar.FormatGNU, 186 }, 187 contents: contents, 188 } 189 190 for _, opt := range opts { 191 opt(&file.Header) 192 } 193 194 return file 195 } 196 197 func symlink(name string, target string, opts ...option) *link { 198 symlink := &link{ 199 Header: tar.Header{ 200 Name: name, 201 Typeflag: tar.TypeSymlink, 202 Mode: 0o555, 203 ModTime: time.Now(), 204 Linkname: target, 205 Format: tar.FormatGNU, 206 }, 207 } 208 209 for _, opt := range opts { 210 opt(&symlink.Header) 211 } 212 213 return symlink 214 } 215