repo: Add ostree_repo_write_regfile
authorColin Walters <walters@verbum.org>
Fri, 9 Apr 2021 00:35:54 +0000 (00:35 +0000)
committerColin Walters <walters@verbum.org>
Fri, 9 Apr 2021 21:54:44 +0000 (21:54 +0000)
This API is push rather than pull, which makes it much more
suitable to use cases like parsing a tar file from external
code.

Now, we have a large mess in this area internally because
the original file writing code was pull based, but static
deltas hit the same problem of wanting a push API, so I added
this special `OstreeRepoBareContent` just for writing regular
files from a push API.

Eventually...I'd like to deprecate the pull based API,
and rework things so that for regular files the push API
is the default, and then `write_content_object()` would
be split up into archive/bare cases.

In this world the `ostree_repo_write_content()` API would
then need to hackily bridge pull to push and it'd be
less efficient.

Anyways for now due to this bifurcation, this API only
works on non-archive repositories, but that's fine for
now because that's what I want for the `ostree-ext-container`
bits.

Makefile-libostree.am
apidoc/ostree-sections.txt
src/libostree/libostree-devel.sym
src/libostree/ostree-content-writer.c [new file with mode: 0644]
src/libostree/ostree-content-writer.h [new file with mode: 0644]
src/libostree/ostree-repo-commit.c
src/libostree/ostree-repo-private.h
src/libostree/ostree-repo.h
src/libostree/ostree-types.h
tests/test-core.js
tests/test-repo.c

index 1a5d6f9f477e6d63664d1a70c398c8b35b7feb26..d40de48d13fe8c7cd9c5d4458ae0e60aa3a55e29 100644 (file)
@@ -66,6 +66,8 @@ libostree_1_la_SOURCES = \
        src/libostree/ostree-checksum-input-stream.h \
        src/libostree/ostree-chain-input-stream.c \
        src/libostree/ostree-chain-input-stream.h \
+       src/libostree/ostree-content-writer.c \
+       src/libostree/ostree-content-writer.h \
        src/libostree/ostree-lzma-common.c \
        src/libostree/ostree-lzma-common.h \
        src/libostree/ostree-lzma-compressor.c \
index 65b9e7fd25592b9d366e459b5d7f4ad09dcb313a..b09ba6f9971ef3c3a2e32a3f61c3bfbe72e20ca0 100644 (file)
@@ -165,6 +165,12 @@ ostree_check_version
 ostree_commit_sizes_entry_get_type
 </SECTION>
 
+<SECTION>
+<FILE>ostree-content-writer</FILE>
+ostree_content_writer_get_type
+ostree_content_writer_finish
+</SECTION>
+
 <SECTION>
 <FILE>ostree-deployment</FILE>
 OstreeDeployment
@@ -355,6 +361,7 @@ ostree_repo_write_metadata
 ostree_repo_write_metadata_async
 ostree_repo_write_metadata_finish
 ostree_repo_write_content
+ostree_repo_write_regfile
 ostree_repo_write_regfile_inline
 ostree_repo_write_symlink
 ostree_repo_write_metadata_trusted
