[updates] install via needs directories

pkgfs no longer depends on Amber, instead Amber performs all of the
installation operations off of it's own back.

Change-Id: I8de8f98e2fc4094484d295db45b30d7e1d747667
diff --git a/garnet/go/src/amber/amberd/amberd.go b/garnet/go/src/amber/amberd/amberd.go
index 58891ea..f7cdc4b 100644
--- a/garnet/go/src/amber/amberd/amberd.go
+++ b/garnet/go/src/amber/amberd/amberd.go
@@ -67,7 +67,7 @@
 
 	var ctlSvc amber.ControlService
 	var evtSvc amber.EventsService
-	d, err := daemon.NewDaemon(*store, "", "", &evtSvc)
+	d, err := daemon.NewDaemon(*store, "", "", "", &evtSvc)
 	if err != nil {
 		log.Fatalf("failed to start daemon: %s", err)
 	}
diff --git a/garnet/go/src/amber/control_server/control_server.go b/garnet/go/src/amber/control_server/control_server.go
index 5a45b7f..5174c66 100644
--- a/garnet/go/src/amber/control_server/control_server.go
+++ b/garnet/go/src/amber/control_server/control_server.go
@@ -75,13 +75,17 @@
 func (c *ControlServer) GetUpdateComplete(name string, ver, mer *string) (zx.Channel, error) {
 	r, ch, e := zx.NewChannel(0)
 	if e != nil {
-		log.Printf("Could not create channel")
+		log.Printf("getupdatecomplete: could not create channel")
 		// TODO(raggi): the client is just going to get peer closed, and no indication of why
-		return zx.Channel(zx.HandleInvalid), e
+		return zx.Channel(zx.HandleInvalid), nil
 	}
 
 	if len(name) == 0 {
-		return zx.Channel(zx.HandleInvalid), fmt.Errorf("No package name provided")
+		log.Printf("getupdatecomplete: invalid arguments: empty name")
+		ch.Handle().SignalPeer(0, zx.SignalUser0)
+		ch.Write([]byte(zx.ErrInvalidArgs.String()), []zx.Handle{}, 0)
+		ch.Close()
+		return r, nil
 	}
 
 	var (
@@ -97,6 +101,7 @@
 	}
 
 	go func() {
+		defer ch.Close()
 		c.daemon.UpdateIfStale()
 
 		root, length, err := c.daemon.MerkleFor(name, version, merkle)
@@ -104,56 +109,38 @@
 			log.Printf("control_server: could not get update for %s: %s", filepath.Join(name, version, merkle), err)
 			ch.Handle().SignalPeer(0, zx.SignalUser0)
 			ch.Write([]byte(err.Error()), []zx.Handle{}, 0)
-			ch.Close()
 			return
 		}
 
 		if _, err := os.Stat(filepath.Join("/pkgfs/versions", root)); err == nil {
 			ch.Write([]byte(root), []zx.Handle{}, 0)
-			ch.Close()
 			return
 		}
 
 		log.Printf("control_server: get update: %s", filepath.Join(name, version, merkle))
 
-		c.daemon.AddWatch(root, func(root string, err error) {
-			if os.IsExist(err) {
-				log.Printf("control_server: %s already installed", filepath.Join(name, version, root))
-				// signal success to the client
-				err = nil
-			}
-			if err != nil {
-				log.Printf("control_server: error downloading package: %s", err)
-				ch.Handle().SignalPeer(0, zx.SignalUser0)
-				ch.Write([]byte(err.Error()), []zx.Handle{}, 0)
-				ch.Close()
-				return
-			}
+		err = c.daemon.GetPkg(root, length)
+		if os.IsExist(err) {
+			log.Printf("control_server: %s already installed", filepath.Join(name, version, root))
+			// signal success to the client
+			err = nil
+		}
+		if err != nil {
+			log.Printf("control_server: error downloading package: %s", err)
+			ch.Handle().SignalPeer(0, zx.SignalUser0)
+			ch.Write([]byte(err.Error()), []zx.Handle{}, 0)
+		} else {
 			ch.Write([]byte(root), []zx.Handle{}, 0)
-			ch.Close()
-			return
-		})
-
-		// errors are handled by the watcher callback
-		c.daemon.GetPkg(root, length)
+		}
 	}()
-
 	return r, nil
 }
 
 func (c *ControlServer) PackagesActivated(merkle []string) error {
-	log.Printf("control_server: packages activated %s", merkle)
-	for _, m := range merkle {
-		c.daemon.Activated(m)
-	}
 	return nil
 }
 
 func (c *ControlServer) PackagesFailed(merkle []string, status int32, blobMerkle string) error {
-	log.Printf("control_server: packages failed %s due to blob %s, status: %d", merkle, blobMerkle, status)
-	for _, m := range merkle {
-		c.daemon.Failed(m, zx.Status(status))
-	}
 	return nil
 }
 
