sing-tun/packages_android.go

176 lines
4 KiB
Go

package tun
import (
"bytes"
"encoding/xml"
"io"
"os"
"strconv"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/abx"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
)
type packageManager struct {
callback PackageManagerCallback
logger logger.Logger
watcher *fswatch.Watcher
idByPackage map[string]uint32
sharedByPackage map[string]uint32
packageById map[uint32]string
sharedById map[uint32]string
}
func NewPackageManager(options PackageManagerOptions) (PackageManager, error) {
return &packageManager{
callback: options.Callback,
logger: options.Logger,
}, nil
}
func (m *packageManager) Start() error {
err := m.updatePackages()
if err != nil {
return E.Cause(err, "read packages list")
}
err = m.startWatcher()
if err != nil {
m.logger.Error(E.Cause(err, "create watcher for packages list"))
}
return nil
}
func (m *packageManager) startWatcher() error {
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: []string{"/data/system/packages.xml"},
Direct: true,
Callback: m.packagesUpdated,
Logger: m.logger,
})
if err != nil {
return err
}
err = watcher.Start()
if err != nil {
return err
}
m.watcher = watcher
return nil
}
func (m *packageManager) packagesUpdated(path string) {
err := m.updatePackages()
if err != nil {
m.logger.Error(E.Cause(err, "update packages"))
}
}
func (m *packageManager) Close() error {
return common.Close(common.PtrOrNil(m.watcher))
}
func (m *packageManager) IDByPackage(packageName string) (uint32, bool) {
id, loaded := m.idByPackage[packageName]
return id, loaded
}
func (m *packageManager) IDBySharedPackage(sharedPackage string) (uint32, bool) {
id, loaded := m.sharedByPackage[sharedPackage]
return id, loaded
}
func (m *packageManager) PackageByID(id uint32) (string, bool) {
packageName, loaded := m.packageById[id]
return packageName, loaded
}
func (m *packageManager) SharedPackageByID(id uint32) (string, bool) {
sharedPackage, loaded := m.sharedById[id]
return sharedPackage, loaded
}
func (m *packageManager) updatePackages() error {
packagesData, err := os.ReadFile("/data/system/packages.xml")
if err != nil {
return err
}
var decoder *xml.Decoder
reader, ok := abx.NewReader(packagesData)
if ok {
decoder = xml.NewTokenDecoder(reader)
} else {
decoder = xml.NewDecoder(bytes.NewReader(packagesData))
}
return m.decodePackages(decoder)
}
func (m *packageManager) decodePackages(decoder *xml.Decoder) error {
idByPackage := make(map[string]uint32)
sharedByPackage := make(map[string]uint32)
packageById := make(map[uint32]string)
sharedById := make(map[uint32]string)
for {
token, err := decoder.Token()
if err == io.EOF {
break
} else if err != nil {
return err
}
element, isStart := token.(xml.StartElement)
if !isStart {
continue
}
switch element.Name.Local {
case "package":
var name string
var userID uint64
for _, attr := range element.Attr {
switch attr.Name.Local {
case "name":
name = attr.Value
case "userId", "sharedUserId":
userID, err = strconv.ParseUint(attr.Value, 10, 32)
if err != nil {
return err
}
}
}
if userID == 0 && name == "" {
continue
}
idByPackage[name] = uint32(userID)
packageById[uint32(userID)] = name
case "shared-user":
var name string
var userID uint64
for _, attr := range element.Attr {
switch attr.Name.Local {
case "name":
name = attr.Value
case "userId":
userID, err = strconv.ParseUint(attr.Value, 10, 32)
if err != nil {
return err
}
packageById[uint32(userID)] = name
}
}
if userID == 0 && name == "" {
continue
}
sharedByPackage[name] = uint32(userID)
sharedById[uint32(userID)] = name
}
}
m.idByPackage = idByPackage
m.sharedByPackage = sharedByPackage
m.packageById = packageById
m.sharedById = sharedById
m.callback.OnPackagesUpdated(len(packageById), len(sharedById))
return nil
}