flatpak-builder/xdg-app-utils.c

654 lines
16 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include "xdg-app-utils.h"
#include "xdg-app-dir.h"
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <glib.h>
#include "libgsystem.h"
#include "libglnx/libglnx.h"
#include <libsoup/soup.h>
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;
}