veyron/services/mgmt/device/impl: share packages across instances

Instead of making a fresh copy of all package dirs and files for each new
instance, only create them once in the version directory, and add a symlink from
each instance.

To make this work in multi-user mode, change the permissions for the created
package dirs and files to 755.

Change-Id: I28ac21f69ade980824349b1aa435bf19baac51f7
diff --git a/services/mgmt/device/impl/app_service.go b/services/mgmt/device/impl/app_service.go
index d696aa7..4614bab 100644
--- a/services/mgmt/device/impl/app_service.go
+++ b/services/mgmt/device/impl/app_service.go
@@ -21,17 +21,16 @@
 //       origin(700d)                     - object name for application envelope
 //       config(700d)                     - Config provided by the installer
 //       packages(700d)                   - set of packages specified by the installer
-//       pkg/(700d)                       - downloaded packages
+//       pkg(700d)/                       - downloaded packages
 //         <pkg name>(700d)
 //         <pkg name>.__info(700d)
 //         ...
-//       <version 1 timestamp>/(700d)     - timestamp of when the version was downloaded
+//       <version 1 timestamp>(700d)/     - timestamp of when the version was downloaded
 //         bin(755d)                      - application binary
 //         previous                       - symbolic link to previous version directory
 //         envelope                       - application envelope (JSON-encoded)
-//         pkg(700d)/                     - the application packages
-//           <pkg name>(700d)
-//           <pkg name>.__info(700d)
+//         packages(755d)/                - installed packages (from envelope+installer)
+//           <pkg name>(755d)/
 //           ...
 //       <version 2 timestamp>(700d)
 //       ...
@@ -41,9 +40,7 @@
 //           credentials(700d)/           - holds veyron credentials (unless running
 //                                          through security agent)
 //           root(700a)/                  - workspace that the instance is run from
-//             packages(700a)/            - the installed packages
-//               <pkg name>(700a)/
-//               ...
+//             packages                   - symbolic link to version's packages
 //           logs(755a)/                  - stderr/stdout and log files generated by instance
 //           info(700d)                   - metadata for the instance (such as app
 //                                          cycle manager name and process id)
@@ -394,6 +391,9 @@
 	if err := mkdirPerm(versionDir, 0711); err != nil {
 		return "", verror.New(ErrOperationFailed, nil)
 	}
