Skip to content

Commit f7e28cd

Browse files
feat: [CrashReportMover] support to export crash report
* [AFC] add func `WriteFile` * [AFC] should close `AfcFile`
1 parent d73a058 commit f7e28cd

10 files changed

+374
-16
lines changed

afc.go

+18
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ func (c *afc) Open(filename string, mode AfcFileMode) (file *AfcFile, err error)
116116
if respMsg, err = c.client.Receive(); err != nil {
117117
return nil, fmt.Errorf("afc receive 'Open': %w", err)
118118
}
119+
if err = respMsg.Err(); err != nil {
120+
return nil, fmt.Errorf("afc 'Open': %w", err)
121+
}
119122

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

289+
func (c *afc) WriteFile(filename string, data []byte, perm AfcFileMode) (err error) {
290+
var file *AfcFile
291+
if file, err = c.Open(filename, perm); err != nil {
292+
return err
293+
}
294+
defer func() {
295+
err = file.Close()
296+
}()
297+
298+
if _, err = file.Write(data); err != nil {
299+
return err
300+
}
301+
return
302+
}
303+
286304
func toCString(s ...string) []byte {
287305
buf := new(bytes.Buffer)
288306
for _, v := range s {

afc_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ func Test_afc_Open(t *testing.T) {
8484
if err != nil {
8585
t.Fatal(err)
8686
}
87+
defer func() {
88+
_ = afcFile.Close()
89+
}()
8790

8891
userHomeDir, _ := os.UserHomeDir()
8992
file, err := os.Create(userHomeDir + "/Desktop/tmp.jpeg")

crashreportmover.go

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package giDevice
2+
3+
import (
4+
"fmt"
5+
"github.com/electricbubble/gidevice/pkg/libimobiledevice"
6+
"howett.net/plist"
7+
"io"
8+
"os"
9+
"path"
10+
"path/filepath"
11+
"strings"
12+
)
13+
14+
var _ CrashReportMover = (*crashReportMover)(nil)
15+
16+
func newCrashReportMover(client *libimobiledevice.CrashReportMoverClient) *crashReportMover {
17+
return &crashReportMover{
18+
client: client,
19+
}
20+
}
21+
22+
type crashReportMover struct {
23+
client *libimobiledevice.CrashReportMoverClient
24+
afc Afc
25+
}
26+
27+
func (c *crashReportMover) readPing() (err error) {
28+
var data []byte
29+
if data, err = c.client.InnerConn().Read(4); err != nil {
30+
return err
31+
}
32+
if string(data) != "ping" {
33+
return fmt.Errorf("crashReportMover ping: %v", data)
34+
}
35+
36+
return
37+
}
38+
39+
func (c *crashReportMover) Move(hostDir string, opts ...CrashReportMoverOption) (err error) {
40+
opt := defaultCrashReportMoverOption()
41+
for _, fn := range opts {
42+
fn(opt)
43+
}
44+
45+
toExtract := make([]string, 0, 64)
46+
47+
fn := func(cwd string, info *AfcFileInfo) {
48+
if info.IsDir() {
49+
return
50+
}
51+
if cwd == "." {
52+
cwd = ""
53+
}
54+
55+
devFilename := path.Join(cwd, info.Name())
56+
hostElem := strings.Split(devFilename, "/")
57+
hostFilename := filepath.Join(hostDir, filepath.Join(hostElem...))
58+
hostFilename = strings.TrimSuffix(hostFilename, ".synced")
59+
60+
if opt.extract && strings.HasSuffix(hostFilename, ".plist") {
61+
toExtract = append(toExtract, hostFilename)
62+
}
63+
64+
var afcFile *AfcFile
65+
if afcFile, err = c.afc.Open(devFilename, AfcFileModeRdOnly); err != nil {
66+
debugLog(fmt.Sprintf("crashReportMover open %s: %s", devFilename, err))
67+
return
68+
}
69+
defer func() {
70+
if err = afcFile.Close(); err != nil {
71+
debugLog(fmt.Sprintf("crashReportMover device file close: %s", err))
72+
}
73+
}()
74+
75+
if err = os.MkdirAll(filepath.Dir(hostFilename), 0755); err != nil {
76+
debugLog(fmt.Sprintf("crashReportMover mkdir %s: %s", filepath.Dir(hostFilename), err))
77+
return
78+
}
79+
var hostFile *os.File
80+
if hostFile, err = os.Create(hostFilename); err != nil {
81+
debugLog(fmt.Sprintf("crashReportMover create %s: %s", hostFilename, err))
82+
return
83+
}
84+
defer func() {
85+
if err = hostFile.Close(); err != nil {
86+
debugLog(fmt.Sprintf("crashReportMover host file close: %s", err))
87+
}
88+
}()
89+
90+
if _, err = io.Copy(hostFile, afcFile); err != nil {
91+
debugLog(fmt.Sprintf("crashReportMover copy %s", err))
92+
return
93+
}
94+
95+
opt.whenDone(devFilename)
96+
97+
if opt.keep {
98+
return
99+
}
100+
101+
if err = c.afc.Remove(devFilename); err != nil {
102+
debugLog(fmt.Sprintf("crashReportMover remove %s: %s", devFilename, err))
103+
return
104+
}
105+
}
106+
if err = c.walkDir(".", fn); err != nil {
107+
return err
108+
}
109+
110+
if !opt.extract {
111+
return nil
112+
}
113+
114+
for _, name := range toExtract {
115+
data, err := os.ReadFile(name)
116+
if err != nil {
117+
debugLog(fmt.Sprintf("crashReportMover extract read %s: %s", name, err))
118+
continue
119+
}
120+
m := make(map[string]interface{})
121+
if _, err = plist.Unmarshal(data, &m); err != nil {
122+
debugLog(fmt.Sprintf("crashReportMover extract plist %s: %s", name, err))
123+
continue
124+
}
125+
126+
desc, ok := m["description"]
127+
if !ok {
128+
continue
129+
}
130+
hostExtCrash := strings.TrimSuffix(name, ".plist") + ".crash"
131+
if err = os.WriteFile(hostExtCrash, []byte(fmt.Sprintf("%v", desc)), 0755); err != nil {
132+
debugLog(fmt.Sprintf("crashReportMover extract save %s: %s", name, err))
133+
continue
134+
}
135+
}
136+
137+
return
138+
}
139+
140+
func (c *crashReportMover) walkDir(dirname string, fn func(path string, info *AfcFileInfo)) (err error) {
141+
var names []string
142+
if names, err = c.afc.ReadDir(dirname); err != nil {
143+
return err
144+
}
145+
146+
cwd := dirname
147+
148+
for _, n := range names {
149+
if n == "." || n == ".." {
150+
continue
151+
}
152+
153+
var info *AfcFileInfo
154+
if info, err = c.afc.Stat(path.Join(cwd, n)); err != nil {
155+
return err
156+
}
157+
if info.IsDir() {
158+
if err = c.walkDir(path.Join(cwd, info.name), fn); err != nil {
159+
return err
160+
}
161+
}
162+
163+
fn(cwd, info)
164+
}
165+
166+
return
167+
}

crashreportmover_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package giDevice
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
)
8+
9+
var crashReportMoverSrv CrashReportMover
10+
11+
func setupCrashReportMoverSrv(t *testing.T) {
12+
setupLockdownSrv(t)
13+
14+
var err error
15+
if lockdownSrv, err = dev.lockdownService(); err != nil {
16+
t.Fatal(err)
17+
}
18+
19+
if crashReportMoverSrv, err = lockdownSrv.CrashReportMoverService(); err != nil {
20+
t.Fatal(err)
21+
}
22+
}
23+
24+
func Test_crashReportMover_Move(t *testing.T) {
25+
setupCrashReportMoverSrv(t)
26+
27+
SetDebug(true)
28+
userHomeDir, _ := os.UserHomeDir()
29+
// err := crashReportMoverSrv.Move(userHomeDir + "/Documents/temp/2021-04/out_gidevice")
30+
// err := crashReportMoverSrv.Move(userHomeDir+"/Documents/temp/2021-04/out_gidevice",
31+
err := crashReportMoverSrv.Move(userHomeDir+"/Documents/temp/2021-04/out_gidevice_extract",
32+
WithKeepCrashReport(true),
33+
WithExtractRawCrashReport(true),
34+
WithWhenMoveIsDone(func(filename string) {
35+
fmt.Println("Copy:", filename)
36+
}),
37+
)
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
}

device.go

+27-14
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/electricbubble/gidevice/pkg/nskeyedarchiver"
1111
uuid "github.com/satori/go.uuid"
1212
"howett.net/plist"
13-
"io/ioutil"
13+
"os"
1414
"path"
1515
"strings"
1616
"time"
@@ -42,6 +42,7 @@ type device struct {
4242
afc Afc
4343
houseArrest HouseArrest
4444
syslogRelay SyslogRelay
45+
crashReportMover CrashReportMover
4546
}
4647

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

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

397-
var file *AfcFile
398-
if file, err = d.afc.Open(installationPath, AfcFileModeWr); err != nil {
398+
var data []byte
399+
if data, err = os.ReadFile(ipaPath); err != nil {
399400
return err
400401
}
401-
402-
if data, err := ioutil.ReadFile(ipaPath); err != nil {
402+
if err = d.afc.WriteFile(installationPath, data, AfcFileModeWr); err != nil {
403403
return err
404-
} else {
405-
if _, err := file.Write(data); err != nil {
406-
return err
407-
}
408404
}
409405

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

463+
func (d *device) crashReportMoverService() (crashReportMover CrashReportMover, err error) {
464+
if d.crashReportMover != nil {
465+
return d.crashReportMover, nil
466+
}
467+
if _, err = d.lockdownService(); err != nil {
468+
return nil, err
469+
}
470+
if d.crashReportMover, err = d.lockdown.CrashReportMoverService(); err != nil {
471+
return nil, err
472+
}
473+
crashReportMover = d.crashReportMover
474+
return
475+
}
476+
477+
func (d *device) MoveCrashReport(hostDir string, opts ...CrashReportMoverOption) (err error) {
478+
if _, err = d.crashReportMoverService(); err != nil {
479+
return err
480+
}
481+
return d.crashReportMover.Move(hostDir, opts...)
482+
}
483+
467484
func (d *device) XCTest(bundleID string) (out <-chan string, cancel context.CancelFunc, err error) {
468485
ctx, cancelFunc := context.WithCancel(context.TODO())
469486
_out := make(chan string)
@@ -690,11 +707,7 @@ func (d *device) _uploadXCTestConfiguration(bundleID string, sessionId uuid.UUID
690707
return "", err
691708
}
692709

693-
var file *AfcFile
694-
if file, err = appAfc.Open(pathXCTestCfg, AfcFileModeWr); err != nil {
695-
return "", err
696-
}
697-
if _, err = file.Write(content); err != nil {
710+
if err = appAfc.WriteFile(pathXCTestCfg, content, AfcFileModeWr); err != nil {
698711
return "", err
699712
}
700713

0 commit comments

Comments
 (0)