github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/lib/filesystem/untar/decode.go (about) 1 package untar 2 3 import ( 4 "archive/tar" 5 "errors" 6 "fmt" 7 "io" 8 "path" 9 "strings" 10 "syscall" 11 12 "github.com/Cloud-Foundations/Dominator/lib/filesystem" 13 "github.com/Cloud-Foundations/Dominator/lib/filter" 14 ) 15 16 type decoderData struct { 17 nextInodeNumber uint64 18 fileSystem filesystem.FileSystem 19 inodeTable map[string]uint64 20 directoryTable map[string]*filesystem.DirectoryInode 21 } 22 23 func decode(tarReader *tar.Reader, hasher Hasher, filter *filter.Filter) ( 24 *filesystem.FileSystem, error) { 25 var decoderData decoderData 26 decoderData.inodeTable = make(map[string]uint64) 27 decoderData.directoryTable = make(map[string]*filesystem.DirectoryInode) 28 fileSystem := &decoderData.fileSystem 29 fileSystem.InodeTable = make(filesystem.InodeTable) 30 // Create a default top-level directory which may be updated. 31 decoderData.addInode("/", &fileSystem.DirectoryInode) 32 fileSystem.DirectoryInode.Mode = syscall.S_IFDIR | syscall.S_IRWXU | 33 syscall.S_IRGRP | syscall.S_IXGRP | syscall.S_IROTH | syscall.S_IXOTH 34 decoderData.directoryTable["/"] = &fileSystem.DirectoryInode 35 for { 36 header, err := tarReader.Next() 37 if err == io.EOF { 38 break 39 } 40 if err != nil { 41 return nil, err 42 } 43 header.Name = normaliseFilename(header.Name) 44 if header.Name == "/.subd" || 45 strings.HasPrefix(header.Name, "/.subd/") { 46 continue 47 } 48 if filter != nil && filter.Match(header.Name) { 49 continue 50 } 51 err = decoderData.addHeader(tarReader, hasher, header) 52 if err != nil { 53 return nil, err 54 } 55 } 56 delete(fileSystem.InodeTable, 0) 57 fileSystem.DirectoryCount = uint64(len(decoderData.directoryTable)) 58 fileSystem.ComputeTotalDataBytes() 59 sortDirectory(&fileSystem.DirectoryInode) 60 return fileSystem, nil 61 } 62 63 func normaliseFilename(filename string) string { 64 if filename[:2] == "./" { 65 filename = filename[1:] 66 } else if filename[0] != '/' { 67 filename = "/" + filename 68 } 69 length := len(filename) 70 if length > 1 && filename[length-1] == '/' { 71 filename = filename[:length-1] 72 } 73 return filename 74 } 75 76 func (decoderData *decoderData) addHeader(tarReader *tar.Reader, hasher Hasher, 77 header *tar.Header) error { 78 parentDir, ok := decoderData.directoryTable[path.Dir(header.Name)] 79 if !ok { 80 return errors.New(fmt.Sprintf( 81 "No parent directory found for: %s", header.Name)) 82 } 83 leafName := path.Base(header.Name) 84 if header.Typeflag == tar.TypeReg || header.Typeflag == tar.TypeRegA { 85 return decoderData.addRegularFile(tarReader, hasher, header, 86 parentDir, leafName) 87 } else if header.Typeflag == tar.TypeLink { 88 return decoderData.addHardlink(header, parentDir, leafName) 89 } else if header.Typeflag == tar.TypeSymlink { 90 return decoderData.addSymlink(header, parentDir, leafName) 91 } else if header.Typeflag == tar.TypeChar { 92 return decoderData.addSpecialFile(header, parentDir, leafName) 93 } else if header.Typeflag == tar.TypeBlock { 94 return decoderData.addSpecialFile(header, parentDir, leafName) 95 } else if header.Typeflag == tar.TypeDir { 96 return decoderData.addDirectory(header, parentDir, leafName) 97 } else if header.Typeflag == tar.TypeFifo { 98 return decoderData.addSpecialFile(header, parentDir, leafName) 99 } else { 100 return errors.New(fmt.Sprintf("Unsupported file type: %v", 101 header.Typeflag)) 102 } 103 } 104 105 func (decoderData *decoderData) addRegularFile(tarReader *tar.Reader, 106 hasher Hasher, header *tar.Header, parent *filesystem.DirectoryInode, 107 name string) error { 108 var newInode filesystem.RegularInode 109 newInode.Mode = filesystem.FileMode((header.Mode & ^syscall.S_IFMT) | 110 syscall.S_IFREG) 111 newInode.Uid = uint32(header.Uid) 112 newInode.Gid = uint32(header.Gid) 113 newInode.MtimeNanoSeconds = int32(header.ModTime.Nanosecond()) 114 newInode.MtimeSeconds = header.ModTime.Unix() 115 newInode.Size = uint64(header.Size) 116 if header.Size > 0 { 117 var err error 118 newInode.Hash, err = hasher.Hash(tarReader, uint64(header.Size)) 119 if err != nil { 120 return err 121 } 122 } 123 decoderData.addEntry(parent, header.Name, name, &newInode) 124 return nil 125 } 126 127 func (decoderData *decoderData) addDirectory(header *tar.Header, 128 parent *filesystem.DirectoryInode, name string) error { 129 var newInode filesystem.DirectoryInode 130 newInode.Mode = filesystem.FileMode((header.Mode & ^syscall.S_IFMT) | 131 syscall.S_IFDIR) 132 newInode.Uid = uint32(header.Uid) 133 newInode.Gid = uint32(header.Gid) 134 if header.Name == "/" { 135 *decoderData.directoryTable[header.Name] = newInode 136 return nil 137 } 138 decoderData.addEntry(parent, header.Name, name, &newInode) 139 decoderData.directoryTable[header.Name] = &newInode 140 return nil 141 } 142 143 func (decoderData *decoderData) addHardlink(header *tar.Header, 144 parent *filesystem.DirectoryInode, name string) error { 145 header.Linkname = normaliseFilename(header.Linkname) 146 if inum, ok := decoderData.inodeTable[header.Linkname]; ok { 147 var newEntry filesystem.DirectoryEntry 148 newEntry.Name = name 149 newEntry.InodeNumber = inum 150 parent.EntryList = append(parent.EntryList, &newEntry) 151 } else { 152 return errors.New(fmt.Sprintf("missing hardlink target: %s", 153 header.Linkname)) 154 } 155 return nil 156 } 157 158 func (decoderData *decoderData) addSymlink(header *tar.Header, 159 parent *filesystem.DirectoryInode, name string) error { 160 var newInode filesystem.SymlinkInode 161 newInode.Uid = uint32(header.Uid) 162 newInode.Gid = uint32(header.Gid) 163 newInode.Symlink = header.Linkname 164 decoderData.addEntry(parent, header.Name, name, &newInode) 165 return nil 166 } 167 168 func (decoderData *decoderData) addSpecialFile(header *tar.Header, 169 parent *filesystem.DirectoryInode, name string) error { 170 var newInode filesystem.SpecialInode 171 if header.Typeflag == tar.TypeChar { 172 newInode.Mode = filesystem.FileMode((header.Mode & ^syscall.S_IFMT) | 173 syscall.S_IFCHR) 174 } else if header.Typeflag == tar.TypeBlock { 175 newInode.Mode = filesystem.FileMode((header.Mode & ^syscall.S_IFMT) | 176 syscall.S_IFBLK) 177 } else if header.Typeflag == tar.TypeFifo { 178 newInode.Mode = filesystem.FileMode((header.Mode & ^syscall.S_IFMT) | 179 syscall.S_IFIFO) 180 } else { 181 return errors.New(fmt.Sprintf("unsupported type: %v", header.Typeflag)) 182 } 183 newInode.Uid = uint32(header.Uid) 184 newInode.Gid = uint32(header.Gid) 185 newInode.MtimeNanoSeconds = int32(header.ModTime.Nanosecond()) 186 newInode.MtimeSeconds = header.ModTime.Unix() 187 if header.Devminor > 255 { 188 return errors.New(fmt.Sprintf("minor device number: %d too large", 189 header.Devminor)) 190 } 191 newInode.Rdev = uint64(header.Devmajor<<8 | header.Devminor) 192 decoderData.addEntry(parent, header.Name, name, &newInode) 193 return nil 194 } 195 196 func (decoderData *decoderData) addEntry(parent *filesystem.DirectoryInode, 197 fullName, name string, inode filesystem.GenericInode) { 198 var newEntry filesystem.DirectoryEntry 199 newEntry.Name = name 200 newEntry.InodeNumber = decoderData.nextInodeNumber 201 newEntry.SetInode(inode) 202 parent.EntryList = append(parent.EntryList, &newEntry) 203 decoderData.addInode(fullName, inode) 204 } 205 206 func (decoderData *decoderData) addInode(fullName string, 207 inode filesystem.GenericInode) { 208 decoderData.inodeTable[fullName] = decoderData.nextInodeNumber 209 decoderData.fileSystem.InodeTable[decoderData.nextInodeNumber] = inode 210 decoderData.nextInodeNumber++ 211 }