1package driver 2 3import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "net/url" 8 "os" 9 "path/filepath" 10) 11 12// settings holds pprof settings. 13type settings struct { 14 // Configs holds a list of named UI configurations. 15 Configs []namedConfig `json:"configs"` 16} 17 18// namedConfig associates a name with a config. 19type namedConfig struct { 20 Name string `json:"name"` 21 config 22} 23 24// settingsFileName returns the name of the file where settings should be saved. 25func settingsFileName() (string, error) { 26 // Return "pprof/settings.json" under os.UserConfigDir(). 27 dir, err := os.UserConfigDir() 28 if err != nil { 29 return "", err 30 } 31 return filepath.Join(dir, "pprof", "settings.json"), nil 32} 33 34// readSettings reads settings from fname. 35func readSettings(fname string) (*settings, error) { 36 data, err := ioutil.ReadFile(fname) 37 if err != nil { 38 if os.IsNotExist(err) { 39 return &settings{}, nil 40 } 41 return nil, fmt.Errorf("could not read settings: %w", err) 42 } 43 settings := &settings{} 44 if err := json.Unmarshal(data, settings); err != nil { 45 return nil, fmt.Errorf("could not parse settings: %w", err) 46 } 47 for i := range settings.Configs { 48 settings.Configs[i].resetTransient() 49 } 50 return settings, nil 51} 52 53// writeSettings saves settings to fname. 54func writeSettings(fname string, settings *settings) error { 55 data, err := json.MarshalIndent(settings, "", " ") 56 if err != nil { 57 return fmt.Errorf("could not encode settings: %w", err) 58 } 59 60 // create the settings directory if it does not exist 61 // XDG specifies permissions 0700 when creating settings dirs: 62 // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html 63 if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil { 64 return fmt.Errorf("failed to create settings directory: %w", err) 65 } 66 67 if err := ioutil.WriteFile(fname, data, 0644); err != nil { 68 return fmt.Errorf("failed to write settings: %w", err) 69 } 70 return nil 71} 72 73// configMenuEntry holds information for a single config menu entry. 74type configMenuEntry struct { 75 Name string 76 URL string 77 Current bool // Is this the currently selected config? 78 UserConfig bool // Is this a user-provided config? 79} 80 81// configMenu returns a list of items to add to a menu in the web UI. 82func configMenu(fname string, url url.URL) []configMenuEntry { 83 // Start with system configs. 84 configs := []namedConfig{{Name: "Default", config: defaultConfig()}} 85 if settings, err := readSettings(fname); err == nil { 86 // Add user configs. 87 configs = append(configs, settings.Configs...) 88 } 89 90 // Convert to menu entries. 91 result := make([]configMenuEntry, len(configs)) 92 lastMatch := -1 93 for i, cfg := range configs { 94 dst, changed := cfg.config.makeURL(url) 95 if !changed { 96 lastMatch = i 97 } 98 result[i] = configMenuEntry{ 99 Name: cfg.Name, 100 URL: dst.String(), 101 UserConfig: (i != 0), 102 } 103 } 104 // Mark the last matching config as currennt 105 if lastMatch >= 0 { 106 result[lastMatch].Current = true 107 } 108 return result 109} 110 111// editSettings edits settings by applying fn to them. 112func editSettings(fname string, fn func(s *settings) error) error { 113 settings, err := readSettings(fname) 114 if err != nil { 115 return err 116 } 117 if err := fn(settings); err != nil { 118 return err 119 } 120 return writeSettings(fname, settings) 121} 122 123// setConfig saves the config specified in request to fname. 124func setConfig(fname string, request url.URL) error { 125 q := request.Query() 126 name := q.Get("config") 127 if name == "" { 128 return fmt.Errorf("invalid config name") 129 } 130 cfg := currentConfig() 131 if err := cfg.applyURL(q); err != nil { 132 return err 133 } 134 return editSettings(fname, func(s *settings) error { 135 for i, c := range s.Configs { 136 if c.Name == name { 137 s.Configs[i].config = cfg 138 return nil 139 } 140 } 141 s.Configs = append(s.Configs, namedConfig{Name: name, config: cfg}) 142 return nil 143 }) 144} 145 146// removeConfig removes config from fname. 147func removeConfig(fname, config string) error { 148 return editSettings(fname, func(s *settings) error { 149 for i, c := range s.Configs { 150 if c.Name == config { 151 s.Configs = append(s.Configs[:i], s.Configs[i+1:]...) 152 return nil 153 } 154 } 155 return fmt.Errorf("config %s not found", config) 156 }) 157} 158