github.com/Cloud-Foundations/Dominator@v0.3.4/lib/filesystem/util/unpack.go (about) 1 package util 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "path" 9 "syscall" 10 "time" 11 12 "github.com/Cloud-Foundations/Dominator/lib/filesystem" 13 "github.com/Cloud-Foundations/Dominator/lib/format" 14 "github.com/Cloud-Foundations/Dominator/lib/hash" 15 "github.com/Cloud-Foundations/Dominator/lib/log" 16 "github.com/Cloud-Foundations/Dominator/lib/objectserver" 17 ) 18 19 const ( 20 dirPerms = syscall.S_IRWXU 21 filePerms = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IRGRP 22 ) 23 24 func createEmptyFile(filename string) error { 25 if file, err := os.Create(filename); err != nil { 26 return err 27 } else { 28 // Don't wait for finaliser to close, otherwise we can have too many 29 // open files. 30 file.Close() 31 return nil 32 } 33 } 34 35 func unpack(fs *filesystem.FileSystem, objectsGetter objectserver.ObjectsGetter, 36 dirname string, logger log.Logger) error { 37 for _, entry := range fs.EntryList { 38 if entry.Name == ".inodes" { 39 return errors.New("cannot unpack a file-system with /.inodes") 40 } 41 } 42 os.Mkdir(dirname, dirPerms) 43 inodesDir := path.Join(dirname, ".inodes") 44 if err := os.Mkdir(inodesDir, dirPerms); err != nil { 45 return err 46 } 47 defer os.RemoveAll(inodesDir) 48 var statfs syscall.Statfs_t 49 if err := syscall.Statfs(inodesDir, &statfs); err != nil { 50 return fmt.Errorf("unable to Statfs: %s %s\n", inodesDir, err) 51 } 52 if fs.TotalDataBytes > uint64(statfs.Bsize)*statfs.Bfree { 53 return errors.New("image will not fit on file-system") 54 } 55 hashes, inums, lengths := getHashes(fs) 56 err := writeObjects(objectsGetter, hashes, inums, lengths, inodesDir, 57 logger) 58 if err != nil { 59 return err 60 } 61 startWriteTime := time.Now() 62 if err := writeInodes(fs.InodeTable, inodesDir); err != nil { 63 return err 64 } 65 if err = fs.DirectoryInode.Write(dirname); err != nil { 66 return err 67 } 68 startBuildTime := time.Now() 69 writeDuration := startBuildTime.Sub(startWriteTime) 70 err = buildTree(&fs.DirectoryInode, dirname, "", inodesDir) 71 if err != nil { 72 return err 73 } 74 buildDuration := time.Since(startBuildTime) 75 logger.Printf("Unpacked file-system: made inodes in %s, built tree in %s\n", 76 format.Duration(writeDuration), format.Duration(buildDuration)) 77 return nil 78 } 79 80 func getHashes(fs *filesystem.FileSystem) ([]hash.Hash, []uint64, []uint64) { 81 hashes := make([]hash.Hash, 0, fs.NumRegularInodes) 82 inums := make([]uint64, 0, fs.NumRegularInodes) 83 lengths := make([]uint64, 0, fs.NumRegularInodes) 84 for inum, inode := range fs.InodeTable { 85 if inode, ok := inode.(*filesystem.RegularInode); ok { 86 if inode.Size > 0 { 87 hashes = append(hashes, inode.Hash) 88 inums = append(inums, inum) 89 lengths = append(lengths, inode.Size) 90 } 91 } 92 } 93 return hashes, inums, lengths 94 } 95 96 func writeObjects(objectsGetter objectserver.ObjectsGetter, hashes []hash.Hash, 97 inums []uint64, lengths []uint64, inodesDir string, 98 logger log.Logger) error { 99 startTime := time.Now() 100 objectsReader, err := objectsGetter.GetObjects(hashes) 101 if err != nil { 102 return fmt.Errorf("error getting object reader: %s\n", err) 103 } 104 defer objectsReader.Close() 105 var totalLength uint64 106 buffer := make([]byte, 32<<10) 107 for index := range hashes { 108 err = writeObject(objectsReader, inums[index], lengths[index], 109 inodesDir, buffer) 110 if err != nil { 111 return err 112 } 113 totalLength += lengths[index] 114 } 115 duration := time.Since(startTime) 116 speed := uint64(float64(totalLength) / duration.Seconds()) 117 logger.Printf("Copied %d objects (%s) in %s (%s/s)\n", 118 len(hashes), format.FormatBytes(totalLength), format.Duration(duration), 119 format.FormatBytes(speed)) 120 return nil 121 } 122 123 func writeObject(objectsReader objectserver.ObjectsReader, inodeNumber uint64, 124 length uint64, inodesDir string, buffer []byte) error { 125 rlength, reader, err := objectsReader.NextObject() 126 if err != nil { 127 return err 128 } 129 defer reader.Close() 130 if rlength != length { 131 return errors.New("mismatched lengths") 132 } 133 filename := path.Join(inodesDir, fmt.Sprintf("%d", inodeNumber)) 134 destFile, err := os.OpenFile(filename, 135 os.O_CREATE|os.O_TRUNC|os.O_WRONLY, filePerms) 136 if err != nil { 137 return err 138 } 139 doClose := true 140 defer func() { 141 if doClose { 142 destFile.Close() 143 } 144 }() 145 iLength := int64(length) 146 nCopied, err := io.CopyBuffer(destFile, io.LimitReader(reader, iLength), 147 buffer) 148 if err != nil { 149 return fmt.Errorf("error copying: %s", err) 150 } 151 if nCopied != iLength { 152 return fmt.Errorf("expected length: %d, got: %d for: %s\n", 153 length, nCopied, filename) 154 } 155 doClose = false 156 return destFile.Close() 157 } 158 159 func writeInode(inode filesystem.GenericInode, filename string) error { 160 switch inode := inode.(type) { 161 case *filesystem.RegularInode: 162 if inode.Size < 1 { 163 if err := createEmptyFile(filename); err != nil { 164 return err 165 } 166 } 167 if err := inode.WriteMetadata(filename); err != nil { 168 return err 169 } 170 case *filesystem.ComputedRegularInode: 171 if err := createEmptyFile(filename); err != nil { 172 return err 173 } 174 tmpInode := &filesystem.RegularInode{ 175 Mode: inode.Mode, 176 Uid: inode.Uid, 177 Gid: inode.Gid, 178 } 179 if err := tmpInode.WriteMetadata(filename); err != nil { 180 return err 181 } 182 case *filesystem.SymlinkInode: 183 if err := inode.Write(filename); err != nil { 184 return err 185 } 186 case *filesystem.SpecialInode: 187 if err := inode.Write(filename); err != nil { 188 return err 189 } 190 case *filesystem.DirectoryInode: 191 if err := inode.Write(filename); err != nil { 192 return err 193 } 194 default: 195 return errors.New("unsupported inode type") 196 } 197 return nil 198 } 199 200 func writeInodes(inodeTable filesystem.InodeTable, inodesDir string) error { 201 for inodeNumber, inode := range inodeTable { 202 filename := path.Join(inodesDir, fmt.Sprintf("%d", inodeNumber)) 203 if err := writeInode(inode, filename); err != nil { 204 return err 205 } 206 } 207 return nil 208 } 209 210 func buildTree(directory *filesystem.DirectoryInode, 211 rootDir, mySubPathName, inodesDir string) error { 212 for _, dirent := range directory.EntryList { 213 oldPath := path.Join(inodesDir, fmt.Sprintf("%d", dirent.InodeNumber)) 214 newSubPath := path.Join(mySubPathName, dirent.Name) 215 newFullPath := path.Join(rootDir, newSubPath) 216 if inode := dirent.Inode(); inode == nil { 217 panic("no inode pointer for: " + newSubPath) 218 } else if dinode, ok := inode.(*filesystem.DirectoryInode); ok { 219 if err := renameDir(oldPath, newFullPath, dinode); err != nil { 220 return err 221 } 222 err := buildTree(dinode, rootDir, newSubPath, inodesDir) 223 if err != nil { 224 return err 225 } 226 } else { 227 if err := link(oldPath, newFullPath, inode); err != nil { 228 if !os.IsNotExist(err) { 229 return err 230 } 231 } 232 } 233 } 234 return nil 235 } 236 237 func link(oldname, newname string, inode filesystem.GenericInode) error { 238 if err := os.Link(oldname, newname); err == nil { 239 return nil 240 } 241 if finode, ok := inode.(*filesystem.RegularInode); ok { 242 if finode.Size > 0 { 243 reader, err := os.Open(oldname) 244 if err != nil { 245 return err 246 } 247 defer reader.Close() 248 writer, err := os.Create(newname) 249 if err != nil { 250 return err 251 } 252 defer writer.Close() 253 if _, err := io.Copy(writer, reader); err != nil { 254 return err 255 } 256 } 257 } 258 if err := writeInode(inode, newname); err != nil { 259 return err 260 } 261 return nil 262 } 263 264 func renameDir(oldpath, newpath string, 265 inode *filesystem.DirectoryInode) error { 266 if err := os.Rename(oldpath, newpath); err == nil { 267 return nil 268 } 269 if oldFi, err := os.Lstat(oldpath); err != nil { 270 return err 271 } else { 272 if !oldFi.IsDir() { 273 return fmt.Errorf("%s is not a directory", oldpath) 274 } 275 } 276 if newFi, err := os.Lstat(newpath); err != nil { 277 if !os.IsNotExist(err) { 278 return err 279 } 280 if err := inode.Write(newpath); err != nil { 281 return err 282 } 283 } else { 284 if !newFi.IsDir() { 285 return fmt.Errorf("%s is not a directory", newpath) 286 } 287 if err := inode.WriteMetadata(newpath); err != nil { 288 return err 289 } 290 } 291 if err := os.Remove(oldpath); err != nil { 292 return err 293 } 294 return nil 295 }