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