2723 lines
86 KiB
C
2723 lines
86 KiB
C
/*
|
||
* Copyright (C) 2011 Colin Walters <walters@verbum.org>
|
||
*
|
||
* SPDX-License-Identifier: LGPL-2.0+
|
||
*
|
||
* This library 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, write to the
|
||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||
* Boston, MA 02111-1307, USA.
|
||
*
|
||
* Author: Colin Walters <walters@verbum.org>
|
||
*/
|
||
|
||
#include "config.h"
|
||
|
||
#include <sys/types.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <gio/gfiledescriptorbased.h>
|
||
#include <gio/gunixinputstream.h>
|
||
#include "libglnx.h"
|
||
#include "ostree.h"
|
||
#include "ostree-core-private.h"
|
||
#include "ostree-chain-input-stream.h"
|
||
#include "ostree-varint.h"
|
||
#include "otutil.h"
|
||
|
||
/* Generic ABI checks */
|
||
G_STATIC_ASSERT(OSTREE_REPO_MODE_BARE == 0);
|
||
G_STATIC_ASSERT(OSTREE_REPO_MODE_ARCHIVE_Z2 == 1);
|
||
G_STATIC_ASSERT(OSTREE_REPO_MODE_ARCHIVE == OSTREE_REPO_MODE_ARCHIVE_Z2);
|
||
G_STATIC_ASSERT(OSTREE_REPO_MODE_BARE_USER == 2);
|
||
G_STATIC_ASSERT(OSTREE_REPO_MODE_BARE_USER_ONLY == 3);
|
||
|
||
static GBytes *variant_to_lenprefixed_buffer (GVariant *variant);
|
||
|
||
#define ALIGN_VALUE(this, boundary) \
|
||
(( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
|
||
|
||
/* Return a copy of @input suitable for addition to
|
||
* a GError message; newlines are quashed, the value
|
||
* is forced to be UTF-8, is truncated to @maxlen (if maxlen != -1).
|
||
*/
|
||
static char *
|
||
quash_string_for_error_message (const char *input,
|
||
ssize_t len,
|
||
ssize_t maxlen)
|
||
{
|
||
if (len == -1)
|
||
len = strlen (input);
|
||
if (maxlen != -1 && maxlen < len)
|
||
len = maxlen;
|
||
#if GLIB_CHECK_VERSION(2, 52, 0)
|
||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||
char *buf = g_utf8_make_valid (input, len);
|
||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||
#else
|
||
char *buf = g_strndup (input, len);
|
||
#endif
|
||
for (char *iter = buf; iter && *iter; iter++)
|
||
{
|
||
char c = *iter;
|
||
if (c == '\n')
|
||
*iter = ' ';
|
||
#if !GLIB_CHECK_VERSION(2, 52, 0)
|
||
/* No g_utf8_make_valid()? OK, let's just brute force this. */
|
||
if (!g_ascii_isprint (c))
|
||
*iter = ' ';
|
||
#endif
|
||
}
|
||
return buf;
|
||
}
|
||
|
||
static gboolean
|
||
file_header_parse (GVariant *metadata,
|
||
GFileInfo **out_file_info,
|
||
GVariant **out_xattrs,
|
||
GError **error);
|
||
static gboolean
|
||
zlib_file_header_parse (GVariant *metadata,
|
||
GFileInfo **out_file_info,
|
||
GVariant **out_xattrs,
|
||
GError **error);
|
||
|
||
/**
|
||
* SECTION:ostree-core
|
||
* @title: Core repository-independent functions
|
||
* @short_description: Create, validate, and convert core data types
|
||
*
|
||
* These functions implement repository-independent algorithms for
|
||
* operating on the core OSTree data formats, such as converting
|
||
* #GFileInfo into a #GVariant.
|
||
*
|
||
* There are 4 types of objects; file, dirmeta, tree, and commit. The
|
||
* last 3 are metadata, and the file object is the only content object
|
||
* type.
|
||
*
|
||
* All metadata objects are stored as #GVariant (big endian). The
|
||
* rationale for this is the same as that of the ext{2,3,4} family of
|
||
* filesystems; most developers will be using LE, and so it's better
|
||
* to continually test the BE->LE swap.
|
||
*
|
||
* The file object is a custom format in order to support streaming.
|
||
*/
|
||
|
||
const GVariantType *
|
||
ostree_metadata_variant_type (OstreeObjectType objtype)
|
||
{
|
||
switch (objtype)
|
||
{
|
||
case OSTREE_OBJECT_TYPE_DIR_TREE:
|
||
return OSTREE_TREE_GVARIANT_FORMAT;
|
||
case OSTREE_OBJECT_TYPE_DIR_META:
|
||
return OSTREE_DIRMETA_GVARIANT_FORMAT;
|
||
case OSTREE_OBJECT_TYPE_COMMIT:
|
||
return OSTREE_COMMIT_GVARIANT_FORMAT;
|
||
default:
|
||
g_assert_not_reached ();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ostree_validate_checksum_string:
|
||
* @sha256: SHA256 hex string
|
||
* @error: Error
|
||
*
|
||
* Use this function to see if input strings are checksums.
|
||
*
|
||
* Returns: %TRUE if @sha256 is a valid checksum string, %FALSE otherwise
|
||
*/
|
||
gboolean
|
||
ostree_validate_checksum_string (const char *sha256,
|
||
GError **error)
|
||
{
|
||
return ostree_validate_structureof_checksum_string (sha256, error);
|
||
}
|
||
|
||
/* This used to allow leading - and ., but was changed in
|
||
* https://github.com/ostreedev/ostree/pull/1286
|
||
*/
|
||
#define OSTREE_REF_FRAGMENT_REGEXP "[\\w\\d][-._\\w\\d]*"
|
||
#define OSTREE_REF_REGEXP "(?:" OSTREE_REF_FRAGMENT_REGEXP "/)*" OSTREE_REF_FRAGMENT_REGEXP
|
||
#define OSTREE_REMOTE_NAME_REGEXP OSTREE_REF_FRAGMENT_REGEXP
|
||
|
||
/**
|
||
* ostree_parse_refspec:
|
||
* @refspec: A "refspec" string
|
||
* @out_remote: (out) (nullable) (optional): Return location for the remote name,
|
||
* or %NULL if the refspec refs to a local ref
|
||
* @out_ref: (out) (not nullable) (optional): Return location for the ref name
|
||
* @error: Error
|
||
*
|
||
* Split a refspec like `gnome-ostree:gnome-ostree/buildmaster` or just
|
||
* `gnome-ostree/buildmaster` into two parts. In the first case, @out_remote
|
||
* will be set to `gnome-ostree`, and @out_ref to `gnome-ostree/buildmaster`.
|
||
* In the second case (a local ref), @out_remote will be %NULL, and @out_ref
|
||
* will be `gnome-ostree/buildmaster`. In both cases, %TRUE will be returned.
|
||
*
|
||
* Returns: %TRUE on successful parsing, %FALSE otherwise
|
||
*/
|
||
gboolean
|
||
ostree_parse_refspec (const char *refspec,
|
||
char **out_remote,
|
||
char **out_ref,
|
||
GError **error)
|
||
{
|
||
static GRegex *regex;
|
||
static gsize regex_initialized;
|
||
if (g_once_init_enter (®ex_initialized))
|
||
{
|
||
regex = g_regex_new ("^(" OSTREE_REMOTE_NAME_REGEXP ":)?(" OSTREE_REF_REGEXP ")$", 0, 0, NULL);
|
||
g_assert (regex);
|
||
g_once_init_leave (®ex_initialized, 1);
|
||
}
|
||
|
||
g_autoptr(GMatchInfo) match = NULL;
|
||
if (!g_regex_match (regex, refspec, 0, &match))
|
||
return glnx_throw (error, "Invalid refspec %s", refspec);
|
||
|
||
g_autofree char *remote = g_match_info_fetch (match, 1);
|
||
if (*remote == '\0')
|
||
{
|
||
g_clear_pointer (&remote, g_free);
|
||
}
|
||
else
|
||
{
|
||
/* Trim the : */
|
||
remote[strlen(remote)-1] = '\0';
|
||
}
|
||
|
||
if (out_remote)
|
||
*out_remote = g_steal_pointer (&remote);
|
||
if (out_ref != NULL)
|
||
*out_ref = g_match_info_fetch (match, 2);
|
||
return TRUE;
|
||
}
|
||
|
||
gboolean
|
||
_ostree_validate_ref_fragment (const char *fragment,
|
||
GError **error)
|
||
{
|
||
static GRegex *regex;
|
||
static gsize regex_initialized;
|
||
if (g_once_init_enter (®ex_initialized))
|
||
{
|
||
regex = g_regex_new ("^" OSTREE_REF_FRAGMENT_REGEXP "$", 0, 0, NULL);
|
||
g_assert (regex);
|
||
g_once_init_leave (®ex_initialized, 1);
|
||
}
|
||
|
||
g_autoptr(GMatchInfo) match = NULL;
|
||
if (!g_regex_match (regex, fragment, 0, &match))
|
||
return glnx_throw (error, "Invalid ref fragment '%s'", fragment);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_validate_rev:
|
||
* @rev: A revision string
|
||
* @error: Error
|
||
*
|
||
* Returns: %TRUE if @rev is a valid ref string
|
||
*/
|
||
gboolean
|
||
ostree_validate_rev (const char *rev,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GMatchInfo) match = NULL;
|
||
|
||
static gsize regex_initialized;
|
||
static GRegex *regex;
|
||
if (g_once_init_enter (®ex_initialized))
|
||
{
|
||
regex = g_regex_new ("^" OSTREE_REF_REGEXP "$", 0, 0, NULL);
|
||
g_assert (regex);
|
||
g_once_init_leave (®ex_initialized, 1);
|
||
}
|
||
|
||
if (!g_regex_match (regex, rev, 0, &match))
|
||
return glnx_throw (error, "Invalid ref name %s", rev);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_validate_remote_name:
|
||
* @remote_name: A remote name
|
||
* @error: Error
|
||
*
|
||
* Returns: %TRUE if @remote_name is a valid remote name
|
||
* Since: 2017.8
|
||
*/
|
||
gboolean
|
||
ostree_validate_remote_name (const char *remote_name,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GMatchInfo) match = NULL;
|
||
|
||
static gsize regex_initialized;
|
||
static GRegex *regex;
|
||
if (g_once_init_enter (®ex_initialized))
|
||
{
|
||
regex = g_regex_new ("^" OSTREE_REMOTE_NAME_REGEXP "$", 0, 0, NULL);
|
||
g_assert (regex);
|
||
g_once_init_leave (®ex_initialized, 1);
|
||
}
|
||
|
||
if (!g_regex_match (regex, remote_name, 0, &match))
|
||
return glnx_throw (error, "Invalid remote name %s", remote_name);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_validate_collection_id:
|
||
* @collection_id: (nullable): A collection ID
|
||
* @error: Error
|
||
*
|
||
* Check whether the given @collection_id is valid. Return an error if it is
|
||
* invalid or %NULL.
|
||
*
|
||
* Valid collection IDs are reverse DNS names:
|
||
* * They are composed of 1 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]_` and must not
|
||
* begin with a digit.
|
||
* * They must contain at least one `.` (period) character (and thus at least two elements).
|
||
* * They must not begin with a `.` (period) character.
|
||
* * They must not exceed 255 characters in length.
|
||
*
|
||
* (This makes their format identical to D-Bus interface names, for consistency.)
|
||
*
|
||
* Returns: %TRUE if @collection_id is a valid collection ID, %FALSE if it is invalid
|
||
* or %NULL
|
||
* Since: 2018.6
|
||
*/
|
||
gboolean
|
||
ostree_validate_collection_id (const char *collection_id, GError **error)
|
||
{
|
||
/* Abuse g_dbus_is_interface_name(), since collection IDs have the same format. */
|
||
if (collection_id == NULL || !g_dbus_is_interface_name (collection_id))
|
||
return glnx_throw (error, "Invalid collection ID %s", collection_id);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* The file header is part of the "object stream" format
|
||
* that's not compressed. It's comprised of uid,gid,mode,
|
||
* and possibly symlink targets from @file_info, as well
|
||
* as @xattrs (which if NULL, is taken to be the empty set).
|
||
*/
|
||
GBytes *
|
||
_ostree_file_header_new (GFileInfo *file_info,
|
||
GVariant *xattrs)
|
||
{
|
||
|
||
guint32 uid = g_file_info_get_attribute_uint32 (file_info, "unix::uid");
|
||
guint32 gid = g_file_info_get_attribute_uint32 (file_info, "unix::gid");
|
||
guint32 mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
|
||
|
||
const char *symlink_target;
|
||
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK)
|
||
symlink_target = g_file_info_get_symlink_target (file_info);
|
||
else
|
||
symlink_target = "";
|
||
|
||
g_autoptr(GVariant) tmp_xattrs = NULL;
|
||
if (xattrs == NULL)
|
||
tmp_xattrs = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0));
|
||
|
||
g_autoptr(GVariant) ret = g_variant_new ("(uuuus@a(ayay))", GUINT32_TO_BE (uid),
|
||
GUINT32_TO_BE (gid), GUINT32_TO_BE (mode), 0,
|
||
symlink_target, xattrs ?: tmp_xattrs);
|
||
return variant_to_lenprefixed_buffer (g_variant_ref_sink (ret));
|
||
}
|
||
|
||
/* Like _ostree_file_header_new(), but used for the compressed format in archive
|
||
* repositories. This format hence lives on disk; normally the uncompressed
|
||
* stream format doesn't. Instead for "bare" repositories, the file data is
|
||
* stored directly, or for the special case of bare-user repositories, as a
|
||
* user.ostreemeta xattr.
|
||
*/
|
||
GBytes *
|
||
_ostree_zlib_file_header_new (GFileInfo *file_info,
|
||
GVariant *xattrs)
|
||
{
|
||
guint64 size = g_file_info_get_size (file_info);
|
||
guint32 uid = g_file_info_get_attribute_uint32 (file_info, "unix::uid");
|
||
guint32 gid = g_file_info_get_attribute_uint32 (file_info, "unix::gid");
|
||
guint32 mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
|
||
|
||
const char *symlink_target;
|
||
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK)
|
||
symlink_target = g_file_info_get_symlink_target (file_info);
|
||
else
|
||
symlink_target = "";
|
||
|
||
g_autoptr(GVariant) tmp_xattrs = NULL;
|
||
if (xattrs == NULL)
|
||
tmp_xattrs = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0));
|
||
|
||
g_autoptr(GVariant) ret = g_variant_new ("(tuuuus@a(ayay))",
|
||
GUINT64_TO_BE (size), GUINT32_TO_BE (uid),
|
||
GUINT32_TO_BE (gid), GUINT32_TO_BE (mode), 0,
|
||
symlink_target, xattrs ?: tmp_xattrs);
|
||
return variant_to_lenprefixed_buffer (g_variant_ref_sink (ret));
|
||
}
|
||
|
||
/* Serialize a variant to a buffer prefixed with its length. The variant will
|
||
* have an 8-byte alignment so it can be safely used with `mmap()`.
|
||
*/
|
||
static GBytes *
|
||
variant_to_lenprefixed_buffer (GVariant *variant)
|
||
{
|
||
/* This string is really a binary memory buffer */
|
||
g_autoptr(GString) buf = g_string_new (NULL);
|
||
/* Write variant size */
|
||
const guint64 variant_size = g_variant_get_size (variant);
|
||
g_assert (variant_size < G_MAXUINT32);
|
||
const guint32 variant_size_u32_be = GUINT32_TO_BE((guint32) variant_size);
|
||
|
||
g_string_append_len (buf, (char*)&variant_size_u32_be, sizeof (variant_size_u32_be));
|
||
const gsize alignment_offset = sizeof (variant_size_u32_be);
|
||
|
||
/* Write NULs for alignment. At the moment this is a constant 4 bytes (i.e.
|
||
* align to 8, since the length is 4 bytes). For now, I decided to keep some
|
||
* of the (now legacy) more generic logic here in case we want to revive it
|
||
* later.
|
||
*/
|
||
const guint alignment = 8;
|
||
const guchar padding_nuls[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||
guint bits;
|
||
if (alignment == 8)
|
||
bits = alignment_offset & 7; /* mod 8 */
|
||
else
|
||
bits = alignment_offset & 3; /* mod 4 */
|
||
const guint padding_len = alignment - bits;
|
||
|
||
if (bits > 0)
|
||
g_string_append_len (buf, (char*)padding_nuls, padding_len);
|
||
|
||
g_string_append_len (buf, (char*)g_variant_get_data (variant), g_variant_get_size (variant));
|
||
return g_string_free_to_bytes (g_steal_pointer (&buf));
|
||
}
|
||
|
||
/*
|
||
* header_and_input_to_stream:
|
||
* @file_header: A file header
|
||
* @input: File raw content stream
|
||
* @out_input: (out): Serialized object stream
|
||
* @cancellable: Cancellable
|
||
* @error: Error
|
||
*
|
||
* Combines @file_header and @input into a single stream.
|
||
*/
|
||
static gboolean
|
||
header_and_input_to_stream (GBytes *file_header,
|
||
GInputStream *input,
|
||
GInputStream **out_input,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
/* Our result stream chain */
|
||
g_autoptr(GPtrArray) streams = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
|
||
|
||
/* Append the header to the chain */
|
||
g_autoptr(GInputStream) header_in_stream = g_memory_input_stream_new_from_bytes (file_header);
|
||
|
||
g_ptr_array_add (streams, g_object_ref (header_in_stream));
|
||
|
||
/* And if we have an input stream, append that */
|
||
if (input)
|
||
g_ptr_array_add (streams, g_object_ref (input));
|
||
|
||
/* Return the result stream */
|
||
g_autoptr(GInputStream) ret_input = (GInputStream*)ostree_chain_input_stream_new (streams);
|
||
ot_transfer_out_value (out_input, &ret_input);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Convert file metadata + file content into an archive-format stream. */
|
||
gboolean
|
||
_ostree_raw_file_to_archive_stream (GInputStream *input,
|
||
GFileInfo *file_info,
|
||
GVariant *xattrs,
|
||
guint compression_level,
|
||
GInputStream **out_input,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GInputStream) zlib_input = NULL;
|
||
if (input != NULL)
|
||
{
|
||
g_autoptr(GConverter) zlib_compressor =
|
||
G_CONVERTER (g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW, compression_level));
|
||
zlib_input = g_converter_input_stream_new (input, zlib_compressor);
|
||
}
|
||
g_autoptr(GBytes) file_header = _ostree_zlib_file_header_new (file_info, xattrs);
|
||
return header_and_input_to_stream (file_header,
|
||
zlib_input,
|
||
out_input,
|
||
cancellable,
|
||
error);
|
||
}
|
||
|
||
/**
|
||
* ostree_raw_file_to_archive_z2_stream:
|
||
* @input: File raw content stream
|
||
* @file_info: A file info
|
||
* @xattrs: (allow-none): Optional extended attributes
|
||
* @out_input: (out): Serialized object stream
|
||
* @cancellable: Cancellable
|
||
* @error: Error
|
||
*
|
||
* Convert from a "bare" file representation into an
|
||
* OSTREE_OBJECT_TYPE_FILE stream suitable for ostree pull.
|
||
*
|
||
* Since: 2016.6
|
||
*/
|
||
gboolean
|
||
ostree_raw_file_to_archive_z2_stream (GInputStream *input,
|
||
GFileInfo *file_info,
|
||
GVariant *xattrs,
|
||
GInputStream **out_input,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
return _ostree_raw_file_to_archive_stream (input, file_info, xattrs,
|
||
OSTREE_ARCHIVE_DEFAULT_COMPRESSION_LEVEL,
|
||
out_input, cancellable, error);
|
||
}
|
||
|
||
/**
|
||
* ostree_raw_file_to_archive_z2_stream_with_options:
|
||
* @input: File raw content stream
|
||
* @file_info: A file info
|
||
* @xattrs: (allow-none): Optional extended attributes
|
||
* @options: (nullable): A GVariant `a{sv}` with an extensible set of flags
|
||
* @out_input: (out): Serialized object stream
|
||
* @cancellable: Cancellable
|
||
* @error: Error
|
||
*
|
||
* Like ostree_raw_file_to_archive_z2_stream(), but supports an extensible set
|
||
* of flags. The following flags are currently defined:
|
||
*
|
||
* - `compression-level` (`i`): Level of compression to use, 0–9, with 0 being
|
||
* the least compression, and <0 giving the default level (currently 6).
|
||
*
|
||
* Since: 2017.3
|
||
*/
|
||
gboolean
|
||
ostree_raw_file_to_archive_z2_stream_with_options (GInputStream *input,
|
||
GFileInfo *file_info,
|
||
GVariant *xattrs,
|
||
GVariant *options,
|
||
GInputStream **out_input,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
gint compression_level = -1;
|
||
|
||
if (options)
|
||
(void) g_variant_lookup (options, "compression-level", "i", &compression_level);
|
||
|
||
if (compression_level < 0)
|
||
compression_level = OSTREE_ARCHIVE_DEFAULT_COMPRESSION_LEVEL;
|
||
|
||
return _ostree_raw_file_to_archive_stream (input, file_info, xattrs,
|
||
compression_level,
|
||
out_input, cancellable, error);
|
||
}
|
||
|
||
/**
|
||
* ostree_raw_file_to_content_stream:
|
||
* @input: File raw content stream
|
||
* @file_info: A file info
|
||
* @xattrs: (allow-none): Optional extended attributes
|
||
* @out_input: (out): Serialized object stream
|
||
* @out_length: (out): Length of stream
|
||
* @cancellable: Cancellable
|
||
* @error: Error
|
||
*
|
||
* Convert from a "bare" file representation into an
|
||
* OSTREE_OBJECT_TYPE_FILE stream. This is a fundamental operation
|
||
* for writing data to an #OstreeRepo.
|
||
*/
|
||
gboolean
|
||
ostree_raw_file_to_content_stream (GInputStream *input,
|
||
GFileInfo *file_info,
|
||
GVariant *xattrs,
|
||
GInputStream **out_input,
|
||
guint64 *out_length,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autoptr(GBytes) file_header = _ostree_file_header_new (file_info, xattrs);
|
||
if (!header_and_input_to_stream (file_header,
|
||
input,
|
||
out_input,
|
||
cancellable,
|
||
error))
|
||
return FALSE;
|
||
if (out_length)
|
||
*out_length = g_bytes_get_size (file_header) + g_file_info_get_size (file_info);
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_content_stream_parse:
|
||
* @compressed: Whether or not the stream is zlib-compressed
|
||
* @input: Object content stream
|
||
* @input_length: Length of stream
|
||
* @trusted: If %TRUE, assume the content has been validated
|
||
* @out_input: (out): The raw file content stream
|
||
* @out_file_info: (out): Normal metadata
|
||
* @out_xattrs: (out): Extended attributes
|
||
* @cancellable: Cancellable
|
||
* @error: Error
|
||
*
|
||
* The reverse of ostree_raw_file_to_content_stream(); this function
|
||
* converts an object content stream back into components.
|
||
*/
|
||
gboolean
|
||
ostree_content_stream_parse (gboolean compressed,
|
||
GInputStream *input,
|
||
guint64 input_length,
|
||
gboolean trusted,
|
||
GInputStream **out_input,
|
||
GFileInfo **out_file_info,
|
||
GVariant **out_xattrs,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
guint32 archive_header_size;
|
||
guchar dummy[4];
|
||
gsize bytes_read;
|
||
|
||
if (!g_input_stream_read_all (input,
|
||
&archive_header_size, 4, &bytes_read,
|
||
cancellable, error))
|
||
return FALSE;
|
||
archive_header_size = GUINT32_FROM_BE (archive_header_size);
|
||
if (archive_header_size > input_length)
|
||
return glnx_throw (error, "File header size %u exceeds size %" G_GUINT64_FORMAT,
|
||
(guint)archive_header_size, input_length);
|
||
else if (archive_header_size == 0)
|
||
return glnx_throw (error, "File header size is zero");
|
||
|
||
/* Skip over padding */
|
||
if (!g_input_stream_read_all (input,
|
||
dummy, 4, &bytes_read,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
g_autofree guchar *buf = g_malloc (archive_header_size);
|
||
if (!g_input_stream_read_all (input, buf, archive_header_size, &bytes_read,
|
||
cancellable, error))
|
||
return FALSE;
|
||
g_autoptr(GVariant) file_header =
|
||
g_variant_ref_sink(g_variant_new_from_data (compressed ? _OSTREE_ZLIB_FILE_HEADER_GVARIANT_FORMAT : _OSTREE_FILE_HEADER_GVARIANT_FORMAT,
|
||
buf, archive_header_size, trusted,
|
||
g_free, buf));
|
||
buf = NULL;
|
||
g_autoptr(GFileInfo) ret_file_info = NULL;
|
||
g_autoptr(GVariant) ret_xattrs = NULL;
|
||
if (compressed)
|
||
{
|
||
if (!zlib_file_header_parse (file_header,
|
||
&ret_file_info,
|
||
out_xattrs ? &ret_xattrs : NULL,
|
||
error))
|
||
return FALSE;
|
||
}
|
||
else
|
||
{
|
||
if (!file_header_parse (file_header,
|
||
&ret_file_info,
|
||
out_xattrs ? &ret_xattrs : NULL,
|
||
error))
|
||
return FALSE;
|
||
g_file_info_set_size (ret_file_info, input_length - archive_header_size - 8);
|
||
}
|
||
|
||
g_autoptr(GInputStream) ret_input = NULL;
|
||
if (g_file_info_get_file_type (ret_file_info) == G_FILE_TYPE_REGULAR
|
||
&& out_input)
|
||
{
|
||
/* Give the input stream at its current position as return value;
|
||
* assuming the caller doesn't seek, this should be fine. We might
|
||
* want to wrap it though in a non-seekable stream.
|
||
**/
|
||
if (compressed)
|
||
{
|
||
g_autoptr(GConverter) zlib_decomp = (GConverter*)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW);
|
||
ret_input = g_converter_input_stream_new (input, zlib_decomp);
|
||
}
|
||
else
|
||
ret_input = g_object_ref (input);
|
||
}
|
||
|
||
ot_transfer_out_value (out_input, &ret_input);
|
||
ot_transfer_out_value (out_file_info, &ret_file_info);
|
||
ot_transfer_out_value (out_xattrs, &ret_xattrs);
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_content_file_parse_at:
|
||
* @compressed: Whether or not the stream is zlib-compressed
|
||
* @parent_dfd: Directory file descriptor
|
||
* @path: Subpath
|
||
* @trusted: If %TRUE, assume the content has been validated
|
||
* @out_input: (out): The raw file content stream
|
||
* @out_file_info: (out): Normal metadata
|
||
* @out_xattrs: (out): Extended attributes
|
||
* @cancellable: Cancellable
|
||
* @error: Error
|
||
*
|
||
* A thin wrapper for ostree_content_stream_parse(); this function
|
||
* converts an object content stream back into components.
|
||
*/
|
||
gboolean
|
||
ostree_content_file_parse_at (gboolean compressed,
|
||
int parent_dfd,
|
||
const char *path,
|
||
gboolean trusted,
|
||
GInputStream **out_input,
|
||
GFileInfo **out_file_info,
|
||
GVariant **out_xattrs,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
glnx_autofd int fd = -1;
|
||
if (!glnx_openat_rdonly (parent_dfd, path, TRUE, &fd, error))
|
||
return FALSE;
|
||
|
||
struct stat stbuf;
|
||
if (!glnx_fstat (fd, &stbuf, error))
|
||
return FALSE;
|
||
|
||
g_autoptr(GInputStream) file_input = g_unix_input_stream_new (glnx_steal_fd (&fd), TRUE);
|
||
|
||
g_autoptr(GFileInfo) ret_file_info = NULL;
|
||
g_autoptr(GVariant) ret_xattrs = NULL;
|
||
g_autoptr(GInputStream) ret_input = NULL;
|
||
if (!ostree_content_stream_parse (compressed, file_input, stbuf.st_size, trusted,
|
||
out_input ? &ret_input : NULL,
|
||
&ret_file_info, &ret_xattrs,
|
||
cancellable, error))
|
||
return FALSE;
|
||
|
||
ot_transfer_out_value (out_input, &ret_input);
|
||
ot_transfer_out_value (out_file_info, &ret_file_info);
|
||
ot_transfer_out_value (out_xattrs, &ret_xattrs);
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_content_file_parse:
|
||
* @compressed: Whether or not the stream is zlib-compressed
|
||
* @content_path: Path to file containing content
|
||
* @trusted: If %TRUE, assume the content has been validated
|
||
* @out_input: (out): The raw file content stream
|
||
* @out_file_info: (out): Normal metadata
|
||
* @out_xattrs: (out): Extended attributes
|
||
* @cancellable: Cancellable
|
||
* @error: Error
|
||
*
|
||
* A thin wrapper for ostree_content_stream_parse(); this function
|
||
* converts an object content stream back into components.
|
||
*/
|
||
gboolean
|
||
ostree_content_file_parse (gboolean compressed,
|
||
GFile *content_path,
|
||
gboolean trusted,
|
||
GInputStream **out_input,
|
||
GFileInfo **out_file_info,
|
||
GVariant **out_xattrs,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
return ostree_content_file_parse_at (compressed, AT_FDCWD,
|
||
gs_file_get_path_cached (content_path),
|
||
trusted,
|
||
out_input, out_file_info, out_xattrs,
|
||
cancellable, error);
|
||
}
|
||
|
||
static gboolean
|
||
break_symhardlink (int dfd,
|
||
const char *path,
|
||
struct stat *stbuf,
|
||
GLnxFileCopyFlags copyflags,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
guint count;
|
||
gboolean copy_success = FALSE;
|
||
char *path_tmp = glnx_strjoina (path, ".XXXXXX");
|
||
|
||
for (count = 0; count < 100; count++)
|
||
{
|
||
g_autoptr(GError) tmp_error = NULL;
|
||
|
||
glnx_gen_temp_name (path_tmp);
|
||
|
||
if (!glnx_file_copy_at (dfd, path, stbuf, dfd, path_tmp, copyflags,
|
||
cancellable, &tmp_error))
|
||
{
|
||
if (g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
|
||
continue;
|
||
g_propagate_error (error, g_steal_pointer (&tmp_error));
|
||
return FALSE;
|
||
}
|
||
|
||
copy_success = TRUE;
|
||
break;
|
||
}
|
||
|
||
if (!copy_success)
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS,
|
||
"Exceeded limit of %u file creation attempts", count);
|
||
return FALSE;
|
||
}
|
||
|
||
if (!glnx_renameat (dfd, path_tmp, dfd, path, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_break_hardlink:
|
||
* @dfd: Directory fd
|
||
* @path: Path relative to @dfd
|
||
* @skip_xattrs: Do not copy extended attributes
|
||
* @error: error
|
||
*
|
||
* In many cases using libostree, a program may need to "break"
|
||
* hardlinks by performing a copy. For example, in order to
|
||
* logically append to a file.
|
||
*
|
||
* This function performs full copying, including e.g. extended
|
||
* attributes and permissions of both regular files and symbolic links.
|
||
*
|
||
* If the file is not hardlinked, this function does nothing and
|
||
* returns successfully.
|
||
*
|
||
* This function does not perform synchronization via `fsync()` or
|
||
* `fdatasync()`; the idea is this will commonly be done as part
|
||
* of an `ostree_repo_commit_transaction()`, which itself takes
|
||
* care of synchronization.
|
||
*
|
||
* Since: 2017.15
|
||
*/
|
||
gboolean ostree_break_hardlink (int dfd,
|
||
const char *path,
|
||
gboolean skip_xattrs,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
struct stat stbuf;
|
||
|
||
if (!glnx_fstatat (dfd, path, &stbuf, AT_SYMLINK_NOFOLLOW, error))
|
||
return FALSE;
|
||
|
||
if (stbuf.st_nlink <= 1)
|
||
return TRUE; /* Note early return */
|
||
|
||
const GLnxFileCopyFlags copyflags = skip_xattrs ? GLNX_FILE_COPY_NOXATTRS : 0;
|
||
|
||
if (S_ISREG (stbuf.st_mode))
|
||
/* Note it's now completely safe to copy a file to itself,
|
||
* as glnx_file_copy_at() is documented to do an O_TMPFILE + rename()
|
||
* with GLNX_FILE_COPY_OVERWRITE.
|
||
*/
|
||
return glnx_file_copy_at (dfd, path, &stbuf, dfd, path,
|
||
copyflags | GLNX_FILE_COPY_OVERWRITE,
|
||
cancellable, error);
|
||
else if (S_ISLNK (stbuf.st_mode))
|
||
return break_symhardlink (dfd, path, &stbuf, copyflags,
|
||
cancellable, error);
|
||
else
|
||
return glnx_throw (error, "Unsupported type for entry '%s'", path);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_file_from_input:
|
||
* @file_info: File information
|
||
* @xattrs: (allow-none): Optional extended attributes
|
||
* @in: (allow-none): File content, should be %NULL for symbolic links
|
||
* @objtype: Object type
|
||
* @out_csum: (out) (array fixed-size=32): Return location for binary checksum
|
||
* @cancellable: Cancellable
|
||
* @error: Error
|
||
*
|
||
* Compute the OSTree checksum for a given input.
|
||
*/
|
||
gboolean
|
||
ostree_checksum_file_from_input (GFileInfo *file_info,
|
||
GVariant *xattrs,
|
||
GInputStream *in,
|
||
OstreeObjectType objtype,
|
||
guchar **out_csum,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
|
||
g_auto(OtChecksum) checksum = { 0, };
|
||
ot_checksum_init (&checksum);
|
||
|
||
if (OSTREE_OBJECT_TYPE_IS_META (objtype))
|
||
{
|
||
if (!ot_gio_splice_update_checksum (NULL, in, &checksum, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
g_autoptr(GVariant) dirmeta = ostree_create_directory_metadata (file_info, xattrs);
|
||
ot_checksum_update (&checksum, g_variant_get_data (dirmeta),
|
||
g_variant_get_size (dirmeta));
|
||
}
|
||
else
|
||
{
|
||
g_autoptr(GBytes) file_header = _ostree_file_header_new (file_info, xattrs);
|
||
|
||
ot_checksum_update_bytes (&checksum, file_header);
|
||
|
||
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
|
||
{
|
||
if (!ot_gio_splice_update_checksum (NULL, in, &checksum, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
*out_csum = g_malloc (OSTREE_SHA256_DIGEST_LEN);
|
||
ot_checksum_get_digest (&checksum, *out_csum, OSTREE_SHA256_DIGEST_LEN);
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_file:
|
||
* @f: File path
|
||
* @objtype: Object type
|
||
* @out_csum: (out) (array fixed-size=32): Return location for binary checksum
|
||
* @cancellable: Cancellable
|
||
* @error: Error
|
||
*
|
||
* Compute the OSTree checksum for a given file.
|
||
*/
|
||
gboolean
|
||
ostree_checksum_file (GFile *f,
|
||
OstreeObjectType objtype,
|
||
guchar **out_csum,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
if (g_cancellable_set_error_if_cancelled (cancellable, error))
|
||
return FALSE;
|
||
|
||
g_autoptr(GFileInfo) file_info =
|
||
g_file_query_info (f, OSTREE_GIO_FAST_QUERYINFO,
|
||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
cancellable, error);
|
||
if (!file_info)
|
||
return FALSE;
|
||
|
||
g_autoptr(GInputStream) in = NULL;
|
||
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
|
||
{
|
||
in = (GInputStream*)g_file_read (f, cancellable, error);
|
||
if (!in)
|
||
return FALSE;
|
||
}
|
||
|
||
g_autoptr(GVariant) xattrs = NULL;
|
||
if (objtype == OSTREE_OBJECT_TYPE_FILE)
|
||
{
|
||
if (!glnx_dfd_name_get_all_xattrs (AT_FDCWD, gs_file_get_path_cached (f),
|
||
&xattrs, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
g_autofree guchar *ret_csum = NULL;
|
||
if (!ostree_checksum_file_from_input (file_info, xattrs, in, objtype,
|
||
&ret_csum, cancellable, error))
|
||
return FALSE;
|
||
|
||
ot_transfer_out_value(out_csum, &ret_csum);
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_file_at:
|
||
* @dfd: Directory file descriptor
|
||
* @path: Subpath
|
||
* @stbuf (allow-none): Optional stat buffer
|
||
* @objtype: Object type
|
||
* @flags: Flags
|
||
* @out_checksum (out) (transfer full): Return location for hex checksum
|
||
* @cancellable: Cancellable
|
||
* @error: Error
|
||
*
|
||
* Compute the OSTree checksum for a given file. This is an fd-relative version
|
||
* of ostree_checksum_file() which also takes flags and fills in a caller
|
||
* allocated buffer.
|
||
*
|
||
* Since: 2017.13
|
||
*/
|
||
gboolean
|
||
ostree_checksum_file_at (int dfd,
|
||
const char *path,
|
||
struct stat *stbuf,
|
||
OstreeObjectType objtype,
|
||
OstreeChecksumFlags flags,
|
||
char **out_checksum,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_return_val_if_fail (out_checksum != NULL, FALSE);
|
||
|
||
if (g_cancellable_set_error_if_cancelled (cancellable, error))
|
||
return FALSE;
|
||
|
||
struct stat local_stbuf;
|
||
if (stbuf == NULL)
|
||
{
|
||
stbuf = &local_stbuf;
|
||
if (!glnx_fstatat (dfd, path, stbuf, AT_SYMLINK_NOFOLLOW, error))
|
||
return FALSE;
|
||
}
|
||
|
||
g_autoptr(GFileInfo) file_info = _ostree_stbuf_to_gfileinfo (stbuf);
|
||
|
||
g_autoptr(GInputStream) in = NULL;
|
||
if (S_ISREG (stbuf->st_mode))
|
||
{
|
||
glnx_autofd int fd = -1;
|
||
if (!glnx_openat_rdonly (dfd, path, FALSE, &fd, error))
|
||
return FALSE;
|
||
in = g_unix_input_stream_new (glnx_steal_fd (&fd), TRUE);
|
||
}
|
||
else if (S_ISLNK (stbuf->st_mode))
|
||
{
|
||
if (!ot_readlinkat_gfile_info (dfd, path, file_info, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
const gboolean ignore_xattrs =
|
||
((flags & OSTREE_CHECKSUM_FLAGS_IGNORE_XATTRS) > 0);
|
||
|
||
g_autoptr(GVariant) xattrs = NULL;
|
||
if (!ignore_xattrs && objtype == OSTREE_OBJECT_TYPE_FILE)
|
||
{
|
||
if (!glnx_dfd_name_get_all_xattrs (dfd, path, &xattrs, cancellable, error))
|
||
return FALSE;
|
||
}
|
||
|
||
g_autofree guchar *csum_bytes = NULL;
|
||
if (!ostree_checksum_file_from_input (file_info, xattrs, in, objtype,
|
||
&csum_bytes, cancellable, error))
|
||
return FALSE;
|
||
|
||
*out_checksum = ostree_checksum_from_bytes (csum_bytes);
|
||
return TRUE;
|
||
}
|
||
|
||
typedef struct {
|
||
GFile *f;
|
||
OstreeObjectType objtype;
|
||
guchar *csum;
|
||
} ChecksumFileAsyncData;
|
||
|
||
static void
|
||
checksum_file_async_thread (GSimpleAsyncResult *res,
|
||
GObject *object,
|
||
GCancellable *cancellable)
|
||
{
|
||
GError *error = NULL;
|
||
ChecksumFileAsyncData *data;
|
||
guchar *csum = NULL;
|
||
|
||
data = g_simple_async_result_get_op_res_gpointer (res);
|
||
if (!ostree_checksum_file (data->f, data->objtype, &csum, cancellable, &error))
|
||
g_simple_async_result_take_error (res, error);
|
||
else
|
||
data->csum = csum;
|
||
}
|
||
|
||
static void
|
||
checksum_file_async_data_free (gpointer datap)
|
||
{
|
||
ChecksumFileAsyncData *data = datap;
|
||
|
||
g_object_unref (data->f);
|
||
g_free (data->csum);
|
||
g_free (data);
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_file_async:
|
||
* @f: File path
|
||
* @objtype: Object type
|
||
* @io_priority: Priority for operation, see %G_IO_PRIORITY_DEFAULT
|
||
* @cancellable: Cancellable
|
||
* @callback: Invoked when operation is complete
|
||
* @user_data: Data for @callback
|
||
*
|
||
* Asynchronously compute the OSTree checksum for a given file;
|
||
* complete with ostree_checksum_file_async_finish().
|
||
*/
|
||
void
|
||
ostree_checksum_file_async (GFile *f,
|
||
OstreeObjectType objtype,
|
||
int io_priority,
|
||
GCancellable *cancellable,
|
||
GAsyncReadyCallback callback,
|
||
gpointer user_data)
|
||
{
|
||
GSimpleAsyncResult *res;
|
||
ChecksumFileAsyncData *data;
|
||
|
||
data = g_new0 (ChecksumFileAsyncData, 1);
|
||
data->f = g_object_ref (f);
|
||
data->objtype = objtype;
|
||
|
||
res = g_simple_async_result_new (G_OBJECT (f), callback, user_data, ostree_checksum_file_async);
|
||
g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)checksum_file_async_data_free);
|
||
|
||
g_simple_async_result_run_in_thread (res, checksum_file_async_thread, io_priority, cancellable);
|
||
g_object_unref (res);
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_file_async_finish:
|
||
* @f: File path
|
||
* @result: Async result
|
||
* @out_csum: (out) (array fixed-size=32): Return location for binary checksum
|
||
* @error: Error
|
||
*
|
||
* Finish computing the OSTree checksum for a given file; see
|
||
* ostree_checksum_file_async().
|
||
*/
|
||
gboolean
|
||
ostree_checksum_file_async_finish (GFile *f,
|
||
GAsyncResult *result,
|
||
guchar **out_csum,
|
||
GError **error)
|
||
{
|
||
GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
|
||
ChecksumFileAsyncData *data;
|
||
|
||
g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == ostree_checksum_file_async);
|
||
|
||
if (g_simple_async_result_propagate_error (simple, error))
|
||
return FALSE;
|
||
|
||
data = g_simple_async_result_get_op_res_gpointer (simple);
|
||
/* Transfer ownership */
|
||
*out_csum = data->csum;
|
||
data->csum = NULL;
|
||
return TRUE;
|
||
}
|
||
|
||
/* Common helper to compare checksums for an object, so we have a consistent
|
||
* error message.
|
||
*/
|
||
gboolean
|
||
_ostree_compare_object_checksum (OstreeObjectType objtype,
|
||
const char *expected,
|
||
const char *actual,
|
||
GError **error)
|
||
{
|
||
if (!g_str_equal (expected, actual))
|
||
return glnx_throw (error, "Corrupted %s object; checksum expected='%s' actual='%s'",
|
||
ostree_object_type_to_string (objtype),
|
||
expected, actual);
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_create_directory_metadata:
|
||
* @dir_info: a #GFileInfo containing directory information
|
||
* @xattrs: (allow-none): Optional extended attributes
|
||
*
|
||
* Returns: (transfer full): A new #GVariant containing %OSTREE_OBJECT_TYPE_DIR_META
|
||
*/
|
||
GVariant *
|
||
ostree_create_directory_metadata (GFileInfo *dir_info,
|
||
GVariant *xattrs)
|
||
{
|
||
GVariant *ret_metadata = NULL;
|
||
|
||
ret_metadata = g_variant_new ("(uuu@a(ayay))",
|
||
GUINT32_TO_BE (g_file_info_get_attribute_uint32 (dir_info, "unix::uid")),
|
||
GUINT32_TO_BE (g_file_info_get_attribute_uint32 (dir_info, "unix::gid")),
|
||
GUINT32_TO_BE (g_file_info_get_attribute_uint32 (dir_info, "unix::mode")),
|
||
xattrs ? xattrs : g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0));
|
||
g_variant_ref_sink (ret_metadata);
|
||
|
||
return ret_metadata;
|
||
}
|
||
|
||
/* Create a randomly-named symbolic link in @tempdir which points to
|
||
* @target. The filename will be returned in @out_file.
|
||
*
|
||
* The reason this odd function exists is that the repo should only
|
||
* contain objects in their final state. For bare repositories, we
|
||
* need to first create the symlink, then chown it, and apply all
|
||
* extended attributes, before finally rename()ing it into place.
|
||
*
|
||
* Furthermore for checkouts, we use this to implement union mode
|
||
* where we override existing files via tempfile+rename().
|
||
*/
|
||
gboolean
|
||
_ostree_make_temporary_symlink_at (int tmp_dirfd,
|
||
const char *target,
|
||
char **out_name,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
g_autofree char *tmpname = g_strdup ("tmplink.XXXXXX");
|
||
const int max_attempts = 128;
|
||
guint i;
|
||
|
||
for (i = 0; i < max_attempts; i++)
|
||
{
|
||
glnx_gen_temp_name (tmpname);
|
||
if (symlinkat (target, tmp_dirfd, tmpname) < 0)
|
||
{
|
||
if (errno == EEXIST)
|
||
continue;
|
||
else
|
||
return glnx_throw_errno (error);
|
||
}
|
||
else
|
||
break;
|
||
}
|
||
if (i == max_attempts)
|
||
return glnx_throw (error, "Exhausted attempts to open temporary file");
|
||
|
||
if (out_name)
|
||
*out_name = g_steal_pointer (&tmpname);
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
/**
|
||
* ostree_object_type_to_string:
|
||
* @objtype: an #OstreeObjectType
|
||
*
|
||
* Serialize @objtype to a string; this is used for file extensions.
|
||
*/
|
||
const char *
|
||
ostree_object_type_to_string (OstreeObjectType objtype)
|
||
{
|
||
switch (objtype)
|
||
{
|
||
case OSTREE_OBJECT_TYPE_FILE:
|
||
return "file";
|
||
case OSTREE_OBJECT_TYPE_DIR_TREE:
|
||
return "dirtree";
|
||
case OSTREE_OBJECT_TYPE_DIR_META:
|
||
return "dirmeta";
|
||
case OSTREE_OBJECT_TYPE_COMMIT:
|
||
return "commit";
|
||
case OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT:
|
||
return "tombstone-commit";
|
||
case OSTREE_OBJECT_TYPE_COMMIT_META:
|
||
return "commitmeta";
|
||
case OSTREE_OBJECT_TYPE_PAYLOAD_LINK:
|
||
return "payload-link";
|
||
default:
|
||
g_assert_not_reached ();
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ostree_object_type_from_string:
|
||
* @str: A stringified version of #OstreeObjectType
|
||
*
|
||
* The reverse of ostree_object_type_to_string().
|
||
*/
|
||
OstreeObjectType
|
||
ostree_object_type_from_string (const char *str)
|
||
{
|
||
if (!strcmp (str, "file"))
|
||
return OSTREE_OBJECT_TYPE_FILE;
|
||
else if (!strcmp (str, "dirtree"))
|
||
return OSTREE_OBJECT_TYPE_DIR_TREE;
|
||
else if (!strcmp (str, "dirmeta"))
|
||
return OSTREE_OBJECT_TYPE_DIR_META;
|
||
else if (!strcmp (str, "commit"))
|
||
return OSTREE_OBJECT_TYPE_COMMIT;
|
||
else if (!strcmp (str, "tombstone-commit"))
|
||
return OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT;
|
||
else if (!strcmp (str, "commitmeta"))
|
||
return OSTREE_OBJECT_TYPE_COMMIT_META;
|
||
else if (!strcmp (str, "payload-link"))
|
||
return OSTREE_OBJECT_TYPE_PAYLOAD_LINK;
|
||
g_assert_not_reached ();
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* ostree_object_to_string:
|
||
* @checksum: An ASCII checksum
|
||
* @objtype: Object type
|
||
*
|
||
* Returns: A string containing both @checksum and a stringifed version of @objtype
|
||
*/
|
||
char *
|
||
ostree_object_to_string (const char *checksum,
|
||
OstreeObjectType objtype)
|
||
{
|
||
return g_strconcat (checksum, ".", ostree_object_type_to_string (objtype), NULL);
|
||
}
|
||
|
||
/**
|
||
* ostree_object_from_string:
|
||
* @str: An ASCII checksum
|
||
* @out_checksum: (out) (transfer full): Parsed checksum
|
||
* @out_objtype: (out): Parsed object type
|
||
*
|
||
* Reverse ostree_object_to_string().
|
||
*/
|
||
void
|
||
ostree_object_from_string (const char *str,
|
||
gchar **out_checksum,
|
||
OstreeObjectType *out_objtype)
|
||
{
|
||
const char *dot;
|
||
|
||
dot = strrchr (str, '.');
|
||
g_assert (dot != NULL);
|
||
*out_checksum = g_strndup (str, dot - str);
|
||
*out_objtype = ostree_object_type_from_string (dot + 1);
|
||
}
|
||
|
||
/**
|
||
* ostree_hash_object_name:
|
||
* @a: A #GVariant containing a serialized object
|
||
*
|
||
* Use this function with #GHashTable and ostree_object_name_serialize().
|
||
*/
|
||
guint
|
||
ostree_hash_object_name (gconstpointer a)
|
||
{
|
||
GVariant *variant = (gpointer)a;
|
||
const char *checksum;
|
||
OstreeObjectType objtype;
|
||
gint objtype_int;
|
||
|
||
ostree_object_name_deserialize (variant, &checksum, &objtype);
|
||
objtype_int = (gint) objtype;
|
||
return g_str_hash (checksum) + g_int_hash (&objtype_int);
|
||
}
|
||
|
||
/**
|
||
* ostree_cmp_checksum_bytes:
|
||
* @a: A binary checksum
|
||
* @b: A binary checksum
|
||
*
|
||
* Compare two binary checksums, using memcmp().
|
||
*/
|
||
int
|
||
ostree_cmp_checksum_bytes (const guchar *a,
|
||
const guchar *b)
|
||
{
|
||
return memcmp (a, b, OSTREE_SHA256_DIGEST_LEN);
|
||
}
|
||
|
||
/**
|
||
* ostree_object_name_serialize:
|
||
* @checksum: An ASCII checksum
|
||
* @objtype: An object type
|
||
*
|
||
* Returns: (transfer floating): A new floating #GVariant containing checksum string and objtype
|
||
*/
|
||
GVariant *
|
||
ostree_object_name_serialize (const char *checksum,
|
||
OstreeObjectType objtype)
|
||
{
|
||
g_assert (objtype >= OSTREE_OBJECT_TYPE_FILE
|
||
&& objtype <= OSTREE_OBJECT_TYPE_LAST);
|
||
return g_variant_new ("(su)", checksum, (guint32)objtype);
|
||
}
|
||
|
||
/**
|
||
* ostree_object_name_deserialize:
|
||
* @variant: A #GVariant of type (su)
|
||
* @out_checksum: (out) (transfer none): Pointer into string memory of @variant with checksum
|
||
* @out_objtype: (out): Return object type
|
||
*
|
||
* Reverse ostree_object_name_serialize(). Note that @out_checksum is
|
||
* only valid for the lifetime of @variant, and must not be freed.
|
||
*/
|
||
void
|
||
ostree_object_name_deserialize (GVariant *variant,
|
||
const char **out_checksum,
|
||
OstreeObjectType *out_objtype)
|
||
{
|
||
guint32 objtype_u32;
|
||
g_variant_get (variant, "(&su)", out_checksum, &objtype_u32);
|
||
*out_objtype = (OstreeObjectType)objtype_u32;
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_b64_inplace_to_bytes: (skip)
|
||
* @checksum: (array fixed-size=32): An binary checksum of length 32
|
||
* @buf: Output location, must be at least 45 bytes in length
|
||
*
|
||
* Overwrite the contents of @buf with stringified version of @csum.
|
||
*/
|
||
void
|
||
ostree_checksum_b64_inplace_to_bytes (const char *checksum,
|
||
guchar *buf)
|
||
{
|
||
int state = 0;
|
||
guint save = 0;
|
||
char tmpbuf[44];
|
||
int i;
|
||
|
||
for (i = 0; i < 43; i++)
|
||
{
|
||
char c = checksum[i];
|
||
if (c == '_')
|
||
tmpbuf[i] = '/';
|
||
else
|
||
tmpbuf[i] = c;
|
||
}
|
||
tmpbuf[43] = '=';
|
||
|
||
g_base64_decode_step (tmpbuf, sizeof (tmpbuf), (guchar *) buf, &state, &save);
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_inplace_to_bytes:
|
||
* @checksum: a SHA256 string
|
||
* @buf: Output buffer with at least 32 bytes of space
|
||
*
|
||
* Convert @checksum from a string to binary in-place, without
|
||
* allocating memory. Use this function in hot code paths.
|
||
*/
|
||
void
|
||
ostree_checksum_inplace_to_bytes (const char *checksum,
|
||
guchar *buf)
|
||
{
|
||
guint i;
|
||
guint j;
|
||
|
||
for (i = 0, j = 0; i < OSTREE_SHA256_DIGEST_LEN; i += 1, j += 2)
|
||
{
|
||
gint big, little;
|
||
|
||
g_assert (checksum[j]);
|
||
g_assert (checksum[j+1]);
|
||
|
||
big = g_ascii_xdigit_value (checksum[j]);
|
||
little = g_ascii_xdigit_value (checksum[j+1]);
|
||
|
||
g_assert (big != -1);
|
||
g_assert (little != -1);
|
||
|
||
buf[i] = (big << 4) | little;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_to_bytes:
|
||
* @checksum: An ASCII checksum
|
||
*
|
||
* Returns: (transfer full) (array fixed-size=32): Binary checksum from @checksum of length 32; free with g_free().
|
||
*/
|
||
guchar *
|
||
ostree_checksum_to_bytes (const char *checksum)
|
||
{
|
||
guchar *ret = g_malloc (OSTREE_SHA256_DIGEST_LEN);
|
||
ostree_checksum_inplace_to_bytes (checksum, ret);
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_to_bytes_v:
|
||
* @checksum: An ASCII checksum
|
||
*
|
||
* Returns: (transfer full): New #GVariant of type ay with length 32
|
||
*/
|
||
GVariant *
|
||
ostree_checksum_to_bytes_v (const char *checksum)
|
||
{
|
||
guchar result[OSTREE_SHA256_DIGEST_LEN];
|
||
ostree_checksum_inplace_to_bytes (checksum, result);
|
||
return ot_gvariant_new_bytearray ((guchar*)result, OSTREE_SHA256_DIGEST_LEN);
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_b64_to_bytes:
|
||
* @checksum: An ASCII checksum
|
||
*
|
||
* Returns: (transfer full) (array fixed-size=32): Binary version of @checksum.
|
||
*
|
||
* Since: 2016.8
|
||
*/
|
||
guchar *
|
||
ostree_checksum_b64_to_bytes (const char *checksum)
|
||
{
|
||
guchar *ret = g_malloc (32);
|
||
ostree_checksum_b64_inplace_to_bytes (checksum, ret);
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_inplace_from_bytes: (skip)
|
||
* @csum: (array fixed-size=32): An binary checksum of length 32
|
||
* @buf: Output location, must be at least OSTREE_SHA256_STRING_LEN+1 bytes in length
|
||
*
|
||
* Overwrite the contents of @buf with stringified version of @csum.
|
||
*/
|
||
void
|
||
ostree_checksum_inplace_from_bytes (const guchar *csum,
|
||
char *buf)
|
||
{
|
||
ot_bin2hex (buf, csum, OSTREE_SHA256_DIGEST_LEN);
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_b64_inplace_from_bytes: (skip)
|
||
* @csum: (array fixed-size=32): An binary checksum of length 32
|
||
* @buf: Output location, must be at least 44 bytes in length
|
||
*
|
||
* Overwrite the contents of @buf with modified base64 encoding of @csum.
|
||
* The "modified" term refers to the fact that instead of '/', the '_'
|
||
* character is used.
|
||
*/
|
||
void
|
||
ostree_checksum_b64_inplace_from_bytes (const guchar *csum,
|
||
char *buf)
|
||
{
|
||
char tmpbuf[44];
|
||
int save = 0;
|
||
int state = 0;
|
||
gsize outlen;
|
||
int i;
|
||
|
||
/* At some point, we can optimize this, but for now it's
|
||
* a lot easier to reuse GLib's base64 encoder and postprocess it
|
||
* to replace the '/' with '_'.
|
||
*/
|
||
outlen = g_base64_encode_step (csum, OSTREE_SHA256_DIGEST_LEN, FALSE, tmpbuf, &state, &save);
|
||
outlen += g_base64_encode_close (FALSE, tmpbuf+outlen, &state, &save);
|
||
g_assert (outlen == 44);
|
||
|
||
for (i = 0; i < sizeof (tmpbuf); i++)
|
||
{
|
||
char c = tmpbuf[i];
|
||
if (c == '=')
|
||
{
|
||
g_assert (i == 43);
|
||
buf[i] = '\0';
|
||
}
|
||
else if (c == '/')
|
||
buf[i] = '_';
|
||
else
|
||
buf[i] = c;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_from_bytes:
|
||
* @csum: (array fixed-size=32): An binary checksum of length 32
|
||
*
|
||
* Returns: (transfer full): String form of @csum
|
||
*/
|
||
char *
|
||
ostree_checksum_from_bytes (const guchar *csum)
|
||
{
|
||
char *ret = g_malloc (OSTREE_SHA256_STRING_LEN+1);
|
||
ostree_checksum_inplace_from_bytes (csum, ret);
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_from_bytes_v:
|
||
* @csum_v: #GVariant of type ay
|
||
*
|
||
* Returns: (transfer full): String form of @csum_bytes
|
||
*/
|
||
char *
|
||
ostree_checksum_from_bytes_v (GVariant *csum_v)
|
||
{
|
||
return ostree_checksum_from_bytes (ostree_checksum_bytes_peek (csum_v));
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_b64_from_bytes:
|
||
* @csum: (array fixed-size=32): An binary checksum of length 32
|
||
*
|
||
* Returns: (transfer full): Modified base64 encoding of @csum
|
||
*
|
||
* The "modified" term refers to the fact that instead of '/', the '_'
|
||
* character is used.
|
||
*
|
||
* Since: 2016.8
|
||
*/
|
||
char *
|
||
ostree_checksum_b64_from_bytes (const guchar *csum)
|
||
{
|
||
char *ret = g_malloc (44);
|
||
ostree_checksum_b64_inplace_from_bytes (csum, ret);
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_bytes_peek:
|
||
* @bytes: #GVariant of type ay
|
||
*
|
||
* Returns: (transfer none) (array fixed-size=32) (element-type guint8): Binary checksum data in @bytes; do not free. If @bytes does not have the correct length, return %NULL.
|
||
*/
|
||
const guchar *
|
||
ostree_checksum_bytes_peek (GVariant *bytes)
|
||
{
|
||
gsize n_elts;
|
||
const guchar *ret;
|
||
ret = g_variant_get_fixed_array (bytes, &n_elts, 1);
|
||
if (G_UNLIKELY (n_elts != OSTREE_SHA256_DIGEST_LEN))
|
||
return NULL;
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* ostree_checksum_bytes_peek_validate:
|
||
* @bytes: #GVariant of type ay
|
||
* @error: Errror
|
||
*
|
||
* Like ostree_checksum_bytes_peek(), but also throws @error.
|
||
*
|
||
* Returns: (transfer none) (array fixed-size=32) (element-type guint8): Binary checksum data
|
||
*/
|
||
const guchar *
|
||
ostree_checksum_bytes_peek_validate (GVariant *bytes,
|
||
GError **error)
|
||
{
|
||
const guchar *ret = ostree_checksum_bytes_peek (bytes);
|
||
if (G_UNLIKELY (!ret))
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
"Invalid checksum of length %" G_GUINT64_FORMAT
|
||
" expected 32", (guint64) g_variant_n_children (bytes));
|
||
return NULL;
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
/*
|
||
* _ostree_loose_path:
|
||
* @buf: Output buffer, must be _OSTREE_LOOSE_PATH_MAX in size
|
||
* @checksum: ASCII checksum
|
||
* @objtype: Object type
|
||
* @mode: Repository mode
|
||
*
|
||
* Overwrite the contents of @buf with relative path for loose
|
||
* object.
|
||
*/
|
||
void
|
||
_ostree_loose_path (char *buf,
|
||
const char *checksum,
|
||
OstreeObjectType objtype,
|
||
OstreeRepoMode mode)
|
||
{
|
||
*buf = checksum[0];
|
||
buf++;
|
||
*buf = checksum[1];
|
||
buf++;
|
||
snprintf (buf, _OSTREE_LOOSE_PATH_MAX - 2, "/%s.%s%s",
|
||
checksum + 2, ostree_object_type_to_string (objtype),
|
||
(!OSTREE_OBJECT_TYPE_IS_META (objtype) && mode == OSTREE_REPO_MODE_ARCHIVE) ? "z" : "");
|
||
}
|
||
|
||
/**
|
||
* _ostree_stbuf_to_gfileinfo:
|
||
* @mode: File mode
|
||
* @uid: File uid
|
||
* @gid: File gid
|
||
*
|
||
* OSTree only stores a subset of file attributes; for example,
|
||
* timestamps are intentionally not stored. This function creates a
|
||
* #GFileInfo based on the attributes of a `struct stat` that match
|
||
* those file attributes.
|
||
*
|
||
* Returns: (transfer full): A new #GFileInfo mapping a subset of @stbuf.
|
||
*/
|
||
GFileInfo *
|
||
_ostree_stbuf_to_gfileinfo (const struct stat *stbuf)
|
||
{
|
||
GFileInfo *ret = g_file_info_new ();
|
||
GFileType ftype;
|
||
const mode_t mode = stbuf->st_mode;
|
||
if (S_ISDIR (mode))
|
||
ftype = G_FILE_TYPE_DIRECTORY;
|
||
else if (S_ISREG (mode))
|
||
ftype = G_FILE_TYPE_REGULAR;
|
||
else if (S_ISLNK (mode))
|
||
ftype = G_FILE_TYPE_SYMBOLIC_LINK;
|
||
else if (S_ISBLK (mode) || S_ISCHR(mode) || S_ISFIFO(mode))
|
||
ftype = G_FILE_TYPE_SPECIAL;
|
||
else
|
||
ftype = G_FILE_TYPE_UNKNOWN;
|
||
g_file_info_set_attribute_uint32 (ret, "standard::type", ftype);
|
||
g_file_info_set_attribute_boolean (ret, "standard::is-symlink", S_ISLNK (mode));
|
||
g_file_info_set_attribute_uint32 (ret, "unix::uid", stbuf->st_uid);
|
||
g_file_info_set_attribute_uint32 (ret, "unix::gid", stbuf->st_gid);
|
||
g_file_info_set_attribute_uint32 (ret, "unix::mode", mode);
|
||
|
||
/* those aren't stored by ostree, but used by the devino cache */
|
||
g_file_info_set_attribute_uint32 (ret, "unix::device", stbuf->st_dev);
|
||
g_file_info_set_attribute_uint64 (ret, "unix::inode", stbuf->st_ino);
|
||
|
||
if (S_ISREG (mode))
|
||
g_file_info_set_attribute_uint64 (ret, "standard::size", stbuf->st_size);
|
||
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* _ostree_gfileinfo_to_stbuf:
|
||
* @file_info: File info
|
||
* @out_stbuf: (out): stat buffer
|
||
*
|
||
* Map GFileInfo data from @file_info onto @out_stbuf.
|
||
*/
|
||
void
|
||
_ostree_gfileinfo_to_stbuf (GFileInfo *file_info,
|
||
struct stat *out_stbuf)
|
||
{
|
||
struct stat stbuf = {0,};
|
||
stbuf.st_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
|
||
stbuf.st_uid = g_file_info_get_attribute_uint32 (file_info, "unix::uid");
|
||
stbuf.st_gid = g_file_info_get_attribute_uint32 (file_info, "unix::gid");
|
||
if (S_ISREG (stbuf.st_mode))
|
||
stbuf.st_size = g_file_info_get_attribute_uint64 (file_info, "standard::size");
|
||
*out_stbuf = stbuf;
|
||
}
|
||
|
||
/**
|
||
* _ostree_gfileinfo_equal:
|
||
* @a: First file info
|
||
* @b: Second file info
|
||
*
|
||
* OSTree only cares about a subset of file attributes. This function
|
||
* checks whether two #GFileInfo objects are equal as far as OSTree is
|
||
* concerned.
|
||
*
|
||
* Returns: TRUE if the #GFileInfo objects are OSTree-equivalent.
|
||
*/
|
||
gboolean
|
||
_ostree_gfileinfo_equal (GFileInfo *a, GFileInfo *b)
|
||
{
|
||
/* trivial case */
|
||
if (a == b)
|
||
return TRUE;
|
||
|
||
#define CHECK_ONE_ATTR(type, attr, a, b) \
|
||
do { if (g_file_info_get_attribute_##type(a, attr) != \
|
||
g_file_info_get_attribute_##type(b, attr)) \
|
||
return FALSE; \
|
||
} while (0)
|
||
|
||
CHECK_ONE_ATTR (uint32, "unix::uid", a, b);
|
||
CHECK_ONE_ATTR (uint32, "unix::gid", a, b);
|
||
CHECK_ONE_ATTR (uint32, "unix::mode", a, b);
|
||
CHECK_ONE_ATTR (uint32, "standard::type", a, b);
|
||
CHECK_ONE_ATTR (uint64, "standard::size", a, b);
|
||
|
||
#undef CHECK_ONE_ATTR
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Same motives as _ostree_gfileinfo_equal(), but for stat structs. */
|
||
gboolean
|
||
_ostree_stbuf_equal (struct stat *stbuf_a, struct stat *stbuf_b)
|
||
{
|
||
/* trivial case */
|
||
if (stbuf_a == stbuf_b)
|
||
return TRUE;
|
||
if (stbuf_a->st_mode != stbuf_b->st_mode)
|
||
return FALSE;
|
||
if (S_ISREG (stbuf_a->st_mode) && (stbuf_a->st_size != stbuf_b->st_size))
|
||
return FALSE;
|
||
if (stbuf_a->st_uid != stbuf_b->st_uid)
|
||
return FALSE;
|
||
if (stbuf_a->st_gid != stbuf_b->st_gid)
|
||
return FALSE;
|
||
return TRUE;
|
||
}
|
||
|
||
/* Many parts of libostree only care about mode,uid,gid - this creates
|
||
* a new GFileInfo with those fields see.
|
||
*/
|
||
GFileInfo *
|
||
_ostree_mode_uidgid_to_gfileinfo (mode_t mode, uid_t uid, gid_t gid)
|
||
{
|
||
struct stat stbuf = { 0, };
|
||
stbuf.st_mode = mode;
|
||
stbuf.st_uid = uid;
|
||
stbuf.st_gid = gid;
|
||
return _ostree_stbuf_to_gfileinfo (&stbuf);
|
||
}
|
||
|
||
/*
|
||
* _ostree_get_relative_object_path:
|
||
* @checksum: ASCII checksum string
|
||
* @type: Object type
|
||
* @compressed: Whether or not the repository object is compressed
|
||
*
|
||
* Returns: (transfer full): Relative path for a loose object
|
||
*/
|
||
char *
|
||
_ostree_get_relative_object_path (const char *checksum,
|
||
OstreeObjectType type,
|
||
gboolean compressed)
|
||
{
|
||
GString *path;
|
||
|
||
g_assert (strlen (checksum) == OSTREE_SHA256_STRING_LEN);
|
||
|
||
path = g_string_new ("objects/");
|
||
|
||
g_string_append_len (path, checksum, 2);
|
||
g_string_append_c (path, '/');
|
||
g_string_append (path, checksum + 2);
|
||
g_string_append_c (path, '.');
|
||
g_string_append (path, ostree_object_type_to_string (type));
|
||
if (!OSTREE_OBJECT_TYPE_IS_META (type) && compressed)
|
||
g_string_append (path, "z");
|
||
|
||
return g_string_free (path, FALSE);
|
||
}
|
||
|
||
char *
|
||
_ostree_get_relative_static_delta_path (const char *from,
|
||
const char *to,
|
||
const char *target)
|
||
{
|
||
guint8 csum_to[OSTREE_SHA256_DIGEST_LEN];
|
||
char to_b64[44];
|
||
guint8 csum_to_copy[OSTREE_SHA256_DIGEST_LEN];
|
||
GString *ret = g_string_new ("deltas/");
|
||
|
||
ostree_checksum_inplace_to_bytes (to, csum_to);
|
||
ostree_checksum_b64_inplace_from_bytes (csum_to, to_b64);
|
||
ostree_checksum_b64_inplace_to_bytes (to_b64, csum_to_copy);
|
||
|
||
g_assert (memcmp (csum_to, csum_to_copy, OSTREE_SHA256_DIGEST_LEN) == 0);
|
||
|
||
if (from != NULL)
|
||
{
|
||
guint8 csum_from[OSTREE_SHA256_DIGEST_LEN];
|
||
char from_b64[44];
|
||
|
||
ostree_checksum_inplace_to_bytes (from, csum_from);
|
||
ostree_checksum_b64_inplace_from_bytes (csum_from, from_b64);
|
||
|
||
g_string_append_c (ret, from_b64[0]);
|
||
g_string_append_c (ret, from_b64[1]);
|
||
g_string_append_c (ret, '/');
|
||
g_string_append (ret, from_b64 + 2);
|
||
g_string_append_c (ret, '-');
|
||
}
|
||
|
||
g_string_append_c (ret, to_b64[0]);
|
||
g_string_append_c (ret, to_b64[1]);
|
||
if (from == NULL)
|
||
g_string_append_c (ret, '/');
|
||
g_string_append (ret, to_b64 + 2);
|
||
|
||
if (target != NULL)
|
||
{
|
||
g_string_append_c (ret, '/');
|
||
g_string_append (ret, target);
|
||
}
|
||
|
||
return g_string_free (ret, FALSE);
|
||
}
|
||
|
||
char *
|
||
_ostree_get_relative_static_delta_superblock_path (const char *from,
|
||
const char *to)
|
||
{
|
||
return _ostree_get_relative_static_delta_path (from, to, "superblock");
|
||
}
|
||
|
||
char *
|
||
_ostree_get_relative_static_delta_detachedmeta_path (const char *from,
|
||
const char *to)
|
||
{
|
||
return _ostree_get_relative_static_delta_path (from, to, "meta");
|
||
}
|
||
|
||
char *
|
||
_ostree_get_relative_static_delta_part_path (const char *from,
|
||
const char *to,
|
||
guint i)
|
||
{
|
||
g_autofree char *partstr = g_strdup_printf ("%u", i);
|
||
return _ostree_get_relative_static_delta_path (from, to, partstr);
|
||
}
|
||
|
||
gboolean
|
||
_ostree_parse_delta_name (const char *delta_name,
|
||
char **out_from,
|
||
char **out_to,
|
||
GError **error)
|
||
{
|
||
g_auto(GStrv) parts = NULL;
|
||
g_return_val_if_fail (delta_name != NULL, FALSE);
|
||
|
||
parts = g_strsplit (delta_name, "-", 2);
|
||
|
||
/* NB: if delta_name is "", parts[0] is NULL, but the error
|
||
* validate_checksum_string() gives for "" is nice enough,
|
||
* so we just coerce it here */
|
||
if (!ostree_validate_checksum_string (parts[0] ?: "", error))
|
||
return FALSE;
|
||
|
||
if (parts[0] && parts[1] &&
|
||
!ostree_validate_checksum_string (parts[1], error))
|
||
return FALSE;
|
||
|
||
*out_from = *out_to = NULL;
|
||
if (parts[0] && parts[1])
|
||
{
|
||
ot_transfer_out_value (out_from, &parts[0]);
|
||
ot_transfer_out_value (out_to, &parts[1]);
|
||
}
|
||
else
|
||
{
|
||
ot_transfer_out_value (out_to, &parts[0]);
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/*
|
||
* file_header_parse:
|
||
* @metadata: A metadata variant of type %OSTREE_FILE_HEADER_GVARIANT_FORMAT
|
||
* @out_file_info: (out): Parsed file information
|
||
* @out_xattrs: (out): Parsed extended attribute set
|
||
* @error: Error
|
||
*
|
||
* Load file header information into standard Gio #GFileInfo object,
|
||
* along with extended attributes tored in @out_xattrs.
|
||
*/
|
||
static gboolean
|
||
file_header_parse (GVariant *metadata,
|
||
GFileInfo **out_file_info,
|
||
GVariant **out_xattrs,
|
||
GError **error)
|
||
{
|
||
guint32 uid, gid, mode, rdev;
|
||
const char *symlink_target;
|
||
g_autoptr(GVariant) ret_xattrs = NULL;
|
||
|
||
g_variant_get (metadata, "(uuuu&s@a(ayay))",
|
||
&uid, &gid, &mode, &rdev,
|
||
&symlink_target, &ret_xattrs);
|
||
if (rdev != 0)
|
||
return glnx_throw (error, "Corrupted archive file; invalid rdev %u", GUINT32_FROM_BE (rdev));
|
||
|
||
uid = GUINT32_FROM_BE (uid);
|
||
gid = GUINT32_FROM_BE (gid);
|
||
mode = GUINT32_FROM_BE (mode);
|
||
g_autoptr(GFileInfo) ret_file_info = _ostree_mode_uidgid_to_gfileinfo (mode, uid, gid);
|
||
|
||
if (S_ISREG (mode))
|
||
{
|
||
;
|
||
}
|
||
else if (S_ISLNK (mode))
|
||
{
|
||
g_file_info_set_attribute_byte_string (ret_file_info, "standard::symlink-target", symlink_target);
|
||
}
|
||
else
|
||
{
|
||
return glnx_throw (error, "Corrupted archive file; invalid mode %u", mode);
|
||
}
|
||
|
||
ot_transfer_out_value(out_file_info, &ret_file_info);
|
||
ot_transfer_out_value(out_xattrs, &ret_xattrs);
|
||
return TRUE;
|
||
}
|
||
|
||
/*
|
||
* zlib_file_header_parse:
|
||
* @metadata: A metadata variant of type %OSTREE_FILE_HEADER_GVARIANT_FORMAT
|
||
* @out_file_info: (out): Parsed file information
|
||
* @out_xattrs: (out): Parsed extended attribute set
|
||
* @error: Error
|
||
*
|
||
* Like ostree_file_header_parse(), but operates on zlib-compressed
|
||
* content.
|
||
*/
|
||
static gboolean
|
||
zlib_file_header_parse (GVariant *metadata,
|
||
GFileInfo **out_file_info,
|
||
GVariant **out_xattrs,
|
||
GError **error)
|
||
{
|
||
guint64 size;
|
||
guint32 uid, gid, mode, rdev;
|
||
const char *symlink_target;
|
||
g_autoptr(GVariant) ret_xattrs = NULL;
|
||
|
||
g_variant_get (metadata, "(tuuuu&s@a(ayay))", &size,
|
||
&uid, &gid, &mode, &rdev,
|
||
&symlink_target, &ret_xattrs);
|
||
if (rdev != 0)
|
||
return glnx_throw (error, "Corrupted archive file; invalid rdev %u", GUINT32_FROM_BE (rdev));
|
||
|
||
uid = GUINT32_FROM_BE (uid);
|
||
gid = GUINT32_FROM_BE (gid);
|
||
mode = GUINT32_FROM_BE (mode);
|
||
g_autoptr(GFileInfo) ret_file_info = _ostree_mode_uidgid_to_gfileinfo (mode, uid, gid);
|
||
g_file_info_set_size (ret_file_info, GUINT64_FROM_BE (size));
|
||
|
||
if (S_ISREG (mode))
|
||
{
|
||
;
|
||
}
|
||
else if (S_ISLNK (mode))
|
||
{
|
||
g_file_info_set_attribute_byte_string (ret_file_info, "standard::symlink-target", symlink_target);
|
||
}
|
||
else
|
||
{
|
||
return glnx_throw (error, "Corrupted archive file; invalid mode %u", mode);
|
||
}
|
||
|
||
ot_transfer_out_value(out_file_info, &ret_file_info);
|
||
ot_transfer_out_value(out_xattrs, &ret_xattrs);
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_validate_structureof_objtype:
|
||
* @objtype:
|
||
* @error: Error
|
||
*
|
||
* Returns: %TRUE if @objtype represents a valid object type
|
||
*/
|
||
gboolean
|
||
ostree_validate_structureof_objtype (guchar objtype,
|
||
GError **error)
|
||
{
|
||
OstreeObjectType objtype_v = (OstreeObjectType) objtype;
|
||
if (objtype_v < OSTREE_OBJECT_TYPE_FILE
|
||
|| objtype_v > OSTREE_OBJECT_TYPE_COMMIT)
|
||
return glnx_throw (error, "Invalid object type '%u'", objtype);
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_validate_structureof_csum_v:
|
||
* @checksum: a #GVariant of type "ay"
|
||
* @error: Error
|
||
*
|
||
* Returns: %TRUE if @checksum is a valid binary SHA256 checksum
|
||
*/
|
||
gboolean
|
||
ostree_validate_structureof_csum_v (GVariant *checksum,
|
||
GError **error)
|
||
{
|
||
return ostree_checksum_bytes_peek_validate (checksum, error) != NULL;
|
||
}
|
||
|
||
/**
|
||
* ostree_validate_structureof_checksum_string:
|
||
* @checksum: an ASCII string
|
||
* @error: Error
|
||
*
|
||
* Returns: %TRUE if @checksum is a valid ASCII SHA256 checksum
|
||
*/
|
||
gboolean
|
||
ostree_validate_structureof_checksum_string (const char *checksum,
|
||
GError **error)
|
||
{
|
||
int i = 0;
|
||
size_t len = strlen (checksum);
|
||
|
||
if (len != OSTREE_SHA256_STRING_LEN)
|
||
{
|
||
/* If we happen to get e.g. an Apache directory listing HTML, don't
|
||
* dump it all to the error.
|
||
* https://github.com/projectatomic/rpm-ostree/issues/885
|
||
*/
|
||
g_autofree char *sanitized = quash_string_for_error_message (checksum, len,
|
||
OSTREE_SHA256_STRING_LEN);
|
||
return glnx_throw (error, "Invalid rev %s", sanitized);
|
||
}
|
||
|
||
for (i = 0; i < len; i++)
|
||
{
|
||
guint8 c = ((guint8*) checksum)[i];
|
||
|
||
if (!((c >= 48 && c <= 57)
|
||
|| (c >= 97 && c <= 102)))
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
"Invalid character '%d' in rev '%s'",
|
||
c, checksum);
|
||
return FALSE;
|
||
}
|
||
}
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
validate_variant (GVariant *variant,
|
||
const GVariantType *variant_type,
|
||
GError **error)
|
||
{
|
||
if (!g_variant_is_normal_form (variant))
|
||
{
|
||
return glnx_throw (error, "%s", "Not normal form");
|
||
}
|
||
if (!g_variant_is_of_type (variant, variant_type))
|
||
{
|
||
return glnx_throw (error, "Doesn't match variant type '%s'",
|
||
(char *)variant_type);
|
||
}
|
||
return TRUE;
|
||
}
|
||
|
||
/* TODO: make this public later; just wraps the previously public
|
||
* commit/dirtree/dirmeta verifiers.
|
||
*/
|
||
gboolean
|
||
_ostree_validate_structureof_metadata (OstreeObjectType objtype,
|
||
GVariant *metadata,
|
||
GError **error)
|
||
{
|
||
g_assert (OSTREE_OBJECT_TYPE_IS_META (objtype));
|
||
|
||
switch (objtype)
|
||
{
|
||
case OSTREE_OBJECT_TYPE_COMMIT:
|
||
if (!ostree_validate_structureof_commit (metadata, error))
|
||
return FALSE;
|
||
break;
|
||
case OSTREE_OBJECT_TYPE_DIR_TREE:
|
||
if (!ostree_validate_structureof_dirtree (metadata, error))
|
||
return FALSE;
|
||
break;
|
||
case OSTREE_OBJECT_TYPE_DIR_META:
|
||
if (!ostree_validate_structureof_dirmeta (metadata, error))
|
||
return FALSE;
|
||
break;
|
||
case OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT:
|
||
case OSTREE_OBJECT_TYPE_COMMIT_META:
|
||
case OSTREE_OBJECT_TYPE_PAYLOAD_LINK:
|
||
/* TODO */
|
||
break;
|
||
case OSTREE_OBJECT_TYPE_FILE:
|
||
g_assert_not_reached ();
|
||
break;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Used by fsck as well as pull. Verify the checksum of a metadata object
|
||
* and its "structure" or the additional schema we impose on GVariants such
|
||
* as ensuring the "ay" checksum entries are of length 32. Another important
|
||
* one is checking for path traversal in dirtree objects.
|
||
*/
|
||
gboolean
|
||
_ostree_verify_metadata_object (OstreeObjectType objtype,
|
||
const char *expected_checksum,
|
||
GVariant *metadata,
|
||
GError **error)
|
||
{
|
||
g_assert (expected_checksum);
|
||
|
||
g_auto(OtChecksum) hasher = { 0, };
|
||
ot_checksum_init (&hasher);
|
||
ot_checksum_update (&hasher, g_variant_get_data (metadata), g_variant_get_size (metadata));
|
||
|
||
char actual_checksum[OSTREE_SHA256_STRING_LEN+1];
|
||
ot_checksum_get_hexdigest (&hasher, actual_checksum, sizeof (actual_checksum));
|
||
if (!_ostree_compare_object_checksum (objtype, expected_checksum, actual_checksum, error))
|
||
return FALSE;
|
||
|
||
/* Add the checksum + objtype prefix here */
|
||
{ const char *error_prefix = glnx_strjoina (expected_checksum, ".", ostree_object_type_to_string (objtype));
|
||
GLNX_AUTO_PREFIX_ERROR(error_prefix, error);
|
||
if (!_ostree_validate_structureof_metadata (objtype, metadata, error))
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_validate_structureof_commit:
|
||
* @commit: A commit object, %OSTREE_OBJECT_TYPE_COMMIT
|
||
* @error: Error
|
||
*
|
||
* Use this to validate the basic structure of @commit, independent of
|
||
* any other objects it references.
|
||
*
|
||
* Returns: %TRUE if @commit is structurally valid
|
||
*/
|
||
gboolean
|
||
ostree_validate_structureof_commit (GVariant *commit,
|
||
GError **error)
|
||
{
|
||
if (!validate_variant (commit, OSTREE_COMMIT_GVARIANT_FORMAT, error))
|
||
return FALSE;
|
||
|
||
g_autoptr(GVariant) parent_csum_v = NULL;
|
||
g_variant_get_child (commit, 1, "@ay", &parent_csum_v);
|
||
gsize n_elts;
|
||
(void) g_variant_get_fixed_array (parent_csum_v, &n_elts, 1);
|
||
if (n_elts > 0)
|
||
{
|
||
if (!ostree_validate_structureof_csum_v (parent_csum_v, error))
|
||
return FALSE;
|
||
}
|
||
|
||
g_autoptr(GVariant) content_csum_v = NULL;
|
||
g_variant_get_child (commit, 6, "@ay", &content_csum_v);
|
||
if (!ostree_validate_structureof_csum_v (content_csum_v, error))
|
||
return FALSE;
|
||
|
||
g_autoptr(GVariant) metadata_csum_v = NULL;
|
||
g_variant_get_child (commit, 7, "@ay", &metadata_csum_v);
|
||
if (!ostree_validate_structureof_csum_v (metadata_csum_v, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_validate_structureof_dirtree:
|
||
* @dirtree: A dirtree object, %OSTREE_OBJECT_TYPE_DIR_TREE
|
||
* @error: Error
|
||
*
|
||
* Use this to validate the basic structure of @dirtree, independent of
|
||
* any other objects it references.
|
||
*
|
||
* Returns: %TRUE if @dirtree is structurally valid
|
||
*/
|
||
gboolean
|
||
ostree_validate_structureof_dirtree (GVariant *dirtree,
|
||
GError **error)
|
||
{
|
||
const char *filename;
|
||
g_autoptr(GVariant) content_csum_v = NULL;
|
||
g_autoptr(GVariant) meta_csum_v = NULL;
|
||
g_autoptr(GVariantIter) contents_iter = NULL;
|
||
|
||
if (!validate_variant (dirtree, OSTREE_TREE_GVARIANT_FORMAT, error))
|
||
return FALSE;
|
||
|
||
g_variant_get_child (dirtree, 0, "a(say)", &contents_iter);
|
||
|
||
while (g_variant_iter_loop (contents_iter, "(&s@ay)",
|
||
&filename, &content_csum_v))
|
||
{
|
||
if (!ot_util_filename_validate (filename, error))
|
||
return FALSE;
|
||
if (!ostree_validate_structureof_csum_v (content_csum_v, error))
|
||
return FALSE;
|
||
}
|
||
/* Note we only use autoptr in case we broke out of the loop early;
|
||
* g_variant_iter_loop() has special semantics.
|
||
*/
|
||
content_csum_v = NULL;
|
||
|
||
g_variant_iter_free (contents_iter);
|
||
g_variant_get_child (dirtree, 1, "a(sayay)", &contents_iter);
|
||
|
||
while (g_variant_iter_loop (contents_iter, "(&s@ay@ay)",
|
||
&filename, &content_csum_v, &meta_csum_v))
|
||
{
|
||
if (!ot_util_filename_validate (filename, error))
|
||
return FALSE;
|
||
if (!ostree_validate_structureof_csum_v (content_csum_v, error))
|
||
return FALSE;
|
||
if (!ostree_validate_structureof_csum_v (meta_csum_v, error))
|
||
return FALSE;
|
||
}
|
||
content_csum_v = NULL;
|
||
meta_csum_v = NULL;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* This bit mirrors similar code in commit_loose_regfile_object() for the
|
||
* bare-user-only mode. It's opt-in though for all pulls.
|
||
*/
|
||
gboolean
|
||
_ostree_validate_bareuseronly_mode (guint32 content_mode,
|
||
const char *checksum,
|
||
GError **error)
|
||
{
|
||
if (S_ISREG (content_mode))
|
||
{
|
||
const guint32 invalid_modebits = ((content_mode & ~S_IFMT) & ~0775);
|
||
if (invalid_modebits > 0)
|
||
return glnx_throw (error, "Content object %s: invalid mode 0%04o with bits 0%04o",
|
||
checksum, content_mode, invalid_modebits);
|
||
}
|
||
else if (S_ISLNK (content_mode))
|
||
; /* Nothing */
|
||
else
|
||
g_assert_not_reached ();
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static gboolean
|
||
validate_stat_mode_perms (guint32 mode,
|
||
GError **error)
|
||
{
|
||
guint32 otherbits = (~S_IFMT & ~S_IRWXU & ~S_IRWXG & ~S_IRWXO &
|
||
~S_ISUID & ~S_ISGID & ~S_ISVTX);
|
||
|
||
if (mode & otherbits)
|
||
return glnx_throw (error, "Invalid mode %u; invalid bits in mode", mode);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_validate_structureof_file_mode:
|
||
* @mode: A Unix filesystem mode
|
||
* @error: Error
|
||
*
|
||
* Returns: %TRUE if @mode represents a valid file type and permissions
|
||
*/
|
||
gboolean
|
||
ostree_validate_structureof_file_mode (guint32 mode,
|
||
GError **error)
|
||
{
|
||
if (!(S_ISREG (mode) || S_ISLNK (mode)))
|
||
return glnx_throw (error, "Invalid file metadata mode %u; not a valid file type", mode);
|
||
|
||
if (!validate_stat_mode_perms (mode, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_validate_structureof_dirmeta:
|
||
* @dirmeta: A dirmeta object, %OSTREE_OBJECT_TYPE_DIR_META
|
||
* @error: Error
|
||
*
|
||
* Use this to validate the basic structure of @dirmeta.
|
||
*
|
||
* Returns: %TRUE if @dirmeta is structurally valid
|
||
*/
|
||
gboolean
|
||
ostree_validate_structureof_dirmeta (GVariant *dirmeta,
|
||
GError **error)
|
||
{
|
||
guint32 mode;
|
||
|
||
if (!validate_variant (dirmeta, OSTREE_DIRMETA_GVARIANT_FORMAT, error))
|
||
return FALSE;
|
||
|
||
g_variant_get_child (dirmeta, 2, "u", &mode);
|
||
mode = GUINT32_FROM_BE (mode);
|
||
|
||
if (!S_ISDIR (mode))
|
||
return glnx_throw (error, "Invalid directory metadata mode %u; not a directory", mode);
|
||
|
||
if (!validate_stat_mode_perms (mode, error))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_commit_get_parent:
|
||
* @commit_variant: Variant of type %OSTREE_OBJECT_TYPE_COMMIT
|
||
*
|
||
* Returns: Checksum of the parent commit of @commit_variant, or %NULL
|
||
* if none
|
||
*/
|
||
gchar *
|
||
ostree_commit_get_parent (GVariant *commit_variant)
|
||
{
|
||
g_autoptr(GVariant) bytes = NULL;
|
||
bytes = g_variant_get_child_value (commit_variant, 1);
|
||
if (g_variant_n_children (bytes) == 0)
|
||
return NULL;
|
||
return ostree_checksum_from_bytes_v (bytes);
|
||
}
|
||
|
||
/**
|
||
* ostree_commit_get_timestamp:
|
||
* @commit_variant: Commit object
|
||
*
|
||
* Returns: timestamp in seconds since the Unix epoch, UTC
|
||
* Since: 2016.3
|
||
*/
|
||
guint64
|
||
ostree_commit_get_timestamp (GVariant *commit_variant)
|
||
{
|
||
guint64 ret;
|
||
g_variant_get_child (commit_variant, 5, "t", &ret);
|
||
return GUINT64_FROM_BE (ret);
|
||
}
|
||
|
||
|
||
/**
|
||
* ostree_commit_get_content_checksum:
|
||
* @commit_variant: A commit object
|
||
*
|
||
* There are use cases where one wants a checksum just of the content of a
|
||
* commit. OSTree commits by default capture the current timestamp, and may have
|
||
* additional metadata, which means that re-committing identical content
|
||
* often results in a new checksum.
|
||
*
|
||
* By comparing checksums of content, it's possible to easily distinguish
|
||
* cases where nothing actually changed.
|
||
*
|
||
* The content checksums is simply defined as `SHA256(root dirtree_checksum || root_dirmeta_checksum)`,
|
||
* i.e. the SHA-256 of the root "dirtree" object's checksum concatenated with the
|
||
* root "dirmeta" checksum (both in binary form, not hexadecimal).
|
||
*
|
||
* Returns: (nullable): A SHA-256 hex string, or %NULL if @commit_variant is not well-formed
|
||
*
|
||
* Since: 2018.2
|
||
*/
|
||
gchar *
|
||
ostree_commit_get_content_checksum (GVariant *commit_variant)
|
||
{
|
||
g_auto(OtChecksum) checksum = { 0, };
|
||
ot_checksum_init (&checksum);
|
||
|
||
g_autoptr(GVariant) tree_contents_csum = NULL;
|
||
g_autoptr(GVariant) tree_meta_csum = NULL;
|
||
|
||
g_variant_get_child (commit_variant, 6, "@ay", &tree_contents_csum);
|
||
g_variant_get_child (commit_variant, 7, "@ay", &tree_meta_csum);
|
||
|
||
const guchar *bytes;
|
||
bytes = ostree_checksum_bytes_peek_validate (tree_contents_csum, NULL);
|
||
if (!bytes)
|
||
return NULL;
|
||
ot_checksum_update (&checksum, bytes, OSTREE_SHA256_DIGEST_LEN);
|
||
bytes = ostree_checksum_bytes_peek_validate (tree_meta_csum, NULL);
|
||
if (!bytes)
|
||
return NULL;
|
||
ot_checksum_update (&checksum, bytes, OSTREE_SHA256_DIGEST_LEN);
|
||
char hexdigest[OSTREE_SHA256_STRING_LEN+1];
|
||
ot_checksum_get_hexdigest (&checksum, hexdigest, sizeof (hexdigest));
|
||
return g_strdup (hexdigest);
|
||
}
|
||
|
||
G_DEFINE_BOXED_TYPE (OstreeCommitSizesEntry, ostree_commit_sizes_entry,
|
||
ostree_commit_sizes_entry_copy, ostree_commit_sizes_entry_free)
|
||
|
||
/**
|
||
* ostree_commit_sizes_entry_new:
|
||
* @checksum: (not nullable): object checksum
|
||
* @objtype: object type
|
||
* @unpacked: unpacked object size
|
||
* @archived: compressed object size
|
||
*
|
||
* Create a new #OstreeCommitSizesEntry for representing an object in a
|
||
* commit's "ostree.sizes" metadata.
|
||
*
|
||
* Returns: (transfer full) (nullable): a new #OstreeCommitSizesEntry
|
||
* Since: 2020.1
|
||
*/
|
||
OstreeCommitSizesEntry *
|
||
ostree_commit_sizes_entry_new (const gchar *checksum,
|
||
OstreeObjectType objtype,
|
||
guint64 unpacked,
|
||
guint64 archived)
|
||
{
|
||
g_return_val_if_fail (checksum == NULL || ostree_validate_checksum_string (checksum, NULL), NULL);
|
||
|
||
g_autoptr(OstreeCommitSizesEntry) entry = g_new0 (OstreeCommitSizesEntry, 1);
|
||
entry->checksum = g_strdup (checksum);
|
||
entry->objtype = objtype;
|
||
entry->unpacked = unpacked;
|
||
entry->archived = archived;
|
||
|
||
return g_steal_pointer (&entry);
|
||
}
|
||
|
||
/**
|
||
* ostree_commit_sizes_entry_copy:
|
||
* @entry: (not nullable): an #OstreeCommitSizesEntry
|
||
*
|
||
* Create a copy of the given @entry.
|
||
*
|
||
* Returns: (transfer full) (nullable): a new copy of @entry
|
||
* Since: 2020.1
|
||
*/
|
||
OstreeCommitSizesEntry *
|
||
ostree_commit_sizes_entry_copy (const OstreeCommitSizesEntry *entry)
|
||
{
|
||
g_return_val_if_fail (entry != NULL, NULL);
|
||
|
||
return ostree_commit_sizes_entry_new (entry->checksum,
|
||
entry->objtype,
|
||
entry->unpacked,
|
||
entry->archived);
|
||
}
|
||
|
||
/**
|
||
* ostree_commit_sizes_entry_free:
|
||
* @entry: (transfer full): an #OstreeCommitSizesEntry
|
||
*
|
||
* Free given @entry.
|
||
*
|
||
* Since: 2020.1
|
||
*/
|
||
void
|
||
ostree_commit_sizes_entry_free (OstreeCommitSizesEntry *entry)
|
||
{
|
||
g_return_if_fail (entry != NULL);
|
||
|
||
g_free (entry->checksum);
|
||
g_free (entry);
|
||
}
|
||
|
||
static gboolean
|
||
read_sizes_entry (GVariant *entry,
|
||
OstreeCommitSizesEntry **out_sizes,
|
||
GError **error)
|
||
{
|
||
gsize entry_size = g_variant_get_size (entry);
|
||
g_return_val_if_fail (entry_size >= OSTREE_SHA256_DIGEST_LEN + 2, FALSE);
|
||
|
||
const guchar *buffer = g_variant_get_data (entry);
|
||
if (buffer == NULL)
|
||
return glnx_throw (error, "Could not read ostree.sizes metadata entry");
|
||
|
||
char checksum[OSTREE_SHA256_STRING_LEN + 1];
|
||
ostree_checksum_inplace_from_bytes (buffer, checksum);
|
||
buffer += OSTREE_SHA256_DIGEST_LEN;
|
||
entry_size -= OSTREE_SHA256_DIGEST_LEN;
|
||
|
||
gsize bytes_read = 0;
|
||
guint64 archived = 0;
|
||
if (!_ostree_read_varuint64 (buffer, entry_size, &archived, &bytes_read))
|
||
return glnx_throw (error, "Unexpected EOF reading ostree.sizes varint");
|
||
buffer += bytes_read;
|
||
entry_size -= bytes_read;
|
||
|
||
guint64 unpacked = 0;
|
||
if (!_ostree_read_varuint64 (buffer, entry_size, &unpacked, &bytes_read))
|
||
return glnx_throw (error, "Unexpected EOF reading ostree.sizes varint");
|
||
buffer += bytes_read;
|
||
entry_size -= bytes_read;
|
||
|
||
/* On newer commits, an additional byte is used for the object type. */
|
||
OstreeObjectType objtype;
|
||
if (entry_size > 0)
|
||
{
|
||
objtype = *buffer;
|
||
if (objtype < OSTREE_OBJECT_TYPE_FILE || objtype > OSTREE_OBJECT_TYPE_LAST)
|
||
return glnx_throw (error, "Unexpected ostree.sizes object type %u",
|
||
objtype);
|
||
buffer++;
|
||
entry_size--;
|
||
}
|
||
else
|
||
{
|
||
/* Assume the object is a file. */
|
||
objtype = OSTREE_OBJECT_TYPE_FILE;
|
||
}
|
||
|
||
g_autoptr(OstreeCommitSizesEntry) sizes = ostree_commit_sizes_entry_new (checksum,
|
||
objtype,
|
||
unpacked,
|
||
archived);
|
||
|
||
if (out_sizes != NULL)
|
||
*out_sizes = g_steal_pointer (&sizes);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* ostree_commit_get_object_sizes:
|
||
* @commit_variant: (not nullable): variant of type %OSTREE_OBJECT_TYPE_COMMIT
|
||
* @out_sizes_entries: (out) (element-type OstreeCommitSizesEntry) (transfer container) (optional):
|
||
* return location for an array of object size entries
|
||
* @error: Error
|
||
*
|
||
* Reads a commit's "ostree.sizes" metadata and returns an array of
|
||
* #OstreeCommitSizesEntry in @out_sizes_entries. Each element
|
||
* represents an object in the commit. If the commit does not contain
|
||
* the "ostree.sizes" metadata, a %G_IO_ERROR_NOT_FOUND error will be
|
||
* returned.
|
||
*
|
||
* Since: 2020.1
|
||
*/
|
||
gboolean
|
||
ostree_commit_get_object_sizes (GVariant *commit_variant,
|
||
GPtrArray **out_sizes_entries,
|
||
GError **error)
|
||
{
|
||
g_return_val_if_fail (commit_variant != NULL, FALSE);
|
||
|
||
g_autoptr(GVariant) metadata = g_variant_get_child_value (commit_variant, 0);
|
||
g_autoptr(GVariant) sizes_variant =
|
||
g_variant_lookup_value (metadata, "ostree.sizes",
|
||
G_VARIANT_TYPE ("a" _OSTREE_OBJECT_SIZES_ENTRY_SIGNATURE));
|
||
if (sizes_variant == NULL)
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||
"No metadata key ostree.sizes in commit");
|
||
return FALSE;
|
||
}
|
||
|
||
g_autoptr(GPtrArray) sizes_entries =
|
||
g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_commit_sizes_entry_free);
|
||
g_autoptr(GVariant) entry = NULL;
|
||
GVariantIter entry_iter;
|
||
g_variant_iter_init (&entry_iter, sizes_variant);
|
||
while ((entry = g_variant_iter_next_value (&entry_iter)))
|
||
{
|
||
OstreeCommitSizesEntry *sizes_entry = NULL;
|
||
if (!read_sizes_entry (entry, &sizes_entry, error))
|
||
return FALSE;
|
||
g_clear_pointer (&entry, g_variant_unref);
|
||
g_ptr_array_add (sizes_entries, sizes_entry);
|
||
}
|
||
|
||
if (out_sizes_entries != NULL)
|
||
*out_sizes_entries = g_steal_pointer (&sizes_entries);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* Used in pull/deploy to validate we're not being downgraded */
|
||
gboolean
|
||
_ostree_compare_timestamps (const char *current_rev,
|
||
guint64 current_ts,
|
||
const char *new_rev,
|
||
guint64 new_ts,
|
||
GError **error)
|
||
{
|
||
/* Newer timestamp is OK */
|
||
if (new_ts > current_ts)
|
||
return TRUE;
|
||
/* If they're equal, ensure they're the same rev */
|
||
if (new_ts == current_ts || strcmp (current_rev, new_rev) == 0)
|
||
return TRUE;
|
||
|
||
/* Looks like a downgrade, format an error message */
|
||
g_autoptr(GDateTime) current_dt = g_date_time_new_from_unix_utc (current_ts);
|
||
g_autoptr(GDateTime) new_dt = g_date_time_new_from_unix_utc (new_ts);
|
||
|
||
if (current_dt == NULL || new_dt == NULL)
|
||
return glnx_throw (error, "Upgrade target revision '%s' timestamp (%" G_GINT64_FORMAT ") or current revision '%s' timestamp (%" G_GINT64_FORMAT ") is invalid",
|
||
new_rev, new_ts,
|
||
current_rev, current_ts);
|
||
|
||
g_autofree char *current_ts_str = g_date_time_format (current_dt, "%c");
|
||
g_autofree char *new_ts_str = g_date_time_format (new_dt, "%c");
|
||
|
||
return glnx_throw (error, "Upgrade target revision '%s' with timestamp '%s' is chronologically older than current revision '%s' with timestamp '%s'; use --allow-downgrade to permit",
|
||
new_rev, new_ts_str, current_rev, current_ts_str);
|
||
}
|
||
|
||
|
||
#ifndef OSTREE_DISABLE_GPGME
|
||
GVariant *
|
||
_ostree_detached_metadata_append_gpg_sig (GVariant *existing_metadata,
|
||
GBytes *signature_bytes)
|
||
{
|
||
GVariantDict metadata_dict;
|
||
g_autoptr(GVariant) signature_data = NULL;
|
||
g_autoptr(GVariantBuilder) signature_builder = NULL;
|
||
|
||
g_variant_dict_init (&metadata_dict, existing_metadata);
|
||
|
||
signature_data = g_variant_dict_lookup_value (&metadata_dict,
|
||
_OSTREE_METADATA_GPGSIGS_NAME,
|
||
_OSTREE_METADATA_GPGSIGS_TYPE);
|
||
|
||
/* signature_data may be NULL */
|
||
signature_builder = ot_util_variant_builder_from_variant (signature_data, _OSTREE_METADATA_GPGSIGS_TYPE);
|
||
|
||
g_variant_builder_add (signature_builder, "@ay", ot_gvariant_new_ay_bytes (signature_bytes));
|
||
|
||
g_variant_dict_insert_value (&metadata_dict,
|
||
_OSTREE_METADATA_GPGSIGS_NAME,
|
||
g_variant_builder_end (signature_builder));
|
||
|
||
return g_variant_ref_sink (g_variant_dict_end (&metadata_dict));
|
||
}
|
||
#endif /* OSTREE_DISABLE_GPGME */
|
||
|
||
/**
|
||
* _ostree_get_default_sysroot_path:
|
||
*
|
||
* Returns a #GFile for the default system root, which is usually the root
|
||
* directory ("/") unless overridden by the %OSTREE_SYSROOT environment
|
||
* variable.
|
||
*
|
||
* Returns: a #GFile for the default system root
|
||
*/
|
||
GFile *
|
||
_ostree_get_default_sysroot_path (void)
|
||
{
|
||
static gsize default_sysroot_path_initialized;
|
||
static GFile *default_sysroot_path;
|
||
|
||
if (g_once_init_enter (&default_sysroot_path_initialized))
|
||
{
|
||
const char *path = g_getenv ("OSTREE_SYSROOT");
|
||
if (path == NULL || *path == '\0')
|
||
path = "/";
|
||
default_sysroot_path = g_file_new_for_path (path);
|
||
g_once_init_leave (&default_sysroot_path_initialized, 1);
|
||
}
|
||
|
||
return default_sysroot_path;
|
||
}
|
||
|
||
/**
|
||
* ostree_check_version:
|
||
* @required_year: Major/year required
|
||
* @required_release: Release version required
|
||
*
|
||
* Returns: %TRUE if current libostree has at least the requested version, %FALSE otherwise
|
||
*
|
||
* Since: 2017.4
|
||
*/
|
||
gboolean
|
||
ostree_check_version (guint required_year, guint required_release)
|
||
{
|
||
return OSTREE_CHECK_VERSION(required_year, required_release);
|
||
}
|