diff --git a/afc.go b/afc.go index 6dbdb75..fee5a5a 100644 --- a/afc.go +++ b/afc.go @@ -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) @@ -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 { diff --git a/afc_test.go b/afc_test.go index 33999d0..12432d1 100644 --- a/afc_test.go +++ b/afc_test.go @@ -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") diff --git a/crashreportmover.go b/crashreportmover.go new file mode 100644 index 0000000..54de9a1 --- /dev/null +++ b/crashreportmover.go @@ -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 +} diff --git a/crashreportmover_test.go b/crashreportmover_test.go new file mode 100644 index 0000000..806ed40 --- /dev/null +++ b/crashreportmover_test.go @@ -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) + } +} diff --git a/device.go b/device.go index 9856d23..d152305 100644 --- a/device.go +++ b/device.go @@ -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" @@ -42,6 +42,7 @@ type device struct { afc Afc houseArrest HouseArrest syslogRelay SyslogRelay + crashReportMover CrashReportMover } func (d *device) Properties() DeviceProperties { @@ -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 { @@ -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) @@ -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 } diff --git a/idevice.go b/idevice.go index 6c01abd..3123bf9 100644 --- a/idevice.go +++ b/idevice.go @@ -61,6 +61,9 @@ type Device interface { Syslog() (lines <-chan string, err error) SyslogStop() + crashReportMoverService() (crashReportMover CrashReportMover, err error) + MoveCrashReport(hostDir string, opts ...CrashReportMoverOption) (err error) + XCTest(bundleID string) (out <-chan string, cancel context.CancelFunc, err error) } @@ -88,6 +91,7 @@ type Lockdown interface { AfcService() (afc Afc, err error) HouseArrestService() (houseArrest HouseArrest, err error) SyslogRelayService() (syslogRelay SyslogRelay, err error) + CrashReportMoverService() (crashReportMover CrashReportMover, err error) } type ImageMounter interface { @@ -159,6 +163,8 @@ type Afc interface { // HashWithRange sha1 algorithm with file range HashWithRange(filePath string, start, end uint64) ([]byte, error) RemoveAll(path string) (err error) + + WriteFile(filename string, data []byte, perm AfcFileMode) (err error) } type HouseArrest interface { @@ -187,6 +193,11 @@ type SyslogRelay interface { Stop() } +type CrashReportMover interface { + Move(hostDir string, opts ...CrashReportMoverOption) (err error) + walkDir(dirname string, fn func(path string, info *AfcFileInfo)) (err error) +} + type InnerConn = libimobiledevice.InnerConn type LockdownType = libimobiledevice.LockdownType @@ -320,6 +331,39 @@ type Process struct { StartDate time.Time `json:"startDate"` } +type crashReportMoverOption struct { + whenDone func(filename string) + keep bool + extract bool +} + +func defaultCrashReportMoverOption() *crashReportMoverOption { + return &crashReportMoverOption{ + whenDone: func(filename string) {}, + keep: false, + } +} + +type CrashReportMoverOption func(opt *crashReportMoverOption) + +func WithKeepCrashReport(b bool) CrashReportMoverOption { + return func(opt *crashReportMoverOption) { + opt.keep = b + } +} + +func WithExtractRawCrashReport(b bool) CrashReportMoverOption { + return func(opt *crashReportMoverOption) { + opt.extract = b + } +} + +func WithWhenMoveIsDone(whenDone func(filename string)) CrashReportMoverOption { + return func(opt *crashReportMoverOption) { + opt.whenDone = whenDone + } +} + func _removeDuplicate(strSlice []string) []string { existed := make(map[string]bool, len(strSlice)) noRepeat := make([]string, 0, len(strSlice)) diff --git a/lockdown.go b/lockdown.go index 6527b2f..096d975 100644 --- a/lockdown.go +++ b/lockdown.go @@ -447,6 +447,26 @@ func (c *lockdown) SyslogRelayService() (syslogRelay SyslogRelay, err error) { return } +func (c *lockdown) CrashReportMoverService() (crashReportMover CrashReportMover, err error) { + var innerConn InnerConn + if innerConn, err = c._startService(libimobiledevice.CrashReportMoverServiceName, nil); err != nil { + return nil, err + } + + mover := newCrashReportMover(libimobiledevice.NewCrashReportMoverClient(innerConn)) + if err = mover.readPing(); err != nil { + return nil, err + } + + if innerConn, err = c._startService(libimobiledevice.CrashReportCopyMobileServiceName, nil); err != nil { + return nil, err + } + mover.afc = newAfc(libimobiledevice.NewAfcClient(innerConn)) + + crashReportMover = mover + return +} + func (c *lockdown) _startService(serviceName string, escrowBag []byte) (innerConn InnerConn, err error) { if err = c.handshake(); err != nil { return nil, err diff --git a/lockdown_test.go b/lockdown_test.go index a267839..bd123c9 100644 --- a/lockdown_test.go +++ b/lockdown_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "os/signal" + "path" "testing" "time" ) @@ -73,3 +74,31 @@ func Test_lockdown_SyslogRelayService(t *testing.T) { syslogRelaySrv.Stop() time.Sleep(time.Second) } + +func Test_lockdown_CrashReportMoverService(t *testing.T) { + setupLockdownSrv(t) + + crashReportMoverSrv, err := lockdownSrv.CrashReportMoverService() + if err != nil { + t.Fatal(err) + } + + filenames := make([]string, 0, 36) + fn := func(cwd string, info *AfcFileInfo) { + if cwd == "." { + cwd = "" + } + filenames = append(filenames, path.Join(cwd, info.Name())) + // fmt.Println(path.Join(cwd, name)) + } + err = crashReportMoverSrv.walkDir(".", fn) + if err != nil { + t.Fatal(err) + } + + for _, n := range filenames { + fmt.Println(n) + } + + t.Log(len(filenames)) +} diff --git a/pkg/ipa/ipa.go b/pkg/ipa/ipa.go index f3add5a..f0a27e0 100644 --- a/pkg/ipa/ipa.go +++ b/pkg/ipa/ipa.go @@ -5,7 +5,6 @@ import ( "fmt" "howett.net/plist" "io" - "io/ioutil" "path" ) @@ -15,6 +14,10 @@ func Info(ipaPath string) (info map[string]interface{}, err error) { return nil, err } + defer func() { + err = reader.Close() + }() + for _, file := range reader.File { matched, _err := path.Match("Payload/*.app/Info.plist", file.Name) if _err != nil { @@ -29,7 +32,7 @@ func Info(ipaPath string) (info map[string]interface{}, err error) { if rd, _err = file.Open(); _err != nil { return nil, _err } - data, _err := ioutil.ReadAll(rd) + data, _err := io.ReadAll(rd) if _err != nil { return nil, _err } diff --git a/pkg/libimobiledevice/crashreportmover.go b/pkg/libimobiledevice/crashreportmover.go new file mode 100644 index 0000000..4d4ca85 --- /dev/null +++ b/pkg/libimobiledevice/crashreportmover.go @@ -0,0 +1,20 @@ +package libimobiledevice + +const ( + CrashReportMoverServiceName = "com.apple.crashreportmover" + CrashReportCopyMobileServiceName = "com.apple.crashreportcopymobile" +) + +func NewCrashReportMoverClient(innerConn InnerConn) *CrashReportMoverClient { + return &CrashReportMoverClient{ + newServicePacketClient(innerConn), + } +} + +type CrashReportMoverClient struct { + client *servicePacketClient +} + +func (c *CrashReportMoverClient) InnerConn() InnerConn { + return c.client.innerConn +}