Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 1 | package exec |
| 2 | |
| 3 | import ( |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 4 | "encoding/binary" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 5 | "errors" |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 6 | "io" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 7 | "os" |
Robert Kroeger | b5d6bda | 2015-01-30 16:10:49 -0800 | [diff] [blame] | 8 | "strconv" |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 9 | "sync" |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 10 | "unicode/utf8" |
Cosmos Nicolaou | a6fef89 | 2015-02-20 23:09:03 -0800 | [diff] [blame] | 11 | |
Jiri Simsa | ffceefa | 2015-02-28 11:03:34 -0800 | [diff] [blame] | 12 | "v.io/x/ref/lib/exec/consts" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 13 | ) |
| 14 | |
| 15 | var ( |
Cosmos Nicolaou | a6fef89 | 2015-02-20 23:09:03 -0800 | [diff] [blame] | 16 | 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 Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 18 | ) |
| 19 | |
| 20 | type ChildHandle struct { |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 21 | // Config is passed down from the parent. |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 22 | Config Config |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 23 | // Secret is a secret passed to the child by its parent via a |
| 24 | // trusted channel. |
| 25 | Secret string |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 26 | // 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 Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 30 | statusPipe *os.File |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 31 | |
| 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 Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 40 | } |
| 41 | |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 42 | var ( |
| 43 | childHandle *ChildHandle |
| 44 | childHandleErr error |
| 45 | once sync.Once |
| 46 | ) |
| 47 | |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 48 | // FileOffset accounts for the file descriptors that are always passed |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 49 | // 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 Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 52 | const FileOffset = 5 |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 53 | |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 54 | // 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 Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 59 | // |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 60 | // 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 Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 64 | func GetChildHandle() (*ChildHandle, error) { |
| 65 | once.Do(func() { |
| 66 | childHandle, childHandleErr = createChildHandle() |
| 67 | }) |
| 68 | return childHandle, childHandleErr |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 69 | } |
| 70 | |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 71 | func (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 Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 92 | } |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 93 | return c.statusErr |
Bogdan Caprita | 66ca353 | 2015-02-05 21:08:10 -0800 | [diff] [blame] | 94 | } |
| 95 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 96 | // SetReady writes a 'ready' status to its parent. |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 97 | // 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 Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 101 | func (c *ChildHandle) SetReady() error { |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 102 | return c.writeStatus(readyStatus, strconv.Itoa(os.Getpid())) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 103 | } |
| 104 | |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 105 | // SetFailed writes a 'failed' status to its parent. |
| 106 | func (c *ChildHandle) SetFailed(oerr error) error { |
Matt Rosencrantz | 119962e | 2015-02-12 16:08:38 -0800 | [diff] [blame] | 107 | return c.writeStatus(failedStatus, oerr.Error()) |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 108 | } |
| 109 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 110 | // 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. |
| 113 | func (c *ChildHandle) NewExtraFile(i uintptr, name string) *os.File { |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 114 | return os.NewFile(i+FileOffset, name) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 115 | } |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 116 | |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 117 | func createChildHandle() (*ChildHandle, error) { |
Cosmos Nicolaou | a6fef89 | 2015-02-20 23:09:03 -0800 | [diff] [blame] | 118 | // TODO(cnicolaou): need to use major.minor.build format for |
| 119 | // version #s. |
| 120 | switch os.Getenv(consts.ExecVersionVariable) { |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 121 | case "": |
| 122 | return nil, ErrNoVersion |
| 123 | case version1: |
Cosmos Nicolaou | a6fef89 | 2015-02-20 23:09:03 -0800 | [diff] [blame] | 124 | os.Setenv(consts.ExecVersionVariable, "") |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 125 | default: |
| 126 | return nil, ErrUnsupportedVersion |
| 127 | } |
| 128 | dataPipe := os.NewFile(3, "data_rd") |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 129 | serializedConfig, err := decodeString(dataPipe) |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 130 | if err != nil { |
| 131 | return nil, err |
| 132 | } |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 133 | cfg := NewConfig() |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 134 | if err := cfg.MergeFrom(serializedConfig); err != nil { |
| 135 | return nil, err |
| 136 | } |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 137 | secret, err := decodeString(dataPipe) |
| 138 | if err != nil { |
| 139 | return nil, err |
| 140 | } |
| 141 | childHandle = &ChildHandle{ |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 142 | Config: cfg, |
| 143 | Secret: secret, |
| 144 | statusPipe: os.NewFile(4, "status_wr"), |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 145 | } |
| 146 | return childHandle, nil |
| 147 | } |
| 148 | |
| 149 | func decodeString(r io.Reader) (string, error) { |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 150 | 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 | } |