diff --git a/Makefile-src.am b/Makefile-src.am index 9990ecdc..ae28761e 100644 --- a/Makefile-src.am +++ b/Makefile-src.am @@ -40,8 +40,8 @@ libostree_la_SOURCES = src/libostree/ostree.h \ src/libostree/ostree-checkout.c \ src/libostree/ostree-checkout.h \ $(NULL) -libostree_la_CFLAGS = -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS) -libostree_la_LIBADD = libotutil.la $(GIO_UNIX_LIBS) +libostree_la_CFLAGS = -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS) +libostree_la_LIBADD = libotutil.la $(OT_COREBIN_DEP_LIBS) bin_PROGRAMS += ostree @@ -53,10 +53,18 @@ ostree_SOURCES = src/main.c \ src/ot-builtin-init.c \ src/ot-builtin-link-file.c \ src/ot-builtin-log.c \ + src/ot-builtin-pull.c \ src/ot-builtin-run-triggers.c \ src/ot-builtin-remote.c \ src/ot-builtin-rev-parse.c \ src/ot-builtin-show.c \ $(NULL) -ostree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS) -ostree_LDADD = libotutil.la libostree.la $(GIO_UNIX_LIBS) +ostree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS) +ostree_LDADD = libotutil.la libostree.la $(OT_COREBIN_DEP_LIBS) + +bin_PROGRAMS += ostree-http-backend + +ostree_http_backend_SOURCES = src/ostree-http-backend.c +ostree_http_backend_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS) +ostree_http_backend_LDADD = libotutil.la libostree.la $(OT_COREBIN_DEP_LIBS) + diff --git a/configure.ac b/configure.ac index be78b1fa..92c06d5e 100644 --- a/configure.ac +++ b/configure.ac @@ -32,6 +32,7 @@ LT_INIT PKG_PROG_PKG_CONFIG PKG_CHECK_MODULES(GIO_UNIX, [gio-unix-2.0 >= 2.28]) +PKG_CHECK_MODULES(OT_COREBIN_DEP, [libsoup-gnome-2.4 >= 2.34.0 gio-unix-2.0 >= 2.28]) AM_PATH_PYTHON diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index d92681b8..ce54fbc4 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -27,13 +27,27 @@ #include #include -static char * -stat_to_string (struct stat *stbuf) +gboolean +ostree_validate_checksum_string (const char *sha256, + GError **error) { - return g_strdup_printf ("%u:%u:%u", - (guint32)(stbuf->st_mode & ~S_IFMT), - (guint32)stbuf->st_uid, - (guint32)stbuf->st_gid); + if (strlen (sha256) != 64) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid rev '%s'", sha256); + return FALSE; + } + return TRUE; +} + + +void +ostree_checksum_update_stat (GChecksum *checksum, guint32 uid, guint32 gid, guint32 mode) +{ + guint32 perms = (mode & ~S_IFMT); + g_checksum_update (checksum, (guint8*) &uid, 4); + g_checksum_update (checksum, (guint8*) &gid, 4); + g_checksum_update (checksum, (guint8*) &perms, 4); } static char * @@ -144,6 +158,7 @@ ostree_get_xattrs_for_path (const char *path, } ret = g_variant_builder_end (&builder); + g_variant_ref_sink (ret); out: if (!ret) g_variant_builder_clear (&builder); @@ -154,9 +169,10 @@ ostree_get_xattrs_for_path (const char *path, gboolean ostree_stat_and_checksum_file (int dir_fd, const char *path, - GChecksum **out_checksum, - struct stat *out_stbuf, - GError **error) + OstreeObjectType objtype, + GChecksum **out_checksum, + struct stat *out_stbuf, + GError **error) { GChecksum *content_sha256 = NULL; GChecksum *content_and_meta_sha256 = NULL; @@ -202,10 +218,12 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path, } } - stat_string = stat_to_string (&stbuf); - xattrs = ostree_get_xattrs_for_path (path, error); - if (!xattrs) - goto out; + if (objtype == OSTREE_OBJECT_TYPE_FILE) + { + xattrs = ostree_get_xattrs_for_path (path, error); + if (!xattrs) + goto out; + } content_sha256 = g_checksum_new (G_CHECKSUM_SHA256); @@ -224,6 +242,8 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path, else if (S_ISLNK(stbuf.st_mode)) { symlink_target = g_malloc (PATH_MAX); + + g_assert (objtype == OSTREE_OBJECT_TYPE_FILE); bytes_read = readlinkat (dir_fd, basename, symlink_target, PATH_MAX); if (bytes_read < 0) @@ -235,6 +255,7 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path, } else if (S_ISCHR(stbuf.st_mode) || S_ISBLK(stbuf.st_mode)) { + g_assert (objtype == OSTREE_OBJECT_TYPE_FILE); device_id = g_strdup_printf ("%u", (guint)stbuf.st_rdev); g_checksum_update (content_sha256, (guint8*)device_id, strlen (device_id)); } @@ -249,8 +270,12 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path, content_and_meta_sha256 = g_checksum_copy (content_sha256); - g_checksum_update (content_and_meta_sha256, (guint8*)stat_string, strlen (stat_string)); - g_checksum_update (content_and_meta_sha256, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs)); + if (objtype == OSTREE_OBJECT_TYPE_FILE) + { + ostree_checksum_update_stat (content_and_meta_sha256, stbuf.st_uid, + stbuf.st_gid, stbuf.st_mode); + g_checksum_update (content_and_meta_sha256, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs)); + } *out_stbuf = stbuf; *out_checksum = content_and_meta_sha256; @@ -267,6 +292,264 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path, g_variant_unref (xattrs); if (content_sha256) g_checksum_free (content_sha256); - return ret; } + +gboolean +ostree_set_xattrs (const char *path, GVariant *xattrs, GError **error) +{ + gboolean ret = FALSE; + int i, n; + + 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; + + g_variant_unref (value); + if (loop_err) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + + ret = TRUE; + out: + return ret; +} + +gboolean +ostree_parse_metadata_file (const char *path, + OstreeSerializedVariantType *out_type, + GVariant **out_variant, + GError **error) +{ + GMappedFile *mfile = NULL; + gboolean ret = FALSE; + GVariant *ret_variant = NULL; + GVariant *container = NULL; + guint32 ret_type; + + mfile = g_mapped_file_new (path, FALSE, error); + if (mfile == NULL) + { + goto out; + } + else + { + container = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT), + g_mapped_file_get_contents (mfile), + g_mapped_file_get_length (mfile), + FALSE, + (GDestroyNotify) g_mapped_file_unref, + mfile); + g_variant_get (container, "(uv)", + &ret_type, &ret_variant); + if (ret_type <= 0 || ret_type > OSTREE_SERIALIZED_VARIANT_LAST) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted metadata object '%s'; invalid type %d", path, ret_type); + goto out; + } + mfile = NULL; + } + + ret = TRUE; + *out_type = ret_type; + *out_variant = ret_variant; + ret_variant = NULL; + out: + if (ret_variant) + g_variant_unref (ret_variant); + if (container != NULL) + g_variant_unref (container); + if (mfile != NULL) + g_mapped_file_unref (mfile); + return ret; +} + +char * +ostree_get_relative_object_path (const char *checksum, + OstreeObjectType type, + gboolean archive) +{ + GString *path; + const char *type_string; + + 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); + switch (type) + { + case OSTREE_OBJECT_TYPE_FILE: + if (archive) + type_string = ".packfile"; + else + type_string = ".file"; + break; + case OSTREE_OBJECT_TYPE_META: + type_string = ".meta"; + break; + default: + g_assert_not_reached (); + } + g_string_append (path, type_string); + return g_string_free (path, FALSE); +} + +gboolean +ostree_pack_object (GOutputStream *output, + GFile *file, + OstreeObjectType objtype, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + char *path = NULL; + GFileInfo *finfo = NULL; + GFileInputStream *instream = NULL; + gboolean pack_builder_initialized = FALSE; + GVariantBuilder pack_builder; + GVariant *pack_variant = NULL; + GVariant *xattrs = NULL; + gsize bytes_written; + + path = g_file_get_path (file); + + finfo = g_file_query_info (file, "standard::type,standard::size,standard::is-symlink,standard::symlink-target,unix::*", + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error); + if (!finfo) + goto out; + + if (objtype == OSTREE_OBJECT_TYPE_META) + { + guint64 object_size_be = GUINT64_TO_BE ((guint64)g_file_info_get_size (finfo)); + if (!g_output_stream_write_all (output, &object_size_be, 8, &bytes_written, cancellable, error)) + goto out; + + instream = g_file_read (file, NULL, error); + if (!instream) + goto out; + + if (g_output_stream_splice (output, (GInputStream*)instream, 0, cancellable, error) < 0) + goto out; + } + else + { + guint32 uid, gid, mode; + guint32 device = 0; + guint32 metadata_size_be; + const char *target = NULL; + guint64 object_size; + + 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); + + g_variant_builder_init (&pack_builder, G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT)); + pack_builder_initialized = TRUE; + g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (0)); + 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)); + + xattrs = ostree_get_xattrs_for_path (path, error); + if (!xattrs) + goto out; + g_variant_builder_add (&pack_builder, "@a(ayay)", xattrs); + + if (S_ISREG (mode)) + { + object_size = (guint64)g_file_info_get_size (finfo); + } + else if (S_ISLNK (mode)) + { + target = g_file_info_get_attribute_byte_string (finfo, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET); + object_size = strlen (target); + } + else if (S_ISBLK (mode) || S_ISCHR (mode)) + { + device = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_DEVICE); + object_size = 4; + } + else + g_assert_not_reached (); + + g_variant_builder_add (&pack_builder, "t", GUINT64_TO_BE (object_size)); + pack_variant = g_variant_builder_end (&pack_builder); + pack_builder_initialized = FALSE; + + metadata_size_be = GUINT32_TO_BE (g_variant_get_size (pack_variant)); + + if (!g_output_stream_write_all (output, &metadata_size_be, 4, + &bytes_written, cancellable, error)) + goto out; + g_assert (bytes_written == 4); + + if (!g_output_stream_write_all (output, g_variant_get_data (pack_variant), g_variant_get_size (pack_variant), + &bytes_written, cancellable, error)) + goto out; + + if (S_ISREG (mode)) + { + instream = g_file_read (file, NULL, error); + if (!instream) + goto out; + bytes_written = g_output_stream_splice (output, (GInputStream*)instream, 0, cancellable, error); + if (bytes_written < 0) + goto out; + if (bytes_written != object_size) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "File size changed unexpectedly"); + goto out; + } + } + else if (S_ISLNK (mode)) + { + if (!g_output_stream_write_all (output, target, object_size, + &bytes_written, cancellable, error)) + goto out; + } + else if (S_ISBLK (mode) || S_ISCHR (mode)) + { + guint32 device_be = GUINT32_TO_BE (device); + g_assert (object_size == 4); + if (!g_output_stream_write_all (output, &device_be, object_size, + &bytes_written, cancellable, error)) + goto out; + g_assert (bytes_written == 4); + } + else + g_assert_not_reached (); + } + + ret = TRUE; + out: + g_free (path); + g_clear_object (&finfo); + g_clear_object (&instream); + if (xattrs) + g_variant_unref (xattrs); + if (pack_builder_initialized) + g_variant_builder_clear (&pack_builder); + if (pack_variant) + g_variant_unref (pack_variant); + return ret; +} + diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h index 6d80941f..3c169189 100644 --- a/src/libostree/ostree-core.h +++ b/src/libostree/ostree-core.h @@ -39,6 +39,7 @@ typedef enum { OSTREE_SERIALIZED_DIRMETA_VARIANT = 3, OSTREE_SERIALIZED_XATTR_VARIANT = 4 } OstreeSerializedVariantType; +#define OSTREE_SERIALIZED_VARIANT_LAST 4 #define OSTREE_SERIALIZED_VARIANT_FORMAT "(uv)" @@ -83,13 +84,54 @@ typedef enum { */ #define OSTREE_COMMIT_GVARIANT_FORMAT "(ua{sv}ssstss)" -GVariant *ostree_get_xattrs_for_path (const char *path, - GError **error); +gboolean ostree_validate_checksum_string (const char *sha256, + GError **error); + +char *ostree_get_relative_object_path (const char *checksum, + OstreeObjectType type, + gboolean archive); + +GVariant *ostree_get_xattrs_for_path (const char *path, + GError **error); + +gboolean ostree_set_xattrs (const char *path, GVariant *xattrs, GError **error); + +gboolean ostree_parse_metadata_file (const char *path, + OstreeSerializedVariantType *out_type, + GVariant **out_variant, + GError **error); gboolean ostree_stat_and_checksum_file (int dirfd, const char *path, - GChecksum **out_checksum, - struct stat *out_stbuf, - GError **error); + OstreeObjectType type, + GChecksum **out_checksum, + struct stat *out_stbuf, + GError **error); + +/* Packed files: + * + * guint32 metadata_length [metadata gvariant] [content] + * + * metadata variant: + * u - Version + * u - uid + * u - gid + * u - mode + * a(ayay) - xattrs + * t - content length + * + * And then following the end of the variant is the content. If + * symlink, then this is the target; if device, then device ID as + * network byte order uint32. + */ +#define OSTREE_PACK_FILE_VARIANT_FORMAT "(uuuua(ayay)t)" + +gboolean ostree_pack_object (GOutputStream *output, + GFile *path, + OstreeObjectType objtype, + GCancellable *cancellable, + GError **error); + +void ostree_checksum_update_stat (GChecksum *checksum, guint32 uid, guint32 gid, guint32 mode); #endif /* _OSTREE_REPO */ diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 964ce695..a1323c69 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -57,12 +57,14 @@ struct _OstreeRepoPrivate { char *path; GFile *repo_file; GFile *local_heads_dir; + GFile *remote_heads_dir; char *objects_path; char *config_path; gboolean inited; GKeyFile *config; + gboolean archive; }; static void @@ -74,6 +76,7 @@ ostree_repo_finalize (GObject *object) g_free (priv->path); g_clear_object (&priv->repo_file); g_clear_object (&priv->local_heads_dir); + g_clear_object (&priv->remote_heads_dir); g_free (priv->objects_path); g_free (priv->config_path); if (priv->config) @@ -140,6 +143,7 @@ ostree_repo_constructor (GType gtype, priv->repo_file = ot_util_new_file_for_path (priv->path); priv->local_heads_dir = g_file_resolve_relative_path (priv->repo_file, "refs/heads"); + priv->remote_heads_dir = g_file_resolve_relative_path (priv->repo_file, "refs/remotes"); priv->objects_path = g_build_filename (priv->path, "objects", NULL); priv->config_path = g_build_filename (priv->path, "config", NULL); @@ -179,19 +183,6 @@ ostree_repo_new (const char *path) return g_object_new (OSTREE_TYPE_REPO, "path", path, NULL); } -static gboolean -validate_checksum_string (const char *sha256, - GError **error) -{ - if (strlen (sha256) != 64) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Invalid rev '%s'", sha256); - return FALSE; - } - return TRUE; -} - static gboolean parse_rev_file (OstreeRepo *self, const char *path, @@ -252,7 +243,7 @@ parse_rev_file (OstreeRepo *self, } else { - if (!validate_checksum_string (rev, error)) + if (!ostree_validate_checksum_string (rev, error)) goto out; } @@ -304,7 +295,7 @@ resolve_rev (OstreeRepo *self, { g_strchomp (ret_rev); - if (!validate_checksum_string (ret_rev, error)) + if (!ostree_validate_checksum_string (ret_rev, error)) goto out; } } @@ -357,12 +348,88 @@ write_checksum_file (GFile *parentdir, return ret; } +/** + * ostree_repo_get_config: + * @self: + * + * Returns: (transfer none): The repository configuration; do not modify + */ +GKeyFile * +ostree_repo_get_config (OstreeRepo *self) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + + g_return_val_if_fail (priv->inited, NULL); + + return priv->config; +} + +/** + * ostree_repo_copy_config: + * @self: + * + * Returns: (transfer full): A newly-allocated copy of the repository config + */ +GKeyFile * +ostree_repo_copy_config (OstreeRepo *self) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + GKeyFile *copy; + char *data; + gsize len; + + g_return_val_if_fail (priv->inited, NULL); + + copy = g_key_file_new (); + data = g_key_file_to_data (priv->config, &len, NULL); + if (!g_key_file_load_from_data (copy, data, len, 0, NULL)) + g_assert_not_reached (); + g_free (data); + return copy; +} + +/** + * ostree_repo_write_config: + * @self: + * @new_config: Overwrite the config file with this data. Do not change later! + * @error: a #GError + * + * Save @new_config in place of this repository's config file. Note + * that @new_config should not be modified after - this function + * simply adds a reference. + */ +gboolean +ostree_repo_write_config (OstreeRepo *self, + GKeyFile *new_config, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + char *data = NULL; + gsize len; + gboolean ret = FALSE; + + g_return_val_if_fail (priv->inited, FALSE); + + data = g_key_file_to_data (new_config, &len, error); + if (!g_file_set_contents (priv->config_path, data, len, error)) + goto out; + + g_key_file_unref (priv->config); + priv->config = g_key_file_ref (new_config); + + ret = TRUE; + out: + g_free (data); + return ret; +} + gboolean ostree_repo_check (OstreeRepo *self, GError **error) { OstreeRepoPrivate *priv = GET_PRIVATE (self); gboolean ret = FALSE; char *version = NULL;; + GError *temp_error = NULL; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); @@ -383,9 +450,12 @@ ostree_repo_check (OstreeRepo *self, GError **error) goto out; } - version = g_key_file_get_value (priv->config, "core", "repo_version", error); - if (!version) - goto out; + version = g_key_file_get_value (priv->config, "core", "repo_version", &temp_error); + if (temp_error) + { + g_propagate_error (error, temp_error); + goto out; + } if (strcmp (version, "0") != 0) { @@ -394,6 +464,20 @@ ostree_repo_check (OstreeRepo *self, GError **error) goto out; } + priv->archive = g_key_file_get_boolean (priv->config, "core", "archive", &temp_error); + if (temp_error) + { + if (g_error_matches (temp_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) + { + g_clear_error (&temp_error); + } + else + { + g_propagate_error (error, temp_error); + goto out; + } + } + priv->inited = TRUE; ret = TRUE; @@ -402,6 +486,23 @@ ostree_repo_check (OstreeRepo *self, GError **error) return ret; } +const char * +ostree_repo_get_path (OstreeRepo *self) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + return priv->path; +} + +gboolean +ostree_repo_is_archive (OstreeRepo *self) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + + g_return_val_if_fail (priv->inited, FALSE); + + return priv->archive; +} + static gboolean import_gvariant_object (OstreeRepo *self, OstreeSerializedVariantType type, @@ -463,54 +564,13 @@ load_gvariant_object_unknown (OstreeRepo *self, GVariant **out_variant, GError **error) { - GMappedFile *mfile = NULL; gboolean ret = FALSE; - GVariant *ret_variant = NULL; - GVariant *container = NULL; char *path = NULL; - guint32 ret_type; path = get_object_path (self, sha256, OSTREE_OBJECT_TYPE_META); - - mfile = g_mapped_file_new (path, FALSE, error); - if (mfile == NULL) - goto out; - else - { - container = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT), - g_mapped_file_get_contents (mfile), - g_mapped_file_get_length (mfile), - FALSE, - (GDestroyNotify) g_mapped_file_unref, - mfile); - if (!g_variant_is_of_type (container, G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT))) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Corrupted metadata object '%s'", sha256); - goto out; - } - g_variant_get (container, "(uv)", - &ret_type, &ret_variant); - mfile = NULL; - } - - ret = TRUE; - out: - if (!ret) - { - if (ret_variant) - g_variant_unref (ret_variant); - } - else - { - *out_type = ret_type; - *out_variant = ret_variant; - } - if (container != NULL) - g_variant_unref (container); + ret = ostree_parse_metadata_file (path, out_type, out_variant, error); g_free (path); - if (mfile != NULL) - g_mapped_file_unref (mfile); + return ret; } @@ -534,7 +594,6 @@ load_gvariant_object (OstreeRepo *self, "Corrupted metadata object '%s'; found type %u, expected %u", sha256, type, (guint32)expected_type); goto out; - } ret = TRUE; @@ -616,41 +675,26 @@ get_object_path (OstreeRepo *self, OstreeObjectType type) { OstreeRepoPrivate *priv = GET_PRIVATE (self); - char *checksum_prefix; - char *base_path; char *ret; - const char *type_string; + char *relpath; - checksum_prefix = g_strndup (checksum, 2); - base_path = g_build_filename (priv->objects_path, checksum_prefix, checksum + 2, NULL); - switch (type) - { - case OSTREE_OBJECT_TYPE_FILE: - type_string = ".file"; - break; - case OSTREE_OBJECT_TYPE_META: - type_string = ".meta"; - break; - default: - g_assert_not_reached (); - } - ret = g_strconcat (base_path, type_string, NULL); - g_free (base_path); - g_free (checksum_prefix); + relpath = ostree_get_relative_object_path (checksum, type, priv->archive); + ret = g_build_filename (priv->path, relpath, NULL); + g_free (relpath); return ret; } static char * prepare_dir_for_checksum_get_object_path (OstreeRepo *self, - GChecksum *checksum, + const char *checksum, OstreeObjectType type, GError **error) { char *checksum_dir = NULL; char *object_path = NULL; - object_path = get_object_path (self, g_checksum_get_string (checksum), type); + object_path = get_object_path (self, checksum, type); checksum_dir = g_path_get_dirname (object_path); if (!ot_util_ensure_directory (checksum_dir, FALSE, error)) @@ -662,21 +706,23 @@ prepare_dir_for_checksum_get_object_path (OstreeRepo *self, } static gboolean -link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type, - gboolean ignore_exists, gboolean force, - GChecksum **out_checksum, - GError **error) +link_object_trusted (OstreeRepo *self, + const char *path, + const char *checksum, + OstreeObjectType objtype, + gboolean ignore_exists, + gboolean force, + gboolean *did_exist, + GError **error) { char *src_basename = NULL; char *src_dirname = NULL; char *dest_basename = NULL; char *tmp_dest_basename = NULL; char *dest_dirname = NULL; - GChecksum *id = NULL; DIR *src_dir = NULL; DIR *dest_dir = NULL; gboolean ret = FALSE; - struct stat stbuf; char *dest_path = NULL; src_basename = g_path_get_basename (path); @@ -689,9 +735,7 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type, goto out; } - if (!ostree_stat_and_checksum_file (dirfd (src_dir), path, &id, &stbuf, error)) - goto out; - dest_path = prepare_dir_for_checksum_get_object_path (self, id, type, error); + dest_path = prepare_dir_for_checksum_get_object_path (self, checksum, objtype, error); if (!dest_path) goto out; @@ -719,7 +763,11 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type, ot_util_set_error_from_errno (error, errno); goto out; } + else + *did_exist = TRUE; } + else + *did_exist = FALSE; if (force) { @@ -732,12 +780,8 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type, (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0); } - *out_checksum = id; - id = NULL; ret = TRUE; out: - if (id != NULL) - g_checksum_free (id); if (src_dir != NULL) closedir (src_dir); if (dest_dir != NULL) @@ -750,12 +794,108 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type, return ret; } +static gboolean +archive_file_trusted (OstreeRepo *self, + const char *path, + const char *checksum, + OstreeObjectType objtype, + gboolean ignore_exists, + gboolean force, + gboolean *did_exist, + GError **error) +{ + GFile *infile = NULL; + GFile *outfile = NULL; + GFileOutputStream *out = NULL; + gboolean ret = FALSE; + char *dest_path = NULL; + char *dest_tmp_path = NULL; + + infile = ot_util_new_file_for_path (path); + + dest_path = prepare_dir_for_checksum_get_object_path (self, checksum, objtype, error); + if (!dest_path) + goto out; + + dest_tmp_path = g_strconcat (dest_path, ".tmp", NULL); + + outfile = ot_util_new_file_for_path (dest_tmp_path); + out = g_file_replace (outfile, NULL, FALSE, 0, NULL, error); + if (!out) + goto out; + + if (!ostree_pack_object ((GOutputStream*)out, infile, objtype, NULL, error)) + goto out; + + if (!g_output_stream_close ((GOutputStream*)out, NULL, error)) + goto out; + + if (rename (dest_tmp_path, dest_path) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + + ret = TRUE; + out: + g_free (dest_path); + g_free (dest_tmp_path); + g_clear_object (&infile); + g_clear_object (&outfile); + g_clear_object (&out); + return ret; +} + +gboolean +ostree_repo_store_object_trusted (OstreeRepo *self, + const char *path, + const char *checksum, + OstreeObjectType objtype, + gboolean ignore_exists, + gboolean force, + gboolean *did_exist, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + if (priv->archive && objtype == OSTREE_OBJECT_TYPE_FILE) + return archive_file_trusted (self, path, checksum, objtype, ignore_exists, force, did_exist, error); + else + return link_object_trusted (self, path, checksum, objtype, ignore_exists, force, did_exist, error); +} + +static gboolean +link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type, + gboolean ignore_exists, gboolean force, + GChecksum **out_checksum, + GError **error) +{ + gboolean ret = FALSE; + struct stat stbuf; + GChecksum *id = NULL; + gboolean did_exist; + + if (!ostree_stat_and_checksum_file (-1, path, type, &id, &stbuf, error)) + goto out; + + if (!ostree_repo_store_object_trusted (self, path, g_checksum_get_string (id), type, + ignore_exists, force, &did_exist, error)) + goto out; + + *out_checksum = id; + id = NULL; + ret = TRUE; + out: + if (id != NULL) + g_checksum_free (id); + return ret; +} + gboolean ostree_repo_link_file (OstreeRepo *self, - const char *path, - gboolean ignore_exists, - gboolean force, - GError **error) + const char *path, + gboolean ignore_exists, + gboolean force, + GError **error) { OstreeRepoPrivate *priv = GET_PRIVATE (self); GChecksum *checksum = NULL; @@ -770,6 +910,227 @@ ostree_repo_link_file (OstreeRepo *self, return TRUE; } +static gboolean +unpack_and_checksum_packfile (OstreeRepo *self, + const char *path, + gchar **out_filename, + GChecksum **out_checksum, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + gboolean ret = FALSE; + GFile *file = NULL; + char *temp_path = NULL; + GFile *temp_file = NULL; + GFileOutputStream *temp_out = NULL; + char *metadata_buf = NULL; + GVariant *metadata = NULL; + GVariant *xattrs = NULL; + GFileInputStream *in = NULL; + GChecksum *ret_checksum = NULL; + guint32 metadata_len; + guint32 version, uid, gid, mode; + guint64 content_len; + gsize bytes_read, bytes_written; + char buf[8192]; + int temp_fd = -1; + + file = ot_util_new_file_for_path (path); + + in = g_file_read (file, NULL, error); + if (!in) + goto out; + + if (!g_input_stream_read_all ((GInputStream*)in, &metadata_len, 4, &bytes_read, NULL, error)) + goto out; + if (bytes_read != 4) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted packfile; too short while reading metadata length"); + goto out; + } + + metadata_len = GUINT32_FROM_BE (metadata_len); + metadata_buf = g_malloc (metadata_len); + + if (!g_input_stream_read_all ((GInputStream*)in, metadata_buf, metadata_len, &bytes_read, NULL, error)) + goto out; + if (bytes_read != metadata_len) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted packfile; too short while reading metadata"); + goto out; + } + + metadata = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT), + metadata_buf, metadata_len, FALSE, NULL, NULL); + + g_variant_get (metadata, "(uuuu@a(ayay)t)", + &version, &uid, &gid, &mode, + &xattrs, &content_len); + uid = GUINT32_FROM_BE (uid); + gid = GUINT32_FROM_BE (gid); + mode = GUINT32_FROM_BE (mode); + content_len = GUINT64_FROM_BE (content_len); + + temp_path = g_build_filename (priv->path, "tmp-packfile-XXXXXX"); + temp_file = ot_util_new_file_for_path (temp_path); + + ret_checksum = g_checksum_new (G_CHECKSUM_SHA256); + + if (S_ISREG (mode)) + { + temp_fd = g_mkstemp (temp_path); + if (temp_fd < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + close (temp_fd); + temp_fd = -1; + temp_out = g_file_replace (temp_file, NULL, FALSE, 0, NULL, error); + if (!temp_out) + goto out; + + do + { + if (!g_input_stream_read_all ((GInputStream*)in, buf, sizeof(buf), &bytes_read, NULL, error)) + goto out; + g_checksum_update (ret_checksum, (guint8*)buf, bytes_read); + if (!g_output_stream_write_all ((GOutputStream*)temp_out, buf, bytes_read, &bytes_written, NULL, error)) + goto out; + } + while (bytes_read > 0); + + if (!g_output_stream_close ((GOutputStream*)temp_out, NULL, error)) + goto out; + } + else if (S_ISLNK (mode)) + { + g_assert (sizeof (buf) > PATH_MAX); + + if (!g_input_stream_read_all ((GInputStream*)in, buf, sizeof(buf), &bytes_read, NULL, error)) + goto out; + buf[bytes_read] = '\0'; + if (symlink (buf, temp_path) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + else if (S_ISCHR (mode) || S_ISBLK (mode)) + { + guint32 dev; + + if (!g_input_stream_read_all ((GInputStream*)in, &dev, 4, &bytes_read, NULL, error)) + goto out; + if (bytes_read != 4) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted packfile; too short while reading device id"); + goto out; + } + dev = GUINT32_FROM_BE (dev); + if (mknod (temp_path, mode, dev) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted packfile; invalid mode %u", mode); + goto out; + } + + if (!S_ISLNK (mode)) + { + if (chmod (temp_path, mode) < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + } + + if (!ostree_set_xattrs (temp_path, xattrs, error)) + goto out; + + ostree_checksum_update_stat (ret_checksum, uid, gid, mode); + g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs)); + + ret = TRUE; + *out_checksum = ret_checksum; + ret_checksum = NULL; + *out_filename = temp_path; + temp_path = NULL; + out: + if (ret_checksum) + g_checksum_free (ret_checksum); + g_free (metadata_buf); + if (temp_path) + (void) unlink (temp_path); + g_free (temp_path); + g_clear_object (&file); + g_clear_object (&in); + g_clear_object (&temp_file); + g_clear_object (&temp_out); + if (metadata) + g_variant_unref (metadata); + if (xattrs) + g_variant_unref (xattrs); + return ret; +} + +gboolean +ostree_repo_store_packfile (OstreeRepo *self, + const char *expected_checksum, + const char *path, + OstreeObjectType objtype, + GError **error) +{ + gboolean ret = FALSE; + char *tempfile = NULL; + GChecksum *checksum = NULL; + struct stat stbuf; + gboolean did_exist; + + if (objtype == OSTREE_OBJECT_TYPE_META) + { + if (!ostree_stat_and_checksum_file (-1, path, objtype, &checksum, &stbuf, error)) + goto out; + } + else + { + if (!unpack_and_checksum_packfile (self, path, &tempfile, &checksum, error)) + goto out; + + } + + if (strcmp (g_checksum_get_string (checksum), expected_checksum) != 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Corrupted object %s (actual checksum is %s)", + expected_checksum, g_checksum_get_string (checksum)); + goto out; + } + + if (!ostree_repo_store_object_trusted (self, tempfile ? tempfile : path, + expected_checksum, + objtype, + TRUE, FALSE, &did_exist, error)) + goto out; + + ret = TRUE; + out: + if (tempfile) + (void) unlink (tempfile); + g_free (tempfile); + if (checksum) + g_checksum_free (checksum); + return ret; +} + typedef struct _ParsedTreeData ParsedTreeData; typedef struct _ParsedDirectoryData ParsedDirectoryData; @@ -839,6 +1200,7 @@ parse_tree (OstreeRepo *self, sha256, &tree_variant, error)) goto out; + /* PARSE OSTREE_SERIALIZED_TREE_VARIANT */ g_variant_get (tree_variant, "(u@a{sv}@a(ss)@a(sss))", &version, &meta_variant, &files_variant, &dirs_variant); @@ -922,6 +1284,7 @@ load_commit_and_trees (OstreeRepo *self, commit_sha256, &ret_commit, error)) goto out; + /* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */ g_variant_get_child (ret_commit, 6, "&s", &tree_contents_checksum); g_variant_get_child (ret_commit, 7, "&s", &tree_meta_checksum); @@ -1366,6 +1729,18 @@ add_files_to_tree_and_import (OstreeRepo *self, return ret; } +gboolean +ostree_repo_write_ref (OstreeRepo *self, + gboolean is_local, + const char *name, + const char *rev, + GError **error) +{ + OstreeRepoPrivate *priv = GET_PRIVATE (self); + return write_checksum_file (is_local ? priv->local_heads_dir : priv->remote_heads_dir, + name, rev, error); +} + static gboolean commit_parsed_tree (OstreeRepo *self, const char *branch, @@ -1377,7 +1752,6 @@ commit_parsed_tree (OstreeRepo *self, GChecksum **out_commit, GError **error) { - OstreeRepoPrivate *priv = GET_PRIVATE (self); gboolean ret = FALSE; GChecksum *root_checksum = NULL; GChecksum *ret_commit = NULL; @@ -1403,7 +1777,7 @@ commit_parsed_tree (OstreeRepo *self, commit, &ret_commit, error)) goto out; - if (!write_checksum_file (priv->local_heads_dir, branch, g_checksum_get_string (ret_commit), error)) + if (!ostree_repo_write_ref (self, TRUE, branch, g_checksum_get_string (ret_commit), error)) goto out; ret = TRUE; @@ -1648,7 +2022,8 @@ iter_object_dir (OstreeRepo *self, if (type != G_FILE_TYPE_DIRECTORY && (g_str_has_suffix (name, ".meta") - || g_str_has_suffix (name, ".file"))) + || g_str_has_suffix (name, ".file") + || g_str_has_suffix (name, ".packfile"))) { char *dot; char *path; @@ -1789,6 +2164,7 @@ checkout_one_directory (OstreeRepo *self, dest_path = g_build_filename (destination, dirname, NULL); + /* PARSE OSTREE_SERIALIZED_DIRMETA_VARIANT */ g_variant_get (dir->meta_data, "(uuuu@a(ayay))", &version, &uid, &gid, &mode, &xattr_variant); diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index c0159ea4..15d5036a 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -53,17 +53,50 @@ OstreeRepo* ostree_repo_new (const char *path); gboolean ostree_repo_check (OstreeRepo *self, GError **error); +const char * ostree_repo_get_path (OstreeRepo *self); + +gboolean ostree_repo_is_archive (OstreeRepo *self); + +GKeyFile * ostree_repo_get_config (OstreeRepo *self); + +GKeyFile * ostree_repo_copy_config (OstreeRepo *self); + +gboolean ostree_repo_write_config (OstreeRepo *self, + GKeyFile *new_config, + GError **error); + gboolean ostree_repo_link_file (OstreeRepo *self, - const char *path, - gboolean ignore_exists, - gboolean force, - GError **error); + const char *path, + gboolean ignore_exists, + gboolean force, + GError **error); + +gboolean ostree_repo_store_packfile (OstreeRepo *self, + const char *expected_checksum, + const char *path, + OstreeObjectType objtype, + GError **error); + +gboolean ostree_repo_store_object_trusted (OstreeRepo *self, + const char *path, + const char *checksum, + OstreeObjectType objtype, + gboolean ignore_exists, + gboolean force, + gboolean *did_exist, + GError **error); gboolean ostree_repo_resolve_rev (OstreeRepo *self, const char *rev, char **out_resolved, GError **error); +gboolean ostree_repo_write_ref (OstreeRepo *self, + gboolean is_local, + const char *name, + const char *rev, + GError **error); + gboolean ostree_repo_load_variant (OstreeRepo *self, const char *sha256, OstreeSerializedVariantType *out_type, diff --git a/src/main.c b/src/main.c index f32e741b..ad5dfd8f 100644 --- a/src/main.c +++ b/src/main.c @@ -33,6 +33,7 @@ static OstreeBuiltin builtins[] = { { "commit", ostree_builtin_commit, 0 }, { "link-file", ostree_builtin_link_file, 0 }, { "log", ostree_builtin_log, 0 }, + { "pull", ostree_builtin_pull, 0 }, { "fsck", ostree_builtin_fsck, 0 }, { "remote", ostree_builtin_remote, 0 }, { "rev-parse", ostree_builtin_rev_parse, 0 }, diff --git a/src/ostree-http-backend.c b/src/ostree-http-backend.c new file mode 100644 index 00000000..ebde526d --- /dev/null +++ b/src/ostree-http-backend.c @@ -0,0 +1,45 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; 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 +#include + +#include + +int +main (int argc, + char **argv) +{ + const char *repo_root; + + g_type_init (); + + repo_root = g_getenv ("OSTREE_REPO_PREFIX"); + if (!repo_root) + { + g_printerr ("OSTREE_REPO_PREFIX not set\n"); + return 1; + } + + return 0; +} diff --git a/src/ot-builtin-fsck.c b/src/ot-builtin-fsck.c index 766d17fe..3a217524 100644 --- a/src/ot-builtin-fsck.c +++ b/src/ot-builtin-fsck.c @@ -39,6 +39,82 @@ typedef struct { guint n_objects; } HtFsckData; +static gboolean +checksum_packed_file (HtFsckData *data, + const char *path, + GChecksum **out_checksum, + GError **error) +{ + gboolean ret = FALSE; + GChecksum *ret_checksum = NULL; + GFile *file = NULL; + char *metadata_buf = NULL; + GVariant *metadata = NULL; + GVariant *xattrs = NULL; + GFileInputStream *in = NULL; + guint32 metadata_len; + guint32 version, uid, gid, mode; + guint64 content_len; + gsize bytes_read; + char buf[8192]; + + file = ot_util_new_file_for_path (path); + + in = g_file_read (file, NULL, error); + if (!in) + goto out; + + if (!g_input_stream_read_all ((GInputStream*)in, &metadata_len, 4, &bytes_read, NULL, error)) + goto out; + + metadata_len = GUINT32_FROM_BE (metadata_len); + + metadata_buf = g_malloc (metadata_len); + + if (!g_input_stream_read_all ((GInputStream*)in, metadata_buf, metadata_len, &bytes_read, NULL, error)) + goto out; + + metadata = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT), + metadata_buf, metadata_len, FALSE, NULL, NULL); + + g_variant_get (metadata, "(uuuu@a(ayay)t)", + &version, &uid, &gid, &mode, + &xattrs, &content_len); + uid = GUINT32_FROM_BE (uid); + gid = GUINT32_FROM_BE (gid); + mode = GUINT32_FROM_BE (mode); + content_len = GUINT64_FROM_BE (content_len); + + ret_checksum = g_checksum_new (G_CHECKSUM_SHA256); + + do + { + if (!g_input_stream_read_all ((GInputStream*)in, buf, sizeof(buf), &bytes_read, NULL, error)) + goto out; + g_checksum_update (ret_checksum, (guint8*)buf, bytes_read); + } + while (bytes_read > 0); + + ostree_checksum_update_stat (ret_checksum, uid, gid, mode); + g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs)); + + ret = TRUE; + *out_checksum = ret_checksum; + ret_checksum = NULL; + out: + if (ret_checksum) + g_checksum_free (ret_checksum); + g_free (metadata_buf); + g_clear_object (&file); + g_clear_object (&in); + if (metadata) + g_variant_unref (metadata); + if (xattrs) + g_variant_unref (xattrs); + return ret; +} + + static void object_iter_callback (OstreeRepo *repo, const char *path, @@ -53,25 +129,46 @@ object_iter_callback (OstreeRepo *repo, char *checksum_prefix = NULL; char *checksum_string = NULL; char *filename_checksum = NULL; + gboolean packed = FALSE; + OstreeObjectType objtype; char *dot; - dirname = g_path_get_dirname (path); - checksum_prefix = g_path_get_basename (dirname); - /* nlinks = g_file_info_get_attribute_uint32 (file_info, "unix::nlink"); if (nlinks < 2 && !quiet) g_printerr ("note: floating object: %s\n", path); */ - if (!ostree_stat_and_checksum_file (-1, path, &checksum, &stbuf, &error)) - goto out; + if (g_str_has_suffix (path, ".meta")) + objtype = OSTREE_OBJECT_TYPE_META; + else if (g_str_has_suffix (path, ".file")) + objtype = OSTREE_OBJECT_TYPE_FILE; + else if (g_str_has_suffix (path, ".packfile")) + { + objtype = OSTREE_OBJECT_TYPE_FILE; + packed = TRUE; + } + else + g_assert_not_reached (); + + if (packed && objtype == OSTREE_OBJECT_TYPE_FILE) + { + if (!checksum_packed_file (data, path, &checksum, &error)) + goto out; + } + else + { + if (!ostree_stat_and_checksum_file (-1, path, objtype, &checksum, &stbuf, &error)) + goto out; + } filename_checksum = g_strdup (g_file_info_get_name (file_info)); dot = strrchr (filename_checksum, '.'); g_assert (dot != NULL); *dot = '\0'; - + + dirname = g_path_get_dirname (path); + checksum_prefix = g_path_get_basename (dirname); checksum_string = g_strconcat (checksum_prefix, filename_checksum, NULL); - + if (strcmp (checksum_string, g_checksum_get_string (checksum)) != 0) { g_printerr ("ERROR: corrupted object '%s' expected checksum: %s\n", diff --git a/src/ot-builtin-init.c b/src/ot-builtin-init.c index ef4530b6..d16b57c8 100644 --- a/src/ot-builtin-init.c +++ b/src/ot-builtin-init.c @@ -27,8 +27,11 @@ #include static char *repo_path; +static gboolean archive; + static GOptionEntry options[] = { { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL }, + { "archive", 0, 0, G_OPTION_ARG_NONE, &archive, "Initialize repository as archive", NULL }, { NULL } }; @@ -44,6 +47,7 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error) GFile *repodir = NULL; GFile *child = NULL; GFile *grandchild = NULL; + GString *config_data = NULL; context = g_option_context_new ("- Initialize a new empty repository"); g_option_context_add_main_entries (context, options, NULL); @@ -54,12 +58,15 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error) if (repo_path == NULL) repo_path = "."; - repodir = g_file_new_for_path (repo_path); + repodir = ot_util_new_file_for_path (repo_path); child = g_file_get_child (repodir, "config"); + + config_data = g_string_new (DEFAULT_CONFIG_CONTENTS); + g_string_append_printf (config_data, "archive=%s\n", archive ? "true" : "false"); if (!g_file_replace_contents (child, - DEFAULT_CONFIG_CONTENTS, - strlen (DEFAULT_CONFIG_CONTENTS), + config_data->str, + config_data->len, NULL, FALSE, 0, NULL, NULL, error)) goto out; @@ -73,18 +80,20 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error) child = g_file_get_child (repodir, "refs"); if (!g_file_make_directory (child, NULL, error)) goto out; + grandchild = g_file_get_child (child, "heads"); if (!g_file_make_directory (grandchild, NULL, error)) goto out; - g_clear_object (&child); g_clear_object (&grandchild); - child = g_file_get_child (repodir, "tags"); - if (!g_file_make_directory (child, NULL, error)) + grandchild = g_file_get_child (child, "remotes"); + if (!g_file_make_directory (grandchild, NULL, error)) goto out; + g_clear_object (&grandchild); + g_clear_object (&child); - child = g_file_get_child (repodir, "remotes"); + child = g_file_get_child (repodir, "tags"); if (!g_file_make_directory (child, NULL, error)) goto out; g_clear_object (&child); @@ -93,6 +102,8 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error) out: if (context) g_option_context_free (context); + if (config_data) + g_string_free (config_data, TRUE); g_clear_object (&repodir); g_clear_object (&child); g_clear_object (&grandchild); diff --git a/src/ot-builtin-pull.c b/src/ot-builtin-pull.c new file mode 100644 index 00000000..a9ba6e0e --- /dev/null +++ b/src/ot-builtin-pull.c @@ -0,0 +1,365 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; 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 "ot-builtins.h" +#include "ostree.h" + +#include + +#include + +static char *repo_path; + +static GOptionEntry options[] = { + { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" }, + { NULL } +}; + +static void +usage_error (GOptionContext *context, const char *message, GError **error) +{ + gchar *help = g_option_context_get_help (context, TRUE, NULL); + g_printerr ("%s\n", help); + g_free (help); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + message); +} + +static gboolean +fetch_uri (OstreeRepo *repo, + SoupSession *soup, + SoupURI *uri, + char **temp_filename, + GError **error) +{ + gboolean ret = FALSE; + SoupMessage *msg = NULL; + guint response; + char *template = NULL; + int fd; + SoupBuffer *buf = NULL; + GFile *tempf = NULL; + + msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); + + response = soup_session_send_message (soup, msg); + if (response != 200) + { + char *uri_string = soup_uri_to_string (uri, FALSE); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to retrieve '%s': %d %s", + uri_string, response, msg->reason_phrase); + g_free (uri_string); + goto out; + } + + template = g_strdup_printf ("%s/tmp-fetchXXXXXX", ostree_repo_get_path (repo)); + + fd = g_mkstemp (template); + if (fd < 0) + { + ot_util_set_error_from_errno (error, errno); + goto out; + } + close (fd); + tempf = ot_util_new_file_for_path (template); + + buf = soup_message_body_flatten (msg->response_body); + + if (!g_file_replace_contents (tempf, buf->data, buf->length, NULL, FALSE, 0, NULL, NULL, error)) + goto out; + + *temp_filename = template; + template = NULL; + + ret = TRUE; + out: + g_free (template); + g_clear_object (&msg); + g_clear_object (&tempf); + return ret; +} + +static gboolean +store_object (OstreeRepo *repo, + SoupSession *soup, + SoupURI *baseuri, + const char *object, + OstreeObjectType objtype, + gboolean *did_exist, + GError **error) +{ + gboolean ret = FALSE; + char *filename = NULL; + char *objpath = NULL; + char *relpath = NULL; + SoupURI *obj_uri = NULL; + + objpath = ostree_get_relative_object_path (object, objtype, TRUE); + obj_uri = soup_uri_copy (baseuri); + relpath = g_build_filename (soup_uri_get_path (obj_uri), objpath, NULL); + soup_uri_set_path (obj_uri, relpath); + + if (!fetch_uri (repo, soup, obj_uri, &filename, error)) + goto out; + + if (!ostree_repo_store_packfile (repo, object, filename, objtype, error)) + goto out; + + ret = TRUE; + out: + if (obj_uri) + soup_uri_free (obj_uri); + if (filename) + (void) unlink (filename); + g_free (filename); + g_free (objpath); + g_free (relpath); + return ret; +} + +static gboolean +store_tree_recurse (OstreeRepo *repo, + SoupSession *soup, + SoupURI *base_uri, + const char *rev, + GError **error) +{ + gboolean ret = FALSE; + GVariant *tree = NULL; + GVariant *files_variant = NULL; + GVariant *dirs_variant = NULL; + OstreeSerializedVariantType metatype; + gboolean did_exist; + int i, n; + + if (!store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_META, &did_exist, error)) + goto out; + + if (!did_exist) + { + if (!ostree_repo_load_variant (repo, rev, &metatype, &tree, error)) + goto out; + + if (metatype != OSTREE_SERIALIZED_TREE_VARIANT) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Tree metadata '%s' has wrong type %d, expected %d", + rev, metatype, OSTREE_SERIALIZED_TREE_VARIANT); + goto out; + } + + /* PARSE OSTREE_SERIALIZED_TREE_VARIANT */ + g_variant_get_child (tree, 2, "@a(ss)", &files_variant); + g_variant_get_child (tree, 3, "@a(sss)", &dirs_variant); + + n = g_variant_n_children (files_variant); + for (i = 0; i < n; i++) + { + const char *filename; + const char *checksum; + + g_variant_get_child (files_variant, i, "(ss)", &filename, &checksum); + + if (!store_object (repo, soup, base_uri, checksum, OSTREE_OBJECT_TYPE_FILE, &did_exist, error)) + goto out; + } + + for (i = 0; i < n; i++) + { + const char *dirname; + const char *tree_checksum; + const char *meta_checksum; + + g_variant_get_child (dirs_variant, i, "(sss)", + &dirname, &tree_checksum, &meta_checksum); + + if (!store_tree_recurse (repo, soup, base_uri, tree_checksum, error)) + goto out; + + if (!store_object (repo, soup, base_uri, meta_checksum, OSTREE_OBJECT_TYPE_META, &did_exist, error)) + goto out; + } + } + + ret = TRUE; + out: + if (tree) + g_variant_unref (tree); + if (files_variant) + g_variant_unref (files_variant); + if (dirs_variant) + g_variant_unref (dirs_variant); + return ret; +} + +static gboolean +store_commit_recurse (OstreeRepo *repo, + SoupSession *soup, + SoupURI *base_uri, + const char *rev, + GError **error) +{ + gboolean ret = FALSE; + GVariant *commit = NULL; + OstreeSerializedVariantType metatype; + const char *tree_contents_checksum; + const char *tree_meta_checksum; + gboolean did_exist; + + if (!store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_META, &did_exist, error)) + goto out; + + if (!did_exist) + { + if (!ostree_repo_load_variant (repo, rev, &metatype, &commit, error)) + goto out; + + if (metatype != OSTREE_SERIALIZED_COMMIT_VARIANT) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Commit '%s' has wrong type %d, expected %d", + rev, metatype, OSTREE_SERIALIZED_COMMIT_VARIANT); + goto out; + } + + /* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */ + g_variant_get_child (commit, 6, "&s", &tree_contents_checksum); + g_variant_get_child (commit, 7, "&s", &tree_meta_checksum); + + if (!store_object (repo, soup, base_uri, tree_meta_checksum, OSTREE_OBJECT_TYPE_META, &did_exist, error)) + goto out; + + if (!store_tree_recurse (repo, soup, base_uri, tree_contents_checksum, error)) + goto out; + } + + ret = TRUE; + out: + if (commit) + g_variant_unref (commit); + return ret; +} + +gboolean +ostree_builtin_pull (int argc, char **argv, const char *prefix, GError **error) +{ + GOptionContext *context; + gboolean ret = FALSE; + OstreeRepo *repo = NULL; + const char *remote; + const char *branch; + char *remote_branch_ref_path = NULL; + char *key = NULL; + char *baseurl = NULL; + char *refpath = NULL; + char *temppath = NULL; + GKeyFile *config = NULL; + SoupURI *base_uri = NULL; + SoupURI *target_uri = NULL; + SoupSession *soup = NULL; + char *rev = NULL; + + context = g_option_context_new ("REMOTE BRANCH - Download data from remote repository"); + g_option_context_add_main_entries (context, options, NULL); + + if (!g_option_context_parse (context, &argc, &argv, error)) + goto out; + + if (repo_path == NULL) + repo_path = "."; + + repo = ostree_repo_new (repo_path); + if (!ostree_repo_check (repo, error)) + goto out; + + if (argc < 3) + { + usage_error (context, "REMOTE and BRANCH must be specified", error); + goto out; + } + + remote = argv[1]; + branch = argv[2]; + + config = ostree_repo_get_config (repo); + + key = g_strdup_printf ("remote \"%s\"", remote); + baseurl = g_key_file_get_string (config, key, "url", error); + if (!baseurl) + goto out; + base_uri = soup_uri_new (baseurl); + if (!base_uri) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to parse url '%s'", baseurl); + goto out; + } + target_uri = soup_uri_copy (base_uri); + g_free (refpath); + refpath = g_build_filename (soup_uri_get_path (target_uri), "refs", "heads", branch, NULL); + soup_uri_set_path (target_uri, refpath); + + soup = soup_session_sync_new_with_options (SOUP_SESSION_USER_AGENT, "ostree ", + SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_GNOME_FEATURES_2_26, + SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER, + SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_COOKIE_JAR, + NULL); + if (!fetch_uri (repo, soup, target_uri, &temppath, error)) + goto out; + + rev = ot_util_get_file_contents_utf8 (temppath, error); + if (!rev) + goto out; + g_strchomp (rev); + + if (!ostree_validate_checksum_string (rev, error)) + goto out; + + if (!store_commit_recurse (repo, soup, base_uri, rev, error)) + goto out; + + if (!ostree_repo_write_ref (repo, FALSE, branch, rev, error)) + goto out; + + ret = TRUE; + out: + if (context) + g_option_context_free (context); + if (temppath) + (void) unlink (temppath); + g_free (temppath); + g_free (key); + g_free (rev); + g_free (baseurl); + g_free (refpath); + g_free (remote_branch_ref_path); + g_clear_object (&soup); + if (base_uri) + soup_uri_free (base_uri); + if (target_uri) + soup_uri_free (target_uri); + g_clear_object (&repo); + g_clear_object (&soup); + return ret; +} diff --git a/src/ot-builtin-remote.c b/src/ot-builtin-remote.c index f26d683a..40f7c295 100644 --- a/src/ot-builtin-remote.c +++ b/src/ot-builtin-remote.c @@ -51,9 +51,6 @@ ostree_builtin_remote (int argc, char **argv, const char *prefix, GError **error OstreeRepo *repo = NULL; OstreeCheckout *checkout = NULL; const char *op; - gsize len; - char *config_path = NULL; - char *data = NULL; GKeyFile *config = NULL; context = g_option_context_new ("OPERATION [args] - Control remote repository configuration"); @@ -77,10 +74,7 @@ ostree_builtin_remote (int argc, char **argv, const char *prefix, GError **error op = argv[1]; - config = g_key_file_new (); - config_path = g_build_filename (repo_path, "config", NULL); - if (!g_key_file_load_from_file (config, config_path, 0, error)) - goto out; + config = ostree_repo_copy_config (repo); if (!strcmp (op, "add")) { @@ -92,25 +86,23 @@ ostree_builtin_remote (int argc, char **argv, const char *prefix, GError **error } key = g_strdup_printf ("remote \"%s\"", argv[2]); g_key_file_set_string (config, key, "url", argv[3]); + g_free (key); } else { usage_error (context, "Unknown operation", error); goto out; } - - data = g_key_file_to_data (config, &len, error); - if (!g_file_set_contents (config_path, data, len, error)) - goto out; + if (!ostree_repo_write_config (repo, config, error)) + goto out; + ret = TRUE; out: if (context) g_option_context_free (context); if (config) g_key_file_unref (config); - g_free (data); - g_free (config_path); g_clear_object (&repo); g_clear_object (&checkout); return ret; diff --git a/src/ot-builtins.h b/src/ot-builtins.h index 1da3db9b..1373f601 100644 --- a/src/ot-builtins.h +++ b/src/ot-builtins.h @@ -41,6 +41,7 @@ gboolean ostree_builtin_commit (int argc, char **argv, const char *prefix, GErro gboolean ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error); gboolean ostree_builtin_log (int argc, char **argv, const char *prefix, GError **error); gboolean ostree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error); +gboolean ostree_builtin_pull (int argc, char **argv, const char *prefix, GError **error); gboolean ostree_builtin_run_triggers (int argc, char **argv, const char *prefix, GError **error); gboolean ostree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error); gboolean ostree_builtin_show (int argc, char **argv, const char *prefix, GError **error); diff --git a/tests/.gitignore b/tests/.gitignore index 24600083..8a94c781 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1 +1,4 @@ !Makefile +ostree-http-server +run-apache +tmpdir-lifecycle diff --git a/tests/Makefile b/tests/Makefile index c24db2c7..6beba04e 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -20,7 +20,13 @@ TESTS = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) -all: +all: tmpdir-lifecycle run-apache + +tmpdir-lifecycle: tmpdir-lifecycle.c Makefile + gcc $(CFLAGS) `pkg-config --cflags --libs gio-unix-2.0` -o $@ $< + +run-apache: run-apache.c Makefile + gcc $(CFLAGS) `pkg-config --cflags --libs gio-unix-2.0` -o $@ $< check: @for test in $(TESTS); do \ diff --git a/tests/libtest.sh b/tests/libtest.sh index cedf5370..9601ec27 100644 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -18,6 +18,9 @@ # # Author: Colin Walters +cd `dirname $0` +SRCDIR=`pwd` +cd - TMPDIR=${TMPDIR:-/tmp} export TMPDIR test_tmpdir=`mktemp -d "$TMPDIR/ostree-tests.XXXXXXXXXX"` @@ -91,4 +94,53 @@ setup_test_repository2 () { ostree fsck -q $ot_repo } +setup_fake_remote_repo1() { + oldpwd=`pwd` + mkdir ostree-srv + cd ostree-srv + mkdir gnomerepo + ostree init --archive --repo=gnomerepo + mkdir gnomerepo-files + cd gnomerepo-files + echo first > firstfile + mkdir baz + echo moo > baz/cow + echo alien > baz/saucer + find | grep -v '^\.$' | ostree commit --repo=${test_tmpdir}/ostree-srv/gnomerepo -b main -s "A remote commit" -m "Some Commit body" --from-stdin + mkdir baz/deeper + ostree commit --repo=${test_tmpdir}/ostree-srv/gnomerepo -b main -s "Add deeper" --add=baz/deeper + echo hi > baz/deeper/ohyeah + mkdir baz/another/ + echo x > baz/another/y + find | grep -v '^\.$' | ostree commit --repo=${test_tmpdir}/ostree-srv/gnomerepo -b main -s "The rest" --from-stdin + cd .. + rm -rf gnomerepo-files + + cd ${test_tmpdir} + mkdir ${test_tmpdir}/httpd + cd httpd + cp $(command -v ostree-http-backend) . + chmod a+x ostree-http-backend + cat >httpd.conf < + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Colin Walters + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Taken from gnome-user-share src/httpd.c under the GPLv2 */ +static int +get_port (void) +{ + int sock; + int saved_errno; + struct sockaddr_in addr; + int reuse; + socklen_t len; + + sock = socket (PF_INET, SOCK_STREAM, 0); + if (sock < 0) + { + return -1; + } + + memset (&addr, 0, sizeof (addr)); + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + + reuse = 1; + setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse)); + if (bind (sock, (struct sockaddr *)&addr, sizeof (addr)) == -1) + { + saved_errno = errno; + close (sock); + errno = saved_errno; + return -1; + } + + len = sizeof (addr); + if (getsockname (sock, (struct sockaddr *)&addr, &len) == -1) + { + saved_errno = errno; + close (sock); + errno = saved_errno; + return -1; + } + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) + /* XXX This exposes a potential race condition, but without this, + * httpd will not start on the above listed platforms due to the fact + * that SO_REUSEADDR is also needed when Apache binds to the listening + * socket. At this time, Apache does not support that socket option. + */ + close (sock); +#endif + return ntohs (addr.sin_port); +} + +static const char *known_httpd_modules_locations [] = { + "/usr/libexec/apache2", + "/usr/lib/apache2/modules", + "/usr/lib64/httpd/modules", + "/usr/lib/httpd/modules", + NULL +}; + +static gchar* +get_httpd_modules_path () +{ + int i; + + for (i = 0; known_httpd_modules_locations[i]; i++) + { + if (g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_EXECUTABLE) + && g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_DIR)) + { + return g_strdup (known_httpd_modules_locations[i]); + } + } + return NULL; +} + +int +main (int argc, + char **argv) +{ + int port; + char *listen; + char *address_string; + GError *error = NULL; + GPtrArray *httpd_argv; + char *modules; + + if (argc != 3) + { + fprintf (stderr, "usage: run-apache CONF PORTFILE"); + return 1; + } + + g_type_init (); + + port = get_port (); + if (port == -1) + { + perror ("Failed to bind port"); + return 1; + } + + httpd_argv = g_ptr_array_new (); + g_ptr_array_add (httpd_argv, "httpd"); + g_ptr_array_add (httpd_argv, "-f"); + g_ptr_array_add (httpd_argv, argv[1]); + g_ptr_array_add (httpd_argv, "-C"); + listen = g_strdup_printf ("Listen 127.0.0.1:%d", port); + g_ptr_array_add (httpd_argv, listen); + g_ptr_array_add (httpd_argv, NULL); + + address_string = g_strdup_printf ("http://127.0.0.1:%d\n", port); + + if (!g_file_set_contents (argv[2], address_string, -1, &error)) + { + g_printerr ("%s\n", error->message); + return 1; + } + + setenv ("LANG", "C", 1); + modules = get_httpd_modules_path (); + if (modules == NULL) + { + g_printerr ("Failed to find httpd modules\n"); + return 1; + } + if (symlink (modules, "modules") < 0) + { + perror ("failed to make modules symlink"); + return 1; + } + execvp ("httpd", (char**)httpd_argv->pdata); + perror ("Failed to run httpd"); + return 1; +} diff --git a/tests/t0012-pull.sh b/tests/t0012-pull.sh new file mode 100755 index 00000000..a0161a44 --- /dev/null +++ b/tests/t0012-pull.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Copyright (C) 2011 Colin Walters +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# Author: Colin Walters + +set -e + +. libtest.sh + +echo '1..1' + +setup_fake_remote_repo1 +cd ${test_tmpdir} +mkdir repo +ostree init --repo=repo +ostree remote --repo=repo add origin $(cat httpd-address)/ostree/gnomerepo +ostree pull --repo=repo origin main +echo "ok pull" diff --git a/tests/t0013-commit-archive.sh b/tests/t0013-commit-archive.sh new file mode 100755 index 00000000..7f6ffc9b --- /dev/null +++ b/tests/t0013-commit-archive.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# +# Copyright (C) 2011 Colin Walters +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# Author: Colin Walters + +set -e + +. libtest.sh + +echo '1..4' + +mkdir files +cd files +echo first > firstfile +echo second > secondfile +ln -s foo bar + +mkdir ../repo +repo="--repo=../repo" +ostree init --archive $repo +echo 'ok init' +ostree commit $repo -b test -s "Test Commit 1" -m "Commit body first" --add=firstfile +echo 'ok commit 1' +ostree commit $repo -b test -s "Test Commit 2" -m "Commit body first" --add=secondfile --add=bar +echo 'ok commit 2' +ostree fsck -q $repo +echo 'ok fsck' diff --git a/tests/ostree-http-server.c b/tests/tmpdir-lifecycle.c similarity index 52% rename from tests/ostree-http-server.c rename to tests/tmpdir-lifecycle.c index 63edda56..95aae633 100644 --- a/tests/ostree-http-server.c +++ b/tests/tmpdir-lifecycle.c @@ -1,4 +1,6 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Kill a child process when the current directory is deleted * * Copyright (C) 2011 Colin Walters * @@ -19,35 +21,16 @@ * Author: Colin Walters */ -#include -#include +#include #include #include +#include -static void -request_callback (SoupServer *server, SoupMessage *msg, - const char *path, GHashTable *query, - SoupClientContext *context, gpointer data) -{ - if (msg->method == SOUP_METHOD_GET) - { - GFile *file; - char *content; - gsize len; - - /* Strip leading / */ - file = g_file_new_for_path (path + 1); - - if (g_file_load_contents (file, NULL, &content, &len, NULL, NULL)) - soup_message_set_response (msg, "application/octet-stream", SOUP_MEMORY_TAKE, content, len); - else - soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); - } - else - { - soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED); - } -} +struct TmpdirLifecyleData { + GMainLoop *loop; + GPid pid; + gboolean exited; +}; static void on_dir_changed (GFileMonitor *mon, @@ -56,49 +39,65 @@ on_dir_changed (GFileMonitor *mon, GFileMonitorEvent event, gpointer user_data) { - GMainLoop *loop = user_data; + struct TmpdirLifecyleData *data = user_data; if (event == G_FILE_MONITOR_EVENT_DELETED) - g_main_loop_quit (loop); + g_main_loop_quit (data->loop); +} + +static void +on_child_exited (GPid pid, + int status, + gpointer user_data) +{ + struct TmpdirLifecyleData *data = user_data; + + data->exited = TRUE; + g_main_loop_quit (data->loop); } int main (int argc, char **argv) { - SoupAddress *addr; - SoupServer *server; - GMainLoop *loop; GFileMonitor *monitor; GFile *curdir; GError *error = NULL; + GPtrArray *new_argv; + int i; + GPid pid; + struct TmpdirLifecyleData data; g_type_init (); - loop = g_main_loop_new (NULL, TRUE); + memset (&data, 0, sizeof (data)); + + data.loop = g_main_loop_new (NULL, TRUE); curdir = g_file_new_for_path ("."); monitor = g_file_monitor_directory (curdir, 0, NULL, &error); if (!monitor) exit (1); g_signal_connect (monitor, "changed", - G_CALLBACK (on_dir_changed), loop); + G_CALLBACK (on_dir_changed), &data); - addr = soup_address_new ("127.0.0.1", SOUP_ADDRESS_ANY_PORT); - soup_address_resolve_sync (addr, NULL); + new_argv = g_ptr_array_new (); + for (i = 1; i < argc; i++) + g_ptr_array_add (new_argv, argv[i]); + g_ptr_array_add (new_argv, NULL); - server = soup_server_new (SOUP_SERVER_INTERFACE, addr, - SOUP_SERVER_ASYNC_CONTEXT, g_main_loop_get_context (loop), - NULL); - soup_server_add_handler (server, NULL, - request_callback, - NULL, NULL); + if (!g_spawn_async (NULL, (char**)new_argv->pdata, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, &pid, &error)) + { + g_printerr ("%s\n", error->message); + return 1; + } + g_child_watch_add (pid, on_child_exited, &data); - soup_server_run_async (server); + g_main_loop_run (data.loop); - g_print ("http://127.0.0.1:%ld\n", (long)soup_server_get_port (server)); - - g_main_loop_run (loop); + if (!data.exited) + kill (data.pid, SIGTERM); return 0; }