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