Fix undo of StepCopyBackup
[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 SetOwnership(const bf::path& path, uid_t uid, gid_t gid) {
96   int fd = open(path.c_str(), O_RDONLY);
97   if (fd < 0) {
98     LOG(ERROR) << "Can't open directory : " << path;
99     return false;
100   }
101   int ret = fchown(fd, uid, gid);
102   close(fd);
103   if (ret != 0) {
104     LOG(ERROR) << "Failed to change owner of: " << path;
105     return false;
106   }
107   return true;
108 }
109
110 bool SetOwnershipAll(const bf::path& path, uid_t uid, gid_t gid) {
111   if (!SetOwnership(path, uid, gid))
112     return false;
113   if (!bf::is_directory(path))
114     return true;
115   for (bf::recursive_directory_iterator iter(path);
116     iter != bf::recursive_directory_iterator();
117     ++iter) {
118     bf::path current(iter->path());
119     if (bf::is_symlink(symlink_status(current)))
120       continue;
121     if (!SetOwnership(current, uid, gid))
122       return false;
123   }
124   return true;
125 }
126
127 bool CreateDir(const bf::path& path) {
128   if (bf::exists(path))
129     return true;
130
131   boost::system::error_code error;
132   bf::create_directories(path, error);
133
134   if (error) {
135     LOG(ERROR) << "Failed to create directory: "
136                << boost::system::system_error(error).what();
137     return false;
138   }
139   return true;
140 }
141
142 bool SetDirPermissions(const boost::filesystem::path& path,
143                       boost::filesystem::perms permissions) {
144   boost::system::error_code error;
145   bf::permissions(path, permissions, error);
146
147   if (error) {
148     LOG(ERROR) << "Failed to set permissions for directory: " << path
149                << boost::system::system_error(error).what();
150     return false;
151   }
152   return true;
153 }
154
155 bool CopyDir(const bf::path& src, const bf::path& dst,
156              FSFlag flags, bool skip_symlink) {
157   try {
158     // Check whether the function call is valid
159     if (!bf::exists(src) || !bf::is_directory(src)) {
160       LOG(ERROR) << "Source directory " << src
161                  << " does not exist or is not a directory.";
162       return false;
163     }
164     if (!bf::exists(dst)) {
165       // Create the destination directory
166       if (!CreateDir(dst)) {
167         LOG(ERROR) << "Unable to create destination directory" << dst;
168         return false;
169       }
170     } else {
171       if (!(flags & FS_MERGE_DIRECTORIES)) {
172         LOG(ERROR) << "Destination directory " << dst.string()
173                    << " already exists.";
174         return false;
175       }
176     }
177   } catch (const bf::filesystem_error& error) {
178     LOG(ERROR) << "Failed to copy directory: " << error.what();
179     return false;
180   }
181
182   // Iterate through the source directory
183   for (bf::directory_iterator file(src);
184       file != bf::directory_iterator();
185       ++file) {
186     try {
187       bf::path current(file->path());
188       bf::path target = dst / current.filename();
189
190       if (bf::is_symlink(symlink_status(current))) {
191         if (skip_symlink)
192           continue;
193         if ((flags & FS_MERGE_DIRECTORIES) && bf::exists(target))
194           continue;
195         bf::copy_symlink(current, target);
196       } else if (bf::is_directory(current)) {
197         // Found directory: Recursion
198         if (!CopyDir(current, target, flags, skip_symlink)) {
199           return false;
200         }
201       } else {
202         if ((flags & FS_MERGE_DIRECTORIES) && bf::exists(target))
203           continue;
204         bf::copy_file(current, target);
205       }
206     } catch (const bf::filesystem_error& error) {
207       LOG(ERROR) << "Failed to copy directory: " << error.what();
208       return false;
209     }
210   }
211   return true;
212 }
213
214 bool CopyFile(const bf::path& src, const bf::path& dst) {
215   bs::error_code error;
216
217   bf::copy_file(src, dst, bf::copy_option::overwrite_if_exists, error);
218   if (error) {
219     LOG(WARNING) << "copy file " << src << " due to error [" << error << "]";
220     return false;
221   }
222   return true;
223 }
224
225 bool MoveDir(const bf::path& src, const bf::path& dst, FSFlag flags) {
226   if (bf::exists(dst) && !(flags & FS_MERGE_DIRECTORIES)) {
227     LOG(ERROR) << "Destination directory does exist: " << dst;
228     return false;
229   }
230
231   bs::error_code error;
232   bf::rename(src, dst, error);
233   if (error) {
234     LOG(WARNING) << "Cannot move directory: " << src << ". Will copy/remove...";
235     if (!CopyDir(src, dst, flags, false)) {
236       LOG(ERROR) << "Cannot copy directory: " << src;
237       return false;
238     }
239     bf::remove_all(src, error);
240     if (error) {
241       LOG(ERROR) << "Cannot remove old directory when coping: " << src;
242       return false;
243     }
244   }
245   return true;
246 }
247
248 bool MoveFile(const bf::path& src, const bf::path& dst) {
249   if (bf::exists(dst))
250     return false;
251   bs::error_code error;
252   bf::rename(src, dst, error);
253   if (error) {
254     LOG(WARNING) << "Cannot move file: " << src <<
255         ". Will copy/remove... with error [" << error << "]";
256     bf::copy_file(src, dst, bf::copy_option::overwrite_if_exists, error);
257     if (error) {
258       LOG(WARNING) << "Cannot copy file " << src <<
259           " due to error [" << error << "]";
260       return false;
261     }
262     bf::remove_all(src, error);
263     if (error) {
264       LOG(ERROR) << "Cannot remove old file when coping: " << src <<
265           "with error [" << error << "]";
266     }
267   }
268   return true;
269 }
270
271 int64_t GetUnpackedPackageSize(const bf::path& path) {
272   int64_t size = 0;
273   int64_t block_size = GetBlockSizeForPath(path);
274
275   // if failed to stat path
276   if (block_size == -1)
277     return -1;
278
279   unz_global_info info;
280   unz_file_info64 raw_file_info;
281   char raw_file_name_in_zip[kZipMaxPath];
282
283   unzFile* zip_file = static_cast<unzFile*>(unzOpen(path.string().c_str()));
284   if (zip_file == nullptr) {
285     LOG(ERROR) << "Failed to open the source dir: " << path.string();
286     return -1;
287   }
288
289   if (unzGetGlobalInfo(zip_file, &info) != UNZ_OK) {
290     LOG(ERROR) << "Failed to read global info";
291     unzClose(zip_file);
292     return -1;
293   }
294
295   for (uLong i = 0; i < info.number_entry; i++) {
296     if (unzGetCurrentFileInfo64(zip_file, &raw_file_info, raw_file_name_in_zip,
297         sizeof(raw_file_name_in_zip), nullptr, 0, nullptr, 0) != UNZ_OK) {
298       LOG(ERROR) << "Failed to read file info";
299       unzClose(zip_file);
300       return -1;
301     }
302     size += RoundUpToBlockSizeOf(raw_file_info.uncompressed_size, block_size);
303     unzGoToNextFile(zip_file);
304   }
305
306   // FIXME: calculate space needed for directories
307   unzClose(zip_file);
308   return size;
309 }
310
311 int64_t GetDirectorySize(const boost::filesystem::path& path) {
312   int64_t block_size = GetBlockSizeForPath(path);
313
314   // if failed to stat path
315   if (block_size == -1)
316     return -1;
317
318   int64_t size = 0;
319   for (bf::recursive_directory_iterator iter(path);
320       iter != bf::recursive_directory_iterator(); ++iter) {
321       struct stat buf;
322       if (lstat(iter->path().c_str(), &buf) == -1) {
323         LOG(ERROR) << "lstat() failed for: " << iter->path();
324         return -1;
325       }
326       size += RoundUpToBlockSizeOf(buf.st_size, block_size);
327   }
328
329   // FIXME: block size for external device may differ...
330   return size;
331 }
332
333 boost::filesystem::path GenerateTmpDir(const bf::path &app_path) {
334   boost::filesystem::path install_tmp_dir;
335   boost::filesystem::path tmp_dir(app_path);
336
337   do {
338     boost::filesystem::path model;
339     boost::filesystem::path unique_dir =
340         boost::filesystem::unique_path(model = "unpack-%%%%%%");
341
342     install_tmp_dir = tmp_dir /= unique_dir;
343   } while (boost::filesystem::exists(install_tmp_dir) &&
344            boost::filesystem::is_directory(install_tmp_dir));
345
346   return install_tmp_dir;
347 }
348
349 boost::filesystem::path GenerateTemporaryPath(
350     const boost::filesystem::path& path) {
351   bf::path pattern = path;
352   pattern += "-%%%%%%";
353   bf::path tmp_path;
354   do {
355     tmp_path = boost::filesystem::unique_path(pattern);
356   } while (boost::filesystem::exists(tmp_path));
357   return tmp_path;
358 }
359
360 bool ExtractToTmpDir(const char* zip_path,
361                      const boost::filesystem::path& tmp_dir) {
362   return ExtractToTmpDir(zip_path, tmp_dir, "");
363 }
364
365 bool ExtractToTmpDir(const char* zip_path, const bf::path& tmp_dir,
366                      const std::string& filter_prefix) {
367   unz_global_info info;
368   char read_buffer[kZipBufSize];
369   unz_file_info raw_file_info;
370   char raw_file_name_in_zip[kZipMaxPath];
371
372   current_path(tmp_dir);
373
374   UnzFilePointer zip_file;
375   if (!zip_file.Open(zip_path)) {
376     LOG(ERROR) << "Failed to open the source dir: " << zip_path;
377     return false;
378   }
379
380   if (unzGetGlobalInfo(zip_file.Get(), &info) != UNZ_OK) {
381     LOG(ERROR) << "Failed to read global info";
382     return false;
383   }
384
385   for (uLong i = 0; i < info.number_entry; i++) {
386     if (unzGetCurrentFileInfo(zip_file.Get(),
387                               &raw_file_info,
388                               raw_file_name_in_zip,
389                               sizeof(raw_file_name_in_zip),
390                               nullptr,
391                               0,
392                               nullptr,
393                               0) != UNZ_OK) {
394       LOG(ERROR) << "Failed to read file info";
395       return false;
396     }
397
398     if (raw_file_name_in_zip[0] == '\0')
399       return false;
400
401     // unpack if filter is empty or path is matched
402     if (filter_prefix.empty() ||
403         std::string(raw_file_name_in_zip).find(filter_prefix) == 0) {
404       bf::path filename_in_zip_path(raw_file_name_in_zip);
405
406       // prevent "directory climbing" attack
407       if (HasDirectoryClimbing(filename_in_zip_path)) {
408         LOG(ERROR) << "Relative path in widget in malformed";
409         return false;
410       }
411
412       if (!filename_in_zip_path.parent_path().empty()) {
413         if (!CreateDir(filename_in_zip_path.parent_path())) {
414           LOG(ERROR) << "Failed to create directory: "
415               << filename_in_zip_path.parent_path();
416           return false;
417         }
418       }
419
420       if (!zip_file.OpenCurrent()) {
421         LOG(ERROR) << "Failed to open file";
422         return false;
423       }
424
425       if (!is_directory(filename_in_zip_path)) {
426         FILE *out = fopen(raw_file_name_in_zip, "wb");
427         if (!out) {
428           LOG(ERROR) << "Failed to open destination ";
429           return false;
430         }
431
432         int ret = UNZ_OK;
433         do {
434           ret = unzReadCurrentFile(zip_file.Get(), read_buffer, kZipBufSize);
435           if (ret < 0) {
436             LOG(ERROR) << "Failed to read data: " << ret;
437             return false;
438           } else {
439             fwrite(read_buffer, sizeof(char), ret, out);
440           }
441         } while (ret > 0);
442
443         fclose(out);
444       }
445
446       zip_file.CloseCurrent();
447     }
448
449     if ((i+1) < info.number_entry) {
450       if (unzGoToNextFile(zip_file.Get()) != UNZ_OK) {
451         LOG(ERROR) << "Failed to read next file";
452         return false;
453       }
454     }
455   }
456
457   return true;
458 }
459
460 bool CheckPathInZipArchive(const char* zip_archive_path,
461                            const boost::filesystem::path& relative_zip_path,
462                            bool* found) {
463   *found = false;
464   UnzFilePointer zip_file;
465   if (!zip_file.Open(zip_archive_path)) {
466     LOG(ERROR) << "Failed to open the source dir: " << zip_archive_path;
467     return false;
468   }
469
470   unz_global_info info;
471   if (unzGetGlobalInfo(zip_file.Get(), &info) != UNZ_OK) {
472     LOG(ERROR) << "Failed to read global info";
473     return false;
474   }
475
476   char raw_file_name_in_zip[kZipMaxPath];
477   unz_file_info raw_file_info;
478   for (uLong i = 0; i < info.number_entry; i++) {
479     if (unzGetCurrentFileInfo(zip_file.Get(),
480                               &raw_file_info,
481                               raw_file_name_in_zip,
482                               sizeof(raw_file_name_in_zip),
483                               nullptr,
484                               0,
485                               nullptr,
486                               0) != UNZ_OK) {
487       LOG(ERROR) << "Failed to read file info";
488       return false;
489     }
490
491     if (raw_file_name_in_zip[0] == '\0')
492       return false;
493
494     if (relative_zip_path.string() == raw_file_name_in_zip) {
495       *found = true;
496       return true;
497     }
498
499     if ((i + 1) < info.number_entry) {
500       if (unzGoToNextFile(zip_file.Get()) != UNZ_OK) {
501         LOG(ERROR) << "Failed to read next file";
502         return false;
503       }
504     }
505   }
506
507   return true;
508 }
509
510 bool HasDirectoryClimbing(const boost::filesystem::path& path) {
511   std::vector<std::string> segments;
512   ba::split(segments, path.string(), ba::is_any_of("/\\"));
513   return std::any_of(segments.begin(), segments.end(),
514                   [](const std::string& segment) {
515                     return segment == "..";
516                   });
517 }
518
519 boost::filesystem::path MakeRelativePath(const boost::filesystem::path& input,
520                                          const boost::filesystem::path& base) {
521   bf::path input_absolute = bf::absolute(input);
522   bf::path base_absolute = bf::absolute(base);
523   return input_absolute.string().substr(base_absolute.string().length() + 1);
524 }
525
526 bool IsSubDir(const boost::filesystem::path& path,
527     const boost::filesystem::path& root) {
528   boost::filesystem::path p = path;
529   while (p != boost::filesystem::path()) {
530     if (bf::equivalent(p, root))
531       return true;
532     else
533       p = p.parent_path();
534   }
535   return false;
536 }
537
538 }  // namespace common_installer