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 }