func (g *guestTools) doListFolder(ID, path string) {
	g.log.Info("Listing path: ", path)

	files := []string{}
	err := filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
		// Stop iteration and send an error to metaservice, if there is an error
		// with the path we were asked to iterate over.
		if p == path && err != nil {
			return err
		}

		// We ignore errors, directories and anything that isn't plain files
		if info != nil && err == nil && ioext.IsPlainFileInfo(info) {
			files = append(files, p)
		}
		return nil // Ignore other errors
	})
	notFound := err != nil

	// Create reply request... We use got here, this means that we get retries...
	// There is no harm in retries, server will just ignore them.
	req := g.got.Post(g.url("engine/v1/reply?id="+ID), nil)
	err = req.JSON(metaservice.Files{
		Files:    files,
		NotFound: notFound,
	})
	if err != nil {
		g.log.Panic("Failed to serialize JSON payload, error: ", err)
	}

	// Send the reply
	res, err := req.Send()
	if err != nil {
		g.log.Error("Reply with list-folder for path: ", path, " failed error: ", err)
		return
	}
	if res.StatusCode != http.StatusOK {
		g.log.Error("Reply with list-folder for path: ", path, " got status: ", res.StatusCode)
	}
}
func (r *resultSet) ExtractFile(path string) (ioext.ReadSeekCloser, error) {
	// Evaluate symlinks
	p, err := filepath.EvalSymlinks(filepath.Join(r.homeFolder.Path(), path))
	if err != nil {
		if _, ok := err.(*os.PathError); ok {
			return nil, engines.ErrResourceNotFound
		}
		return nil, engines.NewMalformedPayloadError(
			"Unable to evaluate path: ", path,
		)
	}

	// Cleanup the path
	p = filepath.Clean(p)

	// Check that p is inside homeFolder
	if !strings.HasPrefix(p, r.homeFolder.Path()+string(filepath.Separator)) {
		return nil, engines.ErrResourceNotFound
	}

	// Stat the file to make sure it's a file
	info, err := os.Lstat(p)
	if err != nil {
		return nil, engines.ErrResourceNotFound
	}
	// Don't allow anything that isn't a plain file
	if !ioext.IsPlainFileInfo(info) {
		return nil, engines.ErrResourceNotFound
	}

	// Open file
	f, err := os.Open(p)
	if err != nil {
		return nil, engines.ErrResourceNotFound
	}

	return f, nil
}
func (r *resultSet) ExtractFolder(path string, handler engines.FileHandler) error {
	// Evaluate symlinks
	p, err := filepath.EvalSymlinks(filepath.Join(r.homeFolder.Path(), path))
	if err != nil {
		if _, ok := err.(*os.PathError); ok {
			return engines.ErrResourceNotFound
		}
		return engines.NewMalformedPayloadError(
			"Unable to evaluate path: ", path,
		)
	}

	// Cleanup the path
	p = filepath.Clean(p)

	// Check that p is inside homeFolder
	if !strings.HasPrefix(p, r.homeFolder.Path()+string(filepath.Separator)) {
		return engines.ErrResourceNotFound
	}

	first := true
	return filepath.Walk(p, func(abspath string, info os.FileInfo, err error) error {
		// If there is a path error, on the first call then the folder is missing
		if _, ok := err.(*os.PathError); ok && first {
			return engines.ErrResourceNotFound
		}
		first = false

		// Ignore folder we can't walk (probably a permission issues)
		if err != nil {
			return nil
		}

		// Skip anything that isn't a plain file
		if !ioext.IsPlainFileInfo(info) {
			return nil
		}

		// If we can't construct relative file path this internal error, we'll skip
		relpath, err := filepath.Rel(p, abspath)
		if err != nil {
			// TODO: Send error to sentry
			r.log.Errorf(
				"ExtractFolder from %s, filepath.Rel('%s', '%s') returns error: %s",
				path, p, abspath, err,
			)
			return nil
		}

		f, err := os.Open(abspath)
		if err != nil {
			// file must have been deleted as we tried to open it
			// that makes no sense, but who knows...
			return nil
		}

		// If handler returns an error we return ErrHandlerInterrupt
		if handler(filepath.ToSlash(relpath), f) != nil {
			return engines.ErrHandlerInterrupt
		}
		return nil
	})
}