runc.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  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. "os"
  22. "os/exec"
  23. "path/filepath"
  24. "strconv"
  25. "strings"
  26. "syscall"
  27. "time"
  28. specs "github.com/opencontainers/runtime-spec/specs-go"
  29. "github.com/opencontainers/runtime-spec/specs-go/features"
  30. )
  31. // Format is the type of log formatting options available
  32. type Format string
  33. // TopResults represents the structured data of the full ps output
  34. type TopResults struct {
  35. // Processes running in the container, where each is process is an array of values corresponding to the headers
  36. Processes [][]string `json:"Processes"`
  37. // Headers are the names of the columns
  38. Headers []string `json:"Headers"`
  39. }
  40. const (
  41. none Format = ""
  42. // JSON represents the JSON format
  43. JSON Format = "json"
  44. // Text represents plain text format
  45. Text Format = "text"
  46. )
  47. // DefaultCommand is the default command for Runc
  48. var DefaultCommand = "runc"
  49. // Runc is the client to the runc cli
  50. type Runc struct {
  51. // Command overrides the name of the runc binary. If empty, DefaultCommand
  52. // is used.
  53. Command string
  54. Root string
  55. Debug bool
  56. Log string
  57. LogFormat Format
  58. // PdeathSignal sets a signal the child process will receive when the
  59. // parent dies.
  60. //
  61. // When Pdeathsig is set, command invocations will call runtime.LockOSThread
  62. // to prevent OS thread termination from spuriously triggering the
  63. // signal. See https://github.com/golang/go/issues/27505 and
  64. // https://github.com/golang/go/blob/126c22a09824a7b52c019ed9a1d198b4e7781676/src/syscall/exec_linux.go#L48-L51
  65. //
  66. // A program with GOMAXPROCS=1 might hang because of the use of
  67. // runtime.LockOSThread. Callers should ensure they retain at least one
  68. // unlocked thread.
  69. PdeathSignal syscall.Signal // using syscall.Signal to allow compilation on non-unix (unix.Syscall is an alias for syscall.Signal)
  70. Setpgid bool
  71. // Criu sets the path to the criu binary used for checkpoint and restore.
  72. //
  73. // Deprecated: runc option --criu is now ignored (with a warning), and the
  74. // option will be removed entirely in a future release. Users who need a non-
  75. // standard criu binary should rely on the standard way of looking up binaries
  76. // in $PATH.
  77. Criu string
  78. SystemdCgroup bool
  79. Rootless *bool // nil stands for "auto"
  80. ExtraArgs []string
  81. }
  82. // List returns all containers created inside the provided runc root directory
  83. func (r *Runc) List(context context.Context) ([]*Container, error) {
  84. data, err := r.cmdOutput(r.command(context, "list", "--format=json"), false, nil)
  85. defer putBuf(data)
  86. if err != nil {
  87. return nil, err
  88. }
  89. var out []*Container
  90. if err := json.Unmarshal(data.Bytes(), &out); err != nil {
  91. return nil, err
  92. }
  93. return out, nil
  94. }
  95. // State returns the state for the container provided by id
  96. func (r *Runc) State(context context.Context, id string) (*Container, error) {
  97. data, err := r.cmdOutput(r.command(context, "state", id), true, nil)
  98. defer putBuf(data)
  99. if err != nil {
  100. return nil, fmt.Errorf("%s: %s", err, data.String())
  101. }
  102. var c Container
  103. if err := json.Unmarshal(data.Bytes(), &c); err != nil {
  104. return nil, err
  105. }
  106. return &c, nil
  107. }
  108. // ConsoleSocket handles the path of the socket for console access
  109. type ConsoleSocket interface {
  110. Path() string
  111. }
  112. // CreateOpts holds all the options information for calling runc with supported options
  113. type CreateOpts struct {
  114. IO
  115. // PidFile is a path to where a pid file should be created
  116. PidFile string
  117. ConsoleSocket ConsoleSocket
  118. Detach bool
  119. NoPivot bool
  120. NoNewKeyring bool
  121. ExtraFiles []*os.File
  122. Started chan<- int
  123. ExtraArgs []string
  124. }
  125. func (o *CreateOpts) args() (out []string, err error) {
  126. if o.PidFile != "" {
  127. abs, err := filepath.Abs(o.PidFile)
  128. if err != nil {
  129. return nil, err
  130. }
  131. out = append(out, "--pid-file", abs)
  132. }
  133. if o.ConsoleSocket != nil {
  134. out = append(out, "--console-socket", o.ConsoleSocket.Path())
  135. }
  136. if o.NoPivot {
  137. out = append(out, "--no-pivot")
  138. }
  139. if o.NoNewKeyring {
  140. out = append(out, "--no-new-keyring")
  141. }
  142. if o.Detach {
  143. out = append(out, "--detach")
  144. }
  145. if o.ExtraFiles != nil {
  146. out = append(out, "--preserve-fds", strconv.Itoa(len(o.ExtraFiles)))
  147. }
  148. if len(o.ExtraArgs) > 0 {
  149. out = append(out, o.ExtraArgs...)
  150. }
  151. return out, nil
  152. }
  153. func (r *Runc) startCommand(cmd *exec.Cmd) (chan Exit, error) {
  154. if r.PdeathSignal != 0 {
  155. return Monitor.StartLocked(cmd)
  156. }
  157. return Monitor.Start(cmd)
  158. }
  159. // Create creates a new container and returns its pid if it was created successfully
  160. func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOpts) error {
  161. args := []string{"create", "--bundle", bundle}
  162. if opts == nil {
  163. opts = &CreateOpts{}
  164. }
  165. oargs, err := opts.args()
  166. if err != nil {
  167. return err
  168. }
  169. args = append(args, oargs...)
  170. cmd := r.command(context, append(args, id)...)
  171. if opts.IO != nil {
  172. opts.Set(cmd)
  173. }
  174. cmd.ExtraFiles = opts.ExtraFiles
  175. if cmd.Stdout == nil && cmd.Stderr == nil {
  176. data, err := r.cmdOutput(cmd, true, nil)
  177. defer putBuf(data)
  178. if err != nil {
  179. return fmt.Errorf("%s: %s", err, data.String())
  180. }
  181. return nil
  182. }
  183. ec, err := r.startCommand(cmd)
  184. if err != nil {
  185. return err
  186. }
  187. if opts.IO != nil {
  188. if c, ok := opts.IO.(StartCloser); ok {
  189. if err := c.CloseAfterStart(); err != nil {
  190. return err
  191. }
  192. }
  193. }
  194. status, err := Monitor.Wait(cmd, ec)
  195. if err == nil && status != 0 {
  196. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  197. }
  198. return err
  199. }
  200. // Start will start an already created container
  201. func (r *Runc) Start(context context.Context, id string) error {
  202. return r.runOrError(r.command(context, "start", id))
  203. }
  204. // ExecOpts holds optional settings when starting an exec process with runc
  205. type ExecOpts struct {
  206. IO
  207. PidFile string
  208. ConsoleSocket ConsoleSocket
  209. Detach bool
  210. Started chan<- int
  211. ExtraArgs []string
  212. }
  213. func (o *ExecOpts) args() (out []string, err error) {
  214. if o.ConsoleSocket != nil {
  215. out = append(out, "--console-socket", o.ConsoleSocket.Path())
  216. }
  217. if o.Detach {
  218. out = append(out, "--detach")
  219. }
  220. if o.PidFile != "" {
  221. abs, err := filepath.Abs(o.PidFile)
  222. if err != nil {
  223. return nil, err
  224. }
  225. out = append(out, "--pid-file", abs)
  226. }
  227. if len(o.ExtraArgs) > 0 {
  228. out = append(out, o.ExtraArgs...)
  229. }
  230. return out, nil
  231. }
  232. // Exec executes an additional process inside the container based on a full
  233. // OCI Process specification
  234. func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
  235. if opts == nil {
  236. opts = &ExecOpts{}
  237. }
  238. if opts.Started != nil {
  239. defer close(opts.Started)
  240. }
  241. f, err := os.CreateTemp(os.Getenv("XDG_RUNTIME_DIR"), "runc-process")
  242. if err != nil {
  243. return err
  244. }
  245. defer os.Remove(f.Name())
  246. err = json.NewEncoder(f).Encode(spec)
  247. f.Close()
  248. if err != nil {
  249. return err
  250. }
  251. args := []string{"exec", "--process", f.Name()}
  252. oargs, err := opts.args()
  253. if err != nil {
  254. return err
  255. }
  256. args = append(args, oargs...)
  257. cmd := r.command(context, append(args, id)...)
  258. if opts.IO != nil {
  259. opts.Set(cmd)
  260. }
  261. if cmd.Stdout == nil && cmd.Stderr == nil {
  262. data, err := r.cmdOutput(cmd, true, opts.Started)
  263. defer putBuf(data)
  264. if err != nil {
  265. return fmt.Errorf("%w: %s", err, data.String())
  266. }
  267. return nil
  268. }
  269. ec, err := r.startCommand(cmd)
  270. if err != nil {
  271. return err
  272. }
  273. if opts.Started != nil {
  274. opts.Started <- cmd.Process.Pid
  275. }
  276. if opts.IO != nil {
  277. if c, ok := opts.IO.(StartCloser); ok {
  278. if err := c.CloseAfterStart(); err != nil {
  279. return err
  280. }
  281. }
  282. }
  283. status, err := Monitor.Wait(cmd, ec)
  284. if err == nil && status != 0 {
  285. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  286. }
  287. return err
  288. }
  289. // Run runs the create, start, delete lifecycle of the container
  290. // and returns its exit status after it has exited
  291. func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
  292. if opts == nil {
  293. opts = &CreateOpts{}
  294. }
  295. if opts.Started != nil {
  296. defer close(opts.Started)
  297. }
  298. args := []string{"run", "--bundle", bundle}
  299. oargs, err := opts.args()
  300. if err != nil {
  301. return -1, err
  302. }
  303. args = append(args, oargs...)
  304. cmd := r.command(context, append(args, id)...)
  305. if opts.IO != nil {
  306. opts.Set(cmd)
  307. }
  308. cmd.ExtraFiles = opts.ExtraFiles
  309. ec, err := r.startCommand(cmd)
  310. if err != nil {
  311. return -1, err
  312. }
  313. if opts.Started != nil {
  314. opts.Started <- cmd.Process.Pid
  315. }
  316. status, err := Monitor.Wait(cmd, ec)
  317. if err == nil && status != 0 {
  318. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  319. }
  320. return status, err
  321. }
  322. // DeleteOpts holds the deletion options for calling `runc delete`
  323. type DeleteOpts struct {
  324. Force bool
  325. ExtraArgs []string
  326. }
  327. func (o *DeleteOpts) args() (out []string) {
  328. if o.Force {
  329. out = append(out, "--force")
  330. }
  331. if len(o.ExtraArgs) > 0 {
  332. out = append(out, o.ExtraArgs...)
  333. }
  334. return out
  335. }
  336. // Delete deletes the container
  337. func (r *Runc) Delete(context context.Context, id string, opts *DeleteOpts) error {
  338. args := []string{"delete"}
  339. if opts != nil {
  340. args = append(args, opts.args()...)
  341. }
  342. return r.runOrError(r.command(context, append(args, id)...))
  343. }
  344. // KillOpts specifies options for killing a container and its processes
  345. type KillOpts struct {
  346. All bool
  347. ExtraArgs []string
  348. }
  349. func (o *KillOpts) args() (out []string) {
  350. if o.All {
  351. out = append(out, "--all")
  352. }
  353. if len(o.ExtraArgs) > 0 {
  354. out = append(out, o.ExtraArgs...)
  355. }
  356. return out
  357. }
  358. // Kill sends the specified signal to the container
  359. func (r *Runc) Kill(context context.Context, id string, sig int, opts *KillOpts) error {
  360. args := []string{
  361. "kill",
  362. }
  363. if opts != nil {
  364. args = append(args, opts.args()...)
  365. }
  366. return r.runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...))
  367. }
  368. // Stats return the stats for a container like cpu, memory, and io
  369. func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
  370. cmd := r.command(context, "events", "--stats", id)
  371. rd, err := cmd.StdoutPipe()
  372. if err != nil {
  373. return nil, err
  374. }
  375. ec, err := r.startCommand(cmd)
  376. if err != nil {
  377. return nil, err
  378. }
  379. defer func() {
  380. rd.Close()
  381. Monitor.Wait(cmd, ec)
  382. }()
  383. var e Event
  384. if err := json.NewDecoder(rd).Decode(&e); err != nil {
  385. return nil, err
  386. }
  387. return e.Stats, nil
  388. }
  389. // Events returns an event stream from runc for a container with stats and OOM notifications
  390. func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
  391. cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
  392. rd, err := cmd.StdoutPipe()
  393. if err != nil {
  394. return nil, err
  395. }
  396. ec, err := r.startCommand(cmd)
  397. if err != nil {
  398. rd.Close()
  399. return nil, err
  400. }
  401. var (
  402. dec = json.NewDecoder(rd)
  403. c = make(chan *Event, 128)
  404. )
  405. go func() {
  406. defer func() {
  407. close(c)
  408. rd.Close()
  409. Monitor.Wait(cmd, ec)
  410. }()
  411. for {
  412. var e Event
  413. if err := dec.Decode(&e); err != nil {
  414. if err == io.EOF {
  415. return
  416. }
  417. e = Event{
  418. Type: "error",
  419. Err: err,
  420. }
  421. }
  422. c <- &e
  423. }
  424. }()
  425. return c, nil
  426. }
  427. // Pause the container with the provided id
  428. func (r *Runc) Pause(context context.Context, id string) error {
  429. return r.runOrError(r.command(context, "pause", id))
  430. }
  431. // Resume the container with the provided id
  432. func (r *Runc) Resume(context context.Context, id string) error {
  433. return r.runOrError(r.command(context, "resume", id))
  434. }
  435. // Ps lists all the processes inside the container returning their pids
  436. func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
  437. data, err := r.cmdOutput(r.command(context, "ps", "--format", "json", id), true, nil)
  438. defer putBuf(data)
  439. if err != nil {
  440. return nil, fmt.Errorf("%s: %s", err, data.String())
  441. }
  442. var pids []int
  443. if err := json.Unmarshal(data.Bytes(), &pids); err != nil {
  444. return nil, err
  445. }
  446. return pids, nil
  447. }
  448. // Top lists all the processes inside the container returning the full ps data
  449. func (r *Runc) Top(context context.Context, id string, psOptions string) (*TopResults, error) {
  450. data, err := r.cmdOutput(r.command(context, "ps", "--format", "table", id, psOptions), true, nil)
  451. defer putBuf(data)
  452. if err != nil {
  453. return nil, fmt.Errorf("%s: %s", err, data.String())
  454. }
  455. topResults, err := ParsePSOutput(data.Bytes())
  456. if err != nil {
  457. return nil, fmt.Errorf("%s: ", err)
  458. }
  459. return topResults, nil
  460. }
  461. // CheckpointOpts holds the options for performing a criu checkpoint using runc
  462. type CheckpointOpts struct {
  463. // ImagePath is the path for saving the criu image file
  464. ImagePath string
  465. // WorkDir is the working directory for criu
  466. WorkDir string
  467. // ParentPath is the path for previous image files from a pre-dump
  468. ParentPath string
  469. // AllowOpenTCP allows open tcp connections to be checkpointed
  470. AllowOpenTCP bool
  471. // AllowExternalUnixSockets allows external unix sockets to be checkpointed
  472. AllowExternalUnixSockets bool
  473. // AllowTerminal allows the terminal(pty) to be checkpointed with a container
  474. AllowTerminal bool
  475. // CriuPageServer is the address:port for the criu page server
  476. CriuPageServer string
  477. // FileLocks handle file locks held by the container
  478. FileLocks bool
  479. // Cgroups is the cgroup mode for how to handle the checkpoint of a container's cgroups
  480. Cgroups CgroupMode
  481. // EmptyNamespaces creates a namespace for the container but does not save its properties
  482. // Provide the namespaces you wish to be checkpointed without their settings on restore
  483. EmptyNamespaces []string
  484. // LazyPages uses userfaultfd to lazily restore memory pages
  485. LazyPages bool
  486. // StatusFile is the file criu writes \0 to once lazy-pages is ready
  487. StatusFile *os.File
  488. ExtraArgs []string
  489. }
  490. // CgroupMode defines the cgroup mode used for checkpointing
  491. type CgroupMode string
  492. const (
  493. // Soft is the "soft" cgroup mode
  494. Soft CgroupMode = "soft"
  495. // Full is the "full" cgroup mode
  496. Full CgroupMode = "full"
  497. // Strict is the "strict" cgroup mode
  498. Strict CgroupMode = "strict"
  499. )
  500. func (o *CheckpointOpts) args() (out []string) {
  501. if o.ImagePath != "" {
  502. out = append(out, "--image-path", o.ImagePath)
  503. }
  504. if o.WorkDir != "" {
  505. out = append(out, "--work-path", o.WorkDir)
  506. }
  507. if o.ParentPath != "" {
  508. out = append(out, "--parent-path", o.ParentPath)
  509. }
  510. if o.AllowOpenTCP {
  511. out = append(out, "--tcp-established")
  512. }
  513. if o.AllowExternalUnixSockets {
  514. out = append(out, "--ext-unix-sk")
  515. }
  516. if o.AllowTerminal {
  517. out = append(out, "--shell-job")
  518. }
  519. if o.CriuPageServer != "" {
  520. out = append(out, "--page-server", o.CriuPageServer)
  521. }
  522. if o.FileLocks {
  523. out = append(out, "--file-locks")
  524. }
  525. if string(o.Cgroups) != "" {
  526. out = append(out, "--manage-cgroups-mode", string(o.Cgroups))
  527. }
  528. for _, ns := range o.EmptyNamespaces {
  529. out = append(out, "--empty-ns", ns)
  530. }
  531. if o.LazyPages {
  532. out = append(out, "--lazy-pages")
  533. }
  534. if len(o.ExtraArgs) > 0 {
  535. out = append(out, o.ExtraArgs...)
  536. }
  537. return out
  538. }
  539. // CheckpointAction represents specific actions executed during checkpoint/restore
  540. type CheckpointAction func([]string) []string
  541. // LeaveRunning keeps the container running after the checkpoint has been completed
  542. func LeaveRunning(args []string) []string {
  543. return append(args, "--leave-running")
  544. }
  545. // PreDump allows a pre-dump of the checkpoint to be made and completed later
  546. func PreDump(args []string) []string {
  547. return append(args, "--pre-dump")
  548. }
  549. // Checkpoint allows you to checkpoint a container using criu
  550. func (r *Runc) Checkpoint(context context.Context, id string, opts *CheckpointOpts, actions ...CheckpointAction) error {
  551. args := []string{"checkpoint"}
  552. extraFiles := []*os.File{}
  553. if opts != nil {
  554. args = append(args, opts.args()...)
  555. if opts.StatusFile != nil {
  556. // pass the status file to the child process
  557. extraFiles = []*os.File{opts.StatusFile}
  558. // set status-fd to 3 as this will be the file descriptor
  559. // of the first file passed with cmd.ExtraFiles
  560. args = append(args, "--status-fd", "3")
  561. }
  562. }
  563. for _, a := range actions {
  564. args = a(args)
  565. }
  566. cmd := r.command(context, append(args, id)...)
  567. cmd.ExtraFiles = extraFiles
  568. return r.runOrError(cmd)
  569. }
  570. // RestoreOpts holds the options for performing a criu restore using runc
  571. type RestoreOpts struct {
  572. CheckpointOpts
  573. IO
  574. Detach bool
  575. PidFile string
  576. NoSubreaper bool
  577. NoPivot bool
  578. ConsoleSocket ConsoleSocket
  579. ExtraArgs []string
  580. }
  581. func (o *RestoreOpts) args() ([]string, error) {
  582. out := o.CheckpointOpts.args()
  583. if o.Detach {
  584. out = append(out, "--detach")
  585. }
  586. if o.PidFile != "" {
  587. abs, err := filepath.Abs(o.PidFile)
  588. if err != nil {
  589. return nil, err
  590. }
  591. out = append(out, "--pid-file", abs)
  592. }
  593. if o.ConsoleSocket != nil {
  594. out = append(out, "--console-socket", o.ConsoleSocket.Path())
  595. }
  596. if o.NoPivot {
  597. out = append(out, "--no-pivot")
  598. }
  599. if o.NoSubreaper {
  600. out = append(out, "-no-subreaper")
  601. }
  602. if len(o.ExtraArgs) > 0 {
  603. out = append(out, o.ExtraArgs...)
  604. }
  605. return out, nil
  606. }
  607. // Restore restores a container with the provide id from an existing checkpoint
  608. func (r *Runc) Restore(context context.Context, id, bundle string, opts *RestoreOpts) (int, error) {
  609. args := []string{"restore"}
  610. if opts != nil {
  611. oargs, err := opts.args()
  612. if err != nil {
  613. return -1, err
  614. }
  615. args = append(args, oargs...)
  616. }
  617. args = append(args, "--bundle", bundle)
  618. cmd := r.command(context, append(args, id)...)
  619. if opts != nil && opts.IO != nil {
  620. opts.Set(cmd)
  621. }
  622. ec, err := r.startCommand(cmd)
  623. if err != nil {
  624. return -1, err
  625. }
  626. if opts != nil && opts.IO != nil {
  627. if c, ok := opts.IO.(StartCloser); ok {
  628. if err := c.CloseAfterStart(); err != nil {
  629. return -1, err
  630. }
  631. }
  632. }
  633. status, err := Monitor.Wait(cmd, ec)
  634. if err == nil && status != 0 {
  635. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  636. }
  637. return status, err
  638. }
  639. // Update updates the current container with the provided resource spec
  640. func (r *Runc) Update(context context.Context, id string, resources *specs.LinuxResources) error {
  641. buf := getBuf()
  642. defer putBuf(buf)
  643. if err := json.NewEncoder(buf).Encode(resources); err != nil {
  644. return err
  645. }
  646. args := []string{"update", "--resources=-", id}
  647. cmd := r.command(context, args...)
  648. cmd.Stdin = buf
  649. return r.runOrError(cmd)
  650. }
  651. // ErrParseRuncVersion is used when the runc version can't be parsed
  652. var ErrParseRuncVersion = errors.New("unable to parse runc version")
  653. // Version represents the runc version information
  654. type Version struct {
  655. Runc string
  656. Commit string
  657. Spec string
  658. }
  659. // Version returns the runc and runtime-spec versions
  660. func (r *Runc) Version(context context.Context) (Version, error) {
  661. data, err := r.cmdOutput(r.command(context, "--version"), false, nil)
  662. defer putBuf(data)
  663. if err != nil {
  664. return Version{}, err
  665. }
  666. return parseVersion(data.Bytes())
  667. }
  668. func parseVersion(data []byte) (Version, error) {
  669. var v Version
  670. parts := strings.Split(strings.TrimSpace(string(data)), "\n")
  671. if len(parts) > 0 {
  672. if !strings.HasPrefix(parts[0], "runc version ") {
  673. return v, nil
  674. }
  675. v.Runc = parts[0][13:]
  676. for _, part := range parts[1:] {
  677. if strings.HasPrefix(part, "commit: ") {
  678. v.Commit = part[8:]
  679. } else if strings.HasPrefix(part, "spec: ") {
  680. v.Spec = part[6:]
  681. }
  682. }
  683. }
  684. return v, nil
  685. }
  686. // Features shows the features implemented by the runtime.
  687. //
  688. // Availability:
  689. //
  690. // - runc: supported since runc v1.1.0
  691. // - crun: https://github.com/containers/crun/issues/1177
  692. // - youki: https://github.com/containers/youki/issues/815
  693. func (r *Runc) Features(context context.Context) (*features.Features, error) {
  694. data, err := r.cmdOutput(r.command(context, "features"), false, nil)
  695. defer putBuf(data)
  696. if err != nil {
  697. return nil, err
  698. }
  699. var feat features.Features
  700. if err := json.Unmarshal(data.Bytes(), &feat); err != nil {
  701. return nil, err
  702. }
  703. return &feat, nil
  704. }
  705. func (r *Runc) args() (out []string) {
  706. if r.Root != "" {
  707. out = append(out, "--root", r.Root)
  708. }
  709. if r.Debug {
  710. out = append(out, "--debug")
  711. }
  712. if r.Log != "" {
  713. out = append(out, "--log", r.Log)
  714. }
  715. if r.LogFormat != none {
  716. out = append(out, "--log-format", string(r.LogFormat))
  717. }
  718. if r.SystemdCgroup {
  719. out = append(out, "--systemd-cgroup")
  720. }
  721. if r.Rootless != nil {
  722. // nil stands for "auto" (differs from explicit "false")
  723. out = append(out, "--rootless="+strconv.FormatBool(*r.Rootless))
  724. }
  725. if len(r.ExtraArgs) > 0 {
  726. out = append(out, r.ExtraArgs...)
  727. }
  728. return out
  729. }
  730. // runOrError will run the provided command. If an error is
  731. // encountered and neither Stdout or Stderr was set the error and the
  732. // stderr of the command will be returned in the format of <error>:
  733. // <stderr>
  734. func (r *Runc) runOrError(cmd *exec.Cmd) error {
  735. if cmd.Stdout != nil || cmd.Stderr != nil {
  736. ec, err := r.startCommand(cmd)
  737. if err != nil {
  738. return err
  739. }
  740. status, err := Monitor.Wait(cmd, ec)
  741. if err == nil && status != 0 {
  742. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  743. }
  744. return err
  745. }
  746. data, err := r.cmdOutput(cmd, true, nil)
  747. defer putBuf(data)
  748. if err != nil {
  749. return fmt.Errorf("%s: %s", err, data.String())
  750. }
  751. return nil
  752. }
  753. // callers of cmdOutput are expected to call putBuf on the returned Buffer
  754. // to ensure it is released back to the shared pool after use.
  755. func (r *Runc) cmdOutput(cmd *exec.Cmd, combined bool, started chan<- int) (*bytes.Buffer, error) {
  756. b := getBuf()
  757. cmd.Stdout = b
  758. if combined {
  759. cmd.Stderr = b
  760. }
  761. ec, err := r.startCommand(cmd)
  762. if err != nil {
  763. return nil, err
  764. }
  765. if started != nil {
  766. started <- cmd.Process.Pid
  767. }
  768. status, err := Monitor.Wait(cmd, ec)
  769. if err == nil && status != 0 {
  770. err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
  771. }
  772. return b, err
  773. }
  774. // ExitError holds the status return code when a process exits with an error code
  775. type ExitError struct {
  776. Status int
  777. }
  778. func (e *ExitError) Error() string {
  779. return fmt.Sprintf("exit status %d", e.Status)
  780. }