SpawnSendTelemetry spawns a detached subprocess to send telemetry. The payload is written to the child's stdin via a pipe so that it is not visible to other users through process argument inspection (e.g. ps aux). The parent writes the full payload and closes the pipe before returning, so no long-li
(executable string, payload SendTelemetryPayload)
| 360 | // |
| 361 | // All errors are silently ignored since telemetry is best-effort. |
| 362 | func SpawnSendTelemetry(executable string, payload SendTelemetryPayload) { |
| 363 | payloadBytes, err := json.Marshal(payload) |
| 364 | if err != nil { |
| 365 | return |
| 366 | } |
| 367 | |
| 368 | if len(payloadBytes) > maxPayloadSize { |
| 369 | return |
| 370 | } |
| 371 | |
| 372 | // Resolve the executable to an absolute path before changing the child's |
| 373 | // working directory. Without this, a relative path (e.g. from GH_PATH) would |
| 374 | // be resolved against cmd.Dir at Start time and fail to spawn. |
| 375 | if abs, err := filepath.Abs(executable); err == nil { |
| 376 | executable = abs |
| 377 | } |
| 378 | |
| 379 | cmd := exec.Command(executable, "send-telemetry") |
| 380 | |
| 381 | cmd.Stdout = io.Discard |
| 382 | cmd.Stderr = io.Discard |
| 383 | |
| 384 | // Set the working directory to a stable directory elsewhere so that the subprocess doesn't |
| 385 | // hold a reference to the parent's current working directory, avoiding any weirdness around |
| 386 | // deleting the parent process's current working directory while the child is still running. |
| 387 | // Only do this when we have an absolute executable path so that the child can still be found. |
| 388 | if filepath.IsAbs(executable) { |
| 389 | cmd.Dir = os.TempDir() |
| 390 | } |
| 391 | |
| 392 | // Configure the child process to be detached from the parent so that it can continue running |
| 393 | // after the parent exits, and so that it doesn't receive any signals sent to the parent. |
| 394 | cmd.SysProcAttr = detachAttrs() |
| 395 | |
| 396 | // Get the write end of the stdin pipe before starting. |
| 397 | stdin, err := cmd.StdinPipe() |
| 398 | if err != nil { |
| 399 | return |
| 400 | } |
| 401 | |
| 402 | if err := cmd.Start(); err != nil { |
| 403 | _ = stdin.Close() |
| 404 | return |
| 405 | } |
| 406 | |
| 407 | // Write the payload synchronously into the kernel pipe buffer, then close |
| 408 | // the pipe to signal EOF. The child reads the complete payload from stdin. |
| 409 | // io.Copy loops until all bytes are written, avoiding any risk of a short write. |
| 410 | _, _ = io.Copy(stdin, bytes.NewReader(payloadBytes)) |
| 411 | _ = stdin.Close() |
| 412 | |
| 413 | // Release resources associated with the child process since we will never Wait for it. |
| 414 | _ = cmd.Process.Release() |
| 415 | } |
| 416 | |
| 417 | type NoOpService struct{} |
| 418 |