1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * Copyright (C) 2009-2015 Richard Hughes <richard@hughsie.com>
4 *
5 * Licensed under the GNU Lesser General Public License Version 2.1
6 *
7 * Most of this code was taken from Zif, libzif/zif-lock.c
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or(at your option) any later version.
13 *
14 * This library 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 GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 */
23
24 /**
25 * SECTION:dnf-lock
26 * @short_description: Lock the package system.
27 * @include: libdnf.h
28 * @stability: Unstable
29 *
30 * This object either works per-thread or per-system with a configured lock file.
31 *
32 * See also: #DnfState
33 */
34
35
36 #include <gio/gio.h>
37
38 #include "catch-error.hpp"
39 #include "dnf-lock.h"
40 #include "dnf-types.h"
41 #include "dnf-utils.h"
42
43 typedef struct
44 {
45 GMutex mutex;
46 GPtrArray *item_array; /* of DnfLockItem */
47 gchar *lock_dir;
48 } DnfLockPrivate;
49
50 typedef struct {
51 gpointer owner;
52 guint id;
53 guint refcount;
54 DnfLockMode mode;
55 DnfLockType type;
56 } DnfLockItem;
57
58 G_DEFINE_TYPE_WITH_PRIVATE(DnfLock, dnf_lock, G_TYPE_OBJECT)
59 #define GET_PRIVATE(o) (static_cast<DnfLockPrivate *>(dnf_lock_get_instance_private (o)))
60
61 enum {
62 SIGNAL_STATE_CHANGED,
63 SIGNAL_LAST
64 };
65
66 static guint signals [SIGNAL_LAST] = { 0 };
67
68 static gpointer dnf_lock_object = NULL;
69
70 /**
71 * dnf_lock_finalize:
72 **/
73 static void
dnf_lock_finalize(GObject * object)74 dnf_lock_finalize(GObject *object)
75 {
76 DnfLock *lock = DNF_LOCK(object);
77 DnfLockPrivate *priv = GET_PRIVATE(lock);
78 guint i;
79
80 /* unlock if we hold the lock */
81 for (i = 0; i < priv->item_array->len; i++) {
82 auto item = static_cast<DnfLockItem *>(g_ptr_array_index(priv->item_array, i));
83 if (item->refcount > 0) {
84 g_warning("held lock %s at shutdown",
85 dnf_lock_type_to_string(item->type));
86 dnf_lock_release(lock, item->id, NULL);
87 }
88 }
89 g_ptr_array_unref(priv->item_array);
90 g_free(priv->lock_dir);
91
92 G_OBJECT_CLASS(dnf_lock_parent_class)->finalize(object);
93 }
94
95 /**
96 * dnf_lock_init:
97 **/
98 static void
dnf_lock_init(DnfLock * lock)99 dnf_lock_init(DnfLock *lock)
100 {
101 DnfLockPrivate *priv = GET_PRIVATE(lock);
102 priv->item_array = g_ptr_array_new_with_free_func(g_free);
103 priv->lock_dir = g_strdup("/var/run");
104 }
105
106 /**
107 * dnf_lock_class_init:
108 **/
109 static void
dnf_lock_class_init(DnfLockClass * klass)110 dnf_lock_class_init(DnfLockClass *klass)
111 {
112 GObjectClass *object_class = G_OBJECT_CLASS(klass);
113
114 signals [SIGNAL_STATE_CHANGED] =
115 g_signal_new("state-changed",
116 G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
117 G_STRUCT_OFFSET(DnfLockClass, state_changed),
118 NULL, NULL, g_cclosure_marshal_VOID__UINT,
119 G_TYPE_NONE, 1, G_TYPE_UINT);
120
121 object_class->finalize = dnf_lock_finalize;
122 }
123
124 /**
125 * dnf_lock_type_to_string:
126 * @lock_type: a #DnfLockType.
127 *
128 * Returns: The string representation of the lock type.
129 *
130 * Since: 0.1.0
131 **/
132 const gchar *
dnf_lock_type_to_string(DnfLockType lock_type)133 dnf_lock_type_to_string(DnfLockType lock_type)
134 {
135 if (lock_type == DNF_LOCK_TYPE_RPMDB)
136 return "rpmdb";
137 if (lock_type == DNF_LOCK_TYPE_REPO)
138 return "src";
139 if (lock_type == DNF_LOCK_TYPE_METADATA)
140 return "metadata";
141 if (lock_type == DNF_LOCK_TYPE_CONFIG)
142 return "config";
143 return "unknown";
144 }
145
146 /**
147 * dnf_lock_mode_to_string:
148 **/
149 static const gchar *
dnf_lock_mode_to_string(DnfLockMode lock_mode)150 dnf_lock_mode_to_string(DnfLockMode lock_mode)
151 {
152 if (lock_mode == DNF_LOCK_MODE_THREAD)
153 return "thread";
154 if (lock_mode == DNF_LOCK_MODE_PROCESS)
155 return "process";
156 return "unknown";
157 }
158
159 /**
160 * dnf_lock_get_item_by_type_mode:
161 **/
162 static DnfLockItem *
dnf_lock_get_item_by_type_mode(DnfLock * lock,DnfLockType type,DnfLockMode mode)163 dnf_lock_get_item_by_type_mode(DnfLock *lock,
164 DnfLockType type,
165 DnfLockMode mode)
166 {
167 DnfLockPrivate *priv = GET_PRIVATE(lock);
168 guint i;
169
170 /* search for the item that matches type */
171 for (i = 0; i < priv->item_array->len; i++) {
172 auto item = static_cast<DnfLockItem *>(g_ptr_array_index(priv->item_array, i));
173 if (item->type == type && item->mode == mode)
174 return item;
175 }
176 return NULL;
177 }
178
179 /**
180 * dnf_lock_get_item_by_id:
181 **/
182 static DnfLockItem *
dnf_lock_get_item_by_id(DnfLock * lock,guint id)183 dnf_lock_get_item_by_id(DnfLock *lock, guint id)
184 {
185 DnfLockPrivate *priv = GET_PRIVATE(lock);
186 guint i;
187
188 /* search for the item that matches the ID */
189 for (i = 0; i < priv->item_array->len; i++) {
190 auto item = static_cast<DnfLockItem *>(g_ptr_array_index(priv->item_array, i));
191 if (item->id == id)
192 return item;
193 }
194 return NULL;
195 }
196
197 /**
198 * dnf_lock_create_item:
199 **/
200 static DnfLockItem *
dnf_lock_create_item(DnfLock * lock,DnfLockType type,DnfLockMode mode)201 dnf_lock_create_item(DnfLock *lock, DnfLockType type, DnfLockMode mode)
202 {
203 DnfLockItem *item;
204 DnfLockPrivate *priv = GET_PRIVATE(lock);
205 static guint id = 1;
206
207 item = g_new0(DnfLockItem, 1);
208 item->id = id++;
209 item->type = type;
210 item->owner = g_thread_self();
211 item->refcount = 1;
212 item->mode = mode;
213 g_ptr_array_add(priv->item_array, item);
214 return item;
215 }
216
217 /**
218 * dnf_lock_get_pid:
219 **/
220 static guint
dnf_lock_get_pid(DnfLock * lock,const gchar * filename,GError ** error)221 dnf_lock_get_pid(DnfLock *lock, const gchar *filename, GError **error)
222 {
223 gboolean ret;
224 guint64 pid;
225 gchar *endptr = NULL;
226 g_autoptr(GError) error_local = NULL;
227 g_autofree gchar *contents = NULL;
228
229 g_return_val_if_fail(DNF_IS_LOCK(lock), FALSE);
230
231 /* file doesn't exists */
232 ret = g_file_test(filename, G_FILE_TEST_EXISTS);
233 if (!ret) {
234 g_set_error_literal(error,
235 DNF_ERROR,
236 DNF_ERROR_INTERNAL_ERROR,
237 "lock file not present");
238 return 0;
239 }
240
241 /* get contents */
242 ret = g_file_get_contents(filename, &contents, NULL, &error_local);
243 if (!ret) {
244 g_set_error(error,
245 DNF_ERROR,
246 DNF_ERROR_INTERNAL_ERROR,
247 "lock file not set: %s",
248 error_local->message);
249 return 0;
250 }
251
252 /* convert to int */
253 pid = g_ascii_strtoull(contents, &endptr, 10);
254
255 /* failed to parse */
256 if (contents == endptr) {
257 g_set_error(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR,
258 "failed to parse pid: %s", contents);
259 return 0;
260 }
261
262 /* too large */
263 if (pid > G_MAXUINT) {
264 g_set_error(error, DNF_ERROR, DNF_ERROR_INTERNAL_ERROR,
265 "pid too large %" G_GUINT64_FORMAT, pid);
266 return 0;
267 }
268 return(guint) pid;
269 }
270
271 /**
272 * dnf_lock_get_filename_for_type:
273 **/
274 static gchar *
dnf_lock_get_filename_for_type(DnfLock * lock,DnfLockType type)275 dnf_lock_get_filename_for_type(DnfLock *lock, DnfLockType type)
276 {
277 DnfLockPrivate *priv = GET_PRIVATE(lock);
278 return g_strdup_printf("%s/dnf-%s.lock",
279 priv->lock_dir,
280 dnf_lock_type_to_string(type));
281 }
282
283 /**
284 * dnf_lock_get_cmdline_for_pid:
285 **/
286 static gchar *
dnf_lock_get_cmdline_for_pid(guint pid)287 dnf_lock_get_cmdline_for_pid(guint pid)
288 {
289 gboolean ret;
290 g_autoptr(GError) error = NULL;
291 g_autofree gchar *data = NULL;
292 g_autofree gchar *filename = NULL;
293
294 /* find the cmdline */
295 filename = g_strdup_printf("/proc/%i/cmdline", pid);
296 ret = g_file_get_contents(filename, &data, NULL, &error);
297 if (ret)
298 return g_strdup_printf("%s(%i)", data, pid);
299 g_warning("failed to get cmdline: %s", error->message);
300 return g_strdup_printf("unknown(%i)", pid);
301 }
302
303 /**
304 * dnf_lock_set_lock_dir:
305 * @lock: a #DnfLock instance.
306 * @lock_dir: the directory to use for lock files
307 *
308 * Sets the directory to use for lock files.
309 *
310 * Since: 0.1.4
311 **/
312 void
dnf_lock_set_lock_dir(DnfLock * lock,const gchar * lock_dir)313 dnf_lock_set_lock_dir(DnfLock *lock, const gchar *lock_dir)
314 {
315 DnfLockPrivate *priv = GET_PRIVATE(lock);
316 g_return_if_fail(DNF_IS_LOCK(lock));
317 g_free(priv->lock_dir);
318 priv->lock_dir = g_strdup(lock_dir);
319 }
320
321 /**
322 * dnf_lock_get_state:
323 * @lock: a #DnfLock instance.
324 *
325 * Gets a bitfield of what locks have been taken
326 *
327 * Returns: A bitfield.
328 *
329 * Since: 0.1.0
330 **/
331 guint
dnf_lock_get_state(DnfLock * lock)332 dnf_lock_get_state(DnfLock *lock)
333 {
334 DnfLockPrivate *priv = GET_PRIVATE(lock);
335 guint bitfield = 0;
336 guint i;
337
338 g_return_val_if_fail(DNF_IS_LOCK(lock), FALSE);
339
340 for (i = 0; i < priv->item_array->len; i++) {
341 auto item = static_cast<DnfLockItem *>(g_ptr_array_index(priv->item_array, i));
342 bitfield += 1 << item->type;
343 }
344 return bitfield;
345 }
346
347 /**
348 * dnf_lock_emit_state:
349 **/
350 static void
dnf_lock_emit_state(DnfLock * lock)351 dnf_lock_emit_state(DnfLock *lock)
352 {
353 guint bitfield = 0;
354 bitfield = dnf_lock_get_state(lock);
355 g_signal_emit(lock, signals [SIGNAL_STATE_CHANGED], 0, bitfield);
356 }
357
358 /**
359 * dnf_lock_take:
360 * @lock: a #DnfLock instance.
361 * @type: A #ZifLockType, e.g. %DNF_LOCK_TYPE_RPMDB
362 * @mode: A #ZifLockMode, e.g. %DNF_LOCK_MODE_PROCESS
363 * @error: A #GError, or %NULL
364 *
365 * Tries to take a lock for the packaging system.
366 *
367 * Returns: A lock ID greater than 0, or 0 for an error.
368 *
369 * Since: 0.1.0
370 **/
371 guint
dnf_lock_take(DnfLock * lock,DnfLockType type,DnfLockMode mode,GError ** error)372 dnf_lock_take(DnfLock *lock,
373 DnfLockType type,
374 DnfLockMode mode,
375 GError **error) try
376 {
377 DnfLockItem *item;
378 DnfLockPrivate *priv = GET_PRIVATE(lock);
379 gboolean ret;
380 guint id = 0;
381 guint pid;
382 g_autoptr(GError) error_local = NULL;
383 g_autofree gchar *cmdline = NULL;
384 g_autofree gchar *filename = NULL;
385 g_autofree gchar *pid_filename = NULL;
386 g_autofree gchar *pid_text = NULL;
387
388 g_return_val_if_fail(DNF_IS_LOCK(lock), FALSE);
389 g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
390
391 /* lock other threads */
392 g_mutex_lock(&priv->mutex);
393
394 /* find the lock type, and ensure we find a process lock for
395 * a thread lock */
396 item = dnf_lock_get_item_by_type_mode(lock, type, mode);
397 if (item == NULL && mode == DNF_LOCK_MODE_THREAD) {
398 item = dnf_lock_get_item_by_type_mode(lock,
399 type,
400 DNF_LOCK_MODE_PROCESS);
401 }
402
403 /* create a lock file for process locks */
404 if (item == NULL && mode == DNF_LOCK_MODE_PROCESS) {
405
406 /* does lock file already exists? */
407 filename = dnf_lock_get_filename_for_type(lock, type);
408 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
409
410 /* check the pid is still valid */
411 pid = dnf_lock_get_pid(lock, filename, error);
412 if (pid == 0)
413 goto out;
414
415 /* pid is not still running? */
416 pid_filename = g_strdup_printf("/proc/%i/cmdline", pid);
417 ret = g_file_test(pid_filename, G_FILE_TEST_EXISTS);
418 if (ret) {
419 cmdline = dnf_lock_get_cmdline_for_pid(pid);
420 g_set_error(error,
421 DNF_ERROR,
422 DNF_ERROR_CANNOT_GET_LOCK,
423 "%s[%s] already locked by %s",
424 dnf_lock_type_to_string(type),
425 dnf_lock_mode_to_string(mode),
426 cmdline);
427 goto out;
428 }
429 }
430
431 /* create file with our process ID */
432 pid_text = g_strdup_printf("%i", getpid());
433 if (!g_file_set_contents(filename, pid_text, -1, &error_local)) {
434 g_set_error(error,
435 DNF_ERROR,
436 DNF_ERROR_CANNOT_GET_LOCK,
437 "failed to obtain lock '%s': %s",
438 dnf_lock_type_to_string(type),
439 error_local->message);
440 goto out;
441 }
442 }
443
444 /* create new lock */
445 if (item == NULL) {
446 item = dnf_lock_create_item(lock, type, mode);
447 id = item->id;
448 dnf_lock_emit_state(lock);
449 goto out;
450 }
451
452 /* we're trying to lock something that's already locked
453 * in another thread */
454 if (item->owner != g_thread_self()) {
455 g_set_error(error,
456 DNF_ERROR,
457 DNF_ERROR_CANNOT_GET_LOCK,
458 "failed to obtain lock '%s' already taken by thread %p",
459 dnf_lock_type_to_string(type),
460 item->owner);
461 goto out;
462 }
463
464 /* increment ref count */
465 item->refcount++;
466
467 /* emit the new locking bitfield */
468 dnf_lock_emit_state(lock);
469
470 /* success */
471 id = item->id;
472 out:
473 /* unlock other threads */
474 g_mutex_unlock(&priv->mutex);
475 return id;
476 } CATCH_TO_GERROR(0)
477
478 /**
479 * dnf_lock_release:
480 * @lock: a #DnfLock instance.
481 * @id: A lock ID, as given by zif_lock_take()
482 * @error: A #GError, or %NULL
483 *
484 * Tries to release a lock for the packaging system.
485 *
486 * Returns: %TRUE if we locked, else %FALSE and the error is set
487 *
488 * Since: 0.1.0
489 **/
490 gboolean
dnf_lock_release(DnfLock * lock,guint id,GError ** error)491 dnf_lock_release(DnfLock *lock, guint id, GError **error) try
492 {
493 DnfLockItem *item;
494 DnfLockPrivate *priv = GET_PRIVATE(lock);
495 gboolean ret = FALSE;
496
497 g_assert(DNF_IS_LOCK(lock));
498 g_assert(id != 0);
499 g_assert(error == NULL || *error == NULL);
500
501 /* lock other threads */
502 g_mutex_lock(&priv->mutex);
503
504 /* never took */
505 item = dnf_lock_get_item_by_id(lock, id);
506 if (item == NULL) {
507 g_set_error(error,
508 DNF_ERROR,
509 DNF_ERROR_INTERNAL_ERROR,
510 "Lock was never taken with id %i", id);
511 goto out;
512 }
513
514 /* not the same thread */
515 if (item->owner != g_thread_self()) {
516 g_set_error(error,
517 DNF_ERROR,
518 DNF_ERROR_INTERNAL_ERROR,
519 "Lock %s was not taken by this thread",
520 dnf_lock_type_to_string(item->type));
521 goto out;
522 }
523
524 /* decrement ref count */
525 item->refcount--;
526
527 /* delete file for process locks */
528 if (item->refcount == 0 &&
529 item->mode == DNF_LOCK_MODE_PROCESS) {
530 g_autoptr(GError) error_local = NULL;
531 g_autofree gchar *filename = NULL;
532 g_autoptr(GFile) file = NULL;
533
534 /* unlink */
535 filename = dnf_lock_get_filename_for_type(lock, item->type);
536 file = g_file_new_for_path(filename);
537 ret = g_file_delete(file, NULL, &error_local);
538 if (!ret) {
539 g_set_error(error,
540 DNF_ERROR,
541 DNF_ERROR_INTERNAL_ERROR,
542 "failed to write: %s",
543 error_local->message);
544 goto out;
545 }
546 }
547
548 /* no thread now owns this lock */
549 if (item->refcount == 0)
550 g_ptr_array_remove(priv->item_array, item);
551
552 /* emit the new locking bitfield */
553 dnf_lock_emit_state(lock);
554
555 /* success */
556 ret = TRUE;
557 out:
558 /* unlock other threads */
559 g_mutex_unlock(&priv->mutex);
560 return ret;
561 } CATCH_TO_GERROR(FALSE)
562
563 /**
564 * dnf_lock_release_noerror:
565 * @lock: a #DnfLock instance.
566 * @id: A lock ID, as given by zif_lock_take()
567 *
568 * Tries to release a lock for the packaging system. This method
569 * should not be used lightly as no error will be returned.
570 *
571 * Since: 0.1.0
572 **/
573 void
574 dnf_lock_release_noerror(DnfLock *lock, guint id)
575 {
576 g_autoptr(GError) error = NULL;
577 if (!dnf_lock_release(lock, id, &error))
578 g_warning("Handled locally: %s", error->message);
579 }
580 /**
581 * dnf_lock_new:
582 *
583 * Creates a new #DnfLock.
584 *
585 * Returns:(transfer full): a #DnfLock
586 *
587 * Since: 0.1.0
588 **/
589 DnfLock *
dnf_lock_new(void)590 dnf_lock_new(void)
591 {
592 if (dnf_lock_object != NULL) {
593 g_object_ref(dnf_lock_object);
594 } else {
595 dnf_lock_object = g_object_new(DNF_TYPE_LOCK, NULL);
596 g_object_add_weak_pointer(static_cast<GObject *>(dnf_lock_object), &dnf_lock_object);
597 }
598 return DNF_LOCK(dnf_lock_object);
599 }
600