Skip to content

Commit

Permalink
feat: [CrashReportMover] support to export crash report
Browse files Browse the repository at this point in the history
* [AFC] add func `WriteFile`
* [AFC] should close `AfcFile`
  • Loading branch information
electricbubble committed Apr 26, 2021
1 parent d73a058 commit f7e28cd
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 16 deletions.
18 changes: 18 additions & 0 deletions afc.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ func (c *afc) Open(filename string, mode AfcFileMode) (file *AfcFile, err error)
if respMsg, err = c.client.Receive(); err != nil {
return nil, fmt.Errorf("afc receive 'Open': %w", err)
}
if err = respMsg.Err(); err != nil {
return nil, fmt.Errorf("afc 'Open': %w", err)
}

if respMsg.Operation != libimobiledevice.AfcOperationFileOpenResult {
return nil, fmt.Errorf("afc operation mistake 'Open': '%d'", respMsg.Operation)
Expand Down Expand Up @@ -283,6 +286,21 @@ func (c *afc) RemoveAll(path string) (err error) {
return
}

func (c *afc) WriteFile(filename string, data []byte, perm AfcFileMode) (err error) {
var file *AfcFile
if file, err = c.Open(filename, perm); err != nil {
return err
}
defer func() {
err = file.Close()
}()

if _, err = file.Write(data); err != nil {
return err
}
return
}

func toCString(s ...string) []byte {
buf := new(bytes.Buffer)
for _, v := range s {
Expand Down
3 changes: 3 additions & 0 deletions afc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ func Test_afc_Open(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer func() {
_ = afcFile.Close()
}()

userHomeDir, _ := os.UserHomeDir()
file, err := os.Create(userHomeDir + "/Desktop/tmp.jpeg")
Expand Down
167 changes: 167 additions & 0 deletions crashreportmover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package giDevice

import (
"fmt"
"github.com/electricbubble/gidevice/pkg/libimobiledevice"
"howett.net/plist"
"io"
"os"
"path"
"path/filepath"
"strings"
)

var _ CrashReportMover = (*crashReportMover)(nil)

func newCrashReportMover(client *libimobiledevice.CrashReportMoverClient) *crashReportMover {
return &crashReportMover{
client: client,
}
}

type crashReportMover struct {
client *libimobiledevice.CrashReportMoverClient
afc Afc
}

func (c *crashReportMover) readPing() (err error) {
var data []byte
if data, err = c.client.InnerConn().Read(4); err != nil {
return err
}
if string(data) != "ping" {
return fmt.Errorf("crashReportMover ping: %v", data)
}

return
}

func (c *crashReportMover) Move(hostDir string, opts ...CrashReportMoverOption) (err error) {
opt := defaultCrashReportMoverOption()
for _, fn := range opts {
fn(opt)
}

toExtract := make([]string, 0, 64)

fn := func(cwd string, info *AfcFileInfo) {
if info.IsDir() {
return
}
if cwd == "." {
cwd = ""
}

devFilename := path.Join(cwd, info.Name())
hostElem := strings.Split(devFilename, "/")
hostFilename := filepath.Join(hostDir, filepath.Join(hostElem...))
hostFilename = strings.TrimSuffix(hostFilename, ".synced")

if opt.extract && strings.HasSuffix(hostFilename, ".plist") {
toExtract = append(toExtract, hostFilename)
}

var afcFile *AfcFile
if afcFile, err = c.afc.Open(devFilename, AfcFileModeRdOnly); err != nil {
debugLog(fmt.Sprintf("crashReportMover open %s: %s", devFilename, err))
return
}
defer func() {
if err = afcFile.Close(); err != nil {
debugLog(fmt.Sprintf("crashReportMover device file close: %s", err))
}
}()

if err = os.MkdirAll(filepath.Dir(hostFilename), 0755); err != nil {
debugLog(fmt.Sprintf("crashReportMover mkdir %s: %s", filepath.Dir(hostFilename), err))
return
}
var hostFile *os.File
if hostFile, err = os.Create(hostFilename); err != nil {
debugLog(fmt.Sprintf("crashReportMover create %s: %s", hostFilename, err))
return
}
defer func() {
if err = hostFile.Close(); err != nil {
debugLog(fmt.Sprintf("crashReportMover host file close: %s", err))
}
}()

if _, err = io.Copy(hostFile, afcFile); err != nil {
debugLog(fmt.Sprintf("crashReportMover copy %s", err))
return
}

opt.whenDone(devFilename)

if opt.keep {
return
}

if err = c.afc.Remove(devFilename); err != nil {
debugLog(fmt.Sprintf("crashReportMover remove %s: %s", devFilename, err))
return
}
}
if err = c.walkDir(".", fn); err != nil {
return err
}

if !opt.extract {
return nil
}

for _, name := range toExtract {
data, err := os.ReadFile(name)
if err != nil {
debugLog(fmt.Sprintf("crashReportMover extract read %s: %s", name, err))
continue
}
m := make(map[string]interface{})
if _, err = plist.Unmarshal(data, &m); err != nil {
debugLog(fmt.Sprintf("crashReportMover extract plist %s: %s", name, err))
continue
}

desc, ok := m["description"]
if !ok {
continue
}
hostExtCrash := strings.TrimSuffix(name, ".plist") + ".crash"
if err = os.WriteFile(hostExtCrash, []byte(fmt.Sprintf("%v", desc)), 0755); err != nil {
debugLog(fmt.Sprintf("crashReportMover extract save %s: %s", name, err))
continue
}
}

return
}

func (c *crashReportMover) walkDir(dirname string, fn func(path string, info *AfcFileInfo)) (err error) {
var names []string
if names, err = c.afc.ReadDir(dirname); err != nil {
return err
}

cwd := dirname

for _, n := range names {
if n == "." || n == ".." {
continue
}

var info *AfcFileInfo
if info, err = c.afc.Stat(path.Join(cwd, n)); err != nil {
return err
}
if info.IsDir() {
if err = c.walkDir(path.Join(cwd, info.name), fn); err != nil {
return err
}
}

fn(cwd, info)
}

return
}
41 changes: 41 additions & 0 deletions crashreportmover_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package giDevice

import (
"fmt"
"os"
"testing"
)

var crashReportMoverSrv CrashReportMover

func setupCrashReportMoverSrv(t *testing.T) {
setupLockdownSrv(t)

var err error
if lockdownSrv, err = dev.lockdownService(); err != nil {
t.Fatal(err)
}

if crashReportMoverSrv, err = lockdownSrv.CrashReportMoverService(); err != nil {
t.Fatal(err)
}
}

func Test_crashReportMover_Move(t *testing.T) {
setupCrashReportMoverSrv(t)

SetDebug(true)
userHomeDir, _ := os.UserHomeDir()
// err := crashReportMoverSrv.Move(userHomeDir + "/Documents/temp/2021-04/out_gidevice")
// err := crashReportMoverSrv.Move(userHomeDir+"/Documents/temp/2021-04/out_gidevice",
err := crashReportMoverSrv.Move(userHomeDir+"/Documents/temp/2021-04/out_gidevice_extract",
WithKeepCrashReport(true),
WithExtractRawCrashReport(true),
WithWhenMoveIsDone(func(filename string) {
fmt.Println("Copy:", filename)
}),
)
if err != nil {
t.Fatal(err)
}
}
41 changes: 27 additions & 14 deletions device.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/electricbubble/gidevice/pkg/nskeyedarchiver"
uuid "github.com/satori/go.uuid"
"howett.net/plist"
"io/ioutil"
"os"
"path"
"strings"
"time"
Expand Down Expand Up @@ -42,6 +42,7 @@ type device struct {
afc Afc
houseArrest HouseArrest
syslogRelay SyslogRelay
crashReportMover CrashReportMover
}

func (d *device) Properties() DeviceProperties {
Expand Down Expand Up @@ -394,17 +395,12 @@ func (d *device) AppInstall(ipaPath string) (err error) {

installationPath := path.Join(stagingPath, fmt.Sprintf("%s.ipa", bundleID))

var file *AfcFile
if file, err = d.afc.Open(installationPath, AfcFileModeWr); err != nil {
var data []byte
if data, err = os.ReadFile(ipaPath); err != nil {
return err
}

if data, err := ioutil.ReadFile(ipaPath); err != nil {
if err = d.afc.WriteFile(installationPath, data, AfcFileModeWr); err != nil {
return err
} else {
if _, err := file.Write(data); err != nil {
return err
}
}

if _, err = d.installationProxyService(); err != nil {
Expand Down Expand Up @@ -464,6 +460,27 @@ func (d *device) SyslogStop() {
d.syslogRelay.Stop()
}

func (d *device) crashReportMoverService() (crashReportMover CrashReportMover, err error) {
if d.crashReportMover != nil {
return d.crashReportMover, nil
}
if _, err = d.lockdownService(); err != nil {
return nil, err
}
if d.crashReportMover, err = d.lockdown.CrashReportMoverService(); err != nil {
return nil, err
}
crashReportMover = d.crashReportMover
return
}

func (d *device) MoveCrashReport(hostDir string, opts ...CrashReportMoverOption) (err error) {
if _, err = d.crashReportMoverService(); err != nil {
return err
}
return d.crashReportMover.Move(hostDir, opts...)
}

func (d *device) XCTest(bundleID string) (out <-chan string, cancel context.CancelFunc, err error) {
ctx, cancelFunc := context.WithCancel(context.TODO())
_out := make(chan string)
Expand Down Expand Up @@ -690,11 +707,7 @@ func (d *device) _uploadXCTestConfiguration(bundleID string, sessionId uuid.UUID
return "", err
}

var file *AfcFile
if file, err = appAfc.Open(pathXCTestCfg, AfcFileModeWr); err != nil {
return "", err
}
if _, err = file.Write(content); err != nil {
if err = appAfc.WriteFile(pathXCTestCfg, content, AfcFileModeWr); err != nil {
return "", err
}

Expand Down
Loading

0 comments on commit f7e28cd

Please sign in to comment.