/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2011 Colin Walters * * 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 */ /* for mkdtemp */ #define _GNU_SOURCE #include "config.h" #include "ostree.h" #include "otutil.h" #include #include #include #include #define ALIGN_VALUE(this, boundary) \ (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1))) 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 (); } } gboolean ostree_validate_checksum_string (const char *sha256, GError **error) { return ostree_validate_structureof_checksum_string (sha256, error); } gboolean ostree_validate_rev (const char *rev, GError **error) { gboolean ret = FALSE; ot_lptrarray GPtrArray *components = NULL; if (!ot_util_path_split_validate (rev, &components, error)) goto out; if (components->len == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid empty rev"); goto out; } ret = TRUE; out: return ret; } static char * canonicalize_xattrs (char *xattr_string, size_t len) { char *p; GSList *xattrs = NULL; GSList *iter; GString *result; result = g_string_new (0); p = xattr_string; while (p < xattr_string+len) { xattrs = g_slist_prepend (xattrs, p); p += strlen (p) + 1; } xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp); for (iter = xattrs; iter; iter = iter->next) g_string_append (result, iter->data); g_slist_free (xattrs); return g_string_free (result, FALSE); } static gboolean read_xattr_name_array (const char *path, const char *xattrs, size_t len, GVariantBuilder *builder, GError **error) { gboolean ret = FALSE; const char *p; p = xattrs; while (p < xattrs+len) { ssize_t bytes_read; char *buf; bytes_read = lgetxattr (path, p, NULL, 0); if (bytes_read < 0) { ot_util_set_error_from_errno (error, errno); goto out; } if (bytes_read == 0) continue; buf = g_malloc (bytes_read); if (lgetxattr (path, p, buf, bytes_read) < 0) { ot_util_set_error_from_errno (error, errno); g_free (buf); goto out; } g_variant_builder_add (builder, "(@ay@ay)", g_variant_new_bytestring (p), g_variant_new_from_data (G_VARIANT_TYPE ("ay"), buf, bytes_read, FALSE, g_free, buf)); p = p + strlen (p) + 1; } ret = TRUE; out: return ret; } gboolean ostree_get_xattrs_for_file (GFile *f, GVariant **out_xattrs, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; const char *path; ssize_t bytes_read; ot_lvariant GVariant *ret_xattrs = NULL; ot_lfree char *xattr_names = NULL; ot_lfree char *xattr_names_canonical = NULL; GVariantBuilder builder; gboolean builder_initialized = FALSE; path = ot_gfile_get_path_cached (f); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)")); builder_initialized = TRUE; bytes_read = llistxattr (path, NULL, 0); if (bytes_read < 0) { if (errno != ENOTSUP) { ot_util_set_error_from_errno (error, errno); goto out; } } else if (bytes_read > 0) { xattr_names = g_malloc (bytes_read); if (llistxattr (path, xattr_names, bytes_read) < 0) { ot_util_set_error_from_errno (error, errno); goto out; } xattr_names_canonical = canonicalize_xattrs (xattr_names, bytes_read); if (!read_xattr_name_array (path, xattr_names_canonical, bytes_read, &builder, error)) goto out; } ret_xattrs = g_variant_builder_end (&builder); g_variant_ref_sink (ret_xattrs); ret = TRUE; ot_transfer_out_value (out_xattrs, &ret_xattrs); out: if (!builder_initialized) g_variant_builder_clear (&builder); return ret; } GVariant * ostree_file_header_new (GFileInfo *file_info, GVariant *xattrs) { guint32 uid; guint32 gid; guint32 mode; guint32 rdev; const char *symlink_target; GVariant *ret; ot_lvariant GVariant *tmp_xattrs = NULL; uid = g_file_info_get_attribute_uint32 (file_info, "unix::uid"); gid = g_file_info_get_attribute_uint32 (file_info, "unix::gid"); mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); rdev = g_file_info_get_attribute_uint32 (file_info, "unix::rdev"); 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 = ""; if (xattrs == NULL) tmp_xattrs = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0)); ret = g_variant_new ("(uuuus@a(ayay))", GUINT32_TO_BE (uid), GUINT32_TO_BE (gid), GUINT32_TO_BE (mode), GUINT32_TO_BE (rdev), symlink_target, xattrs ? xattrs : tmp_xattrs); g_variant_ref_sink (ret); return ret; } static gboolean write_padding (GOutputStream *output, guint alignment, gsize offset, gsize *out_bytes_written, GChecksum *checksum, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint bits; guint padding_len; guchar padding_nuls[8] = {0, 0, 0, 0, 0, 0, 0, 0}; if (alignment == 8) bits = ((offset) & 7); else bits = ((offset) & 3); if (bits > 0) { padding_len = alignment - bits; if (!ot_gio_write_update_checksum (output, (guchar*)padding_nuls, padding_len, out_bytes_written, checksum, cancellable, error)) goto out; } ret = TRUE; out: return ret; } gboolean ostree_write_variant_with_size (GOutputStream *output, GVariant *variant, guint64 alignment_offset, gsize *out_bytes_written, GChecksum *checksum, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint64 variant_size; guint32 variant_size_u32_be; gsize bytes_written; gsize ret_bytes_written = 0; /* Write variant size */ variant_size = g_variant_get_size (variant); g_assert (variant_size < G_MAXUINT32); variant_size_u32_be = GUINT32_TO_BE((guint32) variant_size); bytes_written = 0; if (!ot_gio_write_update_checksum (output, &variant_size_u32_be, 4, &bytes_written, checksum, cancellable, error)) goto out; ret_bytes_written += bytes_written; alignment_offset += bytes_written; bytes_written = 0; /* Pad to offset of 8, write variant */ if (!write_padding (output, 8, alignment_offset, &bytes_written, checksum, cancellable, error)) goto out; ret_bytes_written += bytes_written; bytes_written = 0; if (!ot_gio_write_update_checksum (output, g_variant_get_data (variant), variant_size, &bytes_written, checksum, cancellable, error)) goto out; ret_bytes_written += bytes_written; ret = TRUE; if (out_bytes_written) *out_bytes_written = ret_bytes_written; out: return ret; } gboolean ostree_write_file_header_update_checksum (GOutputStream *out, GVariant *header, GChecksum *checksum, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gsize bytes_written; if (!ostree_write_variant_with_size (out, header, 0, &bytes_written, checksum, cancellable, error)) goto out; ret = TRUE; out: return ret; } gboolean ostree_raw_file_to_content_stream (GInputStream *input, GFileInfo *file_info, GVariant *xattrs, GInputStream **out_input, guint64 *out_length, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gpointer header_data; gsize header_size; ot_lobj GInputStream *ret_input = NULL; ot_lvariant GVariant *file_header = NULL; ot_lptrarray GPtrArray *streams = NULL; ot_lobj GOutputStream *header_out_stream = NULL; ot_lobj GInputStream *header_in_stream = NULL; file_header = ostree_file_header_new (file_info, xattrs); header_out_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); if (!ostree_write_variant_with_size (header_out_stream, file_header, 0, NULL, NULL, cancellable, error)) goto out; if (!g_output_stream_close (header_out_stream, cancellable, error)) goto out; header_size = g_memory_output_stream_get_data_size ((GMemoryOutputStream*) header_out_stream); header_data = g_memory_output_stream_steal_data ((GMemoryOutputStream*) header_out_stream); header_in_stream = g_memory_input_stream_new_from_data (header_data, header_size, g_free); streams = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); g_ptr_array_add (streams, g_object_ref (header_in_stream)); if (input) g_ptr_array_add (streams, g_object_ref (input)); ret_input = (GInputStream*)ostree_chain_input_stream_new (streams); ret = TRUE; ot_transfer_out_value (out_input, &ret_input); if (out_length) *out_length = header_size + g_file_info_get_size (file_info); out: return ret; } gboolean ostree_content_stream_parse (GInputStream *input, guint64 input_length, gboolean trusted, GInputStream **out_input, GFileInfo **out_file_info, GVariant **out_xattrs, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint32 archive_header_size; guchar dummy[4]; gsize bytes_read; ot_lobj GInputStream *ret_input = NULL; ot_lobj GFileInfo *content_file_info = NULL; ot_lobj GFileInfo *ret_file_info = NULL; ot_lvariant GVariant *ret_xattrs = NULL; ot_lvariant GVariant *file_header = NULL; ot_lfree guchar *buf = NULL; if (!g_input_stream_read_all (input, &archive_header_size, 4, &bytes_read, cancellable, error)) goto out; archive_header_size = GUINT32_FROM_BE (archive_header_size); if (archive_header_size > input_length) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "File header size %u exceeds size %" G_GUINT64_FORMAT, (guint)archive_header_size, input_length); goto out; } if (archive_header_size == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "File header size is zero"); goto out; } /* Skip over padding */ if (!g_input_stream_read_all (input, dummy, 4, &bytes_read, cancellable, error)) goto out; buf = g_malloc (archive_header_size); if (!g_input_stream_read_all (input, buf, archive_header_size, &bytes_read, cancellable, error)) goto out; file_header = g_variant_new_from_data (OSTREE_FILE_HEADER_GVARIANT_FORMAT, buf, archive_header_size, trusted, g_free, buf); buf = NULL; if (!ostree_file_header_parse (file_header, out_file_info ? &ret_file_info : NULL, out_xattrs ? &ret_xattrs : NULL, error)) goto out; if (ret_file_info) g_file_info_set_size (ret_file_info, input_length - archive_header_size - 8); 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. **/ ret_input = g_object_ref (input); } ret = TRUE; 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); out: return ret; } gboolean ostree_zlib_content_stream_open (GInputStream *input, guint64 *out_len, GInputStream **out_uncompressed, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint64 uncompressed_len; ot_lobj GConverter *zlib_decomp = NULL; ot_lobj GInputStream *uncomp_input = NULL; if (!g_input_stream_read_all (input, &uncompressed_len, sizeof (guint64), NULL, cancellable, error)) goto out; uncompressed_len = GUINT64_FROM_BE (uncompressed_len); zlib_decomp = (GConverter*)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW); uncomp_input = g_converter_input_stream_new (input, zlib_decomp); if (out_len) *out_len = uncompressed_len; ot_transfer_out_value (out_uncompressed, &uncomp_input); ret = TRUE; out: return ret; } gboolean ostree_content_file_parse (GFile *content_path, gboolean trusted, GInputStream **out_input, GFileInfo **out_file_info, GVariant **out_xattrs, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint64 length; ot_lobj GInputStream *file_input = NULL; ot_lobj GInputStream *ret_input = NULL; ot_lobj GFileInfo *content_file_info = NULL; ot_lobj GFileInfo *ret_file_info = NULL; ot_lvariant GVariant *ret_xattrs = NULL; file_input = (GInputStream*)gs_file_read_noatime (content_path, cancellable, error); if (!file_input) goto out; content_file_info = g_file_input_stream_query_info ((GFileInputStream*)file_input, OSTREE_GIO_FAST_QUERYINFO, cancellable, error); if (!content_file_info) goto out; length = g_file_info_get_size (content_file_info); if (!ostree_content_stream_parse (file_input, length, trusted, out_input ? &ret_input : NULL, &ret_file_info, &ret_xattrs, cancellable, error)) goto out; ret = TRUE; 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); out: return ret; } gboolean ostree_checksum_file_from_input (GFileInfo *file_info, GVariant *xattrs, GInputStream *in, OstreeObjectType objtype, guchar **out_csum, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; ot_lfree guchar *ret_csum = NULL; GChecksum *checksum = NULL; checksum = g_checksum_new (G_CHECKSUM_SHA256); if (OSTREE_OBJECT_TYPE_IS_META (objtype)) { if (!ot_gio_splice_update_checksum (NULL, in, checksum, cancellable, error)) goto out; } else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) { ot_lvariant GVariant *dirmeta = ostree_create_directory_metadata (file_info, xattrs); g_checksum_update (checksum, g_variant_get_data (dirmeta), g_variant_get_size (dirmeta)); } else { ot_lvariant GVariant *file_header = NULL; file_header = ostree_file_header_new (file_info, xattrs); if (!ostree_write_file_header_update_checksum (NULL, file_header, checksum, cancellable, error)) goto out; if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) { if (!ot_gio_splice_update_checksum (NULL, in, checksum, cancellable, error)) goto out; } } ret_csum = ot_csum_from_gchecksum (checksum); ret = TRUE; ot_transfer_out_value (out_csum, &ret_csum); out: g_clear_pointer (&checksum, (GDestroyNotify)g_checksum_free); return ret; } gboolean ostree_checksum_file (GFile *f, OstreeObjectType objtype, guchar **out_csum, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; ot_lobj GFileInfo *file_info = NULL; ot_lobj GInputStream *in = NULL; ot_lvariant GVariant *xattrs = NULL; ot_lfree guchar *ret_csum = NULL; if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; file_info = g_file_query_info (f, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!file_info) goto out; if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) { in = (GInputStream*)g_file_read (f, cancellable, error); if (!in) goto out; } if (objtype == OSTREE_OBJECT_TYPE_FILE) { if (!ostree_get_xattrs_for_file (f, &xattrs, cancellable, error)) goto out; } if (!ostree_checksum_file_from_input (file_info, xattrs, in, objtype, &ret_csum, cancellable, error)) goto out; ret = TRUE; ot_transfer_out_value(out_csum, &ret_csum); out: return ret; } 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); } 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); } 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; } 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; } gboolean ostree_set_xattrs (GFile *f, GVariant *xattrs, GCancellable *cancellable, GError **error) { const char *path; gboolean ret = FALSE; int i, n; path = ot_gfile_get_path_cached (f); n = g_variant_n_children (xattrs); for (i = 0; i < n; i++) { const guint8* name; GVariant *value; const guint8* value_data; gsize value_len; gboolean loop_err; g_variant_get_child (xattrs, i, "(^&ay@ay)", &name, &value); value_data = g_variant_get_fixed_array (value, &value_len, 1); loop_err = lsetxattr (path, (char*)name, (char*)value_data, value_len, XATTR_REPLACE) < 0; g_clear_pointer (&value, (GDestroyNotify) g_variant_unref); if (loop_err) { ot_util_set_error_from_errno (error, errno); goto out; } } ret = TRUE; out: return ret; } 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"; default: g_assert_not_reached (); return NULL; } } 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; g_assert_not_reached (); return 0; } char * ostree_object_to_string (const char *checksum, OstreeObjectType objtype) { return g_strconcat (checksum, ".", ostree_object_type_to_string (objtype), NULL); } 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); } 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); } int ostree_cmp_checksum_bytes (const guchar *a, const guchar *b) { return memcmp (a, b, 32); } GVariant * ostree_object_name_serialize (const char *checksum, OstreeObjectType objtype) { g_assert (objtype >= OSTREE_OBJECT_TYPE_FILE && objtype <= OSTREE_OBJECT_TYPE_COMMIT); return g_variant_new ("(su)", checksum, (guint32)objtype); } 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; } static void checksum_to_bytes (const char *checksum, guchar *buf) { guint i; guint j; for (i = 0, j = 0; i < 32; 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; } } guchar * ostree_checksum_to_bytes (const char *checksum) { guchar *ret = g_malloc (32); checksum_to_bytes (checksum, ret); return ret; } GVariant * ostree_checksum_to_bytes_v (const char *checksum) { guchar result[32]; checksum_to_bytes (checksum, result); return ot_gvariant_new_bytearray ((guchar*)result, 32); } char * ostree_checksum_from_bytes (const guchar *csum) { static const gchar hexchars[] = "0123456789abcdef"; char *ret; guint i, j; ret = g_malloc (65); for (i = 0, j = 0; i < 32; i++, j += 2) { guchar byte = csum[i]; ret[j] = hexchars[byte >> 4]; ret[j+1] = hexchars[byte & 0xF]; } ret[j] = '\0'; return ret; } char * ostree_checksum_from_bytes_v (GVariant *csum_bytes) { return ostree_checksum_from_bytes (ostree_checksum_bytes_peek (csum_bytes)); } const guchar * ostree_checksum_bytes_peek (GVariant *bytes) { gsize n_elts; return g_variant_get_fixed_array (bytes, &n_elts, 1); } char * ostree_get_relative_object_path (const char *checksum, OstreeObjectType type, gboolean compressed) { GString *path; g_assert (strlen (checksum) == 64); 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_archive_content_path (const char *checksum) { GString *path; g_assert (strlen (checksum) == 64); 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 (path, ".filecontent"); return g_string_free (path, FALSE); } gboolean ostree_file_header_parse (GVariant *metadata, GFileInfo **out_file_info, GVariant **out_xattrs, GError **error) { gboolean ret = FALSE; guint32 uid, gid, mode, rdev; const char *symlink_target; ot_lobj GFileInfo *ret_file_info = NULL; ot_lvariant GVariant *ret_xattrs = NULL; g_variant_get (metadata, "(uuuu&s@a(ayay))", &uid, &gid, &mode, &rdev, &symlink_target, &ret_xattrs); uid = GUINT32_FROM_BE (uid); gid = GUINT32_FROM_BE (gid); mode = GUINT32_FROM_BE (mode); rdev = GUINT32_FROM_BE (rdev); ret_file_info = g_file_info_new (); g_file_info_set_attribute_uint32 (ret_file_info, "standard::type", ot_gfile_type_for_mode (mode)); g_file_info_set_attribute_boolean (ret_file_info, "standard::is-symlink", S_ISLNK (mode)); g_file_info_set_attribute_uint32 (ret_file_info, "unix::uid", uid); g_file_info_set_attribute_uint32 (ret_file_info, "unix::gid", gid); g_file_info_set_attribute_uint32 (ret_file_info, "unix::mode", mode); 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 if (S_ISCHR (mode) || S_ISBLK (mode)) { g_file_info_set_attribute_uint32 (ret_file_info, "unix::rdev", rdev); } else if (S_ISFIFO (mode)) { ; } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Corrupted archive file; invalid mode %u", mode); goto out; } ret = TRUE; ot_transfer_out_value(out_file_info, &ret_file_info); ot_transfer_out_value(out_xattrs, &ret_xattrs); out: return ret; } gboolean ostree_create_file_from_input (GFile *dest_file, GFileInfo *finfo, GVariant *xattrs, GInputStream *input, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; const char *dest_path; guint32 uid, gid, mode; ot_lobj GFileOutputStream *out = NULL; if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; if (finfo != NULL) { mode = g_file_info_get_attribute_uint32 (finfo, "unix::mode"); } else { mode = S_IFREG | 0664; } dest_path = ot_gfile_get_path_cached (dest_file); if (S_ISDIR (mode)) { if (mkdir (ot_gfile_get_path_cached (dest_file), mode) < 0) { ot_util_set_error_from_errno (error, errno); goto out; } } else if (S_ISREG (mode)) { out = g_file_create (dest_file, 0, cancellable, error); if (!out) goto out; if (input) { if (g_output_stream_splice ((GOutputStream*)out, input, 0, cancellable, error) < 0) goto out; } if (!g_output_stream_close ((GOutputStream*)out, NULL, error)) goto out; } else if (S_ISLNK (mode)) { const char *target = g_file_info_get_attribute_byte_string (finfo, "standard::symlink-target"); if (symlink (target, dest_path) < 0) { ot_util_set_error_from_errno (error, errno); goto out; } } else if (S_ISCHR (mode) || S_ISBLK (mode)) { guint32 dev = g_file_info_get_attribute_uint32 (finfo, "unix::rdev"); if (mknod (dest_path, mode, dev) < 0) { ot_util_set_error_from_errno (error, errno); goto out; } } else if (S_ISFIFO (mode)) { if (mkfifo (dest_path, mode) < 0) { ot_util_set_error_from_errno (error, errno); goto out; } } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid mode %u", mode); goto out; } if (finfo != NULL) { uid = g_file_info_get_attribute_uint32 (finfo, "unix::uid"); gid = g_file_info_get_attribute_uint32 (finfo, "unix::gid"); if (lchown (dest_path, uid, gid) < 0) { ot_util_set_error_from_errno (error, errno); g_prefix_error (error, "lchown(%u, %u) failed: ", uid, gid); goto out; } } if (!S_ISLNK (mode)) { if (chmod (dest_path, mode) < 0) { ot_util_set_error_from_errno (error, errno); g_prefix_error (error, "chmod(%u) failed: ", mode); goto out; } } if (xattrs != NULL) { if (!ostree_set_xattrs (dest_file, xattrs, cancellable, error)) goto out; } ret = TRUE; out: if (!ret && !S_ISDIR(mode)) { (void) unlink (dest_path); } return ret; } static GString * create_tmp_string (const char *dirpath, const char *prefix, const char *suffix) { GString *tmp_name = NULL; if (!prefix) prefix = "tmp"; if (!suffix) suffix = "tmp"; tmp_name = g_string_new (dirpath); g_string_append_c (tmp_name, '/'); g_string_append (tmp_name, prefix); g_string_append (tmp_name, "-XXXXXXXXXXXX."); g_string_append (tmp_name, suffix); return tmp_name; } static char * subst_xxxxxx (const char *string) { static const char table[] = "ABCEDEFGHIJKLMNOPQRSTUVWXYZabcedefghijklmnopqrstuvwxyz0123456789"; char *ret = g_strdup (string); guint8 *xxxxxx = (guint8*)strstr (ret, "XXXXXX"); g_assert (xxxxxx != NULL); while (*xxxxxx == 'X') { int offset = g_random_int_range (0, sizeof (table) - 1); *xxxxxx = (guint8)table[offset]; xxxxxx++; } return ret; } gboolean ostree_create_temp_file_from_input (GFile *dir, const char *prefix, const char *suffix, GFileInfo *finfo, GVariant *xattrs, GInputStream *input, GFile **out_file, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GError *temp_error = NULL; int i = 0; ot_lfree char *possible_name = NULL; ot_lobj GFile *possible_file = NULL; ot_lfree guchar *ret_csum = NULL; GString *tmp_name = NULL; tmp_name = create_tmp_string (ot_gfile_get_path_cached (dir), prefix, suffix); /* 128 attempts seems reasonable... */ for (i = 0; i < 128; i++) { if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto out; g_free (possible_name); possible_name = subst_xxxxxx (tmp_name->str); g_clear_object (&possible_file); possible_file = g_file_get_child (dir, possible_name); if (!ostree_create_file_from_input (possible_file, finfo, xattrs, input, cancellable, &temp_error)) { if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { g_clear_error (&temp_error); continue; } else { g_propagate_error (error, temp_error); goto out; } } else { break; } } if (i >= 128) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Exhausted 128 attempts to create a temporary file"); goto out; } ret = TRUE; ot_transfer_out_value(out_file, &possible_file); out: if (tmp_name) g_string_free (tmp_name, TRUE); return ret; } gboolean ostree_create_temp_regular_file (GFile *dir, const char *prefix, const char *suffix, GFile **out_file, GOutputStream **out_stream, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; ot_lobj GFile *ret_file = NULL; ot_lobj GOutputStream *ret_stream = NULL; if (!ostree_create_temp_file_from_input (dir, prefix, suffix, NULL, NULL, NULL, &ret_file, cancellable, error)) goto out; ret_stream = (GOutputStream*)g_file_append_to (ret_file, 0, cancellable, error); if (ret_stream == NULL) goto out; ret = TRUE; ot_transfer_out_value(out_file, &ret_file); ot_transfer_out_value(out_stream, &ret_stream); out: return ret; } gboolean ostree_create_temp_dir (GFile *dir, const char *prefix, const char *suffix, GFile **out_file, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; ot_lfree char *template = NULL; ot_lobj GFile *ret_file = NULL; if (dir == NULL) dir = g_file_new_for_path (g_get_tmp_dir ()); template = g_strdup_printf ("%s/%s-XXXXXX", ot_gfile_get_path_cached (dir), prefix ? prefix : "tmp"); if (mkdtemp (template) == NULL) { ot_util_set_error_from_errno (error, errno); goto out; } ret_file = g_file_new_for_path (template); ret = TRUE; ot_transfer_out_value (out_file, &ret_file); out: return ret; } 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) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid object type '%u'", objtype); return FALSE; } return TRUE; } gboolean ostree_validate_structureof_csum_v (GVariant *checksum, GError **error) { gsize n_children = g_variant_n_children (checksum); if (n_children != 32) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid checksum of length %" G_GUINT64_FORMAT " expected 32", (guint64) n_children); return FALSE; } return TRUE; } gboolean ostree_validate_structureof_checksum_string (const char *checksum, GError **error) { int i = 0; size_t len = strlen (checksum); if (len != 64) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid rev '%s'", checksum); return FALSE; } 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)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Not normal form"); return FALSE; } if (!g_variant_is_of_type (variant, variant_type)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Doesn't match variant type '%s'", (char*)variant_type); return FALSE; } return TRUE; } gboolean ostree_validate_structureof_commit (GVariant *commit, GError **error) { gboolean ret = FALSE; ot_lvariant GVariant *parent_csum_v = NULL; ot_lvariant GVariant *content_csum_v = NULL; ot_lvariant GVariant *metadata_csum_v = NULL; gsize n_elts; if (!validate_variant (commit, OSTREE_COMMIT_GVARIANT_FORMAT, error)) goto out; g_variant_get_child (commit, 1, "@ay", &parent_csum_v); (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)) goto out; } g_variant_get_child (commit, 6, "@ay", &content_csum_v); if (!ostree_validate_structureof_csum_v (content_csum_v, error)) goto out; g_variant_get_child (commit, 7, "@ay", &metadata_csum_v); if (!ostree_validate_structureof_csum_v (metadata_csum_v, error)) goto out; ret = TRUE; out: return ret; } gboolean ostree_validate_structureof_dirtree (GVariant *dirtree, GError **error) { gboolean ret = FALSE; const char *filename; ot_lvariant GVariant *content_csum_v = NULL; ot_lvariant GVariant *meta_csum_v = NULL; GVariantIter *contents_iter = NULL; if (!validate_variant (dirtree, OSTREE_TREE_GVARIANT_FORMAT, error)) goto out; 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)) goto out; if (!ostree_validate_structureof_csum_v (content_csum_v, error)) goto out; } 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)) goto out; if (!ostree_validate_structureof_csum_v (content_csum_v, error)) goto out; if (!ostree_validate_structureof_csum_v (meta_csum_v, error)) goto out; } content_csum_v = NULL; meta_csum_v = NULL; ret = TRUE; out: if (contents_iter) g_variant_iter_free (contents_iter); return ret; } static gboolean validate_stat_mode_perms (guint32 mode, GError **error) { gboolean ret = FALSE; guint32 otherbits = (~S_IFMT & ~S_IRWXU & ~S_IRWXG & ~S_IRWXO & ~S_ISUID & ~S_ISGID & ~S_ISVTX); if (mode & otherbits) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid mode %u; invalid bits in mode", mode); goto out; } ret = TRUE; out: return ret; } gboolean ostree_validate_structureof_file_mode (guint32 mode, GError **error) { gboolean ret = FALSE; if (!(S_ISREG (mode) || S_ISLNK (mode) || S_ISCHR (mode) || S_ISBLK (mode) || S_ISFIFO (mode))) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid file metadata mode %u; not a valid file type", mode); goto out; } if (!validate_stat_mode_perms (mode, error)) goto out; ret = TRUE; out: return ret; } gboolean ostree_validate_structureof_dirmeta (GVariant *dirmeta, GError **error) { gboolean ret = FALSE; guint32 mode; if (!validate_variant (dirmeta, OSTREE_DIRMETA_GVARIANT_FORMAT, error)) goto out; g_variant_get_child (dirmeta, 2, "u", &mode); mode = GUINT32_FROM_BE (mode); if (!S_ISDIR (mode)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid directory metadata mode %u; not a directory", mode); goto out; } if (!validate_stat_mode_perms (mode, error)) goto out; ret = TRUE; out: return ret; }