index e218dddacad90aeb861a11fa234216a5a2129125..8cc11c658c8127d4cb41d04418d398fa32e4c4e2 100644 (file)
@@ -26,6 +26,9 @@ LIBOSTREE_2021.2 {
 global:
   ostree_repo_write_regfile_inline;
   ostree_repo_write_symlink;
+  ostree_repo_write_regfile;
+  ostree_content_writer_get_type;
+  ostree_content_writer_finish;
 } LIBOSTREE_2021.1;
 
 /* Stub section for the stable release *after* this development one; don't
diff --git a/src/libostree/ostree-content-writer.c b/src/libostree/ostree-content-writer.c
new file mode 100644 (file)
index 0000000..2e37530
--- /dev/null
@@ -0,0 +1,144 @@
+/* 
+ * 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.
+ */
+
+#include "config.h"
+
+#include "ostree-content-writer.h"
+#include "ostree-repo-private.h"
+#include "ostree-autocleanups.h"
+
+struct _OstreeContentWriter
+{
+  GOutputStream parent_instance;
+
+  OstreeRepo *repo;
+  OstreeRepoBareContent output;
+};
+
+G_DEFINE_TYPE (OstreeContentWriter, ostree_content_writer, G_TYPE_OUTPUT_STREAM)
+
+static void     ostree_content_writer_finalize     (GObject *object);
+static gssize   ostree_content_writer_write         (GOutputStream         *stream,
+                                                     const void                 *buffer,
+                                                     gsize                 count,
+                                                     GCancellable         *cancellable,
+                                                     GError              **error);
+static gboolean ostree_content_writer_close        (GOutputStream         *stream,
+                                                    GCancellable         *cancellable,
+                                                    GError              **error);
+
+static void
+ostree_content_writer_class_init (OstreeContentWriterClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass);
+  
+  gobject_class->finalize     = ostree_content_writer_finalize;
+
+  stream_class->write_fn = ostree_content_writer_write;
+  stream_class->close_fn = ostree_content_writer_close;
+}
+
+static void
+ostree_content_writer_finalize (GObject *object)
+{
+  OstreeContentWriter *stream;
+
+  stream = (OstreeContentWriter*)(object);
+
+  g_clear_object (&stream->repo);
+  _ostree_repo_bare_content_cleanup (&stream->output);
+
+  G_OBJECT_CLASS (ostree_content_writer_parent_class)->finalize (object);
+}
+
+static void
+ostree_content_writer_init (OstreeContentWriter *self)
+{
+  self->output.initialized = FALSE;
+ }
+
+OstreeContentWriter *
+_ostree_content_writer_new (OstreeRepo           *repo,
+                           const char            *checksum,
+                           guint                  uid,
+                           guint                  gid,
+                           guint                  mode,
+                           guint64                content_len,
+                           GVariant              *xattrs,
+                           GError               **error)
+{
+  g_autoptr(OstreeContentWriter) stream = g_object_new (OSTREE_TYPE_CONTENT_WRITER, NULL);
+  stream->repo = g_object_ref (repo);
+  if (!_ostree_repo_bare_content_open (stream->repo, checksum, content_len, uid, gid, mode, xattrs,
+                                       &stream->output, NULL, error))
+    return NULL;
+  return g_steal_pointer (&stream);
+}
+
+static gssize
+ostree_content_writer_write (GOutputStream  *stream,
+                             const void    *buffer,
+                             gsize          count,
+                             GCancellable  *cancellable,
+                             GError       **error)
+{
+  OstreeContentWriter *self = (OstreeContentWriter*) stream;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    return -1;
+
+  if (!_ostree_repo_bare_content_write (self->repo, &self->output,
+                                        buffer, count, cancellable, error))
+    return -1;
+  return count;
+}
+
+static gboolean
+ostree_content_writer_close (GOutputStream        *stream,
+                             GCancellable         *cancellable,
+                             GError              **error)
+{
+  /* We don't expect people to invoke close() - they need to call finish()
+   * to get the checksum.  We'll clean up in finalize anyways if need be.
+   */
+  return TRUE;
+}
+
+/**
+ * ostree_content_writer_finish:
+ * @self: Writer
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Complete the object write and return the checksum.
+ * Returns: (transfer full): Checksum, or %NULL on error
+ */
+char *
+ostree_content_writer_finish (OstreeContentWriter  *self,
+                              GCancellable         *cancellable,
+                              GError              **error)
+{
+  char actual_checksum[OSTREE_SHA256_STRING_LEN+1];
+  if (!_ostree_repo_bare_content_commit (self->repo, &self->output, actual_checksum,
+                                         sizeof (actual_checksum), cancellable, error))
+    return NULL;
+
+  return g_strdup (actual_checksum);
+}
diff --git a/src/libostree/ostree-content-writer.h b/src/libostree/ostree-content-writer.h
new file mode 100644 (file)
index 0000000..87a85aa
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define OSTREE_TYPE_CONTENT_WRITER (ostree_content_writer_get_type ())
+_OSTREE_PUBLIC G_DECLARE_FINAL_TYPE (OstreeContentWriter, ostree_content_writer, OSTREE, CONTENT_WRITER, GOutputStream)
+
+_OSTREE_PUBLIC
+char * ostree_content_writer_finish (OstreeContentWriter  *self,
+                                     GCancellable         *cancellable,
+                                     GError              **error);
+
+G_END_DECLS
index ae93eedbc4bd2283b0205bb40b1e8176122b19d3..769dd6a76b7eca7a45ef7bf6a7446e40a2504af1 100644 (file)
@@ -2855,6 +2855,39 @@ ostree_repo_write_symlink (OstreeRepo       *self,
   return ostree_checksum_from_bytes (csum);
 }
 
