From 8aca4846290caccfecd3e83501389112c6241faa Mon Sep 17 00:00:00 2001 From: Mateusz Moscicki Date: Tue, 3 Oct 2023 13:23:21 +0200 Subject: [PATCH] Add ISU API Change-Id: I935c5e76b7cf061d3f8ef451ca527b0be96e0121 --- CMakeLists.txt | 5 + config/isu.conf | 4 +- packaging/isu.spec | 40 +++ src/isud/CMakeLists.txt | 22 ++ src/isud/isud.c | 240 ++++++++++++++++ src/isud/isud.conf | 22 ++ src/isud/isud.service | 11 + src/isud/org.tizen.system.isu.service | 5 + src/libisu/CMakeLists.txt | 33 +++ src/libisu/libisu-internal.c | 501 ++++++++++++++++++++++++++++++++++ src/libisu/libisu-internal.h | 69 +++++ src/libisu/libisu.c | 228 ++++++++++++++++ src/libisu/libisu.h | 199 ++++++++++++++ src/libisu/libisu.pc.in | 14 + 14 files changed, 1391 insertions(+), 2 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 src/isud/CMakeLists.txt create mode 100644 src/isud/isud.c create mode 100644 src/isud/isud.conf create mode 100644 src/isud/isud.service create mode 100644 src/isud/org.tizen.system.isu.service create mode 100644 src/libisu/CMakeLists.txt create mode 100644 src/libisu/libisu-internal.c create mode 100644 src/libisu/libisu-internal.h create mode 100644 src/libisu/libisu.c create mode 100644 src/libisu/libisu.h create mode 100644 src/libisu/libisu.pc.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0e8a791 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,5 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.6) +PROJECT(isu C) +SET(PREFIX ${CMAKE_INSTALL_PREFIX}) +ADD_SUBDIRECTORY(src/libisu) +ADD_SUBDIRECTORY(src/isud) diff --git a/config/isu.conf b/config/isu.conf index ad26c18..177847b 100644 --- a/config/isu.conf +++ b/config/isu.conf @@ -1,4 +1,4 @@ -d /opt/isu 0755 root root - +d /opt/isu 0755 system system - t /opt/isu - - - - security.SMACK64="_" -d /run/isu 0755 root root - +d /run/isu 0755 system system - t /run/isu - - - - security.SMACK64="_" diff --git a/packaging/isu.spec b/packaging/isu.spec index 083f27e..361bc6a 100644 --- a/packaging/isu.spec +++ b/packaging/isu.spec @@ -1,9 +1,17 @@ +%define libisu_version 0.0.1 Summary: Individual Service Upgrade support Name: isu Version: 8.0.16 Release: 1 Source0: %{name}-%{version}.tar.gz License: MIT +BuildRequires: cmake +BuildRequires: pkgconfig +BuildRequires: pkgconfig(libarchive) +BuildRequires: pkgconfig(dlog) +BuildRequires: pkgconfig(glib-2.0) +BuildRequires: pkgconfig(mount) +BuildRequires: pkgconfig(capi-system-info) %description Individual Service Upgrade is Tizen mechanism used to allow extending @@ -14,13 +22,27 @@ ISU uses systemd.generator(7) mechanism to install systemd services from extension packages and modified bubblewrap to run service in partially sandboxed environment. +%package -n libisu +Summary: ISU API library +%description -n libisu +Provies ISU API library + +%package -n libisu-devel +Summary: Package provides headers needed to develop programs using ISU API +%description -n libisu-devel + %prep %setup -q %build cp packaging/isu.manifest . +%cmake . -DCMAKE_INSTALL_PREFIX=%{_prefix} -DLIBISU_VERSION=%{libisu_version} +make %{?_smp_mflags} %install +rm -rf %{buildroot} +%make_install + mkdir -p %{buildroot}/usr/libexec mkdir -p %{buildroot}/usr/lib/systemd/system-generators mkdir -p %{buildroot}/usr/lib/systemd/user-generators @@ -47,3 +69,21 @@ install -m750 src/pkg_manager/isu %{buildroot}/%{_bindir}/isu /etc/isu/user-service-common.inc /etc/isu/upgrade.cfg %{_bindir}/isu + +%files -n libisu +%manifest isu.manifest +%license LICENSE.MIT +%{_libdir}/libisu.so.* +%{_bindir}/isud +%attr(-,root,root) /etc/dbus-1/system.d/isud.conf +%attr(-,root,root) %{_unitdir}/isud.service +%attr(-,root,root) %{_datadir}/dbus-1/system-services/org.tizen.system.isu.service + + +%files -n libisu-devel +%manifest isu.manifest +%license LICENSE.MIT +%{_libdir}/libisu.so +%{_includedir}/libisu.h +%{_includedir}/libisu-internal.h +%{_libdir}/pkgconfig/*.pc diff --git a/src/isud/CMakeLists.txt b/src/isud/CMakeLists.txt new file mode 100644 index 0000000..51904a3 --- /dev/null +++ b/src/isud/CMakeLists.txt @@ -0,0 +1,22 @@ +FIND_PACKAGE(PkgConfig) +INCLUDE(GNUInstallDirs) + +pkg_check_modules(deps REQUIRED + dlog + gio-2.0 + gio-unix-2.0 + capi-system-info + ) + + +ADD_EXECUTABLE(isud isud.c) +TARGET_COMPILE_OPTIONS(isud PRIVATE ${deps_CFLAGS_OTHER} -fPIC) +TARGET_INCLUDE_DIRECTORIES(isud PRIVATE ${deps_INCLUDE_DIRS} ../libisu/) +TARGET_LINK_LIBRARIES(isud PRIVATE isu ${deps_LDFLAGS} -pie -larchive) + + +INSTALL(TARGETS isud DESTINATION bin) +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/isud.conf DESTINATION /etc/dbus-1/system.d) +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/isud.service DESTINATION /usr/lib/systemd/system) +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/org.tizen.system.isu.service DESTINATION /usr/share/dbus-1/system-services) + diff --git a/src/isud/isud.c b/src/isud/isud.c new file mode 100644 index 0000000..5a69097 --- /dev/null +++ b/src/isud/isud.c @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * 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. + */ +#include +#include +#include +#include +#include +#include + +#include "libisu-internal.h" + +#ifdef LOG_TAG + #undef LOG_TAG +#endif +#define LOG_TAG "ISUSERVICE" + +#define TIMEOUT_INTERVAL_SEC 30 + +static GMainLoop *loop; +static GMutex timeout_mutex; +static guint timeout_id; +static GDBusNodeInfo *introspection_data; +static const gchar introspection_xml[] = +"" +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +""; + +static int timeout_cb(gpointer data) +{ + SLOGI("Time out!"); + g_main_loop_quit((GMainLoop *)data); + + return 0; +} + +static void add_timeout(void) +{ + g_mutex_lock(&timeout_mutex); + + if (timeout_id) + g_source_remove(timeout_id); + timeout_id = g_timeout_add_seconds(TIMEOUT_INTERVAL_SEC, timeout_cb, loop); + + g_mutex_unlock(&timeout_mutex); + SLOGD("Add loop timeout (%d)", TIMEOUT_INTERVAL_SEC); +} + +static void remove_timeout(void) +{ + g_mutex_lock(&timeout_mutex); + + if (timeout_id) { + g_source_remove(timeout_id); + timeout_id = 0; + } + + g_mutex_unlock(&timeout_mutex); + SLOGD("Remove loop timeout"); +} + +static void call_handler(GDBusMethodInvocation *invocation, bool install) +{ + GDBusMessage *incoming_message = g_dbus_method_invocation_get_message(invocation); + GVariant *body = g_dbus_message_get_body(incoming_message); + gchar *data; + g_variant_get(body, "(s)", &data); + int res; + if (install) { + res = isu_install_internal(data); + } else { + res = isu_uninstall_internal(data); + } + SLOGD("ISU Package \"%s\" %s %s (%d)", data, + install ? "installation" : "uninstallation", + res == ISU_RES_OK ? "success" : "error", + res); + g_dbus_method_invocation_return_value(invocation, + g_variant_new("(i)", res)); +} + +static void install_handler(GDBusMethodInvocation *invocation) +{ + call_handler(invocation, true); +} + +static void uninstall_handler(GDBusMethodInvocation *invocation) +{ + call_handler(invocation, false); +} + +static void method_call_handler(GDBusConnection *conn, + const gchar *sender, + const gchar *object_path, + const gchar *iface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + remove_timeout(); + + if (g_strcmp0(method_name, "install") == 0) { + SLOGD("Install"); + install_handler(invocation); + } else if (g_strcmp0(method_name, "uninstall") == 0) { + SLOGD("Uninstall"); + uninstall_handler(invocation); + } else { + SLOGE("Unsupported method: %s", method_name); + } + + add_timeout(); +} + +static const GDBusInterfaceVTable interface_vtable = { + method_call_handler, + NULL, + NULL +}; + +static void on_bus_acquired(GDBusConnection *conn, + const gchar *name, + gpointer user_data) +{ + const char *object_path; + GDBusInterfaceInfo *interface_info; + guint registration_id; + GError *error = NULL; + + if (strcmp(name, ISUSERVICE_BUS_NAME) == 0) { + object_path = ISUSERVICE_OBJECT_PATH; + interface_info = introspection_data->interfaces[0]; + } else { + SLOGE("on_bus_acquired: unknown name: %s", name); + return; + } + + registration_id = g_dbus_connection_register_object(conn, + object_path, + interface_info, + &interface_vtable, + NULL, + NULL, + &error); + if (registration_id == 0 || error) { + SLOGD("Failed to g_dbus_connection_register_object for %s: %s", + name, + error ? error->message : ""); + if (error) + g_error_free(error); + } +} + +static void on_name_acquired(GDBusConnection *conn, + const gchar *name, + gpointer user_data) +{ + SLOGD("Acquired the name %s on the system bus", name); +} + +static void on_name_lost(GDBusConnection *conn, + const gchar *name, + gpointer user_data) +{ + SLOGD("Lost the name %s on the system bus", name); +} + +static bool dbus_init(void) +{ + GError *error = NULL; + + introspection_data = + g_dbus_node_info_new_for_xml(introspection_xml, NULL); + if (introspection_data == NULL) { + SLOGE("Failed to init g_dbus_info_new_for_xml"); + return false; + } + + GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (!conn || error) { + SLOGE("Failed to get dbus: %s", error ? error->message : ""); + if (error) + g_error_free(error); + return false; + } + + g_bus_own_name(G_BUS_TYPE_SYSTEM, ISUSERVICE_BUS_NAME, + G_BUS_NAME_OWNER_FLAGS_NONE, on_bus_acquired, + on_name_acquired, on_name_lost, NULL, NULL); + + return true; +} + +int main(void) +{ + loop = g_main_loop_new(NULL, false); + if (loop == NULL) { + SLOGE("GLib main loop create error"); + return EXIT_FAILURE; + } + + if (!dbus_init()) { + g_main_loop_unref(loop); + return EXIT_FAILURE; + } + + g_mutex_init(&timeout_mutex); + add_timeout(); + + g_main_loop_run(loop); + + if (introspection_data) + g_dbus_node_info_unref(introspection_data); + g_mutex_clear(&timeout_mutex); + g_main_loop_unref(loop); + + return EXIT_SUCCESS; +} diff --git a/src/isud/isud.conf b/src/isud/isud.conf new file mode 100644 index 0000000..1797911 --- /dev/null +++ b/src/isud/isud.conf @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/isud/isud.service b/src/isud/isud.service new file mode 100644 index 0000000..b720b4c --- /dev/null +++ b/src/isud/isud.service @@ -0,0 +1,11 @@ +[Unit] +Description=Individual Service Upgrade management daemon + +[Service] +Type=dbus +User=system +Group=system +BusName=org.tizen.system.isu +ExecStart=/usr/bin/isud +SmackProcessLabel=System +KillMode=mixed diff --git a/src/isud/org.tizen.system.isu.service b/src/isud/org.tizen.system.isu.service new file mode 100644 index 0000000..c9a5539 --- /dev/null +++ b/src/isud/org.tizen.system.isu.service @@ -0,0 +1,5 @@ +[D-BUS Service] +Name=org.tizen.system.isu +Exec=/bin/false +SystemdService=isud.service + diff --git a/src/libisu/CMakeLists.txt b/src/libisu/CMakeLists.txt new file mode 100644 index 0000000..509d5e5 --- /dev/null +++ b/src/libisu/CMakeLists.txt @@ -0,0 +1,33 @@ +FIND_PACKAGE(PkgConfig) +INCLUDE(GNUInstallDirs) + +pkg_check_modules(deps REQUIRED + dlog + gio-2.0 + gio-unix-2.0 + capi-system-info + mount + ) + + +SET(PC_VERSION ${LIBISU_VERSION}) +SET(PC_PREFIX ${CMAKE_INSTALL_PREFIX}) +SET(PC_NAME ${PROJECT_NAME}) +SET(PC_LIBDIR "${CMAKE_INSTALL_LIBDIR}") +SET(PC_INCLUDE "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}") +SET(PC_DESCRIPTION "Individual Service Upgrade support library") +SET(PC_REQUIRED "dlog glib-2.0 gio-unix-2.0 capi-system-info") +SET(PC_LDFLAGS -lisu) +CONFIGURE_FILE(libisu.pc.in ${CMAKE_CURRENT_SOURCE_DIR}/libisu.pc @ONLY) + +ADD_LIBRARY(isu SHARED libisu.c libisu-internal.c) +SET_TARGET_PROPERTIES(isu PROPERTIES VERSION ${LIBISU_VERSION}) +SET_TARGET_PROPERTIES(isu PROPERTIES SOVERSION 1) +SET_TARGET_PROPERTIES(isu PROPERTIES PUBLIC_HEADER libisu.h) +TARGET_COMPILE_OPTIONS(isu PUBLIC -fPIE) +TARGET_INCLUDE_DIRECTORIES(isu PRIVATE . ${deps_INCLUDE_DIRS}) + +INSTALL(TARGETS isu PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/libisu.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/libisu-internal.h DESTINATION ${INCLUDE_INSTALL_DIR}) + diff --git a/src/libisu/libisu-internal.c b/src/libisu/libisu-internal.c new file mode 100644 index 0000000..d473736 --- /dev/null +++ b/src/libisu/libisu-internal.c @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * 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. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libisu-internal.h" + +// Arbitrarily determined block size +#define BLOCK_SIZE 10240 +#define CFG_NAME "name" +#define CFG_VERSION "version" +#define CFG_SYSTEM_SERVICE "system_service" + +bool is_isu_feature_supported() +{ + int ret; + bool feature_res; + + ret = system_info_get_platform_bool(ISU_FEATURE, &feature_res); + if (ret != SYSTEM_INFO_ERROR_NONE) { + SLOGE("Failed to get feature info"); + return false; + } + + return feature_res; +} + +isu_result isu_dbus_call(const char *method, const char *parameter) +{ + GError *error = NULL; + int res; + GDBusConnection *conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (conn == NULL) { + SLOGE("isu_%s: g_bus_get_sync error: %s", method, error ? error->message : ""); + if (error) + g_error_free(error); + return ISU_RES_ERR_INTERNAL; + } + + GDBusMessage *msg = g_dbus_message_new_method_call( + ISUSERVICE_BUS_NAME, + ISUSERVICE_OBJECT_PATH, + ISUSERVICE_INTERFACE_NAME, + method); + if (msg == NULL) { + SLOGE("isu_%s: g_dbus_message_new_method_call error", method); + return ISU_RES_ERR_INTERNAL; + } + + g_dbus_message_set_body(msg, g_variant_new("(s)", parameter)); + + GDBusMessage *reply = g_dbus_connection_send_message_with_reply_sync( + conn, + msg, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + -1, + NULL, + NULL, + &error); + if (reply == NULL) { + SLOGE("isu_%s: send message error: %s", method, error ? error->message : "" ); + g_error_free(error); + } + + GDBusMessageType msg_type = g_dbus_message_get_message_type(reply); + if (msg_type == G_DBUS_MESSAGE_TYPE_ERROR) { + SLOGE("isu_%s: No permission to call this method", method); + res = ISU_RES_ERR_NOT_PERMITTED; + goto exit; + } + + GVariant *body = g_dbus_message_get_body(reply); + if (body == NULL) { + SLOGE("isu_%s: get message body error", method); + return ISU_RES_ERR_INTERNAL; + } + g_variant_get(body, "(i)", &res); +exit: + g_object_unref(msg); + g_object_unref(reply); + g_object_unref(conn); + return res; +} + +static void fill_system_services(char *line, struct _isu_pkg_info *pkg_info) +{ + char *saveptr; + char *token = strtok_r(line, " ", &saveptr); + while (token != NULL) { + pkg_info->service_files_len++; + pkg_info->service_files = realloc(pkg_info->service_files, + pkg_info->service_files_len*sizeof(char*)); + pkg_info->service_files[pkg_info->service_files_len-1] = strdup(token); + token = strtok_r(NULL, " ", &saveptr); + } +} + +static int copy_data(struct archive *ar, struct archive *aw) +{ + assert(ar); + assert(aw); + + int res; + const void *buff; + size_t size; + la_int64_t offset; + + for (;;) { + res = archive_read_data_block(ar, &buff, &size, &offset); + if (res == ARCHIVE_EOF) + return ARCHIVE_OK; + if (res == ARCHIVE_RETRY) + continue; + if (res < ARCHIVE_OK) { + SLOGE("Archive read data block error (%d): %s\n", res, archive_error_string(ar)); + return res; + } + res = archive_write_data_block(aw, buff, size, offset); + if (res < ARCHIVE_OK) { + SLOGE("Archive write data block error (%d): %s\n", res, archive_error_string(aw)); + return res; + } + } +} + +static int extract(const char *arch_path, const char *dest_path, const char *file_name) +{ + assert(arch_path); + assert(dest_path); + + struct archive *a; + struct archive *ext; + struct archive_entry *entry; + int res; + int result = ISU_RES_ERR_INVALID_FORMAT; + int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR; + + a = archive_read_new(); + archive_read_support_format_all(a); + archive_read_support_filter_all(a); + ext = archive_write_disk_new(); + archive_write_disk_set_options(ext, flags); + archive_write_disk_set_standard_lookup(ext); + + res = archive_read_open_filename(a, arch_path, BLOCK_SIZE); + if (res != 0) { + SLOGE("Open archive %s error (%d): %s\n", arch_path, res, archive_error_string(a)); + goto exit; + } + for (;;) { + res = archive_read_next_header(a, &entry); + if (res == ARCHIVE_EOF) { + result = ISU_RES_OK; + break; + } + if (res < ARCHIVE_OK) { + SLOGE("Archive read next header error (%d): %s\n", res, archive_error_string(a)); + break; + } + + const char *entry_path = archive_entry_pathname(entry); + if (entry_path == NULL) { + SLOGE("Entry path is empty"); + continue; + } + if (file_name != NULL) { + // In case we are extracting a particular file (file_name != NULL), + // skip if the current one is not the one we are looking for + if (strcmp(entry_path, file_name) != 0) { + continue; + } + } + + char new_entry_path[PATH_MAX]; + snprintf(new_entry_path, sizeof(new_entry_path), "%s/%s", dest_path, entry_path); + archive_entry_set_pathname(entry, new_entry_path); + + res = archive_write_header(ext, entry); + if (res < ARCHIVE_OK) { + SLOGE("write error: %s\n", archive_error_string(ext)); + result = ISU_RES_ERR_INTERNAL; + break; + } else if (archive_entry_size(entry) > 0) { + res = copy_data(a, ext); + if (res < ARCHIVE_OK) { + SLOGE("copy data error: %s\n", archive_error_string(ext)); + result = ISU_RES_ERR_INTERNAL; + break; + } + } + + res = archive_write_finish_entry(ext); + if (res < ARCHIVE_OK) { + SLOGE("write finish error: %s\n", archive_error_string(ext)); + result = ISU_RES_ERR_INTERNAL; + break; + } + } + +exit: + archive_read_close(a); + archive_read_free(a); + archive_write_close(ext); + archive_write_free(ext); + + return result; +} + +static bool path_exists(const char *path) +{ + assert(path); + + struct stat st; + return stat(path, &st) == 0; +} + +static int rm_cb(const char *path, const struct stat *sb, int type_flag, struct FTW *ftw) +{ + assert(path); + assert(sb); + assert(ftw); + + int res = remove(path); + if (res != 0) { + SLOGE("Remove %s error (%d): %m\n", path, errno); + } + return res; +} + +static int remove_dir(const char *path) +{ + assert(path); + return nftw(path, rm_cb, FTW_F | FTW_D, FTW_DEPTH | FTW_PHYS); +} + +static struct _isu_pkg_info* isu_pkg_info_prepare() +{ + struct _isu_pkg_info *pkg_info = malloc(sizeof(struct _isu_pkg_info)); + if (pkg_info == NULL) { + SLOGE("Memory allocation error (%d): %m\n", errno); + return NULL; + } + pkg_info->name = NULL; + pkg_info->version = NULL; + pkg_info->service_files_len = 0; + pkg_info->service_files = NULL; + + return pkg_info; +} + +struct _isu_pkg_info* get_pkg_info(const char *isu_cfg_path) +{ + assert(isu_cfg_path); + + struct _isu_pkg_info *pkg_info = isu_pkg_info_prepare(); + if (pkg_info == NULL) { + return NULL; + } + + FILE *f = fopen(isu_cfg_path, "r"); + if (f == NULL) { + SLOGE("File %s open error (%d): %m\n", isu_cfg_path, errno); + free(pkg_info); + pkg_info = NULL; + return NULL; + } + + char *lineptr = NULL; + size_t n = 0; + while (getline(&lineptr, &n, f) > 0) { + char *line = lineptr; + while (line && line[0] == ' ') { line++; }; + if (line == NULL) continue; + if (line[0] == '#') continue; + + char *saveptr; + char *token = strtok_r(line, "=", &saveptr); + if (!token) continue; + char *value = strtok_r(NULL, "=", &saveptr); + if (!value) continue; + while (value[0] == ' ') { value++; }; + + size_t val_len = strlen(value); + + if (val_len > 0 && value[val_len-1] == '\n') { + value[val_len-1] = '\0'; + } + + if (strncmp(token, CFG_NAME, strlen(CFG_NAME)) == 0) { + pkg_info->name = strdup(value); + } else if (strncmp(token, CFG_VERSION, strlen(CFG_VERSION)) == 0) { + pkg_info->version = strdup(value); + } else if (strncmp(token, CFG_SYSTEM_SERVICE, strlen(CFG_SYSTEM_SERVICE)) == 0) { + fill_system_services(value, pkg_info); + } + + } + if (!pkg_info->name || !pkg_info->version) { + SLOGE("Cannot read content of isu.cfg"); + isu_pkg_info_free(pkg_info); + pkg_info = NULL; + } + + free(lineptr); + if (f != NULL) { + fclose(f); + } + return pkg_info; +} + +isu_result isu_install_internal(const char *path) +{ + RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE); + assert(path); + + int result = ISU_RES_ERR_INVALID_FORMAT; + struct _isu_pkg_info *pkg_info = NULL; + + char temp_path[] = "/tmp/isu-XXXXXX"; + if (mkdtemp(temp_path) == NULL) { + result = ISU_RES_ERR_INTERNAL; + goto exit; + } + + if (extract(path, temp_path, ISU_CFG) != ISU_RES_OK) { + goto exit; + } + + char buff[PATH_MAX]; + snprintf(buff, sizeof(buff), "%s/%s", temp_path, ISU_CFG); + pkg_info = get_pkg_info(buff); + if (pkg_info == NULL) { + SLOGE("File %s is not an ISU Package\n", path); + goto exit; + } + if (pkg_info->name == NULL) { + SLOGE("Package %s does not contain the name\n", path); + goto exit; + } + + snprintf(buff, sizeof(buff), "%s/%s", ISU_PKG_PATH, pkg_info->name); + if (path_exists(buff) && remove_dir(buff) != 0) { + goto exit; + } + + if (extract(path, buff, NULL) != ISU_RES_OK) { + goto exit; + } + + result = ISU_RES_OK; + +exit: + if (result == ISU_RES_OK) { + SLOGI("ISU Package (%s %s) install success\n", pkg_info->name, pkg_info->version); + } else { + SLOGE("ISU Package (%s) install error: %d\n", path, result); + } + + if (path_exists(temp_path)) { + remove_dir(temp_path); + } + + isu_pkg_info_free(pkg_info); + return result; +} + +isu_result isu_uninstall_internal(const char *name) +{ + RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE); + assert(name); + + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s", ISU_PKG_PATH, name); + int result = remove_dir(path); + if (result == 0) { + SLOGI("ISU Package (%s) uninstall success\n", name); + result = ISU_RES_OK; + } else { + SLOGE("ISU Package (%s) uninstall error: %d\n", name, result); + result = ISU_RES_ERR_NOT_EXIST; + } + return result; +} + +void cleanup_libmnt_table(struct libmnt_table **table) +{ + mnt_unref_table(*table); +} + +void cleanup_char_ptr(char **ptr) +{ + free(*ptr); +} + +isu_file_res is_isu_file(const char *path, pid_t pid, char **source_path) +{ + if (!is_isu_feature_supported()) + return ISU_FILE_RES_NOT_SUPPORTED; + + assert(path); + assert(source_path); + *source_path = NULL; + + char mountinfo_path[PATH_MAX]; + snprintf(mountinfo_path, sizeof(mountinfo_path), "/proc/%d/mountinfo", pid); + + __attribute__ ((__cleanup__(cleanup_libmnt_table))) struct libmnt_table *table = mnt_new_table_from_file(mountinfo_path); + if (table == NULL) { + SLOGW("Invalid mountinfo path (%s)\n", mountinfo_path); + return ISU_FILE_RES_ERROR; + } + + __attribute__ ((__cleanup__(cleanup_char_ptr))) char *fname_orig = mnt_resolve_path(path, NULL); + + /* + * Check if the fname_orig file or it subpath is bindmounted + */ + struct libmnt_fs *fs = NULL; + do { + if ((fs = mnt_table_find_target(table, fname_orig, MNT_ITER_FORWARD)) != NULL) { + /* + * Yes, fname_orig is mounted + */ + break; + } + char *slash = strrchr(fname_orig, '/'); + if (slash == NULL) + break; + /* + * Cut off the last part of the path + */ + *slash = '\0'; + } while (true); + + if (fs == NULL) { + goto normal_file; + } + + const char *source = mnt_fs_get_source(fs); + if (source == NULL) { + SLOGE("mnt_fs_get_source error\n"); + return ISU_FILE_RES_ERROR; + } + /* + * Check if the fname_orig path is mounted from the loop device + */ + if (strstr(source, "/dev/loop") == NULL) { + goto normal_file; + } + + /* + * If so, get the path in the mounted image + */ + char *in_image = strdup(mnt_fs_get_root(fs)); + if (in_image == NULL) { + SLOGE("Memory allocation error"); + return ISU_FILE_RES_ERROR; + } + + fs = mnt_table_find_source(table, mnt_fs_get_source(fs), MNT_ITER_FORWARD); + if (fs == NULL) { + SLOGE("mnt_table_find_source error"); + return ISU_FILE_RES_ERROR; + } + + /* + * Get the full path of the file outside the sandbox + */ + const char *img_mount_dir = mnt_fs_get_target(fs); + if (asprintf(source_path, "%s/%s", img_mount_dir, in_image) == -1) { + SLOGE("Memory allocation error"); + return ISU_FILE_RES_ERROR; + } + + return ISU_FILE_RES_IS_ISU_FILE; + +normal_file: + return ISU_FILE_RES_IS_NORMAL_FILE; +} + diff --git a/src/libisu/libisu-internal.h b/src/libisu/libisu-internal.h new file mode 100644 index 0000000..8b15994 --- /dev/null +++ b/src/libisu/libisu-internal.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * 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. + */ +#pragma once +#include +#include "libisu.h" + +#define ISUSERVICE_BUS_NAME "org.tizen.system.isu" +#define ISUSERVICE_OBJECT_PATH "/Org/Tizen/System/Isu" +#define ISUSERVICE_INTERFACE_NAME ISUSERVICE_BUS_NAME +#define ISU_FEATURE "http://tizen.org/feature/isu" +#define ISU_PKG_PATH "/opt/isu/" +#define ISU_RUN_PATH "/run/isu/" +#define ISU_CFG "isu.cfg" + +#ifdef LOG_TAG + #undef LOG_TAG +#endif +#define LOG_TAG "LIBISU" + +typedef enum { + ISU_FILE_RES_NOT_SUPPORTED = -2, + ISU_FILE_RES_ERROR = -1, + ISU_FILE_RES_IS_NORMAL_FILE = 0, + ISU_FILE_RES_IS_ISU_FILE = 1, +} isu_file_res; + +struct _isu_pkg_list { + struct _isu_pkg_list_element *head, *cur; +}; + +bool is_isu_feature_supported(); +#define RET_IF_FEATURE_NOT_SUPPORTED(value) \ + { \ + if (!is_isu_feature_supported()) { \ + SLOGE("ISU feature is not supported"); \ + return value; \ + } \ + } while(0) \ + + +struct _isu_pkg_list_element { + char *name; + struct _isu_pkg_list_element *next; +}; + +struct _isu_pkg_info { + char *name; + char *version; + size_t service_files_len; + char **service_files; +}; + +isu_result isu_install_internal(const char *path); +isu_result isu_uninstall_internal(const char *name); +isu_file_res is_isu_file(const char *path, pid_t pid, char **source_path); +struct _isu_pkg_info* get_pkg_info(const char *isu_cfg_path); diff --git a/src/libisu/libisu.c b/src/libisu/libisu.c new file mode 100644 index 0000000..4270c4e --- /dev/null +++ b/src/libisu/libisu.c @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * 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. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libisu-internal.h" + + +static bool is_valid_isu_pkg(const char *path) +{ + assert(path); + + struct stat statbuff; + char pathbuff[PATH_MAX+8]; + snprintf(pathbuff, sizeof(pathbuff), "%s/%s", path, ISU_CFG); + + return stat(pathbuff, &statbuff) == 0; +} + +/* + * Public API Functions + */ + +isu_pkg_list isu_list_init() +{ + RET_IF_FEATURE_NOT_SUPPORTED(NULL); + struct _isu_pkg_list *pkg_list = malloc(sizeof(struct _isu_pkg_list)); + if (pkg_list == NULL) { + SLOGE("Memory allocation error (%d): %m\n", errno); + return NULL; + } + + pkg_list->head = NULL; + pkg_list->cur = NULL; + DIR *dir = opendir(ISU_PKG_PATH); + if (dir == NULL) { + SLOGE("Open dir %s error (%d): %m\n", ISU_PKG_PATH, errno); + return NULL; + } + + struct _isu_pkg_list_element *prev = NULL; + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type != DT_DIR) continue; + + char pathbuff[PATH_MAX]; + snprintf(pathbuff, sizeof(pathbuff), "%s/%s", ISU_PKG_PATH, entry->d_name); + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + if (is_valid_isu_pkg(pathbuff)) { + struct _isu_pkg_list_element *element = malloc(sizeof(struct _isu_pkg_list_element)); + if (element == NULL) { + SLOGE("Memory allocation error (%d): %m\n", errno); + isu_list_free(pkg_list); + closedir(dir); + return NULL; + } + element->name = strdup(entry->d_name); + element->next = NULL; + if (prev == NULL) { + pkg_list->head = element; + pkg_list->cur = element; + } else { + prev->next = element; + } + prev = element; + } else { + SLOGW("Invalid ISU package directory: %s\n", pathbuff); + } + } + + closedir(dir); + + return pkg_list; +} + +isu_result isu_list_free(isu_pkg_list pkg_list) +{ + RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE); + if (pkg_list == NULL) + return ISU_RES_OK; + + struct _isu_pkg_list *pkg_list_i = (struct _isu_pkg_list*)pkg_list; + + struct _isu_pkg_list_element *element = pkg_list_i->head; + while (element != NULL) { + struct _isu_pkg_list_element *tmp = element->next; + free(element->name); + free(element); + element = tmp; + } + + free(pkg_list); + return ISU_RES_OK; +} + +isu_pkg_info isu_list_next(isu_pkg_list pkg_list) +{ + RET_IF_FEATURE_NOT_SUPPORTED(NULL); + struct _isu_pkg_list *pkg_list_i = (struct _isu_pkg_list *)pkg_list; + + if (pkg_list == NULL) { + SLOGE("Argument is NULL"); + return NULL; + } + + if (pkg_list_i->cur == NULL) + return NULL; + + char isu_cfg_path[PATH_MAX]; + snprintf(isu_cfg_path, sizeof(isu_cfg_path), "%s/%s/%s", ISU_PKG_PATH, pkg_list_i->cur->name, ISU_CFG); + isu_pkg_info pkg_info = get_pkg_info(isu_cfg_path); + + pkg_list_i->cur = pkg_list_i->cur->next; + return pkg_info; +} + +isu_result isu_pkg_get_name(isu_pkg_info pkg_info, char *name, size_t len) +{ + RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE); + + if (pkg_info == NULL || name == NULL) + return ISU_RES_ERR_ARGUMENT; + + struct _isu_pkg_info* pkg_info_i = (struct _isu_pkg_info*)pkg_info; + + if (len < strlen(pkg_info_i->name)) { + return ISU_RES_ERR_BUFF_TOO_SMALL; + } + + if (snprintf(name, len, "%s", pkg_info_i->name) < strlen(pkg_info_i->name)) { + return ISU_RES_ERR_INTERNAL; + } + return ISU_RES_OK; +} + +isu_result isu_pkg_get_version(isu_pkg_info pkg_info, char *version, size_t len) +{ + RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE); + + if (pkg_info == NULL || version == NULL) + return ISU_RES_ERR_ARGUMENT; + + struct _isu_pkg_info* pkg_info_i = (struct _isu_pkg_info*)pkg_info; + + if (len < strlen(pkg_info_i->version)) { + return ISU_RES_ERR_BUFF_TOO_SMALL; + } + + if (snprintf(version, len, "%s", pkg_info_i->version) < strlen(pkg_info_i->version)) { + return ISU_RES_ERR_INTERNAL; + } + return ISU_RES_OK; +} + +isu_result isu_pkg_info_free(isu_pkg_info pkg_info) +{ + RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE); + if (pkg_info == NULL) { + goto exit; + } + + if (pkg_info == NULL) + return ISU_RES_ERR_ARGUMENT; + + struct _isu_pkg_info* pkg_info_i = (struct _isu_pkg_info*)pkg_info; + + if (pkg_info_i != NULL) { + for (size_t i = 0; i < pkg_info_i->service_files_len; i++) { + free(pkg_info_i->service_files[i]); + } + free(pkg_info_i->service_files); + } + + free(pkg_info_i->name); + free(pkg_info_i->version); + free(pkg_info_i); +exit: + return ISU_RES_OK; +} + +isu_result isu_install(const char *path) +{ + RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE); + + if (path == NULL) + return ISU_RES_ERR_ARGUMENT; + + return isu_dbus_call("install", path); +} + +isu_result isu_uninstall(const char *path) +{ + RET_IF_FEATURE_NOT_SUPPORTED(ISU_RES_ERR_FEATURE); + + if (path == NULL) + return ISU_RES_ERR_ARGUMENT; + + return isu_dbus_call("uninstall", path); +} diff --git a/src/libisu/libisu.h b/src/libisu/libisu.h new file mode 100644 index 0000000..5d22ee3 --- /dev/null +++ b/src/libisu/libisu.h @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * + * 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. + */ + +#pragma once +#include + +/** + * @addtogroup CAPI_ISU_MODULE + * @{ + */ + +/** + * @brief ISU package list context + */ +typedef void* isu_pkg_list; + +/** + * @brief ISU package info context + */ +typedef void* isu_pkg_info; + +/** + * @brief Result + */ +typedef enum { + ISU_RES_OK, /**< Successful */ + ISU_RES_ERR_ARGUMENT, /**< Invalid arguments provided */ + ISU_RES_ERR_NAME, /**< Invalid name */ + ISU_RES_ERR_PATH, /**< Invalid path */ + ISU_RES_ERR_NOT_EXIST, /**< Package not exist */ + ISU_RES_ERR_NOT_PERMITTED, /**< No permission to perform the operation */ + ISU_RES_ERR_INVALID_FORMAT, /**< Invalid or corrupted ISU archive */ + ISU_RES_ERR_CHECKSUM, /**< Checksum does not match */ + ISU_RES_ERR_FEATURE, /**< Feature not supported */ + ISU_RES_ERR_INTERNAL, /**< Internal error */ + ISU_RES_ERR_BUFF_TOO_SMALL /**< Provided buffer is too small to store th full content */ +} isu_result; + +/** + * @platform + * @brief Install ISU package + * @since_tizen 9.0 + * @privlevel platform + * @privilege http://tizen.org/privilege/isu.admin + * + * + * @param[in] path - Path to the zip archive + * + * @return #isu_result + * @retval #ISU_RES_OK Success + * @retval #ISU_RES_ERR_ARGUMENT Invalid argument provided + * @retval #ISU_RES_ERR_FEATURE ISU Feature not supported + * @retval #ISU_RES_ERR_NOT_PERMITTED No permission to perform the operation + * @retval #ISU_RES_ERR_PATH Provided file not exist + * @retval #ISU_RES_ERR_INVALID_FORMAT Incorrect or corrupted ISU package + * @retval #ISU_RES_ERR_CHECKSUM Incorrect or corrupted ISU package + * @retval #ISU_RES_ERR_INTERNAL Internal error + */ +isu_result isu_install(const char *path); + +/** + * @brief Init ISU package list + * + * @return #isu_pkg_list on success, otherwise NULL + * + * @code + * + * void print_isu_pkgs(void) { + * isu_pkg_list pkg_list = isu_list_init(); + * if (pkg_list == NULL) { + * printf("isu_list_init error\n"); + * return; + * } + * + * isu_pkg_info pkg_info = NULL; + * + * while ((pkg_info = isu_list_next(isu_pkg_list)) != NULL) { + * char name[256]; + * isu_result = isu_pkg_get_name(pkg_info, name, sizeof(name)); + * isu_pkg_info_free(isu_result); + * + * if (isu_result != ISU_RES_OK) { + * printf("isu_pkg_get_name() error\n"); + * return; + * } + * printf("ISU package name: %s\n", name); + * } + * + * isu_list_free(pkg_list); + * } + * + * @endcode + */ +isu_pkg_list isu_list_init(); + +/** + * @brief Get the next ISU package + * @since_tizen 9.0 + * + * @param[in] pkg_list ISU list returned by #isu_list_init + * + * This function allocates new isu_pkg_list, which should be freed with #isu_list_free + * + * @return #isu_pkg_info on success, otherwise NULL + * @retval #isu_pkg_info + */ +isu_pkg_info isu_list_next(isu_pkg_list pkg_list); + +/** + * @brief Get ISU package name + * @since_tizen 9.0 + * + * @param[in] pkg_info ISU package info + * @param[out] name buffer for the ISU package name + * @param[in] len The name buffer length + * + * @return #isu_result + * @retval #ISU_RES_OK Success + * @retval #ISU_RES_ERR_ARGUMENT Invalid argument provided + * @retval #ISU_RES_ERR_BUFF_TOO_SMALL Buffer is to small to store the full content + * @retval #ISU_RES_ERR_INTERNAL Internal error + */ +isu_result isu_pkg_get_name(isu_pkg_info pkg_info, char *name, size_t len); + +/** + * @brief Get ISU version + * @since_tizen 9.0 + * + * @param[in] pkg_info ISU package info + * @param[out] version buffer for the ISU package version + * @param[in] len The name buffer length + * + * @return #isu_result + * @retval #ISU_RES_OK Success + * @retval #ISU_RES_ERR_ARGUMENT Invalid argument provided + * @retval #ISU_RES_ERR_INTERNAL Internal error + * @retval #ISU_RES_ERR_BUFF_TOO_SMALL Buffer is to small to store the full content + * @retval #ISU_RES_ERR_FEATURE ISU Feature not supported + */ +isu_result isu_pkg_get_version(isu_pkg_info pkg_info, char *version, size_t len); + +/** + * @brief Free the ISU package info context + * @since_tizen 9.0 + * + * @param[in] pkg_info ISU package info context + * + * @return #isu_result + * @retval #ISU_RES_OK Success + * @retval #ISU_RES_ERR_ARGUMENT Invalid argument provided + * @retval #ISU_RES_ERR_FEATURE ISU Feature not supported + */ +isu_result isu_pkg_info_free(isu_pkg_info pkg_info); + +/** + * @brief Free the ISU packages list context + * @since_tizen 9.0 + * + * @param[in] pkg_list ISU packages list info context + * + * @return #isu_result + * @retval #ISU_RES_OK Success + */ +isu_result isu_list_free(isu_pkg_list pkg_list); + +/** + * @platform + * @brief Uninstall ISU package + * @since_tizen 9.0 + * @privlevel platform + * @privilege http://tizen.org/privilege/isu.admin + * + * @param[in] pkg_name ISU package name + * + * @return #isu_result + * @retval #ISU_RES_OK Success + * @retval #ISU_RES_ERR_ARGUMENT Invalid argument provided + * @retval #ISU_RES_ERR_NOT_EXIST ISU package not installed + * @retval #ISU_RES_ERR_FEATURE ISU Feature not supported + * @retval #ISU_RES_ERR_NOT_PERMITTED No permission to perform the operation + */ +isu_result isu_uninstall(const char *pkg_name); + +/** + * @} + */ diff --git a/src/libisu/libisu.pc.in b/src/libisu/libisu.pc.in new file mode 100644 index 0000000..982be81 --- /dev/null +++ b/src/libisu/libisu.pc.in @@ -0,0 +1,14 @@ +# Package Information for pkg-config + +prefix=@PC_PREFIX@ +exec_prefix=/usr +libdir=@PC_LIBDIR@ +includedir=@PC_INCLUDE@ + +Name: @PC_NAME@ +Description: @PC_DESCRIPTION@ +Version: @PC_VERSION@ +Requires: @PC_REQUIRED@ +Libs: -L${libdir} @PC_LDFLAGS@ +Cflags: -I${includedir} + -- 2.7.4