internal/telemetry: encapsulate the telemetry directory

In several places throughout tests, we set all of telemetry.{LocalDir,
UploadDir, Mode} according to the standard schema ("local/", "upload/",
and "mode").

In the interest of reducing or eliminating global state to improve
testability, introduce a telemetry.Dir type which encapsulates the
telemetry directory layout. The default layout is accessed through
telemetry.Default. This makes it easier for future components to close
over or pass around a single piece of state (the Dir). In subsequent
CLs, this will be used to make uploading more testable.

For golang/go#66003

Change-Id: I31db8df20f133d834219ff17c2fe2d4f9e446b5d
Reviewed-on: https://go-review.googlesource.com/c/telemetry/+/575059
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Peter Weinberger <pjw@google.com>
diff --git a/cmd/gotelemetry/internal/csv/csv.go b/cmd/gotelemetry/internal/csv/csv.go
index b20357c..bba140b 100644
--- a/cmd/gotelemetry/internal/csv/csv.go
+++ b/cmd/gotelemetry/internal/csv/csv.go
@@ -32,7 +32,7 @@
 }
 
 func Csv() {
-	files, err := readdir(telemetry.LocalDir, nil)
+	files, err := readdir(telemetry.Default.LocalDir(), nil)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/cmd/gotelemetry/internal/view/view.go b/cmd/gotelemetry/internal/view/view.go
index 25232d7..b781b3e 100644
--- a/cmd/gotelemetry/internal/view/view.go
+++ b/cmd/gotelemetry/internal/view/view.go
@@ -114,16 +114,17 @@
 		if err != nil {
 			return err
 		}
-		if _, err := os.Stat(telemetry.LocalDir); err != nil {
+		localDir := telemetry.Default.LocalDir()
+		if _, err := os.Stat(localDir); err != nil {
 			return fmt.Errorf(
 				`The telemetry dir %s does not exist.
-There is nothing to report.`, telemetry.LocalDir)
+There is nothing to report.`, telemetry.Default.LocalDir())
 		}
-		reports, err := reports(telemetry.LocalDir, cfg)
+		reports, err := reports(localDir, cfg)
 		if err != nil {
 			return err
 		}
-		files, err := files(telemetry.LocalDir, cfg)
+		files, err := files(localDir, cfg)
 		if err != nil {
 			return err
 		}
diff --git a/cmd/gotelemetry/main.go b/cmd/gotelemetry/main.go
index 6538d97..c86213f 100644
--- a/cmd/gotelemetry/main.go
+++ b/cmd/gotelemetry/main.go
@@ -213,10 +213,10 @@
 }
 
 func runOn(_ []string) {
-	if old, _ := telemetry.Mode(); old == "on" {
+	if old, _ := telemetry.Default.Mode(); old == "on" {
 		return
 	}
-	if err := telemetry.SetMode("on"); err != nil {
+	if err := telemetry.Default.SetMode("on"); err != nil {
 		failf("Failed to enable telemetry: %v", err)
 	}
 	// We could perhaps only show the telemetry on message when the mode goes
@@ -236,19 +236,19 @@
 }
 
 func runLocal(_ []string) {
-	if old, _ := telemetry.Mode(); old == "local" {
+	if old, _ := telemetry.Default.Mode(); old == "local" {
 		return
 	}
-	if err := telemetry.SetMode("local"); err != nil {
+	if err := telemetry.Default.SetMode("local"); err != nil {
 		failf("Failed to set the telemetry mode to local: %v", err)
 	}
 }
 
 func runOff(_ []string) {
-	if old, _ := telemetry.Mode(); old == "off" {
+	if old, _ := telemetry.Default.Mode(); old == "off" {
 		return
 	}
-	if err := telemetry.SetMode("off"); err != nil {
+	if err := telemetry.Default.SetMode("off"); err != nil {
 		failf("Failed to disable telemetry: %v", err)
 	}
 }
@@ -258,12 +258,12 @@
 }
 
 func runEnv(_ []string) {
-	m, t := telemetry.Mode()
+	m, t := telemetry.Default.Mode()
 	fmt.Printf("mode: %s %s\n", m, t)
 	fmt.Println()
-	fmt.Println("modefile:", telemetry.ModeFile)
-	fmt.Println("localdir:", telemetry.LocalDir)
-	fmt.Println("uploaddir:", telemetry.UploadDir)
+	fmt.Println("modefile:", telemetry.Default.ModeFile())
+	fmt.Println("localdir:", telemetry.Default.LocalDir())
+	fmt.Println("uploaddir:", telemetry.Default.UploadDir())
 }
 
 func runClean(_ []string) {
@@ -271,8 +271,8 @@
 	// It would probably be OK to just remove everything, but it may
 	// be useful to preserve the weekends file.
 	for dir, suffixes := range map[string][]string{
-		telemetry.LocalDir:  {"." + counter.FileVersion + ".count", ".json"},
-		telemetry.UploadDir: {".json"},
+		telemetry.Default.LocalDir():  {"." + counter.FileVersion + ".count", ".json"},
+		telemetry.Default.UploadDir(): {".json"},
 	} {
 		entries, err := os.ReadDir(dir)
 		if err != nil {
@@ -307,7 +307,7 @@
 
 func runDump(args []string) {
 	if len(args) == 0 {
-		localdir := telemetry.LocalDir
+		localdir := telemetry.Default.LocalDir()
 		fi, err := os.ReadDir(localdir)
 		if err != nil && len(args) == 0 {
 			log.Fatal(err)
diff --git a/counter/countertest/countertest.go b/counter/countertest/countertest.go
index c2f41f6..dc8bb11 100644
--- a/counter/countertest/countertest.go
+++ b/counter/countertest/countertest.go
@@ -7,7 +7,6 @@
 package countertest
 
 import (
-	"path/filepath"
 	"sync"
 
 	"golang.org/x/telemetry/counter"
@@ -39,9 +38,7 @@
 	if opened {
 		panic("Open was called more than once")
 	}
-	telemetry.ModeFile = telemetry.ModeFilePath(filepath.Join(telemetryDir, "mode"))
-	telemetry.LocalDir = filepath.Join(telemetryDir, "local")
-	telemetry.UploadDir = filepath.Join(telemetryDir, "upload")
+	telemetry.Default = telemetry.NewDir(telemetryDir)
 
 	counter.Open()
 	opened = true
diff --git a/internal/counter/counter_test.go b/internal/counter/counter_test.go
index ba5dfab..e3d6ff5 100644
--- a/internal/counter/counter_test.go
+++ b/internal/counter/counter_test.go
@@ -70,7 +70,7 @@
 
 func TestMissingLocalDir(t *testing.T) {
 	testenv.SkipIfUnsupportedPlatform(t)
-	err := os.RemoveAll(telemetry.LocalDir)
+	err := os.RemoveAll(telemetry.Default.LocalDir())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -236,7 +236,7 @@
 		var f file
 		c := f.New("gophers")
 		// shouldn't see a file yet
-		fi, err := os.ReadDir(telemetry.LocalDir)
+		fi, err := os.ReadDir(telemetry.Default.LocalDir())
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -245,7 +245,7 @@
 		}
 		c.Add(9)
 		// still shouldn't see a file
-		fi, err = os.ReadDir(telemetry.LocalDir)
+		fi, err = os.ReadDir(telemetry.Default.LocalDir())
 		if err != nil {
 			close(&f)
 			t.Fatal(err)
@@ -256,7 +256,7 @@
 		}
 		f.rotate()
 		// now we should see a count file and a weekends file
-		fi, _ = os.ReadDir(telemetry.LocalDir)
+		fi, _ = os.ReadDir(telemetry.Default.LocalDir())
 		if len(fi) != 2 {
 			close(&f)
 			t.Fatalf("len(fi) = %d, want 2", len(fi))
@@ -267,7 +267,7 @@
 			case "weekends":
 				weekendsFile = f.Name()
 				// while we're here, check that is ok
-				buf, err := os.ReadFile(filepath.Join(telemetry.LocalDir, weekendsFile))
+				buf, err := os.ReadFile(filepath.Join(telemetry.Default.LocalDir(), weekendsFile))
 				if err != nil {
 					t.Fatal(err)
 				}
@@ -280,7 +280,7 @@
 			}
 		}
 
-		buf, err := os.ReadFile(filepath.Join(telemetry.LocalDir, countFile))
+		buf, err := os.ReadFile(filepath.Join(telemetry.Default.LocalDir(), countFile))
 		if err != nil {
 			close(&f)
 			t.Fatal(err)
@@ -303,8 +303,8 @@
 		}
 		close(&f)
 		// remove the file for the next iteration of the loop
-		os.Remove(filepath.Join(telemetry.LocalDir, countFile))
-		os.Remove(filepath.Join(telemetry.LocalDir, weekendsFile))
+		os.Remove(filepath.Join(telemetry.Default.LocalDir(), countFile))
+		os.Remove(filepath.Join(telemetry.Default.LocalDir(), weekendsFile))
 	}
 }
 
@@ -316,12 +316,12 @@
 	for i := 0; i < 7; i++ {
 		counterTime = future(i)
 		for index := range "0123456" {
-			os.WriteFile(filepath.Join(telemetry.LocalDir, "weekends"), []byte{byte(index + '0')}, 0666)
+			os.WriteFile(filepath.Join(telemetry.Default.LocalDir(), "weekends"), []byte{byte(index + '0')}, 0666)
 			var f file
 			c := f.New("gophers")
 			c.Add(7)
 			f.rotate()
-			fis, err := os.ReadDir(telemetry.LocalDir)
+			fis, err := os.ReadDir(telemetry.Default.LocalDir())
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -330,11 +330,11 @@
 			for _, fi := range fis {
 				// ignore errors for brevity: something else will fail
 				if fi.Name() == "weekends" {
-					buf, _ := os.ReadFile(filepath.Join(telemetry.LocalDir, fi.Name()))
+					buf, _ := os.ReadFile(filepath.Join(telemetry.Default.LocalDir(), fi.Name()))
 					buf = bytes.TrimSpace(buf)
 					weekends = time.Weekday(buf[0] - '0')
 				} else if strings.HasSuffix(fi.Name(), ".count") {
-					buf, _ := os.ReadFile(filepath.Join(telemetry.LocalDir, fi.Name()))
+					buf, _ := os.ReadFile(filepath.Join(telemetry.Default.LocalDir(), fi.Name()))
 					parsed, _ := Parse(fi.Name(), buf)
 					begins, _ = time.Parse(time.RFC3339, parsed.Meta["TimeBegin"])
 					ends, _ = time.Parse(time.RFC3339, parsed.Meta["TimeEnd"])
@@ -361,7 +361,7 @@
 			close(&f)
 			// remove files for the next iteration of the loop
 			for _, f := range fis {
-				os.Remove(filepath.Join(telemetry.LocalDir, f.Name()))
+				os.Remove(filepath.Join(telemetry.Default.LocalDir(), f.Name()))
 			}
 		}
 	}
@@ -505,13 +505,9 @@
 
 func setup(t *testing.T) {
 	log.SetFlags(log.Lshortfile)
-	tmpDir := t.TempDir() // new dir for each test
-	telemetry.LocalDir = tmpDir + "/local"
-	telemetry.UploadDir = tmpDir + "/upload"
-	os.MkdirAll(telemetry.LocalDir, 0777)
-	os.MkdirAll(telemetry.UploadDir, 0777)
-	telemetry.ModeFile = telemetry.ModeFilePath(filepath.Join(tmpDir, "mode"))
-	// os.UserConfigDir() is "" in tests so no point in looking at it
+	telemetry.Default = telemetry.NewDir(t.TempDir()) // new dir for each test
+	os.MkdirAll(telemetry.Default.LocalDir(), 0777)
+	os.MkdirAll(telemetry.Default.UploadDir(), 0777)
 }
 
 func restore() {
diff --git a/internal/counter/file.go b/internal/counter/file.go
index 742b1fc..12181b2 100644
--- a/internal/counter/file.go
+++ b/internal/counter/file.go
@@ -124,11 +124,11 @@
 		f.err = errNoBuildInfo
 		return
 	}
-	if mode, _ := telemetry.Mode(); mode == "off" {
+	if mode, _ := telemetry.Default.Mode(); mode == "off" {
 		f.err = ErrDisabled
 		return
 	}
-	dir := telemetry.LocalDir
+	dir := telemetry.Default.LocalDir()
 
 	if err := os.MkdirAll(dir, 0777); err != nil {
 		f.err = err
@@ -203,11 +203,11 @@
 	// If there is no 'weekends' file create it and initialize it
 	// to a random day of the week. There is a short interval for
 	// a race.
-	weekends := filepath.Join(telemetry.LocalDir, "weekends")
+	weekends := filepath.Join(telemetry.Default.LocalDir(), "weekends")
 	day := fmt.Sprintf("%d\n", rand.Intn(7))
 	if _, err := os.ReadFile(weekends); err != nil {
-		if err := os.MkdirAll(telemetry.LocalDir, 0777); err != nil {
-			debugPrintf("%v: could not create telemetry.LocalDir %s", err, telemetry.LocalDir)
+		if err := os.MkdirAll(telemetry.Default.LocalDir(), 0777); err != nil {
+			debugPrintf("%v: could not create telemetry.LocalDir %s", err, telemetry.Default.LocalDir())
 			return 7, err
 		}
 		if err = os.WriteFile(weekends, []byte(day), 0666); err != nil {
@@ -357,7 +357,7 @@
 	if telemetry.DisabledOnPlatform {
 		return func() {}
 	}
-	if mode, _ := telemetry.Mode(); mode == "off" {
+	if mode, _ := telemetry.Default.Mode(); mode == "off" {
 		// Don't open the file when telemetry is off.
 		defaultFile.err = ErrDisabled
 		return func() {} // No need to clean up.
diff --git a/internal/counter/rotate_test.go b/internal/counter/rotate_test.go
index 8b2c390..9e8ae49 100644
--- a/internal/counter/rotate_test.go
+++ b/internal/counter/rotate_test.go
@@ -147,7 +147,7 @@
 	setup(t)
 	defer restore()
 	// pretend something was uploaded
-	os.WriteFile(filepath.Join(telemetry.UploadDir, "anything"), []byte{}, 0666)
+	os.WriteFile(filepath.Join(telemetry.Default.UploadDir(), "anything"), []byte{}, 0666)
 	var f file
 	defer close(&f)
 	c := f.New("gophers")
@@ -156,7 +156,7 @@
 	for i := 0; i < 2; i++ {
 		// nothing should change on the second rotate
 		f.rotate()
-		fi, err := os.ReadDir(telemetry.LocalDir)
+		fi, err := os.ReadDir(telemetry.Default.LocalDir())
 		if err != nil || len(fi) != 2 {
 			t.Fatalf("err=%v, len(fi) = %d, want 2", err, len(fi))
 		}
@@ -170,7 +170,7 @@
 		if us != now {
 			t.Errorf("us = %v, want %v, i=%d y=%s", us, now, i, y)
 		}
-		fd, err := os.Open(filepath.Join(telemetry.LocalDir, fi[0].Name()))
+		fd, err := os.Open(filepath.Join(telemetry.Default.LocalDir(), fi[0].Name()))
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -189,7 +189,7 @@
 	}
 	counterTime = func() time.Time { return now.Add(7 * 24 * time.Hour) }
 	f.rotate()
-	fi, err := os.ReadDir(telemetry.LocalDir)
+	fi, err := os.ReadDir(telemetry.Default.LocalDir())
 	if err != nil || len(fi) != 3 {
 		t.Fatalf("err=%v, len(fi) = %d, want 3", err, len(fi))
 	}
diff --git a/internal/regtest/e2e_test.go b/internal/regtest/e2e_test.go
index 9346c3d..d2ff7d3 100644
--- a/internal/regtest/e2e_test.go
+++ b/internal/regtest/e2e_test.go
@@ -73,20 +73,19 @@
 
 	for _, test := range tests {
 		t.Run(fmt.Sprintf("mode=%s", test.mode), func(t *testing.T) {
-			telemetryDir := t.TempDir()
+			dir := telemetry.NewDir(t.TempDir())
 			if test.mode != "" {
-				if err := telemetry.ModeFilePath(filepath.Join(telemetryDir, "mode")).SetMode(test.mode); err != nil {
+				if err := dir.SetMode(test.mode); err != nil {
 					t.Fatalf("SetMode failed: %v", err)
 				}
 			}
-			out, err := RunProg(t, telemetryDir, prog)
+			out, err := RunProg(t, dir.Dir(), prog)
 			if err != nil {
 				t.Fatalf("program failed unexpectedly (%v)\n%s", err, out)
 			}
-			localDir := filepath.Join(telemetryDir, "local")
-			_, err = os.Stat(localDir)
+			_, err = os.Stat(dir.LocalDir())
 			if err != nil && !os.IsNotExist(err) {
-				t.Fatalf("os.Stat(%q): unexpected error: %v", localDir, err)
+				t.Fatalf("os.Stat(%q): unexpected error: %v", dir.LocalDir(), err)
 			}
 			if gotLocalDir := err == nil; gotLocalDir != test.wantLocalDir {
 				t.Errorf("got /local dir: %v, want %v; out:\n%s", gotLocalDir, test.wantLocalDir, string(out))
diff --git a/internal/telemetry/dir.go b/internal/telemetry/dir.go
new file mode 100644
index 0000000..62f9a75
--- /dev/null
+++ b/internal/telemetry/dir.go
@@ -0,0 +1,155 @@
+// Copyright 2023 The Go 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 telemetry manages the telemetry mode file.
+package telemetry
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"time"
+)
+
+// Default is the default directory containing Go telemetry configuration and
+// data.
+//
+// If Default is uninitialized, Default.Mode will be "off". As a consequence,
+// no data should be written to the directory, and so the path values of
+// LocalDir, UploadDir, etc. must not matter.
+//
+// Default is a global for convenience and testing, but should not be mutated
+// outside of tests.
+//
+// TODO(rfindley): it would be nice to completely eliminate this global state,
+// or at least push it in the golang.org/x/telemetry package
+var Default Dir
+
+// A Dir holds paths to telemetry data inside a directory.
+type Dir struct {
+	dir, local, upload, modefile string
+}
+
+// NewDir creates a new Dir encapsulating paths in the given dir.
+//
+// NewDir does not create any new directories or files--it merely encapsulates
+// the telemetry directory layout.
+func NewDir(dir string) Dir {
+	return Dir{
+		dir:      dir,
+		local:    filepath.Join(dir, "local"),
+		upload:   filepath.Join(dir, "upload"),
+		modefile: filepath.Join(dir, "mode"),
+	}
+}
+
+func init() {
+	cfgDir, err := os.UserConfigDir()
+	if err != nil {
+		return
+	}
+	Default = NewDir(filepath.Join(cfgDir, "go", "telemetry"))
+}
+
+func (d Dir) Dir() string {
+	return d.dir
+}
+
+func (d Dir) LocalDir() string {
+	return d.local
+}
+
+func (d Dir) UploadDir() string {
+	return d.upload
+}
+
+func (d Dir) ModeFile() string {
+	return d.modefile
+}
+
+// SetMode updates the telemetry mode with the given mode.
+// Acceptable values for mode are "on", "off", or "local".
+//
+// SetMode always writes the mode file, and explicitly records the date at
+// which the modefile was updated. This means that calling SetMode with "on"
+// effectively resets the timeout before the next telemetry report is uploaded.
+func (d Dir) SetMode(mode string) error {
+	return d.SetModeAsOf(mode, time.Now())
+}
+
+// SetModeAsOf is like SetMode, but accepts an explicit time to use to
+// back-date the mode state. This exists only for testing purposes.
+func (d Dir) SetModeAsOf(mode string, asofTime time.Time) error {
+	mode = strings.TrimSpace(mode)
+	switch mode {
+	case "on", "off", "local":
+	default:
+		return fmt.Errorf("invalid telemetry mode: %q", mode)
+	}
+	if d.modefile == "" {
+		return fmt.Errorf("cannot determine telemetry mode file name")
+	}
+	// TODO(rfindley): why is this not 777, consistent with the use of 666 below?
+	if err := os.MkdirAll(filepath.Dir(d.modefile), 0755); err != nil {
+		return fmt.Errorf("cannot create a telemetry mode file: %w", err)
+	}
+
+	asof := asofTime.UTC().Format("2006-01-02")
+	// Defensively guarantee that we can parse the asof time.
+	if _, err := time.Parse("2006-01-02", asof); err != nil {
+		return fmt.Errorf("internal error: invalid mode date %q: %v", asof, err)
+	}
+
+	data := []byte(mode + " " + asof)
+	return os.WriteFile(d.modefile, data, 0666)
+}
+
+// Mode returns the current telemetry mode, as well as the time that the mode
+// was effective.
+//
+// If there is no effective time, the second result is the zero time.
+//
+// If Mode is "off", no data should be written to the telemetry directory, and
+// the other paths values referenced by Dir should be considered undefined.
+// This accounts for the case where initializing [Default] fails, and therefore
+// local telemetry paths are unknown.
+func (d Dir) Mode() (string, time.Time) {
+	if d.modefile == "" {
+		return "off", time.Time{} // it's likely LocalDir/UploadDir are empty too. Turn off telemetry.
+	}
+	data, err := os.ReadFile(d.modefile)
+	if err != nil {
+		return "local", time.Time{} // default
+	}
+	mode := string(data)
+	mode = strings.TrimSpace(mode)
+
+	// Forward compatibility for https://go.dev/issue/63142#issuecomment-1734025130
+	//
+	// If the modefile contains a date, return it.
+	if idx := strings.Index(mode, " "); idx >= 0 {
+		d, err := time.Parse("2006-01-02", mode[idx+1:])
+		if err != nil {
+			d = time.Time{}
+		}
+		return mode[:idx], d
+	}
+
+	return mode, time.Time{}
+}
+
+// DisabledOnPlatform indicates whether telemetry is disabled
+// due to bugs in the current platform.
+const DisabledOnPlatform = false ||
+	// The following platforms could potentially be supported in the future:
+	runtime.GOOS == "openbsd" || // #60614
+	runtime.GOOS == "solaris" || // #60968 #60970
+	runtime.GOOS == "android" || // #60967
+	runtime.GOOS == "illumos" || // #65544
+	// These platforms fundamentally can't be supported:
+	runtime.GOOS == "js" || // #60971
+	runtime.GOOS == "wasip1" || // #60971
+	runtime.GOOS == "plan9" // https://github.com/golang/go/issues/57540#issuecomment-1470766639
diff --git a/internal/telemetry/mode_test.go b/internal/telemetry/dir_test.go
similarity index 63%
rename from internal/telemetry/mode_test.go
rename to internal/telemetry/dir_test.go
index ebde64b..aafe06b 100644
--- a/internal/telemetry/mode_test.go
+++ b/internal/telemetry/dir_test.go
@@ -6,42 +6,38 @@
 package telemetry
 
 import (
-	"fmt"
 	"os"
-	"path/filepath"
 	"testing"
 	"time"
 )
 
-func TestTelemetryDefault(t *testing.T) {
+func TestDefaults(t *testing.T) {
 	defaultDirMissing := false
 	if _, err := os.UserConfigDir(); err != nil {
 		defaultDirMissing = true
 	}
 	if defaultDirMissing {
-		if LocalDir != "" || UploadDir != "" || ModeFile != "" {
-			t.Errorf("DefaultSetting: (%q, %q, %q), want empty LocalDir/UploadDir/ModeFile", LocalDir, UploadDir, ModeFile)
+		if Default.LocalDir() != "" || Default.UploadDir() != "" || Default.ModeFile() != "" {
+			t.Errorf("DefaultSetting: (%q, %q, %q), want empty LocalDir/UploadDir/ModeFile", Default.LocalDir(), Default.UploadDir(), Default.ModeFile())
 		}
 	} else {
-		if LocalDir == "" || UploadDir == "" || ModeFile == "" {
-			t.Errorf("DefaultSetting: (%q, %q, %q), want non-empty LocalDir/UploadDir/ModeFile", LocalDir, UploadDir, ModeFile)
+		if Default.LocalDir() == "" || Default.UploadDir() == "" || Default.ModeFile() == "" {
+			t.Errorf("DefaultSetting: (%q, %q, %q), want non-empty LocalDir/UploadDir/ModeFile", Default.LocalDir(), Default.UploadDir(), Default.ModeFile())
 		}
 	}
 }
 
 func TestTelemetryModeWithNoModeConfig(t *testing.T) {
-	tmp := t.TempDir()
 	tests := []struct {
-		modefile ModeFilePath
-		want     string
+		dir  Dir
+		want string
 	}{
-		{ModeFilePath(filepath.Join(tmp, "mode")), "local"},
-		{"", "off"},
+		{NewDir(t.TempDir()), "local"},
+		{Dir{}, "off"},
 	}
 	for _, tt := range tests {
-		if got, _ := tt.modefile.Mode(); got != tt.want {
-			t.Logf("Mode file: %q", tt.modefile)
-			t.Errorf("Mode() = %v, want %v", got, tt.want)
+		if got, _ := tt.dir.Mode(); got != tt.want {
+			t.Errorf("Dir{modefile=%q}.Mode() = %v, want %v", tt.dir.ModeFile(), got, tt.want)
 		}
 	}
 }
@@ -59,18 +55,17 @@
 		{"bogus", true},
 		{"", true},
 	}
-	tmp := t.TempDir()
-	for i, tt := range tests {
+	for _, tt := range tests {
 		t.Run("mode="+tt.in, func(t *testing.T) {
-			modefile := ModeFilePath(filepath.Join(tmp, fmt.Sprintf("modefile%d", i)))
-			setErr := modefile.SetMode(tt.in)
+			dir := NewDir(t.TempDir())
+			setErr := dir.SetMode(tt.in)
 			if (setErr != nil) != tt.wantErr {
 				t.Fatalf("Set() error = %v, wantErr %v", setErr, tt.wantErr)
 			}
 			if setErr != nil {
 				return
 			}
-			if got, _ := modefile.Mode(); got != tt.in {
+			if got, _ := dir.Mode(); got != tt.in {
 				t.Errorf("LookupMode() = %q, want %q", got, tt.in)
 			}
 		})
@@ -88,16 +83,15 @@
 		{"off", "off", time.Time{}},
 		{"local", "local", time.Time{}},
 	}
-	tmp := t.TempDir()
-	for i, tt := range tests {
+	for _, tt := range tests {
 		t.Run("mode="+tt.in, func(t *testing.T) {
-			fname := filepath.Join(tmp, fmt.Sprintf("modefile%d", i))
-			if err := os.WriteFile(fname, []byte(tt.in), 0666); err != nil {
+			dir := NewDir(t.TempDir())
+			if err := os.WriteFile(dir.ModeFile(), []byte(tt.in), 0666); err != nil {
 				t.Fatal(err)
 			}
 			// Note: the checks below intentionally do not use time.Equal:
 			// we want this exact representation of time.
-			if gotMode, gotTime := ModeFilePath(fname).Mode(); gotMode != tt.wantMode || gotTime != tt.wantTime {
+			if gotMode, gotTime := dir.Mode(); gotMode != tt.wantMode || gotTime != tt.wantTime {
 				t.Errorf("ModeFilePath(contents=%s).Mode() = %q, %v, want %q, %v", tt.in, gotMode, gotTime, tt.wantMode, tt.wantTime)
 			}
 		})
diff --git a/internal/telemetry/mode.go b/internal/telemetry/mode.go
deleted file mode 100644
index d7d3f24..0000000
--- a/internal/telemetry/mode.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2023 The Go 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 telemetry manages the telemetry mode file.
-package telemetry
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"runtime"
-	"strings"
-	"time"
-)
-
-// The followings are the process' default Settings.
-// The values are subdirectories and a file under
-// os.UserConfigDir()/go/telemetry.
-// For convenience, each field is made to global
-// and they are not supposed to be changed.
-var (
-	// Default directory containing count files, local reports (not yet uploaded), and logs
-	LocalDir string
-	// Default directory containing uploaded reports.
-	UploadDir string
-	// Default file path that holds the telemetry mode info.
-	ModeFile ModeFilePath
-)
-
-// ModeFilePath is the telemetry mode file path with methods to manipulate the file contents.
-type ModeFilePath string
-
-func init() {
-	cfgDir, err := os.UserConfigDir()
-	if err != nil {
-		return
-	}
-	gotelemetrydir := filepath.Join(cfgDir, "go", "telemetry")
-	LocalDir = filepath.Join(gotelemetrydir, "local")
-	UploadDir = filepath.Join(gotelemetrydir, "upload")
-	ModeFile = ModeFilePath(filepath.Join(gotelemetrydir, "mode"))
-}
-
-// SetMode updates the telemetry mode with the given mode.
-// Acceptable values for mode are "on", "off", or "local".
-//
-// SetMode always writes the mode file, and explicitly records the date at
-// which the modefile was updated. This means that calling SetMode with "on"
-// effectively resets the timeout before the next telemetry report is uploaded.
-func SetMode(mode string) error {
-	return ModeFile.SetMode(mode)
-}
-
-func (m ModeFilePath) SetMode(mode string) error {
-	return m.SetModeAsOf(mode, time.Now())
-}
-
-// SetModeAsOf is like SetMode, but accepts an explicit time to use to
-// back-date the mode state. This exists only for testing purposes.
-func (m ModeFilePath) SetModeAsOf(mode string, asofTime time.Time) error {
-	mode = strings.TrimSpace(mode)
-	switch mode {
-	case "on", "off", "local":
-	default:
-		return fmt.Errorf("invalid telemetry mode: %q", mode)
-	}
-	fname := string(m)
-	if fname == "" {
-		return fmt.Errorf("cannot determine telemetry mode file name")
-	}
-	if err := os.MkdirAll(filepath.Dir(fname), 0755); err != nil {
-		return fmt.Errorf("cannot create a telemetry mode file: %w", err)
-	}
-
-	asof := asofTime.UTC().Format("2006-01-02")
-	// Defensively guarantee that we can parse the asof time.
-	if _, err := time.Parse("2006-01-02", asof); err != nil {
-		return fmt.Errorf("internal error: invalid mode date %q: %v", asof, err)
-	}
-
-	data := []byte(mode + " " + asof)
-	return os.WriteFile(fname, data, 0666)
-}
-
-// Mode returns the current telemetry mode, as well as the time that the mode
-// was effective.
-//
-// If there is no effective time, the second result is the zero time.
-func Mode() (string, time.Time) {
-	return ModeFile.Mode()
-}
-
-func (m ModeFilePath) Mode() (string, time.Time) {
-	fname := string(m)
-	if fname == "" {
-		return "off", time.Time{} // it's likely LocalDir/UploadDir are empty too. Turn off telemetry.
-	}
-	data, err := os.ReadFile(fname)
-	if err != nil {
-		return "local", time.Time{} // default
-	}
-	mode := string(data)
-	mode = strings.TrimSpace(mode)
-
-	// Forward compatibility for https://go.dev/issue/63142#issuecomment-1734025130
-	//
-	// If the modefile contains a date, return it.
-	if idx := strings.Index(mode, " "); idx >= 0 {
-		d, err := time.Parse("2006-01-02", mode[idx+1:])
-		if err != nil {
-			d = time.Time{}
-		}
-		return mode[:idx], d
-	}
-
-	return mode, time.Time{}
-}
-
-// DisabledOnPlatform indicates whether telemetry is disabled
-// due to bugs in the current platform.
-const DisabledOnPlatform = false ||
-	// The following platforms could potentially be supported in the future:
-	runtime.GOOS == "openbsd" || // #60614
-	runtime.GOOS == "solaris" || // #60968 #60970
-	runtime.GOOS == "android" || // #60967
-	runtime.GOOS == "illumos" || // #65544
-	// These platforms fundamentally can't be supported:
-	runtime.GOOS == "js" || // #60971
-	runtime.GOOS == "wasip1" || // #60971
-	runtime.GOOS == "plan9" // https://github.com/golang/go/issues/57540#issuecomment-1470766639
diff --git a/internal/upload/dates_test.go b/internal/upload/dates_test.go
index 495ac2e..bf41e37 100644
--- a/internal/upload/dates_test.go
+++ b/internal/upload/dates_test.go
@@ -66,24 +66,21 @@
 	// and scheduled to get expired in the future.
 	// Let's pretend telemetry was enabled a year ago by mutating the mode file,
 	// we are in the future, and test if the count files are successfully uploaded.
-	uploader.ModeFilePath.SetModeAsOf("on", uploader.StartTime.Add(-365*24*time.Hour).UTC())
-	uploadedContent, fname := subtest(t, uc, uploader) // TODO(hyangah) : inline
+	uploader.Dir.SetModeAsOf("on", uploader.StartTime.Add(-365*24*time.Hour).UTC())
+	uploadedContent, fname := subtest(t, uploader) // TODO(hyangah) : inline
 
 	if want, got := [][]byte{uploadedContent}, uploaded(); !reflect.DeepEqual(want, got) {
 		t.Errorf("server got %s\nwant %s", got, want)
 	}
 	// and check that the uploaded report is in the upload dir
-	uname := filepath.Join(uploader.UploadDir, fname)
+	uname := filepath.Join(uploader.Dir.UploadDir(), fname)
 	if _, err := os.Stat(uname); err != nil {
 		t.Errorf("%v for uploade report %s", err, uname)
 	}
 }
 
 func newTestUploader(uc *telemetry.UploadConfig, telemetryDir string, srv *httptest.Server) *Uploader {
-	uploader := NewUploader(uc)
-	uploader.LocalDir = filepath.Join(telemetryDir, "local")
-	uploader.UploadDir = filepath.Join(telemetryDir, "upload")
-	uploader.ModeFilePath = telemetry.ModeFilePath(filepath.Join(telemetryDir, "mode"))
+	uploader := NewUploader(telemetryDir, uc)
 	uploader.UploadServerURL = srv.URL
 	return uploader
 }
@@ -103,7 +100,7 @@
 }
 
 // createFailingUploadServer creates a test server that returns errors
-func createFailingUploadServer(t *testing.T) *httptest.Server {
+func createFailingUploadServer() *httptest.Server {
 	f := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		io.ReadAll(r.Body)
 		http.Error(w, "failed", http.StatusBadRequest)
@@ -130,7 +127,7 @@
 	uc := createTestUploadConfig(t, []string{"knownCounter"}, []string{"aStack"})
 
 	// Start upload server
-	srv := createFailingUploadServer(t)
+	srv := createFailingUploadServer()
 	defer srv.Close()
 
 	uploader := newTestUploader(uc, telemetryDir, srv)
@@ -151,11 +148,11 @@
 	// and scheduled to get expired in the future.
 	// Let's pretend telemetry was enabled a year ago by mutating the mode file,
 	// we are in the future, and test if the count files are successfully uploaded.
-	uploader.ModeFilePath.SetModeAsOf("on", uploader.StartTime.Add(-365*24*time.Hour).UTC())
-	_, fname := subtest(t, uc, uploader)
+	uploader.Dir.SetModeAsOf("on", uploader.StartTime.Add(-365*24*time.Hour).UTC())
+	_, fname := subtest(t, uploader)
 	// check that fname does not exist
-	lname := filepath.Join(uploader.LocalDir, fname)
-	uname := filepath.Join(uploader.UploadDir, fname)
+	lname := filepath.Join(uploader.Dir.LocalDir(), fname)
+	uname := filepath.Join(uploader.Dir.UploadDir(), fname)
 	if _, err := os.Stat(lname); err == nil {
 		t.Errorf("local file %s should not exist", lname)
 	}
@@ -322,7 +319,7 @@
 			defer srv.Close()
 
 			uploader := newTestUploader(uc, telemetryDir, srv)
-			uploader.ModeFilePath.SetModeAsOf("on", telemetryEnableTime)
+			uploader.Dir.SetModeAsOf("on", telemetryEnableTime)
 			uploader.UploadServerURL = srv.URL
 			uploader.StartTime = mustParseDate(tx.today)
 
@@ -441,13 +438,13 @@
 }
 
 func doTest(t *testing.T, u *Uploader, doing *Test, known *countFileInfo) int {
-	dbg := filepath.Join(u.LocalDir, "debug")
+	dbg := filepath.Join(u.Dir.LocalDir(), "debug")
 	os.MkdirAll(dbg, 0777)
 	if err := LogIfDebug(dbg); err != nil {
 		t.Errorf("debug logging: %v", err)
 	}
 	if len(doing.uploads) > 0 {
-		os.MkdirAll(u.UploadDir, 0777)
+		os.MkdirAll(u.Dir.UploadDir(), 0777)
 	}
 	contents := bytes.Join([][]byte{
 		known.buf[:known.beginOffset],
@@ -457,25 +454,25 @@
 		known.buf[known.endOffset+len("YYYY-MM-DD"):],
 	}, nil)
 	filename := known.namePrefix + doing.date + ".v1.count"
-	if err := os.WriteFile(filepath.Join(u.LocalDir, filename), contents, 0666); err != nil {
+	if err := os.WriteFile(filepath.Join(u.Dir.LocalDir(), filename), contents, 0666); err != nil {
 		t.Errorf("%v writing count file for %s (%s)", err, doing.name, filename)
 		return 0
 	}
 	for _, x := range doing.locals {
 		nm := fmt.Sprintf("local.%s.json", x)
-		if err := os.WriteFile(filepath.Join(u.LocalDir, nm), []byte{}, 0666); err != nil {
+		if err := os.WriteFile(filepath.Join(u.Dir.LocalDir(), nm), []byte{}, 0666); err != nil {
 			t.Errorf("%v writing local file %s", err, nm)
 		}
 	}
 	for _, x := range doing.readys {
 		nm := fmt.Sprintf("%s.json", x)
-		if err := os.WriteFile(filepath.Join(u.LocalDir, nm), []byte{}, 0666); err != nil {
+		if err := os.WriteFile(filepath.Join(u.Dir.LocalDir(), nm), []byte{}, 0666); err != nil {
 			t.Errorf("%v writing ready file %s", err, nm)
 		}
 	}
 	for _, x := range doing.uploads {
 		nm := fmt.Sprintf("%s.json", x)
-		if err := os.WriteFile(filepath.Join(u.UploadDir, nm), []byte{}, 0666); err != nil {
+		if err := os.WriteFile(filepath.Join(u.Dir.UploadDir(), nm), []byte{}, 0666); err != nil {
 			t.Errorf("%v writing upload %s", err, nm)
 		}
 	}
@@ -485,9 +482,9 @@
 
 	// check results
 	var cfiles, rfiles, lfiles, ufiles, logcnt int
-	fis, err := os.ReadDir(u.LocalDir)
+	fis, err := os.ReadDir(u.Dir.LocalDir())
 	if err != nil {
-		t.Errorf("%v reading localdir %s", err, u.LocalDir)
+		t.Errorf("%v reading localdir %s", err, u.Dir.LocalDir())
 		return 0
 	}
 	for _, f := range fis {
@@ -500,7 +497,7 @@
 		case strings.HasSuffix(f.Name(), ".json"):
 			rfiles++
 		case f.Name() == "debug":
-			dbgname := filepath.Join(u.LocalDir, "debug")
+			dbgname := filepath.Join(u.Dir.LocalDir(), "debug")
 			logs, err := os.ReadDir(dbgname)
 			if err != nil {
 				break
@@ -513,9 +510,9 @@
 	if logcnt != 1 {
 		t.Errorf("expected 1 log file, got %d", logcnt)
 	}
-	fis, err = os.ReadDir(u.UploadDir)
+	fis, err = os.ReadDir(u.Dir.UploadDir())
 	if err != nil {
-		t.Errorf("%v reading uploaddir %s", err, u.UploadDir)
+		t.Errorf("%v reading uploaddir %s", err, u.Dir.UploadDir())
 		return 0
 	}
 	ufiles = len(fis) // assume there's nothing but .json reports
@@ -543,7 +540,7 @@
 }
 
 // check that generated report is as expected, and return its contents and its name
-func subtest(t *testing.T, c *telemetry.UploadConfig, u *Uploader) ([]byte, string) {
+func subtest(t *testing.T, u *Uploader) ([]byte, string) {
 	// check state before generating report
 	work := u.findWork()
 	// expect one count file and nothing else
@@ -569,7 +566,7 @@
 		// the uploadable report
 		t.Errorf("expected one readyfile, got %d", len(got.readyfiles))
 	}
-	fi, err := os.ReadDir(u.LocalDir)
+	fi, err := os.ReadDir(u.Dir.LocalDir())
 	if len(fi) != 3 || err != nil {
 		// one local report, one uploadable report, one weekends file
 		t.Errorf("expected three files in LocalDir, got %d, %v", len(fi), err)
@@ -581,7 +578,7 @@
 	var localFile, uploadFile []byte
 	var uploadName string
 	for _, f := range fi {
-		fname := filepath.Join(u.LocalDir, f.Name())
+		fname := filepath.Join(u.Dir.LocalDir(), f.Name())
 		buf, err := os.ReadFile(fname)
 		if err != nil {
 			t.Fatal(err)
diff --git a/internal/upload/findwork.go b/internal/upload/findwork.go
index 286d6be..da3a16a 100644
--- a/internal/upload/findwork.go
+++ b/internal/upload/findwork.go
@@ -23,7 +23,7 @@
 // that need to be uploaded. (There may be unexpected leftover files
 // and uploading is supposed to be idempotent.)
 func (u *Uploader) findWork() work {
-	localdir, uploaddir := u.LocalDir, u.UploadDir
+	localdir, uploaddir := u.Dir.LocalDir(), u.Dir.UploadDir()
 	var ans work
 	fis, err := os.ReadDir(localdir)
 	if err != nil {
@@ -31,7 +31,7 @@
 		return ans
 	}
 
-	mode, asof := u.ModeFilePath.Mode()
+	mode, asof := u.Dir.Mode()
 	logger.Printf("mode %s, asof %s", mode, asof)
 
 	// count files end in .v1.count
diff --git a/internal/upload/reports.go b/internal/upload/reports.go
index e8a65bc..c679612 100644
--- a/internal/upload/reports.go
+++ b/internal/upload/reports.go
@@ -23,7 +23,7 @@
 
 // reports generates reports from inactive count files
 func (u *Uploader) reports(todo *work) ([]string, error) {
-	if mode, _ := u.ModeFilePath.Mode(); mode == "off" {
+	if mode, _ := u.Dir.Mode(); mode == "off" {
 		return nil, nil // no reports
 	}
 	thisInstant := u.StartTime
@@ -120,7 +120,7 @@
 		u.ConfigVersion = v
 	}
 	uploadOK := true
-	mode, asof := u.ModeFilePath.Mode()
+	mode, asof := u.Dir.Mode()
 	if u.Config == nil || mode != "on" {
 		logger.Printf("no upload config or mode %q is not 'on'", mode)
 		uploadOK = false // no config, nothing to upload
@@ -227,8 +227,8 @@
 			return "", fmt.Errorf("failed to marshal upload report (%v)", err)
 		}
 	}
-	localFileName := filepath.Join(u.LocalDir, "local."+expiryDate+".json")
-	uploadFileName := filepath.Join(u.LocalDir, expiryDate+".json")
+	localFileName := filepath.Join(u.Dir.LocalDir(), "local."+expiryDate+".json")
+	uploadFileName := filepath.Join(u.Dir.LocalDir(), expiryDate+".json")
 
 	/* Prepare to write files */
 	// if either file exists, someone has been here ahead of us
diff --git a/internal/upload/run.go b/internal/upload/run.go
index f21d973..beab886 100644
--- a/internal/upload/run.go
+++ b/internal/upload/run.go
@@ -44,7 +44,7 @@
 // dirname, if it exists. If dirname is the empty string,
 // the function tries the directory it.Localdir/debug.
 func LogIfDebug(dirname string) error {
-	dname := filepath.Join(telemetry.LocalDir, "debug")
+	dname := filepath.Join(telemetry.Default.LocalDir(), "debug")
 	if dirname != "" {
 		dname = dirname
 	}
@@ -93,12 +93,8 @@
 	// ConfigVersion is the version of the config.
 	ConfigVersion string
 
-	// LocalDir is where the local counter files are.
-	LocalDir string
-	// UploadDir is where uploader leaves the copy of uploaded data.
-	UploadDir string
-	// ModeFilePath is the file.
-	ModeFilePath telemetry.ModeFilePath
+	// Dir is the telemetry directory to process.
+	Dir telemetry.Dir
 
 	UploadServerURL string
 	StartTime       time.Time
@@ -107,13 +103,24 @@
 }
 
 // NewUploader creates a default uploader.
-func NewUploader(config *telemetry.UploadConfig) *Uploader {
+//
+// If telemetryDir is set, it is used as the telemetry data directory to
+// process, instead of the default directory.
+//
+// If config is set, it is used as the upload config, rather than the
+// downloaded upload configuration.
+func NewUploader(telemetryDir string, config *telemetry.UploadConfig) *Uploader {
+	var dir telemetry.Dir
+	if telemetryDir != "" {
+		dir = telemetry.NewDir(telemetryDir)
+	} else {
+		dir = telemetry.Default
+	}
+	// TODO(rfindley): get the config here, rather than during the upload process.
 	return &Uploader{
 		Config:          config,
 		ConfigVersion:   "custom",
-		LocalDir:        telemetry.LocalDir,
-		UploadDir:       telemetry.UploadDir,
-		ModeFilePath:    telemetry.ModeFile,
+		Dir:             dir,
 		UploadServerURL: "https://telemetry.go.dev/upload",
 		StartTime:       time.Now().UTC(),
 	}
diff --git a/internal/upload/upload.go b/internal/upload/upload.go
index 9be10a7..c01f9c4 100644
--- a/internal/upload/upload.go
+++ b/internal/upload/upload.go
@@ -85,7 +85,7 @@
 		return false
 	}
 	// put a copy in the uploaded directory
-	newname := filepath.Join(u.UploadDir, fdate+".json")
+	newname := filepath.Join(u.Dir.UploadDir(), fdate+".json")
 	if err := os.WriteFile(newname, buf, 0644); err == nil {
 		os.Remove(fname) // if it exists
 	}
diff --git a/mode.go b/mode.go
index fb9672c..1361d64 100644
--- a/mode.go
+++ b/mode.go
@@ -24,7 +24,7 @@
 //
 // [gotelemetry]: https://pkg.go.dev/golang.org/x/telemetry/cmd/gotelemetry
 func Mode() string {
-	mode, _ := telemetry.Mode()
+	mode, _ := telemetry.Default.Mode()
 	return mode
 }
 
@@ -36,5 +36,5 @@
 // An error is returned if the provided mode value is invalid, or if an error
 // occurs while persisting the mode value to the file system.
 func SetMode(mode string) error {
-	return telemetry.SetMode(mode)
+	return telemetry.Default.SetMode(mode)
 }
diff --git a/start.go b/start.go
index 6c88992..3cc5c1f 100644
--- a/start.go
+++ b/start.go
@@ -70,11 +70,9 @@
 // be executed twice (parent and child).
 func Start(config Config) {
 	if config.TelemetryDir != "" {
-		telemetry.ModeFile = telemetry.ModeFilePath(filepath.Join(config.TelemetryDir, "mode"))
-		telemetry.LocalDir = filepath.Join(config.TelemetryDir, "local")
-		telemetry.UploadDir = filepath.Join(config.TelemetryDir, "upload")
+		telemetry.Default = telemetry.NewDir(config.TelemetryDir)
 	}
-	mode, _ := telemetry.Mode()
+	mode, _ := telemetry.Default.Mode()
 	if mode == "off" {
 		// Telemetry is turned off. Crash reporting doesn't work without telemetry
 		// at least set to "local", and the uploader isn't started in uploaderChild if
@@ -84,7 +82,7 @@
 
 	counter.Open()
 
-	if _, err := os.Stat(telemetry.LocalDir); err != nil {
+	if _, err := os.Stat(telemetry.Default.LocalDir()); err != nil {
 		// There was a problem statting LocalDir, which is needed for both
 		// crash monitoring and counter uploading. Most likely, there was an
 		// error creating telemetry.LocalDir in the counter.Open call above.
@@ -121,7 +119,7 @@
 	cmd := exec.Command(exe, "** telemetry **") // this unused arg is just for ps(1)
 	daemonize(cmd)
 	cmd.Env = append(os.Environ(), telemetryChildVar+"=1")
-	cmd.Dir = telemetry.LocalDir
+	cmd.Dir = telemetry.Default.LocalDir()
 
 	// The child process must write to a log file, not
 	// the stderr file it inherited from the parent, as
@@ -132,7 +130,7 @@
 	// By default, we discard the child process's stderr,
 	// but in line with the uploader, log to a file in local/debug
 	// only if that directory was created by the user.
-	localDebug := filepath.Join(telemetry.LocalDir, "debug")
+	localDebug := filepath.Join(telemetry.Default.LocalDir(), "debug")
 	fd, err := os.Stat(localDebug)
 	if err != nil {
 		if !os.IsNotExist(err) {
@@ -190,17 +188,17 @@
 }
 
 func uploaderChild() {
-	if mode, _ := telemetry.Mode(); mode == "off" {
+	if mode, _ := telemetry.Default.Mode(); mode == "off" {
 		// There's no work to be done if telemetry is turned off.
 		return
 	}
-	if telemetry.LocalDir == "" {
+	if telemetry.Default.LocalDir() == "" {
 		// The telemetry dir wasn't initialized properly, probably because
 		// os.UserConfigDir did not complete successfully. In that case
 		// there are no counters to upload, so we should just do nothing.
 		return
 	}
-	tokenfilepath := filepath.Join(telemetry.LocalDir, "upload.token")
+	tokenfilepath := filepath.Join(telemetry.Default.LocalDir(), "upload.token")
 	ok, err := acquireUploadToken(tokenfilepath)
 	if err != nil {
 		log.Printf("error acquiring upload token: %v", err)
diff --git a/upload/upload.go b/upload/upload.go
index 122b725..bb2eecc 100644
--- a/upload/upload.go
+++ b/upload/upload.go
@@ -25,7 +25,7 @@
 			log.Printf("upload recover: %v", err)
 		}
 	}()
-	upload.NewUploader(nil).Run()
+	upload.NewUploader("", nil).Run()
 }
 
 // A Control allows the user to override various default