/* * Copyright © 2014 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 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: * Alexander Larsson */ #include "config.h" #include "xdg-app-utils.h" #include "xdg-app-dir.h" #include #include #include #include #include #include #include #include #include #include "libgsystem.h" #include "libglnx/libglnx.h" #include const char * xdg_app_get_arch (void) { static struct utsname buf; static char *arch = NULL; if (arch == NULL) { if (uname (&buf)) arch = "unknown"; else arch = buf.machine; } return arch; } static gboolean is_valid_initial_name_character (gint c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'); } static gboolean is_valid_name_character (gint c) { return is_valid_initial_name_character (c) || (c >= '0' && c <= '9'); } /** xdg_app_is_name: * @string: The string to check * * Checks if @string is a valid application name. * * App names are composed of 3 or more elements separated by a period * ('.') character. All elements must contain at least one character. * * Each element must only contain the ASCII characters * "[A-Z][a-z][0-9]_". Elements may not begin with a digit. * * App names must not begin with a '.' (period) character. * * App names must not exceed 255 characters in length. * * The above means that any app name is also a valid DBus well known * bus name, but not all DBus names are valid app names. The difference are: * 1) DBus name elements may contain '-' * 2) DBus names require only two elements * * Returns: %TRUE if valid, %FALSE otherwise. * * Since: 2.26 */ gboolean xdg_app_is_valid_name (const char *string) { guint len; gboolean ret; const gchar *s; const gchar *end; int dot_count; g_return_val_if_fail (string != NULL, FALSE); ret = FALSE; len = strlen (string); if (G_UNLIKELY (len == 0 || len > 255)) goto out; end = string + len; s = string; if (G_UNLIKELY (*s == '.')) { /* can't start with a . */ goto out; } else if (G_UNLIKELY (!is_valid_initial_name_character (*s))) goto out; s += 1; dot_count = 0; while (s != end) { if (*s == '.') { s += 1; if (G_UNLIKELY (s == end || !is_valid_initial_name_character (*s))) goto out; dot_count++; } else if (G_UNLIKELY (!is_valid_name_character (*s))) goto out; s += 1; } if (G_UNLIKELY (dot_count < 2)) goto out; ret = TRUE; out: return ret; } gboolean xdg_app_has_name_prefix (const char *string, const char *name) { const char *rest; if (!g_str_has_prefix (string, name)) return FALSE; rest = string + strlen (name); return *rest == 0 || *rest == '.' || !is_valid_name_character (*rest); } static gboolean is_valid_initial_branch_character (gint c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_') || (c == '-'); } static gboolean is_valid_branch_character (gint c) { return is_valid_initial_branch_character (c) || (c == '.'); } /** xdg_app_is_valid_branch: * @string: The string to check * * Checks if @string is a valid branch name. * * Branch names must only contain the ASCII characters * "[A-Z][a-z][0-9]_-.". * Branch names may not begin with a digit. * Branch names must contain at least one character. * * Returns: %TRUE if valid, %FALSE otherwise. * * Since: 2.26 */ gboolean xdg_app_is_valid_branch (const char *string) { guint len; gboolean ret; const gchar *s; const gchar *end; g_return_val_if_fail (string != NULL, FALSE); ret = FALSE; len = strlen (string); if (G_UNLIKELY (len == 0)) goto out; end = string + len; s = string; if (G_UNLIKELY (!is_valid_initial_branch_character (*s))) goto out; s += 1; while (s != end) { if (G_UNLIKELY (!is_valid_branch_character (*s))) goto out; s += 1; } ret = TRUE; out: return ret; } char * xdg_app_build_untyped_ref (const char *runtime, const char *branch, const char *arch) { if (arch == NULL) arch = xdg_app_get_arch (); return g_build_filename (runtime, arch, branch, NULL); } char * xdg_app_build_runtime_ref (const char *runtime, const char *branch, const char *arch) { if (arch == NULL) arch = xdg_app_get_arch (); return g_build_filename ("runtime", runtime, arch, branch, NULL); } char * xdg_app_build_app_ref (const char *app, const char *branch, const char *arch) { if (arch == NULL) arch = xdg_app_get_arch (); return g_build_filename ("app", app, arch, branch, NULL); } char ** xdg_app_list_deployed_refs (const char *type, const char *name_prefix, const char *branch, const char *arch, GCancellable *cancellable, GError **error) { gchar **ret = NULL; g_autoptr(GPtrArray) names = NULL; g_autoptr(GHashTable) hash = NULL; g_autoptr(XdgAppDir) user_dir = NULL; g_autoptr(XdgAppDir) system_dir = NULL; const char *key; GHashTableIter iter; hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); user_dir = xdg_app_dir_get_user (); system_dir = xdg_app_dir_get_system (); if (!xdg_app_dir_collect_deployed_refs (user_dir, type, name_prefix, branch, arch, hash, cancellable, error)) goto out; if (!xdg_app_dir_collect_deployed_refs (system_dir, type, name_prefix, branch, arch, hash, cancellable, error)) goto out; names = g_ptr_array_new (); g_hash_table_iter_init (&iter, hash); while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL)) g_ptr_array_add (names, g_strdup (key)); g_ptr_array_sort (names, (GCompareFunc)g_strcmp0); g_ptr_array_add (names, NULL); ret = (char **)g_ptr_array_free (names, FALSE); names = NULL; out: return ret; } GFile * xdg_app_find_deploy_dir_for_ref (const char *ref, GCancellable *cancellable, GError **error) { g_autoptr(XdgAppDir) user_dir = NULL; g_autoptr(XdgAppDir) system_dir = NULL; GFile *deploy = NULL; user_dir = xdg_app_dir_get_user (); system_dir = xdg_app_dir_get_system (); deploy = xdg_app_dir_get_if_deployed (user_dir, ref, NULL, cancellable); if (deploy == NULL) deploy = xdg_app_dir_get_if_deployed (system_dir, ref, NULL, cancellable); if (deploy == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s not installed", ref); return NULL; } return deploy; } static gboolean overlay_symlink_tree_dir (int source_parent_fd, const char *source_name, const char *source_symlink_prefix, int destination_parent_fd, const char *destination_name, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; int res; g_auto(GLnxDirFdIterator) source_iter = { 0 }; glnx_fd_close int destination_dfd = -1; struct dirent *dent; if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error)) goto out; do res = mkdirat (destination_parent_fd, destination_name, 0777); while (G_UNLIKELY (res == -1 && errno == EINTR)); if (res == -1) { if (errno != EEXIST) { glnx_set_error_from_errno (error); goto out; } } if (!gs_file_open_dir_fd_at (destination_parent_fd, destination_name, &destination_dfd, cancellable, error)) goto out; while (TRUE) { gboolean is_dir = FALSE; if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error)) goto out; if (dent == NULL) break; if (dent->d_type == DT_DIR) is_dir = TRUE; else if (dent->d_type == DT_UNKNOWN) { struct stat stbuf; if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1) { if (errno == ENOENT) continue; else { glnx_set_error_from_errno (error); goto out; } } is_dir = S_ISDIR (stbuf.st_mode); } if (is_dir) { g_autofree gchar *target = g_build_filename ("..", source_symlink_prefix, dent->d_name, NULL); if (!overlay_symlink_tree_dir (source_iter.fd, dent->d_name, target, destination_dfd, dent->d_name, cancellable, error)) goto out; } else { g_autofree gchar *target = g_build_filename (source_symlink_prefix, dent->d_name, NULL); if (unlinkat (destination_dfd, dent->d_name, 0) != 0 && errno != ENOENT) { glnx_set_error_from_errno (error); goto out; } if (symlinkat (target, destination_dfd, dent->d_name) != 0) { glnx_set_error_from_errno (error); goto out; } } } ret = TRUE; out: return ret; } gboolean xdg_app_overlay_symlink_tree (GFile *source, GFile *destination, const char *symlink_prefix, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; if (!gs_file_ensure_directory (destination, TRUE, cancellable, error)) goto out; /* The fds are closed by this call */ if (!overlay_symlink_tree_dir (AT_FDCWD, gs_file_get_path_cached (source), symlink_prefix, AT_FDCWD, gs_file_get_path_cached (destination), cancellable, error)) goto out; ret = TRUE; out: return ret; } static gboolean remove_dangling_symlinks (int parent_fd, const char *name, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; struct dirent *dent; GLnxDirFdIterator iter; if (!glnx_dirfd_iterator_init_at (parent_fd, name, FALSE, &iter, error)) goto out; while (TRUE) { gboolean is_dir = FALSE; gboolean is_link = FALSE; if (!glnx_dirfd_iterator_next_dent (&iter, &dent, cancellable, error)) goto out; if (dent == NULL) break; if (dent->d_type == DT_DIR) is_dir = TRUE; else if (dent->d_type == DT_LNK) is_link = TRUE; else if (dent->d_type == DT_UNKNOWN) { struct stat stbuf; if (fstatat (iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1) { if (errno == ENOENT) continue; else { glnx_set_error_from_errno (error); goto out; } } is_dir = S_ISDIR (stbuf.st_mode); is_link = S_ISLNK (stbuf.st_mode); } if (is_dir) { if (!remove_dangling_symlinks (iter.fd, dent->d_name, cancellable, error)) goto out; } else if (is_link) { struct stat stbuf; if (fstatat (iter.fd, dent->d_name, &stbuf, 0) != 0 && errno == ENOENT) { if (unlinkat (iter.fd, dent->d_name, 0) != 0) { glnx_set_error_from_errno (error); goto out; } } } } ret = TRUE; out: return ret; } gboolean xdg_app_remove_dangling_symlinks (GFile *dir, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; /* The fd is closed by this call */ if (!remove_dangling_symlinks (AT_FDCWD, gs_file_get_path_cached (dir), cancellable, error)) goto out; ret = TRUE; out: return ret; } static gboolean load_contents (const char *uri, GBytes **contents, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; g_autofree char *scheme = NULL; scheme = g_uri_parse_scheme (uri); if (strcmp (scheme, "file") == 0) { char *buffer; gsize length; g_autoptr(GFile) file = NULL; g_debug ("Loading summary %s using GIO", uri); file = g_file_new_for_uri (uri); if (!g_file_load_contents (file, cancellable, &buffer, &length, NULL, NULL)) goto out; *contents = g_bytes_new_take (buffer, length); } else { g_autoptr(SoupSession) session = NULL; g_autoptr(SoupMessage) msg = NULL; g_debug ("Loading summary %s using libsoup", uri); session = soup_session_new (); msg = soup_message_new ("GET", uri); soup_session_send_message (session, msg); if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) goto out; *contents = g_bytes_new (msg->response_body->data, msg->response_body->length); } ret = TRUE; g_debug ("Received %ld bytes", g_bytes_get_size (*contents)); out: return ret; } gboolean ostree_repo_load_summary (const char *repository_url, GHashTable **refs, gchar **title, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; g_autofree char *summary_url = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GHashTable) local_refs = NULL; g_autofree char *local_title = NULL; local_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); summary_url = g_build_filename (repository_url, "summary", NULL); if (load_contents (summary_url, &bytes, cancellable, NULL)) { g_autoptr(GVariant) summary; g_autoptr(GVariant) ref_list; g_autoptr(GVariant) extensions; GVariantDict dict; int i, n; summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, bytes, FALSE); ref_list = g_variant_get_child_value (summary, 0); extensions = g_variant_get_child_value (summary, 1); n = g_variant_n_children (ref_list); g_debug ("Summary contains %d refs", n); for (i = 0; i < n; i++) { g_autoptr(GVariant) ref = NULL; g_autoptr(GVariant) csum_v = NULL; char *refname; char *checksum; ref = g_variant_get_child_value (ref_list, i); g_variant_get (ref, "(&s(t@aya{sv}))", &refname, NULL, &csum_v, NULL); if (!ostree_validate_rev (refname, error)) goto out; checksum = ostree_checksum_from_bytes_v (csum_v); g_debug ("\t%s -> %s", refname, checksum); g_hash_table_insert (local_refs, g_strdup (refname), checksum); } g_variant_dict_init (&dict, extensions); g_variant_dict_lookup (&dict, "xa.title", "s", &local_title); g_debug ("Summary title: %s", local_title); g_variant_dict_end (&dict); } *refs = g_hash_table_ref (local_refs); *title = g_strdup (local_title); ret = TRUE; out: return ret; }