Skip to content

Commit

Permalink
add support for non-octal mode setting
Browse files Browse the repository at this point in the history
Signed-off-by: Tonis Tiigi <[email protected]>
  • Loading branch information
tonistiigi committed Sep 28, 2024
1 parent 8754824 commit de187bf
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 8 deletions.
26 changes: 20 additions & 6 deletions copy/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/moby/patternmatcher"
"github.com/pkg/errors"
"github.com/tonistiigi/fsutil"
"hg.sr.ht/~dchapes/mode"
)

var bufferPool = &sync.Pool{
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
73 changes: 73 additions & 0 deletions copy/copy_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"os"
"path/filepath"
"runtime"
"syscall"
"testing"

Expand Down Expand Up @@ -93,3 +94,75 @@ 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) {
if runtime.GOOS == "windows" {
t.Skip("skipping on windows")
}

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

if err := os.Chmod(name, fi.Mode()); err != nil {
return errors.Wrapf(err, "failed to chmod %s", name)
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
golang.org/x/sync v0.1.0
golang.org/x/sys v0.11.0
google.golang.org/protobuf v1.31.0
hg.sr.ht/~dchapes/mode v0.6.4
)

require (
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
Expand All @@ -43,3 +44,5 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
hg.sr.ht/~dchapes/mode v0.6.4 h1:Eb/r0ewCQL6HovTRGnRsz1NN+OtXRry2qSMp8Ikoh9E=
hg.sr.ht/~dchapes/mode v0.6.4/go.mod h1:grRSTqbe5t8QoD6bWuiljNvlHAgPPuaF9wQQgQbjexM=

0 comments on commit de187bf

Please sign in to comment.