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  }