mirror of
https://github.com/ostreedev/ostree.git
synced 2025-01-12 13:18:27 +03:00
repo/commit: Split up metadata/content commit paths
There was a lot of conditionals inside `write_object()` differentating between metadata/content, and then for content, on the different repo types. Further, in the metadata path since the logic is simpler, can present a non-streaming API, and further use `OtTmpfile`, etc. Splitting them up helps drop a lot of conditionals. We introduce a small `CleanupUnlinkat` that allows us to fully convert to the new code style in both functions. This itself is still prep for fully switching to `GLnxTmpfile`. Closes: #881 Approved by: jlebon
This commit is contained in:
parent
ec1964dd44
commit
f4f1330789
@ -591,292 +591,356 @@ create_regular_tmpfile_linkable_with_content (OstreeRepo *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
write_object (OstreeRepo *self,
|
||||
OstreeObjectType objtype,
|
||||
const char *expected_checksum,
|
||||
GInputStream *input,
|
||||
guint64 file_object_length,
|
||||
guchar **out_csum,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
const char *actual_checksum = NULL;
|
||||
g_autofree char *actual_checksum_owned = NULL;
|
||||
gboolean do_commit;
|
||||
OstreeRepoMode repo_mode;
|
||||
g_autofree char *temp_filename = NULL;
|
||||
g_autofree guchar *ret_csum = NULL;
|
||||
glnx_unref_object OtChecksumInstream *checksum_input = NULL;
|
||||
g_autoptr(GInputStream) file_input = NULL;
|
||||
g_autoptr(GFileInfo) file_info = NULL;
|
||||
g_autoptr(GVariant) xattrs = NULL;
|
||||
gboolean have_obj;
|
||||
gboolean temp_file_is_regular;
|
||||
gboolean temp_file_is_symlink;
|
||||
glnx_fd_close int temp_fd = -1;
|
||||
gboolean object_is_symlink = FALSE;
|
||||
gssize unpacked_size = 0;
|
||||
gboolean indexable = FALSE;
|
||||
/* A little helper to call unlinkat() as a cleanup
|
||||
* function. Mostly only necessary to handle
|
||||
* deletion of temporary symlinks.
|
||||
*/
|
||||
typedef struct {
|
||||
int dfd;
|
||||
const char *path;
|
||||
} CleanupUnlinkat;
|
||||
|
||||
static void
|
||||
cleanup_unlinkat (CleanupUnlinkat *cleanup)
|
||||
{
|
||||
if (cleanup->path)
|
||||
(void) unlinkat (cleanup->dfd, cleanup->path, 0);
|
||||
}
|
||||
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(CleanupUnlinkat, cleanup_unlinkat);
|
||||
|
||||
/* Write a content object. */
|
||||
static gboolean
|
||||
write_content_object (OstreeRepo *self,
|
||||
const char *expected_checksum,
|
||||
GInputStream *input,
|
||||
guint64 file_object_length,
|
||||
guchar **out_csum,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (expected_checksum || out_csum, FALSE);
|
||||
|
||||
if (g_cancellable_set_error_if_cancelled (cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
repo_mode = ostree_repo_get_mode (self);
|
||||
OstreeRepoMode repo_mode = ostree_repo_get_mode (self);
|
||||
|
||||
glnx_unref_object OtChecksumInstream *checksum_input = NULL;
|
||||
if (out_csum)
|
||||
checksum_input = ot_checksum_instream_new (input, G_CHECKSUM_SHA256);
|
||||
|
||||
if (objtype == OSTREE_OBJECT_TYPE_FILE)
|
||||
g_autoptr(GInputStream) file_input = NULL;
|
||||
g_autoptr(GVariant) xattrs = NULL;
|
||||
g_autoptr(GFileInfo) file_info = NULL;
|
||||
if (!ostree_content_stream_parse (FALSE, checksum_input ? (GInputStream*)checksum_input : input,
|
||||
file_object_length, FALSE,
|
||||
&file_input, &file_info, &xattrs,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
gboolean temp_file_is_regular = g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR;
|
||||
gboolean temp_file_is_symlink = g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK;
|
||||
gboolean object_is_symlink = temp_file_is_symlink;
|
||||
|
||||
if (repo_mode == OSTREE_REPO_MODE_BARE_USER && object_is_symlink)
|
||||
{
|
||||
if (!ostree_content_stream_parse (FALSE, checksum_input ? (GInputStream*)checksum_input : input,
|
||||
file_object_length, FALSE,
|
||||
&file_input, &file_info, &xattrs,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
const char *target_str = g_file_info_get_symlink_target (file_info);
|
||||
g_autoptr(GBytes) target = g_bytes_new (target_str, strlen (target_str) + 1);
|
||||
|
||||
temp_file_is_regular = g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR;
|
||||
temp_file_is_symlink = object_is_symlink =
|
||||
g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK;
|
||||
/* For bare-user we can't store symlinks as symlinks, as symlinks don't
|
||||
support user xattrs to store the ownership. So, instead store them
|
||||
as regular files */
|
||||
temp_file_is_regular = TRUE;
|
||||
temp_file_is_symlink = FALSE;
|
||||
|
||||
if (repo_mode == OSTREE_REPO_MODE_BARE_USER && object_is_symlink)
|
||||
{
|
||||
const char *target_str = g_file_info_get_symlink_target (file_info);
|
||||
g_autoptr(GBytes) target = g_bytes_new (target_str, strlen (target_str) + 1);
|
||||
|
||||
/* For bare-user we can't store symlinks as symlinks, as symlinks don't
|
||||
support user xattrs to store the ownership. So, instead store them
|
||||
as regular files */
|
||||
temp_file_is_regular = TRUE;
|
||||
temp_file_is_symlink = FALSE;
|
||||
if (file_input != NULL)
|
||||
g_object_unref (file_input);
|
||||
|
||||
/* Include the terminating zero so we can e.g. mmap this file */
|
||||
file_input = g_memory_input_stream_new_from_bytes (target);
|
||||
}
|
||||
|
||||
if (!(temp_file_is_regular || temp_file_is_symlink))
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Unsupported file type %u", g_file_info_get_file_type (file_info));
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* For regular files, we create them with default mode, and only
|
||||
* later apply any xattrs and setuid bits. The rationale here
|
||||
* is that an attacker on the network with the ability to MITM
|
||||
* could potentially cause the system to make a temporary setuid
|
||||
* binary with trailing garbage, creating a window on the local
|
||||
* system where a malicious setuid binary exists.
|
||||
*/
|
||||
if ((_ostree_repo_mode_is_bare (repo_mode)) && temp_file_is_regular)
|
||||
{
|
||||
guint64 size = g_file_info_get_size (file_info);
|
||||
|
||||
if (!create_regular_tmpfile_linkable_with_content (self, size, file_input,
|
||||
&temp_fd, &temp_filename,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
else if (_ostree_repo_mode_is_bare (repo_mode) && temp_file_is_symlink)
|
||||
{
|
||||
/* Note: This will not be hit for bare-user mode because its converted to a
|
||||
regular file and take the branch above */
|
||||
if (!_ostree_make_temporary_symlink_at (self->tmp_dir_fd,
|
||||
g_file_info_get_symlink_target (file_info),
|
||||
&temp_filename,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
else if (repo_mode == OSTREE_REPO_MODE_ARCHIVE_Z2)
|
||||
{
|
||||
g_autoptr(GVariant) file_meta = NULL;
|
||||
g_autoptr(GConverter) zlib_compressor = NULL;
|
||||
g_autoptr(GOutputStream) compressed_out_stream = NULL;
|
||||
g_autoptr(GOutputStream) temp_out = NULL;
|
||||
|
||||
if (self->generate_sizes)
|
||||
indexable = TRUE;
|
||||
|
||||
if (!glnx_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY|O_CLOEXEC,
|
||||
&temp_fd, &temp_filename,
|
||||
error))
|
||||
goto out;
|
||||
temp_file_is_regular = TRUE;
|
||||
temp_out = g_unix_output_stream_new (temp_fd, FALSE);
|
||||
|
||||
file_meta = _ostree_zlib_file_header_new (file_info, xattrs);
|
||||
|
||||
if (!_ostree_write_variant_with_size (temp_out, file_meta, 0, NULL, NULL,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
|
||||
{
|
||||
zlib_compressor = (GConverter*)g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW, self->zlib_compression_level);
|
||||
compressed_out_stream = g_converter_output_stream_new (temp_out, zlib_compressor);
|
||||
/* Don't close the base; we'll do that later */
|
||||
g_filter_output_stream_set_close_base_stream ((GFilterOutputStream*)compressed_out_stream, FALSE);
|
||||
|
||||
unpacked_size = g_output_stream_splice (compressed_out_stream, file_input,
|
||||
0, cancellable, error);
|
||||
if (unpacked_size < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!g_output_stream_flush (temp_out, cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (fchmod (temp_fd, 0644) < 0)
|
||||
{
|
||||
glnx_set_error_from_errno (error);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
else
|
||||
g_assert_not_reached ();
|
||||
if (file_input != NULL)
|
||||
g_object_unref (file_input);
|
||||
/* Include the terminating zero so we can e.g. mmap this file */
|
||||
file_input = g_memory_input_stream_new_from_bytes (target);
|
||||
}
|
||||
else
|
||||
|
||||
if (!(temp_file_is_regular || temp_file_is_symlink))
|
||||
return glnx_throw (error, "Unsupported file type %u", g_file_info_get_file_type (file_info));
|
||||
|
||||
/* For regular files, we create them with default mode, and only
|
||||
* later apply any xattrs and setuid bits. The rationale here
|
||||
* is that an attacker on the network with the ability to MITM
|
||||
* could potentially cause the system to make a temporary setuid
|
||||
* binary with trailing garbage, creating a window on the local
|
||||
* system where a malicious setuid binary exists.
|
||||
*/
|
||||
/* These variables are almost equivalent to OtTmpfile, except
|
||||
* temp_filename might also be a symlink. Hence the CleanupUnlinkat
|
||||
* which handles that case.
|
||||
*/
|
||||
g_auto(CleanupUnlinkat) tmp_unlinker = { self->tmp_dir_fd, NULL };
|
||||
glnx_fd_close int temp_fd = -1;
|
||||
g_autofree char *temp_filename = NULL;
|
||||
gssize unpacked_size = 0;
|
||||
gboolean indexable = FALSE;
|
||||
if ((_ostree_repo_mode_is_bare (repo_mode)) && temp_file_is_regular)
|
||||
{
|
||||
if (!create_regular_tmpfile_linkable_with_content (self, file_object_length,
|
||||
checksum_input ? (GInputStream*)checksum_input : input,
|
||||
guint64 size = g_file_info_get_size (file_info);
|
||||
|
||||
if (!create_regular_tmpfile_linkable_with_content (self, size, file_input,
|
||||
&temp_fd, &temp_filename,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
tmp_unlinker.path = temp_filename;
|
||||
}
|
||||
else if (_ostree_repo_mode_is_bare (repo_mode) && temp_file_is_symlink)
|
||||
{
|
||||
/* Note: This will not be hit for bare-user mode because its converted to a
|
||||
regular file and take the branch above */
|
||||
if (!_ostree_make_temporary_symlink_at (self->tmp_dir_fd,
|
||||
g_file_info_get_symlink_target (file_info),
|
||||
&temp_filename,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
tmp_unlinker.path = temp_filename;
|
||||
}
|
||||
else if (repo_mode == OSTREE_REPO_MODE_ARCHIVE_Z2)
|
||||
{
|
||||
g_autoptr(GVariant) file_meta = NULL;
|
||||
g_autoptr(GConverter) zlib_compressor = NULL;
|
||||
g_autoptr(GOutputStream) compressed_out_stream = NULL;
|
||||
g_autoptr(GOutputStream) temp_out = NULL;
|
||||
|
||||
if (self->generate_sizes)
|
||||
indexable = TRUE;
|
||||
|
||||
if (!glnx_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY|O_CLOEXEC,
|
||||
&temp_fd, &temp_filename,
|
||||
error))
|
||||
return FALSE;
|
||||
tmp_unlinker.path = temp_filename;
|
||||
temp_file_is_regular = TRUE;
|
||||
temp_out = g_unix_output_stream_new (temp_fd, FALSE);
|
||||
|
||||
file_meta = _ostree_zlib_file_header_new (file_info, xattrs);
|
||||
|
||||
if (!_ostree_write_variant_with_size (temp_out, file_meta, 0, NULL, NULL,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
|
||||
{
|
||||
zlib_compressor = (GConverter*)g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW, self->zlib_compression_level);
|
||||
compressed_out_stream = g_converter_output_stream_new (temp_out, zlib_compressor);
|
||||
/* Don't close the base; we'll do that later */
|
||||
g_filter_output_stream_set_close_base_stream ((GFilterOutputStream*)compressed_out_stream, FALSE);
|
||||
|
||||
unpacked_size = g_output_stream_splice (compressed_out_stream, file_input,
|
||||
0, cancellable, error);
|
||||
if (unpacked_size < 0)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!g_output_stream_flush (temp_out, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
if (fchmod (temp_fd, 0644) < 0)
|
||||
return glnx_throw_errno_prefix (error, "fchmod");
|
||||
}
|
||||
|
||||
const char *actual_checksum = NULL;
|
||||
g_autofree char *actual_checksum_owned = NULL;
|
||||
if (!checksum_input)
|
||||
actual_checksum = expected_checksum;
|
||||
else
|
||||
{
|
||||
actual_checksum = actual_checksum_owned = ot_checksum_instream_get_string (checksum_input);
|
||||
if (expected_checksum && strcmp (actual_checksum, expected_checksum) != 0)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Corrupted %s object %s (actual checksum is %s)",
|
||||
ostree_object_type_to_string (objtype),
|
||||
expected_checksum, actual_checksum);
|
||||
goto out;
|
||||
}
|
||||
return glnx_throw (error, "Corrupted %s object %s (actual checksum is %s)",
|
||||
ostree_object_type_to_string (OSTREE_OBJECT_TYPE_FILE),
|
||||
expected_checksum, actual_checksum);
|
||||
}
|
||||
|
||||
g_assert (actual_checksum != NULL); /* Pacify static analysis */
|
||||
|
||||
|
||||
/* See whether or not we have the object, now that we know the
|
||||
* checksum.
|
||||
*/
|
||||
gboolean have_obj;
|
||||
if (!_ostree_repo_has_loose_object (self, actual_checksum, OSTREE_OBJECT_TYPE_FILE,
|
||||
&have_obj, cancellable, error))
|
||||
return FALSE;
|
||||
/* If we already have it, just update the stats. */
|
||||
if (have_obj)
|
||||
{
|
||||
g_mutex_lock (&self->txn_stats_lock);
|
||||
self->txn_stats.content_objects_total++;
|
||||
g_mutex_unlock (&self->txn_stats_lock);
|
||||
if (out_csum)
|
||||
*out_csum = ostree_checksum_to_bytes (actual_checksum);
|
||||
/* Note early return */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
const guint32 uid = g_file_info_get_attribute_uint32 (file_info, "unix::uid");
|
||||
const guint32 gid = g_file_info_get_attribute_uint32 (file_info, "unix::gid");
|
||||
const guint32 mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
|
||||
if (!commit_loose_object_trusted (self, actual_checksum,
|
||||
OSTREE_OBJECT_TYPE_FILE,
|
||||
temp_filename,
|
||||
object_is_symlink,
|
||||
uid, gid, mode,
|
||||
xattrs, temp_fd,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
/* Clear the unlinker path, it was consumed */
|
||||
tmp_unlinker.path = NULL;
|
||||
|
||||
/* Update size metadata if configured */
|
||||
if (indexable && temp_file_is_regular)
|
||||
{
|
||||
struct stat stbuf;
|
||||
|
||||
if (fstat (temp_fd, &stbuf) == -1)
|
||||
{
|
||||
glnx_set_error_from_errno (error);
|
||||
goto out;
|
||||
}
|
||||
if (!glnx_fstat (temp_fd, &stbuf, error))
|
||||
return FALSE;
|
||||
|
||||
repo_store_size_entry (self, actual_checksum, unpacked_size, stbuf.st_size);
|
||||
}
|
||||
|
||||
if (!_ostree_repo_has_loose_object (self, actual_checksum, objtype, &have_obj,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
do_commit = !have_obj;
|
||||
|
||||
if (do_commit)
|
||||
{
|
||||
guint32 uid, gid, mode;
|
||||
|
||||
if (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");
|
||||
}
|
||||
else
|
||||
uid = gid = mode = 0;
|
||||
|
||||
if (!commit_loose_object_trusted (self, actual_checksum, objtype,
|
||||
temp_filename,
|
||||
object_is_symlink,
|
||||
uid, gid, mode,
|
||||
xattrs, temp_fd,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
|
||||
if (objtype == OSTREE_OBJECT_TYPE_COMMIT)
|
||||
{
|
||||
GError *local_error = NULL;
|
||||
/* If we are writing a commit, be sure there is no tombstone for it.
|
||||
We may have deleted the commit and now we are trying to pull it again. */
|
||||
if (!ostree_repo_delete_object (self,
|
||||
OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT,
|
||||
actual_checksum,
|
||||
cancellable,
|
||||
&local_error))
|
||||
{
|
||||
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||||
g_clear_error (&local_error);
|
||||
else
|
||||
{
|
||||
g_propagate_error (error, local_error);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (OSTREE_OBJECT_TYPE_IS_META (objtype))
|
||||
{
|
||||
if (G_UNLIKELY (file_object_length > OSTREE_MAX_METADATA_WARN_SIZE))
|
||||
{
|
||||
g_autofree char *metasize = g_format_size (file_object_length);
|
||||
g_autofree char *warnsize = g_format_size (OSTREE_MAX_METADATA_WARN_SIZE);
|
||||
g_autofree char *maxsize = g_format_size (OSTREE_MAX_METADATA_SIZE);
|
||||
g_warning ("metadata object %s is %s, which is larger than the warning threshold of %s." \
|
||||
" The hard limit on metadata size is %s. Put large content in the tree itself, not in metadata.",
|
||||
actual_checksum,
|
||||
metasize, warnsize, maxsize);
|
||||
}
|
||||
}
|
||||
|
||||
g_clear_pointer (&temp_filename, g_free);
|
||||
}
|
||||
|
||||
/* Update statistics */
|
||||
g_mutex_lock (&self->txn_stats_lock);
|
||||
if (do_commit)
|
||||
{
|
||||
if (OSTREE_OBJECT_TYPE_IS_META (objtype))
|
||||
{
|
||||
self->txn_stats.metadata_objects_written++;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->txn_stats.content_objects_written++;
|
||||
self->txn_stats.content_bytes_written += file_object_length;
|
||||
}
|
||||
}
|
||||
if (OSTREE_OBJECT_TYPE_IS_META (objtype))
|
||||
self->txn_stats.metadata_objects_total++;
|
||||
else
|
||||
self->txn_stats.content_objects_total++;
|
||||
self->txn_stats.content_objects_written++;
|
||||
self->txn_stats.content_bytes_written += file_object_length;
|
||||
self->txn_stats.content_objects_total++;
|
||||
g_mutex_unlock (&self->txn_stats_lock);
|
||||
|
||||
if (checksum_input)
|
||||
if (out_csum)
|
||||
{
|
||||
g_assert (actual_checksum);
|
||||
ret_csum = ostree_checksum_to_bytes (actual_checksum);
|
||||
*out_csum = ostree_checksum_to_bytes (actual_checksum);
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
ot_transfer_out_value(out_csum, &ret_csum);
|
||||
out:
|
||||
if (temp_filename)
|
||||
(void) unlinkat (self->tmp_dir_fd, temp_filename, 0);
|
||||
return ret;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
write_metadata_object (OstreeRepo *self,
|
||||
OstreeObjectType objtype,
|
||||
const char *expected_checksum,
|
||||
GBytes *buf,
|
||||
guchar **out_csum,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (expected_checksum || out_csum, FALSE);
|
||||
|
||||
if (g_cancellable_set_error_if_cancelled (cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
/* In the metadata case, we're not streaming, so we don't bother creating a
|
||||
* tempfile until we compute the checksum. Some metadata like dirmeta is
|
||||
* commonly duplicated, and computing the checksum is going to be cheaper than
|
||||
* making a tempfile.
|
||||
*
|
||||
* However, tombstone commit types don't make sense to checksum, because for
|
||||
* historical reasons we used ostree_repo_write_metadata_trusted() with the
|
||||
* *original* sha256 to say what commit was being killed.
|
||||
*/
|
||||
const gboolean is_tombstone = (objtype == OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT);
|
||||
g_autofree char *actual_checksum = NULL;
|
||||
if (is_tombstone)
|
||||
{
|
||||
actual_checksum = g_strdup (expected_checksum);
|
||||
}
|
||||
else
|
||||
{
|
||||
actual_checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, buf);
|
||||
gboolean have_obj;
|
||||
if (!_ostree_repo_has_loose_object (self, actual_checksum, objtype, &have_obj,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
/* If we already have the object, we just need to update the tried-to-commit
|
||||
* stat for metadata and be done here.
|
||||
*/
|
||||
if (have_obj)
|
||||
{
|
||||
g_mutex_lock (&self->txn_stats_lock);
|
||||
self->txn_stats.metadata_objects_total++;
|
||||
g_mutex_unlock (&self->txn_stats_lock);
|
||||
|
||||
if (out_csum)
|
||||
*out_csum = ostree_checksum_to_bytes (actual_checksum);
|
||||
/* Note early return */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (expected_checksum && strcmp (actual_checksum, expected_checksum) != 0)
|
||||
return glnx_throw (error, "Corrupted %s object %s (actual checksum is %s)",
|
||||
ostree_object_type_to_string (objtype),
|
||||
expected_checksum, actual_checksum);
|
||||
}
|
||||
|
||||
/* Ok, checksum is known, let's get the data */
|
||||
gsize len;
|
||||
const guint8 *bufp = g_bytes_get_data (buf, &len);
|
||||
|
||||
/* Do the size warning here, to avoid warning for already extant metadata */
|
||||
if (G_UNLIKELY (len > OSTREE_MAX_METADATA_WARN_SIZE))
|
||||
{
|
||||
g_autofree char *metasize = g_format_size (len);
|
||||
g_autofree char *warnsize = g_format_size (OSTREE_MAX_METADATA_WARN_SIZE);
|
||||
g_autofree char *maxsize = g_format_size (OSTREE_MAX_METADATA_SIZE);
|
||||
g_warning ("metadata object %s is %s, which is larger than the warning threshold of %s." \
|
||||
" The hard limit on metadata size is %s. Put large content in the tree itself, not in metadata.",
|
||||
actual_checksum,
|
||||
metasize, warnsize, maxsize);
|
||||
}
|
||||
|
||||
/* Write the metadata to a temporary file */
|
||||
g_auto(OtTmpfile) tmpf = { 0, };
|
||||
if (!ot_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY|O_CLOEXEC,
|
||||
&tmpf, error))
|
||||
return FALSE;
|
||||
if (!ot_fallocate (tmpf.fd, len, error))
|
||||
return FALSE;
|
||||
if (glnx_loop_write (tmpf.fd, bufp, len) < 0)
|
||||
return glnx_throw_errno_prefix (error, "write()");
|
||||
if (fchmod (tmpf.fd, 0644) < 0)
|
||||
return glnx_throw_errno_prefix (error, "fchmod");
|
||||
|
||||
/* And commit it into place */
|
||||
if (!_ostree_repo_commit_loose_final (self, actual_checksum, objtype,
|
||||
self->tmp_dir_fd, tmpf.fd, tmpf.path,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
/* The temp path was consumed */
|
||||
g_clear_pointer (&tmpf.path, g_free);
|
||||
|
||||
if (objtype == OSTREE_OBJECT_TYPE_COMMIT)
|
||||
{
|
||||
GError *local_error = NULL;
|
||||
/* If we are writing a commit, be sure there is no tombstone for it.
|
||||
We may have deleted the commit and now we are trying to pull it again. */
|
||||
if (!ostree_repo_delete_object (self,
|
||||
OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT,
|
||||
actual_checksum,
|
||||
cancellable,
|
||||
&local_error))
|
||||
{
|
||||
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
||||
g_clear_error (&local_error);
|
||||
else
|
||||
{
|
||||
g_propagate_error (error, local_error);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the stats, note we both wrote one and add to total */
|
||||
g_mutex_lock (&self->txn_stats_lock);
|
||||
self->txn_stats.metadata_objects_written++;
|
||||
self->txn_stats.metadata_objects_total++;
|
||||
g_mutex_unlock (&self->txn_stats_lock);
|
||||
|
||||
if (out_csum)
|
||||
*out_csum = ostree_checksum_to_bytes (actual_checksum);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@ -1541,11 +1605,9 @@ ostree_repo_write_metadata (OstreeRepo *self,
|
||||
if (!metadata_size_valid (objtype, g_variant_get_size (normalized), error))
|
||||
return FALSE;
|
||||
|
||||
g_autoptr(GInputStream) input = ot_variant_read (normalized);
|
||||
if (!write_object (self, objtype, expected_checksum,
|
||||
input, g_variant_get_size (normalized),
|
||||
out_csum,
|
||||
cancellable, error))
|
||||
g_autoptr(GBytes) vdata = g_variant_get_data_as_bytes (normalized);
|
||||
if (!write_metadata_object (self, objtype, expected_checksum,
|
||||
vdata, out_csum, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
@ -1805,9 +1867,9 @@ ostree_repo_write_content (OstreeRepo *self,
|
||||
}
|
||||
}
|
||||
|
||||
return write_object (self, OSTREE_OBJECT_TYPE_FILE, expected_checksum,
|
||||
object_input, length, out_csum,
|
||||
cancellable, error);
|
||||
return write_content_object (self, expected_checksum,
|
||||
object_input, length, out_csum,
|
||||
cancellable, error);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
|
Loading…
Reference in New Issue
Block a user