2015-05-21 22:33:41 +03:00
/*
* Copyright ( C ) 2015 Red Hat , Inc .
*
* 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 . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
2015-06-10 01:57:32 +03:00
# include "config.h"
2015-08-24 22:44:40 +03:00
# include "rpmostreed-utils.h"
# include "rpmostreed-errors.h"
2016-11-06 02:48:32 +03:00
# include "libglnx.h"
2015-05-21 22:33:41 +03:00
2015-06-10 01:57:32 +03:00
# include <libglnx.h>
2015-05-22 21:23:57 +03:00
2015-05-25 20:25:05 +03:00
static void
append_to_object_path ( GString * str ,
const gchar * s )
{
guint n ;
for ( n = 0 ; s [ n ] ! = ' \0 ' ; n + + )
{
gint c = s [ n ] ;
/* D-Bus spec sez:
*
* Each element must only contain the ASCII characters " [A-Z][a-z][0-9]_- "
*/
if ( ( c > = ' A ' & & c < = ' Z ' ) | | ( c > = ' a ' & & c < = ' z ' ) | | ( c > = ' 0 ' & & c < = ' 9 ' ) | | c = = ' _ ' )
{
g_string_append_c ( str , c ) ;
}
else if ( c = = ' - ' | | c = = ' / ' )
{
/* Swap / or - for _ to keep names easier to read */
g_string_append_c ( str , ' _ ' ) ;
}
else
{
/* Escape bytes not in [A-Z][a-z][0-9] as _<hex-with-two-digits> */
g_string_append_printf ( str , " _%02x " , c & 0xFF ) ;
}
}
}
/**
2015-08-24 22:44:40 +03:00
* rpmostreed_generate_object_path :
2015-05-25 20:25:05 +03:00
* @ base : The base object path ( without trailing ' / ' ) .
* @ part . . . : UTF - 8 strings .
*
* Appends @ s to @ base in a way such that only characters that can be
* used in a D - Bus object path will be used . E . g . a character not in
* < literal > [ A - Z ] [ a - z ] [ 0 - 9 ] _ < / literal > will be escaped as _HEX where
* HEX is a two - digit hexadecimal number .
*
* Note that his mapping is not bijective - e . g . you cannot go back
* to the original string .
*
* Returns : An allocated string that must be freed with g_free ( ) .
*/
gchar *
2015-08-24 22:44:40 +03:00
rpmostreed_generate_object_path ( const gchar * base ,
const gchar * part ,
. . . )
2015-05-25 20:25:05 +03:00
{
gchar * result ;
va_list va ;
va_start ( va , part ) ;
2015-08-24 22:44:40 +03:00
result = rpmostreed_generate_object_path_from_va ( base , part , va ) ;
2015-05-25 20:25:05 +03:00
va_end ( va ) ;
return result ;
}
gchar *
2015-08-24 22:44:40 +03:00
rpmostreed_generate_object_path_from_va ( const gchar * base ,
const gchar * part ,
va_list va )
2015-05-25 20:25:05 +03:00
{
GString * path ;
g_return_val_if_fail ( base ! = NULL , NULL ) ;
g_return_val_if_fail ( g_variant_is_object_path ( base ) , NULL ) ;
g_return_val_if_fail ( ! g_str_has_suffix ( base , " / " ) , NULL ) ;
path = g_string_new ( base ) ;
while ( part ! = NULL )
{
if ( ! g_utf8_validate ( part , - 1 , NULL ) )
{
g_string_free ( path , TRUE ) ;
return NULL ;
}
else
{
g_string_append_c ( path , ' / ' ) ;
append_to_object_path ( path , part ) ;
part = va_arg ( va , const gchar * ) ;
}
}
return g_string_free ( path , FALSE ) ;
}
2015-06-11 05:17:49 +03:00
/**
2015-08-24 22:44:40 +03:00
* rpmostreed_refspec_parse_partial :
2015-06-11 05:17:49 +03:00
* @ new_provided_refspec : The provided refspec
* @ base_refspec : The refspec string to base on .
* @ out_refspec : Pointer to the new refspec
* @ error : Pointer to an error pointer .
*
* Takes a refspec string and adds any missing bits based on the
* base_refspec argument . Errors if a full valid refspec can ' t
* be derived .
*
2015-08-24 22:44:40 +03:00
* Returns : True on success .
2015-06-11 05:17:49 +03:00
*/
gboolean
2015-08-24 22:44:40 +03:00
rpmostreed_refspec_parse_partial ( const gchar * new_provided_refspec ,
2015-02-11 21:06:43 +03:00
const gchar * base_refspec ,
2015-08-24 22:44:40 +03:00
gchar * * out_refspec ,
GError * * error )
2015-06-11 05:17:49 +03:00
{
g_autofree gchar * ref = NULL ;
g_autofree gchar * remote = NULL ;
2017-05-05 22:40:55 +03:00
gboolean infer_remote = TRUE ; ;
2015-06-11 05:17:49 +03:00
/* Allow just switching remotes */
if ( g_str_has_suffix ( new_provided_refspec , " : " ) )
{
2017-05-05 22:39:01 +03:00
remote = g_strndup ( new_provided_refspec , strlen ( new_provided_refspec ) - 1 ) ;
2015-06-11 05:17:49 +03:00
}
2017-05-05 22:40:55 +03:00
/* Allow switching to a local branch */
else if ( g_str_has_prefix ( new_provided_refspec , " : " ) )
{
infer_remote = FALSE ;
ref = g_strdup ( new_provided_refspec + 1 ) ;
}
2015-06-11 05:17:49 +03:00
else
{
2017-05-05 22:39:01 +03:00
g_autoptr ( GError ) parse_error = NULL ;
2015-06-11 05:17:49 +03:00
if ( ! ostree_parse_refspec ( new_provided_refspec , & remote ,
& ref , & parse_error ) )
{
g_set_error_literal ( error , RPM_OSTREED_ERROR ,
2017-05-05 22:39:01 +03:00
RPM_OSTREED_ERROR_INVALID_REFSPEC ,
parse_error - > message ) ;
return FALSE ;
2015-06-11 05:17:49 +03:00
}
}
2017-05-05 22:39:01 +03:00
g_autofree gchar * origin_ref = NULL ;
g_autofree gchar * origin_remote = NULL ;
2015-06-11 05:17:49 +03:00
if ( base_refspec ! = NULL )
{
2017-05-05 22:39:01 +03:00
g_autoptr ( GError ) parse_error = NULL ;
2015-06-11 05:17:49 +03:00
if ( ! ostree_parse_refspec ( base_refspec , & origin_remote ,
2017-05-05 22:39:01 +03:00
& origin_ref , & parse_error ) )
{
g_set_error_literal ( error , RPM_OSTREED_ERROR ,
RPM_OSTREED_ERROR_INVALID_REFSPEC ,
parse_error - > message ) ;
return FALSE ;
}
2015-06-11 05:17:49 +03:00
}
if ( ref = = NULL )
{
if ( origin_ref )
{
ref = g_strdup ( origin_ref ) ;
}
else
{
g_set_error ( error , RPM_OSTREED_ERROR ,
RPM_OSTREED_ERROR_INVALID_REFSPEC ,
" Could not determine default ref to pull. " ) ;
2017-05-05 22:39:01 +03:00
return FALSE ;
2015-06-11 05:17:49 +03:00
}
}
2017-05-05 22:40:55 +03:00
else if ( infer_remote & & remote = = NULL )
2015-06-11 05:17:49 +03:00
{
if ( origin_remote )
{
remote = g_strdup ( origin_remote ) ;
}
}
if ( g_strcmp0 ( origin_remote , remote ) = = 0 & &
g_strcmp0 ( origin_ref , ref ) = = 0 )
{
g_set_error ( error , RPM_OSTREED_ERROR ,
RPM_OSTREED_ERROR_INVALID_REFSPEC ,
" Old and new refs are equal: %s:%s " ,
remote , ref ) ;
2017-05-05 22:39:01 +03:00
return FALSE ;
2015-06-11 05:17:49 +03:00
}
2016-06-30 22:50:57 +03:00
if ( remote = = NULL )
* out_refspec = g_steal_pointer ( & ref ) ;
else
* out_refspec = g_strconcat ( remote , " : " , ref , NULL ) ;
2017-05-05 22:39:01 +03:00
return TRUE ;
2015-05-25 20:25:05 +03:00
}
2015-10-07 09:22:36 +03:00
void
rpmostreed_reboot ( GCancellable * cancellable , GError * * error )
{
2016-10-12 22:17:14 +03:00
const char * child_argv [ ] = { " systemctl " , " reboot " , NULL } ;
( void ) g_spawn_sync ( NULL , ( char * * ) child_argv , NULL , G_SPAWN_CHILD_INHERITS_STDIN | G_SPAWN_SEARCH_PATH ,
NULL , NULL , NULL , NULL , NULL , NULL ) ;
2015-10-07 09:22:36 +03:00
}
2015-10-09 23:47:39 +03:00
/**
* rpmostreed_repo_pull_ancestry :
* @ repo : Repo
* @ refspec : Repository branch
* @ visitor : ( allow - none ) : Visitor function to call on each commit
* @ visitor_data : ( allow - none ) : User data for @ visitor
* @ progress : ( allow - none ) : Progress
* @ cancellable : Cancellable
* @ error : Error
*
* Downloads an ancestry of commit objects starting from @ refspec .
*
* If a @ visitor function pointer is given , commit objects are downloaded
* in batches and the @ visitor function is called for each commit object .
* The @ visitor function can stop the recursion , such as when looking for
* a particular commit .
*
* Returns : % TRUE on success , % FALSE on failure
*/
gboolean
rpmostreed_repo_pull_ancestry ( OstreeRepo * repo ,
const char * refspec ,
RpmostreedCommitVisitor visitor ,
gpointer visitor_data ,
OstreeAsyncProgress * progress ,
GCancellable * cancellable ,
GError * * error )
{
OstreeRepoPullFlags flags ;
GVariantDict options ;
GVariant * refs_value ;
const char * refs_array [ ] = { NULL , NULL } ;
g_autofree char * remote = NULL ;
g_autofree char * ref = NULL ;
g_autofree char * checksum = NULL ;
int depth , ii ;
gboolean ret = FALSE ;
2016-12-22 20:39:28 +03:00
/* Only fetch the HEAD on the first pass. See also:
* https : //github.com/projectatomic/rpm-ostree/pull/557 */
gboolean first_pass = TRUE ;
2015-10-09 23:47:39 +03:00
g_return_val_if_fail ( OSTREE_IS_REPO ( repo ) , FALSE ) ;
g_return_val_if_fail ( refspec ! = NULL , FALSE ) ;
if ( ! ostree_parse_refspec ( refspec , & remote , & ref , error ) )
goto out ;
/* If no visitor function was provided then we won't be short-circuiting
* the recursion , so pull everything in one shot . Otherwise pull commits
* in increasingly large batches . */
depth = ( visitor ! = NULL ) ? 10 : - 1 ;
flags = OSTREE_REPO_PULL_FLAGS_COMMIT_ONLY ;
/* It's important to use the ref name instead of a checksum on the first
* pass because we want to search from the latest available commit on the
* remote server , which is not necessarily what the ref name is currently
* pointing at in our local repo . */
refs_array [ 0 ] = ref ;
while ( TRUE )
{
2016-06-30 19:41:20 +03:00
if ( remote ! = NULL )
{
/* Floating reference, transferred to dictionary. */
refs_value =
g_variant_new_strv ( ( const char * const * ) refs_array , - 1 ) ;
g_variant_dict_init ( & options , NULL ) ;
2016-12-22 20:39:28 +03:00
if ( ! first_pass )
g_variant_dict_insert ( & options , " depth " , " i " , depth ) ;
2016-06-30 19:41:20 +03:00
g_variant_dict_insert ( & options , " flags " , " i " , flags ) ;
g_variant_dict_insert_value ( & options , " refs " , refs_value ) ;
if ( ! ostree_repo_pull_with_options ( repo , remote ,
g_variant_dict_end ( & options ) ,
progress , cancellable , error ) )
goto out ;
}
2015-10-09 23:47:39 +03:00
/* First pass only. Now we can resolve the ref to a checksum. */
if ( checksum = = NULL )
{
2016-10-31 19:19:08 +03:00
if ( ! ostree_repo_resolve_rev ( repo , refspec , FALSE , & checksum , error ) )
2015-10-09 23:47:39 +03:00
goto out ;
}
2016-06-30 19:41:20 +03:00
if ( visitor ! = NULL )
2015-10-09 23:47:39 +03:00
{
2016-12-22 20:39:28 +03:00
for ( ii = 0 ; ii < ( first_pass ? 1 : depth ) & & checksum ! = NULL ; ii + + )
2016-06-30 19:41:20 +03:00
{
g_autoptr ( GVariant ) commit = NULL ;
gboolean stop = FALSE ;
2015-10-09 23:47:39 +03:00
2016-06-30 19:41:20 +03:00
if ( ! ostree_repo_load_commit ( repo , checksum , & commit ,
NULL , error ) )
goto out ;
2015-10-09 23:47:39 +03:00
2016-06-30 19:41:20 +03:00
if ( ! visitor ( repo , checksum , commit , visitor_data , & stop , error ) )
goto out ;
2015-10-09 23:47:39 +03:00
2016-06-30 19:41:20 +03:00
g_clear_pointer ( & checksum , g_free ) ;
2015-10-09 23:47:39 +03:00
2016-06-30 19:41:20 +03:00
if ( ! stop )
checksum = ostree_commit_get_parent ( commit ) ;
}
2015-10-09 23:47:39 +03:00
}
/* Break if no visitor, or visitor told us to stop. */
2016-06-30 19:41:20 +03:00
if ( visitor = = NULL | | checksum = = NULL )
2015-10-09 23:47:39 +03:00
break ;
/* Pull the next batch of commits, twice as many. */
refs_array [ 0 ] = checksum ;
2016-12-22 20:39:28 +03:00
if ( ! first_pass )
depth = depth * 2 ;
first_pass = FALSE ;
2015-10-09 23:47:39 +03:00
}
ret = TRUE ;
out :
return ret ;
}
typedef struct {
const char * version ;
char * checksum ;
} VersionVisitorClosure ;
static gboolean
version_visitor ( OstreeRepo * repo ,
const char * checksum ,
GVariant * commit ,
gpointer user_data ,
gboolean * out_stop ,
GError * * error )
{
VersionVisitorClosure * closure = user_data ;
g_autoptr ( GVariant ) metadict = NULL ;
const char * version = NULL ;
metadict = g_variant_get_child_value ( commit , 0 ) ;
if ( g_variant_lookup ( metadict , " version " , " &s " , & version ) )
{
if ( g_str_equal ( version , closure - > version ) )
{
closure - > checksum = g_strdup ( checksum ) ;
* out_stop = TRUE ;
}
}
return TRUE ;
}
/**
* rpmostreed_repo_lookup_version :
* @ repo : Repo
* @ refspec : Repository branch
* @ version : Version to look for
* @ progress : ( allow - none ) : Progress
* @ cancellable : Cancellable
* @ out_checksum : ( out ) ( allow - none ) : Commit checksum , or % NULL
* @ error : Error
*
* Tries to determine the commit checksum for @ version on @ refspec .
* This may require pulling commit objects from a remote repository .
*
* Returns : % TRUE on success , % FALSE on failure
*/
gboolean
rpmostreed_repo_lookup_version ( OstreeRepo * repo ,
const char * refspec ,
const char * version ,
OstreeAsyncProgress * progress ,
GCancellable * cancellable ,
char * * out_checksum ,
GError * * error )
{
VersionVisitorClosure closure = { version , NULL } ;
gboolean ret = FALSE ;
g_return_val_if_fail ( OSTREE_IS_REPO ( repo ) , FALSE ) ;
g_return_val_if_fail ( refspec ! = NULL , FALSE ) ;
g_return_val_if_fail ( version ! = NULL , FALSE ) ;
if ( ! rpmostreed_repo_pull_ancestry ( repo , refspec ,
version_visitor , & closure ,
progress , cancellable , error ) )
goto out ;
if ( closure . checksum = = NULL )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_NOT_FOUND ,
" Version %s not found in %s " , version , refspec ) ;
goto out ;
}
if ( out_checksum ! = NULL )
* out_checksum = g_steal_pointer ( & closure . checksum ) ;
g_free ( closure . checksum ) ;
ret = TRUE ;
out :
return ret ;
}
2015-10-22 19:22:56 +03:00
2016-10-18 00:18:11 +03:00
typedef struct {
const char * wanted_checksum ;
gboolean found ;
} ChecksumVisitorClosure ;
static gboolean
checksum_visitor ( OstreeRepo * repo ,
const char * checksum ,
GVariant * commit ,
gpointer user_data ,
gboolean * out_stop ,
GError * * error )
{
ChecksumVisitorClosure * closure = user_data ;
* out_stop = closure - > found = g_str_equal ( checksum , closure - > wanted_checksum ) ;
return TRUE ;
}
/**
* rpmostreed_repo_lookup_checksum :
* @ repo : Repo
* @ refspec : Repository branch
* @ checksum : Checksum to look for
* @ progress : ( allow - none ) : Progress
* @ cancellable : Cancellable
* @ error : Error
*
* Tries to determine if the checksum given belongs on the remote and branch
* given by @ refspec . This may require pulling commit objects from a remote
* repository .
*
* Returns : % TRUE on success , % FALSE on failure
*/
gboolean
rpmostreed_repo_lookup_checksum ( OstreeRepo * repo ,
const char * refspec ,
const char * checksum ,
OstreeAsyncProgress * progress ,
GCancellable * cancellable ,
GError * * error )
{
ChecksumVisitorClosure closure = { checksum , FALSE } ;
g_return_val_if_fail ( OSTREE_IS_REPO ( repo ) , FALSE ) ;
g_return_val_if_fail ( refspec ! = NULL , FALSE ) ;
g_return_val_if_fail ( checksum ! = NULL , FALSE ) ;
if ( ! rpmostreed_repo_pull_ancestry ( repo , refspec ,
checksum_visitor , & closure ,
progress , cancellable , error ) )
return FALSE ;
if ( ! closure . found )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_NOT_FOUND ,
" Checksum %s not found in %s " , checksum , refspec ) ;
return FALSE ;
}
return TRUE ;
}
2015-10-22 19:22:56 +03:00
/**
* rpmostreed_repo_lookup_cached_version :
* @ repo : Repo
* @ refspec : Repository branch
* @ version : Version to look for
* @ cancellable : Cancellable
* @ out_checksum : ( out ) ( allow - none ) : Commit checksum , or % NULL
* @ error : Error
*
* Similar to rpmostreed_repo_lookup_version ( ) , except without pulling
* from a remote repository . It traverses whatever commits are available
* locally in @ repo .
*
* Returns : % TRUE on success , % FALSE on failure
*/
gboolean
rpmostreed_repo_lookup_cached_version ( OstreeRepo * repo ,
const char * refspec ,
const char * version ,
GCancellable * cancellable ,
char * * out_checksum ,
GError * * error )
{
VersionVisitorClosure closure = { version , NULL } ;
g_autofree char * checksum = NULL ;
gboolean ret = FALSE ;
g_return_val_if_fail ( OSTREE_IS_REPO ( repo ) , FALSE ) ;
g_return_val_if_fail ( refspec ! = NULL , FALSE ) ;
g_return_val_if_fail ( version ! = NULL , FALSE ) ;
if ( ! ostree_repo_resolve_rev ( repo , refspec , FALSE , & checksum , error ) )
goto out ;
while ( checksum ! = NULL )
{
g_autoptr ( GVariant ) commit = NULL ;
gboolean stop = FALSE ;
if ( ! ostree_repo_load_commit ( repo , checksum , & commit , NULL , error ) )
goto out ;
if ( ! version_visitor ( repo , checksum , commit , & closure , & stop , error ) )
goto out ;
g_clear_pointer ( & checksum , g_free ) ;
if ( ! stop )
checksum = ostree_commit_get_parent ( commit ) ;
}
if ( closure . checksum = = NULL )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_NOT_FOUND ,
" Version %s not cached in %s " , version , refspec ) ;
goto out ;
}
if ( out_checksum ! = NULL )
* out_checksum = g_steal_pointer ( & closure . checksum ) ;
g_free ( closure . checksum ) ;
ret = TRUE ;
out :
return ret ;
}
2015-11-05 05:14:14 +03:00
/**
* rpmostreed_parse_revision :
* @ revision : Revision string
* @ out_checksum : ( out ) ( allow - none ) : Commit checksum , or % NULL
* @ out_version : ( out ) ( allow - none ) : Version value , or % NULL
* @ error : Error
*
* Determines @ revision to either be a SHA256 checksum or a version metadata
* value , sets one of @ out_checksum or @ out_version appropriately , and then
* returns % TRUE .
*
* The @ revision string may have a " revision= " prefix to denote a SHA256
* checksum , or a " version= " prefix to denote a version metadata value . If
* the @ revision string lacks either prefix , the function attempts to infer
* the type of revision . The prefixes are case - insensitive .
*
* The only possible error is if a " revision= " prefix is given , but the
* rest of the string is not a valid SHA256 checksum . In that case the
* function sets @ error and returns % FALSE .
*
* Returns : % TRUE on success , % FALSE of failure
*/
gboolean
rpmostreed_parse_revision ( const char * revision ,
char * * out_checksum ,
char * * out_version ,
GError * * error )
{
const char * checksum = NULL ;
const char * version = NULL ;
gboolean ret = FALSE ;
g_return_val_if_fail ( revision ! = NULL , FALSE ) ;
if ( g_ascii_strncasecmp ( revision , " revision= " , 9 ) = = 0 )
{
checksum = revision + 9 ;
/* Since this claims to be a checksum, fail if it isn't. */
if ( ! ostree_validate_checksum_string ( checksum , error ) )
goto out ;
}
else if ( g_ascii_strncasecmp ( revision , " version= " , 8 ) = = 0 )
{
version = revision + 8 ;
}
else if ( ostree_validate_checksum_string ( revision , NULL ) )
{
/* If it looks like a checksum, assume it is. */
checksum = revision ;
}
else
{
/* Treat anything else as a version metadata value. */
version = revision ;
}
if ( out_checksum ! = NULL )
* out_checksum = g_strdup ( checksum ) ;
if ( out_version ! = NULL )
* out_version = g_strdup ( version ) ;
ret = TRUE ;
out :
return ret ;
}