Verified Commit 0c8248ed authored by Karel Koci's avatar Karel Koci 🤘

Use argp parser to handle arguments

This moves most of the arguments from library to applications and also
changes design of parsers to effectivelly apply configuration change
from parser directly, not later by application.
parent f4e91ced
Pipeline #47281 passed with stages
in 4 minutes and 47 seconds
......@@ -13,6 +13,7 @@ Binary dependencies:
* libevent2
* libb64
* uthash
* (argp-standalone on non-glibc systems)
Runtime dependencies:
* usign (for signatures validation)
......
......@@ -47,6 +47,9 @@ libupdater_MODULES_3RDPARTY := picosat-965/picosat
libupdater_PKG_CONFIGS := $(LUA_NAME) libevent libcurl libcrypto liburiparser
# Workaround, lua.pc doesn't containd -ldl, even when it uses dlopen
libupdater_SO_LIBS += dl b64
ifneq ($(ARGP_STANDALONE),)
libupdater_SO_LIBS += argp
endif
LIB_DOCS := \
journal \
......
This diff is collapsed.
/*
* Copyright 2016, CZ.NIC z.s.p.o. (http://www.nic.cz/)
* Copyright 2016-2019, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
......@@ -16,106 +16,13 @@
* You should have received a copy of the GNU General Public License
* along with Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UPDATER_ARGUMENTS_H
#define UPDATER_ARGUMENTS_H
// An operation type to be performed
enum cmd_op_type {
// Terminate with non-zero exit code.
COT_CRASH,
// Terminate with zero exit code.
COT_EXIT,
// Print help.
COT_HELP,
// Print updater version.
COT_VERSION,
// Print error message stored in argument variable.
COT_ERR_MSG,
// Clean up any unfinished journal work and roll back whatever can be.
COT_JOURNAL_ABORT,
// Resume interrupted operation from journal, if any is there.
COT_JOURNAL_RESUME,
// Install a package. A parameter is passed, with the path to the .ipk file.
COT_INSTALL,
// Remove a package from the system. A parameter is passed with the name of the package.
COT_REMOVE,
// Set a root directory (the parameter is the directory to set to)
COT_ROOT_DIR,
// Run without the user confirmation
COT_BATCH,
// Internally used when reexecuting. Informs program that this is restarted instance.
COT_REEXEC,
// Internally used when reexecuting to pass information that we should reboot after updater finishes.
COT_REBOOT,
// Enable dumping state to files in /tmp/updater-state directory
COT_STATE_LOG,
// Ask for approval of the operations by generating a report and requiring the --approve flag
COT_ASK_APPROVAL,
// Approve operation with the given ID (as obtained from the above mentioned report). The ID is in the parameter.
COT_APPROVE,
// Syslog level
COT_SYSLOG_LEVEL,
// Stderr log level
COT_STDERR_LEVEL,
// Name of the syslog
COT_SYSLOG_NAME,
// Put the output into a file
COT_OUTPUT,
// Log tasks into a file
COT_TASK_LOG,
// Exclude this from output
COT_EXCLUDE,
// Path to usign tool
COT_USIGN,
// Don't replan (do whole install at once)
COT_NO_REPLAN,
// Don't immediatelly reboot system
COT_NO_IMMEDIATE_REBOOT,
// Run out of root (implies COT_NO_REPLAN and COT_NO_IMMEDIATE_REBOOT)
// This one is enabled automatically if NO_REPLAN and NO_IMMEDIATE_REBOOT is enabled
COT_OUT_OF_ROOT,
// Argument isn't option.
COT_NO_OP,
// Automatic last dummy value to know size of enum
COT_LAST
};
// A whole operation to be performed, with any needed parameter.
struct cmd_op {
// What to do.
enum cmd_op_type type;
// With what. If the type doesn't expect a parameter, it is set to NULL.
const char *parameter;
};
#include <argp.h>
/*
* Parse the command line arguments (or any other string array,
* as passed) and produce list of requested operations. Note that
* the operations don't have to correspond one to one with the
* arguments.
*
* Argument accepts must be COT_LAST terminated array of all allowed operations.
*
* The result is allocated on the heap. The parameters are not
* allocated, they point to the strings passed in argv.
*
* The result is always terminated by an operation of type COT_CRASH
* or COT_EXIT.
*/
struct cmd_op *cmd_args_parse(int argc, char *argv[], const enum cmd_op_type accepts[]) __attribute__((nonnull)) __attribute__((returns_nonnull));
/*
* Prints help for accepted arguments
*
* Argument accepts is COT_LAST terminated array of all allowed operations.
*/
void cmd_args_help(const enum cmd_op_type accepts[]);
/*
* Prints message containing updater version
*/
void cmd_args_version(void);
// Common parser child to be used in argp parsers of executables
extern struct argp_child argp_parser_lib_child[];
/*
* Deep-copy the arguments. They can be used in the reexec() function.
......
BINARIES += src/pkgtransaction/pkgtransaction
pkgtransaction_MODULES := main
pkgtransaction_MODULES := main arguments
pkgtransaction_LOCAL_LIBS := updater
pkgtransaction_PKG_CONFIGS := $(LUA_NAME)
#include "arguments.h"
#include "../lib/arguments.h"
#include "../lib/util.h"
#include "../lib/logging.h"
const char *argp_program_version = "pkgtransaction " UPDATER_VERSION;
static const char doc[] =
"Updater-ng backend tool. This tool can directly manipulate local system state.\n"
"THIS TOOL IS DANGEROUS! Don't use it unless you know what you are doing.";
static struct argp_option options[] = {
{"add", 'a', "IPK", 0, "Install package IPK to system.", 0},
{"remove", 'r', "PACKAGE", 0, "Remove package PACKAGE from system.", 0},
{"abort", 'b', NULL, 0, "Abort interrupted work in the journal and clean.", 1},
{"journal-abort", 0, NULL, OPTION_ALIAS, NULL, 1},
{"journal", 'j', NULL, 0, "Recover from a crash/reboot from a journal.", 1},
{"journal-resume", 0, NULL, OPTION_ALIAS, NULL, 1},
{NULL}
};
static void new_op(struct opts *opts, enum op_type type, const char *pkg) {
opts->ops = realloc(opts->ops, (++opts->ops_cnt) * sizeof *opts->ops);
opts->ops[opts->ops_cnt - 1] = (struct operation){
.type = type,
.pkg = pkg,
};
}
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
struct opts *opts = state->input;
switch (key) {
case 'a':
new_op(opts, OPT_OP_ADD, arg);
break;
case 'r':
new_op(opts, OPT_OP_REM, arg);
break;
case 'j':
opts->journal_resume = true;
break;
case 'b':
opts->journal_abort = true;
break;
case ARGP_KEY_END:
if (!opts->journal_abort && !opts->journal_resume && opts->ops_cnt == 0)
argp_error(state, "No operation specified. Please specify what to do.");
break;
default:
return ARGP_ERR_UNKNOWN;
};
if (opts->journal_abort && opts->journal_resume)
argp_error(state, "Aborting and resuming journal at the same time is not possible.");
return 0;
}
struct argp argp_parser = {
.options = options,
.parser = parse_opt,
.doc = doc,
.children = argp_parser_lib_child,
};
/*
* Copyright 2019, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
* Updater 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 3 of the License, or
* (at your option) any later version.
*
* Updater 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 Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PKGTRANSACTION_ARGUMENTS_H
#define PKGTRANSACTION_ARGUMENTS_H
#include <argp.h>
#include <stdbool.h>
enum op_type {
OPT_OP_ADD,
OPT_OP_REM,
};
struct operation {
enum op_type type;
const char *pkg;
};
struct opts {
struct operation *ops;
size_t ops_cnt;
bool journal_resume;
bool journal_abort;
};
extern struct argp argp_parser;
#endif
......@@ -16,17 +16,16 @@
* You should have received a copy of the GNU General Public License
* along with Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "../lib/arguments.h"
#include "../lib/events.h"
#include "../lib/interpreter.h"
#include "../lib/util.h"
#include "../lib/syscnf.h"
#include "../lib/logging.h"
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "arguments.h"
static bool results_interpret(struct interpreter *interpreter, size_t result_count) {
bool result = true;
......@@ -40,21 +39,6 @@ static bool results_interpret(struct interpreter *interpreter, size_t result_cou
return result;
}
static const enum cmd_op_type cmd_op_allows[] = {
COT_JOURNAL_ABORT, COT_JOURNAL_RESUME, COT_INSTALL, COT_REMOVE, COT_ROOT_DIR, COT_SYSLOG_LEVEL, COT_STDERR_LEVEL, COT_SYSLOG_NAME, COT_REEXEC, COT_USIGN, COT_LAST };
static void print_help() {
fputs("Usage: pkgtransaction [OPTION]...\n", stderr);
fputs("Tool to create and apply updater-ng transactions on to the system.\n", stderr);
fputs("Note that this tool does only minimal verification and because of that it allows you to trash your system.\n", stderr);
cmd_args_help(cmd_op_allows);
}
static void print_version() {
fputs("pkgtransaction ", stderr);
cmd_args_version();
}
int main(int argc, char *argv[]) {
// Some setup of the machinery
log_stderr_level(LL_INFO);
......@@ -63,8 +47,14 @@ int main(int argc, char *argv[]) {
args_backup(argc, (const char **)argv);
struct events *events = events_new();
// Parse the arguments
struct cmd_op *ops = cmd_args_parse(argc, argv, cmd_op_allows);
struct cmd_op *op = ops;
struct opts opts = {
.ops = NULL,
.ops_cnt = 0,
.journal_resume = false,
.journal_abort = false,
};
argp_parse (&argp_parser, argc, argv, 0, 0, &opts);
// Prepare the interpreter and load it with the embedded lua scripts
struct interpreter *interpreter = interpreter_create(events);
const char *err = interpreter_autoload(interpreter);
......@@ -72,97 +62,37 @@ int main(int argc, char *argv[]) {
fputs(err, stderr);
return 1;
}
bool transaction_run = false;
bool journal_resume = false;
bool trans_ok = true;
bool early_exit = false;
const char *usign_exec = NULL;
for (; op->type != COT_EXIT && op->type != COT_CRASH; op ++)
switch (op->type) {
case COT_HELP:
print_help();
early_exit = true;
break;
case COT_VERSION:
print_version();
early_exit = true;
break;
case COT_ERR_MSG:
fputs(op->parameter, stderr);
break;
case COT_JOURNAL_RESUME:
journal_resume = true;
break;
case COT_INSTALL:
err = interpreter_call(interpreter, "transaction.queue_install", NULL, "s", op->parameter);
ASSERT_MSG(!err, "%s", err);
transaction_run = true;
break;
case COT_REMOVE:
err = interpreter_call(interpreter, "transaction.queue_remove", NULL, "s", op->parameter);
ASSERT_MSG(!err, "%s", err);
transaction_run = true;
break;
#define NIP(TYPE) case COT_##TYPE: fputs("Operation " #TYPE " not implemented yet\n", stderr); return 1
NIP(JOURNAL_ABORT);
case COT_ROOT_DIR:
set_root_dir(op->parameter);
break;
case COT_USIGN:
err = interpreter_call(interpreter, "uri.usign_exec_set", NULL, "s", usign_exec);
ASSERT_MSG(!err, "%s", err);
break;
case COT_STDERR_LEVEL: {
enum log_level level = log_level_get(op->parameter);
ASSERT_MSG(level != LL_UNKNOWN, "Unknown log level %s", op->parameter);
log_stderr_level(level);
break;
}
case COT_SYSLOG_LEVEL: {
enum log_level level = log_level_get(op->parameter);
ASSERT_MSG(level != LL_UNKNOWN, "Unknown log level %s", op->parameter);
log_syslog_level(level);
break;
}
case COT_SYSLOG_NAME:
log_syslog_name(op->parameter);
break;
case COT_REEXEC:
// We are currenty not using this here, but lets accept it so we would prevent problems with reexecuting.
break;
default:
assert(0);
}
enum cmd_op_type exit_type = op->type;
if (exit_type != COT_EXIT || early_exit)
goto CLEANUP;
system_detect();
bool trans_ok = true;
size_t result_count;
if (journal_resume) {
// First manage journal requests
if (opts.journal_resume) {
err = interpreter_call(interpreter, "transaction.recover_pretty", &result_count, "");
ASSERT_MSG(!err, "%s", err);
trans_ok = results_interpret(interpreter, result_count);
} else if (transaction_run) {
} else if (opts.journal_abort) {
DIE("Journal abort not implemented yet.");
} else if (opts.ops_cnt > 0) {
for (size_t i = 0; i < opts.ops_cnt; i++) {
switch (opts.ops[i].type) {
case OPT_OP_ADD:
err = interpreter_call(interpreter, "transaction.queue_install", NULL, "s", opts.ops[i].pkg);
ASSERT_MSG(!err, "%s", err);
break;
case OPT_OP_REM:
err = interpreter_call(interpreter, "transaction.queue_remove", NULL, "s", opts.ops[i].pkg);
ASSERT_MSG(!err, "%s", err);
break;
}
}
err = interpreter_call(interpreter, "transaction.perform_queue", &result_count, "");
ASSERT_MSG(!err, "%s", err);
trans_ok = results_interpret(interpreter, result_count);
} else if (!early_exit && exit_type != COT_CRASH) {
fputs("No operation specified. Please specify what to do.\n", stderr);
print_help();
exit_type = COT_CRASH;
}
CLEANUP:
free(ops);
free(opts.ops);
interpreter_destroy(interpreter);
events_destroy(events);
arg_backup_clear();
if (exit_type == COT_EXIT) {
if (trans_ok)
return 0;
else
return 2;
} else
return 1;
return !trans_ok;
}
BINARIES += src/pkgupdate/pkgupdate
pkgupdate_MODULES := main
pkgupdate_MODULES := main arguments
pkgupdate_LOCAL_LIBS := updater
pkgupdate_PKG_CONFIGS := $(LUA_NAME)
......
#include "arguments.h"
#include "../lib/arguments.h"
#include "../lib/util.h"
#include "../lib/logging.h"
const char *argp_program_version = "pkgupdate " UPDATER_VERSION;
static const char usage_doc[] = "[SCRIPT]";
static const char doc[] = "Updater-ng core tool. This updates system to latest version and syncs it with configuration.";
enum option_val_prg {
OPT_BATCH_VAL = 300,
OPT_ASK_APPROVAL,
OPT_APPROVE,
OPT_TASKLOG,
OPT_NO_REPLAN,
OPT_NO_IMMEDIATE_REBOOT,
OPT_OUT_OF_ROOT,
OPT_TASK_LOG,
OPT_STATE_LOG,
OPT_REEXEC,
OPT_REBOOT_FINISHED,
};
static struct argp_option options[] = {
{"batch", OPT_BATCH_VAL, NULL, 0, "Run without user confirmation.", 0},
{"ask-approval", OPT_ASK_APPROVAL, "FILE", 0, "Require user's approval to proceed (abort if --approve with appropriate ID is not present, plan of action is put into the FILE if approval is needed)", 0},
{"approve", OPT_APPROVE, "HASH", 0, "Approve actions with given HASH (multiple allowed).", 0},
{"out-of-root", OPT_OUT_OF_ROOT, NULL, 0, "We are running updater out of root filesystem. This implies --no-replan and --no-immediate-reboot and is suggested to be used with --root option.", 0},
{"task-log", OPT_TASK_LOG, "FILE", 0, "Append list of executed tasks into a log file.", 0},
{"state-log", OPT_STATE_LOG, NULL, 0, "Dump state to files in /tmp/updater-state directory", 0},
// Following options are internal
{"reexec", OPT_REEXEC, NULL, OPTION_HIDDEN, "", 0},
{"reboot-finished", OPT_REBOOT_FINISHED, NULL, OPTION_HIDDEN, "", 0},
{NULL}
};
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
struct opts *opts = state->input;
switch (key) {
case OPT_BATCH_VAL:
opts->batch = true;
break;
case OPT_ASK_APPROVAL:
opts->approval_file = arg;
break;
case OPT_APPROVE:
opts->approve = realloc(opts->approve, (++opts->approve_cnt) * sizeof *opts->approve);
opts->approve[opts->approve_cnt - 1] = arg;
break;
case OPT_TASK_LOG:
opts->task_log = arg;
break;
case OPT_STATE_LOG:
set_state_log(true);
break;
case OPT_REEXEC:
opts->reexec = true;
break;
case OPT_REBOOT_FINISHED:
opts->no_immediate_reboot = true;
system_reboot_disable();
break;
case ARGP_KEY_ARG:
if (!opts->config) {
opts->config = arg;
break;
}
FALLTROUGH;
default:
return ARGP_ERR_UNKNOWN;
};
return 0;
}
struct argp argp_parser = {
.options = options,
.parser = parse_opt,
.args_doc = usage_doc,
.doc = doc,
.children = argp_parser_lib_child,
};
/*
* Copyright 2019, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
* Updater 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 3 of the License, or
* (at your option) any later version.
*
* Updater 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 Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PKGUPDATE_ARGUMENTS_H
#define PKGUPDATE_ARGUMENTS_H
#include <argp.h>
#include <stdbool.h>
struct opts {
bool batch; // --batch
const char *approval_file; // --ask-approval
const char **approve; // --approve
size_t approve_cnt;
const char *task_log; // --task-log
bool no_replan; // --no-replan
bool no_immediate_reboot; // --no-immediate-reboot
const char *config; // CONFIG
bool reexec; // --reexec
};
extern struct argp argp_parser;
#endif
/*
* Copyright 2016, CZ.NIC z.s.p.o. (http://www.nic.cz/)
* Copyright 2016-2019, CZ.NIC z.s.p.o. (http://www.nic.cz/)
*
* This file is part of the turris updater.
*
......@@ -16,7 +16,12 @@
* You should have received a copy of the GNU General Public License
* along with Updater. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "../lib/syscnf.h"
#include "../lib/events.h"
#include "../lib/interpreter.h"
......@@ -24,13 +29,7 @@
#include "../lib/logging.h"
#include "../lib/arguments.h"
#include "../lib/journal.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "arguments.h"
static bool results_interpret(struct interpreter *interpreter, size_t result_count) {
bool result = true;
......@@ -45,20 +44,6 @@ static bool results_interpret(struct interpreter *interpreter, size_t result_cou
return result;
}
static const enum cmd_op_type cmd_op_allows[] = {
COT_BATCH, COT_NO_OP, COT_REEXEC, COT_REBOOT, COT_STATE_LOG, COT_ROOT_DIR, COT_SYSLOG_LEVEL, COT_STDERR_LEVEL, COT_SYSLOG_NAME, COT_ASK_APPROVAL, COT_APPROVE, COT_TASK_LOG, COT_USIGN, COT_NO_REPLAN, COT_NO_IMMEDIATE_REBOOT, COT_LAST
};
static void print_help() {
fputs("Usage: pkgupdate [OPTION]...\n", stderr);
cmd_args_help(cmd_op_allows);
}
static void print_version() {
fputs("pkgupdate ", stderr);
cmd_args_version();
}
const char *hook_preupdate = "/etc/updater/hook_preupdate";
const char *hook_postupdate = "/etc/updater/hook_postupdate";
const char *hook_reboot_delayed = "/etc/updater/hook_reboot_required";
......@@ -115,7 +100,7 @@ static const char *time_load(void) {
// Cleanup depends on what ever we are running replan or not so for simplicity we
// use this macro.
#define GOTO_CLEANUP do { if (replan) goto REPLAN_CLEANUP; else goto CLEANUP; } while(false)
#define GOTO_CLEANUP do { if (opts.reexec) goto REPLAN_CLEANUP; else goto CLEANUP; } while(false)
int main(int argc, char *argv[]) {
// Some setup of the machinery
......@@ -123,87 +108,19 @@ int main(int argc, char *argv[]) {
log_syslog_level(LL_INFO);
args_backup(argc, (const char **)argv);
// Parse the arguments
struct cmd_op *ops = cmd_args_parse(argc, argv, cmd_op_allows);
struct cmd_op *op = ops;
const char *top_level_config = NULL;
bool batch = false, early_exit = false, replan = false, reboot_finished = false;
const char *approval_file = NULL;
const char **approvals = NULL;
size_t approval_count = 0;
const char *task_log = NULL;
const char *usign_exec = NULL;
bool no_replan = false;
for (; op->type != COT_EXIT && op->type != COT_CRASH; op ++)
switch (op->type) {
case COT_HELP:
print_help();
early_exit = true;
break;
case COT_VERSION:
print_version();
early_exit = true;
break;
case COT_ERR_MSG:
fputs(op->parameter, stderr);
break;
case COT_NO_OP:
top_level_config = op->parameter;
break;
case COT_BATCH:
batch = true;
break;
case COT_REEXEC:
replan = true;
break;
case COT_REBOOT:
reboot_finished = true;
break;
case COT_ROOT_DIR:
set_root_dir(op->parameter);
break;
case COT_STATE_LOG:
set_state_log(true);
break;
case COT_STDERR_LEVEL: {
enum log_level level = log_level_get(op->parameter);
ASSERT_MSG(level != LL_UNKNOWN, "Unknown log level %s", op->parameter);
log_stderr_level(level);
break;
}
case COT_SYSLOG_LEVEL: {
enum log_level level = log_level_get(op->parameter);
ASSERT_MSG(level != LL_UNKNOWN, "Unknown log level %s", op->parameter);
log_syslog_level(level);
break;
}
case COT_SYSLOG_NAME:
log_syslog_name(op->parameter);
break;
case COT_ASK_APPROVAL:
approval_file = op->parameter;
break;
case COT_APPROVE:
// cppcheck-suppress memleakOnRealloc
approvals = realloc(approvals, (++ approval_count) * sizeof *approvals);
approvals[approval_count - 1] = op->parameter;
break;
case COT_TASK_LOG:
task_log = op->parameter;
break;
case COT_USIGN:
usign_exec = op->parameter;
break;
case COT_NO_REPLAN:
no_replan = true;
break;
case COT_NO_IMMEDIATE_REBOOT:
system_reboot_disable();
break;
default:
DIE("Unknown COT");
}
enum cmd_op_type exit_type = op->type;
free(ops);
struct opts opts = {
.batch = false,
.approval_file = NULL,
.approve = NULL,
.approve_cnt = 0,
.task_log = NULL,
.no_replan = false,
.no_immediate_reboot = false,
.config = NULL,
.reexec = false,
};
argp_parse (&argp_parser, argc, argv, 0, 0, &opts);
system_detect();
update_state(LS_INIT);
......@@ -214,15 +131,9 @@ int main(int argc, char *argv[]) {
ASSERT_MSG(!err, "%s", err);
bool trans_ok = true;
if (exit_type != COT_EXIT || early_exit)
goto CLEANUP;
size_t result_count;
// Set some configuration
if (usign_exec) {
err = interpreter_call(interpreter, "uri.usign_exec_set", NULL, "s", usign_exec);
ASSERT_MSG(!err, "%s", err);
}
if (no_replan) {
if (opts.no_replan) {
err = interpreter_call(interpreter, "updater.disable_replan", NULL, "");
ASSERT_MSG(!err, "%s", err);