blob: 4cb59a051f6efe3679f33da03ff7189d2d0aec14 [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Jiri Simsa5293dcb2014-05-10 09:56:38 -07005package exec
6
7import (
Jiri Simsac199bc12014-05-30 12:52:24 -07008 "encoding/binary"
Jiri Simsac199bc12014-05-30 12:52:24 -07009 "io"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070010 "os"
Robert Kroegerb5d6bda2015-01-30 16:10:49 -080011 "strconv"
Jiri Simsa84059da2014-06-02 17:22:05 -070012 "sync"
Bogdan Caprita66ca3532015-02-05 21:08:10 -080013 "unicode/utf8"
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -080014
Mike Burrowsccca2f42015-03-27 13:57:29 -070015 "v.io/v23/verror"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070016)
17
18var (
Todd Wang644ec622015-04-06 17:46:44 -070019 ErrNoVersion = verror.Register(pkgPath+".ErrNoVersion", verror.NoRetry, "{1:}{2:} "+ExecVersionVariable+" environment variable missing{:_}")
20 ErrUnsupportedVersion = verror.Register(pkgPath+".ErrUnsupportedVersion", verror.NoRetry, "{1:}{2:} Unsupported version of v.io/x/ref/lib/exec request by "+ExecVersionVariable+" environment variable{:_}")
Mike Burrowsccca2f42015-03-27 13:57:29 -070021
22 errDifferentStatusSent = verror.Register(pkgPath+".errDifferentStatusSent", verror.NoRetry, "{1:}{2:} A different status: {3} has already been sent{:_}")
23 errPartialRead = verror.Register(pkgPath+".PartialRead", verror.NoRetry, "{1:}{2:} partial read{:_}")
Jiri Simsa5293dcb2014-05-10 09:56:38 -070024)
25
26type ChildHandle struct {
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070027 // Config is passed down from the parent.
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070028 Config Config
Jiri Simsac199bc12014-05-30 12:52:24 -070029 // Secret is a secret passed to the child by its parent via a
30 // trusted channel.
31 Secret string
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070032 // statusPipe is a pipe that is used to notify the parent that the child
33 // process has started successfully. It is meant to be invoked by the
Suharsh Sivakumard1cc6e02015-03-16 13:58:49 -070034 // vanadium framework to notify the parent that the child process has
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070035 // successfully started.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070036 statusPipe *os.File
Matt Rosencrantz119962e2015-02-12 16:08:38 -080037
38 // statusMu protexts sentStatus and statusErr and prevents us from trying to
39 // send multiple status updates to the parent.
40 statusMu sync.Mutex
41 // sentStatus records the status that was already sent to the parent.
42 sentStatus string
43 // statusErr records the error we received, if any, when sentStatus
44 // was sent.
45 statusErr error
Jiri Simsa5293dcb2014-05-10 09:56:38 -070046}
47
Jiri Simsa84059da2014-06-02 17:22:05 -070048var (
49 childHandle *ChildHandle
50 childHandleErr error
51 once sync.Once
52)
53
Bogdan Caprita7f491672014-11-13 14:51:08 -080054// FileOffset accounts for the file descriptors that are always passed
Jiri Simsac199bc12014-05-30 12:52:24 -070055// to the child by the parent: stderr, stdin, stdout, data read, and
56// status write. Any extra files added by the client will follow
57// fileOffset.
Bogdan Caprita7f491672014-11-13 14:51:08 -080058const FileOffset = 5
Jiri Simsa5293dcb2014-05-10 09:56:38 -070059
Jiri Simsa84059da2014-06-02 17:22:05 -070060// GetChildHandle returns a ChildHandle that can be used to signal
61// that the child is 'ready' (by calling SetReady) to its parent or to
62// retrieve data securely passed to this process by its parent. For
63// instance, a secret intended to create a secure communication
64// channels and or authentication.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070065//
Jiri Simsac199bc12014-05-30 12:52:24 -070066// If the child is relying on exec.Cmd.ExtraFiles then its first file
67// descriptor will not be 3, but will be offset by extra files added
68// by the framework. The developer should use the NewExtraFile method
69// to robustly get their extra files with the correct offset applied.
Jiri Simsa84059da2014-06-02 17:22:05 -070070func GetChildHandle() (*ChildHandle, error) {
71 once.Do(func() {
72 childHandle, childHandleErr = createChildHandle()
73 })
74 return childHandle, childHandleErr
Jiri Simsa5293dcb2014-05-10 09:56:38 -070075}
76
Matt Rosencrantz119962e2015-02-12 16:08:38 -080077func (c *ChildHandle) writeStatus(status, detail string) error {
78 c.statusMu.Lock()
79 defer c.statusMu.Unlock()
80
81 if c.sentStatus == "" {
82 c.sentStatus = status
83 status = status + detail
84 toWrite := make([]byte, 0, len(status))
85 var buf [utf8.UTFMax]byte
86 // This replaces any invalid utf-8 bytes in the status string with the
87 // Unicode replacement character. This ensures that we only send valid
88 // utf-8 (followed by the eofChar).
89 for _, r := range status {
90 n := utf8.EncodeRune(buf[:], r)
91 toWrite = append(toWrite, buf[:n]...)
92 }
93 toWrite = append(toWrite, eofChar)
94 _, c.statusErr = c.statusPipe.Write(toWrite)
95 c.statusPipe.Close()
96 } else if c.sentStatus != status {
Mike Burrowsccca2f42015-03-27 13:57:29 -070097 return verror.New(errDifferentStatusSent, nil, c.sentStatus)
Bogdan Caprita66ca3532015-02-05 21:08:10 -080098 }
Matt Rosencrantz119962e2015-02-12 16:08:38 -080099 return c.statusErr
Bogdan Caprita66ca3532015-02-05 21:08:10 -0800100}
101
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700102// SetReady writes a 'ready' status to its parent.
Matt Rosencrantz119962e2015-02-12 16:08:38 -0800103// Only one of SetReady or SetFailed can be called, attempting to send
104// both will fail. In addition the status is only sent once to the parent
105// subsequent calls will return immediately with the same error that was
106// returned on the first call (possibly nil).
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700107func (c *ChildHandle) SetReady() error {
Matt Rosencrantz119962e2015-02-12 16:08:38 -0800108 return c.writeStatus(readyStatus, strconv.Itoa(os.Getpid()))
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700109}
110
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -0700111// SetFailed writes a 'failed' status to its parent.
112func (c *ChildHandle) SetFailed(oerr error) error {
Matt Rosencrantz119962e2015-02-12 16:08:38 -0800113 return c.writeStatus(failedStatus, oerr.Error())
Cosmos Nicolaou1c18c1c2014-10-08 16:37:10 -0700114}
115
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700116// NewExtraFile creates a new file handle for the i-th file descriptor after
117// discounting stdout, stderr, stdin and the files reserved by the framework for
118// its own purposes.
119func (c *ChildHandle) NewExtraFile(i uintptr, name string) *os.File {
Bogdan Caprita7f491672014-11-13 14:51:08 -0800120 return os.NewFile(i+FileOffset, name)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700121}
Jiri Simsac199bc12014-05-30 12:52:24 -0700122
Jiri Simsa84059da2014-06-02 17:22:05 -0700123func createChildHandle() (*ChildHandle, error) {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800124 // TODO(cnicolaou): need to use major.minor.build format for
125 // version #s.
Todd Wang644ec622015-04-06 17:46:44 -0700126 switch os.Getenv(ExecVersionVariable) {
Jiri Simsa84059da2014-06-02 17:22:05 -0700127 case "":
Mike Burrowsccca2f42015-03-27 13:57:29 -0700128 return nil, verror.New(ErrNoVersion, nil)
Jiri Simsa84059da2014-06-02 17:22:05 -0700129 case version1:
Todd Wang644ec622015-04-06 17:46:44 -0700130 os.Setenv(ExecVersionVariable, "")
Jiri Simsa84059da2014-06-02 17:22:05 -0700131 default:
Mike Burrowsccca2f42015-03-27 13:57:29 -0700132 return nil, verror.New(ErrUnsupportedVersion, nil)
Jiri Simsa84059da2014-06-02 17:22:05 -0700133 }
134 dataPipe := os.NewFile(3, "data_rd")
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700135 serializedConfig, err := decodeString(dataPipe)
Jiri Simsa84059da2014-06-02 17:22:05 -0700136 if err != nil {
137 return nil, err
138 }
Cosmos Nicolaou486d3492014-09-30 22:21:20 -0700139 cfg := NewConfig()
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700140 if err := cfg.MergeFrom(serializedConfig); err != nil {
141 return nil, err
142 }
Jiri Simsa84059da2014-06-02 17:22:05 -0700143 secret, err := decodeString(dataPipe)
144 if err != nil {
145 return nil, err
146 }
147 childHandle = &ChildHandle{
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700148 Config: cfg,
149 Secret: secret,
150 statusPipe: os.NewFile(4, "status_wr"),
Jiri Simsa84059da2014-06-02 17:22:05 -0700151 }
152 return childHandle, nil
153}
154
155func decodeString(r io.Reader) (string, error) {
Jiri Simsac199bc12014-05-30 12:52:24 -0700156 var l int64 = 0
157 if err := binary.Read(r, binary.BigEndian, &l); err != nil {
158 return "", err
159 }
160 var data []byte = make([]byte, l)
161 if n, err := r.Read(data); err != nil || int64(n) != l {
162 if err != nil {
163 return "", err
164 } else {
Mike Burrowsccca2f42015-03-27 13:57:29 -0700165 return "", verror.New(errPartialRead, nil)
Jiri Simsac199bc12014-05-30 12:52:24 -0700166 }
167 }
168 return string(data), nil
169}