@@ -174,13 +161,6 @@
 }
 
 func (c *ControlServer) GetBlob(merkle string) error {
-	log.Printf("control_server: blob requested: %q", merkle)
-	if !merklePat.MatchString(merkle) {
-		return fmt.Errorf("%q is not a valid merkle root", merkle)
-	}
-
-	go c.daemon.GetBlob(merkle)
-
 	return nil
 }
 
diff --git a/garnet/go/src/amber/daemon/activation_watcher.go b/garnet/go/src/amber/daemon/activation_watcher.go
deleted file mode 100644
index d821f97..0000000
--- a/garnet/go/src/amber/daemon/activation_watcher.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package daemon
-
-import (
-	"sync"
-)
-
-type activationWatcher struct {
-	mu        sync.Mutex
-	observers map[string][]func(string, error)
-}
-
-func (a *activationWatcher) addWatch(root string, f func(string, error)) {
-	a.mu.Lock()
-	defer a.mu.Unlock()
-
-	if a.observers == nil {
-		a.observers = make(map[string][]func(string, error))
-	}
-
-	a.observers[root] = append(a.observers[root], f)
-}
-
-func (a *activationWatcher) update(root string, err error) {
-	a.mu.Lock()
-	fs := a.observers[root]
-	delete(a.observers, root)
-	a.mu.Unlock()
-
-	for _, f := range fs {
-		f(root, err)
-	}
-}
diff --git a/garnet/go/src/amber/daemon/daemon.go b/garnet/go/src/amber/daemon/daemon.go
index 2308e0b..f4d3fb1 100644
--- a/garnet/go/src/amber/daemon/daemon.go
+++ b/garnet/go/src/amber/daemon/daemon.go
@@ -25,6 +25,7 @@
 const (
 	DefaultPkgInstallDir  = "/pkgfs/install/pkg"
 	DefaultBlobInstallDir = "/pkgfs/install/blob"
+	DefaultPkgNeedsDir    = "/pkgfs/needs/packages"
 	PackageGarbageDir     = "/pkgfs/garbage"
 )
 
@@ -32,27 +33,31 @@
 	store          string
 	pkgInstallDir  string
 	blobInstallDir string
+	pkgNeedsDir    string
 
 	muSrcs sync.Mutex
 	srcs   map[string]*source.Source
 
 	events *amber.EventsService
-	aw     activationWatcher
 }
 
 // NewDaemon creates a Daemon
-func NewDaemon(store, pkgInstallDir, blobInstallDir string, events *amber.EventsService) (*Daemon, error) {
+func NewDaemon(store, pkgInstallDir, blobInstallDir, pkgNeedsDir string, events *amber.EventsService) (*Daemon, error) {
 	if pkgInstallDir == "" {
 		pkgInstallDir = DefaultPkgInstallDir
 	}
 	if blobInstallDir == "" {
 		blobInstallDir = DefaultBlobInstallDir
 	}
+	if pkgNeedsDir == "" {
+		pkgNeedsDir = DefaultPkgNeedsDir
+	}
 
 	d := &Daemon{
 		store:          store,
 		pkgInstallDir:  pkgInstallDir,
 		blobInstallDir: blobInstallDir,
+		pkgNeedsDir:    pkgNeedsDir,
 		srcs:           make(map[string]*source.Source),
 		events:         events,
 	}
@@ -394,28 +399,38 @@
 
 	err := d.fetchInto(merkle, length, d.pkgInstallDir)
 	if os.IsExist(err) {
-		// Packages that already exist are simply "successful"
-		d.aw.update(merkle, nil)
 		return nil
 	}
 
 	if err != nil {
-		// errors fetching the package meta.far are terminal
-		d.aw.update(merkle, err)
 		log.Printf("error fetching pkg %q: %s", merkle, err)
+		return err
 	}
 
-	// In the non-error case, waiters are updated by activation.
-	// XXX(raggi): note this is a potentially unbounded wait.
-	return err
-}
+	needsDir, err := os.Open(filepath.Join(d.pkgNeedsDir, merkle))
+	if os.IsNotExist(err) {
+		// Package is fully installed already
+		return nil
+	}
+	defer needsDir.Close()
 
-// GetBlob is a blocking call which downloads the requested blob
-func (d *Daemon) GetBlob(merkle string) error {
-	err := d.fetchInto(merkle, -1, d.blobInstallDir)
-	d.aw.update(merkle, err)
-	if err != nil && !os.IsExist(err) {
-		log.Printf("error fetching blob %q: %s", merkle, err)
+	neededBlobs, err := needsDir.Readdirnames(-1)
+	if err != nil {
+		return err
+	}
+	for len(neededBlobs) > 0 {
+		for _, blob := range neededBlobs {
+			// TODO(raggi): switch to using the needs paths for install
+			err := d.fetchInto(blob, -1, d.blobInstallDir)
+			if err != nil {
+				return err
+			}
+		}
+
+		neededBlobs, err = needsDir.Readdirnames(-1)
+		if err != nil {
+			return err
+		}
 	}
 	return err
 }
@@ -444,18 +459,6 @@
 	})
 }
 
