Separate the TAC into the RW area and the RO area (#521)
author최종헌/MDE Lab(SR)/삼성전자 <j-h.choi@samsung.com>
Wed, 29 May 2024 07:37:42 +0000 (16:37 +0900)
committerGitHub Enterprise <noreply-CODE@samsung.com>
Wed, 29 May 2024 07:37:42 +0000 (16:37 +0900)
Change-Id: Iaf90d00dfd65c779ad941d154d733e8a04d278f4

16 files changed:
NativeLauncher/CMakeLists.txt
NativeLauncher/inc/launcher_env.h
NativeLauncher/inc/tac_common.h
NativeLauncher/inc/tac_db.h
NativeLauncher/inc/utils.h
NativeLauncher/tool/dotnettool.cc
NativeLauncher/tool/ni_common.cc
NativeLauncher/tool/tac_common.cc
NativeLauncher/tool/tac_db.cc
NativeLauncher/tool/tac_installer.cc
NativeLauncher/util/utils.cc
dotnet-launcher.manifest
packaging/dotnet-launcher.spec
tests/TCs/4_TAC/TAC_RO.py [new file with mode: 0755]
tests/TCs/4_TAC/TAC_RW.py [moved from tests/TCs/4_TAC/TAC.py with 99% similarity]
tests/TCs/Utils.py

index 019d031..bafb9ff 100644 (file)
@@ -55,6 +55,10 @@ IF(DEFINED READ_ONLY_APP_UPDATE_DIR)
     SET(EXTRA_CFLAGS_COMMON "${EXTRA_CFLAGS_COMMON} -DREAD_ONLY_APP_UPDATE_DIR=${READ_ONLY_APP_UPDATE_DIR}")
 ENDIF(DEFINED READ_ONLY_APP_UPDATE_DIR)
 
+IF(DEFINED READ_ONLY_TAC_DIR)
+    SET(EXTRA_CFLAGS_COMMON "${EXTRA_CFLAGS_COMMON} -DREAD_ONLY_TAC_DIR=${READ_ONLY_TAC_DIR}")
+ENDIF(DEFINED READ_ONLY_TAC_DIR)
+
 IF(DEFINED USE_DEFAULT_BASE_ADDR)
     SET(EXTRA_CFLAGS_COMMON "${EXTRA_CFLAGS_COMMON} -DUSE_DEFAULT_BASE_ADDR")
 ENDIF(DEFINED USE_DEFAULT_BASE_ADDR)
index 6b24f87..d313a61 100644 (file)
 #define APP_NI_SUB_TMP_DIR           ".native_image_tmp"
 #define TAC_SYMLINK_SUB_DIR          ".tac_symlink"
 #define TAC_SHA_256_INFO             ".SHA256.info"
-#define TAC_APP_LIST_DB              "/opt/usr/dotnet/.TAC.App.list.db"
-#define TAC_APP_LIST_RESTORE_DB      "/opt/usr/dotnet/.TAC.App.list.restore.db"
-#define TLC_APP_LIST_DB              "/opt/usr/dotnet/.TLC.App.list.db"
-#define TLC_APP_LIST_RESTORE_DB      "/opt/usr/dotnet/.TLC.App.list.restore.db"
-#define TLC_LIBRARIES_DIR            "/opt/usr/dotnet/Libraries"
+#define TAC_APP_LIST_DB              ".TAC.App.list.db"
+#define TAC_APP_LIST_RESTORE_DB      ".TAC.App.list.restore.db"
+#define TLC_APP_LIST_DB              ".TLC.App.list.db"
+#define TLC_APP_LIST_RESTORE_DB      ".TLC.App.list.restore.db"
+#define TLC_LIBRARIES_DIR            "Libraries"
 #define TIZEN_DOTNET_NUGET           "Tizen.NET"
 #define TIZEN_DOTNET_SDK_NUGET       "Tizen.NET.Sdk"
 #define NET_STANDARD_LIBRARY_NUGET   "NETStandard.Library"
index 17e2441..32ff451 100644 (file)
@@ -31,9 +31,10 @@ typedef enum {
 
 /**
  * @brief restore database of TAC
+ * @param[in] tac readonly flag
  * @return tac_error_e
  */
-tac_error_e tac_restoreDB();
+tac_error_e tac_restoreDB(bool isReadonly);
 
 /**
  * @brief disable tac feature.
@@ -66,8 +67,9 @@ std::vector<std::string> getLibrariesInfo(const std::string& rootPath);
 
 /**
  * @brief restore database of TLC
+ * @param[in] tlc readonly flag
  * @return tac_error_e
  */
-tac_error_e tlc_restoreDB();
+tac_error_e tlc_restoreDB(bool isReadonly);
 
 #endif /* __TAC_COMMON_H__ */
index 896f20d..c808911 100644 (file)
@@ -24,9 +24,9 @@ int sqliteCb(void *count, int argc, char **argv, char **colName);
 
 
 /* TAC related DB functions */
-int tac_createDB();
+int tac_createDB(bool isReadonly);
 
-int tac_openDB();
+int tac_openDB(bool isReadonly);
 
 int tac_insertDB(const std::string& pkgId, const std::string& np, const std::string& tac_name, const std::string& tac_version);
 
@@ -44,9 +44,9 @@ bool tac_closeDB();
 
 
 /* TAC related DB functions */
-int tlc_createDB();
+int tlc_createDB(bool isReadonly);
 
-int tlc_openDB();
+int tlc_openDB(bool isReadonly);
 
 int tlc_insertDB(const std::string& pkgId, const std::string& fileSha);
 
index 38c6361..ec74c3d 100644 (file)
@@ -128,6 +128,28 @@ std::string changeExtension(const std::string& path, const std::string& from, co
 bool isReadOnlyArea(const std::string& path);
 
 /**
+ * @brief check the package is 'readonly' or not
+ * @param[in] package ID
+ * @return bool package readonly value
+ */
+bool isReadOnlyPkg(const std::string& pkgId);
+
+/**
+ * @brief get db path of tac
+ * @param[in] tac readonly flag
+ * @param[in] db file name
+ * @return tac db path
+ */
+std::string tacDBPath(bool isReadonly, const std::string& dbName);
+
+/**
+ * @brief get library path of tlc
+ * @param[in] tlac readonly flag
+ * @return tlc library path
+ */
+std::string tlcLBPath(bool isReadonly);
+
+/**
  * @brief split path with ":" delimiter and put that in the vector
  * @param[in] source path
  * @param[out] string vector
index fbd2db7..d4fcf1a 100644 (file)
@@ -375,13 +375,17 @@ int main(int argc, char* argv[])
        }
        //sh-3.2# dotnettool --tac-restore-db
        else if (cmd == "--tac-restore-db") {
-               int ret = tac_restoreDB();
-               if (ret != TAC_ERROR_NONE) {
-                       _SERR("Failed to restore TAC db");
+               if (tac_restoreDB(false) != TAC_ERROR_NONE) {
+                       _SERR("Failed to restore TAC db of RW");
                }
-               ret = tlc_restoreDB();
-               if (ret != TAC_ERROR_NONE) {
-                       _SERR("Failed to restore TLC db");
+               if (tlc_restoreDB(false) != TAC_ERROR_NONE) {
+                       _SERR("Failed to restore TLC db of RW");
+               }
+               if (tac_restoreDB(true) != TAC_ERROR_NONE) {
+                       _SERR("Failed to restore TAC db of RO");
+               }
+               if (tlc_restoreDB(true) != TAC_ERROR_NONE) {
+                       _SERR("Failed to restore TLC db of RO");
                }
        }
        //sh-3.2# dotnettool --tac-enable-pkg [pkgId] [pkgId] ...
index 38db04f..ed4e7aa 100644 (file)
@@ -813,27 +813,20 @@ static ni_error_e doAOTFile(const std::string& dllFile, const std::string& refPa
        return doAOTList(dllList, refPaths, opt);
 }
 
-static bool isReadOnlyPkg(std::string pkgId)
+static ni_error_e removeAndCreateNI(const char* pkgId, NIOption* pOptions)
 {
-       int ret = 0;
-       bool readonly = false;
-       pkgmgrinfo_pkginfo_h handle;
-
-       ret = pkgmgrinfo_pkginfo_get_pkginfo(pkgId.c_str(), &handle);
-       if (ret != PMINFO_R_OK) {
-               _ERR("Fail to get pkginfo");
-               return false;
+       if (removeNIUnderPkgRoot(pkgId) != NI_ERROR_NONE) {
+               _SERR("Failed to remove previous dlls from [%s]", pkgId);
+               return NI_ERROR_UNKNOWN;
        }
 
-       ret = pkgmgrinfo_pkginfo_is_readonly(handle, &readonly);
-       if (ret != PMINFO_R_OK) {
-               _ERR("Fail to get is_readonly");
-               pkgmgrinfo_pkginfo_destroy_pkginfo(handle);
-               return false;
+       if (createNIUnderPkgRoot(pkgId, pOptions) != NI_ERROR_NONE) {
+               _SERR("Failed to generate NI file [%s]", pkgId);
+               return NI_ERROR_UNKNOWN;
        }
 
-       pkgmgrinfo_pkginfo_destroy_pkginfo(handle);
-       return readonly;
+       _SOUT("Complete make native image for pkg (%s)", pkgId);
+       return NI_ERROR_NONE;
 }
 
 // callback function of "pkgmgrinfo_appinfo_metadata_filter_foreach"
@@ -1302,7 +1295,7 @@ static int regenTacCb(pkgmgrinfo_appinfo_h handle, void *userData)
                return 0;
        }
 
-       sqlite3 *tac_db = openDB(TAC_APP_LIST_DB);
+       sqlite3 *tac_db = openDB(concatPath(__DOTNET_DIR, TAC_APP_LIST_DB));
        if (!tac_db) {
                _SERR("Sqlite open error");
                return -1;
index 03ff5a8..e0575c9 100644 (file)
@@ -34,6 +34,7 @@
 #define __STR(x) __XSTR(x)
 static const char* __DOTNET_DIR = __STR(DOTNET_DIR);
 static const char* __READ_ONLY_APP_UPDATE_DIR = __STR(READ_ONLY_APP_UPDATE_DIR);
+static const char* __READ_ONLY_TAC_DIR = __STR(READ_ONLY_TAC_DIR);
 #undef __STR
 #undef __XSTR
 
@@ -42,14 +43,15 @@ static sqlite3 *tlc_db = NULL;
 static std::vector<std::string> restore_nuget;
 static std::vector<std::string> restore_library;
 
-static void cleanupDirectory()
+static void cleanupDirectory(bool isReadonly)
 {
        std::vector<std::string> removeNuget;
        try {
-               for (auto& nuget : bf::recursive_directory_iterator(__DOTNET_DIR)) {
+               std::string tacLocation = isReadonly ? __READ_ONLY_TAC_DIR : __DOTNET_DIR;
+               for (auto& nuget : bf::recursive_directory_iterator(tacLocation)) {
                        std::string nugetPath = nuget.path().string();
                        if (!bf::is_directory(nugetPath) ||
-                               nugetPath.find(TLC_LIBRARIES_DIR) != std::string::npos ||
+                               nugetPath.find(tlcLBPath(isReadonly)) != std::string::npos ||
                                nugetPath.find(__READ_ONLY_APP_UPDATE_DIR) != std::string::npos) {
                                continue;
                        }
@@ -84,6 +86,7 @@ static int tac_restoreDBCb(pkgmgrinfo_appinfo_h handle, void *userData)
        char *pkgId = NULL;
        char *root = NULL;
        char *exec = NULL;
+       bool *isReadOnly = (bool*)userData;
 
        int ret = pkgmgrinfo_appinfo_get_pkgid(handle, &pkgId);
        if (ret != PMINFO_R_OK) {
@@ -91,6 +94,10 @@ static int tac_restoreDBCb(pkgmgrinfo_appinfo_h handle, void *userData)
                return -1;
        }
 
+       if (userData == NULL || isReadOnlyPkg(pkgId) != *isReadOnly) {
+               return -1;
+       }
+
        ret = pkgmgrinfo_appinfo_get_root_path(handle, &root);
        if (ret != PMINFO_R_OK) {
                _SERR("Failed to get root path");
@@ -128,7 +135,7 @@ static int tac_restoreDBCb(pkgmgrinfo_appinfo_h handle, void *userData)
                                "INSERT INTO TAC (PKGID, NUGET, NAME, VERSION) " \
                                "VALUES (%Q, %Q, %Q, %Q);",     pkgId, nuget.c_str(), name.c_str(), version.c_str());
                        insertDB(tac_db, sql);
-                       restore_nuget.push_back(concatPath(__DOTNET_DIR, nuget));
+                       restore_nuget.push_back(concatPath(*isReadOnly ? __READ_ONLY_TAC_DIR : __DOTNET_DIR, nuget));
                        sqlite3_free(sql);
                }
        }
@@ -137,20 +144,21 @@ static int tac_restoreDBCb(pkgmgrinfo_appinfo_h handle, void *userData)
        return 0;
 }
 
-tac_error_e tac_restoreDB()
+tac_error_e tac_restoreDB(bool isReadonly)
 {
-       if (!removeFile(TAC_APP_LIST_RESTORE_DB)) {
-               _SERR("Failed to remove of %s", TAC_APP_LIST_RESTORE_DB);
+       std::string tacRestoreDbPath = tacDBPath(isReadonly, TAC_APP_LIST_RESTORE_DB);
+       if (!removeFile(tacRestoreDbPath)) {
+               _SERR("Failed to remove of %s", tacRestoreDbPath.c_str());
                return TAC_ERROR_UNKNOWN;
        }
 
-       std::string dbRestoreJournal = TAC_APP_LIST_RESTORE_DB + std::string("-journal");
+       std::string dbRestoreJournal = tacRestoreDbPath + std::string("-journal");
        if (!removeFile(dbRestoreJournal)) {
                _SERR("Failed to remove of %s", dbRestoreJournal.c_str());
                return TAC_ERROR_UNKNOWN;
        }
 
-       tac_db = createDB(TAC_APP_LIST_RESTORE_DB, CREATE_TAC_DB_TABLE);
+       tac_db = createDB(tacRestoreDbPath, CREATE_TAC_DB_TABLE);
        if (!tac_db) {
                _SERR("Sqlite create error");
                return TAC_ERROR_UNKNOWN;
@@ -169,7 +177,7 @@ tac_error_e tac_restoreDB()
                return TAC_ERROR_UNKNOWN;
        }
 
-       ret = pkgmgrAppMDFilterForeach(handle, tac_restoreDBCb, NULL);
+       ret = pkgmgrAppMDFilterForeach(handle, tac_restoreDBCb, &isReadonly);
        if (ret != 0) {
                pkgmgrinfo_appinfo_metadata_filter_destroy(handle);
                return TAC_ERROR_UNKNOWN;
@@ -182,16 +190,17 @@ tac_error_e tac_restoreDB()
                tac_db = NULL;
        }
 
-       if (!copyFile(TAC_APP_LIST_RESTORE_DB, TAC_APP_LIST_DB)) {
-               _SERR("Failed to copy of %s", TAC_APP_LIST_DB);
+       std::string tacDbPath = tacDBPath(isReadonly, TAC_APP_LIST_DB);
+       if (!copyFile(tacRestoreDbPath, tacDbPath)) {
+               _SERR("Failed to copy of %s", tacDbPath.c_str());
                return TAC_ERROR_UNKNOWN;
        }
-       if (!removeFile(TAC_APP_LIST_RESTORE_DB)) {
-               _SERR("Failed to remove of %s", TAC_APP_LIST_RESTORE_DB);
+       if (!removeFile(tacRestoreDbPath)) {
+               _SERR("Failed to remove of %s", tacRestoreDbPath.c_str());
                return TAC_ERROR_UNKNOWN;
        }
 
-       std::string dbJournal = TAC_APP_LIST_DB + std::string("-journal");
+       std::string dbJournal = tacDbPath + std::string("-journal");
        if (!copyFile(dbRestoreJournal, dbJournal)) {
                _SERR("Failed to copy of %s", dbJournal.c_str());
                return TAC_ERROR_UNKNOWN;
@@ -201,7 +210,7 @@ tac_error_e tac_restoreDB()
                return TAC_ERROR_UNKNOWN;
        }
 
-       cleanupDirectory();
+       cleanupDirectory(isReadonly);
 
        return TAC_ERROR_NONE;
 }
@@ -274,6 +283,7 @@ tac_error_e disableTACPackage(const std::string& pkgId)
 
 tac_error_e enableTACPackage(const std::string& pkgId)
 {
+       std::string tacLocation = isReadOnlyPkg(pkgId) ? __READ_ONLY_TAC_DIR : __DOTNET_DIR;
        std::string rootPath = getRootPath(pkgId);
        if (rootPath.empty()) {
                _SERR("Failed to get root path from [%s]", pkgId.c_str());
@@ -319,7 +329,7 @@ tac_error_e enableTACPackage(const std::string& pkgId)
        for (auto& npAssembly : depsJsonParser(rootPath, execName)) {
                std::string nugetPackage = npAssembly.substr(0, npAssembly.rfind(':'));
                std::string assemblyName = npAssembly.substr(npAssembly.rfind(':') + 1);
-               std::string nugetPath = concatPath(__DOTNET_DIR, nugetPackage);
+               std::string nugetPath = concatPath(tacLocation, nugetPackage);
                if (exist(nugetPath)) {
                        std::string originPath = concatPath(nugetPath, assemblyName);
                        if (exist(originPath)) {
@@ -427,6 +437,7 @@ static int tlc_restoreDBCb(pkgmgrinfo_appinfo_h handle, void *userData)
        char *pkgId = NULL;
        char *root = NULL;
        std::string rootPath;
+       bool *isReadOnly = (bool*)userData;
 
        int ret = pkgmgrinfo_appinfo_get_pkgid(handle, &pkgId);
        if (ret != PMINFO_R_OK) {
@@ -434,6 +445,10 @@ static int tlc_restoreDBCb(pkgmgrinfo_appinfo_h handle, void *userData)
                return -1;
        }
 
+       if (userData == NULL || isReadOnlyPkg(pkgId) != *isReadOnly) {
+               return -1;
+       }
+
        ret = pkgmgrinfo_appinfo_get_root_path(handle, &root);
        if (ret != PMINFO_R_OK) {
                _SERR("Failed to get root path");
@@ -454,20 +469,21 @@ static int tlc_restoreDBCb(pkgmgrinfo_appinfo_h handle, void *userData)
        return 0;
 }
 
-tac_error_e tlc_restoreDB()
+tac_error_e tlc_restoreDB(bool isReadonly)
 {
-       if (!removeFile(TLC_APP_LIST_RESTORE_DB)) {
-               _SERR("Failed to remove of %s", TLC_APP_LIST_RESTORE_DB);
+       std::string tlcRestoreDbPath = tacDBPath(isReadonly, TLC_APP_LIST_RESTORE_DB);
+       if (!removeFile(tlcRestoreDbPath)) {
+               _SERR("Failed to remove of %s", tlcRestoreDbPath.c_str());
                return TAC_ERROR_UNKNOWN;
        }
 
-       std::string dbRestoreJournal = TLC_APP_LIST_RESTORE_DB + std::string("-journal");
+       std::string dbRestoreJournal = tlcRestoreDbPath + std::string("-journal");
        if (!removeFile(dbRestoreJournal)) {
                _SERR("Failed to remove of %s", dbRestoreJournal.c_str());
                return TAC_ERROR_UNKNOWN;
        }
 
-       tlc_db = createDB(TLC_APP_LIST_RESTORE_DB, CREATE_TLC_DB_TABLE);
+       tlc_db = createDB(tlcRestoreDbPath, CREATE_TLC_DB_TABLE);
        if (!tlc_db) {
                _SERR("Sqlite create error");
                return TAC_ERROR_UNKNOWN;
@@ -486,7 +502,7 @@ tac_error_e tlc_restoreDB()
                return TAC_ERROR_UNKNOWN;
        }
 
-       ret = pkgmgrAppMDFilterForeach(handle, tlc_restoreDBCb, NULL);
+       ret = pkgmgrAppMDFilterForeach(handle, tlc_restoreDBCb, &isReadonly);
        if (ret != 0) {
                pkgmgrinfo_appinfo_metadata_filter_destroy(handle);
                return TAC_ERROR_UNKNOWN;
@@ -499,16 +515,17 @@ tac_error_e tlc_restoreDB()
                tlc_db = NULL;
        }
 
-       if (!copyFile(TLC_APP_LIST_RESTORE_DB, TLC_APP_LIST_DB)) {
-               _SERR("Failed to copy of %s", TLC_APP_LIST_DB);
+       std::string tlcDbPath = tacDBPath(isReadonly, TLC_APP_LIST_DB);
+       if (!copyFile(tlcRestoreDbPath, tlcDbPath)) {
+               _SERR("Failed to copy of %s", tlcDbPath.c_str());
                return TAC_ERROR_UNKNOWN;
        }
-       if (!removeFile(TLC_APP_LIST_RESTORE_DB)) {
-               _SERR("Failed to remove of %s", TLC_APP_LIST_RESTORE_DB);
+       if (!removeFile(tlcRestoreDbPath)) {
+               _SERR("Failed to remove of %s", tlcRestoreDbPath.c_str());
                return TAC_ERROR_UNKNOWN;
        }
 
-       std::string dbJournal = TLC_APP_LIST_DB + std::string("-journal");
+       std::string dbJournal = tlcDbPath + std::string("-journal");
        if (!copyFile(dbRestoreJournal, dbJournal)) {
                _SERR("Failed to copy of %s", dbJournal.c_str());
                return TAC_ERROR_UNKNOWN;
@@ -533,7 +550,7 @@ tac_error_e tlc_restoreDB()
                }
        };
 
-       scanFilesInDirectory(TLC_LIBRARIES_DIR, convert, 0);
+       scanFilesInDirectory(tlcLBPath(isReadonly), convert, 0);
 
        return TAC_ERROR_NONE;
 }
index 3484b5f..9e1a231 100644 (file)
@@ -28,6 +28,7 @@
 #define __XSTR(x) #x
 #define __STR(x) __XSTR(x)
 static const char* __DOTNET_DIR = __STR(DOTNET_DIR);
+static const char* __READ_ONLY_TAC_DIR = __STR(READ_ONLY_TAC_DIR);
 #undef __STR
 #undef __XSTR
 
@@ -42,16 +43,17 @@ int sqliteCb(void *count, int argc, char **argv, char **colName)
        return 0;
 }
 
-int tac_createDB()
+int tac_createDB(bool isReadonly)
 {
-       tac_db = createDB(TAC_APP_LIST_DB, CREATE_TAC_DB_TABLE);
+       std::string tacDbPath = tacDBPath(isReadonly, TAC_APP_LIST_DB);
+       tac_db = createDB(tacDbPath, CREATE_TAC_DB_TABLE);
        if (!tac_db) {
                _ERR("Sqlite create error. So restore the database.");
-               if (tac_restoreDB() != TAC_ERROR_NONE) {
+               if (tac_restoreDB(isReadonly) != TAC_ERROR_NONE) {
                        _ERR("Sqlite create error");
                        return -1;
                }
-               tac_db = createDB(TAC_APP_LIST_DB, CREATE_TAC_DB_TABLE);
+               tac_db = createDB(tacDbPath, CREATE_TAC_DB_TABLE);
                if (!tac_db) {
                        _ERR("Sqlite create error");
                        return -1;
@@ -61,16 +63,17 @@ int tac_createDB()
        return 0;
 }
 
-int tac_openDB()
+int tac_openDB(bool isReadonly)
 {
-       tac_db = openDB(TAC_APP_LIST_DB);
+       std::string tacDbPath = tacDBPath(isReadonly, TAC_APP_LIST_DB);
+       tac_db = openDB(tacDbPath);
        if (!tac_db) {
                _ERR("Sqlite open error. So restore the database.");
-               if (tac_restoreDB() != TAC_ERROR_NONE) {
+               if (tac_restoreDB(isReadonly) != TAC_ERROR_NONE) {
                        _ERR("Sqlite open error");
                        return -1;
                }
-               tac_db = openDB(TAC_APP_LIST_DB);
+               tac_db = openDB(tacDbPath);
                if (!tac_db) {
                        _ERR("Sqlite open error");
                        return -1;
@@ -168,22 +171,25 @@ bool tac_closeDB()
        return false;
 }
 
-int tlc_createDB()
+int tlc_createDB(bool isReadonly)
 {
-       if (!createDir(TLC_LIBRARIES_DIR)) {
-               _ERR("Cannot create directory: %s", TLC_LIBRARIES_DIR);
+       std::string tlcPath = tlcLBPath(isReadonly);
+       if (!createDir(tlcPath)) {
+               _ERR("Cannot create directory: %s", tlcPath.c_str());
                return -1;
        }
-       copySmackAndOwnership(__DOTNET_DIR, TLC_LIBRARIES_DIR);
+       std::string tacLocation = isReadonly ? __READ_ONLY_TAC_DIR : __DOTNET_DIR;
+       copySmackAndOwnership(tacLocation, tlcPath);
 
-       tlc_db = createDB(TLC_APP_LIST_DB, CREATE_TLC_DB_TABLE);
+       std::string tlcDbPath = tacDBPath(isReadonly, TLC_APP_LIST_DB);
+       tlc_db = createDB(tlcDbPath, CREATE_TLC_DB_TABLE);
        if (!tlc_db) {
                _ERR("Sqlite create error. So restore the database.");
-               if (tlc_restoreDB() != TAC_ERROR_NONE) {
+               if (tlc_restoreDB(isReadonly) != TAC_ERROR_NONE) {
                        _ERR("Sqlite create error");
                        return -1;
                }
-               tlc_db = createDB(TLC_APP_LIST_DB, CREATE_TLC_DB_TABLE);
+               tlc_db = createDB(tlcDbPath, CREATE_TLC_DB_TABLE);
                if (!tlc_db) {
                        _ERR("Sqlite create error");
                        return -1;
@@ -193,16 +199,17 @@ int tlc_createDB()
        return 0;
 }
 
-int tlc_openDB()
+int tlc_openDB(bool isReadonly)
 {
-       tlc_db = openDB(TLC_APP_LIST_DB);
+       std::string tlcDbPath = tacDBPath(isReadonly, TLC_APP_LIST_DB);
+       tlc_db = openDB(tlcDbPath);
        if (!tlc_db) {
                _ERR("Sqlite open error. So restore the database.");
-               if (tlc_restoreDB() != TAC_ERROR_NONE) {
+               if (tlc_restoreDB(isReadonly) != TAC_ERROR_NONE) {
                        _ERR("Sqlite open error");
                        return 0;
                }
-               tlc_db = openDB(TLC_APP_LIST_DB);
+               tlc_db = openDB(tlcDbPath);
                if (!tlc_db) {
                        _ERR("Sqlite open error");
                        return 0;
index b8b7b66..3fe4446 100644 (file)
@@ -38,6 +38,7 @@
 #define __XSTR(x) #x
 #define __STR(x) __XSTR(x)
 static const char* __DOTNET_DIR = __STR(DOTNET_DIR);
+static const char* __READ_ONLY_TAC_DIR = __STR(READ_ONLY_TAC_DIR);
 #undef __STR
 #undef __XSTR
 
@@ -50,6 +51,8 @@ static std::vector<std::string> updateTlc;
 static tac_state tacState = TAC_STATE_NONE;
 static std::string prevInstallPkgId = std::string("");
 static std::string prevFinishPkgId = std::string("");
+static std::string tacLocation = __DOTNET_DIR;
+static bool isTacReadonly = false;
 
 // initialize static vector to support multi-package install scenario
 static void tacInitialize()
@@ -116,7 +119,7 @@ static bool compareSHA256Info(std::string sha256Info, std::string nugetPackage)
 static bool copyAssemblyCreateSymlink(std::string binPath, std::string tacDir, std::string nugetPackage, bool isCreateTacDir)
 {
        std::string binNiPath = concatPath(binPath, APP_NI_SUB_DIR);
-       std::string tac_version_dir = concatPath(__DOTNET_DIR, nugetPackage);
+       std::string tac_version_dir = concatPath(tacLocation, nugetPackage);
        bool nuget_restoration = false;
        for (auto& npAssemblySha : nugetPackagesAssembliesSha) {
                std::string nuget_package_assembly = npAssemblySha.substr(0, npAssemblySha.rfind(':'));
@@ -189,7 +192,7 @@ static void copyLibraryCreateSymlink(const std::string pkgId, std::vector<std::s
                std::string library = librarySha.substr(0, librarySha.find(':'));
                std::string filename = library.substr(library.rfind('/') + 1);
                std::string fileSha = filename + ".." + librarySha.substr(librarySha.find(':') + 1);
-               std::string shaPath = concatPath(TLC_LIBRARIES_DIR, fileSha);
+               std::string shaPath = concatPath(tlcLBPath(isTacReadonly), fileSha);
                bool fileCopied = false;
                if (!exist(shaPath)) {
                        if (!copyFile(library, shaPath)) {
@@ -261,7 +264,7 @@ static int generateTAC(const std::string& pkgId, const std::string& binPath)
                _INFO("TAC version : %s", tac_version.c_str());
 
                bs::error_code error;
-               std::string tac_version_dir = concatPath(__DOTNET_DIR, np);
+               std::string tac_version_dir = concatPath(tacLocation, np);
                std::string sha256_info = concatPath(tac_version_dir, TAC_SHA_256_INFO);
                bool isCreateTacDir = false;
                if (!exist(tac_version_dir)) {
@@ -347,7 +350,7 @@ void tacUpdateDB(const std::string& pkgId)
                }
 
                if (count == 0) {
-                       std::string tac_version_dir_prev = concatPath(__DOTNET_DIR, unp);
+                       std::string tac_version_dir_prev = concatPath(tacLocation, unp);
                        std::string tac_version_dir_backup = tac_version_dir_prev + ".bck";
                        if (!copyDir(tac_version_dir_prev, tac_version_dir_backup)) {
                                _ERR("Failed to copy of %s to %s", tac_version_dir_prev.c_str(), tac_version_dir_backup.c_str());
@@ -370,7 +373,7 @@ void tlcUpdateDB(const std::string& pkgId)
                }
 
                if (count == 0) {
-                       std::string library_prev = concatPath(TLC_LIBRARIES_DIR, ulp);
+                       std::string library_prev = concatPath(tlcLBPath(isTacReadonly), ulp);
                        std::string library_backup = library_prev + ".bck";
                        if (!copyFile(library_prev, library_backup)) {
                                _ERR("Failed to copy of %s", library_prev.c_str());
@@ -391,6 +394,9 @@ int tacInstall(const std::string& pkgId, tac_state state, bool tacForce)
 
        tacInitialize();
 
+       isTacReadonly = isReadOnlyPkg(pkgId);
+       tacLocation = isTacReadonly ? __READ_ONLY_TAC_DIR : __DOTNET_DIR;
+
        // Can be multiple apps in one package
        if (strcmp(pkgId.c_str(), prevInstallPkgId.c_str()) == 0) {
                _INFO("TAC Plugin(INSTALL) already run for same pkgId (%s)", pkgId.c_str());
@@ -430,7 +436,7 @@ int tacInstall(const std::string& pkgId, tac_state state, bool tacForce)
                return 0;
        }
 
-       if (tac_createDB() != 0) {
+       if (tac_createDB(isTacReadonly) != 0) {
                return -1;
        }
 
@@ -440,7 +446,7 @@ int tacInstall(const std::string& pkgId, tac_state state, bool tacForce)
        }
 
        ///// TLC /////
-       if (tlc_createDB() != 0) {
+       if (tlc_createDB(isTacReadonly) != 0) {
                tac_closeDB();
                return -1;
        }
@@ -459,6 +465,9 @@ int tacUpgrade(const std::string& pkgId, tac_state state, bool tacForce)
 
        tacInitialize();
 
+       isTacReadonly = isReadOnlyPkg(pkgId);
+       tacLocation = isTacReadonly ? __READ_ONLY_TAC_DIR : __DOTNET_DIR;
+
        // Can be multiple apps in one package
        if (strcmp(pkgId.c_str(), prevInstallPkgId.c_str()) == 0) {
                _INFO("TAC Plugin(UPGRADE) already run for same pkgId (%s)", pkgId.c_str());
@@ -498,7 +507,7 @@ int tacUpgrade(const std::string& pkgId, tac_state state, bool tacForce)
        }
 
        tacState = TAC_STATE_UPGRADE;
-       if (tac_createDB() != 0) {
+       if (tac_createDB(isTacReadonly) != 0) {
                return -1;
        }
 
@@ -539,7 +548,7 @@ int tacUpgrade(const std::string& pkgId, tac_state state, bool tacForce)
        }
 
        ///// TLC /////
-       if (tlc_createDB() != 0) {
+       if (tlc_createDB(isTacReadonly) != 0) {
                tac_closeDB();
                return -1;
        }
@@ -569,6 +578,9 @@ int tacUninstall(const std::string& pkgId, tac_state state)
 
        tacInitialize();
 
+       isTacReadonly = isReadOnlyPkg(pkgId);
+       tacLocation = isTacReadonly ? __READ_ONLY_TAC_DIR : __DOTNET_DIR;
+
        // Can be multiple apps in one package
        if (strcmp(pkgId.c_str(), prevInstallPkgId.c_str()) == 0) {
                _INFO("TAC Plugin(UNINSTALL) already run for same pkgId (%s)", pkgId.c_str());
@@ -577,7 +589,7 @@ int tacUninstall(const std::string& pkgId, tac_state state)
        prevInstallPkgId = pkgId;
 
        tacState= state;
-       if (tac_openDB() != 0) {
+       if (tac_openDB(isTacReadonly) != 0) {
                return -1;
        }
 
@@ -591,7 +603,7 @@ int tacUninstall(const std::string& pkgId, tac_state state)
        tacUpdateDB(pkgId);
 
        ///// TLC /////
-       if (tlc_openDB() != 0) {
+       if (tlc_openDB(isTacReadonly) != 0) {
                tac_closeDB();
                return -1;
        }
@@ -622,7 +634,7 @@ int tacRemoved(const std::string& pkgId)
 
 void undoStep(std::string tac)
 {
-       std::string current_tac = concatPath(__DOTNET_DIR, tac.substr(0, tac.find('/')));
+       std::string current_tac = concatPath(tacLocation, tac.substr(0, tac.find('/')));
        try {
                for (auto& bck : bf::recursive_directory_iterator(current_tac)) {
                        std::string bck_path = bck.path().string();
@@ -647,7 +659,7 @@ void undoStep(std::string tac)
                }
        };
 
-       scanFilesInDirectory(TLC_LIBRARIES_DIR, convert, 0);
+       scanFilesInDirectory(tlcLBPath(isTacReadonly), convert, 0);
 }
 
 void install_Undo()
@@ -688,6 +700,9 @@ int tacUndo(const std::string& pkgId)
        _DBG("[===== PKGMGR_MDPARSER_PLUGIN_UNDO =====]");
        _INFO("PackageID : %s", pkgId.c_str());
 
+       isTacReadonly = isReadOnlyPkg(pkgId);
+       tacLocation = isTacReadonly ? __READ_ONLY_TAC_DIR : __DOTNET_DIR;
+
        // Can be multiple apps in one package
        if (strcmp(pkgId.c_str(), prevFinishPkgId.c_str()) == 0) {
                _INFO("TAC Plugin(UNDO) already run for same pkgId (%s)", pkgId.c_str());
@@ -714,10 +729,10 @@ int tacUndo(const std::string& pkgId)
 
 void changeOwnershipTAC(std::string current_tac)
 {
-       copySmackAndOwnership(__DOTNET_DIR, current_tac);
+       copySmackAndOwnership(tacLocation, current_tac);
        try {
                for (auto& path : bf::recursive_directory_iterator(current_tac))
-                       copySmackAndOwnership(__DOTNET_DIR, path.path().string());
+                       copySmackAndOwnership(tacLocation, path.path().string());
        } catch (const bf::filesystem_error& error) {
                _ERR("Failed to recursive directory: %s", error.what());
        }
@@ -725,7 +740,7 @@ void changeOwnershipTAC(std::string current_tac)
 
 void cleanStep(std::string tac)
 {
-       std::string current_tac = concatPath(__DOTNET_DIR, tac.substr(0, tac.find('/')));
+       std::string current_tac = concatPath(tacLocation, tac.substr(0, tac.find('/')));
        try {
                for (auto& bck : bf::recursive_directory_iterator(current_tac)) {
                        std::string bck_path = bck.path().string();
@@ -764,18 +779,18 @@ void cleanStep(std::string tac)
                }
        };
 
-       scanFilesInDirectory(TLC_LIBRARIES_DIR, convert, 0);
+       scanFilesInDirectory(tlcLBPath(isTacReadonly), convert, 0);
 }
 
 void install_Clean()
 {
        for (auto& cd : createDirectories) {
                changeOwnershipTAC(cd);
-               copySmackAndOwnership(__DOTNET_DIR, cd.substr(0, cd.rfind('/')));
+               copySmackAndOwnership(tacLocation, cd.substr(0, cd.rfind('/')));
        }
 
        for (auto& cl : createLibraries) {
-               copySmackAndOwnership(__DOTNET_DIR, cl);
+               copySmackAndOwnership(tacLocation, cl);
        }
 }
 
@@ -792,7 +807,7 @@ void update_Clean()
        if (!tacDB.empty()) {
                for (auto& np : tacDB) {
                        cleanStep(np);
-                       changeOwnershipTAC(concatPath(__DOTNET_DIR, np.substr(0, np.find('/'))));
+                       changeOwnershipTAC(concatPath(tacLocation, np.substr(0, np.find('/'))));
                }
        }
        unInstall_Clean();
@@ -854,13 +869,15 @@ int tacClean(const std::string& pkgId)
        }
 
        if (tac_closeDB()) {
-               copySmackAndOwnership(__DOTNET_DIR, TAC_APP_LIST_DB);
-               copySmackAndOwnership(__DOTNET_DIR, TAC_APP_LIST_DB + std::string("-journal"));
+               std::string tacDbPath = tacDBPath(isTacReadonly, TAC_APP_LIST_DB);
+               copySmackAndOwnership(tacLocation, tacDbPath);
+               copySmackAndOwnership(tacLocation, tacDbPath + std::string("-journal"));
        }
 
        if (tlc_closeDB()) {
-               copySmackAndOwnership(__DOTNET_DIR, TLC_APP_LIST_DB);
-               copySmackAndOwnership(__DOTNET_DIR, TLC_APP_LIST_DB + std::string("-journal"));
+               std::string tlcDbPath = tacDBPath(isTacReadonly, TLC_APP_LIST_DB);
+               copySmackAndOwnership(tacLocation, tlcDbPath);
+               copySmackAndOwnership(tacLocation, tlcDbPath + std::string("-journal"));
        }
 
        return 0;
index f1dcc59..29d26d2 100644 (file)
 #include "path_manager.h"
 #include "r2r_checker.h"
 
+#define __XSTR(x) #x
+#define __STR(x) __XSTR(x)
+static const char* __DOTNET_DIR = __STR(DOTNET_DIR);
+static const char* __READ_ONLY_TAC_DIR = __STR(READ_ONLY_TAC_DIR);
+#undef __STR
+#undef __XSTR
+
 static bool iCompare(const std::string& a, int aOffset, const std::string& b, int bOffset, int length)
 {
        return static_cast<int>(a.length()) - length >= aOffset &&
@@ -265,6 +272,45 @@ bool isReadOnlyArea(const std::string& path)
 
 }
 
+bool isReadOnlyPkg(const std::string& pkgId)
+{
+       uid_t uid = 0;
+       int ret = 0;
+       bool readonly = false;
+       pkgmgrinfo_pkginfo_h handle;
+
+       if (pkgmgr_installer_info_get_target_uid(&uid) < 0) {
+               _ERR("Failed to get UID");
+               return false;
+       }
+
+       ret = pkgmgrinfo_pkginfo_get_usr_pkginfo(pkgId.c_str(), uid, &handle);
+       if (ret != PMINFO_R_OK) {
+               _ERR("Fail to get pkginfo");
+               return false;
+       }
+
+       ret = pkgmgrinfo_pkginfo_is_readonly(handle, &readonly);
+       if (ret != PMINFO_R_OK) {
+               _ERR("Fail to get is_readonly");
+               pkgmgrinfo_pkginfo_destroy_pkginfo(handle);
+               return false;
+       }
+
+       pkgmgrinfo_pkginfo_destroy_pkginfo(handle);
+       return readonly;
+}
+
+std::string tacDBPath(bool isReadonly, const std::string& dbName)
+{
+       return isReadonly ? concatPath(__READ_ONLY_TAC_DIR, dbName) : concatPath(__DOTNET_DIR, dbName);
+}
+
+std::string tlcLBPath(bool isReadonly)
+{
+       return isReadonly ? concatPath(__READ_ONLY_TAC_DIR, TLC_LIBRARIES_DIR) : concatPath(__DOTNET_DIR, TLC_LIBRARIES_DIR);
+}
+
 std::string getBaseName(const std::string& path)
 {
        auto pos = path.find_last_of(PATH_SEPARATOR);
index ec6f15f..737e625 100644 (file)
@@ -4,6 +4,7 @@
     </request>
     <assign>
         <filesystem path="/opt/usr/dotnet" label="System::Shared" type="transmutable" />
+        <filesystem path="/usr/share/dotnet.tizen/tac" label="System::Shared" type="transmutable" />
         <filesystem path="/opt/usr/dotnet/apps" label="User::Home"/>
         <filesystem path="/usr/bin/dotnet-loader" label="User" exec_label="User" />
         <filesystem path="/usr/bin/dotnet-hydra-loader" label="User" exec_label="User" />
index 20b0899..9b63d32 100644 (file)
@@ -58,6 +58,7 @@ Requires(preun): /usr/bin/systemctl
 %define _native_lib_dir /usr/share/dotnet.tizen/lib
 %define _dotnet_dir /opt/usr/dotnet
 %define _readonly_app_update_dir /opt/usr/dotnet/apps
+%define _readonly_tac_dir /usr/share/dotnet.tizen/tac
 %define _system_base_addr_file /opt/usr/dotnet.system.base.addr
 %define _tizen_preload_dir /usr/share/dotnet.tizen/preload
 
@@ -161,6 +162,7 @@ cmake \
        -DINSTALL_PLUGIN_DIR=%{_install_plugin_dir} \
        -DDOTNET_DIR=%{_dotnet_dir} \
        -DREAD_ONLY_APP_UPDATE_DIR=%{_readonly_app_update_dir} \
+       -DREAD_ONLY_TAC_DIR=%{_readonly_tac_dir} \
        -DVERSION=%{version} \
        -DNATIVE_LIB_DIR=%{_native_lib_dir} \
 %ifarch %{arm} aarch64
@@ -187,6 +189,7 @@ mv Managed/Tizen.Runtime/bin/Release/Tizen.Runtime.pdb %{buildroot}%{_framework_
 
 mkdir -p %{buildroot}%{_dotnet_dir}
 mkdir -p %{buildroot}%{_readonly_app_update_dir}
+mkdir -p %{buildroot}%{_readonly_tac_dir}
 mkdir -p %{buildroot}%{_native_lib_dir}
 ln -sf %{_libdir}/libsqlite3.so.0 %{buildroot}%{_native_lib_dir}/libsqlite3.so
 
@@ -240,6 +243,7 @@ chsmack -a User /usr/bin/dotnet-uts-loader
 %{_framework_dir}/Tizen.Runtime.dll
 %{_dotnet_dir}
 %{_readonly_app_update_dir}
+%{_readonly_tac_dir}
 %{_tizen_preload_dir}
 %{_rw_update_scripts_dir}/%{_rw_dotnet_update_script}
 %if 0%{_build_dotnet_plugin}
diff --git a/tests/TCs/4_TAC/TAC_RO.py b/tests/TCs/4_TAC/TAC_RO.py
new file mode 100755 (executable)
index 0000000..4e9d725
--- /dev/null
@@ -0,0 +1,589 @@
+#!/usr/bin/env python3
+import os, subprocess, sys, argparse
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
+
+from time import sleep
+from Utils import *
+
+
+module_name = "TAC"
+
+# The `Launcher_TC_TAC_01` application must have TAC applied.
+def TC_01():
+    sln_name = "Launcher_TC_TAC_01.Tizen"
+
+    tpk_path = get_tpk_path(tpk_list, f"{sln_name}")
+    if tpk_path == None:
+        return f"FAIL : Get the tpk path for {sln_name}"
+
+    raw = cmd(f"push {tpk_path} /usr/apps/.preload-tpk/")
+    if "1 file(s) pushed. 0 file(s) skipped." in raw:
+        cmd(f"shell install_preload_pkg")
+
+    pkg_id = f"org.tizen.example.Launcher_TC_TAC_01.Tizen"
+
+    root_path = get_root_path(f"{pkg_id}")
+    if root_path == "None":
+        return f"FAIL : Get the root path for {pkg_id}"
+
+    if not exist(f"{root_path}/bin/.tac_symlink"):
+        return "FAIL : The .tac_symlink folder should exist"
+
+    raw = cmd(f"shell find {root_path}/bin/.tac_symlink/ -name *.dll -not -name *.ni.dll")
+    lines1 = [l for l in raw.splitlines()]
+    raw = cmd(f"shell find {RO_TAC_DIR}Xamarin.Forms/4.6.0.967/ -name *.dll -not -name *.ni.dll")
+    lines2 = [l for l in raw.splitlines()]
+    if len(lines1) != len(lines2):
+        return "FAIL : The number of .dll in the .tac_symlink and .dll in the TAC must match"
+
+    raw = cmd(f"shell ls -alZ {root_path}/bin/.tac_symlink/*.dll")
+    lines = [l for l in raw.splitlines() if ".ni.dll" not in l]
+    for dll in lines:
+        origin_path = dll.split("->")[1].strip()
+        if not exist(f"{origin_path}"):
+            return "FAIL : The original file of the symbolic link must exist"
+
+    pid = launch_and_get_pid(f"-e", f"{pkg_id}")
+    if 0 == pid:
+        return f"FAIL : Get the pid for {pkg_id}"
+
+    raw = cmd(f"shell cat /proc/{pid}/smaps | grep Xamarin.Forms.*.dll")
+    if (f"{RO_TAC_DIR}Xamarin.Forms/4.6.0.967/Xamarin.Forms.Platform.Tizen.dll" not in raw) or \
+       (f"{RO_TAC_DIR}Xamarin.Forms/4.6.0.967/Xamarin.Forms.Core.dll" not in raw) or \
+       (f"{RO_TAC_DIR}Xamarin.Forms/4.6.0.967/Xamarin.Forms.Platform.dll" not in raw):
+        return "FAIL : The Xamarin.Forms in the TAC should be loaded when running the application"
+
+    cmd(f"shell app_launcher -t {pkg_id}")
+
+    return "PASS"
+
+# The `Launcher_TC_TAC_02` application must have TAC applied.
+def TC_02():
+    sln_name = "Launcher_TC_TAC_02.Tizen"
+
+    tpk_path = get_tpk_path(tpk_list, f"{sln_name}")
+    if tpk_path == None:
+        return f"FAIL : Get the tpk path for {sln_name}"
+
+    raw = cmd(f"push {tpk_path} /usr/apps/.preload-tpk/")
+    if "1 file(s) pushed. 0 file(s) skipped." in raw:
+        cmd(f"shell install_preload_pkg")
+
+    pkg_id = f"org.tizen.example.Launcher_TC_TAC_00.Tizen"
+
+    root_path = get_root_path(f"{pkg_id}")
+    if root_path == "None":
+        return f"FAIL : Get the root path for {pkg_id}"
+
+    if not exist(f"{root_path}/bin/.tac_symlink"):
+        return "FAIL : The .tac_symlink folder should exist"
+
+    raw = cmd(f"shell find {root_path}/bin/.tac_symlink/ -name *.dll -not -name *.ni.dll")
+    lines1 = [l for l in raw.splitlines()]
+    raw = cmd(f"shell find {RO_TAC_DIR}Xamarin.Forms/4.8.0.1364/ -name *.dll -not -name *.ni.dll")
+    lines2 = [l for l in raw.splitlines()]
+    raw = cmd(f"shell find {RO_TAC_DIR}Newtonsoft.Json/13.0.1/ -name *.dll -not -name *.ni.dll")
+    lines3 = [l for l in raw.splitlines()]
+    if len(lines1) != len(lines2)+len(lines3):
+        return "FAIL : The number of .dll in the .tac_symlink and .dll in the TAC must match"
+
+    raw = cmd(f"shell ls -alZ {root_path}/bin/.tac_symlink/*.dll")
+    lines = [l for l in raw.splitlines() if ".ni.dll" not in l]
+    for dll in lines:
+        origin_path = dll.split("->")[1].strip()
+        if not exist(f"{origin_path}"):
+            return "FAIL : The original file of the symbolic link must exist"
+
+    pid = launch_and_get_pid(f"-e", f"{pkg_id}")
+    if 0 == pid:
+        return f"FAIL : Get the pid for {pkg_id}"
+
+    raw = cmd(f"shell cat /proc/{pid}/smaps | grep Xamarin.Forms.*.dll")
+    if (f"{RO_TAC_DIR}Xamarin.Forms/4.8.0.1364/Xamarin.Forms.Platform.Tizen.dll" not in raw) or \
+       (f"{RO_TAC_DIR}Xamarin.Forms/4.8.0.1364/Xamarin.Forms.Core.dll" not in raw) or \
+       (f"{RO_TAC_DIR}Xamarin.Forms/4.8.0.1364/Xamarin.Forms.Platform.dll" not in raw):
+        return "FAIL : The Xamarin.Forms in the TAC should be loaded when running the application"
+
+    raw = cmd(f"shell cat /proc/{pid}/smaps | grep Newtonsoft.Json.dll")
+    if f"{RO_TAC_DIR}Newtonsoft.Json/13.0.1/Newtonsoft.Json.dll" not in raw:
+        return "FAIL : The Newtonsoft.Json in the TAC should be loaded when running the application"
+
+    cmd(f"shell app_launcher -t {pkg_id}")
+
+    return "PASS"
+
+# The `Launcher_TC_TAC_03` application is normally TAC applied when updating.
+def TC_03():
+    sln_name = "Launcher_TC_TAC_03.Tizen"
+
+    tpk_path = get_tpk_path(tpk_list, f"{sln_name}")
+    if tpk_path == None:
+        return f"FAIL : Get the tpk path for {sln_name}"
+
+    raw = cmd(f"push {tpk_path} /usr/apps/.preload-tpk/")
+    if "1 file(s) pushed. 0 file(s) skipped." in raw:
+        cmd(f"shell install_preload_pkg")
+
+    pkg_id = f"org.tizen.example.Launcher_TC_TAC_00.Tizen"
+
+    root_path = get_root_path(f"{pkg_id}")
+    if root_path == "None":
+        return f"FAIL : Get the root path for {pkg_id}"
+
+    if not exist(f"{root_path}/bin/.tac_symlink"):
+        return "FAIL : The .tac_symlink folder should exist"
+
+    raw = cmd(f"shell find {root_path}/bin/.tac_symlink/ -name *.dll -not -name *.ni.dll")
+    lines1 = [l for l in raw.splitlines()]
+    raw = cmd(f"shell find {RO_TAC_DIR}Xamarin.Forms/4.8.0.1687/ -name *.dll -not -name *.ni.dll")
+    lines2 = [l for l in raw.splitlines()]
+    raw = cmd(f"shell find {RO_TAC_DIR}sqlite-net-base/1.7.335/ -name *.dll -not -name *.ni.dll")
+    lines3 = [l for l in raw.splitlines()]
+    raw = cmd(f"shell find {RO_TAC_DIR}SQLitePCLRaw.core/2.0.3/ -name *.dll -not -name *.ni.dll")
+    lines4 = [l for l in raw.splitlines()]
+    if len(lines1) != len(lines2)+len(lines3)+len(lines4):
+        return "FAIL : The number of .dll in the .tac_symlink and .dll in the TAC must match"
+
+    raw = cmd(f"shell ls -alZ {root_path}/bin/.tac_symlink/*.dll")
+    lines = [l for l in raw.splitlines() if ".ni.dll" not in l]
+    for dll in lines:
+        origin_path = dll.split("->")[1].strip()
+        if not exist(f"{origin_path}"):
+            return "FAIL : The original file of the symbolic link must exist"
+
+    if exist(f"{RO_TAC_DIR}Xamarin.Forms/4.8.0.1364/ -name *.dll"):
+        return f"FAIL : The Xamarin.Forms/4.8.0.1364 nuget should not exist in {RO_TAC_DIR}"
+
+    if exist(f"{RO_TAC_DIR}Newtonsoft.Json/13.0.1/ -name *.dll"):
+        return f"FAIL : The Newtonsoft.Json/13.0.1 nuget should not exist in {RO_TAC_DIR}"
+
+    pid = launch_and_get_pid(f"-e", f"{pkg_id}")
+    if 0 == pid:
+        return f"FAIL : Get the pid for {pkg_id}"
+
+    raw = cmd(f"shell cat /proc/{pid}/smaps | grep Xamarin.Forms.*.dll")
+    if (f"{RO_TAC_DIR}Xamarin.Forms/4.8.0.1687/Xamarin.Forms.Platform.Tizen.dll" not in raw) or \
+       (f"{RO_TAC_DIR}Xamarin.Forms/4.8.0.1687/Xamarin.Forms.Core.dll" not in raw) or \
+       (f"{RO_TAC_DIR}Xamarin.Forms/4.8.0.1687/Xamarin.Forms.Platform.dll" not in raw):
+        return "FAIL : The Xamarin.Forms in the TAC should be loaded when running the application"
+
+    raw = cmd(f"shell cat /proc/{pid}/smaps | grep SQLite-net.dll")
+    if f"{RO_TAC_DIR}sqlite-net-base/1.7.335/SQLite-net.dll" not in raw:
+        return "FAIL : The sqlite-net-base in the TAC should be loaded when running the application"
+
+    raw = cmd(f"shell cat /proc/{pid}/smaps | grep SQLitePCLRaw.core.dll")
+    if f"{RO_TAC_DIR}SQLitePCLRaw.core/2.0.3/SQLitePCLRaw.core.dll" not in raw:
+        return "FAIL : The SQLitePCLRaw.core in the TAC should be loaded when running the application"
+
+    cmd(f"shell app_launcher -t {pkg_id}")
+
+    return "PASS"
+
+# The `Launcher_TC_TAC_04` application should not apply TAC when updating.
+def TC_04():
+    sln_name = "Launcher_TC_TAC_04.Tizen"
+
+    tpk_path = get_tpk_path(tpk_list, f"{sln_name}")
+    if tpk_path == None:
+        return f"FAIL : Get the tpk path for {sln_name}"
+
+    raw = cmd(f"push {tpk_path} /usr/apps/.preload-tpk/")
+    if "1 file(s) pushed. 0 file(s) skipped." in raw:
+        cmd(f"shell install_preload_pkg")
+
+    pkg_id = f"org.tizen.example.Launcher_TC_TAC_00.Tizen"
+
+    root_path = get_root_path(f"{pkg_id}")
+    if root_path == "None":
+        return f"FAIL : Get the root path for {pkg_id}"
+
+    if exist(f"{root_path}/bin/.tac_symlink"):
+        return "FAIL : The .tac_symlink folder should not exist"
+
+    if exist(f"{RO_TAC_DIR}Xamarin.Forms/4.8.0.1364/ -name *.dll"):
+        return f"FAIL : The Xamarin.Forms/4.8.0.1364 nuget should not exist in {RO_TAC_DIR}"
+
+    if exist(f"{RO_TAC_DIR}Newtonsoft.Json/13.0.1/ -name *.dll"):
+        return f"FAIL : The Newtonsoft.Json/13.0.1 nuget should not exist in {RO_TAC_DIR}"
+
+    if exist(f"{RO_TAC_DIR}sqlite-net-base/1.7.335/ -name *.dll"):
+        return f"FAIL : The sqlite-net-base/1.7.335 nuget should not exist in {RO_TAC_DIR}"
+
+    if exist(f"{RO_TAC_DIR}SQLitePCLRaw.core/2.0.3/ -name *.dll"):
+        return f"FAIL : The SQLitePCLRaw.core/2.0.3 nuget should not exist in {RO_TAC_DIR}"
+
+    pid = launch_and_get_pid(f"-e", f"{pkg_id}")
+    if 0 == pid:
+        return f"FAIL : Get the pid for {pkg_id}"
+
+    raw = cmd(f"shell cat /proc/{pid}/smaps | grep Xamarin.Forms.*.dll")
+    if (f"{root_path}/bin/Xamarin.Forms.Platform.Tizen.dll" not in raw) or \
+       (f"{root_path}/bin/Xamarin.Forms.Core.dll" not in raw) or \
+       (f"{root_path}/bin/Xamarin.Forms.Platform.dll" not in raw):
+        return "FAIL : The Xamarin.Forms in the application should be loaded when running the application"
+
+    cmd(f"shell app_launcher -t {pkg_id}")
+
+    return "PASS"
+
+# The `Launcher_TC_TAC_05`, `Launcher_TC_TAC_06` applications using the same nuget are normally TAC applied.
+def TC_05():
+    sln_name = "Launcher_TC_TAC_05.Tizen"
+
+    tpk_path = get_tpk_path(tpk_list, f"{sln_name}")
+    if tpk_path == None:
+        return f"FAIL : Get the tpk path for {sln_name}"
+
+    raw = cmd(f"push {tpk_path} /usr/apps/.preload-tpk/")
+    if "1 file(s) pushed. 0 file(s) skipped." in raw:
+        cmd(f"shell install_preload_pkg")
+
+    pkg_id1 = f"org.tizen.example.Launcher_TC_TAC_05.Tizen"
+
+    root_path = get_root_path(f"{pkg_id1}")
+    if root_path == "None":
+        return f"FAIL : Get the root path for {pkg_id1}"
+
+    if not exist(f"{root_path}/bin/.tac_symlink"):
+        return "FAIL : The .tac_symlink folder should exist"
+
+    raw = cmd(f"shell find {root_path}/bin/.tac_symlink/ -name *.dll -not -name *.ni.dll")
+    lines1 = [l for l in raw.splitlines()]
+    raw = cmd(f"shell find {RO_TAC_DIR}Xamarin.Forms/5.0.0.1558-pre3/ -name *.dll -not -name *.ni.dll")
+    lines2 = [l for l in raw.splitlines()]
+    if len(lines1) != len(lines2):
+        return "FAIL : The number of .dll in the .tac_symlink and .dll in the TAC must match"
+
+    raw = cmd(f"shell ls -alZ {root_path}/bin/.tac_symlink/*.dll")
+    lines = [l for l in raw.splitlines() if ".ni.dll" not in l]
+    for dll in lines:
+        origin_path = dll.split("->")[1].strip()
+        if not exist(f"{origin_path}"):
+            return "FAIL : The original file of the symbolic link must exist"
+
+    sln_name = "Launcher_TC_TAC_06.Tizen"
+
+    tpk_path = get_tpk_path(tpk_list, f"{sln_name}")
+    if tpk_path == None:
+        return f"FAIL : Get the tpk path for {sln_name}"
+
+    raw = cmd(f"push {tpk_path} /usr/apps/.preload-tpk/")
+    if "1 file(s) pushed. 0 file(s) skipped." in raw:
+        cmd(f"shell install_preload_pkg")
+
+    pkg_id2 = f"org.tizen.example.Launcher_TC_TAC_06.Tizen"
+
+    root_path = get_root_path(f"{pkg_id2}")
+    if root_path == "None":
+        return f"FAIL : Get the root path for {pkg_id2}"
+
+    if not exist(f"{root_path}/bin/.tac_symlink"):
+        return "FAIL : The .tac_symlink folder should exist"
+
+    raw = cmd(f"shell find {root_path}/bin/.tac_symlink/ -name *.dll -not -name *.ni.dll")
+    lines1 = [l for l in raw.splitlines()]
+    raw = cmd(f"shell find {RO_TAC_DIR}Xamarin.Forms/5.0.0.1558-pre3/ -name *.dll -not -name *.ni.dll")
+    lines2 = [l for l in raw.splitlines()]
+    if len(lines1) != len(lines2):
+        return "FAIL : The number of .dll in the .tac_symlink and .dll in the TAC must match"
+
+    raw = cmd(f"shell ls -alZ {root_path}/bin/.tac_symlink/*.dll")
+    lines = [l for l in raw.splitlines() if ".ni.dll" not in l]
+    for dll in lines:
+        origin_path = dll.split("->")[1].strip()
+        if not exist(f"{origin_path}"):
+            return "FAIL : The original file of the symbolic link must exist"
+
+    cmd(f"shell tpk-backend --force-remove --preload -d {pkg_id1}")
+
+    raw = cmd(f"shell find {root_path}/bin/.tac_symlink/ -name *.dll -not -name *.ni.dll")
+    lines1 = [l for l in raw.splitlines()]
+    raw = cmd(f"shell find {RO_TAC_DIR}Xamarin.Forms/5.0.0.1558-pre3/ -name *.dll -not -name *.ni.dll")
+    lines2 = [l for l in raw.splitlines()]
+    if len(lines1) != len(lines2):
+        return "FAIL : The number of .dll in the .tac_symlink and .dll in the TAC must match"
+
+    raw = cmd(f"shell ls -alZ {root_path}/bin/.tac_symlink/*.dll")
+    lines = [l for l in raw.splitlines() if ".ni.dll" not in l]
+    for dll in lines:
+        origin_path = dll.split("->")[1].strip()
+        if not exist(f"{origin_path}"):
+            return "FAIL : The original file of the symbolic link must exist"
+
+    pid = launch_and_get_pid(f"-e", f"{pkg_id2}")
+    if 0 == pid:
+        return f"FAIL : Get the pid for {pkg_id2}"
+
+    raw = cmd(f"shell cat /proc/{pid}/smaps | grep Xamarin.Forms.*.dll")
+    if (f"{RO_TAC_DIR}Xamarin.Forms/5.0.0.1558-pre3/Xamarin.Forms.Platform.Tizen.dll" not in raw) or \
+       (f"{RO_TAC_DIR}Xamarin.Forms/5.0.0.1558-pre3/Xamarin.Forms.Core.dll" not in raw) or \
+       (f"{RO_TAC_DIR}Xamarin.Forms/5.0.0.1558-pre3/Xamarin.Forms.Platform.dll" not in raw):
+        return "FAIL : The Xamarin.Forms in the TAC should be loaded when running the application"
+
+    cmd(f"shell app_launcher -t {pkg_id2}")
+
+    return "PASS"
+
+# The `Launcher_TC_TAC_07` application is normally TAC applied when uninstall.
+def TC_06():
+    sln_name = "Launcher_TC_TAC_07.Tizen"
+
+    tpk_path = get_tpk_path(tpk_list, f"{sln_name}")
+    if tpk_path == None:
+        return f"FAIL : Get the tpk path for {sln_name}"
+
+    raw = cmd(f"push {tpk_path} /usr/apps/.preload-tpk/")
+    if "1 file(s) pushed. 0 file(s) skipped." in raw:
+        cmd(f"shell install_preload_pkg")
+
+    pkg_id = f"org.tizen.example.Launcher_TC_TAC_07.Tizen"
+
+    root_path = get_root_path(f"{pkg_id}")
+    if root_path == "None":
+        return f"FAIL : Get the root path for {pkg_id}"
+
+    if not exist(f"{root_path}/bin/.tac_symlink"):
+        return "FAIL : The .tac_symlink folder should exist"
+
+    raw = cmd(f"shell find {root_path}/bin/.tac_symlink/ -name *.dll -not -name *.ni.dll")
+    lines1 = [l for l in raw.splitlines()]
+    raw = cmd(f"shell find {RO_TAC_DIR}Xamarin.Forms/4.4.0.991864/ -name *.dll -not -name *.ni.dll")
+    lines2 = [l for l in raw.splitlines()]
+    if len(lines1) != len(lines2):
+        return "FAIL : The number of .dll in the .tac_symlink and .dll in the TAC must match"
+
+    raw = cmd(f"shell ls -alZ {root_path}/bin/.tac_symlink/*.dll")
+    lines = [l for l in raw.splitlines() if ".ni.dll" not in l]
+    for dll in lines:
+        origin_path = dll.split("->")[1].strip()
+        if not exist(f"{origin_path}"):
+            return "FAIL : The original file of the symbolic link must exist"
+
+    cmd(f"shell tpk-backend --force-remove --preload -d {pkg_id}")
+
+    if exist(f"{RO_TAC_DIR}Xamarin.Forms/4.4.0.991864/ -name *.dll"):
+        return f"FAIL : The Xamarin.Forms/4.4.0.991864 nuget should not exist in {RO_TAC_DIR}"
+
+    return "PASS"
+
+# The `Launcher_TC_TAC_08` application should be applied to TAC, but The `Launcher_TC_TAC_09` application should not be applied to TAC.
+def TC_07():
+    raw = cmd(f"shell find {FRAMEWORK_DIR}/XSF.*")
+    if "XSF.dll" in raw:
+        cmd(f"shell mv {FRAMEWORK_DIR}/XSF.dll {FRAMEWORK_DIR}/XSF.dll2")
+    elif "XSF.ni.dll" in raw:
+        cmd(f"shell mv {FRAMEWORK_DIR}/XSF.ni.dll {FRAMEWORK_DIR}/XSF.ni.dll2")
+
+    sln_name = "Launcher_TC_TAC_08.Tizen"
+
+    tpk_path = get_tpk_path(tpk_list, f"{sln_name}")
+    if tpk_path == None:
+        return f"FAIL : Get the tpk path for {sln_name}"
+
+    raw = cmd(f"push {tpk_path} /usr/apps/.preload-tpk/")
+    if "1 file(s) pushed. 0 file(s) skipped." in raw:
+        cmd(f"shell install_preload_pkg")
+
+    pkg_id = f"org.tizen.example.Launcher_TC_TAC_08.Tizen"
+
+    root_path = get_root_path(f"{pkg_id}")
+    if root_path == "None":
+        return f"FAIL : Get the root path for {pkg_id}"
+
+    if not exist(f"{root_path}/bin/.tac_symlink"):
+        return "FAIL : The .tac_symlink folder should exist"
+
+    raw = cmd(f"shell find {root_path}/bin/.tac_symlink/ -name *.dll -not -name *.ni.dll")
+    lines1 = [l for l in raw.splitlines()]
+    raw = cmd(f"shell find {RO_TAC_DIR}XSF/1.0.0.0/ -name *.dll -not -name *.ni.dll")
+    lines2 = [l for l in raw.splitlines()]
+    if len(lines1) != len(lines2):
+        return "FAIL : The number of .dll in the .tac_symlink and .dll in the TAC must match"
+
+    raw = cmd(f"shell ls -alZ {root_path}/bin/.tac_symlink/*.dll")
+    lines = [l for l in raw.splitlines() if ".ni.dll" not in l]
+    for dll in lines:
+        origin_path = dll.split("->")[1].strip()
+        if not exist(f"{origin_path}"):
+            return "FAIL : The original file of the symbolic link must exist"
+
+    sln_name = "Launcher_TC_TAC_09.Tizen"
+
+    tpk_path = get_tpk_path(tpk_list, f"{sln_name}")
+    if tpk_path == None:
+        return f"FAIL : Get the tpk path for {sln_name}"
+
+    raw = cmd(f"push {tpk_path} /usr/apps/.preload-tpk/")
+    if "1 file(s) pushed. 0 file(s) skipped." in raw:
+        cmd(f"shell install_preload_pkg")
+
+    pkg_id = f"org.tizen.example.Launcher_TC_TAC_09.Tizen"
+
+    root_path = get_root_path(f"{pkg_id}")
+    if root_path == "None":
+        return f"FAIL : Get the root path for {pkg_id}"
+
+    raw = cmd(f"shell find {root_path}/bin/.tac_symlink -name XSF.dll -not -name XSF.ni.dll")
+    lines = [l for l in raw.splitlines()]
+    if len(lines) != 0:
+        return "FAIL : The version is the same Nuget, but the SHA value is different"
+
+    pid = launch_and_get_pid(f"-e", f"{pkg_id}")
+    if 0 == pid:
+        return f"FAIL : Get the pid for {pkg_id}"
+
+    raw = cmd(f"shell cat /proc/{pid}/smaps | grep XSF.dll")
+    if f"{root_path}/bin/XSF.dll" not in raw:
+        return "FAIL : The XSF in the application should be loaded when running the application"
+
+    cmd(f"shell app_launcher -t {pkg_id}")
+
+    return "PASS"
+
+# The Launcher_TC_TAC_10 application should match the information of nuget with the value of TAC DB.
+def TC_08():
+    sln_name = "Launcher_TC_TAC_10.Tizen"
+
+    tpk_path = get_tpk_path(tpk_list, f"{sln_name}")
+    if tpk_path == None:
+        return f"FAIL : Get the tpk path for {sln_name}"
+
+    raw = cmd(f"push {tpk_path} /usr/apps/.preload-tpk/")
+    if "1 file(s) pushed. 0 file(s) skipped." in raw:
+        cmd(f"shell install_preload_pkg")
+
+    pkg_id = f"org.tizen.example.Launcher_TC_TAC_10.Tizen"
+
+    root_path = get_root_path(f"{pkg_id}")
+    if root_path == "None":
+        return f"FAIL : Get the root path for {pkg_id}"
+
+    if not exist(f"{root_path}/bin/.tac_symlink"):
+        return "FAIL : The .tac_symlink folder should exist"
+
+    raw = subprocess.run((f"sdb -s {serial} shell sqlite3 {RO_TAC_DIR}.TAC.App.list.db").split(), stdout=subprocess.PIPE, input=f"select * from TAC;\n.q\n", encoding="utf-8").stdout
+    lines = [l for l in raw.splitlines() if f"{pkg_id}" in l]
+    for rcd in lines:
+        is_exist = False
+        if ("Xamarin.Forms/4.8.0.1560" in rcd) or \
+           ("Newtonsoft.Json/12.0.3" in rcd) or \
+           ("Google.Apis.Core/1.49.0" in rcd) or \
+           ("Google.Apis/1.49.0" in rcd):
+            is_exist = True
+            continue
+        if not is_exist:
+            return "FAIL : TAC database must have a valid value"
+
+    return "PASS"
+
+# The Launcher_TC_TAC_11 application must match the version of the nuget in .deps.json and the version of the nuget in TAC DB.
+def TC_09():
+    sln_name = "Launcher_TC_TAC_11.Tizen"
+
+    tpk_path = get_tpk_path(tpk_list, f"{sln_name}")
+    if tpk_path == None:
+        return f"FAIL : Get the tpk path for {sln_name}"
+
+    raw = cmd(f"push {tpk_path} /usr/apps/.preload-tpk/")
+    if "1 file(s) pushed. 0 file(s) skipped." in raw:
+        cmd(f"shell install_preload_pkg")
+
+    pkg_id = f"org.tizen.example.Launcher_TC_TAC_11.Tizen"
+
+    root_path = get_root_path(f"{pkg_id}")
+    if root_path == "None":
+        return f"FAIL : Get the root path for {pkg_id}"
+
+    raw = subprocess.run((f"sdb -s {serial} shell sqlite3 {RO_TAC_DIR}.TAC.App.list.db").split(), stdout=subprocess.PIPE, input=f"select * from TAC;\n.q\n", encoding="utf-8").stdout
+    lines = [l for l in raw.splitlines() if f"{pkg_id}" in l]
+    for nuget in lines:
+        name = nuget.split("|")[3]
+        version = nuget.split("|")[4]
+        raw = cmd(f"shell cat {root_path}/{sln_name}.deps.json | grep {name}/")
+        if f"{version}" not in f"{raw}":
+            return "FAIL : "
+
+    return "PASS"
+
+# Run the test
+def run():
+    cmd(f"root on")
+    cmd(f"shell mount -o remount,rw /")
+
+    global tpk_list
+    tpk_list = search_tpk(f"{module_name}")
+
+    pn = run_tc_array(module_name, tc_array)
+    n = int(pn.split(":")[0])
+    f = int(pn.split(":")[1])
+    p = int(pn.split(":")[2])
+    r = 0.0
+    if (len(tc_array) - n) != 0:
+        r = round(((p / (len(tc_array) - n)) * 100), 2)
+    print(f"--- {module_name}_RO TCT Result ---\nNONE : [{n}] / FAIL : [{f}] / PASS : [{p}] - [{r}%]\n")
+
+    with open(f"{RESULT_PATH}", "a+") as file:
+        file.write(f"|   {module_name}_RO  |   {p}  |   {f}  |   {n}  | {r} |\n")
+
+# Uninstall the application and restore to original state
+def clean():
+    cmd(f"shell tpk-backend --force-remove --preload -d org.tizen.example.Launcher_TC_TAC_00.Tizen")
+    cmd(f"shell tpk-backend --force-remove --preload -d org.tizen.example.Launcher_TC_TAC_01.Tizen")
+    cmd(f"shell tpk-backend --force-remove --preload -d org.tizen.example.Launcher_TC_TAC_06.Tizen")
+    cmd(f"shell tpk-backend --force-remove --preload -d org.tizen.example.Launcher_TC_TAC_07.Tizen")
+    cmd(f"shell tpk-backend --force-remove --preload -d org.tizen.example.Launcher_TC_TAC_08.Tizen")
+    cmd(f"shell tpk-backend --force-remove --preload -d org.tizen.example.Launcher_TC_TAC_09.Tizen")
+    cmd(f"shell tpk-backend --force-remove --preload -d org.tizen.example.Launcher_TC_TAC_10.Tizen")
+    cmd(f"shell tpk-backend --force-remove --preload -d org.tizen.example.Launcher_TC_TAC_11.Tizen")
+
+    cmd(f"shell mv {FRAMEWORK_DIR}/XSF.dll2 {FRAMEWORK_DIR}/XSF.dll")
+    cmd(f"shell mv {FRAMEWORK_DIR}/XSF.ni.dll2 {FRAMEWORK_DIR}/XSF.ni.dll")
+
+# Main entry point
+def main():
+    parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter)
+    parser.add_argument("TC_NUMBER", type=str, nargs="*", help="Individual excution")
+    args = parser.parse_args()
+
+    global tc_array
+    if args.TC_NUMBER and "TC_" in args.TC_NUMBER[0]:
+        tc_array = []
+        for tc_num in args.TC_NUMBER:
+            if tc_num not in funcMap:
+                print(f"There is no {tc_num} test.")
+                exit(1)
+            else:
+                tc_array.append(funcMap[tc_num])
+    else:
+        tc_array = [TC_01, TC_02, TC_03, TC_04, TC_05, TC_06, TC_07, TC_08, TC_09]
+
+    global serial
+    if len(sys.argv) >= 2 and "TC_" not in sys.argv[1]:
+        serial = read_serial(sys.argv[1])
+    else:
+        serial = read_serial(None)
+
+    if serial is None:
+        print("No connected device(s).")
+        exit(1)
+
+    device = get_device_type()
+    print(f"=== Dotnet-Launcher [{device}] Test Case - ({module_name}_RO) ===")
+
+    run()
+    clean()
+
+
+funcMap = {
+'TC_01': TC_01, 'TC_02': TC_02, 'TC_03': TC_03, 'TC_04': TC_04, 'TC_05': TC_05, 'TC_06': TC_06, 'TC_07': TC_07, 'TC_08': TC_08, 'TC_09': TC_09,
+'TAC_TC_01': TC_01, 'TAC_TC_02': TC_02, 'TAC_TC_03': TC_03, 'TAC_TC_04': TC_04, 'TAC_TC_05': TC_05,
+'TAC_TC_06': TC_06, 'TAC_TC_07': TC_07, 'TAC_TC_08': TC_08, 'TAC_TC_09': TC_09
+}
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except KeyboardInterrupt:
+        print("\nExit (Pressed Ctrl+C)")
+        exit(1)
similarity index 99%
rename from tests/TCs/4_TAC/TAC.py
rename to tests/TCs/4_TAC/TAC_RW.py
index 8a10c09..cd04125 100755 (executable)
@@ -513,10 +513,10 @@ def run():
     r = 0.0
     if (len(tc_array) - n) != 0:
         r = round(((p / (len(tc_array) - n)) * 100), 2)
-    print(f"--- {module_name} TCT Result ---\nNONE : [{n}] / FAIL : [{f}] / PASS : [{p}] - [{r}%]\n")
+    print(f"--- {module_name}_RW TCT Result ---\nNONE : [{n}] / FAIL : [{f}] / PASS : [{p}] - [{r}%]\n")
 
     with open(f"{RESULT_PATH}", "a+") as file:
-        file.write(f"|    {module_name}    |   {p}  |   {f}  |   {n}  | {r} |\n")
+        file.write(f"|   {module_name}_RW  |   {p}  |   {f}  |   {n}  | {r} |\n")
 
 # Uninstall the application and restore to original state
 def clean():
@@ -561,7 +561,7 @@ def main():
         exit(1)
 
     device = get_device_type()
-    print(f"=== Dotnet-Launcher [{device}] Test Case - ({module_name}) ===")
+    print(f"=== Dotnet-Launcher [{device}] Test Case - ({module_name}_RW) ===")
 
     run()
     clean()
index b867036..b492bc0 100755 (executable)
@@ -10,6 +10,7 @@ RUNTIME_DIR = "/usr/share/dotnet.tizen/netcoreapp/"
 FRAMEWORK_DIR = "/usr/share/dotnet.tizen/framework/"
 PRELOAD_DIR = "/usr/share/dotnet.tizen/preload/"
 IBCDATA_DIR = "/usr/share/dotnet.tizen/ibcdata/"
+RO_TAC_DIR = "/usr/share/dotnet.tizen/tac/"
 DOTNET_DIR = "/opt/usr/dotnet/"
 OWNER_DIR = "/home/owner/"
 SPC_DLL = "System.Private.CoreLib.dll"