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