Line data Source code
1 : /*
2 : * Copyright (C) 2017 Red Hat, Inc.
3 : *
4 : * This library is free software; you can redistribute it and/or
5 : * modify it under the terms of the GNU Lesser General Public
6 : * License as published by the Free Software Foundation; either
7 : * version 2.1 of the License, or (at your option) any later version.
8 : *
9 : * This library is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : * Lesser General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU Lesser General Public
15 : * License along with this library; if not, see <http://www.gnu.org/licenses/>.
16 : *
17 : * Author: Vojtech Trefny <vtrefny@redhat.com>
18 : */
19 :
20 : #include <glib.h>
21 : #include <glib/gprintf.h>
22 : #include <libkmod.h>
23 : #include <string.h>
24 : #include <syslog.h>
25 : #include <locale.h>
26 : #include <sys/utsname.h>
27 :
28 : #include "module.h"
29 : #include "exec.h"
30 : #include "logging.h"
31 :
32 :
33 : /**
34 : * bd_utils_module_error_quark: (skip)
35 : */
36 5 : GQuark bd_utils_module_error_quark (void)
37 : {
38 5 : return g_quark_from_static_string ("g-bd-utils-module-error-quark");
39 : }
40 :
41 678 : static void utils_kmod_log_redirect (void *log_data G_GNUC_UNUSED, int priority,
42 : const char *file G_GNUC_UNUSED, int line G_GNUC_UNUSED,
43 : const char *fn G_GNUC_UNUSED, const char *format,
44 : va_list args) {
45 678 : gchar *kmod_msg = NULL;
46 678 : gchar *message = NULL;
47 678 : gint ret = 0;
48 :
49 678 : ret = g_vasprintf (&kmod_msg, format, args);
50 678 : if (ret < 0) {
51 0 : g_free (kmod_msg);
52 0 : return;
53 : }
54 :
55 : #ifdef DEBUG
56 : message = g_strdup_printf ("[libmkod] %s:%d %s() %s", file, line, fn, kmod_msg);
57 : #else
58 678 : message = g_strdup_printf ("[libmkod] %s", kmod_msg);
59 : #endif
60 678 : bd_utils_log (priority, message);
61 :
62 678 : g_free (kmod_msg);
63 678 : g_free (message);
64 :
65 : }
66 :
67 339 : static void set_kmod_logging (struct kmod_ctx *ctx) {
68 : #ifdef DEBUG
69 : kmod_set_log_priority (ctx, LOG_DEBUG);
70 : #else
71 339 : kmod_set_log_priority (ctx, LOG_INFO);
72 : #endif
73 339 : kmod_set_log_fn (ctx, utils_kmod_log_redirect, NULL);
74 339 : }
75 :
76 : /**
77 : * bd_utils_have_kernel_module:
78 : * @module_name: name of the kernel module to check
79 : * @error: (out) (optional): place to store error (if any)
80 : *
81 : * Returns: whether the @module_name was found in the system, either as a module
82 : * or built-in in the kernel
83 : */
84 335 : gboolean bd_utils_have_kernel_module (const gchar *module_name, GError **error) {
85 335 : gint ret = 0;
86 335 : struct kmod_ctx *ctx = NULL;
87 335 : struct kmod_module *mod = NULL;
88 335 : struct kmod_list *list = NULL;
89 335 : gchar *null_config = NULL;
90 335 : const gchar *path = NULL;
91 335 : gboolean have_path = FALSE;
92 335 : gboolean builtin = FALSE;
93 335 : locale_t c_locale = newlocale (LC_ALL_MASK, "C", (locale_t) 0);
94 :
95 335 : ctx = kmod_new (NULL, (const gchar * const*) &null_config);
96 335 : if (!ctx) {
97 0 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_KMOD_INIT_FAIL,
98 : "Failed to initialize kmod context");
99 0 : freelocale (c_locale);
100 0 : return FALSE;
101 : }
102 335 : set_kmod_logging (ctx);
103 :
104 335 : ret = kmod_module_new_from_lookup (ctx, module_name, &list);
105 335 : if (ret < 0) {
106 1 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_FAIL,
107 1 : "Failed to get the module: %s", strerror_l (-ret, c_locale));
108 1 : kmod_unref (ctx);
109 1 : kmod_module_unref_list (list);
110 1 : freelocale (c_locale);
111 1 : return FALSE;
112 : }
113 :
114 334 : if (list == NULL) {
115 1 : kmod_unref (ctx);
116 1 : freelocale (c_locale);
117 1 : return FALSE;
118 : }
119 :
120 333 : mod = kmod_module_get_module (list);
121 333 : path = kmod_module_get_path (mod);
122 333 : have_path = (path != NULL) && (g_strcmp0 (path, "") != 0);
123 333 : if (!have_path) {
124 143 : builtin = kmod_module_get_initstate (mod) == KMOD_MODULE_BUILTIN;
125 : }
126 333 : kmod_module_unref (mod);
127 333 : kmod_module_unref_list (list);
128 333 : kmod_unref (ctx);
129 333 : freelocale (c_locale);
130 :
131 333 : return have_path || builtin;
132 : }
133 :
134 : /**
135 : * bd_utils_load_kernel_module:
136 : * @module_name: name of the kernel module to load
137 : * @options: (nullable): module options
138 : * @error: (out) (optional): place to store error (if any)
139 : *
140 : * Returns: whether the @module_name was successfully loaded or not
141 : */
142 3 : gboolean bd_utils_load_kernel_module (const gchar *module_name, const gchar *options, GError **error) {
143 3 : gint ret = 0;
144 3 : struct kmod_ctx *ctx = NULL;
145 3 : struct kmod_module *mod = NULL;
146 3 : gchar *null_config = NULL;
147 3 : locale_t c_locale = newlocale (LC_ALL_MASK, "C", (locale_t) 0);
148 :
149 3 : ctx = kmod_new (NULL, (const gchar * const*) &null_config);
150 3 : if (!ctx) {
151 0 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_KMOD_INIT_FAIL,
152 : "Failed to initialize kmod context");
153 0 : freelocale (c_locale);
154 0 : return FALSE;
155 : }
156 3 : set_kmod_logging (ctx);
157 :
158 3 : ret = kmod_module_new_from_name (ctx, module_name, &mod);
159 3 : if (ret < 0) {
160 0 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_FAIL,
161 0 : "Failed to get the module: %s", strerror_l (-ret, c_locale));
162 0 : kmod_unref (ctx);
163 0 : freelocale (c_locale);
164 0 : return FALSE;
165 : }
166 :
167 3 : if (!kmod_module_get_path (mod)) {
168 1 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_NOEXIST,
169 : "Module '%s' doesn't exist", module_name);
170 1 : kmod_module_unref (mod);
171 1 : kmod_unref (ctx);
172 1 : freelocale (c_locale);
173 1 : return FALSE;
174 : }
175 :
176 : /* module, flags, options, run_install, data, print_action
177 : flag KMOD_PROBE_FAIL_ON_LOADED is used for backwards compatibility */
178 2 : ret = kmod_module_probe_insert_module (mod, KMOD_PROBE_FAIL_ON_LOADED,
179 : options, NULL, NULL, NULL);
180 2 : if (ret < 0) {
181 2 : if (options)
182 0 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_FAIL,
183 : "Failed to load the module '%s' with options '%s': %s",
184 0 : module_name, options, strerror_l (-ret, c_locale));
185 : else
186 2 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_FAIL,
187 : "Failed to load the module '%s': %s",
188 2 : module_name, strerror_l (-ret, c_locale));
189 2 : kmod_module_unref (mod);
190 2 : kmod_unref (ctx);
191 2 : freelocale (c_locale);
192 2 : return FALSE;
193 : }
194 :
195 0 : kmod_module_unref (mod);
196 0 : kmod_unref (ctx);
197 0 : freelocale (c_locale);
198 0 : return TRUE;
199 : }
200 :
201 : /**
202 : * bd_utils_unload_kernel_module:
203 : * @module_name: name of the kernel module to unload
204 : * @error: (out) (optional): place to store error (if any)
205 : *
206 : * Returns: whether the @module_name was successfully unloaded or not
207 : */
208 1 : gboolean bd_utils_unload_kernel_module (const gchar *module_name, GError **error) {
209 1 : gint ret = 0;
210 1 : struct kmod_ctx *ctx = NULL;
211 1 : struct kmod_module *mod = NULL;
212 1 : struct kmod_list *list = NULL;
213 1 : struct kmod_list *cur = NULL;
214 1 : gchar *null_config = NULL;
215 1 : gboolean found = FALSE;
216 1 : locale_t c_locale = newlocale (LC_ALL_MASK, "C", (locale_t) 0);
217 :
218 1 : ctx = kmod_new (NULL, (const gchar * const*) &null_config);
219 1 : if (!ctx) {
220 0 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_KMOD_INIT_FAIL,
221 : "Failed to initialize kmod context");
222 0 : freelocale (c_locale);
223 0 : return FALSE;
224 : }
225 1 : set_kmod_logging (ctx);
226 :
227 1 : ret = kmod_module_new_from_loaded (ctx, &list);
228 1 : if (ret < 0) {
229 0 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_FAIL,
230 0 : "Failed to get the module: %s", strerror_l (-ret, c_locale));
231 0 : kmod_unref (ctx);
232 0 : freelocale (c_locale);
233 0 : return FALSE;
234 : }
235 :
236 189 : for (cur=list; !found && cur != NULL; cur = kmod_list_next(list, cur)) {
237 188 : mod = kmod_module_get_module (cur);
238 188 : if (g_strcmp0 (kmod_module_get_name (mod), module_name) == 0)
239 0 : found = TRUE;
240 : else
241 188 : kmod_module_unref (mod);
242 : }
243 1 : kmod_module_unref_list (list);
244 :
245 1 : if (!found) {
246 1 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_NOEXIST,
247 : "Module '%s' is not loaded", module_name);
248 1 : kmod_unref (ctx);
249 1 : freelocale (c_locale);
250 1 : return FALSE;
251 : }
252 :
253 : /* module, flags */
254 0 : ret = kmod_module_remove_module (mod, 0);
255 0 : if (ret < 0) {
256 0 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_FAIL,
257 : "Failed to unload the module '%s': %s",
258 0 : module_name, strerror_l (-ret, c_locale));
259 0 : kmod_module_unref (mod);
260 0 : kmod_unref (ctx);
261 0 : freelocale (c_locale);
262 0 : return FALSE;
263 : }
264 :
265 0 : kmod_module_unref (mod);
266 0 : kmod_unref (ctx);
267 0 : freelocale (c_locale);
268 0 : return TRUE;
269 : }
270 :
271 :
272 : static BDUtilsLinuxVersion detected_linux_ver;
273 : static gboolean have_linux_ver = FALSE;
274 :
275 : G_LOCK_DEFINE_STATIC (detected_linux_ver);
276 :
277 2 : static BDUtilsLinuxVersion * _get_linux_version (gboolean lock, GError **error) {
278 : struct utsname buf;
279 :
280 2 : if (lock)
281 2 : G_LOCK (detected_linux_ver);
282 :
283 : /* return cached value if available */
284 2 : if (have_linux_ver) {
285 1 : if (lock)
286 1 : G_UNLOCK (detected_linux_ver);
287 1 : return &detected_linux_ver;
288 : }
289 :
290 1 : memset (&detected_linux_ver, 0, sizeof (BDUtilsLinuxVersion));
291 :
292 1 : if (uname (&buf)) {
293 0 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_FAIL,
294 : "Failed to get linux kernel version: %m");
295 0 : if (lock)
296 0 : G_UNLOCK (detected_linux_ver);
297 0 : return NULL;
298 : }
299 :
300 1 : if (g_ascii_strncasecmp (buf.sysname, "Linux", sizeof buf.sysname) != 0) {
301 0 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_INVALID_PLATFORM,
302 : "Failed to get kernel version: spurious sysname '%s' detected", buf.sysname);
303 0 : if (lock)
304 0 : G_UNLOCK (detected_linux_ver);
305 0 : return NULL;
306 : }
307 :
308 1 : if (sscanf (buf.release, "%d.%d.%d",
309 : &detected_linux_ver.major,
310 : &detected_linux_ver.minor,
311 : &detected_linux_ver.micro) < 1) {
312 0 : g_set_error (error, BD_UTILS_MODULE_ERROR, BD_UTILS_MODULE_ERROR_FAIL,
313 : "Failed to parse kernel version: malformed release string '%s'", buf.release);
314 0 : if (lock)
315 0 : G_UNLOCK (detected_linux_ver);
316 0 : return NULL;
317 : }
318 :
319 1 : have_linux_ver = TRUE;
320 1 : if (lock)
321 1 : G_UNLOCK (detected_linux_ver);
322 1 : return &detected_linux_ver;
323 : }
324 :
325 : /**
326 : * bd_utils_get_linux_version:
327 : * @error: (out) (optional): place to store error (if any)
328 : *
329 : * Retrieves version of currently running Linux kernel. Acts also as an initializer for statically cached data.
330 : *
331 : * Returns: (transfer none): Detected Linux kernel version or %NULL in case of an error. The returned value belongs to the library, do not free.
332 : */
333 2 : BDUtilsLinuxVersion * bd_utils_get_linux_version (GError **error) {
334 2 : return _get_linux_version (TRUE, error);
335 : }
336 :
337 : /**
338 : * bd_utils_check_linux_version:
339 : * @major: Minimal major linux kernel version.
340 : * @minor: Minimal minor linux kernel version.
341 : * @micro: Minimal micro linux kernel version.
342 : *
343 : * Checks whether the currently running linux kernel version is equal or higher
344 : * than the specified required @major.@minor.@micro version.
345 : *
346 : * Returns: an integer less than, equal to, or greater than zero, if detected version is <, == or > than the specified @major.@minor.@micro version.
347 : */
348 3 : gint bd_utils_check_linux_version (guint major, guint minor, guint micro) {
349 : gint ret;
350 :
351 3 : G_LOCK (detected_linux_ver);
352 3 : if (!have_linux_ver)
353 0 : _get_linux_version (FALSE, NULL);
354 :
355 3 : ret = detected_linux_ver.major - major;
356 3 : if (ret == 0)
357 1 : ret = detected_linux_ver.minor - minor;
358 3 : if (ret == 0)
359 1 : ret = detected_linux_ver.micro - micro;
360 :
361 3 : G_UNLOCK (detected_linux_ver);
362 :
363 3 : return ret;
364 : }
|