From e607198dfc931cb72c2c77e624a0badcc487f0e9 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 7 May 2018 19:58:36 -0700 Subject: [PATCH] osutil: fix Windows use old Camlistore-named APPDATA directory Fixes #1150 Change-Id: Ia2843437abbaba96e936c1c0f46bf8b171a2e28e (cherry picked from commit 245bb63fff36ef0aacfec4c972b781a69adedf32) --- internal/osutil/paths.go | 103 +++++++++++++++++++++++++--------- internal/osutil/paths_test.go | 74 ++++++++++++++++++++++++ pkg/serverinit/genconfig.go | 14 ++++- server/perkeepd/run_test.go | 14 +++-- 4 files changed, 169 insertions(+), 36 deletions(-) diff --git a/internal/osutil/paths.go b/internal/osutil/paths.go index b2c4d9147..8ec3d2866 100644 --- a/internal/osutil/paths.go +++ b/internal/osutil/paths.go @@ -90,22 +90,56 @@ func makeCacheDir() { } } -func CamliVarDir() string { +func upperFirst(s string) string { + return strings.ToUpper(s[:1]) + s[1:] +} + +func CamliVarDir() (string, error) { + oldName := camliVarDirOf("camlistore") + newName := camliVarDirOf("perkeep") + + if fi, err := os.Lstat(oldName); err == nil && fi.IsDir() && oldName != newName { + n := numRegularFilesUnder(oldName) + if n == 0 { + log.Printf("removing old, empty var directory %s", oldName) + os.RemoveAll(oldName) + } else { + return "", fmt.Errorf("Now that Perkeep has been renamed from Camlistore, you need to rename your data directory from %s to %s", oldName, newName) + } + } + return newName, nil +} + +func numRegularFilesUnder(dir string) (n int) { + filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { + if fi != nil && fi.Mode().IsRegular() { + n++ + } + return nil + }) + return +} + +func camliVarDirOf(name string) string { if d := os.Getenv("CAMLI_VAR_DIR"); d != "" { return d } failInTests() switch runtime.GOOS { case "windows": - return filepath.Join(os.Getenv("APPDATA"), "Camlistore") + return filepath.Join(os.Getenv("APPDATA"), upperFirst(name)) case "darwin": - return filepath.Join(HomeDir(), "Library", "Camlistore") + return filepath.Join(HomeDir(), "Library", upperFirst(name)) } - return filepath.Join(HomeDir(), "var", "camlistore") + return filepath.Join(HomeDir(), "var", name) } -func CamliBlobRoot() string { - return filepath.Join(CamliVarDir(), "blobs") +func CamliBlobRoot() (string, error) { + varDir, err := CamliVarDir() + if err != nil { + return "", err + } + return filepath.Join(varDir, "blobs"), nil } // RegisterConfigDirFunc registers a func f to return the Perkeep configuration directory. @@ -127,36 +161,41 @@ func CamliConfigDir() string { } failInTests() - return camliConfigDir() -} - -func camliConfigDir() string { - if fi, err := os.Lstat(oldCamliConfigDir()); err == nil && fi.IsDir() { - fmt.Fprintf(os.Stderr, "Error: old configuration directory detected. Not running until it's moved.\nRename %s to %s\n", - oldCamliConfigDir(), perkeepConfigDir()) - os.Exit(1) + dir, err := perkeepConfigDir() + if err != nil { + log.Fatalf("PerkeepConfigDir: %v", err) } - return perkeepConfigDir() + return dir } -func perkeepConfigDir() string { - if runtime.GOOS == "windows" { - return filepath.Join(os.Getenv("APPDATA"), "Perkeep") - } - if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" { - return filepath.Join(xdg, "perkeep") +func perkeepConfigDir() (string, error) { + oldName := configDirNamed("camlistore") + newName := configDirNamed("perkeep") + if fi, err := os.Lstat(oldName); err == nil && fi.IsDir() && oldName != newName { + n := numRegularFilesUnder(oldName) + if n == 0 { + log.Printf("removing old, empty config dir %s", oldName) + os.RemoveAll(oldName) + } else { + return "", fmt.Errorf("Error: old configuration directory detected. Not running until it's moved.\nRename %s to %s\n", oldName, newName) + } } - return filepath.Join(HomeDir(), ".config", "perkeep") + return newName, nil } -func oldCamliConfigDir() string { +var configDirNamedTestHook func(string) string + +func configDirNamed(name string) string { + if h := configDirNamedTestHook; h != nil { + return h(name) + } if runtime.GOOS == "windows" { - return filepath.Join(os.Getenv("APPDATA"), "Camlistore") + return filepath.Join(os.Getenv("APPDATA"), upperFirst(name)) } if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" { - return filepath.Join(xdg, "camlistore") + return filepath.Join(xdg, name) } - return filepath.Join(HomeDir(), ".config", "camlistore") + return filepath.Join(HomeDir(), ".config", name) } func UserServerConfigPath() string { @@ -207,13 +246,21 @@ func ExplicitSecretRingFile() (string, bool) { // DefaultSecretRingFile returns the path to the default GPG secret // keyring. It is not influenced by any flag or CAMLI* env var. func DefaultSecretRingFile() string { - return filepath.Join(camliConfigDir(), "identity-secring.gpg") + dir, err := perkeepConfigDir() + if err != nil { + log.Fatalf("couldn't compute DefaultSecretRingFile: %v", err) + } + return filepath.Join(dir, "identity-secring.gpg") } // identitySecretRing returns the path to the default GPG // secret keyring. It is still affected by CAMLI_CONFIG_DIR. func identitySecretRing() string { - return filepath.Join(CamliConfigDir(), "identity-secring.gpg") + dir, err := perkeepConfigDir() + if err != nil { + log.Fatalf("couldn't compute DefaultSecretRingFile: %v", err) + } + return filepath.Join(dir, "identity-secring.gpg") } // SecretRingFile returns the path to the user's GPG secret ring file. diff --git a/internal/osutil/paths_test.go b/internal/osutil/paths_test.go index 11965dd84..786b2868a 100644 --- a/internal/osutil/paths_test.go +++ b/internal/osutil/paths_test.go @@ -19,8 +19,10 @@ package osutil import ( "fmt" "io/ioutil" + "log" "os" "path/filepath" + "strings" "testing" ) @@ -140,3 +142,75 @@ func TestOpenCamliIncludePath(t *testing.T) { os.Setenv("CAMLI_INCLUDE_PATH", "/not/a/camli/config/dir"+sep+td+sep+"/another/fake/camli/dir") checkOpen(t, name) } + +func TestCamPkConfigMigration(t *testing.T) { + oldFuncs := configDirFuncs + defer func() { + configDirFuncs = oldFuncs + configDirNamedTestHook = nil + log.SetOutput(os.Stderr) + }() + log.SetOutput(ioutil.Discard) + + td, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(td) + + configDirNamedTestHook = func(name string) string { + return filepath.Join(td, name) + } + + oldDir := filepath.Join(td, "camlistore") + newDir := filepath.Join(td, "perkeep") + + if err := os.MkdirAll(filepath.Join(oldDir, "blobs", "foo", "sub"), 0755); err != nil { + t.Fatal(err) + } + + calls := 0 + RegisterConfigDirFunc(func() string { + calls++ + log.Printf("call %d", calls) + switch calls { + case 1: + return oldDir + case 2: + return newDir + } + t.Fatalf("unexpected %d calls to get config dir", calls) + return "" + }) + + got, err := perkeepConfigDir() + if err != nil { + t.Fatal(err) + } + if got != newDir { + t.Errorf("first call = %v; want %v", got, newDir) + } + + if fi, err := os.Lstat(oldDir); !os.IsNotExist(err) { + t.Errorf("Lstat = %v, %v; want IsNotExist error", fi, err) + } + + // Now try with some regular file in the old dir. + if err := os.MkdirAll(filepath.Join(oldDir, "blobs"), 0755); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(filepath.Join(oldDir, "blobs/x.dat"), []byte("hi"), 0644); err != nil { + t.Fatal(err) + } + + _, err = perkeepConfigDir() + if err == nil { + t.Error("unexpected success looking up config dir after the old one had a file in it") + } else if !strings.Contains(err.Error(), "old configuration directory detected") { + t.Errorf("expected migration error; got: %v", err) + } + + if fi, err := os.Lstat(oldDir); err != nil || !fi.IsDir() { + t.Errorf("error looking up old directory; want valid directory. Got: %v, %v", fi, err) + } +} diff --git a/pkg/serverinit/genconfig.go b/pkg/serverinit/genconfig.go index 6e73743c4..871acdc37 100644 --- a/pkg/serverinit/genconfig.go +++ b/pkg/serverinit/genconfig.go @@ -1240,16 +1240,24 @@ var defaultBaseConfig = serverconfig.Config{ // leveldb. If filePath already exists, it is overwritten. func WriteDefaultConfigFile(filePath string, useSQLite bool) error { conf := defaultBaseConfig - blobDir := osutil.CamliBlobRoot() + blobDir, err := osutil.CamliBlobRoot() + if err != nil { + return err + } + varDir, err := osutil.CamliVarDir() + if err != nil { + return err + } if err := wkfs.MkdirAll(blobDir, 0700); err != nil { return fmt.Errorf("Could not create default blobs directory: %v", err) } conf.BlobPath = blobDir conf.PackRelated = true + if useSQLite { - conf.SQLite = filepath.Join(osutil.CamliVarDir(), "index.sqlite") + conf.SQLite = filepath.Join(varDir, "index.sqlite") } else { - conf.LevelDB = filepath.Join(osutil.CamliVarDir(), "index.leveldb") + conf.LevelDB = filepath.Join(varDir, "index.leveldb") } keyID, secretRing, err := getOrMakeKeyring() diff --git a/server/perkeepd/run_test.go b/server/perkeepd/run_test.go index 60525dfd6..b56d6c8c9 100644 --- a/server/perkeepd/run_test.go +++ b/server/perkeepd/run_test.go @@ -45,11 +45,15 @@ func TestStarts(t *testing.T) { if _, err := os.Stat(osutil.CamliConfigDir()); !os.IsNotExist(err) { t.Fatalf("expected conf dir %q to not exist", osutil.CamliConfigDir()) } - if !strings.Contains(osutil.CamliBlobRoot(), td) { - t.Fatalf("blob root %q should contain the temp dir %q", osutil.CamliBlobRoot(), td) + blobRoot, err := osutil.CamliBlobRoot() + if err != nil { + t.Fatal(err) + } + if !strings.Contains(blobRoot, td) { + t.Fatalf("blob root %q should contain the temp dir %q", blobRoot, td) } - if _, err := os.Stat(osutil.CamliBlobRoot()); !os.IsNotExist(err) { - t.Fatalf("expected blobroot dir %q to not exist", osutil.CamliBlobRoot()) + if _, err := os.Stat(blobRoot); !os.IsNotExist(err) { + t.Fatalf("expected blobroot dir %q to not exist", blobRoot) } if fi, err := os.Stat(osutil.UserServerConfigPath()); !os.IsNotExist(err) { t.Errorf("expected no server config file; got %v, %v", fi, err) @@ -57,7 +61,7 @@ func TestStarts(t *testing.T) { mkdir(t, confDir) *flagOpenBrowser = false - *flagListen = ":0" + *flagListen = "localhost:0" up := make(chan struct{}) down := make(chan struct{})