/* -*- 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 */ #include "config.h" #include "ostree.h" #include "otutil.h" #include #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); } #define OSTREE_REF_FRAGMENT_REGEXP "[-._\\w\\d]+" #define OSTREE_REF_REGEXP "(?:" OSTREE_REF_FRAGMENT_REGEXP "/)*" OSTREE_REF_FRAGMENT_REGEXP gboolean ostree_parse_refspec (const char *refspec, char **out_remote, char **out_ref, GError **error) { gboolean ret = FALSE; GMatchInfo *match = NULL; char *remote; static gsize regex_initialized; static GRegex *regex; if (g_once_init_enter (®ex_initialized)) { regex = g_regex_new ("^(" OSTREE_REF_FRAGMENT_REGEXP ":)?(" OSTREE_REF_REGEXP ")$", 0, 0, NULL); g_assert (regex); g_once_init_leave (®ex_initialized, 1); } if (!g_regex_match (regex, refspec, 0, &match)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid refspec %s", refspec); goto out; } remote = g_match_info_fetch (match, 1); if (*remote == '\0') { g_clear_pointer (&remote, g_free); } else { /* Trim the : */ remote[strlen(remote)-1] = '\0'; } ret = TRUE; *out_remote = remote; *out_ref = g_match_info_fetch (match, 2); out: if (match) g_match_info_unref (match); return ret; } gboolean ostree_validate_rev (const char *rev, GError **error) { gboolean ret = FALSE; gs_unref_ptrarray GPtrArray *components = NULL; 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)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid ref name %s", rev); goto out; } ret = TRUE; out: if (match) g_match_info_unref (match); 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; gs_unref_variant GVariant *ret_xattrs = NULL; gs_free char *xattr_names = NULL; gs_free char *xattr_names_canonical = NULL; GVariantBuilder builder; gboolean builder_initialized = FALSE; path = gs_file_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; gs_unref_variant 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; } GVariant * ostree_zlib_file_header_new (GFileInfo *file_info, GVariant *xattrs) { guint64 size; guint32 uid; guint32 gid; guint32 mode; guint32 rdev; const char *symlink_target; GVariant *ret; gs_unref_variant GVariant *tmp_xattrs = NULL; size = g_file_info_get_size (file_info); 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 ("(tuuuus@a(ayay))", GUINT64_TO_BE (size), 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; gs_unref_object GInputStream *ret_input = NULL; gs_unref_variant GVariant *file_header = NULL; gs_unref_ptrarray GPtrArray *streams = NULL; gs_unref_object GOutputStream *header_out_stream = NULL; gs_unref_object 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 (gboolean compressed, 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; gs_unref_object GInputStream *ret_input = NULL; gs_unref_object GFileInfo *content_file_info = NULL; gs_unref_object GFileInfo *ret_file_info = NULL; gs_unref_variant GVariant *ret_xattrs = NULL; gs_unref_variant GVariant *file_header = NULL; gs_free 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 (compressed ? OSTREE_ZLIB_FILE_HEADER_GVARIANT_FORMAT : OSTREE_FILE_HEADER_GVARIANT_FORMAT, buf, archive_header_size, trusted, g_free, buf); buf = NULL; if (compressed) { if (!ostree_zlib_file_header_parse (file_header, out_file_info ? &ret_file_info : NULL, out_xattrs ? &ret_xattrs : NULL, error)) goto out; } else { 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. **/ if (compressed) { gs_unref_object 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); } 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_content_file_parse (gboolean compressed, GFile *content_path, gboolean trusted, GInputStream **out_input, GFileInfo **out_file_info, GVariant **out_xattrs, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; guint64 length; struct stat stbuf; gs_unref_object GInputStream *file_input = NULL; gs_unref_object GInputStream *ret_input = NULL; gs_unref_object GFileInfo *ret_file_info = NULL; gs_unref_variant GVariant *ret_xattrs = NULL; if (out_input) { file_input = (GInputStream*)gs_file_read_noatime (content_path, cancellable, error); if (!file_input) goto out; if (fstat (g_file_descriptor_based_get_fd ((GFileDescriptorBased*)file_input), &stbuf) < 0) { ot_util_set_error_from_errno (error, errno); goto out; } length = stbuf.st_size; } else { GMappedFile *mmaped; GBytes *bytes; mmaped = gs_file_map_noatime (content_path, cancellable, error); if (!mmaped) goto out; bytes = g_mapped_file_get_bytes (mmaped); g_mapped_file_unref (mmaped); mmaped = NULL; file_input = g_memory_input_stream_new_from_bytes (bytes); length = g_bytes_get_size (bytes); g_bytes_unref (bytes); } if (!ostree_content_stream_parse (compressed, 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; gs_free 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) { gs_unref_variant GVariant *dirmeta = ostree_create_directory_metadata (file_info, xattrs); g_checksum_update (checksum, g_variant_get_data (dirmeta), g_variant_get_size (dirmeta)); } else { gs_unref_variant 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; gs_unref_object GFileInfo *file_info = NULL; gs_unref_object GInputStream *in = NULL; gs_unref_variant GVariant *xattrs = NULL; gs_free 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 = gs_file_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); } void ostree_checksum_inplace_from_bytes (const guchar *csum, char *buf) { static const gchar hexchars[] = "0123456789abcdef"; guint i, j; for (i = 0, j = 0; i < 32; i++, j += 2) { guchar byte = csum[i]; buf[j] = hexchars[byte >> 4]; buf[j+1] = hexchars[byte & 0xF]; } buf[j] = '\0'; } char * ostree_checksum_from_bytes (const guchar *csum) { char *ret = g_malloc (65); ostree_checksum_inplace_from_bytes (csum, ret); 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; gs_unref_object GFileInfo *ret_file_info = NULL; gs_unref_variant 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_zlib_file_header_parse (GVariant *metadata, GFileInfo **out_file_info, GVariant **out_xattrs, GError **error) { gboolean ret = FALSE; guint64 size; guint32 uid, gid, mode, rdev; const char *symlink_target; gs_unref_object GFileInfo *ret_file_info = NULL; gs_unref_variant GVariant *ret_xattrs = NULL; g_variant_get (metadata, "(tuuuu&s@a(ayay))", &size, &uid, &gid, &mode, &rdev, &symlink_target, &ret_xattrs); size = GUINT64_FROM_BE (size); 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_size (ret_file_info, size); 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; gs_unref_object GOutputStream *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 = gs_file_get_path_cached (dest_file); if (S_ISDIR (mode)) { if (mkdir (gs_file_get_path_cached (dest_file), mode) < 0) { ot_util_set_error_from_errno (error, errno); goto out; } } else if (S_ISREG (mode)) { if (!gs_file_create (dest_file, mode, &out, cancellable, error)) 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 (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 const char * get_default_tmp_prefix (void) { static char *tmpprefix = NULL; if (g_once_init_enter (&tmpprefix)) { const char *prgname = g_get_prgname (); const char *p; char *prefix; p = strrchr (prgname, '/'); if (p) prgname = p + 1; prefix = g_strdup_printf ("tmp-%s%u-", prgname, getuid ()); g_once_init_leave (&tmpprefix, prefix); } return tmpprefix; } static char * create_tmp_name (const char *dirpath, const char *prefix, const char *suffix) { static const char table[] = "ABCEDEFGHIJKLMNOPQRSTUVWXYZabcedefghijklmnopqrstuvwxyz0123456789"; GString *tmp_name = NULL; guint i; if (!prefix) prefix = get_default_tmp_prefix (); if (!suffix) suffix = "tmp"; tmp_name = g_string_new (dirpath); g_string_append_c (tmp_name, '/'); g_string_append (tmp_name, prefix); for (i = 0; i < 8; i++) { int offset = g_random_int_range (0, sizeof (table) - 1); g_string_append_c (tmp_name, (guint8)table[offset]); } g_string_append_c (tmp_name, '.'); g_string_append (tmp_name, suffix); return g_string_free (tmp_name, FALSE); } 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; gs_unref_object GFile *possible_file = NULL; gs_free guchar *ret_csum = NULL; /* 128 attempts seems reasonable... */ for (i = 0; i < 128; i++) { gs_free char *possible_name = NULL; if (g_cancellable_set_error_if_cancelled (cancellable, error)) goto out; possible_name = create_tmp_name (gs_file_get_path_cached (dir), prefix, suffix); 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: 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; gs_unref_object GFile *ret_file = NULL; gs_unref_object GOutputStream *ret_stream = NULL; if (!ostree_create_temp_file_from_input (dir, prefix, suffix, NULL, NULL, NULL, &ret_file, cancellable, error)) goto out; if (out_stream) { 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; gs_free char *template = NULL; gs_unref_object GFile *ret_file = NULL; if (dir == NULL) dir = g_file_new_for_path (g_get_tmp_dir ()); template = g_strdup_printf ("%s/%s-XXXXXX", gs_file_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; gs_unref_variant GVariant *parent_csum_v = NULL; gs_unref_variant GVariant *content_csum_v = NULL; gs_unref_variant 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; gs_unref_variant GVariant *content_csum_v = NULL; gs_unref_variant 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; }