diff --git a/copier/copier.go b/copier/copier.go index 4e8b5f2b7..bd082396a 100644 --- a/copier/copier.go +++ b/copier/copier.go @@ -201,7 +201,7 @@ func (req *request) UIDMap() []idtools.IDMap { case requestEval: return nil case requestStat: - return nil + return req.StatOptions.UIDMap case requestGet: return req.GetOptions.UIDMap case requestPut: @@ -226,7 +226,7 @@ func (req *request) GIDMap() []idtools.IDMap { case requestEval: return nil case requestStat: - return nil + return req.StatOptions.GIDMap case requestGet: return req.GetOptions.GIDMap case requestPut: @@ -284,6 +284,7 @@ type StatForItem struct { Size int64 // dereferenced value for symlinks Mode os.FileMode // dereferenced value for symlinks ModTime time.Time // dereferenced value for symlinks + UID, GID int64 // usually in the uint32 range, set to -1 if unknown IsSymlink bool IsDir bool // dereferenced value for symlinks IsRegular bool // dereferenced value for symlinks @@ -342,8 +343,9 @@ func Eval(root string, directory string, _ EvalOptions) (string, error) { // StatOptions controls parts of Stat()'s behavior. type StatOptions struct { - CheckForArchives bool // check for and populate the IsArchive bit in returned values - Excludes []string // contents to pretend don't exist, using the OS-specific path separator + UIDMap, GIDMap []idtools.IDMap // map from hostIDs to containerIDs when returning results + CheckForArchives bool // check for and populate the IsArchive bit in returned values + Excludes []string // contents to pretend don't exist, using the OS-specific path separator } // Stat globs the specified pattern in the specified directory and returns its @@ -975,7 +977,7 @@ func copierHandler(bulkReader io.Reader, bulkWriter io.Writer, req request) (*re resp := copierHandlerEval(req) return resp, nil, nil case requestStat: - resp := copierHandlerStat(req, pm) + resp := copierHandlerStat(req, pm, idMappings) return resp, nil, nil case requestGet: return copierHandlerGet(bulkWriter, req, pm, idMappings) @@ -1102,7 +1104,7 @@ func copierHandlerEval(req request) *response { return &response{Eval: evalResponse{Evaluated: filepath.Join(req.rootPrefix, resolvedTarget)}} } -func copierHandlerStat(req request, pm *fileutils.PatternMatcher) *response { +func copierHandlerStat(req request, pm *fileutils.PatternMatcher, idMappings *idtools.IDMappings) *response { errorResponse := func(fmtspec string, args ...any) *response { return &response{Error: fmt.Sprintf(fmtspec, args...), Stat: statResponse{}} } @@ -1160,6 +1162,17 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher) *response { } result.Size = linfo.Size() result.Mode = linfo.Mode() + result.UID, result.GID = -1, -1 + if uid, gid, err := owner(linfo); err == nil { + if idMappings != nil && !idMappings.Empty() { + hostPair := idtools.IDPair{UID: uid, GID: gid} + uid, gid, err = idMappings.ToContainer(hostPair) + if err != nil { + return errorResponse("copier: stat: mapping host filesystem owners %#v to container filesystem owners: %w", hostPair, err) + } + } + result.UID, result.GID = int64(uid), int64(gid) + } result.ModTime = linfo.ModTime() result.IsDir = linfo.IsDir() result.IsRegular = result.Mode.IsRegular() @@ -1272,7 +1285,7 @@ func checkLinks(item string, req request, info os.FileInfo) (string, os.FileInfo func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMatcher, idMappings *idtools.IDMappings) (*response, func() error, error) { statRequest := req statRequest.Request = requestStat - statResponse := copierHandlerStat(req, pm) + statResponse := copierHandlerStat(req, pm, idMappings) errorResponse := func(fmtspec string, args ...any) (*response, func() error, error) { return &response{Error: fmt.Sprintf(fmtspec, args...), Stat: statResponse.Stat, Get: getResponse{}}, nil, nil } @@ -2270,7 +2283,7 @@ type EnsurePath struct { // EnsureOptions controls parts of Ensure()'s behavior. type EnsureOptions struct { - UIDMap, GIDMap []idtools.IDMap // map from hostIDs to containerIDs in the chroot + UIDMap, GIDMap []idtools.IDMap // map from containerIDs to hostIDs in the chroot Paths []EnsurePath } @@ -2437,7 +2450,7 @@ type ConditionalRemovePath struct { // ConditionalRemoveOptions controls parts of ConditionalRemove()'s behavior. type ConditionalRemoveOptions struct { - UIDMap, GIDMap []idtools.IDMap // map from hostIDs to containerIDs in the chroot + UIDMap, GIDMap []idtools.IDMap // map from containerIDs to hostIDs in the chroot Paths []ConditionalRemovePath } diff --git a/copier/copier_test.go b/copier/copier_test.go index b8589a6f9..abc2ead73 100644 --- a/copier/copier_test.go +++ b/copier/copier_test.go @@ -716,6 +716,7 @@ func testStat(t *testing.T) { if actualContent, ok := testArchive.contents[testItem.Name]; ok { testItem.Size = int64(len(actualContent)) } + checkStatInfoOwnership(t, result) require.Equal(t, testItem.Size, result.Size, "unexpected size difference for %q", name) require.True(t, result.IsRegular, "expected %q.IsRegular to be true", glob) require.False(t, result.IsDir, "expected %q.IsDir to be false", glob) diff --git a/copier/copier_unix_test.go b/copier/copier_unix_test.go index 722672d79..2da316660 100644 --- a/copier/copier_unix_test.go +++ b/copier/copier_unix_test.go @@ -5,6 +5,8 @@ package copier import ( "os" "testing" + + "github.com/stretchr/testify/require" ) const ( @@ -101,3 +103,9 @@ func TestConditionalRemoveChroot(t *testing.T) { testConditionalRemove(t) canChroot = couldChroot } + +func checkStatInfoOwnership(t *testing.T, result *StatForItem) { + t.Helper() + require.EqualValues(t, 0, result.UID, "expected the owning user to be reported") + require.EqualValues(t, 0, result.GID, "expected the owning group to be reported") +} diff --git a/copier/copier_windows_test.go b/copier/copier_windows_test.go index 8533a324a..efaf31d15 100644 --- a/copier/copier_windows_test.go +++ b/copier/copier_windows_test.go @@ -2,7 +2,14 @@ package copier -const ( - testModeMask = int64(0o600) - testIgnoreSymlinkDates = true +import ( + "testing" + + "github.com/stretchr/testify/require" ) + +func checkStatInfoOwnership(t *testing.T, result *StatForItem) { + t.Helper() + require.EqualValues(t, -1, result.UID, "expected the owning user to not be supported") + require.EqualValues(t, -1, result.GID, "expected the owning group to not be supported") +}