+/**
+ * ostree_repo_write_regfile:
+ * @self: Repo,
+ * @expected_checksum: (allow-none): Expected checksum (SHA-256 hex string)
+ * @uid: user id
+ * @gid: group id
+ * @mode: Unix file mode
+ * @content_len: Expected content length
+ * @xattrs: (allow-none): Extended attributes (GVariant type `(ayay)`)
+ * @error: Error
+ * 
+ * Create an `OstreeContentWriter` that allows streaming output into
+ * the repository.
+ *
+ * Returns: (transfer full): A new writer, or %NULL on error
+ * Since: 2021.2
+ */
+OstreeContentWriter *    
+ostree_repo_write_regfile (OstreeRepo       *self,
+                           const char       *expected_checksum,
+                           guint32           uid,
+                           guint32           gid,
+                           guint32           mode,
+                           guint64           content_len,
+                           GVariant         *xattrs,
+                           GError          **error)
+{
+  if (self->mode == OSTREE_REPO_MODE_ARCHIVE)
+    return glnx_null_throw (error, "Cannot currently use ostree_repo_write_regfile() on an archive mode repository");
+
+  return _ostree_content_writer_new (self, expected_checksum, uid, gid, mode, content_len, xattrs, error);
+}
+
 typedef struct {
   OstreeRepo *repo;
   char *expected_checksum;
index 6c01bc6bc4265a92b990910da7f76fd163d15cf7..14b29c0bbaa4123b16bbb6bec8388e9679d40383 100644 (file)
@@ -462,6 +462,16 @@ _ostree_repo_bare_content_commit (OstreeRepo                 *self,
                                   GCancellable               *cancellable,
                                   GError                    **error);
 
+OstreeContentWriter *
+_ostree_content_writer_new (OstreeRepo            *repo,
+                           const char            *checksum,
+                           guint                  uid,
+                           guint                  gid,
+                           guint                  mode,
+                           guint64                content_len,
+                           GVariant              *xattrs,
+                           GError               **error);
+
 gboolean
 _ostree_repo_load_file_bare (OstreeRepo         *self,
                              const char         *checksum,
index 7e08361b39d774df5dbe690d5858d52451bbe1d2..f94d70be1d1224d7e821393c514678efa23f48c0 100644 (file)
@@ -435,6 +435,16 @@ char *      ostree_repo_write_regfile_inline (OstreeRepo       *self,
                                                 GCancellable     *cancellable,
                                                 GError          **error);
 
+_OSTREE_PUBLIC
+OstreeContentWriter *    ostree_repo_write_regfile (OstreeRepo       *self,
+                                                    const char       *expected_checksum,
+                                                    guint32           uid,
+                                                    guint32           gid,
+                                                    guint32           mode,
+                                                    guint64           content_len,
+                                                    GVariant         *xattrs,
+                                                    GError          **error);
+             
 _OSTREE_PUBLIC
 char *      ostree_repo_write_symlink (OstreeRepo       *self,
                                        const char       *expected_checksum,
index bbc6ca64ffa8417893a609a6c7d0f775b308a3d9..c6f9cba11a0e830c47ec7117ce64aa3d9c86c0bd 100644 (file)
@@ -38,6 +38,7 @@ typedef struct OstreeSysroot OstreeSysroot;
 typedef struct OstreeSysrootUpgrader OstreeSysrootUpgrader;
 typedef struct OstreeMutableTree OstreeMutableTree;
 typedef struct OstreeRepoFile OstreeRepoFile;
+typedef struct _OstreeContentWriter OstreeContentWriter;
 typedef struct OstreeRemote OstreeRemote;
 
 G_END_DECLS
index 5f3e9fe356c09de87cb97b41cadd19883c1257bc..1d13cfbe8545634f2e3eec55c8d33901406785da 100755 (executable)
@@ -27,6 +27,22 @@ function assertEquals(a, b) {
        throw new Error("assertion failed " + JSON.stringify(a) + " == " + JSON.stringify(b));
 }
 
+function assertThrows(s, f) {
+  let success = false;
+  try {
+    f();
+    success = true;
+  } catch(e) {
+    let msg = e.toString();
+    if (msg.indexOf(s) == -1) {
+      throw new Error("Error message didn't match '" + s + "': " + msg)    
+    }
+  }
+  if (success) {
+    throw new Error("Function was expected to throw, but didn't")
+  }
+}
+
 print('1..1')
 
 let testDataDir = Gio.File.new_for_path('test-data');
@@ -51,18 +67,14 @@ print("commit => " + commit);
 
 // Test direct write APIs
 let inline_content = "default 0.0.0.0\nloopback 127.0.0.0\nlink-local 169.254.0.0\n";
+let networks_checksum = "8aaa9dc13a0c5839fe4a277756798c609c53fac6fa2290314ecfef9041065873";
 let regfile_mode = 33188; // 0o100000 | 0o644 (but in decimal so old gjs works)
 let inline_checksum = repo.write_regfile_inline(null, 0, 0, regfile_mode, null, inline_content, null);
 assertEquals(inline_checksum, "8aaa9dc13a0c5839fe4a277756798c609c53fac6fa2290314ecfef9041065873");
-let written = false;
-try {
+assertThrows("Corrupted file object", function() {
     // Changed an a to b from above to make the checksum not match
     repo.write_regfile_inline("8baa9dc13a0c5839fe4a277756798c609c53fac6fa2290314ecfef9041065873", 0, 0, regfile_mode, null, inline_content, null);
-    written = true;
-} catch (e) {
-}
-if (written)
-  throw new Error("Wrote invalid checksum");
+});
 
 repo.commit_transaction(null, null);
 
@@ -85,4 +97,21 @@ repo.commit_transaction(null, null);
 [,readCommit] = repo.resolve_rev('someref', true);
 assertEquals(readCommit, null);
 
+// Test direct write API for regular files
+let clen = inline_content.length;
+assertThrows("Cannot currently use", function() {  
+  let w = repo.write_regfile(null, 0, 0, regfile_mode, clen, null);
+});
+
+let bareRepoPath = Gio.File.new_for_path('repo');
+let repo_bareu = OSTree.Repo.new(Gio.File.new_for_path('repo-bare'));
+repo_bareu.create(OSTree.RepoMode.BARE_USER_ONLY, null);
+let w = repo_bareu.write_regfile(null, 0, 0, regfile_mode, clen, null);
+// Test multiple write() calls
+w.write(inline_content.slice(0, 4), null)
+w.write(inline_content.slice(4, 10), null)
+w.write(inline_content.slice(10), null)
+let actual_checksum = w.finish(null)
+assertEquals(actual_checksum, networks_checksum)
+
 print("ok test-core");
index 35e929f977b8bffa0936df0e39656622bf25be5a..9337ac3bd330311366b2b36edeabf9bab145b382 100644 (file)
@@ -241,7 +241,6 @@ test_write_regfile_api (Fixture *fixture,
   g_clear_pointer (&xattrs, g_variant_unref);
   g_variant_builder_init (&xattrs_builder, (GVariantType*)"a(ayay)");
   g_variant_builder_add (&xattrs_builder, "(^ay^ay)", "security.selinux", "system_u:object_r:bin_t:s0");
-  g_clear_pointer (&xattrs, g_variant_unref);
   xattrs = g_variant_ref_sink (g_variant_builder_end (&xattrs_builder));
 
   g_clear_pointer (&checksum, g_free);