-func (d *Daemon) AddWatch(merkle string, f func(string, error)) {
-	d.aw.addWatch(merkle, f)
-}
-
-func (d *Daemon) Activated(merkle string) {
-	d.aw.update(merkle, nil)
-}
-
-func (d *Daemon) Failed(merkle string, status zx.Status) {
-	d.aw.update(merkle, &zx.Error{Status: status})
-}
-
 func (d *Daemon) GC() error {
 	// Garbage collection is done by trying to unlink a particular control
 	// file exposed by pkgfs.
diff --git a/garnet/go/src/amber/daemon/daemon_test.go b/garnet/go/src/amber/daemon/daemon_test.go
index 0f7d067..f46a6b4 100644
--- a/garnet/go/src/amber/daemon/daemon_test.go
+++ b/garnet/go/src/amber/daemon/daemon_test.go
@@ -29,7 +29,7 @@
 	}
 	defer os.RemoveAll(store)
 
-	d, err := NewDaemon(store, "", "", nil)
+	d, err := NewDaemon(store, "", "", "", nil)
 	if err != nil {
 		panic(err)
 	}
@@ -209,8 +209,11 @@
 	blobsDir, err := ioutil.TempDir("", "amber-test-blobs")
 	panicerr(err)
 	defer os.RemoveAll(blobsDir)
+	pkgNeedsDir, err := ioutil.TempDir("", "amber-test-pkgneeds")
+	panicerr(err)
+	defer os.RemoveAll(pkgNeedsDir)
 
