github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/lib/filesystem/tar/encode.go (about)

     1  package tar
     2  
     3  import (
     4  	"archive/tar"
     5  	"fmt"
     6  	"io"
     7  	"syscall"
     8  	"time"
     9  
    10  	"github.com/Cloud-Foundations/Dominator/lib/filesystem"
    11  	"github.com/Cloud-Foundations/Dominator/lib/hash"
    12  	"github.com/Cloud-Foundations/Dominator/lib/objectserver"
    13  )
    14  
    15  func encode(tarWriter *tar.Writer, fileSystem *filesystem.FileSystem,
    16  	objectsGetter objectserver.ObjectsGetter) error {
    17  	hashList := getOrderedObjectsList(fileSystem)
    18  	objectsReader, err := objectsGetter.GetObjects(hashList)
    19  	if err != nil {
    20  		return err
    21  	}
    22  	defer objectsReader.Close()
    23  	return writeDirectory(tarWriter, fileSystem, &fileSystem.DirectoryInode,
    24  		".", objectsReader, make(map[uint64]struct{}))
    25  }
    26  
    27  func getOrderedObjectsList(fileSystem *filesystem.FileSystem) []hash.Hash {
    28  	hashList := make([]hash.Hash, 0, len(fileSystem.InodeTable))
    29  	inodeTable := make(map[uint64]struct{}, len(fileSystem.InodeTable))
    30  	listObjects(&fileSystem.DirectoryInode, &hashList, inodeTable)
    31  	return hashList
    32  }
    33  
    34  func listObjects(inode *filesystem.DirectoryInode, hashList *[]hash.Hash,
    35  	inodeTable map[uint64]struct{}) {
    36  	for _, entry := range inode.EntryList {
    37  		if eInode, ok := entry.Inode().(*filesystem.RegularInode); ok {
    38  			if eInode.Size > 0 {
    39  				if _, ok := inodeTable[entry.InodeNumber]; !ok {
    40  					*hashList = append(*hashList, eInode.Hash)
    41  					inodeTable[entry.InodeNumber] = struct{}{}
    42  				}
    43  			}
    44  		} else if eInode, ok := entry.Inode().(*filesystem.DirectoryInode); ok {
    45  			listObjects(eInode, hashList, inodeTable)
    46  		}
    47  	}
    48  }
    49  
    50  func writeDirectory(tarWriter *tar.Writer, fileSystem *filesystem.FileSystem,
    51  	inode *filesystem.DirectoryInode, dirname string,
    52  	objectsReader objectserver.ObjectsReader,
    53  	inodeTable map[uint64]struct{}) error {
    54  	header := tar.Header{
    55  		Name:     dirname + "/",
    56  		Mode:     int64(inode.Mode),
    57  		Uid:      int(inode.Uid),
    58  		Gid:      int(inode.Gid),
    59  		Typeflag: tar.TypeDir,
    60  	}
    61  	if err := tarWriter.WriteHeader(&header); err != nil {
    62  		return err
    63  	}
    64  	for _, entry := range inode.EntryList {
    65  		err := writeInode(tarWriter, fileSystem, entry.Inode(),
    66  			dirname+"/"+entry.Name, entry.InodeNumber, objectsReader,
    67  			inodeTable)
    68  		if err != nil {
    69  			return err
    70  		}
    71  	}
    72  	return nil
    73  }
    74  
    75  func writeRegularFile(tarWriter *tar.Writer, fileSystem *filesystem.FileSystem,
    76  	inode *filesystem.RegularInode, name string, inodeNumber uint64,
    77  	objectsReader objectserver.ObjectsReader,
    78  	inodeTable map[uint64]struct{}) error {
    79  	header := tar.Header{
    80  		Name:     name,
    81  		Mode:     int64(inode.Mode),
    82  		Uid:      int(inode.Uid),
    83  		Gid:      int(inode.Gid),
    84  		Size:     int64(inode.Size),
    85  		ModTime:  time.Unix(inode.MtimeSeconds, int64(inode.MtimeNanoSeconds)),
    86  		Typeflag: tar.TypeReg,
    87  	}
    88  	err := writeHeader(tarWriter, fileSystem, &header, inodeNumber,
    89  		inodeTable)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	if header.Size > 0 {
    94  		size, reader, err := objectsReader.NextObject()
    95  		if err != nil {
    96  			return err
    97  		}
    98  		defer reader.Close()
    99  		if size != inode.Size {
   100  			return fmt.Errorf("%s inode size: %d, object size: %d",
   101  				name, inode.Size, size)
   102  		}
   103  		nCopied, err := io.Copy(tarWriter, reader)
   104  		if err != nil {
   105  			return err
   106  		}
   107  		if nCopied != int64(size) {
   108  			return fmt.Errorf("nCopied: %d != size: %d", nCopied, size)
   109  		}
   110  	}
   111  	return nil
   112  }
   113  
   114  func writeHeader(tarWriter *tar.Writer, fileSystem *filesystem.FileSystem,
   115  	header *tar.Header, inum uint64, inodeTable map[uint64]struct{}) error {
   116  	if _, ok := inodeTable[inum]; ok {
   117  		header.Linkname = "." + fileSystem.InodeToFilenamesTable()[inum][0]
   118  		header.Size = 0
   119  		header.Typeflag = tar.TypeLink
   120  	} else {
   121  		inodeTable[inum] = struct{}{}
   122  	}
   123  	return tarWriter.WriteHeader(header)
   124  }
   125  
   126  func writeInode(tarWriter *tar.Writer, fileSystem *filesystem.FileSystem,
   127  	inode filesystem.GenericInode, name string, inodeNumber uint64,
   128  	objectsReader objectserver.ObjectsReader,
   129  	inodeTable map[uint64]struct{}) error {
   130  	var err error
   131  	if eInode, ok := inode.(*filesystem.DirectoryInode); ok {
   132  		err = writeDirectory(tarWriter, fileSystem, eInode, name, objectsReader,
   133  			inodeTable)
   134  	} else if eInode, ok := inode.(*filesystem.RegularInode); ok {
   135  		err = writeRegularFile(tarWriter, fileSystem, eInode, name, inodeNumber,
   136  			objectsReader, inodeTable)
   137  	} else if eInode, ok := inode.(*filesystem.ComputedRegularInode); ok {
   138  		err = writeRegularFile(tarWriter, fileSystem, &filesystem.RegularInode{
   139  			Mode: eInode.Mode,
   140  			Uid:  eInode.Uid,
   141  			Gid:  eInode.Gid,
   142  		}, name, inodeNumber, objectsReader, inodeTable)
   143  	} else if eInode, ok := inode.(*filesystem.SpecialInode); ok {
   144  		err = writeSpecial(tarWriter, fileSystem, eInode, name, inodeNumber,
   145  			inodeTable)
   146  	} else if eInode, ok := inode.(*filesystem.SymlinkInode); ok {
   147  		err = writeSymlink(tarWriter, fileSystem, eInode, name, inodeNumber,
   148  			inodeTable)
   149  	}
   150  	return err
   151  }
   152  
   153  func writeSpecial(tarWriter *tar.Writer, fileSystem *filesystem.FileSystem,
   154  	inode *filesystem.SpecialInode, name string, inodeNumber uint64,
   155  	inodeTable map[uint64]struct{}) error {
   156  	header := tar.Header{
   157  		Name:     name,
   158  		Mode:     int64(inode.Mode),
   159  		Uid:      int(inode.Uid),
   160  		Gid:      int(inode.Gid),
   161  		ModTime:  time.Unix(inode.MtimeSeconds, int64(inode.MtimeNanoSeconds)),
   162  		Devmajor: int64(inode.Rdev >> 8),
   163  		Devminor: int64(inode.Rdev & 0xff),
   164  	}
   165  	if inode.Mode&syscall.S_IFMT == syscall.S_IFCHR {
   166  		header.Typeflag = tar.TypeChar
   167  	} else if inode.Mode&syscall.S_IFMT == syscall.S_IFBLK {
   168  		header.Typeflag = tar.TypeBlock
   169  	} else if inode.Mode&syscall.S_IFMT == syscall.S_IFIFO {
   170  		header.Typeflag = tar.TypeFifo
   171  	} else {
   172  		return fmt.Errorf("unsupported inode mode: %d", inode.Mode)
   173  	}
   174  	return writeHeader(tarWriter, fileSystem, &header, inodeNumber, inodeTable)
   175  }
   176  
   177  func writeSymlink(tarWriter *tar.Writer, fileSystem *filesystem.FileSystem,
   178  	inode *filesystem.SymlinkInode, name string, inodeNumber uint64,
   179  	inodeTable map[uint64]struct{}) error {
   180  	header := tar.Header{
   181  		Name:     name,
   182  		Mode:     0777,
   183  		Uid:      int(inode.Uid),
   184  		Gid:      int(inode.Gid),
   185  		Typeflag: tar.TypeSymlink,
   186  		Linkname: inode.Symlink,
   187  	}
   188  	return writeHeader(tarWriter, fileSystem, &header, inodeNumber, inodeTable)
   189  }