rpm-ostree/src/libpriv/rpmostree-util.c
Colin Walters ff8e2992da libglnx porting: Switch to glnx_file_get_contents_utf8_at()
A small one, but a start.

Closes: #421
Approved by: jlebon
2016-08-08 18:07:46 +00:00

533 lines
14 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2014 Colin Walters <walters@verbum.org>
*
* 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 licence 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, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <string.h>
#include <stdio.h>
#include <glib-unix.h>
#include <json-glib/json-glib.h>
#include <gio/gunixoutputstream.h>
#include "rpmostree-util.h"
#include "libgsystem.h"
#include "libglnx.h"
void
_rpmostree_set_error_from_errno (GError **error,
gint errsv)
{
g_set_error_literal (error,
G_IO_ERROR,
g_io_error_from_errno (errsv),
g_strerror (errsv));
errno = errsv;
}
void
_rpmostree_set_prefix_error_from_errno (GError **error,
gint errsv,
const char *format,
...)
{
g_autofree char *formatted = NULL;
va_list args;
va_start (args, format);
formatted = g_strdup_vprintf (format, args);
va_end (args);
_rpmostree_set_error_from_errno (error, errsv);
g_prefix_error (error, "%s", formatted);
errno = errsv;
}
void
_rpmostree_perror_fatal (const char *message)
{
perror (message);
exit (1);
}
GVariant *
_rpmostree_vardict_lookup_value_required (GVariantDict *dict,
const char *key,
const GVariantType *fmt,
GError **error)
{
GVariant *r = g_variant_dict_lookup_value (dict, key, fmt);
if (!r)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
"Failed to find metadata key %s (signature %s)", key, (char*)fmt);
return NULL;
}
return r;
}
gboolean
rpmostree_mkdtemp (const char *template,
char **out_tmpdir,
int *out_tmpdir_dfd, /* allow-none */
GError **error)
{
gboolean ret = FALSE;
g_autofree char *tmpdir = g_strdup (template);
gboolean created_tmpdir = FALSE;
glnx_fd_close int ret_tmpdir_dfd = -1;
if (mkdtemp (tmpdir) == NULL)
{
glnx_set_error_from_errno (error);
goto out;
}
created_tmpdir = TRUE;
if (out_tmpdir_dfd)
{
if (!glnx_opendirat (AT_FDCWD, tmpdir, FALSE, &ret_tmpdir_dfd, error))
goto out;
}
ret = TRUE;
*out_tmpdir = g_steal_pointer (&tmpdir);
if (out_tmpdir_dfd)
{
*out_tmpdir_dfd = ret_tmpdir_dfd;
ret_tmpdir_dfd = -1;
}
out:
if (created_tmpdir && tmpdir)
{
(void) glnx_shutil_rm_rf_at (AT_FDCWD, tmpdir, NULL, NULL);
}
return ret;
}
/* Given a string of the form
* "bla blah ${foo} blah ${bar}"
* and a hash table of variables, substitute the variable values.
*/
char *
_rpmostree_varsubst_string (const char *instr,
GHashTable *substitutions,
GError **error)
{
const char *s;
const char *p;
/* Acts as a reusable buffer space */
g_autoptr(GString) varnamebuf = g_string_new ("");
g_autoptr(GString) result = g_string_new ("");
s = instr;
while ((p = strstr (s, "${")) != NULL)
{
const char *varstart = p + 2;
const char *varend = strchr (varstart, '}');
const char *value;
if (!varend)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Unclosed variable reference starting at %u bytes",
(guint)(p - instr));
return NULL;
}
/* Append leading bytes */
g_string_append_len (result, s, p - s);
/* Get a NUL-terminated copy of the variable name */
g_string_truncate (varnamebuf, 0);
g_string_append_len (varnamebuf, varstart, varend - varstart);
value = g_hash_table_lookup (substitutions, varnamebuf->str);
if (!value)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Unknown variable reference ${%s}",
varnamebuf->str);
return NULL;
}
/* Append the replaced value */
g_string_append (result, value);
/* On to the next */
s = varend+1;
}
if (s != instr)
{
char *r;
g_string_append_len (result, s, p - s);
/* Steal the C string, NULL out the GString since we freed it */
r = g_string_free (result, FALSE);
result = NULL;
return r;
}
else
return g_strdup (instr);
}
gboolean
_rpmostree_util_enumerate_directory_allow_noent (GFile *dirpath,
const char *queryargs,
GFileQueryInfoFlags queryflags,
GFileEnumerator **out_direnum,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
GError *temp_error = NULL;
g_autoptr(GFileEnumerator) ret_direnum = NULL;
ret_direnum = g_file_enumerate_children (dirpath, queryargs, queryflags,
cancellable, &temp_error);
if (!ret_direnum)
{
if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
{
g_clear_error (&temp_error);
ret = TRUE;
}
else
g_propagate_error (error, temp_error);
goto out;
}
ret = TRUE;
gs_transfer_out_value (out_direnum, &ret_direnum);
out:
return ret;
}
gboolean
_rpmostree_file_load_contents_utf8_allow_noent (GFile *path,
char **out_contents,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
GError *temp_error = NULL;
g_autofree char *ret_contents = NULL;
ret_contents = glnx_file_get_contents_utf8_at (AT_FDCWD, gs_file_get_path_cached (path), NULL,
cancellable, &temp_error);
if (!ret_contents)
{
if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
{
g_clear_error (&temp_error);
}
else
{
g_propagate_error (error, temp_error);
goto out;
}
}
ret = TRUE;
gs_transfer_out_value (out_contents, &ret_contents);
out:
return ret;
}
gboolean
_rpmostree_util_update_checksum_from_file (GChecksum *checksum,
GFile *src,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
gsize bytes_read;
char buf[4096];
g_autoptr(GInputStream) filein = NULL;
filein = (GInputStream*)g_file_read (src, cancellable, error);
if (!filein)
goto out;
do
{
if (!g_input_stream_read_all (filein, buf, sizeof(buf), &bytes_read,
cancellable, error))
goto out;
g_checksum_update (checksum, (guint8*)buf, bytes_read);
}
while (bytes_read > 0);
ret = TRUE;
out:
return ret;
}
static char *
ost_get_prev_commit (OstreeRepo *repo, char *checksum)
{
char *ret = NULL;
g_autoptr(GVariant) commit = NULL;
g_autoptr(GVariant) parent_csum_v = NULL;
GError *tmp_error = NULL;
if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, checksum,
&commit, &tmp_error))
goto out;
ret = ostree_commit_get_parent (commit);
out:
g_clear_error (&tmp_error);
return ret;
}
GPtrArray *
_rpmostree_util_get_commit_hashes (OstreeRepo *repo,
const char *beg,
const char *end,
GCancellable *cancellable,
GError **error)
{
GPtrArray *ret = NULL;
g_autofree char *beg_checksum = NULL;
g_autofree char *end_checksum = NULL;
g_autofree char *parent = NULL;
char *checksum = NULL;
gboolean worked = FALSE;
if (!ostree_repo_read_commit (repo, beg, NULL, &beg_checksum, cancellable, error))
goto out;
ret = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (ret, g_strdup (beg)); /* Add the user defined REFSPEC. */
if (end &&
!ostree_repo_read_commit (repo, end, NULL, &end_checksum, cancellable, error))
goto out;
if (end && g_str_equal (end_checksum, beg_checksum))
goto worked_out;
checksum = beg_checksum;
while ((parent = ost_get_prev_commit (repo, checksum)))
{
if (end && g_str_equal (end_checksum, parent))
{ /* Add the user defined REFSPEC. */
g_ptr_array_add (ret, g_strdup (end));
break;
}
g_ptr_array_add (ret, parent);
checksum = parent;
}
if (end && !parent)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid ref range: %s is not a parent of %s", end, beg);
goto out;
}
worked_out:
worked = TRUE;
out:
if (!worked)
{
g_ptr_array_free (ret, TRUE);
ret = NULL;
}
return ret;
}
gboolean
_rpmostree_sync_wait_on_pid (pid_t pid,
GError **error)
{
gboolean ret = FALSE;
pid_t r;
int estatus;
do
r = waitpid (pid, &estatus, 0);
while (G_UNLIKELY (r == -1 && errno == EINTR));
if (r == -1)
{
_rpmostree_set_prefix_error_from_errno (error, errno, "waitpid: ");
goto out;
}
if (!g_spawn_check_exit_status (estatus, error))
goto out;
ret = TRUE;
out:
return ret;
}
char *
_rpmostree_util_next_version (const char *auto_version_prefix,
const char *last_version)
{
unsigned long long num = 0;
const char *end = NULL;
if (!last_version || !g_str_has_prefix (last_version, auto_version_prefix))
return g_strdup (auto_version_prefix);
if (g_str_equal (last_version, auto_version_prefix))
return g_strdup_printf ("%s.1", auto_version_prefix);
end = last_version + strlen(auto_version_prefix);
if (*end != '.')
return g_strdup (auto_version_prefix);
++end;
num = g_ascii_strtoull (end, NULL, 10);
return g_strdup_printf ("%s.%llu", auto_version_prefix, num + 1);
}
GKeyFile *
_rpmostree_util_keyfile_clone (GKeyFile *keyfile)
{
GKeyFile *ret = g_key_file_new ();
gsize len;
g_autofree char *data = g_key_file_to_data (keyfile, &len, NULL);
gboolean loaded;
loaded = g_key_file_load_from_data (ret, data, len, 0, NULL);
g_assert (loaded);
return ret;
}
gboolean
_rpmostree_util_parse_origin (GKeyFile *origin,
char **out_refspec,
char ***out_packages,
GError **error)
{
gboolean ret = FALSE;
g_autofree char *origin_refspec = NULL;
gboolean origin_is_bare_refspec = TRUE;
origin_refspec = g_key_file_get_string (origin, "origin", "refspec", NULL);
if (!origin_refspec)
{
origin_refspec = g_key_file_get_string (origin, "origin", "baserefspec", NULL);
origin_is_bare_refspec = FALSE;
}
if (!origin_refspec)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"No origin/refspec or origin/baserefspec in current deployment origin; cannot upgrade via rpm-ostree");
goto out;
}
if (out_refspec)
*out_refspec = g_steal_pointer (&origin_refspec);
if (out_packages)
{
if (origin_is_bare_refspec)
*out_packages = NULL;
else
*out_packages = g_key_file_get_string_list (origin, "packages", "requested", NULL, NULL);
}
ret = TRUE;
out:
return ret;
}
gboolean
rpmostree_split_path_ptrarray_validate (const char *path,
GPtrArray **out_components,
GError **error)
{
gboolean ret = FALSE;
g_autoptr(GPtrArray) ret_components = NULL;
if (strlen (path) > PATH_MAX)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Path '%s' is too long", path);
goto out;
}
ret_components = g_ptr_array_new_with_free_func (g_free);
do
{
const char *p = strchr (path, '/');
g_autofree char *component = NULL;
if (!p)
{
component = g_strdup (path);
path = NULL;
}
else
{
component = g_strndup (path, p - path);
path = p + 1;
}
if (!component[0])
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid empty component in path '%s'", path);
goto out;
}
if (g_str_equal (component, ".") ||
g_str_equal (component, ".."))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid special element '.' or '..' in path %s", path);
goto out;
}
g_ptr_array_add (ret_components, (char*)g_steal_pointer (&component));
} while (path && *path);
ret = TRUE;
*out_components = g_steal_pointer (&ret_components);
out:
return ret;
}
/* Replace every occurrence of @old in @buf with @new. */
char *
rpmostree_str_replace (const char *buf,
const char *old,
const char *new,
GError **error)
{
g_autofree char *literal_old = g_regex_escape_string (old, -1);
g_autoptr(GRegex) regex = g_regex_new (literal_old, 0, 0, error);
if (regex == NULL)
return NULL;
return g_regex_replace_literal (regex, buf, -1, 0, new, 0, error);
}