func untar()

in internal/pkg/agent/application/upgrade/step_unpack.go [317:462]


func untar(log *logger.Logger, archivePath, dataDir string, flavor string) (UnpackResult, error) {
	var versionedHome string
	var rootDir string
	var hash string

	// Look up manifest in the archive and prepare path mappings, if any
	pm := pathMapper{}

	metadata, err := getPackageMetadataFromTar(archivePath)
	if err != nil {
		return UnpackResult{}, fmt.Errorf("retrieving package metadata from %q: %w", archivePath, err)
	}

	hash = metadata.hash[:hashLen]
	var registry map[string][]string

	if metadata.manifest != nil {
		// set the path mappings
		pm.mappings = metadata.manifest.Package.PathMappings
		versionedHome = filepath.FromSlash(pm.Map(metadata.manifest.Package.VersionedHome))
		registry = metadata.manifest.Package.Flavors
	} else {
		// set default value of versioned home if it wasn't set by reading the manifest
		versionedHome = createVersionedHomeFromHash(metadata.hash)
	}

	skipFn, err := skipFnFromTar(log, archivePath, flavor, registry)
	if err != nil {
		return UnpackResult{}, err
	}

	r, err := os.Open(archivePath)
	if err != nil {
		return UnpackResult{}, errors.New(fmt.Sprintf("artifact for 'elastic-agent' could not be found at '%s'", archivePath), errors.TypeFilesystem, errors.M(errors.MetaKeyPath, archivePath))
	}
	defer r.Close()

	zr, err := gzip.NewReader(r)
	if err != nil {
		return UnpackResult{}, errors.New("requires gzip-compressed body", err, errors.TypeFilesystem)
	}

	tr := tar.NewReader(zr)

	fileNamePrefix := getFileNamePrefix(archivePath)

	// go through all the content of a tar archive
	// if elastic-agent.active.commit file is found, get commit of the version unpacked
	// otherwise copy everything inside data directory (everything related to new version),
	// pieces outside of data we already have and should not be overwritten as they are usually configs
	for {
		f, err := tr.Next()
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			return UnpackResult{}, err
		}

		if !validFileName(f.Name) {
			return UnpackResult{}, errors.New("tar contained invalid filename: %q", f.Name, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, f.Name))
		}

		fileName := strings.TrimPrefix(f.Name, fileNamePrefix)

		if fileName == agentCommitFile {
			// we already loaded the hash, skip this one
			continue
		}

		// map the filename
		fileName = pm.Map(fileName)

		// we should check that the path is a local one but since we discard anything that is not under "data/" we can
		// skip the additional check

		// skip everything outside data/
		if !strings.HasPrefix(fileName, "data/") {
			continue
		}

		if skipFn(fileName) {
			continue
		}

		rel := filepath.FromSlash(strings.TrimPrefix(fileName, "data/"))
		abs := filepath.Join(dataDir, rel)

		// find the root dir
		if currentDir := filepath.Dir(abs); rootDir == "" || len(filepath.Dir(rootDir)) > len(currentDir) {
			rootDir = currentDir
		}

		fi := f.FileInfo()
		mode := fi.Mode()
		switch {
		case mode.IsRegular():
			log.Debugw("Unpacking file", "archive", "tar", "file.path", abs)
			// create non-existing containing folders with 0750 permissions right now, we'll fix the permission of each
			// directory as we come across them while processing the other package entries
			if err = os.MkdirAll(filepath.Dir(abs), 0750); err != nil {
				return UnpackResult{}, errors.New(err, "TarInstaller: creating directory for file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs))
			}

			// remove any world permissions from the file
			wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm()&0770)
			if err != nil {
				return UnpackResult{}, errors.New(err, "TarInstaller: creating file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs))
			}

			//nolint:gosec // legacy
			_, err = io.Copy(wf, tr)
			if closeErr := wf.Close(); closeErr != nil && err == nil {
				err = closeErr
			}
			if err != nil {
				return UnpackResult{}, fmt.Errorf("TarInstaller: error writing to %s: %w", abs, err)
			}
		case mode.IsDir():
			log.Debugw("Unpacking directory", "archive", "tar", "file.path", abs)
			// check if the directory already exists
			_, err = os.Stat(abs)
			if errors.Is(err, fs.ErrNotExist) {
				// the directory does not exist, create it and any non-existing parent directory with the same permissions
				if err := os.MkdirAll(abs, mode.Perm()&0770); err != nil {
					return UnpackResult{}, errors.New(err, "TarInstaller: creating directory for file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs))
				}
			} else if err != nil {
				return UnpackResult{}, errors.New(err, "TarInstaller: stat() directory for file "+abs, errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs))
			} else {
				// directory already exists, set the appropriate permissions
				err = os.Chmod(abs, mode.Perm()&0770)
				if err != nil {
					return UnpackResult{}, errors.New(err, fmt.Sprintf("TarInstaller: setting permissions %O for directory %q", mode.Perm()&0770, abs), errors.TypeFilesystem, errors.M(errors.MetaKeyPath, abs))
				}
			}
		default:
			return UnpackResult{}, errors.New(fmt.Sprintf("tar file entry %s contained unsupported file type %v", fileName, mode), errors.TypeFilesystem, errors.M(errors.MetaKeyPath, fileName))
		}
	}

	return UnpackResult{
		Hash:          hash,
		VersionedHome: versionedHome,
	}, nil
}