1 /*
2  *
3  * Conky, a system monitor, based on torsmo
4  *
5  * Please see COPYING for details
6  *
7  * Copyright (C) 2010 Pavel Labath et al.
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22 
23 #include <config.h>
24 
25 #include "setting.hh"
26 
27 #include <algorithm>
28 #include <memory>
29 #include <unordered_map>
30 #include <utility>
31 #include <vector>
32 
33 namespace conky {
34 
35 namespace {
36 typedef std::unordered_map<std::string, priv::config_setting_base *>
37     settings_map;
38 using settings_vector = std::vector<priv::config_setting_base *>;
39 
40 /*
41  * We cannot construct this object statically, because order of object
42  * construction in different modules is not defined, so config_setting_base
43  * could be called before this object is constructed. Therefore, we create it on
44  * the first call to config_setting_base constructor.
45  */
46 settings_map *settings;
47 
48 /*
49  * Returns the setting record corresponding to the value at the specified index.
50  * If the value is not valid, returns nullptr and prints an error.
51  */
get_setting(lua::state & l,int index)52 priv::config_setting_base *get_setting(lua::state &l, int index) {
53   lua::Type type = l.type(index);
54   if (type != lua::TSTRING) {
55     NORM_ERR("invalid setting of type '%s'", l.type_name(type));
56     return nullptr;
57   }
58 
59   const std::string &name = l.tostring(index);
60   auto iter = settings->find(name);
61   if (iter == settings->end()) {
62     NORM_ERR("Unknown setting '%s'", name.c_str());
63     return nullptr;
64   }
65 
66   return iter->second;
67 }
68 
69 // returns a vector of all settings, sorted in order of registration
make_settings_vector()70 settings_vector make_settings_vector() {
71   settings_vector ret;
72   ret.reserve(settings->size());
73 
74   for (auto &setting : *settings) { ret.push_back(setting.second); }
75   sort(ret.begin(), ret.end(), &priv::config_setting_base::seq_compare);
76 
77   return ret;
78 }
79 
80 /*
81  * Returns the seq_no for the new setting object. Also constructs settings
82  * object if needed.
83  */
get_next_seq_no()84 size_t get_next_seq_no() {
85   struct settings_constructor {
86     settings_constructor() { settings = new settings_map; }
87     ~settings_constructor() {
88       delete settings;
89       settings = nullptr;
90     }
91   };
92   static settings_constructor constructor;
93 
94   return settings->size();
95 }
96 }  // namespace
97 
98 namespace priv {
99 
config_setting_base(std::string name_)100 config_setting_base::config_setting_base(std::string name_)
101     : name(std::move(name_)), seq_no(get_next_seq_no()) {
102   bool inserted = settings->insert({name, this}).second;
103   if (!inserted) {
104     throw std::logic_error("Setting with name '" + name +
105                            "' already registered");
106   }
107 }
108 
lua_set(lua::state & l)109 void config_setting_base::lua_set(lua::state &l) {
110   std::lock_guard<lua::state> guard(l);
111   lua::stack_sentry s(l, -1);
112   l.checkstack(2);
113 
114   l.getglobal("conky");
115   l.rawgetfield(-1, "config");
116   l.replace(-2);
117   l.insert(-2);
118 
119   l.setfield(-2, name.c_str());
120   l.pop();
121 }
122 
123 /*
124  * Performs the actual assignment of settings. Calls the setting-specific setter
125  * after some sanity-checking. stack on entry: | ..., new_config_table, key,
126  * value, old_value | stack on exit:  | ..., new_config_table |
127  */
process_setting(lua::state & l,bool init)128 void config_setting_base::process_setting(lua::state &l, bool init) {
129   lua::stack_sentry s(l, -3);
130 
131   config_setting_base *ptr = get_setting(l, -3);
132   if (ptr == nullptr) { return; }
133 
134   ptr->lua_setter(l, init);
135   l.pushvalue(-2);
136   l.insert(-2);
137   l.rawset(-4);
138 }
139 
140 /*
141  * Called when user sets a new value for a setting
142  * stack on entry: | config_table, key, value |
143  * stack on exit:  | |
144  */
config__newindex(lua::state * l)145 int config_setting_base::config__newindex(lua::state *l) {
146   lua::stack_sentry s(*l, -3);
147   l->checkstack(1);
148 
149   l->getmetatable(-3);
150   l->replace(-4);
151 
152   l->pushvalue(-2);
153   l->rawget(-4);
154   process_setting(*l, false);
155 
156   return 0;
157 }
158 
159 /*
160  * conky.config will not be a table, but a userdata with some metamethods we do
161  * this because we want to control access to the settings we use the metatable
162  * for storing the settings, that means having a setting whose name starts with
163  * "__" is a bad idea stack on entry: | ... | stack on exit:  | ...
164  * new_config_table |
165  */
make_conky_config(lua::state & l)166 void config_setting_base::make_conky_config(lua::state &l) {
167   lua::stack_sentry s(l);
168   l.checkstack(3);
169 
170   l.newuserdata(1);
171 
172   l.newtable();
173   {
174     l.pushboolean(false);
175     l.rawsetfield(-2, "__metatable");
176 
177     l.pushvalue(-1);
178     l.rawsetfield(-2, "__index");
179 
180     l.pushfunction(&priv::config_setting_base::config__newindex);
181     l.rawsetfield(-2, "__newindex");
182   }
183   l.setmetatable(-2);
184 
185   ++s;
186 }
187 }  // namespace priv
188 
set_config_settings(lua::state & l)189 void set_config_settings(lua::state &l) {
190   lua::stack_sentry s(l);
191   l.checkstack(6);
192 
193   // Force creation of settings map. In the off chance we have no settings.
194   get_next_seq_no();
195 
196   l.getglobal("conky");
197   {
198     if (l.type(-1) != lua::TTABLE) {
199       throw std::runtime_error("conky must be a table");
200     }
201 
202     l.rawgetfield(-1, "config");
203     {
204       if (l.type(-1) != lua::TTABLE) {
205         throw std::runtime_error("conky.config must be a table");
206       }
207 
208       priv::config_setting_base::make_conky_config(l);
209       l.rawsetfield(-3, "config");
210 
211       l.rawgetfield(-2, "config");
212       l.getmetatable(-1);
213       l.replace(-2);
214       {
215         const settings_vector &v = make_settings_vector();
216 
217         for (auto i : v) {
218           l.pushstring(i->name);
219           l.rawgetfield(-3, i->name.c_str());
220           l.pushnil();
221           priv::config_setting_base::process_setting(l, true);
222         }
223       }
224       l.pop();
225 
226       // print error messages for unknown settings
227       l.pushnil();
228       while (l.next(-2)) {
229         l.pop();
230         get_setting(l, -1);
231       }
232     }
233     l.pop();
234   }
235   l.pop();
236 }
237 
cleanup_config_settings(lua::state & l)238 void cleanup_config_settings(lua::state &l) {
239   lua::stack_sentry s(l);
240   l.checkstack(2);
241 
242   l.getglobal("conky");
243   l.rawgetfield(-1, "config");
244   l.replace(-2);
245 
246   const settings_vector &v = make_settings_vector();
247   for (size_t i = v.size(); i > 0; --i) {
248     l.getfield(-1, v[i - 1]->name.c_str());
249     v[i - 1]->cleanup(l);
250   }
251 
252   l.pop();
253 }
254 
255 /////////// example settings, remove after real settings are available ///////
256 range_config_setting<int> asdf("asdf", 42, 47, 45, true);
257 }  // namespace conky
258