/* * Copyright (C) 2011 Colin Walters * * SPDX-License-Identifier: LGPL-2.0+ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * * Author: Colin Walters */ #include "config.h" #include "libglnx.h" #include "ostree.h" #include "ostree-repo-private.h" #include "otutil.h" /* See ostree-repo.c for a bit more info about these ABI checks */ #if __SIZEOF_POINTER__ == 8 && __SIZEOF_LONG__ == 8 && __SIZEOF_INT__ == 4 G_STATIC_ASSERT(sizeof(OstreeDiffDirsOptions) == sizeof(int) * 2 + sizeof(gpointer) + sizeof(int) * (7+6) + sizeof(int) + /* hole */ sizeof(gpointer) * 7); #endif static gboolean get_file_checksum (OstreeDiffFlags flags, GFile *f, GFileInfo *f_info, char **out_checksum, GCancellable *cancellable, GError **error) { g_autofree char *ret_checksum = NULL; if (OSTREE_IS_REPO_FILE (f)) { ret_checksum = g_strdup (ostree_repo_file_get_checksum ((OstreeRepoFile*)f)); } else { g_autoptr(GVariant) xattrs = NULL; g_autoptr(GInputStream) in = NULL; if (!(flags & OSTREE_DIFF_FLAGS_IGNORE_XATTRS)) { if (!glnx_dfd_name_get_all_xattrs (AT_FDCWD, gs_file_get_path_cached (f), &xattrs, cancellable, error)) return FALSE; } if (g_file_info_get_file_type (f_info) == G_FILE_TYPE_REGULAR) { in = (GInputStream*)g_file_read (f, cancellable, error); if (!in) return FALSE; } g_autofree guchar *csum = NULL; if (!ostree_checksum_file_from_input (f_info, xattrs, in, OSTREE_OBJECT_TYPE_FILE, &csum, cancellable, error)) return FALSE; ret_checksum = ostree_checksum_from_bytes (csum); } ot_transfer_out_value(out_checksum, &ret_checksum); return TRUE; } OstreeDiffItem * ostree_diff_item_ref (OstreeDiffItem *diffitem) { g_atomic_int_inc (&diffitem->refcount); return diffitem; } void ostree_diff_item_unref (OstreeDiffItem *diffitem) { if (!g_atomic_int_dec_and_test (&diffitem->refcount)) return; g_clear_object (&diffitem->src); g_clear_object (&diffitem->target); g_clear_object (&diffitem->src_info); g_clear_object (&diffitem->target_info); g_free (diffitem->src_checksum); g_free (diffitem->target_checksum); g_free (diffitem); } G_DEFINE_BOXED_TYPE(OstreeDiffItem, ostree_diff_item, ostree_diff_item_ref, ostree_diff_item_unref); static OstreeDiffItem * diff_item_new (GFile *a, GFileInfo *a_info, GFile *b, GFileInfo *b_info, char *checksum_a, char *checksum_b) { OstreeDiffItem *ret = g_new0 (OstreeDiffItem, 1); ret->refcount = 1; ret->src = a ? g_object_ref (a) : NULL; ret->src_info = a_info ? g_object_ref (a_info) : NULL; ret->target = b ? g_object_ref (b) : NULL; ret->target_info = b_info ? g_object_ref (b_info) : b_info; ret->src_checksum = g_strdup (checksum_a); ret->target_checksum = g_strdup (checksum_b); return ret; } static gboolean diff_files (OstreeDiffFlags flags, GFile *a, GFileInfo *a_info, GFile *b, GFileInfo *b_info, OstreeDiffItem **out_item, GCancellable *cancellable, GError **error) { g_autofree char *checksum_a = NULL; g_autofree char *checksum_b = NULL; if (!get_file_checksum (flags, a, a_info, &checksum_a, cancellable, error)) return FALSE; if (!get_file_checksum (flags, b, b_info, &checksum_b, cancellable, error)) return FALSE; g_autoptr(OstreeDiffItem) ret_item = NULL; if (strcmp (checksum_a, checksum_b) != 0) { ret_item = diff_item_new (a, a_info, b, b_info, checksum_a, checksum_b); } ot_transfer_out_value(out_item, &ret_item); return TRUE; } static gboolean diff_add_dir_recurse (GFile *d, GPtrArray *added, GCancellable *cancellable, GError **error) { g_autoptr(GFileEnumerator) dir_enum = g_file_enumerate_children (d, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!dir_enum) return FALSE; while (TRUE) { GFileInfo *child_info; const char *name; if (!g_file_enumerator_iterate (dir_enum, &child_info, NULL, cancellable, error)) return FALSE; if (child_info == NULL) break; name = g_file_info_get_name (child_info); g_autoptr(GFile) child = g_file_get_child (d, name); g_ptr_array_add (added, g_object_ref (child)); if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY) { if (!diff_add_dir_recurse (child, added, cancellable, error)) return FALSE; } } return TRUE; } /** * ostree_diff_dirs: * @flags: Flags * @a: First directory path, or %NULL * @b: First directory path * @modified: (element-type OstreeDiffItem): Modified files * @removed: (element-type Gio.File): Removed files * @added: (element-type Gio.File): Added files * @cancellable: Cancellable * @error: Error * * Compute the difference between directory @a and @b as 3 separate * sets of #OstreeDiffItem in @modified, @removed, and @added. */ gboolean ostree_diff_dirs (OstreeDiffFlags flags, GFile *a, GFile *b, GPtrArray *modified, GPtrArray *removed, GPtrArray *added, GCancellable *cancellable, GError **error) { return ostree_diff_dirs_with_options (flags, a, b, modified, removed, added, NULL, cancellable, error); } /** * ostree_diff_dirs_with_options: * @flags: Flags * @a: First directory path, or %NULL * @b: First directory path * @modified: (element-type OstreeDiffItem): Modified files * @removed: (element-type Gio.File): Removed files * @added: (element-type Gio.File): Added files * @cancellable: Cancellable * @options: (allow-none): Options * @error: Error * * Compute the difference between directory @a and @b as 3 separate * sets of #OstreeDiffItem in @modified, @removed, and @added. * * Since: 2017.4 */ gboolean ostree_diff_dirs_with_options (OstreeDiffFlags flags, GFile *a, GFile *b, GPtrArray *modified, GPtrArray *removed, GPtrArray *added, OstreeDiffDirsOptions *options, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; GError *temp_error = NULL; g_autoptr(GFileEnumerator) dir_enum = NULL; g_autoptr(GFile) child_a = NULL; g_autoptr(GFile) child_b = NULL; g_autoptr(GFileInfo) child_a_info = NULL; g_autoptr(GFileInfo) child_b_info = NULL; OstreeDiffDirsOptions default_opts = OSTREE_DIFF_DIRS_OPTIONS_INIT; if (!options) options = &default_opts; /* If we're diffing versus a repo, and either of them have xattrs disabled, * then disable for both. */ if (OSTREE_IS_REPO_FILE (a)) { OstreeRepo *repo = ostree_repo_file_get_repo ((OstreeRepoFile*)a); if (repo->disable_xattrs || repo->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) flags |= OSTREE_DIFF_FLAGS_IGNORE_XATTRS; } if (OSTREE_IS_REPO_FILE (b)) { OstreeRepo *repo = ostree_repo_file_get_repo ((OstreeRepoFile*)b); if (repo->disable_xattrs || repo->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) flags |= OSTREE_DIFF_FLAGS_IGNORE_XATTRS; } if (a == NULL) { if (!diff_add_dir_recurse (b, added, cancellable, error)) goto out; ret = TRUE; goto out; } child_a_info = g_file_query_info (a, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!child_a_info) goto out; child_b_info = g_file_query_info (b, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!child_b_info) goto out; /* Fast path test for unmodified directories */ if (g_file_info_get_file_type (child_a_info) == G_FILE_TYPE_DIRECTORY && g_file_info_get_file_type (child_b_info) == G_FILE_TYPE_DIRECTORY && OSTREE_IS_REPO_FILE (a) && OSTREE_IS_REPO_FILE (b)) { OstreeRepoFile *a_repof = (OstreeRepoFile*) a; OstreeRepoFile *b_repof = (OstreeRepoFile*) b; if (strcmp (ostree_repo_file_tree_get_contents_checksum (a_repof), ostree_repo_file_tree_get_contents_checksum (b_repof)) == 0) { ret = TRUE; goto out; } } g_clear_object (&child_a_info); g_clear_object (&child_b_info); dir_enum = g_file_enumerate_children (a, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!dir_enum) goto out; while ((child_a_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) { const char *name; GFileType child_a_type; GFileType child_b_type; name = g_file_info_get_name (child_a_info); g_clear_object (&child_a); child_a = g_file_get_child (a, name); child_a_type = g_file_info_get_file_type (child_a_info); g_clear_object (&child_b); child_b = g_file_get_child (b, name); g_clear_object (&child_b_info); child_b_info = g_file_query_info (child_b, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, &temp_error); if (!child_b_info) { if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_clear_error (&temp_error); g_ptr_array_add (removed, g_object_ref (child_a)); } else { g_propagate_error (error, temp_error); goto out; } } else { if (options->owner_uid >= 0) g_file_info_set_attribute_uint32 (child_b_info, "unix::uid", options->owner_uid); if (options->owner_gid >= 0) g_file_info_set_attribute_uint32 (child_b_info, "unix::gid", options->owner_gid); child_b_type = g_file_info_get_file_type (child_b_info); if (child_a_type != child_b_type) { OstreeDiffItem *diff_item = diff_item_new (child_a, child_a_info, child_b, child_b_info, NULL, NULL); g_ptr_array_add (modified, diff_item); } else { OstreeDiffItem *diff_item = NULL; if (!diff_files (flags, child_a, child_a_info, child_b, child_b_info, &diff_item, cancellable, error)) goto out; if (diff_item) g_ptr_array_add (modified, diff_item); /* Transfer ownership */ if (child_a_type == G_FILE_TYPE_DIRECTORY) { if (!ostree_diff_dirs_with_options (flags, child_a, child_b, modified, removed, added, options, cancellable, error)) goto out; } } } g_clear_object (&child_a_info); } if (temp_error != NULL) { g_propagate_error (error, temp_error); goto out; } g_clear_object (&dir_enum); dir_enum = g_file_enumerate_children (b, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); if (!dir_enum) goto out; g_clear_object (&child_b_info); while ((child_b_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) { const char *name; name = g_file_info_get_name (child_b_info); g_clear_object (&child_a); child_a = g_file_get_child (a, name); g_clear_object (&child_b); child_b = g_file_get_child (b, name); g_clear_object (&child_a_info); child_a_info = g_file_query_info (child_a, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, &temp_error); if (!child_a_info) { if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_clear_error (&temp_error); g_ptr_array_add (added, g_object_ref (child_b)); if (g_file_info_get_file_type (child_b_info) == G_FILE_TYPE_DIRECTORY) { if (!diff_add_dir_recurse (child_b, added, cancellable, error)) goto out; } } else { g_propagate_error (error, temp_error); goto out; } } g_clear_object (&child_b_info); } if (temp_error != NULL) { g_propagate_error (error, temp_error); goto out; } ret = TRUE; out: return ret; } static void print_diff_item (char prefix, GFile *base, GFile *file) { if (g_file_is_native (file)) { g_autofree char *relpath = g_file_get_relative_path (base, file); g_print ("%c %s\n", prefix, relpath); } else { g_print ("%c %s\n", prefix, gs_file_get_path_cached (file)); } } /** * ostree_diff_print: * @a: First directory path * @b: First directory path * @modified: (element-type OstreeDiffItem): Modified files * @removed: (element-type Gio.File): Removed files * @added: (element-type Gio.File): Added files * * Print the contents of a diff to stdout. */ void ostree_diff_print (GFile *a, GFile *b, GPtrArray *modified, GPtrArray *removed, GPtrArray *added) { guint i; for (i = 0; i < modified->len; i++) { OstreeDiffItem *diff = modified->pdata[i]; print_diff_item ('M', a, diff->src); } for (i = 0; i < removed->len; i++) { GFile *removed_file = removed->pdata[i]; print_diff_item ('D', a, removed_file); } for (i = 0; i < added->len; i++) { GFile *added_f = added->pdata[i]; print_diff_item ('A', b, added_f); } }