diff --git a/Makefile.am b/Makefile.am
index ad89afc0..a0499d44 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -34,6 +34,7 @@ AM_CPPFLAGS = $(AM_CFLAGS) $(libarchive_CFLAGS)
swupd_SOURCES = \
src/3rd_party_add.c \
src/3rd_party.c \
+ src/3rd_party_config.c \
src/3rd_party_internal.h \
src/3rd_party_list.c \
src/3rd_party_remove.c \
@@ -68,6 +69,8 @@ swupd_SOURCES = \
src/lib/archives.h \
src/lib/config_parser.c \
src/lib/config_parser.h \
+ src/lib/config_writer.c \
+ src/lib/config_writer.h \
src/lib/formatter_json.c \
src/lib/formatter_json.h \
src/lib/hashmap.c \
diff --git a/configure.ac b/configure.ac
index 0a7821f0..ae3142b6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -83,12 +83,13 @@ AS_IF(
THIRDPARTY="yes"],
[THIRDPARTY="no"]
)
+AM_CONDITIONAL([ENABLE_THIRD_PARTY_TESTS], [test "x$enable_third_party" = "xyes"])
AH_TEMPLATE([THIRDPARTY_REPO_PREFIX], [prefix path for third party repos])
AS_IF(
[test "$enable_third_party" = "yes"],
- [AC_DEFINE([THIRDPARTY_REPO_PREFIX], ["/opt/user-repo/"])]
+ [AC_DEFINE([THIRDPARTY_REPO_PREFIX], ["/opt/3rd_party/"])]
)
AC_ARG_ENABLE(
diff --git a/src/3rd_party_add.c b/src/3rd_party_add.c
index 35057b8d..d179e77c 100644
--- a/src/3rd_party_add.c
+++ b/src/3rd_party_add.c
@@ -19,7 +19,6 @@
#define _GNU_SOURCE
#include "3rd_party_internal.h"
-#include "config.h"
#ifdef THIRDPARTY
@@ -48,21 +47,22 @@ static bool parse_options(int argc, char **argv)
return false;
}
- if (argc == 1) {
- error("The positional args: repo-name and URL are missing\n\n");
+ int positional_args = argc - ind;
+ /* Ensure that repo add expects only two args: repo-name, repo-url */
+ switch (positional_args) {
+ case 0:
+ error("The positional args: repo-name and repo-url are missing\n\n");
return false;
- } else if (argc == 2) {
- error("The positional args: repo-URL is missing\n\n");
+ case 1:
+ error("The positional args: repo-url is missing\n\n");
return false;
- }
-
- /* Ensure that repo add only expects only two args: repo-name, repo-url */
- if ((argc - ind) != EXACT_ARG_COUNT) {
+ case EXACT_ARG_COUNT:
+ return true;
+ default:
error("Unexpected arguments\n\n");
return false;
}
-
- return true;
+ return false;
}
enum swupd_code third_party_add_main(int argc, char **argv)
@@ -75,18 +75,23 @@ enum swupd_code third_party_add_main(int argc, char **argv)
print_help();
return SWUPD_INVALID_OPTION;
}
- progress_init_steps("third-party", step_in_third_party_add);
+ progress_init_steps("third-party-add", step_in_third_party_add);
- ret = swupd_init(SWUPD_NO_ROOT);
+ ret = swupd_init(SWUPD_ALL);
if (ret != SWUPD_OK) {
goto finish;
}
- /* TODO implement */
-
- swupd_deinit();
+ /* The last two in reverse are the repo-name, repo-url */
+ if (add_repo_config(argv[argc - 2], argv[argc - 1]) == 0) {
+ info("Repository %s added successfully\n", argv[argc - 2]);
+ } else {
+ ret = SWUPD_COULDNT_WRITE_FILE;
+ error("Failed to add repo: %s to config\n", argv[argc - 2]);
+ }
finish:
+ swupd_deinit();
progress_finish_steps(ret);
return ret;
}
diff --git a/src/3rd_party_config.c b/src/3rd_party_config.c
new file mode 100644
index 00000000..422e1825
--- /dev/null
+++ b/src/3rd_party_config.c
@@ -0,0 +1,223 @@
+/*
+ * Software Updater - client side
+ *
+ * Copyright © 2019 Intel Corporation.
+ *
+ * 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, version 2 or later of the License.
+ *
+ * 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, see .
+ *
+ */
+
+#include "3rd_party_internal.h"
+#include "config.h"
+#include "lib/config_parser.h"
+#include "lib/config_writer.h"
+#include "lib/list.h"
+#include "lib/sys.h"
+#include "swupd.h"
+
+#include
+#include
+#include
+#include
+
+#ifdef THIRDPARTY
+
+/** @brief Internal Memory representation of a repo. */
+struct repo {
+ char *name;
+ char *url;
+};
+
+/** @brief List of repos. */
+struct list *repos;
+
+/**
+ * @brief This function is called by the repo ini parse.
+ *
+ * @param section A string containing section of the file
+ * @param key A string containing key
+ * @param value A string containing value
+ *
+ * @returns True for every valid line which could be extracted, false otherwise
+ */
+bool parse_key_values(char *section, char *key, char *value, void UNUSED_PARAM *data)
+{
+ struct repo *repo;
+ char *lkey;
+
+ lkey = str_tolower(key);
+ if (strcmp(lkey, "url") == 0) {
+ repo = calloc(1, sizeof(struct repo));
+ ON_NULL_ABORT(repo);
+ repo->name = strdup_or_die(section);
+ repo->url = strdup_or_die(value);
+ repos = list_append_data(repos, repo);
+ repos = list_head(repos);
+ }
+ free_string(&lkey);
+
+ return true;
+}
+
+static char *get_repo_config_path(void)
+{
+ char *repo_config_file_path;
+ string_or_die(&repo_config_file_path, "%s/%s/%s", globals.state_dir, "3rd_party", "repo.ini");
+ return repo_config_file_path;
+}
+
+static bool repo_config_read(void)
+{
+ bool parse_ret = false;
+ char *repo_config_file_path = get_repo_config_path();
+
+ parse_ret = config_parse(repo_config_file_path, parse_key_values, NULL);
+ free_string(&repo_config_file_path);
+ return parse_ret;
+}
+
+/* TODO move to 3rd-party helpers */
+static void repo_free(struct repo *repo)
+{
+ if (!repo) {
+ return;
+ }
+
+ free_string(&repo->name);
+ free_string(&repo->url);
+ free(repo);
+}
+
+/* TODO move to 3rd-party helpers */
+static void repo_free_data(void *data)
+{
+ struct repo *repo = (struct repo *)data;
+ repo_free(repo);
+}
+
+static int third_party_config_create_dir(void)
+{
+ int ret = 0;
+ char *repo_config_file_path = get_repo_config_path();
+ char *repo_config_dir = sys_dirname(repo_config_file_path);
+
+ ret = mkdir_p(repo_config_dir);
+ if (ret) {
+ error("Failed to create repository config directory\n");
+ }
+
+ free_string(&repo_config_dir);
+ free_string(&repo_config_file_path);
+ return ret;
+}
+
+static int third_party_config_create_file(void)
+{
+ char *repo_config_file_path = get_repo_config_path();
+ int ret = 0;
+ int fd = open(repo_config_file_path, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+ if (fd < 0 && errno != EEXIST) {
+ error("Failed to create repo config file, error: %d", errno);
+ ret = -1;
+ }
+
+ close(fd);
+ free_string(&repo_config_file_path);
+ return ret;
+}
+
+int repo_config_init(void)
+{
+ int ret = 0;
+ ret = third_party_config_create_dir();
+
+ if (ret) {
+ goto exit;
+ }
+
+ ret = third_party_config_create_file();
+
+ if (ret) {
+ goto exit;
+ }
+
+ if (!repo_config_read()) {
+ error("Failed to read & parse repo config file");
+ ret = -1;
+ }
+
+exit:
+ return ret;
+}
+
+void repo_config_deinit(void)
+{
+ list_free_list_and_data(repos, repo_free_data);
+}
+
+/**
+ * @brief This function performs the repo config operation part for repo add
+ * It checks if a repo with repo_name already exists and if not creates an
+ * entry in the repo config file.
+ *
+ * @param repo_name A string containing repo_name
+ * @param repo_url A string containing repo_url
+ *
+ * @returns 0 on success ie: a repo with the name is successfully added to repo config
+ * otherwise a -1 on any failure
+ */
+int add_repo_config(char *repo_name, char *repo_url)
+{
+ struct list *repo;
+ int ret = 0;
+ FILE *fp = NULL;
+ char *repo_config_file_path;
+
+ if (repo_config_init()) {
+ ret = -1;
+ goto exit;
+ }
+
+ repo = list_head(repos);
+ while (repo) {
+ struct repo *repo_iter = repo->data;
+ if (!strncmp(repo_iter->name, repo_name, strlen(repo_name))) {
+ error("The repo: %s already exists\n", repo_name);
+ ret = -1;
+ goto exit;
+ }
+ repo = repo->next;
+ }
+
+ // If we are here, we are cleared to write to file
+ repo_config_file_path = get_repo_config_path();
+ fp = fopen(repo_config_file_path, "a");
+
+ ret = config_write_section(fp, repo_name);
+ if (ret) {
+ goto exit;
+ }
+
+ // write url
+ ret = config_write_config(fp, "url", repo_url);
+
+exit:
+ if (fp) {
+ fclose(fp);
+ }
+ free_string(&repo_config_file_path);
+ repo_config_deinit();
+ return ret;
+}
+
+#endif
diff --git a/src/lib/config_writer.c b/src/lib/config_writer.c
new file mode 100644
index 00000000..dcc3efc1
--- /dev/null
+++ b/src/lib/config_writer.c
@@ -0,0 +1,69 @@
+/*
+ * Software Updater - client side
+ *
+ * Copyright © 2012-2019 Intel Corporation.
+ *
+ * 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, version 2 or later of the License.
+ *
+ * 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, see .
+ *
+ */
+
+#define _GNU_SOURCE
+#include
+#include
+
+#include "log.h"
+#include "strings.h"
+
+int config_write_section(FILE *file, const char *section)
+{
+ int ret = 0;
+ char *repo_string;
+
+ if (!file) {
+ return -1;
+ }
+
+ string_or_die(&repo_string, "\n[%s]\n\n", section);
+ ret = fputs(repo_string, file);
+
+ if (ret < 0 || ret == EOF) {
+ error("config section write failed\n");
+ } else if (ret > 0) {
+ ret = 0;
+ }
+
+ free_string(&repo_string);
+ return ret;
+}
+
+int config_write_config(FILE *file, const char *key, const char *value)
+{
+ int ret = 0;
+ char *repo_string;
+
+ if (!file) {
+ return -1;
+ }
+
+ string_or_die(&repo_string, "%s=%s\n", key, value);
+ ret = fputs(repo_string, file);
+
+ if (ret < 0 || ret == EOF) {
+ error("config(key,value) write failed\n");
+ } else if (ret > 0) {
+ ret = 0;
+ }
+
+ free_string(&repo_string);
+ return ret;
+}
diff --git a/src/lib/config_writer.h b/src/lib/config_writer.h
new file mode 100644
index 00000000..a17793c9
--- /dev/null
+++ b/src/lib/config_writer.h
@@ -0,0 +1,39 @@
+#ifndef __CONFIG_WRITER__
+#define __CONFIG_WRITER__
+
+/**
+ * @file
+ * @brief Configuration fie writing
+ */
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Writes a section to ini file using string as an argument.
+ *
+ * @param FILE file pointer *FILE to the config file
+ * @param section string to written to config file
+ * @returns 0 if no errors or negative on any error
+ */
+int config_write_section(FILE *file, const char *section);
+
+/**
+ * @brief Writes a config(key,value) line to ini file using key,value strings as an
+ * argument
+ *
+ * @param FILE file pointer *FILE to the config file
+ * @param key string
+ * @param value string
+ * @returns 0 if no errors or negative on any error
+ */
+int config_write_config(FILE *file, const char *key, const char *value);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/test/functional/3rd-party-repo-add/3rd-party-repo-add-negative.bats b/test/functional/3rd-party-repo-add/3rd-party-repo-add-negative.bats
new file mode 100755
index 00000000..bbb9de95
--- /dev/null
+++ b/test/functional/3rd-party-repo-add/3rd-party-repo-add-negative.bats
@@ -0,0 +1,68 @@
+#!/usr/bin/env bats
+
+# Author: Karthik Prabhu Vinod
+# Email: karthik.prabhu.vinod@intel.com
+
+load "../testlib"
+
+@test "TRA003: Negative test, repo add usage" {
+
+ run sudo sh -c "$SWUPD 3rd-party add $SWUPD_OPTS"
+ assert_status_is "$SWUPD_INVALID_OPTION"
+ expected_output=$(cat <<-EOM
+ Error: The positional args: repo-name and repo-url are missing
+ EOM
+ )
+ assert_in_output "$expected_output"
+
+ run sudo sh -c "$SWUPD 3rd-party add test-repo1 $SWUPD_OPTS"
+ assert_status_is "$SWUPD_INVALID_OPTION"
+ expected_output=$(cat <<-EOM
+ Error: The positional args: repo-url is missing
+ EOM
+ )
+ assert_in_output "$expected_output"
+
+ run sudo sh -c "$SWUPD 3rd-party add test-repo1 http://abc.com junk_positional $SWUPD_OPTS"
+ assert_status_is "$SWUPD_INVALID_OPTION"
+ expected_output=$(cat <<-EOM
+ Error: Unexpected arguments
+ EOM
+ )
+ assert_in_output "$expected_output"
+
+}
+
+@test "TRA004: Negative test, Add an already added repo" {
+
+ run sudo sh -c "$SWUPD 3rd-party add test-repo1 https://www.xyz.com $SWUPD_OPTS"
+ assert_status_is "$SWUPD_OK"
+ expected_output=$(cat <<-EOM
+ Repository test-repo1 added successfully
+ EOM
+ )
+ assert_is_output "$expected_output"
+
+ run sudo sh -c "$SWUPD 3rd-party add test-repo1 https://abc.com $SWUPD_OPTS"
+ assert_status_is "$SWUPD_COULDNT_WRITE_FILE"
+ expected_output=$(cat <<-EOM
+ Error: The repo: test-repo1 already exists
+ Error: Failed to add repo: test-repo1 to config
+ EOM
+ )
+ assert_is_output "$expected_output"
+
+}
+
+@test "TRA005: Negative test, Invalid URL" {
+
+ run sudo sh -c "$SWUPD 3rd-party add test-repo1 www.xyz.com $SWUPD_OPTS"
+ assert_status_is "$SWUPD_COULDNT_WRITE_FILE"
+ expected_output=$(cat <<-EOM
+ Error: Invalid argument: repo-url
+
+ EOM
+ )
+ assert_is_output "$expected_output"
+
+}
diff --git a/test/functional/3rd-party-repo-add/3rd-party-repo-add.bats b/test/functional/3rd-party-repo-add/3rd-party-repo-add.bats
new file mode 100755
index 00000000..13cdf80f
--- /dev/null
+++ b/test/functional/3rd-party-repo-add/3rd-party-repo-add.bats
@@ -0,0 +1,98 @@
+#!/usr/bin/env bats
+
+# Author: Karthik Prabhu Vinod
+# Email: karthik.prabhu.vinod@intel.com
+
+load "../testlib"
+
+@test "TRA001: Add a single repo" {
+
+ run sudo sh -c "$SWUPD 3rd-party add test-repo1 http://xyz.com $SWUPD_OPTS"
+ assert_status_is "$SWUPD_OK"
+ expected_output=$(cat <<-EOM
+ Repository test-repo1 added successfully
+ EOM
+ )
+ assert_is_output "$expected_output"
+
+ run sudo sh -c "cat $STATEDIR/3rd_party/repo.ini"
+ expected_output=$(cat <<-EOM
+ [test-repo1]
+ url=http://xyz.com
+ EOM
+ )
+ assert_is_output "$expected_output"
+
+}
+
+@test "TRA002: Add multiple repos in repo config" {
+
+ # Add 5 repos in a row one by one
+ run sudo sh -c "$SWUPD 3rd-party add test-repo1 https://xyz.com $SWUPD_OPTS"
+ assert_status_is "$SWUPD_OK"
+ expected_output=$(cat <<-EOM
+ Repository test-repo1 added successfully
+ EOM
+ )
+ assert_is_output "$expected_output"
+
+ run sudo sh -c "$SWUPD 3rd-party add test-repo2 file://abc.com $SWUPD_OPTS"
+ assert_status_is "$SWUPD_OK"
+ expected_output=$(cat <<-EOM
+ Repository test-repo2 added successfully
+ EOM
+ )
+ assert_is_output "$expected_output"
+
+ run sudo sh -c "$SWUPD 3rd-party add test-repo3 https://efg.com $SWUPD_OPTS"
+ assert_status_is "$SWUPD_OK"
+ expected_output=$(cat <<-EOM
+ Repository test-repo3 added successfully
+ EOM
+ )
+ assert_is_output "$expected_output"
+
+ run sudo sh -c "$SWUPD 3rd-party add test-repo4 http://hij.com $SWUPD_OPTS"
+ assert_status_is "$SWUPD_OK"
+ expected_output=$(cat <<-EOM
+ Repository test-repo4 added successfully
+ EOM
+ )
+ assert_is_output "$expected_output"
+
+ run sudo sh -c "$SWUPD 3rd-party add test-repo5 https://klm.com $SWUPD_OPTS"
+ assert_status_is "$SWUPD_OK"
+ expected_output=$(cat <<-EOM
+ Repository test-repo5 added successfully
+ EOM
+ )
+ assert_is_output "$expected_output"
+
+ run sudo sh -c "cat $STATEDIR/3rd_party/repo.ini"
+ assert_status_is "$SWUPD_OK"
+ expected_output=$(cat <<-EOM
+
+ [test-repo1]
+
+ url=https://xyz.com
+
+ [test-repo2]
+
+ url=file://abc.com
+
+ [test-repo3]
+
+ url=https://efg.com
+
+ [test-repo4]
+
+ url=http://hij.com
+
+ [test-repo5]
+
+ url=https://klm.com
+ EOM
+ )
+ assert_is_output --identical "$expected_output"
+
+}
diff --git a/test/functional/README.md b/test/functional/README.md
index 3efbaf2e..3d4ce86c 100644
--- a/test/functional/README.md
+++ b/test/functional/README.md
@@ -138,6 +138,7 @@ or more of the [assertions](#assertions) provided by the test library.
| Clean cached files | clean | CLN |
| Configure mirror URL for swupd content | mirror | MIR |
| Usability | N/A | USA |
+| 3rd-party repo add | 3rd-party-repo-add | TRA |
### Test Principles
diff --git a/test/functional/testlib.bash b/test/functional/testlib.bash
index efffcddc..48c9747f 100644
--- a/test/functional/testlib.bash
+++ b/test/functional/testlib.bash
@@ -3179,6 +3179,7 @@ get_next_available_id() { # swupd_function
id=$((id+1))
test_dir=$(basename "$(realpath "$test_dir")")
case "$test_dir" in
+ 3rd-party-repo-add) group=TRA;;
autoupdate) group=AUT;;
bundleadd) group=ADD;;
bundleremove) group=REM;;