mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
feat: add launchd config to ipsw diff
This commit is contained in:
+3
-24
@@ -48,27 +48,6 @@ func isURL(str string) bool {
|
||||
return err == nil && u.Scheme != "" && u.Host != ""
|
||||
}
|
||||
|
||||
func extractFromDMG(ipswPath, dmgPath, destPath string, pattern *regexp.Regexp) error {
|
||||
// check if filesystem DMG already exists (due to previous mount command)
|
||||
if _, err := os.Stat(dmgPath); os.IsNotExist(err) {
|
||||
dmgs, err := utils.Unzip(ipswPath, "", func(f *zip.File) bool {
|
||||
return strings.EqualFold(filepath.Base(f.Name), dmgPath)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract %s from IPSW: %v", dmgPath, err)
|
||||
}
|
||||
if len(dmgs) == 0 {
|
||||
return fmt.Errorf("failed to find %s in IPSW", dmgPath)
|
||||
}
|
||||
defer os.Remove(dmgs[0])
|
||||
}
|
||||
if err := utils.ExtractFromDMG(dmgPath, destPath, pattern); err != nil {
|
||||
return fmt.Errorf("failed to extract files matching pattern: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(extractCmd)
|
||||
|
||||
@@ -346,17 +325,17 @@ var extractCmd = &cobra.Command{
|
||||
|
||||
if viper.GetBool("extract.files") { // SEARCH THE DMGs
|
||||
if appOS, err := i.GetAppOsDmg(); err == nil {
|
||||
if err := extractFromDMG(ipswPath, appOS, destPath, patternRE); err != nil {
|
||||
if err := utils.ExtractFromDMG(ipswPath, appOS, destPath, patternRE); err != nil {
|
||||
return fmt.Errorf("failed to extract files from AppOS %s: %v", appOS, err)
|
||||
}
|
||||
}
|
||||
if systemOS, err := i.GetSystemOsDmg(); err == nil {
|
||||
if err := extractFromDMG(ipswPath, systemOS, destPath, patternRE); err != nil {
|
||||
if err := utils.ExtractFromDMG(ipswPath, systemOS, destPath, patternRE); err != nil {
|
||||
return fmt.Errorf("failed to extract files from SystemOS %s: %v", systemOS, err)
|
||||
}
|
||||
}
|
||||
if fsOS, err := i.GetFileSystemOsDmg(); err == nil {
|
||||
if err := extractFromDMG(ipswPath, fsOS, destPath, patternRE); err != nil {
|
||||
if err := utils.ExtractFromDMG(ipswPath, fsOS, destPath, patternRE); err != nil {
|
||||
return fmt.Errorf("failed to extract files from filesystem %s: %v", fsOS, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Package diff provides a way to diff two ipsws
|
||||
package diff
|
||||
|
||||
import (
|
||||
@@ -7,8 +8,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
@@ -22,18 +25,32 @@ import (
|
||||
"github.com/blacktop/ipsw/pkg/kernelcache"
|
||||
)
|
||||
|
||||
const (
|
||||
systemOsDmg = "sys"
|
||||
appOsDmg = "app"
|
||||
fileSystemDmg = "fs"
|
||||
)
|
||||
|
||||
type kernel struct {
|
||||
Path string
|
||||
Version *kernelcache.Version
|
||||
Kexts []string
|
||||
}
|
||||
|
||||
type mount struct {
|
||||
DmgPath string
|
||||
MountPath string
|
||||
IsMounted bool
|
||||
}
|
||||
|
||||
// Context is the context for the diff
|
||||
type Context struct {
|
||||
IPSWPath string
|
||||
Info *info.Info
|
||||
Version string
|
||||
Build string
|
||||
Folder string
|
||||
Mount map[string]mount
|
||||
SystemOsDmgPath string
|
||||
MountPath string
|
||||
IsMounted bool
|
||||
@@ -41,8 +58,11 @@ type Context struct {
|
||||
DSC string
|
||||
Webkit string
|
||||
KDK string
|
||||
|
||||
mu *sync.Mutex
|
||||
}
|
||||
|
||||
// Diff is the diff
|
||||
type Diff struct {
|
||||
Title string
|
||||
|
||||
@@ -57,6 +77,7 @@ type Diff struct {
|
||||
Removed string
|
||||
Updated string
|
||||
}
|
||||
Launchd string
|
||||
|
||||
tmpDir string
|
||||
}
|
||||
@@ -68,9 +89,11 @@ func New(title, ipswOld, ipswNew string, kdks []string) *Diff {
|
||||
Title: title,
|
||||
Old: Context{
|
||||
IPSWPath: ipswOld,
|
||||
Mount: make(map[string]mount),
|
||||
},
|
||||
New: Context{
|
||||
IPSWPath: ipswNew,
|
||||
Mount: make(map[string]mount),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -78,10 +101,12 @@ func New(title, ipswOld, ipswNew string, kdks []string) *Diff {
|
||||
Title: title,
|
||||
Old: Context{
|
||||
IPSWPath: ipswOld,
|
||||
Mount: make(map[string]mount),
|
||||
KDK: kdks[0],
|
||||
},
|
||||
New: Context{
|
||||
IPSWPath: ipswNew,
|
||||
Mount: make(map[string]mount),
|
||||
KDK: kdks[1],
|
||||
},
|
||||
}
|
||||
@@ -166,6 +191,11 @@ func (d *Diff) Diff() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Diffing launchd PLIST")
|
||||
if err := d.parseLaunchdPlists(); err != nil {
|
||||
return fmt.Errorf("failed to parse launchd config plists: %v", err)
|
||||
}
|
||||
|
||||
log.Info("Diffing ENTITLEMENTS")
|
||||
d.Ents, err = d.parseEntitlements()
|
||||
if err != nil {
|
||||
@@ -459,3 +489,54 @@ func (d *Diff) parseEntitlements() (string, error) {
|
||||
DiffTool: "git",
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Diff) parseLaunchdPlists() error {
|
||||
oldFsDMG, err := d.Old.Info.GetFileSystemOsDmg()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get 'Old' File System DMG: %v", err)
|
||||
}
|
||||
if err := utils.ExtractFromDMG(d.Old.IPSWPath, oldFsDMG, filepath.Join(d.tmpDir, "old"), regexp.MustCompile(`.*/sbin/launchd$`)); err != nil {
|
||||
return err
|
||||
}
|
||||
m, err := macho.Open(filepath.Join(d.tmpDir, "old/sbin/launchd"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer m.Close()
|
||||
|
||||
oldData, err := m.Section("__TEXT", "__config").Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newFsDMG, err := d.New.Info.GetFileSystemOsDmg()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get 'New' File System DMG: %v", err)
|
||||
}
|
||||
if err := utils.ExtractFromDMG(d.New.IPSWPath, newFsDMG, filepath.Join(d.tmpDir, "new"), regexp.MustCompile(`.*/sbin/launchd$`)); err != nil {
|
||||
return err
|
||||
}
|
||||
m2, err := macho.Open(filepath.Join(d.tmpDir, "new/sbin/launchd"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer m2.Close()
|
||||
|
||||
newData, err := m.Section("__TEXT", "__config").Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := utils.GitDiff(
|
||||
string(oldData)+"\n",
|
||||
string(newData)+"\n",
|
||||
&utils.GitDiffConfig{Color: false, Tool: "git"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(out) > 0 {
|
||||
d.Launchd = "```diff\n" + out + "\n```"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -74,6 +74,11 @@ const diffMarkdownTemplate = `
|
||||
|
||||
{{ .Ents | noescape }}
|
||||
|
||||
{{ if .Launchd }}
|
||||
## launchd Config
|
||||
{{ .Launchd | noescape }}
|
||||
{{end -}}
|
||||
|
||||
## DSC
|
||||
|
||||
### WebKit
|
||||
|
||||
+57
-43
@@ -1,6 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/blacktop/go-plist"
|
||||
@@ -382,69 +384,70 @@ func Mount(image, mountPoint string) error {
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
} else if runtime.GOOS == "linux" {
|
||||
cmd := exec.Command("apfs-fuse", image, mountPoint)
|
||||
if _, err := exec.LookPath("apfs-fuse"); err != nil {
|
||||
return fmt.Errorf("utils.Mount: apfs-fuse not found (required on non-darwin systems): %v", err)
|
||||
}
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if strings.Contains(string(out), "hdiutil: mount failed - Resource busy") {
|
||||
return ErrMountResourceBusy
|
||||
}
|
||||
return fmt.Errorf("%v: %s", err, out)
|
||||
cmd := exec.Command("apfs-fuse", image, mountPoint)
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
if strings.Contains(string(out), "hdiutil: mount failed - Resource busy") {
|
||||
return ErrMountResourceBusy
|
||||
}
|
||||
|
||||
return nil
|
||||
return fmt.Errorf("%v: %s", err, out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsAlreadyMounted(image string) (string, bool, error) {
|
||||
info, err := MountInfo()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
for _, i := range info.Images {
|
||||
if strings.Contains(i.ImagePath, image) {
|
||||
for _, entry := range i.SystemEntities {
|
||||
if entry.MountPoint != "" {
|
||||
return entry.MountPoint, true, nil
|
||||
func IsAlreadyMounted(image, mountPoint string) (string, bool, error) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
info, err := MountInfo()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
for _, i := range info.Images {
|
||||
if strings.Contains(i.ImagePath, image) {
|
||||
for _, entry := range i.SystemEntities {
|
||||
if entry.MountPoint != "" {
|
||||
return entry.MountPoint, true, nil
|
||||
}
|
||||
}
|
||||
return "", true, nil
|
||||
}
|
||||
return "", true, nil
|
||||
}
|
||||
} else if runtime.GOOS == "linux" {
|
||||
if _, err := os.Stat(filepath.Join(mountPoint, "root")); !os.IsNotExist(err) {
|
||||
return mountPoint, true, nil
|
||||
}
|
||||
}
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
func MountFS(image string) (string, bool, error) {
|
||||
var mountPoint string
|
||||
mountPoint := fmt.Sprintf("/tmp/%s.mount", filepath.Base(image))
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
mountPoint = fmt.Sprintf("/tmp/%s.mount", filepath.Base(image))
|
||||
// check if already mounted
|
||||
if prevMountPoint, mounted, err := IsAlreadyMounted(image); mounted && err == nil {
|
||||
if prevMountPoint, mounted, err := IsAlreadyMounted(image, mountPoint); mounted && err == nil {
|
||||
if prevMountPoint != "" {
|
||||
mountPoint = prevMountPoint
|
||||
}
|
||||
return mountPoint, true, nil
|
||||
}
|
||||
} else {
|
||||
if _, ok := os.LookupEnv("IPSW_IN_DOCKER"); ok {
|
||||
// Create in-docker mount point
|
||||
os.MkdirAll("/data", 0750)
|
||||
mountPoint = "/mnt"
|
||||
} else {
|
||||
// Create temporary non-darwin mount point
|
||||
mountPoint = image + "_temp_mount"
|
||||
if err := os.Mkdir(mountPoint, 0750); err != nil {
|
||||
return "", false, fmt.Errorf("failed to create temporary mount point %s: %v", mountPoint, err)
|
||||
}
|
||||
if err := os.Mkdir(mountPoint, 0750); err != nil {
|
||||
return "", false, fmt.Errorf("failed to create temporary mount point %s: %v", mountPoint, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := Mount(image, mountPoint); err != nil {
|
||||
return "", false, fmt.Errorf("failed to mount %s: %v", image, err)
|
||||
}
|
||||
|
||||
return mountPoint, false, nil
|
||||
}
|
||||
|
||||
@@ -459,20 +462,16 @@ func Unmount(mountPoint string, force bool) error {
|
||||
cmd = exec.Command("hdiutil", "detach", mountPoint)
|
||||
}
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if err := cmd.Run(); err != nil {
|
||||
var edetail string
|
||||
if strings.Contains(err.Error(), "exit status 16") {
|
||||
edetail = " (Resource busy)"
|
||||
}
|
||||
return fmt.Errorf("failed to unmount %s%s: %v", mountPoint, edetail, err)
|
||||
}
|
||||
|
||||
} else if runtime.GOOS == "linux" {
|
||||
cmd := exec.Command("umount", mountPoint)
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to unmount %s: %v", mountPoint, err)
|
||||
}
|
||||
}
|
||||
@@ -524,7 +523,20 @@ func MountInfo() (*HdiUtilInfo, error) {
|
||||
return nil, fmt.Errorf("only supported on macOS")
|
||||
}
|
||||
|
||||
func ExtractFromDMG(dmgPath, destPath string, pattern *regexp.Regexp) error {
|
||||
func ExtractFromDMG(ipswPath, dmgPath, destPath string, pattern *regexp.Regexp) error {
|
||||
// check if filesystem DMG already exists (due to previous mount command)
|
||||
if _, err := os.Stat(dmgPath); os.IsNotExist(err) {
|
||||
dmgs, err := Unzip(ipswPath, "", func(f *zip.File) bool {
|
||||
return strings.EqualFold(filepath.Base(f.Name), dmgPath)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract %s from IPSW: %v", dmgPath, err)
|
||||
}
|
||||
if len(dmgs) == 0 {
|
||||
return fmt.Errorf("failed to find %s in IPSW", dmgPath)
|
||||
}
|
||||
defer os.Remove(dmgs[0])
|
||||
}
|
||||
|
||||
Indent(log.Info, 2)(fmt.Sprintf("Mounting DMG %s", dmgPath))
|
||||
mountPoint, alreadyMounted, err := MountFS(dmgPath)
|
||||
@@ -536,8 +548,10 @@ func ExtractFromDMG(dmgPath, destPath string, pattern *regexp.Regexp) error {
|
||||
} else {
|
||||
defer func() {
|
||||
Indent(log.Debug, 2)(fmt.Sprintf("Unmounting %s", dmgPath))
|
||||
if err := Unmount(mountPoint, false); err != nil {
|
||||
log.Errorf("failed to unmount DMG at %s: %v", dmgPath, err)
|
||||
if err := Retry(3, 2*time.Second, func() error {
|
||||
return Unmount(mountPoint, false)
|
||||
}); err != nil {
|
||||
log.Errorf("failed to unmount DMG %s at %s: %v", dmgPath, mountPoint, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -77,15 +77,9 @@ func ExtractFromDMG(i *info.Info, dmgPath, destPath string, arches []string) err
|
||||
return fmt.Errorf("failed to create destination directory %s: %v", destPath, err)
|
||||
}
|
||||
} else {
|
||||
// Create temporary mount point
|
||||
if err := os.MkdirAll(destPath, 0750); err != nil {
|
||||
return fmt.Errorf("failed to create destination directory %s: %v", destPath, err)
|
||||
}
|
||||
if err := os.MkdirAll(mountPoint, 0750); err != nil {
|
||||
return fmt.Errorf("failed to create temporary mount point %s: %v", mountPoint, err)
|
||||
} else {
|
||||
defer os.RemoveAll(mountPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user