blob: 1e9154c5f533690a90727f76c13cc14fea0f3d38 [file] [log] [blame]
package driver
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
)
// settings holds pprof settings.
type settings struct {
// Configs holds a list of named UI configurations.
Configs []namedConfig `json:"configs"`
}
// namedConfig associates a name with a config.
type namedConfig struct {
Name string `json:"name"`
config
}
// settingsFileName returns the name of the file where settings should be saved.
func settingsFileName() (string, error) {
// Return "pprof/settings.json" under os.UserConfigDir().
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "pprof", "settings.json"), nil
}
// readSettings reads settings from fname.
func readSettings(fname string) (*settings, error) {
data, err := ioutil.ReadFile(fname)
if err != nil {
if os.IsNotExist(err) {
return &settings{}, nil
}
return nil, fmt.Errorf("could not read settings: %w", err)
}
settings := &settings{}
if err := json.Unmarshal(data, settings); err != nil {
return nil, fmt.Errorf("could not parse settings: %w", err)
}
for i := range settings.Configs {
settings.Configs[i].resetTransient()
}
return settings, nil
}
// writeSettings saves settings to fname.
func writeSettings(fname string, settings *settings) error {
data, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return fmt.Errorf("could not encode settings: %w", err)
}
// create the settings directory if it does not exist
// XDG specifies permissions 0700 when creating settings dirs:
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
return fmt.Errorf("failed to create settings directory: %w", err)
}
if err := ioutil.WriteFile(fname, data, 0644); err != nil {
return fmt.Errorf("failed to write settings: %w", err)
}
return nil
}
// configMenuEntry holds information for a single config menu entry.
type configMenuEntry struct {
Name string
URL string
Current bool // Is this the currently selected config?
UserConfig bool // Is this a user-provided config?
}
// configMenu returns a list of items to add to a menu in the web UI.
func configMenu(fname string, u url.URL) []configMenuEntry {
// Start with system configs.
configs := []namedConfig{{Name: "Default", config: defaultConfig()}}
if settings, err := readSettings(fname); err == nil {
// Add user configs.
configs = append(configs, settings.Configs...)
}
// Convert to menu entries.
result := make([]configMenuEntry, len(configs))
lastMatch := -1
for i, cfg := range configs {
dst, changed := cfg.config.makeURL(u)
if !changed {
lastMatch = i
}
// Use a relative URL to work in presence of stripping/redirects in webui.go.
rel := &url.URL{RawQuery: dst.RawQuery, ForceQuery: true}
result[i] = configMenuEntry{
Name: cfg.Name,
URL: rel.String(),
UserConfig: (i != 0),
}
}
// Mark the last matching config as currennt
if lastMatch >= 0 {
result[lastMatch].Current = true
}
return result
}
// editSettings edits settings by applying fn to them.
func editSettings(fname string, fn func(s *settings) error) error {
settings, err := readSettings(fname)
if err != nil {
return err
}
if err := fn(settings); err != nil {
return err
}
return writeSettings(fname, settings)
}
// setConfig saves the config specified in request to fname.
func setConfig(fname string, request url.URL) error {
q := request.Query()
name := q.Get("config")
if name == "" {
return fmt.Errorf("invalid config name")
}
cfg := currentConfig()
if err := cfg.applyURL(q); err != nil {
return err
}
return editSettings(fname, func(s *settings) error {
for i, c := range s.Configs {
if c.Name == name {
s.Configs[i].config = cfg
return nil
}
}
s.Configs = append(s.Configs, namedConfig{Name: name, config: cfg})
return nil
})
}
// removeConfig removes config from fname.
func removeConfig(fname, config string) error {
return editSettings(fname, func(s *settings) error {
for i, c := range s.Configs {
if c.Name == config {
s.Configs = append(s.Configs[:i], s.Configs[i+1:]...)
return nil
}
}
return fmt.Errorf("config %s not found", config)
})
}