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
 | 
						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) {
 | 
					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, configDir, w, nil)
 | 
						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 {
 | 
						if len(nodes) == 0 {
 | 
				
			||||||
		return nil, errors.Errorf("driver required for build")
 | 
							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
 | 
									return nil, err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			localOpt := opt
 | 
								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
 | 
								opts[k] = localOpt
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return nil, err
 | 
									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
 | 
									return nil, err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			addGitAttrs(so)
 | 
								addGitAttrs(so)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,10 +5,11 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/docker/buildx/builder"
 | 
						"github.com/docker/buildx/builder"
 | 
				
			||||||
	"github.com/docker/buildx/localstate"
 | 
						"github.com/docker/buildx/localstate"
 | 
				
			||||||
 | 
						"github.com/docker/buildx/util/confutil"
 | 
				
			||||||
	"github.com/moby/buildkit/client"
 | 
						"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
 | 
						var err error
 | 
				
			||||||
	if so.Ref == "" {
 | 
						if so.Ref == "" {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
@@ -30,7 +31,7 @@ func saveLocalState(so *client.SolveOpt, target string, opts Options, node build
 | 
				
			|||||||
	if lp == "" && dp == "" {
 | 
						if lp == "" && dp == "" {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	l, err := localstate.New(configDir)
 | 
						l, err := localstate.New(cfg)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@ import (
 | 
				
			|||||||
	"github.com/tonistiigi/fsutil"
 | 
						"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
 | 
						nodeDriver := node.Driver
 | 
				
			||||||
	defers := make([]func(), 0, 2)
 | 
						defers := make([]func(), 0, 2)
 | 
				
			||||||
	releaseF := func() {
 | 
						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
 | 
						// add node identifier to shared key if one was specified
 | 
				
			||||||
	if so.SharedKey != "" {
 | 
						if so.SharedKey != "" {
 | 
				
			||||||
		so.SharedKey += ":" + confutil.TryNodeIdentifier(configDir)
 | 
							so.SharedKey += ":" + cfg.TryNodeIdentifier()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if opt.Pull {
 | 
						if opt.Pull {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -439,7 +439,7 @@ func Create(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts Cre
 | 
				
			|||||||
	if buildkitdConfigFile == "" {
 | 
						if buildkitdConfigFile == "" {
 | 
				
			||||||
		// if buildkit daemon config is not provided, check if the default one
 | 
							// if buildkit daemon config is not provided, check if the default one
 | 
				
			||||||
		// is available and use it
 | 
							// is available and use it
 | 
				
			||||||
		if f, ok := confutil.DefaultConfigFile(dockerCli); ok {
 | 
							if f, ok := confutil.NewConfig(dockerCli).BuildKitConfigFile(); ok {
 | 
				
			||||||
			buildkitdConfigFile = f
 | 
								buildkitdConfigFile = f
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -584,7 +584,7 @@ func Leave(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts Leav
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ls, err := localstate.New(confutil.ConfigDir(dockerCli))
 | 
						ls, err := localstate.New(confutil.NewConfig(dockerCli))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -265,7 +265,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	done := timeBuildCommand(mp, attributes)
 | 
						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 {
 | 
						if err := printer.Wait(); retErr == nil {
 | 
				
			||||||
		retErr = err
 | 
							retErr = err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -470,7 +470,7 @@ func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string
 | 
				
			|||||||
		refs = append(refs, b.Ref)
 | 
							refs = append(refs, b.Ref)
 | 
				
			||||||
		bo[k] = b
 | 
							bo[k] = b
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	l, err := localstate.New(confutil.ConfigDir(dockerCli))
 | 
						l, err := localstate.New(confutil.NewConfig(dockerCli))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -621,7 +621,7 @@ func bakeMetricAttributes(dockerCli command.Cli, driverType, url, cmdContext str
 | 
				
			|||||||
		commandNameAttribute.String("bake"),
 | 
							commandNameAttribute.String("bake"),
 | 
				
			||||||
		attribute.Stringer(string(commandOptionsHash), &bakeOptionsHash{
 | 
							attribute.Stringer(string(commandOptionsHash), &bakeOptionsHash{
 | 
				
			||||||
			bakeOptions: options,
 | 
								bakeOptions: options,
 | 
				
			||||||
			configDir:   confutil.ConfigDir(dockerCli),
 | 
								cfg:         confutil.NewConfig(dockerCli),
 | 
				
			||||||
			url:         url,
 | 
								url:         url,
 | 
				
			||||||
			cmdContext:  cmdContext,
 | 
								cmdContext:  cmdContext,
 | 
				
			||||||
			targets:     targets,
 | 
								targets:     targets,
 | 
				
			||||||
@@ -633,7 +633,7 @@ func bakeMetricAttributes(dockerCli command.Cli, driverType, url, cmdContext str
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type bakeOptionsHash struct {
 | 
					type bakeOptionsHash struct {
 | 
				
			||||||
	*bakeOptions
 | 
						*bakeOptions
 | 
				
			||||||
	configDir  string
 | 
						cfg        *confutil.Config
 | 
				
			||||||
	url        string
 | 
						url        string
 | 
				
			||||||
	cmdContext string
 | 
						cmdContext string
 | 
				
			||||||
	targets    []string
 | 
						targets    []string
 | 
				
			||||||
@@ -657,7 +657,7 @@ func (o *bakeOptionsHash) String() string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		joinedFiles := strings.Join(files, ",")
 | 
							joinedFiles := strings.Join(files, ",")
 | 
				
			||||||
		joinedTargets := strings.Join(targets, ",")
 | 
							joinedTargets := strings.Join(targets, ",")
 | 
				
			||||||
		salt := confutil.TryNodeIdentifier(o.configDir)
 | 
							salt := o.cfg.TryNodeIdentifier()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		h := sha256.New()
 | 
							h := sha256.New()
 | 
				
			||||||
		for _, s := range []string{url, cmdContext, joinedFiles, joinedTargets, salt} {
 | 
							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"),
 | 
							commandNameAttribute.String("build"),
 | 
				
			||||||
		attribute.Stringer(string(commandOptionsHash), &buildOptionsHash{
 | 
							attribute.Stringer(string(commandOptionsHash), &buildOptionsHash{
 | 
				
			||||||
			buildOptions: options,
 | 
								buildOptions: options,
 | 
				
			||||||
			configDir:    confutil.ConfigDir(dockerCli),
 | 
								cfg:          confutil.NewConfig(dockerCli),
 | 
				
			||||||
		}),
 | 
							}),
 | 
				
			||||||
		driverNameAttribute.String(options.builder),
 | 
							driverNameAttribute.String(options.builder),
 | 
				
			||||||
		driverTypeAttribute.String(driverType),
 | 
							driverTypeAttribute.String(driverType),
 | 
				
			||||||
@@ -250,7 +250,7 @@ func buildMetricAttributes(dockerCli command.Cli, driverType string, options *bu
 | 
				
			|||||||
// the fmt.Stringer interface.
 | 
					// the fmt.Stringer interface.
 | 
				
			||||||
type buildOptionsHash struct {
 | 
					type buildOptionsHash struct {
 | 
				
			||||||
	*buildOptions
 | 
						*buildOptions
 | 
				
			||||||
	configDir  string
 | 
						cfg        *confutil.Config
 | 
				
			||||||
	result     string
 | 
						result     string
 | 
				
			||||||
	resultOnce sync.Once
 | 
						resultOnce sync.Once
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -267,7 +267,7 @@ func (o *buildOptionsHash) String() string {
 | 
				
			|||||||
		if contextPath != "-" && osutil.IsLocalDir(contextPath) {
 | 
							if contextPath != "-" && osutil.IsLocalDir(contextPath) {
 | 
				
			||||||
			contextPath = osutil.ToAbs(contextPath)
 | 
								contextPath = osutil.ToAbs(contextPath)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		salt := confutil.TryNodeIdentifier(o.configDir)
 | 
							salt := o.cfg.TryNodeIdentifier()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		h := sha256.New()
 | 
							h := sha256.New()
 | 
				
			||||||
		for _, s := range []string{target, contextPath, dockerfile, salt} {
 | 
							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 {
 | 
						if generateResult {
 | 
				
			||||||
		var mu sync.Mutex
 | 
							var mu sync.Mutex
 | 
				
			||||||
		var idx int
 | 
							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()
 | 
								mu.Lock()
 | 
				
			||||||
			defer mu.Unlock()
 | 
								defer mu.Unlock()
 | 
				
			||||||
			if res == nil || driverIndex < idx {
 | 
								if res == nil || driverIndex < idx {
 | 
				
			||||||
@@ -222,7 +222,7 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.No
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	} else {
 | 
						} 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 {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, res, err
 | 
							return nil, res, err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -258,7 +258,7 @@ func prepareRootDir(dockerCli command.Cli, config *serverConfig) (string, error)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func rootDataDir(dockerCli command.Cli) string {
 | 
					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) {
 | 
					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/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
 | 
				
			||||||
	github.com/shibumi/go-pathspec v1.3.0 // indirect
 | 
						github.com/shibumi/go-pathspec v1.3.0 // indirect
 | 
				
			||||||
	github.com/theupdateframework/notary v0.7.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/units v0.0.0-20180711220420-6950e57a87ea // indirect
 | 
				
			||||||
	github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
 | 
						github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
 | 
				
			||||||
	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // 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/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 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
 | 
				
			||||||
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
 | 
					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 h1:H9++AiQUqjwrOMA/DOpWhxWp3JLyyT+MN4sRPbMmwoY=
 | 
				
			||||||
github.com/tonistiigi/fsutil v0.0.0-20241003195857-3f140a1299b0/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw=
 | 
					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=
 | 
					github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ import (
 | 
				
			|||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/docker/docker/pkg/ioutils"
 | 
						"github.com/docker/buildx/util/confutil"
 | 
				
			||||||
	"github.com/pkg/errors"
 | 
						"github.com/pkg/errors"
 | 
				
			||||||
	"golang.org/x/sync/errgroup"
 | 
						"golang.org/x/sync/errgroup"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -42,18 +42,18 @@ type StateGroup struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type LocalState struct {
 | 
					type LocalState struct {
 | 
				
			||||||
	root string
 | 
						cfg *confutil.Config
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func New(root string) (*LocalState, error) {
 | 
					func New(cfg *confutil.Config) (*LocalState, error) {
 | 
				
			||||||
	if root == "" {
 | 
						if cfg.Dir() == "" {
 | 
				
			||||||
		return nil, errors.Errorf("root dir empty")
 | 
							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 nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return &LocalState{
 | 
						return &LocalState{
 | 
				
			||||||
		root: root,
 | 
							cfg: cfg,
 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -61,7 +61,7 @@ func (ls *LocalState) ReadRef(builderName, nodeName, id string) (*State, error)
 | 
				
			|||||||
	if err := ls.validate(builderName, nodeName, id); err != nil {
 | 
						if err := ls.validate(builderName, nodeName, id); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							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 {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							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 {
 | 
						if err := ls.validate(builderName, nodeName, id); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	refDir := filepath.Join(ls.root, refsDir, builderName, nodeName)
 | 
						refDir := filepath.Join(refsDir, builderName, nodeName)
 | 
				
			||||||
	if err := os.MkdirAll(refDir, 0700); err != nil {
 | 
						if err := ls.cfg.MkdirAll(refDir, 0700); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	dt, err := json.Marshal(st)
 | 
						dt, err := json.Marshal(st)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							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) {
 | 
					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 {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -100,15 +100,15 @@ func (ls *LocalState) ReadGroup(id string) (*StateGroup, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (ls *LocalState) SaveGroup(id string, stg StateGroup) error {
 | 
					func (ls *LocalState) SaveGroup(id string, stg StateGroup) error {
 | 
				
			||||||
	refDir := filepath.Join(ls.root, refsDir, groupDir)
 | 
						refDir := filepath.Join(refsDir, groupDir)
 | 
				
			||||||
	if err := os.MkdirAll(refDir, 0700); err != nil {
 | 
						if err := ls.cfg.MkdirAll(refDir, 0700); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	dt, err := json.Marshal(stg)
 | 
						dt, err := json.Marshal(stg)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							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 {
 | 
					func (ls *LocalState) RemoveBuilder(builderName string) error {
 | 
				
			||||||
@@ -116,7 +116,7 @@ func (ls *LocalState) RemoveBuilder(builderName string) error {
 | 
				
			|||||||
		return errors.Errorf("builder name empty")
 | 
							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 _, err := os.Lstat(dir); err != nil {
 | 
				
			||||||
		if !os.IsNotExist(err) {
 | 
							if !os.IsNotExist(err) {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
@@ -147,7 +147,7 @@ func (ls *LocalState) RemoveBuilderNode(builderName string, nodeName string) err
 | 
				
			|||||||
		return errors.Errorf("node name empty")
 | 
							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 _, err := os.Lstat(dir); err != nil {
 | 
				
			||||||
		if !os.IsNotExist(err) {
 | 
							if !os.IsNotExist(err) {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
@@ -208,7 +208,7 @@ func (ls *LocalState) removeGroup(id string) error {
 | 
				
			|||||||
	if id == "" {
 | 
						if id == "" {
 | 
				
			||||||
		return errors.Errorf("group ref empty")
 | 
							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 _, err := os.Lstat(f); err != nil {
 | 
				
			||||||
		if !os.IsNotExist(err) {
 | 
							if !os.IsNotExist(err) {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import (
 | 
				
			|||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/docker/buildx/util/confutil"
 | 
				
			||||||
	"github.com/stretchr/testify/require"
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,10 +40,10 @@ func newls(t *testing.T) *LocalState {
 | 
				
			|||||||
	t.Helper()
 | 
						t.Helper()
 | 
				
			||||||
	tmpdir := t.TempDir()
 | 
						tmpdir := t.TempDir()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	l, err := New(tmpdir)
 | 
						l, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
 | 
				
			||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
	require.DirExists(t, filepath.Join(tmpdir, refsDir))
 | 
						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))
 | 
						require.NoError(t, l.SaveRef(testBuilderName, testNodeName, testStateRefID, testStateRef))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/docker/buildx/localstate"
 | 
						"github.com/docker/buildx/localstate"
 | 
				
			||||||
	"github.com/docker/docker/pkg/ioutils"
 | 
						"github.com/docker/buildx/util/confutil"
 | 
				
			||||||
	"github.com/gofrs/flock"
 | 
						"github.com/gofrs/flock"
 | 
				
			||||||
	"github.com/opencontainers/go-digest"
 | 
						"github.com/opencontainers/go-digest"
 | 
				
			||||||
	"github.com/pkg/errors"
 | 
						"github.com/pkg/errors"
 | 
				
			||||||
@@ -20,25 +20,25 @@ const (
 | 
				
			|||||||
	activityDir = "activity"
 | 
						activityDir = "activity"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func New(root string) (*Store, error) {
 | 
					func New(cfg *confutil.Config) (*Store, error) {
 | 
				
			||||||
	if err := os.MkdirAll(filepath.Join(root, instanceDir), 0700); err != nil {
 | 
						if err := cfg.MkdirAll(instanceDir, 0700); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							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
 | 
							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 nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return &Store{root: root}, nil
 | 
						return &Store{cfg: cfg}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Store struct {
 | 
					type Store struct {
 | 
				
			||||||
	root string
 | 
						cfg *confutil.Config
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Store) Txn() (*Txn, func(), error) {
 | 
					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 {
 | 
						if err := l.Lock(); err != nil {
 | 
				
			||||||
		return nil, nil, err
 | 
							return nil, nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -54,7 +54,7 @@ type Txn struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (t *Txn) List() ([]*NodeGroup, error) {
 | 
					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)
 | 
						fis, err := os.ReadDir(pp)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@@ -84,7 +84,7 @@ func (t *Txn) NodeGroupByName(name string) (*NodeGroup, error) {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							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 {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -110,7 +110,7 @@ func (t *Txn) Save(ng *NodeGroup) error {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							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 {
 | 
					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 {
 | 
						if err := t.RemoveLastActivity(name); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ls, err := localstate.New(t.s.root)
 | 
						ls, err := localstate.New(t.s.cfg)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err := ls.RemoveBuilder(name); err != nil {
 | 
						if err := ls.RemoveBuilder(name); err != nil {
 | 
				
			||||||
		return err
 | 
							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 {
 | 
					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 {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							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
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	h := toHash(key)
 | 
						h := toHash(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if def {
 | 
						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
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} 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
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (t *Txn) UpdateLastActivity(ng *NodeGroup) error {
 | 
					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) {
 | 
					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 err != nil {
 | 
				
			||||||
		if os.IsNotExist(errors.Cause(err)) {
 | 
							if os.IsNotExist(errors.Cause(err)) {
 | 
				
			||||||
			return la, nil
 | 
								return la, nil
 | 
				
			||||||
@@ -177,7 +177,7 @@ func (t *Txn) RemoveLastActivity(name string) error {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							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 {
 | 
					func (t *Txn) reset(key string) error {
 | 
				
			||||||
@@ -185,11 +185,11 @@ func (t *Txn) reset(key string) error {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							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) {
 | 
					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 err != nil {
 | 
				
			||||||
		if !os.IsNotExist(err) {
 | 
							if !os.IsNotExist(err) {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
@@ -220,7 +220,7 @@ func (t *Txn) Current(key string) (*NodeGroup, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	h := toHash(key)
 | 
						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 err != nil {
 | 
				
			||||||
		if os.IsNotExist(err) {
 | 
							if os.IsNotExist(err) {
 | 
				
			||||||
			t.reset(key)
 | 
								t.reset(key)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ import (
 | 
				
			|||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/docker/buildx/util/confutil"
 | 
				
			||||||
	"github.com/pkg/errors"
 | 
						"github.com/pkg/errors"
 | 
				
			||||||
	"github.com/stretchr/testify/require"
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -15,7 +16,7 @@ func TestEmptyStartup(t *testing.T) {
 | 
				
			|||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
	defer os.RemoveAll(tmpdir)
 | 
						defer os.RemoveAll(tmpdir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s, err := New(tmpdir)
 | 
						s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
 | 
				
			||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	txn, release, err := s.Txn()
 | 
						txn, release, err := s.Txn()
 | 
				
			||||||
@@ -33,7 +34,7 @@ func TestNodeLocking(t *testing.T) {
 | 
				
			|||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
	defer os.RemoveAll(tmpdir)
 | 
						defer os.RemoveAll(tmpdir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s, err := New(tmpdir)
 | 
						s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
 | 
				
			||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, release, err := s.Txn()
 | 
						_, release, err := s.Txn()
 | 
				
			||||||
@@ -68,7 +69,7 @@ func TestNodeManagement(t *testing.T) {
 | 
				
			|||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
	defer os.RemoveAll(tmpdir)
 | 
						defer os.RemoveAll(tmpdir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s, err := New(tmpdir)
 | 
						s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
 | 
				
			||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	txn, release, err := s.Txn()
 | 
						txn, release, err := s.Txn()
 | 
				
			||||||
@@ -240,7 +241,7 @@ func TestNodeInvalidName(t *testing.T) {
 | 
				
			|||||||
	t.Parallel()
 | 
						t.Parallel()
 | 
				
			||||||
	tmpdir := t.TempDir()
 | 
						tmpdir := t.TempDir()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s, err := New(tmpdir)
 | 
						s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
 | 
				
			||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	txn, release, err := s.Txn()
 | 
						txn, release, err := s.Txn()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// GetStore returns current builder instance store
 | 
					// GetStore returns current builder instance store
 | 
				
			||||||
func GetStore(dockerCli command.Cli) (*store.Txn, func(), error) {
 | 
					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 {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, nil, err
 | 
							return nil, nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@ import (
 | 
				
			|||||||
	"github.com/containerd/platforms"
 | 
						"github.com/containerd/platforms"
 | 
				
			||||||
	"github.com/creack/pty"
 | 
						"github.com/creack/pty"
 | 
				
			||||||
	"github.com/docker/buildx/localstate"
 | 
						"github.com/docker/buildx/localstate"
 | 
				
			||||||
 | 
						"github.com/docker/buildx/util/confutil"
 | 
				
			||||||
	"github.com/docker/buildx/util/gitutil"
 | 
						"github.com/docker/buildx/util/gitutil"
 | 
				
			||||||
	"github.com/moby/buildkit/client"
 | 
						"github.com/moby/buildkit/client"
 | 
				
			||||||
	"github.com/moby/buildkit/frontend/subrequests/lint"
 | 
						"github.com/moby/buildkit/frontend/subrequests/lint"
 | 
				
			||||||
@@ -167,7 +168,7 @@ COPY --from=base /etc/bar /bar
 | 
				
			|||||||
	err = json.Unmarshal(dt, &md)
 | 
						err = json.Unmarshal(dt, &md)
 | 
				
			||||||
	require.NoError(t, err)
 | 
						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)
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	refParts := strings.Split(md.BuildRef, "/")
 | 
						refParts := strings.Split(md.BuildRef, "/")
 | 
				
			||||||
@@ -209,7 +210,7 @@ COPY --from=base /etc/bar /bar
 | 
				
			|||||||
	err = json.Unmarshal(dt, &md)
 | 
						err = json.Unmarshal(dt, &md)
 | 
				
			||||||
	require.NoError(t, err)
 | 
						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)
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	refParts := strings.Split(md.BuildRef, "/")
 | 
						refParts := strings.Split(md.BuildRef, "/")
 | 
				
			||||||
@@ -261,7 +262,7 @@ COPY foo /foo
 | 
				
			|||||||
	err = json.Unmarshal(dt, &md)
 | 
						err = json.Unmarshal(dt, &md)
 | 
				
			||||||
	require.NoError(t, err)
 | 
						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)
 | 
						require.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	refParts := strings.Split(md.BuildRef, "/")
 | 
						refParts := strings.Split(md.BuildRef, "/")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,39 +1,143 @@
 | 
				
			|||||||
package confutil
 | 
					package confutil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"encoding/hex"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
					 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/docker/cli/cli/command"
 | 
						"github.com/docker/cli/cli/command"
 | 
				
			||||||
 | 
						"github.com/docker/docker/pkg/ioutils"
 | 
				
			||||||
	"github.com/pelletier/go-toml"
 | 
						"github.com/pelletier/go-toml"
 | 
				
			||||||
	"github.com/pkg/errors"
 | 
						"github.com/pkg/errors"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						fs "github.com/tonistiigi/fsutil/copy"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ConfigDir will look for correct configuration store path;
 | 
					const defaultBuildKitConfigFile = "buildkitd.default.toml"
 | 
				
			||||||
// 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
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	buildxConfig := filepath.Join(filepath.Dir(dockerCli.ConfigFile().Filename), "buildx")
 | 
					type Config struct {
 | 
				
			||||||
	logrus.Debugf("using default config store %q", buildxConfig)
 | 
						dir     string
 | 
				
			||||||
	return buildxConfig
 | 
						chowner *chowner
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DefaultConfigFile returns the default BuildKit configuration file path
 | 
					type chowner struct {
 | 
				
			||||||
func DefaultConfigFile(dockerCli command.Cli) (string, bool) {
 | 
						uid int
 | 
				
			||||||
	f := path.Join(ConfigDir(dockerCli), "buildkitd.default.toml")
 | 
						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 {
 | 
						if _, err := os.Stat(f); err == nil {
 | 
				
			||||||
		return f, true
 | 
							return f, true
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return "", false
 | 
						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
 | 
					// LoadConfigTree loads BuildKit config toml tree
 | 
				
			||||||
func LoadConfigTree(fp string) (*toml.Tree, error) {
 | 
					func LoadConfigTree(fp string) (*toml.Tree, error) {
 | 
				
			||||||
	f, err := os.Open(fp)
 | 
						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/signed
 | 
				
			||||||
github.com/theupdateframework/notary/tuf/utils
 | 
					github.com/theupdateframework/notary/tuf/utils
 | 
				
			||||||
github.com/theupdateframework/notary/tuf/validation
 | 
					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
 | 
					# github.com/tonistiigi/fsutil v0.0.0-20241003195857-3f140a1299b0
 | 
				
			||||||
## explicit; go 1.21
 | 
					## explicit; go 1.21
 | 
				
			||||||
github.com/tonistiigi/fsutil
 | 
					github.com/tonistiigi/fsutil
 | 
				
			||||||
 | 
					github.com/tonistiigi/fsutil/copy
 | 
				
			||||||
github.com/tonistiigi/fsutil/types
 | 
					github.com/tonistiigi/fsutil/types
 | 
				
			||||||
# github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
 | 
					# github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
 | 
				
			||||||
## explicit; go 1.16
 | 
					## explicit; go 1.16
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user