blob: bde4143fc268b7226f7bde4926ebd3a86b94d3ad [file] [log] [blame]
Bogdan Caprita4d67c042014-08-19 10:41:19 -07001package impl
2
3// The app invoker is responsible for managing the state of applications on the
Bogdan Caprita2b219362014-12-09 17:03:33 -08004// device manager. The device manager manages the applications it installs and
5// runs using the following directory structure:
Bogdan Caprita4d67c042014-08-19 10:41:19 -07006//
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 Kroegeracc778b2014-11-03 17:17:21 -080012// 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 Caprita8c776b22014-08-28 17:29:07 -070018// <status> - one of the values for installationState enum
Bogdan Capritabce0a632014-09-03 16:15:26 -070019// origin - object name for application envelope
Bogdan Caprita4d67c042014-08-19 10:41:19 -070020// <version 1 timestamp>/ - timestamp of when the version was downloaded
21// bin - application binary
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -070022// previous - symbolic link to previous version directory
Bogdan Caprita4d67c042014-08-19 10:41:19 -070023// envelope - application envelope (JSON-encoded)
Robin Thellende2627892014-11-26 09:34:37 -080024// pkg/ - the application packages
25// <pkg name>
26// <pkg name>.__info
27// ...
Bogdan Caprita4d67c042014-08-19 10:41:19 -070028// <version 2 timestamp>
29// ...
30// current - symbolic link to the current version
31// instances/
32// instance-<id a>/ - instances are labelled with ids
Bogdan Caprita7f491672014-11-13 14:51:08 -080033// credentials/ - holds veyron credentials (unless running
34// through security agent)
Bogdan Caprita4d67c042014-08-19 10:41:19 -070035// root/ - workspace that the instance is run from
Robin Thellende2627892014-11-26 09:34:37 -080036// packages/ - the installed packages
37// <pkg name>/
38// ...
Bogdan Caprita4d67c042014-08-19 10:41:19 -070039// logs/ - stderr/stdout and log files generated by instance
Bogdan Caprita7f491672014-11-13 14:51:08 -080040// info - metadata for the instance (such as app
41// cycle manager name and process id)
Bogdan Caprita4d67c042014-08-19 10:41:19 -070042// version - symbolic link to installation version for the instance
Robert Kroegeracc778b2014-11-03 17:17:21 -080043// 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 Caprita268b4192014-08-28 10:04:44 -070049// <status> - one of the values for instanceState enum
Robert Kroeger1ce0bd72014-10-22 13:57:14 -070050// systemname - the system name used to execute this instance
Bogdan Caprita4d67c042014-08-19 10:41:19 -070051// instance-<id b>
52// ...
Bogdan Caprita4d67c042014-08-19 10:41:19 -070053// installation-<id 2>
54// ...
55// app-<hash 2>
56// ...
57//
Bogdan Caprita2b219362014-12-09 17:03:33 -080058// The device manager uses the suid helper binary to invoke an application as a
Bogdan Caprita962d5e02014-10-28 18:36:09 -070059// specified user. The path to the helper is specified as config.Helper.
60
Bogdan Caprita2b219362014-12-09 17:03:33 -080061// When device manager starts up, it goes through all instances and resumes the
Bogdan Caprita4d67c042014-08-19 10:41:19 -070062// 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 Caprita2b219362014-12-09 17:03:33 -080065// When device manager shuts down, it suspends all running instances.
Bogdan Caprita4d67c042014-08-19 10:41:19 -070066//
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 Caprita268b4192014-08-28 10:04:44 -070077// first step; subsequent Uninstall's will fail. Instances can be created
Bogdan Caprita4d67c042014-08-19 10:41:19 -070078// independently of one another, as long as the installation exists (if it gets
Bogdan Caprita268b4192014-08-28 10:04:44 -070079// 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 Caprita4d67c042014-08-19 10:41:19 -0700102//
Bogdan Caprita2b219362014-12-09 17:03:33 -0800103// TODO(caprita): There is room for synergy between how device manager organizes
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700104// 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
108import (
109 "crypto/md5"
Bogdan Caprita26929102014-11-07 11:56:56 -0800110 "crypto/rand"
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700111 "encoding/base64"
112 "encoding/binary"
Bogdan Caprita26929102014-11-07 11:56:56 -0800113 "encoding/hex"
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700114 "encoding/json"
115 "fmt"
116 "hash/crc64"
117 "io/ioutil"
118 "os"
119 "os/exec"
Robert Kroegerdd07b362014-09-18 17:34:42 -0700120 "os/user"
Robert Kroegeracc778b2014-11-03 17:17:21 -0800121 "path"
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700122 "path/filepath"
Bogdan Capritabce0a632014-09-03 16:15:26 -0700123 "reflect"
Bogdan Caprita7f491672014-11-13 14:51:08 -0800124 "strconv"
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700125 "strings"
Bogdan Caprita7f491672014-11-13 14:51:08 -0800126 "sync"
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700127 "time"
128
Jiri Simsa764efb72014-12-25 20:57:03 -0800129 "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 Nicolaou486d3492014-09-30 22:21:20 -0700141
Jiri Simsa764efb72014-12-25 20:57:03 -0800142 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 Caprita4d67c042014-08-19 10:41:19 -0700150)
151
152// instanceInfo holds state about a running instance.
153type instanceInfo struct {
Bogdan Caprita2b219362014-12-09 17:03:33 -0800154 AppCycleMgrName string
155 Pid int
156 DeviceManagerPeerPattern string
157 SecurityAgentHandle []byte
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700158}
159
160func 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 Wang34ed4c62014-11-26 15:15:52 -0800164 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700165 }
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 Wang34ed4c62014-11-26 15:15:52 -0800169 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700170 }
171 return nil
172}
173
174func 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 Wang34ed4c62014-11-26 15:15:52 -0800179 return nil, verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700180 } else if err := json.Unmarshal(infoBytes, info); err != nil {
181 vlog.Errorf("Unmarshal(%v) failed: %v", infoBytes, err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800182 return nil, verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700183 }
184 return info, nil
185}
186
Bogdan Caprita7f491672014-11-13 14:51:08 -0800187type 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 Caprita2b219362014-12-09 17:03:33 -0800198// appService implements the Device manager's Application interface.
Robin Thellend9bc8fcb2014-11-17 10:23:04 -0800199type appService struct {
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700200 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 Kroeger1cb4a0d2014-10-20 11:55:38 -0700206 uat BlessingSystemAssociationStore
Robert Kroegeracc778b2014-11-03 17:17:21 -0800207 locks aclLocks
Bogdan Caprita2b219362014-12-09 17:03:33 -0800208 // Reference to the devicemanager top-level ACL list.
209 deviceACL access.TaggedACLMap
Bogdan Caprita7f491672014-11-13 14:51:08 -0800210 // securityAgent holds state related to the security agent (nil if not
211 // using the agent).
212 securityAgent *securityAgentState
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700213}
214
215func 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 Wang34ed4c62014-11-26 15:15:52 -0800219 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700220 }
Bogdan Capritabce0a632014-09-03 16:15:26 -0700221 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 Wang34ed4c62014-11-26 15:15:52 -0800224 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700225 }
226 return nil
227}
228
229func loadEnvelope(dir string) (*application.Envelope, error) {
Bogdan Capritabce0a632014-09-03 16:15:26 -0700230 path := filepath.Join(dir, "envelope")
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700231 envelope := new(application.Envelope)
Bogdan Capritabce0a632014-09-03 16:15:26 -0700232 if envelopeBytes, err := ioutil.ReadFile(path); err != nil {
233 vlog.Errorf("ReadFile(%v) failed: %v", path, err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800234 return nil, verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700235 } else if err := json.Unmarshal(envelopeBytes, envelope); err != nil {
236 vlog.Errorf("Unmarshal(%v) failed: %v", envelopeBytes, err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800237 return nil, verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700238 }
239 return envelope, nil
240}
241
242func 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 Wang34ed4c62014-11-26 15:15:52 -0800246 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700247 }
248 return nil
249}
250
Bogdan Capritabce0a632014-09-03 16:15:26 -0700251func 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 Wang34ed4c62014-11-26 15:15:52 -0800255 return "", verror2.Make(ErrOperationFailed, nil)
Bogdan Capritabce0a632014-09-03 16:15:26 -0700256 } else {
257 return string(originBytes), nil
258 }
259}
260
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700261// generateID returns a new unique id string. The uniqueness is based on the
262// current timestamp. Not cryptographically secure.
263func 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 Caprita26929102014-11-07 11:56:56 -0800272// generateRandomString returns a cryptographically-strong random string.
273func 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 Caprita4d67c042014-08-19 10:41:19 -0700282// 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.
289func 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
296func installationDirName(installationID string) string {
297 return "installation-" + installationID
298}
299
300func instanceDirName(instanceID string) string {
301 return "instance-" + instanceID
302}
303
Bogdan Caprita268b4192014-08-28 10:04:44 -0700304func 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 Caprita4d67c042014-08-19 10:41:19 -0700311}
312
Bogdan Capritabce0a632014-09-03 16:15:26 -0700313func 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 Caprita2b219362014-12-09 17:03:33 -0800318 if envelope.Title == application.DeviceManagerTitle {
319 // Disallow device manager apps from being installed like a
Bogdan Capritabce0a632014-09-03 16:15:26 -0700320 // regular app.
Todd Wang34ed4c62014-11-26 15:15:52 -0800321 return nil, verror2.Make(ErrInvalidOperation, ctx)
Bogdan Capritabce0a632014-09-03 16:15:26 -0700322 }
323 return envelope, nil
324}
325
326// newVersion sets up the directory for a new application version.
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800327func newVersion(ctx context.T, installationDir string, envelope *application.Envelope, oldVersionDir string) (string, error) {
Bogdan Capritabce0a632014-09-03 16:15:26 -0700328 versionDir := filepath.Join(installationDir, generateVersionDirName())
329 if err := mkdir(versionDir); err != nil {
Todd Wang34ed4c62014-11-26 15:15:52 -0800330 return "", verror2.Make(ErrOperationFailed, nil)
Bogdan Capritabce0a632014-09-03 16:15:26 -0700331 }
Robin Thellende2627892014-11-26 09:34:37 -0800332 pkgDir := filepath.Join(versionDir, "pkg")
333 if err := mkdir(pkgDir); err != nil {
Todd Wang34ed4c62014-11-26 15:15:52 -0800334 return "", verror2.Make(ErrOperationFailed, nil)
Robin Thellende2627892014-11-26 09:34:37 -0800335 }
Bogdan Capritabce0a632014-09-03 16:15:26 -0700336 // TODO(caprita): Share binaries if already existing locally.
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800337 if err := downloadBinary(ctx, versionDir, "bin", envelope.Binary); err != nil {
Bogdan Capritabce0a632014-09-03 16:15:26 -0700338 return versionDir, err
339 }
Robin Thellende2627892014-11-26 09:34:37 -0800340 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 Wang34ed4c62014-11-26 15:15:52 -0800343 return versionDir, verror2.Make(ErrOperationFailed, nil)
Robin Thellende2627892014-11-26 09:34:37 -0800344 }
345 path := filepath.Join(pkgDir, localPkg)
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800346 if err := libbinary.DownloadToFile(ctx, pkgName, path); err != nil {
Robin Thellende2627892014-11-26 09:34:37 -0800347 vlog.Infof("DownloadToFile(%q, %q) failed: %v", pkgName, path, err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800348 return versionDir, verror2.Make(ErrOperationFailed, nil)
Robin Thellende2627892014-11-26 09:34:37 -0800349 }
350 }
Bogdan Capritabce0a632014-09-03 16:15:26 -0700351 if err := saveEnvelope(versionDir, envelope); err != nil {
352 return versionDir, err
353 }
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -0700354 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 Wang34ed4c62014-11-26 15:15:52 -0800358 return versionDir, verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -0700359 }
360 }
Bogdan Capritabce0a632014-09-03 16:15:26 -0700361 // 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 Shankar68885192014-11-26 12:48:35 -0800367// TODO(rjkroege): Refactor this code with the instance creation code.
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800368func initializeInstallationACLs(principal security.Principal, dir string, blessings []string, acl access.TaggedACLMap) error {
Asim Shankar68885192014-11-26 12:48:35 -0800369 // 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 Kroegeracc778b2014-11-03 17:17:21 -0800374 }
Robert Kroegeracc778b2014-11-03 17:17:21 -0800375 aclDir := path.Join(dir, "acls")
376 aclData := path.Join(aclDir, "data")
377 aclSig := path.Join(aclDir, "signature")
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800378 return writeACLs(principal, aclData, aclSig, aclDir, acl)
Robert Kroegeracc778b2014-11-03 17:17:21 -0800379}
380
Robin Thellend9bc8fcb2014-11-17 10:23:04 -0800381func (i *appService) Install(call ipc.ServerContext, applicationVON string) (string, error) {
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700382 if len(i.suffix) > 0 {
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800383 return "", verror2.Make(ErrInvalidSuffix, call.Context())
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700384 }
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800385 ctx, cancel := call.Context().WithTimeout(ipcContextTimeout)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700386 defer cancel()
Bogdan Capritabce0a632014-09-03 16:15:26 -0700387 envelope, err := fetchAppEnvelope(ctx, applicationVON)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700388 if err != nil {
389 return "", err
390 }
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700391 installationID := generateID()
392 installationDir := filepath.Join(i.config.Root, applicationDirName(envelope.Title), installationDirName(installationID))
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700393 deferrer := func() {
Robert Kroeger94ec7562014-10-28 17:58:44 -0700394 cleanupDir(installationDir, "")
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700395 }
396 defer func() {
397 if deferrer != nil {
398 deferrer()
399 }
400 }()
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800401 if _, err := newVersion(call.Context(), installationDir, envelope, ""); err != nil {
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700402 return "", err
403 }
Bogdan Capritabce0a632014-09-03 16:15:26 -0700404 if err := saveOrigin(installationDir, applicationVON); err != nil {
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700405 return "", err
406 }
Bogdan Caprita8c776b22014-08-28 17:29:07 -0700407 if err := initializeInstallation(installationDir, active); err != nil {
408 return "", err
409 }
Robert Kroegeracc778b2014-11-03 17:17:21 -0800410
Asim Shankar68885192014-11-26 12:48:35 -0800411 // TODO(caprita,rjkroege): Should the installation ACLs really be
Bogdan Caprita2b219362014-12-09 17:03:33 -0800412 // seeded with the device ACL? Instead, might want to hide the deviceACL
Asim Shankar68885192014-11-26 12:48:35 -0800413 // from the app?
Bogdan Caprita2b219362014-12-09 17:03:33 -0800414 if err := initializeInstallationACLs(call.LocalPrincipal(), installationDir, call.RemoteBlessings().ForContext(call), i.deviceACL.Copy()); err != nil {
Robert Kroegeracc778b2014-11-03 17:17:21 -0800415 return "", err
416 }
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700417 deferrer = nil
418 return naming.Join(envelope.Title, installationID), nil
419}
420
Robin Thellend9bc8fcb2014-11-17 10:23:04 -0800421func (*appService) Refresh(ipc.ServerContext) error {
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700422 // TODO(jsimsa): Implement.
423 return nil
424}
425
Robin Thellend9bc8fcb2014-11-17 10:23:04 -0800426func (*appService) Restart(ipc.ServerContext) error {
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700427 // TODO(jsimsa): Implement.
428 return nil
429}
430
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700431func openWriteFile(path string) (*os.File, error) {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700432 perm := os.FileMode(0600)
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700433 file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, perm)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700434 if err != nil {
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700435 vlog.Errorf("OpenFile(%v) failed: %v", path, err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800436 return nil, verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700437 }
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700438 return file, nil
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700439}
440
Robert Kroegeracc778b2014-11-03 17:17:21 -0800441func installationDirCore(components []string, root string) (string, error) {
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700442 if nComponents := len(components); nComponents != 2 {
Todd Wang34ed4c62014-11-26 15:15:52 -0800443 return "", verror2.Make(ErrInvalidSuffix, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700444 }
445 app, installation := components[0], components[1]
Robert Kroegeracc778b2014-11-03 17:17:21 -0800446 installationDir := filepath.Join(root, applicationDirName(app), installationDirName(installation))
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700447 if _, err := os.Stat(installationDir); err != nil {
448 if os.IsNotExist(err) {
Mike Burrowsd65df962014-12-17 10:01:19 -0800449 return "", verror2.Make(verror2.NoExist, nil, naming.Join(components...))
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700450 }
451 vlog.Errorf("Stat(%v) failed: %v", installationDir, err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800452 return "", verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700453 }
454 return installationDir, nil
455}
456
Bogdan Caprita730bde12014-11-08 15:35:43 -0800457// setupPrincipal sets up the instance's principal, with the right blessings.
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800458func setupPrincipal(ctx context.T, instanceDir, versionDir string, call ipc.ServerContext, securityAgent *securityAgentState, info *instanceInfo) error {
Bogdan Caprita7f491672014-11-13 14:51:08 -0800459 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 Rosencrantz5180d162014-12-03 13:48:40 -0800463 handle, conn, err := securityAgent.keyMgrAgent.NewPrincipal(ctx, false)
Bogdan Caprita7f491672014-11-13 14:51:08 -0800464 defer conn.Close()
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800465
466 runtime := veyron2.RuntimeFromContext(ctx)
467 client, err := runtime.NewClient(options.VCSecurityNone)
Bogdan Caprita7f491672014-11-13 14:51:08 -0800468 if err != nil {
469 vlog.Errorf("NewClient() failed: %v", err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800470 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita7f491672014-11-13 14:51:08 -0800471 }
472 defer client.Close()
473 // TODO(caprita): release the socket created by NewAgentPrincipal.
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800474 if p, err = agent.NewAgentPrincipal(client, int(conn.Fd()), ctx); err != nil {
Bogdan Caprita7f491672014-11-13 14:51:08 -0800475 vlog.Errorf("NewAgentPrincipal() failed: %v", err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800476 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita7f491672014-11-13 14:51:08 -0800477 }
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 Wang34ed4c62014-11-26 15:15:52 -0800486 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita7f491672014-11-13 14:51:08 -0800487 }
Bogdan Caprita26929102014-11-07 11:56:56 -0800488 }
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 Caprita9c4aa222014-12-10 14:46:30 -0800498 dmPrincipal := call.LocalPrincipal()
Bogdan Caprita26929102014-11-07 11:56:56 -0800499 // Take the blessings conferred upon us by the Start-er, extend them
500 // with the app title.
Bogdan Caprita730bde12014-11-08 15:35:43 -0800501 grantedBlessings := call.Blessings()
502 if grantedBlessings == nil {
Todd Wang34ed4c62014-11-26 15:15:52 -0800503 return verror2.Make(ErrInvalidBlessing, nil)
Bogdan Caprita730bde12014-11-08 15:35:43 -0800504 }
Bogdan Caprita26929102014-11-07 11:56:56 -0800505 // TODO(caprita): Revisit UnconstrainedUse.
Bogdan Caprita9c4aa222014-12-10 14:46:30 -0800506 appBlessings, err := dmPrincipal.Bless(p.PublicKey(), grantedBlessings, envelope.Title, security.UnconstrainedUse())
Bogdan Caprita26929102014-11-07 11:56:56 -0800507 if err != nil {
508 vlog.Errorf("Bless() failed: %v", err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800509 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita26929102014-11-07 11:56:56 -0800510 }
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 Wang34ed4c62014-11-26 15:15:52 -0800515 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita26929102014-11-07 11:56:56 -0800516 }
517 if _, err := p.BlessingStore().Set(appBlessings, security.AllPrincipals); err != nil {
518 vlog.Errorf("BlessingStore.Set() failed: %v", err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800519 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita26929102014-11-07 11:56:56 -0800520 }
521 if err := p.AddToRoots(appBlessings); err != nil {
522 vlog.Errorf("AddToRoots() failed: %v", err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800523 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita26929102014-11-07 11:56:56 -0800524 }
525 // In addition, we give the app separate blessings for the purpose of
Bogdan Caprita2b219362014-12-09 17:03:33 -0800526 // communicating with the device manager.
Bogdan Caprita730bde12014-11-08 15:35:43 -0800527 //
528 // NOTE(caprita/ataly): Giving the app an unconstrained blessing from
Bogdan Caprita2b219362014-12-09 17:03:33 -0800529 // the device manager's default presents the app with a blessing that's
Bogdan Caprita730bde12014-11-08 15:35:43 -0800530 // potentially more powerful than what is strictly needed to allow
Bogdan Caprita2b219362014-12-09 17:03:33 -0800531 // communication between device manager and app (which could be more
Bogdan Caprita730bde12014-11-08 15:35:43 -0800532 // narrowly accomplished by using a custom-purpose self-signed blessing
Bogdan Caprita2b219362014-12-09 17:03:33 -0800533 // that the device manger produces solely to talk to the app).
Bogdan Caprita730bde12014-11-08 15:35:43 -0800534 //
535 // TODO(caprita): Figure out if there is any feature value in providing
Bogdan Caprita2b219362014-12-09 17:03:33 -0800536 // the app with a device manager-derived blessing (e.g., may the app
537 // need to prove it's running on the device?).
Bogdan Caprita9c4aa222014-12-10 14:46:30 -0800538 dmBlessings, err := dmPrincipal.Bless(p.PublicKey(), dmPrincipal.BlessingStore().Default(), "callback", security.UnconstrainedUse())
Bogdan Caprita2b219362014-12-09 17:03:33 -0800539 // 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 Caprita9c4aa222014-12-10 14:46:30 -0800542 names := dmPrincipal.BlessingStore().Default().ForContext(call)
Bogdan Caprita26929102014-11-07 11:56:56 -0800543 for _, n := range names {
Bogdan Caprita9c4aa222014-12-10 14:46:30 -0800544 if _, err := p.BlessingStore().Set(dmBlessings, security.BlessingPattern(n)); err != nil {
Bogdan Caprita26929102014-11-07 11:56:56 -0800545 vlog.Errorf("BlessingStore.Set() failed: %v", err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800546 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita26929102014-11-07 11:56:56 -0800547 }
548 }
549 // We also want to override the app cycle manager's server blessing in
Bogdan Caprita2b219362014-12-09 17:03:33 -0800550 // 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 Caprita26929102014-11-07 11:56:56 -0800553 randomPattern, err := generateRandomString()
554 if err != nil {
555 vlog.Errorf("generateRandomString() failed: %v", err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800556 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita26929102014-11-07 11:56:56 -0800557 }
Bogdan Caprita9c4aa222014-12-10 14:46:30 -0800558 if _, err := p.BlessingStore().Set(dmBlessings, security.BlessingPattern(randomPattern)); err != nil {
Bogdan Caprita26929102014-11-07 11:56:56 -0800559 vlog.Errorf("BlessingStore.Set() failed: %v", err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800560 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita26929102014-11-07 11:56:56 -0800561 }
Bogdan Caprita2b219362014-12-09 17:03:33 -0800562 info.DeviceManagerPeerPattern = randomPattern
Bogdan Caprita9c4aa222014-12-10 14:46:30 -0800563 if err := p.AddToRoots(dmBlessings); err != nil {
Bogdan Caprita26929102014-11-07 11:56:56 -0800564 vlog.Errorf("AddToRoots() failed: %v", err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800565 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita26929102014-11-07 11:56:56 -0800566 }
567 return nil
568}
569
Robert Kroegeracc778b2014-11-03 17:17:21 -0800570// 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 Thellend9bc8fcb2014-11-17 10:23:04 -0800574func (i *appService) installationDir() (string, error) {
Robert Kroegeracc778b2014-11-03 17:17:21 -0800575 return installationDirCore(i.suffix, i.config.Root)
576}
577
Robin Thellende2627892014-11-26 09:34:37 -0800578// installPackages installs all the packages for a new instance.
579func 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 Rosencrantz5180d162014-12-03 13:48:40 -0800603func initializeInstanceACLs(principal security.Principal, instanceDir string, blessings []string, acl access.TaggedACLMap) error {
Asim Shankar68885192014-11-26 12:48:35 -0800604 for _, b := range blessings {
605 for _, tag := range access.AllTypicalTags() {
606 acl.Add(security.BlessingPattern(b), string(tag))
607 }
Robert Kroegeracc778b2014-11-03 17:17:21 -0800608 }
Robert Kroegeracc778b2014-11-03 17:17:21 -0800609 aclDir := path.Join(instanceDir, "acls")
610 aclData := path.Join(aclDir, "data")
611 aclSig := path.Join(aclDir, "signature")
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800612 return writeACLs(principal, aclData, aclSig, aclDir, acl)
Robert Kroegeracc778b2014-11-03 17:17:21 -0800613}
614
Bogdan Caprita268b4192014-08-28 10:04:44 -0700615// newInstance sets up the directory for a new application instance.
Robin Thellend9bc8fcb2014-11-17 10:23:04 -0800616func (i *appService) newInstance(call ipc.ServerContext) (string, string, error) {
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700617 installationDir, err := i.installationDir()
618 if err != nil {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700619 return "", "", err
620 }
Bogdan Caprita8c776b22014-08-28 17:29:07 -0700621 if !installationStateIs(installationDir, active) {
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800622 return "", "", verror2.Make(ErrInvalidOperation, call.Context())
Bogdan Caprita8c776b22014-08-28 17:29:07 -0700623 }
Bogdan Caprita268b4192014-08-28 10:04:44 -0700624 instanceID := generateID()
625 instanceDir := filepath.Join(installationDir, "instances", instanceDirName(instanceID))
626 if mkdir(instanceDir) != nil {
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800627 return "", instanceID, verror2.Make(ErrOperationFailed, call.Context())
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700628 }
629 currLink := filepath.Join(installationDir, "current")
Bogdan Caprita268b4192014-08-28 10:04:44 -0700630 versionDir, err := filepath.EvalSymlinks(currLink)
631 if err != nil {
632 vlog.Errorf("EvalSymlinks(%v) failed: %v", currLink, err)
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800633 return instanceDir, instanceID, verror2.Make(ErrOperationFailed, call.Context())
Bogdan Caprita268b4192014-08-28 10:04:44 -0700634 }
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 Rosencrantz8f9fca12014-12-19 14:02:31 -0800638 return instanceDir, instanceID, verror2.Make(ErrOperationFailed, call.Context())
Bogdan Caprita268b4192014-08-28 10:04:44 -0700639 }
Robin Thellende2627892014-11-26 09:34:37 -0800640 if err := installPackages(versionDir, instanceDir); err != nil {
641 vlog.Errorf("installPackages(%v, %v) failed: %v", versionDir, instanceDir, err)
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800642 return instanceDir, instanceID, verror2.Make(ErrOperationFailed, call.Context())
Robin Thellende2627892014-11-26 09:34:37 -0800643 }
Bogdan Caprita26929102014-11-07 11:56:56 -0800644 instanceInfo := new(instanceInfo)
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800645 if err := setupPrincipal(call.Context(), instanceDir, versionDir, call, i.securityAgent, instanceInfo); err != nil {
Bogdan Caprita26929102014-11-07 11:56:56 -0800646 return instanceDir, instanceID, err
647 }
648 if err := saveInstanceInfo(instanceDir, instanceInfo); err != nil {
649 return instanceDir, instanceID, err
650 }
Bogdan Caprita8c776b22014-08-28 17:29:07 -0700651 if err := initializeInstance(instanceDir, suspended); err != nil {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700652 return instanceDir, instanceID, err
653 }
Robert Kroegeracc778b2014-11-03 17:17:21 -0800654
Bogdan Caprita2b219362014-12-09 17:03:33 -0800655 if err := initializeInstanceACLs(call.LocalPrincipal(), instanceDir, call.RemoteBlessings().ForContext(call), i.deviceACL.Copy()); err != nil {
Robert Kroegeracc778b2014-11-03 17:17:21 -0800656 return instanceDir, instanceID, err
657 }
Bogdan Caprita268b4192014-08-28 10:04:44 -0700658 return instanceDir, instanceID, nil
659}
660
Robert Kroeger362ff892014-09-29 14:23:47 -0700661// isSetuid is defined like this so we can override its
662// implementation for tests.
663var 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 Caprita2b219362014-12-09 17:03:33 -0800669// application. If the helper exists and is setuid, the device manager
Robert Kroeger362ff892014-09-29 14:23:47 -0700670// 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 Wang34ed4c62014-11-26 15:15:52 -0800676func systemAccountForHelper(ctx ipc.ServerContext, helperPath string, uat BlessingSystemAssociationStore) (systemName string, err error) {
677 identityNames := ctx.RemoteBlessings().ForContext(ctx)
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700678 helperStat, err := os.Stat(helperPath)
679 if err != nil {
680 vlog.Errorf("Stat(%v) failed: %v. helper is required.", helperPath, err)
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800681 return "", verror2.Make(ErrOperationFailed, ctx.Context())
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700682 }
Robert Kroeger362ff892014-09-29 14:23:47 -0700683 haveHelper := isSetuid(helperStat)
Robert Kroeger1cb4a0d2014-10-20 11:55:38 -0700684 systemName, present := uat.SystemAccountForBlessings(identityNames)
Robert Kroeger362ff892014-09-29 14:23:47 -0700685
686 switch {
687 case haveHelper && present:
688 return systemName, nil
689 case haveHelper && !present:
Bogdan Caprita2b219362014-12-09 17:03:33 -0800690 // 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 Rosencrantz8f9fca12014-12-19 14:02:31 -0800695 return "", verror2.Make(verror2.NoAccess, ctx.Context(), "use of setuid helper requires an associated uname.")
Robert Kroeger362ff892014-09-29 14:23:47 -0700696 case !haveHelper:
697 // When the helper is not setuid, the helper can't change the
Bogdan Caprita2b219362014-12-09 17:03:33 -0800698 // app's uid so just run the app as the device manager's uname
Robert Kroeger362ff892014-09-29 14:23:47 -0700699 // whether or not there is an association.
Bogdan Caprita2b219362014-12-09 17:03:33 -0800700 vlog.VI(1).Infof("helper not setuid. Device manager will invoke app with its own userid")
Robert Kroeger362ff892014-09-29 14:23:47 -0700701 user, err := user.Current()
702 if err != nil {
703 vlog.Errorf("user.Current() failed: %v", err)
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800704 return "", verror2.Make(ErrOperationFailed, ctx.Context())
Robert Kroeger362ff892014-09-29 14:23:47 -0700705 }
706 return user.Username, nil
707 }
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800708 return "", verror2.Make(ErrOperationFailed, ctx.Context())
Robert Kroeger362ff892014-09-29 14:23:47 -0700709}
710
Bogdan Caprita2556a6c2014-12-04 15:51:01 -0800711func genCmd(instanceDir, helperPath, systemName string, nsRoots []string) (*exec.Cmd, error) {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700712 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 Wang34ed4c62014-11-26 15:15:52 -0800716 return nil, verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700717 }
718 envelope, err := loadEnvelope(versionDir)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700719 if err != nil {
720 return nil, err
721 }
Bogdan Caprita268b4192014-08-28 10:04:44 -0700722 binPath := filepath.Join(versionDir, "bin")
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700723 if _, err := os.Stat(binPath); err != nil {
724 vlog.Errorf("Stat(%v) failed: %v", binPath, err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800725 return nil, verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700726 }
Robert Kroegerdd07b362014-09-18 17:34:42 -0700727
Robert Kroegerdd07b362014-09-18 17:34:42 -0700728 cmd := exec.Command(helperPath)
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700729 cmd.Args = append(cmd.Args, "--username", systemName)
Robert Kroegerdd07b362014-09-18 17:34:42 -0700730
Bogdan Caprita2556a6c2014-12-04 15:51:01 -0800731 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 Caprita25d4faa2014-08-28 10:21:23 -0700736 rootDir := filepath.Join(instanceDir, "root")
737 if err := mkdir(rootDir); err != nil {
738 return nil, err
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700739 }
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700740 cmd.Dir = rootDir
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700741 cmd.Args = append(cmd.Args, "--workspace", rootDir)
Robert Kroegerdd07b362014-09-18 17:34:42 -0700742
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700743 logDir := filepath.Join(instanceDir, "logs")
744 if err := mkdir(logDir); err != nil {
745 return nil, err
746 }
Robert Kroegera99ad142014-10-30 17:56:39 -0700747 cmd.Args = append(cmd.Args, "--logdir", logDir)
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700748 timestamp := time.Now().UnixNano()
Robert Kroegera99ad142014-10-30 17:56:39 -0700749
Robert Kroegerdd07b362014-09-18 17:34:42 -0700750 stdoutLog := filepath.Join(logDir, fmt.Sprintf("STDOUT-%d", timestamp))
751 if cmd.Stdout, err = openWriteFile(stdoutLog); err != nil {
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700752 return nil, err
753 }
Robert Kroegerdd07b362014-09-18 17:34:42 -0700754 stderrLog := filepath.Join(logDir, fmt.Sprintf("STDERR-%d", timestamp))
755 if cmd.Stderr, err = openWriteFile(stderrLog); err != nil {
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700756 return nil, err
757 }
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700758 cmd.Args = append(cmd.Args, "--run", binPath)
Robert Kroegerdd07b362014-09-18 17:34:42 -0700759 cmd.Args = append(cmd.Args, "--")
760
Bogdan Caprita2556a6c2014-12-04 15:51:01 -0800761 // Args to be passed by helper to the app.
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700762 cmd.Args = append(cmd.Args, "--log_dir=../logs")
763 cmd.Args = append(cmd.Args, envelope.Args...)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700764 return cmd, nil
765}
766
Robin Thellend9bc8fcb2014-11-17 10:23:04 -0800767func (i *appService) startCmd(instanceDir string, cmd *exec.Cmd) error {
Bogdan Caprita26929102014-11-07 11:56:56 -0800768 info, err := loadInstanceInfo(instanceDir)
769 if err != nil {
770 return err
771 }
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700772 // Setup up the child process callback.
773 callbackState := i.callback
Bogdan Caprita78b62162014-08-21 15:35:08 -0700774 listener := callbackState.listenFor(mgmt.AppCycleManagerConfigKey)
775 defer listener.cleanup()
Cosmos Nicolaou486d3492014-09-30 22:21:20 -0700776 cfg := vexec.NewConfig()
Jiri Simsaf57930f2014-11-05 15:19:31 -0800777 cfg.Set(mgmt.ParentNameConfigKey, listener.name())
778 cfg.Set(mgmt.ProtocolConfigKey, "tcp")
779 cfg.Set(mgmt.AddressConfigKey, "127.0.0.1:0")
Bogdan Caprita2b219362014-12-09 17:03:33 -0800780 cfg.Set(mgmt.ParentBlessingConfigKey, info.DeviceManagerPeerPattern)
Bogdan Caprita7f491672014-11-13 14:51:08 -0800781
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 Caprita4d67c042014-08-19 10:41:19 -0700813 handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
Bogdan Caprita268b4192014-08-28 10:04:44 -0700814 defer func() {
815 if handle != nil {
816 if err := handle.Clean(); err != nil {
817 vlog.Errorf("Clean() failed: %v", err)
818 }
819 }
820 }()
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700821 // Start the child process.
822 if err := handle.Start(); err != nil {
Bogdan Caprita7f491672014-11-13 14:51:08 -0800823 if agentCleaner != nil {
824 agentCleaner()
825 }
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700826 vlog.Errorf("Start() failed: %v", err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800827 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700828 }
Bogdan Caprita7f491672014-11-13 14:51:08 -0800829 if agentCleaner != nil {
830 agentCleaner()
831 }
832
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700833 // Wait for the child process to start.
Bogdan Caprita916e99f2014-11-24 15:47:19 -0800834 if err := handle.WaitForReady(childReadyTimeout); err != nil {
835 vlog.Errorf("WaitForReady(%v) failed: %v", childReadyTimeout, err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800836 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700837 }
Bogdan Caprita916e99f2014-11-24 15:47:19 -0800838 childName, err := listener.waitForValue(childReadyTimeout)
Bogdan Caprita78b62162014-08-21 15:35:08 -0700839 if err != nil {
Todd Wang34ed4c62014-11-26 15:15:52 -0800840 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700841 }
Bogdan Caprita26929102014-11-07 11:56:56 -0800842 info.AppCycleMgrName, info.Pid = childName, handle.Pid()
843 if err := saveInstanceInfo(instanceDir, info); err != nil {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700844 return err
Bogdan Caprita78b62162014-08-21 15:35:08 -0700845 }
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 Caprita268b4192014-08-28 10:04:44 -0700848 handle = nil
849 return nil
850}
851
Bogdan Caprita2556a6c2014-12-04 15:51:01 -0800852func (i *appService) run(nsRoots []string, instanceDir, systemName string) error {
Bogdan Caprita8c776b22014-08-28 17:29:07 -0700853 if err := transitionInstance(instanceDir, suspended, starting); err != nil {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700854 return err
855 }
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700856
Bogdan Caprita2556a6c2014-12-04 15:51:01 -0800857 cmd, err := genCmd(instanceDir, i.config.Helper, systemName, nsRoots)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700858 if err == nil {
859 err = i.startCmd(instanceDir, cmd)
860 }
861 if err != nil {
Bogdan Caprita8c776b22014-08-28 17:29:07 -0700862 transitionInstance(instanceDir, starting, suspended)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700863 return err
864 }
Bogdan Caprita8c776b22014-08-28 17:29:07 -0700865 return transitionInstance(instanceDir, starting, started)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700866}
867
Robin Thellend9bc8fcb2014-11-17 10:23:04 -0800868func (i *appService) Start(call ipc.ServerContext) ([]string, error) {
Robert Kroeger94ec7562014-10-28 17:58:44 -0700869 helper := i.config.Helper
Bogdan Caprita26929102014-11-07 11:56:56 -0800870 instanceDir, instanceID, err := i.newInstance(call)
Robert Kroegeracc778b2014-11-03 17:17:21 -0800871
Bogdan Caprita268b4192014-08-28 10:04:44 -0700872 if err != nil {
Robert Kroeger94ec7562014-10-28 17:58:44 -0700873 cleanupDir(instanceDir, helper)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700874 return nil, err
875 }
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700876
Todd Wang34ed4c62014-11-26 15:15:52 -0800877 systemName, err := systemAccountForHelper(call, helper, i.uat)
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700878 if err != nil {
Robert Kroeger94ec7562014-10-28 17:58:44 -0700879 cleanupDir(instanceDir, helper)
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700880 return nil, err
881 }
882
883 if err := saveSystemNameForInstance(instanceDir, systemName); err != nil {
Robert Kroeger94ec7562014-10-28 17:58:44 -0700884 cleanupDir(instanceDir, helper)
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700885 return nil, err
886 }
887
Bogdan Caprita2b219362014-12-09 17:03:33 -0800888 // For now, use the namespace roots of the device manager runtime to
889 // pass to the app.
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800890 if err = i.run(veyron2.RuntimeFromContext(call.Context()).Namespace().Roots(), instanceDir, systemName); err != nil {
Bogdan Caprita7f491672014-11-13 14:51:08 -0800891 // 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 Kroeger1ce0bd72014-10-22 13:57:14 -0700895 return nil, err
896 }
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700897 return []string{instanceID}, nil
898}
899
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700900// instanceDir returns the path to the directory containing the app instance
Robin Thellend4c5266e2014-10-27 13:19:29 -0700901// referred to by the given suffix relative to the given root directory.
902func instanceDir(root string, suffix []string) (string, error) {
903 if nComponents := len(suffix); nComponents != 3 {
Todd Wang34ed4c62014-11-26 15:15:52 -0800904 return "", verror2.Make(ErrInvalidSuffix, nil)
Robin Thellend4c5266e2014-10-27 13:19:29 -0700905 }
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 Caprita2968f4b2014-08-22 14:11:58 -0700913// 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 Thellend9bc8fcb2014-11-17 10:23:04 -0800915func (i *appService) instanceDir() (string, error) {
Robin Thellend4c5266e2014-10-27 13:19:29 -0700916 return instanceDir(i.config.Root, i.suffix)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700917}
918
Robin Thellend9bc8fcb2014-11-17 10:23:04 -0800919func (i *appService) Resume(call ipc.ServerContext) error {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700920 instanceDir, err := i.instanceDir()
921 if err != nil {
922 return err
923 }
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700924
Todd Wang34ed4c62014-11-26 15:15:52 -0800925 systemName, err := systemAccountForHelper(call, i.config.Helper, i.uat)
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700926 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 Rosencrantz8f9fca12014-12-19 14:02:31 -0800936 return verror2.Make(verror2.NoAccess, call.Context(), "Not allowed to resume an application under a different system name.")
Robert Kroeger1ce0bd72014-10-22 13:57:14 -0700937 }
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800938 return i.run(veyron2.RuntimeFromContext(call.Context()).Namespace().Roots(), instanceDir, systemName)
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700939}
940
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800941func stopAppRemotely(ctx context.T, appVON string) error {
Todd Wang702385a2014-11-07 01:54:08 -0800942 appStub := appcycle.AppCycleClient(appVON)
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800943 ctx, cancel := ctx.WithTimeout(ipcContextTimeout)
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700944 defer cancel()
945 stream, err := appStub.Stop(ctx)
946 if err != nil {
947 vlog.Errorf("%v.Stop() failed: %v", appVON, err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800948 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700949 }
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 Wang34ed4c62014-11-26 15:15:52 -0800956 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700957 }
958 if err := stream.Finish(); err != nil {
959 vlog.Errorf("Finish() failed: %v", err)
Todd Wang34ed4c62014-11-26 15:15:52 -0800960 return verror2.Make(ErrOperationFailed, nil)
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700961 }
962 return nil
963}
964
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800965func stop(ctx context.T, instanceDir string) error {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700966 info, err := loadInstanceInfo(instanceDir)
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700967 if err != nil {
968 return err
969 }
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800970 return stopAppRemotely(ctx, info.AppCycleMgrName)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700971}
972
Bogdan Caprita268b4192014-08-28 10:04:44 -0700973// TODO(caprita): implement deadline for Stop.
974
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800975func (i *appService) Stop(ctx ipc.ServerContext, deadline uint32) error {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700976 instanceDir, err := i.instanceDir()
977 if err != nil {
978 return err
979 }
Todd Wang34ed4c62014-11-26 15:15:52 -0800980 if err := transitionInstance(instanceDir, suspended, stopped); verror2.Is(err, ErrOperationFailed.ID) || err == nil {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700981 return err
982 }
Bogdan Caprita8c776b22014-08-28 17:29:07 -0700983 if err := transitionInstance(instanceDir, started, stopping); err != nil {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700984 return err
985 }
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -0800986 if err := stop(ctx.Context(), instanceDir); err != nil {
Bogdan Caprita8c776b22014-08-28 17:29:07 -0700987 transitionInstance(instanceDir, stopping, started)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700988 return err
989 }
Bogdan Caprita8c776b22014-08-28 17:29:07 -0700990 return transitionInstance(instanceDir, stopping, stopped)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700991}
992
Matt Rosencrantz5180d162014-12-03 13:48:40 -0800993func (i *appService) Suspend(ctx ipc.ServerContext) error {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700994 instanceDir, err := i.instanceDir()
995 if err != nil {
996 return err
997 }
Bogdan Caprita8c776b22014-08-28 17:29:07 -0700998 if err := transitionInstance(instanceDir, started, suspending); err != nil {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700999 return err
1000 }
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -08001001 if err := stop(ctx.Context(), instanceDir); err != nil {
Bogdan Caprita8c776b22014-08-28 17:29:07 -07001002 transitionInstance(instanceDir, suspending, started)
Bogdan Caprita268b4192014-08-28 10:04:44 -07001003 return err
1004 }
Bogdan Caprita8c776b22014-08-28 17:29:07 -07001005 return transitionInstance(instanceDir, suspending, suspended)
Bogdan Caprita4d67c042014-08-19 10:41:19 -07001006}
1007
Robin Thellend9bc8fcb2014-11-17 10:23:04 -08001008func (i *appService) Uninstall(ipc.ServerContext) error {
Bogdan Caprita8c776b22014-08-28 17:29:07 -07001009 installationDir, err := i.installationDir()
1010 if err != nil {
1011 return err
1012 }
1013 return transitionInstallation(installationDir, active, uninstalled)
1014}
1015
Todd Wang34ed4c62014-11-26 15:15:52 -08001016func (i *appService) Update(call ipc.ServerContext) error {
Bogdan Capritabce0a632014-09-03 16:15:26 -07001017 installationDir, err := i.installationDir()
1018 if err != nil {
1019 return err
1020 }
1021 if !installationStateIs(installationDir, active) {
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -08001022 return verror2.Make(ErrInvalidOperation, call.Context())
Bogdan Capritabce0a632014-09-03 16:15:26 -07001023 }
1024 originVON, err := loadOrigin(installationDir)
1025 if err != nil {
1026 return err
1027 }
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -08001028 ctx, cancel := call.Context().WithTimeout(ipcContextTimeout)
Bogdan Capritabce0a632014-09-03 16:15:26 -07001029 defer cancel()
1030 newEnvelope, err := fetchAppEnvelope(ctx, originVON)
1031 if err != nil {
1032 return err
1033 }
1034 currLink := filepath.Join(installationDir, "current")
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -07001035 oldVersionDir, err := filepath.EvalSymlinks(currLink)
1036 if err != nil {
1037 vlog.Errorf("EvalSymlinks(%v) failed: %v", currLink, err)
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -08001038 return verror2.Make(ErrOperationFailed, call.Context())
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -07001039 }
Bogdan Capritabce0a632014-09-03 16:15:26 -07001040 // 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 Caprita53b7b7e2014-09-03 20:51:16 -07001048 oldEnvelope, err := loadEnvelope(oldVersionDir)
Bogdan Capritabce0a632014-09-03 16:15:26 -07001049 if err != nil {
1050 return err
1051 }
1052 if oldEnvelope.Title != newEnvelope.Title {
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -08001053 return verror2.Make(ErrAppTitleMismatch, call.Context())
Bogdan Capritabce0a632014-09-03 16:15:26 -07001054 }
1055 if reflect.DeepEqual(oldEnvelope, newEnvelope) {
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -08001056 return verror2.Make(ErrUpdateNoOp, call.Context())
Bogdan Capritabce0a632014-09-03 16:15:26 -07001057 }
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -08001058 versionDir, err := newVersion(call.Context(), installationDir, newEnvelope, oldVersionDir)
Bogdan Capritabce0a632014-09-03 16:15:26 -07001059 if err != nil {
Robert Kroeger94ec7562014-10-28 17:58:44 -07001060 cleanupDir(versionDir, "")
Bogdan Capritabce0a632014-09-03 16:15:26 -07001061 return err
1062 }
Bogdan Caprita4d67c042014-08-19 10:41:19 -07001063 return nil
1064}
1065
Robin Thellend9bc8fcb2014-11-17 10:23:04 -08001066func (*appService) UpdateTo(_ ipc.ServerContext, von string) error {
Bogdan Caprita4d67c042014-08-19 10:41:19 -07001067 // TODO(jsimsa): Implement.
1068 return nil
1069}
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -07001070
Todd Wang34ed4c62014-11-26 15:15:52 -08001071func (i *appService) Revert(ctx ipc.ServerContext) error {
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -07001072 installationDir, err := i.installationDir()
1073 if err != nil {
1074 return err
1075 }
1076 if !installationStateIs(installationDir, active) {
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -08001077 return verror2.Make(ErrInvalidOperation, ctx.Context())
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -07001078 }
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 Rosencrantz8f9fca12014-12-19 14:02:31 -08001088 return verror2.Make(ErrOperationFailed, ctx.Context())
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -07001089 }
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 Rosencrantz8f9fca12014-12-19 14:02:31 -08001094 return verror2.Make(ErrUpdateNoOp, ctx.Context())
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -07001095 }
1096 vlog.Errorf("Lstat(%v) failed: %v", previousLink, err)
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -08001097 return verror2.Make(ErrOperationFailed, ctx.Context())
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -07001098 }
1099 prevVersionDir, err := filepath.EvalSymlinks(previousLink)
1100 if err != nil {
1101 vlog.Errorf("EvalSymlinks(%v) failed: %v", previousLink, err)
Matt Rosencrantz8f9fca12014-12-19 14:02:31 -08001102 return verror2.Make(ErrOperationFailed, ctx.Context())
Bogdan Caprita53b7b7e2014-09-03 20:51:16 -07001103 }
1104 return updateLink(prevVersionDir, currLink)
1105}
Robin Thellend09929f42014-10-01 10:18:13 -07001106
Robin Thellend9e523a62014-10-07 16:19:53 -07001107type treeNode struct {
1108 children map[string]*treeNode
1109}
1110
1111func newTreeNode() *treeNode {
Robin Thellendb9dd9bb2014-10-29 13:54:08 -07001112 return &treeNode{children: make(map[string]*treeNode)}
Robin Thellend9e523a62014-10-07 16:19:53 -07001113}
1114
1115func (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 Thellend9bc8fcb2014-11-17 10:23:04 -08001136func (i *appService) scanEnvelopes(tree *treeNode, appDir string) {
Robin Thellendac7128c2014-11-11 09:58:28 -08001137 // Find all envelopes, extract installID.
1138 envGlob := []string{i.config.Root, appDir, "installation-*", "*", "envelope"}
Robin Thellend9e523a62014-10-07 16:19:53 -07001139 envelopes, err := filepath.Glob(filepath.Join(envGlob...))
1140 if err != nil {
1141 vlog.Errorf("unexpected error: %v", err)
Robin Thellendac7128c2014-11-11 09:58:28 -08001142 return
Robin Thellend9e523a62014-10-07 16:19:53 -07001143 }
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 Thellend9e523a62014-10-07 16:19:53 -07001155 installID := strings.TrimPrefix(elems[1], "installation-")
Robin Thellend9e523a62014-10-07 16:19:53 -07001156 tree.find([]string{env.Title, installID}, true)
1157 }
Robin Thellendac7128c2014-11-11 09:58:28 -08001158 return
1159}
Robin Thellend9e523a62014-10-07 16:19:53 -07001160
Robin Thellend9bc8fcb2014-11-17 10:23:04 -08001161func (i *appService) scanInstances(tree *treeNode) {
Robin Thellendac7128c2014-11-11 09:58:28 -08001162 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 Caprita43bc7372014-12-03 21:51:12 -08001170 // Add the node corresponding to the installation itself.
1171 tree.find(i.suffix[:2], true)
Robin Thellend9e523a62014-10-07 16:19:53 -07001172 // Find all instances.
Robin Thellendac7128c2014-11-11 09:58:28 -08001173 infoGlob := []string{installDir, "instances", "instance-*", "info"}
Robin Thellend9e523a62014-10-07 16:19:53 -07001174 instances, err := filepath.Glob(filepath.Join(infoGlob...))
1175 if err != nil {
1176 vlog.Errorf("unexpected error: %v", err)
Robin Thellendac7128c2014-11-11 09:58:28 -08001177 return
Robin Thellend9e523a62014-10-07 16:19:53 -07001178 }
1179 for _, path := range instances {
Robin Thellend4c5266e2014-10-27 13:19:29 -07001180 instanceDir := filepath.Dir(path)
Robin Thellendac7128c2014-11-11 09:58:28 -08001181 i.scanInstance(tree, title, instanceDir)
1182 }
1183 return
1184}
1185
Robin Thellend9bc8fcb2014-11-17 10:23:04 -08001186func (i *appService) scanInstance(tree *treeNode, title, instanceDir string) {
Robin Thellendac7128c2014-11-11 09:58:28 -08001187 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 Thellend39ac3232014-12-02 09:50:41 -08001206func (i *appService) GlobChildren__(ipc.ServerContext) (<-chan string, error) {
Robin Thellendac7128c2014-11-11 09:58:28 -08001207 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 Thellendb9dd9bb2014-10-29 13:54:08 -07001218 if err != nil {
Robin Thellendac7128c2014-11-11 09:58:28 -08001219 break
Robin Thellend9e523a62014-10-07 16:19:53 -07001220 }
Robin Thellendac7128c2014-11-11 09:58:28 -08001221 i.scanInstance(tree, i.suffix[0], dir)
1222 default:
Mike Burrowsd65df962014-12-17 10:01:19 -08001223 return nil, verror2.Make(verror2.NoExist, nil, i.suffix)
Robin Thellend9e523a62014-10-07 16:19:53 -07001224 }
Robin Thellendac7128c2014-11-11 09:58:28 -08001225 n := tree.find(i.suffix, false)
Robin Thellend9e523a62014-10-07 16:19:53 -07001226 if n == nil {
Todd Wang34ed4c62014-11-26 15:15:52 -08001227 return nil, verror2.Make(ErrInvalidSuffix, nil)
Robin Thellend09929f42014-10-01 10:18:13 -07001228 }
Robin Thellenda2b00432014-12-18 15:03:25 -08001229 ch := make(chan string)
Robin Thellend8e9cc242014-11-26 09:43:10 -08001230 go func() {
1231 for child, _ := range n.children {
1232 ch <- child
1233 }
1234 close(ch)
1235 }()
1236 return ch, nil
Robin Thellendb9dd9bb2014-10-29 13:54:08 -07001237}
Robert Kroegeracc778b2014-11-03 17:17:21 -08001238
1239// TODO(rjkroege): Refactor to eliminate redundancy with newAppSpecificAuthorizer.
1240func 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 Wang34ed4c62014-11-26 15:15:52 -08001256 return "", verror2.Make(ErrInvalidSuffix, nil)
Robert Kroegeracc778b2014-11-03 17:17:21 -08001257}
1258
1259// TODO(rjkroege): Consider maintaining an in-memory ACL cache.
Matt Rosencrantz5180d162014-12-03 13:48:40 -08001260func (i *appService) SetACL(ctx ipc.ServerContext, acl access.TaggedACLMap, etag string) error {
Robert Kroegeracc778b2014-11-03 17:17:21 -08001261 dir, err := dirFromSuffix(i.suffix, i.config.Root)
1262 if err != nil {
1263 return err
1264 }
Matt Rosencrantz5180d162014-12-03 13:48:40 -08001265 return setAppACL(ctx.LocalPrincipal(), i.locks, dir, acl, etag)
Robert Kroegeracc778b2014-11-03 17:17:21 -08001266}
1267
Asim Shankar68885192014-11-26 12:48:35 -08001268func (i *appService) GetACL(_ ipc.ServerContext) (acl access.TaggedACLMap, etag string, err error) {
Robert Kroegeracc778b2014-11-03 17:17:21 -08001269 dir, err := dirFromSuffix(i.suffix, i.config.Root)
1270 if err != nil {
Asim Shankar68885192014-11-26 12:48:35 -08001271 return nil, "", err
Robert Kroegeracc778b2014-11-03 17:17:21 -08001272 }
Robert Kroeger7d979d82014-11-07 17:24:35 -08001273 return getAppACL(i.locks, dir)
Robert Kroegeracc778b2014-11-03 17:17:21 -08001274}