Skip to content

Commit 42d5139

Browse files
committed
Add support for symbolic chmod options
This adds an optional Chmod string option to GetOptions and PutOptions. If set, this overrides the ChmodDirs and ChmodFiles options. If unset, they continue to work as before. The --chmod option to buildah add and buildah copy works with symbolic as well as numeric arguments. Fixes #6066 Signed-off-by: Nick White <git@njw.name>
1 parent 09917c4 commit 42d5139

16 files changed

Lines changed: 992 additions & 35 deletions

File tree

add.go

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
v1 "github.com/opencontainers/image-spec/specs-go/v1"
3131
"github.com/opencontainers/runtime-spec/specs-go"
3232
"github.com/sirupsen/logrus"
33+
"github.com/tonistiigi/dchapes-mode"
3334
"go.podman.io/common/pkg/retry"
3435
"go.podman.io/image/v5/pkg/tlsclientconfig"
3536
"go.podman.io/image/v5/types"
@@ -138,7 +139,7 @@ func sourceIsRemote(source string) bool {
138139
}
139140

140141
// getURL writes a tar archive containing the named content
141-
func getURL(src string, chown *idtools.IDPair, mountpoint, renameTarget string, writer io.Writer, chmod *os.FileMode, srcDigest digest.Digest, certPath string, insecureSkipTLSVerify types.OptionalBool, timestamp *time.Time) error {
142+
func getURL(src string, chown *idtools.IDPair, mountpoint, renameTarget string, writer io.Writer, chmod string, srcDigest digest.Digest, certPath string, insecureSkipTLSVerify types.OptionalBool, timestamp *time.Time) error {
142143
url, err := url.Parse(src)
143144
if err != nil {
144145
return err
@@ -227,17 +228,21 @@ func getURL(src string, chown *idtools.IDPair, mountpoint, renameTarget string,
227228
uid = chown.UID
228229
gid = chown.GID
229230
}
230-
var mode int64 = 0o600
231-
if chmod != nil {
232-
mode = int64(*chmod)
231+
var mod int64 = 0o600
232+
if chmod != "" {
233+
p, err := mode.Parse(chmod)
234+
if err != nil {
235+
return fmt.Errorf("parsing chmod %q: %w", chmod, err)
236+
}
237+
mod = int64(p.Apply(os.FileMode(mod)))
233238
}
234239
hdr := tar.Header{
235240
Typeflag: tar.TypeReg,
236241
Name: name,
237242
Size: size,
238243
Uid: uid,
239244
Gid: gid,
240-
Mode: mode,
245+
Mode: mod,
241246
ModTime: date,
242247
}
243248
err = tw.WriteHeader(&hdr)
@@ -413,15 +418,6 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
413418
return fmt.Errorf("looking up UID/GID for %q: %w", options.Chown, err)
414419
}
415420
}
416-
var chmodDirsFiles *os.FileMode
417-
if options.Chmod != "" {
418-
p, err := strconv.ParseUint(options.Chmod, 8, 32)
419-
if err != nil {
420-
return fmt.Errorf("parsing chmod %q: %w", options.Chmod, err)
421-
}
422-
perm := os.FileMode(p)
423-
chmodDirsFiles = &perm
424-
}
425421

426422
chownDirs = &idtools.IDPair{UID: int(userUID), GID: int(userGID)}
427423
chownFiles = &idtools.IDPair{UID: int(userUID), GID: int(userGID)}
@@ -614,10 +610,9 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
614610
GIDMap: srcGIDMap,
615611
Excludes: options.Excludes,
616612
ExpandArchives: extract,
613+
Chmod: options.Chmod,
617614
ChownDirs: chownDirs,
618-
ChmodDirs: chmodDirsFiles,
619615
ChownFiles: chownFiles,
620-
ChmodFiles: chmodDirsFiles,
621616
StripSetuidBit: options.StripSetuidBit,
622617
StripSetgidBit: options.StripSetgidBit,
623618
StripStickyBit: options.StripStickyBit,
@@ -630,7 +625,7 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
630625
} else {
631626
go func() {
632627
getErr = retry.IfNecessary(context.TODO(), func() error {
633-
return getURL(src, chownFiles, mountPoint, renameTarget, pipeWriter, chmodDirsFiles, srcDigest, options.CertPath, options.InsecureSkipTLSVerify, options.Timestamp)
628+
return getURL(src, chownFiles, mountPoint, renameTarget, pipeWriter, options.Chmod, srcDigest, options.CertPath, options.InsecureSkipTLSVerify, options.Timestamp)
634629
}, &retry.Options{
635630
MaxRetry: options.MaxRetries,
636631
Delay: options.RetryDelay,
@@ -781,10 +776,9 @@ func (b *Builder) Add(destination string, extract bool, options AddAndCopyOption
781776
GIDMap: srcGIDMap,
782777
Excludes: options.Excludes,
783778
ExpandArchives: extract,
779+
Chmod: options.Chmod,
784780
ChownDirs: chownDirs,
785-
ChmodDirs: chmodDirsFiles,
786781
ChownFiles: chownFiles,
787-
ChmodFiles: chmodDirsFiles,
788782
StripSetuidBit: options.StripSetuidBit,
789783
StripSetgidBit: options.StripSetgidBit,
790784
StripStickyBit: options.StripStickyBit,

commit_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,3 +575,88 @@ func TestCommitEmpty(t *testing.T) {
575575
require.Equalf(t, layerDigest.Digest(), image.RootFS.DiffIDs[len(image.RootFS.DiffIDs)-1], "expected new diff ID to match the randomly-generated layer")
576576
})
577577
}
578+
579+
func TestCommitChmod(t *testing.T) {
580+
ctx := context.TODO()
581+
graphDriverName := os.Getenv("STORAGE_DRIVER")
582+
if graphDriverName == "" {
583+
graphDriverName = "vfs"
584+
}
585+
t.Logf("using storage driver %q", graphDriverName)
586+
store, err := storage.GetStore(storageTypes.StoreOptions{
587+
RunRoot: t.TempDir(),
588+
GraphRoot: t.TempDir(),
589+
GraphDriverName: graphDriverName,
590+
})
591+
require.NoError(t, err, "initializing storage")
592+
t.Cleanup(func() { _, err := store.Shutdown(true); assert.NoError(t, err) })
593+
594+
// Build a from-scratch image with one layer.
595+
builderOptions := BuilderOptions{
596+
FromImage: "scratch",
597+
NamespaceOptions: []NamespaceOption{{
598+
Name: string(rspec.NetworkNamespace),
599+
Host: true,
600+
}},
601+
SystemContext: &testSystemContext,
602+
}
603+
b, err := NewBuilder(ctx, store, builderOptions)
604+
require.NoError(t, err, "creating builder")
605+
imgName := "image0"
606+
b.SetCreatedBy(imgName)
607+
608+
type perms struct {
609+
chmod string
610+
perm int64
611+
startMode os.FileMode
612+
}
613+
614+
filePerms := map[string]perms{
615+
"symbolic": {chmod: "u=rwX,go=rX", perm: 0o644},
616+
"symbolic adding mode bits": {chmod: "u=rwX,go=rX", perm: 0o755, startMode: 0o111},
617+
"octal": {chmod: "750", perm: 0o750},
618+
"octal overwrite start mode": {chmod: "644", perm: 0o644, startMode: 0o753},
619+
"clear group and other mode bits": {chmod: "go=", perm: 0o700, startMode: 0o777},
620+
}
621+
for name, v := range filePerms {
622+
f := makeFile(t, name, 0)
623+
if v.startMode != 0 {
624+
err = os.Chmod(f, v.startMode)
625+
require.NoError(t, err, "chmod", f)
626+
}
627+
err = b.Add("/", false, AddAndCopyOptions{Chmod: v.chmod}, f)
628+
require.NoError(t, err, "adding", f)
629+
}
630+
631+
commitOptions := CommitOptions{
632+
SystemContext: &testSystemContext,
633+
}
634+
ref, err := imageStorage.Transport.ParseStoreReference(store, imgName)
635+
require.NoError(t, err, "parsing reference for to-be-committed image", imgName)
636+
_, _, _, err = b.Commit(ctx, ref, commitOptions)
637+
require.NoError(t, err, "committing", imgName)
638+
639+
src, err := ref.NewImageSource(ctx, &testSystemContext)
640+
require.NoError(t, err, "opening image source")
641+
defer src.Close()
642+
img, err := ref.NewImage(ctx, &testSystemContext)
643+
require.NoError(t, err, "opening image")
644+
defer img.Close()
645+
646+
infos, err := img.LayerInfosForCopy(ctx)
647+
require.NoError(t, err, "getting layer infos")
648+
649+
for i, blobInfo := range infos {
650+
rc, _, err := src.GetBlob(ctx, blobInfo, nil)
651+
require.NoError(t, err, "getting blob", i)
652+
defer rc.Close()
653+
tr := tar.NewReader(rc)
654+
entry, err := tr.Next()
655+
for entry != nil {
656+
expected := filePerms[entry.Name]
657+
require.Equalf(t, entry.Mode, expected.perm, "unexpected mode for %s, got %#od wanted %#od", entry.Name, entry.Mode, expected.perm)
658+
entry, err = tr.Next()
659+
}
660+
require.ErrorIs(t, err, io.EOF)
661+
}
662+
}

copier/copier.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"unicode"
2424

2525
"github.com/sirupsen/logrus"
26+
"github.com/tonistiigi/dchapes-mode"
2627
"go.podman.io/image/v5/pkg/compression"
2728
"go.podman.io/storage/pkg/archive"
2829
"go.podman.io/storage/pkg/fileutils"
@@ -383,6 +384,7 @@ type GetOptions struct {
383384
UIDMap, GIDMap []idtools.IDMap // map from hostIDs to containerIDs in the output archive
384385
Excludes []string // contents to pretend don't exist, using the OS-specific path separator
385386
ExpandArchives bool // extract the contents of named items that are archives
387+
Chmod string // set permissions in octal or symbolic notation. overrides ChmodDirs and ChmodFiles if set. no effect on archives being extracted
386388
ChownDirs *idtools.IDPair // set ownership on directories. no effect on archives being extracted
387389
ChmodDirs *os.FileMode // set permissions on directories. no effect on archives being extracted
388390
ChownFiles *idtools.IDPair // set ownership of files. no effect on archives being extracted
@@ -437,7 +439,8 @@ func Get(root string, directory string, options GetOptions, globs []string, bulk
437439
type PutOptions struct {
438440
UIDMap, GIDMap []idtools.IDMap // map from containerIDs to hostIDs when writing contents to disk
439441
DefaultDirOwner *idtools.IDPair // set ownership of implicitly-created directories, default is ChownDirs, or 0:0 if ChownDirs not set
440-
DefaultDirMode *os.FileMode // set permissions on implicitly-created directories, default is ChmodDirs, or 0755 if ChmodDirs not set
442+
DefaultDirMode *os.FileMode // set permissions on implicitly-created directories, default is Chmod or ChmodDirs, or 0755 if neither is set
443+
Chmod string // set permissions in octal or symbolic notation. overrides ChmodDirs and ChmodFiles if set
441444
ChownDirs *idtools.IDPair // set ownership of newly-created directories
442445
ChmodDirs *os.FileMode // set permissions on newly-created directories
443446
ChownFiles *idtools.IDPair // set ownership of newly-created files
@@ -1705,11 +1708,22 @@ func copierHandlerGetOne(srcfi os.FileInfo, symlinkTarget, name, contentPath str
17051708
}
17061709
}
17071710
// force ownership and/or permissions, if requested
1711+
if options.Chmod != "" {
1712+
p, err := mode.Parse(options.Chmod)
1713+
if err != nil {
1714+
return fmt.Errorf("parsing chmod %q: %w", options.Chmod, err)
1715+
}
1716+
stat, err := os.Lstat(contentPath)
1717+
if err != nil {
1718+
return fmt.Errorf("reading permissions of %q: %w", contentPath, err)
1719+
}
1720+
hdr.Mode = int64(p.Apply(stat.Mode()))
1721+
}
17081722
if hdr.Typeflag == tar.TypeDir {
17091723
if options.ChownDirs != nil {
17101724
hdr.Uid, hdr.Gid = options.ChownDirs.UID, options.ChownDirs.GID
17111725
}
1712-
if options.ChmodDirs != nil {
1726+
if options.ChmodDirs != nil && options.Chmod == "" {
17131727
hdr.Mode = int64(*options.ChmodDirs)
17141728
}
17151729
if !strings.HasSuffix(hdr.Name, "/") {
@@ -1719,7 +1733,7 @@ func copierHandlerGetOne(srcfi os.FileInfo, symlinkTarget, name, contentPath str
17191733
if options.ChownFiles != nil {
17201734
hdr.Uid, hdr.Gid = options.ChownFiles.UID, options.ChownFiles.GID
17211735
}
1722-
if options.ChmodFiles != nil {
1736+
if options.ChmodFiles != nil && options.Chmod == "" {
17231737
hdr.Mode = int64(*options.ChmodFiles)
17241738
}
17251739
}
@@ -1785,6 +1799,15 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM
17851799
if req.PutOptions.ChmodDirs != nil {
17861800
defaultDirMode = *req.PutOptions.ChmodDirs
17871801
}
1802+
var chmod *mode.Set
1803+
if req.PutOptions.Chmod != "" {
1804+
p, err := mode.Parse(req.PutOptions.Chmod)
1805+
if err != nil {
1806+
return errorResponse("parsing chmod %q: %v", req.PutOptions.Chmod, err)
1807+
}
1808+
chmod = &p
1809+
defaultDirMode = chmod.Apply(defaultDirMode)
1810+
}
17881811
if req.PutOptions.DefaultDirOwner != nil {
17891812
defaultDirUID, defaultDirGID = req.PutOptions.DefaultDirOwner.UID, req.PutOptions.DefaultDirOwner.GID
17901813
}
@@ -1976,13 +1999,17 @@ func copierHandlerPut(bulkReader io.Reader, req request, idMappings *idtools.IDM
19761999
if req.PutOptions.StripStickyBit && hdr.Mode&cISVTX == cISVTX {
19772000
hdr.Mode &^= cISVTX
19782001
}
1979-
if hdr.Typeflag == tar.TypeDir {
1980-
if req.PutOptions.ChmodDirs != nil {
1981-
hdr.Mode = int64(*req.PutOptions.ChmodDirs)
1982-
}
2002+
if chmod != nil {
2003+
hdr.Mode = int64(chmod.Apply(os.FileMode(hdr.Mode)))
19832004
} else {
1984-
if req.PutOptions.ChmodFiles != nil {
1985-
hdr.Mode = int64(*req.PutOptions.ChmodFiles)
2005+
if hdr.Typeflag == tar.TypeDir {
2006+
if req.PutOptions.ChmodDirs != nil {
2007+
hdr.Mode = int64(*req.PutOptions.ChmodDirs)
2008+
}
2009+
} else {
2010+
if req.PutOptions.ChmodFiles != nil {
2011+
hdr.Mode = int64(*req.PutOptions.ChmodFiles)
2012+
}
19862013
}
19872014
}
19882015
// create the new item

0 commit comments

Comments
 (0)