Refine getting security install type at PrepareRequest
[platform/core/appfw/app-installers.git] / src / common / utils / file_util.cc
1 /* 2014, Copyright © Intel Coporation, license APACHE-2.0, see LICENSE file */
2
3 #include "common/utils/file_util.h"
4
5 #include <fcntl.h>
6 #include <sys/stat.h>
7 #include <linux/limits.h>
8 #include <unzip.h>
9 #include <zlib.h>
10
11 #include <boost/algorithm/string/classification.hpp>
12 #include <boost/algorithm/string/split.hpp>
13 #include <boost/filesystem/operations.hpp>
14 #include <boost/filesystem/path.hpp>
15 #include <boost/system/error_code.hpp>
16
17 #include <manifest_parser/utils/logging.h>
18
19 #include <algorithm>
20 #include <string>
21 #include <vector>
22
23 #include "common/utils/byte_size_literals.h"
24
25 namespace ba = boost::algorithm;
26 namespace bs = boost::system;
27 namespace bf = boost::filesystem;
28
29 namespace {
30
31 unsigned kZipBufSize = 8_kB;
32 unsigned kZipMaxPath = PATH_MAX;
33
34 int64_t GetBlockSizeForPath(const bf::path& path_in_partition) {
35   struct stat stats;
36   if (stat(path_in_partition.string().c_str(), &stats)) {
37     LOG(ERROR) << "stat(" << path_in_partition.string()
38                << ") failed - error code: " << errno;
39     return -1;
40   }
41   return stats.st_blksize;
42 }
43
44 int64_t RoundUpToBlockSizeOf(int64_t size, int64_t block_size) {
45   return ((size + block_size - 1) / block_size) * block_size;
46 }
47
48 class UnzFilePointer {
49  public:
50   UnzFilePointer()
51     : zipFile_(nullptr),
52       fileOpened_(false),
53       currentFileOpened_(false) { }
54
55   ~UnzFilePointer() {
56     if (currentFileOpened_)
57       unzCloseCurrentFile(zipFile_);
58     if (fileOpened_)
59       unzClose(zipFile_);
60   }
61
62   bool Open(const char* zip_path) {
63     zipFile_ = static_cast<unzFile*>(unzOpen(zip_path));
64     if (!zipFile_)
65        return false;
66     fileOpened_ = true;
67     return true;
68   }
69
70   bool OpenCurrent() {
71     if (unzOpenCurrentFile(zipFile_) != UNZ_OK)
72       return false;
73     currentFileOpened_ = true;
74     return true;
75   }
76
77   void CloseCurrent() {
78     if (currentFileOpened_)
79       unzCloseCurrentFile(zipFile_);
80     currentFileOpened_ = false;
81   }
82
83   unzFile* Get() { return zipFile_; }
84
85  private:
86   unzFile* zipFile_;
87   bool fileOpened_;
88   bool currentFileOpened_;
89 };
90
91 }  // namespace
92
93 namespace common_installer {
94
95 bool CreateDir(const bf::path& path) {
96   if (bf::exists(path))
97     return true;
98
99   boost::system::error_code error;
100   bf::create_directories(path, error);
101
102   if (error) {
103     LOG(ERROR) << "Failed to create directory: "
104                << boost::system::system_error(error).what();
105     return false;
106   }
107   return true;
108 }
109
110 bool SetDirPermissions(const boost::filesystem::path& path,
111                       boost::filesystem::perms permissions) {
112   boost::system::error_code error;
113   bf::permissions(path, permissions, error);
114
115   if (error) {
116     LOG(ERROR) << "Failed to set permissions for directory: " << path
117                << boost::system::system_error(error).what();
118     return false;
119   }
120   return true;
121 }
122
123 bool CopyDir(const bf::path& src, const bf::path& dst,
124              FSFlag flags, bool skip_symlink) {
125   try {
126     // Check whether the function call is valid
127     if (!bf::exists(src) || !bf::is_directory(src)) {
128       LOG(ERROR) << "Source directory " << src
129                  << " does not exist or is not a directory.";
130       return false;
131     }
132     if (!bf::exists(dst)) {
133       // Create the destination directory
134       if (!CreateDir(dst)) {
135         LOG(ERROR) << "Unable to create destination directory" << dst;
136         return false;
137       }
138     } else {
139       if (!(flags & FS_MERGE_DIRECTORIES)) {
140         LOG(ERROR) << "Destination directory " << dst.string()
141                    << " already exists.";
142         return false;
143       }
144     }
145   } catch (const bf::filesystem_error& error) {
146     LOG(ERROR) << "Failed to copy directory: " << error.what();
147     return false;
148   }
149
150   // Iterate through the source directory
151   for (bf::directory_iterator file(src);
152       file != bf::directory_iterator();
153       ++file) {
154     try {
155       bf::path current(file->path());
156       bf::path target = dst / current.filename();
157
158       if (bf::is_symlink(symlink_status(current))) {
159         if (skip_symlink)
160           continue;
161         if ((flags & FS_MERGE_DIRECTORIES) && bf::exists(target))
162           continue;
163         bf::copy_symlink(current, target);
164       } else if (bf::is_directory(current)) {
165         // Found directory: Recursion
166         if (!CopyDir(current, target, flags, skip_symlink)) {
167           return false;
168         }
169       } else {
170         if ((flags & FS_MERGE_DIRECTORIES) && bf::exists(target))
171           continue;
172         bf::copy_file(current, target);
173       }
174     } catch (const bf::filesystem_error& error) {
175       LOG(ERROR) << "Failed to copy directory: " << error.what();
176       return false;
177     }
178   }
179   return true;
180 }
181
182 bool CopyFile(const bf::path& src, const bf::path& dst) {
183   bs::error_code error;
184
185   bf::copy_file(src, dst, bf::copy_option::overwrite_if_exists, error);
186   if (error) {
187     LOG(WARNING) << "copy file " << src << " due to error [" << error << "]";
188     return false;
189   }
190   return true;
191 }
192
193 bool MoveDir(const bf::path& src, const bf::path& dst, FSFlag flags) {
194   if (bf::exists(dst) && !(flags & FS_MERGE_DIRECTORIES)) {
195     LOG(ERROR) << "Destination directory does exist: " << dst;
196     return false;
197   }
198
199   bs::error_code error;
200   bf::rename(src, dst, error);
201   if (error) {
202     LOG(WARNING) << "Cannot move directory: " << src << ". Will copy/remove...";
203     if (!CopyDir(src, dst, flags, false)) {
204       LOG(ERROR) << "Cannot copy directory: " << src;
205       return false;
206     }
207     bf::remove_all(src, error);
208     if (error) {
209       LOG(ERROR) << "Cannot remove old directory when coping: " << src;
210       return false;
211     }
212   }
213   return true;
214 }
215
216 bool MoveFile(const bf::path& src, const bf::path& dst) {
217   if (bf::exists(dst))
218     return false;
219   bs::error_code error;
220   bf::rename(src, dst, error);
221   if (error) {
222     LOG(WARNING) << "Cannot move file: " << src <<
223         ". Will copy/remove... with error [" << error << "]";
224     bf::copy_file(src, dst, bf::copy_option::overwrite_if_exists, error);
225     if (error) {
226       LOG(WARNING) << "Cannot copy file " << src <<
227           " due to error [" << error << "]";
228       return false;
229     }
230     bf::remove_all(src, error);
231     if (error) {
232       LOG(ERROR) << "Cannot remove old file when coping: " << src <<
233           "with error [" << error << "]";
234     }
235   }
236   return true;
237 }
238
239 int64_t GetUnpackedPackageSize(const bf::path& path) {
240   int64_t size = 0;
241   int64_t block_size = GetBlockSizeForPath(path);
242
243   // if failed to stat path
244   if (block_size == -1)
245     return -1;
246
247   unz_global_info info;
248   unz_file_info64 raw_file_info;
249   char raw_file_name_in_zip[kZipMaxPath];
250
251   unzFile* zip_file = static_cast<unzFile*>(unzOpen(path.string().c_str()));
252   if (zip_file == nullptr) {
253     LOG(ERROR) << "Failed to open the source dir: " << path.string();
254     return -1;
255   }
256
257   if (unzGetGlobalInfo(zip_file, &info) != UNZ_OK) {
258     LOG(ERROR) << "Failed to read global info";
259     unzClose(zip_file);
260     return -1;
261   }
262
263   for (uLong i = 0; i < info.number_entry; i++) {
264     if (unzGetCurrentFileInfo64(zip_file, &raw_file_info, raw_file_name_in_zip,
265         sizeof(raw_file_name_in_zip), nullptr, 0, nullptr, 0) != UNZ_OK) {
266       LOG(ERROR) << "Failed to read file info";
267       unzClose(zip_file);
268       return -1;
269     }
270     size += RoundUpToBlockSizeOf(raw_file_info.uncompressed_size, block_size);
271     unzGoToNextFile(zip_file);
272   }
273
274   // FIXME: calculate space needed for directories
275   unzClose(zip_file);
276   return size;
277 }
278
279 int64_t GetDirectorySize(const boost::filesystem::path& path) {
280   int64_t block_size = GetBlockSizeForPath(path);
281
282   // if failed to stat path
283   if (block_size == -1)
284     return -1;
285
286   int64_t size = 0;
287   for (bf::recursive_directory_iterator iter(path);
288       iter != bf::recursive_directory_iterator(); ++iter) {
289       struct stat buf;
290       if (lstat(iter->path().c_str(), &buf) == -1) {
291         LOG(ERROR) << "lstat() failed for: " << iter->path();
292         return -1;
293       }
294       size += RoundUpToBlockSizeOf(buf.st_size, block_size);
295   }
296
297   // FIXME: block size for external device may differ...
298   return size;
299 }
300
301 boost::filesystem::path GenerateTmpDir(const bf::path &app_path) {
302   boost::filesystem::path install_tmp_dir;
303   boost::filesystem::path tmp_dir(app_path);
304
305   do {
306     boost::filesystem::path model;
307     boost::filesystem::path unique_dir =
308         boost::filesystem::unique_path(model = "unpack-%%%%%%");
309
310     install_tmp_dir = tmp_dir /= unique_dir;
311   } while (boost::filesystem::exists(install_tmp_dir) &&
312            boost::filesystem::is_directory(install_tmp_dir));
313
314   return install_tmp_dir;
315 }
316
317 boost::filesystem::path GenerateTemporaryPath(
318     const boost::filesystem::path& path) {
319   bf::path pattern = path;
320   pattern += "-%%%%%%";
321   bf::path tmp_path;
322   do {
323     tmp_path = boost::filesystem::unique_path(pattern);
324   } while (boost::filesystem::exists(tmp_path));
325   return tmp_path;
326 }
327
328 bool ExtractToTmpDir(const char* zip_path,
329                      const boost::filesystem::path& tmp_dir) {
330   return ExtractToTmpDir(zip_path, tmp_dir, "");
331 }
332
333 bool ExtractToTmpDir(const char* zip_path, const bf::path& tmp_dir,
334                      const std::string& filter_prefix) {
335   unz_global_info info;
336   char read_buffer[kZipBufSize];
337   unz_file_info raw_file_info;
338   char raw_file_name_in_zip[kZipMaxPath];
339
340   current_path(tmp_dir);
341
342   UnzFilePointer zip_file;
343   if (!zip_file.Open(zip_path)) {
344     LOG(ERROR) << "Failed to open the source dir: " << zip_path;
345     return false;
346   }
347
348   if (unzGetGlobalInfo(zip_file.Get(), &info) != UNZ_OK) {
349     LOG(ERROR) << "Failed to read global info";
350     return false;
351   }
352
353   for (uLong i = 0; i < info.number_entry; i++) {
354     if (unzGetCurrentFileInfo(zip_file.Get(),
355                               &raw_file_info,
356                               raw_file_name_in_zip,
357                               sizeof(raw_file_name_in_zip),
358                               nullptr,
359                               0,
360                               nullptr,
361                               0) != UNZ_OK) {
362       LOG(ERROR) << "Failed to read file info";
363       return false;
364     }
365
366     if (raw_file_name_in_zip[0] == '\0')
367       return false;
368
369     // unpack if filter is empty or path is matched
370     if (filter_prefix.empty() ||
371         std::string(raw_file_name_in_zip).find(filter_prefix) == 0) {
372       bf::path filename_in_zip_path(raw_file_name_in_zip);
373
374       // prevent "directory climbing" attack
375       if (HasDirectoryClimbing(filename_in_zip_path)) {
376         LOG(ERROR) << "Relative path in widget in malformed";
377         return false;
378       }
379
380       if (!filename_in_zip_path.parent_path().empty()) {
381         if (!CreateDir(filename_in_zip_path.parent_path())) {
382           LOG(ERROR) << "Failed to create directory: "
383               << filename_in_zip_path.parent_path();
384           return false;
385         }
386       }
387
388       if (!zip_file.OpenCurrent()) {
389         LOG(ERROR) << "Failed to open file";
390         return false;
391       }
392
393       if (!is_directory(filename_in_zip_path)) {
394         FILE *out = fopen(raw_file_name_in_zip, "wb");
395         if (!out) {
396           LOG(ERROR) << "Failed to open destination ";
397           return false;
398         }
399
400         int ret = UNZ_OK;
401         do {
402           ret = unzReadCurrentFile(zip_file.Get(), read_buffer, kZipBufSize);
403           if (ret < 0) {
404             LOG(ERROR) << "Failed to read data: " << ret;
405             return false;
406           } else {
407             fwrite(read_buffer, sizeof(char), ret, out);
408           }
409         } while (ret > 0);
410
411         fclose(out);
412       }
413
414       zip_file.CloseCurrent();
415     }
416
417     if ((i+1) < info.number_entry) {
418       if (unzGoToNextFile(zip_file.Get()) != UNZ_OK) {
419         LOG(ERROR) << "Failed to read next file";
420         return false;
421       }
422     }
423   }
424
425   return true;
426 }
427
428 bool CheckPathInZipArchive(const char* zip_archive_path,
429                            const boost::filesystem::path& relative_zip_path,
430                            bool* found) {
431   *found = false;
432   UnzFilePointer zip_file;
433   if (!zip_file.Open(zip_archive_path)) {
434     LOG(ERROR) << "Failed to open the source dir: " << zip_archive_path;
435     return false;
436   }
437
438   unz_global_info info;
439   if (unzGetGlobalInfo(zip_file.Get(), &info) != UNZ_OK) {
440     LOG(ERROR) << "Failed to read global info";
441     return false;
442   }
443
444   char raw_file_name_in_zip[kZipMaxPath];
445   unz_file_info raw_file_info;
446   for (uLong i = 0; i < info.number_entry; i++) {
447     if (unzGetCurrentFileInfo(zip_file.Get(),
448                               &raw_file_info,
449                               raw_file_name_in_zip,
450                               sizeof(raw_file_name_in_zip),
451                               nullptr,
452                               0,
453                               nullptr,
454                               0) != UNZ_OK) {
455       LOG(ERROR) << "Failed to read file info";
456       return false;
457     }
458
459     if (raw_file_name_in_zip[0] == '\0')
460       return false;
461
462     if (relative_zip_path.string() == raw_file_name_in_zip) {
463       *found = true;
464       return true;
465     }
466
467     if ((i + 1) < info.number_entry) {
468       if (unzGoToNextFile(zip_file.Get()) != UNZ_OK) {
469         LOG(ERROR) << "Failed to read next file";
470         return false;
471       }
472     }
473   }
474
475   return true;
476 }
477
478 bool HasDirectoryClimbing(const boost::filesystem::path& path) {
479   std::vector<std::string> segments;
480   ba::split(segments, path.string(), ba::is_any_of("/\\"));
481   return std::any_of(segments.begin(), segments.end(),
482                   [](const std::string& segment) {
483                     return segment == "..";
484                   });
485 }
486
487 boost::filesystem::path MakeRelativePath(const boost::filesystem::path& input,
488                                          const boost::filesystem::path& base) {
489   bf::path input_absolute = bf::absolute(input);
490   bf::path base_absolute = bf::absolute(base);
491   return input_absolute.string().substr(base_absolute.string().length() + 1);
492 }
493
494 bool IsSubDir(const boost::filesystem::path& path,
495     const boost::filesystem::path& root) {
496   boost::filesystem::path p = path;
497   while (p != boost::filesystem::path()) {
498     if (bf::equivalent(p, root))
499       return true;
500     else
501       p = p.parent_path();
502   }
503   return false;
504 }
505
506 }  // namespace common_installer