diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index e5ce157c..1ba02cfc 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -135,6 +135,7 @@ ostree_raw_file_to_archive_z2_stream_with_options ostree_raw_file_to_content_stream ostree_checksum_file_from_input ostree_checksum_file +ostree_checksum_file_at ostree_checksum_file_async ostree_checksum_file_async_finish ostree_create_directory_metadata diff --git a/bash/ostree b/bash/ostree index 034f101b..c132a43f 100644 --- a/bash/ostree +++ b/bash/ostree @@ -745,6 +745,7 @@ _ostree_checkout() { _ostree_checksum() { local boolean_options=" $main_boolean_options + --ignore-xattrs " case "$cur" in diff --git a/man/ostree-checksum.xml b/man/ostree-checksum.xml index 53914782..c6e16a8b 100644 --- a/man/ostree-checksum.xml +++ b/man/ostree-checksum.xml @@ -61,6 +61,19 @@ Boston, MA 02111-1307, USA. + + Options + + + + + + Ignore extended attributes when checksumming. + + + + + Example $ ostree checksum file1 diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 896d2b75..07d99f15 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -19,6 +19,8 @@ /* Add new symbols here. Release commits should copy this section into -released.sym. */ LIBOSTREE_2017.13 { +global: + ostree_checksum_file_at; } LIBOSTREE_2017.12; /* Stub section for the stable release *after* this development one; don't @@ -27,6 +29,6 @@ LIBOSTREE_2017.13 { * with whatever the next version with new symbols will be. LIBOSTREE_2017.$NEWVERSION { global: - someostree_symbol_deleteme; + someostree_symbol_deleteme; } LIBOSTREE_2017.$LASTSTABLE; */ diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index 733901f7..615f5dec 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -832,6 +832,81 @@ ostree_checksum_file (GFile *f, return TRUE; } +/** + * ostree_checksum_file_at: + * @dfd: Directory file descriptor + * @path: Subpath + * @stbuf (allow-none): Optional stat buffer + * @objtype: Object type + * @flags: Flags + * @out_checksum (out) (transfer full): Return location for hex checksum + * @cancellable: Cancellable + * @error: Error + * + * Compute the OSTree checksum for a given file. This is an fd-relative version + * of ostree_checksum_file() which also takes flags and fills in a caller + * allocated buffer. + * + * Since: 2017.13 + */ +gboolean +ostree_checksum_file_at (int dfd, + const char *path, + struct stat *stbuf, + OstreeObjectType objtype, + OstreeChecksumFlags flags, + char **out_checksum, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (out_checksum != NULL, FALSE); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + struct stat local_stbuf; + if (stbuf == NULL) + { + stbuf = &local_stbuf; + if (!glnx_fstatat (dfd, path, stbuf, AT_SYMLINK_NOFOLLOW, error)) + return FALSE; + } + + g_autoptr(GFileInfo) file_info = _ostree_stbuf_to_gfileinfo (stbuf); + + g_autoptr(GInputStream) in = NULL; + if (S_ISREG (stbuf->st_mode)) + { + glnx_autofd int fd = -1; + if (!glnx_openat_rdonly (dfd, path, FALSE, &fd, error)) + return FALSE; + in = g_unix_input_stream_new (glnx_steal_fd (&fd), TRUE); + } + else if (S_ISLNK (stbuf->st_mode)) + { + if (!ot_readlinkat_gfile_info (dfd, path, file_info, cancellable, error)) + return FALSE; + } + + const gboolean ignore_xattrs = + ((flags & OSTREE_CHECKSUM_FLAGS_IGNORE_XATTRS) > 0); + + g_autoptr(GVariant) xattrs = NULL; + if (!ignore_xattrs && objtype == OSTREE_OBJECT_TYPE_FILE) + { + if (!glnx_dfd_name_get_all_xattrs (dfd, path, &xattrs, cancellable, error)) + return FALSE; + } + + g_autofree guchar *csum_bytes = NULL; + if (!ostree_checksum_file_from_input (file_info, xattrs, in, objtype, + &csum_bytes, cancellable, error)) + return FALSE; + + *out_checksum = ostree_checksum_from_bytes (csum_bytes); + return TRUE; +} + typedef struct { GFile *f; OstreeObjectType objtype; diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h index aae86d54..979b35a2 100644 --- a/src/libostree/ostree-core.h +++ b/src/libostree/ostree-core.h @@ -21,6 +21,7 @@ #pragma once +#include #include #include @@ -420,6 +421,26 @@ gboolean ostree_checksum_file (GFile *f, GCancellable *cancellable, GError **error); +/** + * OstreeChecksumFlags: + * + * Since: 2017.13 + */ +typedef enum { + OSTREE_CHECKSUM_FLAGS_NONE = 0, + OSTREE_CHECKSUM_FLAGS_IGNORE_XATTRS = (1 << 0), +} OstreeChecksumFlags; + +_OSTREE_PUBLIC +gboolean ostree_checksum_file_at (int dfd, + const char *path, + struct stat *stbuf, + OstreeObjectType objtype, + OstreeChecksumFlags flags, + char **out_checksum, + GCancellable *cancellable, + GError **error); + _OSTREE_PUBLIC void ostree_checksum_file_async (GFile *f, OstreeObjectType objtype, diff --git a/src/ostree/ot-builtin-checksum.c b/src/ostree/ot-builtin-checksum.c index 0bc98d3d..008c4fe7 100644 --- a/src/ostree/ot-builtin-checksum.c +++ b/src/ostree/ot-builtin-checksum.c @@ -32,7 +32,10 @@ * man page (man/ostree-checksum.xml) when changing the option list. */ +static gboolean opt_ignore_xattrs; + static GOptionEntry options[] = { + { "ignore-xattrs", 0, 0, G_OPTION_ARG_NONE, &opt_ignore_xattrs, "Don't include xattrs in checksum", NULL }, { NULL } }; @@ -49,12 +52,14 @@ on_checksum_received (GObject *obj, { AsyncChecksumData *data = user_data; - g_autofree guchar *csum = NULL; - data->success = ostree_checksum_file_async_finish ((GFile*)obj, result, &csum, data->error); + g_autofree guchar *csum_bytes = NULL; + data->success = + ostree_checksum_file_async_finish ((GFile*)obj, result, &csum_bytes, data->error); if (data->success) { - g_autofree char *checksum = ostree_checksum_from_bytes (csum); - g_print ("%s\n", checksum); + char csum[OSTREE_SHA256_STRING_LEN+1]; + ostree_checksum_inplace_from_bytes (csum_bytes, csum); + g_print ("%s\n", csum); } g_main_loop_quit (data->loop); @@ -73,15 +78,28 @@ ostree_builtin_checksum (int argc, char **argv, GCancellable *cancellable, GErro return glnx_throw (error, "A filename must be given"); const char *path = argv[1]; - g_autoptr(GFile) f = g_file_new_for_path (path); - g_autoptr(GMainLoop) loop = g_main_loop_new (NULL, FALSE); + /* for test coverage, use the async API if no flags are needed */ + if (!opt_ignore_xattrs) + { + g_autoptr(GFile) f = g_file_new_for_path (path); + g_autoptr(GMainLoop) loop = g_main_loop_new (NULL, FALSE); - AsyncChecksumData data = { 0, }; + AsyncChecksumData data = { 0, }; - data.loop = loop; - data.error = error; - ostree_checksum_file_async (f, OSTREE_OBJECT_TYPE_FILE, G_PRIORITY_DEFAULT, cancellable, - on_checksum_received, &data); - g_main_loop_run (data.loop); - return data.success; + data.loop = loop; + data.error = error; + ostree_checksum_file_async (f, OSTREE_OBJECT_TYPE_FILE, G_PRIORITY_DEFAULT, + cancellable, on_checksum_received, &data); + g_main_loop_run (data.loop); + return data.success; + } + + g_autofree char *checksum = NULL; + if (!ostree_checksum_file_at (AT_FDCWD, path, NULL, OSTREE_OBJECT_TYPE_FILE, + OSTREE_CHECKSUM_FLAGS_IGNORE_XATTRS, &checksum, + cancellable, error)) + return FALSE; + + g_print ("%s\n", checksum); + return TRUE; } diff --git a/tests/basic-test.sh b/tests/basic-test.sh index 037f7b45..4aae6b33 100644 --- a/tests/basic-test.sh +++ b/tests/basic-test.sh @@ -19,7 +19,7 @@ set -euo pipefail -echo "1..$((74 + ${extra_basic_tests:-0}))" +echo "1..$((75 + ${extra_basic_tests:-0}))" CHECKOUT_U_ARG="" CHECKOUT_H_ARGS="-H" @@ -238,6 +238,33 @@ fi assert_file_has_content err.txt "No such metadata object" echo "ok commit orphaned" +cd ${test_tmpdir} +# in bare-user-only mode, we canonicalize ownership to 0:0, so checksums won't +# match -- we could add a --ignore-ownership option I suppose? +if is_bare_user_only_repo repo; then + echo "ok # SKIP checksums won't match up in bare-user-only" +else + $OSTREE fsck + CHECKSUM_FLAG= + if [ -n "${OSTREE_NO_XATTRS:-}" ]; then + CHECKSUM_FLAG=--ignore-xattrs + fi + rm -rf checksum-test + $OSTREE checkout test2 checksum-test + find checksum-test/ -type f | while read fn; do + checksum=$($CMD_PREFIX ostree checksum $CHECKSUM_FLAG $fn) + objpath=repo/objects/${checksum::2}/${checksum:2}.file + assert_has_file $objpath + # running `ostree checksum` on the obj might not necessarily match, let's + # just check that they have the same content to confirm that it's + # (probably) the originating file + object_content_checksum=$(sha256sum $objpath | cut -f1 -d' ') + checkout_content_checksum=$(sha256sum $fn | cut -f1 -d' ') + assert_streq "$object_content_checksum" "$checkout_content_checksum" + done + echo "ok checksum CLI" +fi + cd ${test_tmpdir} $OSTREE diff test2^ test2 > diff-test2 assert_file_has_content diff-test2 'D */a/5'