From a5896b154f6032739feb669fd897b415748729fc Mon Sep 17 00:00:00 2001 From: HyungKyu Song Date: Sat, 16 Feb 2013 00:54:06 +0900 Subject: [PATCH] Tizen 2.0 Release --- AUTHORS | 1 + CMakeLists.txt | 53 + app2sd.manifest | 15 + app2sd.pc.in | 12 + debian/app2sd-dev.install.in | 2 + debian/app2sd.install.in | 1 + debian/changelog | 8 + debian/compat | 1 + debian/control | 27 + debian/copyright | 16 + debian/dirs | 2 + debian/rules | 126 +++ doc/images/SLP_app2ext_PG_image01.png | 0 doc/images/app2ext_diag.png | Bin 0 -> 19351 bytes doc/images/app2ext_install_diag.png | Bin 0 -> 23536 bytes doc/images/app2ext_uninstall_diag.png | Bin 0 -> 24973 bytes inc/SLP_app2ext_PG.h | 65 ++ inc/app2ext_interface.h | 402 ++++++++ packaging/app2sd.spec | 60 ++ plugin/app2sd/CMakeLists.txt | 44 + plugin/app2sd/inc/SLP_app2sd_PG.h | 74 ++ plugin/app2sd/inc/app2sd_interface.h | 404 ++++++++ plugin/app2sd/inc/app2sd_internals.h | 183 ++++ plugin/app2sd/src/app2sd_interface.c | 722 +++++++++++++ plugin/app2sd/src/app2sd_internals.c | 1371 +++++++++++++++++++++++++ plugin/app2sd/src/app2sd_internals_registry.c | 252 +++++ plugin/app2sd/src/app2sd_internals_utils.c | 470 +++++++++ src/app2ext_interface.c | 152 +++ 28 files changed, 4463 insertions(+) create mode 100755 AUTHORS create mode 100755 CMakeLists.txt create mode 100755 app2sd.manifest create mode 100755 app2sd.pc.in create mode 100644 debian/app2sd-dev.install.in create mode 100644 debian/app2sd.install.in create mode 100755 debian/changelog create mode 100755 debian/compat create mode 100755 debian/control create mode 100755 debian/copyright create mode 100755 debian/dirs create mode 100755 debian/rules create mode 100755 doc/images/SLP_app2ext_PG_image01.png create mode 100755 doc/images/app2ext_diag.png create mode 100755 doc/images/app2ext_install_diag.png create mode 100755 doc/images/app2ext_uninstall_diag.png create mode 100755 inc/SLP_app2ext_PG.h create mode 100755 inc/app2ext_interface.h create mode 100755 packaging/app2sd.spec create mode 100755 plugin/app2sd/CMakeLists.txt create mode 100755 plugin/app2sd/inc/SLP_app2sd_PG.h create mode 100755 plugin/app2sd/inc/app2sd_interface.h create mode 100755 plugin/app2sd/inc/app2sd_internals.h create mode 100755 plugin/app2sd/src/app2sd_interface.c create mode 100755 plugin/app2sd/src/app2sd_internals.c create mode 100755 plugin/app2sd/src/app2sd_internals_registry.c create mode 100755 plugin/app2sd/src/app2sd_internals_utils.c create mode 100755 src/app2ext_interface.c diff --git a/AUTHORS b/AUTHORS new file mode 100755 index 0000000..ead8dbd --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Garima Shrivastava diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..ed20cfa --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,53 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +#SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true) +PROJECT(app2ext C) + +SET(VERSION_MAJOR 0) +SET(VERSION "${VERSION_MAJOR}.4.2") + +#Add your submodule directory name +ADD_SUBDIRECTORY(plugin/app2sd) +### Required packages +INCLUDE(FindPkgConfig) +pkg_check_modules(pkgs REQUIRED dlog) + +FOREACH(flag ${pkgs_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +pkg_check_modules(libpkgs REQUIRED dlog) + +FOREACH(flag ${libpkgs_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") + +### Local include directories +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/inc ${CMAKE_SOURCE_DIR}/src) + +## build app2ext library +SET(app2ext_dir "${CMAKE_SOURCE_DIR}") +SET(app2ext_inc_dir "${app2ext_dir}/inc") +SET(app2ext_src_dir "${app2ext_dir}/src") +SET(APP2EXT "app2ext") +SET(libapp2ext_SOURCES ${app2ext_src_dir}/app2ext_interface.c) +SET(libapp2ext_LDFLAGS " -L/usr/lib -module -avoid-version -ldl ") +SET(libapp2ext_CFLAGS " ${CFLAGS} -fPIC -I${app2ext_inc_dir} ") + +ADD_LIBRARY(${APP2EXT} SHARED ${libapp2ext_SOURCES}) +SET_TARGET_PROPERTIES(${APP2EXT} PROPERTIES SOVERSION ${VERSION_MAJOR}) +SET_TARGET_PROPERTIES(${APP2EXT} PROPERTIES VERSION ${VERSION}) +SET_TARGET_PROPERTIES(${APP2EXT} PROPERTIES COMPILE_FLAGS "${libapp2ext_CFLAGS}") +TARGET_LINK_LIBRARIES(${APP2EXT} ${libpkgs_LDFLAGS}) + +SET(CMAKE_INSTALL_PREFIX "/usr") +SET(PREFIX ${CMAKE_INSTALL_PREFIX}) + + +CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/app2sd.pc.in ${CMAKE_BINARY_DIR}/app2sd.pc @ONLY) + +INSTALL(TARGETS ${APP2EXT} DESTINATION lib COMPONENT RuntimeLibraries) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/app2sd.pc DESTINATION lib/pkgconfig) +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/inc/app2ext_interface.h DESTINATION include) + diff --git a/app2sd.manifest b/app2sd.manifest new file mode 100755 index 0000000..ec5cd53 --- /dev/null +++ b/app2sd.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/app2sd.pc.in b/app2sd.pc.in new file mode 100755 index 0000000..bc90238 --- /dev/null +++ b/app2sd.pc.in @@ -0,0 +1,12 @@ +# Package information for app2sd +prefix=/usr +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: app2sd +Description: The app2sd Library +Version: 1.1.0 +Requires: vconf dlog +Cflags: -I${includedir} +Libs: -L${libdir} -lapp2sd -lapp2ext diff --git a/debian/app2sd-dev.install.in b/debian/app2sd-dev.install.in new file mode 100644 index 0000000..258b9f0 --- /dev/null +++ b/debian/app2sd-dev.install.in @@ -0,0 +1,2 @@ +@PREFIX@/include/app2sd_interface.h +@PREFIX@/lib/pkgconfig/*.pc diff --git a/debian/app2sd.install.in b/debian/app2sd.install.in new file mode 100644 index 0000000..bf766f0 --- /dev/null +++ b/debian/app2sd.install.in @@ -0,0 +1 @@ +@PREFIX@/lib/*.so* diff --git a/debian/changelog b/debian/changelog new file mode 100755 index 0000000..0d77b89 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,8 @@ +app2sd (0.2.1) unstable; urgency=low + + * Initial release + * Git: slp/pkgs/a/app2sd + * Tag: app2sd_0.2.1 + + -- Jaeho Lee Thu, 24 May 2012 12:04:51 +0530 + diff --git a/debian/compat b/debian/compat new file mode 100755 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100755 index 0000000..7bf93c6 --- /dev/null +++ b/debian/control @@ -0,0 +1,27 @@ +Source: app2sd +Section: devel +Priority: extra +Maintainer: Garima Shrivastava , Jaeho Lee +Build-Depends: debhelper (>= 5),libssl-dev, libslp-setting-dev, libslp-db-util-dev +Standards-Version: 3.7.2 + +Package: app2sd +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: + +Package: app2sd-dev +Architecture: any +Depends: app2sd (= ${Source-Version}) +Description: App2sd dev package + +Package: app2sd-doc +Architecture: all +Description: + +Package: app2sd-dbg +Section: debug +Architecture: any +Depends: app2sd (= ${Source-Version}) +Description: App2sd dbg package + diff --git a/debian/copyright b/debian/copyright new file mode 100755 index 0000000..37b94ac --- /dev/null +++ b/debian/copyright @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2012 - 2013 Samsung Electronics Co., Ltd. All rights reserved. + * + * 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. + * + */ diff --git a/debian/dirs b/debian/dirs new file mode 100755 index 0000000..ca882bb --- /dev/null +++ b/debian/dirs @@ -0,0 +1,2 @@ +usr/bin +usr/sbin diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..f902937 --- /dev/null +++ b/debian/rules @@ -0,0 +1,126 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + +# These are used for cross-compiling and for saving the configure script +# from having to guess our platform (since we know it already) +DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) +DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) +DEB_HOST_ARCH ?= $(shell dpkg-architecture -qDEB_HOST_ARCH) +DEB_HOST_ARCH_OS ?= $(shell dpkg-architecture -qDEB_HOST_GNU_OS) + +CFLAGS ?= -Wall -g +LDFLAGS ?= +PREFIX ?= /usr +DATADIR ?= /opt + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +# architecture is not arm +ifneq (, $(findstring arm, $(DEB_HOST_ARCH))) + # do something here +else + # do something here for arm +endif + +CFLAGS += -fvisibility=hidden -fPIC +LDFLAGS += -Wl,--rpath=$(PREFIX)/lib -Wl,--as-needed + +CMAKE_TMP_DIR = $(CURDIR)/cmake_tmp + +config.status: + +configure: configure-stamp + +configure-stamp: + dh_testdir + mkdir -p $(CMAKE_TMP_DIR); + export LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(CMAKE_TMP_DIR) && cd $(CMAKE_TMP_DIR); CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" LDFLAGS="$(LDFLAGS)" cmake .. -DCMAKE_INSTALL_PREFIX=$(PREFIX) + touch configure-stamp + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + # Add here commands to compile the package. + cd $(CMAKE_TMP_DIR) && $(MAKE) all + + for f in `find $(CURDIR)/debian/ -name "*.in"`; do \ + cat $$f > $${f%.in}; \ + sed -i -e "s#@PREFIX@#$(PREFIX)#g" $${f%.in}; \ + sed -i -e "s#@DATADIR@#$(DATADIR)#g" $${f%.in}; \ + done + + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f *-stamp + + rm -rf $(CMAKE_TMP_DIR) + + for f in `find $(CURDIR)/debian/ -name "*.in"`; do \ + rm -f $${f%.in}; \ + done + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Add here commands to install the package into debian/ncurses. + cd $(CMAKE_TMP_DIR) && $(MAKE) DESTDIR=$(CURDIR)/debian/tmp install + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs +# dh_installdocs + dh_installexamples + dh_install --list-missing --sourcedir=debian/tmp +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip --dbg-package=app2sd-dbg + dh_compress + dh_fixperms +# dh_perl + dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install diff --git a/doc/images/SLP_app2ext_PG_image01.png b/doc/images/SLP_app2ext_PG_image01.png new file mode 100755 index 0000000..e69de29 diff --git a/doc/images/app2ext_diag.png b/doc/images/app2ext_diag.png new file mode 100755 index 0000000000000000000000000000000000000000..137eaf30c7331c64240c3c8b61f815e63204a63d GIT binary patch literal 19351 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 literal 0 HcmV?d00001 diff --git a/doc/images/app2ext_install_diag.png b/doc/images/app2ext_install_diag.png new file mode 100755 index 0000000000000000000000000000000000000000..6fd73eeb0cc0e2d8323607d1afc5078954d47f7a GIT binary patch literal 23536 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 literal 0 HcmV?d00001 diff --git a/doc/images/app2ext_uninstall_diag.png b/doc/images/app2ext_uninstall_diag.png new file mode 100755 index 0000000000000000000000000000000000000000..2e61c714850db438e0a8fbcd7c16b6867ebc0f95 GIT binary patch literal 24973 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 z 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 o&pO~wyM1Piknr(+?{KoF`|fO)-T^ + * + * 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. + * + */ + +/** + * + * @ingroup SLP_PG + * @defgroup app2ext_PG App2Ext + * @{ + +

Introduction

+App2Ext is a feature that enables package installers to install applications on external storage like SD card, Micro USB flash drive or Cloud. +It also provides option to move installed applications from external memory to internal memory and vice versa. + +

App2Ext process view

+\image html app2ext_diag.png "Picture 1. App2Ext Process View Diagram" +\image rtf app2ext_diag.png "Picture 1. App2Ext Process View Diagram" + +

Installation to SD Card

+Package installer should call the App2Ext's Init API to initialize SD plug-in. Once the plug-in initialization is done App2Ext returns a storage handle to the Package installer. Package installer should then call the pre-install setup API with respect to the storage handle which will be mapped to app2sd's pre-install API. The App2Ext Pre-install API performs the setup required for the installation based on the external storage type. +After pre-install setup is done Package installer can proceed with the installation of the application. When package installation is completed by the package installer, post-install setup API should be called. This API removes the setup created for installation during pre-install API. + +Refer to Picture 2. for flow diagram. + +

Installation Setup Flow for App2SD plug-in

+\image html app2ext_install_diag.png "Picture 2. Installation on SD card Flow Diagram" +\image rtf app2ext_install_diag.png "Picture 2. Installation on SD card Flow Diagram" + +

Un-installation from SD Card

+Package installer should call the App2Ext's Init API to initialize SD plug-in. Once the plug-in initialization is done App2Ext returns a storage handle to the Package installer. Package installer should then call the pre-uninstall setup API with respect to the storage handle which will be mapped to app2sd's pre-uninstall API. Pre-uninstall API performs the setup required for the package un-installation based on the external storage type. +After pre-uninstall setup is done Package installer can proceed with un-installation of the package. When package un-installation is completed by the package installer, post-uninstall setup API should be called. This API removes the setup created for un-installation during pre-uninstall API. + +Refer to Picture 3. for flow diagram. + +

Un-installation Setup Flow for App2SD plug-in

+\image html app2ext_uninstall_diag.png "Picture 3. Un-installation from SD card Flow Diagram" +\image rtf app2ext_uninstall_diag.png "Picture 3. Un-installation from SD card Flow Diagram" + + +

API list and description

+
    +
  • app2ext_init() : Initialize plug-in based on storage type
  • +
  • app2ext_deinit() : De-initialize plug-in
  • +
  • app2ext_get_app_location() : Returns application current location
  • +
