From 336f8872897986cdd6975ccb4d6ecb09ac4be52c Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 30 Aug 2018 22:10:36 -0400 Subject: [PATCH] Add an instance api This is made to let gnome-software enumerate all running instances and get the same information about them that flatpak ps provides. For now, we keep it private. It will become library api once we open new development --- common/Makefile.am.inc | 2 + common/flatpak-instance-private.h | 68 ++++++ common/flatpak-instance.c | 353 ++++++++++++++++++++++++++++++ 3 files changed, 423 insertions(+) create mode 100644 common/flatpak-instance-private.h create mode 100644 common/flatpak-instance.c diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc index d39d6513..f3966903 100644 --- a/common/Makefile.am.inc +++ b/common/Makefile.am.inc @@ -120,6 +120,8 @@ libflatpak_common_la_SOURCES = \ common/flatpak-error.c \ common/flatpak-installation-private.h \ common/flatpak-installation.c \ + common/flatpak-instance-private.h \ + common/flatpak-instance.c \ common/valgrind-private.h \ $(NULL) diff --git a/common/flatpak-instance-private.h b/common/flatpak-instance-private.h new file mode 100644 index 00000000..f9a27108 --- /dev/null +++ b/common/flatpak-instance-private.h @@ -0,0 +1,68 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Matthias Clasen + */ + +#if !defined(__FLATPAK_H_INSIDE__) && !defined(FLATPAK_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __FLATPAK_INSTANCE_H__ +#define __FLATPAK_INSTANCE_H__ + +typedef struct _FlatpakInstance FlatpakInstance; + +#include + +#define FLATPAK_TYPE_INSTANCE flatpak_instance_get_type () +#define FLATPAK_INSTANCE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLATPAK_TYPE_INSTANCE, FlatpakInstance)) +#define FLATPAK_IS_INSTANCE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLATPAK_TYPE_INSTANCE)) + +FLATPAK_EXTERN GType flatpak_instance_get_type (void); + +struct _FlatpakInstance +{ + GObject parent; +}; + +typedef struct +{ + GObjectClass parent_class; +} FlatpakInstanceClass; + + +#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakInstance, g_object_unref) +#endif + +FLATPAK_EXTERN GPtrArray * flatpak_instance_get_all (void); + +FLATPAK_EXTERN const char * flatpak_instance_get_id (FlatpakInstance *self); +FLATPAK_EXTERN const char * flatpak_instance_get_app (FlatpakInstance *self); +FLATPAK_EXTERN const char * flatpak_instance_get_arch (FlatpakInstance *self); +FLATPAK_EXTERN const char * flatpak_instance_get_branch (FlatpakInstance *self); +FLATPAK_EXTERN const char * flatpak_instance_get_commit (FlatpakInstance *self); +FLATPAK_EXTERN const char * flatpak_instance_get_runtime (FlatpakInstance *self); +FLATPAK_EXTERN const char * flatpak_instance_get_runtime_commit (FlatpakInstance *self); +FLATPAK_EXTERN int flatpak_instance_get_pid (FlatpakInstance *self); +FLATPAK_EXTERN int flatpak_instance_get_child_pid (FlatpakInstance *self); +FLATPAK_EXTERN GKeyFile * flatpak_instance_get_info (FlatpakInstance *self); + +FLATPAK_EXTERN gboolean flatpak_instance_is_running (FlatpakInstance *self); + +#endif /* __FLATPAK_INSTANCE_H__ */ diff --git a/common/flatpak-instance.c b/common/flatpak-instance.c new file mode 100644 index 00000000..1f15776f --- /dev/null +++ b/common/flatpak-instance.c @@ -0,0 +1,353 @@ +/* + * Copyright © 2018 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Matthias Clasen + */ + +#include "config.h" + +#include "flatpak-utils-private.h" +#include "flatpak-run-private.h" +#include "flatpak-instance-private.h" +#include "flatpak-enum-types.h" + + +typedef struct _FlatpakInstancePrivate FlatpakInstancePrivate; + +struct _FlatpakInstancePrivate +{ + char *id; + char *dir; + + GKeyFile *info; + char *app; + char *arch; + char *branch; + char *commit; + char *runtime; + char *runtime_commit; + + int pid; + int child_pid; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (FlatpakInstance, flatpak_instance, G_TYPE_OBJECT) + +static void +flatpak_instance_finalize (GObject *object) +{ + FlatpakInstance *self = FLATPAK_INSTANCE (object); + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + g_free (priv->id); + g_free (priv->dir); + g_free (priv->app); + g_free (priv->arch); + g_free (priv->branch); + g_free (priv->commit); + g_free (priv->runtime); + g_free (priv->runtime_commit); + + if (priv->info) + g_key_file_unref (priv->info); + + G_OBJECT_CLASS (flatpak_instance_parent_class)->finalize (object); +} + +static void +flatpak_instance_class_init (FlatpakInstanceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = flatpak_instance_finalize; +} + +static void +flatpak_instance_init (FlatpakInstance *self) +{ +} + +const char * +flatpak_instance_get_id (FlatpakInstance *self) +{ + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + return priv->id; +} + +const char * +flatpak_instance_get_app (FlatpakInstance *self) +{ + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + return priv->app; +} + +const char * +flatpak_instance_get_arch (FlatpakInstance *self) +{ + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + return priv->arch; +} + +const char * +flatpak_instance_get_branch (FlatpakInstance *self) +{ + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + return priv->branch; +} + +const char * +flatpak_instance_get_commit (FlatpakInstance *self) +{ + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + return priv->commit; +} + +const char * +flatpak_instance_get_runtime (FlatpakInstance *self) +{ + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + return priv->runtime; +} + +const char * +flatpak_instance_get_runtime_commit (FlatpakInstance *self) +{ + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + return priv->runtime_commit; +} + +int +flatpak_instance_get_pid (FlatpakInstance *self) +{ + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + return priv->pid; +} + +int +flatpak_instance_get_child_pid (FlatpakInstance *self) +{ + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + return priv->child_pid; +} + +GKeyFile * +flatpak_instance_get_info (FlatpakInstance *self) +{ + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + return priv->info; +} + +static GKeyFile * +get_instance_info (const char *dir) +{ + g_autofree char *file = NULL; + g_autoptr(GKeyFile) key_file = NULL; + g_autoptr(GError) error = NULL; + + file = g_build_filename (dir, "info", NULL); + + key_file = g_key_file_new (); + if (!g_key_file_load_from_file (key_file, file, G_KEY_FILE_NONE, &error)) + { + g_debug ("Failed to load instance info file '%s': %s", file, error->message); + return NULL; + } + + return g_steal_pointer (&key_file); +} + +static int +get_child_pid (const char *dir) +{ + g_autofree char *file = NULL; + g_autofree char *contents = NULL; + gsize length; + g_autoptr(GError) error = NULL; + g_autoptr(JsonParser) parser = NULL; + JsonNode *node; + JsonObject *obj; + + file = g_build_filename (dir, "bwrapinfo.json", NULL); + + if (!g_file_get_contents (file, &contents, &length, &error)) + { + g_debug ("Failed to load bwrapinfo.json file '%s': %s", file, error->message); + return 0; + } + + parser = json_parser_new (); + if (!json_parser_load_from_data (parser, contents, length, &error)) + { + g_debug ("Failed to parse bwrapinfo.json file '%s': %s", file, error->message); + return 0; + } + + node = json_parser_get_root (parser); + obj = json_node_get_object (node); + + return json_object_get_int_member (obj, "child-pid"); +} + +static int +get_pid (const char *dir) +{ + g_autofree char *file = NULL; + g_autofree char *contents = NULL; + g_autoptr(GError) error = NULL; + + file = g_build_filename (dir, "pid", NULL); + + if (!g_file_get_contents (file, &contents, NULL, &error)) + { + g_debug ("Failed to load pid file '%s': %s", file, error->message); + return 0; + } + + return (int) g_ascii_strtoll (contents, NULL, 10); +} + +static FlatpakInstance * +flatpak_instance_new (const char *id) +{ + FlatpakInstance *self = g_object_new (flatpak_instance_get_type (), NULL); + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + priv->id = g_strdup (id); + + priv->dir = g_build_filename (g_get_user_runtime_dir (), ".flatpak", id, NULL); + + priv->pid = get_pid (priv->dir); + priv->child_pid = get_child_pid (priv->dir); + priv->info = get_instance_info (priv->dir); + + if (priv->info) + { + priv->app = g_key_file_get_string (priv->info, + FLATPAK_METADATA_GROUP_APPLICATION, FLATPAK_METADATA_KEY_NAME, NULL); + priv->runtime = g_key_file_get_string (priv->info, + FLATPAK_METADATA_GROUP_APPLICATION, FLATPAK_METADATA_KEY_RUNTIME, NULL); + + priv->arch = g_key_file_get_string (priv->info, + FLATPAK_METADATA_GROUP_INSTANCE, FLATPAK_METADATA_KEY_ARCH, NULL); + priv->branch = g_key_file_get_string (priv->info, + FLATPAK_METADATA_GROUP_INSTANCE, FLATPAK_METADATA_KEY_BRANCH, NULL); + priv->commit = g_key_file_get_string (priv->info, + FLATPAK_METADATA_GROUP_INSTANCE, FLATPAK_METADATA_KEY_APP_COMMIT, NULL); + priv->runtime_commit = g_key_file_get_string (priv->info, + FLATPAK_METADATA_GROUP_INSTANCE, FLATPAK_METADATA_KEY_RUNTIME_COMMIT, NULL); + } + + return self; +} + +GPtrArray * +flatpak_instance_get_all (void) +{ + g_autoptr(GPtrArray) instances = NULL; + g_autofree char *base_dir = NULL; + g_auto(GLnxDirFdIterator) iter = { 0 }; + struct dirent *dent; + + instances = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + base_dir = g_build_filename (g_get_user_runtime_dir (), ".flatpak", NULL); + + if (!glnx_dirfd_iterator_init_at (AT_FDCWD, base_dir, FALSE, &iter, NULL)) + return g_steal_pointer (&instances); + + while (TRUE) + { + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent, NULL, NULL)) + break; + + if (dent == NULL) + break; + + if (dent->d_type == DT_DIR) + { + g_autofree char *ref_file = g_strconcat (dent->d_name, "/.ref", NULL); + struct stat statbuf; + struct flock l = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0 + }; + glnx_autofd int lock_fd = openat (iter.fd, ref_file, O_RDWR | O_CLOEXEC); + if (lock_fd != -1 && + fstat (lock_fd, &statbuf) == 0 && + /* Only gc if created at least 3 secs ago, to work around race mentioned in flatpak_run_allocate_id() */ + statbuf.st_mtime + 3 < time (NULL) && + fcntl (lock_fd, F_GETLK, &l) == 0 && + l.l_type == F_UNLCK) + { + /* The instance is not used, remove it */ + g_debug ("Cleaning up unused container id %s", dent->d_name); + glnx_shutil_rm_rf_at (iter.fd, dent->d_name, NULL, NULL); + continue; + } + + g_ptr_array_add (instances, flatpak_instance_new (dent->d_name)); + } + } + + return g_steal_pointer (&instances); +} + +gboolean +flatpak_instance_is_running (FlatpakInstance *self) +{ + FlatpakInstancePrivate *priv = flatpak_instance_get_instance_private (self); + + if (priv->dir) + { + g_autofree char *ref_file = g_strconcat (priv->dir, "/.ref", NULL); + struct stat statbuf; + struct flock l = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0 + }; + glnx_autofd int lock_fd = open (ref_file, O_RDWR | O_CLOEXEC); + if (lock_fd != -1 && + fstat (lock_fd, &statbuf) == 0 && + /* Only gc if created at least 3 secs ago, to work around race mentioned in flatpak_run_allocate_id() */ + statbuf.st_mtime + 3 < time (NULL) && + fcntl (lock_fd, F_GETLK, &l) == 0 && + l.l_type == F_UNLCK) + { + /* The instance is not used, remove it */ + g_debug ("Cleaning up unused container id %s", priv->id); + glnx_shutil_rm_rf_at (-1, priv->dir, NULL, NULL); + + g_clear_pointer (&priv->dir, g_free); + } + } + + return priv->dir != NULL; +} +