コード例 #1
0
ファイル: genie.go プロジェクト: itchio/wharf
// ParseHeader is the first step of the genie's operation - it reads both
// containers, leaving the caller a chance to use them later, when parsing
// the contents
func (g *Genie) ParseHeader(patchReader io.Reader) error {
	rawPatchWire := wire.NewReadContext(patchReader)
	err := rawPatchWire.ExpectMagic(pwr.PatchMagic)
	if err != nil {
		return errors.Wrap(err, 1)
	}

	header := &pwr.PatchHeader{}
	err = rawPatchWire.ReadMessage(header)
	if err != nil {
		return errors.Wrap(err, 1)
	}

	patchWire, err := pwr.DecompressWire(rawPatchWire, header.Compression)
	if err != nil {
		return errors.Wrap(err, 1)
	}
	g.PatchWire = patchWire

	g.TargetContainer = &tlc.Container{}
	err = patchWire.ReadMessage(g.TargetContainer)
	if err != nil {
		return errors.Wrap(err, 1)
	}

	g.SourceContainer = &tlc.Container{}
	err = patchWire.ReadMessage(g.SourceContainer)
	if err != nil {
		return errors.Wrap(err, 1)
	}

	return nil
}
コード例 #2
0
ファイル: compression.go プロジェクト: itchio/wharf
// DecompressWire wraps a wire.ReadContext into a decompressor, according to the given settings,
// so that any messages read through the returned ReadContext will first be decompressed.
func DecompressWire(ctx *wire.ReadContext, compression *CompressionSettings) (*wire.ReadContext, error) {
	if compression == nil {
		return nil, errors.Wrap(fmt.Errorf("no compression specified"), 1)
	}

	if compression.Algorithm == CompressionAlgorithm_NONE {
		return ctx, nil
	}

	decompressor := decompressors[compression.Algorithm]
	if decompressor == nil {
		return nil, errors.Wrap(fmt.Errorf("no compressor registered for %s", compression.Algorithm.String()), 1)
	}

	compressedReader, err := decompressor.Apply(ctx.Reader())
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}

	return wire.NewReadContext(compressedReader), nil
}
コード例 #3
0
ファイル: compression_test.go プロジェクト: itchio/wharf
func Test_Compression(t *testing.T) {
	fc := &fakeCompressor{}
	RegisterCompressor(CompressionAlgorithm_GZIP, fc)

	fd := &fakeDecompressor{}
	RegisterDecompressor(CompressionAlgorithm_GZIP, fd)

	assert.EqualValues(t, false, fc.called)

	buf := new(bytes.Buffer)
	wc := wire.NewWriteContext(buf)
	_, err := CompressWire(wc, &CompressionSettings{
		Algorithm: CompressionAlgorithm_BROTLI,
		Quality:   3,
	})
	assert.NotNil(t, err)

	cwc, err := CompressWire(wc, &CompressionSettings{
		Algorithm: CompressionAlgorithm_GZIP,
		Quality:   3,
	})
	assert.NoError(t, err)

	assert.EqualValues(t, true, fc.called)
	assert.EqualValues(t, 3, fc.quality)

	assert.NoError(t, cwc.WriteMessage(&SyncHeader{
		FileIndex: 672,
	}))

	rc := wire.NewReadContext(bytes.NewReader(buf.Bytes()))

	sh := &SyncHeader{}
	assert.NoError(t, rc.ReadMessage(sh))

	assert.EqualValues(t, 672, sh.FileIndex)
	assert.NotNil(t, rc.ReadMessage(sh))
}
コード例 #4
0
ファイル: probe.go プロジェクト: itchio/butler
func doProbe(patch string) error {
	patchReader, err := eos.Open(patch)
	if err != nil {
		return err
	}

	defer patchReader.Close()

	stats, err := patchReader.Stat()
	if err != nil {
		return err
	}

	comm.Statf("patch:  %s", humanize.IBytes(uint64(stats.Size())))

	rctx := wire.NewReadContext(patchReader)
	err = rctx.ExpectMagic(pwr.PatchMagic)
	if err != nil {
		return err
	}

	header := &pwr.PatchHeader{}
	err = rctx.ReadMessage(header)
	if err != nil {
		return err
	}

	rctx, err = pwr.DecompressWire(rctx, header.Compression)
	if err != nil {
		return err
	}

	target := &tlc.Container{}
	err = rctx.ReadMessage(target)
	if err != nil {
		return err
	}

	source := &tlc.Container{}
	err = rctx.ReadMessage(source)
	if err != nil {
		return err
	}

	comm.Statf("target: %s in %s", humanize.IBytes(uint64(target.Size)), target.Stats())
	comm.Statf("source: %s in %s", humanize.IBytes(uint64(target.Size)), source.Stats())

	var patchStats []patchStat

	sh := &pwr.SyncHeader{}
	rop := &pwr.SyncOp{}

	for fileIndex, f := range source.Files {
		stat := patchStat{
			fileIndex: int64(fileIndex),
			freshData: f.Size,
		}

		sh.Reset()
		err = rctx.ReadMessage(sh)
		if err != nil {
			return err
		}

		if sh.FileIndex != int64(fileIndex) {
			return fmt.Errorf("malformed patch: expected file %d, got %d", fileIndex, sh.FileIndex)
		}

		readingOps := true

		var pos int64

		for readingOps {
			rop.Reset()

			err = rctx.ReadMessage(rop)
			if err != nil {
				return err
			}

			switch rop.Type {
			case pwr.SyncOp_BLOCK_RANGE:
				fixedSize := (rop.BlockSpan - 1) * pwr.BlockSize
				lastIndex := rop.BlockIndex + (rop.BlockSpan - 1)
				lastSize := pwr.ComputeBlockSize(f.Size, lastIndex)
				totalSize := (fixedSize + lastSize)
				stat.freshData -= totalSize
				pos += totalSize
			case pwr.SyncOp_DATA:
				totalSize := int64(len(rop.Data))
				if *appArgs.verbose {
					comm.Debugf("%s fresh data at %s (%d-%d)", humanize.IBytes(uint64(totalSize)), humanize.IBytes(uint64(pos)),
						pos, pos+totalSize)
				}
				pos += totalSize
			case pwr.SyncOp_HEY_YOU_DID_IT:
				readingOps = false
			}
		}

		patchStats = append(patchStats, stat)
	}

	sort.Sort(byDecreasingFreshData(patchStats))

	var totalFresh int64
	for _, stat := range patchStats {
		totalFresh += stat.freshData
	}

	var eightyFresh = int64(0.8 * float64(totalFresh))
	var printedFresh int64

	comm.Opf("80%% of fresh data is in the following files:")

	for _, stat := range patchStats {
		f := source.Files[stat.fileIndex]
		comm.Logf("%s in %s (%.2f%% changed)",
			humanize.IBytes(uint64(stat.freshData)),
			f.Path,
			float64(stat.freshData)/float64(f.Size)*100.0)

		printedFresh += stat.freshData
		if printedFresh >= eightyFresh {
			break
		}
	}

	return nil
}
コード例 #5
0
ファイル: heal.go プロジェクト: itchio/butler
func doHeal(dir string, woundsPath string, spec string) error {
	reader, err := os.Open(woundsPath)
	if err != nil {
		return err
	}
	defer reader.Close()

	healer, err := pwr.NewHealer(spec, dir)
	if err != nil {
		return err
	}

	consumer := comm.NewStateConsumer()

	healer.SetConsumer(consumer)

	rctx := wire.NewReadContext(reader)

	err = rctx.ExpectMagic(pwr.WoundsMagic)
	if err != nil {
		return err
	}

	wh := &pwr.WoundsHeader{}
	err = rctx.ReadMessage(wh)
	if err != nil {
		return err
	}

	container := &tlc.Container{}
	err = rctx.ReadMessage(container)
	if err != nil {
		return err
	}

	wounds := make(chan *pwr.Wound)
	errs := make(chan error)

	comm.StartProgress()

	go func() {
		errs <- healer.Do(container, wounds)
	}()

	wound := &pwr.Wound{}
	for {
		wound.Reset()
		err = rctx.ReadMessage(wound)
		if err != nil {
			if err == io.EOF {
				// all good
				break
			}
		}

		select {
		case wounds <- wound:
			// all good
		case healErr := <-errs:
			return healErr
		}
	}

	close(wounds)
	healErr := <-errs

	comm.EndProgress()

	if healErr != nil {
		return healErr
	}

	comm.Opf("All healed!")
	return nil
}
コード例 #6
0
ファイル: sign.go プロジェクト: itchio/wharf
// ReadSignature reads the hashes from all files of a given container, from a
// wharf signature file.
func ReadSignature(signatureReader io.Reader) (*SignatureInfo, error) {
	rawSigWire := wire.NewReadContext(signatureReader)
	err := rawSigWire.ExpectMagic(SignatureMagic)
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}

	header := &SignatureHeader{}
	err = rawSigWire.ReadMessage(header)
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}

	sigWire, err := DecompressWire(rawSigWire, header.Compression)
	if err != nil {
		return nil, errors.Wrap(err, 1)
	}

	container := &tlc.Container{}
	err = sigWire.ReadMessage(container)
	if err != nil {
		if errors.Is(err, io.EOF) {
			// ok
		} else {
			return nil, errors.Wrap(err, 1)
		}
	}

	var hashes []wsync.BlockHash
	hash := &BlockHash{}

	for fileIndex, f := range container.Files {
		numBlocks := ComputeNumBlocks(f.Size)
		if numBlocks == 0 {
			hash.Reset()
			err = sigWire.ReadMessage(hash)

			if err != nil {
				if errors.Is(err, io.EOF) {
					break
				}
				return nil, errors.Wrap(err, 1)
			}

			// empty files have a 0-length shortblock for historical reasons.
			blockHash := wsync.BlockHash{
				FileIndex:  int64(fileIndex),
				BlockIndex: 0,

				WeakHash:   hash.WeakHash,
				StrongHash: hash.StrongHash,

				ShortSize: 0,
			}
			hashes = append(hashes, blockHash)
		}

		for blockIndex := int64(0); blockIndex < numBlocks; blockIndex++ {
			hash.Reset()
			err = sigWire.ReadMessage(hash)

			if err != nil {
				if errors.Is(err, io.EOF) {
					break
				}
				return nil, errors.Wrap(err, 1)
			}

			// full blocks have a shortSize of 0, for more compact storage
			shortSize := int32(0)
			if (blockIndex+1)*BlockSize > f.Size {
				shortSize = int32(f.Size % BlockSize)
			}

			blockHash := wsync.BlockHash{
				FileIndex:  int64(fileIndex),
				BlockIndex: blockIndex,

				WeakHash:   hash.WeakHash,
				StrongHash: hash.StrongHash,

				ShortSize: shortSize,
			}
			hashes = append(hashes, blockHash)
		}
	}

	signature := &SignatureInfo{
		Container: container,
		Hashes:    hashes,
	}
	return signature, nil
}
コード例 #7
0
ファイル: bspatch.go プロジェクト: itchio/butler
func doBspatch(patch string, target string, output string) error {
	targetReader, err := os.Open(target)
	if err != nil {
		return err
	}

	defer targetReader.Close()

	patchReader, err := os.Open(patch)
	if err != nil {
		return err
	}

	defer patchReader.Close()

	err = os.MkdirAll(filepath.Dir(output), 0755)
	if err != nil {
		return err
	}

	outputWriter, err := os.Create(output)
	if err != nil {
		return err
	}

	defer outputWriter.Close()

	rctx := wire.NewReadContext(patchReader)

	err = rctx.ExpectMagic(pwr.PatchMagic)
	if err != nil {
		return err
	}

	ph := &pwr.PatchHeader{}

	err = rctx.ReadMessage(ph)
	if err != nil {
		return err
	}

	compression := ph.GetCompression()

	rctx, err = pwr.DecompressWire(rctx, compression)
	if err != nil {
		return err
	}

	targetContainer := &tlc.Container{}
	err = rctx.ReadMessage(targetContainer)
	if err != nil {
		return err
	}

	sourceContainer := &tlc.Container{}
	err = rctx.ReadMessage(sourceContainer)
	if err != nil {
		return err
	}

	if len(targetContainer.Files) != 1 {
		return fmt.Errorf("expected only one file in target container")
	}

	if len(sourceContainer.Files) != 1 {
		return fmt.Errorf("expected only one file in source container")
	}

	sh := &pwr.SyncHeader{}
	err = rctx.ReadMessage(sh)
	if err != nil {
		return err
	}

	if sh.FileIndex != 0 {
		return fmt.Errorf("wrong sync header")
	}

	op := &pwr.SyncOp{}
	err = rctx.ReadMessage(op)
	if err != nil {
		return err
	}

	if op.Type != pwr.SyncOp_BSDIFF {
		return fmt.Errorf("expected bsdiff syncop")
	}

	if op.FileIndex != 0 {
		return fmt.Errorf("expected bsdiff syncop to operate on only file")
	}

	outputSize := sourceContainer.Files[op.FileIndex].Size

	err = bsdiff.Patch(targetReader, outputWriter, outputSize, rctx.ReadMessage)
	if err != nil {
		return err
	}

	return nil
}
コード例 #8
0
ファイル: apply.go プロジェクト: itchio/butler
// ApplyPatch reads a patch, parses it, and generates the new file tree
func (actx *ApplyContext) ApplyPatch(patchReader io.Reader) error {
	actx.actualOutputPath = actx.OutputPath
	if actx.OutputPool == nil {
		if actx.InPlace {
			// applying in-place is a bit tricky: we can't overwrite files in the
			// target directory (old) while we're reading the patch otherwise
			// we might be copying new bytes instead of old bytes into later files
			// so, we rebuild 'touched' files in a staging area
			stagePath := actx.actualOutputPath + "-stage"
			err := os.MkdirAll(stagePath, os.FileMode(0755))
			if err != nil {
				return errors.Wrap(err, 1)
			}

			defer os.RemoveAll(stagePath)
			actx.OutputPath = stagePath
		} else {
			os.MkdirAll(actx.OutputPath, os.FileMode(0755))
		}
	} else {
		if actx.actualOutputPath != "" {
			return fmt.Errorf("cannot specify both OutputPath and OutputPool")
		}
	}

	rawPatchWire := wire.NewReadContext(patchReader)
	err := rawPatchWire.ExpectMagic(PatchMagic)
	if err != nil {
		return errors.Wrap(err, 1)
	}

	header := &PatchHeader{}
	err = rawPatchWire.ReadMessage(header)
	if err != nil {
		return errors.Wrap(err, 1)
	}

	patchWire, err := DecompressWire(rawPatchWire, header.Compression)
	if err != nil {
		return errors.Wrap(err, 1)
	}

	targetContainer := &tlc.Container{}
	err = patchWire.ReadMessage(targetContainer)
	if err != nil {
		return errors.Wrap(err, 1)
	}
	actx.TargetContainer = targetContainer

	sourceContainer := &tlc.Container{}
	err = patchWire.ReadMessage(sourceContainer)
	if err != nil {
		return errors.Wrap(err, 1)
	}
	actx.SourceContainer = sourceContainer

	if actx.VetApply != nil {
		err = actx.VetApply(actx)
		if err != nil {
			return errors.Wrap(err, 1)
		}
	}

	var ghosts []Ghost

	// when not working with a custom output pool
	if actx.OutputPool == nil {
		if actx.InPlace {
			// when working in-place, we have to keep track of which files were deleted
			// from one version to the other, so that we too may delete them in the end.
			ghosts = detectGhosts(actx.SourceContainer, actx.TargetContainer)
		} else {
			// when rebuilding in a fresh directory, there's no need to worry about
			// deleted files, because they won't even exist in the first place.
			err = sourceContainer.Prepare(actx.OutputPath)
			if err != nil {
				return errors.Wrap(err, 1)
			}
		}
	}

	err = actx.patchAll(patchWire, actx.Signature)
	if err != nil {
		return errors.Wrap(err, 1)
	}

	if actx.InPlace {
		err = actx.ensureDirsAndSymlinks(actx.actualOutputPath)
		if err != nil {
			return errors.Wrap(err, 1)
		}

		actx.Stats.StageSize, err = actx.mergeFolders(actx.actualOutputPath, actx.OutputPath)
		if err != nil {
			return errors.Wrap(err, 1)
		}

		err = actx.deleteGhosts(actx.actualOutputPath, ghosts)
		if err != nil {
			return errors.Wrap(err, 1)
		}
		actx.OutputPath = actx.actualOutputPath
	}

	return nil
}
コード例 #9
0
ファイル: manifest.go プロジェクト: itchio/wharf
// ReadManifest reads container info and block addresses from a wharf manifest file.
// Does not close manifestReader.
func ReadManifest(manifestReader io.Reader) (*tlc.Container, *BlockHashMap, error) {
	container := &tlc.Container{}
	blockHashes := NewBlockHashMap()

	rawWire := wire.NewReadContext(manifestReader)
	err := rawWire.ExpectMagic(pwr.ManifestMagic)
	if err != nil {
		return nil, nil, errors.Wrap(err, 1)
	}

	mh := &pwr.ManifestHeader{}
	err = rawWire.ReadMessage(mh)
	if err != nil {
		return nil, nil, errors.Wrap(err, 1)
	}

	if mh.Algorithm != pwr.HashAlgorithm_SHAKE128_32 {
		err = fmt.Errorf("Manifest has unsupported hash algorithm %d, expected %d", mh.Algorithm, pwr.HashAlgorithm_SHAKE128_32)
		return nil, nil, errors.Wrap(err, 1)
	}

	wire, err := pwr.DecompressWire(rawWire, mh.GetCompression())
	if err != nil {
		return nil, nil, errors.Wrap(err, 1)
	}

	err = wire.ReadMessage(container)
	if err != nil {
		return nil, nil, errors.Wrap(err, 1)
	}

	sh := &pwr.SyncHeader{}
	mbh := &pwr.ManifestBlockHash{}

	for fileIndex, f := range container.Files {
		sh.Reset()
		err = wire.ReadMessage(sh)
		if err != nil {
			return nil, nil, errors.Wrap(err, 1)
		}

		if int64(fileIndex) != sh.FileIndex {
			err = fmt.Errorf("manifest format error: expected file %d, got %d", fileIndex, sh.FileIndex)
			return nil, nil, errors.Wrap(err, 1)
		}

		numBlocks := ComputeNumBlocks(f.Size)
		for blockIndex := int64(0); blockIndex < numBlocks; blockIndex++ {
			mbh.Reset()
			err = wire.ReadMessage(mbh)
			if err != nil {
				return nil, nil, errors.Wrap(err, 1)
			}

			loc := BlockLocation{FileIndex: int64(fileIndex), BlockIndex: blockIndex}
			blockHashes.Set(loc, append([]byte{}, mbh.Hash...))
		}
	}

	return container, blockHashes, nil
}
コード例 #10
0
ファイル: meta_ops.go プロジェクト: itchio/butler
func file(path string) {
	reader, err := eos.Open(path)
	must(err)

	defer reader.Close()

	stats, err := reader.Stat()
	if os.IsNotExist(err) {
		comm.Dief("%s: no such file or directory", path)
	}
	must(err)

	if stats.IsDir() {
		comm.Logf("%s: directory", path)
		return
	}

	if stats.Size() == 0 {
		comm.Logf("%s: empty file. peaceful.", path)
		return
	}

	prettySize := humanize.IBytes(uint64(stats.Size()))

	var magic int32
	must(binary.Read(reader, wire.Endianness, &magic))

	switch magic {
	case pwr.PatchMagic:
		{
			ph := &pwr.PatchHeader{}
			rctx := wire.NewReadContext(reader)
			must(rctx.ReadMessage(ph))

			rctx, err = pwr.DecompressWire(rctx, ph.GetCompression())
			must(err)
			container := &tlc.Container{}
			must(rctx.ReadMessage(container)) // target container
			container.Reset()
			must(rctx.ReadMessage(container)) // source container

			comm.Logf("%s: %s wharf patch file (%s) with %s", path, prettySize, ph.GetCompression().ToString(), container.Stats())
			comm.Result(ContainerResult{
				Type:             "wharf/patch",
				NumFiles:         len(container.Files),
				NumDirs:          len(container.Dirs),
				NumSymlinks:      len(container.Symlinks),
				UncompressedSize: container.Size,
			})
		}

	case pwr.SignatureMagic:
		{
			sh := &pwr.SignatureHeader{}
			rctx := wire.NewReadContext(reader)
			must(rctx.ReadMessage(sh))

			rctx, err = pwr.DecompressWire(rctx, sh.GetCompression())
			must(err)
			container := &tlc.Container{}
			must(rctx.ReadMessage(container))

			comm.Logf("%s: %s wharf signature file (%s) with %s", path, prettySize, sh.GetCompression().ToString(), container.Stats())
			comm.Result(ContainerResult{
				Type:             "wharf/signature",
				NumFiles:         len(container.Files),
				NumDirs:          len(container.Dirs),
				NumSymlinks:      len(container.Symlinks),
				UncompressedSize: container.Size,
			})
		}

	case pwr.ManifestMagic:
		{
			mh := &pwr.ManifestHeader{}
			rctx := wire.NewReadContext(reader)
			must(rctx.ReadMessage(mh))

			rctx, err = pwr.DecompressWire(rctx, mh.GetCompression())
			must(err)
			container := &tlc.Container{}
			must(rctx.ReadMessage(container))

			comm.Logf("%s: %s wharf manifest file (%s) with %s", path, prettySize, mh.GetCompression().ToString(), container.Stats())
			comm.Result(ContainerResult{
				Type:             "wharf/manifest",
				NumFiles:         len(container.Files),
				NumDirs:          len(container.Dirs),
				NumSymlinks:      len(container.Symlinks),
				UncompressedSize: container.Size,
			})
		}

	case pwr.WoundsMagic:
		{
			wh := &pwr.WoundsHeader{}
			rctx := wire.NewReadContext(reader)
			must(rctx.ReadMessage(wh))

			container := &tlc.Container{}
			must(rctx.ReadMessage(container))

			files := make(map[int64]bool)
			totalWounds := int64(0)

			for {
				wound := &pwr.Wound{}

				err = rctx.ReadMessage(wound)
				if err != nil {
					if errors.Is(err, io.EOF) {
						break
					} else {
						must(err)
					}
				}

				if wound.Kind == pwr.WoundKind_FILE {
					totalWounds += (wound.End - wound.Start)
					files[wound.Index] = true
				}
			}

			comm.Logf("%s: %s wharf wounds file with %s, %s wounds in %d files", path, prettySize, container.Stats(),
				humanize.IBytes(uint64(totalWounds)), len(files))
			comm.Result(ContainerResult{
				Type: "wharf/wounds",
			})
		}

	default:
		_, err := reader.Seek(0, os.SEEK_SET)
		must(err)

		wasZip := func() bool {
			zr, err := zip.NewReader(reader, stats.Size())
			if err != nil {
				if err != zip.ErrFormat {
					must(err)
				}
				return false
			}

			container, err := tlc.WalkZip(zr, func(fi os.FileInfo) bool { return true })
			must(err)

			comm.Logf("%s: %s zip file with %s", path, prettySize, container.Stats())
			comm.Result(ContainerResult{
				Type:             "zip",
				NumFiles:         len(container.Files),
				NumDirs:          len(container.Dirs),
				NumSymlinks:      len(container.Symlinks),
				UncompressedSize: container.Size,
			})
			return true
		}()

		if !wasZip {
			comm.Logf("%s: not sure - try the file(1) command if your system has it!", path)
		}
	}
}
コード例 #11
0
ファイル: meta_ops.go プロジェクト: itchio/butler
func ls(path string) {
	reader, err := eos.Open(path)
	must(err)

	defer reader.Close()

	stats, err := reader.Stat()
	if os.IsNotExist(err) {
		comm.Dief("%s: no such file or directory", path)
	}
	must(err)

	if stats.IsDir() {
		comm.Logf("%s: directory", path)
		return
	}

	if stats.Size() == 0 {
		comm.Logf("%s: empty file. peaceful.", path)
		return
	}

	log := func(line string) {
		comm.Logf(line)
	}

	var magic int32
	must(binary.Read(reader, wire.Endianness, &magic))

	switch magic {
	case pwr.PatchMagic:
		{
			h := &pwr.PatchHeader{}
			rctx := wire.NewReadContext(reader)
			must(rctx.ReadMessage(h))

			rctx, err = pwr.DecompressWire(rctx, h.GetCompression())
			must(err)
			container := &tlc.Container{}
			must(rctx.ReadMessage(container))
			log("pre-patch container:")
			container.Print(log)

			container.Reset()
			must(rctx.ReadMessage(container))
			log("================================")
			log("post-patch container:")
			container.Print(log)
		}

	case pwr.SignatureMagic:
		{
			h := &pwr.SignatureHeader{}
			rctx := wire.NewReadContext(reader)
			must(rctx.ReadMessage(h))

			rctx, err = pwr.DecompressWire(rctx, h.GetCompression())
			must(err)
			container := &tlc.Container{}
			must(rctx.ReadMessage(container))
			container.Print(log)
		}

	case pwr.ManifestMagic:
		{
			h := &pwr.ManifestHeader{}
			rctx := wire.NewReadContext(reader)
			must(rctx.ReadMessage(h))

			rctx, err = pwr.DecompressWire(rctx, h.GetCompression())
			must(err)
			container := &tlc.Container{}
			must(rctx.ReadMessage(container))
			container.Print(log)
		}

	case pwr.WoundsMagic:
		{
			wh := &pwr.WoundsHeader{}
			rctx := wire.NewReadContext(reader)
			must(rctx.ReadMessage(wh))

			container := &tlc.Container{}
			must(rctx.ReadMessage(container))
			container.Print(log)

			for {
				wound := &pwr.Wound{}
				err = rctx.ReadMessage(wound)
				if err != nil {
					if errors.Is(err, io.EOF) {
						break
					} else {
						must(err)
					}
				}
				comm.Logf(wound.PrettyString(container))
			}
		}

	default:
		_, err := reader.Seek(0, os.SEEK_SET)
		must(err)

		wasZip := func() bool {
			zr, err := zip.NewReader(reader, stats.Size())
			if err != nil {
				if err != zip.ErrFormat {
					must(err)
				}
				return false
			}

			container, err := tlc.WalkZip(zr, func(fi os.FileInfo) bool { return true })
			must(err)
			container.Print(log)
			return true
		}()

		if !wasZip {
			comm.Logf("%s: not sure - try the file(1) command if your system has it!", path)
		}
	}
}