
handleLChmod() does not properly check that files behind the handlinks exist before calling os.Chmod(). We've seen base images where this results in "no such file or directory" error from os.Chmod() when unpacking the image. To keep the existing logic but fix the problem, this commit simply skips IsNotExist error. Signed-off-by: Mikko Ylinen <mikko.ylinen@intel.com>
220 lines
5.7 KiB
Go
220 lines
5.7 KiB
Go
// +build !windows
|
|
|
|
/*
|
|
Copyright The containerd Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package archive
|
|
|
|
import (
|
|
"archive/tar"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/containerd/continuity/fs"
|
|
"github.com/containerd/continuity/sysx"
|
|
"github.com/opencontainers/runc/libcontainer/system"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func tarName(p string) (string, error) {
|
|
return p, nil
|
|
}
|
|
|
|
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
|
return perm
|
|
}
|
|
|
|
func setHeaderForSpecialDevice(hdr *tar.Header, name string, fi os.FileInfo) error {
|
|
s, ok := fi.Sys().(*syscall.Stat_t)
|
|
if !ok {
|
|
return errors.New("unsupported stat type")
|
|
}
|
|
|
|
// Rdev is int32 on darwin/bsd, int64 on linux/solaris
|
|
rdev := uint64(s.Rdev) // nolint: unconvert
|
|
|
|
// Currently go does not fill in the major/minors
|
|
if s.Mode&syscall.S_IFBLK != 0 ||
|
|
s.Mode&syscall.S_IFCHR != 0 {
|
|
hdr.Devmajor = int64(unix.Major(rdev))
|
|
hdr.Devminor = int64(unix.Minor(rdev))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func open(p string) (*os.File, error) {
|
|
return os.Open(p)
|
|
}
|
|
|
|
func openFile(name string, flag int, perm os.FileMode) (*os.File, error) {
|
|
f, err := os.OpenFile(name, flag, perm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Call chmod to avoid permission mask
|
|
if err := os.Chmod(name, perm); err != nil {
|
|
return nil, err
|
|
}
|
|
return f, err
|
|
}
|
|
|
|
func mkdir(path string, perm os.FileMode) error {
|
|
if err := os.Mkdir(path, perm); err != nil {
|
|
return err
|
|
}
|
|
// Only final created directory gets explicit permission
|
|
// call to avoid permission mask
|
|
return os.Chmod(path, perm)
|
|
}
|
|
|
|
var (
|
|
inUserNS bool
|
|
nsOnce sync.Once
|
|
)
|
|
|
|
func setInUserNS() {
|
|
inUserNS = system.RunningInUserNS()
|
|
}
|
|
|
|
func skipFile(hdr *tar.Header) bool {
|
|
switch hdr.Typeflag {
|
|
case tar.TypeBlock, tar.TypeChar:
|
|
// cannot create a device if running in user namespace
|
|
nsOnce.Do(setInUserNS)
|
|
return inUserNS
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// handleTarTypeBlockCharFifo is an OS-specific helper function used by
|
|
// createTarFile to handle the following types of header: Block; Char; Fifo.
|
|
// This function must not be called for Block and Char when running in userns.
|
|
// (skipFile() should return true for them.)
|
|
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
|
|
mode := uint32(hdr.Mode & 07777)
|
|
switch hdr.Typeflag {
|
|
case tar.TypeBlock:
|
|
mode |= unix.S_IFBLK
|
|
case tar.TypeChar:
|
|
mode |= unix.S_IFCHR
|
|
case tar.TypeFifo:
|
|
mode |= unix.S_IFIFO
|
|
}
|
|
|
|
return unix.Mknod(path, mode, int(unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor))))
|
|
}
|
|
|
|
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
|
|
if hdr.Typeflag == tar.TypeLink {
|
|
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
|
|
if err := os.Chmod(path, hdrInfo.Mode()); err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
}
|
|
} else if hdr.Typeflag != tar.TypeSymlink {
|
|
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getxattr(path, attr string) ([]byte, error) {
|
|
b, err := sysx.LGetxattr(path, attr)
|
|
if err == unix.ENOTSUP || err == sysx.ENODATA {
|
|
return nil, nil
|
|
}
|
|
return b, err
|
|
}
|
|
|
|
func setxattr(path, key, value string) error {
|
|
// Do not set trusted attributes
|
|
if strings.HasPrefix(key, "trusted.") {
|
|
return errors.Wrap(unix.ENOTSUP, "admin attributes from archive not supported")
|
|
}
|
|
return unix.Lsetxattr(path, key, []byte(value), 0)
|
|
}
|
|
|
|
func copyDirInfo(fi os.FileInfo, path string) error {
|
|
st := fi.Sys().(*syscall.Stat_t)
|
|
if err := os.Lchown(path, int(st.Uid), int(st.Gid)); err != nil {
|
|
if os.IsPermission(err) {
|
|
// Normally if uid/gid are the same this would be a no-op, but some
|
|
// filesystems may still return EPERM... for instance NFS does this.
|
|
// In such a case, this is not an error.
|
|
if dstStat, err2 := os.Lstat(path); err2 == nil {
|
|
st2 := dstStat.Sys().(*syscall.Stat_t)
|
|
if st.Uid == st2.Uid && st.Gid == st2.Gid {
|
|
err = nil
|
|
}
|
|
}
|
|
}
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to chown %s", path)
|
|
}
|
|
}
|
|
|
|
if err := os.Chmod(path, fi.Mode()); err != nil {
|
|
return errors.Wrapf(err, "failed to chmod %s", path)
|
|
}
|
|
|
|
timespec := []unix.Timespec{
|
|
unix.NsecToTimespec(syscall.TimespecToNsec(fs.StatAtime(st))),
|
|
unix.NsecToTimespec(syscall.TimespecToNsec(fs.StatMtime(st))),
|
|
}
|
|
if err := unix.UtimesNanoAt(unix.AT_FDCWD, path, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
|
return errors.Wrapf(err, "failed to utime %s", path)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func copyUpXAttrs(dst, src string) error {
|
|
xattrKeys, err := sysx.LListxattr(src)
|
|
if err != nil {
|
|
if err == unix.ENOTSUP || err == sysx.ENODATA {
|
|
return nil
|
|
}
|
|
return errors.Wrapf(err, "failed to list xattrs on %s", src)
|
|
}
|
|
for _, xattr := range xattrKeys {
|
|
// Do not copy up trusted attributes
|
|
if strings.HasPrefix(xattr, "trusted.") {
|
|
continue
|
|
}
|
|
data, err := sysx.LGetxattr(src, xattr)
|
|
if err != nil {
|
|
if err == unix.ENOTSUP || err == sysx.ENODATA {
|
|
continue
|
|
}
|
|
return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
|
}
|
|
if err := unix.Lsetxattr(dst, xattr, data, unix.XATTR_CREATE); err != nil {
|
|
if err == unix.ENOTSUP || err == unix.ENODATA || err == unix.EEXIST {
|
|
continue
|
|
}
|
|
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|