github.com/mgoltzsche/ctnr@v0.7.1-alpha/pkg/fs/source/sources.go (about)

     1  package source
     2  
     3  import (
     4  	"archive/tar"
     5  	"compress/bzip2"
     6  	"compress/gzip"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"syscall"
    11  	"time"
    12  
    13  	"github.com/mgoltzsche/ctnr/pkg/fs"
    14  	"github.com/mgoltzsche/ctnr/pkg/idutils"
    15  	"github.com/openSUSE/umoci/pkg/fseval"
    16  	"github.com/pkg/errors"
    17  	"golang.org/x/sys/unix"
    18  )
    19  
    20  type Sources struct {
    21  	fsEval     fseval.FsEval
    22  	attrMapper fs.AttrMapper
    23  	sourceMap  map[string]fs.Source
    24  }
    25  
    26  func NewSources(fsEval fseval.FsEval, attrMapper fs.AttrMapper) *Sources {
    27  	return &Sources{fsEval, attrMapper, map[string]fs.Source{}}
    28  }
    29  
    30  func (s *Sources) File(file string, fi os.FileInfo, usr *idutils.UserIds) (r fs.Source, err error) {
    31  	a, err := s.fileAttrs(file, fi, usr)
    32  	if err != nil {
    33  		return
    34  	}
    35  	fm := fi.Mode()
    36  	var da fs.DeviceAttrs
    37  	switch {
    38  	case fm.IsRegular():
    39  		a.Size = fi.Size()
    40  		r = NewSourceFile(fs.NewFileReader(file, s.fsEval), a)
    41  	case fm.IsDir():
    42  		r = NewSourceDir(a)
    43  	case fm&os.ModeSymlink != 0:
    44  		a.Symlink, err = s.fsEval.Readlink(file)
    45  		r = NewSourceSymlink(a)
    46  	case fm&os.ModeDevice != 0 || fm&os.ModeCharDevice != 0:
    47  		da, err = s.devAttrs(file, a)
    48  		r = NewSourceDevice(da)
    49  	case fm&os.ModeNamedPipe != 0:
    50  		da, err = s.devAttrs(file, a)
    51  		r = NewSourceFifo(da)
    52  	case fm&os.ModeSocket != 0:
    53  		return nil, errors.Errorf("source: sockets not supported (%s)", file)
    54  	default:
    55  		return nil, errors.Errorf("source: unknown file mode %v in %s", fm, file)
    56  	}
    57  	if err != nil {
    58  		return nil, errors.Wrap(err, "source file")
    59  	}
    60  
    61  	st := fi.Sys().(*syscall.Stat_t)
    62  	if st.Nlink > 1 {
    63  		// Handle hardlink - more than one path point to this node.
    64  		// Make sure a separate file is used if the user is different since same
    65  		// user is always applied on all hardlinks
    66  		inode := fmt.Sprintf("%x:%x:%d:%d", st.Dev, st.Ino, a.Uid, a.Gid)
    67  		src := s.sourceMap[inode]
    68  		if src == nil {
    69  			//r = NewSourceLink(inode, UpperLink, r)
    70  			s.sourceMap[inode] = r
    71  		} else {
    72  			r = src
    73  		}
    74  	}
    75  	return
    76  }
    77  
    78  func (s *Sources) devAttrs(file string, a fs.FileAttrs) (r fs.DeviceAttrs, err error) {
    79  	st, err := s.fsEval.Lstatx(file)
    80  	if err != nil {
    81  		return
    82  	}
    83  	r.FileAttrs = a
    84  	r.Devmajor = int64(unix.Major(st.Rdev))
    85  	r.Devminor = int64(unix.Minor(st.Rdev))
    86  	return
    87  }
    88  
    89  func (s *Sources) FileOverlay(file string, fi os.FileInfo, usr *idutils.UserIds) (r fs.Source, err error) {
    90  	if fi.Mode().IsRegular() {
    91  		return s.sourceMaybeOverlay(file, fi, usr)
    92  	}
    93  	return s.File(file, fi, usr)
    94  }
    95  
    96  func (s *Sources) fileAttrs(file string, si os.FileInfo, usr *idutils.UserIds) (r fs.FileAttrs, err error) {
    97  	/*symlink := ""
    98  	if si.Mode()&os.ModeSymlink != 0 {
    99  		if symlink, err = s.fsEval.Readlink(file); err != nil {
   100  			return r, errors.Wrap(err, "file attrs")
   101  		}
   102  	}
   103  	hdr, err := tar.FileInfoHeader(si, symlink)
   104  	if err != nil {
   105  		return r, errors.Wrap(err, "file attrs")
   106  	}*/
   107  
   108  	// permissions
   109  	r.Mode = si.Mode()
   110  	// atime/mtime
   111  	r.Atime, r.Mtime = fileTime(si)
   112  	// xattrs
   113  	xattrs, err := s.fsEval.Llistxattr(file)
   114  	if err != nil {
   115  		return r, errors.Wrap(err, "list xattrs of "+file)
   116  	}
   117  	if len(xattrs) > 0 {
   118  		r.Xattrs = map[string]string{}
   119  		for _, name := range xattrs {
   120  			value, e := s.fsEval.Lgetxattr(file, name)
   121  			if e != nil {
   122  				return r, errors.Wrapf(e, "get xattr %s of %s", name, file)
   123  			}
   124  			r.Xattrs[name] = string(value)
   125  		}
   126  	}
   127  	// uid/gid
   128  	if usr == nil {
   129  		st := si.Sys().(*syscall.Stat_t)
   130  		r.UserIds = idutils.UserIds{uint(st.Uid), uint(st.Gid)}
   131  		if err = s.attrMapper.ToContainer(&r); err != nil {
   132  			return r, errors.Wrapf(err, "source file %s", file)
   133  		}
   134  	}
   135  	// TODO: make sure user.rootlesscontainers xattr is removed
   136  	if usr != nil {
   137  		r.UserIds = *usr
   138  	}
   139  	return
   140  }
   141  
   142  func fileTime(st os.FileInfo) (atime, mtime time.Time) {
   143  	stu := st.Sys().(*syscall.Stat_t)
   144  	atime = time.Unix(int64(stu.Atim.Sec), int64(stu.Atim.Nsec)).UTC()
   145  	mtime = st.ModTime().UTC()
   146  	return
   147  }
   148  
   149  // Creates source for archive or simple file as fallback
   150  func (s *Sources) sourceMaybeOverlay(file string, fi os.FileInfo, usr *idutils.UserIds) (src fs.Source, err error) {
   151  	// Try open tar file
   152  	f, err := os.Open(file)
   153  	if err != nil {
   154  		return nil, errors.Wrap(err, "overlay source")
   155  	}
   156  	defer func() {
   157  		if err != nil {
   158  			err = errors.Wrapf(err, "overlay source %s", file)
   159  		}
   160  	}()
   161  	defer f.Close()
   162  	var r io.Reader = f
   163  	isGzip := false
   164  	isBzip2 := false
   165  
   166  	// Try to decompress gzip
   167  	gr, err := gzip.NewReader(r)
   168  	if err == nil {
   169  		r = gr
   170  		isGzip = true
   171  	} else if err != io.EOF && err != io.ErrUnexpectedEOF && err != gzip.ErrHeader {
   172  		return
   173  	} else if _, err = f.Seek(0, 0); err != nil {
   174  		return
   175  	}
   176  
   177  	// Try to decompress bzip2
   178  	if !isGzip && err != io.EOF {
   179  		br := bzip2.NewReader(r)
   180  		b := make([]byte, 512)
   181  		_, err = br.Read(b)
   182  		if err == nil {
   183  			r = br
   184  			isBzip2 = true
   185  		} else if err != io.EOF && err != io.ErrUnexpectedEOF {
   186  			if _, ok := err.(bzip2.StructuralError); !ok {
   187  				return
   188  			}
   189  		}
   190  		if _, err = f.Seek(0, 0); err != nil {
   191  			return
   192  		}
   193  	}
   194  
   195  	// Try to read as tar
   196  	tr := tar.NewReader(r)
   197  	if _, err = tr.Next(); err == nil {
   198  		if isGzip {
   199  			src = NewSourceTarGz(file)
   200  		} else if isBzip2 {
   201  			src = NewSourceTarBz(file)
   202  		} else {
   203  			src = NewSourceTar(file)
   204  		}
   205  		return src, nil
   206  	} else if err != io.EOF && err != io.ErrUnexpectedEOF && err != tar.ErrHeader {
   207  		return
   208  	}
   209  
   210  	// Treat as ordinary file if no compressed archive detected
   211  	return s.File(file, fi, usr)
   212  }