blob: ea02bf8cbf505fd35e0d45b7b2b372d5aa699900 [file] [log] [blame]
Jiri Simsa5293dcb2014-05-10 09:56:38 -07001package exec
2
3import (
Jiri Simsac199bc12014-05-30 12:52:24 -07004 "encoding/binary"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07005 "errors"
Jiri Simsac199bc12014-05-30 12:52:24 -07006 "io"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07007 "os"
Robert Kroegerb5d6bda2015-01-30 16:10:49 -08008 "strconv"
Jiri Simsa84059da2014-06-02 17:22:05 -07009 "sync"
Bogdan Caprita66ca3532015-02-05 21:08:10 -080010 "unicode/utf8"
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -080011
Jiri Simsaffceefa2015-02-28 11:03:34 -080012 "v.io/x/ref/lib/exec/consts"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070013)
14
15var (
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -080016 ErrNoVersion = errors.New(consts.ExecVersionVariable + " environment variable missing")
17 ErrUnsupportedVersion = errors.New("Unsupported version of veyron/lib/exec request by " + consts.ExecVersionVariable + " environment variable")
Jiri Simsa5293dcb2014-05-10 09:56:38 -070018)
19
20type ChildHandle struct {
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070021 // Config is passed down from the parent.
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070022 Config Config
Jiri Simsac199bc12014-05-30 12:52:24 -070023 // Secret is a secret passed to the child by its parent via a
24 // trusted channel.
25 Secret string
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070026 // statusPipe is a pipe that is used to notify the parent that the child
27 // process has started successfully. It is meant to be invoked by the
28 // veyron framework to notify the parent that the child process has
29 // successfully started.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070030 statusPipe *os.File
Matt Rosencrantz119962e2015-02-12 16:08:38 -080031
32 // statusMu protexts sentStatus and statusErr and prevents us from trying to
33 // send multiple status updates to the parent.
34 statusMu sync.Mutex
35 // sentStatus records the status that was already sent to the parent.
36 sentStatus string
37 // statusErr records the error we received, if any, when sentStatus
38 // was sent.
39 statusErr error
Jiri Simsa5293dcb2014-05-10 09:56:38 -070040}
41
Jiri Simsa84059da2014-06-02 17:22:05 -070042var (
43 childHandle *ChildHandle
44 childHandleErr error
45 once sync.Once
46)
47
Bogdan Caprita7f491672014-11-13 14:51:08 -080048// FileOffset accounts for the file descriptors that are always passed
Jiri Simsac199bc12014-05-30 12:52:24 -070049// to the child by the parent: stderr, stdin, stdout, data read, and
50// status write. Any extra files added by the client will follow
51// fileOffset.
Bogdan Caprita7f491672014-11-13 14:51:08 -080052const FileOffset = 5
Jiri Simsa5293dcb2014-05-10 09:56:38 -070053
Jiri Simsa84059da2014-06-02 17:22:05 -070054// GetChildHandle returns a ChildHandle that can be used to signal
55// that the child is 'ready' (by calling SetReady) to its parent or to
56// retrieve data securely passed to this process by its parent. For
57// instance, a secret intended to create a secure communication
58// channels and or authentication.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070059//
Jiri Simsac199bc12014-05-30 12:52:24 -070060// If the child is relying on exec.Cmd.ExtraFiles then its first file
61// descriptor will not be 3, but will be offset by extra files added
62// by the framework. The developer should use the NewExtraFile method
63// to robustly get their extra files with the correct offset applied.
Jiri Simsa84059da2014-06-02 17:22:05 -070064func GetChildHandle() (*ChildHandle, error) {
65 once.Do(func() {
66 childHandle, childHandleErr = createChildHandle()
67 })
68 return childHandle, childHandleErr
Jiri Simsa5293dcb2014-05-10 09:56:38 -070069}
70
Matt Rosencrantz119962e2015-02-12 16:08:38 -080071func (c *ChildHandle) writeStatus(status, detail string) error {
72 c.statusMu.Lock()
73 defer c.statusMu.Unlock()
74
75 if c.sentStatus == "" {
76 c.sentStatus = status
77 status = status + detail
78 toWrite := make([]byte, 0, len(status))
79 var buf [utf8.UTFMax]byte
80 // This replaces any invalid utf-8 bytes in the status string with the
81 // Unicode replacement character. This ensures that we only send valid
82 // utf-8 (followed by the eofChar).
83 for _, r := range status {
84 n := utf8.EncodeRune(buf[:], r)
85 toWrite = append(toWrite, buf[:n]...)
86 }
87 toWrite = append(toWrite, eofChar)
88 _, c.statusErr = c.statusPipe.Write(toWrite)
89 c.statusPipe.Close()
90 } else if c.sentStatus != status {
91 return errors.New("A different status: " + c.sentStatus + " has already been sent.")
Bogdan Caprita66ca3532015-02-05 21:08:10 -080092 }
Matt Rosencrantz119962e2015-02-12 16:08:38 -080093 return c.statusErr
Bogdan Caprita66ca3532015-02-05 21:08:10 -080094}
95
Jiri Simsa5293dcb2014-05-10 09:56:38 -070096// SetReady writes a 'ready' status to its parent.
Matt Rosencrantz119962e2015-02-12 16:08:38 -080097// Only one of SetReady or SetFailed can be called, attempting to send
98// both will fail. In addition the status is only sent once to the parent
99// subsequent calls will return immediately with the same error that was
100// returned on the first call (possibly nil).
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700101func (c *ChildHandle) SetReady() error {
Matt Rosencrantz119962e2015-02-12 16:08:38 -0800102 return c.writeStatus(readyStatus, strconv.Itoa(os.Getpid()))
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700103}
104
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -0700105// SetFailed writes a 'failed' status to its parent.
106func (c *ChildHandle) SetFailed(oerr error) error {
Matt Rosencrantz119962e2015-02-12 16:08:38 -0800107 return c.writeStatus(failedStatus, oerr.Error())
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -0700108}
109
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700110// NewExtraFile creates a new file handle for the i-th file descriptor after
111// discounting stdout, stderr, stdin and the files reserved by the framework for
112// its own purposes.
113func (c *ChildHandle) NewExtraFile(i uintptr, name string) *os.File {
Bogdan Caprita7f491672014-11-13 14:51:08 -0800114 return os.NewFile(i+FileOffset, name)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700115}
Jiri Simsac199bc12014-05-30 12:52:24 -0700116
Jiri Simsa84059da2014-06-02 17:22:05 -0700117func createChildHandle() (*ChildHandle, error) {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800118 // TODO(cnicolaou): need to use major.minor.build format for
119 // version #s.
120 switch os.Getenv(consts.ExecVersionVariable) {
Jiri Simsa84059da2014-06-02 17:22:05 -0700121 case "":
122 return nil, ErrNoVersion
123 case version1:
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800124 os.Setenv(consts.ExecVersionVariable, "")
Jiri Simsa84059da2014-06-02 17:22:05 -0700125 default:
126 return nil, ErrUnsupportedVersion
127 }
128 dataPipe := os.NewFile(3, "data_rd")
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700129 serializedConfig, err := decodeString(dataPipe)
Jiri Simsa84059da2014-06-02 17:22:05 -0700130 if err != nil {
131 return nil, err
132 }
Cosmos Nicolaou486d3492014-09-30 22:21:20 -0700133 cfg := NewConfig()
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700134 if err := cfg.MergeFrom(serializedConfig); err != nil {
135 return nil, err
136 }
Jiri Simsa84059da2014-06-02 17:22:05 -0700137 secret, err := decodeString(dataPipe)
138 if err != nil {
139 return nil, err
140 }
141 childHandle = &ChildHandle{
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700142 Config: cfg,
143 Secret: secret,
144 statusPipe: os.NewFile(4, "status_wr"),
Jiri Simsa84059da2014-06-02 17:22:05 -0700145 }
146 return childHandle, nil
147}
148
149func decodeString(r io.Reader) (string, error) {
Jiri Simsac199bc12014-05-30 12:52:24 -0700150 var l int64 = 0
151 if err := binary.Read(r, binary.BigEndian, &l); err != nil {
152 return "", err
153 }
154 var data []byte = make([]byte, l)
155 if n, err := r.Read(data); err != nil || int64(n) != l {
156 if err != nil {
157 return "", err
158 } else {
159 return "", errors.New("partial read")
160 }
161 }
162 return string(data), nil
163}