-	d, err := NewDaemon(store, pkgsDir, blobsDir, nil)
+	d, err := NewDaemon(store, pkgsDir, blobsDir, pkgNeedsDir, nil)
 	panicerr(err)
 
 	err = d.AddSource(&amber.SourceConfig{
@@ -242,30 +245,17 @@
 		t.Errorf("merkleFor length: got %d, want %d", length, pkgBlobLength)
 	}
 
-	// TODO(raggi): add coverage for error propagatation cases
-	var rootSeen string
-	var errSeen error
-	d.AddWatch(pkgBlob, func(root string, err error) {
-		rootSeen = root
-		errSeen = err
-	})
+	os.MkdirAll(filepath.Join(pkgNeedsDir, pkgBlob), 0755)
+	panicerr(ioutil.WriteFile(filepath.Join(pkgNeedsDir, pkgBlob, root1), []byte{}, 0644))
+
 	panicerr(d.GetPkg(pkgBlob, pkgBlobLength))
 
-	d.Activated(pkgBlob)
-
-	if rootSeen != pkgBlob {
-		t.Errorf("activation: got %q, want %q", rootSeen, pkgBlob)
-	}
-	panicerr(errSeen)
-
 	c, err := ioutil.ReadFile(pkgsDir + "/" + pkgBlob)
 	panicerr(err)
 	if got := string(c); got != pkgContent {
 		t.Errorf("getpkg: got %q, want %q", got, pkgContent)
 	}
 
-	panicerr(d.GetBlob(root1))
-
 	c, err = ioutil.ReadFile(blobsDir + "/" + root1)
 	panicerr(err)
 	if got, want := string(c), "first blob"; got != want {
@@ -341,8 +331,11 @@
 	blobsDir, err := ioutil.TempDir("", "amber-test-blobs")
 	panicerr(err)
 	defer os.RemoveAll(blobsDir)
+	pkgNeedsDir, err := ioutil.TempDir("", "amber-test-pkgneeds")
+	panicerr(err)
+	defer os.RemoveAll(pkgNeedsDir)
 
-	d, err := NewDaemon(store, pkgsDir, blobsDir, nil)
+	d, err := NewDaemon(store, pkgsDir, blobsDir, pkgNeedsDir, nil)
 	panicerr(err)
 
 	err = d.AddSource(&amber.SourceConfig{
@@ -377,252 +370,20 @@
 		t.Errorf("merkleFor length: got %d, want %d", length, pkgBlobLength)
 	}
 
-	// TODO(raggi): add coverage for error propagatation cases
-	var rootSeen string
-	var errSeen error
-	d.AddWatch(pkgBlob, func(root string, err error) {
-		rootSeen = root
-		errSeen = err
-	})
+	os.MkdirAll(filepath.Join(pkgNeedsDir, pkgBlob), 0755)
+	panicerr(ioutil.WriteFile(filepath.Join(pkgNeedsDir, pkgBlob, root1), []byte{}, 0644))
+
 	panicerr(d.GetPkg(pkgBlob, pkgBlobLength))
 
-	d.Activated(pkgBlob)
-
-	if rootSeen != pkgBlob {
-		t.Errorf("activation: got %q, want %q", rootSeen, pkgBlob)
-	}
-	panicerr(errSeen)
-
 	c, err := ioutil.ReadFile(pkgsDir + "/" + pkgBlob)
 	panicerr(err)
 	if got := string(c); got != pkgContent {
 		t.Errorf("getpkg: got %q, want %q", got, pkgContent)
 	}
 
-	panicerr(d.GetBlob(root1))
-
 	c, err = ioutil.ReadFile(blobsDir + "/" + root1)
 	panicerr(err)
 	if got, want := string(c), "first blob"; got != want {
 		t.Errorf("getblob: got %q, want %q", got, want)
 	}
 }
-
-/***
-In the time between PKG-414 and when the tests in this file were re-enabled,
-the following case rotted because it is no longer possible to Mount
-filesystems in this way.
-
-// Mock blobfs that always returns "no space".
-
-type blobfsFile struct {
-	pkgfs.UnsupportedFile
-}
-
-func (f blobfsFile) Truncate(size uint64) error {
-	return fs.ErrNoSpace
-}
-
-var _ = fs.File(blobfsFile{})
-
-type blobfsDirectory struct {
-	pkgfs.UnsupportedDirectory
-}
-
-func (d blobfsDirectory) Open(name string, flags fs.OpenFlags) (fs.File, fs.Directory, *fs.Remote, error) {
-	return blobfsFile{}, nil, nil, nil
-}
-
-var _ = fs.Directory(blobfsDirectory{})
-
-type blobfsFilesystem struct {
-	serveChannel zx.Channel
-}
-
-func (fs *blobfsFilesystem) Mount(path string) error {
-	var err error
-	err = os.MkdirAll(path, os.ModePerm)
-	panicerr(err)
-	parentFd, err := syscall.Open(path, syscall.O_ADMIN|syscall.O_DIRECTORY, 0777)
-	panicerr(err)
-	var rpcChan, mountChan zx.Channel
-	rpcChan, mountChan, err = zx.NewChannel(0)
-	panicerr(err)
-	remote := zxio.DirectoryInterface(fidl.InterfaceRequest{Channel: mountChan})
-	dirChan := zx.Channel(syscall.FDIOForFD(parentFd).Handles()[0])
-	dir := zxio.DirectoryAdminInterface(fidl.InterfaceRequest{Channel: dirChan})
-	panicerr(err)
-	status, err := dir.Mount(remote)
-	if zx.Status(status) != zx.ErrOk {
-		panic(fmt.Sprintf("Mount error: %s", zx.Status(status)))
-	}
-	panicerr(err)
-	fs.Serve(rpcChan)
-	return nil
-}
-
-func (fs *blobfsFilesystem) Unmount() {
-	// Just a test, let this be a no-op.
-}
-
-// Starts a Directory protocol RPC server on the given channel. Does not block.
-func (fs *blobfsFilesystem) Serve(c zx.Channel) error {
-	// rpc.NewServer takes ownership of the Handle and will close it on error.
-	vfs, err := rpc.NewServer(fs, zx.Handle(c))
-	if err != nil {
-		return fmt.Errorf("vfs server creation: %s", err)
-	}
-	fs.serveChannel = c
-
-	// TODO(raggi): serve has no quit/shutdown path.
-	for i := runtime.NumCPU(); i > 0; i-- {
-		go vfs.Serve()
-	}
-	return nil
-}
-
-func (fs *blobfsFilesystem) Blockcount() int64 {
-	return 0
-}
-
-func (fs *blobfsFilesystem) Blocksize() int64 {
-	return 0
-}
-
-func (fs *blobfsFilesystem) Size() int64 {
-	return 0
-}
-
-func (fs *blobfsFilesystem) Close() error {
-	fs.Unmount()
-	return nil
-}
-
-func (fs *blobfsFilesystem) RootDirectory() fs.Directory {
-	return blobfsDirectory{}
-}
-
-func (fs *blobfsFilesystem) Type() string {
-	return "blobfs"
-}
-
-func (fs *blobfsFilesystem) FreeSize() int64 {
-	return 0
-}
-
-func (fs *blobfsFilesystem) DevicePath() string {
-	return ""
-}
-
-var _ = fs.FileSystem(&blobfsFilesystem{})
-
-type eventsImpl struct{}
-
-var _ = amber.Events(eventsImpl{})
-
-func TestOutOfSpace(t *testing.T) {
-	var err error
-
-	// Fake blobfs that always returns fs.ErrNoSpace.
-	blobfsRootPath, err := ioutil.TempDir("", "pkgfs-test-blobfs")
-	defer os.RemoveAll(blobfsRootPath)
-	panicerr(err)
-
-	blobfsPath := filepath.Join(blobfsRootPath, "blobfs")
-	var fs blobfsFilesystem
-	fs.Mount(blobfsPath)
-	defer fs.Unmount()
-
-	// Set up pkgfs.
-
-	indexPath, err := ioutil.TempDir("", "pkgfs-test-index")
-	if err != nil {
-		panic(err)
-	}
-	panicerr(err)
-	defer os.RemoveAll(indexPath)
-
-	pkgfs, err := pkgfs.New(indexPath, blobfsPath, amberer.NewAmberClient())
-	panicerr(err)
-
-	pkgfsDir, err := ioutil.TempDir("", "pkgfs-test-mount")
-	panicerr(err)
-	defer os.RemoveAll(pkgfsDir)
-
-	go func() {
-		if err := pkgfs.Mount(pkgfsDir); err != nil {
-			panic(err)
-		}
-	}()
-	defer pkgfs.Unmount()
-
-	// Set up daemon.
-
-	store, err := ioutil.TempDir("", "amber-test")
-	panicerr(err)
-	defer os.RemoveAll(store)
-
-	installPkgDir := filepath.Join(pkgfsDir, "install/pkg")
-	installBlobDir := filepath.Join(pkgfsDir, "install/blob")
-	var evtSvc amber.EventsService
-	d, err := NewDaemon(store, installPkgDir, installBlobDir, &evtSvc)
-	panicerr(err)
-
-	// Create a fake package and blob.
-
-	// TODO(raggi): make this a real package instead, but that's a lot more setup
-	pkgContent := "very fake package"
-	pkgBlob, err := makeBlob(store, pkgContent)
-	panicerr(err)
-	root1Content := "first blob"
-	root1Length := int64(len(root1Content))
-	root1, err := makeBlob(store, root1Content)
-	panicerr(err)
-
-	// Add a source.
-
-	repoDir, err := ioutil.TempDir("", "amber-test-repo")
-	panicerr(err)
-	defer os.RemoveAll(repoDir)
-	repo := initRepo(repoDir, store, []string{pkgBlob, root1})
-	keys, err := repo.RootKeys()
-	panicerr(err)
-
-	rootKey := keys[0]
-	server := httptest.NewServer(http.FileServer(http.Dir(repoDir + "/repository")))
-
-	// XXX(raggi): cleanup disabled because networking bug!
-	// defer server.Close()
-	// // so that the httptest server can close:
-	// defer http.DefaultTransport.(*http.Transport).CloseIdleConnections()
-
-	err = d.AddSource(&amber.SourceConfig{
-		Id:          "testing",
-		RepoUrl:     server.URL,
-		BlobRepoUrl: server.URL + "/blobs",
-		// TODO(raggi): fix keyconfig
-		RootKeys: []amber.KeyConfig{
-			{
-				Type:  rootKey.Type,
-				Value: rootKey.Value.Public.String(),
-			},
-		},
-		StatusConfig: &amber.StatusConfig{Enabled: true},
-	})
-	panicerr(err)
-
-	// Fetch into daemon and check that the event was signaled.
-	req, evtIfc, err := amber.NewEventsInterfaceRequest()
-	panicerr(err)
-	_, err = evtSvc.Add(eventsImpl{}, req.ToChannel(), nil)
-	panicerr(err)
-	err = d.fetchInto(root1, root1Length, installBlobDir)
-	if e, ok := err.(zx.Error); ok && e.Status == zx.ErrNoSpace {
-		// Expected.
-	} else {
-		panic(fmt.Sprintf("fetchInto returned unexpected error %s", err))
-	}
-	err = evtIfc.ExpectOnOutOfSpace()
-	panicerr(err)
-}
-*/
diff --git a/garnet/go/src/amber/sys_update/sys_update.go b/garnet/go/src/amber/sys_update/sys_update.go
index c36b30b..755dc55 100644
--- a/garnet/go/src/amber/sys_update/sys_update.go
+++ b/garnet/go/src/amber/sys_update/sys_update.go
@@ -16,7 +16,6 @@
 	"path/filepath"
 	"regexp"
 	"strings"
-	"sync"
 	"time"
 
 	"amber/atonce"
@@ -114,14 +113,7 @@
 			return err
 		}
 
-		var w sync.WaitGroup
-		w.Add(1)
-		upMon.d.AddWatch(root, func(root string, e error) {
-			err = e
-			w.Done()
-		})
-		upMon.d.GetPkg(root, length)
-		w.Wait()
+		err = upMon.d.GetPkg(root, length)
 		if err != nil {
 			log.Printf("sys_upd_mon: unable to fetch package update: %s", err)
 			return err
diff --git a/garnet/go/src/pmd/amberer/amberer.go b/garnet/go/src/pmd/amberer/amberer.go
deleted file mode 100644
index ed4597a..0000000
--- a/garnet/go/src/pmd/amberer/amberer.go
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Amberer is a fire-and-forget auto-reconnecting proxy to the Amber Control Interface
-package amberer
-
-import (
-	"app/context"
-	"fidl/fuchsia/amber"
-	"log"
-	"sync"
-	"syscall/zx"
-)
-
-type AmberClient interface {
-	PackagesActivated(merkles []string)
-	PackagesFailed(merkles []string, status zx.Status, blob_merkle string)
-	GetBlob(merkle string)
-}
-
-type realAmberClient struct {
-	mu    sync.RWMutex
-	proxy *amber.ControlInterface
-}
-
-func (am *realAmberClient) PackagesActivated(merkles []string) {
-	go func() {
-		amber, err := am.get()
-		if err != nil {
-			log.Printf("pkgfs: amber.PackagesActivated(%v) failed: %s", merkles, err)
-			return
-		}
-
-		err = amber.PackagesActivated(merkles)
-		if am.checkErr(err) {
-			log.Printf("pkgfs: amber.PackagesActivated(%v) failed: %s", merkles, err)
-		}
-	}()
-}
-
-func (am *realAmberClient) PackagesFailed(merkles []string, status zx.Status, blobMerkle string) {
-	go func() {
-		amber, err := am.get()
-		if err != nil {
-			log.Printf("pkgfs: amber.PackagesFailed(%v, %s) failed: %s", merkles, blobMerkle, err)
-			return
-		}
-
-		err = amber.PackagesFailed(merkles, int32(status), blobMerkle)
-		if am.checkErr(err) {
-			log.Printf("pkgfs: amber.PackagesFailed(%v, %s) failed: %s", merkles, blobMerkle, err)
-		}
-	}()
-}
-
-func (am *realAmberClient) GetBlob(merkle string) {
-	amber, err := am.get()
-	if err != nil {
-		log.Printf("pkgfs: amber.GetBlob(%q) failed: %s", merkle, err)
-		return
-	}
-
-	err = amber.GetBlob(merkle)
-	if am.checkErr(err) {
-		log.Printf("pkgfs: amber.GetBlob(%q) failed: %s", merkle, err)
-	}
-}
-
-var _ AmberClient = &realAmberClient{}
-
-func NewAmberClient() AmberClient {
-	return &realAmberClient{}
-}
-
-func (am *realAmberClient) get() (*amber.ControlInterface, error) {
-	am.mu.RLock()
-	var a = am.proxy
-	am.mu.RUnlock()
-
-	if a != nil {
-		return a, nil
-	}
-
-	am.mu.Lock()
-	defer am.mu.Unlock()
-
-	req, pxy, err := amber.NewControlInterfaceRequest()
-	if err != nil {
-		return nil, err
-	}
-	context.CreateFromStartupInfo().ConnectToEnvService(req)
-	am.proxy = pxy
-	return pxy, nil
-}
-
-func (am *realAmberClient) checkErr(err error) bool {
-	if err != nil {
-		am.mu.Lock()
-		if am.proxy != nil {
-			am.proxy.Close()
-		}
-		am.proxy = nil
-		am.mu.Unlock()
-		return true
-	}
-	return false
-}
diff --git a/garnet/go/src/pmd/index/dynamic_index.go b/garnet/go/src/pmd/index/dynamic_index.go
index 4b977be..4fbde1c 100644
--- a/garnet/go/src/pmd/index/dynamic_index.go
+++ b/garnet/go/src/pmd/index/dynamic_index.go
@@ -16,7 +16,6 @@
 	"syscall/zx"
 
 	"fuchsia.googlesource.com/pm/pkg"
-	"fuchsia.googlesource.com/pmd/amberer"
 )
 
 // DynamicIndex provides concurrency safe access to a dynamic index of packages and package metadata
@@ -25,9 +24,6 @@
 
 	static *StaticIndex
 
-	// client to connect to amber
-	amberClient amberer.AmberClient
-
 	// mu protects all following fields
 	mu sync.Mutex
 
@@ -45,17 +41,16 @@
 }
 
 // NewDynamic initializes an DynamicIndex with the given root path.
