config: fix file/folder ownership

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax 2024-10-18 13:28:45 +02:00
parent 1de332530f
commit f89f861999
No known key found for this signature in database
GPG Key ID: ADE44D8C9D44FBE4
44 changed files with 2514 additions and 122 deletions

View File

@ -151,11 +151,11 @@ func toRepoOnly(in string) (string, error) {
return strings.Join(out, ","), nil
}
func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
return BuildWithResultHandler(ctx, nodes, opts, docker, configDir, w, nil)
func Build(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
return BuildWithResultHandler(ctx, nodes, opts, docker, cfg, w, nil)
}
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultHandle)) (resp map[string]*client.SolveResponse, err error) {
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[string]Options, docker *dockerutil.Client, cfg *confutil.Config, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultHandle)) (resp map[string]*client.SolveResponse, err error) {
if len(nodes) == 0 {
return nil, errors.Errorf("driver required for build")
}
@ -234,12 +234,12 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opts map[
return nil, err
}
localOpt := opt
so, release, err := toSolveOpt(ctx, np.Node(), multiDriver, &localOpt, gatewayOpts, configDir, w, docker)
so, release, err := toSolveOpt(ctx, np.Node(), multiDriver, &localOpt, gatewayOpts, cfg, w, docker)
opts[k] = localOpt
if err != nil {
return nil, err
}
if err := saveLocalState(so, k, opt, np.Node(), configDir); err != nil {
if err := saveLocalState(so, k, opt, np.Node(), cfg); err != nil {
return nil, err
}
addGitAttrs(so)

View File

@ -5,10 +5,11 @@ import (
"github.com/docker/buildx/builder"
"github.com/docker/buildx/localstate"
"github.com/docker/buildx/util/confutil"
"github.com/moby/buildkit/client"
)
func saveLocalState(so *client.SolveOpt, target string, opts Options, node builder.Node, configDir string) error {
func saveLocalState(so *client.SolveOpt, target string, opts Options, node builder.Node, cfg *confutil.Config) error {
var err error
if so.Ref == "" {
return nil
@ -30,7 +31,7 @@ func saveLocalState(so *client.SolveOpt, target string, opts Options, node build
if lp == "" && dp == "" {
return nil
}
l, err := localstate.New(configDir)
l, err := localstate.New(cfg)
if err != nil {
return err
}

View File

@ -35,7 +35,7 @@ import (
"github.com/tonistiigi/fsutil"
)
func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *Options, bopts gateway.BuildOpts, configDir string, pw progress.Writer, docker *dockerutil.Client) (_ *client.SolveOpt, release func(), err error) {
func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *Options, bopts gateway.BuildOpts, cfg *confutil.Config, pw progress.Writer, docker *dockerutil.Client) (_ *client.SolveOpt, release func(), err error) {
nodeDriver := node.Driver
defers := make([]func(), 0, 2)
releaseF := func() {
@ -271,7 +271,7 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt *O
// add node identifier to shared key if one was specified
if so.SharedKey != "" {
so.SharedKey += ":" + confutil.TryNodeIdentifier(configDir)
so.SharedKey += ":" + cfg.TryNodeIdentifier()
}
if opt.Pull {

View File

@ -439,7 +439,7 @@ func Create(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts Cre
if buildkitdConfigFile == "" {
// if buildkit daemon config is not provided, check if the default one
// is available and use it
if f, ok := confutil.DefaultConfigFile(dockerCli); ok {
if f, ok := confutil.NewConfig(dockerCli).BuildKitConfigFile(); ok {
buildkitdConfigFile = f
}
}
@ -584,7 +584,7 @@ func Leave(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts Leav
return err
}
ls, err := localstate.New(confutil.ConfigDir(dockerCli))
ls, err := localstate.New(confutil.NewConfig(dockerCli))
if err != nil {
return err
}

View File

@ -265,7 +265,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
}
done := timeBuildCommand(mp, attributes)
resp, retErr := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer)
resp, retErr := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), printer)
if err := printer.Wait(); retErr == nil {
retErr = err
}
@ -470,7 +470,7 @@ func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string
refs = append(refs, b.Ref)
bo[k] = b
}
l, err := localstate.New(confutil.ConfigDir(dockerCli))
l, err := localstate.New(confutil.NewConfig(dockerCli))
if err != nil {
return err
}
@ -621,7 +621,7 @@ func bakeMetricAttributes(dockerCli command.Cli, driverType, url, cmdContext str
commandNameAttribute.String("bake"),
attribute.Stringer(string(commandOptionsHash), &bakeOptionsHash{
bakeOptions: options,
configDir: confutil.ConfigDir(dockerCli),
cfg: confutil.NewConfig(dockerCli),
url: url,
cmdContext: cmdContext,
targets: targets,
@ -633,7 +633,7 @@ func bakeMetricAttributes(dockerCli command.Cli, driverType, url, cmdContext str
type bakeOptionsHash struct {
*bakeOptions
configDir string
cfg *confutil.Config
url string
cmdContext string
targets []string
@ -657,7 +657,7 @@ func (o *bakeOptionsHash) String() string {
joinedFiles := strings.Join(files, ",")
joinedTargets := strings.Join(targets, ",")
salt := confutil.TryNodeIdentifier(o.configDir)
salt := o.cfg.TryNodeIdentifier()
h := sha256.New()
for _, s := range []string{url, cmdContext, joinedFiles, joinedTargets, salt} {

View File

@ -238,7 +238,7 @@ func buildMetricAttributes(dockerCli command.Cli, driverType string, options *bu
commandNameAttribute.String("build"),
attribute.Stringer(string(commandOptionsHash), &buildOptionsHash{
buildOptions: options,
configDir: confutil.ConfigDir(dockerCli),
cfg: confutil.NewConfig(dockerCli),
}),
driverNameAttribute.String(options.builder),
driverTypeAttribute.String(driverType),
@ -250,7 +250,7 @@ func buildMetricAttributes(dockerCli command.Cli, driverType string, options *bu
// the fmt.Stringer interface.
type buildOptionsHash struct {
*buildOptions
configDir string
cfg *confutil.Config
result string
resultOnce sync.Once
}
@ -267,7 +267,7 @@ func (o *buildOptionsHash) String() string {
if contextPath != "-" && osutil.IsLocalDir(contextPath) {
contextPath = osutil.ToAbs(contextPath)
}
salt := confutil.TryNodeIdentifier(o.configDir)
salt := o.cfg.TryNodeIdentifier()
h := sha256.New()
for _, s := range []string{target, contextPath, dockerfile, salt} {

View File

@ -214,7 +214,7 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.No
if generateResult {
var mu sync.Mutex
var idx int
resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress, func(driverIndex int, gotRes *build.ResultHandle) {
resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress, func(driverIndex int, gotRes *build.ResultHandle) {
mu.Lock()
defer mu.Unlock()
if res == nil || driverIndex < idx {
@ -222,7 +222,7 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.No
}
})
} else {
resp, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress)
resp, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.NewConfig(dockerCli), progress)
}
if err != nil {
return nil, res, err

View File

@ -258,7 +258,7 @@ func prepareRootDir(dockerCli command.Cli, config *serverConfig) (string, error)
}
func rootDataDir(dockerCli command.Cli) string {
return filepath.Join(confutil.ConfigDir(dockerCli), "controller")
return filepath.Join(confutil.NewConfig(dockerCli).Dir(), "controller")
}
func newBuildxClientAndCheck(ctx context.Context, addr string) (*Client, error) {

1
go.mod
View File

@ -147,6 +147,7 @@ require (
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 // indirect
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect

2
go.sum
View File

@ -439,6 +439,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205 h1:eUk79E1w8yMtXeHSzjKorxuC8qJOnyXQnLaJehxpJaI=
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
github.com/tonistiigi/fsutil v0.0.0-20241003195857-3f140a1299b0 h1:H9++AiQUqjwrOMA/DOpWhxWp3JLyyT+MN4sRPbMmwoY=
github.com/tonistiigi/fsutil v0.0.0-20241003195857-3f140a1299b0/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw=
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=

View File

@ -8,7 +8,7 @@ import (
"path/filepath"
"sync"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/buildx/util/confutil"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
@ -42,18 +42,18 @@ type StateGroup struct {
}
type LocalState struct {
root string
cfg *confutil.Config
}
func New(root string) (*LocalState, error) {
if root == "" {
return nil, errors.Errorf("root dir empty")
func New(cfg *confutil.Config) (*LocalState, error) {
if cfg.Dir() == "" {
return nil, errors.Errorf("config dir empty")
}
if err := os.MkdirAll(filepath.Join(root, refsDir), 0700); err != nil {
if err := cfg.MkdirAll(refsDir, 0700); err != nil {
return nil, err
}
return &LocalState{
root: root,
cfg: cfg,
}, nil
}
@ -61,7 +61,7 @@ func (ls *LocalState) ReadRef(builderName, nodeName, id string) (*State, error)
if err := ls.validate(builderName, nodeName, id); err != nil {
return nil, err
}
dt, err := os.ReadFile(filepath.Join(ls.root, refsDir, builderName, nodeName, id))
dt, err := os.ReadFile(filepath.Join(ls.cfg.Dir(), refsDir, builderName, nodeName, id))
if err != nil {
return nil, err
}
@ -76,19 +76,19 @@ func (ls *LocalState) SaveRef(builderName, nodeName, id string, st State) error
if err := ls.validate(builderName, nodeName, id); err != nil {
return err
}
refDir := filepath.Join(ls.root, refsDir, builderName, nodeName)
if err := os.MkdirAll(refDir, 0700); err != nil {
refDir := filepath.Join(refsDir, builderName, nodeName)
if err := ls.cfg.MkdirAll(refDir, 0700); err != nil {
return err
}
dt, err := json.Marshal(st)
if err != nil {
return err
}
return ioutils.AtomicWriteFile(filepath.Join(refDir, id), dt, 0600)
return ls.cfg.AtomicWriteFile(filepath.Join(refDir, id), dt, 0644)
}
func (ls *LocalState) ReadGroup(id string) (*StateGroup, error) {
dt, err := os.ReadFile(filepath.Join(ls.root, refsDir, groupDir, id))
dt, err := os.ReadFile(filepath.Join(ls.cfg.Dir(), refsDir, groupDir, id))
if err != nil {
return nil, err
}
@ -100,15 +100,15 @@ func (ls *LocalState) ReadGroup(id string) (*StateGroup, error) {
}
func (ls *LocalState) SaveGroup(id string, stg StateGroup) error {
refDir := filepath.Join(ls.root, refsDir, groupDir)
if err := os.MkdirAll(refDir, 0700); err != nil {
refDir := filepath.Join(refsDir, groupDir)
if err := ls.cfg.MkdirAll(refDir, 0700); err != nil {
return err
}
dt, err := json.Marshal(stg)
if err != nil {
return err
}
return ioutils.AtomicWriteFile(filepath.Join(refDir, id), dt, 0600)
return ls.cfg.AtomicWriteFile(filepath.Join(refDir, id), dt, 0600)
}
func (ls *LocalState) RemoveBuilder(builderName string) error {
@ -116,7 +116,7 @@ func (ls *LocalState) RemoveBuilder(builderName string) error {
return errors.Errorf("builder name empty")
}
dir := filepath.Join(ls.root, refsDir, builderName)
dir := filepath.Join(ls.cfg.Dir(), refsDir, builderName)
if _, err := os.Lstat(dir); err != nil {
if !os.IsNotExist(err) {
return err
@ -147,7 +147,7 @@ func (ls *LocalState) RemoveBuilderNode(builderName string, nodeName string) err
return errors.Errorf("node name empty")
}
dir := filepath.Join(ls.root, refsDir, builderName, nodeName)
dir := filepath.Join(ls.cfg.Dir(), refsDir, builderName, nodeName)
if _, err := os.Lstat(dir); err != nil {
if !os.IsNotExist(err) {
return err
@ -208,7 +208,7 @@ func (ls *LocalState) removeGroup(id string) error {
if id == "" {
return errors.Errorf("group ref empty")
}
f := filepath.Join(ls.root, refsDir, groupDir, id)
f := filepath.Join(ls.cfg.Dir(), refsDir, groupDir, id)
if _, err := os.Lstat(f); err != nil {
if !os.IsNotExist(err) {
return err

View File

@ -4,6 +4,7 @@ import (
"path/filepath"
"testing"
"github.com/docker/buildx/util/confutil"
"github.com/stretchr/testify/require"
)
@ -39,10 +40,10 @@ func newls(t *testing.T) *LocalState {
t.Helper()
tmpdir := t.TempDir()
l, err := New(tmpdir)
l, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
require.NoError(t, err)
require.DirExists(t, filepath.Join(tmpdir, refsDir))
require.Equal(t, tmpdir, l.root)
require.Equal(t, tmpdir, l.cfg.Dir())
require.NoError(t, l.SaveRef(testBuilderName, testNodeName, testStateRefID, testStateRef))

View File

@ -8,7 +8,7 @@ import (
"time"
"github.com/docker/buildx/localstate"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/buildx/util/confutil"
"github.com/gofrs/flock"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
@ -20,25 +20,25 @@ const (
activityDir = "activity"
)
func New(root string) (*Store, error) {
if err := os.MkdirAll(filepath.Join(root, instanceDir), 0700); err != nil {
func New(cfg *confutil.Config) (*Store, error) {
if err := cfg.MkdirAll(instanceDir, 0700); err != nil {
return nil, err
}
if err := os.MkdirAll(filepath.Join(root, defaultsDir), 0700); err != nil {
if err := cfg.MkdirAll(defaultsDir, 0700); err != nil {
return nil, err
}
if err := os.MkdirAll(filepath.Join(root, activityDir), 0700); err != nil {
if err := cfg.MkdirAll(activityDir, 0700); err != nil {
return nil, err
}
return &Store{root: root}, nil
return &Store{cfg: cfg}, nil
}
type Store struct {
root string
cfg *confutil.Config
}
func (s *Store) Txn() (*Txn, func(), error) {
l := flock.New(filepath.Join(s.root, ".lock"))
l := flock.New(filepath.Join(s.cfg.Dir(), ".lock"))
if err := l.Lock(); err != nil {
return nil, nil, err
}
@ -54,7 +54,7 @@ type Txn struct {
}
func (t *Txn) List() ([]*NodeGroup, error) {
pp := filepath.Join(t.s.root, instanceDir)
pp := filepath.Join(t.s.cfg.Dir(), instanceDir)
fis, err := os.ReadDir(pp)
if err != nil {
return nil, err
@ -84,7 +84,7 @@ func (t *Txn) NodeGroupByName(name string) (*NodeGroup, error) {
if err != nil {
return nil, err
}
dt, err := os.ReadFile(filepath.Join(t.s.root, instanceDir, name))
dt, err := os.ReadFile(filepath.Join(t.s.cfg.Dir(), instanceDir, name))
if err != nil {
return nil, err
}
@ -110,7 +110,7 @@ func (t *Txn) Save(ng *NodeGroup) error {
if err != nil {
return err
}
return ioutils.AtomicWriteFile(filepath.Join(t.s.root, instanceDir, name), dt, 0600)
return t.s.cfg.AtomicWriteFile(filepath.Join(instanceDir, name), dt, 0600)
}
func (t *Txn) Remove(name string) error {
@ -121,14 +121,14 @@ func (t *Txn) Remove(name string) error {
if err := t.RemoveLastActivity(name); err != nil {
return err
}
ls, err := localstate.New(t.s.root)
ls, err := localstate.New(t.s.cfg)
if err != nil {
return err
}
if err := ls.RemoveBuilder(name); err != nil {
return err
}
return os.RemoveAll(filepath.Join(t.s.root, instanceDir, name))
return os.RemoveAll(filepath.Join(t.s.cfg.Dir(), instanceDir, name))
}
func (t *Txn) SetCurrent(key, name string, global, def bool) error {
@ -141,28 +141,28 @@ func (t *Txn) SetCurrent(key, name string, global, def bool) error {
if err != nil {
return err
}
if err := ioutils.AtomicWriteFile(filepath.Join(t.s.root, "current"), dt, 0600); err != nil {
if err := t.s.cfg.AtomicWriteFile("current", dt, 0600); err != nil {
return err
}
h := toHash(key)
if def {
if err := ioutils.AtomicWriteFile(filepath.Join(t.s.root, defaultsDir, h), []byte(name), 0600); err != nil {
if err := t.s.cfg.AtomicWriteFile(filepath.Join(defaultsDir, h), []byte(name), 0600); err != nil {
return err
}
} else {
os.RemoveAll(filepath.Join(t.s.root, defaultsDir, h)) // ignore error
os.RemoveAll(filepath.Join(t.s.cfg.Dir(), defaultsDir, h)) // ignore error
}
return nil
}
func (t *Txn) UpdateLastActivity(ng *NodeGroup) error {
return ioutils.AtomicWriteFile(filepath.Join(t.s.root, activityDir, ng.Name), []byte(time.Now().UTC().Format(time.RFC3339)), 0600)
return t.s.cfg.AtomicWriteFile(filepath.Join(activityDir, ng.Name), []byte(time.Now().UTC().Format(time.RFC3339)), 0600)
}
func (t *Txn) GetLastActivity(ng *NodeGroup) (la time.Time, _ error) {
dt, err := os.ReadFile(filepath.Join(t.s.root, activityDir, ng.Name))
dt, err := os.ReadFile(filepath.Join(t.s.cfg.Dir(), activityDir, ng.Name))
if err != nil {
if os.IsNotExist(errors.Cause(err)) {
return la, nil
@ -177,7 +177,7 @@ func (t *Txn) RemoveLastActivity(name string) error {
if err != nil {
return err
}
return os.RemoveAll(filepath.Join(t.s.root, activityDir, name))
return os.RemoveAll(filepath.Join(t.s.cfg.Dir(), activityDir, name))
}
func (t *Txn) reset(key string) error {
@ -185,11 +185,11 @@ func (t *Txn) reset(key string) error {
if err != nil {
return err
}
return ioutils.AtomicWriteFile(filepath.Join(t.s.root, "current"), dt, 0600)
return t.s.cfg.AtomicWriteFile("current", dt, 0600)
}
func (t *Txn) Current(key string) (*NodeGroup, error) {
dt, err := os.ReadFile(filepath.Join(t.s.root, "current"))
dt, err := os.ReadFile(filepath.Join(t.s.cfg.Dir(), "current"))
if err != nil {
if !os.IsNotExist(err) {
return nil, err
@ -220,7 +220,7 @@ func (t *Txn) Current(key string) (*NodeGroup, error) {
h := toHash(key)
dt, err = os.ReadFile(filepath.Join(t.s.root, defaultsDir, h))
dt, err = os.ReadFile(filepath.Join(t.s.cfg.Dir(), defaultsDir, h))
if err != nil {
if os.IsNotExist(err) {
t.reset(key)

View File

@ -5,6 +5,7 @@ import (
"testing"
"time"
"github.com/docker/buildx/util/confutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
@ -15,7 +16,7 @@ func TestEmptyStartup(t *testing.T) {
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
s, err := New(tmpdir)
s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
require.NoError(t, err)
txn, release, err := s.Txn()
@ -33,7 +34,7 @@ func TestNodeLocking(t *testing.T) {
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
s, err := New(tmpdir)
s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
require.NoError(t, err)
_, release, err := s.Txn()
@ -68,7 +69,7 @@ func TestNodeManagement(t *testing.T) {
require.NoError(t, err)
defer os.RemoveAll(tmpdir)
s, err := New(tmpdir)
s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
require.NoError(t, err)
txn, release, err := s.Txn()
@ -240,7 +241,7 @@ func TestNodeInvalidName(t *testing.T) {
t.Parallel()
tmpdir := t.TempDir()
s, err := New(tmpdir)
s, err := New(confutil.NewConfig(nil, confutil.WithDir(tmpdir)))
require.NoError(t, err)
txn, release, err := s.Txn()

View File

@ -17,7 +17,7 @@ import (
// GetStore returns current builder instance store
func GetStore(dockerCli command.Cli) (*store.Txn, func(), error) {
s, err := store.New(confutil.ConfigDir(dockerCli))
s, err := store.New(confutil.NewConfig(dockerCli))
if err != nil {
return nil, nil, err
}

View File

@ -17,6 +17,7 @@ import (
"github.com/containerd/platforms"
"github.com/creack/pty"
"github.com/docker/buildx/localstate"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/gitutil"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/frontend/subrequests/lint"
@ -167,7 +168,7 @@ COPY --from=base /etc/bar /bar
err = json.Unmarshal(dt, &md)
require.NoError(t, err)
ls, err := localstate.New(buildxConfig(sb))
ls, err := localstate.New(confutil.NewConfig(nil, confutil.WithDir(buildxConfig(sb))))
require.NoError(t, err)
refParts := strings.Split(md.BuildRef, "/")
@ -209,7 +210,7 @@ COPY --from=base /etc/bar /bar
err = json.Unmarshal(dt, &md)
require.NoError(t, err)
ls, err := localstate.New(buildxConfig(sb))
ls, err := localstate.New(confutil.NewConfig(nil, confutil.WithDir(buildxConfig(sb))))
require.NoError(t, err)
refParts := strings.Split(md.BuildRef, "/")
@ -261,7 +262,7 @@ COPY foo /foo
err = json.Unmarshal(dt, &md)
require.NoError(t, err)
ls, err := localstate.New(buildxConfig(sb))
ls, err := localstate.New(confutil.NewConfig(nil, confutil.WithDir(buildxConfig(sb))))
require.NoError(t, err)
refParts := strings.Split(md.BuildRef, "/")

View File

@ -1,39 +1,143 @@
package confutil
import (
"crypto/rand"
"encoding/hex"
"os"
"path"
"path/filepath"
"sync"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/pkg/ioutils"
"github.com/pelletier/go-toml"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
fs "github.com/tonistiigi/fsutil/copy"
)
// ConfigDir will look for correct configuration store path;
// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory
// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`)
func ConfigDir(dockerCli command.Cli) string {
if buildxConfig := os.Getenv("BUILDX_CONFIG"); buildxConfig != "" {
logrus.Debugf("using config store %q based in \"$BUILDX_CONFIG\" environment variable", buildxConfig)
return buildxConfig
}
const defaultBuildKitConfigFile = "buildkitd.default.toml"
buildxConfig := filepath.Join(filepath.Dir(dockerCli.ConfigFile().Filename), "buildx")
logrus.Debugf("using default config store %q", buildxConfig)
return buildxConfig
type Config struct {
dir string
chowner *chowner
}
// DefaultConfigFile returns the default BuildKit configuration file path
func DefaultConfigFile(dockerCli command.Cli) (string, bool) {
f := path.Join(ConfigDir(dockerCli), "buildkitd.default.toml")
type chowner struct {
uid int
gid int
}
type ConfigOption func(*configOptions)
type configOptions struct {
dir string
}
func WithDir(dir string) ConfigOption {
return func(o *configOptions) {
o.dir = dir
}
}
func NewConfig(dockerCli command.Cli, opts ...ConfigOption) *Config {
co := configOptions{}
for _, opt := range opts {
opt(&co)
}
configDir := co.dir
if configDir == "" {
configDir = os.Getenv("BUILDX_CONFIG")
if configDir == "" {
configDir = filepath.Join(filepath.Dir(dockerCli.ConfigFile().Filename), "buildx")
}
}
return &Config{
dir: configDir,
chowner: sudoer(configDir),
}
}
// Dir will look for correct configuration store path;
// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory
// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`)
func (c *Config) Dir() string {
return c.dir
}
// BuildKitConfigFile returns the default BuildKit configuration file path
func (c *Config) BuildKitConfigFile() (string, bool) {
f := filepath.Join(c.dir, defaultBuildKitConfigFile)
if _, err := os.Stat(f); err == nil {
return f, true
}
return "", false
}
// MkdirAll creates a directory and all necessary parents within the config dir.
func (c *Config) MkdirAll(dir string, perm os.FileMode) error {
var chown fs.Chowner
if c.chowner != nil {
chown = func(user *fs.User) (*fs.User, error) {
return &fs.User{UID: c.chowner.uid, GID: c.chowner.gid}, nil
}
}
d := filepath.Join(c.dir, dir)
st, err := os.Stat(d)
if err != nil {
if os.IsNotExist(err) {
return fs.MkdirAll(d, perm, chown, nil)
}
return err
}
// if directory already exists, fix the owner if necessary
if c.chowner == nil {
return nil
}
currentOwner := fileOwner(st)
if currentOwner != nil && (currentOwner.uid != c.chowner.uid || currentOwner.gid != c.chowner.gid) {
return os.Chown(d, c.chowner.uid, c.chowner.gid)
}
return nil
}
// AtomicWriteFile writes data to a file within the config dir atomically
func (c *Config) AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
f := filepath.Join(c.dir, filename)
if err := ioutils.AtomicWriteFile(f, data, perm); err != nil {
return err
}
if c.chowner == nil {
return nil
}
return os.Chown(f, c.chowner.uid, c.chowner.gid)
}
var nodeIdentifierMu sync.Mutex
func (c *Config) TryNodeIdentifier() (out string) {
nodeIdentifierMu.Lock()
defer nodeIdentifierMu.Unlock()
sessionFilename := ".buildNodeID"
sessionFilepath := filepath.Join(c.Dir(), sessionFilename)
if _, err := os.Lstat(sessionFilepath); err != nil {
if os.IsNotExist(err) { // create a new file with stored randomness
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
return out
}
if err := c.AtomicWriteFile(sessionFilename, []byte(hex.EncodeToString(b)), 0600); err != nil {
return out
}
}
}
dt, err := os.ReadFile(sessionFilepath)
if err == nil {
return string(dt)
}
return
}
// LoadConfigTree loads BuildKit config toml tree
func LoadConfigTree(fp string) (*toml.Tree, error) {
f, err := os.Open(fp)

View 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
}

View 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)
})
}
}

View File

@ -0,0 +1,11 @@
package confutil
import "os"
func sudoer(_ string) *chowner {
return nil
}
func fileOwner(_ os.FileInfo) *chowner {
return nil
}

View File

@ -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
View 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
View 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
View 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
View 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.
[![Go Reference](https://pkg.go.dev/badge/hg.sr.ht/~dchapes/mode.svg)](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
View 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
}

View 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
View 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
View 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)
}

View 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)
}

View 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
View 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)
}

View 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
View 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
}

View 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
View 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
}

View 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
}

View 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
View 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
View 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
}

View 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
View 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
View 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
View File

@ -710,9 +710,13 @@ github.com/theupdateframework/notary/tuf/data
github.com/theupdateframework/notary/tuf/signed
github.com/theupdateframework/notary/tuf/utils
github.com/theupdateframework/notary/tuf/validation
# github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205
## explicit; go 1.21
github.com/tonistiigi/dchapes-mode
# github.com/tonistiigi/fsutil v0.0.0-20241003195857-3f140a1299b0
## explicit; go 1.21
github.com/tonistiigi/fsutil
github.com/tonistiigi/fsutil/copy
github.com/tonistiigi/fsutil/types
# github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
## explicit; go 1.16