Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // 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 Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 5 | package exec |
| 6 | |
| 7 | import ( |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 8 | "encoding/binary" |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 9 | "io" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 10 | "os" |
Robert Kroeger | b5d6bda | 2015-01-30 16:10:49 -0800 | [diff] [blame] | 11 | "strconv" |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 12 | "sync" |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 13 | "unicode/utf8" |
Cosmos Nicolaou | a6fef89 | 2015-02-20 23:09:03 -0800 | [diff] [blame] | 14 | |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 15 | "v.io/v23/verror" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 16 | ) |
| 17 | |
| 18 | var ( |
Todd Wang | 644ec62 | 2015-04-06 17:46:44 -0700 | [diff] [blame] | 19 | 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 Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 21 | |
| 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 Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 24 | ) |
| 25 | |
| 26 | type ChildHandle struct { |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 27 | // Config is passed down from the parent. |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 28 | Config Config |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 29 | // Secret is a secret passed to the child by its parent via a |
| 30 | // trusted channel. |
| 31 | Secret string |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 32 | // 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 Sivakumar | d1cc6e0 | 2015-03-16 13:58:49 -0700 | [diff] [blame] | 34 | // vanadium framework to notify the parent that the child process has |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 35 | // successfully started. |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 36 | statusPipe *os.File |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 37 | |
| 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 Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 46 | } |
| 47 | |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 48 | var ( |
| 49 | childHandle *ChildHandle |
| 50 | childHandleErr error |
| 51 | once sync.Once |
| 52 | ) |
| 53 | |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 54 | // FileOffset accounts for the file descriptors that are always passed |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 55 | // 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 Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 58 | const FileOffset = 5 |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 59 | |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 60 | // 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 Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 65 | // |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 66 | // 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 Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 70 | func GetChildHandle() (*ChildHandle, error) { |
| 71 | once.Do(func() { |
| 72 | childHandle, childHandleErr = createChildHandle() |
| 73 | }) |
| 74 | return childHandle, childHandleErr |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 75 | } |
| 76 | |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 77 | func (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 Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 97 | return verror.New(errDifferentStatusSent, nil, c.sentStatus) |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 98 | } |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 99 | return c.statusErr |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 100 | } |
| 101 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 102 | // SetReady writes a 'ready' status to its parent. |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 103 | // 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 Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 107 | func (c *ChildHandle) SetReady() error { |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 108 | return c.writeStatus(readyStatus, strconv.Itoa(os.Getpid())) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 109 | } |
| 110 | |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 111 | // SetFailed writes a 'failed' status to its parent. |
| 112 | func (c *ChildHandle) SetFailed(oerr error) error { |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 113 | return c.writeStatus(failedStatus, oerr.Error()) |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 114 | } |
| 115 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 116 | // 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. |
| 119 | func (c *ChildHandle) NewExtraFile(i uintptr, name string) *os.File { |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 120 | return os.NewFile(i+FileOffset, name) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 121 | } |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 122 | |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 123 | func createChildHandle() (*ChildHandle, error) { |
Cosmos Nicolaou | a6fef89 | 2015-02-20 23:09:03 -0800 | [diff] [blame] | 124 | // TODO(cnicolaou): need to use major.minor.build format for |
| 125 | // version #s. |
Todd Wang | 644ec62 | 2015-04-06 17:46:44 -0700 | [diff] [blame] | 126 | switch os.Getenv(ExecVersionVariable) { |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 127 | case "": |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 128 | return nil, verror.New(ErrNoVersion, nil) |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 129 | case version1: |
Todd Wang | 644ec62 | 2015-04-06 17:46:44 -0700 | [diff] [blame] | 130 | os.Setenv(ExecVersionVariable, "") |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 131 | default: |
Mike Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 132 | return nil, verror.New(ErrUnsupportedVersion, nil) |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 133 | } |
| 134 | dataPipe := os.NewFile(3, "data_rd") |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 135 | serializedConfig, err := decodeString(dataPipe) |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 136 | if err != nil { |
| 137 | return nil, err |
| 138 | } |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 139 | cfg := NewConfig() |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 140 | if err := cfg.MergeFrom(serializedConfig); err != nil { |
| 141 | return nil, err |
| 142 | } |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 143 | secret, err := decodeString(dataPipe) |
| 144 | if err != nil { |
| 145 | return nil, err |
| 146 | } |
| 147 | childHandle = &ChildHandle{ |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 148 | Config: cfg, |
| 149 | Secret: secret, |
| 150 | statusPipe: os.NewFile(4, "status_wr"), |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 151 | } |
| 152 | return childHandle, nil |
| 153 | } |
| 154 | |
| 155 | func decodeString(r io.Reader) (string, error) { |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 156 | 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 Burrows | ccca2f4 | 2015-03-27 13:57:29 -0700 | [diff] [blame] | 165 | return "", verror.New(errPartialRead, nil) |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 166 | } |
| 167 | } |
| 168 | return string(data), nil |
| 169 | } |