-func NewDynamic(root string, static *StaticIndex, am amberer.AmberClient) *DynamicIndex {
+func NewDynamic(root string, static *StaticIndex) *DynamicIndex {
 	// TODO(PKG-14): error is deliberately ignored. This should not be fatal to boot.
 	_ = os.MkdirAll(root, os.ModePerm)
 	return &DynamicIndex{
-		root:        root,
-		static:      static,
-		roots:       make(map[string]pkg.Package),
-		installing:  make(map[string]pkg.Package),
-		needs:       make(map[string]map[string]struct{}),
-		waiting:     make(map[string]map[string]struct{}),
-		amberClient: am,
+		root:       root,
+		static:     static,
+		roots:      make(map[string]pkg.Package),
+		installing: make(map[string]pkg.Package),
+		needs:      make(map[string]map[string]struct{}),
+		waiting:    make(map[string]map[string]struct{}),
 	}
 }
 
@@ -160,8 +155,6 @@
 	for p := range ps {
 		pkgRoots = append(pkgRoots, p)
 	}
-
-	idx.amberClient.PackagesFailed(pkgRoots, status, blobRoot)
 }
 
 // InstallingFailedForPackage removes an entry from the package installation index,
@@ -198,19 +191,6 @@
 	// We wait on all of the "needs", that is, all blobs that were not found on the
 	// system at the time of import.
 	idx.waiting[root] = needs
