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