func (s *State) generateMirrors(url string) (mirrors []string) {
	for _, pam := range s.autoMirrors {
		matches := pam.re.FindStringSubmatch(url)
		if matches == nil {
			continue
		}
		mirror := os.Expand(pam.mirror, func(key string) string {
			return matches[pam.groups[key]]
		})
		mirrors = append(mirrors, mirror)
	}
	return
}
Return the generated mirrors that match URL.
package state
import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"time"
	"github.com/pkg/errors"
	"github.com/cashapp/hermit/archive"
	"github.com/cashapp/hermit/cache"
	"github.com/cashapp/hermit/github"
	"github.com/cashapp/hermit/internal/dao"
	"github.com/cashapp/hermit/manifest"
	"github.com/cashapp/hermit/sources"
	"github.com/cashapp/hermit/ui"
	"github.com/cashapp/hermit/util"
	"github.com/cashapp/hermit/vfs"
)
var DefaultSources = []string{"https://github.com/cashapp/hermit-packages.git"}
type precompiledAutoMirror struct {
	re     *regexp.Regexp
	groups map[string]int
	mirror string
}
type AutoMirror struct {
	Origin string
	Mirror string
}
type Config struct {
	Sources []string
	AutoMirrors []AutoMirror
	Builtin *sources.BuiltInSource
}
type State struct {
	root        string  
	cacheDir    string  
	pkgDir      string  
	sourcesDir  string  
	binaryDir   string  
	config      Config
	autoMirrors []precompiledAutoMirror
	cache       *cache.Cache
	dao         *dao.DAO
	lock        *util.FileLock
}
func Open(stateDir string, config Config, ghClient *github.Client, client *http.Client, fastFailClient *http.Client) (*State, error) {
	if config.Builtin == nil {
		return nil, errors.Errorf("state.Config.Builtin not provided")
	}
	pkgDir := filepath.Join(stateDir, "pkg")
	cacheDir := filepath.Join(stateDir, "cache")
	sourcesDir := filepath.Join(stateDir, "sources")
	binaryDir := filepath.Join(stateDir, "binaries")
	cache, err := cache.Open(cacheDir, ghClient, client, fastFailClient)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	dao := dao.Open(stateDir)
	if config.Sources == nil {
		config.Sources = DefaultSources
	}
	autoMirrors, err := validateAndCompileAutoMirrors(config)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	s := &State{
		dao:         dao,
		autoMirrors: autoMirrors,
		root:        stateDir,
		cacheDir:    cacheDir,
		sourcesDir:  sourcesDir,
		binaryDir:   binaryDir,
		config:      config,
		pkgDir:      pkgDir,
		cache:       cache,
		lock:        util.NewLock(filepath.Join(stateDir, ".lock"), 1*time.Second),
	}
	return s, nil
}
func validateAndCompileAutoMirrors(config Config) ([]precompiledAutoMirror, error) {
	autoMirrors := []precompiledAutoMirror{}
	for _, mirror := range config.AutoMirrors {
		re, err := regexp.Compile(mirror.Origin)
		if err != nil {
			return nil, errors.Errorf("auto-mirror key %q is not a valid regular expression", mirror.Origin)
		}
		pam := precompiledAutoMirror{
			re:     re,
			mirror: mirror.Mirror,
			groups: map[string]int{},
		}
		for id, name := range re.SubexpNames() {
			pam.groups[name] = id
		}
		os.Expand(mirror.Mirror, func(name string) string {
			_, ok := pam.groups[name]
			if !ok {
				err = errors.Errorf("unknown capture group %q in auto-mirror", name)
			}
			return ""
		})
		if err != nil {
			return nil, errors.WithStack(err)
		}
		autoMirrors = append(autoMirrors, pam)
	}
	return autoMirrors, nil
}
func (s *State) Resolve(l *ui.UI, mathcer manifest.Selector) (*manifest.Package, error) {
	resolver, err := s.resolver(l)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	return resolver.Resolve(l, mathcer)
}
func (s *State) Search(l *ui.UI, glob string) (manifest.Packages, error) {
	resolver, err := s.resolver(l)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	pkgs, err := resolver.Search(l, glob)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	return pkgs, nil
}
func (s *State) DumpDB(w io.Writer) error {
	return s.dao.Dump(w)
}
func (s *State) Config() Config {
	return s.config
}
func (s *State) SourcesDir() string {
	return s.sourcesDir
}
func (s *State) Root() string {
	return s.root
}
func (s *State) PkgDir() string {
	return s.pkgDir
}
func (s *State) BinaryDir() string {
	return s.binaryDir
}
func (s *State) resolver(l *ui.UI) (*manifest.Resolver, error) {
	ss, err := s.Sources(l)
	if err != nil {
		return nil, err
	}
	return manifest.New(ss, manifest.Config{
		State: s.Root(),
		OS:    runtime.GOOS,
		Arch:  runtime.GOARCH,
	})
}
func (s *State) Sources(l *ui.UI) (*sources.Sources, error) {
	ss, err := sources.ForURIs(l, s.SourcesDir(), "", s.config.Sources)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	ss.Prepend(s.config.Builtin)
	return ss, nil
}
func (s *State) acquireLock(log ui.Logger) (*util.FileLock, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	err := s.lock.Acquire(ctx, log)
	cancel()
	if err != nil {
		return nil, fmt.Errorf("failed to acquire lock: %w", err)
	}
	return s.lock, nil
}
func (s *State) ReadPackageState(pkg *manifest.Package) {
	if _, err := os.Stat(pkg.Root); err == nil {
		pkg.State = manifest.PackageStateInstalled
	} else if s.cache.IsCached(pkg.SHA256, pkg.Source) {
		pkg.State = manifest.PackageStateDownloaded
	}
	dbInfo, _ := s.dao.GetPackage(pkg.String())
	if dbInfo == nil {
		dbInfo = &dao.Package{}
	}
	pkg.LastUsed = dbInfo.UsedAt
	pkg.ETag = dbInfo.Etag
	pkg.UpdatedAt = dbInfo.UpdateCheckedAt
}
func (s *State) WritePackageState(p *manifest.Package, binDir string) error {
	var updatedAt = time.Time{}
	if p.UpdateInterval > 0 {
		updatedAt = p.UpdatedAt
	}
	pkg := &dao.Package{
		UsedAt:          time.Now(),
		Etag:            p.ETag,
		UpdateCheckedAt: updatedAt,
	}
	return s.dao.UpdatePackageWithUsage(binDir, p.Reference.String(), pkg)
}
func (s *State) removeRecursive(b *ui.Task, dest string) error {
	_, err := os.Stat(dest)
	if errors.Is(err, os.ErrNotExist) {
		return nil
	}
	if err != nil {
		return errors.WithStack(err)
	}
	task := b.SubTask("remove")
	lock, err := s.acquireLock(b)
	if err != nil {
		return errors.WithStack(err)
	}
	defer lock.Release(b)
	task.Debugf("chmod -R +w %s", dest)
	_ = filepath.Walk(dest, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return errors.WithStack(err)
		}
		err = os.Chmod(path, info.Mode()|0200)
		if errors.Is(err, os.ErrNotExist) {
			task.Debugf("file did not exist during removal %q", path)
			return nil
		}
		return errors.WithStack(err)
	})
	task.Debugf("rm -rf %s", dest)
	return errors.WithStack(os.RemoveAll(dest))
}
func (s *State) CacheAndUnpack(b *ui.Task, p *manifest.Package) error {
	if (s.isExtracted(p) && s.areBinariesLinked(p)) || p.Source == "/" {
		return nil
	}
	lock, err := s.acquireLock(b)
	if err != nil {
		return errors.WithStack(err)
	}
	defer lock.Release(b)
	if !s.isExtracted(p) {
		if err := s.extract(b, p); err != nil {
			return errors.WithStack(err)
		}
	}
	if !s.areBinariesLinked(p) {
		if err := s.linkBinaries(p); err != nil {
			return errors.WithStack(err)
		}
	}
	return nil
}
func (s *State) linkBinaries(p *manifest.Package) error {
	dir := filepath.Join(s.binaryDir, p.Reference.String())
	if err := os.MkdirAll(dir, 0700); err != nil {
		return errors.WithStack(err)
	}
	bins, err := p.ResolveBinaries()
	if err != nil {
		return errors.WithStack(err)
	}
	for _, bin := range bins {
		to := filepath.Join(dir, filepath.Base(bin))
		if err := os.Symlink(bin, to); err != nil {
			return errors.WithStack(err)
		}
	}
	return nil
}
func (s *State) extract(b *ui.Task, p *manifest.Package) error {
	var (
		path string
		etag string
		err  error
	)
	if !s.isCached(p) {
		mirrors := append(p.Mirrors, s.generateMirrors(p.Source)...)
		path, etag, err = s.cache.Download(b, p.SHA256, p.Source, mirrors...)
		p.ETag = etag
		if err != nil {
			return errors.WithStack(err)
		}
	} else {
		path = s.cache.Path(p.SHA256, p.Source)
	}
	finalise, err := archive.Extract(b, path, p)
	if err != nil {
		return errors.WithStack(err)
	}
	for _, file := range p.Files {
		err = vfs.CopyFile(file.FS, file.FromPath, file.ToPAth)
		if err != nil {
			return errors.WithStack(err)
		}
	}
	if _, err = p.Trigger(b, manifest.EventUnpack); err != nil {
		_ = os.RemoveAll(p.Dest)
		return errors.WithStack(err)
	}
	return errors.WithStack(finalise())
}
func (s *State) isCached(p *manifest.Package) bool {
	return s.cache.IsCached(p.SHA256, p.Source)
}
func (s *State) isExtracted(p *manifest.Package) bool {
	_, err := os.Stat(p.Root)
	return err == nil
}
func (s *State) areBinariesLinked(p *manifest.Package) bool {
	_, err := os.Stat(filepath.Join(s.binaryDir, p.Reference.String()))
	return err == nil
}
func (s *State) RecordUninstall(pkg *manifest.Package, binDir string) error {
	err := s.dao.PackageRemovedAt(pkg.Reference.String(), binDir)
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}
func (s *State) CleanPackages(b *ui.UI) error {
	lock, err := s.acquireLock(b)
	if err != nil {
		return err
	}
	defer lock.Release(b)
	bins, err := os.ReadDir(s.binaryDir)
	if err != nil {
		return errors.WithStack(err)
	}
	for _, entry := range bins {
		path := filepath.Join(s.binaryDir, entry.Name())
		if err = s.removeRecursive(b.Task(entry.Name()), path); err != nil {
			return errors.WithStack(err)
		}
	}
	entries, err := os.ReadDir(s.pkgDir)
	if err != nil {
		return errors.WithStack(err)
	}
	for _, entry := range entries {
		if strings.HasPrefix(entry.Name(), "hermit@") {
			continue
		}
		path := filepath.Join(s.pkgDir, entry.Name())
		if err = s.removeRecursive(b.Task(entry.Name()), path); err != nil {
			return errors.WithStack(err)
		}
	}
	return nil
}
func (s *State) CleanCache(b ui.Logger) error {
	lock, err := s.acquireLock(b)
	if err != nil {
		return err
	}
	defer lock.Release(b)
	b.Debugf("rm -rf %q", s.cacheDir)
	return os.RemoveAll(s.cacheDir)
}
func (s *State) UpgradeChannel(b *ui.Task, pkg *manifest.Package) error {
	if !pkg.Reference.IsChannel() {
		panic("UpgradeChannel can only be used with channel packages")
	}
	name := pkg.Reference.String()
	mirrors := append(pkg.Mirrors, s.generateMirrors(pkg.Source)...)
	etag, err := s.cache.ETag(b, pkg.Source, mirrors...)
	if err != nil {
		b.Warnf("Could not check updates for %s. Skipping update. Error: %s", name, err)
	} else if etag == "" {
		b.Warnf("No ETag found for %s. Skipping update.", name)
	} else if etag != pkg.ETag {
		b.Infof("Fetching a new version for %s", name)
		if err := s.evictPackage(b, pkg); err != nil {
			return errors.WithStack(err)
		}
		if err := s.CacheAndUnpack(b, pkg); err != nil {
			return errors.WithStack(err)
		}
		etag = pkg.ETag
	} else {
		b.Infof("No updated required")
	}
	if etag == "" {
		etag = pkg.ETag
	}
	pkg.UpdatedAt = time.Now()
	dpkg := &dao.Package{
		UsedAt:          time.Now(),
		Etag:            etag,
		UpdateCheckedAt: time.Now(),
	}
	return errors.WithStack(s.dao.UpdatePackage(name, dpkg))
}
func (s *State) GC(p *ui.UI, age time.Duration, pkgResolver func(b *ui.UI, selector manifest.Selector, syncOnMissing bool) (*manifest.Package, error)) error {
	lock, err := s.acquireLock(p)
	if err != nil {
		return err
	}
	defer lock.Release(p)
	err = s.CleanCache(p)
	if err != nil {
		return errors.WithStack(err)
	}
	unused, err := s.dao.GetUnusedSince(time.Now().UTC().Add(-age))
	if err != nil {
		return errors.WithStack(err)
	}
	for _, name := range unused {
		task := p.Task(name)
		binDirs, err := s.dao.GetKnownUsages(name)
		if err != nil {
			return errors.WithStack(err)
		}
		inUse := false
		for _, binDir := range binDirs {
			exists, err := doesPackageExistAt(name, binDir)
			if err != nil {
				return errors.WithStack(err)
			}
			if exists {
				inUse = true
				continue
			}
			err = s.dao.PackageRemovedAt(name, binDir)
			if err != nil {
				return errors.WithStack(err)
			}
		}
		if inUse {
			continue
		}
		task.Infof("Clearing %s", name)
		err = s.dao.DeletePackage(name)
		if err != nil {
			return errors.WithStack(err)
		}
		pkg, err := pkgResolver(p, manifest.ExactSelector(manifest.ParseReference(name)), false)
		if errors.Is(err, manifest.ErrUnknownPackage) {
			continue
		}
		if err != nil {
			return errors.WithStack(err)
		}
		if err = s.removePackage(task, pkg); err != nil {
			return errors.WithStack(err)
		}
	}
	return nil
}
func (s *State) removePackage(task *ui.Task, pkg *manifest.Package) error {
	err := s.removeRecursive(task, filepath.Join(s.binaryDir, pkg.Reference.String()))
	if err != nil {
		return errors.WithStack(err)
	}
	err = s.removeRecursive(task, pkg.Dest)
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}
func (s *State) evictPackage(b *ui.Task, pkg *manifest.Package) error {
	lock, err := s.acquireLock(b)
	if err != nil {
		return errors.WithStack(err)
	}
	defer lock.Release(b)
	if err := s.cache.Evict(b, pkg.SHA256, pkg.Source); err != nil {
		return errors.WithStack(err)
	}
	if err := s.removePackage(b, pkg); err != nil {
		return errors.WithStack(err)
	}
	return nil
}