]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | #include <lua.hpp> |
2 | #include "services/svc_zone.h" | |
3 | #include "services/svc_sys_obj.h" | |
4 | #include "common/dout.h" | |
5 | #include "rgw_lua_utils.h" | |
6 | #include "rgw_sal_rados.h" | |
7 | #include "rgw_lua.h" | |
8 | #ifdef WITH_RADOSGW_LUA_PACKAGES | |
20effc67 | 9 | #include <filesystem> |
f67539c2 | 10 | #include <boost/process.hpp> |
f67539c2 TL |
11 | #include "rgw_lua_version.h" |
12 | #endif | |
13 | ||
14 | #define dout_subsys ceph_subsys_rgw | |
15 | ||
16 | namespace rgw::lua { | |
17 | ||
18 | context to_context(const std::string& s) | |
19 | { | |
20 | if (strcasecmp(s.c_str(), "prerequest") == 0) { | |
21 | return context::preRequest; | |
22 | } | |
23 | if (strcasecmp(s.c_str(), "postrequest") == 0) { | |
24 | return context::postRequest; | |
25 | } | |
26 | return context::none; | |
27 | } | |
28 | ||
29 | std::string to_string(context ctx) | |
30 | { | |
31 | switch (ctx) { | |
32 | case context::preRequest: | |
33 | return "prerequest"; | |
34 | case context::postRequest: | |
35 | return "postrequest"; | |
36 | case context::none: | |
37 | break; | |
38 | } | |
39 | return "none"; | |
40 | } | |
41 | ||
42 | bool verify(const std::string& script, std::string& err_msg) | |
43 | { | |
44 | lua_State *L = luaL_newstate(); | |
45 | lua_state_guard guard(L); | |
46 | open_standard_libs(L); | |
47 | try { | |
48 | if (luaL_loadstring(L, script.c_str()) != LUA_OK) { | |
49 | err_msg.assign(lua_tostring(L, -1)); | |
50 | return false; | |
51 | } | |
52 | } catch (const std::runtime_error& e) { | |
53 | err_msg = e.what(); | |
54 | return false; | |
55 | } | |
56 | err_msg = ""; | |
57 | return true; | |
58 | } | |
59 | ||
60 | std::string script_oid(context ctx, const std::string& tenant) { | |
61 | static const std::string SCRIPT_OID_PREFIX("script."); | |
62 | return SCRIPT_OID_PREFIX + to_string(ctx) + "." + tenant; | |
63 | } | |
64 | ||
65 | ||
20effc67 | 66 | int read_script(const DoutPrefixProvider *dpp, rgw::sal::Store* store, const std::string& tenant, optional_yield y, context ctx, std::string& script) |
f67539c2 | 67 | { |
20effc67 | 68 | auto lua_script = store->get_lua_script_manager(); |
f67539c2 | 69 | |
20effc67 | 70 | return lua_script->get(dpp, y, script_oid(ctx, tenant), script); |
f67539c2 TL |
71 | } |
72 | ||
20effc67 | 73 | int write_script(const DoutPrefixProvider *dpp, rgw::sal::Store* store, const std::string& tenant, optional_yield y, context ctx, const std::string& script) |
f67539c2 | 74 | { |
20effc67 | 75 | auto lua_script = store->get_lua_script_manager(); |
f67539c2 | 76 | |
20effc67 | 77 | return lua_script->put(dpp, y, script_oid(ctx, tenant), script); |
f67539c2 TL |
78 | } |
79 | ||
20effc67 | 80 | int delete_script(const DoutPrefixProvider *dpp, rgw::sal::Store* store, const std::string& tenant, optional_yield y, context ctx) |
f67539c2 | 81 | { |
20effc67 | 82 | auto lua_script = store->get_lua_script_manager(); |
f67539c2 | 83 | |
20effc67 | 84 | return lua_script->del(dpp, y, script_oid(ctx, tenant)); |
f67539c2 TL |
85 | } |
86 | ||
87 | #ifdef WITH_RADOSGW_LUA_PACKAGES | |
88 | ||
89 | const std::string PACKAGE_LIST_OBJECT_NAME = "lua_package_allowlist"; | |
90 | ||
91 | namespace bp = boost::process; | |
92 | ||
20effc67 TL |
93 | int add_package(const DoutPrefixProvider *dpp, rgw::sal::RadosStore* store, optional_yield y, const std::string& package_name, bool allow_compilation) { |
94 | // verify that luarocks can load this package | |
f67539c2 TL |
95 | const auto p = bp::search_path("luarocks"); |
96 | if (p.empty()) { | |
97 | return -ECHILD; | |
98 | } | |
99 | bp::ipstream is; | |
100 | const auto cmd = p.string() + " search --porcelain" + (allow_compilation ? " " : " --binary ") + package_name; | |
101 | bp::child c(cmd, | |
102 | bp::std_in.close(), | |
103 | bp::std_err > bp::null, | |
104 | bp::std_out > is); | |
105 | ||
106 | std::string line; | |
107 | bool package_found = false; | |
108 | while (c.running() && std::getline(is, line) && !line.empty()) { | |
109 | package_found = true; | |
110 | } | |
111 | c.wait(); | |
112 | auto ret = c.exit_code(); | |
113 | if (ret) { | |
114 | return -ret; | |
115 | } | |
116 | ||
117 | if (!package_found) { | |
118 | return -EINVAL; | |
119 | } | |
120 | ||
20effc67 TL |
121 | //replace previous versions of the package |
122 | const std::string package_name_no_version = package_name.substr(0, package_name.find(" ")); | |
123 | ret = remove_package(dpp, store, y, package_name_no_version); | |
124 | if (ret < 0) { | |
125 | return ret; | |
126 | } | |
127 | ||
f67539c2 TL |
128 | // add package to list |
129 | const bufferlist empty_bl; | |
130 | std::map<std::string, bufferlist> new_package{{package_name, empty_bl}}; | |
131 | librados::ObjectWriteOperation op; | |
132 | op.omap_set(new_package); | |
20effc67 | 133 | ret = rgw_rados_operate(dpp, *(store->getRados()->get_lc_pool_ctx()), |
f67539c2 TL |
134 | PACKAGE_LIST_OBJECT_NAME, &op, y); |
135 | ||
136 | if (ret < 0) { | |
137 | return ret; | |
138 | } | |
139 | return 0; | |
140 | } | |
141 | ||
20effc67 | 142 | int remove_package(const DoutPrefixProvider *dpp, rgw::sal::RadosStore* store, optional_yield y, const std::string& package_name) { |
f67539c2 | 143 | librados::ObjectWriteOperation op; |
20effc67 TL |
144 | size_t pos = package_name.find(" "); |
145 | if (pos != package_name.npos) { | |
146 | // remove specfic version of the the package | |
147 | op.omap_rm_keys(std::set<std::string>({package_name})); | |
148 | auto ret = rgw_rados_operate(dpp, *(store->getRados()->get_lc_pool_ctx()), | |
149 | PACKAGE_LIST_OBJECT_NAME, &op, y); | |
150 | if (ret < 0) { | |
151 | return ret; | |
152 | } | |
153 | return 0; | |
154 | } | |
155 | // otherwise, remove any existing versions of the package | |
156 | packages_t packages; | |
157 | auto ret = list_packages(dpp, store, y, packages); | |
158 | if (ret < 0 && ret != -ENOENT) { | |
f67539c2 TL |
159 | return ret; |
160 | } | |
20effc67 TL |
161 | for(const auto& package : packages) { |
162 | const std::string package_no_version = package.substr(0, package.find(" ")); | |
163 | if (package_no_version.compare(package_name) == 0) { | |
164 | op.omap_rm_keys(std::set<std::string>({package})); | |
165 | ret = rgw_rados_operate(dpp, *(store->getRados()->get_lc_pool_ctx()), | |
166 | PACKAGE_LIST_OBJECT_NAME, &op, y); | |
167 | if (ret < 0) { | |
168 | return ret; | |
169 | } | |
170 | } | |
171 | } | |
f67539c2 TL |
172 | return 0; |
173 | } | |
174 | ||
20effc67 | 175 | int list_packages(const DoutPrefixProvider *dpp, rgw::sal::RadosStore* store, optional_yield y, packages_t& packages) { |
f67539c2 TL |
176 | constexpr auto max_chunk = 1024U; |
177 | std::string start_after; | |
178 | bool more = true; | |
179 | int rval; | |
180 | while (more) { | |
181 | librados::ObjectReadOperation op; | |
182 | packages_t packages_chunk; | |
183 | op.omap_get_keys2(start_after, max_chunk, &packages_chunk, &more, &rval); | |
b3b6e05e | 184 | const auto ret = rgw_rados_operate(dpp, *(store->getRados()->get_lc_pool_ctx()), |
f67539c2 TL |
185 | PACKAGE_LIST_OBJECT_NAME, &op, nullptr, y); |
186 | ||
187 | if (ret < 0) { | |
188 | return ret; | |
189 | } | |
190 | ||
191 | packages.merge(packages_chunk); | |
192 | } | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
20effc67 | 197 | int install_packages(const DoutPrefixProvider *dpp, rgw::sal::RadosStore* store, optional_yield y, packages_t& failed_packages, std::string& output) { |
f67539c2 | 198 | // luarocks directory cleanup |
20effc67 | 199 | std::error_code ec; |
f67539c2 | 200 | const auto& luarocks_path = store->get_luarocks_path(); |
20effc67 TL |
201 | if (std::filesystem::remove_all(luarocks_path, ec) |
202 | == static_cast<std::uintmax_t>(-1) && | |
203 | ec != std::errc::no_such_file_or_directory) { | |
f67539c2 TL |
204 | output.append("failed to clear luarock directory: "); |
205 | output.append(ec.message()); | |
206 | output.append("\n"); | |
207 | return ec.value(); | |
208 | } | |
209 | ||
210 | packages_t packages; | |
b3b6e05e | 211 | auto ret = list_packages(dpp, store, y, packages); |
f67539c2 TL |
212 | if (ret == -ENOENT) { |
213 | // allowlist is empty | |
214 | return 0; | |
215 | } | |
216 | if (ret < 0) { | |
217 | return ret; | |
218 | } | |
219 | // verify that luarocks exists | |
220 | const auto p = bp::search_path("luarocks"); | |
221 | if (p.empty()) { | |
222 | return -ECHILD; | |
223 | } | |
224 | ||
225 | // the lua rocks install dir will be created by luarocks the first time it is called | |
226 | for (const auto& package : packages) { | |
227 | bp::ipstream is; | |
20effc67 TL |
228 | const auto cmd = p.string() + " install --lua-version " + CEPH_LUA_VERSION + " --tree " + luarocks_path + " --deps-mode one " + package; |
229 | bp::child c(cmd, bp::std_in.close(), (bp::std_err & bp::std_out) > is); | |
f67539c2 TL |
230 | |
231 | // once package reload is supported, code should yield when reading output | |
20effc67 | 232 | std::string line = std::string("CMD: ") + cmd; |
f67539c2 TL |
233 | |
234 | do { | |
235 | if (!line.empty()) { | |
236 | output.append(line); | |
237 | output.append("\n"); | |
238 | } | |
239 | } while (c.running() && std::getline(is, line)); | |
240 | ||
241 | c.wait(); | |
242 | if (c.exit_code()) { | |
243 | failed_packages.insert(package); | |
244 | } | |
245 | } | |
246 | ||
247 | return 0; | |
248 | } | |
249 | ||
250 | #endif | |
251 | ||
252 | } | |
253 |