runc.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. /*
  2. Copyright The containerd Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package runc
  14. import (
  15. "bytes"
  16. "context"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "io"
  21. "io/ioutil"
  22. "os"
  23. "os/exec"
  24. "path/filepath"
  25. "strconv"
  26. "strings"
  27. "time"
  28. specs "github.com/opencontainers/runtime-spec/specs-go"
  29. )
  30. // Format is the type of log formatting options avaliable
  31. type Format string
  32. // TopBody represents the structured data of the full ps output
  33. type TopResults struct {
  34. // Processes running in the container, where each is process is an array of values corresponding to the headers
  35. Processes [][]string `json:"Processes"`
  36. // Headers are the names of the columns
  37. Headers []string `json:"Headers"`
  38. }
  39. const (
  40. none Format = ""
  41. JSON Format = "json"
  42. Text Format = "text"
  43. // DefaultCommand is the default command for Runc
  44. DefaultCommand = "runc"
  45. )
  46. // List returns all containers created inside the provided runc root directory
  47. func (r *Runc) List(context context.Context) ([]*Container, error) {
  48. data, err := cmdOutput(r.command(context, "list", "--format=json"), false, nil)
  49. defer putBuf(data)
  50. if err != nil {
  51. return nil, err
  52. }
  53. var out []*Container
  54. if err := json.Unmarshal(data.Bytes(), &out); err != nil {
  55. return nil, err
  56. }
  57. return out, nil
  58. }
  59. // State returns the state for the container provided by id
  60. func (r *Runc) State(context context.Context, id string) (*Container, error) {
  61. data, err := cmdOutput(r.command(context, "state", id), true, nil)
  62. defer putBuf(data)
  63. if err != nil {
  64. return nil, fmt.Errorf("%s: %s", err, data.String())
  65. }
  66. var c Container
  67. if err := json.Unmarshal(data.Bytes(), &c); err != nil {
  68. return nil, err
  69. }
  70. return &c, nil
  71. }
  72. type ConsoleSocket interface {
  73. Path() string
  74. }
  75. type CreateOpts struct {
  76. IO
  77. // PidFile is a path to where a pid file should be created
  78. PidFile string
  79. ConsoleSocket ConsoleSocket
  80. Detach bool
  81. NoPivot bool
  82. NoNewKeyring bool
  83. ExtraFiles []*os.File
  84. Started chan<- int
  85. }
  86. func (o *CreateOpts) args() (out []string, err error) {
  87. if o.PidFile != "" {
  88. abs, err := filepath.Abs(o.PidFile)
  89. if err != nil {
  90. return nil, err
  91. }
  92. out = append(out, "--pid-file", abs)
  93. }
  94. if o.ConsoleSocket != nil {
  95. out = append(out, "--console-socket", o.ConsoleSocket.Path())
  96. }
  97. if o.NoPivot {
  98. out = append(out, "--no-pivot")
  99. }
  100. if o.NoNewKeyring {
  101. out = append(out, "--no-new-keyring")
  102. }
  103. if o.Detach {
  104. out = append(out, "--detach")
  105. }
  106. if o.ExtraFiles != nil {
  107. out = append(out, "--preserve-fds", strconv.Itoa(len(o.ExtraFiles)))
  108. }
  109. return out, nil
  110. }
  111. // Create creates a new container and returns its pid if it was created successfully
  112. func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOpts) error {
  113. args := []string{"create", "--bundle", bundle}
  114. if opts != nil {
  115. oargs, err := opts.args()
  116. if err != nil {
  117. return err
  118. }
  119. args = append(args, oargs...)
  120. }
  121. cmd := r.command(context, append(args, id)...)
  122. if opts != nil && opts.IO != nil {
  123. opts.Set(cmd)
  124. }
  125. cmd.ExtraFiles = opts.ExtraFiles
  126. if cmd.Stdout == nil && cmd.Stderr == nil {
  127. data, err := cmdOutput(cmd, true, nil)
  128. defer putBuf(data)
  129. if err != nil {
  130. return fmt.Errorf("%s: %s", err, data.String())
  131. }
  132. return nil
  133. }
  134. ec, err := Monitor.Start(cmd)
  135. if err != nil {
  136. return err
  137. }
  138. if opts != nil && opts.IO != nil {
  139. if c, ok := opts.IO.(StartCloser); ok {
  140. if err := c.CloseAfterStart(); err != nil {
  141. return err
  142. }
  143. }
  144. }
  145. status, err := Monitor.Wait(cmd, ec)
  146. if err == nil && status != 0 {
  147. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  148. }
  149. return err
  150. }
  151. // Start will start an already created container
  152. func (r *Runc) Start(context context.Context, id string) error {
  153. return r.runOrError(r.command(context, "start", id))
  154. }
  155. type ExecOpts struct {
  156. IO
  157. PidFile string
  158. ConsoleSocket ConsoleSocket
  159. Detach bool
  160. Started chan<- int
  161. }
  162. func (o *ExecOpts) args() (out []string, err error) {
  163. if o.ConsoleSocket != nil {
  164. out = append(out, "--console-socket", o.ConsoleSocket.Path())
  165. }
  166. if o.Detach {
  167. out = append(out, "--detach")
  168. }
  169. if o.PidFile != "" {
  170. abs, err := filepath.Abs(o.PidFile)
  171. if err != nil {
  172. return nil, err
  173. }
  174. out = append(out, "--pid-file", abs)
  175. }
  176. return out, nil
  177. }
  178. // Exec executes an additional process inside the container based on a full
  179. // OCI Process specification
  180. func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
  181. if opts.Started != nil {
  182. defer close(opts.Started)
  183. }
  184. f, err := ioutil.TempFile(os.Getenv("XDG_RUNTIME_DIR"), "runc-process")
  185. if err != nil {
  186. return err
  187. }
  188. defer os.Remove(f.Name())
  189. err = json.NewEncoder(f).Encode(spec)
  190. f.Close()
  191. if err != nil {
  192. return err
  193. }
  194. args := []string{"exec", "--process", f.Name()}
  195. if opts != nil {
  196. oargs, err := opts.args()
  197. if err != nil {
  198. return err
  199. }
  200. args = append(args, oargs...)
  201. }
  202. cmd := r.command(context, append(args, id)...)
  203. if opts != nil && opts.IO != nil {
  204. opts.Set(cmd)
  205. }
  206. if cmd.Stdout == nil && cmd.Stderr == nil {
  207. data, err := cmdOutput(cmd, true, opts.Started)
  208. defer putBuf(data)
  209. if err != nil {
  210. return fmt.Errorf("%w: %s", err, data.String())
  211. }
  212. return nil
  213. }
  214. ec, err := Monitor.Start(cmd)
  215. if err != nil {
  216. return err
  217. }
  218. if opts.Started != nil {
  219. opts.Started <- cmd.Process.Pid
  220. }
  221. if opts != nil && opts.IO != nil {
  222. if c, ok := opts.IO.(StartCloser); ok {
  223. if err := c.CloseAfterStart(); err != nil {
  224. return err
  225. }
  226. }
  227. }
  228. status, err := Monitor.Wait(cmd, ec)
  229. if err == nil && status != 0 {
  230. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  231. }
  232. return err
  233. }
  234. // Run runs the create, start, delete lifecycle of the container
  235. // and returns its exit status after it has exited
  236. func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
  237. if opts.Started != nil {
  238. defer close(opts.Started)
  239. }
  240. args := []string{"run", "--bundle", bundle}
  241. if opts != nil {
  242. oargs, err := opts.args()
  243. if err != nil {
  244. return -1, err
  245. }
  246. args = append(args, oargs...)
  247. }
  248. cmd := r.command(context, append(args, id)...)
  249. if opts != nil && opts.IO != nil {
  250. opts.Set(cmd)
  251. }
  252. ec, err := Monitor.Start(cmd)
  253. if err != nil {
  254. return -1, err
  255. }
  256. if opts.Started != nil {
  257. opts.Started <- cmd.Process.Pid
  258. }
  259. status, err := Monitor.Wait(cmd, ec)
  260. if err == nil && status != 0 {
  261. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  262. }
  263. return status, err
  264. }
  265. type DeleteOpts struct {
  266. Force bool
  267. }
  268. func (o *DeleteOpts) args() (out []string) {
  269. if o.Force {
  270. out = append(out, "--force")
  271. }
  272. return out
  273. }
  274. // Delete deletes the container
  275. func (r *Runc) Delete(context context.Context, id string, opts *DeleteOpts) error {
  276. args := []string{"delete"}
  277. if opts != nil {
  278. args = append(args, opts.args()...)
  279. }
  280. return r.runOrError(r.command(context, append(args, id)...))
  281. }
  282. // KillOpts specifies options for killing a container and its processes
  283. type KillOpts struct {
  284. All bool
  285. }
  286. func (o *KillOpts) args() (out []string) {
  287. if o.All {
  288. out = append(out, "--all")
  289. }
  290. return out
  291. }
  292. // Kill sends the specified signal to the container
  293. func (r *Runc) Kill(context context.Context, id string, sig int, opts *KillOpts) error {
  294. args := []string{
  295. "kill",
  296. }
  297. if opts != nil {
  298. args = append(args, opts.args()...)
  299. }
  300. return r.runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...))
  301. }
  302. // Stats return the stats for a container like cpu, memory, and io
  303. func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
  304. cmd := r.command(context, "events", "--stats", id)
  305. rd, err := cmd.StdoutPipe()
  306. if err != nil {
  307. return nil, err
  308. }
  309. ec, err := Monitor.Start(cmd)
  310. if err != nil {
  311. return nil, err
  312. }
  313. defer func() {
  314. rd.Close()
  315. Monitor.Wait(cmd, ec)
  316. }()
  317. var e Event
  318. if err := json.NewDecoder(rd).Decode(&e); err != nil {
  319. return nil, err
  320. }
  321. return e.Stats, nil
  322. }
  323. // Events returns an event stream from runc for a container with stats and OOM notifications
  324. func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
  325. cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
  326. rd, err := cmd.StdoutPipe()
  327. if err != nil {
  328. return nil, err
  329. }
  330. ec, err := Monitor.Start(cmd)
  331. if err != nil {
  332. rd.Close()
  333. return nil, err
  334. }
  335. var (
  336. dec = json.NewDecoder(rd)
  337. c = make(chan *Event, 128)
  338. )
  339. go func() {
  340. defer func() {
  341. close(c)
  342. rd.Close()
  343. Monitor.Wait(cmd, ec)
  344. }()
  345. for {
  346. var e Event
  347. if err := dec.Decode(&e); err != nil {
  348. if err == io.EOF {
  349. return
  350. }
  351. e = Event{
  352. Type: "error",
  353. Err: err,
  354. }
  355. }
  356. c <- &e
  357. }
  358. }()
  359. return c, nil
  360. }
  361. // Pause the container with the provided id
  362. func (r *Runc) Pause(context context.Context, id string) error {
  363. return r.runOrError(r.command(context, "pause", id))
  364. }
  365. // Resume the container with the provided id
  366. func (r *Runc) Resume(context context.Context, id string) error {
  367. return r.runOrError(r.command(context, "resume", id))
  368. }
  369. // Ps lists all the processes inside the container returning their pids
  370. func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
  371. data, err := cmdOutput(r.command(context, "ps", "--format", "json", id), true, nil)
  372. defer putBuf(data)
  373. if err != nil {
  374. return nil, fmt.Errorf("%s: %s", err, data.String())
  375. }
  376. var pids []int
  377. if err := json.Unmarshal(data.Bytes(), &pids); err != nil {
  378. return nil, err
  379. }
  380. return pids, nil
  381. }
  382. // Top lists all the processes inside the container returning the full ps data
  383. func (r *Runc) Top(context context.Context, id string, psOptions string) (*TopResults, error) {
  384. data, err := cmdOutput(r.command(context, "ps", "--format", "table", id, psOptions), true, nil)
  385. defer putBuf(data)
  386. if err != nil {
  387. return nil, fmt.Errorf("%s: %s", err, data.String())
  388. }
  389. topResults, err := ParsePSOutput(data.Bytes())
  390. if err != nil {
  391. return nil, fmt.Errorf("%s: ", err)
  392. }
  393. return topResults, nil
  394. }
  395. type CheckpointOpts struct {
  396. // ImagePath is the path for saving the criu image file
  397. ImagePath string
  398. // WorkDir is the working directory for criu
  399. WorkDir string
  400. // ParentPath is the path for previous image files from a pre-dump
  401. ParentPath string
  402. // AllowOpenTCP allows open tcp connections to be checkpointed
  403. AllowOpenTCP bool
  404. // AllowExternalUnixSockets allows external unix sockets to be checkpointed
  405. AllowExternalUnixSockets bool
  406. // AllowTerminal allows the terminal(pty) to be checkpointed with a container
  407. AllowTerminal bool
  408. // CriuPageServer is the address:port for the criu page server
  409. CriuPageServer string
  410. // FileLocks handle file locks held by the container
  411. FileLocks bool
  412. // Cgroups is the cgroup mode for how to handle the checkpoint of a container's cgroups
  413. Cgroups CgroupMode
  414. // EmptyNamespaces creates a namespace for the container but does not save its properties
  415. // Provide the namespaces you wish to be checkpointed without their settings on restore
  416. EmptyNamespaces []string
  417. // LazyPages uses userfaultfd to lazily restore memory pages
  418. LazyPages bool
  419. // StatusFile is the file criu writes \0 to once lazy-pages is ready
  420. StatusFile *os.File
  421. }
  422. type CgroupMode string
  423. const (
  424. Soft CgroupMode = "soft"
  425. Full CgroupMode = "full"
  426. Strict CgroupMode = "strict"
  427. )
  428. func (o *CheckpointOpts) args() (out []string) {
  429. if o.ImagePath != "" {
  430. out = append(out, "--image-path", o.ImagePath)
  431. }
  432. if o.WorkDir != "" {
  433. out = append(out, "--work-path", o.WorkDir)
  434. }
  435. if o.ParentPath != "" {
  436. out = append(out, "--parent-path", o.ParentPath)
  437. }
  438. if o.AllowOpenTCP {
  439. out = append(out, "--tcp-established")
  440. }
  441. if o.AllowExternalUnixSockets {
  442. out = append(out, "--ext-unix-sk")
  443. }
  444. if o.AllowTerminal {
  445. out = append(out, "--shell-job")
  446. }
  447. if o.CriuPageServer != "" {
  448. out = append(out, "--page-server", o.CriuPageServer)
  449. }
  450. if o.FileLocks {
  451. out = append(out, "--file-locks")
  452. }
  453. if string(o.Cgroups) != "" {
  454. out = append(out, "--manage-cgroups-mode", string(o.Cgroups))
  455. }
  456. for _, ns := range o.EmptyNamespaces {
  457. out = append(out, "--empty-ns", ns)
  458. }
  459. if o.LazyPages {
  460. out = append(out, "--lazy-pages")
  461. }
  462. return out
  463. }
  464. type CheckpointAction func([]string) []string
  465. // LeaveRunning keeps the container running after the checkpoint has been completed
  466. func LeaveRunning(args []string) []string {
  467. return append(args, "--leave-running")
  468. }
  469. // PreDump allows a pre-dump of the checkpoint to be made and completed later
  470. func PreDump(args []string) []string {
  471. return append(args, "--pre-dump")
  472. }
  473. // Checkpoint allows you to checkpoint a container using criu
  474. func (r *Runc) Checkpoint(context context.Context, id string, opts *CheckpointOpts, actions ...CheckpointAction) error {
  475. args := []string{"checkpoint"}
  476. extraFiles := []*os.File{}
  477. if opts != nil {
  478. args = append(args, opts.args()...)
  479. if opts.StatusFile != nil {
  480. // pass the status file to the child process
  481. extraFiles = []*os.File{opts.StatusFile}
  482. // set status-fd to 3 as this will be the file descriptor
  483. // of the first file passed with cmd.ExtraFiles
  484. args = append(args, "--status-fd", "3")
  485. }
  486. }
  487. for _, a := range actions {
  488. args = a(args)
  489. }
  490. cmd := r.command(context, append(args, id)...)
  491. cmd.ExtraFiles = extraFiles
  492. return r.runOrError(cmd)
  493. }
  494. type RestoreOpts struct {
  495. CheckpointOpts
  496. IO
  497. Detach bool
  498. PidFile string
  499. NoSubreaper bool
  500. NoPivot bool
  501. ConsoleSocket ConsoleSocket
  502. }
  503. func (o *RestoreOpts) args() ([]string, error) {
  504. out := o.CheckpointOpts.args()
  505. if o.Detach {
  506. out = append(out, "--detach")
  507. }
  508. if o.PidFile != "" {
  509. abs, err := filepath.Abs(o.PidFile)
  510. if err != nil {
  511. return nil, err
  512. }
  513. out = append(out, "--pid-file", abs)
  514. }
  515. if o.ConsoleSocket != nil {
  516. out = append(out, "--console-socket", o.ConsoleSocket.Path())
  517. }
  518. if o.NoPivot {
  519. out = append(out, "--no-pivot")
  520. }
  521. if o.NoSubreaper {
  522. out = append(out, "-no-subreaper")
  523. }
  524. return out, nil
  525. }
  526. // Restore restores a container with the provide id from an existing checkpoint
  527. func (r *Runc) Restore(context context.Context, id, bundle string, opts *RestoreOpts) (int, error) {
  528. args := []string{"restore"}
  529. if opts != nil {
  530. oargs, err := opts.args()
  531. if err != nil {
  532. return -1, err
  533. }
  534. args = append(args, oargs...)
  535. }
  536. args = append(args, "--bundle", bundle)
  537. cmd := r.command(context, append(args, id)...)
  538. if opts != nil && opts.IO != nil {
  539. opts.Set(cmd)
  540. }
  541. ec, err := Monitor.Start(cmd)
  542. if err != nil {
  543. return -1, err
  544. }
  545. if opts != nil && opts.IO != nil {
  546. if c, ok := opts.IO.(StartCloser); ok {
  547. if err := c.CloseAfterStart(); err != nil {
  548. return -1, err
  549. }
  550. }
  551. }
  552. status, err := Monitor.Wait(cmd, ec)
  553. if err == nil && status != 0 {
  554. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  555. }
  556. return status, err
  557. }
  558. // Update updates the current container with the provided resource spec
  559. func (r *Runc) Update(context context.Context, id string, resources *specs.LinuxResources) error {
  560. buf := getBuf()
  561. defer putBuf(buf)
  562. if err := json.NewEncoder(buf).Encode(resources); err != nil {
  563. return err
  564. }
  565. args := []string{"update", "--resources", "-", id}
  566. cmd := r.command(context, args...)
  567. cmd.Stdin = buf
  568. return r.runOrError(cmd)
  569. }
  570. var ErrParseRuncVersion = errors.New("unable to parse runc version")
  571. type Version struct {
  572. Runc string
  573. Commit string
  574. Spec string
  575. }
  576. // Version returns the runc and runtime-spec versions
  577. func (r *Runc) Version(context context.Context) (Version, error) {
  578. data, err := cmdOutput(r.command(context, "--version"), false, nil)
  579. defer putBuf(data)
  580. if err != nil {
  581. return Version{}, err
  582. }
  583. return parseVersion(data.Bytes())
  584. }
  585. func parseVersion(data []byte) (Version, error) {
  586. var v Version
  587. parts := strings.Split(strings.TrimSpace(string(data)), "\n")
  588. if len(parts) > 0 {
  589. if !strings.HasPrefix(parts[0], "runc version ") {
  590. return v, nil
  591. }
  592. v.Runc = parts[0][13:]
  593. for _, part := range parts[1:] {
  594. if strings.HasPrefix(part, "commit: ") {
  595. v.Commit = part[8:]
  596. } else if strings.HasPrefix(part, "spec: ") {
  597. v.Spec = part[6:]
  598. }
  599. }
  600. }
  601. return v, nil
  602. }
  603. func (r *Runc) args() (out []string) {
  604. if r.Root != "" {
  605. out = append(out, "--root", r.Root)
  606. }
  607. if r.Debug {
  608. out = append(out, "--debug")
  609. }
  610. if r.Log != "" {
  611. out = append(out, "--log", r.Log)
  612. }
  613. if r.LogFormat != none {
  614. out = append(out, "--log-format", string(r.LogFormat))
  615. }
  616. if r.Criu != "" {
  617. out = append(out, "--criu", r.Criu)
  618. }
  619. if r.SystemdCgroup {
  620. out = append(out, "--systemd-cgroup")
  621. }
  622. if r.Rootless != nil {
  623. // nil stands for "auto" (differs from explicit "false")
  624. out = append(out, "--rootless="+strconv.FormatBool(*r.Rootless))
  625. }
  626. return out
  627. }
  628. // runOrError will run the provided command. If an error is
  629. // encountered and neither Stdout or Stderr was set the error and the
  630. // stderr of the command will be returned in the format of <error>:
  631. // <stderr>
  632. func (r *Runc) runOrError(cmd *exec.Cmd) error {
  633. if cmd.Stdout != nil || cmd.Stderr != nil {
  634. ec, err := Monitor.Start(cmd)
  635. if err != nil {
  636. return err
  637. }
  638. status, err := Monitor.Wait(cmd, ec)
  639. if err == nil && status != 0 {
  640. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  641. }
  642. return err
  643. }
  644. data, err := cmdOutput(cmd, true, nil)
  645. defer putBuf(data)
  646. if err != nil {
  647. return fmt.Errorf("%s: %s", err, data.String())
  648. }
  649. return nil
  650. }
  651. // callers of cmdOutput are expected to call putBuf on the returned Buffer
  652. // to ensure it is released back to the shared pool after use.
  653. func cmdOutput(cmd *exec.Cmd, combined bool, started chan<- int) (*bytes.Buffer, error) {
  654. b := getBuf()
  655. cmd.Stdout = b
  656. if combined {
  657. cmd.Stderr = b
  658. }
  659. ec, err := Monitor.Start(cmd)
  660. if err != nil {
  661. return nil, err
  662. }
  663. if started != nil {
  664. started <- cmd.Process.Pid
  665. }
  666. status, err := Monitor.Wait(cmd, ec)
  667. if err == nil && status != 0 {
  668. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  669. }
  670. return b, err
  671. }
  672. type ExitError struct {
  673. Status int
  674. }
  675. func (e *ExitError) Error() string {
  676. return fmt.Sprintf("exit status %d", e.Status)
  677. }