github.com/hinshun/containerd@v0.2.7/ctr/container.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"log"
     9  	"net"
    10  	"os"
    11  	"os/signal"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"syscall"
    16  	"text/tabwriter"
    17  	"time"
    18  
    19  	"github.com/codegangsta/cli"
    20  	"github.com/docker/containerd/api/grpc/types"
    21  	"github.com/docker/containerd/specs"
    22  	"github.com/docker/docker/pkg/term"
    23  	"github.com/golang/protobuf/ptypes"
    24  	netcontext "golang.org/x/net/context"
    25  	"golang.org/x/sys/unix"
    26  	"google.golang.org/grpc"
    27  	"google.golang.org/grpc/grpclog"
    28  	"google.golang.org/grpc/transport"
    29  )
    30  
    31  // TODO: parse flags and pass opts
    32  func getClient(ctx *cli.Context) types.APIClient {
    33  	// Parse proto://address form addresses.
    34  	bindSpec := ctx.GlobalString("address")
    35  	bindParts := strings.SplitN(bindSpec, "://", 2)
    36  	if len(bindParts) != 2 {
    37  		fatal(fmt.Sprintf("bad bind address format %s, expected proto://address", bindSpec), 1)
    38  	}
    39  
    40  	// reset the logger for grpc to log to dev/null so that it does not mess with our stdio
    41  	grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
    42  	dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithTimeout(ctx.GlobalDuration("conn-timeout"))}
    43  	dialOpts = append(dialOpts,
    44  		grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
    45  			return net.DialTimeout(bindParts[0], bindParts[1], timeout)
    46  		},
    47  		))
    48  	conn, err := grpc.Dial(bindSpec, dialOpts...)
    49  	if err != nil {
    50  		fatal(err.Error(), 1)
    51  	}
    52  	return types.NewAPIClient(conn)
    53  }
    54  
    55  var contSubCmds = []cli.Command{
    56  	execCommand,
    57  	killCommand,
    58  	listCommand,
    59  	pauseCommand,
    60  	resumeCommand,
    61  	startCommand,
    62  	stateCommand,
    63  	statsCommand,
    64  	watchCommand,
    65  	updateCommand,
    66  }
    67  
    68  var containersCommand = cli.Command{
    69  	Name:        "containers",
    70  	Usage:       "interact with running containers",
    71  	ArgsUsage:   "COMMAND [arguments...]",
    72  	Subcommands: contSubCmds,
    73  	Description: func() string {
    74  		desc := "\n    COMMAND:\n"
    75  		for _, command := range contSubCmds {
    76  			desc += fmt.Sprintf("    %-10.10s%s\n", command.Name, command.Usage)
    77  		}
    78  		return desc
    79  	}(),
    80  	Action: listContainers,
    81  }
    82  
    83  var stateCommand = cli.Command{
    84  	Name:  "state",
    85  	Usage: "get a raw dump of the containerd state",
    86  	Action: func(context *cli.Context) {
    87  		c := getClient(context)
    88  		resp, err := c.State(netcontext.Background(), &types.StateRequest{
    89  			Id: context.Args().First(),
    90  		})
    91  		if err != nil {
    92  			fatal(err.Error(), 1)
    93  		}
    94  		data, err := json.Marshal(resp)
    95  		if err != nil {
    96  			fatal(err.Error(), 1)
    97  		}
    98  		fmt.Print(string(data))
    99  	},
   100  }
   101  
   102  var listCommand = cli.Command{
   103  	Name:   "list",
   104  	Usage:  "list all running containers",
   105  	Action: listContainers,
   106  }
   107  
   108  func listContainers(context *cli.Context) {
   109  	c := getClient(context)
   110  	resp, err := c.State(netcontext.Background(), &types.StateRequest{
   111  		Id: context.Args().First(),
   112  	})
   113  	if err != nil {
   114  		fatal(err.Error(), 1)
   115  	}
   116  	w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
   117  	fmt.Fprint(w, "ID\tPATH\tSTATUS\tPROCESSES\n")
   118  	sortContainers(resp.Containers)
   119  	for _, c := range resp.Containers {
   120  		procs := []string{}
   121  		for _, p := range c.Processes {
   122  			procs = append(procs, p.Pid)
   123  		}
   124  		fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", c.Id, c.BundlePath, c.Status, strings.Join(procs, ","))
   125  	}
   126  	if err := w.Flush(); err != nil {
   127  		fatal(err.Error(), 1)
   128  	}
   129  }
   130  
   131  var startCommand = cli.Command{
   132  	Name:      "start",
   133  	Usage:     "start a container",
   134  	ArgsUsage: "ID BundlePath",
   135  	Flags: []cli.Flag{
   136  		cli.StringFlag{
   137  			Name:  "checkpoint,c",
   138  			Value: "",
   139  			Usage: "checkpoint to start the container from",
   140  		},
   141  		cli.StringFlag{
   142  			Name:  "checkpoint-dir",
   143  			Value: "",
   144  			Usage: "path to checkpoint directory",
   145  		},
   146  		cli.BoolFlag{
   147  			Name:  "attach,a",
   148  			Usage: "connect to the stdio of the container",
   149  		},
   150  		cli.StringSliceFlag{
   151  			Name:  "label,l",
   152  			Value: &cli.StringSlice{},
   153  			Usage: "set labels for the container",
   154  		},
   155  		cli.BoolFlag{
   156  			Name:  "no-pivot",
   157  			Usage: "do not use pivot root",
   158  		},
   159  		cli.StringFlag{
   160  			Name:  "runtime,r",
   161  			Value: "runc",
   162  			Usage: "name or path of the OCI compliant runtime to use when executing containers",
   163  		},
   164  		cli.StringSliceFlag{
   165  			Name:  "runtime-args",
   166  			Value: &cli.StringSlice{},
   167  			Usage: "specify additional runtime args",
   168  		},
   169  	},
   170  	Action: func(context *cli.Context) {
   171  		var (
   172  			id   = context.Args().Get(0)
   173  			path = context.Args().Get(1)
   174  		)
   175  		if path == "" {
   176  			fatal("bundle path cannot be empty", ExitStatusMissingArg)
   177  		}
   178  		if id == "" {
   179  			fatal("container id cannot be empty", ExitStatusMissingArg)
   180  		}
   181  		bpath, err := filepath.Abs(path)
   182  		if err != nil {
   183  			fatal(fmt.Sprintf("cannot get the absolute path of the bundle: %v", err), 1)
   184  		}
   185  		s, tmpDir, err := createStdio()
   186  		defer func() {
   187  			if tmpDir != "" {
   188  				os.RemoveAll(tmpDir)
   189  			}
   190  		}()
   191  		if err != nil {
   192  			fatal(err.Error(), 1)
   193  		}
   194  		var (
   195  			restoreAndCloseStdin func()
   196  			tty                  bool
   197  			c                    = getClient(context)
   198  			r                    = &types.CreateContainerRequest{
   199  				Id:            id,
   200  				BundlePath:    bpath,
   201  				Checkpoint:    context.String("checkpoint"),
   202  				CheckpointDir: context.String("checkpoint-dir"),
   203  				Stdin:         s.stdin,
   204  				Stdout:        s.stdout,
   205  				Stderr:        s.stderr,
   206  				Labels:        context.StringSlice("label"),
   207  				NoPivotRoot:   context.Bool("no-pivot"),
   208  				Runtime:       context.String("runtime"),
   209  				RuntimeArgs:   context.StringSlice("runtime-args"),
   210  			}
   211  		)
   212  		restoreAndCloseStdin = func() {
   213  			if state != nil {
   214  				term.RestoreTerminal(os.Stdin.Fd(), state)
   215  			}
   216  			if stdin != nil {
   217  				stdin.Close()
   218  			}
   219  		}
   220  		defer restoreAndCloseStdin()
   221  		if context.Bool("attach") {
   222  			mkterm, err := readTermSetting(bpath)
   223  			if err != nil {
   224  				fatal(err.Error(), 1)
   225  			}
   226  			tty = mkterm
   227  			if mkterm {
   228  				s, err := term.SetRawTerminal(os.Stdin.Fd())
   229  				if err != nil {
   230  					fatal(err.Error(), 1)
   231  				}
   232  				state = s
   233  			}
   234  			if err := attachStdio(s); err != nil {
   235  				fatal(err.Error(), 1)
   236  			}
   237  		}
   238  		events, err := c.Events(netcontext.Background(), &types.EventsRequest{})
   239  		if err != nil {
   240  			fatal(err.Error(), 1)
   241  		}
   242  		if _, err := c.CreateContainer(netcontext.Background(), r); err != nil {
   243  			fatal(err.Error(), 1)
   244  		}
   245  		if context.Bool("attach") {
   246  			go func() {
   247  				io.Copy(stdin, os.Stdin)
   248  				if _, err := c.UpdateProcess(netcontext.Background(), &types.UpdateProcessRequest{
   249  					Id:         id,
   250  					Pid:        "init",
   251  					CloseStdin: true,
   252  				}); err != nil {
   253  					fatal(err.Error(), 1)
   254  				}
   255  				restoreAndCloseStdin()
   256  			}()
   257  			if tty {
   258  				resize(id, "init", c)
   259  				go func() {
   260  					s := make(chan os.Signal, 64)
   261  					signal.Notify(s, syscall.SIGWINCH)
   262  					for range s {
   263  						if err := resize(id, "init", c); err != nil {
   264  							log.Println(err)
   265  						}
   266  					}
   267  				}()
   268  			}
   269  			waitForExit(c, events, id, "init", restoreAndCloseStdin)
   270  		}
   271  	},
   272  }
   273  
   274  func resize(id, pid string, c types.APIClient) error {
   275  	ws, err := term.GetWinsize(os.Stdin.Fd())
   276  	if err != nil {
   277  		return err
   278  	}
   279  	if _, err := c.UpdateProcess(netcontext.Background(), &types.UpdateProcessRequest{
   280  		Id:     id,
   281  		Pid:    "init",
   282  		Width:  uint32(ws.Width),
   283  		Height: uint32(ws.Height),
   284  	}); err != nil {
   285  		return err
   286  	}
   287  	return nil
   288  }
   289  
   290  var (
   291  	stdin io.WriteCloser
   292  	state *term.State
   293  )
   294  
   295  // readTermSetting reads the Terminal option out of the specs configuration
   296  // to know if ctr should allocate a pty
   297  func readTermSetting(path string) (bool, error) {
   298  	f, err := os.Open(filepath.Join(path, "config.json"))
   299  	if err != nil {
   300  		return false, err
   301  	}
   302  	defer f.Close()
   303  	var spec specs.Spec
   304  	if err := json.NewDecoder(f).Decode(&spec); err != nil {
   305  		return false, err
   306  	}
   307  	return spec.Process.Terminal, nil
   308  }
   309  
   310  func attachStdio(s stdio) error {
   311  	stdinf, err := os.OpenFile(s.stdin, syscall.O_RDWR, 0)
   312  	if err != nil {
   313  		return err
   314  	}
   315  	// FIXME: assign to global
   316  	stdin = stdinf
   317  	stdoutf, err := os.OpenFile(s.stdout, syscall.O_RDWR, 0)
   318  	if err != nil {
   319  		return err
   320  	}
   321  	go io.Copy(os.Stdout, stdoutf)
   322  	stderrf, err := os.OpenFile(s.stderr, syscall.O_RDWR, 0)
   323  	if err != nil {
   324  		return err
   325  	}
   326  	go io.Copy(os.Stderr, stderrf)
   327  	return nil
   328  }
   329  
   330  var watchCommand = cli.Command{
   331  	Name:  "watch",
   332  	Usage: "print container events",
   333  	Action: func(context *cli.Context) {
   334  		c := getClient(context)
   335  		id := context.Args().First()
   336  		if id != "" {
   337  			resp, err := c.State(netcontext.Background(), &types.StateRequest{Id: id})
   338  			if err != nil {
   339  				fatal(err.Error(), 1)
   340  			}
   341  			for _, c := range resp.Containers {
   342  				if c.Id == id {
   343  					break
   344  				}
   345  			}
   346  			if id == "" {
   347  				fatal("Invalid container id", 1)
   348  			}
   349  		}
   350  		events, reqErr := c.Events(netcontext.Background(), &types.EventsRequest{})
   351  		if reqErr != nil {
   352  			fatal(reqErr.Error(), 1)
   353  		}
   354  
   355  		for {
   356  			e, err := events.Recv()
   357  			if err != nil {
   358  				fatal(err.Error(), 1)
   359  			}
   360  
   361  			if id == "" || e.Id == id {
   362  				fmt.Printf("%#v\n", e)
   363  			}
   364  		}
   365  	},
   366  }
   367  
   368  var pauseCommand = cli.Command{
   369  	Name:  "pause",
   370  	Usage: "pause a container",
   371  	Action: func(context *cli.Context) {
   372  		id := context.Args().First()
   373  		if id == "" {
   374  			fatal("container id cannot be empty", ExitStatusMissingArg)
   375  		}
   376  		c := getClient(context)
   377  		_, err := c.UpdateContainer(netcontext.Background(), &types.UpdateContainerRequest{
   378  			Id:     id,
   379  			Pid:    "init",
   380  			Status: "paused",
   381  		})
   382  		if err != nil {
   383  			fatal(err.Error(), 1)
   384  		}
   385  	},
   386  }
   387  
   388  var resumeCommand = cli.Command{
   389  	Name:  "resume",
   390  	Usage: "resume a paused container",
   391  	Action: func(context *cli.Context) {
   392  		id := context.Args().First()
   393  		if id == "" {
   394  			fatal("container id cannot be empty", ExitStatusMissingArg)
   395  		}
   396  		c := getClient(context)
   397  		_, err := c.UpdateContainer(netcontext.Background(), &types.UpdateContainerRequest{
   398  			Id:     id,
   399  			Pid:    "init",
   400  			Status: "running",
   401  		})
   402  		if err != nil {
   403  			fatal(err.Error(), 1)
   404  		}
   405  	},
   406  }
   407  
   408  var killCommand = cli.Command{
   409  	Name:  "kill",
   410  	Usage: "send a signal to a container or its processes",
   411  	Flags: []cli.Flag{
   412  		cli.StringFlag{
   413  			Name:  "pid,p",
   414  			Value: "init",
   415  			Usage: "pid of the process to signal within the container",
   416  		},
   417  		cli.IntFlag{
   418  			Name:  "signal,s",
   419  			Value: 15,
   420  			Usage: "signal to send to the container",
   421  		},
   422  	},
   423  	Action: func(context *cli.Context) {
   424  		id := context.Args().First()
   425  		if id == "" {
   426  			fatal("container id cannot be empty", ExitStatusMissingArg)
   427  		}
   428  		c := getClient(context)
   429  		if _, err := c.Signal(netcontext.Background(), &types.SignalRequest{
   430  			Id:     id,
   431  			Pid:    context.String("pid"),
   432  			Signal: uint32(context.Int("signal")),
   433  		}); err != nil {
   434  			fatal(err.Error(), 1)
   435  		}
   436  	},
   437  }
   438  
   439  var execCommand = cli.Command{
   440  	Name:  "exec",
   441  	Usage: "exec another process in an existing container",
   442  	Flags: []cli.Flag{
   443  		cli.StringFlag{
   444  			Name:  "id",
   445  			Usage: "container id to add the process to",
   446  		},
   447  		cli.StringFlag{
   448  			Name:  "pid",
   449  			Usage: "process id for the new process",
   450  		},
   451  		cli.BoolFlag{
   452  			Name:  "attach,a",
   453  			Usage: "connect to the stdio of the container",
   454  		},
   455  		cli.StringFlag{
   456  			Name:  "cwd",
   457  			Usage: "current working directory for the process",
   458  		},
   459  		cli.BoolFlag{
   460  			Name:  "tty,t",
   461  			Usage: "create a terminal for the process",
   462  		},
   463  		cli.StringSliceFlag{
   464  			Name:  "env,e",
   465  			Value: &cli.StringSlice{},
   466  			Usage: "environment variables for the process",
   467  		},
   468  		cli.IntFlag{
   469  			Name:  "uid,u",
   470  			Usage: "user id of the user for the process",
   471  		},
   472  		cli.IntFlag{
   473  			Name:  "gid,g",
   474  			Usage: "group id of the user for the process",
   475  		},
   476  	},
   477  	Action: func(context *cli.Context) {
   478  		var restoreAndCloseStdin func()
   479  
   480  		p := &types.AddProcessRequest{
   481  			Id:       context.String("id"),
   482  			Pid:      context.String("pid"),
   483  			Args:     context.Args(),
   484  			Cwd:      context.String("cwd"),
   485  			Terminal: context.Bool("tty"),
   486  			Env:      context.StringSlice("env"),
   487  			User: &types.User{
   488  				Uid: uint32(context.Int("uid")),
   489  				Gid: uint32(context.Int("gid")),
   490  			},
   491  		}
   492  		s, tmpDir, err := createStdio()
   493  		defer func() {
   494  			if tmpDir != "" {
   495  				os.RemoveAll(tmpDir)
   496  			}
   497  		}()
   498  		if err != nil {
   499  			fatal(err.Error(), 1)
   500  		}
   501  		p.Stdin = s.stdin
   502  		p.Stdout = s.stdout
   503  		p.Stderr = s.stderr
   504  		restoreAndCloseStdin = func() {
   505  			if state != nil {
   506  				term.RestoreTerminal(os.Stdin.Fd(), state)
   507  			}
   508  			if stdin != nil {
   509  				stdin.Close()
   510  			}
   511  		}
   512  		defer restoreAndCloseStdin()
   513  		if context.Bool("attach") {
   514  			if context.Bool("tty") {
   515  				s, err := term.SetRawTerminal(os.Stdin.Fd())
   516  				if err != nil {
   517  					fatal(err.Error(), 1)
   518  				}
   519  				state = s
   520  			}
   521  			if err := attachStdio(s); err != nil {
   522  				fatal(err.Error(), 1)
   523  			}
   524  		}
   525  		c := getClient(context)
   526  		events, err := c.Events(netcontext.Background(), &types.EventsRequest{})
   527  		if err != nil {
   528  			fatal(err.Error(), 1)
   529  		}
   530  		if _, err := c.AddProcess(netcontext.Background(), p); err != nil {
   531  			fatal(err.Error(), 1)
   532  		}
   533  		if context.Bool("attach") {
   534  			go func() {
   535  				io.Copy(stdin, os.Stdin)
   536  				if _, err := c.UpdateProcess(netcontext.Background(), &types.UpdateProcessRequest{
   537  					Id:         p.Id,
   538  					Pid:        p.Pid,
   539  					CloseStdin: true,
   540  				}); err != nil {
   541  					log.Println(err)
   542  				}
   543  				restoreAndCloseStdin()
   544  			}()
   545  			if context.Bool("tty") {
   546  				resize(p.Id, p.Pid, c)
   547  				go func() {
   548  					s := make(chan os.Signal, 64)
   549  					signal.Notify(s, syscall.SIGWINCH)
   550  					for range s {
   551  						if err := resize(p.Id, p.Pid, c); err != nil {
   552  							log.Println(err)
   553  						}
   554  					}
   555  				}()
   556  			}
   557  			waitForExit(c, events, context.String("id"), context.String("pid"), restoreAndCloseStdin)
   558  		}
   559  	},
   560  }
   561  
   562  var statsCommand = cli.Command{
   563  	Name:  "stats",
   564  	Usage: "get stats for running container",
   565  	Action: func(context *cli.Context) {
   566  		req := &types.StatsRequest{
   567  			Id: context.Args().First(),
   568  		}
   569  		c := getClient(context)
   570  		stats, err := c.Stats(netcontext.Background(), req)
   571  		if err != nil {
   572  			fatal(err.Error(), 1)
   573  		}
   574  		data, err := json.Marshal(stats)
   575  		if err != nil {
   576  			fatal(err.Error(), 1)
   577  		}
   578  		fmt.Print(string(data))
   579  	},
   580  }
   581  
   582  func getUpdateCommandInt64Flag(context *cli.Context, name string) uint64 {
   583  	str := context.String(name)
   584  	if str == "" {
   585  		return 0
   586  	}
   587  
   588  	val, err := strconv.ParseUint(str, 0, 64)
   589  	if err != nil {
   590  		fatal(err.Error(), 1)
   591  	}
   592  
   593  	return val
   594  }
   595  
   596  var updateCommand = cli.Command{
   597  	Name:  "update",
   598  	Usage: "update a containers resources",
   599  	Flags: []cli.Flag{
   600  		cli.StringFlag{
   601  			Name: "memory-limit",
   602  		},
   603  		cli.StringFlag{
   604  			Name: "memory-reservation",
   605  		},
   606  		cli.StringFlag{
   607  			Name: "memory-swap",
   608  		},
   609  		cli.StringFlag{
   610  			Name: "cpu-quota",
   611  		},
   612  		cli.StringFlag{
   613  			Name: "cpu-period",
   614  		},
   615  		cli.StringFlag{
   616  			Name: "kernel-limit",
   617  		},
   618  		cli.StringFlag{
   619  			Name: "kernel-tcp-limit",
   620  		},
   621  		cli.StringFlag{
   622  			Name: "blkio-weight",
   623  		},
   624  		cli.StringFlag{
   625  			Name: "cpuset-cpus",
   626  		},
   627  		cli.StringFlag{
   628  			Name: "cpuset-mems",
   629  		},
   630  	},
   631  	Action: func(context *cli.Context) {
   632  		req := &types.UpdateContainerRequest{
   633  			Id: context.Args().First(),
   634  		}
   635  		req.Resources = &types.UpdateResource{}
   636  		req.Resources.MemoryLimit = getUpdateCommandInt64Flag(context, "memory-limit")
   637  		req.Resources.MemoryReservation = getUpdateCommandInt64Flag(context, "memory-reservation")
   638  		req.Resources.MemorySwap = getUpdateCommandInt64Flag(context, "memory-swap")
   639  		req.Resources.BlkioWeight = getUpdateCommandInt64Flag(context, "blkio-weight")
   640  		req.Resources.CpuPeriod = getUpdateCommandInt64Flag(context, "cpu-period")
   641  		req.Resources.CpuQuota = getUpdateCommandInt64Flag(context, "cpu-quota")
   642  		req.Resources.CpuShares = getUpdateCommandInt64Flag(context, "cpu-shares")
   643  		req.Resources.CpusetCpus = context.String("cpuset-cpus")
   644  		req.Resources.CpusetMems = context.String("cpuset-mems")
   645  		req.Resources.KernelMemoryLimit = getUpdateCommandInt64Flag(context, "kernel-limit")
   646  		req.Resources.KernelTCPMemoryLimit = getUpdateCommandInt64Flag(context, "kernel-tcp-limit")
   647  		c := getClient(context)
   648  		if _, err := c.UpdateContainer(netcontext.Background(), req); err != nil {
   649  			fatal(err.Error(), 1)
   650  		}
   651  	},
   652  }
   653  
   654  func waitForExit(c types.APIClient, events types.API_EventsClient, id, pid string, closer func()) {
   655  	timestamp := time.Now()
   656  	for {
   657  		e, err := events.Recv()
   658  		if err != nil {
   659  			if grpc.ErrorDesc(err) == transport.ErrConnClosing.Desc {
   660  				closer()
   661  				os.Exit(128 + int(syscall.SIGHUP))
   662  			}
   663  			time.Sleep(1 * time.Second)
   664  			tsp, err := ptypes.TimestampProto(timestamp)
   665  			if err != nil {
   666  				closer()
   667  				fmt.Fprintf(os.Stderr, "%s", err.Error())
   668  				os.Exit(1)
   669  			}
   670  			events, _ = c.Events(netcontext.Background(), &types.EventsRequest{Timestamp: tsp})
   671  			continue
   672  		}
   673  		timestamp, err = ptypes.Timestamp(e.Timestamp)
   674  		if e.Id == id && e.Type == "exit" && e.Pid == pid {
   675  			closer()
   676  			os.Exit(int(e.Status))
   677  		}
   678  	}
   679  }
   680  
   681  type stdio struct {
   682  	stdin  string
   683  	stdout string
   684  	stderr string
   685  }
   686  
   687  func createStdio() (s stdio, tmp string, err error) {
   688  	tmp, err = ioutil.TempDir("", "ctr-")
   689  	if err != nil {
   690  		return s, tmp, err
   691  	}
   692  	// create fifo's for the process
   693  	for name, fd := range map[string]*string{
   694  		"stdin":  &s.stdin,
   695  		"stdout": &s.stdout,
   696  		"stderr": &s.stderr,
   697  	} {
   698  		path := filepath.Join(tmp, name)
   699  		if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
   700  			return s, tmp, err
   701  		}
   702  		*fd = path
   703  	}
   704  	return s, tmp, nil
   705  }