Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions acquisition/acquisition.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
rt "github.com/botherder/go-savetime/runtime"
"github.com/google/uuid"
"github.com/mvt-project/androidqf/adb"
"github.com/mvt-project/androidqf/assets"
"github.com/mvt-project/androidqf/log"
"github.com/mvt-project/androidqf/utils"
)
Expand Down Expand Up @@ -175,9 +174,9 @@ func (a *Acquisition) Complete() {
a.Collector.Clean()
}

// Stop ADB server before trying to remove extracted assets
// Stop ADB server, then clean up any temp directory used for bundled assets.
adb.Client.KillServer()
assets.CleanAssets()
adb.Client.Cleanup()
}

func (a *Acquisition) GetSystemInformation() error {
Expand Down
15 changes: 13 additions & 2 deletions adb/adb.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package adb
import (
"errors"
"fmt"
"os"
"os/exec"
"strings"

Expand All @@ -16,8 +17,18 @@ import (
)

type ADB struct {
ExePath string
Serial string
ExePath string
Serial string
TmpAssetsDir string
}

// Cleanup removes the temporary directory used to store extracted adb assets,
// if one was created. It is a no-op when the system adb was used instead.
func (a *ADB) Cleanup() {
if a.TmpAssetsDir != "" {
os.RemoveAll(a.TmpAssetsDir)
a.TmpAssetsDir = ""
}
}

var Client *ADB
Expand Down
28 changes: 19 additions & 9 deletions adb/adb_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,35 @@
package adb

import (
"fmt"
"os"
"os/exec"
"path/filepath"

saveRuntime "github.com/botherder/go-savetime/runtime"
"github.com/mvt-project/androidqf/assets"
)

func (a *ADB) findExe() error {
err := assets.DeployAssets()
// Prefer a system-installed adb (covers distro packages where adb is on PATH).
if path, err := exec.LookPath("adb"); err == nil {
a.ExePath = path
return nil
}

// Fall back to the bundled binary. Extract it into a temp directory so we
// never try to write next to the executable (which may be /usr/bin or
// another read-only system path).
tmpDir, err := os.MkdirTemp("", "androidqf-adb-*")
if err != nil {
return err
return fmt.Errorf("failed to create temp dir for adb: %v", err)
}

adbPath, err := exec.LookPath("adb")
if err == nil {
a.ExePath = adbPath
return nil
} else {
a.ExePath = filepath.Join(saveRuntime.GetExecutableDirectory(), "adb")
if err := assets.DeployAssetsToDir(tmpDir); err != nil {
os.RemoveAll(tmpDir)
return fmt.Errorf("failed to deploy bundled adb: %v", err)
}

a.ExePath = filepath.Join(tmpDir, "adb")
a.TmpAssetsDir = tmpDir
return nil
}
27 changes: 19 additions & 8 deletions adb/adb_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,35 @@
package adb

import (
"fmt"
"os"
"os/exec"
"path/filepath"

saveRuntime "github.com/botherder/go-savetime/runtime"
"github.com/mvt-project/androidqf/assets"
)

func (a *ADB) findExe() error {
err := assets.DeployAssets()
// Prefer a system-installed adb (covers distro packages where adb is on PATH).
if path, err := exec.LookPath("adb"); err == nil {
a.ExePath = path
return nil
}

// Fall back to the bundled binary. Extract it into a temp directory so we
// never try to write next to the executable (which may be /usr/bin or
// another read-only system path).
tmpDir, err := os.MkdirTemp("", "androidqf-adb-*")
if err != nil {
return err
return fmt.Errorf("failed to create temp dir for adb: %v", err)
}

adbPath, err := exec.LookPath("adb")
if err == nil {
a.ExePath = adbPath
} else {
a.ExePath = filepath.Join(saveRuntime.GetExecutableDirectory(), "adb")
if err := assets.DeployAssetsToDir(tmpDir); err != nil {
os.RemoveAll(tmpDir)
return fmt.Errorf("failed to deploy bundled adb: %v", err)
}

a.ExePath = filepath.Join(tmpDir, "adb")
a.TmpAssetsDir = tmpDir
return nil
}
45 changes: 26 additions & 19 deletions adb/adb_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package adb

import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
Expand All @@ -16,28 +17,34 @@ import (
)

func (a *ADB) findExe() error {
// TODO: only deploy assets when needed
err := assets.DeployAssets()
// Prefer a system-installed adb (covers distro packages where adb is on PATH).
if path, err := exec.LookPath("adb.exe"); err == nil {
a.ExePath = path
return nil
}

// Fall back to the bundled binary. Extract it (and the required DLLs) into
// a temp directory so we never try to write next to the executable (which
// may be a read-only system path).
tmpDir, err := os.MkdirTemp("", "androidqf-adb-*")
if err != nil {
return err
return fmt.Errorf("failed to create temp dir for adb: %v", err)
}

adbPath, err := exec.LookPath("adb.exe")
if err == nil {
a.ExePath = adbPath
} else {
// Get path of the current directory
ex, err := os.Executable()
if err != nil {
return err
}
// Need full path to bypass go 1.19 restrictions about local path
a.ExePath = filepath.Join(filepath.Dir(ex), "adb.exe")
_, err = os.Stat(a.ExePath)
if err != nil {
log.Debugf("ADB doesn't exist at %s", a.ExePath)
return errors.New("Impossible to find ADB")
}
if err := assets.DeployAssetsToDir(tmpDir); err != nil {
os.RemoveAll(tmpDir)
return fmt.Errorf("failed to deploy bundled adb: %v", err)
}

// Need full path to bypass Go 1.19+ restrictions about relative executable paths.
exePath := filepath.Join(tmpDir, "adb.exe")
if _, err := os.Stat(exePath); err != nil {
os.RemoveAll(tmpDir)
log.Debugf("ADB doesn't exist at %s", exePath)
return errors.New("impossible to find ADB")
}

a.ExePath = exePath
a.TmpAssetsDir = tmpDir
return nil
}
30 changes: 24 additions & 6 deletions adb/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"strings"

"github.com/mvt-project/androidqf/log"

"github.com/mvt-project/androidqf/assets"
)

Expand Down Expand Up @@ -112,13 +111,32 @@ func (c *Collector) Install() error {
}

log.Debugf("Deploying collector binary '%s' for architecture '%s'.", collectorName, c.Architecture)
collectorBinary, err := assets.Collector.ReadFile(collectorName)
if err != nil {
// Somehow the file doesn't exist
return errors.New("couldn't find the collector binary")

// If the caller has pointed us at a directory of pre-built collector
// binaries (e.g. a distro package placing them under
// /usr/lib/androidqf/android-collector/), use those in preference to the
// embedded assets. This lets packagers ship the collectors separately
// without patching the source, while portable-binary users get the
// embedded fallback automatically.
var collectorBinary []byte
if collectorDir := os.Getenv("ANDROIDQF_COLLECTOR_DIR"); collectorDir != "" {
data, readErr := os.ReadFile(filepath.Join(collectorDir, collectorName))
if readErr == nil {
collectorBinary = data
log.Debugf("Using collector from ANDROIDQF_COLLECTOR_DIR: %s", collectorDir)
} else {
log.Debugf("ANDROIDQF_COLLECTOR_DIR set but could not read collector: %v — falling back to embedded", readErr)
}
}
if len(collectorBinary) == 0 {
var err error
collectorBinary, err = assets.Collector.ReadFile(collectorName)
if err != nil {
return errors.New("couldn't find the collector binary")
}
}

collectorTemp, _ := os.CreateTemp("", "collector_")
collectorTemp, err := os.CreateTemp("", "collector_")
if err != nil {
return err
}
Expand Down
50 changes: 14 additions & 36 deletions assets/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
"errors"
"os"
"path/filepath"

saveRuntime "github.com/botherder/go-savetime/runtime"
)

//go:embed collector_*
Expand All @@ -22,55 +20,35 @@ type Asset struct {
Data []byte
}

// DeployAssets is used to retrieve the embedded adb binaries and store them.
func DeployAssets() error {
cwd := saveRuntime.GetExecutableDirectory()

// DeployAssetsToDir extracts the embedded adb binaries into the given directory.
// If a file already exists there it is silently skipped, so calling this
// function more than once (or concurrently) is safe.
func DeployAssetsToDir(dir string) error {
for _, asset := range getAssets() {
assetPath := filepath.Join(cwd, asset.Name)
assetPath := filepath.Join(dir, asset.Name)

// If the file already exists, skip it. This avoids failing when adb
// is already deployed or in use by another process.
// Already present – skip without error.
if _, err := os.Stat(assetPath); err == nil {
continue
} else if !os.IsNotExist(err) {
// Can't determine file existence (e.g., permission error); skip deploying this asset.
// Permission or other stat errorskip this asset rather than abort.
continue
}

// Try to create the asset file. If creation fails (for example because
// the file was created between the Stat and OpenFile calls, or because
// the file is locked by another process), skip the asset instead of failing.
assetFile, err := os.OpenFile(assetPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o755)
// O_EXCL ensures we don't clobber a file created between Stat and here.
f, err := os.OpenFile(assetPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o755)
if err != nil {
// If the file exists now, just continue; otherwise skip this asset.
if errors.Is(err, os.ErrExist) {
continue
}
// Could be locked or another transient error — do not fail the whole deployment.
// Transient error (e.g. locked) – skip rather than abort.
continue
}

// Write and close immediately (avoid defer in a loop).
_, err = assetFile.Write(asset.Data)
assetFile.Close()
if err != nil {
return err
}
}

return nil
}

// Remove assets from the local disk
func CleanAssets() error {
cwd := saveRuntime.GetExecutableDirectory()

for _, asset := range getAssets() {
assetPath := filepath.Join(cwd, asset.Name)
err := os.Remove(assetPath)
if err != nil {
return err
_, writeErr := f.Write(asset.Data)
f.Close()
if writeErr != nil {
return writeErr
}
}

Expand Down
Loading