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-07-02 06:07:56 +04:00
# include <sys/mount.h>
2014-05-03 18:05:43 +04:00
# include <gio/gunixoutputstream.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"
2014-01-29 23:37:44 +04:00
# include "rpmostree-postprocess.h"
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 ;
static char * opt_proxy ;
static char * opt_repo ;
2014-07-11 22:02:45 +04:00
static char * * opt_override_pkg_repos ;
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-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-05-03 14:55:35 +04:00
{ " cachedir " , 0 , 0 , G_OPTION_ARG_STRING , & opt_cachedir , " Cached state " , " CACHEDIR " } ,
{ " 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 " } ,
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 }
} ;
2014-05-18 22:13:31 +04:00
typedef struct {
GPtrArray * treefile_context_dirs ;
2014-09-07 20:38:34 +04:00
GBytes * serialized_treefile ;
2014-05-18 22:13:31 +04:00
} RpmOstreeTreeComposeContext ;
2014-02-26 02:07:59 +04:00
static char *
subprocess_context_print_args ( GSSubprocessContext * ctx )
{
GString * ret = g_string_new ( " " ) ;
gs_strfreev char * * argv = NULL ;
char * * strviter ;
g_object_get ( ( GObject * ) ctx , " argv " , & argv , NULL ) ;
for ( strviter = argv ; strviter & & * strviter ; strviter + + )
{
gs_free char * quoted = g_shell_quote ( * strviter ) ;
2014-05-06 01:57:16 +04:00
g_string_append_c ( ret , ' ' ) ;
2014-02-26 02:07:59 +04:00
g_string_append ( ret , quoted ) ;
}
return g_string_free ( ret , FALSE ) ;
}
2014-03-29 04:15:43 +04:00
static gboolean
object_get_optional_string_member ( JsonObject * object ,
const char * member_name ,
const char * * out_value ,
GError * * error )
{
gboolean ret = FALSE ;
JsonNode * node = json_object_get_member ( object , member_name ) ;
if ( node ! = NULL )
{
* out_value = json_node_get_string ( node ) ;
if ( ! * out_value )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Member '%s' is not a string " , member_name ) ;
goto out ;
}
}
else
* out_value = NULL ;
ret = TRUE ;
out :
return ret ;
}
2014-05-03 14:55:35 +04:00
static const char *
object_require_string_member ( JsonObject * object ,
const char * member_name ,
GError * * error )
{
const char * ret ;
if ( ! object_get_optional_string_member ( object , member_name , & ret , error ) )
return NULL ;
if ( ! ret )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Member '%s' not found " , member_name ) ;
return NULL ;
}
return ret ;
}
2014-02-13 03:26:31 +04:00
static const char *
array_require_string_element ( JsonArray * array ,
guint i ,
GError * * error )
{
const char * ret = json_array_get_string_element ( array , i ) ;
if ( ! ret )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Element at index %u is not a string " , i ) ;
return NULL ;
}
return ret ;
}
static gboolean
append_string_array_to ( JsonObject * object ,
const char * member_name ,
GPtrArray * array ,
GError * * error )
{
JsonArray * jarray = json_object_get_array_member ( object , member_name ) ;
guint i , len ;
if ( ! jarray )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" No member '%s' found " , member_name ) ;
return FALSE ;
}
2014-01-26 19:13:45 +04:00
2014-02-13 03:26:31 +04:00
len = json_array_get_length ( jarray ) ;
2014-01-26 19:13:45 +04:00
for ( i = 0 ; i < len ; i + + )
{
2014-02-13 03:26:31 +04:00
const char * v = array_require_string_element ( jarray , i , error ) ;
if ( ! v )
return FALSE ;
g_ptr_array_add ( array , g_strdup ( v ) ) ;
2014-01-26 19:13:45 +04:00
}
2014-02-13 03:26:31 +04:00
return TRUE ;
2014-01-26 19:13:45 +04:00
}
typedef struct {
GSSubprocess * process ;
GFile * tmp_reposdir_path ;
GDataOutputStream * stdin ;
/* GDataInputStream *stdout; */
} YumContext ;
2014-01-24 22:34:19 +04:00
2014-01-26 19:13:45 +04:00
static gboolean
yum_context_close ( YumContext * yumctx ,
GCancellable * cancellable ,
GError * * error )
2014-01-24 22:34:19 +04:00
{
2014-01-26 19:13:45 +04:00
gboolean ret = FALSE ;
2014-01-24 22:34:19 +04:00
2014-01-26 19:13:45 +04:00
if ( ! yumctx )
return TRUE ;
2014-01-24 22:34:19 +04:00
2014-01-26 19:13:45 +04:00
if ( yumctx - > process )
{
if ( yumctx - > stdin )
{
if ( ! g_output_stream_close ( ( GOutputStream * ) yumctx - > stdin , cancellable , error ) )
goto out ;
g_clear_object ( & yumctx - > stdin ) ;
}
/*
if ( yumctx - > stdout )
{
if ( ! g_input_stream_close ( ( GInputStream * ) yumctx - > stdout , cancellable , error ) )
goto out ;
g_clear_object ( & yumctx - > stdout ) ;
}
*/
g_print ( " Waiting for yum... \n " ) ;
if ( ! gs_subprocess_wait_sync_check ( yumctx - > process , cancellable , error ) )
goto out ;
g_print ( " Waiting for yum [OK] \n " ) ;
g_clear_object ( & yumctx - > process ) ;
}
2014-01-24 22:34:19 +04:00
2014-01-26 19:13:45 +04:00
ret = TRUE ;
out :
2014-01-24 22:34:19 +04:00
return ret ;
}
2014-01-26 19:13:45 +04:00
static void
yum_context_free ( YumContext * yumctx )
{
if ( ! yumctx )
return ;
( void ) yum_context_close ( yumctx , NULL , NULL ) ;
g_free ( yumctx ) ;
}
2014-02-26 02:07:59 +04:00
static inline
void cleanup_keyfile_unref ( void * loc )
{
GKeyFile * locp = * ( ( GKeyFile * * ) loc ) ;
if ( locp )
g_key_file_unref ( locp ) ;
}
2014-02-13 03:26:31 +04:00
static gboolean
2014-05-18 22:13:31 +04:00
append_repo_and_cache_opts ( RpmOstreeTreeComposeContext * self ,
JsonObject * treedata ,
2014-05-03 14:55:35 +04:00
GFile * workdir ,
2014-02-13 03:26:31 +04:00
GPtrArray * args ,
2014-02-26 02:07:59 +04:00
GCancellable * cancellable ,
2014-02-13 03:26:31 +04:00
GError * * error )
2014-01-24 22:34:19 +04:00
{
2014-02-13 03:26:31 +04:00
gboolean ret = FALSE ;
2014-02-26 02:07:59 +04:00
JsonArray * enable_repos = NULL ;
2014-05-18 22:13:31 +04:00
guint i ;
2014-07-11 22:02:45 +04:00
char * * iter ;
2014-02-26 02:07:59 +04:00
gs_unref_object GFile * yumcache_lookaside = NULL ;
gs_unref_object GFile * repos_tmpdir = NULL ;
2014-07-11 22:02:45 +04:00
gs_unref_ptrarray GPtrArray * reposdir_args = g_ptr_array_new_with_free_func ( g_free ) ;
2014-02-26 02:07:59 +04:00
2014-05-03 14:55:35 +04:00
yumcache_lookaside = g_file_resolve_relative_path ( workdir , " yum-cache " ) ;
2014-02-26 02:07:59 +04:00
if ( ! gs_file_ensure_directory ( yumcache_lookaside , TRUE , cancellable , error ) )
goto out ;
2014-05-03 14:55:35 +04:00
repos_tmpdir = g_file_resolve_relative_path ( workdir , " tmp-repos " ) ;
2014-02-26 02:07:59 +04:00
if ( ! gs_shutil_rm_rf ( repos_tmpdir , cancellable , error ) )
goto out ;
if ( ! gs_file_ensure_directory ( repos_tmpdir , TRUE , cancellable , error ) )
goto out ;
2014-01-26 19:13:45 +04:00
if ( g_getenv ( " RPM_OSTREE_OFFLINE " ) )
g_ptr_array_add ( args , g_strdup ( " -C " ) ) ;
2014-05-18 17:29:51 +04:00
{
const char * proxy ;
if ( opt_proxy )
proxy = opt_proxy ;
else
proxy = g_getenv ( " http_proxy " ) ;
2014-07-11 22:02:45 +04:00
2014-05-18 17:29:51 +04:00
if ( proxy )
g_ptr_array_add ( args , g_strconcat ( " --setopt=proxy= " , proxy , NULL ) ) ;
}
2014-05-03 14:55:35 +04:00
2014-07-11 22:02:45 +04:00
g_ptr_array_add ( args , g_strdup ( " --disablerepo=* " ) ) ;
/* Add the directory for each treefile to the reposdir argument */
for ( i = 0 ; i < self - > treefile_context_dirs - > len ; i + + )
{
GFile * contextdir = self - > treefile_context_dirs - > pdata [ i ] ;
g_ptr_array_add ( reposdir_args , g_file_get_path ( contextdir ) ) ;
}
/* Process local overrides */
for ( iter = opt_override_pkg_repos ; iter & & * iter ; iter + + )
{
const char * repodir = * iter ;
gs_free char * bn = g_path_get_basename ( repodir ) ;
gs_free char * reponame = g_strconcat ( " rpm-ostree-override- " , repodir , NULL ) ;
gs_free char * baseurl = g_strconcat ( " file:// " , repodir , NULL ) ;
gs_free char * tmprepo_filename = g_strconcat ( reponame , " .repo " , NULL ) ;
gs_unref_object GFile * tmprepo_path = g_file_get_child ( repos_tmpdir , tmprepo_filename ) ;
__attribute__ ( ( cleanup ( cleanup_keyfile_unref ) ) ) GKeyFile * keyfile = NULL ;
gs_free char * data = NULL ;
gsize len ;
keyfile = g_key_file_new ( ) ;
g_key_file_set_string ( keyfile , reponame , " name " , reponame ) ;
g_key_file_set_string ( keyfile , reponame , " baseurl " , baseurl ) ;
data = g_key_file_to_data ( keyfile , & len , NULL ) ;
if ( ! g_file_replace_contents ( tmprepo_path , data , len , NULL , FALSE , 0 , NULL ,
cancellable , error ) )
goto out ;
g_ptr_array_add ( args , g_strconcat ( " --enablerepo= " , reponame , NULL ) ) ;
}
if ( opt_override_pkg_repos )
g_ptr_array_add ( reposdir_args , g_file_get_path ( repos_tmpdir ) ) ;
2014-05-18 22:13:31 +04:00
{
gboolean first = TRUE ;
2014-07-11 22:02:45 +04:00
GString * reposdir_value = g_string_new ( " --setopt=reposdir= " ) ;
for ( i = 0 ; i < reposdir_args - > len ; i + + )
2014-05-18 22:13:31 +04:00
{
2014-07-11 22:02:45 +04:00
const char * reponame = reposdir_args - > pdata [ i ] ;
2014-05-18 22:13:31 +04:00
if ( first )
first = FALSE ;
else
g_string_append_c ( reposdir_value , ' , ' ) ;
2014-07-11 22:02:45 +04:00
g_string_append ( reposdir_value , reponame ) ;
2014-05-18 22:13:31 +04:00
}
g_ptr_array_add ( args , g_string_free ( reposdir_value , FALSE ) ) ;
}
2014-02-26 02:07:59 +04:00
if ( json_object_has_member ( treedata , " repos " ) )
enable_repos = json_object_get_array_member ( treedata , " repos " ) ;
2014-02-13 03:26:31 +04:00
if ( enable_repos )
{
guint i ;
guint n = json_array_get_length ( enable_repos ) ;
for ( i = 0 ; i < n ; i + + )
{
const char * reponame = array_require_string_element ( enable_repos , i , error ) ;
if ( ! reponame )
goto out ;
g_ptr_array_add ( args , g_strconcat ( " --enablerepo= " , reponame , NULL ) ) ;
}
}
2014-01-31 04:04:58 +04:00
2014-06-19 19:57:33 +04:00
g_ptr_array_add ( args , g_strdup ( " --setopt=keepcache=0 " ) ) ;
2014-02-26 02:07:59 +04:00
g_ptr_array_add ( args , g_strconcat ( " --setopt=cachedir= " ,
gs_file_get_path_cached ( yumcache_lookaside ) ,
NULL ) ) ;
2014-02-13 03:26:31 +04:00
ret = TRUE ;
out :
return ret ;
2014-01-26 19:13:45 +04:00
}
static YumContext *
2014-05-18 22:13:31 +04:00
yum_context_new ( RpmOstreeTreeComposeContext * self ,
JsonObject * treedata ,
2014-02-13 03:26:31 +04:00
GFile * yumroot ,
2014-05-03 14:55:35 +04:00
GFile * workdir ,
2014-01-26 19:13:45 +04:00
GCancellable * cancellable ,
GError * * error )
{
gboolean success = FALSE ;
YumContext * yumctx = NULL ;
GPtrArray * yum_argv = g_ptr_array_new_with_free_func ( g_free ) ;
2014-01-24 22:34:19 +04:00
gs_unref_object GSSubprocessContext * context = NULL ;
gs_unref_object GSSubprocess * yum_process = NULL ;
2014-01-26 19:13:45 +04:00
g_ptr_array_add ( yum_argv , g_strdup ( " yum " ) ) ;
g_ptr_array_add ( yum_argv , g_strdup ( " -y " ) ) ;
2014-01-31 04:04:58 +04:00
2014-05-18 22:13:31 +04:00
if ( ! append_repo_and_cache_opts ( self , treedata , workdir , yum_argv ,
2014-02-26 02:07:59 +04:00
cancellable , error ) )
2014-02-13 03:26:31 +04:00
goto out ;
2014-01-31 04:04:58 +04:00
2014-01-26 19:13:45 +04:00
g_ptr_array_add ( yum_argv , g_strconcat ( " --installroot= " ,
gs_file_get_path_cached ( yumroot ) ,
NULL ) ) ;
2014-01-24 22:34:19 +04:00
2014-01-26 19:13:45 +04:00
g_ptr_array_add ( yum_argv , g_strdup ( " shell " ) ) ;
2014-01-24 22:34:19 +04:00
g_ptr_array_add ( yum_argv , NULL ) ;
context = gs_subprocess_context_new ( ( char * * ) yum_argv - > pdata ) ;
{
gs_strfreev char * * duped_environ = g_get_environ ( ) ;
duped_environ = g_environ_setenv ( duped_environ , " OSTREE_KERNEL_INSTALL_NOOP " , " 1 " , TRUE ) ;
2014-02-21 23:56:12 +04:00
/* See fedora's kernel.spec */
duped_environ = g_environ_setenv ( duped_environ , " HARDLINK " , " no " , TRUE ) ;
2014-01-24 22:34:19 +04:00
gs_subprocess_context_set_environment ( context , duped_environ ) ;
}
2014-05-05 16:34:45 +04:00
gs_subprocess_context_set_stdin_disposition ( context , GS_SUBPROCESS_STREAM_DISPOSITION_PIPE ) ;
/* gs_subprocess_context_set_stdout_disposition (context, GS_SUBPROCESS_STREAM_DISPOSITION_PIPE); */
yumctx = g_new0 ( YumContext , 1 ) ;
2014-02-26 02:07:59 +04:00
{
gs_free char * cmdline = subprocess_context_print_args ( context ) ;
g_print ( " Starting %s \n " , cmdline ) ;
}
2014-01-26 19:13:45 +04:00
yumctx - > process = gs_subprocess_new ( context , cancellable , error ) ;
if ( ! yumctx - > process )
2014-01-24 22:34:19 +04:00
goto out ;
2014-01-26 19:13:45 +04:00
yumctx - > stdin = ( GDataOutputStream * ) g_data_output_stream_new ( gs_subprocess_get_stdin_pipe ( yumctx - > process ) ) ;
/* yumctx->stdout = (GDataInputStream*)g_data_input_stream_new (gs_subprocess_get_stdout_pipe (yumctx->process)); */
success = TRUE ;
out :
if ( ! success )
2014-01-24 22:34:19 +04:00
{
2014-01-26 19:13:45 +04:00
yum_context_free ( yumctx ) ;
return NULL ;
2014-01-24 22:34:19 +04:00
}
2014-01-26 19:13:45 +04:00
return yumctx ;
}
2014-01-24 22:34:19 +04:00
2014-01-26 19:13:45 +04:00
static gboolean
yum_context_command ( YumContext * yumctx ,
const char * cmd ,
GPtrArray * * out_lines ,
GCancellable * cancellable ,
GError * * error )
{
gboolean ret = FALSE ;
gsize bytes_written ;
gs_unref_ptrarray GPtrArray * lines = g_ptr_array_new_with_free_func ( g_free ) ;
gs_free char * cmd_nl = g_strconcat ( cmd , " \n " , NULL ) ;
g_print ( " yum> %s " , cmd_nl ) ;
if ( ! g_output_stream_write_all ( ( GOutputStream * ) yumctx - > stdin ,
cmd_nl , strlen ( cmd_nl ) , & bytes_written ,
cancellable , error ) )
2014-01-24 22:34:19 +04:00
goto out ;
ret = TRUE ;
2014-01-26 19:13:45 +04:00
gs_transfer_out_value ( out_lines , & lines ) ;
2014-01-24 22:34:19 +04:00
out :
return ret ;
}
2014-01-26 19:13:45 +04:00
2014-01-24 22:34:19 +04:00
static gboolean
2014-05-18 22:13:31 +04:00
yuminstall ( RpmOstreeTreeComposeContext * self ,
JsonObject * treedata ,
2014-02-13 03:26:31 +04:00
GFile * yumroot ,
2014-05-03 14:55:35 +04:00
GFile * workdir ,
2014-01-26 19:13:45 +04:00
char * * packages ,
GCancellable * cancellable ,
GError * * error )
2014-01-24 22:34:19 +04:00
{
gboolean ret = FALSE ;
char * * strviter ;
2014-01-26 19:13:45 +04:00
YumContext * yumctx ;
2014-01-24 22:34:19 +04:00
2014-05-18 22:13:31 +04:00
yumctx = yum_context_new ( self , treedata , yumroot , workdir , cancellable , error ) ;
2014-01-26 19:13:45 +04:00
if ( ! yumctx )
goto out ;
2014-01-24 22:34:19 +04:00
for ( strviter = packages ; strviter & & * strviter ; strviter + + )
{
2014-01-26 19:13:45 +04:00
gs_free char * cmd = NULL ;
2014-01-24 22:34:19 +04:00
const char * package = * strviter ;
2014-01-26 19:13:45 +04:00
gs_unref_ptrarray GPtrArray * lines = NULL ;
2014-01-24 22:34:19 +04:00
if ( g_str_has_prefix ( package , " @ " ) )
2014-01-26 19:13:45 +04:00
cmd = g_strconcat ( " group install " , package , NULL ) ;
2014-01-24 22:34:19 +04:00
else
2014-01-26 19:13:45 +04:00
cmd = g_strconcat ( " install " , package , NULL ) ;
if ( ! yum_context_command ( yumctx , cmd , & lines ,
cancellable , error ) )
goto out ;
2014-01-24 22:34:19 +04:00
}
2014-01-26 19:13:45 +04:00
{
gs_unref_ptrarray GPtrArray * lines = NULL ;
if ( ! yum_context_command ( yumctx , " run " , & lines ,
cancellable , error ) )
goto out ;
}
if ( ! yum_context_close ( yumctx , cancellable , error ) )
2014-01-24 22:34:19 +04:00
goto out ;
ret = TRUE ;
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
{
gs_unref_object GFile * parent = g_file_get_parent ( treefile_path ) ;
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-05-03 18:05:43 +04:00
if ( ! object_get_optional_string_member ( root , " include " , & include_path , error ) )
goto out ;
if ( include_path )
{
gs_unref_object GFile * treefile_dirpath = g_file_get_parent ( treefile_path ) ;
gs_unref_object GFile * parent_path = g_file_resolve_relative_path ( treefile_dirpath , include_path ) ;
gs_unref_object JsonParser * parent_parser = json_parser_new ( ) ;
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-05-16 01:46:51 +04:00
static char *
cachedir_fssafe_key ( const char * primary_key )
{
GString * ret = g_string_new ( " " ) ;
for ( ; * primary_key ; primary_key + + )
{
const char c = * primary_key ;
if ( ! g_ascii_isprint ( c ) | | c = = ' - ' )
g_string_append_printf ( ret , " \\ %02x " , c ) ;
else if ( c = = ' / ' )
g_string_append_c ( ret , ' - ' ) ;
else
g_string_append_c ( ret , c ) ;
}
return g_string_free ( ret , FALSE ) ;
}
static GFile *
cachedir_keypath ( GFile * cachedir ,
const char * primary_key )
{
gs_free char * fssafe_key = cachedir_fssafe_key ( primary_key ) ;
return g_file_get_child ( cachedir , fssafe_key ) ;
}
static gboolean
cachedir_lookup_string ( GFile * cachedir ,
const char * key ,
char * * out_value ,
GCancellable * cancellable ,
GError * * error )
{
gboolean ret = FALSE ;
gs_free char * ret_value = NULL ;
if ( cachedir )
{
gs_unref_object GFile * keypath = cachedir_keypath ( cachedir , key ) ;
if ( ! _rpmostree_file_load_contents_utf8_allow_noent ( keypath , & ret_value ,
cancellable , error ) )
goto out ;
}
ret = TRUE ;
gs_transfer_out_value ( out_value , & ret_value ) ;
out :
return ret ;
}
static gboolean
cachedir_set_string ( GFile * cachedir ,
const char * key ,
const char * value ,
GCancellable * cancellable ,
GError * * error )
{
gboolean ret = FALSE ;
gs_unref_object GFile * keypath = NULL ;
if ( ! cachedir )
return TRUE ;
keypath = cachedir_keypath ( cachedir , key ) ;
if ( ! g_file_replace_contents ( keypath , value , strlen ( value ) , NULL ,
FALSE , 0 , NULL ,
cancellable , error ) )
goto out ;
ret = TRUE ;
out :
return ret ;
}
static gboolean
2014-09-07 20:38:34 +04:00
compute_checksum_for_compose ( RpmOstreeTreeComposeContext * self ,
JsonObject * treefile_rootval ,
2014-05-16 01:46:51 +04:00
GFile * yumroot ,
char * * out_checksum ,
GCancellable * cancellable ,
GError * * error )
{
gboolean ret = FALSE ;
gs_free char * ret_checksum = NULL ;
GChecksum * checksum = g_checksum_new ( G_CHECKSUM_SHA256 ) ;
{
gsize len ;
2014-09-07 20:38:34 +04:00
const guint8 * buf = g_bytes_get_data ( self - > serialized_treefile , & len ) ;
2014-05-16 01:46:51 +04:00
2014-09-07 20:38:34 +04:00
g_checksum_update ( checksum , buf , len ) ;
2014-05-16 01:46:51 +04:00
}
2014-06-06 22:51:18 +04:00
/* Query the generated rpmdb, to see if anything has changed. */
2014-05-16 01:46:51 +04:00
{
int estatus ;
2014-06-06 22:51:18 +04:00
gs_free char * yumroot_var_lib_rpm =
g_build_filename ( gs_file_get_path_cached ( yumroot ) ,
" var/lib/rpm " ,
NULL ) ;
const char * rpmqa_argv [ ] = { PKGLIBDIR " /rpmqa-sorted-and-clean " ,
yumroot_var_lib_rpm ,
NULL } ;
2014-05-16 01:46:51 +04:00
gs_free char * rpmqa_result = NULL ;
if ( ! g_spawn_sync ( NULL , ( char * * ) rpmqa_argv , NULL ,
G_SPAWN_SEARCH_PATH , NULL , NULL ,
& rpmqa_result , NULL , & estatus , error ) )
goto out ;
if ( ! g_spawn_check_exit_status ( estatus , error ) )
2014-06-06 22:51:18 +04:00
{
g_prefix_error ( error , " Executing %s: " ,
rpmqa_argv [ 0 ] ) ;
goto out ;
}
2014-05-29 22:57:55 +04:00
if ( ! * rpmqa_result )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
2014-06-06 22:51:18 +04:00
" Empty result from %s " , rpmqa_argv [ 0 ] ) ;
2014-05-29 22:57:55 +04:00
goto out ;
}
2014-05-16 01:46:51 +04:00
g_checksum_update ( checksum , ( guint8 * ) rpmqa_result , strlen ( rpmqa_result ) ) ;
}
ret_checksum = g_strdup ( g_checksum_get_string ( checksum ) ) ;
ret = TRUE ;
gs_transfer_out_value ( out_checksum , & ret_checksum ) ;
out :
if ( checksum ) g_checksum_free ( checksum ) ;
return ret ;
}
2014-03-22 23:05:41 +04:00
gboolean
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
{
2014-03-22 23:05:41 +04:00
gboolean ret = FALSE ;
2014-01-24 22:34:19 +04:00
GOptionContext * context = g_option_context_new ( " - Run yum and commit the result to an OSTree repository " ) ;
const char * ref ;
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 ;
2014-02-26 02:07:59 +04:00
JsonArray * units = NULL ;
2014-02-13 03:26:31 +04:00
guint len ;
2014-05-16 01:47:47 +04:00
guint i ;
2014-01-24 22:34:19 +04:00
gs_free char * ref_unix = NULL ;
2014-05-16 01:46:51 +04:00
gs_free char * cachekey = NULL ;
gs_free char * cached_compose_checksum = NULL ;
gs_free char * new_compose_checksum = NULL ;
2014-05-03 14:55:35 +04:00
gs_unref_object GFile * workdir = NULL ;
2014-01-24 22:34:19 +04:00
gs_unref_object GFile * cachedir = NULL ;
gs_unref_object GFile * yumroot = NULL ;
gs_unref_object GFile * targetroot = NULL ;
gs_unref_object GFile * yumroot_varcache = NULL ;
2014-01-29 23:37:44 +04:00
gs_unref_object OstreeRepo * repo = NULL ;
2014-02-13 03:26:31 +04:00
gs_unref_ptrarray GPtrArray * bootstrap_packages = NULL ;
gs_unref_ptrarray GPtrArray * packages = NULL ;
gs_unref_object GFile * treefile_path = NULL ;
2014-05-03 14:55:35 +04:00
gs_unref_object GFile * repo_path = NULL ;
2014-02-13 03:26:31 +04:00
gs_unref_object JsonParser * treefile_parser = 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
g_option_context_add_main_entries ( context , option_entries , NULL ) ;
if ( ! g_option_context_parse ( context , & argc , & argv , error ) )
goto out ;
2014-03-22 23:05:41 +04:00
if ( argc < 2 )
2014-01-24 22:34:19 +04:00
{
2014-05-26 23:05:08 +04:00
g_printerr ( " usage: rpm-ostree compose tree TREEFILE \n " ) ;
2014-01-24 22:34:19 +04:00
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Option processing failed " ) ;
goto out ;
}
2014-05-03 14:55:35 +04:00
if ( ! opt_repo )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" --repo must be specified " ) ;
goto out ;
}
repo_path = g_file_new_for_path ( opt_repo ) ;
repo = ostree_repo_new ( repo_path ) ;
if ( ! ostree_repo_open ( repo , cancellable , error ) )
goto out ;
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 )
{
workdir = g_file_new_for_path ( opt_workdir ) ;
}
else
{
gs_free char * tmpd = g_mkdtemp ( g_strdup ( " /var/tmp/rpm-ostree.XXXXXX " ) ) ;
workdir = g_file_new_for_path ( tmpd ) ;
workdir_is_tmp = TRUE ;
2014-07-02 06:07:56 +04:00
if ( opt_workdir_tmpfs )
{
/* Use a private mount namespace to avoid polluting the global
* namespace , and to ensure the mount gets cleaned up if we exit
* unexpectedly .
*/
if ( unshare ( CLONE_NEWNS ) ! = 0 )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" unshare(CLONE_NEWNS): %s " , g_strerror ( errno ) ) ;
goto out ;
}
if ( mount ( NULL , " / " , " none " , MS_PRIVATE | MS_REC , NULL ) = = - 1 )
{
int errsv = errno ;
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" mount(/, MS_PRIVATE | MS_REC): %s " ,
g_strerror ( errsv ) ) ;
goto out ;
}
if ( mount ( " tmpfs " , tmpd , " tmpfs " , 0 , ( const void * ) " mode=755 " ) ! = 0 )
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" mount(tmpfs): %s " , g_strerror ( errno ) ) ;
goto out ;
}
}
2014-05-03 14:55:35 +04:00
}
2014-05-16 01:46:51 +04:00
if ( opt_cachedir )
2014-06-07 02:25:08 +04:00
{
cachedir = g_file_new_for_path ( opt_cachedir ) ;
if ( ! gs_file_ensure_directory ( cachedir , FALSE , cancellable , error ) )
goto out ;
}
2014-05-16 01:46:51 +04:00
2014-05-03 14:55:35 +04:00
if ( chdir ( gs_file_get_path_cached ( workdir ) ) ! = 0 )
2014-01-24 22:34:19 +04:00
{
2014-02-13 03:26:31 +04:00
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Failed to chdir to '%s': %s " ,
opt_workdir , strerror ( errno ) ) ;
goto out ;
2014-01-24 22:34:19 +04:00
}
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 )
{
gs_unref_object JsonGenerator * generator = json_generator_new ( ) ;
gs_unref_object GOutputStream * stdout = g_unix_output_stream_new ( 1 , FALSE ) ;
json_generator_set_pretty ( generator , TRUE ) ;
json_generator_set_root ( generator , treefile_rootval ) ;
( void ) json_generator_to_stream ( generator , stdout , NULL , NULL ) ;
ret = TRUE ;
goto out ;
}
2014-02-13 03:26:31 +04:00
2014-05-03 14:55:35 +04:00
yumroot = g_file_get_child ( workdir , " rootfs.tmp " ) ;
2014-01-24 22:34:19 +04:00
if ( ! gs_shutil_rm_rf ( yumroot , cancellable , error ) )
goto out ;
2014-05-03 14:55:35 +04:00
targetroot = g_file_get_child ( workdir , " rootfs " ) ;
2014-01-24 22:34:19 +04:00
2014-02-13 03:26:31 +04:00
ref = object_require_string_member ( treefile , " ref " , error ) ;
if ( ! ref )
goto out ;
2014-01-26 19:13:45 +04:00
ref_unix = g_strdelimit ( g_strdup ( ref ) , " / " , ' _ ' ) ;
2014-01-24 22:34:19 +04:00
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-07-11 02:28:22 +04:00
if ( ! append_string_array_to ( treefile , " bootstrap_packages " , packages , error ) )
2014-02-13 03:26:31 +04:00
goto out ;
if ( ! append_string_array_to ( treefile , " packages " , packages , error ) )
goto out ;
g_ptr_array_add ( packages , NULL ) ;
2014-01-24 22:34:19 +04:00
2014-09-30 00:24:53 +04:00
{
gs_unref_object JsonGenerator * generator = json_generator_new ( ) ;
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 ) ;
}
2014-05-18 22:13:31 +04:00
if ( ! yuminstall ( self , treefile , yumroot , workdir ,
2014-07-11 02:28:22 +04:00
( char * * ) packages - > pdata ,
2014-05-16 01:47:47 +04:00
cancellable , error ) )
goto out ;
2014-05-16 01:46:51 +04:00
cachekey = g_strconcat ( " treecompose/ " , ref , NULL ) ;
if ( ! cachedir_lookup_string ( cachedir , cachekey ,
& cached_compose_checksum ,
cancellable , error ) )
goto out ;
2014-09-07 20:38:34 +04:00
if ( ! compute_checksum_for_compose ( self , treefile , yumroot ,
2014-05-16 01:46:51 +04:00
& new_compose_checksum ,
cancellable , error ) )
goto out ;
if ( g_strcmp0 ( cached_compose_checksum , new_compose_checksum ) = = 0 )
{
g_print ( " No changes to input, reusing cached commit \n " ) ;
ret = TRUE ;
goto out ;
}
2014-05-16 01:47:47 +04:00
ref_unix = g_strdelimit ( g_strdup ( ref ) , " / " , ' _ ' ) ;
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-10-16 06:10:15 +04:00
{
const char * boot_location_str = NULL ;
RpmOstreePostprocessBootLocation boot_location =
RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH ;
if ( ! object_get_optional_string_member ( treefile , " boot_location " ,
& boot_location_str , error ) )
goto out ;
if ( boot_location_str ! = NULL )
{
if ( strcmp ( boot_location_str , " legacy " ) = = 0 )
boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_LEGACY ;
else if ( strcmp ( boot_location_str , " both " ) = = 0 )
boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_BOTH ;
else if ( strcmp ( boot_location_str , " new " ) = = 0 )
boot_location = RPMOSTREE_POSTPROCESS_BOOT_LOCATION_NEW ;
else
{
g_set_error ( error , G_IO_ERROR , G_IO_ERROR_FAILED ,
" Invalid boot location '%s' " , boot_location_str ) ;
goto out ;
}
}
if ( ! rpmostree_postprocess ( yumroot , boot_location , cancellable , error ) )
goto out ;
}
2014-01-26 19:13:45 +04:00
2014-05-16 01:47:47 +04:00
if ( json_object_has_member ( treefile , " units " ) )
units = json_object_get_array_member ( treefile , " units " ) ;
2014-01-26 19:13:45 +04:00
2014-05-16 01:47:47 +04:00
if ( units )
len = json_array_get_length ( units ) ;
else
len = 0 ;
2014-01-26 19:13:45 +04:00
2014-05-16 01:47:47 +04:00
{
gs_unref_object GFile * multiuser_wants_dir =
g_file_resolve_relative_path ( yumroot , " usr/etc/systemd/system/multi-user.target.wants " ) ;
2014-01-30 03:37:05 +04:00
2014-05-16 01:47:47 +04:00
if ( ! gs_file_ensure_directory ( multiuser_wants_dir , TRUE , cancellable , error ) )
2014-01-30 03:12:50 +04:00
goto out ;
2014-05-16 01:47:47 +04:00
for ( i = 0 ; i < len ; i + + )
{
const char * unitname = array_require_string_element ( units , i , error ) ;
gs_unref_object GFile * unit_link_target = NULL ;
gs_free char * symlink_target = NULL ;
2014-02-13 03:26:31 +04:00
2014-05-16 01:47:47 +04:00
if ( ! unitname )
goto out ;
2014-02-13 03:26:31 +04:00
2014-05-16 01:47:47 +04:00
symlink_target = g_strconcat ( " /usr/lib/systemd/system/ " , unitname , NULL ) ;
unit_link_target = g_file_get_child ( multiuser_wants_dir , unitname ) ;
2014-02-28 19:44:43 +04:00
2014-05-16 01:47:47 +04:00
if ( g_file_query_file_type ( unit_link_target , G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS , NULL ) = = G_FILE_TYPE_SYMBOLIC_LINK )
continue ;
2014-02-28 19:44:43 +04:00
2014-05-16 01:47:47 +04:00
g_print ( " Adding %s to multi-user.target.wants \n " , unitname ) ;
2014-02-28 19:44:43 +04:00
2014-05-16 01:47:47 +04:00
if ( ! g_file_make_symbolic_link ( unit_link_target , symlink_target ,
cancellable , error ) )
goto out ;
}
}
2014-02-13 03:26:31 +04:00
2014-05-16 01:47:47 +04:00
{
gs_unref_object GFile * target_treefile_dir_path =
g_file_resolve_relative_path ( yumroot , " usr/share/rpm-ostree " ) ;
gs_unref_object GFile * target_treefile_path =
g_file_get_child ( target_treefile_dir_path , " treefile.json " ) ;
2014-09-07 20:38:34 +04:00
2014-05-16 01:47:47 +04:00
if ( ! gs_file_ensure_directory ( target_treefile_dir_path , TRUE ,
cancellable , error ) )
goto out ;
2014-02-13 03:26:31 +04:00
2014-05-16 01:47:47 +04:00
g_print ( " Copying '%s' to '%s' \n " ,
gs_file_get_path_cached ( treefile_path ) ,
gs_file_get_path_cached ( target_treefile_path ) ) ;
2014-09-30 00:24:53 +04:00
{
gsize len ;
const guint8 * buf = g_bytes_get_data ( self - > serialized_treefile , & len ) ;
if ( ! g_file_replace_contents ( target_treefile_path , ( char * ) buf , len ,
NULL , FALSE , G_FILE_CREATE_REPLACE_DESTINATION ,
NULL , cancellable , error ) )
goto out ;
}
2014-05-16 01:47:47 +04:00
}
2014-06-17 21:31:23 +04:00
{
const char * default_target = NULL ;
if ( ! object_get_optional_string_member ( treefile , " default_target " ,
& default_target , error ) )
goto out ;
if ( default_target ! = NULL )
{
gs_unref_object GFile * default_target_path =
g_file_resolve_relative_path ( yumroot , " usr/etc/systemd/system/default.target " ) ;
2014-10-14 16:28:17 +04:00
gs_free char * dest_default_target_path =
g_strconcat ( " /usr/lib/systemd/system/ " , default_target , NULL ) ;
2014-06-17 21:31:23 +04:00
( void ) gs_file_unlink ( default_target_path , NULL , NULL ) ;
2014-10-14 16:28:17 +04:00
if ( ! g_file_make_symbolic_link ( default_target_path , dest_default_target_path ,
2014-06-17 21:31:23 +04:00
cancellable , error ) )
goto out ;
}
}
2014-03-29 04:15:43 +04:00
2014-05-16 01:47:47 +04:00
{
const char * gpgkey ;
if ( ! object_get_optional_string_member ( treefile , " gpg_key " , & gpgkey , error ) )
goto out ;
2014-03-29 04:15:43 +04:00
2014-05-16 01:47:47 +04:00
if ( ! rpmostree_commit ( yumroot , repo , ref , gpgkey ,
json_object_get_boolean_member ( treefile , " selinux " ) ,
cancellable , error ) )
2014-01-29 23:37:44 +04:00
goto out ;
2014-01-24 22:34:19 +04:00
}
2014-05-16 01:46:51 +04:00
if ( ! cachedir_set_string ( cachedir , cachekey ,
new_compose_checksum ,
cancellable , error ) )
goto out ;
2014-01-24 22:34:19 +04:00
g_print ( " Complete \n " ) ;
out :
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 )
( void ) umount ( gs_file_get_path_cached ( workdir ) ) ;
( void ) gs_shutil_rm_rf ( workdir , NULL , NULL ) ;
}
2014-05-18 22:13:31 +04:00
if ( self )
{
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 ) ;
}
2014-03-22 23:05:41 +04:00
return ret ;
2014-01-24 22:34:19 +04:00
}