syncbase: Allow Syncbase root dir to be moved

When Syncbase creates a new service instance, it stores the location
of the new LevelDB as an absolute path (DbInfo.RootDir). If Syncbase's
root directory is ever moved, it won't be able to boot regardless of
the updated rootDir parameter passed at init (which was ignored). This
CL changes this behavior to store relative paths in DbInfo, while
allowing existing absolute paths to continue to work. This CL does not
correct the older values, which means this CL only fixes the behavior
for new Syncbase instances.

MultiPart: 2/2
Change-Id: I42721ce61b8186cb95849e2d63752086aca50271
diff --git a/services/syncbase/server/database.go b/services/syncbase/server/database.go
index eb7de69..bf0b800 100644
--- a/services/syncbase/server/database.go
+++ b/services/syncbase/server/database.go
@@ -6,7 +6,7 @@
 
 import (
 	"math/rand"
-	"path"
+	"path/filepath"
 	"sync"
 	"time"
 
@@ -70,7 +70,7 @@
 type DatabaseOptions struct {
 	// Database-level permissions.
 	Perms access.Permissions
-	// Root dir for data storage.
+	// Root dir for data storage. This path is relative from the service's RootDir.
 	RootDir string
 	// Storage engine to use.
 	Engine string
@@ -174,7 +174,10 @@
 // openDatabase opens a database and returns a *database for it. Designed for
 // use from within newDatabase and newService.
 func openDatabase(ctx *context.T, s *service, id wire.Id, opts DatabaseOptions, openOpts storeutil.OpenOptions) (*database, error) {
-	st, err := storeutil.OpenStore(opts.Engine, path.Join(opts.RootDir, opts.Engine), openOpts)
+	// DatabaseOption's RootDir is relative to the service's RootDir (but for backwards compatibility
+	// s.absRootDir will return any absolute paths as-is).
+	p := s.absRootDir(filepath.Join(opts.RootDir, opts.Engine))
+	st, err := storeutil.OpenStore(opts.Engine, p, openOpts)
 	if err != nil {
 		return nil, err
 	}
diff --git a/services/syncbase/server/db_gc.go b/services/syncbase/server/db_gc.go
index ff194a2..b0829fb 100644
--- a/services/syncbase/server/db_gc.go
+++ b/services/syncbase/server/db_gc.go
@@ -59,8 +59,9 @@
 // finalizeDatabaseDestroy attempts to close (if stRef is not nil) and destroy
 // the database store. If successful, it removes the dbInfo record from the
 // garbage collection log.
-func finalizeDatabaseDestroy(ctx *context.T, stw store.StoreWriter, dbInfo *DbInfo, stRef store.Store) error {
-	vlog.VI(2).Infof("server: destroying store at %q for database %v (closing: %v)", dbInfo.RootDir, dbInfo.Id, stRef != nil)
+func finalizeDatabaseDestroy(ctx *context.T, s *service, dbInfo *DbInfo, stRef store.Store) error {
+	rootDir := s.absRootDir(dbInfo.RootDir)
+	vlog.VI(2).Infof("server: destroying store at %q for database %v (closing: %v)", rootDir, dbInfo.Id, stRef != nil)
 	if stRef != nil {
 		// TODO(ivanpi): Safer to crash syncbased on Close() failure? Otherwise,
 		// already running calls might continue using the database. Alternatively,
@@ -69,10 +70,10 @@
 			return wrapGCError(dbInfo, err)
 		}
 	}
-	if err := storeutil.DestroyStore(dbInfo.Engine, dbInfo.RootDir); err != nil {
+	if err := storeutil.DestroyStore(dbInfo.Engine, rootDir); err != nil {
 		return wrapGCError(dbInfo, err)
 	}
-	if err := delDbGCEntry(ctx, stw, dbInfo); err != nil {
+	if err := delDbGCEntry(ctx, s.st, dbInfo); err != nil {
 		return wrapGCError(dbInfo, err)
 	}
 	return nil
@@ -85,11 +86,11 @@
 // creation or deletion since it can cause spurious failures (e.g. garbage
 // collecting a database that is being created).
 // TODO(ivanpi): Consider adding mutex to allow running GC concurrently.
-func runGCInactiveDatabases(ctx *context.T, st store.Store) error {
+func runGCInactiveDatabases(ctx *context.T, s *service) error {
 	vlog.VI(2).Infof("server: starting garbage collection of inactive databases")
 	total := 0
 	deleted := 0
-	gcIt := st.Scan(common.ScanPrefixArgs(common.DbGCPrefix, ""))
+	gcIt := s.st.Scan(common.ScanPrefixArgs(common.DbGCPrefix, ""))
 	var diBytes []byte
 	for gcIt.Advance() {
 		diBytes = gcIt.Value(diBytes)
@@ -98,7 +99,7 @@
 			verror.New(verror.ErrInternal, ctx, err)
 		}
 		total++
-		if err := finalizeDatabaseDestroy(ctx, st, &dbInfo, nil); err != nil {
+		if err := finalizeDatabaseDestroy(ctx, s, &dbInfo, nil); err != nil {
 			vlog.Error(err)
 		} else {
 			deleted++
diff --git a/services/syncbase/server/service.go b/services/syncbase/server/service.go
index 2349561..c51d939 100644
--- a/services/syncbase/server/service.go
+++ b/services/syncbase/server/service.go
@@ -165,7 +165,7 @@
 		// Run garbage collection of inactive databases.
 		// TODO(ivanpi): This is currently unsafe to call concurrently with
 		// database creation/deletion. Add mutex and run asynchronously.
-		if err := runGCInactiveDatabases(ctx, st); err != nil {
+		if err := runGCInactiveDatabases(ctx, s); err != nil {
 			return nil, err
 		}
 		// Initialize in-memory data structures, namely the dbs map.
@@ -480,7 +480,7 @@
 			// on syncbased restart. (It is safe to pass nil stRef if step 3 fails.)
 			// TODO(ivanpi): Consider running asynchronously. However, see TODO in
 			// finalizeDatabaseDestroy.
-			if err := finalizeDatabaseDestroy(ctx, s.st, dbInfo, stRef); err != nil {
+			if err := finalizeDatabaseDestroy(ctx, s, dbInfo, stRef); err != nil {
 				ctx.Error(err)
 			}
 		}
@@ -574,7 +574,7 @@
 	// might still be using the store.
 	// TODO(ivanpi): Consider running asynchronously. However, see TODO in
 	// finalizeDatabaseDestroy.
-	if err := finalizeDatabaseDestroy(ctx, s.st, dbInfo, d.St()); err != nil {
+	if err := finalizeDatabaseDestroy(ctx, s, dbInfo, d.St()); err != nil {
 		ctx.Error(err)
 	}
 
@@ -641,5 +641,16 @@
 	if len(appDir) > 255 || len(dbDir) > 255 {
 		ctx.Fatalf("appDir %s or dbDir %s is too long", appDir, dbDir)
 	}
-	return filepath.Join(s.opts.RootDir, common.AppDir, appDir, common.DbDir, dbDir), nil
+	return filepath.Join(common.AppDir, appDir, common.DbDir, dbDir), nil
+}
+
+// absRootDir returns rootDir if it is absolute, or the joined path with this service's
+// RootDir if it is relative. This allows DbInfo to store relative database paths but
+// pre-existing absolute paths (before CL 23453) will still open as is (achieving
+// backwards compatibility).
+func (s *service) absRootDir(rootDir string) string {
+	if !filepath.IsAbs(rootDir) {
+		rootDir = filepath.Join(s.opts.RootDir, rootDir)
+	}
+	return rootDir
 }