@@ -320,80 +320,242 @@ func (r *runtimeOCI) ExecSyncContainer(ctx context.Context, c *Container, comman
320320 return nil , nil
321321 }
322322
323- processFile , err := prepareProcessExec ( c , command , c . terminal )
323+ pidFile , parentPipe , childPipe , err := prepareExec ( )
324324 if err != nil {
325325 return nil , & ExecSyncError {
326326 ExitCode : - 1 ,
327327 Err : err ,
328328 }
329329 }
330- defer os .RemoveAll (processFile )
330+ defer parentPipe .Close ()
331+ defer func () {
332+ if e := os .Remove (pidFile ); e != nil {
333+ log .Warnf (ctx , "Could not remove temporary PID file %s" , pidFile )
334+ }
335+ }()
331336
332- pidDir , err := ioutil .TempDir ("" , "pidfile" )
337+ logFile , err := ioutil .TempFile ("" , "crio-log-" + c . id )
333338 if err != nil {
334- return nil , err
339+ return nil , & ExecSyncError {
340+ ExitCode : - 1 ,
341+ Err : err ,
342+ }
335343 }
336- defer os . RemoveAll ( pidDir )
344+ logFile . Close ( )
337345
338- pidFile := filepath .Join (pidDir , c .id )
346+ logPath := logFile .Name ()
347+ defer func () {
348+ os .RemoveAll (logPath )
349+ }()
339350
340- cmd := r .constructExecCommand (ctx , c , processFile , pidFile )
341- cmd .SysProcAttr = sysProcAttrPlatform ()
351+ args := []string {
352+ "-c" , c .id ,
353+ "-n" , c .name ,
354+ "-r" , r .path ,
355+ "-p" , pidFile ,
356+ "-e" ,
357+ "-l" , logPath ,
358+ "--socket-dir-path" , r .config .ContainerAttachSocketDir ,
359+ "--log-level" , logrus .GetLevel ().String (),
360+ }
361+
362+ if r .config .ConmonSupportsSync () {
363+ args = append (args , "--sync" )
364+ }
365+ if c .terminal {
366+ args = append (args , "-t" )
367+ }
368+ if timeout > 0 {
369+ args = append (args , "-T" , fmt .Sprintf ("%d" , timeout ))
370+ }
371+
372+ processFile , err := prepareProcessExec (c , command , c .terminal )
373+ if err != nil {
374+ return nil , & ExecSyncError {
375+ ExitCode : - 1 ,
376+ Err : err ,
377+ }
378+ }
379+ defer os .RemoveAll (processFile )
380+
381+ args = append (args ,
382+ "--exec-process-spec" , processFile ,
383+ "--runtime-arg" , fmt .Sprintf ("%s=%s" , rootFlag , r .root ))
384+
385+ cmd := exec .Command (r .config .Conmon , args ... ) // nolint: gosec
342386
343387 var stdoutBuf , stderrBuf bytes.Buffer
344388 cmd .Stdout = & stdoutBuf
345389 cmd .Stderr = & stderrBuf
390+ cmd .ExtraFiles = append (cmd .ExtraFiles , childPipe )
391+ // 0, 1 and 2 are stdin, stdout and stderr
392+ cmd .Env = r .config .ConmonEnv
393+ cmd .Env = append (cmd .Env , fmt .Sprintf ("_OCI_SYNCPIPE=%d" , 3 ))
394+ if v , found := os .LookupEnv ("XDG_RUNTIME_DIR" ); found {
395+ cmd .Env = append (cmd .Env , fmt .Sprintf ("XDG_RUNTIME_DIR=%s" , v ))
396+ }
346397
347398 err = cmd .Start ()
348399 if err != nil {
349- return nil , err
400+ childPipe .Close ()
401+ return nil , & ExecSyncError {
402+ Stdout : stdoutBuf ,
403+ Stderr : stderrBuf ,
404+ ExitCode : - 1 ,
405+ Err : err ,
406+ }
350407 }
351408
352- // wait till the command is done
353- done := make (chan error , 1 )
354- go func () {
355- done <- cmd .Wait ()
356- }()
357-
358- if timeout > 0 {
359- select {
360- case <- time .After (time .Second * time .Duration (timeout )):
361- // Ensure the process is not left behind
362- killContainerExecProcess (ctx , pidFile , cmd )
363-
364- // Make sure the runtime process has been cleaned up
365- <- done
366-
367- // If the command timed out, we should return an ExecSyncResponse with a non-zero exit code because
368- // the prober code in the kubelet checks for it. If we return a custom error,
369- // then the probes transition into Unknown status and the container isn't restarted as expected.
409+ // We don't need childPipe on the parent side
410+ childPipe .Close ()
370411
412+ // first, wait till the command is done
413+ waitErr := cmd .Wait ()
414+
415+ // regardless of what is in waitErr
416+ // we should attempt to decode the output of the parent pipe
417+ // this allows us to catch TimedOutMessage, which will cause waitErr to not be nil
418+ var ec * exitCodeInfo
419+ decodeErr := json .NewDecoder (parentPipe ).Decode (& ec )
420+ if decodeErr == nil {
421+ log .Debugf (ctx , "Received container exit code: %v, message: %s" , ec .ExitCode , ec .Message )
422+
423+ // When we timeout the command in conmon then we should return
424+ // an ExecSyncResponse with a non-zero exit code because
425+ // the prober code in the kubelet checks for it. If we return
426+ // a custom error, then the probes transition into Unknown status
427+ // and the container isn't restarted as expected.
428+ if ec .ExitCode == - 1 && ec .Message == conmonconfig .TimedOutMessage {
371429 return & ExecSyncResponse {
372430 Stderr : []byte (conmonconfig .TimedOutMessage ),
373431 ExitCode : - 1 ,
374432 }, nil
375- case err = <- done :
376- break
377433 }
378- } else {
379- err = <- done
380434 }
381435
382- // gather exit code from err
383- exitCode := int32 (0 )
436+ if waitErr != nil {
437+ // if we aren't a ExitError, some I/O problems probably occurred
438+ if _ , ok := waitErr .(* exec.ExitError ); ! ok {
439+ return nil , & ExecSyncError {
440+ Stdout : stdoutBuf ,
441+ Stderr : stderrBuf ,
442+ ExitCode : - 1 ,
443+ Err : waitErr ,
444+ }
445+ }
446+ }
447+
448+ if decodeErr != nil {
449+ return nil , & ExecSyncError {
450+ Stdout : stdoutBuf ,
451+ Stderr : stderrBuf ,
452+ ExitCode : - 1 ,
453+ Err : decodeErr ,
454+ }
455+ }
456+
457+ if ec .ExitCode == - 1 {
458+ return nil , & ExecSyncError {
459+ Stdout : stdoutBuf ,
460+ Stderr : stderrBuf ,
461+ ExitCode : - 1 ,
462+ Err : fmt .Errorf (ec .Message ),
463+ }
464+ }
465+
466+ // The actual logged output is not the same as stdoutBuf and stderrBuf,
467+ // which are used for getting error information. For the actual
468+ // ExecSyncResponse we have to read the logfile.
469+ // XXX: Currently runC dups the same console over both stdout and stderr,
470+ // so we can't differentiate between the two.
471+ logBytes , err := ioutil .ReadFile (logPath )
384472 if err != nil {
385- if exitError , ok := err .(* exec.ExitError ); ok {
386- exitCode = int32 (exitError .ExitCode ())
473+ return nil , & ExecSyncError {
474+ Stdout : stdoutBuf ,
475+ Stderr : stderrBuf ,
476+ ExitCode : - 1 ,
477+ Err : err ,
387478 }
388479 }
389480
481+ // We have to parse the log output into {stdout, stderr} buffers.
482+ stdoutBytes , stderrBytes := parseLog (ctx , logBytes )
390483 return & ExecSyncResponse {
391- Stdout : stdoutBuf . Bytes () ,
392- Stderr : stderrBuf . Bytes () ,
393- ExitCode : exitCode ,
484+ Stdout : stdoutBytes ,
485+ Stderr : stderrBytes ,
486+ ExitCode : ec . ExitCode ,
394487 }, nil
395488}
396489
490+ // exitCodeInfo is used to return the monitored process exit code to the daemon
491+ type exitCodeInfo struct {
492+ ExitCode int32 `json:"exit_code"`
493+ Message string `json:"message,omitempty"`
494+ }
495+
496+ func parseLog (ctx context.Context , l []byte ) (stdout , stderr []byte ) {
497+ // Split the log on newlines, which is what separates entries.
498+ lines := bytes .SplitAfter (l , []byte {'\n' })
499+ for _ , line := range lines {
500+ // Ignore empty lines.
501+ if len (line ) == 0 {
502+ continue
503+ }
504+
505+ // The format of log lines is "DATE pipe LogTag REST".
506+ parts := bytes .SplitN (line , []byte {' ' }, 4 )
507+ if len (parts ) < 4 {
508+ // Ignore the line if it's formatted incorrectly, but complain
509+ // about it so it can be debugged.
510+ log .Warnf (ctx , "Hit invalid log format: %q" , string (line ))
511+ continue
512+ }
513+
514+ pipe := string (parts [1 ])
515+ content := parts [3 ]
516+
517+ linetype := string (parts [2 ])
518+ if linetype == "P" {
519+ contentLen := len (content )
520+ if contentLen > 0 && content [contentLen - 1 ] == '\n' {
521+ content = content [:contentLen - 1 ]
522+ }
523+ }
524+
525+ switch pipe {
526+ case "stdout" :
527+ stdout = append (stdout , content ... )
528+ case "stderr" :
529+ stderr = append (stderr , content ... )
530+ default :
531+ // Complain about unknown pipes.
532+ log .Warnf (ctx , "Hit invalid log format [unknown pipe %s]: %q" , pipe , string (line ))
533+ continue
534+ }
535+ }
536+
537+ return stdout , stderr
538+ }
539+
540+ func prepareExec () (pidFileName string , parentPipe , childPipe * os.File , _ error ) {
541+ var err error
542+ parentPipe , childPipe , err = os .Pipe ()
543+ if err != nil {
544+ return "" , nil , nil , err
545+ }
546+
547+ pidFile , err := ioutil .TempFile ("" , "pidfile" )
548+ if err != nil {
549+ parentPipe .Close ()
550+ childPipe .Close ()
551+ return "" , nil , nil , err
552+ }
553+ pidFile .Close ()
554+ pidFileName = pidFile .Name ()
555+
556+ return pidFileName , parentPipe , childPipe , nil
557+ }
558+
397559func (r * runtimeOCI ) constructExecCommand (ctx context.Context , c * Container , processFile , pidFile string ) * exec.Cmd {
398560 args := []string {rootFlag , r .root , "exec" }
399561 if pidFile != "" {
0 commit comments