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  }