1 /*
2  * peas-lua-utils.c
3  * This file is part of libpeas
4  *
5  * Copyright (C) 2014-2015 - Garrett Regier
6  *
7  * libpeas is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * libpeas is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include "peas-lua-utils.h"
27 
28 #include <gio/gio.h>
29 
30 #include <lauxlib.h>
31 
32 
33 gboolean
peas_lua_utils_require(lua_State * L,const gchar * name)34 peas_lua_utils_require (lua_State   *L,
35                         const gchar *name)
36 {
37   luaL_checkstack (L, 2, "");
38 
39   lua_getglobal (L, "require");
40   lua_pushstring (L, name);
41 
42   if (lua_pcall (L, 1, 1, 0) != 0)
43     {
44       g_warning ("Error failed to load Lua module '%s': %s",
45                  name, lua_tostring (L, -1));
46 
47       /* Pop error */
48       lua_pop (L, 1);
49       return FALSE;
50     }
51 
52   if (!lua_istable (L, -1))
53     {
54       g_warning ("Error invalid Lua module for '%s': "
55                  "expected table, got: %s",
56                  name, lua_tostring (L, -1));
57 
58       /* Pop the module's table */
59       lua_pop (L, 1);
60       return FALSE;
61     }
62 
63   return TRUE;
64 }
65 
66 gboolean
peas_lua_utils_check_version(lua_State * L,guint req_major,guint req_minor,guint req_micro)67 peas_lua_utils_check_version (lua_State *L,
68                               guint      req_major,
69                               guint      req_minor,
70                               guint      req_micro)
71 {
72   const gchar *version_str;
73   gchar **version_str_parts;
74   gint n_version_parts;
75   gint64 *version_parts;
76   gint i;
77   gboolean success = FALSE;
78 
79   lua_getfield (L, -1, "_VERSION");
80   version_str = lua_tostring (L, -1);
81 
82   version_str_parts = g_strsplit (version_str, ".", 0);
83 
84   n_version_parts = g_strv_length (version_str_parts);
85   version_parts = g_newa (gint64, n_version_parts);
86 
87   for (i = 0; i < n_version_parts; ++i)
88     {
89       gchar *end;
90 
91       version_parts[i] = g_ascii_strtoll (version_str_parts[i], &end, 10);
92 
93       if (*end != '\0' ||
94           version_parts[i] < 0 ||
95           version_parts[i] == G_MAXINT64)
96         {
97           g_warning ("Invalid version string: %s", version_str);
98           goto error;
99         }
100     }
101 
102   if (n_version_parts < 3 ||
103       version_parts[0] != req_major ||
104       version_parts[1] < req_minor ||
105       (version_parts[1] == req_minor && version_parts[2] < req_micro))
106     {
107       g_warning ("Version mismatch %d.%d.%d is required, found %s",
108                  req_major, req_minor, req_micro, version_str);
109       goto error;
110     }
111 
112   success = TRUE;
113 
114 error:
115 
116   /* Pop _VERSION */
117   lua_pop (L, 1);
118 
119   g_strfreev (version_str_parts);
120   return success;
121 }
122 
123 static gint
traceback(lua_State * L)124 traceback (lua_State *L)
125 {
126   /* Always ignore an error that isn't a string */
127   if (!lua_isstring (L, 1))
128     return 1;
129 
130   lua_getglobal (L, "debug");
131   if (!lua_istable (L, -1))
132     {
133       lua_pop (L, 1);
134       return 1;
135     }
136 
137   lua_getfield (L, -1, "traceback");
138   if (!lua_isfunction (L, -1))
139     {
140       lua_pop (L, 2);
141       return 1;
142     }
143 
144   /* Replace debug with traceback */
145   lua_replace (L, -2);
146 
147   /* Push the error */
148   lua_pushvalue (L, 1);
149 
150   /* Skip this function when generating the traceback */
151   lua_pushinteger (L, 2);
152 
153   /* If we fail we have a new error object... */
154   lua_pcall (L, 2, 1, 0);
155   return 1;
156 }
157 
158 gboolean
peas_lua_utils_call(lua_State * L,guint n_args,guint n_results)159 peas_lua_utils_call (lua_State *L,
160                      guint      n_args,
161                      guint      n_results)
162 {
163   gboolean success;
164 
165   /* Push the error function */
166   lua_pushcfunction (L, traceback);
167 
168   /* Move traceback to before the arguments */
169   lua_insert (L, -2 - n_args);
170 
171   success = lua_pcall (L, n_args, n_results, -2 - n_args) == 0;
172 
173   /* Remove traceback */
174   lua_remove (L, -1 - (success ? n_results : 1));
175   return success;
176 }
177 
178 gboolean
peas_lua_utils_load_resource(lua_State * L,const gchar * name,guint n_args,guint n_results)179 peas_lua_utils_load_resource (lua_State   *L,
180                               const gchar *name,
181                               guint        n_args,
182                               guint        n_results)
183 {
184   gchar *resource_path;
185   GBytes *lua_resource;
186   const gchar *code;
187   gsize code_len;
188   gchar *lua_filename;
189 
190   /* We don't use the byte-compiled Lua source
191    * because glib-compile-resources cannot output
192    * depends for generated files.
193    *
194    * There are also concerns that the bytecode is
195    * not stable enough between different Lua versions.
196    *
197    * https://bugzilla.gnome.org/show_bug.cgi?id=673101
198    */
199   resource_path = g_strconcat ("/org/gnome/libpeas/loaders/lua5.1/",
200                                name, NULL);
201   lua_resource = g_resources_lookup_data (resource_path,
202                                           G_RESOURCE_LOOKUP_FLAGS_NONE,
203                                           NULL);
204   g_free (resource_path);
205 
206   if (lua_resource == NULL)
207     {
208       g_warning ("Failed to find '%s' resource", name);
209       return FALSE;
210     }
211 
212   code = g_bytes_get_data (lua_resource, &code_len);
213 
214   /* Filenames are prefixed with '@' */
215   lua_filename = g_strconcat ("@peas-lua-", name, NULL);
216 
217   if (luaL_loadbuffer (L, code, code_len, lua_filename) != 0)
218     {
219       g_warning ("Failed to load '%s' resource: %s",
220                  name, lua_tostring (L, -1));
221 
222       /* Pop error */
223       lua_pop (L, 1);
224       g_free (lua_filename);
225       g_bytes_unref (lua_resource);
226       return FALSE;
227     }
228 
229   g_free (lua_filename);
230   g_bytes_unref (lua_resource);
231 
232   if (!peas_lua_utils_call (L, n_args, n_results))
233     {
234       g_warning ("Failed to run '%s' resource: %s",
235                  name, lua_tostring (L, -1));
236 
237       /* Pop error */
238       lua_pop (L, 1);
239       return FALSE;
240     }
241 
242   return TRUE;
243 }
244