Some checks failed
RKNnoVPN Linux and Android CI / policy-and-go (push) Successful in 14s
RKNnoVPN Linux and Android CI / android-debug (push) Successful in 2m5s
Full build and Forgejo release / Privacy Lint (push) Successful in 3s
Full build and Forgejo release / Local-first Runtime Guardrail (push) Successful in 2s
Full build and Forgejo release / Go Tests (push) Successful in 13s
Full build and Forgejo release / Resolve sing-box release (push) Successful in 1s
Full build and Forgejo release / Resolve Xray-core release (push) Successful in 1s
Full build and Forgejo release / Android Guardrails & Tests (push) Failing after 16s
Full build and Forgejo release / Build APK (push) Has been skipped
Full build and Forgejo release / Build Runtime CLI (arm64) (push) Successful in 11s
Full build and Forgejo release / Build Runtime CLI (armv7) (push) Successful in 11s
Full build and Forgejo release / Build sing-box (arm64) (push) Failing after 2s
Full build and Forgejo release / Build sing-box (armv7) (push) Failing after 2s
Full build and Forgejo release / Build Xray-core (arm64) (push) Failing after 3s
Full build and Forgejo release / Build Xray-core (armv7) (push) Failing after 2s
Full build and Forgejo release / Build Magisk Module (push) Has been skipped
Full build and Forgejo release / Create Release (push) Has been skipped
123 lines
3 KiB
Go
123 lines
3 KiB
Go
package modulehelper
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"git.zapret.moe/zapretdiscordyoutube/RKNnoVPN/runtime/internal/modulecontract"
|
|
)
|
|
|
|
const (
|
|
StatusActive = "active"
|
|
StatusPendingUpdate = "pending_update"
|
|
StatusPendingRemove = "pending_remove"
|
|
StatusDisabled = "disabled"
|
|
StatusMissing = "missing"
|
|
StatusServiceMissing = "service_missing"
|
|
StatusRuntimeMissing = "runtime_missing"
|
|
)
|
|
|
|
type State struct {
|
|
Status string `json:"status"`
|
|
ModuleDir string `json:"moduleDir"`
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
func CurrentState(moduleDir string) State {
|
|
paths := modulecontract.NewPaths(moduleDir)
|
|
state := State{
|
|
Status: StatusActive,
|
|
ModuleDir: paths.Dir(),
|
|
}
|
|
|
|
if hasPendingUpdate(paths.Dir()) {
|
|
state.Status = StatusPendingUpdate
|
|
state.Message = "module update is staged; reboot required"
|
|
return state
|
|
}
|
|
if exists(filepath.Join(paths.Dir(), "remove")) {
|
|
state.Status = StatusPendingRemove
|
|
state.Message = "module is marked for removal; reboot required"
|
|
return state
|
|
}
|
|
if exists(filepath.Join(paths.Dir(), "disable")) {
|
|
state.Status = StatusDisabled
|
|
state.Message = "module is disabled"
|
|
return state
|
|
}
|
|
if !exists(paths.Dir()) {
|
|
state.Status = StatusMissing
|
|
state.Message = "module directory is missing"
|
|
return state
|
|
}
|
|
if !isExecutable(filepath.Join(paths.Dir(), "service.sh")) {
|
|
state.Status = StatusServiceMissing
|
|
state.Message = "service.sh is missing or not executable"
|
|
return state
|
|
}
|
|
if !isExecutable(filepath.Join(paths.BinDir(), "rknnovpn-runtime")) {
|
|
state.Status = StatusRuntimeMissing
|
|
state.Message = "rknnovpn-runtime is missing or not executable"
|
|
return state
|
|
}
|
|
return state
|
|
}
|
|
|
|
func hasPendingUpdate(moduleDir string) bool {
|
|
for _, base := range []string{"/data/adb/modules_update", "/data/adb/ksu/modules_update", "/data/adb/ap/modules_update"} {
|
|
if exists(filepath.Join(base, filepath.Base(moduleDir))) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func exists(path string) bool {
|
|
_, err := os.Stat(path)
|
|
return err == nil
|
|
}
|
|
|
|
func isExecutable(path string) bool {
|
|
info, err := os.Stat(path)
|
|
return err == nil && !info.IsDir() && info.Mode()&0111 != 0
|
|
}
|
|
|
|
func readPID(path string) int {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
pid, err := strconv.Atoi(strings.TrimSpace(string(data)))
|
|
if err != nil || pid <= 0 {
|
|
return 0
|
|
}
|
|
return pid
|
|
}
|
|
|
|
func processExists(pid int) bool {
|
|
if pid <= 0 {
|
|
return false
|
|
}
|
|
err := syscall.Kill(pid, 0)
|
|
return err == nil || errors.Is(err, syscall.EPERM)
|
|
}
|
|
|
|
func processMatchesPath(pid int, wanted string) bool {
|
|
if !processExists(pid) {
|
|
return false
|
|
}
|
|
if exe, err := os.Readlink(filepath.Join("/proc", strconv.Itoa(pid), "exe")); err == nil {
|
|
if exe == wanted || filepath.Clean(exe) == filepath.Clean(wanted) {
|
|
return true
|
|
}
|
|
}
|
|
cmdline, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "cmdline"))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return strings.Contains(strings.ReplaceAll(string(cmdline), "\x00", " "), wanted)
|
|
}
|