github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/client/allocdir/alloc_dir.go (about)

     1  package allocdir
     2  
     3  import (
     4  	"archive/tar"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"sync"
    12  	"time"
    13  
    14  	"net/http"
    15  	"strings"
    16  
    17  	hclog "github.com/hashicorp/go-hclog"
    18  	multierror "github.com/hashicorp/go-multierror"
    19  	cstructs "github.com/hashicorp/nomad/client/structs"
    20  	"github.com/hashicorp/nomad/nomad/structs"
    21  	"github.com/hpcloud/tail/watch"
    22  	tomb "gopkg.in/tomb.v1"
    23  )
    24  
    25  const (
    26  	// idUnsupported is what the uid/gid will be set to on platforms (eg
    27  	// Windows) that don't support integer ownership identifiers.
    28  	idUnsupported = -1
    29  )
    30  
    31  var (
    32  	// SnapshotErrorTime is the sentinel time that will be used on the
    33  	// error file written by Snapshot when it encounters as error.
    34  	SnapshotErrorTime = time.Date(2000, 0, 0, 0, 0, 0, 0, time.UTC)
    35  
    36  	// The name of the directory that is shared across tasks in a task group.
    37  	SharedAllocName = "alloc"
    38  
    39  	// Name of the directory where logs of Tasks are written
    40  	LogDirName = "logs"
    41  
    42  	// SharedDataDir is one of the shared allocation directories. It is
    43  	// included in snapshots.
    44  	SharedDataDir = "data"
    45  
    46  	// TmpDirName is the name of the temporary directory in each alloc and
    47  	// task.
    48  	TmpDirName = "tmp"
    49  
    50  	// The set of directories that exist inside each shared alloc directory.
    51  	SharedAllocDirs = []string{LogDirName, TmpDirName, SharedDataDir}
    52  
    53  	// The name of the directory that exists inside each task directory
    54  	// regardless of driver.
    55  	TaskLocal = "local"
    56  
    57  	// TaskSecrets is the name of the secret directory inside each task
    58  	// directory
    59  	TaskSecrets = "secrets"
    60  
    61  	// TaskDirs is the set of directories created in each tasks directory.
    62  	TaskDirs = map[string]os.FileMode{TmpDirName: os.ModeSticky | 0777}
    63  
    64  	// AllocGRPCSocket is the path relative to the task dir root for the
    65  	// unix socket connected to Consul's gRPC endpoint.
    66  	AllocGRPCSocket = filepath.Join(SharedAllocName, TmpDirName, "consul_grpc.sock")
    67  )
    68  
    69  // AllocDir allows creating, destroying, and accessing an allocation's
    70  // directory. All methods are safe for concurrent use.
    71  type AllocDir struct {
    72  	// AllocDir is the directory used for storing any state
    73  	// of this allocation. It will be purged on alloc destroy.
    74  	AllocDir string
    75  
    76  	// The shared directory is available to all tasks within the same task
    77  	// group.
    78  	SharedDir string
    79  
    80  	// TaskDirs is a mapping of task names to their non-shared directory.
    81  	TaskDirs map[string]*TaskDir
    82  
    83  	// built is true if Build has successfully run
    84  	built bool
    85  
    86  	mu sync.RWMutex
    87  
    88  	logger hclog.Logger
    89  }
    90  
    91  // AllocDirFS exposes file operations on the alloc dir
    92  type AllocDirFS interface {
    93  	List(path string) ([]*cstructs.AllocFileInfo, error)
    94  	Stat(path string) (*cstructs.AllocFileInfo, error)
    95  	ReadAt(path string, offset int64) (io.ReadCloser, error)
    96  	Snapshot(w io.Writer) error
    97  	BlockUntilExists(ctx context.Context, path string) (chan error, error)
    98  	ChangeEvents(ctx context.Context, path string, curOffset int64) (*watch.FileChanges, error)
    99  }
   100  
   101  // NewAllocDir initializes the AllocDir struct with allocDir as base path for
   102  // the allocation directory.
   103  func NewAllocDir(logger hclog.Logger, allocDir string) *AllocDir {
   104  	logger = logger.Named("alloc_dir")
   105  	return &AllocDir{
   106  		AllocDir:  allocDir,
   107  		SharedDir: filepath.Join(allocDir, SharedAllocName),
   108  		TaskDirs:  make(map[string]*TaskDir),
   109  		logger:    logger,
   110  	}
   111  }
   112  
   113  // Copy an AllocDir and all of its TaskDirs. Returns nil if AllocDir is
   114  // nil.
   115  func (d *AllocDir) Copy() *AllocDir {
   116  	d.mu.RLock()
   117  	defer d.mu.RUnlock()
   118  
   119  	if d == nil {
   120  		return nil
   121  	}
   122  	dcopy := &AllocDir{
   123  		AllocDir:  d.AllocDir,
   124  		SharedDir: d.SharedDir,
   125  		TaskDirs:  make(map[string]*TaskDir, len(d.TaskDirs)),
   126  		logger:    d.logger,
   127  	}
   128  	for k, v := range d.TaskDirs {
   129  		dcopy.TaskDirs[k] = v.Copy()
   130  	}
   131  	return dcopy
   132  }
   133  
   134  // NewTaskDir creates a new TaskDir and adds it to the AllocDirs TaskDirs map.
   135  func (d *AllocDir) NewTaskDir(name string) *TaskDir {
   136  	d.mu.Lock()
   137  	defer d.mu.Unlock()
   138  
   139  	td := newTaskDir(d.logger, d.AllocDir, name)
   140  	d.TaskDirs[name] = td
   141  	return td
   142  }
   143  
   144  // Snapshot creates an archive of the files and directories in the data dir of
   145  // the allocation and the task local directories
   146  //
   147  // Since a valid tar may have been written even when an error occurs, a special
   148  // file "NOMAD-${ALLOC_ID}-ERROR.log" will be appended to the tar with the
   149  // error message as the contents.
   150  func (d *AllocDir) Snapshot(w io.Writer) error {
   151  	d.mu.RLock()
   152  	defer d.mu.RUnlock()
   153  
   154  	allocDataDir := filepath.Join(d.SharedDir, SharedDataDir)
   155  	rootPaths := []string{allocDataDir}
   156  	for _, taskdir := range d.TaskDirs {
   157  		rootPaths = append(rootPaths, taskdir.LocalDir)
   158  	}
   159  
   160  	tw := tar.NewWriter(w)
   161  	defer tw.Close()
   162  
   163  	walkFn := func(path string, fileInfo os.FileInfo, err error) error {
   164  		if err != nil {
   165  			return err
   166  		}
   167  
   168  		// Include the path of the file name relative to the alloc dir
   169  		// so that we can put the files in the right directories
   170  		relPath, err := filepath.Rel(d.AllocDir, path)
   171  		if err != nil {
   172  			return err
   173  		}
   174  		link := ""
   175  		if fileInfo.Mode()&os.ModeSymlink != 0 {
   176  			target, err := os.Readlink(path)
   177  			if err != nil {
   178  				return fmt.Errorf("error reading symlink: %v", err)
   179  			}
   180  			link = target
   181  		}
   182  		hdr, err := tar.FileInfoHeader(fileInfo, link)
   183  		if err != nil {
   184  			return fmt.Errorf("error creating file header: %v", err)
   185  		}
   186  		hdr.Name = relPath
   187  		if err := tw.WriteHeader(hdr); err != nil {
   188  			return err
   189  		}
   190  
   191  		// If it's a directory or symlink we just write the header into the tar
   192  		if fileInfo.IsDir() || (fileInfo.Mode()&os.ModeSymlink != 0) {
   193  			return nil
   194  		}
   195  
   196  		// Write the file into the archive
   197  		file, err := os.Open(path)
   198  		if err != nil {
   199  			return err
   200  		}
   201  		defer file.Close()
   202  
   203  		if _, err := io.Copy(tw, file); err != nil {
   204  			return err
   205  		}
   206  		return nil
   207  	}
   208  
   209  	// Walk through all the top level directories and add the files and
   210  	// directories in the archive
   211  	for _, path := range rootPaths {
   212  		if err := filepath.Walk(path, walkFn); err != nil {
   213  			allocID := filepath.Base(d.AllocDir)
   214  			if writeErr := writeError(tw, allocID, err); writeErr != nil {
   215  				// This could be bad; other side won't know
   216  				// snapshotting failed. It could also just mean
   217  				// the snapshotting side closed the connect
   218  				// prematurely and won't try to use the tar
   219  				// anyway.
   220  				d.logger.Warn("snapshotting failed and unable to write error marker", "error", writeErr)
   221  			}
   222  			return fmt.Errorf("failed to snapshot %s: %v", path, err)
   223  		}
   224  	}
   225  
   226  	return nil
   227  }
   228  
   229  // Move other alloc directory's shared path and local dir to this alloc dir.
   230  func (d *AllocDir) Move(other *AllocDir, tasks []*structs.Task) error {
   231  	d.mu.RLock()
   232  	if !d.built {
   233  		// Enforce the invariant that Build is called before Move
   234  		d.mu.RUnlock()
   235  		return fmt.Errorf("unable to move to %q - alloc dir is not built", d.AllocDir)
   236  	}
   237  
   238  	// Moving is slow and only reads immutable fields, so unlock during heavy IO
   239  	d.mu.RUnlock()
   240  
   241  	// Move the data directory
   242  	otherDataDir := filepath.Join(other.SharedDir, SharedDataDir)
   243  	dataDir := filepath.Join(d.SharedDir, SharedDataDir)
   244  	if fileInfo, err := os.Stat(otherDataDir); fileInfo != nil && err == nil {
   245  		os.Remove(dataDir) // remove an empty data dir if it exists
   246  		if err := os.Rename(otherDataDir, dataDir); err != nil {
   247  			return fmt.Errorf("error moving data dir: %v", err)
   248  		}
   249  	}
   250  
   251  	// Move the task directories
   252  	for _, task := range tasks {
   253  		otherTaskDir := filepath.Join(other.AllocDir, task.Name)
   254  		otherTaskLocal := filepath.Join(otherTaskDir, TaskLocal)
   255  
   256  		fileInfo, err := os.Stat(otherTaskLocal)
   257  		if fileInfo != nil && err == nil {
   258  			// TaskDirs haven't been built yet, so create it
   259  			newTaskDir := filepath.Join(d.AllocDir, task.Name)
   260  			if err := os.MkdirAll(newTaskDir, 0777); err != nil {
   261  				return fmt.Errorf("error creating task %q dir: %v", task.Name, err)
   262  			}
   263  			localDir := filepath.Join(newTaskDir, TaskLocal)
   264  			os.Remove(localDir) // remove an empty local dir if it exists
   265  			if err := os.Rename(otherTaskLocal, localDir); err != nil {
   266  				return fmt.Errorf("error moving task %q local dir: %v", task.Name, err)
   267  			}
   268  		}
   269  	}
   270  
   271  	return nil
   272  }
   273  
   274  // Tears down previously build directory structure.
   275  func (d *AllocDir) Destroy() error {
   276  	// Unmount all mounted shared alloc dirs.
   277  	var mErr multierror.Error
   278  	if err := d.UnmountAll(); err != nil {
   279  		mErr.Errors = append(mErr.Errors, err)
   280  	}
   281  
   282  	if err := os.RemoveAll(d.AllocDir); err != nil {
   283  		mErr.Errors = append(mErr.Errors, fmt.Errorf("failed to remove alloc dir %q: %v", d.AllocDir, err))
   284  	}
   285  
   286  	// Unset built since the alloc dir has been destroyed.
   287  	d.mu.Lock()
   288  	d.built = false
   289  	d.mu.Unlock()
   290  	return mErr.ErrorOrNil()
   291  }
   292  
   293  // UnmountAll linked/mounted directories in task dirs.
   294  func (d *AllocDir) UnmountAll() error {
   295  	d.mu.RLock()
   296  	defer d.mu.RUnlock()
   297  
   298  	var mErr multierror.Error
   299  	for _, dir := range d.TaskDirs {
   300  		// Check if the directory has the shared alloc mounted.
   301  		if pathExists(dir.SharedTaskDir) {
   302  			if err := unlinkDir(dir.SharedTaskDir); err != nil {
   303  				mErr.Errors = append(mErr.Errors,
   304  					fmt.Errorf("failed to unmount shared alloc dir %q: %v", dir.SharedTaskDir, err))
   305  			} else if err := os.RemoveAll(dir.SharedTaskDir); err != nil {
   306  				mErr.Errors = append(mErr.Errors,
   307  					fmt.Errorf("failed to delete shared alloc dir %q: %v", dir.SharedTaskDir, err))
   308  			}
   309  		}
   310  
   311  		if pathExists(dir.SecretsDir) {
   312  			if err := removeSecretDir(dir.SecretsDir); err != nil {
   313  				mErr.Errors = append(mErr.Errors,
   314  					fmt.Errorf("failed to remove the secret dir %q: %v", dir.SecretsDir, err))
   315  			}
   316  		}
   317  
   318  		// Unmount dev/ and proc/ have been mounted.
   319  		if err := dir.unmountSpecialDirs(); err != nil {
   320  			mErr.Errors = append(mErr.Errors, err)
   321  		}
   322  	}
   323  
   324  	return mErr.ErrorOrNil()
   325  }
   326  
   327  // Build the directory tree for an allocation.
   328  func (d *AllocDir) Build() error {
   329  	// Make the alloc directory, owned by the nomad process.
   330  	if err := os.MkdirAll(d.AllocDir, 0755); err != nil {
   331  		return fmt.Errorf("Failed to make the alloc directory %v: %v", d.AllocDir, err)
   332  	}
   333  
   334  	// Make the shared directory and make it available to all user/groups.
   335  	if err := os.MkdirAll(d.SharedDir, 0777); err != nil {
   336  		return err
   337  	}
   338  
   339  	// Make the shared directory have non-root permissions.
   340  	if err := dropDirPermissions(d.SharedDir, os.ModePerm); err != nil {
   341  		return err
   342  	}
   343  
   344  	// Create shared subdirs
   345  	for _, dir := range SharedAllocDirs {
   346  		p := filepath.Join(d.SharedDir, dir)
   347  		if err := os.MkdirAll(p, 0777); err != nil {
   348  			return err
   349  		}
   350  		if err := dropDirPermissions(p, os.ModePerm); err != nil {
   351  			return err
   352  		}
   353  	}
   354  
   355  	// Mark as built
   356  	d.mu.Lock()
   357  	d.built = true
   358  	d.mu.Unlock()
   359  	return nil
   360  }
   361  
   362  // List returns the list of files at a path relative to the alloc dir
   363  func (d *AllocDir) List(path string) ([]*cstructs.AllocFileInfo, error) {
   364  	if escapes, err := structs.PathEscapesAllocDir("", path); err != nil {
   365  		return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err)
   366  	} else if escapes {
   367  		return nil, fmt.Errorf("Path escapes the alloc directory")
   368  	}
   369  
   370  	p := filepath.Join(d.AllocDir, path)
   371  	finfos, err := ioutil.ReadDir(p)
   372  	if err != nil {
   373  		return []*cstructs.AllocFileInfo{}, err
   374  	}
   375  	files := make([]*cstructs.AllocFileInfo, len(finfos))
   376  	for idx, info := range finfos {
   377  		files[idx] = &cstructs.AllocFileInfo{
   378  			Name:     info.Name(),
   379  			IsDir:    info.IsDir(),
   380  			Size:     info.Size(),
   381  			FileMode: info.Mode().String(),
   382  			ModTime:  info.ModTime(),
   383  		}
   384  	}
   385  	return files, err
   386  }
   387  
   388  // Stat returns information about the file at a path relative to the alloc dir
   389  func (d *AllocDir) Stat(path string) (*cstructs.AllocFileInfo, error) {
   390  	if escapes, err := structs.PathEscapesAllocDir("", path); err != nil {
   391  		return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err)
   392  	} else if escapes {
   393  		return nil, fmt.Errorf("Path escapes the alloc directory")
   394  	}
   395  
   396  	p := filepath.Join(d.AllocDir, path)
   397  	info, err := os.Stat(p)
   398  	if err != nil {
   399  		return nil, err
   400  	}
   401  
   402  	contentType := detectContentType(info, p)
   403  
   404  	return &cstructs.AllocFileInfo{
   405  		Size:        info.Size(),
   406  		Name:        info.Name(),
   407  		IsDir:       info.IsDir(),
   408  		FileMode:    info.Mode().String(),
   409  		ModTime:     info.ModTime(),
   410  		ContentType: contentType,
   411  	}, nil
   412  }
   413  
   414  // detectContentType tries to infer the file type by reading the first
   415  // 512 bytes of the file. Json file extensions are special cased.
   416  func detectContentType(fileInfo os.FileInfo, path string) string {
   417  	contentType := "application/octet-stream"
   418  	if !fileInfo.IsDir() {
   419  		f, err := os.Open(path)
   420  		// Best effort content type detection
   421  		// We ignore errors because this is optional information
   422  		if err == nil {
   423  			fileBytes := make([]byte, 512)
   424  			_, err := f.Read(fileBytes)
   425  			if err == nil {
   426  				contentType = http.DetectContentType(fileBytes)
   427  			}
   428  		}
   429  	}
   430  	// Special case json files
   431  	if strings.HasSuffix(path, ".json") {
   432  		contentType = "application/json"
   433  	}
   434  	return contentType
   435  }
   436  
   437  // ReadAt returns a reader for a file at the path relative to the alloc dir
   438  func (d *AllocDir) ReadAt(path string, offset int64) (io.ReadCloser, error) {
   439  	if escapes, err := structs.PathEscapesAllocDir("", path); err != nil {
   440  		return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err)
   441  	} else if escapes {
   442  		return nil, fmt.Errorf("Path escapes the alloc directory")
   443  	}
   444  
   445  	p := filepath.Join(d.AllocDir, path)
   446  
   447  	// Check if it is trying to read into a secret directory
   448  	d.mu.RLock()
   449  	for _, dir := range d.TaskDirs {
   450  		if filepath.HasPrefix(p, dir.SecretsDir) {
   451  			d.mu.RUnlock()
   452  			return nil, fmt.Errorf("Reading secret file prohibited: %s", path)
   453  		}
   454  	}
   455  	d.mu.RUnlock()
   456  
   457  	f, err := os.Open(p)
   458  	if err != nil {
   459  		return nil, err
   460  	}
   461  	if _, err := f.Seek(offset, 0); err != nil {
   462  		return nil, fmt.Errorf("can't seek to offset %q: %v", offset, err)
   463  	}
   464  	return f, nil
   465  }
   466  
   467  // BlockUntilExists blocks until the passed file relative the allocation
   468  // directory exists. The block can be cancelled with the passed context.
   469  func (d *AllocDir) BlockUntilExists(ctx context.Context, path string) (chan error, error) {
   470  	if escapes, err := structs.PathEscapesAllocDir("", path); err != nil {
   471  		return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err)
   472  	} else if escapes {
   473  		return nil, fmt.Errorf("Path escapes the alloc directory")
   474  	}
   475  
   476  	// Get the path relative to the alloc directory
   477  	p := filepath.Join(d.AllocDir, path)
   478  	watcher := getFileWatcher(p)
   479  	returnCh := make(chan error, 1)
   480  	t := &tomb.Tomb{}
   481  	go func() {
   482  		<-ctx.Done()
   483  		t.Kill(nil)
   484  	}()
   485  	go func() {
   486  		returnCh <- watcher.BlockUntilExists(t)
   487  		close(returnCh)
   488  	}()
   489  	return returnCh, nil
   490  }
   491  
   492  // ChangeEvents watches for changes to the passed path relative to the
   493  // allocation directory. The offset should be the last read offset. The context is
   494  // used to clean up the watch.
   495  func (d *AllocDir) ChangeEvents(ctx context.Context, path string, curOffset int64) (*watch.FileChanges, error) {
   496  	if escapes, err := structs.PathEscapesAllocDir("", path); err != nil {
   497  		return nil, fmt.Errorf("Failed to check if path escapes alloc directory: %v", err)
   498  	} else if escapes {
   499  		return nil, fmt.Errorf("Path escapes the alloc directory")
   500  	}
   501  
   502  	t := &tomb.Tomb{}
   503  	go func() {
   504  		<-ctx.Done()
   505  		t.Kill(nil)
   506  	}()
   507  
   508  	// Get the path relative to the alloc directory
   509  	p := filepath.Join(d.AllocDir, path)
   510  	watcher := getFileWatcher(p)
   511  	return watcher.ChangeEvents(t, curOffset)
   512  }
   513  
   514  // getFileWatcher returns a FileWatcher for the given path.
   515  func getFileWatcher(path string) watch.FileWatcher {
   516  	return watch.NewPollingFileWatcher(path)
   517  }
   518  
   519  // fileCopy from src to dst setting the permissions and owner (if uid & gid are
   520  // both greater than 0)
   521  func fileCopy(src, dst string, uid, gid int, perm os.FileMode) error {
   522  	// Do a simple copy.
   523  	srcFile, err := os.Open(src)
   524  	if err != nil {
   525  		return fmt.Errorf("Couldn't open src file %v: %v", src, err)
   526  	}
   527  	defer srcFile.Close()
   528  
   529  	dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, perm)
   530  	if err != nil {
   531  		return fmt.Errorf("Couldn't create destination file %v: %v", dst, err)
   532  	}
   533  	defer dstFile.Close()
   534  
   535  	if _, err := io.Copy(dstFile, srcFile); err != nil {
   536  		return fmt.Errorf("Couldn't copy %q to %q: %v", src, dst, err)
   537  	}
   538  
   539  	if uid != idUnsupported && gid != idUnsupported {
   540  		if err := dstFile.Chown(uid, gid); err != nil {
   541  			return fmt.Errorf("Couldn't copy %q to %q: %v", src, dst, err)
   542  		}
   543  	}
   544  
   545  	return nil
   546  }
   547  
   548  // pathExists is a helper function to check if the path exists.
   549  func pathExists(path string) bool {
   550  	if _, err := os.Stat(path); err != nil {
   551  		if os.IsNotExist(err) {
   552  			return false
   553  		}
   554  	}
   555  	return true
   556  }
   557  
   558  // pathEmpty returns true if a path exists, is listable, and is empty. If the
   559  // path does not exist or is not listable an error is returned.
   560  func pathEmpty(path string) (bool, error) {
   561  	f, err := os.Open(path)
   562  	if err != nil {
   563  		return false, err
   564  	}
   565  	defer f.Close()
   566  	entries, err := f.Readdir(1)
   567  	if err != nil && err != io.EOF {
   568  		return false, err
   569  	}
   570  	return len(entries) == 0, nil
   571  }
   572  
   573  // createDir creates a directory structure inside the basepath. This functions
   574  // preserves the permissions of each of the subdirectories in the relative path
   575  // by looking up the permissions in the host.
   576  func createDir(basePath, relPath string) error {
   577  	filePerms, err := splitPath(relPath)
   578  	if err != nil {
   579  		return err
   580  	}
   581  
   582  	// We are going backwards since we create the root of the directory first
   583  	// and then create the entire nested structure.
   584  	for i := len(filePerms) - 1; i >= 0; i-- {
   585  		fi := filePerms[i]
   586  		destDir := filepath.Join(basePath, fi.Name)
   587  		if err := os.MkdirAll(destDir, fi.Perm); err != nil {
   588  			return err
   589  		}
   590  
   591  		if fi.Uid != idUnsupported && fi.Gid != idUnsupported {
   592  			if err := os.Chown(destDir, fi.Uid, fi.Gid); err != nil {
   593  				return err
   594  			}
   595  		}
   596  	}
   597  	return nil
   598  }
   599  
   600  // fileInfo holds the path and the permissions of a file
   601  type fileInfo struct {
   602  	Name string
   603  	Perm os.FileMode
   604  
   605  	// Uid and Gid are unsupported on Windows
   606  	Uid int
   607  	Gid int
   608  }
   609  
   610  // splitPath stats each subdirectory of a path. The first element of the array
   611  // is the file passed to this function, and the last element is the root of the
   612  // path.
   613  func splitPath(path string) ([]fileInfo, error) {
   614  	var mode os.FileMode
   615  	fi, err := os.Stat(path)
   616  
   617  	// If the path is not present in the host then we respond with the most
   618  	// flexible permission.
   619  	uid, gid := idUnsupported, idUnsupported
   620  	if err != nil {
   621  		mode = os.ModePerm
   622  	} else {
   623  		uid, gid = getOwner(fi)
   624  		mode = fi.Mode()
   625  	}
   626  	var dirs []fileInfo
   627  	dirs = append(dirs, fileInfo{Name: path, Perm: mode, Uid: uid, Gid: gid})
   628  	currentDir := path
   629  	for {
   630  		dir := filepath.Dir(filepath.Clean(currentDir))
   631  		if dir == currentDir {
   632  			break
   633  		}
   634  
   635  		// We try to find the permission of the file in the host. If the path is not
   636  		// present in the host then we respond with the most flexible permission.
   637  		uid, gid := idUnsupported, idUnsupported
   638  		fi, err := os.Stat(dir)
   639  		if err != nil {
   640  			mode = os.ModePerm
   641  		} else {
   642  			uid, gid = getOwner(fi)
   643  			mode = fi.Mode()
   644  		}
   645  		dirs = append(dirs, fileInfo{Name: dir, Perm: mode, Uid: uid, Gid: gid})
   646  		currentDir = dir
   647  	}
   648  	return dirs, nil
   649  }
   650  
   651  // SnapshotErrorFilename returns the filename which will exist if there was an
   652  // error snapshotting a tar.
   653  func SnapshotErrorFilename(allocID string) string {
   654  	return fmt.Sprintf("NOMAD-%s-ERROR.log", allocID)
   655  }
   656  
   657  // writeError writes a special file to a tar archive with the error encountered
   658  // during snapshotting. See Snapshot().
   659  func writeError(tw *tar.Writer, allocID string, err error) error {
   660  	contents := []byte(fmt.Sprintf("Error snapshotting: %v", err))
   661  	hdr := tar.Header{
   662  		Name:       SnapshotErrorFilename(allocID),
   663  		Mode:       0666,
   664  		Size:       int64(len(contents)),
   665  		AccessTime: SnapshotErrorTime,
   666  		ChangeTime: SnapshotErrorTime,
   667  		ModTime:    SnapshotErrorTime,
   668  		Typeflag:   tar.TypeReg,
   669  	}
   670  
   671  	if err := tw.WriteHeader(&hdr); err != nil {
   672  		return err
   673  	}
   674  
   675  	_, err = tw.Write(contents)
   676  	return err
   677  }