-
-	// create a copy of the needs so we're concurrency safe
-	cn := make([]string, 0, len(needs))
-	for root := range needs {
-		cn = append(cn, root)
-	}
-
-	log.Printf("asking amber to fetch %d needed blobs", len(cn))
-	go func() {
-		for _, root := range cn {
-			idx.amberClient.GetBlob(root)
-		}
-	}()
 	return nil
 }
 
@@ -317,11 +297,6 @@
 }
 
 func (idx *DynamicIndex) Notify(roots ...string) {
-	if len(roots) == 0 {
-		return
-	}
-
-	idx.amberClient.PackagesActivated(roots)
 }
 
 // GetRoot looks for a package by merkleroot, returning the matching package and
diff --git a/garnet/go/src/pmd/index/dynamic_index_test.go b/garnet/go/src/pmd/index/dynamic_index_test.go
index 099aeda..445cec6 100644
--- a/garnet/go/src/pmd/index/dynamic_index_test.go
+++ b/garnet/go/src/pmd/index/dynamic_index_test.go
@@ -15,41 +15,8 @@
 	"testing"
 
 	"fuchsia.googlesource.com/pm/pkg"
-	"fuchsia.googlesource.com/pmd/amberer"
 )
 
-type packageFailure struct {
-	merkles    []string
-	status     zx.Status
-	blobMerkle string
-}
-
-type fakeAmberClient struct {
-	activated []string
-	failed    []packageFailure
-}
-
-func (am *fakeAmberClient) PackagesActivated(merkles []string) {
-	for _, m := range merkles {
-		am.activated = append(am.activated, m)
-	}
-}
-
-func (am *fakeAmberClient) PackagesFailed(merkles []string, status zx.Status, blobMerkle string) {
-	am.failed = append(am.failed, packageFailure{merkles, status, blobMerkle})
-}
-
-func (am *fakeAmberClient) GetBlob(merkle string) {}
-
-func newFakeAmberClient() *fakeAmberClient {
-	return &fakeAmberClient{
-		activated: make([]string, 0),
-		failed:    make([]packageFailure, 0),
-	}
-}
-
-var _ amberer.AmberClient = &fakeAmberClient{}
-
 func TestList(t *testing.T) {
 	d, err := ioutil.TempDir("", t.Name())
 	if err != nil {
@@ -66,7 +33,7 @@
 		t.Fatal(err)
 	}
 
-	idx := NewDynamic(d, NewStatic(), newFakeAmberClient())
+	idx := NewDynamic(d, NewStatic())
 	pkgs, err := idx.List()
 	if err != nil {
 		t.Fatal(err)
@@ -122,7 +89,7 @@
 		t.Fatal(err)
 	}
 
-	idx := NewDynamic(d, NewStatic(), newFakeAmberClient())
+	idx := NewDynamic(d, NewStatic())
 
 	err = idx.Add(pkg.Package{Name: "foo", Version: "0"}, "abc")
 	if err != nil {
@@ -165,8 +132,7 @@
 		t.Fatal(err)
 	}
 
-	am := newFakeAmberClient()
-	idx := NewDynamic(d, NewStatic(), am)
+	idx := NewDynamic(d, NewStatic())
 
 	// New package, no blobs pre-installed.
 	{
@@ -214,11 +180,6 @@
 			t.Errorf("got %q, want %q", idx.needs, wantNeeds)
 		}
 
-		wantActivated := []string{}
-		if !reflect.DeepEqual(am.activated, wantActivated) {
-			t.Errorf("got %q, want %q", am.activated, wantActivated)
-		}
-
 		// Fulfill other blob. Package is done and appears in index.
 		idx.Fulfill("blob2")
 		if _, ok := idx.waiting["root1"]; ok {
@@ -232,10 +193,6 @@
 			t.Errorf("root1 was not deleted from installing")
 		}
 
-		wantActivated = []string{"root1"}
-		if !reflect.DeepEqual(am.activated, wantActivated) {
-			t.Errorf("got %q, want %q", am.activated, wantActivated)
-		}
 		paths, err := filepath.Glob(filepath.Join(d, "packages/*/*"))
 		if err != nil {
 			t.Fatal(err)
@@ -280,17 +237,6 @@
 		// Fail blob with error. Causes package failure to be signaled.
 		idx.InstallingFailedForBlob("blob4", zx.ErrNoSpace)
 
-		wantFailed := []packageFailure{
-			{
-				merkles:    []string{"root2"},
-				status:     zx.ErrNoSpace,
-				blobMerkle: "blob4",
-			},
-		}
-		if !reflect.DeepEqual(am.failed, wantFailed) {
-			t.Errorf("got %v, want %v", am.failed, wantFailed)
-		}
-
 		// Fulfill blob. Now the package should be marked finished.
 		idx.Fulfill("blob4")
 
@@ -305,10 +251,6 @@
 			t.Errorf("root2 was not deleted from installing")
 		}
 
-		wantActivated := []string{"root1", "root2"}
-		if !reflect.DeepEqual(am.activated, wantActivated) {
-			t.Errorf("got %q, want %q", am.activated, wantActivated)
-		}
 		paths, err := filepath.Glob(filepath.Join(d, "packages/*/*"))
 		if err != nil {
 			t.Fatal(err)
diff --git a/garnet/go/src/pmd/pkgfs/needs_directory.go b/garnet/go/src/pmd/pkgfs/needs_directory.go
index 6ca8fc98..f47e3c7 100644
--- a/garnet/go/src/pmd/pkgfs/needs_directory.go
+++ b/garnet/go/src/pmd/pkgfs/needs_directory.go
@@ -60,7 +60,7 @@
 }
 
 func (d *needsRoot) Read() ([]fs.Dirent, error) {
-	return []fs.Dirent{dirDirEnt("blobs")}, nil
+	return []fs.Dirent{dirDirEnt("blobs"), dirDirEnt("packages")}, nil
 }
 
 func (d *needsRoot) Stat() (int64, time.Time, time.Time, error) {
diff --git a/garnet/go/src/pmd/pkgfs/pkgfs.go b/garnet/go/src/pmd/pkgfs/pkgfs.go
index 68c1ecd2..eb7b983 100644
--- a/garnet/go/src/pmd/pkgfs/pkgfs.go
+++ b/garnet/go/src/pmd/pkgfs/pkgfs.go
@@ -27,7 +27,6 @@
 	"thinfs/zircon/rpc"
 
 	"fuchsia.googlesource.com/pm/pkg"
-	"fuchsia.googlesource.com/pmd/amberer"
 	"fuchsia.googlesource.com/pmd/blobfs"
 	"fuchsia.googlesource.com/pmd/index"
 )
@@ -43,7 +42,7 @@
 }
 
 // New initializes a new pkgfs filesystem server
