@@ -5,8 +5,11 @@ import (
55 "io/ioutil"
66 "os"
77 "path/filepath"
8+ "strconv"
9+ "time"
810
911 "github.com/docker/docker/daemon/caps"
12+ "github.com/docker/docker/pkg/stringid"
1013 "github.com/docker/docker/pkg/term"
1114 "github.com/pkg/errors"
1215 "github.com/projectatomic/libpod/libpod/driver"
@@ -235,7 +238,86 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e
235238 capList = caps .GetAllCapabilities ()
236239 }
237240
238- return c .runtime .ociRuntime .execContainer (c , cmd , tty , user , capList , env )
241+ // Generate exec session ID
242+ // Ensure we don't conflict with an existing session ID
243+ sessionID := stringid .GenerateNonCryptoID ()
244+ found := true
245+ // This really ought to be a do-while, but Go doesn't have those...
246+ for found {
247+ found = false
248+ for id , _ := range c .state .ExecSessions {
249+ if id == sessionID {
250+ found = true
251+ break
252+ }
253+ }
254+ if found == true {
255+ sessionID = stringid .GenerateNonCryptoID ()
256+ }
257+ }
258+
259+ execCmd , err := c .runtime .ociRuntime .execContainer (c , cmd , capList , env , tty , user , sessionID )
260+ if err != nil {
261+ return errors .Wrapf (err , "error creating exec command for container %s" , c .ID ())
262+ }
263+
264+ if err := execCmd .Start (); err != nil {
265+ return errors .Wrapf (err , "error starting exec command for container %s" , c .ID ())
266+ }
267+
268+ pidFile := c .execPidPath (sessionID )
269+ const pidWaitTimeout = 250
270+
271+ // Wait until runc makes the pidfile
272+ // TODO: If runc errors before the PID file is created, we have to wait for timeout here
273+ if err := WaitForFile (pidFile , pidWaitTimeout * time .Millisecond ); err != nil {
274+ logrus .Debugf ("Timed out waiting for pidfile from runc for container %s exec" , c .ID ())
275+
276+ // Check if an error occurred in the process before we made a pidfile
277+ // TODO: Wait() here is a poor choice - is there a way to see if
278+ // a process has finished, instead of waiting for it to finish?
279+ if err := execCmd .Wait (); err != nil {
280+ return err
281+ }
282+
283+ return errors .Wrapf (err , "timed out waiting for runc to create pidfile for exec session in container %s" , c .ID ())
284+ }
285+
286+ // Pidfile exists, read it
287+ contents , err := ioutil .ReadFile (pidFile )
288+ if err != nil {
289+ // We don't know the PID of the exec session
290+ // However, it may still be alive
291+ // TODO handle this better
292+ return errors .Wrapf (err , "could not read pidfile for exec session %s in container %s" , sessionID , c .ID ())
293+ }
294+ pid , err := strconv .ParseInt (string (contents ), 10 , 32 )
295+ if err != nil {
296+ // As above, we don't have a valid PID, but the exec session is likely still alive
297+ // TODO handle this better
298+ return errors .Wrapf (err , "error parsing PID of exec session %s in container %s" , sessionID , c .ID ())
299+ }
300+
301+ // We have the PID, add it to state
302+ if c .state .ExecSessions == nil {
303+ c .state .ExecSessions = make (map [string ]int )
304+ }
305+ c .state .ExecSessions [sessionID ] = int (pid )
306+ if err := c .save (); err != nil {
307+ // Now we have a PID but we can't save it in the DB
308+ // TODO handle this better
309+ return errors .Wrapf (err , "error saving exec sessions %s for container %s" , sessionID , c .ID ())
310+ }
311+
312+ waitErr := execCmd .Wait ()
313+
314+ // Remove the exec session from state
315+ delete (c .state .ExecSessions , sessionID )
316+ if err := c .save (); err != nil {
317+ logrus .Errorf ("Error removing exec session %s from container %s state: %v" , sessionID , c .ID (), err )
318+ }
319+
320+ return waitErr
239321}
240322
241323// Attach attaches to a container
0 commit comments