/* -*- 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 #define ALIGN_VALUE(this, boundary) \ (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1))) gboolean ostree_validate_checksum_string (const char *sha256, GError **error) { int i = 0; size_t len = strlen (sha256); if (len != 64) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid rev '%s'", sha256); return FALSE; } for (i = 0; i < len; i++) { guint8 c = ((guint8*) sha256)[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, sha256); return FALSE; } } return TRUE; } gboolean ostree_validate_rev (const char *rev, GError **error) { gboolean ret = FALSE; 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: g_ptr_array_unref (components); return ret; } GVariant * ostree_wrap_metadata_variant (OstreeObjectType type, GVariant *metadata) { return g_variant_new ("(uv)", GUINT32_TO_BE ((guint32)type), metadata); } void ostree_checksum_update_stat (GChecksum *checksum, guint32 uid, guint32 gid, guint32 mode) { guint32 perms; perms = GUINT32_TO_BE (mode & ~S_IFMT); uid = GUINT32_TO_BE (uid); gid = GUINT32_TO_BE (gid); g_checksum_update (checksum, (guint8*) &uid, 4); g_checksum_update (checksum, (guint8*) &gid, 4); g_checksum_update (checksum, (guint8*) &perms, 4); } 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; } GVariant * ostree_get_xattrs_for_file (GFile *f, GError **error) { const char *path; GVariant *ret = NULL; GVariantBuilder builder; char *xattr_names = NULL; char *xattr_names_canonical = NULL; ssize_t bytes_read; path = ot_gfile_get_path_cached (f); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)")); 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 = g_variant_builder_end (&builder); g_variant_ref_sink (ret); out: if (!ret) g_variant_builder_clear (&builder); g_free (xattr_names); g_free (xattr_names_canonical); return ret; } gboolean ostree_checksum_file_from_input (GFileInfo *file_info, GVariant *xattrs, GInputStream *in, OstreeObjectType objtype, GChecksum **out_checksum, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GChecksum *ret_checksum = NULL; GVariant *dirmeta = NULL; GVariant *packed = NULL; guint8 buf[8192]; gsize bytes_read; guint32 mode; if (OSTREE_OBJECT_TYPE_IS_META (objtype)) return ot_gio_checksum_stream (in, out_checksum, cancellable, error); ret_checksum = g_checksum_new (G_CHECKSUM_SHA256); mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) { dirmeta = ostree_create_directory_metadata (file_info, xattrs); packed = ostree_wrap_metadata_variant (OSTREE_OBJECT_TYPE_DIR_META, dirmeta); g_checksum_update (ret_checksum, g_variant_get_data (packed), g_variant_get_size (packed)); } else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) { while ((bytes_read = g_input_stream_read (in, buf, sizeof (buf), cancellable, error)) > 0) g_checksum_update (ret_checksum, buf, bytes_read); if (bytes_read < 0) goto out; } else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK) { const char *symlink_target = g_file_info_get_symlink_target (file_info); g_assert (symlink_target != NULL); g_checksum_update (ret_checksum, (guint8*)symlink_target, strlen (symlink_target)); } else if (S_ISCHR(mode) || S_ISBLK(mode)) { guint32 rdev = g_file_info_get_attribute_uint32 (file_info, "unix::rdev"); rdev = GUINT32_TO_BE (rdev); g_checksum_update (ret_checksum, (guint8*)&rdev, 4); } else if (S_ISFIFO(mode)) { ; } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unsupported file (must be regular, symbolic link, fifo, or character/block device)"); goto out; } if (objtype != OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT) { ostree_checksum_update_stat (ret_checksum, g_file_info_get_attribute_uint32 (file_info, "unix::uid"), g_file_info_get_attribute_uint32 (file_info, "unix::gid"), g_file_info_get_attribute_uint32 (file_info, "unix::mode")); /* FIXME - ensure empty xattrs are the same as NULL xattrs */ if (xattrs) g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs)); } ret = TRUE; ot_transfer_out_value (out_checksum, &ret_checksum); out: ot_clear_checksum (&ret_checksum); ot_clear_gvariant (&dirmeta); ot_clear_gvariant (&packed); return ret; } gboolean ostree_checksum_file (GFile *f, OstreeObjectType objtype, GChecksum **out_checksum, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GFileInfo *file_info = NULL; GChecksum *ret_checksum = NULL; GInputStream *in = NULL; GVariant *xattrs = 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_RAW_FILE) { xattrs = ostree_get_xattrs_for_file (f, error); if (!xattrs) goto out; } if (!ostree_checksum_file_from_input (file_info, xattrs, in, objtype, &ret_checksum, cancellable, error)) goto out; ret = TRUE; ot_transfer_out_value(out_checksum, &ret_checksum); out: g_clear_object (&file_info); g_clear_object (&in); ot_clear_checksum(&ret_checksum); ot_clear_gvariant(&xattrs); return ret; } typedef struct { GFile *f; OstreeObjectType objtype; GChecksum *checksum; } ChecksumFileAsyncData; static void checksum_file_async_thread (GSimpleAsyncResult *res, GObject *object, GCancellable *cancellable) { GError *error = NULL; ChecksumFileAsyncData *data; GChecksum *checksum = NULL; data = g_simple_async_result_get_op_res_gpointer (res); if (!ostree_checksum_file (data->f, data->objtype, &checksum, cancellable, &error)) g_simple_async_result_take_error (res, error); else data->checksum = checksum; } static void checksum_file_async_data_free (gpointer datap) { ChecksumFileAsyncData *data = datap; g_object_unref (data->f); ot_clear_checksum (&data->checksum); 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, GChecksum **out_checksum, 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_checksum = data->checksum; data->checksum = NULL; return TRUE; } GVariant * ostree_create_directory_metadata (GFileInfo *dir_info, GVariant *xattrs) { GVariant *ret_metadata = NULL; ret_metadata = g_variant_new ("(uuuu@a(ayay))", OSTREE_DIR_META_VERSION, 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; ot_clear_gvariant (&value); if (loop_err) { ot_util_set_error_from_errno (error, errno); goto out; } } ret = TRUE; out: return ret; } gboolean ostree_unwrap_metadata (GVariant *container, OstreeObjectType expected_type, GVariant **out_variant, GError **error) { gboolean ret = FALSE; GVariant *ret_variant = NULL; guint32 actual_type; g_variant_get (container, "(uv)", &actual_type, &ret_variant); actual_type = GUINT32_FROM_BE (actual_type); if (actual_type != expected_type) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Corrupted metadata object; found type %u, expected %u", actual_type, (guint32)expected_type); goto out; } ret = TRUE; ot_transfer_out_value (out_variant, &ret_variant); out: ot_clear_gvariant (&ret_variant); return ret; } gboolean ostree_map_metadata_file (GFile *file, OstreeObjectType expected_type, GVariant **out_variant, GError **error) { gboolean ret = FALSE; GVariant *ret_variant = NULL; GVariant *container = NULL; if (!ot_util_variant_map (file, OSTREE_SERIALIZED_VARIANT_FORMAT, &container, error)) goto out; if (!ostree_unwrap_metadata (container, expected_type, &ret_variant, error)) { g_prefix_error (error, "While parsing '%s': ", ot_gfile_get_path_cached (file)); goto out; } ret = TRUE; ot_transfer_out_value(out_variant, &ret_variant); out: ot_clear_gvariant (&ret_variant); ot_clear_gvariant (&container); return ret; } const char * ostree_object_type_to_string (OstreeObjectType objtype) { switch (objtype) { case OSTREE_OBJECT_TYPE_RAW_FILE: return "file"; case OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT: return "archive-content"; case OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META: return "archive-meta"; 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_RAW_FILE; else if (!strcmp (str, "archive-content")) return OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT; else if (!strcmp (str, "archive-meta")) return OSTREE_OBJECT_TYPE_ARCHIVED_FILE_META; 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 (GVariant *a, GVariant *b) { gconstpointer a_data; gconstpointer b_data; gsize a_n_elts; gsize b_n_elts; a_data = g_variant_get_fixed_array (a, &a_n_elts, 1); g_assert (a_n_elts == 32); b_data = g_variant_get_fixed_array (b, &b_n_elts, 1); g_assert (b_n_elts == 32); return memcmp (a_data, b_data, 32); } GVariant * ostree_object_name_serialize (const char *checksum, OstreeObjectType objtype) { 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; } GVariant * ostree_checksum_to_bytes (const char *sha256) { guchar result[32]; guint i; guint j; for (i = 0, j = 0; i < 32; i += 1, j += 2) { gint big, little; g_assert (sha256[j]); g_assert (sha256[j+1]); big = g_ascii_xdigit_value (sha256[j]); little = g_ascii_xdigit_value (sha256[j+1]); g_assert (big != -1); g_assert (little != -1); result[i] = (big << 4) | little; } return ot_gvariant_new_bytearray ((guchar*)result, 32); } char * ostree_checksum_from_bytes (GVariant *csum_bytes) { static const gchar hexchars[] = "0123456789abcdef"; char *ret; const guchar *bytes; gsize n_elts; guint i, j; bytes = g_variant_get_fixed_array (csum_bytes, &n_elts, 1); g_assert (n_elts == 32); ret = g_malloc (65); for (i = 0, j = 0; i < 32; i++, j += 2) { guchar byte = bytes[i]; ret[j] = hexchars[byte >> 4]; ret[j+1] = hexchars[byte & 0xF]; } ret[j] = '\0'; return ret; } GVariant * ostree_object_name_serialize_v2 (const char *checksum, OstreeObjectType objtype) { return g_variant_new ("(u@ay)", (guint32)objtype, ostree_checksum_to_bytes (checksum)); } void ostree_object_name_deserialize_v2_hex (GVariant *variant, char **out_checksum, OstreeObjectType *out_objtype) { GVariant *csum_bytes; guint32 objtype_u32; g_variant_get (variant, "(u@ay)", &objtype_u32, &csum_bytes); g_variant_ref_sink (csum_bytes); *out_checksum = ostree_checksum_from_bytes (csum_bytes); g_variant_unref (csum_bytes); *out_objtype = (OstreeObjectType)objtype_u32; } void ostree_object_name_deserialize_v2_bytes (GVariant *variant, const guchar **out_checksum, OstreeObjectType *out_objtype) { GVariant *csum_bytes; guint32 objtype_u32; gsize n_elts; g_variant_get (variant, "(u@ay)", &objtype_u32, &csum_bytes); *out_checksum = (guchar*)g_variant_get_fixed_array (csum_bytes, &n_elts, 1); *out_objtype = (OstreeObjectType)objtype_u32; } char * ostree_get_relative_object_path (const char *checksum, OstreeObjectType type) { 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)); return g_string_free (path, FALSE); } GVariant * ostree_create_archive_file_metadata (GFileInfo *finfo, GVariant *xattrs) { guint32 uid, gid, mode, rdev; GVariantBuilder pack_builder; GVariant *ret = NULL; uid = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_UID); gid = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_GID); mode = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_MODE); rdev = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_RDEV); g_variant_builder_init (&pack_builder, OSTREE_ARCHIVED_FILE_VARIANT_FORMAT); g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (0)); /* Version */ g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (uid)); g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (gid)); g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (mode)); g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (rdev)); if (S_ISLNK (mode)) g_variant_builder_add (&pack_builder, "s", g_file_info_get_symlink_target (finfo)); else g_variant_builder_add (&pack_builder, "s", ""); g_variant_builder_add (&pack_builder, "@a(ayay)", xattrs ? xattrs : g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0)); ret = g_variant_builder_end (&pack_builder); g_variant_ref_sink (ret); return ret; } gboolean ostree_parse_archived_file_meta (GVariant *metadata, GFileInfo **out_file_info, GVariant **out_xattrs, GError **error) { gboolean ret = FALSE; GFileInfo *ret_file_info = NULL; GVariant *ret_xattrs = NULL; guint32 version, uid, gid, mode, rdev; const char *symlink_target; g_variant_get (metadata, "(uuuuu&s@a(ayay))", &version, &uid, &gid, &mode, &rdev, &symlink_target, &ret_xattrs); version = GUINT32_FROM_BE (version); if (version != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid version %d in archived file metadata", version); goto out; } 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: g_clear_object (&ret_file_info); ot_clear_gvariant (&ret_xattrs); return ret; } gboolean ostree_create_file_from_input (GFile *dest_file, GFileInfo *finfo, GVariant *xattrs, GInputStream *input, OstreeObjectType objtype, GChecksum **out_checksum, GCancellable *cancellable, GError **error) { const char *dest_path; gboolean ret = FALSE; GFileOutputStream *out = NULL; guint32 uid, gid, mode; GChecksum *ret_checksum = NULL; gboolean is_meta; gboolean is_archived_content; is_meta = OSTREE_OBJECT_TYPE_IS_META (objtype); is_archived_content = objtype == OSTREE_OBJECT_TYPE_ARCHIVED_FILE_CONTENT; if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; if (finfo != NULL) { mode = g_file_info_get_attribute_uint32 (finfo, "unix::mode"); /* Archived content files should always be readable by all and * read/write by owner. If the base file is executable then * we're also executable. */ if (is_archived_content) mode |= 0644; } 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 (!ot_gio_splice_and_checksum ((GOutputStream*)out, input, out_checksum ? &ret_checksum : NULL, cancellable, error)) 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"); g_assert (!is_meta); if (out_checksum) ret_checksum = g_checksum_new (G_CHECKSUM_SHA256); if (ret_checksum) g_checksum_update (ret_checksum, (guint8*)target, strlen (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"); guint32 dev_be; g_assert (!is_meta); dev_be = GUINT32_TO_BE (dev); if (out_checksum) ret_checksum = g_checksum_new (G_CHECKSUM_SHA256); if (ret_checksum) g_checksum_update (ret_checksum, (guint8*)&dev_be, 4); if (mknod (dest_path, mode, dev) < 0) { ot_util_set_error_from_errno (error, errno); goto out; } } else if (S_ISFIFO (mode)) { g_assert (!is_meta); if (out_checksum) ret_checksum = g_checksum_new (G_CHECKSUM_SHA256); 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 && !is_meta && !is_archived_content) { 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); goto out; } } if (!S_ISLNK (mode)) { if (chmod (dest_path, mode) < 0) { ot_util_set_error_from_errno (error, errno); goto out; } } if (xattrs != NULL) { g_assert (!is_meta); if (!ostree_set_xattrs (dest_file, xattrs, cancellable, error)) goto out; } if (ret_checksum && !is_meta && !is_archived_content) { g_assert (finfo != NULL); ostree_checksum_update_stat (ret_checksum, g_file_info_get_attribute_uint32 (finfo, "unix::uid"), g_file_info_get_attribute_uint32 (finfo, "unix::gid"), mode); if (xattrs) g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs)); } ret = TRUE; ot_transfer_out_value(out_checksum, &ret_checksum); out: if (!ret && !S_ISDIR(mode)) { (void) unlink (dest_path); } ot_clear_checksum (&ret_checksum); g_clear_object (&out); 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, OstreeObjectType objtype, GFile **out_file, GChecksum **out_checksum, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GChecksum *ret_checksum = NULL; GString *tmp_name = NULL; char *possible_name = NULL; GFile *possible_file = NULL; GError *temp_error = NULL; int i = 0; 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, objtype, out_checksum ? &ret_checksum : NULL, 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_checksum, &ret_checksum); ot_transfer_out_value(out_file, &possible_file); out: if (tmp_name) g_string_free (tmp_name, TRUE); g_free (possible_name); g_clear_object (&possible_file); ot_clear_checksum (&ret_checksum); 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; GFile *ret_file = NULL; GOutputStream *ret_stream = NULL; if (!ostree_create_temp_file_from_input (dir, prefix, suffix, NULL, NULL, NULL, OSTREE_OBJECT_TYPE_RAW_FILE, &ret_file, NULL, 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: g_clear_object (&ret_file); g_clear_object (&ret_stream); return ret; } gboolean ostree_create_temp_hardlink (GFile *dir, GFile *src, const char *prefix, const char *suffix, GFile **out_file, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GString *tmp_name = NULL; char *possible_name = NULL; GFile *possible_file = NULL; int i = 0; 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 (link (ot_gfile_get_path_cached (src), ot_gfile_get_path_cached (possible_file)) < 0) { if (errno == EEXIST) continue; else { ot_util_set_error_from_errno (error, errno); 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); g_free (possible_name); g_clear_object (&possible_file); return ret; } gboolean ostree_read_pack_entry_raw (guchar *pack_data, guint64 pack_len, guint64 offset, gboolean trusted, GVariant **out_entry, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GVariant *ret_entry = NULL; guint64 entry_start; guint64 entry_end; guint32 entry_len; if (G_UNLIKELY (!(offset <= pack_len))) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Corrupted pack index; out of range offset %" G_GUINT64_FORMAT, offset); goto out; } if (G_UNLIKELY (!((offset & 0x3) == 0))) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Corrupted pack index; unaligned offset %" G_GUINT64_FORMAT, offset); goto out; } entry_start = ALIGN_VALUE (offset + 4, 8); if (G_UNLIKELY (!(entry_start <= pack_len))) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Corrupted pack index; out of range data offset %" G_GUINT64_FORMAT, entry_start); goto out; } g_assert ((((guint64)pack_data+offset) & 0x3) == 0); entry_len = GUINT32_FROM_BE (*((guint32*)(pack_data+offset))); entry_end = entry_start + entry_len; if (G_UNLIKELY (!(entry_end <= pack_len))) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Corrupted pack index; out of range entry length %u", entry_len); goto out; } ret_entry = g_variant_new_from_data (OSTREE_PACK_FILE_CONTENT_VARIANT_FORMAT, pack_data+entry_start, entry_len, trusted, NULL, NULL); g_variant_ref_sink (ret_entry); ret = TRUE; ot_transfer_out_value (out_entry, &ret_entry); out: ot_clear_gvariant (&ret_entry); return ret; } GInputStream * ostree_read_pack_entry_as_stream (GVariant *pack_entry) { GInputStream *memory_input; GInputStream *ret_input = NULL; GVariant *pack_data = NULL; guchar entry_flags; gconstpointer data_ptr; gsize data_len; g_variant_get_child (pack_entry, 1, "y", &entry_flags); g_variant_get_child (pack_entry, 3, "@ay", &pack_data); data_ptr = g_variant_get_fixed_array (pack_data, &data_len, 1); memory_input = g_memory_input_stream_new_from_data (data_ptr, data_len, NULL); g_object_set_data_full ((GObject*)memory_input, "ostree-mem-gvariant", pack_data, (GDestroyNotify) g_variant_unref); if (entry_flags & OSTREE_PACK_FILE_ENTRY_FLAG_GZIP) { GConverter *decompressor; decompressor = (GConverter*)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP); ret_input = (GInputStream*)g_object_new (G_TYPE_CONVERTER_INPUT_STREAM, "converter", decompressor, "base-stream", memory_input, "close-base-stream", TRUE, NULL); g_object_unref (memory_input); g_object_unref (decompressor); } else { ret_input = memory_input; memory_input = NULL; } return ret_input; } gboolean ostree_read_pack_entry_variant (GVariant *pack_entry, OstreeObjectType expected_objtype, gboolean trusted, GVariant **out_variant, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GInputStream *stream = NULL; GVariant *container_variant = NULL; GVariant *ret_variant = NULL; guint32 actual_type; stream = ostree_read_pack_entry_as_stream (pack_entry); if (!ot_util_variant_from_stream (stream, OSTREE_SERIALIZED_VARIANT_FORMAT, trusted, &container_variant, cancellable, error)) goto out; g_variant_get (container_variant, "(uv)", &actual_type, &ret_variant); actual_type = GUINT32_FROM_BE (actual_type); if (actual_type != expected_objtype) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Corrupted metadata object in pack file; found type %u, expected %u", actual_type, (guint32)expected_objtype); goto out; } ret = TRUE; ot_transfer_out_value (out_variant, &ret_variant); out: g_clear_object (&stream); ot_clear_gvariant (&ret_variant); ot_clear_gvariant (&container_variant); return ret; } gboolean ostree_pack_index_search (GVariant *index, GVariant *csum_bytes, OstreeObjectType objtype, guint64 *out_offset) { gboolean ret = FALSE; GVariant *index_contents; gsize imax, imin; gsize n; guint32 target_objtype; index_contents = g_variant_get_child_value (index, 2); target_objtype = (guint32) objtype; n = g_variant_n_children (index_contents); if (n == 0) goto out; imax = n - 1; imin = 0; while (imax >= imin) { GVariant *cur_csum_bytes; guint32 cur_objtype; guint64 cur_offset; gsize imid; int c; imid = (imin + imax) / 2; g_variant_get_child (index_contents, imid, "(u@ayt)", &cur_objtype, &cur_csum_bytes, &cur_offset); cur_objtype = GUINT32_FROM_BE (cur_objtype); c = ostree_cmp_checksum_bytes (cur_csum_bytes, csum_bytes); if (c == 0) { if (cur_objtype < target_objtype) c = -1; else if (cur_objtype > target_objtype) c = 1; } g_variant_unref (cur_csum_bytes); if (c < 0) imin = imid + 1; else if (c > 0) { if (imid == 0) goto out; imax = imid - 1; } else { if (out_offset) *out_offset = GUINT64_FROM_BE (cur_offset); ret = TRUE; goto out; } } out: ot_clear_gvariant (&index_contents); return ret; } gboolean ostree_validate_structureof_objtype (guint32 objtype, GError **error) { objtype = GUINT32_FROM_BE (objtype); if (objtype < OSTREE_OBJECT_TYPE_RAW_FILE || objtype > 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_checksum (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; } 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_pack_index (GVariant *index, GError **error) { gboolean ret = FALSE; const char *header; GVariantIter *content_iter = NULL; guint32 objtype; GVariant *csum_bytes = NULL; guint64 offset; if (!validate_variant (index, OSTREE_PACK_INDEX_VARIANT_FORMAT, error)) goto out; g_variant_get_child (index, 0, "&s", &header); if (strcmp (header, "OSTv0PACKINDEX") != 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid pack index; doesn't match header"); goto out; } g_variant_get_child (index, 2, "a(uayt)", &content_iter); while (g_variant_iter_loop (content_iter, "(u@ayt)", &objtype, &csum_bytes, &offset)) { if (!ostree_validate_structureof_objtype (objtype, error)) goto out; if (!ostree_validate_structureof_checksum (csum_bytes, error)) goto out; } csum_bytes = NULL; ret = TRUE; out: if (content_iter) g_variant_iter_free (content_iter); ot_clear_gvariant (&csum_bytes); return ret; } gboolean ostree_validate_structureof_pack_superindex (GVariant *superindex, GError **error) { gboolean ret = FALSE; const char *header; GVariant *csum_bytes = NULL; GVariant *bloom = NULL; GVariantIter *content_iter = NULL; if (!validate_variant (superindex, OSTREE_PACK_SUPER_INDEX_VARIANT_FORMAT, error)) goto out; g_variant_get_child (superindex, 0, "&s", &header); if (strcmp (header, "OSTv0SUPERPACKINDEX") != 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid pack superindex; doesn't match header"); goto out; } g_variant_get_child (superindex, 2, "a(ayay)", &content_iter); while (g_variant_iter_loop (content_iter, "(@ay@ay)", &csum_bytes, &bloom)) { if (!ostree_validate_structureof_checksum (csum_bytes, error)) goto out; } csum_bytes = NULL; ret = TRUE; out: if (content_iter) g_variant_iter_free (content_iter); ot_clear_gvariant (&csum_bytes); ot_clear_gvariant (&bloom); return ret; }