diff --git a/README.md b/README.md index 6c4327e..10185c9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # GoSquashfs My playground to mess around with Squashfs in Go. Might turn into an actual library someday. Mainly for AppImage +Right Now it's mostly based on [distri's squashfs library](https://github.com/distr1/distri/tree/master/internal/squashfs) + # Ideas * Link directly to squashfs-tool using cgo * cgo can be a butt diff --git a/compressionoptions.go b/compressionoptions.go new file mode 100644 index 0000000..06ed4f5 --- /dev/null +++ b/compressionoptions.go @@ -0,0 +1,45 @@ +package squashfs + +//CompressionOptions +type CompressionOptions interface { + Decompress([]byte) []byte +} + +//TODO: Allow creation of options for compression. + +type gzipOptionsRaw struct { + compressionLevel int32 + windowSize int16 + strategies int16 +} + +//GzipOptions is the options used for gzip compression. Backed by the raw format, with strategies parsed. +type GzipOptions struct { + CompressionOptions + raw *gzipOptionsRaw + DefaultStrategy bool + FilteredStrategy bool + HuffmanOnlyStrategy bool + RunLengthEncodedStrategy bool + FixedStretegy bool +} + +type xzOptionsRaw struct { + dictionarySize int32 + executableFilters int32 +} + +type lz4OptionsRaw struct { + version int32 + flags int32 +} + +//ZstdOptions is the options set for zstdOptions +type ZstdOptions struct { + CompressionLevel int32 //CompressionLevel should be between 1 and 22 +} + +type lzoOptionsRaw struct { + algorithm int32 + compressionLevel int32 +} diff --git a/reader_old.go b/reader_old.go index 2187a66..832e3d1 100644 --- a/reader_old.go +++ b/reader_old.go @@ -1,618 +1,618 @@ package squashfs -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "io/ioutil" - "log" - "os" - "path/filepath" - "strings" - "sync" - "syscall" - "time" +// import ( +// "bytes" +// "encoding/binary" +// "fmt" +// "io" +// "io/ioutil" +// "log" +// "os" +// "path/filepath" +// "strings" +// "sync" +// "syscall" +// "time" - "golang.org/x/xerrors" -) +// "golang.org/x/xerrors" +// ) -type Reader struct { - r io.ReaderAt - super superblock -} +// type Reader struct { +// r io.ReaderAt +// super superblock +// } -func NewReader(r io.ReaderAt) (*Reader, error) { - var sb superblock +// func NewReader(r io.ReaderAt) (*Reader, error) { +// var sb superblock - if err := binary.Read(io.NewSectionReader(r, 0, int64(binary.Size(sb))), binary.LittleEndian, &sb); err != nil { - return nil, fmt.Errorf("reading superblock: %v", err) - } +// if err := binary.Read(io.NewSectionReader(r, 0, int64(binary.Size(sb))), binary.LittleEndian, &sb); err != nil { +// return nil, fmt.Errorf("reading superblock: %v", err) +// } - if got, want := sb.Magic, uint32(magic); got != want { - return nil, fmt.Errorf("invalid magic (not a SquashFS image?): got %x, want %x", got, want) - } +// if got, want := sb.Magic, uint32(magic); got != want { +// return nil, fmt.Errorf("invalid magic (not a SquashFS image?): got %x, want %x", got, want) +// } - //log.Printf("superblock: %+v", sb) - return &Reader{ - r: r, - super: sb, - }, nil -} +// //log.Printf("superblock: %+v", sb) +// return &Reader{ +// r: r, +// super: sb, +// }, nil +// } -// TODO: maybe mmap instead of seeking? +// // TODO: maybe mmap instead of seeking? -func (r *Reader) inode(i Inode) (blockoffset int64, offset int64) { - return int64(i >> 16), int64(i & 0xFFFF) -} +// func (r *Reader) inode(i Inode) (blockoffset int64, offset int64) { +// return int64(i >> 16), int64(i & 0xFFFF) +// } -type blockReader struct { - r io.ReadSeeker - lenBuf [2]byte - buf []byte - i int64 +// type blockReader struct { +// r io.ReadSeeker +// lenBuf [2]byte +// buf []byte +// i int64 - off int64 // TODO: remove this once using mmap -} +// off int64 // TODO: remove this once using mmap +// } -func (br *blockReader) Read(p []byte) (n int, err error) { - if br.i >= int64(len(br.buf)) { - br.i = 0 - if _, err := io.ReadFull(br.r, br.lenBuf[:]); err != nil { - return 0, err - } - l := binary.LittleEndian.Uint16(br.lenBuf[:]) - //uncompressed := l&0x8000 > 0 - l &= 0x7FFF - //log.Printf("block of len %d, uncompressed: %v", l, uncompressed) - if int(l) > cap(br.buf) { - br.buf = make([]byte, int(l)) - } - br.buf = br.buf[:l] - if _, err := io.ReadFull(br.r, br.buf); err != nil { - return 0, err - } - //log.Printf("(retry) n = %v, err = %v", n, err) - } - n = copy(p, br.buf[br.i:]) - br.i += int64(n) - return n, err -} +// func (br *blockReader) Read(p []byte) (n int, err error) { +// if br.i >= int64(len(br.buf)) { +// br.i = 0 +// if _, err := io.ReadFull(br.r, br.lenBuf[:]); err != nil { +// return 0, err +// } +// l := binary.LittleEndian.Uint16(br.lenBuf[:]) +// //uncompressed := l&0x8000 > 0 +// l &= 0x7FFF +// //log.Printf("block of len %d, uncompressed: %v", l, uncompressed) +// if int(l) > cap(br.buf) { +// br.buf = make([]byte, int(l)) +// } +// br.buf = br.buf[:l] +// if _, err := io.ReadFull(br.r, br.buf); err != nil { +// return 0, err +// } +// //log.Printf("(retry) n = %v, err = %v", n, err) +// } +// n = copy(p, br.buf[br.i:]) +// br.i += int64(n) +// return n, err +// } -func (br *blockReader) Close() error { - blockReaderPool.Put(br) - return nil -} +// func (br *blockReader) Close() error { +// blockReaderPool.Put(br) +// return nil +// } -var blockReaderPool = sync.Pool{ - New: func() interface{} { - return &blockReader{ - buf: make([]byte, 0, metadataBlockSize), - } - }, -} +// var blockReaderPool = sync.Pool{ +// New: func() interface{} { +// return &blockReader{ +// buf: make([]byte, 0, metadataBlockSize), +// } +// }, +// } -func (r *Reader) blockReader(blockoffset, offset int64) (io.ReadCloser, error) { - //log.Printf("blockoffset %v (%x), offset %v (%x)", blockoffset, blockoffset, offset, offset) - br := blockReaderPool.Get().(*blockReader) - br.buf = br.buf[:0] - br.r = io.NewSectionReader(r.r, blockoffset, 5500*1024*1024) // TODO: correct limit? can we use IntMax - br.off = blockoffset - br.i = 0 - //log.Printf("discarding %d bytes", offset) - for n := int64(0); n < offset; { - remaining := offset - n - if remaining > metadataBlockSize { - remaining = metadataBlockSize - } - nn, err := br.Read(br.buf[:remaining]) - if err != nil { - return nil, err - } - n += int64(nn) - } - return br, nil -} +// func (r *Reader) blockReader(blockoffset, offset int64) (io.ReadCloser, error) { +// //log.Printf("blockoffset %v (%x), offset %v (%x)", blockoffset, blockoffset, offset, offset) +// br := blockReaderPool.Get().(*blockReader) +// br.buf = br.buf[:0] +// br.r = io.NewSectionReader(r.r, blockoffset, 5500*1024*1024) // TODO: correct limit? can we use IntMax +// br.off = blockoffset +// br.i = 0 +// //log.Printf("discarding %d bytes", offset) +// for n := int64(0); n < offset; { +// remaining := offset - n +// if remaining > metadataBlockSize { +// remaining = metadataBlockSize +// } +// nn, err := br.Read(br.buf[:remaining]) +// if err != nil { +// return nil, err +// } +// n += int64(nn) +// } +// return br, nil +// } -// TODO: define an inode type to use instead of interface{}? -func (r *Reader) readInode(i Inode) (interface{}, error) { - blockoffset, offset := r.inode(i) - br, err := r.blockReader(r.super.InodeTableStart+blockoffset, offset) - if err != nil { - fmt.Println("oops! ", err) - return nil, err - } - defer br.Close() - fmt.Println("Hello There") +// // TODO: define an inode type to use instead of interface{}? +// func (r *Reader) readInode(i Inode) (interface{}, error) { +// blockoffset, offset := r.inode(i) +// br, err := r.blockReader(r.super.InodeTableStart+blockoffset, offset) +// if err != nil { +// fmt.Println("oops! ", err) +// return nil, err +// } +// defer br.Close() +// fmt.Println("Hello There") - // We need the inode type before we know which type to pass to binary.Read, - // so we need to read it twice: - var inodeType uint16 - typeBuf := bytes.NewBuffer(make([]byte, 0, binary.Size(inodeType))) - if err := binary.Read(io.TeeReader(br, typeBuf), binary.LittleEndian, &inodeType); err != nil { - return nil, err - } - br = ioutil.NopCloser(io.MultiReader(typeBuf, br)) +// // We need the inode type before we know which type to pass to binary.Read, +// // so we need to read it twice: +// var inodeType uint16 +// typeBuf := bytes.NewBuffer(make([]byte, 0, binary.Size(inodeType))) +// if err := binary.Read(io.TeeReader(br, typeBuf), binary.LittleEndian, &inodeType); err != nil { +// return nil, err +// } +// br = ioutil.NopCloser(io.MultiReader(typeBuf, br)) - // var ih inodeHeader - // if err := binary.Read(br, binary.LittleEndian, &ih); err != nil { - // return err - // } - // //log.Printf("ih: %+v", ih) +// // var ih inodeHeader +// // if err := binary.Read(br, binary.LittleEndian, &ih); err != nil { +// // return err +// // } +// // //log.Printf("ih: %+v", ih) - //log.Printf("inode type: %v", inodeType) - switch inodeType { - case dirType: - var di dirInodeHeader - if err := binary.Read(br, binary.LittleEndian, &di); err != nil { - return nil, err - } - return di, nil +// //log.Printf("inode type: %v", inodeType) +// switch inodeType { +// case dirType: +// var di dirInodeHeader +// if err := binary.Read(br, binary.LittleEndian, &di); err != nil { +// return nil, err +// } +// return di, nil - case fileType: - var ri regInodeHeader - if err := binary.Read(br, binary.LittleEndian, &ri); err != nil { - return nil, err - } - return ri, nil +// case fileType: +// var ri regInodeHeader +// if err := binary.Read(br, binary.LittleEndian, &ri); err != nil { +// return nil, err +// } +// return ri, nil - case symlinkType: - var si symlinkInodeHeader - if err := binary.Read(br, binary.LittleEndian, &si); err != nil { - return nil, err - } - return si, nil +// case symlinkType: +// var si symlinkInodeHeader +// if err := binary.Read(br, binary.LittleEndian, &si); err != nil { +// return nil, err +// } +// return si, nil - case ldirType: - var di ldirInodeHeader - if err := binary.Read(br, binary.LittleEndian, &di); err != nil { - return nil, err - } - return di, nil +// case ldirType: +// var di ldirInodeHeader +// if err := binary.Read(br, binary.LittleEndian, &di); err != nil { +// return nil, err +// } +// return di, nil - case lregType: - var di lregInodeHeader - if err := binary.Read(br, binary.LittleEndian, &di); err != nil { - return nil, err - } - return di, nil +// case lregType: +// var di lregInodeHeader +// if err := binary.Read(br, binary.LittleEndian, &di); err != nil { +// return nil, err +// } +// return di, nil - // TODO: - // blkdevType - // chrdevType - // fifoType - // socketType - // // The larger types are used for e.g. sparse files, xattrs, etc. - // ldirType - // lsymlinkType - // lblkdevType - // lchrdevType - // lfifoType - // lsocketType +// // TODO: +// // blkdevType +// // chrdevType +// // fifoType +// // socketType +// // // The larger types are used for e.g. sparse files, xattrs, etc. +// // ldirType +// // lsymlinkType +// // lblkdevType +// // lchrdevType +// // lfifoType +// // lsocketType - } - return nil, fmt.Errorf("unknown inode type %d", inodeType) -} +// } +// return nil, fmt.Errorf("unknown inode type %d", inodeType) +// } -func (r *Reader) RootInode() Inode { - return r.super.RootInode -} +// func (r *Reader) RootInode() Inode { +// return r.super.RootInode +// } -func (r *Reader) Stat(name string, i Inode) (os.FileInfo, error) { - inode, err := r.readInode(i) - if err != nil { - return nil, err - } - //log.Printf("i %d, inode: %T, %+v", i, inode, inode) - switch x := inode.(type) { - case dirInodeHeader: - return &FileInfo{ - name: name, - size: int64(x.FileSize), - mode: os.ModeDir | os.FileMode(x.Mode), - modTime: time.Unix(int64(x.Mtime), 0), - Inode: i, - }, nil +// func (r *Reader) Stat(name string, i Inode) (os.FileInfo, error) { +// inode, err := r.readInode(i) +// if err != nil { +// return nil, err +// } +// //log.Printf("i %d, inode: %T, %+v", i, inode, inode) +// switch x := inode.(type) { +// case dirInodeHeader: +// return &FileInfo{ +// name: name, +// size: int64(x.FileSize), +// mode: os.ModeDir | os.FileMode(x.Mode), +// modTime: time.Unix(int64(x.Mtime), 0), +// Inode: i, +// }, nil - case ldirInodeHeader: - return &FileInfo{ - name: name, - size: int64(x.FileSize), - mode: os.ModeDir | os.FileMode(x.Mode), - modTime: time.Unix(int64(x.Mtime), 0), - Inode: i, - }, nil +// case ldirInodeHeader: +// return &FileInfo{ +// name: name, +// size: int64(x.FileSize), +// mode: os.ModeDir | os.FileMode(x.Mode), +// modTime: time.Unix(int64(x.Mtime), 0), +// Inode: i, +// }, nil - case regInodeHeader: - mode := os.FileMode(x.Mode & 0777) - if x.Mode&syscall.S_ISUID != 0 { - mode |= os.ModeSetuid - } - return &FileInfo{ - name: name, - size: int64(x.FileSize), - mode: mode, - modTime: time.Unix(int64(x.Mtime), 0), - Inode: i, - }, nil +// case regInodeHeader: +// mode := os.FileMode(x.Mode & 0777) +// if x.Mode&syscall.S_ISUID != 0 { +// mode |= os.ModeSetuid +// } +// return &FileInfo{ +// name: name, +// size: int64(x.FileSize), +// mode: mode, +// modTime: time.Unix(int64(x.Mtime), 0), +// Inode: i, +// }, nil - case lregInodeHeader: - mode := os.FileMode(x.Mode & 0777) - if x.Mode&syscall.S_ISUID != 0 { - mode |= os.ModeSetuid - } - return &FileInfo{ - name: name, - size: int64(x.FileSize), - mode: mode, - modTime: time.Unix(int64(x.Mtime), 0), - Inode: i, - }, nil +// case lregInodeHeader: +// mode := os.FileMode(x.Mode & 0777) +// if x.Mode&syscall.S_ISUID != 0 { +// mode |= os.ModeSetuid +// } +// return &FileInfo{ +// name: name, +// size: int64(x.FileSize), +// mode: mode, +// modTime: time.Unix(int64(x.Mtime), 0), +// Inode: i, +// }, nil - case symlinkInodeHeader: - return &FileInfo{ - name: name, - size: int64(x.SymlinkSize), - mode: os.ModeSymlink | os.FileMode(x.Mode), - modTime: time.Unix(int64(x.Mtime), 0), - Inode: i, - }, nil - } +// case symlinkInodeHeader: +// return &FileInfo{ +// name: name, +// size: int64(x.SymlinkSize), +// mode: os.ModeSymlink | os.FileMode(x.Mode), +// modTime: time.Unix(int64(x.Mtime), 0), +// Inode: i, +// }, nil +// } - return nil, fmt.Errorf("unknown inode type %T", inode) -} +// return nil, fmt.Errorf("unknown inode type %T", inode) +// } -func (r *Reader) ReadLink(i Inode) (string, error) { - // TODO: reduce code duplication with readInode - blockoffset, offset := r.inode(i) - br, err := r.blockReader(r.super.InodeTableStart+blockoffset, offset) - if err != nil { - return "", err - } - defer br.Close() +// func (r *Reader) ReadLink(i Inode) (string, error) { +// // TODO: reduce code duplication with readInode +// blockoffset, offset := r.inode(i) +// br, err := r.blockReader(r.super.InodeTableStart+blockoffset, offset) +// if err != nil { +// return "", err +// } +// defer br.Close() - // We need the inode type before we know which type to pass to binary.Read, - // so we need to read it twice: - var inodeType uint16 - typeBuf := bytes.NewBuffer(make([]byte, 0, binary.Size(inodeType))) - if err := binary.Read(io.TeeReader(br, typeBuf), binary.LittleEndian, &inodeType); err != nil { - return "", err - } - br = ioutil.NopCloser(io.MultiReader(typeBuf, br)) +// // We need the inode type before we know which type to pass to binary.Read, +// // so we need to read it twice: +// var inodeType uint16 +// typeBuf := bytes.NewBuffer(make([]byte, 0, binary.Size(inodeType))) +// if err := binary.Read(io.TeeReader(br, typeBuf), binary.LittleEndian, &inodeType); err != nil { +// return "", err +// } +// br = ioutil.NopCloser(io.MultiReader(typeBuf, br)) - if inodeType != symlinkType { - return "", fmt.Errorf("invalid inode type: got %d instead of symlink", inodeType) - } - var si symlinkInodeHeader - if err := binary.Read(br, binary.LittleEndian, &si); err != nil { - return "", err - } +// if inodeType != symlinkType { +// return "", fmt.Errorf("invalid inode type: got %d instead of symlink", inodeType) +// } +// var si symlinkInodeHeader +// if err := binary.Read(br, binary.LittleEndian, &si); err != nil { +// return "", err +// } - // Assumption: r.r is positioned right after the inode - buf := make([]byte, si.SymlinkSize) - if _, err := io.ReadFull(br, buf); err != nil { - return "", err - } - return string(buf), nil -} +// // Assumption: r.r is positioned right after the inode +// buf := make([]byte, si.SymlinkSize) +// if _, err := io.ReadFull(br, buf); err != nil { +// return "", err +// } +// return string(buf), nil +// } -func (r *Reader) FileReader(inode Inode) (*io.SectionReader, error) { - //log.Printf("Readfile(%v)", inode) - i, err := r.readInode(inode) - if err != nil { - return nil, err - } - //log.Printf("i: %+v", i) - // TODO(compression): read the blocksizes to read compressed blocks - switch ri := i.(type) { - case regInodeHeader: - off := int64(ri.StartBlock) + int64(ri.Offset) - return io.NewSectionReader(r.r, off, int64(ri.FileSize)), nil - case lregInodeHeader: - off := int64(ri.StartBlock) + int64(ri.Offset) - return io.NewSectionReader(r.r, off, int64(ri.FileSize)), nil - default: - return nil, fmt.Errorf("BUG: non-file inode type") - } -} +// func (r *Reader) FileReader(inode Inode) (*io.SectionReader, error) { +// //log.Printf("Readfile(%v)", inode) +// i, err := r.readInode(inode) +// if err != nil { +// return nil, err +// } +// //log.Printf("i: %+v", i) +// // TODO(compression): read the blocksizes to read compressed blocks +// switch ri := i.(type) { +// case regInodeHeader: +// off := int64(ri.StartBlock) + int64(ri.Offset) +// return io.NewSectionReader(r.r, off, int64(ri.FileSize)), nil +// case lregInodeHeader: +// off := int64(ri.StartBlock) + int64(ri.Offset) +// return io.NewSectionReader(r.r, off, int64(ri.FileSize)), nil +// default: +// return nil, fmt.Errorf("BUG: non-file inode type") +// } +// } -type FileNotFoundError struct { - path string -} +// type FileNotFoundError struct { +// path string +// } -func (e *FileNotFoundError) Error() string { - return fmt.Sprintf("%q not found", e.path) -} +// func (e *FileNotFoundError) Error() string { +// return fmt.Sprintf("%q not found", e.path) +// } -func (r *Reader) lookupComponent(parent Inode, component string) (Inode, error) { - rfis, err := r.readdir(parent, false) - if err != nil { - return 0, err - } - for _, rfi := range rfis { - if rfi.Name() == component { - return rfi.Sys().(*FileInfo).Inode, nil - } - } - return 0, &FileNotFoundError{path: component} -} +// func (r *Reader) lookupComponent(parent Inode, component string) (Inode, error) { +// rfis, err := r.readdir(parent, false) +// if err != nil { +// return 0, err +// } +// for _, rfi := range rfis { +// if rfi.Name() == component { +// return rfi.Sys().(*FileInfo).Inode, nil +// } +// } +// return 0, &FileNotFoundError{path: component} +// } -func (r *Reader) lookupPath(path string, followSymlink bool) (Inode, error) { - inode := r.RootInode() - parts := strings.Split(path, "/") - for idx, part := range parts { - var err error - inode, err = r.lookupComponent(inode, part) - if err != nil { - if _, ok := err.(*FileNotFoundError); ok { - return 0, &FileNotFoundError{path: path} - } - return 0, err - } - if !followSymlink { - continue - } - i, err := r.readInode(inode) - if err != nil { - return 0, xerrors.Errorf("Stat(%d): %v", inode, err) - } - if _, ok := i.(symlinkInodeHeader); ok { - target, err := r.ReadLink(inode) - if err != nil { - return 0, err - } - //log.Printf("component %q (full: %q) resolved to %q", part, parts[:idx+1], target) - target = filepath.Clean(filepath.Join(append(parts[:idx] /* parent */, target)...)) - //log.Printf("-> %s", target) - i, err := r.LookupPath(target) - if err != nil { - return 0, err - } - inode = i - } - } - return inode, nil -} +// func (r *Reader) lookupPath(path string, followSymlink bool) (Inode, error) { +// inode := r.RootInode() +// parts := strings.Split(path, "/") +// for idx, part := range parts { +// var err error +// inode, err = r.lookupComponent(inode, part) +// if err != nil { +// if _, ok := err.(*FileNotFoundError); ok { +// return 0, &FileNotFoundError{path: path} +// } +// return 0, err +// } +// if !followSymlink { +// continue +// } +// i, err := r.readInode(inode) +// if err != nil { +// return 0, xerrors.Errorf("Stat(%d): %v", inode, err) +// } +// if _, ok := i.(symlinkInodeHeader); ok { +// target, err := r.ReadLink(inode) +// if err != nil { +// return 0, err +// } +// //log.Printf("component %q (full: %q) resolved to %q", part, parts[:idx+1], target) +// target = filepath.Clean(filepath.Join(append(parts[:idx] /* parent */, target)...)) +// //log.Printf("-> %s", target) +// i, err := r.LookupPath(target) +// if err != nil { +// return 0, err +// } +// inode = i +// } +// } +// return inode, nil +// } -func (r *Reader) LookupPath(path string) (Inode, error) { - return r.lookupPath(path, true) -} +// func (r *Reader) LookupPath(path string) (Inode, error) { +// return r.lookupPath(path, true) +// } -// LlookupPath is like LookupPath, but does not follow symbolic links, i.e. will -// instead return the inode of the link itself. -func (r *Reader) LlookupPath(path string) (Inode, error) { - return r.lookupPath(path, false) -} +// // LlookupPath is like LookupPath, but does not follow symbolic links, i.e. will +// // instead return the inode of the link itself. +// func (r *Reader) LlookupPath(path string) (Inode, error) { +// return r.lookupPath(path, false) +// } -func (r *Reader) Readdir(dirInode Inode) ([]os.FileInfo, error) { - return r.readdir(dirInode, true) -} +// func (r *Reader) Readdir(dirInode Inode) ([]os.FileInfo, error) { +// return r.readdir(dirInode, true) +// } -// Like Readdir, but does not call Stat on each file. The returned FileInfo -// structs will still have a filled in Name, partly filled in Mode, and filled -// in Inode. -func (r *Reader) ReaddirNoStat(dirInode Inode) ([]os.FileInfo, error) { - return r.readdir(dirInode, false) -} +// // Like Readdir, but does not call Stat on each file. The returned FileInfo +// // structs will still have a filled in Name, partly filled in Mode, and filled +// // in Inode. +// func (r *Reader) ReaddirNoStat(dirInode Inode) ([]os.FileInfo, error) { +// return r.readdir(dirInode, false) +// } -var nameBufPool = sync.Pool{ - New: func() interface{} { - return &bytes.Buffer{} - }, -} +// var nameBufPool = sync.Pool{ +// New: func() interface{} { +// return &bytes.Buffer{} +// }, +// } -func (r *Reader) readdir(dirInode Inode, stat bool) ([]os.FileInfo, error) { - //log.Printf("Readdir(%v (%x))", dirInode, dirInode) - i, err := r.readInode(dirInode) - fmt.Println("Yodle") - if err != nil { - return nil, err - } - var ( - startBlock int64 - fileSize int64 - offset int64 - ) - switch x := i.(type) { - case dirInodeHeader: - startBlock = int64(x.StartBlock) - fileSize = int64(x.FileSize) - offset = int64(x.Offset) +// func (r *Reader) readdir(dirInode Inode, stat bool) ([]os.FileInfo, error) { +// //log.Printf("Readdir(%v (%x))", dirInode, dirInode) +// i, err := r.readInode(dirInode) +// fmt.Println("Yodle") +// if err != nil { +// return nil, err +// } +// var ( +// startBlock int64 +// fileSize int64 +// offset int64 +// ) +// switch x := i.(type) { +// case dirInodeHeader: +// startBlock = int64(x.StartBlock) +// fileSize = int64(x.FileSize) +// offset = int64(x.Offset) - case ldirInodeHeader: - startBlock = int64(x.StartBlock) - fileSize = int64(x.FileSize) - offset = int64(x.Offset) +// case ldirInodeHeader: +// startBlock = int64(x.StartBlock) +// fileSize = int64(x.FileSize) +// offset = int64(x.Offset) - default: - return nil, fmt.Errorf("unknown directory inode type %T", i) - } +// default: +// return nil, fmt.Errorf("unknown directory inode type %T", i) +// } - br, err := r.blockReader(r.super.DirectoryTableStart+startBlock, offset) - if err != nil { - return nil, err - } - defer br.Close() +// br, err := r.blockReader(r.super.DirectoryTableStart+startBlock, offset) +// if err != nil { +// return nil, err +// } +// defer br.Close() - // See also https://elixir.bootlin.com/linux/v4.18.9/source/fs/squashfs/dir.c#L63 - limit := fileSize - int64(len(".")) - int64(len("..")) - br = ioutil.NopCloser(io.LimitReader(br, limit)) +// // See also https://elixir.bootlin.com/linux/v4.18.9/source/fs/squashfs/dir.c#L63 +// limit := fileSize - int64(len(".")) - int64(len("..")) +// br = ioutil.NopCloser(io.LimitReader(br, limit)) - var fis []os.FileInfo - var dh dirHeader - var de dirEntry - var dhBuf [12]byte - var deBuf [8]byte - nameBuf := nameBufPool.Get().(*bytes.Buffer) - defer nameBufPool.Put(nameBuf) - for { - if _, err := io.ReadFull(br, dhBuf[:]); err != nil { - if err == io.EOF { - return fis, nil - } - return nil, err - } - dh.Unmarshal(dhBuf[:]) - dh.Count++ // SquashFS stores count-1 - //log.Printf("dh: %+v", dh) +// var fis []os.FileInfo +// var dh dirHeader +// var de dirEntry +// var dhBuf [12]byte +// var deBuf [8]byte +// nameBuf := nameBufPool.Get().(*bytes.Buffer) +// defer nameBufPool.Put(nameBuf) +// for { +// if _, err := io.ReadFull(br, dhBuf[:]); err != nil { +// if err == io.EOF { +// return fis, nil +// } +// return nil, err +// } +// dh.Unmarshal(dhBuf[:]) +// dh.Count++ // SquashFS stores count-1 +// //log.Printf("dh: %+v", dh) - for i := 0; i < int(dh.Count); i++ { - if _, err := io.ReadFull(br, deBuf[:]); err != nil { - return nil, err - } - de.Unmarshal(deBuf[:]) - de.Size++ // SquashFS stores size-1 - //log.Printf("de: %+v", de) - nameBuf.Reset() - nameBuf.Grow(int(de.Size)) - nb := nameBuf.Bytes()[:de.Size] - if _, err := io.ReadFull(br, nb); err != nil { - return nil, err - } - name := string(nb) - //log.Printf("name: %q", string(name)) +// for i := 0; i < int(dh.Count); i++ { +// if _, err := io.ReadFull(br, deBuf[:]); err != nil { +// return nil, err +// } +// de.Unmarshal(deBuf[:]) +// de.Size++ // SquashFS stores size-1 +// //log.Printf("de: %+v", de) +// nameBuf.Reset() +// nameBuf.Grow(int(de.Size)) +// nb := nameBuf.Bytes()[:de.Size] +// if _, err := io.ReadFull(br, nb); err != nil { +// return nil, err +// } +// name := string(nb) +// //log.Printf("name: %q", string(name)) - var fi os.FileInfo - if stat { - var err error - fi, err = r.Stat(name, Inode(int64(dh.StartBlock)<<16|int64(de.Offset))) - if err != nil { - return nil, err - } - } else { - ffi := &FileInfo{ - name: name, - Inode: Inode(int64(dh.StartBlock)<<16 | int64(de.Offset)), - } - switch de.EntryType { - case dirType, ldirType: - ffi.mode |= os.ModeDir - case symlinkType, lsymlinkType: - ffi.mode |= os.ModeSymlink - } - fi = ffi - } - fis = append(fis, fi) - } - } +// var fi os.FileInfo +// if stat { +// var err error +// fi, err = r.Stat(name, Inode(int64(dh.StartBlock)<<16|int64(de.Offset))) +// if err != nil { +// return nil, err +// } +// } else { +// ffi := &FileInfo{ +// name: name, +// Inode: Inode(int64(dh.StartBlock)<<16 | int64(de.Offset)), +// } +// switch de.EntryType { +// case dirType, ldirType: +// ffi.mode |= os.ModeDir +// case symlinkType, lsymlinkType: +// ffi.mode |= os.ModeSymlink +// } +// fi = ffi +// } +// fis = append(fis, fi) +// } +// } - return fis, nil -} +// return fis, nil +// } -type FileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time - Inode Inode -} +// type FileInfo struct { +// name string +// size int64 +// mode os.FileMode +// modTime time.Time +// Inode Inode +// } -func (fi *FileInfo) Name() string { return fi.name } -func (fi *FileInfo) Size() int64 { return fi.size } -func (fi *FileInfo) Mode() os.FileMode { return fi.mode } -func (fi *FileInfo) IsDir() bool { return fi.mode.IsDir() } -func (fi *FileInfo) ModTime() time.Time { return fi.modTime } -func (fi *FileInfo) Sys() interface{} { return fi } +// func (fi *FileInfo) Name() string { return fi.name } +// func (fi *FileInfo) Size() int64 { return fi.size } +// func (fi *FileInfo) Mode() os.FileMode { return fi.mode } +// func (fi *FileInfo) IsDir() bool { return fi.mode.IsDir() } +// func (fi *FileInfo) ModTime() time.Time { return fi.modTime } +// func (fi *FileInfo) Sys() interface{} { return fi } -func (r *Reader) readXattr(tableHeader xattrTableHeader, id xattrId) (*Xattr, error) { - blockoffset, offset := r.inode(Inode(id.Xattr)) - br, err := r.blockReader(int64(tableHeader.XattrTableStart)+blockoffset, offset) - if err != nil { - return nil, err - } - defer br.Close() - var typ, nameSize uint16 - if err := binary.Read(br, binary.LittleEndian, &typ); err != nil { - return nil, err - } - if err := binary.Read(br, binary.LittleEndian, &nameSize); err != nil { - return nil, err - } - log.Printf("type = %v, nameSize = %v", typ, nameSize) - name := make([]byte, nameSize) - if _, err := io.ReadFull(br, name); err != nil { - return nil, err - } - log.Printf("name = %v", string(name)) - var valSize uint32 - if err := binary.Read(br, binary.LittleEndian, &valSize); err != nil { - return nil, err - } - val := make([]byte, valSize) - if _, err := io.ReadFull(br, val); err != nil { - return nil, err - } - log.Printf("val = %x", val) - return &Xattr{ - Type: typ, - FullName: xattrPrefix[int(typ)] + string(name), - Value: val, - }, nil -} +// func (r *Reader) readXattr(tableHeader xattrTableHeader, id xattrId) (*Xattr, error) { +// blockoffset, offset := r.inode(Inode(id.Xattr)) +// br, err := r.blockReader(int64(tableHeader.XattrTableStart)+blockoffset, offset) +// if err != nil { +// return nil, err +// } +// defer br.Close() +// var typ, nameSize uint16 +// if err := binary.Read(br, binary.LittleEndian, &typ); err != nil { +// return nil, err +// } +// if err := binary.Read(br, binary.LittleEndian, &nameSize); err != nil { +// return nil, err +// } +// log.Printf("type = %v, nameSize = %v", typ, nameSize) +// name := make([]byte, nameSize) +// if _, err := io.ReadFull(br, name); err != nil { +// return nil, err +// } +// log.Printf("name = %v", string(name)) +// var valSize uint32 +// if err := binary.Read(br, binary.LittleEndian, &valSize); err != nil { +// return nil, err +// } +// val := make([]byte, valSize) +// if _, err := io.ReadFull(br, val); err != nil { +// return nil, err +// } +// log.Printf("val = %x", val) +// return &Xattr{ +// Type: typ, +// FullName: xattrPrefix[int(typ)] + string(name), +// Value: val, +// }, nil +// } -func (r *Reader) ReadXattrs(inode Inode) ([]Xattr, error) { - //log.Printf("Readdir(%v (%x))", dirInode, dirInode) - i, err := r.readInode(inode) - if err != nil { - return nil, err - } - var xid uint32 - switch x := i.(type) { - case regInodeHeader, - dirInodeHeader, - ldirInodeHeader, - symlinkInodeHeader: - return nil, nil // no extended attributes +// func (r *Reader) ReadXattrs(inode Inode) ([]Xattr, error) { +// //log.Printf("Readdir(%v (%x))", dirInode, dirInode) +// i, err := r.readInode(inode) +// if err != nil { +// return nil, err +// } +// var xid uint32 +// switch x := i.(type) { +// case regInodeHeader, +// dirInodeHeader, +// ldirInodeHeader, +// symlinkInodeHeader: +// return nil, nil // no extended attributes - case lregInodeHeader: - if x.Xattr == invalidXattr { - return nil, nil // file has no extended attributes - } - xid = x.Xattr +// case lregInodeHeader: +// if x.Xattr == invalidXattr { +// return nil, nil // file has no extended attributes +// } +// xid = x.Xattr - default: - return nil, fmt.Errorf("unknown inode type %T", i) - } +// default: +// return nil, fmt.Errorf("unknown inode type %T", i) +// } - const idEntriesPerBlock = 512 // = 8192 / 16 /* sizeof(xattrId) */ - block := xid / idEntriesPerBlock - offset := (xid % idEntriesPerBlock) * 16 - log.Printf("xattr id %d, block %d, offset %d", xid, block, offset) - log.Printf("r.super.XattrIdTableStart = 0x%x, r.super.XattrIdTableStart = %v", r.super.XattrIdTableStart, r.super.XattrIdTableStart) - br := ioutil.NopCloser(io.NewSectionReader(r.r, r.super.XattrIdTableStart, int64(16 /* sizeof(xattrTableHeader) */ +(block+1)*4 /* sizeof(uint32) */))) - var tableHeader xattrTableHeader - if err := binary.Read(br, binary.LittleEndian, &tableHeader); err != nil { - return nil, err - } - // index starts here - if _, err := io.CopyN(ioutil.Discard, br, int64(block*4 /* sizeof(uint32) */)); err != nil { - return nil, err - } - var blockOffset uint32 - if err := binary.Read(br, binary.LittleEndian, &blockOffset); err != nil { - return nil, err - } - log.Printf("blockOffset = 0x%x (%d)", blockOffset, blockOffset) - br, err = r.blockReader(int64(blockOffset), int64(offset)) - if err != nil { - return nil, err - } - defer br.Close() - var id xattrId - if err := binary.Read(br, binary.LittleEndian, &id); err != nil { - return nil, err - } - log.Printf("id: %+v", id) - log.Printf("tableHeader: %+v (start 0x%x)", tableHeader, tableHeader.XattrTableStart) +// const idEntriesPerBlock = 512 // = 8192 / 16 /* sizeof(xattrId) */ +// block := xid / idEntriesPerBlock +// offset := (xid % idEntriesPerBlock) * 16 +// log.Printf("xattr id %d, block %d, offset %d", xid, block, offset) +// log.Printf("r.super.XattrIdTableStart = 0x%x, r.super.XattrIdTableStart = %v", r.super.XattrIdTableStart, r.super.XattrIdTableStart) +// br := ioutil.NopCloser(io.NewSectionReader(r.r, r.super.XattrIdTableStart, int64(16 /* sizeof(xattrTableHeader) */ +(block+1)*4 /* sizeof(uint32) */))) +// var tableHeader xattrTableHeader +// if err := binary.Read(br, binary.LittleEndian, &tableHeader); err != nil { +// return nil, err +// } +// // index starts here +// if _, err := io.CopyN(ioutil.Discard, br, int64(block*4 /* sizeof(uint32) */)); err != nil { +// return nil, err +// } +// var blockOffset uint32 +// if err := binary.Read(br, binary.LittleEndian, &blockOffset); err != nil { +// return nil, err +// } +// log.Printf("blockOffset = 0x%x (%d)", blockOffset, blockOffset) +// br, err = r.blockReader(int64(blockOffset), int64(offset)) +// if err != nil { +// return nil, err +// } +// defer br.Close() +// var id xattrId +// if err := binary.Read(br, binary.LittleEndian, &id); err != nil { +// return nil, err +// } +// log.Printf("id: %+v", id) +// log.Printf("tableHeader: %+v (start 0x%x)", tableHeader, tableHeader.XattrTableStart) - var xattrs []Xattr - for i := 0; i < int(id.Count); i++ { - xattr, err := r.readXattr(tableHeader, id) - if err != nil { - return nil, err - } - xattrs = append(xattrs, *xattr) - } +// var xattrs []Xattr +// for i := 0; i < int(id.Count); i++ { +// xattr, err := r.readXattr(tableHeader, id) +// if err != nil { +// return nil, err +// } +// xattrs = append(xattrs, *xattr) +// } - return xattrs, nil -} +// return xattrs, nil +// } diff --git a/reader_test.go b/reader_test.go index 500f5bb..78d99d3 100644 --- a/reader_test.go +++ b/reader_test.go @@ -1,373 +1,373 @@ package squashfs -import ( - "crypto/md5" - "fmt" - "io" - "os" - "testing" - "time" +// import ( +// "crypto/md5" +// "fmt" +// "io" +// "os" +// "testing" +// "time" - "github.com/google/go-cmp/cmp" -) +// "github.com/google/go-cmp/cmp" +// ) -func cmpFileInfo(got os.FileInfo, want FileInfo) error { - if got, want := got.Name(), want.name; got != want { - return fmt.Errorf("unexpected file name: got %q, want %q", got, want) - } - if got, want := got.Size(), want.size; got != want { - return fmt.Errorf("unexpected size: got %d, want %d", got, want) - } - if got, want := got.IsDir(), want.mode.IsDir(); got != want { - return fmt.Errorf("IsDir: got %v, want %v", got, want) - } - // TODO: re-enable when it’s no longer just a change detector - // if got, want := got.ModTime(), want.modTime; !got.Equal(want) { - // return fmt.Errorf("IsDir: got %v, want %v", got, want) - // } +// func cmpFileInfo(got os.FileInfo, want FileInfo) error { +// if got, want := got.Name(), want.name; got != want { +// return fmt.Errorf("unexpected file name: got %q, want %q", got, want) +// } +// if got, want := got.Size(), want.size; got != want { +// return fmt.Errorf("unexpected size: got %d, want %d", got, want) +// } +// if got, want := got.IsDir(), want.mode.IsDir(); got != want { +// return fmt.Errorf("IsDir: got %v, want %v", got, want) +// } +// // TODO: re-enable when it’s no longer just a change detector +// // if got, want := got.ModTime(), want.modTime; !got.Equal(want) { +// // return fmt.Errorf("IsDir: got %v, want %v", got, want) +// // } - return nil -} +// return nil +// } -func TestReaddir(t *testing.T) { - t.Parallel() - // TODO: ship testdata files generated by mksquashfs - pwd, err := os.Getwd() - if err != nil { - t.Fatal(err) - } - fmt.Println(pwd + "/testdata/testing.squashfs") - f, err := os.Open(pwd + "/testdata/testing.squashfs") - if err != nil { - t.Fatal(err) - } - defer f.Close() - rd, err := NewReader(f) - if err != nil { - t.Fatal(err) - } - fis, err := rd.Readdir(rd.RootInode()) - if err != nil { - t.Fatal(err) - } - for _, fi := range fis { - fmt.Println(fi.Name()) - } - rdr, err := rd.FileReader(fis[0].Sys().(*FileInfo).Inode) - if err != nil { - t.Fatal(err) - } - err = os.Remove(pwd + "/testdata/Magisk.zip") - if !os.IsNotExist(err) && err != nil { - t.Fatal(err) - } - // rdrzlib, err := zlib.NewReader(rdr) - magFil, err := os.Create(pwd + "/testdata/Magisk.zip") - io.Copy(magFil, rdr) - if got, want := len(fis), 3; got != want { - t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) - } +// func TestReaddir(t *testing.T) { +// t.Parallel() +// // TODO: ship testdata files generated by mksquashfs +// pwd, err := os.Getwd() +// if err != nil { +// t.Fatal(err) +// } +// fmt.Println(pwd + "/testdata/testing.squashfs") +// f, err := os.Open(pwd + "/testdata/testing.squashfs") +// if err != nil { +// t.Fatal(err) +// } +// defer f.Close() +// rd, err := NewReader(f) +// if err != nil { +// t.Fatal(err) +// } +// fis, err := rd.Readdir(rd.RootInode()) +// if err != nil { +// t.Fatal(err) +// } +// for _, fi := range fis { +// fmt.Println(fi.Name()) +// } +// rdr, err := rd.FileReader(fis[0].Sys().(*FileInfo).Inode) +// if err != nil { +// t.Fatal(err) +// } +// err = os.Remove(pwd + "/testdata/Magisk.zip") +// if !os.IsNotExist(err) && err != nil { +// t.Fatal(err) +// } +// // rdrzlib, err := zlib.NewReader(rdr) +// magFil, err := os.Create(pwd + "/testdata/Magisk.zip") +// io.Copy(magFil, rdr) +// if got, want := len(fis), 3; got != want { +// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) +// } - if err := cmpFileInfo(fis[0], FileInfo{ - name: "bin", - size: 26, - mode: 0555 | os.ModeDir, - modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/bin - }); err != nil { - t.Fatal(err) - } +// if err := cmpFileInfo(fis[0], FileInfo{ +// name: "bin", +// size: 26, +// mode: 0555 | os.ModeDir, +// modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/bin +// }); err != nil { +// t.Fatal(err) +// } - if err := cmpFileInfo(fis[1], FileInfo{ - name: "lib", - size: 3, - mode: 0555 | os.ModeDir, - modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/lib - }); err != nil { - t.Fatal(err) - } +// if err := cmpFileInfo(fis[1], FileInfo{ +// name: "lib", +// size: 3, +// mode: 0555 | os.ModeDir, +// modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/lib +// }); err != nil { +// t.Fatal(err) +// } - if err := cmpFileInfo(fis[2], FileInfo{ - name: "out", - size: 48, - mode: 0555 | os.ModeDir, - modTime: time.Unix(1581275130, 0), // stat -c %Y /ro/ack-amd64-2.24/out - }); err != nil { - t.Fatal(err) - } +// if err := cmpFileInfo(fis[2], FileInfo{ +// name: "out", +// size: 48, +// mode: 0555 | os.ModeDir, +// modTime: time.Unix(1581275130, 0), // stat -c %Y /ro/ack-amd64-2.24/out +// }); err != nil { +// t.Fatal(err) +// } - fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode) - if err != nil { - t.Fatal(err) - } +// fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode) +// if err != nil { +// t.Fatal(err) +// } - if got, want := len(fis), 1; got != want { - t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) - } +// if got, want := len(fis), 1; got != want { +// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) +// } - if err := cmpFileInfo(fis[0], FileInfo{ - name: "ack", - size: 38400, - mode: 0755, - modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/bin/ack - }); err != nil { - t.Fatal(err) - } -} +// if err := cmpFileInfo(fis[0], FileInfo{ +// name: "ack", +// size: 38400, +// mode: 0755, +// modTime: time.Unix(1581275131, 0), // stat -c %Y /ro/ack-amd64-2.24/bin/ack +// }); err != nil { +// t.Fatal(err) +// } +// } -// TestReaddirSmoke is a smoke-test, reading the root directories of SquashFS -// images which are known to trigger code paths which were buggy. -func TestReaddirSmoke(t *testing.T) { - t.Parallel() +// // TestReaddirSmoke is a smoke-test, reading the root directories of SquashFS +// // images which are known to trigger code paths which were buggy. +// func TestReaddirSmoke(t *testing.T) { +// t.Parallel() - for _, fn := range []string{ - // bash exercises the code path where an inode is split across metadata - // blocks. - "/home/michael/distri/_build/distri/pkg/bash-amd64-5.0-4.squashfs", +// for _, fn := range []string{ +// // bash exercises the code path where an inode is split across metadata +// // blocks. +// "/home/michael/distri/_build/distri/pkg/bash-amd64-5.0-4.squashfs", - // cmake exercises the code path where the root directory entries are - // located outside of the first block. - "/home/michael/distri/_build/distri/pkg/cmake-amd64-3.12.4-8.squashfs", - } { - // TODO: ship testdata files generated by mksquashfs - f, err := os.Open(fn) - if err != nil { - t.Fatal(err) - } - defer f.Close() - rd, err := NewReader(f) - if err != nil { - t.Fatal(err) - } +// // cmake exercises the code path where the root directory entries are +// // located outside of the first block. +// "/home/michael/distri/_build/distri/pkg/cmake-amd64-3.12.4-8.squashfs", +// } { +// // TODO: ship testdata files generated by mksquashfs +// f, err := os.Open(fn) +// if err != nil { +// t.Fatal(err) +// } +// defer f.Close() +// rd, err := NewReader(f) +// if err != nil { +// t.Fatal(err) +// } - fis, err := rd.Readdir(rd.RootInode()) - if err != nil { - t.Fatal(err) - } +// fis, err := rd.Readdir(rd.RootInode()) +// if err != nil { +// t.Fatal(err) +// } - if got, want := len(fis), 4; got != want { - t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) - } - } -} +// if got, want := len(fis), 4; got != want { +// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) +// } +// } +// } -func TestReaddirEmpty(t *testing.T) { - t.Parallel() - // TODO: ship testdata files generated by mksquashfs - f, err := os.Open("/home/michael/distri/_build/distri/pkg/zlib-amd64-1.2.11-4.squashfs") - if err != nil { - t.Fatal(err) - } - defer f.Close() - rd, err := NewReader(f) - if err != nil { - t.Fatal(err) - } +// func TestReaddirEmpty(t *testing.T) { +// t.Parallel() +// // TODO: ship testdata files generated by mksquashfs +// f, err := os.Open("/home/michael/distri/_build/distri/pkg/zlib-amd64-1.2.11-4.squashfs") +// if err != nil { +// t.Fatal(err) +// } +// defer f.Close() +// rd, err := NewReader(f) +// if err != nil { +// t.Fatal(err) +// } - fis, err := rd.Readdir(rd.RootInode()) - if err != nil { - t.Fatal(err) - } +// fis, err := rd.Readdir(rd.RootInode()) +// if err != nil { +// t.Fatal(err) +// } - if got, want := len(fis), 4; got != want { - t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) - } +// if got, want := len(fis), 4; got != want { +// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) +// } - if err := cmpFileInfo(fis[0], FileInfo{ - name: "bin", - size: 3, - mode: 0555 | os.ModeDir, - modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/bin - }); err != nil { - t.Fatal(err) - } +// if err := cmpFileInfo(fis[0], FileInfo{ +// name: "bin", +// size: 3, +// mode: 0555 | os.ModeDir, +// modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/bin +// }); err != nil { +// t.Fatal(err) +// } - fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode) - if err != nil { - t.Fatal(err) - } +// fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode) +// if err != nil { +// t.Fatal(err) +// } - if got, want := len(fis), 0; got != want { - t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) - } -} +// if got, want := len(fis), 0; got != want { +// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) +// } +// } -func TestReaddirSymlink(t *testing.T) { - t.Parallel() - // TODO: ship testdata files generated by mksquashfs - f, err := os.Open("/home/michael/distri/_build/distri/pkg/zlib-amd64-1.2.11-4.squashfs") - if err != nil { - t.Fatal(err) - } - defer f.Close() - rd, err := NewReader(f) - if err != nil { - t.Fatal(err) - } +// func TestReaddirSymlink(t *testing.T) { +// t.Parallel() +// // TODO: ship testdata files generated by mksquashfs +// f, err := os.Open("/home/michael/distri/_build/distri/pkg/zlib-amd64-1.2.11-4.squashfs") +// if err != nil { +// t.Fatal(err) +// } +// defer f.Close() +// rd, err := NewReader(f) +// if err != nil { +// t.Fatal(err) +// } - fis, err := rd.Readdir(rd.RootInode()) - if err != nil { - t.Fatal(err) - } +// fis, err := rd.Readdir(rd.RootInode()) +// if err != nil { +// t.Fatal(err) +// } - if got, want := len(fis), 4; got != want { - t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) - } +// if got, want := len(fis), 4; got != want { +// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) +// } - if err := cmpFileInfo(fis[3], FileInfo{ - name: "out", - size: 54, - mode: 0555 | os.ModeDir, - modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out - }); err != nil { - t.Fatal(err) - } +// if err := cmpFileInfo(fis[3], FileInfo{ +// name: "out", +// size: 54, +// mode: 0555 | os.ModeDir, +// modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out +// }); err != nil { +// t.Fatal(err) +// } - fis, err = rd.Readdir(fis[3].Sys().(*FileInfo).Inode) - if err != nil { - t.Fatal(err) - } +// fis, err = rd.Readdir(fis[3].Sys().(*FileInfo).Inode) +// if err != nil { +// t.Fatal(err) +// } - if got, want := len(fis), 3; got != want { - t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) - } +// if got, want := len(fis), 3; got != want { +// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) +// } - if err := cmpFileInfo(fis[1], FileInfo{ - name: "lib", - size: 83, - mode: 0555 | os.ModeDir, - modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out/lib - }); err != nil { - t.Fatal(err) - } +// if err := cmpFileInfo(fis[1], FileInfo{ +// name: "lib", +// size: 83, +// mode: 0555 | os.ModeDir, +// modTime: time.Unix(1583085224, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out/lib +// }); err != nil { +// t.Fatal(err) +// } - fis, err = rd.Readdir(fis[1].Sys().(*FileInfo).Inode) - if err != nil { - t.Fatal(err) - } +// fis, err = rd.Readdir(fis[1].Sys().(*FileInfo).Inode) +// if err != nil { +// t.Fatal(err) +// } - if got, want := len(fis), 4; got != want { - t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) - } +// if got, want := len(fis), 4; got != want { +// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) +// } - if err := cmpFileInfo(fis[1], FileInfo{ - name: "libz.so", - size: 9, - mode: 0555 | os.ModeSymlink, - modTime: time.Unix(1583085223, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out/lib/libz.so - }); err != nil { - t.Fatal(err) - } +// if err := cmpFileInfo(fis[1], FileInfo{ +// name: "libz.so", +// size: 9, +// mode: 0555 | os.ModeSymlink, +// modTime: time.Unix(1583085223, 0), // stat -c %Y /ro/zlib-amd64-1.2.11/out/lib/libz.so +// }); err != nil { +// t.Fatal(err) +// } - // TODO: readlink - target, err := rd.ReadLink(fis[1].Sys().(*FileInfo).Inode) - if err != nil { - t.Fatal(err) - } - if got, want := target, "libz.so.1"; got != want { - t.Fatalf("ReadLink(libz.so): got %q, want %q", got, want) - } -} +// // TODO: readlink +// target, err := rd.ReadLink(fis[1].Sys().(*FileInfo).Inode) +// if err != nil { +// t.Fatal(err) +// } +// if got, want := target, "libz.so.1"; got != want { +// t.Fatalf("ReadLink(libz.so): got %q, want %q", got, want) +// } +// } -func TestReadfile(t *testing.T) { - t.Parallel() - // TODO: ship testdata files generated by mksquashfs - f, err := os.Open("/home/michael/distri/_build/distri/pkg/ack-amd64-3.3.1-7.squashfs") - if err != nil { - t.Fatal(err) - } - defer f.Close() - rd, err := NewReader(f) - if err != nil { - t.Fatal(err) - } +// func TestReadfile(t *testing.T) { +// t.Parallel() +// // TODO: ship testdata files generated by mksquashfs +// f, err := os.Open("/home/michael/distri/_build/distri/pkg/ack-amd64-3.3.1-7.squashfs") +// if err != nil { +// t.Fatal(err) +// } +// defer f.Close() +// rd, err := NewReader(f) +// if err != nil { +// t.Fatal(err) +// } - fis, err := rd.Readdir(rd.RootInode()) - if err != nil { - t.Fatal(err) - } +// fis, err := rd.Readdir(rd.RootInode()) +// if err != nil { +// t.Fatal(err) +// } - if got, want := len(fis), 3; got != want { - t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) - } +// if got, want := len(fis), 3; got != want { +// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) +// } - fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode) - if err != nil { - t.Fatal(err) - } +// fis, err = rd.Readdir(fis[0].Sys().(*FileInfo).Inode) +// if err != nil { +// t.Fatal(err) +// } - if got, want := len(fis), 1; got != want { - t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) - } +// if got, want := len(fis), 1; got != want { +// t.Fatalf("unexpected number of directory entries: got %d, want %d", got, want) +// } - r, err := rd.FileReader(fis[0].Sys().(*FileInfo).Inode) - if err != nil { - t.Fatal(err) - } +// r, err := rd.FileReader(fis[0].Sys().(*FileInfo).Inode) +// if err != nil { +// t.Fatal(err) +// } - for i := 0; i < 2; i++ { - if _, err := r.Seek(0, io.SeekStart); err != nil { - t.Fatal(err) - } - h := md5.New() - if _, err := io.Copy(h, r); err != nil { - t.Fatal(err) - } - sum := fmt.Sprintf("%x", h.Sum(nil)) - if got, want := sum, "c6c9b5d4d2a49f1b8b5e501f0f827a5c"; got != want { - t.Fatalf("md5(bin/ack): got %s, want %s", got, want) - } - } -} +// for i := 0; i < 2; i++ { +// if _, err := r.Seek(0, io.SeekStart); err != nil { +// t.Fatal(err) +// } +// h := md5.New() +// if _, err := io.Copy(h, r); err != nil { +// t.Fatal(err) +// } +// sum := fmt.Sprintf("%x", h.Sum(nil)) +// if got, want := sum, "c6c9b5d4d2a49f1b8b5e501f0f827a5c"; got != want { +// t.Fatalf("md5(bin/ack): got %s, want %s", got, want) +// } +// } +// } -// TODO: add test exercising ldirInodeHeader, e.g. '/mnt/loop/ca-certificates-3.39/buildoutput/etc/ssl' +// // TODO: add test exercising ldirInodeHeader, e.g. '/mnt/loop/ca-certificates-3.39/buildoutput/etc/ssl' -func TestReadXattr(t *testing.T) { - t.Parallel() +// func TestReadXattr(t *testing.T) { +// t.Parallel() - // TODO: generate a smaller version of this file - f, err := os.Open("testdata/xattr.squashfs") - if err != nil { - t.Fatal(err) - } - defer f.Close() +// // TODO: generate a smaller version of this file +// f, err := os.Open("testdata/xattr.squashfs") +// if err != nil { +// t.Fatal(err) +// } +// defer f.Close() - rd, err := NewReader(f) - if err != nil { - t.Fatal(err) - } - for _, tt := range []struct { - Path string - Want []Xattr - }{ - { - Path: "mtr-packet", - Want: []Xattr{ - { - Type: XattrTypeSecurity, - FullName: "security.capability", - Value: []byte{1, 0, 0, 2, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, - }, - }, - { - Path: "gnome-keyring-daemon", - Want: []Xattr{ - { - Type: XattrTypeSecurity, - FullName: "security.capability", - Value: []byte{1, 0, 0, 2, 0, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, - }, - }, - } { - inode, err := rd.LookupPath(tt.Path) - if err != nil { - t.Fatal(err) - } - xattrs, err := rd.ReadXattrs(inode) - if err != nil { - t.Fatalf("ReadXattrs(%v): %v", inode, err) - } - if diff := cmp.Diff(tt.Want, xattrs); diff != "" { - t.Fatalf("unexpected ReadXattrs result: diff (-want +got):\n%s", diff) - } - } -} +// rd, err := NewReader(f) +// if err != nil { +// t.Fatal(err) +// } +// for _, tt := range []struct { +// Path string +// Want []Xattr +// }{ +// { +// Path: "mtr-packet", +// Want: []Xattr{ +// { +// Type: XattrTypeSecurity, +// FullName: "security.capability", +// Value: []byte{1, 0, 0, 2, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, +// }, +// }, +// { +// Path: "gnome-keyring-daemon", +// Want: []Xattr{ +// { +// Type: XattrTypeSecurity, +// FullName: "security.capability", +// Value: []byte{1, 0, 0, 2, 0, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, +// }, +// }, +// } { +// inode, err := rd.LookupPath(tt.Path) +// if err != nil { +// t.Fatal(err) +// } +// xattrs, err := rd.ReadXattrs(inode) +// if err != nil { +// t.Fatalf("ReadXattrs(%v): %v", inode, err) +// } +// if diff := cmp.Diff(tt.Want, xattrs); diff != "" { +// t.Fatalf("unexpected ReadXattrs result: diff (-want +got):\n%s", diff) +// } +// } +// } diff --git a/squashfs_test.go b/squashfs_test.go index 2696020..ca4d9f3 100644 --- a/squashfs_test.go +++ b/squashfs_test.go @@ -1,4 +1,4 @@ -package squashfs_test +package squashfs import ( "io" @@ -15,8 +15,29 @@ const ( squashfsName = "Code_OSS.Squashfs" ) -func TestCreateSquashFromAppImage(t *testing.T) { +func TestAppImageSquash(t *testing.T) { t.Parallel() + wd, err := os.Getwd() + if err != nil { + t.Error(err) + } + squashFil, err := os.Open(wd + "/testing/" + squashfsName) + if os.IsNotExist(err) { + TestCreateSquashFromAppImage(t) + squashFil, err = os.Open(wd + "/testing/" + squashfsName) + if err != nil { + t.Error(err) + } + } + defer squashFil.Close() + squash, err := NewSquashfs(squashFil) + if err != nil { + t.Error(err) + } + t.Fatal("Testing") +} + +func TestCreateSquashFromAppImage(t *testing.T) { wd, err := os.Getwd() if err != nil { t.Fatal(err) diff --git a/superblock.go b/superblock.go index e163c02..6c7225a 100644 --- a/superblock.go +++ b/superblock.go @@ -1,24 +1,59 @@ package squashfs +//Superblock is a raw representation of a squashfs //Descriptions provided by https://dr-emann.github.io/squashfs/ -type superblock struct { - Magic uint32 //Magic will be 0x73717368 if it's a legit Squashfs filesystem - Inodes uint32 //Inodes is the number of inodes in the inodes table - MkfsTime int32 //MkfsTime is when archive was created - BlockSize uint32 //BlockSize is the size of data blocks in bytes - Fragments uint32 //Fragments is the number of entries in fragment table - Compression uint16 //Compression is what type of compression is used - BlockLog uint16 //BlockLog should be log base 2 of BlockSize. If not then the squash might be corrupt - Flags uint16 //Flags are the superblock's flags - IDCount uint16 //IDCount is the number of IDs in the id lookup table - Major uint16 - Minor uint16 - RootInode Inode - BytesUsed int64 - IDTableStart int64 - XattrIDTableStart int64 - InodeTableStart int64 - DirectoryTableStart int64 - FragmentTableStart int64 - LookupTableStart int64 +type Superblock struct { + Magic uint32 //Magic will be 0x73717368 if it's a legit Squashfs filesystem + Inodes uint32 //Inodes is the number of inodes in the inodes table + MkfsTime uint32 //MkfsTime is when archive was created + BlockSize uint32 //BlockSize is the size of data blocks in bytes + Fragments uint32 //Fragments is the number of entries in fragment table + Compression uint16 //Compression is what type of compression is used + BlockLog uint16 //BlockLog should be log base 2 of BlockSize. If not then the squash might be corrupt + Flags uint16 //Flags are the superblock's flags + IDCount uint16 //IDCount is the number of IDs in the id lookup table + Major uint16 //Major version of squashfs format + Minor uint16 //Minor version of squashfs format + RootInode uint64 //RootInode is a reference to the root of the squashfs + BytesUsed uint64 //BytesUsed is how many bytes the archive is. squashfs archives are often padded to 4KB. + IDTableOffset uint64 //IDTableOff is the byte offset of the IDTable + XattrIDTableOffset uint64 //XattrIDTableOffset is the byte offset of the xattr id table + InodeTableOffset uint64 //InodeTableOffset is the byte offset of the inode table + DirectoryTableOffset uint64 //DirectoryTableOffset is the byte offset of the directory table + FragmentTableOffset uint64 //FragmentTableOffset is the byte offset of the fragment table + ExportTableOffset uint64 //ExportTableOffset is the byte offset of the export table +} + +//SuperblockFlags is a parsed list of options set in Superblock.Flags +type SuperblockFlags struct { + UncompressedInodes bool + UncompressedData bool + Check bool //Check is unused in current versions of squashfs + UncompressedFragments bool + NoFragments bool + AlwaysFragments bool + Duplicates bool //Identical files are stored only once + Exportable bool + UncompressedXattrs bool + NoXattrs bool + CompressorOptions bool + UncompressedIDs bool +} + +//GetFlags returns the Flags parsed into a SuperblockFlags +func (s *Superblock) GetFlags() SuperblockFlags { + return SuperblockFlags{ + UncompressedInodes: s.Flags&0x1 == 0x1, + UncompressedData: s.Flags&0x2 == 0x2, + Check: s.Flags&0x4 == 0x4, + UncompressedFragments: s.Flags&0x8 == 0x8, + NoFragments: s.Flags&0x10 == 0x10, + AlwaysFragments: s.Flags&0x20 == 0x20, + Duplicates: s.Flags&0x40 == 0x40, + Exportable: s.Flags&0x80 == 0x80, + UncompressedXattrs: s.Flags&0x100 == 0x100, + NoXattrs: s.Flags&0x200 == 0x200, + CompressorOptions: s.Flags&0x400 == 0x400, + UncompressedIDs: s.Flags&0x800 == 0x800, + } } diff --git a/uncompress.go b/uncompress.go new file mode 100644 index 0000000..d161d00 --- /dev/null +++ b/uncompress.go @@ -0,0 +1,14 @@ +package squashfs + +import "io" + +func uncompressData(data []byte, compressionType int) []byte { + //TODO: check compression type and uncompress the data + return make([]byte, 0) +} + +//same os uncompressData, but uses a reader instead. reader's seek will be +func uncompressReaderData(reader *io.Reader, compressionType int) []byte { + //TODO: check compression type and uncompress the data + return make([]byte, 0) +} diff --git a/unsquash.go b/unsquash.go index c6f4ba7..0a41480 100644 --- a/unsquash.go +++ b/unsquash.go @@ -1,9 +1,30 @@ package squashfs -import "io" +import ( + "encoding/binary" + "io" +) -//Squashfs is a squashfs backed by a reader. +//Squashfs is a squashfs backed by a ReadSeeker. type Squashfs struct { - rdr *io.Reader //underlyting reader + rdr *io.ReaderAt //underlying reader super Superblock } + +//NewSquashfs creates a new Squashfs backed by the given reader +func NewSquashfs(reader io.ReaderAt) (*Squashfs, error) { + var superblock Superblock + err := binary.Read(io.NewSectionReader(reader, 0, int64(binary.Size(superblock))), binary.LittleEndian, &superblock) + if err != nil { + return nil, err + } + return &Squashfs{ + rdr: &reader, + super: superblock, + }, nil +} + +//GetFlags return the SuperblockFlags of the Squashfs +func (s *Squashfs) GetFlags() SuperblockFlags { + return s.super.GetFlags() +} diff --git a/writer_old.go b/writer_old.go index 42b3720..d013e6a 100644 --- a/writer_old.go +++ b/writer_old.go @@ -1,913 +1,913 @@ -// Package squashfs implements writing SquashFS file system images using zlib -// compression for data blocks (inodes and directory entries are written -// uncompressed for simplicity). -// -// Note that SquashFS requires directory entries to be sorted, i.e. files and -// directories need to be added in the correct order. -// -// This package intentionally only implements a subset of SquashFS. Notably, -// block devices, character devices, FIFOs, sockets and xattrs are not -// supported. +// // Package squashfs implements writing SquashFS file system images using zlib +// // compression for data blocks (inodes and directory entries are written +// // uncompressed for simplicity). +// // +// // Note that SquashFS requires directory entries to be sorted, i.e. files and +// // directories need to be added in the correct order. +// // +// // This package intentionally only implements a subset of SquashFS. Notably, +// // block devices, character devices, FIFOs, sockets and xattrs are not +// // supported. package squashfs -import ( - "bytes" - "compress/zlib" - "encoding/binary" - "io" - "os" - "path/filepath" - "strings" - "time" - - "golang.org/x/sys/unix" -) - -// inode contains a block number + offset within that block. -type Inode int64 - -const ( - zlibCompression = 1 + iota - lzmaCompression - lzoCompression - xzCompression - lz4Compression - zstdCompression -) - -const ( - invalidFragment = 0xFFFFFFFF - invalidXattr = 0xFFFFFFFF -) - -const ( - dirType = 1 + iota - fileType - symlinkType - blkdevType - chrdevType - fifoType - socketType - // The larger types are used for e.g. sparse files, xattrs, etc. - ldirType - lregType - lsymlinkType - lblkdevType - lchrdevType - lfifoType - lsocketType -) - -type inodeHeader struct { - InodeType uint16 - Mode uint16 - Uid uint16 - Gid uint16 - Mtime int32 - InodeNumber uint32 -} - -// fileType -type regInodeHeader struct { - inodeHeader - - // full byte offset from the start of the file system, e.g. 96 for first - // file contents. Not using fragments limits us to 2^32-1-96 (≈ 4GiB) bytes - // of file contents. - StartBlock uint32 - Fragment uint32 - Offset uint32 - FileSize uint32 - - // Followed by a uint32 array of compressed block sizes. -} - -// lregType -type lregInodeHeader struct { - inodeHeader - - // full byte offset from the start of the file system, e.g. 96 for first - // file contents. Not using fragments limits us to 2^32-1-96 (≈ 4GiB) bytes - // of file contents. - StartBlock uint64 - FileSize uint64 - Sparse uint64 - Nlink uint32 - Fragment uint32 - Offset uint32 - Xattr uint32 - - // Followed by a uint32 array of compressed block sizes. -} - -// symlinkType -type symlinkInodeHeader struct { - inodeHeader - - Nlink uint32 - SymlinkSize uint32 - - // Followed by a byte array of SymlinkSize bytes. -} - -// chrdevType and blkdevType -type devInodeHeader struct { - inodeHeader - - Nlink uint32 - Rdev uint32 -} - -// fifoType and socketType -type ipcInodeHeader struct { - inodeHeader - - Nlink uint32 -} - -// dirType -type dirInodeHeader struct { - inodeHeader - - StartBlock uint32 - Nlink uint32 - FileSize uint16 - Offset uint16 - ParentInode uint32 -} - -// ldirType -type ldirInodeHeader struct { - inodeHeader - - Nlink uint32 - FileSize uint32 - StartBlock uint32 - ParentInode uint32 - Icount uint16 - Offset uint16 - Xattr uint32 -} - -type dirHeader struct { - Count uint32 - StartBlock uint32 - InodeOffset uint32 -} - -func (d *dirHeader) Unmarshal(b []byte) { - _ = b[11] - e := binary.LittleEndian - d.Count = e.Uint32(b) - d.StartBlock = e.Uint32(b[4:]) - d.InodeOffset = e.Uint32(b[8:]) -} - -type dirEntry struct { - Offset uint16 - InodeNumber int16 - EntryType uint16 - Size uint16 - - // Followed by a byte array of Size bytes. -} - -func (d *dirEntry) Unmarshal(b []byte) { - _ = b[7] - e := binary.LittleEndian - d.Offset = e.Uint16(b) - d.InodeNumber = int16(e.Uint16(b[2:])) - d.EntryType = e.Uint16(b[4:]) - d.Size = e.Uint16(b[6:]) -} - -// xattr types -const ( - XattrTypeUser = iota - XattrTypeTrusted - XattrTypeSecurity -) - -var xattrPrefix = map[int]string{ - XattrTypeUser: "user.", - XattrTypeTrusted: "trusted.", - XattrTypeSecurity: "security.", -} - -type Xattr struct { - Type uint16 - FullName string - Value []byte -} - -func XattrFromAttr(attr string, val []byte) Xattr { - for typ, prefix := range xattrPrefix { - if !strings.HasPrefix(attr, prefix) { - continue - } - return Xattr{ - Type: uint16(typ), - FullName: strings.TrimPrefix(attr, prefix), - Value: val, - } - } - return Xattr{} -} - -type xattrId struct { - Xattr uint64 - Count uint32 - Size uint32 -} - -func writeIdTable(w io.WriteSeeker, ids []uint32) (start int64, err error) { - metaOff, err := w.Seek(0, io.SeekCurrent) - if err != nil { - return 0, err - } - var buf bytes.Buffer - if err := binary.Write(&buf, binary.LittleEndian, ids); err != nil { - return 0, err - } - - if err := binary.Write(w, binary.LittleEndian, uint16(buf.Len())|0x8000); err != nil { - return 0, err - } - if _, err := io.Copy(w, &buf); err != nil { - return 0, err - } - off, err := w.Seek(0, io.SeekCurrent) - if err != nil { - return 0, err - } - return off, binary.Write(w, binary.LittleEndian, metaOff) -} - -type fullDirEntry struct { - startBlock uint32 - offset uint16 - inodeNumber uint32 - entryType uint16 - name string -} - -const ( - magic = 0x73717368 - dataBlockSize = 131072 - metadataBlockSize = 8192 - majorVersion = 4 - minorVersion = 0 -) - -type Writer struct { - // Root represents the file system root. Like all directories, Flush must be - // called precisely once. - Root *Directory - - xattrs []Xattr - xattrIds []xattrId - - w io.WriteSeeker - - sb superblock - inodeBuf bytes.Buffer - dirBuf bytes.Buffer - - writeInodeNumTo map[string][]int64 -} - -// TODO: document what this is doing and what it is used for -func slog(block uint32) uint16 { - for i := uint16(12); i <= 20; i++ { - if block == (1 << i) { - return i - } - } - return 0 -} - -// filesystemFlags returns flags for a SquashFS file system created by this -// package (disabling most features for now). -func filesystemFlags() uint16 { - const ( - noI = 1 << iota // uncompressed metadata - noD // uncompressed data - _ - noF // uncompressed fragments - noFrag // never use fragments - alwaysFrag // always use fragments - duplicateChecking // de-duplication - exportable // exportable via NFS - noX // uncompressed xattrs - noXattr // no xattrs - compopt // compressor-specific options present? - ) - return noI | noF | noFrag | noX | noXattr -} - -// NewWriter returns a Writer which will write a SquashFS file system image to w -// once Flush is called. -// -// Create new files and directories with the corresponding methods on the Root -// directory of the Writer. -// -// File data is written to w even before Flush is called. -func NewWriter(w io.WriteSeeker, mkfsTime time.Time) (*Writer, error) { - // Skip over superblock to the data area, we come back to the superblock - // when flushing. - if _, err := w.Seek(96, io.SeekStart); err != nil { - return nil, err - } - wr := &Writer{ - w: w, - sb: superblock{ - Magic: magic, - MkfsTime: int32(mkfsTime.Unix()), - BlockSize: dataBlockSize, - Fragments: 0, - Compression: zlibCompression, - BlockLog: slog(dataBlockSize), - Flags: filesystemFlags(), - NoIds: 1, // just one uid/gid mapping (for root) - Major: majorVersion, - Minor: minorVersion, - XattrIdTableStart: -1, // not present - LookupTableStart: -1, // not present - }, - writeInodeNumTo: make(map[string][]int64), - } - wr.Root = &Directory{ - w: wr, - name: "", // root - modTime: mkfsTime, - } - return wr, nil -} - -// Directory represents a SquashFS directory. -type Directory struct { - w *Writer - name string - modTime time.Time - dirEntries []fullDirEntry - parent *Directory -} - -func (d *Directory) path() string { - if d.parent == nil { - return d.name - } - return filepath.Join(d.parent.path(), d.name) -} - -type file struct { - w *Writer - d *Directory - off int64 - size uint32 - name string - modTime time.Time - mode uint16 - - // buf accumulates at least dataBlockSize bytes, at which point a new block - // is being written. - buf bytes.Buffer - - // blocksizes stores, for each block of dataBlockSize bytes (uncompressed), - // the number of bytes the block compressed down to. - blocksizes []uint32 - - // compBuf is used for holding a block during compression to avoid memory - // allocations. - compBuf *bytes.Buffer - // zlibWriter is re-used for each compressed block - zlibWriter *zlib.Writer - - xattrRef uint32 -} - -// Directory creates a new directory with the specified name and modTime. -func (d *Directory) Directory(name string, modTime time.Time) *Directory { - return &Directory{ - w: d.w, - name: name, - modTime: modTime, - parent: d, - } -} - -// File creates a file with the specified name, modTime and mode. The returned -// io.WriterCloser must be closed after writing the file. -func (d *Directory) File(name string, modTime time.Time, mode uint16, xattrs []Xattr) (io.WriteCloser, error) { - off, err := d.w.w.Seek(0, io.SeekCurrent) - if err != nil { - return nil, err - } - - // zlib.BestSpeed results in only a 2x slow-down over no compression - // (compared to >4x slow-down with DefaultCompression), but generates - // results which are in the same ball park (10% larger). - zw, err := zlib.NewWriterLevel(nil, zlib.BestSpeed) - if err != nil { - return nil, err - } - - xattrRef := uint32(invalidXattr) - if len(xattrs) > 0 { - xattrRef = uint32(len(d.w.xattrs)) - d.w.xattrs = append(d.w.xattrs, xattrs[0]) // TODO: support multiple - size := len(xattrs[0].FullName) + len(xattrs[0].Value) - d.w.xattrIds = append(d.w.xattrIds, xattrId{ - // Xattr is populated in writeXattrTables - Count: 1, // TODO: support multiple - Size: uint32(size), - }) - } - return &file{ - w: d.w, - d: d, - off: off, - name: name, - modTime: modTime, - mode: mode, - compBuf: bytes.NewBuffer(make([]byte, dataBlockSize)), - zlibWriter: zw, - xattrRef: xattrRef, - }, nil -} - -// Symlink creates a symbolic link from newname to oldname with the specified -// modTime and mode. -func (d *Directory) Symlink(oldname, newname string, modTime time.Time, mode os.FileMode) error { - startBlock := d.w.inodeBuf.Len() / metadataBlockSize - offset := d.w.inodeBuf.Len() - startBlock*metadataBlockSize - - if err := binary.Write(&d.w.inodeBuf, binary.LittleEndian, symlinkInodeHeader{ - inodeHeader: inodeHeader{ - InodeType: symlinkType, - Mode: uint16(mode), - Uid: 0, - Gid: 0, - Mtime: int32(modTime.Unix()), - InodeNumber: d.w.sb.Inodes + 1, - }, - Nlink: 1, // TODO(later): when is this not 1? - SymlinkSize: uint32(len(oldname)), - }); err != nil { - return err - } - if _, err := d.w.inodeBuf.Write([]byte(oldname)); err != nil { - return err - } - - d.dirEntries = append(d.dirEntries, fullDirEntry{ - startBlock: uint32(startBlock), - offset: uint16(offset), - inodeNumber: d.w.sb.Inodes + 1, - entryType: symlinkType, - name: newname, - }) - - d.w.sb.Inodes++ - return nil -} - -// Flush writes directory entries and creates inodes for the directory. -func (d *Directory) Flush() error { - countByStartBlock := make(map[uint32]uint32) - for _, de := range d.dirEntries { - countByStartBlock[de.startBlock]++ - } - - dirBufStartBlock := d.w.dirBuf.Len() / metadataBlockSize - dirBufOffset := d.w.dirBuf.Len() - - currentBlock := int64(-1) - currentInodeOffset := int64(-1) - var subdirs int - for _, de := range d.dirEntries { - if de.entryType == dirType { - subdirs++ - } - if int64(de.startBlock) != currentBlock { - dh := dirHeader{ - Count: countByStartBlock[de.startBlock] - 1, - StartBlock: de.startBlock * (metadataBlockSize + 2), - InodeOffset: de.inodeNumber, - } - if err := binary.Write(&d.w.dirBuf, binary.LittleEndian, &dh); err != nil { - return err - } - - currentBlock = int64(de.startBlock) - currentInodeOffset = int64(de.inodeNumber) - } - if err := binary.Write(&d.w.dirBuf, binary.LittleEndian, &dirEntry{ - Offset: de.offset, - InodeNumber: int16(de.inodeNumber - uint32(currentInodeOffset)), - EntryType: de.entryType, - Size: uint16(len(de.name) - 1), - }); err != nil { - return err - } - if _, err := d.w.dirBuf.Write([]byte(de.name)); err != nil { - return err - } - } - - startBlock := d.w.inodeBuf.Len() / metadataBlockSize - offset := d.w.inodeBuf.Len() - startBlock*metadataBlockSize - inodeBufOffset := d.w.inodeBuf.Len() - - // parentInodeOffset is the offset (in bytes) of the ParentInode field - // within a dirInodeHeader or ldirInodeHeader - var parentInodeOffset int64 - - if len(d.dirEntries) > 256 || - d.w.dirBuf.Len()-dirBufOffset > metadataBlockSize { - parentInodeOffset = (2 + 2 + 2 + 2 + 4 + 4) + 4 + 4 + 4 - if err := binary.Write(&d.w.inodeBuf, binary.LittleEndian, ldirInodeHeader{ - inodeHeader: inodeHeader{ - InodeType: ldirType, - Mode: unix.S_IRUSR | unix.S_IWUSR | unix.S_IXUSR | - unix.S_IRGRP | unix.S_IXGRP | - unix.S_IROTH | unix.S_IXOTH, - Uid: 0, - Gid: 0, - Mtime: int32(d.modTime.Unix()), - InodeNumber: d.w.sb.Inodes + 1, - }, - - Nlink: uint32(subdirs + 2 - 1), // + 2 for . and .. - FileSize: uint32(d.w.dirBuf.Len()-dirBufOffset) + 3, - StartBlock: uint32(dirBufStartBlock * (metadataBlockSize + 2)), - ParentInode: d.w.sb.Inodes + 2, // invalid - Icount: 0, // no directory index - Offset: uint16(dirBufOffset - dirBufStartBlock*metadataBlockSize), - Xattr: invalidXattr, - }); err != nil { - return err - } - } else { - parentInodeOffset = (2 + 2 + 2 + 2 + 4 + 4) + 4 + 4 + 2 + 2 - if err := binary.Write(&d.w.inodeBuf, binary.LittleEndian, dirInodeHeader{ - inodeHeader: inodeHeader{ - InodeType: dirType, - Mode: unix.S_IRUSR | unix.S_IWUSR | unix.S_IXUSR | - unix.S_IRGRP | unix.S_IXGRP | - unix.S_IROTH | unix.S_IXOTH, - Uid: 0, - Gid: 0, - Mtime: int32(d.modTime.Unix()), - InodeNumber: d.w.sb.Inodes + 1, - }, - StartBlock: uint32(dirBufStartBlock * (metadataBlockSize + 2)), - Nlink: uint32(subdirs + 2 - 1), // + 2 for . and .. - FileSize: uint16(d.w.dirBuf.Len()-dirBufOffset) + 3, - Offset: uint16(dirBufOffset - dirBufStartBlock*metadataBlockSize), - ParentInode: d.w.sb.Inodes + 2, // invalid - }); err != nil { - return err - } - } - - path := d.path() - for _, offset := range d.w.writeInodeNumTo[path] { - // Directly manipulating unread data in bytes.Buffer via Bytes(), as per - // https://groups.google.com/d/msg/golang-nuts/1ON9XVQ1jXE/8j9RaeSYxuEJ - b := d.w.inodeBuf.Bytes() - binary.LittleEndian.PutUint32(b[offset:offset+4], d.w.sb.Inodes+1) - } - - if d.parent != nil { - parentPath := filepath.Dir(d.path()) - if parentPath == "." { - parentPath = "" - } - d.w.writeInodeNumTo[parentPath] = append(d.w.writeInodeNumTo[parentPath], int64(inodeBufOffset)+parentInodeOffset) - d.parent.dirEntries = append(d.parent.dirEntries, fullDirEntry{ - startBlock: uint32(startBlock), - offset: uint16(offset), - inodeNumber: d.w.sb.Inodes + 1, - entryType: dirType, - name: d.name, - }) - } else { // root - d.w.sb.RootInode = Inode((startBlock*(metadataBlockSize+2))<<16 | offset) - } - - d.w.sb.Inodes++ - - return nil -} - -// Write implements io.Writer -func (f *file) Write(p []byte) (n int, err error) { - n, err = f.buf.Write(p) - if n > 0 { - // Keep track of the uncompressed file size. - f.size += uint32(n) - for f.buf.Len() >= dataBlockSize { - if err := f.writeBlock(); err != nil { - return 0, err - } - } - } - return n, err -} - -func (f *file) writeBlock() error { - n := f.buf.Len() - if n > dataBlockSize { - n = dataBlockSize - } - // Feed dataBlockSize bytes to the compressor - b := f.buf.Bytes() - block := b[:n] - rest := b[n:] - /* - f.compBuf.Reset() - f.zlibWriter.Reset(f.compBuf) - if _, err := f.zlibWriter.Write(block); err != nil { - return err - } - if err := f.zlibWriter.Close(); err != nil { - return err - } - - size := f.compBuf.Len() - if size > len(block) { - // Copy uncompressed data: Linux returns i/o errors when it encounters a - // compressed block which is larger than the uncompressed data: - // https://github.com/torvalds/linux/blob/3ca24ce9ff764bc27bceb9b2fd8ece74846c3fd3/fs/squashfs/block.c#L150 - size = len(block) | (1 << 24) // SQUASHFS_COMPRESSED_BIT_BLOCK - if _, err := f.w.w.Write(block); err != nil { - return err - } - } else { - if _, err := io.Copy(f.w.w, f.compBuf); err != nil { - return err - } - } - */ - // Copy uncompressed data: Linux returns i/o errors when it encounters a - // compressed block which is larger than the uncompressed data: - // https://github.com/torvalds/linux/blob/3ca24ce9ff764bc27bceb9b2fd8ece74846c3fd3/fs/squashfs/block.c#L150 - size := len(block) | (1 << 24) // SQUASHFS_COMPRESSED_BIT_BLOCK - if _, err := f.w.w.Write(block); err != nil { - return err - } - - f.blocksizes = append(f.blocksizes, uint32(size)) - - // Keep the rest in f.buf for the next write - copy(b, rest) - f.buf.Truncate(len(rest)) - return nil -} - -// Close implements io.Closer -func (f *file) Close() error { - for f.buf.Len() > 0 { - if err := f.writeBlock(); err != nil { - return err - } - } - - startBlock := f.w.inodeBuf.Len() / metadataBlockSize - offset := f.w.inodeBuf.Len() - startBlock*metadataBlockSize - - if err := binary.Write(&f.w.inodeBuf, binary.LittleEndian, lregInodeHeader{ - inodeHeader: inodeHeader{ - InodeType: lregType, - Mode: f.mode, - Uid: 0, - Gid: 0, - Mtime: int32(f.modTime.Unix()), - InodeNumber: f.w.sb.Inodes + 1, - }, - StartBlock: uint64(f.off), - FileSize: uint64(f.size), - Nlink: 1, - Fragment: invalidFragment, - Offset: 0, - Xattr: f.xattrRef, - }); err != nil { - return err - } - - if err := binary.Write(&f.w.inodeBuf, binary.LittleEndian, f.blocksizes); err != nil { - return err - } - - f.d.dirEntries = append(f.d.dirEntries, fullDirEntry{ - startBlock: uint32(startBlock), - offset: uint16(offset), - inodeNumber: f.w.sb.Inodes + 1, - entryType: fileType, - name: f.name, - }) - - f.w.sb.Inodes++ - - return nil -} - -func writeXattr(w io.Writer, xattrs []Xattr) error { - for _, attr := range xattrs { - if err := binary.Write(w, binary.LittleEndian, struct { - Type uint16 - NameSize uint16 - }{ - Type: attr.Type, - NameSize: uint16(len(attr.FullName)), - }); err != nil { - return err - } - if _, err := w.Write([]byte(attr.FullName)); err != nil { - return err - } - - if err := binary.Write(w, binary.LittleEndian, struct { - ValSize uint32 - }{ - ValSize: uint32(len(attr.Value)), - }); err != nil { - return err - } - - if _, err := w.Write(attr.Value); err != nil { - return err - } - } - return nil -} - -type xattrTableHeader struct { - XattrTableStart uint64 - XattrIds uint32 - Unused uint32 -} - -func (w *Writer) writeXattrTables() (int64, error) { - if len(w.xattrs) == 0 { - return -1, nil - } - off, err := w.w.Seek(0, io.SeekCurrent) - if err != nil { - return 0, err - } - xattrTableStart := uint64(off) - - var xattrBuf bytes.Buffer - if err := writeXattr(&xattrBuf, w.xattrs); err != nil { - return 0, err - } - xattrBlocks := (xattrBuf.Len() + (metadataBlockSize - 1)) / metadataBlockSize - - if err := w.writeMetadataChunks(&xattrBuf); err != nil { - return 0, err - } - - // write xattr id table - off, err = w.w.Seek(0, io.SeekCurrent) - if err != nil { - return 0, err - } - idTableOff := uint64(off) - var xattrIdBuf bytes.Buffer - size := uint64(0) - for _, id := range w.xattrIds { - id.Xattr = uint64(size) - size += uint64(id.Size) + 8 /* sizeof(Type+NameSize+ValSize) */ - if err := binary.Write(&xattrIdBuf, binary.LittleEndian, id); err != nil { - return 0, err - } - } - if err := w.writeMetadataChunks(&xattrIdBuf); err != nil { - return 0, err - } - - // xattr table header - off, err = w.w.Seek(0, io.SeekCurrent) - if err != nil { - return 0, err - } - if err := binary.Write(w.w, binary.LittleEndian, xattrTableHeader{ - XattrTableStart: xattrTableStart, - XattrIds: uint32(len(w.xattrs)), - }); err != nil { - return 0, err - } - // write block index - for i := 0; i < xattrBlocks; i++ { - if err := binary.Write(w.w, binary.LittleEndian, struct { - BlockOffset uint64 - }{ - BlockOffset: idTableOff + (uint64(i) * (8192 + 2 /* sizeof(uint16) */)), - }); err != nil { - return 0, err - } - } - return off, nil -} - -// writeMetadataChunks copies from r to w in blocks of metadataBlockSize bytes -// each, prefixing each block with a uint16 length header, setting the -// uncompressed bit. -func (w *Writer) writeMetadataChunks(r io.Reader) error { - buf := make([]byte, metadataBlockSize) - for { - buf = buf[:metadataBlockSize] - n, err := r.Read(buf) - if err != nil { - if err == io.EOF { // done - return nil - } - return err - } - buf = buf[:n] - if err := binary.Write(w.w, binary.LittleEndian, uint16(len(buf))|0x8000); err != nil { - return err - } - if _, err := w.w.Write(buf); err != nil { - return err - } - } -} - -// Flush writes the SquashFS file system. The Writer must not be used after -// calling Flush. -func (w *Writer) Flush() error { - // (1) superblock will be written later - - // (2) compressor-specific options omitted - - // (3) data has already been written - - // (4) write inode table - off, err := w.w.Seek(0, io.SeekCurrent) - if err != nil { - return err - } - w.sb.InodeTableStart = off - - if err := w.writeMetadataChunks(&w.inodeBuf); err != nil { - return err - } - - // (5) write directory table - off, err = w.w.Seek(0, io.SeekCurrent) - if err != nil { - return err - } - w.sb.DirectoryTableStart = off - - if err := w.writeMetadataChunks(&w.dirBuf); err != nil { - return err - } - - // (6) fragment table omitted - off, err = w.w.Seek(0, io.SeekCurrent) - if err != nil { - return err - } - w.sb.FragmentTableStart = off - - // (7) export table omitted - - // (8) write uid/gid lookup table - idTableStart, err := writeIdTable(w.w, []uint32{0}) - if err != nil { - return err - } - w.sb.IdTableStart = idTableStart - - // (9) xattr table - off, err = w.writeXattrTables() - if err != nil { - return err - } - w.sb.XattrIdTableStart = off - - off, err = w.w.Seek(0, io.SeekCurrent) - if err != nil { - return err - } - w.sb.BytesUsed = off - - // Pad to 4096, required for the kernel to be able to access all pages - if pad := off % 4096; pad > 0 { - padding := make([]byte, 4096-pad) - if _, err := w.w.Write(padding); err != nil { - return err - } - } - - // (1) Write superblock - if _, err := w.w.Seek(0, io.SeekStart); err != nil { - return err - } - - return binary.Write(w.w, binary.LittleEndian, &w.sb) -} +// import ( +// "bytes" +// "compress/zlib" +// "encoding/binary" +// "io" +// "os" +// "path/filepath" +// "strings" +// "time" + +// "golang.org/x/sys/unix" +// ) + +// // inode contains a block number + offset within that block. +// type Inode int64 + +// const ( +// zlibCompression = 1 + iota +// lzmaCompression +// lzoCompression +// xzCompression +// lz4Compression +// zstdCompression +// ) + +// const ( +// invalidFragment = 0xFFFFFFFF +// invalidXattr = 0xFFFFFFFF +// ) + +// const ( +// dirType = 1 + iota +// fileType +// symlinkType +// blkdevType +// chrdevType +// fifoType +// socketType +// // The larger types are used for e.g. sparse files, xattrs, etc. +// ldirType +// lregType +// lsymlinkType +// lblkdevType +// lchrdevType +// lfifoType +// lsocketType +// ) + +// type inodeHeader struct { +// InodeType uint16 +// Mode uint16 +// Uid uint16 +// Gid uint16 +// Mtime int32 +// InodeNumber uint32 +// } + +// // fileType +// type regInodeHeader struct { +// inodeHeader + +// // full byte offset from the start of the file system, e.g. 96 for first +// // file contents. Not using fragments limits us to 2^32-1-96 (≈ 4GiB) bytes +// // of file contents. +// StartBlock uint32 +// Fragment uint32 +// Offset uint32 +// FileSize uint32 + +// // Followed by a uint32 array of compressed block sizes. +// } + +// // lregType +// type lregInodeHeader struct { +// inodeHeader + +// // full byte offset from the start of the file system, e.g. 96 for first +// // file contents. Not using fragments limits us to 2^32-1-96 (≈ 4GiB) bytes +// // of file contents. +// StartBlock uint64 +// FileSize uint64 +// Sparse uint64 +// Nlink uint32 +// Fragment uint32 +// Offset uint32 +// Xattr uint32 + +// // Followed by a uint32 array of compressed block sizes. +// } + +// // symlinkType +// type symlinkInodeHeader struct { +// inodeHeader + +// Nlink uint32 +// SymlinkSize uint32 + +// // Followed by a byte array of SymlinkSize bytes. +// } + +// // chrdevType and blkdevType +// type devInodeHeader struct { +// inodeHeader + +// Nlink uint32 +// Rdev uint32 +// } + +// // fifoType and socketType +// type ipcInodeHeader struct { +// inodeHeader + +// Nlink uint32 +// } + +// // dirType +// type dirInodeHeader struct { +// inodeHeader + +// StartBlock uint32 +// Nlink uint32 +// FileSize uint16 +// Offset uint16 +// ParentInode uint32 +// } + +// // ldirType +// type ldirInodeHeader struct { +// inodeHeader + +// Nlink uint32 +// FileSize uint32 +// StartBlock uint32 +// ParentInode uint32 +// Icount uint16 +// Offset uint16 +// Xattr uint32 +// } + +// type dirHeader struct { +// Count uint32 +// StartBlock uint32 +// InodeOffset uint32 +// } + +// func (d *dirHeader) Unmarshal(b []byte) { +// _ = b[11] +// e := binary.LittleEndian +// d.Count = e.Uint32(b) +// d.StartBlock = e.Uint32(b[4:]) +// d.InodeOffset = e.Uint32(b[8:]) +// } + +// type dirEntry struct { +// Offset uint16 +// InodeNumber int16 +// EntryType uint16 +// Size uint16 + +// // Followed by a byte array of Size bytes. +// } + +// func (d *dirEntry) Unmarshal(b []byte) { +// _ = b[7] +// e := binary.LittleEndian +// d.Offset = e.Uint16(b) +// d.InodeNumber = int16(e.Uint16(b[2:])) +// d.EntryType = e.Uint16(b[4:]) +// d.Size = e.Uint16(b[6:]) +// } + +// // xattr types +// const ( +// XattrTypeUser = iota +// XattrTypeTrusted +// XattrTypeSecurity +// ) + +// var xattrPrefix = map[int]string{ +// XattrTypeUser: "user.", +// XattrTypeTrusted: "trusted.", +// XattrTypeSecurity: "security.", +// } + +// type Xattr struct { +// Type uint16 +// FullName string +// Value []byte +// } + +// func XattrFromAttr(attr string, val []byte) Xattr { +// for typ, prefix := range xattrPrefix { +// if !strings.HasPrefix(attr, prefix) { +// continue +// } +// return Xattr{ +// Type: uint16(typ), +// FullName: strings.TrimPrefix(attr, prefix), +// Value: val, +// } +// } +// return Xattr{} +// } + +// type xattrId struct { +// Xattr uint64 +// Count uint32 +// Size uint32 +// } + +// func writeIdTable(w io.WriteSeeker, ids []uint32) (start int64, err error) { +// metaOff, err := w.Seek(0, io.SeekCurrent) +// if err != nil { +// return 0, err +// } +// var buf bytes.Buffer +// if err := binary.Write(&buf, binary.LittleEndian, ids); err != nil { +// return 0, err +// } + +// if err := binary.Write(w, binary.LittleEndian, uint16(buf.Len())|0x8000); err != nil { +// return 0, err +// } +// if _, err := io.Copy(w, &buf); err != nil { +// return 0, err +// } +// off, err := w.Seek(0, io.SeekCurrent) +// if err != nil { +// return 0, err +// } +// return off, binary.Write(w, binary.LittleEndian, metaOff) +// } + +// type fullDirEntry struct { +// startBlock uint32 +// offset uint16 +// inodeNumber uint32 +// entryType uint16 +// name string +// } + +// const ( +// magic = 0x73717368 +// dataBlockSize = 131072 +// metadataBlockSize = 8192 +// majorVersion = 4 +// minorVersion = 0 +// ) + +// type Writer struct { +// // Root represents the file system root. Like all directories, Flush must be +// // called precisely once. +// Root *Directory + +// xattrs []Xattr +// xattrIds []xattrId + +// w io.WriteSeeker + +// sb superblock +// inodeBuf bytes.Buffer +// dirBuf bytes.Buffer + +// writeInodeNumTo map[string][]int64 +// } + +// // TODO: document what this is doing and what it is used for +// func slog(block uint32) uint16 { +// for i := uint16(12); i <= 20; i++ { +// if block == (1 << i) { +// return i +// } +// } +// return 0 +// } + +// // filesystemFlags returns flags for a SquashFS file system created by this +// // package (disabling most features for now). +// func filesystemFlags() uint16 { +// const ( +// noI = 1 << iota // uncompressed metadata +// noD // uncompressed data +// _ +// noF // uncompressed fragments +// noFrag // never use fragments +// alwaysFrag // always use fragments +// duplicateChecking // de-duplication +// exportable // exportable via NFS +// noX // uncompressed xattrs +// noXattr // no xattrs +// compopt // compressor-specific options present? +// ) +// return noI | noF | noFrag | noX | noXattr +// } + +// // NewWriter returns a Writer which will write a SquashFS file system image to w +// // once Flush is called. +// // +// // Create new files and directories with the corresponding methods on the Root +// // directory of the Writer. +// // +// // File data is written to w even before Flush is called. +// func NewWriter(w io.WriteSeeker, mkfsTime time.Time) (*Writer, error) { +// // Skip over superblock to the data area, we come back to the superblock +// // when flushing. +// if _, err := w.Seek(96, io.SeekStart); err != nil { +// return nil, err +// } +// wr := &Writer{ +// w: w, +// sb: superblock{ +// Magic: magic, +// MkfsTime: int32(mkfsTime.Unix()), +// BlockSize: dataBlockSize, +// Fragments: 0, +// Compression: zlibCompression, +// BlockLog: slog(dataBlockSize), +// Flags: filesystemFlags(), +// NoIds: 1, // just one uid/gid mapping (for root) +// Major: majorVersion, +// Minor: minorVersion, +// XattrIdTableStart: -1, // not present +// LookupTableStart: -1, // not present +// }, +// writeInodeNumTo: make(map[string][]int64), +// } +// wr.Root = &Directory{ +// w: wr, +// name: "", // root +// modTime: mkfsTime, +// } +// return wr, nil +// } + +// // Directory represents a SquashFS directory. +// type Directory struct { +// w *Writer +// name string +// modTime time.Time +// dirEntries []fullDirEntry +// parent *Directory +// } + +// func (d *Directory) path() string { +// if d.parent == nil { +// return d.name +// } +// return filepath.Join(d.parent.path(), d.name) +// } + +// type file struct { +// w *Writer +// d *Directory +// off int64 +// size uint32 +// name string +// modTime time.Time +// mode uint16 + +// // buf accumulates at least dataBlockSize bytes, at which point a new block +// // is being written. +// buf bytes.Buffer + +// // blocksizes stores, for each block of dataBlockSize bytes (uncompressed), +// // the number of bytes the block compressed down to. +// blocksizes []uint32 + +// // compBuf is used for holding a block during compression to avoid memory +// // allocations. +// compBuf *bytes.Buffer +// // zlibWriter is re-used for each compressed block +// zlibWriter *zlib.Writer + +// xattrRef uint32 +// } + +// // Directory creates a new directory with the specified name and modTime. +// func (d *Directory) Directory(name string, modTime time.Time) *Directory { +// return &Directory{ +// w: d.w, +// name: name, +// modTime: modTime, +// parent: d, +// } +// } + +// // File creates a file with the specified name, modTime and mode. The returned +// // io.WriterCloser must be closed after writing the file. +// func (d *Directory) File(name string, modTime time.Time, mode uint16, xattrs []Xattr) (io.WriteCloser, error) { +// off, err := d.w.w.Seek(0, io.SeekCurrent) +// if err != nil { +// return nil, err +// } + +// // zlib.BestSpeed results in only a 2x slow-down over no compression +// // (compared to >4x slow-down with DefaultCompression), but generates +// // results which are in the same ball park (10% larger). +// zw, err := zlib.NewWriterLevel(nil, zlib.BestSpeed) +// if err != nil { +// return nil, err +// } + +// xattrRef := uint32(invalidXattr) +// if len(xattrs) > 0 { +// xattrRef = uint32(len(d.w.xattrs)) +// d.w.xattrs = append(d.w.xattrs, xattrs[0]) // TODO: support multiple +// size := len(xattrs[0].FullName) + len(xattrs[0].Value) +// d.w.xattrIds = append(d.w.xattrIds, xattrId{ +// // Xattr is populated in writeXattrTables +// Count: 1, // TODO: support multiple +// Size: uint32(size), +// }) +// } +// return &file{ +// w: d.w, +// d: d, +// off: off, +// name: name, +// modTime: modTime, +// mode: mode, +// compBuf: bytes.NewBuffer(make([]byte, dataBlockSize)), +// zlibWriter: zw, +// xattrRef: xattrRef, +// }, nil +// } + +// // Symlink creates a symbolic link from newname to oldname with the specified +// // modTime and mode. +// func (d *Directory) Symlink(oldname, newname string, modTime time.Time, mode os.FileMode) error { +// startBlock := d.w.inodeBuf.Len() / metadataBlockSize +// offset := d.w.inodeBuf.Len() - startBlock*metadataBlockSize + +// if err := binary.Write(&d.w.inodeBuf, binary.LittleEndian, symlinkInodeHeader{ +// inodeHeader: inodeHeader{ +// InodeType: symlinkType, +// Mode: uint16(mode), +// Uid: 0, +// Gid: 0, +// Mtime: int32(modTime.Unix()), +// InodeNumber: d.w.sb.Inodes + 1, +// }, +// Nlink: 1, // TODO(later): when is this not 1? +// SymlinkSize: uint32(len(oldname)), +// }); err != nil { +// return err +// } +// if _, err := d.w.inodeBuf.Write([]byte(oldname)); err != nil { +// return err +// } + +// d.dirEntries = append(d.dirEntries, fullDirEntry{ +// startBlock: uint32(startBlock), +// offset: uint16(offset), +// inodeNumber: d.w.sb.Inodes + 1, +// entryType: symlinkType, +// name: newname, +// }) + +// d.w.sb.Inodes++ +// return nil +// } + +// // Flush writes directory entries and creates inodes for the directory. +// func (d *Directory) Flush() error { +// countByStartBlock := make(map[uint32]uint32) +// for _, de := range d.dirEntries { +// countByStartBlock[de.startBlock]++ +// } + +// dirBufStartBlock := d.w.dirBuf.Len() / metadataBlockSize +// dirBufOffset := d.w.dirBuf.Len() + +// currentBlock := int64(-1) +// currentInodeOffset := int64(-1) +// var subdirs int +// for _, de := range d.dirEntries { +// if de.entryType == dirType { +// subdirs++ +// } +// if int64(de.startBlock) != currentBlock { +// dh := dirHeader{ +// Count: countByStartBlock[de.startBlock] - 1, +// StartBlock: de.startBlock * (metadataBlockSize + 2), +// InodeOffset: de.inodeNumber, +// } +// if err := binary.Write(&d.w.dirBuf, binary.LittleEndian, &dh); err != nil { +// return err +// } + +// currentBlock = int64(de.startBlock) +// currentInodeOffset = int64(de.inodeNumber) +// } +// if err := binary.Write(&d.w.dirBuf, binary.LittleEndian, &dirEntry{ +// Offset: de.offset, +// InodeNumber: int16(de.inodeNumber - uint32(currentInodeOffset)), +// EntryType: de.entryType, +// Size: uint16(len(de.name) - 1), +// }); err != nil { +// return err +// } +// if _, err := d.w.dirBuf.Write([]byte(de.name)); err != nil { +// return err +// } +// } + +// startBlock := d.w.inodeBuf.Len() / metadataBlockSize +// offset := d.w.inodeBuf.Len() - startBlock*metadataBlockSize +// inodeBufOffset := d.w.inodeBuf.Len() + +// // parentInodeOffset is the offset (in bytes) of the ParentInode field +// // within a dirInodeHeader or ldirInodeHeader +// var parentInodeOffset int64 + +// if len(d.dirEntries) > 256 || +// d.w.dirBuf.Len()-dirBufOffset > metadataBlockSize { +// parentInodeOffset = (2 + 2 + 2 + 2 + 4 + 4) + 4 + 4 + 4 +// if err := binary.Write(&d.w.inodeBuf, binary.LittleEndian, ldirInodeHeader{ +// inodeHeader: inodeHeader{ +// InodeType: ldirType, +// Mode: unix.S_IRUSR | unix.S_IWUSR | unix.S_IXUSR | +// unix.S_IRGRP | unix.S_IXGRP | +// unix.S_IROTH | unix.S_IXOTH, +// Uid: 0, +// Gid: 0, +// Mtime: int32(d.modTime.Unix()), +// InodeNumber: d.w.sb.Inodes + 1, +// }, + +// Nlink: uint32(subdirs + 2 - 1), // + 2 for . and .. +// FileSize: uint32(d.w.dirBuf.Len()-dirBufOffset) + 3, +// StartBlock: uint32(dirBufStartBlock * (metadataBlockSize + 2)), +// ParentInode: d.w.sb.Inodes + 2, // invalid +// Icount: 0, // no directory index +// Offset: uint16(dirBufOffset - dirBufStartBlock*metadataBlockSize), +// Xattr: invalidXattr, +// }); err != nil { +// return err +// } +// } else { +// parentInodeOffset = (2 + 2 + 2 + 2 + 4 + 4) + 4 + 4 + 2 + 2 +// if err := binary.Write(&d.w.inodeBuf, binary.LittleEndian, dirInodeHeader{ +// inodeHeader: inodeHeader{ +// InodeType: dirType, +// Mode: unix.S_IRUSR | unix.S_IWUSR | unix.S_IXUSR | +// unix.S_IRGRP | unix.S_IXGRP | +// unix.S_IROTH | unix.S_IXOTH, +// Uid: 0, +// Gid: 0, +// Mtime: int32(d.modTime.Unix()), +// InodeNumber: d.w.sb.Inodes + 1, +// }, +// StartBlock: uint32(dirBufStartBlock * (metadataBlockSize + 2)), +// Nlink: uint32(subdirs + 2 - 1), // + 2 for . and .. +// FileSize: uint16(d.w.dirBuf.Len()-dirBufOffset) + 3, +// Offset: uint16(dirBufOffset - dirBufStartBlock*metadataBlockSize), +// ParentInode: d.w.sb.Inodes + 2, // invalid +// }); err != nil { +// return err +// } +// } + +// path := d.path() +// for _, offset := range d.w.writeInodeNumTo[path] { +// // Directly manipulating unread data in bytes.Buffer via Bytes(), as per +// // https://groups.google.com/d/msg/golang-nuts/1ON9XVQ1jXE/8j9RaeSYxuEJ +// b := d.w.inodeBuf.Bytes() +// binary.LittleEndian.PutUint32(b[offset:offset+4], d.w.sb.Inodes+1) +// } + +// if d.parent != nil { +// parentPath := filepath.Dir(d.path()) +// if parentPath == "." { +// parentPath = "" +// } +// d.w.writeInodeNumTo[parentPath] = append(d.w.writeInodeNumTo[parentPath], int64(inodeBufOffset)+parentInodeOffset) +// d.parent.dirEntries = append(d.parent.dirEntries, fullDirEntry{ +// startBlock: uint32(startBlock), +// offset: uint16(offset), +// inodeNumber: d.w.sb.Inodes + 1, +// entryType: dirType, +// name: d.name, +// }) +// } else { // root +// d.w.sb.RootInode = Inode((startBlock*(metadataBlockSize+2))<<16 | offset) +// } + +// d.w.sb.Inodes++ + +// return nil +// } + +// // Write implements io.Writer +// func (f *file) Write(p []byte) (n int, err error) { +// n, err = f.buf.Write(p) +// if n > 0 { +// // Keep track of the uncompressed file size. +// f.size += uint32(n) +// for f.buf.Len() >= dataBlockSize { +// if err := f.writeBlock(); err != nil { +// return 0, err +// } +// } +// } +// return n, err +// } + +// func (f *file) writeBlock() error { +// n := f.buf.Len() +// if n > dataBlockSize { +// n = dataBlockSize +// } +// // Feed dataBlockSize bytes to the compressor +// b := f.buf.Bytes() +// block := b[:n] +// rest := b[n:] +// /* +// f.compBuf.Reset() +// f.zlibWriter.Reset(f.compBuf) +// if _, err := f.zlibWriter.Write(block); err != nil { +// return err +// } +// if err := f.zlibWriter.Close(); err != nil { +// return err +// } + +// size := f.compBuf.Len() +// if size > len(block) { +// // Copy uncompressed data: Linux returns i/o errors when it encounters a +// // compressed block which is larger than the uncompressed data: +// // https://github.com/torvalds/linux/blob/3ca24ce9ff764bc27bceb9b2fd8ece74846c3fd3/fs/squashfs/block.c#L150 +// size = len(block) | (1 << 24) // SQUASHFS_COMPRESSED_BIT_BLOCK +// if _, err := f.w.w.Write(block); err != nil { +// return err +// } +// } else { +// if _, err := io.Copy(f.w.w, f.compBuf); err != nil { +// return err +// } +// } +// */ +// // Copy uncompressed data: Linux returns i/o errors when it encounters a +// // compressed block which is larger than the uncompressed data: +// // https://github.com/torvalds/linux/blob/3ca24ce9ff764bc27bceb9b2fd8ece74846c3fd3/fs/squashfs/block.c#L150 +// size := len(block) | (1 << 24) // SQUASHFS_COMPRESSED_BIT_BLOCK +// if _, err := f.w.w.Write(block); err != nil { +// return err +// } + +// f.blocksizes = append(f.blocksizes, uint32(size)) + +// // Keep the rest in f.buf for the next write +// copy(b, rest) +// f.buf.Truncate(len(rest)) +// return nil +// } + +// // Close implements io.Closer +// func (f *file) Close() error { +// for f.buf.Len() > 0 { +// if err := f.writeBlock(); err != nil { +// return err +// } +// } + +// startBlock := f.w.inodeBuf.Len() / metadataBlockSize +// offset := f.w.inodeBuf.Len() - startBlock*metadataBlockSize + +// if err := binary.Write(&f.w.inodeBuf, binary.LittleEndian, lregInodeHeader{ +// inodeHeader: inodeHeader{ +// InodeType: lregType, +// Mode: f.mode, +// Uid: 0, +// Gid: 0, +// Mtime: int32(f.modTime.Unix()), +// InodeNumber: f.w.sb.Inodes + 1, +// }, +// StartBlock: uint64(f.off), +// FileSize: uint64(f.size), +// Nlink: 1, +// Fragment: invalidFragment, +// Offset: 0, +// Xattr: f.xattrRef, +// }); err != nil { +// return err +// } + +// if err := binary.Write(&f.w.inodeBuf, binary.LittleEndian, f.blocksizes); err != nil { +// return err +// } + +// f.d.dirEntries = append(f.d.dirEntries, fullDirEntry{ +// startBlock: uint32(startBlock), +// offset: uint16(offset), +// inodeNumber: f.w.sb.Inodes + 1, +// entryType: fileType, +// name: f.name, +// }) + +// f.w.sb.Inodes++ + +// return nil +// } + +// func writeXattr(w io.Writer, xattrs []Xattr) error { +// for _, attr := range xattrs { +// if err := binary.Write(w, binary.LittleEndian, struct { +// Type uint16 +// NameSize uint16 +// }{ +// Type: attr.Type, +// NameSize: uint16(len(attr.FullName)), +// }); err != nil { +// return err +// } +// if _, err := w.Write([]byte(attr.FullName)); err != nil { +// return err +// } + +// if err := binary.Write(w, binary.LittleEndian, struct { +// ValSize uint32 +// }{ +// ValSize: uint32(len(attr.Value)), +// }); err != nil { +// return err +// } + +// if _, err := w.Write(attr.Value); err != nil { +// return err +// } +// } +// return nil +// } + +// type xattrTableHeader struct { +// XattrTableStart uint64 +// XattrIds uint32 +// Unused uint32 +// } + +// func (w *Writer) writeXattrTables() (int64, error) { +// if len(w.xattrs) == 0 { +// return -1, nil +// } +// off, err := w.w.Seek(0, io.SeekCurrent) +// if err != nil { +// return 0, err +// } +// xattrTableStart := uint64(off) + +// var xattrBuf bytes.Buffer +// if err := writeXattr(&xattrBuf, w.xattrs); err != nil { +// return 0, err +// } +// xattrBlocks := (xattrBuf.Len() + (metadataBlockSize - 1)) / metadataBlockSize + +// if err := w.writeMetadataChunks(&xattrBuf); err != nil { +// return 0, err +// } + +// // write xattr id table +// off, err = w.w.Seek(0, io.SeekCurrent) +// if err != nil { +// return 0, err +// } +// idTableOff := uint64(off) +// var xattrIdBuf bytes.Buffer +// size := uint64(0) +// for _, id := range w.xattrIds { +// id.Xattr = uint64(size) +// size += uint64(id.Size) + 8 /* sizeof(Type+NameSize+ValSize) */ +// if err := binary.Write(&xattrIdBuf, binary.LittleEndian, id); err != nil { +// return 0, err +// } +// } +// if err := w.writeMetadataChunks(&xattrIdBuf); err != nil { +// return 0, err +// } + +// // xattr table header +// off, err = w.w.Seek(0, io.SeekCurrent) +// if err != nil { +// return 0, err +// } +// if err := binary.Write(w.w, binary.LittleEndian, xattrTableHeader{ +// XattrTableStart: xattrTableStart, +// XattrIds: uint32(len(w.xattrs)), +// }); err != nil { +// return 0, err +// } +// // write block index +// for i := 0; i < xattrBlocks; i++ { +// if err := binary.Write(w.w, binary.LittleEndian, struct { +// BlockOffset uint64 +// }{ +// BlockOffset: idTableOff + (uint64(i) * (8192 + 2 /* sizeof(uint16) */)), +// }); err != nil { +// return 0, err +// } +// } +// return off, nil +// } + +// // writeMetadataChunks copies from r to w in blocks of metadataBlockSize bytes +// // each, prefixing each block with a uint16 length header, setting the +// // uncompressed bit. +// func (w *Writer) writeMetadataChunks(r io.Reader) error { +// buf := make([]byte, metadataBlockSize) +// for { +// buf = buf[:metadataBlockSize] +// n, err := r.Read(buf) +// if err != nil { +// if err == io.EOF { // done +// return nil +// } +// return err +// } +// buf = buf[:n] +// if err := binary.Write(w.w, binary.LittleEndian, uint16(len(buf))|0x8000); err != nil { +// return err +// } +// if _, err := w.w.Write(buf); err != nil { +// return err +// } +// } +// } + +// // Flush writes the SquashFS file system. The Writer must not be used after +// // calling Flush. +// func (w *Writer) Flush() error { +// // (1) superblock will be written later + +// // (2) compressor-specific options omitted + +// // (3) data has already been written + +// // (4) write inode table +// off, err := w.w.Seek(0, io.SeekCurrent) +// if err != nil { +// return err +// } +// w.sb.InodeTableStart = off + +// if err := w.writeMetadataChunks(&w.inodeBuf); err != nil { +// return err +// } + +// // (5) write directory table +// off, err = w.w.Seek(0, io.SeekCurrent) +// if err != nil { +// return err +// } +// w.sb.DirectoryTableStart = off + +// if err := w.writeMetadataChunks(&w.dirBuf); err != nil { +// return err +// } + +// // (6) fragment table omitted +// off, err = w.w.Seek(0, io.SeekCurrent) +// if err != nil { +// return err +// } +// w.sb.FragmentTableStart = off + +// // (7) export table omitted + +// // (8) write uid/gid lookup table +// idTableStart, err := writeIdTable(w.w, []uint32{0}) +// if err != nil { +// return err +// } +// w.sb.IdTableStart = idTableStart + +// // (9) xattr table +// off, err = w.writeXattrTables() +// if err != nil { +// return err +// } +// w.sb.XattrIdTableStart = off + +// off, err = w.w.Seek(0, io.SeekCurrent) +// if err != nil { +// return err +// } +// w.sb.BytesUsed = off + +// // Pad to 4096, required for the kernel to be able to access all pages +// if pad := off % 4096; pad > 0 { +// padding := make([]byte, 4096-pad) +// if _, err := w.w.Write(padding); err != nil { +// return err +// } +// } + +// // (1) Write superblock +// if _, err := w.w.Seek(0, io.SeekStart); err != nil { +// return err +// } + +// return binary.Write(w.w, binary.LittleEndian, &w.sb) +// } diff --git a/writer_test.go b/writer_test.go index a42b51b..759c358 100644 --- a/writer_test.go +++ b/writer_test.go @@ -1,310 +1,310 @@ package squashfs -import ( - "bytes" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - "time" +// import ( +// "bytes" +// "flag" +// "fmt" +// "io" +// "io/ioutil" +// "os" +// "os/exec" +// "path/filepath" +// "strings" +// "testing" +// "time" - "github.com/distr1/distri" - // "github.com/distr1/distri/internal/distritest" - "github.com/google/go-cmp/cmp" - "github.com/orcaman/writerseeker" - "golang.org/x/sys/unix" -) +// "github.com/distr1/distri" +// // "github.com/distr1/distri/internal/distritest" +// "github.com/google/go-cmp/cmp" +// "github.com/orcaman/writerseeker" +// "golang.org/x/sys/unix" +// ) -var fsImagePath = flag.String("fs_image_path", "", "Store the SquashFS test file system in the specified path for manual inspection") +// var fsImagePath = flag.String("fs_image_path", "", "Store the SquashFS test file system in the specified path for manual inspection") -func writeTestImage(iow io.WriteSeeker, xattr bool) error { - w, err := NewWriter(iow, time.Now()) - if err != nil { - return err - } +// func writeTestImage(iow io.WriteSeeker, xattr bool) error { +// w, err := NewWriter(iow, time.Now()) +// if err != nil { +// return err +// } - var xattrs []Xattr - if xattr { - xattrs = append(xattrs, Xattr{ - Type: 2, - FullName: "capability", - Value: []byte{1, 0, 0, 2, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }) - } - ff, err := w.Root.File("hellö wörld", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, xattrs) - if err != nil { - return err - } - if _, err := ff.Write([]byte("hello world!")); err != nil { - return err - } - if err := ff.Close(); err != nil { - return err - } +// var xattrs []Xattr +// if xattr { +// xattrs = append(xattrs, Xattr{ +// Type: 2, +// FullName: "capability", +// Value: []byte{1, 0, 0, 2, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, +// }) +// } +// ff, err := w.Root.File("hellö wörld", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, xattrs) +// if err != nil { +// return err +// } +// if _, err := ff.Write([]byte("hello world!")); err != nil { +// return err +// } +// if err := ff.Close(); err != nil { +// return err +// } - ff, err = w.Root.File("leer", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil) - if err != nil { - return err - } - if err := ff.Close(); err != nil { - return err - } +// ff, err = w.Root.File("leer", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil) +// if err != nil { +// return err +// } +// if err := ff.Close(); err != nil { +// return err +// } - ff, err = w.Root.File("second file", time.Now(), unix.S_IRUSR|unix.S_IXUSR| - unix.S_IRGRP|unix.S_IXGRP| - unix.S_IROTH|unix.S_IXOTH, - nil) - if err != nil { - return err - } - if _, err := ff.Write([]byte("NON.\n")); err != nil { - return err - } - if err := ff.Close(); err != nil { - return err - } +// ff, err = w.Root.File("second file", time.Now(), unix.S_IRUSR|unix.S_IXUSR| +// unix.S_IRGRP|unix.S_IXGRP| +// unix.S_IROTH|unix.S_IXOTH, +// nil) +// if err != nil { +// return err +// } +// if _, err := ff.Write([]byte("NON.\n")); err != nil { +// return err +// } +// if err := ff.Close(); err != nil { +// return err +// } - if err := w.Root.Symlink("second file", "second link", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH); err != nil { - return err - } +// if err := w.Root.Symlink("second file", "second link", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH); err != nil { +// return err +// } - subdir := w.Root.Directory("subdir", time.Now()) +// subdir := w.Root.Directory("subdir", time.Now()) - subsubdir := subdir.Directory("deep", time.Now()) - ff, err = subsubdir.File("yo", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil) - if err != nil { - return err - } - if _, err := ff.Write([]byte("foo\n")); err != nil { - return err - } - if err := ff.Close(); err != nil { - return err - } - if err := subsubdir.Flush(); err != nil { - return err - } +// subsubdir := subdir.Directory("deep", time.Now()) +// ff, err = subsubdir.File("yo", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil) +// if err != nil { +// return err +// } +// if _, err := ff.Write([]byte("foo\n")); err != nil { +// return err +// } +// if err := ff.Close(); err != nil { +// return err +// } +// if err := subsubdir.Flush(); err != nil { +// return err +// } - // TODO: write another file in subdir now, will result in invalid parent inode +// // TODO: write another file in subdir now, will result in invalid parent inode - ff, err = subdir.File("third file (in subdir)", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil) - if err != nil { - return err - } - if _, err := ff.Write([]byte("contents\n")); err != nil { - return err - } - if err := ff.Close(); err != nil { - return err - } +// ff, err = subdir.File("third file (in subdir)", time.Now(), unix.S_IRUSR|unix.S_IRGRP|unix.S_IROTH, nil) +// if err != nil { +// return err +// } +// if _, err := ff.Write([]byte("contents\n")); err != nil { +// return err +// } +// if err := ff.Close(); err != nil { +// return err +// } - if err := subdir.Flush(); err != nil { - return err - } - ff, err = w.Root.File("testbin", time.Now(), unix.S_IRUSR|unix.S_IXUSR| - unix.S_IRGRP|unix.S_IXGRP| - unix.S_IROTH|unix.S_IXOTH, - nil) - if err != nil { - return err - } - zf, err := os.Open(os.Args[0]) - if err != nil { - return err - } - defer zf.Close() - if _, err := io.Copy(ff, zf); err != nil { - return err - } - if err := ff.Close(); err != nil { - return err - } +// if err := subdir.Flush(); err != nil { +// return err +// } +// ff, err = w.Root.File("testbin", time.Now(), unix.S_IRUSR|unix.S_IXUSR| +// unix.S_IRGRP|unix.S_IXGRP| +// unix.S_IROTH|unix.S_IXOTH, +// nil) +// if err != nil { +// return err +// } +// zf, err := os.Open(os.Args[0]) +// if err != nil { +// return err +// } +// defer zf.Close() +// if _, err := io.Copy(ff, zf); err != nil { +// return err +// } +// if err := ff.Close(); err != nil { +// return err +// } - if err := w.Root.Flush(); err != nil { - return err - } - if err := w.Flush(); err != nil { - return err - } - return nil -} +// if err := w.Root.Flush(); err != nil { +// return err +// } +// if err := w.Flush(); err != nil { +// return err +// } +// return nil +// } -func TestUnsquashfs(t *testing.T) { - t.Parallel() +// func TestUnsquashfs(t *testing.T) { +// t.Parallel() - ctx, canc := distri.InterruptibleContext() - defer canc() +// ctx, canc := distri.InterruptibleContext() +// defer canc() - if _, err := exec.LookPath("unsquashfs"); err != nil { - t.Skip("unsquashfs not found in $PATH") - } +// if _, err := exec.LookPath("unsquashfs"); err != nil { +// t.Skip("unsquashfs not found in $PATH") +// } - for _, xattr := range []bool{false, true} { - t.Run(fmt.Sprintf("xattr %v", xattr), func(t *testing.T) { - var ( - f *os.File - err error - ) - if *fsImagePath != "" { - f, err = os.Create(*fsImagePath + fmt.Sprintf("-xattr-%v", xattr)) - } else { - f, err = ioutil.TempFile("", fmt.Sprintf("squashfs-xattr-%v", xattr)) - if err == nil { - defer os.Remove(f.Name()) - } - } - if err != nil { - t.Fatal(err) - } +// for _, xattr := range []bool{false, true} { +// t.Run(fmt.Sprintf("xattr %v", xattr), func(t *testing.T) { +// var ( +// f *os.File +// err error +// ) +// if *fsImagePath != "" { +// f, err = os.Create(*fsImagePath + fmt.Sprintf("-xattr-%v", xattr)) +// } else { +// f, err = ioutil.TempFile("", fmt.Sprintf("squashfs-xattr-%v", xattr)) +// if err == nil { +// defer os.Remove(f.Name()) +// } +// } +// if err != nil { +// t.Fatal(err) +// } - if err := writeTestImage(f, xattr); err != nil { - t.Fatal(err) - } +// if err := writeTestImage(f, xattr); err != nil { +// t.Fatal(err) +// } - if err := f.Close(); err != nil { - t.Fatal(err) - } +// if err := f.Close(); err != nil { +// t.Fatal(err) +// } - // Extract our generated file system using unsquashfs(1) - out, err := ioutil.TempDir("", fmt.Sprintf("unsquashfs-xattr-%v", xattr)) - if err != nil { - t.Fatal(err) - } - // defer distritest.RemoveAll(t, out) - cmd := exec.CommandContext(ctx, "unsquashfs", "-no-xattrs", "-d", filepath.Join(out, "x"), f.Name()) - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - t.Fatal(err) - } +// // Extract our generated file system using unsquashfs(1) +// out, err := ioutil.TempDir("", fmt.Sprintf("unsquashfs-xattr-%v", xattr)) +// if err != nil { +// t.Fatal(err) +// } +// // defer distritest.RemoveAll(t, out) +// cmd := exec.CommandContext(ctx, "unsquashfs", "-no-xattrs", "-d", filepath.Join(out, "x"), f.Name()) +// cmd.Stderr = os.Stderr +// if err := cmd.Run(); err != nil { +// t.Fatal(err) +// } - fbin, err := os.Open(os.Args[0]) - if err != nil { - t.Fatal(err) - } +// fbin, err := os.Open(os.Args[0]) +// if err != nil { +// t.Fatal(err) +// } - // Verify the extracted files match our expectations. - for _, entry := range []struct { - path string - contents io.Reader - }{ - {"leer", strings.NewReader("")}, - {"hellö wörld", strings.NewReader("hello world!")}, - {"testbin", fbin}, - {"subdir/third file (in subdir)", strings.NewReader("contents\n")}, - } { - entry := entry // copy - t.Run(entry.path, func(t *testing.T) { - in, err := os.Open(filepath.Join(out, "x", entry.path)) - if err != nil { - t.Fatal(err) - } - got, err := ioutil.ReadAll(in) - if err != nil { - t.Fatal(err) - } - want, err := ioutil.ReadAll(entry.contents) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(got, want) { - t.Fatalf("path %q differs", entry.path) - } - }) - } - }) - } -} +// // Verify the extracted files match our expectations. +// for _, entry := range []struct { +// path string +// contents io.Reader +// }{ +// {"leer", strings.NewReader("")}, +// {"hellö wörld", strings.NewReader("hello world!")}, +// {"testbin", fbin}, +// {"subdir/third file (in subdir)", strings.NewReader("contents\n")}, +// } { +// entry := entry // copy +// t.Run(entry.path, func(t *testing.T) { +// in, err := os.Open(filepath.Join(out, "x", entry.path)) +// if err != nil { +// t.Fatal(err) +// } +// got, err := ioutil.ReadAll(in) +// if err != nil { +// t.Fatal(err) +// } +// want, err := ioutil.ReadAll(entry.contents) +// if err != nil { +// t.Fatal(err) +// } +// if !bytes.Equal(got, want) { +// t.Fatalf("path %q differs", entry.path) +// } +// }) +// } +// }) +// } +// } -func TestReader(t *testing.T) { - t.Parallel() +// func TestReader(t *testing.T) { +// t.Parallel() - for _, xattr := range []bool{false, true} { - t.Run(fmt.Sprintf("xattr %v", xattr), func(t *testing.T) { - var err error - buf := &writerseeker.WriterSeeker{} - if err := writeTestImage(buf, xattr); err != nil { - t.Fatal(err) - } +// for _, xattr := range []bool{false, true} { +// t.Run(fmt.Sprintf("xattr %v", xattr), func(t *testing.T) { +// var err error +// buf := &writerseeker.WriterSeeker{} +// if err := writeTestImage(buf, xattr); err != nil { +// t.Fatal(err) +// } - if _, err := buf.Seek(0, io.SeekCurrent); err != nil { - t.Fatal(err) - } +// if _, err := buf.Seek(0, io.SeekCurrent); err != nil { +// t.Fatal(err) +// } - rd, err := NewReader(buf.BytesReader()) - if err != nil { - t.Fatal(err) - } +// rd, err := NewReader(buf.BytesReader()) +// if err != nil { +// t.Fatal(err) +// } - fbin, err := os.Open(os.Args[0]) - if err != nil { - t.Fatal(err) - } +// fbin, err := os.Open(os.Args[0]) +// if err != nil { +// t.Fatal(err) +// } - // Verify the extracted files match our expectations. - for _, entry := range []struct { - path string - contents io.Reader - }{ - {"leer", strings.NewReader("")}, - {"hellö wörld", strings.NewReader("hello world!")}, - {"testbin", fbin}, - {"subdir/third file (in subdir)", strings.NewReader("contents\n")}, - } { - entry := entry // copy - t.Run(entry.path, func(t *testing.T) { - // TODO: is this t.Parallel()-safe? - inode, err := rd.LookupPath(entry.path) - if err != nil { - t.Fatal(err) - } - in, err := rd.FileReader(inode) - if err != nil { - t.Fatal(err) - } - got, err := ioutil.ReadAll(in) - if err != nil { - t.Fatal(err) - } - want, err := ioutil.ReadAll(entry.contents) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(got, want) { - t.Fatalf("path %q differs", entry.path) - } - }) - } +// // Verify the extracted files match our expectations. +// for _, entry := range []struct { +// path string +// contents io.Reader +// }{ +// {"leer", strings.NewReader("")}, +// {"hellö wörld", strings.NewReader("hello world!")}, +// {"testbin", fbin}, +// {"subdir/third file (in subdir)", strings.NewReader("contents\n")}, +// } { +// entry := entry // copy +// t.Run(entry.path, func(t *testing.T) { +// // TODO: is this t.Parallel()-safe? +// inode, err := rd.LookupPath(entry.path) +// if err != nil { +// t.Fatal(err) +// } +// in, err := rd.FileReader(inode) +// if err != nil { +// t.Fatal(err) +// } +// got, err := ioutil.ReadAll(in) +// if err != nil { +// t.Fatal(err) +// } +// want, err := ioutil.ReadAll(entry.contents) +// if err != nil { +// t.Fatal(err) +// } +// if !bytes.Equal(got, want) { +// t.Fatalf("path %q differs", entry.path) +// } +// }) +// } - if xattr { - t.Run("xattrs", func(t *testing.T) { - inode, err := rd.LookupPath("hellö wörld") - if err != nil { - t.Fatal(err) - } +// if xattr { +// t.Run("xattrs", func(t *testing.T) { +// inode, err := rd.LookupPath("hellö wörld") +// if err != nil { +// t.Fatal(err) +// } - xattrs, err := rd.ReadXattrs(inode) - if err != nil { - t.Fatal(err) - } +// xattrs, err := rd.ReadXattrs(inode) +// if err != nil { +// t.Fatal(err) +// } - if got, want := len(xattrs), 1; got != want { - t.Fatalf("unexpected number of extended attributes: got %d, want %d", got, want) - } - wantXattr := Xattr{ - Type: 2, - FullName: "security.capability", - Value: []byte{1, 0, 0, 2, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - } - if diff := cmp.Diff(wantXattr, xattrs[0]); diff != "" { - t.Errorf("unexpected extended attribute: diff (-want +got):\n%s", diff) - } - }) - } - }) - } -} +// if got, want := len(xattrs), 1; got != want { +// t.Fatalf("unexpected number of extended attributes: got %d, want %d", got, want) +// } +// wantXattr := Xattr{ +// Type: 2, +// FullName: "security.capability", +// Value: []byte{1, 0, 0, 2, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, +// } +// if diff := cmp.Diff(wantXattr, xattrs[0]); diff != "" { +// t.Errorf("unexpected extended attribute: diff (-want +got):\n%s", diff) +// } +// }) +// } +// }) +// } +// }