Add 'Tizen Assembly Cache' feature
[platform/core/dotnet/launcher.git] / NativeLauncher / util / utils.cc
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <dirent.h>
18 #include <fcntl.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21 #include <limits.h>
22 #include <strings.h>
23 #include <pkgmgr-info.h>
24 #include <pkgmgr_installer_info.h>
25
26 #include <cstdlib>
27 #include <cstring>
28 #include <algorithm>
29 #include <unordered_map>
30 #include <vector>
31 #include <iterator>
32 #include <sstream>
33 #include <map>
34
35 #include "log.h"
36 #include "utils.h"
37 #include "path_manager.h"
38
39 static bool iCompare(const std::string& a, int aOffset, const std::string& b, int bOffset, int length)
40 {
41         return static_cast<int>(a.length()) - length >= aOffset &&
42                 static_cast<int>(b.length()) - length >= bOffset &&
43                 std::equal(b.begin() + bOffset, b.begin() + bOffset + length, a.begin() + aOffset,
44                         [](unsigned char a, unsigned char b)
45                         { return std::tolower(a) == std::tolower(b); });
46 }
47
48 bool isManagedAssembly(const std::string& fileName)
49 {
50         return (iCompare(fileName, fileName.size()-4, ".dll", 0, 4) ||
51                         iCompare(fileName, fileName.size()-4, ".exe", 0, 4)) &&
52                         !isNativeImage(fileName);
53 }
54
55 bool isNativeImage(const std::string& fileName)
56 {
57         return iCompare(fileName, fileName.size()-7, ".ni", 0, 3);
58 }
59
60 bool cmdOptionExists(char** begin, char** end, const std::string& option)
61 {
62         return std::find(begin, end, option) != end;
63 }
64
65 std::string readSelfPath()
66 {
67         char buff[PATH_MAX];
68         ssize_t len = ::readlink("/proc/self/exe", buff, sizeof(buff)-1);
69         if (len != -1) {
70                 buff[len] = '\0';
71                 return std::string(buff);
72         }
73
74         return "";
75 }
76
77 std::string concatPath(const std::string& path1, const std::string& path2)
78 {
79         std::string path(path1);
80         if (path.back() == PATH_SEPARATOR) {
81                 path.append(path2);
82         } else {
83                 path += PATH_SEPARATOR;
84                 path.append(path2);
85         }
86
87         return path;
88 }
89
90 void splitPath(const std::string& path, std::vector<std::string>& out)
91 {
92         std::istringstream ss(path);
93         std::string token;
94
95         while (std::getline(ss, token, ':')) {
96                 out.push_back(token);
97         }
98 }
99
100 std::string absolutePath(const std::string& path)
101 {
102         std::string absPath;
103         char realPath[PATH_MAX];
104         if (realpath(path.c_str(), realPath) != nullptr && realPath[0] != '\0')
105                 absPath.assign(realPath);
106
107         return absPath;
108 }
109
110 int getRootPath(std::string pkgId, std::string& rootPath)
111 {
112         int ret = 0;
113         char *path = 0;
114         uid_t uid = 0;
115
116         if (pkgmgr_installer_info_get_target_uid(&uid) < 0) {
117                 _ERR("Failed to get UID");
118                 return -1;
119         }
120
121         pkgmgrinfo_pkginfo_h handle;
122         if (uid == 0) {
123                 ret = pkgmgrinfo_pkginfo_get_pkginfo(pkgId.c_str(), &handle);
124                 if (ret != PMINFO_R_OK) {
125                         return -1;
126                 }
127         } else {
128                 ret = pkgmgrinfo_pkginfo_get_usr_pkginfo(pkgId.c_str(), uid, &handle);
129                 if (ret != PMINFO_R_OK) {
130                         return -1;
131                 }
132         }
133
134         ret = pkgmgrinfo_pkginfo_get_root_path(handle, &path);
135         if (ret != PMINFO_R_OK) {
136                 pkgmgrinfo_pkginfo_destroy_pkginfo(handle);
137                 return -1;
138         }
139         rootPath = path;
140         pkgmgrinfo_pkginfo_destroy_pkginfo(handle);
141         return 0;
142 }
143
144 std::string baseName(const std::string& path)
145 {
146         auto pos = path.find_last_of(PATH_SEPARATOR);
147         if (pos != std::string::npos)
148                 return path.substr(0, pos);
149         else
150                 return std::string(".");
151         return path;
152 }
153
154 bool isFileExist(const std::string& path)
155 {
156         struct stat sb;
157         return stat(path.c_str(), &sb) == 0;
158 }
159
160 std::string stripNiDLL(const std::string& path)
161 {
162         std::string niPath(path);
163         if (path.size() < 5) return niPath;
164         if (!strncasecmp(path.c_str() + path.size() - 4, ".dll", 4))
165                 niPath = path.substr(0, path.size()-4);
166         else if (!strncasecmp(path.c_str() + path.size() - 4, ".exe", 4))
167                 niPath = path.substr(0, path.size()-4);
168
169         if (!strncasecmp(niPath.c_str() + niPath.size() - 3, ".ni", 3))
170                 return niPath.substr(0, niPath.size()-3);
171
172         return niPath;
173 }
174
175 void assembliesInDirectory(const std::vector<std::string>& directories, std::string& tpaList)
176 {
177         std::map<std::string, std::string> assemblyList;
178         std::map<std::string, std::string> tmpList;
179
180         auto reader = [&assemblyList, &tmpList] (const std::string& path, const char* name) {
181                 if (isManagedAssembly(path) || isNativeImage(path)) {
182                         std::string dllName = stripNiDLL(name);
183                         std::pair<std::map<std::string, std::string>::iterator, bool> ret;
184                         ret = tmpList.insert(std::pair<std::string, std::string>(dllName, path));
185                         if (ret.second == false) {
186                                 if (isNativeImage(path))
187                                         tmpList[dllName] = path;
188                         }
189                 }
190         };
191
192         for (auto directory : directories) {
193                 scanFilesInDir(directory.c_str(), reader, 1);
194                 // merge scaned dll list to tpa list.
195                 // if the dll is already exist in the list, that is skipped.
196                 assemblyList.insert(tmpList.begin(), tmpList.end());
197         }
198
199         std::map<std::string, std::string>::iterator it;
200         for (it = assemblyList.begin(); it != assemblyList.end(); it++)
201                 tpaList += it->second + ':';
202
203         if (tpaList.back() == ':')
204                 tpaList.pop_back();
205 }
206
207 void scanFilesInDir(const std::string& directory, FileReader reader, unsigned int depth)
208 {
209         DIR *dir;
210         struct dirent* entry;
211         bool isDir;
212
213         if (strstr(directory.c_str(), ".TAC.Release") != NULL)
214                 return; // skip nitool --regen-all-app (--r2r)
215
216         dir = opendir(directory.c_str());
217
218         if (dir == nullptr)
219                 return;
220
221         std::vector<std::string> innerDirectories;
222
223         while ((entry = readdir(dir)) != nullptr) {
224                 isDir = false;
225                 std::string path = concatPath(directory, entry->d_name);
226                 switch (entry->d_type) {
227                         case DT_REG: break;
228                         case DT_DIR:
229                                 isDir = true;
230                                 break;
231                         case DT_LNK:
232                         case DT_UNKNOWN:
233                                 struct stat sb;
234                                 if (stat(path.c_str(), &sb) == -1)
235                                         continue;
236
237                                 if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode))
238                                         break;
239                         default:
240                                 continue;
241                 }
242                 if (!isDir)
243                         reader(path, entry->d_name);
244                 else if (depth > 1 && strcmp(entry->d_name, ".") && strcmp(entry->d_name, ".."))
245                         innerDirectories.push_back(path);
246         }
247
248         if (depth != 0)
249                 for (auto& d : innerDirectories)
250                         scanFilesInDir(d.c_str(), reader, depth - 1);
251
252         closedir(dir);
253 }
254
255 static bool setOwnership(const bf::path& path, uid_t uid, gid_t gid) {
256         int fd = open(path.c_str(), O_RDONLY);
257         if (fd < 0) {
258                 _ERR("Can't open directory: %s", path.c_str());
259                 return false;
260         }
261         int ret = fchown(fd, uid, gid);
262         close(fd);
263         if (ret != 0) {
264                 _ERR("Failed to change owner of: %s", path.c_str());
265                 return false;
266         }
267         return true;
268 }
269
270 static bool setDirPermissions(const bf::path& path, bf::perms permissions) {
271         bs::error_code error;
272         bf::permissions(path, permissions, error);
273         if (error) {
274                 _ERR("Failed to set permissions for directory: %s, %s", path.c_str(), error.message().c_str());
275                 return false;
276         }
277         return true;
278 }
279
280 static bool setDirOwnershipAndPermissions(const bf::path& path, bf::perms permissions, uid_t uid, gid_t gid) {
281         if (!setOwnership(path, uid, gid)) {
282                 _ERR("Failed to change owner: %s, (uid: %d, gid: %d)", path.c_str(), uid, gid);
283                 return false;
284         }
285         if (!setDirPermissions(path, permissions)) {
286                 _ERR("Failed to change permission: %s, (%d)", path.c_str(), permissions);
287                 return false;
288         }
289         return true;
290 }
291
292 static bool copyOwnershipAndPermissions(const bf::path& path, const bf::path& path2) {
293         if (!bf::exists(path)) {
294                 _ERR("Failed to copy ownership and permissions from %s to %s", path.c_str(), path2.c_str());
295                 return false;
296         }
297         bf::perms permissions = bf::status(path).permissions();
298         struct stat stats;
299         if (stat(path.c_str(), &stats) != 0) {
300                 return false;
301         }
302         if (!setDirOwnershipAndPermissions(path2, permissions, stats.st_uid, stats.st_gid)) {
303                 _ERR("Failed to copy ownership and permissions from %s to %s", path.c_str(), path2.c_str());
304                 return false;
305         }
306         return true;
307 }
308
309 bool createDir(const bf::path& path) {
310         if (bf::exists(path)) {
311                 return true;
312         }
313         bs::error_code error;
314         bf::create_directories(path, error);
315         if (error) {
316                 _ERR("Failed to create directory: %s", error.message().c_str());
317                 return false;
318         }
319         return true;
320 }
321
322 bool copyDir(const bf::path& path1, const bf::path& path2, FSFlag flags) {
323         try {
324                 // Check whether the function call is valid
325                 if (!bf::exists(path1) || !bf::is_directory(path1)) {
326                         _ERR("Source directory %s does not exist or is not a directory", path1.c_str());
327                         return false;
328                 }
329                 if (!bf::exists(path2)) {
330                         // Create the destination directory
331                         if (!createDir(path2)) {
332                                 _ERR("Unable to create destination directory %s", path2.c_str());
333                                 return false;
334                         }
335                         if (flags & FS_PRESERVE_OWNERSHIP_AND_PERMISSIONS) {
336                                 copyOwnershipAndPermissions(path1, path2);
337                         }
338                 } else {
339                         if (!(flags & (FS_MERGE_SKIP | FS_MERGE_OVERWRITE))) {
340                                 _ERR("Destination directory %s already exists", path2.c_str());
341                                 return false;
342                         }
343                         if (flags & (FS_MERGE_OVERWRITE | FS_PRESERVE_OWNERSHIP_AND_PERMISSIONS)) {
344                                 copyOwnershipAndPermissions(path1, path2);
345                         }
346                 }
347         } catch (const bf::filesystem_error& error) {
348                 _ERR("Failed to copy directory: %s", error.what());
349                 return false;
350         }
351
352         // Iterate through the source directory
353         for (bf::directory_iterator file(path1); file != bf::directory_iterator(); ++file) {
354                 try {
355                         bf::path current(file->path());
356                         bf::path target = path2 / current.filename();
357                         if (bf::is_symlink(symlink_status(current))) {
358                                 if ((flags & (FS_MERGE_SKIP | FS_MERGE_OVERWRITE)) && bf::exists(target)) {
359                                         continue;
360                                 }
361                                 bs::error_code error;
362                                 bf::copy_symlink(current, target, error);
363                                 if (error) {
364                                         _ERR("Failed to copy symlink: %s, %s", current.c_str(), error.message().c_str());
365                                         return false;
366                                 }
367                         } else if (bf::is_directory(current)) {
368                                 // Found directory: Recursion
369                                 if (!copyDir(current, target, flags)) {
370                                         return false;
371                                 }
372                         } else {
373                                 if ((flags & FS_MERGE_SKIP) && bf::exists(target)) {
374                                         continue;
375                                 }
376                                 bf::path destination = target;
377                                 if (flags & FS_COMMIT_COPY_FILE) {
378                                         destination = bf::unique_path(target.parent_path() / "%%%%-%%%%-%%%%-%%%%");
379                                 }
380                                 if (flags & FS_MERGE_OVERWRITE) {
381                                         bf::copy_file(current, destination, bf::copy_option::overwrite_if_exists);
382                                 } else {
383                                         bf::copy_file(current, destination);
384                                 }
385                                 if (flags & FS_PRESERVE_OWNERSHIP_AND_PERMISSIONS) {
386                                         copyOwnershipAndPermissions(current, destination);
387                                 }
388                                 if (flags & FS_COMMIT_COPY_FILE) {
389                                         if (flags & FS_MERGE_OVERWRITE) {
390                                                 bf::remove(target);
391                                         }
392                                         bf::rename(destination, target);
393                                 }
394                         }
395                 } catch (const bf::filesystem_error& error) {
396                         _ERR("Failed to copy directory: %s", error.what());
397                         return false;
398                 }
399         }
400         return true;
401 }
402
403 bool copyFile(const bf::path& path1, const bf::path& path2) {
404         bs::error_code error;
405         bf::copy_file(path1, path2, bf::copy_option::overwrite_if_exists, error);
406         if (error) {
407                 _ERR("copy file %s due to error [%s]", path1.c_str(), error.message().c_str());
408                 return false;
409         }
410         return true;
411 }
412
413 bool moveFile(const bf::path& path1, const bf::path& path2) {
414         if (bf::exists(path2)) {
415                 return false;
416         }
417         bs::error_code error;
418         bf::rename(path1, path2, error);
419         if (error) {
420                 _ERR("Cannot move file: %s. Will copy/remove... with error [%s]", path1.c_str(), error.message().c_str());
421                 bf::copy_file(path1, path2, bf::copy_option::overwrite_if_exists, error);
422                 if (error) {
423                         _ERR("Cannot copy file %s due to error [%s]", path1.c_str(), error.message().c_str());
424                         return false;
425                 }
426                 bf::remove_all(path1, error);
427                 if (error) {
428                         _ERR("Cannot remove old file when coping: %s with error [%s]", path1.c_str(), error.message().c_str());
429                         return false;
430                 }
431         }
432         return true;
433 }
434
435 bool removeFile(const bf::path& path) {
436         if (!bf::exists(path)) {
437                 return true;
438         }
439         bs::error_code error;
440         bf::remove(path, error);
441         if (error) {
442                 _ERR("Cannot remove: %s, %s", path.c_str(), error.message().c_str());
443                 return false;
444         }
445         return true;
446 }
447
448 bool removeAll(const bf::path& path) {
449         if (!exists(path)) {
450                 return true;
451         }
452         bs::error_code error;
453         bf::remove_all(path, error);
454         if (error) {
455                 _ERR("Cannot remove: %s, %s", path.c_str(), error.message().c_str());
456                 return false;
457         }
458         return true;
459 }