mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +08:00 
			
		
		
		
	Merge pull request #2745 from crazy-max/detect-sudo
config: fix file/folder ownership
This commit is contained in:
		@@ -151,11 +151,11 @@ func toRepoOnly(in string) (string, error) {
 | 
			
		||||
	return strings.Join(out, ","), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
 | 
			
		||||
	return BuildWithResultHandler(ctx, nodes, opts, docker, configDir, w, nil)
 | 
			
		||||
func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
 | 
			
		||||
	return BuildWithResultHandler(ctx, nodes, opts, docker, cfg, w, nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultHandle)) (resp map[string]*client.SolveResponse, err error) {
 | 
			
		||||
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultHandle)) (resp map[string]*client.SolveResponse, err error) {
 | 
			
		||||
	if len(nodes) == 0 {
 | 
			
		||||
		return nil, errors.Errorf("driver required for build")
 | 
			
		||||
	}
 | 
			
		||||
@@ -234,12 +234,12 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			localOpt := opt
 | 
			
		||||
			so, release, err := toSolveOpt(ctx, np.Node(), multiDriver, &localOpt, gatewayOpts, configDir, w, docker)
 | 
			
		||||
			so, release, err := toSolveOpt(ctx, np.Node(), multiDriver, &localOpt, gatewayOpts, cfg, w, docker)
 | 
			
		||||
			opts[k] = localOpt
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			if err := saveLocalState(so, k, opt, np.Node(), configDir); err != nil {
 | 
			
		||||
			if err := saveLocalState(so, k, opt, np.Node(), cfg); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			addGitAttrs(so)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,10 +5,11 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/buildx/builder"
 | 
			
		||||
	"github.com/docker/buildx/localstate"
 | 
			
		||||
	"github.com/docker/buildx/util/confutil"
 | 
			
		||||
	"github.com/moby/buildkit/client"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func saveLocalState(so *client.SolveOpt, target string, opts Options, node builder.Node, configDir string) error {
 | 
			
		||||
func saveLocalState(so *client.SolveOpt, target string, opts Options, node builder.Node, cfg *confutil.Config) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	if so.Ref == "" {
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -30,7 +31,7 @@ func saveLocalState(so *client.SolveOpt, target string, opts Options, node build
 | 
			
		||||
	if lp == "" && dp == "" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	l, err := localstate.New(configDir)
 | 
			
		||||
	l, err := localstate.New(cfg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ import (
 | 
			
		||||
	"github.com/tonistiigi/fsutil"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *Options, bopts gateway.BuildOpts, configDir string, pw progress.Writer, docker *dockerutil.Client) (_ *client.SolveOpt, release func(), err error) {
 | 
			
		||||
func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *Options, bopts gateway.BuildOpts, cfg *confutil.Config, pw progress.Writer, docker *dockerutil.Client) (_ *client.SolveOpt, release func(), err error) {
 | 
			
		||||
	nodeDriver := node.Driver
 | 
			
		||||
	defers := make([]func(), 0, 2)
 | 
			
		||||
	releaseF := func() {
 | 
			
		||||
@@ -271,7 +271,7 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *O
 | 
			
		||||
 | 
			
		||||
	// add node identifier to shared key if one was specified
 | 
			
		||||
	if so.SharedKey != "" {
 | 
			
		||||
		so.SharedKey += ":" + confutil.TryNodeIdentifier(configDir)
 | 
			
		||||
		so.SharedKey += ":" + cfg.TryNodeIdentifier()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opt.Pull {
 | 
			
		||||
 
 | 
			
		||||
@@ -439,7 +439,7 @@ func Create(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts Cre
 | 
			
		||||
	if buildkitdConfigFile == "" {
 | 
			
		||||
		// if buildkit daemon config is not provided, check if the default one
 | 
			
		||||
		// is available and use it
 | 
			
		||||
		if f, ok := confutil.DefaultConfigFile(dockerCli); ok {
 | 
			
		||||
		if f, ok := confutil.NewConfig(dockerCli).BuildKitConfigFile(); ok {
 | 
			
		||||
			buildkitdConfigFile = f
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -584,7 +584,7 @@ func Leave(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts Leav
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ls, err := localstate.New(confutil.ConfigDir(dockerCli))
 | 
			
		||||
	ls, err := localstate.New(confutil.NewConfig(dockerCli))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -265,7 +265,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	done := timeBuildCommand(mp, attributes)
 | 
			
		||||
	resp, retErr := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer)
 | 
			
		||||
	resp, retErr := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), printer)
 | 
			
		||||
	if err := printer.Wait(); retErr == nil {
 | 
			
		||||
		retErr = err
 | 
			
		||||
	}
 | 
			
		||||
@@ -470,7 +470,7 @@ func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string
 | 
			
		||||
		refs = append(refs, b.Ref)
 | 
			
		||||
		bo[k] = b
 | 
			
		||||
	}
 | 
			
		||||
	l, err := localstate.New(confutil.ConfigDir(dockerCli))
 | 
			
		||||
	l, err := localstate.New(confutil.NewConfig(dockerCli))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -621,7 +621,7 @@ func bakeMetricAttributes(dockerCli command.Cli, driverType, url, cmdContext str
 | 
			
		||||
		commandNameAttribute.String("bake"),
 | 
			
		||||
		attribute.Stringer(string(commandOptionsHash), &bakeOptionsHash{
 | 
			
		||||
			bakeOptions: options,
 | 
			
		||||
			configDir:   confutil.ConfigDir(dockerCli),
 | 
			
		||||
			cfg:         confutil.NewConfig(dockerCli),
 | 
			
		||||
			url:         url,
 | 
			
		||||
			cmdContext:  cmdContext,
 | 
			
		||||
			targets:     targets,
 | 
			
		||||
@@ -633,7 +633,7 @@ func bakeMetricAttributes(dockerCli command.Cli, driverType, url, cmdContext str
 | 
			
		||||
 | 
			
		||||
type bakeOptionsHash struct {
 | 
			
		||||
	*bakeOptions
 | 
			
		||||
	configDir  string
 | 
			
		||||
	cfg        *confutil.Config
 | 
			
		||||
	url        string
 | 
			
		||||
	cmdContext string
 | 
			
		||||
	targets    []string
 | 
			
		||||
@@ -657,7 +657,7 @@ func (o *bakeOptionsHash) String() string {
 | 
			
		||||
 | 
			
		||||
		joinedFiles := strings.Join(files, ",")
 | 
			
		||||
		joinedTargets := strings.Join(targets, ",")
 | 
			
		||||
		salt := confutil.TryNodeIdentifier(o.configDir)
 | 
			
		||||
		salt := o.cfg.TryNodeIdentifier()
 | 
			
		||||
 | 
			
		||||
		h := sha256.New()
 | 
			
		||||
		for _, s := range []string{url, cmdContext, joinedFiles, joinedTargets, salt} {
 | 
			
		||||
 
 | 
			
		||||
@@ -238,7 +238,7 @@ func buildMetricAttributes(dockerCli command.Cli, driverType string, options *bu
 | 
			
		||||
		commandNameAttribute.String("build"),
 | 
			
		||||
		attribute.Stringer(string(commandOptionsHash), &buildOptionsHash{
 | 
			
		||||
			buildOptions: options,
 | 
			
		||||
			configDir:    confutil.ConfigDir(dockerCli),
 | 
			
		||||
			cfg:          confutil.NewConfig(dockerCli),
 | 
			
		||||
		}),
 | 
			
		||||
		driverNameAttribute.String(options.builder),
 | 
			
		||||
		driverTypeAttribute.String(driverType),
 | 
			
		||||
@@ -250,7 +250,7 @@ func buildMetricAttributes(dockerCli command.Cli, driverType string, options *bu
 | 
			
		||||
// the fmt.Stringer interface.
 | 
			
		||||
type buildOptionsHash struct {
 | 
			
		||||
	*buildOptions
 | 
			
		||||
	configDir  string
 | 
			
		||||
	cfg        *confutil.Config
 | 
			
		||||
	result     string
 | 
			
		||||
	resultOnce sync.Once
 | 
			
		||||
}
 | 
			
		||||
@@ -267,7 +267,7 @@ func (o *buildOptionsHash) String() string {
 | 
			
		||||
		if contextPath != "-" && osutil.IsLocalDir(contextPath) {
 | 
			
		||||
			contextPath = osutil.ToAbs(contextPath)
 | 
			
		||||
		}
 | 
			
		||||
		salt := confutil.TryNodeIdentifier(o.configDir)
 | 
			
		||||
		salt := o.cfg.TryNodeIdentifier()
 | 
			
		||||
 | 
			
		||||
		h := sha256.New()
 | 
			
		||||
		for _, s := range []string{target, contextPath, dockerfile, salt} {
 | 
			
		||||
 
 | 
			
		||||
@@ -214,7 +214,7 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.No
 | 
			
		||||
	if generateResult {
 | 
			
		||||
		var mu sync.Mutex
 | 
			
		||||
		var idx int
 | 
			
		||||
		resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress, func(driverIndex int, gotRes *build.ResultHandle) {
 | 
			
		||||
		resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress, func(driverIndex int, gotRes *build.ResultHandle) {
 | 
			
		||||
			mu.Lock()
 | 
			
		||||
			defer mu.Unlock()
 | 
			
		||||
			if res == nil || driverIndex < idx {
 | 
			
		||||
@@ -222,7 +222,7 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.No
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	} else {
 | 
			
		||||
		resp, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress)
 | 
			
		||||
		resp, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, res, err
 | 
			
		||||
 
 | 
			
		||||
@@ -258,7 +258,7 @@ func prepareRootDir(dockerCli command.Cli, config *serverConfig) (string, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func rootDataDir(dockerCli command.Cli) string {
 | 
			
		||||
	return filepath.Join(confutil.ConfigDir(dockerCli), "controller")
 | 
			
		||||
	return filepath.Join(confutil.NewConfig(dockerCli).Dir(), "controller")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newBuildxClientAndCheck(ctx context.Context, addr string) (*Client, error) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							@@ -147,6 +147,7 @@ require (
 | 
			
		||||
	github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
 | 
			
		||||
	github.com/shibumi/go-pathspec v1.3.0 // indirect
 | 
			
		||||
	github.com/theupdateframework/notary v0.7.0 // indirect
 | 
			
		||||
	github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 // indirect
 | 
			
		||||
	github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
 | 
			
		||||
	github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
 | 
			
		||||
	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							@@ -439,6 +439,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
 | 
			
		||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
 | 
			
		||||
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
 | 
			
		||||
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 h1:eUk79E1w8yMtXeHSzjKorxuC8qJOnyXQnLaJehxpJaI=
 | 
			
		||||
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
 | 
			
		||||
github.com/tonistiigi/fsutil v0.0.0-20241003195857-3f140a1299b0 h1:H9++AiQUqjwrOMA/DOpWhxWp3JLyyT+MN4sRPbMmwoY=
 | 
			
		||||
github.com/tonistiigi/fsutil v0.0.0-20241003195857-3f140a1299b0/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw=
 | 
			
		||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/ioutils"
 | 
			
		||||
	"github.com/docker/buildx/util/confutil"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/sync/errgroup"
 | 
			
		||||
)
 | 
			
		||||
@@ -42,18 +42,18 @@ type StateGroup struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LocalState struct {
 | 
			
		||||
	root string
 | 
			
		||||
	cfg *confutil.Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(root string) (*LocalState, error) {
 | 
			
		||||
	if root == "" {
 | 
			
		||||
		return nil, errors.Errorf("root dir empty")
 | 
			
		||||
func New(cfg *confutil.Config) (*LocalState, error) {
 | 
			
		||||
	if cfg.Dir() == "" {
 | 
			
		||||
		return nil, errors.Errorf("config dir empty")
 | 
			
		||||
	}
 | 
			
		||||
	if err := os.MkdirAll(filepath.Join(root, refsDir), 0700); err != nil {
 | 
			
		||||
	if err := cfg.MkdirAll(refsDir, 0700); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &LocalState{
 | 
			
		||||
		root: root,
 | 
			
		||||
		cfg: cfg,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -61,7 +61,7 @@ func (ls *LocalState) ReadRef(builderName, nodeName, id string) (*State, error)
 | 
			
		||||
	if err := ls.validate(builderName, nodeName, id); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	dt, err := os.ReadFile(filepath.Join(ls.root, refsDir, builderName, nodeName, id))
 | 
			
		||||
	dt, err := os.ReadFile(filepath.Join(ls.cfg.Dir(), refsDir, builderName, nodeName, id))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -76,19 +76,19 @@ func (ls *LocalState) SaveRef(builderName, nodeName, id string, st State) error
 | 
			
		||||
	if err := ls.validate(builderName, nodeName, id); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	refDir := filepath.Join(ls.root, refsDir, builderName, nodeName)
 | 
			
		||||
	if err := os.MkdirAll(refDir, 0700); err != nil {
 | 
			
		||||
	refDir := filepath.Join(refsDir, builderName, nodeName)
 | 
			
		||||
	if err := ls.cfg.MkdirAll(refDir, 0700); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	dt, err := json.Marshal(st)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return ioutils.AtomicWriteFile(filepath.Join(refDir, id), dt, 0600)
 | 
			
		||||
	return ls.cfg.AtomicWriteFile(filepath.Join(refDir, id), dt, 0644)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ls *LocalState) ReadGroup(id string) (*StateGroup, error) {
 | 
			
		||||
	dt, err := os.ReadFile(filepath.Join(ls.root, refsDir, groupDir, id))
 | 
			
		||||
	dt, err := os.ReadFile(filepath.Join(ls.cfg.Dir(), refsDir, groupDir, id))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -100,15 +100,15 @@ func (ls *LocalState) ReadGroup(id string) (*StateGroup, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ls *LocalState) SaveGroup(id string, stg StateGroup) error {
 | 
			
		||||
	refDir := filepath.Join(ls.root, refsDir, groupDir)
 | 
			
		||||
	if err := os.MkdirAll(refDir, 0700); err != nil {
 | 
			
		||||
	refDir := filepath.Join(refsDir, groupDir)
 | 
			
		||||
	if err := ls.cfg.MkdirAll(refDir, 0700); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	dt, err := json.Marshal(stg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return ioutils.AtomicWriteFile(filepath.Join(refDir, id), dt, 0600)
 | 
			
		||||
	return ls.cfg.AtomicWriteFile(filepath.Join(refDir, id), dt, 0600)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ls *LocalState) RemoveBuilder(builderName string) error {
 | 
			
		||||
@@ -116,7 +116,7 @@ func (ls *LocalState) RemoveBuilder(builderName string) error {
 | 
			
		||||
		return errors.Errorf("builder name empty")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dir := filepath.Join(ls.root, refsDir, builderName)
 | 
			
		||||
	dir := filepath.Join(ls.cfg.Dir(), refsDir, builderName)
 | 
			
		||||
	if _, err := os.Lstat(dir); err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
@@ -147,7 +147,7 @@ func (ls *LocalState) RemoveBuilderNode(builderName string, nodeName string) err
 | 
			
		||||
		return errors.Errorf("node name empty")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dir := filepath.Join(ls.root, refsDir, builderName, nodeName)
 | 
			
		||||
	dir := filepath.Join(ls.cfg.Dir(), refsDir, builderName, nodeName)
 | 
			
		||||
	if _, err := os.Lstat(dir); err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
@@ -208,7 +208,7 @@ func (ls *LocalState) removeGroup(id string) error {
 | 
			
		||||
	if id == "" {
 | 
			
		||||
		return errors.Errorf("group ref empty")
 | 
			
		||||
	}
 | 
			
		||||
	f := filepath.Join(ls.root, refsDir, groupDir, id)
 | 
			
		||||
	f := filepath.Join(ls.cfg.Dir(), refsDir, groupDir, id)
 | 
			
		||||
	if _, err := os.Lstat(f); err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/buildx/util/confutil"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -39,10 +40,10 @@ func newls(t *testing.T) *LocalState {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
	tmpdir := t.TempDir()
 | 
			
		||||
 | 
			
		||||
	l, err := New(tmpdir)
 | 
			
		||||
	l, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	require.DirExists(t, filepath.Join(tmpdir, refsDir))
 | 
			
		||||
	require.Equal(t, tmpdir, l.root)
 | 
			
		||||
	require.Equal(t, tmpdir, l.cfg.Dir())
 | 
			
		||||
 | 
			
		||||
	require.NoError(t, l.SaveRef(testBuilderName, testNodeName, testStateRefID, testStateRef))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/buildx/localstate"
 | 
			
		||||
	"github.com/docker/docker/pkg/ioutils"
 | 
			
		||||
	"github.com/docker/buildx/util/confutil"
 | 
			
		||||
	"github.com/gofrs/flock"
 | 
			
		||||
	"github.com/opencontainers/go-digest"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
@@ -20,25 +20,25 @@ const (
 | 
			
		||||
	activityDir = "activity"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func New(root string) (*Store, error) {
 | 
			
		||||
	if err := os.MkdirAll(filepath.Join(root, instanceDir), 0700); err != nil {
 | 
			
		||||
func New(cfg *confutil.Config) (*Store, error) {
 | 
			
		||||
	if err := cfg.MkdirAll(instanceDir, 0700); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := os.MkdirAll(filepath.Join(root, defaultsDir), 0700); err != nil {
 | 
			
		||||
	if err := cfg.MkdirAll(defaultsDir, 0700); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := os.MkdirAll(filepath.Join(root, activityDir), 0700); err != nil {
 | 
			
		||||
	if err := cfg.MkdirAll(activityDir, 0700); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &Store{root: root}, nil
 | 
			
		||||
	return &Store{cfg: cfg}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Store struct {
 | 
			
		||||
	root string
 | 
			
		||||
	cfg *confutil.Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Store) Txn() (*Txn, func(), error) {
 | 
			
		||||
	l := flock.New(filepath.Join(s.root, ".lock"))
 | 
			
		||||
	l := flock.New(filepath.Join(s.cfg.Dir(), ".lock"))
 | 
			
		||||
	if err := l.Lock(); err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -54,7 +54,7 @@ type Txn struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Txn) List() ([]*NodeGroup, error) {
 | 
			
		||||
	pp := filepath.Join(t.s.root, instanceDir)
 | 
			
		||||
	pp := filepath.Join(t.s.cfg.Dir(), instanceDir)
 | 
			
		||||
	fis, err := os.ReadDir(pp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
@@ -84,7 +84,7 @@ func (t *Txn) NodeGroupByName(name string) (*NodeGroup, error) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	dt, err := os.ReadFile(filepath.Join(t.s.root, instanceDir, name))
 | 
			
		||||
	dt, err := os.ReadFile(filepath.Join(t.s.cfg.Dir(), instanceDir, name))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -110,7 +110,7 @@ func (t *Txn) Save(ng *NodeGroup) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return ioutils.AtomicWriteFile(filepath.Join(t.s.root, instanceDir, name), dt, 0600)
 | 
			
		||||
	return t.s.cfg.AtomicWriteFile(filepath.Join(instanceDir, name), dt, 0600)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Txn) Remove(name string) error {
 | 
			
		||||
@@ -121,14 +121,14 @@ func (t *Txn) Remove(name string) error {
 | 
			
		||||
	if err := t.RemoveLastActivity(name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	ls, err := localstate.New(t.s.root)
 | 
			
		||||
	ls, err := localstate.New(t.s.cfg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := ls.RemoveBuilder(name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return os.RemoveAll(filepath.Join(t.s.root, instanceDir, name))
 | 
			
		||||
	return os.RemoveAll(filepath.Join(t.s.cfg.Dir(), instanceDir, name))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Txn) SetCurrent(key, name string, global, def bool) error {
 | 
			
		||||
@@ -141,28 +141,28 @@ func (t *Txn) SetCurrent(key, name string, global, def bool) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := ioutils.AtomicWriteFile(filepath.Join(t.s.root, "current"), dt, 0600); err != nil {
 | 
			
		||||
	if err := t.s.cfg.AtomicWriteFile("current", dt, 0600); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	h := toHash(key)
 | 
			
		||||
 | 
			
		||||
	if def {
 | 
			
		||||
		if err := ioutils.AtomicWriteFile(filepath.Join(t.s.root, defaultsDir, h), []byte(name), 0600); err != nil {
 | 
			
		||||
		if err := t.s.cfg.AtomicWriteFile(filepath.Join(defaultsDir, h), []byte(name), 0600); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		os.RemoveAll(filepath.Join(t.s.root, defaultsDir, h)) // ignore error
 | 
			
		||||
		os.RemoveAll(filepath.Join(t.s.cfg.Dir(), defaultsDir, h)) // ignore error
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Txn) UpdateLastActivity(ng *NodeGroup) error {
 | 
			
		||||
	return ioutils.AtomicWriteFile(filepath.Join(t.s.root, activityDir, ng.Name), []byte(time.Now().UTC().Format(time.RFC3339)), 0600)
 | 
			
		||||
	return t.s.cfg.AtomicWriteFile(filepath.Join(activityDir, ng.Name), []byte(time.Now().UTC().Format(time.RFC3339)), 0600)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Txn) GetLastActivity(ng *NodeGroup) (la time.Time, _ error) {
 | 
			
		||||
	dt, err := os.ReadFile(filepath.Join(t.s.root, activityDir, ng.Name))
 | 
			
		||||
	dt, err := os.ReadFile(filepath.Join(t.s.cfg.Dir(), activityDir, ng.Name))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(errors.Cause(err)) {
 | 
			
		||||
			return la, nil
 | 
			
		||||
@@ -177,7 +177,7 @@ func (t *Txn) RemoveLastActivity(name string) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return os.RemoveAll(filepath.Join(t.s.root, activityDir, name))
 | 
			
		||||
	return os.RemoveAll(filepath.Join(t.s.cfg.Dir(), activityDir, name))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Txn) reset(key string) error {
 | 
			
		||||
@@ -185,11 +185,11 @@ func (t *Txn) reset(key string) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return ioutils.AtomicWriteFile(filepath.Join(t.s.root, "current"), dt, 0600)
 | 
			
		||||
	return t.s.cfg.AtomicWriteFile("current", dt, 0600)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Txn) Current(key string) (*NodeGroup, error) {
 | 
			
		||||
	dt, err := os.ReadFile(filepath.Join(t.s.root, "current"))
 | 
			
		||||
	dt, err := os.ReadFile(filepath.Join(t.s.cfg.Dir(), "current"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return nil, err
 | 
			
		||||
@@ -220,7 +220,7 @@ func (t *Txn) Current(key string) (*NodeGroup, error) {
 | 
			
		||||
 | 
			
		||||
	h := toHash(key)
 | 
			
		||||
 | 
			
		||||
	dt, err = os.ReadFile(filepath.Join(t.s.root, defaultsDir, h))
 | 
			
		||||
	dt, err = os.ReadFile(filepath.Join(t.s.cfg.Dir(), defaultsDir, h))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			t.reset(key)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/buildx/util/confutil"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
@@ -15,7 +16,7 @@ func TestEmptyStartup(t *testing.T) {
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	defer os.RemoveAll(tmpdir)
 | 
			
		||||
 | 
			
		||||
	s, err := New(tmpdir)
 | 
			
		||||
	s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	txn, release, err := s.Txn()
 | 
			
		||||
@@ -33,7 +34,7 @@ func TestNodeLocking(t *testing.T) {
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	defer os.RemoveAll(tmpdir)
 | 
			
		||||
 | 
			
		||||
	s, err := New(tmpdir)
 | 
			
		||||
	s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	_, release, err := s.Txn()
 | 
			
		||||
@@ -68,7 +69,7 @@ func TestNodeManagement(t *testing.T) {
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	defer os.RemoveAll(tmpdir)
 | 
			
		||||
 | 
			
		||||
	s, err := New(tmpdir)
 | 
			
		||||
	s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	txn, release, err := s.Txn()
 | 
			
		||||
@@ -240,7 +241,7 @@ func TestNodeInvalidName(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
	tmpdir := t.TempDir()
 | 
			
		||||
 | 
			
		||||
	s, err := New(tmpdir)
 | 
			
		||||
	s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	txn, release, err := s.Txn()
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ import (
 | 
			
		||||
 | 
			
		||||
// GetStore returns current builder instance store
 | 
			
		||||
func GetStore(dockerCli command.Cli) (*store.Txn, func(), error) {
 | 
			
		||||
	s, err := store.New(confutil.ConfigDir(dockerCli))
 | 
			
		||||
	s, err := store.New(confutil.NewConfig(dockerCli))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ import (
 | 
			
		||||
	"github.com/containerd/platforms"
 | 
			
		||||
	"github.com/creack/pty"
 | 
			
		||||
	"github.com/docker/buildx/localstate"
 | 
			
		||||
	"github.com/docker/buildx/util/confutil"
 | 
			
		||||
	"github.com/docker/buildx/util/gitutil"
 | 
			
		||||
	"github.com/moby/buildkit/client"
 | 
			
		||||
	"github.com/moby/buildkit/frontend/subrequests/lint"
 | 
			
		||||
@@ -167,7 +168,7 @@ COPY --from=base /etc/bar /bar
 | 
			
		||||
	err = json.Unmarshal(dt, &md)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	ls, err := localstate.New(buildxConfig(sb))
 | 
			
		||||
	ls, err := localstate.New(confutil.NewConfig(nil, confutil.WithDir(buildxConfig(sb))))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	refParts := strings.Split(md.BuildRef, "/")
 | 
			
		||||
@@ -209,7 +210,7 @@ COPY --from=base /etc/bar /bar
 | 
			
		||||
	err = json.Unmarshal(dt, &md)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	ls, err := localstate.New(buildxConfig(sb))
 | 
			
		||||
	ls, err := localstate.New(confutil.NewConfig(nil, confutil.WithDir(buildxConfig(sb))))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	refParts := strings.Split(md.BuildRef, "/")
 | 
			
		||||
@@ -261,7 +262,7 @@ COPY foo /foo
 | 
			
		||||
	err = json.Unmarshal(dt, &md)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	ls, err := localstate.New(buildxConfig(sb))
 | 
			
		||||
	ls, err := localstate.New(confutil.NewConfig(nil, confutil.WithDir(buildxConfig(sb))))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	refParts := strings.Split(md.BuildRef, "/")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,39 +1,143 @@
 | 
			
		||||
package confutil
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/cli/cli/command"
 | 
			
		||||
	"github.com/docker/docker/pkg/ioutils"
 | 
			
		||||
	"github.com/pelletier/go-toml"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	fs "github.com/tonistiigi/fsutil/copy"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ConfigDir will look for correct configuration store path;
 | 
			
		||||
// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory
 | 
			
		||||
// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`)
 | 
			
		||||
func ConfigDir(dockerCli command.Cli) string {
 | 
			
		||||
	if buildxConfig := os.Getenv("BUILDX_CONFIG"); buildxConfig != "" {
 | 
			
		||||
		logrus.Debugf("using config store %q based in \"$BUILDX_CONFIG\" environment variable", buildxConfig)
 | 
			
		||||
		return buildxConfig
 | 
			
		||||
	}
 | 
			
		||||
const defaultBuildKitConfigFile = "buildkitd.default.toml"
 | 
			
		||||
 | 
			
		||||
	buildxConfig := filepath.Join(filepath.Dir(dockerCli.ConfigFile().Filename), "buildx")
 | 
			
		||||
	logrus.Debugf("using default config store %q", buildxConfig)
 | 
			
		||||
	return buildxConfig
 | 
			
		||||
type Config struct {
 | 
			
		||||
	dir     string
 | 
			
		||||
	chowner *chowner
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DefaultConfigFile returns the default BuildKit configuration file path
 | 
			
		||||
func DefaultConfigFile(dockerCli command.Cli) (string, bool) {
 | 
			
		||||
	f := path.Join(ConfigDir(dockerCli), "buildkitd.default.toml")
 | 
			
		||||
type chowner struct {
 | 
			
		||||
	uid int
 | 
			
		||||
	gid int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ConfigOption func(*configOptions)
 | 
			
		||||
 | 
			
		||||
type configOptions struct {
 | 
			
		||||
	dir string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WithDir(dir string) ConfigOption {
 | 
			
		||||
	return func(o *configOptions) {
 | 
			
		||||
		o.dir = dir
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewConfig(dockerCli command.Cli, opts ...ConfigOption) *Config {
 | 
			
		||||
	co := configOptions{}
 | 
			
		||||
	for _, opt := range opts {
 | 
			
		||||
		opt(&co)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	configDir := co.dir
 | 
			
		||||
	if configDir == "" {
 | 
			
		||||
		configDir = os.Getenv("BUILDX_CONFIG")
 | 
			
		||||
		if configDir == "" {
 | 
			
		||||
			configDir = filepath.Join(filepath.Dir(dockerCli.ConfigFile().Filename), "buildx")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &Config{
 | 
			
		||||
		dir:     configDir,
 | 
			
		||||
		chowner: sudoer(configDir),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Dir will look for correct configuration store path;
 | 
			
		||||
// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory
 | 
			
		||||
// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`)
 | 
			
		||||
func (c *Config) Dir() string {
 | 
			
		||||
	return c.dir
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BuildKitConfigFile returns the default BuildKit configuration file path
 | 
			
		||||
func (c *Config) BuildKitConfigFile() (string, bool) {
 | 
			
		||||
	f := filepath.Join(c.dir, defaultBuildKitConfigFile)
 | 
			
		||||
	if _, err := os.Stat(f); err == nil {
 | 
			
		||||
		return f, true
 | 
			
		||||
	}
 | 
			
		||||
	return "", false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MkdirAll creates a directory and all necessary parents within the config dir.
 | 
			
		||||
func (c *Config) MkdirAll(dir string, perm os.FileMode) error {
 | 
			
		||||
	var chown fs.Chowner
 | 
			
		||||
	if c.chowner != nil {
 | 
			
		||||
		chown = func(user *fs.User) (*fs.User, error) {
 | 
			
		||||
			return &fs.User{UID: c.chowner.uid, GID: c.chowner.gid}, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	d := filepath.Join(c.dir, dir)
 | 
			
		||||
	st, err := os.Stat(d)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return fs.MkdirAll(d, perm, chown, nil)
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// if directory already exists, fix the owner if necessary
 | 
			
		||||
	if c.chowner == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	currentOwner := fileOwner(st)
 | 
			
		||||
	if currentOwner != nil && (currentOwner.uid != c.chowner.uid || currentOwner.gid != c.chowner.gid) {
 | 
			
		||||
		return os.Chown(d, c.chowner.uid, c.chowner.gid)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AtomicWriteFile writes data to a file within the config dir atomically
 | 
			
		||||
func (c *Config) AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
 | 
			
		||||
	f := filepath.Join(c.dir, filename)
 | 
			
		||||
	if err := ioutils.AtomicWriteFile(f, data, perm); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if c.chowner == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return os.Chown(f, c.chowner.uid, c.chowner.gid)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var nodeIdentifierMu sync.Mutex
 | 
			
		||||
 | 
			
		||||
func (c *Config) TryNodeIdentifier() (out string) {
 | 
			
		||||
	nodeIdentifierMu.Lock()
 | 
			
		||||
	defer nodeIdentifierMu.Unlock()
 | 
			
		||||
	sessionFilename := ".buildNodeID"
 | 
			
		||||
	sessionFilepath := filepath.Join(c.Dir(), sessionFilename)
 | 
			
		||||
	if _, err := os.Lstat(sessionFilepath); err != nil {
 | 
			
		||||
		if os.IsNotExist(err) { // create a new file with stored randomness
 | 
			
		||||
			b := make([]byte, 8)
 | 
			
		||||
			if _, err := rand.Read(b); err != nil {
 | 
			
		||||
				return out
 | 
			
		||||
			}
 | 
			
		||||
			if err := c.AtomicWriteFile(sessionFilename, []byte(hex.EncodeToString(b)), 0600); err != nil {
 | 
			
		||||
				return out
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	dt, err := os.ReadFile(sessionFilepath)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return string(dt)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadConfigTree loads BuildKit config toml tree
 | 
			
		||||
func LoadConfigTree(fp string) (*toml.Tree, error) {
 | 
			
		||||
	f, err := os.Open(fp)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								util/confutil/config_unix.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								util/confutil/config_unix.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
//go:build !windows
 | 
			
		||||
// +build !windows
 | 
			
		||||
 | 
			
		||||
package confutil
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/user"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// sudoer returns the user that invoked the current process with sudo only if
 | 
			
		||||
// sudo HOME env matches the home directory of the user that ran sudo and is
 | 
			
		||||
// part of configDir.
 | 
			
		||||
func sudoer(configDir string) *chowner {
 | 
			
		||||
	if _, ok := os.LookupEnv("SUDO_COMMAND"); !ok {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	suidenv := os.Getenv("SUDO_UID") // https://www.sudo.ws/docs/man/sudo.man/#SUDO_UID
 | 
			
		||||
	sgidenv := os.Getenv("SUDO_GID") // https://www.sudo.ws/docs/man/sudo.man/#SUDO_GID
 | 
			
		||||
	if suidenv == "" || sgidenv == "" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	u, err := user.LookupId(suidenv)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	suid, err := strconv.Atoi(suidenv)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	sgid, err := strconv.Atoi(sgidenv)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	home, _ := os.UserHomeDir()
 | 
			
		||||
	if home == "" || u.HomeDir != home {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if ok, _ := isSubPath(home, configDir); !ok {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return &chowner{uid: suid, gid: sgid}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fileOwner(fi os.FileInfo) *chowner {
 | 
			
		||||
	st := fi.Sys().(*syscall.Stat_t)
 | 
			
		||||
	return &chowner{uid: int(st.Uid), gid: int(st.Gid)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isSubPath(basePath, subPath string) (bool, error) {
 | 
			
		||||
	rel, err := filepath.Rel(basePath, subPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return !strings.HasPrefix(rel, "..") && rel != ".", nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										58
									
								
								util/confutil/config_unix_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								util/confutil/config_unix_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
//go:build !windows
 | 
			
		||||
// +build !windows
 | 
			
		||||
 | 
			
		||||
package confutil
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestIsSubPath(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name     string
 | 
			
		||||
		basePath string
 | 
			
		||||
		subPath  string
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:     "SubPath is a direct subdirectory",
 | 
			
		||||
			basePath: "/home/user",
 | 
			
		||||
			subPath:  "/home/user/docs",
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "SubPath is the same as basePath",
 | 
			
		||||
			basePath: "/home/user",
 | 
			
		||||
			subPath:  "/home/user",
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "SubPath is not a subdirectory",
 | 
			
		||||
			basePath: "/home/user",
 | 
			
		||||
			subPath:  "/home/otheruser",
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "SubPath is a nested subdirectory",
 | 
			
		||||
			basePath: "/home/user",
 | 
			
		||||
			subPath:  "/home/user/docs/reports",
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "SubPath is a sibling directory",
 | 
			
		||||
			basePath: "/home/user",
 | 
			
		||||
			subPath:  "/home/user2",
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		tt := tt
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			ok, err := isSubPath(tt.basePath, tt.subPath)
 | 
			
		||||
			assert.NoError(t, err)
 | 
			
		||||
			assert.Equal(t, tt.expected, ok)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								util/confutil/config_windows.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								util/confutil/config_windows.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
package confutil
 | 
			
		||||
 | 
			
		||||
import "os"
 | 
			
		||||
 | 
			
		||||
func sudoer(_ string) *chowner {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fileOwner(_ os.FileInfo) *chowner {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
package confutil
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var nodeIdentifierMu sync.Mutex
 | 
			
		||||
 | 
			
		||||
func TryNodeIdentifier(configDir string) (out string) {
 | 
			
		||||
	nodeIdentifierMu.Lock()
 | 
			
		||||
	defer nodeIdentifierMu.Unlock()
 | 
			
		||||
	sessionFile := filepath.Join(configDir, ".buildNodeID")
 | 
			
		||||
	if _, err := os.Lstat(sessionFile); err != nil {
 | 
			
		||||
		if os.IsNotExist(err) { // create a new file with stored randomness
 | 
			
		||||
			b := make([]byte, 8)
 | 
			
		||||
			if _, err := rand.Read(b); err != nil {
 | 
			
		||||
				return out
 | 
			
		||||
			}
 | 
			
		||||
			if err := os.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
 | 
			
		||||
				return out
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dt, err := os.ReadFile(sessionFile)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return string(dt)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								vendor/github.com/tonistiigi/dchapes-mode/.hgignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/tonistiigi/dchapes-mode/.hgignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
syntax: glob
 | 
			
		||||
bench*.out*
 | 
			
		||||
cmode
 | 
			
		||||
coverage.out
 | 
			
		||||
coverage.txt
 | 
			
		||||
							
								
								
									
										29
									
								
								vendor/github.com/tonistiigi/dchapes-mode/Dockerfile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/tonistiigi/dchapes-mode/Dockerfile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
 | 
			
		||||
# syntax=docker/dockerfile:1
 | 
			
		||||
 | 
			
		||||
ARG GO_VERSION=1.23
 | 
			
		||||
ARG XX_VERSION=1.5.0
 | 
			
		||||
 | 
			
		||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
 | 
			
		||||
 | 
			
		||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS base
 | 
			
		||||
RUN apk add --no-cache git
 | 
			
		||||
COPY --from=xx / /
 | 
			
		||||
WORKDIR /src
 | 
			
		||||
 | 
			
		||||
FROM base AS build
 | 
			
		||||
ARG TARGETPLATFORM
 | 
			
		||||
RUN --mount=target=. --mount=target=/go/pkg/mod,type=cache \
 | 
			
		||||
    --mount=target=/root/.cache,type=cache \
 | 
			
		||||
    xx-go build ./...
 | 
			
		||||
 | 
			
		||||
FROM base AS test
 | 
			
		||||
ARG TESTFLAGS
 | 
			
		||||
RUN --mount=target=. --mount=target=/go/pkg/mod,type=cache \
 | 
			
		||||
    --mount=target=/root/.cache,type=cache \
 | 
			
		||||
    xx-go test -v -coverprofile=/tmp/coverage.txt  -covermode=atomic ${TESTFLAGS} ./...
 | 
			
		||||
 | 
			
		||||
FROM scratch AS test-coverage
 | 
			
		||||
COPY --from=test /tmp/coverage.txt /coverage-root.txt
 | 
			
		||||
 | 
			
		||||
FROM build
 | 
			
		||||
							
								
								
									
										22
									
								
								vendor/github.com/tonistiigi/dchapes-mode/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/tonistiigi/dchapes-mode/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
Copyright © 2016-2018, Dave Chapeskie
 | 
			
		||||
All rights reserved.
 | 
			
		||||
 | 
			
		||||
Redistribution and use in source and binary forms, with or without
 | 
			
		||||
modification, are permitted provided that the following conditions are met:
 | 
			
		||||
 | 
			
		||||
1. Redistributions of source code must retain the above copyright notice, this
 | 
			
		||||
   list of conditions and the following disclaimer.
 | 
			
		||||
2. Redistributions in binary form must reproduce the above copyright notice,
 | 
			
		||||
   this list of conditions and the following disclaimer in the documentation
 | 
			
		||||
   and/or other materials provided with the distribution.
 | 
			
		||||
 | 
			
		||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 | 
			
		||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 | 
			
		||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 | 
			
		||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 | 
			
		||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 | 
			
		||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 | 
			
		||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 | 
			
		||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
			
		||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 | 
			
		||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
							
								
								
									
										26
									
								
								vendor/github.com/tonistiigi/dchapes-mode/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								vendor/github.com/tonistiigi/dchapes-mode/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
Mode
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
This is a fork of [hg.sr.ht/~dchapes/mode](https://hg.sr.ht/~dchapes/mode) with minimal patches and basic CI.
 | 
			
		||||
 | 
			
		||||
[Mode](https://hg.sr.ht/~dchapes/mode)
 | 
			
		||||
is a [Go](http://golang.org/) package that provides
 | 
			
		||||
a native Go implementation of BSD's
 | 
			
		||||
[`setmode`](https://www.freebsd.org/cgi/man.cgi?query=setmode&sektion=3)
 | 
			
		||||
and `getmode` which can be used to modify the mode bits of
 | 
			
		||||
an [`os.FileMode`](https://golang.org/pkg/os#FileMode) value
 | 
			
		||||
based on a symbolic value as described by the
 | 
			
		||||
Unix [`chmod`](https://www.freebsd.org/cgi/man.cgi?query=chmod&sektion=1) command.
 | 
			
		||||
 | 
			
		||||
[](https://pkg.go.dev/hg.sr.ht/~dchapes/mode)
 | 
			
		||||
 | 
			
		||||
Online package documentation is available via
 | 
			
		||||
[pkg.go.dev](https://pkg.go.dev/hg.sr.ht/~dchapes/mode).
 | 
			
		||||
 | 
			
		||||
To install:
 | 
			
		||||
 | 
			
		||||
		go get hg.sr.ht/~dchapes/mode
 | 
			
		||||
 | 
			
		||||
or `go build` any Go code that imports it:
 | 
			
		||||
 | 
			
		||||
		import "hg.sr.ht/~dchapes/mode"
 | 
			
		||||
							
								
								
									
										76
									
								
								vendor/github.com/tonistiigi/dchapes-mode/bits.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								vendor/github.com/tonistiigi/dchapes-mode/bits.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
package mode
 | 
			
		||||
 | 
			
		||||
import "os"
 | 
			
		||||
 | 
			
		||||
type modet uint16
 | 
			
		||||
 | 
			
		||||
// Although many of these can be found in the syscall package
 | 
			
		||||
// we don't use those to avoid the dependency, add some more
 | 
			
		||||
// values, use non-exported Go names, and use octal for better clarity.
 | 
			
		||||
//
 | 
			
		||||
// Note that Go only uses the the nine least significant bits as "Unix
 | 
			
		||||
// permission bits" (os.ModePerm == 0777). We use chmod(1)'s octal
 | 
			
		||||
// definitions that include three further bits: isUID, isGID, and
 | 
			
		||||
// isTXT (07000). Go has os.ModeSetuid=1<<23, os.ModeSetgid=1<<22,
 | 
			
		||||
// and os.ModeSticy=1<<20 for these. We do this so that absolute
 | 
			
		||||
// octal values can include those bits as defined by chmod(1).
 | 
			
		||||
const (
 | 
			
		||||
	//ifDir   = 040000 // directory
 | 
			
		||||
	isUID   = 04000 // set user id on execution
 | 
			
		||||
	isGID   = 02000 // set group id on execution
 | 
			
		||||
	isTXT   = 01000 // sticky bit
 | 
			
		||||
	iRWXU   = 00700 // RWX mask for owner
 | 
			
		||||
	iRUser  = 00400 // R for owner
 | 
			
		||||
	iWUser  = 00200 // W for owner
 | 
			
		||||
	iXUser  = 00100 // X for owner
 | 
			
		||||
	iRWXG   = 00070 // RWX mask for group
 | 
			
		||||
	iRGroup = 00040 // R for group
 | 
			
		||||
	iWGroup = 00020 // W for group
 | 
			
		||||
	iXGroup = 00010 // X for group
 | 
			
		||||
	iRWXO   = 00007 // RWX mask for other
 | 
			
		||||
	iROther = 00004 // R for other
 | 
			
		||||
	iWOther = 00002 // W for other
 | 
			
		||||
	iXOther = 00001 // X for other
 | 
			
		||||
 | 
			
		||||
	standardBits = isUID | isGID | iRWXU | iRWXG | iRWXO
 | 
			
		||||
 | 
			
		||||
	// os.FileMode bits we touch
 | 
			
		||||
	fmBits = os.ModeSetuid | os.ModeSetgid | os.ModeSticky | os.ModePerm
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func fileModeToBits(fm os.FileMode) modet {
 | 
			
		||||
	m := modet(fm.Perm())
 | 
			
		||||
	/*
 | 
			
		||||
		if fm&os.ModeSetuid != 0 {
 | 
			
		||||
			m |= isUID
 | 
			
		||||
		}
 | 
			
		||||
		if fm&os.ModeSetgid != 0 {
 | 
			
		||||
			m |= isGID
 | 
			
		||||
		}
 | 
			
		||||
		if fm&os.ModeSticky != 0 {
 | 
			
		||||
			m |= isTXT
 | 
			
		||||
		}
 | 
			
		||||
	*/
 | 
			
		||||
	m |= modet(fm & (os.ModeSetuid | os.ModeSetgid) >> 12)
 | 
			
		||||
	m |= modet(fm & os.ModeSticky >> 11)
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func bitsToFileMode(old os.FileMode, m modet) os.FileMode {
 | 
			
		||||
	fm := old &^ fmBits
 | 
			
		||||
	fm |= os.FileMode(m) & os.ModePerm
 | 
			
		||||
	/*
 | 
			
		||||
		if m&isUID != 0 {
 | 
			
		||||
			fm |= os.ModeSetuid
 | 
			
		||||
		}
 | 
			
		||||
		if m&isGID != 0 {
 | 
			
		||||
			fm |= os.ModeSetgid
 | 
			
		||||
		}
 | 
			
		||||
		if m&isTXT != 0 {
 | 
			
		||||
			fm |= os.ModeSticky
 | 
			
		||||
		}
 | 
			
		||||
	*/
 | 
			
		||||
	fm |= os.FileMode(m&(isUID|isGID)) << 12
 | 
			
		||||
	fm |= os.FileMode(m&isTXT) << 11
 | 
			
		||||
	return fm
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								vendor/github.com/tonistiigi/dchapes-mode/docker-bake.hcl
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/tonistiigi/dchapes-mode/docker-bake.hcl
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
variable "GO_VERSION" {
 | 
			
		||||
  default = null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
group "default" {
 | 
			
		||||
  targets = ["build"]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
target "build" {
 | 
			
		||||
  args = {
 | 
			
		||||
    GO_VERSION = GO_VERSION
 | 
			
		||||
  }
 | 
			
		||||
  output = ["type=cacheonly"]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
target "test" {
 | 
			
		||||
  inherits = ["build"]
 | 
			
		||||
  target = "test"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
target "cross" {
 | 
			
		||||
  inherits = ["build"]
 | 
			
		||||
  platforms = ["linux/amd64", "linux/386", "linux/arm64", "linux/arm", "linux/ppc64le", "linux/s390x", "darwin/amd64", "darwin/arm64", "windows/amd64", "windows/arm64", "freebsd/amd64", "freebsd/arm64"]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										546
									
								
								vendor/github.com/tonistiigi/dchapes-mode/mode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										546
									
								
								vendor/github.com/tonistiigi/dchapes-mode/mode.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,546 @@
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Parts of this file are a heavily modified C to Go
 | 
			
		||||
translation of BSD's /usr/src/lib/libc/gen/setmode.c
 | 
			
		||||
that contains the following copyright notice:
 | 
			
		||||
 | 
			
		||||
 * Copyright (c) 1989, 1993, 1994
 | 
			
		||||
 *	The Regents of the University of California.  All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * This code is derived from software contributed to Berkeley by
 | 
			
		||||
 * Dave Borman at Cray Research, Inc.
 | 
			
		||||
 *
 | 
			
		||||
 * Redistribution and use in source and binary forms, with or without
 | 
			
		||||
 * modification, are permitted provided that the following conditions
 | 
			
		||||
 * are met:
 | 
			
		||||
 * 1. Redistributions of source code must retain the above copyright
 | 
			
		||||
 *    notice, this list of conditions and the following disclaimer.
 | 
			
		||||
 * 2. Redistributions in binary form must reproduce the above copyright
 | 
			
		||||
 *    notice, this list of conditions and the following disclaimer in the
 | 
			
		||||
 *    documentation and/or other materials provided with the distribution.
 | 
			
		||||
 * 4. Neither the name of the University nor the names of its contributors
 | 
			
		||||
 *    may be used to endorse or promote products derived from this software
 | 
			
		||||
 *    without specific prior written permission.
 | 
			
		||||
 *
 | 
			
		||||
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 | 
			
		||||
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
			
		||||
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
			
		||||
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 | 
			
		||||
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
			
		||||
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | 
			
		||||
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | 
			
		||||
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
			
		||||
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
			
		||||
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | 
			
		||||
 * SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// Package mode provides a native Go implementation of BSD's setmode and getmode
 | 
			
		||||
// which can be used to modify the mode bits of an os.FileMode value based on
 | 
			
		||||
// a symbolic value as described by the Unix chmod command.
 | 
			
		||||
//
 | 
			
		||||
// For a full description of the mode string see chmod(1).
 | 
			
		||||
// Some examples include:
 | 
			
		||||
//
 | 
			
		||||
//	644		make a file readable by anyone and writable by the owner
 | 
			
		||||
//			only.
 | 
			
		||||
//
 | 
			
		||||
//	go-w		deny write permission to group and others.
 | 
			
		||||
//
 | 
			
		||||
//	=rw,+X		set the read and write permissions to the usual defaults,
 | 
			
		||||
//			but retain any execute permissions that are currently set.
 | 
			
		||||
//
 | 
			
		||||
//	+X		make a directory or file searchable/executable by everyone
 | 
			
		||||
//			if it is already searchable/executable by anyone.
 | 
			
		||||
//
 | 
			
		||||
//	755
 | 
			
		||||
//	u=rwx,go=rx
 | 
			
		||||
//	u=rwx,go=u-w	make a file readable/executable by everyone and writable by
 | 
			
		||||
//			the owner only.
 | 
			
		||||
//
 | 
			
		||||
//	go=		clear all mode bits for group and others.
 | 
			
		||||
//
 | 
			
		||||
//	go=u-w		set the group bits equal to the user bits, but clear the
 | 
			
		||||
//			group write bit.
 | 
			
		||||
//
 | 
			
		||||
// See Also:
 | 
			
		||||
//
 | 
			
		||||
//	setmode(3): https://www.freebsd.org/cgi/man.cgi?query=setmode&sektion=3
 | 
			
		||||
//	chmod(1):   https://www.freebsd.org/cgi/man.cgi?query=chmod&sektion=1
 | 
			
		||||
package mode
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Set is a set of changes to apply to an os.FileMode.
 | 
			
		||||
// Changes include setting or clearing specific bits, copying bits from one
 | 
			
		||||
// user class to another (e.g. "u=go" sets the user permissions to a copy of
 | 
			
		||||
// the group and other permsissions), etc.
 | 
			
		||||
type Set struct {
 | 
			
		||||
	cmds []bitcmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type bitcmd struct {
 | 
			
		||||
	cmd  byte
 | 
			
		||||
	cmd2 byte
 | 
			
		||||
	bits modet
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	cmd2Clear byte = 1 << iota
 | 
			
		||||
	cmd2Set
 | 
			
		||||
	cmd2GBits
 | 
			
		||||
	cmd2OBits
 | 
			
		||||
	cmd2UBits
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (c bitcmd) String() string {
 | 
			
		||||
	c2 := ""
 | 
			
		||||
	if c.cmd2 != 0 {
 | 
			
		||||
		c2 = " cmd2:"
 | 
			
		||||
		if c.cmd2&cmd2Clear != 0 {
 | 
			
		||||
			c2 += " CLR"
 | 
			
		||||
		}
 | 
			
		||||
		if c.cmd2&cmd2Set != 0 {
 | 
			
		||||
			c2 += " SET"
 | 
			
		||||
		}
 | 
			
		||||
		if c.cmd2&cmd2UBits != 0 {
 | 
			
		||||
			c2 += " UBITS"
 | 
			
		||||
		}
 | 
			
		||||
		if c.cmd2&cmd2GBits != 0 {
 | 
			
		||||
			c2 += " GBITS"
 | 
			
		||||
		}
 | 
			
		||||
		if c.cmd2&cmd2OBits != 0 {
 | 
			
		||||
			c2 += " OBITS"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("cmd: %q bits %#05o%s", c.cmd, c.bits, c2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The String method will likely only be useful when testing.
 | 
			
		||||
func (s Set) String() string {
 | 
			
		||||
	var buf strings.Builder
 | 
			
		||||
	buf.Grow(21*len(s.cmds) + 10)
 | 
			
		||||
	_, _ = buf.WriteString("set: {\n")
 | 
			
		||||
	for _, c := range s.cmds {
 | 
			
		||||
		_, _ = buf.WriteString(c.String())
 | 
			
		||||
		_ = buf.WriteByte('\n')
 | 
			
		||||
	}
 | 
			
		||||
	_, _ = buf.WriteString("}")
 | 
			
		||||
	return buf.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrSyntax indicates an argument does not represent a valid mode.
 | 
			
		||||
var ErrSyntax = errors.New("invalid syntax")
 | 
			
		||||
 | 
			
		||||
// Apply changes the provided os.FileMode based on the given umask and
 | 
			
		||||
// absolute or symbolic mode value.
 | 
			
		||||
//
 | 
			
		||||
// Apply is a convience to calling ParseWithUmask followed by Apply.
 | 
			
		||||
// Since it needs to parse the mode value string on each call it
 | 
			
		||||
// should only be used when mode value string will not be reapplied.
 | 
			
		||||
func Apply(s string, perm os.FileMode, umask uint) (os.FileMode, error) {
 | 
			
		||||
	set, err := ParseWithUmask(s, umask)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	return set.Apply(perm), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse takes an absolute (octal) or symbolic mode value,
 | 
			
		||||
// as described in chmod(1), as an argument and returns
 | 
			
		||||
// the set of bit operations representing the mode value
 | 
			
		||||
// that can be applied to specific os.FileMode values.
 | 
			
		||||
//
 | 
			
		||||
// Same as ParseWithUmask(s, 0).
 | 
			
		||||
func Parse(s string) (Set, error) {
 | 
			
		||||
	return ParseWithUmask(s, 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO(dchapes): A Set.Parse method that reuses existing memory.
 | 
			
		||||
 | 
			
		||||
// TODO(dchapes): Only call syscall.Umask when abosolutely necessary and
 | 
			
		||||
// provide a Set method to query if set is umask dependant (and perhaps
 | 
			
		||||
// the umask that was in effect when parsed).
 | 
			
		||||
 | 
			
		||||
// ParseWithUmask is like Parse but uses the provided
 | 
			
		||||
// file creation mask instead of calling syscall.Umask.
 | 
			
		||||
func ParseWithUmask(s string, umask uint) (Set, error) {
 | 
			
		||||
	var m Set
 | 
			
		||||
	if s == "" {
 | 
			
		||||
		return m, ErrSyntax
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If an absolute number, get it and return;
 | 
			
		||||
	// disallow non-octal digits or illegal bits.
 | 
			
		||||
	if d := s[0]; '0' <= d && d <= '9' {
 | 
			
		||||
		v, err := strconv.ParseInt(s, 8, 16)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return m, err
 | 
			
		||||
		}
 | 
			
		||||
		if v&^(standardBits|isTXT) != 0 {
 | 
			
		||||
			return m, ErrSyntax
 | 
			
		||||
		}
 | 
			
		||||
		// We know this takes exactly two bitcmds.
 | 
			
		||||
		m.cmds = make([]bitcmd, 0, 2)
 | 
			
		||||
		m.addcmd('=', standardBits|isTXT, modet(v), 0)
 | 
			
		||||
		return m, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get a copy of the mask for the permissions that are mask relative.
 | 
			
		||||
	// Flip the bits, we want what's not set.
 | 
			
		||||
	var mask modet = ^modet(umask)
 | 
			
		||||
 | 
			
		||||
	// Pre-allocate room for several commands.
 | 
			
		||||
	//m.cmds = make([]bitcmd, 0, 8)
 | 
			
		||||
 | 
			
		||||
	// Build list of bitcmd structs to set/clear/copy bits as described by
 | 
			
		||||
	// each clause of the symbolic mode.
 | 
			
		||||
	equalOpDone := false
 | 
			
		||||
	for {
 | 
			
		||||
		// First, find out which bits might be modified.
 | 
			
		||||
		var who modet
 | 
			
		||||
	whoLoop:
 | 
			
		||||
		for {
 | 
			
		||||
			if len(s) == 0 {
 | 
			
		||||
				return Set{}, ErrSyntax
 | 
			
		||||
			}
 | 
			
		||||
			switch s[0] {
 | 
			
		||||
			case 'a':
 | 
			
		||||
				who |= standardBits
 | 
			
		||||
			case 'u':
 | 
			
		||||
				who |= isUID | iRWXU
 | 
			
		||||
			case 'g':
 | 
			
		||||
				who |= isGID | iRWXG
 | 
			
		||||
			case 'o':
 | 
			
		||||
				who |= iRWXO
 | 
			
		||||
			default:
 | 
			
		||||
				break whoLoop
 | 
			
		||||
			}
 | 
			
		||||
			s = s[1:]
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var op byte
 | 
			
		||||
	getop:
 | 
			
		||||
		op, s = s[0], s[1:]
 | 
			
		||||
		switch op {
 | 
			
		||||
		case '+', '-':
 | 
			
		||||
			// Nothing.
 | 
			
		||||
		case '=':
 | 
			
		||||
			equalOpDone = false
 | 
			
		||||
		default:
 | 
			
		||||
			return Set{}, ErrSyntax
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		who &^= isTXT
 | 
			
		||||
	permLoop:
 | 
			
		||||
		for perm, permX := modet(0), modet(0); ; s = s[1:] {
 | 
			
		||||
			var b byte
 | 
			
		||||
			if len(s) > 0 {
 | 
			
		||||
				b = s[0]
 | 
			
		||||
			}
 | 
			
		||||
			switch b {
 | 
			
		||||
			case 'r':
 | 
			
		||||
				perm |= iRUser | iRGroup | iROther
 | 
			
		||||
			case 's':
 | 
			
		||||
				// If only "other" bits ignore set-id.
 | 
			
		||||
				if who == 0 || who&^iRWXO != 0 {
 | 
			
		||||
					perm |= isUID | isGID
 | 
			
		||||
				}
 | 
			
		||||
			case 't':
 | 
			
		||||
				// If only "other bits ignore sticky.
 | 
			
		||||
				if who == 0 || who&^iRWXO != 0 {
 | 
			
		||||
					who |= isTXT
 | 
			
		||||
					perm |= isTXT
 | 
			
		||||
				}
 | 
			
		||||
			case 'w':
 | 
			
		||||
				perm |= iWUser | iWGroup | iWOther
 | 
			
		||||
			case 'X':
 | 
			
		||||
				if op == '+' {
 | 
			
		||||
					permX = iXUser | iXGroup | iXOther
 | 
			
		||||
				}
 | 
			
		||||
			case 'x':
 | 
			
		||||
				perm |= iXUser | iXGroup | iXOther
 | 
			
		||||
			case 'u', 'g', 'o':
 | 
			
		||||
				// Whenever we hit 'u', 'g', or 'o', we have
 | 
			
		||||
				// to flush out any partial mode that we have,
 | 
			
		||||
				// and then do the copying of the mode bits.
 | 
			
		||||
				if perm != 0 {
 | 
			
		||||
					m.addcmd(op, who, perm, mask)
 | 
			
		||||
					perm = 0
 | 
			
		||||
				}
 | 
			
		||||
				if op == '=' {
 | 
			
		||||
					equalOpDone = true
 | 
			
		||||
				}
 | 
			
		||||
				if permX != 0 {
 | 
			
		||||
					m.addcmd('X', who, permX, mask)
 | 
			
		||||
					permX = 0
 | 
			
		||||
				}
 | 
			
		||||
				m.addcmd(b, who, modet(op), mask)
 | 
			
		||||
			default:
 | 
			
		||||
				// Add any permissions that we haven't alread done.
 | 
			
		||||
				if perm != 0 || op == '=' && !equalOpDone {
 | 
			
		||||
					if op == '=' {
 | 
			
		||||
						equalOpDone = true
 | 
			
		||||
					}
 | 
			
		||||
					m.addcmd(op, who, perm, mask)
 | 
			
		||||
					//perm = 0
 | 
			
		||||
				}
 | 
			
		||||
				if permX != 0 {
 | 
			
		||||
					m.addcmd('X', who, permX, mask)
 | 
			
		||||
					//permX = 0
 | 
			
		||||
				}
 | 
			
		||||
				break permLoop
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if s == "" {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if s[0] != ',' {
 | 
			
		||||
			goto getop
 | 
			
		||||
		}
 | 
			
		||||
		s = s[1:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m.compress()
 | 
			
		||||
	return m, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Apply returns the os.FileMode after applying the set of changes.
 | 
			
		||||
func (s Set) Apply(perm os.FileMode) os.FileMode {
 | 
			
		||||
	omode := fileModeToBits(perm)
 | 
			
		||||
	newmode := omode
 | 
			
		||||
 | 
			
		||||
	// When copying the user, group or other bits around, we "know"
 | 
			
		||||
	// where the bits are in the mode so that we can do shifts to
 | 
			
		||||
	// copy them around.  If we don't use shifts, it gets real
 | 
			
		||||
	// grundgy with lots of single bit checks and bit sets.
 | 
			
		||||
	common := func(c bitcmd, value modet) {
 | 
			
		||||
		if c.cmd2&cmd2Clear != 0 {
 | 
			
		||||
			var clrval modet
 | 
			
		||||
			if c.cmd2&cmd2Set != 0 {
 | 
			
		||||
				clrval = iRWXO
 | 
			
		||||
			} else {
 | 
			
		||||
				clrval = value
 | 
			
		||||
			}
 | 
			
		||||
			if c.cmd2&cmd2UBits != 0 {
 | 
			
		||||
				newmode &^= clrval << 6 & c.bits
 | 
			
		||||
			}
 | 
			
		||||
			if c.cmd2&cmd2GBits != 0 {
 | 
			
		||||
				newmode &^= clrval << 3 & c.bits
 | 
			
		||||
			}
 | 
			
		||||
			if c.cmd2&cmd2OBits != 0 {
 | 
			
		||||
				newmode &^= clrval & c.bits
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if c.cmd2&cmd2Set != 0 {
 | 
			
		||||
			if c.cmd2&cmd2UBits != 0 {
 | 
			
		||||
				newmode |= value << 6 & c.bits
 | 
			
		||||
			}
 | 
			
		||||
			if c.cmd2&cmd2GBits != 0 {
 | 
			
		||||
				newmode |= value << 3 & c.bits
 | 
			
		||||
			}
 | 
			
		||||
			if c.cmd2&cmd2OBits != 0 {
 | 
			
		||||
				newmode |= value & c.bits
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, c := range s.cmds {
 | 
			
		||||
		switch c.cmd {
 | 
			
		||||
		case 'u':
 | 
			
		||||
			common(c, newmode&iRWXU>>6)
 | 
			
		||||
		case 'g':
 | 
			
		||||
			common(c, newmode&iRWXG>>3)
 | 
			
		||||
		case 'o':
 | 
			
		||||
			common(c, newmode&iRWXO)
 | 
			
		||||
 | 
			
		||||
		case '+':
 | 
			
		||||
			newmode |= c.bits
 | 
			
		||||
		case '-':
 | 
			
		||||
			newmode &^= c.bits
 | 
			
		||||
 | 
			
		||||
		case 'X':
 | 
			
		||||
			if omode&(iXUser|iXGroup|iXOther) != 0 || perm.IsDir() {
 | 
			
		||||
				newmode |= c.bits
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return bitsToFileMode(perm, newmode)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Chmod is a convience routine that applies the changes in
 | 
			
		||||
// Set to the named file. To avoid some race conditions,
 | 
			
		||||
// it opens the file and uses os.File.Stat and
 | 
			
		||||
// os.File.Chmod rather than os.Stat and os.Chmod if possible.
 | 
			
		||||
func (s *Set) Chmod(name string) (old, new os.FileMode, err error) {
 | 
			
		||||
	if f, err := os.Open(name); err == nil { // nolint: vetshadow
 | 
			
		||||
		defer f.Close() // nolint: errcheck
 | 
			
		||||
		return s.ChmodFile(f)
 | 
			
		||||
	}
 | 
			
		||||
	// Fallback to os.Stat and os.Chmod if we
 | 
			
		||||
	// don't have permission to open the file.
 | 
			
		||||
	fi, err := os.Stat(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, 0, err
 | 
			
		||||
	}
 | 
			
		||||
	old = fi.Mode()
 | 
			
		||||
	new = s.Apply(old)
 | 
			
		||||
	if new != old {
 | 
			
		||||
		err = os.Chmod(name, new)
 | 
			
		||||
	}
 | 
			
		||||
	return old, new, err
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ChmodFile is a convience routine that applies
 | 
			
		||||
// the changes in Set to the open file f.
 | 
			
		||||
func (s *Set) ChmodFile(f *os.File) (old, new os.FileMode, err error) {
 | 
			
		||||
	fi, err := f.Stat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, 0, err
 | 
			
		||||
	}
 | 
			
		||||
	old = fi.Mode()
 | 
			
		||||
	new = s.Apply(old)
 | 
			
		||||
	if new != old {
 | 
			
		||||
		err = f.Chmod(new)
 | 
			
		||||
	}
 | 
			
		||||
	return old, new, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Set) addcmd(op byte, who, oparg, mask modet) {
 | 
			
		||||
	c := bitcmd{}
 | 
			
		||||
	switch op {
 | 
			
		||||
	case '=':
 | 
			
		||||
		c.cmd = '-'
 | 
			
		||||
		if who != 0 {
 | 
			
		||||
			c.bits = who
 | 
			
		||||
		} else {
 | 
			
		||||
			c.bits = standardBits
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.cmds = append(s.cmds, c)
 | 
			
		||||
		//c = bitcmd{} // reset, not actually needed
 | 
			
		||||
		op = '+'
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case '+', '-', 'X':
 | 
			
		||||
		c.cmd = op
 | 
			
		||||
		if who != 0 {
 | 
			
		||||
			c.bits = who & oparg
 | 
			
		||||
		} else {
 | 
			
		||||
			c.bits = mask & oparg
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case 'u', 'g', 'o':
 | 
			
		||||
		c.cmd = op
 | 
			
		||||
		if who != 0 {
 | 
			
		||||
			if who&iRUser != 0 {
 | 
			
		||||
				c.cmd2 |= cmd2UBits
 | 
			
		||||
			}
 | 
			
		||||
			if who&iRGroup != 0 {
 | 
			
		||||
				c.cmd2 |= cmd2GBits
 | 
			
		||||
			}
 | 
			
		||||
			if who&iROther != 0 {
 | 
			
		||||
				c.cmd2 |= cmd2OBits
 | 
			
		||||
			}
 | 
			
		||||
			c.bits = ^modet(0)
 | 
			
		||||
		} else {
 | 
			
		||||
			c.cmd2 = cmd2UBits | cmd2GBits | cmd2OBits
 | 
			
		||||
			c.bits = mask
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch oparg {
 | 
			
		||||
		case '+':
 | 
			
		||||
			c.cmd2 |= cmd2Set
 | 
			
		||||
		case '-':
 | 
			
		||||
			c.cmd2 |= cmd2Clear
 | 
			
		||||
		case '=':
 | 
			
		||||
			c.cmd2 |= cmd2Set | cmd2Clear
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		panic("unreachable")
 | 
			
		||||
	}
 | 
			
		||||
	s.cmds = append(s.cmds, c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// compress by compacting consecutive '+', '-' and 'X'
 | 
			
		||||
// commands into at most 3 commands, one of each. The 'u',
 | 
			
		||||
// 'g' and 'o' commands continue to be separate. They could
 | 
			
		||||
// probably be compacted, but it's not worth the effort.
 | 
			
		||||
func (s *Set) compress() {
 | 
			
		||||
	//log.Println("before:", *m)
 | 
			
		||||
	//log.Println("Start compress:")
 | 
			
		||||
	j := 0
 | 
			
		||||
	for i := 0; i < len(s.cmds); i++ {
 | 
			
		||||
		c := s.cmds[i]
 | 
			
		||||
		//log.Println(" read", i, c)
 | 
			
		||||
		if strings.IndexByte("+-X", c.cmd) < 0 {
 | 
			
		||||
			// Copy over any 'u', 'g', and 'o' commands.
 | 
			
		||||
			if i != j {
 | 
			
		||||
				s.cmds[j] = c
 | 
			
		||||
			}
 | 
			
		||||
			//log.Println(" wrote", j, "from", i)
 | 
			
		||||
			j++
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		var setbits, clrbits, Xbits modet
 | 
			
		||||
		for ; i < len(s.cmds); i++ {
 | 
			
		||||
			c = s.cmds[i]
 | 
			
		||||
			//log.Println(" scan", i, c)
 | 
			
		||||
			switch c.cmd {
 | 
			
		||||
			case '-':
 | 
			
		||||
				clrbits |= c.bits
 | 
			
		||||
				setbits &^= c.bits
 | 
			
		||||
				Xbits &^= c.bits
 | 
			
		||||
				continue
 | 
			
		||||
			case '+':
 | 
			
		||||
				setbits |= c.bits
 | 
			
		||||
				clrbits &^= c.bits
 | 
			
		||||
				Xbits &^= c.bits
 | 
			
		||||
				continue
 | 
			
		||||
			case 'X':
 | 
			
		||||
				Xbits |= c.bits &^ setbits
 | 
			
		||||
				continue
 | 
			
		||||
			default:
 | 
			
		||||
				i--
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if clrbits != 0 {
 | 
			
		||||
			s.cmds[j].cmd = '-'
 | 
			
		||||
			s.cmds[j].cmd2 = 0
 | 
			
		||||
			s.cmds[j].bits = clrbits
 | 
			
		||||
			//log.Println(" wrote", j, "clrbits")
 | 
			
		||||
			j++
 | 
			
		||||
		}
 | 
			
		||||
		if setbits != 0 {
 | 
			
		||||
			s.cmds[j].cmd = '+'
 | 
			
		||||
			s.cmds[j].cmd2 = 0
 | 
			
		||||
			s.cmds[j].bits = setbits
 | 
			
		||||
			//log.Println(" wrote", j, "setbits")
 | 
			
		||||
			j++
 | 
			
		||||
		}
 | 
			
		||||
		if Xbits != 0 {
 | 
			
		||||
			s.cmds[j].cmd = 'X'
 | 
			
		||||
			s.cmds[j].cmd2 = 0
 | 
			
		||||
			s.cmds[j].bits = Xbits
 | 
			
		||||
			//log.Println(" wrote", j, "Xbits")
 | 
			
		||||
			j++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	/*
 | 
			
		||||
		if len(m.cmds) != j {
 | 
			
		||||
			log.Println("compressed", len(m.cmds), "down to", j)
 | 
			
		||||
		}
 | 
			
		||||
	*/
 | 
			
		||||
	s.cmds = s.cmds[:j]
 | 
			
		||||
	//log.Println("after:", *m)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										691
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										691
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,691 @@
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/continuity/fs"
 | 
			
		||||
	"github.com/moby/patternmatcher"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	mode "github.com/tonistiigi/dchapes-mode"
 | 
			
		||||
	"github.com/tonistiigi/fsutil"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var bufferPool = &sync.Pool{
 | 
			
		||||
	New: func() interface{} {
 | 
			
		||||
		buffer := make([]byte, 32*1024)
 | 
			
		||||
		return &buffer
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func rootPath(root, p string, followLinks bool) (string, error) {
 | 
			
		||||
	p = filepath.Join("/", p)
 | 
			
		||||
	if p == "/" {
 | 
			
		||||
		return root, nil
 | 
			
		||||
	}
 | 
			
		||||
	if followLinks {
 | 
			
		||||
		return fs.RootPath(root, p)
 | 
			
		||||
	}
 | 
			
		||||
	d, f := filepath.Split(p)
 | 
			
		||||
	ppath, err := fs.RootPath(root, d)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return filepath.Join(ppath, f), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ResolveWildcards(root, src string, followLinks bool) ([]string, error) {
 | 
			
		||||
	d1, d2 := splitWildcards(src)
 | 
			
		||||
	if d2 != "" {
 | 
			
		||||
		p, err := rootPath(root, d1, followLinks)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		matches, err := resolveWildcards(p, d2)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		for i, m := range matches {
 | 
			
		||||
			p, err := rel(root, m)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			matches[i] = p
 | 
			
		||||
		}
 | 
			
		||||
		return matches, nil
 | 
			
		||||
	}
 | 
			
		||||
	return []string{d1}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Copy copies files using `cp -a` semantics.
 | 
			
		||||
// Copy is likely unsafe to be used in non-containerized environments.
 | 
			
		||||
func Copy(ctx context.Context, srcRoot, src, dstRoot, dst string, opts ...Opt) error {
 | 
			
		||||
	var ci CopyInfo
 | 
			
		||||
	for _, o := range opts {
 | 
			
		||||
		o(&ci)
 | 
			
		||||
	}
 | 
			
		||||
	ensureDstPath := dst
 | 
			
		||||
	if d, f := filepath.Split(dst); f != "" && f != "." {
 | 
			
		||||
		ensureDstPath = d
 | 
			
		||||
	}
 | 
			
		||||
	if ensureDstPath != "" {
 | 
			
		||||
		ensureDstPath, err := fs.RootPath(dstRoot, ensureDstPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := MkdirAll(ensureDstPath, 0755, ci.Chown, ci.Utime); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var modeSet *mode.Set
 | 
			
		||||
	if ci.ModeStr != "" {
 | 
			
		||||
		ms, err := mode.ParseWithUmask(ci.ModeStr, 0)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		modeSet = &ms
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dst, err := fs.RootPath(dstRoot, filepath.Clean(dst))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c, err := newCopier(dstRoot, ci.Chown, ci.Utime, ci.Mode, modeSet, ci.XAttrErrorHandler, ci.IncludePatterns, ci.ExcludePatterns, ci.AlwaysReplaceExistingDestPaths, ci.ChangeFunc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	srcs := []string{src}
 | 
			
		||||
 | 
			
		||||
	if ci.AllowWildcards {
 | 
			
		||||
		matches, err := ResolveWildcards(srcRoot, src, ci.FollowLinks)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if len(matches) == 0 {
 | 
			
		||||
			return errors.Errorf("no matches found: %s", src)
 | 
			
		||||
		}
 | 
			
		||||
		srcs = matches
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, src := range srcs {
 | 
			
		||||
		srcFollowed, err := rootPath(srcRoot, src, ci.FollowLinks)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		dst, err := c.prepareTargetDir(srcFollowed, src, dst, ci.CopyDirContents)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := c.copy(ctx, srcFollowed, "", dst, false, patternmatcher.MatchInfo{}, patternmatcher.MatchInfo{}); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) prepareTargetDir(srcFollowed, src, destPath string, copyDirContents bool) (string, error) {
 | 
			
		||||
	fiSrc, err := os.Lstat(srcFollowed)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fiDest, err := os.Stat(destPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return "", errors.Wrap(err, "failed to lstat destination path")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!copyDirContents && fiSrc.IsDir() && fiDest != nil) || (!fiSrc.IsDir() && fiDest != nil && fiDest.IsDir()) {
 | 
			
		||||
		destPath = filepath.Join(destPath, filepath.Base(src))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := filepath.Dir(destPath)
 | 
			
		||||
 | 
			
		||||
	if copyDirContents && fiSrc.IsDir() && fiDest == nil {
 | 
			
		||||
		target = destPath
 | 
			
		||||
	}
 | 
			
		||||
	if err := MkdirAll(target, 0755, c.chown, c.utime); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return destPath, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type User struct {
 | 
			
		||||
	UID, GID int
 | 
			
		||||
	SID      string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Chowner func(*User) (*User, error)
 | 
			
		||||
 | 
			
		||||
type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
 | 
			
		||||
 | 
			
		||||
type CopyInfo struct {
 | 
			
		||||
	Chown          Chowner
 | 
			
		||||
	Utime          *time.Time
 | 
			
		||||
	AllowWildcards bool
 | 
			
		||||
	Mode           *int
 | 
			
		||||
	// ModeStr is mode in non-octal format. Overrides Mode if non-empty.
 | 
			
		||||
	ModeStr           string
 | 
			
		||||
	XAttrErrorHandler XAttrErrorHandler
 | 
			
		||||
	CopyDirContents   bool
 | 
			
		||||
	FollowLinks       bool
 | 
			
		||||
	// Include only files/dirs matching at least one of these patterns
 | 
			
		||||
	IncludePatterns []string
 | 
			
		||||
	// Exclude files/dir matching any of these patterns (even if they match an include pattern)
 | 
			
		||||
	ExcludePatterns []string
 | 
			
		||||
	// If true, any source path that overwrite existing destination paths will always replace
 | 
			
		||||
	// the existing destination path, even if they are of different types (e.g. a directory will
 | 
			
		||||
	// replace any existing symlink or file)
 | 
			
		||||
	AlwaysReplaceExistingDestPaths bool
 | 
			
		||||
	ChangeFunc                     fsutil.ChangeFunc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Opt func(*CopyInfo)
 | 
			
		||||
 | 
			
		||||
func WithCopyInfo(ci CopyInfo) func(*CopyInfo) {
 | 
			
		||||
	return func(c *CopyInfo) {
 | 
			
		||||
		*c = ci
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WithChown(uid, gid int) Opt {
 | 
			
		||||
	return func(ci *CopyInfo) {
 | 
			
		||||
		ci.Chown = func(*User) (*User, error) {
 | 
			
		||||
			return &User{UID: uid, GID: gid}, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func AllowWildcards(ci *CopyInfo) {
 | 
			
		||||
	ci.AllowWildcards = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WithXAttrErrorHandler(h XAttrErrorHandler) Opt {
 | 
			
		||||
	return func(ci *CopyInfo) {
 | 
			
		||||
		ci.XAttrErrorHandler = h
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func AllowXAttrErrors(ci *CopyInfo) {
 | 
			
		||||
	h := func(string, string, string, error) error {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	WithXAttrErrorHandler(h)(ci)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WithIncludePattern(includePattern string) Opt {
 | 
			
		||||
	return func(ci *CopyInfo) {
 | 
			
		||||
		ci.IncludePatterns = append(ci.IncludePatterns, includePattern)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WithExcludePattern(excludePattern string) Opt {
 | 
			
		||||
	return func(ci *CopyInfo) {
 | 
			
		||||
		ci.ExcludePatterns = append(ci.ExcludePatterns, excludePattern)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WithChangeNotifier(fn fsutil.ChangeFunc) Opt {
 | 
			
		||||
	return func(ci *CopyInfo) {
 | 
			
		||||
		ci.ChangeFunc = fn
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type copier struct {
 | 
			
		||||
	chown                          Chowner
 | 
			
		||||
	utime                          *time.Time
 | 
			
		||||
	mode                           *int
 | 
			
		||||
	modeSet                        *mode.Set
 | 
			
		||||
	inodes                         map[uint64]string
 | 
			
		||||
	xattrErrorHandler              XAttrErrorHandler
 | 
			
		||||
	includePatternMatcher          *patternmatcher.PatternMatcher
 | 
			
		||||
	excludePatternMatcher          *patternmatcher.PatternMatcher
 | 
			
		||||
	parentDirs                     []parentDir
 | 
			
		||||
	changefn                       fsutil.ChangeFunc
 | 
			
		||||
	root                           string
 | 
			
		||||
	alwaysReplaceExistingDestPaths bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type parentDir struct {
 | 
			
		||||
	srcPath string
 | 
			
		||||
	dstPath string
 | 
			
		||||
	copied  bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newCopier(root string, chown Chowner, tm *time.Time, mode *int, modeSet *mode.Set, xeh XAttrErrorHandler, includePatterns, excludePatterns []string, alwaysReplaceExistingDestPaths bool, changeFunc fsutil.ChangeFunc) (*copier, error) {
 | 
			
		||||
	if xeh == nil {
 | 
			
		||||
		xeh = func(dst, src, key string, err error) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var includePatternMatcher *patternmatcher.PatternMatcher
 | 
			
		||||
	if len(includePatterns) != 0 {
 | 
			
		||||
		var err error
 | 
			
		||||
		includePatternMatcher, err = patternmatcher.New(includePatterns)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrapf(err, "invalid includepatterns: %s", includePatterns)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var excludePatternMatcher *patternmatcher.PatternMatcher
 | 
			
		||||
	if len(excludePatterns) != 0 {
 | 
			
		||||
		var err error
 | 
			
		||||
		excludePatternMatcher, err = patternmatcher.New(excludePatterns)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrapf(err, "invalid excludepatterns: %s", excludePatterns)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &copier{
 | 
			
		||||
		root:                           root,
 | 
			
		||||
		inodes:                         map[uint64]string{},
 | 
			
		||||
		chown:                          chown,
 | 
			
		||||
		utime:                          tm,
 | 
			
		||||
		xattrErrorHandler:              xeh,
 | 
			
		||||
		mode:                           mode,
 | 
			
		||||
		modeSet:                        modeSet,
 | 
			
		||||
		includePatternMatcher:          includePatternMatcher,
 | 
			
		||||
		excludePatternMatcher:          excludePatternMatcher,
 | 
			
		||||
		changefn:                       changeFunc,
 | 
			
		||||
		alwaysReplaceExistingDestPaths: alwaysReplaceExistingDestPaths,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// dest is always clean
 | 
			
		||||
func (c *copier) copy(ctx context.Context, src, srcComponents, target string, overwriteTargetMetadata bool, parentIncludeMatchInfo, parentExcludeMatchInfo patternmatcher.MatchInfo) error {
 | 
			
		||||
	select {
 | 
			
		||||
	case <-ctx.Done():
 | 
			
		||||
		return ctx.Err()
 | 
			
		||||
	default:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fi, err := os.Lstat(src)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to stat %s", src)
 | 
			
		||||
	}
 | 
			
		||||
	targetFi, err := os.Lstat(target)
 | 
			
		||||
	if err != nil && !os.IsNotExist(err) {
 | 
			
		||||
		return errors.Wrapf(err, "failed to stat %s", src)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	include := true
 | 
			
		||||
	var (
 | 
			
		||||
		includeMatchInfo patternmatcher.MatchInfo
 | 
			
		||||
		excludeMatchInfo patternmatcher.MatchInfo
 | 
			
		||||
	)
 | 
			
		||||
	if srcComponents != "" {
 | 
			
		||||
		matchesIncludePattern := false
 | 
			
		||||
		matchesExcludePattern := false
 | 
			
		||||
		matchesIncludePattern, includeMatchInfo, err = c.include(srcComponents, fi, parentIncludeMatchInfo)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		include = matchesIncludePattern
 | 
			
		||||
 | 
			
		||||
		matchesExcludePattern, excludeMatchInfo, err = c.exclude(srcComponents, fi, parentExcludeMatchInfo)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if matchesExcludePattern {
 | 
			
		||||
			include = false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if include {
 | 
			
		||||
		if err := c.removeTargetIfNeeded(src, target, fi, targetFi); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := c.createParentDirs(src, srcComponents, target, overwriteTargetMetadata); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !fi.IsDir() {
 | 
			
		||||
		if !include {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := ensureEmptyFileTarget(target); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	copyFileInfo := include
 | 
			
		||||
	restoreFileTimestamp := false
 | 
			
		||||
	notify := true
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case fi.IsDir():
 | 
			
		||||
		if created, err := c.copyDirectory(
 | 
			
		||||
			ctx, src, srcComponents, target, fi, overwriteTargetMetadata,
 | 
			
		||||
			include, includeMatchInfo, excludeMatchInfo,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		} else if !overwriteTargetMetadata {
 | 
			
		||||
			// if we aren't supposed to overwrite existing target metadata,
 | 
			
		||||
			// then we only need to copy the new file info if we newly created
 | 
			
		||||
			// it, or restore the previous file timestamp if not
 | 
			
		||||
			copyFileInfo = created
 | 
			
		||||
			restoreFileTimestamp = !created
 | 
			
		||||
		}
 | 
			
		||||
		notify = false
 | 
			
		||||
	case (fi.Mode() & os.ModeType) == 0:
 | 
			
		||||
		link, err := getLinkSource(target, fi, c.inodes)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "failed to get hardlink")
 | 
			
		||||
		}
 | 
			
		||||
		if link != "" {
 | 
			
		||||
			if err := os.Link(link, target); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "failed to create hard link")
 | 
			
		||||
			}
 | 
			
		||||
		} else if err := copyFile(src, target); err != nil {
 | 
			
		||||
			return errors.Wrap(err, "failed to copy files")
 | 
			
		||||
		}
 | 
			
		||||
	case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
 | 
			
		||||
		link, err := os.Readlink(src)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "failed to read link: %s", src)
 | 
			
		||||
		}
 | 
			
		||||
		if err := os.Symlink(link, target); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "failed to create symlink: %s", target)
 | 
			
		||||
		}
 | 
			
		||||
	case (fi.Mode() & os.ModeDevice) == os.ModeDevice,
 | 
			
		||||
		(fi.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe,
 | 
			
		||||
		(fi.Mode() & os.ModeSocket) == os.ModeSocket:
 | 
			
		||||
		if err := copyDevice(target, fi); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "failed to create device")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if copyFileInfo {
 | 
			
		||||
		if err := c.copyFileInfo(fi, src, target); err != nil {
 | 
			
		||||
			return errors.Wrap(err, "failed to copy file info")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := copyXAttrs(target, src, c.xattrErrorHandler); err != nil {
 | 
			
		||||
			return errors.Wrap(err, "failed to copy xattrs")
 | 
			
		||||
		}
 | 
			
		||||
	} else if restoreFileTimestamp && targetFi != nil {
 | 
			
		||||
		if err := c.copyFileTimestamp(fi, target); err != nil {
 | 
			
		||||
			return errors.Wrap(err, "failed to restore file timestamp")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if notify {
 | 
			
		||||
		if err := c.notifyChange(target, fi); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) notifyChange(target string, fi os.FileInfo) error {
 | 
			
		||||
	if c.changefn != nil {
 | 
			
		||||
		if err := c.changefn(fsutil.ChangeKindAdd, path.Clean(strings.TrimPrefix(target, c.root)), fi, nil); err != nil {
 | 
			
		||||
			return errors.Wrap(err, "failed to notify file change")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) include(path string, fi os.FileInfo, parentIncludeMatchInfo patternmatcher.MatchInfo) (bool, patternmatcher.MatchInfo, error) {
 | 
			
		||||
	if c.includePatternMatcher == nil {
 | 
			
		||||
		return true, patternmatcher.MatchInfo{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m, matchInfo, err := c.includePatternMatcher.MatchesUsingParentResults(path, parentIncludeMatchInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, matchInfo, errors.Wrap(err, "failed to match includepatterns")
 | 
			
		||||
	}
 | 
			
		||||
	return m, matchInfo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) exclude(path string, fi os.FileInfo, parentExcludeMatchInfo patternmatcher.MatchInfo) (bool, patternmatcher.MatchInfo, error) {
 | 
			
		||||
	if c.excludePatternMatcher == nil {
 | 
			
		||||
		return false, patternmatcher.MatchInfo{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m, matchInfo, err := c.excludePatternMatcher.MatchesUsingParentResults(path, parentExcludeMatchInfo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, matchInfo, errors.Wrap(err, "failed to match excludepatterns")
 | 
			
		||||
	}
 | 
			
		||||
	return m, matchInfo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) removeTargetIfNeeded(src, target string, srcFi, targetFi os.FileInfo) error {
 | 
			
		||||
	if !c.alwaysReplaceExistingDestPaths {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if targetFi == nil {
 | 
			
		||||
		// already doesn't exist
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if srcFi.IsDir() && targetFi.IsDir() {
 | 
			
		||||
		// directories are merged, not replaced
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return os.RemoveAll(target)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delayed creation of parent directories when a file or dir matches an include
 | 
			
		||||
// pattern.
 | 
			
		||||
func (c *copier) createParentDirs(src, srcComponents, target string, overwriteTargetMetadata bool) error {
 | 
			
		||||
	for i, parentDir := range c.parentDirs {
 | 
			
		||||
		if parentDir.copied {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fi, err := os.Stat(parentDir.srcPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "failed to stat %s", src)
 | 
			
		||||
		}
 | 
			
		||||
		if !fi.IsDir() {
 | 
			
		||||
			return errors.Errorf("%s is not a directory", parentDir.srcPath)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		created, err := copyDirectoryOnly(parentDir.srcPath, parentDir.dstPath, fi, overwriteTargetMetadata)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if created {
 | 
			
		||||
			if err := c.copyFileInfo(fi, parentDir.srcPath, parentDir.dstPath); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "failed to copy file info")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := copyXAttrs(parentDir.dstPath, parentDir.srcPath, c.xattrErrorHandler); err != nil {
 | 
			
		||||
				return errors.Wrap(err, "failed to copy xattrs")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.parentDirs[i].copied = true
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) copyDirectory(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	src string,
 | 
			
		||||
	srcComponents string,
 | 
			
		||||
	dst string,
 | 
			
		||||
	stat os.FileInfo,
 | 
			
		||||
	overwriteTargetMetadata bool,
 | 
			
		||||
	include bool,
 | 
			
		||||
	includeMatchInfo patternmatcher.MatchInfo,
 | 
			
		||||
	excludeMatchInfo patternmatcher.MatchInfo,
 | 
			
		||||
) (bool, error) {
 | 
			
		||||
	if !stat.IsDir() {
 | 
			
		||||
		return false, errors.Errorf("source is not directory")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	created := false
 | 
			
		||||
 | 
			
		||||
	parentDir := parentDir{
 | 
			
		||||
		srcPath: src,
 | 
			
		||||
		dstPath: dst,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If this directory passed include/exclude matching directly, go ahead
 | 
			
		||||
	// and create the directory. Otherwise, delay to handle include
 | 
			
		||||
	// patterns like a/*/c where we do not want to create a/b until we
 | 
			
		||||
	// encounter a/b/c.
 | 
			
		||||
	if include {
 | 
			
		||||
		var err error
 | 
			
		||||
		created, err = copyDirectoryOnly(src, dst, stat, overwriteTargetMetadata)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return created, err
 | 
			
		||||
		}
 | 
			
		||||
		if created || overwriteTargetMetadata {
 | 
			
		||||
			if err := c.notifyChange(dst, stat); err != nil {
 | 
			
		||||
				return created, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		parentDir.copied = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.parentDirs = append(c.parentDirs, parentDir)
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		c.parentDirs = c.parentDirs[:len(c.parentDirs)-1]
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	fis, err := os.ReadDir(src)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, errors.Wrapf(err, "failed to read %s", src)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, fi := range fis {
 | 
			
		||||
		if err := c.copy(
 | 
			
		||||
			ctx,
 | 
			
		||||
			filepath.Join(src, fi.Name()), filepath.Join(srcComponents, fi.Name()),
 | 
			
		||||
			filepath.Join(dst, fi.Name()),
 | 
			
		||||
			true, includeMatchInfo, excludeMatchInfo,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return created, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyDirectoryOnly(src, dst string, stat os.FileInfo, overwriteTargetMetadata bool) (bool, error) {
 | 
			
		||||
	if st, err := os.Lstat(dst); err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		if err := os.Mkdir(dst, stat.Mode()); err != nil {
 | 
			
		||||
			return false, errors.Wrapf(err, "failed to mkdir %s", dst)
 | 
			
		||||
		}
 | 
			
		||||
		return true, nil
 | 
			
		||||
	} else if !st.IsDir() {
 | 
			
		||||
		return false, errors.Errorf("cannot copy to non-directory: %s", dst)
 | 
			
		||||
	} else if overwriteTargetMetadata {
 | 
			
		||||
		if err := os.Chmod(dst, stat.Mode()); err != nil {
 | 
			
		||||
			return false, errors.Wrapf(err, "failed to chmod on %s", dst)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ensureEmptyFileTarget(dst string) error {
 | 
			
		||||
	fi, err := os.Lstat(dst)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return errors.Wrap(err, "failed to lstat file target")
 | 
			
		||||
	}
 | 
			
		||||
	if fi.IsDir() {
 | 
			
		||||
		return errors.Errorf("cannot replace to directory %s with file", dst)
 | 
			
		||||
	}
 | 
			
		||||
	return os.Remove(dst)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func containsWildcards(name string) bool {
 | 
			
		||||
	isWindows := runtime.GOOS == "windows"
 | 
			
		||||
	for i := 0; i < len(name); i++ {
 | 
			
		||||
		ch := name[i]
 | 
			
		||||
		if ch == '\\' && !isWindows {
 | 
			
		||||
			i++
 | 
			
		||||
		} else if ch == '*' || ch == '?' || ch == '[' {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func splitWildcards(p string) (d1, d2 string) {
 | 
			
		||||
	parts := strings.Split(filepath.Join(p), string(filepath.Separator))
 | 
			
		||||
	var p1, p2 []string
 | 
			
		||||
	var found bool
 | 
			
		||||
	for _, p := range parts {
 | 
			
		||||
		if !found && containsWildcards(p) {
 | 
			
		||||
			found = true
 | 
			
		||||
		}
 | 
			
		||||
		if p == "" {
 | 
			
		||||
			p = "/"
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			p1 = append(p1, p)
 | 
			
		||||
		} else {
 | 
			
		||||
			p2 = append(p2, p)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return filepath.Join(p1...), filepath.Join(p2...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func resolveWildcards(basePath, comp string) ([]string, error) {
 | 
			
		||||
	var out []string
 | 
			
		||||
	err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		rel, err := rel(basePath, path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if rel == "." {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if match, _ := filepath.Match(comp, rel); !match {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		out = append(out, path)
 | 
			
		||||
		if info.IsDir() {
 | 
			
		||||
			return filepath.SkipDir
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return out, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// rel makes a path relative to base path. Same as `filepath.Rel` but can also
 | 
			
		||||
// handle UUID paths in windows.
 | 
			
		||||
func rel(basepath, targpath string) (string, error) {
 | 
			
		||||
	// filepath.Rel can't handle UUID paths in windows
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		pfx := basepath + `\`
 | 
			
		||||
		if strings.HasPrefix(targpath, pfx) {
 | 
			
		||||
			p := strings.TrimPrefix(targpath, pfx)
 | 
			
		||||
			if p == "" {
 | 
			
		||||
				p = "."
 | 
			
		||||
			}
 | 
			
		||||
			return p, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return filepath.Rel(basepath, targpath)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_darwin.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_darwin.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
//go:build darwin
 | 
			
		||||
// +build darwin
 | 
			
		||||
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func copyFile(source, target string) error {
 | 
			
		||||
	if err := unix.Clonefileat(unix.AT_FDCWD, source, unix.AT_FDCWD, target, unix.CLONE_NOFOLLOW); err != nil {
 | 
			
		||||
		if err != unix.EINVAL && err != unix.EXDEV {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	src, err := os.Open(source)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to open source %s", source)
 | 
			
		||||
	}
 | 
			
		||||
	defer src.Close()
 | 
			
		||||
	tgt, err := os.Create(target)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to open target %s", target)
 | 
			
		||||
	}
 | 
			
		||||
	defer tgt.Close()
 | 
			
		||||
 | 
			
		||||
	return copyFileContent(tgt, src)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyFileContent(dst, src *os.File) error {
 | 
			
		||||
	buf := bufferPool.Get().(*[]byte)
 | 
			
		||||
	_, err := io.CopyBuffer(dst, src, *buf)
 | 
			
		||||
	bufferPool.Put(buf)
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mknod(dst string, mode uint32, rDev int) error {
 | 
			
		||||
	return unix.Mknod(dst, uint32(mode), rDev)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_freebsd.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_freebsd.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
//go:build freebsd
 | 
			
		||||
// +build freebsd
 | 
			
		||||
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func copyFile(source, target string) error {
 | 
			
		||||
	src, err := os.Open(source)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to open source %s", source)
 | 
			
		||||
	}
 | 
			
		||||
	defer src.Close()
 | 
			
		||||
	tgt, err := os.Create(target)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to open target %s", target)
 | 
			
		||||
	}
 | 
			
		||||
	defer tgt.Close()
 | 
			
		||||
 | 
			
		||||
	return copyFileContent(tgt, src)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyFileContent(dst, src *os.File) error {
 | 
			
		||||
	buf := bufferPool.Get().(*[]byte)
 | 
			
		||||
	_, err := io.CopyBuffer(dst, src, *buf)
 | 
			
		||||
	bufferPool.Put(buf)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mknod(dst string, mode uint32, rDev int) error {
 | 
			
		||||
	return unix.Mknod(dst, uint32(mode), uint64(rDev))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										129
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_linux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"math"
 | 
			
		||||
	"os"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getUIDGID(fi os.FileInfo) (uid, gid int) {
 | 
			
		||||
	st := fi.Sys().(*syscall.Stat_t)
 | 
			
		||||
	return int(st.Uid), int(st.Gid)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) copyFileInfo(fi os.FileInfo, src, name string) error {
 | 
			
		||||
	chown := c.chown
 | 
			
		||||
	uid, gid := getUIDGID(fi)
 | 
			
		||||
	old := &User{UID: uid, GID: gid}
 | 
			
		||||
	if chown == nil {
 | 
			
		||||
		chown = func(u *User) (*User, error) {
 | 
			
		||||
			return u, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := Chown(name, old, chown); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to chown %s", name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := fi.Mode()
 | 
			
		||||
	if c.modeSet != nil {
 | 
			
		||||
		m = c.modeSet.Apply(m)
 | 
			
		||||
	} else if c.mode != nil {
 | 
			
		||||
		m = os.FileMode(*c.mode).Perm()
 | 
			
		||||
		if *c.mode&syscall.S_ISGID != 0 {
 | 
			
		||||
			m |= os.ModeSetgid
 | 
			
		||||
		}
 | 
			
		||||
		if *c.mode&syscall.S_ISUID != 0 {
 | 
			
		||||
			m |= os.ModeSetuid
 | 
			
		||||
		}
 | 
			
		||||
		if *c.mode&syscall.S_ISVTX != 0 {
 | 
			
		||||
			m |= os.ModeSticky
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
 | 
			
		||||
		if err := os.Chmod(name, m); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "failed to chmod %s", name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := c.copyFileTimestamp(fi, name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) copyFileTimestamp(fi os.FileInfo, name string) error {
 | 
			
		||||
	if c.utime != nil {
 | 
			
		||||
		return Utimes(name, c.utime)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	st := fi.Sys().(*syscall.Stat_t)
 | 
			
		||||
	timespec := []unix.Timespec{unix.Timespec(StatAtime(st)), unix.Timespec(StatMtime(st))}
 | 
			
		||||
	if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to utime %s", name)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyFile(source, target string) error {
 | 
			
		||||
	src, err := os.Open(source)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to open source %s", source)
 | 
			
		||||
	}
 | 
			
		||||
	defer src.Close()
 | 
			
		||||
	tgt, err := os.Create(target)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to open target %s", target)
 | 
			
		||||
	}
 | 
			
		||||
	defer tgt.Close()
 | 
			
		||||
 | 
			
		||||
	return copyFileContent(tgt, src)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyFileContent(dst, src *os.File) error {
 | 
			
		||||
	st, err := src.Stat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "unable to stat source")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var written int64
 | 
			
		||||
	size := st.Size()
 | 
			
		||||
	first := true
 | 
			
		||||
 | 
			
		||||
	for written < size {
 | 
			
		||||
		var desired int
 | 
			
		||||
		if size-written > math.MaxInt32 {
 | 
			
		||||
			desired = int(math.MaxInt32)
 | 
			
		||||
		} else {
 | 
			
		||||
			desired = int(size - written)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		n, err := unix.CopyFileRange(int(src.Fd()), nil, int(dst.Fd()), nil, desired, 0)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// matches go/src/internal/poll/copy_file_range_linux.go
 | 
			
		||||
			if (err != unix.ENOSYS && err != unix.EXDEV && err != unix.EPERM && err != syscall.EIO && err != unix.EOPNOTSUPP && err != syscall.EINVAL) || !first {
 | 
			
		||||
				return errors.Wrap(err, "copy file range failed")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			buf := bufferPool.Get().(*[]byte)
 | 
			
		||||
			_, err = io.CopyBuffer(dst, src, *buf)
 | 
			
		||||
			bufferPool.Put(buf)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrap(err, "userspace copy failed")
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		first = false
 | 
			
		||||
		written += int64(n)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mknod(dst string, mode uint32, rDev int) error {
 | 
			
		||||
	return unix.Mknod(dst, uint32(mode), rDev)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_nowindows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_nowindows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
//go:build !windows
 | 
			
		||||
// +build !windows
 | 
			
		||||
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/continuity/sysx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// copyXAttrs requires xeh to be non-nil
 | 
			
		||||
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
 | 
			
		||||
	xattrKeys, err := sysx.LListxattr(src)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xeh(dst, src, "", errors.Wrapf(err, "failed to list xattrs on %s", src))
 | 
			
		||||
	}
 | 
			
		||||
	for _, xattr := range xattrKeys {
 | 
			
		||||
		data, err := sysx.LGetxattr(src, xattr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return xeh(dst, src, xattr, errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src))
 | 
			
		||||
		}
 | 
			
		||||
		if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
 | 
			
		||||
			return xeh(dst, src, xattr, errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyDevice(dst string, fi os.FileInfo) error {
 | 
			
		||||
	st, ok := fi.Sys().(*syscall.Stat_t)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return errors.New("unsupported stat type")
 | 
			
		||||
	}
 | 
			
		||||
	var rDev int
 | 
			
		||||
	if fi.Mode()&os.ModeDevice == os.ModeDevice || fi.Mode()&os.ModeCharDevice == os.ModeCharDevice {
 | 
			
		||||
		rDev = int(st.Rdev)
 | 
			
		||||
	}
 | 
			
		||||
	mode := st.Mode
 | 
			
		||||
	mode &^= syscall.S_IFSOCK // socket copied as stub
 | 
			
		||||
	return mknod(dst, uint32(mode), rDev)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
//go:build solaris || darwin || freebsd
 | 
			
		||||
// +build solaris darwin freebsd
 | 
			
		||||
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getUIDGID(fi os.FileInfo) (uid, gid int) {
 | 
			
		||||
	st := fi.Sys().(*syscall.Stat_t)
 | 
			
		||||
	return int(st.Uid), int(st.Gid)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) copyFileInfo(fi os.FileInfo, src, name string) error {
 | 
			
		||||
	chown := c.chown
 | 
			
		||||
	uid, gid := getUIDGID(fi)
 | 
			
		||||
	old := &User{UID: uid, GID: gid}
 | 
			
		||||
	if chown == nil {
 | 
			
		||||
		chown = func(u *User) (*User, error) {
 | 
			
		||||
			return u, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := Chown(name, old, chown); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to chown %s", name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := fi.Mode()
 | 
			
		||||
	if c.modeSet != nil {
 | 
			
		||||
		m = c.modeSet.Apply(m)
 | 
			
		||||
	} else if c.mode != nil {
 | 
			
		||||
		m = os.FileMode(*c.mode).Perm()
 | 
			
		||||
		if *c.mode&syscall.S_ISGID != 0 {
 | 
			
		||||
			m |= os.ModeSetgid
 | 
			
		||||
		}
 | 
			
		||||
		if *c.mode&syscall.S_ISUID != 0 {
 | 
			
		||||
			m |= os.ModeSetuid
 | 
			
		||||
		}
 | 
			
		||||
		if *c.mode&syscall.S_ISVTX != 0 {
 | 
			
		||||
			m |= os.ModeSticky
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
 | 
			
		||||
		if err := os.Chmod(name, m); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "failed to chmod %s", name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := c.copyFileTimestamp(fi, name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) copyFileTimestamp(fi os.FileInfo, name string) error {
 | 
			
		||||
	if c.utime != nil {
 | 
			
		||||
		return Utimes(name, c.utime)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	st := fi.Sys().(*syscall.Stat_t)
 | 
			
		||||
	timespec := []unix.Timespec{unix.Timespec(StatAtime(st)), unix.Timespec(StatMtime(st))}
 | 
			
		||||
	if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to utime %s", name)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								vendor/github.com/tonistiigi/fsutil/copy/copy_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/Microsoft/go-winio"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/sys/windows"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	seTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getUIDGID(fi os.FileInfo) (uid, gid int) {
 | 
			
		||||
	return 0, 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getFileSecurityInfo(name string) (*windows.SID, *windows.ACL, error) {
 | 
			
		||||
	secInfo, err := windows.GetNamedSecurityInfo(
 | 
			
		||||
		name, windows.SE_FILE_OBJECT,
 | 
			
		||||
		windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, errors.Wrap(err, "fetching security info")
 | 
			
		||||
	}
 | 
			
		||||
	sid, _, err := secInfo.Owner()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, errors.Wrap(err, "fetching owner SID")
 | 
			
		||||
	}
 | 
			
		||||
	dacl, _, err := secInfo.DACL()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, errors.Wrap(err, "fetching dacl")
 | 
			
		||||
	}
 | 
			
		||||
	return sid, dacl, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) copyFileInfo(fi os.FileInfo, src, name string) error {
 | 
			
		||||
	if c.modeSet != nil {
 | 
			
		||||
		return errors.Errorf("non-octal mode not supported on windows")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := os.Chmod(name, fi.Mode()); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to chmod %s", name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sid, dacl, err := getFileSecurityInfo(src)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "getting file info")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.chown != nil {
 | 
			
		||||
		// Use the defined chowner.
 | 
			
		||||
		usr := &User{SID: sid.String()}
 | 
			
		||||
		if err := Chown(name, usr, c.chown); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "failed to chown %s", name)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	} else {
 | 
			
		||||
		// Copy file ownership and ACL from the source file.
 | 
			
		||||
		// We need SeRestorePrivilege and SeTakeOwnershipPrivilege in order
 | 
			
		||||
		// to restore security info on a file, especially if we're trying to
 | 
			
		||||
		// apply security info which includes SIDs not necessarily present on
 | 
			
		||||
		// the host.
 | 
			
		||||
		privileges := []string{winio.SeRestorePrivilege, seTakeOwnershipPrivilege}
 | 
			
		||||
		if err := winio.EnableProcessPrivileges(privileges); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		defer winio.DisableProcessPrivileges(privileges)
 | 
			
		||||
 | 
			
		||||
		if err := windows.SetNamedSecurityInfo(
 | 
			
		||||
			name, windows.SE_FILE_OBJECT,
 | 
			
		||||
			windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
 | 
			
		||||
			sid, nil, dacl, nil); err != nil {
 | 
			
		||||
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := c.copyFileTimestamp(fi, name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *copier) copyFileTimestamp(fi os.FileInfo, name string) error {
 | 
			
		||||
	if c.utime != nil {
 | 
			
		||||
		return Utimes(name, c.utime)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if fi.Mode()&os.ModeSymlink == 0 {
 | 
			
		||||
		if err := os.Chtimes(name, fi.ModTime(), fi.ModTime()); err != nil {
 | 
			
		||||
			return errors.Wrap(err, "changing mtime")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyFile(source, target string) error {
 | 
			
		||||
	src, err := os.Open(source)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to open source %s", source)
 | 
			
		||||
	}
 | 
			
		||||
	defer src.Close()
 | 
			
		||||
	tgt, err := os.Create(target)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to open target %s", target)
 | 
			
		||||
	}
 | 
			
		||||
	defer tgt.Close()
 | 
			
		||||
 | 
			
		||||
	return copyFileContent(tgt, src)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyFileContent(dst, src *os.File) error {
 | 
			
		||||
	buf := bufferPool.Get().(*[]byte)
 | 
			
		||||
	_, err := io.CopyBuffer(dst, src, *buf)
 | 
			
		||||
	bufferPool.Put(buf)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyDevice(dst string, fi os.FileInfo) error {
 | 
			
		||||
	return errors.New("device copy not supported")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								vendor/github.com/tonistiigi/fsutil/copy/hardlink.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/tonistiigi/fsutil/copy/hardlink.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import "os"
 | 
			
		||||
 | 
			
		||||
// GetLinkInfo returns an identifier representing the node a hardlink is pointing
 | 
			
		||||
// to. If the file is not hard linked then 0 will be returned.
 | 
			
		||||
func GetLinkInfo(fi os.FileInfo) (uint64, bool) {
 | 
			
		||||
	return getLinkInfo(fi)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getLinkSource returns a path for the given name and
 | 
			
		||||
// file info to its link source in the provided inode
 | 
			
		||||
// map. If the given file name is not in the map and
 | 
			
		||||
// has other links, it is added to the inode map
 | 
			
		||||
// to be a source for other link locations.
 | 
			
		||||
func getLinkSource(name string, fi os.FileInfo, inodes map[uint64]string) (string, error) {
 | 
			
		||||
	inode, isHardlink := getLinkInfo(fi)
 | 
			
		||||
	if !isHardlink {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path, ok := inodes[inode]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		inodes[inode] = name
 | 
			
		||||
	}
 | 
			
		||||
	return path, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								vendor/github.com/tonistiigi/fsutil/copy/hardlink_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/tonistiigi/fsutil/copy/hardlink_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
//go:build !windows
 | 
			
		||||
// +build !windows
 | 
			
		||||
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"syscall"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getLinkInfo(fi os.FileInfo) (uint64, bool) {
 | 
			
		||||
	s, ok := fi.Sys().(*syscall.Stat_t)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return 0, false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								vendor/github.com/tonistiigi/fsutil/copy/hardlink_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/tonistiigi/fsutil/copy/hardlink_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import "os"
 | 
			
		||||
 | 
			
		||||
func getLinkInfo(fi os.FileInfo) (uint64, bool) {
 | 
			
		||||
	return 0, false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								vendor/github.com/tonistiigi/fsutil/copy/mkdir.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								vendor/github.com/tonistiigi/fsutil/copy/mkdir.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MkdirAll is forked os.MkdirAll
 | 
			
		||||
func MkdirAll(path string, perm os.FileMode, user Chowner, tm *time.Time) error {
 | 
			
		||||
	// Fast path: if we can tell whether path is a directory or file, stop with success or error.
 | 
			
		||||
	dir, err := os.Stat(path)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		if dir.IsDir() {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Slow path: make sure parent exists and then call Mkdir for path.
 | 
			
		||||
	i := len(path)
 | 
			
		||||
	for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
 | 
			
		||||
		i--
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	j := i
 | 
			
		||||
	for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
 | 
			
		||||
		j--
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if j > 1 {
 | 
			
		||||
		// Create parent.
 | 
			
		||||
		err = MkdirAll(fixRootDirectory(path[:j-1]), perm, user, tm)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dir, err1 := os.Lstat(path)
 | 
			
		||||
	if err1 == nil && dir.IsDir() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Parent now exists; invoke Mkdir and use its result.
 | 
			
		||||
	err = os.Mkdir(path, perm)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// Handle arguments like "foo/." by
 | 
			
		||||
		// double-checking that directory doesn't exist.
 | 
			
		||||
		dir, err1 := os.Lstat(path)
 | 
			
		||||
		if err1 == nil && dir.IsDir() {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := Chown(path, nil, user); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := Utimes(path, tm); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								vendor/github.com/tonistiigi/fsutil/copy/mkdir_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								vendor/github.com/tonistiigi/fsutil/copy/mkdir_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
//go:build !windows
 | 
			
		||||
// +build !windows
 | 
			
		||||
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func fixRootDirectory(p string) string {
 | 
			
		||||
	return p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Utimes(p string, tm *time.Time) error {
 | 
			
		||||
	if tm == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ts, err := unix.TimeToTimespec(*tm)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timespec := []unix.Timespec{ts, ts}
 | 
			
		||||
	if err := unix.UtimesNanoAt(unix.AT_FDCWD, p, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to utime %s", p)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Chown(p string, old *User, fn Chowner) error {
 | 
			
		||||
	if fn == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	user, err := fn(old)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.WithStack(err)
 | 
			
		||||
	}
 | 
			
		||||
	if user != nil {
 | 
			
		||||
		if err := os.Lchown(p, user.UID, user.GID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								vendor/github.com/tonistiigi/fsutil/copy/mkdir_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								vendor/github.com/tonistiigi/fsutil/copy/mkdir_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
//go:build windows
 | 
			
		||||
// +build windows
 | 
			
		||||
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Microsoft/go-winio"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"golang.org/x/sys/windows"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	containerAdministratorSidString = "S-1-5-93-2-1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func fixRootDirectory(p string) string {
 | 
			
		||||
	if len(p) == len(`\\?\c:`) {
 | 
			
		||||
		if os.IsPathSeparator(p[0]) && os.IsPathSeparator(p[1]) && p[2] == '?' && os.IsPathSeparator(p[3]) && p[5] == ':' {
 | 
			
		||||
			return p + `\`
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Utimes(p string, tm *time.Time) error {
 | 
			
		||||
	info, err := os.Lstat(p)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "fetching file info")
 | 
			
		||||
	}
 | 
			
		||||
	if tm != nil && info.Mode()&os.ModeSymlink == 0 {
 | 
			
		||||
		if err := os.Chtimes(p, *tm, *tm); err != nil {
 | 
			
		||||
			return errors.Wrap(err, "changing times")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Chown(p string, old *User, fn Chowner) error {
 | 
			
		||||
	if fn == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	user, err := fn(old)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.WithStack(err)
 | 
			
		||||
	}
 | 
			
		||||
	var userSIDstring string
 | 
			
		||||
	if user != nil && user.SID != "" {
 | 
			
		||||
		userSIDstring = user.SID
 | 
			
		||||
	}
 | 
			
		||||
	if userSIDstring == "" {
 | 
			
		||||
		userSIDstring = containerAdministratorSidString
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sidPtr, err := syscall.UTF16PtrFromString(userSIDstring)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "converting to utf16 ptr")
 | 
			
		||||
	}
 | 
			
		||||
	var userSID *windows.SID
 | 
			
		||||
	if err := windows.ConvertStringSidToSid(sidPtr, &userSID); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "converting to windows SID")
 | 
			
		||||
	}
 | 
			
		||||
	var dacl *windows.ACL
 | 
			
		||||
	newEntries := []windows.EXPLICIT_ACCESS{
 | 
			
		||||
		{
 | 
			
		||||
			AccessPermissions: windows.GENERIC_ALL,
 | 
			
		||||
			AccessMode:        windows.GRANT_ACCESS,
 | 
			
		||||
			Inheritance:       windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
 | 
			
		||||
			Trustee: windows.TRUSTEE{
 | 
			
		||||
				TrusteeForm:  windows.TRUSTEE_IS_SID,
 | 
			
		||||
				TrusteeValue: windows.TrusteeValueFromSID(userSID),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	newAcl, err := windows.ACLFromEntries(newEntries, dacl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("adding acls: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Copy file ownership and ACL
 | 
			
		||||
	// We need SeRestorePrivilege and SeTakeOwnershipPrivilege in order
 | 
			
		||||
	// to restore security info on a file, especially if we're trying to
 | 
			
		||||
	// apply security info which includes SIDs not necessarily present on
 | 
			
		||||
	// the host.
 | 
			
		||||
	privileges := []string{winio.SeRestorePrivilege, seTakeOwnershipPrivilege}
 | 
			
		||||
	err = winio.RunWithPrivileges(privileges, func() error {
 | 
			
		||||
		if err := windows.SetNamedSecurityInfo(
 | 
			
		||||
			p, windows.SE_FILE_OBJECT,
 | 
			
		||||
			windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
 | 
			
		||||
			userSID, nil, newAcl, nil); err != nil {
 | 
			
		||||
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								vendor/github.com/tonistiigi/fsutil/copy/stat_bsd.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/tonistiigi/fsutil/copy/stat_bsd.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
// +build darwin freebsd netbsd openbsd
 | 
			
		||||
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"syscall"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Returns the last-accessed time
 | 
			
		||||
func StatAtime(st *syscall.Stat_t) syscall.Timespec {
 | 
			
		||||
	return st.Atimespec
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns the last-modified time
 | 
			
		||||
func StatMtime(st *syscall.Stat_t) syscall.Timespec {
 | 
			
		||||
	return st.Mtimespec
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								vendor/github.com/tonistiigi/fsutil/copy/stat_sysv.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/tonistiigi/fsutil/copy/stat_sysv.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
//go:build dragonfly || linux || solaris
 | 
			
		||||
// +build dragonfly linux solaris
 | 
			
		||||
 | 
			
		||||
package fs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"syscall"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Returns the last-accessed time
 | 
			
		||||
func StatAtime(st *syscall.Stat_t) syscall.Timespec {
 | 
			
		||||
	return st.Atim
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns the last-modified time
 | 
			
		||||
func StatMtime(st *syscall.Stat_t) syscall.Timespec {
 | 
			
		||||
	return st.Mtim
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							@@ -710,9 +710,13 @@ github.com/theupdateframework/notary/tuf/data
 | 
			
		||||
github.com/theupdateframework/notary/tuf/signed
 | 
			
		||||
github.com/theupdateframework/notary/tuf/utils
 | 
			
		||||
github.com/theupdateframework/notary/tuf/validation
 | 
			
		||||
# github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205
 | 
			
		||||
## explicit; go 1.21
 | 
			
		||||
github.com/tonistiigi/dchapes-mode
 | 
			
		||||
# github.com/tonistiigi/fsutil v0.0.0-20241003195857-3f140a1299b0
 | 
			
		||||
## explicit; go 1.21
 | 
			
		||||
github.com/tonistiigi/fsutil
 | 
			
		||||
github.com/tonistiigi/fsutil/copy
 | 
			
		||||
github.com/tonistiigi/fsutil/types
 | 
			
		||||
# github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
 | 
			
		||||
## explicit; go 1.16
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user