+ + * @} + */ diff --git a/inc/app2ext_interface.h b/inc/app2ext_interface.h new file mode 100755 index 0000000..fc11918 --- /dev/null +++ b/inc/app2ext_interface.h @@ -0,0 +1,402 @@ +/* + * app2ext + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Jyotsna Dhumale + * + * 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. + * + */ + +#ifndef __APP2EXT_INTERFACE_H__ +#define __APP2EXT_INTERFACE_H__ + +/** + * @file app2ext_interface.h + * @version 0.5 + * @brief This file declares API of app2ext library + */ +/** + * @addtogroup APPLICATION_FRAMEWORK + * @{ + * + * @defgroup app2ext + * @version 0.5 + * + * @section Header to use them: + * @code + * #include + * @endcode + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef API +#define API __attribute__ ((visibility("default"))) +#endif + +#include +#include +#ifdef LOG_TAG +#undef LOG_TAG +#endif + +#define LOG_TAG "APP2EXT" + +#ifdef _DEBUG_MODE_ +#define app2ext_print(fmt, arg...) LOGD(fmt,##arg) +#else +#define app2ext_print(FMT, ARG...) SLOGD(FMT,##ARG); +#endif + +#define APP2EXT_SUCCESS 0 +#define MMC_PATH "/opt/storage/sdcard" +#define APP2SD_PATH MMC_PATH"/app2sd/" +#define APP_INSTALLATION_PATH "/opt/usr/apps/" + +/** + * Enum for application installation location + */ +typedef enum app2ext_install_location_t { + APP2EXT_INTERNAL_MEM = 0, + APP2EXT_SD_CARD, + APP2EXT_MICRO_USB, + APP2EXT_CLOUD, + APP2EXT_NOT_INSTALLED +} app2ext_install_location; + +/** + * Enum for installation/upgrade status[success/failure] + */ +typedef enum app2ext_status_t { + APP2EXT_STATUS_FAILED = 1, + APP2EXT_STATUS_SUCCESS +} app2ext_status; + +/** + * Enum for directory type + */ +typedef enum app2ext_dir_type_t { + APP2EXT_DIR_RO, + APP2EXT_DIR_RW, +} app2ext_dir_type; + +/** + * Enum for move command + * @see app2sd_move_installed_app() + */ +typedef enum app2ext_move_type_t { + APP2EXT_MOVE_TO_EXT = 1, + APP2EXT_MOVE_TO_PHONE +} app2ext_move_type; + +/** + * Enum for error codes + */ +typedef enum app2ext_error_t { + APP2EXT_ERROR_INVALID_ARGUMENTS = 2, + APP2EXT_ERROR_MOVE, + APP2EXT_ERROR_PRE_UNINSTALL, + APP2EXT_ERROR_MMC_STATUS, + APP2EXT_ERROR_DB_INITIALIZE, + APP2EXT_ERROR_SQLITE_REGISTRY, + APP2EXT_ERROR_PASSWD_GENERATION, + APP2EXT_ERROR_MMC_INFORMATION, + APP2EXT_ERROR_MMC_INSUFFICIENT_MEMORY, + APP2EXT_ERROR_DELETE_DIRECTORY, + APP2EXT_ERROR_CREATE_SYMLINK, + APP2EXT_ERROR_CREATE_DIRECTORY, + APP2EXT_ERROR_DELETE_LINK_FILE, + APP2EXT_ERROR_PKG_EXISTS, + APP2EXT_ERROR_ACCESS_FILE, + APP2EXT_ERROR_OPEN_DIR, + APP2EXT_ERROR_ALREADY_FILE_PRESENT, + APP2EXT_ERROR_FILE_ABSENT, + APP2EXT_ERROR_STRCMP_FAILED, + APP2EXT_ERROR_INVALID_PACKAGE, + APP2EXT_ERROR_CREATE_DIR_ENTRY, + APP2EXT_ERROR_PASSWORD_GENERATION, + APP2EXT_ERROR_COPY_DIRECTORY, + APP2EXT_ERROR_INVALID_CASE, + APP2EXT_ERROR_SYMLINK_ALREADY_EXISTS, + APP2EXT_ERROR_APPEND_HASH_TO_FILE, + APP2EXT_ERROR_CREATE_DEVICE, + APP2EXT_ERROR_DO_LOSETUP, + APP2EXT_ERROR_CREATE_FS, + APP2EXT_ERROR_MOUNT_PATH, + APP2EXT_ERROR_CLEANUP, + APP2EXT_ERROR_MOUNT, + APP2EXT_ERROR_REMOUNT, + APP2EXT_ERROR_PIPE_CREATION, + APP2EXT_ERROR_LOOPBACK_DEVICE_UNAVAILABLE, + APP2EXT_ERROR_VCONF_REGISTRY, + APP2EXT_ERROR_FIND_ASSOCIATED_DEVICE_NODE, + APP2EXT_ERROR_UNMOUNT, + APP2EXT_ERROR_DELETE_LOOPBACK_DEVICE, + APP2EXT_ERROR_DETACH_LOOPBACK_DEVICE, + APP2EXT_ERROR_ALREADY_MOUNTED, + APP2EXT_ERROR_PLUGIN_INIT_FAILED, + APP2EXT_ERROR_PLUGIN_DEINIT_FAILED +} app2ext_error; + +/** + * @brief :This function type is for a function that is implemented by plugin + * and called before application is to be installed. + * + * @param[in] appname application package name which is to be installed + * @param[in] dir_list directory structure of the application + * This should be polulated by the package manager + * before calling pre_install and should be freed after + * pre_install returns. + * Each node of dir_list is of type app2ext_dir_details + * which has members Name(dirname) and Type (RO/RW) + * For eg for rpm the dir_list should be populated with + * nodes like : (lib, APP2EXT_DIR_RO), (res, APP2EXT_DIR_RO), + (bin, APP2EXT_DIR_RO), (data, APP2EXT_DIR_RW) + * @param[in] size Size of the application + * @return 0 if success, error code(>0) if fail + */ +typedef int (*app2ext_pre_install)(const char *appname, GList* dir_list, int size); + +/** + * @brief :This function type is for a function that is implemented by plugin + * and called after application installation. + * + * @param[in] appname application package name which is to be installed + * @param[in] install_status Installation status (Success/Failure) + * [ Enum :APP2EXT_STATUS_SUCCESS, + * APP2EXT_STATUS_FAILED] + * @return 0 if success, error code(>0) if fail + */ +typedef int (*app2ext_post_install)(const char *appname, app2ext_status install_status); + +/** + * @brief :This function type is for a function that is implemented by plugin + * and called before application upgrade. + * + * @param[in] appname application package name which is to be upgraded + * @param[in] dir_list directory structure of the application + * This should be polulated by the package manager + * before calling pre_upgrade and should be freed after + * pre_upgrade returns. + * Each node of dir_list is of type app2ext_dir_details + * which has members Name(dirname) and Type (RO/RW) + * For eg for rpm the dir_list should be populated with + * nodes like : (lib, APP2EXT_DIR_RO), (res, APP2EXT_DIR_RO), + (bin, APP2EXT_DIR_RO), (data, APP2EXT_DIR_RW) + * @param[in] size Size of the application + * @return 0 if success, error code(>0) if fail + */ +typedef int (*app2ext_pre_upgrade)(const char *appname, GList* dir_list, int size); + +/** + * @brief :This function type is for a function that is implemented by plugin + * and called before application upgradation. + * + * @param[in] appname application package name which is to be upgraded + * @param[in] upgrade_status Upgrade status (Success/Failure) + * [ Enum :APP2EXT_STATUS_SUCCESS, + * APP2EXT_STATUS_FAILED] + * @return 0 if success, error code(>0) if fail + */ +typedef int (*app2ext_post_upgrade)(const char *appname, app2ext_status upgrade_status); + +/** + * @brief :This function type is for a function that is implemented by plugin + * and called before application uninstallation. + * + * @param[in] appname application package name which is to be uninstalled + * @return 0 if success, error code(>0) if fail + */ +typedef int (*app2ext_pre_uninstall)(const char *appname); + +/** + * @brief :This function type is for a function that is implemented by plugin + * and called after application uninstallation. + * + * @param[in] appname application package name which is to be uninstalled + * @return 0 if success, error code(>0) if fail + */ +typedef int (*app2ext_post_uninstall)(const char *appname); + +/** + * @brief :This function type is for a function that is implemented by plugin + * and called when application is to be moved from extrenal memory + *to internal memory or vice versa. + * + * @param[in] appname application package name which is to be moved + * @param[in] dir_list directory structure of the application + * This should be polulated by the package manager + * before calling move and should be freed after + * move returns. + * Each node of dir_list is of type app2ext_dir_details + * which has members Name(dirname) and Type (RO/RW) + * For eg for rpm the dir_list should be populated with + * nodes like : (lib, APP2EXT_DIR_RO), (res, APP2EXT_DIR_RO), + (bin, APP2EXT_DIR_RO), (data, APP2EXT_DIR_RW) + * @param[in] move_type move type + * [Enum: APP2EXT_MOVE_TO_EXT, APP2EXT_MOVE_TO_PHONE] + * @return 0 if success, error code(>0) if fail + */ +typedef int (*app2ext_move)(const char *appname, GList* dir_list, app2ext_move_type move_type); + +/** + * @brief :This function type is for a function that is implemented by plugin + * and called to enable application before application launch. + * + * @param[in] appname application package name which is to be enabled + * @return 0 if success, error code(>0) if fail + */ +typedef int (*app2ext_enable)(const char *appname); + +/** + * @brief :This function type is for a function that is implemented by plugin + * and called to disable application before application exit. + * + * @param[in] appname application package name which is to be disabled + * @return 0 if success, error code(>0) if fail + */ +typedef int (*app2ext_disable)(const char *appname); + +/** + * This structure defines the app2ext interfaces. Plugins have to implement these functions + */ +typedef struct app2ext_interface_t{ + app2ext_pre_install pre_install; + app2ext_post_install post_install; + app2ext_pre_upgrade pre_upgrade; + app2ext_post_upgrade post_upgrade; + app2ext_pre_uninstall pre_uninstall; + app2ext_post_uninstall post_uninstall; + app2ext_move move; + app2ext_enable enable; + app2ext_disable disable; +} app2ext_interface; + +/** + * This structure defines app2ext handle .Each storage type maps to a different plugin + * type : storage type + * plugin_handle : plugin handle + */ +typedef struct { + app2ext_install_location type; + void *plugin_handle; + app2ext_interface interface; +} app2ext_handle; + +/** + * This structure defines directory details + * name : directory name + * type : permission (rw/ro) + */ +typedef struct { + char * name; + app2ext_dir_type type; +} app2ext_dir_details; + +/** + * @brief : This API initializes the appropriate plugin based on storage type. + * It should be called before installation/uninstallation/upgrade + * @param[in] storage_type Location where package should be installed + * [Ex: SD card, MicroUSB, Cloud] + * @return app2ext_handle pointer if success, NULL if fail + * + @code + #include + app2ext_handle *handle = NULL; + GLIst *dir_list = NULL; + handle = app2ext_init(APP2EXT_SD_CARD); //Initializes SD card plug-in + if(handle) + { + printf("\n SUCCESS"); + // Perform package install/uninstall/upgrade/move here + // Packge install example + // Package manager should polulate dir_list with directory structure information of the package + ret = handle->interface.pre_install("com.samsung.calculator", dir_list, 20); + if (ret) { + printf("\n TC : pre app install API fail. Reason %s", error_list[ret]); + return -1; + } + + // Package manager installs the package + + ret = handle->interface.post_install("com.samsung.calculator", APP2EXT_STATUS_SUCCESS); + if (ret) { + printf("\n TC : post app install API fail Reason %s", error_list[ret]); + + return -1; + } + // Package manager should free dir_list + return; + } else + printf("\n FAILURE"); + @endcode + */ +API app2ext_handle *app2ext_init(int storage_type); + +/** + * @brief : This API deinitializes the plugin + * This should be called when use of the plugin is completed + * @param[in] handle pointer to app2ext_handle which is to be deinitialized + * @pre Initialization is done for the storage handle + * @return 0 if success, error code(>0) if fail + * + @code + #include + app2ext_handle *handle = NULL; + handle = app2ext_init(APP2EXT_SD_CARD); //Initializes SD card plug-in + int ret = -1; + ret = app2ext_deinit(handle); // De-initializes the SD plugin + if(!ret) + { + printf("\n SUCCESS"); + } + else + printf("\n FAILURE"); + @endcode + */ +API int app2ext_deinit(app2ext_handle *handle); + +/** + * @brief : This API returns the application location + * by refering to package manager DB + * This should be called to know location of an application + * @param[in] appname name of the application + * @return APP2EXT_SD_CARD if app is in SD card, + * APP2EXT_INTERNAL_MEM if app is in internal memory + * error code(>0) if fail + *@remarks see app2ext_install_location for more details + @code + #include +int ret = -1; + +ret = app2ext_get_app_location("com.samsung.calculator"); +if (ret == APP2EXT_SD_CARD) { + printf("\n app is in sd card "); +} else if (ret == APP2EXT_INTERNAL_MEM) { + printf("\n app is in internal memory "); +} else { + printf("\napp is not installed"); +} + @endcode + */ +API int app2ext_get_app_location(const char *appname); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/packaging/app2sd.spec b/packaging/app2sd.spec new file mode 100755 index 0000000..012342c --- /dev/null +++ b/packaging/app2sd.spec @@ -0,0 +1,60 @@ +Name: app2sd +Summary: Application installation on external memory +Version: 0.5 +Release: 8 +Group: TO_BE/FILLED_IN +License: Apache2.0 +Source0: %{name}-%{version}.tar.gz + +Requires(post): /sbin/ldconfig +Requires(postun): /sbin/ldconfig +BuildRequires: pkgconfig(libssl) +BuildRequires: pkgconfig(vconf) +BuildRequires: pkgconfig(dlog) +BuildRequires: pkgconfig(openssl) +BuildRequires: pkgconfig(db-util) +BuildRequires: cmake + +%description +Tizen application installation on external memory + +%package devel +Summary: Application install on external memory (devel) +Group: Development/Libraries +Requires: app2sd = %{version}-%{release} + +%description devel +Tizen application installation on external memory (devel) + +%prep +%setup -q + +%build +cmake . -DCMAKE_INSTALL_PREFIX=%{_prefix} + +make %{?jobs:-j%jobs} + +%install +rm -rf %{buildroot} +%make_install + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + +%files +%manifest app2sd.manifest +%defattr(-,root,root,-) +%{_libdir}/libapp2ext.so.* +%{_libdir}/libapp2sd.so* + + +%files devel +%defattr(-,root,root,-) +%{_includedir}/app2ext_interface.h +%{_libdir}/pkgconfig/app2sd.pc +%{_libdir}/libapp2sd.so +%{_libdir}/libapp2ext.so + + + diff --git a/plugin/app2sd/CMakeLists.txt b/plugin/app2sd/CMakeLists.txt new file mode 100755 index 0000000..ceff836 --- /dev/null +++ b/plugin/app2sd/CMakeLists.txt @@ -0,0 +1,44 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT(app2sd C) + +### Required packages +INCLUDE(FindPkgConfig) +pkg_check_modules(pkgs REQUIRED libssl dlog openssl db-util) + +FOREACH(flag ${pkgs_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +pkg_check_modules(libpkgs REQUIRED libssl dlog openssl db-util) + +FOREACH(flag ${libpkgs_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") + +### Local include directories +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/inc ${CMAKE_SOURCE_DIR}/src) + +## build app2sd library +SET(app2sd_dir "${CMAKE_SOURCE_DIR}/plugin/app2sd") +SET(app2sd_inc_dir "${app2sd_dir}/inc") +SET(app2sd_src_dir "${app2sd_dir}/src") +SET(APP2SD "app2sd") +SET(libapp2sd_SOURCES ${app2sd_src_dir}/app2sd_internals.c ${app2sd_src_dir}/app2sd_interface.c ${app2sd_src_dir}/app2sd_internals_registry.c ${app2sd_src_dir}/app2sd_internals_utils.c) +SET(libapp2sd_LDFLAGS " -L/usr/lib -lcrypto -module -avoid-version ") +SET(libapp2sd_CFLAGS " ${CFLAGS} -fPIC -I${app2sd_inc_dir} ") + +ADD_LIBRARY(${APP2SD} SHARED ${libapp2sd_SOURCES}) +SET_TARGET_PROPERTIES(${APP2SD} PROPERTIES SOVERSION ${VERSION_MAJOR}) +SET_TARGET_PROPERTIES(${APP2SD} PROPERTIES VERSION ${VERSION}) +SET_TARGET_PROPERTIES(${APP2SD} PROPERTIES COMPILE_FLAGS "${libapp2sd_CFLAGS}") +TARGET_LINK_LIBRARIES(${APP2SD} ${libpkgs_LDFLAGS}) + +SET(CMAKE_INSTALL_PREFIX "/usr") +SET(PREFIX ${CMAKE_INSTALL_PREFIX}) + + +INSTALL(TARGETS ${APP2SD} DESTINATION lib COMPONENT RuntimeLibraries) + + diff --git a/plugin/app2sd/inc/SLP_app2sd_PG.h b/plugin/app2sd/inc/SLP_app2sd_PG.h new file mode 100755 index 0000000..4933bc1 --- /dev/null +++ b/plugin/app2sd/inc/SLP_app2sd_PG.h @@ -0,0 +1,74 @@ +/* + * app2sd + * + * Copyright (c) 2012 - 2013 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Garima Shrivastava + * Jyotsna Dhumale + * Venkatesha Sarpangala + * + * 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. + * + */ + +/** + * + * @ingroup SLP_PG + * @defgroup app2sd_PG App2sd + * @{ + +

Introduction

+App2sd is a feature that enables package installers to install applications on sdcard. +It also provides API to move installed applications from sd card to internal memory and vice versa. +App2sd provides an API for validating the integrity of the package before launching by the launchpad. + +

App2sd process view

+\image html app2sd_diag.png "Picture 1. Process View Diagram" +\image rtf app2sd_diag.png "Picture 1. Process View Diagram" + +

Installation to SD Card

+Package installer should call the App2sd pre-install setup API before installation. +This API creates directory structure in SD card. +Refer to Picture 2. for flow diagram. + +

App2sd Installation Setup Flow

+\image html app2sd_install_diag.png "Picture 2. Installation Flow Diagram" +\image rtf app2sd_install_diag.png "Picture 2. Installation Flow Diagram" + +

Uninstallation to SD Card

+Package installer should call the App2sd pre-uninstall setup API before uninstallation. +Once the uninstallation is done by the package installer +then App2sd post-uninstall setup API should be called. +This API will clean up the directory structure and remove password from sqlite db. +Refer to Picture 3. for flow diagram. +

App2sd Uninstallation Setup Flow

+\image html app2sd_uninstall_diag.png "Picture 3. Uninstallation Flow Diagram" +\image rtf app2sd_uninstall_diag.png "Picture 3. Uninstallation Flow Diagram" + +

API list and description

