diff --git a/.gitignore b/.gitignore index c060c0e..d7b357f 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ doc/Doxyfile doc/html patches src/*.pc +*.pyc diff --git a/CMakeLists.txt b/CMakeLists.txt index 5834cb7..9ebfaa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,10 +128,17 @@ install(DIRECTORY files/schema/ PATTERN "*.schema" ) -# install the script file +# install script files webos_configure_source_files(LS-CONTROL files/scripts/public/ls-control) install(PROGRAMS ${LS-CONTROL} DESTINATION ${WEBOS_INSTALL_SBINDIR} ${LS2_PERMS}) +install(PROGRAMS files/scripts/public/volatile_dir_control.py DESTINATION ${WEBOS_INSTALL_SBINDIR} ${LS2_PERMS}) + +set(DYNAMIC_OVERLAY_CONF_DIR "/var/run/ls2") +set(LS_SETTINGS_DIR "${WEBOS_INSTALL_SYSCONFDIR}/luna-service2") +webos_configure_source_files(VOLATILE-DIR-CONTROL files/scripts/public/volatile_dir_control_settings.py) +install(PROGRAMS ${VOLATILE-DIR-CONTROL} DESTINATION ${WEBOS_INSTALL_SBINDIR} ${LS2_PERMS}) + # Create the permanent service directories. The ones for downloaded services (called # "dynamic" but not to be confused with services that are started on-demand, which # are known as "dynamic services") are created at boot time by the ls-hubd-private diff --git a/files/conf/ls-private.conf.in b/files/conf/ls-private.conf.in index 0bff430..b33ca9e 100644 --- a/files/conf/ls-private.conf.in +++ b/files/conf/ls-private.conf.in @@ -1,6 +1,6 @@ # @@@LICENSE # -# Copyright (c) 2008-2013 LG Electronics, Inc. +# Copyright (c) 2008-2014 LG Electronics, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ # LICENSE@@@ [General] +Overlay=@DYNAMIC_OVERLAY_CONF_DIR@ LocalSocketDirectory=@CONF_GENERAL_LOCAL_SOCKET_DIRECTORY@ PidDirectory=@CONF_GENERAL_PID_DIRECTORY@ LogServiceStatus=@CONF_GENERAL_LOG_SERVICE_STATUS@ diff --git a/files/conf/ls-public.conf.in b/files/conf/ls-public.conf.in index d7d6639..5efcfc4 100644 --- a/files/conf/ls-public.conf.in +++ b/files/conf/ls-public.conf.in @@ -1,6 +1,6 @@ # @@@LICENSE # -# Copyright (c) 2008-2013 LG Electronics, Inc. +# Copyright (c) 2008-2014 LG Electronics, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ # LICENSE@@@ [General] +Overlay=@DYNAMIC_OVERLAY_CONF_DIR@ LocalSocketDirectory=@CONF_GENERAL_LOCAL_SOCKET_DIRECTORY@ PidDirectory=@CONF_GENERAL_PID_DIRECTORY@ LogServiceStatus=@CONF_GENERAL_LOG_SERVICE_STATUS@ diff --git a/files/scripts/public/volatile_dir_control.py b/files/scripts/public/volatile_dir_control.py new file mode 100755 index 0000000..93e1c70 --- /dev/null +++ b/files/scripts/public/volatile_dir_control.py @@ -0,0 +1,111 @@ +#! /usr/bin/python2 +# @@@LICENSE +# +# Copyright (c) 2014 LG Electronics, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# LICENSE@@@ +import os, sys +import ConfigParser +import optparse +import fcntl + +USAGE = '''%s [OPTIONS] + -a, --add # adds directory to read permissions from + -r, --remove # removes directory to read permissions from + -b, --bus-type # public or private (mandatory option) +''' % sys.argv[0] + +GENERAL_SECTION = 'General' +DYNAMIC_SERVICES_SECTION = 'Dynamic Services' +OVERLAY_DIR_OPTION = 'Overlay' +VOLATILE_DIRS_OPTION = 'VolatileDirectories' + +class ConfigModify : + + def __init__(self, config_file_name, + readonly_conf_dir, # '/etc/luna-service2' + dynamic_conf_dir): # '/var/run/ls2' + self.dynamic_conf_path = os.path.join(dynamic_conf_dir, config_file_name) + self.parser = ConfigParser.SafeConfigParser() + self.parser.read(self.dynamic_conf_path) + + if not self.parser.has_section(DYNAMIC_SERVICES_SECTION) : + self.parser.add_section(DYNAMIC_SERVICES_SECTION) + + if self.parser.has_option(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION) : + effective_parser = self.parser + else: + effective_parser = ConfigParser.SafeConfigParser() + effective_parser.read(os.path.join(readonly_conf_dir, config_file_name)) + + self.volatile = effective_parser.get(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION).split(';') + + def add(self, path): + self.volatile.append(path) + self.set_volatile_dirs() + + def set_volatile_dirs(self): + self.parser.set(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION, ';'.join(self.volatile)) + + def remove(self, path): + self.volatile.remove(path) + self.set_volatile_dirs() + + def __del__(self) : + tmp_write_path = self.dynamic_conf_path + '~' + with os.fdopen(os.open(tmp_write_path, + os.O_WRONLY | os.O_CREAT | os.O_TRUNC, + 0600), + 'w') as dynamic_conf_file : + self.parser.write(dynamic_conf_file) + os.rename(tmp_write_path, self.dynamic_conf_path) + +def main(): + parser = optparse.OptionParser(usage=USAGE) + parser.add_option("-a", "--add", + action="store", type="string", dest="add") + parser.add_option("-r", "--remove", + action="store", type="string", dest="remove") + parser.add_option("-b", "--bus-type", + action="store", type="string", dest="bus") + + options, args = parser.parse_args() + if not options.bus or not (options.add or options.remove) : + parser.error('FAILED wrong arguments %s' % ' '.join(args)) + sys.exit(-1) + + CONFIG_FILE_NAME = 'ls-' + options.bus + '.conf' + + import volatile_dir_control_settings + readonly_conf_dir = volatile_dir_control_settings.CONST_CONF_DIR + readonly_conf_parser = ConfigParser.SafeConfigParser() + readonly_conf_parser.read(os.path.join(readonly_conf_dir, CONFIG_FILE_NAME)) + dynamic_conf_dir = readonly_conf_parser.get(GENERAL_SECTION, OVERLAY_DIR_OPTION) + + fp = open(os.path.join(dynamic_conf_dir, '.volatile-dir-control.lock'), 'w') + fcntl.flock(fp, fcntl.LOCK_EX) + + if options.add: + ConfigModify(CONFIG_FILE_NAME, readonly_conf_dir, dynamic_conf_dir).add(options.add) + elif options.remove: + ConfigModify(CONFIG_FILE_NAME, readonly_conf_dir, dynamic_conf_dir).remove(options.remove) + else: + print USAGE; + sys.exit(-1) + + sys.exit(0) + +if __name__ == '__main__': + main() diff --git a/files/scripts/public/volatile_dir_control_settings.py.in b/files/scripts/public/volatile_dir_control_settings.py.in new file mode 100644 index 0000000..fd98326 --- /dev/null +++ b/files/scripts/public/volatile_dir_control_settings.py.in @@ -0,0 +1,22 @@ +#! /usr/bin/python2 +# @@@LICENSE +# +# Copyright (c) 2014 LG Electronics, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# LICENSE@@@ +CONST_CONF_DIR = '@LS_SETTINGS_DIR@' + +if __name__ == '__main__': + print "CONST_CONF_DIR = " + CONST_CONF_DIR diff --git a/src/ls-hubd/conf.c b/src/ls-hubd/conf.c index 64b8fef..a15dfaf 100644 --- a/src/ls-hubd/conf.c +++ b/src/ls-hubd/conf.c @@ -47,6 +47,8 @@ #define MAX_KEYS_IN_GROUP 15 #define MAX_GROUPS 10 +#define OVERLAY_CONF_DIR_PERMISSIONS 700 + /** * Default values for the two special "exePath" values recognized for JS services and mojo apps */ @@ -115,6 +117,7 @@ void _ConfigFreeSettings(void); * Keyfile format: * * [General] + * Overlay=/path/to/some/dir * LocalSocketDirectory=/path/to/some/dir * PidDirectory=/path/to/some/dir * LogServiceStatus=false @@ -145,6 +148,12 @@ static _ConfigDOM conf_file_dom = { { .group_name = "General", .keys = { + { + .key = "Overlay", + .get_value = _ConfigKeyGetString, + .user_cb = (_ConfigKeyUser*)_ConfigKeySetString, + .user_ctxt = &g_conf_config_overlay_dir, + }, { .key = "LocalSocketDirectory", .get_value = _ConfigKeyGetString, @@ -316,6 +325,7 @@ char *g_conf_dynamic_service_exec_prefix = NULL; /**< prefix added to Exec in se when launching dynamic service */ int g_conf_connect_timeout_ms = 20000; /**< timeout in ms for connect() to complete */ char *g_conf_monitor_exe_path = NULL; /**< path to ls-monitor */ +char *g_conf_config_overlay_dir = NULL; /**< path to configuration overlay directory for g_conf_conf_overlay_dir files */ char *g_conf_monitor_pub_exe_path = NULL; /**< path to ls-monitor-pub */ char *g_conf_sysmgr_exe_path = NULL; /**< path to LunaSysMgr */ char *g_conf_webappmgr_exe_path = NULL; /**< path to WebAppMgr (with the separation of LunaSysMgr and WebAppMgr into two separate components, @@ -330,6 +340,7 @@ char *g_conf_local_socket_path = NULL; /**< directory that contains dom /* static -- local to this file */ static char *config_file_path = NULL; /**< full path to config file */ +static char *config_overlay_path = NULL; /**< path to config overlay file */ static char *config_file_name = NULL; /**< filename only */ static gchar **service_volatile_dirs = NULL; /**< volatile directories with service description files*/ extern gchar **roles_volatile_dirs; /**< volatile directories with role files*/ @@ -839,6 +850,9 @@ _ConfigFreeSettings(void) g_free(g_conf_mojo_app_exe_path); } g_conf_mojo_app_exe_path = NULL; + + g_free(g_conf_config_overlay_dir); + g_conf_config_overlay_dir = NULL; } static bool @@ -1007,12 +1021,6 @@ _ConfigParseFile(const char *path, const _ConfigDOM *dom, LSError *lserror) LOG_LS_DEBUG("%s: parsing file: \"%s\"\n", __func__, path); - /* Free old settings */ - _ConfigFreeSettings(); - - /* Set the defaults */ - ConfigSetDefaults(); - key_file = g_key_file_new(); if (!g_key_file_load_from_file(key_file, path, G_KEY_FILE_NONE, &gerror)) @@ -1079,7 +1087,22 @@ ConfigParseFile(const char *path, LSError *lserror) config_file_name = g_path_get_basename(path); } - return _ConfigParseFile(path, &conf_file_dom, lserror); + _ConfigFreeSettings(); + + ConfigSetDefaults(); + + bool result = _ConfigParseFile(path, &conf_file_dom, lserror); + if (result && g_conf_config_overlay_dir) + { + // read overlay config + config_overlay_path = g_strdup_printf("%s/%s", g_conf_config_overlay_dir, config_file_name); + if (g_mkdir_with_parents(g_conf_config_overlay_dir, OVERLAY_CONF_DIR_PERMISSIONS) == 0) + { + _ConfigParseFile(path, &conf_file_dom, lserror); + } + } + + return result; } void @@ -1087,6 +1110,7 @@ ConfigCleanup() { g_free(config_file_path); g_free(config_file_name); + g_free(config_overlay_path); _ConfigFreeSettings(); diff --git a/src/ls-hubd/conf.h b/src/ls-hubd/conf.h index 66844f4..ef2cc97 100644 --- a/src/ls-hubd/conf.h +++ b/src/ls-hubd/conf.h @@ -40,6 +40,7 @@ extern bool g_conf_security_enabled; extern bool g_conf_log_service_status; extern int g_conf_connect_timeout_ms; extern char* g_conf_monitor_exe_path; +extern char* g_conf_config_overlay_dir; extern char* g_conf_monitor_pub_exe_path; extern char* g_conf_sysmgr_exe_path; extern char* g_conf_webappmgr_exe_path; diff --git a/src/ls-hubd/test/CMakeLists.txt b/src/ls-hubd/test/CMakeLists.txt index 5961713..f044281 100644 --- a/src/ls-hubd/test/CMakeLists.txt +++ b/src/ls-hubd/test/CMakeLists.txt @@ -34,3 +34,6 @@ foreach (TEST ${UNIT_TEST_SOURCES}) target_link_libraries(${TEST} ls-hublib ${CMAKE_PROJECT_NAME} ${TESTLIBNAME} ${PMLOGLIB_LDFLAGS}) add_test(${TEST} ${TEST}) endforeach () + +add_test(NAME volatile-dir-control + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/volatile_dir_control_test.py --config-dir ${CMAKE_BINARY_DIR}/Configured/files/conf/) diff --git a/src/ls-hubd/test/volatile_dir_control_test.py b/src/ls-hubd/test/volatile_dir_control_test.py new file mode 100755 index 0000000..6b243b8 --- /dev/null +++ b/src/ls-hubd/test/volatile_dir_control_test.py @@ -0,0 +1,134 @@ +#! /usr/bin/python2 +# @@@LICENSE +# +# Copyright (c) 2014 LG Electronics, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# LICENSE@@@ +import sys, os, shutil +import unittest +import tempfile +import ConfigParser +import optparse +from tempfile import tempdir +DYNAMIC_SERVICES_SECTION = 5; +sys.path.append(os.path.join(os.path.dirname(__file__), '../../../files/scripts/public')) +from volatile_dir_control import ConfigModify, DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION + +PRIV_CONF_NAME = 'ls-private.conf' +PUB_CONF_NAME = 'ls-public.conf' + +def get_file_content(path): + with open(path) as f: + return f.read() + +class TestVolatileDirControl(unittest.TestCase): + + def setUp(self): + self.conf_dir_name = tempfile.mkdtemp(dir='/tmp/') + + def tearDown(self): + shutil.rmtree(self.conf_dir_name) + + def _test_const_conf(self, conf_file_name): + self.readonly_conf_parser = ConfigParser.SafeConfigParser() + self.readonly_conf_parser.read(os.path.join(options.readonly_config_dir, conf_file_name)) + self.assert_(self.readonly_conf_parser.has_section(DYNAMIC_SERVICES_SECTION)) + self.assert_(self.readonly_conf_parser.has_option(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION)) + + def test_const_conf(self): + self._test_const_conf(PRIV_CONF_NAME) + self._test_const_conf(PUB_CONF_NAME) + + def _test_add(self, conf_name): + self._test_const_conf(conf_name) + + conf_parser = ConfigParser.SafeConfigParser() + conf_parser.read(os.path.join(self.conf_dir_name, conf_name)) + + if conf_parser.has_section(DYNAMIC_SERVICES_SECTION) and conf_parser.has_option(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION) : + was = conf_parser.get(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION) + else: + was = self.readonly_conf_parser.get(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION) + + ConfigModify(conf_name, options.readonly_config_dir, self.conf_dir_name).add(self.conf_dir_name) + conf_parser.read(os.path.join(self.conf_dir_name, conf_name)) + self.assert_(conf_parser.has_section(DYNAMIC_SERVICES_SECTION)) + self.assert_(conf_parser.has_option(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION)) + + become = conf_parser.get(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION) + self.assertEqual(was + ';' + self.conf_dir_name, + become, + "wrong value in new file: " + become) + + def test_add_private(self): + self._test_add(PRIV_CONF_NAME) + + def test_add_public(self): + self._test_add(PUB_CONF_NAME) + + def _test_remove(self, conf_name): + self._test_add(conf_name) + ConfigModify(conf_name, options.readonly_config_dir, self.conf_dir_name).remove(self.conf_dir_name) + + conf_parser = ConfigParser.SafeConfigParser() + conf_parser.read(os.path.join(self.conf_dir_name, conf_name)) + self.assert_(conf_parser.has_section(DYNAMIC_SERVICES_SECTION)) + self.assert_(conf_parser.has_option(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION)) + + was = self.readonly_conf_parser.get(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION) + become = conf_parser.get(DYNAMIC_SERVICES_SECTION, VOLATILE_DIRS_OPTION) + self.assertEqual(was, + become.replace(';'+self.conf_dir_name, ''), + "wrong value in new file: " + become) + + def test_remove_private(self): + self._test_remove(PRIV_CONF_NAME) + + def test_remove_public(self): + self._test_remove(PUB_CONF_NAME) + + def _test_consistency(self, conf_name): + const_conf_was = get_file_content(os.path.join(options.readonly_config_dir, conf_name)) + self._test_const_conf(conf_name) + self._test_add(conf_name) + + const_conf_become = get_file_content(os.path.join(options.readonly_config_dir, conf_name)) + self.assertEqual(const_conf_was, const_conf_become, 'Readonly conf changed') + + self._test_remove(conf_name) + + def test_consistency_pub(self): + self._test_consistency(PUB_CONF_NAME) + + def test_consistency_priv(self): + self._test_consistency(PRIV_CONF_NAME) + + +USAGE = '''%s [OPTIONS] + -c, --config-dir # sets readonly config path (no changes) +''' % sys.argv[0] + +if __name__ == '__main__': + parser = optparse.OptionParser(usage=USAGE) + parser.add_option("-c", "--config-dir", + action="store", type="string", dest="readonly_config_dir") + + global options + options, args = parser.parse_args() + if not options.readonly_config_dir: + parser.error('FAILED wrong arguments %s' % ' '.join(args)) + sys.exit(-1) + + unittest.TestProgram('__main__', None, [sys.argv[0], '-v'])