mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-09 00:56:37 +08:00
fix(agent): recover OpenResty runtime after invalid PID (#45)
* fix(agent): recover OpenResty runtime after invalid PID * fix(agent): scope OpenResty shutdown to instance processes --------- Co-authored-by: Noru Wyrms <wyrmsnoru@gmail.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//nolint:unused // Used by the Linux process inspector.
|
||||
const procParentPIDIndex = 1
|
||||
|
||||
//nolint:unused // Used by the Linux process inspector.
|
||||
const procStartTimeIndex = 19 // Field 22, counting from state after the process name.
|
||||
|
||||
//nolint:unused // Used by the Linux executor; portable for process fixture tests.
|
||||
func inspectProcRuntime(root string, executable os.FileInfo, binary, configPath string, uid int) ([]runtimeProcess, error) {
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var processes []runtimeProcess
|
||||
for _, entry := range entries {
|
||||
pid, err := strconv.Atoi(entry.Name())
|
||||
if err != nil || pid <= 0 || !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
process, found, err := inspectProcProcess(filepath.Join(root, entry.Name()), pid, executable, binary, configPath, uid)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if found {
|
||||
processes = append(processes, process)
|
||||
}
|
||||
}
|
||||
return processes, nil
|
||||
}
|
||||
|
||||
//nolint:unused // Used by the Linux process inspector.
|
||||
func inspectProcProcess(dir string, pid int, executable os.FileInfo, binary, configPath string, uid int) (runtimeProcess, bool, error) {
|
||||
//nolint:gosec // Read a fixed proc entry under a numeric PID directory.
|
||||
status, err := os.ReadFile(filepath.Join(dir, "status"))
|
||||
if err != nil {
|
||||
return runtimeProcess{}, false, err
|
||||
}
|
||||
owned := effectiveProcessUID(string(status)) == uid
|
||||
//nolint:gosec // Read a fixed proc entry under a numeric PID directory.
|
||||
command, err := os.ReadFile(filepath.Join(dir, "cmdline"))
|
||||
if err != nil {
|
||||
return runtimeProcess{}, false, err
|
||||
}
|
||||
title := strings.ReplaceAll(string(command), "\x00", " ")
|
||||
if !strings.HasPrefix(title, "nginx: ") && !strings.HasPrefix(title, "openresty: ") {
|
||||
return runtimeProcess{}, false, nil
|
||||
}
|
||||
master := owned && matchesRuntimeMaster(title, configPath)
|
||||
exe, err := os.Stat(filepath.Join(dir, "exe"))
|
||||
if err != nil {
|
||||
if !os.IsPermission(err) {
|
||||
return runtimeProcess{}, false, err
|
||||
}
|
||||
// Non-master candidates are only retained by runtimeDescendants when
|
||||
// their parent belongs to this instance. A capability-enabled non-root
|
||||
// master may be non-dumpable, making /proc/PID/exe inaccessible; verify
|
||||
// its invocation and UID instead.
|
||||
fields := strings.Fields(title)
|
||||
if master && (len(fields) < 4 || fields[3] != binary) {
|
||||
return runtimeProcess{}, false, fmt.Errorf("cannot verify executable of openresty master %d", pid)
|
||||
}
|
||||
} else if !os.SameFile(executable, exe) {
|
||||
if master {
|
||||
return runtimeProcess{}, false, fmt.Errorf("executable changed for openresty master %d; refusing duplicate start", pid)
|
||||
}
|
||||
return runtimeProcess{}, false, nil
|
||||
}
|
||||
//nolint:gosec // Read a fixed proc entry under a numeric PID directory.
|
||||
stat, err := os.ReadFile(filepath.Join(dir, "stat"))
|
||||
if err != nil {
|
||||
return runtimeProcess{}, false, err
|
||||
}
|
||||
closeParen := strings.LastIndexByte(string(stat), ')')
|
||||
if closeParen < 0 {
|
||||
return runtimeProcess{}, false, fmt.Errorf("invalid stat for process %d", pid)
|
||||
}
|
||||
fields := strings.Fields(string(stat)[closeParen+1:])
|
||||
if len(fields) <= procStartTimeIndex {
|
||||
return runtimeProcess{}, false, fmt.Errorf("incomplete stat for process %d", pid)
|
||||
}
|
||||
if fields[0] == "Z" {
|
||||
return runtimeProcess{}, false, nil
|
||||
}
|
||||
parentPID, err := strconv.Atoi(fields[procParentPIDIndex])
|
||||
if err != nil {
|
||||
return runtimeProcess{}, false, fmt.Errorf("invalid parent pid for process %d: %w", pid, err)
|
||||
}
|
||||
return runtimeProcess{PID: pid, ParentPID: parentPID, Master: master, StartTime: fields[procStartTimeIndex]}, true, nil
|
||||
}
|
||||
|
||||
//nolint:unused // Used by the Linux process inspector.
|
||||
func effectiveProcessUID(status string) int {
|
||||
for _, line := range strings.Split(status, "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 3 && fields[0] == "Uid:" {
|
||||
uid, err := strconv.Atoi(fields[2])
|
||||
if err == nil {
|
||||
return uid
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
Reference in New Issue
Block a user