+
    +
  • app2sd_pre_app_install() : Pre app installation setup.
  • +
  • app2sd_post_app_install() : Post app installation setup.
  • +
  • app2sd_pre_app_upgrade() : Pre app upgrade setup.
  • +
  • app2sd_post_app_upgrade() : Post app upgarde setup.
  • +
  • app2sd_pre_app_uninstall() : Pre app uninstall setup.
  • +
  • app2sd_post_app_uninstall() : Post app uninstall setup.
  • +
  • app2sd_move_installed_app() : Move installed application to/from sdcard
  • +
  • app2sd_get_app_install_location() : Get application installation location[external\internal memory].
  • +
  • app2sd_on_demand_setup_init() : Enables the application installed in sdcard.
  • +
  • app2sd_on_demand_setup_exit() : Disables the application installed in sdcard.
  • +
  • +
+ + * @} + */ diff --git a/plugin/app2sd/inc/app2sd_interface.h b/plugin/app2sd/inc/app2sd_interface.h new file mode 100755 index 0000000..a59cb9a --- /dev/null +++ b/plugin/app2sd/inc/app2sd_interface.h @@ -0,0 +1,404 @@ +/* + * app2ext + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Garima Shrivastava + * Jyotsna Dhumale + * Venkatesha Sarpangala + * + * 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. + * + */ + +#ifndef __APPTOSD_INTERFACE_H__ +#define __APPTOSD_INTERFACE_H__ + +/** + * @file app2sd_interface.h + * @version 0.2 + * @brief This file declares API of app2sd library + */ +/** + * @addtogroup APPLICATION_FRAMEWORK + * @{ + * + * @defgroup app2sd + * @version 0.2 + * + * @section Header to use them: + * @code + * #include + * @endcode + * + * @addtogroup app2sd + * @{ + */ + + +#ifdef __cplusplus +extern "C" { +#endif + +#include + + + /** + * @brief : This API prepares the setup for installation in SD card. + * It should be called before actual installation is done. + * @pre vfat type sd card must be present. + * @post Installation is done by package installer. + Encryption password is saved in db /opt/dbspace/.app2sd.db + * @param[in] appname application package name + * [Ex: com.samsung.calculator] + *This entry is parsed from application package control/manifest file. + * @param[in] dir_list directory structure of the application + * @param[in] size size of memory required by application(in MB). + *This entry is parsed from application package control/manifest file. + * @return 0 if success, error code(>0) if fail + * @remark None. + * + * + @code + #include + int ret = -1; + GList* dir_list = NULL; + // Package manager populates dir_list with directory structure information + ret= app2sd_pre_app_install + ("com.samsung.calculotor", dir_list, 10); + if(!ret) + { + printf("\n SUCCESS"); + // Package installer performs installation + // app2sd_post_app_install() API should be called + return; + } + else + printf("\n FAILURE"); + @endcode + */ + API int app2sd_pre_app_install(const char *appname, + GList* dir_list, int size); + + /** + * @brief : This API does post installation operations after + * the installation in SD card + * @param[in] appname application package name + * [Ex: com.samsung.calculator] + * @param[in] install_status Status of installation of package + *[ enum app2ext_status].If package installation failed then + * install_status= APP2EXT_STATUS_FAILURE else if installation + * was successful then install_status = APP2EXT_ISTATUS_SUCCESS. + * @pre Installation should be done by package installer. + * @return 0 if success, error code(>0) if fail + * @remark @see enum app2sd_install_status + * + * + @code + #include + int ret = -1; + ret= app2sd_pre_app_install + ("com.samsung.calculotor", APP2EXT_NATIVE_APP, 10); + if(!ret) + { + printf("\n SUCCESS"); + // Package installer performs installation + //Package was installed successfully. + if(package_installation _success) + { + ret = app2sd_post_app_install + ("com.samsung.calculator",APP2EXT_STATUS_SUCCESS); + if(!ret) + printf("\n SUCCESS"); + else + printf("\n FAILURE"); + return; + } + else + { + //Package installation failed + ret = app2sd_post_app_install + ("com.samsung.calculator",APP2EXT_STATUS_FAILURE); + if(!ret) + printf("\n SUCCESS"); + else + printf("\n FAILURE"); + return; + } + } + else + printf("\n FAILURE"); + @endcode + */ + API int app2sd_post_app_install(const char *appname, + app2ext_status install_status); + + /** + * @brief : This API prepares the setup for upgradation of + * application package + * @pre vfat type sd card must be present. + * @post Upgradation is done by package installer. + * @param[in] appname application package name + * [Ex: com.samsung.calculator] + * @param[in] dir_list directory structure of the application + * @param[in] size size of memory required by application(in MB). + *This entry is parsed from application package control/manifest file. + * @return 0 if success, error code(>0) if fail + * @remark None. + * + * + @code + #include + int ret = -1; + GList* dir_list = NULL; + // Package manager populates dir_list with directory structure information + ret= app2sd_pre_app_upgrade + ("com.samsung.calculator", dir_list, 10); + if(!ret) + { + printf("\n SUCCESS"); + // Package installer performs upgradation + // app2sd_post_app_upgrade() API should be called + return; + } + else + printf("\n FAILURE"); + @endcode + */ + API int app2sd_pre_app_upgrade(const char *appname, + GList* dir_list, int size); + + /** + * @brief : This API does post upgradation operations after + * the installation in SD card + * @param[in] appname application package name + * [Ex: com.samsung.calculator] + * @param[in] install_status Status of installation of package + *[ enum app2extl_status].If package upgradation failed then + * upgrade_status= APP2EXT_STATUS_FAILURE else if upgradation + * was successful then upgrade_status = APP2EXT_STATUS_SUCCESS. + * @pre Upgradation should be done by package installer. + * @return 0 if success, error code(>0) if fail + * @remark @see enum app2ext_status + * + * + @code + #include + int ret = -1; + ret= app2sd_pre_app_upgrade + ("com.samsung.calculator", APP2EXT_NATIVE_APP, 10); + if(!ret) + { + printf("\n SUCCESS"); + // Package installer performs upgradation + //Package was upgraded successfully. + if(package_upgradation _success) + { + ret = app2sd_post_app_upgrade + ("com.samsung.calculator",APP2EXT_STATUS_SUCCESS); + if(!ret) + printf("\n SUCCESS"); + else + printf("\n FAILURE"); + return; + } + else + { + //Package upgradation failed + ret = app2sd_post_app_upgrade + ("com.samsung.calculator",APP2EXT_STATUS_FAILURE); + if(!ret) + printf("\n SUCCESS"); + else + printf("\n FAILURE"); + return; + } + } + else + printf("\n FAILURE"); + @endcode + */ + API int app2sd_post_app_upgrade(const char *appname, + app2ext_status upgrade_status); + + /** + * @brief: This API prepares the setup for uninstallation + * @pre Package must be installed in sdcard. + * @post Package is uninstalled by the package installer. + * @param[in] appname application package name + * [Ex: com.samsung.calculator] + * @return 0 if success, error code(>0) if fail + * @remark None. + * + * + @code + #include + int ret = -1; + ret= app2sd_pre_app_uninstall + ("com.samsung.calculator"); + if(!ret) + { + printf("\n SUCCESS"); + // Package installer performs uninstallation + // app2sd_post_app_uninstall() API should be called + return; + } + else + printf("\n FAILURE"); + @endcode + */ + API int app2sd_pre_app_uninstall(const char *appname); + + /** + * @brief This API removes the resources created during + app2sd setup.It is called after uninstallation. + * @pre Package must be uninstalled . + * @post Encryption password is removed from sqlite db. + * @param[in] appname application package name + * [Ex: com.samsung.calculator] + * @return 0 if success, error code(>0) if fail + * @remark None. + * + * + @code + #include + int ret = -1; + ret= app2sd_pre_app_uninstall + ("com.samsung.calculator"); + if(!ret) + { + printf("\n SUCCESS"); + // Package installer performs uninstallation + ret = app2sd_post_app_uninstall("com.samsung.calculator"); + if(!ret) + printf("\n SUCCESS"); + else + printf("\n FAILURE"); + return; + } + else + printf("\n FAILURE"); + @endcode + */ + API int app2sd_post_app_uninstall(const char *appname); + + /** + * @brief : This API moves the package from sd card + to internal memory and vice versa. + * @param[in] pkgid application package id + * [Ex: com.samsung.calculator] + * @param[in] move_type Move type[enum app2ext_move_type] + * [sd card to internal/internal to sd card] + * @param[in] dir_list directory structure of the application + * @pre Package must be installed and its installation + * location should be known.Use app2sd_get_app_install_location() + * to get installation location. + * @see app2sd_get_app_install_location(). + * @post Package is moved to new location. + * @return 0 if success, error code(>0) if fail + * @remark None. + * + * + @code + #include + int ret = -1; + GList* dir_list = NULL; + // Package manager populates dir_list with directory structure information + ret = app2sd_get_app_install_location("com.samsung.calculator"); + if(ret == APP2SD_INTERNAL_MEM) + { + ret= app2sd_move_installed_app("com.samsung.calculator", + dir_list, APP2EXT_MOVE_TO_EXT); + if(!ret) + printf("\n SUCCESS"); + else + printf("\n FAILURE"); + } + else if(ret == APP2SD_EXTERNAL_MEM) + { + ret= app2sd_move_installed_app("com.samsung.calculator", + dir_list, APP2SD_MOVE_TO PHONE); + if(!ret) + printf("\n SUCCESS"); + else + printf("\n FAILURE"); + } + @endcode + */ + API int app2sd_move_installed_app(const char *pkgid, + GList* dir_list, app2ext_move_type move_type); + + /** + * @brief : This API Enables the application in sd card + for use. This API should be called by AUL. + * @param[in] pkgid application package id + * [Ex: com.samsung.calculator] + * @pre Package must be installed + * @post application is enabled in SD card. + * @return 0 if success, error code(>0) if fail + * @remark None. + * + * + @code + #include + int ret = -1; + ret= app2sd_on_demand_setup_init("com.samsung.calculator"); + if(!ret) + printf("\n SUCCESS"); + else + printf("\n FAILURE"); + } + @endcode + */ + API int app2sd_on_demand_setup_init(const char *pkgid); + + + /** + * @brief : This API Disables the application in sd card + . This API should be called by Launchpad callback which will be registered + during app launch for exit action of the application + * @param[in] pkgid application package id + * [Ex: com.samsung.calculator] + * @pre Package must be installed and enabled + * and application must be running in SD card + * @post application is disabked in SD card. + * @return 0 if success, error code(>0) if fail + * @remark None. + * + * + @code + #include + int ret = -1; + ret= app2sd_on_demand_setup_exit("com.samsung.calculator"); + if(!ret) + printf("\n SUCCESS"); + else + printf("\n FAILURE"); + } + @endcode + */ + API int app2sd_on_demand_setup_exit(const char *pkgid); + + /** + * @brief : This is the plug-in load function. + The plugin has to bind its functions to function pointers of storage handle + * @param[in/out] st_interface Specifies the storage interface. + * @return None + */ + API void app2ext_on_load(app2ext_interface *st_interface); + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/plugin/app2sd/inc/app2sd_internals.h b/plugin/app2sd/inc/app2sd_internals.h new file mode 100755 index 0000000..c81ca7c --- /dev/null +++ b/plugin/app2sd/inc/app2sd_internals.h @@ -0,0 +1,183 @@ +/* + * app2ext + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Garima Shrivastava + * Jyotsna Dhumale + * Venkatesha Sarpangala + * + * 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. + * + */ + +#ifndef _APP2SD_INTERNAL_H +#define _APP2SD_INTERNAL_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef _BSD_SOURCE +#define _BSD_SOURCE +#endif + +/*Include Headers*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DIR_PERMS (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) + +#define BUF_SIZE 256 +#define MEM_BUF_SIZE 5 /*Memory buffer size in MB*/ +#define PKG_BUF_SIZE 2 /*Memory buffer size in MB*/ + +/*Device entry defines*/ +#define DEV_MAJOR 7 + +#define FS_TYPE "ext4" + +typedef enum mount_type_t { + MOUNT_TYPE_RD = 0, + MOUNT_TYPE_RW, + MOUNT_TYPE_RW_NOEXEC, + MOUNT_TYPE_RD_REMOUNT, + MOUNT_TYPE_RW_REMOUNT +} mount_type; + +typedef enum app2sd_cmd_t { + APP2SD_PRE_INSTALL = 1, + APP2SD_POST_INSTALL, + APP2SD_PRE_UNINSTALL, + APP2SD_POST_UNINSTALL, + APP2SD_PRE_UPGRADE, + APP2SD_POST_UPGRADE, + APP2SD_APP_LAUNCH, + APP2SD_APP_TERMINATE, + APP2SD_MOVE_APP_TO_MMC, + APP2SD_MOVE_APP_TO_PHONE +} app2sd_cmd; + +/*This will store password in DB*/ +int _app2sd_set_passwod_in_db(const char *pkgid, const char *password); + +/*This will remove password from db*/ +int _app2sd_remove_password_from_db(const char *pkgid); + +/*This will fetch password from db*/ +char *_app2sd_get_passowrd_from_db(const char *pkgid); + +/*Checks whether mmc is present or not*/ +int _app2sd_check_mmc_status(void); + +/*this function is similar to system()*/ +int _xsystem(const char *argv[]); + +/*this function will return the free available memory on the SD Card*/ +int _app2sd_get_available_free_memory(const char *sd_path, int *free_mem); + +/*Function to move the application from/to SD Card*/ +int _app2sd_move_app(const char *pkgid, app2ext_move_type move_cmd, GList* dir_list); + +/*utility to delete the directory*/ +int _app2sd_delete_directory(char *dirname); + +/*utility to calculate the size of a directory in MB*/ +unsigned long long _app2sd_calculate_dir_size(char *dirname); + +/*utility to calculate the size of a file in MB*/ +unsigned long long _app2sd_calculate_file_size(const char *filename); + +/*Utility to copy a directory*/ +int _app2sd_copy_dir(const char *src, const char *dest); + +/*Utility to rename a directory*/ +int _app2sd_rename_dir(const char *old_name, const char *new_name); + +/*Utility to create application directory structure entry as per package type*/ +int _app2sd_create_directory_entry(const char *pkgid, GList* dir_list); + +/* Utility to create symlinks */ +int _app2sd_create_symlink(char *pkgid); + +/*This function finds the associated device node for the app*/ +char *_app2sd_find_associated_device_node(const char *pkgid); + +/*This function does the loopback encryption for app*/ +char *_app2sd_do_loopback_encryption_setup(const char *pkgid); + +/*This function detaches the loopback device*/ +char *_app2sd_detach_loop_device(const char *device); + +/*This function finds loopback device associated with the app*/ +char *_app2sd_find_associated_device(const char *mmc_app_path); + +/*This function creates loopback device*/ +int _app2sd_create_loopback_device(const char *pkgid, int size); + +/*This function deletes loopback device associated with the app*/ +int _app2sd_delete_loopback_device(const char *pkgid); + +/*This function creates ext4 FS on the device path*/ +int _app2sd_create_file_system(const char *device_path); + +/*This function mounts the app content on the device node*/ +int _app2sd_mount_app_content(const char *pkgid, const char *dev, + int mount_type, GList* dir_list, app2sd_cmd cmd); + +/*This function unmounts the app content */ +int _app2sd_unmount_app_content(const char *pkgid); + +/*This function removes the loopbck encryption setup for the app*/ +int _app2sd_remove_loopback_encryption_setup(const char *pkgid); + +/*This function updates loopback device size*/ +int _app2sd_update_loopback_device_size(const char *pkgid, + int size, GList* dir_list); + +/* This generates password */ +char *_app2sd_generate_password(const char *pkgid); + +/*This function encrypts device*/ +char *_app2sd_encrypt_device(const char *device, const char *pkgid, + char *passwd); + +/*This function finds free device*/ +char *_app2sd_find_free_device(void); + +/*This function initializes app2sd DB*/ +int _app2sd_initialize_db(); + +/*This function is used to get password from db*/ +char *_app2sd_get_password_from_db(const char *pkgid); + +/*This function removes password from db */ +int _app2sd_remove_password_from_db(const char *pkgid); + +/* This functions saved password in db */ +int _app2sd_set_password_in_db(const char *pkgid, + const char *passwd); + +#endif diff --git a/plugin/app2sd/src/app2sd_interface.c b/plugin/app2sd/src/app2sd_interface.c new file mode 100755 index 0000000..3e9fe76 --- /dev/null +++ b/plugin/app2sd/src/app2sd_interface.c @@ -0,0 +1,722 @@ +/* + * app2ext + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Garima Shrivastava + * Jyotsna Dhumale + * Venkatesha Sarpangala + * + * 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 + +int app2sd_pre_app_install(const char *pkgid, GList* dir_list, + int size) +{ + int ret = 0; + int free_mmc_mem = 0; + char *device_node = NULL; + char *devi = NULL; + char *result = NULL; + + /*Validate the function parameter recieved */ + if (pkgid == NULL || dir_list == NULL || size <= 0) { + app2ext_print("App2Sd Error : Invalid function arguments\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + /*Find available free memory in the MMC card */ + ret = _app2sd_get_available_free_memory(MMC_PATH, + &free_mmc_mem); + if (ret) { + app2ext_print("App2Sd Error : Unable to get available free memory in MMC %d\n", ret); + return APP2EXT_ERROR_MMC_STATUS; + } + /*If avaialalbe free memory in MMC is less than required size + 5MB , return error */ + if ((size + PKG_BUF_SIZE + MEM_BUF_SIZE) > free_mmc_mem) { + app2ext_print("Insufficient memory in MMC for application installation %d\n", ret); + return APP2EXT_ERROR_MMC_INSUFFICIENT_MEMORY; + } + /*Create a loopback device */ + ret = _app2sd_create_loopback_device(pkgid, (size+PKG_BUF_SIZE)); + if (ret) { + app2ext_print("App2Sd Error : Package already present\n"); + return ret; + } + /*Perform Loopback encryption setup */ + device_node = _app2sd_do_loopback_encryption_setup(pkgid); + if (!device_node) { + app2ext_print("App2Sd Error : Loopback encryption setup failed\n"); + _app2sd_delete_loopback_device(pkgid); + return APP2EXT_ERROR_DO_LOSETUP; + } + /*Check whether loopback device is associated with device node or not */ + devi = _app2sd_find_associated_device_node(pkgid); + if (devi == NULL) { + app2ext_print("App2Sd Error : finding associated device node failed\n"); + ret = APP2EXT_ERROR_DO_LOSETUP; + goto FINISH_OFF; + } + + /*Format the loopback file system */ + ret = _app2sd_create_file_system(device_node); + if (ret) { + app2ext_print("App2Sd Error : creating FS failed failed\n"); + ret = APP2EXT_ERROR_CREATE_FS; + goto FINISH_OFF; + } + + /*Mount the loopback encrypted pseudo device on application installation path as with Read Write permission */ + ret =_app2sd_mount_app_content(pkgid, device_node, MOUNT_TYPE_RW, + dir_list, APP2SD_PRE_INSTALL); + if (ret) { + app2ext_print("App2Sd Error : mounting dev path to app install path failed\n"); + ret = APP2EXT_ERROR_MOUNT_PATH; + goto FINISH_OFF; + } + + /*Success */ + ret = APP2EXT_SUCCESS; + goto END; + +FINISH_OFF: + + if (device_node) { + result = _app2sd_detach_loop_device(device_node); + if (result) { + free(result); + result = NULL; + } + _app2sd_delete_loopback_device(pkgid); + } +END: + if (device_node) { + free(device_node); + device_node = NULL; + } + if (devi) { + free(devi); + devi = NULL; + } + return ret; +} + +int app2sd_post_app_install(const char *pkgid, + app2ext_status install_status) +{ + char *device_name = NULL; + char buf_dir[FILENAME_MAX] = { 0, }; + int ret = APP2EXT_SUCCESS; + /*Validate the function parameter recieved */ + if (pkgid == NULL || install_status < APP2EXT_STATUS_FAILED + || install_status > APP2EXT_STATUS_SUCCESS) { + app2ext_print("Invalid func parameters\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + sync(); //2 + /*Get the associated device node for SD card applicationer */ + device_name = _app2sd_find_associated_device_node(pkgid); + if (NULL == device_name) { + return APP2EXT_ERROR_FIND_ASSOCIATED_DEVICE_NODE; + } + ret = _app2sd_unmount_app_content(pkgid); + if (ret) { + if (device_name) { + free(device_name); + device_name = NULL; + } + app2ext_print("Unable to unmount the app content %d\n", ret); + return APP2EXT_ERROR_UNMOUNT; + } + ret = _app2sd_remove_loopback_encryption_setup(pkgid); + if (ret) { + if (device_name) { + free(device_name); + device_name = NULL; + } + app2ext_print + ("Unable to Detach the loopback encryption setup for the application"); + return APP2EXT_ERROR_UNMOUNT; + } + if (device_name) { + free(device_name); + device_name = NULL; + } + + /*Take appropriate action based on installation + status of application package */ + if (install_status == APP2EXT_STATUS_FAILED) { + /*Delete the loopback device from the SD card */ + ret = _app2sd_delete_loopback_device(pkgid); + if (ret) { + app2ext_print + ("App2Sd Error : Unable to delete the loopback device from the SD Card\n"); + return APP2EXT_ERROR_DELETE_LOOPBACK_DEVICE; + } + ret = _app2sd_remove_password_from_db(pkgid); + + if (ret) { + app2ext_print + ("App2Sd Error : Unable to delete the password\n"); + } + + snprintf(buf_dir, FILENAME_MAX, "%s%s", APP_INSTALLATION_PATH, pkgid); + + ret = _app2sd_delete_directory(buf_dir); + + if (ret) { + app2ext_print + ("App2Sd Error : Unable to delete the directory %s\n", + buf_dir); + } + + } + return ret; +} + +int app2sd_on_demand_setup_init(const char *pkgid) +{ + int ret = APP2EXT_SUCCESS; + char app_path[FILENAME_MAX] = { 0, }; + char *device_node = NULL; + char *result = NULL; + FILE *fp = NULL; + + /*Validate the function parameter recieved */ + if (pkgid == NULL) { + app2ext_print + ("App2Sd Error : Invalid function arguments to app launch setup\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + + /*check app entry is there in sd card or not. */ + snprintf(app_path, FILENAME_MAX, "%s%s", APP2SD_PATH, + pkgid); + fp = fopen(app_path, "r+"); + if (fp == NULL) { + app2ext_print + ("App2SD Error: App Entry is not present in SD Card\n"); + return APP2EXT_ERROR_INVALID_PACKAGE; + } + fclose(fp); + result = (char *)_app2sd_find_associated_device(app_path); + /*process the string */ + if ((result!=NULL) && strstr(result, "/dev") != NULL) { + app2ext_print("App2SD Error! Already associated\n"); + free(result); + result = NULL; + return APP2EXT_ERROR_ALREADY_MOUNTED; + } + + /*Do loopback setup */ + device_node = _app2sd_do_loopback_encryption_setup(pkgid); + if (device_node == NULL) { + app2ext_print + ("App2Sd Error : loopback encryption setup failed\n"); + return APP2EXT_ERROR_DO_LOSETUP; + } + + /*Do mounting */ + ret = + _app2sd_mount_app_content(pkgid, device_node, MOUNT_TYPE_RD, + NULL, APP2SD_APP_LAUNCH); + if (ret) { + app2ext_print("App2Sd Error : Re-mount failed\n"); + if (device_node) { + free(device_node); + device_node = NULL; + } + return APP2EXT_ERROR_MOUNT_PATH; + } + if (device_node) { + free(device_node); + device_node = NULL; + } + return ret; +} + +int app2sd_on_demand_setup_exit(const char *pkgid) +{ + int ret = APP2EXT_SUCCESS; + char app_path[FILENAME_MAX] = { 0, }; + FILE *fp = NULL; + + /*Validate the function parameter recieved */ + if (pkgid == NULL) { + app2ext_print + ("App2Sd Error : Invalid function arguments to app launch setup\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + /*check app entry is there in sd card or not. */ + snprintf(app_path, FILENAME_MAX, "%s%s", APP2SD_PATH, + pkgid); + fp = fopen(app_path, "r+"); + if (fp == NULL) { + app2ext_print + ("App2SD Error: App Entry is not present in SD Card\n"); + return APP2EXT_ERROR_INVALID_PACKAGE; + } + fclose(fp); + ret = _app2sd_unmount_app_content(pkgid); + if (ret) { + app2ext_print + ("App2SD Error: Unable to unmount the SD application\n"); + return APP2EXT_ERROR_UNMOUNT; + } + ret = _app2sd_remove_loopback_encryption_setup(pkgid); + if (ret) { + app2ext_print("App2SD Error: Unable to remove loopback setup\n"); + return APP2EXT_ERROR_DELETE_LOOPBACK_DEVICE; + } + return ret; +} + +int app2sd_pre_app_uninstall(const char *pkgid) +{ + int ret = APP2EXT_SUCCESS; + char app_path[FILENAME_MAX] = { 0, }; + char *device_node = NULL; + FILE*fp = NULL; + + /*Validate the function parameter recieved */ + if (pkgid == NULL) { + app2ext_print + ("App2Sd Error : Invalid function arguments to app launch setup\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + /*check app entry is there in sd card or not. */ + snprintf(app_path, FILENAME_MAX, "%s%s", APP2SD_PATH, pkgid); + fp = fopen(app_path, "r+"); + if (fp == NULL) { + app2ext_print + ("App2SD Error: App Entry is not present in SD Card\n"); + return APP2EXT_ERROR_INVALID_PACKAGE; + } + fclose(fp); + + /*Get the associated device node for SD card applicationer */ + device_node = _app2sd_find_associated_device_node(pkgid); + if (NULL == device_node) { + /*Do loopback setup */ + device_node = _app2sd_do_loopback_encryption_setup(pkgid); + + if (device_node == NULL) { + app2ext_print + ("App2Sd Error : loopback encryption setup failed\n"); + return APP2EXT_ERROR_DO_LOSETUP; + } + /*Do mounting */ + ret = + _app2sd_mount_app_content(pkgid, device_node, + MOUNT_TYPE_RW, NULL, + APP2SD_PRE_UNINSTALL); + + if (ret) { + app2ext_print("App2Sd Error : RW-mount failed\n"); + if (device_node) { + free(device_node); + device_node = NULL; + } + return APP2EXT_ERROR_MOUNT_PATH; + } + } else { + /*Do re-mounting */ + ret = + _app2sd_mount_app_content(pkgid, device_node, + MOUNT_TYPE_RW_REMOUNT, NULL, + APP2SD_PRE_UNINSTALL); + + if (ret) { + app2ext_print("App2Sd Error : Re-mount failed\n"); + if (device_node) { + free(device_node); + device_node = NULL; + } + return APP2EXT_ERROR_MOUNT_PATH; + } + } + if (device_node) { + free(device_node); + device_node = NULL; + } + return ret; +} + +/* +* app2sd_post_app_uninstall_setup +* Uninstall Application and free all the allocated resources +* Called after dpkg remove, It deallocates dev node and loopback +*/ +int app2sd_post_app_uninstall(const char *pkgid) +{ + char buf_dir[FILENAME_MAX] = { 0, }; + int ret = APP2EXT_SUCCESS; + int ret1 = APP2EXT_SUCCESS; + /*Validate the function parameter recieved */ + if (pkgid == NULL) { + app2ext_print + ("App2Sd Error : Invalid function arguments to Post Uninstall\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + /*Unmount the loopback encrypted pseudo device from the application installation path */ + ret = _app2sd_unmount_app_content(pkgid); + if (ret) { + app2ext_print("Unable to unmount the app content %d\n", ret); + return APP2EXT_ERROR_UNMOUNT; + } + /*Detach the loopback encryption setup for the application */ + ret = _app2sd_remove_loopback_encryption_setup(pkgid); + if (ret) { + app2ext_print + ("Unable to Detach the loopback encryption setup for the application"); + return APP2EXT_ERROR_DETACH_LOOPBACK_DEVICE; + } + /*Delete the loopback device from the SD card */ + ret = _app2sd_delete_loopback_device(pkgid); + if (ret) { + app2ext_print + ("App2Sd Error : Unable to delete the loopback device from the SD Card\n"); + return APP2EXT_ERROR_DELETE_LOOPBACK_DEVICE; + } + memset((void *)&buf_dir, '\0', FILENAME_MAX); + snprintf(buf_dir, FILENAME_MAX, "%s%s", APP_INSTALLATION_PATH, pkgid); + ret1 = _app2sd_delete_directory(buf_dir); + if (ret1) { + app2ext_print + ("App2Sd Error : Unable to delete the directory %s\n", + buf_dir); + } + /*remove encryption password from DB */ + ret = _app2sd_initialize_db(); + if (ret) { + app2ext_print("\n app2sd db initialize failed"); + return APP2EXT_ERROR_SQLITE_REGISTRY; + } + ret = _app2sd_remove_password_from_db(pkgid); + if (ret) { + app2ext_print("cannot remove password from db \n"); + return APP2EXT_ERROR_SQLITE_REGISTRY; + } + return ret; +} + +int app2sd_move_installed_app(const char *pkgid, GList* dir_list, + app2ext_move_type move_type) +{ + int ret = 0; + + /*Validate function arguments*/ + if (pkgid == NULL || dir_list == NULL + || move_type < APP2EXT_MOVE_TO_EXT + || move_type > APP2EXT_MOVE_TO_PHONE) { + app2ext_print("App2Sd Error : Invalid function arguments\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + + ret = _app2sd_move_app(pkgid, move_type, dir_list); + if (ret) { + app2ext_print("App2Sd Error : Unable to move application\n"); + return ret; + } + return ret; +} + +int app2sd_pre_app_upgrade(const char *pkgid, GList* dir_list, + int size) +{ + int ret = APP2EXT_SUCCESS; + char app_path[FILENAME_MAX] = { 0, }; + char *device_node = NULL; + unsigned long long curr_size = 0; + FILE *fp = NULL; + + /*Validate function arguments*/ + if (pkgid == NULL || dir_list == NULL || size<=0) { + app2ext_print + ("App2Sd Error : Invalid function arguments \n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + /*check app entry is there in sd card or not. */ + snprintf(app_path, FILENAME_MAX, "%s%s", APP2SD_PATH, + pkgid); + app2ext_print("App2Sd Log : Checking path %s\n", app_path); + fp = fopen(app_path, "r+"); + if (fp == NULL) { + app2ext_print + ("App2SD Error: App Entry is not present in SD Card\n"); + return APP2EXT_ERROR_INVALID_PACKAGE; + } + fclose(fp); + /*Get installed app size*/ + curr_size = _app2sd_calculate_file_size(app_path); + curr_size = (curr_size/1024)/1024; + + if (curr_size<=0) { + app2ext_print + ("App2SD Error: App Entry is not present in SD Card\n"); + return APP2EXT_ERROR_LOOPBACK_DEVICE_UNAVAILABLE; + } + if (curr_size APP2EXT_STATUS_SUCCESS) { + app2ext_print("Invalid func parameters\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + + /*Get the associated device node for SD card applicationer */ + device_name = _app2sd_find_associated_device_node(pkgid); + if (NULL == device_name) { + return APP2EXT_ERROR_FIND_ASSOCIATED_DEVICE_NODE; + } + ret = _app2sd_unmount_app_content(pkgid); + if (ret) { + if (device_name) { + free(device_name); + device_name = NULL; + } + app2ext_print("Unable to unmount the app content %d\n", ret); + return APP2EXT_ERROR_UNMOUNT; + } + ret = _app2sd_remove_loopback_encryption_setup(pkgid); + if (ret) { + if (device_name) { + free(device_name); + device_name = NULL; + } + app2ext_print + ("Unable to Detach the loopback encryption setup for the application"); + return APP2EXT_ERROR_UNMOUNT; + } + if (device_name) { + free(device_name); + device_name = NULL; + } + return ret; +} + +/** + * Reserved API for forced cleanup + * + */ +int app2sd_force_cleanup(const char *pkgid){ + char *device_name = NULL; + char buf_dir[FILENAME_MAX] = { 0, }; + int ret = APP2EXT_SUCCESS; + FILE *fp = NULL; + + /*Validate the function parameter recieved */ + if (pkgid == NULL) { + app2ext_print("invalid func parameters\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + memset((void *)&buf_dir, '\0', FILENAME_MAX); + snprintf(buf_dir, FILENAME_MAX, "%s%s", APP2SD_PATH, pkgid); + fp = fopen(buf_dir, "r+"); + if (fp == NULL) { + app2ext_print("\"%s\" not installed on SD Card\n", pkgid); + return APP2EXT_ERROR_INVALID_PACKAGE; + } + fclose(fp); + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + + /*Get the associated device node for SD card applicationer */ + device_name = _app2sd_find_associated_device_node(pkgid); + if (NULL != device_name) { + free(device_name); + device_name = NULL; + ret = _app2sd_unmount_app_content(pkgid); + if (ret) { + app2ext_print("Unable to unmount the app content %d\n", ret); + return APP2EXT_ERROR_UNMOUNT; + } + ret = _app2sd_remove_loopback_encryption_setup(pkgid); + if (ret) { + app2ext_print + ("Unable to Detach the loopback encryption setup for the application"); + return APP2EXT_ERROR_UNMOUNT; + } + } + + memset((void *)&buf_dir, '\0', FILENAME_MAX); + snprintf(buf_dir, FILENAME_MAX, "%s%s", APP_INSTALLATION_PATH, pkgid); + ret = _app2sd_delete_directory(buf_dir); + if (ret) { + app2ext_print + ("App2Sd Error : Unable to delete the directory %s\n", + buf_dir); + } + + /*remove passwrd from DB*/ + ret = _app2sd_initialize_db(); + if (ret) { + app2ext_print("\n app2sd db initialize failed"); + return APP2EXT_ERROR_SQLITE_REGISTRY; + } + ret = _app2sd_remove_password_from_db(pkgid); + if (ret) { + app2ext_print("cannot remove password from db \n"); + return APP2EXT_ERROR_SQLITE_REGISTRY; + } + return ret; +} + +/* This is the plug-in load function. The plugin has to bind its functions to function pointers of handle + @param[in/out] st_interface Specifies the storage interface. +*/ +void +app2ext_on_load(app2ext_interface *st_interface) +{ + /*Plug-in Binding.*/ + st_interface->pre_install= app2sd_pre_app_install; + st_interface->post_install= app2sd_post_app_install; + st_interface->pre_uninstall= app2sd_pre_app_uninstall; + st_interface->post_uninstall= app2sd_post_app_uninstall; + st_interface->pre_upgrade= app2sd_pre_app_upgrade; + st_interface->post_upgrade= app2sd_post_app_upgrade; + st_interface->move= app2sd_move_installed_app; + st_interface->enable= app2sd_on_demand_setup_init; + st_interface->disable= app2sd_on_demand_setup_exit; +} + diff --git a/plugin/app2sd/src/app2sd_internals.c b/plugin/app2sd/src/app2sd_internals.c new file mode 100755 index 0000000..e3942ea --- /dev/null +++ b/plugin/app2sd/src/app2sd_internals.c @@ -0,0 +1,1371 @@ +/* + * app2ext + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Garima Shrivastava + * Jyotsna Dhumale + * Venkatesha Sarpangala + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* +########### Internal APIs ################## + */ + +char *_app2sd_find_associated_device_node(const char *pkgid) +{ + char *ret_result = NULL; + char delims[] = ":"; + char *result = NULL; + char app_path[FILENAME_MAX] = { '\0' }; + + snprintf(app_path, FILENAME_MAX, "%s%s", APP2SD_PATH, + pkgid); + result = (char *)_app2sd_find_associated_device(app_path); + if (result == NULL) { + app2ext_print + ("App2SD Error! Unable to find the associated File\n"); + return NULL; + } + /*process the string*/ + if (strstr(result, "/dev") == NULL) { + app2ext_print + ("App2SD Error! Unable to find the associated File\n"); + return NULL; + } else { + ret_result = strtok(result, delims); + } + + return ret_result; + +} + +char *_app2sd_create_loopdevice_node(void) +{ + char *ret_result = NULL; + mode_t mode = DIR_PERMS; + int count = 0; + int ret = APP2EXT_SUCCESS; + char *result = NULL; + FILE *fp = NULL; + + result = (char *)_app2sd_find_free_device(); + /*validate the result */ + if (result == NULL || strstr(result, "/dev") == NULL) { + app2ext_print("No device found, creating device node...\n"); + + if (result) { + free(result); + result = NULL; + } + count = 0; + char dev_path[BUF_SIZE] = { 0, }; + snprintf(dev_path, BUF_SIZE, "/dev/loop%d", count); + while ((fp = fopen(dev_path, "r+")) != NULL) { + count = count + 1; + snprintf(dev_path, BUF_SIZE, "/dev/loop%d", count); + app2ext_print("next dev path for checking is %s\n", + dev_path); + fclose(fp); + } + app2ext_print("Device node candidate is %s \n", dev_path); + dev_t dev_node; + dev_node = makedev(DEV_MAJOR, count); + if ((ret = mknod(dev_path, S_IFBLK | mode, dev_node)) < 0) { + app2ext_print + ("Error while creating the device node: errno is %d\n", + errno); + return NULL; + } + ret_result = (char *)malloc(strlen(dev_path) + 1); + if (ret_result == NULL) { + app2ext_print("Unable to allocate memory\n"); + return NULL; + } + memset(ret_result, '\0', strlen(dev_path) + 1); + memcpy(ret_result, dev_path, strlen(dev_path)); + } else { + ret_result = (char *)malloc(strlen(result) + 1); + if (ret_result == NULL) { + app2ext_print("Malloc failed!\n"); + free(result); + result = NULL; + return NULL; + } + memset(ret_result, '\0', strlen(result) + 1); + if (strlen(result) > 0) { + memcpy(ret_result, result, strlen(result) - 1); + } + free(result); + result = NULL; + + } + return ret_result; +} + +char *_app2sd_do_loopback_encryption_setup(const char *pkgid) +{ + int ret = APP2EXT_SUCCESS; + char *passwd = NULL; + char app_path[FILENAME_MAX] = { '\0' }; + char *result = NULL; + char *device_node = NULL; + if (pkgid == NULL) { + app2ext_print("App2Sd Error: Invalid argument\n"); + return NULL; + } + + snprintf(app_path, FILENAME_MAX, "%s%s", APP2SD_PATH, + pkgid); + /* Get password for loopback encryption */ + ret = _app2sd_initialize_db(); + if (ret) { + app2ext_print("\n app2sd db initialize failed"); + return NULL; + } + if ((passwd = _app2sd_get_password_from_db(pkgid)) == NULL) { + passwd = (char *)_app2sd_generate_password(pkgid); + if (NULL == passwd) { + app2ext_print + ("App2Sd Error: Unable to generate password\n"); + return NULL; + } else { + app2ext_print("Password is %s\n", passwd); + if ((ret = _app2sd_set_password_in_db(pkgid, + passwd)) < 0) { + app2ext_print + ("App2Sd Error: Unable to save password\n"); + free(passwd); + passwd = NULL; + return NULL; + } + } + } + + /*Get Free device node*/ + device_node = _app2sd_create_loopdevice_node(); + if (NULL == device_node) { + free(passwd); + passwd = NULL; + app2ext_print + ("App2Sd Error: Unable to find free loopback node\n"); + return NULL; + } + result = (char *)_app2sd_encrypt_device(device_node, app_path, passwd); + if (result == NULL) { + app2ext_print("App2Sd Error: Encryption failed!\n\n"); + free(passwd); + passwd = NULL; + return NULL; + } else { + free(result); + result = NULL; + free(passwd); + passwd = NULL; + return device_node; + } +} + +char *_app2sd_do_loopback_duplicate_encryption_setup(const char *pkgid, const char * dup_appname) +{ + int ret = APP2EXT_SUCCESS; + char *passwd = NULL; + char app_path[FILENAME_MAX] = { '\0' }; + char *result = NULL; + char *device_node = NULL; + if (pkgid == NULL || dup_appname == NULL) { + app2ext_print("App2Sd Error: Invalid argument\n"); + return NULL; + } + + snprintf(app_path, FILENAME_MAX, "%s%s", APP2SD_PATH, + dup_appname); + /* Get password for loopback encryption */ + ret = _app2sd_initialize_db(); + if (ret) { + app2ext_print("\n app2sd db initialize failed"); + return NULL; + } + if ((passwd = _app2sd_get_password_from_db(pkgid)) == NULL) { + passwd = (char *)_app2sd_generate_password(pkgid); + if (NULL == passwd) { + app2ext_print + ("App2Sd Error: Unable to generate password\n"); + return NULL; + } else { + app2ext_print("Password is %s\n", passwd); + if ((ret = _app2sd_set_password_in_db(pkgid, + passwd)) < 0) { + app2ext_print + ("App2Sd Error: Unable to save password\n"); + free(passwd); + passwd = NULL; + return NULL; + } + } + + } + /*Get Free device node*/ + device_node = _app2sd_create_loopdevice_node(); + if (NULL == device_node) { + free(passwd); + passwd = NULL; + app2ext_print + ("App2Sd Error: Unable to find free loopback node\n"); + return NULL; + } + result = (char *)_app2sd_encrypt_device(device_node, app_path, passwd); + if (result == NULL) { + app2ext_print("App2Sd Error: Encryption failed!\n\n"); + free(passwd); + passwd = NULL; + return NULL; + } else { + if (strlen(result) == 0) { + free(result); + result = NULL; + free(passwd); + passwd = NULL; + return device_node; + } else { + app2ext_print("App2Sd Error: Error is %s\n", result); + free(result); + result = NULL; + free(passwd); + passwd = NULL; + return NULL; + } + } + return device_node; +} + +int _app2sd_remove_loopback_encryption_setup(const char *pkgid) +{ + int ret = APP2EXT_SUCCESS; + char *result = NULL; + char *dev_node = NULL; + if ((dev_node = _app2sd_find_associated_device_node(pkgid)) == NULL) { + app2ext_print("Unable to find the association\n"); + ret = APP2EXT_ERROR_FIND_ASSOCIATED_DEVICE_NODE; + } + result = (char *)_app2sd_detach_loop_device(dev_node); + if (result == NULL) { + app2ext_print("App2sd Error: Error in detaching\n"); + ret = APP2EXT_ERROR_DETACH_LOOPBACK_DEVICE; + } else { + free(result); + result = NULL; + } + if (dev_node) { + free(dev_node); + dev_node = NULL; + } + return ret; +} + +int _app2sd_create_loopback_device(const char *pkgid, int size) +{ + int ret = APP2EXT_SUCCESS; + char command[FILENAME_MAX] = { 0, }; + mode_t mode = DIR_PERMS; + char external_storage_path[FILENAME_MAX] = { 0, }; + char buff[BUF_SIZE] = { 0, }; + char app_path[FILENAME_MAX] = { 0, }; + FILE *fp = NULL; + + if (NULL == pkgid || size <= 0) { + app2ext_print("App2Sd Error: Invalid argument\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + snprintf(command, FILENAME_MAX, "of=%s%s", APP2SD_PATH, + pkgid); + snprintf(buff, BUF_SIZE, "count=%d", size); + const char *argv1[] = + { "dd", "if=/dev/zero", command, "bs=1M", buff, NULL }; + snprintf(external_storage_path, FILENAME_MAX, "%s", + APP2SD_PATH); + ret = mkdir(external_storage_path, mode); + if (ret) { + if (errno != EEXIST) { + app2ext_print + ("App2sd Error : Create directory failed, error no is %d\n", + errno); + return APP2EXT_ERROR_CREATE_DIRECTORY; + } + } + snprintf(app_path, FILENAME_MAX, "%s%s", APP2SD_PATH, + pkgid); + if ((fp = fopen(app_path, "r+")) != NULL) { + app2ext_print("Application already exists %s\n", app_path); + fclose(fp); + return APP2EXT_ERROR_PKG_EXISTS; + } + + ret = _xsystem(argv1); + if (ret) { + app2ext_print("App2Sd Error : command \"%s\" failed \n", + command); + return ret; + } + return ret; +} + +int _app2sd_delete_loopback_device(const char *pkgid) +{ + int ret = APP2EXT_SUCCESS; + char loopback_device[FILENAME_MAX] = { 0, }; + + snprintf(loopback_device, FILENAME_MAX, "%s%s", APP2SD_PATH, + pkgid); + + ret = unlink(loopback_device); + if (ret) { + if (errno == ENOENT) { + app2ext_print("Unable to access file %s\n", loopback_device); + } else { + app2ext_print("Unable to delete %s\n", loopback_device); + return APP2EXT_ERROR_DELETE_LOOPBACK_DEVICE; + } + } + return ret; +} + +int _app2sd_create_file_system(const char *device_path) +{ + int ret = APP2EXT_SUCCESS; + FILE *fp = NULL; + if (NULL == device_path) { + app2ext_print("App2Sd Error: invalid param [NULL]\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + + /*Format the filesystem [create a filesystem]*/ + const char *argv[] = { "mkfs.ext4", device_path, NULL }; + fp = fopen(device_path, "r+"); + if (fp == NULL) { + app2ext_print + ("App2sd Error: Unable to access %s [System errono is %d.....%s]\n", + device_path, errno, strerror(errno)); + return APP2EXT_ERROR_ACCESS_FILE; + } else { + fclose(fp); + } + ret = _xsystem(argv); + if (ret) { + app2ext_print + ("App2Sd Error : creating file system failed [System error is %s\n", + strerror(errno)); + return APP2EXT_ERROR_CREATE_FS; + } + return ret; +} + +static int _app2sd_create_dir_with_link(const char *pkgid, + const char *dir_name) +{ + mode_t mode = DIR_PERMS; + int ret = APP2EXT_SUCCESS; + char app_dir_mmc_path[FILENAME_MAX] = { 0, }; + char app_dir_path[FILENAME_MAX] = { 0, }; + snprintf(app_dir_mmc_path, FILENAME_MAX, "%s%s/.mmc/%s",APP_INSTALLATION_PATH, + pkgid, dir_name); + snprintf(app_dir_path, FILENAME_MAX, "%s%s/%s", APP_INSTALLATION_PATH, pkgid, + dir_name); + + ret = mkdir(app_dir_mmc_path, mode); + if (ret) { + if (errno != EEXIST) { + app2ext_print + ("App2sd Error : Create directory failed, error no is %d\n", + errno); + return APP2EXT_ERROR_CREATE_DIRECTORY; + } + } + + if ((ret = symlink(app_dir_mmc_path, app_dir_path)) < 0) { + if (errno == EEXIST) { + app2ext_print + ("App2sd : File with Symlink name present %s\n", + app_dir_path); + } else { + app2ext_print + ("A2Sd Error : Symbolic link creation failed, error no is %d\n", + errno); + return APP2EXT_ERROR_CREATE_SYMLINK; + } + } + + return ret; +} + +int _app2sd_create_directory_entry(const char *pkgid, GList* dir_list) +{ + int ret = APP2EXT_SUCCESS; + char app_dir_path[FILENAME_MAX] = { 0, }; + GList *list = NULL; + app2ext_dir_details* dir_detail = NULL; + + snprintf(app_dir_path, FILENAME_MAX, "%s%s", APP_INSTALLATION_PATH, + pkgid); + + list = g_list_first(dir_list); + while (list) { + dir_detail = (app2ext_dir_details *)list->data; + if (dir_detail && dir_detail->name + && dir_detail->type == APP2EXT_DIR_RO) { + ret = _app2sd_create_dir_with_link(pkgid, dir_detail->name); + if (ret) { + return ret; + } + } + list = g_list_next(list); + } + return APP2EXT_SUCCESS; +} + + +/* + * + * _app2sd_mount_app_content + This function is to create the path for mmc and mount the content +Example usage: _app2sd_mount_app_content("deb.com.samsung.helloworld","/dev/loop0",MOUNT_TYPE_RD) +*/ +int _app2sd_mount_app_content(const char *pkgid, const char *dev, + int mount_type, GList* dir_list, app2sd_cmd cmd) +{ + int ret = APP2EXT_SUCCESS; + mode_t mode = DIR_PERMS; + char app_dir_path[FILENAME_MAX] = { 0, }; + char app_dir_mmc_path[FILENAME_MAX] = { 0, }; + if (NULL == pkgid || NULL == dev) { + app2ext_print("App2Sd Error : Input param is NULL %s %s \n", + pkgid, dev); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + snprintf(app_dir_path, FILENAME_MAX, "%s%s", APP_INSTALLATION_PATH, pkgid); + ret = mkdir(app_dir_path, mode); + if (ret) { + if (errno != EEXIST) { + app2ext_print + ("App2Sd Error : Create directory failed, error no is %d\n", + errno); + return APP2EXT_ERROR_CREATE_DIRECTORY; + } + } + snprintf(app_dir_mmc_path, FILENAME_MAX, "%s%s/.mmc", APP_INSTALLATION_PATH, pkgid); + ret = mkdir(app_dir_mmc_path, mode); + if (ret) { + if (errno != EEXIST) { + app2ext_print + ("App2Sd Error : Create directory failed, error no is %d\n", + errno); + return APP2EXT_ERROR_CREATE_DIRECTORY; + } + } + + switch (mount_type) { + case MOUNT_TYPE_RD: + { + if ((ret = + mount(dev, app_dir_mmc_path, FS_TYPE, + MS_MGC_VAL | MS_RDONLY, NULL)) < 0) { + app2ext_print + ("App2Sd Error : Read Only Mount failed [System Erro no is %d], dev is %s path is %s\n", + errno, dev, app_dir_mmc_path); + ret = APP2EXT_ERROR_MOUNT; + } + break; + } + case MOUNT_TYPE_RW: + { + if ((ret = + mount(dev, app_dir_mmc_path, FS_TYPE, MS_MGC_VAL, + NULL)) < 0) { + app2ext_print + ("App2Sd Error : Read Write Mount failed [System Erro no is %d]\n", + errno); + ret = APP2EXT_ERROR_MOUNT; + } + break; + } + case MOUNT_TYPE_RW_NOEXEC: + { + if ((ret = + mount(dev, app_dir_mmc_path, FS_TYPE, + MS_MGC_VAL | MS_NOEXEC, NULL)) < 0) { + app2ext_print + ("App2Sd Error : RWX Mount failed [System Erro no is %d]\n", + errno); + ret = APP2EXT_ERROR_MOUNT; + } + break; + } + case MOUNT_TYPE_RD_REMOUNT: + { + if ((ret = + mount(dev, app_dir_mmc_path, FS_TYPE, + MS_MGC_VAL | MS_RDONLY | MS_REMOUNT, + NULL)) < 0) { + app2ext_print + ("App2Sd Error : RWX Mount failed [System Erro no is %d]\n", + errno); + ret = APP2EXT_ERROR_MOUNT; + } + break; + } + case MOUNT_TYPE_RW_REMOUNT: + { + if ((ret = + mount(dev, app_dir_mmc_path, FS_TYPE, + MS_MGC_VAL | MS_REMOUNT, NULL)) < 0) { + app2ext_print + ("App2Sd Error : RWX Mount failed [System Erro no is %d]\n", + errno); + ret = APP2EXT_ERROR_MOUNT; + } + break; + } + + default: + { + app2ext_print("App2Sd Error: Invalid mount type\n"); + break; + } + } + if (cmd == APP2SD_PRE_INSTALL || cmd == APP2SD_MOVE_APP_TO_MMC) { + ret = _app2sd_create_directory_entry(pkgid, dir_list); + } + return ret; +} + +int _app2sd_unmount_app_content(const char *pkgid) +{ + int ret = APP2EXT_SUCCESS; + char app_dir_mmc_path[FILENAME_MAX] = { 0, }; + snprintf(app_dir_mmc_path, FILENAME_MAX, "%s%s/.mmc", APP_INSTALLATION_PATH, pkgid); + if ((ret = umount(app_dir_mmc_path)) < 0) { + app2ext_print("Unable to umount the dir %s\n", strerror(errno)); + } + return ret; +} + +static int _app2sd_move_to_archive(const char *src_path, const char *arch_path) +{ + int ret = APP2EXT_SUCCESS; + + ret = _app2sd_copy_dir(src_path, arch_path); + if (ret) { + if (ret != APP2EXT_ERROR_ACCESS_FILE) { + app2ext_print + ("App2Sd Error : unable to copy from %s to %s .....err is %s\n", + src_path, arch_path, strerror(errno)); + return APP2EXT_ERROR_MOVE; + } + } + ret = _app2sd_delete_directory((char *)src_path); + if (ret) { + if (ret != APP2EXT_ERROR_ACCESS_FILE) { + app2ext_print("App2Sd Error : unable to delete %s \n", src_path); + return APP2EXT_ERROR_DELETE_DIRECTORY; + } + } + return ret; +} + +int _app2sd_move_app_to_external(const char *pkgid, GList* dir_list) +{ + int ret = APP2EXT_SUCCESS; + char app_path[FILENAME_MAX] = { 0, }; + char path[FILENAME_MAX] = { 0, }; + char app_mmc_path[FILENAME_MAX] = { 0, }; + char app_archive_path[FILENAME_MAX] = { 0, }; + char mmc_path[FILENAME_MAX] = { 0, }; + unsigned long long total_size = 0; + int reqd_size = 0; + char *device_node = NULL; + char *devi = NULL; + mode_t mode = DIR_PERMS; + int free_mmc_mem = 0; + FILE *fp = NULL; + GList *list = NULL; + app2ext_dir_details* dir_detail = NULL; + + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print + ("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + + snprintf(mmc_path, FILENAME_MAX, + "%s%s", APP2SD_PATH, pkgid); + + /*check whether application is in external memory or not */ + fp = fopen(mmc_path, "r+"); + if (fp != NULL) { + app2ext_print + ("Already %s entry is present in the SD Card\n", + pkgid); + fclose(fp); + return APP2EXT_ERROR_ALREADY_FILE_PRESENT; + } + + snprintf(app_mmc_path, FILENAME_MAX, + "%s%s/.mmc", APP_INSTALLATION_PATH, pkgid); + snprintf(app_archive_path, FILENAME_MAX, + "%s%s/.archive", APP_INSTALLATION_PATH, pkgid); + + ret = mkdir(app_archive_path, mode); + if (ret) { + if (errno != EEXIST) { + app2ext_print + ("App2sd Error: Unable to create directory for archiving, error no is %d\n", + errno); + return APP2EXT_ERROR_CREATE_DIRECTORY; + } + } + + list = g_list_first(dir_list); + while (list) { + dir_detail = (app2ext_dir_details *)list->data; + if (dir_detail && dir_detail->name + && dir_detail->type == APP2EXT_DIR_RO) { + memset((void *)&app_path, '\0', + FILENAME_MAX); + snprintf(app_path, FILENAME_MAX, + "%s%s/%s",APP_INSTALLATION_PATH, + pkgid, + dir_detail->name); + total_size += + _app2sd_calculate_dir_size + (app_path); + } + list = g_list_next(list); + } + + reqd_size = ((total_size / 1024) / 1024) + 2; + + /*Find avialable free memory in the MMC card */ + ret = + _app2sd_get_available_free_memory + (MMC_PATH, &free_mmc_mem); + if (ret) { + app2ext_print + ("App2Sd Error : Unable to get available free memory in MMC %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + /*If avaialalbe free memory in MMC is less than required size + 5MB , return error */ + if (reqd_size > free_mmc_mem) { + app2ext_print + ("App2Sd Error : Insufficient memory in MMC for application installation %d\n", + ret); + return APP2EXT_ERROR_MMC_INSUFFICIENT_MEMORY; + } + /*Create a loopback device */ + ret = + _app2sd_create_loopback_device(pkgid, (reqd_size+PKG_BUF_SIZE)); + if (ret) { + app2ext_print + ("App2Sd Error : loopback node creation failed\n"); + return APP2EXT_ERROR_CREATE_DEVICE; + } + /*Perform Loopback encryption setup */ + device_node = + _app2sd_do_loopback_encryption_setup(pkgid); + if (!device_node) { + app2ext_print + ("App2Sd Error : losetup failed, device node is %s\n", + device_node); + return APP2EXT_ERROR_DO_LOSETUP; + } + /*Check whether loopback device is associated with device node or not */ + devi = _app2sd_find_associated_device_node(pkgid); + if (devi == NULL) { + app2ext_print + ("App2Sd Error : _app2sd_find_associated_device_node losetup failed\n"); + return APP2EXT_ERROR_DO_LOSETUP; + } else { + free(devi); + devi = NULL; + } + /*Format the loopback file system */ + ret = _app2sd_create_file_system(device_node); + if (ret) { + app2ext_print + ("App2Sd Error : create ext4 filesystem failed\n"); + return APP2EXT_ERROR_CREATE_FS; + } + /********Archiving code begin***********/ + list = g_list_first(dir_list); + while (list) { + dir_detail = (app2ext_dir_details *)list->data; + if (dir_detail && dir_detail->name + && dir_detail->type == APP2EXT_DIR_RO) { + snprintf(path, FILENAME_MAX, + "%s%s/%s",APP_INSTALLATION_PATH, + pkgid, + dir_detail->name); + ret = + _app2sd_move_to_archive + (path, + app_archive_path); + if (ret) { + if (ret == APP2EXT_ERROR_ACCESS_FILE) { + app2ext_print + ("App2Sd Error : unable to access %s\n", + path); + } else { + app2ext_print + ("App2Sd Error : unable to copy from %s to %s \n", + path, + app_archive_path); + return + APP2EXT_ERROR_MOVE; + } + } + } + list = g_list_next(list); + } + /********Archiving code ends***********/ + + /*mount the loopback encrypted pseudo device on application installation path as with Read Write permission */ + ret = + _app2sd_mount_app_content(pkgid, device_node, + MOUNT_TYPE_RW, dir_list, + APP2SD_MOVE_APP_TO_MMC); + if (ret) { + return ret; + } + /********restore Archive begin***********/ + list = g_list_first(dir_list); + while (list) { + dir_detail = (app2ext_dir_details *)list->data; + if (dir_detail && dir_detail->name + && dir_detail->type == APP2EXT_DIR_RO) { + memset((void *)&path, '\0', + FILENAME_MAX); + snprintf(path, FILENAME_MAX, + "%s%s/.archive/%s",APP_INSTALLATION_PATH, + pkgid, + dir_detail->name); + ret = + _app2sd_copy_dir + (path, + app_mmc_path); + if (ret) { + if (ret == APP2EXT_ERROR_ACCESS_FILE) { + app2ext_print + ("App2Sd Error : unable to access %s\n", + path); + } else { + app2ext_print + ("App2Sd Error : unable to copy from %s to %s .....err is %s\n", + path, + app_mmc_path, + strerror + (errno)); + return + APP2EXT_ERROR_MOVE; + } + } + ret = + _app2sd_delete_directory + (path); + if (ret) { + if (ret == APP2EXT_ERROR_ACCESS_FILE) { + app2ext_print + ("App2Sd Error : unable to access %s\n", + path); + } else { + app2ext_print + ("App2Sd Error : unable to delete %s \n", + path); + return + APP2EXT_ERROR_DELETE_DIRECTORY; + } + } + } + list = g_list_next(list); + } + + ret = _app2sd_delete_directory(app_archive_path); + if (ret) { + app2ext_print + ("App2Sd Error : unable to delete %s \n", + app_archive_path); + return APP2EXT_ERROR_DELETE_DIRECTORY; + } + /*Restore archive ends */ + /*Re-mount the loopback encrypted pseudo device on application installation path as with Read Only permission */ + ret = _app2sd_unmount_app_content(pkgid); + if (ret) { + return APP2EXT_ERROR_REMOUNT; + } + ret = + _app2sd_remove_loopback_encryption_setup(pkgid); + if (ret) { + app2ext_print + ("App2Sd Error : unable to detach loopback setup for %s\n", + pkgid); + return APP2EXT_ERROR_DETACH_LOOPBACK_DEVICE; + } + return APP2EXT_SUCCESS; +} + +int _app2sd_move_app_to_internal(const char *pkgid, GList* dir_list) +{ + int ret = APP2EXT_SUCCESS; + mode_t mode = DIR_PERMS; + char path[FILENAME_MAX] = { 0, }; + char app_mmc_path[FILENAME_MAX] = { 0, }; + char app_path[FILENAME_MAX] = { 0, }; + char mmc_path[FILENAME_MAX] = { 0, }; + char app_archive_path[FILENAME_MAX] = { 0, }; + char *device_node = NULL; + FILE *fp = NULL; + GList *list = NULL; + app2ext_dir_details* dir_detail = NULL; + + snprintf(app_mmc_path, FILENAME_MAX, + "%s%s/.mmc", APP_INSTALLATION_PATH, pkgid); + snprintf(app_path, FILENAME_MAX, "%s%s/", APP_INSTALLATION_PATH, + pkgid); + snprintf(app_archive_path, FILENAME_MAX, + "%s%s/.archive", APP_INSTALLATION_PATH, pkgid); + snprintf(mmc_path, FILENAME_MAX, + "%s%s", APP2SD_PATH, pkgid); + + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print + ("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + + /*check whether application is in external memory or not */ + fp = fopen(mmc_path, "r+"); + if (fp == NULL) { + app2ext_print + ("Application %s is not installed on SD Card\n", + pkgid); + return APP2EXT_ERROR_FILE_ABSENT; + } else { + fclose(fp); + fp = NULL; + } + + /*Get the associated device node for SD card applicationer */ + device_node = + _app2sd_find_associated_device_node(pkgid); + if (NULL == device_node) { + /*Do loopback setup */ + device_node = + _app2sd_do_loopback_encryption_setup + (pkgid); + if (device_node == NULL) { + app2ext_print + ("App2Sd Error : loopback encryption setup failed\n"); + return APP2EXT_ERROR_DO_LOSETUP; + } + /*Do mounting */ + ret = + _app2sd_mount_app_content(pkgid, + device_node, + MOUNT_TYPE_RW, + dir_list, + APP2SD_MOVE_APP_TO_PHONE); + if (ret) { + app2ext_print + ("App2Sd Error : Re-mount failed\n"); + return APP2EXT_ERROR_MOUNT_PATH; + } + } else { + /*Do re-mounting */ + ret = + _app2sd_mount_app_content(pkgid, + device_node, + MOUNT_TYPE_RW_REMOUNT, + dir_list, + APP2SD_MOVE_APP_TO_PHONE); + if (ret) { + app2ext_print + ("App2Sd Error : Re-mount failed\n"); + return APP2EXT_ERROR_MOUNT_PATH; + } + } + ret = mkdir(app_archive_path, mode); + if (ret) { + app2ext_print + ("App2Sd Error : unable to create directory%s\n", + app_archive_path); + return APP2EXT_ERROR_CREATE_DIRECTORY; + } + + + list = g_list_first(dir_list); + while (list) { + dir_detail = (app2ext_dir_details *)list->data; + if (dir_detail && dir_detail->name + && dir_detail->type == APP2EXT_DIR_RO) { + /*Archiving code */ + memset((void *)&path, '\0', + FILENAME_MAX); + snprintf(path, FILENAME_MAX, + "%s%s/.mmc/%s", APP_INSTALLATION_PATH, + pkgid, + dir_detail->name); + ret = + _app2sd_copy_dir + (path, + app_archive_path); + if (ret) { + if (ret == APP2EXT_ERROR_ACCESS_FILE) { + app2ext_print + ("App2Sd Error : unable to access %s\n", + path); + } else { + app2ext_print + ("App2Sd Error : unable to copy from %s to %s .....err is %s\n", + path, + app_archive_path, + strerror + (errno)); + return + APP2EXT_ERROR_MOVE; + } + } + + /*Delete the symbolic link files [bin, lib, res]*/ + memset((void *)&path, '\0', + FILENAME_MAX); + snprintf(path, FILENAME_MAX, + "%s%s/%s", APP_INSTALLATION_PATH, + pkgid, + dir_detail->name); + ret = unlink(path); + if (ret) { + if (errno == ENOENT) { + app2ext_print + ("App2Sd Error : Directory %s does not exist\n", + path); + } else { + app2ext_print + ("App2Sd Error : unable to remove the symbolic link file %s\n", + path); + return + APP2EXT_ERROR_DELETE_LINK_FILE; + } + } + + /*Copy content to destination */ + memset((void *)&path, '\0', + FILENAME_MAX); + snprintf(path, FILENAME_MAX, + "%s%s/.archive/%s", APP_INSTALLATION_PATH, + pkgid, + dir_detail->name); + ret = + _app2sd_copy_dir + (path, app_path); + if (ret) { + if (ret == APP2EXT_ERROR_ACCESS_FILE) { + app2ext_print + ("App2Sd Error : unable to access %s\n", + path); + } else { + app2ext_print + ("App2Sd Error : unable to copy from %s to %s .....err is %s\n", + path, + app_path, + strerror + (errno)); + return + APP2EXT_ERROR_MOVE; + } + } + } + list = g_list_next(list); + } + + ret = _app2sd_unmount_app_content(pkgid); + if (ret) { + app2ext_print + ("App2Sd Error : unable to unmount SD directory for app %s\n", + pkgid); + return APP2EXT_ERROR_UNMOUNT; + } + ret = + _app2sd_remove_loopback_encryption_setup(pkgid); + if (ret) { + app2ext_print + ("App2Sd Error : unable to detach loopback setup for %s\n", + pkgid); + return APP2EXT_ERROR_DETACH_LOOPBACK_DEVICE; + } + ret = _app2sd_delete_loopback_device(pkgid); + if (ret) { + app2ext_print + ("App2Sd Error : unable to delete the loopback device for %s\n", + pkgid); + return APP2EXT_ERROR_DELETE_LOOPBACK_DEVICE; + } + ret = _app2sd_delete_directory(app_mmc_path); + if (ret) { + app2ext_print + ("App2Sd Error : unable to delete %s \n", + app_mmc_path); + return APP2EXT_ERROR_DELETE_DIRECTORY; + } + ret = _app2sd_delete_directory(app_archive_path); + if (ret) { + app2ext_print + ("App2Sd Error : unable to delete %s \n", + app_archive_path); + return APP2EXT_ERROR_DELETE_DIRECTORY; + } + return APP2EXT_SUCCESS; +} + +int _app2sd_move_app(const char *pkgid, app2ext_move_type move_cmd, GList* dir_list) +{ + int ret = APP2EXT_SUCCESS; + + /*Check whether MMC is present or not */ + ret = _app2sd_check_mmc_status(); + if (ret) { + app2ext_print + ("App2Sd Error : MMC not preset OR Not ready %d\n", + ret); + return APP2EXT_ERROR_MMC_STATUS; + } + + switch (move_cmd) { + case APP2EXT_MOVE_TO_EXT: + { + ret = _app2sd_move_app_to_external(pkgid, dir_list); + if (ret) { + app2ext_print + ("App2Sd Error : move app to external memory failed %d\n", + ret); + return ret; + } + break; + } + case APP2EXT_MOVE_TO_PHONE: + { + ret = _app2sd_move_app_to_internal(pkgid, dir_list); + if (ret) { + app2ext_print + ("App2Sd Error : move app to internal memory failed %d\n", + ret); + return ret; + } + break; + } + default: + { + app2ext_print("App2Sd Error : invalid argument\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + } + + return ret; + +} + +int _app2sd_copy_ro_content(const char *src, const char *dest, GList* dir_list) +{ + char path[FILENAME_MAX] = { 0, }; + int ret = APP2EXT_SUCCESS; + GList *list = NULL; + app2ext_dir_details* dir_detail = NULL; + + list = g_list_first(dir_list); + while (list) { + dir_detail = (app2ext_dir_details *)list->data; + if (dir_detail && dir_detail->name + && dir_detail->type == APP2EXT_DIR_RO) { + memset((void *)&path, '\0', + FILENAME_MAX); + snprintf(path, FILENAME_MAX, + "%s/%s", src, + dir_detail->name); + ret = + _app2sd_copy_dir + (path, + dest); + if (ret) { + if (ret == APP2EXT_ERROR_ACCESS_FILE) { + app2ext_print + ("App2Sd Error : unable to access %s\n", + path); + } else { + app2ext_print + ("App2Sd Error : unable to copy from %s to %s .....errno is %d\n", + path, + dest, + errno); + return + APP2EXT_ERROR_MOVE; + } + } + } + list = g_list_next(list); + } + + return APP2EXT_SUCCESS; +} + +int _app2sd_duplicate_device(const char *pkgid, GList* dir_list, char *dev_node, int size) +{ + int ret = 0; + char temp_pkgid[FILENAME_MAX] = { 0, }; + char *devi = NULL; + int err_res = 0; + char *result = NULL; + + /*Create a new loopback device */ + snprintf(temp_pkgid, FILENAME_MAX, + "%s.new", pkgid); + ret = _app2sd_create_loopback_device(temp_pkgid, (size+PKG_BUF_SIZE)); + if (ret) { + app2ext_print("App2Sd Error : Package already present\n"); + return ret; + } + app2ext_print("App2Sd : _app2sd_create_loopback_device SUCCESS\n"); + /*Perform Loopback encryption setup */ + dev_node = _app2sd_do_loopback_duplicate_encryption_setup(pkgid, temp_pkgid); + if (!dev_node) { + app2ext_print("App2Sd Error : losetup failed, device node is %s\n", dev_node); + _app2sd_delete_loopback_device(pkgid); + app2ext_print("App2Sd Error : create ext filesystem failed\n"); + return APP2EXT_ERROR_DO_LOSETUP; + } + app2ext_print("App2Sd : _app2sd_do_loopback_duplicate_encryption_setup SUCCESS\n"); + /*Check whether loopback device is associated with device node or not */ + devi = _app2sd_find_associated_device_node(temp_pkgid); + if (devi == NULL) { + app2ext_print("App2Sd Error : finding associated device node failed\n"); + err_res = APP2EXT_ERROR_DO_LOSETUP; + goto FINISH_OFF; + } + app2ext_print("App2Sd : _app2sd_find_associated_device_node SUCCESS\n"); + /*Format the loopback file system */ + ret = _app2sd_create_file_system(dev_node); + if (ret) { + app2ext_print("App2Sd Error : creating FS failed failed\n"); + err_res = APP2EXT_ERROR_CREATE_FS; + goto FINISH_OFF; + } + app2ext_print("App2Sd : _app2sd_create_file_system SUCCESS\n"); + /*Do mounting for new dev*/ + ret = + _app2sd_mount_app_content(temp_pkgid, dev_node, MOUNT_TYPE_RW, + dir_list, APP2SD_PRE_UPGRADE); + if (ret) { + app2ext_print("App2Sd Error : Re-mount failed\n"); + err_res = APP2EXT_ERROR_MOUNT_PATH; + goto FINISH_OFF; + } + if (devi) { + free(devi); + devi = NULL; + } + return APP2EXT_SUCCESS; + +FINISH_OFF: + if (dev_node) { + result = _app2sd_detach_loop_device(dev_node); + if (result) { + free(result); + result = NULL; + } + _app2sd_delete_loopback_device(pkgid); + free(dev_node); + dev_node = NULL; + } + + if (devi) { + free(devi); + devi = NULL; + } + return err_res; +} + +int _app2sd_update_loopback_device_size(const char *pkgid, + int size, GList* dir_list) +{ + int ret = 0; + char *device_node = NULL; + char *old_device_node = NULL; + char *result = NULL; + int err_res = 0; + char app_mmc_path[FILENAME_MAX] = { 0, }; + char app_archive_path[FILENAME_MAX] = { 0, }; + char temp_pkgid[FILENAME_MAX] = { 0, }; + char app_path[FILENAME_MAX] = { 0, }; + + snprintf(temp_pkgid, FILENAME_MAX, + "%s.new", pkgid); + + ret = _app2sd_duplicate_device(pkgid, dir_list, device_node, size); + if (ret) { + app2ext_print("App2Sd Error : Creating duplicate device failed\n"); + return ret; + } + + app2ext_print("App2Sd : _app2sd_mount_app_content SUCCESS\n"); + /*check app entry is there in sd card or not. */ + snprintf(app_path, FILENAME_MAX, "%s%s", APP2SD_PATH, + pkgid); + + /*Get the associated device node for SD card applicatione */ + old_device_node = _app2sd_find_associated_device_node(pkgid); + if (NULL == old_device_node) { + /*Do loopback setup */ + old_device_node = _app2sd_do_loopback_encryption_setup(pkgid); + if (old_device_node == NULL) { + app2ext_print + ("App2Sd Error : loopback encryption setup failed\n"); + err_res = APP2EXT_ERROR_DO_LOSETUP; + goto FINISH_OFF; + } + /*Do mounting */ + ret = + _app2sd_mount_app_content(pkgid, old_device_node, + MOUNT_TYPE_RW, dir_list, + APP2SD_PRE_UPGRADE); + if (ret) { + app2ext_print("App2Sd Error : Re-mount failed\n"); + err_res = APP2EXT_ERROR_MOUNT_PATH; + goto FINISH_OFF; + } + } else { + /*Do re-mounting */ + ret = + _app2sd_mount_app_content(pkgid, old_device_node, + MOUNT_TYPE_RW_REMOUNT, dir_list, + APP2SD_PRE_UPGRADE); + if (ret) { + app2ext_print("App2Sd Error : Re-mount failed\n"); + err_res = APP2EXT_ERROR_MOUNT_PATH; + goto FINISH_OFF; + } + } + + snprintf(app_mmc_path, FILENAME_MAX, + "%s%s/.mmc", APP_INSTALLATION_PATH, pkgid); + snprintf(app_archive_path, FILENAME_MAX, + "%s%s/.mmc", APP_INSTALLATION_PATH, temp_pkgid); + + ret = _app2sd_copy_ro_content(app_mmc_path, app_archive_path, dir_list); + if (ret) { + app2ext_print("App2Sd Error : copy ro content failed\n"); + err_res = ret; + goto FINISH_OFF; + } + + ret = _app2sd_unmount_app_content(pkgid); + if (ret) { + app2ext_print + ("App2SD Error: Unable to unmount the SD application\n"); + err_res = APP2EXT_ERROR_UNMOUNT; + goto FINISH_OFF; + } + ret = _app2sd_remove_loopback_encryption_setup(pkgid); + if (ret) { + app2ext_print("App2SD Error: Unable to remove loopback setup\n"); + err_res = APP2EXT_ERROR_DELETE_LOOPBACK_DEVICE; + goto FINISH_OFF; + } + ret = _app2sd_unmount_app_content(temp_pkgid); + if (ret) { + app2ext_print + ("App2SD Error: Unable to unmount the SD application\n"); + err_res = APP2EXT_ERROR_UNMOUNT; + goto FINISH_OFF; + } + ret = _app2sd_remove_loopback_encryption_setup(temp_pkgid); + if (ret) { + app2ext_print("App2SD Error: Unable to remove loopback setup\n"); + err_res = APP2EXT_ERROR_DELETE_LOOPBACK_DEVICE; + goto FINISH_OFF; + } + snprintf(app_archive_path, FILENAME_MAX, + "%s%s", APP2SD_PATH, temp_pkgid); + ret = _app2sd_delete_directory(app_path); + if (ret) { + app2ext_print + ("App2Sd Error : unable to delete %s \n", + app_path); + err_res = APP2EXT_ERROR_DELETE_DIRECTORY; + goto FINISH_OFF; + } + ret = _app2sd_rename_dir(app_archive_path, app_path); + if (ret) { + app2ext_print + ("App2Sd Error : unable to rename %s \n", + app_archive_path); + err_res = APP2EXT_ERROR_MOVE; + goto FINISH_OFF; + } + snprintf(app_path, FILENAME_MAX, + "%s%s", APP_INSTALLATION_PATH, temp_pkgid); + ret = _app2sd_delete_directory(app_path); + if (ret) { + app2ext_print + ("App2Sd Error : unable to delete %s \n", + app_path); + err_res = APP2EXT_ERROR_DELETE_DIRECTORY; + goto FINISH_OFF; + } + return APP2EXT_SUCCESS; + +FINISH_OFF: + if (old_device_node) { + free(old_device_node); + old_device_node = NULL; + } + + ret = _app2sd_remove_loopback_encryption_setup(pkgid); + if (ret) { + app2ext_print("App2SD Error: Unable to remove loopback setup\n"); + err_res = APP2EXT_ERROR_DELETE_LOOPBACK_DEVICE; + } + return err_res; +} diff --git a/plugin/app2sd/src/app2sd_internals_registry.c b/plugin/app2sd/src/app2sd_internals_registry.c new file mode 100755 index 0000000..6f29fc9 --- /dev/null +++ b/plugin/app2sd/src/app2sd_internals_registry.c @@ -0,0 +1,252 @@ +/* + * app2ext + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Garima Shrivastava + * Jyotsna Dhumale + * Venkatesha Sarpangala + * + * 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 +#include +#include +#include +#include +#include +#define MAX_QUERY_LEN 4096 +#define PASSWORD_LENGTH 64 +/* +########### Internal APIs ################## + */ + +/*sqlite db code*/ +#define APP2SD_DB_FILE "/opt/dbspace/.app2sd.db" +sqlite3 *app2sd_db; +#define QUERY_CREATE_TABLE_APP2SD "create table app2sd \ + (pkgid text primary key,\ + password text\ + )" + +/* + *@_app2sd_initialize_db + *This function is to initialize sqlite db. + * return: On success, it will return zero else if fail then return val<0. + */ +int _app2sd_initialize_db() +{ + char *error_message = NULL; + int ret; + FILE * fp = NULL; + fp = fopen(APP2SD_DB_FILE, "r"); + if (fp != NULL) { + fclose(fp); + ret = + db_util_open(APP2SD_DB_FILE, &app2sd_db, + DB_UTIL_REGISTER_HOOK_METHOD); + + if (ret != SQLITE_OK) { + app2ext_print("====>>>> connect menu_db [%s] failed!\n", + APP2SD_DB_FILE); + return -1; + } + return 0; + } + + ret = + db_util_open(APP2SD_DB_FILE, &app2sd_db, + DB_UTIL_REGISTER_HOOK_METHOD); + + if (ret != SQLITE_OK) { + app2ext_print("====>>>> connect menu_db [%s] failed!\n", + APP2SD_DB_FILE); + return -1; + } + + if (SQLITE_OK != + sqlite3_exec(app2sd_db, QUERY_CREATE_TABLE_APP2SD, + NULL, NULL, &error_message)) { + app2ext_print("Don't execute query = %s, " + "error message = %s\n", + QUERY_CREATE_TABLE_APP2SD, error_message); + return -1; + } + + app2ext_print("\n db_initialize_done "); + return 0; +} + +/* + *@_app2sd_set_password_in_db + *This function is to store password into db. + * param[in]: pkgid: package id + * param[in]: password: password string + * return: On success, it will return 0. + * Else appropriate error will be returned. + */ +int _app2sd_set_password_in_db(const char *pkgid, + const char *passwd) +{ + char query[MAX_QUERY_LEN] = { 0, }; + char *error_message = NULL; + + snprintf(query, MAX_QUERY_LEN, "insert into app2sd(pkgid,password)\ + values ('%s','%s')", pkgid, passwd); + + if (SQLITE_OK != sqlite3_exec(app2sd_db, query, NULL, NULL, + &error_message)) { + app2ext_print("Don't execute query = %s, error message = %s\n", + query, error_message); + return APP2EXT_ERROR_SQLITE_REGISTRY; + } + app2ext_print("\n sqlite insertion done "); + return APP2EXT_SUCCESS; +} + +/* + *@_app2sd_remove_password_from_db + *This function is to remove passwod from db. + * param[in]: pkgid: package id + * return: On success, it will return 0. + * Else appropriate error will be returned. + */ +int _app2sd_remove_password_from_db(const char *pkgid) +{ + char query[MAX_QUERY_LEN] = { 0 }; + char *error_message = NULL; + + snprintf(query, MAX_QUERY_LEN, + "delete from app2sd where pkgid LIKE '%s'", pkgid); + app2ext_print("\n deletion querys is %s ", query); + + if (SQLITE_OK != sqlite3_exec(app2sd_db, query, NULL, + NULL, &error_message)) { + app2ext_print("Don't execute query = %s, error message = %s\n", + query, error_message); + return APP2EXT_ERROR_SQLITE_REGISTRY; + } + + app2ext_print("\n app2sd password deletion done "); + return APP2EXT_SUCCESS; + +} + +/* + *@_app2sd_access_password_from_db + *This function is to find out whther password exists for an application. + * param[in]: pkgid: package id + * return: On success, it will return zero , else value<0 if fail. + */ +int _app2sd_access_password_from_db(const char *pkgid) +{ + char query[MAX_QUERY_LEN] = { 0 }; + int access_flag = 0; + int i; + sqlite3_stmt *stmt = NULL; + const char *tail = NULL; + + snprintf(query, MAX_QUERY_LEN, + "select * from app2sd where pkgid LIKE '%s'", pkgid); + app2ext_print("\n access querys is %s ", query); + + if (SQLITE_OK != sqlite3_prepare(app2sd_db, query, + strlen(query), &stmt, &tail)) { + app2ext_print("sqlite3_prepare error\n"); + return -1; + } + for (i = 0; SQLITE_ROW == sqlite3_step(stmt); i++) { + app2ext_print("\n entry available in sqlite"); + access_flag = 1; + + } + if (SQLITE_OK != sqlite3_finalize(stmt)) { + app2ext_print("error : sqlite3_finalize\n"); + return -1; + } + if (access_flag == 1) { + app2ext_print("\n app2sd value is accessible "); + return APP2EXT_SUCCESS; + } else { + app2ext_print("\n app2sd value is not accessible "); + return -1; + } +} + +/* + *@_app2sd_get_password_from_db + *This function is to retrive password from DB + * param[in]: pkgid: package id + * return: On success, it will return the password, else NULL. + */ +char *_app2sd_get_password_from_db(const char *pkgid) +{ + char query[MAX_QUERY_LEN] = { 0 }; + sqlite3_stmt *stmt = NULL; + const char *tail = NULL; + int rc = 0; + char *passwd = NULL; + + snprintf(query, MAX_QUERY_LEN, + "select * from app2sd where pkgid LIKE '%s'", pkgid); + app2ext_print("\n access querys is %s ", query); + + if (SQLITE_OK != sqlite3_prepare(app2sd_db, query, + strlen(query), &stmt, &tail)) { + app2ext_print("sqlite3_prepare error\n"); + return NULL; + } + + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW || rc == SQLITE_DONE) { + app2ext_print("No records found"); + goto FINISH_OFF; + } + passwd = malloc(PASSWORD_LENGTH + 1); + if (passwd == NULL) { + app2ext_print("memory allocation failed\n"); + goto FINISH_OFF; + } + + app2ext_print("\n entry available in sqlite"); + strncpy(passwd, (const char*)sqlite3_column_text(stmt, 1), + PASSWORD_LENGTH); + if (passwd == NULL) { + app2ext_print("\n password is NULL "); + goto FINISH_OFF; + } + app2ext_print("\n passwd is %s ", passwd); + if (SQLITE_OK != sqlite3_finalize(stmt)) { + app2ext_print("error : sqlite3_finalize\n"); + goto FINISH_OFF; + } + return passwd; + +FINISH_OFF: + rc = sqlite3_finalize(stmt); + if (rc != SQLITE_OK) { + app2ext_print(" sqlite3_finalize failed - %d", rc); + } + if (passwd) + free(passwd); + return NULL; +} diff --git a/plugin/app2sd/src/app2sd_internals_utils.c b/plugin/app2sd/src/app2sd_internals_utils.c new file mode 100755 index 0000000..23a8c30 --- /dev/null +++ b/plugin/app2sd/src/app2sd_internals_utils.c @@ -0,0 +1,470 @@ +/* + * app2ext + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. + * + * Contact: Garima Shrivastava + * Jyotsna Dhumale + * Venkatesha Sarpangala + * + * 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 +#include +#include +#include +#include +#include +#include + +#define PASSWD_LEN 8 +#define ASCII_PASSWD_CHAR 94 + +/* +########### Internal APIs ################## + */ + +/*Note: Don't use any printf statement inside this function*/ +/*This function is similar to Linux's "system()" for executing a process.*/ +int _xsystem(const char *argv[]) +{ + int status = 0; + pid_t pid; + pid = fork(); + switch (pid) { + case -1: + perror("fork failed"); + return -1; + case 0: + /* child */ + execvp(argv[0], (char *const *)argv); + _exit(-1); + default: + /* parent */ + break; + } + if (waitpid(pid, &status, 0) == -1) { + perror("waitpid failed"); + return -1; + } + if (WIFSIGNALED(status)) { + perror("signal"); + return -1; + } + if (!WIFEXITED(status)) { + /* shouldn't happen */ + perror("should not happen"); + return -1; + } + return WEXITSTATUS(status); +} + + +/* +* @_app2sd_check_mmc_status +* This function checks and returns MMC status +*/ +int _app2sd_check_mmc_status(void) +{ + FILE *fp1 = NULL; + char line[512]; + fp1 = fopen("/etc/mtab", "r"); + if (fp1 == NULL) { + fprintf(stderr, "failed to open file\n"); + app2ext_print("failed to open file /etc/mtab\n"); + return APP2EXT_ERROR_MMC_STATUS; + } + while (fgets(line, 512, fp1) != NULL) { + if (strstr(line, MMC_PATH) != NULL) { + fclose(fp1); + return APP2EXT_SUCCESS; + } + } + fclose(fp1); + return APP2EXT_ERROR_MMC_STATUS; +} + +/* + * @_app2sd_get_available_free_memory + * This function returns the available free memory in the SD Card. + * param [in]: sd_path: This is sd card access path. + * param [out]: free_mem: Result will be available in this. + * User has to pass valid memory address. + * return: On success, it will return 0. + * Else, appropriate error no will be returned. + */ +int _app2sd_get_available_free_memory(const char *sd_path, int *free_mem) +{ + struct statvfs buf; + int ret = 0; + if (sd_path == NULL || free_mem == NULL) { + app2ext_print("App2Sd Error : Invalid input parameter\n"); + return -1; + } + memset((void *)&buf, '\0', sizeof(struct statvfs)); + ret = statvfs(sd_path, &buf); + if (ret) { + app2ext_print + ("App2SD Error: Unable to get SD Card memory information\n"); + return APP2EXT_ERROR_MMC_INFORMATION; + } + *free_mem = ((buf.f_bfree * buf.f_bsize) / 1024) / 1024; + return 0; +} + +int _app2sd_delete_directory(char *dirname) +{ + DIR *dp = NULL; + struct dirent *ep = NULL; + char abs_filename[FILENAME_MAX] = { 0, }; + int ret = 0; + dp = opendir(dirname); + if (dp != NULL) { + while ((ep = readdir(dp)) != NULL) { + struct stat stFileInfo; + + snprintf(abs_filename, FILENAME_MAX, "%s/%s", dirname, + ep->d_name); + + if (lstat(abs_filename, &stFileInfo) < 0) { + perror(abs_filename); + return -1; + } + + if (S_ISDIR(stFileInfo.st_mode)) { + if (strcmp(ep->d_name, ".") + && strcmp(ep->d_name, "..")) { + ret = _app2sd_delete_directory(abs_filename); + if (ret <0) + return -1; + } + } else { + ret = remove(abs_filename); + if (ret <0) + return -1; + } + } + (void)closedir(dp); + ret = remove(dirname); + if (ret <0) + return -1; + } else { + app2ext_print("Couldn't open the directory\n"); + } + return 0; +} + +int _app2sd_copy_dir(const char *src, const char *dest) +{ + int ret = APP2EXT_SUCCESS; + const char *argv_bin[] = { "/bin/cp", "-raf", src, dest, NULL }; + ret = _xsystem(argv_bin); + if (ret) { + app2ext_print("copy fail\n"); + return APP2EXT_ERROR_MOVE; + } + return ret; +} + +int _app2sd_rename_dir(const char *old_name, const char *new_name) +{ + int ret = APP2EXT_SUCCESS; + const char *argv_bin[] = { "/bin/mv", old_name, new_name, NULL }; + ret = _xsystem(argv_bin); + if (ret) { + app2ext_print("mv/rename fail\n"); + return APP2EXT_ERROR_MOVE; + } + return ret; +} + +unsigned long long _app2sd_calculate_dir_size(char *dirname) +{ + static unsigned long long total = 0; + DIR *dp = NULL; + struct dirent *ep = NULL; + char abs_filename[FILENAME_MAX] = { 0, };; + dp = opendir(dirname); + if (dp != NULL) { + while ((ep = readdir(dp)) != NULL) { + struct stat stFileInfo; + + snprintf(abs_filename, FILENAME_MAX, "%s/%s", dirname, + ep->d_name); + + if (stat(abs_filename, &stFileInfo) < 0) + perror(abs_filename); + else { + total += stFileInfo.st_size; + + if (S_ISDIR(stFileInfo.st_mode)) { + if (strcmp(ep->d_name, ".") + && strcmp(ep->d_name, "..")) { + _app2sd_calculate_dir_size + (abs_filename); + } + } else { + /*Do Nothing */ + } + } + } + (void)closedir(dp); + } else { + app2ext_print("\n error in opening directory "); + } + return total; +} + +unsigned long long _app2sd_calculate_file_size(const char *filename) +{ + struct stat stFileInfo; + app2ext_print("\n Calculating file size for %s\n", filename); + + if (stat(filename, &stFileInfo) < 0) { + perror(filename); + return 0; + } else + return stFileInfo.st_size; +} + +/*Note: Don't use any printf statement inside this function*/ +char *_app2sd_encrypt_device(const char *device, const char *pkgid, + char *passwd) +{ + const char *argv[] = + { "/sbin/losetup", "-e", "aes", device, pkgid, "-k", passwd, NULL }; + pid_t pid = 0; + int my_pipe[2] = { 0, }; + char buf[FILENAME_MAX] = { 0, }; + char *ret_result = NULL; + int result = 0; + if (pipe(my_pipe) < 0) { + fprintf(stderr, "Unable to create pipe\n"); + return NULL; + } + pid = fork(); + switch (pid) { + case -1: + perror("fork failed"); + return NULL; + case 0: + /* child */ + close(1); + close(2); + result = dup(my_pipe[1]); + result = dup(my_pipe[1]); + if (execvp(argv[0], (char *const *)argv) < 0) { + fprintf(stderr, "execvp failed %d....%s\n", errno, strerror(errno)); /*Don't use d_msg_app2sd */ + } + _exit(-1); + default: + /* parent */ + close(my_pipe[1]); + result = read(my_pipe[0], buf, FILENAME_MAX); + break; + } + + ret_result = (char *)malloc(strlen(buf) + 1); + if (ret_result == NULL) { + app2ext_print("Malloc failed!\n"); + return NULL; + } + memset(ret_result, '\0', strlen(buf) + 1); + memcpy(ret_result, buf, strlen(buf)); + return ret_result; +} + +/*Note: Don't use any printf statement inside this function*/ +char *_app2sd_detach_loop_device(const char *device) +{ + const char *argv[] = { "/sbin/losetup", "-d", device, NULL }; + pid_t pid; + int my_pipe[2] = { 0, }; + char buf[FILENAME_MAX] = { 0, }; + char *ret_result = NULL; + int result = 0; + if (pipe(my_pipe) < 0) { + fprintf(stderr, "Unable to create pipe\n"); + return NULL; + } + pid = fork(); + switch (pid) { + case -1: + perror("fork failed"); + return NULL; + case 0: + /* child */ + close(1); + close(2); + result = dup(my_pipe[1]); + result = dup(my_pipe[1]); + if (execvp(argv[0], (char *const *)argv) < 0) { + fprintf(stderr, "execvp failed\n"); /*Don't use d_msg_app2sd */ + } + _exit(-1); + default: + /* parent */ + close(my_pipe[1]); + result = read(my_pipe[0], buf, FILENAME_MAX); + break; + } + + ret_result = (char *)malloc(strlen(buf) + 1); + if (ret_result == NULL) { + app2ext_print("Malloc failed!\n"); + return NULL; + } + memset(ret_result, '\0', strlen(buf) + 1); + memcpy(ret_result, buf, strlen(buf)); + + return ret_result; +} + +/*Note: Don't use any printf statement inside this function*/ +char *_app2sd_find_associated_device(const char *mmc_app_path) +{ + const char *argv[] = { "/sbin/losetup", "-j", mmc_app_path, NULL }; + pid_t pid; + int my_pipe[2] = { 0, }; + char buf[FILENAME_MAX] = { 0, }; + char *ret_result = NULL; + int result = 0; + if (pipe(my_pipe) < 0) { + fprintf(stderr, "Unable to create pipe\n"); + return NULL; + } + pid = fork(); + switch (pid) { + case -1: + perror("fork failed"); + return NULL; + case 0: + /* child */ + close(1); + close(2); + result = dup(my_pipe[1]); + result = dup(my_pipe[1]); + if (execvp(argv[0], (char *const *)argv) < 0) { + fprintf(stderr, "execvp failed\n"); /*Don't use d_msg_app2sd */ + } + _exit(-1); + default: + /* parent */ + close(my_pipe[1]); + result = read(my_pipe[0], buf, FILENAME_MAX); + break; + } + + ret_result = (char *)malloc(strlen(buf) + 1); + if (ret_result == NULL) { + app2ext_print("Malloc failed!\n"); + return NULL; + } + memset(ret_result, '\0', strlen(buf) + 1); + memcpy(ret_result, buf, strlen(buf)); + + return ret_result; +} + +/*Note: Don't use any printf statement inside this function*/ +char *_app2sd_find_free_device(void) +{ + const char *argv[] = { "/sbin/losetup", "-f", NULL }; + pid_t pid; + int my_pipe[2] = { 0, }; + char buf[FILENAME_MAX+1] = { 0, }; + char *ret_result = NULL; + int result = 0; + if (pipe(my_pipe) < 0) { + fprintf(stderr, "Unable to create pipe\n"); + return NULL; + } + pid = fork(); + switch (pid) { + case -1: + perror("fork failed"); + return NULL; + case 0: + /* child */ + close(1); + close(2); + result = dup(my_pipe[1]); + result = dup(my_pipe[1]); + if (execvp(argv[0], (char *const *)argv) < 0) { + fprintf(stderr, "execvp failed\n"); /*Don't use d_msg_app2sd */ + } + _exit(-1); + default: + /* parent */ + close(my_pipe[1]); + result = read(my_pipe[0], buf, FILENAME_MAX); + break; + } + + ret_result = (char *)malloc(strlen(buf) + 1); + if (ret_result == NULL) { + app2ext_print("Malloc failed!\n"); + return NULL; + } + memset(ret_result, '\0', strlen(buf) + 1); + memcpy(ret_result, buf, strlen(buf)); + + return ret_result; +} + +/*@_app2sd_generate_password +* This is a simple password generator +* return: On success, it will return the password, else NULL. +*/ +char *_app2sd_generate_password(const char *pkgid) +{ + char passwd[PASSWD_LEN+1] = { 0, }; + char *ret_result = NULL; + char set[ASCII_PASSWD_CHAR+1] = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; + unsigned char char_1; + unsigned char char_2; + int i = 0; + int appname_len = strlen(pkgid); + int j = appname_len; + + /* Length of the password */ + ret_result = (char*)malloc(PASSWD_LEN+1); + if (NULL == ret_result) { + app2ext_print("Unable to Allocate memory\n"); + return NULL; + } + memset((void *)ret_result, '\0', PASSWD_LEN+1); + + while(i < PASSWD_LEN) { + char_1 = (rand()+pkgid[j--])%ASCII_PASSWD_CHAR; + char_2 = rand()%ASCII_PASSWD_CHAR; + passwd[i] = set[char_1]; + passwd[i+1] = set[(pkgid[j--])*2]; + if (i + * + * 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 +#include + +#define APP2EXT_SD_PLUGIN_PATH "/usr/lib/libapp2sd.so" + +app2ext_handle *app2ext_init(int storage_type) +{ + /*Validate the function parameter recieved */ + if (storage_type < APP2EXT_INTERNAL_MEM || + storage_type > APP2EXT_CLOUD) { + app2ext_print("App2Ext Error : Invalid function arguments\n"); + return NULL; + } + if (storage_type != APP2EXT_SD_CARD ) { + app2ext_print("App2Ext Error : Storage type currently not supported\n"); + return NULL; + } + + /* allocate memory for app2ext handle*/ + app2ext_handle *handle = (app2ext_handle *)calloc(1, sizeof(app2ext_handle)); + if (handle == NULL) { + app2ext_print("App2Ext Error : Memory allocation failure\n"); + return NULL; + } + void *dl_handle = NULL; + int (*dl_on_load)(app2ext_interface *)=NULL; + + /* Load SD plugin*/ + handle->type = APP2EXT_SD_CARD; + dl_handle = dlopen(APP2EXT_SD_PLUGIN_PATH, RTLD_LAZY|RTLD_GLOBAL); + if (NULL == dl_handle) + { + app2ext_print("App2Ext Error : dlopen(%s) failed.\n", APP2EXT_SD_PLUGIN_PATH); + free(handle); + return NULL; + } + handle->plugin_handle = dl_handle; + dl_on_load = dlsym(dl_handle, "app2ext_on_load"); + if (NULL == dl_on_load) + { + app2ext_print("App2Ext Error : Cannot find app2ext_on_load symbol in %s.", APP2EXT_SD_PLUGIN_PATH); + dlclose(dl_handle); + free(handle); + return NULL; + } + + /*Initialize the SD plugin*/ + if(!dl_on_load(&(handle->interface))) + { + app2ext_print("App2Ext Error : [%s] app2ext_on_load() failed.", APP2EXT_SD_PLUGIN_PATH); + dlclose(dl_handle); + free(handle); + return NULL; + } + + app2ext_print("App2Ext: %s plugin loaded\n", APP2EXT_SD_PLUGIN_PATH); + + return handle; +} + +int app2ext_deinit(app2ext_handle *handle) +{ + /*Validate the function parameter recieved */ + if (handle == NULL || handle->plugin_handle == NULL){ + app2ext_print("App2Ext Error : Invalid function arguments\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + + /* Close the plugin handle*/ + dlclose(handle->plugin_handle); + + /* Free allocated memory during installtion*/ + free(handle); + return APP2EXT_SUCCESS; +} + +int app2ext_get_app_location(const char *appname) +{ + /*Validate the function parameter received */ + if (appname == NULL) { + app2ext_print("invalid func parameters\n"); + return APP2EXT_ERROR_INVALID_ARGUMENTS; + } + FILE *fp = NULL; + char app_mmc_path[FILENAME_MAX] = { 0, }; + char app_dir_path[FILENAME_MAX] = { 0, }; + char app_mmc_internal_path[FILENAME_MAX] = { 0, }; + snprintf(app_dir_path, FILENAME_MAX, + "%s%s", APP_INSTALLATION_PATH, appname); + snprintf(app_mmc_path, FILENAME_MAX, + "%s%s", APP2SD_PATH, appname); + snprintf(app_mmc_internal_path, FILENAME_MAX, + "%s%s/.mmc", APP_INSTALLATION_PATH, appname); + + + /*check whether application is in external memory or not */ + fp = fopen(app_mmc_path, "r"); + if (fp == NULL) { + app2ext_print + (" app path in external memory not accesible\n"); + } else { + fclose(fp); + fp = NULL; + return APP2EXT_SD_CARD; + } + + /*check whether application is in internal or not */ + fp = fopen(app_dir_path, "r"); + if (fp == NULL) { + app2ext_print + (" app path in internal memory not accesible\n"); + return APP2EXT_NOT_INSTALLED; + } else { + fclose(fp); + /*check whether the application is installed in SD card + but SD card is not present*/ + fp = fopen(app_mmc_internal_path, "r"); + if (fp == NULL) { + return APP2EXT_INTERNAL_MEM; + } else { + fclose(fp); + return APP2EXT_ERROR_MMC_STATUS; + } + } +} -- 2.7.4