2014-06-11 21:47:10 +04:00
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
2014-06-12 18:32:21 +04:00
* Copyright ( C ) 2014 Anne LoVerso < anne . loverso @ students . olin . edu >
2016-05-31 20:12:36 +03:00
* Copyright ( C ) 2016 Red Hat , Inc .
2014-06-11 21:47:10 +04:00
*
* 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>
2016-06-01 20:17:20 +03:00
# include <stdio.h>
2014-06-11 21:47:10 +04:00
# include <glib-unix.h>
2016-06-14 15:34:07 +03:00
# include <gio/gunixoutputstream.h>
2016-06-09 23:00:24 +03:00
# include <json-glib/json-glib.h>
2014-06-11 21:47:10 +04:00
# include "rpmostree-builtins.h"
2015-06-13 03:19:16 +03:00
# include "rpmostree-dbus-helpers.h"
2016-08-30 22:57:40 +03:00
# include "rpmostree-util.h"
2016-05-29 21:15:29 +03:00
# include "libsd-locale-util.h"
2014-06-11 21:47:10 +04:00
2015-06-13 03:19:16 +03:00
# include <libglnx.h>
2014-06-11 21:47:10 +04:00
2014-06-13 21:16:43 +04:00
static gboolean opt_pretty ;
2016-06-09 23:00:24 +03:00
static gboolean opt_json ;
2014-06-13 21:16:43 +04:00
static GOptionEntry option_entries [ ] = {
2016-06-01 20:17:20 +03:00
{ " pretty " , ' p ' , 0 , G_OPTION_ARG_NONE , & opt_pretty , " This option is deprecated and no longer has any effect " , NULL } ,
2016-06-09 23:00:24 +03:00
{ " json " , 0 , 0 , G_OPTION_ARG_NONE , & opt_json , " Output JSON " , NULL } ,
2014-06-13 21:16:43 +04:00
{ NULL }
} ;
2015-06-13 03:19:16 +03:00
static void
2016-06-01 20:17:20 +03:00
printpad ( char c , guint n )
2014-06-13 21:16:43 +04:00
{
2016-06-01 20:17:20 +03:00
for ( guint i = 0 ; i < n ; i + + )
putc ( c , stdout ) ;
2014-06-13 21:16:43 +04:00
}
2016-06-01 20:17:20 +03:00
static void
print_kv ( const char * key ,
guint maxkeylen ,
const char * value )
2015-02-11 21:06:43 +03:00
{
2016-06-01 20:17:20 +03:00
int pad = maxkeylen - strlen ( key ) ;
g_assert ( pad > = 0 ) ;
/* +2 for initial leading spaces */
printpad ( ' ' , pad + 2 ) ;
printf ( " %s: %s \n " , key , value ) ;
2015-02-11 21:06:43 +03:00
}
2016-06-14 16:41:36 +03:00
static GVariant *
get_active_txn ( RPMOSTreeSysroot * sysroot_proxy )
{
GVariant * txn = rpmostree_sysroot_get_active_transaction ( sysroot_proxy ) ;
const char * a , * b , * c ;
if ( txn )
2016-10-28 22:54:12 +03:00
{
g_variant_get ( txn , " (&s&s&s) " , & a , & b , & c ) ;
if ( * a )
return txn ;
}
2016-06-14 16:41:36 +03:00
return NULL ;
}
2017-02-24 17:44:40 +03:00
static void
print_packages ( const char * k , guint max_key_len ,
const char * const * pkgs ,
const char * const * omit_pkgs )
{
g_autofree char * packages_joined = NULL ;
g_autoptr ( GPtrArray ) packages_sorted =
g_ptr_array_new_with_free_func ( g_free ) ;
static gsize regex_initialized ;
static GRegex * safe_chars_regex ;
if ( g_once_init_enter ( & regex_initialized ) )
{
safe_chars_regex = g_regex_new ( " ^[[:alnum:]-._]+$ " , 0 , 0 , NULL ) ;
g_assert ( safe_chars_regex ) ;
g_once_init_leave ( & regex_initialized , 1 ) ;
}
for ( char * * iter = ( char * * ) pkgs ; iter & & * iter ; iter + + )
{
if ( omit_pkgs ! = NULL & & g_strv_contains ( omit_pkgs , * iter ) )
continue ;
/* don't quote if it just has common pkgname/shell-safe chars */
if ( g_regex_match ( safe_chars_regex , * iter , 0 , 0 ) )
g_ptr_array_add ( packages_sorted , g_strdup ( * iter ) ) ;
else
g_ptr_array_add ( packages_sorted , g_shell_quote ( * iter ) ) ;
}
if ( packages_sorted - > len > 0 )
{
g_ptr_array_sort ( packages_sorted , rpmostree_ptrarray_sort_compare_strings ) ;
g_ptr_array_add ( packages_sorted , NULL ) ;
packages_joined = g_strjoinv ( " " , ( char * * ) packages_sorted - > pdata ) ;
print_kv ( k , max_key_len , packages_joined ) ;
}
}
2017-03-03 23:48:56 +03:00
static const gchar * *
lookup_array_and_canonicalize ( GVariantDict * dict ,
const char * key )
{
g_autofree const gchar * * ret = NULL ;
if ( g_variant_dict_lookup ( dict , key , " ^a&s " , & ret ) )
{
/* Canonicalize length 0 strv to NULL */
if ( ! * ret )
g_clear_pointer ( & ret , g_free ) ;
}
return g_steal_pointer ( & ret ) ;
}
2016-05-31 20:12:36 +03:00
/* We will have an optimized path for the case where there are just
* two deployments , this code will be the generic fallback .
*/
static gboolean
2016-06-01 20:17:20 +03:00
status_generic ( RPMOSTreeSysroot * sysroot_proxy ,
RPMOSTreeOS * os_proxy ,
GVariant * deployments ,
GCancellable * cancellable ,
GError * * error )
2014-06-11 21:47:10 +04:00
{
2015-09-08 17:31:22 +03:00
GVariantIter iter ;
2016-06-01 20:17:20 +03:00
gboolean first = TRUE ;
2016-06-01 20:53:49 +03:00
const int is_tty = isatty ( 1 ) ;
2016-06-09 22:47:05 +03:00
const char * bold_prefix = is_tty ? " \x1b [1m " : " " ;
const char * bold_suffix = is_tty ? " \x1b [0m " : " " ;
const char * red_prefix = is_tty ? " \x1b [31m " : " " ;
const char * red_suffix = is_tty ? " \x1b [22m " : " " ;
2016-06-14 16:41:36 +03:00
GVariant * txn = get_active_txn ( sysroot_proxy ) ;
if ( txn )
{
const char * method , * sender , * path ;
g_variant_get ( txn , " (&s&s&s) " , & method , & sender , & path ) ;
g_print ( " State: transaction: %s %s %s \n " , method , sender , path ) ;
}
else
g_print ( " State: idle \n " ) ;
g_print ( " Deployments: \n " ) ;
2014-06-13 21:16:43 +04:00
2015-09-08 17:31:22 +03:00
g_variant_iter_init ( & iter , deployments ) ;
2016-06-01 20:17:20 +03:00
while ( TRUE )
2014-06-11 21:47:10 +04:00
{
2016-06-01 20:17:20 +03:00
g_autoptr ( GVariant ) child = g_variant_iter_next_value ( & iter ) ;
g_autoptr ( GVariantDict ) dict = NULL ;
2017-02-24 17:44:40 +03:00
gboolean is_locally_assembled = FALSE ;
g_autofree const gchar * * origin_packages = NULL ;
g_autofree const gchar * * origin_requested_packages = NULL ;
2017-03-03 23:48:56 +03:00
g_autofree const gchar * * origin_requested_local_packages = NULL ;
2016-06-01 20:17:20 +03:00
const gchar * origin_refspec ;
const gchar * id ;
const gchar * os_name ;
const gchar * checksum ;
const gchar * version_string ;
2016-06-01 20:53:49 +03:00
const gchar * unlocked ;
2016-07-21 23:28:20 +03:00
gboolean gpg_enabled ;
2017-01-04 20:29:01 +03:00
gboolean regenerate_initramfs ;
2016-06-01 20:17:20 +03:00
guint64 t = 0 ;
int serial ;
gboolean is_booted ;
2017-01-27 07:31:53 +03:00
const gboolean was_first = first ;
const guint max_key_len = strlen ( " PendingBaseVersion " ) ;
2016-06-01 20:17:20 +03:00
g_autoptr ( GVariant ) signatures = NULL ;
g_autofree char * timestamp_string = NULL ;
2015-09-08 17:31:22 +03:00
2016-06-01 20:17:20 +03:00
if ( child = = NULL )
break ;
2017-03-03 23:48:56 +03:00
dict = g_variant_dict_new ( child ) ;
2016-06-01 20:17:20 +03:00
/* osname should always be present. */
g_assert ( g_variant_dict_lookup ( dict , " osname " , " &s " , & os_name ) ) ;
g_assert ( g_variant_dict_lookup ( dict , " id " , " &s " , & id ) ) ;
g_assert ( g_variant_dict_lookup ( dict , " serial " , " i " , & serial ) ) ;
2016-12-05 23:21:32 +03:00
g_assert ( g_variant_dict_lookup ( dict , " checksum " , " &s " , & checksum ) ) ;
2016-06-01 20:17:20 +03:00
g_assert ( g_variant_dict_lookup ( dict , " timestamp " , " t " , & t ) ) ;
{ g_autoptr ( GDateTime ) timestamp = g_date_time_new_from_unix_utc ( t ) ;
if ( timestamp ! = NULL )
timestamp_string = g_date_time_format ( timestamp , " %Y-%m-%d %T " ) ;
else
timestamp_string = g_strdup_printf ( " (invalid timestamp) " ) ;
}
2017-02-24 17:44:40 +03:00
2016-12-05 23:21:32 +03:00
if ( g_variant_dict_lookup ( dict , " origin " , " &s " , & origin_refspec ) )
2014-06-12 18:32:21 +04:00
{
2017-03-03 23:48:56 +03:00
origin_packages =
lookup_array_and_canonicalize ( dict , " packages " ) ;
origin_requested_packages =
lookup_array_and_canonicalize ( dict , " requested-packages " ) ;
origin_requested_local_packages =
lookup_array_and_canonicalize ( dict , " requested-local-packages " ) ;
2014-06-12 18:32:21 +04:00
}
2016-06-01 20:17:20 +03:00
else
origin_refspec = NULL ;
if ( ! g_variant_dict_lookup ( dict , " version " , " &s " , & version_string ) )
version_string = NULL ;
2016-06-01 20:53:49 +03:00
if ( ! g_variant_dict_lookup ( dict , " unlocked " , " &s " , & unlocked ) )
unlocked = NULL ;
2015-09-08 17:31:22 +03:00
2017-01-04 20:29:01 +03:00
if ( ! g_variant_dict_lookup ( dict , " regenerate-initramfs " , " b " , & regenerate_initramfs ) )
regenerate_initramfs = FALSE ;
2016-06-01 20:17:20 +03:00
signatures = g_variant_dict_lookup_value ( dict , " signatures " ,
G_VARIANT_TYPE ( " av " ) ) ;
2015-09-08 17:31:22 +03:00
2016-06-01 20:17:20 +03:00
if ( first )
first = FALSE ;
else
g_print ( " \n " ) ;
2015-07-16 21:11:35 +03:00
2016-06-25 00:40:46 +03:00
if ( ! g_variant_dict_lookup ( dict , " booted " , " b " , & is_booted ) )
is_booted = FALSE ;
2015-06-13 03:19:16 +03:00
2016-06-01 20:17:20 +03:00
g_print ( " %s " , is_booted ? libsd_special_glyph ( BLACK_CIRCLE ) : " " ) ;
2015-09-08 17:31:22 +03:00
2016-06-01 20:17:20 +03:00
if ( origin_refspec )
g_print ( " %s " , origin_refspec ) ;
else
g_print ( " %s " , checksum ) ;
g_print ( " \n " ) ;
2017-02-24 17:44:40 +03:00
2016-06-01 20:17:20 +03:00
if ( version_string )
2015-02-11 21:06:43 +03:00
{
2016-06-09 22:47:05 +03:00
g_autofree char * version_time
= g_strdup_printf ( " %s%s%s (%s) " , bold_prefix , version_string ,
bold_suffix , timestamp_string ) ;
2016-06-01 20:17:20 +03:00
print_kv ( " Version " , max_key_len , version_time ) ;
2015-02-11 21:06:43 +03:00
}
else
{
2016-06-01 20:17:20 +03:00
print_kv ( " Timestamp " , max_key_len , timestamp_string ) ;
2015-02-11 21:06:43 +03:00
}
2017-02-24 17:44:40 +03:00
if ( g_variant_dict_contains ( dict , " base-checksum " ) )
2014-06-13 21:16:43 +04:00
{
2016-06-01 20:17:20 +03:00
const char * base_checksum ;
g_assert ( g_variant_dict_lookup ( dict , " base-checksum " , " &s " , & base_checksum ) ) ;
print_kv ( " BaseCommit " , max_key_len , base_checksum ) ;
2017-02-24 17:44:40 +03:00
is_locally_assembled = TRUE ;
2014-06-12 18:32:21 +04:00
}
2016-06-01 20:17:20 +03:00
print_kv ( " Commit " , max_key_len , checksum ) ;
2017-01-27 07:31:53 +03:00
/* Show any difference between the baseref vs head, but only for the
booted commit , and only if there isn ' t a pending deployment . Otherwise
it ' s either unnecessary or too noisy .
*/
if ( is_booted & & was_first )
{
const gchar * pending_checksum = NULL ;
const gchar * pending_version = NULL ;
if ( g_variant_dict_lookup ( dict , " pending-base-checksum " , " &s " , & pending_checksum ) )
{
print_kv ( is_locally_assembled ? " PendingBaseCommit " : " PendingCommit " ,
max_key_len , pending_checksum ) ;
g_assert ( g_variant_dict_lookup ( dict , " pending-base-timestamp " , " t " , & t ) ) ;
g_variant_dict_lookup ( dict , " pending-base-version " , " &s " , & pending_version ) ;
if ( pending_version )
{
g_autoptr ( GDateTime ) timestamp = g_date_time_new_from_unix_utc ( t ) ;
g_autofree char * version_time = NULL ;
if ( timestamp ! = NULL )
timestamp_string = g_date_time_format ( timestamp , " %Y-%m-%d %T " ) ;
else
timestamp_string = g_strdup_printf ( " (invalid timestamp) " ) ;
version_time = g_strdup_printf ( " %s (%s) " , pending_version , timestamp_string ) ;
print_kv ( is_locally_assembled ? " PendingBaseVersion " : " PendingVersion " ,
max_key_len , version_time ) ;
}
}
}
2016-06-01 20:17:20 +03:00
print_kv ( " OSName " , max_key_len , os_name ) ;
2014-06-12 18:32:21 +04:00
2016-07-21 23:28:20 +03:00
if ( ! g_variant_dict_lookup ( dict , " gpg-enabled " , " b " , & gpg_enabled ) )
gpg_enabled = FALSE ;
if ( gpg_enabled )
2014-06-13 21:16:43 +04:00
{
2016-07-21 23:28:20 +03:00
if ( signatures )
{
guint n_sigs = g_variant_n_children ( signatures ) ;
g_autofree char * gpgheader = g_strdup_printf ( " %u signature%s " , n_sigs ,
n_sigs = = 1 ? " " : " s " ) ;
const guint gpgpad = max_key_len + 4 ;
char gpgspaces [ gpgpad + 1 ] ;
memset ( gpgspaces , ' ' , gpgpad ) ;
gpgspaces [ gpgpad ] = ' \0 ' ;
print_kv ( " GPGSignature " , max_key_len , gpgheader ) ;
rpmostree_print_signatures ( signatures , gpgspaces ) ;
}
else
{
print_kv ( " GPGSignature " , max_key_len , " (unsigned) " ) ;
}
2014-06-13 21:16:43 +04:00
}
2014-06-11 21:47:10 +04:00
2017-02-24 17:44:40 +03:00
/* let's be nice and only print requested - layered, rather than repeating
* the ones in layered twice */
if ( origin_requested_packages )
print_packages ( " RequestedPackages " , max_key_len ,
origin_requested_packages , origin_packages ) ;
2017-03-03 23:48:56 +03:00
2016-06-01 20:17:20 +03:00
if ( origin_packages )
2017-02-24 17:44:40 +03:00
print_packages ( " LayeredPackages " , max_key_len ,
origin_packages , NULL ) ;
2017-01-04 20:29:01 +03:00
2017-03-03 23:48:56 +03:00
if ( origin_requested_local_packages )
print_packages ( " LocalPackages " , max_key_len ,
origin_requested_local_packages , NULL ) ;
2017-01-04 20:29:01 +03:00
if ( regenerate_initramfs )
{
g_autoptr ( GString ) buf = g_string_new ( " " ) ;
g_autofree char * * initramfs_args = NULL ;
g_variant_dict_lookup ( dict , " initramfs-args " , " ^a&s " , & initramfs_args ) ;
for ( char * * iter = initramfs_args ; iter & & * iter ; iter + + )
{
g_string_append ( buf , * iter ) ;
g_string_append_c ( buf , ' ' ) ;
}
if ( buf - > len = = 0 )
g_string_append ( buf , " regenerate " ) ;
print_kv ( " Initramfs " , max_key_len , buf - > str ) ;
}
2016-06-01 20:53:49 +03:00
if ( unlocked & & g_strcmp0 ( unlocked , " none " ) ! = 0 )
{
2016-06-09 22:47:05 +03:00
g_print ( " %s%s " , red_prefix , bold_prefix ) ;
2016-06-01 20:53:49 +03:00
print_kv ( " Unlocked " , max_key_len , unlocked ) ;
2016-06-09 22:47:05 +03:00
g_print ( " %s%s " , bold_suffix , red_suffix ) ;
2016-06-01 20:53:49 +03:00
}
2015-04-07 20:49:21 +03:00
}
2016-05-31 20:12:36 +03:00
return TRUE ;
}
int
rpmostree_builtin_status ( int argc ,
char * * argv ,
GCancellable * cancellable ,
GError * * error )
{
int exit_status = EXIT_FAILURE ;
2016-12-05 23:20:23 +03:00
g_autoptr ( GOptionContext ) context = g_option_context_new ( " - Get the version of the booted system " ) ;
2016-05-31 20:12:36 +03:00
glnx_unref_object RPMOSTreeOS * os_proxy = NULL ;
glnx_unref_object RPMOSTreeSysroot * sysroot_proxy = NULL ;
g_autoptr ( GVariant ) deployments = NULL ;
2015-06-13 03:19:16 +03:00
2016-05-31 20:12:36 +03:00
if ( ! rpmostree_option_context_parse ( context ,
option_entries ,
& argc , & argv ,
RPM_OSTREE_BUILTIN_FLAG_NONE ,
cancellable ,
& sysroot_proxy ,
error ) )
goto out ;
if ( ! rpmostree_load_os_proxy ( sysroot_proxy , NULL ,
cancellable , & os_proxy , error ) )
goto out ;
deployments = rpmostree_sysroot_dup_deployments ( sysroot_proxy ) ;
2016-06-09 23:00:24 +03:00
if ( opt_json )
{
2016-06-14 15:34:07 +03:00
glnx_unref_object JsonBuilder * builder = json_builder_new ( ) ;
glnx_unref_object JsonGenerator * generator = json_generator_new ( ) ;
JsonNode * deployments_node = json_gvariant_serialize ( deployments ) ;
JsonNode * json_root ;
2016-06-14 16:41:36 +03:00
JsonNode * txn_node ;
2016-06-14 15:34:07 +03:00
glnx_unref_object GOutputStream * stdout_gio = g_unix_output_stream_new ( 1 , FALSE ) ;
2016-06-14 16:41:36 +03:00
GVariant * txn = get_active_txn ( sysroot_proxy ) ;
2016-06-14 15:34:07 +03:00
json_builder_begin_object ( builder ) ;
json_builder_set_member_name ( builder , " deployments " ) ;
json_builder_add_value ( builder , deployments_node ) ;
2016-06-14 16:41:36 +03:00
json_builder_set_member_name ( builder , " transaction " ) ;
if ( txn )
txn_node = json_gvariant_serialize ( txn ) ;
else
txn_node = json_node_new ( JSON_NODE_NULL ) ;
json_builder_add_value ( builder , txn_node ) ;
2016-06-14 15:34:07 +03:00
json_builder_end_object ( builder ) ;
json_root = json_builder_get_root ( builder ) ;
json_generator_set_root ( generator , json_root ) ;
json_node_free ( json_root ) ;
2016-09-23 21:31:09 +03:00
/* NB: watch out for the misleading API docs */
if ( json_generator_to_stream ( generator , stdout_gio , NULL , error ) < = 0
| | ( error ! = NULL & & * error ! = NULL ) )
2016-06-14 15:34:07 +03:00
goto out ;
2016-06-09 23:00:24 +03:00
}
else
{
if ( ! status_generic ( sysroot_proxy , os_proxy , deployments ,
cancellable , error ) )
goto out ;
}
2016-05-31 20:12:36 +03:00
exit_status = EXIT_SUCCESS ;
2015-06-13 03:19:16 +03:00
out :
2015-08-05 04:09:54 +03:00
/* Does nothing if using the message bus. */
rpmostree_cleanup_peer ( ) ;
2015-06-13 03:19:16 +03:00
2015-11-02 23:43:58 +03:00
return exit_status ;
2014-06-11 21:47:10 +04:00
}