Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 1 | package impl |
| 2 | |
| 3 | // The app invoker is responsible for managing the state of applications on the |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 4 | // device manager. The device manager manages the applications it installs and |
| 5 | // runs using the following directory structure: |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 6 | // |
| 7 | // TODO(caprita): Not all is yet implemented. |
| 8 | // |
| 9 | // <config.Root>/ |
| 10 | // app-<hash 1>/ - the application dir is named using a hash of the application title |
| 11 | // installation-<id 1>/ - installations are labelled with ids |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 12 | // acls/ |
| 13 | // data - the ACL data for this |
| 14 | // installation. Controls acces to |
| 15 | // Start, Uinstall, Update, UpdateTo |
| 16 | // and Revert. |
| 17 | // signature - the signature for the ACLs in data |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 18 | // <status> - one of the values for installationState enum |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 19 | // origin - object name for application envelope |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 20 | // <version 1 timestamp>/ - timestamp of when the version was downloaded |
| 21 | // bin - application binary |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 22 | // previous - symbolic link to previous version directory |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 23 | // envelope - application envelope (JSON-encoded) |
Robin Thellend | e262789 | 2014-11-26 09:34:37 -0800 | [diff] [blame] | 24 | // pkg/ - the application packages |
| 25 | // <pkg name> |
| 26 | // <pkg name>.__info |
| 27 | // ... |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 28 | // <version 2 timestamp> |
| 29 | // ... |
| 30 | // current - symbolic link to the current version |
| 31 | // instances/ |
| 32 | // instance-<id a>/ - instances are labelled with ids |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 33 | // credentials/ - holds veyron credentials (unless running |
| 34 | // through security agent) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 35 | // root/ - workspace that the instance is run from |
Robin Thellend | e262789 | 2014-11-26 09:34:37 -0800 | [diff] [blame] | 36 | // packages/ - the installed packages |
| 37 | // <pkg name>/ |
| 38 | // ... |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 39 | // logs/ - stderr/stdout and log files generated by instance |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 40 | // info - metadata for the instance (such as app |
| 41 | // cycle manager name and process id) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 42 | // version - symbolic link to installation version for the instance |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 43 | // acls/ |
| 44 | // data - the ACLs for this instance. These |
| 45 | // ACLs control access to Refresh, |
| 46 | // Restart, Resume, Stop and |
| 47 | // Suspend. |
| 48 | // signature - the signature for these ACLs. |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 49 | // <status> - one of the values for instanceState enum |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 50 | // systemname - the system name used to execute this instance |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 51 | // instance-<id b> |
| 52 | // ... |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 53 | // installation-<id 2> |
| 54 | // ... |
| 55 | // app-<hash 2> |
| 56 | // ... |
| 57 | // |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 58 | // The device manager uses the suid helper binary to invoke an application as a |
Bogdan Caprita | 962d5e0 | 2014-10-28 18:36:09 -0700 | [diff] [blame] | 59 | // specified user. The path to the helper is specified as config.Helper. |
| 60 | |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 61 | // When device manager starts up, it goes through all instances and resumes the |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 62 | // ones that are not suspended. If the application was still running, it |
| 63 | // suspends it first. If an application fails to resume, it stays suspended. |
| 64 | // |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 65 | // When device manager shuts down, it suspends all running instances. |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 66 | // |
| 67 | // Start starts an instance. Suspend kills the process but leaves the workspace |
| 68 | // untouched. Resume restarts the process. Stop kills the process and prevents |
| 69 | // future resumes (it also eventually gc's the workspace). |
| 70 | // |
| 71 | // If the process dies on its own, it stays dead and is assumed suspended. |
| 72 | // TODO(caprita): Later, we'll add auto-restart option. |
| 73 | // |
| 74 | // Concurrency model: installations can be created independently of one another; |
| 75 | // installations can be removed at any time (any running instances will be |
| 76 | // stopped). The first call to Uninstall will rename the installation dir as a |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 77 | // first step; subsequent Uninstall's will fail. Instances can be created |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 78 | // independently of one another, as long as the installation exists (if it gets |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 79 | // Uninstall'ed during an instance Start, the Start may fail). |
| 80 | // |
| 81 | // The status file present in each instance is used to flag the state of the |
| 82 | // instance and prevent concurrent operations against the instance: |
| 83 | // |
| 84 | // - when an instance is created with Start, it is placed in state 'suspended'. |
| 85 | // To run the instance, Start transitions 'suspended' to 'starting' and then |
| 86 | // 'started' (upon success) or the instance is deleted (upon failure). |
| 87 | // |
| 88 | // - Suspend attempts to transition from 'started' to 'suspending' (if the |
| 89 | // instance was not in 'started' state, Suspend fails). From 'suspending', the |
| 90 | // instance transitions to 'suspended' upon success or back to 'started' upon |
| 91 | // failure. |
| 92 | // |
| 93 | // - Resume attempts to transition from 'suspended' to 'starting' (if the |
| 94 | // instance was not in 'suspended' state, Resume fails). From 'starting', the |
| 95 | // instance transitions to 'started' upon success or back to 'suspended' upon |
| 96 | // failure. |
| 97 | // |
| 98 | // - Stop attempts to transition from 'started' to 'stopping' and then to |
| 99 | // 'stopped' (upon success) or back to 'started' (upon failure); or from |
| 100 | // 'suspended' to 'stopped'. If the initial state is neither 'started' or |
| 101 | // 'suspended', Stop fails. |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 102 | // |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 103 | // TODO(caprita): There is room for synergy between how device manager organizes |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 104 | // its own workspace and that for the applications it runs. In particular, |
| 105 | // previous, origin, and envelope could be part of a single config. We'll |
| 106 | // refine that later. |
| 107 | |
| 108 | import ( |
| 109 | "crypto/md5" |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 110 | "crypto/rand" |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 111 | "encoding/base64" |
| 112 | "encoding/binary" |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 113 | "encoding/hex" |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 114 | "encoding/json" |
| 115 | "fmt" |
| 116 | "hash/crc64" |
| 117 | "io/ioutil" |
| 118 | "os" |
| 119 | "os/exec" |
Robert Kroeger | dd07b36 | 2014-09-18 17:34:42 -0700 | [diff] [blame] | 120 | "os/user" |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 121 | "path" |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 122 | "path/filepath" |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 123 | "reflect" |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 124 | "strconv" |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 125 | "strings" |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 126 | "sync" |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 127 | "time" |
| 128 | |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 129 | "v.io/core/veyron2" |
| 130 | "v.io/core/veyron2/context" |
| 131 | "v.io/core/veyron2/ipc" |
| 132 | "v.io/core/veyron2/mgmt" |
| 133 | "v.io/core/veyron2/naming" |
| 134 | "v.io/core/veyron2/options" |
| 135 | "v.io/core/veyron2/security" |
| 136 | "v.io/core/veyron2/services/mgmt/appcycle" |
| 137 | "v.io/core/veyron2/services/mgmt/application" |
| 138 | "v.io/core/veyron2/services/security/access" |
| 139 | "v.io/core/veyron2/verror2" |
| 140 | "v.io/core/veyron2/vlog" |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 141 | |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 142 | vexec "v.io/core/veyron/lib/exec" |
| 143 | "v.io/core/veyron/lib/flags/consts" |
| 144 | vsecurity "v.io/core/veyron/security" |
| 145 | "v.io/core/veyron/security/agent" |
| 146 | "v.io/core/veyron/security/agent/keymgr" |
| 147 | iconfig "v.io/core/veyron/services/mgmt/device/config" |
| 148 | libbinary "v.io/core/veyron/services/mgmt/lib/binary" |
| 149 | libpackages "v.io/core/veyron/services/mgmt/lib/packages" |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 150 | ) |
| 151 | |
| 152 | // instanceInfo holds state about a running instance. |
| 153 | type instanceInfo struct { |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 154 | AppCycleMgrName string |
| 155 | Pid int |
| 156 | DeviceManagerPeerPattern string |
| 157 | SecurityAgentHandle []byte |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 158 | } |
| 159 | |
| 160 | func saveInstanceInfo(dir string, info *instanceInfo) error { |
| 161 | jsonInfo, err := json.Marshal(info) |
| 162 | if err != nil { |
| 163 | vlog.Errorf("Marshal(%v) failed: %v", info, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 164 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 165 | } |
| 166 | infoPath := filepath.Join(dir, "info") |
| 167 | if err := ioutil.WriteFile(infoPath, jsonInfo, 0600); err != nil { |
| 168 | vlog.Errorf("WriteFile(%v) failed: %v", infoPath, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 169 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 170 | } |
| 171 | return nil |
| 172 | } |
| 173 | |
| 174 | func loadInstanceInfo(dir string) (*instanceInfo, error) { |
| 175 | infoPath := filepath.Join(dir, "info") |
| 176 | info := new(instanceInfo) |
| 177 | if infoBytes, err := ioutil.ReadFile(infoPath); err != nil { |
| 178 | vlog.Errorf("ReadFile(%v) failed: %v", infoPath, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 179 | return nil, verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 180 | } else if err := json.Unmarshal(infoBytes, info); err != nil { |
| 181 | vlog.Errorf("Unmarshal(%v) failed: %v", infoBytes, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 182 | return nil, verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 183 | } |
| 184 | return info, nil |
| 185 | } |
| 186 | |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 187 | type securityAgentState struct { |
| 188 | // Security agent key manager client. |
| 189 | keyMgrAgent *keymgr.Agent |
| 190 | // Ensures only one security agent connection socket is created |
| 191 | // at any time, preventing fork/exec from potentially passing |
| 192 | // down sockets meant for other children (as per ribrdb@, Go's |
| 193 | // exec implementation does not prune the set of files passed |
| 194 | // down to only include those specified in cmd.ExtraFiles). |
| 195 | startLock sync.Mutex |
| 196 | } |
| 197 | |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 198 | // appService implements the Device manager's Application interface. |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 199 | type appService struct { |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 200 | callback *callbackState |
| 201 | config *iconfig.State |
| 202 | // suffix contains the name components of the current invocation name |
| 203 | // suffix. It is used to identify an application, installation, or |
| 204 | // instance. |
| 205 | suffix []string |
Robert Kroeger | 1cb4a0d | 2014-10-20 11:55:38 -0700 | [diff] [blame] | 206 | uat BlessingSystemAssociationStore |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 207 | locks aclLocks |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 208 | // Reference to the devicemanager top-level ACL list. |
| 209 | deviceACL access.TaggedACLMap |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 210 | // securityAgent holds state related to the security agent (nil if not |
| 211 | // using the agent). |
| 212 | securityAgent *securityAgentState |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 213 | } |
| 214 | |
| 215 | func saveEnvelope(dir string, envelope *application.Envelope) error { |
| 216 | jsonEnvelope, err := json.Marshal(envelope) |
| 217 | if err != nil { |
| 218 | vlog.Errorf("Marshal(%v) failed: %v", envelope, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 219 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 220 | } |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 221 | path := filepath.Join(dir, "envelope") |
| 222 | if err := ioutil.WriteFile(path, jsonEnvelope, 0600); err != nil { |
| 223 | vlog.Errorf("WriteFile(%v) failed: %v", path, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 224 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 225 | } |
| 226 | return nil |
| 227 | } |
| 228 | |
| 229 | func loadEnvelope(dir string) (*application.Envelope, error) { |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 230 | path := filepath.Join(dir, "envelope") |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 231 | envelope := new(application.Envelope) |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 232 | if envelopeBytes, err := ioutil.ReadFile(path); err != nil { |
| 233 | vlog.Errorf("ReadFile(%v) failed: %v", path, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 234 | return nil, verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 235 | } else if err := json.Unmarshal(envelopeBytes, envelope); err != nil { |
| 236 | vlog.Errorf("Unmarshal(%v) failed: %v", envelopeBytes, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 237 | return nil, verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 238 | } |
| 239 | return envelope, nil |
| 240 | } |
| 241 | |
| 242 | func saveOrigin(dir, originVON string) error { |
| 243 | path := filepath.Join(dir, "origin") |
| 244 | if err := ioutil.WriteFile(path, []byte(originVON), 0600); err != nil { |
| 245 | vlog.Errorf("WriteFile(%v) failed: %v", path, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 246 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 247 | } |
| 248 | return nil |
| 249 | } |
| 250 | |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 251 | func loadOrigin(dir string) (string, error) { |
| 252 | path := filepath.Join(dir, "origin") |
| 253 | if originBytes, err := ioutil.ReadFile(path); err != nil { |
| 254 | vlog.Errorf("ReadFile(%v) failed: %v", path, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 255 | return "", verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 256 | } else { |
| 257 | return string(originBytes), nil |
| 258 | } |
| 259 | } |
| 260 | |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 261 | // generateID returns a new unique id string. The uniqueness is based on the |
| 262 | // current timestamp. Not cryptographically secure. |
| 263 | func generateID() string { |
| 264 | timestamp := fmt.Sprintf("%v", time.Now().Format(time.RFC3339Nano)) |
| 265 | h := crc64.New(crc64.MakeTable(crc64.ISO)) |
| 266 | h.Write([]byte(timestamp)) |
| 267 | b := make([]byte, 8) |
| 268 | binary.LittleEndian.PutUint64(b, uint64(h.Sum64())) |
| 269 | return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=") |
| 270 | } |
| 271 | |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 272 | // generateRandomString returns a cryptographically-strong random string. |
| 273 | func generateRandomString() (string, error) { |
| 274 | b := make([]byte, 16) |
| 275 | _, err := rand.Read(b) |
| 276 | if err != nil { |
| 277 | return "", err |
| 278 | } |
| 279 | return hex.EncodeToString(b), nil |
| 280 | } |
| 281 | |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 282 | // TODO(caprita): Nothing prevents different applications from sharing the same |
| 283 | // title, and thereby being installed in the same app dir. Do we want to |
| 284 | // prevent that for the same user or across users? |
| 285 | |
| 286 | // applicationDirName generates a cryptographic hash of the application title, |
| 287 | // to be used as a directory name for installations of the application with the |
| 288 | // given title. |
| 289 | func applicationDirName(title string) string { |
| 290 | h := md5.New() |
| 291 | h.Write([]byte(title)) |
| 292 | hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=") |
| 293 | return "app-" + hash |
| 294 | } |
| 295 | |
| 296 | func installationDirName(installationID string) string { |
| 297 | return "installation-" + installationID |
| 298 | } |
| 299 | |
| 300 | func instanceDirName(instanceID string) string { |
| 301 | return "instance-" + instanceID |
| 302 | } |
| 303 | |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 304 | func mkdir(dir string) error { |
| 305 | perm := os.FileMode(0700) |
| 306 | if err := os.MkdirAll(dir, perm); err != nil { |
| 307 | vlog.Errorf("MkdirAll(%v, %v) failed: %v", dir, perm, err) |
| 308 | return err |
| 309 | } |
| 310 | return nil |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 311 | } |
| 312 | |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 313 | func fetchAppEnvelope(ctx context.T, origin string) (*application.Envelope, error) { |
| 314 | envelope, err := fetchEnvelope(ctx, origin) |
| 315 | if err != nil { |
| 316 | return nil, err |
| 317 | } |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 318 | if envelope.Title == application.DeviceManagerTitle { |
| 319 | // Disallow device manager apps from being installed like a |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 320 | // regular app. |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 321 | return nil, verror2.Make(ErrInvalidOperation, ctx) |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 322 | } |
| 323 | return envelope, nil |
| 324 | } |
| 325 | |
| 326 | // newVersion sets up the directory for a new application version. |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 327 | func newVersion(ctx context.T, installationDir string, envelope *application.Envelope, oldVersionDir string) (string, error) { |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 328 | versionDir := filepath.Join(installationDir, generateVersionDirName()) |
| 329 | if err := mkdir(versionDir); err != nil { |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 330 | return "", verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 331 | } |
Robin Thellend | e262789 | 2014-11-26 09:34:37 -0800 | [diff] [blame] | 332 | pkgDir := filepath.Join(versionDir, "pkg") |
| 333 | if err := mkdir(pkgDir); err != nil { |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 334 | return "", verror2.Make(ErrOperationFailed, nil) |
Robin Thellend | e262789 | 2014-11-26 09:34:37 -0800 | [diff] [blame] | 335 | } |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 336 | // TODO(caprita): Share binaries if already existing locally. |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 337 | if err := downloadBinary(ctx, versionDir, "bin", envelope.Binary); err != nil { |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 338 | return versionDir, err |
| 339 | } |
Robin Thellend | e262789 | 2014-11-26 09:34:37 -0800 | [diff] [blame] | 340 | for localPkg, pkgName := range envelope.Packages { |
| 341 | if localPkg == "" || localPkg[0] == '.' || strings.Contains(localPkg, string(filepath.Separator)) { |
| 342 | vlog.Infof("invalid local package name: %q", localPkg) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 343 | return versionDir, verror2.Make(ErrOperationFailed, nil) |
Robin Thellend | e262789 | 2014-11-26 09:34:37 -0800 | [diff] [blame] | 344 | } |
| 345 | path := filepath.Join(pkgDir, localPkg) |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 346 | if err := libbinary.DownloadToFile(ctx, pkgName, path); err != nil { |
Robin Thellend | e262789 | 2014-11-26 09:34:37 -0800 | [diff] [blame] | 347 | vlog.Infof("DownloadToFile(%q, %q) failed: %v", pkgName, path, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 348 | return versionDir, verror2.Make(ErrOperationFailed, nil) |
Robin Thellend | e262789 | 2014-11-26 09:34:37 -0800 | [diff] [blame] | 349 | } |
| 350 | } |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 351 | if err := saveEnvelope(versionDir, envelope); err != nil { |
| 352 | return versionDir, err |
| 353 | } |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 354 | if oldVersionDir != "" { |
| 355 | previousLink := filepath.Join(versionDir, "previous") |
| 356 | if err := os.Symlink(oldVersionDir, previousLink); err != nil { |
| 357 | vlog.Errorf("Symlink(%v, %v) failed: %v", oldVersionDir, previousLink, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 358 | return versionDir, verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 359 | } |
| 360 | } |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 361 | // updateLink should be the last thing we do, after we've ensured the |
| 362 | // new version is viable (currently, that just means it installs |
| 363 | // properly). |
| 364 | return versionDir, updateLink(versionDir, filepath.Join(installationDir, "current")) |
| 365 | } |
| 366 | |
Asim Shankar | 6888519 | 2014-11-26 12:48:35 -0800 | [diff] [blame] | 367 | // TODO(rjkroege): Refactor this code with the instance creation code. |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 368 | func initializeInstallationACLs(principal security.Principal, dir string, blessings []string, acl access.TaggedACLMap) error { |
Asim Shankar | 6888519 | 2014-11-26 12:48:35 -0800 | [diff] [blame] | 369 | // Add the invoker's blessings. |
| 370 | for _, b := range blessings { |
| 371 | for _, tag := range access.AllTypicalTags() { |
| 372 | acl.Add(security.BlessingPattern(b), string(tag)) |
| 373 | } |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 374 | } |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 375 | aclDir := path.Join(dir, "acls") |
| 376 | aclData := path.Join(aclDir, "data") |
| 377 | aclSig := path.Join(aclDir, "signature") |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 378 | return writeACLs(principal, aclData, aclSig, aclDir, acl) |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 379 | } |
| 380 | |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 381 | func (i *appService) Install(call ipc.ServerContext, applicationVON string) (string, error) { |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 382 | if len(i.suffix) > 0 { |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 383 | return "", verror2.Make(ErrInvalidSuffix, call.Context()) |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 384 | } |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 385 | ctx, cancel := call.Context().WithTimeout(ipcContextTimeout) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 386 | defer cancel() |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 387 | envelope, err := fetchAppEnvelope(ctx, applicationVON) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 388 | if err != nil { |
| 389 | return "", err |
| 390 | } |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 391 | installationID := generateID() |
| 392 | installationDir := filepath.Join(i.config.Root, applicationDirName(envelope.Title), installationDirName(installationID)) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 393 | deferrer := func() { |
Robert Kroeger | 94ec756 | 2014-10-28 17:58:44 -0700 | [diff] [blame] | 394 | cleanupDir(installationDir, "") |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 395 | } |
| 396 | defer func() { |
| 397 | if deferrer != nil { |
| 398 | deferrer() |
| 399 | } |
| 400 | }() |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 401 | if _, err := newVersion(call.Context(), installationDir, envelope, ""); err != nil { |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 402 | return "", err |
| 403 | } |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 404 | if err := saveOrigin(installationDir, applicationVON); err != nil { |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 405 | return "", err |
| 406 | } |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 407 | if err := initializeInstallation(installationDir, active); err != nil { |
| 408 | return "", err |
| 409 | } |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 410 | |
Asim Shankar | 6888519 | 2014-11-26 12:48:35 -0800 | [diff] [blame] | 411 | // TODO(caprita,rjkroege): Should the installation ACLs really be |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 412 | // seeded with the device ACL? Instead, might want to hide the deviceACL |
Asim Shankar | 6888519 | 2014-11-26 12:48:35 -0800 | [diff] [blame] | 413 | // from the app? |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 414 | if err := initializeInstallationACLs(call.LocalPrincipal(), installationDir, call.RemoteBlessings().ForContext(call), i.deviceACL.Copy()); err != nil { |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 415 | return "", err |
| 416 | } |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 417 | deferrer = nil |
| 418 | return naming.Join(envelope.Title, installationID), nil |
| 419 | } |
| 420 | |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 421 | func (*appService) Refresh(ipc.ServerContext) error { |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 422 | // TODO(jsimsa): Implement. |
| 423 | return nil |
| 424 | } |
| 425 | |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 426 | func (*appService) Restart(ipc.ServerContext) error { |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 427 | // TODO(jsimsa): Implement. |
| 428 | return nil |
| 429 | } |
| 430 | |
Bogdan Caprita | 25d4faa | 2014-08-28 10:21:23 -0700 | [diff] [blame] | 431 | func openWriteFile(path string) (*os.File, error) { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 432 | perm := os.FileMode(0600) |
Bogdan Caprita | 25d4faa | 2014-08-28 10:21:23 -0700 | [diff] [blame] | 433 | file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, perm) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 434 | if err != nil { |
Bogdan Caprita | 25d4faa | 2014-08-28 10:21:23 -0700 | [diff] [blame] | 435 | vlog.Errorf("OpenFile(%v) failed: %v", path, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 436 | return nil, verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 437 | } |
Bogdan Caprita | 25d4faa | 2014-08-28 10:21:23 -0700 | [diff] [blame] | 438 | return file, nil |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 439 | } |
| 440 | |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 441 | func installationDirCore(components []string, root string) (string, error) { |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 442 | if nComponents := len(components); nComponents != 2 { |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 443 | return "", verror2.Make(ErrInvalidSuffix, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 444 | } |
| 445 | app, installation := components[0], components[1] |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 446 | installationDir := filepath.Join(root, applicationDirName(app), installationDirName(installation)) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 447 | if _, err := os.Stat(installationDir); err != nil { |
| 448 | if os.IsNotExist(err) { |
Mike Burrows | d65df96 | 2014-12-17 10:01:19 -0800 | [diff] [blame] | 449 | return "", verror2.Make(verror2.NoExist, nil, naming.Join(components...)) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 450 | } |
| 451 | vlog.Errorf("Stat(%v) failed: %v", installationDir, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 452 | return "", verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 453 | } |
| 454 | return installationDir, nil |
| 455 | } |
| 456 | |
Bogdan Caprita | 730bde1 | 2014-11-08 15:35:43 -0800 | [diff] [blame] | 457 | // setupPrincipal sets up the instance's principal, with the right blessings. |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 458 | func setupPrincipal(ctx context.T, instanceDir, versionDir string, call ipc.ServerContext, securityAgent *securityAgentState, info *instanceInfo) error { |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 459 | var p security.Principal |
| 460 | if securityAgent != nil { |
| 461 | // TODO(caprita): Part of the cleanup upon destroying an |
| 462 | // instance, we should tell the agent to drop the principal. |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 463 | handle, conn, err := securityAgent.keyMgrAgent.NewPrincipal(ctx, false) |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 464 | defer conn.Close() |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 465 | |
| 466 | runtime := veyron2.RuntimeFromContext(ctx) |
| 467 | client, err := runtime.NewClient(options.VCSecurityNone) |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 468 | if err != nil { |
| 469 | vlog.Errorf("NewClient() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 470 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 471 | } |
| 472 | defer client.Close() |
| 473 | // TODO(caprita): release the socket created by NewAgentPrincipal. |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 474 | if p, err = agent.NewAgentPrincipal(client, int(conn.Fd()), ctx); err != nil { |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 475 | vlog.Errorf("NewAgentPrincipal() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 476 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 477 | } |
| 478 | info.SecurityAgentHandle = handle |
| 479 | } else { |
| 480 | credentialsDir := filepath.Join(instanceDir, "credentials") |
| 481 | // TODO(caprita): The app's system user id needs access to this dir. |
| 482 | // Use the suidhelper to chown it. |
| 483 | var err error |
| 484 | if p, err = vsecurity.CreatePersistentPrincipal(credentialsDir, nil); err != nil { |
| 485 | vlog.Errorf("CreatePersistentPrincipal(%v, nil) failed: %v", credentialsDir, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 486 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 487 | } |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 488 | } |
| 489 | // Read the app installation version's envelope to obtain the app title. |
| 490 | // |
| 491 | // NOTE: we could have gotten this from the suffix as well, but the |
| 492 | // format of the object name suffix may change in the future: there's no |
| 493 | // guarantee it will always include the title. |
| 494 | envelope, err := loadEnvelope(versionDir) |
| 495 | if err != nil { |
| 496 | return err |
| 497 | } |
Bogdan Caprita | 9c4aa22 | 2014-12-10 14:46:30 -0800 | [diff] [blame] | 498 | dmPrincipal := call.LocalPrincipal() |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 499 | // Take the blessings conferred upon us by the Start-er, extend them |
| 500 | // with the app title. |
Bogdan Caprita | 730bde1 | 2014-11-08 15:35:43 -0800 | [diff] [blame] | 501 | grantedBlessings := call.Blessings() |
| 502 | if grantedBlessings == nil { |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 503 | return verror2.Make(ErrInvalidBlessing, nil) |
Bogdan Caprita | 730bde1 | 2014-11-08 15:35:43 -0800 | [diff] [blame] | 504 | } |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 505 | // TODO(caprita): Revisit UnconstrainedUse. |
Bogdan Caprita | 9c4aa22 | 2014-12-10 14:46:30 -0800 | [diff] [blame] | 506 | appBlessings, err := dmPrincipal.Bless(p.PublicKey(), grantedBlessings, envelope.Title, security.UnconstrainedUse()) |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 507 | if err != nil { |
| 508 | vlog.Errorf("Bless() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 509 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 510 | } |
| 511 | // The blessings we extended from the blessings that the Start-er |
| 512 | // granted are the default blessings for the app. |
| 513 | if err := p.BlessingStore().SetDefault(appBlessings); err != nil { |
| 514 | vlog.Errorf("BlessingStore.SetDefault() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 515 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 516 | } |
| 517 | if _, err := p.BlessingStore().Set(appBlessings, security.AllPrincipals); err != nil { |
| 518 | vlog.Errorf("BlessingStore.Set() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 519 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 520 | } |
| 521 | if err := p.AddToRoots(appBlessings); err != nil { |
| 522 | vlog.Errorf("AddToRoots() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 523 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 524 | } |
| 525 | // In addition, we give the app separate blessings for the purpose of |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 526 | // communicating with the device manager. |
Bogdan Caprita | 730bde1 | 2014-11-08 15:35:43 -0800 | [diff] [blame] | 527 | // |
| 528 | // NOTE(caprita/ataly): Giving the app an unconstrained blessing from |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 529 | // the device manager's default presents the app with a blessing that's |
Bogdan Caprita | 730bde1 | 2014-11-08 15:35:43 -0800 | [diff] [blame] | 530 | // potentially more powerful than what is strictly needed to allow |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 531 | // communication between device manager and app (which could be more |
Bogdan Caprita | 730bde1 | 2014-11-08 15:35:43 -0800 | [diff] [blame] | 532 | // narrowly accomplished by using a custom-purpose self-signed blessing |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 533 | // that the device manger produces solely to talk to the app). |
Bogdan Caprita | 730bde1 | 2014-11-08 15:35:43 -0800 | [diff] [blame] | 534 | // |
| 535 | // TODO(caprita): Figure out if there is any feature value in providing |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 536 | // the app with a device manager-derived blessing (e.g., may the app |
| 537 | // need to prove it's running on the device?). |
Bogdan Caprita | 9c4aa22 | 2014-12-10 14:46:30 -0800 | [diff] [blame] | 538 | dmBlessings, err := dmPrincipal.Bless(p.PublicKey(), dmPrincipal.BlessingStore().Default(), "callback", security.UnconstrainedUse()) |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 539 | // Put the names of the device manager's default blessings as patterns |
| 540 | // for the child, so that the child uses the right blessing when talking |
| 541 | // back to the device manager. |
Bogdan Caprita | 9c4aa22 | 2014-12-10 14:46:30 -0800 | [diff] [blame] | 542 | names := dmPrincipal.BlessingStore().Default().ForContext(call) |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 543 | for _, n := range names { |
Bogdan Caprita | 9c4aa22 | 2014-12-10 14:46:30 -0800 | [diff] [blame] | 544 | if _, err := p.BlessingStore().Set(dmBlessings, security.BlessingPattern(n)); err != nil { |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 545 | vlog.Errorf("BlessingStore.Set() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 546 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 547 | } |
| 548 | } |
| 549 | // We also want to override the app cycle manager's server blessing in |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 550 | // the child (so that the device manager can send RPCs to it). We |
| 551 | // signal to the child's app manager to use a randomly generated pattern |
| 552 | // to extract the right blessing to use from its store for this purpose. |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 553 | randomPattern, err := generateRandomString() |
| 554 | if err != nil { |
| 555 | vlog.Errorf("generateRandomString() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 556 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 557 | } |
Bogdan Caprita | 9c4aa22 | 2014-12-10 14:46:30 -0800 | [diff] [blame] | 558 | if _, err := p.BlessingStore().Set(dmBlessings, security.BlessingPattern(randomPattern)); err != nil { |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 559 | vlog.Errorf("BlessingStore.Set() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 560 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 561 | } |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 562 | info.DeviceManagerPeerPattern = randomPattern |
Bogdan Caprita | 9c4aa22 | 2014-12-10 14:46:30 -0800 | [diff] [blame] | 563 | if err := p.AddToRoots(dmBlessings); err != nil { |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 564 | vlog.Errorf("AddToRoots() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 565 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 566 | } |
| 567 | return nil |
| 568 | } |
| 569 | |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 570 | // installationDir returns the path to the directory containing the app |
| 571 | // installation referred to by the invoker's suffix. Returns an error if the |
| 572 | // suffix does not name an installation or if the named installation does not |
| 573 | // exist. |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 574 | func (i *appService) installationDir() (string, error) { |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 575 | return installationDirCore(i.suffix, i.config.Root) |
| 576 | } |
| 577 | |
Robin Thellend | e262789 | 2014-11-26 09:34:37 -0800 | [diff] [blame] | 578 | // installPackages installs all the packages for a new instance. |
| 579 | func installPackages(versionDir, instanceDir string) error { |
| 580 | envelope, err := loadEnvelope(versionDir) |
| 581 | if err != nil { |
| 582 | return err |
| 583 | } |
| 584 | packagesDir := filepath.Join(instanceDir, "root", "packages") |
| 585 | if err := os.MkdirAll(packagesDir, os.FileMode(0700)); err != nil { |
| 586 | return err |
| 587 | } |
| 588 | // TODO(rthellend): Consider making the packages read-only and sharing |
| 589 | // them between apps or instances. |
| 590 | for pkg, _ := range envelope.Packages { |
| 591 | pkgFile := filepath.Join(versionDir, "pkg", pkg) |
| 592 | dstDir := filepath.Join(packagesDir, pkg) |
| 593 | if err := os.MkdirAll(dstDir, os.FileMode(0700)); err != nil { |
| 594 | return err |
| 595 | } |
| 596 | if err := libpackages.Install(pkgFile, dstDir); err != nil { |
| 597 | return err |
| 598 | } |
| 599 | } |
| 600 | return nil |
| 601 | } |
| 602 | |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 603 | func initializeInstanceACLs(principal security.Principal, instanceDir string, blessings []string, acl access.TaggedACLMap) error { |
Asim Shankar | 6888519 | 2014-11-26 12:48:35 -0800 | [diff] [blame] | 604 | for _, b := range blessings { |
| 605 | for _, tag := range access.AllTypicalTags() { |
| 606 | acl.Add(security.BlessingPattern(b), string(tag)) |
| 607 | } |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 608 | } |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 609 | aclDir := path.Join(instanceDir, "acls") |
| 610 | aclData := path.Join(aclDir, "data") |
| 611 | aclSig := path.Join(aclDir, "signature") |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 612 | return writeACLs(principal, aclData, aclSig, aclDir, acl) |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 613 | } |
| 614 | |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 615 | // newInstance sets up the directory for a new application instance. |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 616 | func (i *appService) newInstance(call ipc.ServerContext) (string, string, error) { |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 617 | installationDir, err := i.installationDir() |
| 618 | if err != nil { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 619 | return "", "", err |
| 620 | } |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 621 | if !installationStateIs(installationDir, active) { |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 622 | return "", "", verror2.Make(ErrInvalidOperation, call.Context()) |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 623 | } |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 624 | instanceID := generateID() |
| 625 | instanceDir := filepath.Join(installationDir, "instances", instanceDirName(instanceID)) |
| 626 | if mkdir(instanceDir) != nil { |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 627 | return "", instanceID, verror2.Make(ErrOperationFailed, call.Context()) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 628 | } |
| 629 | currLink := filepath.Join(installationDir, "current") |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 630 | versionDir, err := filepath.EvalSymlinks(currLink) |
| 631 | if err != nil { |
| 632 | vlog.Errorf("EvalSymlinks(%v) failed: %v", currLink, err) |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 633 | return instanceDir, instanceID, verror2.Make(ErrOperationFailed, call.Context()) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 634 | } |
| 635 | versionLink := filepath.Join(instanceDir, "version") |
| 636 | if err := os.Symlink(versionDir, versionLink); err != nil { |
| 637 | vlog.Errorf("Symlink(%v, %v) failed: %v", versionDir, versionLink, err) |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 638 | return instanceDir, instanceID, verror2.Make(ErrOperationFailed, call.Context()) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 639 | } |
Robin Thellend | e262789 | 2014-11-26 09:34:37 -0800 | [diff] [blame] | 640 | if err := installPackages(versionDir, instanceDir); err != nil { |
| 641 | vlog.Errorf("installPackages(%v, %v) failed: %v", versionDir, instanceDir, err) |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 642 | return instanceDir, instanceID, verror2.Make(ErrOperationFailed, call.Context()) |
Robin Thellend | e262789 | 2014-11-26 09:34:37 -0800 | [diff] [blame] | 643 | } |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 644 | instanceInfo := new(instanceInfo) |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 645 | if err := setupPrincipal(call.Context(), instanceDir, versionDir, call, i.securityAgent, instanceInfo); err != nil { |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 646 | return instanceDir, instanceID, err |
| 647 | } |
| 648 | if err := saveInstanceInfo(instanceDir, instanceInfo); err != nil { |
| 649 | return instanceDir, instanceID, err |
| 650 | } |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 651 | if err := initializeInstance(instanceDir, suspended); err != nil { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 652 | return instanceDir, instanceID, err |
| 653 | } |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 654 | |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 655 | if err := initializeInstanceACLs(call.LocalPrincipal(), instanceDir, call.RemoteBlessings().ForContext(call), i.deviceACL.Copy()); err != nil { |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 656 | return instanceDir, instanceID, err |
| 657 | } |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 658 | return instanceDir, instanceID, nil |
| 659 | } |
| 660 | |
Robert Kroeger | 362ff89 | 2014-09-29 14:23:47 -0700 | [diff] [blame] | 661 | // isSetuid is defined like this so we can override its |
| 662 | // implementation for tests. |
| 663 | var isSetuid = func(fileStat os.FileInfo) bool { |
| 664 | vlog.VI(2).Infof("running the original isSetuid") |
| 665 | return fileStat.Mode()&os.ModeSetuid == os.ModeSetuid |
| 666 | } |
| 667 | |
| 668 | // systemAccountForHelper returns the uname that the helper uses to invoke the |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 669 | // application. If the helper exists and is setuid, the device manager |
Robert Kroeger | 362ff89 | 2014-09-29 14:23:47 -0700 | [diff] [blame] | 670 | // requires that there is a uname associated with the Veyron |
| 671 | // identity that requested starting an application. |
| 672 | // TODO(rjkroege): This function assumes a desktop installation target |
| 673 | // and is probably not a good fit in other contexts. Revisit the design |
| 674 | // as appropriate. This function also internalizes a decision as to when |
| 675 | // it is possible to start an application that needs to be made explicit. |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 676 | func systemAccountForHelper(ctx ipc.ServerContext, helperPath string, uat BlessingSystemAssociationStore) (systemName string, err error) { |
| 677 | identityNames := ctx.RemoteBlessings().ForContext(ctx) |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 678 | helperStat, err := os.Stat(helperPath) |
| 679 | if err != nil { |
| 680 | vlog.Errorf("Stat(%v) failed: %v. helper is required.", helperPath, err) |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 681 | return "", verror2.Make(ErrOperationFailed, ctx.Context()) |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 682 | } |
Robert Kroeger | 362ff89 | 2014-09-29 14:23:47 -0700 | [diff] [blame] | 683 | haveHelper := isSetuid(helperStat) |
Robert Kroeger | 1cb4a0d | 2014-10-20 11:55:38 -0700 | [diff] [blame] | 684 | systemName, present := uat.SystemAccountForBlessings(identityNames) |
Robert Kroeger | 362ff89 | 2014-09-29 14:23:47 -0700 | [diff] [blame] | 685 | |
| 686 | switch { |
| 687 | case haveHelper && present: |
| 688 | return systemName, nil |
| 689 | case haveHelper && !present: |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 690 | // The helper is owned by the device manager and installed as |
| 691 | // setuid root. Therefore, the device manager must never run an |
| 692 | // app as itself to prevent an app trivially granting itself |
| 693 | // root permissions. There must be an associated uname for the |
| 694 | // account in this case. |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 695 | return "", verror2.Make(verror2.NoAccess, ctx.Context(), "use of setuid helper requires an associated uname.") |
Robert Kroeger | 362ff89 | 2014-09-29 14:23:47 -0700 | [diff] [blame] | 696 | case !haveHelper: |
| 697 | // When the helper is not setuid, the helper can't change the |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 698 | // app's uid so just run the app as the device manager's uname |
Robert Kroeger | 362ff89 | 2014-09-29 14:23:47 -0700 | [diff] [blame] | 699 | // whether or not there is an association. |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 700 | vlog.VI(1).Infof("helper not setuid. Device manager will invoke app with its own userid") |
Robert Kroeger | 362ff89 | 2014-09-29 14:23:47 -0700 | [diff] [blame] | 701 | user, err := user.Current() |
| 702 | if err != nil { |
| 703 | vlog.Errorf("user.Current() failed: %v", err) |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 704 | return "", verror2.Make(ErrOperationFailed, ctx.Context()) |
Robert Kroeger | 362ff89 | 2014-09-29 14:23:47 -0700 | [diff] [blame] | 705 | } |
| 706 | return user.Username, nil |
| 707 | } |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 708 | return "", verror2.Make(ErrOperationFailed, ctx.Context()) |
Robert Kroeger | 362ff89 | 2014-09-29 14:23:47 -0700 | [diff] [blame] | 709 | } |
| 710 | |
Bogdan Caprita | 2556a6c | 2014-12-04 15:51:01 -0800 | [diff] [blame] | 711 | func genCmd(instanceDir, helperPath, systemName string, nsRoots []string) (*exec.Cmd, error) { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 712 | versionLink := filepath.Join(instanceDir, "version") |
| 713 | versionDir, err := filepath.EvalSymlinks(versionLink) |
| 714 | if err != nil { |
| 715 | vlog.Errorf("EvalSymlinks(%v) failed: %v", versionLink, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 716 | return nil, verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 717 | } |
| 718 | envelope, err := loadEnvelope(versionDir) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 719 | if err != nil { |
| 720 | return nil, err |
| 721 | } |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 722 | binPath := filepath.Join(versionDir, "bin") |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 723 | if _, err := os.Stat(binPath); err != nil { |
| 724 | vlog.Errorf("Stat(%v) failed: %v", binPath, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 725 | return nil, verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 726 | } |
Robert Kroeger | dd07b36 | 2014-09-18 17:34:42 -0700 | [diff] [blame] | 727 | |
Robert Kroeger | dd07b36 | 2014-09-18 17:34:42 -0700 | [diff] [blame] | 728 | cmd := exec.Command(helperPath) |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 729 | cmd.Args = append(cmd.Args, "--username", systemName) |
Robert Kroeger | dd07b36 | 2014-09-18 17:34:42 -0700 | [diff] [blame] | 730 | |
Bogdan Caprita | 2556a6c | 2014-12-04 15:51:01 -0800 | [diff] [blame] | 731 | var nsRootEnvs []string |
| 732 | for i, r := range nsRoots { |
| 733 | nsRootEnvs = append(nsRootEnvs, fmt.Sprintf("%s%d=%s", consts.NamespaceRootPrefix, i, r)) |
| 734 | } |
| 735 | cmd.Env = append(nsRootEnvs, envelope.Env...) |
Bogdan Caprita | 25d4faa | 2014-08-28 10:21:23 -0700 | [diff] [blame] | 736 | rootDir := filepath.Join(instanceDir, "root") |
| 737 | if err := mkdir(rootDir); err != nil { |
| 738 | return nil, err |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 739 | } |
Bogdan Caprita | 25d4faa | 2014-08-28 10:21:23 -0700 | [diff] [blame] | 740 | cmd.Dir = rootDir |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 741 | cmd.Args = append(cmd.Args, "--workspace", rootDir) |
Robert Kroeger | dd07b36 | 2014-09-18 17:34:42 -0700 | [diff] [blame] | 742 | |
Bogdan Caprita | 25d4faa | 2014-08-28 10:21:23 -0700 | [diff] [blame] | 743 | logDir := filepath.Join(instanceDir, "logs") |
| 744 | if err := mkdir(logDir); err != nil { |
| 745 | return nil, err |
| 746 | } |
Robert Kroeger | a99ad14 | 2014-10-30 17:56:39 -0700 | [diff] [blame] | 747 | cmd.Args = append(cmd.Args, "--logdir", logDir) |
Bogdan Caprita | 25d4faa | 2014-08-28 10:21:23 -0700 | [diff] [blame] | 748 | timestamp := time.Now().UnixNano() |
Robert Kroeger | a99ad14 | 2014-10-30 17:56:39 -0700 | [diff] [blame] | 749 | |
Robert Kroeger | dd07b36 | 2014-09-18 17:34:42 -0700 | [diff] [blame] | 750 | stdoutLog := filepath.Join(logDir, fmt.Sprintf("STDOUT-%d", timestamp)) |
| 751 | if cmd.Stdout, err = openWriteFile(stdoutLog); err != nil { |
Bogdan Caprita | 25d4faa | 2014-08-28 10:21:23 -0700 | [diff] [blame] | 752 | return nil, err |
| 753 | } |
Robert Kroeger | dd07b36 | 2014-09-18 17:34:42 -0700 | [diff] [blame] | 754 | stderrLog := filepath.Join(logDir, fmt.Sprintf("STDERR-%d", timestamp)) |
| 755 | if cmd.Stderr, err = openWriteFile(stderrLog); err != nil { |
Bogdan Caprita | 25d4faa | 2014-08-28 10:21:23 -0700 | [diff] [blame] | 756 | return nil, err |
| 757 | } |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 758 | cmd.Args = append(cmd.Args, "--run", binPath) |
Robert Kroeger | dd07b36 | 2014-09-18 17:34:42 -0700 | [diff] [blame] | 759 | cmd.Args = append(cmd.Args, "--") |
| 760 | |
Bogdan Caprita | 2556a6c | 2014-12-04 15:51:01 -0800 | [diff] [blame] | 761 | // Args to be passed by helper to the app. |
Bogdan Caprita | 25d4faa | 2014-08-28 10:21:23 -0700 | [diff] [blame] | 762 | cmd.Args = append(cmd.Args, "--log_dir=../logs") |
| 763 | cmd.Args = append(cmd.Args, envelope.Args...) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 764 | return cmd, nil |
| 765 | } |
| 766 | |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 767 | func (i *appService) startCmd(instanceDir string, cmd *exec.Cmd) error { |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 768 | info, err := loadInstanceInfo(instanceDir) |
| 769 | if err != nil { |
| 770 | return err |
| 771 | } |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 772 | // Setup up the child process callback. |
| 773 | callbackState := i.callback |
Bogdan Caprita | 78b6216 | 2014-08-21 15:35:08 -0700 | [diff] [blame] | 774 | listener := callbackState.listenFor(mgmt.AppCycleManagerConfigKey) |
| 775 | defer listener.cleanup() |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 776 | cfg := vexec.NewConfig() |
Jiri Simsa | f57930f | 2014-11-05 15:19:31 -0800 | [diff] [blame] | 777 | cfg.Set(mgmt.ParentNameConfigKey, listener.name()) |
| 778 | cfg.Set(mgmt.ProtocolConfigKey, "tcp") |
| 779 | cfg.Set(mgmt.AddressConfigKey, "127.0.0.1:0") |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 780 | cfg.Set(mgmt.ParentBlessingConfigKey, info.DeviceManagerPeerPattern) |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 781 | |
| 782 | // Set up any agent-specific state. |
| 783 | // NOTE(caprita): This ought to belong in genCmd, but we do it here |
| 784 | // to avoid holding on to the lock for too long. |
| 785 | // |
| 786 | // TODO(caprita): We need to take care to grab/release the lock |
| 787 | // excluding concurrent start operations. See if we can make this more |
| 788 | // robust. |
| 789 | var agentCleaner func() |
| 790 | if sa := i.securityAgent; sa != nil { |
| 791 | sa.startLock.Lock() |
| 792 | file, err := sa.keyMgrAgent.NewConnection(info.SecurityAgentHandle) |
| 793 | if err != nil { |
| 794 | sa.startLock.Unlock() |
| 795 | vlog.Errorf("NewConnection(%v) failed: %v", info.SecurityAgentHandle, err) |
| 796 | return err |
| 797 | } |
| 798 | agentCleaner = func() { |
| 799 | file.Close() |
| 800 | sa.startLock.Unlock() |
| 801 | } |
| 802 | // We need to account for the file descriptors corresponding to |
| 803 | // std{err|out|in} as well as the implementation-specific pipes |
| 804 | // that the vexec library adds to ExtraFiles during |
| 805 | // handle.Start. vexec.FileOffset properly offsets fd |
| 806 | // accordingly. |
| 807 | fd := len(cmd.ExtraFiles) + vexec.FileOffset |
| 808 | cmd.ExtraFiles = append(cmd.ExtraFiles, file) |
| 809 | cfg.Set(mgmt.SecurityAgentFDConfigKey, strconv.Itoa(fd)) |
| 810 | } else { |
| 811 | cmd.Env = append(cmd.Env, consts.VeyronCredentials+"="+filepath.Join(instanceDir, "credentials")) |
| 812 | } |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 813 | handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg}) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 814 | defer func() { |
| 815 | if handle != nil { |
| 816 | if err := handle.Clean(); err != nil { |
| 817 | vlog.Errorf("Clean() failed: %v", err) |
| 818 | } |
| 819 | } |
| 820 | }() |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 821 | // Start the child process. |
| 822 | if err := handle.Start(); err != nil { |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 823 | if agentCleaner != nil { |
| 824 | agentCleaner() |
| 825 | } |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 826 | vlog.Errorf("Start() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 827 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 828 | } |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 829 | if agentCleaner != nil { |
| 830 | agentCleaner() |
| 831 | } |
| 832 | |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 833 | // Wait for the child process to start. |
Bogdan Caprita | 916e99f | 2014-11-24 15:47:19 -0800 | [diff] [blame] | 834 | if err := handle.WaitForReady(childReadyTimeout); err != nil { |
| 835 | vlog.Errorf("WaitForReady(%v) failed: %v", childReadyTimeout, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 836 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 837 | } |
Bogdan Caprita | 916e99f | 2014-11-24 15:47:19 -0800 | [diff] [blame] | 838 | childName, err := listener.waitForValue(childReadyTimeout) |
Bogdan Caprita | 78b6216 | 2014-08-21 15:35:08 -0700 | [diff] [blame] | 839 | if err != nil { |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 840 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 841 | } |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 842 | info.AppCycleMgrName, info.Pid = childName, handle.Pid() |
| 843 | if err := saveInstanceInfo(instanceDir, info); err != nil { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 844 | return err |
Bogdan Caprita | 78b6216 | 2014-08-21 15:35:08 -0700 | [diff] [blame] | 845 | } |
| 846 | // TODO(caprita): Spin up a goroutine to reap child status upon exit and |
| 847 | // transition it to suspended state if it exits on its own. |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 848 | handle = nil |
| 849 | return nil |
| 850 | } |
| 851 | |
Bogdan Caprita | 2556a6c | 2014-12-04 15:51:01 -0800 | [diff] [blame] | 852 | func (i *appService) run(nsRoots []string, instanceDir, systemName string) error { |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 853 | if err := transitionInstance(instanceDir, suspended, starting); err != nil { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 854 | return err |
| 855 | } |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 856 | |
Bogdan Caprita | 2556a6c | 2014-12-04 15:51:01 -0800 | [diff] [blame] | 857 | cmd, err := genCmd(instanceDir, i.config.Helper, systemName, nsRoots) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 858 | if err == nil { |
| 859 | err = i.startCmd(instanceDir, cmd) |
| 860 | } |
| 861 | if err != nil { |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 862 | transitionInstance(instanceDir, starting, suspended) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 863 | return err |
| 864 | } |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 865 | return transitionInstance(instanceDir, starting, started) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 866 | } |
| 867 | |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 868 | func (i *appService) Start(call ipc.ServerContext) ([]string, error) { |
Robert Kroeger | 94ec756 | 2014-10-28 17:58:44 -0700 | [diff] [blame] | 869 | helper := i.config.Helper |
Bogdan Caprita | 2692910 | 2014-11-07 11:56:56 -0800 | [diff] [blame] | 870 | instanceDir, instanceID, err := i.newInstance(call) |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 871 | |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 872 | if err != nil { |
Robert Kroeger | 94ec756 | 2014-10-28 17:58:44 -0700 | [diff] [blame] | 873 | cleanupDir(instanceDir, helper) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 874 | return nil, err |
| 875 | } |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 876 | |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 877 | systemName, err := systemAccountForHelper(call, helper, i.uat) |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 878 | if err != nil { |
Robert Kroeger | 94ec756 | 2014-10-28 17:58:44 -0700 | [diff] [blame] | 879 | cleanupDir(instanceDir, helper) |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 880 | return nil, err |
| 881 | } |
| 882 | |
| 883 | if err := saveSystemNameForInstance(instanceDir, systemName); err != nil { |
Robert Kroeger | 94ec756 | 2014-10-28 17:58:44 -0700 | [diff] [blame] | 884 | cleanupDir(instanceDir, helper) |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 885 | return nil, err |
| 886 | } |
| 887 | |
Bogdan Caprita | 2b21936 | 2014-12-09 17:03:33 -0800 | [diff] [blame] | 888 | // For now, use the namespace roots of the device manager runtime to |
| 889 | // pass to the app. |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 890 | if err = i.run(veyron2.RuntimeFromContext(call.Context()).Namespace().Roots(), instanceDir, systemName); err != nil { |
Bogdan Caprita | 7f49167 | 2014-11-13 14:51:08 -0800 | [diff] [blame] | 891 | // TODO(caprita): We should call cleanupDir here, but we don't |
| 892 | // in order to not lose the logs for the instance (so we can |
| 893 | // debug why run failed). Clean this up. |
| 894 | // cleanupDir(instanceDir, helper) |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 895 | return nil, err |
| 896 | } |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 897 | return []string{instanceID}, nil |
| 898 | } |
| 899 | |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 900 | // instanceDir returns the path to the directory containing the app instance |
Robin Thellend | 4c5266e | 2014-10-27 13:19:29 -0700 | [diff] [blame] | 901 | // referred to by the given suffix relative to the given root directory. |
| 902 | func instanceDir(root string, suffix []string) (string, error) { |
| 903 | if nComponents := len(suffix); nComponents != 3 { |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 904 | return "", verror2.Make(ErrInvalidSuffix, nil) |
Robin Thellend | 4c5266e | 2014-10-27 13:19:29 -0700 | [diff] [blame] | 905 | } |
| 906 | app, installation, instance := suffix[0], suffix[1], suffix[2] |
| 907 | instancesDir := filepath.Join(root, applicationDirName(app), installationDirName(installation), "instances") |
| 908 | instanceDir := filepath.Join(instancesDir, instanceDirName(instance)) |
| 909 | return instanceDir, nil |
| 910 | } |
| 911 | |
| 912 | // instanceDir returns the path to the directory containing the app instance |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 913 | // referred to by the invoker's suffix, as well as the corresponding stopped |
| 914 | // instance dir. Returns an error if the suffix does not name an instance. |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 915 | func (i *appService) instanceDir() (string, error) { |
Robin Thellend | 4c5266e | 2014-10-27 13:19:29 -0700 | [diff] [blame] | 916 | return instanceDir(i.config.Root, i.suffix) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 917 | } |
| 918 | |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 919 | func (i *appService) Resume(call ipc.ServerContext) error { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 920 | instanceDir, err := i.instanceDir() |
| 921 | if err != nil { |
| 922 | return err |
| 923 | } |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 924 | |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 925 | systemName, err := systemAccountForHelper(call, i.config.Helper, i.uat) |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 926 | if err != nil { |
| 927 | return err |
| 928 | } |
| 929 | |
| 930 | startSystemName, err := readSystemNameForInstance(instanceDir) |
| 931 | if err != nil { |
| 932 | return err |
| 933 | } |
| 934 | |
| 935 | if startSystemName != systemName { |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 936 | return verror2.Make(verror2.NoAccess, call.Context(), "Not allowed to resume an application under a different system name.") |
Robert Kroeger | 1ce0bd7 | 2014-10-22 13:57:14 -0700 | [diff] [blame] | 937 | } |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 938 | return i.run(veyron2.RuntimeFromContext(call.Context()).Namespace().Roots(), instanceDir, systemName) |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 939 | } |
| 940 | |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 941 | func stopAppRemotely(ctx context.T, appVON string) error { |
Todd Wang | 702385a | 2014-11-07 01:54:08 -0800 | [diff] [blame] | 942 | appStub := appcycle.AppCycleClient(appVON) |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 943 | ctx, cancel := ctx.WithTimeout(ipcContextTimeout) |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 944 | defer cancel() |
| 945 | stream, err := appStub.Stop(ctx) |
| 946 | if err != nil { |
| 947 | vlog.Errorf("%v.Stop() failed: %v", appVON, err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 948 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 949 | } |
| 950 | rstream := stream.RecvStream() |
| 951 | for rstream.Advance() { |
| 952 | vlog.VI(2).Infof("%v.Stop() task update: %v", appVON, rstream.Value()) |
| 953 | } |
| 954 | if err := rstream.Err(); err != nil { |
| 955 | vlog.Errorf("Advance() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 956 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 957 | } |
| 958 | if err := stream.Finish(); err != nil { |
| 959 | vlog.Errorf("Finish() failed: %v", err) |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 960 | return verror2.Make(ErrOperationFailed, nil) |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 961 | } |
| 962 | return nil |
| 963 | } |
| 964 | |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 965 | func stop(ctx context.T, instanceDir string) error { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 966 | info, err := loadInstanceInfo(instanceDir) |
Bogdan Caprita | 2968f4b | 2014-08-22 14:11:58 -0700 | [diff] [blame] | 967 | if err != nil { |
| 968 | return err |
| 969 | } |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 970 | return stopAppRemotely(ctx, info.AppCycleMgrName) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 971 | } |
| 972 | |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 973 | // TODO(caprita): implement deadline for Stop. |
| 974 | |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 975 | func (i *appService) Stop(ctx ipc.ServerContext, deadline uint32) error { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 976 | instanceDir, err := i.instanceDir() |
| 977 | if err != nil { |
| 978 | return err |
| 979 | } |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 980 | if err := transitionInstance(instanceDir, suspended, stopped); verror2.Is(err, ErrOperationFailed.ID) || err == nil { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 981 | return err |
| 982 | } |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 983 | if err := transitionInstance(instanceDir, started, stopping); err != nil { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 984 | return err |
| 985 | } |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 986 | if err := stop(ctx.Context(), instanceDir); err != nil { |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 987 | transitionInstance(instanceDir, stopping, started) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 988 | return err |
| 989 | } |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 990 | return transitionInstance(instanceDir, stopping, stopped) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 991 | } |
| 992 | |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 993 | func (i *appService) Suspend(ctx ipc.ServerContext) error { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 994 | instanceDir, err := i.instanceDir() |
| 995 | if err != nil { |
| 996 | return err |
| 997 | } |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 998 | if err := transitionInstance(instanceDir, started, suspending); err != nil { |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 999 | return err |
| 1000 | } |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1001 | if err := stop(ctx.Context(), instanceDir); err != nil { |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 1002 | transitionInstance(instanceDir, suspending, started) |
Bogdan Caprita | 268b419 | 2014-08-28 10:04:44 -0700 | [diff] [blame] | 1003 | return err |
| 1004 | } |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 1005 | return transitionInstance(instanceDir, suspending, suspended) |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 1006 | } |
| 1007 | |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 1008 | func (i *appService) Uninstall(ipc.ServerContext) error { |
Bogdan Caprita | 8c776b2 | 2014-08-28 17:29:07 -0700 | [diff] [blame] | 1009 | installationDir, err := i.installationDir() |
| 1010 | if err != nil { |
| 1011 | return err |
| 1012 | } |
| 1013 | return transitionInstallation(installationDir, active, uninstalled) |
| 1014 | } |
| 1015 | |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 1016 | func (i *appService) Update(call ipc.ServerContext) error { |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 1017 | installationDir, err := i.installationDir() |
| 1018 | if err != nil { |
| 1019 | return err |
| 1020 | } |
| 1021 | if !installationStateIs(installationDir, active) { |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1022 | return verror2.Make(ErrInvalidOperation, call.Context()) |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 1023 | } |
| 1024 | originVON, err := loadOrigin(installationDir) |
| 1025 | if err != nil { |
| 1026 | return err |
| 1027 | } |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1028 | ctx, cancel := call.Context().WithTimeout(ipcContextTimeout) |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 1029 | defer cancel() |
| 1030 | newEnvelope, err := fetchAppEnvelope(ctx, originVON) |
| 1031 | if err != nil { |
| 1032 | return err |
| 1033 | } |
| 1034 | currLink := filepath.Join(installationDir, "current") |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 1035 | oldVersionDir, err := filepath.EvalSymlinks(currLink) |
| 1036 | if err != nil { |
| 1037 | vlog.Errorf("EvalSymlinks(%v) failed: %v", currLink, err) |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1038 | return verror2.Make(ErrOperationFailed, call.Context()) |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 1039 | } |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 1040 | // NOTE(caprita): A race can occur between two competing updates, where |
| 1041 | // both use the old version as their baseline. This can result in both |
| 1042 | // updates succeeding even if they are updating the app installation to |
| 1043 | // the same new envelope. This will result in one of the updates |
| 1044 | // becoming the new 'current'. Both versions will point their |
| 1045 | // 'previous' link to the old version. This doesn't appear to be of |
| 1046 | // practical concern, so we avoid the complexity of synchronizing |
| 1047 | // updates. |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 1048 | oldEnvelope, err := loadEnvelope(oldVersionDir) |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 1049 | if err != nil { |
| 1050 | return err |
| 1051 | } |
| 1052 | if oldEnvelope.Title != newEnvelope.Title { |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1053 | return verror2.Make(ErrAppTitleMismatch, call.Context()) |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 1054 | } |
| 1055 | if reflect.DeepEqual(oldEnvelope, newEnvelope) { |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1056 | return verror2.Make(ErrUpdateNoOp, call.Context()) |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 1057 | } |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1058 | versionDir, err := newVersion(call.Context(), installationDir, newEnvelope, oldVersionDir) |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 1059 | if err != nil { |
Robert Kroeger | 94ec756 | 2014-10-28 17:58:44 -0700 | [diff] [blame] | 1060 | cleanupDir(versionDir, "") |
Bogdan Caprita | bce0a63 | 2014-09-03 16:15:26 -0700 | [diff] [blame] | 1061 | return err |
| 1062 | } |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 1063 | return nil |
| 1064 | } |
| 1065 | |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 1066 | func (*appService) UpdateTo(_ ipc.ServerContext, von string) error { |
Bogdan Caprita | 4d67c04 | 2014-08-19 10:41:19 -0700 | [diff] [blame] | 1067 | // TODO(jsimsa): Implement. |
| 1068 | return nil |
| 1069 | } |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 1070 | |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 1071 | func (i *appService) Revert(ctx ipc.ServerContext) error { |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 1072 | installationDir, err := i.installationDir() |
| 1073 | if err != nil { |
| 1074 | return err |
| 1075 | } |
| 1076 | if !installationStateIs(installationDir, active) { |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1077 | return verror2.Make(ErrInvalidOperation, ctx.Context()) |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 1078 | } |
| 1079 | // NOTE(caprita): A race can occur between an update and a revert, where |
| 1080 | // both use the same current version as their starting point. This will |
| 1081 | // render the update inconsequential. This doesn't appear to be of |
| 1082 | // practical concern, so we avoid the complexity of synchronizing |
| 1083 | // updates and revert operations. |
| 1084 | currLink := filepath.Join(installationDir, "current") |
| 1085 | currVersionDir, err := filepath.EvalSymlinks(currLink) |
| 1086 | if err != nil { |
| 1087 | vlog.Errorf("EvalSymlinks(%v) failed: %v", currLink, err) |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1088 | return verror2.Make(ErrOperationFailed, ctx.Context()) |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 1089 | } |
| 1090 | previousLink := filepath.Join(currVersionDir, "previous") |
| 1091 | if _, err := os.Lstat(previousLink); err != nil { |
| 1092 | if os.IsNotExist(err) { |
| 1093 | // No 'previous' link -- must be the first version. |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1094 | return verror2.Make(ErrUpdateNoOp, ctx.Context()) |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 1095 | } |
| 1096 | vlog.Errorf("Lstat(%v) failed: %v", previousLink, err) |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1097 | return verror2.Make(ErrOperationFailed, ctx.Context()) |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 1098 | } |
| 1099 | prevVersionDir, err := filepath.EvalSymlinks(previousLink) |
| 1100 | if err != nil { |
| 1101 | vlog.Errorf("EvalSymlinks(%v) failed: %v", previousLink, err) |
Matt Rosencrantz | 8f9fca1 | 2014-12-19 14:02:31 -0800 | [diff] [blame] | 1102 | return verror2.Make(ErrOperationFailed, ctx.Context()) |
Bogdan Caprita | 53b7b7e | 2014-09-03 20:51:16 -0700 | [diff] [blame] | 1103 | } |
| 1104 | return updateLink(prevVersionDir, currLink) |
| 1105 | } |
Robin Thellend | 09929f4 | 2014-10-01 10:18:13 -0700 | [diff] [blame] | 1106 | |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1107 | type treeNode struct { |
| 1108 | children map[string]*treeNode |
| 1109 | } |
| 1110 | |
| 1111 | func newTreeNode() *treeNode { |
Robin Thellend | b9dd9bb | 2014-10-29 13:54:08 -0700 | [diff] [blame] | 1112 | return &treeNode{children: make(map[string]*treeNode)} |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1113 | } |
| 1114 | |
| 1115 | func (n *treeNode) find(names []string, create bool) *treeNode { |
| 1116 | for { |
| 1117 | if len(names) == 0 { |
| 1118 | return n |
| 1119 | } |
| 1120 | if next, ok := n.children[names[0]]; ok { |
| 1121 | n = next |
| 1122 | names = names[1:] |
| 1123 | continue |
| 1124 | } |
| 1125 | if create { |
| 1126 | nn := newTreeNode() |
| 1127 | n.children[names[0]] = nn |
| 1128 | n = nn |
| 1129 | names = names[1:] |
| 1130 | continue |
| 1131 | } |
| 1132 | return nil |
| 1133 | } |
| 1134 | } |
| 1135 | |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 1136 | func (i *appService) scanEnvelopes(tree *treeNode, appDir string) { |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1137 | // Find all envelopes, extract installID. |
| 1138 | envGlob := []string{i.config.Root, appDir, "installation-*", "*", "envelope"} |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1139 | envelopes, err := filepath.Glob(filepath.Join(envGlob...)) |
| 1140 | if err != nil { |
| 1141 | vlog.Errorf("unexpected error: %v", err) |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1142 | return |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1143 | } |
| 1144 | for _, path := range envelopes { |
| 1145 | env, err := loadEnvelope(filepath.Dir(path)) |
| 1146 | if err != nil { |
| 1147 | continue |
| 1148 | } |
| 1149 | relpath, _ := filepath.Rel(i.config.Root, path) |
| 1150 | elems := strings.Split(relpath, string(filepath.Separator)) |
| 1151 | if len(elems) != len(envGlob)-1 { |
| 1152 | vlog.Errorf("unexpected number of path components: %q (%q)", elems, path) |
| 1153 | continue |
| 1154 | } |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1155 | installID := strings.TrimPrefix(elems[1], "installation-") |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1156 | tree.find([]string{env.Title, installID}, true) |
| 1157 | } |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1158 | return |
| 1159 | } |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1160 | |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 1161 | func (i *appService) scanInstances(tree *treeNode) { |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1162 | if len(i.suffix) < 2 { |
| 1163 | return |
| 1164 | } |
| 1165 | title := i.suffix[0] |
| 1166 | installDir, err := installationDirCore(i.suffix[:2], i.config.Root) |
| 1167 | if err != nil { |
| 1168 | return |
| 1169 | } |
Bogdan Caprita | 43bc737 | 2014-12-03 21:51:12 -0800 | [diff] [blame] | 1170 | // Add the node corresponding to the installation itself. |
| 1171 | tree.find(i.suffix[:2], true) |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1172 | // Find all instances. |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1173 | infoGlob := []string{installDir, "instances", "instance-*", "info"} |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1174 | instances, err := filepath.Glob(filepath.Join(infoGlob...)) |
| 1175 | if err != nil { |
| 1176 | vlog.Errorf("unexpected error: %v", err) |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1177 | return |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1178 | } |
| 1179 | for _, path := range instances { |
Robin Thellend | 4c5266e | 2014-10-27 13:19:29 -0700 | [diff] [blame] | 1180 | instanceDir := filepath.Dir(path) |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1181 | i.scanInstance(tree, title, instanceDir) |
| 1182 | } |
| 1183 | return |
| 1184 | } |
| 1185 | |
Robin Thellend | 9bc8fcb | 2014-11-17 10:23:04 -0800 | [diff] [blame] | 1186 | func (i *appService) scanInstance(tree *treeNode, title, instanceDir string) { |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1187 | if _, err := loadInstanceInfo(instanceDir); err != nil { |
| 1188 | return |
| 1189 | } |
| 1190 | relpath, _ := filepath.Rel(i.config.Root, instanceDir) |
| 1191 | elems := strings.Split(relpath, string(filepath.Separator)) |
| 1192 | if len(elems) < 4 { |
| 1193 | vlog.Errorf("unexpected number of path components: %q (%q)", elems, instanceDir) |
| 1194 | return |
| 1195 | } |
| 1196 | installID := strings.TrimPrefix(elems[1], "installation-") |
| 1197 | instanceID := strings.TrimPrefix(elems[3], "instance-") |
| 1198 | tree.find([]string{title, installID, instanceID, "logs"}, true) |
| 1199 | if instanceStateIs(instanceDir, started) { |
| 1200 | for _, obj := range []string{"pprof", "stats"} { |
| 1201 | tree.find([]string{title, installID, instanceID, obj}, true) |
| 1202 | } |
| 1203 | } |
| 1204 | } |
| 1205 | |
Robin Thellend | 39ac323 | 2014-12-02 09:50:41 -0800 | [diff] [blame] | 1206 | func (i *appService) GlobChildren__(ipc.ServerContext) (<-chan string, error) { |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1207 | tree := newTreeNode() |
| 1208 | switch len(i.suffix) { |
| 1209 | case 0: |
| 1210 | i.scanEnvelopes(tree, "app-*") |
| 1211 | case 1: |
| 1212 | appDir := applicationDirName(i.suffix[0]) |
| 1213 | i.scanEnvelopes(tree, appDir) |
| 1214 | case 2: |
| 1215 | i.scanInstances(tree) |
| 1216 | case 3: |
| 1217 | dir, err := i.instanceDir() |
Robin Thellend | b9dd9bb | 2014-10-29 13:54:08 -0700 | [diff] [blame] | 1218 | if err != nil { |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1219 | break |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1220 | } |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1221 | i.scanInstance(tree, i.suffix[0], dir) |
| 1222 | default: |
Mike Burrows | d65df96 | 2014-12-17 10:01:19 -0800 | [diff] [blame] | 1223 | return nil, verror2.Make(verror2.NoExist, nil, i.suffix) |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1224 | } |
Robin Thellend | ac7128c | 2014-11-11 09:58:28 -0800 | [diff] [blame] | 1225 | n := tree.find(i.suffix, false) |
Robin Thellend | 9e523a6 | 2014-10-07 16:19:53 -0700 | [diff] [blame] | 1226 | if n == nil { |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 1227 | return nil, verror2.Make(ErrInvalidSuffix, nil) |
Robin Thellend | 09929f4 | 2014-10-01 10:18:13 -0700 | [diff] [blame] | 1228 | } |
Robin Thellend | a2b0043 | 2014-12-18 15:03:25 -0800 | [diff] [blame] | 1229 | ch := make(chan string) |
Robin Thellend | 8e9cc24 | 2014-11-26 09:43:10 -0800 | [diff] [blame] | 1230 | go func() { |
| 1231 | for child, _ := range n.children { |
| 1232 | ch <- child |
| 1233 | } |
| 1234 | close(ch) |
| 1235 | }() |
| 1236 | return ch, nil |
Robin Thellend | b9dd9bb | 2014-10-29 13:54:08 -0700 | [diff] [blame] | 1237 | } |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 1238 | |
| 1239 | // TODO(rjkroege): Refactor to eliminate redundancy with newAppSpecificAuthorizer. |
| 1240 | func dirFromSuffix(suffix []string, root string) (string, error) { |
| 1241 | if len(suffix) == 2 { |
| 1242 | p, err := installationDirCore(suffix, root) |
| 1243 | if err != nil { |
| 1244 | vlog.Errorf("dirFromSuffix failed: %v", err) |
| 1245 | return "", err |
| 1246 | } |
| 1247 | return p, nil |
| 1248 | } else if len(suffix) > 2 { |
| 1249 | p, err := instanceDir(root, suffix[0:3]) |
| 1250 | if err != nil { |
| 1251 | vlog.Errorf("dirFromSuffix failed: %v", err) |
| 1252 | return "", err |
| 1253 | } |
| 1254 | return p, nil |
| 1255 | } |
Todd Wang | 34ed4c6 | 2014-11-26 15:15:52 -0800 | [diff] [blame] | 1256 | return "", verror2.Make(ErrInvalidSuffix, nil) |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 1257 | } |
| 1258 | |
| 1259 | // TODO(rjkroege): Consider maintaining an in-memory ACL cache. |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 1260 | func (i *appService) SetACL(ctx ipc.ServerContext, acl access.TaggedACLMap, etag string) error { |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 1261 | dir, err := dirFromSuffix(i.suffix, i.config.Root) |
| 1262 | if err != nil { |
| 1263 | return err |
| 1264 | } |
Matt Rosencrantz | 5180d16 | 2014-12-03 13:48:40 -0800 | [diff] [blame] | 1265 | return setAppACL(ctx.LocalPrincipal(), i.locks, dir, acl, etag) |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 1266 | } |
| 1267 | |
Asim Shankar | 6888519 | 2014-11-26 12:48:35 -0800 | [diff] [blame] | 1268 | func (i *appService) GetACL(_ ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) { |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 1269 | dir, err := dirFromSuffix(i.suffix, i.config.Root) |
| 1270 | if err != nil { |
Asim Shankar | 6888519 | 2014-11-26 12:48:35 -0800 | [diff] [blame] | 1271 | return nil, "", err |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 1272 | } |
Robert Kroeger | 7d979d8 | 2014-11-07 17:24:35 -0800 | [diff] [blame] | 1273 | return getAppACL(i.locks, dir) |
Robert Kroeger | acc778b | 2014-11-03 17:17:21 -0800 | [diff] [blame] | 1274 | } |