6189598d33b38c26e52c143ca8227f90fc64e023
[steamworks] / src / pulley / pulleyscript / backend.cpp
1 /*
2 Copyright (c) 2016 InternetWide.org and the ARPA2.net project
3 All rights reserved. See file LICENSE for exact terms (2-clause BSD license).
4
5 Adriaan de Groot <groot@kde.org>
6 */
7
8 #include "backend.h"
9 #include "parserpp.h"
10 #include "../pulleyback.h"
11
12 #include <assert.h>
13 #include <dlfcn.h>
14 #include <stdio.h>
15
16 #include <unordered_map>
17 #include <string>
18
19 #include "logger.h"
20
21 #ifndef PULLEY_BACKEND_DIR
22 #ifndef PREFIX
23 #define PREFIX "/usr/local"
24 #endif
25 #define PULLEY_BACKEND_DIR PREFIX "/share/steamworks/pulleyback/"
26 #endif
27
28 static const char plugindir[] = PULLEY_BACKEND_DIR;
29
30 static_assert(sizeof(plugindir) > 1, "Prefix / plugin directory path is weird.");
31
32 // static_assert(plugindir[0] == '/', "Backend must be absolute dir.");
33 // static_assert(plugindir[sizeof(plugindir)-1] == '/', "Backend must end in /.");
34
35
36 /**
37  * Helper class when loading the API from a DLL. Uses dlfunc()
38  * to resolve functions and stores them in referenced function
39  * pointers. If any of the name-resolvings fails, the referenced
40  * boolean valid is set to false. Use multiple FuncKeepers
41  * all referencing the same valid bool to load an entire
42  * API and check if it's valid.
43  */
44 template<typename funcptr> class FuncKeeper {
45 private:
46         bool& m_valid;
47         funcptr*& m_func;
48 public:
49         FuncKeeper(void *handle, const char *name, bool& valid, funcptr*& func) :
50                 m_valid(valid),
51                 m_func(func)
52         {
53                 dlfunc_t f = dlfunc(handle, name);
54                 if (f)
55                 {
56                         func = reinterpret_cast<funcptr*>(f);
57                 }
58                 else
59                 {
60                         func = nullptr;
61                         auto& log = SteamWorks::Logging::getLogger("steamworks.pulleyback");
62                         log.warnStream() << "Function " << name << " not found.";
63                         valid = false;
64                 }
65         }
66
67         ~FuncKeeper()
68         {
69                 if (!m_valid)
70                 {
71                         m_func = nullptr;
72                 }
73         }
74 } ;
75
76
77 class SteamWorks::PulleyBack::Loader::Private
78 {
79 private:
80         std::string m_name;
81         bool m_valid;
82         void *m_handle;
83
84 public:
85         decltype(pulleyback_open)* m_pulleyback_open;
86         decltype(pulleyback_close)* m_pulleyback_close;
87         decltype(pulleyback_add)* m_pulleyback_add;
88         decltype(pulleyback_del)* m_pulleyback_del;
89         decltype(pulleyback_reset)* m_pulleyback_reset;
90         decltype(pulleyback_prepare)* m_pulleyback_prepare;  // OPTIONAL
91         decltype(pulleyback_commit)* m_pulleyback_commit;
92         decltype(pulleyback_rollback)* m_pulleyback_rollback;
93         decltype(pulleyback_collaborate) *m_pulleyback_collaborate;
94
95         Private(const std::string& name) :
96                 m_name(name),
97                 m_valid(false),
98                 m_handle(nullptr)
99         {
100                 auto& log = SteamWorks::Logging::getLogger("steamworks.pulleyback");
101                 log.debugStream() << "Trying to load backend '" << name << '\'';
102
103 #ifdef NO_SECURITY
104                 char soname[128];
105                 snprintf(soname, sizeof(soname), "%s", name.c_str());
106 #else
107                 assert(plugindir[0] == '/');
108                 assert(plugindir[sizeof(plugindir)-2] == '/');  // -1 is the NUL, -2 is trailing /
109
110                 if (name.find('/') != std::string::npos)
111                 {
112                         log.errorStream() << "  .. illegal / in backend-name.";
113                         return;
114                 }
115
116                 char soname[sizeof(plugindir) + 128];
117                 snprintf(soname, sizeof(soname), "%spulleyback_%s.so", plugindir, name.c_str());
118 #endif
119
120                 soname[sizeof(soname)-1] = 0;
121                 log.debugStream() << "Trying to load '" << soname << '\'';
122
123                 m_handle = dlopen(soname, RTLD_NOW | RTLD_LOCAL);
124                 if (m_handle == nullptr)
125                 {
126                         log.errorStream() << "  .. could not load backend shared-object. " << dlerror();
127                         return;
128                 }
129
130                 m_valid = true;
131                 // As far as we know .. the FuncKeepers can modify it. Put them in a block
132                 // so that their destructors have run before we (possibly) dlclose the
133                 // shared library their function-pointers point into.
134                 {
135                         bool optionalvalid = true;
136
137                         FuncKeeper<decltype(pulleyback_open)> fk0(m_handle, "pulleyback_open", m_valid, m_pulleyback_open);
138                         FuncKeeper<decltype(pulleyback_close)> fk1(m_handle, "pulleyback_close", m_valid, m_pulleyback_close);
139                         FuncKeeper<decltype(pulleyback_add)> fk2(m_handle, "pulleyback_add", m_valid, m_pulleyback_add);
140                         FuncKeeper<decltype(pulleyback_del)> fk3(m_handle, "pulleyback_del", m_valid, m_pulleyback_del);
141                         FuncKeeper<decltype(pulleyback_reset)> fk4(m_handle, "pulleyback_reset", m_valid, m_pulleyback_reset);
142                         FuncKeeper<decltype(pulleyback_prepare)> fk5(m_handle, "pulleyback_prepare", optionalvalid, m_pulleyback_prepare);
143                         FuncKeeper<decltype(pulleyback_commit)> fk6(m_handle, "pulleyback_commit", m_valid, m_pulleyback_commit);
144                         FuncKeeper<decltype(pulleyback_rollback)> fk7(m_handle, "pulleyback_rollback", m_valid, m_pulleyback_rollback);
145                         FuncKeeper<decltype(pulleyback_collaborate)> fk8(m_handle, "pulleyback_collaborate", m_valid, m_pulleyback_collaborate);
146
147                         optionalvalid &= m_valid;  // If any required func missing, the optionals are invalid too
148                 }
149
150                 // If any function has not been resolved, the FuncKeeper will have set m_valid to false
151                 if (!m_valid)
152                 {
153                         dlclose(m_handle);
154                         m_handle = nullptr;
155                 }
156         }
157
158         ~Private()
159         {
160                 auto& log = SteamWorks::Logging::getLogger("steamworks.pulleyback");
161                 log.debugStream() << "Unloaded priv " << name();
162
163                 if (m_handle != nullptr)
164                 {
165                         dlclose(m_handle);
166                         m_handle = nullptr;
167                 }
168                 m_valid = false;
169         }
170
171         bool is_valid() const { return m_valid; }
172         std::string name() const { return m_name; }
173
174         static std::shared_ptr<Private> get_loader_private(const std::string& name);
175 } ;
176
177
178 std::shared_ptr< SteamWorks::PulleyBack::Loader::Private > SteamWorks::PulleyBack::Loader::Private::get_loader_private(const std::string& name)
179 {
180         static std::unordered_map<std::string, std::weak_ptr<SteamWorks::PulleyBack::Loader::Private> > loaders;
181
182         auto p = loaders[name].lock();
183         if (!p)
184         {
185                 p = std::make_shared<SteamWorks::PulleyBack::Loader::Private>(name);
186                 loaders[name] = p;
187         }
188
189         return p;
190 }
191
192 SteamWorks::PulleyBack::Loader::Loader(const std::string& name) :
193         d(Private::get_loader_private(name))
194 {
195         auto& log = SteamWorks::Logging::getLogger("steamworks.pulleyback");
196         log.debugStream() << "Loaded " << name << " valid? " << (d->is_valid() ? "yes" : "no");
197         log.debugStream() << "  .. open @" << (void *)d->m_pulleyback_open << " close @" << (void *)d->m_pulleyback_close;
198         log.debugStream() << "  .. add @" << (void *)d->m_pulleyback_add;
199 }
200
201 SteamWorks::PulleyBack::Loader::~Loader()
202 {
203         auto& log = SteamWorks::Logging::getLogger("steamworks.pulleyback");
204         log.debugStream() << "Unloaded " << d->name();
205 }
206
207 SteamWorks::PulleyBack::Instance SteamWorks::PulleyBack::Loader::get_instance(int argc, char** argv, int varc)
208 {
209         return Instance(d, argc, argv, varc);
210 }
211
212 SteamWorks::PulleyBack::Instance SteamWorks::PulleyBack::Loader::get_instance(SteamWorks::PulleyScript::BackendParameters& parameters)
213 {
214         return Instance(d, parameters.argc, parameters.argv, parameters.varc);
215 }
216
217 bool SteamWorks::PulleyBack::Loader::is_valid() const
218 {
219         return d->is_valid();
220 }
221
222
223
224 SteamWorks::PulleyBack::Instance::Instance(std::shared_ptr<Loader::Private>& parent_d, int argc, char** argv, int varc) :
225         d(parent_d),
226         m_handle(nullptr)
227 {
228         if (d->is_valid())
229         {
230                 m_handle = d->m_pulleyback_open(argc, argv, varc);
231                 auto& log = SteamWorks::Logging::getLogger("steamworks.pulleyback");
232                 log.debugStream() << "Got instance handle @" << m_handle;
233         }
234 }
235
236 SteamWorks::PulleyBack::Instance::~Instance()
237 {
238         if (m_handle and d->is_valid())
239         {
240                 d->m_pulleyback_close(m_handle);
241         }
242 }
243
244 bool SteamWorks::PulleyBack::Instance::is_valid() const
245 {
246         return m_handle && d->is_valid();
247 }
248
249 std::string SteamWorks::PulleyBack::Instance::name() const
250 {
251         return d->name();
252 }
253
254 int SteamWorks::PulleyBack::Instance::add(der_t* forkdata)
255 {
256         if (d->is_valid())
257         {
258                 auto& log = SteamWorks::Logging::getLogger("steamworks.pulleyback");
259                 log.debugStream() << "Calling into instance " << name() << " add@" << (void *)d->m_pulleyback_add << " handle@" << m_handle;
260                 return d->m_pulleyback_add(m_handle, forkdata);
261         }
262         return 0;
263 }
264
265 int SteamWorks::PulleyBack::Instance::del(der_t* forkdata)
266 {
267         if (d->is_valid())
268         {
269                 auto& log = SteamWorks::Logging::getLogger("steamworks.pulleyback");
270                 log.debugStream() << "Calling into instance " << name() << " del@" << (void *)d->m_pulleyback_del << " handle@" << m_handle;
271                 return d->m_pulleyback_del(m_handle, forkdata);
272         }
273         return 0;
274 }