github.com/Cloud-Foundations/Dominator@v0.3.4/sub/lib/update.go (about)

     1  package lib
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"syscall"
    12  	"time"
    13  
    14  	"github.com/Cloud-Foundations/Dominator/lib/constants"
    15  	"github.com/Cloud-Foundations/Dominator/lib/filesystem"
    16  	"github.com/Cloud-Foundations/Dominator/lib/filesystem/scanner"
    17  	"github.com/Cloud-Foundations/Dominator/lib/filter"
    18  	"github.com/Cloud-Foundations/Dominator/lib/fsutil"
    19  	"github.com/Cloud-Foundations/Dominator/lib/hash"
    20  	"github.com/Cloud-Foundations/Dominator/lib/log"
    21  	"github.com/Cloud-Foundations/Dominator/lib/objectcache"
    22  	"github.com/Cloud-Foundations/Dominator/lib/triggers"
    23  	"github.com/Cloud-Foundations/Dominator/lib/wsyscall"
    24  	"github.com/Cloud-Foundations/Dominator/proto/sub"
    25  )
    26  
    27  func (t *uType) update(request sub.UpdateRequest) error {
    28  	if request.Triggers == nil {
    29  		request.Triggers = triggers.New()
    30  	}
    31  	if t.SkipFilter == nil {
    32  		t.SkipFilter = new(filter.Filter)
    33  	}
    34  	t.copyFilesToCache(request.FilesToCopyToCache)
    35  	t.makeObjectCopies(request.MultiplyUsedObjects)
    36  	if t.RunTriggers != nil &&
    37  		t.OldTriggers != nil && len(t.OldTriggers.Triggers) > 0 {
    38  		t.makeDirectories(request.DirectoriesToMake,
    39  			t.OldTriggers, false)
    40  		t.makeInodes(request.InodesToMake, request.MultiplyUsedObjects,
    41  			t.OldTriggers, false)
    42  		t.makeHardlinks(request.HardlinksToMake, t.OldTriggers, false)
    43  		t.doDeletes(request.PathsToDelete, t.OldTriggers, false)
    44  		t.changeInodes(request.InodesToChange, t.OldTriggers, false)
    45  		matchedOldTriggers := t.OldTriggers.GetMatchedTriggers()
    46  		err := t.checkDisruption(matchedOldTriggers, request.ForceDisruption)
    47  		if err != nil {
    48  			return err
    49  		}
    50  		if t.RunTriggers(matchedOldTriggers, "stop", t.Logger) {
    51  			t.hadTriggerFailures = true
    52  		}
    53  	}
    54  	fsChangeStartTime := time.Now()
    55  	t.makeDirectories(request.DirectoriesToMake, request.Triggers, true)
    56  	t.makeInodes(request.InodesToMake, request.MultiplyUsedObjects,
    57  		request.Triggers, true)
    58  	t.makeHardlinks(request.HardlinksToMake, request.Triggers, true)
    59  	t.doDeletes(request.PathsToDelete, request.Triggers, true)
    60  	t.changeInodes(request.InodesToChange, request.Triggers, true)
    61  	if err := t.writePatchedImageName(request.ImageName); err != nil {
    62  		t.Logger.Println(err)
    63  	}
    64  	t.fsChangeDuration = time.Since(fsChangeStartTime)
    65  	matchedNewTriggers := request.Triggers.GetMatchedTriggers()
    66  	if t.RunTriggers != nil &&
    67  		t.RunTriggers(matchedNewTriggers, "start", t.Logger) {
    68  		t.hadTriggerFailures = true
    69  	}
    70  	return t.lastError
    71  }
    72  
    73  func (t *uType) checkDisruption(matchedTriggers []*triggers.Trigger,
    74  	force bool) error {
    75  	if t.DisruptionRequest == nil && t.DisruptionCancel == nil {
    76  		return nil
    77  	}
    78  	if !isHighImpact(matchedTriggers) {
    79  		if t.DisruptionCancel != nil {
    80  			t.DisruptionCancel()
    81  		}
    82  		return nil
    83  	}
    84  	if force {
    85  		return nil
    86  	}
    87  	if t.DisruptionRequest == nil {
    88  		return nil
    89  	}
    90  	switch t.DisruptionRequest() {
    91  	case sub.DisruptionStateAnytime:
    92  		return nil
    93  	case sub.DisruptionStatePermitted:
    94  		return nil
    95  	case sub.DisruptionStateRequested:
    96  		return errors.New(sub.ErrorDisruptionPending)
    97  	case sub.DisruptionStateDenied:
    98  		return errors.New(sub.ErrorDisruptionDenied)
    99  	default:
   100  		return nil
   101  	}
   102  }
   103  
   104  func isHighImpact(matchedTriggers []*triggers.Trigger) bool {
   105  	if len(matchedTriggers) < 1 {
   106  		return false
   107  	}
   108  	for _, trigger := range matchedTriggers {
   109  		if trigger.HighImpact {
   110  			return true
   111  		}
   112  	}
   113  	return false
   114  }
   115  
   116  func (t *uType) copyFilesToCache(filesToCopyToCache []sub.FileToCopyToCache) {
   117  	for _, fileToCopy := range filesToCopyToCache {
   118  		sourcePathname := filepath.Join(t.RootDirectoryName, fileToCopy.Name)
   119  		destPathname := filepath.Join(t.ObjectsDir,
   120  			objectcache.HashToFilename(fileToCopy.Hash))
   121  		prefix := "Copied"
   122  		if fileToCopy.DoHardlink {
   123  			prefix = "Hardlinked"
   124  		}
   125  		if err := copyFile(destPathname, sourcePathname,
   126  			fileToCopy.DoHardlink); err != nil {
   127  			t.lastError = err
   128  			t.Logger.Println(err)
   129  		} else {
   130  			t.Logger.Printf("%s: %s to cache\n", prefix, sourcePathname)
   131  		}
   132  	}
   133  }
   134  
   135  func copyFile(destPathname, sourcePathname string, doHardlink bool) error {
   136  	dirname := filepath.Dir(destPathname)
   137  	if err := os.MkdirAll(dirname, syscall.S_IRWXU); err != nil {
   138  		return err
   139  	}
   140  	if doHardlink {
   141  		return fsutil.ForceLink(sourcePathname, destPathname)
   142  	}
   143  	sourceFile, err := os.Open(sourcePathname)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	defer sourceFile.Close()
   148  	destFile, err := os.Create(destPathname)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	defer destFile.Close()
   153  	_, err = io.Copy(destFile, sourceFile)
   154  	return err
   155  }
   156  
   157  func (t *uType) makeObjectCopies(multiplyUsedObjects map[hash.Hash]uint64) {
   158  	for hash, numCopies := range multiplyUsedObjects {
   159  		if numCopies < 2 {
   160  			continue
   161  		}
   162  		objectPathname := filepath.Join(t.ObjectsDir,
   163  			objectcache.HashToFilename(hash))
   164  		for numCopies--; numCopies > 0; numCopies-- {
   165  			ext := fmt.Sprintf("~%d~", numCopies)
   166  			if err := copyFile(objectPathname+ext, objectPathname,
   167  				false); err != nil {
   168  				t.lastError = err
   169  				t.Logger.Println(err)
   170  			} else {
   171  				t.Logger.Printf("Copied object: %x%s\n", hash, ext)
   172  			}
   173  		}
   174  	}
   175  }
   176  
   177  func (t *uType) makeInodes(inodesToMake []sub.Inode,
   178  	multiplyUsedObjects map[hash.Hash]uint64, triggers *triggers.Triggers,
   179  	takeAction bool) {
   180  	for _, inode := range inodesToMake {
   181  		triggers.Match(inode.Name)
   182  		if takeAction {
   183  			fullPathname := filepath.Join(t.RootDirectoryName, inode.Name)
   184  			var err error
   185  			switch inode := inode.GenericInode.(type) {
   186  			case *filesystem.RegularInode:
   187  				err = makeRegularInode(fullPathname, inode, multiplyUsedObjects,
   188  					t.ObjectsDir, t.Logger)
   189  			case *filesystem.SymlinkInode:
   190  				err = makeSymlinkInode(fullPathname, inode, t.Logger)
   191  			case *filesystem.SpecialInode:
   192  				err = makeSpecialInode(fullPathname, inode, t.Logger)
   193  			}
   194  			if err != nil {
   195  				t.lastError = err
   196  			}
   197  		}
   198  	}
   199  }
   200  
   201  func makeRegularInode(fullPathname string,
   202  	inode *filesystem.RegularInode, multiplyUsedObjects map[hash.Hash]uint64,
   203  	objectsDir string, logger log.Logger) error {
   204  	var objectPathname string
   205  	if inode.Size > 0 {
   206  		objectPathname = filepath.Join(objectsDir,
   207  			objectcache.HashToFilename(inode.Hash))
   208  		numCopies := multiplyUsedObjects[inode.Hash]
   209  		if numCopies > 1 {
   210  			numCopies--
   211  			objectPathname += fmt.Sprintf("~%d~", numCopies)
   212  			if numCopies < 2 {
   213  				delete(multiplyUsedObjects, inode.Hash)
   214  			} else {
   215  				multiplyUsedObjects[inode.Hash] = numCopies
   216  			}
   217  		}
   218  	} else {
   219  		objectPathname = fmt.Sprintf("%s.empty.%d", fullPathname, os.Getpid())
   220  		if file, err := os.OpenFile(objectPathname,
   221  			os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600); err != nil {
   222  			return err
   223  		} else {
   224  			file.Close()
   225  		}
   226  	}
   227  	if err := fsutil.ForceRename(objectPathname, fullPathname); err != nil {
   228  		logger.Println(err)
   229  		return err
   230  	}
   231  	if err := inode.WriteMetadata(fullPathname); err != nil {
   232  		logger.Println(err)
   233  		return err
   234  	} else {
   235  		if inode.Size > 0 {
   236  			logger.Printf("Made inode: %s from: %x\n",
   237  				fullPathname, inode.Hash)
   238  		} else {
   239  			logger.Printf("Made empty inode: %s\n", fullPathname)
   240  		}
   241  	}
   242  	return nil
   243  }
   244  
   245  func makeSymlinkInode(fullPathname string,
   246  	inode *filesystem.SymlinkInode, logger log.Logger) error {
   247  	if err := inode.Write(fullPathname); err != nil {
   248  		logger.Println(err)
   249  		return err
   250  	}
   251  	logger.Printf("Made symlink inode: %s -> %s\n", fullPathname, inode.Symlink)
   252  	return nil
   253  }
   254  
   255  func makeSpecialInode(fullPathname string, inode *filesystem.SpecialInode,
   256  	logger log.Logger) error {
   257  	if err := inode.Write(fullPathname); err != nil {
   258  		logger.Println(err)
   259  		return err
   260  	}
   261  	logger.Printf("Made special inode: %s\n", fullPathname)
   262  	return nil
   263  }
   264  
   265  func (t *uType) makeHardlinks(hardlinksToMake []sub.Hardlink,
   266  	triggers *triggers.Triggers, takeAction bool) {
   267  	tmpName := filepath.Join(t.ObjectsDir, "temporaryHardlink")
   268  	for _, hardlink := range hardlinksToMake {
   269  		triggers.Match(hardlink.NewLink)
   270  		if takeAction {
   271  			targetPathname := filepath.Join(t.RootDirectoryName,
   272  				hardlink.Target)
   273  			linkPathname := filepath.Join(t.RootDirectoryName, hardlink.NewLink)
   274  			// A Link directly to linkPathname will fail if it exists, so do a
   275  			// Link+Rename using a temporary filename.
   276  			if err := fsutil.ForceLink(targetPathname, tmpName); err != nil {
   277  				t.lastError = err
   278  				t.Logger.Println(err)
   279  				continue
   280  			}
   281  			if err := fsutil.ForceRename(tmpName, linkPathname); err != nil {
   282  				t.Logger.Println(err)
   283  				if err := fsutil.ForceRemove(tmpName); err != nil {
   284  					t.lastError = err
   285  					t.Logger.Println(err)
   286  				}
   287  			} else {
   288  				t.Logger.Printf("Linked: %s => %s\n",
   289  					linkPathname, targetPathname)
   290  			}
   291  		}
   292  	}
   293  }
   294  
   295  func (t *uType) doDeletes(pathsToDelete []string, triggers *triggers.Triggers,
   296  	takeAction bool) {
   297  	for _, pathname := range pathsToDelete {
   298  		triggers.Match(pathname)
   299  		if takeAction {
   300  			fullPathname := filepath.Join(t.RootDirectoryName, pathname)
   301  			if err := fsutil.ForceRemoveAll(fullPathname); err != nil {
   302  				t.lastError = err
   303  				t.Logger.Println(err)
   304  			} else {
   305  				t.Logger.Printf("Deleted: %s\n", fullPathname)
   306  			}
   307  		}
   308  	}
   309  }
   310  
   311  func (t *uType) makeDirectories(directoriesToMake []sub.Inode,
   312  	triggers *triggers.Triggers, takeAction bool) {
   313  	for _, newdir := range directoriesToMake {
   314  		if t.skipPath(newdir.Name) {
   315  			continue
   316  		}
   317  		triggers.Match(newdir.Name)
   318  		if takeAction {
   319  			fullPathname := filepath.Join(t.RootDirectoryName, newdir.Name)
   320  			inode, ok := newdir.GenericInode.(*filesystem.DirectoryInode)
   321  			if !ok {
   322  				t.Logger.Println("%s is not a directory!\n", newdir.Name)
   323  				continue
   324  			}
   325  			if err := inode.Write(fullPathname); err != nil {
   326  				t.lastError = err
   327  				t.Logger.Println(err)
   328  			} else {
   329  				t.Logger.Printf("Made directory: %s (mode=%s)\n",
   330  					fullPathname, inode.Mode)
   331  			}
   332  		}
   333  	}
   334  }
   335  
   336  func (t *uType) changeInodes(inodesToChange []sub.Inode,
   337  	triggers *triggers.Triggers, takeAction bool) {
   338  	for _, inode := range inodesToChange {
   339  		fullPathname := filepath.Join(t.RootDirectoryName, inode.Name)
   340  		if checkNonMtimeChange(fullPathname, inode.GenericInode) {
   341  			triggers.Match(inode.Name)
   342  		}
   343  		if takeAction {
   344  			if err := filesystem.ForceWriteMetadata(inode,
   345  				fullPathname); err != nil {
   346  				t.lastError = err
   347  				t.Logger.Println(err)
   348  				continue
   349  			}
   350  			t.Logger.Printf("Changed inode: %s\n", fullPathname)
   351  		}
   352  	}
   353  }
   354  
   355  func checkNonMtimeChange(filename string, inode filesystem.GenericInode) bool {
   356  	switch inode := inode.(type) {
   357  	case *filesystem.RegularInode:
   358  		var stat wsyscall.Stat_t
   359  		if err := wsyscall.Lstat(filename, &stat); err != nil {
   360  			return true
   361  		}
   362  		if stat.Mode&syscall.S_IFMT == syscall.S_IFREG {
   363  			oldInode := scanner.MakeRegularInode(&stat)
   364  			oldInode.Hash = inode.Hash
   365  			oldInode.MtimeNanoSeconds = inode.MtimeNanoSeconds
   366  			oldInode.MtimeSeconds = inode.MtimeSeconds
   367  			if *oldInode == *inode {
   368  				return false
   369  			}
   370  		}
   371  	case *filesystem.SpecialInode:
   372  		var stat wsyscall.Stat_t
   373  		if err := wsyscall.Lstat(filename, &stat); err != nil {
   374  			return true
   375  		}
   376  		if stat.Mode&syscall.S_IFMT == syscall.S_IFBLK ||
   377  			stat.Mode&syscall.S_IFMT == syscall.S_IFCHR {
   378  			oldInode := scanner.MakeSpecialInode(&stat)
   379  			oldInode.MtimeNanoSeconds = inode.MtimeNanoSeconds
   380  			oldInode.MtimeSeconds = inode.MtimeSeconds
   381  			if *oldInode == *inode {
   382  				return false
   383  			}
   384  		}
   385  	}
   386  	return true
   387  }
   388  
   389  func (t *uType) skipPath(pathname string) bool {
   390  	if t.SkipFilter.Match(pathname) {
   391  		return true
   392  	}
   393  	if pathname == "/.subd" {
   394  		return true
   395  	}
   396  	if strings.HasPrefix(pathname, "/.subd/") {
   397  		return true
   398  	}
   399  	return false
   400  }
   401  
   402  func (t *uType) writePatchedImageName(imageName string) error {
   403  	pathname := filepath.Join(t.RootDirectoryName,
   404  		constants.PatchedImageNameFile)
   405  	if imageName == "" {
   406  		if err := os.Remove(pathname); err != nil {
   407  			if os.IsNotExist(err) {
   408  				return nil
   409  			}
   410  			return err
   411  		}
   412  	}
   413  	if err := os.MkdirAll(filepath.Dir(pathname), fsutil.DirPerms); err != nil {
   414  		return err
   415  	}
   416  	buffer := &bytes.Buffer{}
   417  	fmt.Fprintln(buffer, imageName)
   418  	return fsutil.CopyToFile(pathname, fsutil.PublicFilePerms, buffer, 0)
   419  }