-func New(indexDir string, blobDir string, am amberer.AmberClient) (*Filesystem, error) {
+func New(indexDir string, blobDir string) (*Filesystem, error) {
 	bm, err := blobfs.New(blobDir)
 	if err != nil {
 		return nil, fmt.Errorf("pkgfs: open blobfs: %s", err)
@@ -52,7 +51,7 @@
 	static := index.NewStatic()
 	f := &Filesystem{
 		static: static,
-		index:  index.NewDynamic(indexDir, static, am),
+		index:  index.NewDynamic(indexDir, static),
 		blobfs: bm,
 		mountInfo: mountInfo{
 			parentFd: -1,
diff --git a/garnet/go/src/pmd/pkgfs/pkgfs_test.go b/garnet/go/src/pmd/pkgfs/pkgfs_test.go
index 3637e13..a730c00 100644
--- a/garnet/go/src/pmd/pkgfs/pkgfs_test.go
+++ b/garnet/go/src/pmd/pkgfs/pkgfs_test.go
@@ -25,7 +25,6 @@
 
 	"fuchsia.googlesource.com/merkle"
 	"fuchsia.googlesource.com/pm/build"
-	"fuchsia.googlesource.com/pmd/amberer"
 )
 
 // Adding a file to /in writes the file to blobfs
@@ -123,7 +122,7 @@
 	}
 	defer os.RemoveAll(d)
 
-	pkgfs, err := New(indexPath, blobfsPath, amberer.NewAmberClient())
+	pkgfs, err := New(indexPath, blobfsPath)
 	if err != nil {
 		panic(err)
 	}
diff --git a/garnet/go/src/pmd/pkgsvr/pkgsvr.go b/garnet/go/src/pmd/pkgsvr/pkgsvr.go
index d95bb77..281b03e 100644
--- a/garnet/go/src/pmd/pkgsvr/pkgsvr.go
+++ b/garnet/go/src/pmd/pkgsvr/pkgsvr.go
@@ -11,7 +11,6 @@
 
 	"app/context"
 
-	"fuchsia.googlesource.com/pmd/amberer"
 	"fuchsia.googlesource.com/pmd/pkgfs"
 )
 
@@ -29,7 +28,7 @@
 	sysPkg := flag.Arg(0)
 
 	// TODO(raggi): Reading from the index should be delayed until after verified boot completion
-	fs, err := pkgfs.New(*index, *blob, amberer.NewAmberClient())
+	fs, err := pkgfs.New(*index, *blob)
 	if err != nil {
 		log.Fatalf("pkgfs: initialization failed: %s", err)
 	}