2014-01-24 22:34:19 +04:00
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright ( C ) 2013 , 2014 Colin Walters < walters @ verbum . org >
*
* This program 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 licence 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 <string.h>
# include <glib-unix.h>
2014-02-13 03:26:31 +04:00
# include <json-glib/json-glib.h>
2014-05-03 18:05:43 +04:00
# include <gio/gunixoutputstream.h>
2016-06-09 05:09:00 +03:00
# include <libhif/libhif.h>
2014-11-10 04:28:10 +03:00
# include <stdio.h>
2015-02-15 21:56:14 +03:00
# include <libglnx.h>
2015-01-02 01:44:57 +03:00
# include <rpm/rpmmacro.h>
2014-01-24 22:34:19 +04:00
2014-05-26 23:05:08 +04:00
# include "rpmostree-compose-builtins.h"
2014-05-16 01:46:51 +04:00
# include "rpmostree-util.h"
2016-02-09 18:38:38 +03:00
# include "rpmostree-core.h"
2014-11-13 22:53:43 +03:00
# include "rpmostree-json-parsing.h"
2014-01-29 23:37:44 +04:00
# include "rpmostree-postprocess.h"
2014-11-20 17:40:35 +03:00
# include "rpmostree-passwd-util.h"
2015-10-26 16:36:22 +03:00
# include "rpmostree-libbuiltin.h"
2015-04-19 19:10:32 +03:00
# include "rpmostree-rpm-util.h"
2014-01-29 23:37:44 +04:00
2014-01-24 22:34:19 +04:00
# include "libgsystem.h"
static char * opt_workdir ;
2014-07-02 06:07:56 +04:00
static gboolean opt_workdir_tmpfs ;
2014-05-03 14:55:35 +04:00
static char * opt_cachedir ;
2015-01-03 05:54:23 +03:00
static gboolean opt_force_nocache ;
2014-05-03 14:55:35 +04:00
static char * opt_proxy ;
2014-10-24 01:14:14 +04:00
static char * opt_output_repodata_dir ;
2014-10-14 17:32:29 +04:00
static char * * opt_metadata_strings ;
2014-05-03 14:55:35 +04:00
static char * opt_repo ;
2014-07-11 22:02:45 +04:00
static char * * opt_override_pkg_repos ;
2015-01-29 00:34:29 +03:00
static char * opt_touch_if_changed ;
2016-03-01 23:38:45 +03:00
static gboolean opt_dry_run ;
2014-05-03 18:05:43 +04:00
static gboolean opt_print_only ;
2014-01-24 22:34:19 +04:00
static GOptionEntry option_entries [ ] = {
2014-10-14 17:32:29 +04:00
{ " add-metadata-string " , 0 , 0 , G_OPTION_ARG_STRING_ARRAY , & opt_metadata_strings , " Append given key and value (in string format) to metadata " , " KEY=VALUE " } ,
2014-05-03 14:55:35 +04:00
{ " workdir " , 0 , 0 , G_OPTION_ARG_STRING , & opt_workdir , " Working directory " , " WORKDIR " } ,
2014-07-02 06:07:56 +04:00
{ " workdir-tmpfs " , 0 , 0 , G_OPTION_ARG_NONE , & opt_workdir_tmpfs , " Use tmpfs for working state " , NULL } ,
2014-10-24 01:14:14 +04:00
{ " output-repodata-dir " , 0 , 0 , G_OPTION_ARG_STRING , & opt_output_repodata_dir , " Save downloaded repodata in DIR " , " DIR " } ,
2014-05-03 14:55:35 +04:00
{ " cachedir " , 0 , 0 , G_OPTION_ARG_STRING , & opt_cachedir , " Cached state " , " CACHEDIR " } ,
2015-01-03 05:54:23 +03:00
{ " force-nocache " , 0 , 0 , G_OPTION_ARG_NONE , & opt_force_nocache , " Always create a new OSTree commit, even if nothing appears to have changed " , NULL } ,
2014-05-03 14:55:35 +04:00
{ " repo " , ' r ' , 0 , G_OPTION_ARG_STRING , & opt_repo , " Path to OSTree repository " , " REPO " } ,
2014-07-11 22:02:45 +04:00
{ " add-override-pkg-repo " , 0 , 0 , G_OPTION_ARG_STRING_ARRAY , & opt_override_pkg_repos , " Include an additional package repository from DIRECTORY " , " DIRECTORY " } ,
2014-05-03 14:55:35 +04:00
{ " proxy " , 0 , 0 , G_OPTION_ARG_STRING , & opt_proxy , " HTTP proxy " , " PROXY " } ,
2015-01-29 00:34:29 +03:00
{ " touch-if-changed " , 0 , 0 , G_OPTION_ARG_STRING , & opt_touch_if_changed , " Update the modification time on FILE if a new commit was created " , " FILE " } ,
2016-03-01 23:38:45 +03:00
{ " dry-run " , 0 , 0 , G_OPTION_ARG_NONE , & opt_dry_run , " Just print the transaction and exit " , NULL } ,
2014-05-03 18:05:43 +04:00
{ " print-only " , 0 , 0 , G_OPTION_ARG_NONE , & opt_print_only , " Just expand any includes and print treefile " , NULL } ,
2014-01-24 22:34:19 +04:00
{ NULL }
} ;
2015-01-07 09:30:27 +03:00
/* FIXME: This is a copy of ot_admin_checksum_version */
static char *
checksum_version ( GVariant * checksum )
{
2016-07-15 06:12:37 +03:00
g_autoptr ( GVariant ) metadata = NULL ;
2015-01-07 09:30:27 +03:00
const char * ret = NULL ;
metadata = g_variant_get_child_value ( checksum , 0 ) ;
if ( ! g_variant_lookup ( metadata , " version " , " &s " , & ret ) )
return NULL ;
return g_strdup ( ret ) ;
}
2014-05-18 22:13:31 +04:00
typedef struct {
GPtrArray * treefile_context_dirs ;
2014-11-17 04:05:47 +03:00
GFile * workdir ;
2015-02-15 21:56:14 +03:00
int workdir_dfd ;
2016-02-10 15:47:32 +03:00
int cachedir_dfd ;
2015-01-03 05:54:23 +03:00
OstreeRepo * repo ;
2016-02-10 11:25:58 +03:00
char * ref ;
2015-01-03 05:54:23 +03:00
char * previous_checksum ;
2014-09-07 20:38:34 +04:00
GBytes * serialized_treefile ;
2014-05-18 22:13:31 +04:00
} RpmOstreeTreeComposeContext ;
2015-01-03 05:54:23 +03:00
static gboolean
compute_checksum_from_treefile_and_goal ( RpmOstreeTreeComposeContext * self ,
HyGoal goal ,
2016-04-04 14:48:08 +03:00
GFile * contextdir ,
JsonArray * add_files ,
2015-01-03 05:54:23 +03:00
char * * out_checksum ,
GError * * error )
{
gboolean ret = FALSE ;
2016-07-15 06:12:37 +03:00
g_autofree char * ret_checksum = NULL ;
2015-01-03 05:54:23 +03:00
GChecksum * checksum = g_checksum_new ( G_CHECKSUM_SHA256 ) ;
/* Hash in the raw treefile; this means reordering the input packages
* or adding a comment will cause a recompose , but let ' s be conservative
* here .
*/
{ gsize len ;
const guint8 * buf = g_bytes_get_data ( self - > serialized_treefile , & len ) ;
g_checksum_update ( checksum , buf , len ) ;
}
2016-04-04 14:48:08 +03:00
if ( add_files )
{
guint i , len = json_array_get_length ( add_files ) ;
for ( i = 0 ; i < len ; i + + )
{
2016-07-15 06:12:37 +03:00
g_autoptr ( GFile ) srcfile = NULL ;
2016-04-04 14:48:08 +03:00
const char * src , * dest ;
JsonArray * add_el = json_array_get_array_element ( add_files , i ) ;
2016-07-15 06:12:37 +03:00
g_autoptr ( GFile ) child = NULL ;
2016-04-04 14:48:08 +03:00
if ( ! add_el )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Element in add-files is not an array " ) ;
goto out ;
}
src = _rpmostree_jsonutil_array_require_string_element ( add_el , 0 , error ) ;
if ( ! src )
goto out ;
dest = _rpmostree_jsonutil_array_require_string_element ( add_el , 1 , error ) ;
if ( ! dest )
goto out ;
srcfile = g_file_resolve_relative_path ( contextdir , src ) ;
if ( ! _rpmostree_util_update_checksum_from_file ( checksum ,
srcfile ,
NULL ,
error ) )
goto out ;
g_checksum_update ( checksum , ( const guint8 * ) dest , strlen ( dest ) ) ;
}
}
2015-01-03 05:54:23 +03:00
/* FIXME; we should also hash the post script */
/* Hash in each package */
2016-02-09 21:13:28 +03:00
rpmostree_hif_add_checksum_goal ( checksum , goal ) ;
2015-01-03 05:54:23 +03:00
ret_checksum = g_strdup ( g_checksum_get_string ( checksum ) ) ;
ret = TRUE ;
2016-04-04 14:48:08 +03:00
out :
2015-01-03 05:54:23 +03:00
gs_transfer_out_value ( out_checksum , & ret_checksum ) ;
if ( checksum ) g_checksum_free ( checksum ) ;
return ret ;
}
2015-01-02 01:32:40 +03:00
static void
on_hifstate_percentage_changed ( HifState * hifstate ,
guint percentage ,
gpointer user_data )
{
const char * text = user_data ;
2015-02-18 00:54:54 +03:00
glnx_console_progress_text_percent ( text , percentage ) ;
2015-01-02 01:32:40 +03:00
}
2016-02-10 11:25:58 +03:00
static gboolean
set_keyfile_string_array_from_json ( GKeyFile * keyfile ,
const char * keyfile_group ,
const char * keyfile_key ,
JsonArray * a ,
GError * * error )
{
gboolean ret = FALSE ;
guint len = json_array_get_length ( a ) ;
guint i ;
g_autoptr ( GPtrArray ) instlangs_v = g_ptr_array_new ( ) ;
for ( i = 0 ; i < len ; i + + )
{
const char * elt = _rpmostree_jsonutil_array_require_string_element ( a , i , error ) ;
if ( ! elt )
goto out ;
g_ptr_array_add ( instlangs_v , ( char * ) elt ) ;
}
g_key_file_set_string_list ( keyfile , keyfile_group , keyfile_key ,
( const char * const * ) instlangs_v - > pdata , instlangs_v - > len ) ;
ret = TRUE ;
out :
return ret ;
}
2014-02-13 03:26:31 +04:00
static gboolean
2015-01-01 02:18:33 +03:00
install_packages_in_root ( RpmOstreeTreeComposeContext * self ,
2016-04-28 15:59:20 +03:00
RpmOstreeContext * ctx ,
2015-01-01 02:18:33 +03:00
JsonObject * treedata ,
GFile * yumroot ,
char * * packages ,
2015-01-03 05:54:23 +03:00
gboolean * out_unmodified ,
char * * out_new_inputhash ,
2015-01-01 02:18:33 +03:00
GCancellable * cancellable ,
GError * * error )
2014-01-24 22:34:19 +04:00
{
2014-02-13 03:26:31 +04:00
gboolean ret = FALSE ;
2015-01-02 01:32:40 +03:00
guint progress_sigid ;
2015-01-01 02:18:33 +03:00
GFile * contextdir = self - > treefile_context_dirs - > pdata [ 0 ] ;
2016-02-09 21:13:28 +03:00
g_autoptr ( RpmOstreeInstall ) hifinstall = { 0 , } ;
HifContext * hifctx ;
2016-07-15 06:12:37 +03:00
g_autofree char * ret_new_inputhash = NULL ;
2016-02-10 11:25:58 +03:00
g_autoptr ( GKeyFile ) treespec = g_key_file_new ( ) ;
JsonArray * enable_repos = NULL ;
2016-04-04 14:48:08 +03:00
JsonArray * add_files = NULL ;
2015-01-01 02:18:33 +03:00
2016-02-10 15:47:32 +03:00
/* TODO - uncomment this once we have SELinux working */
#if 0
g_autofree char * cache_repo_pathstr = glnx_fdrel_abspath ( self - > cachedir_dfd , " repo " ) ;
g_autoptr ( GFile ) cache_repo_path = g_file_new_for_path ( cache_repo_pathstr ) ;
glnx_unref_object OstreeRepo * ostreerepo = ostree_repo_new ( cache_repo_path ) ;
if ( ! g_file_test ( cache_repo_pathstr , G_FILE_TEST_EXISTS ) )
{
if ( ! ostree_repo_create ( ostreerepo , OSTREE_REPO_MODE_BARE_USER , cancellable , error ) )
goto out ;
}
# endif
2016-02-09 21:13:28 +03:00
hifctx = rpmostree_context_get_hif ( ctx ) ;
2016-01-10 17:11:04 +03:00
if ( opt_proxy )
hif_context_set_http_proxy ( hifctx , opt_proxy ) ;
2016-02-09 21:13:28 +03:00
2016-06-18 14:58:02 +03:00
/* Hack this here... see https://github.com/rpm-software-management/libhif/issues/53
* but in the future we won ' t be using librpm at all for unpack / scripts , so it won ' t
* matter .
*/
{ const char * debuglevel = getenv ( " RPMOSTREE_RPM_VERBOSITY " ) ;
if ( ! debuglevel )
debuglevel = " info " ;
hif_context_set_rpm_verbosity ( hifctx , debuglevel ) ;
rpmlogSetFile ( NULL ) ;
}
2015-01-01 02:18:33 +03:00
hif_context_set_repo_dir ( hifctx , gs_file_get_path_cached ( contextdir ) ) ;
2016-06-18 15:13:26 +03:00
/* By default, retain packages in addition to metadata with --cachedir */
if ( opt_cachedir )
hif_context_set_keep_cache ( hifctx , TRUE ) ;
2016-02-10 11:25:58 +03:00
g_key_file_set_string ( treespec , " tree " , " ref " , self - > ref ) ;
g_key_file_set_string_list ( treespec , " tree " , " packages " , ( const char * const * ) packages , g_strv_length ( packages ) ) ;
2015-01-02 01:44:57 +03:00
2016-02-10 11:25:58 +03:00
/* Some awful code to translate between JSON and GKeyFile */
if ( json_object_has_member ( treedata , " install-langs " ) )
{
JsonArray * a = json_object_get_array_member ( treedata , " install-langs " ) ;
if ( ! set_keyfile_string_array_from_json ( treespec , " tree " , " install-langs " , a , error ) )
goto out ;
}
2014-01-26 19:13:45 +04:00
2015-01-01 02:18:33 +03:00
/* Bind the json \"repos\" member to the hif state, which looks at the
* enabled = member of the repos file . By default we forcibly enable
* only repos which are specified , ignoring the enabled = flag .
*/
2016-02-10 11:25:58 +03:00
if ( ! json_object_has_member ( treedata , " repos " ) )
{
g_set_error_literal ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Treefile is missing required \" repos \" member " ) ;
goto out ;
}
2014-02-13 03:26:31 +04:00
2016-02-10 11:25:58 +03:00
enable_repos = json_object_get_array_member ( treedata , " repos " ) ;
2016-02-09 21:13:28 +03:00
2016-02-10 11:25:58 +03:00
if ( ! set_keyfile_string_array_from_json ( treespec , " tree " , " repos " , enable_repos , error ) )
goto out ;
2014-01-26 19:13:45 +04:00
2015-01-17 01:23:17 +03:00
{ gboolean docs = TRUE ;
if ( ! _rpmostree_jsonutil_object_get_optional_boolean_member ( treedata ,
" documentation " ,
& docs ,
error ) )
goto out ;
if ( ! docs )
2016-02-10 11:25:58 +03:00
g_key_file_set_boolean ( treespec , " tree " , " documentation " , FALSE ) ;
}
{ g_autoptr ( GError ) tmp_error = NULL ;
g_autoptr ( RpmOstreeTreespec ) treespec_value = rpmostree_treespec_new_from_keyfile ( treespec , & tmp_error ) ;
g_assert_no_error ( tmp_error ) ;
2016-05-20 21:16:38 +03:00
if ( ! rpmostree_context_setup ( ctx , gs_file_get_path_cached ( yumroot ) , " / " , treespec_value ,
2016-02-10 11:25:58 +03:00
cancellable , error ) )
goto out ;
2015-01-17 01:23:17 +03:00
}
2015-01-02 01:32:40 +03:00
/* --- Downloading metadata --- */
2016-02-09 21:13:28 +03:00
if ( ! rpmostree_context_download_metadata ( ctx , cancellable , error ) )
2016-01-26 22:34:51 +03:00
goto out ;
2014-01-24 22:34:19 +04:00
2016-02-10 11:25:58 +03:00
if ( ! rpmostree_context_prepare_install ( ctx , & hifinstall , cancellable , error ) )
2016-01-26 22:34:51 +03:00
goto out ;
2015-01-02 01:32:40 +03:00
2016-07-24 21:26:21 +03:00
rpmostree_print_transaction ( hifctx ) ;
2016-04-04 14:48:08 +03:00
if ( json_object_has_member ( treedata , " add-files " ) )
add_files = json_object_get_array_member ( treedata , " add-files " ) ;
2016-01-26 22:34:51 +03:00
/* FIXME - just do a depsolve here before we compute download requirements */
2015-01-03 05:54:23 +03:00
if ( ! compute_checksum_from_treefile_and_goal ( self , hif_context_get_goal ( hifctx ) ,
2016-04-04 14:48:08 +03:00
contextdir , add_files ,
2015-01-03 05:54:23 +03:00
& ret_new_inputhash , error ) )
goto out ;
2015-01-08 05:40:32 +03:00
/* Only look for previous checksum if caller has passed *out_unmodified */
if ( self - > previous_checksum & & out_unmodified ! = NULL )
2015-01-03 05:54:23 +03:00
{
2016-07-15 06:12:37 +03:00
g_autoptr ( GVariant ) commit_v = NULL ;
g_autoptr ( GVariant ) commit_metadata = NULL ;
2015-01-03 05:54:23 +03:00
const char * previous_inputhash = NULL ;
if ( ! ostree_repo_load_variant ( self - > repo , OSTREE_OBJECT_TYPE_COMMIT ,
self - > previous_checksum ,
& commit_v , error ) )
goto out ;
commit_metadata = g_variant_get_child_value ( commit_v , 0 ) ;
if ( g_variant_lookup ( commit_metadata , " rpmostree.inputhash " , " &s " , & previous_inputhash ) )
{
if ( strcmp ( previous_inputhash , ret_new_inputhash ) = = 0 )
{
* out_unmodified = TRUE ;
ret = TRUE ;
goto out ;
}
}
else
g_print ( " Previous commit found, but without rpmostree.inputhash metadata key \n " ) ;
}
2016-03-01 23:38:45 +03:00
if ( opt_dry_run )
{
ret = TRUE ;
goto out ;
}
2015-01-02 01:32:40 +03:00
/* --- Downloading packages --- */
2016-05-20 21:16:38 +03:00
if ( ! rpmostree_context_download ( ctx , hifinstall , cancellable , error ) )
2016-01-26 22:34:51 +03:00
goto out ;
2015-02-18 00:54:54 +03:00
{ g_auto ( GLnxConsoleRef ) console = { 0 , } ;
2016-07-15 06:12:37 +03:00
glnx_unref_object HifState * hifstate = hif_state_new ( ) ;
2015-01-02 01:32:40 +03:00
progress_sigid = g_signal_connect ( hifstate , " percentage-changed " ,
G_CALLBACK ( on_hifstate_percentage_changed ) ,
" Installing packages: " ) ;
2015-02-18 00:54:54 +03:00
glnx_console_lock ( & console ) ;
2015-01-02 01:32:40 +03:00
if ( ! hif_transaction_commit ( hif_context_get_transaction ( hifctx ) ,
hif_context_get_goal ( hifctx ) ,
hifstate ,
error ) )
goto out ;
g_signal_handler_disconnect ( hifstate , progress_sigid ) ;
}
2014-01-24 22:34:19 +04:00
ret = TRUE ;
2015-01-08 05:40:32 +03:00
if ( out_unmodified )
* out_unmodified = FALSE ;
2015-01-03 05:54:23 +03:00
gs_transfer_out_value ( out_new_inputhash , & ret_new_inputhash ) ;
2014-01-24 22:34:19 +04:00
out :
return ret ;
}
2014-05-03 18:05:43 +04:00
static gboolean
2014-05-18 22:13:31 +04:00
process_includes ( RpmOstreeTreeComposeContext * self ,
GFile * treefile_path ,
2014-05-03 18:05:43 +04:00
guint depth ,
JsonObject * root ,
GCancellable * cancellable ,
GError * * error )
{
gboolean ret = FALSE ;
const char * include_path ;
const guint maxdepth = 50 ;
if ( depth > maxdepth )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Exceeded maximum include depth of %u " , maxdepth ) ;
goto out ;
}
2014-05-18 22:13:31 +04:00
{
2016-07-15 06:12:37 +03:00
g_autoptr ( GFile ) parent = g_file_get_parent ( treefile_path ) ;
2014-05-18 22:13:31 +04:00
gboolean existed = FALSE ;
if ( self - > treefile_context_dirs - > len > 0 )
{
GFile * prev = self - > treefile_context_dirs - > pdata [ self - > treefile_context_dirs - > len - 1 ] ;
if ( g_file_equal ( parent , prev ) )
existed = TRUE ;
}
if ( ! existed )
{
g_ptr_array_add ( self - > treefile_context_dirs , parent ) ;
parent = NULL ; /* Transfer ownership */
}
}
2014-11-13 22:53:43 +03:00
if ( ! _rpmostree_jsonutil_object_get_optional_string_member ( root , " include " , & include_path , error ) )
2014-05-03 18:05:43 +04:00
goto out ;
if ( include_path )
{
2016-07-15 06:12:37 +03:00
g_autoptr ( GFile ) treefile_dirpath = g_file_get_parent ( treefile_path ) ;
g_autoptr ( GFile ) parent_path = g_file_resolve_relative_path ( treefile_dirpath , include_path ) ;
glnx_unref_object JsonParser * parent_parser = json_parser_new ( ) ;
2014-05-03 18:05:43 +04:00
JsonNode * parent_rootval ;
JsonObject * parent_root ;
GList * members ;
GList * iter ;
if ( ! json_parser_load_from_file ( parent_parser ,
gs_file_get_path_cached ( parent_path ) ,
error ) )
goto out ;
parent_rootval = json_parser_get_root ( parent_parser ) ;
if ( ! JSON_NODE_HOLDS_OBJECT ( parent_rootval ) )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Treefile root is not an object " ) ;
goto out ;
}
parent_root = json_node_get_object ( parent_rootval ) ;
2014-05-18 22:13:31 +04:00
if ( ! process_includes ( self , parent_path , depth + 1 , parent_root ,
2014-05-03 18:05:43 +04:00
cancellable , error ) )
goto out ;
members = json_object_get_members ( parent_root ) ;
for ( iter = members ; iter ; iter = iter - > next )
{
const char * name = iter - > data ;
JsonNode * parent_val = json_object_get_member ( parent_root , name ) ;
JsonNode * val = json_object_get_member ( root , name ) ;
g_assert ( parent_val ) ;
if ( ! val )
json_object_set_member ( root , name , json_node_copy ( parent_val ) ) ;
else
{
JsonNodeType parent_type =
json_node_get_node_type ( parent_val ) ;
JsonNodeType child_type =
json_node_get_node_type ( val ) ;
if ( parent_type ! = child_type )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Conflicting element type of '%s' " ,
name ) ;
goto out ;
}
if ( child_type = = JSON_NODE_ARRAY )
{
JsonArray * parent_array = json_node_get_array ( parent_val ) ;
JsonArray * child_array = json_node_get_array ( val ) ;
JsonArray * new_child = json_array_new ( ) ;
guint i , len ;
len = json_array_get_length ( parent_array ) ;
for ( i = 0 ; i < len ; i + + )
json_array_add_element ( new_child , json_node_copy ( json_array_get_element ( parent_array , i ) ) ) ;
len = json_array_get_length ( child_array ) ;
for ( i = 0 ; i < len ; i + + )
json_array_add_element ( new_child , json_node_copy ( json_array_get_element ( child_array , i ) ) ) ;
json_object_set_array_member ( root , name , new_child ) ;
}
}
}
2014-09-07 20:38:34 +04:00
json_object_remove_member ( root , " include " ) ;
2014-05-03 18:05:43 +04:00
}
ret = TRUE ;
out :
return ret ;
}
2014-10-14 17:32:29 +04:00
static gboolean
parse_keyvalue_strings ( char * * strings ,
2015-01-03 05:21:30 +03:00
GVariantBuilder * builder ,
2014-10-14 17:32:29 +04:00
GError * * error )
{
gboolean ret = FALSE ;
char * * iter ;
for ( iter = strings ; * iter ; iter + + )
{
const char * s ;
const char * eq ;
2016-07-15 06:12:37 +03:00
g_autofree char * key = NULL ;
2014-10-14 17:32:29 +04:00
s = * iter ;
eq = strchr ( s , ' = ' ) ;
if ( ! eq )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Missing '=' in KEY=VALUE metadata '%s' " , s ) ;
goto out ;
}
key = g_strndup ( s , eq - s ) ;
g_variant_builder_add ( builder , " {sv} " , key ,
g_variant_new_string ( eq + 1 ) ) ;
}
ret = TRUE ;
out :
return ret ;
}
2015-01-07 09:30:27 +03:00
static gboolean
compose_strv_contains_prefix ( gchar * * strv ,
const gchar * prefix )
{
if ( ! strv )
return FALSE ;
while ( * strv )
{
if ( g_str_has_prefix ( * strv , prefix ) )
return TRUE ;
+ + strv ;
}
return FALSE ;
}
2015-11-02 23:43:58 +03:00
int
2014-05-26 23:05:08 +04:00
rpmostree_compose_builtin_tree ( int argc ,
char * * argv ,
GCancellable * cancellable ,
GError * * error )
2014-01-24 22:34:19 +04:00
{
2015-11-02 23:43:58 +03:00
int exit_status = EXIT_FAILURE ;
2015-01-03 04:42:25 +03:00
GError * temp_error = NULL ;
2015-10-26 16:36:22 +03:00
GOptionContext * context = g_option_context_new ( " TREEFILE - Run yum and commit the result to an OSTree repository " ) ;
2014-05-18 22:13:31 +04:00
RpmOstreeTreeComposeContext selfdata = { NULL , } ;
RpmOstreeTreeComposeContext * self = & selfdata ;
2014-05-03 18:05:43 +04:00
JsonNode * treefile_rootval = NULL ;
2014-02-13 03:26:31 +04:00
JsonObject * treefile = NULL ;
2016-07-15 06:12:37 +03:00
g_autofree char * cachekey = NULL ;
g_autofree char * new_inputhash = NULL ;
g_autoptr ( GFile ) previous_root = NULL ;
g_autofree char * previous_checksum = NULL ;
g_autoptr ( GFile ) yumroot = NULL ;
g_autoptr ( GFile ) yumroot_varcache = NULL ;
glnx_unref_object OstreeRepo * repo = NULL ;
g_autoptr ( GPtrArray ) bootstrap_packages = NULL ;
g_autoptr ( GPtrArray ) packages = NULL ;
g_autoptr ( GFile ) treefile_path = NULL ;
g_autoptr ( GFile ) treefile_dirpath = NULL ;
g_autoptr ( GFile ) repo_path = NULL ;
glnx_unref_object JsonParser * treefile_parser = NULL ;
2015-01-03 05:21:30 +03:00
gs_unref_variant_builder GVariantBuilder * metadata_builder =
g_variant_builder_new ( G_VARIANT_TYPE ( " a{sv} " ) ) ;
2016-04-28 15:59:20 +03:00
g_autoptr ( RpmOstreeContext ) corectx = NULL ;
g_autoptr ( GHashTable ) varsubsts = NULL ;
2014-05-03 14:55:35 +04:00
gboolean workdir_is_tmp = FALSE ;
2014-05-18 22:13:31 +04:00
self - > treefile_context_dirs = g_ptr_array_new_with_free_func ( ( GDestroyNotify ) g_object_unref ) ;
2014-01-24 22:34:19 +04:00
2015-08-05 19:39:07 +03:00
if ( ! rpmostree_option_context_parse ( context ,
option_entries ,
& argc , & argv ,
RPM_OSTREE_BUILTIN_FLAG_LOCAL_CMD ,
cancellable ,
NULL ,
error ) )
2014-01-24 22:34:19 +04:00
goto out ;
2014-03-22 23:05:41 +04:00
if ( argc < 2 )
2014-01-24 22:34:19 +04:00
{
2015-10-26 16:36:22 +03:00
rpmostree_usage_error ( context , " TREEFILE must be specified " , error ) ;
2014-01-24 22:34:19 +04:00
goto out ;
}
2014-05-03 14:55:35 +04:00
if ( ! opt_repo )
{
2015-10-26 16:36:22 +03:00
rpmostree_usage_error ( context , " --repo must be specified " , error ) ;
2014-05-03 14:55:35 +04:00
goto out ;
}
2015-07-24 05:10:48 +03:00
if ( getuid ( ) ! = 0 )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" compose tree must presently be run as uid 0 (root) " ) ;
goto out ;
}
2014-05-03 14:55:35 +04:00
repo_path = g_file_new_for_path ( opt_repo ) ;
2015-01-03 05:54:23 +03:00
repo = self - > repo = ostree_repo_new ( repo_path ) ;
2014-05-03 14:55:35 +04:00
if ( ! ostree_repo_open ( repo , cancellable , error ) )
goto out ;
2014-10-14 17:32:29 +04:00
2014-03-22 23:05:41 +04:00
treefile_path = g_file_new_for_path ( argv [ 1 ] ) ;
2014-01-24 22:34:19 +04:00
2014-05-03 14:55:35 +04:00
if ( opt_workdir )
{
2014-11-17 04:05:47 +03:00
self - > workdir = g_file_new_for_path ( opt_workdir ) ;
2014-05-03 14:55:35 +04:00
}
else
{
2016-07-15 06:12:37 +03:00
g_autofree char * tmpd = NULL ;
2015-05-22 21:19:34 +03:00
if ( ! rpmostree_mkdtemp ( " /var/tmp/rpm-ostree.XXXXXX " , & tmpd , NULL , error ) )
goto out ;
2014-11-17 04:05:47 +03:00
self - > workdir = g_file_new_for_path ( tmpd ) ;
2014-05-03 14:55:35 +04:00
workdir_is_tmp = TRUE ;
2014-07-02 06:07:56 +04:00
if ( opt_workdir_tmpfs )
{
if ( mount ( " tmpfs " , tmpd , " tmpfs " , 0 , ( const void * ) " mode=755 " ) ! = 0 )
{
2014-11-20 02:32:08 +03:00
_rpmostree_set_prefix_error_from_errno ( error , errno ,
" mount(tmpfs): " ) ;
2014-07-02 06:07:56 +04:00
goto out ;
}
}
2014-05-03 14:55:35 +04:00
}
2015-02-15 21:56:14 +03:00
if ( ! glnx_opendirat ( AT_FDCWD , gs_file_get_path_cached ( self - > workdir ) ,
FALSE , & self - > workdir_dfd , error ) )
goto out ;
2014-05-16 01:46:51 +04:00
if ( opt_cachedir )
2014-06-07 02:25:08 +04:00
{
2016-02-10 15:47:32 +03:00
if ( ! glnx_opendirat ( AT_FDCWD , opt_cachedir , TRUE , & self - > cachedir_dfd , error ) )
{
g_prefix_error ( error , " Opening cachedir '%s': " , opt_cachedir ) ;
goto out ;
}
}
else
{
self - > cachedir_dfd = fcntl ( self - > workdir_dfd , F_DUPFD_CLOEXEC , 3 ) ;
if ( self - > cachedir_dfd < 0 )
{
glnx_set_error_from_errno ( error ) ;
goto out ;
}
2014-06-07 02:25:08 +04:00
}
2014-05-16 01:46:51 +04:00
2014-10-14 17:32:29 +04:00
if ( opt_metadata_strings )
{
if ( ! parse_keyvalue_strings ( opt_metadata_strings ,
2015-01-03 05:21:30 +03:00
metadata_builder , error ) )
2014-10-14 17:32:29 +04:00
goto out ;
}
2015-02-15 21:56:14 +03:00
if ( fchdir ( self - > workdir_dfd ) ! = 0 )
2014-01-24 22:34:19 +04:00
{
2015-02-15 21:56:14 +03:00
glnx_set_error_from_errno ( error ) ;
2014-02-13 03:26:31 +04:00
goto out ;
2014-01-24 22:34:19 +04:00
}
2016-06-16 15:52:29 +03:00
corectx = rpmostree_context_new_compose ( self - > cachedir_dfd , cancellable , error ) ;
2016-04-28 15:59:20 +03:00
if ( ! corectx )
goto out ;
varsubsts = rpmostree_context_get_varsubsts ( corectx ) ;
2014-02-13 03:26:31 +04:00
treefile_parser = json_parser_new ( ) ;
if ( ! json_parser_load_from_file ( treefile_parser ,
gs_file_get_path_cached ( treefile_path ) ,
error ) )
goto out ;
2014-05-03 18:05:43 +04:00
treefile_rootval = json_parser_get_root ( treefile_parser ) ;
if ( ! JSON_NODE_HOLDS_OBJECT ( treefile_rootval ) )
2014-02-13 03:26:31 +04:00
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Treefile root is not an object " ) ;
goto out ;
}
2014-05-03 18:05:43 +04:00
treefile = json_node_get_object ( treefile_rootval ) ;
2014-05-18 22:13:31 +04:00
if ( ! process_includes ( self , treefile_path , 0 , treefile ,
2014-05-03 18:05:43 +04:00
cancellable , error ) )
goto out ;
if ( opt_print_only )
{
2016-07-15 06:12:37 +03:00
glnx_unref_object JsonGenerator * generator = json_generator_new ( ) ;
g_autoptr ( GOutputStream ) stdout = g_unix_output_stream_new ( 1 , FALSE ) ;
2014-05-03 18:05:43 +04:00
json_generator_set_pretty ( generator , TRUE ) ;
json_generator_set_root ( generator , treefile_rootval ) ;
( void ) json_generator_to_stream ( generator , stdout , NULL , NULL ) ;
2015-11-02 23:43:58 +03:00
exit_status = EXIT_SUCCESS ;
2014-05-03 18:05:43 +04:00
goto out ;
}
2014-02-13 03:26:31 +04:00
2016-04-28 15:59:20 +03:00
{ const char * input_ref = _rpmostree_jsonutil_object_require_string_member ( treefile , " ref " , error ) ;
if ( ! input_ref )
goto out ;
self - > ref = _rpmostree_varsubst_string ( input_ref , varsubsts , error ) ;
if ( ! self - > ref )
goto out ;
}
2014-02-13 03:26:31 +04:00
2016-02-10 11:25:58 +03:00
if ( ! ostree_repo_read_commit ( repo , self - > ref , & previous_root , & previous_checksum ,
2015-01-03 04:42:25 +03:00
cancellable , & temp_error ) )
{
if ( g_error_matches ( temp_error , G_IO_ERROR , G_IO_ERROR_NOT_FOUND ) )
{
g_clear_error ( & temp_error ) ;
2016-02-10 11:25:58 +03:00
g_print ( " No previous commit for %s \n " , self - > ref ) ;
2015-01-03 04:42:25 +03:00
}
else
{
g_propagate_error ( error , temp_error ) ;
goto out ;
}
}
else
g_print ( " Previous commit: %s \n " , previous_checksum ) ;
2015-01-03 05:54:23 +03:00
self - > previous_checksum = previous_checksum ;
2015-01-03 04:42:25 +03:00
yumroot = g_file_get_child ( self - > workdir , " rootfs.tmp " ) ;
2015-02-15 21:56:14 +03:00
if ( ! glnx_shutil_rm_rf_at ( self - > workdir_dfd , " rootfs.tmp " , cancellable , error ) )
2015-01-03 04:42:25 +03:00
goto out ;
2015-01-07 09:30:27 +03:00
if ( json_object_has_member ( treefile , " automatic_version_prefix " ) & &
! compose_strv_contains_prefix ( opt_metadata_strings , " version= " ) )
{
2016-07-15 06:12:37 +03:00
g_autoptr ( GVariant ) variant = NULL ;
g_autofree char * last_version = NULL ;
g_autofree char * next_version = NULL ;
2015-01-07 09:30:27 +03:00
const char * ver_prefix ;
ver_prefix = _rpmostree_jsonutil_object_require_string_member ( treefile ,
" automatic_version_prefix " ,
error ) ;
if ( ! ver_prefix )
goto out ;
if ( previous_checksum )
{
if ( ! ostree_repo_load_variant ( repo , OSTREE_OBJECT_TYPE_COMMIT ,
previous_checksum , & variant , error ) )
goto out ;
last_version = checksum_version ( variant ) ;
}
2015-01-12 08:07:33 +03:00
next_version = _rpmostree_util_next_version ( ver_prefix , last_version ) ;
2015-01-07 09:30:27 +03:00
g_variant_builder_add ( metadata_builder , " {sv} " , " version " ,
g_variant_new_string ( next_version ) ) ;
}
2014-02-13 03:26:31 +04:00
bootstrap_packages = g_ptr_array_new ( ) ;
packages = g_ptr_array_new ( ) ;
2014-01-24 22:34:19 +04:00
2014-11-17 23:48:08 +03:00
if ( json_object_has_member ( treefile , " bootstrap_packages " ) )
{
2014-11-18 00:08:03 +03:00
if ( ! _rpmostree_jsonutil_append_string_array_to ( treefile , " bootstrap_packages " , packages , error ) )
2014-11-17 23:48:08 +03:00
goto out ;
}
2014-11-18 00:08:03 +03:00
if ( ! _rpmostree_jsonutil_append_string_array_to ( treefile , " packages " , packages , error ) )
2014-02-13 03:26:31 +04:00
goto out ;
g_ptr_array_add ( packages , NULL ) ;
2014-01-24 22:34:19 +04:00
2016-07-15 06:12:37 +03:00
{ glnx_unref_object JsonGenerator * generator = json_generator_new ( ) ;
2014-09-30 00:24:53 +04:00
char * treefile_buf = NULL ;
gsize len ;
json_generator_set_root ( generator , treefile_rootval ) ;
json_generator_set_pretty ( generator , TRUE ) ;
treefile_buf = json_generator_to_data ( generator , & len ) ;
self - > serialized_treefile = g_bytes_new_take ( treefile_buf , len ) ;
}
2015-01-20 09:37:22 +03:00
treefile_dirpath = g_file_get_parent ( treefile_path ) ;
if ( TRUE )
2015-01-03 04:42:25 +03:00
{
gboolean generate_from_previous = TRUE ;
2014-12-24 00:28:53 +03:00
2015-01-03 04:42:25 +03:00
if ( ! _rpmostree_jsonutil_object_get_optional_boolean_member ( treefile ,
" preserve-passwd " ,
& generate_from_previous ,
error ) )
goto out ;
2014-12-24 00:28:53 +03:00
2015-01-03 04:42:25 +03:00
if ( generate_from_previous )
{
2015-01-20 09:37:22 +03:00
if ( ! rpmostree_generate_passwd_from_previous ( repo , yumroot ,
treefile_dirpath ,
previous_root , treefile ,
2015-01-03 04:42:25 +03:00
cancellable , error ) )
goto out ;
}
}
2014-12-24 00:28:53 +03:00
2015-01-03 05:54:23 +03:00
{ gboolean unmodified = FALSE ;
2014-05-16 01:47:47 +04:00
2016-04-28 15:59:20 +03:00
if ( ! install_packages_in_root ( self , corectx , treefile , yumroot ,
2015-01-03 05:54:23 +03:00
( char * * ) packages - > pdata ,
2015-01-08 05:40:32 +03:00
opt_force_nocache ? NULL : & unmodified ,
2015-01-03 05:54:23 +03:00
& new_inputhash ,
cancellable , error ) )
2014-05-16 01:46:51 +04:00
goto out ;
2015-01-03 05:54:23 +03:00
if ( unmodified )
{
g_print ( " No apparent changes since previous commit; use --force-nocache to override \n " ) ;
2015-11-02 23:43:58 +03:00
exit_status = EXIT_SUCCESS ;
2015-01-03 05:54:23 +03:00
goto out ;
}
2016-03-01 23:38:45 +03:00
else if ( opt_dry_run )
{
g_print ( " --dry-run complete, exiting \n " ) ;
exit_status = EXIT_SUCCESS ;
goto out ;
}
2015-01-03 05:54:23 +03:00
}
2014-01-26 19:13:45 +04:00
2014-05-16 01:47:47 +04:00
if ( g_strcmp0 ( g_getenv ( " RPM_OSTREE_BREAK " ) , " post-yum " ) = = 0 )
goto out ;
2014-01-26 19:13:45 +04:00
2014-11-14 19:53:21 +03:00
if ( ! rpmostree_treefile_postprocessing ( yumroot , self - > treefile_context_dirs - > pdata [ 0 ] ,
self - > serialized_treefile , treefile ,
2014-11-13 22:54:33 +03:00
cancellable , error ) )
goto out ;
2014-11-17 17:18:02 +03:00
if ( ! rpmostree_prepare_rootfs_for_commit ( yumroot , treefile , cancellable , error ) )
goto out ;
2014-11-20 17:40:35 +03:00
2016-03-30 16:35:52 +03:00
if ( ! rpmostree_copy_additional_files ( yumroot , self - > treefile_context_dirs - > pdata [ 0 ] , treefile , cancellable , error ) )
goto out ;
2015-01-20 09:37:22 +03:00
if ( ! rpmostree_check_passwd ( repo , yumroot , treefile_dirpath , treefile ,
2016-04-28 16:09:40 +03:00
previous_checksum ,
2014-11-20 17:40:35 +03:00
cancellable , error ) )
goto out ;
2015-01-20 09:37:22 +03:00
if ( ! rpmostree_check_groups ( repo , yumroot , treefile_dirpath , treefile ,
2016-04-28 16:09:40 +03:00
previous_checksum ,
2014-11-20 17:40:35 +03:00
cancellable , error ) )
goto out ;
2014-05-16 01:47:47 +04:00
{
const char * gpgkey ;
2015-03-30 17:54:33 +03:00
gboolean selinux = TRUE ;
2016-07-15 06:12:37 +03:00
g_autoptr ( GVariant ) metadata = NULL ;
2015-01-03 05:54:23 +03:00
g_variant_builder_add ( metadata_builder , " {sv} " ,
" rpmostree.inputhash " ,
g_variant_new_string ( new_inputhash ) ) ;
metadata = g_variant_ref_sink ( g_variant_builder_end ( metadata_builder ) ) ;
2015-01-03 05:21:30 +03:00
2014-11-13 22:53:43 +03:00
if ( ! _rpmostree_jsonutil_object_get_optional_string_member ( treefile , " gpg_key " , & gpgkey , error ) )
2014-05-16 01:47:47 +04:00
goto out ;
2014-03-29 04:15:43 +04:00
2015-03-30 17:54:33 +03:00
if ( ! _rpmostree_jsonutil_object_get_optional_boolean_member ( treefile ,
" selinux " ,
& selinux ,
error ) )
goto out ;
2015-02-11 21:06:43 +03:00
{ g_autofree char * new_revision = NULL ;
glnx_fd_close int rootfs_fd = - 1 ;
if ( ! glnx_opendirat ( AT_FDCWD , gs_file_get_path_cached ( yumroot ) , TRUE ,
& rootfs_fd , error ) )
goto out ;
g_print ( " Committing... \n " ) ;
if ( ! rpmostree_commit ( rootfs_fd , repo , self - > ref , metadata , gpgkey , selinux , NULL ,
& new_revision ,
cancellable , error ) )
goto out ;
2016-02-25 20:42:56 +03:00
g_print ( " %s => %s \n " , self - > ref , new_revision ) ;
2015-02-11 21:06:43 +03:00
}
2014-01-24 22:34:19 +04:00
}
2015-01-29 00:34:29 +03:00
if ( opt_touch_if_changed )
{
gs_fd_close int fd = open ( opt_touch_if_changed , O_CREAT | O_WRONLY | O_NOCTTY , 0644 ) ;
if ( fd = = - 1 )
{
gs_set_error_from_errno ( error , errno ) ;
g_prefix_error ( error , " Updating '%s': " , opt_touch_if_changed ) ;
goto out ;
}
if ( futimens ( fd , NULL ) = = - 1 )
{
gs_set_error_from_errno ( error , errno ) ;
goto out ;
}
}
2015-11-02 23:43:58 +03:00
exit_status = EXIT_SUCCESS ;
2014-01-24 22:34:19 +04:00
out :
2016-06-13 22:40:22 +03:00
/* Explicitly close this one now as it may have references to files
* we delete below .
*/
g_clear_object ( & corectx ) ;
2015-10-31 04:40:14 +03:00
/* Move back out of the workding directory to ensure unmount works */
( void ) chdir ( " / " ) ;
if ( self - > workdir_dfd ! = - 1 )
( void ) close ( self - > workdir_dfd ) ;
2014-07-02 06:07:56 +04:00
2014-06-11 15:27:31 +04:00
if ( workdir_is_tmp )
2014-07-02 06:07:56 +04:00
{
if ( opt_workdir_tmpfs )
2015-10-31 04:40:14 +03:00
if ( umount ( gs_file_get_path_cached ( self - > workdir ) ) ! = 0 )
{
fprintf ( stderr , " warning: umount failed: %m \n " ) ;
}
2014-11-17 04:05:47 +03:00
( void ) gs_shutil_rm_rf ( self - > workdir , NULL , NULL ) ;
2014-07-02 06:07:56 +04:00
}
2014-05-18 22:13:31 +04:00
if ( self )
{
2014-11-17 04:05:47 +03:00
g_clear_object ( & self - > workdir ) ;
2014-09-07 20:38:34 +04:00
g_clear_pointer ( & self - > serialized_treefile , g_bytes_unref ) ;
2014-05-18 22:13:31 +04:00
g_ptr_array_unref ( self - > treefile_context_dirs ) ;
}
2015-11-02 23:43:58 +03:00
return exit_status ;
2014-01-24 22:34:19 +04:00
}