Merge quick/fix-cmdline-space-collapse: browser_list ve Chromium con cmdline colapsado
This commit is contained in:
+33
-6
@@ -57,14 +57,31 @@ type chromiumMaster struct {
|
|||||||
HasCDP bool `json:"has_cdp"`
|
HasCDP bool `json:"has_cdp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// readProcCmdline reads /proc/<pid>/cmdline and splits it on NUL into argv.
|
// parseCmdline turns the raw bytes of /proc/<pid>/cmdline into argv.
|
||||||
// Returns nil if the process is gone or unreadable.
|
//
|
||||||
func readProcCmdline(pid int) []string {
|
// Canonically the kernel separates arguments with NUL bytes. But Chromium (and
|
||||||
b, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "cmdline"))
|
// other programs that rewrite their process title in place) collapse the argv
|
||||||
if err != nil || len(b) == 0 {
|
// region into a single space-separated string, losing the NUL separators. In
|
||||||
|
// that case splitting on NUL yields a single giant element holding the whole
|
||||||
|
// command line, which breaks argv[0] detection and "--flag=" prefix matching.
|
||||||
|
//
|
||||||
|
// So: if the data still carries NUL separators we split on NUL (the correct,
|
||||||
|
// space-safe path). Otherwise we fall back to splitting on whitespace. The
|
||||||
|
// fallback is best-effort and would mis-split a flag value containing spaces
|
||||||
|
// (e.g. a user-data-dir path with a space), but Chromium's own flags don't, so
|
||||||
|
// it recovers the master-detection flags (--user-data-dir, --type=,
|
||||||
|
// --remote-debugging-port, --profile-directory) reliably in practice.
|
||||||
|
func parseCmdline(b []byte) []string {
|
||||||
|
s := strings.TrimRight(string(b), "\x00")
|
||||||
|
if s == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
raw := strings.Split(string(b), "\x00")
|
var raw []string
|
||||||
|
if strings.Contains(s, "\x00") {
|
||||||
|
raw = strings.Split(s, "\x00")
|
||||||
|
} else {
|
||||||
|
raw = strings.Fields(s)
|
||||||
|
}
|
||||||
args := make([]string, 0, len(raw))
|
args := make([]string, 0, len(raw))
|
||||||
for _, a := range raw {
|
for _, a := range raw {
|
||||||
if a != "" {
|
if a != "" {
|
||||||
@@ -74,6 +91,16 @@ func readProcCmdline(pid int) []string {
|
|||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readProcCmdline reads /proc/<pid>/cmdline and parses it into argv.
|
||||||
|
// Returns nil if the process is gone or unreadable.
|
||||||
|
func readProcCmdline(pid int) []string {
|
||||||
|
b, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "cmdline"))
|
||||||
|
if err != nil || len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return parseCmdline(b)
|
||||||
|
}
|
||||||
|
|
||||||
// flagValue returns the value of a "--name=value" flag from argv, plus whether it
|
// flagValue returns the value of a "--name=value" flag from argv, plus whether it
|
||||||
// was present. Matches the exact "--name=" prefix; the first occurrence wins.
|
// was present. Matches the exact "--name=" prefix; the first occurrence wins.
|
||||||
func flagValue(args []string, name string) (string, bool) {
|
func flagValue(args []string, name string) (string, bool) {
|
||||||
|
|||||||
@@ -150,3 +150,48 @@ func TestMatchMaster(t *testing.T) {
|
|||||||
t.Errorf("unknown profile should not match")
|
t.Errorf("unknown profile should not match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestParseCmdline cubre el parsing de /proc/<pid>/cmdline en sus dos formatos:
|
||||||
|
// el canonico separado por NUL y el colapsado por espacios que produce Chromium
|
||||||
|
// al reescribir su titulo de proceso in-place. El segundo caso es el que rompia
|
||||||
|
// browser_list (los flags quedaban dentro de un unico argv[0] gigante).
|
||||||
|
func TestParseCmdline(t *testing.T) {
|
||||||
|
// Caso canonico: argv separado por NUL (proceso normal).
|
||||||
|
nul := []byte("/usr/lib/chromium/chromium\x00--user-data-dir=/tmp/x\x00--remote-debugging-port=9333\x00")
|
||||||
|
got := parseCmdline(nul)
|
||||||
|
want := []string{"/usr/lib/chromium/chromium", "--user-data-dir=/tmp/x", "--remote-debugging-port=9333"}
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf("NUL: got %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
for i := range want {
|
||||||
|
if got[i] != want[i] {
|
||||||
|
t.Errorf("NUL[%d]: got %q, want %q", i, got[i], want[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caso Chromium: cmdline colapsado a una sola cadena separada por espacios.
|
||||||
|
collapsed := []byte("/usr/lib/chromium/chromium --remote-debugging-port=9333 --user-data-dir=/tmp/browser_mcp_userdata --no-first-run https://www.alsa.es/")
|
||||||
|
args := parseCmdline(collapsed)
|
||||||
|
if len(args) == 1 {
|
||||||
|
t.Fatalf("space-collapsed: parse devolvio un unico elemento gigante: %q", args[0])
|
||||||
|
}
|
||||||
|
if args[0] != "/usr/lib/chromium/chromium" {
|
||||||
|
t.Errorf("space-collapsed argv[0]: got %q, want chromium binary", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// El master debe detectarse a partir del cmdline colapsado (regresion de browser_list).
|
||||||
|
m, ok := parseChromiumMaster(18148, args)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("space-collapsed: parseChromiumMaster no detecto el master")
|
||||||
|
}
|
||||||
|
if m.UserDataDir != "/tmp/browser_mcp_userdata" {
|
||||||
|
t.Errorf("space-collapsed udd: got %q, want /tmp/browser_mcp_userdata", m.UserDataDir)
|
||||||
|
}
|
||||||
|
if m.CDPPort != "9333" || !m.HasCDP {
|
||||||
|
t.Errorf("space-collapsed cdp: got port=%q hasCDP=%v, want 9333/true", m.CDPPort, m.HasCDP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parseCmdline([]byte("")) != nil || parseCmdline([]byte("\x00\x00")) != nil {
|
||||||
|
t.Errorf("cmdline vacio debe devolver nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user