Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

copy: add support for non-octal mode setting #210

Merged
merged 1 commit into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bench/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/tonistiigi/fsutil/bench

go 1.20
go 1.21

require (
github.com/containerd/continuity v0.4.1
Expand Down
2 changes: 2 additions & 0 deletions bench/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
Expand Down Expand Up @@ -888,6 +889,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
Expand Down
26 changes: 20 additions & 6 deletions copy/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/containerd/continuity/fs"
"github.com/moby/patternmatcher"
"github.com/pkg/errors"
mode "github.com/tonistiigi/dchapes-mode"
"github.com/tonistiigi/fsutil"
)

Expand Down Expand Up @@ -83,12 +84,21 @@ func Copy(ctx context.Context, srcRoot, src, dstRoot, dst string, opts ...Opt) e
}
}

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, ci.XAttrErrorHandler, ci.IncludePatterns, ci.ExcludePatterns, ci.AlwaysReplaceExistingDestPaths, ci.ChangeFunc)
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
}
Expand Down Expand Up @@ -161,10 +171,12 @@ 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
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
Expand Down Expand Up @@ -234,6 +246,7 @@ type copier struct {
chown Chowner
utime *time.Time
mode *int
modeSet *mode.Set
inodes map[uint64]string
xattrErrorHandler XAttrErrorHandler
includePatternMatcher *patternmatcher.PatternMatcher
Expand All @@ -250,7 +263,7 @@ type parentDir struct {
copied bool
}

func newCopier(root string, chown Chowner, tm *time.Time, mode *int, xeh XAttrErrorHandler, includePatterns, excludePatterns []string, alwaysReplaceExistingDestPaths bool, changeFunc fsutil.ChangeFunc) (*copier, error) {
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
Expand Down Expand Up @@ -282,6 +295,7 @@ func newCopier(root string, chown Chowner, tm *time.Time, mode *int, xeh XAttrEr
utime: tm,
xattrErrorHandler: xeh,
mode: mode,
modeSet: modeSet,
includePatternMatcher: includePatternMatcher,
excludePatternMatcher: excludePatternMatcher,
changefn: changeFunc,
Expand Down
4 changes: 3 additions & 1 deletion copy/copy_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ func (c *copier) copyFileInfo(fi os.FileInfo, src, name string) error {
}

m := fi.Mode()
if c.mode != nil {
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
Expand Down
4 changes: 3 additions & 1 deletion copy/copy_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ func (c *copier) copyFileInfo(fi os.FileInfo, src, name string) error {
}

m := fi.Mode()
if c.mode != nil {
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
Expand Down
68 changes: 68 additions & 0 deletions copy/copy_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,71 @@ func TestCopySetuid(t *testing.T) {
assert.Equal(t, os.FileMode(0), fi.Mode()&os.ModeSetgid)
assert.Equal(t, os.FileMode(0), fi.Mode()&os.ModeSticky)
}

func TestCopyModeTextFormat(t *testing.T) {
t1 := t.TempDir()

err := os.WriteFile(filepath.Join(t1, "file"), []byte("hello"), 0644)
require.NoError(t, err)

err = os.WriteFile(filepath.Join(t1, "executable_file"), []byte("world"), 0755)
require.NoError(t, err)

err = os.Mkdir(filepath.Join(t1, "dir"), 0750)
require.NoError(t, err)

err = os.Mkdir(filepath.Join(t1, "restricted_dir"), 0700)
require.NoError(t, err)

testCases := []struct {
name string
modeStr string
expectedFilePerm os.FileMode
expectedExecPerm os.FileMode
expectedDirPerm os.FileMode
expectedRestrictDirPerm os.FileMode
}{
{"remove write for others", "go-w", 0644, 0755, 0750, 0700},
{"add execute for user", "u+x", 0744, 0755, 0750, 0700},
{"remove all permissions for group", "g-rwx", 0604, 0705, 0700, 0700},
{"add read for others", "o+r", 0644, 0755, 0754, 0704},
{"remove execute for all", "a-x", 0644, 0644, 0640, 0600},
{"remove others and add execute for group", "o-rwx,g+x", 0650, 0750, 0750, 0710},
{"capital X (apply execute only if directory)", "a+X", 0644, 0755, 0751, 0711},
{"remove execute and add write for user", "u-x,u+w", 0644, 0655, 0650, 0600},
{"add execute for user and others", "u+x,o+x", 0745, 0755, 0751, 0701},
{"add write and read for group and others", "g+rw,o+rw", 0666, 0777, 0776, 0766},
{"set read-only for all", "a=r", 0444, 0444, 0444, 0444},
{"set full permissions for user only", "u=rwx,g=,o=", 0700, 0700, 0700, 0700},
{"remove all permissions for others", "o-rwx", 0640, 0750, 0750, 0700},
{"remove read for group, add execute for all", "g-r,a+x", 0715, 0715, 0711, 0711},
{"complex permissions change", "u+rw,g+r,o-x,o+w", 0646, 0756, 0752, 0742},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t2 := t.TempDir()

err := Copy(context.TODO(), t1, ".", t2, ".", WithCopyInfo(CopyInfo{
ModeStr: tc.modeStr,
}))
require.NoError(t, err)

fi, err := os.Lstat(filepath.Join(t2, "file"))
require.NoError(t, err)
assert.Equal(t, tc.expectedFilePerm, fi.Mode().Perm(), "file %04o, got %04o", tc.expectedFilePerm, fi.Mode().Perm())

execFileInfo, err := os.Lstat(filepath.Join(t2, "executable_file"))
require.NoError(t, err)
assert.Equal(t, tc.expectedExecPerm, execFileInfo.Mode().Perm(), "executable file %04o, got %04o", tc.expectedExecPerm, execFileInfo.Mode().Perm())

dirInfo, err := os.Lstat(filepath.Join(t2, "dir"))
require.NoError(t, err)
assert.Equal(t, tc.expectedDirPerm, dirInfo.Mode().Perm(), "dir %04o, got %04o", tc.expectedDirPerm, dirInfo.Mode().Perm())

restrictDirInfo, err := os.Lstat(filepath.Join(t2, "restricted_dir"))
require.NoError(t, err)
assert.Equal(t, tc.expectedRestrictDirPerm, restrictDirInfo.Mode().Perm(), "restricted dir %04o, got %04o", tc.expectedRestrictDirPerm, restrictDirInfo.Mode().Perm())
})
}
}
4 changes: 4 additions & 0 deletions copy/copy_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ func getFileSecurityInfo(name string) (*windows.SID, *windows.ACL, error) {
}

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")
}
Comment on lines +40 to +42
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think a module exists for this but I think on Windows we could use golang.org/x/sys/windows to set corresponding Windows ACL permissions if we want that in the future (cc @gabriel-samfira)


if err := os.Chmod(name, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", name)
}
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/tonistiigi/fsutil

go 1.20
go 1.21

require (
github.com/Microsoft/go-winio v0.5.2
Expand All @@ -9,6 +9,7 @@ require (
github.com/opencontainers/go-digest v1.0.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.4
github.com/tonistiigi/dchapes-mode v0.0.0-20241001053921-ca0759fec205
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the original repository has a number of tagged versions already; should those be inherited? https://hg.sr.ht/~dchapes/mode/tags

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the same source. I don't plan to make my own releases in the fork.

golang.org/x/sync v0.1.0
golang.org/x/sys v0.11.0
google.golang.org/protobuf v1.31.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down