After having installed an update, an external agent (listening
on the progress interface) gets notified and should decide on
further actions to take if any, e.g., shutdown all running
applications and reboot.
Enabling the CONFIG_POSTUPDATE option allows to specify a fallback
command (CONFIG_POSTUPDATE_COMMAND) to be executed via system(3) if
(1) no external agent is registered and
(2) the --postupdate command line option is given.
Suricatta has been adapted to use this new method instead of
unconditionally executing a reboot.
Signed-off-by: Christian Storm <
christi...@siemens.com>
Signed-off-by: Silvano Cirujano Cuesta <
silvano.cir...@siemens.com>
---
Kconfig | 22 +++++++++++++
core/Makefile | 1 +
core/swupdate.c | 15 +++++++++
core/swupdate_postupdate.c | 60 +++++++++++++++++++++++++++++++++++
corelib/progress_thread.c | 9 ++++++
doc/source/suricatta.rst | 19 ++++++++++-
doc/source/swupdate.rst | 5 +++
include/progress.h | 2 ++
include/suricatta/state.h | 3 ++
include/swupdate.h | 1 +
suricatta/server_hawkbit.c | 61 ++++++++++++++++++------------------
suricatta/state.c | 9 ++++++
suricatta/test/Makefile | 15 +++++++--
suricatta/test/test.h | 9 ++++++
suricatta/test/test_server_hawkbit.c | 32 ++++++++++++++++---
15 files changed, 225 insertions(+), 38 deletions(-)
create mode 100644 core/swupdate_postupdate.c
diff --git a/Kconfig b/Kconfig
index c863adf..98bd65d 100644
--- a/Kconfig
+++ b/Kconfig
@@ -70,6 +70,28 @@ config SCRIPTS
in the image. For security reason, this option
can be switched off.
+config POSTUPDATE
+ bool "enable post-update command execution"
+ default y
+ help
+ After having installed an update, an external agent (listening
+ on the progress interface) gets notified and should decide on
+ further actions to take if any, e.g., shutdown all running
+ applications and reboot.
+
+ Enabling this option allows to specify a fallback command to be
+ executed if (1) no external agent is registered and (2) the
+ --postupdate command line option is given.
+
+config POSTUPDATE_COMMAND
+ string "post-update command"
+ default "reboot"
+ depends on POSTUPDATE
+ help
+ The command to be executed if no external agent is registered
+ and the --postupdate command line option is given.
+ The command is executed via SYSTEM(3)
+
config HW_COMPATIBILITY
bool "check for hardware / software compatibility"
default n
diff --git a/core/Makefile b/core/Makefile
index 659f55b..afe017a 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -16,3 +16,4 @@ obj-y += swupdate.o \
parser.o \
pctl.o \
syslog.o \
+ swupdate_postupdate.o \
diff --git a/core/swupdate.c b/core/swupdate.c
index 9567b09..644b435 100644
--- a/core/swupdate.c
+++ b/core/swupdate.c
@@ -112,10 +112,16 @@ static struct option long_options[] = {
{"webserver", required_argument, NULL, 'w'},
#endif
{"check", no_argument, NULL, 'c'},
+#ifdef CONFIG_POSTUPDATE
+ {"postupdate", no_argument, NULL, 'p'},
+#endif
{NULL, 0, NULL, 0}
};
int loglevel = ERRORLEVEL;
+#ifdef CONFIG_POSTUPDATE
+int run_postupdate_cmd = 0;
+#endif
static void usage(char *programname)
{
@@ -134,6 +140,9 @@ static void usage(char *programname)
" connection is broken (0 means undefinetly retries)\n"
" -t, --timeout : timeout to check if a connection is lost\n"
#endif
+#ifdef CONFIG_POSTUPDATE
+ " -p, --postupdate : execute post-update command if no agent is registered\n"
+#endif
" -e, --select <software>,<mode> : Select software images set and source\n"
" Ex.: stable,main\n"
" -i, --image <filename> : Software to be installed\n"
@@ -633,6 +642,11 @@ int main(int argc, char **argv)
case 'c':
opt_c = 1;
break;
+#ifdef CONFIG_POSTUPDATE
+ case 'p':
+ run_postupdate_cmd = 1;
+ break;
+#endif
default:
usage(argv[0]);
exit(1);
@@ -771,6 +785,7 @@ int main(int argc, char **argv)
cleanup_files(&swcfg);
notify(SUCCESS, 0, 0);
+ (void)swupdate_postupdate();
}
if (opt_w || opt_s || opt_u || opt_d)
diff --git a/core/swupdate_postupdate.c b/core/swupdate_postupdate.c
new file mode 100644
index 0000000..2c646a6
--- /dev/null
+++ b/core/swupdate_postupdate.c
@@ -0,0 +1,60 @@
+/*
+ * Author: Christian Storm
+ * Copyright (C) 2016, Siemens AG
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "util.h"
+#include "globals.h"
+#include "progress.h"
+
+#ifdef CONFIG_POSTUPDATE
+extern int run_postupdate_cmd;
+#endif
+
+int swupdate_postupdate(void)
+{
+ if (swupdate_progress_has_subscribers()) {
+ swupdate_progress_done();
+ DEBUG("Notified progress subscribers may take actions.\n");
+ return 0;
+ }
+
+#if defined(CONFIG_POSTUPDATE)
+ if (!run_postupdate_cmd) {
+ DEBUG("Post-update command execution not enabled.\n");
+ return 0;
+ }
+
+ int ret = system(CONFIG_POSTUPDATE_COMMAND);
+ if (WIFEXITED(ret)) {
+ DEBUG("Post-update command returned with %d",
+ WEXITSTATUS(ret));
+ } else {
+ ERROR("Post-update command returned with %d: '%s'", ret,
+ strerror(errno));
+ return -1;
+ }
+#endif
+ return 0;
+}
diff --git a/corelib/progress_thread.c b/corelib/progress_thread.c
index 6b51ec9..b7f18a8 100644
--- a/corelib/progress_thread.c
+++ b/corelib/progress_thread.c
@@ -94,6 +94,15 @@ static void send_progress_msg(void)
}
}
+bool swupdate_progress_has_subscribers(void)
+{
+ struct swupdate_progress *prbar = &progress;
+ pthread_mutex_lock(&prbar->lock);
+ bool has_subscribers = !SIMPLEQ_EMPTY(&prbar->conns);
+ pthread_mutex_unlock(&prbar->lock);
+ return has_subscribers;
+}
+
void swupdate_progress_init(unsigned int nsteps) {
struct swupdate_progress *prbar = &progress;
pthread_mutex_lock(&prbar->lock);
diff --git a/doc/source/suricatta.rst b/doc/source/suricatta.rst
index 21f7c25..b6e8650 100644
--- a/doc/source/suricatta.rst
+++ b/doc/source/suricatta.rst
@@ -48,7 +48,7 @@ a hawkBit instance at ``http://10.0.0.2:8080`` with tenant ``default``
and device ID ``25``.
-Note that initially and after having installed an update, suricatta
+Note that on startup when having installed an update, suricatta
tries to report the update status to its upstream server, e.g.,
hawkBit, prior to entering the main loop awaiting further updates.
If this initial report fails, e.g., because of a not (yet) configured
@@ -60,6 +60,23 @@ to its upstream server irrespective of the error conditions, this has
to be realized externally in terms of restarting swupdate on exit.
+After an update has been performed, an agent listening on the progress
+interface may execute post-update actions, e.g., a reboot, on receiving
+``DONE``. If no such agent is registered and running, a post-update
+action defined at compile-time via ``CONFIG_POSTUPDATE_COMMAND`` can be
+executed if (1) ``CONFIG_POSTUPDATE=y`` is set and (2) the ``-p`` command
+line option is given to SWUpdate.
+Note that currently, at least a restart of SWUpdate has to be performed
+as post-update action since only then suricatta tries to report the
+update status to its upstream server.
+Otherwise, on the next polling interval elapsed, the very same update is
+announced by hawkBit as pending and is installed again if no update
+status storage, i.e., ``CONFIG_SURICATTA_STATE_CHOICE_NONE``, is selected.
+If an update status storage, e.g., ``CONFIG_SURICATTA_STATE_CHOICE_UBOOT``,
+is selected, succinct update actions announced by hawkBit are skipped until
+a restart of SWUpdate.
+
+
Supporting different Servers
----------------------------
diff --git a/doc/source/swupdate.rst b/doc/source/swupdate.rst
index e0d986d..2de5f8e 100644
--- a/doc/source/swupdate.rst
+++ b/doc/source/swupdate.rst
@@ -546,6 +546,11 @@ Command line parameters
| | | referenced in sw-description are present. |
| | | Usage: swupdate -c -i <file> |
+-------------+----------+--------------------------------------------+
+| -p | - | Active only if CONFIG_POSTUPDATE is set. |
+| | | Execute fallback post-update command if no |
+| | | external agent listens on the progress |
+| | | interface. |
++-------------+----------+--------------------------------------------+
Changes in boot-loader code
===========================
diff --git a/include/progress.h b/include/progress.h
index 5035b58..f146772 100644
--- a/include/progress.h
+++ b/include/progress.h
@@ -22,6 +22,7 @@
#ifndef _INSTALL_PROGRESS_H
#define _INSTALL_PROGRESS_H
+#include <stdbool.h>
#include <swupdate_status.h>
#define SOCKET_PROGRESS_PATH "/tmp/swupdateprog"
@@ -47,6 +48,7 @@ void swupdate_progress_inc_step(char *image);
void swupdate_progress_step_completed(void);
void swupdate_progress_end(RECOVERY_STATUS status);
void swupdate_progress_done(void);
+bool swupdate_progress_has_subscribers(void);
void *progress_bar_thread (void *data);
diff --git a/include/suricatta/state.h b/include/suricatta/state.h
index 80243b6..2879dee 100644
--- a/include/suricatta/state.h
+++ b/include/suricatta/state.h
@@ -18,6 +18,7 @@
*/
#pragma once
+#include <stdbool.h>
#include "suricatta.h"
/* (Persistent) Update State Management Functions.
@@ -34,8 +35,10 @@ typedef enum {
STATE_TESTING = '2',
STATE_FAILED = '3',
STATE_NOT_AVAILABLE = '4',
+ STATE_ERROR = '5'
} update_state_t;
server_op_res_t save_state(char *key, update_state_t value);
server_op_res_t read_state(char *key, update_state_t *value);
server_op_res_t reset_state(char *key);
+bool is_state_valid(update_state_t state);
diff --git a/include/swupdate.h b/include/swupdate.h
index b244d3d..8911b82 100644
--- a/include/swupdate.h
+++ b/include/swupdate.h
@@ -144,5 +144,6 @@ struct swupdate_cfg {
off_t extract_sw_description(int fd, const char *descfile, off_t start);
int cpio_scan(int fd, struct swupdate_cfg *cfg, off_t start);
+int swupdate_postupdate(void);
#endif
diff --git a/suricatta/server_hawkbit.c b/suricatta/server_hawkbit.c
index fd641e9..6793d75 100644
--- a/suricatta/server_hawkbit.c
+++ b/suricatta/server_hawkbit.c
@@ -93,7 +93,6 @@ json_object *json_get_key(json_object *json_root, const char *key);
json_object *json_get_path_key(json_object *json_root, const char **json_path);
char *json_get_data_url(json_object *json_root, const char *key);
server_op_res_t map_channel_retcode(channel_op_res_t response);
-void server_reboot(void);
server_op_res_t server_handle_initial_state(update_state_t stateovrrd);
int server_update_status_callback(ipc_message *msg);
int server_update_done_callback(RECOVERY_STATUS status);
@@ -101,6 +100,7 @@ server_op_res_t server_process_update_artifact(json_object *json_data_artifact);
void suricatta_print_help(void);
server_op_res_t server_set_polling_interval(json_object *json_root);
server_op_res_t server_set_config_data(json_object *json_root);
+static update_state_t get_state(void);
server_op_res_t
server_send_deployment_reply(const int action_id, const int job_cnt_max,
const int job_cnt_cur, const char *finished,
@@ -182,17 +182,6 @@ server_op_res_t map_channel_retcode(channel_op_res_t response)
return SERVER_EERR;
}
-#ifdef UNIT_TESTING
-void server_reboot(void) { return; }
-#else
-__attribute__((noreturn)) void server_reboot(void)
-{
- (void)execl("/bin/sh", "sh", "-c", "reboot", (char *)0);
- ERROR("Cannot reboot system: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
-}
-#endif
-
server_op_res_t server_send_cancel_reply(const int action_id)
{
assert(server_hawkbit.url != NULL);
@@ -505,27 +494,43 @@ server_op_res_t server_has_pending_action(int *action_id)
(void)server_send_cancel_reply(*action_id);
result = SERVER_OK;
}
+ if ((result == SERVER_UPDATE_AVAILABLE) &&
+ (get_state() == STATE_INSTALLED)) {
+ WARN("An already installed update is pending testing, "
+ "ignoring available update action.");
+ INFO("Please restart SWUpdate to report the test results "
+ "upstream.");
+ result = SERVER_NO_UPDATE_AVAILABLE;
+ }
return result;
}
+
+static update_state_t get_state(void) {
+ update_state_t state;
+
+ if (read_state((char *)STATE_KEY, &state) != SERVER_OK) {
+ ERROR("Cannot read stored update state.\n");
+ return STATE_ERROR;
+ }
+ TRACE("Read state=%c from persistent storage.\n", state);
+
+ return is_state_valid(state) ? state : STATE_ERROR;
+}
+
server_op_res_t server_handle_initial_state(update_state_t stateovrrd)
{
update_state_t state = STATE_OK;
if (stateovrrd != STATE_NOT_AVAILABLE) {
state = stateovrrd;
TRACE("Got state=%c from command line.\n", state);
+ if (!is_state_valid(state)) {
+ return SERVER_EINIT;
+ }
} else {
- if (read_state((char *)STATE_KEY, &state) != SERVER_OK) {
- ERROR("Cannot read stored update state.\n");
- return SERVER_OK;
+ if ((state = get_state()) == STATE_ERROR) {
+ return SERVER_EINIT;
}
- TRACE("Read state=%c from persistent storage.\n", state);
- }
-
- if ((state < STATE_OK) || (state > STATE_NOT_AVAILABLE)) {
- ERROR("Unknown persistent state=%c, please fix it manually!\n",
- state);
- return SERVER_EINIT;
}
const char *reply_result;
@@ -549,6 +554,7 @@ server_op_res_t server_handle_initial_state(update_state_t stateovrrd)
break;
case STATE_OK:
case STATE_NOT_AVAILABLE:
+ default:
DEBUG("State is STATE_OK/STATE_NOT_AVAILABLE, nothing to "
"report to server.\n");
return SERVER_OK;
@@ -922,14 +928,8 @@ cleanup:
ERROR("JSON object should be freed but was not.\n");
}
if (result == SERVER_OK) {
- /* TODO Notify running applications of successful installation
- * and a pending reboot via IPC and time-out wait. The
- * reboot cannot be done in a post-install script as this
- * is called immediately after installation, after which
- * actions such as, e.g., setting the U-Boot environment
- * are performed. */
- INFO("Update successful, rebooting the system now.\n");
- server_reboot();
+ INFO("Update successful, executing post-update commands.\n");
+ result = swupdate_postupdate() == 0 ? SERVER_OK : SERVER_EERR;
}
return result;
}
@@ -1252,6 +1252,7 @@ server_op_res_t server_start(char *fname, int argc, char *argv[])
if (channel.open(&channel_data_defaults) != CHANNEL_OK) {
return SERVER_EINIT;
}
+
/* If an update was performed, report its status to the hawkBit server
* prior to entering the main loop. May run indefinitely if server is
* unavailable. In case of an error, the error is returned to the main
diff --git a/suricatta/state.c b/suricatta/state.c
index 12ff9f2..da2962a 100644
--- a/suricatta/state.c
+++ b/suricatta/state.c
@@ -18,11 +18,20 @@
*/
#include <stdio.h>
+#include <stdbool.h>
#include <errno.h>
#include <util.h>
#include <fw_env.h>
#include "suricatta/state.h"
+bool is_state_valid(update_state_t state) {
+ if ((state < STATE_OK) || (state > STATE_ERROR)) {
+ ERROR("Unknown update state=%c\n", state);
+ return false;
+ }
+ return true;
+}
+
#ifndef CONFIG_SURICATTA_STATE_CHOICE_UBOOT
server_op_res_t save_state(char *key, update_state_t value)
{
diff --git a/suricatta/test/Makefile b/suricatta/test/Makefile
index 14e2f8e..6415c20 100644
--- a/suricatta/test/Makefile
+++ b/suricatta/test/Makefile
@@ -37,10 +37,17 @@ TEST_SRCS = $(wildcard test_*.c)
TEST_OBJS = $(addsuffix .o, $(basename $(TEST_SRCS)))
TARGETS = $(basename $(TEST_SRCS))
-SURICATTA_OBJS = $(SURICATTA_DIR)/server_hawkbit.o $(SURICATTA_DIR)/channel_hawkbit.o $(SURICATTA_DIR)/state.o $(SURICATTA_DIR)/suricatta.o
+SURICATTA_OBJS = $(SURICATTA_DIR)/server_hawkbit.o \
+ $(SURICATTA_DIR)/channel_hawkbit.o \
+ $(SURICATTA_DIR)/state.o \
+ $(SURICATTA_DIR)/suricatta.o
+
SURICATTA_SRCS = $(addsuffix .c, $(basename $(SURICATTA_OBJS)))
-SWUPDATE_OBJS = $(SWUPDATE_DIR)/ipc/network_ipc.o $(SWUPDATE_DIR)/corelib/swupdate_dict.o
+SWUPDATE_OBJS = $(SWUPDATE_DIR)/ipc/network_ipc.o \
+ $(SWUPDATE_DIR)/corelib/swupdate_dict.o \
+ $(SWUPDATE_DIR)/corelib/progress_thread.o \
+ $(SWUPDATE_DIR)/core/swupdate_postupdate.o
COMPILE.c = $(CC) $(CFLAGS) $(SURICATTA_CFLAGS) $(SURICATTA_DEFINES) $(SURICATTA_INCLUDES) -c
LINK.c = $(CC) $(CFLAGS) $(SURICATTA_LDFLAGS) $(SURICATTA_LIBS) $(SURICATTA_LDMOCKS)
@@ -56,7 +63,9 @@ test_server_hawkbit test_json: % : clean %.c $(SURICATTA_OBJS) $(SURICATTA_SRCS)
@$(COMPILE.c) $@.c -o $@.o
## NOTE: assuming libubootenv.a is available in $(SWUPDATE_DIR)/ for linking
## NOTE: assuming network_ipc.o is available in $(SWUPDATE_DIR)/ipc/ for linking
- ## NOTE: assuming swupdate_dict.o is available in $(SWUPDATE_DIR)/corelib/swupdate_dict.o for linking
+ ## NOTE: assuming swupdate_dict.o is available in $(SWUPDATE_DIR)/corelib/ for linking
+ ## NOTE: assuming progress_thread.o is available in $(SWUPDATE_DIR)/corelib/ for linking
+ ## NOTE: assuming swupdate_postupdate.o is available in $(SWUPDATE_DIR)/core/ for linking
@$(LINK.c) $(SWUPDATE_OBJS) $(SURICATTA_OBJS) $@.o -L$(SWUPDATE_DIR) -lubootenv -o $@
@CMOCKA_MESSAGE_OUTPUT=TAP $(SURICATTA_TEST_DIR)/$@
diff --git a/suricatta/test/test.h b/suricatta/test/test.h
index 9a1430a..bff9fc5 100644
--- a/suricatta/test/test.h
+++ b/suricatta/test/test.h
@@ -24,6 +24,9 @@
/* Disable swupdate core's logging output to not clutter test output */
int loglevel = OFF;
+/* Do not run post-update command in unit tests */
+int run_postupdate_cmd = 0;
+
/* mock notify() to spare linking against swupdate core's notifier.c */
void notify(RECOVERY_STATUS status, int level, const char *msg)
{
@@ -94,3 +97,9 @@ pthread_t start_thread(void *(* start_routine) (void *), void *arg)
(void)arg;
return 0;
}
+
+int listener_create(const char *path, int type) {
+ (void)path;
+ (void)type;
+ return 99;
+}
diff --git a/suricatta/test/test_server_hawkbit.c b/suricatta/test/test_server_hawkbit.c
index 8d5c512..11ec4dc 100644
--- a/suricatta/test/test_server_hawkbit.c
+++ b/suricatta/test/test_server_hawkbit.c
@@ -73,10 +73,20 @@ channel_op_res_t __wrap_channel_get(void *data)
}
extern server_op_res_t __real_save_state(char *key, update_state_t value);
-server_op_res_t __wrap_save_state(void *data);
-server_op_res_t __wrap_save_state(void *data)
+server_op_res_t __wrap_save_state(char *key, update_state_t *value);
+server_op_res_t __wrap_save_state(char *key, update_state_t *value)
{
- (void)data;
+ (void)key;
+ (void)value;
+ return mock_type(server_op_res_t);
+}
+
+extern server_op_res_t __real_read_state(char *key, update_state_t *value);
+server_op_res_t __wrap_read_state(char *key, update_state_t *value);
+server_op_res_t __wrap_read_state(char *key, update_state_t *value)
+{
+ (void)key;
+ *value = mock_type(update_state_t);
return mock_type(server_op_res_t);
}
@@ -158,16 +168,30 @@ static void test_server_has_pending_action(void **state)
assert_int_equal(SERVER_NO_UPDATE_AVAILABLE,
server_has_pending_action(&action_id));
- /* Test Case: Update Action available. */
+ /* Test Case: Update Action available && !STATE_INSTALLED. */
will_return(__wrap_channel_get,
json_tokener_parse(json_reply_update_available));
will_return(__wrap_channel_get, CHANNEL_OK);
will_return(__wrap_channel_get,
json_tokener_parse(json_reply_update_data));
will_return(__wrap_channel_get, CHANNEL_OK);
+ will_return(__wrap_read_state, STATE_NOT_AVAILABLE);
+ will_return(__wrap_read_state, SERVER_OK);
assert_int_equal(SERVER_UPDATE_AVAILABLE,
server_has_pending_action(&action_id));
+ /* Test Case: Update Action available && STATE_INSTALLED. */
+ will_return(__wrap_channel_get,
+ json_tokener_parse(json_reply_update_available));
+ will_return(__wrap_channel_get, CHANNEL_OK);
+ will_return(__wrap_channel_get,
+ json_tokener_parse(json_reply_update_data));
+ will_return(__wrap_channel_get, CHANNEL_OK);
+ will_return(__wrap_read_state, STATE_INSTALLED);
+ will_return(__wrap_read_state, SERVER_OK);
+ assert_int_equal(SERVER_NO_UPDATE_AVAILABLE,
+ server_has_pending_action(&action_id));
+
/* Test Case: Cancel Action available. */
will_return(__wrap_channel_get,
json_tokener_parse(json_reply_cancel_available));
--
2.10.2