+	if err := saveEnvelope(versionDir, envelope); err != nil {
+		return versionDir, err
+	}
 	pkgDir := filepath.Join(versionDir, "pkg")
 	if err := mkdir(pkgDir); err != nil {
 		return "", verror.New(ErrOperationFailed, nil)
@@ -410,8 +410,13 @@
 	if err := downloadPackages(ctx, publisher, envelope.Packages, pkgDir); err != nil {
 		return versionDir, err
 	}
-	if err := saveEnvelope(versionDir, envelope); err != nil {
-		return versionDir, err
+	if err := installPackages(installationDir, versionDir); err != nil {
+		vlog.Errorf("installPackages(%v, %v) failed: %v", installationDir, versionDir, err)
+		return versionDir, verror.New(ErrOperationFailed, nil)
+	}
+	if err := os.RemoveAll(pkgDir); err != nil {
+		vlog.Errorf("RemoveAll(%v) failed: %v", pkgDir, err)
+		return versionDir, verror.New(ErrOperationFailed, nil)
 	}
 	if oldVersionDir != "" {
 		previousLink := filepath.Join(versionDir, "previous")
@@ -441,14 +446,14 @@
 	deferrer := func() {
 		cleanupDir(installationDir, "")
 	}
+	if err := mkdirPerm(installationDir, 0711); err != nil {
+		return "", verror.New(ErrOperationFailed, nil)
+	}
 	defer func() {
 		if deferrer != nil {
 			deferrer()
 		}
 	}()
-	if _, err := newVersion(call.Context(), installationDir, envelope, ""); err != nil {
-		return "", err
-	}
 	if newOrigin, ok := config[mgmt.AppOriginConfigKey]; ok {
 		delete(config, mgmt.AppOriginConfigKey)
 		applicationVON = newOrigin
@@ -462,9 +467,6 @@
 	if err := savePackages(installationDir, packages); err != nil {
 		return "", err
 	}
-	if err := mkdirPerm(installationDir, 0711); err != nil {
-		return "", verror.New(ErrOperationFailed, nil)
-	}
 	pkgDir := filepath.Join(installationDir, "pkg")
 	if err := mkdir(pkgDir); err != nil {
 		return "", verror.New(ErrOperationFailed, nil)
@@ -475,6 +477,9 @@
 	if err := downloadPackages(call.Context(), nil, packages, pkgDir); err != nil {
 		return "", err
 	}
+	if _, err := newVersion(call.Context(), installationDir, envelope, ""); err != nil {
+		return "", err
+	}
 	if err := initializeInstallation(installationDir, active); err != nil {
 		return "", err
 	}
@@ -669,8 +674,8 @@
 	return installationDirCore(i.suffix, i.config.Root)
 }
 
-// installPackages installs all the packages for a new instance.
-func installPackages(installationDir, versionDir, instanceDir string) error {
+// installPackages installs all the packages for a new version.
+func installPackages(installationDir, versionDir string) error {
 	overridePackages, err := loadPackages(installationDir)
 	if err != nil {
 		return err
@@ -682,13 +687,11 @@
 	for pkg, _ := range overridePackages {
 		delete(envelope.Packages, pkg)
 	}
-	packagesDir := filepath.Join(instanceDir, "root", "packages")
-	if err := os.MkdirAll(packagesDir, os.FileMode(0700)); err != nil {
+	packagesDir := filepath.Join(versionDir, "packages")
+	if err := os.MkdirAll(packagesDir, os.FileMode(0755)); err != nil {
 		return err
 	}
 	installFrom := func(packages application.Packages, sourceDir string) error {
-		// TODO(rthellend): Consider making the packages read-only and
-		// sharing them between apps or instances.
 		for pkg, _ := range packages {
 			pkgFile := filepath.Join(sourceDir, "pkg", pkg)
 			dst := filepath.Join(packagesDir, pkg)
@@ -745,8 +748,13 @@
 		vlog.Errorf("Symlink(%v, %v) failed: %v", versionDir, versionLink, err)
 		return instanceDir, instanceID, verror.New(ErrOperationFailed, call.Context())
 	}
-	if err := installPackages(installationDir, versionDir, instanceDir); err != nil {
-		vlog.Errorf("installPackages(%v, %v, %v) failed: %v", installationDir, versionDir, instanceDir, err)
+	rootDir := filepath.Join(instanceDir, "root")
+	if err := mkdir(rootDir); err != nil {
+		return instanceDir, instanceID, verror.New(ErrOperationFailed, call.Context())
+	}
+	packagesDir, packagesLink := filepath.Join(versionDir, "packages"), filepath.Join(rootDir, "packages")
+	if err := os.Symlink(packagesDir, packagesLink); err != nil {
+		vlog.Errorf("Symlink(%v, %v) failed: %v", packagesDir, packagesLink, err)
 		return instanceDir, instanceID, verror.New(ErrOperationFailed, call.Context())
 	}
 	instanceInfo := new(instanceInfo)
@@ -799,9 +807,6 @@
 	cmd.Env = []string{consts.NamespaceRootPrefix + "=" + nsRoot}
 	cmd.Env = append(cmd.Env, envelope.Env...)
 	rootDir := filepath.Join(instanceDir, "root")
-	if err := mkdir(rootDir); err != nil {
-		return nil, err
-	}
 	cmd.Dir = rootDir
 	cmd.Args = append(cmd.Args, "--workspace", rootDir)
 
diff --git a/services/mgmt/lib/packages/packages.go b/services/mgmt/lib/packages/packages.go
index 2b42ade..9f3accf 100644
--- a/services/mgmt/lib/packages/packages.go
+++ b/services/mgmt/lib/packages/packages.go
@@ -17,7 +17,10 @@
 	"v.io/v23/services/mgmt/repository"
 )
 
-const defaultType = "application/octet-stream"
+const (
+	defaultType    = "application/octet-stream"
+	createFileMode = 0755
+)
 
 var typemap = map[string]repository.MediaInfo{
 	".zip":     repository.MediaInfo{Type: "application/zip"},
@@ -153,7 +156,7 @@
 }
 
 func extractZip(zipFile, installDir string) error {
-	if err := os.Mkdir(installDir, os.FileMode(0700)); err != nil {
+	if err := os.Mkdir(installDir, os.FileMode(createFileMode)); err != nil {
 		return fmt.Errorf("os.Mkdir(%q) failed: %v", installDir, err)
 	}
 	zr, err := zip.OpenReader(zipFile)
@@ -167,7 +170,7 @@
 			return fmt.Errorf("failed to extract file %q outside of install directory", file.Name)
 		}
 		if fi.IsDir() {
-			if err := os.MkdirAll(name, os.FileMode(fi.Mode()&0700)); err != nil && !os.IsExist(err) {
+			if err := os.MkdirAll(name, os.FileMode(fi.Mode()&createFileMode)); err != nil && !os.IsExist(err) {
 				return err
 			}
 			continue
@@ -177,10 +180,10 @@
 			return err
 		}
 		parentName := filepath.Dir(name)
-		if err := os.MkdirAll(parentName, os.FileMode(0700)); err != nil {
+		if err := os.MkdirAll(parentName, os.FileMode(createFileMode)); err != nil {
 			return err
 		}
-		out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(fi.Mode()&0700))
+		out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(fi.Mode()&createFileMode))
 		if err != nil {
 			in.Close()
 			return err
@@ -199,7 +202,7 @@
 }
 
 func extractTar(pkgFile string, encoding string, installDir string) error {
-	if err := os.Mkdir(installDir, os.FileMode(0700)); err != nil {
+	if err := os.Mkdir(installDir, os.FileMode(createFileMode)); err != nil {
 		return fmt.Errorf("os.Mkdir(%q) failed: %v", installDir, err)
 	}
 	f, err := os.Open(pkgFile)
@@ -238,7 +241,7 @@
 		}
 		// Regular file
 		if hdr.Typeflag == tar.TypeReg {
-			out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(hdr.Mode&0700))
+			out, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, os.FileMode(hdr.Mode&createFileMode))
 			if err != nil {
 				return err
 			}
@@ -254,7 +257,7 @@
 		}
 		// Directory
 		if hdr.Typeflag == tar.TypeDir {
-			if err := os.Mkdir(name, os.FileMode(hdr.Mode&0700)); err != nil && !os.IsExist(err) {
+			if err := os.Mkdir(name, os.FileMode(hdr.Mode&createFileMode)); err != nil && !os.IsExist(err) {
 				return err
 			}
 			continue