deps: update osbuild/images to 246b718310ea

Current main.
246b718310
This commit is contained in:
Achilleas Koutsou 2023-07-19 17:22:28 +02:00 committed by Ondřej Budai
parent 326f0cfa2f
commit 5c292c61c6
1437 changed files with 208886 additions and 87131 deletions

View file

@ -9,7 +9,6 @@ import (
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"runtime"
@ -25,7 +24,6 @@ import (
"github.com/containers/storage/pkg/system"
"github.com/containers/storage/pkg/unshare"
gzip "github.com/klauspost/pgzip"
"github.com/opencontainers/runc/libcontainer/userns"
"github.com/sirupsen/logrus"
"github.com/ulikunitz/xz"
)
@ -77,6 +75,7 @@ const (
solaris = "solaris"
windows = "windows"
darwin = "darwin"
freebsd = "freebsd"
)
var xattrsToIgnore = map[string]interface{}{
@ -132,16 +131,6 @@ const (
OverlayWhiteoutFormat
)
const (
modeISDIR = 040000 // Directory
modeISFIFO = 010000 // FIFO
modeISREG = 0100000 // Regular file
modeISLNK = 0120000 // Symbolic link
modeISBLK = 060000 // Block special file
modeISCHR = 020000 // Character special file
modeISSOCK = 0140000 // Socket
)
// IsArchivePath checks if the (possibly compressed) file at the given path
// starts with a tar file header.
func IsArchivePath(path string) bool {
@ -329,7 +318,6 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
}
pipeWriter.Close()
}()
return pipeReader
}
@ -360,7 +348,7 @@ func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, erro
if err != nil {
return nil, err
}
hdr.Mode = fillGo18FileTypeBits(int64(chmodTarEntry(os.FileMode(hdr.Mode))), fi)
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
name, err = canonicalTarName(name, fi.IsDir())
if err != nil {
return nil, fmt.Errorf("tar: cannot canonicalize path: %w", err)
@ -372,31 +360,6 @@ func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, erro
return hdr, nil
}
// fillGo18FileTypeBits fills type bits which have been removed on Go 1.9 archive/tar
// https://github.com/golang/go/commit/66b5a2f
func fillGo18FileTypeBits(mode int64, fi os.FileInfo) int64 {
fm := fi.Mode()
switch {
case fm.IsRegular():
mode |= modeISREG
case fi.IsDir():
mode |= modeISDIR
case fm&os.ModeSymlink != 0:
mode |= modeISLNK
case fm&os.ModeDevice != 0:
if fm&os.ModeCharDevice != 0 {
mode |= modeISCHR
} else {
mode |= modeISBLK
}
case fm&os.ModeNamedPipe != 0:
mode |= modeISFIFO
case fm&os.ModeSocket != 0:
mode |= modeISSOCK
}
return mode
}
// ReadSecurityXattrToTarHeader reads security.capability, security,image
// xattrs from filesystem to a tar header
func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error {
@ -484,7 +447,7 @@ func newTarAppender(idMapping *idtools.IDMappings, writer io.Writer, chownOpts *
}
// canonicalTarName provides a platform-independent and consistent posix-style
//path for files and directories to be archived regardless of the platform.
// path for files and directories to be archived regardless of the platform.
func canonicalTarName(name string, isDir bool) (string, error) {
name, err := CanonicalTarNameForPath(name)
if err != nil {
@ -528,6 +491,9 @@ func (ta *tarAppender) addTarFile(path, name string) error {
if err := ReadUserXattrToTarHeader(path, hdr); err != nil {
return err
}
if err := ReadFileFlagsToTarHeader(path, hdr); err != nil {
return err
}
if ta.CopyPass {
copyPassHeader(hdr)
}
@ -550,9 +516,9 @@ func (ta *tarAppender) addTarFile(path, name string) error {
}
}
//handle re-mapping container ID mappings back to host ID mappings before
//writing tar headers/files. We skip whiteout files because they were written
//by the kernel and already have proper ownership relative to the host
// handle re-mapping container ID mappings back to host ID mappings before
// writing tar headers/files. We skip whiteout files because they were written
// by the kernel and already have proper ownership relative to the host
if !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && !ta.IDMappings.Empty() {
fileIDPair, err := getFileUIDGID(fi.Sys())
if err != nil {
@ -673,7 +639,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
if !strings.HasPrefix(targetPath, extractDir) {
return breakoutError(fmt.Errorf("invalid hardlink %q -> %q", targetPath, hdr.Linkname))
}
if err := os.Link(targetPath, path); err != nil {
if err := handleLLink(targetPath, path); err != nil {
return err
}
@ -700,7 +666,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
}
if forceMask != nil && (hdr.Typeflag != tar.TypeSymlink || runtime.GOOS == "darwin") {
value := fmt.Sprintf("%d:%d:0%o", hdr.Uid, hdr.Gid, hdrInfo.Mode()&07777)
value := fmt.Sprintf("%d:%d:0%o", hdr.Uid, hdr.Gid, hdrInfo.Mode()&0o7777)
if err := system.Lsetxattr(path, idtools.ContainersOverrideXattr, []byte(value), 0); err != nil {
return err
}
@ -771,6 +737,15 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
}
// We defer setting flags on directories until the end of
// Unpack or UnpackLayer in case setting them makes the
// directory immutable.
if hdr.Typeflag != tar.TypeDir {
if err := WriteFileFlagsFromTarHeader(path, hdr); err != nil {
return err
}
}
if len(errs) > 0 {
logrus.WithFields(logrus.Fields{
"errors": errs,
@ -789,7 +764,6 @@ func Tar(path string, compression Compression) (io.ReadCloser, error) {
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`.
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
// Fix the source path to work with long path names. This is a no-op
// on platforms other than Windows.
srcPath = fixVolumePathPrefix(srcPath)
@ -865,7 +839,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
rebaseName := options.RebaseNames[include]
walkRoot := getWalkRoot(srcPath, include)
filepath.WalkDir(walkRoot, func(filePath string, d fs.DirEntry, err error) error {
if err := filepath.WalkDir(walkRoot, func(filePath string, d fs.DirEntry, err error) error {
if err != nil {
logrus.Errorf("Tar: Can't stat file %s to tar: %s", srcPath, err)
return nil
@ -875,7 +849,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
if err != nil || (!options.IncludeSourceDir && relFilePath == "." && d.IsDir()) {
// Error getting relative path OR we are looking
// at the source directory path. Skip in both situations.
return nil
return nil //nolint: nilerr
}
if options.IncludeSourceDir && include == "." && relFilePath != "." {
@ -892,8 +866,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
if include != relFilePath {
matches, err := pm.IsMatch(relFilePath)
if err != nil {
logrus.Errorf("Matching %s: %v", relFilePath, err)
return err
return fmt.Errorf("matching %s: %w", relFilePath, err)
}
skip = matches
}
@ -956,7 +929,10 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
}
}
return nil
})
}); err != nil {
logrus.Errorf("%s", err)
return
}
}
}()
@ -1019,7 +995,7 @@ loop:
parent := filepath.Dir(hdr.Name)
parentPath := filepath.Join(dest, parent)
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
err = idtools.MkdirAllAndChownNew(parentPath, 0777, rootIDs)
err = idtools.MkdirAllAndChownNew(parentPath, 0o777, rootIDs)
if err != nil {
return err
}
@ -1100,6 +1076,9 @@ loop:
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
return err
}
if err := WriteFileFlagsFromTarHeader(path, hdr); err != nil {
return err
}
}
return nil
}
@ -1107,7 +1086,9 @@ loop:
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
// and unpacks it into the directory at `dest`.
// The archive may be compressed with one of the following algorithms:
// identity (uncompressed), gzip, bzip2, xz.
//
// identity (uncompressed), gzip, bzip2, xz.
//
// FIXME: specify behavior when target path exists vs. doesn't exist.
func Untar(tarArchive io.Reader, dest string, options *TarOptions) error {
return untarHandler(tarArchive, dest, options, true)
@ -1159,7 +1140,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error {
GIDMaps: tarMappings.GIDs(),
Compression: Uncompressed,
CopyPass: true,
InUserNS: userns.RunningInUserNS(),
InUserNS: unshare.IsRootless(),
}
archive, err := TarWithOptions(src, options)
if err != nil {
@ -1174,7 +1155,7 @@ func (archiver *Archiver) TarUntar(src, dst string) error {
UIDMaps: untarMappings.UIDs(),
GIDMaps: untarMappings.GIDs(),
ChownOpts: archiver.ChownOpts,
InUserNS: userns.RunningInUserNS(),
InUserNS: unshare.IsRootless(),
}
return archiver.Untar(archive, dst, options)
}
@ -1194,7 +1175,7 @@ func (archiver *Archiver) UntarPath(src, dst string) error {
UIDMaps: untarMappings.UIDs(),
GIDMaps: untarMappings.GIDs(),
ChownOpts: archiver.ChownOpts,
InUserNS: userns.RunningInUserNS(),
InUserNS: unshare.IsRootless(),
}
return archiver.Untar(archive, dst, options)
}
@ -1221,7 +1202,7 @@ func (archiver *Archiver) CopyWithTar(src, dst string) error {
}
// Create dst, copy src's content into it
logrus.Debugf("Creating dest directory: %s", dst)
if err := idtools.MkdirAllAndChownNew(dst, 0755, rootIDs); err != nil {
if err := idtools.MkdirAllAndChownNew(dst, 0o755, rootIDs); err != nil {
return err
}
logrus.Debugf("Calling TarUntar(%s, %s)", src, dst)
@ -1248,7 +1229,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
dst = filepath.Join(dst, filepath.Base(src))
}
// Create the holding directory if necessary
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil {
if err := os.MkdirAll(filepath.Dir(dst), 0o700); err != nil {
return err
}
@ -1294,7 +1275,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
UIDMaps: archiver.UntarIDMappings.UIDs(),
GIDMaps: archiver.UntarIDMappings.GIDs(),
ChownOpts: archiver.ChownOpts,
InUserNS: userns.RunningInUserNS(),
InUserNS: unshare.IsRootless(),
NoOverwriteDirNonDir: true,
}
err = archiver.Untar(r, filepath.Dir(dst), options)
@ -1348,7 +1329,7 @@ func remapIDs(readIDMappings, writeIDMappings *idtools.IDMappings, chownOpts *id
// of that file as an archive. The archive can only be read once - as soon as reading completes,
// the file will be deleted.
func NewTempArchive(src io.Reader, dir string) (*TempArchive, error) {
f, err := ioutil.TempFile(dir, "")
f, err := os.CreateTemp(dir, "")
if err != nil {
return nil, err
}
@ -1404,7 +1385,7 @@ func IsArchive(header []byte) bool {
if compression != Uncompressed {
return true
}
r := tar.NewReader(bytes.NewBuffer(header))
r := tar.NewReader(bytes.NewReader(header))
_, err := r.Next()
return err == nil
}
@ -1472,7 +1453,7 @@ func CopyFileWithTarAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap
err = fmt.Errorf("extracting data to %q while copying: %w", dest, err)
}
hashWorker.Wait()
if err == nil {
if err == nil && hashError != nil {
err = fmt.Errorf("calculating digest of data for %q while copying: %w", dest, hashError)
}
return err

View file

@ -1,3 +1,4 @@
//go:build go1.10
// +build go1.10
package archive

View file

@ -1,3 +1,4 @@
//go:build !go1.10
// +build !go1.10
package archive

View file

@ -0,0 +1,19 @@
//go:build freebsd || darwin
// +build freebsd darwin
package archive
import (
"archive/tar"
"os"
"golang.org/x/sys/unix"
)
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo, forceMask *os.FileMode) error {
permissionsMask := hdrInfo.Mode()
if forceMask != nil {
permissionsMask = *forceMask
}
return unix.Fchmodat(unix.AT_FDCWD, path, uint32(permissionsMask), unix.AT_SYMLINK_NOFOLLOW)
}

View file

@ -1,125 +0,0 @@
// +build freebsd
package archive
import (
"archive/tar"
"errors"
"os"
"path/filepath"
"syscall"
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/system"
"github.com/opencontainers/runc/libcontainer/userns"
"golang.org/x/sys/unix"
)
// fixVolumePathPrefix does platform specific processing to ensure that if
// the path being passed in is not in a volume path format, convert it to one.
func fixVolumePathPrefix(srcPath string) string {
return srcPath
}
// getWalkRoot calculates the root path when performing a TarWithOptions.
// We use a separate function as this is platform specific. On Linux, we
// can't use filepath.Join(srcPath,include) because this will clean away
// a trailing "." or "/" which may be important.
func getWalkRoot(srcPath string, include string) string {
return srcPath + string(filepath.Separator) + include
}
// CanonicalTarNameForPath returns platform-specific filepath
// to canonical posix-style path for tar archival. p is relative
// path.
func CanonicalTarNameForPath(p string) (string, error) {
return p, nil // already unix-style
}
// chmodTarEntry is used to adjust the file permissions used in tar header based
// on the platform the archival is done.
func chmodTarEntry(perm os.FileMode) os.FileMode {
return perm // noop for unix as golang APIs provide perm bits correctly
}
func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (err error) {
s, ok := stat.(*syscall.Stat_t)
if ok {
// Currently go does not fill in the major/minors
if s.Mode&unix.S_IFBLK != 0 ||
s.Mode&unix.S_IFCHR != 0 {
hdr.Devmajor = int64(major(uint64(s.Rdev))) // nolint: unconvert
hdr.Devminor = int64(minor(uint64(s.Rdev))) // nolint: unconvert
}
}
return
}
func getInodeFromStat(stat interface{}) (inode uint64, err error) {
s, ok := stat.(*syscall.Stat_t)
if ok {
inode = s.Ino
}
return
}
func getFileUIDGID(stat interface{}) (idtools.IDPair, error) {
s, ok := stat.(*syscall.Stat_t)
if !ok {
return idtools.IDPair{}, errors.New("cannot convert stat value to syscall.Stat_t")
}
return idtools.IDPair{UID: int(s.Uid), GID: int(s.Gid)}, nil
}
func major(device uint64) uint64 {
return (device >> 8) & 0xfff
}
func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00)
}
// handleTarTypeBlockCharFifo is an OS-specific helper function used by
// createTarFile to handle the following types of header: Block; Char; Fifo
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
if userns.RunningInUserNS() {
// cannot create a device if running in user namespace
return nil
}
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 system.Mknod(path, mode, uint64(system.Mkdev(hdr.Devmajor, hdr.Devminor)))
}
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo, forceMask *os.FileMode) error {
permissionsMask := hdrInfo.Mode()
if forceMask != nil {
permissionsMask = *forceMask
}
if hdr.Typeflag == tar.TypeLink {
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
if err := os.Chmod(path, permissionsMask); err != nil {
return err
}
}
} else if hdr.Typeflag != tar.TypeSymlink {
if err := os.Chmod(path, permissionsMask); err != nil {
return err
}
}
return nil
}

View file

@ -53,7 +53,7 @@ func (o overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi
}
// If there are no lower layers, then it can't have been deleted in this layer.
if len(o.rolayers) == 0 {
return nil, nil
return nil, nil //nolint: nilnil
}
// At this point, we have a directory that's opaque. If it appears in one of the lower
// layers, then it was newly-created here, so it wasn't also deleted here.
@ -66,7 +66,7 @@ func (o overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi
if statErr == nil {
if stat.Mode()&os.ModeCharDevice != 0 {
if isWhiteOut(stat) {
return nil, nil
return nil, nil //nolint: nilnil
}
}
// It's not whiteout, so it was there in the older layer, so we need to
@ -100,7 +100,7 @@ func (o overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi
// original directory wasn't inherited into this layer,
// so we don't need to emit whiteout for it.
if isWhiteOut(stat) {
return nil, nil
return nil, nil //nolint: nilnil
}
}
}
@ -153,8 +153,7 @@ func (overlayWhiteoutConverter) ConvertReadWithHandler(hdr *tar.Header, path str
return true, nil
}
type directHandler struct {
}
type directHandler struct{}
func (d directHandler) Setxattr(path, name string, value []byte) error {
return unix.Setxattr(path, name, value, 0)
@ -185,7 +184,26 @@ func GetFileOwner(path string) (uint32, uint32, uint32, error) {
}
s, ok := f.Sys().(*syscall.Stat_t)
if ok {
return s.Uid, s.Gid, s.Mode & 07777, nil
return s.Uid, s.Gid, s.Mode & 0o7777, nil
}
return 0, 0, uint32(f.Mode()), nil
}
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo, forceMask *os.FileMode) error {
permissionsMask := hdrInfo.Mode()
if forceMask != nil {
permissionsMask = *forceMask
}
if hdr.Typeflag == tar.TypeLink {
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
if err := os.Chmod(path, permissionsMask); err != nil {
return err
}
}
} else if hdr.Typeflag != tar.TypeSymlink {
if err := os.Chmod(path, permissionsMask); err != nil {
return err
}
}
return nil
}

View file

@ -1,3 +1,4 @@
//go:build !linux
// +build !linux
package archive

View file

@ -1,4 +1,5 @@
// +build !windows,!freebsd
//go:build !windows
// +build !windows
package archive
@ -49,8 +50,8 @@ func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (
// Currently go does not fill in the major/minors
if s.Mode&unix.S_IFBLK != 0 ||
s.Mode&unix.S_IFCHR != 0 {
hdr.Devmajor = int64(major(uint64(s.Rdev))) // nolint: unconvert
hdr.Devminor = int64(minor(uint64(s.Rdev))) // nolint: unconvert
hdr.Devmajor = int64(major(uint64(s.Rdev))) //nolint: unconvert
hdr.Devminor = int64(minor(uint64(s.Rdev))) //nolint: unconvert
}
}
@ -87,7 +88,7 @@ func minor(device uint64) uint64 {
// handleTarTypeBlockCharFifo is an OS-specific helper function used by
// createTarFile to handle the following types of header: Block; Char; Fifo
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
mode := uint32(hdr.Mode & 07777)
mode := uint32(hdr.Mode & 0o7777)
switch hdr.Typeflag {
case tar.TypeBlock:
mode |= unix.S_IFBLK
@ -97,24 +98,15 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
mode |= unix.S_IFIFO
}
return system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor)))
return system.Mknod(path, mode, system.Mkdev(hdr.Devmajor, hdr.Devminor))
}
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo, forceMask *os.FileMode) error {
permissionsMask := hdrInfo.Mode()
if forceMask != nil {
permissionsMask = *forceMask
}
if hdr.Typeflag == tar.TypeLink {
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
if err := os.Chmod(path, permissionsMask); err != nil {
return err
}
}
} else if hdr.Typeflag != tar.TypeSymlink {
if err := os.Chmod(path, permissionsMask); err != nil {
return err
}
}
return nil
// Hardlink without symlinks
func handleLLink(targetPath, path string) error {
// Note: on Linux, the link syscall will not follow symlinks.
// This behavior is implementation-dependent since
// POSIX.1-2008 so to make it clear that we need non-symlink
// following here we use the linkat syscall which has a flags
// field to select symlink following or not.
return unix.Linkat(unix.AT_FDCWD, targetPath, unix.AT_FDCWD, path, 0)
}

View file

@ -38,18 +38,17 @@ func CanonicalTarNameForPath(p string) (string, error) {
return "", fmt.Errorf("windows path contains forward slash: %s", p)
}
return strings.Replace(p, string(os.PathSeparator), "/", -1), nil
}
// chmodTarEntry is used to adjust the file permissions used in tar header based
// on the platform the archival is done.
func chmodTarEntry(perm os.FileMode) os.FileMode {
//perm &= 0755 // this 0-ed out tar flags (like link, regular file, directory marker etc.)
// perm &= 0755 // this 0-ed out tar flags (like link, regular file, directory marker etc.)
permPart := perm & os.ModePerm
noPermPart := perm &^ os.ModePerm
// Add the x bit: make everything +x from windows
permPart |= 0111
permPart &= 0755
permPart |= 0o111
permPart &= 0o755
return noPermPart | permPart
}
@ -78,3 +77,8 @@ func getFileUIDGID(stat interface{}) (idtools.IDPair, error) {
// no notion of file ownership mapping yet on Windows
return idtools.IDPair{0, 0}, nil
}
// Hardlink without following symlinks
func handleLLink(targetPath string, path string) error {
return os.Link(targetPath, path)
}

View file

@ -5,7 +5,6 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
@ -57,7 +56,7 @@ func (change *Change) String() string {
return fmt.Sprintf("%s %s", change.Kind, change.Path)
}
// for sort.Sort
// changesByPath implements sort.Interface.
type changesByPath []Change
func (c changesByPath) Less(i, j int) bool { return c[i].Path < c[j].Path }
@ -132,9 +131,11 @@ func isENOTDIR(err error) bool {
return false
}
type skipChange func(string) (bool, error)
type deleteChange func(string, string, os.FileInfo) (string, error)
type whiteoutChange func(string, string) (bool, error)
type (
skipChange func(string) (bool, error)
deleteChange func(string, string, os.FileInfo) (string, error)
whiteoutChange func(string, string) (bool, error)
)
func changes(layers []string, rw string, dc deleteChange, sc skipChange, wc whiteoutChange) ([]Change, error) {
var (
@ -300,7 +301,6 @@ func (info *FileInfo) path() string {
}
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
sizeAtEntry := len(*changes)
if oldInfo == nil {
@ -374,7 +374,6 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
copy((*changes)[sizeAtEntry+1:], (*changes)[sizeAtEntry:])
(*changes)[sizeAtEntry] = change
}
}
// Changes add changes to file information.
@ -399,11 +398,9 @@ func newRootFileInfo(idMappings *idtools.IDMappings) *FileInfo {
// ChangesDirs compares two directories and generates an array of Change objects describing the changes.
// If oldDir is "", then all files in newDir will be Add-Changes.
func ChangesDirs(newDir string, newMappings *idtools.IDMappings, oldDir string, oldMappings *idtools.IDMappings) ([]Change, error) {
var (
oldRoot, newRoot *FileInfo
)
var oldRoot, newRoot *FileInfo
if oldDir == "" {
emptyDir, err := ioutil.TempDir("", "empty")
emptyDir, err := os.MkdirTemp("", "empty")
if err != nil {
return nil, err
}

View file

@ -30,8 +30,8 @@ type walker struct {
dir2 string
root1 *FileInfo
root2 *FileInfo
idmap1 *idtools.IDMappings
idmap2 *idtools.IDMappings
idmap1 *idtools.IDMappings //nolint:unused
idmap2 *idtools.IDMappings //nolint:unused
}
// collectFileInfoForChanges returns a complete representation of the trees
@ -397,5 +397,4 @@ func overlayDeletedFile(layers []string, root, path string, fi os.FileInfo) (str
// We didn't find the same path in any older layers, so it was new in this one.
return "", nil
}

View file

@ -43,7 +43,12 @@ func collectFileInfoForChanges(oldDir, newDir string, oldIDMap, newIDMap *idtool
func collectFileInfo(sourceDir string, idMappings *idtools.IDMappings) (*FileInfo, error) {
root := newRootFileInfo(idMappings)
err := filepath.WalkDir(sourceDir, func(path string, d fs.DirEntry, err error) error {
sourceStat, err := system.Lstat(sourceDir)
if err != nil {
return nil, err
}
err = filepath.WalkDir(sourceDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
@ -86,8 +91,12 @@ func collectFileInfo(sourceDir string, idMappings *idtools.IDMappings) (*FileInf
if err != nil {
return err
}
info.stat = s
if s.Dev() != sourceStat.Dev() {
return filepath.SkipDir
}
info.stat = s
info.capability, _ = system.Lgetxattr(path, "security.capability")
parent.children[info.name] = info

View file

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package archive
@ -29,6 +30,7 @@ func statDifferent(oldStat *system.StatT, oldInfo *FileInfo, newStat *system.Sta
if oldStat.Mode() != newStat.Mode() ||
ownerChanged ||
oldStat.Rdev() != newStat.Rdev() ||
oldStat.Flags() != newStat.Flags() ||
// Don't look at size for dirs, its not a good measure of change
(oldStat.Mode()&unix.S_IFDIR != unix.S_IFDIR &&
(!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) {

View file

@ -7,7 +7,6 @@ import (
)
func statDifferent(oldStat *system.StatT, oldInfo *FileInfo, newStat *system.StatT, newInfo *FileInfo) bool {
// Don't look at size for dirs, its not a good measure of change
if oldStat.Mtim() != newStat.Mtim() ||
oldStat.Mode() != newStat.Mode() ||

View file

@ -4,7 +4,6 @@ import (
"archive/tar"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -255,7 +254,7 @@ func PrepareArchiveCopy(srcContent io.Reader, srcInfo, dstInfo CopyInfo) (dstDir
// The destination exists as a directory. No alteration
// to srcContent is needed as its contents can be
// simply extracted to the destination directory.
return dstInfo.Path, ioutil.NopCloser(srcContent), nil
return dstInfo.Path, io.NopCloser(srcContent), nil
case dstInfo.Exists && srcInfo.IsDir:
// The destination exists as some type of file and the source
// content is a directory. This is an error condition since
@ -298,7 +297,6 @@ func PrepareArchiveCopy(srcContent io.Reader, srcInfo, dstInfo CopyInfo) (dstDir
}
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
}
}
// RebaseArchiveEntries rewrites the given srcContent archive replacing

View file

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package archive

View file

@ -5,7 +5,6 @@ import (
"fmt"
"io"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"runtime"
@ -86,7 +85,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
parentPath := filepath.Join(dest, parent)
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
err = os.MkdirAll(parentPath, 0755)
err = os.MkdirAll(parentPath, 0o755)
if err != nil {
return 0, err
}
@ -102,7 +101,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
basename := filepath.Base(hdr.Name)
aufsHardlinks[basename] = hdr
if aufsTempdir == "" {
if aufsTempdir, err = ioutil.TempDir("", "storageplnk"); err != nil {
if aufsTempdir, err = os.MkdirTemp("", "storageplnk"); err != nil {
return 0, err
}
defer os.RemoveAll(aufsTempdir)
@ -146,6 +145,9 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
return nil
}
if _, exists := unpackedPaths[path]; !exists {
if err := resetImmutable(path, nil); err != nil {
return err
}
err := os.RemoveAll(path)
return err
}
@ -157,6 +159,9 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
} else {
originalBase := base[len(WhiteoutPrefix):]
originalPath := filepath.Join(dir, originalBase)
if err := resetImmutable(originalPath, nil); err != nil {
return 0, err
}
if err := os.RemoveAll(originalPath); err != nil {
return 0, err
}
@ -166,7 +171,15 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
// The only exception is when it is a directory *and* the file from
// the layer is also a directory. Then we want to merge them (i.e.
// just apply the metadata from the layer).
//
// We always reset the immutable flag (if present) to allow metadata
// changes and to allow directory modification. The flag will be
// re-applied based on the contents of hdr either at the end for
// directories or in createTarFile otherwise.
if fi, err := os.Lstat(path); err == nil {
if err := resetImmutable(path, &fi); err != nil {
return 0, err
}
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
if err := os.RemoveAll(path); err != nil {
return 0, err
@ -216,6 +229,9 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
return 0, err
}
if err := WriteFileFlagsFromTarHeader(path, hdr); err != nil {
return 0, err
}
}
return size, nil
@ -246,7 +262,9 @@ func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decomp
if err != nil {
return 0, err
}
defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform
defer func() {
_, _ = system.Umask(oldmask) // Ignore err. This can only fail with ErrNotSupportedPlatform, in which case we would have failed above.
}()
if decompress {
layer, err = DecompressStream(layer)

View file

@ -0,0 +1,167 @@
//go:build freebsd
// +build freebsd
package archive
import (
"archive/tar"
"fmt"
"math/bits"
"os"
"strings"
"syscall"
"github.com/containers/storage/pkg/system"
)
const (
paxSCHILYFflags = "SCHILY.fflags"
)
var (
flagNameToValue = map[string]uint32{
"sappnd": system.SF_APPEND,
"sappend": system.SF_APPEND,
"arch": system.SF_ARCHIVED,
"archived": system.SF_ARCHIVED,
"schg": system.SF_IMMUTABLE,
"schange": system.SF_IMMUTABLE,
"simmutable": system.SF_IMMUTABLE,
"sunlnk": system.SF_NOUNLINK,
"sunlink": system.SF_NOUNLINK,
"snapshot": system.SF_SNAPSHOT,
"uappnd": system.UF_APPEND,
"uappend": system.UF_APPEND,
"uarch": system.UF_ARCHIVE,
"uarchive": system.UF_ARCHIVE,
"hidden": system.UF_HIDDEN,
"uhidden": system.UF_HIDDEN,
"uchg": system.UF_IMMUTABLE,
"uchange": system.UF_IMMUTABLE,
"uimmutable": system.UF_IMMUTABLE,
"uunlnk": system.UF_NOUNLINK,
"uunlink": system.UF_NOUNLINK,
"offline": system.UF_OFFLINE,
"uoffline": system.UF_OFFLINE,
"opaque": system.UF_OPAQUE,
"rdonly": system.UF_READONLY,
"urdonly": system.UF_READONLY,
"readonly": system.UF_READONLY,
"ureadonly": system.UF_READONLY,
"reparse": system.UF_REPARSE,
"ureparse": system.UF_REPARSE,
"sparse": system.UF_SPARSE,
"usparse": system.UF_SPARSE,
"system": system.UF_SYSTEM,
"usystem": system.UF_SYSTEM,
}
// Only include the short names for the reverse map
flagValueToName = map[uint32]string{
system.SF_APPEND: "sappnd",
system.SF_ARCHIVED: "arch",
system.SF_IMMUTABLE: "schg",
system.SF_NOUNLINK: "sunlnk",
system.SF_SNAPSHOT: "snapshot",
system.UF_APPEND: "uappnd",
system.UF_ARCHIVE: "uarch",
system.UF_HIDDEN: "hidden",
system.UF_IMMUTABLE: "uchg",
system.UF_NOUNLINK: "uunlnk",
system.UF_OFFLINE: "offline",
system.UF_OPAQUE: "opaque",
system.UF_READONLY: "rdonly",
system.UF_REPARSE: "reparse",
system.UF_SPARSE: "sparse",
system.UF_SYSTEM: "system",
}
)
func parseFileFlags(fflags string) (uint32, uint32, error) {
var set, clear uint32 = 0, 0
for _, fflag := range strings.Split(fflags, ",") {
isClear := false
if strings.HasPrefix(fflag, "no") {
isClear = true
fflag = strings.TrimPrefix(fflag, "no")
}
if value, ok := flagNameToValue[fflag]; ok {
if isClear {
clear |= value
} else {
set |= value
}
} else {
return 0, 0, fmt.Errorf("parsing file flags, unrecognised token: %s", fflag)
}
}
return set, clear, nil
}
func formatFileFlags(fflags uint32) (string, error) {
res := []string{}
for fflags != 0 {
// Extract lowest set bit
fflag := uint32(1) << bits.TrailingZeros32(fflags)
if name, ok := flagValueToName[fflag]; ok {
res = append(res, name)
} else {
return "", fmt.Errorf("formatting file flags, unrecognised flag: %x", fflag)
}
fflags &= ^fflag
}
return strings.Join(res, ","), nil
}
func ReadFileFlagsToTarHeader(path string, hdr *tar.Header) error {
st, err := system.Lstat(path)
if err != nil {
return err
}
fflags, err := formatFileFlags(st.Flags())
if err != nil {
return err
}
if fflags != "" {
if hdr.PAXRecords == nil {
hdr.PAXRecords = map[string]string{}
}
hdr.PAXRecords[paxSCHILYFflags] = fflags
}
return nil
}
func WriteFileFlagsFromTarHeader(path string, hdr *tar.Header) error {
if fflags, ok := hdr.PAXRecords[paxSCHILYFflags]; ok {
var set, clear uint32
set, clear, err := parseFileFlags(fflags)
if err != nil {
return err
}
// Apply the delta to the existing file flags
st, err := system.Lstat(path)
if err != nil {
return err
}
return system.Lchflags(path, (st.Flags() & ^clear)|set)
}
return nil
}
func resetImmutable(path string, fi *os.FileInfo) error {
var flags uint32
if fi != nil {
flags = (*fi).Sys().(*syscall.Stat_t).Flags
} else {
st, err := system.Lstat(path)
if err != nil {
return err
}
flags = st.Flags()
}
if flags&(system.SF_IMMUTABLE|system.UF_IMMUTABLE) != 0 {
flags &= ^(system.SF_IMMUTABLE | system.UF_IMMUTABLE)
return system.Lchflags(path, flags)
}
return nil
}

View file

@ -0,0 +1,21 @@
//go:build !freebsd
// +build !freebsd
package archive
import (
"archive/tar"
"os"
)
func ReadFileFlagsToTarHeader(path string, hdr *tar.Header) error {
return nil
}
func WriteFileFlagsFromTarHeader(path string, hdr *tar.Header) error {
return nil
}
func resetImmutable(path string, fi *os.FileInfo) error {
return nil
}

View file

@ -1,3 +1,4 @@
//go:build !linux
// +build !linux
package archive

View file

@ -17,8 +17,8 @@ import (
// Generate("foo.txt", "hello world", "emptyfile")
//
// The above call will return an archive with 2 files:
// * ./foo.txt with content "hello world"
// * ./empty with empty content
// - ./foo.txt with content "hello world"
// - ./empty with empty content
//
// FIXME: stream content instead of buffering
// FIXME: specify permissions and other archive metadata

View file

@ -6,24 +6,27 @@ package compressor
import (
"bufio"
"bytes"
"encoding/base64"
"io"
"io/ioutil"
"github.com/containers/storage/pkg/chunked/internal"
"github.com/containers/storage/pkg/ioutils"
"github.com/klauspost/compress/zstd"
"github.com/opencontainers/go-digest"
"github.com/vbatts/tar-split/archive/tar"
"github.com/vbatts/tar-split/tar/asm"
"github.com/vbatts/tar-split/tar/storage"
)
const RollsumBits = 16
const holesThreshold = int64(1 << 10)
const (
RollsumBits = 16
holesThreshold = int64(1 << 10)
)
type holesFinder struct {
reader *bufio.Reader
fileOff int64
zeros int64
from int64
threshold int64
state int
@ -36,11 +39,11 @@ const (
holesFinderStateEOF
)
// ReadByte reads a single byte from the underlying reader.
// readByte reads a single byte from the underlying reader.
// If a single byte is read, the return value is (0, RAW-BYTE-VALUE, nil).
// If there are at least f.THRESHOLD consecutive zeros, then the
// return value is (N_CONSECUTIVE_ZEROS, '\x00').
func (f *holesFinder) ReadByte() (int64, byte, error) {
func (f *holesFinder) readByte() (int64, byte, error) {
for {
switch f.state {
// reading the file stream
@ -81,7 +84,7 @@ func (f *holesFinder) ReadByte() (int64, byte, error) {
f.state = holesFinderStateFound
}
} else {
if f.reader.UnreadByte(); err != nil {
if err := f.reader.UnreadByte(); err != nil {
return 0, 0, err
}
f.state = holesFinderStateRead
@ -98,7 +101,7 @@ func (f *holesFinder) ReadByte() (int64, byte, error) {
return holeLen, 0, nil
}
if b != 0 {
if f.reader.UnreadByte(); err != nil {
if err := f.reader.UnreadByte(); err != nil {
return 0, 0, err
}
f.state = holesFinderStateRead
@ -162,7 +165,7 @@ func (rc *rollingChecksumReader) Read(b []byte) (bool, int, error) {
}
for i := 0; i < len(b); i++ {
holeLen, n, err := rc.reader.ReadByte()
holeLen, n, err := rc.reader.readByte()
if err != nil {
if err == io.EOF {
rc.closed = true
@ -199,11 +202,55 @@ type chunk struct {
ChunkType string
}
type tarSplitData struct {
compressed *bytes.Buffer
digester digest.Digester
uncompressedCounter *ioutils.WriteCounter
zstd *zstd.Encoder
packer storage.Packer
}
func newTarSplitData(level int) (*tarSplitData, error) {
compressed := bytes.NewBuffer(nil)
digester := digest.Canonical.Digester()
zstdWriter, err := internal.ZstdWriterWithLevel(io.MultiWriter(compressed, digester.Hash()), level)
if err != nil {
return nil, err
}
uncompressedCounter := ioutils.NewWriteCounter(zstdWriter)
metaPacker := storage.NewJSONPacker(uncompressedCounter)
return &tarSplitData{
compressed: compressed,
digester: digester,
uncompressedCounter: uncompressedCounter,
zstd: zstdWriter,
packer: metaPacker,
}, nil
}
func writeZstdChunkedStream(destFile io.Writer, outMetadata map[string]string, reader io.Reader, level int) error {
// total written so far. Used to retrieve partial offsets in the file
dest := ioutils.NewWriteCounter(destFile)
tr := tar.NewReader(reader)
tarSplitData, err := newTarSplitData(level)
if err != nil {
return err
}
defer func() {
if tarSplitData.zstd != nil {
tarSplitData.zstd.Close()
}
}()
its, err := asm.NewInputTarStream(reader, tarSplitData.packer, nil)
if err != nil {
return err
}
tr := tar.NewReader(its)
tr.RawAccounting = true
buf := make([]byte, 4096)
@ -215,7 +262,6 @@ func writeZstdChunkedStream(destFile io.Writer, outMetadata map[string]string, r
defer func() {
if zstdWriter != nil {
zstdWriter.Close()
zstdWriter.Flush()
}
}()
@ -225,9 +271,6 @@ func writeZstdChunkedStream(destFile io.Writer, outMetadata map[string]string, r
if err := zstdWriter.Close(); err != nil {
return 0, err
}
if err := zstdWriter.Flush(); err != nil {
return 0, err
}
offset = dest.Count
zstdWriter.Reset(dest)
}
@ -374,9 +417,11 @@ func writeZstdChunkedStream(destFile io.Writer, outMetadata map[string]string, r
rawBytes := tr.RawBytes()
if _, err := zstdWriter.Write(rawBytes); err != nil {
zstdWriter.Close()
return err
}
if err := zstdWriter.Flush(); err != nil {
zstdWriter.Close()
return err
}
if err := zstdWriter.Close(); err != nil {
@ -384,7 +429,21 @@ func writeZstdChunkedStream(destFile io.Writer, outMetadata map[string]string, r
}
zstdWriter = nil
return internal.WriteZstdChunkedManifest(dest, outMetadata, uint64(dest.Count), metadata, level)
if err := tarSplitData.zstd.Flush(); err != nil {
return err
}
if err := tarSplitData.zstd.Close(); err != nil {
return err
}
tarSplitData.zstd = nil
ts := internal.TarSplitData{
Data: tarSplitData.compressed.Bytes(),
Digest: tarSplitData.digester.Digest(),
UncompressedSize: tarSplitData.uncompressedCounter.Count,
}
return internal.WriteZstdChunkedManifest(dest, outMetadata, uint64(dest.Count), &ts, metadata, level)
}
type zstdChunkedWriter struct {
@ -432,7 +491,7 @@ func zstdChunkedWriterWithLevel(out io.Writer, metadata map[string]string, level
go func() {
ch <- writeZstdChunkedStream(out, metadata, r, level)
io.Copy(ioutil.Discard, r)
_, _ = io.Copy(io.Discard, r) // Ordinarily writeZstdChunkedStream consumes all of r. If it fails, ensure the write end never blocks and eventually terminates.
r.Close()
close(ch)
}()

View file

@ -25,11 +25,15 @@ import (
"math/bits"
)
const windowSize = 64 // Roll assumes windowSize is a power of 2
const charOffset = 31
const (
windowSize = 64 // Roll assumes windowSize is a power of 2
charOffset = 31
)
const blobBits = 13
const blobSize = 1 << blobBits // 8k
const (
blobBits = 13
blobSize = 1 << blobBits // 8k
)
type RollSum struct {
s1, s2 uint32

View file

@ -88,8 +88,10 @@ func GetType(t byte) (string, error) {
}
const (
ManifestChecksumKey = "io.containers.zstd-chunked.manifest-checksum"
ManifestInfoKey = "io.containers.zstd-chunked.manifest-position"
ManifestChecksumKey = "io.github.containers.zstd-chunked.manifest-checksum"
ManifestInfoKey = "io.github.containers.zstd-chunked.manifest-position"
TarSplitChecksumKey = "io.github.containers.zstd-chunked.tarsplit-checksum"
TarSplitInfoKey = "io.github.containers.zstd-chunked.tarsplit-position"
// ManifestTypeCRFS is a manifest file compatible with the CRFS TOC file.
ManifestTypeCRFS = 1
@ -97,7 +99,7 @@ const (
// FooterSizeSupported is the footer size supported by this implementation.
// Newer versions of the image format might increase this value, so reject
// any version that is not supported.
FooterSizeSupported = 40
FooterSizeSupported = 56
)
var (
@ -114,7 +116,7 @@ func appendZstdSkippableFrame(dest io.Writer, data []byte) error {
return err
}
var size []byte = make([]byte, 4)
size := make([]byte, 4)
binary.LittleEndian.PutUint32(size, uint32(len(data)))
if _, err := dest.Write(size); err != nil {
return err
@ -125,16 +127,23 @@ func appendZstdSkippableFrame(dest io.Writer, data []byte) error {
return nil
}
func WriteZstdChunkedManifest(dest io.Writer, outMetadata map[string]string, offset uint64, metadata []FileMetadata, level int) error {
type TarSplitData struct {
Data []byte
Digest digest.Digest
UncompressedSize int64
}
func WriteZstdChunkedManifest(dest io.Writer, outMetadata map[string]string, offset uint64, tarSplitData *TarSplitData, metadata []FileMetadata, level int) error {
// 8 is the size of the zstd skippable frame header + the frame size
manifestOffset := offset + 8
const zstdSkippableFrameHeader = 8
manifestOffset := offset + zstdSkippableFrameHeader
toc := TOC{
Version: 1,
Entries: metadata,
}
var json = jsoniter.ConfigCompatibleWithStandardLibrary
json := jsoniter.ConfigCompatibleWithStandardLibrary
// Generate the manifest
manifest, err := json.Marshal(toc)
if err != nil {
@ -167,13 +176,20 @@ func WriteZstdChunkedManifest(dest io.Writer, outMetadata map[string]string, off
return err
}
outMetadata[TarSplitChecksumKey] = tarSplitData.Digest.String()
tarSplitOffset := manifestOffset + uint64(len(compressedManifest)) + zstdSkippableFrameHeader
outMetadata[TarSplitInfoKey] = fmt.Sprintf("%d:%d:%d", tarSplitOffset, len(tarSplitData.Data), tarSplitData.UncompressedSize)
if err := appendZstdSkippableFrame(dest, tarSplitData.Data); err != nil {
return err
}
// Store the offset to the manifest and its size in LE order
var manifestDataLE []byte = make([]byte, FooterSizeSupported)
manifestDataLE := make([]byte, FooterSizeSupported)
binary.LittleEndian.PutUint64(manifestDataLE, manifestOffset)
binary.LittleEndian.PutUint64(manifestDataLE[8:], uint64(len(compressedManifest)))
binary.LittleEndian.PutUint64(manifestDataLE[16:], uint64(len(manifest)))
binary.LittleEndian.PutUint64(manifestDataLE[24:], uint64(ManifestTypeCRFS))
copy(manifestDataLE[32:], ZstdChunkedFrameMagic)
binary.LittleEndian.PutUint64(manifestDataLE[8*1:], uint64(len(compressedManifest)))
binary.LittleEndian.PutUint64(manifestDataLE[8*2:], uint64(len(manifest)))
binary.LittleEndian.PutUint64(manifestDataLE[8*3:], uint64(ManifestTypeCRFS))
copy(manifestDataLE[8*4:], ZstdChunkedFrameMagic)
return appendZstdSkippableFrame(dest, manifestDataLE)
}

View file

@ -165,7 +165,7 @@ func (pm *PatternMatcher) Patterns() []*Pattern {
return pm.patterns
}
// Pattern defines a single regexp used used to filter file paths.
// Pattern defines a single regexp used to filter file paths.
type Pattern struct {
cleanedPattern string
dirs []string
@ -183,7 +183,6 @@ func (p *Pattern) Exclusion() bool {
}
func (p *Pattern) match(path string) (bool, error) {
if p.regexp == nil {
if err := p.compile(); err != nil {
return false, filepath.ErrBadPattern
@ -321,14 +320,14 @@ func ReadSymlinkedDirectory(path string) (string, error) {
var realPath string
var err error
if realPath, err = filepath.Abs(path); err != nil {
return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err)
return "", fmt.Errorf("unable to get absolute path for %s: %w", path, err)
}
if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err)
return "", fmt.Errorf("failed to canonicalise path for %s: %w", path, err)
}
realPathInfo, err := os.Stat(realPath)
if err != nil {
return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err)
return "", fmt.Errorf("failed to stat target '%s' of '%s': %w", realPath, path, err)
}
if !realPathInfo.Mode().IsDir() {
return "", fmt.Errorf("canonical path points to a file '%s'", realPath)
@ -356,12 +355,12 @@ func CreateIfNotExists(path string, isDir bool) error {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
if isDir {
return os.MkdirAll(path, 0755)
return os.MkdirAll(path, 0o755)
}
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
f, err := os.OpenFile(path, os.O_CREATE, 0755)
f, err := os.OpenFile(path, os.O_CREATE, 0o755)
if err != nil {
return err
}

View file

@ -1,10 +1,10 @@
//go:build linux || freebsd
// +build linux freebsd
package fileutils
import (
"fmt"
"io/ioutil"
"os"
"github.com/sirupsen/logrus"
@ -13,7 +13,7 @@ import (
// GetTotalUsedFds Returns the number of used File Descriptors by
// reading it via /proc filesystem.
func GetTotalUsedFds() int {
if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
if fds, err := os.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
logrus.Errorf("%v", err)
} else {
return len(fds)

View file

@ -1,3 +1,4 @@
//go:build !linux && !darwin && !freebsd
// +build !linux,!darwin,!freebsd
package homedir

View file

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package homedir
@ -46,7 +47,7 @@ func GetShortcutString() string {
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
func GetRuntimeDir() (string, error) {
if xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR"); xdgRuntimeDir != "" {
return xdgRuntimeDir, nil
return filepath.EvalSymlinks(xdgRuntimeDir)
}
return "", errors.New("could not get XDG_RUNTIME_DIR")
}
@ -62,7 +63,7 @@ func StickRuntimeDirContents(files []string) ([]string, error) {
runtimeDir, err := GetRuntimeDir()
if err != nil {
// ignore error if runtimeDir is empty
return nil, nil
return nil, nil //nolint: nilerr
}
runtimeDir, err = filepath.Abs(runtimeDir)
if err != nil {

View file

@ -2,8 +2,8 @@ package idtools
import (
"bufio"
"errors"
"fmt"
"io/ioutil"
"os"
"os/user"
"runtime"
@ -219,7 +219,7 @@ func getOverflowUID() int {
overflowUIDOnce.Do(func() {
// 65534 is the value on older kernels where /proc/sys/kernel/overflowuid is not present
overflowUID = 65534
if content, err := ioutil.ReadFile("/proc/sys/kernel/overflowuid"); err == nil {
if content, err := os.ReadFile("/proc/sys/kernel/overflowuid"); err == nil {
if tmp, err := strconv.Atoi(string(content)); err == nil {
overflowUID = tmp
}
@ -233,7 +233,7 @@ func getOverflowGID() int {
overflowGIDOnce.Do(func() {
// 65534 is the value on older kernels where /proc/sys/kernel/overflowgid is not present
overflowGID = 65534
if content, err := ioutil.ReadFile("/proc/sys/kernel/overflowgid"); err == nil {
if content, err := os.ReadFile("/proc/sys/kernel/overflowgid"); err == nil {
if tmp, err := strconv.Atoi(string(content)); err == nil {
overflowGID = tmp
}
@ -360,8 +360,9 @@ func parseSubidFile(path, username string) (ranges, error) {
}
func checkChownErr(err error, name string, uid, gid int) error {
if e, ok := err.(*os.PathError); ok && e.Err == syscall.EINVAL {
return fmt.Errorf("potentially insufficient UIDs or GIDs available in user namespace (requested %d:%d for %s): Check /etc/subuid and /etc/subgid if configured locally and run podman-system-migrate: %w", uid, gid, name, err)
var e *os.PathError
if errors.As(err, &e) && e.Err == syscall.EINVAL {
return fmt.Errorf(`potentially insufficient UIDs or GIDs available in user namespace (requested %d:%d for %s): Check /etc/subuid and /etc/subgid if configured locally and run "podman system migrate": %w`, uid, gid, name, err)
}
return err
}

View file

@ -91,13 +91,13 @@ func CanAccess(path string, pair IDPair) bool {
}
func accessible(isOwner, isGroup bool, perms os.FileMode) bool {
if isOwner && (perms&0100 == 0100) {
if isOwner && (perms&0o100 == 0o100) {
return true
}
if isGroup && (perms&0010 == 0010) {
if isGroup && (perms&0o010 == 0o010) {
return true
}
if perms&0001 == 0001 {
if perms&0o001 == 0o001 {
return true
}
return false

View file

@ -1,3 +1,4 @@
//go:build !linux || !libsubid || !cgo
// +build !linux !libsubid !cgo
package idtools

View file

@ -1,3 +1,4 @@
//go:build windows
// +build windows
package idtools

View file

@ -2,11 +2,12 @@ package idtools
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"github.com/containers/storage/pkg/regexp"
)
// add a user and/or group to Linux /etc/passwd, /etc/group using standard
@ -24,7 +25,7 @@ var (
"usermod": "-%s %d-%d %s",
}
idOutRegexp = regexp.MustCompile(`uid=([0-9]+).*gid=([0-9]+)`)
idOutRegexp = regexp.Delayed(`uid=([0-9]+).*gid=([0-9]+)`)
// default length for a UID/GID subordinate range
defaultRangeLen = 65536
defaultRangeStart = 100000
@ -88,7 +89,6 @@ func addUser(userName string) error {
}
func createSubordinateRanges(name string) error {
// first, we should verify that ranges weren't automatically created
// by the distro tooling
ranges, err := readSubuid(name)

View file

@ -19,8 +19,8 @@ func resolveBinary(binname string) (string, error) {
if err != nil {
return "", err
}
//only return no error if the final resolved binary basename
//matches what was searched for
// only return no error if the final resolved binary basename
// matches what was searched for
if filepath.Base(resolvedPath) == binname {
return resolvedPath, nil
}

View file

@ -2,9 +2,9 @@ package ioutils
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"time"
)
// AtomicFileWriterOptions specifies options for creating the atomic file writer.
@ -14,9 +14,26 @@ type AtomicFileWriterOptions struct {
// storage after it has been written and before it is moved to
// the specified path.
NoSync bool
// On successful return from Close() this is set to the mtime of the
// newly written file.
ModTime time.Time
// Specifies whether Commit() must be explicitly called to write state
// to the destination. This allows an application to preserve the original
// file when an error occurs during processing (and not just during write)
// The default is false, which will auto-commit on Close
ExplicitCommit bool
}
var defaultWriterOptions AtomicFileWriterOptions = AtomicFileWriterOptions{}
type CommittableWriter interface {
io.WriteCloser
// Commit closes the temporary file associated with this writer, and
// provided no errors (during commit or previously during write operations),
// will publish the completed file under the intended destination.
Commit() error
}
var defaultWriterOptions = AtomicFileWriterOptions{}
// SetDefaultOptions overrides the default options used when creating an
// atomic file writer.
@ -24,11 +41,21 @@ func SetDefaultOptions(opts AtomicFileWriterOptions) {
defaultWriterOptions = opts
}
// NewAtomicFileWriterWithOpts returns WriteCloser so that writing to it writes to a
// temporary file and closing it atomically changes the temporary file to
// destination path. Writing and closing concurrently is not allowed.
func NewAtomicFileWriterWithOpts(filename string, perm os.FileMode, opts *AtomicFileWriterOptions) (io.WriteCloser, error) {
f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
// NewAtomicFileWriterWithOpts returns a CommittableWriter so that writing to it
// writes to a temporary file, which can later be committed to a destination path,
// either by Closing in the case of auto-commit, or manually calling commit if the
// ExplicitCommit option is enabled. Writing and closing concurrently is not
// allowed.
func NewAtomicFileWriterWithOpts(filename string, perm os.FileMode, opts *AtomicFileWriterOptions) (CommittableWriter, error) {
return newAtomicFileWriter(filename, perm, opts)
}
// newAtomicFileWriter returns a CommittableWriter so that writing to it writes to
// a temporary file, which can later be committed to a destination path, either by
// Closing in the case of auto-commit, or manually calling commit if the
// ExplicitCommit option is enabled. Writing and closing concurrently is not allowed.
func newAtomicFileWriter(filename string, perm os.FileMode, opts *AtomicFileWriterOptions) (*atomicFileWriter, error) {
f, err := os.CreateTemp(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
if err != nil {
return nil, err
}
@ -40,43 +67,56 @@ func NewAtomicFileWriterWithOpts(filename string, perm os.FileMode, opts *Atomic
return nil, err
}
return &atomicFileWriter{
f: f,
fn: abspath,
perm: perm,
noSync: opts.NoSync,
f: f,
fn: abspath,
perm: perm,
noSync: opts.NoSync,
explicitCommit: opts.ExplicitCommit,
}, nil
}
// NewAtomicFileWriter returns WriteCloser so that writing to it writes to a
// temporary file and closing it atomically changes the temporary file to
// destination path. Writing and closing concurrently is not allowed.
func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, error) {
// NewAtomicFileWriterWithOpts returns a CommittableWriter, with auto-commit enabled.
// Writing to it writes to a temporary file and closing it atomically changes the
// temporary file to destination path. Writing and closing concurrently is not allowed.
func NewAtomicFileWriter(filename string, perm os.FileMode) (CommittableWriter, error) {
return NewAtomicFileWriterWithOpts(filename, perm, nil)
}
// AtomicWriteFile atomically writes data to a file named by filename.
func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
f, err := NewAtomicFileWriter(filename, perm)
func AtomicWriteFileWithOpts(filename string, data []byte, perm os.FileMode, opts *AtomicFileWriterOptions) error {
f, err := newAtomicFileWriter(filename, perm, opts)
if err != nil {
return err
}
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
f.(*atomicFileWriter).writeErr = err
f.writeErr = err
}
if err1 := f.Close(); err == nil {
err = err1
}
if opts != nil {
opts.ModTime = f.modTime
}
return err
}
func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
return AtomicWriteFileWithOpts(filename, data, perm, nil)
}
type atomicFileWriter struct {
f *os.File
fn string
writeErr error
perm os.FileMode
noSync bool
f *os.File
fn string
writeErr error
perm os.FileMode
noSync bool
modTime time.Time
closed bool
explicitCommit bool
}
func (w *atomicFileWriter) Write(dt []byte) (int, error) {
@ -87,27 +127,73 @@ func (w *atomicFileWriter) Write(dt []byte) (int, error) {
return n, err
}
func (w *atomicFileWriter) Close() (retErr error) {
func (w *atomicFileWriter) closeTempFile() error {
if w.closed {
return nil
}
w.closed = true
return w.f.Close()
}
func (w *atomicFileWriter) Close() error {
return w.complete(!w.explicitCommit)
}
func (w *atomicFileWriter) Commit() error {
return w.complete(true)
}
func (w *atomicFileWriter) complete(commit bool) (retErr error) {
if w == nil || w.closed {
return nil
}
defer func() {
w.closeTempFile()
if retErr != nil || w.writeErr != nil {
os.Remove(w.f.Name())
}
}()
if !w.noSync {
if err := fdatasync(w.f); err != nil {
w.f.Close()
return err
}
if commit {
return w.commitState()
}
if err := w.f.Close(); err != nil {
return nil
}
func (w *atomicFileWriter) commitState() error {
// Perform a data only sync (fdatasync()) if supported
if err := w.postDataWrittenSync(); err != nil {
return err
}
if err := os.Chmod(w.f.Name(), w.perm); err != nil {
// Capture fstat before closing the fd
info, err := w.f.Stat()
if err != nil {
return err
}
w.modTime = info.ModTime()
if err := w.f.Chmod(w.perm); err != nil {
return err
}
// Perform full sync on platforms that need it
if err := w.preRenameSync(); err != nil {
return err
}
// Some platforms require closing before rename (Windows)
if err := w.closeTempFile(); err != nil {
return err
}
if w.writeErr == nil {
return os.Rename(w.f.Name(), w.fn)
}
return nil
}
@ -124,7 +210,7 @@ type AtomicWriteSet struct {
// commit. If no temporary directory is given the system
// default is used.
func NewAtomicWriteSet(tmpDir string) (*AtomicWriteSet, error) {
td, err := ioutil.TempDir(tmpDir, "write-set-")
td, err := os.MkdirTemp(tmpDir, "write-set-")
if err != nil {
return nil, err
}
@ -159,7 +245,7 @@ func (w syncFileCloser) Close() error {
if !defaultWriterOptions.NoSync {
return w.File.Close()
}
err := fdatasync(w.File)
err := dataOrFullSync(w.File)
if err1 := w.File.Close(); err == nil {
err = err1
}

View file

@ -6,6 +6,18 @@ import (
"golang.org/x/sys/unix"
)
func fdatasync(f *os.File) error {
func dataOrFullSync(f *os.File) error {
return unix.Fdatasync(int(f.Fd()))
}
func (w *atomicFileWriter) postDataWrittenSync() error {
if w.noSync {
return nil
}
return unix.Fdatasync(int(w.f.Fd()))
}
func (w *atomicFileWriter) preRenameSync() error {
// On Linux data can be reliably flushed to media without metadata, so defer
return nil
}

View file

@ -0,0 +1,26 @@
//go:build !linux
// +build !linux
package ioutils
import (
"os"
)
func dataOrFullSync(f *os.File) error {
return f.Sync()
}
func (w *atomicFileWriter) postDataWrittenSync() error {
// many platforms (Mac, Windows) require a full sync to reliably flush to media
return nil
}
func (w *atomicFileWriter) preRenameSync() error {
if w.noSync {
return nil
}
// fsync() on Non-linux Unix, FlushFileBuffers (Windows), F_FULLFSYNC (Mac)
return w.f.Sync()
}

View file

@ -1,11 +0,0 @@
// +build !linux
package ioutils
import (
"os"
)
func fdatasync(f *os.File) error {
return f.Sync()
}

View file

@ -1,11 +1,10 @@
package ioutils
import (
"context"
"crypto/sha256"
"encoding/hex"
"io"
"golang.org/x/net/context"
)
type readCloserWrapper struct {

View file

@ -1,10 +1,11 @@
//go:build !windows
// +build !windows
package ioutils
import "io/ioutil"
import "os"
// TempDir on Unix systems is equivalent to ioutil.TempDir.
// TempDir on Unix systems is equivalent to os.MkdirTemp.
func TempDir(dir, prefix string) (string, error) {
return ioutil.TempDir(dir, prefix)
return os.MkdirTemp(dir, prefix)
}

View file

@ -1,16 +1,17 @@
//go:build windows
// +build windows
package ioutils
import (
"io/ioutil"
"os"
"github.com/containers/storage/pkg/longpath"
)
// TempDir is the equivalent of ioutil.TempDir, except that the result is in Windows longpath format.
// TempDir is the equivalent of os.MkdirTemp, except that the result is in Windows longpath format.
func TempDir(dir, prefix string) (string, error) {
tempDir, err := ioutil.TempDir(dir, prefix)
tempDir, err := os.MkdirTemp(dir, prefix)
if err != nil {
return "", err
}

View file

@ -10,6 +10,8 @@ import (
// A Locker represents a file lock where the file is used to cache an
// identifier of the last party that made changes to whatever's being protected
// by the lock.
//
// Deprecated: Refer directly to *LockFile, the provided implementation, instead.
type Locker interface {
// Acquire a writer lock.
// The default unix implementation panics if:
@ -17,10 +19,6 @@ type Locker interface {
// - tried to lock a read-only lock-file
Lock()
// Acquire a writer lock recursively, allowing for recursive acquisitions
// within the same process space.
RecursiveLock()
// Unlock the lock.
// The default unix implementation panics if:
// - unlocking an unlocked lock
@ -32,10 +30,13 @@ type Locker interface {
// Touch records, for others sharing the lock, that the caller was the
// last writer. It should only be called with the lock held.
//
// Deprecated: Use *LockFile.RecordWrite.
Touch() error
// Modified() checks if the most recent writer was a party other than the
// last recorded writer. It should only be called with the lock held.
// Deprecated: Use *LockFile.ModifiedSince.
Modified() (bool, error)
// TouchedSince() checks if the most recent writer modified the file (likely using Touch()) after the specified time.
@ -44,63 +45,86 @@ type Locker interface {
// IsReadWrite() checks if the lock file is read-write
IsReadWrite() bool
// Locked() checks if lock is locked for writing by a thread in this process
Locked() bool
// AssertLocked() can be used by callers that _know_ that they hold the lock (for reading or writing), for sanity checking.
// It might do nothing at all, or it may panic if the caller is not the owner of this lock.
AssertLocked()
// AssertLockedForWriting() can be used by callers that _know_ that they hold the lock locked for writing, for sanity checking.
// It might do nothing at all, or it may panic if the caller is not the owner of this lock for writing.
AssertLockedForWriting()
}
var (
lockfiles map[string]Locker
lockfilesLock sync.Mutex
lockFiles map[string]*LockFile
lockFilesLock sync.Mutex
)
// GetLockFile opens a read-write lock file, creating it if necessary. The
// *LockFile object may already be locked if the path has already been requested
// by the current process.
func GetLockFile(path string) (*LockFile, error) {
return getLockfile(path, false)
}
// GetLockfile opens a read-write lock file, creating it if necessary. The
// Locker object may already be locked if the path has already been requested
// by the current process.
//
// Deprecated: Use GetLockFile
func GetLockfile(path string) (Locker, error) {
return getLockfile(path, false)
return GetLockFile(path)
}
// GetROLockFile opens a read-only lock file, creating it if necessary. The
// *LockFile object may already be locked if the path has already been requested
// by the current process.
func GetROLockFile(path string) (*LockFile, error) {
return getLockfile(path, true)
}
// GetROLockfile opens a read-only lock file, creating it if necessary. The
// Locker object may already be locked if the path has already been requested
// by the current process.
//
// Deprecated: Use GetROLockFile
func GetROLockfile(path string) (Locker, error) {
return getLockfile(path, true)
return GetROLockFile(path)
}
// getLockfile returns a Locker object, possibly (depending on the platform)
// getLockFile returns a *LockFile object, possibly (depending on the platform)
// working inter-process, and associated with the specified path.
//
// If ro, the lock is a read-write lock and the returned Locker should correspond to the
// If ro, the lock is a read-write lock and the returned *LockFile should correspond to the
// “lock for reading” (shared) operation; otherwise, the lock is either an exclusive lock,
// or a read-write lock and Locker should correspond to the “lock for writing” (exclusive) operation.
// or a read-write lock and *LockFile should correspond to the “lock for writing” (exclusive) operation.
//
// WARNING:
// - The lock may or MAY NOT be inter-process.
// - There may or MAY NOT be an actual object on the filesystem created for the specified path.
// - Even if ro, the lock MAY be exclusive.
func getLockfile(path string, ro bool) (Locker, error) {
lockfilesLock.Lock()
defer lockfilesLock.Unlock()
if lockfiles == nil {
lockfiles = make(map[string]Locker)
func getLockfile(path string, ro bool) (*LockFile, error) {
lockFilesLock.Lock()
defer lockFilesLock.Unlock()
if lockFiles == nil {
lockFiles = make(map[string]*LockFile)
}
cleanPath, err := filepath.Abs(path)
if err != nil {
return nil, fmt.Errorf("ensuring that path %q is an absolute path: %w", path, err)
}
if locker, ok := lockfiles[cleanPath]; ok {
if ro && locker.IsReadWrite() {
if lockFile, ok := lockFiles[cleanPath]; ok {
if ro && lockFile.IsReadWrite() {
return nil, fmt.Errorf("lock %q is not a read-only lock", cleanPath)
}
if !ro && !locker.IsReadWrite() {
if !ro && !lockFile.IsReadWrite() {
return nil, fmt.Errorf("lock %q is not a read-write lock", cleanPath)
}
return locker, nil
return lockFile, nil
}
locker, err := createLockerForPath(cleanPath, ro) // platform-dependent locker
lockFile, err := createLockFileForPath(cleanPath, ro) // platform-dependent LockFile
if err != nil {
return nil, err
}
lockfiles[cleanPath] = locker
return locker, nil
lockFiles[cleanPath] = lockFile
return lockFile, nil
}

View file

@ -18,28 +18,48 @@ import (
"golang.org/x/sys/unix"
)
type lockfile struct {
// *LockFile represents a file lock where the file is used to cache an
// identifier of the last party that made changes to whatever's being protected
// by the lock.
//
// It MUST NOT be created manually. Use GetLockFile or GetROLockFile instead.
type LockFile struct {
// The following fields are only set when constructing *LockFile, and must never be modified afterwards.
// They are safe to access without any other locking.
file string
ro bool
// rwMutex serializes concurrent reader-writer acquisitions in the same process space
rwMutex *sync.RWMutex
// stateMutex is used to synchronize concurrent accesses to the state below
stateMutex *sync.Mutex
counter int64
file string
fd uintptr
lw []byte // "last writer"-unique value valid as of the last .Touch() or .Modified(), generated by newLastWriterID()
lw LastWrite // A global value valid as of the last .Touch() or .Modified()
locktype int16
locked bool
ro bool
recursive bool
// The following fields are only modified on transitions between counter == 0 / counter != 0.
// Thus, they can be safely accessed by users _that currently hold the LockFile_ without locking.
// In other cases, they need to be protected using stateMutex.
fd uintptr
}
// LastWrite is an opaque identifier of the last write to some *LockFile.
// It can be used by users of a *LockFile to determine if the lock indicates changes
// since the last check.
//
// Never construct a LastWrite manually; only accept it from *LockFile methods, and pass it back.
type LastWrite struct {
// Never modify fields of a LastWrite object; it has value semantics.
state []byte // Contents of the lock file.
}
const lastWriterIDSize = 64 // This must be the same as len(stringid.GenerateRandomID)
var lastWriterIDCounter uint64 // Private state for newLastWriterID
// newLastWriterID returns a new "last writer" ID.
// newLastWrite returns a new "last write" ID.
// The value must be different on every call, and also differ from values
// generated by other processes.
func newLastWriterID() []byte {
func newLastWrite() LastWrite {
// The ID is (PID, time, per-process counter, random)
// PID + time represents both a unique process across reboots,
// and a specific time within the process; the per-process counter
@ -61,53 +81,84 @@ func newLastWriterID() []byte {
panic(err) // This shouldn't happen
}
return res
return LastWrite{
state: res,
}
}
// newLastWriteFromData returns a LastWrite corresponding to data that came from a previous LastWrite.serialize
func newLastWriteFromData(serialized []byte) LastWrite {
if serialized == nil {
panic("newLastWriteFromData with nil data")
}
return LastWrite{
state: serialized,
}
}
// serialize returns bytes to write to the lock file to represent the specified write.
func (lw LastWrite) serialize() []byte {
if lw.state == nil {
panic("LastWrite.serialize on an uninitialized object")
}
return lw.state
}
// Equals returns true if lw matches other
func (lw LastWrite) equals(other LastWrite) bool {
if lw.state == nil {
panic("LastWrite.equals on an uninitialized object")
}
if other.state == nil {
panic("LastWrite.equals with an uninitialized counterparty")
}
return bytes.Equal(lw.state, other.state)
}
// openLock opens the file at path and returns the corresponding file
// descriptor. Note that the path is opened read-only when ro is set. If ro
// is unset, openLock will open the path read-write and create the file if
// necessary.
// descriptor. The path is opened either read-only or read-write,
// depending on the value of ro argument.
//
// openLock will create the file and its parent directories,
// if necessary.
func openLock(path string, ro bool) (fd int, err error) {
flags := unix.O_CLOEXEC | os.O_CREATE
if ro {
fd, err = unix.Open(path, os.O_RDONLY|unix.O_CLOEXEC|os.O_CREATE, 0)
flags |= os.O_RDONLY
} else {
fd, err = unix.Open(path,
os.O_RDWR|unix.O_CLOEXEC|os.O_CREATE,
unix.S_IRUSR|unix.S_IWUSR|unix.S_IRGRP|unix.S_IROTH,
)
flags |= os.O_RDWR
}
fd, err = unix.Open(path, flags, 0o644)
if err == nil {
return
return fd, nil
}
// the directory of the lockfile seems to be removed, try to create it
if os.IsNotExist(err) {
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
return fd, fmt.Errorf("creating locker directory: %w", err)
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
return fd, fmt.Errorf("creating lock file directory: %w", err)
}
return openLock(path, ro)
}
return
return fd, &os.PathError{Op: "open", Path: path, Err: err}
}
// createLockerForPath returns a Locker object, possibly (depending on the platform)
// createLockFileForPath returns new *LockFile object, possibly (depending on the platform)
// working inter-process and associated with the specified path.
//
// This function will be called at most once for each path value within a single process.
//
// If ro, the lock is a read-write lock and the returned Locker should correspond to the
// If ro, the lock is a read-write lock and the returned *LockFile should correspond to the
// “lock for reading” (shared) operation; otherwise, the lock is either an exclusive lock,
// or a read-write lock and Locker should correspond to the “lock for writing” (exclusive) operation.
// or a read-write lock and *LockFile should correspond to the “lock for writing” (exclusive) operation.
//
// WARNING:
// - The lock may or MAY NOT be inter-process.
// - There may or MAY NOT be an actual object on the filesystem created for the specified path.
// - Even if ro, the lock MAY be exclusive.
func createLockerForPath(path string, ro bool) (Locker, error) {
func createLockFileForPath(path string, ro bool) (*LockFile, error) {
// Check if we can open the lock.
fd, err := openLock(path, ro)
if err != nil {
@ -119,22 +170,24 @@ func createLockerForPath(path string, ro bool) (Locker, error) {
if ro {
locktype = unix.F_RDLCK
}
return &lockfile{
stateMutex: &sync.Mutex{},
return &LockFile{
file: path,
ro: ro,
rwMutex: &sync.RWMutex{},
file: path,
lw: newLastWriterID(),
stateMutex: &sync.Mutex{},
lw: newLastWrite(), // For compatibility, the first call of .Modified() will always report a change.
locktype: int16(locktype),
locked: false,
ro: ro}, nil
}, nil
}
// lock locks the lockfile via FCTNL(2) based on the specified type and
// command.
func (l *lockfile) lock(lType int16, recursive bool) {
func (l *LockFile) lock(lType int16) {
lk := unix.Flock_t{
Type: lType,
Whence: int16(os.SEEK_SET),
Whence: int16(unix.SEEK_SET),
Start: 0,
Len: 0,
}
@ -142,13 +195,7 @@ func (l *lockfile) lock(lType int16, recursive bool) {
case unix.F_RDLCK:
l.rwMutex.RLock()
case unix.F_WRLCK:
if recursive {
// NOTE: that's okay as recursive is only set in RecursiveLock(), so
// there's no need to protect against hypothetical RDLCK cases.
l.rwMutex.RLock()
} else {
l.rwMutex.Lock()
}
l.rwMutex.Lock()
default:
panic(fmt.Sprintf("attempted to acquire a file lock of unrecognized type %d", lType))
}
@ -158,7 +205,7 @@ func (l *lockfile) lock(lType int16, recursive bool) {
// If we're the first reference on the lock, we need to open the file again.
fd, err := openLock(l.file, l.ro)
if err != nil {
panic(fmt.Sprintf("error opening %q: %v", l.file, err))
panic(err)
}
l.fd = uintptr(fd)
@ -171,39 +218,27 @@ func (l *lockfile) lock(lType int16, recursive bool) {
}
l.locktype = lType
l.locked = true
l.recursive = recursive
l.counter++
}
// Lock locks the lockfile as a writer. Panic if the lock is a read-only one.
func (l *lockfile) Lock() {
func (l *LockFile) Lock() {
if l.ro {
panic("can't take write lock on read-only lock file")
} else {
l.lock(unix.F_WRLCK, false)
}
}
// RecursiveLock locks the lockfile as a writer but allows for recursive
// acquisitions within the same process space. Note that RLock() will be called
// if it's a lockTypReader lock.
func (l *lockfile) RecursiveLock() {
if l.ro {
l.RLock()
} else {
l.lock(unix.F_WRLCK, true)
l.lock(unix.F_WRLCK)
}
}
// LockRead locks the lockfile as a reader.
func (l *lockfile) RLock() {
l.lock(unix.F_RDLCK, false)
func (l *LockFile) RLock() {
l.lock(unix.F_RDLCK)
}
// Unlock unlocks the lockfile.
func (l *lockfile) Unlock() {
func (l *LockFile) Unlock() {
l.stateMutex.Lock()
if l.locked == false {
if !l.locked {
// Panic when unlocking an unlocked lock. That's a violation
// of the lock semantics and will reveal such.
panic("calling Unlock on unlocked lock")
@ -224,7 +259,7 @@ func (l *lockfile) Unlock() {
// file lock.
unix.Close(int(l.fd))
}
if l.locktype == unix.F_RDLCK || l.recursive {
if l.locktype == unix.F_RDLCK {
l.rwMutex.RUnlock()
} else {
l.rwMutex.Unlock()
@ -232,59 +267,157 @@ func (l *lockfile) Unlock() {
l.stateMutex.Unlock()
}
// Locked checks if lockfile is locked for writing by a thread in this process.
func (l *lockfile) Locked() bool {
l.stateMutex.Lock()
defer l.stateMutex.Unlock()
return l.locked && (l.locktype == unix.F_WRLCK)
func (l *LockFile) AssertLocked() {
// DO NOT provide a variant that returns the value of l.locked.
//
// If the caller does not hold the lock, l.locked might nevertheless be true because another goroutine does hold it, and
// we cant tell the difference.
//
// Hence, this “AssertLocked” method, which exists only for sanity checks.
// Dont even bother with l.stateMutex: The caller is expected to hold the lock, and in that case l.locked is constant true
// with no possible writers.
// If the caller does not hold the lock, we are violating the locking/memory model anyway, and accessing the data
// without the lock is more efficient for callers, and potentially more visible to lock analysers for incorrect callers.
if !l.locked {
panic("internal error: lock is not held by the expected owner")
}
}
// Touch updates the lock file with the UID of the user.
func (l *lockfile) Touch() error {
func (l *LockFile) AssertLockedForWriting() {
// DO NOT provide a variant that returns the current lock state.
//
// The same caveats as for AssertLocked apply equally.
l.AssertLocked()
// Like AssertLocked, dont even bother with l.stateMutex.
if l.locktype != unix.F_WRLCK {
panic("internal error: lock is not held for writing")
}
}
// GetLastWrite returns a LastWrite value corresponding to current state of the lock.
// This is typically called before (_not after_) loading the state when initializing a consumer
// of the data protected by the lock.
// During the lifetime of the consumer, the consumer should usually call ModifiedSince instead.
//
// The caller must hold the lock (for reading or writing).
func (l *LockFile) GetLastWrite() (LastWrite, error) {
l.AssertLocked()
contents := make([]byte, lastWriterIDSize)
n, err := unix.Pread(int(l.fd), contents, 0)
if err != nil {
return LastWrite{}, err
}
// It is important to handle the partial read case, because
// the initial size of the lock file is zero, which is a valid
// state (no writes yet)
contents = contents[:n]
return newLastWriteFromData(contents), nil
}
// RecordWrite updates the lock with a new LastWrite value, and returns the new value.
//
// If this function fails, the LastWriter value of the lock is indeterminate;
// the caller should keep using the previously-recorded LastWrite value,
// and possibly detecting its own modification as an external one:
//
// lw, err := state.lock.RecordWrite()
// if err != nil { /* fail */ }
// state.lastWrite = lw
//
// The caller must hold the lock for writing.
func (l *LockFile) RecordWrite() (LastWrite, error) {
l.AssertLockedForWriting()
lw := newLastWrite()
lockContents := lw.serialize()
n, err := unix.Pwrite(int(l.fd), lockContents, 0)
if err != nil {
return LastWrite{}, err
}
if n != len(lockContents) {
return LastWrite{}, unix.ENOSPC
}
return lw, nil
}
// ModifiedSince checks if the lock has been changed since a provided LastWrite value,
// and returns the one to record instead.
//
// If ModifiedSince reports no modification, the previous LastWrite value
// is still valid and can continue to be used.
//
// If this function fails, the LastWriter value of the lock is indeterminate;
// the caller should fail and keep using the previously-recorded LastWrite value,
// so that it continues failing until the situation is resolved. Similarly,
// it should only update the recorded LastWrite value after processing the update:
//
// lw2, modified, err := state.lock.ModifiedSince(state.lastWrite)
// if err != nil { /* fail */ }
// state.lastWrite = lw2
// if modified {
// if err := reload(); err != nil { /* fail */ }
// state.lastWrite = lw2
// }
//
// The caller must hold the lock (for reading or writing).
func (l *LockFile) ModifiedSince(previous LastWrite) (LastWrite, bool, error) {
l.AssertLocked()
currentLW, err := l.GetLastWrite()
if err != nil {
return LastWrite{}, false, err
}
modified := !previous.equals(currentLW)
return currentLW, modified, nil
}
// Touch updates the lock file with to record that the current lock holder has modified the lock-protected data.
//
// Deprecated: Use *LockFile.RecordWrite.
func (l *LockFile) Touch() error {
lw, err := l.RecordWrite()
if err != nil {
return err
}
l.stateMutex.Lock()
if !l.locked || (l.locktype != unix.F_WRLCK) {
panic("attempted to update last-writer in lockfile without the write lock")
}
defer l.stateMutex.Unlock()
l.lw = newLastWriterID()
n, err := unix.Pwrite(int(l.fd), l.lw, 0)
if err != nil {
return err
}
if n != len(l.lw) {
return unix.ENOSPC
}
l.lw = lw
return nil
}
// Modified indicates if the lockfile has been updated since the last time it
// was loaded.
func (l *lockfile) Modified() (bool, error) {
// NOTE: Unlike ModifiedSince, this returns true the first time it is called on a *LockFile.
// Callers cannot, in general, rely on this, because that might have happened for some other
// owner of the same *LockFile who created it previously.
//
// Deprecated: Use *LockFile.ModifiedSince.
func (l *LockFile) Modified() (bool, error) {
l.stateMutex.Lock()
if !l.locked {
panic("attempted to check last-writer in lockfile without locking it first")
}
defer l.stateMutex.Unlock()
currentLW := make([]byte, len(l.lw))
n, err := unix.Pread(int(l.fd), currentLW, 0)
oldLW := l.lw
// Note that this is called with stateMutex held; thats fine because ModifiedSince doesnt need to lock it.
currentLW, modified, err := l.ModifiedSince(oldLW)
if err != nil {
return true, err
}
if n != len(l.lw) {
return true, nil
}
oldLW := l.lw
l.lw = currentLW
return !bytes.Equal(currentLW, oldLW), nil
return modified, nil
}
// IsReadWriteLock indicates if the lock file is a read-write lock.
func (l *lockfile) IsReadWrite() bool {
func (l *LockFile) IsReadWrite() bool {
return !l.ro
}
// TouchedSince indicates if the lock file has been touched since the specified time
func (l *lockfile) TouchedSince(when time.Time) bool {
func (l *LockFile) TouchedSince(when time.Time) bool {
st, err := system.Fstat(int(l.fd))
if err != nil {
return true

View file

@ -1,3 +1,4 @@
//go:build windows
// +build windows
package lockfile
@ -8,65 +9,141 @@ import (
"time"
)
// createLockerForPath returns a Locker object, possibly (depending on the platform)
// createLockFileForPath returns a *LockFile object, possibly (depending on the platform)
// working inter-process and associated with the specified path.
//
// This function will be called at most once for each path value within a single process.
//
// If ro, the lock is a read-write lock and the returned Locker should correspond to the
// If ro, the lock is a read-write lock and the returned *LockFile should correspond to the
// “lock for reading” (shared) operation; otherwise, the lock is either an exclusive lock,
// or a read-write lock and Locker should correspond to the “lock for writing” (exclusive) operation.
// or a read-write lock and *LockFile should correspond to the “lock for writing” (exclusive) operation.
//
// WARNING:
// - The lock may or MAY NOT be inter-process.
// - There may or MAY NOT be an actual object on the filesystem created for the specified path.
// - Even if ro, the lock MAY be exclusive.
func createLockerForPath(path string, ro bool) (Locker, error) {
return &lockfile{locked: false}, nil
func createLockFileForPath(path string, ro bool) (*LockFile, error) {
return &LockFile{locked: false}, nil
}
type lockfile struct {
// *LockFile represents a file lock where the file is used to cache an
// identifier of the last party that made changes to whatever's being protected
// by the lock.
//
// It MUST NOT be created manually. Use GetLockFile or GetROLockFile instead.
type LockFile struct {
mu sync.Mutex
file string
locked bool
}
func (l *lockfile) Lock() {
// LastWrite is an opaque identifier of the last write to some *LockFile.
// It can be used by users of a *LockFile to determine if the lock indicates changes
// since the last check.
// A default-initialized LastWrite never matches any last write, i.e. it always indicates changes.
type LastWrite struct {
// Nothing: The Windows “implementation” does not actually track writes.
}
func (l *LockFile) Lock() {
l.mu.Lock()
l.locked = true
}
func (l *lockfile) RecursiveLock() {
// We don't support Windows but a recursive writer-lock in one process-space
// is really a writer lock, so just panic.
panic("not supported")
}
func (l *lockfile) RLock() {
func (l *LockFile) RLock() {
l.mu.Lock()
l.locked = true
}
func (l *lockfile) Unlock() {
func (l *LockFile) Unlock() {
l.locked = false
l.mu.Unlock()
}
func (l *lockfile) Locked() bool {
return l.locked
func (l *LockFile) AssertLocked() {
// DO NOT provide a variant that returns the value of l.locked.
//
// If the caller does not hold the lock, l.locked might nevertheless be true because another goroutine does hold it, and
// we cant tell the difference.
//
// Hence, this “AssertLocked” method, which exists only for sanity checks.
if !l.locked {
panic("internal error: lock is not held by the expected owner")
}
}
func (l *lockfile) Modified() (bool, error) {
func (l *LockFile) AssertLockedForWriting() {
// DO NOT provide a variant that returns the current lock state.
//
// The same caveats as for AssertLocked apply equally.
l.AssertLocked() // The current implementation does not distinguish between read and write locks.
}
// GetLastWrite() returns a LastWrite value corresponding to current state of the lock.
// This is typically called before (_not after_) loading the state when initializing a consumer
// of the data protected by the lock.
// During the lifetime of the consumer, the consumer should usually call ModifiedSince instead.
//
// The caller must hold the lock (for reading or writing) before this function is called.
func (l *LockFile) GetLastWrite() (LastWrite, error) {
l.AssertLocked()
return LastWrite{}, nil
}
// RecordWrite updates the lock with a new LastWrite value, and returns the new value.
//
// If this function fails, the LastWriter value of the lock is indeterminate;
// the caller should keep using the previously-recorded LastWrite value,
// and possibly detecting its own modification as an external one:
//
// lw, err := state.lock.RecordWrite()
// if err != nil { /* fail */ }
// state.lastWrite = lw
//
// The caller must hold the lock for writing.
func (l *LockFile) RecordWrite() (LastWrite, error) {
return LastWrite{}, nil
}
// ModifiedSince checks if the lock has been changed since a provided LastWrite value,
// and returns the one to record instead.
//
// If ModifiedSince reports no modification, the previous LastWrite value
// is still valid and can continue to be used.
//
// If this function fails, the LastWriter value of the lock is indeterminate;
// the caller should fail and keep using the previously-recorded LastWrite value,
// so that it continues failing until the situation is resolved. Similarly,
// it should only update the recorded LastWrite value after processing the update:
//
// lw2, modified, err := state.lock.ModifiedSince(state.lastWrite)
// if err != nil { /* fail */ }
// state.lastWrite = lw2
// if modified {
// if err := reload(); err != nil { /* fail */ }
// state.lastWrite = lw2
// }
//
// The caller must hold the lock (for reading or writing).
func (l *LockFile) ModifiedSince(previous LastWrite) (LastWrite, bool, error) {
return LastWrite{}, false, nil
}
// Deprecated: Use *LockFile.ModifiedSince.
func (l *LockFile) Modified() (bool, error) {
return false, nil
}
func (l *lockfile) Touch() error {
// Deprecated: Use *LockFile.RecordWrite.
func (l *LockFile) Touch() error {
return nil
}
func (l *lockfile) IsReadWrite() bool {
func (l *LockFile) IsReadWrite() bool {
return false
}
func (l *lockfile) TouchedSince(when time.Time) bool {
func (l *LockFile) TouchedSince(when time.Time) bool {
stat, err := os.Stat(l.file)
if err != nil {
return true

View file

@ -1,16 +1,29 @@
//go:build !windows
// +build !windows
package mount
import "golang.org/x/sys/unix"
import (
"time"
"golang.org/x/sys/unix"
)
func unmount(target string, flags int) error {
err := unix.Unmount(target, flags)
if err == nil || err == unix.EINVAL {
// Ignore "not mounted" error here. Note the same error
// can be returned if flags are invalid, so this code
// assumes that the flags value is always correct.
return nil
var err error
for i := 0; i < 50; i++ {
err = unix.Unmount(target, flags)
switch err {
case unix.EBUSY:
time.Sleep(50 * time.Millisecond)
continue
case unix.EINVAL, nil:
// Ignore "not mounted" error here. Note the same error
// can be returned if flags are invalid, so this code
// assumes that the flags value is always correct.
return nil
}
break
}
return &mountError{

View file

@ -1,3 +1,4 @@
//go:build windows
// +build windows
package mount

View file

@ -1,3 +1,4 @@
//go:build freebsd
// +build freebsd
package reexec

View file

@ -1,3 +1,4 @@
//go:build linux
// +build linux
package reexec

View file

@ -1,3 +1,4 @@
//go:build !linux && !windows && !freebsd && !solaris && !darwin
// +build !linux,!windows,!freebsd,!solaris,!darwin
package reexec

View file

@ -1,3 +1,4 @@
//go:build windows
// +build windows
package reexec

View file

@ -49,7 +49,7 @@ func panicIfNotInitialized() {
}
}
func naiveSelf() string {
func naiveSelf() string { //nolint: unused
name := os.Args[0]
if filepath.Base(name) == name {
if lp, err := exec.LookPath(name); err == nil {

View file

@ -0,0 +1,234 @@
package regexp
import (
"io"
"regexp"
"sync"
)
// Regexp is a wrapper struct used for wrapping MustCompile regex expressions
// used as global variables. Using this structure helps speed the startup time
// of apps that want to use global regex variables. This library initializes them on
// first use as opposed to the start of the executable.
type Regexp struct {
*regexpStruct
}
type regexpStruct struct {
_ noCopy
once sync.Once
regexp *regexp.Regexp
val string
}
func Delayed(val string) Regexp {
re := &regexpStruct{
val: val,
}
if precompile {
re.regexp = regexp.MustCompile(re.val)
}
return Regexp{re}
}
func (re *regexpStruct) compile() {
if precompile {
return
}
re.once.Do(func() {
re.regexp = regexp.MustCompile(re.val)
})
}
func (re *regexpStruct) Expand(dst []byte, template []byte, src []byte, match []int) []byte {
re.compile()
return re.regexp.Expand(dst, template, src, match)
}
func (re *regexpStruct) ExpandString(dst []byte, template string, src string, match []int) []byte {
re.compile()
return re.regexp.ExpandString(dst, template, src, match)
}
func (re *regexpStruct) Find(b []byte) []byte {
re.compile()
return re.regexp.Find(b)
}
func (re *regexpStruct) FindAll(b []byte, n int) [][]byte {
re.compile()
return re.regexp.FindAll(b, n)
}
func (re *regexpStruct) FindAllIndex(b []byte, n int) [][]int {
re.compile()
return re.regexp.FindAllIndex(b, n)
}
func (re *regexpStruct) FindAllString(s string, n int) []string {
re.compile()
return re.regexp.FindAllString(s, n)
}
func (re *regexpStruct) FindAllStringIndex(s string, n int) [][]int {
re.compile()
return re.regexp.FindAllStringIndex(s, n)
}
func (re *regexpStruct) FindAllStringSubmatch(s string, n int) [][]string {
re.compile()
return re.regexp.FindAllStringSubmatch(s, n)
}
func (re *regexpStruct) FindAllStringSubmatchIndex(s string, n int) [][]int {
re.compile()
return re.regexp.FindAllStringSubmatchIndex(s, n)
}
func (re *regexpStruct) FindAllSubmatch(b []byte, n int) [][][]byte {
re.compile()
return re.regexp.FindAllSubmatch(b, n)
}
func (re *regexpStruct) FindAllSubmatchIndex(b []byte, n int) [][]int {
re.compile()
return re.regexp.FindAllSubmatchIndex(b, n)
}
func (re *regexpStruct) FindIndex(b []byte) (loc []int) {
re.compile()
return re.regexp.FindIndex(b)
}
func (re *regexpStruct) FindReaderIndex(r io.RuneReader) (loc []int) {
re.compile()
return re.regexp.FindReaderIndex(r)
}
func (re *regexpStruct) FindReaderSubmatchIndex(r io.RuneReader) []int {
re.compile()
return re.regexp.FindReaderSubmatchIndex(r)
}
func (re *regexpStruct) FindString(s string) string {
re.compile()
return re.regexp.FindString(s)
}
func (re *regexpStruct) FindStringIndex(s string) (loc []int) {
re.compile()
return re.regexp.FindStringIndex(s)
}
func (re *regexpStruct) FindStringSubmatch(s string) []string {
re.compile()
return re.regexp.FindStringSubmatch(s)
}
func (re *regexpStruct) FindStringSubmatchIndex(s string) []int {
re.compile()
return re.regexp.FindStringSubmatchIndex(s)
}
func (re *regexpStruct) FindSubmatch(b []byte) [][]byte {
re.compile()
return re.regexp.FindSubmatch(b)
}
func (re *regexpStruct) FindSubmatchIndex(b []byte) []int {
re.compile()
return re.regexp.FindSubmatchIndex(b)
}
func (re *regexpStruct) LiteralPrefix() (prefix string, complete bool) {
re.compile()
return re.regexp.LiteralPrefix()
}
func (re *regexpStruct) Longest() {
re.compile()
re.regexp.Longest()
}
func (re *regexpStruct) Match(b []byte) bool {
re.compile()
return re.regexp.Match(b)
}
func (re *regexpStruct) MatchReader(r io.RuneReader) bool {
re.compile()
return re.regexp.MatchReader(r)
}
func (re *regexpStruct) MatchString(s string) bool {
re.compile()
return re.regexp.MatchString(s)
}
func (re *regexpStruct) NumSubexp() int {
re.compile()
return re.regexp.NumSubexp()
}
func (re *regexpStruct) ReplaceAll(src, repl []byte) []byte {
re.compile()
return re.regexp.ReplaceAll(src, repl)
}
func (re *regexpStruct) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte {
re.compile()
return re.regexp.ReplaceAllFunc(src, repl)
}
func (re *regexpStruct) ReplaceAllLiteral(src, repl []byte) []byte {
re.compile()
return re.regexp.ReplaceAllLiteral(src, repl)
}
func (re *regexpStruct) ReplaceAllLiteralString(src, repl string) string {
re.compile()
return re.regexp.ReplaceAllLiteralString(src, repl)
}
func (re *regexpStruct) ReplaceAllString(src, repl string) string {
re.compile()
return re.regexp.ReplaceAllString(src, repl)
}
func (re *regexpStruct) ReplaceAllStringFunc(src string, repl func(string) string) string {
re.compile()
return re.regexp.ReplaceAllStringFunc(src, repl)
}
func (re *regexpStruct) Split(s string, n int) []string {
re.compile()
return re.regexp.Split(s, n)
}
func (re *regexpStruct) String() string {
re.compile()
return re.regexp.String()
}
func (re *regexpStruct) SubexpIndex(name string) int {
re.compile()
return re.regexp.SubexpIndex(name)
}
func (re *regexpStruct) SubexpNames() []string {
re.compile()
return re.regexp.SubexpNames()
}
// noCopy may be added to structs which must not be copied
// after the first use.
//
// See https://golang.org/issues/8005#issuecomment-190753527
// for details.
//
// Note that it must not be embedded, due to the Lock and Unlock methods.
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}

View file

@ -0,0 +1,6 @@
//go:build !regexp_precompile
// +build !regexp_precompile
package regexp
const precompile = false

View file

@ -0,0 +1,6 @@
//go:build regexp_precompile
// +build regexp_precompile
package regexp
const precompile = true

View file

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package system
@ -6,9 +7,9 @@ import (
"time"
)
//setCTime will set the create time on a file. On Unix, the create
//time is updated as a side effect of setting the modified time, so
//no action is required.
// setCTime will set the create time on a file. On Unix, the create
// time is updated as a side effect of setting the modified time, so
// no action is required.
func setCTime(path string, ctime time.Time) error {
return nil
}

View file

@ -1,3 +1,4 @@
//go:build windows
// +build windows
package system
@ -8,8 +9,8 @@ import (
"golang.org/x/sys/windows"
)
//setCTime will set the create time on a file. On Windows, this requires
//calling SetFileTime and explicitly including the create time.
// setCTime will set the create time on a file. On Windows, this requires
// calling SetFileTime and explicitly including the create time.
func setCTime(path string, ctime time.Time) error {
ctimespec := windows.NsecToTimespec(ctime.UnixNano())
pathp, e := windows.UTF16PtrFromString(path)

View file

@ -4,7 +4,5 @@ import (
"errors"
)
var (
// ErrNotSupportedPlatform means the platform is not supported.
ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")
)
// ErrNotSupportedPlatform means the platform is not supported.
var ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")

View file

@ -6,7 +6,7 @@ import (
"unsafe"
)
// Used by chtimes
// maxTime is used by chtimes.
var maxTime time.Time
func init() {

View file

@ -13,5 +13,4 @@ func init() {
if os.Getenv("LCOW_SUPPORTED") != "" {
lcowSupported = true
}
}

View file

@ -0,0 +1,56 @@
//go:build freebsd
// +build freebsd
package system
import (
"unsafe"
"golang.org/x/sys/unix"
)
// Flag values from <sys/stat.h>
const (
/*
* Definitions of flags stored in file flags word.
*
* Super-user and owner changeable flags.
*/
UF_SETTABLE uint32 = 0x0000ffff /* mask of owner changeable flags */
UF_NODUMP uint32 = 0x00000001 /* do not dump file */
UF_IMMUTABLE uint32 = 0x00000002 /* file may not be changed */
UF_APPEND uint32 = 0x00000004 /* writes to file may only append */
UF_OPAQUE uint32 = 0x00000008 /* directory is opaque wrt. union */
UF_NOUNLINK uint32 = 0x00000010 /* file may not be removed or renamed */
UF_SYSTEM uint32 = 0x00000080 /* Windows system file bit */
UF_SPARSE uint32 = 0x00000100 /* sparse file */
UF_OFFLINE uint32 = 0x00000200 /* file is offline */
UF_REPARSE uint32 = 0x00000400 /* Windows reparse point file bit */
UF_ARCHIVE uint32 = 0x00000800 /* file needs to be archived */
UF_READONLY uint32 = 0x00001000 /* Windows readonly file bit */
/* This is the same as the MacOS X definition of UF_HIDDEN. */
UF_HIDDEN uint32 = 0x00008000 /* file is hidden */
/*
* Super-user changeable flags.
*/
SF_SETTABLE uint32 = 0xffff0000 /* mask of superuser changeable flags */
SF_ARCHIVED uint32 = 0x00010000 /* file is archived */
SF_IMMUTABLE uint32 = 0x00020000 /* file may not be changed */
SF_APPEND uint32 = 0x00040000 /* writes to file may only append */
SF_NOUNLINK uint32 = 0x00100000 /* file may not be removed or renamed */
SF_SNAPSHOT uint32 = 0x00200000 /* snapshot inode */
)
func Lchflags(path string, flags uint32) error {
p, err := unix.BytePtrFromString(path)
if err != nil {
return err
}
_, _, e1 := unix.Syscall(unix.SYS_LCHFLAGS, uintptr(unsafe.Pointer(p)), uintptr(flags), 0)
if e1 != 0 {
return e1
}
return nil
}

View file

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package system

View file

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package system
@ -14,7 +15,7 @@ import (
func Lstat(path string) (*StatT, error) {
s := &syscall.Stat_t{}
if err := syscall.Lstat(path, s); err != nil {
return nil, &os.PathError{"Lstat", path, err}
return nil, &os.PathError{Op: "Lstat", Path: path, Err: err}
}
return fromStatT(s)
}

View file

@ -4,6 +4,7 @@
package system
import (
"errors"
"fmt"
"unsafe"
@ -58,7 +59,8 @@ func getSwapInfo() (int64, int64, error) {
}
// ReadMemInfo retrieves memory statistics of the host system and returns a
// MemInfo type.
//
// MemInfo type.
func ReadMemInfo() (*MemInfo, error) {
MemTotal, MemFree, err := getMemInfo()
if err != nil {
@ -70,7 +72,7 @@ func ReadMemInfo() (*MemInfo, error) {
}
if MemTotal < 0 || MemFree < 0 || SwapTotal < 0 || SwapFree < 0 {
return nil, fmt.Errorf("getting system memory info %w", err)
return nil, errors.New("getting system memory info")
}
meminfo := &MemInfo{}

View file

@ -81,9 +81,9 @@ func getFreeMem() int64 {
}
// ReadMemInfo retrieves memory statistics of the host system and returns a
// MemInfo type.
//
// MemInfo type.
func ReadMemInfo() (*MemInfo, error) {
ppKernel := C.getPpKernel()
MemTotal := getTotalMem()
MemFree := getFreeMem()

View file

@ -27,7 +27,8 @@ type memorystatusex struct {
}
// ReadMemInfo retrieves memory statistics of the host system and returns a
// MemInfo type.
//
// MemInfo type.
func ReadMemInfo() (*MemInfo, error) {
msi := &memorystatusex{
dwLength: 64,

View file

@ -1,3 +1,4 @@
//go:build !windows && !freebsd
// +build !windows,!freebsd
package system
@ -8,8 +9,8 @@ import (
// Mknod creates a filesystem node (file, device special file or named pipe) named path
// with attributes specified by mode and dev.
func Mknod(path string, mode uint32, dev int) error {
return unix.Mknod(path, mode, dev)
func Mknod(path string, mode uint32, dev uint32) error {
return unix.Mknod(path, mode, int(dev))
}
// Mkdev is used to build the value of linux devices (in /dev/) which specifies major

View file

@ -1,3 +1,4 @@
//go:build freebsd
// +build freebsd
package system
@ -17,6 +18,6 @@ func Mknod(path string, mode uint32, dev uint64) error {
// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes.
// They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major,
// then the top 12 bits of the minor.
func Mkdev(major int64, minor int64) uint32 {
return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
func Mkdev(major int64, minor int64) uint64 {
return uint64(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
}

View file

@ -1,3 +1,4 @@
//go:build windows
// +build windows
package system

View file

@ -17,5 +17,4 @@ func DefaultPathEnv(platform string) string {
return ""
}
return defaultUnixPathEnv
}

View file

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package system

View file

@ -1,3 +1,4 @@
//go:build linux || freebsd || solaris || darwin
// +build linux freebsd solaris darwin
package system

View file

@ -1,6 +1,7 @@
package system
import (
"errors"
"fmt"
"os"
"syscall"
@ -29,6 +30,12 @@ func EnsureRemoveAll(dir string) error {
exitOnErr := make(map[string]int)
maxRetry := 100
// Attempt a simple remove all first, this avoids the more expensive
// RecursiveUnmount call if not needed.
if err := os.RemoveAll(dir); err == nil {
return nil
}
// Attempt to unmount anything beneath this dir first
if err := mount.RecursiveUnmount(dir); err != nil {
logrus.Debugf("RecusiveUnmount on %s failed: %v", dir, err)
@ -40,6 +47,19 @@ func EnsureRemoveAll(dir string) error {
return nil
}
// If the RemoveAll fails with a permission error, we
// may have immutable files so try to remove the
// immutable flag and redo the RemoveAll.
if errors.Is(err, syscall.EPERM) {
if err = resetFileFlags(dir); err != nil {
return fmt.Errorf("resetting file flags: %w", err)
}
err = os.RemoveAll(dir)
if err == nil {
return nil
}
}
pe, ok := err.(*os.PathError)
if !ok {
return err
@ -62,7 +82,7 @@ func EnsureRemoveAll(dir string) error {
continue
}
if pe.Err != syscall.EBUSY {
if !IsEBUSY(pe.Err) {
return err
}

View file

@ -0,0 +1,10 @@
//go:build !freebsd
// +build !freebsd
package system
// Reset file flags in a directory tree. This allows EnsureRemoveAll
// to delete trees which have the immutable flag set.
func resetFileFlags(dir string) error {
return nil
}

View file

@ -0,0 +1,17 @@
package system
import (
"io/fs"
"path/filepath"
)
// Reset file flags in a directory tree. This allows EnsureRemoveAll
// to delete trees which have the immutable flag set.
func resetFileFlags(dir string) error {
return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err := Lchflags(path, 0); err != nil {
return err
}
return nil
})
}

View file

@ -0,0 +1,12 @@
//go:build !freebsd
// +build !freebsd
package system
type platformStatT struct{}
// Flags return file flags if supported or zero otherwise
func (s StatT) Flags() uint32 {
_ = s.platformStatT // Silence warnings that StatT.platformStatT is unused (on these platforms)
return 0
}

View file

@ -4,10 +4,12 @@ import "syscall"
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &StatT{size: s.Size,
return &StatT{
size: s.Size,
mode: uint32(s.Mode),
uid: s.Uid,
gid: s.Gid,
rdev: uint64(s.Rdev),
mtim: s.Mtimespec}, nil
mtim: s.Mtimespec,
}, nil
}

View file

@ -2,12 +2,27 @@ package system
import "syscall"
type platformStatT struct {
flags uint32
}
// Flags return file flags if supported or zero otherwise
func (s StatT) Flags() uint32 {
return s.flags
}
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &StatT{size: s.Size,
st := &StatT{
size: s.Size,
mode: uint32(s.Mode),
uid: s.Uid,
gid: s.Gid,
rdev: uint64(s.Rdev),
mtim: s.Mtimespec}, nil
mtim: s.Mtimespec,
dev: s.Dev,
}
st.flags = s.Flags
st.dev = s.Dev
return st, nil
}

View file

@ -4,12 +4,15 @@ import "syscall"
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &StatT{size: s.Size,
return &StatT{
size: s.Size,
mode: s.Mode,
uid: s.Uid,
gid: s.Gid,
rdev: uint64(s.Rdev),
mtim: s.Mtim}, nil
mtim: s.Mtim,
dev: uint64(s.Dev),
}, nil
}
// FromStatT converts a syscall.Stat_t type to a system.Stat_t type

View file

@ -4,10 +4,12 @@ import "syscall"
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &StatT{size: s.Size,
return &StatT{
size: s.Size,
mode: uint32(s.Mode),
uid: s.Uid,
gid: s.Gid,
rdev: uint64(s.Rdev),
mtim: s.Mtim}, nil
mtim: s.Mtim,
}, nil
}

View file

@ -4,10 +4,12 @@ import "syscall"
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &StatT{size: s.Size,
return &StatT{
size: s.Size,
mode: uint32(s.Mode),
uid: s.Uid,
gid: s.Gid,
rdev: uint64(s.Rdev),
mtim: s.Mtim}, nil
mtim: s.Mtim,
}, nil
}

View file

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package system
@ -17,6 +18,8 @@ type StatT struct {
rdev uint64
size int64
mtim syscall.Timespec
dev uint64
platformStatT
}
// Mode returns file's permission mode.
@ -49,6 +52,11 @@ func (s StatT) Mtim() syscall.Timespec {
return s.mtim
}
// Dev returns a unique identifier for owning filesystem
func (s StatT) Dev() uint64 {
return s.dev
}
// Stat takes a path to a file and returns
// a system.StatT type pertaining to that file.
//

View file

@ -11,6 +11,7 @@ type StatT struct {
mode os.FileMode
size int64
mtim time.Time
platformStatT
}
// Size returns file's size.
@ -42,6 +43,11 @@ func (s StatT) GID() uint32 {
return 0
}
// Dev returns a unique identifier for owning filesystem
func (s StatT) Dev() uint64 {
return 0
}
// Stat takes a path to a file and returns
// a system.StatT type pertaining to that file.
//
@ -59,5 +65,6 @@ func fromStatT(fi *os.FileInfo) (*StatT, error) {
return &StatT{
size: (*fi).Size(),
mode: (*fi).Mode(),
mtim: (*fi).ModTime()}, nil
mtim: (*fi).ModTime(),
}, nil
}

View file

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package system

View file

@ -1,3 +1,4 @@
//go:build windows
// +build windows
package system

View file

@ -10,13 +10,14 @@ import (
// LUtimesNano is used to change access and modification time of the specified path.
// It's used for symbol link file because unix.UtimesNano doesn't support a NOFOLLOW flag atm.
func LUtimesNano(path string, ts []syscall.Timespec) error {
atFdCwd := unix.AT_FDCWD
var _path *byte
_path, err := unix.BytePtrFromString(path)
if err != nil {
return err
}
if _, _, err := unix.Syscall(unix.SYS_LUTIMES, uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), 0); err != 0 && err != unix.ENOSYS {
if _, _, err := unix.Syscall6(unix.SYS_UTIMENSAT, uintptr(atFdCwd), uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), unix.AT_SYMLINK_NOFOLLOW, 0, 0); err != 0 && err != unix.ENOSYS {
return err
}

View file

@ -1,3 +1,4 @@
//go:build !linux && !freebsd
// +build !linux,!freebsd
package system

View file

@ -1,3 +1,4 @@
//go:build !linux && !darwin
// +build !linux,!darwin
package system

View file

@ -1,3 +1,4 @@
//go:build linux && cgo
// +build linux,cgo
package unshare

View file

@ -1,3 +1,4 @@
//go:build linux && !cgo
// +build linux,!cgo
package unshare

View file

@ -5,18 +5,12 @@ import (
"os"
"os/user"
"sync"
"github.com/sirupsen/logrus"
)
var (
homeDirOnce sync.Once
homeDirErr error
homeDir string
hasCapSysAdminOnce sync.Once
hasCapSysAdminRet bool
hasCapSysAdminErr error
)
// HomeDir returns the home directory for the current user.
@ -36,14 +30,3 @@ func HomeDir() (string, error) {
})
return homeDir, homeDirErr
}
func bailOnError(err error, format string, a ...interface{}) { // nolint: golint,goprintffuncname
if err != nil {
if format != "" {
logrus.Errorf("%s: %v", fmt.Sprintf(format, a...), err)
} else {
logrus.Errorf("%v", err)
}
os.Exit(1)
}
}

View file

@ -5,7 +5,7 @@ package unshare
// #cgo CFLAGS: -Wall
// extern void _containers_unshare(void);
// void __attribute__((constructor)) init(void) {
// static void __attribute__((constructor)) init(void) {
// _containers_unshare();
// }
import "C"

View file

@ -1,3 +1,4 @@
//go:build darwin
// +build darwin
package unshare

View file

@ -59,7 +59,7 @@ func (c *Cmd) Start() error {
if err != nil {
pidRead.Close()
pidWrite.Close()
return fmt.Errorf("creating pid pipe: %w", err)
return fmt.Errorf("creating continue read/write pipe: %w", err)
}
c.Env = append(c.Env, fmt.Sprintf("_Containers-continue-pipe=%d", len(c.ExtraFiles)+3))
c.ExtraFiles = append(c.ExtraFiles, continueRead)

View file

@ -1,10 +1,11 @@
//go:build linux && cgo && gccgo
// +build linux,cgo,gccgo
package unshare
// #cgo CFLAGS: -Wall -Wextra
// extern void _containers_unshare(void);
// void __attribute__((constructor)) init(void) {
// static void __attribute__((constructor)) init(void) {
// _containers_unshare();
// }
import "C"

View file

@ -33,9 +33,9 @@ type Cmd struct {
*exec.Cmd
UnshareFlags int
UseNewuidmap bool
UidMappings []specs.LinuxIDMapping // nolint: golint
UidMappings []specs.LinuxIDMapping // nolint: revive,golint
UseNewgidmap bool
GidMappings []specs.LinuxIDMapping // nolint: golint
GidMappings []specs.LinuxIDMapping // nolint: revive,golint
GidMappingsEnableSetgroups bool
Setsid bool
Setpgrp bool
@ -129,7 +129,7 @@ func (c *Cmd) Start() error {
if err != nil {
pidRead.Close()
pidWrite.Close()
return fmt.Errorf("creating pid pipe: %w", err)
return fmt.Errorf("creating continue read/write pipe: %w", err)
}
c.Env = append(c.Env, fmt.Sprintf("_Containers-continue-pipe=%d", len(c.ExtraFiles)+3))
c.ExtraFiles = append(c.ExtraFiles, continueRead)
@ -175,12 +175,11 @@ func (c *Cmd) Start() error {
pidWrite = nil
// Read the child's PID from the pipe.
pidString := ""
b := new(bytes.Buffer)
if _, err := io.Copy(b, pidRead); err != nil {
return fmt.Errorf("reading child PID: %w", err)
}
pidString = b.String()
pidString := b.String()
pid, err := strconv.Atoi(pidString)
if err != nil {
fmt.Fprintf(continueWrite, "error parsing PID %q: %v", pidString, err)
@ -387,10 +386,47 @@ const (
UsernsEnvName = "_CONTAINERS_USERNS_CONFIGURED"
)
// hasFullUsersMappings checks whether the current user namespace has all the IDs mapped.
func hasFullUsersMappings() (bool, error) {
content, err := os.ReadFile("/proc/self/uid_map")
if err != nil {
return false, err
}
// The kernel rejects attempts to create mappings where either starting
// point is (u32)-1: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/user_namespace.c?id=af3e9579ecfb#n1006 .
// So, if the uid_map contains 4294967295, the entire IDs space is available in the
// user namespace, so it is likely the initial user namespace.
return bytes.Contains(content, []byte("4294967295")), nil
}
var (
hasCapSysAdminOnce sync.Once
hasCapSysAdminRet bool
hasCapSysAdminErr error
)
// IsRootless tells us if we are running in rootless mode
func IsRootless() bool {
isRootlessOnce.Do(func() {
isRootless = getRootlessUID() != 0 || getenv(UsernsEnvName) != ""
if !isRootless {
hasCapSysAdmin, err := HasCapSysAdmin()
if err != nil {
logrus.Warnf("Failed to read CAP_SYS_ADMIN presence for the current process")
}
if err == nil && !hasCapSysAdmin {
isRootless = true
}
}
if !isRootless {
hasMappings, err := hasFullUsersMappings()
if err != nil {
logrus.Warnf("Failed to read current user namespace mappings")
}
if err == nil && !hasMappings {
isRootless = true
}
}
})
return isRootless
}
@ -414,10 +450,21 @@ type Runnable interface {
Run() error
}
func bailOnError(err error, format string, a ...interface{}) { // nolint: revive,goprintffuncname
if err != nil {
if format != "" {
logrus.Errorf("%s: %v", fmt.Sprintf(format, a...), err)
} else {
logrus.Errorf("%v", err)
}
os.Exit(1)
}
}
// MaybeReexecUsingUserNamespace re-exec the process in a new namespace
func MaybeReexecUsingUserNamespace(evenForRoot bool) {
// If we've already been through this once, no need to try again.
if os.Geteuid() == 0 && IsRootless() {
if os.Geteuid() == 0 && GetRootlessUID() > 0 {
return
}