1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
22 lockprefix = "crunch-run-"
26 // procinfo is saved in each process's lockfile.
27 type procinfo struct {
34 // Detach acquires a lock for the given uuid, and starts the current
35 // program as a child process (with -detached prepended to the given
36 // arguments so the child knows not to detach again). The lock is
37 // passed along to the child process.
38 func Detach(uuid string, args []string, stdout, stderr io.Writer) int {
39 return exitcode(stderr, detach(uuid, args, stdout, stderr))
41 func detach(uuid string, args []string, stdout, stderr io.Writer) error {
42 lockfile, err := os.OpenFile(filepath.Join(lockdir, lockprefix+uuid+locksuffix), os.O_CREATE|os.O_RDWR, 0700)
46 defer lockfile.Close()
47 err = syscall.Flock(int(lockfile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
53 outfile, err := ioutil.TempFile("", "crunch-run-"+uuid+"-stdout-")
58 errfile, err := ioutil.TempFile("", "crunch-run-"+uuid+"-stderr-")
60 os.Remove(outfile.Name())
65 cmd := exec.Command(args[0], append([]string{"-detached"}, args[1:]...)...)
68 // Child inherits lockfile.
69 cmd.ExtraFiles = []*os.File{lockfile}
70 // Ensure child isn't interrupted even if we receive signals
71 // from parent (sshd) while sending lockfile content to
73 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
76 os.Remove(outfile.Name())
77 os.Remove(errfile.Name())
81 w := io.MultiWriter(stdout, lockfile)
82 err = json.NewEncoder(w).Encode(procinfo{
84 Stdout: outfile.Name(),
85 Stderr: errfile.Name(),
88 os.Remove(outfile.Name())
89 os.Remove(errfile.Name())
95 // KillProcess finds the crunch-run process corresponding to the given
96 // uuid, and sends the given signal to it. It then waits up to 1
97 // second for the process to die. It returns 0 if the process is
98 // successfully killed or didn't exist in the first place.
99 func KillProcess(uuid string, signal syscall.Signal, stdout, stderr io.Writer) int {
100 return exitcode(stderr, kill(uuid, signal, stdout, stderr))
103 func kill(uuid string, signal syscall.Signal, stdout, stderr io.Writer) error {
104 path := filepath.Join(lockdir, lockprefix+uuid+locksuffix)
105 f, err := os.Open(path)
106 if os.IsNotExist(err) {
108 } else if err != nil {
114 err = json.NewDecoder(f).Decode(&pi)
116 return fmt.Errorf("%s: %s\n", path, err)
119 if pi.UUID != uuid || pi.PID == 0 {
120 return fmt.Errorf("%s: bogus procinfo: %+v", path, pi)
123 proc, err := os.FindProcess(pi.PID)
128 err = proc.Signal(signal)
129 for deadline := time.Now().Add(time.Second); err == nil && time.Now().Before(deadline); time.Sleep(time.Second / 100) {
130 err = proc.Signal(syscall.Signal(0))
133 return fmt.Errorf("pid %d: sent signal %d (%s) but process is still alive", pi.PID, signal, signal)
135 fmt.Fprintf(stderr, "pid %d: %s\n", pi.PID, err)
139 // List UUIDs of active crunch-run processes.
140 func ListProcesses(stdout, stderr io.Writer) int {
141 return exitcode(stderr, filepath.Walk(lockdir, func(path string, info os.FileInfo, err error) error {
143 return filepath.SkipDir
145 if name := info.Name(); !strings.HasPrefix(name, lockprefix) || !strings.HasSuffix(name, locksuffix) {
148 if info.Size() == 0 {
149 // race: process has opened/locked but hasn't yet written pid/uuid
153 f, err := os.Open(path)
159 // TODO: Do this check without risk of disrupting lock
160 // acquisition during races, e.g., by connecting to a
161 // unix socket or checking /proc/$pid/fd/$n ->
163 err = syscall.Flock(int(f.Fd()), syscall.LOCK_SH)
166 err := os.Remove(path)
168 fmt.Fprintln(stderr, err)
174 err = json.NewDecoder(f).Decode(&pi)
176 fmt.Fprintf(stderr, "%s: %s\n", path, err)
179 if pi.UUID == "" || pi.PID == 0 {
180 fmt.Fprintf(stderr, "%s: bogus procinfo: %+v", path, pi)
184 fmt.Fprintln(stdout, pi.UUID)
189 // If err is nil, return 0 ("success"); otherwise, print err to stderr
191 func exitcode(stderr io.Writer, err error) int {
193 fmt.Fprintln(stderr, err)